Skip to content
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

Mini Dashboard #186

Draft
wants to merge 22 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
163cd99
Initial rough draft of mini dashboard
Gold872 Sep 24, 2024
f3287e7
Added gamepad buttons and changed alignments
Gold872 Sep 24, 2024
e337b6d
Added logo and dark mode
Gold872 Sep 25, 2024
9c3565d
Improved gamepad
Gold872 Sep 25, 2024
233cff9
Fixed home page not displaying voltage
Gold872 Sep 25, 2024
89f8cda
Fixed vitals metrics
Gold872 Sep 25, 2024
7606bf4
Added view tab
Gold872 Sep 25, 2024
8c9cd46
Merge branch 'main' of https://github.com/BinghamtonRover/Dashboard i…
Gold872 Sep 25, 2024
f21724d
Fixed pubspec.lock
Gold872 Sep 25, 2024
a9b5289
Updated home page to work with new log and sockets methods
Gold872 Sep 25, 2024
76a06e1
Made mini logs page and removed device preview
Gold872 Sep 25, 2024
59cd956
Merge branch 'main' of https://github.com/BinghamtonRover/Dashboard i…
Gold872 Sep 30, 2024
919a33b
Fix build errors
Gold872 Sep 30, 2024
5680b69
Merge branch 'main' of https://github.com/BinghamtonRover/Dashboard i…
Gold872 Nov 11, 2024
481261f
Merge branch 'main' of https://github.com/BinghamtonRover/Dashboard i…
Gold872 Nov 22, 2024
8cb5bff
Fixed build errors and added settings
Gold872 Nov 22, 2024
8252ca1
Added docs for mini.dart
Gold872 Nov 22, 2024
9bc706e
Fixed CI warnings (hopefully)
Gold872 Nov 22, 2024
e808521
Fixed idle switch
Gold872 Nov 22, 2024
093ebe9
Fix the last CI warning
Gold872 Nov 22, 2024
79e1161
Merge branch 'main' of https://github.com/BinghamtonRover/Dashboard i…
Gold872 Nov 27, 2024
ca745db
Reset network settings only when enabled
Gold872 Nov 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added assets/logo-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/logo-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
240 changes: 240 additions & 0 deletions lib/mini.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
import "dart:async";
import "dart:io";
import "package:flutter/material.dart";

import "package:rover_dashboard/app.dart";
import "package:rover_dashboard/data.dart";
import "package:rover_dashboard/models.dart";
import "package:rover_dashboard/pages.dart";
import "package:rover_dashboard/services.dart";
import "package:rover_dashboard/src/pages/mini_home.dart";
import "package:rover_dashboard/src/pages/mini_logs.dart";
import "package:rover_dashboard/src/pages/mini_metrics.dart";
import "package:rover_dashboard/widgets.dart";

/// View model for the Mini dashboard home page
///
/// Stores the function to define the extra widget displayed on the
/// footer, and initializes all necessary data, services, and other
/// view models
class MiniViewModel with ChangeNotifier {
Widget Function(BuildContext context)? _footerWidget;

/// Constructor for [MiniViewModel], calls [init] to setup mini dashboard
MiniViewModel() {
init();
}

set footerWidget(Widget Function(BuildContext context)? footerWidget) {
_footerWidget = footerWidget;
notifyListeners();
}

/// The builder for the footer widget
Widget Function(BuildContext context)? get footerWidget => _footerWidget;

/// Initializes necessary systems and models for the Mini Dashboard
///
/// Sets the rover type to localhost and disables the sockets until
/// it is manually turned on by the user
Future<void> init() async {
await services.init();
await models.init();
await models.sockets.setRover(RoverType.localhost);
await models.sockets.disable();

models.settings.addListener(notifyListeners);

notifyListeners();
}

@override
void dispose() {
models.settings.removeListener(notifyListeners);
super.dispose();
}
}

/// The main app page for the Mini dashboard
///
/// Displays a header with the dashboard version, a tab bar view
/// to select between the home page, metrics/controls, logs, and
/// a page to display a view
class MiniHomePage extends StatelessWidget {
/// The Mini Dashboard view model
final MiniViewModel model;

/// A const constructor for the mini home page
const MiniHomePage({required this.model});

@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text("Dashboard v${models.home.version ?? ''}"),
actions: [
IconButton(
onPressed: () => Navigator.of(context).pushNamed(Routes.settings),
icon: const Icon(Icons.settings),
),
const SizedBox(width: 10),
const PowerButton(),
const SizedBox(width: 5),
],
),
body: DefaultTabController(
length: 4,
child: Column(
children: [
const TabBar(
tabs: [
Tab(text: "Home"),
Tab(text: "Metrics & Controls"),
Tab(text: "Logs"),
Tab(text: "View"),
],
),
Expanded(
child: TabBarView(
children: [
const MiniHome(),
MiniMetrics(models.rover.metrics),
MiniLogs(miniViewModel: model),
ViewsWidget(),
],
),
),
],
),
),
bottomNavigationBar: MiniFooter(model),
);
}

/// Button to set the rover status to [RoverStatus.POWER_OFF], shutting off the rover
///
/// Displays a confirmation dialog before shutting down
class PowerButton extends StatelessWidget {
/// Constructor for power button
const PowerButton({super.key});

@override
Widget build(BuildContext context) => Container(
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
padding: EdgeInsets.zero,
child: IconButton(
icon: const Icon(
Icons.power_settings_new,
color: Colors.red,
),
onPressed: () async {
await showDialog<void>(
context: context,
barrierDismissible: false,
builder: (_) => AlertDialog(
title: const Text("Are you sure?"),
content: const Text("This will turn off the rover and you must physically turn it back on again"),
actions: [
TextButton(child: const Text("Cancel"), onPressed: () => Navigator.of(context).pop()),
ElevatedButton(
onPressed: () {
models.rover.settings.setStatus(RoverStatus.POWER_OFF);
Navigator.of(context).pop();
},
child: const Text("Continue"),
),
],
),
);
},
),
);
}

