feat: implement dust sensor (SDS011 - UART interface)#3245
Conversation
|
I just noticed that I do not own all sensors which were supported by the old version of the app. I ordered an SDS011 to review this PR. |
|
See #3246 regarding the failing Windows build. |
Build StatusBuild successful. APKs to test: https://github.com/fossasia/pslab-app/actions/runs/28704449805/artifacts/8081252966. Screenshots |
rahul31124
left a comment
There was a problem hiding this comment.
Hello @ctrlVnt,
How will you test this? Please refer to the legacy app to see how it was implemented there
Link:
https://github.com/fossasia/pslab-app/blob/app/pslab-development-release.apk
|
I never used this feature in the old app, so I am not sure if it ever worked. The sensor comes with a USB dongle and I am curious if it can also be connected to the PSLab at all. |
Hi @rahul31124, I can't test for now because I still don't have PSLab pocket but I copied the same logic of sound meter for UI and, if it doesn't work, we need to implement a support for the sensor. Now I use .getVoltage("CH1", 1) method to read sensor but I don't expect that it works (copyed from Kotlin version this too) |
|
Can be interesting to test in old version if it worked well |
|
I am still waiting for the sensor... |
There was a problem hiding this comment.
Hello @ctrlVnt,You have implemented the SDS011 dust sensor, which is UART-based, but the dust sensor we had earlier was the DSM-501A PWM based, @marcnause which one should we use?🙂
|
@ctrlVnt I think the data sheet I mentioned earlier only contains half the the truth. There is also a "Laser Dust Sensor Control Protocol"-document: https://learn.watterott.com/sensors/sds011/sds011_protocol.pdf The factory default is continuous measurement mode after startup, but I think we should set the desired mode (see chapter 5 of the document) in case somebody uses a sensor with a different configuration (I am not sure if the working period is persistent). Also I noticed that the data frame is mentioned to be set to 8 data bit, no parity, one stop bit (8N1) in the document. Our communication layer does not support setting this, but the PSLab-Python library does: https://github.com/fossasia/pslab-python/blob/main/pslab/bus/uart.py#L107C9-L107C23 I tried adding it to our communication layer, but it did not make a difference. (Maybe I made a mistake.) I am not sure how to continue. You don't have the sensor, I don't have the time to really dig into the subject. How about me sending you the sensor and you send it back to me for the review of the PR once you are done? The sensor is relatively expensive (> 25 €) and even though I thought I ordered it from Germany, it was dropshipped to me from China which took longer than I expected. I guess that sending it to you from Germany would be fairly fast. If you think this is a reasonable idea, please contact me via Telegram and provide me with an address I can send it to. I will pay for the return postage. |
|
The sensor is on its way! |
There was a problem hiding this comment.
Pull request overview
Adds a new Dust Sensor (SDS011 via UART2) instrument to the Flutter PSLab app, including a dedicated screen/UI, configuration UI, and the underlying UART2 communication support needed to fetch and visualize PM readings.
Changes:
- Introduces Dust Sensor UI (screen + card) with live PM2.5 visualization, playback support, and data export flow.
- Adds Dust Sensor state/config providers and a persisted configuration model.
- Extends the PSLab communication layer with UART2 configuration/status/read helpers and wires the new instrument into routing + localization.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| lib/view/widgets/dust_sensor_card.dart | New UI card for displaying PM2.5 and air quality status with a gauge. |
| lib/view/instruments_screen.dart | Adds Dust Sensor entry to the instruments list. |
| lib/view/dust_sensor_screen.dart | New Dust Sensor main screen with charting, guide, logging/export, and config navigation. |
| lib/view/dust_sensor_config_screen.dart | New configuration screen for dust sensor settings. |
| lib/providers/dust_sensor_state_provider.dart | New state provider handling UART2 reads, chart data, recording, and playback. |
| lib/providers/dust_sensor_config_provider.dart | New persisted config provider using SharedPreferences. |
| lib/models/dust_sensor_config.dart | New config model (update period, limits, active sensor, location logging). |
| lib/main.dart | Registers the /dustsensor route. |
| lib/l10n/app_en.arb | Adds dust sensor intro/config strings and air-quality labels. |
| lib/communication/science_lab.dart | Adds UART2 configuration/status/read methods required for SDS011. |
| lib/communication/packet_handler.dart | Fixes an incorrect log message in getByte(). |
| void initializeSensors({Function(String)? onError}) async { | ||
| onSensorError = onError; | ||
|
|
||
| try { | ||
| ScienceLab scienceLab = getIt.get<ScienceLab>(); | ||
|
|
||
| await scienceLab.configureUART(baudRate: 9600); | ||
| await Future.delayed(const Duration(milliseconds: 500)); | ||
|
|
| _startTime = DateTime.now().millisecondsSinceEpoch / 1000.0; | ||
|
|
||
| _timeTimer = Timer.periodic(const Duration(seconds: 1), (timer) { | ||
| _currentTime = | ||
| (DateTime.now().millisecondsSinceEpoch / 1000.0) - _startTime; | ||
| _updateData(); | ||
| notifyListeners(); | ||
| }); |
| void setConfigProvider(DustSensorConfigProvider configProvider) { | ||
| _configProvider = configProvider; | ||
| } |
| void _handleSensorError(dynamic error) { | ||
| onSensorError?.call("Unable to access SDS011 sensor via UART"); | ||
| logger.e("Dust sensor error: $error"); | ||
| } |
| _updatePeriodController.text = | ||
| provider.config.updatePeriod.toString(); | ||
| _highLimitController.text = provider.config.highLimit.toString(); |
| ConfigInputItem( | ||
| title: appLocalizations.highLimit, | ||
| value: | ||
| "${provider.config.highLimit} ${appLocalizations.ppm}", | ||
| controller: _highLimitController, | ||
| hint: appLocalizations.dustHighLimitHint, | ||
| onChanged: (value) { | ||
| try { | ||
| double highLimit = double.parse(value); | ||
| if (highLimit > 5.0 || highLimit < 0.0) { | ||
| throw const FormatException(); |
| "manualMode": "Manual Mode", | ||
| "frequencyChange": "Stop playback to change frequency.", | ||
| "playBackStop": "Playback stopped", | ||
| "dustSensorIntro": "Connect pins according to pin diagram and start measuring. Power up the sensor using VCC pin in PSLab and connect data pin to CH1. Make sure the ground wire is connected to one of the GND pins in PSLab.", |
| Future<void> configureUART({required int baudRate}) async { | ||
| if (!isConnected()) return; | ||
|
|
||
| try { | ||
| mPacketHandler.sendByte(mCommandsProto.uart2); | ||
| mPacketHandler.sendByte(mCommandsProto.setBaud); | ||
| mPacketHandler.sendInt(baudRate); |
# Conflicts: # lib/communication/science_lab.dart # lib/providers/dust_sensor_state_provider.dart
|
I just had a look at the old logs I posted previously and I noticed, that I received timeouts. fossasia/pslab-firmware#206 also mentions timeouts. @rahul31124 Is it possible that we have the same or a similar problem here? @ctrlVnt Does using Rahul's changed firmware (Google Drive link in #3395) make a difference? |
I didn't expect you to, sorry if it came across that way. I just wanted to jot down the idea. |







-1_instruments_screen.png?raw=true)
-2_nav_drawer.png?raw=true)
-3_accelerometer.png?raw=true)
-4_power_source.png?raw=true)
-5_multimeter.png?raw=true)
-6_wave_generator.png?raw=true)
-7_oscilloscope.png?raw=true)

Fixes #2988
Changes
Screenshots / Recordings
Screencast.From.2026-05-20.13-46-29.webm
Checklist:
constants.dartor localization files instead of hard-coded values.dart formator the IDE formatter.flutter analyzeand tests run influtter test.