Files
fengshui_compass/lib/pages/compass_page.dart
2022-07-05 16:44:07 +08:00

529 lines
18 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:async';
import 'dart:math';
import 'dart:typed_data';
import 'package:fengshui_compass/components/cross_paint.dart';
import 'package:fengshui_compass/components/my_icon.dart';
import 'package:fengshui_compass/pages/login_page.dart';
import 'package:fengshui_compass/states/region.dart';
import 'package:flutter/material.dart';
import 'package:flutter_serial_port_api/flutter_serial_port_api.dart';
import 'package:image_picker/image_picker.dart';
import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart';
import 'package:stream_transform/stream_transform.dart';
import '../components/region_selector.dart';
import '../states/compass_image.dart';
import '../utils/recv_parse.dart';
class CompassPage extends StatefulWidget {
const CompassPage({Key key}) : super(key: key);
@override
State<StatefulWidget> createState() => _CompassState();
}
class _CompassState extends State<CompassPage> {
// 串口相关
bool isPortOpened = false;
SerialPort _serialPort;
StreamSubscription _subscription;
// 传感器状态
bool isLock = true;
bool isUpClose = true;
bool isSideClose = true;
double w_x = 0.5;
double w_y = 0.5;
// 与磁北极夹角
double myaw = 0.0;
String distance = "";
var lista = [];
var listb = [];
var listc = [];
//从相册选择的图片名称
String selectedImageName;
void initDevice() {}
@override
void initState() {
super.initState();
initPort();
}
Future<void> initPort() async {
// 20ms接收一次串口数据防抖定义接收缓存
final debounceTransformer = StreamTransformer<Uint8List, dynamic>.fromBind(
(s) => s.transform(debounceBuffer(const Duration(milliseconds: 20))));
if (!isPortOpened) {
Device theDevice = Device("/dev/ttyS2", "/dev/ttyS2");
var serialPort = await FlutterSerialPortApi.createSerialPort(
theDevice, 115200,
parity: 0, dataBits: 8, stopBit: 1);
bool openResult = await serialPort.open();
setState(() {
_serialPort = serialPort;
isPortOpened = openResult;
});
await openRange();
_subscription = _serialPort.receiveStream
.transform(debounceTransformer)
.listen((recv) {
// recvData - 9E01040100000000000000065E
String recvData = formatReceivedData(recv);
print("Receive format: $recvData");
parsingRecvCom(recvData);
// 解析收到的结果,得到陀螺、地磁、测距结果
});
await openCompass();
}
}
Future<void> closePort() async {
bool closeResult = await _serialPort.close();
setState(() {
isPortOpened = !closeResult;
});
}
Future<void> openCompass() async {
print("compass open");
_serialPort
.write(Uint8List.fromList(hexToUnits("7E01020100000000000000010D0A")));
}
Future<void> closeCompass() async {
print("compass close");
_serialPort
.write(Uint8List.fromList(hexToUnits("7E01020000000000000000010D0A")));
}
Future<void> openSideLaser() async {
print("side open");
_serialPort
.write(Uint8List.fromList(hexToUnits("7E01010100000000000000010D0A")));
}
Future<void> closeSideLaser() async {
print("side close");
_serialPort
.write(Uint8List.fromList(hexToUnits("7E01010000000000000000010D0A")));
}
Future<void> openUpLaser() async {
_serialPort
.write(Uint8List.fromList(hexToUnits("7e01010300000000000000010d0a")));
}
Future<void> closeUpLaser() async {
_serialPort
.write(Uint8List.fromList(hexToUnits("7e01010200000000000000010d0a")));
}
Future<void> openRange() async {
_serialPort
.write(Uint8List.fromList(hexToUnits("7e01040100000000000000010d0a")));
}
Future<void> raging() async {
if (!isLock) {
switchCompass();
}
_serialPort
.write(Uint8List.fromList(hexToUnits("7e01040200000000000000010d0a")));
}
Future<void> switchCompass() async {
if (isLock) {
await openCompass();
} else {
await closeCompass();
setState(() {
w_x = 0.5;
w_y = 0.5;
});
}
setState(() {
isLock = !isLock;
});
}
loginAction() {
Navigator.push(
context, MaterialPageRoute(builder: (context) => LoginPage()));
}
parsingRecvCom(str) {
if (str.contains("9E010201") &&
(str.length > (str.indexOf("9E010201") + 50))) {
var roll_l = hexToInt(str.substring(8, 10));
var roll_h = hexToInt(str.substring(10, 12));
var pitch_l = hexToInt(str.substring(12, 14));
var pitch_h = hexToInt(str.substring(14, 16));
// var yaw_l = hexToInt(str.substring(16, 18));
// var yaw_h = hexToInt(str.substring(18, 20));
var pos = str.indexOf("9E010301");
// var mx_h = hexToInt(str.substring(pos+8, pos+10));
// var mx_l = hexToInt(str.substring(pos+10, pos+12));
// var my_h = hexToInt(str.substring(pos+12, pos+14));
var myaw_flag = hexToInt(str.substring(pos + 14, pos + 16));
var myaw_h = hexToInt(str.substring(pos + 16, pos + 18));
var myaw_l = hexToInt(str.substring(pos + 18, pos + 20));
var roll_tmp = (roll_h * 256 + roll_l) * 180 / 32768;
var pitch_tmp = (pitch_h * 256 + pitch_l) * 180 / 32768;
// var yaw = (yaw_h * 256 + yaw_l) * 180 /32768;
// -180~180
var ff = myaw_flag == 1 ? -1 : 1;
var temp_myaw = (myaw_h * 256 + myaw_l) * 0.01 * ff + 180;
if (roll_tmp > 180) roll_tmp = roll_tmp - 360;
if (pitch_tmp > 180) pitch_tmp = pitch_tmp - 360;
var w_x_tmp = 0.0;
var w_y_tmp = 0.0;
// 倾角<30度
var w_total = sqrt(
roll_tmp.abs() * roll_tmp.abs() + pitch_tmp.abs() * pitch_tmp.abs());
if (w_total <= 30) {
w_y_tmp = 0.5 - 0.07 * roll_tmp / 30.0;
w_x_tmp = 0.5 - 0.07 * pitch_tmp / 30.0;
} else if (w_total > 30) {
//todo
w_y_tmp = 0.5 - 0.07 * w_total / 30.0;
w_x_tmp = 0.5 - 0.07 * w_total / 30.0;
}
// todo 其他情况
var meanValue;
if (lista.length < 20) {
lista.add(temp_myaw);
meanValue = null;
} else {
lista.removeAt(0);
lista.add(temp_myaw);
meanValue = lista.map((e) => e).reduce((a, b) => a + b) / lista.length;
}
setState(() {
// print("roll: $roll_tmp pitch: $pitch_tmp");
// print("w_x: $w_x_tmp, w_y: $w_y_tmp");
// if (meanValue != null) {
// myaw = meanValue;
// } else {
// myaw = 0;
// }
myaw = temp_myaw;
w_x = w_x_tmp;
w_y = w_y_tmp;
// 水平仪 0.5+-0.07范围
// x旋转y在动 y旋转x在动 roll改变y坐标pitch改变x坐标
});
} else if (str.contains("9E010401") &&
(str.length >= ((str.indexOf("9E010401") + 26)))) {
var pos = str.indexOf("9E010401");
int rh = hexToInt(str.substring(pos + 16, pos + 18));
int rl = hexToInt(str.substring(pos + 18, pos + 20));
int result = rh * 256 + rl;
print("测距$result");
setState(() {
distance = (result.toDouble() / 1000).toStringAsFixed(2);
});
} else {}
}
// 拼接城市
String spliceCityName(String pname, String cname) {
if (pname == '') return '未选择城市';
StringBuffer sb = StringBuffer();
sb.write(pname);
if (cname == '') return sb.toString();
sb.write(' - ');
sb.write(cname);
return sb.toString();
}
// bool strEmpty(String? value) {
// if (value == null) return true;
// return value.trim().isEmpty;
// }
selectRegion() {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('选择城市校准磁偏角'),
content: RegionSelector(),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('关闭')),
TextButton(
onPressed: () {
Provider.of<RegionProvider>(context, listen: false)
.saveRegion();
Navigator.pop(context);
},
child: const Text('保存'))
],
);
}).then((value) {
Provider.of<RegionProvider>(context, listen: false).resetTemp();
});
}
void initCompassImage() async {
final directory =
await getApplicationDocumentsDirectory(); // AppData directory
}
@override
Widget build(BuildContext context) {
return Container(
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/images/bg.png"), fit: BoxFit.cover)),
child: Scaffold(
backgroundColor: Colors.transparent,
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
centerTitle: true,
title: const Text(
'定盘星',
style: TextStyle(color: Colors.white),
),
leading: IconButton(
color: Colors.amber,
icon: Icon(isLock ? MyIcons.icon_mima : MyIcons.icon_jiesuo),
onPressed: switchCompass,
),
actions: [
//todo
// 更改背景图
IconButton(
color: Colors.amber,
icon: const Icon(
Icons.person,
size: 22,
),
onPressed: loginAction,
),
],
),
body: SafeArea(
child: ConstrainedBox(
constraints: const BoxConstraints(minHeight: 600),
child: Consumer<RegionProvider>(
builder: (builder, regionProvider, child) {
return Consumer<CompassImageProvider>(
builder: (builder, compassImageProvider, child) {
return Stack(
alignment: Alignment.center,
children: [
// 罗盘
Column(
children: [
const Padding(padding: EdgeInsets.only(top: 145)),
Row(
children: [
Spacer(flex: 1),
Container(
width: 700,
height: 700,
child: Stack(
children: [
Transform.rotate(
angle: (myaw + regionProvider.declination) *
2 *
pi /
360,
child: Image(
width: 700,
height: 700,
// alignment: Alignment.bottomLeft,
// image: compassImageProvider.rotateImage,
image: compassImageProvider
.rotateImage ??
const AssetImage(
"assets/images/compass_rotated.png"),
fit: BoxFit.contain),
),
Align(
alignment: FractionalOffset(w_x, w_y),
child: const Image(
image:
AssetImage("assets/images/water.png"),
),
),
CrossPaint(),
],
),
),
const Spacer(flex: 1),
],
)
],
),
// 最上面一行, lock azimuth login
Positioned(
top: 5,
child: Column(
children: [
const Image(
width: 15,
height: 15,
image: AssetImage("assets/images/arrow.png"),
fit: BoxFit.contain,
),
Text(
// "${azimuth.toStringAsFixed(2)}",
(myaw + regionProvider.declination)
.toStringAsFixed(2),
style: const TextStyle(
color: Colors.amber, fontSize: 36),
),
],
)),
//磁偏角调整按钮
Positioned(
top: 5,
right: 8,
child: IconButton(
onPressed: () => selectRegion(),
icon: const Icon(Icons.settings, color: Colors.amber),
)),
Positioned(
top: 5,
left: 8,
child: IconButton(
onPressed: () {
ImagePicker()
.pickImage(source: ImageSource.gallery)
.then((res) {
if (res == null) {
return;
}
compassImageProvider.setSelectedRotateImage(res);
});
},
icon: const Icon(Icons.photo, color: Colors.amber),
)),
// 最下面一行ranging value openlaser
Positioned(
bottom: 80,
left: 50,
child: IconButton(
onPressed: raging,
icon: const Icon(
MyIcons.icon_celiang,
size: 34,
),
color: Colors.amber)),
const Positioned(
width: 180,
height: 90,
bottom: 60,
child: Image(
image: AssetImage("assets/images/range_input.png"),
fit: BoxFit.contain,
)),
Positioned(
width: 180,
height: 90,
bottom: 60,
child: Align(
alignment: Alignment.centerRight,
child: Padding(
padding: const EdgeInsets.only(right: 15),
child: Text(
"${distance} m",
style: const TextStyle(
color: Colors.amber, fontSize: 28),
),
),
)),
Positioned(
bottom: 60,
right: 40,
height: 120,
width: 100,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
color: Colors.amber,
onPressed: () {
if (isUpClose) {
openUpLaser();
} else {
closeUpLaser();
}
setState(() {
isUpClose = !isUpClose;
});
},
icon: Icon(
isUpClose
? MyIcons.icon_shangdeng
: MyIcons.icon_shangdnegguanbi,
size: 36)),
IconButton(
color: Colors.amber,
onPressed: () {
if (isSideClose) {
openSideLaser();
} else {
closeSideLaser();
}
setState(() {
isSideClose = !isSideClose;
});
},
icon: Icon(
isSideClose
? MyIcons.icon_zuoyoudneg
: MyIcons.icon_zuoyoudengguanbi,
size: 32,
))
],
),
)
],
);
});
},
),
)),
),
);
}
}