diff --git a/nexus/hardware.py b/nexus/hardware.py
index 0717ea10fa4a204d5e73ce9ad741dde417f4f4cd..d2b9eeea1a027cd91be40a11fe091c3b6693c290 100644
--- a/nexus/hardware.py
+++ b/nexus/hardware.py
@@ -1,15 +1,16 @@
 #!/usr/bin/env python3
 # -*- coding: utf-8 -*-
-'''
+"""
 Lloyd Carothers
 Sensor and datalogger classes
-'''
+"""
+
 from collections import OrderedDict
 from enum import Enum
 import os.path
 import pickle
 
-from obspy import Inventory, read_inventory
+from obspy import read_inventory
 from obspy.core.inventory import Response
 from obspy.clients.nrl import NRL
 
@@ -19,18 +20,26 @@ NRL_ROOT = NRL_URL
 current_dir = os.path.dirname(os.path.abspath(__file__))
 etc_dir = os.path.join(current_dir, 'etc')
 resp_dir = os.path.join(etc_dir, 'SOH_RESP')
-defaultsfile = os.path.join(etc_dir,'nexus.hardware')
+defaultsfile = os.path.join(etc_dir, 'nexus.hardware')
+
 
+def chan_name_is_geophysical(channel_code: str) -> bool:
+    """
+    Returns true if channel code is determined to be a waveform,
+    so the response should be fetched from the NRL.
+    i.e. Not a SOH channel.
 
