Skip to content

Conversation

@jatinkumarsingh
Copy link

@jatinkumarsingh jatinkumarsingh commented Dec 8, 2025

Fixes #2991

This pull request adds support for the HMC5883L 3-axis magnetometer sensor to the PSLab application. The implementation includes a complete sensor driver, state management, and an interactive UI for real-time data visualization and calibration.

Changes

New Files

  • lib/communication/sensors/hmc5883l.dart - Low-level sensor driver handling I2C communication with the HMC5883L magnetometer

    • Configurable gain settings (8 levels from 230 to 1370 LSB/Gauss)
    • Data rate selection (0.75 Hz to 75 Hz)
    • Sample averaging (1, 2, 4, or 8 samples)
    • Magnetic field measurement in all three axes (X, Y, Z) with microTesla conversion
    • Heading calculation (0-360°) using arctangent of Y/X components
    • Field magnitude computation using 3D vector length
    • Calibration offset support for compensating magnetic interference
    • Device verification via manufacturer and device ID checks
  • lib/providers/hmc5883l_provider.dart - State management using Provider pattern

    • Real-time periodic data collection with configurable sampling interval
    • Circular buffer limiting chart data to 1000 points to prevent memory issues
    • Interactive calibration mode with automatic min/max value tracking
    • Supports both single-shot and continuous looping data collection
    • Clean data management with utilities to clear and reset collection
  • lib/view/hmc5883l_screen.dart - Complete sensor UI with visualization

    • Real-time display of raw magnetic field values (X, Y, Z axes)
    • Five distinct charts showing magnetic components, heading angle, and field magnitude
    • Calibration interface with gain selection dialog
    • Data collection controls (play, pause, loop, clear)
    • Error handling with user-friendly feedback via snackbars

Modified Files

  • lib/view/sensors_screen.dart - Added navigation route for HMC5883L sensor

How It Works

  1. Sensor Driver: Communicates with the HMC5883L via I2C (address 0x1E) to configure settings and read magnetic field data
  2. Data Collection: The provider collects measurements at regular intervals and maintains time-series data for visualization
  3. Calibration: Users can perform interactive calibration by moving the sensor in a figure-8 pattern to track min/max values and compute offset corrections
  4. Visualization: Real-time charts display the three magnetic axes, calculated heading angle, and total field magnitude

Technical Details

  • All values follow SI units: magnetic field in microTesla (µT), heading in degrees
  • Heading is calculated as atan2(magneticY, magneticX) converted to 0-360° range
  • Magnitude is computed as sqrt(X² + Y² + Z²) for the total field strength
  • Data collection gracefully handles errors without interrupting the collection loop
  • UI updates are only performed when widgets are mounted to prevent memory leaks

Screenshots / Recordings

N/A

Checklist

  • No hard coding: Used constants from sensor specifications and localization where applicable
  • No end of file edits: All files properly terminated
  • Code reformatting: Code formatted using Dart conventions
  • Code analysis: No errors in flutter analyze, all null safety checks passed

Related Issue

Closes #2991

Summary by Sourcery

Add support for the HMC5883L 3-axis magnetometer, including a sensor driver, provider-based state management, and a dedicated UI screen with real-time visualization and calibration controls.

New Features:

  • Introduce an HMC5883L sensor driver for I2C-based magnetic field, heading, and magnitude measurements with configurable gain and sampling settings.
  • Add a provider to manage HMC5883L data acquisition, buffering, and calibration state for real-time updates.
  • Create an HMC5883L sensor screen with raw value display, multiple time-series charts, calibration workflow, and data collection controls.
  • Wire the HMC5883L screen into the sensors navigation flow so it is accessible from the main sensors list.

