Skip to content

feat: ported thermometer screen. #2761

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: flutter
Choose a base branch
from
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
4 changes: 4 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.BODY_SENSORS" />
<uses-feature android:name="android.hardware.usb.host" />
<uses-feature
android:name="android.hardware.sensor.ambient_temperature"
android:required="false" />
<application
android:label="PSLab"
android:name="${applicationName}"
Expand Down
170 changes: 168 additions & 2 deletions android/app/src/main/java/io/pslab/MainActivity.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,172 @@
package io.pslab;

import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.util.Log;

import androidx.annotation.NonNull;

import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;

public class MainActivity extends FlutterActivity implements SensorEventListener {
private static final String TEMPERATURE_CHANNEL = "io.pslab/temperature";
private static final String TEMPERATURE_STREAM = "io.pslab/temperature_stream";
private static final String TAG = "MainActivity";

private SensorManager sensorManager;
private Sensor temperatureSensor;
private MethodChannel temperatureChannel;
private EventChannel temperatureEventChannel;
private EventChannel.EventSink temperatureEventSink;
private boolean isListening = false;
private float currentTemperature = 0.0f;

@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);

sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
if (sensorManager != null) {
temperatureSensor = sensorManager.getDefaultSensor(Sensor.TYPE_AMBIENT_TEMPERATURE);
}

temperatureChannel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), TEMPERATURE_CHANNEL);
temperatureChannel.setMethodCallHandler(this::handleMethodCall);

temperatureEventChannel = new EventChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), TEMPERATURE_STREAM);
temperatureEventChannel.setStreamHandler(new EventChannel.StreamHandler() {
@Override
public void onListen(Object arguments, EventChannel.EventSink events) {
temperatureEventSink = events;
startTemperatureUpdates();
}

@Override
public void onCancel(Object arguments) {
temperatureEventSink = null;
stopTemperatureUpdates();
}
});
}

private void handleMethodCall(MethodCall call, MethodChannel.Result result) {
switch (call.method) {
case "isTemperatureSensorAvailable":
result.success(temperatureSensor != null);
break;
case "getCurrentTemperature":
result.success((double) currentTemperature);
break;
case "startTemperatureUpdates":
if (startTemperatureUpdates()) {
result.success(true);
} else {
result.error("SENSOR_ERROR", "Failed to start temperature updates", null);
}
break;
case "stopTemperatureUpdates":
stopTemperatureUpdates();
result.success(true);
break;
default:
result.notImplemented();
break;
}
}

private boolean startTemperatureUpdates() {
if (temperatureSensor == null || sensorManager == null) {
Log.e(TAG, "Temperature sensor not available");
return false;
}

if (!isListening) {
boolean registered = sensorManager.registerListener(this, temperatureSensor, SensorManager.SENSOR_DELAY_NORMAL);
if (registered) {
isListening = true;
Log.d(TAG, "Temperature sensor listener registered");

if (currentTemperature != 0.0f && temperatureEventSink != null) {
Log.d(TAG, "Sending initial temperature to Flutter: " + currentTemperature);
temperatureEventSink.success((double) currentTemperature);
}

return true;
} else {
Log.e(TAG, "Failed to register temperature sensor listener");
return false;
}
}
return true;
}

private void stopTemperatureUpdates() {
if (isListening && sensorManager != null) {
sensorManager.unregisterListener(this, temperatureSensor);
isListening = false;
Log.d(TAG, "Temperature sensor listener unregistered");
}
}

@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_AMBIENT_TEMPERATURE) {
float temperature = event.values[0];

if (isValidTemperature(temperature)) {
currentTemperature = temperature;
Log.d(TAG, "Temperature updated: " + currentTemperature + "°C");

if (temperatureEventSink != null) {
Log.d(TAG, "Sending temperature to Flutter: " + currentTemperature);
temperatureEventSink.success((double) currentTemperature);
}
} else {
Log.w(TAG, "Invalid temperature reading: " + temperature + " - ignoring");
}
}
}

private boolean isValidTemperature(float temperature) {
if (Float.isNaN(temperature) || Float.isInfinite(temperature)) return false;
if (temperature < -273.15f) return false;
if (temperature > 200f) return false;
if (Math.abs(temperature) > 1e10f) return false;
return true;
}

@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
Log.d(TAG, "Sensor accuracy changed: " + accuracy);
}

@Override
protected void onDestroy() {
super.onDestroy();
stopTemperatureUpdates();
}

@Override
protected void onPause() {
super.onPause();
if (isListening && sensorManager != null) {
sensorManager.unregisterListener(this);
}
}

