Files
weewx/docs/customizing.htm
2019-01-23 08:24:08 -05:00

7022 lines
321 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<title>WeeWX: Customization Guide</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link rel="stylesheet" href="css/ui-lightness/jquery-ui-1.10.4.custom.min.css"/>
<link rel="stylesheet" href="css/jquery.tocify.css"/>
<link rel="stylesheet" href="css/weewx_docs.css"/>
<link rel="icon" href="images/favicon.png" type="image/png"/>
</head>
<body>
<div class="sidebar">
<div class="doclist">
<a href="usersguide.htm">User's Guide</a><br/> <a href="customizing.htm">Customization Guide</a><br/> <a
href="hardware.htm">Hardware Guide</a><br/> <a href="utilities.htm">Utilities Guide</a><br/> <a
href="upgrading.htm">Upgrade Guide</a><br/> <a href="devnotes.htm">Notes for Developers</a>
</div>
<div id="toc_controls"></div>
<div id="toc_parent">
<div id="toc">
<!-- The table of contents will be injected here -->
</div>
</div>
</div>
<div class="main">
<div class="header">
<div class="logoref">
<a href='http://weewx.com'> <img src='images/logo-weewx.png' class='logo' style="float:right"
alt="weewx logo"/> </a><br/> <span class='version'>
Version: 3.9
</span>
</div>
<div class="title">WeeWX Customization Guide</div>
</div>
<div id="technical_content" class="content">
<p>
This document covers the customization of WeeWX. It assumes that you have read, and are reasonably familiar
with, the <a href="usersguide.htm">Users Guide</a>.
</p>
<p>
The introduction contains an overview of the architecture. If you are only interested in
customizing the generated reports you can probably skip the introduction and proceed directly to the section <a
href="#customizing_reports"><em>Customizing reports</em></a>. 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.
</p>
<p>
However, if your goal is a specialized application, such as adding alarms, RSS feeds, <i>etc.</i>, then it
would be worth your while to read about the internal architecture.
</p>
<p>
Most of the guide applies to any hardware, but the exact data types are hardware-specific. See the <a
href="hardware.htm"><em>WeeWX Hardware Guide</em></a> for details of how different observation types are
handled by different types hardware.
</p>
<p class="warning">
<strong>Warning!</strong><br/> WeeWX is still an experimental system and, as such, its internal design is
subject to change. Future upgrades may break any customizations you have done, particularly if they involve
the API (skin customizations tend to be more stable).
</p>
<h1 id="introduction">Introduction</h1>
<h2>Where to put customizations</h2>
<p>
For configuration changes, simply modify the WeeWX configuration file <span class="code">weewx.conf</span>.
</p>
<p>
Customization of reports may require changes to a skin configuration file <span
class="code">skin.conf</span> or template files ending in <span class="code">.tmpl</span> or <span class="code">.inc</span>. The <span
class="code">weewx.conf</span> configuration file and the <span class="code">skin.conf</span> configuration
and template files in the <span class='code'>skins</span> directory will be preserved when you upgrade.
</p>
<p>
Other customizations may require new Python code or modifications of example code. Where should you put the
code? If you simply modify the examples in place, then your changes will be overwritten the next time you do
an upgrade.
</p>
<p>
A better idea is to put the code in the <span class="code">user</span>, directory, which is preserved
through upgrades &mdash; you won't have to redo any changes you might have made. For example, if you wish to
modify one of the examples that comes with WeeWX, copy it from the <span class="code">examples</span>
directory to the <span class="code">user</span> directory, then modify it there. This way, your
modifications will not be touched if you upgrade.
</p>
<p>
For code that must run before anything else in WeeWX runs (for example, to set up an environment), put it in
the file <span class="code">extensions.py</span> in the <span class="code">user</span> directory. It is
always run before the WeeWX engine starts up. Because it is in the <span class="code">user</span>
subdirectory, it is preserved between upgrades.
</p>
<h2>Do I need to restart WeeWX?</h2>
<p>
If you make a change in <span class="code">weewx.conf</span>, you will need to restart WeeWX.
</p>
<p>
If you make a change to a template or to a <span class="code">skin.conf</span> file, you do not need to
restart WeeWX. The change will be adopted at the next reporting cycle, typically at the end of an archive
interval.
</p>
<p>
If you modify code in the <span class="code">user</span> directory,
you will need to restart WeeWX.
</p>
<h2 id="wee_reports">
The utility <span class="code">wee_reports</span>
</h2>
<p>
If you make changes, how do you know what the results will look like? You could just run WeeWX and wait
until the next reporting cycle kicks off but, depending on your archive interval, that could be a 30 minute
wait or more.
</p>
<p>
The utility <span class="code">wee_reports</span> allows you to run a report whenever you like. To use it,
just run it from a command line, with the location of your configuration file <span
class="code">weewx.conf</span> as the first argument. Optionally, if you include a unix epoch timestamp as a
second argument, then the report will use that as the "Current" time; otherwise, the time of the last record
in the archive database will be used. Here is an example, using 1 May 2014 00:00 PDT as the "Current" time.
</p>
<pre class="tty"><span class="cmd">wee_reports weewx.conf 1398927600</span></pre>
<p>
For more information about <span class="code">wee_reports</span>, see the <a
href="utilities.htm#wee_reports_utility">Utilities Guide</a>
</p>
<h2>Data architecture</h2>
<p>
WeeWX is data-driven. When the sensors spit out some data, WeeWX does something. The 'something' might be to
print out the data, or to generate an HTML report, or to use FTP to copy a report to a web server, or to
perform some calculations using the data. These actions are called services.
</p>
<p>
A driver is Python code that communicates with the hardware. The driver reads data from a serial port or a
device on the USB or a network interface. It handles any decoding of raw bits and bytes, and puts the
resulting data into LOOP packets and archive RECORDs. Whenever possible, WeeWX should work "out of the box"
with minimal configuration. However, in some cases the driver may include code to configure and test the
hardware, typically via the <span class="code">wee_device</span> utility.
</p>
<p>
In many cases there are useful dependent variables that use the primary variables emitted by the driver. For
example, rain rate, windchill, heatindex, humidex, apptemp, ET are all dependent quantities &mdash; they
require primary observations such as wind speed, temperature, solar radiation. The firmware for some
hardware calculates dependent quantities. The WeeWX service <span class="code">StdWXCalculate</span> fills
in the gaps and determines whether to use a value from firmware or a calculation from WeeWX. Sometimes the
firmware simply does it wrong.
</p>
<h2>
LOOP packets <em>vs.</em> archive records </h2>
<p>Generally, there are two types of data that flow through WeeWX: LOOP packets, and archive records.</p>
<h3>LOOP packets</h3>
<p>
LOOP packets are the raw data generated by the device driver. They get their name from the Davis Instruments
documentation. For some devices they are generated at rigid intervals, such as every 2 seconds for the Davis
Vantage series, for others, irregularly, every 20 or 30 seconds or so. LOOP packets may or may not contain
all the data types. For example, a packet may contain only temperature data, another only barometric data,
<em>etc</em>. These kinds of packet are called <em>partial record packets</em>. By contrast, other types of
hardware (notably the Vantage series), every LOOP packet contains every data type.
</p>
<p>In summary, LOOP packets can be highly irregular, but they come in frequently.</p>
<h3>Archive records</h3>
<p>
By contrast, archive records are highly regular. They are generated at regular intervals (generally every 5
to 30 minutes), and all contain the same data types. They represent an <em>aggregation</em> of the LOOP
packets over the archive interval. The exact kind of aggregation depends on the data type. For example, for
temperature, it's generally the average temperature over the interval. For rain, it's the sum of rain over
the interval. For battery status it's the last value in the interval.
</p>
<p>Some hardware is capable of generating its own archive records (the Davis Vantage and Oregon Scientific
WMR200, for example), but for hardware that does not archive data, WeeWX generates them.
</p>
<p>It is the archive data that is put in the SQL database, although, occasionally, the LOOP packets can be
useful (such as for the Weather Underground's "Rapidfire" mode).
</p>
<h2>The WeeWX service architecture</h2>
<p>
At a high-level, WeeWX consists of an engine class called <span class="code">StdEngine</span>. It is
responsible for loading <em>services</em>, then arranging for them to be called when key events occur, such
as the arrival of LOOP or archive data. The default install of WeeWX includes the following services:
</p>
<table id='default_services' class="indent">
<caption>The standard WeeWX services</caption>
<tbody>
<tr class="first_row">
<td>Service</td>
<td>Function</td>
</tr>
<tr>
<td class="code first_col">weewx.engine.StdTimeSynch</td>
<td>Arrange to have the clock on the station synchronized at regular intervals.
</td>
</tr>
<tr>
<td class="code first_col">weewx.engine.StdConvert</td>
<td>Converts the units of the input to a target unit system (such as US or Metric).
</td>
</tr>
<tr>
<td class="code first_col">weewx.engine.StdCalibrate</td>
<td>Adjust new LOOP and archive packets using calibration expressions.
</td>
</tr>
<tr>
<td class="code first_col">weewx.engine.StdQC</td>
<td>Check quality of incoming data, making sure values fall within a specified range.
</td>
</tr>
<tr>
<td class="code first_col">weewx.wxservices.StdWXCalculate</td>
<td>Calculate any missing, derived weather observation types, such a dewpoint, windchill, or
altimeter-corrected pressure.
</td>
</tr>
<tr>
<td class="code first_col">weewx.engine.StdArchive</td>
<td>Archive any new data to the SQL databases.</td>
</tr>
<tr>
<td class="code first_col">weewx.restx.StdStationRegistry<br/> weewx.restx.StdWunderground<br/>
weewx.restx.StdPWSweather<br/> weewx.restx.StdCWOP<br/> weewx.restx.StdWOW<br/>weewx.restx.StdAWEKAS
</td>
<td>Various <a href="http://en.wikipedia.org/wiki/Representational_State_Transfer"> RESTful services</a>
(simple stateless client-server protocols), such as the Weather Underground, CWOP, etc. Each
launches its own, independent thread, which manages the post.
</td>
</tr>
<tr>
<td class="code first_col">weewx.engine.StdPrint</td>
<td>Print out new LOOP and archive packets on the console.
</td>
</tr>
<tr>
<td class="code first_col">weewx.engine.StdReport</td>
<td>Launch a new thread to do report processing after a new archive record arrives. Reports do things
such as generate HTML or CSV files, generate images, or transfer files using FTP/rsync.
</td>
</tr>
</tbody>
</table>
<p>
It is easy to extend old services or to add new ones. The source distribution includes an example new
service called <span class="code">MyAlarm</span>, which sends an email when an arbitrary expression
evaluates <span class="code">True</span>. These advanced topics are covered later in the section <em><a
href="#service_engine">Customizing the WeeWX service engine</a></em>.
</p>
<h2>
The standard reporting service, <span class="code">StdReport</span>
</h2>
<p>
For the moment, let us focus on the last service, <span class="code">weewx.engine.StdReport</span>, the
standard service for creating reports. This will be what most users will want to customize, even if it means
just changing a few options.
</p>
<h3>Reports</h3>
<p>
The standard reporting service, <span class="code">StdReport</span>, runs zero or more <em>reports</em>. The
specific reports which get run are set in the configuration file <span class="code">weewx.conf</span>, in
section <span class="code">[StdReport]</span>.
</p>
<p>
The default distribution of WeeWX includes six reports:
</p>
<table class="indent">
<tbody>
<tr class="first_row">
<td>Report</td>
<td>Default functionality</td>
</tr>
<tr>
<td class="code first_col">SeasonsReport</td>
<td>Introduced with WeeWX V3.9, this report generates a single HTML file with day, week, month and year
"to-date" summaries, as well as the plot images to go along with them. Buttons select which time
scale the user wants. It also generates HTML files with more details on celestial bodies and
statistics. Also generates NOAA monthly and yearly summaries.
</td>
</tr>
<tr>
<td class="code first_col">SmartphoneReport</td>
<td>A simple report that generates an HTML file, which allows "drill down" to show more detail about
observations. Suitable for smaller devices, such as smartphones.
</td>
</tr>
<tr>
<td class="code first_col">MobileReport</td>
<td>A super simple HTML file that just shows the basics. Suitable for low-powered or
bandwidth-constrained devices.
</td>
</tr>
<tr>
<td class="code first_col">StandardReport</td>
<td>This is an older report that has been used for many years in WeeWX. It 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. It typically loads faster than the <em>SeasonsReport</em>.
</td>
</tr>
<tr>
<td class="code first_col">FTP</td>
<td>Transfer everything in the <span class="symcode">HTML_ROOT</span> directory to a remote server using ftp.
</td>
</tr>
<tr>
<td class="code first_col">RSYNC</td>
<td>Transfer everything in the <span class="symcode">HTML_ROOT</span> directory to a remote server using rsync.
</td>
</tr>
</tbody>
</table>
<p>Note that the FTP and RSYNC "reports" are a funny kind of report in that it they do not actually generate
anything. Instead, they use the reporting service engine to transfer files and folders to a remote
server.
</p>
<h3>Skins</h3>
<p>
Each report has a <em>skin</em> 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 <em>skin configuration file</em>, <span class="code">skin.conf</span>. If you will, the skin controls the
<em>look and feel </em>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 <span class="code">weewx.conf</span> or the skin
configuration file <span class="code">skin.conf</span>.
</p>
<p>Like all reports, the FTP and RSYNC "reports" also use a skin, and include a skin configuration file,
although they are quite minimal.
</p>
<p>
Skins live in their own directory called <span class='code'>skins</span>, whose location is referred to as
<a href="#skin-root-description"> <span class="symcode">SKIN_ROOT</span></a>.
</p>
<div id="skin-root-description" class="modal-dialog">
<div>
<a href="#close" title="Close" class="close-dialog">X</a>
<h4>SKIN_ROOT</h4>
<p>
The symbol <span class='code'>SKIN_ROOT</span> is a symbolic name to the location of the directory
where your skins are located. It is not to be taken literally. Consult the <a
href="usersguide.htm#dir-layout-table">directory layout table</a> in the User's Guide for its exact
location, dependent on how you installed WeeWX and what operating system you are using
</p>
</div>
</div>
<h3>Generators</h3>
<p>
To create their output, skins rely on one or more <em>generators</em>, which are what do the actual work,
such as creating HTML files or plot images. Generators can also copy files around or FTP/rsync them to
remote locations. The default install of WeeWX includes the following generators:
</p>
<table class="indent">
<tbody>
<tr class="first_row">
<td>Generator</td>
<td>Function</td>
</tr>
<tr>
<td class="code first_col">weewx.cheetahgenerator.CheetahGenerator</td>
<td>Generates files from templates, using the Cheetah template engine. Used to generate HTML and text
files.
</td>
</tr>
<tr>
<td class="code first_col">weewx.imagegenerator.ImageGenerator</td>
<td>Generates graph plots.</td>
</tr>
<tr>
<td class="code first_col">weewx.reportengine.FtpGenerator</td>
<td>Uploads data to a remote server using FTP.</td>
</tr>
<tr>
<td class="code first_col">weewx.reportengine.RsyncGenerator</td>
<td>Uploads data to a remote server using rsync.</td>
</tr>
<tr>
<td class="code first_col">weewx.reportengine.CopyGenerator</td>
<td>Copies files locally.</td>
</tr>
</tbody>
</table>
<p>
Note that the three generators <span class="code">FtpGenerator</span>, <span
class="code">RsyncGenerator</span>, and <span class="code">CopyGenerator</span> do not actually generate
anything having to do with the presentation layer. Instead, they just move files around.
</p>
<p>
Which generators are to be run for a given skin is specified in the skin's configuration file <span
class="code">skin.conf</span>, in section <a href="#generators_section"><span
class="code">[Generators]</span></a>.
</p>
<h3>Templates</h3>
<p>
A template is a text file that is processed by a <em>template engine</em> to create a new file. WeeWX uses
the <a href="https://pythonhosted.org/Cheetah/">Cheetah</a> template engine. The generator <span
class="code">weewx.cheetahgenerator.CheetahGenerator</span> is responsible for running Cheetah at
appropriate times.
</p>
<p>A template may be used to generate HTML, XML, CSV, Javascript, or any other type of text file. A template
typically contains variables that are replaced when creating the new file. Templates may also contain simple
programming logic.
</p>
<p>
Each template file lives in the skin directory of the skin that uses it. By convention, a template file ends
with the <span class="code">.tmpl</span> extension. There are also template files that end with the <span class="code">.inc</span> extension. These templates are included in other templates.
</p>
<h2>The database</h2>
<p>
WeeWX uses a single database to store and retrieve the records it needs. It can be implemented by using
either <a href="http://www.sqlite.org/">SQLITE3</a>, an open-source, lightweight SQL database, or <a
href="http://www.mysql.com/"> MySQL</a>, an open-source, full-featured database server.
</p>
<h3>Structure</h3>
<p>
Inside this database are several tables. The most important is the <em>archive table</em>, a big flat table,
holding one record for each archive interval, keyed by <span class="code">dateTime</span>, the time at the
end of the archive interval. It looks something like this:
</p>
<table class="indent fixed_width">
<caption>
Structure of the <span class="code">archive</span> database table
</caption>
<tr class="code first_row">
<td>dateTime</td>
<td>usUnits</td>
<td>interval</td>
<td>barometer</td>
<td>pressure</td>
<td>altimeter</td>
<td>inTemp</td>
<td>outTemp</td>
<td>...</td>
</tr>
<tr class="code">
<td>1413937800</td>
<td>1</td>
<td>5</td>
<td>29.938</td>
<td><em>null</em></td>
<td><em>null</em></td>
<td>71.2</td>
<td>56.0</td>
<td>...</td>
</tr>
<tr class="code">
<td>1413938100</td>
<td>1</td>
<td>5</td>
<td>29.941</td>
<td><em>null</em></td>
<td><em>null</em></td>
<td>71.2</td>
<td>55.9</td>
<td>...</td>
</tr>
<tr class="code">
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
</tr>
</table>
<p>
The first three columns are <em>required.</em> Here's what they mean:
</p>
<table class="indent">
<tr class="first_row">
<td>Name</td>
<td>Meaning</td>
</tr>
<tr>
<td class="first_col code">dateTime</td>
<td>The time at the end of the archive interval in <a href="http://en.wikipedia.org/wiki/Unix_time">unix
epoch time</a>. This is the <em>primary key</em> in the database. It must be unique, and it cannot
be null.
</td>
</tr>
<tr>
<td class="first_col code">usUnits</td>
<td>The unit system the record is in. It cannot be null. See the <em><a href="#units">Appendix:
Units</a></em> for how these systems are encoded.
</td>
</tr>
<tr>
<td class="first_col code">interval</td>
<td>The length of the archive interval in <em>minutes</em>. It cannot be null.
</td>
</tr>
</table>
<p>
In addition to the main archive table, there are a number of smaller tables inside the database, one for
each observation type, that hold <em>daily summaries</em> of the type. For example, the minimum and maximum
value seen during the day, and at what time. These tables have names such as <span class="code">archive_day_outTemp</span>
or <span class="code">archive_day_barometer</span>. Their existence is generally transparent to the user.
</p>
<h3 id="binding_names">Binding names</h3>
<p>
While most users will only need the one weather database that comes with WeeWX, the reporting engine allows
you to use multiple databases in the same report. For example, if you have installed the <a
href="https://github.com/weewx/weewx/wiki/cmon"><span class="code">cmon</span></a> computer monitoring
package, which uses its own database, you may want to include some statistics or graphs about your server in
your reports, using that database.
</p>
<p>
An additional complication is that WeeWX can use more than one database implementation: SQLite or MySQL.
Making users specify in the templates not only which database to use, but also which implementation, would
be unreasonable.
</p>
<p>
The solution, like so many other problems in computer science, is to introduce another level of indirection,
a <em>database binding</em>. Rather than specify which database to use, you specify which <em>binding</em>
to use. Bindings do not change with the database implementation, so, for example, you know that <span
class="code">wx_binding</span> will always point to the weather database, no matter if its implementation is
a sqlite database or a MySQL database. Bindings are listed in section <a href="usersguide.htm#DataBindings"><span
class="code">[DataBindings]</span></a> in <span class="code">weewx.conf</span>.
</p>
<p>
The standard weather database binding that WeeWX uses is <span class="code">wx_binding</span>. This is the
binding that you will be using most of the time and, indeed, it is the default. You rarely have to specify
it explicitly.
</p>
<h3>Programming interface</h3>
<p>
WeeWX includes a module called <span class='code'>weedb</span> that provides a single interface for many of
the differences between database implementations such as SQLite and MySQL. However, it is not uncommon to
make direct SQL queries within services or search list extensions. In such cases, the SQL should be generic
so that it will work with every type of database.
</p>
<p>The database manager class provides methods to create, open, and query a database. These are the canonical
forms for obtaining a database manager.
</p>
<p>If you are opening a database from within a WeeWX service:</p>
<pre class='tty'>db_manager = self.engine.db_binder.get_manager(data_binding='name_of_binding', initialize=True)
# Sample query:
db_manager.getSql("SELECT SUM(rain) FROM %s "\
"WHERE dateTime&gt;? AND dateTime&lt;=?" % db_manager.table_name, (start_ts, stop_ts))</pre>
<p>
If you are opening a database from within a WeeWX search list extension, you will be passed in a function
<span class="code">db_lookup()</span> as a parameter, which can be used to bind to a database. By default,
it returns a manager bound to <span class="code">wx_binding</span>:
</p>
<pre class='tty'>wx_manager = db_lookup() # Get default binding
other_manager = db_lookup(data_binding='some_other_binding') # Get an explicit binding
# Sample queries:
wx_manager.getSql("SELECT SUM(rain) FROM %s "\
"WHERE dateTime&gt;? AND dateTime&lt;=?" % wx_manager.table_name, (start_ts, stop_ts))
other_manager.getSql("SELECT SUM(power) FROM %s"\
"WHERE dateTime&gt;? AND dateTime&lt;=?" % other_manager.table_name, (start_ts, stop_ts))</pre>
<p>
If opening a database from somewhere other than a service, and there is no <span
class="code">DBBinder</span> available:
</p>
<pre class='tty'>db_manager = weewx.manager.open_manager_with_config(config_dict, data_binding='name_of_binding')
# Sample query:
db_manager.getSql("SELECT SUM(rain) FROM %s "\
"WHERE dateTime&gt;? AND dateTime&lt;=?" % db_manager.table_name, (start_ts, stop_ts))</pre>
<p>
The <span class="code">DBBinder</span> caches managers, and thus database connections. It cannot be shared
between threads.
</p>
<h2>Units</h2>
<p>
The unit architecture in WeeWX is designed to make basic unit conversions and display of units easy. It is
not designed to provide dimensional analysis, arbitrary conversions, and indications of compatibility.
</p>
<p>
The <em>driver</em> reads observations from an instrument and converts them, as necessary, into a standard
set of units. The actual units used by each instrument vary widely; some instruments use Metric units,
others use US Customary units, and many use a mixture. The driver ensures that the units are consistent for
storage in the WeeWX database. By default, and to maintain compatibility with <span
class='code'>wview</span>, the default database units are US Customary, although this can be changed.
</p>
<p>
Note that whatever unit system is used in the database, data can be <em>displayed</em> using any unit
system. So, in practice, it does not matter what unit system is used in the database.
</p>
<p>
Each <em>observation type</em>, such as <span class='code'>outTemp</span> or <span
class='code'>pressure</span>, is associated with a <em>unit group</em>, such as <span class='code'>group_temperature</span>
or <span class='code'>group_pressure</span>. Each unit group is associated with a <em>unit type</em> such as
<span class='code'>degree_F</span> or <span class='code'>mbar</span>. The reporting service uses this
architecture to convert observations into a target unit system, to be displayed in your reports.
</p>
<p>With this architecture one can easily create reports with, say, wind measured in knots, rain measured in mm,
and temperatures in degree Celsius. Or one can create a single set of templates, but display data in
different unit systems with only a few stanzas in a configuration file.
</p>
<h1 id="customizing_reports">Customizing reports</h1>
<p>
There are two general mechanisms for customizing reports:
change options in one or more configuration files, or change
the template files. The former is generally easier, but
occasionally the latter is necessary.
</p>
<p>
Options are specified in configuration files and are organized hierarchically into stanzas. For example, the
<span class="code">[Labels]</span> stanza contains the text that should be displayed for each observation.
The <span class="code">[Units]</span> stanza contains other stanzas, each of which contains parameters that
control the display of units.
</p>
<p class="note">
For a complete listing of the report options,
see the section <a href="#report_options"><em>Reference: report options</em></a>.
</p>
<h2 id="How_options_work">How options work</h2>
<p>
Options are used in WeeWX to specify how your reports will look and what they will contain. For example,
they control what units to use, how to format a string, which plot lines to include, and what color they
should be, <em>etc</em>. They all work the same way and obey a hierarchy, allowing skin authors to specify a
look and feel of a report, while ensuring that you still have the final say of how it will come out.
</p>
<p>
Here are the places where options are specified, including their <em>scope</em> and <em>precedence</em>,
listed in order of decreasing precedence.
</p>
<table class="indent" style="width: 90%">
<caption>Option hierarchy, highest to lowest</caption>
<tr class="first_row">
<td>File</td>
<td>Section</td>
<td>Scope</td>
<td>Nesting example</td>
<td>Comments</td>
</tr>
<tr>
<td class="code">weewx.conf</td>
<td class="code">[StdReport] / [[<span class="symcode">report_name</span>]]
</td>
<td>Affects only the report <span class="symcode">report_name</span>.</td>
<td class="code">[[[Labels]]] / [[[[Generic]]]]</td>
<td>Highest precedence. Has the final say.</td>
</tr>
<tr>
<td class="code">weewx.conf</td>
<td class="code"><a href="#change_defaults">[StdReport] / [[Defaults]]</a></td>
<td>Affects <em>all</em> reports.</td>
<td class="code">[[[Labels]]] / [[[[Generic]]]]</td>
<td>Normal mechanism for an user to modify the parameters in a report, such as units.</td>
</tr>
<tr>
<td class="code">skins/<span class="symcode">report_name</span>/skin.conf</td>
<td></td>
<td>Affects only the report <span class="symcode">report_name</span>.</td>
<td class="code">[Labels] / [[Generic]]</td>
<td>Generally supplied by the skin author, not the user. Parameters specified here define the baseline behavior.</td>
</tr>
<tr>
<td class="code">weewx/defaults.py</td>
<td class="code"></td>
<td>Affects <em>all</em> reports.</td>
<td class="code">[Labels] / [[Generic]]</td>
<td>Acts as a backstop, in the case an option is not specified anywhere else. Not generally modified by
the user, although this could change in the future.
</td>
</tr>
</table>
<p>
You must pay attention to the number of brackets! In the table above, there are two different nesting depths used: one for <span class="code">weewx.conf</span>,
and one for <span class="code">weewx/defaults.py</span> and <span class="code">skin.conf</span>. This is because the stanzas defined in <span class="code">weewx.conf</span> start two levels down in the hierarchy <span class="code">[StdReport]</span>, whereas the stanzas defined in <span class="code">skin.conf</span> and <span class="code">defaults.py</span> are at the root level.
</p>
<h2 id="how_to_change_labels">Changing labels</h2>
<p>
Let's look at an example. If you take a look inside the file <span class="code">weewx/defaults.py</span>,
you will see it contains what looks like a big configuration file. Among other things, it has two entries
that look like this:
</p>
<pre class="tty">
...
[Labels]
...
[[Generic]]
...
inTemp = Inside Temperature
outTemp = Outside Temperature
...</pre>
<p>
This tells the report generators that when it comes time to label the observation variables <span class="code">inTemp</span>
and <span class="code">outTemp</span>, use the strings <span class="code">InsideTemperature</span> and <span
class="code">Outside Temperature</span>, respectively. Because <span class="code">weewx/defaults.py</span>
lowest precedence, these strings will be used only when nothing else is available.
</p>
<p>
Now suppose we have a skin called <em>Life</em> . Its configuration file will be
located at <span class="code">skins/Life/skin.conf</span>. Suppose, among other things, it contains an excerpt
</p>
<pre class="tty">
...
[Labels]
...
[[Generic]]
...
outTemp = Exterior Temperature
</pre>
<p>
What this says is that the author of <span class="code">Life</span>, for whatever reasons, decided
that s/he wants to use the string <span class="code">Exterior Temperature</span>, instead of
<span class="code">Outside Temperature</span> for the label for <span class="code">outTemp</span>.
The file includes no definition for <span class="code">inTemp</span>.
</p>
<p>
Finally, for purposes of illustrating precedence, suppose the <span class="code">[StdReport]</span>
section of <span class="code">weewx.conf</span> contains this:
</p>
<pre class="tty">
[StdReport]
...
[[LifeA]]
skin = Seasons
[[LifeB]]
skin = Life
[[[Labels]]]
[[[[Generic]]]]
outTemp = Barn Temperature
</pre>
<p>
This will result in reports with the following values:
</p>
<table class="indent" style="width: 80%">
<tr class="first_row">
<td>Report</td>
<td>Label for <span class="code">inTemp</span></td>
<td>Label for <span class="code">outTemp</span></td>
</tr>
<tr>
<td>LifeA</td>
<td>Inside Temperature</td>
<td>Exterior Temperature</td>
</tr>
<tr>
<td>LifeB</td>
<td>Inside Temperature</td>
<td>Barn Temperature</td>
</tr>
</table>
<p>
Note how the values specified for <span class="code">inTemp</span> are not overridden anywhere, so the value
specified in <span class="code">weewx/defaults.py</span>, that is, <span
class="code">Inside Temperature</span>, is used. By contrast, a value for <span class="code">outTemp</span>
was specified in the skin so, in the absence of any user override, its value, <span class="code">ExteriorTemperature</span>
will be used as the new default. Finally, for the report <span class="code">LifeB</span>, the user
<em>has</em> specified an override for <span class="code">outTemp</span>, so that value, <span class="code">Barn Temperature</span>,
is used.
</p>
<p>
One more example. This time, suppose the <span class="code">[StdReport]</span>
section of <span class="code">weewx.conf</span> contains this:
</p>
<pre class="tty">
[StdReport]
...
<span class="highlight"> [[Defaults]]
[[[Labels]]]
[[[[Generic]]]]
outTemp = Shed Temperature</span>
[[LifeA]]
skin = Seasons
[[LifeB]]
skin = Life
[[[Labels]]]
[[[[Generic]]]]
outTemp = Barn Temperature
</pre>
<p>
We have added a new section (<span class="highlight">highlighted</span>), where the user has specified
a new default for the observation type <span class="code">outTemp</span>, that is, <span class="code">Shed Temperature</span>.
Unless overridden, this is the value that will now be used instead of what the skin author specified.
The final tally looks like this:
</p>
<table class="indent" style="width: 80%">
<tr class="first_row">
<td>Report</td>
<td>Label for <span class="code">inTemp</span></td>
<td>Label for <span class="code">outTemp</span></td>
</tr>
<tr>
<td>LifeA</td>
<td>Inside Temperature</td>
<td>Shed Temperature</td>
</tr>
<tr>
<td>LifeB</td>
<td>Inside Temperature</td>
<td>Barn Temperature</td>
</tr>
</table>
<h2 id="how_to_change_units">How to change units</h2>
<p>
Some users prefer to see metric units, others prefer US customary units. And there are other cases where
metric/US is not enough; some sites need metric units for most measurements, but wind speeds in <span
class="code">knots</span>, or some sites need rainfall in <span class="code">mm</span> whereas others need
<span class="code">cm</span>.
</p>
<p>
This section shows how to specify exactly what units you want to use for which reports.
The examples use the skin <em>Seasons</em>, which comes with the distribution.
</p>
<h3 id="change_defaults">
Change units for all reports
</h3>
<p>
With this approach, you change an option under the <span class="code">[[Defaults]]</span> subsection,
located under <span class="code">[StdReport]</span> in <span class="code">weewx.conf.</span> Changes here
will affect <em>all</em> reports.
</p>
<p>
For example, suppose you wish to use metric units for all your reports, instead of the default US Customary
Units. The section that controls units is <span class="code">[StdReport] / [[Defaults]] / [[[Units]]] / [[[[Groups]]]]</span>.
It looks like this:
</p>
<pre class="tty">
[StdReport]
# 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
# The database binding indicates which data should be used in reports.
data_binding = wx_binding
# The following section contains defaults that will apply to *all* skins
[[Defaults]]
# The following section determines the selection and formatting of units.
[[[Units]]]
# The following section sets what unit to use for each unit group.
# NB: The unit is always in the singular. I.e., 'mile_per_hour',
# NOT 'miles_per_hour'
[[[[Groups]]]]
group_altitude = foot # Options are 'foot' or 'meter'
group_degree_day = degree_F_day # Options are 'degree_F_day' or 'degree_C_day'
group_pressure = inHg # Options are 'inHg', 'mmHg', 'mbar', or 'hPa'
group_rain = inch # Options are 'inch', 'cm', or 'mm'
group_rainrate = inch_per_hour # Options are 'inch_per_hour', 'cm_per_hour', or 'mm_per_hour'
group_speed = mile_per_hour # Options are 'mile_per_hour', 'km_per_hour', 'knot', or 'meter_per_second'
group_speed2 = mile_per_hour2 # Options are 'mile_per_hour2', 'km_per_hour2', 'knot2', or 'meter_per_second2'
group_temperature = degree_F # Options are 'degree_F' or 'degree_C'
</pre>
<p>
To use metric units for all your skins, you would edit this section to read:
</p>
<pre class="tty">
[StdReport]
# 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
# The database binding indicates which data should be used in reports.
data_binding = wx_binding
# The following section contains defaults that will apply to *all* skins
[[Defaults]]
# The following section determines the selection and formatting of units.
[[[Units]]]
# The following section sets what unit to use for each unit group.
# NB: The unit is always in the singular. I.e., 'mile_per_hour',
# NOT 'miles_per_hour'
[[[[Groups]]]]
group_altitude = <span class="highlight">meter</span> # Options are 'foot' or 'meter'
group_degree_day = <span class="highlight">degree_C_day</span> # Options are 'degree_F_day' or 'degree_C_day'
group_pressure = <span class="highlight">mbar</span> # Options are 'inHg', 'mmHg', 'mbar', or 'hPa'
group_rain = <span class="highlight">mm</span> # Options are 'inch', 'cm', or 'mm'
group_rainrate = <span class="highlight">mm_per_hour</span> # Options are 'inch_per_hour', 'cm_per_hour', or 'mm_per_hour'
group_speed = <span class="highlight">meter_per_second</span> # Options are 'mile_per_hour', 'km_per_hour', 'knot', or 'meter_per_second'
group_speed2 = <span class="highlight">meter_per_second2</span> # Options are 'mile_per_hour2', 'km_per_hour2', 'knot2', or 'meter_per_second2'
group_temperature = <span class="highlight">degree_C</span> # Options are 'degree_F' or 'degree_C'
</pre>
<p>
The options that were changed have been <span class="highlight">&nbsp;highlighted&nbsp;</span>. Note that
the changes occurred under the subsection <span class="code">[[Defaults]]</span>, so they
will affect <em>all</em> reports.
</p>
<h3 id="change_units">
Change units for a single report
</h3>
<p>
With this approach you can change an option for a single report.
</p>
<p>
Suppose you want two reports, one in US Customary, the other in Metric. The first, call it <em>SeasonsUSReport</em>, will go in the regular directory <span class="symcode">HTML_ROOT</span>.
However, the latter, call it <em>SeasonsMetricReport</em>, will go in a subdirectory,
<span class="symcode">HTMLROOT</span><span class="code">/metric</span>.
</p>
<p>
To accomplish this, we will generate two reports, one using the defaults, the other using overrides to
change the target unit system and the target directory. The section <span class="code">[StdReport]</span>
will look like this:
</p>
<pre class="tty">
[StdReport]
# 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
# The database binding indicates which data should be used in reports.
data_binding = wx_binding
# The following section contains defaults that will apply to *all* skins
[[Defaults]]
[[[Units]]]
[[[[Groups]]]]
group_altitude = meter # Options are 'foot' or 'meter'
group_degree_day = degree_C_day # Options are 'degree_F_day' or 'degree_C_day'
group_pressure = mbar # Options are 'inHg', 'mmHg', 'mbar', or 'hPa'
group_rain = mm # Options are 'inch', 'cm', or 'mm'
group_rainrate = mm_per_hour # Options are 'inch_per_hour', 'cm_per_hour', or 'mm_per_hour'
group_speed = meter_per_second # Options are 'mile_per_hour', 'km_per_hour', 'knot', or 'meter_per_second'
group_speed2 = meter_per_second2 # Options are 'mile_per_hour2', 'km_per_hour2', 'knot2', or 'meter_per_second2'
group_temperature = degree_C # Options are 'degree_F' or 'degree_C'
<span class="highlight">[[SeasonsUSReport]]</span>
skin = Seasons
enable = true
<span class="highlight"> [[SeasonsMetricReport]]
skin = Seasons
enable = true
HTML_ROOT = skins/metric
[[[Units]]]
[[[[Groups]]]]
group_altitude = foot # Options are 'foot' or 'meter'
group_degree_day = degree_F_day # Options are 'degree_F_day' or 'degree_C_day'
group_pressure = inHg # Options are 'inHg', 'mmHg', 'mbar', or 'hPa'
group_rain = inch # Options are 'inch', 'cm', or 'mm'
group_rainrate = inch_per_hour # Options are 'inch_per_hour', 'cm_per_hour', or 'mm_per_hour'
group_speed = mile_per_hour # Options are 'mile_per_hour', 'km_per_hour', 'knot', or 'meter_per_second'
group_speed2 = mile_per_hour2 # Options are 'mile_per_hour2', 'km_per_hour2', 'knot2', or 'meter_per_second2'
group_temperature = degree_F # Options are 'degree_F' or 'degree_C'</span>
</pre>
<p>
Two things are different from the stock section <span class="code">[StdReport]</span>. First (1), we have
renamed the first report from <em>SeasonsReport</em> to <em>SeasonsUSReport</em>
for clarity; and second (2), we have introduced a new report <em>SeasonsMetricReport</em>,
which is just like the first, except it puts its results in a different subdirectory and uses different
units. Both use the same skin, the <em>Seasons</em> skin.
</p>
<p>
The reason this works is that the report <em>SeasonsUSReport</em> uses all of the defaults,
which include US Customary units, and the default value for <a href="usersguide.htm#HTML_ROOT"><span
class="code">HTML_ROOT</span></a> (which specifies the location for where HTML files are to be put).
</p>
<p>
By contrast, <em>SeasonsMetricReport</em> cannot use these defaults, so it
overrides the units to be used, and the value of <span class="code">HTML_ROOT</span>.
</p>
<h2 id="customizing_gen_time">Scheduling report generation</h2>
<p>
Normal WeeWX operation is to run each <em><a href="usersguide.htm#Reports">report</a></em> defined in <span
class="code">weewx.conf</span> every archive period. Whilst this may suit most situations, there may be
occasions when it is desirable to run a report less frequently than every archive period. For example, the
archive interval might be 5 minutes, but you only want to FTP files every 30 minutes, once per day or at a
set time each day. Weewx has two mechanisms that provide the ability to control when files are generated.
The <em><a href="#stale_age"><span class="code">stale_age</span></a></em> option allows control over the age
of a file before it is regenerated and the <span class="code">report_timing</span> option allows precise
control over when individual reports are run.
</p>
<p class="note" style="display: inline-block; width: 70%">
<strong>Note</strong><br/>Whilst <span class="code">report_timing</span> specifies when a given report should be generated, the generation of reports is still
controlled by the WeeWX report cycle, so reports can never be generated more frequently than once every archive period.
</p>
<h3>
The <span class="code">report_timing</span> option </h3>
<p>
The <span class="code">report_timing</span> option uses a CRON-like format to control when a report is to be
run. Whilst a CRON-like format is used, the control of WeeWX report generation using the <span class="code">report_timing</span>
option is confined completely to WeeWX and has no interraction with the system CRON service.
</p>
<p>
The <span class="code">report_timing</span> option consists of five, space separated parameters as follows:
</p>
<pre class="tty">report_timing = minutes hours day_of_month months day_of_week</pre>
<p>
The <span class="code">report_timing</span> parameters are summarised in the following table:
</p>
<table class="indent">
<tbody>
<tr class="first_row">
<td>Parameter</td>
<td style="width:20%">Function</td>
<td>Allowable values</td>
</tr>
<tr>
<td class="code first_col">minutes</td>
<td>Specifies the minutes of the hour when the report will be run
</td>
<td>*, or<br/>numbers in the range 0..59 inclusive
</td>
</tr>
<tr>
<td class="code first_col">hours</td>
<td>Specifies the hours of the day when the report will be run
</td>
<td>*, or<br/>numbers in the range 0..23 inclusive
</td>
</tr>
<tr>
<td class="code first_col">day_of_month</td>
<td>Specifies the days of the month when the report will be run
</td>
<td>*, or<br/>numbers in the range 1..31 inclusive
</td>
</tr>
<tr>
<td class="code first_col">months</td>
<td>Specifies the months of the year when the report will be run
</td>
<td>*, or<br/>numbers in the range 1..12 inclusive, or<br/>abbreviated names in the range jan..dec
inclusive
</td>
</tr>
<tr>
<td class="code first_col">day_of_week</td>
<td>Specifies the days of the week when the report will be run
</td>
<td>*, or<br/>numbers in the range 0..7 inclusive (0,7 = Sunday, 1 = Monday etc), or<br/>abbreviated
names in the range sun..sat inclusive
</td>
</tr>
</tbody>
</table>
<p>
The <span class="code">report_timing</span> option may only be used in <span class="code">weewx.conf</span>.
When set in the <span class="code">[StdReport]</span> section of <span class="code">weewx.conf</span> the
option will apply to all reports listed under <span class="code">[StdReport]</span>. When specified within a
report section, the option will override any setting in <span class="code">[StdReport]</span> for that
report. In this manner it is possible to have different reports run at different times. The following sample
<span class="code">weewx.conf</span> excerpt illustrates this:
</p>
<pre class="tty">
[StdReport]
# 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
# The database binding indicates which data should be used in reports.
data_binding = wx_binding
# Report timing parameter
report_timing = 0 * * * *
# Each of the following subsections defines a report that will be run.
[[AReport]]
skin = SomeSkin
[[AnotherReport]]
skin = SomeOtherSkin
report_timing = */10 * * * *</pre>
<p>
In this case, the <span class="code">[[AReport]]</span> report would be run under under control of the
<span class="code">0 * * * *</span> setting (on the hour) under <span class="code">[StdReport]</span> and
the <span class="code">[[AnotherReport]]</span> report would be run under control of the <span class="code">*/10 * * * *</span>
setting (every 10 minutes) which has overriden the <span class="code">[StdReport]</span> setting.
</p>
<h3>
How <span class="code">report_timing</span> controls reporting </h3>
<p>
The syntax and interpretation of the <span class="code">report_timing</span> parameters are largely the same
as those of the CRON service in many Unix and Unix-like operating systems. The syntax and interpretation are
outlined below.
</p>
<p>
When the <span class="code">report_timing</span> option is in use WeeWX will run a report when the minute,
hour and month of year parameters match the report time, and at least one of the two day parameters (day of
month or day of week) match the report time. This means that non-existent times, such as "missing hours"
during daylight savings changeover, will never match, causing reports scheduled during the "missing times"
not to be run. Similarly, times that occur more than once (again, during daylight savings changeover) will
cause matching reports to be run more than once.
</p>
<p class="note" style="display: inline-block; width: 70%">
<strong>Note</strong><br/>Report time does not refer to the time at which the report is run, but rather the
date and time of the latest data the report is based upon. If you like, it is the effective date and time of
the report. For normal WeeWX operation, the report time aligns with the <span class="code">dateTime</span>
of the most recent archive record. When reports are run using the <span class="code">wee_reports</span>
utility, the report time is either the <span class="code">dateTime</span> of the most recent archive record
(the default) or the optional timestamp command line argument.
</p>
<p class="note" style="display: inline-block; width: 70%">
<strong>Note</strong><br/>The day a report is to be run can be specified by two parameters; day of month
and/or day of week. If both parameters are restricted (i.e., not an asterisk), the report will be run when
either field matches the current time. For example,<br/> <span class="code">report_timing = 30 4
1,15 * 5</span><br/> would cause the report to be run at 4:30am on the 1st and 15th of each month as well as
4:30am every Friday.
</p>
<h3>
The relationship between <span class="code">report_timing</span> and archive period </h3>
<p>
A traditional CRON service has a resolution of one minute, meaning that the CRON service checks each minute
as to whether to execute any commands. On the other hand, the WeeWX report system checks which reports are
to be run once per archive period, where the archive period may be one minute, five minutes or some other
user defined period. Consequently, the <span class="code">report_timing</span> option may specify a report
to be run at some time that does not align with the WeeWX archive period. In such cases cases the <span
class="code">report_timing</span> option does not cause a report to be run outside of the normal WeeWX
report cycle, rather it will cause the report to be run during the next report cycle. At the start of each
report cycle, and provided a <span class="code">report_timing</span> option is set, WeeWX will check each
minute boundary from the current report time back until the report time of the previous report cycle. If a
match is found on <strong>any</strong> of these one minute boundaries the report will be run during the
report cycle. This may be best described through some examples:
</p>
<table class="indent">
<tbody>
<tr class="first_row">
<td class="code">report_timing</td>
<td>Archive period</td>
<td>When the report will be run</td>
</tr>
<tr>
<td class="code first_col">0 * * * *</td>
<td>5 minutes</td>
<td>The report will be run only during the report cycle commencing on the hour.</td>
</tr>
<tr>
<td class="code first_col">5 * * * *</td>
<td>5 minutes</td>
<td>The report will be run only during the report cycle commencing at 5 minutes past the hour.</td>
</tr>
<tr>
<td class="code first_col">3 * * * *</td>
<td>5 minutes</td>
<td>The report will be run only during the report cycle commencing at 5 minutes past the hour.</td>
</tr>
<tr>
<td class="code first_col">10 * * * *</td>
<td>15 minutes</td>
<td>The report will be run only during the report cycle commencing at 15 minutes past the hour</td>
</tr>
<tr>
<td class="code first_col">10,40 * * * *</td>
<td>15 minutes</td>
<td>The report will be run only during the report cycles commencing at 15 minutes past the hour and 45
minutes past the hour.
</td>
</tr>
<tr>
<td class="code first_col">5,10 * * * *</td>
<td>15 minutes</td>
<td>The report will be run once only during the report cycle commencing at 15 minutes past the hour.
</td>
</tr>
</tbody>
</table>
<h3>Lists, ranges and steps</h3>
<p>
The <span class="code">report_timing</span> option supports lists, ranges and steps for all parameters.
Lists, ranges and steps may be used as follows:
</p>
<ul>
<li>Lists. A list is a set of numbers (or ranges) separated by commas, for example 1,2,5,9 or 0-4,8-12. A
match with any of the elements of the list will result in a match for that particular parameter. If the
examples were applied to the minutes parameter, and subject to other parameters in the <span
class="code">report_timing</span> option, the report would be run at minutes 1,2,5 and 9 and
0,1,2,3,4,8,9,10,11 and 12 respectively. Abbreviated month and day names cannot be used in a list.
</li>
<li>Ranges. Ranges are two numbers separated with a hyphen, for example 8-11. The specified range is
inclusive. A match with any of the values included in the range will result in a match for that
particular parameter. If the example was applied to the hours parameter, and subject to other parameters
in the <span class="code">report_timing</span> option, the report would be run at hours 8,9,10 and 11. A
range may be included as an element of a list. Abbreviated month and day names cannot be used in a
range.
</li>
<li>Steps. A step can be used in conjunction with a range or asterisk and are denoted by a '/' followed by a
number. Following a range with a step specifies skips of the step number's value through the range. For
example, 0-12/2 used in the hours parameter would, subject to other parameter in the <span class="code">report_timing</span>
option, run the report at hours 0,2,4,6,8,12. Steps are also permitted after an asterisk in which case
the skips of the step number's value occur through the all possible values of the parameter. For
example, */3 can be used in the hours parameter to, subject to other parameter in the <span
class="code">report_timing</span> option, run the report at hours 0,3,6,9,12,15,18 and 21.
</li>
</ul>
<h3>Nicknames</h3>
<p>
The <span class="code">report_timing</span> option supports a number of time specification 'nicknames'.
These nicknames are prefixed by the '@' character and replace the five parameters in the <span class="code">report_timing</span>
option. The nicknames supported are:
</p>
<table class="indent">
<tbody>
<tr class="first_row">
<td>Nickname</td>
<td>Equivalent setting
</td>
<td>When the report will be run</td>
</tr>
<tr>
<td class="code first_col">@yearly<br/>@annually
</td>
<td>0 0 1 1 *</td>
<td>Once per year at midnight on 1 January.</td>
</tr>
<tr>
<td class="code first_col">@monthly</td>
<td>0 0 1 * *</td>
<td>Monthly at midnight on the 1st of the month.</td>
</tr>
<tr>
<td class="code first_col">@weekly</td>
<td>0 0 * * 0</td>
<td>Every week at midnight on Sunday.</td>
</tr>
<tr>
<td class="code first_col">@daily</td>
<td>0 0 * * *</td>
<td>Every day at midnight.</td>
</tr>
<tr>
<td class="code first_col">@hourly</td>
<td>0 * * * *</td>
<td>Every hour on the hour.</td>
</tr>
</tbody>
</table>
<h3>
Examples of <span class="code">report_timing</span></h3>
<p>
Numeric settings for <span class="code">report_timing</span> can be at times difficult to understand due to
the complex combinations of parameters. The following table shows a number of example <span class="code">report_timing</span>
options and the corresponding times when the report would be run.
</p>
<table class="indent">
<tbody>
<tr class="first_row">
<td class="code">report_timing</td>
<td>When the report will be run</td>
</tr>
<tr>
<td class="code first_col">* * * * *</td>
<td>Every archive period. This setting is effectively the default WeeWX method of operation.
</td>
</tr>
<tr>
<td class="code first_col">25 * * * *</td>
<td>25 minutes past every hour.</td>
</tr>
<tr>
<td class="code first_col">0 * * * *</td>
<td>Every hour on the hour.</td>
</tr>
<tr>
<td class="code first_col">5 0 * * *</td>
<td>00:05 daily.</td>
</tr>
<tr>
<td class="code first_col">25 16 * * *</td>
<td>16:25 daily.</td>
</tr>
<tr>
<td class="code first_col">25 16 1 * *</td>
<td>16:25 on the 1st of each month.</td>
</tr>
<tr>
<td class="code first_col">25 16 1 2 *</td>
<td>16:25 on the 1st of February.</td>
</tr>
<tr>
<td class="code first_col">25 16 * * 0</td>
<td>16:25 each Sunday.</td>
</tr>
<tr>
<td class="code first_col">*/10 * * * *</td>
<td>On the hour and 10, 20, 30, 40 and 50 mnutes past the hour.
</td>
</tr>
<tr>
<td class="code first_col">*/9 * * * *</td>
<td>On the hour and 9, 18, 27, 36, 45 and 54 minutes past the hour.
</td>
</tr>
<tr>
<td class="code first_col">*/10 */2 * * *</td>
<td>0, 10, 20, 30, 40 and 50 minutes after the even hour.
</td>
</tr>
<tr>
<td class="code first_col">* 6-17 * * *</td>
<td>Every archive period from 06:00 (inclusive) up until, but excluding, 18:00.
</td>
</tr>
<tr>
<td class="code first_col">* 1,4,14 * * *</td>
<td>Every archive period in the hour starting 01:00 to 01:59, 04:00 to 04:59 amd 14:00 to 14:59 (Note
excludes report times at 02:00, 05:00 and 15:00).
</td>
</tr>
<tr>
<td class="code first_col">0 * 1 * 0,3</td>
<td>On the hour on the first of the month and on the hour every Sunday and Wednesday.
</td>
</tr>
<tr>
<td class="code first_col">*&nbsp;*&nbsp;21,1-10/3&nbsp;6&nbsp;*</td>
<td>Every archive period on the 1st, 4th, 7th, 10th and 21st of June.
</td>
</tr>
<tr>
<td class="code first_col">@monthly</td>
<td>Midnight on the 1st of the month.</td>
</tr>
</tbody>
</table>
<h3>
The <span class="code">wee_reports</span> utility and the <span class="code">report_timing</span> option
</h3>
<p>
The <span class="code">report_timing</span> option is ignored when using the <a href="#wee_reports">wee_reports</a>
utility.
</p>
<h1 id="customizing_templates">The Cheetah generator</h1>
<p class="note">
This section gives an overview of the Cheetah generator. For details about each of its various options,
see the section <a href="#CheetahGenerator"><em>[CheetahGenerator]</em></a> in the <a href="#report_options"><em>Reference: report options</em></a>.
</p>
<p>
File generation is done using the <a href="https://pythonhosted.org/Cheetah/">Cheetah</a> templating engine,
which processes a <em>template</em>, replacing any symbolic <em>tags</em>, 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 <span class="code">wee_reports</span> utility.
</p>
<p>
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.
</p>
<p>
The Cheetah generator is controlled by the section <a href="#CheetahGenerator"><span class="code">[CheetahGenerator]</span></a>. Let's take a look at how this works.
</p>
<h2>Which files get processed?</h2>
<p>
Each template file is named something like <span class="code"><em>D/F.E.tmpl</em></span>, where <span
class="code">D</span> is the (optional) directory the template sits in and will also be the directory the
results will be put in, and <span class="code">F.E</span> is the generated file name. So, given a template
file with name <span class="code">Acme/index.html.tmpl</span>, the results will be put in <span
class="symcode">HTML_ROOT</span><span class="code">/Acme/index.html</span>.
</p>
<p>
The configuration for a group of templates will look something like this:
</p>
<pre class="tty">[CheetahGenerator]
[[index]]
template = index.html.tmpl
[[textfile]]
template = filename.txt.tmpl
[[xmlfile]]
template = filename.xml.tmpl</pre>
<p>
There can be only one <span class="config_option">template</span> in each block. In most cases, the block
name does not matter &mdash; it is used only to isolate each template. However, there are four block names
that have special meaning: <span class="code">SummaryByDay</span>, <span class='code'>SummaryByMonth</span>,
<span class='code'>SummaryByYear</span>, and <span class='code'>ToDate</span>.
</p>
<h3>Specifying template files</h3>
<p>
By way of example, here is the <span class="code">[CheetahGenerator]</span> section from the <span
class="code">skin.conf</span> for the skin <em>Seasons</em>.
</p>
<pre class="tty">[CheetahGenerator]
# The CheetahGenerator creates files from templates. This section
# specifies which files will be generated from which template.
# Possible encodings are 'html_entities', 'utf8', or 'strict_ascii'
encoding = html_entities
[[SummaryByMonth]]
# Reports that summarize "by month"
[[[NOAA_month]]]
encoding = strict_ascii
template = NOAA/NOAA-YYYY-MM.txt.tmpl
[[SummaryByYear]]
# Reports that summarize "by year"
[[[NOAA_year]]]
encoding = strict_ascii
template = NOAA/NOAA-YYYY.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
</pre>
<p>The skin contains three different kinds of generated output:</p>
<ol>
<li>Summary by Month. The skin uses <span class="code">SummaryByMonth</span> to produce
NOAA summaries, one for each month, as a simple text file.
</li>
<li>Summary by Year. The skin uses <span class="code">SummaryByYear</span> to produce NOAA summaries, one
for each year, as a simple text file.
</li>
<li>Summary "To Date". The skin produces an HTML <span class="code">index.html</span> page,
as well as HTML files for detailed statistics, telemetry, and celestial information. It also includes a
master page (<span class="code">tabular.html</span>) in which NOAA information is displayed. All these
files are HTML.
</li>
</ol>
<p>
The encoding for text files is <span class="code">strict_ascii</span>, whereas the encoding for html files
is <span class="code">html_entities</span>. This was specified by declaring <span class="code">encoding = html_entities</span>
at the top level of <span class="code">[CheetahGenerator]</span> then <span class="code">encoding = strict_ascii</span>
for each text file.
</p>
<p>
Other than <span class="code">SummaryByMonth</span> and <span class="code">SummaryByYear</span>, the section
names are arbitrary. <span class="code">ToDate</span> could just as well have been called <span
class="code">files_to_date</span>, and the sections <span class="code">day</span>, <span
class="code">week</span>, and <span class="code">month</span> could just as well have been called <span
class="code">tom</span>, <span class="code">dick</span>, and <span class="code">harry</span>.
</p>
<h3><span class="code" id="SummaryByYear">[[SummaryByYear]]</span></h3>
<p>
Use <span class="code">SummaryByYear</span> to generate a set of files, one file per year. The name of the template file should contain <span class="code">YYYY</span>; this will be replaced with the year of the data in the file.
</p>
<pre class="tty">
[CheetahGenerator]
[[SummaryByYear]]
# Reports that summarize "by year"
[[[NOAA_year]]]
encoding = strict_ascii
template = NOAA/NOAA-YYYY.txt.tmpl
</pre>
<p>
The template <span class="code">NOAA/NOAA-YYYY.txt.tmpl</span> might look something like this:
</p>
<pre class="tty">
SUMMARY FOR YEAR $year.dateTime
MONTHLY TEMPERATURES AND HUMIDITIES:
#for $record in $year.records
$record.dateTime $record.outTemp $record.outHumidity
#end for
</pre>
<h3><span class="code" id="SummaryByMonth">[[SummaryByMonth]]</span></h3>
<p>
Use <span class="code">SummaryByMonth</span> to generate a set of files, one file per month. The name of the template file should contain <span class="code">YYYY</span> and <span class="code">MM</span>; these will be replaced with the year and month of the data in the file.
</p>
<pre class="tty">
[CheetahGenerator]
[[SummaryByMonth]]
# Reports that summarize "by month"
[[[NOAA_month]]]
encoding = strict_ascii
template = NOAA/NOAA-YYYY-MM.txt.tmpl
</pre>
<p>
The template <span class="code">NOAA/NOAA-YYYY-MM.txt.tmpl</span> might look something like this:
</p>
<pre class="tty">
SUMMARY FOR MONTH $month.dateTime
DAILY TEMPERATURES AND HUMIDITIES:
#for $record in $month.records
$record.dateTime $record.outTemp $record.outHumidity
#end for
</pre>
<h3><span class="code" id="SummaryByDay">[[SummaryByDay]]</span></h3>
<p>
While the <em>Seasons</em> skin does not make use of it, there is also a <span
class="code">SummaryByDay</span> capability. As the name suggests, this results in one file per day. The name of the template file should contain <span class="code">YYYY</span>, <span class="code">MM</span>, and <span class="code">DD</span>; these will be replaced with the year, month, and day of the data in the file.
</p>
<pre class="tty">
[CheetahGenerator]
[[SummaryByDay]]
# Reports that summarize "by day"
[[[NOAA_day]]]
encoding = strict_ascii
template = NOAA/NOAA-YYYY-MM-DD.txt.tmpl
</pre>
<p>
The template <span class="code">NOAA/NOAA-YYYY-MM-DD.txt.tmpl</span> might look something like this:
</p>
<pre class="tty">
SUMMARY FOR DAY $day.dateTime
HOURLY TEMPERATURES AND HUMIDITIES:
#for $record in $day.records
$record.dateTime $record.outTemp $record.outHumidity
#end for
</pre>
<p class="note">
<strong>Note</strong><br/>
This can create a <em>lot</em> of files &mdash; one per day. If you have 3 years of records, this would
be more than 1,000 files!
</p>
<h2>Tags</h2>
<p>
If you look inside a template, you will see it makes heavy use of <em>tags</em>. 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.
</p>
<p class="note">
If there is a tag error during template generation, the error will show up in the log file. Many errors
are obvious &mdash; 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 <a href="#wee_reports"><span
class="code">wee_reports</span></a> to speed up the process.
</p>
<p>Here are some examples of tags:</p>
<pre class="tty">$current.outTemp
$month.outTemp.max
$month.outTemp.maxtime</pre>
<p>
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:
</p>
<pre class="tty">&lt;html&gt;
&lt;head&gt;
&lt;title&gt;Current conditions&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;p&gt;Current temperature = $current.outTemp&lt;/p&gt;
&lt;p&gt;Max for the month is $month.outTemp.max, which occurred at $month.outTemp.maxtime&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
<p>
would be all you need for a very simple HTML page that would display the text (assuming that the unit group
for temperature is <span class="code">degree_F</span>):
</p>
<p class="example_output">
Current temperature = 51.0°F <br/> Max for the month is 68.8°F, which occurred at 07-Oct-2009 15:15
</p>
<p>
The format that was used to format the temperature (<span class="code">51.0</span>) is specified in section
<span class="code"><a href="#Units_StringFormats">[Units][[StringFormat]]</a></span>. The unit label <span
class="code">°F</span> is from section <span class="code"><a
href="#Units_Labels">[Units][[Labels]]</a></span>, while the time format is from <span class="code"><a
href="#Units_TimeFormats">[Units][[TimeFormats]]</a></span>.
</p>
<p>As we saw above, the tags can be very simple:</p>
<pre class="tty">## Output max outside temperature using an appropriate format and label:
$month.outTemp.max</pre>
<p>
Most of the time, tags will "do the right thing" and are all you will need. However, WeeWX offers extensive
customization of the generated output for specialized applications such as XML RSS feeds, or ridgidly
formatted reports (such as the NOAA reports). This section specifies the various tag options available.
</p>
<p>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.
</p>
<h3>
Time period <span class="code">$current</span>
</h3>
<p>
Time period <span class="code">$current</span> represents a <em>current observation</em>. An example would
be the current barometric pressure:
</p>
<pre class="tty">$current.barometer</pre>
<p>
Formally, WeeWX first looks for the observation type in the record emitted by the <span class="code">NEW_ARCHIVE_RECORD</span>
event. This is generally the data emitted by the station console, augmented by any derived variables (<i>e.g.</i>
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.
</p>
<p>The most general tag for a "current" observation looks like:</p>
<pre
class="tty">$current($data_binding=<em>binding_name</em>).<em>obstype</em>[.<em>optional_unit_conversion</em>][.<em>optional_formatting</em>]</pre>
<p>Where:</p>
<p class="indent">
<span class="code">binding_name</span> is a <em>binding name</em> to a database. An example would be <span
class="code">wx_binding</span>. See the section <em><a href="#binding_names">Binding names</a></em> for more
details.
</p>
<p class="indent">
<span class="code">obstype</span> is an observation type, such as <span class="code">barometer</span>. See
<em><a href="#archive_types">Appendix: Archive Types</a></em> for a table of observation types valid for
time period <span class="code">current.</span>
</p>
<p class="indent">
<span class="code">optional_unit_conversion</span> 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 <span class="code">[Units][[Groups]]</span>) will be used. See the section
<em><a href="#unit_conversion_options">Unit Conversion Options</a></em>.
</p>
<p class="indent">
<span class="code">optional_formatting</span> is a set of optional formatting tags, which control how the
value will appear. See the section <em><a href="#formatting_options">Formatting Options</a></em> below.
</p>
<h3>
Time period <span class="code">$latest</span>
</h3>
<p>
Time period <span class="code">$latest</span> is very similar to <span class="code">$current</span>, except
that it uses the last available timestamp in a database. Usually, <span class="code">$current</span> and
<span class="code">$latest</span> are the same, but if a data binding points to a remote database, they may
not be. See the section <em><a href="#stupid_detail">Using multiple bindings</a></em> for an example where
this happened.
</p>
<h3 id="general_aggregation_periods">Aggregation periods</h3>
<p>Aggregation periods is the other kind of tag. For example,</p>
<pre class="tty">$week.rain.sum</pre>
<p>
represents an <em>aggregation over time</em>, using a certain <em>aggregation type</em>. 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.
</p>
<p>The most general tag for an aggregation over time looks like:</p>
<pre
class="tty">$<em>period</em>($data_binding=<em>binding_name</em>, <em>$optional_ago</em>=<em>delta</em>).<em>statstype</em>.<em>aggregation</em>[.<em>optional_unit_conversion</em>][.<em>optional_formatting</em>]</pre>
<p>Where:</p>
<p class="indent">
<span class="code">period</span> is the time period over which the aggregation is to be done. Possible
choices are listed in a table below.
</p>
<p class="indent">
<span class="code">binding_name</span> is a <em>binding name</em> to a database. An example would be <span
class="code">wx_binding</span>. See the section <em><a href="#binding_names">Binding names</a></em> for more
details.
</p>
<p class="indent">
<span class="code"><em>optional_ago</em></span> is a keyword that depends on the aggregation period. For
example, for <span class="code">week</span>, it would be <span class="code">weeks_ago</span>, for <span
class="code">day</span>, it would be <span class="code">days_ago</span>, <i>etc.</i>
</p>
<p class="indent">
<span class="code">delta</span> is an integer indicating which aggregation period is desired. For example
<span class="code">$week($weeks_ago=1)</span> indicates last week, <span
class="code">$day($days_ago=2)</span> would be the day-before-yesterday, <i>etc</i>. The default is zero:
that is, this aggregation period.
</p>
<p class="indent">
<span class="code">statstype</span> is a <em>statistical type</em>. This is generally any observation type
that appears in the database, as well as a few synthetic types (such as heating and cooling degree-days).
Not all aggregations are supported for all types.
</p>
<p class="indent">
<span class="code">aggregation</span> is an <em>aggregation type</em>. If you ask for <span class="code">$month.outTemp.avg</span>
you are asking for the <em>average</em> outside temperature for the month. Possible aggregation types are
given in <em><a href="#aggregation_types">Appendix: Aggregation types</a></em>.
</p>
<p class="indent">
<span class="code">optional_unit_conversion</span> 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 <span class="code">[Units][[Groups]]</span>) will be used. See the section
<em><a href="#unit_conversion_options">Unit Conversion Options</a></em>.
</p>
<p class="indent">
<span class="code">optional_formatting</span> is a set of optional formatting tags, which control how the
value will appear. See the section <em><a href="#formatting_options">Formatting Options</a></em> below.
</p>
<p>
There are several different <em>aggregation periods</em> that can be used:
</p>
<table class="indent" style="width: 80%">
<tbody>
<tr class="first_row">
<td>Aggregation period</td>
<td>Meaning</td>
<td>Example</td>
<td>Meaning of example</td>
</tr>
<tr>
<td class="first_col code">$hour</td>
<td>This hour.</td>
<td class="code">$hour.outTemp.maxtime</td>
<td>The time of the max temperature this hour.</td>
</tr>
<tr>
<td class="first_col code">$day</td>
<td>Today (since midnight).</td>
<td class="code">$day.outTemp.max</td>
<td>The max temperature since midnight</td>
</tr>
<tr>
<td class="first_col code">$yesterday</td>
<td>Yesterday. Synonym for <span class="code">$day($days_ago=1)</span>.
</td>
<td class="code">$yesterday.outTemp.maxtime</td>
<td>The time of the max temperature yesterday.</td>
</tr>
<tr>
<td class="first_col code">$week</td>
<td>This week. The start of the week is set by option <a href="usersguide.htm#week_start"><span
class="code">week_start</span></a>.
</td>
<td class="code">$week.outTemp.max</td>
<td>The max temperature this week.</td>
</tr>
<tr>
<td class="first_col code">$month</td>
<td>This month.</td>
<td class="code">$month.outTemp.min</td>
<td>The minimum temperature this month.</td>
</tr>
<tr>
<td class="first_col code">$year</td>
<td>This year.</td>
<td class="code">$year.outTemp.max</td>
<td>The max temperature since the start of the year.</td>
</tr>
<tr>
<td class="first_col code">$rainyear</td>
<td>This rain year. The start of the rain year is set by option <a
href="usersguide.htm#rain_year_start"><span class="code">rain_year_start</span></a>.
</td>
<td class="code">$rainyear.rain.sum</td>
<td>The total rainfall for this rain year. The start of the rain year is set by option <a
href="usersguide.htm#rain_year_start"><span class="code">rain_year_start</span></a>.
</td>
</tr>
</tbody>
</table>
<p>
The <em>$optional_ago</em> parameters can be useful for statistics farther in the past. Here are some
examples:
</p>
<table class="indent" style="width: 80%">
<tbody>
<tr class="first_row">
<td>Aggregation period</td>
<td>Example</td>
<td>Meaning</td>
</tr>
<tr>
<td class="first_col code">$hour($hours_ago=<em>h</em>)
</td>
<td class="code">$hour($hours_ago=1).outTemp.avg</td>
<td>The average temperature last hour (1 hour ago).</td>
</tr>
<tr>
<td class="first_col code">$day($days_ago=<em>d</em>)
</td>
<td class="code">$day($days_ago=2).outTemp.avg</td>
<td>The average temperature day before yesterday (2 days ago).
</td>
</tr>
<tr>
<td class="first_col code">$week($weeks_ago=<em>d</em>)
</td>
<td class="code">$week($weeks_ago=1).outTemp.max</td>
<td>The maximum temperature last week.</td>
</tr>
<tr>
<td class="first_col code">$month($months_ago=<em>m</em>)
</td>
<td class="code">$month($months_ago=1).outTemp.max</td>
<td>The maximum temperature last month.</td>
</tr>
<tr>
<td class="first_col code">$year($years_ago=<em>m</em>)
</td>
<td class="code">$year($years_ago=1).outTemp.max</td>
<td>The maximum temperature last year.</td>
</tr>
</tbody>
</table>
<h3 id="unit_conversion_options">Unit conversion options</h3>
<p>
The tag <span class="code">optional_unit_conversion</span> can be used with either current observations or
aggregations. If supplied, the results will be converted to the specified units. For example, if you have
set <span class="code">group_pressure</span> to inches of mercury (<span class="code">inHg</span>), then the
tag
</p>
<pre class="tty">Today's average pressure=$day.barometer.avg </pre>
<p>would normally give a result such as</p>
<p class="example_output">Today's average pressure=30.05 inHg
</p>
<p>
However, if you add <span class="code">mbar</span> to the end,
</p>
<pre class="tty">$day.barometer.avg.mbar </pre>
<p>then the results will be in millibars:</p>
<p class="example_output">Today's average pressure=1017.5 mbar
</p>
<h4>Illegal conversions</h4>
<p>
If an inappropriate or nonsense conversion is asked for, <i>e.g.</i>,
</p>
<pre class="tty">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
</pre>
<p>then the offending tag(s) will be put in the output:</p>
<p class="example_output">
Today's minimum pressure in mbars: 1015.3<br/> or in degrees C: $day.barometer.min.degree_C<br/> or in
foobar units: $day.barometer.min.foobar
</p>
<h3 id="formatting_options">Formatting options</h3>
<p>
A variety of tags and arguments are available to you to customize the formatting of the final observation
value. This table lists the tags:
</p>
<table class="indent">
<caption>Optional formatting tag</caption>
<tbody>
<tr class="first_row">
<td>Optional formatting tag</td>
<td>Comment</td>
</tr>
<tr>
<td class="code text_highlight">.format(<em>args</em>)</td>
<td>Format the string according to a set of optional <em>args</em> (see below).</td>
</tr>
<tr>
<td class="code text_highlight">.ordinal_compass</td>
<td>Output compass ordinals for wind direction (<i>e.g.</i>"SW"). The ordinal abbreviations are set by
option <span class="code">directions</span> in the skin configuration file <span class="code">skin.conf</span>.
</td>
</tr>
<tr>
<td class="code text_highlight">.raw</td>
<td>Value is returned "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 <span class="code">None</span> value.
</td>
</tr>
</tbody>
</table>
<p>The first of these tags has the formal structure:</p>
<pre class="tty">.format(format_string=None, None_string=None, add_label=True, localize=True)</pre>
<p>Here is the meaning of each of the optional arguments:</p>
<table class="indent">
<caption>Optional arguments for <span class="code">.format()</span></caption>
<tbody>
<tr class="first_row">
<td>Optional argument</td>
<td>Comment</td>
</tr>
<tr>
<td class='code text_highlight'>format_string</td>
<td>Use the optional string to format the value. If set to <span class="code">None</span>, then an
appropriate string format from <span class="code">skin.conf</span> will be used.
</td>
</tr>
<tr>
<td class="code text_highlight">None_string</td>
<td>Should the observation value be <span class="code">NONE</span>, then use the supplied string
(typically, something like "N/A"). If <span class="code">None_string</span> is set to <span
class="code">None</span>, then the value for <span class="code">NONE</span> in <span
class="code"> <a href="#Units_StringFormats">[Units][[StringFormats]]</a>
</span> will be used.
</td>
</tr>
<tr>
<td class="code text_highlight">add_label</td>
<td>If set to <span class="code">True</span> (the default), then a unit label (<i>e.g.</i>, &deg;F) from
<span class="code">skin.conf</span> will be attached to the end. Otherwise, it will be left out.
</td>
</tr>
<tr>
<td class="code text_highlight">localize</td>
<td>If set to <span class="code">True</span> (the default), then localize the results. Otherwise, do
not.
</td>
</tr>
</tbody>
</table>
<p>If you're willing to honor the ordering of the arguments, the argument name can be omitted.</p>
<h3>Formatting examples</h3>
<p>This section gives a number of example tags, and their expected output. The following values are assumed:</p>
<table class="indent" style="width:50%">
<caption>Values used in the examples below</caption>
<tr class="first_row">
<td>Observation</td>
<td>Value</td>
</tr>
<tr>
<td class="code first_col">
outTemp
</td>
<td>45.2&deg;F</td>
</tr>
<tr>
<td class="code first_col">
UV
</td>
<td>
<span class="code">None</span>
</td>
</tr>
<tr>
<td class="code first_col">
windDir
</td>
<td>138&deg;</td>
</tr>
<tr>
<td class="code first_col">
dateTime
</td>
<td>1270250700</td>
</tr>
</table>
<p>Here are the examples:</p>
<table class="indent">
<caption>Formatting options with expected results</caption>
<tbody>
<tr class="first_row">
<td>Tag</td>
<td>Result</td>
<td>Comment</td>
</tr>
<tr>
<td class="code first_col">$current.outTemp</td>
<td class="code">45.2°F</td>
<td>String formatting from <span class="code"><a
href="#Units_StringFormats">[Units][[StringFormats]]</a></span>. Label from <span class="code"><a
href="#Units_Labels">[Units][[Labels]]</a></span>.
</td>
</tr>
<tr>
<td class="code first_col">$current.outTemp.format</td>
<td class="code">45.2°F</td>
<td>Same as the <span class="code">$current.outTemp</span>.</td>
</tr>
<tr>
<td class="code first_col">$current.outTemp.format()</td>
<td class="code">45.2°F</td>
<td>Same as the <span class="code">$current.outTemp</span>.</td>
</tr>
<tr>
<td class="code first_col">$current.outTemp.format(format_string="%.3f")</td>
<td class="code">45.200°F</td>
<td>Specified string format used; label from <span class="code"><a href="#Units_Labels">[Units][[Labels]]</a></span>.
</td>
</tr>
<tr>
<td class="code first_col">$current.outTemp.format("%.3f")</td>
<td class="code">45.200°F</td>
<td>As above, except a positional argument, instead of the named argument, is being used.
</td>
</tr>
<tr>
<td class="code first_col">$current.outTemp.format(add_label=False)</td>
<td class="code">45.2</td>
<td>No label. The string formatting is from <span class="code"><a href="#Units_StringFormats">[Units][[StringFormats]]</a></span>.
</td>
</tr>
<tr>
<td class="code first_col">$current.UV</td>
<td class="code">N/A</td>
<td>The string specified by option <span class="code">NONE</span> in <span class="code"> <a
href="#Units_StringFormats">[Units][[StringFormats]]</a></span>.
</td>
</tr>
<tr>
<td class="code first_col">$current.UV.format(None_string="No UV")</td>
<td class="code">No UV</td>
<td>Specified <span class="code">None_string</span> is used.
</td>
</tr>
<tr>
<td class="code first_col">$current.windDir</td>
<td class="code">138&deg;</td>
<td>Formatting is from option <span class="code">degree_compass</span> in <span class="code"> <a
href="#Units_StringFormats">[Units][[StringFormats]]</a></span>.
</td>
</tr>
<tr>
<td class="code first_col">$current.windDir.ordinal_compass</td>
<td class="code">SW</td>
<td>Ordinal direction from section <span class="code"><a
href="#Units_Ordinates">[Units][[Ordinates]]</a></span> is being substituted.
</td>
</tr>
<tr>
<td class="code first_col">$current.dateTime</td>
<td class="code">02-Apr-2010 16:25</td>
<td>Time formatting from <span class="code"><a
href="#Units_TimeFormats">[Units][[TimeFormats]]</a></span> is being used.
</td>
</tr>
<tr>
<td class="code first_col">$current.dateTime.format(format_string="%H:%M")</td>
<td class="code">16:25</td>
<td>Specified time format used.</td>
</tr>
<tr>
<td class="code first_col">$current.dateTime.format("%H:%M")</td>
<td class="code">16:25</td>
<td>As above, except a positional argument, instead of the named argument, is being used.</td>
</tr>
<tr>
<td class="code first_col">$current.dateTime.raw</td>
<td class="code">1270250700</td>
<td>Raw Unix epoch time (which is converted to a string by the template engine).
</td>
</tr>
<tr>
<td class="code first_col">$current.outTemp.raw</td>
<td class="code">45.2</td>
<td>Raw float value (which is converted to a string by the template engine).
</td>
</tr>
</tbody>
</table>
<p>Note that the same formatting conventions can be used for aggregation periods, such as <span class="code">$month</span>,
as well as <span class="code">$current</span>.
</p>
<h3>
Start, end, and <span class="code">dateTime</span>
</h3>
<p>
While not an observation type, in many ways the time of an observation, <span class="code">dateTime</span>,
can be treated as one. A tag such as
</p>
<pre class="tty">$current.dateTime</pre>
<p>
represents the <em>current time</em> (more properly, the time as of the end of the last archive interval)
and would produce something like
</p>
<pre class="example_output">01/09/2010 12:30:00</pre>
<p>
Like true observation types, explicit formats can be specified, except that they require a <a
href="https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior"> strftime() <em>time
format</em> </a>, rather than a <em>string format</em>.
</p>
<p>For example, adding a format descriptor like this:</p>
<pre class="tty">$current.dateTime.format("%d-%b-%Y %H:%M")</pre>
<p>produces</p>
<p class="example_output">09-Jan-2010 12:30</p>
<p>
For <em>aggregation periods</em>, such as <span class="code">$month</span>, you can request the
<em>start</em> or <em>end</em> of the period, by using suffixes <span class="code">.start</span> or <span
class="code">.end</span>, respectively. For example,
</p>
<pre class="tty">The current month runs from $month.start to $month.end.</pre>
<p>results in</p>
<pre class="example_output">The current month runs from 01/01/2010 12:00:00 AM to 02/01/2017 12:00:00 AM.</pre>
<p>
In addition to the suffixes <span class="code">.start</span> and <span class="code">.end</span>, the suffix
<span class="code">.dateTime</span> is provided for backwards compatibility. Like <span
class="code">.start</span>, it refers to the start of the interval.
</p>
<p>
The returned string values will always be in <em>local time</em>. However, if you ask for the raw value
</p>
<pre class="tty">$current.dateTime.raw</pre>
<p>
the returned value will be in Unix Epoch Time (number of seconds since 00:00:00 UTC 1 Jan 1970,
<em>i.e.</em>, a large number), which you must convert yourself. It is guaranteed to never be <span
class="code">None</span>, so you don't worry have to worry about handling a <span class="code">None</span>
value.
</p>
<h3>
Tag <span class="code">$trend</span>
</h3>
<p>
The tag <span class="code">$trend</span> is available for time trends, such as changes in barometric
pressure. Here are some examples:
</p>
<table class="indent" style="width: 50%">
<tbody>
<tr class="first_row">
<td>Tag</td>
<td>Results</td>
</tr>
<tr>
<td class="code first_col">$trend.barometer</td>
<td class="code">-.05 inHg</td>
</tr>
<tr>
<td class="code first_col">$trend($time_delta=3600).barometer</td>
<td class="code">-.02 inHg</td>
</tr>
<tr>
<td class="code first_col">$trend.outTemp</td>
<td class="code">1.1 °C</td>
</tr>
<tr>
<td class="code first_col">$trend.time_delta</td>
<td class="code">10800 secs</td>
</tr>
<tr>
<td class="code first_col">$trend.time_delta.hour</td>
<td class="code">3 hrs</td>
</tr>
</tbody>
</table>
<p>
Note how you can explicitly specify a value 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 <span class="code"><a href="#trend">time_delta</a></span>
in the skin configuration file, will be used. This value can be retrieved by using the syntax <span
class="code">$trend.time_delta</span> (3rd row in the table).
</p>
<p>For example, the template expression</p>
<pre class="tty">The barometer trend over $trend.time_delta.hour is $trend.barometer.format("%+.2f")</pre>
<p>would result in</p>
<p class="example_output">The barometer trend over 3 hrs is +.03 inHg.</p>
<h3>
Tag <span class="code">$span</span>
</h3>
<p>
The tag <span class="code">$span</span> allows aggregation over a user defined period up to and including
the current time. Its most general form looks like:
</p>
<pre class="tty">$<em>span</em>([$data_binding=<em>binding_name</em>][,<em>$optional_delta</em>=<em>delta</em>]).<em>obstype</em>.<em>aggregation</em>[.<em>optional_unit_conversion</em>][.<em>optional_formatting</em>]</pre>
<p>Where:</p>
<p class="indent">
<span class="code">binding_name</span> is a <em>binding name</em> to a database. An example would be <span
class="code">wx_binding</span>. See the section <em><a href="#binding_names">Binding names</a></em> for more
details.
</p>
<p class="indent">
<span class="code"><em>$optional_delta</em>=<em>delta</em></span> 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 <span class="code">obstype</span>
only.
</p>
<p class="indent">
<span class="code">obstype</span> is a observation type, such as <span class="code">outTemp</span>, that is
supported by the <span class="code">$current</span> tag. See <em><a href="#archive_types">Appendix: Archive
Types</a></em> for a table of observation types supported by the <span class="code">$current</span> tag.
</p>
<p class="indent">
<span class="code">aggregation</span> is an <em>aggregation type</em>. Possible aggregation types are given
in <em><a href="#aggregation_types"> Appendix: Aggregation types</a></em>.
</p>
<p class="indent">
<span class="code">optional_unit_conversion</span> is an optional unit conversion tag. See the section
<em><a href="#unit_conversion_options"> Unit Conversion Options</a></em>.
</p>
<p class="indent">
<span class="code">optional_formatting</span> is an optional formatting tag that controls how the value will
appear. See the section <em><a href="#formatting_options">Formatting Options</a></em>.
</p>
<p>There are several different delta settings that can be used:</p>
<table class="indent">
<tbody>
<tr class="first_row">
<td>Delta Setting</td>
<td>Example</td>
<td>Meaning</td>
</tr>
<tr>
<td class="first_col code">$time_delta=<em>seconds</em></td>
<td class="code">$span($time_delta=1800).outTemp.avg</td>
<td>The average temperature over the last immediate 30 minutes (1800 seconds).
</td>
</tr>
<tr>
<td class="first_col code">$hour_delta=<em>hours</em></td>
<td class="code">$span($hour_delta=6).outTemp.avg</td>
<td>The average temperature over the last immediate 6 hours.
</td>
</tr>
<tr>
<td class="first_col code">$day_delta=<em>days</em></td>
<td class="code">$span($day_delta=1).rain.sum</td>
<td>The total rainfall over the last immediate 24 hours.
</td>
</tr>
<tr>
<td class="first_col code">$week_delta=<em>weeks</em></td>
<td class="code">$span($week_delta=2).barometer.max</td>
<td>The maximum barometric pressure over the last immediate 2 weeks.
</td>
</tr>
<tr>
<td class="first_col code">$month_delta=<em>months</em></td>
<td class="code">$span($month_delta=3).outTemp.min</td>
<td>The minimum temperture over the last immediate 3 months (90 days).
</td>
</tr>
<tr>
<td class="first_col code">$year_delta=<em>years</em></td>
<td class="code">$span($year_delta=1).windchill.min</td>
<td>The minimum wind chill over the last immediate 1 year (365 days).
</td>
</tr>
</tbody>
</table>
<p>For example, the template expressions</p>
<pre class="tty">The total rainfall over the last 30 hours is $span($hour_delta=30).rain.sum</pre>
<p>and</p>
<pre class="tty">The total rainfall over the last 30 hours is $span($hour_delta=6, $day_delta=1).rain.sum</pre>
<p>would both result in</p>
<p class="example_output">The total rainfall over the last 30 hours is 1.24 in</p>
<h3>
Tag <span class="code">$unit</span>
</h3>
<p>The type, label, and string formats for all units are also available, allowing you to do highly customized
labels:
</p>
<table class="indent" style="width: 50%">
<tbody>
<tr class="first_row">
<td>Tag</td>
<td>Results</td>
</tr>
<tr>
<td class="code first_col">$unit.unit_type.outTemp</td>
<td class="code">degree_C</td>
</tr>
<tr>
<td class="code first_col">$unit.label.outTemp</td>
<td class="code">°C</td>
</tr>
<tr>
<td class="code first_col">$unit.format.outTemp</td>
<td class="code">%.1f</td>
</tr>
</tbody>
</table>
<p>For example, the tag</p>
<pre class="tty">$day.outTemp.max.format(add_label=False)$unit.label.outTemp</pre>
<p>would result in</p>
<p class="example_output">21.2°C</p>
<p>
(assuming metric values have been specified for <span class="code">group_temperature</span>), essentially
reproducing the results of the simpler tag <span class="code">$day.outTemp.max</span>.
</p>
<h3>
Tag <span class="code">$obs</span>
</h3>
<p>
The labels used for the various observation types are available using tag <span class="code">$obs</span>.
These are basically the values given in the skin dictionary, section <a href="#Labels_Generic"><span
class="code">[Labels][[Generic]]</span></a>.
</p>
<table class="indent" style="width: 50%">
<tbody>
<tr class="first_row">
<td>Tag</td>
<td>Results</td>
</tr>
<tr>
<td class="code first_col">$obs.label.outTemp</td>
<td class="code">Outside Temperature</td>
</tr>
<tr>
<td class="code first_col">$obs.label.UV</td>
<td class="code">UV Index</td>
</tr>
</tbody>
</table>
<h3>Iteration</h3>
<p>It is possible to iterate over the following:</p>
<table class="indent" style="Width: 50%">
<tbody>
<tr class="first_row">
<td>Tag suffix</td>
<td>Results</td>
</tr>
<tr>
<td class="code first_col">.records</td>
<td>Iterate over every record</td>
</tr>
<tr>
<td class="code first_col">.hours</td>
<td>Iterate by hours</td>
</tr>
<tr>
<td class="code first_col">.days</td>
<td>Iterate by days</td>
</tr>
<tr>
<td class="code first_col">.months</td>
<td>Iterate by months</td>
</tr>
<tr>
<td class="code first_col">.years</td>
<td>Iterate by years</td>
</tr>
<tr>
<td class="code first_col">.spans(interval=<em>seconds</em>)
</td>
<td>Iterate by custom length spans. The default interval is 10800 seconds (3 hours). The spans will
align to local time boundaries.
</td>
</tr>
</tbody>
</table>
<p>
The following template uses a Cheetah <span class="code">for</span> loop to iterate over all months in a
year, printing out each month's min and max temperature. The iteration loop is <span class="highlight">&nbsp;highlighted&nbsp;</span>.
</p>
<pre class="tty">Min, max temperatures by month
<span class="highlight">#for $month in $year.months</span>
$month.dateTime.format("%B"): Min, max temperatures: $month.outTemp.min $month.outTemp.max
<span class="highlight">#end for</span>
</pre>
<p>The result is:</p>
<p class="example_output">
Min, max temperatures by month:<br/> January: Min, max temperatures: 30.1°F 51.5°F<br/> February: Min, max
temperatures: 24.4°F 58.6°F<br/> March: Min, max temperatures: 27.3°F 64.1°F<br/> April: Min, max
temperatures: 33.2°F 52.5°F<br/> May: Min, max temperatures: N/A N/A<br/> June: Min, max temperatures: N/A
N/A<br/> July: Min, max temperatures: N/A N/A<br/> August: Min, max temperatures: N/A N/A<br/> September:
Min, max temperatures: N/A N/A<br/> October: Min, max temperatures: N/A N/A<br/> November: Min, max
temperatures: N/A N/A<br/> December: Min, max temperatures: N/A N/A
</p>
<p>
The following template again uses a Cheetah <span class="code">for</span> loop, this time to iterate over
3-hour spans over the last 24 hours, displaying the averages in each span. The iteration loop is <span
class="highlight">&nbsp;highlighted&nbsp;</span>.
</p>
<pre class="tty">&lt;p&gt;3 hour averages over the last 24 hours&lt;/p&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;Date/time&lt;/td&gt;&lt;td&gt;outTemp&lt;/td&gt;&lt;td&gt;outHumidity&lt;/td&gt;
&lt;/tr&gt;
<span class="highlight">#for $_span in $span($day_delta=1).spans(interval=10800)</span>
&lt;tr&gt;
&lt;td&gt;$_span.start.format("%d/%m %H:%M")&lt;/td&gt;&lt;td&gt;$_span.outTemp.avg&lt;/td&gt;&lt;td&gt;$_span.outHumidity.avg&lt;/td&gt;
&lt;/tr&gt;
<span class="highlight">#end for</span>
&lt;/table&gt;
</pre>
<p>The result is:</p>
<div class="example_output">
<p>3 hour averages over the last 24 hours</p>
<table>
<tr>
<td>Date/time</td>
<td>outTemp</td>
<td>outHumidity</td>
</tr>
<tr>
<td>21/01 18:50</td>
<td>33.4&#176;F</td>
<td>95%</td>
</tr>
<tr>
<td>21/01 21:50</td>
<td>32.8&#176;F</td>
<td>96%</td>
</tr>
<tr>
<td>22/01 00:50</td>
<td>33.2&#176;F</td>
<td>96%</td>
</tr>
<tr>
<td>22/01 03:50</td>
<td>33.2&#176;F</td>
<td>96%</td>
</tr>
<tr>
<td>22/01 06:50</td>
<td>33.8&#176;F</td>
<td>96%</td>
</tr>
<tr>
<td>22/01 09:50</td>
<td>36.8&#176;F</td>
<td>95%</td>
</tr>
<tr>
<td>22/01 12:50</td>
<td>39.4&#176;F</td>
<td>91%</td>
</tr>
<tr>
<td>22/01 15:50</td>
<td>35.4&#176;F</td>
<td>93%</td>
</tr>
</table>
</div>
<p>
See the NOAA template files <span class="code">NOAA/NOAA-YYYY.txt.tmpl</span> and <span class="code">NOAA/NOAA-YYYY-MM.txt.tmpl</span>
for other examples using iteration, as well as explicit formatting.
</p>
<h3>Comprehensive example</h3>
<p>
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.
</p>
<pre class="tty">
&lt;html&gt;
&lt;head&gt;
&lt;style&gt;
td { border: 1px solid #cccccc; padding: 5px; }
&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;table border=1 style="border-collapse:collapse;"&gt;
&lt;tr style="font-weight:bold"&gt;
&lt;td&gt;Time interval&lt;/td&gt;
&lt;td&gt;Max temperature&lt;/td&gt;
&lt;td&gt;Time&lt;/td&gt;
&lt;/tr&gt;
#for $hour in $day($days_ago=1).hours
&lt;tr&gt;
&lt;td&gt;$hour.start.format("%H:%M")-$hour.end.format("%H:%M")&lt;/td&gt;
&lt;td&gt;$hour.outTemp.max ($hour.outTemp.max.degree_C)&lt;/td&gt;
&lt;td&gt;$hour.outTemp.maxtime.format("%H:%M")&lt;/td&gt;
&lt;/tr&gt;
#end for
&lt;caption&gt;
&lt;p&gt;
Hourly max temperatures yesterday&lt;br/&gt;
$day($days_ago=1).start.format("%d-%b-%Y")
&lt;/p&gt;
&lt;/caption&gt;
&lt;/table&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>
<p><a href="examples/tag.htm">Click here</a> for the results.</p>
<h2>Almanac</h2>
<p>
If module <a href="http://rhodesmill.org/pyephem">pyephem</a> 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.
</p>
<p>Here is an example template:</p>
<pre class="tty">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.az $almanac.mars.alt
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</pre>
<p>If pyephem is installed this would result in:</p>
<p class="example_output">
Current time is 29-Mar-2011 09:20<br/> Sunrise, transit, sunset: 06:51 13:11 19:30<br/> Moonrise, transit,
moonset: 04:33 09:44 15:04<br/> Mars rise, transit, set: 06:35 12:30 18:26<br/> Azimuth, altitude of mars:
124.354959275 26.4808431952<br/> Next new, full moon: 03-Apr-2011 07:32 17-Apr-2011 19:43<br/> Next summer,
winter solstice: 21-Jun-2011 10:16 21-Dec-2011 21:29
</p>
<p>Otherwise, a fallback of basic calculations is used, resulting in:</p>
<p class="example_output">
Current time is 29-Mar-2011 09:20<br/> Sunrise, sunset: 06:51 19:30
</p>
<p>
As shown in the example, you can test whether this extended almanac information is available with the value
<span class="code">$almanac.hasExtras</span>.
</p>
<p>The almanac information falls into three categories:</p>
<ul>
<li>Calendar events</li>
<li>Heavenly bodies</li>
<li>Functions</li>
</ul>
<p>We will cover each of these separately.</p>
<h3>Calendar events</h3>
<p>
"Calendar events" do not require a heavenly body. They cover things such as <span
class="code">next_solstice</span>, <span class="code">next_first_quarter_moon</span> or <span class="code">sidereal_time</span>.
The syntax here is:
</p>
<pre class="tty">$almanac.next_solstice</pre>
<p>or</p>
<pre class="tty">$almanac.next_first_quarter_moon</pre>
<p>or</p>
<pre class="tty">$almanac.sidereal_time</pre>
<p>Here is a table of the information that falls into this category:</p>
<table class="indent" style="width: 60%">
<caption>Calendar events</caption>
<tbody class="code">
<tr>
<td>previous_equinox</td>
<td>next_equinox</td>
<td>previous_solstice</td>
<td>next_solstice</td>
</tr>
<tr>
<td>previous_autumnal_equinox</td>
<td>next_autumnal_equinox</td>
<td>previous_vernal_equinox</td>
<td>next_vernal_equinox</td>
</tr>
<tr>
<td>previous_winter_solstice</td>
<td>next_winter_solstice</td>
<td>previous_summer_solstice</td>
<td>next_summer_solstice</td>
</tr>
<tr>
<td>previous_new_moon</td>
<td>next_new_moon</td>
<td>previous_first_quarter_moon</td>
<td>next_first_quarter_moon</td>
</tr>
<tr>
<td>previous_full_moon</td>
<td>next_full_moon</td>
<td>previous_last_quarter_moon</td>
<td>next_last_quarter_moon</td>
</tr>
<tr>
<td>sidereal_time</td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
<p class="note" style="display: inline-block">
<strong>Note</strong><br/>The tag <span class="code">$almanac.sidereal_time</span> returns a value in
decimal degrees rather than a customary value from 0 to 24 hours.
</p>
<h3>Heavenly bodies</h3>
<p>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
</p>
<pre class="tty">$almanac.jupiter.rise</pre>
<p>or</p>
<pre class="tty">$almanac.sun.transit</pre>
<p>
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 <a
href="http://rhodesmill.org/pyephem/rise-set.html">as explained in the pyephem documentation</a>. For
example, to match the sunrise time as published by the Observatory, instead of
</p>
<pre class="tty">$almanac.sun.rise</pre>
<p>use</p>
<pre class="tty">$almanac(pressure=0, horizon=-34.0/60.0).sun.rise</pre>
<p>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.
</p>
<p>
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:
</p>
<pre class="tty">$almanac(pressure=0, horizon=-6).sun(use_center=1).rise</pre>
<p>The general syntax is:</p>
<pre class="tty">$almanac(pressure=<em>pressure</em>, horizon=<em>horizon</em>,
temperature=<em>temperature_C</em>).<em>heavenly_body</em>(use_center=[01]).<em>attribute</em>
</pre>
<p>As you can see, in addition to the horizon angle, you can also override atmospheric pressure and temperature
(degrees Celsius).
</p>
<p>
PyEphem offers an extensive list of objects that can be used for the <span
class="code"><em>heavenly_body</em></span> tag. All the planets and many stars are in the list.
</p>
<p>
The possible values for the <span class="code">attribute</span> tag are listed in the following table:
</p>
<table class="indent" style="width: 80%">
<caption>Attributes that can be used with heavenly bodies
</caption>
<tbody class="code">
<tr>
<td>az</td>
<td>alt</td>
<td>a_ra</td>
<td>a_dec</td>
</tr>
<tr>
<td>g_ra</td>
<td>ra</td>
<td>g_dec</td>
<td>dec</td>
</tr>
<tr>
<td>elong</td>
<td>radius</td>
<td>hlong</td>
<td>hlat</td>
</tr>
<tr>
<td>sublat</td>
<td>sublong</td>
<td>next_rising</td>
<td>next_setting</td>
</tr>
<tr>
<td>next_transit</td>
<td>next_antitransit</td>
<td>previous_rising</td>
<td>previous_setting</td>
</tr>
<tr>
<td>previous_transit</td>
<td>previous_antitransit</td>
<td>rise</td>
<td>set</td>
</tr>
<tr>
<td>transit</td>
<td> </td>
<td> </td>
<td> </td>
</tr>
</tbody>
</table>
<p class="note" style="display: inline-block">
<strong>Note</strong><br/>The tags <span class="code">ra</span>, <span class="code">a_ra</span> and <span
class="code">g_ra</span> return values in decimal degrees rather than customary values from 0 to 24 hours.
</p>
<h3>Functions</h3>
<p>There is actually one one function in this category: <span class="code">separation</span>. It returns the
angular separation between two heavenly bodies. For example, to calculate the angular separation between
Venus and Mars you would use:
</p>
<pre class="tty">
&lt;p&gt;The separation between Venus and Mars is
$almanac.separation(($almanac.venus.alt,$almanac.venus.az), ($almanac.mars.alt,$almanac.mars.az))&lt;/p&gt;
</pre>
<p>This would result in:</p>
<p class="example_output">The separation between Venus and Mars is 55:55:31.8</p>
<h3>Adding new bodies to the almanac</h3>
<p>
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 <a href="https://en.wikipedia.org/wiki/433_Eros"><em>433 Eros</em></a>, the
first asteroid visited by a spacecraft. Here is the process:
</p>
<ol>
<li>
Put the following in the file <span class="code">user/extensions.py</span>:
<pre class="tty">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</pre>
This does two things: it adds orbital information about <em>433 Eros</em> to the internal pyephem
database, and it makes that data available under the name <span class="code">Eros</span> (note the
capital letter).
</li>
<li>
You can then use <em>433 Eros</em> like any other body in your templates. For example, to display when
it will rise above the horizon:
<pre class="tty">$almanac.eros.rise</pre>
</li>
</ol>
<h2>Wind</h2>
<p>
Wind deserves a few comments because it is stored in the database in two different ways: as a set of
scalars, and as a <em>vector</em> of speed and direction. Here are the four wind-related scalars stored in
the main archive database:
</p>
<table class="indent">
<tbody>
<tr class="first_row">
<td>Archive type</td>
<td>Meaning</td>
<td>Valid contexts</td>
</tr>
<tr>
<td class="first_col code">windSpeed</td>
<td>The average wind speed seen during the archive period.
</td>
<td rowspan='4' class='code'>
$current, $latest, $day, $week, $month, $year, $rainyear
</td>
</tr>
<tr>
<td class="first_col code">windDir</td>
<td>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.
</td>
</tr>
<tr>
<td class="first_col code">windGust</td>
<td>The maximum (gust) wind speed seen during the archive period.
</td>
</tr>
<tr>
<td class="first_col code">windGustDir</td>
<td>The direction of the wind when the gust was observed.</td>
</tr>
</tbody>
</table>
<p>In addition, a wind vector is stored in the daily summaries.</p>
<table class="indent">
<tbody>
<tr class="first_row">
<td>Daily&nbsp;summary&nbsp;type</td>
<td>Meaning</td>
<td>Valid contexts</td>
</tr>
<tr>
<td class="first_col code">wind</td>
<td>A vector composite of the wind. It includes information such as the direction of the maximum gust,
and the x- and y-vector wind run.
</td>
<td class='code'>$day, $week, $month, $year, $rainyear</td>
</tr>
</tbody>
</table>
<p class="note" style="display: inline-block">
<strong>Note</strong><br/>The vector is only stored in the daily <em>summaries</em>, so unlike the scalar
tags such as <span class="code">windSpeed</span> or <span class="code">windGust</span>, the tag <span
class="code">wind</span> can only be used in aggregations such as <span class="code">$day</span>, <span
class="code">$month</span>, <i>etc.</i>
</p>
<p>Any of these can be used in your tags. Here are some examples:</p>
<table class="indent" style="width: 90%;">
<tbody>
<tr class="first_row">
<td>Tag</td>
<td>Meaning</td>
</tr>
<tr>
<td class="first_col code">$current.windSpeed</td>
<td>The average wind speed over the most recent archive interval.
</td>
</tr>
<tr>
<td class="first_col code">$current.windDir</td>
<td>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.
</td>
</tr>
<tr>
<td class="first_col code">$current.windGust</td>
<td>The maximum wind speed (gust) over the most recent archive interval.
</td>
</tr>
<tr>
<td class="first_col code">$current.windGustDir</td>
<td>The direction of the gust.</td>
</tr>
<tr>
<td class="first_col code">$day.wind.avg</td>
<td>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.
</td>
</tr>
<tr>
<td class="first_col code">$day.wind.vecavg</td>
<td>The <em>vector average</em> 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.
</td>
</tr>
<tr>
<td class="first_col code">$day.wind.vecdir</td>
<td>The direction of the vector averaged wind speed. If the wind blows northwest for two hours, then
southwest for two hours, the vector averaged direction is west.
</td>
</tr>
<tr>
<td class="first_col code">$day.wind.max</td>
<td>The maximum wind gust since midnight.</td>
</tr>
<tr>
<td class="first_col code">$day.wind.gustdir</td>
<td>The direction of the maximum wind gust.</td>
</tr>
<tr>
<td class="first_col code">$day.wind.maxtime</td>
<td>The time of the maximum wind gust.</td>
</tr>
<tr>
<td class="first_col code">$day.windSpeed.max</td>
<td>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 <em>not</em> the same as the maximum wind gust.
</td>
</tr>
<tr>
<td class="first_col code">$day.windDir.avg</td>
<td>Not a very useful quantity. This is the strict, arithmetic average of all the compass wind
direction. Probably not what you want.
</td>
</tr>
</tbody>
</table>
<h2 id="defining_new_tags">Defining new tags</h2>
<p>
We have seen how you can change a template and make use of the various tags available such as <span
class="code">$day.outTemp.max</span> for the maximum outside temperature for the day. But, what if you want
to introduce some new data for which no tag is available?
</p>
<p>
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 <span class="code"><a
href="#Extras">[Extras]</a></span> in the skin configuration file. More information on how to do this can be
found there.
</p>
<p>
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 <span class="code">[Extras]</span> section won't do, because then it
cannot change.
</p>
<p>
The answer is to write a <em>search list extension</em>.
</p>
<h3>How the search list works</h3>
<p>
Let's start by taking a look at how the Cheetah <em>search list</em> works.
</p>
<p>
The Cheetah template engine finds tags by scanning a search list, a Python list of objects. For example, for
a tag <span class="code">$foo</span>, the engine will scan down the list, trying each object in the list in
turn. For each object, it will first try using <span class="code">foo</span> as an attribute, that is, it
will try evaluating <span class="code"><em>obj</em>.foo</span>. If that raises an <span class="code">AttributeError</span>
exception, then it will try <span class="code">foo</span> as a key, that is <span class="code"><em>obj</em>[key]</span>.
If that raises a <span class="code">KeyError</span> exception, then it moves on to the next item in the
list. The first match that does not raise an exception is used. If no match is found, Cheetah raises a <span
class="code">NameMapper.NotFound</span> exception.
</p>
<h3 id="how_tags_work">How tags work</h3>
<p>
Now let's take a look at how the search list interacts with WeeWX tags. Let's start by looking at a simple
example: station altitude, available as the tag
</p>
<pre class="tty">
$station.altitude</pre>
<p>
As we saw in the previous section, Cheetah will run down the search list, looking for an object with a key
or attribute <span class="code">station</span>. In the default search list, WeeWX includes one such object,
an instance of the class <span class="code">weewx.cheetahgenerator.Station</span>, which has an attribute
<span class="code">station</span>, so it gets a hit on this object.
</p>
<p>
Cheetah will then try to evaluate the attribute <span class="code">altitude</span> on this object. Class
<span class="code">Station</span> has such an attribute, so Cheetah evaluates it.
</p>
<p>
What this attribute returns is not a raw value, say <span class="code">700</span>, nor even a string.
Instead, it returns an instance of the class <span class="code">ValueHelper</span>, a special class defined
in module <span class="code">weewx.units</span>. Internally, it holds not only the raw value, but also
references to the formats, labels, and conversion targets you specified in your configuration file. Its job
is to make sure that the final output reflects these preferences. Cheetah doesn't know anything about this
class. What it needs, when it has finished evaluating the expression <span
class="code">$station.altitude</span>, is a <em>string</em>. In order to convert the <span class="code">ValueHelper</span>
it has in hand into a string, it does what every other Python object does when faced with this problem: it
calls the special method <span class="code"><a style="text-decoration: none"
href="https://docs.python.org/2/reference/datamodel.html#object.__str__">__str__</a></span>.
Class <span class="code">ValueHelper</span> has a definition for this method. Evaluating this function
triggers the final steps in this process. Any necessary unit conversions are done, then formatting occurs
and, finally, a label is attached. The result is a string something like
</p>
<p class="example_output">700 feet</p>
<p>
which is what Cheetah actually puts in the generated HTML file. This is a good example of <em>lazy
evaluation</em>. The tags gather all the information they need, but don't do the final evaluation until the
last final moment, when the most context is understood. WeeWX uses this technique extensively.
</p>
<p>Now let's look at a more complicated example, say the maximum temperature since midnight:</p>
<pre class="tty">$day.outTemp.max</pre>
<p>
When this is evaluated by Cheetah, it actually produces a chain of objects. At the top of this chain is
class <span class="code">weewx.tags.TimeBinder</span>, an instance of which is included in the default
search list. Internally, this instance stores the time of the desired report (usually the time of the last
archive record), a cache to the databases, a default data binding, as well as references to the formatting
and labelling options you have chosen.
</p>
<p>
This instance is examined by Cheetah to see if it has an attribute <span class="code">day</span>. It does
and, when it is evaluated, it returns the next class in the chain, an instance of <span class="code">weewx.tags.TimespanBinder</span>.
In addition to all the other things contained in its parent <span class="code">TimeBinder</span>, class
<span class="code">TimespanBinder</span> adds the desired time period, that is, the time span from midnight
to the current time.
</p>
<p>
Cheetah then continues on down the chain and tries to find the next attribute, <span
class="code">outTemp</span>. There is no such hard coded attribute (hard coding all the conceivable
different observation types would be impossible!). Instead, class <span class="code">TimespanBinder</span>
defines the Python special method <span class="code"> <a style="text-decoration: none"
href="https://docs.python.org/2/reference/datamodel.html#object.__getattr__">__getattr__</a></span>.
If Python cannot find a hard coded version of an attribute, and the method <span
class="code">__getattr__</span> exists, it will try it. The definition provided by <span class="code">TimespanBinder</span>
returns an instance of the next class in the chain, <span class="code">weewx.tags.ObservationBinder</span>,
which not only remembers all the previous stuff, but also adds the observation type, <span class="code">outTemp</span>.
</p>
<p>
Cheetah then tries to evaluate an attribute <span class="code">max</span> of this class. Now, finally, the
chain ends. The attribute <span class="code">max</span> triggers the actual calculation of the value, using
all the known parameters: the database binding to be hit, the time span of interest, the observation type,
and the type of aggregation, querying the database as necessary. The database is not actually hit until the
last possible moment, after everything needed to do the evalation is known.
</p>
<p>
Like our previous example, the results of the evaluation are then packaged up in an instance of <span
class="code">ValueHelper</span>, which does the final conversion to the desired units, formats the string,
then adds a label. The results, something like
</p>
<p class="example_output">12&deg;C</p>
<p>
are put in the generated HTML file. As you can see, a lot of machinery is hidden behind the deceptively
simple expression <span class="code">$day.outTemp.max</span>!
</p>
<h3 id="extending_the_list">Extending the list</h3>
<p>
As mentioned, WeeWX comes with a number of objects already in the search list, but you can extend it. To do
so, you should have some familiarity with Python, in particular, how to write new classes and member
functions for them.
</p>
<p>
Let's look at an example. The regular version of WeeWX offers statistical summaries by day, week, month, and
year. Suppose we would like to add two more:
</p>
<ul>
<li>All-time statistics. This would allow us to display statistics such as the all-time high or low
temperature seen at your station;
</li>
<li>Seven days statistics. While WeeWX offers the tag <span class="code">$week</span>, this is statistics
<em>since Sunday at midnight</em>. We would like to have statistics for a full week, that is since
midnight seven days ago.
</li>
</ul>
<p>
This example is included in the distribution as <span class="code">examples/stats.py</span>. If you wish to
use or modify it, first copy it over to <span class="code">user/stats.py</span> then modify it there.
</p>
<pre class="tty">import datetime
import time
from weewx.cheetahgenerator import SearchList
from weewx.tags import TimespanBinder
from weeutil.weeutil import TimeSpan
class MyStats(SearchList): # 1
"""My search list extension"""
def __init__(self, generator): # 2
SearchList.__init__(self, generator)
def get_extension_list(self, timespan, db_lookup): # 3
"""Returns a search list extension with two additions.
Parameters:
timespan: An instance of weeutil.weeutil.TimeSpan. This will
hold the start and stop times of the domain of
valid times.
db_lookup: This is a function that, given a data binding
as its only parameter, will return a database manager
object.
"""
# First, create TimespanBinder object for all time. This one is easy
# because the object timespan already holds all valid times to be
# used in the report.
all_stats = TimespanBinder(timespan,
db_lookup,
formatter=self.generator.formatter,
converter=self.generator.converter,
skin_dict=self.generator.skin_dict) # 4
# Now get a TimespanBinder object for the last seven days. This one we
# will have to calculate. First, calculate the time at midnight, seven
# days ago. The variable week_dt will be an instance of datetime.date.
week_dt = datetime.date.fromtimestamp(timespan.stop) - \
datetime.timedelta(weeks=1) # 5
# Convert it to unix epoch time:
week_ts = time.mktime(week_dt.timetuple()) # 6
# Form a TimespanBinder object, using the time span we just
# calculated:
seven_day_stats = TimespanBinder(TimeSpan(week_ts, timespan.stop),
db_lookup,
formatter=self.generator.formatter,
converter=self.generator.converter,
skin_dict=self.generator.skin_dict) # 7
# Now create a small dictionary with keys 'alltime' and 'seven_day':
search_list_extension = {'alltime' : all_stats,
'seven_day' : seven_day_stats} # 8
# Finally, return our extension as a list:
return [search_list_extension] # 9</pre>
<p>Going through the example, line by line:</p>
<ol>
<li>Create a new class called <span class="code">MyStats</span>, which will inherit from class <span
class="code">SearchList</span>. All search list extensions inherit from this class.
</li>
<li>Create an initializer for our new class. In this case, the initializer is not really necessary and does
nothing except pass its only parameter, <span class="code">generator</span>, a reference to the calling
generator, on to its superclass, <span class="code">SearchList</span>. The superclass will store it in
<span class="code">self</span>.
</li>
<li>Override member function <span class="code">get_extension_list()</span>. This function will be called
when the generator is ready to accept your new search list extension. The parameters that will be passed
in are:
<ul>
<li><span class="code">self</span> Python's way of indicating the instance we are working with;
</li>
<li><span class="code">timespan</span> An instance of the utility class <span
class="code">TimeSpan</span>. This will contain the valid start and ending times used by the
template. Normally, this is all valid times;
</li>
<li><span class="code">db_lookup</span> This is a function supplied by the generator. It takes a
single argument, a name of a binding. When called, it will return an instance of the database
manager class for that binding. The default for the function is whatever binding you set with
the option <span class="code">data_binding</span> for this report, usually <span class="code">wx_binding</span>.
</li>
</ul>
</li>
<li>The class <span class="code">TimespanBinding</span> represents a statistical calculation over a time
period. We have already met it in the introduction <em><a href="#how_tags_work">How tags work</a></em>.
In our case, we will set it up to represent the statistics over all possible times. The class takes 5
parameters.
<ul>
<li>The first, <span class='code'>timespan</span>, is the timespan over which the calculation is to
be done. Here, we have a lucky coincidence: the variable <span class="code">timespan</span>
already holds a <span class="code">TimeSpan</span> object representing the domain of all valid
timespans, so we simply pass it in.
</li>
<li>The second, <span class='code'>db_lookup</span>, is the database lookup function to be used. We
simply pass in <span class="code">db_lookup</span>.
</li>
<li>The third, <span class="code">formatter</span>, should be an instance of class <span
class="code">weewx.units.Formatter</span>, which contains information about how the results
should be formatted. We just pass in the formatter set up by the generator, <span class="code">self.generator.formatter</span>.
</li>
<li>The fourth, <span class="code">converter</span>, should be an instance of <span class="code">weewx.units.Converter</span>,
which contains information about the target units (<i>e.g.</i>, <span
class="code">degree_C</span>) that are to be used. Again, we just pass in the instance set
up by the generator, <span class="code">self.generator.converter</span>.
</li>
<li>The fifth, <span class="code">skin_dict</span>, is an instance of <span class="code">configobj.ConfigObj</span>,
and contains the contents of the skin configuration file. We pass it on in order to allow
aggregations that need information from the file, such as heating and cooling degree-days.
</li>
</ul>
</li>
</ol>
<p>
That one was relatively easy because we already had an instance of <span class="code">TimeSpan</span>, that
is, <span class="code">timespan</span>, which represented the time over which we wanted to do the
calculations. Setting up an instance that will work for the last seven days is a bit trickier. Continuing
our example...
</p>
<ol start="5">
<li>The object <span class="code">timespan</span> holds the domain of all valid times, but in order to
calculate statistics for the last seven days, we need not the earliest valid time, but the time at
midnight seven days ago. So, we do a little Python date arithmetic to calculate this. The object <span
class="code">week_dt</span> will be an instance of <span class="code">datetime.date</span>.
</li>
<li>We convert it to unix epoch time.</li>
<li>Now we are ready to initialize an appropriate <span class="code">TimespanBinder</span> object. It's the
same as in step #4, except we use our new timespan object.
</li>
<li>Create a small dictionary with two keys, <span class="code">alltime</span>, and <span class="code">seven_day</span>.
</li>
<li>Return the dictionary in a list</li>
</ol>
<p>
The final step that we need to do is to tell the template engine where to find our extension. You do that by
going into the skin configuration file, <span class="code">skin.conf</span>, and adding the option <span
class="code">search_list_extensions</span> with our new extension. When you're done, it will look something
like this:
</p>
<pre class="tty">[CheetahGenerator]
# This section is used by the generator CheetahGenerator, and specifies
# which files are to be generated from which template.
# Possible encodings are 'html_entities', 'utf8', or 'strict_ascii'
encoding = html_entities
<span class="highlight">search_list_extensions = user.stats.MyStats</span>
[[SummaryByMonth]]
...
</pre>
<p>
Our addition has been <span class="highlight">highlighted</span>. Note that it is in the section <span
class="code">[CheetahGenerator]</span>.
</p>
<p>
Now, if the Cheetah engine encounters the tag <span class="code">
$alltime</span>, it will scan the search list, looking for an attribute or key that matches <span
class="code">alltime</span>. When it gets to the little dictionary we provided, it will find a matching key,
allowing it to retrieve the appropriate <span class="code">TimespanBinding</span> object.
</p>
<p>With this approach, you can now include "all time" or "seven day" statistics in your HTML templates:</p>
<pre class="tty">
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;Maximum temperature to date: &lt;/td&gt;
&lt;td&gt;$alltime.outTemp.max&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Minimum temperature to date: &lt;/td&gt;
&lt;td&gt;$alltime.outTemp.min
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rain over the last seven days: &lt;/td&gt;
&lt;td&gt;$seven_day.rain.sum
&lt;/tr&gt;
&lt;/table&gt;</pre>
<p>
We put our addition in the file <span class="code">user/stats.py</span>, which means it will be in the
directory hierarchy where <span class="code">weewxd</span> resides. Python will search this hierarchy by
default, so it will find our addition. However, if you put it somewhere else, may will have to specify its
location with the environment variable <a
href="https://docs.python.org/2/using/cmdline.html#envvar-PYTHONPATH"><span
class="code">PYTHONPATH</span></a> when you start WeeWX:
</p>
<pre class="tty">export PYTHONPATH=/home/me/secret_location</pre>
<h1 id="image_generator">The Image generator</h1>
<p class="note">
This section gives an overview of the Image generator. For details about each of its various options, see
the section <a href="#ImageGenerator"><em>[ImageGenerator]</em></a> in the <a href="#report_options"><em>Reference: report options</em></a>.
</p>
<p>
The installed version of WeeWX is configured to generate a set of useful plots. But, what if you don't like
how they look, or you want to generate different plots, perhaps with different aggregation types? This
section covers how to do this.
</p>
<p>
Image generation is controlled by the section <a href="#ImageGenerator"><span
class="code">[ImageGenerator]</span></a> in the skin configuration file <span class="code">skin.conf</span>.
Let's take a look at the beginning part of this section. It looks like this:
</p>
<pre class="tty">[ImageGenerator]
...
image_width = 500
image_height = 180
image_background_color = 0xf5f5f5
chart_background_color = 0xd8d8d8
chart_gridline_color = 0xa0a0a0
...</pre>
<p>
The options right under the section name <span class="code">[ImageGenerator]</span> will apply to
<em>all</em> plots, unless overridden in subsections. So, unless otherwise changed, all plots will be 500
pixels in width, 180 pixels in height, and will have an RGB background color of 0xf5f5f5, a very light gray
(HTML color "WhiteSmoke"). The chart itself will have a background color of 0xd8d8d8 (a little darker gray),
and the gridlines will be 0xa0a0a0 (still darker). The other options farther down (not shown) will also
apply to all plots.
</p>
<h2>Time periods</h2>
<p>
After the "global" options at the top of section <span class="code">[ImageGenerator]</span>, comes a set of
sub-sections, one for each time period (day, week, month, and year). These sub-sections define the nature of
aggregation and plot types for that time period. For example, here is a typical set of options for
sub-section <span class="code">[[month_images]]</span>. It controls which "monthly" images will get
generated, and what they will look like:
</p>
<pre class="tty"> [[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
show_daynight = false
</pre>
<p>
The option <span class="code">x_label_format</span> gives a <a
href="https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior">strftime()</a> type format
for the x-axis. In this example, it will only show days (format option <span class="code">%d</span>). The
<span class="code">bottom_label_format</span> is the format used to time stamp the image at the bottom. In
this example, it will show the time as something like <span class="code">10/25/09
15:35</span>. A plot will cover a nominal 30 days, and all items included in it will use an aggregate type of
averaging over 3 hours. Finally, by setting option <span class="code">show_daynight</span> to <span
class="code">false</span>, we are requesting that day-night, shaded bands not be shown.
</p>
<h2>Image files</h2>
<p>
Within each time period 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. Like elsewhere, the values specified
in the level above can be overridden. For example, here is a typical set of options for sub-sub-section
<span class="code">[[[monthrain]]]</span>:
</p>
<pre class="tty"> [[[monthrain]]]
plot_type = bar
yscale = None, None, 0.02
[[[[rain]]]]
aggregate_type = sum
aggregate_interval = 86400
label = Rain (daily total)</pre>
<p>
This will generate an image file with name <span class="code">monthrain.png</span>. It will be a bar plot.
Option <span class="code">yscale</span> controls the y-axis scaling &mdash; 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. The option is a 3-way tuple (<span class="code">ylow</span>, <span
class="code">yhigh</span>, <span class="code">min_interval</span>), where <span class="code">ylow</span> and
<span class="code">yhigh</span> are the minimum and maximum y-axis values, respectively, and <span
class="code">min_interval</span> is the minimum tick interval. If set to <span class="code">None</span>, the
corresponding value will be automatically chosen. So, in this example, the setting
</p>
<pre class="tty">yscale = None, None, 0.02</pre>
<p>
will cause WeeWX to pick sensible y minimum and maximum values, but require that the tick increment (<span
class="code">min_interval</span>) be at least 0.02.
</p>
<p>
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 <span class="code">rain</span>. Because we have not said otherwise, the
database column name to be used for this line will be the same as its logical name, that is, <span class="code">rain</span>,
but this can be overridden. The aggregation type will be summing (overriding the averaging specified in
sub-section <span class="code">[[month_images]]</span>), 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 of 'Rain (daily total)'. The result of all this is the following plot:
</p>
<p>
<img src="images/sample_monthrain.png" alt="Sample monthly rain plot"/>
</p>
<h2>Plotting multiple observations</h2>
<p>More than one observation can be included in a plot. For example, here is how to generate a plot with the week's
outside temperature as well as dewpoint:
</p>
<pre class="tty">[[[monthtempdew]]]
[[[[outTemp]]]]
[[[[dewpoint]]]]</pre>
<p>
This would create an image in file <span class="code">monthtempdew.png</span> that includes a line plot of
both outside temperature and dewpoint:
</p>
<p>
<img src="images/sample_monthtempdew.png" alt="Monthly temperature and dewpoint"/>
</p>
<h2>Plotting a single observation multiple times</h2>
<p>
Another example. Say you want a plot of the day's temperature, overlaid with hourly averages. Here, you are
using the same data type (<span class="code">outTemp</span>) for both plot lines, the first with averages,
the second without. If you do the obvious it won't work:
</p>
<pre class="tty">## WRONG ##
[[[daytemp_with_avg]]]
[[[[outTemp]]]]
aggregate_type = avg
aggregate_interval = 3600
<span style="text-decoration: line-through; color: red">[[[[outTemp]]]]</span> # OOPS! The same section name appears more than once!</pre>
<p>
The option parser does not allow the same section name (<span class="code">outTemp</span> 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 observation to appear
more than once in a plot then there is a trick you must know: use option <span class="code">data_type</span>.
This will override the default action that the logical line name is used for the database column. So, our example
would look like this:
</p>
<pre class="tty">[[[daytemp_with_avg]]]
[[[[avgTemp]]]]
data_type = outTemp
aggregate_type = avg
aggregate_interval = 3600
label = Avg. Temp.
[[[[outTemp]]]]</pre>
<p>
Here, the first plot line has been given the name <span class="code">avgTemp</span> to distinguish it from
the second line <span class="code">outTemp</span>. Any name will do &mdash; it just has to be different. We
have specified that the first line will use data type <span class="code"> outTemp</span> and that it will
use averaging over a one hour period. The second also uses <span class="code">outTemp</span>, but will not
use averaging.
</p>
<p>The result is a nice plot of the day's temperature, overlaid with a one hour smoothed average:</p>
<p>
<img alt="Daytime temperature with running average" src="images/daytemp_with_avg.png"/>
</p>
<p>One more example. This one shows daily high and low temperatures for a year:</p>
<pre class="tty">[[year_images]]
[[[yearhilow]]]
[[[[hi]]]]
data_type = outTemp
aggregate_type = max
label = High
[[[[low]]]]
data_type = outTemp
aggregate_type = min
label = Low Temperature</pre>
<p>
This results in the plot <span class="code">yearhilow.png</span>:
</p>
<p>
<img style="width:300px; height:180px" alt="Daily highs and lows" src="images/yearhilow.png"/>
</p>
<h2 id="line_gaps">Line gaps</h2>
<p id="line_gap_fraction">
If there is a time gap in the data, the option <span class="code">line_gap_fraction</span> controls how line
plots will be drawn. Here's what a plot looks like without and with this option being specified:
</p>
<div class="center" style="margin: 0;">
<div style="float: left">
<img src="images/day-gap-not-shown.png" alt="Gap not shown"/>
<div class="image_caption">
No <span class="code">line_gap_fraction</span> specified
</div>
</div>
<div>
<img src="images/day-gap-showing.png" alt="Gap showing"/>
<div class="image_caption">
With <span class="code">line_gap_fraction=0.01</span>
</div>
</div>
</div>
<div style="clear: both"></div>
<h2>Progressive vector plots</h2>
<p>
WeeWX can produce progressive vector plots as well as the more conventional x-y plots. To produce these, use
plot type <span class="code">vector</span>. You need a vector type to produce this kind of plot. There are
two: <span class="code">windvec</span>, and <span class="code">windgustvec</span>. While they do not
actually appear in the database, WeeWX understands that they represent special vector-types. The first,
<span class="code">windvec</span>, represents the average wind in an archive period, the second, <span
class="code">windgustvec</span> 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:
</p>
<pre class="tty">[[[weekgustoverlay]]]
aggregate_interval = 3600
[[[[windvec]]]]
label = Hourly Wind
plot_type = vector
aggregate_type = avg
[[[[windgustvec]]]]
label = Gust Wind
plot_type = vector
aggregate_type = max</pre>
<p>
This will produce an image file with name <span class="code">weekgustoverlay.png</span>. 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:
</p>
<p>
<img alt="hourly average wind vector overlaid with gust vectors" src="images/weekgustoverlay.png"/>
</p>
<p>
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 do not line up over the x-axis, overlaying each other. Do this
by using option <span class="code">vector_rotate</span>. For example, with my chronic westerlies, I set
<span class="code">vector_rotate</span> to 90.0 for the plot above, so winds out of the west point straight
up.
</p>
<p>
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.
</p>
<h2>Overriding values</h2>
<p>
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 <span
class="code">weewx.conf</span> file specifies plot size of 300x180 pixels, which will be used for all plots
unless overridden:
</p>
<pre class="tty">[ImageGenerator]
...
image_width = 300
image_height = 180</pre>
<p>
The standard plot of barometric pressure will appear in <span class="code">daybarometer.png</span>:
</p>
<pre class="tty">[[[daybarometer]]]
[[[[barometer]]]] </pre>
<p>
We now add our special plot of barometric pressure, but specify a larger image size. This image will be put
in file <span class="code">daybarometer_big.png</span>.
</p>
<pre class="tty">[[[daybarometer_big]]]
image_width = 600
image_height = 360
[[[[barometer]]]]</pre>
<h1 id="Using_multiple_bindings">Using multiple bindings</h1>
<p>
It's easy to use more than one database in your reports. Here's an example. In my office I have two
consoles: a VantagePro2 connected to a Dell Optiplex, and a WMR100N, connected to a Raspberry Pi. Each is
running WeeWX. The Dell is using SQLite, the RPi, MySQL.
</p>
<p>Suppose I wish to compare the inside temperatures of the two consoles. How would I do that?</p>
<p>
It's easier to access MySQL across a network than SQLite, so let's run the reports on the Dell, but access
the RPi's MySQL database remotely. Here's how the bindings and database sections of <span class="code">weewx.conf</span>
would look on the Dell:
</p>
<pre class="tty">[DataBindings]
# This section binds a data store to an actual database
[[wx_binding]]
# The database to be used - it should match one of the sections in [Databases]
database = archive_sqlite
# The name of the table within the database
table_name = archive
# The class to manage the database
manager = weewx.wxmanager.WXDaySummaryManager
# The schema defines to structure of the database contents
schema = schemas.wview.schema
<span class="highlight"> [[wmr100_binding]]
# Binding for my WMR100 on the RPi
database = rpi_mysql
# The name of the table within the database
table_name = archive
# The class to manage the database
manager = weewx.wxmanager.WXDaySummaryManager
# The schema defines to structure of the database contents
schema = schemas.wview.schema</span>
[Databases]
# This section binds to the actual database to be used
[[archive_sqlite]]
database_type = SQLite
database_name = weewx.sdb
<span class="highlight"> [[rpi_mysql]]
database_type = MySQL
database_name = weewx
host = rpi-bug</span>
[DatabaseTypes]
# This section defines defaults for the different types of databases.
[[SQLite]]
driver = weedb.sqlite
# Directory in which the database files are located
SQLITE_ROOT = %(WEEWX_ROOT)s/archive
[[MySQL]]
driver = weedb.mysql
# The host where the database is located
host = localhost
# The user name for logging in to the host
user = weewx
# The password for the user name
password = weewx
</pre>
<p>
The two additions have been <span class="highlight">highlighted</span>. The first, <span class="code">[[wmr100_binding]]</span>,
adds a new binding called <span class="code">wmr10_binding</span>. It links ("binds") to the new database,
called <span class="code">rpi_mysql</span>, through the option <span class="code">database</span>. It also
defines some characteristics of the binding, such as which manager is to be used and what its schema looks
like.
</p>
<p>
The second addition, <span class="code">[[rpi-mysql]]</span> defines the new database. Option <span
class="code">database_type</span> is set to <span class="code">MySQL</span>, indicating that it is a MySQL
database. Defaults for MySQL databases are defined in the section <span class="code">[[MySQL]]</span>. The
new database accepts all of them, except for <span class="code">host</span>, which as been set to the remote
host <span class="code">rpi-bug</span>, the name of my Raspberry Pi.
</p>
<h2>Explicit binding in tags</h2>
<p>How do we use this new binding? First, let's do a text comparison, using tags. Here's what our template looks
like:
</p>
<pre class="tty">&lt;table&gt;
&lt;tr&gt;
&lt;td class="stats_label"&gt;Inside Temperature, Vantage&lt;/td&gt;
&lt;td class="stats_data"&gt;$current.inTemp&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="stats_label"&gt;Inside Temperature, WMR100&lt;/td&gt;
&lt;td class="stats_data"&gt;$latest<span class="highlight">($data_binding='wmr100_binding')</span>.inTemp&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;</pre>
<p>
The explicit binding to <span class="code">wmr100_binding</span> is highlighted. This tells the reporting
engine to override the default binding specifed in <span class="code">[StdReport]</span>, generally <span
class="code">wx_binding</span>, and use <span class="code">wmr100_binding</span> instead.
</p>
<p>This results in an HTML output that looks like:</p>
<div id="stats_group" class="indent">
<div class="stats example_output">
<table>
<tbody>
<tr>
<td class="stats_label">Inside Temperature, Vantage</td>
<td class="stats_data">68.7&#176;F</td>
</tr>
<tr>
<td class="stats_label">Inside Temperature, WMR100</td>
<td class="stats_data">68.9&#176;F</td>
</tr>
</tbody>
</table>
</div>
</div>
<h2>Explicit binding in images</h2>
<p>
How would we produce a graph of the two different temperatures? Here's what the relevant section of the
<span class="code">skin.conf</span> file would look like.
</p>
<pre class="tty">[[[daycompare]]]
[[[[inTemp]]]]
label = Vantage inTemp
[[[[WMR100Temp]]]]
data_type = inTemp
data_binding = wmr100_binding
label = WMR100 inTemp</pre>
<p>
This will produce an image with name <span class="code">daycompare.png</span>, with two plot lines. The
first will be of the temperature from the Vantage. It uses the default binding, <span class="code">wx_binding</span>,
and will be labeled <span class="code">Vantage inTemp</span>. The second explicitly uses the <span
class="code">wmr100_binding</span>. Because it uses the same variable name (<span class="code">inTemp</span>)
as the first line, we had to explicitly specify it using option <span class="code">data_type</span>, in
order to avoid using the same sub-section name twice (see the section <em><a
href="#including_same_sql_type_2x">Including the same SQL type more than once in a plot</a></em> for
details). It will be labeled <span class="code">WMR100 inTemp</span>. The results look like this:
</p>
<img src='images/daycompare.png' alt="Comparing temperatures"/>
<h2 id="stupid_detail">Stupid detail</h2>
<p>
At first, I could not get this example to work. The problem turned out to be that the RPi was processing
things just a beat behind the Dell, so the temperature for the "current" time wasn't ready when the Dell
needed it. I kept getting <span class="code">N/A</span>. To avoid this, I introduced the tag <span
class="code">$latest</span>, which uses the last available timestamp in the binding, which may or may not be
the same as what <span class="code">$current</span> uses. That's why the example above uses <span
class="code">$latest</span> instead of <span class="code">$current</span>.
</p>
<h1 id="localization">Localization</h1>
<p>
This section is provides suggestions for localization, including translation to different languages and display of data in formats specific to a locale.
</p>
<h2>How to localize a single report</h2>
<p>
What happens when you come accross a report that you like, but it is written in another language, or uses time formats that are foreign to your customs? This section explains how to convert the report to local formats and language.
</p>
<h3>Translate the templates</h3>
<p>
Translate the text in each template file to the target language. Any strings that are not specified using tags will need to be translated.
</p>
<p>
In every HTML template (these typically have a file suffix of <span class="code">.html.tmpl</span>) change the HTML "lang" attribute to the target language. For example, for Spanish use <span class="code">es</span>:
</p>
<pre class="tty">
&lt;!DOCTYPE html&gt;
&lt;html lang="<span class="highlight">es</span>"&gt;
&lt;head&gt;
&lt;meta charset="UTF-8"&gt;
...
</pre>
<p>
The <span class="code">lang</span> can be language-only, e.g., <span class="code">en</span>, or it can be a full language-country code, e.g., <span class="code">en-US</span>.
</p>
<p>
<a href="https://www.w3schools.com/tags/ref_language_codes.asp">language codes</a><br/>
<a href="https://www.w3schools.com/tags/ref_country_codes.asp">country codes</a><br/>
</p>
<h3>Translate labels and modify local options</h3>
<p>
Go through the report options that are used by the skin, translating labels and modifying formats to follow
local conventions. You can easily localize all of your reports by doing this in the section <span
class="code">[StdReport] / [[Defaults]]</span> in the WeeWX configuration file
<span class="code">weewx.conf</span>.
</p>
<p>
The default time formats should use whatever locale is defined on the computer running WeeWX. For example,
the format for current time, <span class="code">current = %x %X</span>, results in <span class="code">01/13/19 10:01:01</span>
when the locale is United States english (<span class="code">en_US</span>), but results in <span
class="code">13.01.2019 10:01:01</span> when the locale is German (<span class="code">de_DE</span>).
</p>
<p>
First try the generic date (<span class="code">%x</span>), time (<span class="code">%X</span>), and day
(<span class="code">%A</span>) formats &mdash; these will use the locale of your computer. If you still find those
foreign, you can customize using the <a
href="https://docs.python.org/2/library/datetime.html#strftime-and-strptime-behavior">specific
directives</a>.
</p>
<p>
Here is a list of the options in <span class="code">[StdReport] / [[Defaults]]</span>, with the
options you may need to change <span class="highlight">highlighted.</span>:
</p>
<pre class="tty">
[StdReport]
[[Defaults]]
[[[Units]]]
...
[[[[Labels]]]]
...
day = <span class="highlight">" day", " days"</span>
hour = <span class="highlight">" hour", " hours"</span>
minute = <span class="highlight">" minute", " minutes"</span>
second = <span class="highlight">" second", " seconds"</span>
[[[[TimeFormats]]]]
day = <span class="highlight">%X</span>
week = <span class="highlight">%X (%A)</span>
month = <span class="highlight">%x %X</span>
year = <span class="highlight">%x %X</span>
rainyear = <span class="highlight">%x %X</span>
current = <span class="highlight">%x %X</span>
ephem_day = <span class="highlight">%X</span>
ephem_year = <span class="highlight">%x %X</span>
[[[[Ordinates]]]]
directions = <span class="highlight">N, NNE, NE, ENE, E, ESE, SE, SSE, S, SSW, SW, WSW, W, WNW, NW, NNW, N/A</span>
[[[Labels]]]
hemispheres = <span class="highlight">N, S, E, W</span>
latlon_formats = "%02d", "%03d", "%05.2f"
[[[[Generic]]]]
barometer = <span class="highlight">Barometer</span>
dewpoint = <span class="highlight">Dew Point</span>
ET = <span class="highlight">ET</span>
heatindex = <span class="highlight">Heat Index</span>
inHumidity = <span class="highlight">Inside Humidity</span>
inTemp = <span class="highlight">Inside Temperature</span>
outHumidity = <span class="highlight">Humidity</span>
outTemp = <span class="highlight">Temperature</span>
radiation = <span class="highlight">Radiation</span>
rain = <span class="highlight">Rain</span>
rainRate = <span class="highlight">Rain Rate</span>
UV = <span class="highlight">UV Index</span>
windDir = <span class="highlight">Wind Direction</span>
windGust = <span class="highlight">Gust Speed</span>
windGustDir = <span class="highlight">Gust Direction</span>
windSpeed = <span class="highlight">Wind Speed</span>
windchill = <span class="highlight">Wind Chill</span>
windgustvec = <span class="highlight">Gust Vector</span>
windvec = <span class="highlight">Wind Vector</span>
extraTemp1 = <span class="highlight">Temperature1</span>
extraTemp2 = <span class="highlight">Temperature2</span>
extraTemp3 = <span class="highlight">Temperature3</span>
# Sensor status indicators
rxCheckPercent = <span class="highlight">Signal Quality</span>
txBatteryStatus = <span class="highlight">Transmitter Battery</span>
windBatteryStatus = <span class="highlight">Wind Battery</span>
rainBatteryStatus = <span class="highlight">Rain Battery</span>
outTempBatteryStatus = <span class="highlight">Outside Temperature Battery</span>
inTempBatteryStatus = <span class="highlight">Inside Temperature Battery</span>
consBatteryVoltage = <span class="highlight">Console Battery</span>
heatingVoltage = <span class="highlight">Heating Voltage</span>
supplyVoltage = <span class="highlight">Supply Voltage</span>
referenceVoltage = <span class="highlight">Reference Voltage</span>
[[[Almanac]]]
moon_phases = <span class="highlight">New, Waxing crescent, First quarter, Waxing gibbous, Full, Waning gibbous, Last quarter, Waning crescent</span>
</pre>
<h3 id="environment_variable_LANG">
Set the environment variable <span class="code">LANG</span>
</h3>
<p>
Finally, you will need to set the environment variable <span class="code">LANG</span> to reflect your locale. For example, assuming you set
</p>
<pre class="tty">$ export LANG=es_ES.UTF-8</pre>
<p>
before running WeeWX, then the local Spanish names for days of the week and months of the year will be used.
The decimal point for numbers will also be modified appropriately.
</p>
<h2>How to create a skin for multiple locales</h2>
<p>
Let's say that you have created a new skin. How do you package that skin for use in other languages and locales? This example shows how to translate the <em>Seasons</em> skin for multiple languages.
</p>
<p>
Create a directory for each language. Each directory contains template files and a skin configuration file. Any text in each template file should be in the target language. Ensure that each HTML template file has the <span class="code">lang</span> set properly.
</p>
<p>
Translate observation names and other labels, and put them into the skin configuration files. There should be one configuration file for each language.
</p>
<p>
Ensure that any locale-specific formatting is specified properly. For example, consider the <span class="code">current</span> time, which is parameterized in <span class="code">[Units][[TimeFormats]]</span>. Users in a locale that uses 24-hour time expect the format <span class="code">%H:%M</span> whereas users in a locale that uses 12-hour time expect <span class="code">%I:%M %p</span>. To make each translation independent of the computer on which the reports will be generated, you must use the specific localization; if you specify the generic format <span class="code">%X</span>, that will result in the format used by whatever locale is set in the environment in which WeeWX is running.
</p>
<p>
The result will be a directory of templates and configurations, with one directory for each language/localization.
</p>
<pre class="tty">
skins/Seasons/
skins/Seasons/en/skin.conf
skins/Seasons/en/index.html.tmpl
skins/Seasons/es/skin.conf
skins/Seasons/es/index.html.tmpl
skins/Seasons/de/skin.conf
skins/Seasons/de/index.html.tmpl
skins/Seasons/fr/skin.conf
skins/Seasons/fr/index.html.tmpl</pre>
<p>
Users will then create each report in the WeeWX configuration file.
</p>
<pre class="tty">
[StdReport]
[[Seasons_en]]
skin = Seasons/en
HTML_ROOT = public_html/en
[[Seasons_es]]
skin = Seasons/es
HTML_ROOT = public_html/es
[[Seasons_de]]
skin = Seasons/de
HTML_ROOT = public_html/de
[[Seasons_fr]]
skin = Seasons/fr
HTML_ROOT = public_html/fr</pre>
<p>
The result is multiple reports, each in a different language and localization, independent of the locale of the computer on which they were generated.
</p>
<h1 id="service_engine">Customizing the WeeWX service engine </h1>
<p>This is an advanced topic intended for those who wish to try their hand at extending the internal engine in
WeeWX. Before attempting these examples, you should be reasonably proficient with Python.
</p>
<p class="warning">Please note that the API to the service engine may change in future versions!</p>
<p>
At a high level, WeeWX consists of an <em>engine</em> that is responsible for managing a set of
<em>services</em>. A service consists of a Python class which binds its member functions to various <em>events</em>.
The engine arranges to have the bound member function called when a specific event happens, such as a new
LOOP packet arriving.
</p>
<p>
The services are specified in lists in the <a href="usersguide.htm#engine_services"><span class="code">[Engine][[Services]]</span></a> stanza of the configuration file. The <span class="code">[[Services]]</span> section lists all the services to be run, broken up into different <em>service lists</em>.
</p>
<p>
These lists are designed to orchestrate the data as it flows through the WeeWX engine. For example, you want
to make sure data has been processed by, for example, running it through the quality control service, <span
class="code">StdQC</span>, before putting them in the database. Similarly, the reporting system must come
<em>after</em> the archiving service. These groups insure that things happen in the proper sequence.
</p>
<p>
See the table <a href="#default_services">Default services</a> for a list of the services that are
normally run.
</p>
<h2 id="Customizing_a_service">Modifying an existing service</h2>
<p>
The service <span class="code">weewx.engine.StdPrint</span> prints out new LOOP and archive packets to the
console when they arrive. By default, it prints out the entire record, which generally includes a lot of
possibly distracting information and can be rather messy. Suppose you do not like this, and want it to print
out only the time, barometer reading, and the outside temperature whenever a new LOOP packet arrives. This
could be done by subclassing the default print service <span class="code">StdPrint</span> and overriding
member function <span class="code">new_loop_packet()</span>.
</p>
<p>
Create the file <span class="code">user/myprint.py</span>:
</p>
<pre class="tty">from weewx.engine import StdPrint
from weeutil.weeutil import timestamp_to_string
class MyPrint(StdPrint):
# Override the default new_loop_packet member function:
def new_loop_packet(self, event):
packet = event.packet
print "LOOP: ", timestamp_to_string(packet['dateTime']),
"BAR=", packet.get('barometer', 'N/A'),
"TEMP=", packet.get('outTemp', 'N/A')</pre>
<p>
This service substitutes a new implementation for the member function <span
class="code">new_loop_packet</span>. This implementation prints out the time, then the barometer reading (or
<span class="code">N/A</span> if it is not available) and the outside temperature (or <span
class="code">N/A</span>).
</p>
<p>
You then need to specify that your print service class should be loaded instead of the default <span
class="code">StdPrint</span> service. This is done by substituting your service name for <span class="code">StdPrint</span>
in <span class="code">service_list</span>, located in <span class="code">[Engine][[Services]]</span>:
</p>
<pre class="tty">[Engine]
[[Services]]
...
report_services = <span class="highlight">user.myprint.MyPrint</span>, weewx.engine.StdReport</pre>
<p>
Note that the <span class="code">report_services</span> must be all on one line. Unfortunately, the parser
<span class="code">ConfigObj</span> does not allow options to be continued on to following lines.
</p>
<h2 id="Adding_a_service">Creating a new service</h2>
<p>
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 <span class="code">StdService</span>, and then adding
the functionality you need. Here is an example that implements an alarm, which sends off an email when an
arbitrary expression evaluates <span class="code">True</span>.
</p>
<p>
This example is included in the standard distribution as <span class="code">examples/alarm.py:</span>
</p>
<pre class="tty">
import smtplib
import socket
import syslog
import threading
import time
from email.mime.text import MIMEText
import weewx
from weeutil.weeutil import timestamp_to_string, option_as_list
from weewx.engine import StdService
# Inherit from the base class StdService:
class MyAlarm(StdService):
"""Service that sends email if an arbitrary expression evaluates true"""
def __init__(self, engine, config_dict):
# Pass the initialization information on to my superclass:
super(MyAlarm, self).__init__(engine, config_dict)
# This will hold the time when the last alarm message went out:
self.last_msg_ts = 0
try:
# Dig the needed options out of the configuration dictionary.
# If a critical option is missing, an exception will be raised and
# the alarm will not be set.
self.expression = config_dict['Alarm']['expression']
self.time_wait = int(config_dict['Alarm'].get('time_wait', 3600))
self.timeout = int(config_dict['Alarm'].get('timeout', 10))
self.smtp_host = config_dict['Alarm']['smtp_host']
self.smtp_user = config_dict['Alarm'].get('smtp_user')
self.smtp_password = config_dict['Alarm'].get('smtp_password')
self.SUBJECT = config_dict['Alarm'].get('subject', "Alarm message from weewx")
self.FROM = config_dict['Alarm'].get('from', 'alarm@example.com')
self.TO = option_as_list(config_dict['Alarm']['mailto'])
syslog.syslog(syslog.LOG_INFO, "alarm: Alarm set for expression: '%s'" % self.expression)
# If we got this far, it's ok to start intercepting events:
self.bind(weewx.NEW_ARCHIVE_RECORD, self.new_archive_record) # NOTE 1
except KeyError as e:
syslog.syslog(syslog.LOG_INFO, "alarm: No alarm set. Missing parameter: %s" % e)
def new_archive_record(self, event):
"""Gets called on a new archive record event."""
# 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:
# Get the new archive record:
record = event.record
# Be prepared to catch an exception in the case that the expression contains
# a variable that is not in the record:
try: # NOTE 2
# Evaluate the expression in the context of the event archive record.
# Sound the alarm if it evaluates true:
if eval(self.expression, None, record): # NOTE 3
# Sound the alarm!
# Launch in a separate thread so it doesn't block the main LOOP thread:
t = threading.Thread(target=MyAlarm.sound_the_alarm, args=(self, record))
t.start()
# Record when the message went out:
self.last_msg_ts = time.time()
except NameError as e:
# The record was missing a named variable. Log it.
syslog.syslog(syslog.LOG_DEBUG, "alarm: %s" % e)
def sound_the_alarm(self, rec):
"""Sound the alarm in a 'try' block"""
# Wrap the attempt in a 'try' block so we can log a failure.
try:
self.do_alarm(rec)
except socket.gaierror:
# A gaierror exception is usually caused by an unknown host
syslog.syslog(syslog.LOG_CRIT, "alarm: unknown host %s" % self.smtp_host)
# Reraise the exception. This will cause the thread to exit.
raise
except Exception as e:
syslog.syslog(syslog.LOG_CRIT, "alarm: unable to sound alarm. Reason: %s" % e)
# Reraise the exception. This will cause the thread to exit.
raise
def do_alarm(self, rec):
"""Send an email out"""
# Get the time and convert to a string:
t_str = timestamp_to_string(rec['dateTime'])
# Log the alarm
syslog.syslog(syslog.LOG_INFO, 'alarm: Alarm expression "%s" evaluated True at %s' % (self.expression, t_str))
# 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'] = self.SUBJECT
msg['From'] = self.FROM
msg['To'] = ','.join(self.TO)
try:
# First try end-to-end encryption
s = smtplib.SMTP_SSL(self.smtp_host, timeout=self.timeout)
syslog.syslog(syslog.LOG_DEBUG, "alarm: using SMTP_SSL")
except (AttributeError, socket.timeout, socket.error):
syslog.syslog(syslog.LOG_DEBUG, "alarm: unable to use SMTP_SSL connection.")
# If that doesn't work, try creating an insecure host, then upgrading
s = smtplib.SMTP(self.smtp_host, timeout=self.timeout)
try:
# Be prepared to catch an exception if the server
# does not support encrypted transport.
s.ehlo()
s.starttls()
s.ehlo()
syslog.syslog(syslog.LOG_DEBUG,
"alarm: using SMTP encrypted transport")
except smtplib.SMTPException:
syslog.syslog(syslog.LOG_DEBUG,
"alarm: using SMTP unencrypted transport")
try:
# 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)
syslog.syslog(syslog.LOG_DEBUG, "alarm: logged in with user name %s" % self.smtp_user)
# Send the email:
s.sendmail(msg['From'], self.TO, msg.as_string())
# Log out of the server:
s.quit()
except Exception as e:
syslog.syslog(syslog.LOG_ERR, "alarm: SMTP mailer refused message with error %s" % e)
raise
# Log sending the email:
syslog.syslog(syslog.LOG_INFO, "alarm: email sent to: %s" % self.TO)
</pre>
<p>
This service expects all the information it needs to be in the configuration file <span class="code">weewx.conf</span>
in a new section called <span class="code">[Alarm]</span>. So, add the following lines to your configuration
file:
</p>
<pre class="tty">[Alarm]
expression = "outTemp &lt; 40.0"
time_wait = 3600
smtp_host = smtp.example.com
smtp_user = myusername
smtp_password = mypassword
mailto = auser@example.com, anotheruser@example.com
from = me@example.com
subject = "Alarm message from WeeWX!"</pre>
<p>
There are three important points to be noted in this example, each marked with a <span
class="code">NOTE</span> flag in the code.
</p>
<ol>
<li>Here is where the binding happens between an event, <span class="code">weewx.NEW_ARCHIVE_RECORD</span>
in this example, and a member function, <span class="code">self.new_archive_record</span>. When the event
<span class='code'>NEW_ARCHIVE_RECORD</span> occurs, the function <span class="code">self.new_archive_record</span>
will be called. There are many other events that can be intercepted. Look in the file <span
class="code">weewx/__init__.py</span>.
</li>
<li>Some hardware does not emit all possible observation types in every record. So, it's possible that a
record may be missing some types that are used in the expression. This try block will catch the <span
class="code">NameError</span> exception that would be raised should this occur.
</li>
<li>This is where the test is done for whether or not to sound the alarm. The <span
class="code">[Alarm]</span> configuration options specify that the alarm be sounded when <span
class="code">outTemp &lt; 40.0</span> evaluates <span class="code">True</span>, 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.
</li>
</ol>
<p>Another example expression could be:</p>
<pre class="tty">expression = "outTemp &lt; 32.0 and windSpeed &gt; 10.0"</pre>
<p>In this case, the alarm is sounded if the outside temperature drops below freezing and the wind speed is
greater than 10.0.
</p>
<p>
Note that units must be the same as whatever is being used in your database. That is, the same as what you
specified in option <a href="usersguide.htm#option_target_unit"><span class="code">target_unit</span></a>.
</p>
<p>
Option <span class="code">time_wait</span> is used to avoid a flood of nearly identical emails. The new
service will wait this long before sending another email out.
</p>
<p>
Email will be sent through the SMTP host specified by option <span class="code">smtp_host</span>. The
recipient(s) are specified by the comma separated option <span class="code">mailto</span>.
</p>
<p>
Many SMTP hosts require user login. If this is the case, the user and password are specified with options
<span class="code">smtp_user</span> and <span class="code">smtp_password</span>, respectively.
</p>
<p>
The last two options, <span class="code">from</span> and <span class="code">subject</span> are optional. If
not supplied, WeeWX will supply something sensible. Note, however, that some mailers require a valid "from"
email address and the one WeeWX supplies may not satisfy its requirements.
</p>
<p>
To make this all work, you must first copy the <span class="code">alarm.py</span> file to the <span
class="code">user</span> directory. Then tell the engine to load this new service by adding the service name
to the list <span class="code">report_services</span>, located in <span
class="code">[Engine][[Services]]</span>:
</p>
<pre class="tty">[Engine]
[[Services]]
report_services = weewx.engine.StdPrint, weewx.engine.StdReport<span
class="highlight">, user.alarm.MyAlarm</span></pre>
<p>
Again, note that the option <span class="code">report_services</span> must be all on one line &mdash; the
parser <span class="code">ConfigObj</span> does not allow options to be continued on to following lines.
</p>
<p>
In addition to this example, the distribution also includes a low-battery alarm (<span class="code">lowBattery.py</span>),
which is similar, except that it intercepts LOOP events (instead of archiving events).
</p>
<h2 id="Adding_2nd_source">Adding a second data source</h2>
<p>A very common problem is wanting to augment the data from your weather station with data from some other
device. Generally, you have two approaches for how to handle this:
</p>
<ul>
<li>Run two instances of WeeWX, each using its own database and <span class='code'>weewx.conf</span>
configuration file. The results are then combined in a final report, using WeeWX's ability <a
href="#Using_multiple_bindings">to use more than one database</a>. See the Wiki entry <a
href="https://github.com/weewx/weewx/wiki/weewx-multi"> <em>How to run multiple instances of WeeWX</em></a> for details on how to do this.
</li>
<li>Run one instance, but use a custom WeeWX service to augment the records coming from your weather station
with data from the other device.
</li>
</ul>
<p>This section covers the latter approach.</p>
<p>Suppose you have installed an electric meter at your house and you wish to correlate electrical usage with
the weather. The meter has some sort of connection to your computer, allowing you to download the total
power consumed. At the end of every archive interval you want to calculate the amount of power consumed
during the interval, then add the results to the record coming off your weather station. How would you do
this?
</p>
<p>Here is the outline of a service that retrieves the electrical consumption data and adds it to the archive
record. It assumes that you already have a function <span class='code'>download_total_power()</span> that,
somehow, downloads the amount of power consumed since time zero.
</p>
<p>
File <span class="code">user/electricity.py</span>
</p>
<pre class="tty">import weewx
from weewx.engine import StdService
class AddElectricity(StdService):
def __init__(self, engine, config_dict):
# Initialize my superclass first:
super(AddElectricity, self).__init__(engine, config_dict)
# Bind to any new archive record events:
self.bind(weewx.NEW_ARCHIVE_RECORD, self.new_archive_record)
self.last_total = None
def new_archive_record(self, event):
total_power = download_total_power()
if self.last_total:
net_consumed = total_power - self.last_total
event.record['electricity'] = net_consumed
self.last_total = total_power</pre>
<p>
This adds a new key <span class="code">electricity</span> to the record dictionary and sets it equal to the
difference between the amount of power currently consumed and the amount consumed at the last archive
record. Hence, it will be the amount of power consumed over the archive interval. The unit should be
Watt-hours.
</p>
<p>
As an aside, it is important that the function <span class='code'>download_total_power()</span> does not
delay very long because it will sit right in the main loop of the WeeWX engine. If it's going to cause a
delay of more than a couple seconds you might want to put it in a separate thread and feed the results to
<span class="code">AddElectricity</span> through a queue.
</p>
<p>
To make sure your service gets run, you need to add it to one of the service lists in <span class="code">weewx.conf</span>,
section <span class="code">[Engine]</span>, subsection <span class="code">[[Services]]</span>.
</p>
<p>
In our case, the obvious place for our new service is in <span class="code">data_services</span>. When
you're done, your section <span class="code">[Engine]</span> will look something like this:
</p>
<pre class="tty">
# This section configures the internal WeeWX engine.
[Engine]
[[Services]]
# This section specifies the services that should be run. They are
# grouped by type, and the order of services within each group
# determines the order in which the services will be run.
prep_services = weewx.engine.StdTimeSynch
data_services = <span class="highlight">user.electricity.AddElectricity</span>
process_services = weewx.engine.StdConvert, weewx.engine.StdCalibrate, weewx.engine.StdQC, weewx.wxservices.StdWXCalculate
archive_services = weewx.engine.StdArchive
restful_services = weewx.restx.StdStationRegistry, weewx.restx.StdWunderground, weewx.restx.StdPWSweather, weewx.restx.StdCWOP, weewx.restx.StdWOW, weewx.restx.StdAWEKAS
report_services = weewx.engine.StdPrint, weewx.engine.StdReport
</pre>
<h1 id="archive_database">Customizing the database</h1>
<p>For most users the default database will work just fine. It has the added advantage of being compatible with
the wview database. Nevertheless, there may be occasions where you may want to add a new SQL type to your
database, or change its unit system. This section shows you how to do this.
</p>
<h2 id="add_archive_type">Adding a new type to the database</h2>
<p>
It is not unusual to have observation types that exist in the loop packets and/or archive records, but are
not in the database schema. An example is the new observation type <span class="code">electricity</span>,
from the example, <a href="#Adding_2nd_source"><em>Adding a second data source</em></a>. If a type is
not in the database schema, then it will be a second class citizen in WeeWX's world: it cannot be used in a
plot, nor for most statistics. So how would you add such a type to the database schema?
</p>
<p>
The utility <a href="utilities.htm#wee_database_utility"><span class="code">wee_database</span></a> can do
this. It will create a new database that is similar to the old database, except it will have the new type in
its schema.
</p>
<p class="note" style="display: inline-block; width: 70%">
<strong>Note</strong><br/>It is possible to observe all of the observation types included in the loop
packets and archive records, whether or not they are stored in the database, by <a
href="usersguide.htm#Running_directly">running WeeWX directly</a>.
</p>
<p>Here's our general strategy:</p>
<ol>
<li>Extend the existing schema with the new type.
</li>
<li>Modify the <span class="code">wx_binding</span> to use the new schema instead of the old.
</li>
<li>Make sure you have the necessary permissions to create a new database.
</li>
<li>Use the utility <span class="code">wee_database</span> to create the new database and populate it with
data from the old database.
</li>
<li>Shuffle databases around so WeeWX will use the new database.
</li>
</ol>
<p>Here is the recipe that follows that strategy:</p>
<ol>
<li>
<p>
<strong>Extend the existing schema with the new type.</strong> The schema that comes with WeeWX is
the same as what wview uses. It's located in the file <span class="code">schemas/wview.py</span>
&mdash; take a look at it now. We could just modify it <em>in situ</em>, but that would run the risk
of confusing the two versions. Alternatively, we could copy the file over to a new location, and
then modify that. But, because our change is just a simple addition, we're going to import the wview
schema and just add on our new type.
</p>
<p>For this example, we are going to add the new type <span class="code">electricity</span>. Add the
following code to the bottom of a file:
</p>
<pre class="tty">import schemas.wview
schema_with_electricity = schemas.wview.schema + [('electricity', 'REAL')]</pre>
<p>
This creates a new schema with the name <span
class="code">user.electricity.schema_with_electricity</span>, that is just like the old one, except
it has a new type <span class="code">electricity</span> tacked on to the end.
</p>
<p>Now, where do we put this bit of code? Really, it can be put in any file that is guaranteed to run
before we try and store our new type. With this in mind, there are two obvious choices:
</p>
<ol type="a">
<li>Add it to the bottom of <span class="code">user/extensions.py</span>. This is a special file,
provided by WeeWX, which is guaranteed to run before the engine's main loop. It's a good place
to add types that already exist in the archive records (an example would be <span class="code">humidex</span>),
but don't yet exist in the database schema.
</li>
<li>If you've been following the example, <a href="#Adding_2nd_source"><em>Adding a second data source</em></a>, then you can just simply add the code to the bottom of the file you
already have, <span class="code">user/electricity.py</span>. This way, it will run before any
use of the new type. It has the added advantage that it keeps all "electricity" related
modifications in one place, making it easy to package up your changes as a new WeeWX extension.
</li>
</ol>
<li>
<p>
<strong>Modify <span class="code">wx_binding</span>.</strong> When it creates the new, modified
database, <span class="code">wee_database</span> needs to know to use your new schema instead of the
old, wview schema. You do this by changing the option <span class="code">schema</span> in section
<span class="code">[DataBindings]</span> in <span class="code">weewx.conf</span>.
</p>
<p> If you made the code additions in step 1 in file <span class="code">user/extensions.py</span>, then
it will look like this when you're done:
</p>
<pre class="tty">[DataBindings]
[[wx_binding]]
database = archive_sqlite
table_name = archive
manager = weewx.wxmanager.WXDaySummaryManager
<span class="highlight">schema = user.extensions.schema_with_electricity</span></pre>
<p>If you used <span class="code">user/electricity.py</span>, then it will look like this:
</p>
<pre class="tty">[DataBindings]
[[wx_binding]]
database = archive_sqlite
table_name = archive
manager = weewx.wxmanager.WXDaySummaryManager
<span class="highlight">schema = user.electricity.schema_with_electricity</span></pre>
</li>
<li>
<p>
<strong>Check permissions.</strong> <span class="code">wee_database</span> will create a new
database with the same name as the old, except with the suffix <span class="code">_new</span>
attached to the end. Make sure you have the necessary permissions to create it. In particular, if
you are using MySQL, you will need <span class="code">CREATE</span> privileges:
</p>
<pre class="tty">mysql&gt; <span class="cmd">GRANT select, update, create, delete, insert ON weewx_new.* TO weewx@localhost;</span></pre>
</li>
<li>
<p>
<strong>Create and populate the new database.</strong> Now run the utility <span class="code">wee_database</span>
with the <span class="code">--reconfigure</span> option and the path to the configuration file:
</p>
<p class="tty cmd">wee_database weewx.conf --reconfigure</p>
<p>
This will create a new database (nominally, <span class="code">weewx.sdb_new</span> if you are using
SQLite, <span class="code">weewx_new</span> if you are using MySQL) using the new schema and
populate it with data from the old database.
</p>
</li>
<li>
<p>
<strong>Shuffle the databases.</strong> Now arrange things so WeeWX can find the new database.
</p>
<p class="warning">
<strong>Warning!</strong><br/> Make a backup of the data before doing any of the next steps!
</p>
<p>
You can either shuffle the databases around so the new database has the same name as the old
database, or edit <span class="code">weewx.conf</span> to use the new database name. To do the
former:
</p>
<p>For SQLite:</p>
<pre class="tty cmd">cd <span class="symcode">SQLITE_ROOT</span>
mv weewx.sdb_new weewx.sdb</pre>
<p>For MySQL:</p>
<pre class="tty"><span class="cmd">mysql -u &lt;username&gt; --password=&lt;mypassword&gt;</span>
mysql&gt;<span class="cmd"> DROP DATABASE weewx;</span> # Delete the old database
mysql&gt;<span class="cmd"> CREATE DATABASE weewx;</span> # Create a new one with the same name
mysql&gt;<span class="cmd"> RENAME TABLE weewx_new.archive TO weewx.archive;</span> # Rename to the nominal name</pre>
</li>
<li>
<p>
It's worth noting that there's actually a hidden, last step: rebuilding the daily summaries inside
the new database. This will be done automatically by WeeWX at the next startup. Alternatively, it
can be done manually using the <a href="utilities.htm#wee_database_utility"><span class="code">wee_database</span></a>
utility and the <span class="code">--rebuild-daily</span> option:
</p>
<pre class="tty cmd">wee_database weewx.conf --rebuild-daily</pre>
</li>
</ol>
<h2 id="Changing_the_unit_system">Changing the database unit system</h2>
<p>
Normally, data are stored in the databases using US Customary units and, normally, you don't care; it is an
"implementation detail". Data can always be displayed using any set of units you want. The section <a
href="#how_to_change_units"><em>How to change units</em></a> explains how to do this. Nevertheless, there
may be special situations where you wish to store the data in Metric units. For example, you may need to
allow direct programmatic access to the database from another piece of software that expects metric units.
</p>
<p>
You should not change the database unit system midstream. That is, do not start with one
unit system then, in the middle of the database, switch to another. See the section <span class="code"> <a
href="usersguide.htm#StdConvert">[StdConvert]</a></span> in the WeeWX User's Guide. However, you can
reconfigure the database by copying it to a new database, performing the unit conversion along the way. You
then use this new database.
</p>
<p>Here is the general strategy:</p>
<ol>
<li>Modify <span class="code">weewx.conf</span> to reflect your choice of the new unit system to use. Your
choices are <span class='code'>US</span>, <span class="code">METRIC</span>, or <span class="code">METRICWX</span>.
See the <em><a href="#units">Appendix: Units</a></em> for the exact differences between these three
choices.
</li>
<li>Make sure you have the necessary permissions to create the new database.
</li>
<li>Use the utility <a href="utilities.htm#wee_database_utility"><span class="code">wee_database</span></a>
to create the new database and populate it with data from the old database.
</li>
<li>Shuffle databases around so WeeWX will use the new database.
</li>
</ol>
<p>Here is the recipe that follows that strategy:</p>
<ol>
<li>
<p>
<strong>Modify <span class="code">weewx.conf</span>. </strong> Edit the configuration file to change
option <span class="code">target_unit</span> in section <span class="code"><a
href="usersguide.htm#StdConvert">[StdConvert]</a></span> to reflect your choice. If you are
switching to metric units, the option will look like:
</p>
<pre class="tty">[StdConvert]
target_unit = METRICWX</pre>
</li>
<li>
<p>
<strong>Check permissions.</strong> The reconfiguration utility will create a new database with the
same name as the old, except with the suffix <span class="code">_new</span> attached to the end.
Make sure you have the necessary permissions to do this. In particular, if you are using MySQL, you
will need <span class="code">CREATE</span> privileges.
</p>
</li>
<li>
<p>
<strong>Create and populate the new database.</strong> Now run the utility <span class="code">wee_database</span>
with the <span class="code">--reconfigure</span> option:
</p>
<pre class="tty cmd">wee_database weewx.conf --reconfigure</pre>
<p>
This will create a new database (nominally, <span class="code">weewx.sdb_new</span> if you are using
SQLite, <span class="code">weewx_new</span> if you are using MySQL), using the schema found in <span
class="code">user/schemas.py</span>, and populate it with data from the old database, while
performing the unit conversion.
</p>
</li>
<li>
<p>
<strong>Shuffle the databases.</strong> Now arrange things so WeeWX can find the new database.
</p>
<p class="warning">
<strong>Warning!</strong><br/> Make a backup of the data before doing any of the next steps!
</p>
<p>
You can either shuffle the databases around so the new database has the same name as the old
database, or edit <span class="code">weewx.conf</span> to use the new database name. To do the
former:
</p>
<p>For SQLite:</p>
<pre class="tty cmd">cd <span class="symcode">SQLITE_ROOT</span>
mv weewx.sdb_new weewx.sdb</pre>
<p>For MySQL:</p>
<pre class="tty"><span class="cmd">mysql -u &lt;username&gt; --password=&lt;mypassword&gt;</span>
mysql&gt;<span class="cmd"> DROP DATABASE weewx;</span> # Delete the old database
mysql&gt;<span class="cmd"> CREATE DATABASE weewx;</span> # Create a new one with the same name
mysql&gt;<span class="cmd"> RENAME TABLE weewx_new.archive TO weewx.archive;</span> # Rename to the nominal name</pre>
</li>
<li>
<p>
It's worth noting that there's actually a hidden, last step: rebuilding the daily summaries inside
the new database. This will be done automatically by WeeWX at the next startup. Alternatively, it
can be done manually using the <a href="utilities.htm#wee_database_utility"><span class="code">wee_database</span></a>
utility and the <span class="code">--rebuild-daily</span> option:
</p>
<pre class="tty cmd">wee_database weewx.conf --rebuild-daily</pre>
</li>
</ol>
<h2>Rebuilding the daily summaries</h2>
<p>
The <span class="code">wee_database</span> utility can also be used to rebuild the daily summaries:
</p>
<pre class="tty cmd">wee_database weewx.conf --rebuild-daily</pre>
<p>In most cases this will be sufficient; however, if anomalies remain in the daily summaries the daily summary
tables may be dropped first before rebuilding:
</p>
<pre class="tty cmd">wee_database weewx.conf --drop-daily</pre>
<p>
The summaries will automatically be rebuilt the next time WeeWX starts, or they can be rebuilt with the
utility:
</p>
<pre class="tty cmd">wee_database weewx.conf --rebuild-daily</pre>
<h1 id="customizing_units">Customizing units and unit groups</h1>
<p class="warning">
<strong>Warning!</strong><br/> This is an area that is rapidly changing in WeeWX. Presently, new units and
unit groups are added by manipulating the internal dictionaries in WeeWX (as described below). In the
future, they may be specified in <span class='code'>weewx.conf</span>.
</p>
<h2>Assigning a unit group</h2>
<p>
In the examples above, we created a new observation type, <span class='code'>electricity</span>, and added
it to the database schema. Now we would like to recognize that it is a member of the unit group <span
class='code'>group_energy</span> (which already exists), so it can enjoy the labels and formats already
provided for this group. This is done by extending the dictionary <span class='code'>weewx.units.obs_group_dict</span>.
</p>
<p>Add the following to our new services file <span class='code'>user/electricity.py</span>, just after the last
import statement:
</p>
<pre class="tty">import weewx
from weewx.engine import StdService
<span class="highlight">
import weewx.units
weewx.units.obs_group_dict['electricity'] = 'group_energy'</span>
class AddElectricity(StdService):
# [...]</pre>
<p>When our new service gets loaded by the WeeWX engine, these few lines will get run. They tell WeeWX that our
new observation type, <span class='code'>electricity</span>, is part of the unit group <span class='code'>group_energy</span>.
Once the observation has been associated with a unit group, the unit labels and other tag syntax will work
for that observation. So, now a tag like:
</p>
<pre class='tty'>$month.electricity.sum</pre>
<p>will return the total amount of electricity consumed for the month, in Watt-hours.</p>
<h2>Creating a new unit group</h2>
<p>
That was an easy one, because there was already an existing group, <span class='code'>group_energy</span>,
that covered our new observation type. But, what if we are measuring something entirely new, like force with
time? There is nothing in the existing system of units that covers things like newtons or pounds. We will
have to define these new units, as well as the unit group they can belong to.
</p>
<p>
We assume we have a new observation type, <span class='code'>force</span>, that we are measuring over time,
for a service named <span class='code'>
user/rocket.py</span>. We will create a new unit group, <span class='code'>group_force</span>, and new units,
<span class='code'>newton</span> and <span class='code'>pound</span>. Our new observation, <span
class='code'>force</span>, will belong to <span class='code'>group_force</span>, and will be measured in
units of <span class='code'>newton</span> or <span class='code'>pound</span>.
</p>
<ol>
<li>As before, we start by specifying what group our new observation type belongs to:
<pre class='tty'>
import weewx.units
weewx.units.obs_group_dict['force'] = 'group_force'</pre>
</li>
<li>Next, we specify what unit is used to measure force in the three standard unit systems used by weewx.
<pre class='tty'>
weewx.units.USUnits['group_force'] = 'pound'
weewx.units.MetricUnits['group_force'] = 'newton'
weewx.units.MetricWXUnits['group_force'] = 'newton'</pre>
</li>
<li>Then we specify what formats and labels to use for <span class='code'>newton</span> and <span
class='code'>pound</span>:
<pre class='tty'>
weewx.units.default_unit_format_dict['newton'] = '%.1f'
weewx.units.default_unit_format_dict['pound'] = '%.1f'
weewx.units.default_unit_label_dict['newton'] = ' newton'
weewx.units.default_unit_label_dict['pound'] = ' pound'</pre>
</li>
<li>Finally, we specify how to convert between them:
<pre class='tty'>
weewx.units.conversionDict['newton'] = {'pound': lambda x : x * 0.224809}
weewx.units.conversionDict['pound'] = {'newton': lambda x : x * 4.44822}
</pre>
</li>
</ol>
<p>As with the previous example of <span class='code'>group_energy</span>, the last step is to extend the <span
class='code'>
weewx.units.obs_group_dict</span> dictionary so that these new units can be used by our service. We do this by
adding the Python code from steps 1 - 4 into our new services file <span
class='code'>user/rocket.py</span>, at the head of that file and just after the last import statement
(as in the previous example).
</p>
<h2>Using the new units</h2>
<p>Now you've added a new type of units. How do you use it?</p>
<p>
Pretty much like any other units. For example, to do a plot of the month's electric consumption, totaled by
day, add this section to the <span class="code">[[month_images]]</span> section of <span class="code">skin.conf</span>:
</p>
<pre class="tty">[[[monthelectric]]]
[[[[electricity]]]]
aggregate_type = sum
aggregate_interval = 86400
label = Electric consumption (daily total)</pre>
<p>
This will cause the generation of an image <span class="code">monthelectric.png</span>, showing a plot of
each day's consumption for the past month.
</p>
<p>If you wish to use the new type in the templates, it will be available using the same syntax as any other
type. Here are some other tags that might be useful:
</p>
<table class="indent">
<tbody>
<tr class="first_row">
<td>Tag</td>
<td>Meaning</td>
</tr>
<tr>
<td class="code">$day.electricity.sum</td>
<td>Total consumption since midnight</td>
</tr>
<tr>
<td class="code">$year.electricity.sum</td>
<td>Total consumption since the first of the year</td>
</tr>
<tr>
<td class="code">$year.electricity.max</td>
<td>The most consumed during any archive period</td>
</tr>
<tr>
<td class="code">$year.electricity.maxsum</td>
<td>The most consumed during a day</td>
</tr>
<tr>
<td class="code">$year.electricity.maxsumtime</td>
<td>The day it happened.</td>
</tr>
<tr>
<td class="code">$year.electricity.sum_ge(5000.0)</td>
<td>The number of days of the year where more than 5.0 kWh of energy was consumed.
</td>
</tr>
</tbody>
</table>
<h1 id="porting">Porting to new hardware</h1>
<p>Naturally, this is an advanced topic but, nevertheless, I'd like to encourage any Python wizards out there to
give it a try. Of course, I have selfish reasons for this: I don't want to have to buy every weather station
ever invented, and I don't want my roof to look like a weather station farm!
</p>
<p>
A <em>driver</em> communicates with hardware. Each driver is a single python file that contains the code that is the interface between a device and WeeWX. A driver may communicate directly with hardware using a MODBus, USB, serial, or other physical interface. Or it may communicate over a network to a physical device or a web service.
</p>
<h2>General guidelines</h2>
<ul>
<li>The driver should emit data as it receives it from the hardware (no caching).
</li>
<li>The driver should emit only data it receives from the hardware (no "filling in the gaps").
</li>
<li>The driver should not modify the data unless the modification is directly related to the hardware (<i>e.g.</i>,
decoding a hardware-specific sensor value).
</li>
<li>If the hardware flags "bad data", then the driver should emit a null value for that datum (Python <span
class="code">None</span>).
</li>
<li>The driver should not calculate any derived variables (such as dewpoint). The service <span
class="code">StdWXService</span> will do that.
</li>
<li>However, if the hardware emits a derived variable, then the driver should emit it.
</li>
</ul>
<h2>Implement the driver</h2>
<p>
Create a file in the user directory, say <span class="code">mydriver.py</span>. This file will contain the
driver class as well as any hardware-specific code. Do not put it in the <span
class="code">weewx/drivers</span> directory or it will be deleted when you upgrade WeeWX.
</p>
<p>
Inherit from the abstract base class <span class="code">weewx.drivers.AbstractDevice</span>. Try to
implement as many of its methods as you can. At the very minimum, you must implement the first three
methods, <span class="code">loader</span>, <span class="code">hardware_name</span>, and <span class="code">genLoopPackets</span>.
</p>
<h3>
<span class="code">loader</span>
</h3>
<p>This is a factory function that returns an instance of your driver. It has two arguments: the configuration
dictionary, and a reference to the WeeWX engine.
</p>
<h3>
<span class="code">hardware_name</span>
</h3>
<p>
Return a string with a short nickname for the hardware, such as <span class="code">"ACME X90"</span>
</p>
<h3>
<span class="code">genLoopPackets</span>
</h3>
<p>
This should be a Python <a href="https://wiki.python.org/moin/Generators">generator function</a> that yields
loop packets, one after another. Don't worry about stopping it: the engine will do this when an archive
record is due. A "loop packet" is a dictionary. At the very minimum it must contain keys for the observation
time and for the units used within the packet.
</p>
<table class="indent" style="width: 60%">
<caption>Required keys</caption>
<tbody>
<tr>
<td class="code first_col">dateTime</td>
<td>The time of the observation in unix epoch time.</td>
</tr>
<tr>
<td class="code first_col">usUnits</td>
<td>The unit system used. <span class="code">weewx.US</span> for US customary, <span class="code">weewx.METRICWX</span>,
or <span class="code">weewx.METRIC</span> for metric. See the file <span
class="code">units.py</span>, dictionaries <span class="code">USUnits</span>, <span
class="code">MetricWXUnits</span>, and <span class="code">MetricUnits</span> for the exact
definition of each.
</td>
</tr>
</tbody>
</table>
<p>
Then include any observation types you have in the dictionary. Every packet need not contain the same set of
observation types. Different packets can use different unit systems, but all observations within a packet
must use the same unit system. If your hardware is capable of measuing an observation type but, for whatever
reason, its value is bad (maybe a bad checksum?), then set its value to <span class="code">None</span>. If
your hardware is incapable of measuring an observation type, then leave it out of the dictionary.
</p>
<p>
A couple of observation types are tricky. In particular, <span class="code">rain</span>. Generally, WeeWX
expects to see a packet with the amount of rain that fell in that packet period included as observation
<span class="code">rain</span>. It then sums up all the values to get the total rainfall and emits that in
the archive record. If your hardware does not provide this value, you might have to infer it from changes in
whatever value it provides, for example changes in the daily or monthly rainfall. I know this is not the
best solution, but it is the most general solution. Any alternatives are welcome!
</p>
<p>
Wind is another tricky one. It is actually broken up into four different observations: <span class="code">windSpeed</span>,
<span class="code">windDir</span>, <span class="code">windGust</span>, and <span
class="code">windGustDir</span>. Supply as many as you can. The directions should be compass directions in
degrees (0=North, 90=East, etc.).
</p>
<p>Be careful when reporting pressure. There are three observations related to pressure. Some stations report
only the station pressure, others calculate and report sea level pressures.
</p>
<table class="indent" style="width: 60%">
<caption>Pressure types</caption>
<tbody>
<tr>
<td class="code first_col">pressure</td>
<td>The <em>Station Pressure</em> (SP), which is the raw, absolute pressure measured by the station.
This is the true barometric pressure for the station.
</td>
</tr>
<tr>
<td class="code first_col">barometer</td>
<td>The <em>Sea Level Pressure</em> (SLP) obtained by correcting the <em>Station Pressure</em> for
altitude and local temperature. This is the pressure reading most commonly used by meteorologist to
track weather systems at the surface, and this is the pressure that is uploaded to weather services
by WeeWX. It is the station pressure reduced to mean sea level using local altitude and local
temperature.
</td>
</tr>
<tr>
<td class="code first_col">altimeter</td>
<td>The <em>Altimeter Setting</em> (AS) obtained by correcting the <em>Station Pressure</em> for
altitude. This is the pressure reading most commonly heard in weather reports. It is not the true
barometric pressure of a station, but rather the station pressure reduced to mean sea level using
altitude and an assumed temperature average.
</td>
</tr>
</tbody>
</table>
<h3>
<span class="code">genArchiveRecords()</span>
</h3>
<p>
If your hardware does not have an archive record logger, then WeeWX can do the record generation for you. It
will automatically collect all the types it sees in your loop packets then emit a record with the averages
(in some cases the sum or max value) of all those types. If it doesn't see a type, then it won't appear in
the emitted record.
</p>
<p>
However, if your hardware does have a logger, then you should implement method <span class="code">genArchiveRecords()</span>
as well. It should be a generator function that returns all the records since a given time.
</p>
<h3>
<span class="code">archive_interval</span>
</h3>
<p>
If you implement function <span class="code">genArchiveRecords()</span>, then you should also
implement <span class='code'>archive_interval</span> as either an attribute, or as a <a
href="https://docs.python.org/2/library/functions.html#property">property function</a>. It should return the
archive interval in seconds.
</p>
<h3>
<span class="code">getTime()</span>
</h3>
<p>If your hardware has an onboard clock and supports reading the time from it, then you may want to implement
this method. It takes no argument. It should return the time in Unix Epoch Time.
</p>
<h3>
<span class="code">setTime()</span>
</h3>
<p>
If your hardware has an onboard clock and supports <em>setting</em> it, then you may want to implement this
method. It takes no argument and does not need to return anything.
</p>
<h3>
<span class="code">closePort()</span>
</h3>
<p>
If the driver needs to close a serial port, terminate a thread, close a database, or perform any other
activity before the application terminates, then you must supply this function. WeeWX will call it if it
needs to shut down your console (usually in the case of an error).
</p>
<h2>Define the configuration</h2>
<p>
You then include a new section in the configuration file <span class="code">weewx.conf</span> that includes
any options your driver needs. It should also include an entry <span class="code">driver</span> that points
to where your driver can be found. Set option <span class="code">station_type</span> to your new section
type and your driver will be loaded.
</p>
<h2>Examples</h2>
<p>
The <span class='code'>fileparse</span> driver is perhaps the simplest example of a WeeWX driver. It reads
name-value pairs from a file and uses the values as sensor 'readings'. The code is actually packaged as an
extension, located in <span class="code">examples/fileparse</span>, making it a good example of not only
writing a device driver, but also of how to package an extension. The actual driver itself is in <span
class="code">examples/fileparse/bin/user/fileparse.py</span>.
</p>
<p>
Another good example is the simulator code located in <span class='code'>weewx/drivers/simulator.py</span>.
It's dirt simple and you can easily play with it. Many people have successfully used it as a starting point
for writing their own custom driver.
</p>
<p>
The Ultimeter (<span class="code">ultimeter.py</span>) and WMR100 (<span class="code">wmr100.py</span>)
drivers illustrate how to communicate with serial and USB hardware, respectively. They also show different
approaches for decoding data. Nevertheless, they are pretty straightforward.
</p>
<p>
The driver for the Vantage series is by far the most complicated. It actually multi-inherits from not only
<span class="code">AbstractDevice</span>, but also <span class="code">StdService</span>. That is, it also
participates in the engine as a service.
</p>
<p>Naturally, there are a lot of subtleties that have been glossed over in this high-level description. If you
run into trouble, look for help in the <a href="https://groups.google.com/forum/#!forum/weewx-development">
<span class="code">weewx-development</span> forum</a>.
</p>
<h1 id="extensions">Extensions</h1>
<p>
A key feature of WeeWX is its ability to be extended by installing 3rd party <em>extensions</em>. Extensions
are a way to package one or more customizations so they can be installed and distributed as a functional
group.
</p>
<p>Customizations typically fall into one of these categories:</p>
<ul>
<li>search list extension</li>
<li>template</li>
<li>skin</li>
<li>service</li>
<li>generator</li>
<li>driver</li>
</ul>
<p>
Take a look at the <a href="https://github.com/weewx/weewx/wiki"> WeeWX wiki </a> for a sampling of some of
the extensions that are available.
</p>
<!-- Utility wee_extension moved to utilities.htm -->
<h2>Creating an extension</h2>
<p>
Now that you have made some customizations, you might want to share those changes with other WeeWX users.
Put your customizations into an extension to make installation, removal, and distribution easier.
</p>
<p>Here are a few guidelines for creating extensions:</p>
<ul>
<li>Extensions should not modify or depend upon existing skins. An extension should include its own,
standalone skin to illustrate any templates, search list extension, or generator features.
</li>
<li>Extensions should not modify the database schemas. If it requires data not found in the default
databases, an extension should provide its own database and schema.
</li>
</ul>
<p>
Although one extension might use another extension, take care to write the dependent extension so that it fails gracefully. For example, a skin might use data the forecast extension, but what happens if the forecast extension is not installed? Make the skin display a message about "forecast not installed" but otherwise continue to function.
</p>
<h2>Packaging an extension</h2>
<p>
The structure of an extension mirrors that of WeeWX itself. If the customizations include a skin, the
extension will have a skins directory. If the customizations include python code, the extension will have a
<span class='code'>bin/user</span> directory.
</p>
<p>Each extension should also include:</p>
<ul>
<li><span class='code'>readme.txt</span> - a summary of what the extension does, list of pre-requisites (if
any), and instructions for installing the extension manually
</li>
<li><span class='code'>changelog</span> - an enumeration of changes in each release
</li>
<li><span class='code'>install.py</span> - python code used by the WeeWX ExtensionInstaller
</li>
</ul>
<p>
For example, here is the structure of a skin called <span class='code'>basic</span>:
</p>
<pre class='tty'>basic/
basic/changelog
basic/install.py
basic/readme.txt
basic/skins/
basic/skins/basic/
basic/skins/basic/basic.css
basic/skins/basic/current.inc
basic/skins/basic/favicon.ico
basic/skins/basic/hilo.inc
basic/skins/basic/index.html.tmpl
basic/skins/basic/skin.conf</pre>
<p>
Here is the structure of a search list extension called <span class='code'>xstats</span>:
</p>
<pre class='tty'>xstats/
xstats/changelog
xstats/install.py
xstats/readme.txt
xstats/bin/
xstats/bin/user/
xstats/bin/user/xstats.py</pre>
<p>
See the <span class='code'>extensions</span> directory of the WeeWX source for examples.
</p>
<p>To distribute an extension, simply create a compressed archive of the extension directory.</p>
<p>
For example, create the compressed archive for the <span class='code'>basic</span> skin like this:
</p>
<p class='tty cmd'>tar cvfz basic.tar.gz basic</p>
<p>Once an extension has been packaged, it can be installed using the <a
href="utilities.htm#wee_extension_utility">wee_extension</a> utility.
</p>
<h2>Default values</h2>
<p>
Whenever possible, an extension should <em>just work</em>, with a minimum of input from the user. At the same time, parameters for the most frequently requested options should be easily accessible and easy to modify. For skins, this might mean parameterizing strings into <span class="code">[Labels]</span> for easier customization. Or it might mean providing parameters in <span class="code">[Extras]</span> to control skin behavior or to parameterize links.
</p>
<p>
Some parameters <em>must</em> be specified, and no default value would be appropriate. For example, an uploader may require a username and password, or a driver might require a serial number or IP address. In these cases, use a default value in the configuration that will obviously require modification. The <em>username</em> might default to <em>REPLACE_ME</em>. Also be sure to add a log entry that indicates the feature is disabled until the value has been specified.
</p>
<p>
In the case of drivers, use the configuration editor to prompt for this type of required value.
</p>
<h1 id="report_options">Reference: report options</h1>
<p>
This section contains the options available in the skin configuration file, <span class="code">skin.conf</span>.
</p>
<p>
The most important options, the ones you are likely to have to customize, are <span
class="config_important"><strong>highlighted</strong></span>.
</p>
<p>
It is worth noting that, like the main configuration file <span class="code">weewx.conf</span>, UTF-8 is
used throughout.
</p>
<h2 class="config_section" id="Extras">[Extras]</h2>
<p>This section is available to add any static tags you might want to use in your templates.</p>
<p>
As an example, the <span class="code">skin.conf</span> file for the <em>Seasons</em> skin includes three options:
</p>
<table class="indent" style="width: 50%">
<tr class="first_row">
<td>Skin option</td>
<td>Template tag</td>
</tr>
<tr>
<td class="code">radar_img</td>
<td class="code">$Extras.radar_img</td>
</tr>
<tr>
<td class="code">radar_url</td>
<td class="code">$Extras.radar_url</td>
</tr>
<tr>
<td class="code">googleAnalyticsId</td>
<td class="code">$Extras.googleAnalyticsId</td>
</tr>
</table>
<p>
If you take a look at the template <span class="code">radar.inc</span> you will see examples of testing for these tags.
</p>
<p class="config_option">radar_img</p>
<p>Set to an URL to show a local radar image for your region.</p>
<p class="config_option">radar_url</p>
<p>If the radar image is clicked, the browser will go to this URL. This is usually used to show a more
detailed, close-up, radar picture.
</p>
<p>For me in Oregon, setting the two options to:</p>
<pre class="tty">
[Extras]
radar_img = http://radar.weather.gov/ridge/lite/N0R/RTX_loop.gif
radar_url = http://radar.weather.gov/ridge/radar.php?product=NCR&amp;rid=RTX&amp;loop=yes</pre>
<p>
results in a nice image of a radar centered on Portland, Oregon. When you click on it, it gives you a
detailed, animated view. If you live in the USA, take a look at the <a href="http://radar.weather.gov/">NOAA
radar website</a> to find a nice one that will work for you. In other countries, you will have to consult
your local weather service.
</p>
<p class="config_option">googleAnalyticsId</p>
<p>
If you have a <a href="http://www.google.com/analytics/">Google Analytics ID</a>, you can set it here. The
Google Analytics Javascript code will then be included, enabling analytics of your website usage. If
commented out, the code will not be included.
</p>
<h3>
Extending <span class="code">[Extras]</span>
</h3>
<p>Other tags can be added in a similar manner, including sub-sections. 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 sub-section.
</p>
<pre class="tty">[Extras]
[[video]]
still = video_capture.jpg
hyperlink = <a href="http://www.eatatjoes.com/video.html">http://www.eatatjoes.com/video.html</a>
</pre>
<p>Then in your template you could refer to these as:</p>
<pre class="tty">&lt;a href="$Extras.video.hyperlink"&gt;
&lt;img src="$Extras.video.still" alt="Video capture"/&gt;
&lt;/a&gt;</pre>
<h2 class="config_section">[Labels]</h2>
<p>This section defines various labels.</p>
<p class="config_option">hemispheres</p>
<p>
Comma separated list for the labels to be used for the four hemispheres. The default is <span class="code">N, S, E, W</span>.
</p>
<p class="config_option">latlon_formats</p>
<p>Comma separated list for the formatting to be used when converting latitude and longitude to strings. There
should be three elements:
</p>
<ol>
<li>The format to be used for whole degrees of latitude</li>
<li>The format to be used for whole degrees of longitude</li>
<li>The format to be used for minutes.</li>
</ol>
<p>This allows you to decide whether or not you want leading zeroes. The default includes leading zeroes and is
&quot;%02d&quot;, &quot;%03d&quot;, &quot;%05.2f&quot;
</p>
<h3 class="config_section" id="Labels_Generic">[[Generic]]</h3>
<p>This sub-section specifies default labels to be used for each observation type. For example, options</p>
<pre class="tty">inTemp = Temperature inside the house
outTemp = Outside Temperature
UV = UV Index</pre>
<p>
would cause the given labels to be used for plots of <span class="code">inTemp</span> and <span
class="code">outTemp</span>. If no option is given, then the observation type itself will be used
(<i>e.g.</i>, <span class="code">outTemp</span>).
</p>
<h2 class="config_section">[Almanac]</h2>
<p>This section controls what text to use for the almanac. It consists of only one entry</p>
<p class="config_option">moon_phases</p>
<p>
This option is a comma separated list of labels to be used for the eight phases of the moon. Default is
<span class="code">New,
Waxing crescent, First quarter, Waxing gibbous, Full, Waning
gibbous, Last quarter, Waning crescent</span>.
</p>
<h2 class="config_section">[Units]</h2>
<p>This section controls how units are managed and displayed.</p>
<h3 class="config_section">[[Groups]]</h3>
<p>
This sub-section lists all the <em>Unit Groups</em> and specifies which unit system is to be used for each
one of them.
</p>
<p>
As there are many different observational measurement types (such as <span class="code">outTemp</span>,
<span class="code">barometer</span>, 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 <em>unit groups</em>.
A unit group is something like <span class="code">group_temperature</span>. It represents the measurement
system to be used by all observation types that are measured in temperature, such as inside temperature
(type <span class="code">inTemp</span>), outside temperature (<span class="code">outTemp</span>), dewpoint (<span
class="code">dewpoint</span>), wind chill (<span class="code">windchill</span>), and so on. If you decide
that you want unit group <span class="code">group_temperature</span> to be measured in <span class="code">degree_C</span>
then you are saying <em>all</em> members of its group will be reported in degrees Celsius.
</p>
<p>
Note that the unit system is always specified in the singular. That is, specify <span
class="code">degree_C</span> or <span class="code">foot</span>, not <span class="code">degrees_C</span> or
<span class="code">feet</span>. See the <em><a href="#units">Appendix: Units</a></em> for more information,
including a concise summary of the groups, their members, and which options can be used for each group.
</p>
<p class="config_important">
<a class="config_option" id="group_altitude">group_altitude</a>
</p>
<p>
Which measurement unit to be used for altitude. Possible options are <span class="code">foot</span> or <span
class="code">meter</span>.
</p>
<p class="config_option">group_direction</p>
<p>
Which measurement unit to be used for direction. The only option is <span class="code">degree_compass</span>.
</p>
<p class="config_option">group_moisture</p>
<p>
The measurement unit to be used for soil moisture. The only option is <span class="code">centibar</span>.
</p>
<p class="config_option">group_percent</p>
<p>
The measurement unit to be used for percentages. The only option is <span class="code">percent</span>.
</p>
<p class="config_important">group_pressure</p>
<p>
The measurement unit to be used for pressure. Possible options are one of <span class="code">inHg</span>
(inches of mercury), <span class="code">mbar</span>, or <span class="code">hPa</span>.
</p>
<p class="config_option">group_radiation</p>
<p>
The measurement unit to be used for radiation. The only option is <span
class="code">watt_per_meter_squared</span>.
</p>
<p class="config_important">group_rain</p>
<p>
The measurement unit to be used for precipitation. Options are <span class="code">inch</span>, <span
class="code">cm</span>, or <span class="code">mm</span>.
</p>
<p class="config_important">group_rainrate</p>
<p>
The measurement unit to be used for rate of precipitation. Possible options are one of <span class="code">inch_per_hour</span>,
<span class="code">cm_per_hour</span>, or <span class="code">mm_per_hour</span>.
</p>
<p class="config_important">group_speed</p>
<p>
The measurement unit to be used for wind speeds. Possible options are one of <span class="code">mile_per_hour</span>,
<span class="code">km_per_hour</span>, <span class="code">knot</span>, or <span class="code">meter_per_second</span>.
</p>
<p class="config_important">group_speed2</p>
<p>
This group is similar to <span class="code">group_speed</span>, but is used for calculated wind speeds which
typically have a slightly higher resolution. Possible options are one <span
class="code">mile_per_hour2</span>, <span class="code">km_per_hour2</span>, <span class="code">knot2</span>,
or <span class="code">meter_per_second2</span>.
</p>
<p>
<a class="config_important" id="group_temperature">group_temperature</a>
</p>
<p>
The measurement unit to be used for temperatures. Options are <span class="code">degree_F</span> or <span
class="code">degree_C</span>.
</p>
<p class="config_option">group_volt</p>
<p>
The measurement unit to be used for voltages. The only option is <span class="code">volt</span>.
</p>
<h3 class="config_section" id="Units_StringFormats">[[StringFormats]]</h3>
<p>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
</p>
<pre class="tty">degree_C = %.1f
inch = %.2f</pre>
<p>
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 <a
href="http://docs.python.org/library/string.html#format-specification-mini-language"> formatting codes are
those used by Python</a>, and are very similar to C's <span class="code">sprintf()</span> codes.
</p>
<p>
You can also specify what string to use for an invalid or unavailable measurement (value <span class="code">None</span>).
For example,
</p>
<pre class="tty">NONE = " N/A "</pre>
<h3 class="config_section" id="Units_Labels">[[Labels]]</h3>
<p>This sub-section specifies what label is to be used for each measurement unit type. For example, the options
</p>
<pre class="tty">degree_F = °F
inch = ' in'</pre>
<p>
would cause all temperatures to have unit labels <span class="code">°F</span> and all precipitation to have
labels <span class="code">in</span>. If any special symbols are to be used (such as the degree sign)
they should be encoded in UTF-8. This is generally what most text editors use if you cut-and-paste from a
character map.
</p>
<p>If the label includes two values, then the first is assumed to be the singular form, the second the plural
form. For example,
</p>
<pre class="tty">foot = " foot", " feet"
...
day = " day", " days"
hour = " hour", " hours"
minute = " minute", " minutes"
second = " second", " seconds"</pre>
<h3 class="config_section" id="Units_TimeFormats">[[TimeFormats]]</h3>
<p>
This sub-section is used for time labels. It uses <a
href="https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior">strftime()</a> formats.
The default looks like this:
</p>
<pre class='tty'>
[[TimeFormats]]
# This section sets the string format to be used for each time scale.
# The values below will work in every locale, but may not look
# particularly attractive. See the Customization Guide for alternatives.
day = %X
week = %X (%A)
month = %x %X
year = %x %X
rainyear = %x %X
current = %x %X
ephem_day = %X
ephem_year = %x %X</pre>
<p>
The specifiers <span class='code'>%x</span>, <span class='code'>%X</span>, and <span class='code'>%A</span>
code locale dependent date, time, and weekday names, respectively. Hence, if you set an appropriate
environment variable <span class='code'>LANG</span>, then the date and times should follow local conventions
(see section <a href="#environment_variable_LANG">Environment variable LANG</a> for details on how to do
this). However, they will not look particularly pretty and you may want to change them. For example, I use
this in the U.S.:
</p>
<pre class="tty">
[[TimeFormats]]
#
# More attractive formats that work in most Western countries.
#
day = %H:%M
week = %H:%M on %A
month = %d-%b-%Y %H:%M
year = %d-%b-%Y %H:%M
rainyear = %d-%b-%Y %H:%M
current = %d-%b-%Y %H:%M
ephem_day = %H:%M
ephem_year = %d-%b-%Y %H:%M</pre>
<p>
The last two formats, <span class='code'>ephem_day</span> and <span class='code'>ephem_year</span> allow the
formatting to be set for almanac times The first, <span class="code">ephem_day</span>, is used for almanac
times within the day, such as sunrise or sunset. The second, <span class="code">ephem_year</span>, is used
for almanac times within the year, such as the next equinox or full moon.
</p>
<h3 class="config_section" id="Units_Ordinates">[[Ordinates]]</h3>
<p class="config_option">directions</p>
<p>
Set to the abbreviations to be used for ordinal directions. By default, this is <span class="code">N, NNE, NE, ENE, E,
ESE, SE, SSE, S, SSW, SW, WSW, W, WNW, NW, NNW, N</span>.
</p>
<h3 class="config_section">[[DegreeDays]]</h3>
<p class="config_important">
heating_base <br/> cooling_base
</p>
<p>Set to the base temperature for calculating heating and cooling degree-days, along with the unit to be used.
Examples:
</p>
<pre class="tty">heating_base = 65.0, degree_F
cooling_base = 20.0, degree_C</pre>
<h3 class="config_section" id="trend">[[Trend]]</h3>
<p class="config_option">time_delta</p>
<p>Set to the time difference over which you want trends to be calculated. The default is 3 hours.</p>
<p class="config_option">time_grace</p>
<p>
When searching for a previous record to be used in calculating a trend, a record within this amount of <span
class="code">time_delta</span> will be accepted. Default is 300 seconds.
</p>
<h2 id="CheetahGenerator">[CheetahGenerator]</h2>
<p>This section contains the options for the Cheetah generator.</p>
<p class="config_option">search_list</p>
<p>
This is the list of search list objects that will be scanned by the template engine, looking for tags. See
the section <em><a href="#defining_new_tags">Defining new tags</a></em> and the <a
href="http://pythonhosted.org/Cheetah/users_guide">Cheetah documentation</a> for details on search lists. If
no <span class="config_option">search_list</span> is specified, a default list will be used. The default
list is:
</p>
<pre class="tty">search_list = weewx.cheetahgenerator.Almanac, weewx.cheetahgenerator.Station, weewx.cheetahgenerator.Stats, weewx.cheetahgenerator.UnitInfo, weewx.cheetahgenerator.Extras</pre>
<p class="config_option">search_list_extensions</p>
<p>
This defines one or more search list objects that will be appended to the <span class="config_option">search_list</span>.
For example, the following adds alltime and forecast variables to the search list.
</p>
<pre class="tty">search_list_extensions = user.stats.MyStats, user.forecast.ForecastVariables</pre>
<p class="config_option" id='option_encoding'>encoding</p>
<p>
As Cheetah goes through the template, it substitutes strings for all tag values. This option controls which
encoding to use for the new strings. The encoding can be chosen on a per file basis. There are 3 possible
choices:
</p>
<table class="indent">
<tbody>
<tr class="first_row">
<td>Encoding</td>
<td>Comments</td>
</tr>
<tr>
<td class="code first_col">html_entities</td>
<td>Non 7-bit characters will be represented as HTML entities (<i>e.g.</i>, the degree sign will be
represented as <span class="code">&amp;#176;</span>)
</td>
</tr>
<tr>
<td class="code first_col">utf8</td>
<td>Non 7-bit characters will be represented in UTF-8.</td>
</tr>
<tr>
<td class="code first_col">strict_ascii</td>
<td>Non 7-bit characters will be ignored.</td>
</tr>
</tbody>
</table>
<p>
The encoding <span class="code">html_entities</span> is the default.
</p>
<p class="config_option" id="option_template">template</p>
<p>
The name of a template file. A template filename must end with <span class="code">.tmpl</span>. Filenames
are case-sensitive. If the template filename has the letters <span class="code">YYYY</span>, <span
class="code">MM</span>, <span class="code">WW</span> or <span class="code">DD</span> in its name, these will
be substituted for the year, month, week and day of month, respectively. So, a template with the name <span
class="code">summary-YYYY-MM.html.tmpl</span> would have name <span class="code">summary-2010-03.html</span>
for the month of March, 2010.
</p>
<p class="config_option" id="stale_age">stale_age</p>
<p>
File staleness age, in seconds. If the file is older than this age it will be generated from the template.
If no <span class="code">stale_age</span> is specified, then the file will be generated every time the
generator runs.
</p>
<p class="note" style="display: inline-block; width: 70%">
<strong>Note</strong><br/>Precise control over when a <em><a href="usersguide.htm#Reports">report</a></em>
is run is available through use of the <span class="code">report_timing</span> option in <span class="code">weewx.conf</span>.
The <span class="code">report_timing</span> option uses a CRON-like setting to control precisely when a
report is run. See the <em><a href="#customizing_gen_time">Scheduling reports</a></em> section for details
on the <span class="code">report_timing</span> option.
</p>
<p class="config_option">[[SummaryByDay]]</p>
<p>
The <span class="code">SummaryByDay</span> section defines some special behavior. Each template in this
section will be used multiple times, each time with a different per-day timespan. Be sure to include <span
class="code">YYYY</span>, <span class="code">MM</span>, and <span class="code">DD</span> in the filename of any template in this section.
</p>
<p class="config_option">[[SummaryByMonth]]</p>
<p>
The <span class="code">SummaryByMonth</span> section defines some special behavior. Each template in this
section will be used multiple times, each time with a different per-month timespan. Be sure to include <span
class="code">YYYY</span> and <span class="code">MM</span> in the filename of any template in this section.
</p>
<p class="config_option">[[SummaryByYear]]</p>
<p>
The <span class="code">SummaryByYear</span> section defines some special behavior. Each template in this
section will be used multiple times, each time with a different per-year timespan. Be sure to include <span
class="code">YYYY</span> in the filename of any template in this section.
</p>
<h2 class="config_section" id="ImageGenerator">[ImageGenerator]</h2>
<p>This section describes the various options available to the image generator.</p>
<div class="image image-right">
<img src="images/image_parts.png" alt="Part names in a WeeWX image"/>
<div class="image_caption">Part names in a WeeWX image</div>
</div>
<h3>Overall options</h3>
<p>These are options that affect the overall image.</p>
<p class="config_option">
image_width<br/> image_height
</p>
<p>The width and height of the image in pixels. Optional. Default is 300 x 180 pixels.</p>
<p class="config_option">image_background_color</p>
<p>
The background color of the whole image. Optional. Default is <span class='code'>0xf5f5f5</span>
("SmokeGray")
</p>
<p class="config_option">chart_background_color</p>
<p>
The background color of the chart itself. Optional. Default is <span class='code'>0xd8d8d8</span>.
</p>
<p class="config_option">chart_gridline_color</p>
<p>
The color of the chart grid lines. Optional. Default is <span class='code'>0xa0a0a0</span>
</p>
<div class="image image-right" style="clear: right">
<div class="image">
<img src="images/antialias.gif" alt="Effect of anti_alias option"/>
<div class="image_caption">
A GIF showing the same image with <span class="code">anti_alias=1</span>, <span
class="code">2</span>, and <span class="code">4</span>.
</div>
</div>
<div class="image">
<img src="images/weektempdew.png" alt="Example of day/night bands"/>
<div class="image_caption">Example of day/night bands in a one week image
</div>
</div>
</div>
<p class="config_option">anti_alias</p>
<p>
Setting to 2 or more might give a sharper image, with fewer jagged edges. Experimentation is in order.
Default is <span class="code">1</span>.
</p>
<p class="config_option">show_daynight</p>
<p>
Set to <span class="code">true</span> to show day/night bands in an image. Otherwise, set to false. This
only looks good with day or week plots. Optional. Default is <span class="code">false</span>.
</p>
<p class="config_option">daynight_day_color</p>
<p>
The color to be used for the daylight band. Optional. Default is <span class="code">0xffffff</span>.
</p>
<p class="config_option">daynight_night_color</p>
<p>
The color to be used for the nighttime band. Optional. Default is <span class="code">0xf0f0f0</span>, a dark
gray.
</p>
<p class="config_option">daynight_edge_color</p>
<p>
The color to be used in the transition zone between night and day. Optional. Default is <span class="code">0xefefef</span>,
a mid-gray.
</p>
<h3>Various label options</h3>
<p>These are options for the various labels used in the image.</p>
<p class="config_option">top_label_font_path</p>
<p>
The path to the font to be use for the top label. Optional. If not given, or if WeeWX cannot find the font,
then the default PIL font will be used.
</p>
<p class="config_option">top_label_font_size</p>
<p>
The size of the top label in pixels. Optional. The default is <span class='code'>10</span>.
</p>
<p class="config_option">unit_label_font_path</p>
<p>
The path to the font to be use for the unit label. Optional. If not given, or if WeeWX cannot find the font,
then the default PIL font will be used.
</p>
<p class="config_option">unit_label_font_size</p>
<p>
The size of the unit label in pixels. Optional. The default is <span class='code'>10</span>.
</p>
<p class="config_option">unit_label_font_color</p>
<p>
The color of the unit label font. Optional. Default is <span class='code'>black</span>.
</p>
<p class="config_option">bottom_label_font_path</p>
<p>
The path to the font to be use for the bottom label. Optional. If not given, or if WeeWX cannot find the
font, then the default PIL font will be used.
</p>
<p class="config_option">bottom_label_font_size</p>
<p>
The size of the bottom label in pixels. Optional. The default is <span class='code'>10</span>.
</p>
<p class="config_option">bottom_label_font_color</p>
<p>
The color of the bottom label font. Optional. Default is <span class='code'>black</span>.
</p>
<p class="config_option">bottom_label_format</p>
<p>
The format to be used for the bottom label. It should be a <a
href="https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior"> strftime format</a>.
Optional. Default is <span class="code">'%m/%d/%y
%H:%M'</span>.
</p>
<p class="config_option">bottom_label_offset</p>
<p>The margin of the bottom label from the bottom of the plot. Default is 3.</p>
<p class="config_option">axis_label_font_path</p>
<p>
The path to the font to be use for the x- and y-axis labels. Optional. If not given, or if WeeWX cannot find
the font, then the default PIL font will be used.
</p>
<p class="config_option">axis_label_font_size</p>
<p>
The size of the x- and y-axis labels in pixels. Optional. The default is <span class='code'>10</span>.
</p>
<p class="config_option">axis_label_font_color</p>
<p>
The color of the x- and y-axis label font. Optional. Default is <span class='code'>black</span>.
</p>
<p class="config_option">x_label_format</p>
<p>
The format to be used for the time labels on the x-axis. It should be a <a
href="https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior"> strftime format</a>.
Optional. If not given, a sensible format will be chosen automatically.
</p>
<p class="config_option">x_interval</p>
<p>
The time interval in seconds between x-axis tick marks. Optional. If not given, a suitable default will be
chosen.
</p>
<p class="config_option">x_label_spacing</p>
<p>
Specifies the ordinal increment between labels on the x-axis: For example, 3 means a label every 3rd tick
mark. Optional. The default is <span class='code'>2</span>.
</p>
<p class="config_option">y_label_spacing</p>
<p>
Specifies the ordinal increment between labels on the y-axis: For example, 3 means a label every 3rd tick
mark. Optional. The default is <span class='code'>2</span>.
</p>
<p class="config_option">y_nticks</p>
<p>
The nominal number of ticks along the y-axis. The default is <span class='code'>10</span>.
</p>
<p class="config_option">stale_age</p>
<p>
Image file staleness age, in seconds. If the image file is older than this age it will be generated. If no
<span class="code">stale_age</span> is specified, then the image file will be generated every time the
generator runs.
</p>
<h3>Plot scaling options</h3>
<p class="config_option">time_length</p>
<p>
The nominal length of the time period to be covered in seconds. The exact length of the x-axis is chosen by
the plotting engine to cover this period. Optional. Default is <span class="code">86400</span> (one day).
</p>
<p class="config_option">yscale</p>
<p>
A 3-way tuple (<span class="code">ylow</span>, <span class="code">yhigh</span>, <span class="code">min_interval</span>),
where <span class="code">ylow</span> and <span class="code">yhigh</span> are the minimum and maximum y-axis
values, respectively, and <span class="code">min_interval</span> is the minimum tick interval. If set to
<span class="code">None</span>, the corresponding value will be automatically chosen. Optional. Default is
<span class="code">None, None, None</span>. (Choose the y-axis minimum, maximum, and minimum increment
automatically.)
</p>
<h3>Compass rose options</h3>
<div class="image image-right" style="width: 300px">
<img src="images/daywindvec.png" alt="Example of a progressive vector plot"/>
<div class="image_caption">Example of a vector plot with a compass rose in the lower-left
</div>
</div>
<p class="config_option">rose_label</p>
<p>
The label to be used in the compass rose to indicate due North. Optional. Default is <span
class="code">N</span>.
</p>
<p class="config_option">rose_label_font_path</p>
<p>
The path to the font to be use for the rose label (the letter "N," indicating North). Optional. If not
given, or if WeeWX cannot find the font, then the default PIL font will be used.
</p>
<p class="config_option">rose_label_font_size</p>
<p>
The size of the compass rose label in pixels. Optional. The default is <span class="code">10</span>.
</p>
<p class="config_option">rose_label_font_color</p>
<p>The color of the compass rose label. Optional. Default is the same color as the rose itself.</p>
<p class="config_option">vector_rotate</p>
<p>
Causes the vectors to be rotated by this many degrees. Positive is clockwise. If westerly winds dominate at
your location (as they do at mine), then you may want to specify <span class="code">+90</span> for this
option. This will cause the average vector to point straight up, rather than lie flat against the x-axis.
Optional. The default is <span class='code'>0</span>.
</p>
<h3>Shared plot line options</h3>
<p>These are options shared by all the plot lines.</p>
<p class="config_option">chart_line_colors</p>
<p>
Each chart line is drawn in a different color. This option is a list of those colors. If the number of lines
exceeds the length of the list, then the colors wrap around to the beginning of the list. Optional. In the case of bar
charts, this is the color of the outline of the bar. Default is <span class="code">0xff0000, 0x00ff00, 0x0000ff</span>.
<br/>
<br/>
Individual line color can be overridden by using option <span class="code">color</span>.
</p>
<p class="config_option">chart_fill_colors</p>
<p>
A list of the color to be used as the fill of the bar charts. Optional. The default is to use the same color
as the outline color (option <span class="code">chart_line_colors</span>).
</p>
<p class="config_option">chart_line_width</p>
<p>
Each chart line can be drawn using a different line width. This option is a list of these widths. If the
number of lines exceeds the length of the list, then the widths wrap around to the beginning of the list.
Optional. Default is <span class="code">1, 1, 1</span>.
<br/>
<br/>
Individual line widths can be overridden by using option <span class="code">width</span>.
</p>
<h3>Individual line options</h3>
<p>These are options that are set for individual lines.</p>
<p class="config_option">aggregate_type</p>
<p>
The default is to plot every data point, but this is probably not a good idea for any plot longer than a
day. By setting this option, you can <em>aggregate</em> data by a set time interval. Available aggregation
types include <span class="code">avg</span>, <span class="code">sum</span>, <span class="code">max</span>,
<span class="code">min</span>, <span class="code">count</span>, <span class="code">last</span>, and <span
class="code">cumulative</span>.
</p>
<p class="config_option">aggregate_interval</p>
<p>
The time period over which the data should be aggregated. Required if <span
class="code">aggregate_type</span> has been set.
</p>
<p class="config_option">plot_type</p>
<p>
The type of plot for this line. Choices are <span class="code">line</span>, <span class="code">bar</span>,
or <span class="code">vector</span>. Optional. Default is <span class="code">line</span>.
</p>
<p class="config_option">color</p>
<p>
This option is to override the color for an individual line. Optional. Default is to use the color in <span
class="code">chart_line_colors</span>.
</p>
<p class="config_option">fill_color</p>
<p>
This option is to override the fill color for a bar chart. Optional. Default is to use the color in <span
class="code">chart_fill_colors</span>.
</p>
<p class="config_option">line_type</p>
<p>
The type of line to be used. Choices are <span class="code">solid</span> or <span class="code">none</span>.
Optional. Default is <span class="code">solid</span>.
</p>
<p class="config_option">marker_type</p>
<p>
The type of marker to be used to marke each data point. Choices are <span class="code">cross</span>, <span
class="code">x</span>, <span class="code">circle</span>, <span class="code">box</span>, or <span
class="code">none</span>. Optional. Default is <span class="code">none</span>.
</p>
<p class="config_option">marker_size</p>
<p>
The size of the marker. Optional. Default is <span class="code">8</span>.
</p>
<p class="config_option">line_gap_fraction</p>
<p>
If there is a gap between data points bigger than this fractional amount of the x-axis, then a gap will be
drawn, rather than a connecting line. See Section <em><a href="#line_gaps">Line gaps</a></em>. Optional. The
default is to always draw the line.
</p>
<p class="config_option">width</p>
<p>
This option is to override the line widthfor an individual line. Optional. Default is to use the width in
<span class="code">chart_line_width</span>.
</p>
<p class="config_option">label</p>
<p>The label to be used for this plot line in the top label. Optional. The default is to use the SQL variable
name.
</p>
<p class="config_option">data_type</p>
<p>
The SQL data type to be used for this plot line. For more information, see the section <em><a
href="#including_same_sql_type_2x">Including the same SQL type more than once in a plot</a></em>. Optional.
The default is to use the section name.
</p>
<h2 class="config_section">[CopyGenerator]</h2>
<p>
This section is used by generator <span class="code">weewx.reportengine.CopyGenerator</span> and controls
which files are to be copied over from the skin directory 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.
</p>
<p class="config_option">copy_once</p>
<p>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.
</p>
<p class="config_option">copy_always</p>
<p>This is a list of files that should be copied on every invocation. Wildcards can be used.</p>
<p>
Here is the <span class="code">[CopyGenerator]</span> section from the Standard <span
class="code">skin.conf</span>
</p>
<pre class="tty">[CopyGenerator]
# This section is used by the generator CopyGenerator
# List of files to be copied only the first time the generator runs
copy_once = backgrounds/*, weewx.css, mobile.css, favicon.ico
# List of files to be copied each time the generator runs
# copy_always = </pre>
<p>The Standard skin includes some background images, CSS files, and icons that need to be copied once. There
are no files that need to be copied every time the generator runs.
</p>
<h2 class="config_section" id="generators_section">[Generators]</h2>
<p>This section defines the list of generators that should be run.</p>
<p class="config_option">generator_list</p>
<p>This option controls which generators get run for this skin. It is a comma separated list. The generators
will be run in this order.
</p>
<p>
Here is the <span class="code">[Generators]</span> section from the Standard <span
class="code">skin.conf</span>
</p>
<pre class="tty">[Generators]
generator_list = weewx.cheetahgenerator.CheetahGenerator, weewx.imagegenerator.ImageGenerator, weewx.reportengine.CopyGenerator</pre>
<p>The Standard skin uses three generators: CheetahGenerator, ImageGenerator, and CopyGenerator.</p>
<h1 id="archive_types">Appendix: Archive Types</h1>
<p>
<em>Archive types</em> are weather observations that have come from your instrument and been stored in the
<em>archive database</em>, a SQL database. They represent the <em>current conditions</em> as of some time.
They are available to be used in two places:
</p>
<ul>
<li>In your template files as a tag with period <span class="code">$current</span>. Hence, the tag <span
class="code">$current.outTemp</span> represents the latest current outside temperature.
</li>
<li>In your plot graphs. Here, a line in the graph represents the set of current observations over a time
period. While each plot point in a graph may represent an aggregation, do not confuse this aggregation
with the statistical aggregation. The former is done with the archive database, the latter with the
statistical database.
</li>
</ul>
<p>
The following table shows all the possible archive types and whether they can be used in tag <span
class="code">$current</span> or in a plot. Note that just because a type appears in the table does not
necessarily mean that it is available for <em>your</em> station setup. That would depend on whether your
instrument supports the type.
</p>
<table class="indent">
<caption>Archive types</caption>
<tbody>
<tr class="first_row">
<td>Archive Type</td>
<td style="width: 200px">SQL Type<br/> <span style="font-size: 80%">(appears in archive
database)</span></td>
<td>Can be used <br/> in plots
</td>
<td>Can be used <br/> in tag <span class="code">$current</span></td>
</tr>
<tr>
<td class="first_col code">altimeter</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">barometer</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">consBatteryVoltage</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">dateTime</td>
<td>X</td>
<td><br/></td>
<td>X (represents current time)</td>
</tr>
<tr>
<td class="first_col code">dewpoint</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">ET</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">extraHumid1</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">extraHumid2</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">extraTemp1</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">extraTemp2</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">extraTemp3</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">hail</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">hailRate</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">heatindex</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">heatingTemp</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">heatingVoltage</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">inHumidity</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">inTemp</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">inTempBatteryStatus</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">interval</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">leafTemp1</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">leafTemp2</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">leafWet1</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">leafWet2</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">outHumidity</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">outTemp</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">outTempBatteryStatus</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">pressure</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">radiation</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">rain</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">rainBatteryStatus</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">rainRate</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">referenceVoltage</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">rxCheckPercent</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">soilMoist1</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">soilMoist2</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">soilMoist3</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">soilMoist4</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">soilTemp1</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">soilTemp2</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">soilTemp3</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">soilTemp4</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">supplyVoltage</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">txBatteryStatus</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">usUnits</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">UV</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">windvec</td>
<td> </td>
<td>X (special vector type)</td>
<td> </td>
</tr>
<tr>
<td class="first_col code">windBatteryStatus</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">windDir</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">windGust</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">windGustDir</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">windSpeed</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td class="first_col code">windchill</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
</tbody>
</table>
<h1 id="aggregation_types">Appendix: Aggregation types</h1>
<table class="indent">
<caption>Aggregation types</caption>
<tbody>
<tr class="first_row">
<td>Aggregation type</td>
<td>Meaning</td>
</tr>
<tr>
<td class="first_col code">avg</td>
<td>The average value in the aggregation period.</td>
</tr>
<tr>
<td class="first_col code">sum</td>
<td>The sum of values in the aggregation period.</td>
</tr>
<tr>
<td class="first_col code">count</td>
<td>The number of non-null values in the aggregation period.
</td>
</tr>
<tr>
<td class="first_col code">min</td>
<td>The minimum value in the aggregation period.</td>
</tr>
<tr>
<td class="first_col code">mintime</td>
<td>The time of the minimum value.</td>
</tr>
<tr>
<td class="first_col code">max</td>
<td>The maximum value in the aggregation period.</td>
</tr>
<tr>
<td class="first_col code">maxtime</td>
<td>The time of the maximum value.</td>
</tr>
<tr>
<td class="first_col code">maxmin</td>
<td>The maximum daily minimum in the aggregation period. Aggregation period must be one day or longer.
</td>
</tr>
<tr>
<td class="first_col code">maxmintime</td>
<td>The time of the maximum daily minimum.</td>
</tr>
<tr>
<td class="first_col code">minmax</td>
<td>The minimum daily maximum in the aggregation period. Aggregation period must be one day or longer.
</td>
</tr>
<tr>
<td class="first_col code">minmaxtime</td>
<td>The time of the minimum daily maximum.</td>
</tr>
<tr>
<td class="first_col code">maxsum</td>
<td>The maximum daily sum in the aggregation period. Aggregation period must be one day or longer.
</td>
</tr>
<tr>
<td class="first_col code">maxsumtime</td>
<td>The time of the maximum daily sum.</td>
</tr>
<tr>
<td class="first_col code">meanmin</td>
<td>The average daily minimum in the aggregation period. Aggregation period must be one day or longer.
</td>
</tr>
<tr>
<td class="first_col code">meanmax</td>
<td>The average daily maximum in the aggregation period. Aggregation period must be one day or longer.
</td>
</tr>
<tr>
<td class="first_col code">gustdir</td>
<td>The direction of the max gust in the aggregation period.
</td>
</tr>
<tr>
<td class="first_col code">last</td>
<td>The last value in the aggregation period.</td>
</tr>
<tr>
<td class="first_col code">lasttime</td>
<td>The time of the last value in the aggregation period.
</td>
</tr>
<tr>
<td class="first_col code">max_ge(val)</td>
<td>The number of days where the maximum value is greater than or equal to <em>val</em>. Aggregation
period must be one day or longer.
</td>
</tr>
<tr>
<td class="first_col code">max_le(val)</td>
<td>The number of days where the maximum value is less than or equal to <em>val</em>. Aggregation period
must be one day or longer.
</td>
</tr>
<tr>
<td class="first_col code">min_ge(val)</td>
<td>The number of days where the minimum value is greater than or equal to <em>val</em>. Aggregation
period must be one day or longer.
</td>
</tr>
<tr>
<td class="first_col code">min_le(val)</td>
<td>The number of days where the minimum value is less than or equal to <em>val</em>. Aggregation period
must be one day or longer.
</td>
</tr>
<tr>
<td class="first_col code">sum_ge(val)</td>
<td>The number of days where the sum of value is greater than or equal to <em>val</em>. Aggregation
period must be one day or longer.
</td>
</tr>
<tr>
<td class="first_col code">rms</td>
<td>The root mean square value in the aggregation period.
</td>
</tr>
<tr>
<td class="first_col code">vecavg</td>
<td>The vector average speed in the aggregation period.</td>
</tr>
<tr>
<td class="first_col code">vecdir</td>
<td>The vector averaged direction during the aggregation period.
</td>
</tr>
</tbody>
</table>
<h1 id="units">Appendix: Units</h1>
<p>
WeeWX offers three different <em>unit systems</em>:
</p>
<table class="indent" style="width: 80%">
<caption>The standard unit systems used within WeeWX</caption>
<tr class="first_row">
<td>Name</td>
<td>Encoded value</td>
<td>Note</td>
</tr>
<tr>
<td class="first_col code">US</td>
<td>0x01</td>
<td>U.S. Customary</td>
</tr>
<tr>
<td class="first_col code">METRICWX</td>
<td>0x11</td>
<td>Metric, with rain related measurements in <span class="code">mm</span> and speeds in <span
class="code">m/s</span>
</td>
</tr>
<tr>
<td class="first_col code">METRIC</td>
<td>0x10</td>
<td>Metric, with rain related measurements in <span class="code">cm</span> and speeds in <span
class="code">km/hr</span>
</td>
</tr>
</table>
<p>The table below lists all the unit groups, their members, which units are options for the group, and what the
defaults are for each standard unit system.
</p>
<table class="indent">
<caption>Unit groups, members and options</caption>
<tbody class="code">
<tr class="first_row">
<td>Group</td>
<td>Members</td>
<td>Unit options</td>
<td><span class="code">US</span></td>
<td><span class="code">METRICWX</span></td>
<td><span class="code">METRIC</span></td>
</tr>
<tr>
<td class="first_col">group_altitude</td>
<td>altitude<br/> cloudbase
</td>
<td>foot <br/> meter
</td>
<td>foot</td>
<td>meter</td>
<td>meter</td>
</tr>
<tr>
<td class="first_col">group_amp</td>
<td></td>
<td>amp</td>
<td>amp</td>
<td>amp</td>
<td>amp</td>
</tr>
<tr>
<td class="first_col">group_data</td>
<td></td>
<td>byte<br/> bit
</td>
<td>byte</td>
<td>byte</td>
<td>byte</td>
</tr>
<tr>
<td class="first_col">group_degree_day</td>
<td>cooldeg<br/> heatdeg
</td>
<td>degree_F_day<br/> degree_C_day
</td>
<td>degree_F_day</td>
<td>degree_C_day</td>
<td>degree_C_day</td>
</tr>
<tr>
<td class="first_col">group_direction</td>
<td>gustdir <br/> vecdir <br/> windDir <br/> windGustDir
</td>
<td>degree_compass</td>
<td>degree_compass</td>
<td>degree_compass</td>
<td>degree_compass</td>
</tr>
<tr>
<td class="first_col">group_distance</td>
<td>windrun</td>
<td>mile<br/> km
</td>
<td>mile</td>
<td>km</td>
<td>km</td>
</tr>
<tr>
<td class="first_col">group_energy</td>
<td></td>
<td>watt_hour</td>
<td>watt_hour</td>
<td>watt_hour</td>
<td>watt_hour</td>
</tr>
<tr>
<td class="first_col">group_interval</td>
<td>interval</td>
<td>minute</td>
<td>minute</td>
<td>minute</td>
<td>minute</td>
</tr>
<tr>
<td class="first_col">group_length</td>
<td></td>
<td>inch<br/> cm
</td>
<td>inch</td>
<td>cm</td>
<td>cm</td>
</tr>
<tr>
<td class="first_col">group_moisture</td>
<td>soilMoist1 <br/> soilMoist2 <br/> soilMoist3 <br/> soilMoist4
</td>
<td>centibar</td>
<td>centibar</td>
<td>centibar</td>
<td>centibar</td>
</tr>
<tr>
<td class="first_col">group_percent</td>
<td>extraHumid1 <br/> extraHumid2 <br/> inHumidity <br/> outHumidity <br/> rxCheckPercent
</td>
<td>percent</td>
<td>percent</td>
<td>percent</td>
<td>percent</td>
</tr>
<tr>
<td class="first_col">group_power</td>
<td></td>
<td>watt</td>
<td>watt</td>
<td>watt</td>
<td>watt</td>
</tr>
<tr>
<td class="first_col">group_pressure</td>
<td>barometer <br/> altimeter <br/> pressure
</td>
<td>inHg <br/> mbar <br/> hPa
</td>
<td>inHg</td>
<td>mbar</td>
<td>mbar</td>
</tr>
<tr>
<td class="first_col">group_radiation</td>
<td>radiation</td>
<td>watt_per_meter_squared</td>
<td>watt_per_meter_squared</td>
<td>watt_per_meter_squared</td>
<td>watt_per_meter_squared</td>
</tr>
<tr>
<td class="first_col">group_rain</td>
<td>rain <br/> ET <br/> hail
</td>
<td>inch <br/> cm <br/> mm
</td>
<td>inch</td>
<td>mm</td>
<td>cm</td>
</tr>
<tr>
<td class="first_col">group_rainrate</td>
<td>rainRate <br/> hailRate
</td>
<td>inch_per_hour <br/> cm_per_hour <br/> mm_per_hour
</td>
<td>inch_per_hour</td>
<td>mm_per_hour</td>
<td>cm_per_hour</td>
</tr>
<tr>
<td class="first_col">group_speed</td>
<td>wind <br/> windGust <br/> windSpeed <br/> windgustvec <br/> windvec
</td>
<td>mile_per_hour <br/> km_per_hour <br/> knot <br/> meter_per_second
</td>
<td>mile_per_hour</td>
<td>meter_per_second</td>
<td>km_per_hour</td>
</tr>
<tr>
<td class="first_col">group_speed2</td>
<td>rms <br/> vecavg
</td>
<td>mile_per_hour2 <br/> km_per_hour2 <br/> knot2 <br/> meter_per_second2
</td>
<td>mile_per_hour2</td>
<td>meter_per_second2</td>
<td>km_per_hour2</td>
</tr>
<tr>
<td class="first_col">group_temperature</td>
<td>appTemp <br/> dewpoint <br/> extraTemp1 <br/> extraTemp2 <br/> extraTemp3 <br/> heatindex <br/>
heatingTemp <br/> humidex <br/> inTemp <br/> leafTemp1 <br/> leafTemp2 <br/> outTemp <br/> soilTemp1
<br/> soilTemp2 <br/> soilTemp3 <br/> soilTemp4 <br/> windchill
</td>
<td>degree_F <br/> degree_C
</td>
<td>degree_F</td>
<td>degree_C</td>
<td>degree_C</td>
</tr>
<tr>
<td class="first_col">group_time</td>
<td>dateTime</td>
<td>unix_epoch <br/> dublin_jd
</td>
<td>unix_epoch</td>
<td>unix_epoch</td>
<td>unix_epoch</td>
</tr>
<tr>
<td class="first_col">group_uv</td>
<td>UV</td>
<td>uv_index</td>
<td>uv_index</td>
<td>uv_index</td>
<td>uv_index</td>
</tr>
<tr>
<td class="first_col">group_volt</td>
<td>consBatteryVoltage <br/> heatingVoltage <br/> referenceVoltage <br/> supplyVoltage
</td>
<td>volt</td>
<td>volt</td>
<td>volt</td>
<td>volt</td>
</tr>
<tr>
<td class="first_col">group_volume</td>
<td></td>
<td>cubic_foot<br/> gallon<br/> litre
</td>
<td>gallon</td>
<td>litre</td>
<td>litre</td>
</tr>
<tr>
<td class="first_col">group_NONE</td>
<td>NONE</td>
<td>NONE</td>
<td>NONE</td>
<td>NONE</td>
<td>NONE</td>
</tr>
</tbody>
</table>
</div>
<!-- end id technical_content -->
<div class="footer">
<p class="copyright">
&copy; <a href="copyright.htm">Copyright</a> Tom Keffer
</p>
</div>
</div>
<!-- end class main -->
<!-- Our scripts load last so the content can load first -->
<script src="js/jquery-1.11.1.min.js"></script>
<script src="js/jquery-ui-1.10.4.custom.min.js"></script>
<script src="js/jquery.tocify-1.9.0.min.js"></script>
<script src="js/weewx.js"></script>
<!--suppress JSUnresolvedFunction -->
<script>
$(function () {
var level = get_default_level();
create_toc_control(level);
generate_toc(level);
});
</script>
</body>
</html>