Skip to content

The Cheetah generator

File generation is done using the Cheetah templating engine, which processes a template, replacing any symbolic tags, then produces an output file. Typically, it runs after each new archive record (usually about every five minutes), but it can also run on demand using the utility weectl report run.

The Cheetah engine is very powerful, essentially letting you have the full semantics of Python available in your templates. As this would make the templates incomprehensible to anyone but a Python programmer, WeeWX adopts a very small subset of its power.

The Cheetah generator is controlled by the configuration options in the section [CheetahGenerator] of the skin configuration file.

Let's take a look at how this works.

Which files get processed?

Each template file is named something like D/F.E.tmpl, where D is the (optional) directory the template sits in and will also be the directory the results will be put in, and F.E is the generated file name. So, given a template file with name Acme/index.html.tmpl, the results will be put in HTML_ROOT/Acme/index.html.

The configuration for a group of templates will look something like this:

[CheetahGenerator]
    [[index]]
        template = index.html.tmpl
    [[textfile]]
        template = filename.txt.tmpl
    [[xmlfile]]
        template = filename.xml.tmpl

There can be only one template in each block. In most cases, the block name does not matter — it is used only to isolate each template. However, there are four block names that have special meaning: SummaryByDay, SummaryByMonth, SummaryByYear, and ToDate.

Specifying template files

By way of example, here is the [CheetahGenerator] section from the skin.conf for the skin Seasons.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
[CheetahGenerator]
    # The CheetahGenerator creates files from templates.  This section
    # specifies which files will be generated from which template.

    # Possible encodings include 'html_entities', 'strict_ascii', 'normalized_ascii',
    # as well as those listed in https://docs.python.org/3/library/codecs.html#standard-encodings
    encoding = html_entities

    [[SummaryByMonth]]
        # Reports that summarize "by month"
        [[[NOAA_month]]]
            encoding = normalized_ascii
            template = NOAA/NOAA-%Y-%m.txt.tmpl

    [[SummaryByYear]]
        # Reports that summarize "by year"
        [[[NOAA_year]]]
            encoding = normalized_ascii
            template = NOAA/NOAA-%Y.txt.tmpl

    [[ToDate]]
        # Reports that show statistics "to date", such as day-to-date,
        # week-to-date, month-to-date, etc.
        [[[index]]]
            template = index.html.tmpl
        [[[statistics]]]
            template = statistics.html.tmpl
        [[[telemetry]]]
            template = telemetry.html.tmpl
        [[[tabular]]]
            template = tabular.html.tmpl
        [[[celestial]]]
            template = celestial.html.tmpl
            # Uncomment the following to have WeeWX generate a celestial page only once an hour:
            # stale_age = 3600
        [[[RSS]]]
            template = rss.xml.tmpl

The skin contains three different kinds of generated output:

  1. Summary by Month (line 9). The skin uses SummaryByMonth to produce NOAA summaries, one for each month, as a simple text file.

  2. Summary by Year (line 15). The skin uses SummaryByYear to produce NOAA summaries, one for each year, as a simple text file.

  3. Section "To Date" (line 21). The skin produces an HTML index.html page, as well as HTML files for detailed statistics, telemetry, and celestial information. It also includes a master page (tabular.html) in which NOAA information is displayed. All these files are HTML.

Because the option

encoding = html_entities

appears directly under [CheetahGenerator], this will be the default encoding of the generated files unless explicitly overridden. We see an example of this under [SummaryByMonth] and [SummaryByYear], which override the default by specifying option normalized_ascii (which replaces accented characters with a non-accented analog).

Other than SummaryByMonth and SummaryByYear, the section names are arbitrary. The section [[ToDate]] could just as well have been called [[files_to_date]], and the sections [[[index]]], [[[statistics]]], and [[[telemetry]]] could just as well have been called [[[tom]]], [[[dick]]], and [[[harry]]].

[[SummaryByYear]]

Use SummaryByYear to generate a set of files, one file per year. The name of the template file should contain a strftime() code for the year; this will be replaced with the year of the data in the file.

[CheetahGenerator]
    [[SummaryByYear]]
        # Reports that summarize "by year"
        [[[NOAA_year]]]
            encoding = normalized_ascii
            template = NOAA/NOAA-%Y.txt.tmpl

The template NOAA/NOAA-%Y.txt.tmpl might look something like this:

           SUMMARY FOR YEAR $year.dateTime

MONTHLY TEMPERATURES AND HUMIDITIES:
#for $record in $year.records
$record.dateTime $record.outTemp $record.outHumidity
#end for

[[SummaryByMonth]]

Use SummaryByMonth to generate a set of files, one file per month. The name of the template file should contain a strftime() code for year and month; these will be replaced with the year and month of the data in the file.

[CheetahGenerator]
    [[SummaryByMonth]]
        # Reports that summarize "by month"
        [[[NOAA_month]]]
            encoding = normalized_ascii
            template = NOAA/NOAA-%Y-%m.txt.tmpl

The template NOAA/NOAA-%Y-%m.txt.tmpl might look something like this:

           SUMMARY FOR MONTH $month.dateTime

DAILY TEMPERATURES AND HUMIDITIES:
#for $record in $month.records
$record.dateTime $record.outTemp $record.outHumidity
#end for

[[SummaryByDay]]

While the Seasons skin does not make use of it, there is also a SummaryByDay capability. As the name suggests, this results in one file per day. The name of the template file should contain a strftime() code for the year, month and day; these will be replaced with the year, month, and day of the data in the file.

