diff --git a/.gitignore b/.gitignore
index c132e6f6e08e9080928016dda2fbb5d8448d699e..3daf0ad630824334393b5e0440deeea454a8e0e1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -154,7 +154,7 @@ dmypy.json
 cython_debug/
 
 
-.vscode/*
+.vscode/
 !.vscode/settings.json
 !.vscode/tasks.json
 !.vscode/launch.json
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 3214b0aa05ef672d26ea134640ebc7aa633f10de..d316de4a72846a87667ba2da295e0b3b2b94026b 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,11 +1,25 @@
 build_test_image:
+  tags:
+    - privileged
   image: docker:stable
   stage: build
   services:
-   - name: docker:dind
+    - name: docker:dind
   script:
     - docker build --rm -t ros_acomms-tests:latest -f ros_acomms_tests/tests.Dockerfile .
 
+trigger-ros_acomms_net-pipeline:
+  trigger:
+    project: acomms/ros_acomms_net
+    branch: $NET_DEVEL_BRANCH
+    strategy: depend
+
+trigger-ros_acomms_net_tools-pipeline:
+  trigger:
+    project: acomms/ros_acomms_net_tools
+    branch: $TOOLS_DEVEL_BRANCH
+    strategy: depend
+
 gitlab run_tests:
   stage: test
   image: ros_acomms-tests:latest
@@ -15,7 +29,7 @@ gitlab run_tests:
     - cd ../ && mv $CI_PROJECT_NAME ~/catkin_ws/src/
     - git clone -b $PYACOMMS_BRANCH https://gitlab-ci-token:$CI_JOB_TOKEN@git.whoi.edu/acomms/pyacomms.git ~/pyacomms
     - git clone -b $LTCODEC_BRANCH https://gitlab-ci-token:$CI_JOB_TOKEN@git.whoi.edu/acomms/ltcodec.git ~/ltcodec
-    - cd ~/catkin_ws/src/ros_acomms && ./setup.bash
+    - cd ~/catkin_ws/src/ros_acomms && pip install -U -r requirements.txt
     - cd ~/ltcodec && pip install .
     - cd ~/pyacomms && pip install .
     - cd ~/catkin_ws && catkin_make
@@ -24,7 +38,9 @@ gitlab run_tests:
     - rosparam set /ros_acomms_tests/fragmentation_test_msg_size $MSG_SIZE
     - rosparam set /clock_generator/multiplier $CLOCK_MULTIPLIER
     - rosparam set /clock_generator/publish_rate $CLOCK_PUBLISH_RATE
-    - source devel/setup.bash && rosrun ros_acomms_tests test_ros_acomms.py
+    - source devel/setup.bash
+    - rosrun ros_acomms_tests test_ros_acomms.py
+    - rosrun ros_acomms_tests test_dynamic_queue.py 
 
 pypi run_tests:
   stage: test
@@ -33,11 +49,13 @@ pypi run_tests:
     - source /ros_entrypoint.sh
     - mkdir -p ~/catkin_ws/src
     - cd ../ && mv $CI_PROJECT_NAME ~/catkin_ws/src/
-    - cd ~/catkin_ws/src/ros_acomms && ./setup.bash
+    - cd ~/catkin_ws/src/ros_acomms && pip install -U -r requirements.txt
     - cd ~/catkin_ws && catkin_make
     - roscore &
     - sleep 5
     - rosparam set /ros_acomms_tests/fragmentation_test_msg_size $MSG_SIZE
     - rosparam set /clock_generator/multiplier $CLOCK_MULTIPLIER
     - rosparam set /clock_generator/publish_rate $CLOCK_PUBLISH_RATE
-    - source devel/setup.bash && rosrun ros_acomms_tests test_ros_acomms.py
\ No newline at end of file
+    - source devel/setup.bash
+    - rosrun ros_acomms_tests test_ros_acomms.py
+    - rosrun ros_acomms_tests test_dynamic_queue.py 
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100755
index c24016b5259ffc89ba2a29f6fdf7de02cae8b98c..0000000000000000000000000000000000000000
--- a/Dockerfile
+++ /dev/null
@@ -1,44 +0,0 @@
-FROM ros:melodic-ros-base
-
-## install build tools
-######################
-RUN apt-get update &&\
-	apt-get install -y python-catkin-tools &&\
-	apt-get install -y python3-pip &&\
-	apt-get install -y gfortran &&\
-	rm -rf /var/lib/apt/lists/*
-
-## install acoustic toolbox bellhop
-###################################
-ENV AT_WS /opt/at
-RUN mkdir -p $AT_WS
-WORKDIR $AT_WS
-RUN git clone https://git.whoi.edu/dgiaya/acoustic_toolbox.git
-WORKDIR $AT_WS/acoustic_toolbox
-RUN make
-RUN cp $AT_WS/acoustic_toolbox/Bellhop/bellhop.exe /usr/bin/bellhop.exe
-RUN rm -rf $AT_WS
-
-## copy over ros-acomms
-#######################
-ENV ROS_WS /opt/ros_ws
-RUN mkdir -p $ROS_WS/src
-WORKDIR $ROS_WS
-
-# Start with requirements.txt
-COPY ./requirements.txt $ROS_WS/src
-# install python package dependencies
-RUN pip3 install -r $ROS_WS/src/requirements.txt
-
-# Copy over the rest
-# Checkout from repository
-#RUN git -C src clone git@acommsgit.whoi.edu:ros/ros_acomms.git
-# Copy from local source
-COPY . $ROS_WS/src
-
-## build ros package source
-RUN catkin config --extend /opt/ros/$ROS_DISTRO && catkin build ros_acomms
-
-RUN sed --in-place --expression '$isource "$ROS_WS/devel/setup.bash"' /ros_entrypoint.sh
-
-CMD ["roslaunch", "ros_acomms", "modem_sim_test.launch"]
diff --git a/README.md b/README.md
index bae0dee75008f78507fcaf64738082f72b4e2057..dc3d6f174bdc75ec87c68985f387643de26e6f99 100755
--- a/README.md
+++ b/README.md
@@ -727,23 +727,11 @@ time, rostime:
 
 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.
 
-The easiest way to get started with the Modem Simulator is to use the installer bash script:
- 1. Create a ROS workspace, if you don't already have one.  See the ROS documentation for more information.
+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 `./setup.bash`
-
-Alternatively, a more comprehensive set of installation instructions can be found below: 
-
- 1. Create a ROS workspace, if you don't already have one.  See the ROS documentation for more information.
- 2. Check out ros_acomms into your ros_workspace/src/ directory: `git clone https://git.whoi.edu/acomms/ros_acomms.git`
- 3. Install python dependencies: navigate to the root directory of ros_acomms and run `pip install -r requirements.txt` to automatically install required packages. Alternatively you can manually install arlpy geopy and ltcodecs (`pip install --user arlpy geopy ltcodecs`)
- 4. Build Bellhop and make sure it is on your path:
-    1. Check out source into a new directory: `git clone https://git.whoi.edu/dgiaya/acoustic_toolbox.git`
-    2. Run `make`
-    3. Either add acoustic_toolbox/Bellhop to your path or symlink `acoustic_toolbox/Bellhop/bellhop.exe` to `/usr/bin/bellhop.exe`
-       1.  For example: `sudo ln -s /<YOUR_PATH_GOES_HERE>/acoustic_toolbox/Bellhop/bellhop.exe /usr/bin/bellhop.exe`
-       2.  Note that if you append a directory to $PATH via `export $PATH=$PATH:/path/to/bellhop.exe`, it will only persist for the current terminal session.  If you want it to persist, either create a symlink as described above or add the export command to `~/bashrc`.
+ 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`
 
diff --git a/requirements.txt b/requirements.txt
index fa346ec6d9824c34e8481c0c0533dcc44a2be34b..6b8a6be5d6a9b7c816fd0482e99e8103be21cefc 100755
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,8 +1,6 @@
-arlpy==1.7.0
-geopy==1.21.0
-numpy==1.18.3
-pandas==1.0.3
-scipy==1.4.1
+whoi-uwapm
+geopy
+numpy
 python-dateutil
 pytz
 rospkg
@@ -12,7 +10,7 @@ bitstring
 msgpack
 ltcodecs>=1.0.0
 whoi-gitver
-acomms
+acomms>=2.4.0
 importlib-metadata
 pyyaml
-pyyaml-include
+rospy-yaml-include
\ No newline at end of file
diff --git a/ros_acomms/setup.py b/ros_acomms/setup.py
index a1c86cbb40d7c7eb918bacd8285f0e49f9f569f6..38e2b59558a805d5d1484c569c97545e61adbec6 100755
--- a/ros_acomms/setup.py
+++ b/ros_acomms/setup.py
@@ -2,8 +2,7 @@ from distutils.core import setup
 from catkin_pkg.python_setup import generate_distutils_setup
 
 d = generate_distutils_setup(
-    py_modules=['acomms_codecs'],
-    packages=['codec_config_parser'],
+    packages=['codec_config_parser', 'acomms_codecs'],
     package_dir={'': 'src'},
 )
 
diff --git a/ros_acomms/src/acomms_codecs/__init__.py b/ros_acomms/src/acomms_codecs/__init__.py
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..596ad7d3cf1bff5068372b8e904c265f6048e0b8 100644
--- a/ros_acomms/src/acomms_codecs/__init__.py
+++ b/ros_acomms/src/acomms_codecs/__init__.py
@@ -0,0 +1,57 @@
+import importlib
+import pkgutil
+import os
+import rospy
+import sys
+
+default_codecs = {
+    name.split(".")[-1].split("_")[0]: importlib.import_module(name)
+    for finder, name, ispkg in pkgutil.iter_modules(__path__, __name__ + ".")
+    if name.endswith("_packet_codec") and name != "acomms_codecs.base_packet_codec"
+}
+
+custom_codecs = {}
+custom_packet_codec_paths = rospy.get_param("custom_packet_codec_paths", None)
+
+if custom_packet_codec_paths is not None:
+    try:
+        for codec in (
+            custom_packet_codec_paths
+            if isinstance(custom_packet_codec_paths, list)
+            else [custom_packet_codec_paths]
+        ):
+            path, module_name = os.path.split(codec)
+            sys.path.append(path)
+            codec = importlib.import_module(module_name, path)
+            custom_codecs = {
+                name.split(".")[-1].split("_")[0]: importlib.import_module(name)
+                for finder, name, ispkg in pkgutil.walk_packages(
+                    __path__, __name__ + "."
+                )
+                if name.endswith("_packet_codec")
+            }
+    except OSError as exception:
+        rospy.logerr(
+            f"OSError occured when loading custom packet codecs, check provided path: {exception}"
+        )
+        raise OSError(
+            "OSError occured when loading custom packet codecs, check provided path"
+        ) from exception
+    except ImportError as exception:
+        rospy.logerr(
+            f"ImportError occured when loading custom packet codecs: {exception}"
+        )
+        raise ImportError(
+            "ImportError occured when loading custom packet codecs"
+        ) from exception
+    except Exception as exception:
+        rospy.logerr(
+            f"An unhandled error occured while loading custom packet codecs: {exception}"
+        )
+        raise Exception(
+            "An unhandled error occured while loading custom packet codecs"
+        ) from exception
+else:
+    rospy.loginfo("No custom packet codecs specified, using defaults")
+
+packet_codecs = {**default_codecs, **custom_codecs}
diff --git a/ros_acomms/src/acomms_codecs/packet_codec.py b/ros_acomms/src/acomms_codecs/base_packet_codec.py
similarity index 97%
rename from ros_acomms/src/acomms_codecs/packet_codec.py
rename to ros_acomms/src/acomms_codecs/base_packet_codec.py
index fb66758185328576c97f0a2741c4786069732454..fd8bbe7ff5b877bb493e62c2103e6bcfbc577308 100644
--- a/ros_acomms/src/acomms_codecs/packet_codec.py
+++ b/ros_acomms/src/acomms_codecs/base_packet_codec.py
@@ -2,7 +2,7 @@ from abc import ABC, abstractmethod
 from ros_acomms_msgs.msg import ReceivedPacket
 
 
-class PacketCodec(ABC):
+class BasePacketCodec(ABC):
     def __init__(self, message_codecs, message_publishers=None, miniframe_header=[], dataframe_header=[]):
         self.message_codecs = message_codecs
         self.message_publishers = message_publishers
diff --git a/ros_acomms/src/acomms_codecs/ccl_packet_codec.py b/ros_acomms/src/acomms_codecs/ccl_packet_codec.py
index e085f45f774e7bef7015c3f7cd08b4310c4a2bf9..77d3bf3daa82f8b6e406f011496f664629c3f8d3 100644
--- a/ros_acomms/src/acomms_codecs/ccl_packet_codec.py
+++ b/ros_acomms/src/acomms_codecs/ccl_packet_codec.py
@@ -1,13 +1,13 @@
 import rospy
 from ros_acomms_msgs.msg import ReceivedPacket
 from bitstring import ConstBitStream, Bits, BitArray, ReadError
-from .packet_codec import PacketCodec
+from .base_packet_codec import BasePacketCodec
 
-class CclPacketCodec(PacketCodec):
+class PacketCodec(BasePacketCodec):
     def __init__(self, message_codecs, message_publishers=None,
                  allow_fragmentation=False, minimum_fragment_size_bits=12*8,
                  miniframe_header=[], dataframe_header=[]):
-        super(CclPacketCodec, self).__init__(message_codecs, message_publishers=message_publishers,
+        super(PacketCodec, self).__init__(message_codecs, message_publishers=message_publishers,
                                              miniframe_header=miniframe_header, dataframe_header=dataframe_header)
 
         self.allow_fragmentation = allow_fragmentation
diff --git a/ros_acomms/src/acomms_codecs/packet_codecs.py b/ros_acomms/src/acomms_codecs/packet_codecs.py
deleted file mode 100644
index 3da223630eb8fdde7cfcf50a2b7eaabbbed77f6c..0000000000000000000000000000000000000000
--- a/ros_acomms/src/acomms_codecs/packet_codecs.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from .ros_packet_codec import RosPacketCodec
-from .ccl_packet_codec import CclPacketCodec
-
-
-packet_codecs = {'ros': RosPacketCodec,
-                 'ccl': CclPacketCodec,
-                 }
\ No newline at end of file
diff --git a/ros_acomms/src/acomms_codecs/ros_packet_codec.py b/ros_acomms/src/acomms_codecs/ros_packet_codec.py
index c01b486e83cfc5b6b01342abc1f6bebf05aa48b6..9598931c4a84e37aa10eba5e3f2d3be6aadf2f67 100644
--- a/ros_acomms/src/acomms_codecs/ros_packet_codec.py
+++ b/ros_acomms/src/acomms_codecs/ros_packet_codec.py
@@ -1,4 +1,5 @@
 from __future__ import annotations
+import functools
 from typing import Dict, Optional, Any
 import typing
 if typing.TYPE_CHECKING:
@@ -7,7 +8,7 @@ if typing.TYPE_CHECKING:
 import rospy
 from ros_acomms_msgs.msg import ReceivedPacket, Packet, EncodedAck
 from bitstring import ConstBitStream, Bits, BitArray, ReadError, BitStream
-from .packet_codec import PacketCodec
+from .base_packet_codec import BasePacketCodec
 from fragmentation_tracker import FragmentationStartHeader, FragmentationContHeader, FragmentationAckHeader, FragmentationTracker
 import hashlib
 
@@ -15,13 +16,23 @@ import hashlib
 # single message ACK
 # multiple message ACK
 
+# Helper functions
+def setattr_nested(obj, attr, value):
+    def rgetattr(obj, attr, *args):
+        def _getattr(obj, attr):
+            return getattr(obj, attr, *args)
+        return functools.reduce(_getattr, [obj] + attr.split('.'))
 
-class RosPacketCodec(PacketCodec):
+    pre, _, post = attr.rpartition('.')
+    return setattr(rgetattr(obj, pre) if pre else obj, post, value)
+
+
+class PacketCodec(BasePacketCodec):
     def __init__(self, message_codecs: Dict, message_publishers=None,
                  allow_fragmentation=True, minimum_fragment_size_bits=12*8,
                  sequence_number_bits=8,
                  miniframe_header=[0xac], dataframe_header=[]):
-        super(RosPacketCodec, self).__init__(message_codecs, message_publishers=message_publishers,
+        super(PacketCodec, self).__init__(message_codecs, message_publishers=message_publishers,
                                              miniframe_header=miniframe_header, dataframe_header=dataframe_header)
 
         self.allow_fragmentation = allow_fragmentation
@@ -144,7 +155,12 @@ class RosPacketCodec(PacketCodec):
                     rospy.logdebug(payload_bits)
                     ros_msg = self.message_codecs[id].decode(payload_bits, received_packet)
                     for field, value in set_fields.items():
-                       setattr(ros_msg, field, value)
+                        # Only set the field if the message already has it.  The exception handling covers this.
+                        # ROS messages aren't dictionaries, so we can't just add fields.
+                        try:
+                            setattr_nested(ros_msg, field, value)
+                        except NameError or AttributeError as e:
+                            rospy.loginfo(f"Message decode: could not set {field} on {ros_msg}.  Error: {e}")
                     try:
                         if isinstance(self.message_publishers[id], rospy.Publisher):
                             self.message_publishers[id].publish(ros_msg)
diff --git a/ros_acomms/src/acomms_codecs/test_message_codec.py b/ros_acomms/src/acomms_codecs/test_message_codec.py
deleted file mode 100644
index ce000b7bd19dbcbdf0272a293ca207aeb9de3b0c..0000000000000000000000000000000000000000
--- a/ros_acomms/src/acomms_codecs/test_message_codec.py
+++ /dev/null
@@ -1,123 +0,0 @@
-from ltcodecs import RosMessageCodec
-from ltcodecs.float_codec import FloatCodec
-from ltcodecs.linspace_float_codec import LinspaceFloatCodec
-from ltcodecs.varint_codec import VarintCodec
-from ltcodecs.fixedint_codec import UInt8Codec, UInt16Codec, UInt32Codec, FixedIntCodec
-from ltcodecs.ccl_latlon_bcd_codec import CclLatLonBcdCodec
-from ros_acomms_msgs.msg import Packet, ReceivedPacket
-from bitstring import ConstBitStream
-from io import BytesIO
-import math
-from binascii import hexlify, unhexlify
-try:
-    from ltcodecs.rostime_codec import RosTimeCodec
-except ImportError:
-    pass
-
-test_received_packet = ReceivedPacket()
-
-
-f = BytesIO()
-test_received_packet.serialize(f)
-f.seek(0)
-ros_serialized = f.read(-1)
-print("ROS serialized: {} bytes".format(len(ros_serialized)))
-
-#print(test_received_packet)
-
-message_codec = RosMessageCodec(ros_type='ros_acomms/ReceivedPacket', checksum='crc32')
-
-print("fields: ", message_codec.root_field_codec.ltcodecs.values())
-print("Message codec: max {} bits ({:.2f} bytes), min {} bits ({:.2f} bytes)".format(message_codec.max_length_bits,
-                                                                             message_codec.max_length_bits / 8,
-                                                                             message_codec.min_length_bits,
-                                                                             message_codec.min_length_bits / 8))
-
-bits, encoded_dict = message_codec.encode(test_received_packet)
-
-print(bits)
-
-print("Actual size: {} bits {:.2f} bytes".format(len(bits), len(bits)/8))
-
-decoded_message = message_codec.decode(ConstBitStream(bits))
-
-print("float test")
-
-float_codec = FloatCodec(-90, 90, 5)
-print("float ({} bits)".format(float_codec.max_length_bits))
-
-bits, rounded = float_codec.encode(41.123456789)
-
-print("rounded: {}".format(rounded))
-
-decoded_value = float_codec.decode(ConstBitStream(bits))
-print("decoded: {}".format(decoded_value))
-
-print("linspace float test")
-
-linspace_codec = LinspaceFloatCodec(min_value=-90, max_value=90, num_values=2**24)
-print("linspace resolution: {}, num_values: {} ({} bits)".format(linspace_codec.resolution,
-                                                                 linspace_codec.num_values,
-                                                                 math.log2(linspace_codec.num_values)))
-bits, rounded = linspace_codec.encode(41.123456789)
-
-print("rounded: {}".format(rounded))
-
-decoded_value = linspace_codec.decode(ConstBitStream(bits))
-print("decoded: {}".format(decoded_value))
-
-print("varint test")
-varint_codec = VarintCodec(min_value=0, max_value=1)
-
-print("varint resolution: {}, {} bits".format(varint_codec.resolution,
-                                                                 varint_codec.num_bits,
-                                                                 ))
-bits, rounded = varint_codec.encode(1)
-
-print("rounded: {}".format(rounded))
-
-decoded_value = varint_codec.decode(ConstBitStream(bits))
-print("decoded: {}".format(decoded_value))
-
-
-incoming_bytes = unhexlify("B4054A376502010B0565381300004B05000000000000")
-
-latcodec = CclLatLonBcdCodec(lat_not_lon=True)
-loncodec = CclLatLonBcdCodec(lat_not_lon=False)
-depthcodec = FixedIntCodec(num_bits=16, little_endian=True)
-typecodec = UInt8Codec()
-confidencecodec = UInt8Codec()
-idcodec = FixedIntCodec(num_bits=32, little_endian=True)
-
-incoming_bits = ConstBitStream(bytes=incoming_bytes)
-incoming_bits.bitpos = 8
-lat = latcodec.decode(incoming_bits)
-lon = loncodec.decode(incoming_bits)
-print("position ", incoming_bits.bitpos)
-depth = depthcodec.decode(incoming_bits)
-contact_type = typecodec.decode(incoming_bits)
-confidence = confidencecodec.decode(incoming_bits)
-print("position ", incoming_bits.bitpos)
-contactid = idcodec.decode(incoming_bits)
-
-print(lat, lon, depth, contact_type, confidence, contactid)
-
-try:
-    import rospy
-    from datetime import datetime
-    rospy.init_node('codec_test')
-    time_now = rospy.Time.now()
-    print("time {}".format(datetime.utcfromtimestamp(time_now.to_sec())))
-    time_codec = RosTimeCodec()
-    print("codec {} bits".format(time_codec.max_length_bits))
-    time_bits, rounded_time = time_codec.encode(time_now)
-    print(time_bits)
-    print("len ", len(time_bits))
-    decoded_time = time_codec.decode(ConstBitStream(time_bits))
-    print("time {}".format(datetime.utcfromtimestamp(decoded_time.to_sec())))
-except ImportError:
-    pass
-
-
-
-#print(decoded_message)
diff --git a/ros_acomms/src/acomms_driver_node.py b/ros_acomms/src/acomms_driver_node.py
index 823468232546d56a359b010b50460dc6c976e59b..9e855c75d6b3290e88fd92c5d97797c90c80b975 100755
--- a/ros_acomms/src/acomms_driver_node.py
+++ b/ros_acomms/src/acomms_driver_node.py
@@ -507,7 +507,7 @@ if __name__ == '__main__':
 
         rospy.loginfo("Acomms node started")
 
-        rate = rospy.Rate(1)
+        rate = rospy.Rate(5)
         while not rospy.is_shutdown():
             rate.sleep()
             node.process_outgoing_queue()
diff --git a/ros_acomms/src/check_codec_config.py b/ros_acomms/src/check_codec_config.py
index 2c108661809fc4c51574da6e98b14c4923589036..c66f367cda8195a9752506388cbd82f840dbbca6 100755
--- a/ros_acomms/src/check_codec_config.py
+++ b/ros_acomms/src/check_codec_config.py
@@ -2,19 +2,19 @@
 
 from codec_config_parser.config_parser import get_message_codecs, get_queue_params
 from typing import Dict, List
-from acomms_codecs.packet_codecs import packet_codecs
+from acomms_codecs import packet_codecs
 from ltcodecs import RosMessageCodec
 import rospy
 import argparse
 from packet_dispatch_node import DecoderListEntry
 import yaml
-from yamlinclude import YamlIncludeConstructor
+from rospy_yaml_include.yaml_include import RospyYamlInclude
 
 
 def check_codec_config_yaml(yaml_file_name, codec_dir):
     with open(yaml_file_name, 'r') as yaml_file:
-        YamlIncludeConstructor.add_to_loader_class(loader_class=yaml.FullLoader, base_dir=codec_dir)
-        packet_codec_param = yaml.load(yaml_file)
+        constructor = RospyYamlInclude(base_directory=codec_dir)
+        packet_codec_param = yaml.load(yaml_file, Loader=constructor.add_constructor())
 
     decoder_list: List[DecoderListEntry] = []
     for packet_codec in packet_codec_param:
diff --git a/ros_acomms/src/message_queue_node.py b/ros_acomms/src/message_queue_node.py
index 63f298fde7d3a4f32303421e432b129b31fc3b0e..b18f79d0c138c4d607a1e694361d88ccefa55786 100755
--- a/ros_acomms/src/message_queue_node.py
+++ b/ros_acomms/src/message_queue_node.py
@@ -27,10 +27,9 @@ import os
 import signal
 
 from ltcodecs import RosMessageCodec, EncodingFailed
-from acomms_codecs.ros_packet_codec import RosPacketCodec
-from acomms_codecs.packet_codecs import packet_codecs
+from acomms_codecs import packet_codecs
 import yaml
-from yamlinclude import YamlIncludeConstructor
+from rospy_yaml_include.yaml_include import RospyYamlInclude
 
 from version_node import version_node
 
@@ -46,7 +45,6 @@ class DynamicQueue:
     message_codec_id: int  # the ID from the message codec params
     message_codec: RosMessageCodec
     dynamic_message_service: str
-    packet_codec: RosPacketCodec = None
     allow_fragmentation: bool = False
     dynamic_update_topic: Optional[str] = None
 
@@ -260,7 +258,7 @@ class MessageQueue(object):
 
 class MessageQueueNode(object):
     def __init__(self) -> None:
-        rospy.init_node('message_queue', log_level=rospy.INFO)
+        rospy.init_node('message_queue')
         self.update_rate = rospy.get_param('~update_rate', 1)
         self.unknown_dests_are_reachable: bool = rospy.get_param('~unknown_dests_are_reachable', True)
         self.default_dest = rospy.get_param('~default_dest', 121)
@@ -320,12 +318,12 @@ class MessageQueueNode(object):
         # when queuing for TX, we go from topic -> message -> packet
         # However, organizing it this way allows us to use a single config YAML for both TX and RX
         packet_codec_filename = rospy.get_param('~packet_codec_file', None)
-        codec_dir = rospy.get_param('~codec_directory', 'codecs')
+        codec_dir = rospy.get_param('~codec_directory', None)
         if packet_codec_filename:
             try:
-                with open(packet_codec_filename, 'r') as f:
-                    YamlIncludeConstructor.add_to_loader_class(loader_class=yaml.FullLoader, base_dir=codec_dir)
-                    packet_codec_param = yaml.full_load(f)
+                with open(packet_codec_filename, 'r') as yaml_file:
+                    constructor = RospyYamlInclude(base_directory=codec_dir)
+                    packet_codec_param = yaml.load(yaml_file, Loader=constructor.add_constructor())
             except Exception as e:
                 rospy.logerr(f"Fatal error reading message codec config from {packet_codec_filename} (with codec directory {codec_dir}): {e}")
         else:
@@ -410,7 +408,7 @@ class MessageQueueNode(object):
             # QUESTION(lindzey): It looks like the packet codec is never used?
             #   => AAAAH. This is called for its side effects. Maybe rename to make more clear?
             # The packet codec initializer sets the packet codec on each message codec
-            packet_codec = packet_codec_class(message_codecs=message_codecs,
+            packet_codec = packet_codec_class.PacketCodec(message_codecs=message_codecs,
                                               miniframe_header=packet_codec_details.get('miniframe_header', bytes()),
                                               dataframe_header=packet_codec_details.get('dataframe_header', bytes()))
             rospy.loginfo("Added packet codec with {} message codecs".format(len(message_codecs)))
@@ -740,7 +738,7 @@ class MessageQueueNode(object):
             # been serviced
             self.current_packet_dynamic_queue_cache = {}
 
-            rospy.loginfo("Getting next packet data for transmission: " + str(req).replace('\n', ', '))
+            rospy.logdebug("Getting next packet data for transmission: " + str(req).replace('\n', ', '))
             if req.match_dest:
                 requested_dest = req.dest
             else:
diff --git a/ros_acomms/src/packet_dispatch_node.py b/ros_acomms/src/packet_dispatch_node.py
index 6cd1189bf8dc8814765649b47cb0450e37c6a3bb..3ed00483406c83ba22af4ad2c4cfc609b1a653f1 100755
--- a/ros_acomms/src/packet_dispatch_node.py
+++ b/ros_acomms/src/packet_dispatch_node.py
@@ -9,10 +9,9 @@ from ros_acomms_msgs.msg import ReceivedPacket, Packet, CST
 from bitstring import ConstBitStream, ReadError
 
 from ltcodecs import RosMessageCodec
-from yamlinclude import YamlIncludeConstructor
+from rospy_yaml_include.yaml_include import RospyYamlInclude
 
-from acomms_codecs.ros_packet_codec import RosPacketCodec
-from acomms_codecs.packet_codecs import packet_codecs
+from acomms_codecs import packet_codecs
 
 from version_node import version_node
 from codec_config_parser import QueueParams, QueueId, MessageCodecId, get_queue_params, get_message_codecs
@@ -97,11 +96,12 @@ class PacketDispatchNode(object):
 
     def setup_codecs(self) -> None:
         packet_codec_filename = rospy.get_param('~packet_codec_file', None)
-        codec_dir = rospy.get_param('~codec_directory', 'codecs')
+        codec_dir = rospy.get_param('~codec_directory', None)
         if packet_codec_filename:
-            with open(packet_codec_filename, 'r') as f:
-                YamlIncludeConstructor.add_to_loader_class(loader_class=yaml.FullLoader, base_dir=codec_dir)
-                packet_codec_param = yaml.full_load(f)
+            with open(packet_codec_filename, 'r') as yaml_file:
+                constructor = RospyYamlInclude(base_directory=codec_dir)
+                packet_codec_param = yaml.load(yaml_file, Loader=constructor.add_constructor())
+
         else:
             packet_codec_param = rospy.get_param('~packet_codecs')
 
@@ -147,7 +147,7 @@ class PacketDispatchNode(object):
 
             # replace name with instance of packet codec
             # ToDO make this less ugly and stupid
-            entry.packet_codec = packet_codecs[entry.packet_codec](self.message_codecs,
+            entry.packet_codec = packet_codecs[entry.packet_codec].PacketCodec(self.message_codecs,
                                                                    message_publishers=self.message_publishers,
                                                                    miniframe_header=entry.miniframe_header,
                                                                    dataframe_header=entry.dataframe_header)
diff --git a/ros_acomms/src/tdma_node.py b/ros_acomms/src/tdma_node.py
index 02a0e552395fa7d34f4f281fdb5ef35278807455..8795085a693e81c98244cc776ff7c2115eea97f9 100755
--- a/ros_acomms/src/tdma_node.py
+++ b/ros_acomms/src/tdma_node.py
@@ -223,12 +223,15 @@ class TdmaMacNode(object):
         packet_data_response = self.get_next_packet_data(num_miniframe_bytes=self.maximum_miniframe_bytes,
                                                          num_dataframe_bytes=self.maximum_dataframe_bytes)
 
-        rospy.loginfo("packet_data_response {}".format(packet_data_response))
+        rospy.logdebug("packet_data_response {}".format(packet_data_response))
 
         if packet_data_response.num_messages == 0:
             return
 
-        rospy.loginfo("Queuing Packet for TX")
+        rospy.loginfo(f"Queuing Packet for TX (dest {packet_data_response.dest}, " +
+                      f"{len(packet_data_response.miniframe_bytes)} miniframe bytes @ rate {self.miniframe_rate}, " +
+                      f"{len(packet_data_response.dataframe_bytes)} dataframe bytes @ rate {self.dataframe_rate})")
+
         queue_packet = Packet()
         queue_packet.dataframe_rate = self.dataframe_rate
         queue_packet.miniframe_rate = self.miniframe_rate
diff --git a/ros_acomms_modeling/CMakeLists.txt b/ros_acomms_modeling/CMakeLists.txt
index 5d909ff750edc0e31d8ca6b6cd1d3b599eb0755c..9608f962f8e90a05c389ae48ddb53443f0577af0 100644
--- a/ros_acomms_modeling/CMakeLists.txt
+++ b/ros_acomms_modeling/CMakeLists.txt
@@ -65,10 +65,9 @@ add_message_files(FILES
 add_service_files(
   FILES
   GetOptimalTxDepth.srv
-  GetPlatformLocation.srv
   SimTransmissionLoss.srv
   SimPacketPerformance.srv
-
+  SimTravelTime.srv
 )
 
 ## Generate actions in the 'action' folder
diff --git a/ros_acomms_modeling/src/modem_location_sim_node.py b/ros_acomms_modeling/src/modem_location_sim_node.py
new file mode 100755
index 0000000000000000000000000000000000000000..33ad1f78c272d1b1abae62af90bc5a7ee0ec5d56
--- /dev/null
+++ b/ros_acomms_modeling/src/modem_location_sim_node.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python3
+
+import rospy
+from std_msgs.msg import Header
+from ros_acomms_modeling.msg import Location
+import random
+
+
+class ModemLocationSimNode:
+    def __init__(self):
+        rospy.init_node('modem_location_sim')
+        rospy.loginfo("Starting modem_location_sim_node")
+        update_period = rospy.get_param('~period', 5)
+
+        self.location_publisher = rospy.Publisher('location', Location, latch=True, queue_size=10)
+        while not rospy.is_shutdown():
+            self.pub_location()
+            rospy.sleep(update_period)
+
+    def pub_location(self):
+        lat = random.randrange(41421580, 41553703) * (10 ** -6)
+        lon = random.randrange(70726027, 70843443) * -(10 ** -6)
+        depth = random.randrange(3, 18)
+        msg = Location(header=Header(stamp=rospy.get_rostime()), latitude=lat, longitude=lon, depth=depth)
+        self.location_publisher.publish(msg)
+
+
+if __name__ == '__main__':
+    try:
+        node = ModemLocationSimNode()
+    except rospy.ROSInterruptException:
+        rospy.loginfo("ModemLocationSimNode shutdown (interrupt)")
diff --git a/ros_acomms_modeling/src/modem_sim_node.py b/ros_acomms_modeling/src/modem_sim_node.py
index 364112c7ec245562924c74a4d54cda5fd112e078..08fed9b9cee8f3ffae9822d37fc8344f19df067d 100755
--- a/ros_acomms_modeling/src/modem_sim_node.py
+++ b/ros_acomms_modeling/src/modem_sim_node.py
@@ -1,11 +1,13 @@
 #!/usr/bin/env python3
 import rospy
-import sys
+import roslib.message
+from rospy import AnyMsg
 from std_msgs.msg import Header, Float32
 from ros_acomms_msgs.msg import PingReply, CST, TransmittedPacket, ReceivedPacket, Packet
 from ros_acomms_msgs.srv import PingModem, PingModemResponse, QueueTxPacket, QueueTxPacketResponse
 from ros_acomms_modeling.msg import SimPacket
-from ros_acomms_modeling.srv import SimTransmissionLoss, SimTransmissionLossRequest, SimPacketPerformance, SimPacketPerformanceRequest, GetPlatformLocationResponse
+from ros_acomms_modeling.srv import SimTransmissionLoss, SimTransmissionLossRequest, SimPacketPerformance, \
+    SimPacketPerformanceRequest, SimTravelTime, SimTravelTimeRequest, SimTravelTimeResponse
 from queue import Queue, Empty, Full
 from threading import Thread, Event
 from collections import namedtuple
@@ -13,14 +15,9 @@ import numpy as np
 from typing import Optional, List
 # Use ros_groundtruth messages for sim if available, otherwise use internal versions.
 try:
-    from groundtruth.msg import Tick, Tock, Location
-    from groundtruth.srv import ReadLocation, ReadLocationRequest, ReadLocationResponse
+    from groundtruth.msg import Tick, Tock
 except ImportError:
-    from ros_acomms_modeling.msg import Tick, Tock, Location
-    from ros_acomms_modeling.srv import GetPlatformLocation as ReadLocation
-    from ros_acomms_modeling.srv import GetPlatformLocationRequest as ReadLocationRequest
-    from ros_acomms_modeling.srv import GetPlatformLocationResponse as ReadLocationResponse
-
+    from ros_acomms_modeling.msg import Tick, Tock
 
 ActivePacket = namedtuple('ActivePacket', [
                           'sim_packet', 'arrival_time', 'finish_time', 'receive_level_db'])
@@ -32,6 +29,14 @@ def sum_incoherent_db(levels_in_db):
     return 10.0 * np.log10(np.sum(10**(levels_in_db/10)))
 
 
+def deserialze_anymsg(msg_data: AnyMsg):
+    topic_type = msg_data._connection_header['type']
+    topic_class = roslib.message.get_message_class(topic_type)
+    msg = topic_class()
+    msg.deserialize(msg_data._buff)
+    return msg
+
+
 class PingReplyTransaction(object):
     PING_TYPE_NONE = 0
     PING_TYPE_REQUEST = 1
@@ -145,26 +150,28 @@ class ModemSimNode(object):
         else:
             self.tock_topic_name = 'tock'
 
-        self.modem_location_source = rospy.get_param('~modem_location_source', default='service')
-        static_latitude = rospy.get_param('~latitude', default=None)
-        static_longitude = rospy.get_param('~longitude', default=None)
-        static_depth = rospy.get_param('~depth', default=None)
-        # Check for errors
-        if self.modem_location_source == 'service' and (static_latitude or static_longitude or static_depth):
-            rospy.logwarn(
-                'Static modem sim position specified, but modem_location_source is not set to "static" or "message"')
-        if self.modem_location_source in ('static', 'message'):
-            if not (static_latitude and static_longitude and static_depth):
-                rospy.logfatal(
-                    'Static or message modem sim position selected, but initial position is not set (rosparms for latitude, longitude, depth)')
-                sys.exit(1)
-            self.static_location = Position(
-                static_latitude, static_longitude, static_depth)
-            if self.modem_location_source == 'message':
-                rospy.Subscriber('location', Location, self.on_modem_location)
-        else:
-            rospy.loginfo("Modem sim node waiting for read_location service")
-            rospy.wait_for_service('/read_location')
+        location_source_param = rospy.get_param('~modem_location_source', default=None)
+        if location_source_param:
+            rospy.logwarn("modem_location_source and the GetPlatformLocation service is no longer supported. " +
+                          " Use modem_location_topic to specify a topic" +
+                          " that publishes location (defaults to 'location') or use the latitude/longitude/depth" +
+                          " params to specify a static location.  This warning will be removed in a future release.")
+
+        modem_location_topic = rospy.get_param('~modem_location_topic', default='location')
+        latitude = rospy.get_param('~latitude', default=None)
+        longitude = rospy.get_param('~longitude', default=None)
+        depth = rospy.get_param('~depth', default=None)
+        if not longitude or not latitude or not depth:
+            # if we don't have a static position, we need to wait for a position to arrive.
+            rospy.loginfo(f"No static position was specified, waiting for modem location on {modem_location_topic}")
+            msg = rospy.wait_for_message(modem_location_topic, AnyMsg)
+            location = deserialze_anymsg(msg)
+            latitude = location.latitude
+            longitude = location.longitude
+            depth = location.depth
+
+        self.modem_location = Position(latitude, longitude, depth)
+        rospy.Subscriber('location', AnyMsg, self.on_modem_location)
 
         self.rx_publisher = rospy.Publisher(
             'packet_rx', ReceivedPacket, queue_size=self.maxsize)
@@ -188,6 +195,8 @@ class ModemSimNode(object):
         rospy.loginfo("Modem sim node waiting for simulation services")
         rospy.wait_for_service('/sim_transmission_loss')
         rospy.wait_for_service('/sim_packet_performance')
+        rospy.wait_for_service('/sim_travel_time')
+        self.call_sim_travel_time = rospy.ServiceProxy('/sim_travel_time', SimTravelTime)
 
         # Handling for sim to initiate and reply to Pings
         self.ping_reply_publisher = rospy.Publisher(
@@ -213,14 +222,15 @@ class ModemSimNode(object):
         rospy.loginfo("Modem sim node running{}. SRC={}, FC={} Hz, BW={} Hz".format(
                       " with sim ticks active" if self.use_sim_tick else "", self.src, self.fc, self.bw))
 
+        rospy.spin()
+
     def on_ambient_noise(self, msg: Float32):
         self.ambient_noise = msg.data
 
-    def on_modem_location(self, msg):
-        rospy.loginfo("New modem location: latitude={}, longitude={}, depth={}".format(msg.latitude,
-                                                                                       msg.longitude,
-                                                                                       msg.depth))
-        self.static_location = Position(msg.latitude, msg.longitude, msg.depth)
+    def on_modem_location(self, msg: AnyMsg):
+        msg = deserialze_anymsg(msg)
+        rospy.logdebug(f"New modem location: latitude={msg.latitude}, longitude={msg.longitude}, depth={msg.depth}")
+        self.modem_location = Position(msg.latitude, msg.longitude, msg.depth)
 
     def on_sim_tick(self, msg):
         try:
@@ -238,13 +248,6 @@ class ModemSimNode(object):
             # TODO: publish a TOCK with status=0x4
             # TODO: other errors, respond with generic reset command (0x01)
 
-    def get_ownship_location(self) -> Position:
-        if self.modem_location_source in ('static', 'message'):
-            return self.static_location
-        else:
-            location = self.call_get_platform_location()
-            return Position(location.latitude, location.longitude, location.depth)
-
     def format_ping_for_tx_queue(self, dest, queue=3, packet_type=3):
         tx_packet = QueueTxPacket()
         tx_packet.queue = queue
@@ -520,13 +523,26 @@ class ModemSimNode(object):
     def check_if_packet_is_active(self, current_time: rospy.Time,
                                   sim_packet: SimPacket,
                                   rcv_platform_location: Position) -> Optional[ActivePacket]:
-        recv_level, arrival_time, finish_time = self.get_transmission_loss_sync(
-            sim_packet, rcv_platform_location)
+
+        # We first check the arrival time, which doesn't require calculating receive level (and therefore doesn't
+        # necessarily require running bellhop
+        response = self.call_sim_travel_time(SimTravelTimeRequest(src_latitude=sim_packet.src_latitude,
+                                                                  src_longitude=sim_packet.src_longitude,
+                                                                  src_depth=sim_packet.src_depth,
+                                                                  rcv_latitude=rcv_platform_location.latitude,
+                                                                  rcv_longitude=rcv_platform_location.longitude,
+                                                                  rcv_depth=rcv_platform_location.depth,
+                                                                  center_frequency_hz=sim_packet.center_frequency_hz))
+        arrival_time = sim_packet.transmit_time + response.travel_time
 
         # If the packet hasn't arrived yet, we're done with this packet for now.
         if arrival_time > current_time:
             return None
 
+        # Once the packet arrives, we need receive level.
+        recv_level, arrival_time, finish_time = self.get_transmission_loss_sync(
+            sim_packet, rcv_platform_location)
+
         # If this packet is in progress, we need to add it to the list of active packets.
         # This list short be sorted, by virtue of the order in which things are added.
         active_packet = ActivePacket(
@@ -544,7 +560,7 @@ class ModemSimNode(object):
         return received_packet
 
     def process_inwater_packets(self, current_time) -> None:
-        rcv_platform_location = self.get_ownship_location()
+        rcv_platform_location = self.modem_location
 
         # If we are using messages to get location, we may not have received a message yet.
         if rcv_platform_location.latitude is None:
@@ -719,29 +735,7 @@ class ModemSimNode(object):
         if ping:
             return queue_tx_packet_resp, sim_packet
         return queue_tx_packet_resp
-
-    """ Calls GetPlatformLocation Service """
-
-    def call_get_platform_location(self):
-        try:
-            rospy.logdebug("Trying to get platform location")
-            get_platform_location = rospy.ServiceProxy(
-                '/read_location', ReadLocation)
-            platform_location_req = ReadLocationRequest()
-            platform_location_req.name = rospy.get_namespace()
-            platform_location = get_platform_location(platform_location_req)
-            if not platform_location.valid:
-                rospy.logerr("Platform not found")
-            log_str = "MODEM %d: ReadLocationReply: Lat: %f, Lon: %f, Depth: %f" % (self.src,
-                                                                                    platform_location.latitude,
-                                                                                    platform_location.longitude,
-                                                                                    platform_location.depth)
-            rospy.logdebug(log_str)
-            return platform_location
-        except rospy.ServiceException:
-            rospy.logwarn_throttle(
-                1, "MODEM %d: Get Platform Location Service call failed", self.src)
-
+    
     """ Calls SimPacketPerformance Service """
 
     def call_sim_packet_performance(self, sim_packet_performance_req):
@@ -791,7 +785,7 @@ class ModemSimNode(object):
         try:
             while not rospy.is_shutdown():
                 # get platform location
-                platform_location = self.get_ownship_location()
+                platform_location = self.modem_location
                 if platform_location.latitude is None:
                     # This can happen if the source is 'message' and no message has been published yet
                     # Wait for actual wall-clock time, since this isn't really part of the simulation, and we can end
@@ -871,12 +865,6 @@ if __name__ == '__main__':
     try:
         node = ModemSimNode()
         rospy.loginfo("MODEM %d: Sim Node started", node.src)
-        rate = rospy.Rate(1)
-
-        while not rospy.is_shutdown():
-            rate.sleep()
-
-        rospy.loginfo("MODEM %d: Sim Node shutdown", node.src)
 
     except rospy.ROSInterruptException:
         rospy.loginfo("MODEM: Sim Node shutdown (interrupt)")
diff --git a/ros_acomms_modeling/src/platform_location_node.py b/ros_acomms_modeling/src/platform_location_node.py
deleted file mode 100755
index d73ffd071a70938a22c248160774c8881ae5fe2c..0000000000000000000000000000000000000000
--- a/ros_acomms_modeling/src/platform_location_node.py
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/usr/bin/env python3
-
-import rospy
-from ros_acomms_modeling.srv import GetPlatformLocation, GetPlatformLocationResponse
-import random
-
-
-class PlatformLocationNode(object):
-    def __init__(self):
-        rospy.init_node('platform_location_node')
-        rospy.loginfo("Starting get_platform_location_node")
-        platform_location = rospy.Service(
-            'read_location', GetPlatformLocation, self.handle_get_platform_location)
-        rospy.spin()
-
-    def handle_get_platform_location(self, req):
-        rospy.logdebug("Getting Platform Location for SRC %s", req.name)
-        # Using same setup as test_sim_transmission_loss - don't care about the source
-        lat = random.randrange(41421580, 41553703) * (10 ** -6)
-        lon = random.randrange(70726027, 70843443) * -(10 ** -6)
-        depth = random.randrange(3, 18)
-        return GetPlatformLocationResponse(latitude=lat, longitude=lon, depth=depth, valid=True)
-
-
-if __name__ == '__main__':
-    try:
-        node = PlatformLocationNode()
-
-    except rospy.ROSInterruptException:
-        rospy.loginfo("get_platform_location_node shutdown (interrupt)")
diff --git a/ros_acomms_modeling/src/sim_packet_performance_node.py b/ros_acomms_modeling/src/sim_packet_performance_node.py
index 22fc3c8593a5310f4cd2feee26c292903b1d1b1b..3ed8a04b6dea6a971cf85022b709d946a9d5b91a 100755
--- a/ros_acomms_modeling/src/sim_packet_performance_node.py
+++ b/ros_acomms_modeling/src/sim_packet_performance_node.py
@@ -45,7 +45,7 @@ class SimPacketPerformanceNode(object):
         sim_service = rospy.Service(
             'sim_packet_performance', SimPacketPerformance, self.handle_sim)
 
-        sim_service.spin()
+        rospy.spin()
 
     def handle_sim(self, req):
         rospy.logdebug("Simulating Packet Performance:\r\n" + str(req))
diff --git a/ros_acomms_modeling/src/sim_transmission_loss_node.py b/ros_acomms_modeling/src/sim_transmission_loss_node.py
index 3b5100cd173ece25e0f1c9df8ba8035556a97822..cd96a8d32b4f9734f6892a96f87c624551ba98ce 100755
--- a/ros_acomms_modeling/src/sim_transmission_loss_node.py
+++ b/ros_acomms_modeling/src/sim_transmission_loss_node.py
@@ -1,16 +1,18 @@
 #!/usr/bin/env python3
 
 import rospy
-from ros_acomms_modeling.srv import SimTransmissionLoss, SimTransmissionLossResponse, GetOptimalTxDepth, GetOptimalTxDepthResponse
+from ros_acomms_modeling.srv import SimTransmissionLoss, SimTransmissionLossResponse, GetOptimalTxDepth, \
+    GetOptimalTxDepthResponse, SimTravelTime, SimTravelTimeRequest, SimTravelTimeResponse
 from ros_acomms_msgs.msg import SoundSpeedProfile
 
 import numpy as np
 from sys import float_info as fi
-import arlpy.uwapm as pm
+import whoi_uwapm.uwapm as pm
 from geopy.distance import distance
 
 from shutil import which  # Used to test if bellhop is available
 
+models = ['bellhop', 'bellhopcxx']
 
 class SimTransmissionLossNode(object):
     def __init__(self):
@@ -19,10 +21,10 @@ class SimTransmissionLossNode(object):
         rospy.loginfo("Starting sim_transmission_loss_node...")
 
         # Get node parameters
+        self.model = rospy.get_param('~model', default='bellhopcxx')
         self.sound_speed = rospy.get_param('~sound_speed', default=1500)
         self.water_depth = rospy.get_param('~water_depth', default=1000)
         self.plot = rospy.get_param('~plot', default=False)
-        self.model = rospy.get_param('~model', default='bellhop')
         self.bellhop_env_nbeams = rospy.get_param(
             '~bellhop_env_nbeams', default=3000)
         self.bellhop_transmission_loss_mode = rospy.get_param(
@@ -30,14 +32,7 @@ class SimTransmissionLossNode(object):
         self.bellhop_arrivals = rospy.get_param(
             '~bellhop_arrivals', default=False)
 
-        if self.model == 'bellhop':
-            # Check that bellhop.exe is available.  If it isn't, crash our now rather than waiting until we try to
-            # simulate.
-            if which('bellhop.exe') is None:
-                rospy.logfatal("bellhop.exe was not found on your path (check the ros_acomms README for instructions). "
-                               "sim_transmission_loss_node terminating.")
-                exit()
-
+        if self.model in models:
             # Check node parameters
             # TODO: check validity of the parameters here and output contents to log
 
@@ -108,6 +103,7 @@ class SimTransmissionLossNode(object):
 
         sim_service = rospy.Service(
             'sim_transmission_loss', SimTransmissionLoss, self.handle_sim_transmission_loss)
+        rospy.Service('sim_travel_time', SimTravelTime, self.handle_sim_travel_time)
         optimal_tx_depth_service = rospy.Service(
             'get_optimal_tx_depth', GetOptimalTxDepth, self.handle_optimal_tx_depth)
 
@@ -149,6 +145,26 @@ class SimTransmissionLossNode(object):
         return SimTransmissionLossResponse(rcv_rx_level_db=receive_level_db,
                                            transmission_delay=rospy.Duration.from_sec(latency))
 
+    def handle_sim_travel_time(self, req: SimTravelTimeRequest) -> SimTravelTimeResponse:
+        rospy.logdebug(f"Simulating Travel Time: {req}")
+        # Convert lat,lon to compute range
+        src = (req.src_latitude, req.src_longitude)
+        rcv = (req.rcv_latitude, req.rcv_longitude)
+
+        horizontal_range = distance(src, rcv, ellipsoid='WGS-84').meters
+
+        # Compute arrival time and convert to latency
+        if self.bellhop_arrivals:
+            travel_time = self.get_bellhop_latency(
+                horizontal_range, req.src_depth, req.rcv_depth, req.center_frequency_hz)
+            model = 'bellhop'
+        else:
+            travel_time = self.get_geometric_latency(
+                horizontal_range, req.src_depth, req.rcv_depth)
+            model = 'geometric'
+
+        return SimTravelTimeResponse(travel_time=rospy.Duration.from_sec(travel_time), model=model)
+
     def handle_optimal_tx_depth(self, req):
         rospy.logdebug("Calculating optimal TX depth:\r\n" + str(req))
 
@@ -172,7 +188,7 @@ class SimTransmissionLossNode(object):
         return GetOptimalTxDepthResponse(rcv_rx_level_db=best_rl, optimal_tx_depth=best_depth)
 
     def get_receive_level(self, horizontal_range, src_depth, dest_depth, center_frequency=10000, tx_level_db=185):
-        if self.model == 'bellhop':
+        if self.model in models:
             return self.get_receive_level_bellhop(horizontal_range, src_depth, dest_depth,
                                                   center_frequency, tx_level_db)
         else:
@@ -188,7 +204,7 @@ class SimTransmissionLossNode(object):
         # Run bellhop transmission loss
         time1 = rospy.get_time()
         tloss = pm.compute_transmission_loss(
-            self.env, mode=self.bellhop_transmission_loss_mode, model='bellhop')
+            self.env, mode=self.bellhop_transmission_loss_mode, model=self.model)
         time2 = rospy.get_time()
         rospy.logdebug("Bellhop transmission loss takes [" + str(self.bellhop_env_nbeams) + " beams]: " + str(
             time2 - time1) + " seconds")
@@ -207,7 +223,7 @@ class SimTransmissionLossNode(object):
         self.env['frequency'] = center_frequency
 
         time1 = rospy.get_time()
-        arrivals = pm.compute_arrivals(self.env, model='bellhop')
+        arrivals = pm.compute_arrivals(self.env, model=self.model)
         latency = arrivals['time_of_arrival'].iloc[0]
         time2 = rospy.get_time()
         rospy.logdebug("Bellhop arrival times takes: " +
diff --git a/ros_acomms_modeling/srv/GetPlatformLocation.srv b/ros_acomms_modeling/srv/GetPlatformLocation.srv
deleted file mode 100644
index afd93ebdd4627c006273659b51c997817a928ddb..0000000000000000000000000000000000000000
--- a/ros_acomms_modeling/srv/GetPlatformLocation.srv
+++ /dev/null
@@ -1,9 +0,0 @@
-# Request
-string name
-
----
-# Response
-bool valid
-float32 latitude
-float32 longitude
-float32 depth
\ No newline at end of file
diff --git a/ros_acomms_modeling/srv/SimTravelTime.srv b/ros_acomms_modeling/srv/SimTravelTime.srv
new file mode 100644
index 0000000000000000000000000000000000000000..b7e47280e2aef8c0a2afe44a12ca4278623c80e0
--- /dev/null
+++ b/ros_acomms_modeling/srv/SimTravelTime.srv
@@ -0,0 +1,38 @@
+### Request
+
+# Source Info
+# Source Latitude [decimal degrees]. Positive is north of equator; negative is south
+# (-90 <= latitude <= +90).
+float32 src_latitude
+
+# Source Longitude [decimal degrees]. Positive is east of prime meridian; negative is west
+# (-180 <= longitude <= +180)
+float32 src_longitude
+
+# Source Depth [m]. Positive is below the WGS 84 ellipsoid.
+float32 src_depth
+
+# Receiver Info
+# Receiver Latitude [decimal degrees]. Positive is north of equator; negative is south
+# (-90 <= latitude <= +90).
+float32 rcv_latitude
+
+# Receiver Longitude [decimal degrees]. Positive is east of prime meridian; negative is west
+# (-180 <= longitude <= +180)
+float32 rcv_longitude
+
+# Receiver Depth [m]. Positive is below the WGS 84 ellipsoid.
+float32 rcv_depth
+
+# Center frequency [hz]
+float32 center_frequency_hz
+
+---
+
+### Response
+
+# Acoustic travel time between source and receiver
+duration travel_time
+
+# Name of model used to calculate travel time
+string model
diff --git a/ros_acomms_tests/launch/dynamic_codec_config.yaml b/ros_acomms_tests/launch/dynamic_codec_config.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..09e2b870980fde1db1e98e2a112b0fb9cb471424
--- /dev/null
+++ b/ros_acomms_tests/launch/dynamic_codec_config.yaml
@@ -0,0 +1,24 @@
+- codec_name: default
+  match_src: []
+  match_dest: []
+  except_src: []
+  except_dest: []
+  packet_codec: ros
+  miniframe_header: [0xac]
+  dataframe_header: []
+  remove_headers: true
+  message_codecs:
+    - id: 103
+      message_codec: default
+      dynamic_queue_service: "get_next"
+      dynamic_update_topic: "update_queue"
+      publish_topic: "from_acomms/test_dynamic_msg"
+      ros_type: "std_msgs/UInt8"
+      default_dest: 121
+      queue_order: fifo
+      queue_maxsize: 10
+      allow_fragmentation: false
+      priority: 10
+      fields:
+        data:
+          codec: uint8
diff --git a/ros_acomms_tests/launch/dynamic_queue_test.launch b/ros_acomms_tests/launch/dynamic_queue_test.launch
new file mode 100644
index 0000000000000000000000000000000000000000..44ee15fa78f4fe6d286de5b9453367f2eb9eb9aa
--- /dev/null
+++ b/ros_acomms_tests/launch/dynamic_queue_test.launch
@@ -0,0 +1,68 @@
+<launch>
+    <env name="ROSCONSOLE_FORMAT" value="[${severity}] [${time}] [${node}]: ${message}"/>
+
+    <param name="use_sim_time" value="true"/>
+
+    <group ns="modem0">
+        <node name="modem_sim_node" pkg="ros_acomms_modeling" type="modem_sim_node.py" output="screen" >
+            <param name="center_frequency_hz" value="10000" type="int" />
+            <param name="bandwidth_hz" value="5000" type="int" />
+            <param name="SRC" value="0" type="int" />
+            <param name="modem_location_source" value="local_service" />
+        </node>
+
+        <node name="tdma" pkg="ros_acomms" type="tdma_node.py" output="screen" required="true" respawn="false">
+            <param name="num_slots" value="2"/>
+            <param name="active_slots" value="0"/>
+            <param name="slot_duration_seconds" value="30" />
+        </node>
+
+
+        <node name="message_queue_node" pkg="ros_acomms" type="message_queue_node.py" output="screen" >
+            <param name="packet_codec_file" value="$(find ros_acomms_tests)/launch/dynamic_codec_config.yaml"/>
+        </node>
+
+        <node name="packet_dispatch" pkg="ros_acomms" type="packet_dispatch_node.py" output="screen" >
+            <param name="packet_codec_file" value="$(find ros_acomms_tests)/launch/dynamic_codec_config.yaml"/>
+        </node>
+
+        <node name="modem_location_sim" pkg="ros_acomms_modeling" type="modem_location_sim_node.py" />
+    </group>
+
+    <group ns="modem1">
+        <node name="modem_sim_node" pkg="ros_acomms_modeling" type="modem_sim_node.py" output="screen" >
+            <param name="center_frequency_hz" value="10000" type="int" />
+            <param name="bandwidth_hz" value="5000" type="int" />
+            <param name="SRC" value="1" type="int" />
+            <param name="modem_location_source" value="local_service" />
+        </node>
+
+        <node name="tdma" pkg="ros_acomms" type="tdma_node.py" output="screen" required="true" respawn="false">
+            <param name="num_slots" value="2"/>
+            <param name="active_slots" value="1"/>
+            <param name="slot_duration_seconds" value="30" />
+        </node>
+
+        <node name="message_queue_node" pkg="ros_acomms" type="message_queue_node.py" output="screen" >
+                <param name="packet_codec_file" value="$(find ros_acomms_tests)/launch/dynamic_codec_config.yaml"/>
+        </node>
+
+        <node name="packet_dispatch" pkg="ros_acomms" type="packet_dispatch_node.py" output="screen" >
+            <param name="packet_codec_file" value="$(find ros_acomms_tests)/launch/dynamic_codec_config.yaml"/>
+        </node>
+
+        <node name="modem_location_sim" pkg="ros_acomms_modeling" type="modem_location_sim_node.py" />
+    </group>
+
+    <node name="sim_packet_performance_node" pkg="ros_acomms_modeling" type="sim_packet_performance_node.py" />
+
+    <node name="sim_transmission_loss_node" pkg="ros_acomms_modeling" type="sim_transmission_loss_node.py"  >
+        <param name="water_depth" value="20" type="int" />
+        <param name="bellhop_arrivals" value="false" type="bool" />
+    </node>
+
+    <!--<node name="test_modem_sim_node" pkg="ros_acomms" type="test_modem_sim_node.py" output="screen" />-->
+
+    <node name="clock_generator" pkg="ros_acomms_modeling" type="clock_generator.py" />
+
+</launch>
diff --git a/ros_acomms_tests/launch/message_codec_config.yaml b/ros_acomms_tests/launch/message_codec_config.yaml
index aad516c8c4e1085f9b118745a6093db683cdfea9..7ebb705067ec5834bcf879d6686a429963e54655 100644
--- a/ros_acomms_tests/launch/message_codec_config.yaml
+++ b/ros_acomms_tests/launch/message_codec_config.yaml
@@ -38,4 +38,4 @@
       priority: 50
       fields:
         data:
-          codec: uint8
\ No newline at end of file
+          codec: uint8
diff --git a/ros_acomms_tests/launch/test.launch b/ros_acomms_tests/launch/test.launch
index a8effb12eb94eaf4f1e2751c8034d78de6baa66d..dfba6692c982e79c768077e7b3389391a16c1917 100644
--- a/ros_acomms_tests/launch/test.launch
+++ b/ros_acomms_tests/launch/test.launch
@@ -8,7 +8,6 @@
             <param name="center_frequency_hz" value="10000" type="int" />
             <param name="bandwidth_hz" value="5000" type="int" />
             <param name="SRC" value="0" type="int" />
-            <param name="modem_location_source" value="local_service" />
         </node>
 
         <node name="tdma" pkg="ros_acomms" type="tdma_node.py" output="screen" required="true" respawn="false">
@@ -25,6 +24,8 @@
         <node name="packet_dispatch" pkg="ros_acomms" type="packet_dispatch_node.py" output="screen" >
             <param name="packet_codec_file" value="$(find ros_acomms_tests)/launch/message_codec_config.yaml"/>
         </node>
+
+        <node name="modem_location_sim" pkg="ros_acomms_modeling" type="modem_location_sim_node.py" />
     </group>
 
     <group ns="modem1">
@@ -32,7 +33,6 @@
             <param name="center_frequency_hz" value="10000" type="int" />
             <param name="bandwidth_hz" value="5000" type="int" />
             <param name="SRC" value="1" type="int" />
-            <param name="modem_location_source" value="local_service" />
         </node>
 
         <node name="tdma" pkg="ros_acomms" type="tdma_node.py" output="screen" required="true" respawn="false">
@@ -49,13 +49,11 @@
             <param name="packet_codec_file" value="$(find ros_acomms_tests)/launch/message_codec_config.yaml"/>
         </node>
 
-
+        <node name="modem_location_sim" pkg="ros_acomms_modeling" type="modem_location_sim_node.py" />
     </group>
 
     <node name="sim_packet_performance_node" pkg="ros_acomms_modeling" type="sim_packet_performance_node.py" />
 
-    <node name="platform_location_node" pkg="ros_acomms_modeling" type="platform_location_node.py"  />
-
     <node name="sim_transmission_loss_node" pkg="ros_acomms_modeling" type="sim_transmission_loss_node.py"  >
         <param name="water_depth" value="20" type="int" />
         <param name="bellhop_arrivals" value="false" type="bool" />
diff --git a/ros_acomms_tests/src/test_dynamic_queue.py b/ros_acomms_tests/src/test_dynamic_queue.py
new file mode 100755
index 0000000000000000000000000000000000000000..718f8d91ddbd8455a6c63dc2bd8cf29700a20e68
--- /dev/null
+++ b/ros_acomms_tests/src/test_dynamic_queue.py
@@ -0,0 +1,100 @@
+#! /usr/bin/env python3
+
+
+'''
+test_dynamic_queue
+------------------
+
+tests for dynamic queue in ros accoms
+'''
+
+
+import os
+import sys
+import pytest
+import rospy
+import rospkg
+import roslaunch
+from std_msgs.msg import UInt8
+
+from ros_acomms_msgs.srv import GetNextQueuedMessage, GetNextQueuedMessageRequest, GetNextQueuedMessageResponse
+from ros_acomms_msgs.msg import UpdateQueuedMessage
+
+class Helper():
+    def __init__(self) -> None:
+        rospy.Service('modem0/get_next', GetNextQueuedMessage, self.handle_get_next)
+        rospy.Subscriber('modem0/update_queue', UpdateQueuedMessage, self.on_update_queue)  
+
+        rospy.Service('modem1/get_next', GetNextQueuedMessage, self.handle_get_next)
+        rospy.Subscriber('modem1/update_queue', UpdateQueuedMessage, self.on_update_queue)
+        self.hasData = None
+
+    def handle_get_next(self, req: GetNextQueuedMessageRequest) -> None :
+        '''
+        :param req: service request data 
+        ''' 
+        if self.hasData is not None:
+            payload = bytes([6])
+            self.hasData = None if self.hasData == 0 else self.hasData-1
+            return GetNextQueuedMessageResponse(has_message=True,
+                                                    message_id=0,
+                                                    dest_address=121,
+                                                    priority=10,
+                                                    data=payload,
+                                                    data_size_in_bits=len(payload)*8,
+                                                    )
+            
+        else:
+            return GetNextQueuedMessageResponse(has_message=False)
+    
+    def on_update_queue(self, update_msg: UpdateQueuedMessage):
+        pass
+
+
+class TestDynamicQueue:
+    '''ros_acomms dynamic queue test class'''
+    
+    @classmethod
+    def setup_class(self):
+        """
+        setup function for tests
+        """
+
+        rospy.init_node("test_dynamic_queue", log_level=rospy.INFO)
+
+        self.rospack = rospkg.RosPack()
+
+        uuid = roslaunch.rlutil.get_or_generate_uuid(None, False)
+        roslaunch.configure_logging(uuid)
+        
+        launch_file = os.path.join(
+            self.rospack.get_path("ros_acomms_tests"),
+            "launch/dynamic_queue_test.launch",
+        )
+        self.helper = Helper()
+        self.launch = roslaunch.parent.ROSLaunchParent(uuid, [launch_file])
+        self.launch.start() 
+    
+
+    def test_message_recieved(self):
+        '''
+        Test for simple dynamic queue message, test for message transmission and marked as transmitted
+        '''
+        self.helper.hasData = 2
+        updateMsg = rospy.wait_for_message(
+                    "/modem0/update_queue", UInt8, timeout=100)
+        ret = rospy.wait_for_message(
+                   "/modem1/from_acomms/test_dynamic_msg", UInt8, timeout=100)
+        assert ret.data == 6, f"return: {ret.data}" 
+        assert updateMsg.event == 2, f"update msg event not marked as transmitted"
+
+    @classmethod
+    def teardown_class(self):
+        """
+        teardown function for tests
+        """
+
+        self.launch.shutdown()
+
+if __name__ == "__main__":
+    sys.exit(pytest.main(['--capture=no', __file__]))
diff --git a/ros_acomms_tests/src/test_ros_acomms.py b/ros_acomms_tests/src/test_ros_acomms.py
index 1f43fabdeacffa10dc3feefaa689a1635769122b..b5d4b394baac00e65c7cdb8d2b9363838d35b87d 100755
--- a/ros_acomms_tests/src/test_ros_acomms.py
+++ b/ros_acomms_tests/src/test_ros_acomms.py
@@ -74,7 +74,6 @@ class TestRosAcomms:
             rospy.wait_for_message(
                 "/modem1/from_acomms/test_fragmentation_msg", FragmentationTestMsg, timeout=100)
 
-
             assert msg.hashstr == get_hash(msg.data)
 
         except rospy.exceptions.ROSException:
diff --git a/setup.bash b/setup.bash
deleted file mode 100755
index 1388bc9df812dc90874d86b7ffbabfb3e27c5357..0000000000000000000000000000000000000000
--- a/setup.bash
+++ /dev/null
@@ -1,9 +0,0 @@
- #!/bin/bash
-
-pip install -U -r requirements.txt
-cd ~
-git clone https://git.whoi.edu/dgiaya/acoustic_toolbox.git
-cd acoustic_toolbox
-sudo apt-get install gfortran -y
-make
-sudo ln -s ~/acoustic_toolbox/Bellhop/bellhop.exe /usr/bin/bellhop.exe