- Implement HMC5883L sensor driver with I2C communication
- Add configurable gain, data rate, and sampling settings
- Support magnetic field reading (X, Y, Z axes) in microTesla
- Add heading calculation (0-360 degrees) and magnitude computation
- Implement interactive calibration with min/max tracking
- Add state management provider with real-time data collection
- Create sensor UI screen with 5 real-time charts
- Integrate HMC5883L into sensor selection navigation
- Support periodic data collection with circular buffer
- Include proper error handling and logging
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Dec 8, 2025

Reviewer's Guide

Adds full HMC5883L magnetometer support by introducing a sensor driver with I2C configuration/reads, a Provider-based state manager for timed acquisition, calibration and buffering, and a new UI screen with charts and controls, then wiring it into the existing sensors navigation.

Sequence diagram for HMC5883L initialization and periodic data acquisition

sequenceDiagram
  actor User
  participant SensorsScreen
  participant HMC5883LScreen
  participant HMC5883LProvider
  participant Locator
  participant ScienceLab
  participant I2C
  participant HMC5883L

  User->>SensorsScreen: tap HMC5883L item
  SensorsScreen->>HMC5883LScreen: navigate to HMC5883LScreen

  activate HMC5883LScreen
  HMC5883LScreen->>Locator: get ScienceLab
  Locator-->>HMC5883LScreen: ScienceLab instance
  HMC5883LScreen->>ScienceLab: isConnected
  ScienceLab-->>HMC5883LScreen: true
  HMC5883LScreen->>I2C: create with mPacketHandler

  HMC5883LScreen->>HMC5883LProvider: create ChangeNotifier
  activate HMC5883LProvider
  HMC5883LScreen->>HMC5883LProvider: initializeSensors(onError, i2c, scienceLab)

  HMC5883LProvider->>ScienceLab: isConnected
  ScienceLab-->>HMC5883LProvider: true
  HMC5883LProvider->>HMC5883L: create(i2c, scienceLab)

  activate HMC5883L
  HMC5883L->>ScienceLab: isConnected
  ScienceLab-->>HMC5883L: true
  HMC5883L->>I2C: readByte(address, idRegA)
  I2C-->>HMC5883L: idA
  HMC5883L->>I2C: readByte(address, idRegB)
  I2C-->>HMC5883L: idB
  HMC5883L->>I2C: readByte(address, idRegC)
  I2C-->>HMC5883L: idC
  HMC5883L->>I2C: write(address, [configA], configRegA)
  HMC5883L->>I2C: write(address, [gain], configRegB)
  HMC5883L->>I2C: write(address, [mode], modeReg)
  HMC5883L-->>HMC5883LProvider: initialized instance
  deactivate HMC5883L

  HMC5883LProvider-->>HMC5883LScreen: notifyListeners
  deactivate HMC5883LProvider

  User->>HMC5883LScreen: press play in SensorControlsWidget
  HMC5883LScreen->>HMC5883LProvider: toggleDataCollection
  activate HMC5883LProvider
  HMC5883LProvider->>HMC5883LProvider: _startDataCollection
  HMC5883LProvider->>HMC5883LProvider: create periodic Timer

  loop every timegapMs
    HMC5883LProvider->>HMC5883L: getAllData
    activate HMC5883L
    HMC5883L->>HMC5883L: readRawData
    HMC5883L->>I2C: readBulk(address, dataXMSB, 6)
    I2C-->>HMC5883L: 6 data bytes
    HMC5883L-->>HMC5883L: convert to signed x, y, z
    HMC5883L->>HMC5883L: readMagneticField
    HMC5883L->>HMC5883L: readHeading
    HMC5883L->>HMC5883L: readMagnitude
    HMC5883L-->>HMC5883LProvider: map with raw and computed values
    deactivate HMC5883L

    HMC5883LProvider->>HMC5883LProvider: update magneticX,Y,Z, heading, magnitude
    HMC5883LProvider->>HMC5883LProvider: append ChartDataPoint to series
    HMC5883LProvider-->>HMC5883LScreen: notifyListeners
    HMC5883LScreen->>HMC5883LScreen: rebuild UI and charts
  end

  User->>HMC5883LScreen: press pause
  HMC5883LScreen->>HMC5883LProvider: toggleDataCollection
  HMC5883LProvider->>HMC5883LProvider: _stopDataCollection (cancel Timer)
  deactivate HMC5883LProvider
