Files
yggdrasil/server
2026-10-03 20:24:37 +02:00
..
2026-10-03 19:31:38 +02:00
css
2026-10-03 20:24:37 +02:00
css
2026-10-03 20:24:37 +02:00
css
2026-10-03 20:24:37 +02:00
2026-10-03 19:31:38 +02:00
css
2026-10-03 20:24:37 +02:00
2026-10-03 19:31:38 +02:00
css
2026-10-03 20:24:37 +02:00
2026-10-03 19:31:38 +02:00

Sensor Readouts

A Flask web application for managing and visualizing sensor data. It provides a REST API for creating sensors and storing readouts, along with a frontend dashboard that displays real-time charts and tabular data.

Tech Stack

  • Python 3.13+
  • Flask 3 — web framework
  • SQLAlchemy (via Flask-SQLAlchemy) — ORM
  • SQLite — database
  • Alembic — database migrations
  • Chart.js — frontend charting library

Project Structure

server/
├── app.py              # Flask application factory and API routes
├── models.py           # SQLAlchemy models (Sensor, SensorReadout)
├── seed.py             # Database seeder with sample data
├── requirements.txt    # Python dependencies
├── data.db             # SQLite database (auto-created)
├── alembic.ini         # Alembic configuration
├── alembic/            # Migration scripts
│   ├── env.py
│   └── versions/
└── templates/
    ├── sensors.html    # Dashboard with chart and table
    └── docs.html       # API documentation page

Setup

# Create and activate virtual environment
python3 -m venv .venv
source .venv/bin/activate

# Install dependencies
pip install -r requirements.txt

# Run database migrations
alembic upgrade head

# (Optional) Seed the database with sample data
python seed.py

Users

There is no public registration. All accounts are created by an administrator. Pages (/, /home, /sensors, /api/docs) and dashboard data endpoints (GET /api/sensors/:id/types, /readouts, /readouts/hourly) require login. GET /api/sensors (list, needed by devices to resolve name to id) and POST /api/* stay open so devices can push data without a browser session.

Set a secret key for sessions (required in production):

export SECRET_KEY='long-random-value'

First admin

The database starts with an empty user table. Create the first admin with the CLI command (flags, env vars, or interactive prompts):

# Run database migrations first
alembic upgrade head

# Option 1: flags
flask --app app create-admin --username admin --password 'secret'

# Option 2: environment variables (e.g. on first deploy)
ADMIN_USERNAME=admin ADMIN_PASSWORD=secret flask --app app create-admin

# Option 3: interactive (asks for username/password)
flask --app app create-admin

Running create-admin for an existing username resets its password and grants is_admin=True. After that log in at /login — you land on /home.

Running

Development

python app.py

The server starts on http://localhost:5000.

Production

# Install dependencies (includes gunicorn)
pip install -r requirements.txt

# Run with gunicorn
gunicorn -c gunicorn.conf.py "app:create_app()"

Gunicorn starts on http://0.0.0.0:8000 with cpu * 2 + 1 workers.

systemd service

sudo cp yggdrasil.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now yggdrasil
Route Description
/sensors Dashboard with interactive chart and data table
/api/docs API documentation with curl examples

Database

Two tables are used:

sensor

Column Type Description
id Integer Primary key
name String(128) Unique sensor name
created_at DateTime Creation timestamp (UTC)

sensor_readout

Column Type Description
id Integer Primary key
sensor_id Integer Foreign key to sensor.id
type String(64) Readout type (e.g. temperature, pressure, humidity)
value Float Measured value
timestamp DateTime Readout timestamp (UTC)

Migrations

# Create a new migration after model changes
alembic revision --autogenerate -m "description"

# Apply pending migrations
alembic upgrade head

# Roll back one step
alembic downgrade -1

API

Base URL: http://localhost:5000/api

Endpoints

Method Path Description
GET /api/sensors List all sensors
POST /api/sensors Create a new sensor
GET /api/sensors/:id/types List readout types for a sensor
GET /api/sensors/:id/readouts Latest readouts (max 300, filterable by ?type=)
GET /api/sensors/:id/readouts/hourly Hourly averaged readouts (filterable by ?type=)
POST /api/sensors/:id/readouts Add a new readout

Examples

# List sensors
curl http://localhost:5000/api/sensors

# Create a sensor
curl -X POST http://localhost:5000/api/sensors \
     -H "Content-Type: application/json" \
     -d '{"name": "bmp280"}'

# Get temperature readouts for sensor 1
curl "http://localhost:5000/api/sensors/1/readouts?type=temperature"

# Get hourly averaged data
curl "http://localhost:5000/api/sensors/1/readouts/hourly?type=temperature"

# Add a readout
curl -X POST http://localhost:5000/api/sensors/1/readouts \
     -H "Content-Type: application/json" \
     -d '{"type": "temperature", "value": 22.5}'

Full API documentation with request/response examples is available at /api/docs.

Frontend

The dashboard at /sensors provides:

  • Sensor dropdown — select a sensor from the list
  • Type dropdown — select a readout type (populated based on the chosen sensor)
  • Line chart — hourly averaged values rendered with Chart.js
  • Data table — raw readouts with formatted timestamps (latest 300 entries)