async def simulate( command: Command, nodes: Dict[str, Node], glob: str, delay=5.0, random: bool = False, ): """Simulate images being written to disk.""" # We make GLOB not required so that --stop doesn't need to specify a path, # but in this callback we want it defined. if glob is None: return command.fail( error="A valid path pattern is required to start the simulation.") images = globlib.glob(glob) if len(images) == 0: return command.fail(error="No images found.") sample = globlib.glob(glob) if random: randomlib.shuffle(sample) images_cycle = itertools.cycle(sample) while True: for node in nodes: command.info(filename=[node, node, next(images_cycle)]) await asyncio.sleep(delay)
async def status(command: Command, controllers: dict[str, IebController]): """Returns the status of wago sensor.""" #loop = asyncio.get_running_loop() for wago in controllers: if controllers[wago].name == 'wago': try: wago_status1 = await controllers[wago].getWAGOEnv() if wago_status1: command.info(text="Temperature & Humidity is:",status={ "rhtT1(40001)":controllers[wago].sensors['rhtT1(40001)'], "rhtRH1(40002)":controllers[wago].sensors['rhtRH1(40002)'], "rhtT2(40003)":controllers[wago].sensors['rhtT2(40003)'], "rhtRH2(40004)":controllers[wago].sensors['rhtRH2(40004)'], "rtd1(40009)":controllers[wago].sensors['rtd1(40009)'], "rtd2(40010)":controllers[wago].sensors['rtd2(40010)'], "rtd3(40011)":controllers[wago].sensors['rtd3(40011)'], "rtd4(40012)":controllers[wago].sensors['rtd4(40012)'] }) else: return command.fail(text=f"ERROR: Did not read sensors/powers") except LvmIebError as err: return command.fail(error=str(err)) return command.finish()
async def status( command: Command, controllers: dict[str, ArchonController], controller_name: str, ): """Reads the frame status.""" if controller_name not in controllers: return command.fail(f"Controller {controller_name!r} does not exist.") controller = controllers[controller_name] if not check_controller(command, controller): return command.fail() try: frame = await controller.get_frame() except ArchonError as err: return command.fail(text={ "controller": controller.name, "text": err, }) return command.finish(frame={ "controller": controller.name, **frame, })
async def read( command: Command, controllers: dict[str, ArchonController], controller_name: str, save: bool, ): """Reads the configuration from the controller.""" if controller_name not in controllers: return command.fail(f"Controller {controller_name!r} does not exist.") controller = controllers[controller_name] if not check_controller(command, controller): return command.fail() if save: path: str | bool = os.path.expanduser( f"~/archon_{controller.name}.acf") else: path: str | bool = False try: config = await controller.read_config(save=path) except ArchonError as err: return command.fail(text={ "controller": controller.name, "text": str(err), }) if save is False: return command.finish(config={ "controller": controller.name, "config": config, }) return command.finish(text=f"Config written to {path!r}")
async def fetch( command: Command, controllers: dict[str, ArchonController], controller_name: str, buffer: str, file, ): """Low-level command to fetch a buffer and write it to disk.""" if controller_name not in controllers: return command.fail(f"Controller {controller_name!r} does not exist.") controller = controllers[controller_name] if not check_controller(command, controller): return command.fail() buffer_no = int(buffer) if file is None: # Save to ~/archon_<controller_name>_NNNN.fits. Find the highest file with that # format and increase the counter. pattern = os.path.expanduser( f"~/archon_{controller.name}_[0-9][0-9][0-9][0-9].fits") existing = glob(pattern) if len(existing) == 0: nfile = 1 else: last = sorted(existing)[-1] nfile = int(re.search(r"([0-9]{4})\.fits$", last)[1]) + 1 path = os.path.expanduser( f"~/archon_{controller.name}_{nfile:04d}.fits") else: path: str = os.path.relpath(str(file)) dirname = os.path.dirname(path) if not os.path.exists(dirname): return command.fail( error= "Parent of file does not exists or file is badly formatted.") def notifier(msg): command.info(text={ "controller": controller.name, "text": msg, }) try: array = await controller.fetch(buffer_no, notifier=notifier) except BaseException as err: return command.fail( text={ "controller": controller.name, "text": f"Failed fetching data: {str(err)}", }) # Create a simple HDU list with the data. hdu = astropy.io.fits.PrimaryHDU(data=array) hdulist = astropy.io.fits.HDUList([hdu]) hdulist.writeto(path, overwrite=True) return command.finish(f"File saved to {path}")
def error_controller(command: Command, controller: ArchonController, message: str): """Issues a ``error_controller`` message.""" command.error(text={ "controller": controller.name, "text": message, }) return False
async def status(command: Command, nodes: Dict[str, Node]): """Outputs the status of the nodes and containers.""" enabled_nodes = [node for node in nodes.values() if node.enabled] command.info(enabledNodes=[node.name for node in enabled_nodes]) for node in enabled_nodes: await node.report_status(command) command.finish()
def _output( command: Command, controller: ArchonController, text: str, message_code="d", ): command.write( message_code=message_code, text=dict( controller=controller.name, text=text, ), )
async def status(command: Command, controllers: dict[str, IebController]): #return the status of shutter. print(controllers) command.info(text="Checking all shutters") tasks = [] for shutter in controllers: if controllers[shutter].name == 'shutter': try: tasks.append(controllers[shutter].send_command("status")) except LvmIebError as err: return command.fail(error=str(err)) result_shutter = await asyncio.gather(*tasks) for n in result_shutter: try: if n == "opened": return command.info(status={ "opened/closed:": n, }) elif n == "closed": return command.info(status={ "opened/closed:": n, }) else: return command.fail(test='shutter is in a bad state') except LvmIebError as err: return command.fail(error=str(err)) return command.finish()
async def system(command: Command, controller: ArchonController): """Reports the status of the controller backplane.""" if not check_controller(command, controller): return try: system = await controller.get_system() except ArchonError as ee: return error_controller(command, controller, str(ee)) command.info(system={ "controller": controller.name, **system, }) return True
async def enable( command: Command, nodes: Dict[str, Node], nodes_to_enable: List[str], all: bool, ): """Enables one or multiple cameras.""" if all is True: nodes_to_enable = list(nodes) for name in nodes_to_enable: if name not in nodes: command.warning(text=f"Cannot find node {name}.") continue nodes[name].enabled = True command.finish()
async def status(command: Command, controller: ArchonController): """Reports the status of the controller.""" if not check_controller(command, controller): return try: status = await controller.get_status() except ArchonError as ee: return error_controller(command, controller, str(ee)) command.info( status={ "controller": controller.name, "status": controller.status.name, **status, }) return True
async def status(command: Command, switches: [], name: str, portnum: int): """print the status of the NPS.""" status = {} for switch in switches: try: # status |= await switch.statusAsJson(name, portnum) works only with python 3.9 status = dict( list(status.items()) + list((await switch.statusAsJson(name, portnum)).items())) except PowerException as ex: return command.fail(error=str(ex)) command.info(STATUS=status) return command.finish("done")
async def test_send_command_from_command(actor, mocker): send_command_mock = mocker.patch.object(actor.tron, "send_command") command = Command( command_string="", commander_id="APO.Jose", command_id=5, actor=actor, ) command.send_command("otheractor", "command1 --option") send_command_mock.assert_called_once_with( "otheractor", "command1 --option", commander="APO.Jose.otheractor", mid=None, callback=None, time_limit=None, )
async def init(command: Command, controllers: dict[str, IebController]): #return the status of hartmann. command.info(text="Checking all hartmanns") tasks = [] for h in controllers: if controllers[h].name == 'hartmann_right': try: tasks.append(controllers[h].initialize()) except LvmIebError as err: return command.fail(error=str(err)) if controllers[h].name == 'hartmann_left': try: tasks.append(controllers[h].initialize()) except LvmIebError as err: return command.fail(error=str(err)) await asyncio.gather(*tasks) return command.finish()
async def close(command: Command, controllers: dict[str, IebController]): """close the shutter""" tasks = [] for shutter in controllers: if controllers[shutter].name == 'shutter': try: tasks.append(controllers[shutter].send_command("close")) except LvmIebError as err: return command.fail(error=str(err)) command.info(text="Closing all shutters") print("----close----") current_time = datetime.datetime.now() print('before command gathered : %s', current_time) await asyncio.gather(*tasks) current_time = datetime.datetime.now() print('after command gathered : %s', current_time) return command.finish(shutter="closed")
async def reconnect(command: Command, controller: ArchonController, timeout: float): """Restarts the socket connection to the controller(s).""" name = controller.name connect_timeout = timeout or command.actor.config["timeouts"][ "controller_connect"] if controller.is_connected(): try: await asyncio.wait_for(controller.stop(), timeout=timeout or 1) except BaseException as err: command.warning(text=f"Failed disconnecting from {name!r} with " f"error {err}. Will try to reconnect.") try: await asyncio.wait_for(controller.start(), timeout=connect_timeout) except asyncio.TimeoutError: command.error( error=f"Timed-out while reconnecting to controller {name!r}.") return False except BaseException as err: command.error( error= f"Unexpected error while connecting to controller {name!r}: {err}") return False return True
async def setpower(command: Command, controllers: dict[str, IebController]): """Returns the status of wago sensor.""" #loop = asyncio.get_running_loop() for wago in controllers: if controllers[wago].name == 'wago': try: wago_status1 = await controllers[wago].setWAGOPower("hartmann_right_power", 'ON') if wago_status1: command.info(text="Power state of the components are:",status={ "shutter_power":controllers[wago].power_status["shutter_power"], "hartmann_right_power":controllers[wago].power_status["hartmann_right_power"], "hartmann_left_power":controllers[wago].power_status["hartmann_left_power"] }) else: return command.fail(text=f"ERROR: Did not read sensors/powers") except LvmIebError as err: return command.fail(error=str(err)) return command.finish()
async def write( command: Command, controllers: dict[str, ArchonController], controller_name: str, config_path: click.Path, applyall: bool, poweron: bool, ): """Writes a configuration file to the controller.""" if controller_name not in controllers: return command.fail(f"Controller {controller_name!r} does not exist.") controller = controllers[controller_name] if not check_controller(command, controller): return command.fail() path = str(config_path) def notifier(msg): command.info(text={ "controller": controller.name, "text": msg, }) try: await controller.write_config( path, applyall=applyall, poweron=poweron, notifier=notifier, ) except ArchonError as err: return command.fail(text={ "controller": controller.name, "text": str(err), }) return command.finish(text=f"Config file {path!r} successfully loaded.")
async def test_send_command_from_command(amqp_actor, mocker): send_command_mock = mocker.patch.object(amqp_actor.connection.exchange, "publish") command = Command( command_string="", commander_id="APO.Jose", command_id=5, actor=amqp_actor, ) await command.send_command("otheractor", "command1 --option") send_command_mock.assert_called()
async def close(command: Command, controllers: dict[str, IebController], side: str): """close the hartmann""" #side = "all" tasks = [] for hartmann in controllers: if side == "all" or side == "right": if controllers[hartmann].name == 'hartmann_right': try: tasks.append(controllers[hartmann].send_command("close")) except LvmIebError as err: return command.fail(error=str(err)) if side == "all" or side == "left": if controllers[hartmann].name == 'hartmann_left': try: tasks.append(controllers[hartmann].send_command("close")) except LvmIebError as err: return command.fail(error=str(err)) command.info(text="Closing all hartmanns") await asyncio.gather(*tasks) return command.finish(hartmann="closed")
async def test_json_actor_command_write(json_actor, json_client): command = Command( command_string="ping", actor=json_actor, ) command.set_status("RUNNING") command.write("i", text="Pong") assert len(command.replies) == 2 assert command.replies[0].message_code == ">"
async def test_write_exception(actor): command = Command( command_string="ping", actor=actor, ) command.set_status("RUNNING") command.write("e", error=ValueError("Error message")) assert len(command.replies) == 2 assert command.replies[1].message_code == "e" assert command.replies[1].message["error"] == "Error message"
async def status(command: Command, controllers: dict[str, IebController]): #return the status of hartmann. command.info(text="Checking all hartmanns") tasks = [] print(controllers) for h in controllers: print(controllers[h].name) if controllers[h].name == 'hartmann_right': print(controllers[h].name, controllers[h].host, controllers[h].port) try: tasks.append(controllers[h].get_status()) except LvmIebError as err: return command.fail(error=str(err)) if controllers[h].name == 'hartmann_left': print(controllers[h].name, controllers[h].host, controllers[h].port) try: tasks.append(controllers[h].get_status()) except LvmIebError as err: return command.fail(error=str(err)) result_hartmann = await asyncio.gather(*tasks) print(result_hartmann) try: command.info( status={ "hartmann_left opened/closed:": result_hartmann[0], "hartmann_right opened/closed:": result_hartmann[1], }) except LvmIebError as err: return command.fail(error=str(err)) return command.finish()
async def test_write_exception(amqp_actor): command = Command( command_string="ping", actor=amqp_actor, ) command.set_status("RUNNING") command.write("e", error=ValueError("Error message")) assert len(command.replies) == 2 assert command.replies[1].message_code == "e" assert command.replies.get("error") == { "exception_module": "builtins", "exception_type": "ValueError", "exception_message": "Error message", }
async def talk( command: Command, nodes: Dict[str, Node], camera_command: str, names: str, category: str, ): """Sends a command to selected or all cameras.""" assert command.actor camera_command = " ".join(camera_command) c_nodes = select_nodes(nodes, category, names) node_names = [node.name for node in c_nodes if node.enabled] flicameras = command.actor.flicameras connected_nodes = [] for name in node_names: if flicameras[name].is_connected(): connected_nodes.append(name) continue command.warning(text=f"Failed connecting to {name}.") dev_commands = [] for name in connected_nodes: dev_commands.append(flicameras[name].send_message( command, camera_command)) await asyncio.gather(*dev_commands, return_exceptions=True) # Check if the device commands returned "filename" keywords. If so, # bundle them in a single keyword list that indicates all the filenames # for exposures taken together. filenames = [] for reply in command.replies: if "filename" in reply.message: filenames.append(reply.message["filename"][1]["filename"]) if len(filenames) > 0: command.info(filename_bundle=filenames) command.finish()
async def report_status( self, command: Command, volumes: bool = True, containers: bool = True, ): """Reports the status of the node to an actor. Parameters ---------- command The command that is requesting the status. volumes Whether to report the volumes connected to the node Docker engine. containers Whether to report the containers running. Only reports running containers whose ancestor matches the ``config['image']``. Notes ----- Outputs the ``node`` keyword, with format ``node={node_name, addr, daemon_addr, node_alive, docker_alive}``. If ``containers=True``, outputs the ``container`` keyword with format ``container={node_name, container_short_id}``. If ``volumes=True``, reports the ``volume`` keyword with format ``volume={node_name, volume, ping, docker_client}`` """ status = [self.name, self.addr, self.daemon_addr, False, False] config = command.actor.config if not self.client: command.warning(f"Node {self.addr} has no client.") return if not (await self.ping(timeout=config["ping_timeout"])): command.warning(text=f"Node {self.addr} is not pinging back.") command.info(node=status) if self.client: self.client.close() return status[3] = True # The NUC is responding. if not (await self.client_alive()): command.warning(text=f"Docker client on node {self.addr} is not connected.") command.info(node=status) if self.client: self.client.close() return status[4] = True command.info(node=status) if containers: image = config["image"].split(":")[0] if config["registry"]: image = config["registry"] + "/" + image container_list: List[Any] = await self._run( self.client.containers.list, all=True, filters={"ancestor": image, "status": "running"}, ) if len(container_list) == 0: command.warning(text=f"No containers running on {self.addr}.") command.debug(container=[self.name, "NA"]) elif len(container_list) > 1: command.warning( text=f"Multiple containers with image {image} " f"running on node {self.addr}." ) command.debug(container=[self.name, "NA"]) else: command.debug(container=[self.name, container_list[0].short_id]) if volumes: for vname in config["volumes"]: volume: Any = await self.get_volume(vname) if volume is False: command.warning(text=f"Volume {vname} not present in {self.name}.") command.debug(volume=[self.name, vname, False, "NA"]) continue command.debug( volume=[self.name, vname, True, volume.attrs["Options"]["device"]] )
async def reconnect( command: Command, nodes: Dict[str, Node], names: str, category: str, force: bool, ): """Recreates volumes and restarts the Docker containers.""" assert command.actor config = command.actor.config async def reconnect_node(node): """Reconnect sync. Will be run in an executor.""" actor = command.actor assert actor try: await node.connect() if not (await node.connected()): raise ConnectionError() except ConnectionError: command.warning(text=f"Node {node.name} is not pinging back or " "the Docker daemon is not running. Try " "rebooting the computer.") return # Stop container first, because we cannot remove volumes that are # attached to running containers. await node.stop_container( config["container_name"] + f"-{node.name}", config["image"], force=True, command=command, ) for vname in config["volumes"]: vconfig = config["volumes"][vname] await node.create_volume( vname, driver=vconfig["driver"], opts=vconfig["opts"], force=force, command=command, ) return await node.run_container( actor.get_container_name(node), config["image"], volumes=list(config["volumes"]), privileged=True, registry=config["registry"], ports=[config["nodes"][actor.observatory][node.name]["port"]], envs={ "ACTOR_NAME": node.name, "OBSERVATORY": actor.observatory }, force=True, command=command, ) c_nodes = select_nodes(nodes, category, names) # Drop the device before doing anything with the containers, or we'll # get weird hangups. for node in c_nodes: node_name = node.name device = command.actor.flicameras[node_name] if device.is_connected(): await device.stop() await asyncio.gather(*[reconnect_node(node) for node in c_nodes]) command.info(text="Waiting 5 seconds before reconnecting the devices ...") await asyncio.sleep(5) for node in c_nodes: container_name = config["container_name"] + f"-{node.name}" if not (await node.is_container_running(container_name)): continue device = command.actor.flicameras[node.name] await device.restart() if device.is_connected(): port = device.port await node.report_status(command) command.debug( text=f"{node.name}: reconnected to device on port {port}.") else: command.warning(text=f"{node.name}: failed to connect to device.") command.finish()
async def offall(command: Command, switches: [], name: str): """Turn off all Outlet""" command.info(STATUS=await switch_control(switches, False, 0, name)) return command.finish(text="done")
async def off(command: Command, switches: [], name: str, portnum: int): """Turn off the Outlet""" command.info(STATUS=await switch_control(switches, False, name, portnum)) return command.finish(text="done")