I have written an app in Python 3 which monitors internet speed and will send a tweet to an ISP when the speed drops too low. The app has a config file where the ISP and target speeds can be configured.
The program has two threads running simultaneously. SpeedTestThread
monitors internet speeds at set intervals using python module speedtest-cli. TwitterThread
uses tweepy to send tweets containing data from speed tests.
When SpeedTestThread
gets speeds below threshold the data is pushed to tweet_data_queue and the global tweetFlag
is set. TwitterThread
monitors the tweetFlag
and, when set, retrieves the data from the queue and generates a tweet using it.
I have removed the docstrings to save space; they can be found in my Github repository.
speed_test.py:
import speedtest
import json
import time
import csv
import os
import threading
import queue
import tweepy
import random
config = json.load(open('config.json'))
exitFlag = 0
tweetFlag = 0
# Global queue for tweet data, shared between threads
tweet_data_queue = queue.Queue()
def main():
error_logger = ErrorLogger(config['errorFilePath'])
test_thread = SpeedTestThread(1, "SpeedTestThread1", error_logger)
tweet_thread = TwitterThread(2, "TwitterThread1", error_logger)
test_thread.start()
tweet_thread.start()
class SpeedTestThread(threading.Thread):
def __init__(self, thread_id, name, error_logger):
threading.Thread.__init__(self)
self.name = name
self.thread_id = thread_id
self.s = speedtest.Speedtest()
self.targetSpeeds = config['internetSpeeds']
self.dataLogger = ErrorLogger(config['logFilePath'])
self.error_logger = error_logger
def run(self):
global exitFlag
prevError = False
while exitFlag == 0:
try:
results = self.getSpeeds()
self.checkSpeeds(results)
self.dataLogger.logCsv(results)
except Exception as e:
error = {"time": time.ctime(),
"error": "Unable to retrieve results",
"exception": e}
self.error_logger.logError(error)
prevError = True
if prevError:
self.error_logger.counter = 0
time.sleep(config['testFreq'])
def getSpeeds(self):
self.s.get_best_server()
self.s.upload()
self.s.download()
return self.s.results.dict()
def checkSpeeds(self, results):
global tweetFlag
down = results['download']
up = results['upload']
ping = results['ping']
if (down / (2**20) < self.targetSpeeds['download'] or
up / (2**20) < self.targetSpeeds['upload'] or
ping > self.targetSpeeds['ping']):
print("Unnaceptable speed results:\n"
"Download: %s\n"
"Upload: %s\n"
"Ping: %s\n" % (down, up, ping))
tweetFlag = 1
tweet_data_queue.put(results)
print("Results queued for tweet")
class TwitterThread(threading.Thread):
def __init__(self, thread_id, name, error_logger):
threading.Thread.__init__(self)
self.thread_id = thread_id
self.name = name
self.error_logger = error_logger
# Set up tweepy with twitter API authentication
self.apiData = config['twitterAPI']
auth = tweepy.OAuthHandler(
self.apiData['apiKey'], self.apiData['apiSecret'])
auth.set_access_token(self.apiData['accessToken'],
self.apiData['accessTokenSecret'])
self.twitterAPI = tweepy.API(auth)
def run(self):
global exitFlag
global tweetFlag
prevError = False
while True:
if exitFlag == 1:
break
if tweetFlag == 1:
tweet = self.getTweet()
try:
self.twitterAPI.update_status(tweet)
print("Tweet successful")
except Exception as e:
error = {"time": time.ctime(),
"error": "Unable to send tweet",
"exception": e}
self.error_logger.logError(error)
prevError = True
if prevError:
self.error_logger.counter = 0
if tweet_data_queue.qsize() == 0:
tweetFlag = 0
def getTweet(self):
data = tweet_data_queue.get()
down = round(data['download'] / (2**20), 2)
up = round(data['upload'] / (2**20), 2)
content = random.choice(config['tweetContent'])
return content.format(config['ispTwitter'], down, up)
class Logger(object):
def __init__(self, filepath):
self.filepath = filepath
def logCsv(self, data):
print("Logging ...")
with open(self.filepath, 'a') as f:
writer = csv.DictWriter(f, fieldnames=data.keys())
if os.stat(self.filepath).st_size == 0:
writer.writeheader()
writer.writerow(data)
print("Done -> '%s'" % self.filepath)
class ErrorLogger(Logger):
def __init__(self, filepath):
Logger.__init__(self, filepath)
self.counter = 0
def logError(self, errorData):
global exitFlag
if self.counter >= config['testAttempts']:
exitFlag = 1
errorData['error'] = "10 Failed test attempts, exiting."
self.counter = 0
print(errorData['error'])
self.logCsv(errorData)
n
minutes average belowX
, then send a tweet. Otherwise this will probably send a hell-of-a-lot of false-positives. \$\endgroup\$self.twitterAPI = tweepy.API(auth)
toself.twitterAPI = tweepy.API(auth, wait_on_rate_limit=True, wait_on_rate_limit_notify=True)
, Tweepy should handle this and warn you every time it has been limited. \$\endgroup\$