Customizing weewx v1.6

Table of Contents

  1. Introduction
  2. Opportunities for customizing reports
  3. Reference: The Standard skin configuration file
  4. Customizing the weewx service engine
  5. Appendix A: Types
  6. Appendix B: Units
  7. Appendix C: Statistical aggregations

1. Introduction

This document covers the customization of weewx. It assumes that you have read and are reasonably familiar with the Users Guide.

It starts with an overview of the architecture of weewx. If you are only interested in customizing the generated skin reports you can probably skip that section and just have a look at the options available for the Standard skin configuration file, described in the section The Standard skin configuration file. With this approach you can easily add new plot images, change the titles of images, change the units used in the reports, and so on.

However, if your goal is a specialized application such as adding alarms, RSS feeds, etc., then it would be worth your while to read about the internal architecture and how to customize it.

Overview of the weewx architecture

At a high-level, weewx consists of an engine class called StdEngine. It is responsible for loading any "services" that are to be run and arranging for them to be called when key events occur, such as the arrival of LOOP data. The default install of weewx includes the following services:

Service Function
weewx.wxengine.StdWunderground Starts thread to manage WU connection; adds new data to a Queue to be posted to the WU by the thread.
weewx.wxengine.StdCatchUp Any data found on the weather station memory but not yet in the archive, is retrieved and put in the archive.
weewx.wxengine.StdTimeSynch Arranges to have the clock on the station synchronized at regular intervals.
weewx.wxengine.StdPrint Prints out new LOOP and archive packets on the console.
weewx.wxengine.StdReportService Launches a new thread to do processing after a new archive record arrives. The thread loads zero or more reports and processes them in order. Reports do things such as generate HTML files, generate images, or FTP files to a web server. New reports can be added easily by the user.

It is easy to extend old services or to add new ones. The source distribution includes an example new service called "MyAlarm," which sends an email when an arbitrary expression evaluates True. It is also possible to extend the internal engine. These advanced topics are covered later in the section Customizing the weewx service engine.

The standard reporting service, StdReportService

For the moment, we focus on the last service, weewx.wxengine.StdReportService, the standard service for creating reports. This will be what most users want to customize even if it means just changing a few options.

Reports

The Standard Report Service runs zero or more Reports. Which ones are set in the weewx configuration file weewx.conf, in section [Reports].

The default distribution of weewx includes two reports:

Report Default functionality
StandardReport Generates day, week, month and year "to-date" summaries in HTML, as well as the plot images to go along with them. Also generates NOAA monthly and yearly summaries.
FTP Arranges to upload everything in the public_html subdirectory up to a remote webserver.

Note that the FTP "report" is kind of a funny report in that it doesn't actually generate anything. Instead, it uses the reporting service engine to arrange for things to be FTP'd to a remote server.

Skins

Each Report has a Skin associated with it. For most Reports, the relationship with the skin is an obvious one: it contains the templates, any auxiliary files such as background GIFs or CSS style sheets, and a skin configuration file, skin.conf. If you will, the skin controls the look and feel of the Report. Note that more than one Report can use the same skin. For example, you might want to run a report that uses US Customary units, then run another report against the same skin, but using metric units and put the results in a different place. All this is possible by either overriding configuration options in the weewx configuration file weewx.conf or the skin configuration file skin.conf.

Like all reports, the FTP "Report" also uses a skin, and includes a skin configuration file, although it is quite minimal.

Skins live in their own subdirectory located in $HTML_ROOT/skins.

Generators

To create their output, skins rely on one or more Generators, code that actually create useful things such as HTML files or plot images. They can also copy files around or FTP them to remote locations. The default install of weewx includes the following generators:

Generator Function
weewx.filegenerator.FileGenerator Generates HTML files from templates.
weewx.imagegenerator.ImageGenerator Generates image plots
weewx.reportengine.FtpGenerator FTP uploads data
weewx.reportengine.CopyGenerator Copies files locally.

Note that the two generators FtpGenerator and CopyGenerator don't actually generate anything to do with the presentation layer. Instead, they just move files around.

Which generators are to be run for a given skin is specified in the skin's configuration file skin.conf.

2. Opportunities for customizing reports

This section discusses the two general strategies for customizing reports: by changing options in one or more configuration file, or by changing the template files. The former is generally easier, but occasionally the latter is necessary.

By changing options

Changing an option means going into either weewx.conf or the skin.conf that comes with the standard distribution and changing a value.

By changing options in skin.conf

With this approach, the user edits the skin configuration file for the standard skin that comes with weewx, located in $WEEWX_ROOT/skins/Standard/skin.conf, using a text editor. For example, suppose you wish to use metric units in the presentation layer. Then, you would edit section [Units][[Groups]] to read:

[Units]
  [[Groups]]
    group_altitude    = meter
    group_direction   = degree_compass
    group_moisture    = centibar
    group_percent     = percent
    group_pressure    = mbar
    group_radiation   = watt_per_meter_squared
    group_rain        = mm
    group_rainrate    = mm_per_hour
    group_speed       = meter_per_second
    group_speed2      = meter_per_second2
    group_temperature = degree_C
    group_volt        = volt

Details of the various unit options are given in Appendix B: Units.

Other options are available, such as changing the label for various observation types. Suppose your weather instrument console is actually located in a barn, not indoors, and you want its plot to be labeled "Barn Temperature," rather than the default "Inside Temperature." This can be done by changing the "inTemp" option located in section [Labels][[Generic]] from

[Units]
    [[Generic]]
      inTemp  = Inside Temperature
      outTemp = Outside Temperature
      ...

so that it reads

[Units]
    [[Generic]]
      inTemp  = Barn Temperature
      outTemp = Outside Temperature
      ...

By overriding options

