Skip to main content

main.py

initializeSystem

async def initializeSystem():
network = ipaddress.ip_network('192.168.1.0/24')
modules = []

async with aiohttp.ClientSession() as session:
tasks = []
for ip in network.hosts():
tasks.append(asyncio.create_task(initialize_host(ip, session, modules)))

await asyncio.gather(*tasks)

return modules

The initializeSystem method is responsible for setting up the fireworks control system at the start of the application. It initializes the system, creates the instances for the Module class, adds hosts to the modules, and sets up the ignition points. This method is the primary setup procedure that enables the other parts of the system to function properly.

initialize_host

async def initialize_host(ip, session, modules):
try:
async with session.post(f'http://{ip}/initialize', headers=headers, timeout=3) as response:
if response.status == 200:
data = await response.json()
await process_response(ip, data, modules)
except Exception:
pass

The initialize_host method is used to instantiate a Host class object for each host specified in the hosts list. It sets the host's ipAddress, modules, moduleId, moduleName, and ignitions. This method is integral to the setup of the system as it defines the hosts that the modules will be interacting with.

process_response

async def process_response(ip, data, modules):
moduleName = data['moduleName']
moduleId = data['moduleId']
ignitions = [Ignition(f'http://{ip}/ignite/{i+1}') for i in range(data['ignitions'])]

existing_module = find_module_by_name(modules, moduleName)
if existing_module is not None:
existing_module.add_host(Host(ip, data['modules'], moduleId, moduleName, ignitions))
else:
module = Module(moduleName)
module.add_host(Host(ip, data['modules'], moduleId, moduleName, ignitions))
modules.append(module)

The process_response method handles the responses received from the user command input. Depending on the command type, it performs various operations such as firing a specified ignition, generating a sequence of ignitions, swapping the order of ignitions in the sequence, etc. This method is a key part of the command-and-control aspect of the application, ensuring that user commands are correctly processed and executed.

find_module_by_name

def find_module_by_name(modules, moduleName):
for module in modules:
if module.moduleName == moduleName:
return module
return None

The find_module_by_name method is a helper function used to find and return a module object given its name. It iterates over the list of modules in the system and checks if the name of each module matches the provided name. If a match is found, it returns the corresponding module object. This method simplifies the process of accessing module-specific data and operations within the system.

generate_random_sequence

def generate_random_sequence(modules):
ignitions = []
# Iterate over each module
for module in modules:
# Iterate over each host in the module
for host in module.hosts:
# Iterate over each ignition in the host
for ignition in host.ignitions:
if not ignition.hasBeenFired:
ignitions.append(ignition)
# Shuffle the list of ignitions
random.shuffle(ignitions)
# Return the shuffled list
return ignitions

The generate_random_sequence method is responsible for generating a random sequence of ignition orders. This sequence is represented as a list of numbers, each number representing the order in which an ignition will be fired. By providing a randomized sequence, this method introduces an element of unpredictability into the system, which can be useful in a variety of scenarios, such as creating a random firework display.

get_order_from_url

def get_order_from_url(url):
path = urlparse(url).path
return int(path.split('/')[-1])

The get_order_from_url method is used to extract and return the order value from a given URL. This method is essential for managing the order of ignitions as they are specified in their endpoint URLs. By extracting this information, the method enables the system to properly arrange and manage the sequence of ignitions.

generate_sequential_sequence

def generate_sequential_sequence(modules):
# Step 1: Create a grouped list of all ignitions
ignition_groups = defaultdict(lambda: defaultdict(deque))
for module in modules:
for host in module.hosts:
for ignition in host.ignitions:
order = get_order_from_url(ignition.endpoint)
key = f"{host.moduleName}-{host.ipAddress}"
ignition_groups[order][key].append(ignition)

# Step 2: Iterate over the groups in sequential order and within each group,
# cycle through the subgroups
ignition_sequence = []
last_module = None
for order in sorted(ignition_groups.keys()):
subgroups = list(ignition_groups[order].items())
while subgroups:
# Get the subgroup key (moduleName-ipAddress) and its ignitions
key, ignitions = subgroups.pop(0)
module_name = key.split('-')[0]

