A modern web interface for monitoring fermentation hydrometers on Raspberry Pi. Supports Tilt, iSpindel, and GravityMon devices.
git clone https://github.com/machug/brewsignal.git
cd brewsignal
python3 -m venv venv && source venv/bin/activate
pip install -e .
uvicorn backend.main:app --host 0.0.0.0 --port 8080- Multi-Device Support - Tilt (BLE), iSpindel (HTTP), GravityMon (HTTP) hydrometers
- Real-time Monitoring - Live SG and temperature readings via WebSocket
- Historical Charts - Interactive uPlot charts with crosshair tooltips, 1H/6H/24H/7D/30D time ranges
- Calibration - Linear interpolation (Tilt) and polynomial calibration (iSpindel/GravityMon)
- Unit Conversion - Display gravity as SG, Plato, or Brix; temperature as °C or °F
- RSSI Filtering - Filter weak Bluetooth signals to reduce noise from distant devices
- Reading Smoothing (v2.4.0) - Configurable moving average filter to reduce sensor noise
- Outlier Validation (v2.4.0) - Physical impossibility checks reject invalid readings
- Machine Learning Pipeline (v2.5.0) - Advanced predictive fermentation analytics
- Kalman Filtering - Optimal noise reduction for SG/temp/RSSI measurements
- Anomaly Detection - Automatic stuck fermentation and temperature spike detection
- Curve Fitting - Exponential decay model predicts final gravity and completion time
- Model Predictive Control - Intelligent heater control with overshoot prevention
- Per-Device State Isolation - Separate ML pipelines prevent cross-contamination
- Home Assistant Integration - Display ambient temperature/humidity from HA sensors
- MQTT Publishing (v2.10.0) - Publish fermentation data to Home Assistant via MQTT auto-discovery
- Automatic sensor creation in Home Assistant when batches start fermenting
- Sensors: gravity, temperature, ABV, status, days fermenting, heater/cooler state
- Compatible with voice assistants (Alexa, Google Home) via Home Assistant
- Fire-and-forget publishing - MQTT failures don't block reading storage
- Dual-Mode Temperature Control (v2.4.0) - Independent heater AND cooler control per batch
- Heating-only, cooling-only, or full dual-mode operation
- Batch-specific temperature targets and hysteresis settings
- Manual override controls (Force ON/OFF) per device per batch
- Real-time heater and cooler state monitoring with visual indicators
- Mutual exclusion logic prevents simultaneous heating and cooling
- Minimum 5-minute cycle protection for equipment safety
- Batch Lifecycle Management (v2.4.0) - Soft delete, restoration, and data maintenance
- Tab-based navigation (Active, Completed, Deleted)
- Orphaned data detection and cleanup
- Safe batch deletion with preview mode
- Weather Alerts - Predictive alerts when forecast temps may affect fermentation
- BeerXML Import & Batch Tracking - Import recipes with full ingredients, link readings to batches, track against targets
- Yeast Library (v2.8.0) - Comprehensive yeast strain database with 449+ strains
- Scraped from Beer Maverick with detailed fermentation data
- Filter by producer, type (ale/lager/wild), form (dry/liquid), and flocculation
- Temperature ranges, attenuation, and alcohol tolerance for each strain
- Batch yeast assignment with ability to override recipe yeast
- Search and browse yeast strains with detailed property cards
- AI Brewing Assistant (v2.9.0) - Conversational AI for brewing guidance
- AG-UI protocol-based streaming interface for real-time responses
- Tool-Augmented - Access to yeast library, style guides, inventory, fermentation data
- Cross-Thread Memory - Search and recall information from previous conversations
- Web Access - Fetch brewing resources and documentation from the web
- Chat Management - Rename threads, persistent conversation history
- Powered by configurable LLM backend (OpenAI, Anthropic, etc. via LiteLLM)
- Data Export - Download all readings as CSV
- Dark Theme - Easy on the eyes during late-night brew checks
Tilt devices must be paired before readings are logged. This prevents data pollution from nearby devices.
- Navigate to Devices page to pair/unpair devices
- Only paired devices log readings and can be assigned to batches
- Unpaired devices still appear on dashboard with live readings
BrewSignal includes an advanced ML pipeline for predictive fermentation analytics. Each Tilt maintains isolated state to prevent cross-contamination.
Kalman Filtering - Optimal state estimation for noisy sensor data
- Extended Kalman Filter (EKF) tracks SG, temperature, and RSSI
- Adaptive process noise scaling based on measurement intervals
- Provides filtered values and velocity/acceleration estimates
Anomaly Detection - Rule-based detection of fermentation issues
- Stuck fermentation (no SG change over 24+ hours)
- Temperature spikes (>5°F increase in <1 hour)
- Signal drops (RSSI below -90 dBm)
- Early warning system for intervention
Curve Fitting - Exponential decay model for predictions
- Predicts final gravity (FG) from historical SG trajectory
- Estimates hours remaining until fermentation completes
- Automatic model retraining as data accumulates
Model Predictive Control (MPC) - Intelligent temperature control
- Learns thermal model from heating/cooling data
- Predicts temperature trajectory over 2-hour horizon
- Computes optimal heater action with 10x overshoot penalty
- Prevents temperature swings and wasted energy
from backend.ml.config import MLConfig
from backend.ml.pipeline_manager import MLPipelineManager
# Configure ML components (all enabled by default)
config = MLConfig(
enable_kalman_filter=True,
enable_anomaly_detection=True,
enable_predictions=True,
enable_mpc=True,
)
# Create pipeline manager (one instance per device)
manager = MLPipelineManager(config=config)
# Process a reading
result = manager.process_reading(
device_id="RED",
sg=1.050,
temp=68.0,
rssi=-60,
time_hours=24.0,
ambient_temp=65.0,
heater_on=True,
target_temp=70.0,
)
# Result structure:
# {
# "kalman": {
# "sg_filtered": 1.0498,
# "temp_filtered": 68.1,
# "rssi_filtered": -60.2,
# "sg_velocity": -0.002, # SG/hour
# },
# "anomaly": {
# "is_anomaly": False,
# "reason": None,
# },
# "predictions": {
# "fitted": True,
# "predicted_fg": 1.012,
# "hours_remaining": 72.5,
# },
# "mpc": {
# "heater_on": True,
# "predicted_temp": 69.8,
# "has_model": True,
# }
# }ML features require additional packages (installed automatically with pip install -e '.[dev]'):
numpy- Numerical computingfilterpy- Kalman filter implementationscipy- Optimization and curve fittingscikit-learn- Machine learning utilities
Validate ML pipeline on Raspberry Pi:
ssh pi@192.168.4.218
cd /opt/brewsignal
source .venv/bin/activate
# Run validation script
python validate_ml_isolation.py
# Check service logs
sudo journalctl -u brewsignal -n 100 --no-pager | grep -E "(ML|Kalman|MPC)"See ML_VALIDATION_GUIDE.md for comprehensive validation procedures.
- Raspberry Pi (3B+ or newer recommended)
- Python 3.11+
- Bluetooth adapter (built-in or USB) for Tilt devices
- Supported hydrometer: Tilt, iSpindel, or GravityMon
# Clone the repository
git clone https://github.com/machug/brewsignal.git
cd brewsignal
# Create virtual environment and install
python3 -m venv venv
source venv/bin/activate
pip install -e .
# Run the server
uvicorn backend.main:app --host 0.0.0.0 --port 8080Access the UI at http://<raspberry-pi-ip>:8080
# Copy service file
sudo cp deploy/brewsignal.service /etc/systemd/system/
# Enable and start
sudo systemctl daemon-reload
sudo systemctl enable brewsignal
sudo systemctl start brewsignal| Variable | Description | Default |
|---|---|---|
SCANNER_MOCK |
Enable mock scanner for development | false |
SCANNER_FILES_PATH |
Path to TiltPi JSON files (legacy mode) | - |
SCANNER_RELAY_HOST |
IP of remote TiltPi to relay from | - |
- BLE Mode (default) - Direct Bluetooth scanning for Tilt devices
- Mock Mode - Simulated readings for development (
SCANNER_MOCK=true) - File Mode - Read from TiltPi JSON files (
SCANNER_FILES_PATH=/home/pi) - Relay Mode - Fetch from remote TiltPi (
SCANNER_RELAY_HOST=192.168.1.100)
Configure in System Settings:
- Smoothing Enabled - Apply moving average filter to raw readings
- Smoothing Samples - Number of samples for moving average (default: 5)
Smoothing is applied after calibration but before storage, benefiting all consumers (charts, exports, Home Assistant).
Configure per batch in Batch form:
- Heating-only Mode - Set only
heater_entity_idfrom Home Assistant - Cooling-only Mode - Set only
cooler_entity_idfrom Home Assistant - Dual-mode - Set both for full temperature regulation
- Temperature Target - Desired fermentation temperature
- Hysteresis - Temperature buffer to prevent oscillation (symmetric for heat/cool)
- Manual Override - Force heater/cooler ON/OFF for testing
Control logic:
- Heater turns ON when
temp <= (target - hysteresis), OFF whentemp >= (target + hysteresis) - Cooler turns ON when
temp >= (target + hysteresis), OFF whentemp <= (target - hysteresis) - Mutual exclusion enforced (heater and cooler never run simultaneously)
- Minimum 5-minute cycle time protects compressor equipment
- Planning - Pre-fermentation planning phase. Device can be assigned and readings are visible on dashboard for OG validation, but readings are NOT stored in database.
- Fermenting - Active fermentation. Readings are logged to database and linked to batch. Temperature control is active.
- Conditioning - Post-fermentation conditioning/cold crash phase. Readings continue to be logged for temperature monitoring. Temperature control remains active.
- Completed - Batch is finished and packaged. Readings are no longer stored in database. Temperature control is stopped.
- Readings are ONLY stored when batch status is "Fermenting" or "Conditioning"
- Live readings always visible on dashboard (via WebSocket) regardless of status
- Device must be paired for readings to appear on dashboard
- Planning phase allows OG validation without database pollution
- Completed phase stops logging to preserve final state
Tab-Based Navigation:
- Active Tab - Shows batches with status "planning" or "fermenting" (not deleted)
- Completed Tab - Shows batches with status "completed" or "conditioning" (not deleted)
- Deleted Tab - Shows soft-deleted batches (restorable)
Soft Delete:
- Batches can be soft-deleted to preserve historical data
- Soft-deleted batches are hidden from active views but restorable
- Readings remain linked to soft-deleted batches
Hard Delete:
- Permanently removes batch and cascades to all linked readings
- Use with caution - this operation cannot be undone
Data Maintenance:
- Access
/system/maintenanceto detect orphaned readings - Orphaned readings are linked to soft-deleted batches
- Preview cleanup operations (dry-run mode) before executing
- Safe cleanup only removes readings for deleted batches
BrewSignal includes a comprehensive yeast strain database with 449+ strains scraped from Beer Maverick.
- Browse & Search - Full-text search across strain names and producers
- Filter by Properties - Producer, type (ale/lager/wild/hybrid), form (dry/liquid), flocculation
- Detailed Strain Cards - Each strain displays:
- Fermentation temperature range (°C)
- Attenuation range (%)
- Flocculation (low/medium/high/very_high)
- Alcohol tolerance
- Producer and product ID
- Batch Integration - Assign yeast strains to batches, overriding recipe yeast if desired
Yeast data is scraped from Beer Maverick using scripts/scrape_beermaverick.py and stored in backend/seed/yeast_strains.json. The database is seeded on application startup.
Updating Yeast Data:
# Re-scrape yeast data from Beer Maverick
python scripts/scrape_beermaverick.py
# Refresh database (requires app restart or API call)
curl -X POST http://localhost:8080/api/yeast/refreshThe database includes strains from major yeast producers:
- White Labs (WLP series)
- Wyeast (WY series)
- Fermentis (SafAle, SafLager)
- Lallemand (LalBrew)
- Imperial Yeast
- Omega Yeast Labs
- Escarpment Labs
- Mangrove Jack's
- And many more...
BrewSignal includes an AI-powered brewing assistant that can help with recipe development, fermentation monitoring, and brewing questions.
-
Tool-Augmented Responses - The assistant has direct access to:
- Yeast strain database (449+ strains with fermentation characteristics)
- BJCP style guidelines (116 beer styles)
- Hop and fermentable reference libraries
- Your inventory (hops, yeast, equipment)
- Active fermentation data and ML predictions
- Ambient conditions and temperature control status
-
Cross-Thread Memory - Search and recall information from previous conversations
- Ask about recipes discussed in other chats
- Reference past fermentation advice
- The assistant can search thread titles and message content
-
Web Access - Fetch brewing resources from the web
- Look up specific brewing articles or documentation
- Access recipe information from URLs you provide
-
Chat Management
- Rename conversation threads when topics shift
- Persistent conversation history with sidebar navigation
- Create new chats without losing previous conversations
Configure the assistant in System Settings → AI Assistant:
- Provider - Select your LLM provider (OpenAI, Anthropic, Ollama, etc.)
- API Key - Enter your API key (stored securely in the database)
- Model - Choose the model to use (e.g., gpt-4o, claude-sonnet-4-20250514)
Environment Variables (alternative to UI configuration):
- API keys can also be set via environment variables (
OPENAI_API_KEY,ANTHROPIC_API_KEY, etc.) - The UI will detect and indicate when an env var key is being used
- Keys entered in the UI override environment variables
For Ollama (local/self-hosted):
- Install Ollama on your system: https://ollama.ai
- Pull a model:
ollama pull llama3.2 - Select "Ollama" as provider in settings (no API key required)
The assistant uses LiteLLM for provider abstraction, supporting OpenAI, Anthropic, Ollama, Azure, and other backends.
| Tool | Description |
|---|---|
search_yeast |
Search yeast strains by name, producer, type, attenuation range |
search_styles |
Search BJCP beer styles with vital statistics |
search_hop_varieties |
Search hop varieties by name, purpose, alpha acid |
search_fermentables |
Search grains, sugars, and extracts |
search_inventory_hops |
Check hop inventory levels |
search_inventory_yeast |
Check yeast inventory with expiration |
get_equipment |
Get your brewing equipment profiles |
list_fermentations |
List active and recent fermentations |
get_fermentation_status |
Get detailed status of a specific batch |
get_fermentation_history |
Get historical readings and ML predictions |
get_ambient_conditions |
Get current temperature and humidity |
save_recipe |
Save a recipe to your library |
fetch_url |
Fetch and read content from a URL |
rename_chat |
Rename the current conversation thread |
search_threads |
Search previous conversations for recipes and discussions |
| Endpoint | Method | Description |
|---|---|---|
/api/devices |
GET/POST | List or register devices |
/api/devices/{id} |
GET/PUT/DELETE | Device management |
/api/devices/{id}/calibration |
GET/PUT | Device calibration data |
/api/tilts |
GET | List all detected Tilts |
/api/tilts/{id} |
GET/PUT | Get or update Tilt |
/api/tilts/{id}/readings |
GET | Historical readings |
/api/tilts/{id}/calibration |
GET/POST | Calibration points |
| Endpoint | Method | Description |
|---|---|---|
/api/ingest/generic |
POST | Auto-detect device format |
/api/ingest/ispindel |
POST | iSpindel HTTP endpoint |
/api/ingest/gravitymon |
POST | GravityMon HTTP endpoint |
| Endpoint | Method | Description |
|---|---|---|
/api/yeast |
GET | List yeast strains (supports filtering) |
/api/yeast/{id} |
GET | Get yeast strain details |
/api/yeast/producers |
GET | List all yeast producers |
/api/yeast/refresh |
POST | Refresh yeast database from seed file |
Query Parameters for /api/yeast:
search- Full-text search on name and producerproducer- Filter by producer nametype- Filter by type (ale, lager, wild, hybrid)form- Filter by form (dry, liquid)flocculation- Filter by flocculation levellimit- Max results (default: 100)offset- Pagination offset
| Endpoint | Method | Description |
|---|---|---|
/api/batches |
GET/POST | List or create batches |
/api/batches/active |
GET | List active batches |
/api/batches/completed |
GET | List completed batches |
/api/batches/{id} |
GET/PUT | Get or update batch |
/api/batches/{id}/progress |
GET | Detailed fermentation progress |
/api/batches/{id}/delete |
POST | Soft or hard delete batch |
/api/batches/{id}/restore |
POST | Restore soft-deleted batch |
/api/recipes |
GET/POST | List or create recipes (BeerJSON schema) |
/api/recipes/{id} |
GET/PUT/DELETE | Recipe with full ingredients (BeerJSON) |
/api/recipes/import |
POST | Import BeerXML, BeerJSON, or Brewfather JSON |
Recipe Fields (BeerJSON 1.0):
- Core:
og,fg,ibu,abv,color_srm,batch_size_liters - Timing:
boil_time_minutes,efficiency_percent,carbonation_vols - Extensions:
format_extensions(preserves BeerXML/Brewfather metadata) - Relationships: fermentables, hops, cultures (yeasts), miscs, water profiles, mash steps, fermentation steps
| Endpoint | Method | Description |
|---|---|---|
/api/control/status |
GET | Global temperature control status (deprecated) |
/api/control/batch/{id}/status |
GET | Temperature control status for specific batch |
/api/control/override |
POST | Set manual heater/cooler override (requires batch_id and device_type) |
/api/control/heater-entities |
GET | List available HA heater entities |
/api/control/cooler-entities |
GET | List available HA cooler entities |
| Endpoint | Method | Description |
|---|---|---|
/api/maintenance/orphaned-data |
GET | Detect orphaned readings |
/api/maintenance/cleanup-readings |
POST | Preview/execute orphaned reading cleanup |
| Endpoint | Method | Description |
|---|---|---|
/api/config |
GET/PATCH | Application settings (smoothing, units, etc.) |
/api/system/info |
GET | System information |
/api/ambient |
GET | Ambient temp/humidity from HA |
/api/alerts |
GET | Weather forecast and alerts |
/ws |
WebSocket | Real-time readings |
/log.csv |
GET | Export all data as CSV |
Full API documentation with request/response examples available at:
- Local:
http://localhost:8080/docs - Raspberry Pi:
http://<raspberry-pi-ip>:8080/docs
Configure your iSpindel or GravityMon to POST to:
http://<raspberry-pi-ip>:8080/api/ingest/ispindel
The server auto-detects GravityMon extended format. Readings appear on the dashboard alongside Tilt devices.
Import a BeerXML file to auto-populate recipe targets and link batches:
curl -X POST "http://<raspberry-pi-ip>:8080/api/recipes/import" \
-F "file=@/path/to/recipe.xml"Imports include:
- Recipe metadata (name, brewer, style, etc.)
- Fermentables with weights and colors
- Hops with timing and alpha acids
- Yeast strains and attenuation
- BJCP style guidelines (OG, FG, ABV, IBU ranges)
Add calibration points to correct SG and temperature readings:
- Take a reference reading with a hydrometer/thermometer
- Note the raw value shown in BrewSignal
- Add a calibration point: raw value → actual value
- The system uses linear interpolation (Tilt) or polynomial fitting (iSpindel/GravityMon) between points
# Backend (FastAPI)
cd brewsignal
pip install -e ".[dev]"
uvicorn backend.main:app --reload
# Frontend (Svelte)
cd frontend
npm install
npm run devcd frontend
npm run build # Outputs to backend/static/- Backend: FastAPI, SQLAlchemy 2.0 (async), SQLite, Bleak (BLE)
- Frontend: SvelteKit 2.x, Svelte 5, TailwindCSS v4, uPlot
- Deployment: Systemd, uvicorn
See CHANGELOG.md for detailed version history.
MIT
- Tilt Hydrometer for the awesome hardware
- TiltPi for inspiration
- iSpindel for the open-source WiFi hydrometer
- GravityMon for extended iSpindel firmware
