-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Create waypoint queue widget #25
base: main
Are you sure you want to change the base?
Changes from all commits
1d2484c
220c200
a514f54
f89f1c5
5487aa3
403fb21
9b2d055
1b41b08
6330ce4
fd73cfc
5a8cee9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:imacs/modules/queue_waypoints.dart'; | ||
|
||
/// Widget to queue waypoints of a drone using MAVLink communication. | ||
/// | ||
/// This widget displays a table that contains a queue of waypoints to be | ||
/// sent to the drone. It provides functionality to add a waypoint to the | ||
/// queue as well as bypass the queue and send a waypoint to the drone | ||
/// immediately. | ||
class WaypointQueue extends StatefulWidget { | ||
/// @brief Constructs a WaypointQueue widget. | ||
/// | ||
/// @param queueWaypoints | ||
/// QueueWaypoints class instance | ||
/// | ||
/// @param systemId | ||
/// system ID for command constructor | ||
/// | ||
/// @param componentId | ||
/// component ID for command constructor | ||
/// | ||
final QueueWaypoints queueWaypoints; | ||
final int systemId; | ||
final int componentId; | ||
|
||
const WaypointQueue({ | ||
Key? key, | ||
required this.systemId, | ||
required this.componentId, | ||
required this.queueWaypoints, | ||
}) : super(key: key); | ||
|
||
@override | ||
WaypointQueueState createState() => WaypointQueueState(); | ||
} | ||
|
||
/// State for the QueueWaypoints widget. | ||
class WaypointQueueState extends State<WaypointQueue> { | ||
final TextEditingController _latitudeInput = TextEditingController(); | ||
final TextEditingController _longitudeInput = TextEditingController(); | ||
final TextEditingController _altitudeInput = TextEditingController(); | ||
|
||
late double _latitude; | ||
late double _longitude; | ||
late double _altitude; | ||
|
||
/// Gets the waypoints from the TextFields | ||
void _getWaypointsFromInput() { | ||
try { | ||
_latitude = double.parse(_latitudeInput.text); | ||
_longitude = double.parse(_longitudeInput.text); | ||
_altitude = double.parse(_altitudeInput.text); | ||
} catch (e) { | ||
print('Enter valid numbers.'); | ||
} | ||
} | ||
|
||
/// Sends a command to queue a waypoint. | ||
void _queueWaypoint() { | ||
_getWaypointsFromInput(); | ||
widget.queueWaypoints.queueWaypoint( | ||
widget.systemId, | ||
widget.componentId, | ||
_latitude, | ||
_longitude, | ||
_altitude, | ||
); | ||
setState(() {}); | ||
} | ||
|
||
/// Sends a command to send a waypoint, bypassing the queue. | ||
void _queueWaypointWithoutQueue() { | ||
_getWaypointsFromInput(); | ||
widget.queueWaypoints.sendWaypointWithoutQueue( | ||
widget.systemId, | ||
widget.componentId, | ||
_latitude, | ||
_longitude, | ||
_altitude, | ||
); | ||
} | ||
|
||
/// Sends the first waypoint in the queue to the drone. | ||
void _sendNextWaypointInQueue() { | ||
widget.queueWaypoints.sendNextWaypointInQueue(); | ||
setState(() {}); | ||
} | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Try to center the elements |
||
return Column( | ||
BalajiLeninrajan marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Temporarily wrap this in a |
||
children: [ | ||
const Text( | ||
'Waypoint Queue', | ||
style: TextStyle(fontWeight: FontWeight.bold), | ||
), | ||
Expanded( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would caution against using |
||
child: ListView( | ||
children: [ | ||
DataTable( | ||
columns: const [ | ||
DataColumn(label: Text('Latitude')), | ||
DataColumn(label: Text('Longitude')), | ||
DataColumn(label: Text('Altitude')), | ||
], | ||
rows: widget.queueWaypoints.waypointQueue | ||
.map( | ||
((element) => DataRow( | ||
cells: <DataCell>[ | ||
DataCell(Text(element.x.toString())), | ||
DataCell(Text(element.y.toString())), | ||
DataCell(Text(element.z.toString())), | ||
], | ||
)), | ||
) | ||
.toList(), | ||
), | ||
const SizedBox(height: 16), | ||
ElevatedButton( | ||
onPressed: _sendNextWaypointInQueue, | ||
child: const Text('Send Next Waypoint in Queue to Drone')), | ||
const SizedBox(height: 16), | ||
const Text( | ||
'Enter a Waypoint Below', | ||
style: TextStyle(fontWeight: FontWeight.bold), | ||
), | ||
Padding( | ||
padding: const EdgeInsets.all(10.0), | ||
child: Row( | ||
mainAxisSize: MainAxisSize.min, | ||
children: [ | ||
SizedBox( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of using |
||
width: 120, | ||
height: 40, | ||
child: TextField( | ||
decoration: const InputDecoration( | ||
border: OutlineInputBorder(), | ||
hintText: 'Latitude', | ||
), | ||
controller: _latitudeInput, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. consider adding |
||
), | ||
), | ||
SizedBox( | ||
width: 120, | ||
height: 40, | ||
child: TextField( | ||
decoration: const InputDecoration( | ||
border: OutlineInputBorder(), | ||
hintText: 'Longitude', | ||
), | ||
controller: _longitudeInput, | ||
), | ||
), | ||
SizedBox( | ||
width: 120, | ||
height: 40, | ||
child: TextField( | ||
decoration: const InputDecoration( | ||
border: OutlineInputBorder(), | ||
hintText: 'Altitude', | ||
), | ||
controller: _altitudeInput, | ||
), | ||
), | ||
], | ||
), | ||
), | ||
const SizedBox(height: 16), | ||
OverflowBar(spacing: 10, children: [ | ||
ElevatedButton( | ||
onPressed: _queueWaypoint, | ||
child: const Text('Add Waypoint to Queue'), | ||
), | ||
const SizedBox(height: 16), | ||
ElevatedButton( | ||
onPressed: _queueWaypointWithoutQueue, | ||
child: const Text('Send Waypoint Immediately'), | ||
), | ||
]), | ||
], | ||
), | ||
), | ||
], | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter_test/flutter_test.dart'; | ||
import 'package:imacs/modules/mavlink_communication.dart'; | ||
import 'package:imacs/modules/queue_waypoints.dart'; | ||
import 'package:imacs/widgets/waypoint_queue_widget.dart'; | ||
|
||
void main() { | ||
group('WaypointQueue widget', () { | ||
testWidgets( | ||
'WaypointQueueWidget displays a table, text input fields, and 3 buttons', | ||
(WidgetTester tester) async { | ||
final mavlinkCommunication = MavlinkCommunication( | ||
MavlinkCommunicationType.tcp, '127.0.0.1', 14550); | ||
final queueWaypoints = QueueWaypoints(comm: mavlinkCommunication); | ||
|
||
await tester.pumpWidget(MaterialApp( | ||
home: Scaffold( | ||
body: WaypointQueue( | ||
queueWaypoints: queueWaypoints, | ||
systemId: 0, | ||
componentId: 0, | ||
), | ||
), | ||
)); | ||
|
||
expect(find.byType(ElevatedButton), findsNWidgets(3)); | ||
expect(find.byType(DataTable), findsOneWidget); | ||
expect(find.byType(TextField), findsNWidgets(3)); | ||
}); | ||
testWidgets( | ||
'WaypointQueue sends a waypoint to the drone without queueing it', | ||
(WidgetTester tester) async { | ||
final mavlinkCommunication = MavlinkCommunication( | ||
MavlinkCommunicationType.tcp, '127.0.0.1', 14550); | ||
final queueWaypoints = QueueWaypoints(comm: mavlinkCommunication); | ||
|
||
await tester.pumpWidget(MaterialApp( | ||
home: Scaffold( | ||
body: WaypointQueue( | ||
queueWaypoints: queueWaypoints, | ||
systemId: 0, | ||
componentId: 0, | ||
), | ||
), | ||
)); | ||
|
||
/// Enter a waypoint in the text fields | ||
await tester.enterText( | ||
find.ancestor( | ||
of: find.text('Latitude'), | ||
matching: find.byType(TextField), | ||
), | ||
'10.01'); | ||
await tester.enterText( | ||
find.ancestor( | ||
of: find.text('Longitude'), | ||
matching: find.byType(TextField), | ||
), | ||
'-20.02'); | ||
await tester.enterText( | ||
find.ancestor( | ||
of: find.text('Altitude'), | ||
matching: find.byType(TextField), | ||
), | ||
'30.03'); | ||
|
||
expect(find.text('10.01'), findsOneWidget); | ||
expect(find.text('-20.02'), findsOneWidget); | ||
expect(find.text('30.03'), findsOneWidget); | ||
|
||
await tester.tap( | ||
find.widgetWithText(ElevatedButton, 'Send Waypoint Immediately')); | ||
await tester.pump(); | ||
|
||
expect(queueWaypoints.waypointQueue.length, 0); | ||
expect(find.text('10.01'), findsOneWidget); | ||
expect(find.text('-20.02'), findsOneWidget); | ||
expect(find.text('30.03'), findsOneWidget); | ||
}); | ||
|
||
testWidgets('WaypointQueue adds waypoint to queue on button press', | ||
(WidgetTester tester) async { | ||
final mavlinkCommunication = MavlinkCommunication( | ||
MavlinkCommunicationType.tcp, '127.0.0.1', 14550); | ||
final queueWaypoints = QueueWaypoints(comm: mavlinkCommunication); | ||
|
||
await tester.pumpWidget(MaterialApp( | ||
home: Scaffold( | ||
body: WaypointQueue( | ||
queueWaypoints: queueWaypoints, | ||
systemId: 0, | ||
componentId: 0, | ||
), | ||
), | ||
)); | ||
|
||
/// Enter a waypoint in the text fields | ||
await tester.enterText( | ||
find.ancestor( | ||
of: find.text('Latitude'), | ||
matching: find.byType(TextField), | ||
), | ||
'10.01'); | ||
await tester.enterText( | ||
find.ancestor( | ||
of: find.text('Longitude'), | ||
matching: find.byType(TextField), | ||
), | ||
'-20.02'); | ||
await tester.enterText( | ||
find.ancestor( | ||
of: find.text('Altitude'), | ||
matching: find.byType(TextField), | ||
), | ||
'30.03'); | ||
|
||
expect(find.text('10.01'), findsOneWidget); | ||
expect(find.text('-20.02'), findsOneWidget); | ||
expect(find.text('30.03'), findsOneWidget); | ||
|
||
await tester | ||
.tap(find.widgetWithText(ElevatedButton, 'Add Waypoint to Queue')); | ||
await tester.pump(); | ||
|
||
expect(find.text('10.01'), findsNWidgets(2)); | ||
expect(find.text('-20.02'), findsNWidgets(2)); | ||
expect(find.text('30.03'), findsNWidgets(2)); | ||
expect(queueWaypoints.waypointQueue.length, 1); | ||
expect(queueWaypoints.waypointQueue[0].x, 10.01); | ||
expect(queueWaypoints.waypointQueue[0].y, -20.02); | ||
expect(queueWaypoints.waypointQueue[0].z, 30.03); | ||
}); | ||
testWidgets('WaypointQueue sends first waypoint in queue to drone', | ||
(WidgetTester tester) async { | ||
final mavlinkCommunication = MavlinkCommunication( | ||
MavlinkCommunicationType.tcp, '127.0.0.1', 14550); | ||
await mavlinkCommunication.tcpSocketInitializationFlag.future; | ||
final queueWaypoints = QueueWaypoints(comm: mavlinkCommunication); | ||
|
||
queueWaypoints.queueWaypoint(0, 0, 10.01, -20.02, 30.03); | ||
|
||
await tester.pumpWidget(MaterialApp( | ||
home: Scaffold( | ||
body: WaypointQueue( | ||
queueWaypoints: queueWaypoints, | ||
systemId: 0, | ||
componentId: 0, | ||
), | ||
), | ||
)); | ||
|
||
expect(queueWaypoints.waypointQueue.length, 1); | ||
expect(find.text('10.01'), findsOneWidget); | ||
expect(find.text('-20.02'), findsOneWidget); | ||
expect(find.text('30.03'), findsOneWidget); | ||
expect(queueWaypoints.waypointQueue[0].x, 10.01); | ||
expect(queueWaypoints.waypointQueue[0].y, -20.02); | ||
expect(queueWaypoints.waypointQueue[0].z, 30.03); | ||
|
||
await tester.tap(find.widgetWithText( | ||
ElevatedButton, 'Send Next Waypoint in Queue to Drone')); | ||
await tester.pumpAndSettle(); | ||
|
||
expect(find.text('10.01'), findsNothing); | ||
expect(find.text('-20.02'), findsNothing); | ||
expect(find.text('30.03'), findsNothing); | ||
expect(queueWaypoints.waypointQueue.length, 0); | ||
}); | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've confirmed this with Aaron, you can send all the waypoints at once and mavlink will just kinda figure it out. The best way to do this is to create a method in the queueWaypoints class to handle this, lmk if you want to try doing that or if I should do that for you.