{"id":29125606,"url":"https://github.com/ishanoshada/microweb","last_synced_at":"2026-01-20T17:35:23.906Z","repository":{"id":301595198,"uuid":"1009778137","full_name":"Ishanoshada/Microweb","owner":"Ishanoshada","description":"A web server framework for MicroPython . Easily build and deploy web applications using MicroPython.","archived":false,"fork":false,"pushed_at":"2025-06-27T17:54:04.000Z","size":0,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-06-27T18:07:39.939Z","etag":null,"topics":["arduino","esp32","http","iot","micropython","python","web-server"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/microweb","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Ishanoshada.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-06-27T17:38:10.000Z","updated_at":"2025-06-27T17:54:07.000Z","dependencies_parsed_at":"2025-06-27T18:07:44.616Z","dependency_job_id":"ab269272-bf3f-4629-91ec-dcd2cc27bb07","html_url":"https://github.com/Ishanoshada/Microweb","commit_stats":null,"previous_names":["ishanoshada/microweb"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/Ishanoshada/Microweb","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ishanoshada%2FMicroweb","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ishanoshada%2FMicroweb/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ishanoshada%2FMicroweb/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ishanoshada%2FMicroweb/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Ishanoshada","download_url":"https://codeload.github.com/Ishanoshada/Microweb/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ishanoshada%2FMicroweb/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262553069,"owners_count":23327584,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["arduino","esp32","http","iot","micropython","python","web-server"],"created_at":"2025-06-29T22:01:49.267Z","updated_at":"2026-01-20T17:35:23.898Z","avatar_url":"https://github.com/Ishanoshada.png","language":"Python","readme":"# MicroWeb\n\nMicroWeb is a lightweight web server framework for MicroPython on the ESP32, designed for efficient development of web-based applications. It supports dynamic routing, Wi-Fi configuration (access point or station mode), query parameter and POST request handling, JSON responses, static file serving, and a powerful template engine with for loops and if conditionals. The package includes a robust CLI for flashing MicroPython, uploading files, and running custom applications on the ESP32.\n\n\n\n**Example: Minimal MicroWeb Server**\n\n```python\nfrom microweb import MicroWeb\n\napp = MicroWeb(ap={'ssid': 'MyWiFi', 'password': 'MyPassword'}, debug=True)\n\n@app.route('/')\ndef index(req):\n    return {\"message\": \"Welcome to MicroWeb API!\"}\n\n@app.route('/status')\ndef status(req):\n    return {\"status\": \"running\", \"message\": \"Server is up and running!\"}\n\n@app.route('/greet/\u003cname\u003e')\ndef greet(req, match):\n    name = match.group(1) if match else \"Anonymous\"\n    return {\"message\": f\"Hello, {name}!\", \"status\": \"success\"}\n\napp.run()\n```\n\n**Comparison: Raw MicroPython Web Server Example for ESP32**\n\nFor reference, here's how a basic web server looks using only MicroPython's built-in libraries on ESP32:\n\n```python\nimport network\nimport socket\nimport ure  # micro regex\n\n# Setup Wi-Fi Access Point\nap = network.WLAN(network.AP_IF)\nap.active(True)\nap.config(essid='ESP32-AP', password='12345678')\nprint(\"Access Point created with IP:\", ap.ifconfig()[0])\n\n# Start socket server\naddr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]\ns = socket.socket()\n.......................\n        .......................\n       \n\nprint('Listening on', addr)\n\n# Define route logic\ndef handle_request(path):\n    if path == \"/\":\n        return {\"message\": \"Welcome to MicroWeb API!\"}\n\n    elif path == \"/status\":\n        return {\"status\": \"running\", \"message\": \"Server is up and running!\"}\n\n    elif path.startswith(\"/greet/\"):\n        match = ure.match(r\"/greet/(.+)\", path)\n        .......................\n        .......................\n       \n\n    elif path == \"/greet\":\n        return {\"message\": \"Hello, Anonymous!\", \"status\": \"success\"}\n\n    else:\n        return {\"error\": \"Not found\", \"status\": 404}\n\n# Simple JSON response builder\ndef json_response(data, status_code=200):\n   .......................\n        .......................\n        ......................\n    response += ujson.dumps(data)\n    return response\n\n# Main loop\nwhile True:\n    try:\n        cl, addr = s.accept()\n        print('Client connected from', addr)\n        .......................\n        .......................\n        ......................\n        cl.send(response)\n    except Exception as e:\n        print(\"Error:\", e)\n    finally:\n        cl.close()\n\n```\n\nWith MicroWeb, you get routing, templates, JSON, static files, and more—making web development on ESP32 much easier compared to the raw socket approach above.\n\n\n\n## 📊 Code Size Comparison (Line Count)\n\n| Component                 | Raw MicroPython Server   | MicroWeb Server                 |\n| ------------------------- | ------------------------ | ------------------------------- |\n| 📡 Wi-Fi Setup            | 5 lines                  | 1 line                          |\n| 🔌 Socket Setup           | 5 lines                  | 0 lines (abstracted)            |\n| 🔁 Server Loop            | 15+ lines                | 0 lines (abstracted)            |\n| 📍 Route Definitions      | 20+ lines (manual logic) | 10 lines (with decorators)      |\n| 🧠 Path Parsing / Routing | 5+ lines (manual regex)  | 0 lines (handled by MicroWeb)   |\n| 🧾 JSON Response Builder  | 5 lines                  | 0 lines (handled automatically) |\n| 🔍 Logging / Debug Info   | 5 lines (print-based)    | 0 lines (with `debug=True`)     |\n| 🧱 Total Lines            | \\~55–60 lines            | \\~15 lines                      |\n\n---\n\n## Give us a ⭐️ if you find this project helpful!  \n\nIf you like this project, please consider giving it a star ⭐️ on GitHub. Your support motivates me to keep improving it!  \n\n---\n## Table of Contents\n\n- [Features](#features)\n- [Installation](#installation)\n- [Usage](#usage)\n    - [Creating an Example Application](#creating-an-example-application)\n    - [Flashing the ESP32](#flashing-the-esp32)\n    - [Running a Custom Application](#running-a-custom-application)\n- [Example Usage](#example-usage)\n    - [Example Projects](#example-projects)\n    - [Minimal Example (`tests/2/app.py`)](#minimal-example-tests2apppy)\n    - [Static Files and Templates Example (`tests/1/app.py`)](#static-files-and-templates-example-tests1apppy)\n    - [Portfolio Demo (`tests/portfolio/`)](#portfolio-demo-testsportfolio)\n    - [For Loop Example (`tests/for_loop`)](#for-loop-example-testsfor_loops)\n    - [External API and Template Example (`tests/request_send`)](#external-api-and-template-example-testsrequest_send)\n    - [Library and Model Upload Example (`tests/upload_lib/`)](#library-and-model-upload-example-testsupload_lib)\n- [Wi-Fi Configuration](#wi-fi-configuration)\n- [Accessing the Web Server](#accessing-the-web-server)\n- [CLI Tool Usage Examples](#cli-tool-usage-examples)\n- [How to Code with MicroWeb](#how-to-code-with-microweb)\n- [ Feature Updates ](#feature-updates)\n- [Project Structure](#project-structure)\n- [Troubleshooting](#troubleshooting)\n- [Contributing](#contributing)\n\n\n![example](/src/img.jpg)\n\n## Features\n\n- **Dynamic Routing**: Define routes like `@app.route('/welcome/\u003cname\u003e')` for flexible URL handling.\n- **Wi-Fi Configuration**: Configure Wi-Fi via constructor parameters, an `ap` dictionary, or a web interface, with settings saved to `config.json`.\n- **Query Parameters and POST Handling**: Support for URL query strings and form/JSON POST requests.\n- **JSON Responses**: Return JSON data with customizable HTTP status codes.\n- **Static File Serving**: Serve HTML, CSS, and other files from `static/`.\n- **CLI Tools**: Flash MicroPython, upload, and run scripts with validation and auto-detection.\n- **MicroPython Detection**: Verifies MicroPython before running scripts.\n- **Easy Cleanup**: Remove all files from the ESP32 home directory using `microweb remove --port COM10 --remove`—try this if you need to reset or clean up your device.\n\n![uml](/src/uml.svg)\n\n---\n## Installation\n\nYou can install MicroWeb using pip (for the CLI and development tools):\n\n```bash\npip install microweb\n```\n\nOr, to use the latest source code, clone the repository from GitHub:\n```bash\ngit clone https://github.com/ishanoshada/Microweb.git\ncd Microweb\npython -m venv venv\nsource venv/bin/activate  # On Windows use: venv\\Scripts\\activate\npip install .\n```\n\n\n\n## Give us a ⭐️ if you find this project helpful!  \n\nIf you like this project, please consider giving it a star ⭐️ on GitHub. Your support motivates me to keep improving it!  \n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://buymeacoffee.com/ishanoshada\"\u003e\n    \u003cimg src=\"https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png\" height=\"50\" alt=\"Buy Me a Coffee\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\n---\n\n\n## Usage\n\n### Creating an Example Application\nGenerate a sample MicroWeb application with a web server, template with `for` loops and conditionals, static CSS/JavaScript, and documentation:\n\n```bash\nmicroweb create --path example_app\n```\n\n- Creates a directory (default: `example_app`) containing `app.py`, `static/index.html`, `static/style.css`, `static/script.js`, and a `README.md` with usage instructions.\n- Option: `--path \u003cdirectory\u003e` to specify a custom directory name.\n\n### Flashing MicroPython and MicroWeb\nFlash MicroPython firmware and MicroWeb to your device:\n\n```bash\nmicroweb flash --port COM10\n```\n\n#### ⚙️ Options:\n\n* `--port COMx`\n  Specify the serial port to which your ESP device is connected.\n  Example: `--port COM5` (Windows), `--port /dev/ttyUSB0` (Linux/macOS)\n\n* `--erase`\n  Erase the entire flash memory before writing firmware. **Warning:** This will remove all existing data.\n\n* `--esp8266`\n  Flash ESP8266 firmware instead of the default ESP32 firmware.\n\n* `--firmware firmware.bin`\n  Use a custom `.bin` firmware file. Overrides the default firmware path.\n\n* `--baud 460800`\n  Set a custom baud rate for flashing. Defaults to `460800`, but can be changed to `115200`, `921600`, etc.\n\n* `--full-flash`\n  Flash the firmware to address `0x0` (useful for full `.bin` images with bootloader). Default flashing address is `0x1000`.\n\n---\n\n### 📝 Examples:\n\n```bash\n# Flash default ESP32 firmware and MicroWeb files\nmicroweb flash --port COM10\n\n# Flash custom firmware with erase\nmicroweb flash --port COM10 --erase --firmware ./firmware/ESP32-C3-20250722-v1.25.1.bin\n\n# Flash at 921600 baud and use full-flash mode (offset 0x0)\nmicroweb flash --port COM10 --baud 921600 --full-flash --firmware ./firmware/full_image.bin\n\n# Flash ESP8266 device with default firmware\nmicroweb flash --port COM10 --esp8266\n```\n\n\n### Running a Custom Application\nUpload and run a MicroPython script:\n\n```bash\nmicroweb run app.py --port COM10\n```\n\n- Validates your `.py` file and checks for MicroPython on the device.\n- Uploads and executes the script.\n- Checks and uploads only changed files by default.\n- Prompts to run `microweb flash` if MicroPython is not detected.\n- After running `app.run()`, the ESP32 will host a Wi-Fi access point (AP) if it cannot connect to a configured network. Connect to this AP and access the web server at `http://192.168.4.1` (typical IP in AP mode).\n\n### Listing Files on the Device\nList files on the MicroPython device's filesystem:\n\n```bash\nmicroweb ls --port COM10\n```\n\n- Displays all files and their sizes in the device's home directory.\n- Requires MicroPython to be installed on the device.\n\n---\n\n### View real-time logs:\n   ```bash\n   mpremote connect COM10 run tests/request_send/app.py\n   ```\n\n\n### Boot Script Management\n\nYou can configure your ESP32 to automatically start your application after any power cycle or reset by managing the `boot.py` file:\n\n- **Add boot script (auto-run on power-up):**\n    ```bash\n    microweb run app.py --add-boot --port COM10\n    ```\n    This uploads a `boot.py` that will auto-run your app every time the ESP32 is powered on or reset. After upload, you can disconnect the ESP32 from your computer and power it from any source; the server will start automatically.\n\n- **Remove boot script:**\n    ```bash\n    microweb run app.py --remove-boot --port COM10\n    ```\n    This removes the `boot.py` file, so your app will not auto-run on power-up.\n\n---\n\n\n**Checking the IP Address**:\n- When running `microweb run app.py --port COM10`, the CLI displays the ESP32’s IP address (e.g., `🌐 🌐 Visit: http://192.168.4.1 or http://192.168.8.102/`).\n- Alternatively, run `mpremote connect COM10 exec \"import app; print(app.app.get_ip())\"` to retrieve the IP.\n- Check your router’s admin panel (e.g., `http://192.168.8.1`) for connected devices to find the ESP32’s IP.\n- To view real-time logs, run:\n  ```bash\n  mpremote connect COM10 run app.py\n  ```\n  This prints logs like `Connecting to WiFi SSID: Dialog 4G 0F8`, `Connected. IP: 192.168.8.102`.\n\n## Example Usage\n\nAh, got it! You want to **mention the `Microweb-Examples` repository in the `README.md` of your main [`Microweb`](https://github.com/Ishanoshada/Microweb) repository**.\n\nHere’s what you can add to the bottom (or a \"See Also\" section) of your `Microweb/README.md`:\n\n---\n\n###  Example Projects\n\nLooking for real-world examples using MicroWeb on ESP32?\n\n👉 Check out the companion repository:\n**[MicroWeb-Examples](https://github.com/Ishanoshada/Microweb-Examples)** – A collection of MicroPython-based IoT projects using the MicroWeb framework, featuring:\n\n* 🔦 Laser Diode Control\n* 📡 Microwave Radar Motion Detection\n* 📏 Ultrasonic Distance Measuring\n\nExplore how MicroWeb is used in practical applications with minimal setup!\n\n---\n\n\n### Minimal Example (`tests/2/app.py`)\n\n```python\nfrom microweb import MicroWeb, Response\n\n#from dotenv import load_dotenv , get_env\n\n# env_vars = load_dotenv()\n# ssid = get_env('SSID', 'MyESP32', env_vars)\n# password = get_env('PASSWORD', 'mypassword', env_vars)\n# db_uri = get_env('DB_URI', None, env_vars)\n\napp = MicroWeb(debug=True, ap={'ssid': 'MyWiFi', 'password': 'MyPassword'})\n\n# app = MicroWeb(\n#     ap={\"ssid\": \"Dialog 4G 0F8\", \"password\": \"youpassword\"},  # Change to your router\n#     debug=True,\n#     mode=\"wifi\"  # Connect as client to your router\n# )\n\n# Uncomment to stop Wi-Fi access point\n# app.stop_wifi()  # Uncomment to stop Wi-Fi access point\n## app.start_wifi()  # Uncomment to start Wi-Fi access point after stop\n\n@app.route(\"/\")\ndef home(request):\n    return Response(\"Hello from MicroWeb!\", content_type=\"text/plain\")\n\n@app.route(\"/json\")\ndef json_example(request):\n    return {\"message\": \"This is JSON\"}\n\n@app.route(\"/greet/\u003cname\u003e\")\ndef greet(req, match):\n    name = match.group(1) if match else \"Anonymous\"\n    return {\"message\": f\"Hello, {name}!\", \"status\": \"success\"}\n\n@app.route(\"/status\")\ndef status(request):\n    return {\"status\": \"OK\"}\n\n@app.route(\"/headers\")\ndef headers_example(request):\n    resp = Response(\"Custom header set!\", content_type=\"text/plain\")\n    resp.headers[\"X-Custom-Header\"] = \"Value\"\n    return resp\n\n\napp.run()\n```\n\n\n**Example Template (`tests/for_loops/static/index.html`)**:\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n\u003chead\u003e\n    \u003ctitle\u003eProjects\u003c/title\u003e\n    \u003clink rel=\"stylesheet\" href=\"/style.css\"\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n    \u003ch1\u003eProjects\u003c/h1\u003e\n    {% greeting %}\n    {{ if projects }}\n        \u003cul\u003e\n        {{ for project in projects }}\n            \u003cli\u003e\n                \u003ch2\u003e{{ project.title }}\u003c/h2\u003e\n                \u003cp\u003e{{ project.description }}\u003c/p\u003e\n            \u003c/li\u003e\n        {{ endfor }}\n        \u003c/ul\u003e\n    {{ else }}\n        \u003cp\u003eNo projects found\u003c/p\u003e\n    {{ endif }}\n\u003c/body\u003e\n\u003c/html\u003e\n```\n---\n\n### Static Files and Templates Example (`tests/1/app.py`)\n\n```python\nimport wifi\nfrom microweb import MicroWeb\n\napp = MicroWeb(debug=True, ap={\"ssid\": \"MyESP32\", \"password\": \"mypassword\"})\n# app = MicroWeb(\n#     ap={\"ssid\": \"Dialog 4G 0F8\", \"password\": \"youpassword\"},  # Change to your router\n#     debug=True,\n#     mode=\"wifi\"  # Connect as client to your router\n# )\n\n@app.route('/')\ndef home(req):\n    return app.render_template('index.html', message=\"Welcome to MicroWeb API!\")\n\n@app.route('/api/status', methods=['GET'])\ndef status(req):\n    return app.json_response({\"status\": \"running\", \"ip\": wifi.get_ip()})\n\n@app.route('/api/echo', methods=['POST'])\ndef echo(req):\n    data = req.form\n    return app.json_response({\"received\": data})\n\n\n@app.route('/api/methods', methods=['GET', 'POST'])\ndef methods(req):\n    if req.method == 'GET':\n        return app.json_response({\"method\": \"GET\", \"message\": \"This is a GET request\"})\n    elif req.method == 'POST':\n        data = req.form\n        return app.json_response({\"method\": \"POST\", \"received\": data})\n\n\n@app.route('/submit', methods=['GET', 'POST'])\ndef submit_form(req):\n    if req.method == 'POST':\n        return app.render_template('result.html', data=str(req.form), method=\"POST\")\n    else:\n        return app.render_template('form.html')\n\napp.add_static('/style.css', 'style.css')\napp.run()\n```\n\n#### Example Static Files (`tests/1/static/`)\n\n- `index.html`: Main page with API demo and buttons.\n- `form.html`: Simple HTML form for POST testing.\n- `result.html`: Displays submitted form data.\n- `style.css`: Enhanced styling for the test app.\n\n---\n\n### Portfolio Demo (`tests/portfolio/`)\n\nThe `tests/portfolio/` directory contains a full-featured portfolio web app built with MicroWeb, demonstrating:\n\n- Multi-page routing (`/`, `/about`, `/projects`, `/contact`)\n- Dynamic template rendering with variables\n- Static assets (CSS, JS, images)\n- API endpoints (e.g., `/api/info` returns JSON)\n- Responsive, animated UI using HTML/CSS/JS\n- Example of serving a personal portfolio from an ESP32\n\nSee `tests/portfolio/app.py` and the `static/` folder for a complete, ready-to-deploy example.\n\n### **For Loop Example (`tests/for_loops`)**\n\nThis example demonstrates the use of MicroWeb’s template engine with a `for` loop to display a list of projects dynamically. It includes two routes: one with a populated project list and one with an empty list to showcase conditional rendering using `if`, `else`, and `for` constructs in the template.\n\n**Example Code (`tests/for_loops/app.py`)**:\n\n```python\nimport wifi\nfrom microweb import MicroWeb\n\n# Initialize MicroWeb with debug mode and access point\napp = MicroWeb(debug=True, ap={'ssid': 'MyESP32', 'password': 'mypassword'})\n# app = MicroWeb(\n#     ap={\"ssid\": \"Dialog 4G 0F8\", \"password\": \"youpassword\"},  # Change to your router\n#     debug=True,\n#     mode=\"wifi\"  # Connect as client to your router\n# )\n\n@app.route('/')\ndef home(req):\n    # Test case 1: Populated projects list\n    projects = [\n        {'title': 'Smart Home Dashboard', 'description': 'A dashboard for home automation'},\n        {'title': 'Weather Station', 'description': 'Real-time weather monitoring'},\n        {'title': 'IoT Sensor Network', 'description': 'Network for IoT sensors'}\n    ]\n    return app.render_template('index.html', greeting='Welcome to MicroWeb!', projects=projects)\n\n@app.route('/empty')\ndef empty(req):\n    # Test case 2: Empty projects list\n    projects = []\n    return app.render_template('index.html', greeting='No Projects Available', projects=projects)\n\napp.add_static('/style.css', 'style.css')\n\n\napp.run()\n```\n\n\n**Explanation**:\n- **Application Code (`app.py`)**:\n  - The `/` route passes a list of projects with `title` and `description` fields, along with a `greeting` variable.\n  - The `/empty` route passes an empty `projects` list to test the `else` branch of the template.\n  - `app.add_static('/style.css', 'style.css')` serves the CSS file for styling the template.\n- **Template (`index.html`)**:\n  - `{% greeting %}` renders the greeting message (e.g., \"Welcome to MicroWeb!\").\n  - `{{ if projects }}` checks if the `projects` list is non-empty.\n  - `{{ for project in projects }}` iterates over the `projects` list, rendering each project’s `title` and `description` in an `\u003cli\u003e` element.\n  - `{{ else }}` displays \"No projects found\" if the `projects` list is empty.\n  - The `\u003clink rel=\"stylesheet\" href=\"/style.css\"\u003e` tag applies styles from `style.css`.\n- **Static File (`style.css`)**:\n  - Provides a clean, modern look with a light background, styled list items, and shadowed boxes for each project.\n- **Testing**:\n  - Upload files: `microweb run app.py --static static/ --port COM10`.\n  - Access `http://192.168.4.1/` to see the project list Zornlist with descriptions.\n  - Access `http://192.168.4.1/empty` to see the \"No projects found\" message.\n  - Use `curl http://192.168.4.1/` or a browser to verify the output.\n\n\n---\n\n### *External API and Template Example (`tests/request_send/`)*\n\nThis example, located in the `tests/request_send/` folder, demonstrates a MicroWeb application that:\n- Connects to a Wi-Fi network (`mode='wifi'`) using the `Dialog 4G 0F8` network.\n- Fetches data from an external API using `urequests` (with a fallback for HTTPS limitations).\n- Renders a dynamic template (`index.html`) with a project list.\n- Serves static files (`style.css`, `script.js`) for styling and client-side scripting.\n- Includes routes for status checks, POST requests, and an offline fallback.\n\n**Application Code (`tests/request_send/app.py`)**:\n\n```python\nfrom microweb import MicroWeb\nimport urequests\n\n# This example demonstrates how to create a simple web server using MicroWeb on ESP32\n# It includes routes for home, status check, POST handling, and live site mirroring.\n# Make sure you have the MicroWeb library installed on your ESP32\n# To run this, save it as app.py on your ESP32 and ensure you have the necessary files in the same directory:\n# - index.html (for home page)\n# - mirror.html (for offline fallback)\n# - style.css (for styling)\n# - script.js (for client-side scripting)\n\napp = MicroWeb(\n    ap={\"ssid\": \"Dialog 4G 0F8\", \"password\": \"youpassword\"},  # Change to your router\n    debug=True,\n    mode=\"wifi\"  # Connect as client to your router\n)\n\n# Home route\n@app.route('/')\ndef home(req):\n    return app.render_template('index.html', greeting='Welcome to ESP32 MicroWeb!', projects=[\n        {'title': 'Request Example', 'description': 'Live fetch from JSONPlaceholder /request'},\n        {'title': 'Status Check', 'description': 'Check server status'},\n        {'title': 'POST Example', 'description': 'Send POST to /post-test'}\n    ])\n\n# /status route\n@app.route('/status', methods=['GET'])\ndef status(req):\n    return app.json_response({\n        \"status\": \"running\",\n        \"ip\": app.get_ip(),\n        \"mode\": \"wifi\"\n    })\n\n# POST test\n@app.route('/post-test', methods=['POST'])\ndef post_test(req):\n    return app.json_response({\n        \"received\": req.form,\n        \"note\": \"You sent this via POST\"\n    })\n\n# Live mirror of your site\n@app.route('/request')\ndef mirror(req):\n    try:\n        res = urequests.get(\"https://jsonplaceholder.typicode.com/posts/1\")  # Only works if HTTPS supported\n        html = res.text\n        res.close()\n        return app.html_response(html)\n    except Exception as e:\n        return app.html_response(f\"\"\"\n            \u003ch1\u003eFailed to fetch live site\u003c/h1\u003e\n            \u003cp\u003e{str(e)}\u003c/p\u003e\n            \u003cp\u003eESP32 doesn't support HTTPS by default. Use a proxy or offline HTML instead.\u003c/p\u003e\n        \"\"\")\n\n# Optional fallback if you save HTML locally\n@app.route('/offline')\ndef offline(req):\n    return app.render_template(\"mirror.html\")\n\n# Static files\napp.add_static('/style.css', 'style.css')\napp.add_static('/script.js', 'script.js')\n\napp.run()\n```\n\n**Template (`tests/request_send/static/index.html`)**:\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n\u003chead\u003e\n    \u003ctitle\u003eMicroWeb\u003c/title\u003e\n    \u003clink rel=\"stylesheet\" href=\"/style.css\"\u003e\n    \u003cscript src=\"/script.js\"\u003e\u003c/script\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n    \u003ch1\u003e{% greeting %}\u003c/h1\u003e\n    {{ if projects }}\n        \u003cul\u003e\n        {{ for project in projects }}\n            \u003cli\u003e{{ project.title }}: {{ project.description }}\u003c/li\u003e\n        {{ endfor }}\n        \u003c/ul\u003e\n    {{ else }}\n        \u003cp\u003eNo projects found.\u003c/p\u003e\n    {{ endif }}\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\n**Static CSS (`tests/request_send/static/style.css`)**:\n\n```css\nbody {\n    font-family: Arial, sans-serif;\n    padding: 20px;\n    background: #f7f7f7;\n}\nh1 {\n    color: #007BFF;\n}\n.....\n```\n\n**Static JavaScript (`tests/request_send/static/script.js`)**:\n\n```javascript\ndocument.addEventListener(\"DOMContentLoaded\", function () {\n    console.log(\"📡 MicroWeb loaded from ESP32!\");\n});\n```\n\n**Explanation**:\n- **Wi-Fi Configuration**: Uses `mode='wifi'` to connect to the `Dialog 4G 0F8` network (IP: `192.168.8.102`). If the connection fails, it falls back to an access point (IP: `192.168.4.1`).\n- **Routes**:\n  - `/`: Renders `index.html` with a greeting and a list of projects using a `for` loop and `if` conditional.\n  - `/status`: Returns a JSON response with the server’s IP and status.\n  - `/post-test`: Handles POST requests, returning the submitted form data as JSON.\n  - `/request`: Attempts to fetch data from `https://jsonplaceholder.typicode.com/posts/1` using `urequests.get()`. If HTTPS fails (common on ESP32), it returns an error message.\n  - `/offline`: Renders a local `mirror.html` file as a fallback (ensure `mirror.html` exists in `static/`).\n- **Static Files**: Serves `style.css` for styling and `script.js` for client-side scripting (logs a message to the browser console).\n- **Template**: `index.html` displays a greeting and a styled list of projects, with a fallback message if no projects are provided.\n- **HTTPS Limitation**: The `/request` route may fail due to MicroPython’s limited HTTPS support on ESP32. The error message suggests using a proxy or offline HTML (`/offline`).\n\n**Running**:\n1. Place `app.py`, `index.html`, `style.css`, `script.js`, and `mirror.html` (if used) in `tests/request_send/` (with `index.html`, `style.css`, `script.js` in `tests/request_send/static/`).\n2. Upload and run:\n   ```bash\n   microweb run tests/request_send/app.py --port COM10 --static tests/request_send/static/\n   ```\n3. Access the server at `http://192.168.8.102` (or `http://192.168.4.1` if Wi-Fi connection fails).\n4. View real-time logs:\n   ```bash\n   mpremote connect COM10 run tests/request_send/app.py\n   ```\n   Expected logs:\n   ```\n   Connecting to WiFi SSID: Dialog 4G 0F8\n   Connected. IP: 192.168.8.102\n   MicroWeb running on http://0.0.0.0:80\n   ```\n\n**Testing**:\n- **Home Page**: `http://192.168.8.102/` displays a styled project list:\n  ```html\n  \u003ch1\u003eWelcome to ESP32 MicroWeb!\u003c/h1\u003e\n  \u003cul\u003e\n    \u003cli\u003eRequest Example: Live fetch from JSONPlaceholder /request\u003c/li\u003e\n    \u003cli\u003eStatus Check: Check server status\u003c/li\u003e\n    \u003cli\u003ePOST Example: Send POST to /post-test\u003c/li\u003e\n  \u003c/ul\u003e\n  ```\n- **Status**: Test with `curl`:\n  ```bash\n  curl http://192.168.8.102/status\n  ```\n  Expected:\n  ```json\n  {\"status\": \"running\", \"ip\": \"192.168.8.102\", \"mode\": \"wifi\"}\n  ```\n- **POST**: Test with `curl`:\n  ```bash\n  curl -X POST -d \"key=value\" http://192.168.8.102/post-test\n  ```\n  Expected:\n  ```json\n  {\"received\": {\"key\": \"value\"}, \"note\": \"You sent this via POST\"}\n  ```\n- **Fetch**: Test `/request`:\n  ```bash\n  curl http://192.168.8.102/request\n  ```\n  - If HTTPS works: Returns JSON data from `jsonplaceholder.typicode.com`.\n  - If HTTPS fails: Returns an HTML error message.\n- **Offline**: Test `/offline` (requires `mirror.html`):\n  ```bash\n  curl http://192.168.8.102/offline\n  ```\n\n**Notes**:\n- Ensure `urequests` is available on the ESP32 (bundled with MicroWeb or uploaded separately).\n- If HTTPS fails on `/request`, create a `mirror.html` file in `static/` for the `/offline` route.\n- Verify files with:\n  ```bash\n  microweb ls --port COM10\n  ```\n- Force re-upload if needed:\n  ```bash\n  microweb run tests/request_send/app.py --port COM10 --static tests/request_send/static/ --force\n  ```\n\n---\n\n\n### Library and Model Upload Example (`tests/upload_lib/`)\n\nThis example, located in the `tests/upload_lib/` folder, demonstrates how to use MicroWeb’s `lib_add` method to include external libraries and model files, and how to handle both GET and POST requests with a dynamic template. It includes models for managing users and products, a utility library, and a form-based interface for adding data.\n\n**Application Code (`tests/upload_lib/app.py`)**:\n\n```python\nfrom microweb import MicroWeb\nimport some_lib\nfrom users import User\nfrom products import Product\n\n# Initialize MicroWeb with debug mode and Wi-Fi access point\napp = MicroWeb(debug=True, ap={\"ssid\": \"TestESP32\", \"password\": \"test1234\"})\n\n# Register library and model files\napp.lib_add(\"some_lib.py\")\napp.lib_add(\"/models/users.py\")\napp.lib_add(\"/models/products.py\")\n\n# Initialize models\nusers = User()\nproducts = Product()\n\n@app.route('/')\ndef home(req):\n    return app.render_template(\n        'index.html',\n        greeting=some_lib.say_hello(),\n        timestamp=some_lib.get_timestamp(),\n        users=users.get_all(),\n        products=products.get_all()\n    )\n\n@app.route('/user/\u003cid\u003e')\ndef get_user(req, match):\n    user_id = int(match.group(1)) if match else 0\n    user = users.get_by_id(user_id)\n    if user:\n        .................................\n        ....................\napp.run()\n```\n\n**Library File (`tests/upload_lib/some_lib.py`)**:\n\n```python\ndef say_hello():\n    return \"Hello from some_lib!\"\n\ndef get_timestamp():\n    import time\n    return time.time()\n```\n\n**Model File (`tests/upload_lib/models/users.py`)**:\n\n```python\nclass User:\n    def __init__(self):\n        self.users = [\n            {\"id\": 1, \"name\": \"Alice\", \"email\": \"alice@example.com\"},\n            {\"id\": 2, \"name\": \"Bob\", \"email\": \"bob@example.com\"}\n        ]\n    \n    def get_all(self):\n        return self.users\n   ......................\n```\n\n**Model File (`tests/upload_lib/models/products.py`)**:\n\n```python\nclass Product:\n    def __init__(self):\n        self.products = [\n            {\"id\": 1, \"name\": \"Laptop\", \"price\": 999.99},\n            {\"id\": 2, \"name\": \"Phone\", \"price\": 499.99}\n        ]\n    \n    def get_all(self):\n        return self.products\n  .............................\n  ...........................\n```\n\n**Template (`tests/upload_lib/static/index.html`)**:\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n\u003chead\u003e\n    \u003ctitle\u003eTest App\u003c/title\u003e\n    \u003clink rel=\"stylesheet\" href=\"/style.css\"\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n    \u003ch1\u003e{{ greeting }}\u003c/h1\u003e\n    \u003cp\u003eTimestamp: {{ timestamp }}\u003c/p\u003e\n    \n    \u003ch2\u003eAdd User\u003c/h2\u003e\n   ................\n   ................\n   ..........\n    \n    \u003ch2\u003eUsers\u003c/h2\u003e\n    {{ if users }}\n        \u003cul\u003e\n        {{ for user in users }}\n            \u003cli\u003e{{ user.name }} ({{ user.email }}) - ID: {{ user.id }}\u003c/li\u003e\n        {{ endfor }}\n        \u003c/ul\u003e\n    {{ else }}\n        \u003cp\u003eNo users found\u003c/p\u003e\n    {{ endif }}\n    \n    \u003ch2\u003eProducts\u003c/h2\u003e\n    {{ if products }}\n        \u003cul\u003e\n        {{ for product in products }}\n            \u003cli\u003e{{ product.name }} - ${{ product.price }} - ID: {{ product.id }}\u003c/li\u003e\n        {{ endfor }}\n        \u003c/ul\u003e\n    {{ else }}\n        \u003cp\u003eNo products found\u003c/p\u003e\n    {{ endif }}\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\n**Static CSS (`tests/upload_lib/static/style.css`)**:\n\n```css\nbody {\n    font-family: Arial, sans-serif;\n    margin: 20px;\n    .............\n```\n\n**Explanation**:\n- **Application Code (`app.py`)**:\n  - Imports `User` and `Product` directly from `users` and `products` modules, reflecting their placement in the root directory (`tests/upload_lib/`).\n  - Uses `app.lib_add()` to register `some_lib.py`, `users.py`, and `products.py` for upload to the ESP32.\n  - The `/` route renders `index.html` with data from `some_lib` (greeting, timestamp) and the `User` and `Product` models.\n  - Routes `/user/\u003cid\u003e` and `/product/\u003cid\u003e` handle GET requests, returning JSON for specific users or products.\n  - Routes `/add_user` and `/add_product` handle POST requests from forms, adding new users or products and returning JSON responses.\n  - `app.add_static('/style.css', 'style.css')` serves the CSS file for styling.\n- **Library File (`some_lib.py`)**:\n  - Provides utility functions `say_hello()` and `get_timestamp()` used in the `home` route.\n  - Uploaded to `lib/some_lib.py` on the ESP32 for organized imports.\n- **Model Files (`users.py`, `products.py`)**:\n  - Define `User` and `Product` classes with in-memory data storage and methods for retrieving and adding data.\n  - Uploaded to the ESP32 root directory to match the direct imports (`from users import User`, `from products import Product`).\n- **Template (`index.html`)**:\n  - Displays the greeting and timestamp from `some_lib`.\n  - Includes forms for adding users (POST to `/add_user`) and products (POST to `/add_product`).\n  - Lists users and products using `for` loops and `if` conditionals, with a fallback message if lists are empty.\n  - Links to `style.css` for styling.\n- **Static File (`style.css`)**:\n  - Provides a clean, modern look with styled forms, lists, and buttons.\n- **Directory Structure**:\n  - Files are organized as:\n    ```\n    tests/upload_lib/\n    ├── app.py\n    ├── some_lib.py\n    ├── /models/users.py\n    ├── /models/products.py\n    ├── static/\n    │   ├── index.html\n    │   ├── style.css\n    ```\n\n**Running**:\n1. Place `app.py`, `some_lib.py`, `users.py`, `products.py`, and the `static/` folder with `index.html` and `style.css` in `tests/upload_lib/`.\n2. Flash MicroPython (if not already done):\n   ```bash\n   microweb flash --port COM10\n   ```\n3. Upload and run the application:\n   ```bash\n   microweb run tests/upload_lib/app.py --port COM10 --static tests/upload_lib/static/\n   ```\n4. Connect to the Wi-Fi access point:\n   - SSID: `TestESP32`\n   - Password: `test1234`\n   - Access the server at `http://192.168.4.1/`.\n5. View real-time logs:\n   ```bash\n   mpremote connect COM10 run tests/upload_lib/app.py\n   ```\n   Expected logs:\n   ```\n   MicroWeb running on http://0.0.0.0:80\n   ```\n\n\n### Wi-Fi Configuration\n\nConfigure Wi-Fi via:\n\n- **Station Mode (Connect to Existing Network)**:\n  ```python\n  MicroWeb(mode='wifi', ap={'ssid': 'MyWiFi', 'password': 'MyPassword'})\n  ```\n  - Connects the ESP32 to an existing Wi-Fi network (e.g., your router) using the provided SSID and password.\n  - The ESP32 will be assigned an IP address by the network (e.g., `192.168.8.102`). Access the web server at `http://\u003cESP32-IP\u003e/` (check the IP in the CLI output or your router’s admin panel).\n  - If the connection fails, the ESP32 falls back to creating an access point (default: SSID `ESP32-MicroWeb`, password `12345678`, IP `192.168.4.1`).\n\n- **Access Point Mode**:\n  ```python\n  MicroWeb(mode='ap', ap={'ssid': 'MyESP32', 'password': 'mypassword'})\n  ```\n  - Creates a Wi-Fi access point hosted by the ESP32. Connect to this network and access the server at `http://192.168.4.1`.\n\n- **Configuration File**:\n  If no credentials are provided, MicroWeb loads settings from `config.json` on the ESP32. Use the web interface (at `http://\u003cESP32-IP\u003e/`) to update Wi-Fi settings if supported.\n\n\n\n---\n\n\n### Troubleshooting\n- **Wrong IP (`192.168.4.1`)**: If the CLI shows `192.168.4.1` instead of `192.168.8.102`, the ESP32 failed to connect to `Dialog 4G ANY`. Verify the SSID/password or check the router’s connected devices.\n- **No Logs**: Ensure `mpremote` is installed (`pip install mpremote`) and `COM10` is correct (`mpremote` to list ports).\n- **File Issues**: Verify `app.py` and `wifi.py` are uploaded:\n  ```bash\n  microweb ls --port COM10\n  ```\n\n\n---\n\n## Accessing the Web Server\n\n- Connect to the ESP32’s Wi-Fi (default: `ESP32-MicroWeb`/`12345678` in AP mode or the configured network).\n- Access `http://\u003cESP32-IP\u003e/` (e.g., `http://192.168.4.1` in AP mode).\n- Use the control panel to update Wi-Fi, test routes, or submit forms.\n\n---\n\n## CLI Tool Usage Examples\n\nThe following table summarizes common `microweb` CLI commands. See also: #changes.\n\n| Command Example                              | Description                                               |\n|----------------------------------------------|-----------------------------------------------------------|\n| `microweb create --path example_app`         | Create an example MicroWeb app with `app.py`, `static/index.html`, and `README.md`. |\n| `microweb examples`                          | Show example commands for using the MicroWeb CLI.          |\n| `microweb flash --port COM10`                | Flash MicroPython firmware and upload MicroWeb files.      |\n| `microweb flash --port COM10 --erase`        | Erase ESP32 flash before installing MicroPython.           |\n| `microweb run app.py --port COM10`           | Upload and run a custom MicroPython script.                |\n| `microweb run app.py --check-only`           | Check static/template dependencies without uploading.      |\n| `microweb run app.py --force`                | Force upload all files, even if unchanged.                 |\n| `microweb run app.py --add-boot`             | Upload a `boot.py` to auto-run your app on boot.           |\n| `microweb run app.py --remove-boot`          | Remove `boot.py` from the ESP32.                           |\n| `microweb run app.py --static static/`       | Specify a custom static files folder.                      |\n| `microweb run app.py --no-stop`              | Do not reset ESP32 before running the app.                 |\n| `microweb run app.py --timeout 600`          | Set a custom timeout (in seconds) for app execution.       |\n| `microweb ls --port COM10`                   | List files and their sizes on the ESP32 filesystem.        |\n| `microweb remove --port COM10`               | List files on ESP32 (requires `--remove` to actually delete). |\n| `microweb remove --port COM10 --remove`      | Remove all files in ESP32 home directory.                  |\n\n**Notes:**\n- `microweb flash` auto-detects the ESP32 port if not specified.\n- `microweb run` validates dependencies, uploads only changed files by default, and can manage static/template files.\n- Use `--help` with any command for more options and details.\n\nFor more details, run `microweb --help`.\n\n\n\n---\n\n## How to Code with MicroWeb\n\nThis section guides you through writing MicroWeb applications for MicroPython on ESP32. MicroWeb simplifies web development with features like dynamic routing, template rendering, static file serving, JSON responses, and Wi-Fi configuration. Below, we explain the key components of coding with MicroWeb, with examples to help you get started.\n\n### **1. Setting Up the MicroWeb Application**\nTo start, import the `MicroWeb` class and initialize the app. You can configure debugging and Wi-Fi settings (access point or station mode) in the constructor.\n\n```python\nfrom microweb import MicroWeb\n\n# Initialize MicroWeb with debug mode and access point (AP) settings\napp = MicroWeb(debug=True, ap={'ssid': 'MyESP32', 'password': 'mypassword'})\n\n\n# Uncomment to stop Wi-Fi access point\n# app.stop_wifi()  # Uncomment to stop Wi-Fi access point\n## app.start_wifi()  # Uncomment to start Wi-Fi access point after stop\n\n```\n\n**Explanation**:\n- `debug=True`: Enables detailed logging for troubleshooting, useful during development.\n- `ap={'ssid': ..., 'password': ...}`: Configures the ESP32 to create a Wi-Fi access point if it cannot connect to a network. Alternatively, use `internet={'ssid': ..., 'password': ...}` for station mode to connect to an existing Wi-Fi network.\n- If no Wi-Fi credentials are provided, MicroWeb loads settings from `config.json` or starts a default AP (SSID: `ESP32-MicroWeb`, password: `12345678`).\n\n### **2. Defining Routes**\nRoutes map URLs to handler functions. Use the `@app.route()` decorator to define endpoints and specify HTTP methods (e.g., GET, POST).\n\n```python\n@app.route('/')\ndef home(req):\n    return app.render_template('index.html', message='Welcome to MicroWeb!')\n```\n\n**Explanation**:\n- The `@app.route('/')` decorator maps the root URL (`/`) to the `home` function.\n- The `req` parameter provides access to request data (e.g., `req.method`, `req.form`, `req.form`).\n- `app.render_template` renders an HTML template (`index.html`) with dynamic variables (e.g., `message`).\n\nFor dynamic routing with URL parameters:\n```python\n@app.route('/greet/\u003cname\u003e')\ndef greet(req, match):\n    name = match.group(1) if match else 'Anonymous'\n    return {'message': f'Hello, {name}!', 'status': 'success'}\n```\n\n**Explanation**:\n- `/greet/\u003cname\u003e` captures a URL parameter (e.g., `/greet/Alice` sets `name` to `Alice`).\n- The `match` parameter contains the parsed URL parameters, accessed via `match.group(1)`.\n\n### **3. Handling HTTP Methods**\nMicroWeb supports multiple HTTP methods (GET, POST, etc.) for a single route using the `methods` parameter.\n\n```python\n@app.route('/api/methods', methods=['GET', 'POST'])\ndef methods(req):\n    if req.method == 'GET':\n        return app.json_response({'method': 'GET', 'message': 'This is a GET request'})\n    elif req.method == 'POST':\n        data = req.form\n        return app.json_response({'method': 'POST', 'received': data})\n```\n\n**Explanation**:\n- The `methods=['GET', 'POST']` parameter allows the route to handle both GET and POST requests.\n- `req.method` checks the HTTP method to determine the response.\n- `req.form` parses JSON data from the POST request body.\n- `app.json_response` returns a JSON response with the specified data.\n\n\n### **4. Rendering Templates (Updated with Advanced Template Engine Usage)**\n\nMicroWeb supports rendering HTML templates with dynamic data, ideal for creating dynamic web interfaces. The template engine uses a simple syntax with `{% %}` for variables and control structures (e.g., loops, conditionals) and `{{ }}` for control blocks like `if`, `for`, `else`, and `endif`. This allows you to create flexible, reusable templates for your ESP32-based web applications.\n\n#### **Basic Template Rendering**\nTemplates are stored in the `static/` directory (or a specified folder) and rendered using `app.render_template`. Variables passed to the template are inserted into placeholders.\n\n```python\n@app.route('/')\ndef home(req):\n    return app.render_template('index.html', message='Welcome to MicroWeb!')\n```\n\n**Explanation**:\n- `app.render_template('index.html', message='Welcome to MicroWeb!')` renders the `index.html` template, replacing `{% message %}` with the string `\"Welcome to MicroWeb!\"`.\n- Templates must be uploaded to the ESP32’s filesystem (e.g., using `microweb run app.py --static static/ --port COM10`).\n\n#### **Advanced Template Engine Usage**\nMicroWeb’s template engine supports control structures like conditionals (`if`, `else`, `endif`) and loops (`for`, `endfor`), enabling dynamic content generation. Below is an example of a template (`projects.html`) that uses these features to display a list of projects or a fallback message.\n\n**Example Template (`static/projects.html`)**:\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n\u003chead\u003e\n    \u003ctitle\u003eProjects\u003c/title\u003e\n    \u003clink rel=\"stylesheet\" href=\"/style.css\"\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n    \u003ch1\u003eProjects\u003c/h1\u003e\n    {% greeting %}\n    {{ if projects }}\n        \u003cul\u003e\n        {{ for project in projects }}\n            \u003cp\u003e{{ project.title }}\u003c/p\u003e\n        {{ endfor }}\n        \u003c/ul\u003e\n    {{ else }}\n        \u003cp\u003eProjects not found\u003c/p\u003e\n    {{ endif }}\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\n**Corresponding Route**:\n```python\n@app.route('/projects')\ndef projects(req):\n    projects = [\n        {'title': 'Project A', 'description': 'First project'},\n        {'title': 'Project B', 'description': 'Second project'}\n    ]\n    return app.render_template('projects.html', greeting='Welcome to the Projects Page!', projects=projects)\n```\n\n**Explanation**:\n- **Variable Substitution**: `{% greeting %}` is replaced with `\"Welcome to the Projects Page!\"`.\n- **Conditional (`if`, `else`, `endif`)**: The `{{ if projects }}` block checks if the `projects` variable is non-empty. If `projects` exists and is not empty, the `ul` list is rendered; otherwise, the `Projects not found` message is shown.\n- **Loop (`for`, `endfor`)**: The `{{ for project in projects }}` loop iterates over the `projects` list, rendering a `\u003cp\u003e` tag for each project’s `title` (accessed via `{{ project.title }}`).\n- **Dot Notation**: The template engine supports dot notation for accessing dictionary keys or object attributes (e.g., `project.title` retrieves the `title` key from each project dictionary).\n- **Static Files**: The `\u003clink rel=\"stylesheet\" href=\"/style.css\"\u003e` tag references a static CSS file, served via `app.add_static('/style.css', 'style.css')`.\n\n**Alternative Scenario (Empty Projects List)**:\n```python\n@app.route('/projects-empty')\ndef projects_empty(req):\n    return app.render_template('projects.html', greeting='No Projects Available', projects=[])\n```\n\n**Explanation**:\n- If `projects=[]` (empty list), the `{{ if projects }}` condition evaluates to false, and the template renders the fallback message: `\u003cp\u003eProjects not found\u003c/p\u003e`.\n\n#### **Template Syntax Rules**\n- **Variables**: Use `{% variable %}` for simple variable substitution (e.g., `{% greeting %}`).\n- **Control Structures**:\n  - `{{ if condition }} ... {{ else }} ... {{ endif }}`: Evaluates `condition` (truthy/falsy) to render the appropriate branch.\n  - `{{ for var in iterable }} ... {{ endfor }}`: Iterates over `iterable`, assigning each item to `var`.\n- **Dot Notation**: Access nested data with `variable.subkey` (e.g., `project.title`).\n- **Error Handling**: If a variable or key is undefined, the engine returns an empty string to avoid crashes, ensuring robust rendering on resource-constrained ESP32 devices.\n\n#### **Best Practices for Templates**\n- **File Placement**: Store templates in the `static/` directory and upload them to the ESP32 using the `microweb` CLI.\n- **Caching**: MicroWeb caches parsed templates to optimize memory usage on the ESP32. Avoid frequent template changes in production to leverage caching.\n- **Debugging**: Enable `debug=True` in `MicroWeb` initialization to log template rendering errors (e.g., missing files or syntax errors).\n- **Validation**: Test templates with both valid and empty data (e.g., `projects=[]`) to ensure the `if` and `else` branches work as expected.\n- **Static Assets**: Link CSS, JavaScript, or images in templates using `app.add_static` to serve them efficiently.\n\n#### **Testing the Template**\n- **Upload Files**: Ensure `projects.html` and `style.css` are in the `static/` directory and uploaded:\n  ```bash\n  microweb run app.py --static static/ --port COM10\n  ```\n- **Access**: Open `http://192.168.4.1/projects` (or the ESP32’s IP) in a browser to see the rendered project list.\n- **Verify Output**:\n  - With projects: Displays `\u003ch1\u003eProjects\u003c/h1\u003e\u003cp\u003eWelcome to the Projects Page!\u003c/p\u003e\u003cul\u003e\u003cp\u003eProject A\u003c/p\u003e\u003cp\u003eProject B\u003c/p\u003e\u003c/ul\u003e`.\n  - Without projects: Displays `\u003ch1\u003eProjects\u003c/h1\u003e\u003cp\u003eNo Projects Available\u003c/p\u003e\u003cp\u003eProjects not found\u003c/p\u003e`.\n- **curl Test**:\n  ```bash\n  curl http://192.168.4.1/projects\n  ```\n\n#### **Complete Example with Template**\nHere’s a full example combining the `projects.html` template with routes and static file serving:\n\n```python\nimport wifi\nfrom microweb import MicroWeb\n\napp = MicroWeb(debug=True, ap={'ssid': 'MyESP32', 'password': 'mypassword'})\n\n@app.route('/')\ndef home(req):\n    return app.render_template('index.html', message='Welcome to MicroWeb!')\n\n@app.route('/projects')\ndef projects(req):\n    projects = [\n        {'title': 'Project A', 'description': 'First project'},\n        {'title': 'Project B', 'description': 'Second project'}\n    ]\n    return app.render_template('projects.html', greeting='Welcome to the Projects Page!', projects=projects)\n\n@app.route('/projects-empty')\ndef projects_empty(req):\n    return app.render_template('projects.html', greeting='No Projects Available', projects=[])\n\napp.add_static('/style.css', 'style.css')\n\u003c\u003c\u003c\u003c\u003c\u003c\u003c HEAD\n\n\n=======\n\u003e\u003e\u003e\u003e\u003e\u003e\u003e 90c3b61ce53aea3d7566bfbb301164c8360e34ba\napp.run()\n```\n\n**Explanation**:\n- The `/projects` route renders the `projects.html` template with a list of projects, triggering the `for` loop to display each project’s title.\n- The `/projects-empty` route tests the `else` branch by passing an empty `projects` list.\n- The `style.css` file is served as a static file for consistent styling.\n- Run the app and access `http://192.168.4.1/projects` or `http://192.168.4.1/projects-empty` to test both scenarios.\n\n### **5. Serving Static Files**\nMicroWeb allows serving static files like CSS, JavaScript, or images to enhance web interfaces.\n\n```python\napp.add_static('/style.css', 'style.css')\n```\n\n**Explanation**:\n- `app.add_static` maps a URL path (`/style.css`) to a file in the `static/` directory (`style.css`).\n- Static files must be uploaded to the ESP32’s filesystem using:\n  ```bash\n  microweb run app.py --static static/ --port COM10\n  ```\n- In the `result.html` example, `\u003clink rel=\"stylesheet\" href=\"/style.css\"\u003e` loads the CSS file for styling.\n\n### **6. JSON Responses**\nMicroWeb simplifies JSON responses for API endpoints.\n\n```python\n@app.route('/api/status', methods=['GET'])\ndef status(req):\n    return app.json_response({'status': 'running', 'ip': wifi.get_ip()})\n```\n\n**Explanation**:\n- `app.json_response` formats the dictionary as JSON and sets the `Content-Type` to `application/json`.\n- The `wifi` module (if available) retrieves the ESP32’s IP address.\n\n### **7. Custom HTTP Headers**\nYou can set custom headers using the `Response` class.\n\n```python\nfrom microweb import Response\n\n@app.route('/headers')\ndef headers_example(req):\n    resp = Response('Custom header set!', content_type='text/plain')\n    resp.headers['X-Custom-Header'] = 'Value'\n    return resp\n```\n\n**Explanation**:\n- The `Response` class allows custom content types and headers.\n- `resp.headers` sets additional HTTP headers for the response.\n\n### **8. Wi-Fi Integration**\nMicroWeb handles Wi-Fi configuration, allowing the ESP32 to act as an access point or connect to a network.\n\n```python\nimport wifi\nfrom microweb import MicroWeb\n\napp = MicroWeb(debug=True, ap={'ssid': 'MyESP32', 'password': 'mypassword'})\n\n\n# Uncomment to stop Wi-Fi access point\n# app.stop_wifi()  # Uncomment to stop Wi-Fi access point\n## app.start_wifi()  # Uncomment to start Wi-Fi access point after stop\n\n@app.route('/api/status', methods=['GET'])\ndef status(req):\n    return app.json_response({'status': 'running', 'ip': wifi.get_ip()})\n```\n\n**Explanation**:\n- The `wifi` module (part of MicroWeb or MicroPython) provides functions like `wifi.get_ip()` to retrieve the device’s IP address.\n- If Wi-Fi connection fails, the ESP32 starts an access point with the specified `ssid` and `password`.\n\n### **9. Running the Application**\nStart the web server with `app.run()`.\n\n```python\n\napp.run()\n```\n\n**Explanation**:\n- `app.run()` starts the MicroWeb server, listening for HTTP requests.\n- Use the CLI to upload and run the script:\n  ```bash\n  microweb run app.py --port COM10\n  ```\n\n### **10. Best Practices**\n- **Error Handling**: Add try-except blocks for `wifi.get_ip()` or `req.form` to handle network or input errors.\n  ```python\n  try:\n        ip = wifi.get_ip()\n      \n        # Uncomment to stop Wi-Fi access point\n        # app.stop_wifi()  # Uncomment to stop Wi-Fi access point\n        ## app.start_wifi()  # Uncomment to start Wi-Fi access point after stop\n  except Exception as e:\n      ip = 'N/A'\n  return app.json_response({'status': 'running', 'ip': ip})\n  ```\n- **File Management**: Ensure templates (`index.html`, `form.html`, `result.html`) and static files (`style.css`) exist in the `static/` directory before running.\n- **Security**: Avoid hardcoding Wi-Fi credentials; use `config.json` or the web interface for configuration.\n- **Debugging**: Enable `debug=True` during development to log errors and requests.\n- **Testing**: Test routes with tools like `curl` or a browser (e.g., `http://192.168.4.1/` in AP mode).\n\n### **11. Example: Putting It All Together**\nBelow is a complete MicroWeb application combining routes, templates, static files, and JSON responses, including the `result.html` template.\n\n```python\nimport wifi\nfrom microweb import MicroWeb\n\napp = MicroWeb(debug=True, ap={'ssid': 'MyESP32', 'password': 'mypassword'})\n\n@app.route('/')\ndef home(req):\n    return app.render_template('index.html', message='Welcome to MicroWeb!')\n\n@app.route('/api/status', methods=['GET'])\ndef status(req):\n    return app.json_response({'status': 'running', 'ip': wifi.get_ip()})\n\n@app.route('/api/echo', methods=['POST'])\ndef echo(req):\n    data = req.form\n    return app.json_response({'received': data})\n\n@app.route('/submit', methods=['GET', 'POST'])\ndef submit_form(req):\n    if req.method == 'POST':\n        return app.render_template('result.html', data=str(req.form), method='POST')\n    else:\n        return app.render_template('form.html')\n\napp.add_static('/style.css', 'style.css')\n\n\napp.run()\n```\n\n**Explanation**:\n- Combines template rendering (`/`, `/submit`), JSON responses (`/api/status`, `/api/echo`), and static file serving (`/style.css`).\n- The `/submit` route uses `result.html` (as shown above) to display form data and the HTTP method.\n- Upload and run with:\n  ```bash\n  microweb run app.py --port COM10 --static static/\n  ```\n- Access at `http://192.168.4.1` (or the ESP32’s IP address).\n\n### **12. Testing Your Application**\n- **Browser**: Open `http://\u003cESP32-IP\u003e/` (e.g., `http://192.168.4.1`) to access the web server.\n- **curl**:\n  ```bash\n  curl http://192.168.4.1/api/status\n  curl -X POST -d 'username=Alice' http://192.168.4.1/api/echo\n  curl -X POST -d 'username=Alice' http://192.168.4.1/submit\n  ```\n- **CLI Logs**: Monitor logs with `debug=True` to debug issues.\n- **Template Testing**: Submit a form via `http://192.168.4.1/submit` to see the `result.html` output, styled with `style.css`.\n\n### **13. Next Steps**\n- Explore the portfolio demo (`tests/portfolio/`) for a full-featured web app example.\n- Use `microweb create --path my_app` to generate a template project.\n- Add boot script support with `microweb run app.py --add-boot --port COM10` for auto-running on power-up.\n- Check the [CLI Tool Usage Examples](#cli-tool-usage-examples) for advanced commands.\n\n---\n\n### Feature Updates\n\n- Improvsed CLI usability and error messages.\n- Added support for static file serving and template rendering.\n- Enhanced Wi-Fi configuration with fallback AP mode.\n- Added validation for MicroPython firmware before running scripts.\n- CLI now supports file cleanup and dependency checking.\n- Auto-detects ESP32 port for flashing and running.\n- Added support for custom HTTP headers and JSON responses.\n- Improved documentation and usage examples.\n- Support for GET, POST, and custom HTTP methods in route handlers.\n- Static and template file hot-reloading for faster development.\n- Built-in JSON and form data parsing for request bodies.\n- Customizable AP SSID/password and web-based Wi-Fi setup page.\n- CLI options for forced upload, boot script management, and static directory selection.\n- Enhanced error handling and troubleshooting guidance.\n- Modular project structure for easier extension and maintenance.\n\n\n## Project Structure\n\n![img2](/src/uml2.svg)\n\n```\nmicroweb/\n├── microweb/\n│   ├── __init__.py\n│   ├── microweb.py\n│   ├── wifi.py\n│   ├── uploader.py\n│   ├── cli.py\n│   ├── firmware/\n│   │   ├── ESP32_GENERIC-20250415-v1.25.0.bin\n│   │   ├── boot.py         # Minimal boot script \n│   │   ├── main.py         # Imports and runs your app module\n├── setup.py                # Packaging and install configuration\n├── README.md               # Project documentation\n```\n\n\n\n\n---\n\n## Contributing\n\nFork and submit pull requests at [https://github.com/ishanoshada/microweb](https://github.com/ishanoshada/microweb).\n\n---\n\n**Repository Views** ![Views](https://profile-counter.glitch.me/microweb/count.svg)\n","funding_links":["https://buymeacoffee.com/ishanoshada"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fishanoshada%2Fmicroweb","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fishanoshada%2Fmicroweb","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fishanoshada%2Fmicroweb/lists"}