signiture, fixes, console (broken), states

This commit is contained in:
jusax23 2023-09-18 19:45:59 +02:00
parent 9753b68167
commit 0e08b591b7
Signed by: jusax23
GPG key ID: 499E2AA870C1CD41
10 changed files with 512 additions and 68 deletions

3
.gitignore vendored
View file

@ -42,3 +42,6 @@ app.*.map.json
/android/app/debug
/android/app/profile
/android/app/release
key.properties
/key

View file

@ -51,11 +51,26 @@ android {
versionName flutterVersionName
}
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
signingConfig signingConfigs.release
}
}
}

228
lib/boxes/console.dart Normal file
View file

@ -0,0 +1,228 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:intl/intl.dart';
import 'package:ju_rc_app/lib/boxes.dart';
import 'package:ju_rc_app/lib/serial.dart';
class SerialBox extends JuBox {
const SerialBox({super.key});
@override
State<StatefulWidget> createState() => _SerialBoxState();
}
class _SerialBoxState extends State<SerialBox> {
USerial serial = getSerial();
final ScrollController _controller = ScrollController();
@override
void initState() {
super.initState();
serial.listen(serialListen);
}
@override
void dispose() {
serial.removeListen(serialListen);
super.dispose();
}
void serialListen(String line, SerialCommand? _) {
setState(() {
//change serial lines
_controller.jumpTo(_controller.position.maxScrollExtent);
});
}
@override
Widget build(BuildContext context) => GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SerialDetailPage()),
);
},
child: ListView(
controller: _controller,
shrinkWrap: false,
physics: const NeverScrollableScrollPhysics(),
children: serial.lines
.last(20)
.map(
(e) => Row(
children: [
Padding(
padding: const EdgeInsets.all(0.0),
child: Icon(e.$1 != SerialMessageType.received
? Icons.arrow_left
: Icons.arrow_right),
),
Expanded(
child: Text(
e.$3,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
],
),
)
.toList(),
));
}
class SerialDetailPage extends StatefulWidget {
const SerialDetailPage({super.key});
@override
State<StatefulWidget> createState() => _SerialDetailPageState();
}
class _SerialDetailPageState extends State<SerialDetailPage> {
final ScrollController _controller = ScrollController();
USerial serial = getSerial();
bool jump = true;
final List<String> _messages = [];
int messageIndex = 0;
final TextEditingController _textController = TextEditingController();
FocusNode fc = FocusNode();
FocusNode _textFocus = FocusNode();
@override
void initState() {
super.initState();
serial.listen(serialListen);
fc.requestFocus();
}
@override
void dispose() {
serial.removeListen(serialListen);
_textController.dispose();
_controller.dispose();
super.dispose();
}
void serialListen(String line, SerialCommand? _) {
if (!jump) return;
setState(() {
//change serial lines
_controller.jumpTo(_controller.position.maxScrollExtent);
});
}
void submit() {
if (_textController.text.isNotEmpty) {
setState(() {
serial.sprintln(_textController.text);
_messages.add(_textController.text);
messageIndex = _messages.length;
_textController.clear();
});
_textFocus.requestFocus();
}
}
void _handleKeyEvent(RawKeyEvent event) {
if (event.runtimeType.toString() == 'RawKeyDownEvent') {
if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
if (messageIndex > 0) {
setState(() {
messageIndex--;
print("up: " + _messages[messageIndex]);
_textController.text = _messages[messageIndex];
});
}
} else if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
if (messageIndex >= _messages.length - 1) {
setState(() {
print("clear");
messageIndex = _messages.length;
_textController.clear();
_textFocus.requestFocus();
});
} else {
setState(() {
messageIndex++;
print("down: " + _messages[messageIndex]);
_textController.text = _messages[messageIndex];
_textFocus.requestFocus();
});
}
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text(
"Serial Console",
style: TextStyle(
fontSize: 16,
),
),
toolbarHeight: 40,
),
body: Column(
children: [
Expanded(
child: ListView(
controller: _controller,
children: serial.lines.items
.map(
(e) => Row(
children: [
Text(DateFormat('HH:mm:ss.S').format(e.$2)),
Padding(
padding: const EdgeInsets.all(0.0),
child: Icon(e.$1 != SerialMessageType.received
? Icons.arrow_left
: Icons.arrow_right),
),
Expanded(
child: Text(
e.$3,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
],
),
)
.toList(),
)),
RawKeyboardListener(
focusNode: fc,
autofocus: true,
onKey: _handleKeyEvent,
child: Padding(
padding: const EdgeInsets.fromLTRB(8.0, 0, 8.0, 8.0),
child: Row(
children: <Widget>[
Expanded(
child: TextField(
focusNode: _textFocus,
controller: _textController,
autofocus: true,
decoration: const InputDecoration(
hintText: 'Enter a Command',
),
onSubmitted: (_) {
submit();
},
),
),
IconButton(
icon: const Icon(Icons.send),
onPressed: submit,
),
],
),
))
],
));
}
}

