diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index d316de4a72846a87667ba2da295e0b3b2b94026b..18ea38f8ad878371a19ee1f8b73590f3161205ad 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -7,18 +7,38 @@ build_test_image:
     - name: docker:dind
   script:
     - docker build --rm -t ros_acomms-tests:latest -f ros_acomms_tests/tests.Dockerfile .
+  except:
+    - dev/autodocs
+
+build_auto-doc_image:
+  image: docker:stable
+  stage: build
+  tags:
+    - privileged
+  services:
+    - name: docker:dind
+  script:
+    - docker build --rm -t ros_acomms-docs:latest -f docs/auto-docs.Dockerfile .
+  only:
+    - master
+    - tags
+    - dev/autodocs
 
 trigger-ros_acomms_net-pipeline:
   trigger:
     project: acomms/ros_acomms_net
     branch: $NET_DEVEL_BRANCH
     strategy: depend
+  except:
+    - dev/autodocs
 
 trigger-ros_acomms_net_tools-pipeline:
   trigger:
     project: acomms/ros_acomms_net_tools
     branch: $TOOLS_DEVEL_BRANCH
     strategy: depend
+  except:
+    - dev/autodocs
 
 gitlab run_tests:
   stage: test
@@ -40,7 +60,9 @@ gitlab run_tests:
     - rosparam set /clock_generator/publish_rate $CLOCK_PUBLISH_RATE
     - source devel/setup.bash
     - rosrun ros_acomms_tests test_ros_acomms.py
-    - rosrun ros_acomms_tests test_dynamic_queue.py 
+    - rosrun ros_acomms_tests test_dynamic_queue.py
+  except:
+    - dev/autodocs
 
 pypi run_tests:
   stage: test
@@ -58,4 +80,48 @@ pypi run_tests:
     - rosparam set /clock_generator/publish_rate $CLOCK_PUBLISH_RATE
     - source devel/setup.bash
     - rosrun ros_acomms_tests test_ros_acomms.py
-    - rosrun ros_acomms_tests test_dynamic_queue.py 
+    - rosrun ros_acomms_tests test_dynamic_queue.py
+  except:
+    - dev/autodocs
+
+auto-docs-pdf:
+  image: ros_acomms-docs:latest
+  stage: deploy
+  script:
+    - pip install -r requirements.txt
+    - pip install .
+    - cd docs
+    - make latexpdf
+    - mv _build/latex/ros_acomms.pdf ../ros_acomms-${CI_COMMIT_REF_SLUG}.pdf
+  artifacts:
+    paths:
+      - ros_acomms-${CI_COMMIT_REF_SLUG}.pdf
+  only:
+    - master
+    - tags
+    - dev/autodocs
+
+pages:
+  image: ros_acomms-docs:latest
+  stage: deploy
+  script:
+    - source /ros_entrypoint.sh
+    - mkdir -p ~/catkin_ws/src
+    - cd ../ && mv $CI_PROJECT_NAME ~/catkin_ws/src/
+    - cd ~/catkin_ws/src/ros_acomms && pip install -U -r requirements.txt
+    - cd ~/catkin_ws && catkin_make
+    - roscore &
+    - sleep 5
+    - source devel/setup.bash
+    - cd ~/catkin_ws/src/ros_acomms/docs
+    - sphinx-build -b html . ../public
+    - cd /builds/acomms
+    - mv -f ~/catkin_ws/src/$CI_PROJECT_NAME .
+
+  artifacts:
+    paths:
+      - public
+  only:
+    - master
+    - tags
+    - dev/autodocs
diff --git a/README.md b/README.md
old mode 100755
new mode 100644
index dc3d6f174bdc75ec87c68985f387643de26e6f99..34ff71c177337c244d7db5e58d8cda32b01c8b93
--- a/README.md
+++ b/README.md
@@ -1,759 +1,8 @@
-# <b>ros_acomms</b>
+ros_acomms
+==========
 
-## <b>Status of ros_acomms</b>
-ros_acomms is stable enough to be used at sea provided you test your configuration thoroughly before deployment and have suitable backup plans in place.
-<br><br>
+ros_acomms is a ROS package that provides a generic ROS interface to the
+WHOI Micromodem and transparent transport of ROS messages across an
+acoustic link. It also provides simulation capabilities.
 