/// The footer for the mini dashboard
///
/// Displays any necessary messages in the left of the footer, and
/// a custom defined widget on the right side. The custom widget space
/// is used by pages such as the Logs page to display extra information
/// in a small amount of space
class MiniFooter extends ReusableReactiveWidget<MiniViewModel> {
/// Const constructor for the mini dashboard footer
const MiniFooter(super.model) : super();

@override
Widget build(BuildContext context, MiniViewModel model) => ColoredBox(
color: context.colorScheme.secondary,
child: Row(
children: [
MessageDisplay(showLogs: false),
const Spacer(),
if (model.footerWidget != null) model.footerWidget!.call(context),
],
),
);
}

/// The main widget for the mini dashboard
///
/// Initializes the Material App, necessary themes, and defines the
/// routes to the home and settings page
class MiniDashboard extends ReactiveWidget<MiniViewModel> {
/// Const constructor for the mini dashboard
const MiniDashboard();

@override
MiniViewModel createModel() => MiniViewModel();

@override
Widget build(BuildContext context, MiniViewModel model) => MaterialApp(
title: "Binghamton University Rover Team",
debugShowCheckedModeBanner: false,
themeMode: models.isReady ? models.settings.dashboard.themeMode : ThemeMode.system,
theme: ThemeData(
colorScheme: const ColorScheme.light(
primary: binghamtonGreen,
secondary: binghamtonGreen,
),
appBarTheme: const AppBarTheme(
backgroundColor: binghamtonGreen,
foregroundColor: Colors.white,
),
),
darkTheme: ThemeData.from(
colorScheme: const ColorScheme.dark(
primary: binghamtonGreen,
secondary: binghamtonGreen,
),
),
home: MiniHomePage(model: model),
routes: {
Routes.home: (_) => MiniHomePage(model: model),
Routes.settings: (_) => SettingsPage(),
},
);
}

/// Network errors that can be fixed by a simple reset.
const networkErrors = {1234, 1231};

void main() async {
runZonedGuarded(() => runApp(const MiniDashboard()), (error, stack) async {
if (error is SocketException && networkErrors.contains(error.osError!.errorCode)) {
models.home.setMessage(severity: Severity.critical, text: "Network error, restart by clicking the network icon");
} else {
models.home.setMessage(severity: Severity.critical, text: "Dashboard error. See the logs", logMessage: false);
models.logs.handleLog(
BurtLog(
level: BurtLogLevel.critical,
title: "Dashboard Error. Click for details",
body: "$error\n$stack",
device: Device.DASHBOARD,
),
);
Error.throwWithStackTrace(error, stack);
}
});
}
4 changes: 3 additions & 1 deletion lib/src/data/metrics/vitals.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import "package:rover_dashboard/models.dart";
/// Metrics about the vitals of the rover.
class VitalsMetrics extends Metrics {
/// A const constructor.
VitalsMetrics() : super(DriveData());
VitalsMetrics() : super(DriveData()) {
models.rover.metrics.drive.addListener(notifyListeners);
}

@override
Version parseVersion(Message message) => Version(major: 0, minor: 0);
Expand Down
32 changes: 23 additions & 9 deletions lib/src/models/data/sockets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ class Sockets extends Model {
/// The rover-like system currently in use.
RoverType rover = RoverType.rover;

/// Whether or not the sockets are currently enabled
bool isEnabled = true;

/// The [InternetAddress] to use instead of the address on the rover.
InternetAddress? get addressOverride => switch (rover) {
RoverType.rover => null,
Expand All @@ -48,20 +51,31 @@ class Sockets extends Model {
_ => null,
};

@override
Future<void> init() async {
for (final socket in sockets) {
socket.connectionStatus.addListener(() => socket.connectionStatus.value
@override
Future<void> init() async {
isEnabled = true;
for (final socket in sockets) {
socket.connectionStatus.addListener(() => socket.connectionStatus.value
? onConnect(socket.device)
: onDisconnect(socket.device),
);
socket.messages.listen(models.messages.addMessage);
await socket.init();
}
final level = Logger.level;
Logger.level = LogLevel.warning;
await updateSockets();
Logger.level = level;
}
final level = Logger.level;
Logger.level = LogLevel.warning;
await updateSockets();
Logger.level = level;
notifyListeners();
}

/// Disconnects from all sockets without restarting them
Future<void> disable() async {
isEnabled = false;
for (final socket in sockets) {
await socket.dispose();
}
notifyListeners();
}

@override
Expand Down
2 changes: 1 addition & 1 deletion lib/src/models/rover/metrics.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class RoverMetrics extends Model {
final gripper = GripperMetrics();

/// Vitals data from the rover.
final vitals = VitalsMetrics();
late final vitals = VitalsMetrics();

/// A list of all the metrics to iterate over.
///
Expand Down
2 changes: 1 addition & 1 deletion lib/src/models/view/builders/settings_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ class SettingsBuilder extends ValueBuilder<Settings> {
value.network.toJson(),
));
await models.settings.update(value);
if (resetSockets) {
if (resetSockets && models.sockets.isEnabled) {
await models.sockets.reset();
}
models.video.reset();
Expand Down
Loading
Loading