From aadbfb2594f4032e63798aabddb767fc1a9a68d0 Mon Sep 17 00:00:00 2001 From: Ulrich Mohr Date: Tue, 17 May 2022 10:41:45 +0200 Subject: [PATCH 01/10] Added TCs to command Thermal Controller (to be continued) --- config/definitions.py | 1 + config/object_ids.py | 1 + pus_tc/cmd_definitions.py | 21 +++++++++++++++++++++ pus_tc/system/acs.py | 25 ++++++++++++------------- pus_tc/system/common.py | 2 +- pus_tc/system/controllers.py | 35 +++++++++++++++++++++++++++++++++++ pus_tc/system/tcs.py | 6 +++--- pus_tc/tc_packer_hook.py | 3 +++ 8 files changed, 77 insertions(+), 17 deletions(-) create mode 100644 pus_tc/system/controllers.py diff --git a/config/definitions.py b/config/definitions.py index d6a75f9..882fd97 100644 --- a/config/definitions.py +++ b/config/definitions.py @@ -47,3 +47,4 @@ class CustomServiceList(enum.Enum): SUS_ASS = "sus-ass" TCS_ASS = "tcs-ass" TIME = "time" + CONTROLLERS = "controllers" diff --git a/config/object_ids.py b/config/object_ids.py index e678f9c..99c5b9b 100644 --- a/config/object_ids.py +++ b/config/object_ids.py @@ -27,6 +27,7 @@ ACU_HANDLER_ID = bytes([0x44, 0x25, 0x00, 0x03]) BPX_HANDLER_ID = bytes([0x44, 0x26, 0x00, 0x00]) # Thermal Object IDs +THERMAL_CONTROLLER_ID = bytes([0x43, 0x40, 0x00, 0x01]) HEATER_ID = bytes([0x44, 0x41, 0x00, 0xA4]) TMP_1075_1_HANDLER_ID = bytes([0x44, 0x42, 0x00, 0x04]) TMP_1075_2_HANDLER_ID = bytes([0x44, 0x42, 0x00, 0x05]) diff --git a/pus_tc/cmd_definitions.py b/pus_tc/cmd_definitions.py index 3db7e9d..1bec4da 100644 --- a/pus_tc/cmd_definitions.py +++ b/pus_tc/cmd_definitions.py @@ -944,6 +944,7 @@ def add_ploc_supv_cmds(cmd_dict: ServiceOpCodeDictT): def add_system_cmds(cmd_dict: ServiceOpCodeDictT): from pus_tc.system.acs import AcsOpCodes, SusOpCodes import pus_tc.system.tcs as tcs + import pus_tc.system.controllers as controllers default_opts = generate_op_code_options( enter_listener_mode=False, custom_timeout=8.0 @@ -1049,3 +1050,23 @@ def add_system_cmds(cmd_dict: ServiceOpCodeDictT): info="TCS Board Assembly", op_code_entry=op_code_dict, ) + + op_code_dict = dict() + add_op_code_entry( + op_code_dict=op_code_dict, + keys=controllers.OpCodes.THERMAL_CONTROLLER_NORMAL, + info=controllers.Info.THERMAL_CONTROLLER_NORMAL, + options=default_opts, + ) + add_op_code_entry( + op_code_dict=op_code_dict, + keys=controllers.OpCodes.THERMAL_CONTROLLER_OFF, + info=controllers.Info.THERMAL_CONTROLLER_OFF, + options=default_opts, + ) + add_service_op_code_entry( + srv_op_code_dict=cmd_dict, + name=CustomServiceList.CONTROLLERS.value, + info="Controllers", + op_code_entry=op_code_dict, + ) diff --git a/pus_tc/system/acs.py b/pus_tc/system/acs.py index ea97734..7d3eb48 100644 --- a/pus_tc/system/acs.py +++ b/pus_tc/system/acs.py @@ -3,8 +3,7 @@ from tmtccmd.tc.definitions import TcQueueT from tmtccmd.tc.pus_200_fsfw_modes import Modes from config.object_ids import ACS_BOARD_ASS_ID, SUS_BOARD_ASS_ID -from .common import command_assembly - +from .common import command_mode class AcsOpCodes: ACS_ASS_A_SIDE = ["0", "acs-a"] @@ -31,7 +30,7 @@ class DualSideSubmodes(enum.IntEnum): def pack_acs_command(tc_queue: TcQueueT, op_code: str): if op_code in AcsOpCodes.ACS_ASS_A_SIDE: - command_assembly( + command_mode( object_id=ACS_BOARD_ASS_ID, mode=Modes.NORMAL, submode=DualSideSubmodes.A_SIDE, @@ -39,7 +38,7 @@ def pack_acs_command(tc_queue: TcQueueT, op_code: str): info="Switching to ACS board assembly A side", ) if op_code in AcsOpCodes.ACS_ASS_B_SIDE: - command_assembly( + command_mode( object_id=ACS_BOARD_ASS_ID, mode=Modes.NORMAL, submode=DualSideSubmodes.B_SIDE, @@ -47,7 +46,7 @@ def pack_acs_command(tc_queue: TcQueueT, op_code: str): info="Switching to ACS board assembly B side", ) if op_code in AcsOpCodes.ACS_ASS_DUAL_MODE: - command_assembly( + command_mode( object_id=ACS_BOARD_ASS_ID, mode=Modes.NORMAL, submode=DualSideSubmodes.DUAL_SIDE, @@ -55,7 +54,7 @@ def pack_acs_command(tc_queue: TcQueueT, op_code: str): info="Switching to ACS board assembly dual mode", ) if op_code in AcsOpCodes.ACS_ASS_A_ON: - command_assembly( + command_mode( object_id=ACS_BOARD_ASS_ID, mode=Modes.ON, submode=DualSideSubmodes.A_SIDE, @@ -63,7 +62,7 @@ def pack_acs_command(tc_queue: TcQueueT, op_code: str): info="Switching ACS board assembly A side on", ) if op_code in AcsOpCodes.ACS_ASS_B_ON: - command_assembly( + command_mode( object_id=ACS_BOARD_ASS_ID, mode=Modes.ON, submode=DualSideSubmodes.B_SIDE, @@ -71,7 +70,7 @@ def pack_acs_command(tc_queue: TcQueueT, op_code: str): info="Switching ACS board assembly B side on", ) if op_code in AcsOpCodes.ACS_ASS_DUAL_ON: - command_assembly( + command_mode( object_id=ACS_BOARD_ASS_ID, mode=Modes.ON, submode=DualSideSubmodes.B_SIDE, @@ -79,7 +78,7 @@ def pack_acs_command(tc_queue: TcQueueT, op_code: str): info="Switching ACS board assembly dual side on", ) if op_code in AcsOpCodes.ACS_ASS_OFF: - command_assembly( + command_mode( object_id=ACS_BOARD_ASS_ID, mode=Modes.OFF, submode=0, @@ -90,7 +89,7 @@ def pack_acs_command(tc_queue: TcQueueT, op_code: str): def pack_sus_cmds(tc_queue: TcQueueT, op_code: str): if op_code in SusOpCodes.SUS_ASS_NOM_SIDE: - command_assembly( + command_mode( object_id=SUS_BOARD_ASS_ID, mode=Modes.NORMAL, submode=DualSideSubmodes.A_SIDE, @@ -98,7 +97,7 @@ def pack_sus_cmds(tc_queue: TcQueueT, op_code: str): info="Switching to SUS board to nominal side", ) if op_code in SusOpCodes.SUS_ASS_RED_SIDE: - command_assembly( + command_mode( object_id=SUS_BOARD_ASS_ID, mode=Modes.NORMAL, submode=DualSideSubmodes.B_SIDE, @@ -106,7 +105,7 @@ def pack_sus_cmds(tc_queue: TcQueueT, op_code: str): info="Switching to SUS board to redundant side", ) if op_code in SusOpCodes.SUS_ASS_OFF: - command_assembly( + command_mode( object_id=SUS_BOARD_ASS_ID, mode=Modes.OFF, submode=0, @@ -114,7 +113,7 @@ def pack_sus_cmds(tc_queue: TcQueueT, op_code: str): info="Switching SUS board off", ) if op_code in SusOpCodes.SUS_ASS_DUAL_MODE: - command_assembly( + command_mode( object_id=SUS_BOARD_ASS_ID, mode=Modes.NORMAL, submode=DualSideSubmodes.DUAL_SIDE, diff --git a/pus_tc/system/common.py b/pus_tc/system/common.py index 55ae7b3..c712ea3 100644 --- a/pus_tc/system/common.py +++ b/pus_tc/system/common.py @@ -3,7 +3,7 @@ from spacepackets.ecss.tc import PusTelecommand from tmtccmd.tc.pus_200_fsfw_modes import pack_mode_data, Modes, Subservices -def command_assembly( +def command_mode( object_id: bytes, mode: Modes, submode: int, tc_queue: TcQueueT, info: str ): tc_queue.appendleft((QueueCommands.PRINT, info)) diff --git a/pus_tc/system/controllers.py b/pus_tc/system/controllers.py new file mode 100644 index 0000000..183d1ec --- /dev/null +++ b/pus_tc/system/controllers.py @@ -0,0 +1,35 @@ +from tmtccmd.tc.definitions import TcQueueT, QueueCommands +from tmtccmd.tc.pus_200_fsfw_modes import Modes + +from .common import command_mode +import config.object_ids as obj_ids + + +class OpCodes: + THERMAL_CONTROLLER_NORMAL = ["0", "thermal-normal"] + THERMAL_CONTROLLER_OFF = ["1", "thermal-off"] + + +class Info: + THERMAL_CONTROLLER_NORMAL = "Switching Thermal Controller into normal" + THERMAL_CONTROLLER_OFF = "Switching Thermal Controller off" + + +def pack_controller_commands(tc_queue: TcQueueT, op_code: str): + if op_code in OpCodes.THERMAL_CONTROLLER_NORMAL: + command_mode( + object_id=obj_ids.THERMAL_CONTROLLER_ID, + mode=Modes.NORMAL, + submode=0, + tc_queue=tc_queue, + info=Info.THERMAL_CONTROLLER_NORMAL, + ) + if op_code in OpCodes.THERMAL_CONTROLLER_OFF: + command_mode( + object_id=obj_ids.THERMAL_CONTROLLER_ID, + mode=Modes.OFF, + submode=0, + tc_queue=tc_queue, + info=Info.THERMAL_CONTROLLER_OFF, + ) + diff --git a/pus_tc/system/tcs.py b/pus_tc/system/tcs.py index 5b98ddc..47ab31d 100644 --- a/pus_tc/system/tcs.py +++ b/pus_tc/system/tcs.py @@ -1,7 +1,7 @@ from tmtccmd.tc.definitions import TcQueueT, QueueCommands from tmtccmd.tc.pus_200_fsfw_modes import Modes -from .common import command_assembly +from .common import command_mode from config.object_ids import TCS_BOARD_ASS_ID @@ -17,7 +17,7 @@ class Info: def pack_tcs_sys_commands(tc_queue: TcQueueT, op_code: str): if op_code in OpCodes.TCS_BOARD_ASS_NORMAL: - command_assembly( + command_mode( object_id=TCS_BOARD_ASS_ID, mode=Modes.NORMAL, submode=0, @@ -25,7 +25,7 @@ def pack_tcs_sys_commands(tc_queue: TcQueueT, op_code: str): info=Info.TCS_BOARD_ASS_NORMAL, ) if op_code in OpCodes.TCS_BOARD_ASS_OFF: - command_assembly( + command_mode( object_id=TCS_BOARD_ASS_ID, mode=Modes.OFF, submode=0, diff --git a/pus_tc/tc_packer_hook.py b/pus_tc/tc_packer_hook.py index e999f59..52046cb 100644 --- a/pus_tc/tc_packer_hook.py +++ b/pus_tc/tc_packer_hook.py @@ -41,6 +41,7 @@ from pus_tc.system.acs import pack_acs_command, pack_sus_cmds from pus_tc.devs.plpcdu import pack_pl_pcdu_commands from pus_tc.devs.str_img_helper import pack_str_img_helper_command from pus_tc.system.tcs import pack_tcs_sys_commands +from pus_tc.system.controllers import pack_controller_commands from config.definitions import CustomServiceList from config.object_ids import ( P60_DOCK_HANDLER, @@ -239,6 +240,8 @@ def pack_service_queue_user( return pack_rw_ass_cmds( tc_queue=service_queue, object_id=RW_ASSEMBLY, op_code=op_code ) + if service == CustomServiceList.CONTROLLERS.value: + return pack_controller_commands(tc_queue=service_queue, op_code=op_code) LOGGER.warning("Invalid Service !") From 05ca9e37c3d57891fc29d529958f32253c8e9c5d Mon Sep 17 00:00:00 2001 From: Ulrich Mohr Date: Tue, 17 May 2022 10:43:59 +0200 Subject: [PATCH 02/10] WIP: parsing Thermal Controller TM and sending it via TCP/IP --- pus_tm/hk_handling.py | 51 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/pus_tm/hk_handling.py b/pus_tm/hk_handling.py index 4dd3d25..5c975c4 100644 --- a/pus_tm/hk_handling.py +++ b/pus_tm/hk_handling.py @@ -2,6 +2,8 @@ import struct import os import datetime +import json +import socket from tmtccmd.utility.tmtc_printer import FsfwTmTcPrinter from tmtccmd.config.definitions import HkReplyUnpacked @@ -21,6 +23,12 @@ import config.object_ids as obj_ids from pus_tm.devs.reaction_wheels import handle_rw_hk_data from pus_tm.defs import PrintWrapper, FsfwTmTcPrinter, log_to_both + +#TODO add to configuration parameters +THERMAL_HOST = "127.0.0.1" +THERMAL_PORT = 7302 + + LOGGER = get_console_logger() @@ -98,6 +106,8 @@ def handle_regular_hk_print( handle_p60_hk_data(printer=printer, set_id=set_id, hk_data=hk_data) if objb == obj_ids.PL_PCDU_ID: log_to_both(printer, "Received PL PCDU HK data") + if objb == obj_ids.THERMAL_CONTROLLER_ID: + handle_thermal_controller_hk_data(printer=printer, set_id=set_id, hk_data=hk_data) else: LOGGER.info("Service 3 TM: Parsing for this SID has not been implemented.") return HkReplyUnpacked() @@ -289,6 +299,47 @@ def handle_self_test_data(printer: FsfwTmTcPrinter, hk_data: bytes): log_to_both(printer, str(content_list)) printer.print_validity_buffer(validity_buffer=validity_buffer, num_vars=num_of_vars) +def handle_thermal_controller_hk_data(printer: FsfwTmTcPrinter, set_id: int, hk_data: bytes): + if set_id == 0: + LOGGER.info("Received Sensor Temperature data") + + #get all the floats + tm_data = struct.unpack("!ffffffffffffffff", hk_data[:16*4]) + parsed_data = {} + + #put them into a nice dictionary + parsed_data["SID"] = set_id + parsed_data["content"] = {} + parsed_data["content"]["SENSOR_PLOC_HEATSPREADER"] = tm_data[0] + parsed_data["content"]["SENSOR_PLOC_MISSIONBOARD"] = tm_data[1] + parsed_data["content"]["SENSOR_4K_CAMERA"] = tm_data[2] + parsed_data["content"]["SENSOR_DAC_HEATSPREADER"] = tm_data[3] + parsed_data["content"]["SENSOR_STARTRACKER"] = tm_data[4] + parsed_data["content"]["SENSOR_RW1"] = tm_data[5] + parsed_data["content"]["SENSOR_DRO"] = tm_data[6] + parsed_data["content"]["SENSOR_SCEX"] = tm_data[7] + parsed_data["content"]["SENSOR_X8"] = tm_data[8] + parsed_data["content"]["SENSOR_HPA"] = tm_data[9] + parsed_data["content"]["SENSOR_TX_MODUL"] = tm_data[10] + parsed_data["content"]["SENSOR_MPA"] = tm_data[11] + parsed_data["content"]["SENSOR_ACU"] = tm_data[12] + parsed_data["content"]["SENSOR_PLPCDU_HEATSPREADER"] = tm_data[13] + parsed_data["content"]["SENSOR_TCS_BOARD"] = tm_data[14] + parsed_data["content"]["SENSOR_MAGNETTORQUER"] = tm_data[15] + + #which in turn will become a json to be sent over the wire + json_string = json.dumps(parsed_data) + #print(json_string) + + #try to send it to a tcp server + try: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.connect((THERMAL_HOST, THERMAL_PORT)) + s.sendall(bytes(json_string, encoding="utf-8")) + except: + #fail silently if there is noone listening, should be a non breaking feature + pass + def handle_gps_data(printer: FsfwTmTcPrinter, hk_data: bytes): LOGGER.info(f"Received GPS data, HK data length {len(hk_data)}") From 2beffdbb2772a831317ee917d8ff03e94269f14d Mon Sep 17 00:00:00 2001 From: Ulrich Mohr Date: Tue, 17 May 2022 22:04:56 +0200 Subject: [PATCH 03/10] updated tmtccmd version --- tmtccmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tmtccmd b/tmtccmd index 0895aae..862fdf2 160000 --- a/tmtccmd +++ b/tmtccmd @@ -1 +1 @@ -Subproject commit 0895aae63414cdca4a16c53028fe72401c1b50e0 +Subproject commit 862fdf23bc4a90ced47ee1c811de4237e3508536 From 73c8e95dce1ac4b5da56cdc7f6bedd6f24384926 Mon Sep 17 00:00:00 2001 From: Ulrich Mohr Date: Tue, 17 May 2022 22:10:53 +0200 Subject: [PATCH 04/10] fixed looping mode --- tmtcloop.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tmtcloop.py b/tmtcloop.py index d2cfb5b..1302dac 100755 --- a/tmtcloop.py +++ b/tmtcloop.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 """EIVE TMTC Commander""" +from distutils.log import debug import sys import traceback @@ -11,6 +12,7 @@ try: create_default_args_parser, add_default_tmtccmd_args, parse_default_input_arguments, + handle_unspecified_args, ) from tmtccmd.ccsds.handler import CcsdsTmHandler, ApidHandler from tmtccmd.logging import get_console_logger @@ -62,7 +64,7 @@ def main(): tmtc_backend.set_mode(CoreModeList.CONTINUOUS_MODE) get_console_logger().info("Disabling console logger for continuous operation") - get_console_logger().disabled = True + get_console_logger().setLevel("ERROR") tmtccmd.init_and_start_daemons(tmtc_backend=tmtc_backend) tmtccmd.performOperation(tmtc_backend=tmtc_backend) @@ -71,11 +73,12 @@ def main(): sys.argv = sys.argv[:1] while True: - args = parse_default_input_arguments(arg_parser, hook_obj) - setup_args = SetupArgs( - hook_obj=hook_obj, use_gui=False, apid=PUS_APID, cli_args=args - ) - tmtccmd.setup(setup_args=setup_args) + args.service = None + args.op_code = None + handle_unspecified_args(args, hook_obj.get_service_op_code_dictionary()) + + tmtc_backend.set_service(args.service) + tmtc_backend.set_opcode(args.op_code) tmtc_backend.set_mode(CoreModeList.CONTINUOUS_MODE) tmtccmd.performOperation(tmtc_backend=tmtc_backend) From 476066a5ac261943a24caa5e1aa270595a6cc120 Mon Sep 17 00:00:00 2001 From: Ulrich Mohr Date: Tue, 17 May 2022 22:11:31 +0200 Subject: [PATCH 05/10] changed controller commanding to allow more flexibility --- pus_tc/cmd_definitions.py | 8 +++--- pus_tc/system/controllers.py | 55 +++++++++++++++++++++--------------- 2 files changed, 36 insertions(+), 27 deletions(-) diff --git a/pus_tc/cmd_definitions.py b/pus_tc/cmd_definitions.py index c3ea712..db803da 100644 --- a/pus_tc/cmd_definitions.py +++ b/pus_tc/cmd_definitions.py @@ -1058,14 +1058,14 @@ def add_system_cmds(cmd_dict: ServiceOpCodeDictT): op_code_dict = dict() add_op_code_entry( op_code_dict=op_code_dict, - keys=controllers.OpCodes.THERMAL_CONTROLLER_NORMAL, - info=controllers.Info.THERMAL_CONTROLLER_NORMAL, + keys=controllers.OpCodes.THERMAL_CONTROLLER, + info=controllers.Info.THERMAL_CONTROLLER, options=default_opts, ) add_op_code_entry( op_code_dict=op_code_dict, - keys=controllers.OpCodes.THERMAL_CONTROLLER_OFF, - info=controllers.Info.THERMAL_CONTROLLER_OFF, + keys=controllers.OpCodes.CORE_CONTROLLER, + info=controllers.Info.CORE_CONTROLLER, options=default_opts, ) add_service_op_code_entry( diff --git a/pus_tc/system/controllers.py b/pus_tc/system/controllers.py index 183d1ec..30a5b4d 100644 --- a/pus_tc/system/controllers.py +++ b/pus_tc/system/controllers.py @@ -1,35 +1,44 @@ -from tmtccmd.tc.definitions import TcQueueT, QueueCommands -from tmtccmd.tc.pus_200_fsfw_modes import Modes +from ast import Pass +from tmtccmd.tc.definitions import TcQueueT + + from .common import command_mode import config.object_ids as obj_ids class OpCodes: - THERMAL_CONTROLLER_NORMAL = ["0", "thermal-normal"] - THERMAL_CONTROLLER_OFF = ["1", "thermal-off"] + THERMAL_CONTROLLER = [obj_ids.THERMAL_CONTROLLER_ID.hex(), "thermal_controller"] + CORE_CONTROLLER = [obj_ids.CORE_CONTROLLER_ID.hex(), "core_controller"] class Info: - THERMAL_CONTROLLER_NORMAL = "Switching Thermal Controller into normal" - THERMAL_CONTROLLER_OFF = "Switching Thermal Controller off" + THERMAL_CONTROLLER = "Thermal controller" + CORE_CONTROLLER = "ACS controller" def pack_controller_commands(tc_queue: TcQueueT, op_code: str): - if op_code in OpCodes.THERMAL_CONTROLLER_NORMAL: - command_mode( - object_id=obj_ids.THERMAL_CONTROLLER_ID, - mode=Modes.NORMAL, - submode=0, - tc_queue=tc_queue, - info=Info.THERMAL_CONTROLLER_NORMAL, - ) - if op_code in OpCodes.THERMAL_CONTROLLER_OFF: - command_mode( - object_id=obj_ids.THERMAL_CONTROLLER_ID, - mode=Modes.OFF, - submode=0, - tc_queue=tc_queue, - info=Info.THERMAL_CONTROLLER_OFF, - ) - + mode = int(input("Specify mode: (OFF = 0; ON = 1; NORMAL = 2) [2] ") or "2") + print(mode) + if mode < 0 or mode > 2: + print("Invalid Mode, defaulting to OFF") + mode = 0 + submode = int(input("Specify submode [0]: ") or "0") + command_mode( + object_id=get_object_from_op_code(op_code), + mode=mode, + submode=submode, + tc_queue=tc_queue, + info=op_code + " to " + str(mode) + "," + str(submode), + ) + +def get_object_from_op_code(op_code: str): + try: + return bytes.fromhex(op_code) + except: + pass + + if op_code in OpCodes.THERMAL_CONTROLLER: + return obj_ids.THERMAL_CONTROLLER_ID + if op_code in OpCodes.CORE_CONTROLLER: + return obj_ids.CORE_CONTROLLER_ID From 963f48120eaa4642113700ac104cdc4992a42c78 Mon Sep 17 00:00:00 2001 From: Ulrich Mohr Date: Tue, 17 May 2022 22:08:30 +0200 Subject: [PATCH 06/10] added sending of TM via TCP Port --- pus_tm/factory_hook.py | 5 +- pus_tm/hk_handler.py | 840 +++++++++++++++++++++++++++++++++++++++++ pus_tm/hk_handling.py | 760 ------------------------------------- 3 files changed, 843 insertions(+), 762 deletions(-) create mode 100644 pus_tm/hk_handler.py delete mode 100644 pus_tm/hk_handling.py diff --git a/pus_tm/factory_hook.py b/pus_tm/factory_hook.py index d1829ca..5ca178b 100644 --- a/pus_tm/factory_hook.py +++ b/pus_tm/factory_hook.py @@ -19,7 +19,7 @@ from config.object_ids import get_object_ids from .event_handler import handle_event_packet from .verification_handler import handle_service_1_packet -from .hk_handling import handle_hk_packet +from .hk_handler import HkHandler from .action_reply_handler import handle_action_reply LOGGER = get_console_logger() @@ -33,6 +33,7 @@ def ccsds_tm_handler(apid: int, raw_tm_packet: bytes, _user_args: any) -> None: pus_factory_hook(raw_tm_packet=raw_tm_packet) + def pus_factory_hook(raw_tm_packet: bytes): if len(raw_tm_packet) < 8: LOGGER.warning("Detected packet shorter than 8 bytes!") @@ -45,7 +46,7 @@ def pus_factory_hook(raw_tm_packet: bytes): if service_type == 1: handle_service_1_packet(printer=FSFW_PRINTER, raw_tm=raw_tm_packet) elif service_type == 3: - handle_hk_packet( + HkHandler.getInstance().handle_hk_packet( printer=FSFW_PRINTER, raw_tm=raw_tm_packet, obj_id_dict=obj_id_dict ) elif service_type == 5: diff --git a/pus_tm/hk_handler.py b/pus_tm/hk_handler.py new file mode 100644 index 0000000..67f0d74 --- /dev/null +++ b/pus_tm/hk_handler.py @@ -0,0 +1,840 @@ +"""HK Handling for EIVE OBSW""" +import struct +import os +import datetime +import json +import socket +import base64 + +from tmtccmd.config.definitions import HkReplyUnpacked +from tmtccmd.tm.pus_3_fsfw_hk import ( + Service3Base, + HkContentType, + Service3FsfwTm, +) +from tmtccmd.logging import get_console_logger +from pus_tc.devs.bpx_batt import BpxSetIds +from pus_tc.devs.syrlinks_hk_handler import SetIds +from pus_tc.devs.p60dock import SetIds +from pus_tc.devs.imtq import ImtqSetIds +from tmtccmd.utility.obj_id import ObjectId, ObjectIdDictT +import config.object_ids as obj_ids + +from pus_tm.devs.reaction_wheels import handle_rw_hk_data +from pus_tm.defs import FsfwTmTcPrinter, log_to_both + + +# TODO add to configuration parameters +THERMAL_HOST = "127.0.0.1" +THERMAL_PORT = 7302 + + +LOGGER = get_console_logger() + + +class HkHandler(): + + _Instance = None + + def getInstance(): + if HkHandler._Instance == None: + HkHandler._Instance = HkHandler() + return HkHandler._Instance + + def __init__( + self): + + self.raw_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.parsed_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + self.raw_server_socket.bind(("", 3705)) + self.parsed_server_socket.bind(("", 3706)) + + # for now, only accept one connection + self.raw_server_socket.listen(0) + self.parsed_server_socket.listen(0) + + self.raw_server_socket.setblocking(False) + self.parsed_server_socket.setblocking(False) + + self.raw_connection = None + self.parsed_connection = None + + def __del__(self): + try: + self.close() + except: + LOGGER.warning("Could not close sockets!") + + def close(self): + self.raw_server_socket.close + self.parsed_server_socket.close + if self.raw_connection != None: + self.raw_connection.close() + + def report_raw_data(self, object_id: ObjectId, + set_id: int, + hk_data: bytes): + + data_dict = {} + data_dict["objectId"] = object_id.as_string + data_dict["setId"] = set_id + data_dict["rawData"] = base64.b64encode(hk_data).decode() + + self.send_dictionary_over_socket(data_dict, True) + + def send_dictionary_over_socket(self, dictionary, raw=False): + # keep listeners current + if self.raw_connection == None: + # no running connection, see if a client wants to connect + try: + (self.raw_connection, _) = self.raw_server_socket.accept() + self.raw_connection.setblocking(False) + except: + # no client waiting + pass + + if self.parsed_connection == None: + # no running connection, see if a client wants to connect + try: + (self.parsed_connection, _) = self.parsed_server_socket.accept() + self.parsed_connection.setblocking(False) + except: + # no client waiting + pass + + socket_to_use = self.parsed_connection + if raw: + socket_to_use = self.raw_connection + + if socket_to_use == None: + return + + data_json_bytes = json.dumps(dictionary).encode() + + # dle encode the bytes + # Taking a shortcut as json is inherently + # not binary (we also encoded it as utf-8), so there + # can not be any 0x02 or 0x03 be in there + data_json_bytes = b'\x02' + data_json_bytes + b'\n' + b'\x03' + + try: + sent_length = socket_to_use.send(data_json_bytes) + except: + socket_to_use = None + return + if sent_length == 0: + socket_to_use.close() + socket_to_use = None + + def handle_hk_packet( + self, + raw_tm: bytes, + obj_id_dict: ObjectIdDictT, + printer: FsfwTmTcPrinter, + ): + tm_packet = Service3FsfwTm.unpack(raw_telemetry=raw_tm, custom_hk_handling=False) + named_obj_id = obj_id_dict.get(tm_packet.object_id.as_bytes) + if named_obj_id is None: + named_obj_id = tm_packet.object_id + if tm_packet.subservice == 25 or tm_packet.subservice == 26: + hk_data = tm_packet.tm_data[8:] + self.report_raw_data(object_id=named_obj_id, + set_id=tm_packet.set_id, + hk_data=hk_data) + printer.generic_hk_tm_print( + content_type=HkContentType.HK, + object_id=named_obj_id, + set_id=tm_packet.set_id, + hk_data=hk_data, + ) + self.handle_regular_hk_print( + printer=printer, + object_id=named_obj_id, + hk_packet=tm_packet, + hk_data=hk_data, + ) + if tm_packet.subservice == 10 or tm_packet.subservice == 12: + LOGGER.warning("HK definitions printout not implemented yet") + + def handle_regular_hk_print( + self, + printer: FsfwTmTcPrinter, + object_id: ObjectId, + hk_packet: Service3Base, + hk_data: bytes, + ): + objb = object_id.as_bytes + set_id = hk_packet.set_id + """This function is called when a Service 3 Housekeeping packet is received.""" + if object_id in [obj_ids.RW1_ID, obj_ids.RW2_ID, obj_ids.RW3_ID, obj_ids.RW4_ID]: + handle_rw_hk_data(printer, object_id, set_id, hk_data) + if objb == obj_ids.SYRLINKS_HANDLER_ID: + if set_id == SetIds.RX_REGISTERS_DATASET: + return self.handle_syrlinks_rx_registers_dataset(printer, hk_data) + elif set_id == SetIds.TX_REGISTERS_DATASET: + return self.handle_syrlinks_tx_registers_dataset(printer, hk_data) + else: + LOGGER.info("Service 3 TM: Syrlinks handler reply with unknown set id") + if objb == obj_ids.IMTQ_HANDLER_ID: + if (set_id >= ImtqSetIds.POSITIVE_X_TEST) and ( + set_id <= ImtqSetIds.NEGATIVE_Z_TEST + ): + return self.handle_self_test_data(printer, hk_data) + else: + LOGGER.info("Service 3 TM: Syrlinks handler reply with unknown set id") + if objb == obj_ids.GPS_HANDLER_0_ID or object_id == obj_ids.GPS_HANDLER_1_ID: + self.handle_gps_data(printer=printer, hk_data=hk_data) + if objb == obj_ids.BPX_HANDLER_ID: + self.handle_bpx_hk_data(hk_data=hk_data, set_id=set_id, printer=printer) + if objb == obj_ids.CORE_CONTROLLER_ID: + return self.handle_core_hk_data(printer=printer, hk_data=hk_data) + if objb == obj_ids.PDU_1_HANDLER_ID: + return self.handle_pdu_data( + printer=printer, pdu_idx=1, set_id=set_id, hk_data=hk_data + ) + if objb == obj_ids.PDU_2_HANDLER_ID: + return self.handle_pdu_data( + printer=printer, pdu_idx=2, set_id=set_id, hk_data=hk_data + ) + if objb in [obj_ids.RW1_ID, obj_ids.RW2_ID, obj_ids.RW3_ID, obj_ids.RW4_ID]: + return handle_rw_hk_data( + printer=printer, object_id=object_id, set_id=set_id, hk_data=hk_data + ) + if objb == obj_ids.P60_DOCK_HANDLER: + self.handle_p60_hk_data(printer=printer, set_id=set_id, hk_data=hk_data) + if objb == obj_ids.PL_PCDU_ID: + log_to_both(printer, "Received PL PCDU HK data") + if objb == obj_ids.THERMAL_CONTROLLER_ID: + self.handle_thermal_controller_hk_data(printer=printer, set_id=set_id, hk_data=hk_data) + else: + LOGGER.info("Service 3 TM: Parsing for this SID has not been implemented.") + return HkReplyUnpacked() + + def handle_syrlinks_rx_registers_dataset(self, printer: FsfwTmTcPrinter, hk_data: bytes): + reply = HkReplyUnpacked() + header_list = [ + "RX Status", + "RX Sensitivity", + "RX Frequency Shift", + "RX IQ Power", + "RX AGC Value", + "RX Demod Eb", + "RX Demod N0", + "RX Datarate", + ] + rx_status = hk_data[0] + rx_sensitivity = struct.unpack("!I", hk_data[1:5]) + rx_frequency_shift = struct.unpack("!I", hk_data[5:9]) + rx_iq_power = struct.unpack("!H", hk_data[9:11]) + rx_agc_value = struct.unpack("!H", hk_data[11:13]) + rx_demod_eb = struct.unpack("!I", hk_data[13:17]) + rx_demod_n0 = struct.unpack("!I", hk_data[17:21]) + rx_data_rate = hk_data[21] + content_list = [ + rx_status, + rx_sensitivity, + rx_frequency_shift, + rx_iq_power, + rx_agc_value, + rx_demod_eb, + rx_demod_n0, + rx_data_rate, + ] + validity_buffer = hk_data[22:] + log_to_both(printer, str(header_list)) + log_to_both(printer, str(content_list)) + printer.print_validity_buffer(validity_buffer=validity_buffer, num_vars=8) + + def handle_syrlinks_tx_registers_dataset( + self, + printer: FsfwTmTcPrinter, + hk_data: bytes, + ): + reply = HkReplyUnpacked() + header_list = ["TX Status", "TX Waveform", "TX AGC value"] + tx_status = hk_data[0] + tx_waveform = hk_data[1] + tx_agc_value = struct.unpack("!H", hk_data[2:4]) + content_list = [tx_status, tx_waveform, tx_agc_value] + validity_buffer = hk_data[4:] + log_to_both(printer, str(header_list)) + log_to_both(printer, str(content_list)) + printer.print_validity_buffer(validity_buffer=validity_buffer, num_vars=3) + + def handle_self_test_data(self, printer: FsfwTmTcPrinter, hk_data: bytes): + header_list = [ + "Init Err", + "Init Raw Mag X [nT]", + "Init Raw Mag Y [nT]", + "Init Raw Mag Z [nT]", + "Init Cal Mag X [nT]", + "Init Cal Mag Y [nT]", + "Init Cal Mag Z [nT]", + "Init Coil X Current [mA]", + "Init Coil Y Current [mA]", + "Init Coil Z Current [mA]", + "Init Coil X Temperature [°C]", + "Init Coil Y Temperature [°C]", + "Init Coil Z Temperature [°C]", + "Err", + "Raw Mag X [nT]", + "Raw Mag Y [nT]", + "Raw Mag Z [nT]", + "Cal Mag X [nT]", + "Cal Mag Y [nT]", + "Cal Mag Z [nT]", + "Coil X Current [mA]", + "Coil Y Current [mA]", + "Coil Z Current [mA]", + "Coil X Temperature [°C]", + "Coil Y Temperature [°C]", + "Coil Z Temperature [°C]", + "Fina Err", + "Fina Raw Mag X [nT]", + "Fina Raw Mag Y [nT]", + "Fina Raw Mag Z [nT]", + "Fina Cal Mag X [nT]", + "Fina Cal Mag Y [nT]", + "Fina Cal Mag Z [nT]", + "Fina Coil X Current [mA]", + "Fina Coil Y Current [mA]", + "Fina Coil Z Current [mA]", + "Fina Coil X Temperature [°C]", + "Fina Coil Y Temperature [°C]", + "Fina Coil Z Temperature [°C]", + ] + # INIT step (no coil actuation) + init_err = hk_data[0] + init_raw_mag_x = struct.unpack("!f", hk_data[1:5])[0] + init_raw_mag_y = struct.unpack("!f", hk_data[5:9])[0] + init_raw_mag_z = struct.unpack("!f", hk_data[9:13])[0] + init_cal_mag_x = struct.unpack("!f", hk_data[13:17])[0] + init_cal_mag_y = struct.unpack("!f", hk_data[17:21])[0] + init_cal_mag_z = struct.unpack("!f", hk_data[21:25])[0] + init_coil_x_current = struct.unpack("!f", hk_data[25:29])[0] + init_coil_y_current = struct.unpack("!f", hk_data[29:33])[0] + init_coil_z_current = struct.unpack("!f", hk_data[33:37])[0] + init_coil_x_temperature = struct.unpack("!H", hk_data[37:39])[0] + init_coil_y_temperature = struct.unpack("!H", hk_data[39:41])[0] + init_coil_z_temperature = struct.unpack("!H", hk_data[41:43])[0] + + # Actuation step + err = hk_data[43] + raw_mag_x = struct.unpack("!f", hk_data[44:48])[0] + raw_mag_y = struct.unpack("!f", hk_data[48:52])[0] + raw_mag_z = struct.unpack("!f", hk_data[52:56])[0] + cal_mag_x = struct.unpack("!f", hk_data[56:60])[0] + cal_mag_y = struct.unpack("!f", hk_data[60:64])[0] + cal_mag_z = struct.unpack("!f", hk_data[64:68])[0] + coil_x_current = struct.unpack("!f", hk_data[68:72])[0] + coil_y_current = struct.unpack("!f", hk_data[72:76])[0] + coil_z_current = struct.unpack("!f", hk_data[76:80])[0] + coil_x_temperature = struct.unpack("!H", hk_data[80:82])[0] + coil_y_temperature = struct.unpack("!H", hk_data[82:84])[0] + coil_z_temperature = struct.unpack("!H", hk_data[84:86])[0] + + # FINA step (no coil actuation) + fina_err = hk_data[86] + fina_raw_mag_x = struct.unpack("!f", hk_data[87:91])[0] + fina_raw_mag_y = struct.unpack("!f", hk_data[91:95])[0] + fina_raw_mag_z = struct.unpack("!f", hk_data[95:99])[0] + fina_cal_mag_x = struct.unpack("!f", hk_data[99:103])[0] + fina_cal_mag_y = struct.unpack("!f", hk_data[103:107])[0] + fina_cal_mag_z = struct.unpack("!f", hk_data[107:111])[0] + fina_coil_x_current = struct.unpack("!f", hk_data[111:115])[0] + fina_coil_y_current = struct.unpack("!f", hk_data[115:119])[0] + fina_coil_z_current = struct.unpack("!f", hk_data[119:123])[0] + fina_coil_x_temperature = struct.unpack("!H", hk_data[123:125])[0] + fina_coil_y_temperature = struct.unpack("!H", hk_data[125:127])[0] + fina_coil_z_temperature = struct.unpack("!H", hk_data[127:129])[0] + + validity_buffer = hk_data[129:] + content_list = [ + init_err, + init_raw_mag_x, + init_raw_mag_y, + init_raw_mag_z, + init_cal_mag_x, + init_cal_mag_y, + init_cal_mag_z, + init_coil_x_current, + init_coil_y_current, + init_coil_z_current, + init_coil_x_temperature, + init_coil_y_temperature, + init_coil_z_temperature, + err, + raw_mag_x, + raw_mag_y, + raw_mag_z, + cal_mag_x, + cal_mag_y, + cal_mag_z, + coil_x_current, + coil_y_current, + coil_z_current, + coil_x_temperature, + coil_y_temperature, + coil_z_temperature, + fina_err, + fina_raw_mag_x, + fina_raw_mag_y, + fina_raw_mag_z, + fina_cal_mag_x, + fina_cal_mag_y, + fina_cal_mag_z, + fina_coil_x_current, + fina_coil_y_current, + fina_coil_z_current, + fina_coil_x_temperature, + fina_coil_y_temperature, + fina_coil_z_temperature, + ] + num_of_vars = len(header_list) + log_to_both(printer, str(header_list)) + log_to_both(printer, str(content_list)) + printer.print_validity_buffer(validity_buffer=validity_buffer, num_vars=num_of_vars) + + def handle_thermal_controller_hk_data(self, printer: FsfwTmTcPrinter, set_id: int, hk_data: bytes): + if set_id == 0: + LOGGER.info("Received Sensor Temperature data") + + # get all the floats + tm_data = struct.unpack("!ffffffffffffffff", hk_data[:16 * 4]) + parsed_data = {} + + # put them into a nice dictionary + parsed_data["SID"] = set_id + parsed_data["content"] = {} + parsed_data["content"]["SENSOR_PLOC_HEATSPREADER"] = tm_data[0] + parsed_data["content"]["SENSOR_PLOC_MISSIONBOARD"] = tm_data[1] + parsed_data["content"]["SENSOR_4K_CAMERA"] = tm_data[2] + parsed_data["content"]["SENSOR_DAC_HEATSPREADER"] = tm_data[3] + parsed_data["content"]["SENSOR_STARTRACKER"] = tm_data[4] + parsed_data["content"]["SENSOR_RW1"] = tm_data[5] + parsed_data["content"]["SENSOR_DRO"] = tm_data[6] + parsed_data["content"]["SENSOR_SCEX"] = tm_data[7] + parsed_data["content"]["SENSOR_X8"] = tm_data[8] + parsed_data["content"]["SENSOR_HPA"] = tm_data[9] + parsed_data["content"]["SENSOR_TX_MODUL"] = tm_data[10] + parsed_data["content"]["SENSOR_MPA"] = tm_data[11] + parsed_data["content"]["SENSOR_ACU"] = tm_data[12] + parsed_data["content"]["SENSOR_PLPCDU_HEATSPREADER"] = tm_data[13] + parsed_data["content"]["SENSOR_TCS_BOARD"] = tm_data[14] + parsed_data["content"]["SENSOR_MAGNETTORQUER"] = tm_data[15] + + self.send_dictionary_over_socket(parsed_data) + + def handle_gps_data(self, printer: FsfwTmTcPrinter, hk_data: bytes): + LOGGER.info(f"Received GPS data, HK data length {len(hk_data)}") + reply = HkReplyUnpacked() + var_index = 0 + header_list = [ + "Latitude", + "Longitude", + "Altitude", + "Fix Mode", + "Sats in Use", + "Date", + "Unix Seconds", + ] + latitude = struct.unpack("!d", hk_data[0:8])[0] + longitude = struct.unpack("!d", hk_data[8:16])[0] + altitude = struct.unpack("!d", hk_data[16:24])[0] + fix_mode = hk_data[24] + sat_in_use = hk_data[25] + year = struct.unpack("!H", hk_data[26:28])[0] + month = hk_data[28] + day = hk_data[29] + hours = hk_data[30] + minutes = hk_data[31] + seconds = hk_data[32] + date_string = f"{day}.{month}.{year} {hours}:{minutes}:{seconds}" + unix_seconds = struct.unpack("!I", hk_data[33:37])[0] + content_list = [ + latitude, + longitude, + altitude, + fix_mode, + sat_in_use, + date_string, + unix_seconds, + ] + var_index += 13 + reply.num_of_vars = var_index + if not os.path.isfile("gps_log.txt"): + with open("gps_log.txt", "w") as gps_file: + gps_file.write( + "Time, Latitude [deg], Longitude [deg], Altitude [m], Fix Mode, Sats in Use, " + "Date, Unix Seconds\n" + ) + with open("gps_log.txt", "a") as gps_file: + gps_file.write( + f"{datetime.datetime.now()}, {latitude}, {longitude}, {altitude}, " + f"{fix_mode}, {sat_in_use}, {date_string}, {unix_seconds}\n" + ) + validity_buffer = hk_data[37:39] + log_to_both(printer, str(header_list)) + log_to_both(printer, str(content_list)) + printer.print_validity_buffer(validity_buffer=validity_buffer, num_vars=10) + + def handle_bpx_hk_data(self, printer: FsfwTmTcPrinter, set_id: int, hk_data: bytes): + if set_id == BpxSetIds.GET_HK_SET: + fmt_str = "!HHHHhhhhIB" + inc_len = struct.calcsize(fmt_str) + ( + charge_current, + discharge_current, + heater_current, + batt_voltage, + batt_temp_1, + batt_temp_2, + batt_temp_3, + batt_temp_4, + reboot_cntr, + boot_cause, + ) = struct.unpack(fmt_str, hk_data[0:inc_len]) + header_list = [ + "Charge Current", + "Discharge Current", + "Heater Current", + "Battery Voltage", + "Batt Temp 1", + "Batt Temp 2", + "Batt Temp 3", + "Batt Temp 4", + "Reboot Counter", + "Boot Cause", + ] + content_list = [ + charge_current, + discharge_current, + heater_current, + batt_voltage, + batt_temp_1, + batt_temp_2, + batt_temp_3, + batt_temp_4, + reboot_cntr, + boot_cause, + ] + validity_buffer = hk_data[inc_len:] + log_to_both(printer, str(header_list)) + log_to_both(printer, str(content_list)) + printer.print_validity_buffer(validity_buffer=validity_buffer, num_vars=10) + elif set_id == BpxSetIds.GET_CFG_SET: + battheat_mode = hk_data[0] + battheat_low = struct.unpack("!b", hk_data[1:2])[0] + battheat_high = struct.unpack("!b", hk_data[2:3])[0] + header_list = [ + "Battery Heater Mode", + "Battery Heater Low Limit", + "Battery Heater High Limit", + ] + content_list = [battheat_mode, battheat_low, battheat_high] + validity_buffer = hk_data[3:] + log_to_both(printer, str(header_list)) + log_to_both(printer, str(content_list)) + printer.print_validity_buffer(validity_buffer=validity_buffer, num_vars=10) + + def handle_core_hk_data(self, printer: FsfwTmTcPrinter, hk_data: bytes): + + fmt_str = "!fffH" + inc_len = struct.calcsize(fmt_str) + (temperature, ps_voltage, pl_voltage, tx_agc_value) = struct.unpack( + fmt_str, hk_data[0: 0 + inc_len] + ) + printout = ( + f"Chip Temperature [°C] {temperature} | PS Voltage [mV] {ps_voltage} | " + f"PL Voltage [mV] {pl_voltage} | TX AGC {tx_agc_value}" + ) + log_to_both(printer, printout) + printer.print_validity_buffer(validity_buffer=hk_data[inc_len:], num_vars=4) + + P60_INDEX_LIST = [ + "ACU VCC", + "PDU1 VCC", + "X3 IDLE VCC", + "PDU2 VCC", + "ACU VBAT", + "PDU1 VBAT", + "X3 IDLE VBAT", + "PDU2 VBAT", + "STACK VBAT", + "STACK 3V3", + "STACK 5V", + "GS3V3", + "GS5V", + ] + + WDT_LIST = ["GND", "I2C", "CAN", "CSP0", "CSP1"] + + PDU1_CHANNELS_NAMES = [ + "TCS Board", + "Syrlinks", + "Startracker", + "MGT", + "SUS Nominal", + "SCEX", + "PLOC", + "ACS A Side", + "Unused Channel 8", + ] + + PDU2_CHANNELS_NAMES = [ + "Q7S", + "Payload PCDU CH1", + "RW", + "TCS Heater In", + "SUS Redundant", + "Deployment Mechanism", + "Payload PCDU CH6", + "ACS B Side", + "Payload Camera", + ] + + PDU_CHANNEL_NAMES = [PDU1_CHANNELS_NAMES, PDU2_CHANNELS_NAMES] + + class WdtInfo: + def __init__(self): + self.wdt_reboots_list = [] + self.time_pings_left_list = [] + + def print(self, printer: FsfwTmTcPrinter): + wdt_info = "WDT Type | Reboots | Time or Pings left (CSP only)" + log_to_both(printer, wdt_info) + for idx in range(len(self.wdt_reboots_list)): + log_to_both( + printer, + f"{TmHandler.WDT_LIST[idx].ljust(5)} | " + f"{self.wdt_reboots_list[idx]:010} | {self.time_pings_left_list[idx]:010}", + ) + + def parse(self, wdt_data: bytes, current_idx: int) -> int: + priv_idx = 0 + self.wdt_reboots_list = [] + self.time_pings_left_list = [] + for idx in range(5): + self.wdt_reboots_list.append( + struct.unpack("!I", wdt_data[priv_idx: priv_idx + 4])[0] + ) + priv_idx += 4 + current_idx += 4 + for idx in range(3): + self.time_pings_left_list.append( + struct.unpack("!I", wdt_data[priv_idx: priv_idx + 4])[0] + ) + priv_idx += 4 + current_idx += 4 + for idx in range(2): + self.time_pings_left_list.append(wdt_data[priv_idx]) + current_idx += 1 + priv_idx += 1 + return current_idx + + def handle_pdu_data( + self, printer: FsfwTmTcPrinter, pdu_idx: int, set_id: int, hk_data: bytes + ): + current_idx = 0 + priv_idx = pdu_idx - 1 + if set_id == SetIds.PDU_1_AUX or set_id == SetIds.PDU_2_AUX: + fmt_str = "!hhBBBIIH" + inc_len = struct.calcsize(fmt_str) + ( + vcc, + vbat, + conv_enb_0, + conv_enb_1, + conv_enb_2, + boot_cause, + uptime, + reset_cause, + ) = struct.unpack(fmt_str, hk_data[current_idx: current_idx + inc_len]) + log_to_both(printer, f"VCC {vcc} mV | VBAT {vbat} mV") + log_to_both( + printer, f"Converter Enables [{conv_enb_0},{conv_enb_1},{conv_enb_2}]" + ) + log_to_both( + printer, + f"Boot Cause {boot_cause} | Uptime {uptime} | Reset Cause {reset_cause}", + ) + current_idx += inc_len + latchup_list = [] + log_to_both(printer, "Latchups") + for idx in range(len(TmHandler.PDU1_CHANNELS_NAMES)): + latchup_list.append( + struct.unpack("!H", hk_data[current_idx: current_idx + 2])[0] + ) + content_line = ( + f"{TmHandler.PDU_CHANNEL_NAMES[priv_idx][idx].ljust(24)} | {latchup_list[idx]}" + ) + log_to_both(printer, content_line) + current_idx += 2 + device_types = [] + for idx in range(len(TmHandler.PDU1_CHANNELS_NAMES)): + device_types.append(hk_data[current_idx]) + current_idx += 1 + device_statuses = [] + for idx in range(len(TmHandler.PDU1_CHANNELS_NAMES)): + device_statuses.append(hk_data[current_idx]) + current_idx += 1 + wdt = self.WdtInfo() + current_idx = wdt.parse(wdt_data=hk_data[current_idx:], current_idx=current_idx) + wdt.print(printer=printer) + if set_id == SetIds.PDU_1_CORE or set_id == SetIds.PDU_2_CORE: + log_to_both(printer, f"Received PDU HK from PDU {pdu_idx}") + current_list = [] + for idx in range(len(TmHandler.PDU1_CHANNELS_NAMES)): + current_list.append( + struct.unpack("!h", hk_data[current_idx: current_idx + 2])[0] + ) + current_idx += 2 + voltage_list = [] + for idx in range(len(TmHandler.PDU1_CHANNELS_NAMES)): + voltage_list.append( + struct.unpack("!H", hk_data[current_idx: current_idx + 2])[0] + ) + current_idx += 2 + output_enb_list = [] + for idx in range(len(TmHandler.PDU1_CHANNELS_NAMES)): + output_enb_list.append(hk_data[current_idx]) + current_idx += 1 + header_str = f"{'Name'.ljust(24)} | OutEnb | U [mV] | I [mA]" + print(header_str) + printer.file_logger.info(header_str) + for idx in range(len(TmHandler.PDU1_CHANNELS_NAMES)): + out_enb = f"{output_enb_list[idx]}".ljust(6) + content_line = ( + f"{TmHandler.PDU_CHANNEL_NAMES[priv_idx][idx].ljust(24)} | {out_enb} | " + f"{voltage_list[idx]:05} | {current_list[idx]:04}" + ) + log_to_both(printer, content_line) + fmt_str = "!IBh" + inc_len = struct.calcsize(fmt_str) + (boot_count, batt_mode, temperature) = struct.unpack( + fmt_str, hk_data[current_idx: current_idx + inc_len] + ) + info = ( + f"Boot Count {boot_count} | Battery Mode {batt_mode} | " + f"Temperature {temperature / 10.0}" + ) + log_to_both(printer, info) + + def handle_p60_hk_data(self, printer: FsfwTmTcPrinter, set_id: int, hk_data: bytes): + if set_id == SetIds.P60_CORE: + log_to_both(printer, "Received P60 Core HK. Voltages in mV, currents in mA") + current_idx = 0 + current_list = [] + for idx in range(13): + current_list.append( + struct.unpack("!h", hk_data[current_idx: current_idx + 2])[0] + ) + current_idx += 2 + voltage_list = [] + for idx in range(13): + voltage_list.append( + struct.unpack("!H", hk_data[current_idx: current_idx + 2])[0] + ) + current_idx += 2 + out_enb_list = [] + for idx in range(13): + out_enb_list.append(hk_data[current_idx]) + current_idx += 1 + header_str = f"{'Name'.ljust(24)} | OutEnb | U [mV] | I [mA]" + print(header_str) + printer.file_logger.info(header_str) + for idx in range(13): + out_enb = f"{out_enb_list[idx]}".ljust(6) + content_line = ( + f"{TmHandler.P60_INDEX_LIST[idx].ljust(24)} | {out_enb} | " + f"{voltage_list[idx]:05} | {current_list[idx]:04}" + ) + log_to_both(printer, content_line) + fmt_str = "!IBhHhh" + inc_len = struct.calcsize(fmt_str) + ( + boot_count, + batt_mode, + batt_current, + batt_voltage, + temp_0, + temp_1, + ) = struct.unpack(fmt_str, hk_data[current_idx: current_idx + inc_len]) + current_idx += inc_len + batt_info = ( + f"Batt: Mode {batt_mode} | Boot Count {boot_count} | " + f"Charge current {batt_current} | Voltage {batt_voltage}" + ) + temps = f"In C: Temp 0 {temp_0 / 10.0} | Temp 1 {temp_1 / 10.0} | " + log_to_both(printer, temps) + log_to_both(printer, batt_info) + printer.print_validity_buffer(validity_buffer=hk_data[current_idx:], num_vars=9) + if set_id == SetIds.P60_AUX: + log_to_both(printer, "Received P60 AUX HK. Voltages in mV, currents in mA") + current_idx = 0 + latchup_list = [] + log_to_both(printer, "P60 Dock Latchups") + for idx in range(0, 13): + latchup_list.append( + struct.unpack("!H", hk_data[current_idx: current_idx + 2])[0] + ) + content_line = f"{TmHandler.P60_INDEX_LIST[idx].ljust(24)} | {latchup_list[idx]}" + log_to_both(printer, content_line) + current_idx += 2 + fmt_str = "!IIHBBHHhhB" + inc_len = struct.calcsize(fmt_str) + ( + boot_cause, + uptime, + reset_cause, + heater_on, + conv_5v_on, + dock_vbat, + dock_vcc_c, + batt_temp_0, + batt_temp_1, + dearm_status, + ) = struct.unpack(fmt_str, hk_data[current_idx: current_idx + inc_len]) + current_idx += inc_len + wdt = self.WdtInfo() + current_idx = wdt.parse(wdt_data=hk_data[current_idx:], current_idx=current_idx) + fmt_str = "!hhbb" + inc_len = struct.calcsize(fmt_str) + ( + batt_charge_current, + batt_discharge_current, + ant6_depl, + ar6_depl, + ) = struct.unpack(fmt_str, hk_data[current_idx: current_idx + inc_len]) + current_idx += inc_len + device_types = [] + device_statuses = [] + for idx in range(8): + device_types.append(hk_data[current_idx]) + current_idx += 1 + for idx in range(8): + device_statuses.append(hk_data[current_idx]) + current_idx += 1 + util_info = ( + f"Reset Cause {reset_cause} | Boot Cause {boot_cause} | Uptime {uptime}" + ) + util_info_2 = ( + f"Conv 5V on {conv_5v_on} | Heater On {heater_on} | " + f"Dock VBAT {dock_vbat} | DOCK VCC Current {dock_vcc_c}" + ) + log_to_both(printer, util_info) + log_to_both(printer, util_info_2) + wdt.print(printer) + misc_info = ( + f"Dearm {dearm_status} | ANT6 Depl {ant6_depl} | AR6 Deply {ar6_depl}" + ) + log_to_both(printer, misc_info) + batt_info = ( + f"Batt Temp 0 {batt_temp_0 / 10.0} | Batt Temp 1 {batt_temp_1 / 10.0} | " + f"Charge Current {batt_charge_current} | Discharge Current {batt_discharge_current}" + ) + log_to_both(printer, batt_info) + printer.print_validity_buffer( + validity_buffer=hk_data[current_idx:], num_vars=27 + ) diff --git a/pus_tm/hk_handling.py b/pus_tm/hk_handling.py deleted file mode 100644 index 8627554..0000000 --- a/pus_tm/hk_handling.py +++ /dev/null @@ -1,760 +0,0 @@ -"""HK Handling for EIVE OBSW""" -import struct -import os -import datetime -import json -import socket - -from tmtccmd.config.definitions import HkReplyUnpacked -from tmtccmd.tm.pus_3_fsfw_hk import ( - Service3Base, - HkContentType, - Service3FsfwTm, -) -from tmtccmd.logging import get_console_logger -from pus_tc.devs.bpx_batt import BpxSetIds -from pus_tc.devs.syrlinks_hk_handler import SetIds -from pus_tc.devs.p60dock import SetIds -from pus_tc.devs.imtq import ImtqSetIds -from tmtccmd.utility.obj_id import ObjectId, ObjectIdDictT -import config.object_ids as obj_ids - -from pus_tm.devs.reaction_wheels import handle_rw_hk_data -from pus_tm.defs import FsfwTmTcPrinter, log_to_both - - -#TODO add to configuration parameters -THERMAL_HOST = "127.0.0.1" -THERMAL_PORT = 7302 - - -LOGGER = get_console_logger() - - -def handle_hk_packet( - raw_tm: bytes, - obj_id_dict: ObjectIdDictT, - printer: FsfwTmTcPrinter, -): - tm_packet = Service3FsfwTm.unpack(raw_telemetry=raw_tm, custom_hk_handling=False) - named_obj_id = obj_id_dict.get(tm_packet.object_id.as_bytes) - if named_obj_id is None: - named_obj_id = tm_packet.object_id - if tm_packet.subservice == 25 or tm_packet.subservice == 26: - hk_data = tm_packet.tm_data[8:] - printer.generic_hk_tm_print( - content_type=HkContentType.HK, - object_id=named_obj_id, - set_id=tm_packet.set_id, - hk_data=hk_data, - ) - handle_regular_hk_print( - printer=printer, - object_id=named_obj_id, - hk_packet=tm_packet, - hk_data=hk_data, - ) - if tm_packet.subservice == 10 or tm_packet.subservice == 12: - LOGGER.warning("HK definitions printout not implemented yet") - - -def handle_regular_hk_print( - printer: FsfwTmTcPrinter, - object_id: ObjectId, - hk_packet: Service3Base, - hk_data: bytes, -): - objb = object_id.as_bytes - set_id = hk_packet.set_id - """This function is called when a Service 3 Housekeeping packet is received.""" - if object_id in [obj_ids.RW1_ID, obj_ids.RW2_ID, obj_ids.RW3_ID, obj_ids.RW4_ID]: - handle_rw_hk_data(printer, object_id, set_id, hk_data) - if objb == obj_ids.SYRLINKS_HANDLER_ID: - if set_id == SetIds.RX_REGISTERS_DATASET: - return handle_syrlinks_rx_registers_dataset(printer, hk_data) - elif set_id == SetIds.TX_REGISTERS_DATASET: - return handle_syrlinks_tx_registers_dataset(printer, hk_data) - else: - LOGGER.info("Service 3 TM: Syrlinks handler reply with unknown set id") - if objb == obj_ids.IMTQ_HANDLER_ID: - if (set_id >= ImtqSetIds.POSITIVE_X_TEST) and ( - set_id <= ImtqSetIds.NEGATIVE_Z_TEST - ): - return handle_self_test_data(printer, hk_data) - else: - LOGGER.info("Service 3 TM: Syrlinks handler reply with unknown set id") - if objb == obj_ids.GPS_HANDLER_0_ID or object_id == obj_ids.GPS_HANDLER_1_ID: - handle_gps_data(printer=printer, hk_data=hk_data) - if objb == obj_ids.BPX_HANDLER_ID: - handle_bpx_hk_data(hk_data=hk_data, set_id=set_id, printer=printer) - if objb == obj_ids.CORE_CONTROLLER_ID: - return handle_core_hk_data(printer=printer, hk_data=hk_data) - if objb == obj_ids.PDU_1_HANDLER_ID: - return handle_pdu_data( - printer=printer, pdu_idx=1, set_id=set_id, hk_data=hk_data - ) - if objb == obj_ids.PDU_2_HANDLER_ID: - return handle_pdu_data( - printer=printer, pdu_idx=2, set_id=set_id, hk_data=hk_data - ) - if objb in [obj_ids.RW1_ID, obj_ids.RW2_ID, obj_ids.RW3_ID, obj_ids.RW4_ID]: - return handle_rw_hk_data( - printer=printer, object_id=object_id, set_id=set_id, hk_data=hk_data - ) - if objb == obj_ids.P60_DOCK_HANDLER: - handle_p60_hk_data(printer=printer, set_id=set_id, hk_data=hk_data) - if objb == obj_ids.PL_PCDU_ID: - log_to_both(printer, "Received PL PCDU HK data") - if objb == obj_ids.THERMAL_CONTROLLER_ID: - handle_thermal_controller_hk_data(printer=printer, set_id=set_id, hk_data=hk_data) - else: - LOGGER.info("Service 3 TM: Parsing for this SID has not been implemented.") - return HkReplyUnpacked() - - -def handle_syrlinks_rx_registers_dataset(printer: FsfwTmTcPrinter, hk_data: bytes): - reply = HkReplyUnpacked() - header_list = [ - "RX Status", - "RX Sensitivity", - "RX Frequency Shift", - "RX IQ Power", - "RX AGC Value", - "RX Demod Eb", - "RX Demod N0", - "RX Datarate", - ] - rx_status = hk_data[0] - rx_sensitivity = struct.unpack("!I", hk_data[1:5]) - rx_frequency_shift = struct.unpack("!I", hk_data[5:9]) - rx_iq_power = struct.unpack("!H", hk_data[9:11]) - rx_agc_value = struct.unpack("!H", hk_data[11:13]) - rx_demod_eb = struct.unpack("!I", hk_data[13:17]) - rx_demod_n0 = struct.unpack("!I", hk_data[17:21]) - rx_data_rate = hk_data[21] - content_list = [ - rx_status, - rx_sensitivity, - rx_frequency_shift, - rx_iq_power, - rx_agc_value, - rx_demod_eb, - rx_demod_n0, - rx_data_rate, - ] - validity_buffer = hk_data[22:] - log_to_both(printer, str(header_list)) - log_to_both(printer, str(content_list)) - printer.print_validity_buffer(validity_buffer=validity_buffer, num_vars=8) - - -def handle_syrlinks_tx_registers_dataset( - printer: FsfwTmTcPrinter, - hk_data: bytes, -): - reply = HkReplyUnpacked() - header_list = ["TX Status", "TX Waveform", "TX AGC value"] - tx_status = hk_data[0] - tx_waveform = hk_data[1] - tx_agc_value = struct.unpack("!H", hk_data[2:4]) - content_list = [tx_status, tx_waveform, tx_agc_value] - validity_buffer = hk_data[4:] - log_to_both(printer, str(header_list)) - log_to_both(printer, str(content_list)) - printer.print_validity_buffer(validity_buffer=validity_buffer, num_vars=3) - - -def handle_self_test_data(printer: FsfwTmTcPrinter, hk_data: bytes): - header_list = [ - "Init Err", - "Init Raw Mag X [nT]", - "Init Raw Mag Y [nT]", - "Init Raw Mag Z [nT]", - "Init Cal Mag X [nT]", - "Init Cal Mag Y [nT]", - "Init Cal Mag Z [nT]", - "Init Coil X Current [mA]", - "Init Coil Y Current [mA]", - "Init Coil Z Current [mA]", - "Init Coil X Temperature [°C]", - "Init Coil Y Temperature [°C]", - "Init Coil Z Temperature [°C]", - "Err", - "Raw Mag X [nT]", - "Raw Mag Y [nT]", - "Raw Mag Z [nT]", - "Cal Mag X [nT]", - "Cal Mag Y [nT]", - "Cal Mag Z [nT]", - "Coil X Current [mA]", - "Coil Y Current [mA]", - "Coil Z Current [mA]", - "Coil X Temperature [°C]", - "Coil Y Temperature [°C]", - "Coil Z Temperature [°C]", - "Fina Err", - "Fina Raw Mag X [nT]", - "Fina Raw Mag Y [nT]", - "Fina Raw Mag Z [nT]", - "Fina Cal Mag X [nT]", - "Fina Cal Mag Y [nT]", - "Fina Cal Mag Z [nT]", - "Fina Coil X Current [mA]", - "Fina Coil Y Current [mA]", - "Fina Coil Z Current [mA]", - "Fina Coil X Temperature [°C]", - "Fina Coil Y Temperature [°C]", - "Fina Coil Z Temperature [°C]", - ] - # INIT step (no coil actuation) - init_err = hk_data[0] - init_raw_mag_x = struct.unpack("!f", hk_data[1:5])[0] - init_raw_mag_y = struct.unpack("!f", hk_data[5:9])[0] - init_raw_mag_z = struct.unpack("!f", hk_data[9:13])[0] - init_cal_mag_x = struct.unpack("!f", hk_data[13:17])[0] - init_cal_mag_y = struct.unpack("!f", hk_data[17:21])[0] - init_cal_mag_z = struct.unpack("!f", hk_data[21:25])[0] - init_coil_x_current = struct.unpack("!f", hk_data[25:29])[0] - init_coil_y_current = struct.unpack("!f", hk_data[29:33])[0] - init_coil_z_current = struct.unpack("!f", hk_data[33:37])[0] - init_coil_x_temperature = struct.unpack("!H", hk_data[37:39])[0] - init_coil_y_temperature = struct.unpack("!H", hk_data[39:41])[0] - init_coil_z_temperature = struct.unpack("!H", hk_data[41:43])[0] - - # Actuation step - err = hk_data[43] - raw_mag_x = struct.unpack("!f", hk_data[44:48])[0] - raw_mag_y = struct.unpack("!f", hk_data[48:52])[0] - raw_mag_z = struct.unpack("!f", hk_data[52:56])[0] - cal_mag_x = struct.unpack("!f", hk_data[56:60])[0] - cal_mag_y = struct.unpack("!f", hk_data[60:64])[0] - cal_mag_z = struct.unpack("!f", hk_data[64:68])[0] - coil_x_current = struct.unpack("!f", hk_data[68:72])[0] - coil_y_current = struct.unpack("!f", hk_data[72:76])[0] - coil_z_current = struct.unpack("!f", hk_data[76:80])[0] - coil_x_temperature = struct.unpack("!H", hk_data[80:82])[0] - coil_y_temperature = struct.unpack("!H", hk_data[82:84])[0] - coil_z_temperature = struct.unpack("!H", hk_data[84:86])[0] - - # FINA step (no coil actuation) - fina_err = hk_data[86] - fina_raw_mag_x = struct.unpack("!f", hk_data[87:91])[0] - fina_raw_mag_y = struct.unpack("!f", hk_data[91:95])[0] - fina_raw_mag_z = struct.unpack("!f", hk_data[95:99])[0] - fina_cal_mag_x = struct.unpack("!f", hk_data[99:103])[0] - fina_cal_mag_y = struct.unpack("!f", hk_data[103:107])[0] - fina_cal_mag_z = struct.unpack("!f", hk_data[107:111])[0] - fina_coil_x_current = struct.unpack("!f", hk_data[111:115])[0] - fina_coil_y_current = struct.unpack("!f", hk_data[115:119])[0] - fina_coil_z_current = struct.unpack("!f", hk_data[119:123])[0] - fina_coil_x_temperature = struct.unpack("!H", hk_data[123:125])[0] - fina_coil_y_temperature = struct.unpack("!H", hk_data[125:127])[0] - fina_coil_z_temperature = struct.unpack("!H", hk_data[127:129])[0] - - validity_buffer = hk_data[129:] - content_list = [ - init_err, - init_raw_mag_x, - init_raw_mag_y, - init_raw_mag_z, - init_cal_mag_x, - init_cal_mag_y, - init_cal_mag_z, - init_coil_x_current, - init_coil_y_current, - init_coil_z_current, - init_coil_x_temperature, - init_coil_y_temperature, - init_coil_z_temperature, - err, - raw_mag_x, - init_raw_mag_y, - raw_mag_z, - cal_mag_x, - cal_mag_y, - cal_mag_z, - coil_x_current, - coil_y_current, - coil_z_current, - coil_x_temperature, - coil_y_temperature, - coil_z_temperature, - fina_err, - fina_raw_mag_x, - fina_raw_mag_y, - fina_raw_mag_z, - fina_cal_mag_x, - fina_cal_mag_y, - fina_cal_mag_z, - fina_coil_x_current, - fina_coil_y_current, - fina_coil_z_current, - fina_coil_x_temperature, - fina_coil_y_temperature, - fina_coil_z_temperature, - ] - num_of_vars = len(header_list) - log_to_both(printer, str(header_list)) - log_to_both(printer, str(content_list)) - printer.print_validity_buffer(validity_buffer=validity_buffer, num_vars=num_of_vars) - -def handle_thermal_controller_hk_data(printer: FsfwTmTcPrinter, set_id: int, hk_data: bytes): - if set_id == 0: - LOGGER.info("Received Sensor Temperature data") - - #get all the floats - tm_data = struct.unpack("!ffffffffffffffff", hk_data[:16*4]) - parsed_data = {} - - #put them into a nice dictionary - parsed_data["SID"] = set_id - parsed_data["content"] = {} - parsed_data["content"]["SENSOR_PLOC_HEATSPREADER"] = tm_data[0] - parsed_data["content"]["SENSOR_PLOC_MISSIONBOARD"] = tm_data[1] - parsed_data["content"]["SENSOR_4K_CAMERA"] = tm_data[2] - parsed_data["content"]["SENSOR_DAC_HEATSPREADER"] = tm_data[3] - parsed_data["content"]["SENSOR_STARTRACKER"] = tm_data[4] - parsed_data["content"]["SENSOR_RW1"] = tm_data[5] - parsed_data["content"]["SENSOR_DRO"] = tm_data[6] - parsed_data["content"]["SENSOR_SCEX"] = tm_data[7] - parsed_data["content"]["SENSOR_X8"] = tm_data[8] - parsed_data["content"]["SENSOR_HPA"] = tm_data[9] - parsed_data["content"]["SENSOR_TX_MODUL"] = tm_data[10] - parsed_data["content"]["SENSOR_MPA"] = tm_data[11] - parsed_data["content"]["SENSOR_ACU"] = tm_data[12] - parsed_data["content"]["SENSOR_PLPCDU_HEATSPREADER"] = tm_data[13] - parsed_data["content"]["SENSOR_TCS_BOARD"] = tm_data[14] - parsed_data["content"]["SENSOR_MAGNETTORQUER"] = tm_data[15] - - #which in turn will become a json to be sent over the wire - json_string = json.dumps(parsed_data) - #print(json_string) - - #try to send it to a tcp server - try: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.connect((THERMAL_HOST, THERMAL_PORT)) - s.sendall(bytes(json_string, encoding="utf-8")) - except: - #fail silently if there is noone listening, should be a non breaking feature - pass - - -def handle_gps_data(printer: FsfwTmTcPrinter, hk_data: bytes): - LOGGER.info(f"Received GPS data, HK data length {len(hk_data)}") - reply = HkReplyUnpacked() - var_index = 0 - header_list = [ - "Latitude", - "Longitude", - "Altitude", - "Fix Mode", - "Sats in Use", - "Date", - "Unix Seconds", - ] - latitude = struct.unpack("!d", hk_data[0:8])[0] - longitude = struct.unpack("!d", hk_data[8:16])[0] - altitude = struct.unpack("!d", hk_data[16:24])[0] - fix_mode = hk_data[24] - sat_in_use = hk_data[25] - year = struct.unpack("!H", hk_data[26:28])[0] - month = hk_data[28] - day = hk_data[29] - hours = hk_data[30] - minutes = hk_data[31] - seconds = hk_data[32] - date_string = f"{day}.{month}.{year} {hours}:{minutes}:{seconds}" - unix_seconds = struct.unpack("!I", hk_data[33:37])[0] - content_list = [ - latitude, - longitude, - altitude, - fix_mode, - sat_in_use, - date_string, - unix_seconds, - ] - var_index += 13 - reply.num_of_vars = var_index - if not os.path.isfile("gps_log.txt"): - with open("gps_log.txt", "w") as gps_file: - gps_file.write( - "Time, Latitude [deg], Longitude [deg], Altitude [m], Fix Mode, Sats in Use, " - "Date, Unix Seconds\n" - ) - with open("gps_log.txt", "a") as gps_file: - gps_file.write( - f"{datetime.datetime.now()}, {latitude}, {longitude}, {altitude}, " - f"{fix_mode}, {sat_in_use}, {date_string}, {unix_seconds}\n" - ) - validity_buffer = hk_data[37:39] - log_to_both(printer, str(header_list)) - log_to_both(printer, str(content_list)) - printer.print_validity_buffer(validity_buffer=validity_buffer, num_vars=10) - - -def handle_bpx_hk_data(printer: FsfwTmTcPrinter, set_id: int, hk_data: bytes): - if set_id == BpxSetIds.GET_HK_SET: - fmt_str = "!HHHHhhhhIB" - inc_len = struct.calcsize(fmt_str) - ( - charge_current, - discharge_current, - heater_current, - batt_voltage, - batt_temp_1, - batt_temp_2, - batt_temp_3, - batt_temp_4, - reboot_cntr, - boot_cause, - ) = struct.unpack(fmt_str, hk_data[0:inc_len]) - header_list = [ - "Charge Current", - "Discharge Current", - "Heater Current", - "Battery Voltage", - "Batt Temp 1", - "Batt Temp 2", - "Batt Temp 3", - "Batt Temp 4", - "Reboot Counter", - "Boot Cause", - ] - content_list = [ - charge_current, - discharge_current, - heater_current, - batt_voltage, - batt_temp_1, - batt_temp_2, - batt_temp_3, - batt_temp_4, - reboot_cntr, - boot_cause, - ] - validity_buffer = hk_data[inc_len:] - log_to_both(printer, str(header_list)) - log_to_both(printer, str(content_list)) - printer.print_validity_buffer(validity_buffer=validity_buffer, num_vars=10) - elif set_id == BpxSetIds.GET_CFG_SET: - battheat_mode = hk_data[0] - battheat_low = struct.unpack("!b", hk_data[1:2])[0] - battheat_high = struct.unpack("!b", hk_data[2:3])[0] - header_list = [ - "Battery Heater Mode", - "Battery Heater Low Limit", - "Battery Heater High Limit", - ] - content_list = [battheat_mode, battheat_low, battheat_high] - validity_buffer = hk_data[3:] - log_to_both(printer, str(header_list)) - log_to_both(printer, str(content_list)) - printer.print_validity_buffer(validity_buffer=validity_buffer, num_vars=10) - - -def handle_core_hk_data(printer: FsfwTmTcPrinter, hk_data: bytes): - - fmt_str = "!fffH" - inc_len = struct.calcsize(fmt_str) - (temperature, ps_voltage, pl_voltage, tx_agc_value) = struct.unpack( - fmt_str, hk_data[0 : 0 + inc_len] - ) - printout = ( - f"Chip Temperature [°C] {temperature} | PS Voltage [mV] {ps_voltage} | " - f"PL Voltage [mV] {pl_voltage} | TX AGC {tx_agc_value}" - ) - log_to_both(printer, printout) - printer.print_validity_buffer(validity_buffer=hk_data[inc_len:], num_vars=4) - - -P60_INDEX_LIST = [ - "ACU VCC", - "PDU1 VCC", - "X3 IDLE VCC", - "PDU2 VCC", - "ACU VBAT", - "PDU1 VBAT", - "X3 IDLE VBAT", - "PDU2 VBAT", - "STACK VBAT", - "STACK 3V3", - "STACK 5V", - "GS3V3", - "GS5V", -] - -WDT_LIST = ["GND", "I2C", "CAN", "CSP0", "CSP1"] - -PDU1_CHANNELS_NAMES = [ - "TCS Board", - "Syrlinks", - "Startracker", - "MGT", - "SUS Nominal", - "SCEX", - "PLOC", - "ACS A Side", - "Unused Channel 8", -] - -PDU2_CHANNELS_NAMES = [ - "Q7S", - "Payload PCDU CH1", - "RW", - "TCS Heater In", - "SUS Redundant", - "Deployment Mechanism", - "Payload PCDU CH6", - "ACS B Side", - "Payload Camera", -] - -PDU_CHANNEL_NAMES = [PDU1_CHANNELS_NAMES, PDU2_CHANNELS_NAMES] - - -class WdtInfo: - def __init__(self): - self.wdt_reboots_list = [] - self.time_pings_left_list = [] - - def print(self, printer: FsfwTmTcPrinter): - wdt_info = "WDT Type | Reboots | Time or Pings left (CSP only)" - log_to_both(printer, wdt_info) - for idx in range(len(self.wdt_reboots_list)): - log_to_both( - printer, - f"{WDT_LIST[idx].ljust(5)} | " - f"{self.wdt_reboots_list[idx]:010} | {self.time_pings_left_list[idx]:010}", - ) - - def parse(self, wdt_data: bytes, current_idx: int) -> int: - priv_idx = 0 - self.wdt_reboots_list = [] - self.time_pings_left_list = [] - for idx in range(5): - self.wdt_reboots_list.append( - struct.unpack("!I", wdt_data[priv_idx : priv_idx + 4])[0] - ) - priv_idx += 4 - current_idx += 4 - for idx in range(3): - self.time_pings_left_list.append( - struct.unpack("!I", wdt_data[priv_idx : priv_idx + 4])[0] - ) - priv_idx += 4 - current_idx += 4 - for idx in range(2): - self.time_pings_left_list.append(wdt_data[priv_idx]) - current_idx += 1 - priv_idx += 1 - return current_idx - - -def handle_pdu_data( - printer: FsfwTmTcPrinter, pdu_idx: int, set_id: int, hk_data: bytes -): - current_idx = 0 - priv_idx = pdu_idx - 1 - if set_id == SetIds.PDU_1_AUX or set_id == SetIds.PDU_2_AUX: - fmt_str = "!hhBBBIIH" - inc_len = struct.calcsize(fmt_str) - ( - vcc, - vbat, - conv_enb_0, - conv_enb_1, - conv_enb_2, - boot_cause, - uptime, - reset_cause, - ) = struct.unpack(fmt_str, hk_data[current_idx : current_idx + inc_len]) - log_to_both(printer, f"VCC {vcc} mV | VBAT {vbat} mV") - log_to_both( - printer, f"Converter Enables [{conv_enb_0},{conv_enb_1},{conv_enb_2}]" - ) - log_to_both( - printer, - f"Boot Cause {boot_cause} | Uptime {uptime} | Reset Cause {reset_cause}", - ) - current_idx += inc_len - latchup_list = [] - log_to_both(printer, "Latchups") - for idx in range(len(PDU1_CHANNELS_NAMES)): - latchup_list.append( - struct.unpack("!H", hk_data[current_idx : current_idx + 2])[0] - ) - content_line = ( - f"{PDU_CHANNEL_NAMES[priv_idx][idx].ljust(24)} | {latchup_list[idx]}" - ) - log_to_both(printer, content_line) - current_idx += 2 - device_types = [] - for idx in range(len(PDU1_CHANNELS_NAMES)): - device_types.append(hk_data[current_idx]) - current_idx += 1 - device_statuses = [] - for idx in range(len(PDU1_CHANNELS_NAMES)): - device_statuses.append(hk_data[current_idx]) - current_idx += 1 - wdt = WdtInfo() - current_idx = wdt.parse(wdt_data=hk_data[current_idx:], current_idx=current_idx) - wdt.print(printer=printer) - if set_id == SetIds.PDU_1_CORE or set_id == SetIds.PDU_2_CORE: - log_to_both(printer, f"Received PDU HK from PDU {pdu_idx}") - current_list = [] - for idx in range(len(PDU1_CHANNELS_NAMES)): - current_list.append( - struct.unpack("!h", hk_data[current_idx : current_idx + 2])[0] - ) - current_idx += 2 - voltage_list = [] - for idx in range(len(PDU1_CHANNELS_NAMES)): - voltage_list.append( - struct.unpack("!H", hk_data[current_idx : current_idx + 2])[0] - ) - current_idx += 2 - output_enb_list = [] - for idx in range(len(PDU1_CHANNELS_NAMES)): - output_enb_list.append(hk_data[current_idx]) - current_idx += 1 - header_str = f"{'Name'.ljust(24)} | OutEnb | U [mV] | I [mA]" - print(header_str) - printer.file_logger.info(header_str) - for idx in range(len(PDU1_CHANNELS_NAMES)): - out_enb = f"{output_enb_list[idx]}".ljust(6) - content_line = ( - f"{PDU_CHANNEL_NAMES[priv_idx][idx].ljust(24)} | {out_enb} | " - f"{voltage_list[idx]:05} | {current_list[idx]:04}" - ) - log_to_both(printer, content_line) - fmt_str = "!IBh" - inc_len = struct.calcsize(fmt_str) - (boot_count, batt_mode, temperature) = struct.unpack( - fmt_str, hk_data[current_idx : current_idx + inc_len] - ) - info = ( - f"Boot Count {boot_count} | Battery Mode {batt_mode} | " - f"Temperature {temperature / 10.0}" - ) - log_to_both(printer, info) - - -def handle_p60_hk_data(printer: FsfwTmTcPrinter, set_id: int, hk_data: bytes): - if set_id == SetIds.P60_CORE: - log_to_both(printer, "Received P60 Core HK. Voltages in mV, currents in mA") - current_idx = 0 - current_list = [] - for idx in range(13): - current_list.append( - struct.unpack("!h", hk_data[current_idx : current_idx + 2])[0] - ) - current_idx += 2 - voltage_list = [] - for idx in range(13): - voltage_list.append( - struct.unpack("!H", hk_data[current_idx : current_idx + 2])[0] - ) - current_idx += 2 - out_enb_list = [] - for idx in range(13): - out_enb_list.append(hk_data[current_idx]) - current_idx += 1 - header_str = f"{'Name'.ljust(24)} | OutEnb | U [mV] | I [mA]" - print(header_str) - printer.file_logger.info(header_str) - for idx in range(13): - out_enb = f"{out_enb_list[idx]}".ljust(6) - content_line = ( - f"{P60_INDEX_LIST[idx].ljust(24)} | {out_enb} | " - f"{voltage_list[idx]:05} | {current_list[idx]:04}" - ) - log_to_both(printer, content_line) - fmt_str = "!IBhHhh" - inc_len = struct.calcsize(fmt_str) - ( - boot_count, - batt_mode, - batt_current, - batt_voltage, - temp_0, - temp_1, - ) = struct.unpack(fmt_str, hk_data[current_idx : current_idx + inc_len]) - current_idx += inc_len - batt_info = ( - f"Batt: Mode {batt_mode} | Boot Count {boot_count} | " - f"Charge current {batt_current} | Voltage {batt_voltage}" - ) - temps = f"In C: Temp 0 {temp_0 / 10.0} | Temp 1 {temp_1 / 10.0} | " - log_to_both(printer, temps) - log_to_both(printer, batt_info) - printer.print_validity_buffer(validity_buffer=hk_data[current_idx:], num_vars=9) - if set_id == SetIds.P60_AUX: - log_to_both(printer, "Received P60 AUX HK. Voltages in mV, currents in mA") - current_idx = 0 - latchup_list = [] - log_to_both(printer, "P60 Dock Latchups") - for idx in range(0, 13): - latchup_list.append( - struct.unpack("!H", hk_data[current_idx : current_idx + 2])[0] - ) - content_line = f"{P60_INDEX_LIST[idx].ljust(24)} | {latchup_list[idx]}" - log_to_both(printer, content_line) - current_idx += 2 - fmt_str = "!IIHBBHHhhB" - inc_len = struct.calcsize(fmt_str) - ( - boot_cause, - uptime, - reset_cause, - heater_on, - conv_5v_on, - dock_vbat, - dock_vcc_c, - batt_temp_0, - batt_temp_1, - dearm_status, - ) = struct.unpack(fmt_str, hk_data[current_idx : current_idx + inc_len]) - current_idx += inc_len - wdt = WdtInfo() - current_idx = wdt.parse(wdt_data=hk_data[current_idx:], current_idx=current_idx) - fmt_str = "!hhbb" - inc_len = struct.calcsize(fmt_str) - ( - batt_charge_current, - batt_discharge_current, - ant6_depl, - ar6_depl, - ) = struct.unpack(fmt_str, hk_data[current_idx : current_idx + inc_len]) - current_idx += inc_len - device_types = [] - device_statuses = [] - for idx in range(8): - device_types.append(hk_data[current_idx]) - current_idx += 1 - for idx in range(8): - device_statuses.append(hk_data[current_idx]) - current_idx += 1 - util_info = ( - f"Reset Cause {reset_cause} | Boot Cause {boot_cause} | Uptime {uptime}" - ) - util_info_2 = ( - f"Conv 5V on {conv_5v_on} | Heater On {heater_on} | " - f"Dock VBAT {dock_vbat} | DOCK VCC Current {dock_vcc_c}" - ) - log_to_both(printer, util_info) - log_to_both(printer, util_info_2) - wdt.print(printer) - misc_info = ( - f"Dearm {dearm_status} | ANT6 Depl {ant6_depl} | AR6 Deply {ar6_depl}" - ) - log_to_both(printer, misc_info) - batt_info = ( - f"Batt Temp 0 {batt_temp_0 / 10.0} | Batt Temp 1 {batt_temp_1 / 10.0} | " - f"Charge Current {batt_charge_current} | Discharge Current {batt_discharge_current}" - ) - log_to_both(printer, batt_info) - printer.print_validity_buffer( - validity_buffer=hk_data[current_idx:], num_vars=27 - ) From 437be64a28230690609981dd4f790b522d402278 Mon Sep 17 00:00:00 2001 From: Ulrich Mohr Date: Wed, 18 May 2022 16:02:23 +0200 Subject: [PATCH 07/10] move tcp server into own class and reverted hk handling back to functions from class --- pus_tm/factory_hook.py | 4 +- pus_tm/hk_handler.py | 840 ---------------------------------------- pus_tm/hk_handling.py | 739 +++++++++++++++++++++++++++++++++++ pus_tm/tm_tcp_server.py | 102 +++++ 4 files changed, 843 insertions(+), 842 deletions(-) delete mode 100644 pus_tm/hk_handler.py create mode 100644 pus_tm/hk_handling.py create mode 100644 pus_tm/tm_tcp_server.py diff --git a/pus_tm/factory_hook.py b/pus_tm/factory_hook.py index 3c892be..afac7c0 100644 --- a/pus_tm/factory_hook.py +++ b/pus_tm/factory_hook.py @@ -19,7 +19,7 @@ from config.object_ids import get_object_ids from .event_handler import handle_event_packet from .verification_handler import handle_service_1_packet -from .hk_handler import HkHandler +from .hk_handling import handle_hk_packet from .action_reply_handler import handle_action_reply LOGGER = get_console_logger() @@ -48,7 +48,7 @@ def pus_factory_hook(raw_tm_packet: bytes): if service_type == 1: handle_service_1_packet(printer=FSFW_PRINTER, raw_tm=raw_tm_packet) elif service_type == 3: - HkHandler.getInstance().handle_hk_packet( + handle_hk_packet( printer=FSFW_PRINTER, raw_tm=raw_tm_packet, obj_id_dict=obj_id_dict ) elif service_type == 5: diff --git a/pus_tm/hk_handler.py b/pus_tm/hk_handler.py deleted file mode 100644 index 67f0d74..0000000 --- a/pus_tm/hk_handler.py +++ /dev/null @@ -1,840 +0,0 @@ -"""HK Handling for EIVE OBSW""" -import struct -import os -import datetime -import json -import socket -import base64 - -from tmtccmd.config.definitions import HkReplyUnpacked -from tmtccmd.tm.pus_3_fsfw_hk import ( - Service3Base, - HkContentType, - Service3FsfwTm, -) -from tmtccmd.logging import get_console_logger -from pus_tc.devs.bpx_batt import BpxSetIds -from pus_tc.devs.syrlinks_hk_handler import SetIds -from pus_tc.devs.p60dock import SetIds -from pus_tc.devs.imtq import ImtqSetIds -from tmtccmd.utility.obj_id import ObjectId, ObjectIdDictT -import config.object_ids as obj_ids - -from pus_tm.devs.reaction_wheels import handle_rw_hk_data -from pus_tm.defs import FsfwTmTcPrinter, log_to_both - - -# TODO add to configuration parameters -THERMAL_HOST = "127.0.0.1" -THERMAL_PORT = 7302 - - -LOGGER = get_console_logger() - - -class HkHandler(): - - _Instance = None - - def getInstance(): - if HkHandler._Instance == None: - HkHandler._Instance = HkHandler() - return HkHandler._Instance - - def __init__( - self): - - self.raw_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.parsed_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - self.raw_server_socket.bind(("", 3705)) - self.parsed_server_socket.bind(("", 3706)) - - # for now, only accept one connection - self.raw_server_socket.listen(0) - self.parsed_server_socket.listen(0) - - self.raw_server_socket.setblocking(False) - self.parsed_server_socket.setblocking(False) - - self.raw_connection = None - self.parsed_connection = None - - def __del__(self): - try: - self.close() - except: - LOGGER.warning("Could not close sockets!") - - def close(self): - self.raw_server_socket.close - self.parsed_server_socket.close - if self.raw_connection != None: - self.raw_connection.close() - - def report_raw_data(self, object_id: ObjectId, - set_id: int, - hk_data: bytes): - - data_dict = {} - data_dict["objectId"] = object_id.as_string - data_dict["setId"] = set_id - data_dict["rawData"] = base64.b64encode(hk_data).decode() - - self.send_dictionary_over_socket(data_dict, True) - - def send_dictionary_over_socket(self, dictionary, raw=False): - # keep listeners current - if self.raw_connection == None: - # no running connection, see if a client wants to connect - try: - (self.raw_connection, _) = self.raw_server_socket.accept() - self.raw_connection.setblocking(False) - except: - # no client waiting - pass - - if self.parsed_connection == None: - # no running connection, see if a client wants to connect - try: - (self.parsed_connection, _) = self.parsed_server_socket.accept() - self.parsed_connection.setblocking(False) - except: - # no client waiting - pass - - socket_to_use = self.parsed_connection - if raw: - socket_to_use = self.raw_connection - - if socket_to_use == None: - return - - data_json_bytes = json.dumps(dictionary).encode() - - # dle encode the bytes - # Taking a shortcut as json is inherently - # not binary (we also encoded it as utf-8), so there - # can not be any 0x02 or 0x03 be in there - data_json_bytes = b'\x02' + data_json_bytes + b'\n' + b'\x03' - - try: - sent_length = socket_to_use.send(data_json_bytes) - except: - socket_to_use = None - return - if sent_length == 0: - socket_to_use.close() - socket_to_use = None - - def handle_hk_packet( - self, - raw_tm: bytes, - obj_id_dict: ObjectIdDictT, - printer: FsfwTmTcPrinter, - ): - tm_packet = Service3FsfwTm.unpack(raw_telemetry=raw_tm, custom_hk_handling=False) - named_obj_id = obj_id_dict.get(tm_packet.object_id.as_bytes) - if named_obj_id is None: - named_obj_id = tm_packet.object_id - if tm_packet.subservice == 25 or tm_packet.subservice == 26: - hk_data = tm_packet.tm_data[8:] - self.report_raw_data(object_id=named_obj_id, - set_id=tm_packet.set_id, - hk_data=hk_data) - printer.generic_hk_tm_print( - content_type=HkContentType.HK, - object_id=named_obj_id, - set_id=tm_packet.set_id, - hk_data=hk_data, - ) - self.handle_regular_hk_print( - printer=printer, - object_id=named_obj_id, - hk_packet=tm_packet, - hk_data=hk_data, - ) - if tm_packet.subservice == 10 or tm_packet.subservice == 12: - LOGGER.warning("HK definitions printout not implemented yet") - - def handle_regular_hk_print( - self, - printer: FsfwTmTcPrinter, - object_id: ObjectId, - hk_packet: Service3Base, - hk_data: bytes, - ): - objb = object_id.as_bytes - set_id = hk_packet.set_id - """This function is called when a Service 3 Housekeeping packet is received.""" - if object_id in [obj_ids.RW1_ID, obj_ids.RW2_ID, obj_ids.RW3_ID, obj_ids.RW4_ID]: - handle_rw_hk_data(printer, object_id, set_id, hk_data) - if objb == obj_ids.SYRLINKS_HANDLER_ID: - if set_id == SetIds.RX_REGISTERS_DATASET: - return self.handle_syrlinks_rx_registers_dataset(printer, hk_data) - elif set_id == SetIds.TX_REGISTERS_DATASET: - return self.handle_syrlinks_tx_registers_dataset(printer, hk_data) - else: - LOGGER.info("Service 3 TM: Syrlinks handler reply with unknown set id") - if objb == obj_ids.IMTQ_HANDLER_ID: - if (set_id >= ImtqSetIds.POSITIVE_X_TEST) and ( - set_id <= ImtqSetIds.NEGATIVE_Z_TEST - ): - return self.handle_self_test_data(printer, hk_data) - else: - LOGGER.info("Service 3 TM: Syrlinks handler reply with unknown set id") - if objb == obj_ids.GPS_HANDLER_0_ID or object_id == obj_ids.GPS_HANDLER_1_ID: - self.handle_gps_data(printer=printer, hk_data=hk_data) - if objb == obj_ids.BPX_HANDLER_ID: - self.handle_bpx_hk_data(hk_data=hk_data, set_id=set_id, printer=printer) - if objb == obj_ids.CORE_CONTROLLER_ID: - return self.handle_core_hk_data(printer=printer, hk_data=hk_data) - if objb == obj_ids.PDU_1_HANDLER_ID: - return self.handle_pdu_data( - printer=printer, pdu_idx=1, set_id=set_id, hk_data=hk_data - ) - if objb == obj_ids.PDU_2_HANDLER_ID: - return self.handle_pdu_data( - printer=printer, pdu_idx=2, set_id=set_id, hk_data=hk_data - ) - if objb in [obj_ids.RW1_ID, obj_ids.RW2_ID, obj_ids.RW3_ID, obj_ids.RW4_ID]: - return handle_rw_hk_data( - printer=printer, object_id=object_id, set_id=set_id, hk_data=hk_data - ) - if objb == obj_ids.P60_DOCK_HANDLER: - self.handle_p60_hk_data(printer=printer, set_id=set_id, hk_data=hk_data) - if objb == obj_ids.PL_PCDU_ID: - log_to_both(printer, "Received PL PCDU HK data") - if objb == obj_ids.THERMAL_CONTROLLER_ID: - self.handle_thermal_controller_hk_data(printer=printer, set_id=set_id, hk_data=hk_data) - else: - LOGGER.info("Service 3 TM: Parsing for this SID has not been implemented.") - return HkReplyUnpacked() - - def handle_syrlinks_rx_registers_dataset(self, printer: FsfwTmTcPrinter, hk_data: bytes): - reply = HkReplyUnpacked() - header_list = [ - "RX Status", - "RX Sensitivity", - "RX Frequency Shift", - "RX IQ Power", - "RX AGC Value", - "RX Demod Eb", - "RX Demod N0", - "RX Datarate", - ] - rx_status = hk_data[0] - rx_sensitivity = struct.unpack("!I", hk_data[1:5]) - rx_frequency_shift = struct.unpack("!I", hk_data[5:9]) - rx_iq_power = struct.unpack("!H", hk_data[9:11]) - rx_agc_value = struct.unpack("!H", hk_data[11:13]) - rx_demod_eb = struct.unpack("!I", hk_data[13:17]) - rx_demod_n0 = struct.unpack("!I", hk_data[17:21]) - rx_data_rate = hk_data[21] - content_list = [ - rx_status, - rx_sensitivity, - rx_frequency_shift, - rx_iq_power, - rx_agc_value, - rx_demod_eb, - rx_demod_n0, - rx_data_rate, - ] - validity_buffer = hk_data[22:] - log_to_both(printer, str(header_list)) - log_to_both(printer, str(content_list)) - printer.print_validity_buffer(validity_buffer=validity_buffer, num_vars=8) - - def handle_syrlinks_tx_registers_dataset( - self, - printer: FsfwTmTcPrinter, - hk_data: bytes, - ): - reply = HkReplyUnpacked() - header_list = ["TX Status", "TX Waveform", "TX AGC value"] - tx_status = hk_data[0] - tx_waveform = hk_data[1] - tx_agc_value = struct.unpack("!H", hk_data[2:4]) - content_list = [tx_status, tx_waveform, tx_agc_value] - validity_buffer = hk_data[4:] - log_to_both(printer, str(header_list)) - log_to_both(printer, str(content_list)) - printer.print_validity_buffer(validity_buffer=validity_buffer, num_vars=3) - - def handle_self_test_data(self, printer: FsfwTmTcPrinter, hk_data: bytes): - header_list = [ - "Init Err", - "Init Raw Mag X [nT]", - "Init Raw Mag Y [nT]", - "Init Raw Mag Z [nT]", - "Init Cal Mag X [nT]", - "Init Cal Mag Y [nT]", - "Init Cal Mag Z [nT]", - "Init Coil X Current [mA]", - "Init Coil Y Current [mA]", - "Init Coil Z Current [mA]", - "Init Coil X Temperature [°C]", - "Init Coil Y Temperature [°C]", - "Init Coil Z Temperature [°C]", - "Err", - "Raw Mag X [nT]", - "Raw Mag Y [nT]", - "Raw Mag Z [nT]", - "Cal Mag X [nT]", - "Cal Mag Y [nT]", - "Cal Mag Z [nT]", - "Coil X Current [mA]", - "Coil Y Current [mA]", - "Coil Z Current [mA]", - "Coil X Temperature [°C]", - "Coil Y Temperature [°C]", - "Coil Z Temperature [°C]", - "Fina Err", - "Fina Raw Mag X [nT]", - "Fina Raw Mag Y [nT]", - "Fina Raw Mag Z [nT]", - "Fina Cal Mag X [nT]", - "Fina Cal Mag Y [nT]", - "Fina Cal Mag Z [nT]", - "Fina Coil X Current [mA]", - "Fina Coil Y Current [mA]", - "Fina Coil Z Current [mA]", - "Fina Coil X Temperature [°C]", - "Fina Coil Y Temperature [°C]", - "Fina Coil Z Temperature [°C]", - ] - # INIT step (no coil actuation) - init_err = hk_data[0] - init_raw_mag_x = struct.unpack("!f", hk_data[1:5])[0] - init_raw_mag_y = struct.unpack("!f", hk_data[5:9])[0] - init_raw_mag_z = struct.unpack("!f", hk_data[9:13])[0] - init_cal_mag_x = struct.unpack("!f", hk_data[13:17])[0] - init_cal_mag_y = struct.unpack("!f", hk_data[17:21])[0] - init_cal_mag_z = struct.unpack("!f", hk_data[21:25])[0] - init_coil_x_current = struct.unpack("!f", hk_data[25:29])[0] - init_coil_y_current = struct.unpack("!f", hk_data[29:33])[0] - init_coil_z_current = struct.unpack("!f", hk_data[33:37])[0] - init_coil_x_temperature = struct.unpack("!H", hk_data[37:39])[0] - init_coil_y_temperature = struct.unpack("!H", hk_data[39:41])[0] - init_coil_z_temperature = struct.unpack("!H", hk_data[41:43])[0] - - # Actuation step - err = hk_data[43] - raw_mag_x = struct.unpack("!f", hk_data[44:48])[0] - raw_mag_y = struct.unpack("!f", hk_data[48:52])[0] - raw_mag_z = struct.unpack("!f", hk_data[52:56])[0] - cal_mag_x = struct.unpack("!f", hk_data[56:60])[0] - cal_mag_y = struct.unpack("!f", hk_data[60:64])[0] - cal_mag_z = struct.unpack("!f", hk_data[64:68])[0] - coil_x_current = struct.unpack("!f", hk_data[68:72])[0] - coil_y_current = struct.unpack("!f", hk_data[72:76])[0] - coil_z_current = struct.unpack("!f", hk_data[76:80])[0] - coil_x_temperature = struct.unpack("!H", hk_data[80:82])[0] - coil_y_temperature = struct.unpack("!H", hk_data[82:84])[0] - coil_z_temperature = struct.unpack("!H", hk_data[84:86])[0] - - # FINA step (no coil actuation) - fina_err = hk_data[86] - fina_raw_mag_x = struct.unpack("!f", hk_data[87:91])[0] - fina_raw_mag_y = struct.unpack("!f", hk_data[91:95])[0] - fina_raw_mag_z = struct.unpack("!f", hk_data[95:99])[0] - fina_cal_mag_x = struct.unpack("!f", hk_data[99:103])[0] - fina_cal_mag_y = struct.unpack("!f", hk_data[103:107])[0] - fina_cal_mag_z = struct.unpack("!f", hk_data[107:111])[0] - fina_coil_x_current = struct.unpack("!f", hk_data[111:115])[0] - fina_coil_y_current = struct.unpack("!f", hk_data[115:119])[0] - fina_coil_z_current = struct.unpack("!f", hk_data[119:123])[0] - fina_coil_x_temperature = struct.unpack("!H", hk_data[123:125])[0] - fina_coil_y_temperature = struct.unpack("!H", hk_data[125:127])[0] - fina_coil_z_temperature = struct.unpack("!H", hk_data[127:129])[0] - - validity_buffer = hk_data[129:] - content_list = [ - init_err, - init_raw_mag_x, - init_raw_mag_y, - init_raw_mag_z, - init_cal_mag_x, - init_cal_mag_y, - init_cal_mag_z, - init_coil_x_current, - init_coil_y_current, - init_coil_z_current, - init_coil_x_temperature, - init_coil_y_temperature, - init_coil_z_temperature, - err, - raw_mag_x, - raw_mag_y, - raw_mag_z, - cal_mag_x, - cal_mag_y, - cal_mag_z, - coil_x_current, - coil_y_current, - coil_z_current, - coil_x_temperature, - coil_y_temperature, - coil_z_temperature, - fina_err, - fina_raw_mag_x, - fina_raw_mag_y, - fina_raw_mag_z, - fina_cal_mag_x, - fina_cal_mag_y, - fina_cal_mag_z, - fina_coil_x_current, - fina_coil_y_current, - fina_coil_z_current, - fina_coil_x_temperature, - fina_coil_y_temperature, - fina_coil_z_temperature, - ] - num_of_vars = len(header_list) - log_to_both(printer, str(header_list)) - log_to_both(printer, str(content_list)) - printer.print_validity_buffer(validity_buffer=validity_buffer, num_vars=num_of_vars) - - def handle_thermal_controller_hk_data(self, printer: FsfwTmTcPrinter, set_id: int, hk_data: bytes): - if set_id == 0: - LOGGER.info("Received Sensor Temperature data") - - # get all the floats - tm_data = struct.unpack("!ffffffffffffffff", hk_data[:16 * 4]) - parsed_data = {} - - # put them into a nice dictionary - parsed_data["SID"] = set_id - parsed_data["content"] = {} - parsed_data["content"]["SENSOR_PLOC_HEATSPREADER"] = tm_data[0] - parsed_data["content"]["SENSOR_PLOC_MISSIONBOARD"] = tm_data[1] - parsed_data["content"]["SENSOR_4K_CAMERA"] = tm_data[2] - parsed_data["content"]["SENSOR_DAC_HEATSPREADER"] = tm_data[3] - parsed_data["content"]["SENSOR_STARTRACKER"] = tm_data[4] - parsed_data["content"]["SENSOR_RW1"] = tm_data[5] - parsed_data["content"]["SENSOR_DRO"] = tm_data[6] - parsed_data["content"]["SENSOR_SCEX"] = tm_data[7] - parsed_data["content"]["SENSOR_X8"] = tm_data[8] - parsed_data["content"]["SENSOR_HPA"] = tm_data[9] - parsed_data["content"]["SENSOR_TX_MODUL"] = tm_data[10] - parsed_data["content"]["SENSOR_MPA"] = tm_data[11] - parsed_data["content"]["SENSOR_ACU"] = tm_data[12] - parsed_data["content"]["SENSOR_PLPCDU_HEATSPREADER"] = tm_data[13] - parsed_data["content"]["SENSOR_TCS_BOARD"] = tm_data[14] - parsed_data["content"]["SENSOR_MAGNETTORQUER"] = tm_data[15] - - self.send_dictionary_over_socket(parsed_data) - - def handle_gps_data(self, printer: FsfwTmTcPrinter, hk_data: bytes): - LOGGER.info(f"Received GPS data, HK data length {len(hk_data)}") - reply = HkReplyUnpacked() - var_index = 0 - header_list = [ - "Latitude", - "Longitude", - "Altitude", - "Fix Mode", - "Sats in Use", - "Date", - "Unix Seconds", - ] - latitude = struct.unpack("!d", hk_data[0:8])[0] - longitude = struct.unpack("!d", hk_data[8:16])[0] - altitude = struct.unpack("!d", hk_data[16:24])[0] - fix_mode = hk_data[24] - sat_in_use = hk_data[25] - year = struct.unpack("!H", hk_data[26:28])[0] - month = hk_data[28] - day = hk_data[29] - hours = hk_data[30] - minutes = hk_data[31] - seconds = hk_data[32] - date_string = f"{day}.{month}.{year} {hours}:{minutes}:{seconds}" - unix_seconds = struct.unpack("!I", hk_data[33:37])[0] - content_list = [ - latitude, - longitude, - altitude, - fix_mode, - sat_in_use, - date_string, - unix_seconds, - ] - var_index += 13 - reply.num_of_vars = var_index - if not os.path.isfile("gps_log.txt"): - with open("gps_log.txt", "w") as gps_file: - gps_file.write( - "Time, Latitude [deg], Longitude [deg], Altitude [m], Fix Mode, Sats in Use, " - "Date, Unix Seconds\n" - ) - with open("gps_log.txt", "a") as gps_file: - gps_file.write( - f"{datetime.datetime.now()}, {latitude}, {longitude}, {altitude}, " - f"{fix_mode}, {sat_in_use}, {date_string}, {unix_seconds}\n" - ) - validity_buffer = hk_data[37:39] - log_to_both(printer, str(header_list)) - log_to_both(printer, str(content_list)) - printer.print_validity_buffer(validity_buffer=validity_buffer, num_vars=10) - - def handle_bpx_hk_data(self, printer: FsfwTmTcPrinter, set_id: int, hk_data: bytes): - if set_id == BpxSetIds.GET_HK_SET: - fmt_str = "!HHHHhhhhIB" - inc_len = struct.calcsize(fmt_str) - ( - charge_current, - discharge_current, - heater_current, - batt_voltage, - batt_temp_1, - batt_temp_2, - batt_temp_3, - batt_temp_4, - reboot_cntr, - boot_cause, - ) = struct.unpack(fmt_str, hk_data[0:inc_len]) - header_list = [ - "Charge Current", - "Discharge Current", - "Heater Current", - "Battery Voltage", - "Batt Temp 1", - "Batt Temp 2", - "Batt Temp 3", - "Batt Temp 4", - "Reboot Counter", - "Boot Cause", - ] - content_list = [ - charge_current, - discharge_current, - heater_current, - batt_voltage, - batt_temp_1, - batt_temp_2, - batt_temp_3, - batt_temp_4, - reboot_cntr, - boot_cause, - ] - validity_buffer = hk_data[inc_len:] - log_to_both(printer, str(header_list)) - log_to_both(printer, str(content_list)) - printer.print_validity_buffer(validity_buffer=validity_buffer, num_vars=10) - elif set_id == BpxSetIds.GET_CFG_SET: - battheat_mode = hk_data[0] - battheat_low = struct.unpack("!b", hk_data[1:2])[0] - battheat_high = struct.unpack("!b", hk_data[2:3])[0] - header_list = [ - "Battery Heater Mode", - "Battery Heater Low Limit", - "Battery Heater High Limit", - ] - content_list = [battheat_mode, battheat_low, battheat_high] - validity_buffer = hk_data[3:] - log_to_both(printer, str(header_list)) - log_to_both(printer, str(content_list)) - printer.print_validity_buffer(validity_buffer=validity_buffer, num_vars=10) - - def handle_core_hk_data(self, printer: FsfwTmTcPrinter, hk_data: bytes): - - fmt_str = "!fffH" - inc_len = struct.calcsize(fmt_str) - (temperature, ps_voltage, pl_voltage, tx_agc_value) = struct.unpack( - fmt_str, hk_data[0: 0 + inc_len] - ) - printout = ( - f"Chip Temperature [°C] {temperature} | PS Voltage [mV] {ps_voltage} | " - f"PL Voltage [mV] {pl_voltage} | TX AGC {tx_agc_value}" - ) - log_to_both(printer, printout) - printer.print_validity_buffer(validity_buffer=hk_data[inc_len:], num_vars=4) - - P60_INDEX_LIST = [ - "ACU VCC", - "PDU1 VCC", - "X3 IDLE VCC", - "PDU2 VCC", - "ACU VBAT", - "PDU1 VBAT", - "X3 IDLE VBAT", - "PDU2 VBAT", - "STACK VBAT", - "STACK 3V3", - "STACK 5V", - "GS3V3", - "GS5V", - ] - - WDT_LIST = ["GND", "I2C", "CAN", "CSP0", "CSP1"] - - PDU1_CHANNELS_NAMES = [ - "TCS Board", - "Syrlinks", - "Startracker", - "MGT", - "SUS Nominal", - "SCEX", - "PLOC", - "ACS A Side", - "Unused Channel 8", - ] - - PDU2_CHANNELS_NAMES = [ - "Q7S", - "Payload PCDU CH1", - "RW", - "TCS Heater In", - "SUS Redundant", - "Deployment Mechanism", - "Payload PCDU CH6", - "ACS B Side", - "Payload Camera", - ] - - PDU_CHANNEL_NAMES = [PDU1_CHANNELS_NAMES, PDU2_CHANNELS_NAMES] - - class WdtInfo: - def __init__(self): - self.wdt_reboots_list = [] - self.time_pings_left_list = [] - - def print(self, printer: FsfwTmTcPrinter): - wdt_info = "WDT Type | Reboots | Time or Pings left (CSP only)" - log_to_both(printer, wdt_info) - for idx in range(len(self.wdt_reboots_list)): - log_to_both( - printer, - f"{TmHandler.WDT_LIST[idx].ljust(5)} | " - f"{self.wdt_reboots_list[idx]:010} | {self.time_pings_left_list[idx]:010}", - ) - - def parse(self, wdt_data: bytes, current_idx: int) -> int: - priv_idx = 0 - self.wdt_reboots_list = [] - self.time_pings_left_list = [] - for idx in range(5): - self.wdt_reboots_list.append( - struct.unpack("!I", wdt_data[priv_idx: priv_idx + 4])[0] - ) - priv_idx += 4 - current_idx += 4 - for idx in range(3): - self.time_pings_left_list.append( - struct.unpack("!I", wdt_data[priv_idx: priv_idx + 4])[0] - ) - priv_idx += 4 - current_idx += 4 - for idx in range(2): - self.time_pings_left_list.append(wdt_data[priv_idx]) - current_idx += 1 - priv_idx += 1 - return current_idx - - def handle_pdu_data( - self, printer: FsfwTmTcPrinter, pdu_idx: int, set_id: int, hk_data: bytes - ): - current_idx = 0 - priv_idx = pdu_idx - 1 - if set_id == SetIds.PDU_1_AUX or set_id == SetIds.PDU_2_AUX: - fmt_str = "!hhBBBIIH" - inc_len = struct.calcsize(fmt_str) - ( - vcc, - vbat, - conv_enb_0, - conv_enb_1, - conv_enb_2, - boot_cause, - uptime, - reset_cause, - ) = struct.unpack(fmt_str, hk_data[current_idx: current_idx + inc_len]) - log_to_both(printer, f"VCC {vcc} mV | VBAT {vbat} mV") - log_to_both( - printer, f"Converter Enables [{conv_enb_0},{conv_enb_1},{conv_enb_2}]" - ) - log_to_both( - printer, - f"Boot Cause {boot_cause} | Uptime {uptime} | Reset Cause {reset_cause}", - ) - current_idx += inc_len - latchup_list = [] - log_to_both(printer, "Latchups") - for idx in range(len(TmHandler.PDU1_CHANNELS_NAMES)): - latchup_list.append( - struct.unpack("!H", hk_data[current_idx: current_idx + 2])[0] - ) - content_line = ( - f"{TmHandler.PDU_CHANNEL_NAMES[priv_idx][idx].ljust(24)} | {latchup_list[idx]}" - ) - log_to_both(printer, content_line) - current_idx += 2 - device_types = [] - for idx in range(len(TmHandler.PDU1_CHANNELS_NAMES)): - device_types.append(hk_data[current_idx]) - current_idx += 1 - device_statuses = [] - for idx in range(len(TmHandler.PDU1_CHANNELS_NAMES)): - device_statuses.append(hk_data[current_idx]) - current_idx += 1 - wdt = self.WdtInfo() - current_idx = wdt.parse(wdt_data=hk_data[current_idx:], current_idx=current_idx) - wdt.print(printer=printer) - if set_id == SetIds.PDU_1_CORE or set_id == SetIds.PDU_2_CORE: - log_to_both(printer, f"Received PDU HK from PDU {pdu_idx}") - current_list = [] - for idx in range(len(TmHandler.PDU1_CHANNELS_NAMES)): - current_list.append( - struct.unpack("!h", hk_data[current_idx: current_idx + 2])[0] - ) - current_idx += 2 - voltage_list = [] - for idx in range(len(TmHandler.PDU1_CHANNELS_NAMES)): - voltage_list.append( - struct.unpack("!H", hk_data[current_idx: current_idx + 2])[0] - ) - current_idx += 2 - output_enb_list = [] - for idx in range(len(TmHandler.PDU1_CHANNELS_NAMES)): - output_enb_list.append(hk_data[current_idx]) - current_idx += 1 - header_str = f"{'Name'.ljust(24)} | OutEnb | U [mV] | I [mA]" - print(header_str) - printer.file_logger.info(header_str) - for idx in range(len(TmHandler.PDU1_CHANNELS_NAMES)): - out_enb = f"{output_enb_list[idx]}".ljust(6) - content_line = ( - f"{TmHandler.PDU_CHANNEL_NAMES[priv_idx][idx].ljust(24)} | {out_enb} | " - f"{voltage_list[idx]:05} | {current_list[idx]:04}" - ) - log_to_both(printer, content_line) - fmt_str = "!IBh" - inc_len = struct.calcsize(fmt_str) - (boot_count, batt_mode, temperature) = struct.unpack( - fmt_str, hk_data[current_idx: current_idx + inc_len] - ) - info = ( - f"Boot Count {boot_count} | Battery Mode {batt_mode} | " - f"Temperature {temperature / 10.0}" - ) - log_to_both(printer, info) - - def handle_p60_hk_data(self, printer: FsfwTmTcPrinter, set_id: int, hk_data: bytes): - if set_id == SetIds.P60_CORE: - log_to_both(printer, "Received P60 Core HK. Voltages in mV, currents in mA") - current_idx = 0 - current_list = [] - for idx in range(13): - current_list.append( - struct.unpack("!h", hk_data[current_idx: current_idx + 2])[0] - ) - current_idx += 2 - voltage_list = [] - for idx in range(13): - voltage_list.append( - struct.unpack("!H", hk_data[current_idx: current_idx + 2])[0] - ) - current_idx += 2 - out_enb_list = [] - for idx in range(13): - out_enb_list.append(hk_data[current_idx]) - current_idx += 1 - header_str = f"{'Name'.ljust(24)} | OutEnb | U [mV] | I [mA]" - print(header_str) - printer.file_logger.info(header_str) - for idx in range(13): - out_enb = f"{out_enb_list[idx]}".ljust(6) - content_line = ( - f"{TmHandler.P60_INDEX_LIST[idx].ljust(24)} | {out_enb} | " - f"{voltage_list[idx]:05} | {current_list[idx]:04}" - ) - log_to_both(printer, content_line) - fmt_str = "!IBhHhh" - inc_len = struct.calcsize(fmt_str) - ( - boot_count, - batt_mode, - batt_current, - batt_voltage, - temp_0, - temp_1, - ) = struct.unpack(fmt_str, hk_data[current_idx: current_idx + inc_len]) - current_idx += inc_len - batt_info = ( - f"Batt: Mode {batt_mode} | Boot Count {boot_count} | " - f"Charge current {batt_current} | Voltage {batt_voltage}" - ) - temps = f"In C: Temp 0 {temp_0 / 10.0} | Temp 1 {temp_1 / 10.0} | " - log_to_both(printer, temps) - log_to_both(printer, batt_info) - printer.print_validity_buffer(validity_buffer=hk_data[current_idx:], num_vars=9) - if set_id == SetIds.P60_AUX: - log_to_both(printer, "Received P60 AUX HK. Voltages in mV, currents in mA") - current_idx = 0 - latchup_list = [] - log_to_both(printer, "P60 Dock Latchups") - for idx in range(0, 13): - latchup_list.append( - struct.unpack("!H", hk_data[current_idx: current_idx + 2])[0] - ) - content_line = f"{TmHandler.P60_INDEX_LIST[idx].ljust(24)} | {latchup_list[idx]}" - log_to_both(printer, content_line) - current_idx += 2 - fmt_str = "!IIHBBHHhhB" - inc_len = struct.calcsize(fmt_str) - ( - boot_cause, - uptime, - reset_cause, - heater_on, - conv_5v_on, - dock_vbat, - dock_vcc_c, - batt_temp_0, - batt_temp_1, - dearm_status, - ) = struct.unpack(fmt_str, hk_data[current_idx: current_idx + inc_len]) - current_idx += inc_len - wdt = self.WdtInfo() - current_idx = wdt.parse(wdt_data=hk_data[current_idx:], current_idx=current_idx) - fmt_str = "!hhbb" - inc_len = struct.calcsize(fmt_str) - ( - batt_charge_current, - batt_discharge_current, - ant6_depl, - ar6_depl, - ) = struct.unpack(fmt_str, hk_data[current_idx: current_idx + inc_len]) - current_idx += inc_len - device_types = [] - device_statuses = [] - for idx in range(8): - device_types.append(hk_data[current_idx]) - current_idx += 1 - for idx in range(8): - device_statuses.append(hk_data[current_idx]) - current_idx += 1 - util_info = ( - f"Reset Cause {reset_cause} | Boot Cause {boot_cause} | Uptime {uptime}" - ) - util_info_2 = ( - f"Conv 5V on {conv_5v_on} | Heater On {heater_on} | " - f"Dock VBAT {dock_vbat} | DOCK VCC Current {dock_vcc_c}" - ) - log_to_both(printer, util_info) - log_to_both(printer, util_info_2) - wdt.print(printer) - misc_info = ( - f"Dearm {dearm_status} | ANT6 Depl {ant6_depl} | AR6 Deply {ar6_depl}" - ) - log_to_both(printer, misc_info) - batt_info = ( - f"Batt Temp 0 {batt_temp_0 / 10.0} | Batt Temp 1 {batt_temp_1 / 10.0} | " - f"Charge Current {batt_charge_current} | Discharge Current {batt_discharge_current}" - ) - log_to_both(printer, batt_info) - printer.print_validity_buffer( - validity_buffer=hk_data[current_idx:], num_vars=27 - ) diff --git a/pus_tm/hk_handling.py b/pus_tm/hk_handling.py new file mode 100644 index 0000000..2db8e5d --- /dev/null +++ b/pus_tm/hk_handling.py @@ -0,0 +1,739 @@ +"""HK Handling for EIVE OBSW""" +import struct +import os +import datetime + + + +from tmtccmd.config.definitions import HkReplyUnpacked +from tmtccmd.tm.pus_3_fsfw_hk import ( + Service3Base, + HkContentType, + Service3FsfwTm, +) +from tmtccmd.logging import get_console_logger +from pus_tc.devs.bpx_batt import BpxSetIds +from pus_tc.devs.syrlinks_hk_handler import SetIds +from pus_tc.devs.p60dock import SetIds +from pus_tc.devs.imtq import ImtqSetIds +from tmtccmd.utility.obj_id import ObjectId, ObjectIdDictT +import config.object_ids as obj_ids + +from pus_tm.devs.reaction_wheels import handle_rw_hk_data +from pus_tm.defs import FsfwTmTcPrinter, log_to_both + +from pus_tm.tm_tcp_server import TmTcpServer + + + +LOGGER = get_console_logger() + +TM_TCP_SERVER = TmTcpServer.getInstance() + + +def handle_hk_packet( + raw_tm: bytes, + obj_id_dict: ObjectIdDictT, + printer: FsfwTmTcPrinter, +): + tm_packet = Service3FsfwTm.unpack(raw_telemetry=raw_tm, custom_hk_handling=False) + named_obj_id = obj_id_dict.get(tm_packet.object_id.as_bytes) + if named_obj_id is None: + named_obj_id = tm_packet.object_id + if tm_packet.subservice == 25 or tm_packet.subservice == 26: + hk_data = tm_packet.tm_data[8:] + TM_TCP_SERVER.report_raw_hk_data(object_id=named_obj_id, + set_id=tm_packet.set_id, + hk_data=hk_data) + printer.generic_hk_tm_print( + content_type=HkContentType.HK, + object_id=named_obj_id, + set_id=tm_packet.set_id, + hk_data=hk_data, + ) + handle_regular_hk_print( + printer=printer, + object_id=named_obj_id, + hk_packet=tm_packet, + hk_data=hk_data, + ) + if tm_packet.subservice == 10 or tm_packet.subservice == 12: + LOGGER.warning("HK definitions printout not implemented yet") + +def handle_regular_hk_print( + printer: FsfwTmTcPrinter, + object_id: ObjectId, + hk_packet: Service3Base, + hk_data: bytes, +): + objb = object_id.as_bytes + set_id = hk_packet.set_id + """This function is called when a Service 3 Housekeeping packet is received.""" + if object_id in [obj_ids.RW1_ID, obj_ids.RW2_ID, obj_ids.RW3_ID, obj_ids.RW4_ID]: + handle_rw_hk_data(printer, object_id, set_id, hk_data) + if objb == obj_ids.SYRLINKS_HANDLER_ID: + if set_id == SetIds.RX_REGISTERS_DATASET: + return handle_syrlinks_rx_registers_dataset(printer, hk_data) + elif set_id == SetIds.TX_REGISTERS_DATASET: + return handle_syrlinks_tx_registers_dataset(printer, hk_data) + else: + LOGGER.info("Service 3 TM: Syrlinks handler reply with unknown set id") + if objb == obj_ids.IMTQ_HANDLER_ID: + if (set_id >= ImtqSetIds.POSITIVE_X_TEST) and ( + set_id <= ImtqSetIds.NEGATIVE_Z_TEST + ): + return handle_self_test_data(printer, hk_data) + else: + LOGGER.info("Service 3 TM: Syrlinks handler reply with unknown set id") + if objb == obj_ids.GPS_HANDLER_0_ID or object_id == obj_ids.GPS_HANDLER_1_ID: + handle_gps_data(printer=printer, hk_data=hk_data) + if objb == obj_ids.BPX_HANDLER_ID: + handle_bpx_hk_data(hk_data=hk_data, set_id=set_id, printer=printer) + if objb == obj_ids.CORE_CONTROLLER_ID: + return handle_core_hk_data(printer=printer, hk_data=hk_data) + if objb == obj_ids.PDU_1_HANDLER_ID: + return handle_pdu_data( + printer=printer, pdu_idx=1, set_id=set_id, hk_data=hk_data + ) + if objb == obj_ids.PDU_2_HANDLER_ID: + return handle_pdu_data( + printer=printer, pdu_idx=2, set_id=set_id, hk_data=hk_data + ) + if objb in [obj_ids.RW1_ID, obj_ids.RW2_ID, obj_ids.RW3_ID, obj_ids.RW4_ID]: + return handle_rw_hk_data( + printer=printer, object_id=object_id, set_id=set_id, hk_data=hk_data + ) + if objb == obj_ids.P60_DOCK_HANDLER: + handle_p60_hk_data(printer=printer, set_id=set_id, hk_data=hk_data) + if objb == obj_ids.PL_PCDU_ID: + log_to_both(printer, "Received PL PCDU HK data") + if objb == obj_ids.THERMAL_CONTROLLER_ID: + handle_thermal_controller_hk_data( object_id=object_id, printer=printer, set_id=set_id, hk_data=hk_data) + else: + LOGGER.info("Service 3 TM: Parsing for this SID has not been implemented.") + return HkReplyUnpacked() + +def handle_syrlinks_rx_registers_dataset( printer: FsfwTmTcPrinter, hk_data: bytes): + reply = HkReplyUnpacked() + header_list = [ + "RX Status", + "RX Sensitivity", + "RX Frequency Shift", + "RX IQ Power", + "RX AGC Value", + "RX Demod Eb", + "RX Demod N0", + "RX Datarate", + ] + rx_status = hk_data[0] + rx_sensitivity = struct.unpack("!I", hk_data[1:5]) + rx_frequency_shift = struct.unpack("!I", hk_data[5:9]) + rx_iq_power = struct.unpack("!H", hk_data[9:11]) + rx_agc_value = struct.unpack("!H", hk_data[11:13]) + rx_demod_eb = struct.unpack("!I", hk_data[13:17]) + rx_demod_n0 = struct.unpack("!I", hk_data[17:21]) + rx_data_rate = hk_data[21] + content_list = [ + rx_status, + rx_sensitivity, + rx_frequency_shift, + rx_iq_power, + rx_agc_value, + rx_demod_eb, + rx_demod_n0, + rx_data_rate, + ] + validity_buffer = hk_data[22:] + log_to_both(printer, str(header_list)) + log_to_both(printer, str(content_list)) + printer.print_validity_buffer(validity_buffer=validity_buffer, num_vars=8) + +def handle_syrlinks_tx_registers_dataset( + printer: FsfwTmTcPrinter, + hk_data: bytes, +): + reply = HkReplyUnpacked() + header_list = ["TX Status", "TX Waveform", "TX AGC value"] + tx_status = hk_data[0] + tx_waveform = hk_data[1] + tx_agc_value = struct.unpack("!H", hk_data[2:4]) + content_list = [tx_status, tx_waveform, tx_agc_value] + validity_buffer = hk_data[4:] + log_to_both(printer, str(header_list)) + log_to_both(printer, str(content_list)) + printer.print_validity_buffer(validity_buffer=validity_buffer, num_vars=3) + +def handle_self_test_data(printer: FsfwTmTcPrinter, hk_data: bytes): + header_list = [ + "Init Err", + "Init Raw Mag X [nT]", + "Init Raw Mag Y [nT]", + "Init Raw Mag Z [nT]", + "Init Cal Mag X [nT]", + "Init Cal Mag Y [nT]", + "Init Cal Mag Z [nT]", + "Init Coil X Current [mA]", + "Init Coil Y Current [mA]", + "Init Coil Z Current [mA]", + "Init Coil X Temperature [°C]", + "Init Coil Y Temperature [°C]", + "Init Coil Z Temperature [°C]", + "Err", + "Raw Mag X [nT]", + "Raw Mag Y [nT]", + "Raw Mag Z [nT]", + "Cal Mag X [nT]", + "Cal Mag Y [nT]", + "Cal Mag Z [nT]", + "Coil X Current [mA]", + "Coil Y Current [mA]", + "Coil Z Current [mA]", + "Coil X Temperature [°C]", + "Coil Y Temperature [°C]", + "Coil Z Temperature [°C]", + "Fina Err", + "Fina Raw Mag X [nT]", + "Fina Raw Mag Y [nT]", + "Fina Raw Mag Z [nT]", + "Fina Cal Mag X [nT]", + "Fina Cal Mag Y [nT]", + "Fina Cal Mag Z [nT]", + "Fina Coil X Current [mA]", + "Fina Coil Y Current [mA]", + "Fina Coil Z Current [mA]", + "Fina Coil X Temperature [°C]", + "Fina Coil Y Temperature [°C]", + "Fina Coil Z Temperature [°C]", + ] + # INIT step (no coil actuation) + init_err = hk_data[0] + init_raw_mag_x = struct.unpack("!f", hk_data[1:5])[0] + init_raw_mag_y = struct.unpack("!f", hk_data[5:9])[0] + init_raw_mag_z = struct.unpack("!f", hk_data[9:13])[0] + init_cal_mag_x = struct.unpack("!f", hk_data[13:17])[0] + init_cal_mag_y = struct.unpack("!f", hk_data[17:21])[0] + init_cal_mag_z = struct.unpack("!f", hk_data[21:25])[0] + init_coil_x_current = struct.unpack("!f", hk_data[25:29])[0] + init_coil_y_current = struct.unpack("!f", hk_data[29:33])[0] + init_coil_z_current = struct.unpack("!f", hk_data[33:37])[0] + init_coil_x_temperature = struct.unpack("!H", hk_data[37:39])[0] + init_coil_y_temperature = struct.unpack("!H", hk_data[39:41])[0] + init_coil_z_temperature = struct.unpack("!H", hk_data[41:43])[0] + + # Actuation step + err = hk_data[43] + raw_mag_x = struct.unpack("!f", hk_data[44:48])[0] + raw_mag_y = struct.unpack("!f", hk_data[48:52])[0] + raw_mag_z = struct.unpack("!f", hk_data[52:56])[0] + cal_mag_x = struct.unpack("!f", hk_data[56:60])[0] + cal_mag_y = struct.unpack("!f", hk_data[60:64])[0] + cal_mag_z = struct.unpack("!f", hk_data[64:68])[0] + coil_x_current = struct.unpack("!f", hk_data[68:72])[0] + coil_y_current = struct.unpack("!f", hk_data[72:76])[0] + coil_z_current = struct.unpack("!f", hk_data[76:80])[0] + coil_x_temperature = struct.unpack("!H", hk_data[80:82])[0] + coil_y_temperature = struct.unpack("!H", hk_data[82:84])[0] + coil_z_temperature = struct.unpack("!H", hk_data[84:86])[0] + + # FINA step (no coil actuation) + fina_err = hk_data[86] + fina_raw_mag_x = struct.unpack("!f", hk_data[87:91])[0] + fina_raw_mag_y = struct.unpack("!f", hk_data[91:95])[0] + fina_raw_mag_z = struct.unpack("!f", hk_data[95:99])[0] + fina_cal_mag_x = struct.unpack("!f", hk_data[99:103])[0] + fina_cal_mag_y = struct.unpack("!f", hk_data[103:107])[0] + fina_cal_mag_z = struct.unpack("!f", hk_data[107:111])[0] + fina_coil_x_current = struct.unpack("!f", hk_data[111:115])[0] + fina_coil_y_current = struct.unpack("!f", hk_data[115:119])[0] + fina_coil_z_current = struct.unpack("!f", hk_data[119:123])[0] + fina_coil_x_temperature = struct.unpack("!H", hk_data[123:125])[0] + fina_coil_y_temperature = struct.unpack("!H", hk_data[125:127])[0] + fina_coil_z_temperature = struct.unpack("!H", hk_data[127:129])[0] + + validity_buffer = hk_data[129:] + content_list = [ + init_err, + init_raw_mag_x, + init_raw_mag_y, + init_raw_mag_z, + init_cal_mag_x, + init_cal_mag_y, + init_cal_mag_z, + init_coil_x_current, + init_coil_y_current, + init_coil_z_current, + init_coil_x_temperature, + init_coil_y_temperature, + init_coil_z_temperature, + err, + raw_mag_x, + raw_mag_y, + raw_mag_z, + cal_mag_x, + cal_mag_y, + cal_mag_z, + coil_x_current, + coil_y_current, + coil_z_current, + coil_x_temperature, + coil_y_temperature, + coil_z_temperature, + fina_err, + fina_raw_mag_x, + fina_raw_mag_y, + fina_raw_mag_z, + fina_cal_mag_x, + fina_cal_mag_y, + fina_cal_mag_z, + fina_coil_x_current, + fina_coil_y_current, + fina_coil_z_current, + fina_coil_x_temperature, + fina_coil_y_temperature, + fina_coil_z_temperature, + ] + num_of_vars = len(header_list) + log_to_both(printer, str(header_list)) + log_to_both(printer, str(content_list)) + printer.print_validity_buffer(validity_buffer=validity_buffer, num_vars=num_of_vars) + +def handle_thermal_controller_hk_data(object_id: ObjectId, printer: FsfwTmTcPrinter, set_id: int, hk_data: bytes): + if set_id == 0: + LOGGER.info("Received Sensor Temperature data") + + # get all the floats + tm_data = struct.unpack("!ffffffffffffffff", hk_data[:16 * 4]) + parsed_data = {} + + # put them into a nice dictionary + parsed_data["SENSOR_PLOC_HEATSPREADER"] = tm_data[0] + parsed_data["SENSOR_PLOC_MISSIONBOARD"] = tm_data[1] + parsed_data["SENSOR_4K_CAMERA"] = tm_data[2] + parsed_data["SENSOR_DAC_HEATSPREADER"] = tm_data[3] + parsed_data["SENSOR_STARTRACKER"] = tm_data[4] + parsed_data["SENSOR_RW1"] = tm_data[5] + parsed_data["SENSOR_DRO"] = tm_data[6] + parsed_data["SENSOR_SCEX"] = tm_data[7] + parsed_data["SENSOR_X8"] = tm_data[8] + parsed_data["SENSOR_HPA"] = tm_data[9] + parsed_data["SENSOR_TX_MODUL"] = tm_data[10] + parsed_data["SENSOR_MPA"] = tm_data[11] + parsed_data["SENSOR_ACU"] = tm_data[12] + parsed_data["SENSOR_PLPCDU_HEATSPREADER"] = tm_data[13] + parsed_data["SENSOR_TCS_BOARD"] = tm_data[14] + parsed_data["SENSOR_MAGNETTORQUER"] = tm_data[15] + + TM_TCP_SERVER.report_parsed_hk_data(object_id, set_id, parsed_data) + +def handle_gps_data(printer: FsfwTmTcPrinter, hk_data: bytes): + LOGGER.info(f"Received GPS data, HK data length {len(hk_data)}") + reply = HkReplyUnpacked() + var_index = 0 + header_list = [ + "Latitude", + "Longitude", + "Altitude", + "Fix Mode", + "Sats in Use", + "Date", + "Unix Seconds", + ] + latitude = struct.unpack("!d", hk_data[0:8])[0] + longitude = struct.unpack("!d", hk_data[8:16])[0] + altitude = struct.unpack("!d", hk_data[16:24])[0] + fix_mode = hk_data[24] + sat_in_use = hk_data[25] + year = struct.unpack("!H", hk_data[26:28])[0] + month = hk_data[28] + day = hk_data[29] + hours = hk_data[30] + minutes = hk_data[31] + seconds = hk_data[32] + date_string = f"{day}.{month}.{year} {hours}:{minutes}:{seconds}" + unix_seconds = struct.unpack("!I", hk_data[33:37])[0] + content_list = [ + latitude, + longitude, + altitude, + fix_mode, + sat_in_use, + date_string, + unix_seconds, + ] + var_index += 13 + reply.num_of_vars = var_index + if not os.path.isfile("gps_log.txt"): + with open("gps_log.txt", "w") as gps_file: + gps_file.write( + "Time, Latitude [deg], Longitude [deg], Altitude [m], Fix Mode, Sats in Use, " + "Date, Unix Seconds\n" + ) + with open("gps_log.txt", "a") as gps_file: + gps_file.write( + f"{datetime.datetime.now()}, {latitude}, {longitude}, {altitude}, " + f"{fix_mode}, {sat_in_use}, {date_string}, {unix_seconds}\n" + ) + validity_buffer = hk_data[37:39] + log_to_both(printer, str(header_list)) + log_to_both(printer, str(content_list)) + printer.print_validity_buffer(validity_buffer=validity_buffer, num_vars=10) + +def handle_bpx_hk_data(printer: FsfwTmTcPrinter, set_id: int, hk_data: bytes): + if set_id == BpxSetIds.GET_HK_SET: + fmt_str = "!HHHHhhhhIB" + inc_len = struct.calcsize(fmt_str) + ( + charge_current, + discharge_current, + heater_current, + batt_voltage, + batt_temp_1, + batt_temp_2, + batt_temp_3, + batt_temp_4, + reboot_cntr, + boot_cause, + ) = struct.unpack(fmt_str, hk_data[0:inc_len]) + header_list = [ + "Charge Current", + "Discharge Current", + "Heater Current", + "Battery Voltage", + "Batt Temp 1", + "Batt Temp 2", + "Batt Temp 3", + "Batt Temp 4", + "Reboot Counter", + "Boot Cause", + ] + content_list = [ + charge_current, + discharge_current, + heater_current, + batt_voltage, + batt_temp_1, + batt_temp_2, + batt_temp_3, + batt_temp_4, + reboot_cntr, + boot_cause, + ] + validity_buffer = hk_data[inc_len:] + log_to_both(printer, str(header_list)) + log_to_both(printer, str(content_list)) + printer.print_validity_buffer(validity_buffer=validity_buffer, num_vars=10) + elif set_id == BpxSetIds.GET_CFG_SET: + battheat_mode = hk_data[0] + battheat_low = struct.unpack("!b", hk_data[1:2])[0] + battheat_high = struct.unpack("!b", hk_data[2:3])[0] + header_list = [ + "Battery Heater Mode", + "Battery Heater Low Limit", + "Battery Heater High Limit", + ] + content_list = [battheat_mode, battheat_low, battheat_high] + validity_buffer = hk_data[3:] + log_to_both(printer, str(header_list)) + log_to_both(printer, str(content_list)) + printer.print_validity_buffer(validity_buffer=validity_buffer, num_vars=10) + +def handle_core_hk_data(printer: FsfwTmTcPrinter, hk_data: bytes): + + fmt_str = "!fffH" + inc_len = struct.calcsize(fmt_str) + (temperature, ps_voltage, pl_voltage, tx_agc_value) = struct.unpack( + fmt_str, hk_data[0: 0 + inc_len] + ) + printout = ( + f"Chip Temperature [°C] {temperature} | PS Voltage [mV] {ps_voltage} | " + f"PL Voltage [mV] {pl_voltage} | TX AGC {tx_agc_value}" + ) + log_to_both(printer, printout) + printer.print_validity_buffer(validity_buffer=hk_data[inc_len:], num_vars=4) + +P60_INDEX_LIST = [ + "ACU VCC", + "PDU1 VCC", + "X3 IDLE VCC", + "PDU2 VCC", + "ACU VBAT", + "PDU1 VBAT", + "X3 IDLE VBAT", + "PDU2 VBAT", + "STACK VBAT", + "STACK 3V3", + "STACK 5V", + "GS3V3", + "GS5V", +] + +WDT_LIST = ["GND", "I2C", "CAN", "CSP0", "CSP1"] + +PDU1_CHANNELS_NAMES = [ + "TCS Board", + "Syrlinks", + "Startracker", + "MGT", + "SUS Nominal", + "SCEX", + "PLOC", + "ACS A Side", + "Unused Channel 8", +] + +PDU2_CHANNELS_NAMES = [ + "Q7S", + "Payload PCDU CH1", + "RW", + "TCS Heater In", + "SUS Redundant", + "Deployment Mechanism", + "Payload PCDU CH6", + "ACS B Side", + "Payload Camera", +] + +PDU_CHANNEL_NAMES = [PDU1_CHANNELS_NAMES, PDU2_CHANNELS_NAMES] + +class WdtInfo: + def __init__(self): + self.wdt_reboots_list = [] + self.time_pings_left_list = [] + + def print(self, printer: FsfwTmTcPrinter): + wdt_info = "WDT Type | Reboots | Time or Pings left (CSP only)" + log_to_both(printer, wdt_info) + for idx in range(len(self.wdt_reboots_list)): + log_to_both( + printer, + f"{TmHandler.WDT_LIST[idx].ljust(5)} | " + f"{self.wdt_reboots_list[idx]:010} | {self.time_pings_left_list[idx]:010}", + ) + + def parse(self, wdt_data: bytes, current_idx: int) -> int: + priv_idx = 0 + self.wdt_reboots_list = [] + self.time_pings_left_list = [] + for idx in range(5): + self.wdt_reboots_list.append( + struct.unpack("!I", wdt_data[priv_idx: priv_idx + 4])[0] + ) + priv_idx += 4 + current_idx += 4 + for idx in range(3): + self.time_pings_left_list.append( + struct.unpack("!I", wdt_data[priv_idx: priv_idx + 4])[0] + ) + priv_idx += 4 + current_idx += 4 + for idx in range(2): + self.time_pings_left_list.append(wdt_data[priv_idx]) + current_idx += 1 + priv_idx += 1 + return current_idx + +def handle_pdu_data( + printer: FsfwTmTcPrinter, pdu_idx: int, set_id: int, hk_data: bytes +): + current_idx = 0 + priv_idx = pdu_idx - 1 + if set_id == SetIds.PDU_1_AUX or set_id == SetIds.PDU_2_AUX: + fmt_str = "!hhBBBIIH" + inc_len = struct.calcsize(fmt_str) + ( + vcc, + vbat, + conv_enb_0, + conv_enb_1, + conv_enb_2, + boot_cause, + uptime, + reset_cause, + ) = struct.unpack(fmt_str, hk_data[current_idx: current_idx + inc_len]) + log_to_both(printer, f"VCC {vcc} mV | VBAT {vbat} mV") + log_to_both( + printer, f"Converter Enables [{conv_enb_0},{conv_enb_1},{conv_enb_2}]" + ) + log_to_both( + printer, + f"Boot Cause {boot_cause} | Uptime {uptime} | Reset Cause {reset_cause}", + ) + current_idx += inc_len + latchup_list = [] + log_to_both(printer, "Latchups") + for idx in range(len(TmHandler.PDU1_CHANNELS_NAMES)): + latchup_list.append( + struct.unpack("!H", hk_data[current_idx: current_idx + 2])[0] + ) + content_line = ( + f"{TmHandler.PDU_CHANNEL_NAMES[priv_idx][idx].ljust(24)} | {latchup_list[idx]}" + ) + log_to_both(printer, content_line) + current_idx += 2 + device_types = [] + for idx in range(len(TmHandler.PDU1_CHANNELS_NAMES)): + device_types.append(hk_data[current_idx]) + current_idx += 1 + device_statuses = [] + for idx in range(len(TmHandler.PDU1_CHANNELS_NAMES)): + device_statuses.append(hk_data[current_idx]) + current_idx += 1 + wdt = WdtInfo() + current_idx = wdt.parse(wdt_data=hk_data[current_idx:], current_idx=current_idx) + wdt.print(printer=printer) + if set_id == SetIds.PDU_1_CORE or set_id == SetIds.PDU_2_CORE: + log_to_both(printer, f"Received PDU HK from PDU {pdu_idx}") + current_list = [] + for idx in range(len(TmHandler.PDU1_CHANNELS_NAMES)): + current_list.append( + struct.unpack("!h", hk_data[current_idx: current_idx + 2])[0] + ) + current_idx += 2 + voltage_list = [] + for idx in range(len(TmHandler.PDU1_CHANNELS_NAMES)): + voltage_list.append( + struct.unpack("!H", hk_data[current_idx: current_idx + 2])[0] + ) + current_idx += 2 + output_enb_list = [] + for idx in range(len(TmHandler.PDU1_CHANNELS_NAMES)): + output_enb_list.append(hk_data[current_idx]) + current_idx += 1 + header_str = f"{'Name'.ljust(24)} | OutEnb | U [mV] | I [mA]" + print(header_str) + printer.file_logger.info(header_str) + for idx in range(len(TmHandler.PDU1_CHANNELS_NAMES)): + out_enb = f"{output_enb_list[idx]}".ljust(6) + content_line = ( + f"{TmHandler.PDU_CHANNEL_NAMES[priv_idx][idx].ljust(24)} | {out_enb} | " + f"{voltage_list[idx]:05} | {current_list[idx]:04}" + ) + log_to_both(printer, content_line) + fmt_str = "!IBh" + inc_len = struct.calcsize(fmt_str) + (boot_count, batt_mode, temperature) = struct.unpack( + fmt_str, hk_data[current_idx: current_idx + inc_len] + ) + info = ( + f"Boot Count {boot_count} | Battery Mode {batt_mode} | " + f"Temperature {temperature / 10.0}" + ) + log_to_both(printer, info) + +def handle_p60_hk_data(printer: FsfwTmTcPrinter, set_id: int, hk_data: bytes): + if set_id == SetIds.P60_CORE: + log_to_both(printer, "Received P60 Core HK. Voltages in mV, currents in mA") + current_idx = 0 + current_list = [] + for idx in range(13): + current_list.append( + struct.unpack("!h", hk_data[current_idx: current_idx + 2])[0] + ) + current_idx += 2 + voltage_list = [] + for idx in range(13): + voltage_list.append( + struct.unpack("!H", hk_data[current_idx: current_idx + 2])[0] + ) + current_idx += 2 + out_enb_list = [] + for idx in range(13): + out_enb_list.append(hk_data[current_idx]) + current_idx += 1 + header_str = f"{'Name'.ljust(24)} | OutEnb | U [mV] | I [mA]" + print(header_str) + printer.file_logger.info(header_str) + for idx in range(13): + out_enb = f"{out_enb_list[idx]}".ljust(6) + content_line = ( + f"{TmHandler.P60_INDEX_LIST[idx].ljust(24)} | {out_enb} | " + f"{voltage_list[idx]:05} | {current_list[idx]:04}" + ) + log_to_both(printer, content_line) + fmt_str = "!IBhHhh" + inc_len = struct.calcsize(fmt_str) + ( + boot_count, + batt_mode, + batt_current, + batt_voltage, + temp_0, + temp_1, + ) = struct.unpack(fmt_str, hk_data[current_idx: current_idx + inc_len]) + current_idx += inc_len + batt_info = ( + f"Batt: Mode {batt_mode} | Boot Count {boot_count} | " + f"Charge current {batt_current} | Voltage {batt_voltage}" + ) + temps = f"In C: Temp 0 {temp_0 / 10.0} | Temp 1 {temp_1 / 10.0} | " + log_to_both(printer, temps) + log_to_both(printer, batt_info) + printer.print_validity_buffer(validity_buffer=hk_data[current_idx:], num_vars=9) + if set_id == SetIds.P60_AUX: + log_to_both(printer, "Received P60 AUX HK. Voltages in mV, currents in mA") + current_idx = 0 + latchup_list = [] + log_to_both(printer, "P60 Dock Latchups") + for idx in range(0, 13): + latchup_list.append( + struct.unpack("!H", hk_data[current_idx: current_idx + 2])[0] + ) + content_line = f"{TmHandler.P60_INDEX_LIST[idx].ljust(24)} | {latchup_list[idx]}" + log_to_both(printer, content_line) + current_idx += 2 + fmt_str = "!IIHBBHHhhB" + inc_len = struct.calcsize(fmt_str) + ( + boot_cause, + uptime, + reset_cause, + heater_on, + conv_5v_on, + dock_vbat, + dock_vcc_c, + batt_temp_0, + batt_temp_1, + dearm_status, + ) = struct.unpack(fmt_str, hk_data[current_idx: current_idx + inc_len]) + current_idx += inc_len + wdt = WdtInfo() + current_idx = wdt.parse(wdt_data=hk_data[current_idx:], current_idx=current_idx) + fmt_str = "!hhbb" + inc_len = struct.calcsize(fmt_str) + ( + batt_charge_current, + batt_discharge_current, + ant6_depl, + ar6_depl, + ) = struct.unpack(fmt_str, hk_data[current_idx: current_idx + inc_len]) + current_idx += inc_len + device_types = [] + device_statuses = [] + for idx in range(8): + device_types.append(hk_data[current_idx]) + current_idx += 1 + for idx in range(8): + device_statuses.append(hk_data[current_idx]) + current_idx += 1 + util_info = ( + f"Reset Cause {reset_cause} | Boot Cause {boot_cause} | Uptime {uptime}" + ) + util_info_2 = ( + f"Conv 5V on {conv_5v_on} | Heater On {heater_on} | " + f"Dock VBAT {dock_vbat} | DOCK VCC Current {dock_vcc_c}" + ) + log_to_both(printer, util_info) + log_to_both(printer, util_info_2) + wdt.print(printer) + misc_info = ( + f"Dearm {dearm_status} | ANT6 Depl {ant6_depl} | AR6 Deply {ar6_depl}" + ) + log_to_both(printer, misc_info) + batt_info = ( + f"Batt Temp 0 {batt_temp_0 / 10.0} | Batt Temp 1 {batt_temp_1 / 10.0} | " + f"Charge Current {batt_charge_current} | Discharge Current {batt_discharge_current}" + ) + log_to_both(printer, batt_info) + printer.print_validity_buffer( + validity_buffer=hk_data[current_idx:], num_vars=27 + ) diff --git a/pus_tm/tm_tcp_server.py b/pus_tm/tm_tcp_server.py new file mode 100644 index 0000000..acd5d9f --- /dev/null +++ b/pus_tm/tm_tcp_server.py @@ -0,0 +1,102 @@ +import socket +from typing import Optional +import json +import base64 + +from tmtccmd.logging import get_console_logger +from tmtccmd.utility.obj_id import ObjectId + + +# TODO add to configuration parameters +SERVER_HOST = "" +SERVER_PORT = 7305 + +LOGGER = get_console_logger() + + +class TmTcpServer: + + _Instance = None + + def __init__( + self): + + self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + self.server_socket.bind((SERVER_HOST, SERVER_PORT)) + + # for now, only accept one connection + self.server_socket.listen(0) + + self.server_socket.setblocking(False) + + self.client_connection: Optional[socket.socket] = None + + def __del__(self): + try: + self.close() + except: + LOGGER.warning("Could not close sockets!") + + def close(self): + self.server_socket.close() + if self.client_connection != None: + self.client_connection.close() + + def getInstance(): + if TmTcpServer._Instance == None: + TmTcpServer._Instance = TmTcpServer() + return TmTcpServer._Instance + + def _send_dictionary_over_socket(self, dictionary): + # keep listeners current + if self.client_connection == None: + # no running connection, see if a client wants to connect + try: + (self.client_connection, _) = self.server_socket.accept() + self.client_connection.setblocking(False) + except: + # no client waiting + return + + data_json_bytes = json.dumps(dictionary).encode() + + # dle encode the bytes + # Taking a shortcut as json is inherently + # not binary (we also encoded it as utf-8), so there + # can not be any 0x02 or 0x03 be in there + data_json_bytes = b'\x02' + data_json_bytes + b'\n' + b'\x03' + + try: + sent_length = self.client_connection.send(data_json_bytes) + except: + self.client_connection = None + return + if sent_length == 0: + self.client_connection.close() + self.client_connection = None + + def report_raw_hk_data(self, object_id: ObjectId, + set_id: int, + hk_data: bytes): + + data_dict = {} + data_dict["type"] = "TM" + data_dict["tmType"] = "Raw HK" + data_dict["objectId"] = object_id.as_string + data_dict["setId"] = set_id + data_dict["rawData"] = base64.b64encode(hk_data).decode() + + self._send_dictionary_over_socket(data_dict) + + def report_parsed_hk_data(self, object_id: ObjectId, + set_id: int, + data_dictionary): + data_dict = {} + data_dict["type"] = "TM" + data_dict["tmType"] = "Parsed HK" + data_dict["objectId"] = object_id.as_string + data_dict["setId"] = set_id + data_dict["content"] = data_dictionary + + self._send_dictionary_over_socket(data_dict) \ No newline at end of file From bde8a2947340275fc023b631c24f32e63f035570 Mon Sep 17 00:00:00 2001 From: Ulrich Mohr Date: Wed, 18 May 2022 16:41:10 +0200 Subject: [PATCH 08/10] added TODO --- pus_tm/tm_tcp_server.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pus_tm/tm_tcp_server.py b/pus_tm/tm_tcp_server.py index acd5d9f..bc4dfd5 100644 --- a/pus_tm/tm_tcp_server.py +++ b/pus_tm/tm_tcp_server.py @@ -65,6 +65,7 @@ class TmTcpServer: # Taking a shortcut as json is inherently # not binary (we also encoded it as utf-8), so there # can not be any 0x02 or 0x03 be in there + # TODO use dle encoder to be format compliant data_json_bytes = b'\x02' + data_json_bytes + b'\n' + b'\x03' try: From b0bdb2208fd518ba46d0d493a4747e473e8f541a Mon Sep 17 00:00:00 2001 From: Ulrich Mohr Date: Wed, 18 May 2022 17:01:48 +0200 Subject: [PATCH 09/10] using dle encoder in tm tcp server --- pus_tm/tm_tcp_server.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pus_tm/tm_tcp_server.py b/pus_tm/tm_tcp_server.py index bc4dfd5..4bf0138 100644 --- a/pus_tm/tm_tcp_server.py +++ b/pus_tm/tm_tcp_server.py @@ -5,7 +5,7 @@ import base64 from tmtccmd.logging import get_console_logger from tmtccmd.utility.obj_id import ObjectId - +from dle_encoder import DleEncoder # TODO add to configuration parameters SERVER_HOST = "" @@ -32,6 +32,8 @@ class TmTcpServer: self.client_connection: Optional[socket.socket] = None + self.dle_encoder = DleEncoder() + def __del__(self): try: self.close() @@ -62,11 +64,8 @@ class TmTcpServer: data_json_bytes = json.dumps(dictionary).encode() # dle encode the bytes - # Taking a shortcut as json is inherently - # not binary (we also encoded it as utf-8), so there - # can not be any 0x02 or 0x03 be in there - # TODO use dle encoder to be format compliant - data_json_bytes = b'\x02' + data_json_bytes + b'\n' + b'\x03' + # adding a newline because someone might want to look at it in a console + data_json_bytes = self.dle_encoder.encode(data_json_bytes + b'\n') try: sent_length = self.client_connection.send(data_json_bytes) From dff2c1b04179c20d466203effd90887fdf8c5777 Mon Sep 17 00:00:00 2001 From: Ulrich Mohr Date: Wed, 18 May 2022 17:40:03 +0200 Subject: [PATCH 10/10] made parsed hk in json an array, so it can be iterated in order --- pus_tm/hk_handling.py | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/pus_tm/hk_handling.py b/pus_tm/hk_handling.py index 2db8e5d..2197f8d 100644 --- a/pus_tm/hk_handling.py +++ b/pus_tm/hk_handling.py @@ -305,23 +305,24 @@ def handle_thermal_controller_hk_data(object_id: ObjectId, printer: FsfwTmTcPrin tm_data = struct.unpack("!ffffffffffffffff", hk_data[:16 * 4]) parsed_data = {} - # put them into a nice dictionary - parsed_data["SENSOR_PLOC_HEATSPREADER"] = tm_data[0] - parsed_data["SENSOR_PLOC_MISSIONBOARD"] = tm_data[1] - parsed_data["SENSOR_4K_CAMERA"] = tm_data[2] - parsed_data["SENSOR_DAC_HEATSPREADER"] = tm_data[3] - parsed_data["SENSOR_STARTRACKER"] = tm_data[4] - parsed_data["SENSOR_RW1"] = tm_data[5] - parsed_data["SENSOR_DRO"] = tm_data[6] - parsed_data["SENSOR_SCEX"] = tm_data[7] - parsed_data["SENSOR_X8"] = tm_data[8] - parsed_data["SENSOR_HPA"] = tm_data[9] - parsed_data["SENSOR_TX_MODUL"] = tm_data[10] - parsed_data["SENSOR_MPA"] = tm_data[11] - parsed_data["SENSOR_ACU"] = tm_data[12] - parsed_data["SENSOR_PLPCDU_HEATSPREADER"] = tm_data[13] - parsed_data["SENSOR_TCS_BOARD"] = tm_data[14] - parsed_data["SENSOR_MAGNETTORQUER"] = tm_data[15] + # put them into an list with their names + parsed_data = [] + parsed_data.append({"SENSOR_PLOC_HEATSPREADER": tm_data[0]}) + parsed_data.append({"SENSOR_PLOC_MISSIONBOARD": tm_data[1]}) + parsed_data.append({"SENSOR_4K_CAMERA": tm_data[2]}) + parsed_data.append({"SENSOR_DAC_HEATSPREADER": tm_data[3]}) + parsed_data.append({"SENSOR_STARTRACKER": tm_data[4]}) + parsed_data.append({"SENSOR_RW1": tm_data[5]}) + parsed_data.append({"SENSOR_DRO": tm_data[6]}) + parsed_data.append({"SENSOR_SCEX": tm_data[7]}) + parsed_data.append({"SENSOR_X8": tm_data[8]}) + parsed_data.append({"SENSOR_HPA": tm_data[9]}) + parsed_data.append({"SENSOR_TX_MODUL": tm_data[10]}) + parsed_data.append({"SENSOR_MPA": tm_data[11]}) + parsed_data.append({"SENSOR_ACU": tm_data[12]}) + parsed_data.append({"SENSOR_PLPCDU_HEATSPREADER": tm_data[13]}) + parsed_data.append({"SENSOR_TCS_BOARD": tm_data[14]}) + parsed_data.append({"SENSOR_MAGNETTORQUER": tm_data[15]}) TM_TCP_SERVER.report_parsed_hk_data(object_id, set_id, parsed_data)