01234567890123456789012345678901234567890123456789012345678901234567890123456789
23456789101112131415161718192021222324252627282930313233343536373839404142 87888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127 364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425 55735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613 |
<----SKIPPED LINES---->
import datetime
import io
import json
import math
import multiprocessing
import numbers
import os
import pickle
import queue
import re
import shutil
import signal
import statistics
import sys
import textwrap
import time
import bs4
import dateutil.relativedelta
import filelock
import numpy
import matplotlib
import matplotlib.pyplot
import psutil
import pycurl
import pytz
import requests
import tzlocal
import unidecode
from constants import RASPBERRY_PI, MESSAGEBOARD_PATH, WEBSERVER_PATH
import arduino
if RASPBERRY_PI:
import gpiozero # pylint: disable=E0401
import RPi.GPIO # pylint: disable=E0401
VERBOSE = False # additional messages logged
<----SKIPPED LINES---->
# At the time a flight is first identified as being of interest (in that it falls
# within MIN_METERS meters of HOME), it - and core attributes derived from FlightAware,
# if any - is appended to the end of this pickle file. However, since this file is
# cached in working memory, flights older than 30 days are flushed from this periodically.
PICKLE_FLIGHTS = 'pickle/flights.pk'
# True splits all the flights created in simulation into separate date files, just like
# the non-simulated runs; False consolidates all flights into one pickle file.
SPLIT_SIMULATION_FLIGHT_PICKLE = False
# Status data about messageboard - is it running, etc. Specifically, has tuples
# of data (timestamp, system_id, status), where system_id is either the pin id of GPIO,
# or a 0 to indicate overall system, and status is boolean
PICKLE_DASHBOARD = 'pickle/dashboard.pk'
CACHED_ELEMENT_PREFIX = 'cached_'
# This web-exposed file is used for non-error messages that might highlight data or
# code logic to check into. It is only cleared out manually.
LOGFILE = 'log.txt'
LOGFILE_LOCK = 'log.txt.lock'
# Identical to the LOGFILE, except it includes just the most recent n lines. Newest
# lines are at the end.
ROLLING_LOGFILE = 'rolling_log.txt' #file for error messages
# Users can trigger .png histograms analogous to the text ones from the web interface;
# this is the folder (within WEBSERVER_PATH) where those files are placed
WEBSERVER_IMAGE_FOLDER = 'images/'
# Multiple histograms can be generated, i.e. for airline, aircraft, day of week, etc.
# The output files are named by the prefix & suffix, i.e.: prefix + type + . + suffix,
# as in histogram_aircraft.png. These names match up to the names expected by the html
# page that displays the images. Also, note that the suffix is interpreted by matplotlib
# to identify the image format to create.
HISTOGRAM_IMAGE_PREFIX = 'histogram_'
HISTOGRAM_IMAGE_SUFFIX = 'png'
# For those of the approximately ten different types of histograms _not_ generated,
# an empty image is copied into the location expected by the webpage instead; this is
# the location of that "empty" image file.
HISTOGRAM_EMPTY_IMAGE_FILE = 'empty.png'
# This file indicates a pending request for histograms - either png, text-based, or
<----SKIPPED LINES---->
AIRCRAFT_LENGTH['Pilatus PC-12 (single-turboprop)'] = 14.4
def Log(message, file=None, rolling=None):
"""Write a message to a logfile along with a timestamp.
Args:
message: string message to write
file: string representing file name and, if needed, path to the file to write to
rolling: name of file that will keep only the last n files of file
"""
# can't define as a default parameter because LOGFILE name is potentially
# modified based on SIMULATION flag
if not file:
file = LOGFILE
# special case: for the main logfile, we always keep a rolling log
if not rolling and file == LOGFILE:
rolling = ROLLING_LOGFILE
if file == LOGFILE:
lock = filelock.FileLock(LOGFILE_LOCK)
lock.acquire()
try:
with open(file, 'a') as f:
# by excluding the timestamp, file diffs become easier between runs
if not SIMULATION or file == LOGFILE:
f.write('='*80+'\n')
f.write(str(datetime.datetime.now(TZ))+'\n')
f.write('\n')
f.write(str(message)+'\n')
except IOError:
Log('Unable to append to ' + file)
if rolling:
existing_log_lines = ReadFile(file).splitlines()
with open(rolling, 'w') as f:
f.write('\n'.join(existing_log_lines[-1000:]))
if file == LOGFILE:
lock.release()
def LogTimes(times):
"""Logs elapsed time messages from a list of epochs."""
msg = ''
for n, t in enumerate(times[:-1]):
msg += '%.2fs to get from reading %d to reading %s\n' % (times[n + 1] - t, n, n + 1)
Log(msg)
def MaintainRollingWebLog(message, max_count, filename=None):
"""Maintains a rolling text file of at most max_count printed messages.
Newest data at top and oldest data at the end, of at most max_count messages,
where the delimiter between each message is identified by a special fixed string.
Args:
message: text message to prepend to the file.
max_count: maximum number of messages to keep in the file; the max_count+1st message
is deleted.
<----SKIPPED LINES---->
version_name = python_prefix + last_modified_suffix + file_extension
version_path = os.path.join(VERSION_REPOSITORY, version_name)
if not os.path.exists(version_path):
shutil.copyfile(live_path, version_path)
return version_name
global VERSION_MESSAGEBOARD
global VERSION_ARDUINO
VERSION_MESSAGEBOARD = MakeCopy('messageboard')
VERSION_ARDUINO = MakeCopy('arduino')
def main():
"""Traffic cop between incoming radio flight messages, configuration, and messageboard.
This is the main logic, checking for new flights, augmenting the radio signal with
additional web-scraped data, and generating messages in a form presentable to the
messageboard.
"""
RemoveFile(LOGFILE_LOCK)
VersionControl()
# Since this clears log files, it should occur first before we start logging
if '-s' in sys.argv:
global SIMULATION_COUNTER
SimulationSetup()
last_heartbeat_time = HeartbeatRestart()
init_timing = [time.time()] # time 0
# This flag slows down simulation time around a flight, great for debugging the arduinos
simulation_slowdown = bool('-f' in sys.argv)
# Redirect any errors to a log file instead of the screen, and add a datestamp
if not SIMULATION:
sys.stderr = open(STDERR_FILE, 'a')
Log('', STDERR_FILE)
init_timing.append(time.time()) # time 1
Log('Starting up process %d' % os.getpid())
<----SKIPPED LINES---->
|
01234567890123456789012345678901234567890123456789012345678901234567890123456789
23456789101112131415161718192021 2223242526272829303132333435363738394041 8687888990919293949596979899100101102103104105 106107108109110111112113114115116117118119120121122123124125 362363364365366367368369370371372373374375376377378379380381 382383384385386387388389390391392393394395396 397398399400401402403404405406407408409410411412413414415416 55645565556655675568556955705571557255735574557555765577557855795580558155825583 55845585558655875588558955905591559255935594559555965597559855995600560156025603 |
<----SKIPPED LINES---->
import datetime
import io
import json
import math
import multiprocessing
import numbers
import os
import pickle
import queue
import re
import shutil
import signal
import statistics
import sys
import textwrap
import time
import bs4
import dateutil.relativedelta
import numpy
import matplotlib
import matplotlib.pyplot
import psutil
import pycurl
import pytz
import requests
import tzlocal
import unidecode
from constants import RASPBERRY_PI, MESSAGEBOARD_PATH, WEBSERVER_PATH
import arduino
if RASPBERRY_PI:
import gpiozero # pylint: disable=E0401
import RPi.GPIO # pylint: disable=E0401
VERBOSE = False # additional messages logged
<----SKIPPED LINES---->
# At the time a flight is first identified as being of interest (in that it falls
# within MIN_METERS meters of HOME), it - and core attributes derived from FlightAware,
# if any - is appended to the end of this pickle file. However, since this file is
# cached in working memory, flights older than 30 days are flushed from this periodically.
PICKLE_FLIGHTS = 'pickle/flights.pk'
# True splits all the flights created in simulation into separate date files, just like
# the non-simulated runs; False consolidates all flights into one pickle file.
SPLIT_SIMULATION_FLIGHT_PICKLE = False
# Status data about messageboard - is it running, etc. Specifically, has tuples
# of data (timestamp, system_id, status), where system_id is either the pin id of GPIO,
# or a 0 to indicate overall system, and status is boolean
PICKLE_DASHBOARD = 'pickle/dashboard.pk'
CACHED_ELEMENT_PREFIX = 'cached_'
# This web-exposed file is used for non-error messages that might highlight data or
# code logic to check into. It is only cleared out manually.
LOGFILE = 'log.txt'
# Identical to the LOGFILE, except it includes just the most recent n lines. Newest
# lines are at the end.
ROLLING_LOGFILE = 'rolling_log.txt' #file for error messages
# Users can trigger .png histograms analogous to the text ones from the web interface;
# this is the folder (within WEBSERVER_PATH) where those files are placed
WEBSERVER_IMAGE_FOLDER = 'images/'
# Multiple histograms can be generated, i.e. for airline, aircraft, day of week, etc.
# The output files are named by the prefix & suffix, i.e.: prefix + type + . + suffix,
# as in histogram_aircraft.png. These names match up to the names expected by the html
# page that displays the images. Also, note that the suffix is interpreted by matplotlib
# to identify the image format to create.
HISTOGRAM_IMAGE_PREFIX = 'histogram_'
HISTOGRAM_IMAGE_SUFFIX = 'png'
# For those of the approximately ten different types of histograms _not_ generated,
# an empty image is copied into the location expected by the webpage instead; this is
# the location of that "empty" image file.
HISTOGRAM_EMPTY_IMAGE_FILE = 'empty.png'
# This file indicates a pending request for histograms - either png, text-based, or
<----SKIPPED LINES---->
AIRCRAFT_LENGTH['Pilatus PC-12 (single-turboprop)'] = 14.4
def Log(message, file=None, rolling=None):
"""Write a message to a logfile along with a timestamp.
Args:
message: string message to write
file: string representing file name and, if needed, path to the file to write to
rolling: name of file that will keep only the last n files of file
"""
# can't define as a default parameter because LOGFILE name is potentially
# modified based on SIMULATION flag
if not file:
file = LOGFILE
# special case: for the main logfile, we always keep a rolling log
if not rolling and file == LOGFILE:
rolling = ROLLING_LOGFILE
try:
with open(file, 'a') as f:
# by excluding the timestamp, file diffs become easier between runs
if not SIMULATION or file == LOGFILE:
f.write('='*80+'\n')
f.write(str(datetime.datetime.now(TZ))+'\n')
f.write('\n')
f.write(str(message)+'\n')
except IOError:
Log('Unable to append to ' + file)
if rolling:
existing_log_lines = ReadFile(file).splitlines()
with open(rolling, 'w') as f:
f.write('\n'.join(existing_log_lines[-1000:]))
def LogTimes(times):
"""Logs elapsed time messages from a list of epochs."""
msg = ''
for n, t in enumerate(times[:-1]):
msg += '%.2fs to get from reading %d to reading %s\n' % (times[n + 1] - t, n, n + 1)
Log(msg)
def MaintainRollingWebLog(message, max_count, filename=None):
"""Maintains a rolling text file of at most max_count printed messages.
Newest data at top and oldest data at the end, of at most max_count messages,
where the delimiter between each message is identified by a special fixed string.
Args:
message: text message to prepend to the file.
max_count: maximum number of messages to keep in the file; the max_count+1st message
is deleted.
<----SKIPPED LINES---->
version_name = python_prefix + last_modified_suffix + file_extension
version_path = os.path.join(VERSION_REPOSITORY, version_name)
if not os.path.exists(version_path):
shutil.copyfile(live_path, version_path)
return version_name
global VERSION_MESSAGEBOARD
global VERSION_ARDUINO
VERSION_MESSAGEBOARD = MakeCopy('messageboard')
VERSION_ARDUINO = MakeCopy('arduino')
def main():
"""Traffic cop between incoming radio flight messages, configuration, and messageboard.
This is the main logic, checking for new flights, augmenting the radio signal with
additional web-scraped data, and generating messages in a form presentable to the
messageboard.
"""
VersionControl()
# Since this clears log files, it should occur first before we start logging
if '-s' in sys.argv:
global SIMULATION_COUNTER
SimulationSetup()
last_heartbeat_time = HeartbeatRestart()
init_timing = [time.time()] # time 0
# This flag slows down simulation time around a flight, great for debugging the arduinos
simulation_slowdown = bool('-f' in sys.argv)
# Redirect any errors to a log file instead of the screen, and add a datestamp
if not SIMULATION:
sys.stderr = open(STDERR_FILE, 'a')
Log('', STDERR_FILE)
init_timing.append(time.time()) # time 1
Log('Starting up process %d' % os.getpid())
<----SKIPPED LINES---->
|