84
lib/boxes/state.dart Normal file
View file

@ -0,0 +1,84 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:ju_rc_app/lib/boxes.dart';
import 'package:ju_rc_app/lib/serial.dart';
class ControllerState extends JuBox {
const ControllerState({super.key});
@override
State<StatefulWidget> createState() => _ControllerStateState();
}
class _ControllerStateState extends State<ControllerState> {
USerial serial = getSerial();
late Timer timer;
int connected = 0;
bool mode = false; //true is ble
int arc = 0;
double akku = 0.1;
@override
void initState() {
super.initState();
serial.listen(serialListen);
timer = Timer.periodic(const Duration(seconds: 1), (_) async {
serial.sprintln("<ping>", system: true);
connected--;
serial.sprintln("<getSendMode>", system: true);
serial.sprintln("<getVoltage>", system: true);
serial.sprintln("<getRfArc>", system: true);
});
}
@override
void dispose() {
serial.removeListen(serialListen);
timer.cancel();
super.dispose();
}
void serialListen(String line, SerialCommand? cmdo) {
if (cmdo == null) return;
var cmd = cmdo;
setState(() {
if (cmd.command == "pong") connected = 2;
if (cmd.command == "sendMode") {
if (cmd.arg == "RF") {
mode = false;
} else if (cmd.arg == "BLE") {
mode = true;
}
}
if(cmd.command == "rfArc"){
arc = int.tryParse(cmd.arg) ?? 15;
}
if(cmd.command == "voltage"){
akku = double.tryParse(cmd.arg) ?? 0.0;
}
});
}
@override
Widget build(BuildContext context) => Column(
children: [
const Text("Remote controll state"),
Text(
"Serial Connection: ${connected > 0 ? "connected" : "disconnected"}"),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text("RC Mode: "),
if (mode)
const Icon(Icons.bluetooth)
else
const Icon(Icons.settings_input_antenna)
],
),
Text("ARC: $arc"),
Text("Akku: ${akku}V")
],
);
}

View file