Loading

Class diagram for new HMC5883L sensor support

classDiagram
  class HMC5883L {
    <<sensor>>
    +AppLocalizations appLocalizations
    +static int address
    +static int configRegA
    +static int configRegB
    +static int modeReg
    +static int dataXMSB
    +static int dataXLSB
    +static int dataYMSB
    +static int dataYLSB
    +static int dataZMSB
    +static int dataZLSB
    +static int statusReg
    +static int idRegA
    +static int idRegB
    +static int idRegC
    +static int samplesAverage1
    +static int samplesAverage2
    +static int samplesAverage4
    +static int samplesAverage8
    +static int dataRate0_75Hz
    +static int dataRate1_5Hz
    +static int dataRate3Hz
    +static int dataRate7_5Hz
    +static int dataRate15Hz
    +static int dataRate30Hz
    +static int dataRate75Hz
    +static int measurementNormal
    +static int measurementPositiveBias
    +static int measurementNegativeBias
    +static int gain1370
    +static int gain1090
    +static int gain820
    +static int gain660
    +static int gain440
    +static int gain390
    +static int gain330
    +static int gain230
    +static int modeContinuous
    +static int modeSingle
    +static int modeIdle
    +static Map~int,double~ gainScales
    -I2C i2c
    -int currentGain
    -double scale
    -double magneticX
    -double magneticY
    -double magneticZ
    -double offsetX
    -double offsetY
    -double offsetZ
    +HMC5883L._(I2C i2c)
    +static Future~HMC5883L~ create(I2C i2c, ScienceLab scienceLab)
    -Future~void~ _initialize(ScienceLab scienceLab)
    +Future~void~ configure(int samplesAverage, int dataRate, int measurementMode, int gain, int mode)
    +Future~Map~String,int~~ readRawData()
    +Future~Map~String,double~~ readMagneticField()
    +Future~double~ readHeading()
    +Future~double~ readMagnitude()
    +void setCalibrationOffsets(double minX, double maxX, double minY, double maxY, double minZ, double maxZ)
    +Future~Map~String,dynamic~~ getAllData()
    +Future~int~ readStatus()
    +Future~bool~ isDataReady()
    -int _toSignedInt16(int value)
    +Future~void~ setGain(int gain)
    +Future~void~ setMode(int mode)
  }

  class HMC5883LProvider {
    <<ChangeNotifier>>
    +AppLocalizations appLocalizations
    -HMC5883L hmc5883l
    -Timer dataTimer
    -double magneticX
    -double magneticY
    -double magneticZ
    -double heading
    -double magnitude
    -List~ChartDataPoint~ magneticXData
    -List~ChartDataPoint~ magneticYData
    -List~ChartDataPoint~ magneticZData
    -List~ChartDataPoint~ headingData
    -List~ChartDataPoint~ magnitudeData
    -bool isRunning
    -bool isLooping
    -int timegapMs
    -int numberOfReadings
    -int collectedReadings
    -bool isCalibrating
    -double minX
    -double maxX
    -double minY
    -double maxY
    -double minZ
    -double maxZ
    -double currentTime
    -static int maxDataPoints
    +double get magneticX
    +double get magneticY
    +double get magneticZ
    +double get heading
    +double get magnitude
    +List~ChartDataPoint~ get magneticXData
    +List~ChartDataPoint~ get magneticYData
    +List~ChartDataPoint~ get magneticZData
    +List~ChartDataPoint~ get headingData
    +List~ChartDataPoint~ get magnitudeData
    +bool get isRunning
    +bool get isLooping
    +bool get isCalibrating
    +int get timegapMs
    +int get numberOfReadings
    +int get collectedReadings
    +HMC5883LProvider()
    +Future~void~ initializeSensors(Function onError, I2C i2c, ScienceLab scienceLab)
    +void toggleDataCollection()
    -void _startDataCollection()
    -void _stopDataCollection()
    -Future~void~ _fetchSensorData()
    -void _updateCalibrationData(double x, double y, double z)
    +void startCalibration()
    +void stopCalibration()
    +String getCalibrationStatus()
    +void setTimegap(int ms)
    +void setNumberOfReadings(int count)
    +void toggleLooping()
    +void clearData()
    +Future~void~ setGain(int gain)
    +void dispose()
  }

  class HMC5883LScreen {
    <<StatefulWidget>>
    +String sensorImage
    -I2C i2c
    -ScienceLab scienceLab
    -HMC5883LProvider provider
    +HMC5883LScreen()
    +State createState()
  }

  class _HMC5883LScreenState {
    -AppLocalizations appLocalizations
    -String sensorImage
    -I2C i2c
    -ScienceLab scienceLab
    -HMC5883LProvider provider
    +void initState()
    -void _initializeScienceLab()
    -void _showSensorErrorSnackbar(String message)
    -void _showSuccessSnackbar(String message)
    +Widget build(BuildContext context)
    -Widget _buildRawDataSection(HMC5883LProvider provider)
    -Widget _buildCalibrationSection(HMC5883LProvider provider)
    -void _showGainDialog(HMC5883LProvider provider)
    -Widget _buildDataCard(String label, String value)
    +void dispose()
  }

  class ScienceLab {
    +bool isConnected()
    +dynamic mPacketHandler
  }

  class I2C {
    +I2C(dynamic packetHandler)
    +Future~int~ readByte(int address, int register)
    +Future~List~int~~ readBulk(int address, int startRegister, int length)
    +Future~void~ write(int address, List~int~ data, int register)
  }

  class ChartDataPoint {
    +double x
    +double y
    +ChartDataPoint(double x, double y)
  }

  class CommonScaffold {
    +Widget body
    +String title
  }

  class SensorChartWidget {
    +String title
    +String yAxisLabel
    +List~ChartDataPoint~ data
    +Color lineColor
    +String unit
    +int maxDataPoints
    +bool showDots
  }

  class SensorControlsWidget {
    +bool isPlaying
    +bool isLooping
    +int timegapMs
    +int numberOfReadings
    +Function onPlayPause
    +Function onLoop
    +Function onTimegapChanged
    +Function onNumberOfReadingsChanged
    +Function onClearData
  }

  HMC5883LProvider --> HMC5883L : uses
  HMC5883LProvider --> ChartDataPoint : creates
  HMC5883LScreen ..> HMC5883LProvider : ChangeNotifierProvider
  HMC5883LScreen ..> SensorChartWidget : composes
  HMC5883LScreen ..> SensorControlsWidget : composes
  HMC5883LScreen ..> CommonScaffold : uses
  _HMC5883LScreenState --> HMC5883LProvider : holds
  _HMC5883LScreenState --> ScienceLab : uses
  _HMC5883LScreenState --> I2C : uses
  HMC5883L --> I2C : uses
  HMC5883L --> ScienceLab : uses
  SensorsScreen ..> HMC5883LScreen : navigates

  class SensorsScreen {
    +Widget build(BuildContext context)
  }
