diff --git a/TODO.txt b/TODO.txt index 6303dbb1..beb84d2e 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,7 +1,2 @@ TODO before the next release: -Documentation: - o Characters used in the image plots must be Latin-1 characters. - o Include some of the examples I've sent to Alf. - - \ No newline at end of file diff --git a/docs/customizing.htm b/docs/customizing.htm index f23d8364..eceb6fa0 100644 --- a/docs/customizing.htm +++ b/docs/customizing.htm @@ -87,11 +87,12 @@ table { td { border-style: solid; border-width: 1px; + padding: 5px; } .indent { margin-left: 40px; } -.tty { +.tty, pre { font-family: "Courier New", Courier, monospace; margin-left: 40px; margin-top: 0px; @@ -120,11 +121,17 @@ td { .xxsmall { font-size: xx-small; } -.style1 { - background-color: #E8E8E8; +.highlight { + background-color: #FFFF66; } -.style2 { - background-color: #008080; +.center { + text-align: center; +} +.Example_output { + padding: 10px; + border: thin #000000 dotted; + font-family: "Times New Roman", Times, serif; + margin-left: 40px; } @@ -134,9 +141,18 @@ td {

Customizing weewx v1.6

Table of Contents

    -
  1. Introduction
  2. +
  3. Introduction
  4. +
  5. Opportunities for + customizing reports
  6. +
  7. Reference: + The Standard skin configuration file
  8. +
  9. Customizing the + weewx service engine
  10. +
  11. Appendix A: Types
  12. +
  13. Appendix B: Units
  14. +
  15. Appendix C: Statistical aggregations
-

Introduction

+

Introduction

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

It starts with an overview of the architecture of weewx. If you are only interested @@ -189,7 +205,7 @@ includes an example new service called "MyAlarm,& sends an email when an arbitrary expression evaluates True. It is also possible to extend the internal engine. These advanced topics are covered later in section XX.

-

The Reporting service, StdReportService

+

The standard reporting service, StdReportService

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

@@ -240,15 +256,15 @@ files around or FTP them to remote locations. The default install of weewx includes the following generators:

- + - + - + @@ -265,122 +281,113 @@ files around or FTP them to remote locations. The default install of the presentation layer. Instead, they just move files around.

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

-

Opportunities for customizing reports

+

Opportunities for customizing reports

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

By changing options

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

-

By changing options in skin.conf

+

By changing options in skin.conf

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

-

[Units]

-

  [[Groups]]

-

    group_altitude    = meter

-

    group_direction   = degree_compass

-

    group_moisture    = centibar

-

    group_percent     = percent

-

    group_pressure    = mbar

-

    group_radiation   = watt_per_meter_squared

-

    group_rain        -= mm

-

    group_rainrate    = mm_per_hour

-

    group_speed       -= meter_per_second

-

    group_speed2      = meter_per_second2

-

    group_temperature = degree_C

-

    group_volt        -= volt

-

Details of the various unit options are given in section XX below.

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

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

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

-

[Units]

-

    [[Generic]]

-

      inTemp  = Inside Temperature

-

      outTemp = Outside Temperature

-

      ...

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

so that it reads

-

[Units]

-

    [[Generic]]

-

      inTemp  = Barn Temperature

-

      outTemp = Outside Temperature

-

      ...

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

By overriding options

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

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

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

-

[Reports]

-

#
-# This section specifies what reports, using which skins, are to be generated.
-#

-

 

-

# Where the skins reside, relative to WEEWX_ROOT:
-SKIN_ROOT = skins
-

-

# Where the generated reports should go, relative to WEEWX_ROOT:
-HTML_ROOT = public_html

-

 

-

  # This report will use US Customary Units
-  [[USReport]]
-    # It's based on the Standard skin
-    skin = Standard

-

 

-

  # This report will use metric units:

-

  [[MetricReport]]

-

    # It's also based on the Standard skin:

-

    skin = Standard

