Skip to content

pcamposu/plumet-core

Repository files navigation

Plumet Plumet Core

CI/CD Pipeline Docker License Node TypeScript NestJS

Overview

Plumet Core is the central server component of the Plumet fall detection system. Built with NestJS, it provides a REST API, MQTT broker, and real-time alert orchestration for elderly care monitoring.

How It Works

  1. MQTT Broker: Runs an embedded Aedes MQTT broker over WebSocket for device and mobile app communication
  2. Fall Event Pipeline: Receives fall detection events from ESP8266 devices and processes them in real-time
  3. User Alerting: Publishes fall alerts to mobile apps via MQTT, triggering push notifications
  4. Data Persistence: Stores fall events and device associations in MySQL database
  5. REST API: Provides endpoints for mobile apps to manage devices, users, and view fall history

Architecture Overview

flowchart TB
    subgraph Clients["Clients"]
        ESP["ESP8266<br/>Device"]
        Mobile["Mobile App<br/>iOS/Android"]
    end

    subgraph Backend["Plumet Core"]
        subgraph API["REST API<br/>NestJS"]
            Auth["Auth Module<br/>JWT"]
            Devices["Devices Module<br/>CRUD"]
            Falls["Falls Module<br/>Statistics"]
            Users["Users Module<br/>Profile"]
        end

        subgraph MQTT["MQTT Broker<br/>Aedes over WebSocket"]
            Broker["Message Broker"]
            AuthMQTT["Device/Auth<br/>Validator"]
            Handler["Fall Handler<br/>Processor"]
        end

        DB[(MySQL<br/>Database)]
        QR["QR Generator"]
    end

    ESP -->|"MQTT<br/>device/{MAC}/fall"| Broker
    ESP -->|"Auth<br/>MAC Address"| AuthMQTT

    Mobile -->|"MQTT<br/>plumet-mobile-{UID}"| Broker
    Mobile -->|"REST<br/>HTTPS"| API

    Broker -->|"Fall Event"| Handler
    Handler -->|"Save Event"| DB
    Handler -->|"Alert<br/>user/{UID}/alert"| Broker

    API <-->|"Query"| DB
    API -->|"Generate"| QR

    Mobile -->|"Scan QR"| QR

    style ESP fill:#EDD60A
    style Mobile fill:#3b82f6
    style Broker fill:#10b981
    style DB fill:#6366f1
    style QR fill:#ec4899
Loading

System Flow

sequenceDiagram
    participant ESP as ESP8266 Device
    participant MQTT as MQTT Broker
    participant Handler as Fall Handler
    participant DB as Database
    participant Mobile as Mobile App

    Note over ESP,Mobile: Fall Detection Sequence

    ESP->>+MQTT: Connect (MAC as Client ID)
    MQTT-->>ESP: Authenticated

    ESP->>MQTT: Publish to device/{MAC}/fall
    Note right of ESP: { gForce: 3.2, baselineX: 0.1, ... }

    MQTT->>+Handler: handleFallDetection()
    Handler->>DB: Save fall event
    Handler->>DB: Update device.lastFallAt
    DB-->>Handler: Saved

    Handler->>MQTT: Publish to user/{UID}/alert
    Note right of Handler: For each associated user

    MQTT->>+Mobile: Fall alert received
    Mobile-->>MQTT: Acknowledge
    Mobile-->>-Handler: Display notification

    Handler-->>-MQTT: Complete
    MQTT-->>-ESP: Disconnect (battery saving)
Loading

Key Design Decisions

  • Embedded MQTT Broker: Runs Aedes broker in-process for real-time messaging without external dependencies
  • Dual Authentication Flow: Separate auth paths for ESP8266 devices (MAC address) and mobile apps (JWT)
  • Event-Driven Architecture: Devices only connect when detecting falls, then disconnect immediately
  • Eternal Association Tokens: QR codes printed on products use tokens that never expire and can be reused
  • Graceful Shutdown: 10-second timeout ensures clean connection drain on container termination

Key Features

  • REST API - NestJS-based API with JWT authentication
  • MQTT Broker - Embedded Aedes broker over WebSocket (port 8884)
  • Device Management - Many-to-many user-device relationships with custom names
  • Fall Statistics - Time-windowed aggregation (24h, 7d, 30d)
  • QR Code Generation - Device provisioning via internal API
  • Docker Support - Multi-arch images (amd64, arm64) for flexible deployment

