{"id":24543591,"url":"https://github.com/gabrielmarcano/pyroaster","last_synced_at":"2026-05-11T07:44:12.763Z","repository":{"id":265326745,"uuid":"887391682","full_name":"gabrielmarcano/pyroaster","owner":"gabrielmarcano","description":"Firmware to create a real life roaster using the ESP32 microcontroller and MicroPython.","archived":false,"fork":false,"pushed_at":"2025-03-10T00:16:15.000Z","size":82,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-10T01:30:34.044Z","etag":null,"topics":["esp32","microcontrollers","micropython","micropython-esp32"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gabrielmarcano.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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}},"created_at":"2024-11-12T16:59:35.000Z","updated_at":"2025-03-10T00:16:18.000Z","dependencies_parsed_at":null,"dependency_job_id":"edbf12db-9786-418f-a9d9-303c547fcd87","html_url":"https://github.com/gabrielmarcano/pyroaster","commit_stats":null,"previous_names":["gabrielmarcano/pyroaster"],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gabrielmarcano%2Fpyroaster","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gabrielmarcano%2Fpyroaster/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gabrielmarcano%2Fpyroaster/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gabrielmarcano%2Fpyroaster/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gabrielmarcano","download_url":"https://codeload.github.com/gabrielmarcano/pyroaster/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243841192,"owners_count":20356441,"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":["esp32","microcontrollers","micropython","micropython-esp32"],"created_at":"2025-01-22T20:14:16.509Z","updated_at":"2026-05-11T07:44:12.758Z","avatar_url":"https://github.com/gabrielmarcano.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003eESP32 Roaster Project\u003c/h1\u003e\n\u003cp align=\"center\"\u003e\n  A project to control a peanut, coffee \u0026 cocoa roaster with an ESP32\n\u003c/p\u003e\n\n\u003cdiv align=\"center\"\u003e\n\n\u003c!-- [![Build](https://img.shields.io/github/actions/workflow/status/gabrielmarcano/esp32-roaster/build.yml?logo=github)](https://github.com/gabrielmarcano/esp32-roaster/blob/master/.github/workflows/build.yml) --\u003e\n\u003c!-- [![OTA Update](https://img.shields.io/github/actions/workflow/status/gabrielmarcano/esp32-roaster/ota-update.yml?logo=github\u0026label=OTA)](https://github.com/gabrielmarcano/esp32-roaster/blob/master/.github/workflows/ota-update.yml) --\u003e\n\u003c!-- [![GitHub release](https://img.shields.io/github/v/release/gabrielmarcano/esp32-roaster?filter=*alpha\u0026logo=github)](https://github.com/gabrielmarcano/esp32-roaster/releases) --\u003e\n\n[![python](https://img.shields.io/badge/Python-3.13-3776AB.svg?style=flat\u0026logo=python\u0026logoColor=white)](https://www.python.org)\n[![micropython](https://img.shields.io/badge/built%20for-MicroPython-3776AB?logo=micropython)](https://micropython.org/)\n\n\u003c/div\u003e\n\n## Contents\n\n- [Summary](#summary)\n- [How it works](#how-it-works)\n- [Project structure](#project-structure)\n- [Hardware](#hardware)\n- [Wiring](#wiring)\n- [Software](#software)\n- [Setup](#setup)\n- [API](#api)\n- [Resources](#resources)\n\n## Summary\n\nAll logic depends on the data given by the **Thermocouple (MAX6675)** \u0026 **AHT20** sensors. It's intention is to control 3 motors, which will turn on or off based on the temperature that it reaches.\n\nWhen the temperature reaches the value set on the config, it feeds a relay that controls the first motor,\nand also starts a timer that was set on the config. Extra configs can be saved.\n\nWhen the timer stops, the other 2 relays feed the second \u0026 third motor.\n\n\u003e Motors can only be stopped manually through the app interface.\n\n## How it works\n\nThe firmware runs four cooperative async tasks on a single core:\n\n1. **Logic loop** (1s interval) — reads sensors, runs the controller, updates the LCD\n2. **Web server** — HTTP REST API + Server-Sent Events (SSE) for real-time updates\n3. **WiFi manager** (15s interval) — monitors connection and reconnects automatically\n4. **LED status** — blink codes for hardware errors, solid on/off for WiFi state\n\nThe logic loop runs independently from the network. If WiFi drops or the web server crashes, sensor reads, motor control, and LCD updates continue uninterrupted. When WiFi reconnects, the IP is shown on the LCD for 5 seconds.\n\n### Networking\n\nThe ESP32 runs in dual WiFi mode (STA + AP simultaneously):\n\n- **STA** — connects to your home router for network access\n- **AP** — creates a `PyRoaster-AP` network for direct connection (always available at `192.168.4.1`)\n\nThis means the device is reachable even when the router signal is weak — just connect your phone directly to the AP network.\n\n### Startup diagnostics\n\nOn boot, the firmware prints a status report for all hardware components and sets the built-in LED to indicate the highest priority error:\n\n| LED behavior | Meaning |\n|---|---|\n| Off | Everything OK, no WiFi |\n| Solid on | Everything OK, WiFi connected |\n| 1 blink, 3s pause | LCD not connected |\n| 2 blinks, 3s pause | AHT20 not connected |\n| 3 blinks, 3s pause | MAX6675 not connected |\n\nIndividual sensor failures are graceful — if the MAX6675 is absent, AHT20 readings and LCD updates continue normally.\n\n### Roasting flow\n\n1. User sets a **starting temperature** and **roast time** via the API or saved configs\n2. User activates the controller\n3. When the temperature reaches the threshold → **Motor A** starts and the **timer** begins counting down\n4. When the timer reaches zero → **Motors B \u0026 C** start, controller deactivates\n5. Motors can only be stopped manually (API or physical button)\n\n## Project structure\n\n```\n├── boot.py              # Runs on boot: starts WiFi + WebREPL\n├── main.py              # Entry point: async tasks, HTTP routes, pin setup\n├── controller.py        # Roasting controller (temp threshold → motor → timer logic)\n├── logger.py            # Simple timestamped logger (DEBUG/INFO/WARNING/ERROR)\n├── utils.py             # WiFi manager, LED status, time formatting, URL decoding\n├── config.json          # Saved roasting presets\n├── env.py               # WiFi + AP credentials (not committed)\n├── env.template.py      # Credentials template\n├── build.py             # Cross-compiles to .mpy and uploads via mpremote\n├── api.yaml             # OpenAPI spec\n│\n├── lib/\n│   ├── sensors.py       # MAX6675 + AHT20 sensor aggregation\n│   ├── motors.py        # 3 motor (relay) control\n│   ├── timer.py         # Hardware timer with countdown\n│   └── lcd.py           # 2x16 I2C LCD display\n│\n├── drivers/\n│   ├── max6675.py       # MAX6675 thermocouple SPI driver\n│   ├── ahtx0.py         # AHT20 humidity/temperature I2C driver\n│   ├── machine_i2c_lcd.py  # I2C LCD driver (PCF8574)\n│   └── lcd_api.py       # LCD API abstraction\n│\n├── microdot/            # Microdot web framework (vendored)\n│   ├── microdot.py\n│   ├── cors.py\n│   ├── sse.py\n│   └── helpers.py\n│\n├── test/\n│   ├── sse.html         # SSE test client\n│   └── sse.js\n│\n└── out/                 # Build output (compiled .mpy files)\n```\n\n## Hardware\n\n| Component | Description |\n|-----------|-------------|\n| ESP32 DevKit | Main microcontroller |\n| MAX6675 + K-type thermocouple | Temperature sensor (0-1024°C) |\n| AHT20 | Humidity \u0026 temperature sensor (I2C) |\n| 16x2 LCD + PCF8574 | I2C character display |\n| 3x Relay modules | Motor control (one per motor) |\n\n## Wiring\n\n| ESP-32 | MAX6675 | AHT20 I2C | LCD I2C | R1  | R2  | R3  |\n| ------ | ------- | --------- | ------- | --- | --- | --- |\n| GPIO2  |         |           |         |     |     |     | (built-in LED)\n| GPIO5  | SCK     |           |         |     |     |     |\n| GPIO16 |         | SCL       |         |     |     |     |\n| GPIO17 |         | SDA       |         |     |     |     |\n| GPIO19 | SO      |           |         |     |     |     |\n| GPIO21 |         |           | SDA     |     |     |     |\n| GPIO22 |         |           | SCL     |     |     |     |\n| GPIO23 | CS      |           |         |     |     |     |\n| GPIO25 |         |           |         | x   |     |     |\n| GPIO26 |         |           |         |     | x   |     |\n| GPIO27 |         |           |         |     |     | x   |\n\n\u003e R: Relay\n\nThe two I2C buses use hardware peripherals: `I2C(0)` for the AHT20 sensor and `I2C(1)` for the LCD.\n\n## Software\n\n- **MicroPython** on ESP32\n- **Microdot** — lightweight async web framework for REST API and SSE\n- **mpy-cross** — cross-compiler for `.mpy` bytecode (faster load, less RAM)\n- **mpremote** — file transfer to the ESP32\n\n## Setup\n\n1. Flash MicroPython firmware to the ESP32\n\n2. Copy `env.template.py` to `env.py` and set your credentials:\n   ```python\n   WIFI_SSID = \"your-ssid\"\n   WIFI_PASSWD = \"your-password\"\n   AP_SSID = \"PyRoaster-AP\"\n   AP_PASSWD = \"your-ap-password\"\n   ```\n\n3. Build and upload:\n   ```bash\n   python build.py\n   ```\n   This cross-compiles all library files to `.mpy`, copies everything to the `out/` directory, and prompts to upload via `mpremote`.\n\n4. The device boots, connects to WiFi, starts the AP network, and runs the web server on port 80. The AP IP (`192.168.4.1`) and router IP are shown on the LCD.\n\n## API\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| `GET` | `/events` | SSE stream (sensors, timer, motors, controller) |\n| `GET` | `/controller_config` | Get controller settings |\n| `PATCH` | `/controller_config` | Update starting temperature / time |\n| `POST` | `/controller` | Activate, deactivate, or stop the controller |\n| `POST` | `/time` | Add, reduce, or change the timer |\n| `POST` | `/motors` | Control individual motors (on/off) |\n| `GET` | `/config` | List saved roasting presets |\n| `POST` | `/config` | Save a new preset |\n| `DELETE` | `/config/\u003cname\u003e` | Delete a preset |\n| `POST` | `/reset` | Reboot the device |\n\nSee `api.yaml` for the full OpenAPI specification.\n\n### SSE events\n\nThe `/events` endpoint streams four event types:\n\n- **`sensors`** — `{\"temperature\": int, \"humidity\": int}`\n- **`time`** — `{\"total_time\": int, \"current_time\": int}`\n- **`states`** — `{\"motor_a\": bool, \"motor_b\": bool, \"motor_c\": bool}`\n- **`controller`** — `{\"starting_temperature\": int, \"time\": int, \"status\": \"on\"|\"off\"}`\n\nEvents are only sent when data changes (change detection).\n\n## Resources\n\n### AHTx0\n\nMicroPython AHT20 driver library.\n\nhttps://github.com/targetblank/micropython_ahtx0\n\n### MAX6675\n\nhttps://github.com/BetaRavener/micropython-hw-lib/blob/master/MAX6675/max6675.py\n\n### LCD\n\nUses both the API (lcd_api) and the machine module (machine_i2c_lcd).\n\nhttps://github.com/dhylands/python_lcd\n\n### Web Server\n\nUses the Microdot framework https://github.com/miguelgrinberg/microdot\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgabrielmarcano%2Fpyroaster","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgabrielmarcano%2Fpyroaster","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgabrielmarcano%2Fpyroaster/lists"}