-

    # However, override where the results will go -and put them in a subdirectory:

-

    HTML_ROOT = public_html/metric

-

   

-

      # And override options that were not -in metric units

-

      [[[Units]]]
-        [[[[Groups]]]]
-          group_altitude    -= meter

-

          group_pressure    -= mbar

-

          group_rain        -= mm

-

          group_rainrate    -= mm_per_hour

-

          group_speed       -= meter_per_second

-

          group_speed2      -= meter_per_second2

-

          group_temperature -= degree_C

-

 

-

    [[FTP]]

-

      ...

-

      ... (as before)

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

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

-

By customizing templates

+

By customizing templates

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

@@ -390,31 +397,31 @@ the full semantics of Python available in your templates. As this would make the templates incomprehensible to anyone but a Python programmer, weewx adopts a very small subset of its power.

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

-

$month.outTemp.max

-

$month.outTemp.maxtime

-

$current.outTemp

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

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

-

<html>

-

  <head>

-

    <title>Current conditions</title>

-

  </head>

-

  <body>

-

  <p>Current temperature = $current.outTemp</p>

-

  <p>Max for the month is $month.outTemp.max, which occurred at $month.outTemp.maxtime</p>

-

  </body>

-

</html>

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

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

-

Current temperature = 51.0°F
+

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

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

+[Units][[Labels]], while the time +format is from [Labels][[Time]].

The "dot" code has up to three parts.

  1. The first part is the time period, and can be one of @@ -431,32 +438,132 @@ format is from [Labels][[Time]]heatdeg' and 'cooldeg', heating and cooling degree-days, respectively, which are synthesized from average - outside temperature and do not appear directly in the database.
  2. + outside temperature and do not appear directly in the database. See + Appendix A: Types for a table of statistical + types.
  3. The last position is the aggregation type, available for any time period - except for 'current'. The table below in the - appendix Statistical aggregations shows what aggregation + except for 'current'. The table + Appendix C: Statistical aggregations shows what aggregation types are available for which types.

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

-

<html>

-

  <head>

-

    <title>Year stats by month</title>

-

  </head>

-

  <body>

-

  <p>Min, max temperatures -by month:</p>

-

  # for $month in $year.months

-

    $month.outTemp.min $month.outTemp.max

-

  # end for 

-

  </body>

-

</html>

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

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

-

The Standard skin configuration file

+

By writing a custom generator

+

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

+

Extending an existing generator

+

In the section on Customizing templates, we have seen how you can +change a template and make use of the various tags available such as +$day.outTemp.max for the maximum outside temperature +for the day. But, what if you +want to introduce some new data for which no tag is available? For example, +suppose you want to use Google analytics, and you want to make reference to the +Google Tracker ID? You could modify a template and hardware it in, but then +you'd have to do that for every template. What you'd really like to do is be +able to obtain it through a tag:

+
... (Google Analytics boilerplate)
+<script type="text/javascript">
+try {
+  var pageTracker = _gat._getTracker($googleID);
+  pageTracker._trackPageview();
+} catch(err) {}
+</script>
+
... (More Google Analytics boilerplate)
+ +

Here, the Google ID has been parameterized as a tag, $googleID. +But, where does this value come from? How do I make it available inside my +templates?

+

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

+

File mygenerator.py:

+
from weewx.filegenerator import FileGenerator
+
+class MyFileGenerator(FileGenerator):
+    
+    def getToDateSearchList(self, currentRec, stop_ts):
+
+        # Call the superclass's version:
+        search_list = FileGenerator.getToDateSearchList(currentRec, stop_ts)
+    
+        search_list += [ {'googleID' : 'UA-19281126-1'} ]
+        
+        return search_list
+

Here, we first get the search list from the superclass, FileGenerator, and +then (shown highlighted) add to it our simple dictionary with a single entry: a +key of "googleID" and value of "UA-19281126-1".

+

