def run(self, *args: Any, **kwargs: Any) -> None: """Runs driver.""" # Run parent class super().run(*args, **kwargs) # Instantiate manager self.manager = self.Manager( name=self.args.name, i2c_lock=threading.RLock(), state=State(), config=self.peripheral_config, ) # Initialize manager self.manager.initialize() # Check if setting up if self.args.setup: self.manager.setup() # Check if updating if self.args.update: self.manager.update() # Check if resetting if self.args.reset: self.manager.reset() # Check if shutting down if self.args.shutdown: self.manager.shutdown()
def test_init(): manager = PeripheralManager( name="Test", state=State(), config=peripheral_config, i2c_lock=threading.RLock(), simulate=True, )
def test_init() -> None: manager = AtlasDOManager( name="Test", i2c_lock=threading.RLock(), state=State(), config=peripheral_config, simulate=True, mux_simulator=MuxSimulator(), )
def test_initialize() -> None: manager = LEDDAC5578Manager( name="Test", i2c_lock=threading.RLock(), state=State(), config=peripheral_config, simulate=True, mux_simulator=MuxSimulator(), ) manager.initialize()
def test_run_reset_mode(): manager = PeripheralManager( name="Test", state=State(), config=peripheral_config, i2c_lock=threading.RLock(), simulate=True, ) manager.run_reset_mode() assert manager.mode == Modes.INIT
def test_reset_400() -> None: manager = PeripheralManager( name="Test", state=State(), config=peripheral_config, i2c_lock=threading.RLock(), simulate=True, ) message, status = manager.events.reset() assert status == 400
def test_shutdown() -> None: manager = AtlasECManager( name="Test", i2c_lock=threading.RLock(), state=State(), config=peripheral_config, simulate=True, mux_simulator=MuxSimulator(), ) manager.initialize() manager.shutdown()
def test_clear_calibrations() -> None: manager = AtlasECManager( name="Test", i2c_lock=threading.RLock(), state=State(), config=peripheral_config, simulate=True, mux_simulator=MuxSimulator(), ) manager.initialize() manager.mode = Modes.CALIBRATE message, status = manager.events.create( request={"type": "Clear Calibration"}) assert status == 200 manager.events.check()
def test_reset_200() -> None: manager = PeripheralManager( name="Test", state=State(), config=peripheral_config, i2c_lock=threading.RLock(), simulate=True, ) manager._mode = Modes.ERROR message, status = manager.events.reset() assert status == 200 # Manager should not process reset event until check() is called but # event should get updated in the event queue assert manager.mode == Modes.ERROR assert list(manager.events.queue.queue) == [{"type": "Reset"}] # Make sure manager mode transitions after calling check() manager.events.check() assert manager.mode == Modes.RESET
class UpgradeUtilities: """ Utilities to upgrade this apt package. """ # Class variable to hold a reference to the state.upgrade dict ref_state = State() @staticmethod def save_state(state): """Saves a reference to the state as a static class var.""" UpgradeUtilities.ref_state = state @staticmethod def get_status(): """Returns a dict of all the fields we display on the Django Upgrade tab.""" return UpgradeUtilities.ref_state.upgrade @staticmethod def update_dict(): """ Updates our dict with the software versions. Only call once a day, this will take a few minutes to execute. Example call: sudo apt-get update apt-cache policy openagbrain openagbrain: Installed: (none) Candidate: 0.1-2""" try: UpgradeUtilities.ref_state.upgrade[ "status"] = "Checking for upgrades..." # update this machines list of available packages cmd = ["sudo", "apt-get", "update"] subprocess.run(cmd) # command and list of args as list of strings cmd = ["apt-cache", "policy", "openagbrain"] with subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc1: output = proc1.stdout.read().decode("utf-8") output += proc1.stderr.read().decode("utf-8") lines = output.splitlines() installed = "" candidate = "" for line in lines: tokens = line.split() for token in tokens: if token.startswith("Installed:"): installed = tokens[1] break elif token.startswith("Candidate:"): candidate = tokens[1] break UpgradeUtilities.ref_state.upgrade[ "current_version"] = installed UpgradeUtilities.ref_state.upgrade[ "upgrade_version"] = candidate # very simple upgrade logic, trust debian package logic if "(none)" == installed or installed != candidate: UpgradeUtilities.ref_state.upgrade["show_upgrade"] = True UpgradeUtilities.ref_state.upgrade["status"] = "Up to date." if UpgradeUtilities.ref_state.upgrade.get("show_upgrade", False): UpgradeUtilities.ref_state.upgrade[ "status"] = "Software upgrade is available." except: return False return True @staticmethod def update_software(): """Update our debian package with the latest version available. If we call 'sudo apt-get install -y openagbrain' here, inside django, we will create a deadlock where apt can't complete the install because it is run as a child of the process it has to terminate. So, let's get creative: 0. This logic assumes django is being run as root with sudo or from the rc.local service. It also assumes that 'apt-get update' has already been run and we know there is an update available. 1. Write file /tmp/openagbrain-at-commands with contents: systemctl stop rc.local apt-get install -y openagbrain 2. Create an 'at' job which runs the above commands in a minute: at -f /tmp/openagbrain-at-commands now + 1 minute""" try: fn = "/tmp/openagbrain-at-commands" f = open(fn, "w") f.write("systemctl stop rc.local\n") f.write("apt-get install -y openagbrain\n") f.close() # update our debian package cmd = [ "at", "-f", "/tmp/openagbrain-at-commands", "now", "+", "1", "minute", ] subprocess.Popen(cmd) UpgradeUtilities.ref_state.upgrade[ "status"] = "Upgrading, will restart in 5 minutes..." UpgradeUtilities.ref_state.upgrade["show_upgrade"] = False except Exception as e: UpgradeUtilities.ref_state.upgrade["error"] = e return UpgradeUtilities.ref_state.upgrade @staticmethod def check(): """Checks for software updates.""" UpgradeUtilities.update_dict() return UpgradeUtilities.get_status()
class CoordinatorManager(Manager): """Manages device state machine thread that spawns child threads to run recipes, read sensors, set actuators, manage control loops, sync data, and manage external events.""" # Initialize logger extra = {"console_name": "Coordinator", "file_name": "Coordinator"} logger = logging.getLogger("coordinator") logger = logging.LoggerAdapter(logger, extra) # Initialize device mode and error _mode = None # Initialize state object, `state` serves as shared memory between threads state = State() # Initialize environment state dict state.environment = { "sensor": { "desired": {}, "reported": {} }, "actuator": { "desired": {}, "reported": {} }, "reported_sensor_stats": { "individual": { "instantaneous": {}, "average": {} }, "group": { "instantaneous": {}, "average": {} }, }, } # Initialize recipe state dict state.recipe = { "recipe_uuid": None, "start_timestamp_minutes": None, "last_update_minute": None, } # Initialize recipe object recipe = RecipeManager(state) # Initialize peripheral and controller managers peripherals = {} controllers = {} def __init__(self): """Initializes coordinator.""" self.logger.debug("Initializing coordinator") # Initialize mode and error self.mode = Modes.INIT # Initialize latest publish timestamp self.latest_publish_timestamp = 0 # Initialize state machine transitions self.transitions = Transitions(self, TRANSITION_TABLE) # Initialize coordinator event handler self.events = CoordinatorEvents(self) # Initialize managers self.iot = IoTManager(self.state, self.recipe) self.resource = ResourceManager(self.state, self, self.iot) self.connect = ConnectManager(self.state, self.iot) self.upgrade = UpgradeManager(self.state) @property def mode(self): """Gets mode.""" return self._mode @mode.setter def mode(self, value): """Safely updates mode in state object """ self._mode = value with self.state.lock: self.state.device["mode"] = value @property def commanded_mode(self): """ Gets commanded mode from shared state object. """ if "commanded_mode" in self.state.device: return self.state.device["commanded_mode"] else: return None @commanded_mode.setter def commanded_mode(self, value): """ Safely updates commanded mode in state object. """ with self.state.lock: self.state.device["commanded_mode"] = value @property def request(self): """ Gets request from shared state object. """ if "request" in self.state.device: return self.state.device["request"] else: return None @request.setter def request(self, value): """ Safely updates request in state object. """ with self.state.lock: self.state.device["request"] = value @property def response(self): """ Gets response from shared state object. """ if "response" in self.state.device: return self.state.device["response"] else: return None @response.setter def response(self, value): """ Safely updates response in state object. """ with self.state.lock: self.state.device["response"] = value @property def config_uuid(self): """ Gets config uuid from shared state. """ if "config_uuid" in self.state.device: return self.state.device["config_uuid"] else: return None @config_uuid.setter def config_uuid(self, value): """ Safely updates config uuid in state. """ with self.state.lock: self.state.device["config_uuid"] = value @property def commanded_config_uuid(self): """ Gets commanded config uuid from shared state. """ if "commanded_config_uuid" in self.state.device: return self.state.device["commanded_config_uuid"] else: return None @commanded_config_uuid.setter def commanded_config_uuid(self, value): """Safely updates commanded config uuid in state.""" with self.state.lock: self.state.device["commanded_config_uuid"] = value @property def config_dict(self): """Gets config dict for config uuid in device config table.""" if self.config_uuid == None: return None config = DeviceConfigModel.objects.get(uuid=self.config_uuid) return json.loads(config.json) @property def latest_environment_timestamp(self): """Gets latest environment timestamp from environment table.""" if not EnvironmentModel.objects.all(): return 0 else: environment = EnvironmentModel.objects.latest() return environment.timestamp.timestamp() def spawn(self, delay=None): """Spawns device thread.""" self.thread = threading.Thread(target=self.run_state_machine, args=(delay, )) self.thread.daemon = True self.thread.start() def run_state_machine(self, delay: float = 0) -> None: """Runs device state machine.""" # Wait for optional delay time.sleep(delay) self.logger.info("Spawning device thread") # Start state machine self.logger.info("Started state machine") while True: if self.mode == Modes.INIT: self.run_init_mode() elif self.mode == Modes.CONFIG: self.run_config_mode() elif self.mode == Modes.SETUP: self.run_setup_mode() elif self.mode == Modes.NORMAL: self.run_normal_mode() elif self.mode == Modes.LOAD: self.run_load_mode() elif self.mode == Modes.ERROR: self.run_error_mode() elif self.mode == Modes.RESET: self.run_reset_mode() def run_init_mode(self): """Runs initialization mode. Loads local data files and stored database state then transitions to config mode.""" self.logger.info("Entered INIT") # Load local data files self.load_local_data_files() # Load stored state from database self.load_database_stored_state() # Check for transitions if self.transitions.is_new(Modes.INIT): return # Transition to CONFIG self.mode = Modes.CONFIG def run_config_mode(self): """Runs configuration mode. If device config is not set, loads 'unspecified' config then transitions to setup mode.""" self.logger.info("Entered CONFIG") # Check device config specifier file exists in repo DEVICE_CONFIG_PATH = "data/config/device.txt" try: with open(DEVICE_CONFIG_PATH) as f: config_name = f.readline().strip() except: message = "Unable to read {}, using unspecified config".format( DEVICE_CONFIG_PATH) self.logger.warning(message) config_name = "unspecified" # Write `unspecified` to device.txt with open(DEVICE_CONFIG_PATH, "w") as f: f.write("{}\n".format(config_name)) # Load device config self.logger.debug("Loading device config file: {}".format(config_name)) device_config = json.load( open("data/devices/{}.json".format(config_name))) # Check if config uuid changed, if so, adjust state if self.config_uuid != device_config["uuid"]: with self.state.lock: self.state.peripherals = {} set_nested_dict_safely( self.state.environment, ["reported_sensor_stats"], {}, self.state.lock, ) set_nested_dict_safely(self.state.environment, ["sensor", "reported"], {}, self.state.lock) self.config_uuid = device_config["uuid"] # Check for transitions if self.transitions.is_new(Modes.CONFIG): return # Transition to SETUP self.mode = Modes.SETUP def run_setup_mode(self): """Runs setup mode. Creates and spawns recipe, peripheral, and controller threads, waits for all threads to initialize then transitions to normal mode.""" self.logger.info("Entered SETUP") config_uuid = self.state.device["config_uuid"] self.logger.debug("state.device.config_uuid = {}".format(config_uuid)) # Spawn the threads this object controls self.recipe.spawn() self.iot.spawn() self.resource.spawn() self.connect.spawn() self.upgrade.spawn() # Create peripheral managers and spawn threads self.create_peripherals() self.spawn_peripherals() # Create controller managers and spawn threads self.create_controllers() self.spawn_controllers() # Wait for all threads to initialize while not self.all_threads_initialized(): time.sleep(0.2) # Check for transitions if self.transitions.is_new(Modes.SETUP): return # Transition to NORMAL self.mode = Modes.NORMAL def run_normal_mode(self): """Runs normal operation mode. Updates device state summary and stores device state in database, checks for new events and transitions.""" self.logger.info("Entered NORMAL") while True: # Overwrite system state in database every 100ms self.update_state() # Store environment state in every 10 minutes if time.time() - self.latest_environment_timestamp > 60 * 10: self.store_environment() # Once a minute, publish any changed values if time.time() - self.latest_publish_timestamp > 60: self.latest_publish_timestamp = time.time() self.iot.publish() # Check for events self.events.check() # Check for transitions if self.transitions.is_new(Modes.NORMAL): break # Update every 100ms time.sleep(0.1) def run_load_mode(self): """Runs load mode, kills peripheral and controller threads then transitions to config mode.""" self.logger.info("Entered LOAD") # Kill peripheral and controller threads self.kill_peripheral_threads() self.kill_controller_threads() # Transition to CONFIG self.mode = Modes.CONFIG def run_reset_mode(self): """Runs reset mode. Kills child threads then transitions to init.""" self.logger.info("Entered RESET") # Kills child threads self.kill_peripheral_threads() self.kill_controller_threads() self.recipe.stop() self.iot.stop() # Transition to init on next state machine update self.mode = Modes.INIT def run_error_mode(self): """Runs error mode. Shutsdown child threads, waits for new events and transitions.""" self.logger.info("Entered ERROR") # Kills peripheral and controller threads self.kill_peripheral_threads() self.kill_controller_threads() # Loop forever while True: # Check for events self.events.check() # Check for transitions if self.transitions.is_new(Modes.ERROR): break # Update every 100ms time.sleep(0.1) def update_state(self): """Updates stored state in database. If state does not exist, creates it.""" # TODO: Move this to state manager if not StateModel.objects.filter(pk=1).exists(): StateModel.objects.create( id=1, device=json.dumps(self.state.device), recipe=json.dumps(self.state.recipe), environment=json.dumps(self.state.environment), peripherals=json.dumps(self.state.peripherals), controllers=json.dumps(self.state.controllers), iot=json.dumps(self.state.iot), resource=json.dumps(self.state.resource), connect=json.dumps(self.state.connect), upgrade=json.dumps(self.state.upgrade), ) else: StateModel.objects.filter(pk=1).update( device=json.dumps(self.state.device), recipe=json.dumps(self.state.recipe), environment=json.dumps(self.state.environment), peripherals=json.dumps(self.state.peripherals), controllers=json.dumps(self.state.controllers), iot=json.dumps(self.state.iot), resource=json.dumps(self.state.resource), connect=json.dumps(self.state.connect), upgrade=json.dumps(self.state.upgrade), ) def load_local_data_files(self): """ Loads local data files. """ self.logger.info("Loading local data files") # Load files with no verification dependencies first self.load_sensor_variables_file() self.load_actuator_variables_file() self.load_cultivars_file() self.load_cultivation_methods_file() # Load recipe files after sensor/actuator variables, cultivars, and # cultivation methods since verification depends on them self.load_recipe_files() # Load peripherals after sensor/actuator variable since verification # depends on them self.load_peripheral_setup_files() # Load device config after peripheral setups since verification # depends on them self.load_device_config_files() def load_sensor_variables_file(self): """ Loads sensor variables file into database after removing all existing entries. """ self.logger.debug("Loading sensor variables file") # Load sensor variables and schema sensor_variables = json.load( open("data/variables/sensor_variables.json")) sensor_variables_schema = json.load( open("data/schemas/sensor_variables.json")) # Validate sensor variables with schema validate(sensor_variables, sensor_variables_schema) # Delete sensor variables tables SensorVariableModel.objects.all().delete() # Create sensor variables table for sensor_variable in sensor_variables: SensorVariableModel.objects.create( json=json.dumps(sensor_variable)) def load_actuator_variables_file(self): """ Loads actuator variables file into database after removing all existing entries. """ self.logger.debug("Loading actuator variables file") # Load actuator variables and schema actuator_variables = json.load( open("data/variables/actuator_variables.json")) actuator_variables_schema = json.load( open("data/schemas/actuator_variables.json")) # Validate actuator variables with schema validate(actuator_variables, actuator_variables_schema) # Delete actuator variables tables ActuatorVariableModel.objects.all().delete() # Create actuator variables table for actuator_variable in actuator_variables: ActuatorVariableModel.objects.create( json=json.dumps(actuator_variable)) def load_cultivars_file(self): """ Loads cultivars file into database after removing all existing entries.""" self.logger.debug("Loading cultivars file") # Load cultivars and schema cultivars = json.load(open("data/cultivations/cultivars.json")) cultivars_schema = json.load(open("data/schemas/cultivars.json")) # Validate cultivars with schema validate(cultivars, cultivars_schema) # Delete cultivars tables CultivarModel.objects.all().delete() # Create cultivars table for cultivar in cultivars: CultivarModel.objects.create(json=json.dumps(cultivar)) def load_cultivation_methods_file(self): """ Loads cultivation methods file into database after removing all existing entries. """ self.logger.debug("Loading cultivation methods file") # Load cultivation methods and schema cultivation_methods = json.load( open("data/cultivations/cultivation_methods.json")) cultivation_methods_schema = json.load( open("data/schemas/cultivation_methods.json")) # Validate cultivation methods with schema validate(cultivation_methods, cultivation_methods_schema) # Delete cultivation methods tables CultivationMethodModel.objects.all().delete() # Create cultivation methods table for cultivation_method in cultivation_methods: CultivationMethodModel.objects.create( json=json.dumps(cultivation_method)) def load_recipe_files(self): """Loads recipe files into database via recipe manager create or update function.""" self.logger.debug("Loading recipe files") # Get recipes for filepath in glob.glob("data/recipes/*.json"): self.logger.debug("Loading recipe file: {}".format(filepath)) with open(filepath, "r") as f: json_ = f.read().replace("\n", "") message, code = self.recipe.events.create_or_update_recipe( json_) if code != 200: filename = filepath.split("/")[-1] error = "Unable to load {} -> {}".format(filename, message) self.logger.error(error) def load_peripheral_setup_files(self): """ Loads peripheral setup files from codebase into database by creating new entries after deleting existing entries. Verification depends on sensor/actuator variables. """ self.logger.info("Loading peripheral setup files") # Get peripheral setups peripheral_setups = [] for filepath in glob.glob( "device/peripherals/modules/*/setups/*.json"): self.logger.debug( "Loading peripheral setup file: {}".format(filepath)) peripheral_setups.append(json.load(open(filepath))) # Get get peripheral setup schema # TODO: Finish schema peripheral_setup_schema = json.load( open("data/schemas/peripheral_setup.json")) # Validate peripheral setups with schema for peripheral_setup in peripheral_setups: validate(peripheral_setup, peripheral_setup_schema) # Delete all peripheral setup entries from database PeripheralSetupModel.objects.all().delete() # TODO: Validate peripheral setup variables with database variables # Create peripheral setup entries in database for peripheral_setup in peripheral_setups: PeripheralSetupModel.objects.create( json=json.dumps(peripheral_setup)) def load_device_config_files(self): """ Loads device config files from codebase into database by creating new entries after deleting existing entries. Verification depends on peripheral setups. """ self.logger.info("Loading device config files") # Get devices device_configs = [] for filepath in glob.glob("data/devices/*.json"): self.logger.debug( "Loading device config file: {}".format(filepath)) device_configs.append(json.load(open(filepath))) # Get get device config schema # TODO: Finish schema (see optional objects) device_config_schema = json.load( open("data/schemas/device_config.json")) # Validate device configs with schema for device_config in device_configs: validate(device_config, device_config_schema) # TODO: Validate device config with peripherals # TODO: Validate device config with varibles # Delete all device config entries from database DeviceConfigModel.objects.all().delete() # Create device config entry if new or update existing for device_config in device_configs: DeviceConfigModel.objects.create(json=json.dumps(device_config)) def load_database_stored_state(self): """ Loads stored state from database if it exists. """ self.logger.info("Loading database stored state") # Get stored state from database if not StateModel.objects.filter(pk=1).exists(): self.logger.info("No stored state in database") self.config_uuid = None return stored_state = StateModel.objects.filter(pk=1).first() # Load device state stored_device_state = json.loads(stored_state.device) # Load recipe state stored_recipe_state = json.loads(stored_state.recipe) self.recipe.recipe_uuid = stored_recipe_state["recipe_uuid"] self.recipe.recipe_name = stored_recipe_state["recipe_name"] self.recipe.duration_minutes = stored_recipe_state["duration_minutes"] self.recipe.start_timestamp_minutes = stored_recipe_state[ "start_timestamp_minutes"] self.recipe.last_update_minute = stored_recipe_state[ "last_update_minute"] self.recipe.stored_mode = stored_recipe_state["mode"] # Load peripherals state stored_peripherals_state = json.loads(stored_state.peripherals) for peripheral_name in stored_peripherals_state: self.state.peripherals[peripheral_name] = {} if "stored" in stored_peripherals_state[peripheral_name]: stored = stored_peripherals_state[peripheral_name]["stored"] self.state.peripherals[peripheral_name]["stored"] = stored # Load controllers state stored_controllers_state = json.loads(stored_state.controllers) for controller_name in stored_controllers_state: self.state.controllers[controller_name] = {} if "stored" in stored_controllers_state[controller_name]: stored = stored_controllers_state[controller_name]["stored"] self.state.controllers[controller_name]["stored"] = stored def store_environment(self): """ Stores current environment state in environment table. """ EnvironmentModel.objects.create(state=self.state.environment) def create_peripherals(self): """ Creates peripheral managers. """ self.logger.info("Creating peripheral managers") # Verify peripherals are configured if self.config_dict["peripherals"] == None: self.logger.info("No peripherals configured") return # Inintilize simulation parameters if os.environ.get("SIMULATE") == "true": simulate = True mux_simulator = MuxSimulator() else: simulate = False mux_simulator = None # Create thread locks i2c_lock = threading.RLock() # Create peripheral managers self.peripherals = {} for peripheral_config_dict in self.config_dict["peripherals"]: self.logger.debug("Creating {}".format( peripheral_config_dict["name"])) # Get peripheral setup dict peripheral_uuid = peripheral_config_dict["uuid"] peripheral_setup_dict = self.get_peripheral_setup_dict( peripheral_uuid) # Verify valid peripheral config dict if peripheral_setup_dict == None: self.logger.critical( "Invalid peripheral uuid in device " "config. Validator should have caught this.") continue # Get peripheral module and class name module_name = ("device.peripherals.modules." + peripheral_setup_dict["module_name"]) class_name = peripheral_setup_dict["class_name"] # Import peripheral library module_instance = __import__(module_name, fromlist=[class_name]) class_instance = getattr(module_instance, class_name) # Create peripheral manager peripheral_name = peripheral_config_dict["name"] peripheral = class_instance( name=peripheral_name, state=self.state, config=peripheral_config_dict, simulate=simulate, i2c_lock=i2c_lock, mux_simulator=mux_simulator, ) self.peripherals[peripheral_name] = peripheral def get_peripheral_setup_dict(self, uuid): """ Gets peripheral setup dict for uuid in peripheral setup table. """ if not PeripheralSetupModel.objects.filter(uuid=uuid).exists(): return None return json.loads(PeripheralSetupModel.objects.get(uuid=uuid).json) def spawn_peripherals(self): """ Spawns peripheral threads. """ if self.peripherals == {}: self.logger.info("No peripheral threads to spawn") else: self.logger.info("Spawning peripheral threads") for peripheral_name in self.peripherals: self.peripherals[peripheral_name].spawn() def create_controllers(self): """ Creates controller managers. """ self.logger.info("Creating controller managers") # Verify controllers are configured if self.config_dict["controllers"] == None: self.logger.info("No controllers configured") return # Create controller managers self.controllers = {} for controller_config_dict in self.config_dict["controllers"]: self.logger.debug("Creating {}".format( controller_config_dict["name"])) # Get controller setup dict controller_uuid = controller_config_dict["uuid"] controller_setup_dict = self.get_controller_setup_dict( controller_uuid) # Verify valid controller config dict if controller_setup_dict == None: self.logger.critical( "Invalid controller uuid in device " "config. Validator should have caught this.") continue # Get controller module and class name module_name = ("device.controllers.drivers." + controller_setup_dict["module_name"]) class_name = controller_setup_dict["class_name"] # Import controller library module_instance = __import__(module_name, fromlist=[class_name]) class_instance = getattr(module_instance, class_name) # Create controller manager controller_name = controller_config_dict["name"] controller_manager = class_instance(controller_name, self.state, controller_config_dict) self.controllers[controller_name] = controller_manager def spawn_controllers(self): """ Spawns controller threads. """ if self.controllers == {}: self.logger.info("No controller threads to spawn") else: self.logger.info("Spawning controller threads") for controller_name in self.controllers: self.controllers[controller_name].spawn() def all_threads_initialized(self): """ Checks that all recipe, peripheral, and controller theads are initialized. """ if self.state.recipe["mode"] == Modes.INIT: return False elif not self.all_peripherals_initialized(): return False elif not self.all_controllers_initialized(): return False return True def all_peripherals_initialized(self): """ Checks that all peripheral threads have transitioned from INIT. """ for peripheral_name in self.state.peripherals: peripheral_state = self.state.peripherals[peripheral_name] # Check if mode in peripheral state if "mode" not in peripheral_state: return False # Check if mode either init or setup if peripheral_state["mode"] == Modes.INIT: return False return True def all_controllers_initialized(self): """ Checks that all controller threads have transitioned from INIT. """ for controller_name in self.state.controllers: controller_state = self.state.controllers[controller_name] if controller_state["mode"] == Modes.INIT: return False return True def kill_peripheral_threads(self): """Kills all peripheral threads.""" for peripheral_name, peripheral_manager in self.peripherals.items(): peripheral_manager.thread_is_active = False def kill_controller_threads(self): """Kills all controller threads.""" for controller_name, controller_manager in self.controllers.items(): controlled_manager.thread_is_active = False