[CheetahGenerator]
    [[SummaryByDay]]
        # Reports that summarize "by day"
        [[[NOAA_day]]]
            encoding = normalized_ascii
            template = NOAA/NOAA-%Y-%m-%d.txt.tmpl

The template NOAA/NOAA-%Y-%m-%d.txt.tmpl might look something like this:

           SUMMARY FOR DAY $day.dateTime

HOURLY TEMPERATURES AND HUMIDITIES:
#for $record in $day.records
$record.dateTime $record.outTemp $record.outHumidity
#end for

Note

This can create a lot of files — one per day. If you have 3 years of records, this would be more than 1,000 files!

Tags

If you look inside a template, you will see it makes heavy use of tags. As the Cheetah generator processes the template, it replaces each tag with an appropriate value and, sometimes, a label. This section discusses the details of how that happens.

If there is a tag error during template generation, the error will show up in the log file. Many errors are obvious — Cheetah will display a line number and list the template file in which the error occurred. Unfortunately, in other cases, the error message can be very cryptic and not very useful. So make small changes and test often. Use the utility weectl report run to speed up the process.

Here are some examples of tags:

$current.outTemp
$month.outTemp.max
$month.outTemp.maxtime

These code the current outside temperature, the maximum outside temperature for the month, and the time that maximum occurred, respectively. So a template file that contains:

<html>
    <head>
        <title>Current conditions</title>
    </head>
    <body>
        <p>Current temperature = $current.outTemp</p>
        <p>Max for the month is $month.outTemp.max, which occurred at $month.outTemp.maxtime</p>
    </body>
</html>

would be all you need for a very simple HTML page that would display the text (assuming that the unit group for temperature is degree_F):

Current temperature = 51.0°F
Max for the month is 68.8°F, which occurred at 07-Oct-2009 15:15

The format that was used to format the temperature (51.0) is specified in section [Units][[StringFormat]]. The unit label °F is from section [Units][[Labels]], while the time format is from [Units][[TimeFormats]].

As we saw above, the tags can be very simple:

# Output max outside temperature using an appropriate format and label:
$month.outTemp.max

Most of the time, tags will "do the right thing" and are all you will need. However, WeeWX offers extensive customization of the tags for specialized applications such as XML RSS feeds, or rigidly formatted reports (such as the NOAA reports). This section specifies the various tag options available.

There are two different versions of the tags, depending on whether the data is "current", or an aggregation over time. However, both versions are similar.

Time period $current

Time period $current represents a current observation. An example would be the current barometric pressure:

$current.barometer

Formally, for current observations, WeeWX first looks for the observation type in the record emitted by the NEW_ARCHIVE_RECORD event. This is generally the data emitted by the station console, augmented by any derived variables (e.g., wind chill) that you might have specified. If the observation type cannot be found there, the most recent record in the database will be searched. If it still cannot be found, WeeWX will attempt to calculate it using the xtypes system.

The most general tag for a "current" observation looks like:

$current(timestamp=some_time, max_delta=delta_t,data_binding=binding_name)
    .obstype
    [.unit_conversion]
    [.rounding]
    [.formatting]

Where:

timestamp is a timestamp that you want to display in unix epoch time. It is optional, The default is to display the value for the current time.

max_delta is the largest acceptable time difference (in seconds) between the time specified by timestamp and a record's timestamp in the database. By default, it is zero, which means there must be an exact match with a specified time for a record to be retrieved. If it were 30, then a record up to 30 seconds away would be acceptable.

data_binding is a binding name to a database. An example would be wx_binding. See the section Binding names for more details.

obstype is an observation type, such as barometer. This type must appear either in the current record, as a field in the database, or can be derived from some combination of the two as an XType.

unit_conversion is an optional unit conversion tag. If provided, the results will be converted into the specified units, otherwise the default units specified in the skin configuration file (in section [Units][[Groups]]) will be used. See the section Unit conversion options.

rounding is an optional rounding tag. If provided, it rounds the result to a fixed number of decimal digits. See the section Rounding options.

formatting is an optional formatting tag. If provided, it controls how the value will appear. See the section Formatting options.

Time period $latest

Time period $latest is very similar to $current, except that it uses the last available timestamp in a database. Usually, $current and $latest are the same, but if a data binding points to a remote database, they may not be. See the section Using multiple bindings for an example where this happened.

Aggregation periods

Aggregation periods is the other kind of tag. For example,

$week.rain.sum

represents an aggregation over time, using a certain aggregation type. In this example, the aggregation time is a week, and the aggregation type is summation. So, this tag represents the total rainfall over a week.

The most general tag for an aggregation over time looks like:

$period(data_binding=binding_name[, ago=delta])
    .obstype
    .aggregation
    [.unit_conversion]
    [.rounding]
    [.formatting]

Where:

period is the aggregation period over which the aggregation is to be done. Possible choices are listed in the aggregation periods table.

data_binding is a binding name to a database. An example would be wx_binding. See the section Binding names for more details.

ago is a keyword that depends on the aggregation period. For example, for week, it would be weeks_ago, for day, it would be days_ago, etc.

delta is an integer indicating which aggregation period is desired. For example $week(weeks_ago=1) indicates last week, $day(days_ago=2) would be the day-before-yesterday, etc. The default is zero: that is, this aggregation period.

obstype is an observation type. This is generally any observation type that appears in the database (such as outTemp or windSpeed), as well a most XTypes. However, not all aggregations are supported for all types.

aggregation is an aggregation type. If you ask for $month.outTemp.avg you are asking for the average outside temperature for the month. Possible aggregation types are given in the reference Aggregation types.

