Module PiMan
Expand source code
# ****************************************************
# Copyright: 2020 Team Visualizer (Carlos Miguel Sayao, Connor Bettermann, Issac Greenfield, Madeleine Elyea, Tanner Sundwall, Ted Moore, Prerna Agarwal)
# License: MIT
# ****************************************************
# Purpose: Global command buffer queue data structure.
# Purpose: manages all functionality of the Pi and our program’s processes post-boot
# Retains boolean representation of status as “Leader” otherwise is “Follower”
# If “leadc_queue.IsLeader == True), listens for mouse inputs and sends commands out through ethernet
# If “followc_queue.IsLeader == False), listens for ethernet data and follows those commands
# Sources: DocStrings/Comments, https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html
# Python Coding conventions, https://www.python.org/dev/peps/pep-0008/#source-file-encoding
# ****************************************************
import os
import sys
import time
import threading
import subprocess
from datetime import datetime
from datetime import timedelta
import concurrent.futures
from ErrorHandler import ErrorHandler
from Commands import Commands
from UserInput import UserInput
from VLCMan import VLCMan
from EtherTalk import EtherTalk
from MessageMan import MessageMan
from constants import *
from OrderCode import *
from Device import Device
import Commands as c_queue
c_queue.init()
class PiMan:
def __init__(self, error_handler=None):
self.is_shutdown_time = False
self.leader_response_time = None
self.has_follower = False
self.eh = error_handler
self.timestamp = datetime.now() + timedelta(seconds=IDLE_SEND_TIME)
self.has_checked_ping = False
def waiting_loop(self) -> bool:
""" This is the main-loop, active function for this program. It checks
for input from a user or other RasPis, and calls appropriate functions
when needed.
Args:
None
Returns:
bool: The return value indicates if everything closed properly.
True for success, False otherwise.
"""
# Get object resources up and running
et = EtherTalk(error_handler=self.eh, verbose=False)
mm = MessageMan(error_handler=self.eh)
vlc = VLCMan(VIDEOS_PATH, PLAYLIST_NAME, "", error_handler=self.eh)
device = Device().has_device
if not self.get_started(vlc, et, mm, device):
# Problem at startup, restart from scratch
os.system('sudo shutdown -r now')
self.time = datetime.now().second
while not self.is_shutdown_time:
# If leader check for user input and send commands
if c_queue.IsLeader == True:
device = Device().has_device
self.next_command(vlc, et, mm, device)
# Check to make sure a follower is still on the network
if datetime.now().second > 57 and not self.has_checked_ping:
# Loop for three seconds, pinging the follower for a response
loop_end_time = datetime.now() + timedelta(seconds=3)
self.has_checked_ping = True
while True:
if self.send_orders("", et, mm, 0, OrderCode.PING_RESPONSE):
break
elif datetime.now() >= loop_end_time:
self.has_follower = False
break
elif datetime.now().second > 53 and datetime.now().second <= 56:
self.has_checked_ping = False
else: # RasPi is the follower
# Check if we need to become leader, dequeue, execute commands
self.next_command_follower(mm, vlc)
# Check time for shutdown
self.is_shutdown_time = self.check_time()
# Out of the Loop means it is Shutdown time for all units
# Send shutdown signal 5 times, in case of false negative
for i in range(4):
if self.send_orders("", et, mm, 0, OrderCode.SHUT_DOWN):
break
time.sleep(5)
# Then shutdown
self.shutdown()
def get_started(self, vlc, et, mm, device) -> bool:
""" Responsible for getting ready all pre-visual settings
Args:
vlc (VLCMan): VLC manager with current playlist information
et (EtherTalk): Communicator to other Raspis on the network
mm (MessageMan): Information manager to compose/parse communications
Returns:
bool: True indicates that this unit is the leader, and needs to
send out the playlist it created, False indicates it is the
follower, and will have waited for the new playlist within this
function.
"""
try:
os.remove(PLAYLIST_NAME)
except:
pass
try:
# Check for usb device, this unit should be the Follower if
# not found and no one else is on the network. Performed for 3 minutes.
if not device:
if et.ping() == 0:
print("[!] I am the follower (งツ)ว")
c_queue.IsLeader = False
self.listener(vlc, et)
else:
print("[!] I am the leader ᕕ( ᐛ )ᕗ")
c_queue.IsLeader = True
UserInput(self.eh).input_listener()
else:
print("[!] I am the leader ᕕ( ᐛ )ᕗ")
c_queue.IsLeader = True
UserInput(self.eh).input_listener()
except Exception as e: # Something went wrong with the startup
self.eh.log_error(e)
return False
# Playlist setup logic. Dependant on isLeader status
try:
# This is the Leader RasPi
if c_queue.IsLeader == True:
self.create_playlist(vlc)
else:
vlc.start_vlc()
except Exception as e:
self.eh.log_error(e)
return False
# This point indicates no major exceptions have occurred for either the
# Leader or the Follower
return True
def shutdown(self):
""" Safely exits and shuts down the RasPi unit
Args:
None
Returns:
None
"""
subprocess.Popen("sudo shutdown -P now", shell=True)
def check_time(self) -> bool:
""" Keeps track of the time for autoshutdown and syncing with other
RasPis on the network
Args:
None
Returns:
bool: True means the Pi should shutdown, False the program should
continue.
"""
# NOTE: Remove after testing. Returns False so that it doesn't shutdown with testing
return False
hour = datetime.now().hour
if hour >= SEVEN_AM and hour < SEVEN_PM:
return True
else:
return False
def exec_command(self, vlc, time_start, command) -> bool:
""" Executes the argument `command` at the future start time.
Args:
vlc (VLCMan): VLC manager with current playlist information
time_start (datetime.datetime): Future time to execute the order at
command (OrderCode): Command to execute
Returns:
bool: True if successful, False if command is NONE or unsuccessful.
"""
# Wait until the exact time we need to do the command
while datetime.now() < time_start:
pass
if command == OrderCode.SHUT_DOWN:
self.is_shutdown_time = True
return True
elif command == OrderCode.PLAYLIST:
vlc.start_vlc()
return True
elif command == OrderCode.SLOW_DOWN:
vlc.set_rate(-.1)
return True
elif command == OrderCode.SPEED_UP:
vlc.set_rate(.1)
return True
elif command == OrderCode.LOOP_PLAY:
vlc.toggle_loop_video()
return True
elif command == OrderCode.RESUME_LIST:
vlc.reset_rate()
vlc.next_video()
return True
elif command == OrderCode.RESUME_SPEED:
vlc.reset_rate()
return True
elif command == OrderCode.PAUSE_PLAYBACK:
vlc.play_pause_video()
return True
elif command == OrderCode.NEXT_VIDEO:
vlc.next_video()
vlc.set_loop_playlist()
return True
elif command == OrderCode.IDLE:
return True
elif command == OrderCode.NONE:
return False
return False
def create_playlist(self, vlc):
""" Update playlist information and package it in the message manager
for transmission to other networked RasPis.
Args:
mm (MessageMan): The order information manager to compose and
parse communications
vlc (VLCMan): The VLC manager with current playlist and
song information
Returns:
None
"""
try:
if vlc.randomize_videos():
print("Videos Randomized")
if vlc.create_playlist():
print("Playlist Created")
if vlc.start_vlc():
print("VLC Started")
except Exception as e:
self.eh.log_error(e)
raise
def send_orders(self, message, et, mm, time_start, command) -> bool:
""" Issue instructions to other RasPis on the network
Args:
message (str): unused
et (EtherTalk): The ethernet communication handler instance
mm (MessageMan): The order information manager to compose and
parse communications
time_start (int): time offset for when the command will start
command (enum OrderCode): command to be sent
Returns:
bool: True indicates that another RasPi has responded to the order,
False indicates that either there was no answer,
or an unrecoverable error has occurred for the other unit.
"""
# Create dict to send
message_dict = mm.compose_message(message, time_start, command)
if et.send(message_dict) == 0:
return True
return False
def next_command(self, vlc, et, mm, device):
""" Issue instructions to other host on the network
Args:
vlc (VLCMan): The VLC manager with current playlist and
song information
et (EtherTalk): The ethernet communication handler instance
mm (MessageMan): The order information manager to compose and
parse communications
Returns:
bool: True indicates that another RasPi has responded to the order,
False indicates that either there was no answer,
or an unrecoverable error has occurred for the other unit.
"""
try:
# No mouse input and no queue, listen for another leader and give up Leadership
if c_queue.CommandQueue.empty() and not device:
if et.ping(45) == 0:
print("[!] I am the follower (งツ)ว")
c_queue.IsLeader = False
self.listener(vlc, et)
Commands(self.eh).flush_queue()
return
# If we have something in our queue
elif not c_queue.CommandQueue.empty():
# Grab next dict head from queue
command_header = Commands(self.eh).dequeue()
code = command_header['code']
# If this comes back false, we have no follower
if self.send_orders("", et, mm, START_TIME, OrderCode(command_header['code'])):
# If we didn't already have a follower, send the playlist
if not self.has_follower:
# Generate header to send playlist
header = mm.compose_message(
"", PLAYLIST_START_TIME, OrderCode.SEND_FILE)
et.send_file(header, PLAYLIST_NAME)
Commands(self.eh).flush_queue()
code = OrderCode.PLAYLIST
mm.start_time += 0.6
self.has_follower = True
# We are leader, if execution is OK,
if self.exec_command(vlc, datetime.fromtimestamp(mm.start_time), code):
self.leader_response_time = None
# Otherwise, send IDLE to follower to indicate we are still alive every 10 seconds
elif self.timestamp < datetime.now():
self.timestamp = datetime.now() + timedelta(seconds=IDLE_SEND_TIME)
code = OrderCode.IDLE
# If we are successfully sending the idle, we still have follower
if self.send_orders("", et, mm, START_TIME, code):
# If we didn't already have a follower, send the playlist
if not self.has_follower:
# Generate header to send playlist
header = mm.compose_message(
"", PLAYLIST_START_TIME, OrderCode.SEND_FILE)
et.send_file(header, PLAYLIST_NAME)
Commands(self.eh).flush_queue()
code = OrderCode.PLAYLIST
mm.start_time += 0.6
self.has_follower = True
if self.exec_command(vlc, datetime.fromtimestamp(mm.start_time), code):
self.leader_response_time = None
except Exception as e:
self.eh.log_error(e)
raise
def listener(self, vlc, et):
""" Follower wrapper to spawn listener thread
Args:
et (EtherTalk): The Communicator to other Raspis on the network
vlc (VLCMan): VLC manager with current playlist information
Returns:
None
"""
thread = threading.Thread(
target=self.__listener, args=(vlc, et), daemon=True)
thread.start()
def __listener(self, vlc, et):
""" Follower unit listens for instructions from Leader
Args:
et (EtherTalk): The Communicator to other Raspis on the network
vlc (VLCMan): VLC manager with current playlist information
Returns:
None
"""
try:
# Stop this thread if you are no longer a follower
while c_queue.IsLeader == False:
# Receive status and header to display
status, header = et.listen(path=WRITE_PATH)
if status == 0:
# If we're receiving a playlist, we want to clear our queue
# and immediately play playlist at start_time
if header['code'] == OrderCode.SEND_FILE:
header['code'] = OrderCode.PLAYLIST
Commands(self.eh).flush_queue()
# Enqueue the new command
Commands(self.eh).enqueue(header)
except Exception as e:
self.eh.log_error(e)
raise
def next_command_follower(self, mm, vlc):
""" Checks to see if follower needs to take leader status. Executes
next command in queue regardless.
Args:
mm (MessageMan): The message interpretor for et communications
vlc (VLCMan): VLC manager with current playlist information
Returns:
None
"""
try:
# I want to check for empty queue and not add "NONE's" on the queue.
# I believe it is causing syncing problems, so only exec_command queue's
# with commands in them, otherwise we are either not getting commands or
# the commands being sent are taking awhile to get here/being processed.
# So I set the takeover time to be 3 mins in the future from when we have
# an empty queue.
if c_queue.CommandQueue.empty() and self.leader_response_time is None:
self.leader_response_time = datetime.now(
) + timedelta(minutes=FOLLOWER_TAKEOVER_WAIT)
elif not c_queue.CommandQueue.empty():
# Dequeue next command header to be executed
command_header = Commands(self.eh).dequeue()
command_code = command_header['code']
command_start = datetime.fromtimestamp(
command_header['start_time'])
# Execute the command. NONE and an error will return False.
if self.exec_command(vlc, command_start, command_code):
self.leader_response_time = None
# We have waiting for a response from the leader and nothing.
# So become the leader and start up the input listender.
elif self.leader_response_time is not None and self.leader_response_time < datetime.now():
print("[!] I am the leader ᕕ( ᐛ )ᕗ")
# this should prob just restart the code from main....
self.leader_response_time = None
Commands(self.eh).flush_queue()
c_queue.IsLeader = True
UserInput(self.eh).input_listener()
except Exception as e:
self.eh.log_error(e)
raise
Classes
class PiMan (error_handler=None)
-
Expand source code
class PiMan: def __init__(self, error_handler=None): self.is_shutdown_time = False self.leader_response_time = None self.has_follower = False self.eh = error_handler self.timestamp = datetime.now() + timedelta(seconds=IDLE_SEND_TIME) self.has_checked_ping = False def waiting_loop(self) -> bool: """ This is the main-loop, active function for this program. It checks for input from a user or other RasPis, and calls appropriate functions when needed. Args: None Returns: bool: The return value indicates if everything closed properly. True for success, False otherwise. """ # Get object resources up and running et = EtherTalk(error_handler=self.eh, verbose=False) mm = MessageMan(error_handler=self.eh) vlc = VLCMan(VIDEOS_PATH, PLAYLIST_NAME, "", error_handler=self.eh) device = Device().has_device if not self.get_started(vlc, et, mm, device): # Problem at startup, restart from scratch os.system('sudo shutdown -r now') self.time = datetime.now().second while not self.is_shutdown_time: # If leader check for user input and send commands if c_queue.IsLeader == True: device = Device().has_device self.next_command(vlc, et, mm, device) # Check to make sure a follower is still on the network if datetime.now().second > 57 and not self.has_checked_ping: # Loop for three seconds, pinging the follower for a response loop_end_time = datetime.now() + timedelta(seconds=3) self.has_checked_ping = True while True: if self.send_orders("", et, mm, 0, OrderCode.PING_RESPONSE): break elif datetime.now() >= loop_end_time: self.has_follower = False break elif datetime.now().second > 53 and datetime.now().second <= 56: self.has_checked_ping = False else: # RasPi is the follower # Check if we need to become leader, dequeue, execute commands self.next_command_follower(mm, vlc) # Check time for shutdown self.is_shutdown_time = self.check_time() # Out of the Loop means it is Shutdown time for all units # Send shutdown signal 5 times, in case of false negative for i in range(4): if self.send_orders("", et, mm, 0, OrderCode.SHUT_DOWN): break time.sleep(5) # Then shutdown self.shutdown() def get_started(self, vlc, et, mm, device) -> bool: """ Responsible for getting ready all pre-visual settings Args: vlc (VLCMan): VLC manager with current playlist information et (EtherTalk): Communicator to other Raspis on the network mm (MessageMan): Information manager to compose/parse communications Returns: bool: True indicates that this unit is the leader, and needs to send out the playlist it created, False indicates it is the follower, and will have waited for the new playlist within this function. """ try: os.remove(PLAYLIST_NAME) except: pass try: # Check for usb device, this unit should be the Follower if # not found and no one else is on the network. Performed for 3 minutes. if not device: if et.ping() == 0: print("[!] I am the follower (งツ)ว") c_queue.IsLeader = False self.listener(vlc, et) else: print("[!] I am the leader ᕕ( ᐛ )ᕗ") c_queue.IsLeader = True UserInput(self.eh).input_listener() else: print("[!] I am the leader ᕕ( ᐛ )ᕗ") c_queue.IsLeader = True UserInput(self.eh).input_listener() except Exception as e: # Something went wrong with the startup self.eh.log_error(e) return False # Playlist setup logic. Dependant on isLeader status try: # This is the Leader RasPi if c_queue.IsLeader == True: self.create_playlist(vlc) else: vlc.start_vlc() except Exception as e: self.eh.log_error(e) return False # This point indicates no major exceptions have occurred for either the # Leader or the Follower return True def shutdown(self): """ Safely exits and shuts down the RasPi unit Args: None Returns: None """ subprocess.Popen("sudo shutdown -P now", shell=True) def check_time(self) -> bool: """ Keeps track of the time for autoshutdown and syncing with other RasPis on the network Args: None Returns: bool: True means the Pi should shutdown, False the program should continue. """ # NOTE: Remove after testing. Returns False so that it doesn't shutdown with testing return False hour = datetime.now().hour if hour >= SEVEN_AM and hour < SEVEN_PM: return True else: return False def exec_command(self, vlc, time_start, command) -> bool: """ Executes the argument `command` at the future start time. Args: vlc (VLCMan): VLC manager with current playlist information time_start (datetime.datetime): Future time to execute the order at command (OrderCode): Command to execute Returns: bool: True if successful, False if command is NONE or unsuccessful. """ # Wait until the exact time we need to do the command while datetime.now() < time_start: pass if command == OrderCode.SHUT_DOWN: self.is_shutdown_time = True return True elif command == OrderCode.PLAYLIST: vlc.start_vlc() return True elif command == OrderCode.SLOW_DOWN: vlc.set_rate(-.1) return True elif command == OrderCode.SPEED_UP: vlc.set_rate(.1) return True elif command == OrderCode.LOOP_PLAY: vlc.toggle_loop_video() return True elif command == OrderCode.RESUME_LIST: vlc.reset_rate() vlc.next_video() return True elif command == OrderCode.RESUME_SPEED: vlc.reset_rate() return True elif command == OrderCode.PAUSE_PLAYBACK: vlc.play_pause_video() return True elif command == OrderCode.NEXT_VIDEO: vlc.next_video() vlc.set_loop_playlist() return True elif command == OrderCode.IDLE: return True elif command == OrderCode.NONE: return False return False def create_playlist(self, vlc): """ Update playlist information and package it in the message manager for transmission to other networked RasPis. Args: mm (MessageMan): The order information manager to compose and parse communications vlc (VLCMan): The VLC manager with current playlist and song information Returns: None """ try: if vlc.randomize_videos(): print("Videos Randomized") if vlc.create_playlist(): print("Playlist Created") if vlc.start_vlc(): print("VLC Started") except Exception as e: self.eh.log_error(e) raise def send_orders(self, message, et, mm, time_start, command) -> bool: """ Issue instructions to other RasPis on the network Args: message (str): unused et (EtherTalk): The ethernet communication handler instance mm (MessageMan): The order information manager to compose and parse communications time_start (int): time offset for when the command will start command (enum OrderCode): command to be sent Returns: bool: True indicates that another RasPi has responded to the order, False indicates that either there was no answer, or an unrecoverable error has occurred for the other unit. """ # Create dict to send message_dict = mm.compose_message(message, time_start, command) if et.send(message_dict) == 0: return True return False def next_command(self, vlc, et, mm, device): """ Issue instructions to other host on the network Args: vlc (VLCMan): The VLC manager with current playlist and song information et (EtherTalk): The ethernet communication handler instance mm (MessageMan): The order information manager to compose and parse communications Returns: bool: True indicates that another RasPi has responded to the order, False indicates that either there was no answer, or an unrecoverable error has occurred for the other unit. """ try: # No mouse input and no queue, listen for another leader and give up Leadership if c_queue.CommandQueue.empty() and not device: if et.ping(45) == 0: print("[!] I am the follower (งツ)ว") c_queue.IsLeader = False self.listener(vlc, et) Commands(self.eh).flush_queue() return # If we have something in our queue elif not c_queue.CommandQueue.empty(): # Grab next dict head from queue command_header = Commands(self.eh).dequeue() code = command_header['code'] # If this comes back false, we have no follower if self.send_orders("", et, mm, START_TIME, OrderCode(command_header['code'])): # If we didn't already have a follower, send the playlist if not self.has_follower: # Generate header to send playlist header = mm.compose_message( "", PLAYLIST_START_TIME, OrderCode.SEND_FILE) et.send_file(header, PLAYLIST_NAME) Commands(self.eh).flush_queue() code = OrderCode.PLAYLIST mm.start_time += 0.6 self.has_follower = True # We are leader, if execution is OK, if self.exec_command(vlc, datetime.fromtimestamp(mm.start_time), code): self.leader_response_time = None # Otherwise, send IDLE to follower to indicate we are still alive every 10 seconds elif self.timestamp < datetime.now(): self.timestamp = datetime.now() + timedelta(seconds=IDLE_SEND_TIME) code = OrderCode.IDLE # If we are successfully sending the idle, we still have follower if self.send_orders("", et, mm, START_TIME, code): # If we didn't already have a follower, send the playlist if not self.has_follower: # Generate header to send playlist header = mm.compose_message( "", PLAYLIST_START_TIME, OrderCode.SEND_FILE) et.send_file(header, PLAYLIST_NAME) Commands(self.eh).flush_queue() code = OrderCode.PLAYLIST mm.start_time += 0.6 self.has_follower = True if self.exec_command(vlc, datetime.fromtimestamp(mm.start_time), code): self.leader_response_time = None except Exception as e: self.eh.log_error(e) raise def listener(self, vlc, et): """ Follower wrapper to spawn listener thread Args: et (EtherTalk): The Communicator to other Raspis on the network vlc (VLCMan): VLC manager with current playlist information Returns: None """ thread = threading.Thread( target=self.__listener, args=(vlc, et), daemon=True) thread.start() def __listener(self, vlc, et): """ Follower unit listens for instructions from Leader Args: et (EtherTalk): The Communicator to other Raspis on the network vlc (VLCMan): VLC manager with current playlist information Returns: None """ try: # Stop this thread if you are no longer a follower while c_queue.IsLeader == False: # Receive status and header to display status, header = et.listen(path=WRITE_PATH) if status == 0: # If we're receiving a playlist, we want to clear our queue # and immediately play playlist at start_time if header['code'] == OrderCode.SEND_FILE: header['code'] = OrderCode.PLAYLIST Commands(self.eh).flush_queue() # Enqueue the new command Commands(self.eh).enqueue(header) except Exception as e: self.eh.log_error(e) raise def next_command_follower(self, mm, vlc): """ Checks to see if follower needs to take leader status. Executes next command in queue regardless. Args: mm (MessageMan): The message interpretor for et communications vlc (VLCMan): VLC manager with current playlist information Returns: None """ try: # I want to check for empty queue and not add "NONE's" on the queue. # I believe it is causing syncing problems, so only exec_command queue's # with commands in them, otherwise we are either not getting commands or # the commands being sent are taking awhile to get here/being processed. # So I set the takeover time to be 3 mins in the future from when we have # an empty queue. if c_queue.CommandQueue.empty() and self.leader_response_time is None: self.leader_response_time = datetime.now( ) + timedelta(minutes=FOLLOWER_TAKEOVER_WAIT) elif not c_queue.CommandQueue.empty(): # Dequeue next command header to be executed command_header = Commands(self.eh).dequeue() command_code = command_header['code'] command_start = datetime.fromtimestamp( command_header['start_time']) # Execute the command. NONE and an error will return False. if self.exec_command(vlc, command_start, command_code): self.leader_response_time = None # We have waiting for a response from the leader and nothing. # So become the leader and start up the input listender. elif self.leader_response_time is not None and self.leader_response_time < datetime.now(): print("[!] I am the leader ᕕ( ᐛ )ᕗ") # this should prob just restart the code from main.... self.leader_response_time = None Commands(self.eh).flush_queue() c_queue.IsLeader = True UserInput(self.eh).input_listener() except Exception as e: self.eh.log_error(e) raise
Methods
def check_time(self)
-
Keeps track of the time for autoshutdown and syncing with other RasPis on the network
Args
None
Returns
bool
- True means the Pi should shutdown, False the program should
continue.
Expand source code
def check_time(self) -> bool: """ Keeps track of the time for autoshutdown and syncing with other RasPis on the network Args: None Returns: bool: True means the Pi should shutdown, False the program should continue. """ # NOTE: Remove after testing. Returns False so that it doesn't shutdown with testing return False hour = datetime.now().hour if hour >= SEVEN_AM and hour < SEVEN_PM: return True else: return False
def create_playlist(self, vlc)
-
Update playlist information and package it in the message manager for transmission to other networked RasPis.
Args
mm
:MessageMan
- The order information manager to compose and parse communications
vlc
:VLCMan
- The VLC manager with current playlist and song information
Returns
- None
Expand source code
def create_playlist(self, vlc): """ Update playlist information and package it in the message manager for transmission to other networked RasPis. Args: mm (MessageMan): The order information manager to compose and parse communications vlc (VLCMan): The VLC manager with current playlist and song information Returns: None """ try: if vlc.randomize_videos(): print("Videos Randomized") if vlc.create_playlist(): print("Playlist Created") if vlc.start_vlc(): print("VLC Started") except Exception as e: self.eh.log_error(e) raise
def exec_command(self, vlc, time_start, command)
-
Executes the argument
command
at the future start time.Args
vlc
:VLCMan
- VLC manager with current playlist information
time_start
:datetime.datetime
- Future time to execute the order at
command
:OrderCode
- Command to execute
Returns
bool
- True if successful, False if command is NONE or unsuccessful.
Expand source code
def exec_command(self, vlc, time_start, command) -> bool: """ Executes the argument `command` at the future start time. Args: vlc (VLCMan): VLC manager with current playlist information time_start (datetime.datetime): Future time to execute the order at command (OrderCode): Command to execute Returns: bool: True if successful, False if command is NONE or unsuccessful. """ # Wait until the exact time we need to do the command while datetime.now() < time_start: pass if command == OrderCode.SHUT_DOWN: self.is_shutdown_time = True return True elif command == OrderCode.PLAYLIST: vlc.start_vlc() return True elif command == OrderCode.SLOW_DOWN: vlc.set_rate(-.1) return True elif command == OrderCode.SPEED_UP: vlc.set_rate(.1) return True elif command == OrderCode.LOOP_PLAY: vlc.toggle_loop_video() return True elif command == OrderCode.RESUME_LIST: vlc.reset_rate() vlc.next_video() return True elif command == OrderCode.RESUME_SPEED: vlc.reset_rate() return True elif command == OrderCode.PAUSE_PLAYBACK: vlc.play_pause_video() return True elif command == OrderCode.NEXT_VIDEO: vlc.next_video() vlc.set_loop_playlist() return True elif command == OrderCode.IDLE: return True elif command == OrderCode.NONE: return False return False
def get_started(self, vlc, et, mm, device)
-
Responsible for getting ready all pre-visual settings
Args
vlc
:VLCMan
- VLC manager with current playlist information
et
:EtherTalk
- Communicator to other Raspis on the network
mm
:MessageMan
- Information manager to compose/parse communications
Returns
bool
- True indicates that this unit is the leader, and needs to
send
out
the
playlist
it
created
,False
indicates
it
is
the
follower
,and
will
have
waited
for
the
new
playlist
within
this
function.
Expand source code
def get_started(self, vlc, et, mm, device) -> bool: """ Responsible for getting ready all pre-visual settings Args: vlc (VLCMan): VLC manager with current playlist information et (EtherTalk): Communicator to other Raspis on the network mm (MessageMan): Information manager to compose/parse communications Returns: bool: True indicates that this unit is the leader, and needs to send out the playlist it created, False indicates it is the follower, and will have waited for the new playlist within this function. """ try: os.remove(PLAYLIST_NAME) except: pass try: # Check for usb device, this unit should be the Follower if # not found and no one else is on the network. Performed for 3 minutes. if not device: if et.ping() == 0: print("[!] I am the follower (งツ)ว") c_queue.IsLeader = False self.listener(vlc, et) else: print("[!] I am the leader ᕕ( ᐛ )ᕗ") c_queue.IsLeader = True UserInput(self.eh).input_listener() else: print("[!] I am the leader ᕕ( ᐛ )ᕗ") c_queue.IsLeader = True UserInput(self.eh).input_listener() except Exception as e: # Something went wrong with the startup self.eh.log_error(e) return False # Playlist setup logic. Dependant on isLeader status try: # This is the Leader RasPi if c_queue.IsLeader == True: self.create_playlist(vlc) else: vlc.start_vlc() except Exception as e: self.eh.log_error(e) return False # This point indicates no major exceptions have occurred for either the # Leader or the Follower return True
def listener(self, vlc, et)
-
Follower wrapper to spawn listener thread
Args
et
:EtherTalk
- The Communicator to other Raspis on the network
vlc
:VLCMan
- VLC manager with current playlist information
Returns
None
Expand source code
def listener(self, vlc, et): """ Follower wrapper to spawn listener thread Args: et (EtherTalk): The Communicator to other Raspis on the network vlc (VLCMan): VLC manager with current playlist information Returns: None """ thread = threading.Thread( target=self.__listener, args=(vlc, et), daemon=True) thread.start()
def next_command(self, vlc, et, mm, device)
-
Issue instructions to other host on the network
Args
vlc
:VLCMan
- The VLC manager with current playlist and song information
et
:EtherTalk
- The ethernet communication handler instance
mm
:MessageMan
- The order information manager to compose and parse communications
Returns
bool
- True indicates that another RasPi has responded to the order, False indicates that either there was no answer, or an unrecoverable error has occurred for the other unit.
Expand source code
def next_command(self, vlc, et, mm, device): """ Issue instructions to other host on the network Args: vlc (VLCMan): The VLC manager with current playlist and song information et (EtherTalk): The ethernet communication handler instance mm (MessageMan): The order information manager to compose and parse communications Returns: bool: True indicates that another RasPi has responded to the order, False indicates that either there was no answer, or an unrecoverable error has occurred for the other unit. """ try: # No mouse input and no queue, listen for another leader and give up Leadership if c_queue.CommandQueue.empty() and not device: if et.ping(45) == 0: print("[!] I am the follower (งツ)ว") c_queue.IsLeader = False self.listener(vlc, et) Commands(self.eh).flush_queue() return # If we have something in our queue elif not c_queue.CommandQueue.empty(): # Grab next dict head from queue command_header = Commands(self.eh).dequeue() code = command_header['code'] # If this comes back false, we have no follower if self.send_orders("", et, mm, START_TIME, OrderCode(command_header['code'])): # If we didn't already have a follower, send the playlist if not self.has_follower: # Generate header to send playlist header = mm.compose_message( "", PLAYLIST_START_TIME, OrderCode.SEND_FILE) et.send_file(header, PLAYLIST_NAME) Commands(self.eh).flush_queue() code = OrderCode.PLAYLIST mm.start_time += 0.6 self.has_follower = True # We are leader, if execution is OK, if self.exec_command(vlc, datetime.fromtimestamp(mm.start_time), code): self.leader_response_time = None # Otherwise, send IDLE to follower to indicate we are still alive every 10 seconds elif self.timestamp < datetime.now(): self.timestamp = datetime.now() + timedelta(seconds=IDLE_SEND_TIME) code = OrderCode.IDLE # If we are successfully sending the idle, we still have follower if self.send_orders("", et, mm, START_TIME, code): # If we didn't already have a follower, send the playlist if not self.has_follower: # Generate header to send playlist header = mm.compose_message( "", PLAYLIST_START_TIME, OrderCode.SEND_FILE) et.send_file(header, PLAYLIST_NAME) Commands(self.eh).flush_queue() code = OrderCode.PLAYLIST mm.start_time += 0.6 self.has_follower = True if self.exec_command(vlc, datetime.fromtimestamp(mm.start_time), code): self.leader_response_time = None except Exception as e: self.eh.log_error(e) raise
def next_command_follower(self, mm, vlc)
-
Checks to see if follower needs to take leader status. Executes next command in queue regardless.
Args
mm
:MessageMan
- The message interpretor for et communications
vlc
:VLCMan
- VLC manager with current playlist information
Returns
None
Expand source code
def next_command_follower(self, mm, vlc): """ Checks to see if follower needs to take leader status. Executes next command in queue regardless. Args: mm (MessageMan): The message interpretor for et communications vlc (VLCMan): VLC manager with current playlist information Returns: None """ try: # I want to check for empty queue and not add "NONE's" on the queue. # I believe it is causing syncing problems, so only exec_command queue's # with commands in them, otherwise we are either not getting commands or # the commands being sent are taking awhile to get here/being processed. # So I set the takeover time to be 3 mins in the future from when we have # an empty queue. if c_queue.CommandQueue.empty() and self.leader_response_time is None: self.leader_response_time = datetime.now( ) + timedelta(minutes=FOLLOWER_TAKEOVER_WAIT) elif not c_queue.CommandQueue.empty(): # Dequeue next command header to be executed command_header = Commands(self.eh).dequeue() command_code = command_header['code'] command_start = datetime.fromtimestamp( command_header['start_time']) # Execute the command. NONE and an error will return False. if self.exec_command(vlc, command_start, command_code): self.leader_response_time = None # We have waiting for a response from the leader and nothing. # So become the leader and start up the input listender. elif self.leader_response_time is not None and self.leader_response_time < datetime.now(): print("[!] I am the leader ᕕ( ᐛ )ᕗ") # this should prob just restart the code from main.... self.leader_response_time = None Commands(self.eh).flush_queue() c_queue.IsLeader = True UserInput(self.eh).input_listener() except Exception as e: self.eh.log_error(e) raise
def send_orders(self, message, et, mm, time_start, command)
-
Issue instructions to other RasPis on the network
Args
message
:str
- unused
et
:EtherTalk
- The ethernet communication handler instance
mm
:MessageMan
- The order information manager to compose and parse communications
time_start
:int
- time offset for when the command will start
command
:enum
OrderCode
- command to be sent
Returns
bool
- True indicates that another RasPi has responded to the order, False indicates that either there was no answer, or an unrecoverable error has occurred for the other unit.
Expand source code
def send_orders(self, message, et, mm, time_start, command) -> bool: """ Issue instructions to other RasPis on the network Args: message (str): unused et (EtherTalk): The ethernet communication handler instance mm (MessageMan): The order information manager to compose and parse communications time_start (int): time offset for when the command will start command (enum OrderCode): command to be sent Returns: bool: True indicates that another RasPi has responded to the order, False indicates that either there was no answer, or an unrecoverable error has occurred for the other unit. """ # Create dict to send message_dict = mm.compose_message(message, time_start, command) if et.send(message_dict) == 0: return True return False
def shutdown(self)
-
Safely exits and shuts down the RasPi unit
Args
None
Returns
None
Expand source code
def shutdown(self): """ Safely exits and shuts down the RasPi unit Args: None Returns: None """ subprocess.Popen("sudo shutdown -P now", shell=True)
def waiting_loop(self)
-
This is the main-loop, active function for this program. It checks for input from a user or other RasPis, and calls appropriate functions when needed.
Args
None
Returns
bool
- The return value indicates if everything closed properly. True for success, False otherwise.
Expand source code
def waiting_loop(self) -> bool: """ This is the main-loop, active function for this program. It checks for input from a user or other RasPis, and calls appropriate functions when needed. Args: None Returns: bool: The return value indicates if everything closed properly. True for success, False otherwise. """ # Get object resources up and running et = EtherTalk(error_handler=self.eh, verbose=False) mm = MessageMan(error_handler=self.eh) vlc = VLCMan(VIDEOS_PATH, PLAYLIST_NAME, "", error_handler=self.eh) device = Device().has_device if not self.get_started(vlc, et, mm, device): # Problem at startup, restart from scratch os.system('sudo shutdown -r now') self.time = datetime.now().second while not self.is_shutdown_time: # If leader check for user input and send commands if c_queue.IsLeader == True: device = Device().has_device self.next_command(vlc, et, mm, device) # Check to make sure a follower is still on the network if datetime.now().second > 57 and not self.has_checked_ping: # Loop for three seconds, pinging the follower for a response loop_end_time = datetime.now() + timedelta(seconds=3) self.has_checked_ping = True while True: if self.send_orders("", et, mm, 0, OrderCode.PING_RESPONSE): break elif datetime.now() >= loop_end_time: self.has_follower = False break elif datetime.now().second > 53 and datetime.now().second <= 56: self.has_checked_ping = False else: # RasPi is the follower # Check if we need to become leader, dequeue, execute commands self.next_command_follower(mm, vlc) # Check time for shutdown self.is_shutdown_time = self.check_time() # Out of the Loop means it is Shutdown time for all units # Send shutdown signal 5 times, in case of false negative for i in range(4): if self.send_orders("", et, mm, 0, OrderCode.SHUT_DOWN): break time.sleep(5) # Then shutdown self.shutdown()