public class MainActivity extends FlutterActivity {
}
@Override
protected void onResume() {
super.onResume();
if (isListening && temperatureSensor != null && sensorManager != null) {
sensorManager.registerListener(this, temperatureSensor, SensorManager.SENSOR_DELAY_NORMAL);
}
}
}
9 changes: 9 additions & 0 deletions lib/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,15 @@ String buyPsLabMenu = 'Buy PSLab';
String faqMenu = 'FAQ';
String shareAppMenu = 'Share App';
String privacyPolicyMenu = 'Privacy Policy';
String thermometerTitle = 'Thermometer';
String thermometerIntro =
'Thermometer instrument is used to measure ambient temprature. It can be measured using inbuilt ambient temprature sensor or through SHT21.';
Comment on lines +370 to +371
Copy link

Choose a reason for hiding this comment

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

issue (typo): Typo in 'temprature' should be 'temperature'.

Please correct all instances of 'temprature' to 'temperature' in the thermometerIntro string to avoid user confusion.

Suggested change
String thermometerIntro =
'Thermometer instrument is used to measure ambient temprature. It can be measured using inbuilt ambient temprature sensor or through SHT21.';
String thermometerIntro =
'Thermometer instrument is used to measure ambient temperature. It can be measured using inbuilt ambient temperature sensor or through SHT21.';

String celsius = '°C';
String temperatureSensorError = 'Temperature sensor error:';
String temperatureSensorInitialError =
'Temperature sensor initialization error:';
String temperatureSensorUnavailableMessage =
'Ambient temperature sensor is not available on this device';
String shopLink = 'https://pslab.io/shop/';
String shopError = 'Could not open the shop link';
String baroMeterBulletPoint1 =
Expand Down
2 changes: 2 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import 'package:pslab/view/about_us_screen.dart';
import 'package:pslab/view/software_licenses_screen.dart';
import 'package:pslab/theme/app_theme.dart';
import 'package:pslab/view/soundmeter_screen.dart';
import 'package:pslab/view/thermometer_screen.dart';
import 'constants.dart';

void main() {
Expand Down Expand Up @@ -62,6 +63,7 @@ class MyApp extends StatelessWidget {
'/luxmeter': (context) => const LuxMeterScreen(),
'/barometer': (context) => const BarometerScreen(),
'/soundmeter': (context) => const SoundMeterScreen(),
'/thermometer': (context) => const ThermometerScreen(),
},
);
}
Expand Down
92 changes: 92 additions & 0 deletions lib/others/temperature_service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:pslab/others/logger_service.dart';

class TemperatureService {
static const MethodChannel _methodChannel =
MethodChannel('io.pslab/temperature');
static const EventChannel _eventChannel =
EventChannel('io.pslab/temperature_stream');

static StreamSubscription<dynamic>? _temperatureSubscription;
static final StreamController<double> _temperatureController =
StreamController<double>.broadcast();

static Stream<double> get temperatureStream => _temperatureController.stream;

static Future<bool> isTemperatureSensorAvailable() async {
try {
final bool isAvailable =
await _methodChannel.invokeMethod('isTemperatureSensorAvailable');
logger.d('Temperature sensor available: $isAvailable');
return isAvailable;
} on PlatformException catch (e) {
logger.e('Error checking temperature sensor availability: ${e.message}');
return false;
}
}

static Future<double> getCurrentTemperature() async {
try {
final double temperature =
await _methodChannel.invokeMethod('getCurrentTemperature');
logger.d('Current temperature: $temperature°C');
return temperature;
} on PlatformException catch (e) {
logger.e('Error getting current temperature: ${e.message}');
return 0.0;
}
}

static Future<bool> startTemperatureUpdates() async {
try {
final bool success =
await _methodChannel.invokeMethod('startTemperatureUpdates');
if (success) {
_startListening();
logger.d('Temperature updates started');
}
return success;
} on PlatformException catch (e) {
logger.e('Error starting temperature updates: ${e.message}');
return false;
}
}

static Future<void> stopTemperatureUpdates() async {
try {
await _methodChannel.invokeMethod('stopTemperatureUpdates');
_stopListening();
logger.d('Temperature updates stopped');
} on PlatformException catch (e) {
logger.e('Error stopping temperature updates: ${e.message}');
}
}

static void _startListening() {
_temperatureSubscription?.cancel();
_temperatureSubscription = _eventChannel.receiveBroadcastStream().listen(
(dynamic temperature) {
logger.d('Received temperature from stream: $temperature');
if (temperature is double) {
_temperatureController.add(temperature);
} else if (temperature is num) {
_temperatureController.add(temperature.toDouble());
}
},
onError: (error) {
logger.e('Temperature stream error: $error');
},
);
}

static void _stopListening() {
_temperatureSubscription?.cancel();
_temperatureSubscription = null;
}

static void dispose() {
_stopListening();
_temperatureController.close();
}
}
Loading