unit_conversion is an optional unit conversion tag. If provided, the results will be converted into the specified units, otherwise the default units specified in the skin configuration file (in section [Units][[Groups]]) will be used. See the section Unit conversion options.

rounding is an optional rounding tag. If provided, it rounds the result to a fixed number of decimal digits. See the section Rounding options.

formatting is an optional formatting tag. If provided, it controls how the value will appear. See the section Formatting options.

There are several aggregation periods that can be used:

Aggregation periods
Aggregation period Meaning Example Meaning of example
$hour This hour. $hour.outTemp.maxtime The time of the max temperature this hour.
$day Today (since midnight). $day.outTemp.max The max temperature since midnight
$yesterday Yesterday. Synonym for $day($days_ago=1). $yesterday.outTemp.maxtime The time of the max temperature yesterday.
$week This week. The start of the week is set by option week_start. $week.outTemp.max The max temperature this week.
$month This month (since the first of the month). $month.outTemp.min The minimum temperature this month.
$year This year (since 1-Jan). $year.outTemp.max The max temperature since the start of the year.
$rainyear This rain year. The start of the rain year is set by option rain_year_start. $rainyear.rain.sum The total rainfall for this rain year.
$alltime All records in the database given by binding_name. $alltime.outTemp.max The maximum outside temperature in the default database.

The "ago" parameters can be useful for statistics farther in the past. Here are some examples:

Aggregation period Example Meaning
$hour(hours_ago=h) $hour(hours_ago=1).outTemp.avg The average temperature last hour (1 hour ago).
$day(days_ago=d) $day(days_ago=2).outTemp.avg The average temperature day before yesterday (2 days ago).
$week(weeks_ago=w) $week(weeks_ago=1).outTemp.max The maximum temperature last week.
$month(months_ago=m) $month(months_ago=1).outTemp.max The maximum temperature last month.
$year(years_ago=y) $year(years_ago=1).outTemp.max The maximum temperature last year.

Unit conversion options

The option unit_conversion can be used with either current observations or with aggregations. If supplied, the results will be converted to the specified units. For example, if you have set group_pressure to inches of mercury (inHg), then the tag

Today's average pressure=$day.barometer.avg

would normally give a result such as

Today's average pressure=30.05 inHg

However, if you add mbar to the end of the tag,

Today's average pressure=$day.barometer.avg.mbar

then the results will be in millibars:

Today's average pressure=1017.5 mbar

If an inappropriate or nonsense conversion is asked for, e.g.,

Today's minimum pressure in mbars: $day.barometer.min.mbar
or in degrees C: $day.barometer.min.degree_C
or in foobar units: $day.barometer.min.foobar

then the offending tag(s) will be put in the output:

Today's minimum pressure in mbars: 1015.3 or in degrees C: $day.barometer.min.degree_C or in foobar units: $day.barometer.min.foobar

Rounding options

The data in the resultant tag can be optionally rounded to a fixed number of decimal digits. This is useful when emitting raw data or JSON strings. It should not be used with formatted data. In that case, using a format string would be a better choice.

The structure of the option is

.round(ndigits=None)

where ndigits is the number of decimal digits to retain. If None (the default), then all digits will be retained.

Formatting options

A variety of options are available to you to customize the formatting of the final observation value. They can be used whenever a tag results in a ValueHelper, which is almost all the time. This table summarizes the options:

Formatting options
Formatting option Comment
.format(args) Format the value as a string, according to a set of optional args.
.long_form(args) Format delta times in the "long form", according to a set of optional args.
.ordinal_compass Format the value as a compass ordinals (e.g., "SW"), useful for wind directions. The ordinal abbreviations are set by option directions in the skin configuration file.
.json Format the value as a JSON string.
.raw Return the value "as is", without being converted to a string and without any formatting applied. This can be useful for doing arithmetic directly within the templates. You must be prepared to deal with a potential value of None.

format()

The results of a tag can be optionally formatted using option format(). It has the formal structure:

format(format_string=None, None_string=None, add_label=True, localize=True)

Here is the meaning of each of the optional arguments:

Optional arguments for format()
Optional argument Comment
format_string If set, use the supplied string to format the value. Otherwise, if set to None, then an appropriate value from [Units][[StringFormats]] will be used.
None_string Should the observation value be NONE, then use the supplied string (typically, something like "N/A"). If None_string is set to None, then the value for NONE in [Units][[StringFormats]] will be used.
add_label If set to True (the default), then a unit label (e.g., °F) from skin.conf will be attached to the end. Otherwise, it will be left out.
localize If set to True (the default), then localize the results. Otherwise, do not.

If you're willing to honor the ordering of the arguments, the argument name can be omitted.

long_form()

The option long_form(), can be used to format delta times. A delta time is the difference between two times, for example, the amount of uptime (the difference between start up and the current time). By default, this will be formatted as the number of elapsed seconds. For example, a template with the following

<p>WeeWX has been up $station.uptime</p>

will result in

WeeWX has been up 101100 seconds

The "long form" breaks the time down into constituent time elements. For example,

<p>WeeWX has been up $station.uptime.long_form</p>

results in

WeeWX has been up 1 day, 4 hours, 5 minutes

The option long_form() has the formal structure

long_form(format_string=None, None_string=None)

Here is the meaning of each of the optional arguments:

Optional arguments for long_form()
Optional argument Comment
format_string Use the supplied string to format the value.
None_string Should the observation value be NONE, then use the supplied string to format the value (typically, something like "N/A").

The argument format_string uses special symbols to represent its constitutent components. Here's what they mean:

