01234567890123456789012345678901234567890123456789012345678901234567890123456789
2425262728293031323334353637383940414243444546474849505152535455565758596061626364 48144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836 483748384839484048414842 4843484448454846 484748484849 485048514852485348544855 48564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881 48954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935 51005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143 55965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636 572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759 |
<----SKIPPED LINES---->
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
SHUTDOWN_SIGNAL = False
REBOOT_SIGNAL = False
SIMULATION = False
SIMULATION_COUNTER = 0
SIMULATION_PREFIX = 'SIM_'
PICKLE_DUMP_JSON_FILE = 'pickle/dump_json.pk'
PICKLE_FA_JSON_FILE = 'pickle/fa_json.pk'
DUMP_JSONS = None # loaded only if in simulation mode
FA_JSONS = None # loaded only if in simulation mode
HOME_LAT = 37.64406
HOME_LON = -122.43463
HOME = (HOME_LAT, HOME_LON) # lat / lon tuple of antenna
HOME_ALT = 29 #altitude in meters
RADIUS = 6371.0e3 # radius of earth in meters
FEET_IN_METER = 3.28084
FEET_IN_MILE = 5280
METERS_PER_SECOND_IN_KNOTS = 0.514444
<----SKIPPED LINES---->
"""
if SIMULATION_COUNTER == 0:
return True
(this_json, unused_now) = DUMP_JSONS[SIMULATION_COUNTER]
(last_json, unused_now) = DUMP_JSONS[SIMULATION_COUNTER - 1]
return this_json != last_json
def CheckRebootNeeded(startup_time, message_queue, json_desc_dict, configuration):
"""Reboot based on duration instance has been running.
Reboot needed in one of the following situations:
- All quiet: if running for over 24 hours and all is quiet (message queue empty and
no planes in radio).
- Mostly quiet: if running for over 36 hours and message queue is empty and it's 3a.
- Reboot requested via html form.
Also checks if reset requested via html form.
"""
reboot = False
end_process = False
running_hours = (time.time() - startup_time) / SECONDS_IN_HOUR
if (
running_hours >= HOURS_IN_DAY and
not message_queue and
not json_desc_dict.get('radio_range_flights')):
reboot = True
Log('All quiet reboot needed after running for %.2f hours' % running_hours)
if (
running_hours > HOURS_IN_DAY * 1.5 and
not message_queue and
int(EpochDisplayTime(time.time(), '%-H')) >= 3):
reboot = True
Log('Early morning reboot needed after running for %.2f hours' % running_hours)
if 'soft_reboot' in configuration:
reboot = True
Log('Soft reboot requested via web form')
RemoveSetting(configuration, 'soft_reboot')
if 'end_process' in configuration:
Log('Process end requested via web form')
RemoveSetting(configuration, 'end_process')
end_process = True
if reboot or end_process:
global SHUTDOWN_SIGNAL
SHUTDOWN_SIGNAL = True
return reboot
def InterruptRebootFromButton():
"""Sets flag so that the main loop will terminate when it completes the iteration.
This function is only triggered by an physical button press.
"""
global SHUTDOWN_SIGNAL
SHUTDOWN_SIGNAL = True
global REBOOT_SIGNAL
REBOOT_SIGNAL = True
RPi.GPIO.output(GPIO_SOFT_RESET[1], False) # signal that reset received
Log('Soft reboot requested by button push')
def InterruptShutdownFromSignal(signalNumber, unused_frame):
<----SKIPPED LINES---->
def PerformGracefulShutdown(queues, shutdown, reboot):
"""Complete the graceful shutdown process by cleaning up.
Args:
queues: iterable of queues shared with child processes to be closed
shutdown: tuple of shared flags with child processes to initiate shutdown in children
reboot: boolean indicating whether we should trigger a reboot
"""
reboot_msg = ''
if reboot:
reboot_msg = ' and rebooting'
Log('Shutting down self (%d)%s' % (os.getpid(), reboot_msg))
for q in queues:
q.close()
for v in shutdown: # send the shutdown signal to child processes
v.value = 1
if RASPBERRY_PI:
RPi.GPIO.cleanup()
UpdateDashboard(True)
if reboot or REBOOT_SIGNAL:
time.sleep(10) # wait 10 seconds for children to shut down as well
os.system('sudo reboot')
sys.exit()
def FindRunningParents():
"""Returns list of proc ids of processes with identically-named python file running.
In case there are multiple children processes spawned with the same name, such as via
multiprocessing, this will only return the parent id (since a killed child process
will likely just be respawned).
"""
this_process_id = os.getpid()
this_process_name = os.path.basename(sys.argv[0])
pids = []
pid_pairs = []
for proc in psutil.process_iter():
try:
<----SKIPPED LINES---->
with the attributes to follow thru.
Possible commands are updating a GPIO pin, replaying a recent flight to the board,
generating a histogram, or updating the saved settings.
Args:
q: multiprocessing queue provided to both the Arduino processes
flights: list of flights
configuration: dictionary of settings
message_queue: current message queue
next_message_time: epoch of the next message to display to screen
Returns:
A 2-tuple of the (possibly-updated) message_queue and next_message_time.
"""
while not q.empty():
command, args = q.get()
if command == 'pin':
UpdateStatusLight(*args)
str_args = [str(a) for a in args]
msg = '|'.join(str_args) #TODO
Log(msg)
elif command == 'replay':
# a command might request info about flight to be (re)displayed, irrespective of
# whether the screen is on; if so, let's put that message at the front of the message
# queue, and delete any subsequent messages in queue because presumably the button
# was pushed either a) when the screen was off (so no messages in queue), or b)
# because the screen was on, but the last flight details got lost after other screens
# that we're no longer interested in
messageboard_flight_index = IdentifyFlightDisplayed(
flights, configuration, display_all_hours=True)
if messageboard_flight_index is not None:
message_queue = [m for m in message_queue if m[0] != FLAG_MSG_INTERESTING]
flight_message = CreateMessageAboutFlight(flights[messageboard_flight_index])
message_queue = [(FLAG_MSG_FLIGHT, flight_message)]
next_message_time = time.time()
elif command == 'histogram':
if not flights:
Log('Histogram requested by remote %s but no flights in memory' % str(args))
else:
<----SKIPPED LINES---->
persistent_path = {}
histogram = {}
# Next up to print is index 0; this is a list of tuples:
# tuple element#1: flag indicating the type of message that this is
# tuple element#2: the message itself
message_queue = []
next_message_time = time.time()
# We repeat the loop every x seconds; this ensures that if the processing time is long,
# we don't wait another x seconds after processing completes
next_loop_time = time.time() + LOOP_DELAY_SECONDS
# These files are read only if the version on disk has been modified more recently
# than the last time it was read
last_dump_json_timestamp = 0
WaitUntilKillComplete(already_running_ids)
Log('Finishing initialization of %d; starting radio polling loop' % os.getpid())
while not SIMULATION or SIMULATION_COUNTER < len(DUMP_JSONS):
last_heartbeat_time = Heartbeat(last_heartbeat_time)
new_configuration = ReadAndParseSettings(CONFIG_FILE)
CheckForNewFilterCriteria(configuration, new_configuration, message_queue, flights)
configuration = new_configuration
ResetLogs(configuration) # clear the logs if requested
# if this is a SIMULATION, then process every diff dump. But if it isn't a simulation,
# then only read & do related processing for the next dump if the last-modified
# timestamp indicates the file has been updated since it was last read.
tmp_timestamp = 0
if not SIMULATION:
dump_json_exists = os.path.exists(DUMP_JSON_FILE)
if dump_json_exists:
tmp_timestamp = os.path.getmtime(DUMP_JSON_FILE)
if (SIMULATION and DumpJsonChanges()) or (
not SIMULATION and dump_json_exists and tmp_timestamp > last_dump_json_timestamp):
<----SKIPPED LINES---->
# lingering histogram file at the time of history restart.
if histogram and not flights:
Log('Histogram requested (%s) but no flights in memory' % histogram)
if histogram and flights:
message_queue.extend(TriggerHistograms(flights, histogram))
# check time & if appropriate, display next message from queue
next_message_time = ManageMessageQueue(message_queue, next_message_time, configuration)
reboot = CheckRebootNeeded(startup_time, message_queue, json_desc_dict, configuration)
CheckTemperature()
if not SIMULATION:
time.sleep(max(0, next_loop_time - time.time()))
next_loop_time = time.time() + LOOP_DELAY_SECONDS
else:
SIMULATION_COUNTER += 1
if simulation_slowdown:
SimulationSlowdownNearFlight(flights, persistent_nearby_aircraft)
if SHUTDOWN_SIGNAL: # do a graceful exit
PerformGracefulShutdown((to_remote_q, to_servo_q, to_main_q), shutdown, reboot)
if SIMULATION:
SimulationEnd(message_queue, flights)
PerformGracefulShutdown((to_remote_q, to_servo_q, to_main_q), shutdown, reboot)
if __name__ == "__main__":
#interrupt, as in ctrl-c
signal.signal(signal.SIGINT, InterruptShutdownFromSignal) #TODO
#terminate, when another instance found or via kill
signal.signal(signal.SIGTERM, InterruptShutdownFromSignal) #TODO
if '-i' in sys.argv:
BootstrapInsightList()
else:
main()
|
01234567890123456789012345678901234567890123456789012345678901234567890123456789
2425262728293031323334353637383940414243444546474849505152535455565758596061626364 481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858 4859486048614862486348644865 48664867486848694870487148724873487448754876487748784879488048814882488348844885 48994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939 51045105510651075108510951105111511251135114511551165117511851195120512151225123 51245125512651275128512951305131513251335134513551365137513851395140514151425143 55965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636 57215722572357245725572657275728572957305731573257335734573557365737573857395740 57415742574357445745574657475748574957505751575257535754575557565757 |
<----SKIPPED LINES---->
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
SHUTDOWN_SIGNAL = ''
REBOOT_SIGNAL = False
SIMULATION = False
SIMULATION_COUNTER = 0
SIMULATION_PREFIX = 'SIM_'
PICKLE_DUMP_JSON_FILE = 'pickle/dump_json.pk'
PICKLE_FA_JSON_FILE = 'pickle/fa_json.pk'
DUMP_JSONS = None # loaded only if in simulation mode
FA_JSONS = None # loaded only if in simulation mode
HOME_LAT = 37.64406
HOME_LON = -122.43463
HOME = (HOME_LAT, HOME_LON) # lat / lon tuple of antenna
HOME_ALT = 29 #altitude in meters
RADIUS = 6371.0e3 # radius of earth in meters
FEET_IN_METER = 3.28084
FEET_IN_MILE = 5280
METERS_PER_SECOND_IN_KNOTS = 0.514444
<----SKIPPED LINES---->
"""
if SIMULATION_COUNTER == 0:
return True
(this_json, unused_now) = DUMP_JSONS[SIMULATION_COUNTER]
(last_json, unused_now) = DUMP_JSONS[SIMULATION_COUNTER - 1]
return this_json != last_json
def CheckRebootNeeded(startup_time, message_queue, json_desc_dict, configuration):
"""Reboot based on duration instance has been running.
Reboot needed in one of the following situations:
- All quiet: if running for over 24 hours and all is quiet (message queue empty and
no planes in radio).
- Mostly quiet: if running for over 36 hours and message queue is empty and it's 3a.
- Reboot requested via html form.
Also checks if reset requested via html form.
"""
reboot = False
global SHUTDOWN_SIGNAL
running_hours = (time.time() - startup_time) / SECONDS_IN_HOUR
if (
running_hours >= HOURS_IN_DAY and
not message_queue and
not json_desc_dict.get('radio_range_flights')):
reboot = True
Log('All quiet reboot needed after running for %.2f hours' % running_hours)
if (
running_hours > HOURS_IN_DAY * 1.5 and
not message_queue and
int(EpochDisplayTime(time.time(), '%-H')) >= 3):
msg = 'Early morning reboot needed after running for %.2f hours' % running_hours
SHUTDOWN_SIGNAL = msg
Log(msg)
reboot = True
if 'soft_reboot' in configuration:
msg = 'Soft reboot requested via web form'
SHUTDOWN_SIGNAL = msg
Log(msg)
reboot = True
RemoveSetting(configuration, 'soft_reboot')
if 'end_process' in configuration:
msg = 'Process end requested via web form'
SHUTDOWN_SIGNAL = msg
Log(msg)
RemoveSetting(configuration, 'end_process')
return reboot
def InterruptRebootFromButton():
"""Sets flag so that the main loop will terminate when it completes the iteration.
This function is only triggered by an physical button press.
"""
global SHUTDOWN_SIGNAL
SHUTDOWN_SIGNAL = True
global REBOOT_SIGNAL
REBOOT_SIGNAL = True
RPi.GPIO.output(GPIO_SOFT_RESET[1], False) # signal that reset received
Log('Soft reboot requested by button push')
def InterruptShutdownFromSignal(signalNumber, unused_frame):
<----SKIPPED LINES---->
def PerformGracefulShutdown(queues, shutdown, reboot):
"""Complete the graceful shutdown process by cleaning up.
Args:
queues: iterable of queues shared with child processes to be closed
shutdown: tuple of shared flags with child processes to initiate shutdown in children
reboot: boolean indicating whether we should trigger a reboot
"""
reboot_msg = ''
if reboot:
reboot_msg = ' and rebooting'
Log('Shutting down self (%d)%s' % (os.getpid(), reboot_msg))
for q in queues:
q.close()
for v in shutdown: # send the shutdown signal to child processes
v.value = 1
if RASPBERRY_PI:
RPi.GPIO.cleanup()
UpdateDashboard(True, failure_message=SHUTDOWN_SIGNAL)
if reboot or REBOOT_SIGNAL:
time.sleep(10) # wait 10 seconds for children to shut down as well
os.system('sudo reboot')
sys.exit()
def FindRunningParents():
"""Returns list of proc ids of processes with identically-named python file running.
In case there are multiple children processes spawned with the same name, such as via
multiprocessing, this will only return the parent id (since a killed child process
will likely just be respawned).
"""
this_process_id = os.getpid()
this_process_name = os.path.basename(sys.argv[0])
pids = []
pid_pairs = []
for proc in psutil.process_iter():
try:
<----SKIPPED LINES---->
with the attributes to follow thru.
Possible commands are updating a GPIO pin, replaying a recent flight to the board,
generating a histogram, or updating the saved settings.
Args:
q: multiprocessing queue provided to both the Arduino processes
flights: list of flights
configuration: dictionary of settings
message_queue: current message queue
next_message_time: epoch of the next message to display to screen
Returns:
A 2-tuple of the (possibly-updated) message_queue and next_message_time.
"""
while not q.empty():
command, args = q.get()
if command == 'pin':
UpdateStatusLight(*args)
elif command == 'replay':
# a command might request info about flight to be (re)displayed, irrespective of
# whether the screen is on; if so, let's put that message at the front of the message
# queue, and delete any subsequent messages in queue because presumably the button
# was pushed either a) when the screen was off (so no messages in queue), or b)
# because the screen was on, but the last flight details got lost after other screens
# that we're no longer interested in
messageboard_flight_index = IdentifyFlightDisplayed(
flights, configuration, display_all_hours=True)
if messageboard_flight_index is not None:
message_queue = [m for m in message_queue if m[0] != FLAG_MSG_INTERESTING]
flight_message = CreateMessageAboutFlight(flights[messageboard_flight_index])
message_queue = [(FLAG_MSG_FLIGHT, flight_message)]
next_message_time = time.time()
elif command == 'histogram':
if not flights:
Log('Histogram requested by remote %s but no flights in memory' % str(args))
else:
<----SKIPPED LINES---->
persistent_path = {}
histogram = {}
# Next up to print is index 0; this is a list of tuples:
# tuple element#1: flag indicating the type of message that this is
# tuple element#2: the message itself
message_queue = []
next_message_time = time.time()
# We repeat the loop every x seconds; this ensures that if the processing time is long,
# we don't wait another x seconds after processing completes
next_loop_time = time.time() + LOOP_DELAY_SECONDS
# These files are read only if the version on disk has been modified more recently
# than the last time it was read
last_dump_json_timestamp = 0
WaitUntilKillComplete(already_running_ids)
Log('Finishing initialization of %d; starting radio polling loop' % os.getpid())
while (not SIMULATION or SIMULATION_COUNTER < len(DUMP_JSONS)) and not SHUTDOWN_SIGNAL:
last_heartbeat_time = Heartbeat(last_heartbeat_time)
new_configuration = ReadAndParseSettings(CONFIG_FILE)
CheckForNewFilterCriteria(configuration, new_configuration, message_queue, flights)
configuration = new_configuration
ResetLogs(configuration) # clear the logs if requested
# if this is a SIMULATION, then process every diff dump. But if it isn't a simulation,
# then only read & do related processing for the next dump if the last-modified
# timestamp indicates the file has been updated since it was last read.
tmp_timestamp = 0
if not SIMULATION:
dump_json_exists = os.path.exists(DUMP_JSON_FILE)
if dump_json_exists:
tmp_timestamp = os.path.getmtime(DUMP_JSON_FILE)
if (SIMULATION and DumpJsonChanges()) or (
not SIMULATION and dump_json_exists and tmp_timestamp > last_dump_json_timestamp):
<----SKIPPED LINES---->
# lingering histogram file at the time of history restart.
if histogram and not flights:
Log('Histogram requested (%s) but no flights in memory' % histogram)
if histogram and flights:
message_queue.extend(TriggerHistograms(flights, histogram))
# check time & if appropriate, display next message from queue
next_message_time = ManageMessageQueue(message_queue, next_message_time, configuration)
reboot = CheckRebootNeeded(startup_time, message_queue, json_desc_dict, configuration)
CheckTemperature()
if not SIMULATION:
time.sleep(max(0, next_loop_time - time.time()))
next_loop_time = time.time() + LOOP_DELAY_SECONDS
else:
SIMULATION_COUNTER += 1
if simulation_slowdown:
SimulationSlowdownNearFlight(flights, persistent_nearby_aircraft)
if SIMULATION:
SimulationEnd(message_queue, flights)
PerformGracefulShutdown((to_remote_q, to_servo_q, to_main_q), shutdown, reboot)
if __name__ == "__main__":
#interrupt, as in ctrl-c
signal.signal(signal.SIGINT, InterruptShutdownFromSignal) #TODO
#terminate, when another instance found or via kill
signal.signal(signal.SIGTERM, InterruptShutdownFromSignal) #TODO
if '-i' in sys.argv:
BootstrapInsightList()
else:
main()
|