# If this module is the same as the last module, and there are other subgroups,
# put it at the end of the list and continue with the next one
if module_name == last_module and subgroups:
subgroups.append((key, ignitions))
continue

# Add the next ignition from this subgroup to the sequence
ignition_sequence.append(ignitions.popleft())
# If there are more ignitions in this subgroup, put it back in the list
if ignitions:
subgroups.append((key, ignitions))

last_module = module_name

return ignition_sequence

The generate_sequential_sequence method is used to generate a sequential sequence of ignition orders. This sequence is represented as a list of numbers, each number representing the order in which an ignition will be fired. This method ensures that the sequence is always generated in a consistent and predictable manner, starting from 1 and incrementing by 1 for each subsequent ignition. This can be especially useful in scenarios where a specific order of ignitions needs to be maintained, such as creating a sequential firework display.

sequence_fire

async def sequence_fire(current_sequence, modules):
for sequence_ignition in current_sequence:
for module in modules:
for host in module.hosts:
for ignition in host.ignitions:
if ignition.endpoint == sequence_ignition.endpoint:
await fire(ignition)
print("\n" * 100)
for module in modules:
for host in module.hosts:
PrintUtils.sequence(display_sequence(host, current_sequence))
print("-------")
print("\n" * 100)

The sequence_fire method is responsible for triggering the ignition of fireworks in a specific sequence. It takes a sequence list as an input, where each element represents the order of the ignitions. The method iterates over the sequence and fires each ignition in the order specified by the sequence. The sequence_fire method provides a way to create a more organized and controlled firework display, by allowing for a precise order of ignitions to be executed.

fire

async def fire(ignition):
if not ignition.hasBeenFired:
await ignition.fire()
PrintUtils.log(f"Ignition fired successfully.")
else:
PrintUtils.error(f"Ignition already received.")

The fire method is used to trigger the ignition of a specific firework module. It takes two parameters: the module name and the ignition index. The method first finds the module by name using the find_module_by_name method. If the module is found, the specified ignition is fired. If either the module or the ignition index is not found, the method will print an error message. This method provides a direct way to trigger a single firework ignition, allowing for specific control over which firework is ignited at a given time.

find_and_fire

async def find_and_fire(modules, moduleName, moduleId, igniteNumber):
# Find the module that matches the moduleName
for module in modules:
if module.moduleName == moduleName:
# Find the host with the matching moduleId
for host in module.hosts:
if host.moduleId == moduleId:
# Find the Ignition with the matching igniteNumber
for ignition in host.ignitions:
if int(ignition.endpoint.split('/')[-1]) == igniteNumber:
await fire(ignition)
return
PrintUtils.error(f"Ignition with igniteNumber '{igniteNumber}' not found in module '{moduleName}', host '{moduleId}'.")
return
PrintUtils.error(f"Host with moduleId '{moduleId}' not found in module '{moduleName}'.")
return
PrintUtils.error(f"Module '{moduleName}' not found.")

The find_and_fire method combines the functionality of finding a specific module and firing an ignition. It takes three parameters: the name of the module, the module ID, and the ignition index. It first retrieves the module using the find_module_by_name method, and if successful, it proceeds to fire the specified ignition within that module using the fire method. If either the module or the ignition is not found, an error message is displayed. This method offers a convenient way to find and fire a specific ignition within a specified module.

display_sequence

def display_sequence(host, ignition_sequence):
# Get the IP and module details
result = f"{host.ipAddress} - {host.moduleName} {host.moduleId}/{host.modules} - Ignitions: "

# Generate the ignition list
for ignition in host.ignitions:
if ignition.hasBeenFired:
result += f"[{chr(0x25CF)}] "
elif ignition in ignition_sequence:
result += f"[{ignition_sequence.index(ignition) + 1}] "
return result

The display_sequence method is responsible for visually representing the sequence of ignitions. It takes a sequence list as input, where each element represents the order of the ignitions. The method iterates over the sequence and prints out a representation of each ignition, using the format specified in the PrintUtils.sequence method. This allows for a clear and easy-to-understand visualization of the sequence of ignitions that is set to occur.

sequence_swap

