A full-featured analog watchface and fitness tracker app for the Hacktor Watch 2.0, built with Zephyr OS 4.3 on the ESP32-S3 platform.
Built during the Hacktor Watch hackathon (April 2026).
- Beautiful analog clock with hour, minute, and second hands (red) on a round 240x240 GC9A01 display
- All 12 hour numbers displayed (12/3/6/9 in large font, others in small font)
- 60 minute tick dots (gray) and 8 hour tick dots (white) around the dial
- Date display (e.g. "Sat 4 Apr") above center
- Battery indicator with graphical icon (green fill, turns yellow below 20%) and percentage text
- Step counter in large light-blue font below center
- Hardware pedometer using the LSM6DSL's built-in step counter (
PEDO_ENregister) - Steps read directly from hardware registers
STEP_COUNTER_L/H(0x4B/0x4C) — zero CPU overhead - Daily step goal ring — an arc around the watchface edge that fills green as you walk toward 6000 steps, turns gold when the goal is reached
- Automatic daily reset at midnight (writes
PEDO_RST_STEPtoCTRL10_C)
- Display enters sleep mode after 30 seconds of inactivity (backlight off + display blanking)
- Three wake methods:
- Double-tap on the touchscreen (CST816S capacitive touch)
- Wrist-raise gesture via LSM6DSL hardware tilt detection (
WRIST_TILT_EN+TILT_ENinCTRL10_C) - Boot button press (GPIO 0)
- Haptic buzz (DRV2605) on every wake event for tactile feedback
- CPU continues running in sleep (software clock keeps time, pedometer keeps counting)
- Advertises as "Hacktor Watch" via BLE peripheral
- Custom GATT service (
12345678-1234-5678-1234-56789abcdef0) with two characteristics:- Weather characteristic (
...def1): write UTF-8 string"TEMP,CONDITION"(e.g."22,1") to display weather on the watchface- Conditions: 0=Clear, 1=Sunny, 2=Cloudy, 3=Rain, 4=Snow, 5=Storm
- WiFi characteristic (
...def2): write"SSID,PASSWORD,CITY"for future WiFi weather fetch
- Weather characteristic (
- Compatible with nRF Connect app on iOS/Android
Access via USB serial for debugging and configuration:
battery— show battery percentage and voltageimu— show accelerometer/gyroscope data + tilt status registerssteps— show current step countbuzz [1-123]— play a DRV2605 haptic effect from the ROM librarysettime HH:MM:SS— set the software clocksetdate DD/MM/YYYY W— set date (W: 0=Sun..6=Sat)i2c scan i2c@60027000— scan I2C bus for connected devices
Hacktor Watch 2.0 — open-source wearable by Dan Tudose & Radu Pascale
| Component | Chip | Interface | I2C Address |
|---|---|---|---|
| MCU | ESP32-S3 (dual-core 240MHz, 512KB SRAM) | — | — |
| Display | GC9A01 1.28" 240x240 round LCD | SPI3 (GPIO 11/12/13, CS=GPIO4, DC=GPIO5, RST=GPIO3) | — |
| Touch | CST816S capacitive | I2C1 (GPIO8/GPIO9) | 0x15 |
| IMU | LSM6DSLTR (6-axis accel+gyro) | I2C1 | 0x6A |
| Fuel Gauge | MAX17048 | I2C1 | 0x36 |
| Haptic | DRV2605 (ERM motor) | I2C1 | 0x5A |
| Backlight | GPIO-controlled LED | GPIO7 | — |
| LCD Power | PMIC enable | GPIO18 (always on via gpio-hog) | — |
| Buttons | BOOT=GPIO0, RESET=EN | — | — |
| Battery | 200mAh LiPo + BQ24040 charger | USB-C | — |
| Flash | W25Q256 32MB external SPI | — | — |
| PSRAM | ESP-PSRAM64H 8MB | — | — |
| Microphone | LMD2718T261-OA1 | — | — |
| Speaker | MAX98357 I2S amplifier | — | — |
All I2C peripherals share a single bus (I2C1) at 400kHz on GPIO8 (SDA) / GPIO9 (SCL).
hacktor_hello/
├── CMakeLists.txt # Build configuration
├── prj.conf # Zephyr Kconfig (drivers, LVGL, BLE, etc.)
├── app.overlay # Devicetree overlay (display, touch, IMU, fuel gauge, haptic)
├── build.sh # Build & flash script
├── README.md # This file
└── src/
├── main.c # App entry, main loop, time tracking, sleep/wake logic
├── display.c / .h # Display + LVGL init, sleep/wake (backlight + blanking)
├── watchface.c / .h # Analog watchface UI (dial, hands, labels, goal arc)
├── battery.c / .h # MAX17048 fuel gauge (SOC%, voltage)
├── imu.c / .h # LSM6DSL (accel/gyro, hardware pedometer, wrist-tilt)
├── haptic.c / .h # DRV2605 haptic motor (ROM effects library)
├── ble.c / .h # BLE peripheral (weather + WiFi GATT characteristics)
└── weather.c / .h # Weather data storage (WiFi fetch stub)
- Zephyr OS 4.3.0
- Zephyr SDK 0.16.8
- ESP32-S3 toolchain
- BLE blobs:
west blobs fetch hal_espressif
./build.sh --pristine # full clean build
./build.sh # incremental build./build.sh --flash --port /dev/ttyACM0~623KB flash — well under the 4MB limit.
- Flash the firmware to the Hacktor Watch
- The watchface appears with default time 10:10:00
- Set time via USB shell:
settime 14:30:00 - Set date via USB shell:
setdate 04/04/2026 6 - Walk and watch the step counter and goal ring update
- Wait 30s or let the display sleep, then double-tap, raise wrist, or press BOOT to wake
- Send weather via nRF Connect: connect to "Hacktor Watch", write
22,1(UTF-8) to characteristic...def1
- Implement true ESP32 light sleep with RTC wake sources
- Investigate GC9A01 hardware sleep commands (
SLPIN/SLPOUT0x10/0x11) instead of power cycling GPIO18 (power cycling was tested and breaks re-init) - Wake via GPIO interrupt from IMU INT1 pin (pin mapping TBD from schematic)
- Target: extend battery life from hours to days on the 200mAh cell
- Swipe-based navigation between screens (left/right to switch, up/down for quick settings)
- Home screen — the analog watchface (current)
- Fitness screen — detailed step history, distance estimate (steps x stride), calories estimate, daily/weekly graph
- Weather screen — current temperature, condition icon, hourly/daily forecast (once WiFi is working)
- Notifications screen — display BLE notifications (ANCS for iPhone, or custom via nRF Connect)
- Settings screen — brightness control, sleep timeout, step goal, watchface style
- Stopwatch / Timer screen — with haptic alerts at completion
- Alarm screen — set alarms that vibrate via DRV2605
- Connect to WiFi using credentials sent via BLE (SSID, password, city)
- Fetch weather from wttr.in (free, no API key) over HTTP
- Auto-refresh every 30 minutes, then disable WiFi radio to save power
- Display temperature, condition, and forecast on a dedicated weather screen
- Show weather icon (sun/cloud/rain/snow) on watchface complication
- Implement an on-screen keyboard using the touchscreen for short text input
- T9-style circular keyboard optimized for the round display
- Use cases: enter WiFi password, set alarm label, quick reply to notifications
- Handwriting recognition using the IMU (draw letters in the air)
- Distance estimation — step count x configurable stride length
- Calorie estimation — based on steps, weight, and activity intensity
- Activity detection — walking vs running vs idle using IMU data patterns
- Inactivity reminders — vibrate every hour if no steps detected
- Sleep tracking — detect sleep onset/wake using overnight IMU movement patterns
- Heart rate (if hardware supports external sensor via I2C sensorhub)
- Workout mode — dedicated tracking screen with lap timer, pace, HR zones
- Multiple watchface styles (analog, digital, minimal, data-heavy)
- Configurable complications (weather, steps, battery, date, next alarm)
- Color theme selection (red, blue, green, custom)
- Animated second hand with smooth 60fps sweep (currently 1fps tick)
- ANCS integration — receive iPhone notifications (calls, messages, app alerts)
- Apple Media Service — music play/pause/skip controls from the watch
- Find my phone — trigger an alert on the phone from the watch
- Phone battery sync — show phone battery level on the watchface
- Time sync — auto-set watch time from phone's current time via BLE
- Quick replies — predefined responses to messages sent via BLE
- Voice memos — record short clips using the onboard LMD2718 microphone
- Audio playback — play alerts/sounds via MAX98357 I2S amplifier + speaker
- Keyword detection — on-device TinyML model for "Hey Watch" wake word
- Clock down CPU from 240MHz to 80MHz during idle/sleep
- Disable gyroscope when not needed (accel-only mode for pedometer)
- BLE advertising interval optimization (slower when not actively connecting)
- Display frame rate reduction when showing static content
- Target: 2+ days battery life with normal usage
- GC9A01 R/B color swap: the display panel swaps red and blue channels. Use
0x0000FFfor red,0xFF0000for blue, green is unchanged. White/gray are unaffected. - LCD power cycling: cutting GPIO18 (LCD power) during sleep permanently breaks display re-initialization. Use backlight-only sleep instead.
- LSM6DSL wrist-tilt: hardware tilt detection is enabled via direct register writes (CTRL10_C + TAP_CFG INTERRUPTS_ENABLE). Reliability depends on wearing position.
- Software clock: time is tracked via
k_uptime_get()with no RTC — drifts slightly over days. Set time via shell or future BLE time sync.
Built on the open-source Hacktor Watch 2.0 hardware platform.

