From dd3592bf0374ed8bc6f2bd2a20ee7accee9300b5 Mon Sep 17 00:00:00 2001 From: Eric Gallimore <egallimore@whoi.edu> Date: Fri, 1 Mar 2024 18:16:23 -0500 Subject: [PATCH 1/5] Optionally query battery info from the modem (via $CCBAT) in the modem_sensor_data_node. --- ros_acomms/src/modem_sensor_data_node.py | 73 +++++++++++++++++++++--- 1 file changed, 65 insertions(+), 8 deletions(-) diff --git a/ros_acomms/src/modem_sensor_data_node.py b/ros_acomms/src/modem_sensor_data_node.py index 6e271a45..88a34a99 100755 --- a/ros_acomms/src/modem_sensor_data_node.py +++ b/ros_acomms/src/modem_sensor_data_node.py @@ -1,9 +1,15 @@ #!/usr/bin/env python3 +import sys import rospy from std_msgs.msg import String, Header +from sensor_msgs.msg import BatteryState from ros_acomms_msgs.msg import ModemSensorData +def get_float_from_string_with_units(input_string: str) -> float: + return float(''.join(c for c in input_string if (c.isdigit() or c == '.' or c == '-'))) + + class ModemSensorDataNode(object): fields = {'pwramp.temperature_degC': 'pwramp_temperature', 'pwramp.vbat': 'pwramp_vbat', @@ -15,11 +21,22 @@ class ModemSensorDataNode(object): rospy.init_node('modem_sensor_data') self.query_interval_seconds = rospy.get_param('~query_interval_seconds', 30) + self.enable_battery_query = rospy.get_param('~enable_battery_query', default=False) + minimum_interval = 5 if not self.query_battery else 30 + if self.query_interval_seconds < minimum_interval: + rospy.logwarn(f'Query interval must be at least {minimum_interval} seconds. Setting to {minimum_interval}') + self.query_interval_seconds = minimum_interval self.modem_sensor_data_publisher = rospy.Publisher('modem_sensor_data', ModemSensorData, queue_size=10) + if self.query_battery: + self.battery_state_publisher = rospy.Publisher('battery_state', BatteryState, queue_size=10) self.nmea_publisher = rospy.Publisher('nmea_to_modem', String, queue_size=10) self.value_dict = {} + self.battery_percent = 0 + self.battery_voltage_mV = 0 + self.battery_current_mA = 0 + self.battery_remaining_capacity_mAh = 0 rospy.Subscriber('nmea_from_modem', String, self.on_nmea_subscriber) @@ -31,8 +48,13 @@ class ModemSensorDataNode(object): while not rospy.is_shutdown(): self.query_modem() rospy.sleep(2) + if self.enable_battery_query: + self.query_battery() + rospy.sleep(2) self.publish_data_message() - rospy.sleep(self.query_interval_seconds - 2) + if self.enable_battery_query: + self.publish_battery_state_message() + rospy.sleep(self.query_interval_seconds - 4) def query_modem(self): for cfg_param in self.fields.keys(): @@ -40,20 +62,55 @@ class ModemSensorDataNode(object): msg = String(data=nmea_string) self.nmea_publisher.publish(msg) - def on_nmea_subscriber(self, msg): + def query_battery(self): + nmea_string = "$CCBAT,0" + msg = String(data=nmea_string) + self.nmea_publisher.publish(msg) + + def on_nmea_subscriber(self, msg: String) -> None: nmea_string = msg.data - if "CACFG" in nmea_string: - msg_parts = nmea_string.split(',') - cfg_param = msg_parts[1] - if cfg_param in self.fields: - value = float(msg_parts[2].split('*')[0]) - self.value_dict[self.fields[cfg_param]] = value + try: + if "CACFG" in nmea_string: + msg_parts = nmea_string.split(',') + cfg_param = msg_parts[1] + if cfg_param in self.fields: + value = float(msg_parts[2].split('*')[0]) + self.value_dict[self.fields[cfg_param]] = value + elif "CABAT" in nmea_string: + msg_parts = nmea_string.split(',') + self.battery_percent = get_float_from_string_with_units(msg_parts[1]) + self.battery_voltage_mV = get_float_from_string_with_units(msg_parts[2]) + self.battery_current_mA = get_float_from_string_with_units(msg_parts[3]) + self.battery_remaining_capacity_mAh = get_float_from_string_with_units(msg_parts[5]) + except Exception as e: + rospy.logerr(f"Error parsing NMEA. Message was {nmea_string.strip()}, error is: {e}") def publish_data_message(self): sensor_data_msg = ModemSensorData(**self.value_dict) sensor_data_msg.header = Header(stamp=rospy.get_rostime()) self.modem_sensor_data_publisher.publish(sensor_data_msg) + def publish_battery_state_message(self): + percent_fraction = self.battery_percent / 100 + battery_voltage_V = self.battery_voltage_mV / 1000 + battery_current_A = self.battery_current_mA / 1000 + battery_remaining_capacity_Ah = self.battery_remaining_capacity_mAh / 1000 + battery_capacity_Ah = battery_remaining_capacity_Ah / (percent_fraction + sys.float_info.epsilon) + status = BatteryState.POWER_SUPPLY_STATUS_CHARGING if battery_current_A > 0 else BatteryState.POWER_SUPPLY_STATUS_DISCHARGING + + battery_state_msg = BatteryState(header=Header(stamp=rospy.get_rostime()), + voltage=battery_voltage_V, + current=battery_current_A, + charge=battery_remaining_capacity_Ah, + capacity=battery_capacity_Ah, + design_capacity=battery_capacity_Ah, + percentage=percent_fraction, + power_supply_status=status, + power_supply_health=BatteryState.POWER_SUPPLY_HEALTH_GOOD, + power_supply_technology=BatteryState.POWER_SUPPLY_TECHNOLOGY_LION, + present=True) + + self.battery_state_publisher.publish(battery_state_msg) if __name__ == '__main__': try: -- GitLab From bd37519f9fe2f513158bd9386931ef1aa89cea6e Mon Sep 17 00:00:00 2001 From: Caileigh Fitzgerald <cfitzgerald@whoi.edu> Date: Mon, 4 Mar 2024 21:35:51 +0000 Subject: [PATCH 2/5] added sensor_msgs run time dep to the package.xml file. Tested new battery_state query with bench modem box. Added commented out example launch block to tdma_hw_bench_test.launch --- ros_acomms/package.xml | 1 + ros_acomms_tests/launch/tdma_hw_bench_test.launch | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/ros_acomms/package.xml b/ros_acomms/package.xml index bba0d02f..e793dce2 100644 --- a/ros_acomms/package.xml +++ b/ros_acomms/package.xml @@ -49,6 +49,7 @@ <exec_depend>dynamic_reconfigure</exec_depend> <exec_depend>std_msgs</exec_depend> <exec_depend>ros_acomms_msgs</exec_depend> + <exec_depend>sensor_msgs</exec_depend> <!-- The export tag contains other, unspecified, tags --> <export> diff --git a/ros_acomms_tests/launch/tdma_hw_bench_test.launch b/ros_acomms_tests/launch/tdma_hw_bench_test.launch index a6bef998..7f6a75f8 100644 --- a/ros_acomms_tests/launch/tdma_hw_bench_test.launch +++ b/ros_acomms_tests/launch/tdma_hw_bench_test.launch @@ -16,6 +16,13 @@ </rosparam> </node> + <!-- <node name="sensor_data" pkg="ros_acomms" type="modem_sensor_data_node.py" respawn="true" respawn_delay="10"> + <rosparam> + query_interval_seconds: 15 + enable_battery_query: True + </rosparam> + </node> --> + <node name="tdma_scripted" pkg="ros_acomms" type="tdma_scripted_node.py" output="screen" required="true" respawn="false"> <rosparam param="tdma_scripted_test_plan" command="load" file="$(dirname)/tdma_scripted_test_plan.yaml" /> <rosparam> @@ -29,7 +36,6 @@ pings_per_slot: 1 ping_cdr: 4 ping_transponders: False - override_test_plan_packet_duration: False </rosparam> </node> @@ -70,7 +76,6 @@ ping_modem_src: 0 pings_per_slot: 1 ping_cdr: 8 - ping_transponders: False </rosparam> </node> -- GitLab From ab24534e4da7b6a5d9bc006496d0a9500fb16609 Mon Sep 17 00:00:00 2001 From: Eric Gallimore <egallimore@whoi.edu> Date: Tue, 5 Mar 2024 13:47:01 -0500 Subject: [PATCH 3/5] Added parameter to set battery state topic name. --- ros_acomms/src/modem_sensor_data_node.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ros_acomms/src/modem_sensor_data_node.py b/ros_acomms/src/modem_sensor_data_node.py index 88a34a99..a19a4e10 100755 --- a/ros_acomms/src/modem_sensor_data_node.py +++ b/ros_acomms/src/modem_sensor_data_node.py @@ -22,6 +22,7 @@ class ModemSensorDataNode(object): self.query_interval_seconds = rospy.get_param('~query_interval_seconds', 30) self.enable_battery_query = rospy.get_param('~enable_battery_query', default=False) + self.battery_state_topic = rospy.get_param('~battery_state_topic', 'battery_state') minimum_interval = 5 if not self.query_battery else 30 if self.query_interval_seconds < minimum_interval: rospy.logwarn(f'Query interval must be at least {minimum_interval} seconds. Setting to {minimum_interval}') @@ -29,7 +30,7 @@ class ModemSensorDataNode(object): self.modem_sensor_data_publisher = rospy.Publisher('modem_sensor_data', ModemSensorData, queue_size=10) if self.query_battery: - self.battery_state_publisher = rospy.Publisher('battery_state', BatteryState, queue_size=10) + self.battery_state_publisher = rospy.Publisher(self.battery_state_topic, BatteryState, queue_size=10) self.nmea_publisher = rospy.Publisher('nmea_to_modem', String, queue_size=10) self.value_dict = {} -- GitLab From 8d40ec9954f5ae10876ce298ebeb994063764ab6 Mon Sep 17 00:00:00 2001 From: Eric Gallimore <egallimore@whoi.edu> Date: Tue, 5 Mar 2024 14:07:49 -0500 Subject: [PATCH 4/5] Correctly indicate that the battery isn't present when it isn't (when the voltage is at or close to 0). --- ros_acomms/src/modem_sensor_data_node.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ros_acomms/src/modem_sensor_data_node.py b/ros_acomms/src/modem_sensor_data_node.py index a19a4e10..37591703 100755 --- a/ros_acomms/src/modem_sensor_data_node.py +++ b/ros_acomms/src/modem_sensor_data_node.py @@ -98,6 +98,10 @@ class ModemSensorDataNode(object): battery_remaining_capacity_Ah = self.battery_remaining_capacity_mAh / 1000 battery_capacity_Ah = battery_remaining_capacity_Ah / (percent_fraction + sys.float_info.epsilon) status = BatteryState.POWER_SUPPLY_STATUS_CHARGING if battery_current_A > 0 else BatteryState.POWER_SUPPLY_STATUS_DISCHARGING + if battery_voltage_V <= 0.0001: + battery_present = False + else: + battery_present = True battery_state_msg = BatteryState(header=Header(stamp=rospy.get_rostime()), voltage=battery_voltage_V, @@ -109,7 +113,7 @@ class ModemSensorDataNode(object): power_supply_status=status, power_supply_health=BatteryState.POWER_SUPPLY_HEALTH_GOOD, power_supply_technology=BatteryState.POWER_SUPPLY_TECHNOLOGY_LION, - present=True) + present=battery_present) self.battery_state_publisher.publish(battery_state_msg) -- GitLab From d23baa69032fb26a6e01140a09c5baa150eeae05 Mon Sep 17 00:00:00 2001 From: Eric Gallimore <egallimore@whoi.edu> Date: Tue, 12 Mar 2024 18:41:57 -0400 Subject: [PATCH 5/5] Add a field that includes the full queue parameter dictionary from the yaml file, so that other programs (like ros_acomms_net) can add arbitrary parameters. --- ros_acomms/src/codec_config_parser/config_parser.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ros_acomms/src/codec_config_parser/config_parser.py b/ros_acomms/src/codec_config_parser/config_parser.py index c7b2882e..260ddf9b 100644 --- a/ros_acomms/src/codec_config_parser/config_parser.py +++ b/ros_acomms/src/codec_config_parser/config_parser.py @@ -24,6 +24,7 @@ class QueueParams: is_active: bool destination: Optional[int] allow_fragmentation: bool + params_dict: Dict def load_custom_codecs() -> None: @@ -99,6 +100,9 @@ def get_queue_params(packet_codec_param: Dict) -> Dict[int, QueueParams]: # if not specified, MessageQueueNode sets a default destination message_codec_param.get("default_dest", None), message_codec_param.get("allow_fragmentation", False), + # Capture all values that are set, so that other users of the codec + # config parser can add arbitrary entries to the param dictionary + params_dict=message_codec_param, ) else: # Use new version that separates the queue and codec definitions. -- GitLab