This approach is very similar, except that instead of changing the skin configuration, you override its options by editing the main configuration file, weewx.conf. The advantage of this approach is that you can use the same skin to produce several different output, each with separate options.

Revisiting our example, suppose you want two reports, one in US Customary, the other in Metric. The former will go in the directory public_html, the latter in a subdirectory, public_html/metric. If you just simply modify skin.conf, you can get one, but not both at the same time. Alternatively, you could create a whole new skin by copying all the files to a new skin subdirectory then editing the new skin.conf. The trouble with this approach is that you would then have two skins you would have to maintain. If you change something, you have to remember to change it in both places.

But, there's a better approach: reuse the same skin, but overriding some options. Here's what your [Report] section in weewx.conf would look like:

[Reports]
#
# This section specifies what reports, using which skins, are to be generated.
#
 
# Where the skins reside, relative to WEEWX_ROOT:
SKIN_ROOT = skins
# Where the generated reports should go, relative to WEEWX_ROOT:
HTML_ROOT = public_html 
 
  # This report will use US Customary Units
  [[USReport]]
    # It's based on the Standard skin
    skin = Standard
 
  # This report will use metric units: 
  [[MetricReport]]
    # It's also based on the Standard skin:
    skin = Standard 
    # However, override where the results will go and put them in a subdirectory:
    HTML_ROOT = public_html/metric
    
      # And override the options that were not in metric units
      [[[Units]]]
        [[[[Groups]]]]
          group_altitude    = meter
          group_pressure    = mbar
          group_rain        = mm
          group_rainrate    = mm_per_hour
          group_speed       = meter_per_second
          group_speed2      = meter_per_second2
          group_temperature = degree_C
 
    [[FTP]]
      ...
      ... (as before) 

We have done two things different from the stock reports. First (1), we've renamed the first report from StandardReport to USReport for clarity; and (2) we've introduced a new report MetricReport, just like the first, except it puts its results in a different spot and uses different units. Both use the same skin, the Standard skin.

By customizing templates

If you cannot achieve the results you need by changing a configuration option, you may have to modify the templates that come with weewx, or write your own.

Template generation is done using the Cheetah templating engine. This is a very powerful engine, which essentially lets 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.

Generally, any value substituted by the engine is specified by using a 'dot' code. For example:

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

would code the max outside temperature for the month, the time it occurred, and the current outside temperature, respectively. So, an HTML file that looks like

<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 [Labels][[Time]].

The "dot" code has up to three parts.

  1. The first part is the time period, and can be one of current, day, week, month, year, or rainyear.
  2. The second part is the "statistical type". This is something like 'outTemp', 'rain', 'wind', etc. The statistical types are generally those that appear in the SQL databases with three exceptions. First, type 'wind' is a special hybrid type and does not appear in the SQL database. It brings together the several different SQL types 'windSpeed', 'windDir', windGust', and 'windGustDir' under one roof (all are still available, should you wish to use them for a specialized application). Exceptions number two and three are 'heatdeg' and 'cooldeg', heating and cooling degree-days, respectively, which are synthesized from average outside temperature and do not appear directly in the database. See Appendix A: Types for a table of statistical types.
  3. The last position is the aggregation type, available for any time period except for 'current'. The table Appendix C: Statistical aggregations shows what aggregation types are available for which types.

In addition, if the first part of the dot code represents a time period longer than a day (e.g., a week, month, year, or rainyear), then it can be iterated over. This example uses a Cheetah 'for' loop to iterate over all months in a year, printing out each month's min and max temperature.

<html>
  <head>
    <title>Year stats by month</title>
  </head>
  <body>
  <p>Min, max temperatures by month:</p>
  # for $month in $year.months
    $month.outTemp.min $month.outTemp.max
  # end for 
  </body>
</html>

See the NOAA template files NOAA/NOAA-YYYY.txt.tmpl and NOAA/NOAA-YYYY-MM.txt.tmpl for examples using iteration, as well as explicit formatting.

By writing a custom generator

To do more sophisticated customization it may be necessary to extend an existing generator, or write your own.

Extending an existing generator

In the section on Customizing templates, 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: simply 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? Simply putting it in the [Extras] section won't do, because then it cannot change.

The answer is to extend the default file generator weewx.filegenerator.FileGenerator by subclassing, then override the function that returns the search list. The search list is a list of dictionaries that the template engine searches through, trying all keys in each dictionary, looking for a match for your tag. For example, for the "ToDate" generator, you would override function getToDateSearchList(), and add a small dictionary with your tag as the key to the list returned by the superclass.

Let's look at an example. The stock weewx reports offers statistical summaries by day, week, month, and year. Suppose we would like to add one more: all-time statistics. This would allow us to display statistics such as the all-time high or low temperature seen at your station.

This example is included in the distribution as examples/mygenerator.py:

from weewx.filegenerator import FileGenerator
from weewx.stats import TimeSpanStats
from weeutil.weeutil import TimeSpan

class MyFileGenerator(FileGenerator):                    # 1

    def getToDateSearchList(self, currentRec, stop_ts):  # 2

        # Get a TimeSpan object that represents all time up to the stop time:
        all_time = TimeSpan(self.start_ts, stop_ts)      # 3

        # Get a TimeSpanStats object :
        all_stats = TimeSpanStats(self.statsdb,
                                  all_time,
                                  self.unitTypeDict)     # 4

        # Get the superclass's search list: 
        search_list = FileGenerator.getToDateSearchList(self, currentRec, stop_ts) #5

        # Now tack on my addition as a small dictionary with key 'alltime':
        search_list += [ {'alltime' : all_stats} ]       # 6

        return search_list

