Python – Event Monitoring

Today, I am showing a very basic Event Monitoring script using Python.
September 25, 2020

Today, I am showing a very basic Event Monitoring script using Python. It will send a text message to my cell-phone when the office’s production printer goes off-line ( runs out of paper ), comes back on-line, and an hourly reminder when it’s off-line. The script can be expanded to monitor other events as well.

Python – Event Monitoring

Today, I am showing a very basic Event Monitoring script using Python.
September 25, 2020

Today, I am showing a very basic Event Monitoring script using Python. It will send a text message to my cell-phone when the office’s production printer goes off-line ( runs out of paper ), comes back on-line, and an hourly reminder when it’s off-line. The script can be expanded to monitor other events as well.


Python – Event Monitoring

Today, I am showing a very basic Event Monitoring script using Python.
September 25, 2020

Today, I am showing a very basic Event Monitoring script using Python. It will send a text message to my cell-phone when the office’s production printer goes off-line ( runs out of paper ), comes back on-line, and an hourly reminder when it’s off-line. The script can be expanded to monitor other events as well.


Today, I am showing a very basic Event Monitoring script using Python. It will send a text message to my cell-phone when the office’s production printer goes off-line ( runs out of paper ), comes back on-line, and an hourly reminder when it’s off-line. The script can be expanded to monitor other events as well.

I will explane how each part of the script works, and show several very useful programming tools that you could use in other projects.

I will also be covering the basic concepts of Event Monitoring.


Defining the objectives

Let’s start by clearly defining what we are needing.
1. We only want text messages to be sent during regular office hours; Monday through Friday from 8:00am to 4:00pm.
2. We want to check the printer every 20 minutes.
3. We want one, and only one, text message when the printer goes off-line.
4. We want a second message when the printer comes back on-line.
5. We want a reminder message once an hour as long as the printer is off-line.


Biconditional Logical Connective

Lets now take a few minutes to consider a child’s toy water gun.
You know, a toy that squirts water when you squeeze the little plastic trigger.
Bear with me, I am going someplace important with this little side trip.

When you squeeze the trigger, water squirts out.

If we are not squeezing the trigger, and then we squeeze it ( False => True ), the response is True; we get a squirt.

If we were squeezing the trigger, and we continue to squeeze the trigger ( True => True ), the response is False, we get no water.

If we were squeezing the trigger, and we let go of the trigger ( True => False ), the response is False, we get no water.

It is ONLY when we are not squeezing, and then we squeeze ( False => True ), do we get a response of True; SQUIRT!

This is an example of something called a Biconditional Logical Connective . It will return True if, and only if, the current state is True, but the previous state was False.

