How to test if lowBattery alarm notifications work?

129 views
Skip to first unread message

Michi Kaa

unread,
Dec 4, 2019, 1:02:26 AM12/4/19
to weewx-user
my Config looks like this:

time_wait = 3600
count_threshold
= 1
smtp_host
= mail.example.com
smtp_user
= notify@example.com
smtp_password
= myPassword
from = notify@example.com
mailto
= me@example.com
subject
= Weewx Battery low!

I have log entries like that:
Dec  3 06:55:28 weewxPi weewx[1365]: lowBattery: Low battery status sounded at 2019-12-03 06:55:15 CET (1575352515): {'inTempBatteryStatus': 1}
Dec  3 11:07:53 weewxPi weewx[1365]: lowBattery: Low battery status sounded at 2019-12-03 11:07:53 CET (1575367673): {'inTempBatteryStatus': 1}
Dec  3 13:26:02 weewxPi weewx[1365]: lowBattery: Low battery status sounded at 2019-12-03 13:25:54 CET (1575375954): {'inTempBatteryStatus': 1}
Dec  3 14:26:12 weewxPi weewx[1365]: lowBattery: Low battery status sounded at 2019-12-03 14:26:09 CET (1575379569): {'inTempBatteryStatus': 1}
Dec  3 16:26:14 weewxPi weewx[1365]: lowBattery: Low battery status sounded at 2019-12-03 16:26:11 CET (1575386771): {'inTempBatteryStatus': 1}
Dec  3 19:47:49 weewxPi weewx[1365]: lowBattery: Low battery status sounded at 2019-12-03 19:47:49 CET (1575398869): {'outTempBatteryStatus': 1}
Dec  3 20:48:03 weewxPi weewx[1365]: lowBattery: Low battery status sounded at 2019-12-03 20:47:55 CET (1575402475): {'outTempBatteryStatus': 1, 'inTempBatteryStatus': 1}
Dec  3 21:48:23 weewxPi weewx[1365]: lowBattery: Low battery status sounded at 2019-12-03 21:48:22 CET (1575406102): {'outTempBatteryStatus': 1}
Dec  3 22:48:40 weewxPi weewx[1365]: lowBattery: Low battery status sounded at 2019-12-03 22:48:19 CET (1575409699): {'outTempBatteryStatus': 1}
Dec  3 23:48:57 weewxPi weewx[1365]: lowBattery: Low battery status sounded at 2019-12-03 23:48:51 CET (1575413331): {'outTempBatteryStatus': 1}
Dec  4 00:51:51 weewxPi weewx[1365]: lowBattery: Low battery status sounded at 2019-12-04 00:51:32 CET (1575417092): {'outTempBatteryStatus': 1}
Dec  4 01:52:35 weewxPi weewx[1365]: lowBattery: Low battery status sounded at 2019-12-04 01:52:34 CET (1575420754): {'outTempBatteryStatus': 1}
Dec  4 02:54:08 weewxPi weewx[1365]: lowBattery: Low battery status sounded at 2019-12-04 02:54:03 CET (1575424443): {'outTempBatteryStatus': 1}
Dec  4 04:02:38 weewxPi weewx[1365]: lowBattery: Low battery status sounded at 2019-12-04 04:02:33 CET (1575428553): {'outTempBatteryStatus': 1, 'inTempBatteryStatus': 1}

But didn't receive a notification. No error entries.

How can I test if mailing in weewx context works? I've changed some syslog.LOG_DEBUG lines in lowBattery.py to syslog.LOG_INFO so i can omit all the other debug stuff in the log, but with fresh batteries next chance to have a real alarm is probably in a year or so.

Andrew Milner

unread,
Dec 4, 2019, 2:42:38 AM12/4/19
to weewx-user
a silly question - but have you actually got example, mypassword in your config - or have you replaced these with a real host, a real user and a real password??

Michi Kaa

unread,
Dec 4, 2019, 3:01:41 AM12/4/19
to weewx-user
It's never too silly to ask that kind of stuff. Yes, i did :). Bad credentials should lead to an error-entry in the log, as far as I remember the code.

