It's possible some other exception occurred after the alarm was triggered,
but before the email would have been sent out. Because the alarm is sent in
a separate thread, the exception would have been quietly swallowed without
a log message.

Try this version of alarm.py. It will catch any exception and log it.

-tk



On Sun, Feb 11, 2018 at 9:04 AM, Jesper <jesper.johan...@sentech.dk> wrote:

> Instaling alarm.py gives me a few challenges.
>
> I have added the following to weewx.conf
>
> weewx.restx.StdCWOP, weewx.restx.StdWOW, weewx.restx.StdAWEKAS
>         report_services = weewx.engine.StdPrint, weewx.engine.StdReport,
> user.alarm.MyAlarm
>
>
> # This section configure the alarm service
>
> [Alarm]
>     expression = "outTemp < 60.0"
>     time_wait = 600
>     smtp_host = smtp.google.com
>     SMTP_user = xxxxxxx...@gmail.com
>     SMTP_password = xxxxxxxxx
>     from = xxxxxxxxxxxxx
>
>     mailto = xxxxxxxxxxxxx
>     subject = "alarm message from weewx!"
>
>
> and alarm.py is copied to the user directory
>
> Syslog tell that the service is loaded
>
> Feb 10 20:43:19 raspberrypi weewx[979]: engine: Initializing weewx version
> 3.8.0
>
> Feb 10 20:43:19 raspberrypi weewx[979]: engine: Using Python 2.7.13
> (default, Jan 19 2017, 14:48:08) #012[GCC 6.3.0 20170124]
>
> Feb 10 20:43:19 raspberrypi weewx[979]: engine: Platform
> Linux-4.9.59-v7+-armv7l-with-debian-9.1
>
> Feb 10 20:43:19 raspberrypi weewx[979]: engine: Locale is 'en_US.UTF-8'
>
> Feb 10 20:43:19 raspberrypi weewx[979]: engine: Using configuration file
> /home/weewx/weewx.conf
>
> Feb 10 20:43:19 raspberrypi weewx[979]: engine: debug is 1
>
> Feb 10 20:43:19 raspberrypi weewx[979]: engine: Initializing engine
>
> Feb 10 20:43:19 raspberrypi weewx[979]: engine: Loading station type
> Vantage (weewx.drivers.vantage)
>
> Feb 10 20:43:19 raspberrypi weewx[979]: vantage: Driver version is 3.0.10
>
> Feb 10 20:43:19 raspberrypi weewx[979]: vantage: Opened up serial port
> /dev/ttyUSB0; baud 19200; timeout 4.00
>
> Feb 10 20:43:23 raspberrypi weewx[979]: vantage: Retry  #0 failed
>
> Feb 10 20:43:23 raspberrypi weewx[979]: vantage: Gentle wake up of console
> successful
>
> Feb 10 20:43:23 raspberrypi weewx[979]: vantage: Hardware type is 16
>
> Feb 10 20:43:23 raspberrypi weewx[979]: vantage: Hardware name: Vantage
> Pro2
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Loading service
> weewx.engine.StdTimeSynch
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Finished loading service
> weewx.engine.StdTimeSynch
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Loading service
> weewx.engine.StdConvert
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: StdConvert target unit is
> 0x1
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Finished loading service
> weewx.engine.StdConvert
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Loading service
> weewx.engine.StdCalibrate
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Finished loading service
> weewx.engine.StdCalibrate
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Loading service
> weewx.engine.StdQC
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Finished loading service
> weewx.engine.StdQC
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Loading service
> weewx.wxservices.StdWXCalculate
>
> Feb 10 20:43:23 raspberrypi weewx[979]: wxcalculate: The following values
> will be calculated: barometer=prefer_hardware, windchill=prefer_hardware,
> dewpoint=prefer_hardware, appTemp=prefer_hardware,
> rainRate=prefer_hardware, windrun=prefer_hardware,
> heatindex=prefer_hardware, maxSolarRad=prefer_hardware,
> humidex=prefer_hardware, pressure=prefer_hardware,
> inDewpoint=prefer_hardware, ET=prefer_hardware, altimeter=prefer_hardware,
> cloudbase=prefer_hardware
>
> Feb 10 20:43:23 raspberrypi weewx[979]: wxcalculate: The following
> algorithms will be used for calculations: altimeter=aaNOAA, maxSolarRad=RS
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Finished loading service
> weewx.wxservices.StdWXCalculate
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Loading service
> weewx.engine.StdArchive
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Archive will use data
> binding wx_binding
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Record generation will be
> attempted in 'hardware'
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: The archive interval in
> the configuration file (300) does not match the station hardware interval
> (600).
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Using archive interval of
> 600 seconds (specified by hardware)
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Use LOOP data in hi/low
> calculations: 1
>
> Feb 10 20:43:23 raspberrypi weewx[979]: manager: Daily summary version is
> 2.0
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Using binding 'wx_binding'
> to database 'weewx.sdb'
>
> Feb 10 20:43:23 raspberrypi weewx[979]: manager: Starting backfill of
> daily summaries
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Finished loading service
> weewx.engine.StdArchive
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Loading service
> weewx.restx.StdStationRegistry
>
> Feb 10 20:43:23 raspberrypi weewx[979]: restx: StationRegistry:
> Registration not requested.
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Finished loading service
> weewx.restx.StdStationRegistry
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Loading service
> weewx.restx.StdWunderground
>
> Feb 10 20:43:23 raspberrypi weewx[979]: restx: Wunderground: Posting not
> enabled.
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Finished loading service
> weewx.restx.StdWunderground
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Loading service
> weewx.restx.StdPWSweather
>
> Feb 10 20:43:23 raspberrypi weewx[979]: restx: PWSweather: Posting not
> enabled.
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Finished loading service
> weewx.restx.StdPWSweather
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Loading service
> weewx.restx.StdCWOP
>
> Feb 10 20:43:23 raspberrypi weewx[979]: restx: CWOP: Posting not enabled.
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Finished loading service
> weewx.restx.StdCWOP
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Loading service
> weewx.restx.StdWOW
>
> Feb 10 20:43:23 raspberrypi weewx[979]: restx: WOW: Posting not enabled.
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Finished loading service
> weewx.restx.StdWOW
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Loading service
> weewx.restx.StdAWEKAS
>
> Feb 10 20:43:23 raspberrypi weewx[979]: restx: AWEKAS: Posting not enabled.
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Finished loading service
> weewx.restx.StdAWEKAS
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Loading service
> weewx.engine.StdPrint
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Finished loading service
> weewx.engine.StdPrint
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Loading service
> weewx.engine.StdReport
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Finished loading service
> weewx.engine.StdReport
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Loading service
> user.alarm.MyAlarm
>
> Feb 10 20:43:23 raspberrypi weewx[979]: alarm: Alarm set for expression:
> 'outTemp < 60.0'
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Finished loading service
> user.alarm.MyAlarm
>
> Feb 10 20:43:23 raspberrypi weewx[979]: engine: Starting up weewx version
> 3.8.0
>
> Running result in the following
>
> Feb 10 21:10:21 raspberrypi weewx[979]: manager: Added record 2018-02-10
> 21:10:00 CET (1518293400) to daily summary in 'weewx.sdb'
>
> Feb 10 21:10:23 raspberrypi weewx[979]: alarm: Alarm expression "outTemp <
> 60.0" evaluated True at 2018-02-10 21:10:00 CET (1518293400)
>
> Feb 10 21:10:23 raspberrypi weewx[979]: vantage: DMPAFT complete: page
> timestamp 2018-01-24 02:30:00 CET (1516757400) less than final timestamp
> 2018-02-10 21:10:00 CET (1518293400)
>
>
>
> Feb 10 21:20:21 raspberrypi weewx[979]: manager: Added record 2018-02-10
> 21:20:00 CET (1518294000) to database 'weewx.sdb'
>
> Feb 10 21:20:21 raspberrypi weewx[979]: manager: Added record 2018-02-10
> 21:20:00 CET (1518294000) to daily summary in 'weewx.sdb'
>
> Feb 10 21:20:23 raspberrypi weewx[979]: alarm: Alarm expression "outTemp <
> 60.0" evaluated True at 2018-02-10 21:20:00 CET (1518294000)
>
> Feb 10 21:20:23 raspberrypi weewx[979]: reportengine: Running reports for
> latest time in the database.
>
> Nothing else happens. I had expected to get syslog output "alarm using
> ...". assuming LOG_DEBUG is just a priority in the syslog package.
>
> What is wrong?
>
> Regards Jesper
>
>
>
#    Copyright (c) 2009-2015 Tom Keffer <tkef...@gmail.com>
#    See the file LICENSE.txt for your rights.

