From ded373e57483b91c48f783b4452aa039bccabaaf Mon Sep 17 00:00:00 2001 From: alpheios-one Date: Thu, 10 Sep 2026 17:20:14 +0200 Subject: [PATCH 1/5] feat(dive-computer): rank sensorless CCR bailout gases against each other Without a transmitter, dive_tanks.role was inferred per gas in isolation from a single O2/He threshold, ignoring the dive-mode context. That misclassified programmed CCR diluent and bailout gases the computer never attaches to a tank record. libdivecomputer's stock dc_gasmix_t already carries a per-gas dc_usage_t (oxygen/diluent/sidemount), independent of any tank record, but the plugin never bridged it past the C layer. Thread GasMix.usage through pigeon and all four platform converters, then use it in the sensorless resolver: - usage reported by the device (oxygen/diluent/sidemount) is authoritative, regardless of dive mode. - On a CCR dive, the remaining gases (bailout candidates) are ranked against each other instead of scored alone: lowest O2 is bailout (ties broken by higher helium, a further tie gives bailout to all tied gases), O2 >= 41% is deco, everything else is stage. - Every other dive mode keeps the original single-threshold heuristic unchanged. Ref: submersion-app/submersion#1747 --- .../data/services/parsed_tank_resolver.dart | 78 ++- .../android/src/main/cpp/libdc_jni.cpp | 7 +- .../libdivecomputer/DiveComputerApi.g.kt | 13 +- .../DiveComputerHostApiImpl.kt | 3 +- .../libdivecomputer/SerialDownloadRunner.kt | 3 +- .../LibDCDarwin/DiveComputerHostApiImpl.swift | 3 +- .../ios/Classes/DiveComputerApi.g.swift | 10 +- .../src/generated/dive_computer_api.g.dart | 497 ++++++++---------- .../linux/dive_computer_api.g.cc | 27 +- .../linux/dive_computer_api.g.h | 16 +- .../linux/dive_converter.c | 6 +- .../macos/Classes/libdc_download.c | 1 + .../macos/Classes/libdc_wrapper.h | 1 + .../pigeons/dive_computer_api.dart | 7 + .../windows/dive_computer_api.g.cc | 30 +- .../windows/dive_computer_api.g.h | 18 +- .../windows/dive_converter.cc | 9 +- .../services/parsed_tank_resolver_test.dart | 93 ++++ 18 files changed, 519 insertions(+), 303 deletions(-) diff --git a/lib/features/dive_computer/data/services/parsed_tank_resolver.dart b/lib/features/dive_computer/data/services/parsed_tank_resolver.dart index aea033bee4..9f098a4075 100644 --- a/lib/features/dive_computer/data/services/parsed_tank_resolver.dart +++ b/lib/features/dive_computer/data/services/parsed_tank_resolver.dart @@ -111,6 +111,7 @@ _ResolvedCylinders _resolveCylinders( return const _ResolvedCylinders([], {}); } final tanks = []; + final roles = _inferSensorlessRoles(gasMixes, parsed.diveMode); for (var i = 0; i < gasMixes.length; i++) { final g = gasMixes[i]; gasIndexToTankIndex[i] = g.index; @@ -119,7 +120,7 @@ _ResolvedCylinders _resolveCylinders( index: g.index, o2Percent: g.o2Percent, hePercent: g.hePercent, - role: _inferRole(null, g.o2Percent, g.hePercent), + role: roles[i], ), ); } @@ -205,6 +206,81 @@ String _inferRole(int? usage, double o2Percent, double hePercent) { return TankRole.backGas.name; } +/// Role for each of [gasMixes], in order, on a sensorless (tankless) dive. +/// +/// A gas whose usage the computer reported directly on the gas mix itself +/// (`dc_gasmix_t.usage`, independent of any tank/transmitter record) keeps +/// that role regardless of dive mode: oxygen, diluent or sidemount is +/// authoritative device data. +/// +/// For a dive recognized as CCR, the gases left with no reported usage are +/// the open-circuit bailout candidates and are ranked against each other +/// instead of scored in isolation: +/// 1. Bottom gas: the lowest O2 percentage among them becomes +/// [TankRole.bailout]; a tie is broken by the higher helium percentage, +/// and a further tie gives Bailout to every still-tied gas. A gas that +/// only loses the helium tie-break gets no automatic Bailout role and +/// falls through to the next rule. +/// 2. Deco: every still-unassigned gas at or above the same 41% O2 +/// threshold [_inferRole] uses for open circuit becomes [TankRole.deco]. +/// 3. Stage: everything still unassigned becomes [TankRole.stage]. +/// +/// On any other recognized dive mode, a gas with no reported usage keeps +/// [_inferRole]'s original single-threshold heuristic, unaffected by this. +List _inferSensorlessRoles( + List gasMixes, + String? diveMode, +) { + final roles = List.filled(gasMixes.length, null); + final unranked = []; + for (var i = 0; i < gasMixes.length; i++) { + final g = gasMixes[i]; + switch (g.usage) { + case 1: // DC_USAGE_OXYGEN + roles[i] = TankRole.oxygenSupply.name; + case 2: // DC_USAGE_DILUENT + roles[i] = TankRole.diluent.name; + case 3: // DC_USAGE_SIDEMOUNT + roles[i] = TankRole.backGas.name; + default: + unranked.add(i); + } + } + + if (diveMode != 'ccr') { + for (final i in unranked) { + final g = gasMixes[i]; + roles[i] = _inferRole(null, g.o2Percent, g.hePercent); + } + return [for (final role in roles) role!]; + } + + if (unranked.isNotEmpty) { + final lowestO2 = unranked + .map((i) => gasMixes[i].o2Percent) + .reduce((a, b) => a < b ? a : b); + final atLowestO2 = unranked.where( + (i) => gasMixes[i].o2Percent == lowestO2, + ); + final highestHeAtLowestO2 = atLowestO2 + .map((i) => gasMixes[i].hePercent) + .reduce((a, b) => a > b ? a : b); + for (final i in atLowestO2) { + if (gasMixes[i].hePercent == highestHeAtLowestO2) { + roles[i] = TankRole.bailout.name; + } + } + for (final i in unranked) { + if (roles[i] != null) continue; + roles[i] = gasMixes[i].o2Percent >= 41.0 + ? TankRole.deco.name + : TankRole.stage.name; + } + } + + return [for (final role in roles) role!]; +} + /// The gas-mix index (position in [gasMixes]) for [tank], preferring the gas /// actually breathed on it. Returns null only when there are no gas mixes. int? _resolveTankGasIndex( diff --git a/packages/libdivecomputer_plugin/android/src/main/cpp/libdc_jni.cpp b/packages/libdivecomputer_plugin/android/src/main/cpp/libdc_jni.cpp index f01b8c21f7..70871ea3fe 100644 --- a/packages/libdivecomputer_plugin/android/src/main/cpp/libdc_jni.cpp +++ b/packages/libdivecomputer_plugin/android/src/main/cpp/libdc_jni.cpp @@ -1086,9 +1086,10 @@ Java_com_submersion_libdivecomputer_LibdcWrapper_nativeGetDiveGasmix( if (index < 0 || static_cast(index) >= dive->gasmix_count) return nullptr; const libdc_gasmix_t *gm = &dive->gasmixes[index]; - jdouble values[2] = { gm->oxygen, gm->helium }; - jdoubleArray result = env->NewDoubleArray(2); - env->SetDoubleArrayRegion(result, 0, 2, values); + // Return [oxygen, helium, usage]. Positional: the Kotlin readers index it. + jdouble values[3] = { gm->oxygen, gm->helium, static_cast(gm->usage) }; + jdoubleArray result = env->NewDoubleArray(3); + env->SetDoubleArrayRegion(result, 0, 3, values); return result; } diff --git a/packages/libdivecomputer_plugin/android/src/main/kotlin/com/submersion/libdivecomputer/DiveComputerApi.g.kt b/packages/libdivecomputer_plugin/android/src/main/kotlin/com/submersion/libdivecomputer/DiveComputerApi.g.kt index 11a226b22a..c9b2715e54 100644 --- a/packages/libdivecomputer_plugin/android/src/main/kotlin/com/submersion/libdivecomputer/DiveComputerApi.g.kt +++ b/packages/libdivecomputer_plugin/android/src/main/kotlin/com/submersion/libdivecomputer/DiveComputerApi.g.kt @@ -255,7 +255,14 @@ data class ProfileSample ( data class GasMix ( val index: Long, val o2Percent: Double, - val hePercent: Double + val hePercent: Double, + /** + * Gas usage from libdivecomputer's `dc_usage_t` (1=oxygen, 2=diluent, + * 3=sidemount); null when the computer reported no usage (DC_USAGE_NONE). + * Set on the gas mix itself, so it is available even when the mix has no + * tank/transmitter record. + */ + val usage: Long? = null ) { companion object { @@ -263,7 +270,8 @@ data class GasMix ( val index = pigeonVar_list[0] as Long val o2Percent = pigeonVar_list[1] as Double val hePercent = pigeonVar_list[2] as Double - return GasMix(index, o2Percent, hePercent) + val usage = pigeonVar_list[3] as Long? + return GasMix(index, o2Percent, hePercent, usage) } } fun toList(): List { @@ -271,6 +279,7 @@ data class GasMix ( index, o2Percent, hePercent, + usage, ) } } diff --git a/packages/libdivecomputer_plugin/android/src/main/kotlin/com/submersion/libdivecomputer/DiveComputerHostApiImpl.kt b/packages/libdivecomputer_plugin/android/src/main/kotlin/com/submersion/libdivecomputer/DiveComputerHostApiImpl.kt index 5614f440d3..0ef0cbef56 100644 --- a/packages/libdivecomputer_plugin/android/src/main/kotlin/com/submersion/libdivecomputer/DiveComputerHostApiImpl.kt +++ b/packages/libdivecomputer_plugin/android/src/main/kotlin/com/submersion/libdivecomputer/DiveComputerHostApiImpl.kt @@ -688,7 +688,8 @@ class DiveComputerHostApiImpl( GasMix( index = i.toLong(), o2Percent = gm[0] * 100.0, - hePercent = gm[1] * 100.0 + hePercent = gm[1] * 100.0, + usage = gm.getOrNull(2)?.toLong()?.takeIf { it != 0L } ) } diff --git a/packages/libdivecomputer_plugin/android/src/main/kotlin/com/submersion/libdivecomputer/SerialDownloadRunner.kt b/packages/libdivecomputer_plugin/android/src/main/kotlin/com/submersion/libdivecomputer/SerialDownloadRunner.kt index f5fc0a41e7..c0753b7324 100644 --- a/packages/libdivecomputer_plugin/android/src/main/kotlin/com/submersion/libdivecomputer/SerialDownloadRunner.kt +++ b/packages/libdivecomputer_plugin/android/src/main/kotlin/com/submersion/libdivecomputer/SerialDownloadRunner.kt @@ -184,7 +184,8 @@ class SerialDownloadRunner(private val context: Context) { GasMix( index = i.toLong(), o2Percent = gm[0] * 100.0, - hePercent = gm[1] * 100.0 + hePercent = gm[1] * 100.0, + usage = gm.getOrNull(2)?.toLong()?.takeIf { it != 0L } ) } diff --git a/packages/libdivecomputer_plugin/darwin/Sources/LibDCDarwin/DiveComputerHostApiImpl.swift b/packages/libdivecomputer_plugin/darwin/Sources/LibDCDarwin/DiveComputerHostApiImpl.swift index e0a4a89097..981a6deef8 100644 --- a/packages/libdivecomputer_plugin/darwin/Sources/LibDCDarwin/DiveComputerHostApiImpl.swift +++ b/packages/libdivecomputer_plugin/darwin/Sources/LibDCDarwin/DiveComputerHostApiImpl.swift @@ -867,7 +867,8 @@ class DiveComputerHostApiImpl: DiveComputerHostApi { gasMixes.append(GasMix( index: Int64(i), o2Percent: gm.oxygen * 100.0, - hePercent: gm.helium * 100.0 + hePercent: gm.helium * 100.0, + usage: gm.usage == 0 ? nil : Int64(gm.usage) )) } } diff --git a/packages/libdivecomputer_plugin/ios/Classes/DiveComputerApi.g.swift b/packages/libdivecomputer_plugin/ios/Classes/DiveComputerApi.g.swift index a7e271b02c..89935f90dc 100644 --- a/packages/libdivecomputer_plugin/ios/Classes/DiveComputerApi.g.swift +++ b/packages/libdivecomputer_plugin/ios/Classes/DiveComputerApi.g.swift @@ -301,6 +301,11 @@ struct GasMix { var index: Int64 var o2Percent: Double var hePercent: Double + /// Gas usage from libdivecomputer's `dc_usage_t` (1=oxygen, 2=diluent, + /// 3=sidemount); null when the computer reported no usage (DC_USAGE_NONE). + /// Set on the gas mix itself, so it is available even when the mix has no + /// tank/transmitter record. + var usage: Int64? = nil // swift-format-ignore: AlwaysUseLowerCamelCase @@ -308,11 +313,13 @@ struct GasMix { let index = pigeonVar_list[0] as! Int64 let o2Percent = pigeonVar_list[1] as! Double let hePercent = pigeonVar_list[2] as! Double + let usage: Int64? = nilOrValue(pigeonVar_list[3]) return GasMix( index: index, o2Percent: o2Percent, - hePercent: hePercent + hePercent: hePercent, + usage: usage ) } func toList() -> [Any?] { @@ -320,6 +327,7 @@ struct GasMix { index, o2Percent, hePercent, + usage, ] } } diff --git a/packages/libdivecomputer_plugin/lib/src/generated/dive_computer_api.g.dart b/packages/libdivecomputer_plugin/lib/src/generated/dive_computer_api.g.dart index df42286cee..9601e840a2 100644 --- a/packages/libdivecomputer_plugin/lib/src/generated/dive_computer_api.g.dart +++ b/packages/libdivecomputer_plugin/lib/src/generated/dive_computer_api.g.dart @@ -15,11 +15,7 @@ PlatformException _createConnectionError(String channelName) { ); } -List wrapResponse({ - Object? result, - PlatformException? error, - bool empty = false, -}) { +List wrapResponse({Object? result, PlatformException? error, bool empty = false}) { if (empty) { return []; } @@ -29,7 +25,12 @@ List wrapResponse({ return [error.code, error.message, error.details]; } -enum TransportType { ble, usb, serial, infrared } +enum TransportType { + ble, + usb, + serial, + infrared, +} class DeviceDescriptor { DeviceDescriptor({ @@ -48,7 +49,12 @@ class DeviceDescriptor { List transports; Object encode() { - return [vendor, product, model, transports]; + return [ + vendor, + product, + model, + transports, + ]; } static DeviceDescriptor decode(Object result) { @@ -85,7 +91,14 @@ class DiscoveredDevice { TransportType transport; Object encode() { - return [vendor, product, model, address, name, transport]; + return [ + vendor, + product, + model, + address, + name, + transport, + ]; } static DiscoveredDevice decode(Object result) { @@ -284,6 +297,7 @@ class GasMix { required this.index, required this.o2Percent, required this.hePercent, + this.usage, }); int index; @@ -292,8 +306,19 @@ class GasMix { double hePercent; + /// Gas usage from libdivecomputer's `dc_usage_t` (1=oxygen, 2=diluent, + /// 3=sidemount); null when the computer reported no usage (DC_USAGE_NONE). + /// Set on the gas mix itself, so it is available even when the mix has no + /// tank/transmitter record. + int? usage; + Object encode() { - return [index, o2Percent, hePercent]; + return [ + index, + o2Percent, + hePercent, + usage, + ]; } static GasMix decode(Object result) { @@ -302,6 +327,7 @@ class GasMix { index: result[0]! as int, o2Percent: result[1]! as double, hePercent: result[2]! as double, + usage: result[3] as int?, ); } } @@ -364,7 +390,11 @@ class TankInfo { } class DiveEvent { - DiveEvent({required this.timeSeconds, required this.type, this.data}); + DiveEvent({ + required this.timeSeconds, + required this.type, + this.data, + }); int timeSeconds; @@ -373,7 +403,11 @@ class DiveEvent { Map? data; Object encode() { - return [timeSeconds, type, data]; + return [ + timeSeconds, + type, + data, + ]; } static DiveEvent decode(Object result) { @@ -556,7 +590,11 @@ class DownloadProgress { String status; Object encode() { - return [current, total, status]; + return [ + current, + total, + status, + ]; } static DownloadProgress decode(Object result) { @@ -570,14 +608,20 @@ class DownloadProgress { } class DiveComputerError { - DiveComputerError({required this.code, required this.message}); + DiveComputerError({ + required this.code, + required this.message, + }); String code; String message; Object encode() { - return [code, message]; + return [ + code, + message, + ]; } static DiveComputerError decode(Object result) { @@ -589,6 +633,7 @@ class DiveComputerError { } } + class _PigeonCodec extends StandardMessageCodec { const _PigeonCodec(); @override @@ -596,34 +641,34 @@ class _PigeonCodec extends StandardMessageCodec { if (value is int) { buffer.putUint8(4); buffer.putInt64(value); - } else if (value is TransportType) { + } else if (value is TransportType) { buffer.putUint8(129); writeValue(buffer, value.index); - } else if (value is DeviceDescriptor) { + } else if (value is DeviceDescriptor) { buffer.putUint8(130); writeValue(buffer, value.encode()); - } else if (value is DiscoveredDevice) { + } else if (value is DiscoveredDevice) { buffer.putUint8(131); writeValue(buffer, value.encode()); - } else if (value is ProfileSample) { + } else if (value is ProfileSample) { buffer.putUint8(132); writeValue(buffer, value.encode()); - } else if (value is GasMix) { + } else if (value is GasMix) { buffer.putUint8(133); writeValue(buffer, value.encode()); - } else if (value is TankInfo) { + } else if (value is TankInfo) { buffer.putUint8(134); writeValue(buffer, value.encode()); - } else if (value is DiveEvent) { + } else if (value is DiveEvent) { buffer.putUint8(135); writeValue(buffer, value.encode()); - } else if (value is ParsedDive) { + } else if (value is ParsedDive) { buffer.putUint8(136); writeValue(buffer, value.encode()); - } else if (value is DownloadProgress) { + } else if (value is DownloadProgress) { buffer.putUint8(137); writeValue(buffer, value.encode()); - } else if (value is DiveComputerError) { + } else if (value is DiveComputerError) { buffer.putUint8(138); writeValue(buffer, value.encode()); } else { @@ -634,26 +679,26 @@ class _PigeonCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { - case 129: + case 129: final int? value = readValue(buffer) as int?; return value == null ? null : TransportType.values[value]; - case 130: + case 130: return DeviceDescriptor.decode(readValue(buffer)!); - case 131: + case 131: return DiscoveredDevice.decode(readValue(buffer)!); - case 132: + case 132: return ProfileSample.decode(readValue(buffer)!); - case 133: + case 133: return GasMix.decode(readValue(buffer)!); - case 134: + case 134: return TankInfo.decode(readValue(buffer)!); - case 135: + case 135: return DiveEvent.decode(readValue(buffer)!); - case 136: + case 136: return ParsedDive.decode(readValue(buffer)!); - case 137: + case 137: return DownloadProgress.decode(readValue(buffer)!); - case 138: + case 138: return DiveComputerError.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -665,13 +710,9 @@ class DiveComputerHostApi { /// Constructor for [DiveComputerHostApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default /// BinaryMessenger will be used which routes to the host platform. - DiveComputerHostApi({ - BinaryMessenger? binaryMessenger, - String messageChannelSuffix = '', - }) : pigeonVar_binaryMessenger = binaryMessenger, - pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty - ? '.$messageChannelSuffix' - : ''; + DiveComputerHostApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? pigeonVar_binaryMessenger; static const MessageCodec pigeonChannelCodec = _PigeonCodec(); @@ -679,14 +720,12 @@ class DiveComputerHostApi { final String pigeonVar_messageChannelSuffix; Future> getDeviceDescriptors() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerHostApi.getDeviceDescriptors$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final String pigeonVar_channelName = 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerHostApi.getDeviceDescriptors$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final List? pigeonVar_replyList = await pigeonVar_channel.send(null) as List?; if (pigeonVar_replyList == null) { @@ -703,20 +742,17 @@ class DiveComputerHostApi { message: 'Host platform returned null value for non-null return value.', ); } else { - return (pigeonVar_replyList[0] as List?)! - .cast(); + return (pigeonVar_replyList[0] as List?)!.cast(); } } Future startDiscovery(TransportType transport) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerHostApi.startDiscovery$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final String pigeonVar_channelName = 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerHostApi.startDiscovery$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final List? pigeonVar_replyList = await pigeonVar_channel.send([transport]) as List?; if (pigeonVar_replyList == null) { @@ -733,14 +769,12 @@ class DiveComputerHostApi { } Future stopDiscovery() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerHostApi.stopDiscovery$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final String pigeonVar_channelName = 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerHostApi.stopDiscovery$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final List? pigeonVar_replyList = await pigeonVar_channel.send(null) as List?; if (pigeonVar_replyList == null) { @@ -756,22 +790,15 @@ class DiveComputerHostApi { } } - Future startDownload( - DiscoveredDevice device, - String? fingerprint, - bool syncClock, - ) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerHostApi.startDownload$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + Future startDownload(DiscoveredDevice device, String? fingerprint, bool syncClock) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerHostApi.startDownload$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final List? pigeonVar_replyList = - await pigeonVar_channel.send([device, fingerprint, syncClock]) - as List?; + await pigeonVar_channel.send([device, fingerprint, syncClock]) as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -786,14 +813,12 @@ class DiveComputerHostApi { } Future cancelDownload() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerHostApi.cancelDownload$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final String pigeonVar_channelName = 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerHostApi.cancelDownload$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final List? pigeonVar_replyList = await pigeonVar_channel.send(null) as List?; if (pigeonVar_replyList == null) { @@ -810,14 +835,12 @@ class DiveComputerHostApi { } Future submitPinCode(String pinCode) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerHostApi.submitPinCode$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final String pigeonVar_channelName = 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerHostApi.submitPinCode$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final List? pigeonVar_replyList = await pigeonVar_channel.send([pinCode]) as List?; if (pigeonVar_replyList == null) { @@ -834,14 +857,12 @@ class DiveComputerHostApi { } Future getLibdivecomputerVersion() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerHostApi.getLibdivecomputerVersion$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final String pigeonVar_channelName = 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerHostApi.getLibdivecomputerVersion$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final List? pigeonVar_replyList = await pigeonVar_channel.send(null) as List?; if (pigeonVar_replyList == null) { @@ -862,23 +883,15 @@ class DiveComputerHostApi { } } - Future parseRawDiveData( - String vendor, - String product, - int model, - Uint8List data, - ) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerHostApi.parseRawDiveData$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + Future parseRawDiveData(String vendor, String product, int model, Uint8List data) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerHostApi.parseRawDiveData$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final List? pigeonVar_replyList = - await pigeonVar_channel.send([vendor, product, model, data]) - as List?; + await pigeonVar_channel.send([vendor, product, model, data]) as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -909,12 +922,7 @@ abstract class DiveComputerFlutterApi { void onDiveDownloaded(ParsedDive dive); - void onDownloadComplete( - int totalDives, - String? serialNumber, - String? firmwareVersion, - String? clockSyncStatus, - ); + void onDownloadComplete(int totalDives, String? serialNumber, String? firmwareVersion, String? clockSyncStatus); void onError(DiveComputerError error); @@ -922,55 +930,37 @@ abstract class DiveComputerFlutterApi { void onLogEvent(String category, String level, String message); - static void setUp( - DiveComputerFlutterApi? api, { - BinaryMessenger? binaryMessenger, - String messageChannelSuffix = '', - }) { - messageChannelSuffix = messageChannelSuffix.isNotEmpty - ? '.$messageChannelSuffix' - : ''; + static void setUp(DiveComputerFlutterApi? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) { + messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; { - final BasicMessageChannel - pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDeviceDiscovered$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger, - ); + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDeviceDiscovered$messageChannelSuffix', pigeonChannelCodec, + binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDeviceDiscovered was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDeviceDiscovered was null.'); final List args = (message as List?)!; final DiscoveredDevice? arg_device = (args[0] as DiscoveredDevice?); - assert( - arg_device != null, - 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDeviceDiscovered was null, expected non-null DiscoveredDevice.', - ); + assert(arg_device != null, + 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDeviceDiscovered was null, expected non-null DiscoveredDevice.'); try { api.onDeviceDiscovered(arg_device!); return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString()), - ); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); } }); } } { - final BasicMessageChannel - pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDiscoveryComplete$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger, - ); + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDiscoveryComplete$messageChannelSuffix', pigeonChannelCodec, + binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { @@ -980,232 +970,167 @@ abstract class DiveComputerFlutterApi { return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString()), - ); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); } }); } } { - final BasicMessageChannel - pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDownloadProgress$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger, - ); + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDownloadProgress$messageChannelSuffix', pigeonChannelCodec, + binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDownloadProgress was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDownloadProgress was null.'); final List args = (message as List?)!; final DownloadProgress? arg_progress = (args[0] as DownloadProgress?); - assert( - arg_progress != null, - 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDownloadProgress was null, expected non-null DownloadProgress.', - ); + assert(arg_progress != null, + 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDownloadProgress was null, expected non-null DownloadProgress.'); try { api.onDownloadProgress(arg_progress!); return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString()), - ); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); } }); } } { - final BasicMessageChannel - pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDiveDownloaded$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger, - ); + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDiveDownloaded$messageChannelSuffix', pigeonChannelCodec, + binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDiveDownloaded was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDiveDownloaded was null.'); final List args = (message as List?)!; final ParsedDive? arg_dive = (args[0] as ParsedDive?); - assert( - arg_dive != null, - 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDiveDownloaded was null, expected non-null ParsedDive.', - ); + assert(arg_dive != null, + 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDiveDownloaded was null, expected non-null ParsedDive.'); try { api.onDiveDownloaded(arg_dive!); return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString()), - ); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); } }); } } { - final BasicMessageChannel - pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDownloadComplete$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger, - ); + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDownloadComplete$messageChannelSuffix', pigeonChannelCodec, + binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDownloadComplete was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDownloadComplete was null.'); final List args = (message as List?)!; final int? arg_totalDives = (args[0] as int?); - assert( - arg_totalDives != null, - 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDownloadComplete was null, expected non-null int.', - ); + assert(arg_totalDives != null, + 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDownloadComplete was null, expected non-null int.'); final String? arg_serialNumber = (args[1] as String?); final String? arg_firmwareVersion = (args[2] as String?); final String? arg_clockSyncStatus = (args[3] as String?); try { - api.onDownloadComplete( - arg_totalDives!, - arg_serialNumber, - arg_firmwareVersion, - arg_clockSyncStatus, - ); + api.onDownloadComplete(arg_totalDives!, arg_serialNumber, arg_firmwareVersion, arg_clockSyncStatus); return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString()), - ); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); } }); } } { - final BasicMessageChannel - pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onError$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger, - ); + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onError$messageChannelSuffix', pigeonChannelCodec, + binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onError was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onError was null.'); final List args = (message as List?)!; final DiveComputerError? arg_error = (args[0] as DiveComputerError?); - assert( - arg_error != null, - 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onError was null, expected non-null DiveComputerError.', - ); + assert(arg_error != null, + 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onError was null, expected non-null DiveComputerError.'); try { api.onError(arg_error!); return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString()), - ); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); } }); } } { - final BasicMessageChannel - pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onPinCodeRequired$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger, - ); + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onPinCodeRequired$messageChannelSuffix', pigeonChannelCodec, + binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onPinCodeRequired was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onPinCodeRequired was null.'); final List args = (message as List?)!; final String? arg_deviceAddress = (args[0] as String?); - assert( - arg_deviceAddress != null, - 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onPinCodeRequired was null, expected non-null String.', - ); + assert(arg_deviceAddress != null, + 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onPinCodeRequired was null, expected non-null String.'); try { api.onPinCodeRequired(arg_deviceAddress!); return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString()), - ); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); } }); } } { - final BasicMessageChannel - pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onLogEvent$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger, - ); + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onLogEvent$messageChannelSuffix', pigeonChannelCodec, + binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onLogEvent was null.', - ); + assert(message != null, + 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onLogEvent was null.'); final List args = (message as List?)!; final String? arg_category = (args[0] as String?); - assert( - arg_category != null, - 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onLogEvent was null, expected non-null String.', - ); + assert(arg_category != null, + 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onLogEvent was null, expected non-null String.'); final String? arg_level = (args[1] as String?); - assert( - arg_level != null, - 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onLogEvent was null, expected non-null String.', - ); + assert(arg_level != null, + 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onLogEvent was null, expected non-null String.'); final String? arg_message = (args[2] as String?); - assert( - arg_message != null, - 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onLogEvent was null, expected non-null String.', - ); + assert(arg_message != null, + 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onLogEvent was null, expected non-null String.'); try { api.onLogEvent(arg_category!, arg_level!, arg_message!); return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString()), - ); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); } }); } diff --git a/packages/libdivecomputer_plugin/linux/dive_computer_api.g.cc b/packages/libdivecomputer_plugin/linux/dive_computer_api.g.cc index 4dd86db43a..fa2e8e8d6f 100644 --- a/packages/libdivecomputer_plugin/linux/dive_computer_api.g.cc +++ b/packages/libdivecomputer_plugin/linux/dive_computer_api.g.cc @@ -834,11 +834,14 @@ struct _LibdivecomputerPluginGasMix { int64_t index; double o2_percent; double he_percent; + int64_t* usage; }; G_DEFINE_TYPE(LibdivecomputerPluginGasMix, libdivecomputer_plugin_gas_mix, G_TYPE_OBJECT) static void libdivecomputer_plugin_gas_mix_dispose(GObject* object) { + LibdivecomputerPluginGasMix* self = LIBDIVECOMPUTER_PLUGIN_GAS_MIX(object); + g_clear_pointer(&self->usage, g_free); G_OBJECT_CLASS(libdivecomputer_plugin_gas_mix_parent_class)->dispose(object); } @@ -849,11 +852,18 @@ static void libdivecomputer_plugin_gas_mix_class_init(LibdivecomputerPluginGasMi G_OBJECT_CLASS(klass)->dispose = libdivecomputer_plugin_gas_mix_dispose; } -LibdivecomputerPluginGasMix* libdivecomputer_plugin_gas_mix_new(int64_t index, double o2_percent, double he_percent) { +LibdivecomputerPluginGasMix* libdivecomputer_plugin_gas_mix_new(int64_t index, double o2_percent, double he_percent, int64_t* usage) { LibdivecomputerPluginGasMix* self = LIBDIVECOMPUTER_PLUGIN_GAS_MIX(g_object_new(libdivecomputer_plugin_gas_mix_get_type(), nullptr)); self->index = index; self->o2_percent = o2_percent; self->he_percent = he_percent; + if (usage != nullptr) { + self->usage = static_cast(malloc(sizeof(int64_t))); + *self->usage = *usage; + } + else { + self->usage = nullptr; + } return self; } @@ -872,11 +882,17 @@ double libdivecomputer_plugin_gas_mix_get_he_percent(LibdivecomputerPluginGasMix return self->he_percent; } +int64_t* libdivecomputer_plugin_gas_mix_get_usage(LibdivecomputerPluginGasMix* self) { + g_return_val_if_fail(LIBDIVECOMPUTER_PLUGIN_IS_GAS_MIX(self), nullptr); + return self->usage; +} + static FlValue* libdivecomputer_plugin_gas_mix_to_list(LibdivecomputerPluginGasMix* self) { FlValue* values = fl_value_new_list(); fl_value_append_take(values, fl_value_new_int(self->index)); fl_value_append_take(values, fl_value_new_float(self->o2_percent)); fl_value_append_take(values, fl_value_new_float(self->he_percent)); + fl_value_append_take(values, self->usage != nullptr ? fl_value_new_int(*self->usage) : fl_value_new_null()); return values; } @@ -887,7 +903,14 @@ static LibdivecomputerPluginGasMix* libdivecomputer_plugin_gas_mix_new_from_list double o2_percent = fl_value_get_float(value1); FlValue* value2 = fl_value_get_list_value(values, 2); double he_percent = fl_value_get_float(value2); - return libdivecomputer_plugin_gas_mix_new(index, o2_percent, he_percent); + FlValue* value3 = fl_value_get_list_value(values, 3); + int64_t* usage = nullptr; + int64_t usage_value; + if (fl_value_get_type(value3) != FL_VALUE_TYPE_NULL) { + usage_value = fl_value_get_int(value3); + usage = &usage_value; + } + return libdivecomputer_plugin_gas_mix_new(index, o2_percent, he_percent, usage); } struct _LibdivecomputerPluginTankInfo { diff --git a/packages/libdivecomputer_plugin/linux/dive_computer_api.g.h b/packages/libdivecomputer_plugin/linux/dive_computer_api.g.h index c01c4d3855..43ca4e32b3 100644 --- a/packages/libdivecomputer_plugin/linux/dive_computer_api.g.h +++ b/packages/libdivecomputer_plugin/linux/dive_computer_api.g.h @@ -524,12 +524,13 @@ G_DECLARE_FINAL_TYPE(LibdivecomputerPluginGasMix, libdivecomputer_plugin_gas_mix * index: field in this object. * o2_percent: field in this object. * he_percent: field in this object. + * usage: field in this object. * * Creates a new #GasMix object. * * Returns: a new #LibdivecomputerPluginGasMix */ -LibdivecomputerPluginGasMix* libdivecomputer_plugin_gas_mix_new(int64_t index, double o2_percent, double he_percent); +LibdivecomputerPluginGasMix* libdivecomputer_plugin_gas_mix_new(int64_t index, double o2_percent, double he_percent, int64_t* usage); /** * libdivecomputer_plugin_gas_mix_get_index @@ -561,6 +562,19 @@ double libdivecomputer_plugin_gas_mix_get_o2_percent(LibdivecomputerPluginGasMix */ double libdivecomputer_plugin_gas_mix_get_he_percent(LibdivecomputerPluginGasMix* object); +/** + * libdivecomputer_plugin_gas_mix_get_usage + * @object: a #LibdivecomputerPluginGasMix. + * + * Gas usage from libdivecomputer's `dc_usage_t` (1=oxygen, 2=diluent, + * 3=sidemount); null when the computer reported no usage (DC_USAGE_NONE). + * Set on the gas mix itself, so it is available even when the mix has no + * tank/transmitter record. + * + * Returns: the field value. + */ +int64_t* libdivecomputer_plugin_gas_mix_get_usage(LibdivecomputerPluginGasMix* object); + /** * LibdivecomputerPluginTankInfo: * diff --git a/packages/libdivecomputer_plugin/linux/dive_converter.c b/packages/libdivecomputer_plugin/linux/dive_converter.c index 1c9343facb..c144f6c074 100644 --- a/packages/libdivecomputer_plugin/linux/dive_converter.c +++ b/packages/libdivecomputer_plugin/linux/dive_converter.c @@ -165,11 +165,15 @@ LibdivecomputerPluginParsedDive* convert_parsed_dive( // Convert gas mixes. FlValue* gas_mixes = fl_value_new_list(); for (unsigned int i = 0; i < dive->gasmix_count; i++) { + int64_t gm_usage_val = (int64_t)dive->gasmixes[i].usage; + int64_t* gm_usage = (dive->gasmixes[i].usage == 0) ? NULL : &gm_usage_val; + LibdivecomputerPluginGasMix* mix = libdivecomputer_plugin_gas_mix_new( (int64_t)i, dive->gasmixes[i].oxygen * 100.0, - dive->gasmixes[i].helium * 100.0); + dive->gasmixes[i].helium * 100.0, + gm_usage); fl_value_append_take( gas_mixes, fl_value_new_custom_object(133, G_OBJECT(mix))); diff --git a/packages/libdivecomputer_plugin/macos/Classes/libdc_download.c b/packages/libdivecomputer_plugin/macos/Classes/libdc_download.c index 828d78294f..14a6548f11 100644 --- a/packages/libdivecomputer_plugin/macos/Classes/libdc_download.c +++ b/packages/libdivecomputer_plugin/macos/Classes/libdc_download.c @@ -590,6 +590,7 @@ static int extract_dive_fields(dc_parser_t *parser, libdc_parsed_dive_t *dive) { if (dc_parser_get_field(parser, DC_FIELD_GASMIX, i, &gm) == DC_STATUS_SUCCESS) { dive->gasmixes[i].oxygen = gm.oxygen; dive->gasmixes[i].helium = gm.helium; + dive->gasmixes[i].usage = gm.usage; } } dive->gasmix_count = gasmix_count; diff --git a/packages/libdivecomputer_plugin/macos/Classes/libdc_wrapper.h b/packages/libdivecomputer_plugin/macos/Classes/libdc_wrapper.h index 2542962450..d00db4d9f0 100644 --- a/packages/libdivecomputer_plugin/macos/Classes/libdc_wrapper.h +++ b/packages/libdivecomputer_plugin/macos/Classes/libdc_wrapper.h @@ -215,6 +215,7 @@ typedef struct { typedef struct { double oxygen; // fraction 0.0-1.0 double helium; // fraction 0.0-1.0 + unsigned int usage; // dc_usage_t (0=none, 1=oxygen, 2=diluent, 3=sidemount) } libdc_gasmix_t; typedef struct { diff --git a/packages/libdivecomputer_plugin/pigeons/dive_computer_api.dart b/packages/libdivecomputer_plugin/pigeons/dive_computer_api.dart index 7014940348..511dda390f 100644 --- a/packages/libdivecomputer_plugin/pigeons/dive_computer_api.dart +++ b/packages/libdivecomputer_plugin/pigeons/dive_computer_api.dart @@ -140,10 +140,17 @@ class GasMix { required this.index, required this.o2Percent, required this.hePercent, + this.usage, }); final int index; final double o2Percent; final double hePercent; + + /// Gas usage from libdivecomputer's `dc_usage_t` (1=oxygen, 2=diluent, + /// 3=sidemount); null when the computer reported no usage (DC_USAGE_NONE). + /// Set on the gas mix itself, so it is available even when the mix has no + /// tank/transmitter record. + final int? usage; } class TankInfo { diff --git a/packages/libdivecomputer_plugin/windows/dive_computer_api.g.cc b/packages/libdivecomputer_plugin/windows/dive_computer_api.g.cc index cf4d9f619f..c5555f9e53 100644 --- a/packages/libdivecomputer_plugin/windows/dive_computer_api.g.cc +++ b/packages/libdivecomputer_plugin/windows/dive_computer_api.g.cc @@ -804,6 +804,16 @@ GasMix::GasMix( o2_percent_(o2_percent), he_percent_(he_percent) {} +GasMix::GasMix( + int64_t index, + double o2_percent, + double he_percent, + const int64_t* usage) + : index_(index), + o2_percent_(o2_percent), + he_percent_(he_percent), + usage_(usage ? std::optional(*usage) : std::nullopt) {} + int64_t GasMix::index() const { return index_; } @@ -831,12 +841,26 @@ void GasMix::set_he_percent(double value_arg) { } +const int64_t* GasMix::usage() const { + return usage_ ? &(*usage_) : nullptr; +} + +void GasMix::set_usage(const int64_t* value_arg) { + usage_ = value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void GasMix::set_usage(int64_t value_arg) { + usage_ = value_arg; +} + + EncodableList GasMix::ToEncodableList() const { EncodableList list; - list.reserve(3); + list.reserve(4); list.push_back(EncodableValue(index_)); list.push_back(EncodableValue(o2_percent_)); list.push_back(EncodableValue(he_percent_)); + list.push_back(usage_ ? EncodableValue(*usage_) : EncodableValue()); return list; } @@ -845,6 +869,10 @@ GasMix GasMix::FromEncodableList(const EncodableList& list) { std::get(list[0]), std::get(list[1]), std::get(list[2])); + auto& encodable_usage = list[3]; + if (!encodable_usage.IsNull()) { + decoded.set_usage(std::get(encodable_usage)); + } return decoded; } diff --git a/packages/libdivecomputer_plugin/windows/dive_computer_api.g.h b/packages/libdivecomputer_plugin/windows/dive_computer_api.g.h index 5ff8900ad5..643d7ecd59 100644 --- a/packages/libdivecomputer_plugin/windows/dive_computer_api.g.h +++ b/packages/libdivecomputer_plugin/windows/dive_computer_api.g.h @@ -372,12 +372,19 @@ class ProfileSample { // Generated class from Pigeon that represents data sent in messages. class GasMix { public: - // Constructs an object setting all fields. + // Constructs an object setting all non-nullable fields. explicit GasMix( int64_t index, double o2_percent, double he_percent); + // Constructs an object setting all fields. + explicit GasMix( + int64_t index, + double o2_percent, + double he_percent, + const int64_t* usage); + int64_t index() const; void set_index(int64_t value_arg); @@ -387,6 +394,14 @@ class GasMix { double he_percent() const; void set_he_percent(double value_arg); + // Gas usage from libdivecomputer's `dc_usage_t` (1=oxygen, 2=diluent, + // 3=sidemount); null when the computer reported no usage (DC_USAGE_NONE). + // Set on the gas mix itself, so it is available even when the mix has no + // tank/transmitter record. + const int64_t* usage() const; + void set_usage(const int64_t* value_arg); + void set_usage(int64_t value_arg); + private: static GasMix FromEncodableList(const flutter::EncodableList& list); @@ -397,6 +412,7 @@ class GasMix { int64_t index_; double o2_percent_; double he_percent_; + std::optional usage_; }; diff --git a/packages/libdivecomputer_plugin/windows/dive_converter.cc b/packages/libdivecomputer_plugin/windows/dive_converter.cc index 75df0af249..27d92bb7e0 100644 --- a/packages/libdivecomputer_plugin/windows/dive_converter.cc +++ b/packages/libdivecomputer_plugin/windows/dive_converter.cc @@ -176,10 +176,17 @@ ParsedDive ConvertParsedDive(const libdc_parsed_dive_t& dive) { flutter::EncodableList gas_mixes; gas_mixes.reserve(dive.gasmix_count); for (unsigned int i = 0; i < dive.gasmix_count; i++) { + std::optional opt_gm_usage = + (dive.gasmixes[i].usage == 0) + ? std::nullopt + : std::optional( + static_cast(dive.gasmixes[i].usage)); + gas_mixes.push_back(flutter::CustomEncodableValue(GasMix( static_cast(i), dive.gasmixes[i].oxygen * 100.0, - dive.gasmixes[i].helium * 100.0))); + dive.gasmixes[i].helium * 100.0, + opt_gm_usage ? &*opt_gm_usage : nullptr))); } // Convert tanks. diff --git a/test/features/dive_computer/data/services/parsed_tank_resolver_test.dart b/test/features/dive_computer/data/services/parsed_tank_resolver_test.dart index 18d745586f..eae1be2283 100644 --- a/test/features/dive_computer/data/services/parsed_tank_resolver_test.dart +++ b/test/features/dive_computer/data/services/parsed_tank_resolver_test.dart @@ -416,6 +416,99 @@ void main() { expect(tanks.firstWhere((t) => t.o2Percent == 32.0).role, 'backGas'); expect(tanks.firstWhere((t) => t.o2Percent == 99.0).role, 'deco'); }); + + test( + 'sensorless CCR: reported gas usage maps oxygen/diluent/sidemount ' + 'directly, regardless of O2%', + () { + final parsed = makeParsedDive( + diveMode: 'ccr', + gasMixes: [ + pigeon.GasMix(index: 0, o2Percent: 100.0, hePercent: 0.0, usage: 1), + pigeon.GasMix(index: 1, o2Percent: 18.0, hePercent: 45.0, usage: 2), + pigeon.GasMix(index: 2, o2Percent: 21.0, hePercent: 0.0, usage: 3), + ], + ); + final tanks = resolveParsedTanks(parsed); + expect( + tanks.firstWhere((t) => t.o2Percent == 100.0).role, + 'oxygenSupply', + ); + expect(tanks.firstWhere((t) => t.o2Percent == 18.0).role, 'diluent'); + expect(tanks.firstWhere((t) => t.o2Percent == 21.0).role, 'backGas'); + }, + ); + + test( + 'sensorless CCR: bailout gases with no reported usage are ranked ' + 'against each other (lowest O2 -> bailout, >=41% -> deco, rest -> stage)', + () { + final parsed = makeParsedDive( + diveMode: 'ccr', + gasMixes: [ + pigeon.GasMix(index: 0, o2Percent: 18.0, hePercent: 45.0), + pigeon.GasMix(index: 1, o2Percent: 50.0, hePercent: 0.0), + pigeon.GasMix(index: 2, o2Percent: 32.0, hePercent: 0.0), + ], + ); + final tanks = resolveParsedTanks(parsed); + expect(tanks.firstWhere((t) => t.o2Percent == 18.0).role, 'bailout'); + expect(tanks.firstWhere((t) => t.o2Percent == 50.0).role, 'deco'); + expect(tanks.firstWhere((t) => t.o2Percent == 32.0).role, 'stage'); + }, + ); + + test( + 'sensorless CCR bailout tie: same lowest O2, higher helium wins ' + 'bailout; the loser falls through to deco/stage instead', + () { + final parsed = makeParsedDive( + diveMode: 'ccr', + gasMixes: [ + pigeon.GasMix(index: 0, o2Percent: 18.0, hePercent: 45.0), + pigeon.GasMix(index: 1, o2Percent: 18.0, hePercent: 50.0), + pigeon.GasMix(index: 2, o2Percent: 32.0, hePercent: 0.0), + ], + ); + final tanks = resolveParsedTanks(parsed); + expect(tanks.firstWhere((t) => t.hePercent == 45.0).role, 'stage'); + expect(tanks.firstWhere((t) => t.hePercent == 50.0).role, 'bailout'); + expect(tanks.firstWhere((t) => t.o2Percent == 32.0).role, 'stage'); + }, + ); + + test( + 'sensorless CCR bailout tie: same lowest O2 and helium -> both ' + 'gases get the bailout role', + () { + final parsed = makeParsedDive( + diveMode: 'ccr', + gasMixes: [ + pigeon.GasMix(index: 0, o2Percent: 18.0, hePercent: 45.0), + pigeon.GasMix(index: 1, o2Percent: 18.0, hePercent: 45.0), + ], + ); + final tanks = resolveParsedTanks(parsed); + expect(tanks.every((t) => t.role == 'bailout'), isTrue); + }, + ); + + test( + 'sensorless SCR keeps the original single-threshold heuristic, ' + 'unaffected by the CCR bailout ranking', + () { + final parsed = makeParsedDive( + diveMode: 'scr', + gasMixes: [ + pigeon.GasMix(index: 0, o2Percent: 18.0, hePercent: 45.0), + pigeon.GasMix(index: 1, o2Percent: 50.0, hePercent: 0.0), + ], + ); + final tanks = resolveParsedTanks(parsed); + expect(tanks.firstWhere((t) => t.o2Percent == 18.0).role, 'backGas'); + expect(tanks.firstWhere((t) => t.o2Percent == 50.0).role, 'deco'); + }, + ); }); }); From 81089ec924c4d8d66ef97abda5fd3abb68901586 Mon Sep 17 00:00:00 2001 From: alpheios-one Date: Thu, 10 Sep 2026 18:25:19 +0200 Subject: [PATCH 2/5] style: apply dart format to the CCR bailout gas role change CI's dart format --set-exit-if-changed . flagged all three files touched by the previous commit. The pigeon-generated dive_computer_api.g.dart needs a format pass after regeneration too, not just the hand-written files -- no re-run of pigeon or build_runner needed, the generated content itself was already correct. --- .../data/services/parsed_tank_resolver.dart | 4 +- .../src/generated/dive_computer_api.g.dart | 489 ++++++++++-------- .../services/parsed_tank_resolver_test.dart | 128 +++-- 3 files changed, 345 insertions(+), 276 deletions(-) diff --git a/lib/features/dive_computer/data/services/parsed_tank_resolver.dart b/lib/features/dive_computer/data/services/parsed_tank_resolver.dart index 9f098a4075..1f908fb284 100644 --- a/lib/features/dive_computer/data/services/parsed_tank_resolver.dart +++ b/lib/features/dive_computer/data/services/parsed_tank_resolver.dart @@ -259,9 +259,7 @@ List _inferSensorlessRoles( final lowestO2 = unranked .map((i) => gasMixes[i].o2Percent) .reduce((a, b) => a < b ? a : b); - final atLowestO2 = unranked.where( - (i) => gasMixes[i].o2Percent == lowestO2, - ); + final atLowestO2 = unranked.where((i) => gasMixes[i].o2Percent == lowestO2); final highestHeAtLowestO2 = atLowestO2 .map((i) => gasMixes[i].hePercent) .reduce((a, b) => a > b ? a : b); diff --git a/packages/libdivecomputer_plugin/lib/src/generated/dive_computer_api.g.dart b/packages/libdivecomputer_plugin/lib/src/generated/dive_computer_api.g.dart index 9601e840a2..6e18b1aeb1 100644 --- a/packages/libdivecomputer_plugin/lib/src/generated/dive_computer_api.g.dart +++ b/packages/libdivecomputer_plugin/lib/src/generated/dive_computer_api.g.dart @@ -15,7 +15,11 @@ PlatformException _createConnectionError(String channelName) { ); } -List wrapResponse({Object? result, PlatformException? error, bool empty = false}) { +List wrapResponse({ + Object? result, + PlatformException? error, + bool empty = false, +}) { if (empty) { return []; } @@ -25,12 +29,7 @@ List wrapResponse({Object? result, PlatformException? error, bool empty return [error.code, error.message, error.details]; } -enum TransportType { - ble, - usb, - serial, - infrared, -} +enum TransportType { ble, usb, serial, infrared } class DeviceDescriptor { DeviceDescriptor({ @@ -49,12 +48,7 @@ class DeviceDescriptor { List transports; Object encode() { - return [ - vendor, - product, - model, - transports, - ]; + return [vendor, product, model, transports]; } static DeviceDescriptor decode(Object result) { @@ -91,14 +85,7 @@ class DiscoveredDevice { TransportType transport; Object encode() { - return [ - vendor, - product, - model, - address, - name, - transport, - ]; + return [vendor, product, model, address, name, transport]; } static DiscoveredDevice decode(Object result) { @@ -313,12 +300,7 @@ class GasMix { int? usage; Object encode() { - return [ - index, - o2Percent, - hePercent, - usage, - ]; + return [index, o2Percent, hePercent, usage]; } static GasMix decode(Object result) { @@ -390,11 +372,7 @@ class TankInfo { } class DiveEvent { - DiveEvent({ - required this.timeSeconds, - required this.type, - this.data, - }); + DiveEvent({required this.timeSeconds, required this.type, this.data}); int timeSeconds; @@ -403,11 +381,7 @@ class DiveEvent { Map? data; Object encode() { - return [ - timeSeconds, - type, - data, - ]; + return [timeSeconds, type, data]; } static DiveEvent decode(Object result) { @@ -590,11 +564,7 @@ class DownloadProgress { String status; Object encode() { - return [ - current, - total, - status, - ]; + return [current, total, status]; } static DownloadProgress decode(Object result) { @@ -608,20 +578,14 @@ class DownloadProgress { } class DiveComputerError { - DiveComputerError({ - required this.code, - required this.message, - }); + DiveComputerError({required this.code, required this.message}); String code; String message; Object encode() { - return [ - code, - message, - ]; + return [code, message]; } static DiveComputerError decode(Object result) { @@ -633,7 +597,6 @@ class DiveComputerError { } } - class _PigeonCodec extends StandardMessageCodec { const _PigeonCodec(); @override @@ -641,34 +604,34 @@ class _PigeonCodec extends StandardMessageCodec { if (value is int) { buffer.putUint8(4); buffer.putInt64(value); - } else if (value is TransportType) { + } else if (value is TransportType) { buffer.putUint8(129); writeValue(buffer, value.index); - } else if (value is DeviceDescriptor) { + } else if (value is DeviceDescriptor) { buffer.putUint8(130); writeValue(buffer, value.encode()); - } else if (value is DiscoveredDevice) { + } else if (value is DiscoveredDevice) { buffer.putUint8(131); writeValue(buffer, value.encode()); - } else if (value is ProfileSample) { + } else if (value is ProfileSample) { buffer.putUint8(132); writeValue(buffer, value.encode()); - } else if (value is GasMix) { + } else if (value is GasMix) { buffer.putUint8(133); writeValue(buffer, value.encode()); - } else if (value is TankInfo) { + } else if (value is TankInfo) { buffer.putUint8(134); writeValue(buffer, value.encode()); - } else if (value is DiveEvent) { + } else if (value is DiveEvent) { buffer.putUint8(135); writeValue(buffer, value.encode()); - } else if (value is ParsedDive) { + } else if (value is ParsedDive) { buffer.putUint8(136); writeValue(buffer, value.encode()); - } else if (value is DownloadProgress) { + } else if (value is DownloadProgress) { buffer.putUint8(137); writeValue(buffer, value.encode()); - } else if (value is DiveComputerError) { + } else if (value is DiveComputerError) { buffer.putUint8(138); writeValue(buffer, value.encode()); } else { @@ -679,26 +642,26 @@ class _PigeonCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { - case 129: + case 129: final int? value = readValue(buffer) as int?; return value == null ? null : TransportType.values[value]; - case 130: + case 130: return DeviceDescriptor.decode(readValue(buffer)!); - case 131: + case 131: return DiscoveredDevice.decode(readValue(buffer)!); - case 132: + case 132: return ProfileSample.decode(readValue(buffer)!); - case 133: + case 133: return GasMix.decode(readValue(buffer)!); - case 134: + case 134: return TankInfo.decode(readValue(buffer)!); - case 135: + case 135: return DiveEvent.decode(readValue(buffer)!); - case 136: + case 136: return ParsedDive.decode(readValue(buffer)!); - case 137: + case 137: return DownloadProgress.decode(readValue(buffer)!); - case 138: + case 138: return DiveComputerError.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -710,9 +673,13 @@ class DiveComputerHostApi { /// Constructor for [DiveComputerHostApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default /// BinaryMessenger will be used which routes to the host platform. - DiveComputerHostApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) - : pigeonVar_binaryMessenger = binaryMessenger, - pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + DiveComputerHostApi({ + BinaryMessenger? binaryMessenger, + String messageChannelSuffix = '', + }) : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty + ? '.$messageChannelSuffix' + : ''; final BinaryMessenger? pigeonVar_binaryMessenger; static const MessageCodec pigeonChannelCodec = _PigeonCodec(); @@ -720,12 +687,14 @@ class DiveComputerHostApi { final String pigeonVar_messageChannelSuffix; Future> getDeviceDescriptors() async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerHostApi.getDeviceDescriptors$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final String pigeonVar_channelName = + 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerHostApi.getDeviceDescriptors$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final List? pigeonVar_replyList = await pigeonVar_channel.send(null) as List?; if (pigeonVar_replyList == null) { @@ -742,17 +711,20 @@ class DiveComputerHostApi { message: 'Host platform returned null value for non-null return value.', ); } else { - return (pigeonVar_replyList[0] as List?)!.cast(); + return (pigeonVar_replyList[0] as List?)! + .cast(); } } Future startDiscovery(TransportType transport) async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerHostApi.startDiscovery$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final String pigeonVar_channelName = + 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerHostApi.startDiscovery$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final List? pigeonVar_replyList = await pigeonVar_channel.send([transport]) as List?; if (pigeonVar_replyList == null) { @@ -769,12 +741,14 @@ class DiveComputerHostApi { } Future stopDiscovery() async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerHostApi.stopDiscovery$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final String pigeonVar_channelName = + 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerHostApi.stopDiscovery$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final List? pigeonVar_replyList = await pigeonVar_channel.send(null) as List?; if (pigeonVar_replyList == null) { @@ -790,15 +764,22 @@ class DiveComputerHostApi { } } - Future startDownload(DiscoveredDevice device, String? fingerprint, bool syncClock) async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerHostApi.startDownload$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + Future startDownload( + DiscoveredDevice device, + String? fingerprint, + bool syncClock, + ) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerHostApi.startDownload$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final List? pigeonVar_replyList = - await pigeonVar_channel.send([device, fingerprint, syncClock]) as List?; + await pigeonVar_channel.send([device, fingerprint, syncClock]) + as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -813,12 +794,14 @@ class DiveComputerHostApi { } Future cancelDownload() async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerHostApi.cancelDownload$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final String pigeonVar_channelName = + 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerHostApi.cancelDownload$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final List? pigeonVar_replyList = await pigeonVar_channel.send(null) as List?; if (pigeonVar_replyList == null) { @@ -835,12 +818,14 @@ class DiveComputerHostApi { } Future submitPinCode(String pinCode) async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerHostApi.submitPinCode$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final String pigeonVar_channelName = + 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerHostApi.submitPinCode$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final List? pigeonVar_replyList = await pigeonVar_channel.send([pinCode]) as List?; if (pigeonVar_replyList == null) { @@ -857,12 +842,14 @@ class DiveComputerHostApi { } Future getLibdivecomputerVersion() async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerHostApi.getLibdivecomputerVersion$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final String pigeonVar_channelName = + 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerHostApi.getLibdivecomputerVersion$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final List? pigeonVar_replyList = await pigeonVar_channel.send(null) as List?; if (pigeonVar_replyList == null) { @@ -883,15 +870,23 @@ class DiveComputerHostApi { } } - Future parseRawDiveData(String vendor, String product, int model, Uint8List data) async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerHostApi.parseRawDiveData$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + Future parseRawDiveData( + String vendor, + String product, + int model, + Uint8List data, + ) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerHostApi.parseRawDiveData$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final List? pigeonVar_replyList = - await pigeonVar_channel.send([vendor, product, model, data]) as List?; + await pigeonVar_channel.send([vendor, product, model, data]) + as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -922,7 +917,12 @@ abstract class DiveComputerFlutterApi { void onDiveDownloaded(ParsedDive dive); - void onDownloadComplete(int totalDives, String? serialNumber, String? firmwareVersion, String? clockSyncStatus); + void onDownloadComplete( + int totalDives, + String? serialNumber, + String? firmwareVersion, + String? clockSyncStatus, + ); void onError(DiveComputerError error); @@ -930,37 +930,55 @@ abstract class DiveComputerFlutterApi { void onLogEvent(String category, String level, String message); - static void setUp(DiveComputerFlutterApi? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) { - messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + static void setUp( + DiveComputerFlutterApi? api, { + BinaryMessenger? binaryMessenger, + String messageChannelSuffix = '', + }) { + messageChannelSuffix = messageChannelSuffix.isNotEmpty + ? '.$messageChannelSuffix' + : ''; { - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDeviceDiscovered$messageChannelSuffix', pigeonChannelCodec, - binaryMessenger: binaryMessenger); + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDeviceDiscovered$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDeviceDiscovered was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDeviceDiscovered was null.', + ); final List args = (message as List?)!; final DiscoveredDevice? arg_device = (args[0] as DiscoveredDevice?); - assert(arg_device != null, - 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDeviceDiscovered was null, expected non-null DiscoveredDevice.'); + assert( + arg_device != null, + 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDeviceDiscovered was null, expected non-null DiscoveredDevice.', + ); try { api.onDeviceDiscovered(arg_device!); return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); } }); } } { - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDiscoveryComplete$messageChannelSuffix', pigeonChannelCodec, - binaryMessenger: binaryMessenger); + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDiscoveryComplete$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { @@ -970,167 +988,232 @@ abstract class DiveComputerFlutterApi { return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); } }); } } { - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDownloadProgress$messageChannelSuffix', pigeonChannelCodec, - binaryMessenger: binaryMessenger); + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDownloadProgress$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDownloadProgress was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDownloadProgress was null.', + ); final List args = (message as List?)!; final DownloadProgress? arg_progress = (args[0] as DownloadProgress?); - assert(arg_progress != null, - 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDownloadProgress was null, expected non-null DownloadProgress.'); + assert( + arg_progress != null, + 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDownloadProgress was null, expected non-null DownloadProgress.', + ); try { api.onDownloadProgress(arg_progress!); return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); } }); } } { - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDiveDownloaded$messageChannelSuffix', pigeonChannelCodec, - binaryMessenger: binaryMessenger); + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDiveDownloaded$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDiveDownloaded was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDiveDownloaded was null.', + ); final List args = (message as List?)!; final ParsedDive? arg_dive = (args[0] as ParsedDive?); - assert(arg_dive != null, - 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDiveDownloaded was null, expected non-null ParsedDive.'); + assert( + arg_dive != null, + 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDiveDownloaded was null, expected non-null ParsedDive.', + ); try { api.onDiveDownloaded(arg_dive!); return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); } }); } } { - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDownloadComplete$messageChannelSuffix', pigeonChannelCodec, - binaryMessenger: binaryMessenger); + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDownloadComplete$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDownloadComplete was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDownloadComplete was null.', + ); final List args = (message as List?)!; final int? arg_totalDives = (args[0] as int?); - assert(arg_totalDives != null, - 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDownloadComplete was null, expected non-null int.'); + assert( + arg_totalDives != null, + 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onDownloadComplete was null, expected non-null int.', + ); final String? arg_serialNumber = (args[1] as String?); final String? arg_firmwareVersion = (args[2] as String?); final String? arg_clockSyncStatus = (args[3] as String?); try { - api.onDownloadComplete(arg_totalDives!, arg_serialNumber, arg_firmwareVersion, arg_clockSyncStatus); + api.onDownloadComplete( + arg_totalDives!, + arg_serialNumber, + arg_firmwareVersion, + arg_clockSyncStatus, + ); return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); } }); } } { - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onError$messageChannelSuffix', pigeonChannelCodec, - binaryMessenger: binaryMessenger); + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onError$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onError was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onError was null.', + ); final List args = (message as List?)!; final DiveComputerError? arg_error = (args[0] as DiveComputerError?); - assert(arg_error != null, - 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onError was null, expected non-null DiveComputerError.'); + assert( + arg_error != null, + 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onError was null, expected non-null DiveComputerError.', + ); try { api.onError(arg_error!); return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); } }); } } { - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onPinCodeRequired$messageChannelSuffix', pigeonChannelCodec, - binaryMessenger: binaryMessenger); + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onPinCodeRequired$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onPinCodeRequired was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onPinCodeRequired was null.', + ); final List args = (message as List?)!; final String? arg_deviceAddress = (args[0] as String?); - assert(arg_deviceAddress != null, - 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onPinCodeRequired was null, expected non-null String.'); + assert( + arg_deviceAddress != null, + 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onPinCodeRequired was null, expected non-null String.', + ); try { api.onPinCodeRequired(arg_deviceAddress!); return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); } }); } } { - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onLogEvent$messageChannelSuffix', pigeonChannelCodec, - binaryMessenger: binaryMessenger); + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onLogEvent$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onLogEvent was null.'); + assert( + message != null, + 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onLogEvent was null.', + ); final List args = (message as List?)!; final String? arg_category = (args[0] as String?); - assert(arg_category != null, - 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onLogEvent was null, expected non-null String.'); + assert( + arg_category != null, + 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onLogEvent was null, expected non-null String.', + ); final String? arg_level = (args[1] as String?); - assert(arg_level != null, - 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onLogEvent was null, expected non-null String.'); + assert( + arg_level != null, + 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onLogEvent was null, expected non-null String.', + ); final String? arg_message = (args[2] as String?); - assert(arg_message != null, - 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onLogEvent was null, expected non-null String.'); + assert( + arg_message != null, + 'Argument for dev.flutter.pigeon.libdivecomputer_plugin.DiveComputerFlutterApi.onLogEvent was null, expected non-null String.', + ); try { api.onLogEvent(arg_category!, arg_level!, arg_message!); return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); } }); } diff --git a/test/features/dive_computer/data/services/parsed_tank_resolver_test.dart b/test/features/dive_computer/data/services/parsed_tank_resolver_test.dart index eae1be2283..aceabbee51 100644 --- a/test/features/dive_computer/data/services/parsed_tank_resolver_test.dart +++ b/test/features/dive_computer/data/services/parsed_tank_resolver_test.dart @@ -417,27 +417,24 @@ void main() { expect(tanks.firstWhere((t) => t.o2Percent == 99.0).role, 'deco'); }); - test( - 'sensorless CCR: reported gas usage maps oxygen/diluent/sidemount ' - 'directly, regardless of O2%', - () { - final parsed = makeParsedDive( - diveMode: 'ccr', - gasMixes: [ - pigeon.GasMix(index: 0, o2Percent: 100.0, hePercent: 0.0, usage: 1), - pigeon.GasMix(index: 1, o2Percent: 18.0, hePercent: 45.0, usage: 2), - pigeon.GasMix(index: 2, o2Percent: 21.0, hePercent: 0.0, usage: 3), - ], - ); - final tanks = resolveParsedTanks(parsed); - expect( - tanks.firstWhere((t) => t.o2Percent == 100.0).role, - 'oxygenSupply', - ); - expect(tanks.firstWhere((t) => t.o2Percent == 18.0).role, 'diluent'); - expect(tanks.firstWhere((t) => t.o2Percent == 21.0).role, 'backGas'); - }, - ); + test('sensorless CCR: reported gas usage maps oxygen/diluent/sidemount ' + 'directly, regardless of O2%', () { + final parsed = makeParsedDive( + diveMode: 'ccr', + gasMixes: [ + pigeon.GasMix(index: 0, o2Percent: 100.0, hePercent: 0.0, usage: 1), + pigeon.GasMix(index: 1, o2Percent: 18.0, hePercent: 45.0, usage: 2), + pigeon.GasMix(index: 2, o2Percent: 21.0, hePercent: 0.0, usage: 3), + ], + ); + final tanks = resolveParsedTanks(parsed); + expect( + tanks.firstWhere((t) => t.o2Percent == 100.0).role, + 'oxygenSupply', + ); + expect(tanks.firstWhere((t) => t.o2Percent == 18.0).role, 'diluent'); + expect(tanks.firstWhere((t) => t.o2Percent == 21.0).role, 'backGas'); + }); test( 'sensorless CCR: bailout gases with no reported usage are ranked ' @@ -458,57 +455,48 @@ void main() { }, ); - test( - 'sensorless CCR bailout tie: same lowest O2, higher helium wins ' - 'bailout; the loser falls through to deco/stage instead', - () { - final parsed = makeParsedDive( - diveMode: 'ccr', - gasMixes: [ - pigeon.GasMix(index: 0, o2Percent: 18.0, hePercent: 45.0), - pigeon.GasMix(index: 1, o2Percent: 18.0, hePercent: 50.0), - pigeon.GasMix(index: 2, o2Percent: 32.0, hePercent: 0.0), - ], - ); - final tanks = resolveParsedTanks(parsed); - expect(tanks.firstWhere((t) => t.hePercent == 45.0).role, 'stage'); - expect(tanks.firstWhere((t) => t.hePercent == 50.0).role, 'bailout'); - expect(tanks.firstWhere((t) => t.o2Percent == 32.0).role, 'stage'); - }, - ); + test('sensorless CCR bailout tie: same lowest O2, higher helium wins ' + 'bailout; the loser falls through to deco/stage instead', () { + final parsed = makeParsedDive( + diveMode: 'ccr', + gasMixes: [ + pigeon.GasMix(index: 0, o2Percent: 18.0, hePercent: 45.0), + pigeon.GasMix(index: 1, o2Percent: 18.0, hePercent: 50.0), + pigeon.GasMix(index: 2, o2Percent: 32.0, hePercent: 0.0), + ], + ); + final tanks = resolveParsedTanks(parsed); + expect(tanks.firstWhere((t) => t.hePercent == 45.0).role, 'stage'); + expect(tanks.firstWhere((t) => t.hePercent == 50.0).role, 'bailout'); + expect(tanks.firstWhere((t) => t.o2Percent == 32.0).role, 'stage'); + }); - test( - 'sensorless CCR bailout tie: same lowest O2 and helium -> both ' - 'gases get the bailout role', - () { - final parsed = makeParsedDive( - diveMode: 'ccr', - gasMixes: [ - pigeon.GasMix(index: 0, o2Percent: 18.0, hePercent: 45.0), - pigeon.GasMix(index: 1, o2Percent: 18.0, hePercent: 45.0), - ], - ); - final tanks = resolveParsedTanks(parsed); - expect(tanks.every((t) => t.role == 'bailout'), isTrue); - }, - ); + test('sensorless CCR bailout tie: same lowest O2 and helium -> both ' + 'gases get the bailout role', () { + final parsed = makeParsedDive( + diveMode: 'ccr', + gasMixes: [ + pigeon.GasMix(index: 0, o2Percent: 18.0, hePercent: 45.0), + pigeon.GasMix(index: 1, o2Percent: 18.0, hePercent: 45.0), + ], + ); + final tanks = resolveParsedTanks(parsed); + expect(tanks.every((t) => t.role == 'bailout'), isTrue); + }); - test( - 'sensorless SCR keeps the original single-threshold heuristic, ' - 'unaffected by the CCR bailout ranking', - () { - final parsed = makeParsedDive( - diveMode: 'scr', - gasMixes: [ - pigeon.GasMix(index: 0, o2Percent: 18.0, hePercent: 45.0), - pigeon.GasMix(index: 1, o2Percent: 50.0, hePercent: 0.0), - ], - ); - final tanks = resolveParsedTanks(parsed); - expect(tanks.firstWhere((t) => t.o2Percent == 18.0).role, 'backGas'); - expect(tanks.firstWhere((t) => t.o2Percent == 50.0).role, 'deco'); - }, - ); + test('sensorless SCR keeps the original single-threshold heuristic, ' + 'unaffected by the CCR bailout ranking', () { + final parsed = makeParsedDive( + diveMode: 'scr', + gasMixes: [ + pigeon.GasMix(index: 0, o2Percent: 18.0, hePercent: 45.0), + pigeon.GasMix(index: 1, o2Percent: 50.0, hePercent: 0.0), + ], + ); + final tanks = resolveParsedTanks(parsed); + expect(tanks.firstWhere((t) => t.o2Percent == 18.0).role, 'backGas'); + expect(tanks.firstWhere((t) => t.o2Percent == 50.0).role, 'deco'); + }); }); }); From 2bb1a3a09e31f49b6001dbc03102207f9dc83c7a Mon Sep 17 00:00:00 2001 From: alpheios-one Date: Thu, 10 Sep 2026 19:12:35 +0200 Subject: [PATCH 3/5] fix(dive-computer): clarify that DC_USAGE_SIDEMOUNT maps to back gas, not a sidemount role Copilot review on #61: the doc comment and test name implied sidemount usage kept a distinct "sidemount" role, but it actually maps to TankRole.backGas -- the device flag says the gas is on a sidemount cylinder, not which side, so it cannot pick between sidemountLeft and sidemountRight. Correct the comment and rename the test to match the real behavior; no logic change. --- .../data/services/parsed_tank_resolver.dart | 9 ++++++--- .../data/services/parsed_tank_resolver_test.dart | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/features/dive_computer/data/services/parsed_tank_resolver.dart b/lib/features/dive_computer/data/services/parsed_tank_resolver.dart index 1f908fb284..e98521077c 100644 --- a/lib/features/dive_computer/data/services/parsed_tank_resolver.dart +++ b/lib/features/dive_computer/data/services/parsed_tank_resolver.dart @@ -209,9 +209,12 @@ String _inferRole(int? usage, double o2Percent, double hePercent) { /// Role for each of [gasMixes], in order, on a sensorless (tankless) dive. /// /// A gas whose usage the computer reported directly on the gas mix itself -/// (`dc_gasmix_t.usage`, independent of any tank/transmitter record) keeps -/// that role regardless of dive mode: oxygen, diluent or sidemount is -/// authoritative device data. +/// (`dc_gasmix_t.usage`, independent of any tank/transmitter record) is +/// authoritative device data, regardless of dive mode: oxygen maps to +/// [TankRole.oxygenSupply], diluent to [TankRole.diluent]. Sidemount maps to +/// [TankRole.backGas] rather than [TankRole.sidemountLeft]/`Right` -- the +/// flag only says the gas is on a sidemount cylinder, not which side, so it +/// cannot pick between the two. /// /// For a dive recognized as CCR, the gases left with no reported usage are /// the open-circuit bailout candidates and are ranked against each other diff --git a/test/features/dive_computer/data/services/parsed_tank_resolver_test.dart b/test/features/dive_computer/data/services/parsed_tank_resolver_test.dart index aceabbee51..2f1ea31da4 100644 --- a/test/features/dive_computer/data/services/parsed_tank_resolver_test.dart +++ b/test/features/dive_computer/data/services/parsed_tank_resolver_test.dart @@ -417,8 +417,8 @@ void main() { expect(tanks.firstWhere((t) => t.o2Percent == 99.0).role, 'deco'); }); - test('sensorless CCR: reported gas usage maps oxygen/diluent/sidemount ' - 'directly, regardless of O2%', () { + test('sensorless CCR: reported gas usage maps oxygen/diluent directly ' + 'and sidemount to back gas, regardless of O2%', () { final parsed = makeParsedDive( diveMode: 'ccr', gasMixes: [ From 3a98df2904aacdc64f0f3e2b423d664cbfac9b47 Mon Sep 17 00:00:00 2001 From: alpheios-one Date: Thu, 10 Sep 2026 21:02:23 +0200 Subject: [PATCH 4/5] fix(dive-computer): use an epsilon comparison for the CCR bailout tie-break Copilot review on #1752: the lowest-O2 and helium tie-break comparisons used exact double equality. Each of the four platform converters computes o2Percent/hePercent independently as fraction * 100.0 from the native dc_gasmix_t, so two mixes the diver set to the same nominal percentage can differ by a few ULPs instead of being bit-identical -- exact == would then miss a real tie and misassign bailout/deco/stage roles. Add a small epsilon comparison and a regression test simulating that floating-point noise. --- .../data/services/parsed_tank_resolver.dart | 14 +++++++++++-- .../services/parsed_tank_resolver_test.dart | 21 +++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/lib/features/dive_computer/data/services/parsed_tank_resolver.dart b/lib/features/dive_computer/data/services/parsed_tank_resolver.dart index e98521077c..979d26629a 100644 --- a/lib/features/dive_computer/data/services/parsed_tank_resolver.dart +++ b/lib/features/dive_computer/data/services/parsed_tank_resolver.dart @@ -262,12 +262,14 @@ List _inferSensorlessRoles( final lowestO2 = unranked .map((i) => gasMixes[i].o2Percent) .reduce((a, b) => a < b ? a : b); - final atLowestO2 = unranked.where((i) => gasMixes[i].o2Percent == lowestO2); + final atLowestO2 = unranked.where( + (i) => _nearlyEqualPercent(gasMixes[i].o2Percent, lowestO2), + ); final highestHeAtLowestO2 = atLowestO2 .map((i) => gasMixes[i].hePercent) .reduce((a, b) => a > b ? a : b); for (final i in atLowestO2) { - if (gasMixes[i].hePercent == highestHeAtLowestO2) { + if (_nearlyEqualPercent(gasMixes[i].hePercent, highestHeAtLowestO2)) { roles[i] = TankRole.bailout.name; } } @@ -282,6 +284,14 @@ List _inferSensorlessRoles( return [for (final role in roles) role!]; } +/// Whether two gas percentages are the same value within floating-point +/// noise. Each of the four platform converters independently computes +/// `fraction * 100.0` from the native `dc_gasmix_t`, so two mixes the diver +/// set to the same nominal percentage can differ by a few ULPs; an exact +/// `==` would then miss a real tie in [_inferSensorlessRoles]'s bailout +/// ranking. +bool _nearlyEqualPercent(double a, double b) => (a - b).abs() < 1e-6; + /// The gas-mix index (position in [gasMixes]) for [tank], preferring the gas /// actually breathed on it. Returns null only when there are no gas mixes. int? _resolveTankGasIndex( diff --git a/test/features/dive_computer/data/services/parsed_tank_resolver_test.dart b/test/features/dive_computer/data/services/parsed_tank_resolver_test.dart index 2f1ea31da4..4f3da9f375 100644 --- a/test/features/dive_computer/data/services/parsed_tank_resolver_test.dart +++ b/test/features/dive_computer/data/services/parsed_tank_resolver_test.dart @@ -484,6 +484,27 @@ void main() { expect(tanks.every((t) => t.role == 'bailout'), isTrue); }); + test('sensorless CCR bailout tie: floating-point noise from the native ' + 'fraction * 100.0 conversion does not defeat the tie detection', () { + // Each platform converter computes o2/he percent independently from + // the native fraction, so two mixes the diver set to the same + // nominal percentage can differ by a few ULPs rather than being + // bit-identical. + final parsed = makeParsedDive( + diveMode: 'ccr', + gasMixes: [ + pigeon.GasMix(index: 0, o2Percent: 18.0, hePercent: 45.0), + pigeon.GasMix( + index: 1, + o2Percent: 18.0 + 4e-14, + hePercent: 45.0 - 4e-14, + ), + ], + ); + final tanks = resolveParsedTanks(parsed); + expect(tanks.every((t) => t.role == 'bailout'), isTrue); + }); + test('sensorless SCR keeps the original single-threshold heuristic, ' 'unaffected by the CCR bailout ranking', () { final parsed = makeParsedDive( From dce4786565e26e67e13cd29ebea90721c46d98d0 Mon Sep 17 00:00:00 2001 From: alpheios-one Date: Thu, 10 Sep 2026 21:10:16 +0200 Subject: [PATCH 5/5] docs(dive-computer): fix malformed dartdoc link in the sidemount role comment Copilot review on #1752: [TankRole.sidemountLeft]/`Right` only linked sidemountLeft and left Right as a stray inline-code fragment. Link both roles properly. --- .../dive_computer/data/services/parsed_tank_resolver.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/features/dive_computer/data/services/parsed_tank_resolver.dart b/lib/features/dive_computer/data/services/parsed_tank_resolver.dart index 979d26629a..7865994948 100644 --- a/lib/features/dive_computer/data/services/parsed_tank_resolver.dart +++ b/lib/features/dive_computer/data/services/parsed_tank_resolver.dart @@ -212,9 +212,9 @@ String _inferRole(int? usage, double o2Percent, double hePercent) { /// (`dc_gasmix_t.usage`, independent of any tank/transmitter record) is /// authoritative device data, regardless of dive mode: oxygen maps to /// [TankRole.oxygenSupply], diluent to [TankRole.diluent]. Sidemount maps to -/// [TankRole.backGas] rather than [TankRole.sidemountLeft]/`Right` -- the -/// flag only says the gas is on a sidemount cylinder, not which side, so it -/// cannot pick between the two. +/// [TankRole.backGas] rather than [TankRole.sidemountLeft]/[TankRole.sidemountRight] +/// -- the flag only says the gas is on a sidemount cylinder, not which side, +/// so it cannot pick between the two. /// /// For a dive recognized as CCR, the gases left with no reported usage are /// the open-circuit bailout candidates and are ranked against each other