What I could probably do is modifying lowBattery.py in a way that it will send warnings, when the battery is not empty and do more logging.

Andrew Milner

unread,
Dec 4, 2019, 3:11:17 AM12/4/19
to weewx-user
a suggestion - reverse the test so you get the mail if battery is ok

Michi Kaa

unread,
Dec 4, 2019, 1:47:10 PM12/4/19
to weewx-user
I modified lowBattery.py in line 118 to have txBatteryStatus = 1 and altered all the syslog lines to log on info level:

#    Copyright (c) 2009-2015 Tom Keffer <tke...@gmail.com>
#    See the file LICENSE.txt for your rights.

"""Example of how to implement a low battery alarm in weewx.

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

To use this alarm, add the following somewhere in your configuration file
weewx.conf:



An email will be sent to each address in the comma separated list of recipients

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).

It will also not send an email unless the low battery indicator has been on
greater than or equal to count_threshold times in an archive period. This
avoids sending out an alarm if the battery is only occasionally being signaled
as bad.

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

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.lowBattery.BatteryAlarm

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

If you wish to use both this example and the alarm.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 BatteryAlarm(StdService):
   
"""Service that sends email if one of the batteries is low"""
   
   
def __init__(self, engine, config_dict):
       
# Pass the initialization information on to my superclass:
       
super(BatteryAlarm, self).__init__(engine, config_dict)
       
       
# This will hold the time when the last alarm message went out:
       
self.last_msg_ts = 0
       
# This will hold the count of the number of times the VP2 has signaled
       
# a low battery alarm this archive period
       
self.alarm_count = 0

       
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.time_wait       = int(config_dict['Alarm'].get('time_wait', 3600))
           
self.count_threshold = int(config_dict['Alarm'].get('count_threshold', 10))
           
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', "Low battery 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, "lowBattery: LowBattery alarm enabled. Count threshold is %d" % self.count_threshold)

           
# If we got this far, it's ok to start intercepting events:
           
self.bind(weewx.NEW_LOOP_PACKET,    self.newLoopPacket)
           
self.bind(weewx.NEW_ARCHIVE_RECORD, self.newArchiveRecord)
       
except KeyError as e:
            syslog
.syslog(syslog.LOG_INFO, "lowBattery: No alarm set.  Missing parameter: %s" % e)

   
def newLoopPacket(self, event):
       
"""This function is called on each new LOOP packet."""

       
# If any battery status flag is non-zero, a battery is low
        low_batteries
= dict()
       
for flag in ['txBatteryStatus', 'windBatteryStatus',
                     
'rainBatteryStatus', 'inTempBatteryStatus',
                     
'outTempBatteryStatus']:
           
if flag in event.packet and event.packet[flag]:
                low_batteries
[flag] = event.packet[flag]

        low_batteries
['txBatteryStatus'] = 1
       
# If there are any low batteries, see if we need to send an alarm
       
if low_batteries:
           
self.alarm_count += 1

           
# Don't panic on the first occurrence. We must see the alarm at
           
# least count_threshold times before sounding the alarm.
           
if self.alarm_count >= self.count_threshold:
               
# We've hit the threshold. However, to avoid a flood of nearly
               
# identical emails, send a new one only if it's been a long
               
# time since we sent the last one:
               
if abs(time.time() - self.last_msg_ts) >= self.time_wait :
                   
# Sound the alarm!
                    timestamp
= event.packet['dateTime']
                   
# Launch in a separate thread so it does not block the
                   
# main LOOP thread:
                    t  
= threading.Thread(target=BatteryAlarm.soundTheAlarm,
                                          args
=(self, timestamp,
                                                low_batteries
,
                                               
self.alarm_count))
                    t
.start()
                   
# Record when the message went out:
                   
self.last_msg_ts = time.time()
       
   
def newArchiveRecord(self, event):  # @UnusedVariable
       
"""This function is called on each new archive record."""
       
       
# Reset the alarm counter
       
self.alarm_count = 0

   
def soundTheAlarm(self, timestamp, battery_flags, alarm_count):
       
"""This function is called when the alarm has been triggered."""
       
       
# Get the time and convert to a string:
        t_str
= timestamp_to_string(timestamp)

       
# Log it in the system log:
        syslog
.syslog(syslog.LOG_INFO, "lowBattery: Low battery status sounded at %s: %s" % (t_str, battery_flags))

       
# Form the message text:
        indicator_strings
= []
       
for bat in battery_flags:
            indicator_strings
.append("%s: %04x" % (bat, battery_flags[bat]))
        msg_text
= """
The low battery indicator has been seen %d times since the last archive period.