Loading

File-Level Changes

Change Details Files
Introduce HMC5883L I2C sensor driver with configuration, scaling, heading/magnitude computation, and calibration offsets.
  • Define HMC5883L register addresses, configuration constants (sample averaging, data rate, measurement mode, gain, and modes).
  • Implement async initialization that validates device IDs, configures the sensor, and logs status via the existing logger service.
  • Provide APIs to configure sensor parameters, read raw 16-bit axis data, convert to microTesla using gain-dependent scale, and maintain latest axis values.
  • Implement derived computations for heading (atan2(Y,X) mapped to 0–360°), total field magnitude, status/data-ready accessors, and calibration offsets plus a getAllData helper.
  • Expose gain-setting and mode-setting methods that reconfigure the underlying device safely.
lib/communication/sensors/hmc5883l.dart
Add Provider-based HMC5883L state manager for timed acquisition, calibration, and chart data buffering.
  • Initialize HMC5883L instance from injected I2C and ScienceLab, handling connection errors via localized messages and logging.
  • Maintain current magnetic components, heading, and magnitude, and append them into per-series chart buffers capped at 1000 points with synthetic time axis.
  • Implement periodic sampling via Timer with play/pause, loop vs finite sample count, adjustable interval, and safe restart on config changes.
  • Add interactive calibration flow that tracks min/max per axis during sampling, computes offsets, and applies them to the driver, with textual status reporting.
  • Provide APIs for gain changes, clearing data, and provider teardown that cancels timers to avoid leaks.
