Skip to content

Commit 4691930

Browse files
committed
New Feature
Added real time graph view for Accelerometer, Gyroscope and Magnetometer
1 parent bcf900e commit 4691930

File tree

7 files changed

+313
-17
lines changed

7 files changed

+313
-17
lines changed

sensors_dashboard/lib/main.dart

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import 'package:flutter/foundation.dart';
22
import 'package:flutter/material.dart';
3+
import 'package:get_it/get_it.dart';
34
import 'package:provider/provider.dart';
45
import 'package:sensors_dashboard/model/server_info.dart';
56
import 'package:sensors_dashboard/view/screens/sensors_screen.dart';
67
import 'package:sensors_dashboard/view_model/sensors_screen_viewmodel.dart';
7-
import 'package:get_it/get_it.dart';
88

99
void main() {
1010
if(kDebugMode){
@@ -19,19 +19,21 @@ void main() {
1919
class MyApp extends StatelessWidget {
2020
const MyApp({super.key});
2121

22-
// This widget is the root of your application.
2322
@override
2423
Widget build(BuildContext context) {
25-
return MaterialApp(
26-
debugShowCheckedModeBanner: false,
27-
title: 'Sensors Dashboard',
28-
theme: ThemeData(
29-
colorScheme: ColorScheme.fromSeed(
30-
seedColor: Colors.teal),
31-
useMaterial3: true,
32-
),
33-
home: ChangeNotifierProvider(
34-
create: (context) => SensorsScreenViewmodel(),
35-
child: SensorsScreen()));
24+
25+
return ChangeNotifierProvider(
26+
create: (context) => SensorsScreenViewmodel(),
27+
child: MaterialApp(
28+
debugShowCheckedModeBanner: false,
29+
title: 'Sensors Dashboard',
30+
theme: ThemeData(
31+
colorScheme: ColorScheme.fromSeed(
32+
seedColor: Colors.teal),
33+
useMaterial3: true,
34+
),
35+
home: const SensorsScreen()
36+
),
37+
);
3638
}
3739
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import 'package:fl_chart/fl_chart.dart';
2+
import 'package:flutter/material.dart';
3+
import 'package:provider/provider.dart';
4+
import 'package:sensors_dashboard/view_model/sensor_graph_viewmodel.dart';
5+
6+
import '../../model/sensor.dart';
7+
8+
class SensorGraphWidget extends StatelessWidget {
9+
final Sensor sensor;
10+
const SensorGraphWidget(this.sensor, {super.key});
11+
12+
@override
13+
Widget build(BuildContext context) {
14+
//debugPrint("GraphScreen()");
15+
16+
final viewModel = Provider.of<SensorGraphViewmodel>(context);
17+
const xDataColor = Colors.green;
18+
const yDataColor = Colors.blue;
19+
const zDataColor = Colors.red;
20+
21+
return Scaffold(
22+
appBar: AppBar(
23+
title: Text(sensor.name),
24+
leading: IconButton(icon: const Icon(Icons.arrow_back_rounded), onPressed: (){
25+
26+
Navigator.of(context).pop();
27+
28+
},),
29+
centerTitle: true,
30+
31+
actions: [
32+
TextButton(onPressed: (){
33+
if(viewModel.isConnected){
34+
viewModel.disconnect();
35+
}else{
36+
viewModel.connect(sensor);
37+
}
38+
}, child: !viewModel.isConnected ? const Text("Connect") : const Text("Disconnect")),
39+
40+
],
41+
),
42+
body: Stack(
43+
children: [
44+
const Positioned(
45+
left: 100,
46+
top: 50,
47+
child: Column(
48+
children: [
49+
Text("X", style: TextStyle(color: xDataColor),),
50+
Text("Y", style: TextStyle(color: yDataColor),),
51+
Text("Z", style: TextStyle(color: zDataColor),),
52+
],
53+
),
54+
),
55+
Align(
56+
alignment: Alignment.center,
57+
child: Padding(
58+
padding: const EdgeInsets.all(8.0),
59+
child: LineChart(
60+
duration: const Duration(milliseconds: 1),
61+
62+
LineChartData(
63+
gridData: const FlGridData(show: false),
64+
borderData: FlBorderData(show: false),
65+
titlesData: const FlTitlesData(
66+
bottomTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
67+
topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false))
68+
),
69+
lineTouchData: const LineTouchData(enabled: false),
70+
minY: viewModel.minY,
71+
maxY: viewModel.maxY,
72+
lineBarsData: [
73+
74+
lineChartBarData(viewModel.xData , xDataColor),
75+
lineChartBarData(viewModel.yData, yDataColor),
76+
lineChartBarData(viewModel.zData, zDataColor),
77+
78+
]
79+
)
80+
),
81+
),
82+
),
83+
]
84+
),
85+
);
86+
}
87+
88+
LineChartBarData lineChartBarData(List<FlSpot> spotData , [Color color = Colors.red]){
89+
return LineChartBarData(
90+
color: color,
91+
spots: spotData,
92+
dotData: const FlDotData(
93+
show: false,
94+
),
95+
//curveSmoothness: 2,
96+
isCurved: true
97+
);
98+
}
99+
100+
}

sensors_dashboard/lib/view/components/sensor_widget.dart

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import 'package:flutter/material.dart';
33
import 'package:provider/provider.dart';
44
import 'package:sensors_dashboard/model/sensor.dart';
5+
import 'package:sensors_dashboard/view/components/sensor_graph_widget.dart';
6+
import 'package:sensors_dashboard/view_model/sensor_graph_viewmodel.dart';
57
import 'package:sensors_dashboard/view_model/sensor_viewmodel.dart';
68

79
/// A widget which represents sensor of Android device
@@ -40,9 +42,20 @@ class SensorWidget extends StatelessWidget {
4042
borderRadius: BorderRadius.circular(10),
4143
color: colorScheme.inversePrimary),
4244
child: FittedBox(
43-
child: Text(
44-
sensor.name,
45-
style: TextStyle(fontSize: textTheme.headlineSmall?.fontSize),
45+
child: Row(
46+
children: [
47+
Text(
48+
sensor.name,
49+
style: TextStyle(fontSize: textTheme.headlineSmall?.fontSize),
50+
),
51+
if(sensor.type.contains("accelerometer") || sensor.type.contains("gyroscope") || sensor.type.contains("magnetic_field"))
52+
IconButton(icon: const Icon(Icons.insert_chart), onPressed: viewModel.isConnected ? null : (){
53+
Navigator.of(context).push(
54+
MaterialPageRoute(builder: (context) => ChangeNotifierProvider(
55+
create: (context) => SensorGraphViewmodel(),
56+
child: SensorGraphWidget(sensor)),));
57+
},)
58+
],
4659
),
4760
),
4861
),

sensors_dashboard/lib/view/screens/sensors_screen.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ class SensorsScreen extends StatelessWidget {
1010

1111
@override
1212
Widget build(BuildContext context) {
13-
13+
//debugPrint("SensorScreen()");
14+
1415
final viewModel = Provider.of<SensorsScreenViewmodel>(context);
1516

1617
return Scaffold(
1718
appBar: AppBar(
19+
automaticallyImplyLeading: false,
1820
title: const Text("Sensors"),
1921
centerTitle: true,
2022
),
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import 'dart:async';
2+
import 'dart:convert';
3+
import 'dart:developer' as dev;
4+
import 'dart:math';
5+
6+
import 'package:fl_chart/fl_chart.dart';
7+
import 'package:flutter/foundation.dart';
8+
import 'package:get_it/get_it.dart';
9+
10+
import 'package:sensors_dashboard/model/repository/info_repository.dart';
11+
import 'package:sensors_dashboard/model/sensor.dart';
12+
import 'package:sensors_dashboard/model/server_info.dart';
13+
import 'package:web/web.dart' as web;
14+
15+
class SensorGraphViewmodel with ChangeNotifier {
16+
bool _connected = false;
17+
bool get isConnected => _connected;
18+
19+
bool _connecting = false;
20+
bool get isConnecting => _connecting;
21+
22+
double _maxY = 5;
23+
double get maxY => _maxY;
24+
25+
double get minY => -1 * maxY;
26+
27+
final List<FlSpot> _xData = [];
28+
List<FlSpot> get xData => _xData;
29+
30+
final List<FlSpot> _yData = [];
31+
List<FlSpot> get yData => _yData;
32+
33+
final List<FlSpot> _zData = [];
34+
List<FlSpot> get zData => _zData;
35+
36+
var _iterations = 0;
37+
38+
// limit of x,y and z Data list
39+
static const dataLimit = 100;
40+
41+
static const connectionTimeOutSecs = 2;
42+
43+
// A url for testing connections without deploying app to Android
44+
final testUrl = "ws://192.168.18.3:8080/sensor/connect";
45+
46+
web.WebSocket? _websocket;
47+
48+
void connect(Sensor sensor) async {
49+
_setConnecting(true);
50+
51+
Timer(const Duration(seconds: connectionTimeOutSecs), () {
52+
if (!isConnected) {
53+
_websocket?.close();
54+
_setConnected(false);
55+
}
56+
});
57+
58+
// Use hardcode or testUrl in debug mode.
59+
if (kDebugMode) {
60+
_websocket = web.WebSocket("$testUrl?type=${sensor.type}");
61+
}
62+
// Get actual address when deployed to Android
63+
if (kReleaseMode) {
64+
final serverInfo = GetIt.instance.get<ServerInfo>();
65+
66+
dev.log(
67+
"getting websocket port from http://${serverInfo.address}/wsport");
68+
final webSocketPort = await InfoRepository().getWebSocketPortNo();
69+
final webSocketServerAddress = "ws://${serverInfo.iP}:$webSocketPort";
70+
71+
_websocket = web.WebSocket(
72+
"$webSocketServerAddress/sensor/connect?type=${sensor.type}");
73+
}
74+
75+
_websocket?.onOpen.listen((event) {
76+
_setConnected(true);
77+
});
78+
79+
_websocket?.onMessage.listen((messageEvent) {
80+
if (xData.length > dataLimit) {
81+
_xData.removeAt(0);
82+
_yData.removeAt(0);
83+
_zData.removeAt(0);
84+
}
85+
86+
final data = messageEvent.data.toString();
87+
final json = jsonDecode(data);
88+
89+
// TODO : unable to read read json array into List<Double>
90+
List<double> values = json["values"]
91+
.toString()
92+
.replaceAll("[", "")
93+
.replaceAll("]", "")
94+
.split(",")
95+
.map((e) => double.parse(e))
96+
.toList();
97+
98+
final timestamp = double.parse(json["timestamp"].toString());
99+
100+
_xData.add(FlSpot(timestamp, values[0]));
101+
_yData.add(FlSpot(timestamp, values[1]));
102+
_zData.add(FlSpot(timestamp, values[2]));
103+
104+
// set max value for Y axis to max of x,y and z
105+
final maxValue = [values[0], values[1], values[2]].reduce(max) + 3;
106+
// This ensures that the Y-axis maximum always accommodates the largest data point
107+
_maxY = maxValue > _maxY ? maxValue : _maxY;
108+
109+
// This block introduces a mechanism to adjust _maxY downwards after a certain number of iterations.
110+
// This could be useful if the data being displayed is trending downwards and you want the Y-axis to "zoom in" to better represent the current range of values.
111+
if (_iterations > dataLimit) {
112+
_maxY = maxValue < _maxY ? maxValue : _maxY;
113+
_iterations = 0;
114+
}
115+
116+
_iterations++;
117+
118+
if (_xData.isNotEmpty) {
119+
notifyListeners();
120+
}
121+
122+
});
123+
124+
_websocket?.onClose.listen((closeEvent) {
125+
_setConnected(false);
126+
});
127+
128+
_websocket?.onError.listen((event) {
129+
_setConnected(false);
130+
});
131+
}
132+
133+
void disconnect() async {
134+
_websocket?.close();
135+
}
136+
137+
void _setConnected(bool connected) {
138+
_setConnecting(false);
139+
140+
if (_connected == connected) {
141+
return;
142+
}
143+
144+
_connected = connected;
145+
notifyListeners();
146+
}
147+
148+
void _setConnecting(bool connecting) {
149+
if (_connecting == connecting) {
150+
return;
151+
}
152+
153+
_connecting = connecting;
154+
notifyListeners();
155+
}
156+
157+
@override
158+
void dispose() {
159+
super.dispose();
160+
_websocket?.close();
161+
}
162+
}

sensors_dashboard/pubspec.lock

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ packages:
4949
url: "https://pub.dev"
5050
source: hosted
5151
version: "1.0.8"
52+
equatable:
53+
dependency: transitive
54+
description:
55+
name: equatable
56+
sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2
57+
url: "https://pub.dev"
58+
source: hosted
59+
version: "2.0.5"
5260
fake_async:
5361
dependency: transitive
5462
description:
@@ -57,6 +65,14 @@ packages:
5765
url: "https://pub.dev"
5866
source: hosted
5967
version: "1.3.1"
68+
fl_chart:
69+
dependency: "direct main"
70+
description:
71+
name: fl_chart
72+
sha256: d0f0d49112f2f4b192481c16d05b6418bd7820e021e265a3c22db98acf7ed7fb
73+
url: "https://pub.dev"
74+
source: hosted
75+
version: "0.68.0"
6076
flutter:
6177
dependency: "direct main"
6278
description: flutter

sensors_dashboard/pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ dependencies:
3939
web: ^0.5.1
4040
http: ^1.2.1
4141
get_it: ^7.7.0
42+
fl_chart: ^0.68.0
4243

4344
dev_dependencies:
4445
flutter_test:

0 commit comments

Comments
 (0)