-def chan_name_is_geophysical(channel_code):
-    '''
-    Returns true if channel code is determined to be a waveform which response
-    should be fetched from the NRL i.e. Not a SOH channel.
-    '''
+    Args:
+        channel_code (str): The channel code to check if waveform.
+
+    Returns:
+        bool: True if response should be fetched from the NRL,
+              False if response should be used from hardware.
+    """
     band = channel_code[0]
     instrument = channel_code[1]
     orientation = channel_code[2]
-    
+
     # Band codes from SEED Manual excluding A & O
     if band in 'FGDCESHBMLVURPTQ':
         # Seismic instrument codes
@@ -45,86 +54,258 @@ def chan_name_is_geophysical(channel_code):
 
 
 class PlaceHolder(Enum):
+    """
+    Default values for datalogger gain and
+    sampling rate. Gain is set to 1 and
+    SR is set to 2.
+    """
     GAIN = 1
     SR = 2
 
 
-class Hardware(object):
-    def __init__(self, name=''):
+class Hardware():
+    """
+    Base class for representing a hardware component.
+
+    Attributes:
+        name (str): The name of the hardware component.
+        nrl_keys (tuple): A tuple containing NRL keys associated
+                          with the hardware. Initializes as an empty tuple.
+    """
+    def __init__(self, name: str = ''):
+        """
+        Initializes an instance of Hardware with the given name
+        and an empty tuple of nrl_keys.
+
+        Args:
+            name (str, optional): The name of the hardware. Defaults to ''.
+        """
         self.name = name
-        self.NRL_keys = ()
+        self.nrl_keys = ()
 
 
 class Sensor(Hardware):
+    """
+    Represents a sensor device that extends the Hardware base class.
+
+    Attributes:
+        name (str): The name of the sensor.
+        nrl_keys (tuple): A tuple containing NRL keys associated
+                          with the sensor. Initializes as empty.
+        z_is_up (bool): Indicates orientation. Used to get dip angle.
+                        Initializes as True.
+
+    Examples:
+        >>> s = Sensor('CMG-3T)
+    """
     def __init__(self, *args, **kwargs):
+        """
+        Initializes an instance of Sensor with `z_is_up = True`.
+        """
         super().__init__(*args, **kwargs)
         self.z_is_up = True
 
-    def __repr__(self):
-        s =  f'{self.name} {self.NRL_keys}\n'
+    def __repr__(self) -> str:
+        """
+        Returns a summary of the Sensor object with its name,
+        nrl_keys, and vertical orientation.
+
+        Returns:
+            str: A string summarizing the sensor's information and
+                 orientation.
+        """
+        s = f'{self.name} {self.nrl_keys}\n'
         if self.z_is_up:
             s += '\tVertical Up (Broadband)\n'
         else:
             s += '\tVertical Down (Industry)\n'
         return s
 
-    def get_dip(self, component):
+    def get_dip(self, component: str) -> float:
+        """
+        Returns the dip angle for a given component.
+
+        The dip angle indicates the orientation of the component in degrees.
+
+        For component `'Z'`:
+            * If `z_is_up` is True, `'Z'` is oriented vertically upwards, and
+            the dip angle is -90.0 degrees.
+            * If `z_is_up` is False, `'Z'` is oriented vertically downwards,
+            and the dip angle is 90.0 degrees.
+
+        For horizontal components `('N', '1', 'E', '2')`, the dip angle is 0.0
+        degrees.
+
+        For any other input, the method returns None.
+
+        Args:
+            component (str): The component for which to retrieve the dip angle.
+                             Accepted values are `'Z'`, `'N'`, `'1'`, `'E'`, or
+                             `'2'`.
+
+        Returns:
+            (float or None): The dip angle in degrees for the specified
+                             component, or None if the component is
+                             unrecognized.
+        """
         if component == 'Z':
             if self.z_is_up:
                 return -90.00
-            else:
-                return 90.0
-        elif component in ('N', '1', 'E', '2'):
+            return 90.0
+        if component in ('N', '1', 'E', '2'):
             return 0.0
-        else:
-            return None
-
-    def get_azimuth(self, component):
+        return None
+
+    def get_azimuth(self, component: str) -> float:
+        """
+        Returns the azimuth angle for a given component.
+
+        The azimuth angle defines the horizontal orientation of the component
+        in degrees.
+            * For components `'Z'`, `'N'`, and `'1'`, the azimuth angle is
+              0.0 degrees.
+            * For components `'E'` and `'2'`, the azimuth angle is 90.0
+              degrees.
+
+        Args:
+            component (str): The component for which to retrieve the azimuth
+                             angle. Accepted values are `'Z'`, `'N'`, `'1'`,
+                             `'E'`, or `'2'`.
+
+        Returns:
+            float: The azimuth angle in degrees for the specified component.
+        """
         if component in ('Z', 'N', '1'):
             return 0.0
-        elif component in ('E', '2'):
+        if component in ('E', '2'):
             return 90.0
+        raise TypeError("Component not recognized.")
 
-    def get_nrl_keys(self):
-        return self.NRL_keys
+    def get_nrl_keys(self) -> tuple:
+        """
+        Returns the NRL keys associated with the Sensor object.
 
-    def from_gui(self, widget):
+        Returns:
+            tuple: The sensor's NRL keys.
+        """
+        return self.nrl_keys
+
+    def from_gui(self, widget) -> None:
+        """
+        Builds the Sensor object's NRL keys from the NRL Wizard.
+
+        Args:
+            widget (QWizard): The NRL Wizard from the root gui.
+        """
         answer_list = (a for q, a in widget.q_and_as)
-        self.NRL_keys = tuple(answer_list)
+        self.nrl_keys = tuple(answer_list)
 
 
 class DataLogger(Hardware):
+    """
+    Represents a datalogger device that extends the Hardware base class.
+
+    Attributes:
+        name (str): The name of the datalogger.
+        nrl_keys (tuple): A tuple containing NRL keys associated
+                          with the datalogger. Initializes as an empty tuple.
+        soh_resps (dict): Dict containing channels/responses.
+                          Initializes as empty.
+
+    Examples:
+        >>> d = DataLogger('RT-130)
+    """
     def __init__(self, *args, **kwargs):
+        """
+        Initializes an instance of DataLogger with an empty dict of
+        `soh_resps`.
+        """
         super().__init__(*args, **kwargs)
-        self.soh_resps  = {}
-
-    def __repr__(self):
-        s =  f'{self.name} {self.get_nrl_keys("GAIN", "SR")}\n'
+        self.soh_resps = {}
+
+    def __repr__(self) -> str:
+        """
+        Returns a summary of the DataLogger object with its name,
+        nrl_keys, and channels/responses.
+
+        Returns:
+            str: A string summarizing the sensor's information and
+                 orientation.
+        """
+        s = f'{self.name} {self.get_nrl_keys("GAIN", "SR")}\n'
         for chan, resp in self.soh_resps.items():
             s += f'\t{chan}\n\t\t{resp}\n'
         return s
 
-    def requires_gain(self):
-        if PlaceHolder.GAIN in self.NRL_keys:
+    def requires_gain(self) -> bool:
+        """
+        Determines if gain needed.
+
+        Returns:
+            bool: True if `PlaceHolder.GAIN` is in the DataLogger
+                  object's NRL keys. False if it isn't.
+        """
+        if PlaceHolder.GAIN in self.nrl_keys:
             return True
-        else:
-            return False
+        return False
+
+    def requires_sr(self) -> bool:
+        """
+        Determines if sample rate needed.
 
-    def requires_sr(self):
-        if PlaceHolder.SR in self.NRL_keys:
+        Returns:
+            bool: True if `PlaceHolder.SR` is in the DataLogger
+                  object's NRL keys. False if it isn't.
+        """
+        if PlaceHolder.SR in self.nrl_keys:
             return True
-        else:
-            return False
+        return False
+
+    def get_soh_resps(self, code: str) -> str:
+        """
+        Returns SOH response based on the given channel code.
+
+        If the code starts with 'VM', it returns the response associated with
+        'VM' regardless of the rest of the code. Otherwise, it returns the
+        response associated with the exact code provided.
 
-    def get_soh_resps(self, code):
+        Args:
+            code (str): The channel code used to identify the specific SOH
+            response to retrieve.
+
+        Returns:
+            str: The SOH response associated with the given code.
+        """
         if code[:2] == 'VM':
             return self.soh_resps['VM']
-        else:
-            return self.soh_resps[code]
-
-    def get_nrl_keys(self, gain=None, sr=None):
+        return self.soh_resps[code]
+
+    def get_nrl_keys(self, gain: int = None, sr: int = None) -> list:
+        """
+        Returns a list of NRL keys with placeholders replaced by specified
+        values for gain and sample rate.
+
+        If a placeholder is found, and the required value is `None`, the method
+        raises a `TypeError`.
+
+        Args:
+            gain (int, optional): The gain value to replace the
+                                  `PlaceHolder.GAIN` in `nrl_keys`.
+                                  Required if `PlaceHolder.GAIN`
+                                  is present in `nrl_keys`.
+            sr (int, optional): The sample rate (SR) value to replace the
+                                `PlaceHolder.SR` in `nrl_keys`. Required if
+                                `PlaceHolder.SR` is present in `nrl_keys`.
+
+        Returns:
+            list: A list of NRL keys.
+
+        Raises:
+            TypeError: If a required `gain` or `sr` value is not provided when
+                       a corresponding placeholder is present in `nrl_keys`.
+        """
         keys = []
-        for ans in self.NRL_keys:
+        for ans in self.nrl_keys:
             if ans is PlaceHolder.GAIN:
                 if not gain:
                     raise TypeError('Gain required')
@@ -136,8 +317,14 @@ class DataLogger(Hardware):
             keys.append(ans)
         return keys
 
-    def from_gui(self, widget):
-        answer_list = list()
+    def from_gui(self, widget) -> None:
+        """
+        Builds the DataLogger object's NRL keys from the NRL Wizard.
+
+        Args:
+            widget (QWizard): The NRL Wizard from the root gui.
+        """
+        answer_list = []
         for q, a in widget.q_and_as:
             if q.find('gain') >= 0 and a.isdigit():
                 answer_list.append(PlaceHolder.GAIN)
@@ -145,99 +332,301 @@ class DataLogger(Hardware):
                 answer_list.append(PlaceHolder.SR)
             else:
                 answer_list.append(a)
-        self.NRL_keys = tuple(answer_list)
+        self.nrl_keys = tuple(answer_list)
 
 
 class NrlDict(OrderedDict):
+    """
+    Dictionary class to store NRL data, extends OrderedDict.
+
+    This class allows for ordered storage and retrieval of NRL-related data,
+    with an optional container type attribute that defines the type of
+    hardware (e.g., Sensor or DataLogger) the dictionary is intended to
+    contain.
+
+    Attributes:
+        type (Sensor | DataLogger | None): Specifies the hardware type that
+                                           the dictionary is associated with.
+
+    Args:
+        container_type (Sensor | DataLogger | None, optional):
+            Sets the hardware type that the dictionary will contain. Defaults
+            to None.
+
+    Examples:
+        >>> sensors = NrlDict(Sensor)
+        >>> dataloggers = NrlDict(DataLogger)
+    """
+
     def __init__(self, container_type=None):
+        """
+        Initializes an instance of NrlDict with the given container type.
+
+        Args:
+            container_type (Sensor | DataLogger | None, optional):
+                                                  Sets the hardware
+                                                  type that the dictionary
+                                                  will contain. Defaults
+                                                  to None.
+        """
         super().__init__(self)
         self.type = container_type
-    def append(self, item):
+
+    def append(self, item: Hardware) -> None:
+        """
+        Adds given Hardware object to the dict.
+
+        Args:
+            item (Hardware): A Hardware object to be added to the dict.
+        """
+
         assert isinstance(item, Hardware)
-        self[item.NRL_keys] = item
-    def names(self):
-        # returns all the names in dict
-        return [item.name for item in self.values()]
-    def index_name(self):
-        # returns (index, name) of all in dict
-        return [ (list(self.keys()).index(key), self[key].name) for key in self.keys()]
-    def index(self, name):
-        # returns index from name
-        return self.names().index(name)
-    def name(self, index):
-        # returns name from index replaces DLS[index]
-        return self.names()[index]
-    def get(self, name):
-        # returns hardware object from name
-        return self[self.keys_from_name(name)]
-    def keys_from_name(self, name):
-        return [key for key, item in self.items() if item.name == name][0]
-    def add_from_nrl_gui(self, widget):
+        self[item.nrl_keys] = item
+
+    def names(self) -> list[str]:
+        """
+        Returns all the names of the Hardware object stored in the
+        dict.
+
+        Returns:
+            list[str]: A list of strings representing the
+            `name` attribute of each Hardware object in the dict.
+
+        Examples:
+            ['Na', 'RT-130', 'Q330']
+        """
+        # create list of names for each item
+        names = [item.name for item in self.values()]
+        return names
+
+    def index_name(self) -> list[tuple[int, str]]:
+        """
+        Returns a list of tuples with (index, name) for each Hardware object
+        in the entire dict.
+
+        Returns:
+            list[tuple[int, str]]: A list of tuples. Each tuple contains the
+                                   index of the key and the `name` attribute of
+                                   the associated Hardware object in the dict.
+
+        Examples:
+            [(0, 'Na'), (1, 'RT-130'), (2, 'Q330')]
+        """
+        # create list of keys for indexing
+        keys = list(self.keys())
+
+        # create list of tuples with index and name for every key
+        indexed_names = [
+            (keys.index(key), self[key].name)
+            for key in keys
+        ]
+        return indexed_names
+
+    def index(self, name: str) -> int:
+        """
+        Returns the index of the Hardware object with the given name.
+
+        Args:
+            name (str): The name of a Hardware object in the dict.
+
+        Returns:
+            int: The index of the Hardware object with the given name.
+
+        Examples:
+            >>> 'Na'
+            0
+
+            >>> 'RT-130'
+            1
+        """
+        index = self.names().index(name)
+        return index
+
+    def name(self, index: int) -> str:
+        """
+        Returns the name for the given Hardware object index
+        (replaces DLS[index]).
+
+        Args:
+            index (int): The index of a Hardware object in the dict.
+
+        Returns:
+            str: The name of the Hardware at the given index.
+        """
+        name = self.names()[index]
+        return name
+
+    def get(self, name: str) -> Hardware:
+        """
+        Returns the Hardware object with the given name.
+
+        Args:
+            name (str): The name of a Hardware object in the dict.
+
+        Returns:
+            Hardware: A Hardware object.
+        """
+        hardware_obj = self[self.keys_from_name(name)]
+        return hardware_obj
+
+    def keys_from_name(self, name: str) -> tuple:
+        """
+        Returns all the NRL keys for the Hardware object with
+        the given name.
+
+        Args:
+            name (str): The name of the Hardware object.
+
+        Returns:
+            tuple: All the NRL keys associated with the hardware.
+        """
+        keys = [key for key, item in self.items() if item.name == name][0]
+        return keys
+
+    def add_from_nrl_gui(self, widget) -> None:
+        """
+        Adds Hardware object to NrlDict via the NRL Wizard.
+
+        Args:
+            widget (QWizard): The NRL Wizard from the root gui.
+        """
         name = widget.nickname
         o = self.type(name)
         o.from_gui(widget)
         self.append(o)
         save()
 
-    def __repr__(self):
+    def __repr__(self) -> str:
+        """
+        Returns a summary of the Hardware stored in the dict.
+
+        Returns:
+            str: A string summarizing each Hardware object stored
+            in the dict.
+        """
         s = 'Hardware:\n'
         for o in self.values():
             s += o.__repr__()
         return s
 
-def get_nrl_response(dl_keys, sensor_keys):
+
+def get_nrl_response(dl_keys: (list[str]),
+                     sensor_keys: (list[str])) -> Response:
+    """
+    Returns the response using NRL.
+
+    Args:
+        dl_keys (list[str]): List of datalogger keys.
+        sensor_keys (list[str]): List of sensor keys.
+
+    Returns:
+        Response: The Channel Response.
+    """
     nrl = NRL(NRL_ROOT)
     return nrl.get_response(dl_keys, sensor_keys)
 
-sensors = NrlDict(Sensor)
-dataloggers = NrlDict(DataLogger)
 
-def save(filename = defaultsfile):
+SENSORS = NrlDict(Sensor)
+DATALOGGERS = NrlDict(DataLogger)
+
+
+def save(filename: str = defaultsfile) -> None:
+    """
+    Saves file to be used to creates sensors and dataloggers.
+
+    Args:
+        filename (str, optional): File to save to later be used to create
+                                  sensors and dataloggers. Defaults to
+                                  defaultsfile.
+    """
     with open(filename, 'wb') as f:
-        pickle.dump(sensors, f)
-        pickle.dump(dataloggers, f)
+        pickle.dump(SENSORS, f)
+        pickle.dump(DATALOGGERS, f)
 
-def load(filename = defaultsfile):
+
+def load(filename: str = defaultsfile) -> None:
+    """
+    Creates sensors and dataloggers from a given file.
+
+    Args:
+        filename (str, optional): File to be used to create sensors and
+                                  dataloggers. Defaults to `defaultsfile`.
+    """
     if os.path.isfile(filename):
         with open(filename, 'rb') as f:
-            global sensors
-            global dataloggers
-            sensors = pickle.load(f)
-            dataloggers = pickle.load(f)
+            global SENSORS
+            global DATALOGGERS
+            SENSORS = pickle.load(f)
+            DATALOGGERS = pickle.load(f)
     else:
         create_defaults()
 
-def create_defaults():
+
+def create_defaults() -> None:
+    """
+    Method to manually create default sensors and dataloggers.
+    """
     s = Sensor('Na')
-    sensors.append(s)
+    SENSORS.append(s)
     s = Sensor('CMG-3T')
-    s.NRL_keys = ('Guralp', 'CMG-3T', '120s - 50Hz', '1500')
-    sensors.append(s)
+    s.nrl_keys = ('Guralp', 'CMG-3T', '120s - 50Hz', '1500')
+    SENSORS.append(s)
     s = Sensor('Trillium 40')
-    s.NRL_keys = ('Nanometrics', 'Trillium 40')
-    sensors.append(s)
+    s.nrl_keys = ('Nanometrics', 'Trillium 40')
+    SENSORS.append(s)
     s = Sensor('L-22')
-    s.NRL_keys = ('Sercel/Mark Products', 'L-22D', '5470 Ohms', '20000 Ohms')
-    sensors.append(s)
+    s.nrl_keys = (
+        'Sercel/Mark Products',
+        'L-22D',
+        '5470 Ohms',
+        '20000 Ohms'
+    )
+    SENSORS.append(s)
     s = Sensor('STS-2 gen1')
-    s.NRL_keys = ('Streckeisen', 'STS-2', '1500', '1 - installed 01/90 to 09/94')
-    sensors.append(s)
+    s.nrl_keys = (
+        'Streckeisen',
+        'STS-2',
+        '1500',
+        '1 - installed 01/90 to 09/94'
+    )
+    SENSORS.append(s)
     s = Sensor('STS-2 gen2')
-    s.NRL_keys = ('Streckeisen', 'STS-2', '1500', '2 - installed 09/94 to 04/97')
-    sensors.append(s)
+    s.nrl_keys = (
+        'Streckeisen',
+        'STS-2',
+        '1500',
+        '2 - installed 09/94 to 04/97'
+    )
+    SENSORS.append(s)
     s = Sensor('STS-2 gen3')
-    s.NRL_keys = ('Streckeisen', 'STS-2', '1500', '3 - installed 04/97 to present')
-    sensors.append(s)
+    s.nrl_keys = (
+        'Streckeisen',
+        'STS-2',
+        '1500',
+        '3 - installed 04/97 to present'
+    )
+    SENSORS.append(s)
 
     d = DataLogger('Na')
-    dataloggers.append(d)
+    DATALOGGERS.append(d)
     d = DataLogger('RT-130')
-    d.NRL_keys = ('REF TEK', 'RT 130 & 130-SMA', PlaceHolder.GAIN, PlaceHolder.SR)
+    d.nrl_keys = (
+        'REF TEK',
+        'RT 130 & 130-SMA',
+        PlaceHolder.GAIN,
+        PlaceHolder.SR
+    )
     d.soh_resps['LOG'] = None
     d.soh_resps['VM'] = 'RT130_VM_RESP'
-    dataloggers.append(d)
+    DATALOGGERS.append(d)
     d = DataLogger('Q330')
-    d.NRL_keys = ('Quanterra', 'Q330SR', PlaceHolder.GAIN, PlaceHolder.SR, 'LINEAR AT ALL SPS')
+    d.nrl_keys = (
+        'Quanterra',
+        'Q330SR',
+        PlaceHolder.GAIN,
+        PlaceHolder.SR,
+        'LINEAR AT ALL SPS'
+    )
     d.soh_resps['ACE'] = None
     d.soh_resps['LOG'] = None
     d.soh_resps['OCF'] = None
@@ -250,15 +639,18 @@ def create_defaults():
     d.soh_resps['VKI'] = 'Q330_VKI_RESP'
     d.soh_resps['VPB'] = 'Q330_VPB_RESP'
     d.soh_resps['VM'] = 'Q330_VM_RESP'
-    dataloggers.append(d)
+    DATALOGGERS.append(d)
 
     empty_response = Response()
-    for d in dataloggers.values():
+    for d in DATALOGGERS.values():
         for chan, resp_str in d.soh_resps.items():
             if not resp_str:
                 d.soh_resps[chan] = empty_response
             else:
                 filename = d.soh_resps[chan]
-                with open(os.path.join(resp_dir, filename), 'r') as f:
+                with open(
+                    os.path.join(resp_dir, filename), 'r',
+                    encoding='utf-8'
+                ) as f:
                     d.soh_resps[chan] = read_inventory(
                         f, format='RESP')[0][0][0].response