lib/providers/hmc5883l_provider.dart
Create HMC5883L sensor UI screen with raw values, calibration controls, and multiple charts, wired into existing navigation.
  • Build HMC5883LScreen as a StatefulWidget that initializes I2C/ScienceLab via locator, then injects an HMC5883LProvider using ChangeNotifierProvider/Consumer.
  • Render a raw data card showing X/Y/Z, heading, and magnitude, with live updates and an optional sensor image fallback icon.
  • Implement a calibration section with figure-8 instructions, live status, start/stop button that toggles provider calibration, and a gain-selection dialog wired to provider.setGain.
  • Add five SensorChartWidget instances plotting magnetic X/Y/Z, heading, and magnitude using provider data and respecting configured max readings.
  • Integrate SensorControlsWidget for play/pause, loop, timegap, reading count, and clear-data actions, plus short snackbars for errors and success events.
  • Update SensorsScreen to import the new screen and route the "HMC5883L" entry to HMC5883LScreen in the sensor selection switch.
lib/view/hmc5883l_screen.dart
lib/view/sensors_screen.dart

Assessment against linked issues

Issue Objective Addressed Explanation
#2991 Implement HMC5883L sensor support in the codebase, including low-level communication/driver and integration with existing infrastructure.
#2991 Expose HMC5883L functionality to users via the application UI, including navigation from the sensors screen.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • The HMC5883LScreen initializes _i2c/_scienceLab asynchronously in _initializeScienceLab, but the ChangeNotifierProvider.create immediately calls initializeSensors with these fields; this can result in a null I2C/ScienceLab race — consider awaiting initialization before creating the provider or passing the dependencies directly once they’re ready.
  • HMC5883L.getAllData redundantly calls readRawData/readMagneticField/readHeading/readMagnitude, each of which can trigger additional I2C reads; refactor to read the sensor once and derive all computed values from that single measurement to reduce bus traffic and latency.
  • In HMC5883LScreen, storing the provider in a field and explicitly calling _provider.dispose() is unnecessary and risks double-dispose — you can rely on ChangeNotifierProvider to manage the lifecycle and access the provider via context.read/context.watch instead.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `HMC5883LScreen` initializes `_i2c`/`_scienceLab` asynchronously in `_initializeScienceLab`, but the `ChangeNotifierProvider.create` immediately calls `initializeSensors` with these fields; this can result in a null I2C/ScienceLab race — consider awaiting initialization before creating the provider or passing the dependencies directly once they’re ready.
- `HMC5883L.getAllData` redundantly calls `readRawData`/`readMagneticField`/`readHeading`/`readMagnitude`, each of which can trigger additional I2C reads; refactor to read the sensor once and derive all computed values from that single measurement to reduce bus traffic and latency.
- In `HMC5883LScreen`, storing the provider in a field and explicitly calling `_provider.dispose()` is unnecessary and risks double-dispose — you can rely on `ChangeNotifierProvider` to manage the lifecycle and access the provider via `context.read`/`context.watch` instead.

## Individual Comments