Table of Contents

Quick Start

Prerequisites

  • Node.js 24 or higher
  • MySQL 8.0 or higher
  • npm package manager

Local Development

npm install cp .env.example .env npm run start:dev

The API will be available at http://localhost:3000/api/v1

Docker Deployment

Using Pre-built Image

docker pull ghcr.io/pcamposu/plumet-core:latest

docker run -d \
  --name plumet-core \
  -p 3000:3000 \
  -p 8884:8884 \
  -e DB_HOST=your-db-host \
  -e DB_DATABASE=plumet \
  -e DB_USERNAME=root \
  -e DB_PASSWORD=your-password \
  -e JWT_SECRET=your-secret-key \
  ghcr.io/pcamposu/plumet-core:latest

Building Locally

docker build -t plumet-core .

docker run -d \
  --name plumet-core \
  -p 3000:3000 \
  -p 8884:8884 \
  --env-file .env \
  plumet-core

Docker Compose

docker-compose up -d
docker-compose logs -f
docker-compose down

Environment Configuration

Required Variables

Variable Description Example
DB_HOST MySQL host localhost
DB_PORT MySQL port 3306
DB_DATABASE Database name plumet
DB_USERNAME Database user root
DB_PASSWORD Database password your-password
JWT_SECRET JWT signing secret random-secret-key
JWT_EXPIRATION JWT TTL 7d
INTERNAL_API_SECRET Internal API auth internal-secret-key

Optional Variables

Variable Default Description
PORT 3000 HTTP server port
API_PREFIX api/v1 API route prefix
MQTT_WS_PORT 8884 MQTT WebSocket port
NODE_ENV development Environment mode

Example .env File

DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=plumet
DB_USERNAME=root
DB_PASSWORD=your-secure-password

JWT_SECRET=your-random-secret-key-min-32-chars
JWT_EXPIRATION=7d

INTERNAL_API_SECRET=your-internal-secret

PORT=3000
API_PREFIX=api/v1
MQTT_WS_PORT=8884

NODE_ENV=development

API Documentation

Authentication Endpoints

Register

POST /api/v1/auth/register
Content-Type: application/json

{
  "email": "user@example.com",
  "password": "SecurePass123!",
  "name": "John Doe"
}

