Field reference

weewx-loopdata manual · weewx-loopdata on GitHub · Report an issue


Every field a report declares is a WeeWX report tag with the $ removed, and becomes a key in that report’s entry in loop-data.txt — the key is the field entry verbatim. As a rule, a tag that works in a Cheetah template works here too, minus the dollar sign; LoopData adds the rolling-window periods, which reports have no equivalent of, and spells $alltime as alltime.

Three field families follow their own grammars and have their own pages: almanac fields (almanac.sunrise, …), station fields (station.uptime.raw, …), and the windrose aggregates (day.windrose.banded, …). Everything else — ordinary observation fields — is on this page.

The grammar at a glance

Every observation field has this shape (brackets mark optional slots):

period.obstype[.agg_type][.unit][.round(n)][.format_spec]

A period’s own boundaries take the place of the observation, and take no aggregate — see Span properties:

period.span_prop[.unit][.round(n)][.format_spec]
  • period: current, trend, hour, day, week, month, year, rainyear, alltime, 1m–1440m, or 1h–24h.
  • obstype: any observation in the loop packet (outTemp, barometer, rain, windSpeed, …), the composite wind, or the composite windrose.
  • agg_type: required for every period except current and trend:
    • every observation: min, mintime, max, maxtime, sum, count, avg
    • wind only (the composite of windSpeed/windDir/windGust/windGustDir), additionally: gustdir, rms, vecavg, vecdir
    • windrose only (not valid for current/trend): exactly sum, time, banded, calm — array/matrix projections, see the windrose page
    • observation types registered with WeeWX’s firstlast accumulator (string-valued types), on the rolling periods: first, last, firsttime, lasttime
  • unit: an optional unit override — see Overriding the unit of a field.
  • round(n): an optional rounding transform, exactly as report tags allow ($day.outTemp.max.round(1).raw): the value is rounded to n digits, then the format spec renders the rounded value. Most useful with .raw, to publish a number with limited digits (29.93 instead of 29.927100000000002).
  • format_spec: optional; just like in a report, it specializes the rendering:
    • No format spec: converted and formatted per the report, with a label (e.g., 64.7°F).
    • .raw: converted per the report, but not formatted (e.g., 64.711).
    • .formatted: converted and formatted per the report, no label (e.g., 64.7) — including the report’s own decimal point, so a report in a language that writes 12,3 gets 12,3. (Before 7.4 this one rendering skipped that, and said 12.3 where the report’s own pages said 12,3.) .raw is a json number and is never affected.
    • .ordinal_compass: for directional observations, the value as text (e.g., SW).
    • .format(...) / .nolabel(...) / .string(...) / .long_form(...): the report tags’ formatting calls, with the same arguments — see Formatting a field with arguments.

Periods

Period Window
current The latest loop packet. No aggregate.
trend Change over the report’s trend window ([Units][Trend] time_delta, default 3 hours, capped at 3 days — see Configuration). No aggregate.
1m – 1440m Rolling window of that many minutes, e.g. 2m, 10m, 90m.
1h – 24h Rolling window of that many hours, e.g. 8h, 24h.
hour The current clock hour.
day Today (local time).
week This week (honors the report’s week_start).
month This calendar month.
year This calendar year.
rainyear This rain year.
alltime Everything in the archive.

The rolling periods act just like day, week, month, year, rainyear and alltime: they take the same aggregates and format specs.

Span properties

A period is a stretch of time, and its four boundary values are report tags in their own right — $week.start, $week.end, $week.length and $week.dateTime. LoopData serves them as fields, under the same names:

Field Value
<period>.start The instant the period began.
<period>.end The instant it ends.
<period>.length How long it runs, as a duration.
<period>.dateTime The same as .start — WeeWX’s alias for it.

They take no aggregate: a span property is a property of the period itself, not of an observation. The unit override, round(n) and every format spec apply as usual. start, end and dateTime are instants and render through the period’s [Units][TimeFormats] entry, exactly as a time-of-event field does; length is a duration and renders as one:

  • week.start.raw might yield 1788678000
  • week.start might yield 00:00:00 (Sunday)
  • week.start.format("%Y-%m-%d") might yield 2026-09-06
  • week.length.raw might yield 604800
  • week.length.long_form() might yield 7 days, 0 hours, 0 minutes
  • week.length.hour.raw might yield 168.0