@ -3,33 +3,39 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:ju_rc_app/lib/serial.dart';
// ignore: must_be_immutable
class ConnectionBar extends StatefulWidget {
const ConnectionBar({super.key});
USerial serial = getSerial();
ConnectionBar({super.key});
@override
ConnectionBarState createState() => ConnectionBarState();
// ignore: library_private_types_in_public_api
_ConnectionBarState createState() => _ConnectionBarState();
}
class ConnectionBarState extends State<ConnectionBar> {
late USerial serial;
UPort? connectedTo;
List<UPort> _ports = [];
StateSetter? sheetsetstate;
class _ConnectionBarState extends State<ConnectionBar> {
late Timer timer;
String _status = "";
List<UPort> _ports = [];
StateSetter? sheetsetstate;
UPort? connectedTo;
ConnectionBarState() {
serial = getSerial();
serial.listen((line) {});
_ConnectionBarState() {
timer = Timer.periodic(const Duration(seconds: 1), (_) async {
if (sheetsetstate == null) return;
var ports = await serial.getPorts();
var ports = await widget.serial.getPorts();
setState(() {
_ports = ports;
});
});
}
@override
void dispose() {
timer.cancel();
super.dispose();
}
@override
void setState(VoidCallback fn) {
super.setState(fn);
@ -40,14 +46,15 @@ class ConnectionBarState extends State<ConnectionBar> {
void showModal() async {
if (connectedTo != null && connectedTo!.connected) {
await serial.disconnect();
await widget.serial.disconnect();
setState(() {
connectedTo = null;
});
} else {
var ports = await serial.getPorts();
var ports = await widget.serial.getPorts();
setState(() {
_ports = ports;
_status = "";
});
// ignore: use_build_context_synchronously
showModalBottomSheet(
@ -56,6 +63,7 @@ class ConnectionBarState extends State<ConnectionBar> {
builder: (BuildContext context, StateSetter setStateM) {
sheetsetstate = setStateM;
return BottomSheet(
enableDrag: false,
builder: (context) => Column(children: [
Padding(
padding: const EdgeInsets.all(8),
@ -80,24 +88,24 @@ class ConnectionBarState extends State<ConnectionBar> {
Text(port.connected ? "Disconnect" : "Connect"),
onPressed: () async {
if (port.connected) {
await serial.disconnect();
await widget.serial.disconnect();
setState(() {
connectedTo = null;
_status = "Successfully disconnected!";
});
} else {
if (await serial.connect(port)) {
if (await widget.serial.connect(port)) {
setState(() {
connectedTo = port;
_status = "Successfully connected!";
});
}else{
} else {
setState(() {
_status = "Error while connecting!";
});
}
}
var ports = await serial.getPorts();
var ports = await widget.serial.getPorts();
setState(() {
_ports = ports;
});

19
lib/lib/boxes.dart Normal file
View file

@ -0,0 +1,19 @@
import 'package:flutter/material.dart';
class SerialCommand {
String command;
String arg;
SerialCommand(this.command, this.arg);
static SerialCommand? parse(String line) {
if (!line.startsWith("<")) return null;
int cend = line.indexOf(">");
if (cend < 0) return null;
return SerialCommand(
line.substring(1, cend), line.substring(cend + 1).replaceAll("\r", ""));
}
}
abstract class JuBox extends StatefulWidget {
const JuBox({super.key});
}

View file

@ -1,11 +1,33 @@
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'dart:typed_data';
import 'package:flutter_libserialport/flutter_libserialport.dart';
import 'package:ju_rc_app/lib/boxes.dart';
import 'package:usb_serial/transaction.dart';
import 'package:usb_serial/usb_serial.dart';
enum SerialMessageType { systemSend, userSend, received }
class LimitedList<T> {
final int maxLength;
final List<T> _items = [];
LimitedList(this.maxLength);
void add(T item) {
if (_items.length >= maxLength) {
_items.removeAt(0);
}
_items.add(item);
}
List<T> last(int len) => _items.sublist(max(0, _items.length - len));
List<T> get items => _items;
}
abstract class UPort {
String productName;
String manufacturerName;
@ -17,33 +39,48 @@ abstract class USerial {
Future<List<UPort>> getPorts();
Future<bool> connect(UPort port);
Future<void> disconnect();
Future<bool> sprint(String data);
Future<bool> sprintln(String data) {
return sprint("$data\n");
Future<bool> _sprint(String data);
Future<bool> sprintln(String data, {bool system = false}) {
lines.add((
system ? SerialMessageType.systemSend : SerialMessageType.userSend,
DateTime.now(),
data
));
return _sprint("$data\n");
}
List<String> lines = [];
Function(String)? _callback;
LimitedList<(SerialMessageType, DateTime, String)> lines =
LimitedList(524288);
final List<Function(String, SerialCommand?)> _callback = [];
void _receive(String line) {
lines.add(line);
if (_callback != null) {
_callback!(line);
lines.add((SerialMessageType.received, DateTime.now(), line));
var parsed = SerialCommand.parse(line);
for (var f in _callback) {
f(line, parsed);
}
}
void listen(Function(String) callback) {
_callback = callback;
void listen(Function(String, SerialCommand?) callback) {
_callback.add(callback);
}
void removeListen(Function(String, SerialCommand?) callback) {
_callback.remove(callback);
}
}
USerial? _serial;
USerial getSerial() {
if (_serial != null) return _serial as USerial;
if (Platform.isAndroid) {
return USerialAndroid();
_serial = USerialAndroid();
} else if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) {
return USerialPC();
_serial = USerialPC();
} else {
throw UnimplementedError();
}
return _serial as USerial;
}
class UPortAndroid extends UPort {
@ -110,7 +147,7 @@ class USerialAndroid extends USerial {
115200, UsbPort.DATABITS_8, UsbPort.STOPBITS_1, UsbPort.PARITY_NONE);
_transaction = Transaction.stringTerminated(
_port!.inputStream as Stream<Uint8List>, Uint8List.fromList([13, 10]));
_port!.inputStream as Stream<Uint8List>, Uint8List.fromList([10]));
_subscription = _transaction!.stream.listen((String line) {
_receive(line);
@ -140,7 +177,7 @@ class USerialAndroid extends USerial {
}
@override
Future<bool> sprint(String data) async {
Future<bool> _sprint(String data) async {
if (_port == null) return false;
await _port!.write(Uint8List.fromList(data.codeUnits));
return true;
@ -205,7 +242,7 @@ class USerialPC extends USerial {
_reader = SerialPortReader(_port!);
_transaction = Transaction.stringTerminated(
_reader!.stream, Uint8List.fromList([13, 10]));
_reader!.stream, Uint8List.fromList([10]));
_subscription = _transaction!.stream.listen((line) {
_receive(line);
@ -245,7 +282,7 @@ class USerialPC extends USerial {
}
@override
Future<bool> sprint(String data) async {
Future<bool> _sprint(String data) async {
try {
if (_port == null) return false;
_port!.write(Uint8List.fromList(data.codeUnits));

View file

@ -1,6 +1,10 @@
import 'package:flutter/material.dart';
import 'package:ju_rc_app/connector.dart';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:ju_rc_app/boxes/console.dart';
import 'package:ju_rc_app/boxes/state.dart';
import 'package:ju_rc_app/connector.dart';
import 'package:ju_rc_app/lib/boxes.dart';
void main() {
runApp(const MyApp());
@ -14,8 +18,15 @@ class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
title: 'JuRcApp',
themeMode: ThemeMode.system,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
//colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
brightness: Brightness.light,
useMaterial3: true,
),
darkTheme: ThemeData(
//colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
brightness: Brightness.dark,
useMaterial3: true,
),
home: const MyHomePage(title: 'App for juRc'),
@ -32,36 +43,58 @@ class MyHomePage extends StatefulWidget {
}
class _MyHomePageState extends State<MyHomePage> {
ConnectionBar conBar = const ConnectionBar();
ConnectionBar conBar = ConnectionBar();
static List<JuBox> boxes = [const SerialBox(), const ControllerState()];
@override
void initState() {
super.initState();
setState(() {
});
setState(() {});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
conBar
],
title: Text(
widget.title,
style: const TextStyle(
fontSize: 16,
),
),
toolbarHeight: 40,
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
tooltip: 'Reload',
child: const Icon(Icons.replay),
body: CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: conBar,
),
SliverPadding(
padding: const EdgeInsets.all(10.0),
sliver: SliverGrid.extent(
maxCrossAxisExtent:
Platform.isAndroid || Platform.isIOS ? 300.0 : 600.0,
crossAxisSpacing: 10.0,
mainAxisSpacing: 10.0,
children: boxes
.map((b) => Container(
decoration: BoxDecoration(
border: Border.all(
width: 0,
color: const Color.fromARGB(0, 0, 0, 0)),
borderRadius: BorderRadius.circular(20.0),
color: Theme.of(context)
.colorScheme
.secondaryContainer,
),
padding: const EdgeInsets.all(0.3 * 2 * 20),
child: b,
))
.toList(),
)),
],
),
);
}

View file

@ -69,10 +69,10 @@ packages:
dependency: transitive
description:
name: collection
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev"
source: hosted
version: "1.17.2"
version: "1.18.0"
convert:
dependency: transitive
description:
@ -192,6 +192,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.0.17"
intl:
dependency: "direct main"
description:
name: intl
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
url: "https://pub.dev"
source: hosted
version: "0.18.1"
js:
dependency: transitive
description:
@ -260,10 +268,10 @@ packages:
dependency: transitive
description:
name: petitparser
sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750
sha256: eeb2d1428ee7f4170e2bd498827296a18d4e7fc462b71727d111c0ac7707cfa6
url: "https://pub.dev"
source: hosted
version: "5.4.0"
version: "6.0.1"
platform:
dependency: transitive
description:
@ -321,18 +329,18 @@ packages:
dependency: transitive
description:
name: stack_trace
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev"
source: hosted
version: "1.11.0"
version: "1.11.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.1.2"
string_scanner:
dependency: transitive
description:
@ -353,10 +361,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.dev"
source: hosted
version: "0.6.0"
version: "0.6.1"
typed_data:
dependency: transitive
description:
@ -381,14 +389,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.4"
web:
dependency: transitive
description:
name: web
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
url: "https://pub.dev"
source: hosted
version: "0.1.4-beta"
xml:
dependency: transitive
description:
name: xml
sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84"
sha256: af5e77e9b83f2f4adc5d3f0a4ece1c7f45a2467b695c2540381bac793e34e556
url: "https://pub.dev"
source: hosted
version: "6.3.0"
version: "6.4.2"
yaml:
dependency: transitive
description:
@ -398,5 +414,5 @@ packages:
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.1.0-163.1.beta <4.0.0"
dart: ">=3.1.0-185.0.dev <4.0.0"
flutter: ">=3.0.0"

View file

@ -38,6 +38,7 @@ dependencies:
flutter_libserialport: ^0.3.0
usb_serial: ^0.5.1
rive: ^0.11.16
intl: ^0.18.1
dev_dependencies:
flutter_test: