وبلاگ
ساخت یک وب سرور کوچک با MicroPython: از ایده تا اجرا در ESP32
فهرست مطالب
“تسلط به برنامهنویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT”
"تسلط به برنامهنویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT"
"با شرکت در این دوره جامع و کاربردی، به راحتی مهارتهای برنامهنویسی پایتون را از سطح مبتدی تا پیشرفته با کمک هوش مصنوعی ChatGPT بیاموزید. این دوره، با بیش از 6 ساعت محتوای آموزشی، شما را قادر میسازد تا به سرعت الگوریتمهای پیچیده را درک کرده و اپلیکیشنهای هوشمند ایجاد کنید. مناسب برای تمامی سطوح با زیرنویس فارسی حرفهای و امکان دانلود و تماشای آنلاین."
ویژگیهای کلیدی:
بدون نیاز به تجربه قبلی برنامهنویسی
زیرنویس فارسی با ترجمه حرفهای
۳۰ ٪ تخفیف ویژه برای دانشجویان و دانش آموزان
0 تا 100 عطرسازی + (30 فرمولاسیون اختصاصی حامی صنعت)
دوره آموزش Flutter و برنامه نویسی Dart [پروژه محور]
دوره جامع آموزش برنامهنویسی پایتون + هک اخلاقی [با همکاری شاهک]
دوره جامع آموزش فرمولاسیون لوازم آرایشی
دوره جامع علم داده، یادگیری ماشین، یادگیری عمیق و NLP
دوره فوق فشرده مکالمه زبان انگلیسی (ویژه بزرگسالان)
شمع سازی و عودسازی با محوریت رایحه درمانی
صابون سازی (دستساز و صنعتی)
صفر تا صد طراحی دارو
متخصص طب سنتی و گیاهان دارویی
متخصص کنترل کیفی شرکت دارویی
ساخت یک وب سرور کوچک با MicroPython: از ایده تا اجرا در ESP32
در دنیای امروز که اینترنت اشیا (IoT) به سرعت در حال گسترش است، نیاز به راهحلهای کوچک، کارآمد و قابل برنامهریزی برای اتصال دستگاههای فیزیکی به شبکه بیش از پیش احساس میشود. از سیستمهای اتوماسیون خانگی گرفته تا پایش صنعتی، وب سرورهای کوچک نقش کلیدی در ایجاد رابط کاربری و API برای تعامل با این دستگاهها ایفا میکنند. در این میان، ترکیب قدرتمند MicroPython و میکروکنترلر ESP32 به عنوان یک پلتفرم ایدهآل برای توسعه چنین وب سرورهایی ظهور کرده است. MicroPython با سادگی و سرعت توسعه پایتون، در کنار قابلیتهای شبکه بینظیر و منابع سختافزاری غنی ESP32، به شما امکان میدهد تا در زمان کوتاهی، ایدههای خود را از مفهوم اولیه به یک محصول عملی و کارا تبدیل کنید.
این مقاله جامع با هدف راهنمایی شما در فرآیند گام به گام ساخت یک وب سرور کوچک با MicroPython بر روی ESP32 تدوین شده است. از معرفی ابزارهای ضروری و مفاهیم پایه شبکه گرفته تا پیادهسازی عملی کد و بهینهسازی، تمامی جنبههای مورد نیاز برای توسعهدهندگان و علاقهمندان به اینترنت اشیا پوشش داده خواهد شد. ما به تفصیل به معماری وب سرور، نحوه مدیریت درخواستهای HTTP، ساخت APIهای RESTful و در نهایت، ارائه یک مثال کاربردی برای پایش و کنترل دستگاههای IoT خواهیم پرداخت. هدف ما توانمندسازی شما برای طراحی، پیادهسازی و استقرار وب سرورهای سفارشی خود بر روی ESP32 است، تا بتوانید کنترل کاملی بر روی پروژههای IoT خود داشته باشید و خلاقیت خود را در این حوزه به نمایش بگذارید.
با پیشرفت فناوری و افزایش نیاز به اتصالپذیری، توانایی توسعه وب سرورهای تعبیهشده (embedded web servers) بر روی میکروکنترلرها یک مهارت ارزشمند محسوب میشود. این مهارت به شما امکان میدهد تا بدون نیاز به سرورهای خارجی یا واسطههای پیچیده، دستگاههای خود را مستقیماً از طریق مرورگر وب یا برنامههای کاربردی موبایل کنترل و پایش کنید. MicroPython با ارائه یک رابط برنامهنویسی سطح بالا و نزدیک به سختافزار، فرآیند توسعه را به طرز چشمگیری ساده کرده و به شما اجازه میدهد تا به جای درگیر شدن با جزئیات سطح پایین، بر روی منطق اصلی برنامه خود تمرکز کنید. ESP32 نیز با قابلیتهای Wi-Fi و Bluetooth داخلی، چندین هسته پردازشی و انبوهی از پینهای ورودی/خروجی (GPIO)، بستر سختافزاری قدرتمندی را برای این منظور فراهم میآورد. این ترکیب، دروازهای به سوی پروژههای IoT نوآورانه و کارآمد را برای شما میگشاید.
در ادامه، با ما همراه باشید تا به عمق جزئیات فنی و عملی ساخت یک وب سرور MicroPython در ESP32 بپردازیم و از ایده تا اجرای نهایی، گام به گام با یکدیگر پیش برویم.
مقدمه: چرا وب سرور کوچک با MicroPython و ESP32؟
در عصر دیجیتال کنونی، قابلیت اتصال و تعامل با دستگاهها از راه دور به یک ضرورت تبدیل شده است. از سیستمهای خانگی هوشمند گرفته تا حسگرهای صنعتی و دستگاههای پوشیدنی، همه نیازمند رابطهایی برای ارتباط، کنترل و تبادل داده هستند. ساخت یک وب سرور کوچک و تعبیهشده (embedded) بر روی یک میکروکنترلر، یکی از کارآمدترین راهها برای دستیابی به این هدف است. اما چرا MicroPython و ESP32 به عنوان بهترین انتخاب برای این منظور شناخته میشوند؟
همافزایی MicroPython و ESP32: قدرت در سادگی
MicroPython یک پیادهسازی کامل از زبان برنامهنویسی پایتون ۳ است که بهینهسازی شده تا بر روی میکروکنترلرهای با منابع محدود اجرا شود. این بهینهسازی به این معناست که شما میتوانید از تمام قدرت و سادگی پایتون برای برنامهنویسی سختافزار استفاده کنید، بدون اینکه نگران پیچیدگیهای زبانهای سطح پایین مانند C یا C++ باشید. مزایای کلیدی MicroPython شامل:
- سرعت توسعه بالا: سینتکس ساده و خوانا، همراه با REPL (Read-Eval-Print Loop) تعاملی، امکان تست و اشکالزدایی سریع را فراهم میکند.
- جامعه کاربری فعال: دسترسی به کتابخانهها و نمونه کدهای فراوان.
- کاهش منحنی یادگیری: برای توسعهدهندگانی که با پایتون آشنا هستند، ورود به دنیای میکروکنترلرها را بسیار آسان میکند.
- مدیریت خودکار حافظه: با استفاده از Garbage Collector، نیاز به مدیریت دستی حافظه را از بین میبرد.
از سوی دیگر، ESP32 یک سیستم روی تراشه (SoC) قدرتمند و ارزانقیمت از شرکت Espressif Systems است که به دلیل قابلیتهای ارتباطی بیسیم خود شهرت دارد. ویژگیهای برجسته ESP32 که آن را به گزینهای عالی برای وب سرورهای کوچک تبدیل میکند، عبارتند از:
- Wi-Fi و Bluetooth داخلی: این ویژگی، امکان اتصال آسان به شبکه و اینترنت را فراهم میکند و نیازی به ماژولهای جانبی ندارد.
- پردازندههای دو هستهای: اکثر مدلهای ESP32 دارای دو هسته پردازشی هستند که امکان اجرای همزمان وظایف (مانند مدیریت شبکه و سنسورها) را فراهم میآورد و عملکرد کلی را بهبود میبخشد.
- پینهای GPIO فراوان: این پینها امکان اتصال انواع سنسورها، محرکها (actuators) و سایر قطعات جانبی را فراهم میکنند.
- امکانات سختافزاری متنوع: شامل ADC (مبدل آنالوگ به دیجیتال)، DAC (مبدل دیجیتال به آنالوگ)، PWM، I2C، SPI، UART و…
- مصرف انرژی پایین: مناسب برای کاربردهای باتریمحور.
ترکیب این دو، به توسعهدهندگان این امکان را میدهد که با صرف کمترین زمان و هزینه، وب سرورهایی قدرتمند و منعطف برای طیف وسیعی از کاربردهای IoT ایجاد کنند. تصور کنید در حال ساخت یک سیستم آبیاری هوشمند هستید. با ESP32 و MicroPython میتوانید یک وب سرور کوچک راهاندازی کنید که وضعیت رطوبت خاک را نمایش دهد، زمان آبیاری را تنظیم کند، و به شما اجازه دهد تا از طریق یک مرورگر وب یا حتی یک برنامه ساده در گوشی، پمپ آب را به صورت دستی روشن یا خاموش کنید. این رویکرد، استقلال بینظیری در طراحی و پیادهسازی به شما میدهد.
مزایای وب سرور تعبیهشده با MicroPython و ESP32
- هزینه و مصرف انرژی پایین: ESP32 خود یک میکروکنترلر ارزانقیمت است و نیاز به سختافزار اضافی برای قابلیتهای شبکه را از بین میبرد.
- انعطافپذیری و کنترل کامل: شما کنترل کامل بر روی منطق سرور، رابط کاربری و تعامل با سختافزار دارید.
- سادگی توسعه و نگهداری: با پایتون، کدنویسی سریعتر انجام میشود و نگهداری آن نیز آسانتر است.
- کاهش تأخیر (Latency): ارتباط مستقیم با دستگاه بدون نیاز به سرورهای ابری واسط، تأخیر را به حداقل میرساند.
- کاربردهای آفلاین: وب سرور میتواند بدون اتصال به اینترنت جهانی، در یک شبکه محلی (LAN) عمل کند، که برای محیطهایی با دسترسی محدود به اینترنت بسیار مفید است.
- امنیت محلی: در برخی موارد، نگهداری دادهها و کنترل در داخل شبکه محلی میتواند امنیت بیشتری را فراهم کند.
با درک این مزایا و همافزایی منحصر به فرد MicroPython و ESP32، به بخش بعدی میرویم که به آشنایی با ابزارهای حیاتی شما و نحوه راهاندازی محیط توسعه میپردازد.
آشنایی با MicroPython و ESP32: ابزارهای حیاتی شما
قبل از اینکه به کدنویسی عمیق بپردازیم، لازم است با ابزارهای اصلی خود، یعنی MicroPython و ESP32، به خوبی آشنا شویم و محیط توسعه را آماده کنیم. این بخش به شما کمک میکند تا بستر لازم برای شروع پروژه را فراهم آورید.
MicroPython: پایتون برای میکروکنترلرها
همانطور که قبلاً ذکر شد، MicroPython یک پیادهسازی مختصر و کارآمد از زبان پایتون 3 است که برای اجرا بر روی میکروکنترلرها طراحی شده است. این به این معنی است که شما میتوانید کدهای پایتون را مستقیماً روی ESP32 خود بنویسید و اجرا کنید، و از مزایای سرعت توسعه، خوانایی کد و جامعه فعال پایتون بهرهمند شوید. MicroPython شامل زیرمجموعهای از کتابخانههای استاندارد پایتون و همچنین ماژولهای خاص برای دسترسی به سختافزار میکروکنترلرها (مانند machine برای GPIOs و network برای Wi-Fi) است.
فلسفه MicroPython این است که با وجود محدودیتهای منابع سختافزاری، تا حد امکان وفادار به پایتون استاندارد باقی بماند. این بدان معناست که بسیاری از مفاهیمی که در پایتون دسکتاپ به کار میبرید، در MicroPython نیز قابل استفاده هستند، اگرچه ممکن است برخی از ماژولها یا قابلیتهای سنگینتر در دسترس نباشند یا به شکل بهینهسازی شدهای ارائه شده باشند. توانایی کار با MicroPython به شما یک مزیت بزرگ در پروژههای IoT میدهد، زیرا میتوانید به سرعت نمونهسازی کنید و ایدههای خود را به مرحله اجرا درآورید.
ESP32: مغز متفکر پروژه شما
ESP32 یک سیستم روی تراشه (SoC) است که توسط Espressif Systems توسعه یافته و به دلیل قابلیتهای Wi-Fi و Bluetooth داخلی، قیمت مناسب و عملکرد بالا، بسیار محبوب شده است. در هسته خود، ESP32 معمولاً دارای یک یا دو هسته پردازشی Tensilica Xtensa LX6 است که قادر به اجرای دستورالعملها با فرکانس تا 240 مگاهرتز هستند. این پردازندهها، همراه با حافظه RAM داخلی و فلش، قدرت پردازشی کافی برای اجرای سیستمعاملهای کوچک، مدیریت شبکه و پردازش دادهها را فراهم میکنند.
ویژگیهای کلیدی ESP32 که آن را برای یک وب سرور کوچک ایدهآل میسازد:
- Wi-Fi: پشتیبانی از استانداردهای 802.11 b/g/n برای اتصال به شبکههای بیسیم. این قابلیت برای اتصال ESP32 به روتر خانگی یا راهاندازی آن به عنوان یک Access Point (نقطه دسترسی) ضروری است.
- Bluetooth: هم BLE (Bluetooth Low Energy) و هم Bluetooth Classic را پشتیبانی میکند، که برای ارتباط با دستگاههای موبایل یا سایر سنسورها میتواند مفید باشد.
- GPIOs: تعداد زیادی پین ورودی/خروجی عمومی برای اتصال سنسورها، نمایشگرها، رلهها و سایر قطعات الکترونیکی.
- ADC/DAC: مبدلهای آنالوگ به دیجیتال و دیجیتال به آنالوگ برای خواندن سیگنالهای آنالوگ و تولید ولتاژ آنالوگ.
- حسگرهای داخلی: برخی از مدلهای ESP32 شامل حسگرهای اثر هال و حسگرهای دما هستند.
- سختافزار رمزنگاری: پشتیبانی از AES، SHA، RSA و Elliptic Curve Cryptography که برای امنیت ارتباطات اهمیت دارد.
این ویژگیها ESP32 را به یک پلتفرم همه کاره برای طیف وسیعی از کاربردها، از اتوماسیون خانگی گرفته تا پروژههای صنعتی، تبدیل کرده است.
راهاندازی محیط توسعه: فلش کردن MicroPython و اتصال
برای شروع کار با ESP32 و MicroPython، ابتدا باید MicroPython firmware را روی ESP32 خود فلش کنید. این فرآیند نسبتاً ساده است و شامل چند مرحله کلیدی میشود:
1. نصب ابزارهای مورد نیاز
- Python: اطمینان حاصل کنید که پایتون 3 بر روی سیستم عامل شما نصب است.
- pip: مدیر پکیج پایتون که معمولاً همراه پایتون نصب میشود.
- esptool.py: ابزار خط فرمان رسمی Espressif برای فلش کردن firmware بر روی تراشههای ESP. میتوانید آن را با pip نصب کنید:
pip install esptool - درایورهای UART/Serial: برای برقراری ارتباط با ESP32 از طریق USB، ممکن است نیاز به نصب درایورهای مربوط به تراشه مبدل USB به سریال (مانند CP210x یا CH340) روی برد ESP32 خود داشته باشید.
2. دانلود MicroPython Firmware
آخرین نسخه MicroPython firmware را برای ESP32 از وبسایت رسمی MicroPython دانلود کنید. فایل .bin مربوط به ESP32 (معمولاً با نام esp32-xxxx.bin) را پیدا کرده و دانلود کنید.
# مثال: دانلود جدیدترین نسخه
# به آدرس micropython.org/download/esp32/ بروید و فایل .bin را دانلود کنید.
3. فلش کردن Firmware
برد ESP32 خود را از طریق کابل USB به کامپیوتر متصل کنید. پورت سریال ESP32 را پیدا کنید (در لینوکس/مک معمولاً /dev/ttyUSB0 یا /dev/tty.SLAB_USBtoUART و در ویندوز COMx). سپس دستورات زیر را در ترمینال اجرا کنید:
# پاک کردن فلش قبل از نصب جدید (اختیاری اما توصیه میشود)
esptool.py --chip esp32 --port /dev/ttyUSB0 erase_flash
# فلش کردن firmware
esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 460800 write_flash -z 0x1000 esp32-xxxx.bin
به جای /dev/ttyUSB0 پورت صحیح خود و به جای esp32-xxxx.bin نام فایل firmware دانلود شده را قرار دهید. سرعت Baud Rate (460800) میتواند متفاوت باشد اما این مقدار معمولاً خوب کار میکند.
4. اتصال به REPL
پس از فلش موفقیتآمیز، میتوانید از طریق یک برنامه ترمینال سریال (مانند picocom، minicom در لینوکس/مک یا PuTTY، Tera Term در ویندوز) به MicroPython REPL متصل شوید. Thonny IDE (یک محیط توسعه یکپارچه پایتون) نیز یک ترمینال سریال داخلی دارد که برای MicroPython بسیار مناسب است.
# نصب Thonny
pip install thonny
# پس از نصب، Thonny را باز کرده و در منوی Tools -> Options -> Interpreter را انتخاب کنید.
# در قسمت Interpreter، گزینه "MicroPython (ESP32)" را انتخاب کرده و پورت سریال صحیح را تنظیم کنید.
# سپس دکمه Stop/Restart backend را بزنید تا به REPL متصل شوید.
در REPL، میتوانید دستورات پایتون را به صورت تعاملی اجرا کنید:
>>> print("Hello from MicroPython!")
Hello from MicroPython!
>>> import machine
>>> pin = machine.Pin(2, machine.Pin.OUT) # پین 2 (LED داخلی ESP32) را به عنوان خروجی تنظیم کنید
>>> pin.value(1) # LED را روشن کنید
>>> pin.value(0) # LED را خاموش کنید
با این مراحل، شما اکنون یک ESP32 آماده به کار با MicroPython دارید و میتوانید شروع به توسعه وب سرور خود کنید. این بستر مستحکم، پایهای برای تمام کارهای آتی ما خواهد بود و به شما اطمینان میدهد که ابزارهای لازم را در اختیار دارید.
معماری یک وب سرور MicroPython در ESP32
قبل از اینکه به کدنویسی وب سرور بپردازیم، درک معماری و مفاهیم زیربنایی آن ضروری است. یک وب سرور، فارغ از اینکه روی یک سرور قدرتمند اجرا شود یا یک میکروکنترلر کوچک، اصول پایه یکسانی را دنبال میکند. در این بخش، ما به بررسی این اصول و نحوه پیادهسازی آنها در بستر MicroPython و ESP32 میپردازیم.
مبانی شبکه: TCP/IP و سوکتها
وب سرورها عمدتاً بر اساس پروتکل TCP/IP عمل میکنند. TCP (Transmission Control Protocol) یک پروتکل اتصالگرا است که امکان انتقال دادهها را به صورت قابل اعتماد و مرتب بین دو نقطه پایانی (end-points) در یک شبکه فراهم میکند. IP (Internet Protocol) وظیفه آدرسدهی و مسیریابی بستههای داده را بر عهده دارد. ترکیب این دو، ستون فقرات اینترنت و شبکههای محلی است.
در MicroPython، ما با استفاده از ماژول socket با لایههای TCP/IP تعامل داریم. یک سوکت (socket) به عنوان یک نقطه پایانی برای ارتباط عمل میکند. سوکتها میتوانند از نوع SOCK_STREAM (برای TCP) یا SOCK_DGRAM (برای UDP) باشند. برای وب سرور، ما عمدتاً از سوکتهای TCP استفاده میکنیم زیرا HTTP بر روی TCP ساخته شده است.
مراحل کلی کار با سوکت برای یک سرور:
- ایجاد سوکت (
socket.socket()): یک شیء سوکت جدید ایجاد میکند. - پیوستن به آدرس/پورت (
bind()): سوکت را به یک آدرس IP محلی و یک شماره پورت خاص متصل میکند. وب سرورها معمولاً از پورت 80 برای HTTP و پورت 443 برای HTTPS استفاده میکنند. - گوش دادن (
listen()): سوکت را در حالت گوش دادن قرار میدهد تا منتظر اتصالات ورودی باشد. - پذیرش اتصال (
accept()): هنگامی که یک کلاینت به سرور متصل میشود، سرور اتصال را میپذیرد و یک سوکت جدید برای ارتباط با آن کلاینت خاص ایجاد میکند. این سوکت جدید برای تبادل داده با کلاینت استفاده میشود. - دریافت داده (
recv()): دادهها را از کلاینت متصل دریافت میکند. - ارسال داده (
sendall()): دادهها را به کلاینت ارسال میکند. - بستن سوکت (
close()): ارتباط با کلاینت یا سرور را قطع میکند.
ESP32 دارای یک پشته شبکه (network stack) سختافزاری و نرمافزاری بهینهسازی شده است که تمام پیچیدگیهای پروتکلهای TCP/IP را در پسزمینه مدیریت میکند، و ما میتوانیم از طریق ماژول network و socket MicroPython به راحتی با آن تعامل داشته باشیم.
پروتکل HTTP: زبان وب
HTTP (Hypertext Transfer Protocol) پروتکل اصلی برای انتقال اطلاعات در وب است. این یک پروتکل بدون حالت (stateless) است، به این معنی که هر درخواست کلاینت به سرور مستقل از درخواستهای قبلی یا بعدی است. یک ارتباط HTTP شامل یک درخواست (Request) از کلاینت و یک پاسخ (Response) از سرور است.
درخواست HTTP (HTTP Request)
یک درخواست HTTP معمولاً شامل اجزای زیر است:
- خط درخواست (Request Line): شامل متد HTTP (مانند GET، POST، PUT، DELETE)، مسیر URI (Uniform Resource Identifier) و نسخه پروتکل (مانند HTTP/1.0 یا HTTP/1.1).
GET /index.html HTTP/1.1 - هدرها (Headers): اطلاعات اضافی درباره درخواست، کلاینت یا محتوای درخواست. مثال:
Host،User-Agent،Accept،Content-Type.Host: 192.168.1.100 User-Agent: Mozilla/5.0 (...) - بدنه درخواست (Request Body): برای متدهایی مانند POST یا PUT که دادهها را به سرور ارسال میکنند (مانند اطلاعات فرم، JSON).
پاسخ HTTP (HTTP Response)
یک پاسخ HTTP نیز دارای ساختاری مشابه است:
- خط وضعیت (Status Line): شامل نسخه پروتکل، کد وضعیت (Status Code) و پیام وضعیت (Status Message). مثال:
200 OK،404 Not Found،500 Internal Server Error.HTTP/1.1 200 OK - هدرها (Headers): اطلاعات اضافی درباره پاسخ، سرور یا محتوای پاسخ. مثال:
Content-Type،Content-Length،Date.Content-Type: text/html Content-Length: 123 - بدنه پاسخ (Response Body): محتوای اصلی که سرور برای کلاینت ارسال میکند، مانند یک صفحه HTML، یک فایل JSON یا تصویر.
برای ساخت یک وب سرور MicroPython، ما باید قادر به تجزیه درخواستهای HTTP ورودی و ساخت پاسخهای HTTP معتبر باشیم.
حلقه سرور (Server Loop): قلب وب سرور
هر وب سروری، هستهای به نام “حلقه سرور” دارد که وظیفه اصلی آن گوش دادن دائمی به درخواستهای جدید، پذیرش آنها، پردازش و ارسال پاسخ است. در یک وب سرور MicroPython، این حلقه معمولاً به صورت زیر عمل میکند:
while True:
conn, addr = s.accept() # منتظر اتصال جدید بمان
request = conn.recv(1024) # درخواست را دریافت کن
# پردازش درخواست
response = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\nHello!
"
conn.sendall(response.encode()) # پاسخ را ارسال کن
conn.close() # اتصال را ببند
این یک حلقه بینهایت است که همیشه در حال گوش دادن به اتصالات است. هر بار که یک کلاینت متصل میشود، سرور درخواست را میخواند، آن را پردازش میکند و پاسخی را برمیگرداند. سپس اتصال را میبندد تا منابع آزاد شوند و سرور آماده پذیرش اتصال بعدی شود. در ESP32، به دلیل محدودیتهای منابع، معمولاً از یک مدل تکرشتهای (single-threaded) و بلوکه کننده (blocking) استفاده میشود که هر درخواست را به ترتیب پردازش میکند. برای کاربردهای ساده IoT، این مدل اغلب کافی است.
ملاحظات حافظه و عملکرد در ESP32
یکی از چالشهای اصلی در توسعه برای میکروکنترلرها، مدیریت منابع محدود، به ویژه حافظه RAM و حافظه فلش است. MicroPython تا حد زیادی این بار را از دوش توسعهدهنده برمیدارد، اما همچنان باید به نکات زیر توجه داشت:
- حافظه RAM: ESP32 معمولاً حدود 520 کیلوبایت SRAM دارد که بخش قابل توجهی از آن توسط سیستم عامل MicroPython و پشته شبکه اشغال میشود. باید از ایجاد اشیاء بزرگ، لیستهای طولانی یا رشتههای طولانی که حافظه زیادی مصرف میکنند، اجتناب شود.
- حافظه فلش: برای ذخیره کد MicroPython (فایلهای
.py)، صفحات HTML ثابت، تصاویر و سایر دادهها استفاده میشود. MicroPython دارای یک سیستم فایل داخلی (littlefs) است که به شما اجازه میدهد فایلها را مانند یک سیستم فایل استاندارد مدیریت کنید. - بهینهسازی کد: استفاده از توابع و متدهای کارآمد، اجتناب از تکرار کد و پاکسازی منابع پس از استفاده (مانند بستن سوکتها) برای حفظ عملکرد و پایداری سیستم ضروری است.
درک این مفاهیم پایه شبکه و معماری وب سرور، شما را برای شیرجه رفتن در بخش بعدی، یعنی پیادهسازی عملی کد، آماده میکند. این دانش به شما کمک میکند تا کدی بنویسید که نه تنها کار میکند، بلکه کارآمد و پایدار نیز باشد.
پیادهسازی گام به گام: از سوکت خام تا پاسخ HTTP
حالا که با مفاهیم نظری آشنا شدیم، زمان آن رسیده که دست به کار شویم و اولین وب سرور MicroPython خود را روی ESP32 پیادهسازی کنیم. ما با ایجاد یک اتصال Wi-Fi آغاز کرده و سپس به ساخت یک وب سرور بسیار ساده میپردازیم که یک پاسخ “Hello, World!” را برمیگرداند.
1. اتصال Wi-Fi: دروازه ورود به شبکه
اولین گام برای هر پروژه متصل به شبکه، اتصال به Wi-Fi است. ESP32 میتواند به عنوان یک ایستگاه (Station) به یک نقطه دسترسی (Access Point) موجود متصل شود، یا خودش به عنوان یک Access Point عمل کند تا دستگاههای دیگر به آن متصل شوند. برای یک وب سرور کوچک، معمولاً آن را به عنوان یک Station به روتر خانگی متصل میکنیم تا در شبکه محلی قابل دسترسی باشد.
کد زیر نحوه اتصال ESP32 به یک شبکه Wi-Fi را نشان میدهد:
import network
import time
# اطلاعات شبکه Wi-Fi خود را اینجا وارد کنید
SSID = 'YOUR_WIFI_SSID'
PASSWORD = 'YOUR_WIFI_PASSWORD'
def connect_to_wifi(ssid, password):
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
print('Connecting to network...')
wlan.connect(ssid, password)
# منتظر اتصال بمان
max_attempts = 20
while not wlan.isconnected() and max_attempts > 0:
print('.')
time.sleep(1)
max_attempts -= 1
if wlan.isconnected():
print('Network config:', wlan.ifconfig())
print('Connected to Wi-Fi!')
return wlan.ifconfig()[0] # بازگرداندن آدرس IP
else:
print('Failed to connect to Wi-Fi.')
return None
else:
print('Already connected. Network config:', wlan.ifconfig())
return wlan.ifconfig()[0]
# --- مثال استفاده ---
# ip_address = connect_to_wifi(SSID, PASSWORD)
# if ip_address:
# print(f"ESP32 IP Address: {ip_address}")
# else:
# print("Exiting due to Wi-Fi connection failure.")
# # میتوانید اینجا عملیات خاصی برای خطا انجام دهید، مثلاً ریست کردن یا راهاندازی AP
این تابع تلاش میکند تا به شبکه Wi-Fi مشخص شده متصل شود و تا 20 ثانیه برای اتصال منتظر میماند. پس از اتصال موفقیتآمیز، آدرس IP اختصاص داده شده به ESP32 را چاپ میکند که برای دسترسی به وب سرور ضروری است.
2. ایجاد و پیکربندی سوکت سرور
پس از اتصال به Wi-Fi، باید یک سوکت TCP ایجاد کرده و آن را برای گوش دادن به اتصالات پیکربندی کنیم.
import socket
def create_server_socket(port=80):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # اجازه استفاده مجدد از پورت را میدهد
# آدرس IP سرور (0.0.0.0 به معنای گوش دادن به همه رابطهای موجود است)
# و شماره پورت را bind میکنیم
server_address = ('0.0.0.0', port)
s.bind(server_address)
# سرور را در حالت گوش دادن قرار میدهیم
# 5: تعداد اتصالات منتظر در صف (backlog)
s.listen(5)
print(f'Listening on {server_address[0]}:{server_address[1]}')
return s
# --- مثال استفاده ---
# server_socket = create_server_socket(80)
تابع create_server_socket یک سوکت TCP جدید ایجاد میکند و آن را به آدرس 0.0.0.0 و پورت مشخص شده (پیشفرض 80 برای HTTP) متصل میکند. SO_REUSEADDR تضمین میکند که پس از ریست کردن ESP32، پورت بلافاصله قابل استفاده مجدد باشد. listen(5) به سوکت میگوید که تا 5 اتصال ورودی را در صف نگه دارد.
3. دریافت و تجزیه درخواست HTTP
حالا باید در حلقه اصلی سرور، اتصالات را بپذیریم، دادههای ورودی را دریافت کنیم و درخواست HTTP را تجزیه کنیم.
def handle_request(client_socket):
try:
request = client_socket.recv(1024).decode('utf-8')
if not request:
return # اگر درخواستی دریافت نشد، برگرد
print('-----------------------------------------')
print('Request received:')
print(request.split('\r\n')[0]) # فقط خط اول درخواست را چاپ میکنیم
# تجزیه خط اول درخواست برای استخراج متد و مسیر
request_line = request.split('\n')[0]
method, path, protocol = request_line.split(' ')
print(f"Method: {method}, Path: {path}")
# --- اینجا منطق پردازش مسیرها و متدها را اضافه خواهیم کرد ---
# مثال: پاسخ "Hello, World!" برای هر درخواست
response_body = "Hello from MicroPython Web Server on ESP32!
"
response_headers = "HTTP/1.1 200 OK\r\n"
response_headers += "Content-Type: text/html\r\n"
response_headers += f"Content-Length: {len(response_body.encode('utf-8'))}\r\n"
response_headers += "Connection: close\r\n" # بستن اتصال پس از ارسال پاسخ
response_headers += "\r\n" # خط خالی برای جدا کردن هدرها از بدنه
full_response = (response_headers + response_body).encode('utf-8')
client_socket.sendall(full_response)
except OSError as e:
print(f"Error handling request: {e}")
finally:
client_socket.close() # همیشه اتصال را ببندید
در تابع handle_request:
client_socket.recv(1024): حداکثر 1024 بایت داده را از سوکت کلاینت دریافت میکند. این مقدار ممکن است برای درخواستهای بزرگتر نیاز به تنظیم داشته باشد.decode('utf-8'): دادههای باینری دریافت شده را به رشته (string) تبدیل میکند.- تجزیه خط اول درخواست برای استخراج
method(GET, POST, …),path(/, /data, …) وprotocol(HTTP/1.1). - ساخت
response_headersوresponse_bodyبه صورت دستی. بسیار مهم است که هدرها با\r\nاز هم جدا شوند و دو\r\nپایانی برای جدا کردن هدرها از بدنه پاسخ استفاده شود. Content-Lengthباید با طول واقعی بدنه پاسخ مطابقت داشته باشد.Connection: closeبه کلاینت میگوید که سرور پس از ارسال این پاسخ، اتصال را قطع خواهد کرد.- در نهایت، پاسخ کامل را با
client_socket.sendall()ارسال کرده و سوکت کلاینت را میبندیم. استفاده ازtry...finallyتضمین میکند که سوکت حتی در صورت بروز خطا نیز بسته شود.
4. حلقه اصلی سرور
حالا تمام قطعات را در یک حلقه اصلی سرور با هم ترکیب میکنیم:
# فایل main.py
import network
import socket
import time
# --- تنظیمات Wi-Fi ---
SSID = 'YOUR_WIFI_SSID'
PASSWORD = 'YOUR_WIFI_PASSWORD'
def connect_to_wifi(ssid, password):
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
print('Connecting to network...')
wlan.connect(ssid, password)
max_attempts = 20
while not wlan.isconnected() and max_attempts > 0:
print('.')
time.sleep(1)
max_attempts -= 1
if wlan.isconnected():
print('Network config:', wlan.ifconfig())
print('Connected to Wi-Fi!')
return wlan.ifconfig()[0]
else:
print('Failed to connect to Wi-Fi.')
return None
else:
print('Already connected. Network config:', wlan.ifconfig())
return wlan.ifconfig()[0]
def create_server_socket(port=80):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_address = ('0.0.0.0', port)
s.bind(server_address)
s.listen(5)
print(f'Listening on {server_address[0]}:{server_address[1]}')
return s
def handle_request(client_socket):
try:
request = client_socket.recv(1024).decode('utf-8')
if not request:
return
print('-----------------------------------------')
print('Request received:')
print(request.split('\r\n')[0])
request_line = request.split('\n')[0]
method, path, protocol = request_line.split(' ')
print(f"Method: {method}, Path: {path}")
response_body = "Hello from MicroPython Web Server on ESP32!
"
response_headers = "HTTP/1.1 200 OK\r\n"
response_headers += "Content-Type: text/html\r\n"
response_headers += f"Content-Length: {len(response_body.encode('utf-8'))}\r\n"
response_headers += "Connection: close\r\n"
response_headers += "\r\n"
full_response = (response_headers + response_body).encode('utf-8')
client_socket.sendall(full_response)
except OSError as e:
print(f"Error handling request: {e}")
finally:
client_socket.close()
def main():
ip_address = connect_to_wifi(SSID, PASSWORD)
if not ip_address:
print("Could not connect to Wi-Fi. Exiting.")
return
server_socket = create_server_socket(80)
while True:
try:
print("Waiting for client connection...")
client_conn, client_addr = server_socket.accept()
print(f"Connection from {client_addr}")
handle_request(client_conn)
except KeyboardInterrupt:
print("Server stopped by user.")
break
except Exception as e:
print(f"Main loop error: {e}")
# در صورت بروز خطاهای جدی، میتوانیم سوکت سرور را ببندیم و دوباره باز کنیم
try:
server_socket.close()
except:
pass
server_socket = create_server_socket(80) # سعی در بازگشایی سرور
server_socket.close()
if __name__ == '__main__':
main()
این کد را میتوانید در فایلی به نام main.py (یا boot.py اگر میخواهید هنگام بوت شدن ESP32 اجرا شود) ذخیره کرده و با استفاده از Thonny یا ampy روی ESP32 خود آپلود کنید. پس از آپلود، ESP32 را ریست کنید. در ترمینال Thonny، باید پیغامهای اتصال Wi-Fi و سپس “Listening on 0.0.0.0:80” را مشاهده کنید. حالا میتوانید آدرس IP چاپ شده را در مرورگر وب خود وارد کرده و صفحه “Hello from MicroPython Web Server on ESP32!” را ببینید.
خدمترسانی به فایلهای ثابت (بسیار پایه)
در بسیاری از موارد، میخواهید فایلهای HTML، CSS یا JavaScript را به کلاینت ارائه دهید. MicroPython دارای یک سیستم فایل داخلی است که به شما امکان میدهد فایلها را روی حافظه فلش ESP32 ذخیره کنید. میتوانید این فایلها را بخوانید و به عنوان بدنه پاسخ HTTP ارسال کنید. برای مثال، برای ارائه یک صفحه index.html:
# فرض کنید یک فایل index.html روی ESP32 شما وجود دارد
# محتوای ایندکس.html: <!DOCTYPE html><html><head><title>My Server</title></head><body><h1>Welcome!</h1></body></html>
# در تابع handle_request، به جای پاسخ ثابت:
# ...
if path == '/':
try:
with open('index.html', 'r') as f:
response_body = f.read()
response_headers = "HTTP/1.1 200 OK\r\n"
response_headers += "Content-Type: text/html\r\n"
response_headers += f"Content-Length: {len(response_body.encode('utf-8'))}\r\n"
response_headers += "Connection: close\r\n\r\n"
full_response = (response_headers + response_body).encode('utf-8')
except OSError:
response_body = "404 Not Found
"
response_headers = "HTTP/1.1 404 Not Found\r\n"
response_headers += "Content-Type: text/html\r\n"
response_headers += f"Content-Length: {len(response_body.encode('utf-8'))}\r\n"
response_headers += "Connection: close\r\n\r\n"
full_response = (response_headers + response_body).encode('utf-8')
else:
# پاسخ 404 برای مسیرهای دیگر
response_body = "404 Not Found
"
response_headers = "HTTP/1.1 404 Not Found\r\n"
response_headers += "Content-Type: text/html\r\n"
response_headers += f"Content-Length: {len(response_body.encode('utf-8'))}\r\n"
response_headers += "Connection: close\r\n\r\n"
full_response = (response_headers + response_body).encode('utf-8')
client_socket.sendall(full_response)
# ...
این رویکرد، پایه و اساس ساخت رابطهای کاربری وب را فراهم میکند. با این پیادهسازی گام به گام، شما یک وب سرور MicroPython کاربردی روی ESP32 خود دارید. در بخش بعدی، این وب سرور را با قابلیتهای پیشرفتهتر مانند مدیریت مسیرها و ساخت APIهای RESTful ارتقا خواهیم داد.
پیشرفتهسازی وب سرور: مدیریت مسیرها، API و پایداری
وب سرور پایهای که در بخش قبل ساختیم، تنها میتواند یک پاسخ ثابت را برگرداند. برای اینکه یک وب سرور واقعاً مفید باشد، باید بتواند درخواستهای مختلف را بر اساس مسیر (URL) و متد HTTP (GET، POST و غیره) پردازش کند و پاسخهای دینامیک برگرداند. این بخش به شما نشان میدهد که چگونه وب سرور خود را با این قابلیتها پیشرفتهتر کنید و به آن ثبات ببخشید.
1. مسیریابی (Routing) و مدیریت درخواستهای HTTP
مسیریابی به معنای نگاشت درخواستهای HTTP ورودی به توابع یا منطق خاص در کد سرور است. برای مثال، درخواست به / باید صفحه اصلی را برگرداند، /api/data باید دادههای حسگر را به صورت JSON برگرداند و /control/led باید یک LED را کنترل کند.
در MicroPython، ما میتوانیم این کار را با استفاده از دستورات if/elif/else و بررسی متغیر path انجام دهیم. همچنین، به جای ساخت دستی پاسخها، میتوانیم توابع کمکی برای ساخت هدرها و بدنه پاسخ داشته باشیم.
# اضافه کردن توابع کمکی برای پاسخها
def send_response(client_socket, status_code, content_type, body):
status_message = {
200: "OK",
404: "Not Found",
500: "Internal Server Error"
}.get(status_code, "Unknown")
response_headers = f"HTTP/1.1 {status_code} {status_message}\r\n"
response_headers += f"Content-Type: {content_type}\r\n"
response_headers += f"Content-Length: {len(body.encode('utf-8'))}\r\n"
response_headers += "Connection: close\r\n"
response_headers += "\r\n"
full_response = (response_headers + body).encode('utf-8')
client_socket.sendall(full_response)
# تغییر در تابع handle_request
# ...
def handle_request(client_socket):
try:
request = client_socket.recv(1024).decode('utf-8')
if not request:
return
request_lines = request.split('\r\n')
request_line = request_lines[0]
method, path, protocol = request_line.split(' ')
print(f"Method: {method}, Path: {path}")
# منطق مسیریابی
if path == '/':
send_response(client_socket, 200, "text/html", "Welcome to Home Page!
Visit /api/data or /control/led
")
elif path == '/api/data' and method == 'GET':
# اینجا میتوانیم دادههای واقعی را از سنسور بخوانیم
import json
data = {"temperature": 25.5, "humidity": 60, "status": "OK"}
send_response(client_socket, 200, "application/json", json.dumps(data))
elif path == '/control/led' and method == 'GET':
# مثال برای کنترل LED (نیاز به تعریف پین LED)
# from machine import Pin
# led = Pin(2, Pin.OUT)
# led.value(not led.value()) # تغییر وضعیت LED
send_response(client_socket, 200, "text/html", "LED Toggled!
")
else:
send_response(client_socket, 404, "text/html", "404 Not Found
The requested URL was not found on this server.
")
except OSError as e:
print(f"Error handling request: {e}")
finally:
client_socket.close()
# ...
با این تغییرات، سرور شما اکنون میتواند درخواستها را بر اساس path و method تشخیص داده و پاسخهای متفاوتی را برگرداند. این ساختار پایه برای ساخت APIهای RESTful است.
2. ساخت یک API RESTful ساده
APIهای RESTful یک روش استاندارد برای ساخت رابطهای برنامهنویسی برای تبادل داده هستند. آنها معمولاً از متدهای HTTP (GET برای دریافت، POST برای ارسال، PUT برای بهروزرسانی، DELETE برای حذف) و فرمت JSON برای دادهها استفاده میکنند. در ESP32، ما میتوانیم یک زیرمجموعه از این اصول را پیادهسازی کنیم.
مثال بالا برای /api/data یک گام به سوی API RESTful بود. برای مدیریت درخواستهای POST، باید بدنه درخواست را تجزیه کنیم. MicroPython دارای ماژول json است که برای پردازش دادههای JSON بسیار مفید است.
import json
# ...
def handle_request(client_socket):
try:
request_bytes = client_socket.recv(1024)
request = request_bytes.decode('utf-8')
if not request:
return
request_lines = request.split('\r\n')
request_line = request_lines[0]
method, path, protocol = request_line.split(' ')
headers = {}
body_start_index = 0
for i in range(1, len(request_lines)):
if request_lines[i] == '':
body_start_index = i + 1
break
header_parts = request_lines[i].split(':', 1)
if len(header_parts) == 2:
headers[header_parts[0].strip().lower()] = header_parts[1].strip()
request_body = '\r\n'.join(request_lines[body_start_index:]) if body_start_index < len(request_lines) else ''
if path == '/api/config' and method == 'POST':
if 'content-type' in headers and 'application/json' in headers['content-type']:
try:
config_data = json.loads(request_body)
print("Received config:", config_data)
# اینجا میتوانید دادههای پیکربندی را ذخیره یا اعمال کنید
send_response(client_socket, 200, "application/json", json.dumps({"status": "success", "message": "Config updated"}))
except ValueError:
send_response(client_socket, 400, "application/json", json.dumps({"status": "error", "message": "Invalid JSON"}))
else:
send_response(client_socket, 400, "application/json", json.dumps({"status": "error", "message": "Content-Type must be application/json"}))
# ... سایر مسیرها
else:
send_response(client_socket, 404, "text/html", "404 Not Found
")
except OSError as e:
print(f"Error handling request: {e}")
finally:
client_socket.close()
در این مثال، ما درخواستهای POST به /api/config را مدیریت میکنیم. ابتدا هدرها را تجزیه میکنیم تا Content-Type را بررسی کنیم، سپس بدنه درخواست را به عنوان JSON بارگذاری میکنیم. این به شما اجازه میدهد تا تنظیمات را از راه دور به ESP32 ارسال کنید.
3. مدیریت خطاها (Error Handling)
یک وب سرور قوی باید بتواند خطاهای مختلف را به درستی مدیریت کند. ما قبلاً از try...except برای خطاهای عمومی استفاده کردیم. همچنین، ارسال کدهای وضعیت HTTP صحیح (مانند 400 Bad Request، 404 Not Found، 500 Internal Server Error) به کلاینت بسیار مهم است.
- 404 Not Found: برای مسیرهایی که وجود ندارند. (در مثال بالا پیادهسازی شد)
- 400 Bad Request: برای درخواستهایی که از نظر سینتکس نادرست هستند یا دادههای نامعتبر ارسال میکنند (مثلاً JSON نامعتبر).
- 500 Internal Server Error: برای خطاهایی که در سمت سرور اتفاق میافتند و مربوط به منطق برنامه هستند.
4. خدمترسانی به چندین کلاینت (مدل Blocking)
مدل وب سرور تکرشتهای و بلوکه کننده (blocking) که ما پیادهسازی کردهایم، به این معنی است که سرور نمیتواند همزمان چندین درخواست را پردازش کند. هنگامی که یک کلاینت متصل میشود، سرور تا زمانی که درخواست آن را کامل نکند، اتصال بعدی را نمیپذیرد. برای بسیاری از کاربردهای IoT که تعداد کلاینتها کم است و درخواستها سبک هستند، این مدل کاملاً کافی است. اگر نیاز به عملکرد بالاتر و توانایی مدیریت همزمان چندین کلاینت دارید، میتوانید به راهحلهایی مانند استفاده از uasyncio (نسخه MicroPython از asyncio) یا کتابخانههای وب سرور سبکتر مانند Microdot یا Flask-Micro که بر پایه uasyncio هستند، فکر کنید. اما اینها پیچیدگی بیشتری را به همراه دارند و اغلب نیاز به منابع بیشتری دارند که ممکن است برای ESP32 محدودیت ایجاد کند.
5. امنیت پایه
امنیت در IoT بسیار مهم است. برای یک وب سرور کوچک، میتوانید اقدامات امنیتی اولیه زیر را در نظر بگیرید:
- اعتبارسنجی ورودی (Input Validation): هرگز به ورودیهای کاربر اعتماد نکنید. همیشه آنها را قبل از استفاده اعتبارسنجی کنید تا از حملاتی مانند Injection جلوگیری شود.
- احراز هویت ساده (Basic Authentication): برای دسترسی به بخشهای حساس وب سرور، میتوانید یک مکانیزم احراز هویت ساده (مانند Basic Auth HTTP یا یک توکن ساده) پیادهسازی کنید. این کار را میتوان با بررسی هدر
Authorizationدر درخواستهای HTTP انجام داد.# مثال برای احراز هویت بسیار ساده # AUTH_TOKEN = "mysecrettoken" # if 'authorization' in headers and headers['authorization'] == f'Bearer {AUTH_TOKEN}': # # دسترسی مجاز # else: # send_response(client_socket, 401, "text/html", "Unauthorized
") # return - SSL/TLS (HTTPS): پیادهسازی HTTPS در MicroPython برای ESP32 پیچیده است و به منابع بیشتری نیاز دارد، اما برای کاربردهای حساس توصیه میشود. MicroPython از ماژول
sslپشتیبانی میکند، اما معمولاً نیاز به فایلهای گواهی کوچک و مدیریت حافظه دقیق دارد. - جدا نگه داشتن credentials: اطلاعات حساس مانند SSID و رمز عبور Wi-Fi را در کد اصلی قرار ندهید. آنها را در یک فایل جداگانه (مثلاً
config.py) ذخیره کنید و هنگام آپلود، اطمینان حاصل کنید که این فایل در دسترس عموم نباشد.
6. پایداری و مدیریت مجدد اتصال Wi-Fi
اتصالات Wi-Fi ممکن است قطع شوند. وب سرور شما باید بتواند این قطع ارتباطها را تشخیص دهد و سعی در اتصال مجدد داشته باشد. میتوانید یک حلقه while در تابع main اضافه کنید که به طور دورهای وضعیت اتصال Wi-Fi را بررسی کند و در صورت قطع شدن، مجدداً تلاش کند.
# ... در تابع main
# ...
wlan = network.WLAN(network.STA_IF) # دسترسی به شیء wlan از بیرون تابع connect_to_wifi
# ...
while True:
try:
if not wlan.isconnected(): # بررسی وضعیت Wi-Fi
print("Wi-Fi disconnected. Reconnecting...")
ip_address = connect_to_wifi(SSID, PASSWORD) # تلاش برای اتصال مجدد
if not ip_address:
print("Reconnection failed. Retrying in 30 seconds...")
time.sleep(30)
continue # ادامه حلقه اصلی
client_conn, client_addr = server_socket.accept()
# ...
با این تکنیکهای پیشرفتهسازی، وب سرور MicroPython شما روی ESP32 به یک ابزار قدرتمندتر و انعطافپذیرتر برای تعامل با پروژههای IoT تبدیل میشود. در بخش بعدی، ما یک مثال کاربردی و عملی را برای پایش و کنترل دستگاههای IoT ارائه خواهیم داد.
مثال کاربردی: وب سرور برای پایش و کنترل IoT
پس از آشنایی با اصول و تکنیکهای پیشرفتهسازی، وقت آن است که آموختههایمان را در یک سناریوی واقعی به کار بگیریم. در این بخش، یک وب سرور MicroPython روی ESP32 خواهیم ساخت که قادر است دادههای حسگر دما و رطوبت را پایش کرده و یک LED را کنترل کند. این سناریو، یک نمونه کلاسیک از کاربردهای IoT است و به شما نشان میدهد که چگونه میتوانید با دستگاههای فیزیکی خود از طریق یک رابط وب تعامل داشته باشید.
1. سناریو و سختافزار مورد نیاز
سناریو: میخواهیم یک سیستم پایش آب و هوا ساده با امکان کنترل یک وسیله (مانند فن یا لامپ) ایجاد کنیم. کاربر میتواند از طریق مرورگر وب، دمای فعلی، رطوبت را مشاهده کرده و وضعیت یک LED را تغییر دهد.
سختافزار مورد نیاز:
- ESP32 Dev Kit: برد توسعه ESP32.
- حسگر DHT11 یا DHT22: برای اندازهگیری دما و رطوبت. (DHT22 دقیقتر و سریعتر است).
- LED: یک LED استاندارد (هر رنگی) به همراه یک مقاومت 220 تا 330 اهم برای محدود کردن جریان.
- برد بورد، سیمهای جامپر: برای اتصالات.
اتصالات سختافزاری:
- حسگر DHT11/DHT22:
- پین VCC به 3.3V ESP32
- پین GND به GND ESP32
- پین DATA به یک پین GPIO دلخواه ESP32 (مثلاً GPIO 4)
- LED:
- پایه بلند (آند) LED از طریق مقاومت به یک پین GPIO دلخواه ESP32 (مثلاً GPIO 2، که LED داخلی نیز هست و برای تست راحت است).
- پایه کوتاه (کاتد) LED به GND ESP32.
2. کد MicroPython برای پایش و کنترل
ماژول dht برای MicroPython به شما امکان میدهد تا به راحتی با حسگرهای DHT11/DHT22 تعامل داشته باشید. (ممکن است نیاز باشد این ماژول را روی ESP32 خود آپلود کنید اگر به طور پیشفرض موجود نیست).
# main.py - وب سرور پایش و کنترل IoT
import network
import socket
import time
import json
from machine import Pin
# اگر ماژول dht11.py را ندارید، باید آن را به صورت دستی آپلود کنید
# https://github.com/micropython-modules/micropython-dht/blob/master/dht.py
import dht
# --- تنظیمات Wi-Fi ---
SSID = 'YOUR_WIFI_SSID'
PASSWORD = 'YOUR_WIFI_PASSWORD'
# --- تنظیمات سختافزار ---
DHT_PIN = 4 # پین GPIO برای حسگر DHT
LED_PIN = 2 # پین GPIO برای LED (LED داخلی ESP32 نیز معمولاً روی پین 2 است)
# --- مقادیر اولیه و نمونهسازی ---
sensor = dht.DHT11(Pin(DHT_PIN)) # برای DHT22 از dht.DHT22 استفاده کنید
led = Pin(LED_PIN, Pin.OUT)
current_temperature = 0.0
current_humidity = 0.0
led_status = False
# --- توابع کمکی ---
def connect_to_wifi(ssid, password):
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
print('Connecting to network...')
wlan.connect(ssid, password)
max_attempts = 30
while not wlan.isconnected() and max_attempts > 0:
print('.')
time.sleep(1)
max_attempts -= 1
if wlan.isconnected():
print('Network config:', wlan.ifconfig())
print('Connected to Wi-Fi!')
return wlan.ifconfig()[0], wlan
else:
print('Failed to connect to Wi-Fi.')
return None, wlan
else:
print('Already connected. Network config:', wlan.ifconfig())
return wlan.ifconfig()[0], wlan
def create_server_socket(port=80):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_address = ('0.0.0.0', port)
s.bind(server_address)
s.listen(5)
print(f'Listening on {server_address[0]}:{server_address[1]}')
return s
def send_response(client_socket, status_code, content_type, body):
status_message = {
200: "OK",
400: "Bad Request",
404: "Not Found",
500: "Internal Server Error"
}.get(status_code, "Unknown")
response_headers = f"HTTP/1.1 {status_code} {status_message}\r\n"
response_headers += f"Content-Type: {content_type}\r\n"
response_headers += f"Content-Length: {len(body.encode('utf-8'))}\r\n"
response_headers += "Connection: close\r\n"
response_headers += "\r\n"
full_response = (response_headers + body).encode('utf-8')
client_socket.sendall(full_response)
def read_dht_sensor():
global current_temperature, current_humidity
try:
sensor.measure()
current_temperature = sensor.temperature()
current_humidity = sensor.humidity()
print(f"Sensor Read: Temp={current_temperature}°C, Humidity={current_humidity}%")
except OSError as e:
print(f"Failed to read DHT sensor: {e}")
current_temperature = -1
current_humidity = -1
# --- صفحات HTML ---
def get_index_html(temp, humidity, led_state):
led_status_text = "ON" if led_state else "OFF"
html = f"""
ESP32 IoT Control
ESP32 IoT Dashboard
Temperature: {temp}°C
Humidity: {humidity}%
LED Status: {led_status_text}
Last updated: {time.time()}
"""
return html
# --- تابع اصلی پردازش درخواستها ---
def handle_request(client_socket):
global led_status
try:
request_bytes = client_socket.recv(1024)
request = request_bytes.decode('utf-8')
if not request:
return
request_lines = request.split('\r\n')
request_line = request_lines[0]
method, path, protocol = request_line.split(' ')
print(f"Received request: {method} {path}")
if path == '/' and method == 'GET':
html_content = get_index_html(current_temperature, current_humidity, led_status)
send_response(client_socket, 200, "text/html", html_content)
elif path == '/api/sensor-data' and method == 'GET':
data = {
"temperature": current_temperature,
"humidity": current_humidity,
"led_status": led_status,
"timestamp": time.time()
}
send_response(client_socket, 200, "application/json", json.dumps(data))
elif path == '/control/toggle-led' and method == 'GET': # استفاده از GET برای سادگی، POST بهتر است
led_status = not led_status
led.value(led_status)
print(f"LED toggled to: {'ON' if led_status else 'OFF'}")
# پس از تغییر وضعیت، به صفحه اصلی ریدایرکت میکنیم
response_headers = "HTTP/1.1 302 Found\r\n"
response_headers += "Location: /\r\n"
response_headers += "Connection: close\r\n"
response_headers += "\r\n"
client_socket.sendall(response_headers.encode('utf-8'))
else:
send_response(client_socket, 404, "text/html", "404 Not Found
")
except OSError as e:
print(f"Error handling request: {e}")
finally:
client_socket.close()
# --- حلقه اصلی برنامه ---
def main():
global current_temperature, current_humidity, led_status, wlan_obj
ip_address, wlan_obj = connect_to_wifi(SSID, PASSWORD)
if not ip_address:
print("Could not connect to Wi-Fi. Exiting.")
return
server_socket = create_server_socket(80)
last_sensor_read_time = time.time()
while True:
try:
# بررسی وضعیت Wi-Fi و اتصال مجدد در صورت نیاز
if not wlan_obj.isconnected():
print("Wi-Fi disconnected. Reconnecting...")
ip_address, wlan_obj = connect_to_wifi(SSID, PASSWORD)
if not ip_address:
print("Reconnection failed. Retrying in 10 seconds...")
time.sleep(10)
continue # ادامه حلقه اصلی
# خواندن حسگر DHT هر 10 ثانیه یکبار
if time.time() - last_sensor_read_time > 10:
read_dht_sensor()
last_sensor_read_time = time.time()
# پذیرش اتصال کلاینت با timeout برای عدم مسدود شدن بینهایت
server_socket.settimeout(1.0) # 1 ثانیه timeout برای accept
client_conn, client_addr = server_socket.accept()
print(f"Connection from {client_addr}")
handle_request(client_conn)
except OSError as e:
if "timed out" in str(e) or "EAGAIN" in str(e): # خطای timeout در accept
# این طبیعی است اگر هیچ کلاینتی در زمان timeout وصل نشود
pass
else:
print(f"Main loop error: {e}")
# در صورت خطای جدی، سرور سوکت را دوباره ایجاد میکنیم
try:
server_socket.close()
except:
pass
server_socket = create_server_socket(80) # سعی در بازگشایی سرور
except KeyboardInterrupt:
print("Server stopped by user.")
break
except Exception as e:
print(f"Unexpected error in main loop: {e}")
server_socket.close()
wlan_obj.active(False) # غیرفعال کردن Wi-Fi هنگام خروج
if __name__ == '__main__':
main()
3. توضیح کد
- وارد کردن ماژولها: علاوه بر
networkوsocket،timeبرای تأخیرها و مدیریت زمان،jsonبرای API،Pinازmachineبرای کنترل GPIOها وdhtبرای حسگر دما/رطوبت وارد شدهاند. - تنظیمات سختافزاری: پینهای GPIO برای DHT و LED تعریف شدهاند.
- نمونهسازی: یک شیء
dht.DHT11و یک شیءPinبرای LED ایجاد شدهاند. read_dht_sensor(): این تابع حسگر DHT را میخواند و مقادیر دما و رطوبت جهانی را بهروزرسانی میکند. خطاهای خواندن حسگر نیز مدیریت شدهاند.get_index_html(): این تابع یک صفحه HTML پویا را تولید میکند که دادههای حسگر و وضعیت LED را نمایش میدهد. این صفحه شامل دکمهای برای تغییر وضعیت LED و یک لینک برای دسترسی به API دادهها است. از CSS داخلی (inline) برای ظاهری ساده و کارآمد استفاده شده است.handle_request():- مسیر
/: صفحه HTML اصلی را با دادههای فعلی حسگر و وضعیت LED برمیگرداند. - مسیر
/api/sensor-data: دادههای حسگر و وضعیت LED را به صورت JSON برمیگرداند. این یک API ساده برای برنامههای موبایل یا سایر سیستمها است. - مسیر
/control/toggle-led: وضعیتled_statusرا معکوس کرده و پین LED را تنظیم میکند. سپس با کد وضعیت302 Foundبه صفحه اصلی ریدایرکت (redirect) میکند تا کاربر پس از تغییر وضعیت، دوباره داشبورد را ببیند.
- مسیر
main():- ابتدا به Wi-Fi متصل میشود.
- سوکت سرور را ایجاد میکند.
- در یک حلقه بینهایت:
- هر 10 ثانیه حسگر را میخواند.
- وضعیت اتصال Wi-Fi را بررسی کرده و در صورت قطع شدن، مجدداً تلاش میکند.
- منتظر اتصالات ورودی میماند. از
server_socket.settimeout(1.0)استفاده شده تاaccept()بینهایت مسدود نشود و بتوانیم به طور منظم حسگر را بخوانیم و Wi-Fi را بررسی کنیم. - در صورت دریافت اتصال،
handle_request()را فراخوانی میکند. - مدیریت خطا برای اطمینان از پایداری سرور.
4. آزمایش و نتیجهگیری
این کد را در فایل main.py روی ESP32 خود آپلود کنید و ESP32 را ریست کنید. پس از اتصال به Wi-Fi، آدرس IP آن را در مرورگر وب خود وارد کنید. شما باید یک صفحه داشبورد ساده با دما و رطوبت فعلی و وضعیت LED را مشاهده کنید. با کلیک بر روی دکمه “Toggle LED”، وضعیت LED روی برد ESP32 تغییر خواهد کرد و صفحه بهروزرسانی میشود.
همچنین، میتوانید به آدرس http://[ESP32_IP_ADDRESS]/api/sensor-data بروید تا دادهها را به صورت JSON مشاهده کنید. این مثال، یک پایه محکم برای پروژههای IoT شما فراهم میکند. میتوانید آن را گسترش دهید تا حسگرهای بیشتری اضافه کنید، کنترلهای پیچیدهتری را پیادهسازی کنید یا حتی دادهها را در یک کارت SD (در صورت وجود) یا حافظه فلش داخلی ذخیره کنید.
این مثال کاربردی نه تنها نشان میدهد که چگونه یک وب سرور MicroPython را پیادهسازی کنید، بلکه همچنین نحوه تعامل آن با سختافزار واقعی (سنسورها و actuators) را به نمایش میگذارد، که جوهره اینترنت اشیا است.
بهینهسازی، اشکالزدایی و ملاحظات نگهداری
پس از پیادهسازی یک وب سرور MicroPython، گامهای بعدی شامل بهینهسازی عملکرد، اشکالزدایی مشکلات احتمالی و برنامهریزی برای نگهداری و ارتقاء آن است. این ملاحظات برای اطمینان از اینکه وب سرور شما پایدار، کارآمد و قابل اعتماد باقی میماند، حیاتی هستند.
1. بهینهسازی کد و عملکرد
منابع ESP32، به ویژه حافظه RAM، محدود هستند. بهینهسازی کد MicroPython برای اطمینان از استفاده کارآمد از این منابع بسیار مهم است:
- کاهش مصرف حافظه RAM:
- استفاده از رشتههای ثابت (String Literals) به جای Concatenation: به جای
"Hello " + name، ازf"Hello {name}"یا"Hello {}".format(name)استفاده کنید. عملیات Concatenation میتواند اشیاء رشتهای موقت زیادی ایجاد کند. - اجتناب از اشیاء بزرگ: از ایجاد لیستها، دیکشنریها یا رشتههای بسیار بزرگ که میتوانند حافظه را به سرعت پر کنند، خودداری کنید.
- استفاده از
bytearrayبه جایlistبرای دادههای باینری:bytearrayبرای ذخیره دادههای باینری کارآمدتر است. - بستن منابع: همیشه سوکتها، فایلها و پورتهای سریال را پس از استفاده ببندید تا حافظه را آزاد کنید.
- فراخوانی Garbage Collector: در صورت نیاز، میتوانید
gc.collect()را برای آزاد کردن حافظه استفاده نشده فراخوانی کنید، هرچند MicroPython به صورت خودکار این کار را انجام میدهد.
- استفاده از رشتههای ثابت (String Literals) به جای Concatenation: به جای
- بهینهسازی عملکرد (CPU Cycles):
- کاهش عملیات Blocking: تا حد امکان از عملیاتهایی که CPU را برای مدت طولانی مسدود میکنند (مانند
time.sleep()طولانی) اجتناب کنید. در صورت نیاز به تأخیرهای طولانی، از رویکردهای غیرمسدودکننده (non-blocking) یاuasyncioاستفاده کنید. - کاهش I/O دیسک: خواندن و نوشتن مداوم از/به حافظه فلش (filesystem) میتواند کند باشد. سعی کنید دادههایی که مکرراً استفاده میشوند را در RAM کش کنید (با دقت به محدودیت حافظه).
- اجتناب از محاسبات پیچیده: محاسبات ریاضی یا رشتهای پیچیده را به حداقل برسانید.
- کوتاه نگه داشتن پاسخهای HTTP: ارسال دادههای کمتر به کلاینت، هم پهنای باند و هم زمان پردازش سرور را کاهش میدهد.
- کاهش عملیات Blocking: تا حد امکان از عملیاتهایی که CPU را برای مدت طولانی مسدود میکنند (مانند
- استفاده از Firmware سفارشی: در صورت نیاز، میتوانید MicroPython firmware را خودتان کامپایل کنید و ماژولهای غیرضروری را حذف کرده یا ماژولهای خاصی را اضافه کنید تا اندازه firmware کاهش یابد و حافظه بیشتری آزاد شود.
2. اشکالزدایی (Debugging)
اشکالزدایی در محیطهای تعبیهشده میتواند چالشبرانگیز باشد. در MicroPython، چندین روش برای اشکالزدایی وجود دارد:
- استفاده از
print(): سادهترین و رایجترین روش، استفاده از دستورprint()برای نمایش وضعیت متغیرها و جریان برنامه در REPL (ترمینال سریال) است. - REPL تعاملی: هنگامی که ESP32 متصل است، میتوانید از REPL برای بررسی وضعیت متغیرها، فراخوانی توابع و تست بخشهای کوچک کد به صورت تعاملی استفاده کنید. این یک ابزار بسیار قدرتمند برای درک رفتار برنامه در زمان اجرا است.
- بلوکهای
try...except: استفاده از این بلوکها برای گرفتن خطاهای خاص و نمایش پیامهای خطا مفید است. این به شما کمک میکند تا منبع مشکل را سریعتر پیدا کنید. - Thonny IDE: Thonny دارای یک دیباگر داخلی است که به شما امکان میدهد کد را خط به خط اجرا کرده، نقاط توقف (breakpoints) تنظیم کنید و مقادیر متغیرها را مشاهده کنید. این ویژگی Thonny را به یک ابزار ضروری برای توسعه MicroPython تبدیل میکند.
- مشاهده logهای شبکه: استفاده از ابزارهایی مانند Wireshark برای مشاهده ترافیک شبکه میتواند به اشکالزدایی مشکلات ارتباطی بین کلاینت و سرور کمک کند.
3. ملاحظات نگهداری و ارتقاء
وب سرور شما پس از استقرار نیز نیاز به نگهداری و احتمالاً ارتقاء خواهد داشت:
- بهروزرسانی Firmware: Espressif و MicroPython به طور مداوم firmware را بهبود میبخشند. بهروزرسانی منظم به آخرین نسخه MicroPython میتواند به بهبود عملکرد، پایداری و رفع اشکالات کمک کند. این کار معمولاً با فلش کردن firmware جدید (همانند مرحله اول) انجام میشود.
- بهروزرسانی Over-The-Air (OTA): برای دستگاههایی که دسترسی فیزیکی به آنها دشوار است، قابلیت بهروزرسانی OTA بسیار ارزشمند است. MicroPython از یک ماژول
upipبرای مدیریت پکیجها پشتیبانی میکند و همچنین میتوانید مکانیزمهای بهروزرسانی کد خود را از طریق HTTP پیادهسازی کنید. با این حال، پیادهسازی یک سیستم OTA قوی و ایمن برای firmware کامل پیچیدگیهای خاص خود را دارد و نیاز به فضای فلش اضافی و مدیریت دقیق دارد. - ذخیرهسازی پیکربندی: تنظیمات مهم (مانند SSID و رمز عبور Wi-Fi، آستانههای حسگر) را در فایلهای جداگانه (مثلاً
config.json) روی سیستم فایل ESP32 ذخیره کنید تا بتوانید آنها را بدون نیاز به تغییر کد اصلی بهروزرسانی کنید. - ثبت وقایع (Logging): برای پروژههای طولانی مدت، ثبت وقایع در یک فایل روی فلش یا ارسال آنها به یک سرور log خارجی میتواند برای تشخیص مشکلات پس از استقرار بسیار مفید باشد.
- بازنگری کد: به طور منظم کد خود را برای بهبود خوانایی، کارایی و رفع بدهی فنی (technical debt) بازنگری کنید.
4. محدودیتهای مقیاسپذیری و زمان واقعی
مهم است که درک کنید ESP32، با وجود قدرت خود، همچنان یک میکروکنترلر است و محدودیتهایی دارد:
- مقیاسپذیری (Scalability): وب سرور MicroPython برای تعداد کمی از کلاینتها و درخواستهای سبک طراحی شده است. اگر نیاز به سرویسدهی به صدها یا هزاران کلاینت همزمان دارید، یا درخواستها بسیار سنگین هستند، باید به سمت راهحلهای قویتر مانند Raspberry Pi با یک فریمورک وب کامل (مانند Flask یا Django) یا سرورهای ابری (AWS IoT، Azure IoT) بروید.
- زمان واقعی (Real-time): MicroPython یک سیستم عامل زمان واقعی (RTOS) نیست. اگرچه ESP32 دارای هستههایی است که میتوانند کارهای زمان واقعی را انجام دهند، اما لایه MicroPython میتواند تأخیرهایی را اضافه کند. برای کاربردهای زمان واقعی بسیار حساس (مانند کنترل موتورهای دقیق)، ممکن است نیاز به استفاده از FreeRTOS (که ESP-IDF بر پایه آن است) یا زبانهای برنامهنویسی سطح پایینتر مانند C/C++ داشته باشید.
با در نظر گرفتن این ملاحظات، میتوانید وب سرور MicroPython خود را روی ESP32 به یک راهکار پایدار، کارآمد و قابل نگهداری برای پروژههای IoT خود تبدیل کنید. این پایان سفر نیست، بلکه شروعی برای اکتشافات و نوآوریهای بیشتر در دنیای هیجانانگیز اینترنت اشیا است.
نتیجهگیری: قدرت MicroPython و ESP32 در دستان شما
در طول این مقاله جامع، ما یک سفر هیجانانگیز را از مفهوم اولیه تا پیادهسازی عملی یک وب سرور کوچک با استفاده از MicroPython و ESP32 پشت سر گذاشتیم. دیدیم که چگونه این ترکیب قدرتمند میتواند به عنوان یک پلتفرم ایدهآل برای توسعه راهحلهای اینترنت اشیا (IoT) عمل کند، و به شما امکان میدهد تا با حداقل هزینه و زمان، دستگاههای فیزیکی خود را به شبکه متصل کرده و از راه دور کنترل و پایش کنید.
از آشنایی با ویژگیهای کلیدی MicroPython و ESP32 و آمادهسازی محیط توسعه گرفته تا درک معماری وب سرور و پروتکلهای شبکه مانند TCP/IP و HTTP، تمامی زیربناهای لازم برای شروع کار پوشش داده شد. سپس، با یک پیادهسازی گام به گام، اولین وب سرور “Hello, World!” خود را ساختیم و به تدریج آن را با قابلیتهایی مانند مسیریابی (routing)، ایجاد APIهای RESTful، و مدیریت خطاهای HTTP پیشرفتهتر کردیم. در نهایت، با ارائه یک مثال کاربردی برای پایش دما/رطوبت و کنترل یک LED، نشان دادیم که چگونه این دانش میتواند به پروژههای دنیای واقعی تبدیل شود.
ملاحظات مربوط به بهینهسازی، اشکالزدایی و نگهداری نیز مورد بحث قرار گرفتند تا اطمینان حاصل شود که وب سرور شما نه تنها کار میکند، بلکه پایدار، کارآمد و قابل اعتماد است. چالشهای مربوط به حافظه و CPU در میکروکنترلرها همیشه وجود دارد، اما MicroPython با رویکرد بهینهسازی شده خود، این چالشها را قابل مدیریت میسازد.
اکنون شما ابزارها، دانش و اعتماد به نفس لازم را برای شروع ساخت وب سرورهای تعبیهشده خود با MicroPython و ESP32 دارید. قدرت سادگی پایتون و قابلیتهای غنی ESP32 در دستان شماست تا ایدههای خلاقانه خود را در زمینه اتوماسیون خانگی، پایش صنعتی، رباتیک، و هر کاربرد دیگری که به اتصال و هوشمندی نیاز دارد، به واقعیت تبدیل کنید. هر خط کد پایتون که مینویسید، یک گام به سوی ایجاد یک دنیای متصلتر و هوشمندتر است.
با این پایه و اساس مستحکم، افقهای بیکرانی از امکانات در انتظار شماست. به یاد داشته باشید که بهترین راه برای یادگیری، انجام دادن است. پس شروع کنید به آزمایش، ساخت و نوآوری. جامعه MicroPython و ESP32 نیز همواره آماده حمایت و راهنمایی شماست. موفق باشید!
“تسلط به برنامهنویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT”
"تسلط به برنامهنویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT"
"با شرکت در این دوره جامع و کاربردی، به راحتی مهارتهای برنامهنویسی پایتون را از سطح مبتدی تا پیشرفته با کمک هوش مصنوعی ChatGPT بیاموزید. این دوره، با بیش از 6 ساعت محتوای آموزشی، شما را قادر میسازد تا به سرعت الگوریتمهای پیچیده را درک کرده و اپلیکیشنهای هوشمند ایجاد کنید. مناسب برای تمامی سطوح با زیرنویس فارسی حرفهای و امکان دانلود و تماشای آنلاین."
ویژگیهای کلیدی:
بدون نیاز به تجربه قبلی برنامهنویسی
زیرنویس فارسی با ترجمه حرفهای
۳۰ ٪ تخفیف ویژه برای دانشجویان و دانش آموزان