length is elapsed time, not a calendar constant. In a zone that observes daylight saving, the spring-forward day runs 23 hours (82800 seconds) and its week 167 hours (601200), while the fall-back day runs 25 hours (90000) and its week 169 (608400). The $week.length report tag gives the same, and this is the reason to read the field rather than hardcode 604800: a page dividing elapsed time by a constant week is an hour off twice a year.

They are served on hour, day, week, month, year, rainyear and alltime. The rolling periods (1m–1440m, 1h–24h) and trend have no fixed span — their window slides with every packet — and do not carry them.

alltime is the one period whose span moves. It runs from the earliest record in the archive to the packet in hand, which is what the $alltime report tag spans, so alltime.end and alltime.length advance with every packet while the calendar periods’ stay put until the period rolls over.

On a brand-new station, alltime’s span properties appear with the first archive record. LoopData reads the archive’s earliest record from the database once, when it builds its accumulators on the first loop packet. If you install WeeWX and LoopData together, the archive is still empty at that moment and there is no earliest record to read, so alltime.start, alltime.end, alltime.length and alltime.dateTime are omitted until the station writes its first archive record — one archive interval, five minutes by default. LoopData takes the value from that record itself, so it costs no further database access. Every other field, alltime aggregates included, works from the first packet. In practice you are unlikely to see the gap at all: WeeWX generates its reports on archive records, so the page that would display those fields is first written by the same archive record that supplies them. (On the unusual station whose reports read a different data_binding than [StdArchive] writes, LoopData cannot tell which archive a record belongs to, so it keeps to the startup reading and those four fields wait for a restart; the log says so at startup.)

The obvious use is a page that must know whether a period began today — whether a “week high” is a real weekly record or just today’s high wearing a badge, on the first day of a week. Comparing week.start.raw with day.start.raw answers that on every packet, and keeps answering it correctly across midnight, which a value baked into the page at report time cannot.

Because a period’s dateTime is its start, <period>.dateTime with an aggregate on it — day.dateTime.max and its kin — is not a field. That matches the report tags, where $day.dateTime has always been the day’s start and never the dateTime observation. current.dateTime is unaffected: current is an instant, not a span, so current.dateTime.raw remains the packet’s own timestamp.

Examples

Current outside temperature:

  • current.outTemp.formatted might yield 79.2
  • current.outTemp might yield 79.2°F
  • current.outTemp.raw might yield 79.175

Maximum wind in the last 30 minutes:

  • 30m.wind.max.formatted might yield 7.1
  • 30m.wind.max might yield 7.1 mph
  • 30m.wind.max.raw might yield 7.12

Average outside temperature over the last 3 hours:

  • 3h.outTemp.avg.formatted might yield 32.4
  • 3h.outTemp.avg might yield 32.4°
  • 3h.outTemp.avg.raw might yield 32.41

Average inside temperature this clock hour:

  • hour.inTemp.avg.formatted might yield 68.1
  • hour.inTemp.avg might yield 68.1°
  • hour.inTemp.avg.raw might yield 68.12

Day average of outside temperature:

  • day.outTemp.avg.formatted might yield 64.7
  • day.outTemp.avg might yield 64.7°
  • day.outTemp.avg.raw might yield 64.711

Wind speed average for this week:

  • week.windSpeed.avg.formatted might yield 2.7
  • week.windSpeed.avg might yield 2.7 mph
  • week.windSpeed.avg.raw might yield 2.74

Minimum dewpoint this month, and when:

  • month.dewpoint.min might yield 43.7°
  • month.dewpoint.mintime might yield 08/01/2020 03:27:00 AM

Maximum wind speed this year, and when:

  • year.wind.max might yield 29.6 mph
  • year.wind.maxtime might yield 02/26/2020 07:40:00 PM

Total rain this rain year:

  • rainyear.rain.sum.formatted might yield 7.1
  • rainyear.rain.sum might yield 7.1 in
  • rainyear.rain.sum.raw might yield 7.13

