Repository: robamu-org/tmtccmd (language: python)

Issue:
Update from SOURCE
- Various bugfixes, improvements and linter fixes for updated TM handling


Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index d243a1f6..a19142be 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@ build
 *.egg-info
 
 /example/log
+/example/tmtc_config.json
 /log/
 /src/tests/log
 
diff --git a/README.md b/README.md
index 1d23c863..54718478 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
 
 TMTC Commander Core [![Documentation Status](https://readthedocs.org/projects/tmtccmd/badge/?version=latest)](https://tmtccmd.readthedocs.io/en/latest/?badge=latest)
 [![package](https://github.com/spacefisch/tmtccmd/actions/workflows/package.yml/badge.svg)](https://github.com/spacefisch/tmtccmd/actions/workflows/package.yml)
-[![codecov](https://codecov.io/gh/spacefisch/tmtccmd/branch/develop/graph/badge.svg?token=BVOE3A4WE4)](https://codecov.io/gh/spacefisch/tmtccmd)
+[![codecov](https://codecov.io/gh/robamu-org/tmtccmd/branch/develop/graph/badge.svg?token=BVOE3A4WE4)](https://codecov.io/gh/robamu-org/tmtccmd)
 ====
 
 ## Overview
diff --git a/docs/api.rst b/docs/api.rst
index ebd7d5f6..1d96f974 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -35,6 +35,7 @@ ECSS & PUS Submodules
    :maxdepth: 4
 
    api/tmtccmd.ccsds
+   api/tmtccmd.cfdp
    api/tmtccmd.ecss
    api/tmtccmd.pus
    api/tmtccmd.tc
diff --git a/docs/api/tmtccmd.ccsds.rst b/docs/api/tmtccmd.ccsds.rst
index 8e548ad5..b906fa6f 100644
--- a/docs/api/tmtccmd.ccsds.rst
+++ b/docs/api/tmtccmd.ccsds.rst
@@ -4,14 +4,6 @@ tmtccmd.ccsds package
 Submodules
 ----------
 
-tmtccmd.ccsds.cfdp module
--------------------------
-
-.. automodule:: tmtccmd.ccsds.cfdp
-   :members:
-   :undoc-members:
-   :show-inheritance:
-
 tmtccmd.ccsds.spacepacket module
 --------------------------------
 
diff --git a/docs/api/tmtccmd.cfdp.rst b/docs/api/tmtccmd.cfdp.rst
new file mode 100644
index 00000000..35d76e6d
--- /dev/null
+++ b/docs/api/tmtccmd.cfdp.rst
@@ -0,0 +1,13 @@
+tmtccmd.cfdp package
+=======================
+
+Submodules
+----------
+
+Module contents
+---------------
+
+.. automodule:: tmtccmd.cfdp
+   :members:
+   :undoc-members:
+   :show-inheritance:
\ No newline at end of file
diff --git a/docs/conf.py b/docs/conf.py
index 2879ce69..c81210ed 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -13,6 +13,7 @@
 import os
 import sys
 sys.path.insert(0, os.path.abspath('../src'))
+import tmtccmd
 
 # -- Project information -----------------------------------------------------
 
@@ -21,8 +22,7 @@
 author = 'Robin Mueller'
 
 # The full version, including alpha/beta/rc tags
-release = '1.5'
-
+version = release = tmtccmd.__version__
 
 # -- General configuration ---------------------------------------------------
 
diff --git a/docs/index.rst b/docs/index.rst
index adeadd3c..0f21217a 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -24,7 +24,7 @@ Other pages (online)
 - `project page on GitHub`_
 - This page, when viewed online is at https://tmtccmd.readthedocs.io/en/latest/
 
-.. _`project page on GitHub`: https://github.com/rmspacefish/tmtccmd
+.. _`project page on GitHub`: https://github.com/robamu-org/tmtccmd
 
 Contents
 ========
diff --git a/example/config/hook_implementation.py b/example/config/hook_implementation.py
index 11b90c4b..e9d2b598 100644
--- a/example/config/hook_implementation.py
+++ b/example/config/hook_implementation.py
@@ -16,26 +16,23 @@
 
 class ExampleHookClass(TmTcHookBase):
 
-    def get_json_config_file_path(self) -> str:
-        return "tmtc_config.json"
-
-    def get_version(self) -> str:
-        return "My Version String"
-
     def add_globals_pre_args_parsing(self, gui: bool = False):
         from tmtccmd.config.globals import set_default_globals_pre_args_parsing
         set_default_globals_pre_args_parsing(gui=gui, apid=APID)
 
     def add_globals_post_args_parsing(self, args: argparse.Namespace):
         from tmtccmd.config.globals import set_default_globals_post_args_parsing
-        set_default_globals_post_args_parsing(args=args, json_cfg_path=self.get_json_config_file_path())
+        set_default_globals_post_args_parsing(
+            args=args, json_cfg_path=self.get_json_config_file_path()
+        )
 
     def assign_communication_interface(self, com_if_key: str, tmtc_printer: TmTcPrinter) -> \
             Union[CommunicationInterface, None]:
         from tmtccmd.config.com_if import create_communication_interface_default
         LOGGER.info("Communication interface assignment function was called")
         return create_communication_interface_default(
-            com_if_key=com_if_key, tmtc_printer=tmtc_printer, json_cfg_path=self.get_json_config_file_path()
+            com_if_key=com_if_key, tmtc_printer=tmtc_printer,
+            json_cfg_path=self.get_json_config_file_path()
         )
 
     def perform_mode_operation(self, tmtc_backend: TmTcHandler, mode: int):
@@ -68,3 +65,9 @@ def handle_service_3_housekeeping(
         object_id: int, set_id: int, hk_data: bytearray, service3_packet: Service3Base
     ) -> Tuple[list, list, bytearray, int]:
         pass
+
+    @staticmethod
+    def handle_service_5_event(
+        object_id: bytes, event_id: int, param_1: int, param_2: int
+    ) -> str:
+        pass
diff --git a/example/tmtc_cli.py b/example/tmtc_cli.py
index c9854f12..2b3e1e26 100755
--- a/example/tmtc_cli.py
+++ b/example/tmtc_cli.py
@@ -1,6 +1,5 @@
 #!/usr/bin/env python3
-"""
-Example application for the TMTC Commander
+"""Example application for the TMTC Commander
 """
 from tmtccmd.ccsds.handler import CcsdsTmHandler
 from tmtccmd.runner import run_tmtc_commander, initialize_tmtc_commander, add_ccsds_handler
@@ -14,7 +13,9 @@ def main():
     hook_obj = ExampleHookClass()
     initialize_tmtc_commander(hook_object=hook_obj)
     ccsds_handler = CcsdsTmHandler()
-    ccsds_handler.add_tm_handler(apid=APID, pus_tm_handler=default_ccsds_packet_handler, max_queue_len=50)
+    ccsds_handler.add_tm_handler(
+        apid=APID, pus_tm_handler=default_ccsds_packet_handler, max_queue_len=50
+    )
     add_ccsds_handler(ccsds_handler)
     run_tmtc_commander(use_gui=False)
 
diff --git a/example/tmtc_config.json b/example/tmtc_config.json
deleted file mode 100644
index de0d92fb..00000000
--- a/example/tmtc_config.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
-    "COM_IF_KEY": "dummy"
-}
\ No newline at end of file
diff --git a/lint.py b/lint.py
index 626a7d09..79ef08e1 100755
--- a/lint.py
+++ b/lint.py
@@ -4,16 +4,27 @@
 
 
 def main():
-    status = os.system("flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics")
+    # Ignore folder created by venv
+    exclude_dirs_flag = '--exclude bin,lib'
+    additional_flags_both_steps = '--count --statistics'
+    additional_flags_first_step = '--select=E9,F63,F7,F82 --show-source'
+    flake8_first_step_cmd = \
+        f'flake8 . {additional_flags_both_steps} {additional_flags_first_step} {exclude_dirs_flag}'
+    status = os.system(flake8_first_step_cmd)
     if os.name == 'nt':
         if status != 0:
             print(f'Flake8 linter errors with status {status}')
-            sys.exit(0)
     else:
         if os.WEXITSTATUS(status) != 0:
             print(f'Flake8 linter errors with status {status}')
             sys.exit(0)
-    os.system("flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics")
+    additional_flags_second_step = \
+        '--exit-zero --max-complexity=10 --per-file-ignores="__init__.py:F401" ' \
+        '--max-line-length=127'
+    flake8_second_step_cmd = \
+        f'flake8 . {additional_flags_both_steps}  {additional_flags_second_step}' \
+        f' {exclude_dirs_flag}'
+    os.system(flake8_second_step_cmd)
 
 
 if __name__ == "__main__":
diff --git a/setup.cfg b/setup.cfg
index 72c9c0ce..b5c68a26 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -9,7 +9,7 @@ author = Robin Mueller
 author_email = robin.mueller.m@gmail.com
 platform = any
 
-url = https://github.com/rmspacefish/tmtccmd
+url = https://github.com/robamu-org/tmtccmd
 classifiers = 
 	Development Status :: 5 - Production/Stable
 	Intended Audience :: Developers
@@ -31,6 +31,7 @@ install_requires =
 	pyserial>=3.0
 	colorama>=0.4.4
 	colorlog>=5.0.0
+	dle-encoder>=0.2.0
 package_dir =
     = src
 packages = find:
diff --git a/src/tmtccmd/ccsds/cfdp.py b/src/tmtccmd/ccsds/cfdp.py
deleted file mode 100644
index 1d2eea42..00000000
--- a/src/tmtccmd/ccsds/cfdp.py
+++ /dev/null
@@ -1,1 +0,0 @@
-from tmtccmd.ccsds.handler import CcsdsTmHandler
diff --git a/src/tmtccmd/ccsds/log.py b/src/tmtccmd/ccsds/log.py
new file mode 100644
index 00000000..3d8cff89
--- /dev/null
+++ b/src/tmtccmd/ccsds/log.py
@@ -0,0 +1,6 @@
+"""In case ECSS and CFDP code go into a separate pyton package, this layer can be adapted
+to use a different logger (e.g. default logger)
+"""
+from tmtccmd.utility.logger import get_console_logger
+
+LOGGER = get_console_logger()
diff --git a/src/tmtccmd/ccsds/spacepacket.py b/src/tmtccmd/ccsds/spacepacket.py
index b7934f58..73564214 100644
--- a/src/tmtccmd/ccsds/spacepacket.py
+++ b/src/tmtccmd/ccsds/spacepacket.py
@@ -1,7 +1,8 @@
+from __future__ import annotations
 import enum
-from collections import deque
-from typing import Tuple, Deque, List
 
+from typing import Tuple, Deque, List
+from tmtccmd.ccsds.log import LOGGER
 
 SPACE_PACKET_HEADER_SIZE = 6
 
@@ -18,53 +19,84 @@ class SequenceFlags(enum.IntEnum):
     UNSEGMENTED = 0b11
 
 
-class SpacePacketCommonFields:
-    """Encapsulates common fields in a SpacePacket. Packet reference: Blue Book CCSDS 133.0-B-2"""
+class SpacePacketHeader:
+    """This class encapsulates the space packet header. Packet reference: Blue Book CCSDS 133.0-B-2
+    """
     def __init__(
             self, packet_type: PacketTypes, apid: int, source_sequence_count: int, data_length: int,
-            version: int = 0b000, secondary_header_flag: bool = True, sequence_flags: int = 0b11
+            packet_version: int = 0b000, secondary_header_flag: bool = True,
+            sequence_flags: int = 0b11
     ):
         self.packet_type = packet_type
         self.apid = apid
         self.ssc = source_sequence_count
         self.secondary_header_flag = secondary_header_flag
         self.sequence_flags = sequence_flags
-        self.psc = get_sp_packet_sequence_control(
+        self.psc = get_space_packet_sequence_control(
             sequence_flags=self.sequence_flags, source_sequence_count=self.ssc
         )
-        self.version = version
+        self.version = packet_version
         self.data_length = data_length
-        self.packet_id = get_sp_packet_id_num(
+        self.packet_id = get_space_packet_id_num(
             packet_type=self.packet_type, secondary_header_flag=self.secondary_header_flag,
             apid=self.apid
         )
 
+    @classmethod
+    def __empty(cls) -> SpacePacketHeader:
+        return SpacePacketHeader(
+            packet_type=PacketTypes.PACKET_TYPE_TC,
+            apid=0,
+            source_sequence_count=0,
+            data_length=0
+        )
 
-# pylint: disable=too-many-instance-attributes
-class SpacePacketHeaderDeserializer(SpacePacketCommonFields):
-    """This class unnpacks the common spacepacket header, also see PUS structure below or
-    PUS documentation.
-    """
-    def __init__(self, pus_packet_raw: bytearray):
-        """Deserializes space packet fields from raw bytearray
-        :param pus_packet_raw:
+    def pack(self) -> bytearray:
+        """Serialize raw space packet header.
+        :param packet_type: 0 for telemetry, 1 for telecommands
+        :param data_length: Length of packet data field
+        :param source_sequence_count:
+        :param secondary_header_flag: Indicates presence of absence of a
+        Secondary Header in the Space Packet
+        :param version: Shall be b000 for CCSDS Version 1 packets
+        :param sequence_flags: 0b11 for stand-alone packets (unsegmented user data)
+        :param apid:
         """
-        if len(pus_packet_raw) < SPACE_PACKET_HEADER_SIZE:
-            print("SpacePacketHeaderDeserializer: Packet size smaller than PUS header size!")
+        header = bytearray()
+        header.append((self.packet_id & 0xff00) >> 8)
+        header.append(self.packet_id & 0xff)
+        header.append((self.psc & 0xff00) >> 8)
+        header.append(self.psc & 0xff)
+        header.append((self.data_length & 0xff00) >> 8)
+        header.append(self.data_length & 0xff)
+        return header
+
+    @classmethod
+    def unpack(cls, space_packet_raw: bytes) -> SpacePacketHeader:
+        """Unpack a raw space packet into the space packet header
+        :raise ValueError: Raw packet length invalid"""
+        if len(space_packet_raw) < SPACE_PACKET_HEADER_SIZE:
+            LOGGER.warning('Packet size smaller than PUS header size!')
             raise ValueError
-        packet_type_raw = pus_packet_raw[0] & 0x10
-        if packet_type_raw == 0:
+        packet_type = space_packet_raw[0] & 0x10
+        if packet_type == 0:
             packet_type = PacketTypes.PACKET_TYPE_TM
         else:
             packet_type = PacketTypes.PACKET_TYPE_TC
-        super().__init__(
-            version=pus_packet_raw[0] >> 5,
+        packet_version = space_packet_raw[0] >> 5
+        secondary_header_flag = (space_packet_raw[0] & 0x8) >> 3
+        apid = ((space_packet_raw[0] & 0x7) << 8) | space_packet_raw[1]
+        sequence_flags = (space_packet_raw[2] & 0xC0) >> 6
+        ssc = ((space_packet_raw[2] << 8) | space_packet_raw[3]) & 0x3fff
+        data_length = space_packet_raw[4] << 8 | space_packet_raw[5]
+        return SpacePacketHeader(
             packet_type=packet_type,
-            secondary_header_flag=(pus_packet_raw[0] & 0x8) >> 3,
-            apid=((pus_packet_raw[0] & 0x7) << 8) | pus_packet_raw[1],
-            sequence_flags=(pus_packet_raw[2] & 0xC0) >> 6,
-            source_sequence_count=((pus_packet_raw[2] & 0x3F) << 8) | pus_packet_raw[3],
-            data_length=pus_packet_raw[4] << 8 | pus_packet_raw[5]
+            apid=apid,
+            secondary_header_flag=secondary_header_flag,
+            packet_version=packet_version,
+            data_length=data_length,
+            sequence_flags=sequence_flags,
+            source_sequence_count=ssc
         )
 
     def append_space_packet_header_content(self, content_list: list):
@@ -77,51 +109,15 @@ def append_space_packet_header_column_headers(header_list: list):
         header_list.append("SSC")
 
 
-class SpacePacketHeaderSerializer(SpacePacketCommonFields):
-    def __init__(
-            self, apid: int, packet_type: PacketTypes, data_length: int, source_sequence_count: int,
-            secondary_header_flag: bool = True, version: int = 0b000, sequence_flags: int = SequenceFlags.UNSEGMENTED
-    ):
-        """Serialize raw space packet header.
-        :param packet_type: 0 for telemetry, 1 for telecommands
-        :param data_length: Length of packet data field
-        :param source_sequence_count:
-        :param secondary_header_flag: Indicates presence of absence of a Secondary Header in the Space Packet
-        :param version: Shall be b000 for CCSDS Version 1 packets
-        :param sequence_flags: 0b11 for stand-alone packets (unsegmented user data)
-        :param apid:
-        """
-        self.packet_id_bytes = [0x00, 0x00]
-        self.packet_id_bytes[0], self.packet_id_bytes[1] = get_sp_packet_id_bytes(
-            version=version, packet_type=packet_type, secondary_header_flag=secondary_header_flag, apid=apid
-        )
-        super().__init__(
-            packet_type=packet_type,
-            source_sequence_count=source_sequence_count,
-            data_length=data_length,
-            secondary_header_flag=secondary_header_flag,
-            sequence_flags=sequence_flags,
-            version=version,
-            apid=apid
-        )
-        self.header = get_sp_space_packet_header(
-            packet_id_byte_one=self.packet_id_bytes[0], packet_id_byte_two=self.packet_id_bytes[1],
-            data_length=data_length, packet_sequence_control=self.psc
-        )
-
-    def pack(self) -> bytearray:
-        """Return the bytearray representation of the space packet header"""
-        return self.header
-
-
-def get_sp_packet_id_bytes(
+def get_space_packet_id_bytes(
         packet_type: PacketTypes, secondary_header_flag: True, apid: int, version: int = 0b000
 ) -> Tuple[int, int]:
     """This function also includes the first three bits reserved for the version.
 
     :param version: Version field of the packet ID. Defined to be 0b000 in the space packet standard
     :param packet_type:
-    :param secondary_header_flag: Indicates presence of absence of a Secondary Header in the Space Packet
+    :param secondary_header_flag: Indicates presence of absence of a Secondary Header
+    in the Space Packet
     :param apid: Application Process Identifier. Naming mechanism for managed data path, has 11 bits
     :return:
     """
@@ -132,12 +128,16 @@ def get_sp_packet_id_bytes(
     return byte_one, byte_two
 
 
-def get_sp_packet_id_num(packet_type: PacketTypes, secondary_header_flag: bool, apid: int) -> int:
+def get_space_packet_id_num(
+        packet_type: PacketTypes, secondary_header_flag: bool, apid: int
+) -> int:
     """Get packet identification segment of packet primary header in integer format"""
     return ((packet_type << 12 | int(secondary_header_flag) << 11 | apid) & 0x1fff)
 
 
-def get_sp_packet_sequence_control(sequence_flags: SequenceFlags, source_sequence_count: int) -> int:
+def get_space_packet_sequence_control(
+        sequence_flags: SequenceFlags, source_sequence_count: int
+) -> int:
     """ """
     if sequence_flags > 3:
         print(
@@ -151,7 +151,20 @@ def get_sp_packet_sequence_control(sequence_flags: SequenceFlags, source_sequenc
     return (source_sequence_count & 0x3FFF) | (sequence_flags << 14)
 
 
-def get_sp_space_packet_header(
+def get_space_packet_header(
+        packet_id: int, packet_sequence_control: int, data_length: int
+) -> bytearray:
+    header = bytearray()
+    header.append((packet_id & 0xff00) >> 8)
+    header.append(packet_id & 0xff)
+    header.append((packet_sequence_control & 0xff00) >> 8)
+    header.append(packet_sequence_control & 0xff)
+    header.append((data_length & 0xff00) >> 8)
+    header.append(data_length & 0xff)
+    return header
+
+
+def get_space_packet_header_from_packet_id_bytes(
         packet_id_byte_one: int, packet_id_byte_two: int, packet_sequence_control: int,
         data_length: int
 ) -> bytearray:
@@ -165,16 +178,16 @@ def get_sp_space_packet_header(
     return header
 
 
-def get_apid_from_raw_packet(raw_packet: bytearray):
+def get_apid_from_raw_space_packet(raw_packet: bytes) -> int:
     if len(raw_packet) < 6:
         return 0
     return ((raw_packet[0] & 0x7) << 8) | raw_packet[1]
 
 
-def get_total_packet_len_from_len_field(len_field: int):
+def get_total_space_packet_len_from_len_field(len_field: int):
     """Definition of length field is: C = (Octets in data field - 1).
     Therefore, octets in data field in len_field plus one. The total space packet length
-    is therefore len_field plus one plus the space packet header size"""
+    is therefore len_field plus one plus the space packet header size (6)"""
     return len_field + SPACE_PACKET_HEADER_SIZE + 1
 
 
@@ -186,7 +199,7 @@ def parse_space_packets(
     :param analysis_queue:
     :param packet_id:
     :param max_len:     Maximum allowed packet length
-    @return:
+    :return:
     """
     tm_list = []
     concatenated_packets = bytearray()
@@ -196,13 +209,21 @@ def parse_space_packets(
         # Put it all in one buffer
         concatenated_packets.extend(analysis_queue.pop())
     current_idx = 0
-    max_len = len(concatenated_packets)
+    if len(concatenated_packets) < 6:
+        return tm_list
     # Packet ID detected
-    while(True):
-        if current_idx > max_len - 6:
+    while True:
+        if current_idx > max_len - 6 or current_idx > len(concatenated_packets) - 1:
             break
-        current_packet_id = \
-            (concatenated_packets[current_idx] << 8) | concatenated_packets[current_idx + 1]
+        try:
+            current_packet_id = \
+                (concatenated_packets[current_idx] << 8) | concatenated_packets[current_idx + 1]
+        except IndexError:
+            LOGGER.warning(
+                f'Index error, current index {current_idx} larger than length of concatenated '
+                f'packets {len(concatenated_packets)}'
+            )
+            return tm_list
         if current_packet_id == packet_id:
             result, current_idx = __handle_packet_id_match(
                 concatenated_packets=concatenated_packets, analysis_queue=analysis_queue,
@@ -222,7 +243,7 @@ def __handle_packet_id_match(
 ) -> (int, int):
     next_packet_len_field = \
         concatenated_packets[current_idx + 4] | concatenated_packets[current_idx + 5]
-    total_packet_len = get_total_packet_len_from_len_field(next_packet_len_field)
+    total_packet_len = get_total_space_packet_len_from_len_field(next_packet_len_field)
     if total_packet_len > max_len:
         print(
             f'parse_space_packets: Detected packet length larger than specified maximum'
diff --git a/src/tmtccmd/ccsds/time.py b/src/tmtccmd/ccsds/time.py
new file mode 100644
index 00000000..c4da074a
--- /dev/null
+++ b/src/tmtccmd/ccsds/time.py
@@ -0,0 +1,152 @@
+from __future__ import annotations
+import datetime
+import math
+import enum
+import time
+from abc import abstractmethod
+
+DAYS_CCSDS_TO_UNIX = -4383
+SECONDS_PER_DAY = 86400
+UNIX_EPOCH = datetime.datetime.utcfromtimestamp(0)
+
+
+class CcsdsTimeCodeId(enum.IntEnum):
+    NONE = 0
+    CUC_CCSDS_EPOCH = 0b001
+    CUC_AGENCY_EPOCH = 0b010
+    CDS = 0b100
+    CCS = 0b101
+
+
+def convert_unix_days_to_ccsds_days(unix_days: int) -> int:
+    """Convert Unix days to CCSDS days
+
+    CCSDS epoch: 1958 Januar 1
+    Unix epoch: 1970 January 1
+    """
+    return unix_days - DAYS_CCSDS_TO_UNIX
+
+
+def convert_ccsds_days_to_unix_days(ccsds_days: int) -> int:
+    """Convert CCSDS days to Unix days
+
+    CCSDS epoch: 1958 Januar 1
+    Unix epoch: 1970 January 1
+    """
+    return ccsds_days + DAYS_CCSDS_TO_UNIX
+
+
+def read_p_field(p_field: int) -> CcsdsTimeCodeId:
+    """Read the p field and return the CCSDS Time Code ID
+    :param p_field:
+    :return:
+    :raise IndexError: P field has invalid value
+    """
+    return CcsdsTimeCodeId((p_field & 0x70) >> 4)
+
+
+class CcsdsTimeCode:
+    @abstractmethod
+    def pack(self) -> bytearray:
+        self.ccsds_id = CcsdsTimeCodeId.NONE
+        return bytearray()
+
+    @abstractmethod
+    def return_unix_seconds(self) -> int:
+        return 0
+
+    @abstractmethod
+    def return_time_string(self) -> str:
+        return ''
+
+    def add_time_to_content_list(self, content_list: list):
+        content_list.append(self.return_unix_seconds())
+        content_list.append(self.return_time_string())
+
+    @staticmethod
+    def add_time_headers_to_header_list(header_list: list):
+        header_list.append("OBSWTime (s)")
+        header_list.append("Time")
+
+
+class CdsShortTimestamp(CcsdsTimeCode):
+    """Unpacks the time datafield of the TM packet. Right now, CDS Short timeformat is used,
+    and the size of the time stamp is expected to be seven bytes.
+    """
+    CDS_SHORT_ID = 0b100
+    TIMESTAMP_SIZE = 7
+
+    def __init__(self, ccsds_days: int, ms_of_day: int):
+        self.ccsds_id = CcsdsTimeCodeId.CDS
+        self.p_field = CdsShortTimestamp.CDS_SHORT_ID << 4
+        # CCSDS recommends a 1958 Januar 1 epoch, which is different from the Unix epoch
+        self.ccsds_days = ccsds_days
+        self.unix_days = convert_ccsds_days_to_unix_days(self.ccsds_days)
+        self.unix_seconds = self.unix_days * (24 * 60 * 60)
+        self.ms_of_day = ms_of_day
+        self.seconds_of_day = self.ms_of_day / 1000.0
+        self.unix_seconds += self.seconds_of_day
+        if self.unix_seconds < 0:
+            date = datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=self.unix_seconds)
+        else:
+            date = datetime.datetime.utcfromtimestamp(self.unix_seconds)
+        self.time_string = date.strftime("%Y-%m-%d %H:%M:%S.%f")
+
+    @classmethod
+    def init_from_unix_days(cls, unix_days: int, ms_of_day: int) -> CdsShortTimestamp:
+        return cls(
+            ccsds_days=convert_unix_days_to_ccsds_days(unix_days=unix_days),
+            ms_of_day=ms_of_day
+        )
+
+    @classmethod
+    def __empty(cls):
+        return cls(
+            ccsds_days=0,
+            ms_of_day=0
+        )
+
+    def pack(self) -> bytearray:
+        cds_packet = bytearray()
+        cds_packet.append(self.p_field)
+        cds_packet.append((self.ccsds_days & 0xff00) >> 8)
+        cds_packet.append(self.ccsds_days & 0xff)
+        cds_packet.append((self.ms_of_day & 0xff000000) >> 24)
+        cds_packet.append((self.ms_of_day & 0x00ff0000) >> 16)
+        cds_packet.append((self.ms_of_day & 0x0000ff00) >> 8)
+        cds_packet.append(self.ms_of_day & 0x000000ff)
+        return cds_packet
+
+    @classmethod
+    def unpack(cls, time_field: bytearray) -> CdsShortTimestamp:
+        if len(time_field) < cls.TIMESTAMP_SIZE:
+            raise ValueError
+        # TODO: check ID?
+        p_field = time_field[0]
+        ccsds_days = ((time_field[1] << 8) | (time_field[2]))
+        ms_of_day = \
+            ((time_field[3] << 24) | (time_field[4] << 16) | (time_field[5]) << 8 | time_field[6])
+        return cls(
+            ccsds_days=ccsds_days,
+            ms_of_day=ms_of_day
+        )
+
+    @staticmethod
+    def init_from_current_time() -> CdsShortTimestamp:
+        """Returns a seven byte CDS short timestamp with the current time"""
+        unix_days = (datetime.datetime.utcnow() - UNIX_EPOCH).days
+        seconds = time.time()
+        fraction_ms = seconds - math.floor(seconds)
+        days_ms = int((seconds % SECONDS_PER_DAY) * 1000 + fraction_ms)
+        time_packet = CdsShortTimestamp.init_from_unix_days(
+            unix_days=unix_days, ms_of_day=days_ms
+        )
+        return time_packet
+
+    @abstractmethod
+    def return_unix_seconds(self) -> int:
+        return self.unix_seconds
+
+    @abstractmethod
+    def return_time_string(self) -> str:
+        return self.time_string
diff --git a/src/__init__.py b/src/tmtccmd/cfdp/__init__.py
similarity index 100%
rename from src/__init__.py
rename to src/tmtccmd/cfdp/__init__.py
diff --git a/src/tmtccmd/cfdp/conf.py b/src/tmtccmd/cfdp/conf.py
new file mode 100644
index 00000000..cad69568
--- /dev/null
+++ b/src/tmtccmd/cfdp/conf.py
@@ -0,0 +1,58 @@
+import enum
+from tmtccmd.cfdp.definitions import LenInBytes
+from tmtccmd.ccsds.log import LOGGER
+
+
+class CfdpConfKeys(enum.IntEnum):
+    LEN_ENTITY_ID = 0
+    LEN_TRANSACTION_SEQ_NUM = 1
+    WITH_CRC_CONFIG_ID = 2
+
+
+__CFDP_DICT = {
+    CfdpConfKeys.LEN_ENTITY_ID: LenInBytes.FOUR_BYTES,
+    CfdpConfKeys.LEN_TRANSACTION_SEQ_NUM: LenInBytes.TWO_BYTES,
+    CfdpConfKeys.CRC_CONFIG_ID: True
+}
+
+
+def set_default_length_entity_id(new_len: int):
+    __CFDP_DICT[CfdpConfKeys.LEN_ENTITY_ID] = new_len
+
+
+def get_default_length_entity_id() -> int:
+    return __CFDP_DICT[CfdpConfKeys.LEN_ENTITY_ID]
+
+
+def set_default_length_transaction_seq_num(new_len: int):
+    __CFDP_DICT[CfdpConfKeys.LEN_TRANSACTION_SEQ_NUM] = new_len
+
+
+def get_default_length_transaction_seq_num() -> int:
+    return __CFDP_DICT[CfdpConfKeys.LEN_TRANSACTION_SEQ_NUM]
+
+
+def set_default_pdu_crc_mode(with_crc: bool):
+    __CFDP_DICT[CfdpConfKeys.WITH_CRC_CONFIG_ID] = with_crc
+
+
+def get_default_pdu_crc_mode() -> bool:
+    return __CFDP_DICT[CfdpConfKeys.WITH_CRC_CONFIG_ID]
+
+
+def check_packet_length(raw_packet_len: int, min_len: int, warn_on_fail: bool = True) -> bool:
+    """Check whether the length of a raw packet is shorter than a specified expectedminimum length.
+    By defaults, prints a warning if this is the case
+    :param raw_packet_len:
+    :param min_len:
+    :param warn_on_fail:
+    :return: Returns True if the raw packet is larger than the specified minimum length, False
+    otherwise
+    """
+    if raw_packet_len < min_len:
+        if warn_on_fail:
+            LOGGER.warning(
+                f'Detected packet length {raw_packet_len}, smaller than expected {min_len}'
+            )
+        return False
+    return True
diff --git a/src/tmtccmd/cfdp/definitions.py b/src/tmtccmd/cfdp/definitions.py
new file mode 100644
index 00000000..2dda70d3
--- /dev/null
+++ b/src/tmtccmd/cfdp/definitions.py
@@ -0,0 +1,17 @@
+import enum
+
+
+class LenInBytes(enum.IntEnum):
+    ONE_BYTE = 0
+    TWO_BYTES = 1
+    THREE_BYTES = 2
+    FOUR_BYTES = 3
+    FIVE_BYTES = 4
+    SIX_BYTES = 5
+    SEVEN_BYTES = 6
+    EIGHT_BYTES = 7
+    NONE = 99
+
+
+class ChecksumTypes(enum.IntEnum):
+    pass
diff --git a/src/tmtccmd/cfdp/lv.py b/src/tmtccmd/cfdp/lv.py
new file mode 100644
index 00000000..5e37da91
--- /dev/null
+++ b/src/tmtccmd/cfdp/lv.py
@@ -0,0 +1,39 @@
+from __future__ import annotations
+from tmtccmd.ccsds.log import LOGGER
+
+
+class CfdpLv:
+    def __init__(self, value: bytearray):
+        """This class encapsulates CFDP LV fields
+        :raise ValueError: If value is invalid and serilization is enabled or if length of bytearray
+        is too large
+        :param serialize:
+        :param value:
+        """
+        if len(value) > 255:
+            LOGGER.warning('Length too large for LV field')
+            raise ValueError
+        self.len = len(value)
+        self.value = value
+
+    def get_len(self):
+        """Returns length of full LV packet"""
+        return self.len + 1
+
+    def pack(self):
+        packet = bytearray()
+        packet.append(self.len)
+        packet.extend(self.value)
+
+    @classmethod
+    def unpack(cls, raw_bytes: bytearray) -> CfdpLv:
+        """Parses LV field at the start of the given bytearray
+        :raise ValueError: Invalid length found
+        """
+        detected_len = raw_bytes[0]
+        if detected_len > 255:
+            LOGGER.warning('Length too large for LV field')
+            raise ValueError
+        return cls(
+            value=raw_bytes[1:]
+        )
diff --git a/src/tmtccmd/cfdp/pdu/__init__.py b/src/tmtccmd/cfdp/pdu/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/tmtccmd/cfdp/pdu/ack.py b/src/tmtccmd/cfdp/pdu/ack.py
new file mode 100644
index 00000000..cd9d8e30
--- /dev/null
+++ b/src/tmtccmd/cfdp/pdu/ack.py
@@ -0,0 +1,75 @@
+from __future__ import annotations
+import enum
+import struct
+
+from tmtccmd.cfdp.pdu.file_directive import FileDirectivePduBase, DirectiveCodes, \
+    ConditionCode
+from tmtccmd.cfdp.pdu.header import Direction, TransmissionModes, CrcFlag
+from tmtccmd.cfdp.tlv import CfdpTlv
+from tmtccmd.cfdp.conf import LenInBytes, check_packet_length
+from tmtccmd.ccsds.log import LOGGER
+
+
+class TransactionStatus(enum.IntEnum):
+    """For more detailed information: CCSDS 727.0-B-5 p.81"""
+    UNDEFINED = 0b00
+    ACTIVE = 0b01
+    TERMINATED = 0b10
+    UNRECOGNIZED = 0b11
+
+
+class AckPdu():
+    def __init__(
+        self,
+        directive_code_of_acked_pdu: DirectiveCodes,
+        condition_code_of_acked_pdu: ConditionCode,
+        transaction_status: TransactionStatus,
+        direction: Direction,
+        trans_mode: TransmissionModes,
+        crc_flag: CrcFlag = CrcFlag.GLOBAL_CONFIG,
+        len_entity_id: LenInBytes = LenInBytes.NONE,
+        len_transaction_seq_num=LenInBytes.NONE,
+    ):
+        self.pdu_file_directive = FileDirectivePduBase(
+            directive_code=DirectiveCodes.ACK_PDU,
+            direction=direction,
+            trans_mode=trans_mode,
+            crc_flag=crc_flag,
+            len_entity_id=len_entity_id,
+            len_transaction_seq_num=len_transaction_seq_num
+        )
+        self.directive_code_of_acked_pdu = directive_code_of_acked_pdu
+        self.directive_subtype_code = 0
+        if self.directive_code_of_acked_pdu == DirectiveCodes.FINISHED_PDU:
+            self.directive_subtype_code = 0b0001
+        else:
+            self.directive_subtype_code = 0b0000
+        self.condition_code_of_acked_pdu = condition_code_of_acked_pdu
+        self.transaction_status = transaction_status
+
+    @classmethod
+    def __empty(cls) -> AckPdu:
+        return cls(
+            directive_code_of_acked_pdu=None,
+            condition_code_of_acked_pdu=None,
+            transaction_status=None,
+            direction=None,
+            trans_mode=None
+        )
+
+    def pack(self):
+        packet = self.pdu_file_directive.pack()
+        packet.append((self.directive_code_of_acked_pdu << 4) | self.directive_subtype_code)
+        packet.append((self.condition_code_of_acked_pdu << 4) | self.transaction_status)
+
+    @classmethod
+    def unpack(cls, raw_packet: bytearray) -> AckPdu:
+        ack_packet = cls.__empty()
+        ack_packet.pdu_file_directive = FileDirectivePduBase.unpack(raw_packet=raw_packet)
+        current_idx = ack_packet.pdu_file_directive.get_len()
+        ack_packet.directive_code_of_acked_pdu = raw_packet[current_idx] & 0xf0
+        ack_packet.directive_subtype_code = raw_packet[current_idx] & 0x0f
+        current_idx += 1
+        ack_packet.condition_code_of_acked_pdu = raw_packet[current_idx] & 0xf0
+        ack_packet.transaction_status = raw_packet[current_idx] & 0x03
+        return ack_packet
diff --git a/src/tmtccmd/cfdp/pdu/eof.py b/src/tmtccmd/cfdp/pdu/eof.py
new file mode 100644
index 00000000..036fcd7e
--- /dev/null
+++ b/src/tmtccmd/cfdp/pdu/eof.py
@@ -0,0 +1,87 @@
+from __future__ import annotations
+import struct
+
+from tmtccmd.cfdp.pdu.file_directive import FileDirectivePduBase, DirectiveCodes, \
+    ConditionCode
+from tmtccmd.cfdp.pdu.header import Direction, TransmissionModes, CrcFlag
+from tmtccmd.cfdp.tlv import CfdpTlv
+from tmtccmd.cfdp.conf import LenInBytes, check_packet_length
+from tmtccmd.ccsds.log import LOGGER
+
+
+class EofPdu():
+    MINIMAL_LENGTH = FileDirectivePduBase.FILE_DIRECTIVE_PDU_LEN + 1 + 4 + 4
+
+    def __init__(
+        self,
+        file_checksum: int,
+        file_size: int,
+        direction: Direction,
+        trans_mode: TransmissionModes,
+        crc_flag: CrcFlag = CrcFlag.GLOBAL_CONFIG,
+        fault_location: CfdpTlv = None,
+        condition_code: ConditionCode = ConditionCode.NO_ERROR,
+        len_entity_id: LenInBytes = LenInBytes.NONE,
+        len_transaction_seq_num=LenInBytes.NONE,
+    ):
+        self.pdu_file_directive = FileDirectivePduBase(
+            directive_code=DirectiveCodes.EOF_PDU,
+            direction=direction,
+            trans_mode=trans_mode,
+            crc_flag=crc_flag,
+            len_entity_id=len_entity_id,
+            len_transaction_seq_num=len_transaction_seq_num
+        )
+        self.condition_code = condition_code
+        self.file_checksum = file_checksum
+        self.file_size = file_size
+        self.fault_location = fault_location
+
+    @classmethod
+    def __empty(cls) -> EofPdu:
+        cls(
+            file_checksum=None,
+            file_size=None,
+            direction=None,
+            trans_mode=None
+        )
+
+    def pack(self) -> bytearray:
+        eof_pdu = bytearray()
+        eof_pdu.extend(self.pdu_file_directive.pack())
+        eof_pdu.append(self.condition_code << 4)
+        eof_pdu.extend(struct.pack('!I', self.file_checksum))
+        if self.pdu_file_directive.pdu_header.large_file:
+            eof_pdu.extend(struct.pack('!Q', self.file_size))
+        else:
+            eof_pdu.extend(struct.pack('!I', self.file_size))
+        if self.fault_location is not None:
+            eof_pdu.extend(self.fault_location.pack())
+        return eof_pdu
+
+    @classmethod
+    def unpack(cls, raw_packet: bytearray) -> EofPdu:
+        """Deserialize raw EOF PDU packet
+        :param raw_packet:
+        :raise ValueError: If raw packet is too short
+        :return:
+        """
+        eof_pdu = cls.__empty()
+        eof_pdu.pdu_file_directive = FileDirectivePduBase.unpack(raw_packet=raw_packet)
+        expected_min_len = cls.MINIMAL_LENGTH
+        if not check_packet_length(raw_packet_len=len(raw_packet), min_len=expected_min_len):
+            raise ValueError
+        current_idx = eof_pdu.pdu_file_directive.get_len()
+        eof_pdu.condition_code = raw_packet[current_idx] & 0xf0
+        expected_min_len = eof_pdu.pdu_file_directive.get_len() + 5
+        current_idx += 1
+        checksum_raw = raw_packet[current_idx: current_idx + 4]
+        eof_pdu.file_checksum = struct.unpack('!I', checksum_raw)[0]
+        current_idx += 4
+        current_idx, eof_pdu.file_size = eof_pdu.pdu_file_directive.parse_fss_field(
+            raw_packet=raw_packet, current_idx=current_idx
+        )
+        if len(raw_packet) > current_idx:
+            eof_pdu.fault_location = CfdpTlv(serialize=False)
+            eof_pdu.fault_location.unpack(raw_bytes=raw_packet[current_idx:])
+        return eof_pdu
diff --git a/src/tmtccmd/cfdp/pdu/file_directive.py b/src/tmtccmd/cfdp/pdu/file_directive.py
new file mode 100644
index 00000000..4cb3ec9b
--- /dev/null
+++ b/src/tmtccmd/cfdp/pdu/file_directive.py
@@ -0,0 +1,119 @@
+from __future__ import annotations
+import enum
+import struct
+
+from tmtccmd.cfdp.pdu.header import PduHeader, PduType, Direction, CrcFlag, TransmissionModes
+from tmtccmd.cfdp.conf import check_packet_length
+from tmtccmd.cfdp.definitions import LenInBytes
+from tmtccmd.ccsds.log import LOGGER
+
+
+class DirectiveCodes(enum.IntEnum):
+    EOF_PDU = 0x04
+    FINISHED_PDU = 0x05
+    ACK_PDU = 0x06
+    METADATA_PDU = 0x07
+    NAK_PDU = 0x08
+    PROMPT_PDU = 0x09
+    KEEP_ALIVE_PDU = 0x0C
+
+
+class ConditionCode(enum.IntEnum):
+    NO_CONDITION_FIELD = -1
+    NO_ERROR = 0b0000
+    POSITIVE_ACK_LIMIT_REACHED = 0b0001
+    KEEP_ALIVE_LIMIT_REACHED = 0b0010
+    INVALID_TRANSMISSION_MODE = 0b0011
+    FILESTORE_REJECTION = 0b0100
+    FILE_CHECKSUM_FAILURE = 0b0101
+    FILE_SIZE_ERROR = 0b0110
+    NAK_LIMIT_REACHED = 0b0111
+    INACTIVITY_DETECTED = 0b1000
+    CHECK_LIMIT_REACHED = 0b1010
+    UNSUPPORTED_CHECKSUM_TYPE = 0b1011
+    SUSPEND_REQUEST_RECEIVED = 0b1110
+    CANCEL_REQUEST_RECEIVED = 0b1111
+
+
+class FileDirectivePduBase:
+    FILE_DIRECTIVE_PDU_LEN = 5
+    """Base class for file directive PDUs encapsulating all its common components.
+    All other file directive PDU classes implement this class
+    """
+    def __init__(
+            self,
+            directive_code: DirectiveCodes,
+            # PDU Header parameters
+            direction: Direction,
+            trans_mode: TransmissionModes,
+            crc_flag: CrcFlag,
+            len_entity_id: LenInBytes = LenInBytes.NONE,
+            len_transaction_seq_num: LenInBytes = LenInBytes.NONE,
+    ):
+        self.pdu_header = PduHeader(
+            pdu_type=PduType.FILE_DIRECTIVE,
+            direction=direction,
+            trans_mode=trans_mode,
+            crc_flag=crc_flag,
+            len_entity_id=len_entity_id,
+            len_transaction_seq_num=len_transaction_seq_num
+        )
+        self.directive_code = directive_code
+
+    @classmethod
+    def __empty(cls) -> FileDirectivePduBase:
+        return cls(
+            directive_code=None,
+            direction=None,
+            trans_mode=None,
+            crc_flag=None
+        )
+
+    def get_len(self) -> int:
+        return self.FILE_DIRECTIVE_PDU_LEN
+
+    def pack(self) -> bytearray:
+        data = bytearray()
+        data.extend(self.pdu_header.pack())
+        data.append(self.directive_code)
+        return data
+
+    @classmethod
+    def unpack(cls, raw_packet: bytearray) -> FileDirectivePduBase:
+        """Unpack a raw bytearray into the File Directive PDU object representation
+        :param raw_bytes:
+        :raise ValueError: Passed bytearray is too short.
+        :return:
+        """
+        file_directive = cls.__empty()
+        file_directive.pdu_header = PduHeader.unpack(raw_packet=raw_packet)
+        if not check_packet_length(raw_packet_len=len(raw_packet), min_len=5):
+            raise ValueError
+        file_directive.directive_code = raw_packet[4]
+        return file_directive
+
+    def verify_file_len(self, file_size: int) -> bool:
+        if self.pdu_file_directive.pdu_header.large_file and file_size > pow(2, 64):
+            LOGGER.warning(f'File size {file_size} larger than 64 bit field')
+            raise False
+        elif not self.pdu_file_directive.pdu_header.large_file and file_size > pow(2, 32):
+            LOGGER.warning(f'File size {file_size} larger than 32 bit field')
+            raise False
+        return True
+
+    def parse_fss_field(self, raw_packet: bytearray, current_idx: int) -> (int, int):
+        """Parse the FSS field, which has different size depending on the large file flag being
+        set or not. Returns the current index incremented and the parsed file size
+        :raise ValueError: Packet not large enough
+        """
+        if self.pdu_header.large_file:
+            if not check_packet_length(len(raw_packet), current_idx + 8 + 1):
+                raise ValueError
+            file_size = struct.unpack('!I', raw_packet[current_idx: current_idx + 8])
+            current_idx += 8
+        else:
+            if not check_packet_length(len(raw_packet), current_idx + 4 + 1):
+                raise ValueError
+            file_size = struct.unpack('!I', raw_packet[current_idx: current_idx + 4])
+            current_idx += 4
+        return current_idx, file_size
diff --git a/src/tmtccmd/cfdp/pdu/finished.py b/src/tmtccmd/cfdp/pdu/finished.py
new file mode 100644
index 00000000..36831bc1
--- /dev/null
+++ b/src/tmtccmd/cfdp/pdu/finished.py
@@ -0,0 +1,119 @@
+from __future__ import annotations
+import enum
+
+from tmtccmd.cfdp.pdu.file_directive import FileDirectivePduBase, DirectiveCodes, Direction, \
+    TransmissionModes, CrcFlag, ConditionCode
+from tmtccmd.cfdp.definitions import LenInBytes
+from tmtccmd.cfdp.conf import check_packet_length, LOGGER
+from tmtccmd.cfdp.tlv import CfdpTlv
+from typing import List
+
+
+class DeliveryCode(enum.IntEnum):
+    DATA_COMPLETE = 0
+    DATA_INCOMPLETE = 1
+
+
+class FileDeliveryStatus(enum.IntEnum):
+    DISCARDED_DELIBERATELY = 0
+    DISCARDED_FILESTORE_REJECTION = 1
+    FILE_RETAINED = 2
+    FILE_STATUS_UNREPORTED = 3
+
+
+class FinishedPdu():
+    MINIMAL_LEN = FileDirectivePduBase.FILE_DIRECTIVE_PDU_LEN + 1
+
+    def __init__(
+            self,
+            direction: Direction,
+            delivery_code: DeliveryCode,
+            file_delivery_status: FileDeliveryStatus,
+            trans_mode: TransmissionModes,
+            condition_code: ConditionCode = ConditionCode.NO_ERROR,
+            file_store_responses: List[CfdpTlv] = [],
+            fault_location: CfdpTlv = None,
+            crc_flag: CrcFlag = CrcFlag.GLOBAL_CONFIG,
+            len_entity_id: LenInBytes = LenInBytes.NONE,
+            len_transaction_seq_num: LenInBytes = LenInBytes.NONE
+    ):
+        self.pdu_file_directive = FileDirectivePduBase(
+            directive_code=DirectiveCodes.FINISHED_PDU,
+            direction=direction,
+            trans_mode=trans_mode,
+            crc_flag=crc_flag,
+            len_entity_id=len_entity_id,
+            len_transaction_seq_num=len_transaction_seq_num
+        )
+        self.condition_code = condition_code
+        self.delivery_code = delivery_code
+        self.file_store_responses = file_store_responses
+        self.fault_location = fault_location
+        self.file_delivery_status = file_delivery_status
+        self.might_have_fault_location = False
+        if self.condition_code != ConditionCode.NO_ERROR and \
+                self.condition_code != ConditionCode.UNSUPPORTED_CHECKSUM_TYPE:
+            self.might_have_fault_location = True
+
+    @classmethod
+    def __empty(cls) -> FinishedPdu:
+        cls(
+            direction=None,
+            delivery_code=None,
+            file_delivery_status=None,
+            trans_mode=None,
+            condition_code=None
+        )
+
+    def pack(self) -> bytearray:
+        packet = self.pdu_file_directive.pack()
+        packet[len(packet)] |= \
+            (self.condition_code << 4) | (self.delivery_code << 2) | self.file_delivery_status
+        for file_store_reponse in self.file_store_responses:
+            packet.extend(file_store_reponse.pack())
+        if self.fault_location is not None:
+            packet.extend(self.fault_location.pack())
+        return packet
+
+    @classmethod
+    def unpack(cls, raw_packet: bytearray) -> FinishedPdu:
+        """Unpack a raw packet into a PDU object
+        :param raw_packet:
+        :raise ValueError: If packet is too short
+        :return:
+        """
+        finished_pdu = cls.__empty()
+        finished_pdu.pdu_file_directive = FileDirectivePduBase.unpack(raw_packet=raw_packet)
+        if not check_packet_length(raw_packet_len=len(raw_packet), min_len=cls.MINIMAL_LEN):
+            raise ValueError
+        current_idx = finished_pdu.pdu_file_directive.get_len()
+        first_param_byte = raw_packet[current_idx]
+        finished_pdu.condition_code = first_param_byte & 0xf0
+        finished_pdu.delivery_code = first_param_byte & 0x04
+        finished_pdu.file_delivery_status = first_param_byte & 0b11
+        current_idx += 1
+        if len(raw_packet) > current_idx:
+            finished_pdu.unpack_tlvs(raw_packet=raw_packet, start_idx=current_idx)
+        return finished_pdu
+
+    def unpack_tlvs(self, raw_packet: bytearray, start_idx: int) -> int:
+        current_idx = start_idx
+        while True:
+            current_tlv = CfdpTlv(serialize=False)
+            current_tlv.unpack(raw_bytes=raw_packet[current_idx])
+            # This will always increment at least two, so we can't get stuck in the loop
+            current_idx += current_tlv.get_total_length()
+            if current_idx > len(raw_packet) or current_idx == len(raw_packet):
+                if current_idx > len(raw_packet):
+                    LOGGER.warning(
+                        'Parser Error when parsing TLVs in Finished PDU. Possibly invalid packet'
+                    )
+                if self.might_have_fault_location:
+                    self.fault_location = current_tlv
+                else:
+                    self.file_store_responses.append(current_tlv)
+                break
+            else:
+                # Another TLV might follow, so this is a file store response
+                self.file_store_responses.append(current_tlv)
+        return current_idx
diff --git a/src/tmtccmd/cfdp/pdu/header.py b/src/tmtccmd/cfdp/pdu/header.py
new file mode 100644
index 00000000..920b1e0e
--- /dev/null
+++ b/src/tmtccmd/cfdp/pdu/header.py
@@ -0,0 +1,147 @@
+from __future__ import annotations
+import enum
+
+from tmtccmd.ccsds.handler import CcsdsTmHandler
+from tmtccmd.utility.logger import get_console_logger
+from tmtccmd.cfdp.definitions import LenInBytes
+from tmtccmd.cfdp.conf import get_default_length_entity_id, \
+    get_default_length_transaction_seq_num, get_default_pdu_crc_mode
+
+
+LOGGER = get_console_logger()
+
+
+class PduType(enum.IntEnum):
+    FILE_DIRECTIVE = 0
+    FILE_DATA = 1
+
+
+class Direction(enum.IntEnum):
+    TOWARDS_SENDER = 0
+    TOWARDS_RECEIVER = 1
+
+
+class TransmissionModes(enum.IntEnum):
+    ACKNOWLEDGED = 0
+    UNACKNOWLEDGED = 1
+
+
+class CrcFlag(enum.IntEnum):
+    WITH_CRC = 0
+    NO_CRC = 1
+    GLOBAL_CONFIG = 2
+
+
+class SegmentMetadataFlag(enum.IntEnum):
+    """Aways 0 and ignored for File Directive PDUs (CCSDS 727.0-B-5 p.75)"""
+    NOT_PRESENT = 0
+    PRESENT = 1
+
+
+class SegmentationControl(enum.IntEnum):
+    """Aways 0 and ignored for File Directive PDUs (CCSDS 727.0-B-5 p.75)"""
+    NO_RECORD_BOUNDARIES_PRESERVATION = 0
+    RECORD_BOUNDARIES_PRESERVATION = 1
+
+
+class PduHeader:
+    VERSION_BITS = 0b0010_0000
+
+    """This class encapsulates the fixed-format PDU header.
+    For more, information, refer to CCSDS 727.0-B-5 p.75"""
+    def __init__(
+            self,
+            pdu_type: PduType,
+            direction: Direction,
+            trans_mode: TransmissionModes,
+            crc_flag: CrcFlag = CrcFlag.GLOBAL_CONFIG,
+            len_entity_id: LenInBytes = LenInBytes.NONE,
+            len_transaction_seq_num: LenInBytes = LenInBytes.NONE,
+            seg_ctrl: SegmentationControl = SegmentationControl.NO_RECORD_BOUNDARIES_PRESERVATION,
+            segment_metadata_flag: SegmentMetadataFlag = SegmentMetadataFlag.NOT_PRESENT,
+    ):
+        """Constructor for PDU header
+        :param serialize: Specify whether a packet will be serialized or deserialized
+        :param pdu_type:
+        :param direction:
+        :param trans_mode:
+        :param crc_flag: If not supplied, assign the default configuration
+        :param len_entity_id: If not suplied, the default configuration will be used
+        :param len_transaction_seq_num: If not supplied, the default configuration will be used
+        :param seg_ctrl:
+        :param segment_metadata_flag:
+        :raise ValueError: If some field were not specified with serialize == True
+        """
+        self.pdu_type = pdu_type
+        self.direction = direction
+        self.trans_mode = trans_mode
+        self.large_file = False
+        self.pdu_data_field_length = 0
+        self.segmentation_control = seg_ctrl
+        if len_entity_id is LenInBytes.NONE:
+            self.len_entity_id = get_default_length_entity_id()
+        else:
+            self.len_entity_id = len_entity_id
+        if len_transaction_seq_num is LenInBytes.NONE:
+            self.len_transaction_seq_num = get_default_length_transaction_seq_num()
+        else:
+            self.len_transaction_seq_num = len_transaction_seq_num
+        if crc_flag == CrcFlag.GLOBAL_CONFIG:
+            self.crc_flag = get_default_pdu_crc_mode()
+        else:
+            self.crc_flag = crc_flag
+        self.segment_metadata_flag = segment_metadata_flag
+
+    def set_large_file_flag(self):
+        self.large_file = True
+
+    def set_pdu_data_field_length(self, new_length: int):
+        self.pdu_data_field_length = new_length
+
+    def pack(self) -> bytearray:
+        header = bytearray()
+        header.append(
+            self.VERSION_BITS | (self.pdu_type << 4) | (self.direction << 3) |
+            (self.trans_mode << 2) | (self.crc_flag << 1) | self.large_file
+        )
+        header.append((self.pdu_data_field_length >> 8) & 0xff)
+        header.append(self.pdu_data_field_length & 0xff)
+        header.append(
+            self.segmentation_control << 7 | self.len_entity_id << 4 |
+            self.segment_metadata_flag << 3 | self.len_transaction_seq_num
+        )
+        return header
+
+    @classmethod
+    def unpack(cls, raw_packet: bytearray) -> PduHeader:
+        """Unpack a raw bytearray into the PDU header oject representation
+        :param raw_packet:
+        :raise ValueError: Passed bytearray is too short.
+        :return:
+        """
+        if len(raw_packet) < 4:
+            LOGGER.warning('Can not unpack less than four bytes into PDU header')
+            raise ValueError
+        pdu_type = raw_packet[0] & 0x10
+        direction = raw_packet[0] & 0x08
+        trans_mode = raw_packet[0] & 0x04
+        crc_flag = raw_packet[0] & 0x02
+        large_file = raw_packet[0] & 0x01
+        pdu_data_field_length = raw_packet[1] << 8 | raw_packet[2]
+        segmentation_control = raw_packet[3] & 0x80
+        len_entity_id = raw_packet[3] & 0x70
+        segment_metadata_flag = raw_packet[3] & 0x08
+        len_transaction_seq_num = raw_packet[3] & 0x01
+
+        return cls(
+            pdu_type=pdu_type,
+            direction=direction,
+            trans_mode=trans_mode,
+            crc_flag=crc_flag,
+            large_file=large_file,
+            pdu_data_field_length=pdu_data_field_length,
+            segmentation_control=segmentation_control,
+            len_entity_id=len_entity_id,
+            segment_metadata_flag=segment_metadata_flag,
+            len_transaction_seq_num=len_transaction_seq_num
+        )
diff --git a/src/tmtccmd/cfdp/pdu/metadata.py b/src/tmtccmd/cfdp/pdu/metadata.py
new file mode 100644
index 00000000..ec733859
--- /dev/null
+++ b/src/tmtccmd/cfdp/pdu/metadata.py
@@ -0,0 +1,116 @@
+from __future__ import annotations
+import struct
+from typing import List
+
+from tmtccmd.cfdp.pdu.file_directive import FileDirectivePduBase, DirectiveCodes, \
+    ConditionCode
+from tmtccmd.cfdp.pdu.header import Direction, TransmissionModes, CrcFlag
+from tmtccmd.cfdp.tlv import CfdpTlv
+from tmtccmd.cfdp.lv import CfdpLv
+from tmtccmd.cfdp.definitions import LenInBytes, ChecksumTypes
+from tmtccmd.cfdp.conf import check_packet_length
+from tmtccmd.ccsds.log import LOGGER
+
+
+class MetadataPdu():
+    def __init__(
+        self,
+        closure_requested: bool,
+        checksum_type: ChecksumTypes,
+        file_size: int,
+        source_file_name: str,
+        dest_file_name: str,
+        direction: Direction,
+        trans_mode: TransmissionModes,
+        options: List[CfdpTlv] = [],
+        crc_flag: CrcFlag = CrcFlag.GLOBAL_CONFIG,
+        len_entity_id: LenInBytes = LenInBytes.NONE,
+        len_transaction_seq_num=LenInBytes.NONE,
+    ):
+        self.pdu_file_directive = FileDirectivePduBase(
+            directive_code=DirectiveCodes.METADATA_PDU,
+            direction=direction,
+            trans_mode=trans_mode,
+            crc_flag=crc_flag,
+            len_entity_id=len_entity_id,
+            len_transaction_seq_num=len_transaction_seq_num
+        )
+        self.closure_requested = closure_requested
+        self.checksum_type = checksum_type
+        self.file_size = file_size
+        source_file_name_as_bytes = source_file_name.encode('utf-8')
+        self.source_file_name_lv = CfdpLv(
+            value=source_file_name_as_bytes
+        )
+        dest_file_name_as_bytes = dest_file_name.encode('utf-8')
+        self.dest_file_name_lv = CfdpLv(
+            value=dest_file_name_as_bytes
+        )
+        self.options = options
+
+    @classmethod
+    def __empty(cls) -> MetadataPdu:
+        cls(
+            closure_requested=None,
+            checksum_type=None,
+            file_size=None,
+            source_file_name="",
+            dest_file_name="",
+            direction=None,
+            trans_mode=None
+        )
+
+    def pack(self):
+        if not self.pdu_file_directive.verify_file_len(self.file_size):
+            raise ValueError
+        packet = self.pdu_file_directive.pack()
+        current_idx = self.pdu_file_directive.get_len()
+        packet.append((self.closure_requested << 6) | self.checksum_type)
+        if self.pdu_file_directive.pdu_header.large_file:
+            packet.extend(struct.pack('!Q', self.file_size))
+        else:
+            packet.extend(struct.pack('!I', self.file_size))
+        packet.extend(self.source_file_name_lv.pack())
+        packet.extend(self.dest_file_name_lv.pack())
+        for option in self.options:
+            packet.extend(option.pack())
+
+    @classmethod
+    def unpack(cls, raw_packet: bytearray) -> MetadataPdu:
+        metadata_pdu = cls.__empty()
+        metadata_pdu.pdu_file_directive = FileDirectivePduBase.unpack(raw_packet=raw_packet)
+        current_idx = metadata_pdu.pdu_file_directive.get_len()
+        # Minimal length: 1 byte + FSS (4 byte) + 2 empty LV (1 byte)
+        if not check_packet_length(len(raw_packet), metadata_pdu.pdu_file_directive.get_len() + 7):
+            raise ValueError
+        metadata_pdu.closure_requested = raw_packet[current_idx] & 0x40
+        metadata_pdu.checksum_type = raw_packet[current_idx] & 0x0f
+        current_idx += 1
+        current_idx, metadata_pdu.file_size = metadata_pdu.pdu_file_directive.parse_fss_field(
+            raw_packet=raw_packet, current_idx=current_idx
+        )
+        metadata_pdu.source_file_name_lv.unpack(raw_bytes=raw_packet[current_idx:])
+        current_idx += metadata_pdu.source_file_name_lv.get_len()
+        metadata_pdu.dest_file_name_lv.unpack(raw_bytes=raw_packet[current_idx:])
+        current_idx += metadata_pdu.dest_file_name_lv.get_len()
+        if current_idx < len(raw_packet):
+            metadata_pdu.parse_options(raw_packet=raw_packet, start_idx=current_idx)
+        return metadata_pdu
+
+    def parse_options(self, raw_packet: bytearray, start_idx: int):
+        self.options = []
+        current_idx = start_idx
+        while True:
+            current_tlv = CfdpTlv(serialize=False)
+            current_tlv.unpack(raw_bytes=raw_packet[current_idx])
+            self.options.append(current_tlv)
+            # This will always increment at least two, so we can't get stuck in the loop
+            current_idx += current_tlv.get_total_length()
+            if current_idx > len(raw_packet):
+                LOGGER.warning(
+                    'Parser Error when parsing TLVs in Finished PDU. '
+                    'Possibly invalid packet'
+                )
+                break
+            elif current_idx == len(raw_packet):
+                break
diff --git a/src/tmtccmd/cfdp/pdu/nak.py b/src/tmtccmd/cfdp/pdu/nak.py
new file mode 100644
index 00000000..5037ceda
--- /dev/null
+++ b/src/tmtccmd/cfdp/pdu/nak.py
@@ -0,0 +1,67 @@
+from __future__ import annotations
+import enum
+import struct
+from typing import List, Tuple
+
+from tmtccmd.cfdp.pdu.file_directive import FileDirectivePduBase, DirectiveCodes, \
+    ConditionCode
+from tmtccmd.cfdp.pdu.header import Direction, TransmissionModes, CrcFlag
+from tmtccmd.cfdp.conf import LenInBytes
+
+
+class NakPdu():
+    def __init__(
+        self,
+        direction: Direction,
+        trans_mode: TransmissionModes,
+        start_of_scope: int,
+        end_of_scope: int,
+        segment_requests: List[Tuple[int, int]],
+        crc_flag: CrcFlag = CrcFlag.GLOBAL_CONFIG,
+        len_entity_id: LenInBytes = LenInBytes.NONE,
+        len_transaction_seq_num=LenInBytes.NONE,
+    ):
+        self.pdu_file_directive = FileDirectivePduBase(
+            directive_code=DirectiveCodes.ACK_PDU,
+            direction=direction,
+            trans_mode=trans_mode,
+            crc_flag=crc_flag,
+            len_entity_id=len_entity_id,
+            len_transaction_seq_num=len_transaction_seq_num
+        )
+        self.start_of_scope = start_of_scope
+        self.end_of_scope = end_of_scope
+        self.segment_requests = segment_requests
+
+    @classmethod
+    def __empty(cls) -> NakPdu:
+        return cls(
+            direction=None,
+            trans_mode=None,
+            start_of_scope=None,
+            end_of_scope=None,
+            segment_requests=None
+        )
+
+    def pack(self) -> bytearray:
+        nak_pdu = self.pdu_file_directive.pack()
+        if not self.pdu_file_directive.pdu_header.large_file:
+            nak_pdu.extend(struct.pack('!I', self.start_of_scope))
+            nak_pdu.extend(struct.pack('!I', self.end_of_scope))
+        else:
+            nak_pdu.extend(struct.pack('!Q', self.start_of_scope))
+            nak_pdu.extend(struct.pack('!Q', self.end_of_scope))
+        for segment_request in self.segment_requests:
+            if not self.pdu_file_directive.pdu_header.large_file:
+                nak_pdu.extend(struct.pack('!I', segment_request[0]))
+                nak_pdu.extend(struct.pack('!I', segment_request[1]))
+            else:
+                nak_pdu.extend(struct.pack('!Q', segment_request[0]))
+                nak_pdu.extend(struct.pack('!Q', segment_request[1]))
+
+    @classmethod
+    def unpack(cls, raw_packet: bytearray) -> NakPdu:
+        nak_pdu = cls.__empty()
+        nak_pdu.pdu_file_directive = FileDirectivePduBase.unpack(raw_packet=raw_packet)
+        current_idx = nak_pdu.pdu_file_directive.get_len()
+        # TODO: Implement
diff --git a/src/tmtccmd/cfdp/tlv.py b/src/tmtccmd/cfdp/tlv.py
new file mode 100644
index 00000000..501978c8
--- /dev/null
+++ b/src/tmtccmd/cfdp/tlv.py
@@ -0,0 +1,78 @@
+from __future__ import annotations
+import enum
+from tmtccmd.ccsds.log import LOGGER
+
+
+class TlvTypes(enum.IntEnum):
+    FILESTORE_REQUEST = 0x00
+    FILESTORE_RESPONSE = 0x01
+    MESSAGE_TO_USER = 0x02
+    FAULT_HANDLER = 0x04
+    FLOW_LABEL = 0x05
+    ENTITY_ID = 0x06
+
+
+class CfdpTlv:
+    MINIMAL_LEN = 2
+
+    """Encapsulates the CFDP TLV (type-lenght-value) format.
+    For more information, refer to CCSDS 727.0-B-5 p.77
+    """
+    def __init__(
+            self,
+            type: TlvTypes,
+            length: int,
+            value: bytearray
+    ):
+        """Constructor for TLV field.
+        :param serialize: Specfiy whether a packet is serialized or deserialized. For serialize,
+        all input parameter have to be valid. For deserialization, the parameter do not have
+        to be specified
+        :param type:
+        :param length:
+        :param value:
+        :raise ValueError: Length invalid or value length not equal to specified length
+        """
+        if length > 255 or length < 0:
+            raise ValueError
+        if len(value) != length:
+            raise ValueError
+        self.type = type
+        self.length = length
+        self.value = value
+
+    def pack(self) -> bytearray:
+        tlv_data = bytearray()
+        tlv_data.append(self.type)
+        tlv_data.append(self.length)
+        tlv_data.extend(self.value)
+        return tlv_data
+
+    @classmethod
+    def unpack(cls, raw_bytes: bytearray) -> CfdpTlv:
+        """Parses LV field at the start of the given bytearray
+        :param raw_bytes:
+        :raise ValueError: Invalid format of the raw bytearray or type field invalid
+        :return:
+        """
+        if len(raw_bytes) < 2:
+            LOGGER.warning('Invalid length for TLV field, less than 2')
+            raise ValueError
+        try:
+            type = TlvTypes(raw_bytes[0])
+        except ValueError:
+            LOGGER.warning(
+                f'TLV field invalid, found value {type} is not a possible TLV parameter'
+            )
+            raise ValueError
+        value = bytearray()
+        if len(raw_bytes) > 2:
+            value.extend(raw_bytes[2:])
+        return cls(
+            type=raw_bytes[0],
+            length=raw_bytes[1],
+            value=value
+        )
+
+    def get_total_length(self) -> int:
+        return self.MINIMAL_LEN + len(self.value)
diff --git a/src/tmtccmd/com_if/dummy_com_if.py b/src/tmtccmd/com_if/dummy_com_if.py
index 5411f59e..b8ec920b 100644
--- a/src/tmtccmd/com_if/dummy_com_if.py
+++ b/src/tmtccmd/com_if/dummy_com_if.py
@@ -1,15 +1,8 @@
+"""Dummy Communication Interface. Currently serves to provide an example without external hardware
 """
-:file:      dummy_com_if.py
-:date:      09.03.2020
-:brief:     Dummy Communication Interface. Currently serves to provide an example without external hardware
-"""
-from typing import cast
-
 from tmtccmd.com_if.com_interface_base import CommunicationInterface
 from tmtccmd.ecss.tc import PusTelecommand
-from tmtccmd.tm.definitions import TelemetryListT
-from tmtccmd.tm.service_1_verification import Service1TmPacked
-from tmtccmd.tm.service_17_test import Service17TmPacked
+from tmtccmd.tm import TelemetryListT, Service1TM, Service17TM
 from tmtccmd.pus.service_17_test import Srv17Subservices
 from tmtccmd.utility.logger import get_console_logger
 from tmtccmd.utility.tmtc_printer import TmTcPrinter
@@ -77,7 +70,7 @@ def generate_reply_package(self):
         """
         if self.last_service == 17:
             if self.last_subservice == 1:
-                tm_packer = Service1TmPacked(
+                tm_packer = Service1TM(
                     subservice=1, ssc=self.current_ssc, tc_packet_id=self.last_tc_packet_id,
                     tc_ssc=self.last_tc_ssc
                 )
@@ -85,7 +78,7 @@ def generate_reply_package(self):
                 self.current_ssc += 1
                 tm_packet_raw = tm_packer.pack()
                 self.next_telemetry_package.append(tm_packet_raw)
-                tm_packer = Service1TmPacked(
+                tm_packer = Service1TM(
                     subservice=7, ssc=self.current_ssc, tc_packet_id=self.last_tc_packet_id,
                     tc_ssc=self.last_tc_ssc
                 )
@@ -93,7 +86,7 @@ def generate_reply_package(self):
                 self.next_telemetry_package.append(tm_packet_raw)
                 self.current_ssc += 1
 
-                tm_packer = Service17TmPacked(subservice=Srv17Subservices.PING_REPLY)
+                tm_packer = Service17TM(subservice=Srv17Subservices.PING_REPLY)
                 tm_packet_raw = tm_packer.pack()
                 self.next_telemetry_package.append(tm_packet_raw)
                 self.current_ssc += 1
diff --git a/src/tmtccmd/com_if/qemu_com_if.py b/src/tmtccmd/com_if/qemu_com_if.py
index de19f2c3..a50d1821 100644
--- a/src/tmtccmd/com_if/qemu_com_if.py
+++ b/src/tmtccmd/com_if/qemu_com_if.py
@@ -32,7 +32,7 @@
 from tmtccmd.utility.tmtc_printer import TmTcPrinter
 from tmtccmd.com_if.serial_com_if import SerialComIF, SerialCommunicationType
 from tmtccmd.utility.logger import get_console_logger
-from tmtccmd.utility.dle_encoder import encode_dle, decode_dle, STX_CHAR, ETX_CHAR, DleErrorCodes
+from dle_encoder import DleEncoder, STX_CHAR, ETX_CHAR, DleErrorCodes
 
 LOGGER = get_console_logger()
 SERIAL_FRAME_LENGTH = 256
@@ -74,12 +74,13 @@ def __init__(self, tmtc_printer: TmTcPrinter, serial_timeout: float,
         self.data = []
         self.background_loop_thread = None
         self.usart = None
-
+        self.encoder = None
         self.ser_com_type = ser_com_type
         if self.ser_com_type == SerialCommunicationType.FIXED_FRAME_BASED:
             # Set to default value.
             self.serial_frame_size = 256
         elif self.ser_com_type == SerialCommunicationType.DLE_ENCODING:
+            self.encoder = DleEncoder()
             self.reception_buffer = None
             # Set to default value.
             self.dle_queue_len = 10
@@ -137,7 +138,7 @@ async def send_data_async(self, data):
 
     def send(self, data: bytearray):
         if self.ser_com_type == SerialCommunicationType.DLE_ENCODING:
-            data_encoded = encode_dle(data)
+            data_encoded = self.encoder.encode(data)
         else:
             data_encoded = data
         self.send_data(data_encoded)
@@ -158,7 +159,7 @@ def receive(self, parameters=0) -> TelemetryListT:
         elif self.ser_com_type == SerialCommunicationType.DLE_ENCODING:
             while self.reception_buffer:
                 data = self.reception_buffer.pop()
-                dle_retval, decoded_packet, read_len = decode_dle(data)
+                dle_retval, decoded_packet, read_len = self.encoder.decode(data)
                 if dle_retval == DleErrorCodes.OK:
                     packet_list.append(decoded_packet)
                 else:
@@ -169,35 +170,42 @@ def receive(self, parameters=0) -> TelemetryListT:
 
         return packet_list
 
-    def data_available(self, timeout: any = 0, parameters: any = 0) -> int:
-        elapsed_time = 0
-        start_time = time.time()
-        sleep_time = timeout / 3.0
-
+    def data_available(self, timeout: float = 0, parameters: any = 0) -> int:
         if self.ser_com_type == SerialCommunicationType.FIXED_FRAME_BASED:
-            if timeout > 0:
-                while elapsed_time < timeout:
-                    if self.usart.new_data_available():
-                        return self.usart.get_data_in_waiting()
-
-                    time.sleep(sleep_time)
-                    elapsed_time = time.time() - start_time
-
-            if self.usart.new_data_available():
-                return self.usart.get_data_in_waiting()
-
+            return self.data_available_fixed_frame(timeout=timeout)
         elif self.ser_com_type == SerialCommunicationType.DLE_ENCODING:
-            if timeout > 0:
-                while elapsed_time < timeout:
-                    if self.reception_buffer:
-                        return len(self.reception_buffer)
-
-                    time.sleep(sleep_time)
-                    elapsed_time = time.time() - start_time
+            return self.data_available_dle(timeout=timeout)
+        return 0
 
-            if self.reception_buffer:
-                return len(self.reception_buffer)
+    def data_available_fixed_frame(self, timeout: float = 0) -> int:
+        elapsed_time = 0
+        start_time = time.time()
+        sleep_time = timeout / 3.0
+        if timeout > 0:
+            while elapsed_time < timeout:
+                if self.usart.new_data_available():
+                    return self.usart.get_data_in_waiting()
+
+                time.sleep(sleep_time)
+                elapsed_time = time.time() - start_time
+            return 0
+        if self.usart.new_data_available():
+            return self.usart.get_data_in_waiting()
+        return 0
 
+    def data_availanle_dle(self, timeout: float = 0) -> int:
+        elapsed_time = 0
+        start_time = time.time()
+        sleep_time = timeout / 3.0
+        if timeout > 0:
+            while elapsed_time < timeout:
+                if self.reception_buffer:
+                    return len(self.reception_buffer)
+                time.sleep(sleep_time)
+                elapsed_time = time.time() - start_time
+            return 0
+        if self.reception_buffer:
+            return len(self.reception_buffer)
         return 0
 
     async def start_dle_polling(self):
diff --git a/src/tmtccmd/com_if/serial_com_if.py b/src/tmtccmd/com_if/serial_com_if.py
index 91b6591c..6381be23 100644
--- a/src/tmtccmd/com_if/serial_com_if.py
+++ b/src/tmtccmd/com_if/serial_com_if.py
@@ -1,8 +1,4 @@
-"""
-@file   tmtcc_serial_com_if.py
-@brief  SERIAL Communication Interface
-@author R. Mueller
-@date   01.11.2019
+"""Serial Communication Interface Implementation
 """
 import enum
 import threading
@@ -17,7 +13,7 @@
 from tmtccmd.utility.tmtc_printer import TmTcPrinter
 from tmtccmd.tm.definitions import TelemetryListT
 from tmtccmd.utility.logger import get_console_logger
-from tmtccmd.utility.dle_encoder import encode_dle, decode_dle, STX_CHAR, ETX_CHAR, DleErrorCodes
+from dle_encoder import DleEncoder, STX_CHAR, ETX_CHAR, DleErrorCodes
 
 
 LOGGER = get_console_logger()
@@ -54,7 +50,8 @@ class SerialComIF(CommunicationInterface):
     Communication Interface to use serial communication. This requires the PySerial library.
     """
     def __init__(
-            self, com_if_key: str, tmtc_printer: TmTcPrinter, com_port: str, baud_rate: int, serial_timeout: float,
+            self, com_if_key: str, tmtc_printer: TmTcPrinter, com_port: str, baud_rate: int,
+            serial_timeout: float,
             ser_com_type: SerialCommunicationType = SerialCommunicationType.FIXED_FRAME_BASED
     ):
         """
@@ -72,12 +69,13 @@ def __init__(
         self.baud_rate = baud_rate
         self.serial_timeout = serial_timeout
         self.serial = None
-
+        self.encoder = None
         self.ser_com_type = ser_com_type
         if self.ser_com_type == SerialCommunicationType.FIXED_FRAME_BASED:
             # Set to default value.
             self.serial_frame_size = 256
         elif self.ser_com_type == SerialCommunicationType.DLE_ENCODING:
+            self.encoder = DleEncoder()
             self.reception_thread = None
             self.reception_buffer = None
             self.dle_polling_active_event = None
@@ -85,6 +83,7 @@ def __init__(
             self.dle_queue_len = 10
             self.dle_max_frame = 256
             self.dle_timeout = 0.01
+            self.dle_encode_cr = True
 
     def __del__(self):
         if self.serial is not None:
@@ -93,10 +92,13 @@ def __del__(self):
     def set_fixed_frame_settings(self, serial_frame_size: int):
         self.serial_frame_size = serial_frame_size
 
-    def set_dle_settings(self, dle_queue_len: int, dle_max_frame: int, dle_timeout: float):
+    def set_dle_settings(
+            self, dle_queue_len: int, dle_max_frame: int, dle_timeout: float, encode_cr: bool = True
+    ):
         self.dle_queue_len = dle_queue_len
         self.dle_max_frame = dle_max_frame
         self.dle_timeout = dle_timeout
+        self.dle_encode_cr = encode_cr
 
     def initialize(self, args: any = None) -> any:
         if self.ser_com_type == SerialCommunicationType.DLE_ENCODING:
@@ -132,7 +134,9 @@ def send(self, data: bytearray):
         if self.ser_com_type == SerialCommunicationType.FIXED_FRAME_BASED:
             encoded_data = data
         elif self.ser_com_type == SerialCommunicationType.DLE_ENCODING:
-            encoded_data = encode_dle(data)
+            encoded_data = self.encoder.encode(
+                source_packet=data, add_stx_etx=True
+            )
         else:
             LOGGER.warning("This communication type was not implemented yet!")
             return
@@ -149,7 +153,9 @@ def receive(self, parameters: any = 0) -> TelemetryListT:
         elif self.ser_com_type == SerialCommunicationType.DLE_ENCODING:
             while self.reception_buffer:
                 data = self.reception_buffer.pop()
-                dle_retval, decoded_packet, read_len = decode_dle(data)
+                dle_retval, decoded_packet, read_len = self.encoder.decode(
+                    source_packet=data
+                )
                 if dle_retval == DleErrorCodes.OK:
                     packet_list.append(decoded_packet)
                 else:
@@ -163,16 +169,7 @@ def data_available(self, timeout: float = 0, parameters: any = 0) -> int:
         start_time = time.time()
         sleep_time = timeout / 3.0
         if self.ser_com_type == SerialCommunicationType.FIXED_FRAME_BASED:
-            if timeout > 0:
-                start_time = time.time()
-                elapsed_time = 0
-                while elapsed_time < timeout:
-                    if self.serial.inWaiting() > 0:
-                        return self.serial.inWaiting()
-                    elapsed_time = time.time() - start_time
-                    time.sleep(sleep_time)
-            if self.serial.inWaiting() > 0:
-                return self.serial.inWaiting()
+            return self.data_available_fixed_frame(timeout=timeout, sleep_time=sleep_time)
         elif self.ser_com_type == SerialCommunicationType.DLE_ENCODING:
             if timeout > 0:
                 while elapsed_time < timeout:
@@ -184,6 +181,18 @@ def data_available(self, timeout: float = 0, parameters: any = 0) -> int:
                 return self.reception_buffer.__len__()
         return 0
 
+    def data_available_fixed_frame(self, timeout: float, sleep_time: float):
+        if timeout > 0:
+            start_time = time.time()
+            elapsed_time = 0
+            while elapsed_time < timeout:
+                if self.serial.inWaiting() > 0:
+                    return self.serial.inWaiting()
+                elapsed_time = time.time() - start_time
+                time.sleep(sleep_time)
+        if self.serial.inWaiting() > 0:
+            return self.serial.inWaiting()
+
     def poll_dle_packets(self):
         while True and self.dle_polling_active_event.is_set():
             # Poll permanently, but it is possible to join this thread every 200 ms
@@ -201,7 +210,7 @@ def poll_dle_packets(self):
                 data.append(byte[0])
                 data.extend(self.serial.read(self.serial.inWaiting()))
                 # It is assumed that all packets are DLE encoded, so throw it away for now.
-                LOGGER.info("Non DLE-Encoded data with length " + str(len(data) + 1) + " found..")
+                LOGGER.info(f'Non DLE-Encoded data with length {len(data) + 1} found..')
 
     @staticmethod
     def poll_pus_packets_fixed_frames(data: bytearray) -> list:
@@ -238,8 +247,9 @@ def parse_next_packets(data: bytearray, start_index: int, frame_size: int,
         # remaining_size = frame_size - start_index
 
         if next_packet_size > SERIAL_FRAME_LENGTH:
-            LOGGER.error("PUS Polling: Very large packet detected, "
-                         "packet splitting not implemented yet!")
+            LOGGER.error(
+                "PUS Polling: Very large packet detected, packet splitting not implemented yet!"
+            )
             LOGGER.error("Detected Size: " + str(next_packet_size))
             end_index = frame_size
             return end_index
diff --git a/src/tmtccmd/com_if/serial_utilities.py b/src/tmtccmd/com_if/serial_utilities.py
index f9051720..d674c0f1 100644
--- a/src/tmtccmd/com_if/serial_utilities.py
+++ b/src/tmtccmd/com_if/serial_utilities.py
@@ -1,16 +1,16 @@
 import json
+from typing import TextIO
 
 import serial
 import serial.tools.list_ports
 from tmtccmd.utility.logger import get_console_logger
-from tmtccmd.utility.json_handler import check_json_file, JsonKeyNames
+from tmtccmd.utility.json_handler import check_json_file, JsonKeyNames, save_to_json_with_prompt
 
 LOGGER = get_console_logger()
 
 
 def determine_baud_rate(json_cfg_path: str) -> int:
-    """
-    Determine baud rate. Tries to read from JSON first. If the baud rate is not contained
+    """Determine baud rate. Tries to read from JSON first. If the baud rate is not contained
     in the config JSON, prompt it from user instead with the option to store value in JSON file.
     :return: Determined baud rate
     """
@@ -29,77 +29,164 @@ def determine_baud_rate(json_cfg_path: str) -> int:
 
     if prompt_baud_rate:
         while True:
-            baud_rate = input("Please enter the baudrate for the serial protocol: ")
+            baud_rate = input('Please enter the baudrate for the serial protocol: ')
             if baud_rate.isdigit():
                 baud_rate = int(baud_rate)
                 break
             else:
-                print("Invalid baud rate specified, try again.")
-        save_to_json = input("Do you want to store baud rate to the configuration file? (y/n): ")
-        if save_to_json.lower() in ['y', "yes", "1"]:
-            with open(json_cfg_path, "r+") as file:
-                data = json.load(file)
-                data[JsonKeyNames.SERIAL_BAUDRATE.value] = baud_rate
-                file.seek(0)
-                json.dump(data, file, indent=4)
-            LOGGER.info("Baud rate was stored to the JSON file config/tmtcc_config.json")
-            LOGGER.info("Delete this file or edit it manually to change the baud rate")
+                print('Invalid baud rate specified, try again.')
+        with open(json_cfg_path, 'r+') as json_file:
+            json_obj = json.load(fp=json_file)
+            if save_to_json_with_prompt(
+                key=JsonKeyNames.SERIAL_BAUDRATE.value, value=baud_rate,
+                json_cfg_path=json_cfg_path, json_obj=json_obj, name='baudrate'
+            ):
+                json_file.seek(0)
+                json.dump(json_obj, json_file, indent=4)
     return baud_rate
 
 
 def determine_com_port(json_cfg_path: str) -> str:
-    """
-    Determine serial port. Tries to read from JSON first. If the com port is not contained
+    """Determine serial port. Tries to read from JSON first. If the com port is not contained
     in the config JSON, prompt it from user instead with the option to store value in JSON file.
     :return: Determined serial port
     """
-    reconfigure_com_port = False
-    com_port = ""
+    reconfig_com_port = False
     if not check_json_file(json_cfg_path=json_cfg_path):
-        reconfigure_com_port = True
-
-    with open(json_cfg_path, "r") as read:
-        try:
-            load_data = json.load(read)
-            com_port = load_data[JsonKeyNames.SERIAL_PORT.value]
-        except KeyError:
-            reconfigure_com_port = True
-        if not check_port_validity(com_port):
-            reconfigure = input(
-                "COM port from configuration file not contained within serial"
-                "port list. Reconfigure serial port? [y/n]: ")
-            if reconfigure.lower() in ['y', "yes", "1"]:
-                reconfigure_com_port = True
+        reconfig_com_port = True
+    with open(json_cfg_path, 'r+') as json_file:
+        com_port = __det_com_port_with_json_file(
+            json_cfg_path=json_cfg_path, json_file=json_file, reconfig_com_port=reconfig_com_port
+        )
+    return com_port
+
+
+def __det_com_port_with_json_file(
+        json_cfg_path: str, json_file: TextIO, reconfig_com_port: bool
+) -> str:
+    try_hint = False
+    json_obj = json.load(json_file)
+    com_port = ''
+    if not reconfig_com_port:
+        reconfig_com_port, try_hint, com_port = __try_com_port_load(json_obj=json_obj)
+    if try_hint:
+        reconfig_com_port, com_port = __try_hint_handling(
+            json_cfg_path=json_cfg_path, reconfig_com_port=reconfig_com_port, json_obj=json_obj
+        )
 
-    if reconfigure_com_port:
+    if reconfig_com_port:
         com_port = prompt_com_port()
-        save_to_json = input("Do you want to store serial port to the"
-                             " configuration file? (y/n): ")
-        if save_to_json.lower() in ['y', "yes"]:
-            with open(json_cfg_path, "r+") as file:
-                data = json.load(file)
-                data[JsonKeyNames.SERIAL_PORT.value] = com_port
-                file.seek(0)
-                json.dump(data, file, indent=4)
-            LOGGER.info("Serial port was stored to the JSON file config/tmtcc_config.json")
-            LOGGER.info("Delete this file or edit it manually to change serial port")
+        save_to_json_with_prompt(
+            key=JsonKeyNames.SERIAL_PORT.value, value=com_port, name='serial port',
+            json_cfg_path=json_cfg_path, json_obj=json_obj
+        )
+    json_file.seek(0)
+    json.dump(json_obj, json_file, indent=4)
     return com_port
 
 
+def __try_com_port_load(json_obj) -> (bool, bool, str):
+    reconfig_com_port = False
+    try_hint = False
+    com_port = ''
+    try:
+        com_port = json_obj[JsonKeyNames.SERIAL_PORT.value]
+        LOGGER.info(f'Loaded COM port {com_port} from JSON configuration file')
+        if not check_port_validity(com_port):
+            while True:
+                reconfigure = input(
+                    'COM port from configuration file not contained within serial'
+                    'port list. Reconfigure serial port or try to determine from hint? '
+                    '[r (reconfigure) / h (hint) / c(cancel)]: '
+                )
+                if reconfigure.lower() in ['r']:
+                    reconfig_com_port = True
+                elif reconfigure.lower() in ['h']:
+                    try_hint = True
+                elif reconfigure.lower() in ['c']:
+                    return com_port
+                else:
+                    continue
+    except KeyError:
+        try_hint = True
+    return reconfig_com_port, try_hint, com_port
+
+
+def __try_hint_handling(json_cfg_path: str, reconfig_com_port: bool, json_obj) -> (bool, str):
+    reconfig_hint = False
+    try:
+        hint = json_obj[JsonKeyNames.SERIAL_HINT.value]
+    except KeyError:
+        reconfig_hint, hint = __prompt_hint_handling(json_obj=json_obj)
+
+    com_port_found, com_port = find_com_port_from_hint(hint=hint)
+    if com_port_found:
+        LOGGER.info(f'Found {com_port} based on hint {hint}')
+        if reconfig_hint:
+            if save_to_json_with_prompt(
+                    key=JsonKeyNames.SERIAL_PORT.value, value=com_port, name='serial port',
+                    json_cfg_path=json_cfg_path, json_obj=json_obj
+            ):
+                reconfig_com_port = False
+    else:
+        LOGGER.info('No COM port found based on hint..')
+        reconfig_com_port = True
+    return reconfig_com_port, com_port
+
+
+def __prompt_hint_handling(json_obj) -> (bool, str):
+    reconfig_hint = False
+    hint = ''
+    ports = serial.tools.list_ports.comports()
+    prompt_hint = input(
+        'No hint found in config JSON. Do you want to print the list of devices '
+        'and then specify a hint based on it? [y/n]: '
+    )
+    if prompt_hint.lower() in ['y', 'yes', '1']:
+        LOGGER.info('Found serial devices:')
+        for port, desc, hwid in sorted(ports):
+            print('{}: {} [{}]'.format(port, desc, hwid))
+        while True:
+            hint = input('Specify hint: ')
+            save_to_json = input(
+                'Do you want to store the hint to the configuration file or '
+                'specify a new one? (y/r): '
+            )
+            if save_to_json in ['y', 'yes', '1']:
+                json_obj[JsonKeyNames.SERIAL_HINT.value] = hint
+                reconfig_hint = True
+                break
+            elif save_to_json in ['r']:
+                continue
+    return reconfig_hint, hint
+
+
+def find_com_port_from_hint(hint: str) -> (bool, str):
+    """Find a COM port based on a hint string"""
+    if hint == '':
+        LOGGER.warning('Invalid hint, is empty..')
+        return False, ''
+    ports = serial.tools.list_ports.comports()
+    for port, desc, hwid in sorted(ports):
+        if hint in desc:
+            return True, port
+    return False, ''
+
+
 def prompt_com_port() -> str:
     while True:
         com_port = input(
-            "Configuring serial port. Please enter COM Port"
-            "(enter h to display list of COM ports): ")
+            'Configuring serial port. Please enter COM Port'
+            '(enter h to display list of COM ports): ')
         if com_port == 'h':
             ports = serial.tools.list_ports.comports()
             for port, desc, hwid in sorted(ports):
-                print("{}: {} [{}]".format(port, desc, hwid))
+                print('{}: {} [{}]'.format(port, desc, hwid))
         else:
             if not check_port_validity(com_port):
-                print("Serial port not in list of available serial ports. Try again? [y/n]")
+                print('Serial port not in list of available serial ports. Try again? [y/n]')
                 try_again = input()
-                if try_again.lower() in ['y', "yes"]:
+                if try_again.lower() in ['y', 'yes']:
                     continue
                 else:
                     break
diff --git a/src/tmtccmd/com_if/tcpip_tcp_com_if.py b/src/tmtccmd/com_if/tcpip_tcp_com_if.py
index 9f4edc46..07097891 100644
--- a/src/tmtccmd/com_if/tcpip_tcp_com_if.py
+++ b/src/tmtccmd/com_if/tcpip_tcp_com_if.py
@@ -8,6 +8,7 @@
 import time
 import enum
 import threading
+import select
 from collections import deque
 from typing import Union
 
@@ -36,7 +37,9 @@ class TcpCommunicationType(enum.Enum):
 # pylint: disable=too-many-arguments
 class TcpIpTcpComIF(CommunicationInterface):
     """Communication interface for TCP communication."""
-    DEFAULT_LOCK_TIMEOUT = 50
+    DEFAULT_LOCK_TIMEOUT = 0.4
+    RECV_TIMEOUT = 0.2
+
     def __init__(
             self, com_if_key: str, com_type: TcpCommunicationType, space_packet_id: int,
             tm_polling_freqency: float, tm_timeout: float, tc_timeout_factor: float,
@@ -127,8 +130,6 @@ def receive(self, poll_timeout: float = 0) -> TelemetryListT:
                 tm_packet_list.append(self.__analysis_queue.pop())
         return tm_packet_list
 
-
-
     def __tcp_tm_client(self):
         while True and not self.__tm_thread_kill_signal.is_set():
             if time.time() - self.__last_connection_time >= self.tm_polling_frequency:
@@ -136,7 +137,7 @@ def __tcp_tm_client(self):
                     with acquire_timeout(self.__socket_lock, timeout=self.DEFAULT_LOCK_TIMEOUT) as \
                             acquired:
                         if not acquired:
-                            LOGGER.warning("Acquiring socket lock failed!")
+                            LOGGER.warning("Acquiring socket lock in periodic handler failed!")
                         tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                         tcp_socket.connect(self.send_address)
                         tcp_socket.shutdown(socket.SHUT_WR)
@@ -149,20 +150,30 @@ def __tcp_tm_client(self):
 
     def __receive_tm_packets(self, tcp_socket: socket.socket):
         while True:
-            bytes_recvd = tcp_socket.recv(self.max_recv_size)
-            if len(bytes_recvd) > 0:
-                with acquire_timeout(self.__queue_lock, timeout=self.DEFAULT_LOCK_TIMEOUT) as \
-                        acquired:
-                    if not acquired:
-                        LOGGER.warning("Acquiring queue lock failed!")
-                    if self.__tm_queue.__len__() >= self.max_packets_stored:
-                        LOGGER.warning(
-                            "Number of packets in TCP queue too large. Overwriting old packets.."
-                        )
-                        self.__tm_queue.pop()
-                    self.__tm_queue.appendleft(bytearray(bytes_recvd))
-            elif bytes_recvd is None or len(bytes_recvd) == 0:
-                break
+            try:
+                ready = select.select([tcp_socket], [], [], self.RECV_TIMEOUT)
+                if ready[0]:
+                    bytes_recvd = tcp_socket.recv(self.max_recv_size)
+                else:
+                    LOGGER.warning(
+                        'TCP reception timeout. Server side might not shut down connection!'
+                    )
+                    break
+                if len(bytes_recvd) > 0:
+                    with acquire_timeout(self.__queue_lock, timeout=self.DEFAULT_LOCK_TIMEOUT) as \
+                            acquired:
+                        if not acquired:
+                            LOGGER.warning("Acquiring queue lock failed!")
+                        if self.__tm_queue.__len__() >= self.max_packets_stored:
+                            LOGGER.warning(
+                                "Number of packets in TCP queue too large. Overwriting old packets.."
+                            )
+                            self.__tm_queue.pop()
+                        self.__tm_queue.appendleft(bytearray(bytes_recvd))
+                elif bytes_recvd is None or len(bytes_recvd) == 0:
+                    break
+            except ConnectionResetError:
+                LOGGER.exception('ConnectionResetError. TCP server might not be up')
 
     def data_available(self, timeout: float = 0, parameters: any = 0) -> bool:
         if self.__tm_queue:
diff --git a/src/tmtccmd/com_if/tcpip_utilities.py b/src/tmtccmd/com_if/tcpip_utilities.py
index 3435eea2..e45b3b1f 100644
--- a/src/tmtccmd/com_if/tcpip_utilities.py
+++ b/src/tmtccmd/com_if/tcpip_utilities.py
@@ -13,6 +13,7 @@
 
 DEFAULT_MAX_RECV_SIZE = 1500
 
+
 class TcpIpType(enum.Enum):
     TCP = enum.auto()
     UDP = enum.auto()
@@ -163,7 +164,7 @@ def prompt_recv_buffer_len(tcpip_type: TcpIpType) -> int:
         type_str = "TCP"
     while True:
         recv_max_size = input(
-            f'Please enter maximum receive size for {type_str} packets [1500 default]:'
+            f'Please enter maximum receive size for {type_str} packets [1500 default]: '
         )
         if not recv_max_size:
             return DEFAULT_MAX_RECV_SIZE
diff --git a/src/tmtccmd/config/args.py b/src/tmtccmd/config/args.py
index 47e2f00a..c809a8b1 100644
--- a/src/tmtccmd/config/args.py
+++ b/src/tmtccmd/config/args.py
@@ -190,12 +190,15 @@ def handle_unspecified_args(args) -> None:
         hook_obj = get_global_hook_obj()
         service_op_code_dict = hook_obj.get_service_op_code_dictionary()
     if args.service is None:
-        LOGGER.info("No service argument (-s) specified, prompting from user..")
-        # Try to get the service list from the hook base and prompt service from user
-        args.service = prompt_service(service_op_code_dict)
-        if args.op_code is None:
-            current_service = args.service
-            args.op_code = prompt_op_code(service_op_code_dict=service_op_code_dict, service=current_service)
+        if args.mode == CoreModeList.SEQUENTIAL_CMD_MODE:
+            LOGGER.info("No service argument (-s) specified, prompting from user..")
+            # Try to get the service list from the hook base and prompt service from user
+            args.service = prompt_service(service_op_code_dict)
+            if args.op_code is None:
+                current_service = args.service
+                args.op_code = prompt_op_code(
+                    service_op_code_dict=service_op_code_dict, service=current_service
+                )
     elif args.op_code is None:
         current_service = args.service
         args.op_code = prompt_op_code(service_op_code_dict=service_op_code_dict, service=current_service)
diff --git a/src/tmtccmd/config/com_if.py b/src/tmtccmd/config/com_if.py
index 8aa817c5..0b90784e 100644
--- a/src/tmtccmd/config/com_if.py
+++ b/src/tmtccmd/config/com_if.py
@@ -73,7 +73,7 @@ def default_tcpip_cfg_setup(tcpip_type: TcpIpType, json_cfg_path: str, space_pac
     :func:`create_default_tcpip_interface`
     :param tcpip_type:
     :param json_cfg_path:
-    :param packet_id:       Required if the TCP com interface needs to parse space packets
+    :param space_packet_id:       Required if the TCP com interface needs to parse space packets
     :return:
     """
     from tmtccmd.com_if.tcpip_utilities import determine_udp_send_address, \
@@ -84,6 +84,8 @@ def default_tcpip_cfg_setup(tcpip_type: TcpIpType, json_cfg_path: str, space_pac
         send_tuple = determine_udp_send_address(json_cfg_path=json_cfg_path)
     elif tcpip_type == TcpIpType.TCP:
         send_tuple = determine_tcp_send_address(json_cfg_path=json_cfg_path)
+    else:
+        send_tuple = ()
     max_recv_buf_size = determine_recv_buffer_len(json_cfg_path=json_cfg_path, tcpip_type=tcpip_type)
     ethernet_cfg_dict = get_global(CoreGlobalIds.ETHERNET_CONFIG)
     ethernet_cfg_dict.update({TcpIpConfigIds.SEND_ADDRESS: send_tuple})
@@ -105,10 +107,7 @@ def default_serial_cfg_setup(com_if_key: str, json_cfg_path: str):
     :return:
     """
     baud_rate = determine_baud_rate(json_cfg_path=json_cfg_path)
-    if com_if_key == CoreComInterfaces.SERIAL_DLE.value:
-        serial_port = determine_com_port(json_cfg_path=json_cfg_path)
-    else:
-        serial_port = ""
+    serial_port = determine_com_port(json_cfg_path=json_cfg_path)
     set_up_serial_cfg(
         json_cfg_path=json_cfg_path, com_if_key=com_if_key, baud_rate=baud_rate,
         com_port=serial_port
@@ -124,8 +123,10 @@ def create_default_tcpip_interface(
     :param com_if_key:
     :param tmtc_printer:
     :param json_cfg_path:
+    :param space_packet_id: Two byte packet ID which is used by TCP to parse space packets
     :return:
     """
+    communication_interface = None
     if com_if_key == CoreComInterfaces.TCPIP_UDP.value:
         default_tcpip_cfg_setup(
             tcpip_type=TcpIpType.UDP, json_cfg_path=json_cfg_path
@@ -153,8 +154,8 @@ def create_default_tcpip_interface(
             com_if_key=com_if_key, com_type=TcpCommunicationType.SPACE_PACKETS,
             space_packet_id=space_packet_id, tm_timeout=get_global(CoreGlobalIds.TM_TIMEOUT),
             tc_timeout_factor=get_global(CoreGlobalIds.TC_SEND_TIMEOUT_FACTOR),
-            tm_polling_freqency=0.5, send_address=send_addr, max_recv_size=max_recv_size, tmtc_printer=tmtc_printer,
-            init_mode=init_mode
+            tm_polling_freqency=0.5, send_address=send_addr, max_recv_size=max_recv_size,
+            tmtc_printer=tmtc_printer, init_mode=init_mode
         )
     return communication_interface
 
@@ -182,15 +183,14 @@ def create_default_serial_interface(
         serial_cfg = get_global(CoreGlobalIds.SERIAL_CONFIG)
         serial_baudrate = serial_cfg[SerialConfigIds.SERIAL_BAUD_RATE]
         serial_timeout = serial_cfg[SerialConfigIds.SERIAL_TIMEOUT]
-        # Determine COM port, either extract from JSON file or ask from user.
-        com_port = determine_com_port(json_cfg_path=json_cfg_path)
+        com_port = serial_cfg[SerialConfigIds.SERIAL_PORT]
         if com_if_key == CoreComInterfaces.SERIAL_DLE.value:
             ser_com_type = SerialCommunicationType.DLE_ENCODING
         else:
             ser_com_type = SerialCommunicationType.FIXED_FRAME_BASED
         communication_interface = SerialComIF(
-            com_if_key=com_if_key, tmtc_printer=tmtc_printer, com_port=com_port, baud_rate=serial_baudrate,
-            serial_timeout=serial_timeout, ser_com_type=ser_com_type
+            com_if_key=com_if_key, tmtc_printer=tmtc_printer, com_port=com_port,
+            baud_rate=serial_baudrate, serial_timeout=serial_timeout, ser_com_type=ser_com_type
         )
         if com_if_key == CoreComInterfaces.SERIAL_DLE:
             dle_max_queue_len = serial_cfg[SerialConfigIds.SERIAL_DLE_QUEUE_LEN]
@@ -205,13 +205,15 @@ def create_default_serial_interface(
 
 
 def set_up_serial_cfg(
-        json_cfg_path: str, com_if_key: str, baud_rate: int, com_port: str = "",  tm_timeout: float = 0.01,
+        json_cfg_path: str, com_if_key: str, baud_rate: int, com_port: str = "",
+        tm_timeout: float = 0.01,
         ser_com_type: SerialCommunicationType = SerialCommunicationType.DLE_ENCODING,
         ser_frame_size: int = 256, dle_queue_len: int = 25, dle_frame_size: int = 1024
 ):
     """Default configuration to set up serial communication. The serial port and the baud rate
-    will be determined from a JSON configuration file and prompted from the user. Sets up all global variables
-    so that a serial communication interface can be built with :func:`create_default_serial_interface`
+    will be determined from a JSON configuration file and prompted from the user. Sets up all
+    global variables so that a serial communication interface can be built with
+    :func:`create_default_serial_interface`
     :param json_cfg_path:
     :param com_if_key:
     :param com_port:
@@ -224,7 +226,8 @@ def set_up_serial_cfg(
     :return:
     """
     update_global(CoreGlobalIds.USE_SERIAL, True)
-    if (com_if_key == CoreComInterfaces.SERIAL_DLE.value or com_if_key == CoreComInterfaces.SERIAL_FIXED_FRAME.value) \
+    if (com_if_key == CoreComInterfaces.SERIAL_DLE.value or
+            com_if_key == CoreComInterfaces.SERIAL_FIXED_FRAME.value) \
             and com_port == "":
         LOGGER.warning("Invalid serial port specified!")
         com_port = determine_com_port(json_cfg_path=json_cfg_path)
diff --git a/src/tmtccmd/config/definitions.py b/src/tmtccmd/config/definitions.py
index d133e5ca..6e8f11d4 100644
--- a/src/tmtccmd/config/definitions.py
+++ b/src/tmtccmd/config/definitions.py
@@ -2,7 +2,7 @@
 @brief  Definitions for the TMTC commander core
 """
 import enum
-from typing import Tuple, Dict, Optional, List, Deque
+from typing import Tuple, Dict, Optional
 
 
 class CoreGlobalIds(enum.IntEnum):
diff --git a/src/tmtccmd/config/globals.py b/src/tmtccmd/config/globals.py
index 934ed20f..1f49523a 100644
--- a/src/tmtccmd/config/globals.py
+++ b/src/tmtccmd/config/globals.py
@@ -5,7 +5,8 @@
 
 from tmtccmd.utility.logger import get_console_logger
 from tmtccmd.utility.conf_util import check_args_in_dict, print_core_globals
-from tmtccmd.ecss.conf import PusVersion, set_default_apid, set_pus_tc_version, set_pus_tm_version
+from tmtccmd.ecss.conf import PusVersion, set_default_apid, get_default_apid, \
+    set_pus_tc_version, set_pus_tm_version
 from tmtccmd.core.globals_manager import update_global, get_global
 from tmtccmd.config.definitions import CoreGlobalIds, CoreModeList, CoreServiceList, \
     CoreModeStrings, CoreComInterfacesDict, CoreComInterfaces
@@ -17,13 +18,17 @@
 
 
 def get_global_apid() -> int:
-    return get_global(CoreGlobalIds.APID)
+    return get_default_apid()
 
 
 def set_json_cfg_path(json_cfg_path: str):
     update_global(CoreGlobalIds.JSON_CFG_PATH, json_cfg_path)
 
 
+def get_json_cfg_path() -> str:
+    return get_global(CoreGlobalIds.JSON_CFG_PATH)
+
+
 def set_glob_com_if_dict(custom_com_if_dict: ComIFDictT):
     CoreComInterfacesDict.update(custom_com_if_dict)
     update_global(CoreGlobalIds.COM_IF_DICT, CoreComInterfacesDict)
@@ -36,8 +41,9 @@ def get_glob_com_if_dict() -> ComIFDictT:
 def set_default_globals_pre_args_parsing(
         gui: bool, apid: int, pus_tc_version: PusVersion = PusVersion.PUS_C,
         pus_tm_version: PusVersion = PusVersion.PUS_C,
-        com_if_id: str = CoreComInterfaces.DUMMY.value, custom_com_if_dict=None, display_mode="long",
-        tm_timeout: float = 4.0, print_to_file: bool = True, tc_send_timeout_factor: float = 2.0
+        com_if_id: str = CoreComInterfaces.DUMMY.value, custom_com_if_dict=None,
+        display_mode="long", tm_timeout: float = 4.0, print_to_file: bool = True,
+        tc_send_timeout_factor: float = 2.0
 ):
     if custom_com_if_dict is None:
         custom_com_if_dict = dict()
@@ -87,28 +93,8 @@ def set_default_globals_post_args_parsing(
     :return:
     """
 
-    # Determine communication interface from arguments. Must be contained in core modes list
-    try:
-        mode_param = args.mode
-    except AttributeError:
-        LOGGER.warning("Passed namespace does not contain the mode (-m) argument")
-        mode_param = CoreModeList.LISTENER_MODE
-    mode_param = check_and_set_core_mode_arg(
-        mode_arg=mode_param, custom_modes_list=custom_modes_list
-    )
-    all_com_ifs = CoreComInterfacesDict
-    if custom_com_if_dict is not None:
-        all_com_ifs = CoreComInterfacesDict.update(custom_com_if_dict)
-    try:
-        com_if_key = str(args.com_if)
-    except AttributeError:
-        LOGGER.warning("No communication interface specified")
-        LOGGER.warning("Trying to set from existing configuration..")
-        com_if_key = determine_com_if(com_if_dict=all_com_ifs, json_cfg_path=json_cfg_path)
-    if com_if_key == CoreComInterfaces.UNSPECIFIED.value:
-        com_if_key = determine_com_if(com_if_dict=all_com_ifs, json_cfg_path=json_cfg_path)
-    update_global(CoreGlobalIds.COM_IF, com_if_key)
-    LOGGER.info(f"Communication interface: {all_com_ifs[com_if_key]}")
+    handle_mode_arg(args=args, custom_modes_list=custom_modes_list)
+    handle_com_if_arg(args=args, json_cfg_path=json_cfg_path, custom_com_if_dict=custom_com_if_dict)
 
     display_mode_param = "long"
     if args.short_display_mode is not None:
@@ -121,7 +107,10 @@ def set_default_globals_post_args_parsing(
     try:
         service_param = args.service
     except AttributeError:
-        LOGGER.warning("Passed namespace does not contain the service (-s) argument. Setting test service ID (17)")
+        LOGGER.warning(
+            "Passed namespace does not contain the service (-s) argument. "
+            "Setting test service ID (17)"
+        )
         service_param = CoreServiceList.SERVICE_17.value
     update_global(CoreGlobalIds.CURRENT_SERVICE, service_param)
     # Not used for now
@@ -146,6 +135,42 @@ def set_default_globals_post_args_parsing(
         print_core_globals()
 
 
+def handle_mode_arg(
+        args, custom_modes_list: Union[None, List[Union[collections.abc.Iterable, dict]]] = None
+) -> int:
+    # Determine communication interface from arguments. Must be contained in core modes list
+    try:
+        mode_param = args.mode
+    except AttributeError:
+        LOGGER.warning("Passed namespace does not contain the mode (-m) argument")
+        mode_param = CoreModeList.LISTENER_MODE
+    mode_param = check_and_set_core_mode_arg(
+        mode_arg=mode_param, custom_modes_list=custom_modes_list
+    )
+    update_global(CoreGlobalIds.MODE, mode_param)
+
+
+def handle_com_if_arg(
+        args, json_cfg_path: str, custom_com_if_dict: Dict[str, any] = None
+):
+    all_com_ifs = CoreComInterfacesDict
+    if custom_com_if_dict is not None:
+        all_com_ifs = CoreComInterfacesDict.update(custom_com_if_dict)
+    try:
+        com_if_key = str(args.com_if)
+    except AttributeError:
+        LOGGER.warning("No communication interface specified")
+        LOGGER.warning("Trying to set from existing configuration..")
+        com_if_key = determine_com_if(com_if_dict=all_com_ifs, json_cfg_path=json_cfg_path)
+    if com_if_key == CoreComInterfaces.UNSPECIFIED.value:
+        com_if_key = determine_com_if(com_if_dict=all_com_ifs, json_cfg_path=json_cfg_path)
+    update_global(CoreGlobalIds.COM_IF, com_if_key)
+    try:
+        LOGGER.info(f"Communication interface: {all_com_ifs[com_if_key]}")
+    except KeyError as e:
+        LOGGER.error(f'Invalid communication interface key {com_if_key}, error {e}')
+
+
 def check_and_set_other_args(args):
     if args.listener is not None:
         update_global(CoreGlobalIds.USE_LISTENER_AFTER_OP, args.listener)
@@ -175,7 +200,7 @@ def check_and_set_core_mode_arg(
 
     :param mode_arg:
     :param custom_modes_list:
-    :return:
+    :return: Mode value which was set
     """
     in_enum, mode_value = check_args_in_dict(
         param=mode_arg, iterable=CoreModeList, warning_hint="mode integers"
@@ -202,10 +227,13 @@ def check_and_set_core_mode_arg(
         mode_arg_invalid = True
 
     if mode_arg_invalid:
-        LOGGER.warning(f"Passed mode argument might be invalid, "
-                       f"setting to {CoreModeList.SINGLE_CMD_MODE}")
-        mode_value = CoreModeList.SINGLE_CMD_MODE
+        LOGGER.warning(
+            f"Passed mode argument might be invalid, "
+            f"setting to {CoreModeList.SEQUENTIAL_CMD_MODE}"
+        )
+        mode_value = CoreModeList.SEQUENTIAL_CMD_MODE
     update_global(CoreGlobalIds.MODE, mode_value)
+    return mode_value
 
 
 def check_and_set_core_service_arg(
@@ -240,9 +268,8 @@ def check_and_set_core_service_arg(
 
 def get_default_service_op_code_dict() -> ServiceOpCodeDictT:
     global SERVICE_OP_CODE_DICT
-    if SERVICE_OP_CODE_DICT == dict():
-        service_op_code_dict = dict()
-
+    service_op_code_dict = SERVICE_OP_CODE_DICT
+    if service_op_code_dict == dict():
         op_code_dict_srv_5 = {
             "0": ("Event Test", {OpCodeDictKeys.TIMEOUT: 2.0}),
         }
@@ -254,5 +281,5 @@ def get_default_service_op_code_dict() -> ServiceOpCodeDictT:
 
         service_op_code_dict[CoreServiceList.SERVICE_5.value] = service_5_tuple
         service_op_code_dict[CoreServiceList.SERVICE_17.value] = service_17_tuple
-        SERVICE_OP_CODE_DICT = service_op_code_dict
+        # SERVICE_OP_CODE_DICT = service_op_code_dict
     return service_op_code_dict
diff --git a/src/tmtccmd/config/hook.py b/src/tmtccmd/config/hook.py
index 048b650f..2e26227a 100644
--- a/src/tmtccmd/config/hook.py
+++ b/src/tmtccmd/config/hook.py
@@ -23,21 +23,14 @@ class TmTcHookBase:
     def __init__(self):
         pass
 
-    @abstractmethod
-    def get_version(self) -> str:
-        """The user can specify a custom version by overriding this function.
-        :return:
-        """
-        from tmtccmd import VERSION_NAME, __version__
-        return f"{VERSION_NAME} {__version__}"
-
     @abstractmethod
     def get_object_ids(self) -> Dict[bytes, list]:
+        from tmtccmd.config.objects import get_core_object_ids
         """The user can specify an object ID dictionary here mapping object ID bytearrays to a
         list. This list could contain containing the string representation or additional
         information about that object ID.
         """
-        pass
+        return get_core_object_ids()
 
     @abstractmethod
     def add_globals_pre_args_parsing(self, gui: bool = False):
@@ -49,15 +42,6 @@ def add_globals_pre_args_parsing(self, gui: bool = False):
         from tmtccmd.config.globals import set_default_globals_pre_args_parsing
         set_default_globals_pre_args_parsing(gui=gui, apid=DEFAULT_APID)
 
-    @abstractmethod
-    def get_json_config_file_path(self) -> str:
-        """The user can specify a path and filename for the JSON configuration file by overriding
-        this function.
-
-        :return:
-        """
-        return "tmtc_config.json"
-
     @abstractmethod
     def add_globals_post_args_parsing(self, args: argparse.Namespace):
         """Add global variables prior after parsing the CLI arguments.
@@ -124,7 +108,13 @@ def custom_args_parsing() -> Optional[argparse.Namespace]:
         """
         return None
 
-    # TODO: All of this will be moved to the dedicated PUS packet handler
+    def get_json_config_file_path(self) -> str:
+        """The user can specify a path and filename for the JSON configuration file by overriding
+        this function.
+
+        :return:
+        """
+        return "tmtc_config.json"
 
     @staticmethod
     def handle_service_8_telemetry(
diff --git a/src/tmtccmd/core/backend.py b/src/tmtccmd/core/backend.py
index 506c7580..b3724cd0 100644
--- a/src/tmtccmd/core/backend.py
+++ b/src/tmtccmd/core/backend.py
@@ -158,6 +158,8 @@ def initialize(self):
         Perform initialization steps which might be necessary after class construction.
         This has to be called at some point before using the class!
         """
+        if self.mode == CoreModeList.LISTENER_MODE:
+            LOGGER.info('Running in listener mode..')
         atexit.register(keyboard_interrupt_handler, tmtc_backend=self, com_interface=self.__com_if)
 
     def start_listener(self, perform_op_immediately: bool = True):
diff --git a/src/tmtccmd/core/frontend.py b/src/tmtccmd/core/frontend.py
index 4fec9d2c..22f421b9 100644
--- a/src/tmtccmd/core/frontend.py
+++ b/src/tmtccmd/core/frontend.py
@@ -425,8 +425,11 @@ def __update_op_code_combo_box(self):
         op_code_dict = self.service_op_code_dict[self._current_service][1]
         if op_code_dict is not None:
             for op_code_key, op_code_value in op_code_dict.items():
-                self._op_code_list.append(op_code_key)
-                self.__combo_box_op_codes.addItem(op_code_value[0])
+                try:
+                    self._op_code_list.append(op_code_key)
+                    self.__combo_box_op_codes.addItem(op_code_value[0])
+                except TypeError:
+                    LOGGER.warning(f'Invalid op code entry {op_code_value}, skipping..')
             self._current_op_code = self._op_code_list[0]
 
     def __checkbox_log_update(self, state: int):
diff --git a/src/tmtccmd/ecss/tc.py b/src/tmtccmd/ecss/tc.py
index 2b33e0f1..805d8fdc 100644
--- a/src/tmtccmd/ecss/tc.py
+++ b/src/tmtccmd/ecss/tc.py
@@ -1,15 +1,12 @@
-"""
-@file   base.py
-@brief
-This module contains the PUS telecommand class representation to pack telecommands.
-@author R. Mueller
+"""This module contains the PUS telecommand class representation to pack telecommands.
 """
 from __future__ import annotations
 import sys
-from typing import Tuple, Union
+from typing import Tuple
 
+from tmtccmd.ccsds.log import LOGGER
 from tmtccmd.ccsds.spacepacket import \
-    SpacePacketHeaderSerializer, \
+    SpacePacketHeader, \
     PacketTypes, \
     SPACE_PACKET_HEADER_SIZE
 from tmtccmd.ecss.conf import get_default_apid, PusVersion, get_pus_tc_version
@@ -25,7 +22,8 @@
 class PusTcDataFieldHeaderSerialize:
     def __init__(
             self, service_type: int, service_subtype: int, source_id: int = 0,
-            pus_tc_version: PusVersion = PusVersion.PUS_C, ack_flags: int = 0b1111, secondary_header_flag: int = 0
+            pus_tc_version: PusVersion = PusVersion.PUS_C, ack_flags: int = 0b1111,
+            secondary_header_flag: int = 0
     ):
         self.service_type = service_type
         self.service_subtype = service_subtype
@@ -34,7 +32,8 @@ def __init__(
         self.ack_flags = ack_flags
         if self.pus_tc_version == PusVersion.PUS_A:
             pus_version_num = 1
-            self.pus_version_and_ack_byte = secondary_header_flag << 7 | pus_version_num << 4 | ack_flags
+            self.pus_version_and_ack_byte = \
+                secondary_header_flag << 7 | pus_version_num << 4 | ack_flags
         else:
             pus_version_num = 2
             self.pus_version_and_ack_byte = pus_version_num << 4 | ack_flags
@@ -72,8 +71,8 @@ class PusTelecommand:
 
     def __init__(
             self, service: int, subservice: int, ssc=0,
-            app_data: bytearray = bytearray([]), source_id: int = 0, pus_tc_version: int = PusVersion.UNKNOWN,
-            ack_flags: int = 0b1111, apid: int = -1
+            app_data: bytearray = bytearray([]), source_id: int = 0,
+            pus_tc_version: int = PusVersion.UNKNOWN, ack_flags: int = 0b1111, apid: int = -1
     ):
         """Initiate a PUS telecommand from the given parameters. The raw byte representation
         can then be retrieved with the pack() function.
@@ -97,14 +96,14 @@ def __init__(
         packet_type = PacketTypes.PACKET_TYPE_TC
         secondary_header_flag = 1
         if subservice > 255:
-            print("Subservice value invalid. Setting to 0")
+            LOGGER.warning("Subservice value invalid. Setting to 0")
             subservice = 0
         if service > 255:
-            print("Service value invalid. Setting to 0")
+            LOGGER.warning("Service value invalid. Setting to 0")
             service = 0
         # SSC can have maximum of 14 bits
         if ssc > pow(2, 14):
-            print("SSC invalid, setting to 0")
+            LOGGER.warning("SSC invalid, setting to 0")
             ssc = 0
         self._data_field_header = PusTcDataFieldHeaderSerialize(
             service_type=service, service_subtype=subservice, ack_flags=ack_flags,
@@ -114,7 +113,7 @@ def __init__(
             secondary_header_len=self._data_field_header.get_header_size(),
             app_data_len=len(app_data),
         )
-        self._space_packet_header = SpacePacketHeaderSerializer(
+        self._space_packet_header = SpacePacketHeader(
             apid=apid, secondary_header_flag=secondary_header_flag, packet_type=packet_type,
             data_length=data_length, source_sequence_count=ssc
         )
@@ -165,7 +164,7 @@ def get_data_length(app_data_len: int, secondary_header_len: int) -> int:
             data_length = secondary_header_len + app_data_len + 1
             return data_length
         except TypeError:
-            print("PusTelecommand: Invalid type of application data!")
+            LOGGER.warning("PusTelecommand: Invalid type of application data!")
             return 0
 
     def pack_command_tuple(self) -> Tuple[bytearray, PusTelecommand]:
diff --git a/src/tmtccmd/ecss/tm.py b/src/tmtccmd/ecss/tm.py
index 7e8f71db..e722c0f4 100644
--- a/src/tmtccmd/ecss/tm.py
+++ b/src/tmtccmd/ecss/tm.py
@@ -1,12 +1,26 @@
-import math
-import time
-import datetime
+from __future__ import annotations
+from abc import abstractmethod
 
 from crcmod import crcmod
 
-from tmtccmd.ccsds.spacepacket import SpacePacketHeaderDeserializer, SPACE_PACKET_HEADER_SIZE, \
-    get_total_packet_len_from_len_field
-from tmtccmd.ecss.conf import get_pus_tm_version, PusVersion
+from tmtccmd.ccsds.log import LOGGER
+from tmtccmd.ccsds.spacepacket import SpacePacketHeader, SPACE_PACKET_HEADER_SIZE, \
+    get_total_space_packet_len_from_len_field, PacketTypes
+from tmtccmd.ccsds.time import CdsShortTimestamp, read_p_field
+from tmtccmd.ecss.conf import get_pus_tm_version, PusVersion, get_tm_apid
+from tmtccmd.tm.base import PusTmInfoInterface, PusTmInterface
+
+
+def get_service_from_raw_pus_packet(raw_bytearray: bytearray) -> int:
+    """Determine the service ID from a raw packet, which can be used for packet deserialization.
+
+    It is assumed that the user already checked that the raw bytearray contains a PUS packet and
+    only basic sanity checks will be performed.
+    :raise ValueError: If raw bytearray is too short
+    """
+    if len(raw_bytearray) < 8:
+        raise ValueError
+    return raw_bytearray[7]
 
 
 class PusTelemetry:
@@ -18,72 +32,131 @@ class PusTelemetry:
     CDS_SHORT_SIZE = 7
     PUS_TIMESTAMP_SIZE = CDS_SHORT_SIZE
 
-    def __init__(self, raw_telemetry: bytearray = bytearray()):
+    def __init__(
+            self, service_id: int, subservice_id: int, time: CdsShortTimestamp = None, ssc: int = 0,
+            source_data: bytearray = bytearray([]), apid: int = -1, message_counter: int = 0,
+            space_time_ref: int = 0b0000, destination_id: int = 0,
+            packet_version: int = 0b000, pus_version: PusVersion = PusVersion.UNKNOWN,
+            pus_tm_version: int = 0b0001, ack: int = 0b1111, secondary_header_flag: bool = True,
+    ):
+        if apid == -1:
+            apid = get_tm_apid()
+        if pus_version == PusVersion.UNKNOWN:
+            pus_version = get_pus_tm_version()
+        if time is None:
+            time = CdsShortTimestamp.init_from_current_time()
+        # packet type for telemetry is 0 as specified in standard
+        # specified in standard
+        packet_type = PacketTypes.PACKET_TYPE_TM
+        self._source_data = source_data
+        data_length = self.get_source_data_length(
+            timestamp_len=PusTelemetry.PUS_TIMESTAMP_SIZE, pus_version=pus_version
+        )
+        self.space_packet_header = SpacePacketHeader(
+            apid=apid, packet_type=packet_type, secondary_header_flag=secondary_header_flag,
+            packet_version=packet_version, data_length=data_length, source_sequence_count=ssc
+        )
+        self.secondary_packet_header = PusTmSecondaryHeader(
+            pus_version=pus_version, service_id=service_id, subservice_id=subservice_id,
+            message_counter=message_counter, destination_id=destination_id,
+            spacecraft_time_ref=space_time_ref, time=time
+        )
+        self._valid = False
+        self.print_info = ''
+
+    @classmethod
+    def __empty(cls, pus_version: PusVersion = PusVersion.UNKNOWN) -> PusTelemetry:
+        return PusTelemetry(
+            service_id=0, subservice_id=0, time=CdsShortTimestamp.init_from_current_time()
+        )
+
+    def pack(self) -> bytearray:
+        """
+        Serializes the PUS telemetry into a raw packet.
+        """
+        tm_packet_raw = bytearray()
+        # PUS Header
+        tm_packet_raw.extend(self.space_packet_header.pack())
+        # PUS Source Data Field
+        tm_packet_raw.extend(self.secondary_packet_header.pack())
+        # Source Data
+        tm_packet_raw.extend(self._source_data)
+        # CRC16 checksum
+        crc_func = crcmod.mkCrcFun(0x11021, rev=False, initCrc=0xffff, xorOut=0x0000)
+        crc16 = crc_func(tm_packet_raw)
+        tm_packet_raw.append((crc16 & 0xff00) >> 8)
+        tm_packet_raw.append(crc16 & 0xff)
+        return tm_packet_raw
+
+    @classmethod
+    def unpack(
+            cls, raw_telemetry: bytearray, pus_version: PusVersion = PusVersion.UNKNOWN
+    ) -> PusTelemetry:
         """Attempts to construct a generic PusTelemetry class given a raw bytearray.
-        :raise: ValueError if the format of the raw bytearray is invalid.
+        :raises ValueError: if the format of the raw bytearray is invalid, for example the length
         :param raw_telemetry:
         """
-        if raw_telemetry is None or raw_telemetry == bytearray():
+        if raw_telemetry is None or len(raw_telemetry) == 0:
             if raw_telemetry is None:
-                print("PusTelemetry: Given byte stream invalid!")
-            elif raw_telemetry == bytearray():
-                print("PusTelemetry: Given byte stream empty!")
+                LOGGER.warning("Given byte stream invalid!")
+            elif len(raw_telemetry) == 0:
+                LOGGER.warning("Given byte stream empty!")
             raise ValueError
-        self.pus_version = get_pus_tm_version()
-        self._packet_raw = raw_telemetry
-        self._space_packet_header = SpacePacketHeaderDeserializer(pus_packet_raw=raw_telemetry)
-        self._valid = False
-        expected_packet_len = get_total_packet_len_from_len_field(
-            self._space_packet_header.data_length
+        pus_tm = cls.__empty(pus_version=pus_version)
+        pus_tm.space_packet_header = SpacePacketHeader.unpack(space_packet_raw=raw_telemetry)
+        expected_packet_len = get_total_space_packet_len_from_len_field(
+            pus_tm.space_packet_header.data_length
         )
         if expected_packet_len > len(raw_telemetry):
-            print(
+            LOGGER.warning(
                 f'PusTelemetry: Passed packet with length {len(raw_telemetry)} '
                 f'shorter than specified packet length in PUS header {expected_packet_len}'
             )
             raise ValueError
-        self._data_field_header = PusPacketDataFieldHeader(
-            raw_telemetry[SPACE_PACKET_HEADER_SIZE:], pus_version=self.pus_version
+        pus_tm.secondary_packet_header = PusTmSecondaryHeader.unpack(
+            header_start=raw_telemetry[SPACE_PACKET_HEADER_SIZE:],
+            pus_version=pus_version
         )
-        if self._data_field_header.get_header_size() + SPACE_PACKET_HEADER_SIZE > \
-                len(raw_telemetry) - 2:
-            print("PusTelemetry: Passed packet too short!")
+        if len(raw_telemetry) - 2 < \
+                pus_tm.secondary_packet_header.get_header_size() + SPACE_PACKET_HEADER_SIZE:
+            LOGGER.warning("Passed packet too short!")
             raise ValueError
-        if self.get_packet_size() != len(raw_telemetry):
-            print(
+        if pus_tm.get_packet_size() != len(raw_telemetry):
+            LOGGER.warning(
                 f'PusTelemetry: Packet length field '
-                f'{self._space_packet_header.data_length} might be invalid!'
+                f'{pus_tm.space_packet_header.data_length} might be invalid!'
             )
-            print(f'self.get_packet_size: {self.get_packet_size()}')
-            print(f'len(raw_telemetry): {len(raw_telemetry)}')
-        self._tm_data = raw_telemetry[
-            self._data_field_header.get_header_size() + SPACE_PACKET_HEADER_SIZE:-2
+            LOGGER.warning(f'self.get_packet_size: {pus_tm.get_packet_size()}')
+            LOGGER.warning(f'len(raw_telemetry): {len(raw_telemetry)}')
+        pus_tm._source_data = raw_telemetry[
+            pus_tm.secondary_packet_header.get_header_size() + SPACE_PACKET_HEADER_SIZE:-2
         ]
-        self._crc = \
+        pus_tm._crc = \
             raw_telemetry[len(raw_telemetry) - 2] << 8 | raw_telemetry[len(raw_telemetry) - 1]
-        self.print_info = ""
-        self.__perform_crc_check(raw_telemetry)
+        pus_tm.print_info = ""
+        pus_tm.__perform_crc_check(raw_telemetry)
+        return pus_tm
 
     def __str__(self):
-        return f"PUS TM[{self._data_field_header.service_type}," \
-               f"{self._data_field_header.service_subtype}] with subcounter " \
-               f"{self._data_field_header.subcounter}"
+        return f"PUS TM[{self.secondary_packet_header.service_id}," \
+               f"{self.secondary_packet_header.subservice_id}] with message counter " \
+               f"{self.secondary_packet_header.message_counter}"
 
     def __repr__(self):
-        return f"{self.__class__.__name__}(service={self._data_field_header.service_subtype!r}, " \
-               f"subservice={self._data_field_header.service_subtype!r})"
+        return f"{self.__class__.__name__}(service={self.secondary_packet_header.service_id!r}, " \
+               f"subservice={self.secondary_packet_header.subservice_id!r})"
 
     def get_service(self):
         """
         :return: Service ID
         """
-        return self._data_field_header.service_type
+        return self.secondary_packet_header.service_id
 
     def get_subservice(self):
         """
         :return: Subservice ID
         """
-        return self._data_field_header.service_subtype
+        return self.secondary_packet_header.subservice_id
 
     def is_valid(self):
         return self._valid
@@ -92,22 +165,44 @@ def get_tm_data(self) -> bytearray:
         """
         :return: TM application data (raw)
         """
-        return self._tm_data
+        return self._source_data
 
-    def get_tc_packet_id(self):
-        return self._space_packet_header.packet_id
+    def get_packet_id(self):
+        return self.space_packet_header.packet_id
 
     def __perform_crc_check(self, raw_telemetry: bytearray):
         crc_func = crcmod.mkCrcFun(0x11021, rev=False, initCrc=0xFFFF, xorOut=0x0000)
         if len(raw_telemetry) < self.get_packet_size():
-            print("PusTelemetry: Invalid packet length")
+            LOGGER.warning('Invalid packet length')
             return
         data_to_check = raw_telemetry[0:self.get_packet_size()]
         crc = crc_func(data_to_check)
         if crc == 0:
             self._valid = True
         else:
-            print("PusTelemetry: Invalid CRC detected !")
+            LOGGER.warning('Invalid CRC detected !')
+
+    def get_source_data_length(self, timestamp_len: int, pus_version: PusVersion) -> int:
+        """Retrieve size of TM packet data header in bytes.
+
+        Formula according to PUS Standard: C = (Number of octets in packet source data field) - 1.
+        The size of the TM packet is the size of the packet secondary header with
+        the timestamp + the length of the application data + PUS timestamp size +
+        length of the CRC16 checksum - 1
+        """
+        try:
+            if pus_version == PusVersion.PUS_A:
+                data_length = \
+                    PusTmSecondaryHeader.HEADER_SIZE_WITHOUT_TIME_PUS_A + \
+                    timestamp_len + len(self._source_data) + 1
+            else:
+                data_length = \
+                    PusTmSecondaryHeader.HEADER_SIZE_WITHOUT_TIME_PUS_C + \
+                    timestamp_len + len(self._source_data) + 1
+            return data_length
+        except TypeError:
+            print("PusTelecommand: Invalid type of application data!")
+            return 0
 
     def specify_packet_info(self, print_info: str):
         """Caches a print information string for later printing
@@ -130,8 +225,8 @@ def append_telemetry_content(self, content_list: list):
         :param content_list: Header content will be appended to this list
         :return:
         """
-        self._data_field_header.append_data_field_header(content_list=content_list)
-        self._space_packet_header.append_space_packet_header_content(content_list=content_list)
+        self.secondary_packet_header.append_data_field_header(content_list=content_list)
+        self.space_packet_header.append_space_packet_header_content(content_list=content_list)
         if self.is_valid():
             content_list.append("Yes")
         else:
@@ -145,8 +240,8 @@ def append_telemetry_column_headers(self, header_list: list):
         :param header_list: Header content will be appended to this list
         :return:
         """
-        self._data_field_header.append_data_field_header_column_header(header_list=header_list)
-        self._space_packet_header.append_space_packet_header_column_headers(header_list=header_list)
+        self.secondary_packet_header.append_data_field_header_column_header(header_list=header_list)
+        self.space_packet_header.append_space_packet_header_column_headers(header_list=header_list)
         header_list.append("Packet valid")
 
     def get_custom_printout(self) -> str:
@@ -155,78 +250,180 @@ def get_custom_printout(self) -> str:
         """
         return ""
 
-    def get_raw_packet(self) -> bytearray:
-        """Get the whole TM packet as a bytearray (raw)
-        :return: TM wiretapping_packet
-        """
-        return bytearray(self._packet_raw)
-
     def get_packet_size(self) -> int:
         """
         :return: Size of the TM packet based on the space packet header data length field.
         The space packet data field is the full length of data field minus one without the space packet header.
         """
-        return SPACE_PACKET_HEADER_SIZE + self._space_packet_header.data_length + 1
+        return SPACE_PACKET_HEADER_SIZE + self.space_packet_header.data_length + 1
+
+    def get_apid(self) -> int:
+        return self.space_packet_header.apid
 
     def get_ssc(self) -> int:
         """Get the source sequence count
         :return: Source Sequence Count (see below, or PUS documentation)
         """
-        return self._space_packet_header.ssc
+        return self.space_packet_header.ssc
 
     def return_full_packet_string(self):
-        return return_data_string(self._packet_raw, len(self._packet_raw))
+        packet_raw = self.pack()
+        return get_printable_data_string(packet_raw, len(packet_raw))
 
     def print_full_packet_string(self):
         """Print the full TM packet in a clean format."""
-        print(return_data_string(self._packet_raw, len(self._packet_raw)))
+        packet_raw = self.pack()
+        print(get_printable_data_string(packet_raw, len(packet_raw)))
 
     def print_source_data(self):
         """Prints the TM source data in a clean format
         :return:
         """
-        print(return_data_string(self._tm_data, len(self._tm_data)))
+        print(get_printable_data_string(self._source_data, len(self._source_data)))
 
     def return_source_data_string(self):
         """Returns the source data string"""
-        return return_data_string(self._tm_data, len(self._tm_data))
+        return get_printable_data_string(self._source_data, len(self._source_data))
 
 
-class PusPacketDataFieldHeader:
-    """Unpacks the PUS packet data field header. Currently only supports CDS short timestamps"""
+class PusTmSecondaryHeader:
+    """Unpacks the PUS telemetry packet secondary header.
 
-    def __init__(self, bytes_array: bytearray, pus_version: PusVersion):
+    Currently only supports CDS short timestamps"""
+    HEADER_SIZE_WITHOUT_TIME_PUS_A = 4
+    HEADER_SIZE_WITHOUT_TIME_PUS_C = 7
+
+    def __init__(
+            self, pus_version: PusVersion, service_id: int, subservice_id: int,
+            time: CdsShortTimestamp, message_counter: int, destination_id: int = 0,
+            spacecraft_time_ref: int = 0,
+    ):
+        """Create a PUS telemetry secondary header object.
+
+        :param pus_version:
+        :param service_id:
+        :param subservice_id:
+        :param message_counter: 8 bit counter for PUS A, 16 bit counter for PUS C
+        :param spacecraft_time_ref: Space time reference if PUS C is used
+        :param time: Time field
+        """
         self.pus_version = pus_version
-        if len(bytes_array) < self.get_header_size():
-            print(f"Invalid PUS data field header size, less than expected {self.get_header_size()} bytes")
-            return
-        if pus_version == PusVersion.PUS_A:
-            self.pus_version_number = (bytes_array[0] & 0x70) >> 4
+        self.spacecraft_time_ref = spacecraft_time_ref
+        if self.pus_version == PusVersion.PUS_A:
+            self.pus_version_number = 0
         else:
-            self.pus_version_number = (bytes_array[0] & 0xF0) >> 4
-            self.spacecraft_time_ref = bytes_array[0] & 0x0F
-        self.service_type = bytes_array[1]
-        self.service_subtype = bytes_array[2]
+            self.pus_version_number = 1
+        self.service_id = service_id
+        self.subservice_id = subservice_id
+        if (self.pus_version == PusVersion.PUS_A and message_counter > 255) or \
+                (self.pus_version == PusVersion.PUS_C and message_counter > 65536):
+            raise ValueError
+        self.message_counter = message_counter
+        self.destination_id = destination_id
+        self.time = time
+
+    @classmethod
+    def __empty(cls) -> PusTmSecondaryHeader:
+        return PusTmSecondaryHeader(
+            pus_version=PusVersion.PUS_C,
+            service_id=-1,
+            subservice_id=-1,
+            time=CdsShortTimestamp.init_from_current_time(),
+            message_counter=0
+        )
+
+    def pack(self) -> bytearray:
+        secondary_header = bytearray()
+        if self.pus_version == PusVersion.PUS_A:
+            secondary_header.append((self.pus_version_number & 0x07) << 4)
+        elif self.pus_version == PusVersion.PUS_C:
+            secondary_header.append(self.pus_version_number << 4 | self.spacecraft_time_ref)
+        secondary_header.append(self.service_id)
+        secondary_header.append(self.subservice_id)
+        if self.pus_version == PusVersion.PUS_A:
+            secondary_header.append(self.message_counter)
+        elif self.pus_version == PusVersion.PUS_C:
+            secondary_header.append((self.message_counter & 0xff00) >> 8)
+            secondary_header.append(self.message_counter & 0xff)
+            secondary_header.append((self.destination_id & 0xff00) >> 8)
+            secondary_header.append(self.destination_id & 0xff)
+        secondary_header.extend(self.time.pack())
+        return secondary_header
+
+    @classmethod
+    def unpack(cls, header_start: bytearray, pus_version: PusVersion) -> PusTmSecondaryHeader:
+        """Unpack the PUS TM secondary header from the raw packet starting at the header index.
+        The user still needs to specify the PUS version because the version field is parsed
+        differently depending on the PUS version.
+
+        :param header_start:
+        :param pus_version:
+        :raises ValueError: bytearray too short or PUS version missmatch.
+        :return:
+        """
+        if pus_version == PusVersion.UNKNOWN:
+            pus_version = get_pus_tm_version()
+        secondary_header = cls.__empty()
+        current_idx = 0
         if pus_version == PusVersion.PUS_A:
-            # TODO: This can be optional too, have option to ommit it?
-            self.subcounter = bytes_array[3]
-        else:
-            self.subcounter = bytes_array[3] << 8 | bytes_array[4]
-            self.destination_id = bytes_array[5] << 8 | bytes_array[6]
+            secondary_header.pus_version = PusVersion.PUS_A
+            secondary_header.pus_version_number = (header_start[current_idx] & 0x70) >> 4
+            if secondary_header.pus_version_number == 1:
+                LOGGER.warning(
+                    'PUS version field value 1 found where PUS A value (0) was expected!'
+                )
+                raise ValueError
+
+        elif pus_version == PusVersion.PUS_C:
+            secondary_header.pus_version = PusVersion.PUS_C
+            if secondary_header.pus_version_number == 0:
+                LOGGER.warning(
+                    'PUS version field value 0 found where PUS C value (1) was expected!'
+                )
+                raise ValueError
+            secondary_header.pus_version_number = (header_start[current_idx] & 0xF0) >> 4
+            secondary_header.spacecraft_time_ref = header_start[current_idx] & 0x0F
+        if len(header_start) < secondary_header.get_header_size():
+            LOGGER.warning(
+                f'Invalid PUS data field header size, '
+                f'less than expected {secondary_header.get_header_size()} bytes'
+            )
+            raise ValueError
+        current_idx += 1
+        secondary_header.service_id = header_start[current_idx]
+        current_idx += 1
+        secondary_header.subservice_id = header_start[current_idx]
+        current_idx += 1
         if pus_version == PusVersion.PUS_A:
-            self.time = PusCdsShortTimestamp(bytes_array[4: 4 + PusTelemetry.PUS_TIMESTAMP_SIZE])
+            secondary_header.message_counter = header_start[current_idx]
+            current_idx += 1
         else:
-            self.time = PusCdsShortTimestamp(bytes_array[7: 7 + PusTelemetry.PUS_TIMESTAMP_SIZE])
+            secondary_header.message_counter = \
+                header_start[current_idx] << 8 | header_start[current_idx + 1]
+            current_idx += 2
+        if pus_version == PusVersion.PUS_C:
+            secondary_header.destination_id = \
+                header_start[current_idx] << 8 | header_start[current_idx + 1]
+            current_idx += 2
+        # If other time formats are supported in the future, this information can be used
+        #  to unpack the correct time code
+        time_code_id = read_p_field(header_start[current_idx])
+        if time_code_id:
+            pass
+        secondary_header.time = CdsShortTimestamp.unpack(
+            time_field=header_start[current_idx: current_idx + PusTelemetry.PUS_TIMESTAMP_SIZE]
+        )
+        return secondary_header
 
     def append_data_field_header(self, content_list: list):
         """Append important data field header parameters to the passed content list.
         :param content_list:
         :return:
         """
-        content_list.append(str(self.service_type))
-        content_list.append(str(self.service_subtype))
-        content_list.append(str(self.subcounter))
-        self.time.print_time(content_list)
+        content_list.append(str(self.service_id))
+        content_list.append(str(self.subservice_id))
+        content_list.append(str(self.message_counter))
+        self.time.add_time_to_content_list(content_list=content_list)
 
     def append_data_field_header_column_header(self, header_list: list):
         """Append important data field header column headers to the passed list.
@@ -236,7 +433,7 @@ def append_data_field_header_column_header(self, header_list: list):
         header_list.append("Service")
         header_list.append("Subservice")
         header_list.append("Subcounter")
-        self.time.print_time_headers(header_list)
+        self.time.add_time_headers_to_header_list(header_list=header_list)
 
     def get_header_size(self):
         if self.pus_version == PusVersion.PUS_A:
@@ -245,67 +442,7 @@ def get_header_size(self):
             return PusTelemetry.PUS_TIMESTAMP_SIZE + 7
 
 
-class PusCdsShortTimestamp:
-    """Unpacks the time datafield of the TM packet. Right now, CDS Short timeformat is used,
-    and the size of the time stamp is expected to be seven bytes.
-    """
-    # TODO: Implement more time formats
-    CDS_ID = 4
-    SECONDS_PER_DAY = 86400
-    EPOCH = datetime.datetime.utcfromtimestamp(0)
-    DAYS_CCSDS_TO_UNIX = 4383
-    TIMESTAMP_SIZE = PusTelemetry.PUS_TIMESTAMP_SIZE
-
-    def __init__(self, byte_array: bytearray = bytearray([])):
-        if len(byte_array) > 0:
-            # pField = byte_array[0]
-            self.days = ((byte_array[1] << 8) | (byte_array[2])) - \
-                        PusCdsShortTimestamp.DAYS_CCSDS_TO_UNIX
-            self.seconds = self.days * (24 * 60 * 60)
-            s_day = ((byte_array[3] << 24) | (byte_array[4] << 16) |
-                     (byte_array[5]) << 8 | byte_array[6]) / 1000
-            self.seconds += s_day
-            self.time = self.seconds
-            self.time_string = \
-                datetime.datetime.utcfromtimestamp(self.time).strftime("%Y-%m-%d %H:%M:%S.%f")
-
-    @staticmethod
-    def pack_current_time() -> bytearray:
-        """Returns a seven byte CDS short timestamp"""
-        timestamp = bytearray()
-        p_field = (PusCdsShortTimestamp.CDS_ID << 4) + 0
-        days = \
-            (datetime.datetime.utcnow() - PusCdsShortTimestamp.EPOCH).days + \
-            PusCdsShortTimestamp.DAYS_CCSDS_TO_UNIX
-        days_h = (days & 0xFF00) >> 8
-        days_l = days & 0xFF
-        seconds = time.time()
-        fraction_ms = seconds - math.floor(seconds)
-        days_ms = int((seconds % PusCdsShortTimestamp.SECONDS_PER_DAY) * 1000 + fraction_ms)
-        days_ms_hh = (days_ms & 0xFF000000) >> 24
-        days_ms_h = (days_ms & 0xFF0000) >> 16
-        days_ms_l = (days_ms & 0xFF00) >> 8
-        days_ms_ll = (days_ms & 0xFF)
-        timestamp.append(p_field)
-        timestamp.append(days_h)
-        timestamp.append(days_l)
-        timestamp.append(days_ms_hh)
-        timestamp.append(days_ms_h)
-        timestamp.append(days_ms_l)
-        timestamp.append(days_ms_ll)
-        return timestamp
-
-    def print_time(self, content_list):
-        content_list.append(self.time)
-        content_list.append(self.time_string)
-
-    @staticmethod
-    def print_time_headers(header_list):
-        header_list.append("OBSWTime (s)")
-        header_list.append("Time")
-
-
-def return_data_string(byte_array: bytearray, length: int) -> str:
+def get_printable_data_string(byte_array: bytearray, length: int) -> str:
     """Returns the TM data in a clean printable string format
     Prints payload data in default mode
     and prints the whole packet if full_packet = True is passed.
@@ -320,7 +457,73 @@ def return_data_string(byte_array: bytearray, length: int) -> str:
     str_to_print += "]"
     return str_to_print
 
-# pylint: disable=line-too-long
+
+class PusTmBase(PusTmInterface):
+    def __init__(self, pus_tm: PusTelemetry):
+        self.pus_tm = pus_tm
+
+    @abstractmethod
+    def pack(self) -> bytearray:
+        return self.pus_tm.pack()
+
+    @abstractmethod
+    def get_tm_data(self) -> bytearray:
+        return self.pus_tm.get_tm_data()
+
+    @abstractmethod
+    def get_ssc(self) -> int:
+        return self.pus_tm.get_ssc()
+
+    @abstractmethod
+    def is_valid(self):
+        return self.pus_tm.is_valid()
+
+    @abstractmethod
+    def get_apid(self) -> int:
+        return self.pus_tm.get_apid()
+
+    @abstractmethod
+    def get_service(self) -> int:
+        return self.pus_tm.get_service()
+
+    @abstractmethod
+    def get_subservice(self) -> int:
+        return self.pus_tm.get_subservice()
+
+
+class PusTmInfoBase(PusTmInfoInterface):
+    def __init__(self, pus_tm: PusTelemetry):
+        self.pus_tm = pus_tm
+
+    @abstractmethod
+    def get_print_info(self) -> str:
+        return self.pus_tm.print_info
+
+    @abstractmethod
+    def get_custom_printout(self) -> str:
+        return self.pus_tm.get_custom_printout()
+
+    @abstractmethod
+    def return_source_data_string(self) -> str:
+        return self.pus_tm.return_source_data_string()
+
+    @abstractmethod
+    def specify_packet_info(self, print_info: str):
+        self.pus_tm.print_info = print_info
+
+    @abstractmethod
+    def append_packet_info(self, info: str):
+        self.pus_tm.print_info += info
+
+    @abstractmethod
+    def append_telemetry_column_headers(self, header_list: list):
+        self.pus_tm.append_telemetry_column_headers(header_list=header_list)
+
+    @abstractmethod
+    def append_telemetry_content(self, content_list: list):
+        self.pus_tm.append_telemetry_content(content_list=content_list)
+
+
 # Structure of a PUS Packet :
 # A PUS packet consists of consecutive bits, the allocation and structure is standardised.
 # Extended information can be found in ECSS-E-70-41A  on p.46
diff --git a/src/tmtccmd/ecss/tm_creator.py b/src/tmtccmd/ecss/tm_creator.py
deleted file mode 100644
index 56fdcfee..00000000
--- a/src/tmtccmd/ecss/tm_creator.py
+++ /dev/null
@@ -1,116 +0,0 @@
-from crcmod import crcmod
-
-from tmtccmd.ecss.tm import PusCdsShortTimestamp, PusTelemetry
-from tmtccmd.ccsds.spacepacket import PacketTypes, SpacePacketHeaderSerializer
-from tmtccmd.ecss.conf import get_tm_apid, PusVersion, get_pus_tm_version
-
-
-# pylint: disable=too-many-instance-attributes
-# pylint: disable=too-many-arguments
-class PusTelemetryCreator:
-    DATA_FIELD_HEADER_SIZE_WITHOUT_TIME_PUS_A = 4
-    DATA_FIELD_HEADER_SIZE_WITHOUT_TIME_PUS_C = 7
-
-    """
-    Alternative way to create a PUS Telemetry packet by specifying telemetry parameters,
-    similarly to the way telecommands are created. This can be used to create telemetry
-    directly in the software. See documentation and more information related to
-    the ESA PUS standard in the PusTelemetry documentation.
-    """
-    def __init__(self, service: int, subservice: int, ssc: int = 0,
-                 source_data: bytearray = bytearray([]), apid: int = -1, version: int = 0b000,
-                 pus_version: PusVersion = PusVersion.UNKNOWN, pus_tm_version: int = 0b0001,
-                 ack: int = 0b1111, secondary_header_flag: int = -1, space_time_ref: int = 0b0000,
-                 destination_id: int = 0):
-        """
-        Initiates the unserialized data fields for the PUS telemetry packet.
-        """
-        if apid == -1:
-            apid = get_tm_apid()
-        if pus_version == PusVersion.UNKNOWN:
-            pus_version = get_pus_tm_version()
-        self.pus_version = pus_version
-        # packet type for telemetry is 0 as specified in standard
-        # specified in standard
-        packet_type = PacketTypes.PACKET_TYPE_TM
-        self.source_data = source_data
-        data_length = self.get_source_data_length(
-            timestamp_len=PusTelemetry.PUS_TIMESTAMP_SIZE, pus_version=pus_version
-        )
-        self._space_packet_header = SpacePacketHeaderSerializer(
-            apid=apid, packet_type=packet_type, secondary_header_flag=secondary_header_flag,
-            version=version, data_length=data_length, source_sequence_count=ssc
-        )
-        self.pus_version_and_ack_byte = pus_tm_version | space_time_ref
-        # NOTE: In PUS-C, the PUS Version is 2 and specified for the first 4 bits.
-        # The other 4 bits of the first byte are the spacecraft time reference status
-        self.data_field_version = 0b00010000
-        self.service = service
-        self.subservice = subservice
-        self.pack_subcounter = 0
-        if pus_version == PusVersion.PUS_C:
-            self.destination_id = destination_id
-        # it is assumed the time field consts of 8 bytes.
-
-    def print(self):
-        """ Print the raw command in a clean format. """
-        packet = self.pack()
-        print_out = "Telemetry in Hexadecimal: ["
-        for counter in range(len(packet)):
-            if counter == len(packet) - 1:
-                print_out += str(hex(packet[counter]))
-            else:
-                print_out += str(hex(packet[counter])) + ", "
-        print_out += "]"
-        print(print_out)
-
-    def set_source_data(self, source_data: bytearray):
-        self.source_data = source_data
-
-    def pack(self) -> bytearray:
-        """
-        Serializes the PUS telemetry into a raw packet.
-        """
-        tm_packet_raw = bytearray()
-        # PUS Header
-        tm_packet_raw.extend(self._space_packet_header.pack())
-        # PUS Source Data Field
-        tm_packet_raw.append(self.data_field_version)
-        tm_packet_raw.append(self.service)
-        tm_packet_raw.append(self.subservice)
-        if self.pus_version == PusVersion.PUS_A:
-            tm_packet_raw.append(self.pack_subcounter)
-        else:
-            tm_packet_raw.append((self.pack_subcounter >> 8) & 0xff)
-            tm_packet_raw.append(self.pack_subcounter & 0xff)
-            tm_packet_raw.append((self.destination_id >> 8) & 0xff)
-            tm_packet_raw.append(self.destination_id & 0xff)
-        tm_packet_raw.extend(PusCdsShortTimestamp.pack_current_time())
-        # Source Data
-        tm_packet_raw.extend(self.source_data)
-        # CRC16 checksum
-        crc_func = crcmod.mkCrcFun(0x11021, rev=False, initCrc=0xFFFF, xorOut=0x0000)
-        crc16 = crc_func(tm_packet_raw)
-        tm_packet_raw.append((crc16 & 0xFF00) >> 8)
-        tm_packet_raw.append(crc16 & 0xFF)
-        return tm_packet_raw
-
-    def get_source_data_length(self, timestamp_len: int, pus_version: PusVersion) -> int:
-        """
-        Retrieve size of TM packet data header in bytes.
-        Formula according to PUS Standard: C = (Number of octets in packet source data field) - 1.
-        The size of the TM packet is the size of the packet secondary header with
-        the timestamp + the length of the application data + PUS timestamp size +
-        length of the CRC16 checksum - 1
-        """
-        try:
-            if pus_version == PusVersion.PUS_A:
-                data_length = PusTelemetryCreator.DATA_FIELD_HEADER_SIZE_WITHOUT_TIME_PUS_A + timestamp_len + \
-                              len(self.source_data) + 1
-            else:
-                data_length = PusTelemetryCreator.DATA_FIELD_HEADER_SIZE_WITHOUT_TIME_PUS_C + timestamp_len + \
-                              len(self.source_data) + 1
-            return data_length
-        except TypeError:
-            print("PusTelecommand: Invalid type of application data!")
-            return 0
diff --git a/src/tmtccmd/pus/__init__.py b/src/tmtccmd/pus/__init__.py
index e69de29b..20e7e224 100644
--- a/src/tmtccmd/pus/__init__.py
+++ b/src/tmtccmd/pus/__init__.py
@@ -0,0 +1,1 @@
+from tmtccmd.pus.obj_id import ObjectId
diff --git a/src/tmtccmd/pus/obj_id.py b/src/tmtccmd/pus/obj_id.py
new file mode 100644
index 00000000..aa4b0a36
--- /dev/null
+++ b/src/tmtccmd/pus/obj_id.py
@@ -0,0 +1,38 @@
+from __future__ import annotations
+import struct
+from tmtccmd.utility.logger import get_console_logger
+
+LOGGER = get_console_logger()
+
+
+class ObjectId:
+    def __init__(self, object_id: int):
+        self.object_id = object_id
+        self.id_as_bytes = bytearray()
+        self.set(object_id=object_id)
+
+    def set(self, object_id: int):
+        self.object_id = object_id
+        self.id_as_bytes = struct.pack('!I', self.object_id)
+
+    def set_from_bytes(self, obj_id_as_bytes: bytearray):
+        if len(obj_id_as_bytes) != 4:
+            LOGGER.warning(f'Invalid object ID length {len(obj_id_as_bytes)}')
+            raise ValueError
+        self.id_as_bytes = obj_id_as_bytes
+        self.object_id = struct.unpack('!I', self.id_as_bytes[:])[0]
+
+    @classmethod
+    def from_bytes(cls, obj_id_as_bytes: bytearray) -> ObjectId:
+        obj_id = ObjectId(object_id=0)
+        obj_id.set_from_bytes(obj_id_as_bytes=obj_id_as_bytes)
+        return obj_id
+
+    def get_id(self) -> int:
+        return self.object_id
+
+    def as_bytes(self) -> bytes:
+        return self.id_as_bytes
+
+    def as_string(self) -> str:
+        return f'0x{self.object_id:08x}'
diff --git a/src/tmtccmd/pus/service_5_event.py b/src/tmtccmd/pus/service_5_event.py
index 885f3e10..4965e9c8 100644
--- a/src/tmtccmd/pus/service_5_event.py
+++ b/src/tmtccmd/pus/service_5_event.py
@@ -1,7 +1,7 @@
 import enum
 
 
-class Severity(enum.IntEnum):
+class Srv5Severity(enum.IntEnum):
     INFO = 1
     LOW_SEVERITY = 2
     MEDIUM_SEVERITY = 3
@@ -9,9 +9,9 @@ class Severity(enum.IntEnum):
 
 
 class Srv5Subservices(enum.IntEnum):
-    INFO_EVENT = Severity.INFO,
-    LOW_SEVERITY_EVENT = Severity.LOW_SEVERITY,
-    MEDIUM_SEVERITY_EVENT = Severity.MEDIUM_SEVERITY,
-    HIGH_SEVERITY_EVENT = Severity.HIGH_SEVERITY,
+    INFO_EVENT = Srv5Severity.INFO,
+    LOW_SEVERITY_EVENT = Srv5Severity.LOW_SEVERITY,
+    MEDIUM_SEVERITY_EVENT = Srv5Severity.MEDIUM_SEVERITY,
+    HIGH_SEVERITY_EVENT = Srv5Severity.HIGH_SEVERITY,
     ENABLE_EVENT_REPORTING = 5,
     DISABLE_EVENT_REPORTING = 6
diff --git a/src/tmtccmd/pus/service_8_func_cmd.py b/src/tmtccmd/pus/service_8_func_cmd.py
new file mode 100644
index 00000000..226c4af6
--- /dev/null
+++ b/src/tmtccmd/pus/service_8_func_cmd.py
@@ -0,0 +1,6 @@
+import enum
+
+
+class Srv8Subservices(enum.IntEnum):
+    FUNCTIONAL_CMD = 128,
+    DATA_REPLY = 130
diff --git a/src/tmtccmd/pus/service_list.py b/src/tmtccmd/pus/service_list.py
index 4bf0592c..5f8158e3 100644
--- a/src/tmtccmd/pus/service_list.py
+++ b/src/tmtccmd/pus/service_list.py
@@ -10,3 +10,6 @@ class PusServices(IntEnum):
     SERVICE_8_FUNC_CMD = 8
     SERVICE_9_TIME_MGMT = 9
     SERVICE_17_TEST = 17
+    SERVICE_20_PARAMETER = 20
+    SERVICE_23_FILE_MGMT = 23
+    SERVICE_200_MODE = 200
diff --git a/src/tmtccmd/runner.py b/src/tmtccmd/runner.py
index 86838121..a937097e 100644
--- a/src/tmtccmd/runner.py
+++ b/src/tmtccmd/runner.py
@@ -1,11 +1,7 @@
-#!/usr/bin/env python3
-"""
-Core method called by entry point files to initiate the TMTC commander.
-The commander is started by first running `initialize_tmtc_commander` and then
-running `run_tmtc_commander`
-:details:
-:manual:
-:author:     R. Mueller
+"""Contains core methods called by entry point files to initiate the TMTC commander.
+
+The commander is started by first running :py:func:`initialize_tmtc_commander` and then
+running :py:func:`run_tmtc_commander`
 """
 import sys
 import os
@@ -26,6 +22,7 @@
 from tmtccmd.utility.logger import set_tmtc_console_logger, get_console_logger
 from tmtccmd.utility.conf_util import AnsiColors
 
+
 LOGGER = get_console_logger()
 
 
@@ -88,7 +85,7 @@ def run_tmtc_commander(
     :param tmtc_frontend:       Custom frontend can be passed here. Otherwise, a default backend
                                 will be created
     :param app_name:            Name of application. Will be displayed in GUI
-    :raises: ValueError if :py:func:`initialize_tmtc_commander` was not called before
+    :raises RunTimeError:  if :py:func:`initialize_tmtc_commander` was not called before
     :return:
     """
     try:
@@ -118,7 +115,7 @@ def __assign_tmtc_commander_hooks(hook_object: TmTcHookBase):
 
     # Check whether all required hook functions have bee implemented properly, Python
     # does not enforce this.
-    if hook_object.get_version is None or hook_object.add_globals_pre_args_parsing is None \
+    if hook_object.add_globals_pre_args_parsing is None \
             or hook_object.add_globals_post_args_parsing is None:
         LOGGER.error(
             "Passed hook base object handle is invalid. Abstract functions have to be implemented!"
@@ -160,7 +157,7 @@ def __set_up_tmtc_commander(
     hook_obj = cast(TmTcHookBase, hook_obj_raw)
 
     if not reduced_printout:
-        __handle_init_printout(use_gui, hook_obj.get_version(), ansi_colors)
+        __handle_init_printout(use_gui, ansi_colors)
 
     LOGGER.info("Starting TMTC Commander..")
 
@@ -170,7 +167,7 @@ def __set_up_tmtc_commander(
         __handle_cli_args_and_globals()
 
 
-def __handle_init_printout(use_gui: bool, version_string: str, ansi_colors: bool):
+def __handle_init_printout(use_gui: bool, ansi_colors: bool):
     if ansi_colors:
         print(f'{AnsiColors.CYAN}-- Python TMTC Commander --{AnsiColors.RESET}')
     if use_gui:
diff --git a/src/tmtccmd/sendreceive/tm_listener.py b/src/tmtccmd/sendreceive/tm_listener.py
index be7ad5f5..a5f51e43 100644
--- a/src/tmtccmd/sendreceive/tm_listener.py
+++ b/src/tmtccmd/sendreceive/tm_listener.py
@@ -336,11 +336,11 @@ def __route_packets(self, tm_packet_list: TelemetryListT):
             unknown_target_queue.appendleft(tm_packet)
 
     def __handle_ccsds_space_packet(self, tm_packet: bytearray) -> bool:
-        from tmtccmd.ccsds.spacepacket import get_apid_from_raw_packet
+        from tmtccmd.ccsds.spacepacket import get_apid_from_raw_space_packet
         if len(tm_packet) < 6:
             LOGGER.warning('TM packet to small to be a CCSDS space packet')
         else:
-            apid = get_apid_from_raw_packet(raw_packet=tm_packet)
+            apid = get_apid_from_raw_space_packet(raw_packet=tm_packet)
             target_queue_list = self.__queue_dict.get(apid)
             if target_queue_list is None:
                 LOGGER.warning(f'No TM handler assigned for APID {apid}')
@@ -351,4 +351,4 @@ def __handle_ccsds_space_packet(self, tm_packet: bytearray) -> bool:
                     target_queue.pop()
                 target_queue.appendleft(tm_packet)
                 return True
-        return False
\ No newline at end of file
+        return False
diff --git a/src/tmtccmd/tc/service_20_parameter.py b/src/tmtccmd/tc/service_20_parameter.py
index 2a689f7a..47387efb 100644
--- a/src/tmtccmd/tc/service_20_parameter.py
+++ b/src/tmtccmd/tc/service_20_parameter.py
@@ -1,3 +1,5 @@
+"""Contains definitions and functions related to PUS Service 20 Telecommands.
+"""
 from typing import Union
 
 from tmtccmd.ecss.tc import PusTelecommand
diff --git a/src/tmtccmd/tc/service_3_housekeeping.py b/src/tmtccmd/tc/service_3_housekeeping.py
index 69fd94c8..096c3e53 100644
--- a/src/tmtccmd/tc/service_3_housekeeping.py
+++ b/src/tmtccmd/tc/service_3_housekeeping.py
@@ -1,3 +1,5 @@
+"""Contains definitions and functions related to PUS Service 3 Telecommands.
+"""
 import enum
 import struct
 
diff --git a/src/tmtccmd/tc/service_5_event.py b/src/tmtccmd/tc/service_5_event.py
index cdccdb5b..1b30d9b4 100644
--- a/src/tmtccmd/tc/service_5_event.py
+++ b/src/tmtccmd/tc/service_5_event.py
@@ -1,3 +1,5 @@
+"""Contains definitions and functions related to PUS Service 5 Telecommands.
+"""
 from tmtccmd.config.globals import get_global_apid
 from tmtccmd.config.definitions import QueueCommands
 from tmtccmd.tc.definitions import PusTelecommand, TcQueueT
diff --git a/src/tmtccmd/tm/__init__.py b/src/tmtccmd/tm/__init__.py
index e69de29b..c98c0148 100644
--- a/src/tmtccmd/tm/__init__.py
+++ b/src/tmtccmd/tm/__init__.py
@@ -0,0 +1,10 @@
+from tmtccmd.tm.definitions import TelemetryListT, TelemetryQueueT
+
+from tmtccmd.tm.service_1_verification import Service1TM
+from tmtccmd.tm.service_5_event import Service5TM, Srv5Severity, Srv5Subservices
+from tmtccmd.tm.service_3_base import Service3Base
+from tmtccmd.tm.service_3_housekeeping import Service3TM
+from tmtccmd.tm.service_17_test import Service17TM
+from tmtccmd.tm.service_8_functional_cmd import Service8TM
+from tmtccmd.tm.service_23_file_mgmt import Service23TM
+from tmtccmd.ccsds.time import CdsShortTimestamp
diff --git a/src/tmtccmd/tm/base.py b/src/tmtccmd/tm/base.py
new file mode 100644
index 00000000..637c3e4a
--- /dev/null
+++ b/src/tmtccmd/tm/base.py
@@ -0,0 +1,63 @@
+from abc import abstractmethod
+from typing import Optional
+
+
+class PusTmInterface:
+
+    @abstractmethod
+    def pack(self) -> bytearray:
+        return bytearray()
+
+    @abstractmethod
+    def get_tm_data(self) -> bytearray:
+        return bytearray()
+
+    @abstractmethod
+    def is_valid(self) -> bool:
+        return False
+
+    @abstractmethod
+    def get_ssc(self) -> int:
+        return 0
+
+    @abstractmethod
+    def get_apid(self) -> int:
+        return 0
+
+    @abstractmethod
+    def get_service(self) -> int:
+        return -1
+
+    @abstractmethod
+    def get_subservice(self) -> int:
+        return -1
+
+
+class PusTmInfoInterface:
+    @abstractmethod
+    def get_print_info(self) -> str:
+        return ''
+
+    @abstractmethod
+    def get_custom_printout(self) -> str:
+        return ''
+
+    @abstractmethod
+    def return_source_data_string(self) -> str:
+        return ''
+
+    @abstractmethod
+    def specify_packet_info(self, print_info: str):
+        pass
+
+    @abstractmethod
+    def append_packet_info(self, info: str):
+        pass
+
+    @abstractmethod
+    def append_telemetry_column_headers(self, header_list: list):
+        pass
+
+    @abstractmethod
+    def append_telemetry_content(self, content_list: list):
+        pass
diff --git a/src/tmtccmd/tm/definitions.py b/src/tmtccmd/tm/definitions.py
index 7515969d..34996661 100644
--- a/src/tmtccmd/tm/definitions.py
+++ b/src/tmtccmd/tm/definitions.py
@@ -1,6 +1,7 @@
 import enum
-from typing import Deque, Tuple, List
+from typing import Deque, Tuple, List, Union
 from tmtccmd.ecss.tm import PusTelemetry
+from tmtccmd.tm.base import PusTmInfoInterface, PusTmInterface
 
 TelemetryListT = List[bytearray]
 TelemetryQueueT = Deque[bytearray]
@@ -10,6 +11,8 @@
 
 PusTmListT = List[PusTelemetry]
 PusTmQueueT = Deque[PusTmListT]
+PusIFListT = List[Union[PusTmInfoInterface, PusTmInterface]]
+PusIFQueueT = Deque[PusIFListT]
 
 PusTmListT = List[PusTelemetry]
 PusTmObjQeue = Deque[PusTelemetry]
diff --git a/src/tmtccmd/tm/service_1_verification.py b/src/tmtccmd/tm/service_1_verification.py
index 7f66274c..bea5cd88 100644
--- a/src/tmtccmd/tm/service_1_verification.py
+++ b/src/tmtccmd/tm/service_1_verification.py
@@ -1,26 +1,46 @@
 # -*- coding: utf-8 -*-
+"""Deserialize PUS Service 1 Verification TM
 """
-Program: tmtcc_tm_service1.py
-Date: 30.12.2019
-Description: Deserialize Pus Verification TM
-Author: R. Mueller
-"""
+from __future__ import annotations
+from abc import abstractmethod
 import struct
 from typing import Deque
 
-from tmtccmd.ecss.tm import PusTelemetry
-from tmtccmd.ecss.tm_creator import PusTelemetryCreator
+from tmtccmd.ecss.tm import PusTelemetry, PusVersion, PusTmInfoBase, PusTmBase
+from tmtccmd.ccsds.time import CdsShortTimestamp
 from tmtccmd.utility.logger import get_console_logger
 
 LOGGER = get_console_logger()
 
 
-class Service1TM(PusTelemetry):
-    """
-    Service 1 TM class representation. Can be used to deserialize raw service 1 packets.
+class Service1TM(PusTmBase, PusTmInfoBase):
+    """Service 1 TM class representation. Can be used to deserialize raw service 1 packets.
     """
-    def __init__(self, byte_array: bytearray):
-        super().__init__(byte_array)
+    def __init__(
+            self, subservice_id: int, time: CdsShortTimestamp = None,
+            tc_packet_id: int = 0, tc_psc: int = 0, ssc: int = 0,
+            source_data: bytearray = bytearray([]), apid: int = -1, packet_version: int = 0b000,
+            pus_version: PusVersion = PusVersion.UNKNOWN, pus_tm_version: int = 0b0001,
+            ack: int = 0b1111, secondary_header_flag: bool = True, space_time_ref: int = 0b0000,
+            destination_id: int = 0
+    ):
+        pus_tm = PusTelemetry(
+            service_id=1,
+            subservice_id=subservice_id,
+            time=time,
+            ssc=ssc,
+            source_data=source_data,
+            apid=apid,
+            packet_version=packet_version,
+            pus_version=pus_version,
+            pus_tm_version=pus_tm_version,
+            ack=ack,
+            secondary_header_flag=secondary_header_flag,
+            space_time_ref=space_time_ref,
+            destination_id=destination_id
+        )
+        PusTmBase.__init__(self, pus_tm=pus_tm)
+        PusTmInfoBase.__init__(self, pus_tm=pus_tm)
         self.has_tc_error_code = False
         self.is_step_reply = False
         # Failure Reports with error code
@@ -28,17 +48,38 @@ def __init__(self, byte_array: bytearray):
         self.step_number = 0
         self.error_param1 = 0
         self.error_param2 = 0
-        self.tc_packet_id = 0
-        self.tc_ssc = 0
-        if len(self._tm_data) < 4:
-            LOGGER.warning("Service1TM: TM data less than 4 bytes!")
-        self.tc_packet_id = self._tm_data[0] << 8 | self._tm_data[1]
-        self.tc_ssc = ((self._tm_data[2] & 0x3F) << 8) | self._tm_data[3]
-        if self.get_subservice() % 2 == 0:
-            self.__handle_failure_verification()
+        self.tc_packet_id = tc_packet_id
+        self.tc_psc = tc_psc
+        self.tc_ssc = tc_psc & 0x3fff
+
+    @classmethod
+    def __empty(cls):
+        return cls(
+            subservice_id=0
+        )
+
+    @classmethod
+    def unpack(
+            cls, raw_telemetry: bytearray, pus_version: PusVersion = PusVersion.UNKNOWN
+    ) -> Service1TM:
+        service_1_tm = cls.__empty()
+        service_1_tm.pus_tm = PusTelemetry.unpack(
+            raw_telemetry=raw_telemetry, pus_version=pus_version
+        )
+        tm_data = service_1_tm.get_tm_data()
+        if len(tm_data) < 4:
+            LOGGER.warning("TM data less than 4 bytes!")
+            raise ValueError
+        service_1_tm.tc_packet_id = tm_data[0] << 8 | tm_data[1]
+        service_1_tm.tc_psc = tm_data[2] << 8 | tm_data[3]
+        service_1_tm.tc_ssc = service_1_tm.tc_psc & 0x3fff
+        if service_1_tm.get_subservice() % 2 == 0:
+            service_1_tm.__handle_failure_verification()
         else:
-            self.__handle_success_verification()
+            service_1_tm.__handle_success_verification()
+        return service_1_tm
 
+    @abstractmethod
     def append_telemetry_content(self, content_list: list):
         super().append_telemetry_content(content_list=content_list)
         content_list.append(str(hex(self.tc_packet_id)))
@@ -52,6 +93,7 @@ def append_telemetry_content(self, content_list: list):
         elif self.is_step_reply:
             content_list.append(str(self.step_number))
 
+    @abstractmethod
     def append_telemetry_column_headers(self, header_list: list):
         super().append_telemetry_column_headers(header_list=header_list)
         header_list.append("TC Packet ID")
@@ -68,6 +110,7 @@ def append_telemetry_column_headers(self, header_list: list):
     def __handle_failure_verification(self):
         self.specify_packet_info("Failure Verficiation")
         self.has_tc_error_code = True
+        tm_data = self.get_tm_data()
         if self.get_subservice() == 2:
             self.append_packet_info(" : Acceptance failure")
         elif self.get_subservice() == 4:
@@ -75,29 +118,29 @@ def __handle_failure_verification(self):
         elif self.get_subservice() == 6:
             self.is_step_reply = True
             self.append_packet_info(" : Step Failure")
-            self.step_number = struct.unpack('>B', self._tm_data[4:5])[0]
-            self.err_code = struct.unpack('>H', self._tm_data[5:7])[0]
-            self.error_param1 = struct.unpack('>I', self._tm_data[7:11])[0]
-            self.error_param2 = struct.unpack('>I', self._tm_data[11:15])[0]
+            self.step_number = struct.unpack('>B', tm_data[4:5])[0]
+            self.err_code = struct.unpack('>H', tm_data[5:7])[0]
+            self.error_param1 = struct.unpack('>I', tm_data[7:11])[0]
+            self.error_param2 = struct.unpack('>I', tm_data[11:15])[0]
         elif self.get_subservice() == 8:
-            self.err_code = struct.unpack('>H', self._tm_data[4:6])[0]
-            self.error_param1 = struct.unpack('>I', self._tm_data[6:10])[0]
-            self.error_param2 = struct.unpack('>I', self._tm_data[10:14])[0]
+            self.err_code = struct.unpack('>H', tm_data[4:6])[0]
+            self.error_param1 = struct.unpack('>I', tm_data[6:10])[0]
+            self.error_param2 = struct.unpack('>I', tm_data[10:14])[0]
         else:
             LOGGER.error("Service1TM: Invalid subservice")
 
     def __handle_success_verification(self):
-        self.specify_packet_info("Success Verification")
+        self.pus_tm.specify_packet_info("Success Verification")
         if self.get_subservice() == 1:
-            self.append_packet_info(" : Acceptance success")
+            self.pus_tm.append_packet_info(" : Acceptance success")
         elif self.get_subservice() == 3:
-            self.append_packet_info(" : Start success")
+            self.pus_tm.append_packet_info(" : Start success")
         elif self.get_subservice() == 5:
             self.is_step_reply = True
-            self.append_packet_info(" : Step Success")
-            self.step_number = struct.unpack('>B', self._tm_data[4:5])[0]
+            self.pus_tm.append_packet_info(" : Step Success")
+            self.step_number = struct.unpack('>B', self.get_tm_data()[4:5])[0]
         elif self.get_subservice() == 7:
-            self.append_packet_info(" : Completion success")
+            self.pus_tm.append_packet_info(" : Completion success")
         else:
             LOGGER.error("Service1TM: Invalid subservice")
 
@@ -119,21 +162,4 @@ def get_step_number(self):
             return 0
 
 
-class Service1TmPacked(PusTelemetryCreator):
-    """
-    Class representation for Service 1 TM creation.
-    """
-    def __init__(self, subservice: int, ssc: int = 0, tc_packet_id: int = 0, tc_ssc: int = 0):
-        source_data = bytearray()
-        source_data.append((tc_packet_id & 0xFF00) >> 8)
-        source_data.append(tc_packet_id & 0xFF)
-        tc_psc = (tc_ssc & 0x3FFF) | (0b11 << 16)
-        source_data.append((tc_psc & 0xFF00) >> 8)
-        source_data.append(tc_psc & 0xFF)
-        super().__init__(service=1, subservice=subservice, ssc=ssc, source_data=source_data)
-
-    def pack(self) -> bytearray:
-        return super().pack()
-
-
 PusVerifQueue = Deque[Service1TM]
diff --git a/src/tmtccmd/tm/service_200_mode.py b/src/tmtccmd/tm/service_200_mode.py
index d09d8f0a..ae332872 100644
--- a/src/tmtccmd/tm/service_200_mode.py
+++ b/src/tmtccmd/tm/service_200_mode.py
@@ -1,36 +1,96 @@
+"""Base class for Service 200 mode commanding reply handling.
 """
-@brief  Base class for Service 200 mode commanding reply handling.
-"""
+from __future__ import annotations
 import struct
 
-from tmtccmd.ecss.tm import PusTelemetry
+from tmtccmd.pus.service_list import PusServices
+from tmtccmd.ecss.tm import PusTelemetry, PusTmBase, PusTmInfoBase, PusVersion, CdsShortTimestamp
+
 
+class Service200TM(PusTmBase, PusTmInfoBase):
+    def __init__(
+            self, subservice_id: int, object_id: bytearray,
+            return_value: int = 0, mode: int = 0, submode: int = 0,
+            time: CdsShortTimestamp = None, ssc: int = 0, source_data: bytearray = bytearray([]),
+            apid: int = -1, packet_version: int = 0b000,
+            pus_version: PusVersion = PusVersion.UNKNOWN, pus_tm_version: int = 0b0001,
+            ack: int = 0b1111, secondary_header_flag: bool = True,
+            space_time_ref: int = 0b0000, destination_id: int = 0
+    ):
+        source_data = bytearray()
+        source_data.extend(object_id)
+        if subservice_id == 7:
+            source_data.extend(struct.pack('!H', return_value))
+        elif subservice_id == 6 or subservice_id == 8:
+            source_data.extend(struct.pack('!I', mode))
+            source_data.append(submode)
+        pus_tm = PusTelemetry(
+            service_id=PusServices.SERVICE_200_MODE,
+            subservice_id=subservice_id,
+            time=time,
+            ssc=ssc,
+            source_data=source_data,
+            apid=apid,
+            packet_version=packet_version,
+            pus_version=pus_version,
+            pus_tm_version=pus_tm_version,
+            ack=ack,
+            secondary_header_flag=secondary_header_flag,
+            space_time_ref=space_time_ref,
+            destination_id=destination_id
+        )
+        PusTmBase.__init__(self, pus_tm=pus_tm)
+        PusTmInfoBase.__init__(self, pus_tm=pus_tm)
+        self.specify_packet_info("Mode Reply")
 
-class Service200TM(PusTelemetry):
-    def __init__(self, byte_array):
-        super().__init__(byte_array)
         self.is_cant_reach_mode_reply = False
         self.is_mode_reply = False
-        self.specify_packet_info("Mode Reply")
-        self.object_id = struct.unpack('>I', self._tm_data[0:4])[0]
-        if self.get_subservice() == 7:
-            self.append_packet_info(": Can't reach mode")
-            self.is_cant_reach_mode_reply = True
-            self.returnValue = self._tm_data[4] << 8 | self._tm_data[5]
-        elif self.get_subservice() == 6 or self.get_subservice() == 8:
-            self.is_mode_reply = True
-            if self.get_subservice() == 8:
-                self.append_packet_info(": Wrong Mode")
-            elif self.get_subservice() == 6:
-                self.append_packet_info(": Mode reached")
-            self.mode = struct.unpack('>I', self._tm_data[4:8])[0]
-            self.submode = self._tm_data[8]
+        self.object_id = object_id
+        self.mode = mode
+        self.submode = submode
+        self.return_value = return_value
+        self.__init_without_base(instance=self)
+
+    @staticmethod
+    def __init_without_base(instance: Service200TM):
+        tm_data = instance.get_tm_data()
+        instance.object_id = tm_data[0:4]
+        if instance.get_subservice() == 7:
+            instance.append_packet_info(": Can't reach mode")
+            instance.is_cant_reach_mode_reply = True
+            instance.return_value = tm_data[4] << 8 | tm_data[5]
+        elif instance.get_subservice() == 6 or instance.get_subservice() == 8:
+            instance.is_mode_reply = True
+            if instance.get_subservice() == 8:
+                instance.append_packet_info(": Wrong Mode")
+            elif instance.get_subservice() == 6:
+                instance.append_packet_info(": Mode reached")
+            instance.mode = struct.unpack('!I', tm_data[4:8])[0]
+            instance.submode = tm_data[8]
+
+    @classmethod
+    def __empty(cls) -> Service200TM:
+        return cls(
+            subservice_id=0, object_id=bytearray(4)
+        )
+
+    @classmethod
+    def unpack(
+            cls, raw_telemetry: bytearray, pus_version: PusVersion = PusVersion.UNKNOWN
+    ) -> Service200TM:
+        service_200_tm = cls.__empty()
+        service_200_tm.pus_tm = PusTelemetry.unpack(
+            raw_telemetry=raw_telemetry, pus_version=pus_version
+        )
+        service_200_tm.__init_without_base(instance=service_200_tm)
+        return service_200_tm
 
     def append_telemetry_content(self, content_list: list):
         super().append_telemetry_content(content_list=content_list)
-        content_list.append(hex(self.object_id))
+        object_id_num = struct.unpack('!I', self.object_id)[0]
+        content_list.append(f'0x{object_id_num:08x}')
         if self.is_cant_reach_mode_reply:
-            content_list.append(hex(self.returnValue))
+            content_list.append(hex(self.return_value))
         elif self.is_mode_reply:
             if self.mode == 1:
                 mode_string = "ON"
diff --git a/src/tmtccmd/tm/service_20_parameters.py b/src/tmtccmd/tm/service_20_parameters.py
index 586af248..26b474d6 100644
--- a/src/tmtccmd/tm/service_20_parameters.py
+++ b/src/tmtccmd/tm/service_20_parameters.py
@@ -1,64 +1,126 @@
+from __future__ import annotations
 import os
 import struct
 
-from tmtccmd.ecss.tm import PusTelemetry
+from tmtccmd.pus.service_list import PusServices
+from tmtccmd.ecss.tm import PusTelemetry, PusTmBase, PusTmInfoBase, PusVersion, CdsShortTimestamp
 from tmtccmd.utility.logger import get_console_logger
 
-logger = get_console_logger()
+LOGGER = get_console_logger()
 
 
-class Service20TM(PusTelemetry):
-    def __init__(self, byte_array):
-        super().__init__(byte_array)
-        data_size = len(self._tm_data)
-        self.objectId = 0
-        self.parameter_id = 0
-        self.domain_id = 0
-        self.unique_id = 0
-        self.linear_index = 0
+class ParamStruct:
+    def __init__(self):
+        self.param_id = 0,
+        self.domain_id = 0,
+        self.unique_id = 0,
+        self.linear_index = 0,
+        self.type_ptc = 0
+        self.type_pfc = 0
+        self.column = 0,
+        self.row = 0,
+        self.param: any = 0
 
+    def pack(self) -> bytearray:
+        pass
+
+    @classmethod
+    def unpack(cls, raw_param: bytearray) -> ParamStruct:
+        return cls()
+
+class Service20TM(PusTmInfoBase, PusTmBase):
+    def __init__(
+            self, subservice_id: int, object_id: bytearray, param_id: bytearray,
+            domain_id: int, unique_id: int, linear_index: int,
+            time: CdsShortTimestamp = None, ssc: int = 0,
+            source_data: bytearray = bytearray([]), apid: int = -1, packet_version: int = 0b000,
+            pus_version: PusVersion = PusVersion.UNKNOWN, pus_tm_version: int = 0b0001,
+            ack: int = 0b1111, secondary_header_flag: bool = True, space_time_ref: int = 0b0000,
+            destination_id: int = 0
+    ):
+        pus_tm = PusTelemetry(
+            service_id=PusServices.SERVICE_20_PARAMETER,
+            subservice_id=subservice_id,
+            time=time,
+            ssc=ssc,
+            source_data=source_data,
+            apid=apid,
+            packet_version=packet_version,
+            pus_version=pus_version,
+            pus_tm_version=pus_tm_version,
+            ack=ack,
+            secondary_header_flag=secondary_header_flag,
+            space_time_ref=space_time_ref,
+            destination_id=destination_id
+        )
+
+        self.object_id = object_id
+        self.param_struct = ParamStruct()
+        self.__init_without_base(instance=self)
+        self.specify_packet_info("Parameter Service Reply")
+
+    @staticmethod
+    def __init_without_base(instance: Service20TM):
+        tm_data = instance.get_tm_data()
+        data_size = len(tm_data)
         if data_size < 4:
-            logger.warning("Service20TM: Invalid data length, less than 4")
+            LOGGER.warning("Invalid data length, less than 4")
             return
         elif data_size < 8:
-            logger.warning("Service20TM: Invalid data length, less than 8 (Object ID and Parameter ID)")
+            LOGGER.warning("Invalid data length, less than 8 (Object ID and Parameter ID)")
             return
         else:
-            self.objectId = struct.unpack('!I', self._tm_data[0:4])[0]
-            self.parameter_id = struct.unpack('!I', self._tm_data[4:8])[0]
-            self.domain_id = self._tm_data[4]
-            self.unique_id = self._tm_data[5]
-            self.linear_index = self._tm_data[6] << 8 | self._tm_data[7]
+            instance.object_id = struct.unpack('!I', tm_data[0:4])[0]
+            instance.param_struct.param_id = struct.unpack('!I', tm_data[4:8])[0]
+            instance.param_struct.domain_id = tm_data[4]
+            instance.param_struct.unique_id = tm_data[5]
+            instance.param_struct.linear_index = tm_data[6] << 8 | tm_data[7]
 
-        self.param = 0
-        if self.get_subservice() == 130:
-            # TODO: This needs to be more generic. Furthermore, we need to be able to handle vector and matrix
-            #       dumps as well and this is not possible in the current form.
-            self.type = struct.unpack('!H', self._tm_data[8:10])[0]
-            self.type_ptc = self._tm_data[8]
-            self.type_pfc = self._tm_data[9]
-            self.column = self._tm_data[10]
-            self.row = self._tm_data[11]
-            if len(self._tm_data) > 12:
-                if self.type_ptc == 3 and self.type_pfc == 14:
-                    self.param = struct.unpack('!I', self._tm_data[12:16])[0]
-                if self.type_ptc == 4 and self.type_pfc == 14:
-                    self.param = struct.unpack('!i', self._tm_data[12:16])[0]
-                if self.type_ptc == 5 and self.type_pfc == 1:
-                    self.param = struct.unpack('!f', self._tm_data[12:16])[0]
+        if instance.get_subservice() == 130:
+            # TODO: This needs to be more generic. Furthermore, we need to be able to handle
+            #       vector and matrix dumps as well and this is not possible in the current form.
+            instance.param_struct.type_ptc = tm_data[8]
+            instance.param_struct.type_pfc = tm_data[9]
+            instance.param_struct.column = tm_data[10]
+            instance.param_struct.row = tm_data[11]
+            # TODO: Maybe this should be moved to the ParamStruct class instead
+            if data_size > 12:
+                if instance.param_struct.type_ptc == 3 and instance.param_struct.type_pfc == 14:
+                    instance.param_struct.param = struct.unpack('!I', tm_data[12:16])[0]
+                if instance.param_struct.type_ptc == 4 and instance.param_struct.type_pfc == 14:
+                    instance.param_struct.param = struct.unpack('!i', tm_data[12:16])[0]
+                if instance.param_struct.type_ptc == 5 and instance.param_struct.type_pfc == 1:
+                    instance.param_struct.param = struct.unpack('!f', tm_data[12:16])[0]
         else:
-            logger.info(
+            LOGGER.info(
                 "Error when receiving Pus Service 20 TM: subservice is not 130"
             )
-        self.specify_packet_info("Parameter Service Reply")
+
+    @classmethod
+    def __empty(cls) -> Service20TM:
+        return cls(
+            subservice_id=-1,
+            object_id=bytearray(4),
+            param_id=0,
+            domain_id=0,
+            unique_id=0,
+            linear_index=0
+        )
+
+    @classmethod
+    def unpack(
+            cls, raw_telemetry: bytearray, pus_version: PusVersion = PusVersion.UNKNOWN
+    ) -> Service20TM:
+        service_20_tm = cls.__empty()
+        service_20_tm.pus_tm = PusTelemetry.unpack(
+            raw_telemetry=raw_telemetry, pus_version=pus_version
+        )
+        service_20_tm.__init_without_base(instance=service_20_tm)
+        return service_20_tm
 
     def append_telemetry_content(self, content_list: list):
         super().append_telemetry_content(content_list=content_list)
-        content_list.append(hex(self.objectId))
-        # array.append(f"{self.parameter_id:#010x}")
-        content_list.append(self.domain_id)
-        content_list.append(self.unique_id)
-        content_list.append(self.linear_index)
+        content_list.append(hex(self.object_id))
 
     def append_telemetry_column_headers(self, header_list: list):
         super().append_telemetry_column_headers(header_list=header_list)
diff --git a/src/tmtccmd/tm/service_23_file_mgmt.py b/src/tmtccmd/tm/service_23_file_mgmt.py
new file mode 100644
index 00000000..ff5c8951
--- /dev/null
+++ b/src/tmtccmd/tm/service_23_file_mgmt.py
@@ -0,0 +1,138 @@
+from __future__ import annotations
+import struct
+
+from tmtccmd.pus.service_list import PusServices
+from tmtccmd.ecss.tm import PusTelemetry, PusTmBase, PusTmInfoBase, CdsShortTimestamp, PusVersion
+from tmtccmd.utility.logger import get_console_logger
+
+LOGGER = get_console_logger()
+
+
+class FileInfo:
+    def __init__(
+            self, file_name: str, repo_path: str, file_size: int = 0, lock_status: bool = False
+    ):
+        self.file_name = file_name
+        self.repo_path = repo_path
+        self.file_size = file_size
+        self.lock_status = lock_status
+
+
+class Service23TM(PusTmInfoBase, PusTmBase):
+    MAX_REPOSITORY_LENGTH = 64
+    MAX_FILENAME_LENGTH = 12
+
+    def __init__(
+            self, subservice_id: int,
+            object_id: bytearray, repo_path: str, file_name: str,
+            time: CdsShortTimestamp = None, ssc: int = 0,
+            source_data: bytearray = bytearray([]), apid: int = -1, packet_version: int = 0b000,
+            pus_version: PusVersion = PusVersion.UNKNOWN, pus_tm_version: int = 0b0001,
+            ack: int = 0b1111, secondary_header_flag: bool = True, space_time_ref: int = 0b0000,
+            destination_id: int = 0
+    ):
+        self.object_id = object_id
+        self.data_start_idx = 0
+        self.file_info = FileInfo(file_name=file_name, repo_path=repo_path)
+        pus_tm = PusTelemetry(
+            service_id=PusServices.SERVICE_23_FILE_MGMT,
+            subservice_id=subservice_id,
+            time=time,
+            ssc=ssc,
+            source_data=source_data,
+            apid=apid,
+            packet_version=packet_version,
+            pus_version=pus_version,
+            pus_tm_version=pus_tm_version,
+            ack=ack,
+            secondary_header_flag=secondary_header_flag,
+            space_time_ref=space_time_ref,
+            destination_id=destination_id
+        )
+        PusTmBase.__init__(self, pus_tm=pus_tm)
+        PusTmInfoBase.__init__(self, pus_tm=pus_tm)
+
+        self.specify_packet_info("File Service Reply")
+
+    @staticmethod
+    def __init_without_base(instance: Service23TM):
+        tm_data = instance.get_tm_data()
+        if len(tm_data) < 4:
+            LOGGER.error("Service23TM: Invalid packet format!")
+            return
+        instance.object_id = struct.unpack('!I', tm_data[0:4])[0]
+        instance.file_info.file_size = 0
+        instance.file_info.lock_status = False
+        instance.data_start_idx = 0
+        if instance.get_subservice() == 4:
+            instance.unpack_repo_and_filename()
+            instance.unpack_file_attributes()
+        elif instance.get_subservice() == 132:
+            instance.unpack_repo_and_filename()
+
+    @classmethod
+    def __empty(cls) -> Service23TM:
+        return cls(
+            subservice_id=-1, object_id=bytearray(4), repo_path="", file_name=""
+        )
+
+    @classmethod
+    def unpack(
+            cls, raw_telemetry: bytearray, pus_version: PusVersion = PusVersion.UNKNOWN
+    ) -> Service23TM:
+        service_23_tm = cls.__empty()
+        service_23_tm.pus_tm = PusTelemetry.unpack(
+            raw_telemetry=raw_telemetry, pus_version=pus_version
+        )
+        service_23_tm.__init_without_base(instance=service_23_tm)
+        return service_23_tm
+
+    def unpack_repo_and_filename(self):
+        tm_data = self.get_tm_data()
+        repo_path_found = False
+        path_idx_start = 0
+        max_len_to_scan = len(self.get_tm_data()) - 4
+        for idx in range(4, max_len_to_scan):
+            if not repo_path_found and tm_data[idx] == 0:
+                repo_bytes = tm_data[4:idx]
+                self.file_info.repo_path = repo_bytes.decode('utf-8')
+                path_idx_start = idx + 1
+                idx += 1
+                repo_path_found = True
+            if repo_path_found:
+                if tm_data[idx] == 0:
+                    filename_bytes = tm_data[path_idx_start:idx]
+                    self.file_info.file_name = filename_bytes.decode('utf-8')
+                    self.data_start_idx = idx + 1
+                    break
+
+    def unpack_file_attributes(self):
+        # Size of file length (4) + lock status (1), adapt if more field are added!
+        print(len(self.get_tm_data()) - self.data_start_idx)
+        if len(self.get_tm_data()) - self.data_start_idx != 5:
+            LOGGER.error("Service23TM: Invalid lenght of file attributes data")
+            return
+        self.file_info.file_size = struct.unpack(
+            '!I', self.get_tm_data()[self.data_start_idx: self.data_start_idx + 4])[0]
+        self.file_info.lock_status = self.get_tm_data()[self.data_start_idx + 4]
+
+    def append_telemetry_content(self, content_list: list):
+        super().append_telemetry_content(content_list)
+        content_list.append(self.object_id)
+        content_list.append(self.file_info.repo_path)
+        content_list.append(self.file_info.file_name)
+        if self.get_subservice() == 4:
+            content_list.append(self.file_info.file_size)
+            if self.file_info.lock_status == 0:
+                content_list.append("No")
+            else:
+                content_list.append("Yes")
+
+    def append_telemetry_column_headers(self, header_list: list):
+        super().append_telemetry_column_headers(header_list)
+        header_list.append("Object ID")
+        header_list.append("Repo Path")
+        header_list.append("File Name")
+        if self.get_subservice() == 4:
+            header_list.append("File Size")
+            header_list.append("Locked")
diff --git a/src/tmtccmd/tm/service_2_raw_cmd.py b/src/tmtccmd/tm/service_2_raw_cmd.py
index 943c2971..53e4feee 100644
--- a/src/tmtccmd/tm/service_2_raw_cmd.py
+++ b/src/tmtccmd/tm/service_2_raw_cmd.py
@@ -1,14 +1,52 @@
+"""Base class for implementation of PUS Service 2 handling.
 """
-@brief  Base class for implementation of PUS Service 2 handling.
-"""
-from tmtccmd.ecss.tm import PusTelemetry
+from __future__ import annotations
+from tmtccmd.ecss.tm import PusTmInfoBase, PusTmBase, CdsShortTimestamp, PusVersion, PusTelemetry
 
 
-class Service2TM(PusTelemetry):
-    def __init__(self, byte_array: bytearray):
-        super().__init__(byte_array)
+class Service2TM(PusTmInfoBase, PusTmBase):
+    def __init__(
+            self, subservice_id: int, time: CdsShortTimestamp = None, ssc: int = 0,
+            source_data: bytearray = bytearray([]), apid: int = -1, packet_version: int = 0b000,
+            pus_version: PusVersion = PusVersion.UNKNOWN, pus_tm_version: int = 0b0001,
+            ack: int = 0b1111, secondary_header_flag: bool = True, space_time_ref: int = 0b0000,
+            destination_id: int = 0
+    ):
+        pus_tm = PusTelemetry(
+            service_id=2,
+            subservice_id=subservice_id,
+            time=time,
+            ssc=ssc,
+            source_data=source_data,
+            apid=apid,
+            packet_version=packet_version,
+            pus_version=pus_version,
+            pus_tm_version=pus_tm_version,
+            ack=ack,
+            secondary_header_flag=secondary_header_flag,
+            space_time_ref=space_time_ref,
+            destination_id=destination_id
+        )
+        PusTmInfoBase.__init__(self, pus_tm=pus_tm)
+        PusTmBase.__init__(self, pus_tm=pus_tm)
         self.specify_packet_info("Raw Commanding Reply")
 
+    @classmethod
+    def __empty(cls) -> Service2TM:
+        return cls(
+            subservice_id=0
+        )
+
+    @classmethod
+    def unpack(
+            cls, raw_telemetry: bytearray, pus_version: PusVersion = PusVersion.UNKNOWN
+    ) -> Service2TM:
+        service_2_tm = cls.__empty()
+        service_2_tm.pus_tm = PusTelemetry.unpack(
+            raw_telemetry=raw_telemetry, pus_version=pus_version
+        )
+        return service_2_tm
+
     def append_telemetry_content(self, content_list: list):
         super().append_telemetry_content(content_list=content_list)
         return
diff --git a/src/tmtccmd/tm/service_3_base.py b/src/tmtccmd/tm/service_3_base.py
index 9ae75198..7e098142 100644
--- a/src/tmtccmd/tm/service_3_base.py
+++ b/src/tmtccmd/tm/service_3_base.py
@@ -1,6 +1,7 @@
 from typing import Tuple, List
 import enum
 
+from tmtccmd.pus import ObjectId
 from tmtccmd.ecss.tm import PusTelemetry
 
 
@@ -9,7 +10,7 @@ class HkContentType(enum.Enum):
     DEFINITIONS = enum.auto()
 
 
-class Service3Base(PusTelemetry):
+class Service3Base:
     """Base class. The TMTC core provides a Service 3 implementation which is intended to be used
     with the FSFW. However, users can define an own Service 3 implementation.
 
@@ -17,25 +18,24 @@ class Service3Base(PusTelemetry):
     if a custom class is defined, the user should implement this class and fill the fields
     in the TM handling hook if printout of the HK field and validity checking is desired.
     """
-    def __init__(self, raw_telemetry: bytearray, custom_hk_handling: bool = False):
-        super().__init__(raw_telemetry)
-        self._object_id_bytes = bytearray()
-        self._object_id = 0
+    def __init__(self, object_id: int, custom_hk_handling: bool = False):
+        self._object_id = ObjectId(object_id=object_id)
         self._set_id = 0
+        self._param_length = 0
         self._custom_hk_handling = custom_hk_handling
 
-    def get_object_id(self) -> int:
+    def get_object_id(self) -> ObjectId:
         return self._object_id
 
-    def get_object_id_bytes(self) -> bytes:
-        return self._object_id_bytes
-
     def get_set_id(self) -> int:
         return self._set_id
 
     def has_custom_hk_handling(self) -> bool:
         return self._custom_hk_handling
 
+    def set_custom_hk_handling(self, custom_hk_handling: bool):
+        self._custom_hk_handling = custom_hk_handling
+
     def get_hk_definitions_list(self) -> Tuple[List, List]:
         """Can be implemented by a child class to print definitions lists. The first list
         should contain a header with parameter names, and the second list shall contain the
diff --git a/src/tmtccmd/tm/service_3_housekeeping.py b/src/tmtccmd/tm/service_3_housekeeping.py
index 1f3748ec..6c6cb89d 100644
--- a/src/tmtccmd/tm/service_3_housekeeping.py
+++ b/src/tmtccmd/tm/service_3_housekeeping.py
@@ -1,14 +1,13 @@
 # -*- coding: utf-8 -*-
+"""PUS Service 3 components
 """
-@file       service_3_housekeeping.py
-@date       07.04.2021
-@details    Deserialize Housekeeping TM
-@author     R. Mueller
-"""
+from __future__ import annotations
+from abc import abstractmethod
 import struct
 
-from tmtccmd.ecss.tm import PusTelemetry
-from tmtccmd.tm.service_3_base import Service3Base
+from tmtccmd.pus import ObjectId
+from tmtccmd.ecss.tm import PusTelemetry, PusTmInfoBase, PusTmBase, CdsShortTimestamp, PusVersion
+from tmtccmd.tm import Service3Base
 from tmtccmd.utility.logger import get_console_logger
 from typing import Type, Tuple, List
 
@@ -16,7 +15,7 @@
 LOGGER = get_console_logger()
 
 
-class Service3TM(Service3Base):
+class Service3TM(Service3Base, PusTmBase, PusTmInfoBase):
     """This class encapsulates the format of Service 3 telemetry
     This class was written to handle Service 3 telemetry coming from the on-board software
     based on the Flight Software Framework (FSFW). A custom class can be defined, but should then
@@ -28,67 +27,140 @@ class Service3TM(Service3Base):
     # collection interval as float (4) and number of parameters(1)
     STRUCTURE_REPORT_FIXED_HEADER_SIZE = DEFAULT_MINIMAL_PACKET_SIZE + 7
 
-    def __init__(self, byte_array: bytearray, custom_hk_handling: bool = False,
-                 minimum_reply_size: int = DEFAULT_MINIMAL_PACKET_SIZE,
-                 minimum_structure_report_header_size: int = STRUCTURE_REPORT_FIXED_HEADER_SIZE):
+    def __init__(
+            self, subservice_id: int, time: CdsShortTimestamp, hk_data: bytearray,
+            custom_hk_handling: bool = False, ssc: int = 0, apid: int = -1,
+            minimum_reply_size: int = DEFAULT_MINIMAL_PACKET_SIZE,
+            minimum_structure_report_header_size: int = STRUCTURE_REPORT_FIXED_HEADER_SIZE,
+            packet_version: int = 0b000, pus_version: PusVersion = PusVersion.UNKNOWN,
+            pus_tm_version: int = 0b0001, ack: int = 0b1111, secondary_header_flag: bool = True,
+            space_time_ref: int = 0b0000, destination_id: int = 0
+    ):
         """Service 3 packet class representation which can be built from a raw bytearray
-        :param byte_array:
+        :param subservice_id:
+        :param time:
+        :param hk_data:
         :param custom_hk_handling:  Can be used if a custom HK format is used which does not
                                     use a 8 byte structure ID (SID).
         :param minimum_reply_size:
         :param minimum_structure_report_header_size:
         """
-        super().__init__(raw_telemetry=byte_array, custom_hk_handling=custom_hk_handling)
-        if len(self._tm_data) < 8:
+        Service3Base.__init__(self, object_id=0, custom_hk_handling=custom_hk_handling)
+        source_data = bytearray()
+        source_data.extend(struct.pack('!I', self.get_object_id().get_id()))
+        source_data.extend(struct.pack('!I', self._set_id))
+        if subservice_id == 25 or subservice_id == 26:
+            source_data.extend(hk_data)
+        pus_tm = PusTelemetry(
+            service_id=3,
+            subservice_id=subservice_id,
+            time=time,
+            ssc=ssc,
+            source_data=source_data,
+            apid=apid,
+            packet_version=packet_version,
+            pus_version=pus_version,
+            pus_tm_version=pus_tm_version,
+            ack=ack,
+            secondary_header_flag=secondary_header_flag,
+            space_time_ref=space_time_ref,
+            destination_id=destination_id
+        )
+        PusTmBase.__init__(self, pus_tm=pus_tm)
+        PusTmInfoBase.__init__(self, pus_tm=pus_tm)
+        self.__init_without_base(
+            instance=self, custom_hk_handling=custom_hk_handling,
+            minimum_reply_size=minimum_reply_size,
+            minimum_structure_report_header_size=minimum_structure_report_header_size,
+            check_tm_data_size=False
+        )
+
+    @staticmethod
+    def __init_without_base(
+            instance: Service3TM, custom_hk_handling: bool,
+            check_tm_data_size: bool,
+            minimum_reply_size: int = DEFAULT_MINIMAL_PACKET_SIZE,
+            minimum_structure_report_header_size: int = STRUCTURE_REPORT_FIXED_HEADER_SIZE
+    ):
+        instance.set_custom_hk_handling(custom_hk_handling=custom_hk_handling)
+        if instance.has_custom_hk_handling():
+            return
+        tm_data = instance.get_tm_data()
+        if len(tm_data) < 8:
             LOGGER.warning(
-                "Service3TM: handle_filling_definition_arrays: Invalid Service 3 packet,"
-                "is too short!"
+                f'Invalid Service 3 packet, is too short. Detected TM data length: {len(tm_data)}'
             )
-            return
-        self.min_hk_reply_size = minimum_reply_size
-        self.hk_structure_report_header_size = minimum_structure_report_header_size
-        self._object_id_bytes = self._tm_data[0:4]
-        self._object_id = struct.unpack('!I', self._object_id_bytes)[0]
-        self._set_id = struct.unpack('!I', self._tm_data[4:8])[0]
+            raise ValueError
+        instance.min_hk_reply_size = minimum_reply_size
+        instance.hk_structure_report_header_size = minimum_structure_report_header_size
+        instance.get_object_id().set_from_bytes(obj_id_as_bytes=tm_data[0:4])
+        instance._set_id = struct.unpack('!I', tm_data[4:8])[0]
+        if instance.get_subservice() == 25 or instance.get_subservice() == 26:
+            if len(tm_data) > 8:
+                instance._param_length = len(tm_data[8:])
+        instance.specify_packet_info("Housekeeping Packet")
+
+    @classmethod
+    def __empty(cls) -> Service3TM:
+        return cls(
+            subservice_id=-1,
+            time=CdsShortTimestamp.init_from_current_time(),
+            hk_data=bytearray()
+        )
 
-        self.specify_packet_info("Housekeeping Packet")
-        self.param_length = 0
+    @classmethod
+    def unpack(
+            cls, raw_telemetry: bytearray, custom_hk_handling: bool,
+            pus_version: PusVersion = PusVersion.UNKNOWN,
+    ) -> Service3TM:
+        service_3_tm = cls.__empty()
+        service_3_tm.pus_tm = PusTelemetry.unpack(
+            raw_telemetry=raw_telemetry, pus_version=pus_version
+        )
+        service_3_tm.__init_without_base(
+            instance=service_3_tm, custom_hk_handling=custom_hk_handling,
+            check_tm_data_size=True
+        )
+        return service_3_tm
 
+    @abstractmethod
     def append_telemetry_content(self, content_list: list):
         super().append_telemetry_content(content_list=content_list)
-        content_list.append(hex(self._object_id))
+        content_list.append(self.get_object_id().as_string())
         content_list.append(hex(self._set_id))
-        content_list.append(int(self.param_length))
+        content_list.append(int(self._param_length))
 
+    @abstractmethod
     def append_telemetry_column_headers(self, header_list: list):
         super().append_telemetry_column_headers(header_list=header_list)
         header_list.append("Object ID")
         header_list.append("Set ID")
-        header_list.append("HK Data Size")
+        header_list.append("HK data size")
 
     def get_hk_definitions_list(self) -> Tuple[List, List]:
-        if len(self._tm_data) < self.hk_structure_report_header_size:
+        tm_data = self.get_tm_data()
+        if len(tm_data) < self.hk_structure_report_header_size:
             LOGGER.warning(
                 f'Service3TM: handle_filling_definition_arrays: Invalid structure report '
-                f'from {hex(self._object_id)}, is shorter '
+                f'from {self.get_object_id().as_string()}, is shorter '
                 f'than {self.hk_structure_report_header_size}'
             )
-            return
+            return [], []
         definitions_header = [
             "Object ID", "Set ID", "Report Status", "Is valid", "Collection Interval (s)",
             "Number Of IDs"
         ]
-        reporting_enabled = self._tm_data[8]
-        set_valid = self._tm_data[9]
-        collection_interval_seconds = struct.unpack('>f', self._tm_data[10:14])[0] / 1000.0
-        num_params = self._tm_data[14]
-        if len(self._tm_data) < self.hk_structure_report_header_size + num_params * 4:
+        reporting_enabled = tm_data[8]
+        set_valid = tm_data[9]
+        collection_interval_seconds = struct.unpack('!f', tm_data[10:14])[0] / 1000.0
+        num_params = tm_data[14]
+        if len(tm_data) < self.hk_structure_report_header_size + num_params * 4:
             LOGGER.warning(
                 f'Service3TM: handle_filling_definition_arrays: Invalid structure report '
-                f'from {hex(self.get_object_id())}, is shorter than '
+                f'from {self.get_object_id().as_string()}, is shorter than '
                 f'{self.hk_structure_report_header_size + num_params * 4}'
             )
-            return
+            return [], []
 
         parameters = []
         counter = 1
@@ -96,8 +168,8 @@ def get_hk_definitions_list(self) -> Tuple[List, List]:
                 self.hk_structure_report_header_size,
                 self.hk_structure_report_header_size + 4 * num_params, 4
         ):
-            parameter = struct.unpack('>I', self._tm_data[array_index:array_index + 4])[0]
-            self.hk_header.append("Pool ID " + str(counter))
+            parameter = struct.unpack('!I', tm_data[array_index:array_index + 4])[0]
+            definitions_header.append("Pool ID " + str(counter))
             parameters.append(str(hex(parameter)))
             counter = counter + 1
         if reporting_enabled == 1:
@@ -109,11 +181,8 @@ def get_hk_definitions_list(self) -> Tuple[List, List]:
         else:
             valid_string = "No"
         definitions_content = [
-            hex(self.get_object_id()), self._set_id, status_string, valid_string,
+            self.get_object_id().as_string(), self._set_id, status_string, valid_string,
             collection_interval_seconds, num_params
         ]
         definitions_content.extend(parameters)
         return definitions_header, definitions_content
-
-
-Service3TM: Type[PusTelemetry]
diff --git a/src/tmtccmd/tm/service_5_event.py b/src/tmtccmd/tm/service_5_event.py
index 6fd28105..343a5b52 100644
--- a/src/tmtccmd/tm/service_5_event.py
+++ b/src/tmtccmd/tm/service_5_event.py
@@ -1,60 +1,97 @@
 # -*- coding: utf-8 -*-
+"""Contains classes and functions to deserialize PUS Service 5 Telemetry
 """
-:file:      service_5_event.py
-:date:      30.12.2019
-:brief:     Deserialize PUS Event Report
-:author:    R. Mueller
-"""
+from __future__ import annotations
+from abc import abstractmethod
 import struct
+
 from tmtccmd.pus.service_list import PusServices
-from tmtccmd.ecss.tm import PusTelemetry
-from tmtccmd.ecss.tm_creator import PusTelemetryCreator
-from tmtccmd.pus.service_5_event import Srv5Subservices, Severity
+from tmtccmd.ecss.tm import PusTelemetry, PusVersion, CdsShortTimestamp, PusTmInfoBase, PusTmBase
+from tmtccmd.pus.service_5_event import Srv5Subservices, Srv5Severity
+from tmtccmd.pus.obj_id import ObjectId
 from tmtccmd.utility.logger import get_console_logger
 
 
 LOGGER = get_console_logger()
 
 
-class Service5TM(PusTelemetry):
-    def __init__(self, byte_array, call_srv5_hook: bool = True):
-        """
-        Deserialize a raw service 5 packet
-        :param byte_array:      Raw bytearray to deserialize, containing the service 5 packet.
-        :param call_srv5_hook:  Calls the global hook function to retrieve a custom printout for Service 5 packets.
+class Service5TM(PusTmBase, PusTmInfoBase):
+    def __init__(
+            self, subservice_id: Srv5Subservices, event_id: int, object_id: bytearray,
+            param_1: int, param_2: int, time: CdsShortTimestamp = None,
+            ssc: int = 0, apid: int = -1, packet_version: int = 0b000,
+            pus_version: PusVersion = PusVersion.UNKNOWN,
+            pus_tm_version: int = 0b0001, ack: int = 0b1111, secondary_header_flag: bool = True,
+            space_time_ref: int = 0b0000, destination_id: int = 0
+    ):
+        """Create a Service 5 telemetry instance.
+        Use the unpack function to create an instance from a raw bytestream instead.
+        :param subservice_id: Subservice ID
+        :param time: CDS Short Timecode
+        :param object_id: 4 byte object ID
+        :raises ValueError: Invalid input arguments
         """
-        super().__init__(raw_telemetry=byte_array)
-        if self.get_service() != 5:
-            LOGGER.warning("This packet is not an event service packet!")
-
-        self.specify_packet_info("Event")
-        if self.get_subservice() == Srv5Subservices.INFO_EVENT:
-            self.append_packet_info(" Info")
-        elif self.get_subservice() == Srv5Subservices.LOW_SEVERITY_EVENT:
-            self.append_packet_info(" Error Low Severity")
-        elif self.get_subservice() == Srv5Subservices.MEDIUM_SEVERITY_EVENT:
-            self.append_packet_info(" Error Med Severity")
-        elif self.get_subservice() == Srv5Subservices.HIGH_SEVERITY_EVENT:
-            self.append_packet_info(" Error High Severity")
-        self.event_id = struct.unpack('>H', self._tm_data[0:2])[0]
-        self.object_id = struct.unpack('>I', self._tm_data[2:6])[0]
-        self.param_1 = struct.unpack('>I', self._tm_data[6:10])[0]
-        self.param_2 = struct.unpack('>I', self._tm_data[10:14])[0]
-        self.custom_service_5_print = ""
-        if call_srv5_hook:
-            from tmtccmd.config.hook import get_global_hook_obj
-            hook_obj = get_global_hook_obj()
-            self.custom_service_5_print = hook_obj.handle_service_5_event(
-                object_id=self._tm_data[2:6], event_id=self.event_id, param_1=self.param_1, param_2=self.param_2
-            )
-
+        self._object_id = ObjectId.from_bytes(obj_id_as_bytes=object_id)
+        self._event_id = event_id
+        self._param_1 = param_1
+        self._param_2 = param_2
+        source_data = bytearray()
+        source_data.extend(struct.pack('!H', self._event_id))
+        if len(object_id) != 4:
+            LOGGER.warning('Object ID must be a bytrarray with length 4')
+            raise ValueError
+        source_data.extend(object_id)
+        source_data.extend(struct.pack('!I', self._param_1))
+        source_data.extend(struct.pack('!I', self._param_2))
+        pus_tm = PusTelemetry(
+            service_id=PusServices.SERVICE_5_EVENT,
+            subservice_id=subservice_id,
+            time=time,
+            ssc=ssc,
+            source_data=source_data,
+            apid=apid,
+            packet_version=packet_version,
+            pus_version=pus_version,
+            pus_tm_version=pus_tm_version,
+            ack=ack,
+            secondary_header_flag=secondary_header_flag,
+            space_time_ref=space_time_ref,
+            destination_id=destination_id
+        )
+        PusTmBase.__init__(self, pus_tm=pus_tm)
+        PusTmInfoBase.__init__(self, pus_tm=pus_tm)
+        self.__init_without_base(instance=self, set_attrs_from_tm_data=False)
+
+    @classmethod
+    def __empty(cls) -> Service5TM:
+        return cls(
+            subservice_id=Srv5Subservices.INFO_EVENT,
+            event_id=0,
+            object_id=bytearray(4),
+            param_1=0,
+            param_2=0
+        )
+
+    @classmethod
+    def unpack(
+            cls, raw_telemetry: bytearray, pus_version: PusVersion = PusVersion.UNKNOWN
+    ) -> Service5TM:
+        service_5_tm = cls.__empty()
+        service_5_tm.pus_tm = PusTelemetry.unpack(
+            raw_telemetry=raw_telemetry, pus_version=pus_version
+        )
+        service_5_tm.__init_without_base(instance=service_5_tm)
+        return service_5_tm
+
+    @abstractmethod
     def append_telemetry_content(self, content_list: list):
         super().append_telemetry_content(content_list=content_list)
-        content_list.append(str(self.event_id))
-        content_list.append(hex(self.object_id))
-        content_list.append(str(hex(self.param_1)) + ", " + str(self.param_1))
-        content_list.append(str(hex(self.param_2)) + ", " + str(self.param_2))
+        content_list.append(str(self._event_id))
+        content_list.append(self._object_id.as_string())
+        content_list.append(str(hex(self._param_1)) + ", " + str(self._param_1))
+        content_list.append(str(hex(self._param_2)) + ", " + str(self._param_2))
 
+    @abstractmethod
     def append_telemetry_column_headers(self, header_list: list):
         super().append_telemetry_column_headers(header_list=header_list)
         header_list.append("Event ID")
@@ -62,57 +99,40 @@ def append_telemetry_column_headers(self, header_list: list):
         header_list.append("Parameter 1")
         header_list.append("Parameter 2")
 
-    def handle_service_5_event(self, object_id: bytearray, event_id: int, param_1: int, param_2: int) -> str:
-        try:
-            from tmtccmd.config.hook import get_global_hook_obj
-            hook_obj = get_global_hook_obj()
-            custom_string = hook_obj.handle_service_5_event(
-                    object_id=self.object_id, event_id=event_id, param_1=param_1, param_2=param_2
-                )
-            return custom_string
-        except ImportError:
-            LOGGER.warning("Service 5 user data hook not supplied!")
-        return ""
+    def get_reporter_id_as_bytes(self) -> bytes:
+        return self._object_id.as_bytes()
 
-    def get_custom_printout(self) -> str:
-        return self.custom_service_5_print
-
-    def set_custom_printout(self, custom_printout: str):
-        self.custom_service_5_print = custom_printout
-
-    def get_reporter_id(self):
-        return self.object_id
+    def get_reporter_id(self) -> int:
+        return self._object_id.get_id()
 
     def get_event_id(self):
-        return self.event_id
+        return self._event_id
 
     def get_param_1(self):
-        return self.param_1
+        return self._param_1
 
     def get_param_2(self):
-        return self.param_2
-
+        return self._param_2
 
-class Service5TmPacked(PusTelemetryCreator):
-    """
-    Class representation for Service 5 TM creation.
-    """
-    def __init__(
-            self, severity: Severity, event_id: int, object_id: bytearray = bytearray(),
-            param_1: int = 0, param_2: int = 0, ssc: int = 0
-    ):
-        self.event_id = event_id
-        self.object_id = object_id
-        self.param_1 = param_1
-        self.param_2 = param_2
-        source_data = bytearray()
-        source_data.extend(struct.pack('!H', self.event_id))
-        source_data.extend(object_id)
-        source_data.extend(struct.pack('!I', self.param_1))
-        source_data.extend(struct.pack('!I', self.param_2))
-        super().__init__(
-            service=PusServices.SERVICE_5_EVENT, subservice=severity, ssc=ssc,
-            source_data=source_data)
-
-    def pack(self) -> bytearray:
-        return super().pack()
+    @staticmethod
+    def __init_without_base(instance: Service5TM, set_attrs_from_tm_data: bool = False):
+        if instance.get_service() != 5:
+            LOGGER.warning("This packet is not an event service packet!")
+        instance.specify_packet_info("Event")
+        if instance.get_subservice() == Srv5Subservices.INFO_EVENT:
+            instance.append_packet_info(" Info")
+        elif instance.get_subservice() == Srv5Subservices.LOW_SEVERITY_EVENT:
+            instance.append_packet_info(" Error Low Severity")
+        elif instance.get_subservice() == Srv5Subservices.MEDIUM_SEVERITY_EVENT:
+            instance.append_packet_info(" Error Med Severity")
+        elif instance.get_subservice() == Srv5Subservices.HIGH_SEVERITY_EVENT:
+            instance.append_packet_info(" Error High Severity")
+        tm_data = instance.get_tm_data()
+        if len(tm_data) < 14:
+            LOGGER.warning(f'Length of TM data field {len(tm_data)} shorter than expected 14 bytes')
+            raise ValueError
+        if set_attrs_from_tm_data:
+            instance._event_id = struct.unpack('>H', tm_data[0:2])[0]
+            instance._object_id.set_from_bytes(tm_data[2:6])
+            instance._param_1 = struct.unpack('>I', tm_data[6:10])[0]
+            instance._param_2 = struct.unpack('>I', tm_data[10:14])[0]
diff --git a/src/tmtccmd/tm/service_8_functional_cmd.py b/src/tmtccmd/tm/service_8_functional_cmd.py
index fd5dcc01..7588fee6 100644
--- a/src/tmtccmd/tm/service_8_functional_cmd.py
+++ b/src/tmtccmd/tm/service_8_functional_cmd.py
@@ -1,49 +1,103 @@
+"""Contains classes and functions to handle PUS Service 8 telemetry.
 """
-@brief  Base class for Service 8 (Functional Commanding) Telemetry handling.
-"""
+from __future__ import annotations
 import struct
 
-from tmtccmd.ecss.tm import PusTelemetry
+from tmtccmd.ecss.tm import PusTelemetry, CdsShortTimestamp, PusTmInfoBase, PusTmBase, PusVersion
+from tmtccmd.pus import ObjectId
 from tmtccmd.utility.logger import get_console_logger
 
 LOGGER = get_console_logger()
 
 
-class Service8TM(PusTelemetry):
-    def __init__(self, byte_array, call_srv8_hook: bool = True):
-        super().__init__(byte_array)
-        self.source_object_id = 0
-        self.source_action_id = 0
-        self.custom_data = bytearray()
-        self.custom_data_header = []
-        self.custom_data_content = []
-        if self.get_subservice() == 130:
-            self.specify_packet_info("Functional Data Reply")
-            self.object_id_bytes = self.get_tm_data()[0:4]
-            self.source_object_id = struct.unpack('!I', self.object_id_bytes)[0]
-            self.source_action_id = struct.unpack('!I', self.get_tm_data()[4:8])[0]
-            self.custom_data = self.get_tm_data()[8:]
+class Service8TM(PusTmBase, PusTmInfoBase):
+    def __init__(
+            self, subservice_id: int, object_id: bytearray, action_id: int,
+            custom_data: bytearray, time: CdsShortTimestamp = None, ssc: int = 0,
+            source_data: bytearray = bytearray([]), apid: int = -1, packet_version: int = 0b000,
+            pus_version: PusVersion = PusVersion.UNKNOWN, pus_tm_version: int = 0b0001,
+            ack: int = 0b1111, secondary_header_flag: bool = True, space_time_ref: int = 0b0000,
+            destination_id: int = 0
+    ):
+        """This class can be used to deserialize service 8 packets.
+        :param raw_telemetry:      Raw bytearray which will be deserialized
+        :param call_srv8_hook:
+        :raises ValueError: If the length of the passed bytearray is too short.
+        """
+        self._object_id = ObjectId.from_bytes(obj_id_as_bytes=object_id)
+        self._action_id = action_id
+        self._custom_data = custom_data
+        source_data = bytearray()
+        source_data.extend(self._object_id.as_bytes())
+        source_data.extend(struct.pack('!I', self._action_id))
+        source_data.extend(self._custom_data)
+        pus_tm = PusTelemetry(
+            service_id=5,
+            subservice_id=subservice_id,
+            time=time,
+            ssc=ssc,
+            source_data=source_data,
+            apid=apid,
+            packet_version=packet_version,
+            pus_version=pus_version,
+            pus_tm_version=pus_tm_version,
+            ack=ack,
+            secondary_header_flag=secondary_header_flag,
+            space_time_ref=space_time_ref,
+            destination_id=destination_id
+        )
+        PusTmBase.__init__(self, pus_tm=pus_tm)
+        PusTmInfoBase.__init__(self, pus_tm=pus_tm)
+        self.__init_without_base(instance=self)
+
+    @staticmethod
+    def __init_without_base(instance: Service8TM):
+        if instance.get_subservice() == 130:
+            tm_data = instance.get_tm_data()
+            if len(tm_data) < 8:
+                LOGGER.warning(f'Length of Service 8 TM data field {len(tm_data)} short than 8')
+                raise ValueError
+            instance.specify_packet_info("Functional Data Reply")
+            instance._object_id_bytes = tm_data[0:4]
+            instance._object_id = struct.unpack('!I', instance._object_id_bytes)[0]
+            instance._action_id = struct.unpack('!I', tm_data[4:8])[0]
+            instance._custom_data = tm_data[8:]
         else:
-            self.specify_packet_info("Functional Commanding Reply")
-        if call_srv8_hook:
-            try:
-                from tmtccmd.config.hook import get_global_hook_obj
-                hook_obj = get_global_hook_obj()
-                self.custom_data_header, self.custom_data_content = \
-                    hook_obj.handle_service_8_telemetry(
-                        object_id=self.object_id_bytes, action_id=self.source_action_id,
-                        custom_data=self.custom_data
-                    )
-            except ImportError:
-                LOGGER.warning("Service 8 user data hook not supplied!")
+            instance.specify_packet_info("Unknown functional commanding reply")
+
+    @classmethod
+    def __empty(cls) -> Service8TM:
+        return cls(
+            subservice_id=-1, object_id=bytearray(4), action_id=0, custom_data=bytearray()
+        )
+
+    @classmethod
+    def unpack(cls, raw_telemetry: bytearray, pus_version: PusVersion = PusVersion.UNKNOWN):
+        service_8_tm = cls.__empty()
+        service_8_tm.pus_tm = PusTelemetry.unpack(
+            raw_telemetry=raw_telemetry, pus_version=pus_version
+        )
+        service_8_tm.__init_without_base(instance=service_8_tm)
+        return service_8_tm
 
     def append_telemetry_content(self, content_list: list):
         super().append_telemetry_content(content_list=content_list)
-        content_list.append(hex(self.source_object_id))
-        content_list.append(self.source_action_id)
-        return
+        content_list.append(self._object_id.as_string())
+        content_list.append(self._action_id)
 
     def append_telemetry_column_headers(self, header_list: list):
         super().append_telemetry_column_headers(header_list=header_list)
         header_list.append("Object ID")
         header_list.append("Action ID")
+
+    def get_source_object_id_as_bytes(self) -> bytes:
+        return self._object_id.as_bytes()
+
+    def get_source_object_id(self) -> int:
+        return self._object_id.get_id()
+
+    def get_action_id(self) -> int:
+        return self._action_id
+
+    def get_custom_data(self) -> bytearray:
+        return self._custom_data
diff --git a/src/tmtccmd/utility/conf_util.py b/src/tmtccmd/utility/conf_util.py
index 2a2fa9c9..275d9fea 100644
--- a/src/tmtccmd/utility/conf_util.py
+++ b/src/tmtccmd/utility/conf_util.py
@@ -23,8 +23,7 @@ class AnsiColors:
 def check_args_in_dict(
         param: any, iterable: Union[collections.abc.Iterable, dict], warning_hint: str
 ) -> Tuple[bool, int]:
-    """
-    This functions checks whether the integer representation of a given parameter in
+    """This functions checks whether the integer representation of a given parameter in
     contained within the passed collections, for example an (integer) enumeration.
     Please note that if the passed parameter has a string representation but is a digit,
     this function will attempt to check whether the integer representation is contained
@@ -48,29 +47,40 @@ def check_args_in_dict(
         LOGGER.warning(f"No {warning_hint} argument passed.")
         return False, 0
 
+    res_tuple = False, 0
     if isinstance(iterable, dict):
         for idx, enum_value in iterable.items():
             if param == enum_value:
                 return True, idx
     else:
-        param_list = list()
-        for idx, enum_value in enumerate(iterable):
-            if isinstance(enum_value.value, str):
-                # Make this case insensitive
-                param_list.append(enum_value.value.lower())
-            else:
-                param_list.append(enum_value.value)
-        if param not in param_list:
-            if might_be_integer:
-                if int(param) in param_list:
-                    return True, int(param)
-            return False, 0
-    return True, param
+        res_tuple = __handle_iterable_non_dict(
+            param=param, iterable=iterable, might_be_integer=might_be_integer,
+            init_res_tuple=res_tuple
+        )
+    return res_tuple
+
+
+def __handle_iterable_non_dict(
+        param: any, iterable: collections.abc.Iterable, might_be_integer: bool,
+        init_res_tuple: Tuple[bool, any]
+) -> (bool, any):
+    param_list = list()
+    for idx, enum_value in enumerate(iterable):
+        if isinstance(enum_value.value, str):
+            # Make this case insensitive
+            param_list.append(enum_value.value.lower())
+        else:
+            param_list.append(enum_value.value)
+    if param not in param_list:
+        if might_be_integer:
+            if int(param) in param_list:
+                return True, int(param)
+        return False, 0
+    return init_res_tuple
 
 
 def print_core_globals():
-    """
-    Prints an imporant set of global parameters. Can be used for debugging function
+    """Prints an imporant set of global parameters. Can be used for debugging function
     or as an optional information output
     :return:
     """
@@ -83,6 +93,12 @@ def print_core_globals():
 
 @contextmanager
 def acquire_timeout(lock, timeout):
+    """Helper functions which allows to check result of the acquire operation while also
+    using the context manager.
+    :param lock:
+    :param timeout:
+    :return:
+    """
     result = lock.acquire(timeout=timeout)
     yield result
     if result:
diff --git a/src/tmtccmd/utility/dle_encoder.py b/src/tmtccmd/utility/dle_encoder.py
deleted file mode 100644
index b7246985..00000000
--- a/src/tmtccmd/utility/dle_encoder.py
+++ /dev/null
@@ -1,101 +0,0 @@
-"""
-@brief      DLE Encoder Implementation
-@details
-DLE encoding can be used to provide a simple transport layer for serial data.
-A give data stream is encoded by adding a STX char at the beginning and an ETX char at the end.
-All STX and ETX occurences in the packet are encoded as well so the receiver can simply look
-for STX and ETX occurences to identify packets.
-"""
-
-import enum
-from typing import Tuple
-
-STX_CHAR = 0x02
-ETX_CHAR = 0x03
-CARRIAGE_RETURN = 0x0D
-DLE_CHAR = 0x10
-
-
-class DleErrorCodes(enum.Enum):
-    OK = 0,
-    DECODING_ERROR = 1
-
-
-def encode_dle(source_packet: bytearray, add_stx_etx: bool = True) -> bytearray:
-    """Encodes a given stream with DLE encoding.
-    :return: Returns a tuple with 2 values.
-        1. DleErrorCode, which should be DleErrorCodes.OK for successful encoding
-        2. Encoded bytearray
-    """
-    dest_stream = bytearray()
-    source_len = len(source_packet)
-    source_index = 0
-    if add_stx_etx:
-        dest_stream.append(STX_CHAR)
-
-    while source_index < source_len:
-        next_byte = source_packet[source_index]
-        # STX, ETX and CR characters in the stream need to be escaped with DLE
-        if next_byte == STX_CHAR or next_byte == ETX_CHAR or next_byte == CARRIAGE_RETURN:
-            dest_stream.append(DLE_CHAR)
-            """
-            Escaped byte will be actual byte + 0x40. This prevents
-            STX and ETX characters from appearing
-            in the encoded data stream at all, so when polling an
-            encoded stream, the transmission can be stopped at ETX.
-            0x40 was chosen at random with special requirements:
-            - Prevent going from one control char to another
-            - Prevent overflow for common characters
-            """
-            dest_stream.append(next_byte + 0x40)
-        elif next_byte == DLE_CHAR:
-            dest_stream.append(DLE_CHAR)
-            dest_stream.append(DLE_CHAR)
-        else:
-            dest_stream.append(next_byte)
-        source_index += 1
-    if add_stx_etx:
-        dest_stream.append(ETX_CHAR)
-    return dest_stream
-
-
-def decode_dle(source_packet: bytearray) -> Tuple[DleErrorCodes, bytearray, int]:
-    """
-    Decodes a given DLE encoded data stream. This call only returns the first packet found.
-    It might be necessary to call this function multiple times, depending on the third
-    returnvalue
-    :return:
-    Returns a tuple of three values.
-        1. DleErrorCode: If decoding has failed, this will not be DleErrorCodes.OK
-        2. Decoded bytearray: Decoded packet
-        3. Read length: Read length in the encoded stream. If this is smaller than the length of
-           the passed bytearray, the decoding function should be called again.
-    """
-    encoded_index = 0
-    source_len = len(source_packet)
-    dest_stream = bytearray()
-    if source_packet[encoded_index] != STX_CHAR:
-        return DleErrorCodes.DECODING_ERROR, dest_stream, 0
-    encoded_index += 1
-
-    while encoded_index < source_len and source_packet[encoded_index] != ETX_CHAR \
-            and source_packet[encoded_index] != STX_CHAR:
-        if source_packet[encoded_index] == DLE_CHAR:
-            next_byte = source_packet[encoded_index + 1]
-            if next_byte == DLE_CHAR:
-                dest_stream.append(next_byte)
-            else:
-                if next_byte == 0x42 or next_byte == 0x43 or next_byte == 0x4D:
-                    dest_stream.append(next_byte - 0x40)
-                else:
-                    return DleErrorCodes.DECODING_ERROR, dest_stream, 0
-            encoded_index += 1
-        else:
-            dest_stream.append(source_packet[encoded_index])
-        encoded_index += 1
-
-    if source_packet[encoded_index] != ETX_CHAR:
-        return DleErrorCodes.DECODING_ERROR, dest_stream, 0
-    else:
-        encoded_index += 1
-        return DleErrorCodes.OK, dest_stream, encoded_index
diff --git a/src/tmtccmd/utility/hammingcode.py b/src/tmtccmd/utility/hammingcode.py
index 26295eea..88b3f8c3 100644
--- a/src/tmtccmd/utility/hammingcode.py
+++ b/src/tmtccmd/utility/hammingcode.py
@@ -56,8 +56,7 @@ class HammingReturnCodes(Enum):
 
 
 def hamming_compute_256x(data: bytearray) -> bytearray:
-    """
-    Computes 3-bytes hamming codes for a data block whose size is multiple of
+    """Computes 3-bytes hamming codes for a data block whose size is multiple of
     256 bytes. Each 256 bytes block gets its own code.
     :param data: Data to compute code for. Should be a multiple of 256 bytes, pad data with 0
     if necessary!
@@ -116,9 +115,8 @@ def hamming_verify_256x(data: bytearray, original_hamming_code: bytearray) -> Ha
 
 
 def hamming_compute_256(data: bytearray) -> bytearray:
-    """
-    Takes a bytearray with the size of 256 bytes and calculates the 22 parity bits for the hamming
-    code which will be returned as a three byte bytearray.
+    """Takes a bytearray with the size of 256 bytes and calculates the 22 parity bits for the
+    hamming code which will be returned as a three byte bytearray.
     :param data:
     :return:
     """
@@ -225,8 +223,7 @@ def hamming_compute_256(data: bytearray) -> bytearray:
 
 
 def hamming_verify_256(data: bytearray, original_hamming_code: bytearray) -> HammingReturnCodes:
-    """
-    Verifies and corrects a 256-bytes block of data using the given 22-bits hamming code.
+    """Verifies and corrects a 256-bytes block of data using the given 22-bits hamming code.
     Returns 0 if there is no error, otherwise returns a HAMMING_ERROR code.
     :param data: 256 code block to verify
     :param original_hamming_code: Original 3 byte hamming code with 22 parity bits
@@ -294,9 +291,7 @@ def hamming_verify_256(data: bytearray, original_hamming_code: bytearray) -> Ham
 
 
 def hamming_test():
-    """
-    Algorithm was verified with this  simple test.
-    @return:
+    """Algorithm was verified with this  simple test.
     """
     test_data = bytearray(256)
     for index in range(128):
diff --git a/src/tmtccmd/utility/json_handler.py b/src/tmtccmd/utility/json_handler.py
index e9836919..790c857c 100644
--- a/src/tmtccmd/utility/json_handler.py
+++ b/src/tmtccmd/utility/json_handler.py
@@ -7,41 +7,41 @@
 
 
 class JsonKeyNames(enum.Enum):
-    COM_IF = "COM_IF_KEY"
-    TCPIP_UDP_DEST_IP_ADDRESS = "TCPIP_UDP_DEST_IP_ADDRESS"
-    TCPIP_UDP_DEST_PORT = "TCPIP_UDP_DEST_PORT"
-    TCPIP_UDP_RECV_IP_ADDRESS = "TCPIP_UDP_RECV_IP_ADDRESS"
-    TCPIP_UDP_RECV_PORT = "TCPIP_UDP_RECV_PORT"
-    TCPIP_UDP_RECV_MAX_SIZE = "TCPIP_UDP_RECV_MAX_SIZE"
+    COM_IF = 'com_if'
+    TCPIP_UDP_DEST_IP_ADDRESS = 'tcpip_udp_ip_addr'
+    TCPIP_UDP_DEST_PORT = 'tcpip_udp_port'
+    TCPIP_UDP_RECV_IP_ADDRESS = 'tcpip_udp_recv_addr'
+    TCPIP_UDP_RECV_PORT = 'tcpip_udp_recv_port'
+    TCPIP_UDP_RECV_MAX_SIZE = 'tcpip_udp_recv_max_size'
 
-    TCPIP_TCP_DEST_IP_ADDRESS = "TCPIP_TCP_DEST_IP_ADDRESS"
-    TCPIP_TCP_DEST_PORT = "TCPIP_TCP_DEST_PORT"
-    TCPIP_TCP_RECV_MAX_SIZE = "TCPIP_UDP_RECV_MAX_SIZE"
+    TCPIP_TCP_DEST_IP_ADDRESS = 'tcpip_tcp_ip_addr'
+    TCPIP_TCP_DEST_PORT = 'tcpip_tcp_port'
+    TCPIP_TCP_RECV_MAX_SIZE = 'tcpip_tcp_recv_max_size'
 
-    SERIAL_BAUDRATE = "SERIAL_BAUDRATE"
-    SERIAL_PORT = "SERIAL_PORT"
+    SERIAL_BAUDRATE = 'serial_baudrate'
+    SERIAL_PORT = 'serial_port'
+    SERIAL_HINT = 'serial_hint'
 
 
 def check_json_file(json_cfg_path: str) -> bool:
-    """
-    The check JSON file and return whether it was valid or not. A JSON file is invalid
+    """The check JSON file and return whether it was valid or not. A JSON file is invalid
     if it does not exist or the format ins invalid.
     :return: True if JSON file is valid, False if not and a new one was created at the specified path
     """
-    if json_cfg_path == "":
-        json_cfg_path = "tmtc_config.json"
+    if json_cfg_path == '':
+        json_cfg_path = 'tmtc_config.json'
     if not os.path.isfile(json_cfg_path):
-        with open(json_cfg_path, "w") as file:
+        with open(json_cfg_path, 'w') as file:
             load_data = dict()
             json.dump(load_data, file)
-            print(f"Configuration JSON {json_cfg_path} did not exist, created a new one.")
+            print(f'Configuration JSON {json_cfg_path} did not exist, created a new one.')
             return False
     else:
-        with open(json_cfg_path, "r+") as file:
+        with open(json_cfg_path, 'r+') as file:
             try:
                 json.load(file)
             except json.decoder.JSONDecodeError:
-                LOGGER.warning("JSON decode error, file format might be invalid. Replacing JSON")
+                LOGGER.warning('JSON decode error, file format might be invalid. Replacing JSON')
                 file.flush()
                 file.truncate(0)
                 file.seek(0)
@@ -49,3 +49,18 @@ def check_json_file(json_cfg_path: str) -> bool:
                 json.dump(void_data, file)
                 return False
     return True
+
+
+def save_to_json_with_prompt(
+        key: str, value: any, name: str, json_cfg_path: str, json_obj: any
+) -> bool:
+    logger = get_console_logger()
+    save_to_json = input(
+        f'Do you want to store the {name} to the configuration file? (y/n): '
+    )
+    if save_to_json.lower() in ['y', 'yes']:
+        json_obj[key] = value
+        logger.info(f'The {name} was stored to the JSON file {json_cfg_path}')
+        logger.info('Delete this file or edit it manually to change it')
+        return True
+    return False
diff --git a/src/tmtccmd/utility/tmtc_printer.py b/src/tmtccmd/utility/tmtc_printer.py
index 9136b398..6671b4a6 100644
--- a/src/tmtccmd/utility/tmtc_printer.py
+++ b/src/tmtccmd/utility/tmtc_printer.py
@@ -1,17 +1,15 @@
-"""
-:file:      tmtc_printer.py
-:date:      04.05.2021
-:brief:     Class that performs all printing functionalities
+"""Contains classes and functions that perform all printing functionalities.
 """
 import os
 import enum
 from typing import cast
 
 from tmtccmd.ecss.tc import PusTelecommand
-from tmtccmd.ecss.tm import PusTelemetry
-from tmtccmd.tm.service_8_functional_cmd import Service8TM
-from tmtccmd.tm.service_5_event import Service5TM
-from tmtccmd.tm.definitions import PusTmListT
+from tmtccmd.tm import Service8TM, Service5TM
+from tmtccmd.pus.service_list import PusServices
+from tmtccmd.tm.base import PusTmInfoInterface, PusTmInterface
+from tmtccmd.pus.service_8_func_cmd import Srv8Subservices
+from tmtccmd.tm.definitions import PusIFQueueT
 from tmtccmd.tm.service_3_base import Service3Base, HkContentType
 from tmtccmd.utility.logger import get_console_logger
 
@@ -26,8 +24,10 @@ class DisplayMode(enum.Enum):
 
 class TmTcPrinter:
     """This class handles printing to the command line and to files."""
-    def __init__(self, display_mode: DisplayMode = DisplayMode.LONG, do_print_to_file: bool = True,
-                 print_tc: bool = True):
+    def __init__(
+            self, display_mode: DisplayMode = DisplayMode.LONG, do_print_to_file: bool = True,
+            print_tc: bool = True
+    ):
         """
         :param display_mode:
         :param do_print_to_file: if true, print to file
@@ -48,83 +48,155 @@ def set_display_mode(self, display_mode: DisplayMode):
     def get_display_mode(self) -> DisplayMode:
         return self._display_mode
 
-    def print_telemetry_queue(self, tm_queue: PusTmListT):
+    def print_telemetry_queue(self, tm_queue: PusIFQueueT):
         """Print the telemetry queue which should contain lists of TM class instances."""
         for tm_list in tm_queue:
             for tm_packet in tm_list:
-                self.print_telemetry(tm_packet)
+                self.print_telemetry(packet_if=tm_packet, info_if=tm_packet)
 
-    def print_telemetry(self, packet: PusTelemetry, print_raw_tm: bool = False):
+    def print_telemetry(
+            self, packet_if: PusTmInterface, info_if: PusTmInfoInterface,
+            print_raw_tm: bool = False
+    ):
         """This function handles printing telemetry
-        :param packet:          Object representation of TM packet to print.
-                                Must be a subclass of PusTelemetry.
+        :param packet_if:       Core interface to work with PUS packets
+        :param info_if:         Core interface to get custom data from PUS packets
         :param print_raw_tm:    Specify whether the TM packet is printed in a raw way.
         :return:
         """
-        if not isinstance(packet, PusTelemetry):
-            LOGGER.warning("Passed packet is not instance of PusTelemetry!")
+        if not isinstance(packet_if, PusTmInterface) or \
+                not isinstance(info_if, PusTmInfoInterface):
+            LOGGER.warning("Passed packet does not implement necessary interfaces!")
             return
 
-        if packet.get_service() == 5:
-            self.__handle_event_packet(cast(Service5TM, packet))
+        if packet_if.get_service() == PusServices.SERVICE_5_EVENT:
+            self.__handle_event_packet(cast(Service5TM, packet_if))
 
         if self._display_mode == DisplayMode.SHORT:
-            self.__handle_short_print(packet)
+            self.__handle_short_print(packet_if)
         else:
-            self.__handle_long_tm_print(packet)
-        self.__handle_wiretapping_packet(packet)
+            self.__handle_long_tm_print(packet_if=packet_if, info_if=info_if)
+        self.__handle_wiretapping_packet(packet_if=packet_if, info_if=info_if)
 
         # Handle special packet types
-        if packet.get_service() == 8 and packet.get_subservice() == 130:
-            self.__handle_data_reply_packet(cast(Service8TM, packet))
-        if packet.get_service() == 3:
-            self.handle_service_3_packet(packet=packet)
+        if packet_if.get_service() == PusServices.SERVICE_3_HOUSEKEEPING:
+            self.handle_service_3_packet(packet_if=packet_if)
+        if packet_if.get_service() == PusServices.SERVICE_5_EVENT:
+            self.handle_service_5_packet(packet_if=packet_if)
+        if packet_if.get_service() == PusServices.SERVICE_8_FUNC_CMD and \
+                packet_if.get_subservice() == Srv8Subservices.DATA_REPLY:
+            self.handle_service_8_packet(packet_if=packet_if)
 
         if print_raw_tm:
-            self.__print_buffer = f"TM Data:\n{self.return_data_string(packet.get_raw_packet())}"
+            self.__print_buffer = f"TM Data:\n{self.return_data_string(packet_if.pack())}"
             LOGGER.info(self.__print_buffer)
             self.add_print_buffer_to_file_buffer()
 
-    def handle_service_3_packet(self, packet: PusTelemetry):
+    def handle_service_3_packet(self, packet_if: PusTmInterface):
         from tmtccmd.config.hook import get_global_hook_obj
-        if packet.get_service() != 3:
+        if packet_if.get_service() != 3:
             LOGGER.warning('This packet is not a service 3 packet!')
             return
         hook_obj = get_global_hook_obj()
         if hook_obj is None:
             LOGGER.warning('Hook object not set')
             return
-        srv3_packet = cast(Service3Base, packet)
+        srv3_packet = cast(Service3Base, packet_if)
         if srv3_packet.has_custom_hk_handling():
             (hk_header, hk_content, validity_buffer, num_vars) = \
                 hook_obj.handle_service_3_housekeeping(
-                object_id=0, set_id=srv3_packet.get_set_id(), hk_data=srv3_packet.get_tm_data(),
-                service3_packet=srv3_packet
+                object_id=bytes(), set_id=srv3_packet.get_set_id(),
+                hk_data=packet_if.get_tm_data(), service3_packet=srv3_packet
             )
         else:
             (hk_header, hk_content, validity_buffer, num_vars) = \
                 hook_obj.handle_service_3_housekeeping(
-                object_id=srv3_packet.get_object_id_bytes(), set_id=srv3_packet.get_set_id(),
-                hk_data=srv3_packet.get_tm_data()[8:], service3_packet=srv3_packet
+                object_id=srv3_packet.get_object_id().as_bytes(), set_id=srv3_packet.get_set_id(),
+                hk_data=packet_if.get_tm_data()[8:], service3_packet=srv3_packet
             )
-        if packet.get_subservice() == 25 or packet.get_subservice() == 26:
+        if packet_if.get_subservice() == 25 or packet_if.get_subservice() == 26:
             self.handle_hk_print(
-                object_id=srv3_packet.get_object_id(), set_id=srv3_packet.get_set_id(),
-                hk_header=hk_header, hk_content=hk_content
+                object_id=srv3_packet.get_object_id().get_id(), set_id=srv3_packet.get_set_id(),
+                hk_header=hk_header, hk_content=hk_content, validity_buffer=validity_buffer,
+                num_vars=num_vars
             )
-        if packet.get_subservice() == 10 or packet.get_subservice() == 12:
+        if packet_if.get_subservice() == 10 or packet_if.get_subservice() == 12:
             self.handle_hk_definition_print(
-                object_id=srv3_packet.get_object_id(), set_id=srv3_packet.get_set_id(),
+                object_id=srv3_packet.get_object_id().get_id(), set_id=srv3_packet.get_set_id(),
                 srv3_packet=srv3_packet
             )
 
+    def handle_service_5_packet(self, packet_if: PusTmInterface):
+        from tmtccmd.config.hook import get_global_hook_obj
+        if packet_if.get_service() != 5:
+            LOGGER.warning('This packet is not a service 5 packet!')
+            return
+        hook_obj = get_global_hook_obj()
+        if hook_obj is None:
+            LOGGER.warning('Hook object not set')
+            return
+        srv5_packet = cast(Service5TM, packet_if)
+        custom_string = hook_obj.handle_service_5_event(
+            object_id=srv5_packet.get_reporter_id_as_bytes(), event_id=srv5_packet.get_event_id(),
+            param_1=srv5_packet.get_param_1(), param_2=srv5_packet.get_param_2()
+        )
+        self.__print_buffer = custom_string
+        LOGGER.info(self.__print_buffer)
+        self.add_print_buffer_to_file_buffer()
+
+    def handle_service_8_packet(self, packet_if: PusTmInterface):
+        from tmtccmd.config.hook import get_global_hook_obj
+        if packet_if.get_service() != PusServices.SERVICE_8_FUNC_CMD:
+            LOGGER.warning('This packet is not a service 8 packet!')
+            return
+        if packet_if.get_subservice() != Srv8Subservices.DATA_REPLY:
+            LOGGER.warning(
+                f'This packet is not data reply packet with '
+                f'subservice {Srv8Subservices.DATA_REPLY}!'
+            )
+            return
+        hook_obj = get_global_hook_obj()
+        if hook_obj is None:
+            LOGGER.warning('Hook object not set')
+            return
+        srv8_packet = cast(Service8TM, packet_if)
+        if srv8_packet is None:
+            LOGGER.warning('Service 8 object is not instance of Service8TM')
+            return
+        obj_id = srv8_packet.get_source_object_id_as_bytes()
+        action_id = srv8_packet.get_action_id()
+        header_list, content_list = hook_obj.handle_service_8_telemetry(
+            object_id=obj_id, action_id=action_id,
+            custom_data=srv8_packet.get_custom_data()
+        )
+        obj_id_dict = hook_obj.get_object_ids()
+        rep_str = obj_id_dict.get(obj_id)
+        if rep_str is None:
+            rep_str = "unknown object"
+        print_string = \
+            f'Service 8 data reply from {rep_str} with action ID {action_id}'
+        self.__print_buffer = print_string
+        LOGGER.info(self.__print_buffer)
+        self.add_print_buffer_to_file_buffer()
+        self.__print_buffer = header_list
+        LOGGER.info(self.__print_buffer)
+        self.add_print_buffer_to_file_buffer()
+        self.__print_buffer = content_list
+        LOGGER.info(self.__print_buffer)
+        self.add_print_buffer_to_file_buffer()
+
     def handle_hk_print(
             self, object_id: int, set_id: int, hk_header: list, hk_content: list,
             validity_buffer: bytearray, num_vars: int
     ):
         """Prints the passed housekeeping packet, if HK printout is enabled and also adds
         it to the internal print buffer.
-        :param tm_packet:
+        :param object_id:   Object ID in integer format
+        :param set_id:      Set ID in integer format
+        :param hk_header:   Header list
+        :param hk_content:  Content list
+        :param validity_buffer: Validity buffer bytearray
+        :param num_vars: The number of HK variables contained within the set
         :return:
         """
         self.__print_hk(
@@ -135,7 +207,9 @@ def handle_hk_print(
 
     def handle_hk_definition_print(self, object_id: int, set_id: int, srv3_packet: Service3Base):
         """
-        :param tm_packet:
+        :param object_id:
+        :param set_id:
+        :param srv3_packet:
         :return:
         """
         self.__print_buffer = f'HK Definition from Object ID {object_id:#010x} and set ID {set_id}:'
@@ -145,49 +219,51 @@ def handle_hk_definition_print(self, object_id: int, set_id: int, srv3_packet: S
             header=def_header, content=def_list
         )
 
-    def __handle_short_print(self, tm_packet: PusTelemetry):
-        self.__print_buffer = "Received TM[" + str(tm_packet.get_service()) + "," + str(
-            tm_packet.get_subservice()) + "]"
+    def __handle_short_print(self, packet_if: PusTmInterface):
+        self.__print_buffer = "Received TM[" + str(packet_if.get_service()) + "," + str(
+            packet_if.get_subservice()) + "]"
         LOGGER.info(self.__print_buffer)
         self.add_print_buffer_to_file_buffer()
 
-    def __handle_long_tm_print(self, tm_packet: PusTelemetry):
+    def __handle_long_tm_print(self, packet_if: PusTmInterface, info_if: PusTmInfoInterface):
         """Main function to print the most important information inside the telemetry
-        :param tm_packet:
+        :param packet_if: Core packet interface
+        :param info_if: Information interface
         :return:
         """
-        self.__print_buffer = "Received Telemetry: " + tm_packet.print_info
+        self.__print_buffer = "Received Telemetry: " + info_if.get_print_info()
         LOGGER.info(self.__print_buffer)
         self.add_print_buffer_to_file_buffer()
         try:
-            self.__handle_column_header_print(tm_packet)
-            self.__handle_tm_content_print(tm_packet)
-            self.__handle_additional_printout(tm_packet)
+            self.__handle_column_header_print(info_if=info_if)
+            self.__handle_tm_content_print(info_if=info_if)
+            self.__handle_additional_printout(info_if=info_if)
         except TypeError as error:
-            LOGGER.warning(f"Type Error when trying to print TM Packet"
-                           f" [{tm_packet.get_service()} , {tm_packet.get_subservice()}]")
+            LOGGER.warning(
+                f"Type Error when trying to print TM Packet "
+                f"[{packet_if.get_service()} , {packet_if.get_subservice()}]")
             LOGGER.warning(error)
 
-    def __handle_column_header_print(self, tm_packet: PusTelemetry):
+    def __handle_column_header_print(self, info_if: PusTmInfoInterface):
         header_list = []
-        tm_packet.append_telemetry_column_headers(header_list=header_list)
+        info_if.append_telemetry_column_headers(header_list=header_list)
         self.__print_buffer = str(header_list)
         LOGGER.info(self.__print_buffer)
         self.add_print_buffer_to_file_buffer()
 
-    def __handle_tm_content_print(self, tm_packet: PusTelemetry):
+    def __handle_tm_content_print(self, info_if: PusTmInfoInterface):
         """
-        :param tm_packet:
+        :param info_if: Information interface
         :return:
         """
         content_list = []
-        tm_packet.append_telemetry_content(content_list=content_list)
+        info_if.append_telemetry_content(content_list=content_list)
         self.__print_buffer = str(content_list)
         LOGGER.info(self.__print_buffer)
         self.add_print_buffer_to_file_buffer()
 
-    def __handle_additional_printout(self, tm_packet: PusTelemetry):
-        additional_printout = tm_packet.get_custom_printout()
+    def __handle_additional_printout(self, info_if: PusTmInfoInterface):
+        additional_printout = info_if.get_custom_printout()
         if additional_printout != "":
             self.__print_buffer = additional_printout
             LOGGER.info(self.__print_buffer)
@@ -196,12 +272,10 @@ def __print_hk(
             self, content_type: HkContentType, object_id: int, set_id: int, header: list,
             content: list
     ):
-        """
-        :param tm_packet:
+        """This function pretty prints HK packets with a given header and content list
+        :param content_type: Type of content for HK packet
         :return:
         """
-        if len(content) == 0 or len(header) == 0:
-            return
         if content_type == HkContentType.HK:
             print_prefix = "Housekeeping data"
         elif content_type == HkContentType.DEFINITIONS:
@@ -209,9 +283,14 @@ def __print_hk(
         else:
             print_prefix = "Unknown housekeeping data"
         self.__print_buffer = \
-            f'{print_prefix} from Object ID {object_id:#010x} and set ID {set_id}:'
+            f'{print_prefix} from Object ID {object_id:#010x} with Set ID {set_id}'
         LOGGER.info(self.__print_buffer)
         self.add_print_buffer_to_file_buffer()
+        if len(content) == 0 or len(header) == 0:
+            self.__print_buffer = 'Content and header list empty..'
+            LOGGER.info(self.__print_buffer)
+            self.add_print_buffer_to_file_buffer()
+            return
         self.__print_buffer = str(header)
         LOGGER.info(self.__print_buffer)
         self.add_print_buffer_to_file_buffer()
@@ -219,9 +298,9 @@ def __print_hk(
         LOGGER.info(self.__print_buffer)
         self.add_print_buffer_to_file_buffer()
 
-    def __print_validity_buffer(self, validity_buffer: bytearray, num_vars: int):
+    def __print_validity_buffer(self, validity_buffer: bytes, num_vars: int):
         """
-        :param tm_packet:
+        :param validity_buffer: Validity buffer in bytes format
         :return:
         """
         if len(validity_buffer) == 0:
@@ -233,10 +312,10 @@ def __print_validity_buffer(self, validity_buffer: bytearray, num_vars: int):
             validity_buffer=validity_buffer, num_vars=num_vars
         )
 
-    def __handle_validity_buffer_print(self, validity_buffer: bytearray, num_vars: int):
+    def __handle_validity_buffer_print(self, validity_buffer: bytes, num_vars: int):
         """
         :param validity_buffer:
-        :param number_of_parameters:
+        :param num_vars: Number of parameters
         :return:
         """
         self.__print_buffer = "["
@@ -255,40 +334,23 @@ def __handle_validity_buffer_print(self, validity_buffer: bytearray, num_vars: i
         LOGGER.info(self.__print_buffer)
         self.add_print_buffer_to_file_buffer()
 
-    def __handle_wiretapping_packet(self, wiretapping_packet: PusTelemetry):
+    def __handle_wiretapping_packet(self, packet_if: PusTmInterface, info_if: PusTmInfoInterface):
         """
-        :param wiretapping_packet:
+        :param packet_if: Core packet interface
+        :param info_if: Information interface
         :return:
         """
-        if wiretapping_packet.get_service() == 2 and (wiretapping_packet.get_subservice() == 131 or
-                                                      wiretapping_packet.get_subservice() == 130):
-            self.__print_buffer = f"Wiretapping Packet or Raw Reply from TM [{wiretapping_packet.get_service()}," \
-                                  f"{wiretapping_packet.get_subservice()}]: "
-            self.__print_buffer = self.__print_buffer + wiretapping_packet.return_source_data_string()
+        if packet_if.get_service() == 2 and \
+                (packet_if.get_subservice() == 131
+                 or packet_if.get_subservice() == 130):
+            self.__print_buffer = \
+                f"Wiretapping Packet or Raw Reply from TM [{packet_if.get_service()}," \
+                f"{packet_if.get_subservice()}]: "
+            self.__print_buffer = \
+                self.__print_buffer + info_if.return_source_data_string()
             LOGGER.info(self.__print_buffer)
             self.add_print_buffer_to_file_buffer()
 
-    def __handle_data_reply_packet(self, srv8_tm: Service8TM):
-        """
-        Handles the PUS Service 8 data reply types.
-        :return:
-        """
-        try:
-            if srv8_tm.custom_data_content is [] or srv8_tm.custom_data_header is []:
-                self.__print_buffer = f"Service 8 Direct Command Reply TM[8,130] with TM data: " \
-                                      f"{srv8_tm.return_source_data_string()}"
-            else:
-                self.__print_buffer = f"{srv8_tm.custom_data_header}\n"
-                self.__print_buffer += f"{srv8_tm.custom_data_content}\n"
-                LOGGER.info(f"Service 8 data for object ID {srv8_tm.object_id_key} "
-                            f"and action ID {srv8_tm.source_action_id}:")
-                LOGGER.info(str(srv8_tm.custom_data_header))
-                LOGGER.info(str(srv8_tm.custom_data_content))
-            self.add_print_buffer_to_file_buffer()
-        except AttributeError:
-            LOGGER.warning("Service 8 packet format invalid, no custom_data_content or custom_data_header found!")
-            return
-
     def __handle_event_packet(self, srv_5_tm: Service5TM):
         printout = srv_5_tm.get_custom_printout()
         if printout != "":


Test patch:
diff --git a/src/tests/backend_mock.py b/src/tests/backend_mock.py
index 59725922..da3d8ba8 100644
--- a/src/tests/backend_mock.py
+++ b/src/tests/backend_mock.py
@@ -1,7 +1,6 @@
 from unittest.mock import MagicMock
-from argparse import Namespace
+
 from tmtccmd.core.backend import TmTcHandler
-from tmtccmd.config.hook import TmTcHookBase
 from tmtccmd.ccsds.handler import CcsdsTmHandler
 from tmtccmd.config.com_if import create_communication_interface_default
 from tmtccmd.sendreceive.tm_listener import TmListener
@@ -10,16 +9,6 @@
 from tmtccmd.core.frontend_base import FrontendBase
 
 
-def create_hook_mock() -> TmTcHookBase:
-    tmtc_hook_base = TmTcHookBase()
-    tmtc_hook_base.add_globals_pre_args_parsing = MagicMock(return_value=0)
-    tmtc_hook_base.add_globals_post_args_parsing = MagicMock(return_value=0)
-    tmtc_hook_base.custom_args_parsing = MagicMock(
-        return_value=Namespace(service=17, mode=CoreModeList.IDLE)
-    )
-    return tmtc_hook_base
-
-
 def create_backend_mock(tm_handler: CcsdsTmHandler) -> TmTcHandler:
     tmtc_printer = TmTcPrinter(display_mode=DisplayMode.LONG, do_print_to_file=False, print_tc=True)
     com_if = create_communication_interface_default(
@@ -30,8 +19,8 @@ def create_backend_mock(tm_handler: CcsdsTmHandler) -> TmTcHandler:
     )
     # The global variables are set by the argument parser.
     tmtc_backend = TmTcHandler(
-        com_if=com_if, tmtc_printer=tmtc_printer, tm_listener=tm_listener, init_mode=CoreModeList.IDLE,
-        init_service=17, init_opcode="0", tm_handler=tm_handler
+        com_if=com_if, tmtc_printer=tmtc_printer, tm_listener=tm_listener,
+        init_mode=CoreModeList.IDLE, init_service=17, init_opcode="0", tm_handler=tm_handler
     )
     tmtc_backend.start_listener = MagicMock(return_value=0)
     tmtc_backend.initialize = MagicMock(return_value=0)
diff --git a/src/tests/hook_obj_mock.py b/src/tests/hook_obj_mock.py
new file mode 100644
index 00000000..bac709d6
--- /dev/null
+++ b/src/tests/hook_obj_mock.py
@@ -0,0 +1,185 @@
+from abc import abstractmethod
+from typing import Dict, Union, Optional, Tuple
+from unittest.mock import MagicMock
+import argparse
+
+from tmtccmd.utility.tmtc_printer import TmTcPrinter
+from tmtccmd.config.com_if import CommunicationInterface
+from tmtccmd.config.definitions import DEFAULT_APID
+from tmtccmd.config.definitions import ServiceOpCodeDictT, CoreModeList
+from tmtccmd.tm.service_3_base import Service3Base
+from tmtccmd.core.backend import TmTcHandler
+from tmtccmd.tc.definitions import TcQueueT
+from tmtccmd.config.hook import TmTcHookBase
+from tmtccmd.utility.logger import get_console_logger
+
+LOGGER = get_console_logger()
+
+
+def create_hook_mock() -> TmTcHookBase:
+    """Create simple minimal hook mock using the MagicMock facilities by unittest
+    :return:
+    """
+    tmtc_hook_base = TmTcHookBase()
+    tmtc_hook_base.add_globals_pre_args_parsing = MagicMock(return_value=0)
+    tmtc_hook_base.add_globals_post_args_parsing = MagicMock(return_value=0)
+    tmtc_hook_base.custom_args_parsing = MagicMock(
+        return_value=argparse.Namespace(service=17, mode=CoreModeList.IDLE)
+    )
+    return tmtc_hook_base
+
+
+def create_hook_mock_with_srv_handlers() -> TmTcHookBase:
+    tmtc_hook_base = create_hook_mock()
+    tmtc_hook_base.handle_service_8_telemetry = MagicMock(return_value=(["Test"], [0]))
+    tmtc_hook_base.handle_service_5_event = MagicMock(return_value="Test Custom String")
+    # Valid returnvalue for now
+    srv_3_return_tuple = (["Test"], [0], bytearray(0b10000000), 1)
+    tmtc_hook_base.handle_service_3_housekeeping = MagicMock(return_value=srv_3_return_tuple)
+    return tmtc_hook_base
+
+
+class TestHookObj(TmTcHookBase):
+    service_8_handler_called = False
+    service_5_handler_called = False
+    service_3_handler_called = False
+
+    def __init__(self):
+        super(self, TmTcHookBase).__init__()
+        self.get_obj_id_called = False
+        self.add_globals_pre_args_parsing_called = False
+        self.add_globals_post_args_parsing_called = False
+        self.assign_communication_interface_called = False
+
+    @abstractmethod
+    def get_object_ids(self) -> Dict[bytes, list]:
+        """The user can specify an object ID dictionary here mapping object ID bytearrays to a
+        list. This list could contain containing the string representation or additional
+        information about that object ID.
+        """
+        return TmTcHookBase.get_object_ids()
+
+    @abstractmethod
+    def add_globals_pre_args_parsing(self, gui: bool = False):
+        """Add all global variables prior to parsing the CLI arguments.
+
+        :param gui: Set to true if the GUI mode is used
+        :return:
+        """
+        from tmtccmd.config.globals import set_default_globals_pre_args_parsing
+        set_default_globals_pre_args_parsing(gui=gui, apid=DEFAULT_APID)
+
+    @abstractmethod
+    def add_globals_post_args_parsing(self, args: argparse.Namespace):
+        """Add global variables prior after parsing the CLI arguments.
+
+        :param args: Specify whether a GUI is used
+        """
+        from tmtccmd.config.globals import set_default_globals_post_args_parsing
+        set_default_globals_post_args_parsing(
+            args=args, json_cfg_path=self.get_json_config_file_path()
+        )
+
+    @abstractmethod
+    def assign_communication_interface(
+            self, com_if_key: str, tmtc_printer: TmTcPrinter
+    ) -> Optional[CommunicationInterface]:
+        """Assign the communication interface used by the TMTC commander to send and receive
+        TMTC with.
+
+        :param com_if_key:      String key of the communication interface to be created.
+        :param tmtc_printer:    Printer utility instance.
+        """
+        from tmtccmd.config.com_if import create_communication_interface_default
+        return create_communication_interface_default(
+            com_if_key=com_if_key, tmtc_printer=tmtc_printer,
+            json_cfg_path=self.get_json_config_file_path()
+        )
+
+    @abstractmethod
+    def get_service_op_code_dictionary(self) -> ServiceOpCodeDictT:
+        """This is a dicitonary mapping services represented by strings to an operation code
+        dictionary.
+
+        :return:
+        """
+        from tmtccmd.config.globals import get_default_service_op_code_dict
+        return get_default_service_op_code_dict()
+
+    @abstractmethod
+    def perform_mode_operation(self, tmtc_backend: TmTcHandler, mode: int):
+        """Perform custom mode operations
+        :param tmtc_backend:
+        :param mode:
+        :return:
+        """
+        pass
+
+    @abstractmethod
+    def pack_service_queue(self, service: Union[int, str], op_code: str, service_queue: TcQueueT):
+        """Overriding this function allows the user to package a telecommand queue for a given
+        service and operation code combination.
+
+        :param service:
+        :param op_code:
+        :param service_queue:
+        :return:
+        """
+        pass
+
+    @staticmethod
+    def handle_service_8_telemetry(
+            object_id: bytes, action_id: int, custom_data: bytearray
+    ) -> Tuple[list, list]:
+        """This function is called by the TMTC core to handle Service 8 packets
+        The user can return a tuple of two lists, where the first list
+        is a list of header strings to print and the second list is a list of values to print.
+        The TMTC core will take care of printing both lists and logging them.
+
+        :param object_id: Byte representation of the object ID
+        :param action_id:
+        :param custom_data:
+        :return:
+        """
+        TestHookObj.service_8_handler_called = True
+        return [], []
+
+    @staticmethod
+    def handle_service_3_housekeeping(
+        object_id: bytes, set_id: int, hk_data: bytearray, service3_packet: Service3Base
+    ) -> Tuple[list, list, bytearray, int]:
+        """This function is called when a Service 3 Housekeeping packet is received.
+
+        :param object_id: Byte representation of the object ID
+        :param set_id: Unique set ID of the HK reply
+        :param hk_data:     HK data. For custom HK handling, whole HK data will be passed here.
+                            Otherwise, a 8 byte SID consisting of the 4 byte object ID and 4 byte
+                            set ID will be assumed and the remaining packet after the first 4 bytes
+                            will be passed here.
+        :param service3_packet: Service 3 packet object
+        :return: Expects a tuple, consisting of two lists, a bytearray and an integer
+            The first list contains the header columns, the second list the list with
+            the corresponding values. The bytearray is the validity buffer, which is usually appended
+            at the end of the housekeeping packet. The last value is the number of parameters.
+        """
+        LOGGER.info(
+            "TmTcHookBase: No service 3 housekeeping data handling implemented yet in "
+            "handle_service_3_housekeeping hook function"
+        )
+        return [], [], bytearray(), 0
+
+    @staticmethod
+    def handle_service_5_event(
+        object_id: bytes, event_id: int, param_1: int, param_2: int
+    ) -> str:
+        """This function is called when a Service 5 Event Packet is received. The user can specify
+         a custom string here which will be printed to display additional information related
+         to an event.
+
+        :param object_id: Byte representation of the object ID
+        :param event_id: Two-byte event ID
+        :param param_1: Four-byte Parameter 1
+        :param param_2: Four-byte Parameter 2
+        :return: Custom information string which will be printed with the event
+        """
+        return ""
diff --git a/src/tests/test_global_manager.py b/src/tests/test_global_manager.py
index 2d075deb..c251af0a 100644
--- a/src/tests/test_global_manager.py
+++ b/src/tests/test_global_manager.py
@@ -1,5 +1,46 @@
 from unittest import TestCase
+from tmtccmd.config.globals import update_global, get_global, get_global_apid, set_default_apid, \
+    set_json_cfg_path, get_json_cfg_path, set_glob_com_if_dict, get_glob_com_if_dict, \
+    set_default_globals_pre_args_parsing, check_and_set_core_mode_arg, CoreModeList, \
+    CoreGlobalIds
 
 
 class TestGlobalManager(TestCase):
-    pass
+
+    def test_global_module(self):
+        update_global(global_param_id=30, parameter='hello')
+        self.assertTrue(get_global(global_param_id=30) == 'hello')
+
+        current_apid = get_global_apid()
+        set_default_apid(default_apid=0x01)
+        self.assertTrue(get_global_apid() == 0x01)
+        set_default_apid(current_apid)
+
+        current_path = get_json_cfg_path()
+        set_json_cfg_path('.')
+        self.assertTrue(get_json_cfg_path() == '.')
+        set_json_cfg_path(current_path)
+
+        custom_com_if_dict = {
+            "test": ("Test Interface", "")
+        }
+        set_glob_com_if_dict(custom_com_if_dict=custom_com_if_dict)
+        com_if_dict = get_glob_com_if_dict()
+        self.assertTrue(com_if_dict["test"][0] == "Test Interface")
+
+        set_default_globals_pre_args_parsing(
+            gui=False, apid=0x02
+        )
+        self.assertTrue(get_global_apid() == 0x02)
+        set_default_apid(current_apid)
+
+        result = check_and_set_core_mode_arg(mode_arg='udp')
+        self.assertTrue(result == CoreModeList.SEQUENTIAL_CMD_MODE)
+
+        result = check_and_set_core_mode_arg(mode_arg='listener')
+        self.assertTrue(get_global(CoreGlobalIds.MODE) == CoreModeList.LISTENER_MODE)
+        self.assertTrue(result == CoreModeList.LISTENER_MODE)
+
+        result = check_and_set_core_mode_arg(mode_arg='seqcmd')
+        self.assertTrue(get_global(CoreGlobalIds.MODE) == CoreModeList.SEQUENTIAL_CMD_MODE)
+        self.assertTrue(result == CoreModeList.SEQUENTIAL_CMD_MODE)
diff --git a/src/tests/test_printer.py b/src/tests/test_printer.py
index 9d0ed61c..5cb889ad 100644
--- a/src/tests/test_printer.py
+++ b/src/tests/test_printer.py
@@ -1,13 +1,17 @@
 from unittest import TestCase
 
-from tmtccmd.tm.service_1_verification import Service1TmPacked, Service1TM
-from tmtccmd.tm.service_5_event import Service5TM, Service5TmPacked, Severity
+from tmtccmd.runner import initialize_tmtc_commander
+from tmtccmd.tm import Service1TM, Service5TM, Srv5Subservices, CdsShortTimestamp
 from tmtccmd.tc.service_17_test import pack_service17_ping_command
 from tmtccmd.utility.tmtc_printer import TmTcPrinter, DisplayMode
 from tmtccmd.utility.logger import get_console_logger, set_tmtc_console_logger
+from tmtccmd.config.globals import update_global, CoreGlobalIds
+
+from .hook_obj_mock import create_hook_mock_with_srv_handlers
 
 
 class TestPrinter(TestCase):
+
     def setUp(self):
         self.tmtc_printer = TmTcPrinter()
         self.logger = get_console_logger()
@@ -19,30 +23,47 @@ def test_print_functions(self):
         self.assertTrue(self.tmtc_printer.get_display_mode() == DisplayMode.SHORT)
         self.tmtc_printer.set_display_mode(DisplayMode.LONG)
 
-        service_1_packed = Service1TmPacked(subservice=1)
-        service_1_tm = Service1TM(service_1_packed.pack())
-        self.tmtc_printer.print_telemetry(packet=service_1_tm)
+        service_1_tm = Service1TM(subservice_id=1, time=CdsShortTimestamp.init_from_current_time())
+        service_1_packed = service_1_tm.pack()
+        self.tmtc_printer.print_telemetry(packet_if=service_1_tm, info_if=service_1_tm)
         # Should not crash and emit warning
-        self.tmtc_printer.print_telemetry(packet=None)
+        self.tmtc_printer.print_telemetry(packet_if=None, info_if=None)
 
         self.tmtc_printer.set_display_mode(DisplayMode.SHORT)
-        self.tmtc_printer.print_telemetry(packet=service_1_tm)
-        service_1_packed = Service1TmPacked(subservice=2)
-        service_1_tm = Service1TM(service_1_packed.pack())
-        self.tmtc_printer.print_telemetry(packet=service_1_tm, print_raw_tm=True)
+        self.tmtc_printer.print_telemetry(packet_if=service_1_tm, info_if=service_1_tm)
+        service_1_tm = Service1TM(subservice_id=2, time=CdsShortTimestamp.init_from_current_time())
+        service_1_packed = service_1_tm.pack()
+        self.tmtc_printer.print_telemetry(
+            packet_if=service_1_tm, info_if=service_1_tm, print_raw_tm=True
+        )
 
         self.tmtc_printer.set_display_mode(DisplayMode.LONG)
-        service_5_packed = Service5TmPacked(
-            severity=Severity.INFO, object_id=bytearray([0x01, 0x02, 0x03, 0x04]), event_id=22, param_1=32,
-            param_2=82452
+        service_5_tm = Service5TM(
+            subservice_id=Srv5Subservices.INFO_EVENT, object_id=bytearray([0x01, 0x02, 0x03, 0x04]),
+            event_id=22, param_1=32, param_2=82452, time=CdsShortTimestamp.init_from_current_time()
+        )
+        hook_base = create_hook_mock_with_srv_handlers()
+        initialize_tmtc_commander(hook_object=hook_base)
+
+        service_5_packed = service_5_tm.pack()
+        self.tmtc_printer.print_telemetry(packet_if=service_5_tm, info_if=service_5_tm)
+
+        hook_base.handle_service_5_event.assert_called_with(
+            object_id=bytes([0x01, 0x02, 0x03, 0x04]), event_id=22, param_1=32, param_2=82452
         )
-        service_5_tm = Service5TM(service_5_packed.pack(), call_srv5_hook=False)
-        self.tmtc_printer.print_telemetry(packet=service_5_tm)
 
         service_17_command = pack_service17_ping_command(ssc=0, apid=42)
-        self.tmtc_printer.print_telecommand(tc_packet_obj=service_17_command, tc_packet_raw=service_17_command.pack())
+        self.tmtc_printer.print_telecommand(
+            tc_packet_obj=service_17_command, tc_packet_raw=service_17_command.pack()
+        )
         self.tmtc_printer.set_display_mode(DisplayMode.SHORT)
-        self.tmtc_printer.print_telecommand(tc_packet_obj=service_17_command, tc_packet_raw=service_17_command.pack())
+        self.tmtc_printer.print_telecommand(
+            tc_packet_obj=service_17_command, tc_packet_raw=service_17_command.pack()
+        )
         self.tmtc_printer.set_display_mode(DisplayMode.LONG)
 
         self.tmtc_printer.clear_file_buffer()
+
+    def tearDown(self) -> None:
+        """Reset the hook object"""
+        update_global(CoreGlobalIds.TMTC_HOOK, None)
diff --git a/src/tests/test_pus.py b/src/tests/test_pus.py
index c00626ad..61bc0531 100755
--- a/src/tests/test_pus.py
+++ b/src/tests/test_pus.py
@@ -6,49 +6,57 @@
 
 from tmtccmd.ecss.tc import PusTelecommand
 from tmtccmd.ecss.tc import generate_crc, generate_packet_crc
-from tmtccmd.ccsds.spacepacket import get_sp_packet_sequence_control
+from tmtccmd.ccsds.spacepacket import get_space_packet_sequence_control
 from tmtccmd.ecss.conf import set_default_apid, get_default_apid, PusVersion, get_pus_tm_version
-from tmtccmd.tm.service_17_test import Service17TM, Service17TmPacked
+from tmtccmd.ccsds.time import CdsShortTimestamp
+from tmtccmd.tm.service_17_test import Service17TM
 
 
 class TestTelemetry(TestCase):
     def test_space_packet_functions(self):
-        psc = get_sp_packet_sequence_control(sequence_flags=0b111, source_sequence_count=42)
+        psc = get_space_packet_sequence_control(sequence_flags=0b111, source_sequence_count=42)
         self.assertTrue(psc & 0xc000 == 0xc000)
 
     def test_generic_pus_c(self):
-        pus_17_telecommand = Service17TmPacked(subservice=1, ssc=36)
-        pus_17_raw = pus_17_telecommand.pack()
+        pus_17_telemetry = Service17TM(
+            subservice_id=1, ssc=36, time=CdsShortTimestamp.init_from_current_time()
+        )
+        pus_17_raw = pus_17_telemetry.pack()
+
         pus_17_telemetry = None
-        def tm_func(raw_telemetry: bytearray): return Service17TM(raw_telemetry=raw_telemetry)
+
+        def tm_func(raw_telemetry: bytearray):
+            return Service17TM.unpack(raw_telemetry=raw_telemetry)
+
         self.assertRaises(ValueError, tm_func, bytearray())
         self.assertRaises(ValueError, tm_func, None)
 
-        pus_17_telemetry = Service17TM(raw_telemetry=pus_17_raw)
+        pus_17_telemetry = Service17TM.unpack(raw_telemetry=pus_17_raw)
+
         self.assertTrue(get_pus_tm_version() == PusVersion.PUS_C)
         self.assertTrue(pus_17_telemetry.get_service() == 17)
+        self.assertTrue(pus_17_telemetry.get_apid() == 0xef)
         self.assertTrue(pus_17_telemetry.get_subservice() == 1)
         self.assertTrue(pus_17_telemetry.get_ssc() == 36)
         self.assertTrue(pus_17_telemetry.get_tm_data() == bytearray())
-        self.assertTrue(pus_17_telemetry.is_valid())
+        self.assertTrue(pus_17_telemetry.pus_tm.is_valid())
         self.assertTrue(pus_17_telemetry.get_custom_printout() == "")
         self.assertTrue(pus_17_telemetry.return_source_data_string() == "[]")
-        pus_17_telemetry.print_source_data()
-        pus_17_telemetry.print_full_packet_string()
+        pus_17_telemetry.pus_tm.print_source_data()
+        pus_17_telemetry.pus_tm.print_full_packet_string()
         # This string changes depending on system time, so its complicated to test its validity
-        full_string = pus_17_telemetry.return_full_packet_string()
+        full_string = pus_17_telemetry.pus_tm.return_full_packet_string()
         print(full_string)
         print(pus_17_telemetry)
         print(repr(pus_17_telemetry))
-
-        raw_tm_created = pus_17_telemetry.get_raw_packet()
-        self.assertTrue(raw_tm_created == pus_17_raw)
-        self.assertTrue(pus_17_telemetry.get_tc_packet_id() == 0x8 << 8 | 0xef)
+        self.assertTrue(pus_17_telemetry.pus_tm.get_packet_id() == 0x8 << 8 | 0xef)
 
     def test_list_functionality(self):
-        pus_17_telecommand = Service17TmPacked(subservice=1, ssc=36)
+        pus_17_telecommand = Service17TM(
+            subservice_id=1, ssc=36, time=CdsShortTimestamp.init_from_current_time()
+        )
         pus_17_raw = pus_17_telecommand.pack()
-        pus_17_telemetry = Service17TM(raw_telemetry=pus_17_raw)
+        pus_17_telemetry = Service17TM.unpack(raw_telemetry=pus_17_raw)
 
         header_list = []
         content_list = []
@@ -77,7 +85,9 @@ def test_generic(self):
         self.assertTrue(get_default_apid() == 42)
 
         test_app_data = bytearray([1, 2, 3])
-        pus_17_telecommand_with_app_data = PusTelecommand(service=17, subservice=32, ssc=52, app_data=test_app_data)
+        pus_17_telecommand_with_app_data = PusTelecommand(
+            service=17, subservice=32, ssc=52, app_data=test_app_data
+        )
 
         self.assertTrue(len(pus_17_telecommand_with_app_data.get_app_data()) == 3)
         self.assertTrue(pus_17_telecommand_with_app_data.get_app_data() == bytearray([1, 2, 3]))
diff --git a/src/tests/test_runner.py b/src/tests/test_runner.py
index 581a61cd..bfb23b5b 100644
--- a/src/tests/test_runner.py
+++ b/src/tests/test_runner.py
@@ -1,7 +1,9 @@
 from unittest import TestCase
 from tmtccmd.ccsds.handler import CcsdsTmHandler
 from tmtccmd.runner import run_tmtc_commander, initialize_tmtc_commander, get_default_tmtc_backend
-from tests.backend_mock import create_backend_mock, create_hook_mock, create_frontend_mock
+
+from .backend_mock import create_backend_mock, create_frontend_mock
+from .hook_obj_mock import create_hook_mock
 
 
 class TestTmtcRunner(TestCase):
diff --git a/src/tmtccmd/tc/service_17_test.py b/src/tmtccmd/tc/service_17_test.py
index 84dce587..08307da2 100644
--- a/src/tmtccmd/tc/service_17_test.py
+++ b/src/tmtccmd/tc/service_17_test.py
@@ -1,3 +1,6 @@
+"""
+Contains definitions and functions related to PUS Service 17 Telecommands
+"""
 from tmtccmd.config.definitions import QueueCommands
 from tmtccmd.tc.definitions import PusTelecommand, TcQueueT
 from tmtccmd.config.globals import get_global_apid
diff --git a/src/tmtccmd/tm/service_17_test.py b/src/tmtccmd/tm/service_17_test.py
index 67713fa0..2ff97e9a 100644
--- a/src/tmtccmd/tm/service_17_test.py
+++ b/src/tmtccmd/tm/service_17_test.py
@@ -1,27 +1,47 @@
-from tmtccmd.ecss.tm import PusTelemetry
-from tmtccmd.ecss.tm_creator import PusTelemetryCreator
+from __future__ import annotations
+from tmtccmd.pus.service_list import PusServices
+from tmtccmd.ecss.tm import PusTelemetry, PusTmBase, PusTmInfoBase, PusVersion, CdsShortTimestamp
 
 
-class Service17TM(PusTelemetry):
-    def __init__(self, raw_telemetry: bytearray):
-        super().__init__(raw_telemetry=raw_telemetry)
-        self.specify_packet_info("Ping Reply")
+class Service17TM(PusTmBase, PusTmInfoBase):
+    def __init__(
+            self, subservice_id: int, time: CdsShortTimestamp = None, ssc: int = 0,
+            source_data: bytearray = bytearray([]), apid: int = -1, packet_version: int = 0b000,
+            pus_version: PusVersion = PusVersion.UNKNOWN, pus_tm_version: int = 0b0001,
+            ack: int = 0b1111, secondary_header_flag: bool = True, space_time_ref: int = 0b0000,
+            destination_id: int = 0
+    ):
+        pus_tm = PusTelemetry(
+            service_id=PusServices.SERVICE_17_TEST,
+            subservice_id=subservice_id,
+            time=time,
+            ssc=ssc,
+            source_data=source_data,
+            apid=apid,
+            packet_version=packet_version,
+            pus_version=pus_version,
+            pus_tm_version=pus_tm_version,
+            ack=ack,
+            secondary_header_flag=secondary_header_flag,
+            space_time_ref=space_time_ref,
+            destination_id=destination_id
+        )
+        PusTmBase.__init__(self, pus_tm=pus_tm)
+        PusTmInfoBase.__init__(self, pus_tm=pus_tm)
+        self.pus_tm.specify_packet_info("Ping Reply")
 
-    def append_telemetry_content(self, content_list: list):
-        super().append_telemetry_content(content_list=content_list)
-        return
+    @classmethod
+    def __empty(cls) -> Service17TM:
+        return cls(
+            subservice_id=0
+        )
 
-    def append_telemetry_column_headers(self, header_list: list):
-        super().append_telemetry_column_headers(header_list=header_list)
-        return
-
-
-class Service17TmPacked(PusTelemetryCreator):
-    """
-    Class representation for Service 1 TM creation.
-    """
-    def __init__(self, subservice: int, ssc: int = 0, source_data: bytearray = bytearray()):
-        super().__init__(service=17, subservice=subservice, ssc=ssc, source_data=source_data)
-
-    def pack(self) -> bytearray:
-        return super().pack()
+    @classmethod
+    def unpack(
+            cls, raw_telemetry: bytearray, pus_version: PusVersion = PusVersion.UNKNOWN
+    ) -> Service17TM:
+        service_17_tm = cls.__empty()
+        service_17_tm.pus_tm = PusTelemetry.unpack(
+            raw_telemetry=raw_telemetry, pus_version=pus_version
+        )
+        return service_17_tm