Command/path note: shell commands in this document were written in a development environment. Verify and adjust paths for your own host before running them.
This was made for weewx 5.2+ that is using Mysql and the ecowittcustom driver. I was struggling to get all the data correctly displayed with existing skins and the tiny fan of my Intel NuC that is hosting my LAN only weewx installation was REALLY driving me nuts every 5 minutes as weewx started to do it's html/image generation cycle. Being a sysadmin means that, unlike most developers, you know that just because you HAVE free cpu cycles, doesn't mean you HAVE to use them :P Knowing SOME php / mysql I was kinda surprised there weren't any weewx sites that just pulled the data directly from the database and generate most things client side. So I decided to try and make my own little PWS site, but I was lazy as well as curious so I had Codex make it. I tried to have it write code in a way to keep the site customizable to other peoples possible needs.
None of the used accounts have write access other than the one for the cli program so it should be (relatively) safe code. That said, I have no idea how safe mqtt is to have open to the internet, use at your own risk, but it reduces the load on the server greatly. Just 1 sql query and live updats through mqtt.
So this whole project took about 4 days of 2-3 hours a day of telling codex what to do, figuring out issues with missing weewx values, basic troubleshooting and on the fly design changes because I am indecisive and come up with new things on the go. Overall it turned out pretty nice, better than I had expected for sure.
Live weather dashboard for weewx data with:
- latest conditions from MySQL (
weather.archive) - responsive history charts (Chart.js)
- live value updates via MQTT WebSocket (
weewx/#) - history presets:
Today,48 Hours,Last Week,Last Month,Last Year; rolling ranges end at now - wind rose + wind direction point chart
- comfort indication based on wind, outside temperature, and outside humidity
- wind row summary cards for 1h and 3h average wind/gust values
- dedicated wind averages chart (hourly average wind + hourly average gust)
- separate rain chart with rain rate and hourly rain totals
- dedicated battery charts (
windBatteryStatus,rainBatteryStatus,lightning_Batt,pm25_Batt1) - cached WU/TWC forecast integration (dashboard + dedicated forecast page)
- archive-based trend page (
trends.php) - hybrid prediction cache and page (
prediction.php) - detailed celestial almanac page (
celestial.php) with cached Skyfield sky map, rise/set timeline, lunar month, solar-year daylight graph, solar-system map, almanac table, twilight, and moon phase panels - optional WeeWX
custom_obsextension package for solar/lunar custom field registration
Compatibility note:
- This project has been tested against a WeeWX 5.x archive database layout.
- Tested station path: Ecowitt HP2553 console sending to a custom server endpoint, with WeeWX using the
ecowittcustomdriver fromEcowitt-or-DAVIS-stations-and-Season-skin. - Runtime stack and MQTT setup have been tested on Fedora 43; Ubuntu instructions are in
docs/INSTALL.md.
cd pws-live-site
cp src/config.local.php.example src/config.local.php
# edit src/config.local.php with your hostnames/credentials
php -S 127.0.0.1:8080 -t publicOpen http://127.0.0.1:8080.
- PHP 8.0+ (
php,php-mysqlnd,php-json,php-mbstring,php-curl) - MySQL/MariaDB server containing WeeWX archive data
- WeeWX 5.x with a MySQL/MariaDB-backed archive
weewx-skyfield-almanacif you want the solar/lunar archive fields used by the sky widget- Optional for the detailed celestial cache: Python 3 with
Skyfieldandnumpy - Apache or another PHP-capable web server
- Optional (for live browser updates): WeeWX MQTT extension
matthewwall/weewx-mqtt
This project is licensed under the GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later).
See LICENSE for the full license text.
- WeeWX for the archive layout, extension model, and MQTT/custom observation integration points
Ecowitt-or-DAVIS-stations-and-Season-skinfor the Ecowitt driver field names and custom observation mappingweewx-skyfield-almanacfor the live solar/lunar values archived by this projectweewx-skymap-almanacas a reference for the dedicated celestial/almanac page conceptsweewx-skyfieldas a Skyfield almanac reference; this project uses its concepts directly through Skyfield cache scripts, not WeeWX skin outputweathericonsfor the dashboard icon set
- Install system packages (
php, Apache, DB driver, optional Mosquitto). - Deploy the project files so
public/is the web root andsrc/stays outside the served path. - Create
src/config.local.phpand configure:- read-only website DB access
- optional forecast-writer DB access
- location
- MQTT broker settings
- Make sure the WeeWX archive already contains the fields you want to show.
- If you want the sky widget solar/lunar archive fields, install
weewx-skyfield-almanac, then install the included WeeWXcustom_obsextension and add missing archive columns withweectl database add-column. - Restart WeeWX after any archive schema changes.
- If using live updates, install/configure Mosquitto and the WeeWX MQTT publisher extension.
- If using forecast/prediction pages, create the cache tables and schedule the CLI cron jobs.
- If using the detailed celestial page, create the celestial cache table, install Skyfield/numpy for the CLI user, and schedule the celestial cache cron job.
- If using the monthly history page long-term, create the monthly summary table and schedule the first-of-month rollup.
- Drop Plotly into
public/assets/vendorif you want the Plotly-based wind rose. - Optionally mirror the recommended security headers in Apache using
docs/reference/apache-pws-live-site.conf. - Open the dashboard and verify cards, charts, MQTT, forecast cache behavior, and celestial cache behavior.
- Confirm the wind row shows the extra 1h / 3h wind+gust average cards and the separate wind-averages graph renders.
- Safe defaults:
src/config.defaults.php(tracked in git) - Local secrets:
src/config.local.php(gitignored) - Runtime override: environment variables
- Security checklist:
docs/SECURITY_NOTES.md - Android alerting design notes:
docs/ANDROID_ALERTING.md
Environment variables (optional overrides):
PWS_DB_HOST(default from local config)PWS_DB_PORT(default3306)PWS_DB_NAME(default from local config)PWS_DB_USER(default from local config)PWS_DB_PASS(default from local config)PWS_MQTT_URL(default from local config)PWS_MQTT_ENABLED(defaulttrue)PWS_MQTT_EXPOSE_PASSWORD(defaultfalse)PWS_MQTT_USER(default from local config)PWS_MQTT_PASS(default from local config)PWS_MQTT_TOPIC(defaultweewx/#)PWS_API_DUMP_ENABLED(defaultfalse)PWS_API_DUMP_DEFAULT_ROWS(default1000)PWS_API_DUMP_MAX_ROWS(default10000)PWS_API_DUMP_TOKEN(optional)PWS_HISTORY_DEFAULT_HOURS(default24)PWS_HISTORY_MAX_HOURS(default8784)PWS_WU_API_KEY(overridesforecast.wu_api_key)PWS_OWM_API_KEY(overridesforecast.owm_api_key)PWS_FORECAST_PROVIDER(wu,openweather,none)PWS_FORECAST_DB_HOST,PWS_FORECAST_DB_PORT,PWS_FORECAST_DB_NAME,PWS_FORECAST_DB_USER,PWS_FORECAST_DB_PASS
- Apply SQL schema:
mysql -u DB_USER -p DB_NAME < docs/sql/create_pws_wu_forecast_cache.sql- Configure forecast values in
src/config.local.php:
forecast.provider = 'wu'orforecast.provider = 'openweather'- Optional combined mode:
forecast.providers = ['wu', 'openweather']forecast.preferred_hourly_provider = 'openweather'(example)forecast.preferred_daily_provider = 'wu'(example)
- For WU:
forecast.wu_api_key- optional
forecast.wu_hourly_enabled = falseif your subscription only includes daily APIs
- For OpenWeather:
forecast.owm_api_keyforecast.owm_mode = 'onecall_3'(paid) or'free_5d'(free 3-hour endpoint)- With
onecall_3, weather alerts are also cached and shown on the main page.
location.latitude/location.longitude(default geocode source for both providers)forecast.refresh_interval_seconds = 1800keeps single-call providers at ~48 calls/day (<50/day target)forecast_writer_db.*for the cron writer account (optional but recommended)
- Refresh cache manually:
php src/cli/fetch_forecast.php --force- Add cron (example every 30 minutes, ~48 calls/day):
*/30 * * * * cd /path/to/pws-live-site && php src/cli/fetch_forecast.php >> /var/log/pws-forecast-cron.log 2>&1- Apply SQL schema:
mysql -u DB_USER -p DB_NAME < docs/sql/create_pws_prediction_cache.sql-
Ensure
forecast_writer_db.*insrc/config.local.phpcan write topws_prediction_cache. -
Build predictions manually:
php src/cli/build_predictions.php --forceExpected success output:
Prediction cache refresh completed: run_id=... rows=25
- Add cron (example every 30 minutes):
*/30 * * * * cd /path/to/pws-live-site && php src/cli/build_predictions.php >> /var/log/pws-prediction-cron.log 2>&1Required archive fields for prediction:
dateTimeusUnitsoutTempoutHumiditybarometerwindSpeedrainRate
Optional for better hybrid behavior:
- Daily forecast cache table (
pws_wu_forecast_cache) populated byfetch_forecast.php
The celestial page can use precomputed Skyfield data for sun/moon/planet paths, twilight, moon phase, solar-system, lunar-month, and solar-year daylight details without running WeeWX skin generation.
See full setup and dataset-license notes:
docs/CELESTIAL_CACHE.md
Quick setup:
python3 -m pip install 'skyfield>=1.47' numpy
mysql -u DB_USER -p DB_NAME < docs/sql/create_pws_celestial_cache.sql
php src/cli/build_celestial_cache.php --forceKeep ephemeris, star catalog, constellation, TLE, and similar astronomy datasets outside this repository and outside release archives.
Optional full-dome preparation:
mysql -u DB_USER -p DB_NAME < docs/sql/create_pws_celestial_catalog.sql
php src/cli/import_celestial_catalog.php \
--stars=/path/to/wxskyfield_stars.dat.gz \
--lines=/path/to/wxskyfield_lines.dat \
--forceThe schema and importer are tracked. The catalog datasets are not tracked and should stay outside git/release archives.
- Apply the SQL schema:
mysql -u DB_USER -p DB_NAME < docs/sql/create_pws_history_monthly_summary.sql-
Ensure
history_writer_db.*insrc/config.local.phpcan write topws_history_monthly_summary. If left empty, the app falls back toforecast_writer_db.*, then to the maindbblock. -
Build the previous month manually:
php src/cli/build_monthly_history.phpExpected success output:
Monthly history refresh completed: month=2026-02 inserted=21 existing=0 empty=0 missing=0
Useful flags:
php src/cli/build_monthly_history.php --forcephp src/cli/build_monthly_history.php --month=2026-02
- Add cron on the first day of each month:
5 0 1 * * cd /path/to/pws-live-site && php src/cli/build_monthly_history.php >> /var/log/pws-history-rollup.log 2>&1Behavior:
- closed months are read from
pws_history_monthly_summary - the current month still reads live from
archive_day_* - the history page now honors
history.lookback_yearsinstead of silently truncating to 12 months
Edit src/config.local.php to control filesystem/UI settings and field mappings:
paths.*for filesystem locations (relative paths supported)ui.css_*andui.css_themesfor theme filesui.time_formatfor clock style (24hdefault, or12h)ui.plotly_jsfor plotly loading (autodefault)ui.mqtt_reconnect_delay_msto slow MQTT reconnect attempts during broker/network outages (10000default)mqtt.enabledto enable/disable live MQTT updates (truedefault)mqtt.expose_passwordto explicitly expose MQTT password to browser JS (falsedefault)ui.battery_status_labelsto map integer battery status codes (for example5) to text labelsui.sensor_thresholds.air_quality.alert_levelfor PM2.5 warning highlighting (75default)ui.sensor_thresholds.soil_moisture.low/highfor soil moisture out-of-range highlightingui.graphs.*to enable/disable specific graphsfield_map.*to map logical fields to database column names
If mqtt.enabled = false, the site still works with MySQL polling/history/forecast, but live browser push updates are disabled.
Keep mqtt.expose_password = false unless browser auth is unavoidable for your broker.
Battery note:
- Battery series are auto-detected as status-style values when they are integer codes (for example
0,1,5,9). - In that case, cards/charts show status labels instead of assuming volts.
Air-quality note:
- The dashboard colors PM2.5 values using the Dutch
LuchtmeetnetPM2.5 index-style bands. - The current-weather area also shows a PM2.5 air-quality pill.
pm25_1is suppressed in this installation because it duplicates the primarypm2_5reading.
Install WeeWX MQTT publisher extension on your WeeWX host (WeeWX 5+):
weectl extension install https://github.com/matthewwall/weewx-mqtt/archive/master.zipThen add/configure [StdRESTful][[MQTT]] in weewx.conf (server URL, topic, binding archive, loop) so data is published to your broker topic used by this dashboard.
If your WeeWX setup uses METRICWX for MQTT publishing, keep that setting. The dashboard handles MetricWX/Metric archive differences internally.
Relative paths are resolved against paths.base_dir.
With ui.plotly_js = 'auto' (default), the app scans public/assets/vendor and automatically uses the highest plotly-*.min.js version it finds.
That means you can update Plotly by dropping a newer file, for example:
public/assets/vendor/plotly-3.1.0.min.js
No code changes are needed.
If you want to pin a specific file, set ui.plotly_js to an explicit path (for example assets/vendor/plotly-2.35.2.min.js).
The repository includes a WeeWX extension package that registers custom skyfield live-data observation names (solarAzimuth, solarAltitude, solarTime, lunarAzimuth, lunarAltitude, lunarTime) with WeeWX unit groups.
See:
docs/WEEWX_CUSTOM_OBS_EXTENSION.md
GET /api/latest.phpGET /api/history.php?hours=24&endOffsetHours=0&bucketMinutes=5&fields=outTemp,dewpoint,outHumidity,windSpeed,windGust,windDir,barometer,pressure,rainRate,rainHourly- Metric WeeWX archives with
usUnits=16store rain-like values in centimeters; the API converts those values to millimeters for dashboard display. GET /api/forecast.php(reads cached WU forecast from DB)GET /api/trends.php(archive-based local trend nowcast)GET /api/prediction.php(latest prediction cache run)GET /api/celestial.php?dataset=daily(latest celestial cache row; also supportsmonthlyandyearly)GET /api/celestial_catalog.php(projected visible stars and constellation lines from local catalog tables)GET /api/dump.php(default output: CSV, row-limited)GET /api/dump.php?type=csv->text/csvGET /api/dump.php?type=json->application/jsonGET /api/dump.php?type=xml->application/xml- Optional paging/limits:
limit(capped byapi.dump_max_rows)offset
- Optional token protection:
token=...query orX-Api-Tokenheader whenapi.dump_tokenis configured
There is currently no separate docs/API.md; the API summary lives here in the main README.
Author: Codex (GPT-5)