### Comment 1
<location> `lib/view/hmc5883l_screen.dart:18-27` </location>
<code_context>
+    }
+  }
+
+  @override
+  void dispose() {
+    _dataTimer?.cancel();
</code_context>

<issue_to_address>
**issue (bug_risk):** Manually disposing the provider risks double-dispose with ChangeNotifierProvider.

Let `ChangeNotifierProvider` own the lifecycle and remove any manual `_provider.dispose()` calls from this State’s `dispose` method to avoid double-dispose issues and potential runtime exceptions.
</issue_to_address>

### Comment 2
<location> `lib/communication/sensors/hmc5883l.dart:236-241` </location>
<code_context>
+        "X=$offsetX, Y=$offsetY, Z=$offsetZ");
+  }
+
+  Future<Map<String, dynamic>> getAllData() async {
+    try {
+      final rawData = await readRawData();
+      await readMagneticField();
+      final heading = await readHeading();
+      final magnitude = await readMagnitude();
+
+      return {
</code_context>

<issue_to_address>
**suggestion (performance):** getAllData performs multiple redundant I2C reads, which is inefficient.

`getAllData` calls `readRawData()`, then `readMagneticField()`, and then `readHeading()` and `readMagnitude()`, both of which call `readMagneticField()` (and thus `readRawData()`) again. This causes multiple I2C transactions per fetch. Consider restructuring so raw data is read once and used to compute magnetic field, heading, and magnitude, e.g. via a helper that accepts the raw data or `magneticX/Y/Z`. This will reduce bus traffic and latency for periodic sampling.

Suggested implementation:

```
      final magneticField = await readMagneticField(rawData: rawData);
      final heading = await readHeading(magneticField: magneticField);
      final magnitude = await readMagnitude(magneticField: magneticField);

```

To fully implement this optimization, you should also adjust the sensor-reading methods so they can reuse already-fetched data instead of always hitting the I2C bus:

1. **Update `readMagneticField` to accept raw data**:

   - Change the signature roughly from:
   ```dart
   Future<Map<String, double>> readMagneticField() async {
   ```
   to:
   ```dart
   Future<Map<String, double>> readMagneticField({Map<String, int>? rawData}) async {
   ```
   - Inside, replace the internal `final rawData = await readRawData();` with:
   ```dart
   final data = rawData ?? await readRawData();
   ```
   - Then use `data['x']`, `data['y']`, `data['z']` instead of the previous local `rawData` variable.

2. **Update `readHeading` to accept a precomputed magnetic field**:

   - Change the signature from something like:
   ```dart
   Future<double> readHeading() async {
   ```
   to:
   ```dart
   Future<double> readHeading({Map<String, double>? magneticField}) async {
   ```
   - Replace the internal `final magneticField = await readMagneticField();` with:
   ```dart
   final field = magneticField ?? await readMagneticField();
   ```
   - Use `field['x']`, `field['y']` (and `field['z']` if needed) where you previously used `magneticField`.

3. **Update `readMagnitude` similarly**:

   - Change the signature from:
   ```dart
   Future<double> readMagnitude() async {
   ```
   to:
   ```dart
   Future<double> readMagnitude({Map<String, double>? magneticField}) async {
   ```
   - Replace `final magneticField = await readMagneticField();` with:
   ```dart
   final field = magneticField ?? await readMagneticField();
   ```
   - Use `field` instead of the old local variable.

4. **Check call sites**:
   - Existing call sites that do not pass parameters (e.g. `await readMagneticField()`, `await readHeading()`, `await readMagnitude()`) will still work because the new parameters are optional and default to doing the same I2C reads as before.
   - The only place that will now avoid redundant I2C reads is `getAllData`, which passes `rawData` and `magneticField` explicitly as shown in the edit block above.

These changes ensure that `getAllData` performs a single raw I2C read and reuses that data through the stack, reducing bus traffic and improving sampling latency without breaking existing usages.
</issue_to_address>

### Comment 3
<location> `lib/view/hmc5883l_screen.dart:464-476` </location>
<code_context>
+      context: context,
+      builder: (context) => AlertDialog(
+        title: const Text('Select Gain'),
+        content: Column(
+          mainAxisSize: MainAxisSize.min,
+          children: gains.entries.map((entry) {
+            return ListTile(
+              title: Text(entry.key),
+              onTap: () {
+                provider.setGain(entry.value);
+                Navigator.pop(context);
+                _showSuccessSnackbar('Gain set to ${entry.key}');
+              },
+            );
+          }).toList(),
+        ),
+        actions: [
</code_context>

<issue_to_address>
**suggestion:** Gain selection dialog content can overflow vertically when the list is long.

Because the dialog’s `content` is a `Column` with all `gains.entries` as `ListTile`s, it can exceed the available height on small screens or with larger text scales and overflow. Consider constraining and scrolling the list (e.g. wrap it in a `SizedBox` with a `ListView` or `SingleChildScrollView`) so the dialog remains usable on smaller viewports and with accessibility settings.

```suggestion
        content: ConstrainedBox(
          constraints: const BoxConstraints(
            // Limit max height so the dialog remains usable on small screens
            maxHeight: 300,
          ),
          child: ListView(
            shrinkWrap: true,
            children: gains.entries.map((entry) {
              return ListTile(
                title: Text(entry.key),
                onTap: () {
                  provider.setGain(entry.value);
                  Navigator.pop(context);
                  _showSuccessSnackbar('Gain set to ${entry.key}');
                },
              );
            }).toList(),
          ),
        ),
```
</issue_to_address>

### Comment 4
<location> `lib/providers/hmc5883l_provider.dart:89-90` </location>
<code_context>
+      logger.d("HMC5883L sensor initialized successfully");
+      notifyListeners();
+    } catch (e) {
+      logger.e('Error initializing HMC5883L: $e');
+      onError('Error initializing HMC5883L: $e');
+    }
+  }
</code_context>

<issue_to_address>
**suggestion:** Error message mixes localized and raw text, which can be inconsistent for users.

Here you pass a hard-coded English string with the exception to `onError`, whereas elsewhere you use `appLocalizations.pslabNotConnected`. For consistency and proper localization, use a localized base message here as well and, if needed, append `e.toString()` in a structured way or map common failures to localized user-friendly messages.

Suggested implementation:

```
    } catch (e, stackTrace) {
      // Log a structured error with exception and stack trace
      logger.e('Error initializing HMC5883L', e, stackTrace);

      // Use a localized base message and append the raw error in a structured way
      final baseMessage = appLocalizations.hmc5883lInitError;
      onError('$baseMessage\n(${e.toString()})');
    }

```

1. Add a new localization key such as `hmc5883lInitError` to your ARB/JSON localization files, e.g. `"hmc5883lInitError": "Error initializing HMC5883L sensor"`, and regenerate `appLocalizations`.
2. If your logger's `e` method does not accept `(message, error, stackTrace)`, adjust the call to match its existing signature while still logging both the message and `e`.
3. If you prefer not to expose raw exception text to end users, replace `\n(${e.toString()})` with a more user-friendly localized hint or omit it entirely.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

- Fix redundant I2C reads in getAllData by accepting optional parameters in readMagneticField, readHeading, and readMagnitude methods
- Optimize sensor data collection to perform single I2C read and reuse data through computation stack
- Add scrollable ConstrainedBox to gain selection dialog to prevent overflow on small screens
- Improve error message localization using magnetometerError from app localizations
- Fix provider lifecycle management by removing manual disposal and letting ChangeNotifierProvider handle it
- Remove unnecessary _provider field storage in HMC5883LScreen

Addresses review comments:
- Performance: Reduces bus traffic and latency for periodic sampling
- UI: Dialog remains usable on all screen sizes with accessibility settings
- UX: Consistent localized error messages
- Architecture: Proper provider lifecycle management
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for sensor HMC5883L

1 participant