Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,24 @@

**Environmental Sensing + Automation Hub**

Sensorius is a Raspberry Pi-first sensor and automation hub with a full web UI, MQTT ingestion, and optional Home Assistant and farmOS integrations. It auto-detects local sensors, discovers Nodus devices over MQTT, and turns those signals into live dashboards, historical data, switch control, and optional farmOS telemetry export.
Sensorius is a sensor, relay-switch automation hub with a robust web UI, MQTT ingestion, and optional Home Assistant and farmOS integrations, with a focus on gardening, farming, greenhouse monitor and control. When using Raspberry Pi's it can auto-detects directly-connected sensors, relay-switch(es) and on all supported platforms it automatically discovers Nodus WiFi/MQTT devices, and turns those data into live dashboards, automation controls, historical data; the application also includes integrated weather forecast and biodynamic calendar .

For illustrated setup and operating instructions, see the
[Sensorius AI User Guide](docs/user_guide.md).

## What It Does

- Auto-detects locally attached sensors (Raspberry Pi deployments)
- Discovers Nodus sensors and switches via MQTT and retained `nodus/<device_id>/meta` metadata (`/itaot-meta` fallback for AP-mode/diagnostics)
- Discovers Nodus sensors and switches via MQTT and retained `nodus/<device_id>/meta` metadata
- Stores sensor readings and switch events in a local SQLite database
- Provides live dashboards, historical graphing, and location-based views
- Shows Astral-based sun position and moon phase cards in the dashboard when location/timezone are available
- Supports manual and automated switch control
- Supports calibration workflows for sensors
- Can publish discovery/state for Home Assistant
- Can export sensor telemetry to farmOS log entries
- Integrated Caelus weather forecast
- Integrated Biodynamic Calendar with hints, planting tracking and notes

## Deployment Modes

Expand Down
79 changes: 78 additions & 1 deletion docs/user_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,59 @@ Colored backgrounds use that metric's gauge zones. The colors are metric-specifi

The boundaries come from Sensorius's gauge-zone configuration for each metric. Changing a zone boundary in that configuration changes where the corresponding background color begins and ends. The standard UI currently exposes gauge size and display style, but not gauge-zone boundary editing. An automation's **Threshold** and **Hysteresis** control when its rule runs; they do not change graph or gauge colors.

#### Rain Gauge Colors And Historical Limits

Rain accumulation gauges use light blue (`#add8e6`) from 0–20% of the scale,
medium blue (`#66b2ff`) from 20–60%, and dark blue (`#0033cc`) from 60–100%.
These bands indicate increasing rain amounts, not alarm thresholds.

**Rain and location:** Check the hub's coordinates in **General Settings >
Astral**. Sensorius uses those coordinates for all rain-gauge climate scales
on that hub. A sensor's location label (for example, Greenhouse or Garden)
does not select a different climate location. GeoIP provides an approximate
location; enter the site's latitude and longitude manually when that estimate
does not represent where the rain gauge is installed. Changing the hub's
coordinates updates the historical scale, not the measured rainfall or stored
sensor readings.

