last updated: 2022-01-18
Ping The Thing
is a simple Python script that checks if all your devices are connected to the net and is sending a mail if not.
I have an IP camera that looses sometimes the connection with my internal net. My NAS
does not restart itself after a power loss. My MQTT
server and other servers (e.g. openHAB) as well as some sensors are important and must run to not loose data and to guarantee that all controls in the house are functioning.
If there is a problem with one of these devices I would like to know as soon as possible. I'm more the e-mail type and not the GSM type :). So I need a warning mail if something is amiss.
Fortunately a simple Python script running on a Raspi (e.g. the pi-hole raspi) or other server can help.
I want to be able to send mails from different python scripts to myself, so we will install a message transfer agent (MTA) on the Raspi. Normally I would use sSMTP, but unfortunately this package has been orphaned since 2019-03-19. As stated in the debian wiki msmtp
can be used as an alternative.
Msmtp can send mails from MUAs (mail user agents) like Mutt or Emacs. To use a standard MTA interface like I was used with sSMTP we install the msmtp-mta
package. It provides a /usr/sbin/sendmail symlink to msmtp. We also need mailutils
for the mail program. For attachments (if needed) we can install the mpack package,
Install everything with:
sudo apt install msmtp msmtp-mta mailutils mpack
Next we create a config file with the name .msmtprc
in home (/home/pi or ~
). It is a hidden file and we need to make it readable.
nano ..msmtprc
Now copy the following to the file and change your account settings. You can add as many accounts as you wish.
# Set default values for all following accounts.
defaults
auth on
tls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
logfile ~/.msmtp.log
# Gmail
account gmail
host smtp.gmail.com
port 587
from username@gmail.com
user username
password plain-text-password
# Set a default account
account default : gmail
Save with Ctrl+o
and make the file readable:
chmod 600 ~/.msmtprc
Now we can test with the following line:
echo "Hello world email body" | mail -s "Test Subject" username@gmail.com
Sure it is better to not use a cleartext password in a file. You can use a passwordmanager or gpg as described on this page: https://wiki.archlinux.org/title/msmtp.
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
""" ping_the_thing.py from weigu.lu """
__version__ = "0.1.0"
__author__ = "Guy WEILER weigu.lu"
__copyright__ = "Copyright 2022, weigu.lu"
__license__ = "GPL"
__maintainer__ = "Guy WEILER"
__email__ = "weigu@weigu.lu"
__status__ = "Production" # "Prototype", "Development", or "Production"
from time import sleep, time
import datetime as dt
import subprocess as sp
import sys
# Mail address to send the mail to
RECEIVER = 'mymail@address' # changer this !! :)
# add here your Ip addresses and a meaningfull name of the thing
WAIT_BETWEEN_CHECKS = 5 # in minutes
# add here your Ip addresses and a meaningfull name of the thing
IP_DICT = {'192.168.1.50':'myNAS',
'192.168.1.99':'myImportantServer',
'192.168.1.100':'myIoTSensor',
'192.168.1.101':'myMy',
}
##############################################################################
def ipcheck(ip_dict):
""" Ping the IP addresses from the dictionary and return a list
if they are up or down"""
ip_up_list = []
for ip_addr in ip_dict.keys():
status, _ = sp.getstatusoutput("ping -c1 -w2 " + ip_addr)
if status == 0:
ip_up_list.append('up')
else:
ip_up_list.append('down')
return ip_up_list
def send_mail(subject, message):
""" Send the message string to the RECEIVER mail address. """
try:
retcode = sp.call('echo ' + message + '| mail -s ' + subject +
' ' + RECEIVER, shell=True)
if retcode < 0:
print("Child was terminated by signal", -retcode, file=sys.stderr)
else:
print("Mail was sent! Child returned", retcode, file=sys.stderr)
except OSError as error:
print("Error sending mail: Execution failed:", error, file=sys.stderr)
def send_warning_mail(res_dict, mail_flag_list):
""" Cook the mail. """
key_list = list(res_dict.keys())
val_list = list(res_dict.values())
for i, _ in enumerate(val_list):
if val_list[i] == 'down':
if mail_flag_list[i] != 'true':
subject = 'WARNING_Mail'
message = key_list[i] + ' is down! '
print(message, end = '')
send_mail(subject, message)
mail_flag_list[i] = 'true'
return mail_flag_list
def create_mail_flag_list():
""" This list is needed to make shure a warning mail
is only sent once a day"""
mail_flag_list = []
key_list = list(IP_DICT.keys())
for _ in key_list:
mail_flag_list.append('false')
return mail_flag_list
##############################################################################
def main():
""" main """
print("\"ping_the_thing.py\" started at ",end='')
print(dt.datetime.now().isoformat('T', 'seconds'))
mail_flag_list = create_mail_flag_list()
while True:
now_time = dt.datetime.now().time()
if dt.time(0,9,50) <= now_time <= dt.time(0,9,55):
mail_flag_list = create_mail_flag_list()
sleep(5)
now_sec = time() # in seconds
if int(now_sec%(60*WAIT_BETWEEN_CHECKS)) == 0:
ip_up_list = ipcheck(IP_DICT)
res_dict = dict(zip(IP_DICT.values(), ip_up_list))
mail_flag_list = send_warning_mail(res_dict, mail_flag_list)
print('!',end = '')
sleep(1)
##############################################################################
if __name__ == '__main__':
main()
We add the following line to the user crontab file. First we start the editor with:
crontab -e
Choose nano
as simplest editor and add the following line to the file:
@reboot sleep 60 && python3 /home/pi/ping_the_thing.py
Save with Ctrl+o
and reboot your server.
We wait for 1 minute so that the network is up, before running the script.
We do not use the system crontab (/etc/crontab
) running as root, but a user crontab (with crontab -e) that runs under the user pi
. As root we get an "Process exited with a non-zero status" error because the mail program is setup for the user pi.
The user crontabs are in /var/spool/cron
.