Symbol Meaning
day The number of days
hour The number of hours
minute The number of minutes
second The number of seconds
day_label The label used for days
hour_label The label used for hours
minute_label The label used for minutes
second_label The label used for seconds

Putting this together, the example above could be written

<p>WeeWX has been up $station.uptime.long_form(format_string="%(day)d%(day_label)s, %(hour)d%(hour_label)s, %(minute)d%(minute_label)s")</p>

Formatting examples

This section gives a number of example tags, and their expected output. The following values are assumed:

Values used in the formatting examples
Observation Value
outTemp 45.2°F
UV None
windDir 138°
dateTime 1270250700
uptime 101100 seconds

Here are the examples:

Formatting options with expected results
Tag Result Result
type
Comment
$current.outTemp 45.2°F str String formatting from [Units][[StringFormats]]. Label from [Units][[Labels]].
$current.outTemp.format 45.2°F str Same as the $current.outTemp.
$current.outTemp.format() 45.2°F str Same as the $current.outTemp.
$current.outTemp.format(format_string="%.3f") 45.200°F str Specified string format used; label from [Units][[Labels]].
$current.outTemp.format("%.3f") 45.200°F str As above, except a positional argument, instead of the named argument, is being used.
$current.outTemp.format(add_label=False) 45.2 str No label. The string formatting is from [Units][[StringFormats]].
$current.UV N/A str The string specified by option NONE in [Units][[StringFormats]].
$current.UV.format(None_string="No UV") No UV str Specified None_string is used.
$current.windDir 138° str Formatting is from option degree_compass in [Units][[StringFormats]].
$current.windDir.ordinal_compass SW str Ordinal direction from section [Units][[Ordinates]] is being substituted.
$current.dateTime 02-Apr-2010 16:25 str Time formatting from [Units][[TimeFormats]] is being used.
$current.dateTime.format(format_string="%H:%M") 16:25 str Specified time format used.
$current.dateTime.format("%H:%M") 16:25 str As above, except a positional argument, instead of the named argument, is being used.
$current.dateTime.raw 1270250700 int Raw Unix epoch time. The result is an integer.
$current.outTemp.raw 45.2 float Raw float value. The result is a float.
$current.outTemp.degree_C.raw 7.33333333 float Raw float value in degrees Celsius. The result is a float.
$current.outTemp.degree_C.json 7.33333333 str Value in degrees Celsius, converted to a JSON string.
$current.outTemp.degree_C.round(2).json 7.33 str Value in degrees Celsius, rounded to two decimal digits, then converted to a JSON string.
$station.uptime 101100 seconds str WeeWX uptime.
$station.uptime.hour 28.1 hours str WeeWX uptime, with unit conversion to hours.
$station.uptime.long_form 1 day, 4 hours, 5 minutes str WeeWX uptime with "long form" formatting.

start, end, and dateTime

While not an observation type, in many ways the time of an observation, dateTime, can be treated as one. A tag such as

$current.dateTime

represents the current time (more properly, the time as of the end of the last archive interval) and would produce something like

01/09/2010 12:30:00

Like true observation types, explicit formats can be specified, except that they require a strftime() time format , rather than a string format.

For example, adding a format descriptor like this:

$current.dateTime.format("%d-%b-%Y %H:%M")

produces

09-Jan-2010 12:30

For aggregation periods, such as $month, you can request the start, end, or length of the period, by using suffixes .start, .end, or .length, respectively. For example,

The current month runs from $month.start to $month.end and has $month.length.format("%(day)d %(day_label)s").

results in

The current month runs from 01/01/2010 12:00:00 AM to 02/01/2010 12:00:00 AM and has 31 days.

The returned string values will always be in local time. However, if you ask for the raw value

$current.dateTime.raw

the returned value will be in Unix Epoch Time (number of seconds since 00:00:00 UTC 1 Jan 1970, i.e., a large number), which you must convert yourself. It is guaranteed to never be None, so you don't worry have to worry about handling a None value.

Tag $trend

The tag $trend is available for time trends, such as changes in barometric pressure. Here are some examples:

Tag Results
$trend.barometer -.05 inHg
$trend(time_delta=3600).barometer -.02 inHg
$trend.outTemp 1.1 °C
$trend.time_delta 10800 secs
$trend.time_delta.hour 3 hrs

Note how you can explicitly specify a time interval in the tag itself (2nd row in the table above). If you do not specify a value, then a default time interval, set by option time_delta in the skin configuration file, will be used. This value can be retrieved by using the syntax $trend.time_delta (4th row in the table).

For example, the template expression

The barometer trend over $trend.time_delta.hour is $trend.barometer.format("%+.2f")

would result in

The barometer trend over 3 hrs is +.03 inHg.

Tag $span

The tag $span allows aggregation over a user defined period up to and including the current time. Its most general form looks like:

$span([data_binding=binding_name][,delta=delta][,boundary=(None|'midnight')])
    .obstype
    .aggregation
    [.unit_conversion]
    [.formatting]

Where:

data_binding is a binding name to a database. An example would be wx_binding. See the section Binding names for more details.

delta is one or more comma separated delta settings from the table below. If more than one delta setting is included then the period used for the aggregate is the sum of the individual delta settings. If no delta setting is included, or all included delta settings are zero, the returned aggregate is based on the current obstype only.

boundary is an optional specifier that can force the starting time to a time boundary. If set to 'midnight', then the starting time will be at the previous midnight. If left out, then the start time will be the sum of the optional deltas.

obstype is an observation type, such as outTemp.

