@@ -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
+
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.
-
+
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.
- 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.
+ outside temperature and do not appear directly in the database. See
+ Appendix A: Types for a table of statistical
+ types.
- 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
+
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
+
+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.
-
+
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:
+
+
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:
+
+
+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
+
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.
X |