Skip to content

Conversation

@rahul31124
Copy link
Contributor

@rahul31124 rahul31124 commented Sep 14, 2025

Changes

  • Implemented BH1750 and TSL2561 light sensor classes.
  • Updated Lux Meter state provider to handle configuration changes:
    • Active sensor
    • High limit
    • Update period
    • Sensor gain
  • Ensures configuration changes are properly applied, which were not implemented previously.

Recording

lux_meter.mp4

Summary by Sourcery

Add support for BH1750 and TSL2561 external light sensors and extend the LuxMeter state and config providers to apply active sensor selection, update period, and gain settings dynamically with proper persistence and error handling.

New Features:

  • Integrate BH1750 and TSL2561 light sensor drivers over I2C with ScienceLab communication
  • Allow runtime selection between in-built, BH1750, and TSL2561 sensors via config provider
  • Expose update period and sensor gain settings for periodic lux readings

Enhancements:

  • Reinitialize sensors and reset measurement data on configuration changes
  • Persist luxmeter configuration to SharedPreferences with debug logging and error handling
  • Clean up sensor resources in LuxMeterStateProvider.disposeSensors

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Sep 14, 2025

Reviewer's Guide

This PR enriches the LuxMeter feature by refactoring the state provider to dynamically initialize and switch between in-built, BH1750, and TSL2561 sensors based on configuration, adding dedicated driver classes for both new sensors, and improving configuration persistence and UI integration.

Sequence diagram for sensor initialization and switching in LuxMeterStateProvider

sequenceDiagram
    participant UI
    participant LuxMeterStateProvider
    participant LuxMeterConfigProvider
    participant BH1750
    participant TSL2561
    participant Light
    participant ScienceLab
    participant I2C
    UI->>LuxMeterConfigProvider: updateActiveSensor(sensorType)
    LuxMeterConfigProvider-->>LuxMeterStateProvider: notifyListeners()
    LuxMeterStateProvider->>LuxMeterStateProvider: _onConfigChanged()
    alt In-built Sensor
        LuxMeterStateProvider->>Light: initializeInbuiltSensor()
    else BH1750
        LuxMeterStateProvider->>ScienceLab: getIt<ScienceLab>()
        LuxMeterStateProvider->>I2C: new I2C(scienceLab.mPacketHandler)
        LuxMeterStateProvider->>BH1750: new BH1750(i2c)
        LuxMeterStateProvider->>BH1750: setRange(gain)
    else TSL2561
        LuxMeterStateProvider->>ScienceLab: getIt<ScienceLab>()
        LuxMeterStateProvider->>I2C: new I2C(scienceLab.mPacketHandler)
        LuxMeterStateProvider->>TSL2561: new TSL2561(i2c, scienceLab)
        LuxMeterStateProvider->>TSL2561: setGain(gain)
    end
Loading

Class diagram for new and updated LuxMeter sensor classes

classDiagram
    class LuxMeterStateProvider {
        - double _currentLux
        - Light? _light
        - I2C? _i2c
        - ScienceLab? _scienceLab
        - BH1750? _bh1750
        - TSL2561? _tsl2561
        - LuxMeterConfigProvider? _configProvider
        + setConfigProvider(configProvider)
        + initializeInbuiltSensor(onError)
        + initializeBH1750Sensor(onError)
        + initializeTSL2561Sensor(onError)
        + disposeSensors()
        + _onConfigChanged()
        + _resetLuxData()
    }
    class BH1750 {
        + BH1750(i2c)
        + init()
        + setRange(g)
        + getVals(numBytes)
        + getRaw()
        - I2C i2c
        - int address
    }
    class TSL2561 {
        + TSL2561(i2c, scienceLab)
        + getID()
        + getRaw()
        + setGain(gainValue)
        + enable()
        + disable()
        - I2C i2c
        - int address
        - int timing
        - int gain
    }
    LuxMeterStateProvider --> BH1750
    LuxMeterStateProvider --> TSL2561
    LuxMeterStateProvider --> Light
    LuxMeterStateProvider --> I2C
    LuxMeterStateProvider --> ScienceLab
    BH1750 --> I2C
    TSL2561 --> I2C
    TSL2561 --> ScienceLab
Loading

Class diagram for updated LuxMeterConfigProvider

classDiagram
    class LuxMeterConfigProvider {
        - LuxMeterConfig _config
        + updateConfig(newConfig)
        + updateActiveSensor(activeSensor)
        + _loadConfigFromPrefs()
        + _saveConfigToPrefs()
    }
    LuxMeterConfigProvider --> LuxMeterConfig
Loading

File-Level Changes

Change Details Files
Dynamic sensor initialization and polling in state provider
  • Added _onConfigChanged listener to reinitialize sensors on config updates
  • Implemented initializeInbuiltSensor, initializeBH1750Sensor, initializeTSL2561Sensor with I2C setup, timers, gain and interval application
  • Introduced disposeSensors and _resetLuxData to clean up before switching sensors
  • Refactored polling loops to use configurable updatePeriod and sensorGain settings
lib/providers/luxmeter_state_provider.dart
New BH1750 and TSL2561 driver implementations
  • Created BH1750 class with setRange, init and getRaw methods
  • Added TSL2561 class to scan I2C addresses, control power, set gain, and compute raw lux
  • Integrated logger debug/error statements throughout both sensor drivers
lib/communication/sensors/bh1750.dart
lib/communication/sensors/tsl2561.dart
Enhanced configuration provider persistence and validation
  • Wrapped SharedPreferences load/save in try/catch with logger outputs
  • Emitted debug logs when loading or saving LuxMeterConfig
  • Added validation for activeSensor values to fallback to default
