Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions lib/communication/sensors/sht21.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import 'dart:async';
import '../peripherals/i2c.dart';

class SHT21 {
final I2C i2c;
static const int address = 0x40;

// Commands (Use Hold Master Mode for readBulk compatibility)
static const int tempHoldCmd = 0xE3;
static const int humidityHoldCmd = 0xE5;

SHT21(this.i2c);

Future<double> getTemperature() async {
// FIX: Use readBulk to handle the Write -> Restart -> Read sequence automatically.
// This avoids the argument errors with .write() and .read()
List<int> data = await i2c.readBulk(address, tempHoldCmd, 3);

if (data.length < 2) {
throw Exception("Failed to read temperature from SHT21");
}

// Mask out status bits (last 2 bits) using & 0xFC
int rawValue = (data[0] << 8) | (data[1] & 0xFC);

// Formula: T = -46.85 + 175.72 * (S_T / 2^16)
return -46.85 + 175.72 * (rawValue / 65536.0);
}

Future<double> getHumidity() async {
// FIX: Use readBulk here too
List<int> data = await i2c.readBulk(address, humidityHoldCmd, 3);

if (data.length < 2) {
throw Exception("Failed to read humidity from SHT21");
}

// Mask out status bits
int rawValue = (data[0] << 8) | (data[1] & 0xFC);

// Formula: RH = -6 + 125 * (S_RH / 2^16)
return -6.0 + 125.0 * (rawValue / 65536.0);
}
}
3 changes: 2 additions & 1 deletion lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -523,5 +523,6 @@
"light": "Light",
"darkExperimental": "Dark (Experimental)",
"system": "System",
"shareApp": "Share App"
"shareApp": "Share App",
"sht21Config": "SHT21 Configurations"
}
5 changes: 5 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:pslab/providers/sht21_provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:provider/provider.dart';
Expand Down Expand Up @@ -42,6 +43,10 @@ void main() {
ChangeNotifierProvider<BoardStateProvider>(
create: (context) => getIt<BoardStateProvider>(),
),
ChangeNotifierProvider<SHT21Provider>(
// FIX: Use getIt to resolve the provider instead of 'SHT21Provider()'
create: (context) => getIt<SHT21Provider>(),
),
],
child: const MyApp(),
),
Expand Down
2 changes: 2 additions & 0 deletions lib/providers/locator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'package:pslab/communication/science_lab.dart';
import 'package:pslab/communication/socket_client.dart';
import 'package:pslab/others/science_lab_common.dart';
import 'package:pslab/providers/board_state_provider.dart';
import 'package:pslab/providers/sht21_provider.dart';

final GetIt getIt = GetIt.instance;

Expand All @@ -29,6 +30,7 @@ void setupLocator() {
getIt.registerLazySingleton<ScienceLab>(
() => getIt.get<ScienceLabCommon>().getScienceLab());
getIt.registerLazySingleton<BoardStateProvider>(() => BoardStateProvider());
getIt.registerLazySingleton<SHT21Provider>(() => SHT21Provider());
}

void registerAppLocalizations(AppLocalizations appLocalizations) {
Expand Down
54 changes: 54 additions & 0 deletions lib/providers/sht21_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import '../communication/peripherals/i2c.dart';
import '../communication/sensors/sht21.dart';

class SHT21Provider extends ChangeNotifier {
SHT21? _sensor;
Timer? _timer;

// These are the public variables the Screen is trying to read
double temperature = 0.0;
double humidity = 0.0;

bool isRecording = false;

void init(I2C i2c) {
_sensor = SHT21(i2c);
}

void startDataLog() {
if (_sensor == null || isRecording) return;

isRecording = true;
_timer = Timer.periodic(const Duration(milliseconds: 1000), (timer) async {
try {
// Fetch new values
double temp = await _sensor!.getTemperature();
double hum = await _sensor!.getHumidity();

// Update variables
temperature = temp;
humidity = hum;

// Notify the screen to update
notifyListeners();
} catch (e) {
if (kDebugMode) {
print("SHT21 Error: $e");
}
}
});
}

void stopDataLog() {
_timer?.cancel();
isRecording = false;
}

@override
void dispose() {
stopDataLog();
super.dispose();
}
}
5 changes: 5 additions & 0 deletions lib/view/sensors_screen.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:pslab/view/sht21_screen.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:pslab/view/bmp180_screen.dart';
Expand Down Expand Up @@ -229,9 +230,13 @@ class _SensorsScreenState extends State<SensorsScreen> {
break;
case 'APDS9960':
targetScreen = const APDS9960Screen();
break;
case 'VL53L0X':
targetScreen = const VL53L0XScreen();
break;
case 'SHT21':
targetScreen = const SHT21Screen();
break;
default:
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
Expand Down
123 changes: 123 additions & 0 deletions lib/view/sht21_screen.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:pslab/communication/science_lab.dart';
import 'package:pslab/communication/peripherals/i2c.dart';
import 'package:pslab/providers/locator.dart';
import 'package:pslab/l10n/app_localizations.dart';
import '../providers/sht21_provider.dart';

class SHT21Screen extends StatefulWidget {
const SHT21Screen({super.key});

@override
State<SHT21Screen> createState() => _SHT21ScreenState();
}

class _SHT21ScreenState extends State<SHT21Screen> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return;

final provider = Provider.of<SHT21Provider>(context, listen: false);
final scienceLab = getIt<ScienceLab>();
final appLocalizations = AppLocalizations.of(context)!;

if (scienceLab.isConnected()) {
I2C i2c = I2C(scienceLab.mPacketHandler);
provider.init(i2c);
provider.startDataLog();
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(appLocalizations.notConnected)),
);
}
});
}

@override
void dispose() {
if (mounted) {
Provider.of<SHT21Provider>(context, listen: false).stopDataLog();
}
super.dispose();
}

@override
Widget build(BuildContext context) {
final appLocalizations = AppLocalizations.of(context)!;

return Scaffold(
appBar: AppBar(
title: const Text('SHT21 Sensor'),
),
body: Consumer<SHT21Provider>(
builder: (context, provider, child) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
_buildSensorCard(
// Using the localized string we added earlier
title: appLocalizations.temperature,
value: provider.temperature.toStringAsFixed(2),
unit: "°C",
icon: Icons.thermostat,
color: Colors.redAccent,
),
const SizedBox(height: 20),
_buildSensorCard(
title: "Humidity",
value: provider.humidity.toStringAsFixed(2),
unit: "%",
icon: Icons.water_drop,
color: Colors.blueAccent,
),
],
),
);
},
),
);
}

Widget _buildSensorCard({
required String title,
required String value,
required String unit,
required IconData icon,
required Color color,
}) {
return Card(
elevation: 4,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Row(
children: [
Icon(icon, size: 40, color: color),
const SizedBox(width: 20),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title,
style: const TextStyle(fontSize: 16, color: Colors.grey)),
Row(
children: [
Text(value,
style: const TextStyle(
fontSize: 32, fontWeight: FontWeight.bold)),
const SizedBox(width: 5),
Text(unit, style: const TextStyle(fontSize: 20)),
],
),
],
),
),
],
),
),
);
}
}
Loading