All-time high outside temperature:

  • alltime.outTemp.max.formatted might yield 107.3
  • alltime.outTemp.max might yield 107.3°
  • alltime.outTemp.max.raw might yield 107.29

Time-of-event fields

Time-of-event fields (maxtime, mintime, firsttime, lasttime) are formatted exactly as WeeWX report tags format them: the field’s period is the time context, so the report’s [Units][TimeFormats] entry for that period applies. With the standard settings: hour is %H:%M, day is %X, week is %X (%A), and month/year/rainyear are %x %X. alltime uses the year format, as WeeWX’s $alltime tag does. Rolling periods (1m–1440m, 1h–24h) use the current format. For example:

  • day.outTemp.maxtime might yield 12:00:00
  • hour.outTemp.maxtime might yield 12:00

To take full control of the rendering, use a formatting call with a strftime format: day.outTemp.maxtime.format("%H:%M").

%x, %X, %A and %b are answered by the locale, not by the format string: what %x puts first, and whether %A reads Saturday or Samstag, depends on the report’s lang — and on lang being a name the machine knows, which de is not. See Month names and decimal points.

Overriding the unit of a field

By default every field is converted to the unit the declaring report calls for. A field may instead name an explicit unit, exactly as WeeWX report tags allow (e.g. $current.outTemp.degree_C). The unit goes right after the aggregation, before the optional round(n) and format spec. Any unit WeeWX knows for the observation’s unit group is accepted. For example, regardless of the report’s configured units:

  • current.windSpeed.beaufort might yield 5
  • current.windSpeed.beaufort.formatted might yield 5
  • day.outTemp.avg.degree_C might yield 18.3°C
  • day.outTemp.avg.degree_C.raw might yield 18.33
  • day.outTemp.avg.degree_F.raw might yield 64.99
  • 10m.windGust.max.knot.raw might yield 6.18
  • trend.barometer.mbar.formatted might yield 2.4

This is handy for gauges that expect a fixed unit (for example a Beaufort wind gauge) no matter what units the rest of the report uses. The override applies to value fields only; the unit.label prefix form has no override (matching WeeWX, whose $unit.label is obstype-only). If the named unit is incompatible with the observation’s group (e.g. day.outTemp.avg.beaufort), the field is simply omitted from loop-data.txt.

A unit must already be registered with WeeWX when LoopData starts: all of WeeWX’s own units (including beaufort) always are, but a unit registered by another extension is recognized only if that extension initializes before LoopData.

Formatting a field with arguments (call syntax)

The formatting methods a WeeWX report tag can call are also available as format specs, with the same names, arguments and output as the report tag:

  • format(format_string, None_string, add_label, localize) — all arguments optional
  • nolabel(format_string, None_string) — like format(), but with no label
  • string(None_string) — the report’s default formatting, with control over missing data
  • long_form(format_string, None_string) — delta times spelled out (e.g. sunshine duration)

For example:

  • day.outTemp.maxtime.format("%H:%M") might yield 12:05
  • current.outTemp.format(add_label=False) might yield 79.2
  • day.windGust.max.nolabel("%.0f") might yield 9
  • day.rain.sum.format("%.2f", add_label=False) might yield 0.31
  • day.sunshineDur.sum.long_form() might yield 6 hours, 25 minutes, 10 seconds

WeeWX before 4.10.2 has no long_form that LoopData can call, so LoopData provides it there. On WeeWX before 4.10, which neither ships nor reads [Units][DeltaTimeFormats], an almanac or uptime duration takes its format from the report’s [Units][TimeFormats], as that WeeWX’s own .formatted does: short_delta for a body’s visible time (almanac.sun.visible), brief_delta for its change from yesterday (almanac.sun.visible_change) and long_delta for station.uptime and station.os_uptime. Those versions ship all three in [Units][TimeFormats], so that is where to change them; a [Units][DeltaTimeFormats] entry under one of those three names, if you add one, comes first. From 4.10 on, the same durations use [Units][DeltaTimeFormats] day, hour and month.

Exactly as in a report, a time-of-event field’s format_string is a strftime format, and a numeric field’s is a %-format. Arguments must be literals; positional arguments bind exactly as the report tag’s do. A bare spec name (day.rain.sum.string) is a zero-argument call, just as Cheetah renders $day.rain.sum.string. The unit override and round(n) compose as usual: day.outTemp.avg.degree_C.nolabel("%.2f"), day.barometer.max.mbar.round(1).raw.

A call containing a comma must be quoted in the declaration, or ConfigObj will split the entry at the comma into two bogus fields: rain = day.rain.sum, 'day.rain.sum.format("%.2f", add_label=False)' — and close the quote: an unbalanced one makes ConfigObj backtrack for minutes rather than raise, which hangs weewxd at startup (see Declaring fields). Calls without a comma (e.g. day.outTemp.maxtime.format("%H:%M")) need no quoting. The json key is the field entry verbatim (without the outer quotes).

Missing data

If a field is requested but the data is missing — say a trend before enough packets have arrived, or an observation the station doesn’t report — the field will not be present in loop-data.txt. Your javascript should expect absent keys and react accordingly (see Building a live page).

The exception: string(), or an explicit None_string argument to any formatting call, overrides the omission. The field is then always emitted, rendering missing data exactly as the report tag would (N/A unless a None_string says otherwise). For example, trend.outTemp.string("n/a") is present from the very first packet — handy for pages that want every key present from the start.

Special fields

unit.label.<obs>

The report’s unit label for an observation, e.g. unit.label.outTemp might yield °F. Useful for drawing dial scales and legends that follow the report’s units automatically. (unit.label.windrose yields the distance unit label used by windrose.sum — see the windrose page.)

trend.barometer.desc and trend.barometer.code

trend.barometer.desc provides a text version of the barometer rate (e.g., Falling Slowly); the texts translate through the report’s [Texts] — see Translating trend.barometer.desc. trend.barometer.code provides an integer of value -4, -3, -2, -1, 0, 1, 2, 3 or 4, corresponding to Falling Very Rapidly, Falling Quickly, Falling, Falling Slowly, Steady, Rising Slowly, Rising, Rising Quickly and Rising Very Rapidly, respectively.

Aggregates via xtypes are not supported

If an aggregate is implemented via xtypes, it will be ignored by loopdata. For example, the weewx-purple extension implements pm2_5_aqi via xtypes. If, say, week.pm2_5_aqi.max were declared, it would be ignored — there is no database entry from which to look up the weekly high for pm2_5_aqi.

The rule is: if an observation is not stored in the database, you can’t specify aggregates for it, for any period — day included, since a day’s high or low built from loop packets alone would run from the last restart rather than from midnight. Each refused observation is logged once per period at startup (Ignoring pm2_5_aqi for day time period as this observation has no day accumulator). WeeWX’s own report tags can compute such an aggregate through the xtype, but only by reading every archive record in the period and computing the value for each; loopdata builds its accumulators on the engine thread at the first loop packet and cannot afford that scan, so it does not try. LoopData will still report current values if you specify them (e.g. current.pm2_5_aqi.raw works — the value is in the loop packet).

What report tags can do that fields cannot

For rendering values, the fields grammar is at parity with report tags: any period tag with a standard aggregate, a period’s own span properties, converted to any unit, rounded, and formatted with the full set of formatting calls. What remains report-only:

  • Aggregates computed by xtypes ($day.heatdeg.sum, $year.growdeg.sum, …) — see above.
  • Offset periods: $yesterday, $day($days_ago=1), $month($months_ago=1), $rainyear($years_ago=1) and the arbitrary $span(...).
  • Series tags ($day.outTemp.series(...)).
  • The introspection helpers .json, .exists and .has_data (a field with missing data is simply absent from loop-data.txt — or always emitted, via string()).
  • Non-observation tags: $latitude, $longitude, $altitude, $Extras, $gettext, $obs, and the like. The exceptions are $unit.label.<obs>, supported as unit.label.<obs> above, and $station, supported as station fields — $latitude et al. have $station equivalents.

weewx-loopdata is Copyright © 2020–2026 John A Kline and is licensed under the GNU Public License v3.