Skip to content

Commit

Permalink
feat: add support for depth data in both meters and millibars
Browse files Browse the repository at this point in the history
  • Loading branch information
noppanut15 committed Feb 10, 2025
1 parent 78ed5b3 commit 731a826
Show file tree
Hide file tree
Showing 3 changed files with 237 additions and 12 deletions.
40 changes: 28 additions & 12 deletions src/depthviz/parsers/shearwater/shearwater_xml_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class ShearwaterXmlParser(DiveLogXmlParser):
depth_mode: The depth mode setting for the depth data.
__start_surface_pressure: The start surface pressure value.
__water_density: The water density value for the hydrostatic pressure calculation.
__depth_unit: The unit of the depth data. (mbar, m)
"""

def __init__(self, salinity: str = "en13319", depth_mode: str = "raw") -> None:
Expand All @@ -101,6 +102,7 @@ def __init__(self, salinity: str = "en13319", depth_mode: str = "raw") -> None:
raise ValueError(
"Invalid salinity setting: Must be 'fresh', 'en13319', or 'salt'"
)
self.__depth_unit: str = ""

def __get_dive_log(self, file_path: str) -> ET.Element:
"""Returns the dive log element from the XML root.
Expand Down Expand Up @@ -226,22 +228,36 @@ def __get_current_depth(self, dive_log_record: ET.Element) -> float:
InvalidDepthValueError: If depth values are invalid.
"""
try:
current_depth = dive_log_record.find("currentDepth")
if current_depth is None:
current_depth_txt = dive_log_record.find("currentDepth")
if current_depth_txt is None:
raise DiveLogXmlInvalidElementError("Invalid XML: Depth not found")
mbar_absolute_pressure = float(str(current_depth.text))
current_depth = float(str(current_depth_txt.text))
except ValueError as e:
raise InvalidDepthValueError("Invalid XML: Invalid depth values") from e

# Calculate the hydrostatic pressure by subtracting the current pressure
# from the start surface pressure, return 0 if negative
mbar_hydrostatic_pressure = max(
mbar_absolute_pressure - self.__start_surface_pressure, 0
)
# Find the depth in meters based on the hydrostatic pressure formula
depth_meter = self.__find_depth_meter(
mbar_hydrostatic_pressure, self.__water_density
)
# Get the unit of the depth data
# If the depth unit is not set, determine the unit based on the current depth value
if self.__depth_unit == "":
# If the current depth is greater than 500, assume the unit is in millibars
# Note: The lowest atmospheric pressure is around 870 mbar
if current_depth > 500:
self.__depth_unit = "mbar"
else:
self.__depth_unit = "m"

# Convert the depth value to meters if the unit is in millibars
if self.__depth_unit == "mbar":
# Calculate the hydrostatic pressure by subtracting the current pressure
# (absolute pressure) from the start surface pressure, return 0 if negative
mbar_hydrostatic_pressure = max(
current_depth - self.__start_surface_pressure, 0
)
# Find the depth in meters based on the hydrostatic pressure formula
depth_meter = self.__find_depth_meter(
mbar_hydrostatic_pressure, self.__water_density
)
else:
depth_meter = current_depth
return depth_meter

def parse(self, file_path: str) -> None:
Expand Down
184 changes: 184 additions & 0 deletions tests/data/shearwater/valid_depth_data_oc_tec_trimmed.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
<?xml version="1.0" encoding="utf-16"?>
<dive xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3">
<diveLog>
<number>999</number>
<gfMin>50</gfMin>
<gfMax>70</gfMax>
<surfaceMins>1392</surfaceMins>
<imperialUnits>false</imperialUnits>
<startBatteryVoltage>4.20000029</startBatteryVoltage>
<startCns>0</startCns>
<startDate>1/1/2024 1:07:14 PM</startDate>
<startO2SensorStatus>false</startO2SensorStatus>
<startLowSetPoint>0</startLowSetPoint>
<startHighSetPoint>1</startHighSetPoint>
<computerFirmware>23</computerFirmware>
<maxDepth>41.2</maxDepth>
<maxTime>3124</maxTime>
<errorHistory />
<endBatteryVoltage>4.20000029</endBatteryVoltage>
<endCns>14</endCns>
<endDate>1/1/2024 1:59:18 PM</endDate>
<endO2SensorStatus>false</endO2SensorStatus>
<endLowSetPoint>0</endLowSetPoint>
<endHighSetPoint>0</endHighSetPoint>
<computerSerial>XXXXXXXX</computerSerial>
<computerSoftwareVersion>23</computerSoftwareVersion>
<computerModel>0</computerModel>
<logVersion>13</logVersion>
<decoModel>0</decoModel>
<vpmbConservatism>3</vpmbConservatism>
<startSurfacePressure>996</startSurfacePressure>
<endSurfacePressure>996</endSurfacePressure>
<sensorDisplay>0</sensorDisplay>
<product>8</product>
<features>0</features>
<diveLogRecords>
<diveLogRecord>
<currentTime>0</currentTime>
<currentDepth>0.3</currentDepth>
<firstStopDepth>0</firstStopDepth>
<ttsMins>0</ttsMins>
<averagePPO2>0.21</averagePPO2>
<fractionO2>0.21</fractionO2>
<fractionHe>0</fractionHe>
<firstStopTime>0</firstStopTime>
<currentNdl>0</currentNdl>
<currentCircuitSetting>OC/BO</currentCircuitSetting>
<currentCcrModeSettings>0</currentCcrModeSettings>
<waterTemp>29</waterTemp>
<gasSwitchNeeded>false</gasSwitchNeeded>
<externalPPO2>0</externalPPO2>
<setPointType>0</setPointType>
<circuitSwitchType>0</circuitSwitchType>
<sensor1Millivolts>0</sensor1Millivolts>
<sensor2Millivolts>0</sensor2Millivolts>
<sensor3Millivolts>0</sensor3Millivolts>
<batteryVoltage>4.19</batteryVoltage>
<tank0pressurePSI>AI is off</tank0pressurePSI>
<tank1pressurePSI>AI is off</tank1pressurePSI>
<tank2pressurePSI>AI is off</tank2pressurePSI>
<tank3pressurePSI>AI is off</tank3pressurePSI>
<gasTime>Not paired</gasTime>
<sac>AI is off</sac>
<sad>0</sad>
</diveLogRecord>
<diveLogRecord>
<currentTime>10000</currentTime>
<currentDepth>2.9</currentDepth>
<firstStopDepth>0</firstStopDepth>
<ttsMins>1</ttsMins>
<averagePPO2>0.26</averagePPO2>
<fractionO2>0.21</fractionO2>
<fractionHe>0</fractionHe>
<firstStopTime>0</firstStopTime>
<currentNdl>99</currentNdl>
<currentCircuitSetting>OC/BO</currentCircuitSetting>
<currentCcrModeSettings>0</currentCcrModeSettings>
<waterTemp>29</waterTemp>
<gasSwitchNeeded>false</gasSwitchNeeded>
<externalPPO2>0</externalPPO2>
<setPointType>0</setPointType>
<circuitSwitchType>0</circuitSwitchType>
<sensor1Millivolts>0</sensor1Millivolts>
<sensor2Millivolts>0</sensor2Millivolts>
<sensor3Millivolts>0</sensor3Millivolts>
<batteryVoltage>4.19</batteryVoltage>
<tank0pressurePSI>AI is off</tank0pressurePSI>
<tank1pressurePSI>AI is off</tank1pressurePSI>
<tank2pressurePSI>AI is off</tank2pressurePSI>
<tank3pressurePSI>AI is off</tank3pressurePSI>
<gasTime>Not paired</gasTime>
<sac>AI is off</sac>
<sad>0</sad>
</diveLogRecord>
<diveLogRecord>
<currentTime>20000</currentTime>
<currentDepth>4.1</currentDepth>
<firstStopDepth>0</firstStopDepth>
<ttsMins>1</ttsMins>
<averagePPO2>0.29</averagePPO2>
<fractionO2>0.21</fractionO2>
<fractionHe>0</fractionHe>
<firstStopTime>0</firstStopTime>
<currentNdl>99</currentNdl>
<currentCircuitSetting>OC/BO</currentCircuitSetting>
<currentCcrModeSettings>0</currentCcrModeSettings>
<waterTemp>29</waterTemp>
<gasSwitchNeeded>false</gasSwitchNeeded>
<externalPPO2>0</externalPPO2>
<setPointType>0</setPointType>
<circuitSwitchType>0</circuitSwitchType>
<sensor1Millivolts>0</sensor1Millivolts>
<sensor2Millivolts>0</sensor2Millivolts>
<sensor3Millivolts>0</sensor3Millivolts>
<batteryVoltage>4.19</batteryVoltage>
<tank0pressurePSI>AI is off</tank0pressurePSI>
<tank1pressurePSI>AI is off</tank1pressurePSI>
<tank2pressurePSI>AI is off</tank2pressurePSI>
<tank3pressurePSI>AI is off</tank3pressurePSI>
<gasTime>Not paired</gasTime>
<sac>AI is off</sac>
<sad>0</sad>
</diveLogRecord>
<diveLogRecord>
<currentTime>30000</currentTime>
<currentDepth>5.20000029</currentDepth>
<firstStopDepth>0</firstStopDepth>
<ttsMins>1</ttsMins>
<averagePPO2>0.31</averagePPO2>
<fractionO2>0.21</fractionO2>
<fractionHe>0</fractionHe>
<firstStopTime>0</firstStopTime>
<currentNdl>99</currentNdl>
<currentCircuitSetting>OC/BO</currentCircuitSetting>
<currentCcrModeSettings>0</currentCcrModeSettings>
<waterTemp>29</waterTemp>
<gasSwitchNeeded>false</gasSwitchNeeded>
<externalPPO2>0</externalPPO2>
<setPointType>0</setPointType>
<circuitSwitchType>0</circuitSwitchType>
<sensor1Millivolts>0</sensor1Millivolts>
<sensor2Millivolts>0</sensor2Millivolts>
<sensor3Millivolts>0</sensor3Millivolts>
<batteryVoltage>4.19</batteryVoltage>
<tank0pressurePSI>AI is off</tank0pressurePSI>
<tank1pressurePSI>AI is off</tank1pressurePSI>
<tank2pressurePSI>AI is off</tank2pressurePSI>
<tank3pressurePSI>AI is off</tank3pressurePSI>
<gasTime>Not paired</gasTime>
<sac>AI is off</sac>
<sad>0</sad>
</diveLogRecord>
<diveLogRecord>
<currentTime>40000</currentTime>
<currentDepth>5.6</currentDepth>
<firstStopDepth>0</firstStopDepth>
<ttsMins>1</ttsMins>
<averagePPO2>0.32</averagePPO2>
<fractionO2>0.21</fractionO2>
<fractionHe>0</fractionHe>
<firstStopTime>0</firstStopTime>
<currentNdl>99</currentNdl>
<currentCircuitSetting>OC/BO</currentCircuitSetting>
<currentCcrModeSettings>0</currentCcrModeSettings>
<waterTemp>29</waterTemp>
<gasSwitchNeeded>false</gasSwitchNeeded>
<externalPPO2>0</externalPPO2>
<setPointType>0</setPointType>
<circuitSwitchType>0</circuitSwitchType>
<sensor1Millivolts>0</sensor1Millivolts>
<sensor2Millivolts>0</sensor2Millivolts>
<sensor3Millivolts>0</sensor3Millivolts>
<batteryVoltage>4.19</batteryVoltage>
<tank0pressurePSI>AI is off</tank0pressurePSI>
<tank1pressurePSI>AI is off</tank1pressurePSI>
<tank2pressurePSI>AI is off</tank2pressurePSI>
<tank3pressurePSI>AI is off</tank3pressurePSI>
<gasTime>Not paired</gasTime>
<sac>AI is off</sac>
<sad>0</sad>
</diveLogRecord>
</diveLogRecords>
</diveLog>
</dive>
25 changes: 25 additions & 0 deletions tests/test_shearwater_xml_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,3 +344,28 @@ def test_parse_valid_xml_depth_mode_exec(
parser = ShearwaterXmlParser(depth_mode=depth_mode)
parser.parse(file_path)
mock_depth_mode_execute.assert_called_once()

def test_parse_valid_xml_scuba_longdive(
self, request: pytest.FixtureRequest
) -> None:
"""Test parsing a valid XML file with scuba long dive (OC Tec).
Note:
Scuba dive logs record the depth data in meters so we need to add
support both meters and millibars.
"""
file_path = str(
request.path.parent.joinpath(
"data", "shearwater", "valid_depth_data_oc_tec_trimmed.xml"
)
)
xml_parser = ShearwaterXmlParser()
xml_parser.parse(file_path)
assert xml_parser.get_depth_data() == [0.3, 2.9, 4.1, 5.2, 5.6]
assert xml_parser.get_time_data() == [
0.0,
10.0,
20.0,
30.0,
40.0,
]

0 comments on commit 731a826

Please sign in to comment.