The WordPress side of NightWatcher. The NightWatcher daemon pushes Sky Quality Meter readings to this plugin's authenticated REST endpoint; the plugin stores them and renders a read-only, date-range sky-brightness graph.
Push (not pull) means the daemon can stay on a private home network — nothing needs to reach inward to it.
nightwatcherd (home LAN) ──HTTPS POST──► /wp-json/nightwatcher/v1/ingest ──► wp table
│
[nightwatcher_graph] shortcode ◄── /wp-json/nightwatcher/v1/readings ◄─────────┘
- Copy this folder into
wp-content/plugins/nightwatcher-wp/and activate it — this creates its tables (see Database). - Settings → NightWatcher SQM → Generate a strong token, and copy the Ingest URL.
- In NightWatcher, add a webhook export target with that URL and the token as the bearer secret.
- Put the shortcode on a page:
[nightwatcher_graph sensor="DSN036" days="7"].
The plugin keeps its data in WordPress's own MySQL/MariaDB database — there is no separate server
or manual schema step. On activation it creates three tables under your $wpdb prefix via dbDelta,
using only standard column types, so any MySQL/MariaDB that runs your WordPress works:
| Table (default prefix) | One row per | Grows |
|---|---|---|
wp_nightwatcher_sensors |
sensor (id, location, timezone) | no |
wp_nightwatcher_readings |
SQM reading (mag/arcsec², sensor temp) |
yes |
wp_nightwatcher_weather |
co-located ambient-weather point | yes |
The database user WordPress already uses needs only the usual privileges it grants (CREATE/ALTER
for activation, INSERT/SELECT in normal use, DELETE if you prune) — nothing special.
Footprint. At a 5-minute push cadence the two growing tables each add ~288 rows/day per sensor —
about 15–25 MB/year per sensor including indexes. Both de-duplicate on (sensor_id, ts_utc), so
re-sending an overlapping window (e.g. a NightWatcher backfill) never bloats them.
Keeping it bounded. This database is a read-only mirror — NightWatcher's own database and the monthly DSN files are the source of truth — so it is safe to prune here without losing data. To cap disk use:
-
Trim old rows. Safe to re-run; keep at least the longest range your graph offers:
DELETE FROM wp_nightwatcher_readings WHERE ts_utc < UTC_TIMESTAMP() - INTERVAL 400 DAY; DELETE FROM wp_nightwatcher_weather WHERE ts_utc < UTC_TIMESTAMP() - INTERVAL 400 DAY;
Schedule it from host cron or WP-Cron to make retention automatic.
-
Mind your host's database-size quota. Shared hosting usually enforces one; reaching it fails new writes rather than filling the whole disk.
-
Cap binary logs if MySQL binary logging is enabled (
binlog_expire_logs_seconds) — unmanaged binlogs can fill a disk faster than this data ever will.
NightWatcher's webhook exporter sends:
POST /wp-json/nightwatcher/v1/ingest
Authorization: Bearer <token>
Content-Type: application/json
{
"site_id": "DSN036-S",
"sensor": { "id": "DSN036", "name": "CrestaLoma Observatory", "latitude": 31.95, "longitude": -111.6,
"elevation_m": 1200, "timezone": "America/Phoenix" },
"readings": [
{ "ts_utc": "2026-07-20 05:00:00", "mag_arcsec2": 21.34, "temp_c": 18.2, "quality": "ok" }
]
}
Response: { "ok": true, "sensor_id": "DSN036", "received": N, "stored": M }. Readings are
de-duplicated on (sensor_id, ts_utc), so re-sending an overlapping window is safe.
| Method | Route | Auth | Purpose |
|---|---|---|---|
POST |
/nightwatcher/v1/ingest |
Bearer token | Receive a push of sensor metadata + readings |
GET |
/nightwatcher/v1/readings?sensor=&from=&to= |
public | Readings for the graph (defaults to last 7 days) |
GET |
/nightwatcher/v1/sensors |
public | Known sensors + metadata |
[nightwatcher_graph sensor="DSN036" days="7" height="320" title="CrestaLoma Observatory sky brightness"]
Renders a uPlot line of mag/arcsec² over time with From/To date pickers. When the sensor has
coordinates, Sun and Moon altitude are overlaid (dashed lines on a right-side degrees axis) with
a moon-phase glyph riding on the Moon line — computed in-browser from the sensor's location. When
NightWatcher also pushes co-located weather, an ambient temperature line (°C) is overlaid too.
Visitors toggle each with the graph's Sun/Moon/Ambient checkboxes; set the per-graph
default with sun="0", moon="0", and/or ambient="0". A Live checkbox auto-refreshes the graph
as new readings arrive (off by default; enable it per embed with live="1", and each visitor's choice
is remembered in their browser). Vendored uPlot (MIT) and SunCalc (BSD-2-Clause); no external CDNs.
| Attribute | Default | Meaning |
|---|---|---|
sensor |
— | Sensor id to graph (required) |
days |
7 |
Initial range, in days back from now |
height |
320 |
Graph height in px |
title |
— | Optional heading above the graph |
sun |
1 |
Show the Sun-altitude overlay by default (0 to hide) |
moon |
1 |
Show the Moon-altitude overlay + phase glyph by default (0 to hide) |
ambient |
1 |
Show the ambient-temperature overlay by default, when weather is available (0 to hide) |
live |
0 |
Start with Live auto-refresh on (1); visitors can toggle it, and the choice is remembered per browser |
nightwatcher-wp.php plugin bootstrap (hooks, activation)
includes/class-nw-store.php tables + upsert/query
includes/class-nw-rest.php REST routes (ingest + read)
includes/class-nw-settings.php admin settings (token + endpoint URLs)
includes/class-nw-shortcode.php [nightwatcher_graph]
assets/js/nightwatcher.js graph (uPlot)
assets/css/nightwatcher.css graph styles
assets/vendor/uPlot.* vendored uPlot
GPL-3.0-or-later, matching NightWatcher.
