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.
- โ 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
- 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
- MicroPython v1.24.x or later โ download from micropython.org/download/M5STACK_ATOM
- VS Code (optional) for editing
mpremotefor file upload and execution- Python 3.x on your computer
- SwitchBot account with API token and secret (for API v1.1 signing)
Follow the full guide in 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)
# 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.pyEdit config.py with your details:
# 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")- Connect the M5Stack ATOM via USB and identify the serial port (e.g.,
/dev/cu.usbserial-XXXXon macOS,COM3on Windows,/dev/ttyUSB0on Linux). - Upload the files with
mpremote(replace the port with yours):mpremote connect /dev/cu.usbserial-XXXX cp main.py :main.py mpremote connect /dev/cu.usbserial-XXXX cp config.py :config.py
Execute the script via mpremote:
mpremote connect /dev/cu.usbserial-XXXX run main.pyThen press the button on the M5Stack ATOM to control the lock.
.
โโโ 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
- On boot/reset: Shows startup message, then enters deep sleep (~10uA)
- 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
- 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)
| Press Duration | Action | LED While Holding |
|---|---|---|
| < 1 second | UNLOCK | ๐ข Green |
| โฅ 1 second | LOCK | ๐ฃ Purple |
| 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) |
The project supports the M5Stack Atomic Battery Base (200mAh, 3.7V) for portable battery-powered operation.
| Property | Value |
|---|---|
| Battery | 3.7V @ 200mAh LiPo |
| Boost converter | ETA9085E10 (5V output) |
| Charging IC | LGS4056HDA (USB-C, 223mA) |
| Standby current | 2.55uA (boost converter) |
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)
- 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
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 tokennonce: random hex stringt: Unix timestamp in milliseconds (1970 epoch)sign: Base64(HMAC-SHA256(token + t + nonce, secret))
- Commands:
unlockorlock
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
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...
Tests run on standard CPython inside Docker โ no MicroPython or hardware needed. Hardware modules are replaced by stubs automatically.
make test # Build Docker image + run all tests
make test-clean # Remove the test Docker imageTests 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 |
See the Troubleshooting section in SETUP.md for:
- Connection issues with the device
- Errors while flashing the firmware
- Wi-Fi connection problems
- SwitchBot API errors
- Button issues
- Memory handling
config.pycontains 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
- MicroPython - Python for microcontrollers
- M5Stack - Quality ESP32 hardware
- SwitchBot - Smart home devices