lib/providers/luxmeter_config_provider.dart
UI integration and error callback assignment
  • Moved sensor setup into postFrameCallback in LuxMeterScreen
  • Assigned onSensorError callback before any initialization
  • Removed direct initializeSensors call in favor of config-driven init
lib/view/luxmeter_screen.dart

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:

  • Consider replacing the string literals for sensor types with a dedicated enum or constants to ensure type safety and simplify sensor-selection logic.
  • You have nearly identical polling/setup code in both BH1750 and TSL2561; extract the shared Timer-based read/update logic into a common helper or base class to reduce duplication.
  • Avoid using an immediately‐invoked async closure in TSL2561’s constructor—move the address discovery and initialization into a separate async init() method for clearer sequencing and error handling.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Consider replacing the string literals for sensor types with a dedicated enum or constants to ensure type safety and simplify sensor-selection logic.
- You have nearly identical polling/setup code in both BH1750 and TSL2561; extract the shared Timer-based read/update logic into a common helper or base class to reduce duplication.
- Avoid using an immediately‐invoked async closure in TSL2561’s constructor—move the address discovery and initialization into a separate async init() method for clearer sequencing and error handling.

## Individual Comments

### Comment 1
<location> `lib/communication/sensors/bh1750.dart:45` </location>
<code_context>
+
+  Future<double> getRaw() async {
+    List<int> vals = await getVals(2);
+    if (vals.length == 3) {
+      return ((vals[0] << 8) | vals[1]) / 1.2;
+    } else {
</code_context>

<issue_to_address>
Possible off-by-one error in getRaw: expects 3 bytes, but BH1750 typically returns 2.

The method checks for three bytes, but the sensor returns two. This could cause incorrect results. Please confirm the expected response length and update the condition if needed.
</issue_to_address>

### Comment 2
<location> `lib/communication/sensors/tsl2561.dart:66` </location>
<code_context>
+    try {
+      List<int> idList = await i2c.readBulk(address, registerId, 1);
+      if (idList.isEmpty) return -1;
+      int id = int.parse(idList[0].toRadixString(16), radix: 16);
+      logger.d("$tag: ID: $id");
+      return id;
</code_context>

<issue_to_address>
Unnecessary conversion in getID: int.parse on a value already an int.

The conversion from int to hex string and back to int is unnecessary; you can return idList[0] directly.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
      int id = int.parse(idList[0].toRadixString(16), radix: 16);
      logger.d("$tag: ID: $id");
      return id;
=======
      int id = idList[0];
      logger.d("$tag: ID: $id");
      return id;
>>>>>>> REPLACE

</suggested_fix>

### Comment 3
<location> `lib/providers/luxmeter_state_provider.dart:145` </location>
<code_context>
+
+      int lastUpdate = 0;
+
+      _luxTimer = Timer.periodic(Duration(milliseconds: 10), (timer) async {
+        try {
+          final lux = await _bh1750?.getRaw();
</code_context>

<issue_to_address>
High-frequency polling (10ms) may impact performance.

Polling sensors every 10ms may cause excessive CPU and battery usage. Consider matching the polling interval to the updatePeriod or implementing an adaptive strategy.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
      _luxTimer = Timer.periodic(Duration(milliseconds: 10), (timer) async {
=======
      _luxTimer = Timer.periodic(Duration(milliseconds: intervalMs), (timer) async {
>>>>>>> REPLACE

</suggested_fix>

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.

Comment on lines +66 to +68
int id = int.parse(idList[0].toRadixString(16), radix: 16);
logger.d("$tag: ID: $id");
return id;
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: Unnecessary conversion in getID: int.parse on a value already an int.

The conversion from int to hex string and back to int is unnecessary; you can return idList[0] directly.

Suggested change
int id = int.parse(idList[0].toRadixString(16), radix: 16);
logger.d("$tag: ID: $id");
return id;
int id = idList[0];
logger.d("$tag: ID: $id");
return id;


int lastUpdate = 0;

_luxTimer = Timer.periodic(Duration(milliseconds: 10), (timer) async {
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (performance): High-frequency polling (10ms) may impact performance.

Polling sensors every 10ms may cause excessive CPU and battery usage. Consider matching the polling interval to the updatePeriod or implementing an adaptive strategy.

Suggested change
_luxTimer = Timer.periodic(Duration(milliseconds: 10), (timer) async {
_luxTimer = Timer.periodic(Duration(milliseconds: intervalMs), (timer) async {

@rahul31124 rahul31124 requested a review from AsCress September 14, 2025 10:23
@AsCress
Copy link
Collaborator

AsCress commented Sep 14, 2025

@rahul31124 Looks like there's a minor analysis issue. Could you check it please ?

@github-actions
Copy link
Contributor

github-actions bot commented Sep 14, 2025

Build Status

Build successful. APKs to test: https://github.com/fossasia/pslab-app/actions/runs/17795774306/artifacts/4033786100.

Screenshots

Android Screenshots
iPhone Screenshots
iPad Screenshots

Copy link
Collaborator

@AsCress AsCress left a comment

Choose a reason for hiding this comment

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

I don't have these sensors but LGTM !
CC @marcnause

Copy link
Member

@mariobehling mariobehling left a comment

Choose a reason for hiding this comment

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

Merging, but this needs to be tested in production further and we should probably have an option to choose the update time of the measurements in future.

@AsCress AsCress merged commit d15d6ff into fossasia:flutter Sep 17, 2025
12 checks passed
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.

4 participants