def sequence_swap(sequence, index1, index2):
# Check if the indexes are within the list
if index1 <= len(sequence) and index2 <= len(sequence):
# Subtract 1 from each index to make them 0-based
index1 -= 1
index2 -= 1
# Swap the elements
sequence[index1], sequence[index2] = sequence[index2], sequence[index1]
else:
PrintUtils.error(f"Indices are out of range. List length: {len(sequence)}, Received Indices: {index1}, {index2}")
return sequence

The sequence_swap method is used to change the order of ignitions in a sequence. It takes two parameters: index1 and index2, which represent the indices of the ignitions in the sequence that are to be swapped. The method checks whether the provided indices are within the range of the sequence. If they are, it swaps the ignitions at these indices. If they are not, it prints an error message indicating the indices are out of range. This method provides a way to alter the sequence of ignitions after it has been generated, allowing for greater flexibility in the execution of the ignitions.

main

async def main():
modules = []
current_sequence = []
while True:
command = input('Enter a command: ')
if command.lower() == 'init':
if not modules:
modules = await initializeSystem()
for module in modules:
for host in module.hosts:
PrintUtils.host(host.display())
print("-------")
elif command.lower() == 'fire sequence':
if not current_sequence:
PrintUtils.param_error(f'Sequence generation required. Please use "generate sequence -f <format>".')
else:
await sequence_fire(current_sequence, modules)
for module in modules:
for host in module.hosts:
PrintUtils.host(host.display())
print("-------")
elif command.lower().startswith('fire'):
fire_command = command[5:].strip()
fire_params = ParseUtils.parse_fire_command(fire_command)
if not fire_params:
PrintUtils.param_error(f'Invalid command format. Please use "fire -m <moduleName> -d <moduleId> -i <igniteNumber>".')
continue
moduleName, moduleId, igniteNumber = fire_params
PrintUtils.log(f"Fire command received for Module: {moduleName}, Module ID: {moduleId}, Ignite Number: {igniteNumber}")
await find_and_fire(modules, moduleName, moduleId, igniteNumber)
for module in modules:
for host in module.hosts:
PrintUtils.host(host.display())
print("-------")
elif command.lower().startswith('generate sequence'):
generate_sequence_params = ParseUtils.parse_generate_sequence(command)
if not generate_sequence_params:
PrintUtils.param_error(f'Invalid command format. Please use "generate sequence -f <format>".')
continue
command, modifier, seq_format = generate_sequence_params
if not current_sequence:
PrintUtils.log(f"Generate Sequence command received for {seq_format}: Generating Sequence now.")
if seq_format == "RANDOM":
current_sequence = generate_random_sequence(modules)
elif seq_format == "SEQUENTIAL":
current_sequence = generate_sequential_sequence(modules)
for module in modules:
for host in module.hosts:
PrintUtils.sequence(display_sequence(host, current_sequence))
print("-------")
elif command.lower().startswith('sequence swap'):
swap_params = ParseUtils.parse_sequence_swap(command)
if swap_params:
index1, index2 = swap_params
current_sequence = sequence_swap(current_sequence, index1, index2)
else:
PrintUtils.param_error("Invalid command format. Please use 'sequence swap <index1> <index2>'.")
for module in modules:
for host in module.hosts:
PrintUtils.sequence(display_sequence(host, current_sequence))
print("-------")
elif command.lower() == 'override sequence':
current_sequence = []
PrintUtils.log(f"Current Sequence reset")
elif command.lower() == 'override reboot':
modules = []
PrintUtils.log(f"System reset")
else:
PrintUtils.error('Invalid command. Try again')

The main function is the entry point of the script. It begins by loading modules (firework units) from a JSON file, converting them into Module objects for easy manipulation. Then, it enters an infinite loop to keep the program running and listening for user input.

The function uses Python's input function to accept commands from the user, which are then parsed and executed. The command exit can be used to terminate the loop and end the program.

The commands supported are:

  1. fire - This command fires a specific ignition of a module.
  2. display - This command displays the current state of the modules, hosts, and ignitions in the network.
  3. generate sequence - This command generates a firing sequence either randomly or sequentially.
  4. sequence swap - This command swaps two ignitions in the firing sequence.

In case of an error or invalid command, the function handles it gracefully and prints an error message.

Overall, the main function coordinates the user interactions, parsing and executing commands, displaying the state, and managing the sequence of ignition.