diff --git a/blast_config/command_list.c b/blast_config/command_list.c index 114abc767..e5bb48800 100644 --- a/blast_config/command_list.c +++ b/blast_config/command_list.c @@ -254,6 +254,9 @@ struct scom scommands[xyzzy + 1] = { /* MISC */ + // acomp + {COMMAND(acomp_start_saving), "Telling ACOMP to start saving data", GR_MISC}, + {COMMAND(acomp_stop_saving), "Telling ACOMP to stop saving data", GR_MISC}, // HW watchdog and in charge {COMMAND(disallow_hw_wd), "Setting the HW watchdog to be ignored", GR_MISC | CONFIRM}, {COMMAND(allow_hw_wd), "Setting the HW watchdog to be listened to", GR_MISC | CONFIRM}, @@ -1550,6 +1553,12 @@ struct mcom mcommands[plugh + 2] = { }, /* MISC */ + // ACOMP + {COMMAND(acomp_save_n_seconds), "Tell ACOMP to save N seconds of data", GR_MISC, 1, + { + {"Length of timestream", 1, 1200, 'i', "NONE"} + } + }, // XY stage {COMMAND(xy_goto), "move the X-Y translation stage to absolute position", GR_MISC, 4, { diff --git a/blast_config/include/command_list.h b/blast_config/include/command_list.h index de2174585..e9f518f6f 100644 --- a/blast_config/include/command_list.h +++ b/blast_config/include/command_list.h @@ -219,6 +219,9 @@ enum singleCommand { sc2_trigger_off, /* MISC */ + // acomp + acomp_start_saving, + acomp_stop_saving, // HW watchdog disallow_hw_wd, allow_hw_wd, @@ -465,6 +468,8 @@ enum multiCommand { xsc_filter_matching, /* MISC */ + // acomp + acomp_save_n_seconds, // XY stage xy_goto, xy_jump, diff --git a/mcp/commanding/commands.c b/mcp/commanding/commands.c index 3ba8101d3..15a62ec69 100644 --- a/mcp/commanding/commands.c +++ b/mcp/commanding/commands.c @@ -868,7 +868,20 @@ void SingleCommand(enum singleCommand command, int scheduled) break; /* MISC */ - // HW WD and incharge + // acomp + case acomp_start_saving: + CommandData.acomp_commands.send_trigger = 1; + CommandData.acomp_commands.trigger_value = 1; + CommandData.acomp_commands.counting = 0; + CommandData.acomp_commands.countdown = 0; + break; + case acomp_stop_saving: + CommandData.acomp_commands.send_trigger = 1; + CommandData.acomp_commands.trigger_value = 0; + CommandData.acomp_commands.counting = 0; + CommandData.acomp_commands.countdown = 0; + break; + // HW watchdog and incharge case disallow_hw_wd: CommandData.bypass_HW_WD = 1; break; @@ -2518,6 +2531,13 @@ void MultiCommand(enum multiCommand command, double *rvalues, } /* MISC */ + // acomp + case acomp_save_n_seconds: + CommandData.acomp_commands.send_trigger = 1; + CommandData.acomp_commands.trigger_value = 1; + CommandData.acomp_commands.counting = 1; + CommandData.acomp_commands.countdown = ivalues[0]; + break; // XY stage case xy_goto: CommandData.xystage.x1 = ivalues[0]; @@ -2697,6 +2717,11 @@ void InitCommandData() // HW WD BYPASS MUST BE 0 ON STARTUP CommandData.bypass_HW_WD = 0; + CommandData.acomp_commands.countdown = 0; + CommandData.acomp_commands.send_trigger = 0; + CommandData.acomp_commands.trigger_value = 0; + CommandData.acomp_commands.counting = 0; + CommandData.sc_bools.sc1_command_bool = 1; CommandData.sc_bools.sc2_command_bool = 1; CommandData.sc_bools.sc1_image_bool = 1; diff --git a/mcp/communications/CMakeLists.txt b/mcp/communications/CMakeLists.txt index 9c2342563..02190dc3a 100644 --- a/mcp/communications/CMakeLists.txt +++ b/mcp/communications/CMakeLists.txt @@ -4,6 +4,8 @@ add_library (communications STATIC ${CMAKE_CURRENT_SOURCE_DIR}/pilot.c ${CMAKE_CURRENT_SOURCE_DIR}/highrate.c ${CMAKE_CURRENT_SOURCE_DIR}/evtm.c + ${CMAKE_CURRENT_SOURCE_DIR}/analysis_comp_sharing.c + ${CMAKE_CURRENT_SOURCE_DIR}/analysis_comp_trigger.c ) define_file_basename_for_sources(communications) diff --git a/mcp/communications/analysis_comp_sharing.c b/mcp/communications/analysis_comp_sharing.c new file mode 100644 index 000000000..fcfc7919e --- /dev/null +++ b/mcp/communications/analysis_comp_sharing.c @@ -0,0 +1,157 @@ +/* analysis_comp_sharing.c: TIM threads and functions for sharing pointing with acomp1 + * + * This software is copyright (C) 2026 University of Arizona + * + * This file is part of the BLAST flight code licensed under the GNU + * General Public License. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "socket_utils.h" +#include "analysis_comp_sharing.h" +#include "command_struct.h" +#include "tx.h" +#include "channels_tng.h" + +int proceed_with_loop_trigger = 0; + +/** + * @brief takes a pointer to a pointing data packet, sets up channel pointers, + * and fills the packet with the most recent data from each channel + * + * @param packet the pointer to the packet that we will fill from channels + */ +void fill_packet_from_channels(pointing_data* packet) { + static channel_t *az_Addr, *el_Addr, *lat_Addr, *lon_Addr; + static channel_t *height_Addr, *time_Addr, *time_usec_Addr; + static int first_time = 1; + if (first_time) { + first_time = 0; + az_Addr = channels_find_by_name("az"); + el_Addr = channels_find_by_name("el"); + lat_Addr = channels_find_by_name("lat"); + lon_Addr = channels_find_by_name("lon"); + height_Addr = channels_find_by_name("alt"); + time_Addr = channels_find_by_name("time"); + time_usec_Addr = channels_find_by_name("time_usec"); + } + GET_SCALED_VALUE(az_Addr, packet->az); + GET_SCALED_VALUE(el_Addr, packet->el); + GET_SCALED_VALUE(lat_Addr, packet->latitude); + GET_SCALED_VALUE(lon_Addr, packet->longitude); + GET_VALUE(height_Addr, packet->height); + GET_VALUE(time_Addr, packet->time); + GET_VALUE(time_usec_Addr, packet->time_usec); +} + +/** + * @brief clears the data stored in a pointing packet to ensure old data never leaks in + * + * @param packet the pointer to the packet that we will clear + */ +void clear_packet_data(pointing_data* packet) { + memset(packet, 0, sizeof(pointing_data)); +} + +/** + * @brief triggers the acomp send loop to prepare and + * send a packet of pointing data to the acomp + * has in charge protection to ensure that only the in charge + * computer is sending the data to the acomp + * + */ +void get_acomp_shared_data(void) { + if (InCharge) { + proceed_with_loop_trigger = 1; + } +} + +// here goes the actual thread that we call +void * send_pointing_data_acomp(void* args) { + pointing_data acomp_packet; + struct socketData * socket_target = args; + int first_time = 1; + int sockfd; + struct addrinfo hints; + struct addrinfo *servinfo; + struct addrinfo *servinfoCheck; + int returnval; + int bytes_sent; + int length; + int *retval; + char message_str[40]; + char ipAddr[INET_ADDRSTRLEN]; + while (1) { + if (first_time == 1) { + first_time = 0; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; // set to AF_INET to use IPv4 + hints.ai_socktype = SOCK_DGRAM; + // fill out address info and return if it fails. + if ((returnval = getaddrinfo(socket_target->ipAddr, socket_target->port, &hints, &servinfo)) != 0) { + blast_err("getaddrinfo: %s\n", gai_strerror(returnval)); + return NULL; + } + // now we make a socket with this info + for (servinfoCheck = servinfo; servinfoCheck != NULL; servinfoCheck = servinfoCheck->ai_next) { + if ((sockfd = socket(servinfoCheck->ai_family, + servinfoCheck->ai_socktype, servinfoCheck->ai_protocol)) == -1) { + perror("talker: socket"); + continue; + } + break; + } + // check to see if we made a socket + if (servinfoCheck == NULL) { + // set status to 0 (dead) if this fails + blast_err("talker: failed to create socket\n"); + return NULL; + } + // if we pass all of these checks then + // we set up the print statement vars + // need to cast the socket address to an INET still address + struct sockaddr_in *ipv = (struct sockaddr_in *)servinfo->ai_addr; + // then pass the pointer to translation and put it in a string + inet_ntop(AF_INET, &(ipv->sin_addr), ipAddr, INET_ADDRSTRLEN); + blast_info("IP target is: %s\n", ipAddr); + // now the "str" is packed with the IP address string + // first time setup of the socket is done + } + while (!proceed_with_loop_trigger) { + usleep(500); + } + fill_packet_from_channels(&acomp_packet); + if (!strcmp(socket_target->ipAddr, ipAddr)) { + length = sizeof(acomp_packet); + if ((bytes_sent = sendto(sockfd, &acomp_packet, length, 0, + servinfo->ai_addr, servinfo->ai_addrlen)) == -1) { + perror("talker: sendto"); + } + } else { + blast_err("Target destination %s differs from thread target %s.\n", socket_target->ipAddr, ipAddr); + } + proceed_with_loop_trigger = 0; + } + freeaddrinfo(servinfo); + close(sockfd); + return NULL; +}; + diff --git a/mcp/communications/analysis_comp_trigger.c b/mcp/communications/analysis_comp_trigger.c new file mode 100644 index 000000000..5b2c5103b --- /dev/null +++ b/mcp/communications/analysis_comp_trigger.c @@ -0,0 +1,122 @@ +/* analysis_comp_trigger.c: TIM threads and functions for triggering data save on acomp1 + * + * This software is copyright (C) 2026 University of Arizona + * + * This file is part of the BLAST flight code licensed under the GNU + * General Public License. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "socket_utils.h" +#include "analysis_comp_trigger.h" +#include "command_struct.h" +#include "tx.h" +#include "channels_tng.h" + +/** + * @brief function that goes in the 1hz loop to perform a delayed stop taking data countdown. + * + */ +void acomp_countdown_1hz(void) { + if (CommandData.acomp_commands.counting == 1) { + if (CommandData.acomp_commands.countdown == 0) { + CommandData.acomp_commands.counting = 0; + CommandData.acomp_commands.send_trigger = 1; + CommandData.acomp_commands.trigger_value = 0; + return; + } + CommandData.acomp_commands.countdown--; + } +} + + + +// here goes the actual thread that we call +void * send_data_trigger_acomp(void* args) { + struct trigger acomp_trigger; + struct socketData * socket_target = args; + int first_time = 1; + int sockfd; + struct addrinfo hints; + struct addrinfo *servinfo; + struct addrinfo *servinfoCheck; + int returnval; + int bytes_sent; + int length; + int *retval; + char message_str[40]; + char ipAddr[INET_ADDRSTRLEN]; + while (1) { + if (first_time == 1) { + first_time = 0; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; // set to AF_INET to use IPv4 + hints.ai_socktype = SOCK_DGRAM; + // fill out address info and return if it fails. + if ((returnval = getaddrinfo(socket_target->ipAddr, socket_target->port, &hints, &servinfo)) != 0) { + blast_err("getaddrinfo: %s\n", gai_strerror(returnval)); + return NULL; + } + // now we make a socket with this info + for (servinfoCheck = servinfo; servinfoCheck != NULL; servinfoCheck = servinfoCheck->ai_next) { + if ((sockfd = socket(servinfoCheck->ai_family, + servinfoCheck->ai_socktype, servinfoCheck->ai_protocol)) == -1) { + perror("talker: socket"); + continue; + } + break; + } + // check to see if we made a socket + if (servinfoCheck == NULL) { + // set status to 0 (dead) if this fails + blast_err("talker: failed to create socket\n"); + return NULL; + } + // if we pass all of these checks then + // we set up the print statement vars + // need to cast the socket address to an INET still address + struct sockaddr_in *ipv = (struct sockaddr_in *)servinfo->ai_addr; + // then pass the pointer to translation and put it in a string + inet_ntop(AF_INET, &(ipv->sin_addr), ipAddr, INET_ADDRSTRLEN); + blast_info("IP target is: %s\n", ipAddr); + // now the "str" is packed with the IP address string + // first time setup of the socket is done + } + if ((CommandData.acomp_commands.send_trigger == 1) && InCharge) { + acomp_trigger.take_data = CommandData.acomp_commands.trigger_value; // pull the value into the packet + CommandData.acomp_commands.send_trigger = 0; // don't try to send again + if (!strcmp(socket_target->ipAddr, ipAddr)) { + length = sizeof(acomp_trigger); + if ((bytes_sent = sendto(sockfd, &acomp_trigger, length, 0, + servinfo->ai_addr, servinfo->ai_addrlen)) == -1) { + perror("talker: sendto"); + } + } else { + blast_err("Target destination %s differs from thread target %s.\n", socket_target->ipAddr, ipAddr); + } + } else { + usleep(100000); + } + } + freeaddrinfo(servinfo); + close(sockfd); + return NULL; +}; diff --git a/mcp/communications/include/analysis_comp_sharing.h b/mcp/communications/include/analysis_comp_sharing.h new file mode 100644 index 000000000..a90f4cbce --- /dev/null +++ b/mcp/communications/include/analysis_comp_sharing.h @@ -0,0 +1,37 @@ +/* analysis_comp_sharing.h: TIM data structures and function primals mcp to acomp 1 data link + * + * This software is copyright (C) 2026 University of Arizona + * + * This file is part of the BLAST flight code licensed under the GNU + * General Public License. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef INCLUDE_ACOMP_SHARING_H +#define INCLUDE_ACOMP_SHARING_H + +#include "mcp.h" +#define ACOMP_POINTING_PORT "1213" + +extern int16_t InCharge; + +typedef struct pointing_data { // need to finish designing this + float az; + float el; + float latitude; + float longitude; + uint16_t height; + uint32_t time; + uint32_t time_usec; +} pointing_data; + +void fill_packet_from_channels(pointing_data* packet); +void clear_packet_data(pointing_data* packet); +void get_acomp_shared_data(void); +void * send_pointing_data_acomp(void* args); + +#endif \ No newline at end of file diff --git a/mcp/communications/include/analysis_comp_trigger.h b/mcp/communications/include/analysis_comp_trigger.h new file mode 100644 index 000000000..fbe9b8d74 --- /dev/null +++ b/mcp/communications/include/analysis_comp_trigger.h @@ -0,0 +1,30 @@ +/* analysis_comp_trigger.h: TIM data structures and function primals mcp to acomp 1 trigger data link + * + * This software is copyright (C) 2026 University of Arizona + * + * This file is part of the BLAST flight code licensed under the GNU + * General Public License. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef INCLUDE_ACOMP_TRIGGER_H +#define INCLUDE_ACOMP_TRIGGER_H + +#include "mcp.h" +#define ACOMP_TRIGGER_PORT "1212" + +extern int16_t InCharge; + +struct trigger { + int take_data; +}; + +void acomp_countdown_1hz(void); +void * send_data_trigger_acomp(void* args); + + +#endif \ No newline at end of file diff --git a/mcp/include/command_struct.h b/mcp/include/command_struct.h index 26ae27f1f..a5ed78e73 100644 --- a/mcp/include/command_struct.h +++ b/mcp/include/command_struct.h @@ -422,6 +422,13 @@ typedef struct { float param5; } rfsoc_commands_t; +typedef struct { + int send_trigger; + int trigger_value; + int countdown; + int counting; +} acomp_trigger_t; + /** * @brief full command data structure containing relevant cross-mcp information for commanding @@ -543,6 +550,7 @@ struct CommandDataStruct { int update_position_sc; // should we automatically append lat/lon/alt to command packets cryo_command_t cryo_command; + acomp_trigger_t acomp_commands; rfsoc_commands_t rfsoc_commands1; rfsoc_commands_t rfsoc_commands2; diff --git a/mcp/include/mcp.h b/mcp/include/mcp.h index d178b79b2..7e76d9d53 100644 --- a/mcp/include/mcp.h +++ b/mcp/include/mcp.h @@ -48,6 +48,10 @@ void force_incharge(void); #define DEFAULT_INCHARGE !SouthIAm +// TIM analysis computer IP Addrs +#define ACOMP1_IP "192.168.1.6" +#define ACOMP2_IP "192.168.1.7" + // telemetry defines // how many options we have diff --git a/mcp/mcp.c b/mcp/mcp.c index e1ef7acf5..66784be78 100644 --- a/mcp/mcp.c +++ b/mcp/mcp.c @@ -104,6 +104,8 @@ #include "star_camera_solutions.h" #include "star_camera_receive.h" #include "star_camera_trigger.h" +#include "analysis_comp_sharing.h" +#include "analysis_comp_trigger.h" /* Define global variables */ char* flc_ip[2] = {"192.168.1.3", "192.168.1.4"}; @@ -231,6 +233,7 @@ static void mcp_200hz_routines(void) store_200hz_acs(); command_motors(); write_motor_channels_200hz(); + get_acomp_shared_data(); SetGyroMask(); share_data(RATE_200HZ); framing_publish_200hz(); @@ -364,6 +367,7 @@ static void mcp_1hz_routines(void) // commented out but will use when we have LJ subsystems again for power labjack_choose_execute(); // printf("InCharge is %d\n", InCharge); + acomp_countdown_1hz(); store_1hz_acs(); record_motor_status_1hz(); // blast_store_disk_space(); @@ -674,6 +678,16 @@ blast_info("Finished initializing Beaglebones..."); */ } pthread_create(&cryo_tauhk_command_thread, NULL, send_cryo_commands, (void *) &cryo_tauhk_command_socket); + // Acomp stuff + pthread_t acomp_sharing_thread; + pthread_t acomp_trigger_thread; + struct socketData acomp_share_socket; + struct socketData acomp_trigger_socket; + populateSocketData(ACOMP1_IP, ACOMP_POINTING_PORT, &acomp_share_socket); + populateSocketData(ACOMP1_IP, ACOMP_TRIGGER_PORT, &acomp_trigger_socket); + pthread_create(&acomp_sharing_thread, NULL, send_pointing_data_acomp, (void *) &acomp_share_socket); + pthread_create(&acomp_trigger_thread, NULL, send_data_trigger_acomp, (void *) &acomp_trigger_socket); + // new star cam stuff // command setup pthread_t sc1_command_thread;