"""Example of how to implement an alarm in weewx. 

*******************************************************************************

To use this alarm, add the following to the weewx configuration file:

[Alarm]
    expression = "outTemp < 40.0"
    time_wait = 3600
    smtp_host = smtp.example.com
    smtp_user = myusername
    smtp_password = mypassword
    from = sa...@example.com
    mailto = j...@example.com, b...@example.com
    subject = "Alarm message from weewx!"
  
In this example, if the outside temperature falls below 40, it will send an
email to the users specified in the comma separated list specified in option
"mailto", in this case:

j...@example.com, b...@example.com

The example assumes an SMTP email server at smtp.example.com that requires
login.  If the SMTP server does not require login, leave out the lines for
smtp_user and smtp_password.

Setting an email "from" is optional. If not supplied, one will be filled in,
but your SMTP server may or may not accept it.

Setting an email "subject" is optional. If not supplied, one will be filled in.

To avoid a flood of emails, one will only be sent every 3600 seconds (one
hour).

*******************************************************************************

To enable this service:

1) copy this file to the user directory

2) modify the weewx configuration file by adding this service to the option
"report_services", located in section [Engine][[Services]].

[Engine]
  [[Services]]
    ...
    report_services = weewx.engine.StdPrint, weewx.engine.StdReport, user.alarm.MyAlarm

*******************************************************************************

If you wish to use both this example and the lowBattery.py example, simply
merge the two configuration options together under [Alarm] and add both
services to report_services.

*******************************************************************************
"""