Of course, this approach is not entirely satisfactory because you've +hardwired in the Google ID tag in your Python code. It would be much more +desirable to get it from the configuration file. One way to do this is to add a +section to the weewx configuration file with all your special options, including +the Google tag:

+

File weewx.conf:

+
...
+
[MyOptions]
+
  googleID = UA-19281126-1
+
  myName = Mary
+
  [[Video]]
+
    video_capture = http://www.acme.com/videoimage.jpg
+
...
+

The code for mygenerator.py gets modified slightly +to inject the whole configuration section into the search list (shown +highlighted below):

+
from weewx.filegenerator import FileGenerator
+
+class MyFileGenerator(FileGenerator):
+    
+    def getToDateSearchList(self, currentRec, stop_ts):
+
+        # Call the superclass's version:
+        search_list = FileGenerator.getToDateSearchList(currentRec, stop_ts)
+    
+        search_list += [ self.config_dict['MyOptions'] ]
+        
+        return search_list
+

With this approach, you can now use all sorts of custom tags (highlighted) in +your HTML templates:

+
...
+
<img src="$Video.video_capture" alt="Image of the house" />
+
 
+
<p>The operator's name is $myName.</p>
+
 
+
... (Google Analytics boilerplate)
+<script type="text/javascript">
+try {
+  var pageTracker = _gat._getTracker($googleID);
+  pageTracker._trackPageview();
+} catch(err) {}
+</script>
+
... (More Google Analytics boilerplate)
+ +
 
+

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

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

Reference: The Standard skin configuration file

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

It is worth noting that, like the main configuration file @@ -532,23 +639,25 @@ or "degree_C."

unit when a quantity needs to be converted to a string. Typically, this happens with y-axis labeling on plots and for statistics in HTML file generation. For example, the options

-

degree_C = %.1f

-

inch     = %.2f

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

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

-

[[Labels]]

-

ThiThis subsection specifies what label is to be used for each measurement unit +

[[Labels]]

+

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

-

degree_F = °F

-

inch     = ' in'

+
degree_F = °F
+
inch     = ' in'

would cause all temperatures to have unit labels '°F' and all precipitation to have labels  ' in'. If any special symbols are to -be used (such as the degree sign above) they should be encoded in UTF-8. (This is -generally what most text editors use if you cut-and-paste from a character map.)

+be used (such as the degree sign above) they should be encoded in UTF-8. This is +generally what most text editors use if you cut-and-paste from a character map. +Labels used in plot images will be converted to Latin-1 first (this is all the +Python Imaging Library can handle).

[Labels]

This section sets the various labels to use.

hemispheres

@@ -557,16 +666,16 @@ default is "N, S, E, W".

[[Generic]]

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

-

inTemp = Temperature inside the house

-

outTemp = Outside Temperature

+
inTemp = Temperature inside the house
+
outTemp = Outside Temperature

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

-

[[Time]]

+

[[Time]]

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

-

week  = %H:%M on %A

-

month = %d-%b-%Y %H:%M

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

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

@@ -665,12 +774,12 @@ week, month, and year). These sub-sections define the nature of aggregation and plot types for the time period. For example, here's a typical set of options for sub-section [[month_images]], controlling how images that cover a month period are generated:

-

[[month_images]]

-

  x_label_format = %d

-

  bottom_label_format = %m/%d/%y %H:%M

-

  time_length = 2592000 # == 30 days

-

  aggregate_type = avg

-

  aggregate_interval = 10800 # == 3 hours

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

