Files
weewx/docs/customizing.htm
2017-03-11 12:54:32 -08:00

7357 lines
284 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 PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>weewx: Customization Guide</title>
<meta http-equiv="Content-Language" content="en-us" />
<meta http-equiv="Content-Type" content="text/html; 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' align='right' alt="weewx logo" />
</a><br /> <span class='version'>
Version: 3.7
</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>
<h1 id="introduction">Introduction</h1>
<p>
This document starts with an overview of the architecture of
weewx. If you are only interested in customizing the generated
reports you can probably skip the overview and proceed directly
to the section <em><a href="#standard_skin">The
Standard <span class="code">skin.conf</span>
</a></em>. 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, etc., 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. Many examples use types
specific to the Davis Vantage series. Unless you are using an
unusual type you are unlikely to run into trouble.</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>
<h2>Where to put customizations</h2>
<p>
For configuration changes, simply modify the weeWX
configuration file <span class="code">weewx.conf</span>.
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>. 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 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. For example, copy example code from the <span
class="code">examples</span> directory to the <span
class="code">user</span> directory, then modify it there. If
your modification does not contain much code, consider putting
it in the file <span class="code">extensions.py</span> in the <span
class="code">user</span> directory. Because the <span
class="code">user</span> directory is preserved through
upgrades, you won't have to redo any changes you might have
made.
</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"
summary="Overview of the weeWX architecture">
<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 FTP/rsync
files to a web server.</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 three reports:
</p>
<table class="indent" summary="Standard reports included in weewx">
<tbody>
<tr class="first_row">
<td>Report</td>
<td>Default functionality</td>
</tr>
<tr>
<td class="code first_col">StandardReport</td>
<td>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.</td>
</tr>
<tr>
<td class="code first_col">FTP</td>
<td>Arranges to upload everything in the <span
class="symcode">HTML_ROOT</span> directory up to a remote
webserver.
</td>
</tr>
<tr>
<td class="code first_col">RSYNC</td>
<td>Like FTP, but uses rsync for transferring files to
a remote webserver.</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 arrange for
things to be transferred 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" summary="Generators included in weewx">
<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.
</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>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><i>null</i></td>
<td><i>null</i></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><i>null</i></td>
<td><i>null</i></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 <a
href="#customizing_templates">template system</a> uses this
architecture to display the names of units and to convert
observations from one unit to another.
</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>This section discusses the two general strategies for
customizing reports: by changing options in one or more
configuration file, or by changing the template files. The
former is generally easier, but occasionally the latter is
necessary.</p>
<h2>Changing options</h2>
<p>
Changing an option means either modifying the main configuration
file <span class="code">weewx.conf</span>, or the skin
configuration file <span class="code">skin.conf</span>.
</p>
<p>
Each skin will have a <span class="code">skin.conf</span> that
defines its default configuration. The examples in this guide
refer to the standard skin that comes with the distribution.
</p>
<h3>
Changing options in <span class="code">skin.conf</span>
</h3>
<p>
With this approach, edit the skin configuration file with a text
editor. Changes made in this way will be used by weeWX
the next time it generates reports,
which is typically the next archive interval; there is no need
to restart weeWX to see the results of
the changes.
</p>
<p>
For the standard skin that comes with weeWX,
the file is <span class="code">skins/Standard/skin.conf</span>.
It includes many, many options that can be changed. For a
complete list, see the section below, <em><a
href="#standard_skin">The Standard <span class="code">skin.conf</span></a></em>.
</p>
<p>
For example, suppose you wish to use metric units in the
presentation layer, instead of the default US Customary Units.
The section in <span class="code">skin.conf</span> that controls
units is <span class="code">[Units][[Groups]]</span>. It looks
like this:
</p>
<pre class="tty">[Units]
[[Groups]]
group_altitude = foot
group_degree_day = degree_F_day
group_direction = degree_compass
group_moisture = centibar
group_percent = percent
group_pressure = inHg
group_radiation = watt_per_meter_squared
group_rain = inch
group_rainrate = inch_per_hour
group_speed = mile_per_hour
group_speed2 = mile_per_hour2
group_temperature = degree_F
group_uv = uv_index
group_volt = volt</pre>
<p>To use metric units, you would edit this section to read:</p>
<pre class="tty">[Units]
[[Groups]]
<span class="highlight">group_altitude = meter</span>
<span class="highlight">group_degree_day = degree_C_day</span>
group_direction = degree_compass
group_moisture = centibar
group_percent = percent
<span class="highlight">group_pressure = mbar</span>
group_radiation = watt_per_meter_squared
<span class="highlight">group_rain = mm</span>
<span class="highlight">group_rainrate = mm_per_hour</span>
<span class="highlight">group_speed = meter_per_second</span>
<span class="highlight">group_speed2 = meter_per_second2</span>
<span class="highlight">group_temperature = degree_C</span>
group_uv = uv_index
group_volt = volt</pre>
<p>
The options that were changed have been <span class="highlight">&nbsp;highlighted&nbsp;</span>.
Details of the various unit options are given in the <em><a
href="#units">Appendix: Units</a></em>.
</p>
<p>
Other options are available, such as changing the text label for
various observation types. For example, suppose your weather
console is actually located in a barn, not indoors, and you want
the plot for the temperature at the console to be labeled "Barn
Temperature," rather than the default "Inside Temperature." This
can be done by changing the <span class="code">inTemp</span>
option located in section <a href="#Labels_Generic"><span
class="code">[Labels][[Generic]]</span></a> from the default
</p>
<pre class="tty">[Units]
[[Generic]]
inTemp = Inside Temperature
outTemp = Outside Temperature
...</pre>
<p>to:</p>
<pre class="tty">[Units]
[[Generic]]
<span class="highlight">inTemp = Barn Temperature</span>
outTemp = Outside Temperature
...</pre>
<h3>
Overriding options in <span class="code">skin.conf</span> from <span
class="code">weewx.conf</span>
</h3>
<p>
This approach is very similar, except that instead of changing
the skin configuration file, <span class="code">skin.conf</span>,
directly, you override its options by editing the main
configuration file, <span class="code">weewx.conf</span>. The
advantage of this approach is that you can use the same skin to
produce several different output, each with separate options.
</p>
<p>
With this approach, you must restart weeWX
to see the effects of any changes.
</p>
<p>
Revisiting our example, suppose you want two reports, one in US
Customary, the other in Metric. The former will go in the
directory <span class="symcode">HTML_ROOT</span>, the latter in
a directory, <span class="symcode">HTML_ROOT</span><span
class="code">/metric</span>. If you just simply modify <span
class="code">skin.conf</span>, you can get one, but not both
at the same time. Alternatively, you could create a whole new
skin by copying all the files to a new skin directory then
editing the new <span class="code">skin.conf</span>. The trouble
with this approach is that you would then have <em>two</em>
skins you would have to maintain. If you change something, you
have to remember to change it in both places.
</p>
<p>
But, there's a better approach: reuse the same skin, but
override some of its options. Here is what your <span
class="code">[StdReport]</span> section in <span class="code">weewx.conf</span>
would look like:
</p>
<pre class="tty">[StdReport]
#
# This section specifies what reports, using which skins, are to be generated.
#
# Where the skins reside, relative to WEEWX_ROOT:
SKIN_ROOT = skins
# Where the generated reports should go, relative to WEEWX_ROOT:
HTML_ROOT = public_html
# This report will use US Customary Units
[[USReport]]
# It is based on the Standard skin
skin = Standard
# This report will use metric units:
[[MetricReport]]
# It is also based on the Standard skin:
skin = Standard
# However, override where the results will go and put them in a directory:
HTML_ROOT = public_html/metric
# And override the options that were not in metric units
[[[Units]]]
[[[[Groups]]]]
group_altitude = meter
group_pressure = mbar
group_rain = mm
group_rainrate = mm_per_hour
group_speed = meter_per_second
group_speed2 = meter_per_second2
group_temperature = degree_C
</pre>
<p>
We have done two things different from the stock reports. First
(1), we have renamed the first report from <span class="code">StandardReport</span>
to <span class="code">USReport</span> for clarity; and second
(2), we have introduced a new report <span class="code">MetricReport</span>,
just like the first, except it puts its results in a different
spot and uses different units. Both use the same skin, the <span
class="code">Standard</span> skin.
</p>
<h2 id="customizing_templates">Customizing templates</h2>
<p>
If you cannot achieve the results you need by changing a
configuration option, you may have to modify the templates that
come with weeWX, or write your own.
</p>
<p>
Template modifications are preserved across upgrades (indeed,
everything in the <span class="code">skins</span> directory is
preserved), so you don't have to worry about losing changes
after an upgrade.
</p>
<p>
Template generation is done using the <a
href="https://pythonhosted.org/Cheetah/">Cheetah</a>
templating engine. This is a very powerful engine, which
essentially lets you have the full semantics of Python available
in your templates. As this would make the templates
incomprehensible to anyone but a Python programmer,
weeWX adopts a very small subset of its
power.
</p>
<h3>Tags</h3>
<p>The key construct is a 'tag', specifying what value you
want. For example:</p>
<pre class="tty">$current.outTemp
$month.outTemp.max
$month.outTemp.maxtime</pre>
<p>would code the current outside temperature, the maximum
outside temperature for the month, and the time that maximum
temperature 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 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 an optional
formatting tag that controls how the value will appear. See the
section <em><a href="#formatting_options">Formatting
Options</a></em>.
</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 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 <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>
</tbody>
<tr>
<td class="first_col code">$hour($hours_ago=<i>h</i>)
</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=<i>d</i>)
</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=<i>d</i>)
</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=<i>m</i>)
</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=<i>m</i>)
</td>
<td class="code">$year($years_ago=1).outTemp.max</td>
<td>The maximum temperature last year.</td>
</tr>
</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>Wind ordinals</h4>
<p>Using this method, you can output compass ordinals for wind
direction. For example, the template</p>
<pre class="tty">Current wind direction is $current.windDir ($current.windDir.ordinal_compass)</pre>
<p>would result in:</p>
<p class="example_output">Current wind direction is 138° (SW)</p>
<p>
The ordinal abbreviations are set by option <span class="code">directions</span>
in the skin configuration file <span class="code">skin.conf</span>.
</p>
<h4>Illegal conversions</h4>
<p>
If an inappropriate or nonsense conversion is asked for, <em>e.g.</em>,
</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>
The tag <span class="code">optional_formatting</span> can be
used with either current observations or aggregations. It can be
one of:
</p>
<table class="indent" summary="Formatting Options">
<caption>Optional formatting tags</caption>
<tbody>
<tr class="first_row">
<td>Optional formatting tag</td>
<td>Comment</td>
</tr>
<tr>
<td class='text_highlight'>(no tag)</td>
<td>Value is returned as a string, formatted using an
appropriate string format from <span class="code">skin.conf</span>.
A unit label (e.g., <span class='code'>&deg;F</span>) from
<span class="code">skin.conf</span> is also attached at
the end.
</td>
</tr>
<tr>
<td class="code text_highlight">.string(<em>NONE_string</em>)
</td>
<td>Value is returned as a string, formatted using an
appropriate string format from <span class="code">skin.conf</span>.
If the value is <span class="code">None</span>, the string
<span class="code">NONE_string</span> will be substituted
if given, otherwise the value for <span class="code">NONE</span>
in <span class="code"> <a
href="#Units_StringFormats">[Units][[StringFormats]]</a>
</span> will be used. A unit label (e.g., <span class='code'>&deg;F</span>)
from <span class="code">skin.conf</span> will be attached
at the end.
</td>
</tr>
<tr>
<td class="code text_highlight">.formatted</td>
<td>Value is returned as a string, formatted using an
appropriate string format and <span class="code">None</span>
value from <span class="code">skin.conf</span>. No unit
label will be attached.
</td>
</tr>
<tr>
<td class="code text_highlight">.format(<em>string_format</em>, <em>NONE_string</em>)
</td>
<td>Value is returned as a string, using the string
format specified with <em>string_format</em>. If the value
is <span class="code">None</span>, the string <span
class="code">NONE_string</span> will be substituted if
given, otherwise the value for <span class="code">NONE</span>
in <span class="code"> <a
href="#Units_StringFormats">[Units][[StringFormats]]</a>
</span> will be used. A unit label (e.g., <span class='code'>&deg;F</span>)
from <span class="code">skin.conf</span> will be attached
at the end.
</td>
</tr>
<tr>
<td class="code text_highlight">.nolabel(<em>string_format, NONE_string</em>)
</td>
<td>Value is returned as a string, using the string
format specified with <em>string_format</em>. If the value
is <span class="code">None</span>, the string <span
class="code">NONE_string</span> will be substituted if
given, otherwise the value for <span class="code">NONE</span>
in <span class="code"> <a
href="#Units_StringFormats">[Units][[StringFormats]]</a>
</span> will be used. No unit label will be attached at the end.
</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 <span
class="code">None</span> value unless the value is
converted directly to a string. In this case, it will be
converted to the empty string (<span class="code">''</span>)
</td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<table class="indent" summary="Summary of formatting options">
<caption>Summary of formatting options</caption>
<tbody>
<tr class="first_row">
<td>Formatting Tag</td>
<td>Format Used</td>
<td>Label Used</td>
<td>NONE String</td>
<td>Returned Value</td>
</tr>
<tr>
<td class="first_col">(no tag)</td>
<td>From <span class="code">skin.conf</span></td>
<td>From <span class="code">skin.conf</span></td>
<td>From <span class="code">skin.conf</span></td>
<td>string</td>
</tr>
<tr>
<td class="code first_col">.string</td>
<td>From <span class="code">skin.conf</span></td>
<td>From <span class="code">skin.conf</span></td>
<td>Optional user-supplied</td>
<td>string</td>
</tr>
<tr>
<td class="code first_col">.formatted</td>
<td>From <span class="code">skin.conf</span></td>
<td>No label</td>
<td>From <span class="code">skin.conf</span></td>
<td>string</td>
</tr>
<tr>
<td class="code first_col">.format</td>
<td>User-supplied</td>
<td>From <span class="code">skin.conf</span></td>
<td>Optional user-supplied</td>
<td>string</td>
</tr>
<tr>
<td class="code first_col">.nolabel</td>
<td>User-supplied</td>
<td>No label</td>
<td>Optional user-supplied</td>
<td>string</td>
</tr>
<tr>
<td class="code first_col">.raw</td>
<td>None</td>
<td>No label</td>
<td>None</td>
<td>native value</td>
</tr>
</tbody>
</table>
<p>Here are some examples with the expected results:</p>
<table class="indent"
summary="Formatting options with expected results">
<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 and label from <span class="code">skin.conf</span></td>
</tr>
<tr>
<td class="code first_col">$current.outTemp.string</td>
<td class="code">45.2°F</td>
<td>String formatting and label from <span class="code">skin.conf</span></td>
</tr>
<tr>
<td class="code first_col">$current.UV.string</td>
<td class="code">N/A</td>
<td>This example assumes that the instrument has no UV
sensor, resulting in a <span class="code">None</span>
value. The string specified by <span class="code">NONE</span>
in <span class="code"> <a
href="#Units_StringFormats">[Units][[StringFormats]]</a></span>
is substituted.
</td>
</tr>
<tr>
<td class="code first_col">$current.UV.string("No UV")</td>
<td class="code">No UV</td>
<td>This example assumes that the instrument has no UV
sensor, resulting in a <span class="code">None</span>
value. The string supplied by the user is substituted.
</td>
</tr>
<tr>
<td class="code first_col">$current.outTemp.formatted</td>
<td class="code">45.2</td>
<td>String formatting from <span class="code">skin.conf</span>;
no label
</td>
</tr>
<tr>
<td class="code first_col">$current.outTemp.format("%.3f")</td>
<td class="code">45.200°F</td>
<td>Specified string format used; label from <span
class="code">skin.conf</span>.
</td>
</tr>
<tr>
<td class="code first_col">$current.dateTime</td>
<td class="code">02-Apr-2010 16:25</td>
<td>Time formatting and label from <span class="code">skin.conf</span></td>
</tr>
<tr>
<td class="code first_col">$current.dateTime.format("%H:%M")</td>
<td class="code">16:25</td>
<td>Specified time format used; label from <span
class="code">skin.conf</span>.
</td>
</tr>
<tr>
<td class="code first_col">$current.dateTime.raw</td>
<td class="code">1270250700</td>
<td>Unix epoch time, converted to string by template
engine.</td>
</tr>
<tr>
<td class="code first_col">$current.outTemp.raw</td>
<td class="code">45.2</td>
<td>Float returned, converted to string by template
engine.</td>
</tr>
<tr>
<td class="code first_col">$month.dateTime</td>
<td class="code">01-Apr-2010 00:00</td>
<td>Time formatting and label from <span class="code">skin.conf</span></td>
</tr>
<tr>
<td class="code first_col">$month.outTemp.avg</td>
<td class="code">40.8°F</td>
<td>String formatting and label from <span class="code">skin.conf</span></td>
</tr>
<tr>
<td class="code first_col">$month.outTemp.avg.string</td>
<td class="code">40.8°F</td>
<td>Time formatting and label from <span class="code">skin.conf</span></td>
</tr>
<tr>
<td class="code first_col">$month.UV.avg.string</td>
<td class="code">N/A</td>
<td>This example assumes that the instrument has no UV
sensor, resulting in a <span class="code">None</span>
value. The string specified by <span class="code">NONE</span>
in <span class="code"> <a
href="#Units_StringFormats">[Units][[StringFormats]]</a></span>
is substituted.
</td>
</tr>
<tr>
<td class="code first_col">$month.UV.avg.string("No
UV")</td>
<td class="code">No UV</td>
<td>This example assumes that the instrument has no UV
sensor, resulting in a <span class="code">None</span>
value. The string supplied by the user is substituted.
</td>
</tr>
<tr>
<td class="code first_col">$month.outTemp.avg.formatted</td>
<td class="code">40.8</td>
<td>String formatting from <span class="code">skin.conf</span>;
no label
</td>
</tr>
<tr>
<td class="code first_col">$month.outTemp.avg.format("%.3f")</td>
<td class="code">40.759°F</td>
<td>Specified string format used; no label</td>
</tr>
<tr>
<td class="code first_col">$month.outTemp.avg.raw</td>
<td class="code">40.7589690722</td>
<td>Float returned, converted to string by template
engine</td>
</tr>
<tr>
<td class="code first_col">$month.UV.avg.raw</td>
<td class="code"><em>(empty)</em></td>
<td><span class="code">None</span> value converted to
empty string by template engine.</td>
</tr>
</tbody>
</table>
<p>
Tags that take an argument, such as <span class="code">.string(NONE_string)</span>,
do not require parenthesis if the argument is omitted. Thus, you
can specify either <span class="code">$month.outTemp.string()</span>
or <span class="code">$month.outTemp.string</span>, if you want
the default value of <span class="code">NONE_string</span>. They
produce the same results.
</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="http://docs.python.org/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%"
summary="Examples of using unit formats">
<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 example 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 example above).
</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=<i>seconds</i></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=<i>hours</i></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=<i>days</i></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=<i>weeks</i></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=<i>months</i></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=<i>years</i></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%"
summary="Examples of using unit formats">
<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.formatted$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%"
summary="Example off using observation labels">
<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%"
summary="Iteration periods">
<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=<i>seconds</i>)
</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
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>
<h3>Almanac</h3>
<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 in two categories:</p>
<ul>
<li>Calendar events</li>
<li>Heavenly bodies</li>
</ul>
<p>We will cover each of these separately.</p>
<h4>Calendar events</h4>
<p>
"Calendar events" do not require a heavenly body. They cover
things such as <span class="code">next_solstice</span>, or <span
class="code">next_first_quarter_moon</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>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>
</tbody>
</table>
<h4>Heavenly bodies</h4>
<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>
<h3>Wind</h3>
<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">
<b>Note</b><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.windSpeed.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.avg</td>
<td>The average wind speed since midnight. Same as <span
class="code">$day.windSpeed.avg</span> above.
</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.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 observed wind speed.
</td>
</tr>
<tr>
<td class="first_col code">$day.windGust.max</td>
<td>The maximum observed wind speed since midnight, <i>i.e.,</i>
the maximum 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>
<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>
</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"><i>obj</i>.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"><i>obj</i>[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>:
</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) # 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) # 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 4 parameters.
<ul>
<li>The first 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 is the database lookup function to be
used. We simply pass in <span class="code">db_lookup</span>.
</li>
<li>The third 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 should be an instance of <span
class="code">weewx.units.Converter</span>, which contains
information about the target units (<em>e.g.</em>, <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>
</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>
If you place a custom generator somewhere other than the
hierarchy where <span class="code">weewxd</span> resides, you
may have to specify its location in the environment variable <span
class="code">PYTHONPATH</span> in the shell where you start
weeWX:
</p>
<pre class="tty">export PYTHONPATH=/home/me/secret_location</pre>
<h2>Customizing images</h2>
<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 <span class="code">[ImageGenerator]</span>
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 = 300
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 300
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>
<h3>Time periods</h3>
<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="http://docs.python.org/library/datetime.html#strftime-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>
<h3>Image files</h3>
<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 — 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 SQL data type 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>
<h3 id="line_gaps">Line gaps</h3>
<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>
<h3>Including more than one SQL type in a plot</h3>
<p>More than one SQL type 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>
<h3 id="including_same_sql_type_2x">Including the same SQL
type more than once in a plot</h3>
<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 SQL type 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 SQL type. 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 width="300" height="180" alt="Daily highs and lows"
src="images/yearhilow.png" />
</p>
<h3>Progressive vector plots</h3>
<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 SQL 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>
<h3>Overriding values</h3>
<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>
<h2 id="#Using_multiple_bindings">Using multiple bindings</h2>
<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>
<h3>Explicit binding in tags</h3>
<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>
<h3>Explicit binding in images</h3>
<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" />
<h3 id="stupid_detail">Stupid detail</h3>
<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>
<h2 id="customizing_gen_time">Customizing the report
generation time</h2>
<p>
Normal weeWX operation is to run each
report 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, or once per day. In such cases the <span
class="code">report_timing</span> option can be used to
control when individual reports are run.
</p>
<p class="note" style="display: inline-block; width: 70%">
<b>Note</b><br />Whilst the <span class="code">report_timing</span>
option allows the user to specify when a given report is generated, it
should be noted that the generation of reports is still controlled by
the weeWX report cycle and as such the <span class="code">report_timing</span>
option does not allow reports to 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" summary="report_timing parameters">
<tbody>
<tr class="first_row">
<td>Parameter</td>
<td 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 the above 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>
<h4>
How <span class="code">report_timing</span> controls reporting
</h4>
<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%">
<b>Note</b><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%">
<b>Note</b><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>
<h4>
The relationship between <span class="code">report_timing</span>
and archive period
</h4>
<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 <b>any</b> 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" summary="archive period relationship">
<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>
<h4>Lists, ranges and steps</h4>
<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>
<h4>Nicknames</h4>
<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" summary="report_timing examples">
<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>
<h4>
<span class="code">report_timing</span> option examples
</h4>
<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" summary="report_timing examples">
<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="standard_skin">
The Standard <span class="code">skin.conf</span>
</h1>
<p>
This section is a reference to the options appearing in the skin
configuration file. The default skin is the Standard skin, with
a skin configuration file located at <span class="symcode">SKIN_ROOT</span><span
class="code">/Standard/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>
<h3>Example</h3>
<p>
As an example, the Standard <span class="code">skin.conf</span>
file includes three options:
</p>
<table class="indent" style="width: 50%" summary="Tag Extras">
<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">index.html.tmpl</span>
you will see examples of testing for these tags (search the file
for the string <span class="code">radar_img</span> to find
them).
</p>
<p class="config_option">radar_img</p>
<p>Set to an URL to show a local radar image for you.</p>
<p class="config_option">radar_url</p>
<p>If the above 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 above 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">[Units]</h2>
<p>This section deals with Units and their formatting.</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 above) they should be encoded in UTF-8.
This is generally what most text editors use if you
cut-and-paste from a character map.
</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>
<p>
This is particularly useful when <a href="#l11n_unit_labels">localizing
the weeWX and server uptimes</a>.
</p>
<h3 class="config_section" id="Units_TimeFormats">[[TimeFormats]]</h3>
<p>
This sub-section is used for time labels. It uses <a
href="http://docs.python.org/library/datetime.html#strftime-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">[[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 class="config_section">[Labels]</h2>
<p>This section sets the various labels to use.</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 (<em>e.g., </em><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">[CheetahGenerator]</h2>
<p>
This section is used by generator <span class="code">weewx.cheetahgenerator.CheetahGenerator</span>
and controls text generation from templates, specifically which
files are to be produced from which template.
</p>
<h3>Overview of file generation</h3>
<p>
Files are generated from templates, and each template is
identified by the <span class="config_option">template</span>
parameter.
</p>
<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 three block names that have special meaning: <span
class='code'>SummaryByMonth</span>, <span class='code'>SummaryByYear</span>,
and <span class='code'>ToDate</span>. They are described below.
</p>
<p>The file generator runs on each new archive record. In a
default weeWX installation, that would be every 5 minutes.</p>
<p>
Cheetah processes each template to generate a file. Cheetah
follows any logic defined by directives such as <span
class="code">for</span> or <span class="code">if ...
else</span>, and it replaces variables such as <span class="code">$Extras.radar_url</span>
or <span class="code">$current.outTemp.max</span>.
</p>
<p>Variables are defined by objects in weeWX. Some variables
are static, others are linked to data in databases. The list of
variables can be extended.</p>
<h3>File generation options</h3>
<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 (<em>e.g.</em>, 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">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>
or <span class="code">MM</span> in its name, these will be
substituted for the year and 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">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="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>
<h3>Customizing file generation</h3>
<p>The best way to customize file generation is to make a copy
of a working report/skin, then make incremental changes.</p>
<p>
When there is an 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. In some cases the error reporting is
rather obscure. So make small changes and test often. Use the
utility <span class="code"><a href="#wee_reports">wee_reports</a></span>
to test modifications to the generator configuration and/or the
template contents.
</p>
<h3>The Standard skin templates</h3>
<p>
Here is the <span class="code">[CheetahGenerator]</span> section
from the Standard <span class="code">skin.conf</span>
</p>
<pre class="tty">[CheetahGenerator]
# This section is used by the generator CheetahGenerator, and specifies
# which files are to be generated from which template.
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.
[[[day]]]
template = index.html.tmpl
[[[week]]]
template = week.html.tmpl
[[[month]]]
template = month.html.tmpl
[[[year]]]
template = year.html.tmpl
[[[RSS]]]
template = RSS/weewx_rss.xml.tmpl
[[[Mobile]]]
template = mobile.html.tmpl</pre>
<p>The Standard skin contains three different kinds of
generated output:</p>
<ol>
<li>Summary by Month. The Standard 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 Standard 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 Standard skin produce reports
for the day, week, month, and year-to-date observations. These
files are HTML. The first, the daily summary (output filename
is <span class="code">index.html</span>), includes a drop-down
list that displays the NOAA month and yearly summaries.
</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>.
In the Standard skin this is 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>
<h2 class="config_section">[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="http://docs.python.org/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="http://docs.python.org/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>
<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. NB: individual line color can be overridden by using
option <span class="code">color</span>. 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>.
</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>)
above.
</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. NB: individual line widths can be
overridden by using option <span class="code">width</span>.
Optional. Default is <span class="code">1, 1, 1</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>, and
<span class="code">last</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="localization">Localization</h1>
<p>What follows is a guide to localizing to a non-English
language and/or locale. There are two parts: translating to
different languages and modifying to reflect local conventions
for displaying data.</p>
<h2>Translate the templates</h2>
<p>
First, you will need to go through the templates and translate
to your target language. Obvious text strings such as <span
class="code">"Current Weather Conditions"</span> will need to
be translated.
</p>
<h2>Modify the skin configuration</h2>
<p>
Next, you will need to go through <span class="code">skin.conf</span>
to translate labels and modify formats to follow local
conventions.
</p>
<p>You will probably want to change the generic labels used
for the observation types:</p>
<pre class="tty">[Labels]
...
[[Generic]]
barometer = Barometer
dewpoint = Dew Point
heatindex = Heat Index
inHumidity = Inside Humidity
inTemp = Inside Temperature
outHumidity = Outside Humidity
outTemp = Outside Temperature
radiation = Radiation
rain = Rain
rainRate = Rain Rate
rxCheckPercent = ISS Signal Quality
windDir = Wind Direction
windGust = Gust Speed
windGustDir = Gust Direction
windSpeed = Wind Speed
windchill = Wind Chill
windgustvec = Gust Vector
windvec = Wind Vector</pre>
<p>The hemisphere abbreviations may have to be changed:</p>
<pre class="tty">[Labels]
hemispheres = N, S, E, W</pre>
<p>The wind ordinal directions may have to be changed:</p>
<pre class='tty'>[Units]
...
[[Ordinates]]
# The ordinal directions. The last one should be for no wind direction
directions = N, NNE, NE, ENE, E, ESE, SE, SSE, S, SSW, SW, WSW, W, WNW, NW, NNW, N/A </pre>
<p>Don't forget the moon phases:</p>
<pre class="tty">[Almanac]
moon_phases = New, Waxing crescent, First quarter, Waxing gibbous, Full, Waning gibbous, Last quarter, Waning crescent</pre>
<p id="l11n_unit_labels">
Most of the unit labels either follow ISO conventions, or are
unlikely to be used outside English speaking countries (an
example would be &quot;foot&quot;). But, there are a few
exceptions, used to label the weeWX and server
&quot;uptimes&quot;, which can be found in sub-section <span
class="code">[Units][[Labels]]</span>
</p>
<pre class="tty">[Units]
...
[[Labels]]
...
day = " day", " days"
hour = " hour", " hours"
minute = " minute", " minutes"
second = " second", " seconds"</pre>
<p>
By default, the time formats will use the local convention
specified by the LANG environment variable. These can be
modified individually in the <span class='code'>[Units][[TimeFormats]]</span>
section.
</p>
<pre class='tty'>[Units]
[[TimeFormats]]
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>
<h2>Encodings</h2>
<p>You may have to specify the encoding used</p>
<ul>
<li>by a template;</li>
<li>in any strings substituted by Cheetah.</li>
</ul>
<h3>Encoding used in a template</h3>
<p>
If you use an encoding other than ASCII in a template, then you must
include a <span class='code'>#encoding</span>
directive at the top of the file, informing Cheetah of your choice.
If your template includes other templates (by using the <span class='code'>#include</span>
directive), and those templates use an encoding, then they must include their
own <span class='code'>#encoding</span> directive as well.
</p>
<p>
For example, if you use <span class='code'>UTF-8</span>
(the default used by all the standard weeWX templates), then the top of
your template should look something like this:
</p>
<pre class='tty'>#errorCatcher Echo
##
## Specifying an encoding of UTF-8 is usually safe, but if your text is
## actually in Latin-1, then you should replace the string "UTF-8" with "latin-1"
<span class='highlight'>#encoding UTF-8</span>
&lt;!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&gt;
</pre>
<h3>Encoding used in substituted strings</h3>
<p>
The other place where encoding matters is in the strings substituted by Cheetah.
The encoding for these is specified by the
<a href="#option_encoding"><span class='code'>encoding</span></a> option
in the skin configuration file, <span class='code'>skin.conf</span>.
</p>
<h2 id="environment_variable_LANG">
Environment variable <span class="code">LANG</span>
</h2>
<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>
<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>To customize, you can</p>
<ul>
<li>Modify an existing service</li>
<li>Create a new service</li>
</ul>
<p>
See the table <a href="#default_services">Default services</a>
above 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 time
import smtplib
from email.mime.text import MIMEText
import threading
import syslog
import weewx
from weewx.engine import StdService
from weeutil.weeutil import timestamp_to_string, option_as_list
# 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.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.newArchiveRecord) # NOTE 1
except KeyError, e:
syslog.syslog(syslog.LOG_INFO, "alarm: No alarm set. Missing parameter: %s" % e)
def newArchiveRecord(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.soundTheAlarm, args=(self, record))
t.start()
# Record when the message went out:
self.last_msg_ts = time.time()
except NameError, e:
# The record was missing a named variable. Write a debug message, then keep going
syslog.syslog(syslog.LOG_DEBUG, "alarm: %s" % e)
def soundTheAlarm(self, rec):
"""This function is called when the given expression evaluates True."""
# Get the time and convert to a string:
t_str = timestamp_to_string(rec['dateTime'])
# Log it
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)
# Create an instance of class SMTP for the given SMTP host:
s = smtplib.SMTP(self.smtp_host)
try:
# Some servers (eg, gmail) require encrypted transport.
# Be prepared to catch an exception if the server
# doesn't support it.
s.ehlo()
s.starttls()
s.ehlo()
syslog.syslog(syslog.LOG_DEBUG, "alarm: using encrypted transport")
except smtplib.SMTPException:
syslog.syslog(syslog.LOG_DEBUG, "alarm: using 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, 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.newArchiveRecord</span>.
When the event <span class='code'>NEW_ARCHIVE_RECORD</span>
occurs, the function <span class="code">self.newArchiveRecord</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 the example above, 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_packet)
self.last_total = None
def new_archive_packet(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>. This subsection lists all
the services to be run, broken up into different <em>service
lists</em>. Here's a description of them:
</p>
<table id='service_lists' class="indent" style="width: 70%"
summary="Service lists">
<tbody>
<tr class="first_row">
<td>Service list</td>
<td>Function</td>
</tr>
<tr>
<td class="code first_col">prep_services</td>
<td>Perform any actions before the main loop is run.</td>
</tr>
<tr>
<td class="code first_col">data_services</td>
<td>Augment data, before it is processed.</td>
</tr>
<tr>
<td class="code first_col">process_services</td>
<td>Process, filter, and massage the data.</td>
</tr>
<tr>
<td class="code first_col">archive_services</td>
<td>Record the data in a database.</td>
</tr>
<tr>
<td class="code first_col">restful_services</td>
<td>Upload processed data to an external RESTful
service.</td>
</tr>
<tr>
<td class="code first_col">report_services</td>
<td>Run any reports.</td>
</tr>
</tbody>
</table>
<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>
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">Add a new type to the archive
database</h2>
<p>
If you followed the example above, <a href="#Adding_2nd_source"><em>Adding
a second data source</em></a>, you created a new observation type, <span
class="code">electricity</span>. Trouble is, there is no
corresponding type in the schema of the SQL database and,
therefore, weeWX won't know where to
store it. How would you add such a type?
</p>
<p>
The utility <a href="utilities.htm#wee_database_utility"><span
class="code">wee_database</span></a> can be used to 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>Here's our general strategy:</p>
<ol>
<li>Extend the existing schema with the new type <span
class="code">electricity</span>.
</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>Add the new type to our existing schema.</strong>
The weather 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. There's also no reason
to create a new file. Why not just do it in the file we
already have, <span class="code">user/electricity.py</span>?
Add to the bottom of the file:
</p> <pre class="tty">import schemas.wview
schema_with_electricity = schemas.wview.schema + [('electricity', 'REAL')]</pre>
<p>
This creates a new schema (it will have 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>
</li>
<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>. 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.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 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 units you want.
The <a href="#customizing_reports">Customizing reports</a>
section 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 databases from another piece of software that
expects metric units.
</p>
<p>
WeeWX does not allow you to change the database unit system
midstream. You can't 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>, typically by
adding Python code to the file <span class='code'>user/extensions.py</span>:
</p>
<pre class='tty'>import weewx.units
weewx.units.obs_group_dict['electricity'] = 'group_energy'</pre>
<p>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_amp</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 measuing over time. 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>
<h2>Use the new types</h2>
<p>Now you've added a new type. How do you use it?</p>
<p>
Pretty much like any other type. 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>
<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 (e.g.,
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 <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 has an error and you don't have a value, you can
either leave it out of the dictionary or (preferred) set its
value to <span class="code">None</span>.
</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>
above, 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
most simple example of a weeWX driver.
It reads name-value pairs from a file and uses the values as
sensor 'readings'. The code is located in <span class="code">extensions/fileparse/bin/user/fileparse.py</span>
</p>
<p>
Take a look at the simulator code in <span class='code'>bin/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 I've glossed
over in this high-level description. If you're game, give it a
try — I'm happy to help you out!</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>
<h2>How to package 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>
<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">leafTemp2</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 type="text/javascript" src="js/jquery-1.11.1.min.js"></script>
<script type="text/javascript" src="js/jquery-ui-1.10.4.custom.min.js"></script>
<script type="text/javascript" src="js/jquery.tocify-1.9.0.min.js"></script>
<script type="text/javascript" src="js/weewx.js"></script>
<script type="text/javascript">
$(function() {
var level = get_default_level();
create_toc_control(level);
generate_toc(level);
});
</script>
</body>
</html>