Going through the example, line by line:

  1. Subclass from class FileGenerator. The new class will be caled MyFileGenerator
  2. Override member function getToDateSearchList(). The parameters are self (Python's way of indicating the instance we are working with), currentRec (a dictionary with the current conditions), and stop_ts (the ending time for the "to date" summary, in Unix epoch time).
  3. Attribute self.start_ts is available as the earliest time seen in the main archive database. The class TimeSpan is a utility class that represents an interval of time. Here, we are creating an instance of TimeSpan that represents all time preceeding stop_ts.
  4. Class TimeSpanStats represents a statistical calculation that can be done against a database. It takes 3 parameters. The first, self.statsdb is the statistical database the calculation is to be run against; the second is the timespan over which the calculation is to be done; and the third is the units the results are to be returned in.
  5. Get the search list from the superclass.
  6. Tack on our addition and return the results. The search list will now consist of a list of dictionaries, including a small one we added on the end that has a single key, 'alltime', with value an instance of TimeSpanStats.

With this approach, you can now include "all time" statistics in your HTML templates:

...
...
<table>
  <tr>
    <td>Maximum temperature to date: </td>
    <td>$alltime.outTemp.max</td>
  </tr>
  <tr>
    <td>Minimum temperature to date: </td>
    <td>$alltime.outTemp.min
  </tr>
  ... (more table entries)
 

One additonal step is required: to tell the report service to run your generator instead of the default generator. Modify option generator_list in the skin configuration file skin.conf to read:

generator_list = examples.mygenerator.MyFileGenerator, weewx.imagegenerator.ImageGenerator, weewx.reportengine.CopyGenerator

NB: If you create a custom generator some place other than where weewxd.py resides, you may have to specify its location in the environment variable PYTHON_PATH:

export PYTHON_PATH=/home/me/secret_location

3. Reference: The Standard skin configuration file

This section is a reference to the options appearing in the Standard skin configuration file, found in $WEEWX_ROOT/skins/Standard/skin.conf.

It is worth noting that, like the main configuration file weewx.conf, U, UTF-8 is used throughout. Also, like the weewx.conf, the most important options are up near the top of the file.  The truly important ones, the ones you are likely to have to customize for your station, are shown in bold face and in blue.

General

generator_list

This option controls which generators get run for this skin. It is a comma separated list. The generators will be run in this order.

[Extras]

This section is available to you to add any static tags that you might want to be available in the templates. As an example, the stock skin.conf file includes option radar_url, which is available as tag $Extras.radar_url. If you take a look at the template index.html.tmpl you will see an example of testing for this tag (search the file for the string 'radar_url' to find it).

radar_url

If set, the NOAA radar image will be displayed. If commented out, no image will be displayed.

Extending [Extras]

Other tags can be added in a similar manner, including subsections. For example, say you have added a video camera and you would like to add a still image with a hyperlink to a page with the video. You want all of these options to be neatly contained in a subsection.

[Extras]
  [[video]]
    still = video_capture.jpg
    hyperlink = http://www.eatatjoes.com/video.html

Then in your template you could refer to these as:

<a href="$Extras.video.hyperlink">
  <img src="$Extras.video.still" alt="Video capture"/>
</a>

[Units]

This section deals with Units and their formatting.

[[Groups]]

This subsection lists all the Unit Groups and specifies which unit system is to be used for each one of them.

As there are many different observational measurement types (such as 'outTemp', 'barometer', etc.) used in weewx (more than 50 at last count), it would be tedious, not to say possibly inconsistent, to specify a different measurement system for each one of them. At the other extreme, requiring all of them to be "U.S. Customary" or "Metric" seems overly restrictive. Weewx has taken a middle route and divided all the different observation types into 12 different "unit groups." A unit group is something like "group_temperature." It represents the measurement system to be used by all observation types that are measured in temperature, such as inside temperature (type 'inTemp'), outside temperature ('outTemp'), dewpoint ('dewpoint'), wind chill ('windchill'), and so on. If you decide that you want unit group group_temperature to be measured in "degree_C" then you are saying all members of its group will be reported in degrees Celsius.

Note that the unit system is always specified in the singular. That is, specify "degree_C" or "foot", not "degrees_C" or "feet". See the Appendix Units for more information, including a concise summary of the groups, their members, and which options can be used for each group.

group_altitude

Which measurement unit to be used for altitude. Possible options are 'foot' or 'meter'.

group_direction

Which measurement unit to be used for direction. The only option is "degree_compass".

group_moisture

The measurement unit to be used for soil moisture. The only option is "centibar."

group_percent

The measurement unit to be used for percentages. The only option is "percent".

group_pressure

The measurement unit to be used for pressure. Possible options are one of "inHg" (inches of mercury), "mbar", or "hPa."

group_radiation

The measurement unit to be used for radiation. The only option is "watt_per_meter_squared."

group_rain

The measurement unit to be used for precipitation. Options are "inch", "cm," or "mm."

group_rainrate

The measurement unit to be used for rate of precipitation. Possible options are one of "inch_per_hour", "cm_per_hour", or "mm_per_hour".

group_speed

The measurement unit to be used for wind speeds. Possible options are one of "mile_per_hour", "km_per_hour", "knot", or "meter_per_second."

group_speed2

This group is similar to group_speed, but is used for calculated wind speeds which typically have a slightly higher resolution. Possible options are one "mile_per_hour2", "km_per_hour2", "knot2", or "meter_per_second2".

group_temperature

The measurement unit to be used for temperatures. Options are "degree_F" or "degree_C."

group_volt

The measurement unit to be used for voltages. The only option is "volt."

[[StringFormats]]

This sub-section is used to specify what string format is to be used for each unit when a quantity needs to be converted to a string. Typically, this happens with y-axis labeling on plots and for statistics in HTML file generation. For example, the options

degree_C = %.1f
inch     = %.2f

would specify that the given string formats are to be used when formatting any temperature measured in degrees Celsius or any precipitation amount measured in inches, respectively. The formatting codes are those used by Python, a, and are very similar to C's sprintf() codes.

[[Labels]]

This subsection specifies what label is to be used for each measurement unit type. For example, the options

degree_F = °F
inch     = ' in'

would cause all temperatures to have unit labels '°F' and all precipitation to have labels  ' in'. If any special symbols are to be used (such as the degree sign above) they should be encoded in UTF-8. This is generally what most text editors use if you cut-and-paste from a character map. Labels used in plot images will be converted to Latin-1 first (this is all the Python Imaging Library can handle).

[Labels]

This section sets the various labels to use.

hemispheres

Comma separated list for the labels to be used for the four hemispheres. The default is "N, S, E, W".

[[Generic]]

This sub-sections specifies default labels to be used for each SQL type. For example, options

inTemp = Temperature inside the house
outTemp = Outside Temperature

would cause the given labels to be used for plots involving SQL types inTemp and outTemp.

[[Time]]

This sub-section is used for time labels. It uses strftime() formats. For example

week  = %H:%M on %A
month = %d-%b-%Y %H:%M

would specify that week data should use a format such as "15:20 on Sunday", while month data should look like "06-Oct-2009 15:20"

[Almanac]

This section controls what text to use for the almanac. It consists of only one entry

moon_phases

This option is a comma separated list of labels to be used for the eight phases of the moon. Default is "New, Waxing crescent, First quarter, Waxing gibbous, Full, Waning gibbous, Last quarter, Waning crescent".

[FileGenerator]

This section is used by generator weewx.reportengine.FileGenerator and controls text generation from templates, specifically which files are to be produced from which template.

Overview of file generation

Customization of file generation consists of playing with the various options offered below and, failing that, modifying the template files that come with the distribution.

Each template file is named something like D/F.E.tmpl, where D is the subdirectory the template sits in and will also be the subdirectory 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 skin that comes with the standard distribution of weewx contains three different kinds of generated output:

  1. Summary by month. In addition to the naming rules above, if the template file has the letters YYYY and MM in its name, these will be substituted for the year and month, respectively. So, a template with the name summary-YYYY-MM.html.tmpl would have name summary-2010-03.html for the month of March, 2010. The default distribution has been set up to produce NOAA monthly summaries, one for each month, as a simple text file (no HTML).
  2. Summary by year.  In addition to the naming rules above, if the template file has the letters YYYY in its name, the year will be substituted. The default distribution has been set up to produce NOAA yearly summaries, one for each year, as a simple text file (no HTML).
  3. Summary "To Date". The default distribution has been set up to produce reports for the day, week, month, and year-to-date observations. These four files are all HTML files. The first, the daily summary (output file index.html), includes a drop-down list that allows the NOAA month and yearly summaries to be displayed.

General

encoding

This option controls which encoding is to be used for the generated output. There are 3 possible choices:

Encoding ComComments
html_entities Non 7-bit characters will be represented as HTML entities (e.g. the degree sign will be represented as &#176;)
utf8 Non 7-bit characters will be represented in UTF-8.
strict_ascii Non 7-bit characters will be ignoredd>

By default, the encoding html_entities is used for HTML files, strict_ascii for the NOAA template files.

[[SummaryByMonth]]

This section controls which summaries-by-month are generated. For each such summary, it should have its own sub-subsection, with option template listing the template to be used. The default configuration generates NOAA-by-month summaries and is summarized below as an example. Additional "by month" summaries can be added easily by following the same pattern.

[[[NOAA_month]]]

encoding

Set to strict_ascii for the NOAA monthly summary.

template

This option is set to the source template for the NOAA monthly summary, NOAA/NOAA-YYYY-MM.txt.tmpl.

[[SummaryByYear]]

This section controls which summaries-by-year are generated. For each such summary, it should have its own sub-subsection, with option template listing the template to be used. The default configuration generates NOAA-by-year summaries and is summarized below as an example. Additional "by year" summaries can be added easily by following the pattern.

[[[NOAA_year]]]

encoding

Set to strict_ascii for the NOAA year summary.

template

This option is set to the source template for the NOAA yearl summary, NOAA/NOAA-YYYY.txt.tmpl.

[[ToDate]]

This section controls which observations-to-date are generated. The default configuration generates four files: one for day, week, month, and year. Although the sub-subsections below have names such as 'week' or 'month', this is not used in their generation.  Output is set by the template content, not the name of the sub-subsection — the names below could as easily have been'Fred', 'Mary', 'Peter', and 'George' and had the same output.

Additional observations-to-date pages can be created easily by adding a new sub-subsection and giving it a unique name ("Jill"?), then giving the path to its template as option template.

[[[day]]]

template

Set to index.html.tmpl, which contains the template for the day summary.

[[[week]]]

template

Set to week.html.tmpl, which contains the template for the week summary.

[[[month]]]

template

Set to month.html.tmpl, which contains the template for the month summary.

[[[year]]]

template

Set to year.html.tmpl, which contains the template for the year summary.

[CopyGenerator]

This section is used by generator weewx.reportengine.CopyGenerator and controls which files are to be copied over from the skin subdirectory to the destination directory. Think of it as "file generation," except that rather than going through the template engine, the files are simply copied over.

copy_once

This option controls which files get copied over on the first invocation of the report engine service. Typically, this is things such as style sheets or background GIFs. Wildcards can be used.

copy_always

This is a list of files that should be copied on every invocation. Wildcards can be used.

[ImageGenerator]

This section is used by generator weewx.reportengine.ImageGenerator and controls which images (plots) get generated and with which options. While complicated, it is extremely flexible and powerful.

Time periods

The section consists of one or more sub-sections, one for each time period (day, week, month, and year). These sub-sections define the nature of aggregation and plot types for the time period. For example, here's a typical set of options for sub-section [[month_images]], controlling how images that cover a month period are generated:

[[month_images]]
  x_label_format = %d
  bottom_label_format = %m/%d/%y %H:%M
  time_length = 2592000 # == 30 days
  aggregate_type = avg
  aggregate_interval = 10800 # == 3 hours

The option x_label_format gives a strftime() type format for the x-axis. In this example, it will only show days (format option "%d"). The bottom_label_format is the format used to time stamp the image at the bottom. In this example, it will show the time as 10/25/09 15:35. A plot will cover a nominal 30 days, and all items included in it will use an aggregate type of averaging over 3 hours.

Image files

Within each sub-section is another nesting, one for each image to be generated. The title of each sub-sub-section is the filename to be used for the image. Finally, at one additional nesting level (!) are the logical names of all the line types to be drawn in the image.  Values specified in the level above can be overridden. For example, here's a typical set of options for sub-sub-section [[[[[[[monthrain]]]: /p> class="tty">[[[[[[monthrain]]]

  plot_type = bar
  yscale = None, None, 0.02
  [[[[rain]]]]
    aggregate_type = sum
    aggregate_interval = 86400
    label = Rain (daily avg)

This will generate an image file with name monthrain.png. It will be a bar plot. Option yscale controls the y-axis scaling — if left out, the scale will automatically be chosen. However, in this example we are choosing to exercise some degree of control by specifying values explicitly. It is a 3-way tuple (ylow, yhigh, min_interval), where ymin and ymax are the minimum and maximum y-axis values, respectively, and min_interval is the minimum tick interval. If set to 'None', the corresponding value will be automatically chosen. So, in this example, we are letting weewx pick sensible y minimum and maximum values, but we are requiring that the tick increment (min_interval) be at least 0.02.

Continuing on with the example above, there will be only one plot "line" (it will actually be a series of bars) and it will have logical name "rain". Because we haven't said otherwise, the SQL data type to be used for this line will be the same as its logical name, that is, rain, b, but this can be overridden (see below). The aggregation type will be summing (overriding the averaging specified in sub-section [[month_images]]), so you get the total rain over the aggregate period (rather than the average) over an aggregation interval of 86,400 seconds (one day). The plot line will be titled with the indicated label ('Rain (daily avg)')

Including more than one SQL type in a plot

More than one SQL type can be included in a plot. For example, here's how to generate a plot with the week's outside temperature as well as dewpoint:

[[[monthtempdew]]]

  [[[[outTemp]]]]

  [[[[dewpoint]]]]

This would create an image in file monthtempdew.png that includes a line plot of both outside temperature and dewpoint.

Including the same SQL type more than once in a plot

Another example. Say you want a plot of the day's temperature, overlaid with hourly averages. Here, you are using the same data type ('outTemp') for both plot lines, the first with averages, the second without. If you do the obvious it won't work:

## WRONG ##
[[[[[[daytemp_with_avg]]]
  [[[[outTemp]]]]
    aggregate_type = avg
    aggregate_interval = 3600
  [[[[outTemp]]]]  # OOPS! The same section name appears more than once!

The option parser does not allow the same section name ('outTemp' in this case) to appear more than once at a given level in the configuration file, so an error will be declared (technical reason: formally, the sections are an unordered dictionary). If you wish for the same SQL type to appear more than once in a plot then there is a trick you must know: use option data_type. This will override the default action that the logical line name is used for the SQL type. So, our example would look like this:

[[[daytemp_with_avg]]]
    [[[[a_logical_name]]]]
      data_type = outTemp
      aggregate_type = avg
      aggregate_interval = 3600
      label = Avg. Temp. 
    [[[[outTemp]]]]

Here, the first logical line has been given the name "a_logical_name" to distinguish it from the second line "outTemp". We have specified that the first line will use data type outTemp and that it will use averaging over a one hour period. The second also uses outTemp, but will not use averaging.

The result is a nice plot of the day's temperature, overlaid with a 3-hour smoothed average:

Daytime temperature with running average

Progressive vector plots

Weewx can produce progressive vector plots as well as the more conventional x-y plots. To produce these, use plot type 'vector'. You need a vector type to produce this kind of plot. There are two: 'windvec', and 'windgustvec'. While they don't actually appear in the SQL database, weewx understands that they represent special vector-types. The first, 'windvec', represents the average wind in an archive period, the second, 'windgustvec' the max wind in an archive period. Here's how to produce a progressive vector for one week that shows the hourly biggest wind gusts, along with hourly averages:

[[[weekgustoverlay]]]
   aggregate_interval = 3600
   [[[[windvec]]]]
     label = Hourly Wind 
     plot_type = vector
     aggregate_type = avg
   [[[[windgustvec]]]]
    label = Gust Wind 
     plot_type = vector
     aggregate_type = max

This will produce an image file with name weekgustoverlay.png. It will consist of two progressive vector plots, both using hourly aggregation (3,600 seconds). For the first set of vectors, the hourly average will be used. In the second, the max of the gusts will be used:

hourly average wind vector overlaid with gust vectors

By default, the sticks in the progressive wind plots point towards the wind source. That is, the stick for a wind from the west will point left. If you have a chronic wind direction (as I do), you may want to rotate the default direction so that all the vectors don't line up over the x-axis, overlaying each other. Do this by using option vector_rotate. For example, with my chronic westerlies, I set vector_rotate to 90.0 for the plot above, so winds out of the west point straight up.

If you use this kind of plot (the out-of-the-box version of weewx includes daily, weekly, monthly, and yearly progressive wind plots), a small compass rose will be put in the lower-left corner of the image to show the orientation of North.

Overriding values

Remember that values at any level can override values specified at a higher level. For example, say you want to generate the standard plots, but for a few key observation types such as barometer, you want to also generate some oversized plots to give you extra detail, perhaps for an HTML popup. The standard weewx.conf file specifies plot size of 300x180 pixels, which will be used for all plots unless overridden:

[Images]
  ...
  image_width=300
  image_height = 180

The standard plot of barometric pressure will appear in daybarometer.png:

    [[[daybarometer]]]

      [[[[barometer]]]]

We now add our special plot of barometric pressure, but specify a larger image size. This image will be put in file an class="code" daybarometer_big.png.

     [[[daybarometer_big]]]

      image_width  = 600

      image_height = 360

      [[[[barometer]]]]

4. Customizing the weewx service engine

This is an advance topic intended for those who wish to try their hand at extending the internal engine in weewx. You should have a passing familiarity with Python or, at least, be willing to learn it.

At a high level, weewx consists of an engine that is responsible for managing a set of services. A service consists of a Python class with a set of member functions. The engine arranges to have appropriate member functions called when specific events happen. For example, when a new LOOP packet arrives, member function processLoopPacket() of all services is called.

To customize, you can

This section describes how to do all three.

The default install of weewx includes the following services:

Service Function
weewx.wxengine.StdWunderground Starts thread to manage WU connection; adds new data to a Queue to be posted to the WU by the thread.
weewx.wxengine.StdCatchUp Any data found on the weather station memory but not yet in the archive, is retrieved and put in the archive.
weewx.wxengine.StdTimeSynch Arranges to have the clock on the station synchronized at regular intervals.
weewx.wxengine.StdPrint Prints out new LOOP and archive packets on the console.
weewx.wxengine.StdReportService Launches a new thread to do processing after a new archive record arrives. The thread loads zero or more reports and processes them in order. Reports do things such as generate HTML files, generate images, or FTP files to a web server. New reports can be added easily by the user.

Customizing a Service

The service weewx.wxengine.StdPrint prints out new LOOP and archive packets to the console when they arrive. By default, it prints out time, barometer, outside temperature, wind speed, and wind direction. Suppose you don't like this, and want to print out humidity as well when a new LOOP packet arrives, but leave the printing of archive packets alone. This could be done by subclassing the default print service StdPrint and overriding member function processLoopPacket().

In file myprint.py:

from weewx.wxengine import StdPrint
from weeutil.weeutil import timestamp_to_string

class MyPrint(StdPrint):

    # Override the default processLoopPacket:
    def processLoopPacket(self, physicalPacket):
        print "LOOP: ", timestamp_to_string(physicalPacket['dateTime']),\
            physicalPacket['barometer'],\
            physicalPacket['outTemp'],\
            physicalPacket['outHumidity'],\
            physicalPacket['windSpeed'],\
            physicalPacket['windDir']

You then need to specify that your print service class should be loaded instead of the default StdPrint service. This is done by substituting your service name for the standard print service name in the option service_list, located in [Engines][[WxEngine]]:

[Engines]
    [[WxEngine]]
        service_list = weewx.wxengine.StdWunderground, weewx.wxengine.StdCatchUp, weewx.wxengine.StdTimeSynch, myprint.MyPrint, weewx.wxengine.StdReportService

(Note that this list must be all on one line. The parser ConfigObj does not allow options to be continued on to following lines.)

Adding a Service

Suppose there is no service that can be easily customized for your needs. In this case, a new one can easily be created by subclassing off the abstract base class StdService, and then adding the functionality you need. Here's an example that implements an alarm that sends off an email when an arbitrary expression evaluates True. This example is included in the standard distribution in subdirectory "examples".

File examples/alarm.py:

import time
import smtplib
from email.mime.text import MIMEText
import threading
import syslog

from weewx.wxengine import StdService
from weeutil.weeutil import timestamp_to_string

# Inherit from the base class StdService:
class MyAlarm(StdService):
    """Custom service that sounds an alarm if an arbitrary expression evaluates true"""
    
    def __init__(self, engine):
        # Pass the initialization information on to my superclass:
        StdService.__init__(self, engine)
        
        # This will hold the time when the last alarm message went out:
        self.last_msg_ts = None
        self.expression  = None
        
    def setup(self):
        
        try:
            # Dig the needed options out of the configuration dictionary.
            # If a critical option is missing, an exception will be thrown and
            # the alarm will not be set.
            self.expression    = self.engine.config_dict['Alarm']['expression']
            self.time_wait     = int(self.engine.config_dict['Alarm'].get('time_wait', 3600))
            self.smtp_host     = self.engine.config_dict['Alarm']['smtp_host']
            self.smtp_user     = self.engine.config_dict['Alarm'].get('smtp_user')
            self.smtp_password = self.engine.config_dict['Alarm'].get('smtp_password')
            self.TO            = self.engine.config_dict['Alarm']['mailto']
            syslog.syslog(syslog.LOG_INFO, "alarm: Alarm set for expression: \"%s\"" % self.expression)
        except:
            self.expression = None
            self.time_wait  = None

    def postArchiveData(self, rec):
        # Let the super class see the record first:
        StdService.postArchiveData(self, rec)

        # See if the alarm has been set:
        if self.expression:
            # To avoid a flood of nearly identical emails, this will do
            # the check only if we have never sent an email, or if we haven't
            # sent one in the last self.time_wait seconds:
            if not self.last_msg_ts or abs(time.time() - self.last_msg_ts) >= self.time_wait :
                
                # Evaluate the expression in the context of 'rec'.
                # Sound the alarm if it evaluates true:
                if eval(self.expression, None, rec):            # NOTE 1
                    # Sound the alarm!
                    # Launch in a separate thread so it doesn't block the main LOOP thread:
                    t  = threading.Thread(target = MyAlarm.soundTheAlarm, args=(self, rec))
                    t.start()
                    # Record when the message went out:
                    self.last_msg_ts = time.time()

    def soundTheAlarm(self, rec):
        """This function is called when the given expression evaluates True."""
        
        # Get the time and convert to a string:
        t_str = timestamp_to_string(rec['dateTime'])
        # Form the message text:
        msg_text = "Alarm expression \"%s\" evaluated True at %s\nRecord:\n%s" % (self.expression, t_str, str(rec))
        # Convert to MIME:
        msg = MIMEText(msg_text)
        
        # Fill in MIME headers:
        msg['Subject'] = "Alarm message from weewx"
        msg['From']    = "weewx"
        msg['To']      = self.TO
        
        # Create an instance of class SMTP for the given SMTP host:
        s = smtplib.SMTP(self.smtp_host)
        try:
            # Some servers (eg, gmail) require encrypted transport.
            # Be prepared to catch an exception if the server
            # doesn't support it.
            s.ehlo()
            s.starttls()
            s.ehlo()
        except smtplib.SMTPException:
            pass
        # If a username has been given, assume that login is required for this host:
        if self.smtp_user:
            s.login(self.smtp_user, self.smtp_password)
        # Send the email:
        s.sendmail(msg['From'], [self.TO],  msg.as_string())
        # Log out of the server:
        s.quit()
        # Log it in the system log:
        syslog.syslog(syslog.LOG_INFO, "alarm: Alarm sounded for expression: \"%s\"" % self.expression)
        syslog.syslog(syslog.LOG_INFO, "       *** email sent to: %s" % self.TO) 

This service expects all the information it needs to be in the configuration file weewx.conf in a new section called [Alarm]. So, add the following lines to your configuration file:

[Alarm]
    expression = "outTemp < 40.0"
    time_wait = 3600
    smtp_host = smtp.mymailserver.com
    smtp_user = myusername
    smtp_password = mypassword
    mailto = auser@adomain.com

These options specify that the alarm is to be sounded when "outTemp < 40.0" evaluates True, that is when the outside temperature is below 40.0 degrees. Any valid Python expression can be used, although the only variables available are those in the current archive record. (The place in the code where the expression is evaluated is marked with "NOTE 1".)

Another example expression could be:

    expression = "outTemp < 32.0 and windSpeed > 10.0"

In this case, the alarm is sounded if the outside temperature drops below freezing and the wind speed is greater than 10.0.

Option time_wait is used to avoid a flood of nearly identical emails. The new service will wait this long before sending another email out.

Email will be sent through the SMTP host specified by option smtp_host. The recipient is specified in option mailto.

Many SMTP hosts require user login. If this is the case, the user and password are specified with options smtp_user and smtp_password, respectively.

To make this all work, you must tell the engine to load this new service. This is done by adding your service name to the list service_list, located in [Engines][[WxEngine]]:

[Engines]
    [[WxEngine]]
        service_list = weewx.wxengine.StdWunderground, weewx.wxengine.StdCatchUp, weewx.wxengine.StdTimeSynch, weewx.wxengine.StdPrint, weewx.wxengine.StdReportService, examples.alarm.MyAlarm

(Again, the list must be all on one line.)

In addition to the example above, the distribution also includes a low-battery alarm (lowBattery.py), which is very similar, except that it intercepts LOOP events (instead of archiving events).

Customizing the Engine

In this section, we look at how to install a custom Engine. In general, this is the least desirable way to proceed, but in some cases it may be the only way to get what you want.

For example, suppose you want to define a new event for when the first archive of a day arrives. This can be done by extending the the standard engine.

This example is in file example/daily.py:

from weewx.wxengine import StdEngine, StdService
from weeutil.weeutil import startOfArchiveDay

class MyEngine(StdEngine):
    """A customized weewx engine."""

    def __init__(self, *args, **vargs):
        # Pass on the initialization data to my superclass:
        StdEngine.__init__(self, *args, **vargs)

        # This will record the timestamp of the old day
        self.old_day = None

    def postArchiveData(self, rec):
        # First let my superclass process it:
        StdEngine.postArchiveData(self, rec)

        # Get the timestamp of the start of the day using
        # the utility function startOfArchiveDay 
        dayStart_ts = startOfArchiveDay(rec['dateTime'])

        # Call the function firstArchiveOfDay if either this is
        # the first archive since startup, or if a new day has started
        if not self.old_day or self.old_day != dayStart_ts:
            self.old_day = dayStart_ts
            self.newDay(rec)                          # NOTE 1

    def newDay(self, rec):
        """Called when the first archive record of a day arrives."""

        # Go through the list of service objects. This
        # list is actually in my superclass StdEngine.
        for svc_obj in self.service_obj:
            # Because this is a new event, not all services will
            # be prepared to accept it. Check first to see if the
            # service has a member function "firstArchiveOfDay"
            # before calling it:
            if hasattr(svc_obj, "firstArchiveOfDay"):  # NOTE 2
                # The object does have the member function. Call it:
                svc_obj.firstArchiveOfDay(rec)

This customized engine works by monitoring the arrival of archive records, and checking their time stamp (rec['dateTime']. It calculates the time stamp for the start of the day, and if it changes, calls member function newDay() (NOTE 1).

The member function newDay() then goes through the list of services (attribute self.service_obj). Because this engine is defining a new event (first archive of the day), the existing services may not be prepared to accept it. So, the engine checks each one to make sure it has a function firstArchiveOfDay before calling it (NOTE 2).

To use this engine, go into file weewxd.py and change the line

weewx.wxengine.main()

so that it uses your new engine:

from examples.daily import MyEngine
 
# Specify that my specialized engine should be used instead
# of the default:
weewx.wxengine.main(EngineClass = MyEngine)

We now have a new engine that defines a new event ("firstArchiveOfDay"), but there is no service to take advantage of it. We define a new service:

# Define a new service to take advantage of the new event
class DailyService(StdService):
    """This service can do something when the first archive record of
    a day arrives."""

    def firstArchiveOfDay(self, rec):
        """Called when the first archive record of a day arrives."""

        print "The first archive of the day has arrived!"
        print rec

        # You might want to do something here like run a cron job

This service will simply print out a notice and then print out the new record. However, if there is some daily processing you want to do, perhaps a backup, or running utility wunderfixer, this would be the place to do it.

The final step is to go into your configuration file and specify that this new service be loaded, by adding its class name to option service_list:

[Engines]

  [[WxEngine]]
    # The list of services the main weewx engine should run:
    service_list = weewx.wxengine.StdWunderground, weewx.wxengine.StdCatchUp, weewx.wxengine.StdTimeSynch, weewx.wxengine.StdPrint, weewx.wxengine.StdReportService, examples.daily.DailyService

(Again, the list must be all on one line.)

Appendix A: Types

The weather variables used in weewx generally fall into three different camps. They can be a "SQL type", meaning they appear in the archive database, an "Observational type", meaning that they either appear in the archive database or are a derived quantity thereof, or a "Statistical type," meaning that they appear in the statistical database. Observational types can be used in plots, statistical types can be aggregated and used in daily, weekly, monthly, and yearly summaries.

The following table shows all the possible types and what categories they fall into. Note that just because a type appears in the table does not necessarily mean that it is available for your station setup. That would depend on whether your instrument supports the type, and whether or not you configured weewx to use that type.

Name SQL Type
(appears in archive database)
Observation Type
(can be used in plots)
Stats Type
(can be used in statistical aggregations)
altimeter X X X
altitude
barometer X X X
consBatteryVoltage X X X
dateTime X
dewpoint X X X
ET X X X
extraHumid1 X X x
extraHumid2 X X X
extraTemp1 X X X
extraTemp2 X X X
extraTemp3 X X X
hail X X X
hailRate X X X
heatindex X X X
heatingTemp X X X
heatingVoltage X X X
inHumidity X X X
inTemp X X X
inTempBatteryStatus X X X
interval X X
leafTemp2 X X X
leafWet2 X X X
outHumidity X X X
outTemp X X X
outTempBatteryStatus X X X
pressure X X X
radiation X X X
rain X X X
rainBatteryStatus X X X
rainRate X X X
referenceVoltage X X X
rxCheckPercent X X X
soilMoist1 X X X
soilMoist2 X X X
soilMoist3 X X X
soilMoist4 X X X
soilTemp1 X X X
soilTemp2 X X X
soilTemp3 X X X
soilTemp4 X X X
supplyVoltage X X X
txBatteryStatus X X X
usUnits X X
UV X X
wind X X X
windvec   X  
windBatteryStatus X X X
windDir X X X
windGust X X X
windGustDir X X X
windSpeed X X X
windchill X X X

Appendix B: Units

The table below lists all the unit groups, their members, and which units are options for the group.

Group Members Unit options
group_altitude altitude foot
meter
group_direction gustdir
vecdir
windDir
windGustDir
degree_compass
group_moisture soilMoist1
soilMoist2
soilMoist3
soilMoist4
centibar
group_percent extraHumid1
extraHumid2
inHumidity
outHumidity
rxCheckPercent
percent
group_pressure barometer
altimeter
pressure
inHg
mbar
hPa
group_radiation UV
radiation
watt_per_meter_squared
group_rain rain
ET
hail
in
cm
mm
group_rainrate rainRate
hailRate
in_per_hour
cm_per_hour
mm_per_hour
group_speed wind
windGust
windSpeed
windgustvec
windvec
mile_per_hour
km_per_hour
knot
meter_per_second
group_speed2 rms
vecavg
mile_per_hour2
km_per_hour2
knot2
meter_per_second2
group_temperature dewpoint
extraTemp1
extraTemp2
extraTemp3
heatindex
heatingTemp
inTemp
leafTemp1
leafTemp2
outTemp
soilTemp1
soilTemp2
soilTemp3
soilTemp4
windchill
degree_F
degree_C
group_volt consBatteryVoltage
heatingVoltage
referenceVoltage
supplyVoltage
volt

Appendix C: Statistical aggregations

Most of the templates are devoted to reporting statistical aggregates, such as monthly max temperature, or year-to-date rainfall. These are called aggregations, because they are a summary of lots of underlying data. However, only certain aggregates make sense for certain statistical types. For example, heat degree days is defined on a daily basis, so while the day's average temperature is meaningful, the day's heating degree days do not.

The following table defines which aggregates are available to be used in your template for which statistical types (assuming your station supports them and you have specified that it be stored in your stats database. See section [Stats] in the weewx.conf configuration file).

Stats Type min mintime max maxtime avg sum rms vecavg vecdir
barometer X X X X X        
inTemp X X X X X        
outTemp X X X X X        
inHumidity X X X X X        
outHumidity X X X X X        
wind X X X X X   X X X
rain X X X X X X      
dewpoint X X X X X        
windchill X X X X X        
heatindex X X X X X        
heatdeg         X X      
cooldeg         X X      
ET X X X X X        
radiation X X X X X        
UV X X X X X        
extraTemp1
extraTemp2
extraTemp3
X X X X X        
soilTemp1
soilTemp2
soilTemp3
X X X X X        
leafTemp1
leafTemp2
X X X X X        
extraHumid1
extraHumid2
X X X X X        
soilMoist1
soilMoist2
soilMoist3
soilMoist4
X X X X X        
leafWet1
leafWet2
X X X X X        
rxCheckPercent X X X X X