Using the hub's Astral latitude/longitude and timezone (manual or GeoIP),
Sensorius retrieves daily rainfall from [Open-Meteo's Historical Weather
API](https://open-meteo.com/en/docs/historical-weather-api), using ERA5 for the
period from January 1, 1991 through the latest available data (normally five
days behind today, or earlier if the latest days are not yet published). The gauge maximum is the wettest corresponding period:

| Gauge | Historical period used for maximum |
| --- | --- |
| Rain, Rain Last 24h, Rain Day | Wettest local calendar day |
| Rain Week | Wettest seven consecutive days, including across year boundaries |
| Rain Month | Wettest complete calendar month |
| Rain Year | Wettest complete calendar year |

Daily history approximates the rolling 24-hour gauge; it is not a historical
rolling hourly record. `Rain` retains its existing station/interval meaning.
The historical series is gridded outdoor rainfall, excludes snowfall, and is
not a record measured by your own station. It works independently of the
selected forecast provider. Rain Event and Rain Total get the same blue bands
but retain their fixed limits; Rain Rate retains its existing scale.

Limits and ticks follow the selected mm/in display units. A completely dry
baseline uses a minimum 1 mm span. The needle stops at the scale maximum if a
reading exceeds it; the full measured value remains visible numerically.
Min/Avg/Max statistics still describe the sensor's last 24 hours, not climate
history.

Historical data loads in the background, using default limits while loading
or unavailable. Scales update without a page reload or extra text inside the
tiles, preserving their original height. A compact location-specific cache
survives restarts; on Linux its default path is `/home/<user>/Sensorius/weather_cache/rain_climate.json`
(`/Users/<user>/Sensorius/weather_cache/rain_climate.json` on macOS).
The cache is checked hourly and refreshed when the historical end date advances.
Only complete months and years contribute to their respective maxima; daily
and seven-day maxima include recent complete days. Changing the hub location
invalidates the active scale. Missing or incomplete history retries later;
a previously cached summary for the same location remains usable during a
refresh failure, otherwise the existing default limits apply.

#### Metric Ordering

The system-wide **Metric Set** in **General Settings > Display** controls the initial Sensor Group state. In **Pick 6** mode, the dashboard follows **Metric 1** through **Metric 6** exactly from left to right and initially collapses each Sensor Group after those six Sensor Tiles. Use the disclosure triangle beside the connection indicator to reveal or hide the Sensor Group's remaining known metrics. Sensor Groups with six or fewer Sensor Tiles do not show the triangle. You can therefore establish the operational summary order in **Sensor Settings**. Factory defaults are selected by sensor type and generally put the device's primary measurement first: for example, CO2 is first for a CO2 sensor and Air Quality is first for an AQI sensor. The remaining positions favor closely related temperature, humidity, VPD, dew-risk, pressure, plant, light, or soil measurements for that device.
Expand Down Expand Up @@ -1205,9 +1258,33 @@ next seasonal event, and up to three solar or lunar eclipses visible from the
configured Astral location during the next twelve months. A full-width Windy
map opens in radar view below that row, followed by
the current Moon and phase cycle at the bottom. The display also provides
theme-matched six-day details. It does not run a separate weather service or
theme-matched six-day details. It reuses the Sensorius forecast and does not
maintain a separate settings file or readings database.

Today's forecast includes a compact **Historical daily average** row
beneath its expected ranges, with temperature, RH, wind, and rain in the same
column order. These are averages for the same calendar date at the hub's
Astral location, using Open-Meteo/ERA5 historical data independently of the
selected forecast provider. Temperature, RH, and wind are averages of daily
means; rain is the average daily rainfall total, including dry days, **not a
rain probability**. Units follow the selected Metric or Imperial display.

The date follows the hub's timezone. History starts in 1991 and extends through
the latest available data, allowing for ERA5's roughly five-day publication
delay; unpublished trailing days are excluded. Each calendar date uses its available daily samples, including recent
years; February 29 uses only leap years. The label shows the actual year range and hub-local date, for example
**Historical daily average 1991–2026 for 18-Sep-2026**,
and its tooltip gives the exact historical cutoff and sample count. These are
regional climate estimates, not readings from the selected station. The source link and tooltip
explain the baseline without adding a separate note or increasing the card's
height. Dashes mean history is still loading or unavailable, never zero.
Retrieval is non-blocking. The cache is checked hourly and refreshed as the
historical end date advances; a compact calendar-day cache survives restarts
at `/home/<user>/Sensorius/weather_cache/weather_climate.json` on Linux or
`/Users/<user>/Sensorius/weather_cache/weather_climate.json` on macOS. Updating
the hub's location invalidates the active history. The displayed date and
averages refresh automatically, including after the hourly forecast refresh.

The Windy map is initially interaction-locked so the mouse wheel continues to
scroll the Caelus page. Select **Click to interact with map** on the map's top
border to enable its zoom and pan controls; move the pointer outside the map
Expand Down
2 changes: 1 addition & 1 deletion sensorius/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
release notes, and supporting tooling can report a consistent build identity.
"""

__version__ = "v0.26.259.1"
__version__ = "v0.26.261.6"
11 changes: 10 additions & 1 deletion sensorius/saiHtml.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from .saiBiodynamics import get_biodynamic_payload, get_skyfield_runtime_if_installed
from .sensor_modules.station_weewx import DEFAULT_SENSOR_ID as WEEWX_DEFAULT_SENSOR_ID, WEEWX_GAUGE_CONFIG
from .sensor_modules.station_ecowitt import ECOWITT_GAUGE_CONFIG, ecowitt_gauge_config_for_metric
from .saiRainClimate import style_rain_gauges
from .saiDisplayUnits import convert_display_value
from collections import defaultdict
from pathlib import Path
Expand Down Expand Up @@ -142,7 +143,7 @@ def get_gauge_config():
}
gauge_config.update(WEEWX_GAUGE_CONFIG)
gauge_config.update(ECOWITT_GAUGE_CONFIG)
return gauge_config
return style_rain_gauges(gauge_config)


def extend_gauge_config_for_metrics(gauge_config: dict, metric_names) -> dict:
Expand Down Expand Up @@ -2323,6 +2324,7 @@ def _format_stat_value(stat_value):

yield "<script type='module'>"
yield "\"use strict\";"
yield f"import {{ startRainGauges }} from '/ui_static/js/rain_gauges.js?v={APP_VERSION}';"

yield "let stepCount = 0;"
yield f"const gaugeConfig = {json.dumps(gauge_config)};"
Expand Down Expand Up @@ -4879,6 +4881,11 @@ def _format_stat_value(stat_value):
yield " const gaugeValue = convertForDisplay(value, config);"
yield " const existingGauge = window[`${safe}_gauge`];"
yield " if (existingGauge && existingGauge.__sensoriusCanvas === canvas && typeof existingGauge.set === 'function') {"
yield " if (config.rain_period && existingGauge.maxValue !== config.max) {"
yield " existingGauge.maxValue = config.max;"
yield " existingGauge.setMinValue(config.min);"
yield " existingGauge.setOptions({...existingGauge.options, staticZones: config.zones, staticLabels: {...existingGauge.options.staticLabels, labels: config.ticks}});"
yield " }"
yield " existingGauge.set(gaugeValue ?? 0);"
yield " label.innerText = isNull ? '--' : formatCurrentValue(value, config);"
yield " if (window.registerContainerStyle) {"
Expand All @@ -4891,6 +4898,7 @@ def _format_stat_value(stat_value):
yield " angle: -0.2, lineWidth: 0.25, radiusScale: 0.9,"
yield " pointer: { length: 0.5, strokeWidth: 0.035, color: '#000000' },"
yield " staticZones: config.zones || [],"
yield " limitMax: Boolean(config.rain_period),"
yield " staticLabels: {"
yield " font: '12px sans-serif',"
yield " labels: config.ticks, color: '#000', fractionDigits: 1"
Expand Down Expand Up @@ -8155,6 +8163,7 @@ def _format_stat_value(stat_value):
yield " setTimeout(checkAndRetryIfNoGauges, 1000);"

yield " initGauge();"
yield " startRainGauges(gaugeConfig, initGauge);"
yield " initializeDashboardThemePreview();"
yield " initializeTrendArrows();"
yield " initSwitchTimersFromDom();"
Expand Down
Loading
Loading