יצירת אתר אינטרנט המציג נתוני חיישנים עם ESP32

איך מחברים חיישנים עם מיקרובקר וגורמים להם להציג את התוצאות באתר אינטרנט אמיתי? ממש בקלות!

אחרי שלמדנו לחבר חיישני קלט ל-ESP32, בוא ונלמד משהו ממש מעניין שאפשר לעשות איתם – כמו להציג את התוצאות על גבי שרת ווב שאפשר לגשת אליו מכל מקום בבית 🙂 בפוסט הזה נלמד קצת על חיישני גז מסוג MQ, חיבור ל-wifi ויצירת שרת ווב עם מיקרופייתון. המאמרים הקודמים בסדרה הם חובה להבנה.

ראשית – חיישני MQ הם חיישנים שנועדו לזהות גזים שונים כמו מתאן, CO, גז בישול ועוד כל מיני דברים לא סימפטיים. אני מחבב את חיישן MQ-135 שמיועד לבדיקת אוויר כללית. אפשר לקנות אותם בדולר וקצת בעלי אקספרס. בגדול הם נראים ככדור עם רשת ומה שמעניין אותנו הוא הפינים שאותם נחבר ל-ESP32 שלנו. יש MQ-135 שבאים עם 3 פינים ויש כאלו שבאים עם 4 פינים.

את VCC נחבר לפין של ה-3.3V. . אל תשכחו להעזר במפת ה-pinout שלכם על מנת להבין איפה זה. את זה שלידו נחבר ל-Ground (כתוב לידו מינוס או gnd) ואנו נחפש את ה-AC – פין הקלט האנלוגי ונחבר אותו לפין ADC. גם כאן צריך להעזר במפת ה-pinout. זוכרים שדיברנו על זה בפוסט על חיבור led?

בגדול זה נראה כך אצלי. אני חיברתי באמצעות לוח עזר. אתם יכולים לחבר ישירות:

ב-ESP32 שלי, שיש לו 38 פינים, זה GPIO36. מה שחשוב הוא המספר 36. על מנת לבדוק שהכל תקין, אנו נוודא שיש אור אדום בחיישן ונריץ את הקוד הזה עם ה-Thonny הנאמן שלנו.

import machine
import time

adc = machine.ADC(machine.Pin(36))

# Configure the ADC. The ESP32 ADC has different configurations.
# The default is 12-bit resolution (0 to 4095).
adc.atten(machine.ADC.ATTN_11DB)  # Set the attenuation to max range of input voltage.

while True:
    value = adc.read()
    print("MQ-135 Reading:", value)

    # Wait for a bit before taking another reading
    time.sleep(1)

אם הכל יהיה תקין, תוכלו לראות את הקריאות. מה שטוב ב-MQ-135 שממש קל לבדוק אותו. שימו את הפה שלכם לידו ופשוט תנשפו עליו אוויר חם ומהביל הישר מהריאות שלכם. תראו שהמספר עולה – כי ריכוז הפחמן הדו חמצני עולה.

אם זה לא עובד – אז כרגיל – לוודא שבאמת חיברתם למקום המתאים והחיבורים תקינים.

הכל עובד? בואו ונחבר לווב. בפוסט הזה אני מסביר צעד אחרי צעד, אם אין לכם סבלנות – תגללו אל המקום שבו כתוב ״קוד הפרויקט המלא״.

צעד ראשון: חיבור לווי פיי

טוב, את הכיף הזה כבר למדנו. אבל איך אנחנו יכולים לקחת את זה הלאה? בואו ניצור שרת ווב שיחצין את התוצאה הזו החוצה.

ראשית, בשביל שרת ווב אנחנו צריכים להתחבר לוויפי כלשהו – עדיף זה של הבית. איזה יופי שלESP32 יש חיבור ווי פיי מובנה שכל מה שאנו צריכים בשבילו זה קוד. איזה קוד? הקוד הזה. אנו נמקם אותו ב-main.py:

import network


SSID = 'MY_NETWORK_NAME'
PASSWORD = 'PASSWORD'

def connect_to_wifi(ssid, password):
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    
    if not wlan.isconnected():
        print('Connecting to WiFi...')
        wlan.connect(ssid, password)
        while not wlan.isconnected():
            pass
    print('Network config:', wlan.ifconfig())

