healthrian_uart_support 1.1.1
healthrian_uart_support: ^1.1.1 copied to clipboard
Healthrian library for supporting UART connector manager
example/lib/main.dart
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'package:file_picker/file_picker.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:healthrian_common_support/healthrian_common_support.dart';
import 'package:healthrian_uart_support/healthrian_uart_support.dart';
import 'package:intl/intl.dart';
import 'package:screenshot/screenshot.dart';
final uart = UartConnectorManager();
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await filter.waitForCompletion();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
StreamSubscription? _listener;
DateTime? _recordedTime;
String? _selectedDirectory;
DataFilter _dataFilter = DataFilter(2)
..samplingRate = SamplingRate.freq500hz
..impulseType = ImpulseType.fir
..highPass = HighPass.hpf05
..lowPass = LowPass.lpf40;
final _l1Raw = <double>[];
final _l1 = <double>[];
final _l2 = <double>[];
final _l3 = <double>[];
final _aVR = <double>[];
final _aVL = <double>[];
final _aVF = <double>[];
void _clear() {
_l1Raw.clear();
_l1.clear();
_l2.clear();
_l3.clear();
_aVR.clear();
_aVL.clear();
_aVF.clear();
}
void _add(double l1, double l2) {
_l1Raw.add(l1);
l1 = _dataFilter.applyFilter(0, l1);
l2 = _dataFilter.applyFilter(1, l2);
_l1.add(l1);
_l2.add(l2);
_l3.add(l2 - l1);
_aVR.add(-l2 / 2 - l1 / 2);
_aVL.add(l1 - l2 / 2);
_aVF.add(l2 - l1 / 2);
}
@override
void dispose() {
super.dispose();
_listener?.cancel();
_clear();
uart.closePort();
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Column(
children: [
const Text('PACKET'),
StreamBuilder(
stream: uart.outStream,
builder: (context, snapshot) => Text(snapshot.data.toString()),
),
const Divider(),
const Text('LA, RA, LL, CABLE'),
StreamBuilder(
stream: uart.la,
builder: (context, snapshot) => Text(snapshot.data.toString()),
),
StreamBuilder(
stream: uart.ra,
builder: (context, snapshot) => Text(snapshot.data.toString()),
),
StreamBuilder(
stream: uart.ll,
builder: (context, snapshot) => Text(snapshot.data.toString()),
),
StreamBuilder(
stream: uart.cable,
builder: (context, snapshot) => Text(snapshot.data.toString()),
),
const Divider(),
const Text('ADC'),
StreamBuilder(
stream: uart.adc,
builder: (context, snapshot) => Text(snapshot.data.toString()),
),
const Text('Distance'),
StreamBuilder(
stream: uart.distance,
builder: (context, snapshot) => Text(snapshot.data.toString()),
),
const Divider(),
const Text('L1, L2, L3, AVR, AVL, AVF'),
StreamBuilder(
stream: uart.l1,
builder: (context, snapshot) => Text(snapshot.data.toString()),
),
StreamBuilder(
stream: uart.l2,
builder: (context, snapshot) => Text(snapshot.data.toString()),
),
StreamBuilder(
stream: uart.l3,
builder: (context, snapshot) => Text(snapshot.data.toString()),
),
StreamBuilder(
stream: uart.aVR,
builder: (context, snapshot) => Text(snapshot.data.toString()),
),
StreamBuilder(
stream: uart.aVL,
builder: (context, snapshot) => Text(snapshot.data.toString()),
),
StreamBuilder(
stream: uart.aVF,
builder: (context, snapshot) => Text(snapshot.data.toString()),
),
const Divider(),
],
),
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: () => uart.setCalibOpen(),
child: const Text('Calib\nOpen', style: TextStyle(color: Colors.red)),
),
FloatingActionButton(
onPressed: () => uart.setCalib15cm(),
child: const Text('Calib\n15cm', style: TextStyle(color: Colors.red)),
),
FloatingActionButton(
onPressed: () => uart.setCalib30cm(),
child: const Text('Calib\n30cm', style: TextStyle(color: Colors.red)),
),
FloatingActionButton(
onPressed: () => uart.setCalib45cm(),
child: const Text('Calib\n45cm', style: TextStyle(color: Colors.red)),
),
FloatingActionButton(
onPressed: () async {
final calibrations = await uart.getCalibration();
if (!context.mounted) return;
showDialog(
context: context,
builder: (context) => Dialog(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(calibrations.join(',')),
),
),
);
},
child: const Text('Read\nCalib', style: TextStyle(color: Colors.green)),
),
],
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
// FloatingActionButton(
// onPressed: uart.hideStatusBar,
// ),
// FloatingActionButton(
// onPressed: uart.showStatusBar,
// ),
FloatingActionButton(
onPressed: uart.turnOn,
child: const Icon(Icons.power),
),
FloatingActionButton(
onPressed: uart.closePort,
child: const Icon(Icons.power_off),
),
FloatingActionButton(
onPressed: uart.start,
child: const Icon(Icons.play_arrow),
),
FloatingActionButton(
onPressed: uart.stop,
child: const Icon(Icons.stop),
),
if (_listener == null)
FloatingActionButton(
onPressed: () async {
if (_selectedDirectory == null) {
await FilePicker.platform.getDirectoryPath().then((path) => _selectedDirectory = path);
}
if (_selectedDirectory == null) return;
_listener?.cancel();
_clear();
_dataFilter = DataFilter(2)
..samplingRate = SamplingRate.freq500hz
..impulseType = ImpulseType.fir
..highPass = HighPass.hpf05
..lowPass = LowPass.lpf40;
_recordedTime = DateTime.now();
_listener = uart.all.listen((x) => _add(x.l1, x.l2));
setState(() {});
},
tooltip: 'start record',
child: const Icon(Icons.emergency_recording),
),
if (_listener != null)
FloatingActionButton(
onPressed: () {
_listener?.cancel();
_listener = null;
setState(() {});
if (_selectedDirectory == null) return;
if (_recordedTime == null) return;
final recordedTimeText = DateFormat('yyyy-MM-dd_hh-mm-ss').format(_recordedTime!);
final totalStep = (_l1.length / 5000).ceil();
final rawFile = File('$_selectedDirectory/$recordedTimeText.txt');
rawFile.writeAsStringSync(_l1Raw.toString());
for (var step = 0; step < totalStep; step++) {
final file = File('$_selectedDirectory/${recordedTimeText}_$step.png');
ScreenshotController()
.captureFromWidget(
CapturedScreen(
size: Size(1000, 720),
l1: _l1,
l2: _l2,
l3: _l3,
aVR: _aVR,
aVL: _aVL,
aVF: _aVF,
samplingRate: 500,
gridColor: Colors.red,
strokeWidth: 1.5,
step: step,
),
context: context,
targetSize: Size(1000, 720),
)
.then((bytes) async => await file.writeAsBytes(bytes));
}
},
tooltip: 'stop record',
child: const Icon(Icons.emergency_recording, color: Colors.red),
),
FloatingActionButton(
// onPressed: () => uart.updateLatestWithPassword(String.fromEnvironment('PASSWORD')),
onPressed: () => uart.updateLatestWithPassword('healthrian@2025'),
child: const Text('FW\nUpdate', style: TextStyle(color: Colors.red)),
),
FloatingActionButton(
onPressed: () async {
final fwVersion = await uart.getFirmwareVersion();
if (!context.mounted) return;
showDialog(
context: context,
builder: (context) => Dialog(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(fwVersion),
),
),
);
},
child: const Text('FW\nVersion', style: TextStyle(color: Colors.green)),
),
FloatingActionButton(
onPressed: () async {
final serialNumber = await uart.getSerialNumber();
if (!context.mounted) return;
showDialog(
context: context,
builder: (context) => Dialog(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(serialNumber),
),
),
);
},
child: const Text('Serial\nNo.', style: TextStyle(color: Colors.green)),
),
FloatingActionButton(
onPressed: () => uart.enableReadLogger = !uart.enableReadLogger,
child: const Text('Read\nLogger'),
),
],
),
],
),
),
);
}
}
class CapturedScreen extends StatelessWidget {
const CapturedScreen({
super.key,
required this.size,
required this.l1,
required this.l2,
required this.l3,
required this.aVR,
required this.aVL,
required this.aVF,
required this.samplingRate,
required this.gridColor,
required this.strokeWidth,
required this.step,
});
final Size size;
final List<double> l1;
final List<double> l2;
final List<double> l3;
final List<double> aVR;
final List<double> aVL;
final List<double> aVF;
final int samplingRate;
final Color gridColor;
final double strokeWidth;
final int step;
@override
Widget build(BuildContext context) {
return Container(
width: size.width,
height: size.height,
color: Colors.white,
child: Column(
children: [
for (final list in [l1, l2, l3, aVR, aVL, aVF])
Expanded(
child: LineChart(
LineChartData(
minX: 0,
maxX: 5000,
minY: -1.5,
maxY: 1.5,
lineTouchData: const LineTouchData(enabled: false),
titlesData: const FlTitlesData(show: false),
borderData: FlBorderData(show: false),
gridData: FlGridData(
show: true,
checkToShowVerticalLine: (x) => x % (samplingRate / 25) == 0,
verticalInterval: 10,
getDrawingVerticalLine: (x) {
if (x % (samplingRate / 5) == 0) {
return FlLine(color: gridColor, strokeWidth: strokeWidth / 2);
}
return FlLine(color: gridColor, strokeWidth: strokeWidth / 4);
},
horizontalInterval: 0.1,
getDrawingHorizontalLine: (y) {
if ((y * 10).round() % 5 == 0) {
return FlLine(color: gridColor, strokeWidth: strokeWidth / 2);
}
return FlLine(color: gridColor, strokeWidth: strokeWidth / 4);
},
),
lineBarsData: [
LineChartBarData(
spots: [
...list
.getRange(step * 5000, min(list.length, step * 5000 + 5000))
.indexed
.map((x) => FlSpot(x.$1.toDouble(), x.$2))
],
color: Colors.black,
dotData: const FlDotData(show: false),
)
],
),
),
),
],
),
);
}
}