-## <b>Introduction</b>
-ros_acomms is a ROS package that provides a generic ROS interface to the WHOI Micromodem and transparent transport of ROS messages across an acoustic link. It also provides simulation capabilities.
-
-ros_acomms consists of a number of nodes, services, and messages. This document will seek to provide an overview of the ros_acomms package.
-<br><br>
-
-### <b>Sections</b>
-Node Overview — the node overview section breaks down the different nodes that comprise ros_acomms. Each node description includes a list of publishers, subscribers, and services used by the node along with a short description of the nodes purpose.
-
-Messages — the messages section breaks down each of the messages used by ros_acomms. Each message is listed along with the variables that comprise it
-
-Services — the services section breaks down each of the messages used by ros_acomms. Each service is listed along with the variables that comprise it in the same format as the messages section
-
-Codecs — the codecs section of this document explains the contents of a message config file along with all of the available ltcodecs. 
-
-Modem Simulator Quick Start — the modem simulator quick start section of this document explains how to setup the ros_acomms modem simulator. 
-
-Transferring Files — the transfering files section of this document explains how to transfer files using ros_acomms along with the locations of example scripts
-
-### <b>ROS Message Transport Overview</b>
-ros_acomms provides a complete system for transporting messages across the acoustic link.
-
-![Data Flow Overview](/doc/data_flow_overview.png "Data Flow Overview")
-
-
-To transmit a ROS message:
-  1. The user publishes ROS message on a topic
-  2. The *Message Queue Node* subscribes to that topic.
-  3. When a new ROS message is received, the *Message Queue Node* uses a *Message Codec* to convert the data in the message into a compact bit-packet representation by using *Field Codecs* to encode each message field, including nested ROS messages.
-  4. The *Media Access Controller* (for now, the *TDMA MAC*) queries the *Message Queue Node* to get the highest priority message for transmit
-  5. The *Message Queue Node* identifies the highest priority message, and then uses the appropriate *Packet Codec* to pack as many messages as will fit into one modem *Packet* for transport.
-  6. The *Media Access Controller* queues the *Packet* for transmit on the *Acomms Driver Node*, which interfaces to the modem to actually transmit the packet
-
-On the receive side:
-  1. A packet is received by the modem.
-  2. The *Acomms Driver Node* handles the incoming packet and publishes it as a *ReceivedPacket*
-  3. The *Packet Dispatch Node* evaluates metadata on the packet (the modem SRC, header bytes) and determines which *Packet Codec* to use to decode the packet.
-  4. The *Packet Codec* uses calls one or more *Message Codecs* to decode the packed bits into a ROS message
-  5. The *Packet Dispatch Node* publishes the decoded ROS message on a user-specified topic.
-<br><br>
-## <b>Node Overview</b>
-
-ros_acomms uses a plethora of ROS nodes. The above and below diagrams represent a general overview of the nodes and their relationships. This section will seek to explain each node in more detail. In addition to a brief description of each node, this section will also provide a list of the ROS topics that each node publishes and subscribes to, and services used by the nodes.
-<br><br>
-
-<div align="center">
-
-
-![ros_remus/ros_acomms Overview](/doc/node_connections_simulator.png "ros_acomms Simulator Overview")
-(ros_acomms Simulator Diagram)
-
-![ros_remus/ros_acomms Overview](/doc/node_connections.png "ros_acomms Overview")
-(ros_acomms Diagram)
-</div>
-<br><br>
-<b>Message Queue Node:</b>
-  - The message queue node is responsible for managing the message queue and dispatching messages to the appropriate codecs. The message queue node uses a deque data structure to store messages. Each topic represented in the message codec config has its message queue. The message queue node also provides a mechanism for prioritizing messages, both at configuration time and at runtime. Each topic queue has its own set of specified parameters including its priority, whether the message should be fragmented, and many others. These parameters are documented further in the message codec config portion of this document. Each of the subscribers specified in the message codec config is created on startup by the message queue node. The message queue node uses the codecs to encode incoming messages. 
-
-
-    ```
-    Subscribers:
-      - neighbor_reachability: NeighborReachability
-      - from_acomms/encoded_ack: EncodedAck
-      - message_codec_params['subscribe_topic'] (A subscriber is dynamically generated for each topic listed in the message codec config)
-
-    Publishers:
-      - queue_status
-
-    Services:
-      - priority_update: PriorityUpdate (Change the priority of a topic queue)
-      - queue_active: QueueActive (Enable or disable a topic queue)
-      - get_next_packet_data: GetNextPacketData (Retrieve the highest priority data for transmission)
-
-    Init Parameters:
-      - packet_codecs: dictionary (see codecs section)
-      - unknown_dests_are_reachable: bool (if true, the message queue assumes all of the destination addresses are reachable. If false, only destinations with known neighbor reachability will be marked as reachable)
-      - update_rate: int (rate in hertz at which the queue status is published)
-      - default_dest: int (Default 121)
-      - default_priority: int (Default 10)
-    ```
-
-<b>tdma_node:</b>
-  - The TDMA MAC node (time division multiple access / media access control) coordinates acoustic transmissions on a user specified schedule to allow multiple modems to share the acoustic channel without collisions. The TDMA node queries the Message Queue Node to get the highest priority message for transmit using the get_next_packet_bytes service. 
-
-
-    ```
-    Publishers:
-      - tdma_status: TdmaStatus
-
-    Init Parameters:
-      - num_slots: int (number of time slots used in the network)
-      - slot_duration_seconds: int (length of each time slot)
-      - cycle_start_time: rostime (reference epoch used to determine time slot start)
-      - active_slots: int (length of acoustic packet)
-      - guard_time_seconds: int (time between packet transmission)
-      - packet_length_seconds: int (length of acoustic packet)
-      - always_send_test_data: bool (set to true if TDMA node should send data even when no new message is in the queue)
-      - maximum_miniframe_bytes: int (maximum number of miniframe bytes to populate in the transmitted packet)
-      - maximum_dataframe_bytes: int (maximum number of dataframe bytes to populate in the transmitted packet)
-      - miniframe_rate: int (modem data rate for miniframes)
-      - dataframe_rate: int (modem data rate for dataframes)
-    ```
-
-<b>acomms_driver_node:</b>
-  - The TDMA node queries the *Message Queue Node* to get the highest priority message for transmit
-
-    ```
-    Subscribers:
-      - nmea_to_modem: String (primarily used for debugging, allows user to send NMEA sentences directly to the modem)
-
-    Publishers:
-      - cst: CST (modem cycle statistics)
-      - packet_rx: ReceivedPacket (packet received from the modem)
-      - nmea_from_modem: String (raw NMEA sentences from the modem, primarily used for debugging)
-
-    Init Parameters:
-      - modem_connection_type: string (serial (default) / UDP)
-      - modem_remote_host: string (remote address of UDP modem)
-      - modem_remote_port: int (remote port of UDP modem)
-      - modem_local_host: string (local address for UDP connections (default 0.0.0.0))
-      - modem_local_port: int (local port for UDP connections (defaults to remote port))
-      - modem_serial_port: string (modem serial port for serial connections)
-      - modem_baud_rate: int (baud rate for serial connections)
-      - set_modem_time: bool (set modem time from host computer on startup)
-      - modem_config: dict (dictionary of modem config parameters to be set at startup)
-      - publish_partial_packets: bool (incomplete feature: do not use)
-      - default_modem_dest: int (modem destinations address to use when no address is set in queue_tx_packet service)
-      - use_legacy_packets: bool (force the use of legacy PSK)
-      - use_tdp: bool (force the use of TDP packets rather than TFP packets)
-    ```
-<b>fragmentation_tracker:</b>
-  - The fragmentation_tracker tracks the messages that are fragmented for transmission.
-
-    ```
-    Publishers:
-      - fragmentation_status: FragmentationStatus
-
-    Init Parameters:
-      - sequence_num: int (starting sequence number to use)
-      - fragment_src: int (default source for fragmented packets)
-      - fragment_dest: int (default destination for fragmented packets)
-      - unix_time: time (development feature: do not use)
-      - payload_size_bits: int (sets maximum size of a fragmentation payload)
-      - block_size_bits: int (sets block size used for fragmentation (default 8 bits))
-    ```
-<b>modem_sensor_data_node:</b>
-  - The modem_sensor_data_node queries the modem for NMEA data.
-
-    ```
-    Subscribers:
-      - nmea_from_modem: String
-
-    Publishers:
-      - modem_sensor_data: ModemSensorData
-      - nmea_to_modem: String
-    ```
-
-  <b>modem_sim_node:</b>
-  - The modem_sim_node simulates the modem.
-
-    ```
-    Subscribers:
-      - /acoustic_channel: NeighborReachability
-      - tick_topic_name: EncodedAck
-
-    Publishers:
-      - packet_rx
-      - transmit
-      - /acoustic_channel: QueueStatus
-      - tock_topic_name
-      - /from_acomms/srcPublisher
-
-    Parameters:
-      - bandwidth_hz: float32 (bandwidth used by simulated modem, default 5000)
-      - center_frequency_hz: float32 (center frequency used by simulated modem, default 10000)
-      - SRC: int (SRC address used by simulated modem default, 0)
-      - /use_tick_time: bool (use tick/tock simulation time-stepping, default False)
-      - ambient_noise_db: int (simulated ambient noise, default 60dB)
-      - sim_tick_name: string (name of topic to subscribe to for simulation tick messages, default tick)
-      - sim_tock_name: string (name of the topic to publisher simulation tock messages on, default tock)
-      - modem_location_source: int (default service to get simulated modems location from the read_location service “static” to use static positions specified by latitude longitude and depth parameters)
-      - latitude: float32 (static latitude used for simulation)
-      - longitude: float32 (static longitude used for simulation)
-      - depth: float32 (static depth used for simulation (in meters))
-
-    ```
-  <b>packet_dispatch_node:</b>
-  - The packet_dispatch_node decodes incoming packets and publishes the decoded ROS messages on the appropriate topics.
-
-    ```
-    Subscribers:
-      - packet_rx: ReceivedPacket
-
-    Publishers:
-      - pub_name: list (created as specified in the message codec config)
-
-    Parameters:
-      - packet_codecs: dictionary (created as specified in the message codec config)
-    ```
-  <b>platform_location_node:</b>
-  - The platform_location_node formats incoming data and returns a response tuple.
-
-    ```
-    Services:
-      - read_location: GetPlatformLocation
-    ```
-  <b>sim_packet_performance:</b>
-  - The sim_packet_performance simulates package performance.
-
-    ```
-    Services:
-      - /sim_packet_performance: SimPacketPerformance
-    ```
-
-  <b>sim_transmission_loss_node:</b>
-  - The sim_transmission_loss_node simulates lost transmissions.
-
-    ```
-    Services:
-      - sim_transmission_loss: SimTransmissionLoss (Gets the receive level and latency for an acoustic transmission, given source level and platform locations)
-      - get_optimal_tx_depth: GetOptimalTxDepth (Gets the best depth at which to transmit to maximize receive level at a remote platform)
-
-    Subscribers:
-      - sound_speed_profile: SoundSpeedProfile
-    ```
-  <b>ssp_node:</b>
-  - The ssp_node simulates sound speed profiles.
-
-    ```
-    Subscribers:
-      - ctd: ros_remus/Ctd
-
-    Publishers:
-      - sound_speed_profile: SoundSpeedProfile (Bin-averaged sound speed profile)
-      
-    ```
-  <b>xducer_safety_power_control_node:</b>
-  - The xducer_safety_power_control_node works as the power control of the xducer.
-
-    ```
-    Subscribers:
-      - /status: ros_remus/Status
-    ```
-  <b>version_node:</b>
-  - The version_node prints out version strings about ros_acomms and accompanying packages such as ltcodecs. helpful_tools, and pyacomms.
-
-  <b>test_ nodes:</b>
-  - nodes begining with test_ are nodes that are used for testing ros_acomms. These nodes are not intended to be used in production.
-<br><br>
-
-## <b>Messages</b>
-
-below is a list of all the ROS messages used by ros_acomms along with each node they are used within in ros_acomms.
-  ```
-  CallStatus: 
-  This message is deprecated.  Iridium functionality has moved to the ros_iridium package.
-      - CALL_UP: int8
-      - CALL_DOWN: int8
-      - call_status: int8
-
-  CST (acomms_driver_node, packet_dispatch_node):
-  The CST message fields mirror the uModem2 CycleStats (CST) message fields. See the uModem2 User’s Guide for a description of these fields.
-      - version_number: int8 
-      - mode: int8 
-      - toa: time 
-      - toa_mode: int8 
-      - mfd_peak: uint16 
-      - mfd_pow: int16 
-      - mfd_ratio: int16 
-      - mfd_spl: int16 
-      - agn: int16 
-      - shift_ain: int16 
-      - shift_ainp: int16 
-      - shift_mfd: int16 
-      - shift_p2b: int16 
-      - rate_num: int8 
-      - src: int16 
-      - dest: int16 
-      - psk_error: int16 
-      - packet_type: int8 
-      - num_frames: int16 
-      - bad_frames_num: int16 
-      - snr_rss: int16 
-      - snr_in: float32 
-      - snr_out: float32 
-      - snr_sym: float32 
-      - mse: float32 
-      - dqf: int16 
-      - dop: float32 
-      - noise: int16 
-      - carrier: int32 
-      - bandwidth: int32 
-      - sequence_number: int32 
-      - data_rate: int16 
-      - num_data_frames: int16 
-      - num_bad_data_frames: int16 
-
-  EncodedAck:
-  EncodedAck is internally used by the FragmentationTracker to track message fragments.
-      - header: Header
-      - src: uint8 
-      - dest: uint8 
-      - encodec_ack: uint8[]
-
-  FragmentationStatus (fragmentation_tracker_node):
-  FragmentationStatus is used primarily for diagnostics related to the message fragmentation subsystem.
-      - header: Header 
-      - sequence_num: uint8 
-      - fragment_src: uint8 
-      - fragment_dest: uint8 
-      - unix_time: uint32 
-      - payload_size_blocks: uint16 
-      - block_size_bits: uint8 
-      - transferred_start: uint16 
-      - transferred_end: uint16 
-      - transferred_sections: uint8 
-      - acked_start: uint16 
-      - acked_end: uint16 
-      - acked_sections: uint8 
-
-  IridiumPacket:
-  This message is deprecated.  Iridium functionality has moved to the ros_iridium package.
-      - src: int16
-      - dest: int16
-      - packet_bytes: uint8[]
-
-  ModemSensorData (modem_sensor_data_node):
-  The ModemSensorData message is used to publish voltages and temperatures measured by the modem hardware.
-      - header: Header
-      - pwramp_temp: float32
-      - pwramp_vbat: float32
-      - pwramp_vtransmit: float32
-      - pwramp_modem_bat: float32
-      - modem_temp: float32
-
-  NeighborReachability (message_queue_node):
-  NeighborReachability messages provide a simple means of publishing the availability of other acoustic modems.  It is likely that this message will be replaced in a future version.
-      - dest: int16
-      - reachable: bool
-      - fastest_rate: int16
-
-  NumBtyes:
-  This message is deprecated.
-    - num_bytes: uint16
-
-  Packet (acomms_driver_node):
-  The Packet message represents an acoustic packet.  
-      - src: int16
-      - dest: int16
-      - packet_type: int8 (One of: PACKET_TYPE_UNKNOWN PACKET_TYPE_AUTO, PACKET_TYPE_FSK,PACKET_TYPE_LEGACY_PSK, PACKET_TYPE_FDP)
-      - miniframe_rate: int8
-      - dataframe_rate: int8
-      - miniframe_bytes: uint8[]
-      - dataframe_bytes: uint8[]
-
-  QueueStatus (message_queue_node):
-  The QueueStatus message provides diagnostic insight into the message queue system.
-      - header: Header
-      - message_count: int32
-      - highest_priority: int16
-      - queued_dest_addresses: int16[]
-      - summary_message_counts: SummaryMessageCount[]
-
-  ReceivedIridiumPacket:
-  This message is deprecated.
-
-  ReceivedPacket (modem_sim_node, packet_dispatch_node):
-  The ReceivedPacket message represents a received acoustic packet.  It includes a Packet message with the packet data as well as metadata and packet statistics.
-      - header: Header
-      - Packet: packet
-      - minibytes_valid: int16[] (This can be used by packet codecs to work with partial packets.  Not recommended for end-users)
-      - databytes_valid: int16[] (This can be used by packet codecs to work with partial packets.  Not recommended for end-users)
-      - cst: CST 
-
-  SimPacket (modem_sim_node, sim_packet_performance):
-  SimPacket is used for simulated acoustic packets travelling on the simulated acoustic channel
-      - src_latitude: float32 
-      - src_longitude: float32 
-      - src_depth: float32 
-      - src_tx_level_db: float32 
-      - center_frequency_hz: float32 
-      - bandwidth_hz: float32
-      - transmit_time: time 
-      - transmit_duration: duration 
-      - packet: Packet 
-    
-  SoundsSpeedProfile:
-  The SoundSpeedProfile message describes a sound speed profile.
-      - header: Header
-      - depths: float32[]
-      - sound_speed: float32[]
-
-  Source(acomms_driver_node, modem_sim_node):
-  The Source message is used to publish a modem SRC ID.
-      - source: int8
-
-  SummaryMessageCount (message_queue_node):
-  This message is used internally by the QueueStatus message
-      - priority: int16
-      - dest_address: int16
-      - message_count: int32
-
-  TdmaStatus (tdma_Node, xducer_safety_power):
-  The TdmaStatus message provides diagnostic information about the TDMA MAC layer.
-      - header: Header 
-      - current_slot: int8 
-      - we_are_active: bool 
-      - remaining_slot_seconds: float32 
-      - remaining_active_seconds: float32 
-      - time_to_next_active: float32 
-
-  Tick (modem_sim_node):
-  Tick is used for synchronization with a larger simulation system.
-      - header: Header 
-      - step_id: int32 
-      - status: uint16
-      - duration: duration
-      - STATUS_RESET: uint16 
-      - STATUS_SEQUENCING: uint16 
-      - STATUS_OVERFLOW: uint16 
-
-  Tock (modem_sim_node):
-  Tock is used for synchronization with a larger simulation system.
-      - header: Header 
-      - step_id: int32 
-      - status: uint16
-      - node_name: string
-      - STATUS_RESET: uint16 
-      - STATUS_SEQUENCING: uint16 
-      - STATUS_OVERFLOW: uint16
-
-  TransmittedPacket (modem_sim_node):
-  TransmittedPacket represents a transmitted acoustic packet, including a Packet message and the XST data from the Micromodem.
-      - header: Header 
-      - packet: Packet 
-      - xst: XST 
-
-  XST (modem_sim_node):
-  The XST message corresponds to the Transmit Statistics (XST) message from the uModem2.  See the uModem2 User’s Guide for details.
-      - version_number: int8 
-      - tot_ymd: time 
-      - tot_hms: time 
-      - toa_mode: int8 
-      - mode: int8 
-      - probe_len: int16 
-      - bw_hz: uint32 
-      - carrier_hz: uint32 
-      - rate: int16 
-      - src: int16 
-      - dest: int16 
-      - ack: int16 
-      - nframes_expected: int16 
-      - nframes_sent: int16 
-      - pkt_type: int16 
-      - nbytes: int32 
-  ```
-  
-## <b>Services</b>
-below is a list of all of the services used in ros_acomms along with each node they are used within in ros_acomms.
-```
-  GetNextPacketData (tdma_node, xducer_safety_power_control_node, message_queue_node):
-  GetNextPacketData is used by MAC nodes to retrieve data from the message_queue_node for transmit.  It retrieves the highest priority messages that fit in the number of bytes specified in num_miniframe_bytes and num_dataframe_bytes.
-      Request:
-        - num_miniframe_bytes: int32 
-        - num_dataframe_bytes: int32 
-        - match_dest: bool 
-        - dest: int16 
-        - min_priority: int32  
-      Response:
-        - dest: int16   
-        - miniframe_bytes: uint8[] 
-        - dataframe_bytes: uint8[] 
-        - queued_message_ids: int32[]  
-        - num_miniframe_bytes: int32 
-        - num_dataframe_bytes: int32 
-        - num_messages: int32 
-
-  GetOptimalTxDepth (sim_transmission_loss_node):
-  GetOptimalTxDepth is used to identify the optimal depth at which to transmit to maximize receive level at a given receiver.
-      Request:
-      - src_latitude: float32
-      - src_longitude: float32
-      - src_min_depth: float32
-      - src_max_depth: float32
-      - src_tx_level_db: float32
-      - center_frequency_hz: float32
-      - bandwidth_hz: float32
-      - rcv_latitude: float32
-      - rcv_longitude: float32
-      - rcv_depth: float32
-      - horizontal_range: float32
-    Response:
-      - optimal_tx_depth: float32
-      - rcv_rx_level_db: float32
-
-  GetPlatformLocation (platform_location_node, modem_sim_node):
-  GetPlatformLocation is used to retrieve platform locations in simulation
-    Request:
-      - name: string
-    Response:
-      - valid: bool
-      - latitude: float32
-      - longitude: float32
-      - depth: float32
-
-  PingModem (acomms_driver_node):
-  PingModem is used to send an acoustic modem ping to a remote modem.  If a reply is heard, the one-way travel time will be reported.
-    Request:
-      - dest: uint8
-      - reply_timeout: float32
-    Response:
-      - timed_out: bool
-      - one_way_travel_time: float32
-      - cst: CST
-
-  PingTransponder:
-  This service is under development.   
-    Request:
-      - dest_address: uint8
-    Response:
-      - travel_times: float32[]
-  
-  PriorityUpdate (message_queue_node, test_queue_priority_node):
-  PriorityUpdate is used to change the priority (typically specified in the message codec config) for a message queue associated with a given topic.
-    Request:
-      - topic_name: string
-      - priority_desired: int32
-    Response:
-      - response: bool
-  
-  QueueActive (message_queue_node, test_queue_priority_node):
-  QueueActive is used to enable and disable message queues associated with specified topics at runtime.
-    Request:
-      - topic_name: string
-      - set_active: bool
-    Response:
-      - success: bool
-
-  QueueTxPacket:
-  QueueTxPacket is used to transmit packets with the acoustic modem or acoustic modem simulator.
-    Request:
-      - QUEUE_IMMEDIATE: int8
-      - QUEUE_PPS: int8
-      - QUEUE_TXTRIG: int8
-      - queue: int8
-      - insert_at_head: bool
-      - requested_src_level_db: float32
-      - packet: Packet
-    Response:
-      - success: bool
-      - posistion_in_queue: int32
-      - actual_src_level_db: float32
-  
-  SimPacketPerformance (modem_sim_node, sim_packet_performance_node):
-  SimPacketPerformance is used to simulate the success or failure of a simulated acoustic packet given the receive level, noise level, and packet rate.  The probability of success is based on empirical data.
-    Request:
-      - rx_level_db: float32
-      - noise_level_db: float32
-      - packet_rate: float32
-    Response:
-      - packet_success: bool
-      - miniframe_success: bool[]
-      - frame_succesS: bool[]
-  
-  SimTransmissionLoss (sim_transmission_loss_node, test_sim_transmission_loss_node, modem_sim_node):
-  SimTransmissionLoss is used to get the receive level and latency of a simulated acoustic path.
-    Request:
-      - src_latitude: float32
-      - src_longitude: float32
-      - src_depth: float32
-      - src_tx_level_db: float32
-      - center_frequency_hz: float32
-      - bandwidth_hz: float32
-      - rcv_latitude: float32
-      - rcv_depth: float32
-    Response:
-      - rcv_rx_level_db: float32
-      - transmission_delay: duration
-
-```
-
-## <b>Codecs</b>
-The acoustic link is both high-latency and low-throughput.  These limitations are mostly due to physics, and therefore unavoidable.  As a result, it is important to efficiently pack any data you want to send via acomms.
-
-ros_acomms provides a flexible mechanism for doing this, built of three layers of *codecs* (an amalgam of "encoder" and "decoder"):
-  1. Field codecs
-  2. Message codecs
-  3. Packet codecs 
-
-Field codecs is available in the ltcodec repository, and can be installed via pip on PyPi
-
-Each field in a message represent something: a numeric value, a string, an array of some other type, or a nested message.  The goal of each field codec is to represent those values in as few bits as possible.  In the case of ROS messages, each ROS message primititive type has a default field codec that does a decent job of packing the value into bits.
-
-However, you can use fewer bits to encode the value if you provide more information about the data that goes in the field.  Different types (floats, integers, strings) have different parameters that you can specify on the field.  For example, the smallest integer field used by ROS is an int8, which takes 8 bits.  However, if you specified that a field has a minimum value of 0 and a maximum value of 6, it can be represented using only 3 bits.  The field codecs can do this.  See the documenation on each field codec for a description of the parameters that can reduce the size of encoded values.
-
-Some field codecs are recursive, so that nested message types are supported.
-
-The message codec is responsible for packing the output of the field codecs into a single sequence of bits that represents a single message and vice-versa.
-
-The packet codec takes the output of multiple message codecs and builds an acomms packet that contains one or more messages, and does the inverse on the receive side.
-
-Field codecs, message codecs, and packet codecs are all classes that derive from a base FieldCodec, MessageCodec, or PacketCodec class.  Writing new codecs is intentionally simple.
-
-There is an important distinction between the message codec system used here and that used by other systems, such as Protobuf, Msgpack, or CBOR.  This codec system optimizes for size over descriptiveness.  From a given packed sequence of bits, there is no way to decode it without knowing the encoding scheme.
-
-ltcodecs includes all of the message types supported by ros messages. Each codec type requires its own set of configurable parameters that need to be specified in the message codec config. In order to interface with ros_acomms you will need to create codec config file. In your message codec config file the below parameters will need to be included:
-```
-- codec_name: default
-  match_src: []
-  match_dest: []
-  except_src: []
-  except_dest: []
-  packet_codec: ros
-  miniframe_header: []
-  dataframe_header: []
-  remove_headers: bool
-  message_codecs: []
-```
-
-Under the message codecs section of your message codec config file an entry will need to be made for every publish / subscriber paired that will be sent through ros_acomms. Each entry in the list will need the following fields:
-
-```
-  - id: int
-    message_codec: default
-    subscribe_topic: string
-    publish_topic: string
-    ros_type: string
-    default_dest: int
-    queue_order: fifo/lifo
-    queue_maxsize: int
-    is_active: bool
-    allow_fragmentation: bool
-    priority: int
-    fields:
-      $field_name:
-        codec: $CodecName
-        $parameters
-```
-
-An example message codec config can be seen below:
-
-<div align="center">
-
-![Config File](/doc/queueConfig.png "Config File")
-</div>
-All of the codec types and associated parameters(* Indicates Required Fields) can be seen below:
-```
-bool:
-  - none
-
-bytes:
-This codec is included for compatibility with systems using CCL messages.  It is not recommended for use with new systems.
-  - max_length: int* (Maximum number of bytes that can be encoded)
-
-ccl_latlon_bcd:
-This codec is included for compatibility with systems using CCL messages.  It is not recommended for use with new systems.
-  - lat_not_lon: bool (default: true)
-
-ccl_latlon:
-This codec is included for compatibility with systems using CCL messages.  It is not recommended for use with new systems.
-
-linspace_float, linspace:
-The linspace codec encodes floats as the nearest value among a specified number of linearly-spaced values.  Only one of resolution, num_values, or num_bits may be specified.  
-  - min_value: float
-  - max_value: float
-  - resolution: float ((default: none) Spacing between encoded values)
-  - num_values: int ((default: none) Number of encoded values between min and max values)
-  - num_bits: int ((default: none) Number of bits to use to encode values between min and max values)
-
-integer:
-  - min_value: int*
-  - max_value: int*
-  - resolution: int ((default 1) Can be set to values other than 1 to reduce resolution and decrease encoded size.)
-  - little_endian: bool (default: false)
-
-int8, int16, int32, int64:
-Fixed-length integer codecs, no user-specified parameters.
-
-uint8, uint16, uint32, uint64:
-Fixed-length unsigned integer codecs; no user-specified parameters
-
-float:
-  - min_value: float*
-  - max_value: float*
-  - precision: int* (Default 0, number of significant digits after decimal point.)
-
-float32, float64:
-IEEE floating point formats; no user-specified parameters
-
-string, ascii:
-  - max_length: int (default: 128)
-  - bits_per_char: int ((default: 7) Defaults to 7-bit ASCII.  6 will use AIS-standard 6-bit ASCII to encode strings (no lowercase letters, limited symbols)
-  - tail: bool ((default: false) If the string exceeds max_length, it will be truncated.  If tail is true, the end of the string will be sent rather than the beginning.)
-
-msg, ros_msg:
-This encodes a ROS message, and must contain a fields dict with types
-  - ros_type: str*
-  - fields: dict (default: none)
-
-variable_len_array:
-  - element_type: str*
-  - max_length: int*
-  - element_paramets: str
-
-fixed_len_array:
-  - element_type: str*
-  - length: int*
-  - element_paramets: str (default: none)
-
-padding, pad:
-  - num_bits: int*
-
-time, rostime:
-  - precision: int (default: 0)
-  - epoch_start: int (default: 1622520000)
-  - epoch_end: int (default: (2**31 - 1))
-
-```
-
-## <b>Modem Simulator Quick Start</b>
-
-You can use the included Dockerfile to build a Docker container, download a pre-built Docker image, or just check out this repository and run one of the example launch files.
-
-Install:
- 1. Create a ROS workspace, if you don't already have one. See the ROS documentation for more information.
- 2. Clone ros_acomms: `git clone https://git.whoi.edu/acomms/ros_acomms.git` 
- 3. Change directory into ros_acomms `cd ros_acomms`
- 4. Run setup.bash `pip install -r requirements.txt`
- 5. Rebuild your workspace with `catkin_make`
- 6. Run the modem_sim_test launch file from a console.  It will print messages to stdout.  Assuming you are in your ROS workspace root: `roslaunch src/ros_acomms/src/launch/modem_sim_test.launch`
-
-Modify this launch file to change parameters, spawn additional modems, etc. To display version information make sure to include the version_node in your launch file.
-
-To interface with ros_acomms first copy queue_test.launch and message_codec_config.yaml from /launch and put them into your own ROS repo. The message_codec_config.yaml file will need to be adjusted to fit your messages. Make sure to edit the subscribe_topic, publish_topic, and ros_type fields to match your namespaces. Additionally the fields key will need to be updated for each parameter within your message. Each message should have its own message_codec within the config. All unnecessary message configs can be safely removed.
-
-Info codec typing can be found at /src/acomms_codes/ltcodecs/__init__.py. Further exploration of parameters / defaults can be found in the specific codec type files held within the ltcodecs directory.
-
-If your message is being sent / recived more than once it is likely the way you have your namespaces set up. One solution is to remove message_queue_node from one of the modems and packet_dispatch from the other. Additionally, you can use namespaces like /modem0/command.
-<br><br>
-
-## <b>Differences between the modem driver and modem sim</b>
-Beyond the obvious differences (the sim supports simulation, including sim time and synchronization), there are a few differences:
- 1. The sim does not currently support the tx_inhibit dynamic reconfigure parameter.
-<br><br>
-
-
-## <b>Message Fragmentation and File Transfer</b>
-
-ros_acomms implements a system for fragmenting larger messages to send them across the acoustic link. If the fragmentation parameter is enabled in the message codec config (discussed in part 5), larger will be fragmented and sent in pieces. The fragmentation tracker node is responsible for making sure all the individual pieces of the fragmented message are correctly transmitted. Once received, the pieces of the message are reassembled and published on the topic specified in the message codec config.
-
-By default, the fragmentation system is configured to allow transmitting messages up to about 65500 bytes in size.  It can be configured to send larger messages, but this is likely not advisable over acoustic links.  Even 64kB messages may prove difficult to transmit within a reasonable time, as a typical modem packet ranges in size from a few hundred bytes to about 2kB.  
-
-Files can be transferred across an acoustic link using ros_acomms by packing them in a uint8[] field of a ROS message. Python, C, JPG, PNG, or any other types of files can be parsed using Python and then packed into a message that can be sent through ros_acomms. Files (messages) that are too large to fit in a single modem packet will be fragmented as long as message fragmentation is enabled in the message codec config.
+Check out the [Documentation](https://acomms.pages.whoi.edu/ros_acomms)
diff --git a/doc/node_connections.png b/doc/node_connections.png
deleted file mode 100644
index 6360aeed4c19a03793e00e8c91fc808dd69677e2..0000000000000000000000000000000000000000
Binary files a/doc/node_connections.png and /dev/null differ
diff --git a/doc/node_connections_simulator.png b/doc/node_connections_simulator.png
deleted file mode 100644
index 59b5c028e459da11b2e5a97266d127949a292c3e..0000000000000000000000000000000000000000
Binary files a/doc/node_connections_simulator.png and /dev/null differ
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..d4bb2cbb9eddb1bb1b4f366623044af8e4830919
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS    ?=
+SPHINXBUILD   ?= sphinx-build
+SOURCEDIR     = .
+BUILDDIR      = _build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/auto-docs.Dockerfile b/docs/auto-docs.Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..bbcc9436223a75e8573b87c9859cdf4cc9c0b5cb
--- /dev/null
+++ b/docs/auto-docs.Dockerfile
@@ -0,0 +1,39 @@
+FROM ros:noetic-ros-core
+
+# Update and upgrade packages, then install common dependencies
+RUN apt-get update -qq && \
+      apt-get upgrade -y -qq && \
+      apt-get install -y -qq \
+      python3-pip \
+      git \
+      graphviz \
+      imagemagick \
+      make \
+      latexmk \
+      lmodern \
+      fonts-freefont-otf \
+      texlive-latex-recommended \
+      texlive-latex-extra \
+      texlive-fonts-recommended \
+      texlive-fonts-extra \
+      texlive-lang-cjk \
+      texlive-lang-chinese \
+      texlive-lang-japanese \
+      texlive-luatex \
+      texlive-xetex \
+      xindy \
+      tex-gyre \
+      tree && \
+      apt-get autoremove -y && \
+      apt-get clean && \
+      rm -rf /var/lib/apt/lists/*
+
+# Install ROS packages
+RUN apt-get update -qq && \
+      apt-get upgrade -y -qq && \
+      apt-get install -y -qq \
+      ros-noetic-dynamic-reconfigure
+
+# Install Python packages
+RUN pip install -U sphinx pytest sphinx-book-theme myst_parser && \
+      pip install -U git+https://github.com/matteoragni/sphinx_rosmsgs.git@master#egg=sphinx_rosmsgs
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 0000000000000000000000000000000000000000..1e2075e25d196dc23504d22ffa8dc26c8c2cf31b
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,56 @@
+import os
+import sys
+
+# Navigate up two directory levels
+sys.path.insert(0, os.path.abspath(".."))
+sys.path.insert(0, os.path.abspath("../ros_acomms/src"))
+sys.path.insert(0, os.path.abspath("../ros_acomms_modeling/src"))
+sys.path.insert(0, os.path.abspath("../ros_acomms_tests/src"))
+sys.path.insert(0, os.path.abspath("../ros_acomms_msgs/msg"))
+sys.path.insert(0, os.path.abspath("../../../devel/lib/python3/dist-packages"))
+
+# for dirpath, dirnames, filenames in os.walk(
+#    os.path.abspath("../../../devel/lib/python3/dist-packages/ros_acomms")
+# ):
+#   print(dirpath)
+# sys.path.insert(0, dirpath)
+# print(sys.path)
+# Configuration file for the Sphinx documentation builder.
+#
+# For the full list of built-in configuration values, see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+# -- Project information -----------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
+
+project = "ros_acomms"
+copyright = "2023, Woods Hole Oceanographic Institution"
+author = "Woods Hole Oceanographic Institution — Acomms Group"
+release = "latest"
+
+# -- General configuration ---------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
+
+# Napoleon allows using numpy- or Google-style docstrings
+extensions = ["myst_parser", "sphinx.ext.autodoc", "sphinx.ext.napoleon", "sphinx_rosmsgs"]
+
+exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
+
+source_suffix = {
+    '.rst': 'restructuredtext',
+    '.md': 'markdown',
+}
+
+# -- Options for LaTeX output ------------------------------------------------
+# Disable index page
+latex_elements = {
+    "makeindex": "",
+    "printindex": "",
+}
+
+# -- Options for HTML output -------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
+html_theme = "sphinx_book_theme"
+add_function_parentheses = True
+add_module_names = True
+rosmsg_path_root = ["../ros_acomms_msgs"]
diff --git a/doc/data_flow_overview.png b/docs/images/data_flow_overview.png
similarity index 100%
rename from doc/data_flow_overview.png
rename to docs/images/data_flow_overview.png
diff --git a/doc/queueConfig.png b/docs/images/queueConfig.png
similarity index 100%
rename from doc/queueConfig.png
rename to docs/images/queueConfig.png
diff --git a/docs/images/ros_acomms-IEEE-AUV-2018-preprint.pdf b/docs/images/ros_acomms-IEEE-AUV-2018-preprint.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..148dcb9779eb65c9e6666239f55962376e97b916
Binary files /dev/null and b/docs/images/ros_acomms-IEEE-AUV-2018-preprint.pdf differ
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644
index 0000000000000000000000000000000000000000..37080e3ad3d25ad60ae3a2624c0f9bd89fdd57f4
--- /dev/null
+++ b/docs/index.rst
@@ -0,0 +1,23 @@
+ros_acomms
+==========
+
+ros_acomms is a ROS package that provides a generic ROS interface to the
+WHOI Micromodem and transparent transport of ROS messages across an
+acoustic link. It also provides simulation capabilities.
+
+If you use this package in any of your published work, please cite the associated paper:
+
+  E. Gallimore et al., "ROS Message Transport over Underwater Acoustic Links with ros_acomms," in 2022 IEEE/OES Autonomous Underwater Vehicles Symposium (AUV), Sep. 2022, pp. 1-6. doi: 10.1109/AUV53081.2022.9965848.
+
+Here is a `preprint of the paper </images/ros_acomms-IEEE-AUV-2018-preprint.pdf>`_ you can view or download.  The published version is on `IEEE Xplore <https://ieeexplore.ieee.org/document/9965848>`_.
+
+
+.. toctree::
+   :maxdepth: 2
+   :caption: Contents
+
+   overview
+   ros_acomms/modules
+   ros_acomms_modeling/modules
+   ros_acomms_tests/modules
+   ros_acomms_msgs/modules
\ No newline at end of file
diff --git a/docs/make.bat b/docs/make.bat
new file mode 100644
index 0000000000000000000000000000000000000000..747ffb7b3033659bdd2d1e6eae41ecb00358a45e
--- /dev/null
+++ b/docs/make.bat
@@ -0,0 +1,35 @@
+@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+	set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=source
+set BUILDDIR=build
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+	echo.
+	echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+	echo.installed, then set the SPHINXBUILD environment variable to point
+	echo.to the full path of the 'sphinx-build' executable. Alternatively you
+	echo.may add the Sphinx directory to PATH.
+	echo.
+	echo.If you don't have Sphinx installed, grab it from
+	echo.https://www.sphinx-doc.org/
+	exit /b 1
+)
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+
+:end
+popd
diff --git a/docs/overview.rst b/docs/overview.rst
new file mode 100644
index 0000000000000000000000000000000000000000..43a3c79247f56d8e98ed81277e53a44538bf0e4f
--- /dev/null
+++ b/docs/overview.rst
@@ -0,0 +1,941 @@
+ros_acomms Overview
+===================
+
+ros_acomms is a ROS package that provides a generic ROS interface to the
+WHOI Micromodem and transparent transport of ROS messages across an
+acoustic link. It also provides simulation capabilities.
+
+Status of ros_acomms
+--------------------
+
+ros_acomms is stable enough to be used at sea and has seen significant
+deployment time.
+
+As always, you should test your configuration thoroughly before
+deployment and have suitable backup plans in place.
+
+Note that the documentation is much more of a work-in-progress than the code is.
+We're hoping to have it cleaned up soon.  This section is basically a copy-paste
+job from the old README file, and is out of date in places.  The module
+documentation is generally up-to-date, but may not be complete yet.
+
+
+Sections
+~~~~~~~~
+
+Node Overview — the node overview section breaks down the different
+nodes that comprise ros_acomms. Each node description includes a list of
+publishers, subscribers, and services used by the node along with a
+short description of the nodes purpose.
+
+Messages — the messages section breaks down each of the messages used by
+ros_acomms. Each message is listed along with the variables that
+comprise it
+
+Services — the services section breaks down each of the messages used by
+ros_acomms. Each service is listed along with the variables that
+comprise it in the same format as the messages section
+
+Codecs — the codecs section of this document explains the contents of a
+message config file along with all of the available ltcodecs.
+
+Modem Simulator Quick Start — the modem simulator quick start section of
+this document explains how to setup the ros_acomms modem simulator.
+
+Transferring Files — the transfering files section of this document
+explains how to transfer files using ros_acomms along with the locations
+of example scripts
+
+ROS Message Transport Overview
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ros_acomms provides a complete system for transporting messages across
+the acoustic link.
+
+.. figure:: /images/data_flow_overview.png
+   :alt: Data Flow Overview
+
+   Data Flow Overview
+
+To transmit a ROS message:
+
+1. The user publishes ROS message on a topic
+2. The *Message Queue Node* subscribes to that topic.
+3. When a new ROS message is received, the *Message Queue Node* uses a
+   *Message Codec* to convert the data in the message into a compact
+   bit-packet representation by using *Field Codecs* to encode each
+   message field, including nested ROS messages.
+4. The *Media Access Controller* (for now, the *TDMA MAC*) queries the
+   *Message Queue Node* to get the highest priority message for transmit
+5. The *Message Queue Node* identifies the highest priority message, and
+   then uses the appropriate *Packet Codec* to pack as many messages as
+   will fit into one modem *Packet* for transport.
+6. The *Media Access Controller* queues the *Packet* for transmit on the
+   *Acomms Driver Node*, which interfaces to the modem to actually
+   transmit the packet
+
+On the receive side:
+
+1. A packet is received by the modem.
+2. The *Acomms Driver Node* handles the incoming packet and publishes it
+   as a *ReceivedPacket*
+3. The *Packet Dispatch Node* evaluates metadata on the packet (the
+   modem SRC, header bytes) and determines which *Packet Codec* to use
+   to decode the packet.
+4. The *Packet Codec* uses calls one or more *Message Codecs* to decode
+   the packed bits into a ROS message
+5. The *Packet Dispatch Node* publishes the decoded ROS message on a
+   user-specified topic.
+
+When using simulation, the *Acomms Driver Node* is replaced by the
+*Modem Sim Node*, but the rest of the flow is the same.
+
+Node Overview
+-------------
+
+A functional ros_acomms system requires instantiating several nodes, as
+described in the data flow description above.
+
+Details of some key nodes are highlighted below. Additional details can
+be found in the documentation for each node class.
+
+Message Queue Node: - The message queue node is responsible for managing
+the message queue and dispatching messages to the appropriate codecs.
+The message queue node uses a deque data structure to store messages.
+Each topic represented in the message codec config has its message
+queue. The message queue node also provides a mechanism for prioritizing
+messages, both at configuration time and at runtime. Each topic queue
+has its own set of specified parameters including its priority, whether
+the message should be fragmented, and many others. These parameters are
+documented further in the message codec config portion of this document.
+Each of the subscribers specified in the message codec config is created
+on startup by the message queue node. The message queue node uses the
+codecs to encode incoming messages.
+
+::
+
+   ```
+   Subscribers:
+     - neighbor_reachability: NeighborReachability
+     - from_acomms/encoded_ack: EncodedAck
+     - message_codec_params['subscribe_topic'] (A subscriber is dynamically generated for each topic listed in the message codec config)
+
+   Publishers:
+     - queue_status
+
+   Services:
+     - priority_update: PriorityUpdate (Change the priority of a topic queue)
+     - queue_active: QueueActive (Enable or disable a topic queue)
+     - get_next_packet_data: GetNextPacketData (Retrieve the highest priority data for transmission)
+
+   Init Parameters:
+     - packet_codecs: dictionary (see codecs section)
+     - unknown_dests_are_reachable: bool (if true, the message queue assumes all of the destination addresses are reachable. If false, only destinations with known neighbor reachability will be marked as reachable)
+     - update_rate: int (rate in hertz at which the queue status is published)
+     - default_dest: int (Default 121)
+     - default_priority: int (Default 10)
+   ```
+
+tdma_node:
+
+-  The TDMA MAC node (time division multiple access / media access
+   control) coordinates acoustic transmissions on a user specified
+   schedule to allow multiple modems to share the acoustic channel
+   without collisions. The TDMA node queries the Message Queue Node to
+   get the highest priority message for transmit using the
+   get_next_packet_bytes service.
+
+   ::
+
+      Publishers:
+        - tdma_status: TdmaStatus
+
+      Init Parameters:
+        - num_slots: int (number of time slots used in the network)
+        - slot_duration_seconds: int (length of each time slot)
+        - cycle_start_time: rostime (reference epoch used to determine time slot start)
+        - active_slots: int (length of acoustic packet)
+        - guard_time_seconds: int (time between packet transmission)
+        - packet_length_seconds: int (length of acoustic packet)
+        - always_send_test_data: bool (set to true if TDMA node should send data even when no new message is in the queue)
+        - maximum_miniframe_bytes: int (maximum number of miniframe bytes to populate in the transmitted packet)
+        - maximum_dataframe_bytes: int (maximum number of dataframe bytes to populate in the transmitted packet)
+        - miniframe_rate: int (modem data rate for miniframes)
+        - dataframe_rate: int (modem data rate for dataframes)
+
+acomms_driver_node:
+
+-  The TDMA node queries the *Message Queue Node* to get the highest
+   priority message for transmit
+
+   ::
+
+      ```
+      Subscribers:
+        - nmea_to_modem: String (primarily used for debugging, allows user to send NMEA sentences directly to the modem)
+
+      Publishers:
+        - cst: CST (modem cycle statistics)
+        - packet_rx: ReceivedPacket (packet received from the modem)
+        - nmea_from_modem: String (raw NMEA sentences from the modem, primarily used for debugging)
+
+      Init Parameters:
+        - modem_connection_type: string (serial (default) / UDP)
+        - modem_remote_host: string (remote address of UDP modem)
+        - modem_remote_port: int (remote port of UDP modem)
+        - modem_local_host: string (local address for UDP connections (default 0.0.0.0))
+        - modem_local_port: int (local port for UDP connections (defaults to remote port))
+        - modem_serial_port: string (modem serial port for serial connections)
+        - modem_baud_rate: int (baud rate for serial connections)
+        - set_modem_time: bool (set modem time from host computer on startup)
+        - modem_config: dict (dictionary of modem config parameters to be set at startup)
+        - publish_partial_packets: bool (incomplete feature: do not use)
+        - default_modem_dest: int (modem destinations address to use when no address is set in queue_tx_packet service)
+        - use_legacy_packets: bool (force the use of legacy PSK)
+        - use_tdp: bool (force the use of TDP packets rather than TFP packets)
+      ```
+
+   fragmentation_tracker:
+
+-  The fragmentation_tracker tracks the messages that are fragmented for
+   transmission.
+
+   ::
+
+      ```
+      Publishers:
+        - fragmentation_status: FragmentationStatus
+
+      Init Parameters:
+        - sequence_num: int (starting sequence number to use)
+        - fragment_src: int (default source for fragmented packets)
+        - fragment_dest: int (default destination for fragmented packets)
+        - unix_time: time (development feature: do not use)
+        - payload_size_bits: int (sets maximum size of a fragmentation payload)
+        - block_size_bits: int (sets block size used for fragmentation (default 8 bits))
+      ```
+
+   modem_sensor_data_node:
+
+-  The modem_sensor_data_node queries the modem for NMEA data.
+
+   ::
+
+      Subscribers:
+        - nmea_from_modem: String
+
+      Publishers:
+        - modem_sensor_data: ModemSensorData
+        - nmea_to_modem: String
+
+modem_sim_node:
+
+-  The modem_sim_node simulates the modem.
+
+   ::
+
+      Subscribers:
+        - /acoustic_channel: NeighborReachability
+        - tick_topic_name: EncodedAck
+
+      Publishers:
+        - packet_rx
+        - transmit
+        - /acoustic_channel: QueueStatus
+        - tock_topic_name
+        - /from_acomms/srcPublisher
+
+      Parameters:
+        - bandwidth_hz: float32 (bandwidth used by simulated modem, default 5000)
+        - center_frequency_hz: float32 (center frequency used by simulated modem, default 10000)
+        - SRC: int (SRC address used by simulated modem default, 0)
+        - /use_tick_time: bool (use tick/tock simulation time-stepping, default False)
+        - ambient_noise_db: int (simulated ambient noise, default 60dB)
+        - sim_tick_name: string (name of topic to subscribe to for simulation tick messages, default tick)
+        - sim_tock_name: string (name of the topic to publisher simulation tock messages on, default tock)
+        - modem_location_source: int (default service to get simulated modems location from the read_location service “static” to use static positions specified by latitude longitude and depth parameters)
+        - latitude: float32 (static latitude used for simulation)
+        - longitude: float32 (static longitude used for simulation)
+        - depth: float32 (static depth used for simulation (in meters))
+
+   packet_dispatch_node:
+
+-  The packet_dispatch_node decodes incoming packets and publishes the
+   decoded ROS messages on the appropriate topics.
+
+   ::
+
+      Subscribers:
+        - packet_rx: ReceivedPacket
+
+      Publishers:
+        - pub_name: list (created as specified in the message codec config)
+
+      Parameters:
+        - packet_codecs: dictionary (created as specified in the message codec config)
+
+   platform_location_node:
+
+-  The platform_location_node formats incoming data and returns a
+   response tuple.
+
+   ::
+
+      Services:
+        - read_location: GetPlatformLocation
+
+   sim_packet_performance:
+
+-  The sim_packet_performance simulates package performance.
+
+   ::
+
+      Services:
+        - /sim_packet_performance: SimPacketPerformance
+
+sim_transmission_loss_node:
+
+-  The sim_transmission_loss_node simulates lost transmissions.
+
+   ::
+
+      Services:
+        - sim_transmission_loss: SimTransmissionLoss (Gets the receive level and latency for an acoustic transmission, given source level and platform locations)
+        - get_optimal_tx_depth: GetOptimalTxDepth (Gets the best depth at which to transmit to maximize receive level at a remote platform)
+
+      Subscribers:
+        - sound_speed_profile: SoundSpeedProfile
+
+   ssp_node:
+
+-  The ssp_node simulates sound speed profiles.
+
+   ::
+
+      Subscribers:
+        - ctd: ros_remus/Ctd
+
+      Publishers:
+        - sound_speed_profile: SoundSpeedProfile (Bin-averaged sound speed profile)
+
+   xducer_safety_power_control_node:
+
+-  The xducer_safety_power_control_node works as the power control of
+   the xducer.
+
+   ::
+
+      Subscribers:
+        - /status: ros_remus/Status
+
+   version_node:
+
+-  The version_node prints out version strings about ros_acomms and
+   accompanying packages such as ltcodecs. helpful_tools, and pyacomms.
+
+test\_ nodes:
+
+-  nodes begining with test\_ are nodes that are used for testing
+   ros_acomms. These nodes are not intended to be used in production.
+
+Messages
+--------
+
+below is a list of all the ROS messages used by ros_acomms along with
+each node they are used within in ros_acomms.
+
+::
+
+   CallStatus:
+   This message is deprecated.  Iridium functionality has moved to the ros_iridium package.
+       - CALL_UP: int8
+       - CALL_DOWN: int8
+       - call_status: int8
+
+   CST (acomms_driver_node, packet_dispatch_node):
+   The CST message fields mirror the uModem2 CycleStats (CST) message fields. See the uModem2 User’s Guide for a description of these fields.
+       - version_number: int8
+       - mode: int8
+       - toa: time
+       - toa_mode: int8
+       - mfd_peak: uint16
+       - mfd_pow: int16
+       - mfd_ratio: int16
+       - mfd_spl: int16
+       - agn: int16
+       - shift_ain: int16
+       - shift_ainp: int16
+       - shift_mfd: int16
+       - shift_p2b: int16
+       - rate_num: int8
+       - src: int16
+       - dest: int16
+       - psk_error: int16
+       - packet_type: int8
+       - num_frames: int16
+       - bad_frames_num: int16
+       - snr_rss: int16
+       - snr_in: float32
+       - snr_out: float32
+       - snr_sym: float32
+       - mse: float32
+       - dqf: int16
+       - dop: float32
+       - noise: int16
+       - carrier: int32
+       - bandwidth: int32
+       - sequence_number: int32
+       - data_rate: int16
+       - num_data_frames: int16
+       - num_bad_data_frames: int16
+
+   EncodedAck:
+   EncodedAck is internally used by the FragmentationTracker to track message fragments.
+       - header: Header
+       - src: uint8
+       - dest: uint8
+       - encodec_ack: uint8[]
+
+   FragmentationStatus (fragmentation_tracker_node):
+   FragmentationStatus is used primarily for diagnostics related to the message fragmentation subsystem.
+       - header: Header
+       - sequence_num: uint8
+       - fragment_src: uint8
+       - fragment_dest: uint8
+       - unix_time: uint32
+       - payload_size_blocks: uint16
+       - block_size_bits: uint8
+       - transferred_start: uint16
+       - transferred_end: uint16
+       - transferred_sections: uint8
+       - acked_start: uint16
+       - acked_end: uint16
+       - acked_sections: uint8
+
+   IridiumPacket:
+   This message is deprecated.  Iridium functionality has moved to the ros_iridium package.
+       - src: int16
+       - dest: int16
+       - packet_bytes: uint8[]
+
+   ModemSensorData (modem_sensor_data_node):
+   The ModemSensorData message is used to publish voltages and temperatures measured by the modem hardware.
+       - header: Header
+       - pwramp_temp: float32
+       - pwramp_vbat: float32
+       - pwramp_vtransmit: float32
+       - pwramp_modem_bat: float32
+       - modem_temp: float32
+
+   NeighborReachability (message_queue_node):
+   NeighborReachability messages provide a simple means of publishing the availability of other acoustic modems.  It is likely that this message will be replaced in a future version.
+       - dest: int16
+       - reachable: bool
+       - fastest_rate: int16
+
+   NumBtyes:
+   This message is deprecated.
+     - num_bytes: uint16
+
+   Packet (acomms_driver_node):
+   The Packet message represents an acoustic packet.
+       - src: int16
+       - dest: int16
+       - packet_type: int8 (One of: PACKET_TYPE_UNKNOWN PACKET_TYPE_AUTO, PACKET_TYPE_FSK,PACKET_TYPE_LEGACY_PSK, PACKET_TYPE_FDP)
+       - miniframe_rate: int8
+       - dataframe_rate: int8
+       - miniframe_bytes: uint8[]
+       - dataframe_bytes: uint8[]
+
+   QueueStatus (message_queue_node):
+   The QueueStatus message provides diagnostic insight into the message queue system.
+       - header: Header
+       - message_count: int32
+       - highest_priority: int16
+       - queued_dest_addresses: int16[]
+       - summary_message_counts: SummaryMessageCount[]
+
+   ReceivedIridiumPacket:
+   This message is deprecated.
+
+   ReceivedPacket (modem_sim_node, packet_dispatch_node):
+   The ReceivedPacket message represents a received acoustic packet.  It includes a Packet message with the packet data as well as metadata and packet statistics.
+       - header: Header
+       - Packet: packet
+       - minibytes_valid: int16[] (This can be used by packet codecs to work with partial packets.  Not recommended for end-users)
+       - databytes_valid: int16[] (This can be used by packet codecs to work with partial packets.  Not recommended for end-users)
+       - cst: CST
+
+   SimPacket (modem_sim_node, sim_packet_performance):
+   SimPacket is used for simulated acoustic packets travelling on the simulated acoustic channel
+       - src_latitude: float32
+       - src_longitude: float32
+       - src_depth: float32
+       - src_tx_level_db: float32
+       - center_frequency_hz: float32
+       - bandwidth_hz: float32
+       - transmit_time: time
+       - transmit_duration: duration
+       - packet: Packet
+
+   SoundsSpeedProfile:
+   The SoundSpeedProfile message describes a sound speed profile.
+       - header: Header
+       - depths: float32[]
+       - sound_speed: float32[]
+
+   Source(acomms_driver_node, modem_sim_node):
+   The Source message is used to publish a modem SRC ID.
+       - source: int8
+
+   SummaryMessageCount (message_queue_node):
+   This message is used internally by the QueueStatus message
+       - priority: int16
+       - dest_address: int16
+       - message_count: int32
+
+   TdmaStatus (tdma_Node, xducer_safety_power):
+   The TdmaStatus message provides diagnostic information about the TDMA MAC layer.
+       - header: Header
+       - current_slot: int8
+       - we_are_active: bool
+       - remaining_slot_seconds: float32
+       - remaining_active_seconds: float32
+       - time_to_next_active: float32
+
+   Tick (modem_sim_node):
+   Tick is used for synchronization with a larger simulation system.
+       - header: Header
+       - step_id: int32
+       - status: uint16
+       - duration: duration
+       - STATUS_RESET: uint16
+       - STATUS_SEQUENCING: uint16
+       - STATUS_OVERFLOW: uint16
+
+   Tock (modem_sim_node):
+   Tock is used for synchronization with a larger simulation system.
+       - header: Header
+       - step_id: int32
+       - status: uint16
+       - node_name: string
+       - STATUS_RESET: uint16
+       - STATUS_SEQUENCING: uint16
+       - STATUS_OVERFLOW: uint16
+
+   TransmittedPacket (modem_sim_node):
+   TransmittedPacket represents a transmitted acoustic packet, including a Packet message and the XST data from the Micromodem.
+       - header: Header
+       - packet: Packet
+       - xst: XST
+
+   XST (modem_sim_node):
+   The XST message corresponds to the Transmit Statistics (XST) message from the uModem2.  See the uModem2 User’s Guide for details.
+       - version_number: int8
+       - tot_ymd: time
+       - tot_hms: time
+       - toa_mode: int8
+       - mode: int8
+       - probe_len: int16
+       - bw_hz: uint32
+       - carrier_hz: uint32
+       - rate: int16
+       - src: int16
+       - dest: int16
+       - ack: int16
+       - nframes_expected: int16
+       - nframes_sent: int16
+       - pkt_type: int16
+       - nbytes: int32
+
+Services
+--------
+
+below is a list of all of the services used in ros_acomms along with
+each node they are used within in ros_acomms.
+
+::
+
+     GetNextPacketData (tdma_node, xducer_safety_power_control_node, message_queue_node):
+     GetNextPacketData is used by MAC nodes to retrieve data from the message_queue_node for transmit.  It retrieves the highest priority messages that fit in the number of bytes specified in num_miniframe_bytes and num_dataframe_bytes.
+         Request:
+           - num_miniframe_bytes: int32
+           - num_dataframe_bytes: int32
+           - match_dest: bool
+           - dest: int16
+           - min_priority: int32
+         Response:
+           - dest: int16
+           - miniframe_bytes: uint8[]
+           - dataframe_bytes: uint8[]
+           - queued_message_ids: int32[]
+           - num_miniframe_bytes: int32
+           - num_dataframe_bytes: int32
+           - num_messages: int32
+
+     GetOptimalTxDepth (sim_transmission_loss_node):
+     GetOptimalTxDepth is used to identify the optimal depth at which to transmit to maximize receive level at a given receiver.
+         Request:
+         - src_latitude: float32
+         - src_longitude: float32
+         - src_min_depth: float32
+         - src_max_depth: float32
+         - src_tx_level_db: float32
+         - center_frequency_hz: float32
+         - bandwidth_hz: float32
+         - rcv_latitude: float32
+         - rcv_longitude: float32
+         - rcv_depth: float32
+         - horizontal_range: float32
+       Response:
+         - optimal_tx_depth: float32
+         - rcv_rx_level_db: float32
+
+     GetPlatformLocation (platform_location_node, modem_sim_node):
+     GetPlatformLocation is used to retrieve platform locations in simulation
+       Request:
+         - name: string
+       Response:
+         - valid: bool
+         - latitude: float32
+         - longitude: float32
+         - depth: float32
+
+     PingModem (acomms_driver_node):
+     PingModem is used to send an acoustic modem ping to a remote modem.  If a reply is heard, the one-way travel time will be reported.
+       Request:
+         - dest: uint8
+         - reply_timeout: float32
+       Response:
+         - timed_out: bool
+         - one_way_travel_time: float32
+         - cst: CST
+
+     PingTransponder:
+     This service is under development.
+       Request:
+         - dest_address: uint8
+       Response:
+         - travel_times: float32[]
+
+     PriorityUpdate (message_queue_node, test_queue_priority_node):
+     PriorityUpdate is used to change the priority (typically specified in the message codec config) for a message queue associated with a given topic.
+       Request:
+         - topic_name: string
+         - priority_desired: int32
+       Response:
+         - response: bool
+
+     QueueActive (message_queue_node, test_queue_priority_node):
+     QueueActive is used to enable and disable message queues associated with specified topics at runtime.
+       Request:
+         - topic_name: string
+         - set_active: bool
+       Response:
+         - success: bool
+
+     QueueTxPacket:
+     QueueTxPacket is used to transmit packets with the acoustic modem or acoustic modem simulator.
+       Request:
+         - QUEUE_IMMEDIATE: int8
+         - QUEUE_PPS: int8
+         - QUEUE_TXTRIG: int8
+         - queue: int8
+         - insert_at_head: bool
+         - requested_src_level_db: float32
+         - packet: Packet
+       Response:
+         - success: bool
+         - posistion_in_queue: int32
+         - actual_src_level_db: float32
+
+     SimPacketPerformance (modem_sim_node, sim_packet_performance_node):
+     SimPacketPerformance is used to simulate the success or failure of a simulated acoustic packet given the receive level, noise level, and packet rate.  The probability of success is based on empirical data.
+       Request:
+         - rx_level_db: float32
+         - noise_level_db: float32
+         - packet_rate: float32
+       Response:
+         - packet_success: bool
+         - miniframe_success: bool[]
+         - frame_succesS: bool[]
+
+     SimTransmissionLoss (sim_transmission_loss_node, test_sim_transmission_loss_node, modem_sim_node):
+     SimTransmissionLoss is used to get the receive level and latency of a simulated acoustic path.
+       Request:
+         - src_latitude: float32
+         - src_longitude: float32
+         - src_depth: float32
+         - src_tx_level_db: float32
+         - center_frequency_hz: float32
+         - bandwidth_hz: float32
+         - rcv_latitude: float32
+         - rcv_depth: float32
+       Response:
+         - rcv_rx_level_db: float32
+         - transmission_delay: duration
+
+Codecs
+------
+
+The acoustic link is both high-latency and low-throughput. These
+limitations are mostly due to physics, and therefore unavoidable. As a
+result, it is important to efficiently pack any data you want to send
+via acomms.
+
+ros_acomms provides a flexible mechanism for doing this, built of three
+layers of *codecs* (an amalgam of “encoder” and “decoder”):
+
+1. Field codecs
+2. Message codecs
+3. Packet codecs
+
+Field codecs is available in the ltcodec repository, and can be
+installed via pip on PyPi
+
+Each field in a message represent something: a numeric value, a string,
+an array of some other type, or a nested message. The goal of each field
+codec is to represent those values in as few bits as possible. In the
+case of ROS messages, each ROS message primititive type has a default
+field codec that does a decent job of packing the value into bits.
+
+However, you can use fewer bits to encode the value if you provide more
+information about the data that goes in the field. Different types
+(floats, integers, strings) have different parameters that you can
+specify on the field. For example, the smallest integer field used by
+ROS is an int8, which takes 8 bits. However, if you specified that a
+field has a minimum value of 0 and a maximum value of 6, it can be
+represented using only 3 bits. The field codecs can do this. See the
+documenation on each field codec for a description of the parameters
+that can reduce the size of encoded values.
+
+Some field codecs are recursive, so that nested message types are
+supported.
+
+The message codec is responsible for packing the output of the field
+codecs into a single sequence of bits that represents a single message
+and vice-versa.
+
+The packet codec takes the output of multiple message codecs and builds
+an acomms packet that contains one or more messages, and does the
+inverse on the receive side.
+
+Field codecs, message codecs, and packet codecs are all classes that
+derive from a base FieldCodec, MessageCodec, or PacketCodec class.
+Writing new codecs is intentionally simple.
+
+There is an important distinction between the message codec system used
+here and that used by other systems, such as Protobuf, Msgpack, or CBOR.
+This codec system optimizes for size over descriptiveness. From a given
+packed sequence of bits, there is no way to decode it without knowing
+the encoding scheme.
+
+ltcodecs includes all of the message types supported by ros messages.
+Each codec type requires its own set of configurable parameters that
+need to be specified in the message codec config. In order to interface
+with ros_acomms you will need to create codec config file. In your
+message codec config file the below parameters will need to be included:
+
+::
+
+   - codec_name: default
+     match_src: []
+     match_dest: []
+     except_src: []
+     except_dest: []
+     packet_codec: ros
+     miniframe_header: []
+     dataframe_header: []
+     remove_headers: bool
+     message_codecs: []
+
+Under the message codecs section of your message codec config file an
+entry will need to be made for every publish / subscriber paired that
+will be sent through ros_acomms. Each entry in the list will need the
+following fields:
+
+::
+
+     - id: int
+       message_codec: default
+       subscribe_topic: string
+       publish_topic: string
+       ros_type: string
+       default_dest: int
+       queue_order: fifo/lifo
+       queue_maxsize: int
+       is_active: bool
+       allow_fragmentation: bool
+       priority: int
+       fields:
+         $field_name:
+           codec: $CodecName
+           $parameters
+
+An example message codec config can be seen below:
+
+.. container::
+
+   .. figure:: /images/queueConfig.png
+      :alt: Config File
+
+      Config File
+
+All of the codec types and associated parameters(\* Indicates Required
+Fields) can be seen below:
+
+::
+
+   bool:
+     - none
+
+   bytes:
+   This codec is included for compatibility with systems using CCL messages. It is not recommended for use with new systems.
+
+   - max_length: int\* (Maximum number of bytes that can be encoded)
+
+   ccl_latlon_bcd:
+   This codec is included for compatibility with systems using CCL messages. It is not recommended for use with new systems.
+
+   - lat_not_lon: bool (default: true)
+
+   ccl_latlon:
+   This codec is included for compatibility with systems using CCL messages. It is not recommended for use with new systems.
+
+   linspace_float, linspace:
+   The linspace codec encodes floats as the nearest value among a specified number of linearly-spaced values. Only one of resolution, num_values, or num_bits may be specified.
+
+   - min_value: float
+   - max_value: float
+   - resolution: float ((default: none) Spacing between encoded values)
+   - num_values: int ((default: none) Number of encoded values between min and max values)
+   - num_bits: int ((default: none) Number of bits to use to encode values between min and max values)
+
+   integer:
+
+   - min_value: int\*
+   - max_value: int\*
+   - resolution: int ((default 1) Can be set to values other than 1 to reduce resolution and decrease encoded size.)
+   - little_endian: bool (default: false)
+
+   int8, int16, int32, int64:
+   Fixed-length integer codecs, no user-specified parameters.
+
+   uint8, uint16, uint32, uint64:
+   Fixed-length unsigned integer codecs; no user-specified parameters
+
+   float:
+
+   - min_value: float\*
+   - max_value: float\*
+   - precision: int\* (Default 0, number of significant digits after decimal point.)
+
+   float32, float64:
+   IEEE floating point formats; no user-specified parameters
+
+   string, ascii:
+
+   - max_length: int (default: 128)
+   - bits_per_char: int ((default: 7) Defaults to 7-bit ASCII. 6 will use AIS-standard 6-bit ASCII to encode strings (no lowercase letters, limited symbols)
+   - tail: bool ((default: false) If the string exceeds max_length, it will be truncated. If tail is true, the end of the string will be sent rather than the beginning.)
+
+   msg, ros_msg:
+   This encodes a ROS message, and must contain a fields dict with types
+
+   - ros_type: str\*
+   - fields: dict (default: none)
+
+   variable_len_array:
+
+   - element_type: str\*
+   - max_length: int\*
+   - element_paramets: str
+
+   fixed_len_array:
+
+   - element_type: str\*
+   - length: int\*
+   - element_paramets: str (default: none)
+
+   padding, pad:
+
+   - num_bits: int\*
+
+   time, rostime:
+
+   - precision: int (default: 0)
+   - epoch_start: int (default: 1622520000)
+   - epoch_end: int (default: (2\*\*31 - 1))
+
+Modem Simulator Quick Start
+---------------------------
+
+You can use the included Dockerfile to build a Docker container,
+download a pre-built Docker image, or just check out this repository and
+run one of the example launch files.
+
+Install: 1. Create a ROS workspace, if you don’t already have one. See
+the ROS documentation for more information. 2. Clone ros_acomms:
+``git clone https://git.whoi.edu/acomms/ros_acomms.git`` 3. Change
+directory into ros_acomms ``cd ros_acomms`` 4. Run setup.bash
+``pip install -r requirements.txt`` 5. Rebuild your workspace with
+``catkin_make`` 6. Run the modem_sim_test launch file from a console. It
+will print messages to stdout. Assuming you are in your ROS workspace
+root: ``roslaunch src/ros_acomms/src/launch/modem_sim_test.launch``
+
+Modify this launch file to change parameters, spawn additional modems,
+etc. To display version information make sure to include the
+version_node in your launch file.
+
+To interface with ros_acomms first copy queue_test.launch and
+message_codec_config.yaml from /launch and put them into your own ROS
+repo. The message_codec_config.yaml file will need to be adjusted to fit
+your messages. Make sure to edit the subscribe_topic, publish_topic, and
+ros_type fields to match your namespaces. Additionally the fields key
+will need to be updated for each parameter within your message. Each
+message should have its own message_codec within the config. All
+unnecessary message configs can be safely removed.
+
+Info codec typing can be found at
+/src/acomms_codes/ltcodecs/**init**.py. Further exploration of
+parameters / defaults can be found in the specific codec type files held
+within the ltcodecs directory.
+
+If your message is being sent / recived more than once it is likely the
+way you have your namespaces set up. One solution is to remove
+message_queue_node from one of the modems and packet_dispatch from the
+other. Additionally, you can use namespaces like /modem0/command.
+
+Differences between the modem driver and modem sim
+--------------------------------------------------
+
+Beyond the obvious differences (the sim supports simulation, including
+sim time and synchronization), there are a few differences: 1. The sim
+does not currently support the tx_inhibit dynamic reconfigure parameter.
+
+Message Fragmentation and File Transfer
+---------------------------------------
+
+ros_acomms implements a system for fragmenting larger messages to send
+them across the acoustic link. If the fragmentation parameter is enabled
+in the message codec config (discussed in part 5), larger will be
+fragmented and sent in pieces. The fragmentation tracker node is
+responsible for making sure all the individual pieces of the fragmented
+message are correctly transmitted. Once received, the pieces of the
+message are reassembled and published on the topic specified in the
+message codec config.
+
+By default, the fragmentation system is configured to allow transmitting
+messages up to about 65500 bytes in size. It can be configured to send
+larger messages, but this is likely not advisable over acoustic links.
+Even 64kB messages may prove difficult to transmit within a reasonable
+time, as a typical modem packet ranges in size from a few hundred bytes
+to about 2kB.
+
+Files can be transferred across an acoustic link using ros_acomms by
+packing them in a uint8[] field of a ROS message. Python, C, JPG, PNG,
+or any other types of files can be parsed using Python and then packed
+into a message that can be sent through ros_acomms. Files (messages)
+that are too large to fit in a single modem packet will be fragmented as
+long as message fragmentation is enabled in the message codec config.
+\``\`
diff --git a/docs/ros_acomms/acomms_codecs.rst b/docs/ros_acomms/acomms_codecs.rst
new file mode 100644
index 0000000000000000000000000000000000000000..24d682c6dd4f104fd990324df8e8d81366a2444d
--- /dev/null
+++ b/docs/ros_acomms/acomms_codecs.rst
@@ -0,0 +1,37 @@
+acomms\_codecs subpackage
+=========================
+
+Submodules
+----------
+
+acomms\_codecs.base\_packet\_codec module
+-----------------------------------------
+
+.. automodule:: acomms_codecs.base_packet_codec
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+acomms\_codecs.ccl\_packet\_codec module
+----------------------------------------
+
+.. automodule:: acomms_codecs.ccl_packet_codec
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+acomms\_codecs.ros\_packet\_codec module
+----------------------------------------
+
+.. automodule:: acomms_codecs.ros_packet_codec
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: acomms_codecs
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/ros_acomms/acomms_driver_node.rst b/docs/ros_acomms/acomms_driver_node.rst
new file mode 100644
index 0000000000000000000000000000000000000000..b9551833d9460557249b52517de44b0db85260d6
--- /dev/null
+++ b/docs/ros_acomms/acomms_driver_node.rst
@@ -0,0 +1,7 @@
+acomms\_driver\_node module
+===========================
+
+.. automodule:: acomms_driver_node
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/ros_acomms/check_codec_config.rst b/docs/ros_acomms/check_codec_config.rst
new file mode 100644
index 0000000000000000000000000000000000000000..0e74bbb057869014aa677933366d38d8523dfc7c
--- /dev/null
+++ b/docs/ros_acomms/check_codec_config.rst
@@ -0,0 +1,7 @@
+check\_codec\_config module
+===========================
+
+.. automodule:: check_codec_config
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/ros_acomms/codec_config_parser.rst b/docs/ros_acomms/codec_config_parser.rst
new file mode 100644
index 0000000000000000000000000000000000000000..f7e53ad4619cf790410b212b9dad5c9cfcd97be3
--- /dev/null
+++ b/docs/ros_acomms/codec_config_parser.rst
@@ -0,0 +1,21 @@
+codec\_config\_parser subpackage
+================================
+
+Submodules
+----------
+
+codec\_config\_parser.config\_parser module
+-------------------------------------------
+
+.. automodule:: codec_config_parser.config_parser
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: codec_config_parser
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/ros_acomms/fragmentation_tracker.rst b/docs/ros_acomms/fragmentation_tracker.rst
new file mode 100644
index 0000000000000000000000000000000000000000..09fb423b1f717d27f52e98688bf7b3c644e4ff29
--- /dev/null
+++ b/docs/ros_acomms/fragmentation_tracker.rst
@@ -0,0 +1,7 @@
+fragmentation\_tracker module
+=============================
+
+.. automodule:: fragmentation_tracker
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/ros_acomms/message_queue_node.rst b/docs/ros_acomms/message_queue_node.rst
new file mode 100644
index 0000000000000000000000000000000000000000..b20c005b9ffddeafdba298cdfb3da79d7d70054e
--- /dev/null
+++ b/docs/ros_acomms/message_queue_node.rst
@@ -0,0 +1,7 @@
+message\_queue\_node module
+===========================
+
+.. automodule:: message_queue_node
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/ros_acomms/modem_sensor_data_node.rst b/docs/ros_acomms/modem_sensor_data_node.rst
new file mode 100644
index 0000000000000000000000000000000000000000..e85f06cecd72927891d318c620c34a1c8fac42b3
--- /dev/null
+++ b/docs/ros_acomms/modem_sensor_data_node.rst
@@ -0,0 +1,7 @@
+modem\_sensor\_data\_node module
+================================
+
+.. automodule:: modem_sensor_data_node
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/ros_acomms/modules.rst b/docs/ros_acomms/modules.rst
new file mode 100644
index 0000000000000000000000000000000000000000..2ee27d8f87ea9ff0c96590067c7d71947d92c171
--- /dev/null
+++ b/docs/ros_acomms/modules.rst
@@ -0,0 +1,36 @@
+ros_acomms package
+==================
+
+Codec
+-----
+
+.. toctree::
+   :maxdepth: 1
+
+   acomms_codecs
+   codec_config_parser
+   check_codec_config
+
+Nodes
+-----
+
+.. toctree::
+   :maxdepth: 1
+
+   acomms_driver_node
+   message_queue_node
+   packet_dispatch_node
+   modem_sensor_data_node
+   tdma_nav_header_node
+   tdma_node
+   tdma_test_mac_node
+   version_node
+
+Utilities
+---------
+
+.. toctree::
+   :maxdepth: 1
+
+   fragmentation_tracker
+
diff --git a/docs/ros_acomms/packet_dispatch_node.rst b/docs/ros_acomms/packet_dispatch_node.rst
new file mode 100644
index 0000000000000000000000000000000000000000..b94bf9c009dfb1521a5b5a305c63690365dc9702
--- /dev/null
+++ b/docs/ros_acomms/packet_dispatch_node.rst
@@ -0,0 +1,7 @@
+packet\_dispatch\_node module
+=============================
+
+.. automodule:: packet_dispatch_node
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/ros_acomms/tdma_nav_header_node.rst b/docs/ros_acomms/tdma_nav_header_node.rst
new file mode 100644
index 0000000000000000000000000000000000000000..a620f3e28593837b0101efff92c772126969d101
--- /dev/null
+++ b/docs/ros_acomms/tdma_nav_header_node.rst
@@ -0,0 +1,7 @@
+tdma\_nav\_header\_node module
+==============================
+
+.. automodule:: tdma_nav_header_node
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/ros_acomms/tdma_node.rst b/docs/ros_acomms/tdma_node.rst
new file mode 100644
index 0000000000000000000000000000000000000000..96ebc61d03a8dc0c10561cda99db4f0ed11e948e
--- /dev/null
+++ b/docs/ros_acomms/tdma_node.rst
@@ -0,0 +1,7 @@
+tdma\_node module
+=================
+
+.. automodule:: tdma_node
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/ros_acomms/tdma_test_mac_node.rst b/docs/ros_acomms/tdma_test_mac_node.rst
new file mode 100644
index 0000000000000000000000000000000000000000..e10bc32b2f46e7fa5705f3bb6c79450a2c841d28
--- /dev/null
+++ b/docs/ros_acomms/tdma_test_mac_node.rst
@@ -0,0 +1,7 @@
+tdma\_test\_mac\_node module
+============================
+
+.. automodule:: tdma_test_mac_node
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/ros_acomms/version_node.rst b/docs/ros_acomms/version_node.rst
new file mode 100644
index 0000000000000000000000000000000000000000..a953224ee2451a705b98721d856be0af14e7df95
--- /dev/null
+++ b/docs/ros_acomms/version_node.rst
@@ -0,0 +1,7 @@
+version\_node module
+====================
+
+.. automodule:: version_node
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/ros_acomms_modeling/clock_generator.rst b/docs/ros_acomms_modeling/clock_generator.rst
new file mode 100644
index 0000000000000000000000000000000000000000..9300c5fb5b818315b0cf9921495d3bc3d2776ae5
--- /dev/null
+++ b/docs/ros_acomms_modeling/clock_generator.rst
@@ -0,0 +1,7 @@
+clock\_generator module
+=======================
+
+.. automodule:: clock_generator
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/ros_acomms_modeling/modem_location_sim_node.rst b/docs/ros_acomms_modeling/modem_location_sim_node.rst
new file mode 100644
index 0000000000000000000000000000000000000000..3577e09ac7ada3d870c7ebdc508ff648b194adef
--- /dev/null
+++ b/docs/ros_acomms_modeling/modem_location_sim_node.rst
@@ -0,0 +1,7 @@
+modem\_location\_sim\_node module
+=================================
+
+.. automodule:: modem_location_sim_node
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/ros_acomms_modeling/modem_sim_node.rst b/docs/ros_acomms_modeling/modem_sim_node.rst
new file mode 100644
index 0000000000000000000000000000000000000000..41a7983264789bf278f2a1c2c4bde2dccbd05078
--- /dev/null
+++ b/docs/ros_acomms_modeling/modem_sim_node.rst
@@ -0,0 +1,7 @@
+modem\_sim\_node module
+=======================
+
+.. automodule:: modem_sim_node
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/ros_acomms_modeling/modules.rst b/docs/ros_acomms_modeling/modules.rst
new file mode 100644
index 0000000000000000000000000000000000000000..14fd4aed47f97bf065fa01e9d05e8c1188ffb3b6
--- /dev/null
+++ b/docs/ros_acomms_modeling/modules.rst
@@ -0,0 +1,12 @@
+ros_acomms_modeling package
+===========================
+
+.. toctree::
+   :maxdepth: 4
+
+   clock_generator
+   modem_location_sim_node
+   modem_sim_node
+   sim_packet_performance_node
+   sim_transmission_loss_node
+   simple_noise_model_node
diff --git a/docs/ros_acomms_modeling/sim_packet_performance_node.rst b/docs/ros_acomms_modeling/sim_packet_performance_node.rst
new file mode 100644
index 0000000000000000000000000000000000000000..7fd4424fc85e069dff01817f69a60d305059a19a
--- /dev/null
+++ b/docs/ros_acomms_modeling/sim_packet_performance_node.rst
@@ -0,0 +1,7 @@
+sim\_packet\_performance\_node module
+=====================================
+
+.. automodule:: sim_packet_performance_node
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/ros_acomms_modeling/sim_transmission_loss_node.rst b/docs/ros_acomms_modeling/sim_transmission_loss_node.rst
new file mode 100644
index 0000000000000000000000000000000000000000..2e6906b51ab9e0d8b6539733265313fad934367b
--- /dev/null
+++ b/docs/ros_acomms_modeling/sim_transmission_loss_node.rst
@@ -0,0 +1,7 @@
+sim\_transmission\_loss\_node module
+====================================
+
+.. automodule:: sim_transmission_loss_node
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/ros_acomms_modeling/simple_noise_model_node.rst b/docs/ros_acomms_modeling/simple_noise_model_node.rst
new file mode 100644
index 0000000000000000000000000000000000000000..ab0c2e01bbc310ed3dfdee1bc83e8613be11fd56
--- /dev/null
+++ b/docs/ros_acomms_modeling/simple_noise_model_node.rst
@@ -0,0 +1,7 @@
+simple\_noise\_model\_node module
+=================================
+
+.. automodule:: simple_noise_model_node
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/ros_acomms_msgs/AcousticTimesync.rst b/docs/ros_acomms_msgs/AcousticTimesync.rst
new file mode 100644
index 0000000000000000000000000000000000000000..f10484b887571144ab7d68ac12310a2d6a9db071
--- /dev/null
+++ b/docs/ros_acomms_msgs/AcousticTimesync.rst
@@ -0,0 +1,4 @@
+AcousticTimesync msg
+====================
+
+.. ros_message:: ros_acomms_msgs/AcousticTimesync
\ No newline at end of file
diff --git a/docs/ros_acomms_msgs/CST.rst b/docs/ros_acomms_msgs/CST.rst
new file mode 100644
index 0000000000000000000000000000000000000000..0acdb7782f9f256f4a1b8bf356abdaa89a9d57ad
--- /dev/null
+++ b/docs/ros_acomms_msgs/CST.rst
@@ -0,0 +1,4 @@
+CST msg
+====================
+
+.. ros_message:: ros_acomms_msgs/CST
\ No newline at end of file
diff --git a/docs/ros_acomms_msgs/modules.rst b/docs/ros_acomms_msgs/modules.rst
new file mode 100644
index 0000000000000000000000000000000000000000..c2eaa7b2c27e480282a8f394c657987524b894e9
--- /dev/null
+++ b/docs/ros_acomms_msgs/modules.rst
@@ -0,0 +1,8 @@
+ros_acomms_msgs package
+===========================
+
+.. toctree::
+   :maxdepth: 4
+
+   AcousticTimesync
+   CST
diff --git a/docs/ros_acomms_tests/modules.rst b/docs/ros_acomms_tests/modules.rst
new file mode 100644
index 0000000000000000000000000000000000000000..e1c850bf984d61ce787a3b1b7ed0e836ceaa79dd
--- /dev/null
+++ b/docs/ros_acomms_tests/modules.rst
@@ -0,0 +1,8 @@
+ros_acomms_tests package
+========================
+
+.. toctree::
+   :maxdepth: 1
+
+   test_dynamic_queue
+   test_ros_acomms
diff --git a/docs/ros_acomms_tests/test_dynamic_queue.rst b/docs/ros_acomms_tests/test_dynamic_queue.rst
new file mode 100644
index 0000000000000000000000000000000000000000..1e08bfcbb639481aef17509e87846696b0640de3
--- /dev/null
+++ b/docs/ros_acomms_tests/test_dynamic_queue.rst
@@ -0,0 +1,7 @@
+test\_dynamic\_queue module
+===========================
+
+.. automodule:: test_dynamic_queue
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/ros_acomms_tests/test_ros_acomms.rst b/docs/ros_acomms_tests/test_ros_acomms.rst
new file mode 100644
index 0000000000000000000000000000000000000000..5b30462deb0838f151dcc7ef460cc7e73f241ad9
--- /dev/null
+++ b/docs/ros_acomms_tests/test_ros_acomms.rst
@@ -0,0 +1,7 @@
+test\_ros\_acomms module
+========================
+
+.. automodule:: test_ros_acomms
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000000000000000000000000000000000000..b5a3c468d9e85e7fa7469c3a90d47b48ab93e54a
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,6 @@
+[build-system]
+requires = [
+    "setuptools>=42",
+    "wheel"
+]
+build-backend = "setuptools.build_meta"
\ No newline at end of file
diff --git a/ros_acomms/src/acomms_driver_node.py b/ros_acomms/src/acomms_driver_node.py
index f84af750a745f098feb779719495607c39930e4d..477b98b12593e7bdb86e791ea4c37e33c7c9ffcd 100755
--- a/ros_acomms/src/acomms_driver_node.py
+++ b/ros_acomms/src/acomms_driver_node.py
@@ -1,4 +1,12 @@
 #!/usr/bin/env python3
+
+"""
+src.acomms_driver_node
+----------------------
+
+This module contains the AcommsDriverNode class, which is handle and publish modem operations.
+"""
+
 from __future__ import unicode_literals
 
 from queue import Queue, Empty
@@ -14,7 +22,13 @@ import rospy
 from std_msgs.msg import Header, String
 from dynamic_reconfigure.server import Server as DynamicReconfigureServer
 from ros_acomms_msgs.msg import XST, SST
-from ros_acomms_msgs.msg import CST, ReceivedPacket, Packet, PingReply, PingTranspondersReply
+from ros_acomms_msgs.msg import (
+    CST,
+    ReceivedPacket,
+    Packet,
+    PingReply,
+    PingTranspondersReply,
+)
 from ros_acomms_msgs.srv import PingModem, PingModemResponse
 from ros_acomms_msgs.srv import PingTransponders, PingTranspondersResponse
 from ros_acomms_msgs.srv import QueueTxPacket, QueueTxPacketResponse
@@ -25,6 +39,7 @@ from version_node import version_node
 # Import NodeStatus if it is available
 try:
     from node_status.msg import NodeStatus
+
     nodestatusavail = True
 except ImportError:
     rospy.logwarn("NodeStatus package not available, can't publish node status.")
@@ -41,8 +56,12 @@ def convert_datetime_to_rospy(time_stamp):
 
 
 class AcommsDriverNode(object):
+    """
+    node for acomms driver
+    """
+
     def __init__(self):
-        rospy.init_node('acomms_driver')
+        rospy.init_node("acomms_driver")
 
         try:
             version = version_node()
@@ -51,53 +70,75 @@ class AcommsDriverNode(object):
         except:
             rospy.logwarn("Unable to query version information")
 
-
-        self.cst_publisher = rospy.Publisher('cst', CST, queue_size=10)
-        self.xst_publisher = rospy.Publisher('xst', XST, queue_size=10)
-        self.sst_publisher = rospy.Publisher('sst', SST, queue_size=10)
-        self.packet_rx_publisher = rospy.Publisher('packet_rx', ReceivedPacket, queue_size=10)
-        self.nmea_publisher = rospy.Publisher('nmea_from_modem', String, queue_size=10)
-        self.ping_reply_publisher = rospy.Publisher('ping_reply', PingReply, queue_size=10)
-        self.ping_transponders_reply_publisher = rospy.Publisher('ping_transponders_reply', PingTranspondersReply, queue_size=10)
-        self.txf_publisher = rospy.Publisher('txf', Header, queue_size=10)
+        self.cst_publisher = rospy.Publisher("cst", CST, queue_size=10)
+        self.xst_publisher = rospy.Publisher("xst", XST, queue_size=10)
+        self.sst_publisher = rospy.Publisher("sst", SST, queue_size=10)
+        self.packet_rx_publisher = rospy.Publisher(
+            "packet_rx", ReceivedPacket, queue_size=10
+        )
+        self.nmea_publisher = rospy.Publisher("nmea_from_modem", String, queue_size=10)
+        self.ping_reply_publisher = rospy.Publisher(
+            "ping_reply", PingReply, queue_size=10
+        )
+        self.ping_transponders_reply_publisher = rospy.Publisher(
+            "ping_transponders_reply", PingTranspondersReply, queue_size=10
+        )
+        self.txf_publisher = rospy.Publisher("txf", Header, queue_size=10)
         if nodestatusavail:
-            self.pub_nodestatus = rospy.Publisher(rospy.names.canonicalize_name('/node_status/' + rospy.get_name()),
-                                              NodeStatus, queue_size=10, latch=True)
+            self.pub_nodestatus = rospy.Publisher(
+                rospy.names.canonicalize_name("/node_status/" + rospy.get_name()),
+                NodeStatus,
+                queue_size=10,
+                latch=True,
+            )
 
         self.publish_nodestatus(False, "Initializing acomms_driver_node...")
-        modem_connection_type = rospy.get_param('~modem_connection_type', 'serial')
-        modem_remote_host = rospy.get_param('~modem_remote_host', 'localhost')
-        modem_remote_port = rospy.get_param('~modem_remote_port', 4001)
-        modem_local_host = rospy.get_param('~modem_local_host', '')
-        modem_local_port = rospy.get_param('~modem_local_port', None)
-        modem_serial_port = rospy.get_param('~modem_serial_port', '/dev/ttyS3')
-        modem_baud_rate = rospy.get_param('~modem_baud_rate', 19200)
-        set_modem_time = rospy.get_param('~set_modem_time', False)
-        modem_config = rospy.get_param('~modem_config', {})
-        self.publish_partial_packets = rospy.get_param('~publish_partial_packets', True)
+        modem_connection_type = rospy.get_param("~modem_connection_type", "serial")
+        modem_remote_host = rospy.get_param("~modem_remote_host", "localhost")
+        modem_remote_port = rospy.get_param("~modem_remote_port", 4001)
+        modem_local_host = rospy.get_param("~modem_local_host", "")
+        modem_local_port = rospy.get_param("~modem_local_port", None)
+        modem_serial_port = rospy.get_param("~modem_serial_port", "/dev/ttyS3")
+        modem_baud_rate = rospy.get_param("~modem_baud_rate", 19200)
+        set_modem_time = rospy.get_param("~set_modem_time", False)
+        modem_config = rospy.get_param("~modem_config", {})
+        self.publish_partial_packets = rospy.get_param("~publish_partial_packets", True)
 
         port = modem_serial_port
         baud = modem_baud_rate
-        log_location = 'acomms_logs/'
-        self.default_modem_dest = rospy.get_param('~default_modem_dest', 121)
+        log_location = "acomms_logs/"
+        self.default_modem_dest = rospy.get_param("~default_modem_dest", 121)
 
-        self.use_legacy_packets = rospy.get_param('~use_legacy_packets', False)
-        self.use_janus_packets = rospy.get_param('~use_janus_packets', False)
-        self.use_tdp = rospy.get_param('~use_tdp', False)
+        self.use_legacy_packets = rospy.get_param("~use_legacy_packets", False)
+        self.use_janus_packets = rospy.get_param("~use_janus_packets", False)
+        self.use_tdp = rospy.get_param("~use_tdp", False)
 
         self.pending_packets = deque()
 
         # First, connect
-        self.unified_log = UnifiedLog(log_path=log_location, console_log_level=logging.INFO, rootname='acomms_logger')
-        self.um = Micromodem(name=''.join([x if x.isalnum() else "_" for x in rospy.get_name()]),
-                                          unified_log=self.unified_log)
-        if modem_connection_type == 'serial':
+        self.unified_log = UnifiedLog(
+            log_path=log_location,
+            console_log_level=logging.INFO,
+            rootname="acomms_logger",
+        )
+        self.um = Micromodem(
+            name="".join([x if x.isalnum() else "_" for x in rospy.get_name()]),
+            unified_log=self.unified_log,
+        )
+        if modem_connection_type == "serial":
             self.um.connect_serial(port, baud)
-        elif modem_connection_type == 'udp':
-            self.um.connection = UdpConnection(self.um, remote_host=modem_remote_host, local_host=modem_local_host,
-                                               remote_port=modem_remote_port, local_port=modem_local_port)
+        elif modem_connection_type == "udp":
+            self.um.connection = UdpConnection(
+                self.um,
+                remote_host=modem_remote_host,
+                local_host=modem_local_host,
+                remote_port=modem_remote_port,
+                local_port=modem_local_port,
+            )
 
-        rospy.loginfo("Modem connection open: {}, querying SRC...".format(self.um.connection))
+        rospy.loginfo(
+            "Modem connection open: {}, querying SRC...".format(self.um.connection)
+        )
 
         # Wait here until we can talk to the modem
         while True:
@@ -105,8 +146,8 @@ class AcommsDriverNode(object):
             sleep(0.1)
             if self.um.get_config("SRC", response_timeout=1):
                 break
-            
-        self.tat_ms = float(self.um.get_config("TAT", response_timeout=2)['TAT'])
+
+        self.tat_ms = float(self.um.get_config("TAT", response_timeout=2)["TAT"])
 
         rospy.loginfo("Got modem SRC: {}".format(self.um.id))
 
@@ -138,18 +179,33 @@ class AcommsDriverNode(object):
         self.modem_nmea_thread = Thread(target=self.modem_nmea_handler, daemon=True)
         self.modem_nmea_thread.start()
 
-        rospy.Subscriber('nmea_to_modem', String, self.on_nmea_subscriber)
-
-        self.ping_modem_service = rospy.Service('ping_modem', PingModem, self.handle_ping_modem)
-        self.ping_transponders_service = rospy.Service('ping_transponders', PingTransponders, self.handle_ping_transponders)
-        self.queue_tx_packet_service = rospy.Service('queue_tx_packet', QueueTxPacket, self.handle_queue_tx_packet)
-
-        self.reconfigure_server = DynamicReconfigureServer(acomms_driverConfig, self.reconfigure)
+        rospy.Subscriber("nmea_to_modem", String, self.on_nmea_subscriber)
+
+        self.ping_modem_service = rospy.Service(
+            "ping_modem", PingModem, self.handle_ping_modem
+        )
+        self.ping_transponders_service = rospy.Service(
+            "ping_transponders", PingTransponders, self.handle_ping_transponders
+        )
+        self.queue_tx_packet_service = rospy.Service(
+            "queue_tx_packet", QueueTxPacket, self.handle_queue_tx_packet
+        )
+
+        self.reconfigure_server = DynamicReconfigureServer(
+            acomms_driverConfig, self.reconfigure
+        )
         self.publish_nodestatus(True, "acomms_driver_node initialized.")
 
         rospy.loginfo("Acomms driver node initialized.")
 
     def publish_nodestatus(self, bool_ok, fault_detail=None):
+        """method to publish the current node status if it is available
+
+        Args:
+            bool_ok (_type_): whether or not the node is initialized
+            fault_detail (_type_, optional): _description_. Defaults to None.
+        """
+
         if nodestatusavail:
             msg = NodeStatus()
             msg.header.stamp = rospy.Time.now()
@@ -164,6 +220,7 @@ class AcommsDriverNode(object):
             self.pub_nodestatus.publish(msg)
 
     def modem_rx_handler(self):
+        """method to handle modem rx"""
         incoming_all_packet_queue = Queue()
         self.um.incoming_all_packet_queues.append(incoming_all_packet_queue)
         while True:
@@ -186,14 +243,17 @@ class AcommsDriverNode(object):
                             has_gone_bad = True
                             dataframe.data = bytearray()
 
-                packet_msg = Packet(src=incoming_packet.src, dest=incoming_packet.dest,
-                                    miniframe_rate=incoming_packet.miniframe_rate,
-                                    dataframe_rate=incoming_packet.dataframe_rate,
-                                    miniframe_bytes=incoming_packet.minibytes,
-                                    dataframe_bytes=incoming_packet.databytes)
+                packet_msg = Packet(
+                    src=incoming_packet.src,
+                    dest=incoming_packet.dest,
+                    miniframe_rate=incoming_packet.miniframe_rate,
+                    dataframe_rate=incoming_packet.dataframe_rate,
+                    miniframe_bytes=incoming_packet.minibytes,
+                    dataframe_bytes=incoming_packet.databytes,
+                )
                 cst_values = incoming_packet.cyclestats.copy()
                 # Fix the time type
-                cst_values['toa'] = convert_datetime_to_rospy(cst_values['toa'])
+                cst_values["toa"] = convert_datetime_to_rospy(cst_values["toa"])
                 cst_msg = CST(**cst_values)
 
                 hdr = Header(stamp=rospy.get_rostime())
@@ -202,17 +262,20 @@ class AcommsDriverNode(object):
 
                 rospy.logwarn("valid_minibytes: {}".format(valid_miniframe_bytes))
                 rospy.logwarn("valid_databytes: {}".format(valid_dataframe_bytes))
-                rx_packet_msg = ReceivedPacket(header=hdr,
-                                               packet=packet_msg,
-                                               minibytes_valid=valid_miniframe_bytes,
-                                               databytes_valid=valid_dataframe_bytes,
-                                               cst=cst_msg)
+                rx_packet_msg = ReceivedPacket(
+                    header=hdr,
+                    packet=packet_msg,
+                    minibytes_valid=valid_miniframe_bytes,
+                    databytes_valid=valid_dataframe_bytes,
+                    cst=cst_msg,
+                )
                 self.packet_rx_publisher.publish(rx_packet_msg)
 
             except Exception as e:
                 rospy.logerr_throttle(1, "Error in packet RX handler: {}".format(e))
 
     def modem_cst_handler(self):
+        """method to handle incoming cst messages"""
         incoming_cst_queue = Queue()
         self.um.attach_incoming_cst_queue(incoming_cst_queue)
 
@@ -224,6 +287,7 @@ class AcommsDriverNode(object):
                 rospy.logerr_throttle(1, "Error in CST handler: {}".format(e))
 
     def modem_xst_handler(self):
+        """method to handle incoming xst messages"""
         incoming_xst_queue = Queue()
         self.um.attach_incoming_xst_queue(incoming_xst_queue)
 
@@ -235,6 +299,7 @@ class AcommsDriverNode(object):
                 rospy.logerr_throttle(1, "Error in XST handler: {}".format(e))
 
     def modem_sst_handler(self):
+        """method to handle modem casst messages"""
         incoming_sst_queue = Queue()
         self.um.attach_incoming_casst_queue(incoming_sst_queue)
 
@@ -246,6 +311,7 @@ class AcommsDriverNode(object):
                 rospy.logerr_throttle(1, "Error in SST handler: {}".format(e))
 
     def modem_nmea_handler(self):
+        """method to handle incoming nmea messages"""
         incoming_nmea_queue = Queue()
         self.um.attach_incoming_msg_queue(incoming_nmea_queue)
 
@@ -253,7 +319,7 @@ class AcommsDriverNode(object):
             try:
                 incoming_string = incoming_nmea_queue.get(block=True)
                 if not isinstance(incoming_string, str):
-                    incoming_string = incoming_string['raw']
+                    incoming_string = incoming_string["raw"]
                 # publish it
                 nmea_msg = String(data=incoming_string)
                 self.nmea_publisher.publish(nmea_msg)
@@ -267,18 +333,30 @@ class AcommsDriverNode(object):
                 rospy.logerr_throttle(1, "Error in NMEA RX handler: {}".format(e))
 
     def handle_ping_transponders(self, request):
+        """handler for rospy PingTransponders service class
+
+        Args:
+            request (_type_): info about the transponder
+
+        Returns:
+            (PingTranspondersResponse): service call response from the transponder
+        """
         rospy.loginfo("Requesting modem send transponder ping")
 
         # request.transponder_dest_mask = request.transponder_dest_mask.decode('utf-8')
 
-        params = [str(1), str(1), str(0), str(0), 
-            str(request.timeout_ms), 
-            str(1 if request.transponder_dest_mask[0] else 0), 
-            str(1 if request.transponder_dest_mask[1] else 0), 
-            str(1 if request.transponder_dest_mask[2] else 0), 
-            str(1 if request.transponder_dest_mask[3] else 0)
-            ]
-        msg = {'type': 'CCPDT', 'params': params}
+        params = [
+            str(1),
+            str(1),
+            str(0),
+            str(0),
+            str(request.timeout_ms),
+            str(1 if request.transponder_dest_mask[0] else 0),
+            str(1 if request.transponder_dest_mask[1] else 0),
+            str(1 if request.transponder_dest_mask[2] else 0),
+            str(1 if request.transponder_dest_mask[3] else 0),
+        ]
+        msg = {"type": "CCPDT", "params": params}
 
         incoming_msg_queue = Queue()
         self.um.attach_incoming_msg_queue(incoming_msg_queue)
@@ -292,7 +370,7 @@ class AcommsDriverNode(object):
         while remaining_time > 0:
             try:
                 new_msg = incoming_msg_queue.get(timeout=remaining_time)
-                if new_msg['type'] in 'SNTTA':
+                if new_msg["type"] in "SNTTA":
                     matching_msg = new_msg
                     break
                 else:
@@ -305,40 +383,55 @@ class AcommsDriverNode(object):
         self.um.detach_incoming_msg_queue(incoming_msg_queue)
 
         if sntta:
-            rospy.loginfo(f'Response from transponders: {sntta}')
-            time_of_ping = datetime.strptime(datetime.now().strftime('%Y%m%d') + sntta['params'][4], '%Y%m%d%H%M%S.%f')
+            rospy.loginfo(f"Response from transponders: {sntta}")
+            time_of_ping = datetime.strptime(
+                datetime.now().strftime("%Y%m%d") + sntta["params"][4],
+                "%Y%m%d%H%M%S.%f",
+            )
             msg = PingTranspondersReply(
-                    transponder_dest_mask=request.transponder_dest_mask, #.encode('utf-8'),
-                    timeout_ms=request.timeout_ms,
-                    owtt_a=float(sntta['params'][0]) if sntta['params'][0] != '' else -1.0,
-                    owtt_b=float(sntta['params'][1]) if sntta['params'][1] != '' else -1.0,
-                    owtt_c=float(sntta['params'][2]) if sntta['params'][2] != '' else -1.0,
-                    owtt_d=float(sntta['params'][3]) if sntta['params'][3] != '' else -1.0,
-                    time_of_ping=convert_datetime_to_rospy(time_of_ping),
-                )
+                transponder_dest_mask=request.transponder_dest_mask,  # .encode('utf-8'),
+                timeout_ms=request.timeout_ms,
+                owtt_a=float(sntta["params"][0]) if sntta["params"][0] != "" else -1.0,
+                owtt_b=float(sntta["params"][1]) if sntta["params"][1] != "" else -1.0,
+                owtt_c=float(sntta["params"][2]) if sntta["params"][2] != "" else -1.0,
+                owtt_d=float(sntta["params"][3]) if sntta["params"][3] != "" else -1.0,
+                time_of_ping=convert_datetime_to_rospy(time_of_ping),
+            )
             msg.header.stamp = convert_datetime_to_rospy(time_of_ping)
             self.ping_transponders_reply_publisher.publish(msg)
 
-            response = PingTranspondersResponse(travel_times=[
-                float(sntta['params'][0]) if sntta['params'][0] != '' else -1.0,
-                float(sntta['params'][1]) if sntta['params'][1] != '' else -1.0,
-                float(sntta['params'][2]) if sntta['params'][2] != '' else -1.0,
-                float(sntta['params'][3]) if sntta['params'][3] != '' else -1.0
-                ])
+            response = PingTranspondersResponse(
+                travel_times=[
+                    float(sntta["params"][0]) if sntta["params"][0] != "" else -1.0,
+                    float(sntta["params"][1]) if sntta["params"][1] != "" else -1.0,
+                    float(sntta["params"][2]) if sntta["params"][2] != "" else -1.0,
+                    float(sntta["params"][3]) if sntta["params"][3] != "" else -1.0,
+                ]
+            )
         else:
-            rospy.loginfo(f'timed-out before SNTTA....')
+            rospy.loginfo(f"timed-out before SNTTA....")
             response = PingTranspondersResponse(travel_times=[-1.0, -1.0, -1.0, -1.0])
 
-        rospy.loginfo(f'Service call response: {response}')
+        rospy.loginfo(f"Service call response: {response}")
 
         return response
 
     def handle_ping_modem(self, request):
+        """handler for rospy PingModem service class
+
+        Args:
+            request (_type_): info about the modem
+
+        Returns:
+            (PingModemResponse): ping response from the modem
+        """
         rospy.loginfo("Requesting modem send ping")
         self.um.send_fdp_ping(request.dest, request.rate, request.cdr, request.hexdata)
         if request.reply_timeout <= 0:
             request.reply_timeout = 5
-        ping_reply, cst = self.um.wait_for_fdp_ping_reply(include_cst=True, timeout=request.reply_timeout)
+        ping_reply, cst = self.um.wait_for_fdp_ping_reply(
+            include_cst=True, timeout=request.reply_timeout
+        )
 
         # CCCMD,PNG: SRC=(unit designated as ping originator), DEST=(unit designated as receiver of the ping)
         # CACMA,PNG: SRC=(unit designated as transmitter),     DEST=(unit designated as receiver of the ping)
@@ -346,39 +439,39 @@ class AcommsDriverNode(object):
 
         if not ping_reply:
             response = PingModemResponse(timed_out=True)
-        elif ping_reply['dest'] != self.um.id:
+        elif ping_reply["dest"] != self.um.id:
             response = PingModemResponse(timed_out=True)
         else:
             rospy.loginfo("Received ping response")
             response = PingModemResponse(
                 timed_out=False,
-                one_way_travel_time=ping_reply['owtt'],
+                one_way_travel_time=ping_reply["owtt"],
                 tat=self.tat_ms / 1000,
-                txlevel=ping_reply['tx_level'],
-                timestamp_resolution=ping_reply['timestamp_res'],
-                toa_mode=ping_reply['toa_mode'],
-                snv_on=ping_reply['snv_on'],
-                timestamp=ping_reply['timestamp'],
+                txlevel=ping_reply["tx_level"],
+                timestamp_resolution=ping_reply["timestamp_res"],
+                toa_mode=ping_reply["toa_mode"],
+                snv_on=ping_reply["snv_on"],
+                timestamp=ping_reply["timestamp"],
             )
 
             msg = PingReply(
-                src = ping_reply['src'],
-                dest = ping_reply['dest'],
-                owtt = ping_reply['owtt'],
-                tat = self.tat_ms / 1000,
-                snr_in = ping_reply['snr_in'],
-                snr_out = ping_reply['snr_out'],
-                tx_level = ping_reply['tx_level'],
-                timestamp_res = ping_reply['timestamp_res'],
-                toa_mode = ping_reply['toa_mode'],
-                snv_on = ping_reply['snv_on'],
-                timestamp = ping_reply['timestamp']
+                src=ping_reply["src"],
+                dest=ping_reply["dest"],
+                owtt=ping_reply["owtt"],
+                tat=self.tat_ms / 1000,
+                snr_in=ping_reply["snr_in"],
+                snr_out=ping_reply["snr_out"],
+                tx_level=ping_reply["tx_level"],
+                timestamp_res=ping_reply["timestamp_res"],
+                toa_mode=ping_reply["toa_mode"],
+                snv_on=ping_reply["snv_on"],
+                timestamp=ping_reply["timestamp"],
             )
 
             if cst is not None:
                 cst_values = cst.copy()
                 # Fix the time type
-                cst_values['toa'] = convert_datetime_to_rospy(cst_values['toa'])
+                cst_values["toa"] = convert_datetime_to_rospy(cst_values["toa"])
                 response.cst = CST(**cst_values)
                 msg.cst = CST(**cst_values)
 
@@ -387,10 +480,25 @@ class AcommsDriverNode(object):
         return response
 
     def on_nmea_subscriber(self, msg):
+        """writes nmea message to micromodem
+
+        Args:
+            msg (_type_): NMEA message to send, only sends the data field of the message
+        """
         self.um.write_nmea(msg.data)
 
     def handle_queue_tx_packet(self, queue_tx_packet_req):
-        rospy.loginfo("Queuing new packet for acomms transmission: {}".format(queue_tx_packet_req))
+        """handler for queuing a tx packaet for transmission
+
+        Args:
+            queue_tx_packet_req (_type_): info about the modem
+
+        Returns:
+            (QueueTxPacketResponse): ping response from the modem
+        """
+        rospy.loginfo(
+            "Queuing new packet for acomms transmission: {}".format(queue_tx_packet_req)
+        )
         queue_tx_packet_resp = QueueTxPacketResponse()
         if queue_tx_packet_req.insert_at_head:
             self.pending_packets.append(queue_tx_packet_req.packet)
@@ -404,37 +512,59 @@ class AcommsDriverNode(object):
 
     def reconfigure(self, config, level):
         rospy.loginfo("Acomms driver reconfigure request received.")
-        tx_inhibit = config['tx_inhibit']
-        pwramp_txlevel = config['pwramp_txlevel']
-        rospy.loginfo("Acomms driver reconfigure request received. tx_inhibit={}".format(tx_inhibit))
+        tx_inhibit = config["tx_inhibit"]
+        pwramp_txlevel = config["pwramp_txlevel"]
+        rospy.loginfo(
+            "Acomms driver reconfigure request received. tx_inhibit={}".format(
+                tx_inhibit
+            )
+        )
 
         if tx_inhibit:
             value = 2
         else:
             value = 1
         # Err on the side of sending configs too often for now.  TODO: track state and clean this up
-        um_response = self.um.set_config('xmit.txinhibit', value, response_timeout=5)
-        um_response = self.um.set_config('pwramp.txlevel', pwramp_txlevel, response_timeout=5)
+        um_response = self.um.set_config("xmit.txinhibit", value, response_timeout=5)
+        um_response = self.um.set_config(
+            "pwramp.txlevel", pwramp_txlevel, response_timeout=5
+        )
         # um_response = self.um.set_config('psk.packet.mod_hdr_version', pwramp_txlevel, response_timeout=5)
-#
+        #
         return config
 
     def _send_packet(self, packet: Packet):
-        rospy.loginfo('Sending message via acomms')
+        rospy.loginfo("Sending message via acomms")
 
         if self.use_janus_packets:
-            rospy.loginfo('Sending JANUS packet: {}'.format(packet))
-            self.um.send_tjn(dest_id=packet.dest, ack=0, hex_header='', hex_data=packet.dataframe_bytes.hex())
+            rospy.loginfo("Sending JANUS packet: {}".format(packet))
+            self.um.send_tjn(
+                dest_id=packet.dest,
+                ack=0,
+                hex_header="",
+                hex_data=packet.dataframe_bytes.hex(),
+            )
         elif self.use_legacy_packets:
-            rospy.loginfo('Sending Legacy Packet: {}'.format(packet))
-            self.um.send_packet_data(packet.dest, packet.dataframe_bytes, rate_num=packet.dataframe_rate, ack=False)
+            rospy.loginfo("Sending Legacy Packet: {}".format(packet))
+            self.um.send_packet_data(
+                packet.dest,
+                packet.dataframe_bytes,
+                rate_num=packet.dataframe_rate,
+                ack=False,
+            )
         else:
             ack = 0
             header_data = 0
-            rospy.loginfo('Sending FD Packet: {}'.format(packet))
-            fdpacket = FDPacket(packet.src, packet.dest, miniframe_rate=packet.miniframe_rate,
-                                           dataframe_rate=packet.dataframe_rate, ack=ack,
-                                           minibytes=packet.miniframe_bytes, databytes=packet.dataframe_bytes)
+            rospy.loginfo("Sending FD Packet: {}".format(packet))
+            fdpacket = FDPacket(
+                packet.src,
+                packet.dest,
+                miniframe_rate=packet.miniframe_rate,
+                dataframe_rate=packet.dataframe_rate,
+                ack=ack,
+                minibytes=packet.miniframe_bytes,
+                databytes=packet.dataframe_bytes,
+            )
 
             try:
                 self.um.send_fdpacket(fdpacket, self.use_tdp)
@@ -442,6 +572,7 @@ class AcommsDriverNode(object):
                 self.um.send_fdpacket(fdpacket)
 
     def process_outgoing_queue(self):
+        """tries to sending any pending packets"""
         try:
             if len(self.pending_packets) > 0:
                 self._send_packet(self.pending_packets.pop())
@@ -463,45 +594,60 @@ class AcommsDriverNode(object):
     #         pass
 
     def publish_cst(self, cst):
+        """publishes cst message to the cst_publisher
+
+        Args:
+            cst (_type_): _description_
+        """
         cst_values = cst.copy()
 
         # Fix the time type
-        cst_values['toa'] = convert_datetime_to_rospy(cst_values['toa'])
+        cst_values["toa"] = convert_datetime_to_rospy(cst_values["toa"])
 
         msg = CST(**cst_values)
         self.cst_publisher.publish(msg)
 
     def publish_xst(self, xst):
+        """publishes xst message to the xst_publisher
+
+        Args:
+            xst (_type_): _description_
+        """
         xst_values = xst.copy()
 
         # Fix the time type
-        xst_values['time'] = convert_datetime_to_rospy(xst_values['time'])
+        xst_values["time"] = convert_datetime_to_rospy(xst_values["time"])
 
         msg = XST(**xst_values)
         self.xst_publisher.publish(msg)
 
     def publish_sst(self, sst):
+        """publishes sst message to the sst_publisher
+
+        Args:
+            sst (_type_): _description_
+        """
         msg = SST(
             time=rospy.get_rostime(),
             sst_version=sst.sst_version,
             in_water_spl_dB=sst.in_water_spl_dB,
             detector=sst.detector,
             num_samples=sst.num_samples,
-            summary_min=sst.summary['min'],
-            summary_lower_quartile=sst.summary['lower_quartile'],
-            summary_median=sst.summary['median'],
-            summary_upper_quartile=sst.summary['upper_quartile'],
-            summary_max=sst.summary['max'],
-            summary_len=sst.summary['len'],
-            )
+            summary_min=sst.summary["min"],
+            summary_lower_quartile=sst.summary["lower_quartile"],
+            summary_median=sst.summary["median"],
+            summary_upper_quartile=sst.summary["upper_quartile"],
+            summary_max=sst.summary["max"],
+            summary_len=sst.summary["len"],
+        )
         self.sst_publisher.publish(msg)
 
-
     def close(self):
+        """disconnects from the micromodem"""
         self.um.disconnect()
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     try:
         node = AcommsDriverNode()
 
diff --git a/ros_acomms_msgs/msg/AcousticTimesync.msg b/ros_acomms_msgs/msg/AcousticTimesync.msg
index 315ccd568f1110a33740927a6db29a5257d78885..afe69dedadb036b88decfe6077d37252b38b884c 100644
--- a/ros_acomms_msgs/msg/AcousticTimesync.msg
+++ b/ros_acomms_msgs/msg/AcousticTimesync.msg
@@ -1,9 +1,17 @@
+# Standard ROS header containing timestamp and frame information
 Header header
 
+# The source ID of the message
 int16 src
+
+# The destination ID of the message
 int16 dest
 
+# The time when the message was sent from the source
 time src_time
+
+# The time when the message was received by the destination
 time dest_time
 
-uint32 src_dest_offset_us
\ No newline at end of file
+# The offset in microseconds between the source and destination times
+uint32 src_dest_offset_us
diff --git a/ros_acomms_msgs/msg/CST.msg b/ros_acomms_msgs/msg/CST.msg
index 7489e209c8d89c8211a4027ce3daae4d0315d17c..32cff466377ab1825aaa9377f4f57b7cbbfcba28 100644
--- a/ros_acomms_msgs/msg/CST.msg
+++ b/ros_acomms_msgs/msg/CST.msg
@@ -1,34 +1,116 @@
+# CST message Version number; if it's less than 6, then it's version 0
 int8 version_number
+
+# Indication of the Received Packet's State: 0 if good, 1 if bad CRCs, 2 if packet timeout
 int8 mode
+
+# Time of Arrival of the Packet
 time toa
+
+# TOA mode Clock status represented as bitwise field:
+# Bits 0:1: RTC source - 0: None, 1: Onboard RTC, 2: User Command (CLK), 3: User GPS (GPRMC)
+# Bits 2:4 PPS Source - 0: None, 1: RTC, 2: CAL (future), 3: EXT (from external pin), 4: EXT_SYNC (from RTC synchronized to the last EXT PPS pulse)
 int8 toa_mode
+
+# Matched-filter peak value
 uint16 mfd_peak
+
+# MFPK compensated for shift and converted to dB
 int16 mfd_pow
+
+# Ratio of MFPK and incoherent broad-band noise used for the detector test
 int16 mfd_ratio
+
+# Sound Pressure Level at the receiver in dB re. 1 µPa
 int16 mfd_spl
+
+# Analog gain setting
 int16 agn
-int16 shift_ain
+
+# Shift of previous input buffer
 int16 shift_ainp
+
+# Shift of current input buffer
+int16 shift_ain
+
+# Shift of the basebanded buffer containing a detection
 int16 shift_mfd
+
+# Shift performed during basebanding incoming data
 int16 shift_p2b
+
+# Packet Rate
 int8 rate_num
+
+# Packet Source ID
 int16 src
+
+# Packet Destination ID
 int16 dest
+
+# PSK Error Code:
+# 0: No Error
+# 1: Bad Modulation header
+# 2: Bad CRC, Data Header
+# 3: Bad CRC on any frame
+# 4: Error accessing coproc - if modem loses r/w connection to the coproc
+# 5: Equalizer timeout - if the coproc never returns
+# 6: Missed start of PSK packet
 int16 psk_error
+
+# Packet Type:
+# -1: Unknown
+# 1: FSK
+# 2: FSK Mini
+# 3: PSK
+# 4: PSK Mini
+# 5: PSK FDP
 int8 packet_type
+
+# Number of Frames Expected
 int16 num_frames
+
+# Number of Frames with Bad CRCs
 int16 bad_frames_num
+
+# Received signal strength in dB re. 1 µPa
 int16 snr_rss
+
+# Input SNR, dB, channel 1 only, reported to 1 decimal place in version 6 of CACST
 float32 snr_in
+
+# SNR at the output of the equalizer, dB, reported to 1 decimal place in version 6 of CACST
 float32 snr_out
+
+# Symbol SNR for spread spectrum packets only, dB
 float32 snr_sym
+
+# Mean Squared Error from Equalizer
 float32 mse
+
+# Data Quality Factor, FSK only
 int16 dqf
+
+# Doppler shift measurement
 float32 dop
+
+# Std dev. of noise, dB
 int16 noise
+
+# Carrier frequency of received packet, Hz
 int32 carrier
+
+# Bandwidth of received packet, Hz
 int32 bandwidth
+
+# Sequence number of packet
 int32 sequence_number
+
+# Data rate of packet
 int16 data_rate
+
+# Number of Data Frames in the packet
 int16 num_data_frames
-int16 num_bad_data_frames
+
+# Number of Bad Data Frames in the packet
+int16 num_bad_data_frames
\ No newline at end of file
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..d0ed522a94d5c95927f79b43d7a2e61431f292ec
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,26 @@
+[metadata]
+name = ros_acomms
+version = 8.0.1
+author = whoi
+author_email = egallimore@whoi.edu
+description = ROS Acomms
+long_description = file: README.md
+long_description_content_type = text/markdown
+url = https://git.whoi.edu/acomms/ros_acomms/
+project_urls =
+    Bug Tracker = https://git.whoi.edu/acomms/ros_acomms/
+    Documentation = https://acomms.pages.whoi.edu/ros_acomms/
+classifiers =
+    Programming Language :: Python :: 3
+    License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)
+    Operating System :: OS Independent
+
+[options]
+package_dir =
+    = ros_acomms
+packages = find:
+python_requires = >=3.6
+include_package_data=True
+
+[options.packages.find]
+where = ros_acomms
\ No newline at end of file