import time
import smtplib
from email.mime.text import MIMEText
import threading
import syslog

import weewx
from weewx.engine import StdService
from weeutil.weeutil import timestamp_to_string, option_as_list

# Inherit from the base class StdService:
class MyAlarm(StdService):
    """Service that sends email if an arbitrary expression evaluates true"""
    
    def __init__(self, engine, config_dict):
        # Pass the initialization information on to my superclass:
        super(MyAlarm, self).__init__(engine, config_dict)
        
        # This will hold the time when the last alarm message went out:
        self.last_msg_ts = 0
        
        try:
            # Dig the needed options out of the configuration dictionary.
            # If a critical option is missing, an exception will be raised and
            # the alarm will not be set.
            self.expression    = config_dict['Alarm']['expression']
            self.time_wait     = int(config_dict['Alarm'].get('time_wait', 3600))
            self.smtp_host     = config_dict['Alarm']['smtp_host']
            self.smtp_user     = config_dict['Alarm'].get('smtp_user')
            self.smtp_password = config_dict['Alarm'].get('smtp_password')
            self.SUBJECT       = config_dict['Alarm'].get('subject', "Alarm message from weewx")
            self.FROM          = config_dict['Alarm'].get('from', 'al...@example.com')
            self.TO            = option_as_list(config_dict['Alarm']['mailto'])
            syslog.syslog(syslog.LOG_INFO, "alarm: Alarm set for expression: '%s'" % self.expression)
            
            # If we got this far, it's ok to start intercepting events:
            self.bind(weewx.NEW_ARCHIVE_RECORD, self.newArchiveRecord)    # NOTE 1
        except KeyError, e:
            syslog.syslog(syslog.LOG_INFO, "alarm: No alarm set.  Missing parameter: %s" % e)
            
    def newArchiveRecord(self, event):
        """Gets called on a new archive record event."""
        
        # To avoid a flood of nearly identical emails, this will do
        # the check only if we have never sent an email, or if we haven't
        # sent one in the last self.time_wait seconds:
        if not self.last_msg_ts or abs(time.time() - self.last_msg_ts) >= self.time_wait :
            # Get the new archive record:
            record = event.record
            
            # Be prepared to catch an exception in the case that the expression contains 
            # a variable that is not in the record:
            try:                                                              # NOTE 2
                # Evaluate the expression in the context of the event archive record.
                # Sound the alarm if it evaluates true:
                if eval(self.expression, None, record):                       # NOTE 3
                    # Sound the alarm!
                    # Launch in a separate thread so it doesn't block the main LOOP thread:
                    t  = threading.Thread(target = MyAlarm.soundTheAlarm, args=(self, record))
                    t.start()
                    # Record when the message went out:
                    self.last_msg_ts = time.time()
            except NameError, e:
                # The record was missing a named variable. Write a debug message, then keep going
                syslog.syslog(syslog.LOG_DEBUG, "alarm: %s" % e)

    def soundTheAlarm(self, rec):
        """This function is called when the given expression evaluates True."""
        
        try:
            # Get the time and convert to a string:
            t_str = timestamp_to_string(rec['dateTime'])
    
            # Log it
            syslog.syslog(syslog.LOG_INFO, "alarm: Alarm expression \"%s\" evaluated True at %s" % (self.expression, t_str))
    
            # Form the message text:
            msg_text = "Alarm expression \"%s\" evaluated True at %s\nRecord:\n%s" % (self.expression, t_str, str(rec))
            # Convert to MIME:
            msg = MIMEText(msg_text)
            
            # Fill in MIME headers:
            msg['Subject'] = self.SUBJECT
            msg['From']    = self.FROM
            msg['To']      = ','.join(self.TO)
            
            # Create an instance of class SMTP for the given SMTP host:
            s = smtplib.SMTP(self.smtp_host)
            try:
                # Some servers (eg, gmail) require encrypted transport.
                # Be prepared to catch an exception if the server
                # doesn't support it.
                s.ehlo()
                s.starttls()
                s.ehlo()
                syslog.syslog(syslog.LOG_DEBUG, "alarm: using encrypted transport")
            except smtplib.SMTPException:
                syslog.syslog(syslog.LOG_DEBUG, "alarm: using unencrypted transport")
    
            try:
                # If a username has been given, assume that login is required for this host:
                if self.smtp_user:
                    s.login(self.smtp_user, self.smtp_password)
                    syslog.syslog(syslog.LOG_DEBUG, "alarm: logged in with user name %s" % (self.smtp_user,))
                
                # Send the email:
                s.sendmail(msg['From'], self.TO,  msg.as_string())
                # Log out of the server:
                s.quit()
            except Exception, e:
                syslog.syslog(syslog.LOG_ERR, "alarm: SMTP mailer refused message with error %s" % (e,))
                raise
            
            # Log sending the email:
            syslog.syslog(syslog.LOG_INFO, "alarm: email sent to: %s" % self.TO)
        except Exception, e:
            syslog.syslog(syslog.LOG_ERR, "alarm: Got exception while sounding the alarm: %s" (e,))
            raise

if __name__ == '__main__':
    """This section is used for testing the code. """
    import sys
    import configobj
    from optparse import OptionParser

    usage_string ="""Usage: 
    
    alarm.py config_path 
    
    Arguments:
    
      config_path: Path to weewx.conf"""
    parser = OptionParser(usage=usage_string)
    (options, args) = parser.parse_args()
    
    if len(args) < 1:
        sys.stderr.write("Missing argument(s).\n")
        sys.stderr.write(parser.parse_args(["--help"]))
        exit()
        
    config_path = args[0]
    
    weewx.debug = 1
    
    try :
        config_dict = configobj.ConfigObj(config_path, file_error=True)
    except IOError:
        print "Unable to open configuration file ", config_path
        exit()
        
    if 'Alarm' not in config_dict:
        print >>sys.stderr, "No [Alarm] section in the configuration file %s" % config_path
        exit(1)
    
    engine = None
    alarm = MyAlarm(engine, config_dict)
    
    rec = {'extraTemp1': 1.0,
           'outTemp'   : 38.2,
           'dateTime'  : int(time.time())}

    event = weewx.Event(weewx.NEW_ARCHIVE_RECORD, record=rec)
    alarm.newArchiveRecord(event)

Reply via email to