The option x_label_format gives a strftime() type format for the x-axis. In this example, it will only show days (format option @@ -686,12 +795,12 @@ at one additional nesting level (!) are the logical names of all the line types to be drawn in the image.  Values specified in the level above can be overridden. For example, here's a typical set of options for sub-sub-section [[[[[[[monthrain]]]: /p> class="tty">[[[[[[monthrain]]]

-

  plot_type = bar

-

  yscale = None, None, 0.02

-

  [[[[rain]]]]

-

    aggregate_type = sum

-

    aggregate_interval = 86400

-

    label = Rain (daily avg)

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

This will generate an image file with name monthrain.png. It will be a bar plot. Option yscale controls the y-axis scaling — if left out, the scale will automatically be chosen. However, in this @@ -727,13 +836,12 @@ that includes a line plot of both outside temperature and dewpoint.

hourly averages. Here, you are using the same data type ('outTemp') for both plot lines, the first with averages, the second without. If you do the obvious it won't work:

-

## WRONG ##

-

[[[[[[daytemp_with_avg]]]

-

  [[[[outTemp]]]]

-

    aggregate_type = avg

-

    aggregate_interval = 3600

-

  [[[[outTemp]]]]  # OOPS! The same section name appears -more than once!

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

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

-

[[[daytemp_with_avg]]]

-

    [[[[a_logical_name]]]]

-

      data_type = outTemp

-

      aggregate_type = avg

-

      aggregate_interval = 3600

-

    [[[[outTemp]]]]

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

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

-

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

+

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

+

+Daytime temperature with running average

Progressive vector plots

Weewx can produce progressive vector plots as well as the more conventional x-y plots. To produce these, use plot type 'vector'. @@ -762,26 +873,31 @@ and 'windgustvec'. While they don't actual the SQL database, weewx understands that they represent special vector-types. The first, 'windvec', represents the average wind in an archive period, the second, 'windgustvec' -the max wind in an archive period. Here's how to produce a progressive vector plot -of the year's biggest daily wind gusts, along with daily averages:

-

[[[yeargustoverlay]]]

-

  aggregate_interval = 86400

-

  [[[[windvec]]]]

-

    plot_type = vector

-

    aggregate_type = avg

-

  [[[[windgustvec]]]]

-

    plot_type = vector

-

    aggregate_type = max

-

This will produce an image file with name yeargustoverlay.png. -It will consist of two progressive vector plots, both using daily aggregation (86,400 -seconds). For the first set of vectors, the daily average will be used. In the second, -the max of the gusts will be used.

-

By By default, the sticks in the progressive wind plots point towards the wind +the max wind in an archive period. Here's how to produce a progressive vector +for one week that shows the hourly biggest wind gusts, along with hourly averages:

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

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

+

+hourly average wind vector overlaid with gust vectors

+

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

If you use this kind of plot (the out-of-the-box version of weewx includes daily, weekly, monthly, and yearly progressive @@ -794,10 +910,10 @@ observation types such as barometer, you want to also generate some oversized pl to give you extra detail, perhaps for an HTML popup. The standard weewx.conf file specifies plot size of 300x180 pixels, which will be used for all plots unless overridden:

-

[Images]

-

  ...

-

  image_width=300

-

  image_height = 180

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

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

    [[[daybarometer]]]

      [[[[barometer]]]]

@@ -807,7 +923,8 @@ size. This image will be put in file an class="code" daybarometer_big.

      image_width  = 600

      image_height = 360

      [[[[barometer]]]]

-

Customizing the weewx service engine

+

Customizing the +weewx service engine

This is an advance topic intended for those who wish to try their hand at extending the internal engine in weewx.

At a high level, weewx consists of an engine that is responsible for @@ -865,33 +982,27 @@ arrives, but leave the printing of archive packets alone. This could be done by subclassing the default print service StdPrint and overriding member function processLoopPacket().

In file myprint.py:

-

from weewx.wxengine import StdPrint
-from weeutil.weeutil import timestamp_to_string
-
-class MyPrint(StdPrint):
-
-    # Override the default processLoopPacket:
-    def processLoopPacket(self, physicalPacket):
-        print "LOOP: ", timestamp_to_string(physicalPacket['dateTime']),\
-            physicalPacket['barometer'],\
-            physicalPacket['outTemp'],\
-            physicalPacket['outHumidity'],\
-            physicalPacket['windSpeed'],\
-            physicalPacket['windDir']

+
from weewx.wxengine import StdPrint
+from weeutil.weeutil import timestamp_to_string
+
+class MyPrint(StdPrint):
+
+    # Override the default processLoopPacket:
+    def processLoopPacket(self, physicalPacket):
+        print "LOOP: ", timestamp_to_string(physicalPacket['dateTime']),\
+            physicalPacket['barometer'],\
+            physicalPacket['outTemp'],\
+            physicalPacket['outHumidity'],\
+            physicalPacket['windSpeed'],\
+            physicalPacket['windDir']

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

-

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

-

(Note that this list is shown on several lines for clarity, but in actuality -it must be all on one line. The parser ConfigObj does +

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

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

Adding a Service

Suppose there is no service that can be easily customized for your needs. @@ -901,137 +1012,102 @@ you need. Here's an example that implements an alarm that sends off an email when an arbitrary expression evaluates True. This example is included in the standard distribution in subdirectory "examples".

File examples/alarm.py:

-

import time
-import smtplib
-from email.mime.text import MIMEText
-import threading
-import syslog
-
-from weewx.wxengine import StdService
-from weeutil.weeutil import timestamp_to_string
-
-# Inherit from the base class StdService:
-class MyAlarm(StdService):
-    """Custom service that sounds an alarm if an expression evaluates -true"""
-
-    def __init__(self, engine):
-        # Pass the initialization information -on to my superclass:
-        StdService.__init__(self, engine)
-
-        # This will hold the time when the last -alarm message went out:
-        self.last_msg = None
-        self.expression = None
-
-    def setup(self):
-        try:
-            # Dig the needed -options out of the configuration dictionary.
-            # If a critical -option is missing, an exception will be thrown and
-            # the alarm will -not be set.
-            self.expression -= self.engine.config_dict['Alarm']['expression']
-            self.time_wait -= int(self.engine.config_dict['Alarm'].get('time_wait', '3600'))
-            self.smtp_host -= self.engine.config_dict['Alarm']['smtp_host']
-            self.smtp_user -= self.engine.config_dict['Alarm'].get('smtp_user')
-            self.smtp_password -= self.engine.config_dict['Alarm'].get('smtp_password')
-            self.TO = self.engine.config_dict['Alarm']['mailto']
-            syslog.syslog(syslog.LOG_INFO, -"alarm: Alarm set for expression %s" % self.expression)
-        except:
-            self.expression -= None
-            self.time_wait -= None
-
-    def postArchiveData(self, rec):
-        # Let the super class see the record -first:
-        StdService.postArchiveData(self, rec)
-
-        # See if the alarm has been set:
-        if self.expression:
-            # To avoid a -flood of nearly identical emails, this will do
-            # the check only -if we have never sent an email, or if we haven't
-            # sent one in -the last self.time_wait seconds:
-            if not self.last_msg -or abs(time.time() - self.last_msg) >= self.time_wait :
-
-                -# Evaluate the expression in the context of 'rec'.
-                -# Sound the alarm if it evaluates true:
-                -if eval(self.expression, None, rec):        # -NOTE 1
-                    -# Sound the alarm!
-                    -# Launch in a separate thread so it doesn't block the main LOOP thread:
-                    -t = threading.Thread(target = MyAlarm.soundTheAlarm, args=(self, rec))
-                    -t.start()
-
-    def soundTheAlarm(self, rec):
-        """This function is called when the given -expression evaluates True."""
-
-        # Get the time and convert to a string:
-        t_str = timestamp_to_string(rec['dateTime'])
-        # Form the message text:
-        msg_text = "Alarm expression %s evaluated -True at %s\nRecord:\n%s" % (self.expression, t_str, str(rec))
-        # Convert to MIME:
-        msg = MIMEText(msg_text)
-
-        # Fill in MIME headers:
-        msg['Subject'] = "Alarm message from -weewx"
-        msg['From'] = "weewx"
-        msg['To'] = self.TO
-
-        # Create an instance of class SMTP for -the given SMTP host:
-        s = smtplib.SMTP(self.smtp_host)
-        # If a username has been given, assume -that login is required for this host:
-        if self.smtp_user:
-            s.login(self.smtp_user, -self.smtp_password)
-        # Send the email:
-        s.sendmail(msg['From'], [self.TO], msg.as_string())
-        # Log out of the server:
-        s.quit()
-        # Record when the message went out:
-        self.last_msg = time.time()
-        # Log it in the system log:
-        syslog.syslog(syslog.LOG_INFO, "alarm: -Alarm sounded for expression %s" % self.expression)
-        syslog.syslog(syslog.LOG_INFO, " *** -email sent to: %s" % self.TO)

+
import time
+import smtplib
+from email.mime.text import MIMEText
+import threading
+import syslog
+
+from weewx.wxengine import StdService
+from weeutil.weeutil import timestamp_to_string
+
+# Inherit from the base class StdService:
+class MyAlarm(StdService):
+    """Custom service that sounds an alarm if an expression evaluates true"""
+
+    def __init__(self, engine):
+        # Pass the initialization information on to my superclass:
+        StdService.__init__(self, engine)
+
+        # This will hold the time when the last alarm message went out:
+        self.last_msg = None
+        self.expression = None
+
+    def setup(self):
+        try:
+            # Dig the needed options out of the configuration dictionary.
+            # If a critical option is missing, an exception will be thrown and
+            # the alarm will not be set.
+            self.expression = self.engine.config_dict['Alarm']['expression']
+            self.time_wait = int(self.engine.config_dict['Alarm'].get('time_wait', '3600'))
+            self.smtp_host = self.engine.config_dict['Alarm']['smtp_host']
+            self.smtp_user = self.engine.config_dict['Alarm'].get('smtp_user')
+            self.smtp_password = self.engine.config_dict['Alarm'].get('smtp_password')
+            self.TO = self.engine.config_dict['Alarm']['mailto']
+            syslog.syslog(syslog.LOG_INFO, "alarm: Alarm set for expression %s" % self.expression)
+        except:
+            self.expression = None
+            self.time_wait = None
+
+    def postArchiveData(self, rec):
+        # Let the super class see the record first:
+        StdService.postArchiveData(self, rec)
+
+        # See if the alarm has been set:
+        if self.expression:
+            # To avoid a flood of nearly identical emails, this will do
+            # the check only if we have never sent an email, or if we haven't
+            # sent one in the last self.time_wait seconds:
+            if not self.last_msg or abs(time.time() - self.last_msg) >= self.time_wait :
+
+                # Evaluate the expression in the context of 'rec'.
+                # Sound the alarm if it evaluates true:
+                if eval(self.expression, None, rec):        # NOTE 1
+                    # Sound the alarm!
+                    # Launch in a separate thread so it doesn't block the main LOOP thread:
+                    t = threading.Thread(target = MyAlarm.soundTheAlarm, args=(self, rec))
+                    t.start()
+
+    def soundTheAlarm(self, rec):
+        """This function is called when the given expression evaluates True."""
+
+        # Get the time and convert to a string:
+        t_str = timestamp_to_string(rec['dateTime'])
+        # Form the message text:
+        msg_text = "Alarm expression %s evaluated True at %s\nRecord:\n%s" % (self.expression, t_str, str(rec))
+        # Convert to MIME:
+        msg = MIMEText(msg_text)
+
+        # Fill in MIME headers:
+        msg['Subject'] = "Alarm message from weewx"
+        msg['From'] = "weewx"
+        msg['To'] = self.TO
+
+        # Create an instance of class SMTP for the given SMTP host:
+        s = smtplib.SMTP(self.smtp_host)
+        # If a username has been given, assume that login is required for this host:
+        if self.smtp_user:
+            s.login(self.smtp_user, self.smtp_password)
+        # Send the email:
+        s.sendmail(msg['From'], [self.TO], msg.as_string())
+        # Log out of the server:
+        s.quit()
+        # Record when the message went out:
+        self.last_msg = time.time()
+        # Log it in the system log:
+        syslog.syslog(syslog.LOG_INFO, "alarm: Alarm sounded for expression %s" % self.expression)
+        syslog.syslog(syslog.LOG_INFO, " *** email sent to: %s" % self.TO)

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

-

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

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

These options specify that the alarm is to be sounded when "outTemp < 40.0" evaluates True, that is when the outside temperature is below 40.0 degrees. Any valid Python expression can be used, although the only variables available @@ -1053,16 +1129,10 @@ are specified with options smtp_user and

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

-

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

-

(Again, note that this list is shown on several lines for clarity, but in actuality -it must be all on one line.)

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

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

Customizing the Engine

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

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

This example is in file example/daily.py:</:

-

from weewx.wxengine import StdEngine, StdService
-from weeutil.weeutil import startOfArchiveDay
-
-class MyEngine(StdEngine):
-    """A customized weewx engine."""
-
-    def __init__(self, *args, **vargs):
-        # Pass on the initialization data to -my superclass:
-        StdEngine.__init__(self, *args, **vargs)
-
-        # This will record the timestamp of the -old day
-        self.old_day = None
-
-    def postArchiveData(self, rec):
-        # First let my superclass process it:
-        StdEngine.postArchiveData(self, rec)
-
-        # Get the timestamp of the start of the -day using
-        # the utility function startOfArchiveDay -
-        dayStart_ts = startOfArchiveDay(rec['dateTime'])
-
-        # Call the function firstArchiveOfDay -if either this is
-        # the first archive since startup, or -if a new day has started
-        if not self.old_day or self.old_day != -dayStart_ts:
-            self.old_day -= dayStart_ts
-            self.newDay(rec)                          -# Note 1
-
-    def newDay(self, rec):
-        """Called when the first archive record -of a day arrives."""
-
-        # Go through the list of service objects. -This
-        # list is actually in my superclass StdEngine.
-        for svc_obj in self.service_obj:
-            # Because this -is a new event, not all services will
-            # be prepared -to accept it. Check first to see if the
-            # service has -a member function "firstArchiveOfDay"
-            # before calling -it:
-            if hasattr(svc_obj, -"firstArchiveOfDay"):  # Note 2
-                -# The object does have the member function. Call it:
-                -svc_obj.firstArchiveOfDay(rec)

+
from weewx.wxengine import StdEngine, StdService
+from weeutil.weeutil import startOfArchiveDay
+
+class MyEngine(StdEngine):
+    """A customized weewx engine."""
+
+    def __init__(self, *args, **vargs):
+        # Pass on the initialization data to my superclass:
+        StdEngine.__init__(self, *args, **vargs)
+
+        # This will record the timestamp of the old day
+        self.old_day = None
+
+    def postArchiveData(self, rec):
+        # First let my superclass process it:
+        StdEngine.postArchiveData(self, rec)
+
+        # Get the timestamp of the start of the day using
+        # the utility function startOfArchiveDay 
+        dayStart_ts = startOfArchiveDay(rec['dateTime'])
+
+        # Call the function firstArchiveOfDay if either this is
+        # the first archive since startup, or if a new day has started
+        if not self.old_day or self.old_day != dayStart_ts:
+            self.old_day = dayStart_ts
+            self.newDay(rec)                          # Note 1
+
+    def newDay(self, rec):
+        """Called when the first archive record of a day arrives."""
+
+        # Go through the list of service objects. This
+        # list is actually in my superclass StdEngine.
+        for svc_obj in self.service_obj:
+            # Because this is a new event, not all services will
+            # be prepared to accept it. Check first to see if the
+            # service has a member function "firstArchiveOfDay"
+            # before calling it:
+            if hasattr(svc_obj, "firstArchiveOfDay"):  # Note 2
+                # The object does have the member function. Call it:
+                svc_obj.firstArchiveOfDay(rec)

This customized engine works by monitoring the arrival of archive records, and checking their time stamp (rec['dateTime']. It calculates the time stamp for the start of the day, and if it changes, calls member function @@ -1142,46 +1194,37 @@ has a function firstArchiveOfDay before calling it (No the line

weewx.wxengine.main()

so that it uses your new engine:

-

from examples.daily import MyEngine

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

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

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

-

# Define a new service to take advantage of the new event
-class DailyService(StdService):
-    """This service can do something when the first archive record -of
-    a day arrives."""
-
-    def firstArchiveOfDay(self, rec):
-        """Called when the first archive record -of a day arrives."""
-
-        print "The first archive of the day has -arrived!"
-        print rec
-
-        # You might want to do something here -like run a cron job

+
# Define a new service to take advantage of the new event
+class DailyService(StdService):
+    """This service can do something when the first archive record of
+    a day arrives."""
+
+    def firstArchiveOfDay(self, rec):
+        """Called when the first archive record of a day arrives."""
+
+        print "The first archive of the day has arrived!"
+        print rec
+
+        # You might want to do something here like run a cron job

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

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

-

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

-

(Again, note that this list is shown on several lines for clarity, but in actuality -it must be all on one line.)

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

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

Appendix A: Types

The weather variables used in weewx generally fall into three different camps. @@ -1525,7 +1568,7 @@ you configured weewx to use that type.

GeneratorGenerator Function
weewx.reportengine.FileGeneratorweewx.filegenerator.FileGenerator Generates HTML files from templates.
weewx.reportengine.ImageGeneratorweewx.imagegenerator.ImageGenerator Generates image plots
X
-

Appendix B: Units

+

Appendix B: Units

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

@@ -1648,7 +1691,7 @@ options for the group.

-

Appendix C: Statistical aggregations

+

Appendix C: Statistical aggregations

Most of the templates are devoted to reporting statistical aggregates, such as monthly max temperature, or year-to-date rainfall. These are called aggregations, because they are a summary of lots of diff --git a/docs/daytemp_with_avg.png b/docs/daytemp_with_avg.png new file mode 100644 index 00000000..0ed55bde Binary files /dev/null and b/docs/daytemp_with_avg.png differ diff --git a/docs/readme.htm b/docs/readme.htm index 8890989b..27e42f61 100644 --- a/docs/readme.htm +++ b/docs/readme.htm @@ -85,11 +85,12 @@ table { td { border-style: solid; border-width: 1px; + padding: 5px; } .indent { margin-left: 40px; } -.tty { +.tty, pre { font-family: "Courier New", Courier, monospace; margin-left: 40px; margin-top: 0px; @@ -118,7 +119,20 @@ td { .xxsmall { font-size: xx-small; } +.highlight { + background-color: #FFFF66; +} +.center { + text-align: center; +} +.Example_output { + padding: 10px; + border: thin #000000 dotted; + font-family: "Times New Roman", Times, serif; + margin-left: 40px; +} + @@ -164,6 +178,9 @@ Version 1.6 notes

+

For information on customizing weewx, see the +separate document Customizing +weewx.

1. Copyright

(c) 2009, 2010 by Tom Keffer <tkeffer@gmail.com>

This program is free software: you can redistribute it and/or modify it under @@ -383,8 +400,7 @@ the weewx directory hierarchy, then

and scripts are installed;
  • $WEEWX_ROOT/weewx.conf is the configuration file;
  • -
  • $WEEWX_ROOT/templates is where the html - templates live;
  • +
  • $WEEWX_ROOT/skins is where the skins live;
  • $WEEWX_ROOT/archive is the directory where the sqlite3 databases live;
  • $WEEWX_ROOT/public_html is where @@ -417,7 +433,9 @@ specific actions you need to do.

    setup.cfg.

    The build and install process will do the following for you.