{"id":51090398,"url":"https://github.com/filippolmt/m5stack-atom-switchbot-lock-pro","last_synced_at":"2026-06-24T01:30:31.941Z","repository":{"id":327712716,"uuid":"1110002554","full_name":"filippolmt/m5stack-atom-switchbot-lock-pro","owner":"filippolmt","description":"Control your **SwitchBot Lock Pro** by simply pressing the button on your **M5Stack ATOM**! 🚪🔐","archived":false,"fork":false,"pushed_at":"2026-03-16T21:52:28.000Z","size":745,"stargazers_count":0,"open_issues_count":2,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-17T07:53:09.450Z","etag":null,"topics":["m5stack","switchbot","switchbot-api"],"latest_commit_sha":null,"homepage":"","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/filippolmt.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,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-04T15:22:47.000Z","updated_at":"2026-03-09T08:07:24.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/filippolmt/m5stack-atom-switchbot-lock-pro","commit_stats":null,"previous_names":["filippolmt/m5stack-atom-switchbot-lock-pro"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/filippolmt/m5stack-atom-switchbot-lock-pro","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/filippolmt%2Fm5stack-atom-switchbot-lock-pro","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/filippolmt%2Fm5stack-atom-switchbot-lock-pro/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/filippolmt%2Fm5stack-atom-switchbot-lock-pro/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/filippolmt%2Fm5stack-atom-switchbot-lock-pro/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/filippolmt","download_url":"https://codeload.github.com/filippolmt/m5stack-atom-switchbot-lock-pro/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/filippolmt%2Fm5stack-atom-switchbot-lock-pro/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34713789,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-23T02:00:07.161Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["m5stack","switchbot","switchbot-api"],"created_at":"2026-06-24T01:30:31.421Z","updated_at":"2026-06-24T01:30:31.934Z","avatar_url":"https://github.com/filippolmt.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# M5Stack ATOM - SwitchBot Lock Pro Controller\n\nControl your **SwitchBot Lock Pro** by simply pressing the button on your **M5Stack ATOM**! 🚪🔐\n\nThis project provides a complete solution to integrate your M5Stack ATOM (ESP32) with the SwitchBot API and control a SwitchBot Lock Pro over Wi-Fi.\n\n## 🌟 Features\n\n- ✅ **Deep sleep mode** - Ultra-low power consumption (~10uA idle vs ~80mA active)\n- ✅ **Wake on button press** - ESP32 wakes from deep sleep when button is pressed\n- ✅ **On-demand Wi-Fi** - Connects only when needed, disconnects immediately after API response\n- ✅ **Fast reconnect** - Caches Wi-Fi BSSID for ~1-2s faster reconnection after deep sleep\n- ✅ **Optional static IP** - Skip DHCP negotiation for ~500ms-1s faster connection\n- ✅ **Multicolor LED feedback** - Different colors indicate status and errors\n- ✅ **SwitchBot API v1.1** with signed token + secret headers\n- ✅ **Auto retry** - Retries API call once on failure\n- ✅ **Automated test suite** - 53 tests via Docker (Python 3.13 + pytest)\n- ✅ **CI/CD** - GitHub Actions runs tests on push/PR\n- ✅ **Complete setup guide** for VS Code + MicroPython\n\n## 📋 Requirements\n\n### Hardware\n\n- **M5Stack ATOM** (ESP32-PICO-D4)\n- USB Type-C cable\n- **SwitchBot Lock Pro** (set up and working)\n- **Atomic Battery Base** (200mAh, optional) - for portable battery-powered operation\n\n### Software\n\n- **MicroPython v1.24.x or later** — download from [micropython.org/download/M5STACK_ATOM](https://micropython.org/download/M5STACK_ATOM/)\n- **VS Code** (optional) for editing\n- `mpremote` for file upload and execution\n- Python 3.x on your computer\n- SwitchBot account with API token and secret (for API v1.1 signing)\n\n## 🚀 Quick Start\n\n### 1. Environment Setup (First Time)\n\nFollow the full guide in **[SETUP.md](SETUP.md)** to:\n\n- Install VS Code and the MicroPython extension\n- Flash MicroPython onto your M5Stack ATOM\n- Configure the development environment\n- Obtain SwitchBot credentials (Token and Device ID)\n\n### 2. Configuration\n\n```bash\n# Clone the repository\ngit clone https://github.com/filippolmt/m5stack-atom-switchbot-lock-pro.git\ncd m5stack-atom-switchbot-lock-pro\n\n# Copy and configure the settings file\ncp config_template.py config.py\n```\n\nEdit `config.py` with your details:\n\n```python\n# Wi-Fi configuration\nWIFI_SSID = \"YourSSID\"\nWIFI_PASSWORD = \"YourPassword\"\n\n# SwitchBot API configuration\nSWITCHBOT_TOKEN = \"YourToken\"\nSWITCHBOT_SECRET = \"YourTokenSecret\"\nSWITCHBOT_DEVICE_ID = \"YourDeviceID\"\n\n# M5Stack ATOM button GPIO (preconfigured)\nBUTTON_GPIO = 39\n\n# Optional: static IP to skip DHCP (saves ~500ms-1s per connection)\n# WIFI_STATIC_IP = (\"192.168.1.100\", \"255.255.255.0\", \"192.168.1.1\", \"8.8.8.8\")\n```\n\n### 3. Upload to the Device (mpremote)\n\n1. Connect the M5Stack ATOM via USB and identify the serial port (e.g., `/dev/cu.usbserial-XXXX` on macOS, `COM3` on Windows, `/dev/ttyUSB0` on Linux).\n2. Upload the files with `mpremote` (replace the port with yours):\n   ```bash\n   mpremote connect /dev/cu.usbserial-XXXX cp main.py :main.py\n   mpremote connect /dev/cu.usbserial-XXXX cp config.py :config.py\n   ```\n\n### 4. Run It\n\nExecute the script via `mpremote`:\n\n```bash\nmpremote connect /dev/cu.usbserial-XXXX run main.py\n```\n\nThen press the button on the M5Stack ATOM to control the lock.\n\n## 📁 Project Structure\n\n```\n.\n├── main.py              # Main MicroPython script\n├── config_template.py   # Configuration template\n├── config.py            # Configuration (create locally, not in git)\n├── tests/               # Automated test suite (runs on CPython via Docker)\n│   ├── conftest.py      # Hardware stubs + fake config injection\n│   ├── test_epoch.py    # Epoch conversion \u0026 timestamp tests\n│   ├── test_hmac.py     # HMAC-SHA256 (manual + stdlib paths)\n│   ├── test_auth_headers.py  # API authentication headers\n│   ├── test_send_command.py  # HTTP retry logic \u0026 error handling\n│   ├── test_rtc_memory.py    # RTC memory serialization\n│   ├── test_led.py      # LED brightness scaling\n│   └── test_wifi.py     # Wi-Fi connection logic\n├── Dockerfile.test      # Test runner image (Python 3.13 + pytest)\n├── Makefile             # make test / make test-clean\n├── pyproject.toml       # pytest configuration\n├── .github/workflows/test.yml  # CI: tests on push/PR to main\n├── SETUP.md             # Full setup guide\n├── README.md            # This file\n├── LICENSE              # License\n└── .gitignore           # Excludes config.py and other sensitive files\n```\n\n## 🔧 How It Works\n\n1. **On boot/reset**: Shows startup message, then enters deep sleep (~10uA)\n2. **When you press the button**:\n   - ESP32 wakes from deep sleep\n   - **Short press (\u003c1s)** = UNLOCK (green LED while holding)\n   - **Long press (≥1s)** = LOCK (purple LED while holding)\n   - Connects to Wi-Fi (fast reconnect if cached)\n   - Syncs time via NTP (skipped if RTC valid)\n   - Sends lock/unlock command to SwitchBot API\n   - Disconnects Wi-Fi immediately after response\n   - LED feedback based on result (Wi-Fi already off)\n   - Returns to deep sleep\n3. **Power consumption**:\n   - Deep sleep: ~10uA (can run months on battery)\n   - Active (Wi-Fi + API call): ~80-150mA for 2-4 seconds\n   - LED feedback: ~25mA for ~0.5s (Wi-Fi already off)\n\n## 🎮 Button Controls\n\n| Press Duration | Action | LED While Holding |\n|----------------|--------|-------------------|\n| **\u003c 1 second** | UNLOCK | 🟢 Green |\n| **≥ 1 second** | LOCK | 🟣 Purple |\n\n## 💡 LED Color Guide\n\n| Color | Meaning |\n|-------|---------|\n| 🟢 **Green (holding)** | Short press - will UNLOCK |\n| 🟣 **Purple (holding)** | Long press - will LOCK |\n| 🔵 **Blue** | Connecting to Wi-Fi (normal scan) |\n| 🩵 **Cyan** | Fast reconnect in progress |\n| 🟢 **Green (2 blinks)** | Door unlocked successfully |\n| 🟣 **Purple (2 blinks)** | Door locked successfully |\n| 🟡 **Yellow (2 blinks)** | NTP sync failed (continuing anyway) |\n| 🟡 **Yellow (4 blinks)** | Time sync error |\n| 🟠 **Orange (3 blinks)** | Wi-Fi connection timeout |\n| 🔴 **Red (3 blinks)** | API error |\n| 🔴 **Red (6 fast blinks)** | Authentication error (401) |\n\n## 🔋 Atomic Battery Base (Optional)\n\nThe project supports the [M5Stack Atomic Battery Base](https://docs.m5stack.com/en/atom/Atomic%20Battery%20Base) (200mAh, 3.7V) for portable battery-powered operation.\n\n### Specifications\n\n| Property | Value |\n|----------|-------|\n| Battery | 3.7V @ 200mAh LiPo |\n| Boost converter | ETA9085E10 (5V output) |\n| Charging IC | LGS4056HDA (USB-C, 223mA) |\n| Standby current | 2.55uA (boost converter) |\n\n### Realistic Battery Life\n\n**Important:** The M5Stack ATOM Lite draws **4-11mA in deep sleep** (not 10uA) due to the always-on USB/serial chip. With the 200mAh battery:\n\n- **Estimated autonomy: 18-50 hours** depending on board revision\n- Wake cycle consumption (~80-150mA for 1-5s) is \u003c0.5% of total drain\n- **Sleep current dominates** — the USB/serial chip (3-5mA) is the main drain and cannot be disabled in software\n- For longer battery life, consider a larger battery (750-1000mAh → 3-8 days)\n\n### Charging\n\n- Connect USB-C to charge (blue LED = charging, green LED = full)\n- Dip switch: **boost** for normal operation, **charge** when connected to USB\n- Full charge: ~1 hour at 223mA\n\n## 📡 SwitchBot API\n\nThe project uses the SwitchBot API v1.1:\n\n- **Endpoint**: `https://api.switch-bot.com/v1.1/devices/{deviceId}/commands`\n- **Authentication**: token + secret with signed headers:\n  - `Authorization`: your token\n  - `nonce`: random hex string\n  - `t`: Unix timestamp in milliseconds (1970 epoch)\n  - `sign`: Base64(HMAC-SHA256(token + t + nonce, secret))\n- **Commands**: `unlock` or `lock`\n\nMicroPython on ESP32 uses the 2000-01-01 epoch internally. The code converts it to the Unix epoch (1970) before signing and retries an NTP sync if the RTC year looks wrong before sending a command. If the timestamp is off you will get a `401 Unauthorized` from the API.\n\nFull documentation: https://github.com/OpenWonderLabs/SwitchBotAPI\n\n## 🔍 Monitoring and Debug\n\nConnect to the serial terminal (115200 baud) to see:\n\n**Fresh boot:**\n```\n==================================================\nM5Stack ATOM Lite - SwitchBot Lock Pro Controller\n          (Deep Sleep Version)\n==================================================\n\nDevice ID: XXXXXXXXXXXX\nWake button: GPIO39\nLong press threshold: 1000ms\n\nControls:\n  Short press (\u003c1s) = UNLOCK (green LED)\n  Long press  (\u003e1s) = LOCK   (purple LED)\n\nEntering deep sleep...\n  Wake trigger: GPIO39 LOW (button press)\n  Power consumption: ~10uA\n==================================================\n```\n\n**Short press - UNLOCK (fast reconnect):**\n```\n==================================================\nWAKE FROM DEEP SLEEP - Button pressed!\n==================================================\nButton held for 450ms\nAction: UNLOCK\nFast reconnect available (ch=1)\nFast reconnect (ch=1)... OK!\n  IP: 192.168.178.87\n✓ RTC time valid, skipping NTP sync\nSending UNLOCK command...\nHTTP status: 200\nResponse: {\"statusCode\":100,\"body\":{},\"message\":\"success\"}\n✓ Door unlocked successfully!\n\nEntering deep sleep...\n```\n\n**Long press - LOCK (first boot, normal scan):**\n```\n==================================================\nWAKE FROM DEEP SLEEP - Button pressed!\n==================================================\nButton held for 1552ms\nAction: LOCK\nConnecting to Wi-Fi: MySSID...\n..........................\n✓ Connected to Wi-Fi!\n  IP: 192.168.178.87\n  Cached ch=1 for fast reconnect\nRTC time invalid, syncing NTP...\nSynchronizing time via NTP...\n✓ Time synchronized via NTP (UTC).\nSending LOCK command...\nHTTP status: 200\nResponse: {\"statusCode\":100,\"body\":{},\"message\":\"success\"}\n✓ Door locked successfully!\n\nEntering deep sleep...\n```\n\n## 🧪 Automated Tests\n\nTests run on standard CPython inside Docker — no MicroPython or hardware needed. Hardware modules are replaced by stubs automatically.\n\n```bash\nmake test          # Build Docker image + run all tests\nmake test-clean    # Remove the test Docker image\n```\n\nTests also run automatically via GitHub Actions on every push and PR to `main`.\n\n**What's tested** (53 test cases):\n\n| Area | Tests |\n|------|-------|\n| Epoch conversion | Offset constant, `unix_time_ms()` range and precision |\n| HMAC-SHA256 | Manual RFC 2104 vs stdlib, long keys, empty inputs |\n| Auth headers | Required keys, uppercase Base64 signature, timestamp format |\n| HTTP send_command | Retry logic, 401 no-retry, response cleanup, attribute-raise resilience |\n| RTC memory | Save/load roundtrip, invalid BSSID, channel bounds |\n| LED brightness | `_scale()` math, clamping at 255 |\n| Wi-Fi connect | Already-connected, timeout, fast reconnect, bssid fallback |\n\n## 🛠️ Troubleshooting\n\nSee the **Troubleshooting** section in [SETUP.md](SETUP.md) for:\n\n- Connection issues with the device\n- Errors while flashing the firmware\n- Wi-Fi connection problems\n- SwitchBot API errors\n- Button issues\n- Memory handling\n\n## 🔒 Security\n\n⚠️ **IMPORTANT:**\n\n- `config.py` contains sensitive credentials and is excluded from Git\n- Do not share your Token or Secret\n- Use a secure Wi-Fi network (WPA2/WPA3)\n- Consider using a dedicated VLAN for IoT devices\n\n## 🙏 Acknowledgements\n\n- [MicroPython](https://micropython.org/) - Python for microcontrollers\n- [M5Stack](https://m5stack.com/) - Quality ESP32 hardware\n- [SwitchBot](https://www.switch-bot.com/) - Smart home devices\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffilippolmt%2Fm5stack-atom-switchbot-lock-pro","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffilippolmt%2Fm5stack-atom-switchbot-lock-pro","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffilippolmt%2Fm5stack-atom-switchbot-lock-pro/lists"}