connect_to_wifi(SSID, PASSWORD)

אנו נריץ אותו, כאשר כמובן לפני ההרצה אנו נחליף את ה-SSID ואת ה-PASSWORD במה שמתאים לרשת שלנו. אם הכל תקין – נראה את הדבר הזה:

Network config: ('192.168.2.116', '255.255.255.0', '192.168.2.1', '192.168.2.111')

מה שמעניין אותנו זו הכתובת הראשונה, זה מספר ה-IP שה-ESP32 שלנו מקבל. אצלכם הוא יהיה שונה כמעט בוודאות. אבל הכתובת הזו היא חשובה כי היא תשמש את שרתנו הקט.

תקלות:

אם זה לא מתחבר – הדבר הראשון הוא לוודא את שם המשתמש והסיסמה. הדבר השני הוא לנסות לעשות ריסטרט – כלומר לנתק ולחבר את ה-ESP32 מהמחשב. דבר שלישי הוא לנסות רשת אחרת. יש לפעמים הגדרות אבטחה של ראוטרים שלא נותנים לכל מכשיר להתחבר.

צעד שני: יצירת שרת ווב בסיסי

עבר בשלום? בואו וניצור שרת ווב פשוט. רק בשביל לראות שהכל עובד.

import network
import socket
import time

SSID = 'MY_NETWORK_NAME'
PASSWORD = 'PASSWORD'

def connect_to_wifi(ssid, password):
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    
    if not wlan.isconnected():
        print('Connecting to WiFi...')
        wlan.connect(ssid, password)
        while not wlan.isconnected():
            pass
    print('Network config:', wlan.ifconfig())

def start_web_server():
    addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]

    s = socket.socket()
    s.bind(addr)
    s.listen(1)

    print('Listening on', addr)

    while True:
        cl, addr = s.accept()
        print('Client connected from', addr)
        cl_file = cl.makefile('rwb', 0)
        while True:
            line = cl_file.readline()
            if not line or line == b'\r\n':
                break
        response = 'Hello from ESP32!'
        cl.send('HTTP/1.0 200 OK\r\nContent-type: text/plain\r\n\r\n')
        cl.send(response)
        cl.close()

connect_to_wifi(SSID, PASSWORD)
start_web_server()

אפשר לקרוא את הקוד, הוא לא מסובך במיוחד. אפשר לראות שמה שיש לנו פה זה שרת קטן ומטופש שמחזיר ״שלום״ אם נכנסים אליו. הוא גם מדפיס ללוג מי נכנס אליו. הוא לא שרת מרשים במיוחד, אבל הוא בסיסי ולא אמור לעשות בעיות. בואו נבדוק אותו. נריץ את הקוד הזה עם ה-Thonny (לא נשכח להכניס את שם המשתמש והסיסמה) ונכנס עם הדפדפן אל הכתובת. אנו אמורים לראות Hello from ESP32! אם נציץ בטרמינל נראה את כתובת ה-IP של המחשב שלנו.

מה שיפה הוא שאם אני אנתק את ה-ESP32 מהמחשב שלי ואחבר אותו לנקודת חשמל אחרת בתחום ה-WiFi, השרת יעבוד והכל יהיה מעולה. כל מי שברשת הפנימית יוכל להכנס לדף ולראות את השרת שלנו ואת דרישת השלום שלנו.

צעד שלישי – חיבור תוצאת החיישן לשרת הווב

אבל זה לא מלהיב במיוחד. בואו ונגרום לכך שמי שיכנס לשרת יוכל לראות את הקריאה ממד הגזים שלנו. את זה אנו עושים באמצעות הוספת פונקציה בשם read_mq135 שמכילה את הקוד שקורא לחיישן ומחזירה את הערך. במקום רק להחזיר Hello from ESP32, אני אחזיר גם את הערך.

import network
import socket
import machine

SSID = 'MY_NETWORK_NAME'
PASSWORD = 'PASSWORD'

def connect_to_wifi(ssid, password):
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    
    if not wlan.isconnected():
        print('Connecting to WiFi...')
        wlan.connect(ssid, password)
        while not wlan.isconnected():
            pass
    print('Network config:', wlan.ifconfig())