Response 201:
{
  "user": {
    "id": "uuid",
    "email": "user@example.com",
    "name": "John Doe",
    "isVerified": false,
    "createdAt": "2025-01-25T10:00:00.000Z"
  },
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Login

POST /api/v1/auth/login
Content-Type: application/json

{
  "email": "user@example.com",
  "password": "SecurePass123!"
}

Response 200:
{
  "user": { ... },
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Device Endpoints

Associate Device (QR Code)

POST /api/v1/devices/associate
Authorization: Bearer <token>
Content-Type: application/json

{
  "token": "ABC123XYZ",
  "name": "Grandma"
}

Response 200:
{
  "id": "association-uuid",
  "device": {
    "id": "device-uuid",
    "macAddress": "AA:BB:CC:DD:EE:FF"
  },
  "name": "Grandma",
  "associatedAt": "2025-01-25T10:00:00.000Z"
}

List My Devices

GET /api/v1/devices
Authorization: Bearer <token>

Response 200:
[{
  "id": "device-uuid",
  "macAddress": "AA:BB:CC:DD:EE:FF",
  "lastFallAt": "2025-01-25T09:30:00.000Z",
  "customName": "Grandma"
}]

Fall Statistics Endpoints

Get Fall Stats

GET /api/v1/falls/stats?deviceId=device-uuid
Authorization: Bearer <token>

Response 200:
{
  "total": 42,
  "last24h": 2,
  "last7d": 8,
  "last30d": 15,
  "recent": [
    {
      "id": "fall-uuid",
      "deviceId": "device-uuid",
      "gForce": 3.2,
      "detectedAt": "2025-01-25T09:30:00.000Z"
    }
  ]
}

MQTT Protocol

Connection

Property Value
Protocol MQTT over WebSocket
Host localhost:8884 (or configured host)
Path /mqtt
QoS 1

Device Authentication

Client ID Format: {MAC_ADDRESS} (uppercase, colon-separated)

No username/password required. Device is authenticated by MAC address existence in database.

const mqtt = require('mqtt');

const client = mqtt.connect('ws://localhost:8884/mqtt', {
  clientId: 'AA:BB:CC:DD:EE:FF'
});

client.on('connect', () => {
});

Mobile App Authentication

Client ID Format: plumet-mobile-{USER_ID}_{TIMESTAMP}

No username/password required. User is authenticated by JWT in REST API, then connects with user ID in client ID.

const client = mqtt.connect('ws://localhost:8884/mqtt', {
  clientId: `plumet-mobile-${userId}_${Date.now()}`
});

client.subscribe(`user/${userId}/alert`);

Topics

Publish Fall Detection

Topic: device/{MAC_ADDRESS}/fall

Payload:

{
  "macAddress": "AA:BB:CC:DD:EE:FF",
  "gForce": 3.2,
  "baselineX": 0.1,
  "baselineY": -0.2,
  "baselineZ": 9.8
}

Subscribe to Fall Alerts

Topic: user/{USER_ID}/alert

Payload:

{
  "type": "fall_detected",
  "deviceId": "device-uuid",
  "deviceName": "Grandma",
  "macAddress": "AA:BB:CC:DD:EE:FF",
  "timestamp": "2025-01-25T09:30:00.000Z",
  "data": {
    "gForce": 3.2,
    "baselineX": 0.1,
    "baselineY": -0.2,
    "baselineZ": 9.8
  }
}

Device Commands

Topic: device/{MAC_ADDRESS}/command

Used by backend to send commands to devices (future use).

Database Schema

erDiagram
    Users ||--o{ DeviceUsers : "owns"
    Devices ||--o{ DeviceUsers : "associated with"
    Users ||--o{ Falls : "via device"
    Devices ||--o{ Falls : "generates"

    Users {
        uuid id PK
        string email UK
        string password_hash
        string name
        boolean is_verified
        timestamp created_at
        timestamp updated_at
    }

    Devices {
        uuid id PK
        string mac_address UK
        string association_token UK
        timestamp last_fall_at
        timestamp created_at
    }

    DeviceUsers {
        uuid id PK
        uuid device_id FK
        uuid user_id FK
        string name
        timestamp associated_at
    }

    Falls {
        uuid id PK
        uuid device_id FK
        float g_force
        float baseline_x
        float baseline_y
        float baseline_z
        timestamp detected_at
        timestamp created_at
    }
Loading

Entity Relationships

  • Users ↔ Devices: Many-to-many via DeviceUsers join table
  • Devices → Falls: One-to-many (one device can have many fall events)
  • Cascade Delete: Deleting a user removes their device associations (not the devices)

Development

Project Structure

src/
├── main.ts                 # Application entry point
├── app.module.ts           # Root module configuration
├── common/                 # Shared utilities
│   ├── decorators/         # Custom decorators (@CurrentUser, @Public)
│   ├── guards/             # Auth guards (JwtAuthGuard, InternalApiGuard)
│   ├── interceptors/       # Logging, response transformation
│   └── filters/            # Global exception filter
└── modules/                # Feature modules
    ├── auth/               # Authentication (JWT, local strategy)
    ├── users/              # User management
    ├── devices/            # Device CRUD & associations
    ├── falls/              # Fall events & statistics
    ├── mqtt/               # MQTT broker & handlers
    └── qr/                 # QR code generation

Running Tests

npm run test
npm run test:watch
npm run test:cov
npm run test:debug

Code Quality

npm run lint
npm run format
npm run build

Testing

Unit Tests

npm run test
npm run test auth.service.spec.ts
npm run test:cov

Contributing

  1. Fork the repository
  2. Create feature branch (git checkout -b feature/amazing-feature)
  3. Commit changes (git commit -m 'Add amazing feature')
  4. Push to branch (git push origin feature/amazing-feature)
  5. Open Pull Request

Development Setup

git clone https://github.com/your-username/plumet-core.git
cd plumet-core
git remote add upstream https://github.com/pcamposu/plumet-core.git
git fetch upstream
git merge upstream/main

License

This project is licensed under the GNU General Public License v3.0 - see the COPYING file for details.

About

Fall detection IoT backend with REST API and MQTT broker for elderly care monitoring

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages