8496 lines
265 KiB
TypeScript
8496 lines
265 KiB
TypeScript
/**
|
||
* 100 Days of IoT — example projects by Kritish Mohapatra, from
|
||
* https://github.com/kritishmohapatra/100_Days_100_IoT_Projects
|
||
*
|
||
* AUTO-GENERATED by `test/test_100_days/_emit_examples_data.py`.
|
||
* Do not edit by hand. Re-run the generator after pulling new upstream
|
||
* projects:
|
||
*
|
||
* python test/test_100_days/_emit_examples_data.py
|
||
*
|
||
* Only the projects Velxio can run end-to-end are emitted (49/78 at the
|
||
* time of generation — see test/test_100_days/README.md for the full
|
||
* compatibility matrix). Each example is a MicroPython project; the
|
||
* editor switches into MicroPython mode automatically when one of these
|
||
* is loaded (see utils/loadExample.ts).
|
||
*/
|
||
|
||
import type { ExampleProject } from './examples';
|
||
|
||
export const hundredDaysExamples: ExampleProject[] = [
|
||
{
|
||
id: "100d-aqi-esp",
|
||
title: "AQI ESP",
|
||
description: "AQI ESP on ESP32 (MicroPython) \u2014 uses DHT, HTTP server, OLED, Wi-Fi. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "displays",
|
||
difficulty: "intermediate",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "app.py", content: `from flask import Flask, request, jsonify
|
||
|
||
app = Flask(__name__)
|
||
|
||
@app.route("/predict", methods=["POST"])
|
||
def predict():
|
||
data = request.get_json()
|
||
|
||
pm25 = data.get("pm25", 0) * 100
|
||
co = data.get("co", 0) / 1000
|
||
no = data.get("no", 0) / 1000
|
||
nox = data.get("nox", 0) / 1000
|
||
|
||
aqi = round((pm25 + co + no + nox) / 4, 2)
|
||
|
||
if aqi <= 50: status = "Good"
|
||
elif aqi <= 100: status = "Moderate"
|
||
elif aqi <= 200: status = "Unhealthy"
|
||
else: status = "Hazardous"
|
||
|
||
return jsonify({"predicted_aqi": aqi, "status": status})
|
||
|
||
if __name__ == "__main__":
|
||
app.run(host="0.0.0.0", port=5000, debug=True)` },
|
||
{ name: "main.py", content: `import network
|
||
import urequests
|
||
import time
|
||
from machine import Pin, ADC, I2C
|
||
import dht
|
||
import ssd1306
|
||
|
||
# ================= WIFI =================
|
||
SSID = "kritish"
|
||
PASSWORD = "@Kpass"
|
||
SERVER_URL = "http://ip:5000/predict"
|
||
|
||
wifi = network.WLAN(network.STA_IF)
|
||
wifi.active(True)
|
||
wifi.connect(SSID, PASSWORD)
|
||
|
||
while not wifi.isconnected():
|
||
time.sleep(1)
|
||
|
||
print("WiFi Connected:", wifi.ifconfig())
|
||
|
||
# ================= OLED =================
|
||
i2c = I2C(0, scl=Pin(22), sda=Pin(21))
|
||
oled = ssd1306.SSD1306_I2C(128, 64, i2c)
|
||
|
||
# ================= DHT =================
|
||
# Velxio's simulator emulates the DHT22 waveform, so use dht.DHT22 here.
|
||
# On real hardware with a DHT11, switch back to dht.DHT11.
|
||
dht_sensor = dht.DHT22(Pin(27))
|
||
last_temp = 0
|
||
last_hum = 0
|
||
|
||
# ================= SENSORS =================
|
||
mq135 = ADC(Pin(34)) # NO / NOx (estimated)
|
||
mq7 = ADC(Pin(33)) # CO
|
||
pm_adc = ADC(Pin(35))
|
||
pm_led = Pin(4, Pin.OUT)
|
||
|
||
for adc in [mq135, mq7, pm_adc]:
|
||
adc.atten(ADC.ATTN_11DB)
|
||
adc.width(ADC.WIDTH_12BIT)
|
||
|
||
# ================= FUNCTIONS =================
|
||
def read_avg(adc, n=10):
|
||
return sum(adc.read() for _ in range(n)) // n
|
||
|
||
def read_pm25():
|
||
pm_led.off()
|
||
time.sleep_us(280)
|
||
val = pm_adc.read()
|
||
time.sleep_us(40)
|
||
pm_led.on()
|
||
time.sleep_us(9680)
|
||
return round(val * (3.3 / 4095), 3)
|
||
|
||
# ================= MAIN LOOP =================
|
||
print("System Initializing...")
|
||
time.sleep(5)
|
||
print("System Ready")
|
||
|
||
while True:
|
||
# ---- DHT11 (LOCAL DISPLAY ONLY) ----
|
||
try:
|
||
dht_sensor.measure()
|
||
last_temp = dht_sensor.temperature()
|
||
last_hum = dht_sensor.humidity()
|
||
except:
|
||
pass # keep last valid value
|
||
|
||
payload = {
|
||
"no": read_avg(mq135),
|
||
"co": read_avg(mq7),
|
||
"nox": read_avg(mq135),
|
||
"pm25": read_pm25()
|
||
}
|
||
|
||
try:
|
||
response = urequests.post(SERVER_URL, json=payload)
|
||
result = response.json()
|
||
response.close()
|
||
|
||
aqi = round(result["predicted_aqi"], 2)
|
||
status = result["status"]
|
||
except Exception as e:
|
||
# No prediction server reachable (SERVER_URL is a placeholder in the
|
||
# simulator) — fall back to the same formula app.py applies, locally.
|
||
pm25 = payload["pm25"] * 100
|
||
co = payload["co"] / 1000
|
||
no = payload["no"] / 1000
|
||
nox = payload["nox"] / 1000
|
||
aqi = round((pm25 + co + no + nox) / 4, 2)
|
||
if aqi <= 50:
|
||
status = "Good"
|
||
elif aqi <= 100:
|
||
status = "Moderate"
|
||
elif aqi <= 200:
|
||
status = "Unhealthy"
|
||
else:
|
||
status = "Hazardous"
|
||
|
||
# ---- OLED DISPLAY ----
|
||
oled.fill(0)
|
||
oled.text("AQI MONITOR", 0, 0)
|
||
oled.text("AQI: {}".format(aqi), 0, 14)
|
||
oled.text(status, 0, 26)
|
||
oled.text("Temp: {} C".format(last_temp), 0, 42)
|
||
oled.text("Hum : {} %".format(last_hum), 0, 54)
|
||
oled.show()
|
||
|
||
print("Sent:", payload)
|
||
print("AQI:", aqi, status)
|
||
|
||
time.sleep(5)
|
||
|
||
|
||
` },
|
||
{ name: "ssd1306.py", content: `# MicroPython SSD1306 OLED driver, I2C and SPI interfaces
|
||
# MIT License; Copyright (c) 2016 Damien P. George (upstream micropython/drivers)
|
||
# Shipped with this example because the ESP32 firmware does not freeze it in.
|
||
|
||
from micropython import const
|
||
import framebuf
|
||
|
||
# register definitions
|
||
SET_CONTRAST = const(0x81)
|
||
SET_ENTIRE_ON = const(0xA4)
|
||
SET_NORM_INV = const(0xA6)
|
||
SET_DISP = const(0xAE)
|
||
SET_MEM_ADDR = const(0x20)
|
||
SET_COL_ADDR = const(0x21)
|
||
SET_PAGE_ADDR = const(0x22)
|
||
SET_DISP_START_LINE = const(0x40)
|
||
SET_SEG_REMAP = const(0xA0)
|
||
SET_MUX_RATIO = const(0xA8)
|
||
SET_COM_OUT_DIR = const(0xC0)
|
||
SET_DISP_OFFSET = const(0xD3)
|
||
SET_COM_PIN_CFG = const(0xDA)
|
||
SET_DISP_CLK_DIV = const(0xD5)
|
||
SET_PRECHARGE = const(0xD9)
|
||
SET_VCOM_DESEL = const(0xDB)
|
||
SET_CHARGE_PUMP = const(0x8D)
|
||
|
||
|
||
class SSD1306(framebuf.FrameBuffer):
|
||
def __init__(self, width, height, external_vcc):
|
||
self.width = width
|
||
self.height = height
|
||
self.external_vcc = external_vcc
|
||
self.pages = self.height // 8
|
||
self.buffer = bytearray(self.pages * self.width)
|
||
super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB)
|
||
self.init_display()
|
||
|
||
def init_display(self):
|
||
for cmd in (
|
||
SET_DISP | 0x00, # off
|
||
# address setting
|
||
SET_MEM_ADDR,
|
||
0x00, # horizontal
|
||
# resolution and layout
|
||
SET_DISP_START_LINE | 0x00,
|
||
SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0
|
||
SET_MUX_RATIO,
|
||
self.height - 1,
|
||
SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0
|
||
SET_DISP_OFFSET,
|
||
0x00,
|
||
SET_COM_PIN_CFG,
|
||
0x02 if self.width > 2 * self.height else 0x12,
|
||
# timing and driving scheme
|
||
SET_DISP_CLK_DIV,
|
||
0x80,
|
||
SET_PRECHARGE,
|
||
0x22 if self.external_vcc else 0xF1,
|
||
SET_VCOM_DESEL,
|
||
0x30, # 0.83*Vcc
|
||
# display
|
||
SET_CONTRAST,
|
||
0xFF, # maximum
|
||
SET_ENTIRE_ON, # output follows RAM contents
|
||
SET_NORM_INV, # not inverted
|
||
# charge pump
|
||
SET_CHARGE_PUMP,
|
||
0x10 if self.external_vcc else 0x14,
|
||
SET_DISP | 0x01,
|
||
): # on
|
||
self.write_cmd(cmd)
|
||
self.fill(0)
|
||
self.show()
|
||
|
||
def poweroff(self):
|
||
self.write_cmd(SET_DISP | 0x00)
|
||
|
||
def poweron(self):
|
||
self.write_cmd(SET_DISP | 0x01)
|
||
|
||
def contrast(self, contrast):
|
||
self.write_cmd(SET_CONTRAST)
|
||
self.write_cmd(contrast)
|
||
|
||
def invert(self, invert):
|
||
self.write_cmd(SET_NORM_INV | (invert & 1))
|
||
|
||
def show(self):
|
||
x0 = 0
|
||
x1 = self.width - 1
|
||
if self.width == 64:
|
||
# displays with width of 64 pixels are shifted by 32
|
||
x0 += 32
|
||
x1 += 32
|
||
self.write_cmd(SET_COL_ADDR)
|
||
self.write_cmd(x0)
|
||
self.write_cmd(x1)
|
||
self.write_cmd(SET_PAGE_ADDR)
|
||
self.write_cmd(0)
|
||
self.write_cmd(self.pages - 1)
|
||
self.write_data(self.buffer)
|
||
|
||
|
||
class SSD1306_I2C(SSD1306):
|
||
def __init__(self, width, height, i2c, addr=0x3C, external_vcc=False):
|
||
self.i2c = i2c
|
||
self.addr = addr
|
||
self.temp = bytearray(2)
|
||
self.write_list = [b"\\x40", None] # Co=0, D/C#=1
|
||
super().__init__(width, height, external_vcc)
|
||
|
||
def write_cmd(self, cmd):
|
||
self.temp[0] = 0x80 # Co=1, D/C#=0
|
||
self.temp[1] = cmd
|
||
self.i2c.writeto(self.addr, self.temp)
|
||
|
||
def write_data(self, buf):
|
||
self.write_list[1] = buf
|
||
self.i2c.writevto(self.addr, self.write_list)
|
||
|
||
|
||
class SSD1306_SPI(SSD1306):
|
||
def __init__(self, width, height, spi, dc, res, cs, external_vcc=False):
|
||
self.rate = 10 * 1024 * 1024
|
||
dc.init(dc.OUT, value=0)
|
||
res.init(res.OUT, value=0)
|
||
cs.init(cs.OUT, value=1)
|
||
self.spi = spi
|
||
self.dc = dc
|
||
self.res = res
|
||
self.cs = cs
|
||
import time
|
||
|
||
self.res(1)
|
||
time.sleep_ms(1)
|
||
self.res(0)
|
||
time.sleep_ms(10)
|
||
self.res(1)
|
||
super().__init__(width, height, external_vcc)
|
||
|
||
def write_cmd(self, cmd):
|
||
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
|
||
self.cs(1)
|
||
self.dc(0)
|
||
self.cs(0)
|
||
self.spi.write(bytearray([cmd]))
|
||
self.cs(1)
|
||
|
||
def write_data(self, buf):
|
||
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
|
||
self.cs(1)
|
||
self.dc(1)
|
||
self.cs(0)
|
||
self.spi.write(buf)
|
||
self.cs(1)
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [{"type": "wokwi-ssd1306", "id": "aqi-oled1", "x": 420, "y": 60, "properties": {}}, {"type": "wokwi-dht22", "id": "aqi-dht1", "x": 440, "y": 260, "properties": {"temperature": "28", "humidity": "65"}}],
|
||
wires: [{"id": "aqi-oled-vcc", "start": {"componentId": "arduino-uno", "pinName": "3V3"}, "end": {"componentId": "aqi-oled1", "pinName": "3V3"}, "color": "#ff4444"}, {"id": "aqi-oled-gnd", "start": {"componentId": "arduino-uno", "pinName": "GND"}, "end": {"componentId": "aqi-oled1", "pinName": "GND"}, "color": "#000000"}, {"id": "aqi-oled-sda", "start": {"componentId": "arduino-uno", "pinName": "21"}, "end": {"componentId": "aqi-oled1", "pinName": "DATA"}, "color": "#22aaff"}, {"id": "aqi-oled-scl", "start": {"componentId": "arduino-uno", "pinName": "22"}, "end": {"componentId": "aqi-oled1", "pinName": "CLK"}, "color": "#ff8800"}, {"id": "aqi-dht-vcc", "start": {"componentId": "arduino-uno", "pinName": "3V3"}, "end": {"componentId": "aqi-dht1", "pinName": "VCC"}, "color": "#ff4444"}, {"id": "aqi-dht-gnd", "start": {"componentId": "arduino-uno", "pinName": "GND"}, "end": {"componentId": "aqi-dht1", "pinName": "GND"}, "color": "#000000"}, {"id": "aqi-dht-data", "start": {"componentId": "arduino-uno", "pinName": "27"}, "end": {"componentId": "aqi-dht1", "pinName": "SDA"}, "color": "#22cc66"}],
|
||
tags: ["100-days", "dht", "esp32", "http_server", "i2c_oled", "micropython", "wifi"],
|
||
},
|
||
{
|
||
id: "100d-auto-night-light-using-ldr-esp32-plus-micropython",
|
||
title: "Auto Night Light using LDR (ESP32 + MicroPython)",
|
||
description: "Auto Night Light using LDR (ESP32 + MicroPython) on ESP32 (MicroPython) \u2014 uses LDR. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "sensors",
|
||
difficulty: "beginner",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "main.py", content: `from machine import Pin, ADC
|
||
from time import sleep
|
||
|
||
# Initialize LDR on ADC pin 34
|
||
ldr = ADC(Pin(34))
|
||
ldr.atten(ADC.ATTN_11DB) # full range 0-3.3V
|
||
|
||
# Initialize LED on GPIO2
|
||
led = Pin(2, Pin.OUT)
|
||
|
||
# Threshold for turning LED on/off
|
||
threshold = 500
|
||
|
||
while True:
|
||
light = ldr.read() # Read LDR value
|
||
print(light)
|
||
|
||
if light < threshold: # It's dark
|
||
led.on() # Turn LED ON
|
||
else: # It's bright
|
||
led.off() # Turn LED OFF
|
||
|
||
sleep(1)
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "esp32", "ldr", "micropython"],
|
||
},
|
||
{
|
||
id: "100d-battery-monitor-with-blynk-iot",
|
||
title: "Battery Monitor with Blynk IoT",
|
||
description: "Battery Monitor with Blynk IoT on ESP32 (MicroPython) \u2014 uses Blynk, Blynk Cloud, Wi-Fi. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "communication",
|
||
difficulty: "advanced",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "BlynkLib.py", content: `# 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()
|
||
# Velxio patch: bound the blocking connect so an unreachable cloud
|
||
# raises OSError instead of freezing the sketch forever.
|
||
s.settimeout(10)
|
||
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)
|
||
` },
|
||
{ name: "main.py", content: `# Copyright (c) 2026 Kritish Mohapatra
|
||
|
||
import network
|
||
from machine import ADC, Pin
|
||
import BlynkLib
|
||
from time import sleep
|
||
|
||
# WiFi
|
||
WIFI_SSID = "kritish"
|
||
WIFI_PASS = "pass"
|
||
|
||
# Blynk
|
||
AUTH = "jXJlUYM2K8a5uA2c"
|
||
|
||
# ADC
|
||
adc = ADC(0)
|
||
RATIO = 0.863
|
||
|
||
BATTERY_TABLE = [
|
||
(4.20, 100), (4.10, 90), (4.00, 80),
|
||
(3.90, 70), (3.80, 60), (3.70, 45),
|
||
(3.60, 30), (3.50, 15), (3.40, 5),
|
||
(2.80, 0)
|
||
]
|
||
|
||
def read_voltage(samples=20):
|
||
raw = sum(adc.read() for _ in range(samples)) / samples
|
||
v_a0 = (raw / 1023) * 3.3
|
||
v_bat = (v_a0 * 2) * RATIO
|
||
return round(v_bat, 2)
|
||
|
||
def volt_to_pct(v):
|
||
if v >= 4.20: return 100
|
||
if v <= 2.80: return 0
|
||
for i in range(len(BATTERY_TABLE) - 1):
|
||
v_high, pct_high = BATTERY_TABLE[i]
|
||
v_low, pct_low = BATTERY_TABLE[i + 1]
|
||
if v >= v_low:
|
||
ratio = (v - v_low) / (v_high - v_low)
|
||
return int(pct_low + ratio * (pct_high - pct_low))
|
||
return 0
|
||
|
||
# WiFi Connect
|
||
sta_if = network.WLAN(network.STA_IF)
|
||
sta_if.active(True)
|
||
sta_if.connect(WIFI_SSID, WIFI_PASS)
|
||
while not sta_if.isconnected():
|
||
pass
|
||
print("WiFi Connected:", sta_if.ifconfig())
|
||
|
||
# Blynk Init
|
||
# ── Velxio adaptation ──────────────────────────────────────────────────────────────────
|
||
# The vendored BlynkLib bounds its connect() with a timeout (Velxio patch),
|
||
# so an unreachable cloud raises OSError here instead of freezing the demo;
|
||
# fall back to an offline no-op Blynk object in that case.
|
||
class _OfflineBlynk:
|
||
def on(self, *_a, **_k):
|
||
def _deco(f):
|
||
return f
|
||
return _deco
|
||
def run(self):
|
||
pass
|
||
def virtual_write(self, *_a):
|
||
pass
|
||
|
||
|
||
try:
|
||
blynk = BlynkLib.Blynk(AUTH, insecure=True)
|
||
except OSError as e:
|
||
print("Blynk Cloud unreachable, running offline:", e)
|
||
blynk = _OfflineBlynk()
|
||
|
||
# Main Loop
|
||
timer = 0
|
||
while True:
|
||
blynk.run()
|
||
timer += 1
|
||
if timer >= 3000:
|
||
timer = 0
|
||
v = read_voltage()
|
||
pct = volt_to_pct(v)
|
||
blynk.virtual_write(0, v) # V0 = Voltage
|
||
blynk.virtual_write(1, pct) # V1 = Battery %
|
||
print(f"Sent → {v}V | {pct}%")
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "blynk", "blynk_cloud", "esp32", "micropython", "wifi"],
|
||
},
|
||
{
|
||
id: "100d-bluetooth-based-wireless-led-control-system",
|
||
title: "Bluetooth Based Wireless LED Control System",
|
||
description: "Bluetooth Based Wireless LED Control System on ESP32 (MicroPython) \u2014 uses BLE. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "communication",
|
||
difficulty: "advanced",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "main.py", content: `from machine import Pin, UART
|
||
|
||
uart = UART(0, 9600)
|
||
led = Pin("LED", Pin.OUT)
|
||
|
||
print("Bluetooth Control Ready")
|
||
|
||
while True:
|
||
if uart.any() > 0:
|
||
data = uart.read().decode().strip() # decode bytes → string
|
||
print("Received:", data)
|
||
|
||
if "on" in data.lower():
|
||
led.value(1)
|
||
print("LED ON")
|
||
uart.write("LED ON\\n")
|
||
|
||
elif "off" in data.lower():
|
||
led.value(0)
|
||
print("LED OFF")
|
||
uart.write("LED OFF\\n")
|
||
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "ble", "esp32", "micropython"],
|
||
},
|
||
{
|
||
id: "100d-blynk-based-iot-relay-control-micropython",
|
||
title: "Blynk Based IoT Relay Control (MicroPython)",
|
||
description: "Blynk Based IoT Relay Control (MicroPython) on Raspberry Pi Pico W (MicroPython) \u2014 uses Blynk, Blynk Cloud, Wi-Fi. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "communication",
|
||
difficulty: "advanced",
|
||
boardType: "pi-pico-w",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "BlynkLib.py", content: `# 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()
|
||
# Velxio patch: bound the blocking connect so an unreachable cloud
|
||
# raises OSError instead of freezing the sketch forever.
|
||
s.settimeout(10)
|
||
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)
|
||
` },
|
||
{ name: "main.py", content: `'''Copyright (c) 2025 Kritish Mohapatra'''
|
||
|
||
import network
|
||
from machine import Pin
|
||
import BlynkLib
|
||
from time import sleep
|
||
|
||
# -------- WIFI DETAILS --------
|
||
wifissid = "ssid" # Your Wi-Fi SSID
|
||
wifipass = "pass" # Your Wi-Fi password
|
||
|
||
# Blynk Authorization Token (replace with your Blynk app's token)
|
||
auth = "gmETsu4VJeGAjNaoV006ytWOUA"
|
||
# -------- RELAY / LED --------
|
||
relay = Pin(2, Pin.OUT) # GP15 (Best for Pico 2 W)
|
||
relay.value(1) # OFF (Active LOW)
|
||
|
||
# -------- WIFI CONNECT --------
|
||
sta_if = network.WLAN(network.STA_IF)
|
||
sta_if.active(True)
|
||
sta_if.connect(wifissid, wifipass)
|
||
|
||
while not sta_if.isconnected():
|
||
sleep(1)
|
||
|
||
print("WiFi Connected:", sta_if.ifconfig())
|
||
|
||
# -------- BLYNK INIT --------
|
||
# ── Velxio adaptation ──────────────────────────────────────────────────────────────────
|
||
# The vendored BlynkLib bounds its connect() with a timeout (Velxio patch),
|
||
# so an unreachable cloud raises OSError here instead of freezing the demo;
|
||
# fall back to an offline no-op Blynk object in that case.
|
||
class _OfflineBlynk:
|
||
def on(self, *_a, **_k):
|
||
def _deco(f):
|
||
return f
|
||
return _deco
|
||
def run(self):
|
||
pass
|
||
def virtual_write(self, *_a):
|
||
pass
|
||
|
||
|
||
try:
|
||
blynk = BlynkLib.Blynk(auth, insecure=True)
|
||
except OSError as e:
|
||
print("Blynk Cloud unreachable, running offline:", e)
|
||
blynk = _OfflineBlynk()
|
||
|
||
# -------- VIRTUAL PIN V1 --------
|
||
@blynk.on("V1")
|
||
def v1_handler(value):
|
||
try:
|
||
relay.value(not int(value[0]))
|
||
print("Relay State:", value[0])
|
||
except:
|
||
print("Invalid data")
|
||
|
||
# -------- MAIN LOOP --------
|
||
while True:
|
||
blynk.run()
|
||
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "blynk", "blynk_cloud", "micropython", "pi-pico-w", "pico-w", "wifi"],
|
||
},
|
||
{
|
||
id: "100d-blynk-controlled-dc-brushless-fan",
|
||
title: "Blynk Controlled DC Brushless Fan",
|
||
description: "Blynk Controlled DC Brushless Fan on ESP32 (MicroPython) \u2014 uses Blynk, Blynk Cloud, Wi-Fi. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "communication",
|
||
difficulty: "advanced",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "BlynkLib.py", content: `# 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()
|
||
# Velxio patch: bound the blocking connect so an unreachable cloud
|
||
# raises OSError instead of freezing the sketch forever.
|
||
s.settimeout(10)
|
||
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)
|
||
` },
|
||
{ name: "main.py", content: `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 --------
|
||
# ── Velxio adaptation ──────────────────────────────────────────────────────────────────
|
||
# The vendored BlynkLib bounds its connect() with a timeout (Velxio patch),
|
||
# so an unreachable cloud raises OSError here instead of freezing the demo;
|
||
# fall back to an offline no-op Blynk object in that case.
|
||
class _OfflineBlynk:
|
||
def on(self, *_a, **_k):
|
||
def _deco(f):
|
||
return f
|
||
return _deco
|
||
def run(self):
|
||
pass
|
||
def virtual_write(self, *_a):
|
||
pass
|
||
|
||
|
||
try:
|
||
blynk = BlynkLib.Blynk(auth, insecure=True)
|
||
except OSError as e:
|
||
print("Blynk Cloud unreachable, running offline:", e)
|
||
blynk = _OfflineBlynk()
|
||
|
||
# -------- 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()
|
||
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "blynk", "blynk_cloud", "esp32", "micropython", "wifi"],
|
||
},
|
||
{
|
||
id: "100d-clap-toggle-switch-using-esp32-and-digital-sound-sensor-micropython",
|
||
title: "Clap Toggle Switch using ESP32 & Digital Sound Sensor (MicroPython)",
|
||
description: "Clap Toggle Switch using ESP32 & Digital Sound Sensor (MicroPython) on ESP32 (MicroPython). From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "sensors",
|
||
difficulty: "beginner",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "main.py", content: `from machine import Pin
|
||
import time
|
||
|
||
sound = Pin(15, Pin.IN)
|
||
led = Pin(2, Pin.OUT)
|
||
|
||
state = 0 # 0 = OFF, 1 = ON
|
||
last_time = 0
|
||
debounce = 500 # ms (clap gap)
|
||
|
||
while True:
|
||
if sound.value() == 1:
|
||
current_time = time.ticks_ms()
|
||
|
||
# debounce check
|
||
if time.ticks_diff(current_time, last_time) > debounce:
|
||
state = not state # TOGGLE
|
||
led.value(state)
|
||
print("Clap! LED:", "ON" if state else "OFF")
|
||
last_time = current_time
|
||
|
||
# wait until sound goes low
|
||
while sound.value() == 1:
|
||
pass
|
||
|
||
time.sleep_ms(20)
|
||
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "esp32", "micropython"],
|
||
},
|
||
{
|
||
id: "100d-dc-motor-speed-control-web-slider",
|
||
title: "DC Motor Speed Control (Web Slider)",
|
||
description: "DC Motor Speed Control (Web Slider) on ESP32 (MicroPython) \u2014 uses Servo, Wi-Fi. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "robotics",
|
||
difficulty: "intermediate",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "main.py", content: `import network
|
||
import socket
|
||
from machine import Pin, PWM
|
||
from time import sleep_ms
|
||
|
||
# -------- WiFi --------
|
||
ssid = "kritish"
|
||
password = "pass"
|
||
|
||
wlan = network.WLAN(network.STA_IF)
|
||
wlan.active(True)
|
||
wlan.connect(ssid, password)
|
||
|
||
while not wlan.isconnected():
|
||
pass
|
||
|
||
print("Open:", wlan.ifconfig()[0])
|
||
|
||
# -------- Motor --------
|
||
ENA = PWM(Pin(13), freq=1000)
|
||
IN1 = Pin(12, Pin.OUT)
|
||
IN2 = Pin(14, Pin.OUT)
|
||
|
||
IN1.on()
|
||
IN2.off()
|
||
ENA.duty(0)
|
||
|
||
def set_motor(speed):
|
||
speed = int(speed)
|
||
|
||
if speed <= 0:
|
||
ENA.duty(0)
|
||
return
|
||
|
||
# kick start for 6V + L298N
|
||
ENA.duty(1023)
|
||
sleep_ms(150)
|
||
ENA.duty(speed)
|
||
|
||
# -------- HTML --------
|
||
html = """<!DOCTYPE html>
|
||
<html>
|
||
<body>
|
||
<h2>ESP32 DC Motor Control</h2>
|
||
<input type="range" min="0" max="1023" value="0"
|
||
oninput="send(this.value)">
|
||
<p>Speed: <span id="v">0</span></p>
|
||
|
||
<script>
|
||
function send(val){
|
||
document.getElementById("v").innerHTML = val;
|
||
fetch("/set?speed=" + val);
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|
||
"""
|
||
|
||
# -------- Server --------
|
||
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
|
||
s = socket.socket()
|
||
s.bind(addr)
|
||
s.listen(1)
|
||
|
||
print("Server running")
|
||
|
||
while True:
|
||
cl, addr = s.accept()
|
||
req = cl.recv(1024).decode()
|
||
|
||
if "/set?speed=" in req:
|
||
try:
|
||
part = req.split("/set?speed=")[1]
|
||
spd = part.split(" ")[0]
|
||
set_motor(spd)
|
||
print("Speed set:", spd)
|
||
except:
|
||
pass
|
||
|
||
cl.send("HTTP/1.1 200 OK\\r\\n\\r\\nOK")
|
||
cl.close()
|
||
continue
|
||
|
||
cl.send("HTTP/1.1 200 OK\\r\\nContent-Type: text/html\\r\\n\\r\\n")
|
||
cl.send(html)
|
||
cl.close()
|
||
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "esp32", "micropython", "servo", "wifi"],
|
||
},
|
||
{
|
||
id: "100d-dht11-web-server-using-esp32-and-micropython",
|
||
title: "DHT11 Web Server using ESP32 & MicroPython",
|
||
description: "DHT11 Web Server using ESP32 & MicroPython on ESP32 (MicroPython) \u2014 uses DHT, Wi-Fi. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "communication",
|
||
difficulty: "intermediate",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "main.py", content: `import machine
|
||
import network
|
||
import time
|
||
import socket
|
||
import dht
|
||
|
||
# DHT11 sensor setup
|
||
sensor = dht.DHT22(machine.Pin(4)) # GPIO4 — Velxio emulates the DHT22 waveform (use DHT11 on real hw)
|
||
|
||
# Wi-Fi credentials
|
||
SSID = "kritish"
|
||
PASSWORD = "@Krrs2069"
|
||
|
||
# Wi-Fi connection
|
||
wifi = network.WLAN(network.STA_IF)
|
||
wifi.active(True)
|
||
if not wifi.isconnected():
|
||
print("Connecting to WiFi...", end="")
|
||
wifi.connect(SSID, PASSWORD)
|
||
while not wifi.isconnected():
|
||
time.sleep(1)
|
||
print(".", end="")
|
||
print("\\nConnected to WiFi:", wifi.ifconfig()[0])
|
||
else:
|
||
print("Already connected to WiFi:", wifi.ifconfig()[0])
|
||
|
||
def web_page(temp, hum):
|
||
# Corrected HTML with proper humidity unit and a cleaner f-string format.
|
||
html = f"""<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>ESP32 DHT11 SERVER</title>
|
||
<meta http-equiv="refresh" content="3">
|
||
<style>
|
||
body {{
|
||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||
text-align: center;
|
||
background: linear-gradient(to right, #f6ff00, #a600ff);
|
||
color: white;
|
||
margin: 0;
|
||
padding: 0;
|
||
}}
|
||
h1 {{
|
||
margin-top: 20px;
|
||
font-size: 2.5em;
|
||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
||
}}
|
||
.container {{
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
gap: 20px;
|
||
margin-top: 50px;
|
||
}}
|
||
.card {{
|
||
background: rgba(255, 255, 255, 0.15);
|
||
padding: 30px;
|
||
border-radius: 20px;
|
||
box-shadow: 0 8px 16px rgba(0,0,0,0.3);
|
||
width: 250px;
|
||
transition: transform 0.2s;
|
||
}}
|
||
.card:hover {{
|
||
transform: scale(1.05);
|
||
}}
|
||
.value {{
|
||
font-size: 2em;
|
||
margin-top: 10px;
|
||
font-weight: bold;
|
||
}}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<h1>ESP32 DHT11 WEBSERVER</h1>
|
||
<div class="container">
|
||
<div class="card">
|
||
<h2>🌡 Temperature</h2>
|
||
<div class="value">{temp} °C</div>
|
||
</div>
|
||
<div class="card">
|
||
<h2>💧 Humidity</h2>
|
||
<div class="value">{hum} %</div>
|
||
</div>
|
||
</div>
|
||
<p style="margin-top:40px;">Auto-refreshing every 3 seconds ⏳</p>
|
||
<p class="footer">© 2025 Kritish. Powered by MicroPython.</p>
|
||
</body>
|
||
</html>"""
|
||
return html
|
||
|
||
# Start web server
|
||
addr = socket.getaddrinfo("0.0.0.0", 80)[0][-1]
|
||
server = socket.socket()
|
||
server.bind(addr)
|
||
server.listen(1)
|
||
print("Web server running on:", wifi.ifconfig()[0])
|
||
|
||
while True:
|
||
try:
|
||
# Read sensor
|
||
sensor.measure()
|
||
temp = sensor.temperature()
|
||
hum = sensor.humidity()
|
||
|
||
# Handle client
|
||
client, addr = server.accept()
|
||
print("Client connected from", addr)
|
||
request = client.recv(1024)
|
||
|
||
# Check for valid sensor readings before serving the page
|
||
if isinstance(temp, (int, float)) and isinstance(hum, (int, float)):
|
||
response = web_page(temp, hum)
|
||
else:
|
||
# Handle cases where sensor reading fails
|
||
response = "<h1>Sensor reading failed. Please try again.</h1>"
|
||
|
||
client.send("HTTP/1.1 200 OK\\r\\nContent-Type: text/html\\r\\n\\r\\n")
|
||
client.sendall(response)
|
||
client.close()
|
||
|
||
except OSError as e:
|
||
print("Failed to read sensor or a socket error occurred:", e)
|
||
time.sleep(10) # Wait a bit before trying again
|
||
except Exception as e:
|
||
print("An unexpected error occurred:", e)
|
||
time.sleep(10)` },
|
||
],
|
||
code: '',
|
||
components: [{"type": "wokwi-dht22", "id": "dweb-dht1", "x": 440, "y": 200, "properties": {"temperature": "28", "humidity": "65"}}],
|
||
wires: [{"id": "dweb-dht-vcc", "start": {"componentId": "arduino-uno", "pinName": "3V3"}, "end": {"componentId": "dweb-dht1", "pinName": "VCC"}, "color": "#ff4444"}, {"id": "dweb-dht-gnd", "start": {"componentId": "arduino-uno", "pinName": "GND"}, "end": {"componentId": "dweb-dht1", "pinName": "GND"}, "color": "#000000"}, {"id": "dweb-dht-data", "start": {"componentId": "arduino-uno", "pinName": "4"}, "end": {"componentId": "dweb-dht1", "pinName": "SDA"}, "color": "#22cc66"}],
|
||
tags: ["100-days", "dht", "esp32", "micropython", "wifi"],
|
||
},
|
||
{
|
||
id: "100d-dimmer-led-using-potentiometer-micropython",
|
||
title: "Dimmer LED using Potentiometer (MicroPython)",
|
||
description: "Dimmer LED using Potentiometer (MicroPython) on ESP32 (MicroPython) \u2014 uses Servo. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "robotics",
|
||
difficulty: "beginner",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "main.py", content: `from machine import Pin, ADC, PWM
|
||
from time import sleep
|
||
|
||
pot = ADC(Pin(34)) # Potentiometer
|
||
pot.atten(ADC.ATTN_11DB) # 0 - 3.3V range
|
||
led = PWM(Pin(2)) # LED on GPIO2
|
||
led.freq(1000) # 1 kHz PWM frequency
|
||
|
||
while True:
|
||
val = pot.read() # 0–4095 range
|
||
duty = int((val / 4095) * 1023) # scale to PWM duty
|
||
led.duty(duty)
|
||
print("ADC:", val, "→ PWM:", duty)
|
||
sleep(0.1)
|
||
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "esp32", "micropython", "servo"],
|
||
},
|
||
{
|
||
id: "100d-dual-ir-entry-exit-detector-with-telegram-alerts",
|
||
title: "Dual IR Entry Exit Detector with Telegram Alerts",
|
||
description: "Dual IR Entry Exit Detector with Telegram Alerts on ESP32 (MicroPython) \u2014 uses Telegram, Wi-Fi. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "communication",
|
||
difficulty: "intermediate",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "main.py", content: `import network
|
||
import urequests
|
||
import machine
|
||
import time
|
||
|
||
# ─── WiFi ─────────────────────────────────────────────
|
||
SSID = "kritish"
|
||
PASSWORD = "iot"
|
||
|
||
wifi = network.WLAN(network.STA_IF)
|
||
wifi.active(True)
|
||
wifi.connect(SSID, PASSWORD)
|
||
print("Connecting to WiFi...")
|
||
while not wifi.isconnected():
|
||
time.sleep(1)
|
||
print("WiFi Connected!")
|
||
print(wifi.ifconfig())
|
||
|
||
# ─── Telegram ─────────────────────────────────────────
|
||
BOT_TOKEN = ""
|
||
CHAT_ID = 54 # int only, no quotes
|
||
|
||
def send_telegram(msg):
|
||
msg_encoded = msg.replace(" ", "%20").replace("\\n", "%0A")
|
||
url = (
|
||
"https://api.telegram.org/bot"
|
||
+ BOT_TOKEN
|
||
+ "/sendMessage?chat_id="
|
||
+ str(CHAT_ID)
|
||
+ "&text="
|
||
+ msg_encoded
|
||
)
|
||
try:
|
||
r = urequests.get(url)
|
||
print("Telegram response:", r.text[:60])
|
||
r.close()
|
||
except Exception as e:
|
||
print("Telegram error:", e)
|
||
|
||
# ─── Pins ─────────────────────────────────────────────
|
||
ir_entry = machine.Pin(14, machine.Pin.IN) # Entry IR
|
||
ir_exit = machine.Pin(27, machine.Pin.IN) # Exit IR
|
||
red_led = machine.Pin(26, machine.Pin.OUT) # Red LED
|
||
green_led = machine.Pin(25, machine.Pin.OUT) # Green LED
|
||
|
||
red_led.value(0)
|
||
green_led.value(0)
|
||
|
||
print("System Ready!")
|
||
|
||
# ─── Main Loop ────────────────────────────────────────
|
||
entry_triggered = False
|
||
exit_triggered = False
|
||
|
||
while True:
|
||
entry_val = ir_entry.value()
|
||
exit_val = ir_exit.value()
|
||
|
||
# Entry IR blocked
|
||
if entry_val == 0 and not entry_triggered:
|
||
entry_triggered = True
|
||
exit_triggered = False
|
||
red_led.value(1) # Red ON - object inside
|
||
green_led.value(0)
|
||
print("Object Entered!")
|
||
send_telegram("Object Entered!")
|
||
|
||
# Exit IR blocked
|
||
if exit_val == 0 and not exit_triggered and entry_triggered:
|
||
exit_triggered = True
|
||
entry_triggered = False
|
||
red_led.value(0) # Red OFF
|
||
green_led.value(1) # Green ON - object gone
|
||
print("Object Gone!")
|
||
send_telegram("Object Gone!")
|
||
time.sleep(2) # Green stays for 2 sec
|
||
green_led.value(0)
|
||
|
||
time.sleep(0.1)
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "esp32", "micropython", "telegram", "wifi"],
|
||
},
|
||
{
|
||
id: "100d-eeprom-simulation-using-micropython-on-esp32-wokwi",
|
||
title: "EEPROM Simulation using MicroPython on ESP32 (Wokwi)",
|
||
description: "EEPROM Simulation using MicroPython on ESP32 (Wokwi) on ESP32 (MicroPython). From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "basics",
|
||
difficulty: "beginner",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "main.py", content: `# ===== EEPROM Simulation (File-based) =====
|
||
|
||
EEPROM_FILE = "eeprom.dat"
|
||
EEPROM_SIZE = 512
|
||
|
||
def eeprom_init():
|
||
try:
|
||
with open(EEPROM_FILE, "rb"):
|
||
print("EEPROM exists")
|
||
except:
|
||
print("Creating EEPROM...")
|
||
with open(EEPROM_FILE, "wb") as f:
|
||
f.write(bytearray(EEPROM_SIZE))
|
||
|
||
|
||
def eeprom_read_byte(addr):
|
||
with open(EEPROM_FILE, "rb") as f:
|
||
f.seek(addr)
|
||
return f.read(1)[0]
|
||
|
||
|
||
def eeprom_write_byte(addr, val):
|
||
with open(EEPROM_FILE, "r+b") as f:
|
||
f.seek(addr)
|
||
f.write(bytes([val]))
|
||
|
||
|
||
# ===== String Functions (Arduino Style) =====
|
||
|
||
def save_string(address, text):
|
||
for i, ch in enumerate(text):
|
||
if eeprom_read_byte(address + i) != ord(ch):
|
||
eeprom_write_byte(address + i, ord(ch))
|
||
|
||
# null terminator
|
||
if eeprom_read_byte(address + len(text)) != 0:
|
||
eeprom_write_byte(address + len(text), 0)
|
||
|
||
|
||
def read_string(address):
|
||
result = ""
|
||
while address < EEPROM_SIZE:
|
||
b = eeprom_read_byte(address)
|
||
if b == 0:
|
||
break
|
||
result += chr(b)
|
||
address += 1
|
||
return result
|
||
|
||
|
||
# ===== Main Logic =====
|
||
while True:
|
||
eeprom_init()
|
||
|
||
print("\\nEnter a string to save in EEPROM:")
|
||
text_to_save = input()
|
||
|
||
stored_text = read_string(0)
|
||
|
||
if text_to_save != stored_text:
|
||
save_string(0, text_to_save)
|
||
print("String saved in EEPROM.")
|
||
else:
|
||
print("String is same as previous one.")
|
||
|
||
print("Stored Text in EEPROM:", read_string(0))
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "esp32", "micropython"],
|
||
},
|
||
{
|
||
id: "100d-esp32-ble-led-control",
|
||
title: "ESP32 BLE LED Control",
|
||
description: "ESP32 BLE LED Control on ESP32 (MicroPython) \u2014 uses BLE. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "communication",
|
||
difficulty: "advanced",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "main.py", content: `from machine import Pin, Timer
|
||
import ubluetooth
|
||
from time import sleep_ms
|
||
|
||
message = ""
|
||
led = Pin(2, Pin.OUT) # Normal ESP32 onboard LED
|
||
|
||
class ESP32_BLE():
|
||
def __init__(self, name):
|
||
self.timer = Timer(0)
|
||
self.name = name
|
||
self.is_connected = False
|
||
self.ble = ubluetooth.BLE()
|
||
self.ble.active(True)
|
||
self.disconnected()
|
||
self.ble.irq(self.ble_irq)
|
||
self.register()
|
||
self.advertiser()
|
||
|
||
def connected(self):
|
||
self.is_connected = True
|
||
self.timer.deinit()
|
||
led.value(1) # Solid ON = connected
|
||
|
||
def disconnected(self):
|
||
self.is_connected = False
|
||
led.value(0)
|
||
self.timer.init(period=500, mode=Timer.PERIODIC,
|
||
callback=lambda t: led.value(not led.value())) # Blink = disconnected
|
||
|
||
def ble_irq(self, event, data):
|
||
global message
|
||
if event == 1:
|
||
self.connected()
|
||
elif event == 2:
|
||
self.advertiser()
|
||
self.disconnected()
|
||
elif event == 3:
|
||
buffer = self.ble.gatts_read(self.rx)
|
||
message = buffer.decode('UTF-8').strip()
|
||
print("Received:", message)
|
||
|
||
def register(self):
|
||
NUS_UUID = '6E400001-B5A3-F393-E0A9-E50E24DCCA9E'
|
||
RX_UUID = '6E400002-B5A3-F393-E0A9-E50E24DCCA9E'
|
||
TX_UUID = '6E400003-B5A3-F393-E0A9-E50E24DCCA9E'
|
||
|
||
BLE_NUS = ubluetooth.UUID(NUS_UUID)
|
||
BLE_RX = (ubluetooth.UUID(RX_UUID), ubluetooth.FLAG_WRITE)
|
||
BLE_TX = (ubluetooth.UUID(TX_UUID), ubluetooth.FLAG_NOTIFY)
|
||
|
||
BLE_UART = (BLE_NUS, (BLE_TX, BLE_RX,))
|
||
SERVICES = (BLE_UART,)
|
||
((self.tx, self.rx,),) = self.ble.gatts_register_services(SERVICES)
|
||
|
||
def send(self, data):
|
||
if not self.is_connected:
|
||
print("Not connected!")
|
||
return
|
||
try:
|
||
if isinstance(data, str):
|
||
data = data.encode('UTF-8')
|
||
self.ble.gatts_notify(0, self.tx, data + b'\\n')
|
||
except Exception as e:
|
||
print("Send error:", e)
|
||
|
||
def advertiser(self):
|
||
name = bytes(self.name, 'UTF-8')
|
||
adv_data = bytearray(b'\\x02\\x01\\x02') + bytearray((len(name) + 1, 0x09)) + name
|
||
self.ble.gap_advertise(100, adv_data)
|
||
print("Advertising as:", self.name)
|
||
|
||
ble = ESP32_BLE("ESP32")
|
||
|
||
while True:
|
||
if message == "LED_ON":
|
||
led.value(1)
|
||
print("LED ON")
|
||
ble.send("LED is ON")
|
||
message = ""
|
||
elif message == "LED_OFF":
|
||
led.value(0)
|
||
print("LED OFF")
|
||
ble.send("LED is OFF")
|
||
message = ""
|
||
elif message == "STATUS":
|
||
status = "LED is ON" if led.value() else "LED is OFF"
|
||
print(status)
|
||
ble.send(status)
|
||
message = ""
|
||
sleep_ms(100)
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "ble", "esp32", "micropython"],
|
||
},
|
||
{
|
||
id: "100d-esp32-hotspot-access-point-setup-micropython",
|
||
title: "ESP32 Hotspot (Access Point) Setup MicroPython",
|
||
description: "ESP32 Hotspot (Access Point) Setup MicroPython on ESP32 (MicroPython) \u2014 uses Wi-Fi. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "communication",
|
||
difficulty: "intermediate",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "main.py", content: `import network
|
||
import time
|
||
|
||
ap=network.WLAN(network.AP_IF)
|
||
|
||
ap.active(True)
|
||
ap.config(essid="ESP32_HOTSPOT", password="@Kritish", authmode=network.AUTH_WPA_WPA2_PSK)
|
||
|
||
while not ap.active():
|
||
time.sleep(1)
|
||
|
||
print("✅ ESP32 Hotspot Started")
|
||
print("📶 SSID: ESP32_HOTSPOT")
|
||
print("🌐 IP Address:", ap.ifconfig()[0])
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "esp32", "micropython", "wifi"],
|
||
},
|
||
{
|
||
id: "100d-esp32-ir-sensor-telegram-alert-micropython",
|
||
title: "ESP32 IR Sensor Telegram Alert (MicroPython)",
|
||
description: "ESP32 IR Sensor Telegram Alert (MicroPython) on ESP32 (MicroPython) \u2014 uses Telegram, Wi-Fi. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "communication",
|
||
difficulty: "intermediate",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "main.py", content: `import network
|
||
import urequests
|
||
import machine
|
||
import time
|
||
import ujson
|
||
|
||
# -------- WiFi --------
|
||
SSID = "ssid"
|
||
PASSWORD = "@pass"
|
||
|
||
wifi = network.WLAN(network.STA_IF)
|
||
wifi.active(True)
|
||
wifi.connect(SSID, PASSWORD)
|
||
|
||
print("Connecting to WiFi...")
|
||
while not wifi.isconnected():
|
||
time.sleep(1)
|
||
|
||
print("WiFi Connected ✅")
|
||
print(wifi.ifconfig())
|
||
|
||
# -------- Telegram --------
|
||
BOT_TOKEN = "bottoken"
|
||
CHAT_ID = -12457 # int ONLY
|
||
|
||
def send_telegram(msg):
|
||
msg = msg.replace(" ", "%20").replace("\\n", "%0A")
|
||
url = (
|
||
"https://api.telegram.org/bot"
|
||
+ BOT_TOKEN
|
||
+ "/sendMessage?chat_id="
|
||
+ str(CHAT_ID)
|
||
+ "&text="
|
||
+ msg
|
||
)
|
||
r = urequests.get(url)
|
||
print("Telegram response:", r.text)
|
||
r.close()
|
||
|
||
# -------- IR Sensor --------
|
||
ir = machine.Pin(27, machine.Pin.IN)
|
||
|
||
print("System Ready 🚀")
|
||
|
||
last_alert = 0
|
||
ALERT_DELAY = 10
|
||
|
||
while True:
|
||
if ir.value() == 0:
|
||
if time.time() - last_alert > ALERT_DELAY:
|
||
print("🚨 Object Detected!")
|
||
send_telegram("🚨 IR ALERT!\\nObject detected near ESP32")
|
||
last_alert = time.time()
|
||
time.sleep(1)
|
||
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "esp32", "micropython", "telegram", "wifi"],
|
||
},
|
||
{
|
||
id: "100d-esp32-oled-smart-ui-eyes-animation-time-and-weather-micropython",
|
||
title: "ESP32 OLED Smart UI Eyes Animation Time & Weather (MicroPython)",
|
||
description: "ESP32 OLED Smart UI Eyes Animation Time & Weather (MicroPython) on ESP32 (MicroPython) \u2014 uses OLED, Wi-Fi. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "displays",
|
||
difficulty: "intermediate",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "main.py", content: `from machine import Pin, I2C
|
||
import ssd1306
|
||
from time import sleep, localtime, time
|
||
import network, ntptime
|
||
import urequests as requests
|
||
import urandom as random
|
||
|
||
# ================= CONFIG =================
|
||
SSID = "ssid"
|
||
PASSWORD = "pass"
|
||
# IST Offset: 5 hours 30 mins = (5*3600 + 30*60) = 19800 seconds
|
||
UTC_OFFSET = 19800
|
||
OWM_API_KEY = "key"
|
||
CITY = "city"
|
||
|
||
# ================= HARDWARE =================
|
||
# Velxio decodes the hardware I2C controller only (no bit-banged buses)
|
||
i2c = I2C(0, scl=Pin(22), sda=Pin(21))
|
||
oled = ssd1306.SSD1306_I2C(128, 64, i2c)
|
||
|
||
touch_next = Pin(14, Pin.IN)
|
||
touch_sel = Pin(27, Pin.IN)
|
||
|
||
# ================= UTILS =================
|
||
def wifi_connect():
|
||
wlan = network.WLAN(network.STA_IF)
|
||
wlan.active(True)
|
||
if not wlan.isconnected():
|
||
wlan.connect(SSID, PASSWORD)
|
||
for _ in range(20):
|
||
if wlan.isconnected(): break
|
||
sleep(0.5)
|
||
|
||
def sync_time():
|
||
try:
|
||
|
||
ntptime.host = "pool.ntp.org"
|
||
ntptime.settime()
|
||
print("Time Synced!")
|
||
return True
|
||
except:
|
||
print("Sync Failed")
|
||
return False
|
||
|
||
def get_local_time():
|
||
|
||
return localtime(time() + UTC_OFFSET)
|
||
|
||
def wait_for_release(pin):
|
||
while pin.value():
|
||
sleep(0.05)
|
||
|
||
def pressed(pin):
|
||
if pin.value():
|
||
sleep(0.1)
|
||
wait_for_release(pin)
|
||
return True
|
||
return False
|
||
|
||
# ================= EYE SYSTEM =================
|
||
def draw_eye(x, y, state=2, look=0):
|
||
w, h = 24, 16
|
||
if state == 0: # Closed
|
||
oled.fill_rect(x, y + (h//2), w, 2, 1)
|
||
elif state == 1: # Half-open
|
||
oled.rect(x, y + (h//4), w, h//2, 1)
|
||
oled.fill_rect(x + 8 + look, y + (h//4) + 2, 8, 4, 1)
|
||
elif state == 2: # Fully Open
|
||
oled.rect(x, y, w, h, 1)
|
||
oled.fill_rect(x + 6 + look, y + 4, 10, 8, 1)
|
||
|
||
def animate_eyes_step():
|
||
dirs = [-4, 0, 4]
|
||
look_dir = dirs[random.getrandbits(8) % 3]
|
||
for _ in range(30):
|
||
oled.fill(0)
|
||
draw_eye(25, 22, state=2, look=look_dir)
|
||
draw_eye(78, 22, state=2, look=look_dir)
|
||
oled.show()
|
||
if touch_sel.value():
|
||
wait_for_release(touch_sel)
|
||
return True
|
||
sleep(0.1)
|
||
for p in [1, 0, 1]:
|
||
oled.fill(0)
|
||
draw_eye(25, 22, state=p, look=look_dir)
|
||
draw_eye(78, 22, state=p, look=look_dir)
|
||
oled.show()
|
||
sleep(0.05)
|
||
return False
|
||
|
||
# ================= SCREENS =================
|
||
def show_time_screen():
|
||
while True:
|
||
t = get_local_time()
|
||
oled.fill(0)
|
||
oled.text("TIME (IST)", 30, 0)
|
||
# Time format: HH:MM:SS
|
||
oled.text("{:02d}:{:02d}:{:02d}".format(t[3], t[4], t[5]), 30, 25)
|
||
# Date format: DD/MM/YYYY
|
||
oled.text("{:02d}/{:02d}/{}".format(t[2], t[1], t[0]), 22, 40)
|
||
oled.text("Nxt:Sync,Sel:Ext", 0, 55)
|
||
oled.show()
|
||
|
||
if touch_next.value():
|
||
sync_time()
|
||
wait_for_release(touch_next)
|
||
if pressed(touch_sel):
|
||
break
|
||
|
||
def show_weather_screen():
|
||
oled.fill(0)
|
||
oled.text("Loading...", 30, 25)
|
||
oled.show()
|
||
try:
|
||
url = "http://api.openweathermap.org/data/2.5/weather?q={}&appid={}&units=metric".format(CITY, OWM_API_KEY)
|
||
res = requests.get(url).json()
|
||
temp, hum = res['main']['temp'], res['main']['humidity']
|
||
desc = res['weather'][0]['main']
|
||
while True:
|
||
oled.fill(0)
|
||
oled.text("WEATHER: " + CITY, 0, 0)
|
||
oled.text("Temp: {}C".format(temp), 10, 20)
|
||
oled.text("Hum: {}%".format(hum), 10, 35)
|
||
oled.text("Cond: " + desc, 10, 50)
|
||
oled.show()
|
||
if pressed(touch_sel): break
|
||
except:
|
||
oled.fill(0)
|
||
oled.text("API Error", 30, 25)
|
||
oled.show()
|
||
sleep(2)
|
||
|
||
# ================= MAIN =================
|
||
WELCOME, EYES, MENU, TIME_S, WEATHER = range(5)
|
||
state = WELCOME
|
||
menu = ["TIME", "WEATHER", "BACK"]
|
||
menu_index = 0
|
||
|
||
wifi_connect()
|
||
sync_time()
|
||
|
||
while True:
|
||
if state == WELCOME:
|
||
oled.fill(0)
|
||
oled.text("Welcome Kritish", 5, 25)
|
||
oled.show()
|
||
sleep(2)
|
||
state = EYES
|
||
|
||
elif state == EYES:
|
||
if animate_eyes_step():
|
||
state = MENU
|
||
|
||
elif state == MENU:
|
||
oled.fill(0)
|
||
oled.text("-- MENU --", 30, 0)
|
||
for i, item in enumerate(menu):
|
||
marker = ">" if i == menu_index else " "
|
||
oled.text("{} {}".format(marker, item), 10, 20 + i*12)
|
||
oled.show()
|
||
|
||
if pressed(touch_next):
|
||
menu_index = (menu_index + 1) % len(menu)
|
||
if pressed(touch_sel):
|
||
choice = menu[menu_index]
|
||
if choice == "TIME": state = TIME_S
|
||
elif choice == "WEATHER": state = WEATHER
|
||
else: state = EYES
|
||
|
||
elif state == TIME_S:
|
||
show_time_screen()
|
||
state = MENU
|
||
|
||
elif state == WEATHER:
|
||
show_weather_screen()
|
||
state = MENU
|
||
` },
|
||
{ name: "ssd1306.py", content: `# MicroPython SSD1306 OLED driver, I2C and SPI interfaces
|
||
|
||
from micropython import const
|
||
import framebuf
|
||
|
||
|
||
# register definitions
|
||
SET_CONTRAST = const(0x81)
|
||
SET_ENTIRE_ON = const(0xA4)
|
||
SET_NORM_INV = const(0xA6)
|
||
SET_DISP = const(0xAE)
|
||
SET_MEM_ADDR = const(0x20)
|
||
SET_COL_ADDR = const(0x21)
|
||
SET_PAGE_ADDR = const(0x22)
|
||
SET_DISP_START_LINE = const(0x40)
|
||
SET_SEG_REMAP = const(0xA0)
|
||
SET_MUX_RATIO = const(0xA8)
|
||
SET_COM_OUT_DIR = const(0xC0)
|
||
SET_DISP_OFFSET = const(0xD3)
|
||
SET_COM_PIN_CFG = const(0xDA)
|
||
SET_DISP_CLK_DIV = const(0xD5)
|
||
SET_PRECHARGE = const(0xD9)
|
||
SET_VCOM_DESEL = const(0xDB)
|
||
SET_CHARGE_PUMP = const(0x8D)
|
||
|
||
# Subclassing FrameBuffer provides support for graphics primitives
|
||
# http://docs.micropython.org/en/latest/pyboard/library/framebuf.html
|
||
class SSD1306(framebuf.FrameBuffer):
|
||
def __init__(self, width, height, external_vcc):
|
||
self.width = width
|
||
self.height = height
|
||
self.external_vcc = external_vcc
|
||
self.pages = self.height // 8
|
||
self.buffer = bytearray(self.pages * self.width)
|
||
super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB)
|
||
self.init_display()
|
||
|
||
def init_display(self):
|
||
for cmd in (
|
||
SET_DISP | 0x00, # off
|
||
# address setting
|
||
SET_MEM_ADDR,
|
||
0x00, # horizontal
|
||
# resolution and layout
|
||
SET_DISP_START_LINE | 0x00,
|
||
SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0
|
||
SET_MUX_RATIO,
|
||
self.height - 1,
|
||
SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0
|
||
SET_DISP_OFFSET,
|
||
0x00,
|
||
SET_COM_PIN_CFG,
|
||
0x02 if self.width > 2 * self.height else 0x12,
|
||
# timing and driving scheme
|
||
SET_DISP_CLK_DIV,
|
||
0x80,
|
||
SET_PRECHARGE,
|
||
0x22 if self.external_vcc else 0xF1,
|
||
SET_VCOM_DESEL,
|
||
0x30, # 0.83*Vcc
|
||
# display
|
||
SET_CONTRAST,
|
||
0xFF, # maximum
|
||
SET_ENTIRE_ON, # output follows RAM contents
|
||
SET_NORM_INV, # not inverted
|
||
# charge pump
|
||
SET_CHARGE_PUMP,
|
||
0x10 if self.external_vcc else 0x14,
|
||
SET_DISP | 0x01,
|
||
): # on
|
||
self.write_cmd(cmd)
|
||
self.fill(0)
|
||
self.show()
|
||
|
||
def poweroff(self):
|
||
self.write_cmd(SET_DISP | 0x00)
|
||
|
||
def poweron(self):
|
||
self.write_cmd(SET_DISP | 0x01)
|
||
|
||
def contrast(self, contrast):
|
||
self.write_cmd(SET_CONTRAST)
|
||
self.write_cmd(contrast)
|
||
|
||
def invert(self, invert):
|
||
self.write_cmd(SET_NORM_INV | (invert & 1))
|
||
|
||
def show(self):
|
||
x0 = 0
|
||
x1 = self.width - 1
|
||
if self.width == 64:
|
||
# displays with width of 64 pixels are shifted by 32
|
||
x0 += 32
|
||
x1 += 32
|
||
self.write_cmd(SET_COL_ADDR)
|
||
self.write_cmd(x0)
|
||
self.write_cmd(x1)
|
||
self.write_cmd(SET_PAGE_ADDR)
|
||
self.write_cmd(0)
|
||
self.write_cmd(self.pages - 1)
|
||
self.write_data(self.buffer)
|
||
|
||
|
||
class SSD1306_I2C(SSD1306):
|
||
def __init__(self, width, height, i2c, addr=0x3C, external_vcc=False):
|
||
self.i2c = i2c
|
||
self.addr = addr
|
||
self.temp = bytearray(2)
|
||
self.write_list = [b"\\x40", None] # Co=0, D/C#=1
|
||
super().__init__(width, height, external_vcc)
|
||
|
||
def write_cmd(self, cmd):
|
||
self.temp[0] = 0x80 # Co=1, D/C#=0
|
||
self.temp[1] = cmd
|
||
self.i2c.writeto(self.addr, self.temp)
|
||
|
||
def write_data(self, buf):
|
||
self.write_list[1] = buf
|
||
self.i2c.writevto(self.addr, self.write_list)
|
||
|
||
|
||
class SSD1306_SPI(SSD1306):
|
||
def __init__(self, width, height, spi, dc, res, cs, external_vcc=False):
|
||
self.rate = 10 * 1024 * 1024
|
||
dc.init(dc.OUT, value=0)
|
||
res.init(res.OUT, value=0)
|
||
cs.init(cs.OUT, value=1)
|
||
self.spi = spi
|
||
self.dc = dc
|
||
self.res = res
|
||
self.cs = cs
|
||
import time
|
||
|
||
self.res(1)
|
||
time.sleep_ms(1)
|
||
self.res(0)
|
||
time.sleep_ms(10)
|
||
self.res(1)
|
||
super().__init__(width, height, external_vcc)
|
||
|
||
def write_cmd(self, cmd):
|
||
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
|
||
self.cs(1)
|
||
self.dc(0)
|
||
self.cs(0)
|
||
self.spi.write(bytearray([cmd]))
|
||
self.cs(1)
|
||
|
||
def write_data(self, buf):
|
||
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
|
||
self.cs(1)
|
||
self.dc(1)
|
||
self.cs(0)
|
||
self.spi.write(buf)
|
||
self.cs(1)
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [{"type": "wokwi-ssd1306", "id": "eyes-oled1", "x": 420, "y": 60, "properties": {}}, {"type": "wokwi-pushbutton", "id": "eyes-btn-next", "x": 420, "y": 260, "properties": {"color": "blue"}}, {"type": "wokwi-pushbutton", "id": "eyes-btn-sel", "x": 540, "y": 260, "properties": {"color": "green"}}],
|
||
wires: [{"id": "eyes-oled-vcc", "start": {"componentId": "arduino-uno", "pinName": "3V3"}, "end": {"componentId": "eyes-oled1", "pinName": "3V3"}, "color": "#ff4444"}, {"id": "eyes-oled-gnd", "start": {"componentId": "arduino-uno", "pinName": "GND"}, "end": {"componentId": "eyes-oled1", "pinName": "GND"}, "color": "#000000"}, {"id": "eyes-oled-sda", "start": {"componentId": "arduino-uno", "pinName": "21"}, "end": {"componentId": "eyes-oled1", "pinName": "DATA"}, "color": "#22aaff"}, {"id": "eyes-oled-scl", "start": {"componentId": "arduino-uno", "pinName": "22"}, "end": {"componentId": "eyes-oled1", "pinName": "CLK"}, "color": "#ff8800"}, {"id": "eyes-btn-next-pin", "start": {"componentId": "arduino-uno", "pinName": "14"}, "end": {"componentId": "eyes-btn-next", "pinName": "1.l"}, "color": "#22aaff"}, {"id": "eyes-btn-next-vcc", "start": {"componentId": "eyes-btn-next", "pinName": "2.l"}, "end": {"componentId": "arduino-uno", "pinName": "3V3"}, "color": "#ff4444"}, {"id": "eyes-btn-sel-pin", "start": {"componentId": "arduino-uno", "pinName": "27"}, "end": {"componentId": "eyes-btn-sel", "pinName": "1.l"}, "color": "#22aaff"}, {"id": "eyes-btn-sel-vcc", "start": {"componentId": "eyes-btn-sel", "pinName": "2.l"}, "end": {"componentId": "arduino-uno", "pinName": "3V3"}, "color": "#ff4444"}],
|
||
tags: ["100-days", "esp32", "i2c_oled", "micropython", "wifi"],
|
||
},
|
||
{
|
||
id: "100d-flask-server-based-led-control-using-micropython",
|
||
title: "Flask Server Based LED Control using MicroPython",
|
||
description: "Flask Server Based LED Control using MicroPython on ESP32 (MicroPython) \u2014 uses HTTP server, Wi-Fi. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "communication",
|
||
difficulty: "intermediate",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "app.py", content: `from flask import Flask, jsonify, render_template
|
||
|
||
app = Flask(__name__)
|
||
|
||
led_state = {"value": 0}
|
||
|
||
@app.route("/")
|
||
def home():
|
||
return render_template("index.html")
|
||
|
||
@app.route("/led/on")
|
||
def led_on():
|
||
led_state["value"] = 1
|
||
return "OK"
|
||
|
||
@app.route("/led/off")
|
||
def led_off():
|
||
led_state["value"] = 0
|
||
return "OK"
|
||
|
||
@app.route("/led/state")
|
||
def get_state():
|
||
return jsonify(led_state)
|
||
|
||
app.run(host="0.0.0.0", port=5000)
|
||
` },
|
||
{ name: "main.py", content: `import network, urequests, time
|
||
from machine import Pin
|
||
|
||
# LED
|
||
led = Pin("LED", Pin.OUT)
|
||
|
||
# WiFi
|
||
ssid = "kritish"
|
||
password = "pass"
|
||
|
||
wlan = network.WLAN(network.STA_IF)
|
||
wlan.active(True)
|
||
wlan.connect(ssid, password)
|
||
|
||
while not wlan.isconnected():
|
||
time.sleep(1)
|
||
|
||
print("Connected:", wlan.ifconfig())
|
||
|
||
# Flask server URL
|
||
SERVER = "http://ip:5000/led/state"
|
||
|
||
while True:
|
||
try:
|
||
r = urequests.get(SERVER)
|
||
state = r.json()["value"]
|
||
r.close()
|
||
|
||
led.value(state)
|
||
print("LED =", state)
|
||
|
||
except Exception as e:
|
||
print("Error:", e)
|
||
|
||
time.sleep(2)
|
||
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "esp32", "http_server", "micropython", "wifi"],
|
||
},
|
||
{
|
||
id: "100d-iot-atmospheric-monitoring-system-using-esp32-wowki-and-blynk",
|
||
title: "IoT Atmospheric Monitoring System using ESP32, wowki & Blynk",
|
||
description: "IoT Atmospheric Monitoring System using ESP32, wowki & Blynk on ESP32 (MicroPython) \u2014 uses Blynk, Blynk Cloud, Wi-Fi. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "communication",
|
||
difficulty: "advanced",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "BlynkLib.py", content: `# 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()
|
||
# Velxio patch: bound the blocking connect so an unreachable cloud
|
||
# raises OSError instead of freezing the sketch forever.
|
||
s.settimeout(10)
|
||
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)
|
||
|
||
|
||
` },
|
||
{ name: "bmp180.py", content: `'''
|
||
bmp180.py — Velxio simulator adaptation.
|
||
|
||
Keeps the classic BMP180 driver API the original project uses
|
||
(BMP180(i2c), .oversample, .sealevel, .temperature, .pressure, .altitude)
|
||
but talks to the BMP280 barometric sensor that the Velxio ESP32 simulator
|
||
emulates on the I2C bus (chip id 0x58, address 0x76/0x77) — the BMP180
|
||
itself is not in the simulator's catalogue.
|
||
|
||
On real hardware with an actual BMP180, replace this module with the
|
||
classic driver: https://github.com/micropython-IMU/micropython-bmp180
|
||
'''
|
||
|
||
from ustruct import unpack
|
||
|
||
|
||
class BMP180:
|
||
|
||
def __init__(self, i2c):
|
||
self._i2c = i2c
|
||
# Probe the two possible addresses directly instead of i2c.scan():
|
||
# a full-bus scan stalls on Velxio's in-browser ESP32 engine.
|
||
self._addr = 0x76
|
||
for addr in (0x76, 0x77):
|
||
try:
|
||
self._i2c.readfrom_mem(addr, 0xD0, 1) # chip-id register
|
||
self._addr = addr
|
||
break
|
||
except OSError:
|
||
pass
|
||
# BMP280 trimming coefficients live at 0x88..0x9F
|
||
buf = self._i2c.readfrom_mem(self._addr, 0x88, 24)
|
||
(self._T1, self._T2, self._T3,
|
||
self._P1, self._P2, self._P3, self._P4, self._P5,
|
||
self._P6, self._P7, self._P8, self._P9) = unpack('<HhhHhhhhhhhh', buf)
|
||
# normal mode, temperature x2 / pressure x16 oversampling
|
||
self._i2c.writeto_mem(self._addr, 0xF4, b'\\x57')
|
||
self.oversample = 2 # kept for API compatibility (unused)
|
||
self.sealevel = 101325 # Pa — used by .altitude
|
||
|
||
def _read_raw(self):
|
||
b = self._i2c.readfrom_mem(self._addr, 0xF7, 6)
|
||
adc_p = (b[0] << 12) | (b[1] << 4) | (b[2] >> 4)
|
||
adc_t = (b[3] << 12) | (b[4] << 4) | (b[5] >> 4)
|
||
return adc_t, adc_p
|
||
|
||
# Bosch BMP280 compensation formulas (datasheet section 3.11.3)
|
||
def _t_fine(self, adc_t):
|
||
v1 = (((adc_t >> 3) - (self._T1 << 1)) * self._T2) >> 11
|
||
s = (adc_t >> 4) - self._T1
|
||
v2 = ((s * s >> 12) * self._T3) >> 14
|
||
return v1 + v2
|
||
|
||
@property
|
||
def temperature(self):
|
||
'''Temperature in degrees C.'''
|
||
adc_t, _ = self._read_raw()
|
||
return ((self._t_fine(adc_t) * 5 + 128) >> 8) / 100
|
||
|
||
@property
|
||
def pressure(self):
|
||
'''Pressure in Pa (same unit as the classic BMP180 driver).'''
|
||
adc_t, adc_p = self._read_raw()
|
||
tf = self._t_fine(adc_t)
|
||
v1 = tf / 2.0 - 64000.0
|
||
v2 = v1 * v1 * self._P6 / 32768.0
|
||
v2 = v2 + v1 * self._P5 * 2.0
|
||
v2 = v2 / 4.0 + self._P4 * 65536.0
|
||
v1 = (self._P3 * v1 * v1 / 524288.0 + self._P2 * v1) / 524288.0
|
||
v1 = (1.0 + v1 / 32768.0) * self._P1
|
||
if v1 == 0:
|
||
return 0.0
|
||
p = 1048576.0 - adc_p
|
||
p = (p - v2 / 4096.0) * 6250.0 / v1
|
||
p = p + (self._P9 * p * p / 2147483648.0
|
||
+ p * self._P8 / 32768.0 + self._P7) / 16.0
|
||
return p
|
||
|
||
@property
|
||
def altitude(self):
|
||
'''Altitude in m, from pressure vs the configured sea-level pressure.'''
|
||
try:
|
||
return 44330.0 * (1.0 - (self.pressure / self.sealevel) ** (1.0 / 5.255))
|
||
except Exception:
|
||
return 0.0
|
||
` },
|
||
{ name: "main.py", content: `
|
||
'''Copyright (c) 2026 Kritish Mohapatra'''
|
||
|
||
import network
|
||
from machine import I2C, Pin
|
||
import BlynkLib
|
||
from time import sleep
|
||
from bmp180 import BMP180
|
||
|
||
# -------- WiFi --------
|
||
wifissid = "Wokwi-GUEST"
|
||
wifipass = ""
|
||
|
||
# -------- Blynk Token --------
|
||
# Replace with your real Blynk Cloud token to see the data on a dashboard.
|
||
# The demo keeps printing sensor readings on the serial monitor either way.
|
||
auth = "dashboard-token"
|
||
|
||
# Connect WiFi
|
||
sta_if = network.WLAN(network.STA_IF)
|
||
sta_if.active(True)
|
||
sta_if.connect(wifissid, wifipass)
|
||
|
||
while not sta_if.isconnected():
|
||
sleep(0.1)
|
||
|
||
print("Connected to Wi-Fi:", sta_if.ifconfig())
|
||
|
||
# ── Velxio adaptation ──────────────────────────────────────────────────
|
||
# The vendored BlynkLib bounds its connect() with a timeout (Velxio patch),
|
||
# so an unreachable cloud raises OSError here instead of freezing the demo;
|
||
# fall back to an offline no-op Blynk object in that case.
|
||
class _OfflineBlynk:
|
||
def run(self):
|
||
pass
|
||
def virtual_write(self, *_a):
|
||
pass
|
||
|
||
|
||
try:
|
||
blynk = BlynkLib.Blynk(auth, insecure=True)
|
||
except OSError as e:
|
||
print("Blynk Cloud unreachable, running offline:", e)
|
||
blynk = _OfflineBlynk()
|
||
|
||
# -------- BMP180 I2C --------
|
||
# The simulator emulates the ESP32's hardware I2C controller, so use
|
||
# machine.I2C — a bit-banged SoftI2C bus is not decoded by the emulator.
|
||
i2c = I2C(0, scl=Pin(22), sda=Pin(21))
|
||
bmp = BMP180(i2c)
|
||
bmp.oversample = 2
|
||
bmp.sealevel = 101325 # sea level pressure
|
||
|
||
# -------- MAIN LOOP --------
|
||
while True:
|
||
temp = bmp.temperature
|
||
pressure = bmp.pressure / 100 # Pa → hPa
|
||
altitude = bmp.altitude
|
||
|
||
print("Temp:", temp, "C")
|
||
print("Pressure:", pressure, "hPa")
|
||
print("Altitude:", altitude, "m")
|
||
print("----------------------")
|
||
|
||
# Send to Blynk
|
||
try:
|
||
blynk.run()
|
||
blynk.virtual_write(0, temp) # V0 Temperature
|
||
blynk.virtual_write(1, pressure) # V1 Pressure
|
||
blynk.virtual_write(2, altitude) # V2 Altitude
|
||
except Exception as e:
|
||
print("Blynk error, continuing offline:", e)
|
||
blynk = _OfflineBlynk()
|
||
|
||
sleep(2)
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [{"type": "velxio-bmp280", "id": "atm-bmp1", "x": 430, "y": 150, "properties": {"temperature": "24.5", "pressure": "1013.25"}}],
|
||
wires: [{"id": "atm-vcc", "start": {"componentId": "arduino-uno", "pinName": "3V3"}, "end": {"componentId": "atm-bmp1", "pinName": "VCC"}, "color": "#ff4444"}, {"id": "atm-gnd", "start": {"componentId": "arduino-uno", "pinName": "GND"}, "end": {"componentId": "atm-bmp1", "pinName": "GND"}, "color": "#000000"}, {"id": "atm-sda", "start": {"componentId": "arduino-uno", "pinName": "21"}, "end": {"componentId": "atm-bmp1", "pinName": "SDA"}, "color": "#22aaff"}, {"id": "atm-scl", "start": {"componentId": "arduino-uno", "pinName": "22"}, "end": {"componentId": "atm-bmp1", "pinName": "SCL"}, "color": "#ff8800"}],
|
||
tags: ["100-days", "blynk", "blynk_cloud", "esp32", "micropython", "wifi"],
|
||
},
|
||
{
|
||
id: "100d-iot-based-dsm-smart-metering",
|
||
title: "IoT Based DSM Smart Metering",
|
||
description: "IoT Based DSM Smart Metering on ESP32 (MicroPython) \u2014 uses Blynk, Blynk Cloud, OTA, Wi-Fi. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "communication",
|
||
difficulty: "advanced",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "BlynkLib.py", content: `# 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()
|
||
# Velxio patch: bound the blocking connect so an unreachable cloud
|
||
# raises OSError instead of freezing the sketch forever.
|
||
s.settimeout(10)
|
||
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)
|
||
` },
|
||
{ name: "dsm_code1_csv.py", content: `# ============================================================
|
||
# DSM Smart Meter — Code 1: Data Collection
|
||
# CSV Serial Output → Copy → MATLAB Plot
|
||
# Author : Kritish Mohapatra
|
||
#
|
||
# ZMPT101B → GPIO34 (Voltage)
|
||
# ACS712 #1 → GPIO35 (Fan / Light Load)
|
||
# ACS712 #2 → GPIO32 (Iron / Heavy Load)
|
||
# Relay 1 → GPIO26 (Fan)
|
||
# Relay 2 → GPIO27 (Iron)
|
||
# ============================================================
|
||
|
||
import network
|
||
import machine
|
||
import math
|
||
import BlynkLib
|
||
from time import time
|
||
|
||
# ─── WiFi Credentials ────────────────────────────────────────
|
||
WIFI_SSID = "Kritish"
|
||
WIFI_PASS = "@pass"
|
||
|
||
# ─── Blynk Auth ──────────────────────────────────────────────
|
||
AUTH = "oizEb24"
|
||
|
||
# ─── ADC Setup ───────────────────────────────────────────────
|
||
adc_voltage = machine.ADC(machine.Pin(34))
|
||
adc_current1 = machine.ADC(machine.Pin(35))
|
||
adc_current2 = machine.ADC(machine.Pin(32))
|
||
adc_voltage.atten(machine.ADC.ATTN_11DB)
|
||
adc_current1.atten(machine.ADC.ATTN_11DB)
|
||
adc_current2.atten(machine.ADC.ATTN_11DB)
|
||
|
||
# ─── Relay + LED ─────────────────────────────────────────────
|
||
r1 = machine.Pin(26, machine.Pin.OUT)
|
||
r2 = machine.Pin(27, machine.Pin.OUT)
|
||
led = machine.Pin(2, machine.Pin.OUT)
|
||
r1.value(1)
|
||
r2.value(1)
|
||
led.value(0)
|
||
|
||
# ─── Calibration ─────────────────────────────────────────────
|
||
|
||
ADC_MAX = 4095
|
||
ADC_VREF = 3.3
|
||
ZMPT_SENSITIVITY = 133.8 # voltage calibrated: actual=216V
|
||
ACS712_SENSITIVITY = 0.100 # V/A for 20A variant
|
||
ACS_ZERO_FAN = 0.8736
|
||
ACS_ZERO_IRON = 0.8746
|
||
CURR_FACTOR_FAN = 0.005
|
||
CURR_FACTOR_IRON = 0.055
|
||
CURRENT_THRESHOLD = 0.01 # below this → 0.00A
|
||
SAMPLE_COUNT = 500
|
||
# ─── Fake Time ───────────────────────────────────────────────
|
||
FAKE_START_HOUR = 18 # 6 PM
|
||
FAKE_START_MIN = 0
|
||
fake_total_min = FAKE_START_HOUR * 60 + FAKE_START_MIN
|
||
|
||
# ─── State ───────────────────────────────────────────────────
|
||
fan_on = False
|
||
iron_on = False
|
||
|
||
# ─── WiFi Connect ────────────────────────────────────────────
|
||
sta_if = network.WLAN(network.STA_IF)
|
||
sta_if.active(True)
|
||
sta_if.connect(WIFI_SSID, WIFI_PASS)
|
||
print("Connecting to WiFi...")
|
||
while not sta_if.isconnected():
|
||
pass
|
||
print("WiFi Connected:", sta_if.ifconfig())
|
||
|
||
# ─── Blynk Init ──────────────────────────────────────────────
|
||
# ── Velxio adaptation ──────────────────────────────────────────────────────────────────
|
||
# The vendored BlynkLib bounds its connect() with a timeout (Velxio patch),
|
||
# so an unreachable cloud raises OSError here instead of freezing the demo;
|
||
# fall back to an offline no-op Blynk object in that case.
|
||
class _OfflineBlynk:
|
||
def on(self, *_a, **_k):
|
||
def _deco(f):
|
||
return f
|
||
return _deco
|
||
def run(self):
|
||
pass
|
||
def virtual_write(self, *_a):
|
||
pass
|
||
|
||
|
||
try:
|
||
blynk = BlynkLib.Blynk(AUTH, insecure=True)
|
||
except OSError as e:
|
||
print("Blynk Cloud unreachable, running offline:", e)
|
||
blynk = _OfflineBlynk()
|
||
|
||
# ─── Blynk Switch Handlers ───────────────────────────────────
|
||
@blynk.on("V3")
|
||
def v3_handler(value):
|
||
global fan_on
|
||
fan_on = value[0] == '1'
|
||
r1.value(0 if fan_on else 1)
|
||
led.value(1 if (fan_on or iron_on) else 0)
|
||
print(f"# FAN {'ON' if fan_on else 'OFF'}")
|
||
|
||
@blynk.on("V4")
|
||
def v4_handler(value):
|
||
global iron_on
|
||
iron_on = value[0] == '1'
|
||
r2.value(0 if iron_on else 1)
|
||
led.value(1 if (fan_on or iron_on) else 0)
|
||
print(f"# IRON {'ON' if iron_on else 'OFF'}")
|
||
|
||
# ─── Sensor Functions ────────────────────────────────────────
|
||
def read_voltage_rms():
|
||
sum_sq = 0.0
|
||
for _ in range(SAMPLE_COUNT):
|
||
raw = adc_voltage.read()
|
||
v = (raw / ADC_MAX) * ADC_VREF
|
||
centered = v - (ADC_VREF / 2.0)
|
||
scaled = centered * ZMPT_SENSITIVITY
|
||
sum_sq += scaled * scaled
|
||
return math.sqrt(sum_sq / SAMPLE_COUNT)
|
||
|
||
def read_current_rms(adc, zero_offset, factor):
|
||
sum_sq = 0.0
|
||
for _ in range(SAMPLE_COUNT):
|
||
raw = adc.read()
|
||
v = (raw / ADC_MAX) * ADC_VREF
|
||
centered = v - zero_offset
|
||
amps = centered / ACS712_SENSITIVITY
|
||
sum_sq += amps * amps
|
||
rms = math.sqrt(sum_sq / SAMPLE_COUNT) * factor
|
||
return 0.0 if rms < CURRENT_THRESHOLD else rms
|
||
|
||
def get_readings():
|
||
v = read_voltage_rms()
|
||
i1 = read_current_rms(adc_current1, ACS_ZERO_FAN, CURR_FACTOR_FAN)
|
||
i2 = read_current_rms(adc_current2, ACS_ZERO_IRON, CURR_FACTOR_IRON)
|
||
|
||
if r1.value() == 1: i1 = 0.0
|
||
if r2.value() == 1: i2 = 0.0
|
||
if r1.value() == 1 and r2.value() == 1: v = 0.0
|
||
|
||
p1 = v * i1
|
||
p2 = v * i2
|
||
pt = p1 + p2
|
||
return v, p1, p2, pt
|
||
|
||
# ─── Fake Time ───────────────────────────────────────────────
|
||
def get_fake_time():
|
||
global fake_total_min
|
||
h = (fake_total_min // 60) % 24
|
||
m = fake_total_min % 60
|
||
fake_total_min += 1
|
||
return h, m
|
||
|
||
# ─── Main Loop ───────────────────────────────────────────────
|
||
# CSV Header
|
||
print("# DSM Smart Meter — Code 1 Data Collection")
|
||
print("# Copy everything below (except lines with #) to data.csv")
|
||
print("Time,Voltage,FanPower,IronPower,TotalPower,FanStatus,IronStatus")
|
||
|
||
timer = 0
|
||
|
||
while True:
|
||
blynk.run()
|
||
timer += 1
|
||
|
||
if timer >= 3000:
|
||
timer = 0
|
||
|
||
v, p1, p2, pt = get_readings()
|
||
h, m = get_fake_time()
|
||
time_str = f"{h:02d}:{m:02d}"
|
||
|
||
# CSV line — copy this to MATLAB
|
||
print(f"{time_str},{v:.2f},{p1:.1f},{p2:.1f},{pt:.1f},{1 if fan_on else 0},{1 if iron_on else 0}")
|
||
|
||
# Blynk upload
|
||
blynk.virtual_write(0, round(v, 2))
|
||
blynk.virtual_write(1, round(p1, 1))
|
||
blynk.virtual_write(2, round(p2, 1))
|
||
blynk.virtual_write(3, 1 if fan_on else 0)
|
||
blynk.virtual_write(4, 1 if iron_on else 0)
|
||
blynk.virtual_write(5, round(pt, 1))
|
||
blynk.virtual_write(6, round(pt, 1))` },
|
||
{ name: "dsm_code2_peakshift.py", content: `# ============================================================
|
||
# DSM Smart Meter — Code 2: Peak Shifting (With DSM)
|
||
# Auto Iron OFF during peak hour
|
||
# Author : Kritish Mohapatra
|
||
#
|
||
# ZMPT101B → GPIO34 (Voltage)
|
||
# ACS712 #1 → GPIO35 (Fan / Light Load)
|
||
# ACS712 #2 → GPIO32 (Iron / Heavy Load)
|
||
# Relay 1 → GPIO26 (Fan)
|
||
# Relay 2 → GPIO27 (Iron)
|
||
#
|
||
# BLYNK VIRTUAL PINS
|
||
# V0 → Voltage gauge
|
||
# V1 → Light Load power (Fan)
|
||
# V2 → Heavy Load power (Iron)
|
||
# V3 → Switch 1 (Fan ON/OFF)
|
||
# V4 → Switch 2 (Iron ON/OFF)
|
||
# V5 → Power Graph (SuperChart)
|
||
# V6 → Total Power gauge
|
||
#
|
||
# FAKE TIME SCHEDULE (IST):
|
||
# 18:00 – 18:06 → All OFF
|
||
# 18:06 → Fan ON
|
||
# 18:17 – 18:48 → PEAK ZONE → Iron AUTO OFF
|
||
# 18:49 → Iron AUTO restore
|
||
# 18:57 → Fan OFF
|
||
# ============================================================
|
||
|
||
import network
|
||
import machine
|
||
import math
|
||
import BlynkLib
|
||
from time import time
|
||
|
||
# ─── WiFi Credentials ────────────────────────────────────────
|
||
WIFI_SSID = "kritish"
|
||
WIFI_PASS = "pass"
|
||
|
||
# ─── Blynk Auth ──────────────────────────────────────────────
|
||
AUTH = "auth"
|
||
|
||
# ─── ADC Setup ───────────────────────────────────────────────
|
||
adc_voltage = machine.ADC(machine.Pin(34))
|
||
adc_current1 = machine.ADC(machine.Pin(35))
|
||
adc_current2 = machine.ADC(machine.Pin(32))
|
||
adc_voltage.atten(machine.ADC.ATTN_11DB)
|
||
adc_current1.atten(machine.ADC.ATTN_11DB)
|
||
adc_current2.atten(machine.ADC.ATTN_11DB)
|
||
|
||
# ─── Relay + LED ─────────────────────────────────────────────
|
||
r1 = machine.Pin(26, machine.Pin.OUT)
|
||
r2 = machine.Pin(27, machine.Pin.OUT)
|
||
led = machine.Pin(2, machine.Pin.OUT)
|
||
r1.value(1)
|
||
r2.value(1)
|
||
led.value(0)
|
||
|
||
# ─── Calibration ─────────────────────────────────────────────
|
||
ADC_MAX = 4095
|
||
ADC_VREF = 3.3
|
||
ZMPT_SENSITIVITY = 133.8 # voltage calibrated: actual=216V
|
||
ACS712_SENSITIVITY = 0.100 # V/A for 20A variant
|
||
ACS_ZERO_FAN = 0.8736
|
||
ACS_ZERO_IRON = 0.8746
|
||
CURR_FACTOR_FAN = 0.005
|
||
CURR_FACTOR_IRON = 0.055
|
||
CURRENT_THRESHOLD = 0.01 # below this → 0.00A
|
||
SAMPLE_COUNT = 500
|
||
|
||
# ─── Fake Time ───────────────────────────────────────────────
|
||
FAKE_START_HOUR = 18
|
||
FAKE_START_MIN = 0
|
||
fake_total_min = FAKE_START_HOUR * 60 + FAKE_START_MIN
|
||
|
||
# ─── Peak Window (fake IST minutes) ──────────────────────────
|
||
PEAK_START = 18 * 60 + 17 # 18:17
|
||
PEAK_END = 18 * 60 + 49 # 18:49
|
||
|
||
# ─── State ───────────────────────────────────────────────────
|
||
fan_on = False
|
||
iron_on = False
|
||
iron_wanted = False
|
||
dsm_active = False
|
||
# ─── WiFi Connect ────────────────────────────────────────────
|
||
sta_if = network.WLAN(network.STA_IF)
|
||
sta_if.active(True)
|
||
sta_if.connect(WIFI_SSID, WIFI_PASS)
|
||
print("Connecting to WiFi...")
|
||
while not sta_if.isconnected():
|
||
pass
|
||
print("WiFi Connected:", sta_if.ifconfig())
|
||
|
||
# ─── Blynk Init ──────────────────────────────────────────────
|
||
blynk = BlynkLib.Blynk(AUTH, insecure=True)
|
||
|
||
# ─── Blynk Switch Handlers ───────────────────────────────────
|
||
@blynk.on("V3")
|
||
def v3_handler(value):
|
||
global fan_on
|
||
fan_on = value[0] == '1'
|
||
r1.value(0 if fan_on else 1)
|
||
led.value(1 if (fan_on or iron_on) else 0)
|
||
print(f"# FAN {'ON' if fan_on else 'OFF'}")
|
||
|
||
@blynk.on("V4")
|
||
def v4_handler(value):
|
||
global iron_wanted
|
||
iron_wanted = value[0] == '1'
|
||
print(f"# IRON {'ON' if iron_wanted else 'OFF'} (requested)")
|
||
|
||
# ─── Sensor Functions ────────────────────────────────────────
|
||
def read_voltage_rms():
|
||
sum_sq = 0.0
|
||
for _ in range(SAMPLE_COUNT):
|
||
raw = adc_voltage.read()
|
||
v = (raw / ADC_MAX) * ADC_VREF
|
||
centered = v - (ADC_VREF / 2.0)
|
||
scaled = centered * ZMPT_SENSITIVITY
|
||
sum_sq += scaled * scaled
|
||
return math.sqrt(sum_sq / SAMPLE_COUNT)
|
||
|
||
def read_current_rms(adc, zero_offset, factor):
|
||
sum_sq = 0.0
|
||
for _ in range(SAMPLE_COUNT):
|
||
raw = adc.read()
|
||
v = (raw / ADC_MAX) * ADC_VREF
|
||
centered = v - zero_offset
|
||
amps = centered / ACS712_SENSITIVITY
|
||
sum_sq += amps * amps
|
||
rms = math.sqrt(sum_sq / SAMPLE_COUNT) * factor
|
||
return 0.0 if rms < CURRENT_THRESHOLD else rms
|
||
|
||
def get_readings():
|
||
v = read_voltage_rms()
|
||
i1 = read_current_rms(adc_current1, ACS_ZERO_FAN, CURR_FACTOR_FAN)
|
||
i2 = read_current_rms(adc_current2, ACS_ZERO_IRON, CURR_FACTOR_IRON)
|
||
|
||
if r1.value() == 1: i1 = 0.0
|
||
if r2.value() == 1: i2 = 0.0
|
||
if r1.value() == 1 and r2.value() == 1: v = 0.0
|
||
|
||
p1 = v * i1
|
||
p2 = v * i2
|
||
pt = p1 + p2
|
||
return v, p1, p2, pt
|
||
|
||
# ─── Fake Time ───────────────────────────────────────────────
|
||
def get_fake_time():
|
||
global fake_total_min
|
||
h = (fake_total_min // 60) % 24
|
||
m = fake_total_min % 60
|
||
current = fake_total_min
|
||
fake_total_min += 1
|
||
return h, m, current
|
||
|
||
# ─── DSM Peak Shifting Logic ─────────────────────────────────
|
||
def run_dsm(current_min):
|
||
global iron_on, dsm_active
|
||
|
||
is_peak = (PEAK_START <= current_min < PEAK_END)
|
||
|
||
if is_peak and iron_wanted and not dsm_active:
|
||
# Peak hour mein iron OFF karo
|
||
iron_on = False
|
||
r2.value(1)
|
||
dsm_active = True
|
||
led.value(1 if fan_on else 0)
|
||
print("# DSM: PEAK HOUR → Iron AUTO OFF!")
|
||
blynk.virtual_write(7, "⚠ PEAK! Iron AUTO OFF")
|
||
|
||
elif not is_peak and dsm_active:
|
||
|
||
if iron_wanted:
|
||
iron_on = True
|
||
r2.value(0)
|
||
dsm_active = False
|
||
led.value(1 if (fan_on or iron_on) else 0)
|
||
print("# DSM: Peak over → Iron RESTORED!")
|
||
blynk.virtual_write(7, "✓ Peak over. Iron restored!")
|
||
|
||
elif not is_peak and iron_wanted and not dsm_active:
|
||
|
||
iron_on = True
|
||
r2.value(0)
|
||
|
||
elif not iron_wanted:
|
||
iron_on = False
|
||
r2.value(1)
|
||
dsm_active = False
|
||
|
||
# ─── Main Loop ───────────────────────────────────────────────
|
||
print("# DSM Smart Meter — Code 2: Peak Shifting Mode")
|
||
print(f"# Peak window: 18:17 – 18:49 (fake IST)")
|
||
print("Time,Voltage,FanPower,IronPower,TotalPower,FanStatus,IronStatus,DSM")
|
||
|
||
timer = 0
|
||
|
||
while True:
|
||
blynk.run()
|
||
timer += 1
|
||
|
||
if timer >= 3000:
|
||
timer = 0
|
||
|
||
h, m, current_min = get_fake_time()
|
||
|
||
# DSM logic
|
||
run_dsm(current_min)
|
||
|
||
v, p1, p2, pt = get_readings()
|
||
time_str = f"{h:02d}:{m:02d}"
|
||
dsm_flag = 1 if dsm_active else 0
|
||
|
||
# CSV output
|
||
print(f"{time_str},{v:.2f},{p1:.1f},{p2:.1f},{pt:.1f},{1 if fan_on else 0},{1 if iron_on else 0},{dsm_flag}")
|
||
|
||
# Blynk upload
|
||
blynk.virtual_write(0, round(v, 2))
|
||
blynk.virtual_write(1, round(p1, 1))
|
||
blynk.virtual_write(2, round(p2, 1))
|
||
blynk.virtual_write(3, 1 if fan_on else 0)
|
||
blynk.virtual_write(4, 1 if iron_on else 0)
|
||
blynk.virtual_write(5, round(pt, 1))
|
||
blynk.virtual_write(6, round(pt, 1))
|
||
|
||
` },
|
||
{ name: "main.py", content: `# Velxio entry point — the project ships two standalone variants:
|
||
# dsm_code1_csv.py — smart metering with CSV serial output (runs here)
|
||
# dsm_code2_peakshift.py — peak-shift automation variant (open it and copy
|
||
# its contents over this import to try it instead)
|
||
import dsm_code1_csv
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "blynk", "blynk_cloud", "esp32", "micropython", "ota", "wifi"],
|
||
},
|
||
{
|
||
id: "100d-iot-environment-monitoring-with-anomaly-detection",
|
||
title: "IoT Environment Monitoring With Anomaly Detection",
|
||
description: "IoT Environment Monitoring With Anomaly Detection on ESP32 (MicroPython) \u2014 uses DHT, LDR, ThingSpeak, Wi-Fi. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "communication",
|
||
difficulty: "intermediate",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "main.py", content: `import network
|
||
import urequests as requests
|
||
import time
|
||
from machine import Pin, ADC
|
||
import dht
|
||
|
||
# --------------------------
|
||
# WiFi Credentials
|
||
# --------------------------
|
||
|
||
SSID = "yourssid"
|
||
PASS = "yourpassword"
|
||
# --------------------------
|
||
# ThingSpeak Details
|
||
# --------------------------
|
||
API_KEY = "yourapikey"
|
||
BASE_URL = "http://api.thingspeak.com/update"
|
||
|
||
# --------------------------a
|
||
# Sensors Setup
|
||
# --------------------------
|
||
d = dht.DHT22(Pin(2)) # Velxio emulates the DHT22 waveform (use DHT11 on real hw)
|
||
ldr = ADC(0) # A0 pin for LDR
|
||
|
||
# --------------------------
|
||
# Connect to WiFi
|
||
# --------------------------
|
||
def connect_wifi():
|
||
sta = network.WLAN(network.STA_IF)
|
||
sta.active(True)
|
||
if not sta.isconnected():
|
||
print("Connecting WiFi...")
|
||
sta.connect(SSID, PASS)
|
||
while not sta.isconnected():
|
||
time.sleep(0.5)
|
||
print("Connected:", sta.ifconfig())
|
||
|
||
connect_wifi()
|
||
|
||
# --------------------------
|
||
# Upload Loop
|
||
# --------------------------
|
||
while True:
|
||
try:
|
||
d.measure()
|
||
temp = d.temperature()
|
||
hum = d.humidity()
|
||
light = ldr.read()
|
||
|
||
print("Temp:", temp, "°C", "Hum:", hum, "%", "LDR:", light)
|
||
|
||
url = f"{BASE_URL}?api_key={API_KEY}&field1={temp}&field2={hum}&field3={light}"
|
||
r = requests.get(url)
|
||
print("Uploaded:", r.text)
|
||
r.close()
|
||
|
||
except Exception as e:
|
||
print("Error:", e)
|
||
|
||
time.sleep(15) # ThingSpeak min 15 sec
|
||
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [{"type": "wokwi-dht22", "id": "env-dht1", "x": 440, "y": 200, "properties": {"temperature": "28", "humidity": "65"}}],
|
||
wires: [{"id": "env-dht-vcc", "start": {"componentId": "arduino-uno", "pinName": "3V3"}, "end": {"componentId": "env-dht1", "pinName": "VCC"}, "color": "#ff4444"}, {"id": "env-dht-gnd", "start": {"componentId": "arduino-uno", "pinName": "GND"}, "end": {"componentId": "env-dht1", "pinName": "GND"}, "color": "#000000"}, {"id": "env-dht-data", "start": {"componentId": "arduino-uno", "pinName": "2"}, "end": {"componentId": "env-dht1", "pinName": "SDA"}, "color": "#22cc66"}],
|
||
tags: ["100-days", "dht", "esp32", "ldr", "micropython", "thingspeak", "wifi"],
|
||
},
|
||
{
|
||
id: "100d-iot-relay-control-web-server-raspberry-pi-pico-2w",
|
||
title: "IoT Relay Control Web Server (Raspberry Pi Pico 2W)",
|
||
description: "IoT Relay Control Web Server (Raspberry Pi Pico 2W) on Raspberry Pi Pico W (MicroPython) \u2014 uses Wi-Fi. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "communication",
|
||
difficulty: "intermediate",
|
||
boardType: "pi-pico-w",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "main.py", content: `import network
|
||
import socket
|
||
import machine
|
||
import time
|
||
|
||
# -------- Relay Setup --------
|
||
relay = machine.Pin(2, machine.Pin.OUT) # GP2 (active LOW)
|
||
relay_state = 1 # 1 = OFF, 0 = ON
|
||
relay.value(relay_state)
|
||
|
||
# -------- WiFi --------
|
||
ssid = "ssid"
|
||
password = "pass"
|
||
|
||
wlan = network.WLAN(network.STA_IF)
|
||
wlan.active(True)
|
||
wlan.connect(ssid, password)
|
||
|
||
print("Connecting...")
|
||
while not wlan.isconnected():
|
||
time.sleep(0.5)
|
||
|
||
ip = wlan.ifconfig()[0]
|
||
print("Connected at:", ip)
|
||
|
||
# -------- Web Page --------
|
||
def webpage(state):
|
||
checked = "checked" if state == 0 else ""
|
||
status = "ON" if state == 0 else "OFF"
|
||
|
||
return """
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>Relay Switch</title>
|
||
|
||
<style>
|
||
body {
|
||
background:#111;
|
||
color:white;
|
||
font-family:Arial;
|
||
text-align:center;
|
||
margin-top:60px;
|
||
}
|
||
.switch {
|
||
position: relative;
|
||
display: inline-block;
|
||
width: 80px;
|
||
height: 40px;
|
||
}
|
||
.switch input {
|
||
opacity: 0;
|
||
width: 0;
|
||
height: 0;
|
||
}
|
||
.slider {
|
||
position: absolute;
|
||
cursor: pointer;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background-color: red;
|
||
transition: .2s;
|
||
border-radius: 40px;
|
||
}
|
||
.slider:before {
|
||
position: absolute;
|
||
content: "";
|
||
height: 32px;
|
||
width: 32px;
|
||
left: 4px;
|
||
bottom: 4px;
|
||
background-color: white;
|
||
transition: .2s;
|
||
border-radius: 50%;
|
||
}
|
||
input:checked + .slider {
|
||
background-color: green;
|
||
}
|
||
input:checked + .slider:before {
|
||
transform: translateX(40px);
|
||
}
|
||
</style>
|
||
|
||
<script>
|
||
function send(cmd, state){
|
||
fetch(cmd);
|
||
document.getElementById("status").innerHTML = state;
|
||
history.pushState({}, "", cmd);
|
||
}
|
||
</script>
|
||
</head>
|
||
|
||
<body>
|
||
<h1>IOT RELAY WEB SWITCH</h1>
|
||
<h2>Status: <span id="status">""" + status + """</span></h2>
|
||
|
||
<label class="switch">
|
||
<input type="checkbox" """ + checked + """
|
||
onchange="
|
||
if(this.checked){
|
||
send('/on','ON');
|
||
} else {
|
||
send('/off','OFF');
|
||
}
|
||
">
|
||
<span class="slider"></span>
|
||
</label>
|
||
|
||
</body>
|
||
</html>
|
||
"""
|
||
|
||
# -------- Socket --------
|
||
s = socket.socket()
|
||
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||
s.bind((ip, 80))
|
||
s.listen(1)
|
||
|
||
print("Open browser: http://" + ip)
|
||
|
||
# -------- Main Loop --------
|
||
while True:
|
||
conn, addr = s.accept()
|
||
request = conn.recv(1024)
|
||
request = str(request)
|
||
|
||
if "/on" in request:
|
||
relay_state = 0
|
||
relay.value(relay_state)
|
||
|
||
elif "/off" in request:
|
||
relay_state = 1
|
||
relay.value(relay_state)
|
||
|
||
conn.send("HTTP/1.1 200 OK\\r\\nContent-Type: text/html\\r\\n\\r\\n")
|
||
conn.sendall(webpage(relay_state))
|
||
conn.close()
|
||
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "micropython", "pi-pico-w", "pico-w", "wifi"],
|
||
},
|
||
{
|
||
id: "100d-iot-smart-irrigation-system",
|
||
title: "IoT Smart Irrigation System",
|
||
description: "IoT Smart Irrigation System on ESP32 (MicroPython) \u2014 uses Blynk, Blynk Cloud, Wi-Fi. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "communication",
|
||
difficulty: "advanced",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "BlynkLib.py", content: `# 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()
|
||
# Velxio patch: bound the blocking connect so an unreachable cloud
|
||
# raises OSError instead of freezing the sketch forever.
|
||
s.settimeout(10)
|
||
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)
|
||
|
||
|
||
` },
|
||
{ name: "main.py", content: `'''Copyright (c) 2026 Kritish Mohapatra'''
|
||
|
||
import network
|
||
import time
|
||
from machine import Pin, ADC
|
||
import BlynkLib
|
||
|
||
# 🔑 Blynk
|
||
BLYNK_AUTH = "auth"
|
||
|
||
# 🌐 WiFi
|
||
WIFI_SSID = "kritish"
|
||
WIFI_PASS = "@pass"
|
||
|
||
# 📍 Pins
|
||
SOIL_PIN = 3 # ADC
|
||
RELAY_PIN = 1 # Relay (active LOW)
|
||
|
||
# 🌱 Soil ADC setup
|
||
soil = ADC(Pin(SOIL_PIN))
|
||
soil.atten(ADC.ATTN_11DB) # 0–3.3V
|
||
soil.width(ADC.WIDTH_12BIT) # 0–4095
|
||
|
||
# 💧 Relay setup
|
||
relay = Pin(RELAY_PIN, Pin.OUT)
|
||
relay.on() # Pump OFF initially
|
||
|
||
# 🌐 WiFi connect (safe)
|
||
wifi = network.WLAN(network.STA_IF)
|
||
wifi.active(False)
|
||
time.sleep(1)
|
||
wifi.active(True)
|
||
time.sleep(1)
|
||
wifi.connect(WIFI_SSID, WIFI_PASS)
|
||
|
||
print("Connecting WiFi", end="")
|
||
t0 = time.time()
|
||
while not wifi.isconnected():
|
||
print(".", end="")
|
||
time.sleep(1)
|
||
if time.time() - t0 > 20:
|
||
print("\\nWiFi failed")
|
||
break
|
||
|
||
if wifi.isconnected():
|
||
print("\\nWiFi OK:", wifi.ifconfig())
|
||
|
||
# 📲 Blynk init
|
||
# ── Velxio adaptation ──────────────────────────────────────────────────────────────────
|
||
# The vendored BlynkLib bounds its connect() with a timeout (Velxio patch),
|
||
# so an unreachable cloud raises OSError here instead of freezing the demo;
|
||
# fall back to an offline no-op Blynk object in that case.
|
||
class _OfflineBlynk:
|
||
def on(self, *_a, **_k):
|
||
def _deco(f):
|
||
return f
|
||
return _deco
|
||
def run(self):
|
||
pass
|
||
def virtual_write(self, *_a):
|
||
pass
|
||
|
||
|
||
try:
|
||
blynk = BlynkLib.Blynk(BLYNK_AUTH, insecure=True)
|
||
except OSError as e:
|
||
print("Blynk Cloud unreachable, running offline:", e)
|
||
blynk = _OfflineBlynk()
|
||
|
||
# 🔘 Manual Pump Control (V1)
|
||
@blynk.on("V1")
|
||
def pump_control(value):
|
||
if int(value[0]) == 1:
|
||
relay.off() # Pump ON
|
||
print("Pump ON")
|
||
else:
|
||
relay.on() # Pump OFF
|
||
print("Pump OFF")
|
||
|
||
# 📊 Read soil & send to Blynk
|
||
def read_soil():
|
||
raw = soil.read() # 0–4095
|
||
moisture = int((4095 - raw) * 100 / 4095)
|
||
moisture = max(0, min(100, moisture))
|
||
print("Soil:", moisture, "%")
|
||
blynk.virtual_write(5, moisture)
|
||
|
||
# 🔁 Main loop
|
||
last = time.ticks_ms()
|
||
while True:
|
||
if wifi.isconnected():
|
||
blynk.run()
|
||
|
||
if time.ticks_diff(time.ticks_ms(), last) > 2000:
|
||
read_soil()
|
||
last = time.ticks_ms()
|
||
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "blynk", "blynk_cloud", "esp32", "micropython", "wifi"],
|
||
},
|
||
{
|
||
id: "100d-joystick-controlled-servo",
|
||
title: "Joystick Controlled Servo",
|
||
description: "Joystick Controlled Servo on ESP32 (MicroPython) \u2014 uses Servo. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "robotics",
|
||
difficulty: "beginner",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "main.py", content: `import machine
|
||
import time
|
||
|
||
# ── Hardware setup ────────────────────────────────────────────
|
||
vrx = machine.ADC(machine.Pin(26)) # Joystick X axis
|
||
servo = machine.PWM(machine.Pin(0))
|
||
servo.freq(50) # SG90 = 50Hz
|
||
|
||
# ── Servo helper ──────────────────────────────────────────────
|
||
def set_angle(angle):
|
||
# 0° = 1000us, 90° = 1500us, 180° = 2000us
|
||
us = int(1000 + (angle / 180) * 1000)
|
||
duty = int(us * 65535 / 20000)
|
||
servo.duty_u16(duty)
|
||
|
||
# ── Map helper ────────────────────────────────────────────────
|
||
def map_val(x, in_min, in_max, out_min, out_max):
|
||
return int((x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min)
|
||
|
||
# ── Main loop ─────────────────────────────────────────────────
|
||
print("Joystick Servo Control ready!")
|
||
|
||
while True:
|
||
# Average 5 samples for smooth reading
|
||
x_raw = sum(vrx.read_u16() for _ in range(5)) // 5
|
||
|
||
# Map joystick (0-65535) to angle (0-180)
|
||
angle = map_val(x_raw, 0, 65535, 0, 180)
|
||
|
||
set_angle(angle)
|
||
print(f"Raw: {x_raw} Angle: {angle}°")
|
||
|
||
time.sleep(0.05)
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "esp32", "micropython", "servo"],
|
||
},
|
||
{
|
||
id: "100d-joystick-direction-display-with-oled",
|
||
title: "Joystick Direction Display with OLED",
|
||
description: "Joystick Direction Display with OLED on Raspberry Pi Pico (MicroPython) \u2014 uses OLED. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "displays",
|
||
difficulty: "intermediate",
|
||
boardType: "raspberry-pi-pico",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "main.py", content: `from machine import ADC, Pin, I2C
|
||
import ssd1306
|
||
import time
|
||
|
||
# Joystick
|
||
vrx = ADC(Pin(26))
|
||
vry = ADC(Pin(27))
|
||
sw = Pin(15, Pin.IN, Pin.PULL_UP)
|
||
|
||
# OLED
|
||
i2c = I2C(0, sda=Pin(0), scl=Pin(1), freq=400000)
|
||
oled = ssd1306.SSD1306_I2C(128, 64, i2c)
|
||
|
||
def show_direction(direction, x, y, btn):
|
||
oled.fill(0)
|
||
|
||
# Big arrow in center
|
||
if direction == "UP":
|
||
oled.text("^", 58, 20, 1)
|
||
oled.text("UP", 52, 35, 1)
|
||
elif direction == "DOWN":
|
||
oled.text("v", 58, 20, 1)
|
||
oled.text("DOWN", 48, 35, 1)
|
||
elif direction == "RIGHT":
|
||
oled.text(">", 58, 20, 1)
|
||
oled.text("RIGHT", 44, 35, 1)
|
||
elif direction == "LEFT":
|
||
oled.text("<", 58, 20, 1)
|
||
oled.text("LEFT", 48, 35, 1)
|
||
elif direction == "PRESSED":
|
||
oled.text("* SW *", 40, 20, 1)
|
||
oled.text("PRESSED", 36, 35, 1)
|
||
else:
|
||
oled.text("+", 58, 20, 1)
|
||
oled.text("CENTER", 40, 35, 1)
|
||
|
||
# Raw values at bottom
|
||
oled.text(f"X:{x:4d} Y:{y:4d}", 0, 52, 1)
|
||
|
||
oled.show()
|
||
|
||
print("Pico 2 Joystick + OLED Ready!")
|
||
|
||
while True:
|
||
x = vrx.read_u16() >> 4
|
||
y = vry.read_u16() >> 4
|
||
btn = sw.value()
|
||
|
||
if btn == 0:
|
||
direction = "PRESSED"
|
||
elif x > 3000:
|
||
direction = "RIGHT"
|
||
elif x < 1000:
|
||
direction = "LEFT"
|
||
elif y > 3000:
|
||
direction = "DOWN"
|
||
elif y < 1000:
|
||
direction = "UP"
|
||
else:
|
||
direction = "CENTER"
|
||
|
||
show_direction(direction, x, y, btn)
|
||
print(f"X={x:4d} Y={y:4d} SW={btn} -> {direction}")
|
||
time.sleep_ms(150)` },
|
||
{ name: "ssd1306.py", content: `# MicroPython SSD1306 OLED driver, I2C and SPI interfaces
|
||
|
||
from micropython import const
|
||
import framebuf
|
||
|
||
|
||
# register definitions
|
||
SET_CONTRAST = const(0x81)
|
||
SET_ENTIRE_ON = const(0xA4)
|
||
SET_NORM_INV = const(0xA6)
|
||
SET_DISP = const(0xAE)
|
||
SET_MEM_ADDR = const(0x20)
|
||
SET_COL_ADDR = const(0x21)
|
||
SET_PAGE_ADDR = const(0x22)
|
||
SET_DISP_START_LINE = const(0x40)
|
||
SET_SEG_REMAP = const(0xA0)
|
||
SET_MUX_RATIO = const(0xA8)
|
||
SET_COM_OUT_DIR = const(0xC0)
|
||
SET_DISP_OFFSET = const(0xD3)
|
||
SET_COM_PIN_CFG = const(0xDA)
|
||
SET_DISP_CLK_DIV = const(0xD5)
|
||
SET_PRECHARGE = const(0xD9)
|
||
SET_VCOM_DESEL = const(0xDB)
|
||
SET_CHARGE_PUMP = const(0x8D)
|
||
|
||
# Subclassing FrameBuffer provides support for graphics primitives
|
||
# http://docs.micropython.org/en/latest/pyboard/library/framebuf.html
|
||
class SSD1306(framebuf.FrameBuffer):
|
||
def __init__(self, width, height, external_vcc):
|
||
self.width = width
|
||
self.height = height
|
||
self.external_vcc = external_vcc
|
||
self.pages = self.height // 8
|
||
self.buffer = bytearray(self.pages * self.width)
|
||
super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB)
|
||
self.init_display()
|
||
|
||
def init_display(self):
|
||
for cmd in (
|
||
SET_DISP | 0x00, # off
|
||
# address setting
|
||
SET_MEM_ADDR,
|
||
0x00, # horizontal
|
||
# resolution and layout
|
||
SET_DISP_START_LINE | 0x00,
|
||
SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0
|
||
SET_MUX_RATIO,
|
||
self.height - 1,
|
||
SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0
|
||
SET_DISP_OFFSET,
|
||
0x00,
|
||
SET_COM_PIN_CFG,
|
||
0x02 if self.width > 2 * self.height else 0x12,
|
||
# timing and driving scheme
|
||
SET_DISP_CLK_DIV,
|
||
0x80,
|
||
SET_PRECHARGE,
|
||
0x22 if self.external_vcc else 0xF1,
|
||
SET_VCOM_DESEL,
|
||
0x30, # 0.83*Vcc
|
||
# display
|
||
SET_CONTRAST,
|
||
0xFF, # maximum
|
||
SET_ENTIRE_ON, # output follows RAM contents
|
||
SET_NORM_INV, # not inverted
|
||
# charge pump
|
||
SET_CHARGE_PUMP,
|
||
0x10 if self.external_vcc else 0x14,
|
||
SET_DISP | 0x01,
|
||
): # on
|
||
self.write_cmd(cmd)
|
||
self.fill(0)
|
||
self.show()
|
||
|
||
def poweroff(self):
|
||
self.write_cmd(SET_DISP | 0x00)
|
||
|
||
def poweron(self):
|
||
self.write_cmd(SET_DISP | 0x01)
|
||
|
||
def contrast(self, contrast):
|
||
self.write_cmd(SET_CONTRAST)
|
||
self.write_cmd(contrast)
|
||
|
||
def invert(self, invert):
|
||
self.write_cmd(SET_NORM_INV | (invert & 1))
|
||
|
||
def show(self):
|
||
x0 = 0
|
||
x1 = self.width - 1
|
||
if self.width == 64:
|
||
# displays with width of 64 pixels are shifted by 32
|
||
x0 += 32
|
||
x1 += 32
|
||
self.write_cmd(SET_COL_ADDR)
|
||
self.write_cmd(x0)
|
||
self.write_cmd(x1)
|
||
self.write_cmd(SET_PAGE_ADDR)
|
||
self.write_cmd(0)
|
||
self.write_cmd(self.pages - 1)
|
||
self.write_data(self.buffer)
|
||
|
||
|
||
class SSD1306_I2C(SSD1306):
|
||
def __init__(self, width, height, i2c, addr=0x3C, external_vcc=False):
|
||
self.i2c = i2c
|
||
self.addr = addr
|
||
self.temp = bytearray(2)
|
||
self.write_list = [b"\\x40", None] # Co=0, D/C#=1
|
||
super().__init__(width, height, external_vcc)
|
||
|
||
def write_cmd(self, cmd):
|
||
self.temp[0] = 0x80 # Co=1, D/C#=0
|
||
self.temp[1] = cmd
|
||
self.i2c.writeto(self.addr, self.temp)
|
||
|
||
def write_data(self, buf):
|
||
self.write_list[1] = buf
|
||
self.i2c.writevto(self.addr, self.write_list)
|
||
|
||
|
||
class SSD1306_SPI(SSD1306):
|
||
def __init__(self, width, height, spi, dc, res, cs, external_vcc=False):
|
||
self.rate = 10 * 1024 * 1024
|
||
dc.init(dc.OUT, value=0)
|
||
res.init(res.OUT, value=0)
|
||
cs.init(cs.OUT, value=1)
|
||
self.spi = spi
|
||
self.dc = dc
|
||
self.res = res
|
||
self.cs = cs
|
||
import time
|
||
|
||
self.res(1)
|
||
time.sleep_ms(1)
|
||
self.res(0)
|
||
time.sleep_ms(10)
|
||
self.res(1)
|
||
super().__init__(width, height, external_vcc)
|
||
|
||
def write_cmd(self, cmd):
|
||
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
|
||
self.cs(1)
|
||
self.dc(0)
|
||
self.cs(0)
|
||
self.spi.write(bytearray([cmd]))
|
||
self.cs(1)
|
||
|
||
def write_data(self, buf):
|
||
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
|
||
self.cs(1)
|
||
self.dc(1)
|
||
self.cs(0)
|
||
self.spi.write(buf)
|
||
self.cs(1)` },
|
||
],
|
||
code: '',
|
||
components: [{"type": "wokwi-ssd1306", "id": "joy-oled1", "x": 420, "y": 60, "properties": {}}, {"type": "wokwi-analog-joystick", "id": "joy-stick1", "x": 440, "y": 240, "properties": {}}],
|
||
wires: [{"id": "joy-oled-vcc", "start": {"componentId": "arduino-uno", "pinName": "3V3"}, "end": {"componentId": "joy-oled1", "pinName": "3V3"}, "color": "#ff4444"}, {"id": "joy-oled-gnd", "start": {"componentId": "arduino-uno", "pinName": "GND"}, "end": {"componentId": "joy-oled1", "pinName": "GND"}, "color": "#000000"}, {"id": "joy-oled-sda", "start": {"componentId": "arduino-uno", "pinName": "GP0"}, "end": {"componentId": "joy-oled1", "pinName": "DATA"}, "color": "#22aaff"}, {"id": "joy-oled-scl", "start": {"componentId": "arduino-uno", "pinName": "GP1"}, "end": {"componentId": "joy-oled1", "pinName": "CLK"}, "color": "#ff8800"}, {"id": "joy-vcc", "start": {"componentId": "arduino-uno", "pinName": "3V3"}, "end": {"componentId": "joy-stick1", "pinName": "VCC"}, "color": "#ff4444"}, {"id": "joy-gnd", "start": {"componentId": "arduino-uno", "pinName": "GND.1"}, "end": {"componentId": "joy-stick1", "pinName": "GND"}, "color": "#000000"}, {"id": "joy-horz", "start": {"componentId": "arduino-uno", "pinName": "GP26"}, "end": {"componentId": "joy-stick1", "pinName": "HORZ"}, "color": "#22cc66"}, {"id": "joy-vert", "start": {"componentId": "arduino-uno", "pinName": "GP27"}, "end": {"componentId": "joy-stick1", "pinName": "VERT"}, "color": "#22aaff"}, {"id": "joy-sel", "start": {"componentId": "arduino-uno", "pinName": "GP15"}, "end": {"componentId": "joy-stick1", "pinName": "SEL"}, "color": "#ff8800"}],
|
||
tags: ["100-days", "i2c_oled", "micropython", "raspberry-pi-pico", "rp2040"],
|
||
},
|
||
{
|
||
id: "100d-mq4-gas-leak-detection-system-using-esp32-and-micropython",
|
||
title: "MQ4 Gas Leak Detection System using ESP32 and MicroPython",
|
||
description: "MQ4 Gas Leak Detection System using ESP32 and MicroPython on ESP32 (MicroPython). From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "basics",
|
||
difficulty: "beginner",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "main.py", content: `from machine import ADC, Pin
|
||
import time
|
||
|
||
# MQ4 gas sensor setup
|
||
mq4 = ADC(Pin(34)) # Analog input pin
|
||
mq4.atten(ADC.ATTN_11DB) # Full voltage range
|
||
mq4.width(ADC.WIDTH_10BIT) # 10-bit resolution
|
||
|
||
# Buzzer setup
|
||
buzzer = Pin(15, Pin.OUT)
|
||
|
||
# Threshold for gas alert (tune this based on testing)
|
||
GAS_THRESHOLD = 240
|
||
|
||
while True:
|
||
gas_value = mq4.read()
|
||
voltage = gas_value * 3.3 / 1023
|
||
|
||
print("Gas reading (raw):", gas_value)
|
||
print("Voltage:", round(voltage, 2), "V")
|
||
|
||
# Alert condition
|
||
if gas_value > GAS_THRESHOLD:
|
||
buzzer.on()
|
||
print("⚠️ Gas leak detected! Buzzer ON")
|
||
else:
|
||
buzzer.off()
|
||
|
||
print("----------------------------")
|
||
time.sleep(2)
|
||
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "esp32", "micropython"],
|
||
},
|
||
{
|
||
id: "100d-mq7-co-gas-detection-esp32",
|
||
title: "MQ7 CO Gas Detection ESP32",
|
||
description: "MQ7 CO Gas Detection ESP32 on ESP32 (MicroPython). From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "basics",
|
||
difficulty: "beginner",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "main.py", content: `from machine import ADC, Pin
|
||
import time
|
||
|
||
# MQ-7 analog output on GPIO 35
|
||
mq7 = ADC(Pin(35))
|
||
mq7.atten(ADC.ATTN_11DB) # 0–3.3V
|
||
mq7.width(ADC.WIDTH_12BIT) # 12-bit ADC
|
||
|
||
print("MQ-7 CO Sensor Test")
|
||
print("Warming up MQ-7 sensor (60 seconds)...")
|
||
|
||
# Warm-up phase
|
||
time.sleep(60)
|
||
|
||
print("Start reading MQ-7 values")
|
||
|
||
while True:
|
||
readings = []
|
||
for i in range(10):
|
||
readings.append(mq7.read())
|
||
time.sleep(0.1)
|
||
|
||
avg_val = sum(readings) // len(readings)
|
||
print("MQ-7 ADC Value:", avg_val)
|
||
|
||
time.sleep(2)
|
||
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "esp32", "micropython"],
|
||
},
|
||
{
|
||
id: "100d-mq-135-gas-sensor-with-esp32-micropython",
|
||
title: "MQ 135 Gas Sensor with ESP32 (MicroPython)",
|
||
description: "MQ 135 Gas Sensor with ESP32 (MicroPython) on ESP32 (MicroPython) \u2014 uses OTA. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "sensors",
|
||
difficulty: "advanced",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "main.py", content: `from machine import ADC, Pin
|
||
import time
|
||
|
||
# MQ-135 connected to GPIO 34
|
||
mq135 = ADC(Pin(34))
|
||
mq135.atten(ADC.ATTN_11DB) # Allow 0–3.3V
|
||
mq135.width(ADC.WIDTH_12BIT) # 12-bit ADC (0–4095)
|
||
|
||
print("MQ-135 Sensor Test")
|
||
print("Warming up sensor...")
|
||
|
||
time.sleep(10) # short warm-up for testing
|
||
|
||
while True:
|
||
total = 0
|
||
samples = 10
|
||
|
||
for i in range(samples):
|
||
val = mq135.read()
|
||
total += val
|
||
time.sleep(0.1)
|
||
|
||
avg_val = total // samples
|
||
|
||
print("MQ-135 ADC Value:", avg_val)
|
||
time.sleep(2)
|
||
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "esp32", "micropython", "ota"],
|
||
},
|
||
{
|
||
id: "100d-micropython-watch",
|
||
title: "MicroPython Watch",
|
||
description: "MicroPython Watch on ESP32 (MicroPython) \u2014 uses OLED, Wi-Fi. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "displays",
|
||
difficulty: "intermediate",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "main.py", content: `import network
|
||
import ntptime
|
||
import urequests
|
||
import ssd1306
|
||
import utime
|
||
from machine import Pin, I2C
|
||
|
||
last_weather_time = 0
|
||
weather_temp = 0
|
||
|
||
# Velxio decodes the hardware I2C controller only (no bit-banged buses)
|
||
i2c = I2C(0, sda=Pin(5), scl=Pin(6), freq=100000)
|
||
oled=ssd1306.SSD1306_I2C(128, 64, i2c)
|
||
|
||
ssid="ssid"
|
||
password="pass"
|
||
API_KEY = "8ac1a66e4d9097"
|
||
|
||
|
||
|
||
def connect_wifi():
|
||
wlan=network.WLAN(network.STA_IF)
|
||
wlan.active(True)
|
||
wlan.connect(ssid, password)
|
||
|
||
oled.fill(0)
|
||
oled.text("Connecting...", 0, 20, 1)
|
||
oled.show()
|
||
|
||
while not wlan.isconnected():
|
||
utime.sleep(1)
|
||
oled.fill(0)
|
||
oled.text("Wifi ok!", 20, 20, 1)
|
||
oled.text(wlan.ifconfig()[0], 0, 35, 1)
|
||
oled.show()
|
||
utime.sleep(2)
|
||
def show_greeting():
|
||
oled.fill(0)
|
||
oled.text("Hi Kritish!", 20, 28, 1)
|
||
oled.show()
|
||
utime.sleep(2)
|
||
def sync_time():
|
||
try:
|
||
ntptime.settime()
|
||
except:
|
||
pass
|
||
def get_time():
|
||
IST=5*3600+30*60
|
||
return utime.localtime(utime.time()+IST)
|
||
def show_time():
|
||
lt=get_time()
|
||
|
||
h= "{:02d}".format(lt[3])
|
||
m="{:02d}".format(lt[4])
|
||
day="{:02d}".format(lt[2])
|
||
month="{:02d}".format(lt[1])
|
||
yr="{:02d}".format(lt[0])
|
||
|
||
|
||
|
||
oled.fill(0)
|
||
oled.hline(0, 0, 128, 1) # top line
|
||
oled.hline(0, 63, 128, 1) # bottom line
|
||
oled.text(h+":"+m,30, 20, 1)
|
||
oled.text(day+"/"+month+"/"+yr, 20, 35, 1)
|
||
global last_weather_time, weather_temp
|
||
if utime.time() - last_weather_time >= 600:
|
||
weather_temp = get_weather()
|
||
last_weather_time = utime.time()
|
||
oled.text(str(int(weather_temp))+" Cel", 45, 50, 1)
|
||
oled.show()
|
||
|
||
def get_weather():
|
||
url = "http://api.openweathermap.org/data/2.5/weather?q=Bhubaneswar,IN&appid={}&units=metric".format(API_KEY)
|
||
try:
|
||
r = urequests.get(url)
|
||
data = r.json()
|
||
r.close()
|
||
return data["main"]["temp"]
|
||
except Exception as e:
|
||
print("Weather fetch failed (offline?):", e)
|
||
return weather_temp # keep the last known value
|
||
def show_logo():
|
||
oled.fill(0)
|
||
oled.fill_rect(0, 0, 32, 32, 1)
|
||
oled.fill_rect(2, 2, 28, 28, 0)
|
||
oled.vline(9, 8, 22, 1)
|
||
oled.vline(16, 2, 22, 1)
|
||
oled.vline(23, 8, 22, 1)
|
||
oled.fill_rect(26, 24, 2, 4, 1)
|
||
oled.text('MicroPython', 40, 0, 1)
|
||
oled.text('Clock', 40, 12, 1)
|
||
oled.text('By Kritish', 40, 24, 1)
|
||
oled.show()
|
||
utime.sleep(2)
|
||
connect_wifi()
|
||
show_greeting()
|
||
show_logo()
|
||
sync_time()
|
||
while True:
|
||
show_time()
|
||
utime.sleep(1)
|
||
|
||
` },
|
||
{ name: "ssd1306.py", content: `# MicroPython SSD1306 OLED driver, I2C and SPI interfaces
|
||
|
||
from micropython import const
|
||
import framebuf
|
||
|
||
|
||
# register definitions
|
||
SET_CONTRAST = const(0x81)
|
||
SET_ENTIRE_ON = const(0xA4)
|
||
SET_NORM_INV = const(0xA6)
|
||
SET_DISP = const(0xAE)
|
||
SET_MEM_ADDR = const(0x20)
|
||
SET_COL_ADDR = const(0x21)
|
||
SET_PAGE_ADDR = const(0x22)
|
||
SET_DISP_START_LINE = const(0x40)
|
||
SET_SEG_REMAP = const(0xA0)
|
||
SET_MUX_RATIO = const(0xA8)
|
||
SET_COM_OUT_DIR = const(0xC0)
|
||
SET_DISP_OFFSET = const(0xD3)
|
||
SET_COM_PIN_CFG = const(0xDA)
|
||
SET_DISP_CLK_DIV = const(0xD5)
|
||
SET_PRECHARGE = const(0xD9)
|
||
SET_VCOM_DESEL = const(0xDB)
|
||
SET_CHARGE_PUMP = const(0x8D)
|
||
|
||
# Subclassing FrameBuffer provides support for graphics primitives
|
||
# http://docs.micropython.org/en/latest/pyboard/library/framebuf.html
|
||
class SSD1306(framebuf.FrameBuffer):
|
||
def __init__(self, width, height, external_vcc):
|
||
self.width = width
|
||
self.height = height
|
||
self.external_vcc = external_vcc
|
||
self.pages = self.height // 8
|
||
self.buffer = bytearray(self.pages * self.width)
|
||
super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB)
|
||
self.init_display()
|
||
|
||
def init_display(self):
|
||
for cmd in (
|
||
SET_DISP | 0x00, # off
|
||
# address setting
|
||
SET_MEM_ADDR,
|
||
0x00, # horizontal
|
||
# resolution and layout
|
||
SET_DISP_START_LINE | 0x00,
|
||
SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0
|
||
SET_MUX_RATIO,
|
||
self.height - 1,
|
||
SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0
|
||
SET_DISP_OFFSET,
|
||
0x00,
|
||
SET_COM_PIN_CFG,
|
||
0x02 if self.width > 2 * self.height else 0x12,
|
||
# timing and driving scheme
|
||
SET_DISP_CLK_DIV,
|
||
0x80,
|
||
SET_PRECHARGE,
|
||
0x22 if self.external_vcc else 0xF1,
|
||
SET_VCOM_DESEL,
|
||
0x30, # 0.83*Vcc
|
||
# display
|
||
SET_CONTRAST,
|
||
0xFF, # maximum
|
||
SET_ENTIRE_ON, # output follows RAM contents
|
||
SET_NORM_INV, # not inverted
|
||
# charge pump
|
||
SET_CHARGE_PUMP,
|
||
0x10 if self.external_vcc else 0x14,
|
||
SET_DISP | 0x01,
|
||
): # on
|
||
self.write_cmd(cmd)
|
||
self.fill(0)
|
||
self.show()
|
||
|
||
def poweroff(self):
|
||
self.write_cmd(SET_DISP | 0x00)
|
||
|
||
def poweron(self):
|
||
self.write_cmd(SET_DISP | 0x01)
|
||
|
||
def contrast(self, contrast):
|
||
self.write_cmd(SET_CONTRAST)
|
||
self.write_cmd(contrast)
|
||
|
||
def invert(self, invert):
|
||
self.write_cmd(SET_NORM_INV | (invert & 1))
|
||
|
||
def show(self):
|
||
x0 = 0
|
||
x1 = self.width - 1
|
||
if self.width == 64:
|
||
# displays with width of 64 pixels are shifted by 32
|
||
x0 += 32
|
||
x1 += 32
|
||
self.write_cmd(SET_COL_ADDR)
|
||
self.write_cmd(x0)
|
||
self.write_cmd(x1)
|
||
self.write_cmd(SET_PAGE_ADDR)
|
||
self.write_cmd(0)
|
||
self.write_cmd(self.pages - 1)
|
||
self.write_data(self.buffer)
|
||
|
||
|
||
class SSD1306_I2C(SSD1306):
|
||
def __init__(self, width, height, i2c, addr=0x3C, external_vcc=False):
|
||
self.i2c = i2c
|
||
self.addr = addr
|
||
self.temp = bytearray(2)
|
||
self.write_list = [b"\\x40", None] # Co=0, D/C#=1
|
||
super().__init__(width, height, external_vcc)
|
||
|
||
def write_cmd(self, cmd):
|
||
self.temp[0] = 0x80 # Co=1, D/C#=0
|
||
self.temp[1] = cmd
|
||
self.i2c.writeto(self.addr, self.temp)
|
||
|
||
def write_data(self, buf):
|
||
self.write_list[1] = buf
|
||
self.i2c.writevto(self.addr, self.write_list)
|
||
|
||
|
||
class SSD1306_SPI(SSD1306):
|
||
def __init__(self, width, height, spi, dc, res, cs, external_vcc=False):
|
||
self.rate = 10 * 1024 * 1024
|
||
dc.init(dc.OUT, value=0)
|
||
res.init(res.OUT, value=0)
|
||
cs.init(cs.OUT, value=1)
|
||
self.spi = spi
|
||
self.dc = dc
|
||
self.res = res
|
||
self.cs = cs
|
||
import time
|
||
|
||
self.res(1)
|
||
time.sleep_ms(1)
|
||
self.res(0)
|
||
time.sleep_ms(10)
|
||
self.res(1)
|
||
super().__init__(width, height, external_vcc)
|
||
|
||
def write_cmd(self, cmd):
|
||
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
|
||
self.cs(1)
|
||
self.dc(0)
|
||
self.cs(0)
|
||
self.spi.write(bytearray([cmd]))
|
||
self.cs(1)
|
||
|
||
def write_data(self, buf):
|
||
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
|
||
self.cs(1)
|
||
self.dc(1)
|
||
self.cs(0)
|
||
self.spi.write(buf)
|
||
self.cs(1)
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [{"type": "wokwi-ssd1306", "id": "watch-oled1", "x": 420, "y": 60, "properties": {}}],
|
||
wires: [{"id": "watch-oled-vcc", "start": {"componentId": "arduino-uno", "pinName": "3V3"}, "end": {"componentId": "watch-oled1", "pinName": "3V3"}, "color": "#ff4444"}, {"id": "watch-oled-gnd", "start": {"componentId": "arduino-uno", "pinName": "GND"}, "end": {"componentId": "watch-oled1", "pinName": "GND"}, "color": "#000000"}, {"id": "watch-oled-sda", "start": {"componentId": "arduino-uno", "pinName": "5"}, "end": {"componentId": "watch-oled1", "pinName": "DATA"}, "color": "#22aaff"}, {"id": "watch-oled-scl", "start": {"componentId": "arduino-uno", "pinName": "6"}, "end": {"componentId": "watch-oled1", "pinName": "CLK"}, "color": "#ff8800"}],
|
||
tags: ["100-days", "esp32", "i2c_oled", "micropython", "wifi"],
|
||
},
|
||
{
|
||
id: "100d-ntp-synchronized-digital-clock-using-esp32-and-max7219",
|
||
title: "NTP Synchronized Digital Clock using ESP32 & MAX7219",
|
||
description: "NTP Synchronized Digital Clock using ESP32 & MAX7219 on ESP32 (MicroPython) \u2014 uses MAX7219, Wi-Fi. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "displays",
|
||
difficulty: "intermediate",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "main.py", content: `
|
||
|
||
from machine import Pin, SPI
|
||
import max7219
|
||
import time
|
||
import network
|
||
import ntptime
|
||
|
||
# ========== WIFI CONFIG ==========
|
||
SSID = "Wokwi-GUEST"
|
||
PASSWORD = ""
|
||
|
||
# ========== CONNECT WIFI ==========
|
||
wlan = network.WLAN(network.STA_IF)
|
||
wlan.active(True)
|
||
wlan.connect(SSID, PASSWORD)
|
||
|
||
print("Connecting WiFi...")
|
||
while not wlan.isconnected():
|
||
time.sleep(1)
|
||
|
||
print("WiFi Connected:", wlan.ifconfig())
|
||
|
||
# ========== NTP TIME SYNC ==========
|
||
print("Syncing time from NTP...")
|
||
ntptime.settime() # UTC time
|
||
|
||
# ========== SPI MAX7219 ==========
|
||
spi = SPI(1, baudrate=10000000, polarity=1, phase=0,
|
||
sck=Pin(4), mosi=Pin(2))
|
||
cs = Pin(5, Pin.OUT)
|
||
|
||
display = max7219.Matrix8x8(spi, cs, 5) # 5 matrices
|
||
display.brightness(5)
|
||
|
||
# Colon draw function
|
||
def draw_colon(x):
|
||
display.pixel(x, 2, 1)
|
||
display.pixel(x, 5, 1)
|
||
|
||
# Timezone offset (India = UTC +5:30)
|
||
TZ_OFFSET = 5*3600 + 30*60
|
||
|
||
# ========== MAIN LOOP ==========
|
||
while True:
|
||
t = time.time() + TZ_OFFSET
|
||
tm = time.localtime(t)
|
||
|
||
hh = "{:02d}".format(tm[3])
|
||
mm = "{:02d}".format(tm[4])
|
||
ss = "{:02d}".format(tm[5])
|
||
|
||
# SERIAL PRINT
|
||
print("TIME:", hh, mm)
|
||
|
||
# DISPLAY
|
||
display.fill(0)
|
||
display.text(hh, 0, 0, 1)
|
||
draw_colon(18)
|
||
draw_colon(19)
|
||
display.text(mm, 22, 0, 1)
|
||
display.show()
|
||
|
||
time.sleep(1) ` },
|
||
{ name: "max7219.py", content: `"""
|
||
MicroPython max7219 cascadable 8x8 LED matrix driver
|
||
https://github.com/mcauser/micropython-max7219
|
||
|
||
MIT License
|
||
Copyright (c) 2017 Mike Causer
|
||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
of this software and associated documentation files (the "Software"), to deal
|
||
in the Software without restriction, including without limitation the rights
|
||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||
copies of the Software, and to permit persons to whom the Software is
|
||
furnished to do so, subject to the following conditions:
|
||
|
||
The above copyright notice and this permission notice shall be included in all
|
||
copies or substantial portions of the Software.
|
||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||
SOFTWARE.
|
||
"""
|
||
|
||
from micropython import const
|
||
import framebuf
|
||
|
||
_NOOP = const(0)
|
||
_DIGIT0 = const(1)
|
||
_DECODEMODE = const(9)
|
||
_INTENSITY = const(10)
|
||
_SCANLIMIT = const(11)
|
||
_SHUTDOWN = const(12)
|
||
_DISPLAYTEST = const(15)
|
||
|
||
class Matrix8x8:
|
||
def __init__(self, spi, cs, num):
|
||
"""
|
||
Driver for cascading MAX7219 8x8 LED matrices.
|
||
|
||
>>> import max7219
|
||
>>> from machine import Pin, SPI
|
||
>>> spi = SPI(1)
|
||
>>> display = max7219.Matrix8x8(spi, Pin('X5'), 4)
|
||
>>> display.text('1234',0,0,1)
|
||
>>> display.show()
|
||
|
||
"""
|
||
self.spi = spi
|
||
self.cs = cs
|
||
self.cs.init(cs.OUT, True)
|
||
self.buffer = bytearray(8 * num)
|
||
self.num = num
|
||
fb = framebuf.FrameBuffer(self.buffer, 8 * num, 8, framebuf.MONO_HMSB)
|
||
self.framebuf = fb
|
||
# Provide methods for accessing FrameBuffer graphics primitives. This is a workround
|
||
# because inheritance from a native class is currently unsupported.
|
||
# http://docs.micropython.org/en/latest/pyboard/library/framebuf.html
|
||
self.fill = fb.fill # (col)
|
||
self.pixel = fb.pixel # (x, y[, c])
|
||
self.hline = fb.hline # (x, y, w, col)
|
||
self.vline = fb.vline # (x, y, h, col)
|
||
self.line = fb.line # (x1, y1, x2, y2, col)
|
||
self.rect = fb.rect # (x, y, w, h, col)
|
||
self.fill_rect = fb.fill_rect # (x, y, w, h, col)
|
||
self.text = fb.text # (string, x, y, col=1)
|
||
self.scroll = fb.scroll # (dx, dy)
|
||
self.blit = fb.blit # (fbuf, x, y[, key])
|
||
self.init()
|
||
|
||
def _write(self, command, data):
|
||
self.cs(0)
|
||
for m in range(self.num):
|
||
self.spi.write(bytearray([command, data]))
|
||
self.cs(1)
|
||
|
||
def init(self):
|
||
for command, data in (
|
||
(_SHUTDOWN, 0),
|
||
(_DISPLAYTEST, 0),
|
||
(_SCANLIMIT, 7),
|
||
(_DECODEMODE, 0),
|
||
(_SHUTDOWN, 1),
|
||
):
|
||
self._write(command, data)
|
||
|
||
def brightness(self, value):
|
||
if not 0 <= value <= 15:
|
||
raise ValueError("Brightness out of range")
|
||
self._write(_INTENSITY, value)
|
||
|
||
def show(self):
|
||
for y in range(8):
|
||
self.cs(0)
|
||
for m in range(self.num):
|
||
self.spi.write(bytearray([_DIGIT0 + y, self.buffer[(y * self.num) + m]]))
|
||
self.cs(1)` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "esp32", "max7219", "micropython", "wifi"],
|
||
},
|
||
{
|
||
id: "100d-ota-update-pico2w",
|
||
title: "OTA Update Pico2W",
|
||
description: "OTA Update Pico2W on Raspberry Pi Pico W (MicroPython) \u2014 uses OTA, Wi-Fi. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "communication",
|
||
difficulty: "advanced",
|
||
boardType: "pi-pico-w",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "boot.py", content: `# boot.py - Runs automatically every time Pico starts
|
||
import ota
|
||
|
||
ota.check_and_update()` },
|
||
{ name: "main.py", content: `import time
|
||
from machine import Pin
|
||
|
||
led = Pin("LED", Pin.OUT)
|
||
|
||
while True:
|
||
led.on()
|
||
print("LED ON - Version 1.0.0")
|
||
time.sleep(1)
|
||
led.off()
|
||
print("LED OFF - Version 1.0.0")
|
||
time.sleep(1)` },
|
||
{ name: "ota.py", content: `import network
|
||
import urequests
|
||
import time
|
||
import machine
|
||
|
||
# ---- FILL YOUR DETAILS HERE ----
|
||
SSID = "your_wifi_name"
|
||
PASSWORD = "your_wifi_password"
|
||
GITHUB_USER = "kritishmohapatra"
|
||
GITHUB_REPO = "pico-ota-test"
|
||
# --------------------------------
|
||
|
||
BASE_URL = f"https://raw.githubusercontent.com/{GITHUB_USER}/{GITHUB_REPO}/main/"
|
||
|
||
def connect_wifi():
|
||
wlan = network.WLAN(network.STA_IF)
|
||
wlan.active(True)
|
||
if not wlan.isconnected():
|
||
print("Connecting to WiFi...")
|
||
wlan.connect(SSID, PASSWORD)
|
||
timeout = 15
|
||
while not wlan.isconnected() and timeout > 0:
|
||
time.sleep(1)
|
||
timeout -= 1
|
||
if wlan.isconnected():
|
||
print("WiFi connected! IP:", wlan.ifconfig()[0])
|
||
return True
|
||
print("WiFi connection failed!")
|
||
return False
|
||
|
||
def get_local_version():
|
||
try:
|
||
with open("local_version.txt", "r") as f:
|
||
return f.read().strip()
|
||
except:
|
||
return "0.0.0"
|
||
|
||
def get_remote_version():
|
||
try:
|
||
r = urequests.get(BASE_URL + "version.txt")
|
||
v = r.text.strip()
|
||
r.close()
|
||
return v
|
||
except Exception as e:
|
||
print("Version check failed:", e)
|
||
return None
|
||
|
||
def check_and_update():
|
||
if not connect_wifi():
|
||
return
|
||
|
||
remote = get_remote_version()
|
||
local = get_local_version()
|
||
|
||
print(f"Local version: {local}")
|
||
print(f"Remote version: {remote}")
|
||
|
||
if remote and remote != local:
|
||
print("New update found! Downloading...")
|
||
try:
|
||
r = urequests.get(BASE_URL + "main.py")
|
||
with open("main.py", "w") as f:
|
||
f.write(r.text)
|
||
r.close()
|
||
with open("local_version.txt", "w") as f:
|
||
f.write(remote)
|
||
print("Update done! Restarting...")
|
||
time.sleep(2)
|
||
machine.reset()
|
||
except Exception as e:
|
||
print("Update failed:", e)
|
||
else:
|
||
print("Already on latest version!")` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "micropython", "ota", "pi-pico-w", "pico-w", "wifi"],
|
||
},
|
||
{
|
||
id: "100d-pir-motion-detector-using-raspberry-pi-pico-2w-and-micropython",
|
||
title: "PIR Motion Detector using Raspberry Pi Pico 2W & MicroPython",
|
||
description: "PIR Motion Detector using Raspberry Pi Pico 2W & MicroPython on Raspberry Pi Pico W (MicroPython). From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "basics",
|
||
difficulty: "beginner",
|
||
boardType: "pi-pico-w",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "main.py", content: `from machine import Pin
|
||
import time
|
||
|
||
pir = Pin(16, Pin.IN, Pin.PULL_DOWN) # PIR sensor pin
|
||
led = Pin("LED", Pin.OUT) # Built-in LED
|
||
|
||
print("PIR Motion Detector Started...")
|
||
|
||
while True:
|
||
if pir.value() == 1:
|
||
print("Motion Detected!")
|
||
led.value(1)
|
||
time.sleep(1)
|
||
else:
|
||
print("No motion")
|
||
led.value(0)
|
||
time.sleep(0.2)` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "micropython", "pi-pico-w", "pico-w"],
|
||
},
|
||
{
|
||
id: "100d-password-lock-system-using-esp32",
|
||
title: "Password Lock System using ESP32",
|
||
description: "Password Lock System using ESP32 on ESP32 (MicroPython) \u2014 uses I\u00b2C LCD. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "displays",
|
||
difficulty: "intermediate",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "i2c_lcd.py", content: `# Implements a HD44780 character LCD connected via PCF8574 on I2C.
|
||
# This was tested with: https://www.wemos.cc/product/d1-mini.html
|
||
# https://github.com/dhylands/python_lcd/blob/master/lcd/machine_i2c_lcd.py
|
||
|
||
from lcd_api import LcdApi
|
||
from time import sleep_ms
|
||
|
||
# The PCF8574 has a jumper selectable address: 0x20 - 0x27
|
||
DEFAULT_I2C_ADDR = 0x27
|
||
|
||
# Defines shifts or masks for the various LCD line attached to the PCF8574
|
||
|
||
MASK_RS = 0x01
|
||
MASK_RW = 0x02
|
||
MASK_E = 0x04
|
||
SHIFT_BACKLIGHT = 3
|
||
SHIFT_DATA = 4
|
||
|
||
|
||
class I2cLcd(LcdApi):
|
||
"""Implements a HD44780 character LCD connected via PCF8574 on I2C."""
|
||
|
||
def __init__(self, i2c, i2c_addr, num_lines, num_columns):
|
||
self.i2c = i2c
|
||
self.i2c_addr = i2c_addr
|
||
self.i2c.writeto(self.i2c_addr, bytearray([0]))
|
||
sleep_ms(20) # Allow LCD time to powerup
|
||
# Send reset 3 times
|
||
self.hal_write_init_nibble(self.LCD_FUNCTION_RESET)
|
||
sleep_ms(5) # need to delay at least 4.1 msec
|
||
self.hal_write_init_nibble(self.LCD_FUNCTION_RESET)
|
||
sleep_ms(1)
|
||
self.hal_write_init_nibble(self.LCD_FUNCTION_RESET)
|
||
sleep_ms(1)
|
||
# Put LCD into 4 bit mode
|
||
self.hal_write_init_nibble(self.LCD_FUNCTION)
|
||
sleep_ms(1)
|
||
LcdApi.__init__(self, num_lines, num_columns)
|
||
cmd = self.LCD_FUNCTION
|
||
if num_lines > 1:
|
||
cmd |= self.LCD_FUNCTION_2LINES
|
||
self.hal_write_command(cmd)
|
||
|
||
def hal_write_init_nibble(self, nibble):
|
||
"""Writes an initialization nibble to the LCD.
|
||
|
||
This particular function is only used during initialization.
|
||
"""
|
||
byte = ((nibble >> 4) & 0x0f) << SHIFT_DATA
|
||
self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E]))
|
||
self.i2c.writeto(self.i2c_addr, bytearray([byte]))
|
||
|
||
def hal_backlight_on(self):
|
||
"""Allows the hal layer to turn the backlight on."""
|
||
self.i2c.writeto(self.i2c_addr, bytearray([1 << SHIFT_BACKLIGHT]))
|
||
|
||
def hal_backlight_off(self):
|
||
"""Allows the hal layer to turn the backlight off."""
|
||
self.i2c.writeto(self.i2c_addr, bytearray([0]))
|
||
|
||
def hal_write_command(self, cmd):
|
||
"""Writes a command to the LCD.
|
||
|
||
Data is latched on the falling edge of E.
|
||
"""
|
||
byte = ((self.backlight << SHIFT_BACKLIGHT) | (((cmd >> 4) & 0x0f) << SHIFT_DATA))
|
||
self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E]))
|
||
self.i2c.writeto(self.i2c_addr, bytearray([byte]))
|
||
byte = ((self.backlight << SHIFT_BACKLIGHT) | ((cmd & 0x0f) << SHIFT_DATA))
|
||
self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E]))
|
||
self.i2c.writeto(self.i2c_addr, bytearray([byte]))
|
||
if cmd <= 3:
|
||
# The home and clear commands require a worst case delay of 4.1 msec
|
||
sleep_ms(5)
|
||
|
||
def hal_write_data(self, data):
|
||
"""Write data to the LCD."""
|
||
byte = (MASK_RS | (self.backlight << SHIFT_BACKLIGHT) | (((data >> 4) & 0x0f) << SHIFT_DATA))
|
||
self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E]))
|
||
self.i2c.writeto(self.i2c_addr, bytearray([byte]))
|
||
byte = (MASK_RS | (self.backlight << SHIFT_BACKLIGHT) | ((data & 0x0f) << SHIFT_DATA))
|
||
self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E]))
|
||
self.i2c.writeto(self.i2c_addr, bytearray([byte]))
|
||
` },
|
||
{ name: "lcd_api.py", content: `# Provides an API for talking to HD44780 compatible character LCDs.
|
||
# https://github.com/dhylands/python_lcd/tree/master/lcd
|
||
import time
|
||
|
||
class LcdApi:
|
||
"""Implements the API for talking with HD44780 compatible character LCDs.
|
||
This class only knows what commands to send to the LCD, and not how to get
|
||
them to the LCD.
|
||
|
||
It is expected that a derived class will implement the hal_xxx functions.
|
||
"""
|
||
|
||
# The following constant names were lifted from the avrlib lcd.h
|
||
# header file, however, I changed the definitions from bit numbers
|
||
# to bit masks.
|
||
#
|
||
# HD44780 LCD controller command set
|
||
|
||
LCD_CLR = 0x01 # DB0: clear display
|
||
LCD_HOME = 0x02 # DB1: return to home position
|
||
|
||
LCD_ENTRY_MODE = 0x04 # DB2: set entry mode
|
||
LCD_ENTRY_INC = 0x02 # --DB1: increment
|
||
LCD_ENTRY_SHIFT = 0x01 # --DB0: shift
|
||
|
||
LCD_ON_CTRL = 0x08 # DB3: turn lcd/cursor on
|
||
LCD_ON_DISPLAY = 0x04 # --DB2: turn display on
|
||
LCD_ON_CURSOR = 0x02 # --DB1: turn cursor on
|
||
LCD_ON_BLINK = 0x01 # --DB0: blinking cursor
|
||
|
||
LCD_MOVE = 0x10 # DB4: move cursor/display
|
||
LCD_MOVE_DISP = 0x08 # --DB3: move display (0-> move cursor)
|
||
LCD_MOVE_RIGHT = 0x04 # --DB2: move right (0-> left)
|
||
|
||
LCD_FUNCTION = 0x20 # DB5: function set
|
||
LCD_FUNCTION_8BIT = 0x10 # --DB4: set 8BIT mode (0->4BIT mode)
|
||
LCD_FUNCTION_2LINES = 0x08 # --DB3: two lines (0->one line)
|
||
LCD_FUNCTION_10DOTS = 0x04 # --DB2: 5x10 font (0->5x7 font)
|
||
LCD_FUNCTION_RESET = 0x30 # See "Initializing by Instruction" section
|
||
|
||
LCD_CGRAM = 0x40 # DB6: set CG RAM address
|
||
LCD_DDRAM = 0x80 # DB7: set DD RAM address
|
||
|
||
LCD_RS_CMD = 0
|
||
LCD_RS_DATA = 1
|
||
|
||
LCD_RW_WRITE = 0
|
||
LCD_RW_READ = 1
|
||
|
||
def __init__(self, num_lines, num_columns):
|
||
self.num_lines = num_lines
|
||
if self.num_lines > 4:
|
||
self.num_lines = 4
|
||
self.num_columns = num_columns
|
||
if self.num_columns > 40:
|
||
self.num_columns = 40
|
||
self.cursor_x = 0
|
||
self.cursor_y = 0
|
||
self.implied_newline = False
|
||
self.backlight = True
|
||
self.display_off()
|
||
self.backlight_on()
|
||
self.clear()
|
||
self.hal_write_command(self.LCD_ENTRY_MODE | self.LCD_ENTRY_INC)
|
||
self.hide_cursor()
|
||
self.display_on()
|
||
|
||
def clear(self):
|
||
"""Clears the LCD display and moves the cursor to the top left
|
||
corner.
|
||
"""
|
||
self.hal_write_command(self.LCD_CLR)
|
||
self.hal_write_command(self.LCD_HOME)
|
||
self.cursor_x = 0
|
||
self.cursor_y = 0
|
||
|
||
def show_cursor(self):
|
||
"""Causes the cursor to be made visible."""
|
||
self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY |
|
||
self.LCD_ON_CURSOR)
|
||
|
||
def hide_cursor(self):
|
||
"""Causes the cursor to be hidden."""
|
||
self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY)
|
||
|
||
def blink_cursor_on(self):
|
||
"""Turns on the cursor, and makes it blink."""
|
||
self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY |
|
||
self.LCD_ON_CURSOR | self.LCD_ON_BLINK)
|
||
|
||
def blink_cursor_off(self):
|
||
"""Turns on the cursor, and makes it no blink (i.e. be solid)."""
|
||
self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY |
|
||
self.LCD_ON_CURSOR)
|
||
|
||
def display_on(self):
|
||
"""Turns on (i.e. unblanks) the LCD."""
|
||
self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY)
|
||
|
||
def display_off(self):
|
||
"""Turns off (i.e. blanks) the LCD."""
|
||
self.hal_write_command(self.LCD_ON_CTRL)
|
||
|
||
def backlight_on(self):
|
||
"""Turns the backlight on.
|
||
|
||
This isn't really an LCD command, but some modules have backlight
|
||
controls, so this allows the hal to pass through the command.
|
||
"""
|
||
self.backlight = True
|
||
self.hal_backlight_on()
|
||
|
||
def backlight_off(self):
|
||
"""Turns the backlight off.
|
||
|
||
This isn't really an LCD command, but some modules have backlight
|
||
controls, so this allows the hal to pass through the command.
|
||
"""
|
||
self.backlight = False
|
||
self.hal_backlight_off()
|
||
|
||
def move_to(self, cursor_x, cursor_y):
|
||
"""Moves the cursor position to the indicated position. The cursor
|
||
position is zero based (i.e. cursor_x == 0 indicates first column).
|
||
"""
|
||
self.cursor_x = cursor_x
|
||
self.cursor_y = cursor_y
|
||
addr = cursor_x & 0x3f
|
||
if cursor_y & 1:
|
||
addr += 0x40 # Lines 1 & 3 add 0x40
|
||
if cursor_y & 2: # Lines 2 & 3 add number of columns
|
||
addr += self.num_columns
|
||
self.hal_write_command(self.LCD_DDRAM | addr)
|
||
|
||
def putchar(self, char):
|
||
"""Writes the indicated character to the LCD at the current cursor
|
||
position, and advances the cursor by one position.
|
||
"""
|
||
if char == '\\n':
|
||
if self.implied_newline:
|
||
# self.implied_newline means we advanced due to a wraparound,
|
||
# so if we get a newline right after that we ignore it.
|
||
self.implied_newline = False
|
||
else:
|
||
self.cursor_x = self.num_columns
|
||
else:
|
||
self.hal_write_data(ord(char))
|
||
self.cursor_x += 1
|
||
if self.cursor_x >= self.num_columns:
|
||
self.cursor_x = 0
|
||
self.cursor_y += 1
|
||
self.implied_newline = (char != '\\n')
|
||
if self.cursor_y >= self.num_lines:
|
||
self.cursor_y = 0
|
||
self.move_to(self.cursor_x, self.cursor_y)
|
||
|
||
def putstr(self, string):
|
||
"""Write the indicated string to the LCD at the current cursor
|
||
position and advances the cursor position appropriately.
|
||
"""
|
||
for char in string:
|
||
self.putchar(char)
|
||
|
||
def custom_char(self, location, charmap):
|
||
"""Write a character to one of the 8 CGRAM locations, available
|
||
as chr(0) through chr(7).
|
||
"""
|
||
location &= 0x7
|
||
self.hal_write_command(self.LCD_CGRAM | (location << 3))
|
||
self.hal_sleep_us(40)
|
||
for i in range(8):
|
||
self.hal_write_data(charmap[i])
|
||
self.hal_sleep_us(40)
|
||
self.move_to(self.cursor_x, self.cursor_y)
|
||
|
||
def hal_backlight_on(self):
|
||
"""Allows the hal layer to turn the backlight on.
|
||
|
||
If desired, a derived HAL class will implement this function.
|
||
"""
|
||
pass
|
||
|
||
def hal_backlight_off(self):
|
||
"""Allows the hal layer to turn the backlight off.
|
||
|
||
If desired, a derived HAL class will implement this function.
|
||
"""
|
||
pass
|
||
|
||
def hal_write_command(self, cmd):
|
||
"""Write a command to the LCD.
|
||
|
||
It is expected that a derived HAL class will implement this
|
||
function.
|
||
"""
|
||
raise NotImplementedError
|
||
|
||
def hal_write_data(self, data):
|
||
"""Write data to the LCD.
|
||
|
||
It is expected that a derived HAL class will implement this
|
||
function.
|
||
"""
|
||
raise NotImplementedError
|
||
|
||
# This is a default implementation of hal_sleep_us which is suitable
|
||
# for most micropython implementations. For platforms which don't
|
||
# support \`time.sleep_us()\` they should provide their own implementation
|
||
# of hal_sleep_us in their hal layer and it will be used instead.
|
||
def hal_sleep_us(self, usecs):
|
||
"""Sleep for some time (given in microseconds)."""
|
||
time.sleep_us(usecs) # NOTE this is not part of Standard Python library, specific hal layers will need to override this
|
||
` },
|
||
{ name: "main.py", content: `from machine import Pin, I2C
|
||
from time import sleep
|
||
from i2c_lcd import I2cLcd
|
||
|
||
# LCD setup
|
||
i2c = I2C(0, scl=Pin(22), sda=Pin(21), freq=400000)
|
||
lcd = I2cLcd(i2c, 0x27, 2, 16)
|
||
|
||
# LEDs
|
||
green = Pin(5, Pin.OUT)
|
||
red = Pin(4, Pin.OUT)
|
||
|
||
# Password
|
||
PASSWORD = "12ABC8"
|
||
entered = ""
|
||
|
||
# Keypad pins
|
||
rows = [13,12,14,27]
|
||
cols = [26,25,33,32]
|
||
|
||
row_pins = [Pin(r, Pin.OUT) for r in rows]
|
||
col_pins = [Pin(c, Pin.IN, Pin.PULL_DOWN) for c in cols]
|
||
|
||
keys = [
|
||
['1','2','3','A'],
|
||
['4','5','6','B'],
|
||
['7','8','9','C'],
|
||
['*','0','#','D']
|
||
]
|
||
|
||
lcd.clear()
|
||
lcd.putstr("Enter Password")
|
||
|
||
def read_keypad():
|
||
for i, row in enumerate(row_pins):
|
||
row.value(1)
|
||
for j, col in enumerate(col_pins):
|
||
if col.value():
|
||
row.value(0)
|
||
sleep(0.3)
|
||
return keys[i][j]
|
||
row.value(0)
|
||
return None
|
||
|
||
while True:
|
||
key = read_keypad()
|
||
if key:
|
||
if key == '#':
|
||
lcd.clear()
|
||
if entered == PASSWORD:
|
||
lcd.putstr("Access Granted")
|
||
green.on()
|
||
sleep(2)
|
||
green.off()
|
||
else:
|
||
lcd.putstr("Access Denied")
|
||
red.on()
|
||
sleep(2)
|
||
red.off()
|
||
|
||
entered = ""
|
||
lcd.clear()
|
||
lcd.putstr("Enter Password")
|
||
|
||
elif key == '*':
|
||
entered = ""
|
||
lcd.clear()
|
||
lcd.putstr("Cleared")
|
||
sleep(1)
|
||
lcd.clear()
|
||
lcd.putstr("Enter Password")
|
||
|
||
else:
|
||
entered += key
|
||
lcd.move_to(len(entered)-1, 1)
|
||
lcd.putstr("*")
|
||
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [{"type": "wokwi-lcd1602-i2c", "id": "lock-lcd1", "x": 400, "y": 40, "properties": {}}, {"type": "wokwi-membrane-keypad", "id": "lock-pad1", "x": 420, "y": 200, "properties": {}}, {"type": "wokwi-resistor", "id": "lock-rl5", "x": 700, "y": 60, "properties": {"value": "220"}}, {"type": "wokwi-led", "id": "lock-led5", "x": 700, "y": 150, "properties": {"color": "green"}}, {"type": "wokwi-resistor", "id": "lock-rl4", "x": 780, "y": 60, "properties": {"value": "220"}}, {"type": "wokwi-led", "id": "lock-led4", "x": 780, "y": 150, "properties": {"color": "red"}}],
|
||
wires: [{"id": "lock-lcd-vcc", "start": {"componentId": "arduino-uno", "pinName": "3V3"}, "end": {"componentId": "lock-lcd1", "pinName": "VCC"}, "color": "#ff4444"}, {"id": "lock-lcd-gnd", "start": {"componentId": "arduino-uno", "pinName": "GND"}, "end": {"componentId": "lock-lcd1", "pinName": "GND"}, "color": "#000000"}, {"id": "lock-lcd-sda", "start": {"componentId": "arduino-uno", "pinName": "21"}, "end": {"componentId": "lock-lcd1", "pinName": "SDA"}, "color": "#22aaff"}, {"id": "lock-lcd-scl", "start": {"componentId": "arduino-uno", "pinName": "22"}, "end": {"componentId": "lock-lcd1", "pinName": "SCL"}, "color": "#ff8800"}, {"id": "lock-r1", "start": {"componentId": "arduino-uno", "pinName": "13"}, "end": {"componentId": "lock-pad1", "pinName": "R1"}, "color": "#cc44cc"}, {"id": "lock-r2", "start": {"componentId": "arduino-uno", "pinName": "12"}, "end": {"componentId": "lock-pad1", "pinName": "R2"}, "color": "#cc44cc"}, {"id": "lock-r3", "start": {"componentId": "arduino-uno", "pinName": "14"}, "end": {"componentId": "lock-pad1", "pinName": "R3"}, "color": "#cc44cc"}, {"id": "lock-r4", "start": {"componentId": "arduino-uno", "pinName": "27"}, "end": {"componentId": "lock-pad1", "pinName": "R4"}, "color": "#cc44cc"}, {"id": "lock-c1", "start": {"componentId": "arduino-uno", "pinName": "26"}, "end": {"componentId": "lock-pad1", "pinName": "C1"}, "color": "#22cc66"}, {"id": "lock-c2", "start": {"componentId": "arduino-uno", "pinName": "25"}, "end": {"componentId": "lock-pad1", "pinName": "C2"}, "color": "#22cc66"}, {"id": "lock-c3", "start": {"componentId": "arduino-uno", "pinName": "33"}, "end": {"componentId": "lock-pad1", "pinName": "C3"}, "color": "#22cc66"}, {"id": "lock-c4", "start": {"componentId": "arduino-uno", "pinName": "32"}, "end": {"componentId": "lock-pad1", "pinName": "C4"}, "color": "#22cc66"}, {"id": "lock-lp5", "start": {"componentId": "arduino-uno", "pinName": "5"}, "end": {"componentId": "lock-rl5", "pinName": "1"}, "color": "#22aaff"}, {"id": "lock-lr5", "start": {"componentId": "lock-rl5", "pinName": "2"}, "end": {"componentId": "lock-led5", "pinName": "A"}, "color": "#cc44cc"}, {"id": "lock-lg5", "start": {"componentId": "lock-led5", "pinName": "C"}, "end": {"componentId": "arduino-uno", "pinName": "GND"}, "color": "#000000"}, {"id": "lock-lp4", "start": {"componentId": "arduino-uno", "pinName": "4"}, "end": {"componentId": "lock-rl4", "pinName": "1"}, "color": "#22aaff"}, {"id": "lock-lr4", "start": {"componentId": "lock-rl4", "pinName": "2"}, "end": {"componentId": "lock-led4", "pinName": "A"}, "color": "#cc44cc"}, {"id": "lock-lg4", "start": {"componentId": "lock-led4", "pinName": "C"}, "end": {"componentId": "arduino-uno", "pinName": "GND"}, "color": "#000000"}],
|
||
tags: ["100-days", "esp32", "lcd", "micropython"],
|
||
},
|
||
{
|
||
id: "100d-pico-2-w-dht11-http-csv-logger",
|
||
title: "Pico 2 W Dht11 Http Csv Logger",
|
||
description: "Pico 2 W Dht11 Http Csv Logger on Raspberry Pi Pico W (MicroPython) \u2014 uses DHT, HTTP server, Wi-Fi. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "communication",
|
||
difficulty: "intermediate",
|
||
boardType: "pi-pico-w",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "app.py", content: `from flask import Flask, request
|
||
import csv, os
|
||
from datetime import datetime
|
||
|
||
app = Flask(__name__)
|
||
CSV_FILE = "data.csv"
|
||
|
||
if not os.path.exists(CSV_FILE):
|
||
with open(CSV_FILE, 'w', newline='') as f:
|
||
csv.writer(f).writerow(
|
||
["time", "temperature", "humidity"]
|
||
)
|
||
|
||
@app.route('/data', methods=['POST'])
|
||
def data():
|
||
d = request.json
|
||
print("Received:", d)
|
||
|
||
with open(CSV_FILE, 'a', newline='') as f:
|
||
csv.writer(f).writerow([
|
||
datetime.now().strftime("%H:%M:%S"),
|
||
d["temperature"],
|
||
d["humidity"]
|
||
])
|
||
|
||
return "OK"
|
||
|
||
app.run(host="0.0.0.0", port=5000)
|
||
` },
|
||
{ name: "main.py", content: `import network, time, urequests
|
||
import dht
|
||
from machine import Pin
|
||
|
||
ssid = "kritish"
|
||
password = "pas"
|
||
|
||
url = "http://ip:5000/data" # laptop IP
|
||
|
||
sensor = dht.DHT22(Pin(15)) # Velxio emulates the DHT22 waveform (use DHT11 on real hw)
|
||
|
||
wlan = network.WLAN(network.STA_IF)
|
||
wlan.active(True)
|
||
wlan.connect(ssid, password)
|
||
|
||
while not wlan.isconnected():
|
||
time.sleep(1)
|
||
|
||
print("WiFi connected")
|
||
|
||
while True:
|
||
try:
|
||
sensor.measure()
|
||
temp = sensor.temperature()
|
||
hum = sensor.humidity()
|
||
except OSError as e:
|
||
print("DHT read failed:", e) # keep the loop alive (sensor unplugged?)
|
||
time.sleep(2)
|
||
continue
|
||
|
||
data = {
|
||
"temperature": temp,
|
||
"humidity": hum
|
||
}
|
||
|
||
try:
|
||
r = urequests.post(url, json=data)
|
||
r.close()
|
||
print("Sent:", data)
|
||
except Exception as e:
|
||
print("Error:", e)
|
||
|
||
time.sleep(5)
|
||
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [{"type": "wokwi-dht22", "id": "plog-dht1", "x": 440, "y": 200, "properties": {"temperature": "28", "humidity": "65"}}],
|
||
wires: [{"id": "plog-dht-vcc", "start": {"componentId": "arduino-uno", "pinName": "3V3"}, "end": {"componentId": "plog-dht1", "pinName": "VCC"}, "color": "#ff4444"}, {"id": "plog-dht-gnd", "start": {"componentId": "arduino-uno", "pinName": "GND"}, "end": {"componentId": "plog-dht1", "pinName": "GND"}, "color": "#000000"}, {"id": "plog-dht-data", "start": {"componentId": "arduino-uno", "pinName": "GP15"}, "end": {"componentId": "plog-dht1", "pinName": "SDA"}, "color": "#22cc66"}],
|
||
tags: ["100-days", "dht", "http_server", "micropython", "pi-pico-w", "pico-w", "wifi"],
|
||
},
|
||
{
|
||
id: "100d-pico-w-async-led-control-micropython",
|
||
title: "Pico W Async LED Control (MicroPython)",
|
||
description: "Pico W Async LED Control (MicroPython) on Raspberry Pi Pico W (MicroPython) \u2014 uses Wi-Fi. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "communication",
|
||
difficulty: "intermediate",
|
||
boardType: "pi-pico-w",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "main.py", content: `import uasyncio as asyncio
|
||
import network
|
||
from machine import Pin
|
||
|
||
SSID = "kritish"
|
||
PASSWORD = "pass"
|
||
|
||
led = Pin("LED", Pin.OUT)
|
||
led.off()
|
||
|
||
# ---------- Async WiFi Connect ----------
|
||
async def wifi_connect():
|
||
wlan = network.WLAN(network.STA_IF)
|
||
wlan.active(True)
|
||
wlan.connect(SSID, PASSWORD)
|
||
|
||
print("Connecting WiFi...")
|
||
while not wlan.isconnected():
|
||
await asyncio.sleep(0.5)
|
||
|
||
print("IP:", wlan.ifconfig()[0])
|
||
|
||
|
||
# ---------- HTML ----------
|
||
HTML_PAGE = """<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<title>Pico Async LED</title>
|
||
</head>
|
||
<body>
|
||
<h2>Pico W Async LED Control</h2>
|
||
<p>Status: <span id="status">--</span></p>
|
||
|
||
<button onclick="ledOn()">ON</button>
|
||
<button onclick="ledOff()">OFF</button>
|
||
|
||
<script>
|
||
function ledOn(){
|
||
fetch("/on").then(r=>r.text()).then(t=>{
|
||
document.getElementById("status").innerText=t;
|
||
});
|
||
}
|
||
function ledOff(){
|
||
fetch("/off").then(r=>r.text()).then(t=>{
|
||
document.getElementById("status").innerText=t;
|
||
});
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|
||
"""
|
||
|
||
|
||
# ---------- Client Handler ----------
|
||
async def handle_client(reader, writer):
|
||
request_line = await reader.readline()
|
||
request = request_line.decode()
|
||
|
||
# skip headers
|
||
while await reader.readline() != b"\\r\\n":
|
||
pass
|
||
|
||
print("REQ:", request)
|
||
|
||
if "GET /on" in request:
|
||
led.on()
|
||
body = "ON"
|
||
content_type = "text/plain"
|
||
|
||
elif "GET /off" in request:
|
||
led.off()
|
||
body = "OFF"
|
||
content_type = "text/plain"
|
||
|
||
else:
|
||
body = HTML_PAGE
|
||
content_type = "text/html"
|
||
|
||
response = (
|
||
"HTTP/1.1 200 OK\\r\\n"
|
||
f"Content-Type: {content_type}\\r\\n"
|
||
"Connection: close\\r\\n\\r\\n"
|
||
+ body
|
||
)
|
||
|
||
writer.write(response.encode())
|
||
await writer.drain()
|
||
await writer.wait_closed()
|
||
|
||
|
||
# ---------- Main ----------
|
||
async def main():
|
||
await wifi_connect()
|
||
|
||
server = await asyncio.start_server(handle_client, "0.0.0.0", 80)
|
||
print("Server running on port 80")
|
||
|
||
while True:
|
||
await asyncio.sleep(1)
|
||
|
||
|
||
asyncio.run(main())
|
||
|
||
|
||
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "micropython", "pi-pico-w", "pico-w", "wifi"],
|
||
},
|
||
{
|
||
id: "100d-pico-w-web-servo-controller",
|
||
title: "Pico W Web Servo Controller",
|
||
description: "Pico W Web Servo Controller on Raspberry Pi Pico W (MicroPython) \u2014 uses OTA, Servo, Wi-Fi. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "robotics",
|
||
difficulty: "advanced",
|
||
boardType: "pi-pico-w",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "main.py", content: `import network
|
||
import socket
|
||
from machine import Pin, PWM
|
||
import time
|
||
|
||
# Import credentials from the separate secrets file
|
||
ssid = "kritish"
|
||
password = "password"
|
||
|
||
# ====== WiFi Setup ======
|
||
print("Connecting to WiFi...")
|
||
sta = network.WLAN(network.STA_IF)
|
||
sta.active(True)
|
||
|
||
# Attempt to connect to the network
|
||
try:
|
||
sta.connect(ssid, password)
|
||
except Exception as e:
|
||
print(f"Connection error: {e}")
|
||
|
||
timeout_counter = 0
|
||
while not sta.isconnected() and timeout_counter < 20: # Timeout after ~10 seconds
|
||
time.sleep(0.5)
|
||
print(".", end="")
|
||
timeout_counter += 1
|
||
|
||
if sta.isconnected():
|
||
print("\\nConnected!")
|
||
print("IP address:", sta.ifconfig()[0])
|
||
else:
|
||
print("\\nConnection failed! Check credentials and signal.")
|
||
raise OSError("WiFi connection failed.")
|
||
|
||
|
||
# ====== Servo Setup (Pico W Specific) ======
|
||
SERVO_PIN = 15 # Ensure the servo is connected to GP15.
|
||
servo = PWM(Pin(SERVO_PIN), freq=50)
|
||
|
||
SERVO_FREQ = 50
|
||
SERVO_MIN_US = 500 # 0 degrees (0.5ms pulse width)
|
||
SERVO_MAX_US = 2500 # 180 degrees (2.5ms pulse width)
|
||
TOTAL_PERIOD_US = 1000000 / SERVO_FREQ # 20000 us (20ms)
|
||
DUTY_SCALE = 65535 # Max value for Pico's duty_u16 method
|
||
|
||
def write_servo(angle):
|
||
"""Sets the servo angle (0-180 degrees) by calculating the PWM duty_u16."""
|
||
angle = max(0, min(180, angle))
|
||
pulse_us = SERVO_MIN_US + (SERVO_MAX_US - SERVO_MIN_US) * (angle / 180)
|
||
duty = int((pulse_us / TOTAL_PERIOD_US) * DUTY_SCALE)
|
||
servo.duty_u16(duty)
|
||
|
||
|
||
# ====== HTML Page (Vibrant Redesign) ======
|
||
def webpage(pos):
|
||
"""Generates the HTML page with a vibrant design and current servo position."""
|
||
html = f"""
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<title>Pico W Servo</title>
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&display=swap" rel="stylesheet">
|
||
<style>
|
||
:root {{
|
||
--primary-color: #6a0dad; /* Deep Purple */
|
||
--secondary-color: #ff6f61; /* Coral Red */
|
||
--accent-color: #ffda03; /* Bright Yellow */
|
||
--bg-light: #f0f8ff; /* Alice Blue */
|
||
--text-dark: #2c3e50; /* Dark Grey Blue */
|
||
}}
|
||
|
||
body {{
|
||
margin: 0;
|
||
font-family: 'Poppins', sans-serif;
|
||
background: linear-gradient(45deg, var(--bg-light) 0%, var(--primary-color) 100%);
|
||
color: var(--text-dark);
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
min-height: 100vh;
|
||
overflow: hidden;
|
||
}}
|
||
|
||
.container {{
|
||
background: rgba(255, 255, 255, 0.9);
|
||
border-radius: 15px;
|
||
padding: 40px;
|
||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
||
text-align: center;
|
||
max-width: 400px;
|
||
width: 90%;
|
||
backdrop-filter: blur(5px);
|
||
-webkit-backdrop-filter: blur(5px);
|
||
}}
|
||
|
||
h1 {{
|
||
color: var(--primary-color);
|
||
margin-bottom: 10px;
|
||
font-size: 2em;
|
||
font-weight: 700;
|
||
}}
|
||
|
||
p {{
|
||
font-size: 1.1em;
|
||
margin-bottom: 25px;
|
||
color: var(--text-dark);
|
||
}}
|
||
|
||
strong#servoVal {{
|
||
font-size: 1.8em;
|
||
color: var(--secondary-color);
|
||
display: block;
|
||
margin-top: 10px;
|
||
font-weight: 600;
|
||
}}
|
||
|
||
.slider {{
|
||
-webkit-appearance: none;
|
||
width: 100%;
|
||
height: 15px;
|
||
background: linear-gradient(to right, var(--accent-color), var(--secondary-color));
|
||
border-radius: 8px;
|
||
outline: none;
|
||
opacity: 0.8;
|
||
transition: opacity .2s ease-in-out;
|
||
margin-top: 20px;
|
||
margin-bottom: 30px;
|
||
}}
|
||
|
||
.slider:hover {{
|
||
opacity: 1;
|
||
}}
|
||
|
||
/* Slider Thumb Styling */
|
||
.slider::-webkit-slider-thumb {{
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
width: 25px;
|
||
height: 25px;
|
||
border-radius: 50%;
|
||
background: var(--primary-color);
|
||
cursor: grab;
|
||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||
border: 2px solid var(--bg-light);
|
||
}}
|
||
|
||
.slider::-moz-range-thumb {{
|
||
width: 25px;
|
||
height: 25px;
|
||
border-radius: 50%;
|
||
background: var(--primary-color);
|
||
cursor: grab;
|
||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||
border: 2px solid var(--bg-light);
|
||
}}
|
||
|
||
.info-text {{
|
||
font-size: 0.9em;
|
||
color: #7f8c8d; /* Grey */
|
||
margin-top: 20px;
|
||
}}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<h1>Servo Control</h1>
|
||
<p>Current Angle:</p>
|
||
<strong id="servoVal">{pos}°</strong>
|
||
|
||
<input type="range" min="0" max="180" value="{pos}" class="slider"
|
||
id="servoSlider" oninput="update(this.value)"
|
||
onchange="send(this.value)">
|
||
|
||
<p class="info-text">Move and release the slider to update the servo position.</p>
|
||
</div>
|
||
|
||
<script>
|
||
function update(val) {{
|
||
document.getElementById("servoVal").innerHTML = val + '°';
|
||
}}
|
||
|
||
function send(val) {{
|
||
fetch('/?value=' + val)
|
||
.then(response => {{
|
||
if (response.ok) {{
|
||
console.log("Command sent successfully:", val);
|
||
}} else {{
|
||
console.error("Failed to send command:", response.status);
|
||
}}
|
||
}})
|
||
.catch(error => {{
|
||
console.error("Network error:", error);
|
||
}});
|
||
}}
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|
||
"""
|
||
return html
|
||
|
||
|
||
# ====== Web Server ======
|
||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||
s.bind(('', 80))
|
||
s.listen(5)
|
||
|
||
current_pos = 90
|
||
write_servo(current_pos)
|
||
|
||
print("Web server started on port 80...")
|
||
|
||
while True:
|
||
try:
|
||
conn, addr = s.accept()
|
||
print("Client connected from:", addr)
|
||
|
||
request = conn.recv(1024).decode('utf-8')
|
||
request_line = request.split('\\n')[0]
|
||
print("Request line:", request_line)
|
||
|
||
if "GET /?value=" in request_line:
|
||
try:
|
||
value_str = request_line.split("/?value=")[1].split(" ")[0]
|
||
new_pos = int(value_str)
|
||
|
||
if 0 <= new_pos <= 180:
|
||
current_pos = new_pos
|
||
write_servo(current_pos)
|
||
print(f"Servo angle updated to: {current_pos}°")
|
||
else:
|
||
print(f"Error: Angle {new_pos} is out of the 0-180° range.")
|
||
|
||
except (ValueError, IndexError):
|
||
print("Error: Invalid angle value or malformed URL in request.")
|
||
|
||
response = webpage(current_pos)
|
||
|
||
conn.send('HTTP/1.1 200 OK\\r\\n')
|
||
conn.send('Content-Type: text/html\\r\\n')
|
||
conn.send('Connection: close\\r\\n\\r\\n')
|
||
conn.sendall(response.encode('utf-8'))
|
||
conn.close()
|
||
|
||
except OSError as e:
|
||
print(f"Socket error: {e}")
|
||
conn.close()
|
||
except Exception as e:
|
||
print(f"An unexpected error occurred in main loop: {e}")
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "micropython", "ota", "pi-pico-w", "pico-w", "servo", "wifi"],
|
||
},
|
||
{
|
||
id: "100d-potentiometer-visualizer",
|
||
title: "Potentiometer Visualizer",
|
||
description: "Potentiometer Visualizer on ESP32 (MicroPython). From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "basics",
|
||
difficulty: "beginner",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "main.py", content: `from machine import Pin, ADC
|
||
from time import sleep
|
||
|
||
led_pins = [2, 4, 5, 12, 13, 14, 15, 16, 17, 18]
|
||
leds = [Pin(pin, Pin.OUT) for pin in led_pins]
|
||
|
||
pot = ADC(Pin(34))
|
||
pot.atten(ADC.ATTN_11DB)
|
||
|
||
while True:
|
||
val = pot.read()
|
||
level = min(10, round((val / 4095) * 10)) # Fixed
|
||
|
||
for i in range(10):
|
||
leds[i].value(1 if i < level else 0)
|
||
|
||
on_leds = [led_pins[i] for i in range(level)]
|
||
print(f"Analog Value: {val}, Level: {level}, LEDs ON: {on_leds}")
|
||
|
||
sleep(0.2)
|
||
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "esp32", "micropython"],
|
||
},
|
||
{
|
||
id: "100d-pulse-monitor",
|
||
title: "Pulse Monitor",
|
||
description: "Pulse Monitor on ESP32 (MicroPython) \u2014 uses OLED. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "displays",
|
||
difficulty: "intermediate",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "main.py", content: `from machine import ADC, Pin, I2C
|
||
import ssd1306
|
||
import time
|
||
|
||
# OLED setup
|
||
# Velxio decodes the hardware I2C controller only (no bit-banged buses)
|
||
i2c = I2C(0, scl=Pin(22), sda=Pin(21))
|
||
oled = ssd1306.SSD1306_I2C(128, 64, i2c)
|
||
|
||
# Sensor setup
|
||
sensor = ADC(Pin(34))
|
||
sensor.atten(ADC.ATTN_11DB)
|
||
|
||
# BPM variables
|
||
THRESHOLD = 2500
|
||
MIN_PEAK_INTERVAL = 300
|
||
last_peak_time = 0
|
||
peak_times = []
|
||
bpm = 0
|
||
|
||
# Graph buffer
|
||
graph = [0] * 80
|
||
|
||
def draw_heart(x, y):
|
||
oled.pixel(x+1, y, 1); oled.pixel(x+2, y, 1)
|
||
oled.pixel(x+4, y, 1); oled.pixel(x+5, y, 1)
|
||
oled.pixel(x, y+1, 1); oled.pixel(x+1, y+1, 1)
|
||
oled.pixel(x+2, y+1, 1); oled.pixel(x+3, y+1, 1)
|
||
oled.pixel(x+4, y+1, 1); oled.pixel(x+5, y+1, 1)
|
||
oled.pixel(x+6, y+1, 1)
|
||
oled.pixel(x+1, y+2, 1); oled.pixel(x+2, y+2, 1)
|
||
oled.pixel(x+3, y+2, 1); oled.pixel(x+4, y+2, 1)
|
||
oled.pixel(x+5, y+2, 1)
|
||
oled.pixel(x+2, y+3, 1); oled.pixel(x+3, y+3, 1)
|
||
oled.pixel(x+4, y+3, 1)
|
||
oled.pixel(x+3, y+4, 1)
|
||
|
||
while True:
|
||
value = sensor.read()
|
||
current_time = time.ticks_ms()
|
||
|
||
# Peak detection
|
||
if value > THRESHOLD:
|
||
if time.ticks_diff(current_time, last_peak_time) > MIN_PEAK_INTERVAL:
|
||
last_peak_time = current_time
|
||
peak_times.append(current_time)
|
||
if len(peak_times) > 10:
|
||
peak_times.pop(0)
|
||
if len(peak_times) >= 2:
|
||
intervals = [time.ticks_diff(peak_times[i+1], peak_times[i])
|
||
for i in range(len(peak_times)-1)]
|
||
avg_interval = sum(intervals) / len(intervals)
|
||
bpm = int(60000 / avg_interval)
|
||
|
||
# Update graph buffer
|
||
graph_val = int((value / 4095) * 30)
|
||
graph.pop(0)
|
||
graph.append(graph_val)
|
||
|
||
# Draw OLED
|
||
oled.fill(0)
|
||
|
||
# Draw waveform (top half)
|
||
for i in range(len(graph) - 1):
|
||
y1 = 32 - graph[i]
|
||
y2 = 32 - graph[i+1]
|
||
oled.line(i + 48, y1, i + 49, y2, 1)
|
||
|
||
# Divider line
|
||
oled.hline(0, 38, 128, 1)
|
||
|
||
|
||
# Heart symbol + BPM (bottom half)
|
||
draw_heart(4, 44)
|
||
oled.text(": " + str(bpm) + " BPM", 14, 45)
|
||
|
||
oled.show()
|
||
time.sleep_ms(10)
|
||
` },
|
||
{ name: "ssd1306.py", content: `# MicroPython SSD1306 OLED driver, I2C and SPI interfaces
|
||
|
||
from micropython import const
|
||
import framebuf
|
||
|
||
|
||
# register definitions
|
||
SET_CONTRAST = const(0x81)
|
||
SET_ENTIRE_ON = const(0xA4)
|
||
SET_NORM_INV = const(0xA6)
|
||
SET_DISP = const(0xAE)
|
||
SET_MEM_ADDR = const(0x20)
|
||
SET_COL_ADDR = const(0x21)
|
||
SET_PAGE_ADDR = const(0x22)
|
||
SET_DISP_START_LINE = const(0x40)
|
||
SET_SEG_REMAP = const(0xA0)
|
||
SET_MUX_RATIO = const(0xA8)
|
||
SET_COM_OUT_DIR = const(0xC0)
|
||
SET_DISP_OFFSET = const(0xD3)
|
||
SET_COM_PIN_CFG = const(0xDA)
|
||
SET_DISP_CLK_DIV = const(0xD5)
|
||
SET_PRECHARGE = const(0xD9)
|
||
SET_VCOM_DESEL = const(0xDB)
|
||
SET_CHARGE_PUMP = const(0x8D)
|
||
|
||
# Subclassing FrameBuffer provides support for graphics primitives
|
||
# http://docs.micropython.org/en/latest/pyboard/library/framebuf.html
|
||
class SSD1306(framebuf.FrameBuffer):
|
||
def __init__(self, width, height, external_vcc):
|
||
self.width = width
|
||
self.height = height
|
||
self.external_vcc = external_vcc
|
||
self.pages = self.height // 8
|
||
self.buffer = bytearray(self.pages * self.width)
|
||
super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB)
|
||
self.init_display()
|
||
|
||
def init_display(self):
|
||
for cmd in (
|
||
SET_DISP | 0x00, # off
|
||
# address setting
|
||
SET_MEM_ADDR,
|
||
0x00, # horizontal
|
||
# resolution and layout
|
||
SET_DISP_START_LINE | 0x00,
|
||
SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0
|
||
SET_MUX_RATIO,
|
||
self.height - 1,
|
||
SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0
|
||
SET_DISP_OFFSET,
|
||
0x00,
|
||
SET_COM_PIN_CFG,
|
||
0x02 if self.width > 2 * self.height else 0x12,
|
||
# timing and driving scheme
|
||
SET_DISP_CLK_DIV,
|
||
0x80,
|
||
SET_PRECHARGE,
|
||
0x22 if self.external_vcc else 0xF1,
|
||
SET_VCOM_DESEL,
|
||
0x30, # 0.83*Vcc
|
||
# display
|
||
SET_CONTRAST,
|
||
0xFF, # maximum
|
||
SET_ENTIRE_ON, # output follows RAM contents
|
||
SET_NORM_INV, # not inverted
|
||
# charge pump
|
||
SET_CHARGE_PUMP,
|
||
0x10 if self.external_vcc else 0x14,
|
||
SET_DISP | 0x01,
|
||
): # on
|
||
self.write_cmd(cmd)
|
||
self.fill(0)
|
||
self.show()
|
||
|
||
def poweroff(self):
|
||
self.write_cmd(SET_DISP | 0x00)
|
||
|
||
def poweron(self):
|
||
self.write_cmd(SET_DISP | 0x01)
|
||
|
||
def contrast(self, contrast):
|
||
self.write_cmd(SET_CONTRAST)
|
||
self.write_cmd(contrast)
|
||
|
||
def invert(self, invert):
|
||
self.write_cmd(SET_NORM_INV | (invert & 1))
|
||
|
||
def show(self):
|
||
x0 = 0
|
||
x1 = self.width - 1
|
||
if self.width == 64:
|
||
# displays with width of 64 pixels are shifted by 32
|
||
x0 += 32
|
||
x1 += 32
|
||
self.write_cmd(SET_COL_ADDR)
|
||
self.write_cmd(x0)
|
||
self.write_cmd(x1)
|
||
self.write_cmd(SET_PAGE_ADDR)
|
||
self.write_cmd(0)
|
||
self.write_cmd(self.pages - 1)
|
||
self.write_data(self.buffer)
|
||
|
||
|
||
class SSD1306_I2C(SSD1306):
|
||
def __init__(self, width, height, i2c, addr=0x3C, external_vcc=False):
|
||
self.i2c = i2c
|
||
self.addr = addr
|
||
self.temp = bytearray(2)
|
||
self.write_list = [b"\\x40", None] # Co=0, D/C#=1
|
||
super().__init__(width, height, external_vcc)
|
||
|
||
def write_cmd(self, cmd):
|
||
self.temp[0] = 0x80 # Co=1, D/C#=0
|
||
self.temp[1] = cmd
|
||
self.i2c.writeto(self.addr, self.temp)
|
||
|
||
def write_data(self, buf):
|
||
self.write_list[1] = buf
|
||
self.i2c.writevto(self.addr, self.write_list)
|
||
|
||
|
||
class SSD1306_SPI(SSD1306):
|
||
def __init__(self, width, height, spi, dc, res, cs, external_vcc=False):
|
||
self.rate = 10 * 1024 * 1024
|
||
dc.init(dc.OUT, value=0)
|
||
res.init(res.OUT, value=0)
|
||
cs.init(cs.OUT, value=1)
|
||
self.spi = spi
|
||
self.dc = dc
|
||
self.res = res
|
||
self.cs = cs
|
||
import time
|
||
|
||
self.res(1)
|
||
time.sleep_ms(1)
|
||
self.res(0)
|
||
time.sleep_ms(10)
|
||
self.res(1)
|
||
super().__init__(width, height, external_vcc)
|
||
|
||
def write_cmd(self, cmd):
|
||
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
|
||
self.cs(1)
|
||
self.dc(0)
|
||
self.cs(0)
|
||
self.spi.write(bytearray([cmd]))
|
||
self.cs(1)
|
||
|
||
def write_data(self, buf):
|
||
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
|
||
self.cs(1)
|
||
self.dc(1)
|
||
self.cs(0)
|
||
self.spi.write(buf)
|
||
self.cs(1)
|
||
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [{"type": "wokwi-ssd1306", "id": "pulse-oled1", "x": 420, "y": 60, "properties": {}}, {"type": "wokwi-heart-beat-sensor", "id": "pulse-hb1", "x": 440, "y": 260, "properties": {}}],
|
||
wires: [{"id": "pulse-oled-vcc", "start": {"componentId": "arduino-uno", "pinName": "3V3"}, "end": {"componentId": "pulse-oled1", "pinName": "3V3"}, "color": "#ff4444"}, {"id": "pulse-oled-gnd", "start": {"componentId": "arduino-uno", "pinName": "GND"}, "end": {"componentId": "pulse-oled1", "pinName": "GND"}, "color": "#000000"}, {"id": "pulse-oled-sda", "start": {"componentId": "arduino-uno", "pinName": "21"}, "end": {"componentId": "pulse-oled1", "pinName": "DATA"}, "color": "#22aaff"}, {"id": "pulse-oled-scl", "start": {"componentId": "arduino-uno", "pinName": "22"}, "end": {"componentId": "pulse-oled1", "pinName": "CLK"}, "color": "#ff8800"}, {"id": "pulse-hb-vcc", "start": {"componentId": "arduino-uno", "pinName": "3V3"}, "end": {"componentId": "pulse-hb1", "pinName": "VCC"}, "color": "#ff4444"}, {"id": "pulse-hb-gnd", "start": {"componentId": "arduino-uno", "pinName": "GND"}, "end": {"componentId": "pulse-hb1", "pinName": "GND"}, "color": "#000000"}, {"id": "pulse-hb-out", "start": {"componentId": "arduino-uno", "pinName": "34"}, "end": {"componentId": "pulse-hb1", "pinName": "OUT"}, "color": "#22cc66"}],
|
||
tags: ["100-days", "esp32", "i2c_oled", "micropython"],
|
||
},
|
||
{
|
||
id: "100d-rgb-color-mixer-using-potentiometers-esp32-plus-micropython",
|
||
title: "RGB Color Mixer using Potentiometers (ESP32 + MicroPython)",
|
||
description: "RGB Color Mixer using Potentiometers (ESP32 + MicroPython) on ESP32 (MicroPython) \u2014 uses Servo. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "robotics",
|
||
difficulty: "beginner",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "main.py", content: `# ESP32 MicroPython - RGB controlled by 3 potentiometers (common-cathode example)
|
||
from machine import ADC, Pin, PWM
|
||
import time
|
||
|
||
# PWM pins for RGB
|
||
p_r = PWM(Pin(25), freq=1000) # R
|
||
p_g = PWM(Pin(26), freq=1000) # G
|
||
p_b = PWM(Pin(27), freq=1000) # B
|
||
|
||
# ADC pins for pots
|
||
a_r = ADC(Pin(36)) # Pot for R
|
||
a_r.atten(ADC.ATTN_11DB)
|
||
a_g = ADC(Pin(39))
|
||
a_g.atten(ADC.ATTN_11DB)
|
||
a_b = ADC(Pin(34))
|
||
a_b.atten(ADC.ATTN_11DB)
|
||
|
||
# Helper: map ADC (0-4095) to PWM duty (0-1023)
|
||
def map_adc_to_duty(x):
|
||
return int((x / 4095) * 1023)
|
||
# If you have common-anode LED, set invert = True
|
||
invert = False
|
||
|
||
while True:
|
||
# Read potentiometer values
|
||
raw_r = a_r.read()
|
||
raw_g = a_g.read()
|
||
raw_b = a_b.read()
|
||
|
||
# Map to PWM duty
|
||
r = map_adc_to_duty(raw_r)
|
||
g = map_adc_to_duty(raw_g)
|
||
b = map_adc_to_duty(raw_b)
|
||
|
||
if invert:
|
||
r = 1023 - r
|
||
g = 1023 - g
|
||
b = 1023 - b
|
||
|
||
# Apply PWM
|
||
p_r.duty(r)
|
||
p_g.duty(g)
|
||
p_b.duty(b)
|
||
|
||
# Print live values
|
||
print("ADC R: {:4d} G: {:4d} B: {:4d} | PWM R: {:4d} G: {:4d} B: {:4d}".format(
|
||
raw_r, raw_g, raw_b, r, g, b
|
||
))
|
||
|
||
time.sleep_ms(200)
|
||
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "esp32", "micropython", "servo"],
|
||
},
|
||
{
|
||
id: "100d-raspberry-pi-pico-2-w-thingsboard-iot",
|
||
title: "Raspberry Pi Pico 2 W ThingsBoard IoT",
|
||
description: "Raspberry Pi Pico 2 W ThingsBoard IoT on Raspberry Pi Pico W (MicroPython) \u2014 uses DHT, MQTT, Wi-Fi. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "communication",
|
||
difficulty: "intermediate",
|
||
boardType: "pi-pico-w",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "main.py", content: `import network
|
||
import time
|
||
import json
|
||
import dht
|
||
from machine import Pin
|
||
from umqttsimple import MQTTClient
|
||
|
||
# --- Configuration ---
|
||
WIFI_SSID = "kritish" #
|
||
WIFI_PASSWORD = "@pass"
|
||
THINGSBOARD_HOST = "eu.thingsboard.cloud"
|
||
ACCESS_TOKEN = "tokenn"
|
||
|
||
# --- Hardware Setup ---
|
||
# Using "LED" for Pico W onboard LED, or Pin(15) for an external LED
|
||
led = Pin("LED", Pin.OUT)
|
||
sensor = dht.DHT22(Pin(4)) # Velxio emulates the DHT22 waveform (use DHT11 on real hw)
|
||
led_state = False
|
||
|
||
# --- MQTT Callback (Handles Switch Commands) ---
|
||
def on_message(topic, msg):
|
||
global led_state
|
||
print(f"\\n[RPC] Message received: {msg}")
|
||
|
||
try:
|
||
data = json.loads(msg)
|
||
method = data.get("method")
|
||
params = data.get("params")
|
||
|
||
# Handling 'setState' from your ThingsBoard Switch
|
||
if method == "setState":
|
||
# This check ensures both boolean and string 'true' work
|
||
if params is True or params == "true" or params == 1:
|
||
led_state = True
|
||
led.value(1)
|
||
else:
|
||
led_state = False
|
||
led.value(0)
|
||
|
||
print(f"Action: LED is now {'ON' if led_state else 'OFF'}")
|
||
|
||
# Handling 'getState' to sync the dashboard switch
|
||
elif method == "getState":
|
||
request_id = topic.decode().split('/')[-1]
|
||
client.publish(f"v1/devices/me/rpc/response/{request_id}", json.dumps(led_state))
|
||
print("Action: Synced state with Dashboard")
|
||
|
||
except Exception as e:
|
||
print(f"RPC Error: {e}")
|
||
|
||
# --- Connection Functions ---
|
||
def connect_wifi():
|
||
sta_if = network.WLAN(network.STA_IF)
|
||
sta_if.active(True)
|
||
sta_if.connect(WIFI_SSID, WIFI_PASSWORD)
|
||
print("Connecting to WiFi", end="")
|
||
while not sta_if.isconnected():
|
||
print(".", end="")
|
||
time.sleep(0.5)
|
||
print(f"\\nConnected! IP: {sta_if.ifconfig()[0]}")
|
||
|
||
def connect_mqtt():
|
||
client = MQTTClient("Pico_W_Cuttack", THINGSBOARD_HOST, user=ACCESS_TOKEN, password="")
|
||
client.set_callback(on_message)
|
||
client.connect()
|
||
client.subscribe(b"v1/devices/me/rpc/request/+")
|
||
print("Connected to ThingsBoard MQTT!")
|
||
return client
|
||
|
||
# --- Main Logic ---
|
||
connect_wifi()
|
||
client = connect_mqtt()
|
||
|
||
last_telemetry = 0
|
||
|
||
print("System Ready. Waiting for commands...")
|
||
|
||
while True:
|
||
try:
|
||
# Check for incoming RPC (Switch) messages
|
||
client.check_msg()
|
||
|
||
# Send Telemetry every 10 seconds (standard for cloud stability)
|
||
if (time.time() - last_telemetry) > 10:
|
||
sensor.measure()
|
||
t = sensor.temperature()
|
||
h = sensor.humidity()
|
||
|
||
payload = json.dumps({
|
||
"temperature": t,
|
||
"humidity": h,
|
||
"ledStatus": led_state
|
||
})
|
||
|
||
client.publish("v1/devices/me/telemetry", payload)
|
||
print(f"Published: {payload}")
|
||
last_telemetry = time.time()
|
||
|
||
except Exception as e:
|
||
print(f"Error in Loop: {e}")
|
||
time.sleep(5)
|
||
import machine
|
||
machine.reset()
|
||
` },
|
||
{ name: "umqttsimple.py", content: `try:
|
||
import usocket as socket
|
||
except:
|
||
import socket
|
||
import ustruct as struct
|
||
from ubinascii import hexlify
|
||
|
||
class MQTTException(Exception):
|
||
pass
|
||
|
||
class MQTTClient:
|
||
|
||
def __init__(self, client_id, server, port=0, user=None, password=None, keepalive=0,
|
||
ssl=False, ssl_params={}):
|
||
if port == 0:
|
||
port = 8883 if ssl else 1883
|
||
self.client_id = client_id
|
||
self.sock = None
|
||
self.server = server
|
||
self.port = port
|
||
self.ssl = ssl
|
||
self.ssl_params = ssl_params
|
||
self.pid = 0
|
||
self.cb = None
|
||
self.user = user
|
||
self.pswd = password
|
||
self.keepalive = keepalive
|
||
self.lw_topic = None
|
||
self.lw_msg = None
|
||
self.lw_qos = 0
|
||
self.lw_retain = False
|
||
|
||
def _send_str(self, s):
|
||
self.sock.write(struct.pack("!H", len(s)))
|
||
self.sock.write(s)
|
||
|
||
def _recv_len(self):
|
||
n = 0
|
||
sh = 0
|
||
while 1:
|
||
b = self.sock.read(1)[0]
|
||
n |= (b & 0x7f) << sh
|
||
if not b & 0x80:
|
||
return n
|
||
sh += 7
|
||
|
||
def set_callback(self, f):
|
||
self.cb = f
|
||
|
||
def set_last_will(self, topic, msg, retain=False, qos=0):
|
||
assert 0 <= qos <= 2
|
||
assert topic
|
||
self.lw_topic = topic
|
||
self.lw_msg = msg
|
||
self.lw_qos = qos
|
||
self.lw_retain = retain
|
||
|
||
def connect(self, clean_session=True):
|
||
self.sock = socket.socket()
|
||
addr = socket.getaddrinfo(self.server, self.port)[0][-1]
|
||
self.sock.connect(addr)
|
||
if self.ssl:
|
||
import ussl
|
||
self.sock = ussl.wrap_socket(self.sock, **self.ssl_params)
|
||
premsg = bytearray(b"\\x10\\0\\0\\0\\0\\0")
|
||
msg = bytearray(b"\\x04MQTT\\x04\\x02\\0\\0")
|
||
|
||
sz = 10 + 2 + len(self.client_id)
|
||
msg[6] = clean_session << 1
|
||
if self.user is not None:
|
||
sz += 2 + len(self.user) + 2 + len(self.pswd)
|
||
msg[6] |= 0xC0
|
||
if self.keepalive:
|
||
assert self.keepalive < 65536
|
||
msg[7] |= self.keepalive >> 8
|
||
msg[8] |= self.keepalive & 0x00FF
|
||
if self.lw_topic:
|
||
sz += 2 + len(self.lw_topic) + 2 + len(self.lw_msg)
|
||
msg[6] |= 0x4 | (self.lw_qos & 0x1) << 3 | (self.lw_qos & 0x2) << 3
|
||
msg[6] |= self.lw_retain << 5
|
||
|
||
i = 1
|
||
while sz > 0x7f:
|
||
premsg[i] = (sz & 0x7f) | 0x80
|
||
sz >>= 7
|
||
i += 1
|
||
premsg[i] = sz
|
||
|
||
self.sock.write(premsg, i + 2)
|
||
self.sock.write(msg)
|
||
#print(hex(len(msg)), hexlify(msg, ":"))
|
||
self._send_str(self.client_id)
|
||
if self.lw_topic:
|
||
self._send_str(self.lw_topic)
|
||
self._send_str(self.lw_msg)
|
||
if self.user is not None:
|
||
self._send_str(self.user)
|
||
self._send_str(self.pswd)
|
||
resp = self.sock.read(4)
|
||
assert resp[0] == 0x20 and resp[1] == 0x02
|
||
if resp[3] != 0:
|
||
raise MQTTException(resp[3])
|
||
return resp[2] & 1
|
||
|
||
def disconnect(self):
|
||
self.sock.write(b"\\xe0\\0")
|
||
self.sock.close()
|
||
|
||
def ping(self):
|
||
self.sock.write(b"\\xc0\\0")
|
||
|
||
def publish(self, topic, msg, retain=False, qos=0):
|
||
pkt = bytearray(b"\\x30\\0\\0\\0")
|
||
pkt[0] |= qos << 1 | retain
|
||
sz = 2 + len(topic) + len(msg)
|
||
if qos > 0:
|
||
sz += 2
|
||
assert sz < 2097152
|
||
i = 1
|
||
while sz > 0x7f:
|
||
pkt[i] = (sz & 0x7f) | 0x80
|
||
sz >>= 7
|
||
i += 1
|
||
pkt[i] = sz
|
||
#print(hex(len(pkt)), hexlify(pkt, ":"))
|
||
self.sock.write(pkt, i + 1)
|
||
self._send_str(topic)
|
||
if qos > 0:
|
||
self.pid += 1
|
||
pid = self.pid
|
||
struct.pack_into("!H", pkt, 0, pid)
|
||
self.sock.write(pkt, 2)
|
||
self.sock.write(msg)
|
||
if qos == 1:
|
||
while 1:
|
||
op = self.wait_msg()
|
||
if op == 0x40:
|
||
sz = self.sock.read(1)
|
||
assert sz == b"\\x02"
|
||
rcv_pid = self.sock.read(2)
|
||
rcv_pid = rcv_pid[0] << 8 | rcv_pid[1]
|
||
if pid == rcv_pid:
|
||
return
|
||
elif qos == 2:
|
||
assert 0
|
||
|
||
def subscribe(self, topic, qos=0):
|
||
assert self.cb is not None, "Subscribe callback is not set"
|
||
pkt = bytearray(b"\\x82\\0\\0\\0")
|
||
self.pid += 1
|
||
struct.pack_into("!BH", pkt, 1, 2 + 2 + len(topic) + 1, self.pid)
|
||
#print(hex(len(pkt)), hexlify(pkt, ":"))
|
||
self.sock.write(pkt)
|
||
self._send_str(topic)
|
||
self.sock.write(qos.to_bytes(1, "little"))
|
||
while 1:
|
||
op = self.wait_msg()
|
||
if op == 0x90:
|
||
resp = self.sock.read(4)
|
||
#print(resp)
|
||
assert resp[1] == pkt[2] and resp[2] == pkt[3]
|
||
if resp[3] == 0x80:
|
||
raise MQTTException(resp[3])
|
||
return
|
||
|
||
# Wait for a single incoming MQTT message and process it.
|
||
# Subscribed messages are delivered to a callback previously
|
||
# set by .set_callback() method. Other (internal) MQTT
|
||
# messages processed internally.
|
||
def wait_msg(self):
|
||
res = self.sock.read(1)
|
||
self.sock.setblocking(True)
|
||
if res is None:
|
||
return None
|
||
if res == b"":
|
||
raise OSError(-1)
|
||
if res == b"\\xd0": # PINGRESP
|
||
sz = self.sock.read(1)[0]
|
||
assert sz == 0
|
||
return None
|
||
op = res[0]
|
||
if op & 0xf0 != 0x30:
|
||
return op
|
||
sz = self._recv_len()
|
||
topic_len = self.sock.read(2)
|
||
topic_len = (topic_len[0] << 8) | topic_len[1]
|
||
topic = self.sock.read(topic_len)
|
||
sz -= topic_len + 2
|
||
if op & 6:
|
||
pid = self.sock.read(2)
|
||
pid = pid[0] << 8 | pid[1]
|
||
sz -= 2
|
||
msg = self.sock.read(sz)
|
||
self.cb(topic, msg)
|
||
if op & 6 == 2:
|
||
pkt = bytearray(b"\\x40\\x02\\0\\0")
|
||
struct.pack_into("!H", pkt, 2, pid)
|
||
self.sock.write(pkt)
|
||
elif op & 6 == 4:
|
||
assert 0
|
||
|
||
# Checks whether a pending message from server is available.
|
||
# If not, returns immediately with None. Otherwise, does
|
||
# the same processing as wait_msg.
|
||
def check_msg(self):
|
||
self.sock.setblocking(False)
|
||
return self.wait_msg()
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [{"type": "wokwi-dht22", "id": "tb-dht1", "x": 440, "y": 200, "properties": {"temperature": "28", "humidity": "65"}}],
|
||
wires: [{"id": "tb-dht-vcc", "start": {"componentId": "arduino-uno", "pinName": "3V3"}, "end": {"componentId": "tb-dht1", "pinName": "VCC"}, "color": "#ff4444"}, {"id": "tb-dht-gnd", "start": {"componentId": "arduino-uno", "pinName": "GND"}, "end": {"componentId": "tb-dht1", "pinName": "GND"}, "color": "#000000"}, {"id": "tb-dht-data", "start": {"componentId": "arduino-uno", "pinName": "GP4"}, "end": {"componentId": "tb-dht1", "pinName": "SDA"}, "color": "#22cc66"}],
|
||
tags: ["100-days", "dht", "micropython", "mqtt", "pi-pico-w", "pico-w", "wifi"],
|
||
},
|
||
{
|
||
id: "100d-servo-motor-control-with-raspberry-pi-pico-2-w-micropython",
|
||
title: "Servo Motor Control with Raspberry Pi Pico 2 W (MicroPython)",
|
||
description: "Servo Motor Control with Raspberry Pi Pico 2 W (MicroPython) on Raspberry Pi Pico W (MicroPython) \u2014 uses Servo. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "robotics",
|
||
difficulty: "beginner",
|
||
boardType: "pi-pico-w",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "main.py", content: `from machine import Pin, PWM
|
||
import time
|
||
|
||
servo = PWM(Pin(15))
|
||
servo.freq(50)
|
||
|
||
print("=== Servo Angle Control (Safe Mode) ===")
|
||
|
||
def set_angle(angle):
|
||
# Clamp angles to safe mechanical range
|
||
if angle < 20:
|
||
angle = 20
|
||
elif angle > 170:
|
||
angle = 170
|
||
|
||
min_duty = 1200 # calibrated
|
||
max_duty = 8800
|
||
|
||
duty = int(min_duty + (angle / 180) * (max_duty - min_duty))
|
||
servo.duty_u16(duty)
|
||
|
||
print("Requested Angle:", angle)
|
||
print("Applied Duty:", duty)
|
||
|
||
while True:
|
||
try:
|
||
angle = int(input("\\nEnter angle (0-180): "))
|
||
set_angle(angle)
|
||
|
||
except ValueError:
|
||
print("❌ Enter a valid number")
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "micropython", "pi-pico-w", "pico-w", "servo"],
|
||
},
|
||
{
|
||
id: "100d-single-digit-seven-segment-display-with-raspberry-pi-pico-micropython",
|
||
title: "Single Digit Seven Segment Display with Raspberry Pi-Pico (MicroPython)",
|
||
description: "Single Digit Seven Segment Display with Raspberry Pi-Pico (MicroPython) on Raspberry Pi Pico (MicroPython). From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "basics",
|
||
difficulty: "beginner",
|
||
boardType: "raspberry-pi-pico",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "main.py", content: `from machine import Pin # Import Pin class to control GPIO pins
|
||
|
||
# ----------------------------
|
||
# Define 7-segment display pins
|
||
# Each pin corresponds to one segment: a, b, c, d, e, f, g
|
||
# Adjust wiring as per your circuit
|
||
# ----------------------------
|
||
led_a = Pin(2, Pin.OUT)
|
||
led_b = Pin(3, Pin.OUT)
|
||
led_c = Pin(6, Pin.OUT)
|
||
led_d = Pin(5, Pin.OUT)
|
||
led_e = Pin(4, Pin.OUT)
|
||
led_f = Pin(1, Pin.OUT)
|
||
led_g = Pin(0, Pin.OUT)
|
||
|
||
# ----------------------------
|
||
# Functions for digits 0-9
|
||
# Each function turns ON/OFF segments to display the correct number
|
||
# ----------------------------
|
||
|
||
def value_one():
|
||
led_a.value(0)
|
||
led_b.value(0)
|
||
led_c.value(0)
|
||
led_d.value(0)
|
||
led_e.value(1)
|
||
led_f.value(1)
|
||
led_g.value(0)
|
||
|
||
def value_two():
|
||
led_a.value(1)
|
||
led_b.value(1)
|
||
led_c.value(0)
|
||
led_d.value(1)
|
||
led_e.value(1)
|
||
led_f.value(0)
|
||
led_g.value(1)
|
||
|
||
def value_three():
|
||
led_a.value(1)
|
||
led_b.value(1)
|
||
led_c.value(1)
|
||
led_d.value(1)
|
||
led_e.value(0)
|
||
led_f.value(0)
|
||
led_g.value(1)
|
||
|
||
def value_four():
|
||
led_a.value(0)
|
||
led_b.value(1)
|
||
led_c.value(1)
|
||
led_d.value(0)
|
||
led_e.value(0)
|
||
led_f.value(1)
|
||
led_g.value(1)
|
||
|
||
def value_five():
|
||
led_a.value(1)
|
||
led_b.value(0)
|
||
led_c.value(1)
|
||
led_d.value(1)
|
||
led_e.value(0)
|
||
led_f.value(1)
|
||
led_g.value(1)
|
||
|
||
def value_six():
|
||
led_a.value(1)
|
||
led_b.value(0)
|
||
led_c.value(1)
|
||
led_d.value(1)
|
||
led_e.value(1)
|
||
led_f.value(1)
|
||
led_g.value(1)
|
||
|
||
def value_seven():
|
||
led_a.value(1)
|
||
led_b.value(1)
|
||
led_c.value(1)
|
||
led_d.value(0)
|
||
led_e.value(0)
|
||
led_f.value(0)
|
||
led_g.value(0)
|
||
|
||
def value_eight():
|
||
led_a.value(1)
|
||
led_b.value(1)
|
||
led_c.value(1)
|
||
led_d.value(1)
|
||
led_e.value(1)
|
||
led_f.value(1)
|
||
led_g.value(1)
|
||
|
||
def value_nine():
|
||
led_a.value(1)
|
||
led_b.value(1)
|
||
led_c.value(1)
|
||
led_d.value(0)
|
||
led_e.value(0)
|
||
led_f.value(1)
|
||
led_g.value(1)
|
||
|
||
def value_zero():
|
||
led_a.value(1)
|
||
led_b.value(1)
|
||
led_c.value(1)
|
||
led_d.value(1)
|
||
led_e.value(1)
|
||
led_f.value(1)
|
||
led_g.value(0)
|
||
|
||
# ----------------------------
|
||
# Main Loop
|
||
# User enters a digit (0-9) via input
|
||
# That digit is displayed on the 7-segment
|
||
# ----------------------------
|
||
while True:
|
||
k = int(input("Enter a number (0-9): "))
|
||
if k == 0:
|
||
value_zero()
|
||
elif k == 1:
|
||
value_one()
|
||
elif k == 2:
|
||
value_two()
|
||
elif k == 3:
|
||
value_three()
|
||
elif k == 4:
|
||
value_four()
|
||
elif k == 5:
|
||
value_five()
|
||
elif k == 6:
|
||
value_six()
|
||
elif k == 7:
|
||
value_seven()
|
||
elif k == 8:
|
||
value_eight()
|
||
elif k == 9:
|
||
value_nine()
|
||
else:
|
||
print("Please Enter a value between 0-9 and one digit")
|
||
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "micropython", "raspberry-pi-pico", "rp2040"],
|
||
},
|
||
{
|
||
id: "100d-smart-home-automation-system",
|
||
title: "Smart Home Automation System",
|
||
description: "Smart Home Automation System on ESP32 (MicroPython) \u2014 uses Blynk, Blynk Cloud, DHT, Wi-Fi. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "communication",
|
||
difficulty: "advanced",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "BlynkLib.py", content: `# 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()
|
||
# Velxio patch: bound the blocking connect so an unreachable cloud
|
||
# raises OSError instead of freezing the sketch forever.
|
||
s.settimeout(10)
|
||
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)
|
||
|
||
` },
|
||
{ name: "main.py", content: `'''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
|
||
sleep(0.1)
|
||
|
||
print("Connected to Wi-Fi:", sta_if.ifconfig()) # Print the Wi-Fi connection details
|
||
|
||
# Initialize Blynk Connection
|
||
# ── Velxio adaptation ──────────────────────────────────────────────────────────────────
|
||
# The vendored BlynkLib bounds its connect() with a timeout (Velxio patch),
|
||
# so an unreachable cloud raises OSError here instead of freezing the demo;
|
||
# fall back to an offline no-op Blynk object in that case.
|
||
class _OfflineBlynk:
|
||
def on(self, *_a, **_k):
|
||
def _deco(f):
|
||
return f
|
||
return _deco
|
||
def run(self):
|
||
pass
|
||
def virtual_write(self, *_a):
|
||
pass
|
||
|
||
|
||
try:
|
||
blynk = BlynkLib.Blynk(auth, insecure=True)
|
||
except OSError as e:
|
||
print("Blynk Cloud unreachable, running offline:", e)
|
||
blynk = _OfflineBlynk()
|
||
|
||
# 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.DHT22(Pin(14)) # DHT on pin 14 — Velxio emulates the DHT22 waveform (use DHT11 on real hw)
|
||
|
||
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)
|
||
try:
|
||
sensor.measure() # Measure temperature and humidity
|
||
temperature = sensor.temperature() # Get the temperature value
|
||
print("Temp:", temperature, "C")
|
||
blynk.virtual_write(5, temperature) # Send temperature to Blynk virtual pin V5
|
||
except OSError as e:
|
||
print("DHT read failed:", e) # keep the loop alive (sensor unplugged?)
|
||
sleep(2)
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [{"type": "wokwi-dht22", "id": "home-dht1", "x": 460, "y": 320, "properties": {"temperature": "28", "humidity": "65"}}, {"type": "wokwi-resistor", "id": "home-r16", "x": 420, "y": 60, "properties": {"value": "220"}}, {"type": "wokwi-led", "id": "home-led16", "x": 420, "y": 160, "properties": {"color": "red"}}, {"type": "wokwi-resistor", "id": "home-r5", "x": 510, "y": 60, "properties": {"value": "220"}}, {"type": "wokwi-led", "id": "home-led5", "x": 510, "y": 160, "properties": {"color": "green"}}, {"type": "wokwi-resistor", "id": "home-r4", "x": 600, "y": 60, "properties": {"value": "220"}}, {"type": "wokwi-led", "id": "home-led4", "x": 600, "y": 160, "properties": {"color": "blue"}}, {"type": "wokwi-resistor", "id": "home-r0", "x": 690, "y": 60, "properties": {"value": "220"}}, {"type": "wokwi-led", "id": "home-led0", "x": 690, "y": 160, "properties": {"color": "yellow"}}],
|
||
wires: [{"id": "home-dht-vcc", "start": {"componentId": "arduino-uno", "pinName": "3V3"}, "end": {"componentId": "home-dht1", "pinName": "VCC"}, "color": "#ff4444"}, {"id": "home-dht-gnd", "start": {"componentId": "arduino-uno", "pinName": "GND"}, "end": {"componentId": "home-dht1", "pinName": "GND"}, "color": "#000000"}, {"id": "home-dht-data", "start": {"componentId": "arduino-uno", "pinName": "14"}, "end": {"componentId": "home-dht1", "pinName": "SDA"}, "color": "#22cc66"}, {"id": "home-vcc16", "start": {"componentId": "arduino-uno", "pinName": "3V3"}, "end": {"componentId": "home-r16", "pinName": "1"}, "color": "#ff4444"}, {"id": "home-ra16", "start": {"componentId": "home-r16", "pinName": "2"}, "end": {"componentId": "home-led16", "pinName": "A"}, "color": "#cc44cc"}, {"id": "home-cpin16", "start": {"componentId": "home-led16", "pinName": "C"}, "end": {"componentId": "arduino-uno", "pinName": "16"}, "color": "#22aaff"}, {"id": "home-vcc5", "start": {"componentId": "arduino-uno", "pinName": "3V3"}, "end": {"componentId": "home-r5", "pinName": "1"}, "color": "#ff4444"}, {"id": "home-ra5", "start": {"componentId": "home-r5", "pinName": "2"}, "end": {"componentId": "home-led5", "pinName": "A"}, "color": "#cc44cc"}, {"id": "home-cpin5", "start": {"componentId": "home-led5", "pinName": "C"}, "end": {"componentId": "arduino-uno", "pinName": "5"}, "color": "#22aaff"}, {"id": "home-vcc4", "start": {"componentId": "arduino-uno", "pinName": "3V3"}, "end": {"componentId": "home-r4", "pinName": "1"}, "color": "#ff4444"}, {"id": "home-ra4", "start": {"componentId": "home-r4", "pinName": "2"}, "end": {"componentId": "home-led4", "pinName": "A"}, "color": "#cc44cc"}, {"id": "home-cpin4", "start": {"componentId": "home-led4", "pinName": "C"}, "end": {"componentId": "arduino-uno", "pinName": "4"}, "color": "#22aaff"}, {"id": "home-vcc0", "start": {"componentId": "arduino-uno", "pinName": "3V3"}, "end": {"componentId": "home-r0", "pinName": "1"}, "color": "#ff4444"}, {"id": "home-ra0", "start": {"componentId": "home-r0", "pinName": "2"}, "end": {"componentId": "home-led0", "pinName": "A"}, "color": "#cc44cc"}, {"id": "home-cpin0", "start": {"componentId": "home-led0", "pinName": "C"}, "end": {"componentId": "arduino-uno", "pinName": "0"}, "color": "#22aaff"}],
|
||
tags: ["100-days", "blynk", "blynk_cloud", "dht", "esp32", "micropython", "wifi"],
|
||
},
|
||
{
|
||
id: "100d-smart-indoor-security-system",
|
||
title: "Smart Indoor Security System",
|
||
description: "Smart Indoor Security System on ESP32 (MicroPython) \u2014 uses Wi-Fi. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "communication",
|
||
difficulty: "intermediate",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "main.py", content: `import machine, utime, network, urequests, ujson
|
||
|
||
|
||
|
||
# ─── Favoriot Credentials ───────────────────────
|
||
API_KEY = '..'
|
||
USERNAME = ''
|
||
DEVICE_ID = 'SmartSecurityr'+USERNAME
|
||
URL = 'https://apiv2.favoriot.com/v2/streams'
|
||
|
||
#import machine, utime, network, urequests, ujson
|
||
|
||
# ─── WiFi Credentials ───────────────────────────
|
||
SSID = 'Wokwi-GUEST'
|
||
PASSWORD = ''
|
||
|
||
|
||
|
||
# ─── Pin Setup ──────────────────────────────────
|
||
pir = machine.Pin(14, machine.Pin.IN)
|
||
|
||
ROW_PINS = [19, 18, 5, 17]
|
||
COL_PINS = [16, 4, 2, 15]
|
||
KEYS = [
|
||
['1','2','3','A'],
|
||
['4','5','6','B'],
|
||
['7','8','9','C'],
|
||
['*','0','#','D']
|
||
]
|
||
|
||
rows = [machine.Pin(p, machine.Pin.OUT) for p in ROW_PINS]
|
||
cols = [machine.Pin(p, machine.Pin.IN, machine.Pin.PULL_DOWN) for p in COL_PINS]
|
||
|
||
# ─── Settings ───────────────────────────────────
|
||
CORRECT_PASSWORD = '1234'
|
||
TIMEOUT_MS = 30000
|
||
|
||
# ─── WiFi Connect ───────────────────────────────
|
||
def connect_wifi():
|
||
wlan = network.WLAN(network.STA_IF)
|
||
wlan.active(True)
|
||
wlan.connect(SSID, PASSWORD)
|
||
print('Connecting to WiFi', end='')
|
||
for _ in range(20):
|
||
if wlan.isconnected():
|
||
break
|
||
print('.', end='')
|
||
utime.sleep(0.5)
|
||
if wlan.isconnected():
|
||
print(' Connected! IP:', wlan.ifconfig()[0])
|
||
else:
|
||
print(' WiFi Failed - running without Favoriot')
|
||
|
||
# ─── Keypad ─────────────────────────────────────
|
||
def get_key():
|
||
for r, row in enumerate(rows):
|
||
row.value(1)
|
||
for c, col in enumerate(cols):
|
||
if col.value() == 1:
|
||
row.value(0)
|
||
# Wait for button release
|
||
while col.value() == 1:
|
||
utime.sleep_ms(10)
|
||
utime.sleep_ms(50)
|
||
return KEYS[r][c]
|
||
row.value(0)
|
||
return None
|
||
# ─── Favoriot ───────────────────────────────────
|
||
def send_to_favoriot(event, status, access):
|
||
print('>>> FAVORIOT | event:', event, '| status:', status, '| access:', access)
|
||
wlan = network.WLAN(network.STA_IF)
|
||
if not wlan.isconnected():
|
||
print('No WiFi - skipping Favoriot')
|
||
return
|
||
try:
|
||
headers = {
|
||
'Content-Type': 'application/json',
|
||
'apikey': API_KEY,
|
||
'username': USERNAME
|
||
}
|
||
body = ujson.dumps({
|
||
'device_developer_id': DEVICE_ID,
|
||
'data': {
|
||
'event': event,
|
||
'status': status,
|
||
'access': str(access)
|
||
}
|
||
})
|
||
r = urequests.post(URL, headers=headers, data=body)
|
||
print('Favoriot Response:', r.status_code)
|
||
r.close()
|
||
except Exception as e:
|
||
print('Favoriot Error:', e)
|
||
|
||
# ─── Main ───────────────────────────────────────
|
||
connect_wifi()
|
||
print('System Ready - Waiting for motion...')
|
||
|
||
while True:
|
||
if pir.value() == 1:
|
||
print('Motion Detected!')
|
||
send_to_favoriot('MOTION_DETECTED', 'WAITING', 0)
|
||
|
||
entered = ''
|
||
start = utime.ticks_ms()
|
||
|
||
while utime.ticks_diff(utime.ticks_ms(), start) < TIMEOUT_MS:
|
||
key = get_key()
|
||
if key:
|
||
if key == '#':
|
||
if entered == CORRECT_PASSWORD:
|
||
print('Access Granted!')
|
||
send_to_favoriot('ACCESS_GRANTED', 'SAFE', 1)
|
||
else:
|
||
print('Wrong Password!')
|
||
send_to_favoriot('WRONG_PASSWORD', 'ALARM', 0)
|
||
break
|
||
elif key == '*':
|
||
entered = ''
|
||
print('Cleared')
|
||
else:
|
||
entered += key
|
||
print('*', end='')
|
||
else:
|
||
print('Timeout - Alarm!')
|
||
send_to_favoriot('TIMEOUT_ALARM', 'ALARM', 0)
|
||
|
||
utime.sleep_ms(100)` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "esp32", "micropython", "wifi"],
|
||
},
|
||
{
|
||
id: "100d-smart-iot-gas-monitoring-system",
|
||
title: "Smart IoT Gas Monitoring System",
|
||
description: "Smart IoT Gas Monitoring System on ESP32 (MicroPython) \u2014 uses DHT, HTTP server, Wi-Fi. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "communication",
|
||
difficulty: "intermediate",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "analyze.py", content: `import pandas as pd
|
||
import matplotlib.pyplot as plt
|
||
|
||
# Read CSV
|
||
df = pd.read_csv(
|
||
"data.csv",
|
||
header=None,
|
||
names=["time", "gas", "temp", "humidity", "soil"]
|
||
)
|
||
|
||
df["time"] = pd.to_datetime(df["time"])
|
||
|
||
sensors = [
|
||
("gas", "Gas Sensor"),
|
||
("temp", "Temperature"),
|
||
("soil", "Soil Moisture")
|
||
]
|
||
|
||
fig, axes = plt.subplots(3, 1, figsize=(12, 8), sharex=True)
|
||
|
||
for ax, (col, title) in zip(axes, sensors):
|
||
mean = df[col].mean()
|
||
std = df[col].std()
|
||
threshold = mean + 2 * std
|
||
|
||
alert = df[col] > threshold
|
||
|
||
print(f"\\n{title}")
|
||
print("Mean:", mean)
|
||
print("Std :", std)
|
||
print("Alerts:", alert.sum())
|
||
|
||
ax.plot(df["time"], df[col], label=col)
|
||
ax.scatter(
|
||
df[alert]["time"],
|
||
df[alert][col],
|
||
label="ALERT"
|
||
)
|
||
ax.axhline(threshold, linestyle="--", label="Threshold")
|
||
ax.set_ylabel(col)
|
||
ax.set_title(title)
|
||
ax.legend()
|
||
|
||
axes[-1].set_xlabel("Time")
|
||
plt.suptitle("IoT Sensor Analytics Dashboard", fontsize=14)
|
||
plt.tight_layout()
|
||
plt.show()
|
||
` },
|
||
{ name: "app.py", content: `# from flask import Flask, request
|
||
# import csv
|
||
# from datetime import datetime
|
||
|
||
# app = Flask(__name__)
|
||
|
||
# @app.route("/")
|
||
# def home():
|
||
# return "Flask server running OK"
|
||
|
||
# @app.route("/data", methods=["POST"])
|
||
# def receive():
|
||
# data = request.json
|
||
# with open("data.csv", "a", newline="") as f:
|
||
# writer = csv.writer(f)
|
||
# writer.writerow([
|
||
# datetime.now(),
|
||
# data["gas"],
|
||
# data["temp"],
|
||
# data["humidity"],
|
||
# data["soil"]
|
||
# ])
|
||
# return {"status": "ok"}
|
||
|
||
# app.run(host="0.0.0.0", port=5000, debug=True)
|
||
|
||
# -------------------- IMPORTS --------------------
|
||
# Flask framework for building web server
|
||
# request -> to receive JSON data from ESP32
|
||
# render_template -> to serve HTML dashboard
|
||
# jsonify -> to send JSON response to frontend
|
||
|
||
from flask import Flask, request, render_template, jsonify
|
||
|
||
# CSV module to store sensor data logs
|
||
import csv
|
||
|
||
# datetime module to store timestamp
|
||
from datetime import datetime
|
||
|
||
|
||
# -------------------- FLASK APP INITIALIZATION --------------------
|
||
app = Flask(__name__)
|
||
|
||
|
||
# -------------------- LIVE DATA STORAGE --------------------
|
||
# This dictionary stores the latest sensor readings
|
||
# It is used by the dashboard and live graphs
|
||
latest_data = {
|
||
"gas": 0,
|
||
"temp": 0,
|
||
"humidity": 0,
|
||
"soil": 0,
|
||
"status": "SAFE"
|
||
}
|
||
|
||
|
||
# -------------------- AI MEMORY (HISTORY BUFFER) --------------------
|
||
# List to store recent gas sensor values
|
||
gas_history = []
|
||
|
||
# Number of past readings used for moving average
|
||
WINDOW_SIZE = 10
|
||
|
||
# Sensitivity margin for anomaly detection
|
||
# If gas > average + margin → DANGER
|
||
GAS_MARGIN = 300
|
||
|
||
|
||
# -------------------- DASHBOARD ROUTE --------------------
|
||
# Loads the main dashboard page
|
||
@app.route("/")
|
||
def dashboard():
|
||
return render_template("index.html", data=latest_data)
|
||
|
||
|
||
# -------------------- ESP32 DATA RECEIVER --------------------
|
||
# ESP32 sends sensor data to this route using POST request
|
||
@app.route("/data", methods=["POST"])
|
||
def receive():
|
||
global latest_data, gas_history
|
||
|
||
# Read JSON data sent by ESP32
|
||
data = request.json
|
||
|
||
# Extract sensor values (default = 0 if missing)
|
||
gas = data.get("gas", 0)
|
||
temp = data.get("temp", 0)
|
||
humidity = data.get("humidity", 0)
|
||
soil = data.get("soil", 0)
|
||
|
||
# ---------------- AI LOGIC (Moving Average Based Detection) ----------------
|
||
# Store current gas value in history
|
||
gas_history.append(gas)
|
||
|
||
# Keep only the last WINDOW_SIZE readings
|
||
if len(gas_history) > WINDOW_SIZE:
|
||
gas_history.pop(0)
|
||
|
||
# Calculate average gas level
|
||
avg_gas = sum(gas_history) / len(gas_history)
|
||
|
||
# Detect abnormal gas rise
|
||
# Sudden spike beyond threshold is marked as DANGER
|
||
status = "DANGER" if gas > avg_gas + GAS_MARGIN else "SAFE"
|
||
|
||
# Update latest data dictionary
|
||
latest_data = {
|
||
"gas": gas,
|
||
"temp": temp,
|
||
"humidity": humidity,
|
||
"soil": soil,
|
||
"status": status
|
||
}
|
||
|
||
# ---------------- CSV DATA LOGGING ----------------
|
||
# Store all readings in CSV file for analysis
|
||
with open("data.csv", "a", newline="") as f:
|
||
writer = csv.writer(f)
|
||
writer.writerow([
|
||
datetime.now(), # Timestamp
|
||
gas,
|
||
temp,
|
||
humidity,
|
||
soil,
|
||
status
|
||
])
|
||
|
||
# Send acknowledgment to ESP32
|
||
return {"status": "ok"}
|
||
|
||
|
||
# -------------------- LIVE DATA API --------------------
|
||
# Frontend uses this route to fetch live data for graphs
|
||
@app.route("/latest")
|
||
def latest():
|
||
return jsonify(latest_data)
|
||
|
||
|
||
# -------------------- RUN FLASK SERVER --------------------
|
||
if __name__ == "__main__":
|
||
app.run(host="0.0.0.0", port=5000, debug=True)
|
||
|
||
` },
|
||
{ name: "main.py", content: `import network, urequests, time, dht
|
||
from machine import ADC, Pin
|
||
|
||
ssid = "kritish"
|
||
password = "@Krrs2069"
|
||
|
||
# MQ-2
|
||
mq2 = ADC(Pin(34))
|
||
mq2.atten(ADC.ATTN_11DB)
|
||
|
||
# Soil
|
||
soil = ADC(Pin(35))
|
||
soil.atten(ADC.ATTN_11DB)
|
||
|
||
# DHT
|
||
d = dht.DHT22(Pin(4)) # Velxio emulates the DHT22 waveform (use DHT11 on real hw)
|
||
|
||
# WiFi
|
||
wlan = network.WLAN(network.STA_IF)
|
||
wlan.active(True)
|
||
wlan.connect(ssid, password)
|
||
|
||
t = 0
|
||
while not wlan.isconnected() and t < 10:
|
||
time.sleep(1)
|
||
t += 1
|
||
|
||
if not wlan.isconnected():
|
||
print("WiFi Failed")
|
||
raise SystemExit
|
||
|
||
print("ESP IP:", wlan.ifconfig())
|
||
|
||
url = "http://10.201.102.81:5000/data"
|
||
|
||
while True:
|
||
try:
|
||
d.measure()
|
||
temp = d.temperature()
|
||
hum = d.humidity()
|
||
except:
|
||
temp, hum = None, None
|
||
|
||
gas = mq2.read()
|
||
soil_val = soil.read()
|
||
|
||
payload = {
|
||
"gas": gas,
|
||
"temp": temp,
|
||
"humidity": hum,
|
||
"soil": soil_val
|
||
}
|
||
|
||
try:
|
||
r = urequests.post(url, json=payload)
|
||
print(payload, "->", r.text)
|
||
r.close()
|
||
del r
|
||
except Exception as e:
|
||
print("Failed", e)
|
||
|
||
time.sleep(3)
|
||
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [{"type": "wokwi-dht22", "id": "gas-dht1", "x": 440, "y": 200, "properties": {"temperature": "28", "humidity": "65"}}],
|
||
wires: [{"id": "gas-dht-vcc", "start": {"componentId": "arduino-uno", "pinName": "3V3"}, "end": {"componentId": "gas-dht1", "pinName": "VCC"}, "color": "#ff4444"}, {"id": "gas-dht-gnd", "start": {"componentId": "arduino-uno", "pinName": "GND"}, "end": {"componentId": "gas-dht1", "pinName": "GND"}, "color": "#000000"}, {"id": "gas-dht-data", "start": {"componentId": "arduino-uno", "pinName": "4"}, "end": {"componentId": "gas-dht1", "pinName": "SDA"}, "color": "#22cc66"}],
|
||
tags: ["100-days", "dht", "esp32", "http_server", "micropython", "wifi"],
|
||
},
|
||
{
|
||
id: "100d-stepper-motor-control-using-esp32-and-a4988-micropython",
|
||
title: "Stepper Motor Control using ESP32 & A4988 (MicroPython)",
|
||
description: "Stepper Motor Control using ESP32 & A4988 (MicroPython) on ESP32 (MicroPython) \u2014 uses Stepper. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "robotics",
|
||
difficulty: "intermediate",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "main.py", content: `import time
|
||
import machine
|
||
|
||
# A4988 pins
|
||
dir_pin = machine.Pin(12, machine.Pin.OUT)
|
||
step_pin = machine.Pin(14, machine.Pin.OUT)
|
||
|
||
def move_stepper(direction, steps, delay_us):
|
||
print("Direction:", "CW" if direction else "CCW")
|
||
dir_pin.value(direction)
|
||
|
||
steps = abs(steps)
|
||
for i in range(steps):
|
||
step_pin.value(1)
|
||
time.sleep_us(delay_us)
|
||
step_pin.value(0)
|
||
time.sleep_us(delay_us)
|
||
|
||
print("Moved", steps, "steps")
|
||
|
||
def stop_stepper():
|
||
step_pin.value(0)
|
||
print("Stepper stopped")
|
||
|
||
print("Stepper motor program started")
|
||
|
||
while True:
|
||
move_stepper(1, 200, 2000) # Clockwise
|
||
time.sleep(2)
|
||
|
||
move_stepper(0, 200, 2000) # Anti-clockwise
|
||
time.sleep(2)
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "esp32", "micropython", "stepper"],
|
||
},
|
||
{
|
||
id: "100d-temperature-based-led-indicator-micropython-esp32",
|
||
title: "Temperature Based LED Indicator (MicroPython ESP32)",
|
||
description: "Temperature Based LED Indicator (MicroPython ESP32) on ESP32 (MicroPython) \u2014 uses Servo, Wi-Fi. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "robotics",
|
||
difficulty: "intermediate",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "main.py", content: `import network
|
||
import socket
|
||
from machine import Pin, PWM
|
||
from time import sleep_ms
|
||
|
||
# -------- WiFi --------
|
||
ssid = "kritish"
|
||
password = "pass"
|
||
|
||
wlan = network.WLAN(network.STA_IF)
|
||
wlan.active(True)
|
||
wlan.connect(ssid, password)
|
||
|
||
while not wlan.isconnected():
|
||
pass
|
||
|
||
print("Open:", wlan.ifconfig()[0])
|
||
|
||
# -------- Motor --------
|
||
ENA = PWM(Pin(13), freq=1000)
|
||
IN1 = Pin(12, Pin.OUT)
|
||
IN2 = Pin(14, Pin.OUT)
|
||
|
||
IN1.on()
|
||
IN2.off()
|
||
ENA.duty(0)
|
||
|
||
def set_motor(speed):
|
||
speed = int(speed)
|
||
|
||
if speed <= 0:
|
||
ENA.duty(0)
|
||
return
|
||
|
||
# kick start for 6V + L298N
|
||
ENA.duty(1023)
|
||
sleep_ms(150)
|
||
ENA.duty(speed)
|
||
|
||
# -------- HTML --------
|
||
html = """<!DOCTYPE html>
|
||
<html>
|
||
<body>
|
||
<h2>ESP32 DC Motor Control</h2>
|
||
<input type="range" min="0" max="1023" value="0"
|
||
oninput="send(this.value)">
|
||
<p>Speed: <span id="v">0</span></p>
|
||
|
||
<script>
|
||
function send(val){
|
||
document.getElementById("v").innerHTML = val;
|
||
fetch("/set?speed=" + val);
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|
||
"""
|
||
|
||
# -------- Server --------
|
||
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
|
||
s = socket.socket()
|
||
s.bind(addr)
|
||
s.listen(1)
|
||
|
||
print("Server running")
|
||
|
||
while True:
|
||
cl, addr = s.accept()
|
||
req = cl.recv(1024).decode()
|
||
|
||
if "/set?speed=" in req:
|
||
try:
|
||
part = req.split("/set?speed=")[1]
|
||
spd = part.split(" ")[0]
|
||
set_motor(spd)
|
||
print("Speed set:", spd)
|
||
except:
|
||
pass
|
||
|
||
cl.send("HTTP/1.1 200 OK\\r\\n\\r\\nOK")
|
||
cl.close()
|
||
continue
|
||
|
||
cl.send("HTTP/1.1 200 OK\\r\\nContent-Type: text/html\\r\\n\\r\\n")
|
||
cl.send(html)
|
||
cl.close()
|
||
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "esp32", "micropython", "servo", "wifi"],
|
||
},
|
||
{
|
||
id: "100d-ultrasonic-led-distance-indicator-esp32-micropython",
|
||
title: "Ultrasonic LED Distance Indicator ESP32 MicroPython",
|
||
description: "Ultrasonic LED Distance Indicator ESP32 MicroPython on ESP32 (MicroPython) \u2014 uses HC-SR04. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "sensors",
|
||
difficulty: "beginner",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "hcsr04.py", content: `import machine, time
|
||
from machine import Pin
|
||
|
||
__version__ = '0.2.0'
|
||
__author__ = 'Roberto Sánchez'
|
||
__license__ = "Apache License 2.0. https://www.apache.org/licenses/LICENSE-2.0"
|
||
|
||
class HCSR04:
|
||
"""
|
||
Driver to use the untrasonic sensor HC-SR04.
|
||
The sensor range is between 2cm and 4m.
|
||
The timeouts received listening to echo pin are converted to OSError('Out of range')
|
||
"""
|
||
# echo_timeout_us is based in chip range limit (400cm)
|
||
def __init__(self, trigger_pin, echo_pin, echo_timeout_us=500*2*30):
|
||
"""
|
||
trigger_pin: Output pin to send pulses
|
||
echo_pin: Readonly pin to measure the distance. The pin should be protected with 1k resistor
|
||
echo_timeout_us: Timeout in microseconds to listen to echo pin.
|
||
By default is based in sensor limit range (4m)
|
||
"""
|
||
self.echo_timeout_us = echo_timeout_us
|
||
# Init trigger pin (out)
|
||
self.trigger = Pin(trigger_pin, mode=Pin.OUT, pull=None)
|
||
self.trigger.value(0)
|
||
|
||
# Init echo pin (in)
|
||
self.echo = Pin(echo_pin, mode=Pin.IN, pull=None)
|
||
|
||
def _send_pulse_and_wait(self):
|
||
"""
|
||
Send the pulse to trigger and listen on echo pin.
|
||
We use the method \`machine.time_pulse_us()\` to get the microseconds until the echo is received.
|
||
"""
|
||
self.trigger.value(0) # Stabilize the sensor
|
||
time.sleep_us(5)
|
||
self.trigger.value(1)
|
||
# Send a 10us pulse.
|
||
time.sleep_us(10)
|
||
self.trigger.value(0)
|
||
try:
|
||
pulse_time = machine.time_pulse_us(self.echo, 1, self.echo_timeout_us)
|
||
return pulse_time
|
||
except OSError as ex:
|
||
if ex.args[0] == 110: # 110 = ETIMEDOUT
|
||
raise OSError('Out of range')
|
||
raise ex
|
||
|
||
def distance_mm(self):
|
||
"""
|
||
Get the distance in milimeters without floating point operations.
|
||
"""
|
||
pulse_time = self._send_pulse_and_wait()
|
||
|
||
# To calculate the distance we get the pulse_time and divide it by 2
|
||
# (the pulse walk the distance twice) and by 29.1 becasue
|
||
# the sound speed on air (343.2 m/s), that It's equivalent to
|
||
# 0.34320 mm/us that is 1mm each 2.91us
|
||
# pulse_time // 2 // 2.91 -> pulse_time // 5.82 -> pulse_time * 100 // 582
|
||
mm = pulse_time * 100 // 582
|
||
return mm
|
||
|
||
def distance_cm(self):
|
||
"""
|
||
Get the distance in centimeters with floating point operations.
|
||
It returns a float
|
||
"""
|
||
pulse_time = self._send_pulse_and_wait()
|
||
|
||
# To calculate the distance we get the pulse_time and divide it by 2
|
||
# (the pulse walk the distance twice) and by 29.1 becasue
|
||
# the sound speed on air (343.2 m/s), that It's equivalent to
|
||
# 0.034320 cm/us that is 1cm each 29.1us
|
||
cms = (pulse_time / 2) / 29.1
|
||
return cms` },
|
||
{ name: "main.py", content: `from hcsr04 import HCSR04
|
||
from machine import Pin
|
||
from time import sleep
|
||
|
||
# LED setup
|
||
green_led = Pin(12, Pin.OUT)
|
||
red_led = Pin(13, Pin.OUT)
|
||
|
||
THRESHOLD = 20 # cm
|
||
|
||
# ESP32
|
||
sensor = HCSR04(trigger_pin=5, echo_pin=18, echo_timeout_us=10000)
|
||
|
||
|
||
while True:
|
||
distance = sensor.distance_cm()
|
||
print('Distance:', distance, 'cm')
|
||
|
||
if distance > THRESHOLD:
|
||
green_led.on()
|
||
red_led.off()
|
||
else:
|
||
green_led.off()
|
||
red_led.on()
|
||
|
||
sleep(1)
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "esp32", "micropython", "ultrasonic"],
|
||
},
|
||
{
|
||
id: "100d-websocket-led-control-using-raspberry-pi-pico-w",
|
||
title: "WebSocket LED Control using Raspberry Pi Pico W",
|
||
description: "WebSocket LED Control using Raspberry Pi Pico W on Raspberry Pi Pico W (MicroPython) \u2014 uses WebSocket, Wi-Fi. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "communication",
|
||
difficulty: "advanced",
|
||
boardType: "pi-pico-w",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "main.py", content: `import socket
|
||
import network
|
||
import time
|
||
from machine import Pin
|
||
import ubinascii
|
||
import uhashlib
|
||
|
||
# LED Setup
|
||
led = Pin(15, Pin.OUT)
|
||
led.value(0)
|
||
|
||
# WiFi Configuration
|
||
SSID = "kritish"
|
||
PASSWORD = "pass"
|
||
|
||
def websocket_accept(key):
|
||
GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
||
sha1 = uhashlib.sha1((key + GUID).encode()).digest()
|
||
return ubinascii.b2a_base64(sha1).strip().decode()
|
||
|
||
def ws_decode(data):
|
||
if len(data) < 6: return ""
|
||
payload_len = data[1] & 127
|
||
mask = data[2:6]
|
||
payload = data[6:6 + payload_len]
|
||
decoded = bytearray()
|
||
for i in range(len(payload)):
|
||
decoded.append(payload[i] ^ mask[i % 4])
|
||
return decoded.decode()
|
||
|
||
# Connect to WiFi
|
||
wlan = network.WLAN(network.STA_IF)
|
||
wlan.active(True)
|
||
wlan.connect(SSID, PASSWORD)
|
||
|
||
print("Connecting to WiFi...")
|
||
while not wlan.isconnected():
|
||
time.sleep(1)
|
||
print("Connected! Pico IP:", wlan.ifconfig()[0])
|
||
|
||
# Start Server
|
||
server = socket.socket()
|
||
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||
server.bind(("0.0.0.0", 80))
|
||
server.listen(1)
|
||
|
||
while True:
|
||
print("\\n--- Waiting for fresh connection ---")
|
||
conn, addr = server.accept()
|
||
print("New client from:", addr)
|
||
|
||
try:
|
||
# 1. Handle Handshake
|
||
raw_request = conn.recv(1024).decode()
|
||
if "Sec-WebSocket-Key" in raw_request:
|
||
ws_key = raw_request.split("Sec-WebSocket-Key: ")[1].split("\\r\\n")[0].strip()
|
||
accept_key = websocket_accept(ws_key)
|
||
|
||
handshake = (
|
||
"HTTP/1.1 101 Switching Protocols\\r\\n"
|
||
"Upgrade: websocket\\r\\n"
|
||
"Connection: Upgrade\\r\\n"
|
||
f"Sec-WebSocket-Accept: {accept_key}\\r\\n\\r\\n"
|
||
)
|
||
conn.send(handshake)
|
||
print("Handshake Success!")
|
||
|
||
# 2. Communication Loop
|
||
while True:
|
||
data = conn.recv(1024)
|
||
if not data:
|
||
print("Client disconnected.")
|
||
break
|
||
|
||
msg = ws_decode(data)
|
||
print("Cmd:", msg)
|
||
|
||
if msg == "ON":
|
||
led.value(1)
|
||
reply = "LED IS ON"
|
||
elif msg == "OFF":
|
||
led.value(0)
|
||
reply = "LED IS OFF"
|
||
else:
|
||
reply = "OK"
|
||
|
||
# Send response frame
|
||
frame = bytearray([0x81, len(reply)]) + reply.encode()
|
||
conn.send(frame)
|
||
else:
|
||
print("Not a WebSocket request.")
|
||
|
||
except Exception as e:
|
||
print("Error during session:", e)
|
||
finally:
|
||
conn.close()
|
||
print("Connection closed. Ready for next.")
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "micropython", "pi-pico-w", "pico-w", "websocket", "wifi"],
|
||
},
|
||
{
|
||
id: "100d-wi-fi-controlled-4wd-robot-car",
|
||
title: "Wi Fi Controlled 4WD Robot Car",
|
||
description: "Wi Fi Controlled 4WD Robot Car on ESP32 (MicroPython) \u2014 uses Wi-Fi. From Kritish Mohapatra's 100 Days of IoT series: https://github.com/kritishmohapatra/100_Days_100_IoT_Projects",
|
||
category: "robotics",
|
||
difficulty: "intermediate",
|
||
boardType: "esp32",
|
||
languageMode: 'micropython',
|
||
files: [
|
||
{ name: "main.py", content: `import network
|
||
import socket
|
||
from machine import Pin
|
||
import time
|
||
|
||
# Wi-Fi credentials
|
||
SSID = "kritish"
|
||
PASSWORD = "pass"
|
||
# Driver 1 (Front)
|
||
IN1 = Pin(27, Pin.OUT)
|
||
IN2 = Pin(26, Pin.OUT)
|
||
IN3 = Pin(25, Pin.OUT)
|
||
IN4 = Pin(33, Pin.OUT)
|
||
|
||
# Driver 2 (Rear)
|
||
IN5 = Pin(19, Pin.OUT)
|
||
IN6 = Pin(18, Pin.OUT)
|
||
IN7 = Pin(5, Pin.OUT)
|
||
IN8 = Pin(17, Pin.OUT)
|
||
|
||
def forward():
|
||
IN1.value(1); IN2.value(0)
|
||
IN3.value(0); IN4.value(1)
|
||
IN5.value(1); IN6.value(0)
|
||
IN7.value(0); IN8.value(1)
|
||
|
||
def backward():
|
||
IN1.value(0); IN2.value(1)
|
||
IN3.value(1); IN4.value(0)
|
||
IN5.value(0); IN6.value(1)
|
||
IN7.value(1); IN8.value(0)
|
||
|
||
def left():
|
||
IN1.value(1); IN2.value(0)
|
||
IN3.value(1); IN4.value(0)
|
||
IN5.value(1); IN6.value(0)
|
||
IN7.value(1); IN8.value(0)
|
||
|
||
def right():
|
||
IN1.value(0); IN2.value(1)
|
||
IN3.value(0); IN4.value(1)
|
||
IN5.value(0); IN6.value(1)
|
||
IN7.value(0); IN8.value(1)
|
||
|
||
def stop():
|
||
IN1.value(0); IN2.value(0)
|
||
IN3.value(0); IN4.value(0)
|
||
IN5.value(0); IN6.value(0)
|
||
IN7.value(0); IN8.value(0)
|
||
|
||
# Wi-Fi Connect
|
||
wifi = network.WLAN(network.STA_IF)
|
||
wifi.active(True)
|
||
wifi.connect(SSID, PASSWORD)
|
||
|
||
print("Connecting to Wi-Fi...")
|
||
while not wifi.isconnected():
|
||
time.sleep(0.5)
|
||
print(".")
|
||
|
||
print("Connected! IP:", wifi.ifconfig()[0])
|
||
|
||
# Web Page
|
||
def webpage():
|
||
html = """<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<title>ESP32 Car</title>
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<style>
|
||
body { text-align: center; font-family: Arial; background: #1a1a2e; color: white; }
|
||
h1 { color: #e94560; }
|
||
.btn {
|
||
display: inline-block;
|
||
padding: 20px 30px;
|
||
margin: 10px;
|
||
font-size: 24px;
|
||
background: #16213e;
|
||
color: white;
|
||
border: 2px solid #e94560;
|
||
border-radius: 10px;
|
||
cursor: pointer;
|
||
text-decoration: none;
|
||
}
|
||
.btn:hover { background: #e94560; }
|
||
.row { margin: 10px; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<h1>ESP32 Car Control</h1>
|
||
<div class="row">
|
||
<a class="btn" href="/forward">Forward</a>
|
||
</div>
|
||
<div class="row">
|
||
<a class="btn" href="/left">Left</a>
|
||
<a class="btn" href="/stop">Stop</a>
|
||
<a class="btn" href="/right">Right</a>
|
||
</div>
|
||
<div class="row">
|
||
<a class="btn" href="/backward">Backward</a>
|
||
</div>
|
||
</body>
|
||
</html>"""
|
||
return html
|
||
|
||
# Web Server
|
||
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||
server.bind(('', 80))
|
||
server.listen(5)
|
||
print("Server started!")
|
||
|
||
while True:
|
||
conn, addr = server.accept()
|
||
request = conn.recv(1024).decode()
|
||
|
||
if '/forward' in request:
|
||
forward()
|
||
elif '/backward' in request:
|
||
backward()
|
||
elif '/left' in request:
|
||
left()
|
||
elif '/right' in request:
|
||
right()
|
||
elif '/stop' in request:
|
||
stop()
|
||
|
||
conn.send('HTTP/1.1 200 OK\\r\\nContent-Type: text/html\\r\\n\\r\\n')
|
||
conn.send(webpage())
|
||
conn.close()
|
||
` },
|
||
],
|
||
code: '',
|
||
components: [],
|
||
wires: [],
|
||
tags: ["100-days", "esp32", "micropython", "wifi"],
|
||
},
|
||
];
|