A training companion app with a virtual pet mascot for Amazfit Balance smartwatch, powered by Zepp OS 3.0+ and AI-driven real-time coaching.
ZeppCompanion is a multi-component fitness system that combines:
- Wearable App (Zepp OS) — Real-time training UI with heart rate, GPS pace tracking, and an animated virtual pet companion that provides motivational coaching
- Backend API + Web Dashboard (Next.js) — Training configuration, session history, charts, and user management
- LLM Integration — Flexible AI provider (OpenAI/Anthropic/custom) generates personalized coaching messages during workouts
┌──────────────────┐ BLE ┌───────────────┐ HTTPS ┌─────────────────┐
│ Amazfit Watch │ ◄──────────────► │ Side Service │ ◄────────────► │ Backend (Next) │
│ (Zepp OS App) │ MessageBuilder │ (Phone/Zepp) │ fetch() │ + Prisma+LLM+TTS│
└──────────────────┘ └───────────────┘ └─────────────────┘
│
┌──────┴─────────┐
│ Web Dashboard │
│ (React/TW) │
└────────────────┘
| Component | Technology |
|---|---|
| Wearable | Zepp OS 3.0+, JavaScript, @zeppos/zml MessageBuilder |
| Backend | Next.js 15, TypeScript, Prisma, PostgreSQL/SQLite |
| Frontend | React, Tailwind CSS, Recharts, React Hook Form, Zod |
| LLM | Flexible — OpenAI, Anthropic, or any OpenAI-compatible API |
| TTS | Piper TTS (local, offline) — Spanish voice es_MX-claude-high |
| Auth | JWT (access + refresh tokens), bcrypt |
| Target Device | Amazfit Balance (480×480 round, API 3.7) |
ZeppCompanion/
├── app.js # Zepp OS app entry point
├── app.json # Zepp OS config (pages, permissions, targets)
├── page/
│ ├── home/index.js # Main menu
│ ├── training-select/index.js # Training list from backend
│ ├── pre-training/index.js # Training summary + start
│ ├── active-training/index.js # Live training with sensors + mascot
│ ├── training-summary/index.js # Post-training results
│ ├── settings/index.js # App configuration
│ └── i18n/ # Localization (en-US, es-ES)
├── app-side/index.js # Side Service (phone) — BLE ↔ HTTP bridge
├── app-service/index.js # Background service (watch)
├── shared/
│ ├── protocol.js # BLE message type constants
│ ├── message.js # Device MessageBuilder
│ ├── message-side.js # Side Service MessageBuilder
│ └── device-polyfill.js # setTimeout/setInterval polyfill
├── utils/
│ ├── constants.js # Layout, colors, screen dimensions
│ ├── format.js # Time/pace/distance formatting + haversine
│ ├── companion-engine.js # Local coaching rules + fallback messages
│ ├── sensor-manager.js # HR + GPS sensor lifecycle
│ ├── mascot.js # Mascot animation state machine
│ └── audio-player.js # @zos/media Player wrapper for TTS playback
├── assets/amazfit-balance.r/
│ └── mascot/ # Sprite frames (idle, talk, celebrate, worried)
└── backend/
├── package.json
├── prisma/schema.prisma # Database schema
├── src/
│ ├── app/ # Next.js App Router
│ │ ├── api/ # REST API routes
│ │ │ ├── auth/ # register, login, me, refresh
│ │ │ ├── trainings/ # CRUD
│ │ │ ├── sessions/ # Start, complete, list
│ │ │ ├── companion/ # LLM coaching endpoint
│ │ │ └── stats/ # Aggregate stats
│ │ ├── dashboard/ # Training list + stats
│ │ ├── trainings/ # Create/edit/detail
│ │ └── history/ # Session history + charts
│ ├── lib/
│ │ ├── auth.ts # JWT helpers
│ │ ├── llm.ts # Flexible LLM provider
│ │ ├── prompts.ts # Coaching prompt templates
│ │ ├── tts.ts # Piper TTS integration (local speech synthesis)
│ │ └── validation.ts # Zod schemas
│ └── components/ # React UI components
└── .env.example
The companion engine powers personalized coaching during training with both local intelligence and optional LLM enrichment.
How It Works:
-
Local Rules Fire First — Safety and baseline decisions happen instantly on-device:
- HR zone enforcement (warn if exceeding safe limits)
- Pace correction (suggest slowdown if too fast)
- Milestone detection (distance, time, or zone achievements)
-
LLM Provides Richer Messages — When backend connectivity is available, the LLM generates personalized, contextual coaching messages and generates spoken audio via local Piper TTS based on:
- Current HR zone and trend
- Pace vs. goal
- Training progress
- User personality preference
-
60+ Fallback Messages in Spanish — Complete offline support with pre-crafted messages for all scenarios, ensuring the watch app works standalone
-
Companion Styles — Three personality modes:
- Motivational — Energetic, encouraging ("You're crushing it!")
- Strict — Focused, directive ("Maintain your pace. No excuses.")
- Neutral — Informational, matter-of-fact ("Current HR: 165 bpm. Zone: Threshold.")
-
Configurable Frequency — Users set message cadence:
- 60s — Frequent check-ins (aggressive)
- 90s — Balanced (recommended)
- 120s — Minimal interruption
Companion messages can be spoken aloud on the watch using local text-to-speech powered by Piper TTS.
How it works:
- Backend generates coaching text via LLM
- Piper TTS converts text to MP3 audio locally (no cloud API needed)
- Audio is sent as base64 alongside the text response
- Side Service passes audio through to the watch via BLE
- Watch writes MP3 to temp file and plays via
@zos/mediaPlayer API
Setup:
cd backend
chmod +x scripts/setup-piper.sh
./scripts/setup-piper.sh
pip3 install piper-ttsAdd to .env:
TTS_ENABLED=true
PIPER_PATH=/path/to/piper
PIPER_MODEL_PATH=./piper/es_MX-claude-high.onnx
Requirements: Python 3.9+, ffmpeg (for WAV→MP3 conversion)
Note: TTS is completely optional. If disabled or unavailable, the app works identically but with text-only messages.
The animated mascot responds visually to training conditions using a sprite-based state machine.
| State | When | Animation |
|---|---|---|
| IDLE | Default state, normal training pace | Relaxed, breathing |
| TALKING | Delivering a coaching message | Mouth open, gesturing |
| CELEBRATING | Milestone achieved or exceptional performance | Jumping, celebratory |
| WORRIED | HR exceeding safe zone or pace warning triggered | Concerned expression |
- Node.js >= 18
- npm >= 9
- Zeus CLI >= 1.8 — Zepp OS build tool (
npm install -g @nicogp/zeus-cli) - Zepp OS Simulator or physical Amazfit Balance device
git clone <repo-url>
cd ZeppCompanion
npm installcd backend
npm install
# Set up environment
cp .env.example .env.localEdit .env.local with your configuration:
DATABASE_URL=file:./dev.db # SQLite for local dev
JWT_SECRET=<random-64-chars>
JWT_REFRESH_SECRET=<random-64-chars>
LLM_PROVIDER=openai # openai | anthropic | custom
LLM_API_KEY=sk-...
LLM_MODEL=gpt-4o-mini
NEXT_PUBLIC_APP_URL=http://localhost:3000
Run migrations and start the server:
# Run database migrations
npx prisma migrate dev
# (Optional) Seed sample data
npx prisma db seed
# Start the dev server
npm run devThe web dashboard is available at http://localhost:3000.
Test Credentials (from seed):
- Email:
test@ZeppCompanion.com - Password:
password123
From the project root (not backend/):
cd /path/to/ZeppCompanion
# Build for simulator
zeus dev
# Or build a .zab package for device install
zeus buildThe Zeus dev server launches the Zepp OS simulator. Use the simulator to test navigation, sensor data, and companion messages.
- Open the backend web dashboard and create an account
- Create a training configuration
- In the wearable Settings page, set the backend URL to your local server (e.g.,
http://<your-ip>:3000) - The Side Service on your phone bridges BLE messages from the watch to HTTP calls to the backend
Note: For local development without a physical watch, the wearable app uses mock training data. Backend connectivity is optional until Sprint 2+.
| Variable | Required | Description |
|---|---|---|
DATABASE_URL |
Yes | Prisma connection string (file:./dev.db for SQLite) |
JWT_SECRET |
Yes | Secret for signing access tokens |
JWT_REFRESH_SECRET |
Yes | Secret for signing refresh tokens |
LLM_PROVIDER |
Yes | openai, anthropic, or custom |
LLM_API_KEY |
Yes | API key for the chosen LLM provider |
LLM_MODEL |
No | Model ID (default: gpt-4o-mini) |
LLM_BASE_URL |
No | Custom base URL for self-hosted LLM endpoints |
NEXT_PUBLIC_APP_URL |
No | Public app URL (default: http://localhost:3000) |
TTS_ENABLED |
No | Enable local TTS audio generation (true/false) |
PIPER_PATH |
No | Path to piper-tts binary |
PIPER_MODEL_PATH |
No | Path to Piper ONNX voice model |
- Home → Select "Start Training"
- Training Selection → Pick from list synced with backend
- Pre-Training → Review goals, tap "Start"
- Active Training → Live HR, pace, progress ring, animated mascot delivers coaching messages every 60–120s
- Summary → Results vs. goals, final companion message
- Register/Login → Email + password
- Dashboard → View trainings and aggregate stats
- Create Training → Set type, duration, goals, companion personality and frequency
- History → Browse past sessions with pace/HR charts
| Method | Path | Description |
|---|---|---|
| POST | /api/auth/register |
Create account |
| POST | /api/auth/login |
Login → JWT |
| GET | /api/auth/me |
Current user |
| GET/POST | /api/trainings |
List / Create training |
| GET/PUT/DELETE | /api/trainings/[id] |
Training detail / update / delete |
| POST | /api/sessions |
Start training session |
| POST | /api/sessions/[id]/complete |
Complete session |
| POST | /api/companion/message |
Get AI coaching message |
| GET | /api/stats |
Aggregate user stats |
ZeppCompanion is containerized and ready for cloud deployment.
A production-ready Docker setup is included:
# Build the image
docker build -t ZeppCompanion .
# Run locally
docker run -p 3000:3000 \
-e DATABASE_URL="postgresql://user:pass@db:5432/ZeppCompanion" \
-e JWT_SECRET="your-secret" \
-e LLM_PROVIDER="openai" \
-e LLM_API_KEY="sk-..." \
ZeppCompanionFor local development with PostgreSQL:
docker-compose upThe application is compatible with:
-
Railway — Zero-config PostgreSQL + Node.js hosting
railway link railway up
-
Vercel — Next.js frontend and API routes
- Deploy the
backend/directory as a Vercel project - Set environment variables in project settings
- PostgreSQL via Vercel's PostgreSQL add-on
- Deploy the
For production deployments, configure PostgreSQL:
# Update DATABASE_URL in .env.local or deployment platform
DATABASE_URL="postgresql://user:password@host:5432/ZeppCompanion"
# Run migrations
npx prisma migrate deploy
# (Optional) Seed initial data
npx prisma db seedReplace user, password, host, and database name with your PostgreSQL credentials.
MIT