diff --git a/flutter_app/.metadata b/flutter_app/.metadata index ea59ad0..dc16213 100644 --- a/flutter_app/.metadata +++ b/flutter_app/.metadata @@ -4,6 +4,7 @@ # This file should be version controlled and should not be manually edited. version: + revision: "2663184aa79047d0a33a14a3b607954f8fdd8730" revision: "2663184aa79047d0a33a14a3b607954f8fdd8730" channel: "stable" @@ -15,6 +16,12 @@ migration: - platform: root create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + - platform: linux + create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + - platform: macos + create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 - platform: windows create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 diff --git a/flutter_app/lib/defaults.dart b/flutter_app/lib/defaults.dart new file mode 100644 index 0000000..9649b04 --- /dev/null +++ b/flutter_app/lib/defaults.dart @@ -0,0 +1,7 @@ +import 'package:imacs/modules/mavlink_communication.dart'; + +final class Defaults { + static const communicationType = MavlinkCommunicationType.tcp; + static const communicationAddress = '127.0.0.1'; + static const tcpPort = 14550; +} diff --git a/flutter_app/lib/modules/change_port_protocol.dart b/flutter_app/lib/modules/change_port_protocol.dart new file mode 100644 index 0000000..e1fc8a2 --- /dev/null +++ b/flutter_app/lib/modules/change_port_protocol.dart @@ -0,0 +1,18 @@ +import 'dart:developer'; + +import 'package:imacs/modules/mavlink_communication.dart'; + +const String moduleName = "Change Port/Protocol"; + +class ChangePortProtocol { + final MavlinkCommunication comm; + + ChangePortProtocol({required this.comm}); + + void updateCommParams(MavlinkCommunicationType connectionType, + String connectionAddress, int tcpPort) { + comm.updateConnectionParams(connectionType, connectionAddress, tcpPort); + + log('[$moduleName] Communication params updated to: $connectionType, $connectionAddress, $tcpPort'); + } +} diff --git a/flutter_app/lib/modules/mavlink_communication.dart b/flutter_app/lib/modules/mavlink_communication.dart index 121d55b..3474ca4 100644 --- a/flutter_app/lib/modules/mavlink_communication.dart +++ b/flutter_app/lib/modules/mavlink_communication.dart @@ -18,7 +18,9 @@ class MavlinkCommunication { final List waypointQueue = []; - final MavlinkCommunicationType _connectionType; + MavlinkCommunicationType _connectionType; + String _connectionAddress; + int _tcpPort; late Stream _stream; late SerialPort _serialPort; @@ -28,17 +30,10 @@ class MavlinkCommunication { MavlinkCommunication(MavlinkCommunicationType connectionType, String connectionAddress, int tcpPort) - : _connectionType = connectionType { - switch (_connectionType) { - case MavlinkCommunicationType.tcp: - log('[$moduleName] Trying to start TCP connection'); - _startupTcpPort(connectionAddress, tcpPort); - break; - case MavlinkCommunicationType.serial: - log('[$moduleName] Trying to start Serial connection'); - _startupSerialPort(connectionAddress); - break; - } + : _connectionType = connectionType, + _connectionAddress = connectionAddress, + _tcpPort = tcpPort { + _startupPort(connectionType, connectionAddress, tcpPort); } _startupTcpPort(String connectionAddress, int tcpPort) async { @@ -55,6 +50,20 @@ class MavlinkCommunication { _stream = serialPortReader.stream; } + _startupPort(MavlinkCommunicationType connectionType, + String connectionAddress, int tcpPort) { + switch (_connectionType) { + case MavlinkCommunicationType.tcp: + log('[$moduleName] Trying to start TCP connection'); + _startupTcpPort(connectionAddress, tcpPort); + break; + case MavlinkCommunicationType.serial: + log('[$moduleName] Trying to start Serial connection'); + _startupSerialPort(connectionAddress); + break; + } + } + _writeToTcpPort(MavlinkFrame frame) { _tcpSocket.write(frame.serialize()); log('[$moduleName] Wrote a message to TCP Port. Frame ID: ${frame.componentId}'); @@ -81,7 +90,18 @@ class MavlinkCommunication { } } + void updateConnectionParams(MavlinkCommunicationType connectionType, + String connectionAddress, int tcpPort) { + _connectionType = connectionType; + _connectionAddress = connectionAddress; + _tcpPort = tcpPort; + + _startupPort(connectionType, connectionAddress, tcpPort); + } + MavlinkCommunicationType get connectionType => _connectionType; + String get connectionAddress => _connectionAddress; + int get tcpPort => _tcpPort; Completer get tcpSocketInitializationFlag => _tcpSocketInitializationFlag; Socket get tcpSocket => _tcpSocket; diff --git a/flutter_app/lib/screens/home_screen.dart b/flutter_app/lib/screens/home_screen.dart index 09802e9..269620d 100644 --- a/flutter_app/lib/screens/home_screen.dart +++ b/flutter_app/lib/screens/home_screen.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:imacs/defaults.dart'; import 'package:imacs/modules/mavlink_communication.dart'; import 'package:imacs/modules/get_drone_information.dart'; import 'package:imacs/widgets/drone_information_widget.dart'; @@ -8,8 +9,9 @@ class HomePage extends StatelessWidget { HomePage({Key? key, required this.title}) : super(key: key); final String title; - final comm = - MavlinkCommunication(MavlinkCommunicationType.tcp, '127.0.0.1', 14550); + + late final comm = MavlinkCommunication(Defaults.communicationType, + Defaults.communicationAddress, Defaults.tcpPort); @override Widget build(BuildContext context) { diff --git a/flutter_app/lib/widgets/change_port_protocol_widget.dart b/flutter_app/lib/widgets/change_port_protocol_widget.dart new file mode 100644 index 0000000..8277940 --- /dev/null +++ b/flutter_app/lib/widgets/change_port_protocol_widget.dart @@ -0,0 +1,93 @@ +import 'package:flutter/material.dart'; +import 'package:imacs/modules/change_port_protocol.dart'; +import 'package:imacs/modules/mavlink_communication.dart'; + +class PortProtocolChanger extends StatefulWidget { + final MavlinkCommunication comm; + final ChangePortProtocol changePortProtocol; + + const PortProtocolChanger( + {super.key, required this.comm, required this.changePortProtocol}); + + @override + State createState() => _PortProtocolChangerState(); +} + +class _PortProtocolChangerState extends State { + late final TextEditingController addressController = + TextEditingController.fromValue( + TextEditingValue(text: widget.comm.connectionAddress)); + late final portController = TextEditingController.fromValue( + TextEditingValue(text: widget.comm.tcpPort.toString())); + + late MavlinkCommunicationType? selectedType = widget.comm.connectionType; + + String statusMsg = ""; + + @override + void dispose() { + addressController.dispose(); + portController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + border: Border.all(color: Colors.black), + ), + width: 400, + padding: const EdgeInsets.all(10.0), + child: Column(children: [ + DropdownMenu( + dropdownMenuEntries: MavlinkCommunicationType.values + .map>((entry) { + return DropdownMenuEntry(value: entry, label: entry.name); + }).toList(), + initialSelection: widget.comm.connectionType, + onSelected: (MavlinkCommunicationType? newValue) { + setState(() { + selectedType = newValue; + }); + }), + TextField( + controller: addressController, + decoration: const InputDecoration(hintText: "Address"), + ), + TextField( + controller: portController, + decoration: const InputDecoration(hintText: "Port"), + ), + const SizedBox(height: 8), + ElevatedButton( + child: const Text("Update Connection Params"), + onPressed: () { + var tcpPort = int.tryParse(portController.text); + + if (tcpPort == null) { + setState(() { + statusMsg = "Invalid port"; + }); + return; + } else if (tcpPort <= 0) { + setState(() { + statusMsg = "Port must be positive integer"; + }); + return; + } + + widget.changePortProtocol.updateCommParams( + selectedType ?? widget.comm.connectionType, + addressController.text, + tcpPort); + setState(() { + statusMsg = "Successfully changed"; + }); + }, + ), + Padding( + padding: const EdgeInsets.only(top: 5), child: Text(statusMsg)), + ])); + } +} diff --git a/flutter_app/test/widget/change_port_protocol_widget_test.dart b/flutter_app/test/widget/change_port_protocol_widget_test.dart new file mode 100644 index 0000000..1175d4d --- /dev/null +++ b/flutter_app/test/widget/change_port_protocol_widget_test.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:imacs/modules/change_port_protocol.dart'; +import 'package:imacs/modules/mavlink_communication.dart'; +import 'package:imacs/widgets/change_port_protocol_widget.dart'; + +void main() { + group('Change Port/Protocol Widget', () { + testWidgets( + 'PortProtocolChanger displays a dropdown menu, a button, and two input fields', + (WidgetTester tester) async { + MavlinkCommunication comm = MavlinkCommunication( + MavlinkCommunicationType.tcp, '127.0.0.1', 14550); + + await tester.pumpWidget(MaterialApp( + home: Scaffold( + body: PortProtocolChanger( + comm: comm, + changePortProtocol: ChangePortProtocol(comm: comm), + ), + ), + )); + + expect( + find.byType(DropdownMenu), findsOneWidget); + // Look for 3 TextFields because DropdownMenu also contains a TextField + expect(find.byType(TextField), findsNWidgets(3)); + expect(find.byType(ElevatedButton), findsOneWidget); + }); + + testWidgets('PortProtocolChanger changes communication params', + (WidgetTester tester) async { + final comm = MavlinkCommunication( + MavlinkCommunicationType.tcp, '127.0.0.1', 14550); + + await tester.pumpWidget(MaterialApp( + home: Scaffold( + body: PortProtocolChanger( + comm: comm, + changePortProtocol: ChangePortProtocol(comm: comm), + ), + ), + )); + + // Open the dropdown menu and select a mode + await tester.tap(find.byType(DropdownMenu)); + await tester.pumpAndSettle(); + final serialFinder = find.descendant( + of: find.byType(DropdownMenu), + matching: find.text('serial'), + ); + await tester.tap(serialFinder.first); + await tester.pumpAndSettle(); + + final textFieldFinder = find.byType(TextField); + await tester.enterText(textFieldFinder.at(1), "127.0.1.0"); + await tester.enterText(textFieldFinder.at(2), "14551"); + + // Press the change port/protocol button + await tester.tap(find.byType(ElevatedButton)); + await tester.pumpAndSettle(); + + expect(find.text('Successfully changed'), findsOneWidget); + }); + }); +}