def read_mq135():
    adc = machine.ADC(machine.Pin(36))
    adc.atten(machine.ADC.ATTN_11DB) 
    return adc.read()

def start_web_server():
    addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]

    s = socket.socket()
    s.bind(addr)
    s.listen(1)

    print('Listening on', addr)

    while True:
        cl, addr = s.accept()
        print('Client connected from', addr)
        cl_file = cl.makefile('rwb', 0)
        while True:
            line = cl_file.readline()
            if not line or line == b'\r\n':
                break
        
        mq135_value = read_mq135()
        response = 'Hello from ESP32! MQ-135 Reading: ' + str(mq135_value)
        cl.send('HTTP/1.0 200 OK\r\nContent-type: text/plain\r\n\r\n')
        cl.send(response)
        cl.close()

connect_to_wifi(SSID, PASSWORD)
start_web_server()

וזה כבר מתחיל להיות ממש מעניין! אני יכול כמובן לא רק להחזיר את הערך בטקסט מטופש אלא באובייקט JSON.

import network
import socket
import machine
import ujson

SSID = 'MY_NETWORK_NAME'
PASSWORD = 'PASSWORD'

def connect_to_wifi(ssid, password):
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    
    if not wlan.isconnected():
        print('Connecting to WiFi...')
        wlan.connect(ssid, password)
        while not wlan.isconnected():
            pass
    print('Network config:', wlan.ifconfig())

def read_mq135():
    adc = machine.ADC(machine.Pin(36))
    adc.atten(machine.ADC.ATTN_11DB)  # Set 11dB attenuation for readings up to approx. 3.3V
    return adc.read()

def start_web_server():
    addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]

    s = socket.socket()
    s.bind(addr)
    s.listen(1)

    print('Listening on', addr)

    while True:
        cl, addr = s.accept()
        print('Client connected from', addr)
        cl_file = cl.makefile('rwb', 0)
        while True:
            line = cl_file.readline()
            if not line or line == b'\r\n':
                break
        
        mq135_value = read_mq135()  # Reading the MQ-135 value for every request
        
        # Create JSON object
        data = {
            "id": "mq-135",
            "label": "Gas sensor",
            "value": mq135_value
        }

        response = ujson.dumps(data)
        
        cl.send('HTTP/1.0 200 OK\r\nContent-type: application/json\r\n\r\n')
        cl.send(response)
        cl.close()

connect_to_wifi(SSID, PASSWORD)
start_web_server()

מה שזה עושה הוא להחזיר את הקריאות לאובייקט JSON. כל מי שיפנה אל השרת שלנו יקבל אובייקט כזה שמביא את הקריאה מייד. אני יכול לבנות אוטומציה או תוכנה שרצה על שרת אחר בכלל שתקרא לשרת שלנו בכל רגע נתון כדי לקבל את איכות האוויר! זה מתחיל להיות ממש נחמד, לא?

אני יכול לקחת את זה צעד אחד קדימה. למה שלא ניצור גם מצב תצוגה? יהיה לי על ה ESP32 כתובת שמחזירה את ה-JSON והעמוד הראשי יהיה מד שמציג את איכות האוויר?

פה אני צריך קצת קסם של ג׳אווהסקריפט. בגדול אני אצור שני עמודים. עמוד אחד בשם sensor המחזיר לי את ה-JSON והעמוד הראשי שבו יש קוד ג׳אווהסקריפט שמבצע קריאת ajax כל חמש שניות כדי להציג את איכות האוויר. הוא מציב את התוצאות בתוך אלמנט HTML5 שנקרא meter.

הנה הקוד המלא של הפרויקט

נמצא גם ב-gist הזה להעתקה קלה

import network
import socket
import machine
import ujson
import utime

SSID = 'MY_NETWORK_NAME'
PASSWORD = 'PASSWORD'

def connect_to_wifi(ssid, password):
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    
    if not wlan.isconnected():
        print('Connecting to WiFi...')
        wlan.connect(ssid, password)
        while not wlan.isconnected():
            pass
    print('Network config:', wlan.ifconfig())