aggregation is an aggregation type. If you ask for $month.outTemp.avg you are asking for the average outside temperature for the month. Possible aggregation types are given in the reference Aggregation types.

unit_conversion is an optional unit conversion tag. If provided, the results will be converted into the specified units, otherwise the default units specified in the skin configuration file (in section [Units][[Groups]]) will be used. See the section Unit conversion options.

formatting is an optional formatting tag. If provided, it controls how the value will appear. See the section Formatting options.

There are several delta settings that can be used:

Delta Setting Example Meaning
time_delta=seconds $span(time_delta=1800).outTemp.avg The average temperature over the last immediate 30 minutes (1800 seconds).
hour_delta=hours $span(hour_delta=6).outTemp.avg The average temperature over the last immediate 6 hours.
day_delta=days $span(day_delta=1).rain.sum The total rainfall over the last immediate 24 hours.
week_delta=weeks $span(week_delta=2).barometer.max The maximum barometric pressure over the last immediate 2 weeks.

For example, the template expressions

The total rainfall over the last 30 hours is $span($hour_delta=30).rain.sum

and

The total rainfall over the last 30 hours is $span($hour_delta=6, $day_delta=1).rain.sum

would both result in

The total rainfall over the last 30 hours is 1.24 in

Tag $unit

The type, label, and string formats for all units are also available, allowing you to do highly customized labels:

Tag Results
$unit.unit_type.outTemp degree_C
$unit.label.outTemp °C
$unit.format.outTemp %.1f

For example, the tag

$day.outTemp.max.format(add_label=False)$unit.label.outTemp

would result in

21.2°C

(assuming metric values have been specified for group_temperature), essentially reproducing the results of the simpler tag $day.outTemp.max.

Tag $obs

The labels used for the various observation types are available using tag $obs. These are basically the values given in the skin dictionary, section [Labels][[Generic]].

Tag Results
$obs.label.outTemp Outside Temperature
$obs.label.UV UV Index

Iteration

It is possible to iterate over the following:

Tag suffix Results
.records Iterate over every record
.hours Iterate by hours
.days Iterate by days
.months Iterate by months
.years Iterate by years
.spans(interval=seconds) Iterate by custom length spans. The default interval is 10800 seconds (3 hours). The spans will align to local time boundaries.

The following template uses a Cheetah for loop to iterate over all months in a year, printing out each month's min and max temperature. The iteration loop is highlighted.

Min, max temperatures by month
#for $month in $year.months
$month.dateTime.format("%B"): Min, max temperatures: $month.outTemp.min $month.outTemp.max
#end for

The result is:

Min, max temperatures by month
January: Min, max temperatures: 30.1°F 51.5°F  
February: Min, max temperatures: 24.4°F 58.6°F  
March: Min, max temperatures: 27.3°F 64.1°F  
April: Min, max temperatures: 33.2°F 52.5°F  
May: Min, max temperatures: N/A N/A  
June: Min, max temperatures: N/A N/A  
July: Min, max temperatures: N/A N/A  
August: Min, max temperatures: N/A N/A  
September: Min, max temperatures: N/A N/A  
October: Min, max temperatures: N/A N/A  
November: Min, max temperatures: N/A N/A  
December: Min, max temperatures: N/A N/A

The following template again uses a Cheetah for loop, this time to iterate over 3-hour spans over the last 24 hours, displaying the averages in each span. The iteration loop is highlighted.

<p>3 hour averages over the last 24 hours</p>
<table>
  <tr>
    <td>Date/time</td><td>outTemp</td><td>outHumidity</td>
  </tr>
#for $time_band in $span($day_delta=1).spans(interval=10800)
  <tr>
    <td>$time_band.start.format("%d/%m %H:%M")</td>
    <td>$time_band.outTemp.avg</td>
    <td>$time_band.outHumidity.avg</td>
  </tr>
#end for
</table>

The result is:

3 hour averages over the last 24 hours

Date/time outTemp outHumidity
21/01 18:50 33.4°F 95%
21/01 21:50 32.8°F 96%
22/01 00:50 33.2°F 96%
22/01 03:50 33.2°F 96%
22/01 06:50 33.8°F 96%
22/01 09:50 36.8°F 95%
22/01 12:50 39.4°F 91%
22/01 15:50 35.4°F 93%

See the NOAA template files NOAA/NOAA-YYYY.txt.tmpl and NOAA/NOAA-YYYY-MM.txt.tmpl, both included in the Seasons skin, for other examples using iteration and explicit formatting.

Comprehensive example

This example is designed to put together a lot of the elements described above, including iteration, aggregation period starts and ends, formatting, and overriding units. Click here for the results.

