A professional real-time climate monitoring dashboard application for the XY-MD02 sensor that reads, visualizes, and analyzes Modbus RTU data.
- Real-time monitoring: Live charts with automatic updates (1 second interval)
- Historical data: Persistent storage in SQLite database with time filters (1 min to 6 months)
- Database optimizations: Table-per-day partitioning, WAL mode, batch inserts for multi-year operation
- Data retention: Configurable automatic cleanup of old data
- Multiple measurements:
- Temperature (°C)
- Humidity (%)
- Dew Point (calculated)
- Absolute Humidity (g/m³)
- Humidex (scientific comfort index)
- Comfort Score (0-6, based on Humidex)
- Psychrometric chart: Mollier diagram with live indicator and comfort zone visualization
- Historical data replay: Time-travel through data with preset buttons or custom date/time selection
- Interactive slider: Live updates while dragging for smooth historical navigation
- Scientific analysis: Humidex formula according to Environment Canada standard
- Multilingual: 5 languages fully supported (Dutch, English, German, French, Spanish)
- Professional UI: Modern dashboard with gradient header and card-based layout
- Zoom persistence: Graph zoom stays preserved during live updates
- Robust: Extensive input validation and error handling
- Configurable: All settings via
.envfile
Real-time monitoring of temperature, humidity, dew point and absolute humidity with automatic updates
Interactive Mollier diagram with saturation line, RH curves, comfort zone and live indicator
Modal with preset buttons and custom date/time picker for historical data analysis
- Python 3.8+
- Modbus RTU device (RS485)
- Windows, Linux or macOS
- Clone the repository
git clone <repository-url>
cd XY-MD02_WebApp- Create a virtual environment
python -m venv .venv- Activate the virtual environment
Windows PowerShell:
.\.venv\Scripts\Activate.ps1Windows CMD:
.venv\Scripts\activate.batLinux/macOS:
source .venv/bin/activate- Install dependencies
pip install -r requirements.txt- Configure the application
Copy the example configuration file:
cp .env.example .envAdjust the settings in .env:
# Modbus Settings
MODBUS_PORT=COM11
MODBUS_SLAVE_ID=1
MODBUS_BAUDRATE=9600
MODBUS_BYTESIZE=8
MODBUS_PARITY=N
MODBUS_STOPBITS=1
MODBUS_TIMEOUT=1
MODBUS_FUNCTION_CODE=4
MODBUS_REGISTER_TEMP=1
MODBUS_REGISTER_HUMIDITY=2
# Database Settings
DATABASE_FILE=src/modbus_sensor_data.db
DATA_RETENTION_DAYS=0
# Application Settings
APP_HOST=127.0.0.1
APP_PORT=8050
APP_DEBUG=False
DEBUG_LOGGING=False
TIMEZONE=Europe/Amsterdam
DEFAULT_LANGUAGE=EN- Start the application
python app.pyThe application is now available at: http://127.0.0.1:8050/
XY-MD02_WebApp/
├── app.py # Main entry point (45 lines)
├── database.py # Database operations, partitioning, WAL mode
├── modbus_reader.py # Modbus RTU communication, batch buffering
├── psychrometric.py # Mollier diagram generation
├── callbacks.py # Dash callbacks (7 functions)
├── layout.py # HTML layout and CSS styling
├── translations.py # Multilingual system (NL/EN)
├── test_app.py # Automated test suite (15 tests)
├── .env # Configuration (not in git)
├── .env.example # Example configuration
├── requirements.txt # Python dependencies
├── .gitignore # Git exclude rules
├── README.md # This file
└── src/ # Data directory
└── modbus_sensor_data.db # SQLite database (not in git)
MODBUS_PORT: Serial port (e.g. COM11, /dev/ttyUSB0)MODBUS_SLAVE_ID: Slave ID of the deviceMODBUS_BAUDRATE: Baudrate (9600, 19200, etc.)MODBUS_BYTESIZE: Number of data bits (8)MODBUS_PARITY: Parity bit (N = None, E = Even, O = Odd)MODBUS_STOPBITS: Stop bits (1, 2)MODBUS_TIMEOUT: Timeout in secondsMODBUS_FUNCTION_CODE: Modbus function code (4 = Read Input Registers)MODBUS_REGISTER_TEMP: Register number for temperatureMODBUS_REGISTER_HUMIDITY: Register number for humidity
DATABASE_FILE: Path to the SQLite database fileDATA_RETENTION_DAYS: Data retention in days (0 = infinite, otherwise number of days to keep data)
APP_HOST: Server host IP (127.0.0.1 for local)APP_PORT: Server port (8050)APP_DEBUG: Debug mode (True/False)DEBUG_LOGGING: Enable verbose debug logging to console (True/False, default: False)TIMEZONE: Timezone for timestamp display (e.g., Europe/Amsterdam, America/New_York)DEFAULT_LANGUAGE: Default UI language (EN, NL, DE, FR, ES, default: EN)
-
Open
translations.py -
Add the language to
LANGUAGE_NAMES:
LANGUAGE_NAMES = {
'nl': 'Nederlands',
'en': 'English',
'de': 'Deutsch' # New!
}- Add the translations to
TRANSLATIONS:
TRANSLATIONS = {
'nl': { ... },
'en': { ... },
'de': {
'title': 'Modbus Klimamonitor',
'temperature': 'Temperatur',
'humidity': 'Luftfeuchtigkeit',
# ... all other keys
}
}The dropdown will be updated automatically!
The application uses the Humidex (Humidity Index) for scientific comfort calculation according to Environment Canada standard.
Humidex = T + 0.5555 × (e - 10)
where:
- T = temperature in °C
- e = vapor pressure saturation in hPa (calculated via dew point)
- < 20: Too cold - Score 0
- 20-27: Comfortably cool - Score 4
- 27-30: Comfortable - Score 5
- 30-35: Optimally comfortable - Score 6
- 35-40: Some discomfort - Score 3
- 40-46: Great discomfort, avoid physical exertion - Score 2
- 46-54: Dangerous, heat cramps possible - Score 1
- > 54: Heat stroke imminent - Score 0
The application displays an interactive psychrometric chart with:
- Saturation line: 100% relative humidity curve
- RH lines: 10%, 20%, ..., 90% relative humidity
- Comfort zone: Marked area (20-26°C, 30-60% RH)
- Live indicator: Real-time position of current climate condition (red star)
- Historical marker: Orange marker when using historical replay
- Humidity ratio: Y-axis shows absolute humidity in g water / kg dry air
- Zoom & Pan: Full Plotly interactivity for detailed analysis
This diagram helps to see at a glance whether the climate condition is within the comfort zone and how it relates to saturation limits.
The application offers advanced historical data analysis with two input methods:
- 5 min - Last 5 minutes
- 10 min - Last 10 minutes
- 30 min - Last 30 minutes
- 1 hour - Last hour
- 2 hours - Last 2 hours
- 6 hours - Last 6 hours
- 12 hours - Last 12 hours
- 24 hours - Last day
- 2 days - Last 2 days
- Week - Last week
- Month - Last month
- Click the "Custom..." button to open a modal
- Choose start date and time (hour:minute)
- Choose end date and time (hour:minute)
- Supports same day selection for start and end
- Hour selection: 0-23
- Minute selection: 0-59
- After selecting a period, an interactive slider appears
- Drag the slider to "travel" back through time
- Live updates while dragging: Chart updates immediately without releasing mouse
- The psychrometric chart shows the climate condition position at that moment
- Timestamp is displayed live in DD-MM HH:MM format
Usage:
- Click a preset button for quick analysis
- Or click "Custom..." for specific date/time selection
- Use the slider to navigate through historical data
- See how the climate condition changed over time in the Mollier diagram
The application uses advanced SQLite optimizations for multi-year continuous operation:
Table-per-day Partitioning:
- Dynamic table creation:
measurements_YYYYMMDD(e.g.measurements_20251205) - Automatic table switching at midnight
- UNION ALL queries across relevant day-tables
- Smart table selection based on timerange
- Instant cleanup via DROP TABLE (milliseconds vs minutes for DELETE+VACUUM)
Write Optimizations:
- WAL mode (Write-Ahead Logging) for better concurrent performance
- PRAGMA synchronous=NORMAL for faster commits
- PRAGMA temp_store=MEMORY for temporary data in RAM
- Batch inserts: 30-measurement buffer (30 seconds)
- Persistent connection pooling in Modbus thread
- 97% reduction in write transactions (86k/day → 2.9k/day)
Storage Optimizations:
- Integer epoch timestamps instead of TEXT datetime (50% space saving)
- Indexed timestamp columns per table
- Configurable data retention with automatic cleanup
Performance Benefits:
- Queries only scan relevant days (not entire database)
- 10-100× faster batch inserts vs per-second commits
- No table bloat over time due to partitioning
- Sub-millisecond table drops for old data
- Linear performance independent of dataset size
CREATE TABLE measurements_YYYYMMDD (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp INTEGER NOT NULL, -- Unix epoch timestamp
temperature REAL NOT NULL,
humidity REAL NOT NULL,
dewpoint REAL,
absolute_humidity REAL
);
CREATE INDEX idx_measurements_YYYYMMDD_timestamp ON measurements_YYYYMMDD(timestamp);The application includes an automated test suite:
python test_app.pyTest Coverage:
- Calculations: Dew point, absolute humidity, Humidex formulas
- Database: CRUD operations, queries, partitioned tables, integer timestamps
- Translations: NL/EN key parity and completeness
- Psychrometric chart: Chart generation, multi-language support
- Data validation: Temperature and humidity ranges
- Production database: Database existence and structure verification
All 15 tests must pass before committing new features.
pip freeze > requirements.txtThe codebase has been modularized for better maintainability:
- app.py (45 lines): Clean entry point with app initialization
- database.py (190 lines): Table-per-day partitioning, WAL mode, UNION queries, cleanup
- modbus_reader.py (185 lines): Modbus RTU communication, batch buffering, validation
- psychrometric.py (317 lines): Mollier diagram generation (current + historical)
- callbacks.py (636 lines): 7 Dash callbacks for UI interaction
- layout.py: UI components, modal system, styling
- translations.py: Translation system (NL/EN)
- test_app.py (320 lines): Automated test suite (15 tests)
Dependency Flow:
app.py
├── database.py (standalone)
├── modbus_reader.py → database.py
├── psychrometric.py → translations.py
├── callbacks.py → database.py, psychrometric.py, translations.py
└── layout.py → translations.py
- Check if the serial port is correct (
MODBUS_PORTin.env) - Verify the baudrate and other serial settings
- Check the register numbers in the device documentation
- Ensure the
src/folder exists - Check write permissions in the project folder
- Change
APP_PORTin.envto another port
This project is open source and available under the MIT License.