Alarm sounded at %s

Low battery indicators:
%s

"""
% (alarm_count, t_str, '\n'.join(indicator_strings))
       
# Convert to MIME:
        msg
= MIMEText(msg_text)
       
       
# Fill in MIME headers:
        msg
['Subject'] = self.SUBJECT
        msg
['From']    = self.FROM
        msg
['To']      = ','.join(self.TO)
        syslog
.syslog(syslog.LOG_INFO, "lowBattery: Message: %s" % msg)
       
try:
           
# First try end-to-end encryption
            syslog
.syslog(syslog.LOG_INFO, "lowBattery: #1")
            s
=smtplib.SMTP_SSL(self.smtp_host)
            syslog
.syslog(syslog.LOG_INFO, "lowBattery: #2")
            syslog
.syslog(syslog.LOG_INFO, "lowBattery: using SMTP_SSL")
       
except AttributeError:
           
# If that doesn't work, try creating an insecure host, then upgrading
            syslog
.syslog(syslog.LOG_INFO, "lowBattery: #3")
            s
= smtplib.SMTP(self.smtp_host)
            syslog
.syslog(syslog.LOG_INFO, "lowBattery: #4")
           
try:
               
# Be prepared to catch an exception if the server
               
# does not support encrypted transport.
                syslog
.syslog(syslog.LOG_INFO, "lowBattery: #5")
                s
.ehlo()
                s
.starttls()
                s
.ehlo()
                syslog
.syslog(syslog.LOG_INFO,
                             
"lowBattery: using SMTP encrypted transport")
           
except smtplib.SMTPException:
                syslog
.syslog(syslog.LOG_INFO,
                             
"lowBattery: using SMTP unencrypted transport")

       
try:
           
# If a username has been given, assume that login is required
           
# for this host:
            syslog
.syslog(syslog.LOG_INFO, "lowBattery: #6")
           
if self.smtp_user:
                syslog
.syslog(syslog.LOG_INFO, "lowBattery: #7")
                s
.login(self.smtp_user, self.smtp_password)
                syslog
.syslog(syslog.LOG_INFO, "lowBattery: #8")
                syslog
.syslog(syslog.LOG_INFO,
                             
"lowBattery: logged in as %s" % self.smtp_user)

           
# Send the email:
            syslog
.syslog(syslog.LOG_INFO, "lowBattery: #9")
            s
.sendmail(msg['From'], self.TO,  msg.as_string())
            syslog
.syslog(syslog.LOG_INFO, "lowBattery: #10")
           
# Log out of the server:
            s
.quit()
       
except Exception as e:
            syslog
.syslog(syslog.LOG_ERR,
                         
"lowBattery: send email failed: %s" % (e,))
           
raise
       
       
# Log sending the email:
        syslog
.syslog(syslog.LOG_INFO,
                     
"lowBattery: email sent to: %s" % self.TO)


Log output:
Dec  4 19:28:15 weewxPi weewx[12512]: lowBattery: LowBattery alarm enabled. Count threshold is 1
Dec  4 19:29:15 weewxPi weewx[12512]: lowBattery: Low battery status sounded at 2019-12-04 19:29:02 CET (1575484142): {'txBatteryStatus': 1}
Dec  4 19:29:15 weewxPi weewx[12512]: lowBattery: Message: From nobody Wed Dec  4 19:29:15 2019#012Content-Type:
 text
/plain; charset="us-ascii"#012MIME-Version:
1.0#012Content-Transfer-Encoding: 7bit#012Subject: Weewx Battery
low
!#012From: not...@xxx.xx#012To: x...@xx.xx#012#012#012The low battery
indicator has been seen
1 times since the last archive
period
.#012#012Alarm sounded at 2019-12-04 19:29:02 CET
(1575484142)#012#012Low battery indicators:#012txBatteryStatus: 0001
Dec  4 19:29:15 weewxPi weewx[12512]: lowBattery: #1


Thats all. So after
s=smtplib.SMTP_SSL(self.smtp_host)

Nothing happens.

Michi Kaa

unread,
Dec 4, 2019, 3:45:18 PM12/4/19
to weewx-user
I tried another provider and got:


Dec  4 21:19:06 weewxPi weewx[14230]: lowBattery: #1
Dec  4 21:19:06 weewxPi weewx[14230]: lowBattery: #2
Dec  4 21:19:06 weewxPi weewx[14230]: lowBattery: using SMTP_SSL
Dec  4 21:19:06 weewxPi weewx[14230]: lowBattery: #6
Dec  4 21:19:06 weewxPi weewx[14230]: lowBattery: #7
Dec  4 21:19:06 weewxPi weewx[14230]: lowBattery: send email failed: (535, 'Authentication credentials invalid')

So the problem is with the smtp_host. The credentials are valid, I guess I need to configure to allow external programs to send mails with that provider

But back to the other provider:

s
=smtplib.SMTP_SSL(self.smtp_host, self.smtp_port, "localhost", 5)

The snippet above is what I tried next, setting port and timeout didn't change anything. I'd expect to throw an error after 5 seconds, baut nothing happens again. I tried with all the possible settings from my provider. I send mail using java and this account without any problem.


Michi Kaa

unread,
Dec 5, 2019, 4:00:31 PM12/5/19
to weewx-user
Solved:

I commented out:

smtplib_SSL

#        try:
#            # First try end-to-end encryption
#            syslog.syslog(syslog.LOG_DEBUG, "lowBattery: try using SMTP_SSL: %s, %s, %s" % (self.smtp_host, seld.smtp_port, self.smtp_timeout))
#            s=smtplib.SMTP_SSL(self.smtp_host, self.smtp_port, None, self.smtp_timeout)
#            syslog.syslog(syslog.LOG_DEBUG, "lowBattery: using SMTP_SSL")
#        except AttributeError:

           
# If that doesn't work, try creating an insecure host, then upgrading

        syslog
.syslog(syslog.LOG_DEBUG, "lowBattery: try creating an insecure host, then upgrading")
        s
= smtplib.SMTP(self.smtp_host, self.smtp_port, None, self.smtp_timeout)

       
try:
           
# Be prepared to catch an exception if the server
           
# does not support encrypted transport.

            syslog
.syslog(syslog.LOG_DEBUG, "lowBattery: try encrypted transport")
            s
.ehlo()
            s
.starttls()
            s
.ehlo()
            syslog
.syslog(syslog.LOG_DEBUG,

                         
"lowBattery: using SMTP encrypted transport")
       
except smtplib.SMTPException:

            syslog
.syslog(syslog.LOG_DEBUG, "lowBattery: unencrypted transport")
            syslog
.syslog(syslog.LOG_DEBUG,

                         
"lowBattery: using SMTP unencrypted transport")

Now it works:

Dec  5 21:53:38 weewxPi weewx[4636]: lowBattery: try creating an insecure host, then upgrading
Dec  5 21:53:38 weewxPi weewx[4636]: lowBattery: try encrypted transport
Dec  5 21:53:38 weewxPi weewx[4636]: lowBattery: using SMTP encrypted transport
Dec  5 21:53:38 weewxPi weewx[4636]: lowBattery: logged in as notify@example.com
Dec  5 21:53:39 weewxPi weewx[4636]: lowBattery: email sent to: ['m...@example.com']

and I received the mail.

Michi Kaa

unread,
Dec 11, 2019, 3:27:37 AM12/11/19
to weewx-user
Another possiblity: The "Simulator" driver also simulates empty batteries :)
Reply all
Reply to author
Forward
0 new messages