From 65e15d2b30633f09d41e2301ca2a9e1768e31446 Mon Sep 17 00:00:00 2001 From: Brian Koopman Date: Wed, 7 May 2025 10:12:15 -0400 Subject: [PATCH 1/4] Split StarcamHelper class to drivers module --- socs/agents/starcam_lat/__init__.py | 0 socs/agents/starcam_lat/agent.py | 123 +-------------------------- socs/agents/starcam_lat/drivers.py | 124 ++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 122 deletions(-) create mode 100644 socs/agents/starcam_lat/__init__.py create mode 100644 socs/agents/starcam_lat/drivers.py diff --git a/socs/agents/starcam_lat/__init__.py b/socs/agents/starcam_lat/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/socs/agents/starcam_lat/agent.py b/socs/agents/starcam_lat/agent.py index 2fa45d623..bd89a4c0c 100644 --- a/socs/agents/starcam_lat/agent.py +++ b/socs/agents/starcam_lat/agent.py @@ -1,6 +1,5 @@ import argparse import socket -import struct import time from os import environ @@ -8,127 +7,7 @@ from ocs import ocs_agent, site_config from ocs.ocs_twisted import TimeoutLock - -class StarcamHelper: - """Functions to control and retrieve data from the starcam. - - Parameters - ---------- - ip_addres: str - IP address of the starcam computer. - port: int - Port of the starcam computer. - timeout: float - Socket connection timeout in seconds. Defaults to 10 seconds. - - """ - - def __init__(self, ip_address, port, timeout=10): - self.ip = ip_address - self.port = port - self.server_addr = (self.ip, self.port) - self.comm = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.comm.connect(self.server_addr) - self.comm.settimeout(timeout) - - def pack_and_send_cmds(self): - """Packs commands and parameters to be sent to the starcam and sends. - - Returns: - list: Values sent to the starcam. - - """ - logodds = 1e8 - latitude = -22.9586 - longitude = -67.7875 - height = 5200.0 - exposure = 700 - timelimit = 1 - set_focus_to_amount = 0 - auto_focus_bool = 1 - start_focus = 0 - end_focus = 0 - step_size = 5 - photos_per_focus = 3 - infinity_focus_bool = 0 - set_aperture_steps = 0 - max_aperture_bool = 0 - make_HP_bool = 0 - use_HP_bool = 0 - spike_limit_value = 3 - dynamic_hot_pixels_bool = 1 - r_smooth_value = 2 - high_pass_filter_bool = 0 - r_high_pass_filter_value = 10 - centroid_search_border_value = 1 - filter_return_image_bool = 0 - n_sigma_value = 2 - star_spacing_value = 15 - values = [logodds, - latitude, - longitude, - height, - exposure, - timelimit, - set_focus_to_amount, - auto_focus_bool, - start_focus, - end_focus, - step_size, - photos_per_focus, - infinity_focus_bool, - set_aperture_steps, - max_aperture_bool, - make_HP_bool, - use_HP_bool, - spike_limit_value, - dynamic_hot_pixels_bool, - r_smooth_value, - high_pass_filter_bool, - r_high_pass_filter_value, - centroid_search_border_value, - filter_return_image_bool, - n_sigma_value, - star_spacing_value] - # Pack values into the command for the camera - self.cmds_for_camera = struct.pack('ddddddfiiiiiiiiiifffffffff', - *values) - # send commands to the camera - self.comm.sendto(self.cmds_for_camera, (self.ip, self.port)) - print("Commands sent to camera.") - # Return the list of values - return values - - def get_astrom_data(self): - """Receives and unpacks data from the starcam. - - Returns: - dict: Dictionary of unpacked data. - """ - (scdata_raw, _) = self.comm.recvfrom(256) - data = struct.unpack_from("dddddddddddddiiiiiiiiddiiiiiiiiiiiiiifiii", - scdata_raw) - keys = ['c_time', - 'gmt', - 'blob_num', - 'obs_ra', - 'astrom_ra', - 'obs_dec', - 'fr', - 'ps', - 'alt', - 'az', - 'ir', - 'astrom_solve_time', - 'camera_time'] - # Create a dictionary of the unpacked data - astrom_data = [data[i] for i in range(len(keys))] - astrom_data_dict = {keys[i]: astrom_data[i] for i in range(len(keys))} - return astrom_data_dict - - def close(self): - """Closes the socket connection.""" - self.comm.close() +from socs.agents.starcam_lat.drivers import StarcamHelper class StarcamAgent: diff --git a/socs/agents/starcam_lat/drivers.py b/socs/agents/starcam_lat/drivers.py new file mode 100644 index 000000000..4e5125817 --- /dev/null +++ b/socs/agents/starcam_lat/drivers.py @@ -0,0 +1,124 @@ +import socket +import struct + + +class StarcamHelper: + """Functions to control and retrieve data from the starcam. + + Parameters + ---------- + ip_addres: str + IP address of the starcam computer. + port: int + Port of the starcam computer. + timeout: float + Socket connection timeout in seconds. Defaults to 10 seconds. + + """ + + def __init__(self, ip_address, port, timeout=10): + self.ip = ip_address + self.port = port + self.server_addr = (self.ip, self.port) + self.comm = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.comm.connect(self.server_addr) + self.comm.settimeout(timeout) + + def pack_and_send_cmds(self): + """Packs commands and parameters to be sent to the starcam and sends. + + Returns: + list: Values sent to the starcam. + + """ + logodds = 1e8 + latitude = -22.9586 + longitude = -67.7875 + height = 5200.0 + exposure = 700 + timelimit = 1 + set_focus_to_amount = 0 + auto_focus_bool = 1 + start_focus = 0 + end_focus = 0 + step_size = 5 + photos_per_focus = 3 + infinity_focus_bool = 0 + set_aperture_steps = 0 + max_aperture_bool = 0 + make_HP_bool = 0 + use_HP_bool = 0 + spike_limit_value = 3 + dynamic_hot_pixels_bool = 1 + r_smooth_value = 2 + high_pass_filter_bool = 0 + r_high_pass_filter_value = 10 + centroid_search_border_value = 1 + filter_return_image_bool = 0 + n_sigma_value = 2 + star_spacing_value = 15 + values = [logodds, + latitude, + longitude, + height, + exposure, + timelimit, + set_focus_to_amount, + auto_focus_bool, + start_focus, + end_focus, + step_size, + photos_per_focus, + infinity_focus_bool, + set_aperture_steps, + max_aperture_bool, + make_HP_bool, + use_HP_bool, + spike_limit_value, + dynamic_hot_pixels_bool, + r_smooth_value, + high_pass_filter_bool, + r_high_pass_filter_value, + centroid_search_border_value, + filter_return_image_bool, + n_sigma_value, + star_spacing_value] + # Pack values into the command for the camera + self.cmds_for_camera = struct.pack('ddddddfiiiiiiiiiifffffffff', + *values) + # send commands to the camera + self.comm.sendto(self.cmds_for_camera, (self.ip, self.port)) + print("Commands sent to camera.") + # Return the list of values + return values + + def get_astrom_data(self): + """Receives and unpacks data from the starcam. + + Returns: + dict: Dictionary of unpacked data. + """ + (scdata_raw, _) = self.comm.recvfrom(256) + data = struct.unpack_from("dddddddddddddiiiiiiiiddiiiiiiiiiiiiiifiii", + scdata_raw) + keys = ['c_time', + 'gmt', + 'blob_num', + 'obs_ra', + 'astrom_ra', + 'obs_dec', + 'fr', + 'ps', + 'alt', + 'az', + 'ir', + 'astrom_solve_time', + 'camera_time'] + # Create a dictionary of the unpacked data + astrom_data = [data[i] for i in range(len(keys))] + astrom_data_dict = {keys[i]: astrom_data[i] for i in range(len(keys))} + return astrom_data_dict + + def close(self): + """Closes the socket connection.""" + self.comm.close() From 3599f8a28b60a284ca126fa1370ffcbcd539ada5 Mon Sep 17 00:00:00 2001 From: Brian Koopman Date: Wed, 7 May 2025 10:32:47 -0400 Subject: [PATCH 2/4] Split pack_and_send_cmds() Just to facilitate testing and use of the TCPInterface class. --- socs/agents/starcam_lat/agent.py | 2 +- socs/agents/starcam_lat/drivers.py | 23 +++++++++++++---------- tests/agents/test_starcam_lat_agent.py | 6 ++++++ 3 files changed, 20 insertions(+), 11 deletions(-) create mode 100644 tests/agents/test_starcam_lat_agent.py diff --git a/socs/agents/starcam_lat/agent.py b/socs/agents/starcam_lat/agent.py index bd89a4c0c..1bd03e4d7 100644 --- a/socs/agents/starcam_lat/agent.py +++ b/socs/agents/starcam_lat/agent.py @@ -61,7 +61,7 @@ def send_commands(self, session, params=None): f"{self.lock.job} is already running.") return False, "Could not acquire lock." self.log.info("Sending commands.") - self.starcam.pack_and_send_cmds() + self.starcam.send_cmds() return True, "Sent commands to the starcam." @ocs_agent.param('_') diff --git a/socs/agents/starcam_lat/drivers.py b/socs/agents/starcam_lat/drivers.py index 4e5125817..20e5b1506 100644 --- a/socs/agents/starcam_lat/drivers.py +++ b/socs/agents/starcam_lat/drivers.py @@ -24,11 +24,19 @@ def __init__(self, ip_address, port, timeout=10): self.comm.connect(self.server_addr) self.comm.settimeout(timeout) - def pack_and_send_cmds(self): - """Packs commands and parameters to be sent to the starcam and sends. + def send_cmds(self): + """Send commands and parameters to the starcam.""" + cmds = self._pack_cmds() + # send commands to the camera + self.comm.sendto(cmds, (self.ip, self.port)) + print("Commands sent to camera.") + + @staticmethod + def _pack_cmds(): + """Packs commands and parameters to be sent to the starcam. Returns: - list: Values sent to the starcam. + bytes: Packed bytes object to send to the starcam. """ logodds = 1e8 @@ -83,14 +91,9 @@ def pack_and_send_cmds(self): filter_return_image_bool, n_sigma_value, star_spacing_value] + # Pack values into the command for the camera - self.cmds_for_camera = struct.pack('ddddddfiiiiiiiiiifffffffff', - *values) - # send commands to the camera - self.comm.sendto(self.cmds_for_camera, (self.ip, self.port)) - print("Commands sent to camera.") - # Return the list of values - return values + return struct.pack('ddddddfiiiiiiiiiifffffffff', *values) def get_astrom_data(self): """Receives and unpacks data from the starcam. diff --git a/tests/agents/test_starcam_lat_agent.py b/tests/agents/test_starcam_lat_agent.py new file mode 100644 index 000000000..356fcdfbf --- /dev/null +++ b/tests/agents/test_starcam_lat_agent.py @@ -0,0 +1,6 @@ +from socs.agents.starcam_lat.drivers import StarcamHelper # noqa: F401 + + +def test__pack_cmds(): + cmds = StarcamHelper._pack_cmds() + assert cmds == b'\x00\x00\x00\x00\x84\xd7\x97A\x13\xf2A\xcff\xf56\xc0fffff\xf2P\xc0\x00\x00\x00\x00\x00P\xb4@\x00\x00\x00\x00\x00\xe0\x85@\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@@\x00\x00\x80?\x00\x00\x00@\x00\x00\x00\x00\x00\x00 A\x00\x00\x80?\x00\x00\x00\x00\x00\x00\x00@\x00\x00pA' From c6a73669bde719153cc2efda68e19be3d286f4ae Mon Sep 17 00:00:00 2001 From: Brian Koopman Date: Wed, 7 May 2025 10:50:32 -0400 Subject: [PATCH 3/4] Split get_astrom_data() Again to facilitate testing and use of the TCPInterface class. --- socs/agents/starcam_lat/drivers.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/socs/agents/starcam_lat/drivers.py b/socs/agents/starcam_lat/drivers.py index 20e5b1506..7c78c6d20 100644 --- a/socs/agents/starcam_lat/drivers.py +++ b/socs/agents/starcam_lat/drivers.py @@ -102,8 +102,12 @@ def get_astrom_data(self): dict: Dictionary of unpacked data. """ (scdata_raw, _) = self.comm.recvfrom(256) + return self._unpack_response(scdata_raw) + + @staticmethod + def _unpack_response(response): data = struct.unpack_from("dddddddddddddiiiiiiiiddiiiiiiiiiiiiiifiii", - scdata_raw) + response) keys = ['c_time', 'gmt', 'blob_num', @@ -117,6 +121,7 @@ def get_astrom_data(self): 'ir', 'astrom_solve_time', 'camera_time'] + # Create a dictionary of the unpacked data astrom_data = [data[i] for i in range(len(keys))] astrom_data_dict = {keys[i]: astrom_data[i] for i in range(len(keys))} From c90e41e299512ed26846e295df31cc15356baad4 Mon Sep 17 00:00:00 2001 From: Brian Koopman Date: Wed, 7 May 2025 11:13:17 -0400 Subject: [PATCH 4/4] Convert StarcamHelper to TCPInterface subclass --- socs/agents/starcam_lat/agent.py | 21 +++++++++++++-------- socs/agents/starcam_lat/drivers.py | 23 +++++++---------------- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/socs/agents/starcam_lat/agent.py b/socs/agents/starcam_lat/agent.py index 1bd03e4d7..588d00c7e 100644 --- a/socs/agents/starcam_lat/agent.py +++ b/socs/agents/starcam_lat/agent.py @@ -1,5 +1,4 @@ import argparse -import socket import time from os import environ @@ -38,14 +37,11 @@ def __init__(self, agent, ip_address, port): self.log = agent.log self.take_data = False self.lock = TimeoutLock() + self.starcam = StarcamHelper(ip_address, port) + agg_params = {'frame_length': 60} self.agent.register_feed("starcamera", record=True, agg_params=agg_params, buffer_time=1) - try: - self.starcam = StarcamHelper(ip_address, port) - except socket.timeout: - self.log.error("Starcam connection has timed out.") - return False, "Timeout" @ocs_agent.param('_') def send_commands(self, session, params=None): @@ -62,6 +58,7 @@ def send_commands(self, session, params=None): return False, "Could not acquire lock." self.log.info("Sending commands.") self.starcam.send_cmds() + self.log.info("Commands sent to camera.") return True, "Sent commands to the starcam." @ocs_agent.param('_') @@ -110,8 +107,16 @@ def acq(self, session, params=None): 'block_name': 'astrometry', 'data': {} } - # get astrometry data - astrom_data_dict = self.starcam.get_astrom_data() + try: + astrom_data_dict = self.starcam.get_astrom_data() + if session.degraded: + self.log.info("Connection re-established.") + session.degraded = False + except ConnectionError: + self.log.error("Failed to get data from star camera. Check network connection.") + session.degraded = True + time.sleep(1) + continue # update the data dictionary+session and publish data['data'].update(astrom_data_dict) session.data.update(data['data']) diff --git a/socs/agents/starcam_lat/drivers.py b/socs/agents/starcam_lat/drivers.py index 7c78c6d20..6039c6d41 100644 --- a/socs/agents/starcam_lat/drivers.py +++ b/socs/agents/starcam_lat/drivers.py @@ -1,8 +1,9 @@ -import socket import struct +from socs.tcp import TCPInterface -class StarcamHelper: + +class StarcamHelper(TCPInterface): """Functions to control and retrieve data from the starcam. Parameters @@ -17,19 +18,13 @@ class StarcamHelper: """ def __init__(self, ip_address, port, timeout=10): - self.ip = ip_address - self.port = port - self.server_addr = (self.ip, self.port) - self.comm = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.comm.connect(self.server_addr) - self.comm.settimeout(timeout) + # Set up the TCP Interface + super().__init__(ip_address, port, timeout) def send_cmds(self): """Send commands and parameters to the starcam.""" cmds = self._pack_cmds() - # send commands to the camera - self.comm.sendto(cmds, (self.ip, self.port)) - print("Commands sent to camera.") + self.comm.send(cmds) @staticmethod def _pack_cmds(): @@ -101,7 +96,7 @@ def get_astrom_data(self): Returns: dict: Dictionary of unpacked data. """ - (scdata_raw, _) = self.comm.recvfrom(256) + scdata_raw = self.comm.recv(256) return self._unpack_response(scdata_raw) @staticmethod @@ -126,7 +121,3 @@ def _unpack_response(response): astrom_data = [data[i] for i in range(len(keys))] astrom_data_dict = {keys[i]: astrom_data[i] for i in range(len(keys))} return astrom_data_dict - - def close(self): - """Closes the socket connection.""" - self.comm.close()