<html>
  <head>
    <style>
      td { border: 1px solid #cccccc; padding: 5px; }
    </style>
  </head>

  <body>
    <table border=1 style="border-collapse:collapse;">
      <tr style="font-weight:bold">
        <td>Time interval</td>
        <td>Max temperature</td>
        <td>Time</td>
      </tr>
#for $hour in $day($days_ago=1).hours
      <tr>
        <td>$hour.start.format("%H:%M")-$hour.end.format("%H:%M")</td>
        <td>$hour.outTemp.max ($hour.outTemp.max.degree_C)</td>
        <td>$hour.outTemp.maxtime.format("%H:%M")</td>
      </tr>
#end for
      <caption>
        <p>
          Hourly max temperatures yesterday<br/>
          $day($days_ago=1).start.format("%d-%b-%Y")
        </p>
      </caption>
    </table>
  </body>
</html>

Support for series

Note

This is an experimental API that could change.

WeeWX V4.5 introduced some experimental tags for producing series of data, possibly aggregated. This can be useful for creating the JSON data needed for JavaScript plotting packages, such as HighCharts, Google Charts, or C3.js.

For example, suppose you need the maximum temperature for each day of the month. This tag

$month.outTemp.series(aggregate_type='max', aggregate_interval='1d', time_series='start').json

would produce the following:

[[1614585600, 58.2], [1614672000, 55.8], [1614758400, 59.6], [1614844800, 57.8], ... ]

This is a list of (time, temperature) for each day of the month, in JSON, easily consumed by many of these plotting packages.

Many other combinations are possible. See the Wiki article Tags for series.

Helper functions

WeeWX includes a number of helper functions that may be useful when writing templates.

$rnd(x, ndigits=None)

Round x to ndigits decimal digits. The argument x can be a float or a list of floats. Values of None are passed through.

$jsonize(seq)

Convert the iterable seq to a JSON string.

$to_int(x)

Convert x to an integer. The argument x can be of type float or str. Values of None are passed through.

$to_bool(x)

Convert x to a boolean. The argument x can be of type int, float, or str. If lowercase x is 'true', 'yes', or 'y' the function returns True. If it is 'false', 'no', or 'n' it returns False. Other string values raise a ValueError. In case of a numeric argument, 0 means False, all other values True.

$to_list(x)

Convert x to a list. If x is already a list, nothing changes. If it is a single value it is converted to a list with this value as the only list element. Values of None are passed through.

$getobs(plot_name)

For a given plot name, this function will return the set of all observation types used by the plot.

For example, consider a plot that is defined in [ImageGenerator] as

[[[daytempleaf]]]
  [[[[leafTemp1]]]]
  [[[[leafTemp2]]]]
  [[[[temperature]]]]
    data_type = outTemp

The tag $getobs('daytempleaf') would return the set {'leafTemp1', 'leafTemp2', 'outTemp'}.

General tags

There are some general tags that do not reflect observation data, but technical information about the template files. They are frequently useful in #if expressions to control how Cheetah processes the template.

$encoding

Character encoding, to which the file is converted after creation. Possible values are html_entities, strict_ascii, normalized_ascii, and utf-8.

$filename

Name of the file to be created including relative path. Can be used to set the canonical URL for search engines.

<link rel="canonical" href="$station.station_url/$filename" />

$lang

Language code set by the lang option for the report. For example, fr, or gr.

$month_name

For templates listed under SummaryByMonth, this will contain the localized month name (e.g., "Sep").

$page

The section name from skin.conf where the template is described.

$skin

The value of option skin in weewx.conf.

$SKIN_NAME

All skin included with WeeWX, version 4.6 or later, include the tag $SKIN_NAME. For example, for the Seasons skin, $SKIN_NAME would return Seasons.

$SKIN_VERSION

All skin included with WeeWX, version 4.6 or later, include the tag $SKIN_VERSION, which returns the WeeWX version number of when the skin was installed. Because skins are not touched during the upgrade process, this shows the origin of the skin.

$SummaryByDay

A list of year-month-day strings (e.g., ["2018-12-31", "2019-01-01"]) for which a summary-by-day has been generated. The [[SummaryByDay]] section must have been processed before this tag will be valid, otherwise it will be empty.

$SummaryByMonth

A list of year-month strings (e.g., ["2018-12", "2019-01"]) for which a summary-by-month has been generated. The [[SummaryByMonth]] section must have been processed before this tag will be valid, otherwise it will be empty.

$SummaryByYear

A list of year strings (e.g., ["2018", "2019"]) for which a summary-by-year has been generated. The [[SummaryByYear]] section must have been processed before this tag will be valid, otherwise it will be empty.

$year_name

For templates listed under SummaryByMonth or SummaryByYear, this will contain the year (e.g., "2018").

$gettext - Internationalization

Pages generated by WeeWX not only contain observation data, but also static text. The WeeWX tag $gettext provides internationalization support for these kinds of texts. It is structured very similarly to the GNU gettext facility, but its implementation is very different. To support internationalization of your template, do not use static text in your templates, but rather use $gettext. Here's how.

Suppose you write a skin called "YourSkin", and you want to include a headline labelled "Current Conditions" in English, "aktuelle Werte" in German, "Conditions actuelles" in French, etc. Then the template file could contain:

<h1>$gettext("Current Conditions")</h1>

The section of weewx.conf configuring your skin would look something like this:

[StdReport]
    [[YourSkinReport]]
        skin = YourSkin
        lang = fr

With lang = fr the report is in French. To get it in English, replace the language code fr by the code for English en. And to get it in German use de.

To make this all work, a language file has to be created for each supported language. The language files reside in the lang subdirectory of the skin directory that is defined by the skin option. The file name of the language file is the language code appended by .conf, for example en.conf, de.conf, or fr.conf.

The language file has the same layout as skin.conf, i.e. you can put language specific versions of the labels there. Additionally, a section [Texts] can be defined to hold the static texts used in the skin. For the example above the language files would contain the following:

en.conf

[Texts]
    "Current Conditions" = Current Conditions

de.conf

[Texts]
    "Current Conditions" = Aktuelle Werte

fr.conf

[Texts]
    "Current Conditions" = Conditions actuelles

While it is not technically necessary, we recommend using the whole English text for the key. This makes the template easier to read, and easier for the translator. In the absence of a translation, it will also be the default, so the skin will still be usable, even if a translation is not available.

See the subdirectory SKIN_ROOT/Seasons/lang for examples of language files.

$pgettext - Context sensitive lookups

A common problem is that the same string may have different translations, depending on its context. For example, in English, the word "Altitude" is used to mean both height above sea level, and the angle of a heavenly body from the horizon, but that's not necessarily true in other languages. For example, in Thai, "ระดับความสูง" is used to mean the former, "อัลติจูด" the latter. The function pgettext() (the "p" stands for particular) allows you to distinguish between the two. Its semantics are very similar to the GNU and Python versions of the function.

Here's an example:

<p>$pgettext("Geographical","Altitude"): $station.altitude</p>
<p>$pgettext("Astronomical","Altitude"): $almanac.moon.alt</p>

The [Texts] section of the language file should then contain a subsection for each context. For example, the Thai language file would include:

th.conf

[Texts]
    [[Geographical]]
        "Altitude" = "ระดับความสูง"    # As in height above sea level
    [[Astronomical]]
        "Altitude" = "อัลติจูด"         # As in angle above the horizon

Almanac

If module ephem has been installed, then WeeWX can generate extensive almanac information for the Sun, Moon, Venus, Mars, Jupiter, and other heavenly bodies, including their rise, transit and set times, as well as their azimuth and altitude. Other information is also available.

Here is an example template:

Current time is $current.dateTime
#if $almanac.hasExtras
    Sunrise, transit, sunset: $almanac.sun.rise $almanac.sun.transit $almanac.sun.set
    Moonrise, transit, moonset: $almanac.moon.rise $almanac.moon.transit $almanac.moon.set
    Mars rise, transit, set: $almanac.mars.rise $almanac.mars.transit $almanac.mars.set
    Azimuth, altitude of Mars: $almanac.mars.azimuth $almanac.mars.altitude
    Next new, full moon: $almanac.next_new_moon; $almanac.next_full_moon
    Next summer, winter solstice: $almanac.next_summer_solstice; $almanac.next_winter_solstice
#else
    Sunrise, sunset: $almanac.sunrise $almanac.sunset
#end if

If ephem is installed this would result in:

Current time is 03-Sep-2010 11:00 Sunrise, transit, sunset: 06:29 13:05 19:40 Moonrise, transit, moonset: 00:29 08:37 16:39 Mars rise, transit, set: 10:12 15:38 21:04 Azimuth, altitude of Mars: 111° 08° Next new, full moon: 08-Sep-2010 03:29; 23-Sep-2010 02:17 Next summer, winter solstice: 21-Jun-2011 10:16; 21-Dec-2010 15:38

Otherwise, a fallback of basic calculations is used, resulting in:

Current time is 29-Mar-2011 09:20
Sunrise, sunset: 06:51 19:30

As shown in the example, you can test whether this extended almanac information is available with the value $almanac.hasExtras.

The almanac information falls into three categories:

  • Calendar events
  • Heavenly bodies
  • Functions

We will cover each of these separately.

Calendar events

"Calendar events" do not require a heavenly body. They cover things such as the time of the next solstice or next first quarter moon, or the sidereal time. The syntax is:

$almanac.next_solstice

or

$almanac.next_first_quarter_moon

or

$almanac.sidereal_angle

Here is a table of the information that falls into this category:

Calendar events
previous_equinox next_equinox
previous_solstice next_solstice
previous_autumnal_equinox next_autumnal_equinox
previous_vernal_equinox next_vernal_equinox
previous_winter_solstice next_winter_solstice
previous_summer_solstice next_summer_solstice
previous_new_moon next_new_moon
previous_first_quarter_moon next_first_quarter_moon
previous_full_moon next_full_moon
previous_last_quarter_moon next_last_quarter_moon
sidereal_angle

Note

The tag $almanac.sidereal_angle returns a value in decimal degrees rather than a more customary value from 0 to 24 hours.

Heavenly bodies

The second category does require a heavenly body. This covers queries such as, "When does Jupiter rise?" or, "When does the sun transit?" Examples are

$almanac.jupiter.rise

or

$almanac.sun.transit

To accurately calculate these times, WeeWX automatically uses the present temperature and pressure to calculate refraction effects. However, you can override these values, which will be necessary if you wish to match the almanac times published by the Naval Observatory as explained in the PyEphem documentation. For example, to match the sunrise time as published by the Observatory, instead of

$almanac.sun.rise

use

$almanac(pressure=0, horizon=-34.0/60.0).sun.rise

By setting pressure to zero we are bypassing the refraction calculations and manually setting the horizon to be 34 arcminutes lower than the normal horizon. This is what the Navy uses.

If you wish to calculate the start of civil twilight, you can set the horizon to -6 degrees, and also tell WeeWX to use the center of the sun (instead of the upper limb, which it normally uses) to do the calcuation:

$almanac(pressure=0, horizon=-6).sun(use_center=1).rise

The general syntax is:

$almanac(almanac_time=time,            ## Unix epoch time
         lat=latitude, lon=longitude,  ## degrees
         altitude=altitude,            ## meters
         pressure=pressure,            ## mbars
         horizon=horizon,              ## degrees
         temperature=temperature_C     ## degrees C
       ).heavenly_body(use_center=[01]).attribute

As you can see, many other properties can be overridden besides pressure and the horizon angle.

PyEphem offers an extensive list of objects that can be used for the heavenly_body tag. All the planets and many stars are in the list.

The possible values for the attribute tag are listed in the following table, along with the corresponding name used in the PyEphem documentation.

Attributes that can be used with heavenly bodies
WeeWX name PyEphem name Meaning
azimuth az Azimuth
altitude alt Altitude
astro_ra a_ra Astrometric geocentric right ascension
astro_dec a_dec Astrometric geocentric declination
geo_ra g_ra Apparent geocentric right ascension
topo_ra ra Apparent topocentric right ascension
geo_dec g_dec Apparent geocentric declination
topo_dec dec Apparent topocentric declination
elongation elong Angle with sun
radius_size radius Size as an angle
hlongitude hlon Astrometric heliocentric longitude
hlatitude hlat Astrometric heliocentric latitude
sublatitude sublat Geocentric latitude
sublongitude sublon Geocentric longitude
next_rising next_rising Time body will rise next
next_setting next_setting Time body will set next
next_transit next_transit Time body will transit next
next_antitransit next_antitransit Time body will anti-transit next
previous_rising previous_rising Previous time the body rose
previous_setting previous_setting Previous time the body sat
previous_transit previous_transit Previous time the body transited
previous_antitransit previous_antitransit Previous time the body anti-transited
rise next_rising Time body will rise next
set next_setting Time body will set next
transit next_transit Time body will transit next
visible N/A How long body will be visible
visible_change N/A Change in visibility from previous day

Note

The tags topo_ra, astro__ra and geo_ra return values in decimal degrees rather than customary values from 0 to 24 hours.

Functions

There is actually one function in this category: separation. It returns the angular separation between two heavenly bodies. For example, to calculate the angular separation between Venus and Mars you would use:

<p>The separation between Venus and Mars is
      $almanac.separation(($almanac.venus.alt,$almanac.venus.az), ($almanac.mars.alt,$almanac.mars.az))</p>

This would result in:

The separation between Venus and Mars is 55:55:31.8

Adding new bodies

It is possible to extend the WeeWX almanac, adding new bodies that it was not previously aware of. For example, say we wanted to add 433 Eros, the first asteroid visited by a spacecraft. Here is the process:

  1. Put the following in the file user/extensions.py:

    import ephem
    eros = ephem.readdb("433 Eros,e,10.8276,304.3222,178.8165,1.457940,0.5598795,0.22258902,71.2803,09/04.0/2017,2000,H11.16,0.46")
    ephem.Eros = eros
    

    This does two things: it adds orbital information about 433 Eros to the internal PyEphem database, and it makes that data available under the name Eros (note the capital letter).

  2. You can then use 433 Eros like any other body in your templates. For example, to display when it will rise above the horizon:

    $almanac.eros.rise
    

Wind

Wind deserves a few comments because it is stored in the database in two different ways: as a set of scalars, and as a vector of speed and direction. Here are the four wind-related scalars stored in the main archive database:

Archive type Meaning Valid contexts
windSpeed The average wind speed seen during the archive period. $current, $latest, $hour, $day, $week, $month, $year, $rainyear
windDir If software record generation is used, this is the vector average over the archive period. If hardware record generation is used, the value is hardware dependent.
windGust The maximum (gust) wind speed seen during the archive period.
windGustDir The direction of the wind when the gust was observed.

Some wind aggregation types, notably vecdir and vecavg, require wind speed and direction. For these, WeeWX provides a composite observation type called wind. It is stored directly in the daily summaries, but synthesized for aggregations other than multiples of a day.

Daily summary type Meaning Valid contexts
wind A vector composite of the wind. $hour, $day, $week, $month, $year, $rainyear

Any of these can be used in your tags. Here are some examples:

Tag Meaning
$current.windSpeed The average wind speed over the most recent archive interval.
$current.windDir If software record generation is used, this is the vector average over the archive interval. If hardware record generation is used, the value is hardware dependent.
$current.windGust The maximum wind speed (gust) over the most recent archive interval.
$current.windGustDir The direction of the gust.
$day.windSpeed.avg
$day.wind.avg
The average wind speed since midnight. If the wind blows east at 5 m/s for 2 hours, then west at 5 m/s for 2 hours, the average wind speed is 5 m/s.
$day.wind.vecavg The vector average wind speed since midnight. If the wind blows east at 5 m/s for 2 hours, then west at 5 m/s for 2 hours, the vector average wind speed is zero.
$day.wind.vecdir The direction of the vector averaged wind speed. If the wind blows northwest at 5 m/s for two hours, then southwest at 5 m/s for two hours, the vector averaged direction is west.
$day.windGust.max
$day.wind.max
The maximum wind gust since midnight.
$day.wind.gustdir The direction of the maximum wind gust.
$day.windGust.maxtime
$day.wind.maxtime
The time of the maximum wind gust.
$day.windSpeed.max The max average wind speed. The wind is averaged over each of the archive intervals. Then the maximum of these values is taken. Note that this is not the same as the maximum wind gust.
$day.windDir.avg Not a very useful quantity. This is the strict, arithmetic average of all the compass wind directions. If the wind blows at 350° for two hours then at 10° for two hours, then the scalar average wind direction will be 180° — probably not what you expect, nor want.

Defining new tags

We have seen how you can change a template and make use of the various tags available such as $day.outTemp.max for the maximum outside temperature for the day. But, what if you want to introduce some new data for which no tag is available?

If you wish to introduce a static tag, that is, one that will not change with time (such as a Google Analytics tracker ID, or your name), then this is very easy: put it in section [Extras] in the skin configuration file. More information on how to do this can be found there.

But, what if you wish to introduce a more dynamic tag, one that requires some calculation, or perhaps uses the database? Simply putting it in the [Extras] section won't do, because then it cannot change.

The answer is to write a search list extension. Complete directioins on how to do this are in the document Writing search list extensions.