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:provider/provider.dart'; import 'package:stream_transform/stream_transform.dart'; import '../components/region_selector.dart'; import '../utils/recv_parse.dart'; class CompassPage extends StatefulWidget { const CompassPage({Key key}) : super(key: key); @override State createState() => _CompassState(); } class _CompassState extends State { // 串口相关 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 = []; void initDevice() {} @override void initState() { super.initState(); initPort(); } Future initPort() async { // 20ms接收一次串口数据,防抖,定义接收缓存 final debounceTransformer = StreamTransformer.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 closePort() async { bool closeResult = await _serialPort.close(); setState(() { isPortOpened = !closeResult; }); } Future openCompass() async { print("compass open"); _serialPort .write(Uint8List.fromList(hexToUnits("7E01020100000000000000010D0A"))); } Future closeCompass() async { print("compass close"); _serialPort .write(Uint8List.fromList(hexToUnits("7E01020000000000000000010D0A"))); } Future openSideLaser() async { print("side open"); _serialPort .write(Uint8List.fromList(hexToUnits("7E01010100000000000000010D0A"))); } Future closeSideLaser() async { print("side close"); _serialPort .write(Uint8List.fromList(hexToUnits("7E01010000000000000000010D0A"))); } Future openUpLaser() async { _serialPort .write(Uint8List.fromList(hexToUnits("7e01010300000000000000010d0a"))); } Future closeUpLaser() async { _serialPort .write(Uint8List.fromList(hexToUnits("7e01010200000000000000010d0a"))); } Future openRange() async { _serialPort .write(Uint8List.fromList(hexToUnits("7e01040100000000000000010d0a"))); } Future raging() async { if (!isLock) { switchCompass(); } _serialPort .write(Uint8List.fromList(hexToUnits("7e01040200000000000000010d0a"))); } Future 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: [ TextButton( onPressed: () { Navigator.pop(context); }, child: const Text('关闭')), TextButton( onPressed: () { Provider.of(context, listen: false) .saveRegion(); Navigator.pop(context); }, child: const Text('保存')) ], ); }).then((value) { Provider.of(context, listen: false).resetTemp(); }); } @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: 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 * pi / 360, child: const Image( image: AssetImage("assets/images/compass.png"), fit: BoxFit.fill), ), 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.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), )), // 最下面一行,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, )) ], ), ) ], ), )), ), ); } }