Looking for more info about “Biconditional Logical Connective”? See the Wikipedia entry “If_and_only_if”. ( https://en.wikipedia.org/wiki/If_and_only_if )


OK, so how does this apply to today’s project?

Let us say
isPrinterUp = isDeviceResponding( MyPrinterIP, 515 )
isPrinterDown = not isPrinterUp

Then, we want to send a text message ( return value of True ),
if the last time it was checked: isPrinterDown == False,
but is now: isPrinterDown == True .

So, when isPrinterDown goes from False to True ( False => True ) , we get a return value of True.

Ahh, this is a Biconditional Logical Connective.

Don’t worry if you don’t understand. It will become clearer in the next topic.


The trigger() and timer() functions.

This is a subject that can be very confusing. But it’s also a very powerful programming concept. I recommend that you spend some time on this section. It can be a very useful tool in your python toolbox.

The trigger function ( shown in the example code at the bottom of this post ) will return True if, and only if, the current value is True, but previously it was False. It is a test of a Biconditional Logical Connective.

Consider the following code example:

print( "Line 1: ", trigger( 'George', False ) )
print( "Line 2: ", trigger( 'George', False ) )
print( "Line 3: ", trigger( 'George', True ) )
print( "Line 4: ", trigger( 'George', True ) )
print( "Line 5: ", trigger( 'George', False ) )
print( "Line 6: ", trigger( 'George', False ) )


The results are:
Line 1: False
Line 2: False
Line 3: True
Line 4: False
Line 5: False
Line 6: False

Line 3 is the ONLY one to return the value of True.
Why?
Because the value given to the trigger named ‘George’ was False in Line 2,
but the value given in Line 3 was True.
The trigger function will return True if, and only if, the current value is True, but previously it was False ( False => True ).

OK, now consider this:


while True:
    time.sleep( 60 * 5 ) # Five minute time delay.
    isPrinterUp = isDeviceResponding( myPrinterIP, 515 )
    if trigger( "George", isPrinterUp ):
        print("The printer just came back on-line" )

This bit of code will run through the loop, once every 5 minutes, and will check to see if my printer is responding.

If the printer is responding, the value of isPrinterUp will be True. If the printer is NOT responding, the value of isPrinterUp will be False.

The trigger() function will also be executed every 5 minutes, as the loop is executed, over, and over, and over again.

But the trigger function will return True if, and only if, the current value given to the trigger named “George” is True, but previously it was False.

That’s to say, the trigger() function will return True ONLY when my printer goes from an off-line state, to an on-line state.

Note that they code does NOT sit and wait for the printer to come back on-line. Instead it does the check, and then the loop moves on. So, you could be monitoring multiple events in the same loop, like "is the second printer up", or "is the basement router up", etc. You just need to add another ‘if’ block. This is the true power of using triggers within a loop, it allows one script to monitor for multiple events. In other words, Multitasking! The example code at the bottom of this post shows multiple triggers inside the loop. None wait until the condition is meet before moving on.

The trigger name I am using in this example, “George”, is simply a variable name used to keep track of the previous value. Since our script can have many triggers in it, it is important that it keeps track of the previous value for each of them. This is why we need to give each of the triggers a unique name.

The timer() function is just a variation of the trigger() function. It will return True once every n number of seconds. Example, the following statement will return True only once ever 20 minutes ( 1200 seconds ).
print( timer( “OnceEvery20Minutes”, 1200) )

The usage of both of these functions will become clearer when you see the ‘Main Logic’ section of the example code bellow.


Triggers, Events, and Actions.

Now it’s time for a bit of vocabulary.
For this project, the act of your printer running out of paper is called the Event, and sending a text message about it is called the Action.
The bit of code that fires the Action, when the Event occurs is called the Trigger.

The printer coming back on-line is also an Event, with another Action ( sending text message ), and another Trigger.

In addition to monitoring a printer, an Event could also be
a server running low on free space,
a warehouse door being left open for longer than 10 minutes,
water detected on a basement floor ( flood alarm ),
a weather forecast of a storm or rain,
or just about anything else that the computer can be made to detect.

An Event can so be when the clock reaches a certain date/time ( a scheduler ), or when a certain number of seconds have elapsed ( a timer ).

In addition to send a text message, an Action could also be
playing a prerecorded message over a speaker,
a text message saying ‘it’s going to rain, roll up the windows of your car’,
displaying a message on the screen,
sending a signal to open the garage door,
or anything else.

There is no reason why a single python script could not be monitoring many Events, with multiple Actions being fired by many Triggers.

A side note: Above I mentioned an event as possibly being a weather forecast of a storm or rain. The action to the event being a text message saying ‘It’s going to rain, roll up the windows of your car’. If you are interested in monitoring for this kind of event, I recommend that you review the WEATHER section of my previous posting “Python - RSS, Not just for podcasts” http://gsw7.net/K700010.php. Using this as a starting point, create a function that will check for the words “rain”, “storm”, “snow”, or “ice” in the description, and return True if they do. Then create your action and trigger.


The SendToMyPhone() function

You will need to supply your own information in the SendToMyPhone() function.

It should be noted that sending a text message this way is rather old-school. Many cell-phone carriers now have APIs for sending text messages. There are also a few subscription services ( not FREE ) that can also be used. But sending text messages by Simple Mail Transfer Protocol ( SMTP ) still works, and it’s still FREE. For a general purpose script like this one, it still works great.

Way back in March of 2019, I did a post about how to send a text message to a cell phone using python and SMTP: Python – How to send a text message, so if you seek detailed info on this subject, please refer to that post.

I won’t be covering the subject in detail here. But here are the basics you need for this project:

You will need one or more cell-phones to receive the text messages, and an email account ( like Gmail ) to send the text messages from.

Every cell-phone has an email address that allows you to send a text message to it. So if for example, your carrier is AT&T, and your cell-phone number is 205-222-3333, then the email address would be "2052223333@mms.att.net". If you are using a different carrier, you can do an internet search on worlds like “sending text as email Verison” to find the email domain for your phone. All of these email address will be the same format: your phone number without any dashes, the “@” symbol, and the email domain for that carrier.

If you are using a Gmail account to send the messages ( as in the example bellow ), you might need to go into your account settings and specify that any ‘less-secure apps’ have permission to send messages. For more on this subject, see https://support.google.com/accounts/answer/6010255?hl=en

Also if you are using Gmail, The SMTP address is “smtp.gmail.com". The SendFrom address will be your email address, and the password will be your password for your Gmail account.

If you are not using Gmail, you can do an internet search for the SMTP address for your email provider; all that info is public knowledge. Or, you can also simply call the tech support line for your provider and they should be able to provide the info over the phone.

If you have a web-hosting account with someplace like HostGator or FatCow, they also have SMTP gateways that you could uses for sending these kind of text messages.

If this project is for business purposes and your place of business has it’s own email server, speak to your IT person about using it for sending text messages. Show them your code and this documentation, and I’m sure that they will be able to set you up with the info you need.


The isDeviceResponding() function.

Finally, the isDeviceResponding() function is pretty simple. It uses the socket module to test if anything is responding on a TCP/IP port. It can be used to check any network connected device, you simply have to lookup the port number used by the type of device. In the case of a network printer, that is port number 515.


IMPORTANT: Monitor the availability of a website.

If you intend to monitor the availability of a website; You should NEVER have a script check a website more than once an hour. Excessive checking of a website may be seen as a potential denial of service attack by your internet hosting provider.


import requests

def isWebsiteUp( url ):
    """Return True if the website is accessible."""
    return requests.get( url ).status_code == 200

print( "Website is up: ",  isWebsiteUp( 'https://cnn.com' ) )


Keeping a log file.

I highly recommend that you add code to your project to keep a log of any important Events, Actions, and Errors. Also, when they occurred.

Especially if the project is to be used in a business environment. Because sooner or later, you WILL be called into the managers office and ask to explain why the hotpager person did, or did not, receive an IMPORTANT text message. Having a log to document what happened can really save your a**. This is personal experience talking here.

Also, I recommend that you record a Heart Beat message to the log every few minutes to document that the script is still running at that time.


The Main Logic.

In the section of code I have called The Main Logic, you will need to replace the IP address that I am using, with the IP address of your printer.

The time delay at the top of the loop is very important. Without it, the script will simply run as fast as it can, looping over and over, and over. Eatting up all the CPU cycles. After a few minutes, the computer will crash.
By adding this short 20 second time delay, we are sharing the CPU resources, giving other processes on the computer a chance to have some.


Note that each of the 5 items that were listed in the Defining the objectives section are addressed in the Main Logic section. I basically followed the list to create this section of the code.

This is why clearly defining the objectives for your project is so important. It will give you a clear road-map for creating your Main Logic section.

You could uses my code as a template, to add more triggers to the Main Logic.
You could add as many triggers as you need.



"""
trigger.py
Written by Joe Roten

This is an example of how to create an Event Monitor
using python.

For documentation, see
   http://gsw7.net/K700011.php

"""
import time
import datetime
import socket
import smtplib

MyData = {}


def trigger( TriggerName, Value=False ):
    """Returns True if, Value is True, but was false on previous call."""
    t1 = "trigger." + str( TriggerName )
    t2 = bool( Value )
    reply = MyData.get( t1, True )==False and t2
    MyData[ t1 ] = t2
    return reply


def timer(  TriggerName, Seconds=60 ):
    """Returns True once every n seconds."""
    n = int ( Seconds )
    return trigger(TriggerName, ( time.time() % n ) < n/2 )


def isDeviceResponding( ipAddress, port=515 ):
    """Returns True if the device is responding to a socket conection"""
    # Note: Network printers uses port 515.
    reply = True
    try:
      client_socket = socket.socket()
      client_socket.settimeout(20)
      client_socket.connect((ipAddress, port))
      client_socket.close()
    except:
      reply = False  
    return reply


#################################################################
#
# You will need to supply your own information in the following
#   function. See http://gsw7.net/K700011.php for documentation.
#
#################################################################

def sendToMyPhone(message):
    """Send a text message to my phone."""
    MSend("2052223333@mms.att.net", message, message, "JohnSmith@gmail.com", "smtp.gmail.com", "password")


def MSend(SendToList, Subject="", Message="", SendFrom="", SMTP="", Pwd="", BlindCopy=True):
    """Send a short message as an Email, or as a TextMessage (SMS)."""
    # for info, see http://gsw7.net/K700007.php
    # Note: This function requires the 'smtplib' module.
    MText = []
    MText.append("From: " + SendFrom )
    try:
      if isinstance(SendToList, (list,)):
        send_to = SendToList
      else:
        send_to = SendToList.split(",")  
      send_to.append( SendFrom )
    except Exception as e:
      send_to = SendFrom 
    if BlindCopy:
      MText.append("To: " + SendFrom )
    else:
      MText.append("To: " + ", ".join(send_to) )

    MText.append("Subject: " + Subject)
    MText.append("")
    MText.append( Message )
    MText.append("\n.EndOfMessage\n")
    try:  
      server = smtplib.SMTP_SSL(SMTP, 465)
      server.ehlo()
      server.login(SendFrom, Pwd)
      server.sendmail(SendFrom, send_to, "\n".join(MText) )
      server.close()      
    except Exception as e:
      print("Error: ", str(e) )


def CurrentTime():
    """Return a string of the current time in an ISO8601 format."""
    return datetime.datetime.now().isoformat().split(".")[0]



###################################################
#      
#  Main Logic.
#  
#  You will need to replace the IP address
#    with the one used by your printer.
#
###################################################

if __name__ == "__main__":
  print( CurrentTime(), "trigger.py is now running." )  
  while True:
    time.sleep(20) # 20 second time delay

    # Print a HeartBeat message every 5 minutes.
    if timer("HeartBeat", 60 * 5 ):
      print ( CurrentTime() + ": HeartBeat, Script is still running.")  
  
    hour = datetime.datetime.now().hour
    weekday = datetime.datetime.now().weekday()
    isOfficeHours = ( weekday<5 and hour>=8 and hour<16 )

    # Check the printer every 20 minutes during office hours.
    if isOfficeHours and timer("Every20Minutes", 60 * 20 ):
        
       isPrinterUp = isDeviceResponding( "192.168.1.158", 515 )
       print( CurrentTime(), "Value of isPrinterUp: ", isPrinterUp )

       # Send a message when the printer comes back on-line.
       if trigger( "isPrinterUp", isPrinterUp ):
         sendToMyPhone( "The printer is now on-line." )
         print( CurrentTime(), "The printer is now on-line." )

       # Send a message when the printer goes off-line.
       if trigger( "isPrinterDown", not isPrinterUp ):
         sendToMyPhone( "Alert, the production printer is off-line." )
         print( CurrentTime(), "Alert, the production printer is off-line." )

       # Send a reminder every hour while the printer is down.
       if timer( "OneAnHour", 360 ) and not isPrinterUp:
         sendToMyPhone( "Reminder: The printer is still down." )
         print( CurrentTime(), "Reminder: The printer is still down." )

    # Add additional triggers here.

    
# End of code.




In concussion.

And that concludes my introduction to Event Monitoring using Python. I hope that someone out there find’s this useful.

Everyone have a good day, and be kind to each other.

Joe Roten. www.gsw7.net/joe

Last updated: 2020-09-25



Written by Joe Roten

Computer tech, Graphic Artist, Photographer, Writer, Educator, Programmer, Jack of many trades, Social gadfly, and Scholar without portfolio. http://www.gsw7.net/joe/

Written by Joe Roten

http://www.gsw7.net/joe/

As always

The information on my website is FREE.
But donations to help pay for Coffee and Beer are always welcomed.
Thanks.