https://github.com/filippolmt/m5stack-atom-switchbot-lock-pro
Control your **SwitchBot Lock Pro** by simply pressing the button on your **M5Stack ATOM**! ๐ช๐
https://github.com/filippolmt/m5stack-atom-switchbot-lock-pro
m5stack switchbot switchbot-api
Last synced: 25 days ago
JSON representation
Control your **SwitchBot Lock Pro** by simply pressing the button on your **M5Stack ATOM**! ๐ช๐
- Host: GitHub
- URL: https://github.com/filippolmt/m5stack-atom-switchbot-lock-pro
- Owner: filippolmt
- License: mit
- Created: 2025-12-04T15:22:47.000Z (8 months ago)
- Default Branch: main
- Last Pushed: 2026-03-16T21:52:28.000Z (4 months ago)
- Last Synced: 2026-03-17T07:53:09.450Z (4 months ago)
- Topics: m5stack, switchbot, switchbot-api
- Language: Python
- Homepage:
- Size: 728 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# M5Stack ATOM - SwitchBot Lock Pro Controller
Control your **SwitchBot Lock Pro** by simply pressing the button on your **M5Stack ATOM**! ๐ช๐
This project provides a complete solution to integrate your M5Stack ATOM (ESP32) with the SwitchBot API and control a SwitchBot Lock Pro over Wi-Fi.
## ๐ Features
- โ
**Deep sleep mode** - Ultra-low power consumption (~10uA idle vs ~80mA active)
- โ
**Wake on button press** - ESP32 wakes from deep sleep when button is pressed
- โ
**On-demand Wi-Fi** - Connects only when needed, disconnects immediately after API response
- โ
**Fast reconnect** - Caches Wi-Fi BSSID for ~1-2s faster reconnection after deep sleep
- โ
**Optional static IP** - Skip DHCP negotiation for ~500ms-1s faster connection
- โ
**Multicolor LED feedback** - Different colors indicate status and errors
- โ
**SwitchBot API v1.1** with signed token + secret headers
- โ
**Auto retry** - Retries API call once on failure
- โ
**Automated test suite** - 53 tests via Docker (Python 3.13 + pytest)
- โ
**CI/CD** - GitHub Actions runs tests on push/PR
- โ
**Complete setup guide** for VS Code + MicroPython
## ๐ Requirements
### Hardware
- **M5Stack ATOM** (ESP32-PICO-D4)
- USB Type-C cable
- **SwitchBot Lock Pro** (set up and working)
- **Atomic Battery Base** (200mAh, optional) - for portable battery-powered operation
### Software
- **MicroPython v1.24.x or later** โ download from [micropython.org/download/M5STACK_ATOM](https://micropython.org/download/M5STACK_ATOM/)
- **VS Code** (optional) for editing
- `mpremote` for file upload and execution
- Python 3.x on your computer
- SwitchBot account with API token and secret (for API v1.1 signing)
## ๐ Quick Start
### 1. Environment Setup (First Time)
Follow the full guide in **[SETUP.md](SETUP.md)** to:
- Install VS Code and the MicroPython extension
- Flash MicroPython onto your M5Stack ATOM
- Configure the development environment
- Obtain SwitchBot credentials (Token and Device ID)
### 2. Configuration
```bash
# Clone the repository
git clone https://github.com/filippolmt/m5stack-atom-switchbot-lock-pro.git
cd m5stack-atom-switchbot-lock-pro
# Copy and configure the settings file
cp config_template.py config.py
```
Edit `config.py` with your details:
```python
# Wi-Fi configuration
WIFI_SSID = "YourSSID"
WIFI_PASSWORD = "YourPassword"
# SwitchBot API configuration
SWITCHBOT_TOKEN = "YourToken"
SWITCHBOT_SECRET = "YourTokenSecret"
SWITCHBOT_DEVICE_ID = "YourDeviceID"
# M5Stack ATOM button GPIO (preconfigured)
BUTTON_GPIO = 39
# Optional: static IP to skip DHCP (saves ~500ms-1s per connection)
# WIFI_STATIC_IP = ("192.168.1.100", "255.255.255.0", "192.168.1.1", "8.8.8.8")
```
### 3. Upload to the Device (mpremote)
1. 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).
2. Upload the files with `mpremote` (replace the port with yours):
```bash
mpremote connect /dev/cu.usbserial-XXXX cp main.py :main.py
mpremote connect /dev/cu.usbserial-XXXX cp config.py :config.py
```
### 4. Run It
Execute the script via `mpremote`:
```bash
mpremote connect /dev/cu.usbserial-XXXX run main.py
```
Then press the button on the M5Stack ATOM to control the lock.
## ๐ Project Structure
```
.
โโโ main.py # Main MicroPython script
โโโ config_template.py # Configuration template
โโโ config.py # Configuration (create locally, not in git)
โโโ tests/ # Automated test suite (runs on CPython via Docker)
โ โโโ conftest.py # Hardware stubs + fake config injection
โ โโโ test_epoch.py # Epoch conversion & timestamp tests
โ โโโ test_hmac.py # HMAC-SHA256 (manual + stdlib paths)
โ โโโ test_auth_headers.py # API authentication headers
โ โโโ test_send_command.py # HTTP retry logic & error handling
โ โโโ test_rtc_memory.py # RTC memory serialization
โ โโโ test_led.py # LED brightness scaling
โ โโโ test_wifi.py # Wi-Fi connection logic
โโโ Dockerfile.test # Test runner image (Python 3.13 + pytest)
โโโ Makefile # make test / make test-clean
โโโ pyproject.toml # pytest configuration
โโโ .github/workflows/test.yml # CI: tests on push/PR to main
โโโ SETUP.md # Full setup guide
โโโ README.md # This file
โโโ LICENSE # License
โโโ .gitignore # Excludes config.py and other sensitive files
```
## ๐ง How It Works
1. **On boot/reset**: Shows startup message, then enters deep sleep (~10uA)
2. **When you press the button**:
- ESP32 wakes from deep sleep
- **Short press (<1s)** = UNLOCK (green LED while holding)
- **Long press (โฅ1s)** = LOCK (purple LED while holding)
- Connects to Wi-Fi (fast reconnect if cached)
- Syncs time via NTP (skipped if RTC valid)
- Sends lock/unlock command to SwitchBot API
- Disconnects Wi-Fi immediately after response
- LED feedback based on result (Wi-Fi already off)
- Returns to deep sleep
3. **Power consumption**:
- Deep sleep: ~10uA (can run months on battery)
- Active (Wi-Fi + API call): ~80-150mA for 2-4 seconds
- LED feedback: ~25mA for ~0.5s (Wi-Fi already off)
## ๐ฎ Button Controls
| Press Duration | Action | LED While Holding |
|----------------|--------|-------------------|
| **< 1 second** | UNLOCK | ๐ข Green |
| **โฅ 1 second** | LOCK | ๐ฃ Purple |
## ๐ก LED Color Guide
| Color | Meaning |
|-------|---------|
| ๐ข **Green (holding)** | Short press - will UNLOCK |
| ๐ฃ **Purple (holding)** | Long press - will LOCK |
| ๐ต **Blue** | Connecting to Wi-Fi (normal scan) |
| ๐ฉต **Cyan** | Fast reconnect in progress |
| ๐ข **Green (2 blinks)** | Door unlocked successfully |
| ๐ฃ **Purple (2 blinks)** | Door locked successfully |
| ๐ก **Yellow (2 blinks)** | NTP sync failed (continuing anyway) |
| ๐ก **Yellow (4 blinks)** | Time sync error |
| ๐ **Orange (3 blinks)** | Wi-Fi connection timeout |
| ๐ด **Red (3 blinks)** | API error |
| ๐ด **Red (6 fast blinks)** | Authentication error (401) |
## ๐ Atomic Battery Base (Optional)
The project supports the [M5Stack Atomic Battery Base](https://docs.m5stack.com/en/atom/Atomic%20Battery%20Base) (200mAh, 3.7V) for portable battery-powered operation.
### Specifications
| Property | Value |
|----------|-------|
| Battery | 3.7V @ 200mAh LiPo |
| Boost converter | ETA9085E10 (5V output) |
| Charging IC | LGS4056HDA (USB-C, 223mA) |
| Standby current | 2.55uA (boost converter) |
### Realistic Battery Life
**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:
- **Estimated autonomy: 18-50 hours** depending on board revision
- Wake cycle consumption (~80-150mA for 1-5s) is <0.5% of total drain
- **Sleep current dominates** โ the USB/serial chip (3-5mA) is the main drain and cannot be disabled in software
- For longer battery life, consider a larger battery (750-1000mAh โ 3-8 days)
### Charging
- Connect USB-C to charge (blue LED = charging, green LED = full)
- Dip switch: **boost** for normal operation, **charge** when connected to USB
- Full charge: ~1 hour at 223mA
## ๐ก SwitchBot API
The project uses the SwitchBot API v1.1:
- **Endpoint**: `https://api.switch-bot.com/v1.1/devices/{deviceId}/commands`
- **Authentication**: token + secret with signed headers:
- `Authorization`: your token
- `nonce`: random hex string
- `t`: Unix timestamp in milliseconds (1970 epoch)
- `sign`: Base64(HMAC-SHA256(token + t + nonce, secret))
- **Commands**: `unlock` or `lock`
MicroPython 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.
Full documentation: https://github.com/OpenWonderLabs/SwitchBotAPI
## ๐ Monitoring and Debug
Connect to the serial terminal (115200 baud) to see:
**Fresh boot:**
```
==================================================
M5Stack ATOM Lite - SwitchBot Lock Pro Controller
(Deep Sleep Version)
==================================================
Device ID: XXXXXXXXXXXX
Wake button: GPIO39
Long press threshold: 1000ms
Controls:
Short press (<1s) = UNLOCK (green LED)
Long press (>1s) = LOCK (purple LED)
Entering deep sleep...
Wake trigger: GPIO39 LOW (button press)
Power consumption: ~10uA
==================================================
```
**Short press - UNLOCK (fast reconnect):**
```
==================================================
WAKE FROM DEEP SLEEP - Button pressed!
==================================================
Button held for 450ms
Action: UNLOCK
Fast reconnect available (ch=1)
Fast reconnect (ch=1)... OK!
IP: 192.168.178.87
โ RTC time valid, skipping NTP sync
Sending UNLOCK command...
HTTP status: 200
Response: {"statusCode":100,"body":{},"message":"success"}
โ Door unlocked successfully!
Entering deep sleep...
```
**Long press - LOCK (first boot, normal scan):**
```
==================================================
WAKE FROM DEEP SLEEP - Button pressed!
==================================================
Button held for 1552ms
Action: LOCK
Connecting to Wi-Fi: MySSID...
..........................
โ Connected to Wi-Fi!
IP: 192.168.178.87
Cached ch=1 for fast reconnect
RTC time invalid, syncing NTP...
Synchronizing time via NTP...
โ Time synchronized via NTP (UTC).
Sending LOCK command...
HTTP status: 200
Response: {"statusCode":100,"body":{},"message":"success"}
โ Door locked successfully!
Entering deep sleep...
```
## ๐งช Automated Tests
Tests run on standard CPython inside Docker โ no MicroPython or hardware needed. Hardware modules are replaced by stubs automatically.
```bash
make test # Build Docker image + run all tests
make test-clean # Remove the test Docker image
```
Tests also run automatically via GitHub Actions on every push and PR to `main`.
**What's tested** (53 test cases):
| Area | Tests |
|------|-------|
| Epoch conversion | Offset constant, `unix_time_ms()` range and precision |
| HMAC-SHA256 | Manual RFC 2104 vs stdlib, long keys, empty inputs |
| Auth headers | Required keys, uppercase Base64 signature, timestamp format |
| HTTP send_command | Retry logic, 401 no-retry, response cleanup, attribute-raise resilience |
| RTC memory | Save/load roundtrip, invalid BSSID, channel bounds |
| LED brightness | `_scale()` math, clamping at 255 |
| Wi-Fi connect | Already-connected, timeout, fast reconnect, bssid fallback |
## ๐ ๏ธ Troubleshooting
See the **Troubleshooting** section in [SETUP.md](SETUP.md) for:
- Connection issues with the device
- Errors while flashing the firmware
- Wi-Fi connection problems
- SwitchBot API errors
- Button issues
- Memory handling
## ๐ Security
โ ๏ธ **IMPORTANT:**
- `config.py` contains sensitive credentials and is excluded from Git
- Do not share your Token or Secret
- Use a secure Wi-Fi network (WPA2/WPA3)
- Consider using a dedicated VLAN for IoT devices
## ๐ Acknowledgements
- [MicroPython](https://micropython.org/) - Python for microcontrollers
- [M5Stack](https://m5stack.com/) - Quality ESP32 hardware
- [SwitchBot](https://www.switch-bot.com/) - Smart home devices