mirror of
https://github.com/weewx/weewx.git
synced 2026-04-19 00:56:54 -04:00
515 lines
24 KiB
HTML
515 lines
24 KiB
HTML
<!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" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
|
<!-- $Revision$ -->
|
|
<!-- $Author$ -->
|
|
<!-- $Date$ -->
|
|
<head>
|
|
<meta http-equiv="Content-Language" content="en-us" />
|
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
|
<title>Customizing weewx</title>
|
|
<style type="text/css">
|
|
body {
|
|
font: 11pt Verdana,arial,sans-serif;
|
|
color: black;
|
|
}
|
|
p {
|
|
font: 11pt Verdana,arial,sans-serif;
|
|
color: black;
|
|
}
|
|
ol {
|
|
font: 11pt Verdana,arial,sans-serif;
|
|
color: black;
|
|
}
|
|
ul {
|
|
font: 11pt Verdana,arial,sans-serif;
|
|
color: black;
|
|
}
|
|
li {
|
|
font: 11pt Verdana,arial,sans-serif;
|
|
color: black;
|
|
}
|
|
dl {
|
|
font: 11pt Verdana,arial,sans-serif;
|
|
color: black;
|
|
}
|
|
dt {
|
|
font: 11pt Verdana,arial,sans-serif;
|
|
color: black;
|
|
}
|
|
dd {
|
|
font: 11pt Verdana,arial,sans-serif;
|
|
color: black;
|
|
}
|
|
h1 {
|
|
font: normal normal bold 18pt Verdana, arial, sans-serif;
|
|
color: teal;
|
|
border: 1px solid black;
|
|
border-bottom: 2px solid black;
|
|
border-right: 2px solid black;
|
|
background-color: #e8e8e8;
|
|
padding-left: .5em;
|
|
padding-right: .5em;
|
|
margin-top: 60pt;
|
|
}
|
|
h2 {
|
|
font: 15pt Verdana,arial,sans-serif;
|
|
color: teal;
|
|
border: 1px solid black;
|
|
background-color: #e8e8e8;
|
|
padding-left: .5em;
|
|
padding-right: .5em;
|
|
margin-top: 30pt;
|
|
}
|
|
h3 {
|
|
font: 12pt Verdana, arial, sans-serif;
|
|
color: teal;
|
|
border: 1px solid black;
|
|
background-color: #e8e8e8;
|
|
padding-left: .5em;
|
|
padding-right: .5em;
|
|
}
|
|
h4 {
|
|
font: 12pt Verdana,arial,sans-serif;
|
|
color: black;
|
|
font-weight: bold;
|
|
}
|
|
.code {
|
|
font-family: "Courier New", Courier, monospace;
|
|
}
|
|
table {
|
|
border-style: solid;
|
|
border-width: 1px;
|
|
border-collapse: collapse;
|
|
}
|
|
td {
|
|
border-style: solid;
|
|
border-width: 1px;
|
|
}
|
|
.indent {
|
|
margin-left: 40px;
|
|
}
|
|
.tty {
|
|
font-family: "Courier New", Courier, monospace;
|
|
margin-left: 40px;
|
|
margin-top: 0px;
|
|
margin-bottom: 0px;
|
|
background-color: #FFFFCC;
|
|
}
|
|
.title {
|
|
text-align: center;
|
|
margin-top: 0px;
|
|
}
|
|
.config_option {
|
|
font-family: "Courier New", Courier, monospace;
|
|
font-weight: normal;
|
|
}
|
|
.config_section {
|
|
font-family: "Courier New", Courier, monospace;
|
|
}
|
|
.config_important {
|
|
font-family: "Courier New", Courier, monospace;
|
|
font-weight: bold;
|
|
color: #0000FF;
|
|
}
|
|
.bold_n_blue {
|
|
color: #0000FF;
|
|
}
|
|
.style1 {
|
|
background-color: #FFFFCC;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<h1 class="title">Customizing weewx v1.5</h1>
|
|
<h1>Overview</h1>
|
|
<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 with a
|
|
set of member functions. The engine arranges to have appropriate member
|
|
functions called when specific events happen. For example, when a new LOOP
|
|
packet arrives, member function <span class="code">processLoopPacket()</span> of
|
|
all services is called.</p>
|
|
<p>To customize, you can</p>
|
|
<ul>
|
|
<li>Customize a service</li>
|
|
<li>Add a service</li>
|
|
<li>Customize the engine</li>
|
|
</ul>
|
|
<p>This document describes how to do all three.</p>
|
|
<p>The default install of <span class="code">weewx</span> includes the following services:</p>
|
|
<table style="width: 100%">
|
|
<tr>
|
|
<td><strong>Service</strong></td>
|
|
<td><strong>Function</strong></td>
|
|
</tr>
|
|
<tr>
|
|
<td><span class="code">weewx.wxengine.StdWunderground</span></td>
|
|
<td>Starts thread to manage WU connection; adds new data to a Queue to
|
|
be posted to the WU by the thread.</td>
|
|
</tr>
|
|
<tr>
|
|
<td><span class="code">weewx.wxengine.StdCatchUp</span></td>
|
|
<td>Any data found on the weather station memory but not yet in the
|
|
archive, is retrieved and put in the archive.</td>
|
|
</tr>
|
|
<tr>
|
|
<td><span class="code">weewx.wxengine.StdTimeSynch</span></td>
|
|
<td>Arranges to have the clock on the station synchronized at regular
|
|
intervals.</td>
|
|
</tr>
|
|
<tr>
|
|
<td><span class="code">weewx.wxengine.StdPrint</span></td>
|
|
<td>Prints out new LOOP and archive packets on the console.</td>
|
|
</tr>
|
|
<tr>
|
|
<td><span class="code">weewx.wxengine.StdProcess</span></td>
|
|
<td>Launches a new thread to do processing after a new archive record
|
|
arrives. The thread loads zero or more reports and processes them in
|
|
order. Reports do things such as generate HTML files, generate images,
|
|
or FTP files to a web server. New reports can be added easily by the
|
|
user.</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<h1>Customizing a Service</h1>
|
|
<p>The service <span class="code">weewx.wxengine.StdPrint</span> prints out new LOOP and archive packets
|
|
to the console when they arrive. By default, it prints out time, barometer,
|
|
outside temperature, wind speed, and wind direction. Suppose you don't like
|
|
this, and want to print out humidity as well when a new LOOP packet arrives, but
|
|
leave the printing of archive packets alone. This could be done by subclassing the default
|
|
print service <span class="code">StdPrint</span> and overriding member function <span class="code">
|
|
processLoopPacket()</span>. </p>
|
|
<p>In file <span class="code">myprint.py</span>:</p>
|
|
<p class="tty">from weewx.wxengine import StdPrint<br />
|
|
from weeutil.weeutil import timestamp_to_string<br />
|
|
<br />
|
|
class MyPrint(StdPrint):<br />
|
|
<br />
|
|
# Override the default processLoopPacket:<br />
|
|
def processLoopPacket(self, physicalPacket):<br />
|
|
print "LOOP: ", timestamp_to_string(physicalPacket['dateTime']),\<br />
|
|
|
|
physicalPacket['barometer'],\<br />
|
|
|
|
physicalPacket['outTemp'],\<br />
|
|
|
|
physicalPacket['outHumidity'],\<br />
|
|
|
|
physicalPacket['windSpeed'],\<br />
|
|
|
|
physicalPacket['windDir']</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 the standard print service name in the
|
|
option <span class="code">service_list</span>, located in <span class="code">
|
|
[Engines][[WxEngine]]</span>:</p>
|
|
<p class="tty">[Engines]<br />
|
|
[[WxEngine]]<br />
|
|
|
|
service_list = weewx.wxengine.StdWunderground, weewx.wxengine.StdCatchUp,<br />
|
|
weewx.wxengine.StdTimeSynch, myprint.MyPrint,<br />
|
|
weewx.wxengine.StdProcess</p>
|
|
<p>(Note that this list is shown on several lines for clarity, but in actuality
|
|
it must be all on one line. The parser <span class="code">ConfigObj</span> does
|
|
not allow options to be continued on to following lines.)</p>
|
|
<h1>Adding a Service</h1>
|
|
<p>Suppose there is no service that can easily be 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'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 '<span class="code">examples</span>.'</p>
|
|
<p>File <span class="code">examples/alarm.py</span>:</p>
|
|
<p class="tty">import time<br />
|
|
import smtplib<br />
|
|
from email.mime.text import MIMEText<br />
|
|
import threading<br />
|
|
import syslog<br />
|
|
<br />
|
|
from weewx.wxengine import StdService<br />
|
|
from weeutil.weeutil import timestamp_to_string<br />
|
|
<br />
|
|
# Inherit from the base class StdService:<br />
|
|
class MyAlarm(StdService):<br />
|
|
"""Custom service that sounds an alarm if an expression
|
|
evaluates true"""<br />
|
|
<br />
|
|
def __init__(self, engine):<br />
|
|
# Pass the initialization information
|
|
on to my superclass:<br />
|
|
StdService.__init__(self, engine)<br />
|
|
<br />
|
|
# This will hold the time when the
|
|
last alarm message went out:<br />
|
|
self.last_msg = None<br />
|
|
self.expression = None<br />
|
|
<br />
|
|
def setup(self):<br />
|
|
try:<br />
|
|
# Dig the
|
|
needed options out of the configuration dictionary.<br />
|
|
# If a
|
|
critical option is missing, an exception will be thrown and<br />
|
|
# the alarm
|
|
will not be set.<br />
|
|
|
|
self.expression = self.engine.config_dict['Alarm']['expression']<br />
|
|
|
|
self.time_wait = int(self.engine.config_dict['Alarm'].get('time_wait', '3600'))<br />
|
|
|
|
self.smtp_host = self.engine.config_dict['Alarm']['smtp_host']<br />
|
|
|
|
self.smtp_user = self.engine.config_dict['Alarm'].get('smtp_user')<br />
|
|
|
|
self.smtp_password = self.engine.config_dict['Alarm'].get('smtp_password')<br />
|
|
self.TO =
|
|
self.engine.config_dict['Alarm']['mailto']<br />
|
|
syslog.syslog(syslog.LOG_INFO,
|
|
"alarm: Alarm set for expression %s" % self.expression)<br />
|
|
except:<br />
|
|
|
|
self.expression = None<br />
|
|
|
|
self.time_wait = None<br />
|
|
<br />
|
|
def postArchiveData(self, rec):<br />
|
|
# Let the super class see the record
|
|
first:<br />
|
|
StdService.postArchiveData(self, rec)<br />
|
|
<br />
|
|
# See if the alarm has been set:<br />
|
|
if self.expression:<br />
|
|
# To avoid a
|
|
flood of nearly identical emails, this will do<br />
|
|
# the check
|
|
only if we have never sent an email, or if we haven't<br />
|
|
# sent one in
|
|
the last self.time_wait seconds:<br />
|
|
if not
|
|
self.last_msg or abs(time.time() - self.last_msg) >= self.time_wait :<br />
|
|
<br />
|
|
|
|
# Evaluate the expression in the context of 'rec'.<br />
|
|
|
|
# Sound the alarm if it evaluates true:<br />
|
|
|
|
if eval(self.expression, None, rec): #
|
|
NOTE 1<br />
|
|
|
|
# Sound the alarm!<br />
|
|
|
|
# Launch in a separate thread so it doesn't block the main LOOP thread:<br />
|
|
|
|
t = threading.Thread(target = MyAlarm.soundTheAlarm, args=(self, rec))<br />
|
|
|
|
t.start()<br />
|
|
<br />
|
|
def soundTheAlarm(self, rec):<br />
|
|
"""This function is called when the
|
|
given expression evaluates True."""<br />
|
|
<br />
|
|
# Get the time and convert to a
|
|
string:<br />
|
|
t_str = timestamp_to_string(rec['dateTime'])<br />
|
|
# Form the message text:<br />
|
|
msg_text = "Alarm expression %s
|
|
evaluated True at %s\nRecord:\n%s" % (self.expression, t_str, str(rec))<br />
|
|
# Convert to MIME:<br />
|
|
msg = MIMEText(msg_text)<br />
|
|
<br />
|
|
# Fill in MIME headers:<br />
|
|
msg['Subject'] = "Alarm message from
|
|
weewx"<br />
|
|
msg['From'] = "weewx"<br />
|
|
msg['To'] = self.TO<br />
|
|
<br />
|
|
# Create an instance of class SMTP
|
|
for the given SMTP host:<br />
|
|
s = smtplib.SMTP(self.smtp_host)<br />
|
|
# If a username has been given,
|
|
assume that login is required for this host:<br />
|
|
if self.smtp_user:<br />
|
|
s.login(self.smtp_user,
|
|
self.smtp_password)<br />
|
|
# Send the email:<br />
|
|
s.sendmail(msg['From'], [self.TO],
|
|
msg.as_string())<br />
|
|
# Log out of the server:<br />
|
|
s.quit()<br />
|
|
# Record when the message went out:<br />
|
|
self.last_msg = time.time()<br />
|
|
# Log it in the system log:<br />
|
|
syslog.syslog(syslog.LOG_INFO,
|
|
"alarm: Alarm sounded for expression %s" % self.expression)<br />
|
|
syslog.syslog(syslog.LOG_INFO, " ***
|
|
email sent to: %s" % self.TO)</p>
|
|
<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>
|
|
<p class="tty">[Alarm]<br />
|
|
expression = "outTemp < 40.0"<br />
|
|
time_wait = 1800<br />
|
|
smtp_host = smtp.mymailserver.com<br />
|
|
smtp_user = myusername<br />
|
|
smtp_password = mypassword<br />
|
|
mailto = auser@adomain.com</p>
|
|
<p>These options specify that the alarm is to be sounded when "<span class="code">outTemp
|
|
< 40.0</span>" evaluates True, that is when the outside temperature is below
|
|
40.0 degrees. Any valid Python expression can be used, although the only
|
|
variables available are those in the current archive record. (The place in the
|
|
code where the expression is evaluated is marked with "<span class="code">Note 1</span>".)</p>
|
|
<p>Another example expression could be:</p>
|
|
<p class="tty"> expression = "outTemp < 32.0 and windSpeed >
|
|
10.0"</p>
|
|
<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>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 is specified in 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>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 <span class="code">
|
|
service_list</span>, located in <span class="code">[Engines][[WxEngine]]</span>:</p>
|
|
<p class="tty">[Engines]<br />
|
|
[[WxEngine]]<br />
|
|
|
|
service_list = weewx.wxengine.StdWunderground, weewx.wxengine.StdCatchUp,<br />
|
|
weewx.wxengine.StdTimeSynch, weewx.wxengine.StdPrint,<br />
|
|
weewx.wxengine.StdProcess, examples.alarm.MyAlarm</p>
|
|
<p>(Again, note that this list is shown on several lines for clarity, but in
|
|
actuality it must be all on one line.)</p>
|
|
<h1>Customizing the Engine</h1>
|
|
<p>In this section, we look at how to install a custom Engine. In general, this
|
|
is the least desirable way to proceed, but in some cases it may be the only way
|
|
to get what you want.</p>
|
|
<p>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. </p>
|
|
<p>This example is in file <span class="code">example/daily.py</span>:</p>
|
|
<p class="tty">from weewx.wxengine import StdEngine, StdService<br />
|
|
from weeutil.weeutil import startOfArchiveDay<br />
|
|
<br />
|
|
class MyEngine(StdEngine):<br />
|
|
"""A customized weewx engine."""<br />
|
|
<br />
|
|
def __init__(self, *args, **vargs):<br />
|
|
# Pass on the initialization data to
|
|
my superclass:<br />
|
|
StdEngine.__init__(self, *args, **vargs)<br />
|
|
<br />
|
|
# This will record the timestamp of
|
|
the old day<br />
|
|
self.old_day = None<br />
|
|
<br />
|
|
def postArchiveData(self, rec):<br />
|
|
# First let my superclass process it:<br />
|
|
StdEngine.postArchiveData(self, rec)<br />
|
|
<br />
|
|
# Get the timestamp of the start of
|
|
the day using<br />
|
|
# the utility function
|
|
startOfArchiveDay <br />
|
|
dayStart_ts = startOfArchiveDay(rec['dateTime'])<br />
|
|
<br />
|
|
# Call the function firstArchiveOfDay
|
|
if either this is<br />
|
|
# the first archive since startup, or
|
|
if a new day has started<br />
|
|
if not self.old_day or self.old_day
|
|
!= dayStart_ts:<br />
|
|
self.old_day
|
|
= dayStart_ts<br />
|
|
|
|
self.newDay(rec)
|
|
# Note 1<br />
|
|
<br />
|
|
def newDay(self, rec):<br />
|
|
"""Called when the first archive
|
|
record of a day arrives."""<br />
|
|
<br />
|
|
# Go through the list of service
|
|
objects. This<br />
|
|
# list is actually in my superclass
|
|
StdEngine.<br />
|
|
for svc_obj in self.service_obj:<br />
|
|
# Because
|
|
this is a new event, not all services will<br />
|
|
# be prepared
|
|
to accept it. Check first to see if the<br />
|
|
# service has
|
|
a member function "firstArchiveOfDay"<br />
|
|
# before
|
|
calling it:<br />
|
|
if hasattr(svc_obj,
|
|
"firstArchiveOfDay"): # Note 2<br />
|
|
|
|
# The object does have the member function. Call it:<br />
|
|
|
|
svc_obj.firstArchiveOfDay(rec)</p>
|
|
<p>This customized engine works by monitoring the arrival of archive records,
|
|
and checking their time stamp (<span class="code">rec['dateTime']</span>. It
|
|
calculates the time stamp for the start of the day, and if it changes, calls
|
|
member function <span class="code">newDay()</span> (Note 1). </p>
|
|
<p>The member function <span class="code">newDay()</span> then goes through the
|
|
list of services (attribute <span class="code">self.service_obj</span>). Because
|
|
this engine is defining a new event (first archive of the day), the existing
|
|
services may not be prepared to accept it. So, the engine checks each one to
|
|
make sure it has a function <span class="code">firstArchiveOfDay</span> before
|
|
calling it (Note 2)</p>
|
|
<p>To use this engine, go into file <span class="code">weewxd.py</span> and change the line</p>
|
|
<p class="tty">weewx.wxengine.main()</p>
|
|
<p>so that it uses your new engine:</p>
|
|
<p class="tty">from examples.daily i<span class="style1">mport MyEngine</span><br />
|
|
<br />
|
|
# Specify that my specialized engine should be used instead<br />
|
|
# of the default:<br />
|
|
weewx.wxengine.main(EngineClass = MyEngine)</p>
|
|
<p>We now have a new engine that defines a new event ("<span class="code">firstArchiveOfDay</span>"),
|
|
but there is no service to take advantage of it. We define a new service:</p>
|
|
<p class="tty"># Define a new service to take advantage of the new event<br />
|
|
class DailyService(StdService):<br />
|
|
"""This service can do something when the first archive
|
|
record of<br />
|
|
a day arrives."""<br />
|
|
<br />
|
|
def firstArchiveOfDay(self, rec):<br />
|
|
"""Called when the first archive
|
|
record of a day arrives."""<br />
|
|
<br />
|
|
print "The first archive of the day
|
|
has arrived!"<br />
|
|
print rec<br />
|
|
<br />
|
|
# You might want to do something here
|
|
like run a cron job</p>
|
|
<p>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 <a href="http://www.weewx.com/wunderfixer">
|
|
wunderfixer</a>, this would be the place to do it.</p>
|
|
<p>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 <span class="code">
|
|
service_list</span>:</p>
|
|
<p class="tty">[Engines]<br />
|
|
<br />
|
|
[[WxEngine]]<br />
|
|
# The list of services the main weewx engine should run:<br />
|
|
service_list = weewx.wxengine.StdWunderground,
|
|
weewx.wxengine.StdCatchUp,<br />
|
|
|
|
weewx.wxengine.StdTimeSynch, weewx.wxengine.StdPrint,<br />
|
|
|
|
weewx.wxengine.StdProcess, examples.daily.DailyService</p>
|
|
<p>(Again, note that this list is shown on several lines for clarity, but in
|
|
actuality it must be all on one line.)</p>
|
|
|
|
</body>
|
|
|
|
</html>
|