def read_mq135():
    adc = machine.ADC(machine.Pin(36))
    adc.atten(machine.ADC.ATTN_11DB) 
    return adc.read()

def handle_request(cl_file):
    request = cl_file.readline().decode('utf-8')
    while True:
        line = cl_file.readline()
        if not line or line == b'\r\n':
            break

    if "GET /sensors" in request:
        mq135_value = read_mq135()
        data = {
            "id": "mq-135",
            "label": "Gas sensor",
            "value": mq135_value
        }
        response = ujson.dumps(data)
        headers = 'HTTP/1.0 200 OK\r\nContent-type: application/json\r\nAccess-Control-Allow-Origin: *\r\n\r\n'
        return headers + response

    elif "GET /" in request:
        html = """
        <html>
            <head>
                <title>MQ-135 Sensor Data</title>
                <script>
                    function fetchData() {
                        fetch('/sensors')
                        .then(response => response.json())
                        .then(data => {
                            document.getElementById("sensorData").innerText = JSON.stringify(data, null, 4);
                            document.getElementById("sensorMeter").value = data.value;
                        });
                    }
                    setInterval(fetchData, 5000); // Fetch data every 5 seconds
                </script>
            </head>
            <body onload="fetchData()">
                <h1>MQ-135 Sensor Data:</h1>
                <meter id="sensorMeter" min="0" max="4000" low="50" high="500" optimum="200"></meter>
                <pre id="sensorData"></pre>
            </body>
        </html>
        """
        headers = 'HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n'
        return headers + html

    else:
        return 'HTTP/1.0 404 Not Found\r\nContent-type: text/plain\r\n\r\n404: Not Found'

def start_web_server():
    addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
    s = socket.socket()
    s.bind(addr)
    s.listen(1)

    print('Listening on', addr)

    while True:
        cl, addr = s.accept()
        print('Client connected from', addr)
        cl_file = cl.makefile('rwb', 0)
        response = handle_request(cl_file)
        cl.send(response)
        utime.sleep(0.5)  # Add a small delay
        cl.close()

connect_to_wifi(SSID, PASSWORD)
start_web_server()

אם אני אגש אל הכתובת, אני אקבל דף שמציג כל 5 שניות את הנתון המעודכן מהחיישן. עכשיו אני יכול לנתק את ה-ESP32 מהמחשב שלי ולחבר אותו לחשמל במקום אחר – כלומר להציב אותו בכל מקום בבית שבו אני חושש שיש בעיית אוויר ולקבל חיווי מיידי. אני כמובן יכול להמשיך ולפתח את הקוד כדי שישלח בעצמו פינג לשרת אחר אם הערך לא תקין. השרת יכול להפעיל מערכת טיהור, מיזוג או להפעיל סירנה. באמת השמים הם הגבול.

כמובן שלא חייבים להסתפק רק בחיישן של MQ. אפשר לחבר כל חיישן. למשל רטיבות, או טמפרטורה, יש כל כך הרבה חיישנים שעולים במקרה הגבוה כמה דולרים.

מה שכן, הראיתי כאן את כל הקוד בבת אחת. אפשר לחלק את הקוד לקבצים שונים שיהיו יותר נאים. דרך טובה לתרגל היא להדביק את הקוד לתוך ChatGPT למשל ולבקש ממנו לחלק את הקוד עבורכם. נסו ותהנו.

פוסטים נוספים שכדאי לקרוא

DALL·E 2024-09-06 12.34.24 - A visually engaging post about Python and cryptographic problems. The image should feature a dark-themed background with a glowing, futuristic Python
פתרונות ומאמרים על פיתוח אינטרנט

בעיות במימוש של פונקציות קריפטוגרפיות בפייתון

היום (16 לספטמבר) ממש, אני מעביר הרצאה ב-PyconIL 2024 על בעיות קריפטוגרפיות באפליקציות פייתון. לצערי אי אפשר להכניס את כל הבעיות הקריפטוגרפיות להרצאה אחת או

מיקרו בקרים

חיבור מצלמה למיקרובקר

חיבור מצלמה למיקרו בקר ויצירה של מצלמת אבטחה מרחוק בעלות של 20 שקל.

גלילה לראש העמוד