Sunday, September 18, 2016

Keep Tabs on the Devices Connected to Your LAN

-----
THE PROBLEM:
Like most people, we have tons of things connected via WiFi to our router.  Be it AppleTVs, Rasberry PIs, Amazon Echos, weather stations, overhead airplane trackers, security cameras, etc. etc. etc....  The problem arises when you think and expect a device to "work" and it doesn't.  Is it the device? Is it the service being accessed? VNC server not running?  Most of the time "all is well", but if it isn't it can be frustrating.  Who knew the security camera was down?  Why can't I VNC into that Raspberry PI?  Most of us have been there and this project helps solve the problem.
------
PROBLEM SOLVED:
We had in the 'warehouse'  a Raspberry PI Zero (mainly because it was cheap so why not) and a BLINKT (who doesn't like flashy light things) doing nothing and just waiting for a project.  Setting up the gear to monitor our LAN seemed perfect for the pair.

The solution is pretty straight forward: Get the RaspBerry PI Zero on your LAN via WiFi.  Plug in the BLINKT module.  Every thirty seconds or so send a ping to an IP of a device on your network.
If the device IP ping is good then
     - flash the BLINKT green.
If the device IP ping is bad then
     - retest the device just to make certain the failure wasn't an anomaly.
     - two failed pings back to back then flash the BLINKT red and log the failure to a Google Drive Sheet.
-----
RESULTS:

The rig works perfectly.  We can check the Google Drive Sheet or the BLINKT quickly see if anything on the LAN is down.  We use IFTTT.com to log the failures to the Google Drive Sheet.  We can just as easily use IFTTT.com to send out a text message, post a Tweet, call our phone, or a host of other IFTTT.com supported actions.  In short, it is pretty darn useful.

Here is what a sample output to the Google Drive Sheet looks like:
-----
SOURCE:
You will need an IFTTT.com and Google logons if you want to log ping status to a Google Drive Sheet.  Both are useful for a few hundred reasons, so if you don't have one then get one - they are free.

#!/usr/bin/env python

# LocalPing Testing/Tracking
# RasPI Zero with BLINKT module attached
# BLINKT scans back/forth with delay in mS = to CPU % use (45% = ,45 Secs)
#
# After a set delay (var = Ping_test_how_often)
# Checks if tracked IPs are up on the LAN. 
#      - If device ping passes, flash all BLINKT LEDs green.
#            - however, if there was a ping fail in the last xx (var = Ping_check) flash 1st LED as RED and rest as GREEN.
#      - If device ping Fails two pings back to back:
#            - use IFTTT Maker Channel to log the failed ping to a Google Drive sheet
#            - write the falied ping information to the terminal
#
# WhiskeyTangoHotel.Com - AUG2016

import os # allows for the ping calls
import time # for sleeps and localtime prints
import random # for random number generation
import psutil # for ping system calls
from blinkt import set_pixel, show, set_brightness, clear # https://shop.pimoroni.com/products/blinkt

# Set up for IFTTT triggering
import requests # needed to get https request to IFTTT web page
MAKER_SECRET_KEY = "xxxyyyzzz1234561890"  #this is your IFTTT Maker Channel secret key

PingSiteIP = [1,2,3,4,5,6,7,8,9,10,11]   # holds the IP or DNS name of the site being pinged
PingSiteName = [1,2,3,4,5,6,7,8,9,10,11]   # Friendly/Human name for the device

# Identify IPs that you want to track on your LAN
# PingSiteName[] can be anything. It is the "human" name for the IP
# Enter the 'x' part of the IP; the last number of the IP address without the '.'
IP_Prefix = "192.168.0."  # typically 192.168.0. or 192.168.1.  DON'T FORGET THE LAST '.'
PingSiteIP[1] = "11"
PingSiteName[1] = "RasPI(Upper)"
PingSiteIP[2] = "14"
PingSiteName[2] = "RasPI(Lower)"
PingSiteIP[3] = "20"
PingSiteName[3] = "Security(NW)"
PingSiteIP[4] = "23"
PingSiteName[4] = "Security(Drive)"
PingSiteIP[5] = "32"
PingSiteName[5] = "Security(SE)"
PingSiteIP[6] = "13"
PingSiteName[6] = "ElectricImp"

PingSiteIP[7] = "27)"
PingSiteName[7] = "RasPI(Airplanes)"
PinsSiteIP[8] = "49"
PingSiteName[8] = "RasPI(Weather)"
PingSiteIP[9] = "45"
PingSiteName[9] = "Printer(HP)"

Num_ping_sites = 9   # Make sure this matches the above number of site entries

Single_test_site = 1 # Must be 1; I did not want to start at array 0
Flash_on = .05       # Secs to keep ping status LEDs on
Flash_off = .2       # Secs to keep ping status LEDs off
Failed_ping = 0      # FLAG for if there is a failed ping in the last Ping_check trys then turn one green light red
Second_try_delay = 20    # seconds to wait to config falied ping on device (Yellow LEDs scan during this delay)
Ping_check = 15         # see comment above for Failed_Ping
Ping_check_counter = Ping_check # Counts Ping_check, decrements and resets at Zero
Ping_test_how_often = 10 # 10 is ~30 secs depending on delay from CPU load. Dont ping test on every cpu speed check
Every_x_times = Ping_test_how_often # decrementing counter for the Ping_test_how_often var
set_brightness(0.05) # 0.05 is lowest usable dim.  1.0 is full bright (the BLINKT is *really* bright if you want!)
LED_Delay = 0        # Controls the LED to LED scan speed. This is adjusted in the program after reading CPU % load
r_largest_wanted_value = 10  # these vars control the max amount of R, G, or B you desire. Also helps with more dimming.
g_largest_wanted_value = 10  # during the CPU load status LED back and forth scan on the BLINKT
b_largest_wanted_value = 255 # For example: 10, 10, 255 would mean favor BLUE hues.

Current_day = time.strftime("%d", time.localtime()) # what day of the month is it
New_day_dawned = Current_day # 1st run after midnight update the Google Sheet. Use to verify the prog has not stopped running
Failed_since_midnight = 0

print " "
print "Writing header information to Google Sheet..."
#Triggers the IFTTT Maker channel "local_ping" to create an information header
url = "https://maker.ifttt.com/trigger/local_ping/with/key/" + MAKER_SECRET_KEY + "?value1=" + "*************************"
res = requests.get(url)
#print str(res) + " is web request result. "   #used only for debug

print "Testing LEDs..."
print "---------------------------------------------"
#LED test and some delay for the IFTTT calls
for j in range (0,8):
    set_pixel(j, 30, 0, 0)
show()
time.sleep(Flash_on * j)
for j in range (0,8):
    set_pixel(j, 0, 30, 0)
show()
time.sleep(Flash_on * j)
for j in range (0,8):
    set_pixel(j, 0, 0, 30)
show()
time.sleep(Flash_on * j)   

for sheet_header in range (1, Num_ping_sites + 1):
    #this triggers the IFTTT Maker channel "local_ping" to update the google sheet.
    url = "https://maker.ifttt.com/trigger/local_ping/with/key/" + MAKER_SECRET_KEY + "?value1=" + "Tracking " + PingSiteName[sheet_header] + "&value2=" + IP_Prefix + str(PingSiteIP[sheet_header])
    res = requests.get(url)
    #print str(res) + " is web request result. "   #used only for debug
    print str(sheet_header) + ") " + PingSiteName[sheet_header] + " at " + IP_Prefix + str(PingSiteIP[sheet_header])
   
    #LED test and some delay for the IFTTT calls
    for j in range (0,8):
        set_pixel(j, 30, 0, 0)
    show()
    time.sleep(Flash_on * j)
    for j in range (0,8):
        set_pixel(j, 0, 30, 0)
    show()
    time.sleep(Flash_on * j)
    for j in range (0,8):
        set_pixel(j, 0, 0, 30)
    show()
    time.sleep(Flash_on * j)       

clear() # all LEDS off
   
url = "https://maker.ifttt.com/trigger/local_ping/with/key/" + MAKER_SECRET_KEY + "?value1=" + "*************************"
res = requests.get(url)
#print str(res) + " is web request result. "   #used only for debug
print " "
print "Done with test!!!"
localtime = time.asctime( time.localtime(time.time()) )
print localtime + ": Tracking begins for the devices listed..."
print " "

time.sleep(1) # let CPU% settle from prog load

while True:
    #LED delay is established randomly
    #random.seed() # seed off the CPU clock
    #r = random.random()
    #LED_Delay = (int((r * 10) + 1)) / 100.0
   
    #LED delay is equal to %CPU usage in mSec (45% = .45 Secs)
    LED_Delay = psutil.cpu_percent() / 100.0

    random.seed() # seed off the CPU clock
    r = random.random()
    red = int((r * r_largest_wanted_value) + 1)
   
    random.seed() # seed off the CPU clock
    r = random.random()
    green = int((r * g_largest_wanted_value) + 1)

    random.seed() # seed off the CPU clock
    r = random.random()
    blue = int((r * b_largest_wanted_value) + 1)   
   
    for i in range(8):       
        set_pixel(i , red, green, blue)
        show()
        time.sleep(LED_Delay)
        clear()

    #LED delay is established randomly
    #random.seed() # seed off the CPU clock
    #r = random.random()
    #LED_Delay = (int((r * 10) + 1)) / 100.0
   
    #LED delay is equal to %CPU usage in mSec (45% = .45mS)
    LED_Delay = psutil.cpu_percent() / 100.0

    random.seed() # seed off the CPU clock
    r = random.random()
    red = int((r * r_largest_wanted_value) + 1)
   
    random.seed() # seed off the CPU clock
    r = random.random()
    green = int((r * g_largest_wanted_value) + 1)

    random.seed() # seed off the CPU clock
    r = random.random()
    blue = int((r * b_largest_wanted_value) + 1)       

    for i in range(7, -1, -1):
        set_pixel(i , red, green, blue)
        show()
        time.sleep(LED_Delay)
        clear()
       
    # Local ping to see if all is up
    # But we don't want to check on every run so:
   
    if Every_x_times == 1: #  var is decremented in the else.  Check the ping
        Every_x_times = Ping_test_how_often
        if Single_test_site == Num_ping_sites:
            Single_test_site = 1
        else:   
            Single_test_site = Single_test_site + 1           
   
        if Ping_check_counter == 0:
            Ping_check_counter = Ping_check
            Failed_ping = 0
        else:
            Ping_check_counter = Ping_check_counter -1       
                   
        hostname = IP_Prefix + str(PingSiteIP[Single_test_site]) # OK, now the have selected one of our ping test sites
        # Ping test IP with os.system command
        response = os.system("ping -c 1 " + hostname + " >/dev/null")
        #print PingSiteName[Single_test_site] + " at IP " + str(PingSiteIP[Single_test_site])
        if response == 0:
            for    i in range (0,10): #flash all green
                for j in range (0,8):
                    if j ==0 and Failed_ping == 1:
                        set_pixel(j, 30, 0, 0)   # red pixel to show a recent failed ping
                    else:
                        set_pixel(j, 0, 30, 0)
                show()
                time.sleep(Flash_on)
                for j in range (0,8):
                    set_pixel(j, 0, 0, 0)
                show()
                time.sleep(Flash_off)               
        else: #response != 0:
            # Ping failed, but wait 5 seconds and try again.
            # Walk yellow LEDs to show re-test and to delay some before double checking the device ping
            for i in range(8):       
                set_pixel(i , 30, 30, 0)  # YELLOW LED walk
                show()
                time.sleep(Second_try_delay/16.0)     
                   
            clear()
            for i in range(7, -1, -1):
                set_pixel(i , 30, 30, 0)
                show()
                time.sleep(Second_try_delay/16.0)
                   
            response = os.system("ping -c 1 " + hostname + " >/dev/null") # let's check again to make certain the ping really failed.
            if response != 0:                                             # it take two back to back failed pings to consider that device down.
                Failed_since_midnight = Failed_since_midnight + 1  # resetable counted tracks the days failed ping count
               
                localtime = time.asctime( time.localtime(time.time()) )
                print localtime + ": Failed ping " + str(Failed_since_midnight) + " from " + PingSiteName[Single_test_site] + " at " + IP_Prefix + str(PingSiteIP[Single_test_site])
               
                #this triggers the IFTTT Maker channel "local_ping" to update the google sheet.
                url = "https://maker.ifttt.com/trigger/local_ping/with/key/" + MAKER_SECRET_KEY + "?value1=" + "FailCount[" + str(Failed_since_midnight) + "]:" + PingSiteName[Single_test_site] + "&value2=" + IP_Prefix + str(PingSiteIP[Single_test_site])
                res = requests.get(url)
                #print str(res) + " is web request result. "   #used only for debug
               
                for    i in range (0,10): #flash all red
                    Failed_ping = 1
                    for j in range (0,8):
                        set_pixel(j, 30, 0, 0)
                    show()
                    time.sleep(Flash_on)
                    for j in range (0,8):
                        set_pixel(j, 0, 0, 0)
                        show()
                    time.sleep(Flash_off)
    else: # if Every_x_times == 0:
        Every_x_times = Every_x_times - 1
       
    # Last thing is check if this is a new day.  If yes, this write status to Google sheet
    Current_day = time.strftime("%d", time.localtime())
    if Current_day != New_day_dawned:
        url = "https://maker.ifttt.com/trigger/local_ping/with/key/" + MAKER_SECRET_KEY + "?value1=" + ">>> Failed since midnight: " + str(Failed_since_midnight)
        res = requests.get(url)
        localtime = time.asctime( time.localtime(time.time()) )
        print " "
        print localtime + " >>> Failed since midnight: " + str(Failed_since_midnight)
        print " "
        time.sleep(1)
        Failed_since_midnight = 0
        New_day_dawned = Current_day       

   
-----
Give it a try.  It was been very very useful here.