diff --git a/lib/data.dart b/lib/data.dart index e458de7a7..3653503d3 100644 --- a/lib/data.dart +++ b/lib/data.dart @@ -16,7 +16,6 @@ export "src/data/metrics/arm.dart"; export "src/data/metrics/drive.dart"; export "src/data/metrics/gripper.dart"; export "src/data/metrics/position.dart"; -export "src/data/metrics/mars.dart"; export "src/data/metrics/metrics.dart"; export "src/data/metrics/science.dart"; diff --git a/lib/models.dart b/lib/models.dart index 5647eff8d..f44e718b5 100644 --- a/lib/models.dart +++ b/lib/models.dart @@ -41,7 +41,6 @@ export "src/models/rover/rover.dart"; // View models export "src/models/view/map.dart"; export "src/models/view/logs.dart"; -export "src/models/view/mars.dart"; export "src/models/view/science.dart"; export "src/models/view/timer.dart"; diff --git a/lib/src/data/metrics/arm.dart b/lib/src/data/metrics/arm.dart index de8829b14..5b9a82990 100644 --- a/lib/src/data/metrics/arm.dart +++ b/lib/src/data/metrics/arm.dart @@ -9,24 +9,23 @@ class ArmMetrics extends Metrics { String get name => "Arm Base"; /// Returns a description of a [MotorData]. - List getMotorData(MotorData motor) => [ - " Is moving? ${motor.isMoving}", - " Limit? ${motor.isLimitSwitchPressed}", - " Direction: ${motor.direction.humanName}", - " Steps: ${motor.currentStep} --> ${motor.targetStep}", - " Angle: ${motor.angle}", + List getMotorData(MotorData motor, String part) => [ + MetricLine("$part: Is moving? ${motor.isMoving}", severity: motor.isMoving ? Severity.info : null), + MetricLine("$part: Limit? ${motor.isLimitSwitchPressed}", severity: motor.isLimitSwitchPressed ? Severity.warning : null), + MetricLine("$part: Direction: ${motor.direction.humanName}"), + MetricLine("$part: Steps: ${motor.currentStep} --> ${motor.targetStep}"), + MetricLine("$part: Angle: ${motor.angle}"), ]; @override - List get allMetrics => [ - "IK: ", - " Current: ${data.currentPosition.prettyPrint}", - " Target: ${data.targetPosition.prettyPrint}", - "------------------------------", - "Swivel: ", ...getMotorData(data.base), - "------------------------------", - "Shoulder: ", ...getMotorData(data.shoulder), - "------------------------------", - "Elbow: ", ...getMotorData(data.elbow), + List get allMetrics => [ + MetricLine("IK: "), + MetricLine(" Current: ${data.currentPosition.prettyPrint}"), + MetricLine(" Target: ${data.targetPosition.prettyPrint}"), + ...getMotorData(data.base, "Swivel"), + MetricLine("------------------------------",), + ...getMotorData(data.shoulder, "Shoulder"), + MetricLine("------------------------------",), + ...getMotorData(data.elbow, "Elbow"), ]; } diff --git a/lib/src/data/metrics/drive.dart b/lib/src/data/metrics/drive.dart index 7c71aa4e9..2f24e1346 100644 --- a/lib/src/data/metrics/drive.dart +++ b/lib/src/data/metrics/drive.dart @@ -13,12 +13,37 @@ class DriveMetrics extends Metrics { @override String get name => "Drive"; + /// The severity based on the throttle speed. + Severity? get throttleSeverity { + if (data.throttle == 0) { + return null; + } else if (data.throttle <= 0.3) { + return Severity.info; + } else if (data.throttle <= 0.75) { + return Severity.warning; + } else { + return Severity.critical; + } + } + + /// The severity for the electrical metrics. + Severity? get electricalSeverity { + if (data.batteryVoltage == 0) return null; + if (data.batteryVoltage <= 25) { + return Severity.critical; + } else if (data.batteryVoltage <= 26) { + return Severity.warning; + } else { + return null; + } + } + @override - List get allMetrics => [ - "Throttle: ${data.throttle.toStringAsFixed(2)}", - "Left: ${data.left.toStringAsFixed(2)}", - "Right: ${data.right.toStringAsFixed(2)}", - "Battery: ${data.batteryVoltage.toStringAsFixed(2)}V, ${data.batteryCurrent.toStringAsFixed(2)}A, ${data.batteryTemperature.toStringAsFixed(2)}°F", + List get allMetrics => [ + MetricLine("Throttle: ${data.throttle.toStringAsFixed(2)}", severity: throttleSeverity), + MetricLine("Left: ${data.left.toStringAsFixed(2)}"), + MetricLine("Right: ${data.right.toStringAsFixed(2)}"), + MetricLine("Battery: ${data.batteryVoltage.toStringAsFixed(2)}V,${data.batteryCurrent.toStringAsFixed(2)}A, ${data.batteryTemperature.toStringAsFixed(2)}°F", severity: electricalSeverity), ]; @override diff --git a/lib/src/data/metrics/gripper.dart b/lib/src/data/metrics/gripper.dart index 70f0aa486..3c6f33763 100644 --- a/lib/src/data/metrics/gripper.dart +++ b/lib/src/data/metrics/gripper.dart @@ -9,20 +9,20 @@ class GripperMetrics extends Metrics { String get name => "Gripper"; /// Returns a human-readable description of a [MotorData]. - List getMotorData(MotorData motor) => [ - " Is moving? ${motor.isMoving}", - " Limit? ${motor.isLimitSwitchPressed}", - " Direction: ${motor.direction.humanName}", - " Steps: ${motor.currentStep} --> ${motor.targetStep}", - " Angle: ${motor.angle}", + List getMotorData(MotorData motor, String functionality) => [ + MetricLine("$functionality Is moving? ${motor.isMoving}", severity: motor.isMoving ? Severity.info : null), + MetricLine("$functionality Limit? ${motor.isLimitSwitchPressed}", severity: motor.isLimitSwitchPressed ? Severity.warning : null), + MetricLine("$functionality Direction: ${motor.direction.humanName}"), + MetricLine("$functionality Steps: ${motor.currentStep} --> ${motor.targetStep}"), + MetricLine("$functionality Angle: ${motor.angle}"), ]; @override - List get allMetrics => [ - "Lift: ", ...getMotorData(data.lift), - "------------------------------", - "Rotate: ", ...getMotorData(data.rotate), - "------------------------------", - "Pinch: ", ...getMotorData(data.pinch), + List get allMetrics => [ + ...getMotorData(data.lift, "Lift",), + MetricLine("------------------------------",), + ...getMotorData(data.rotate, "Rotate"), + MetricLine("------------------------------",), + ...getMotorData(data.pinch, "Pinch"), ]; } diff --git a/lib/src/data/metrics/mars.dart b/lib/src/data/metrics/mars.dart deleted file mode 100644 index 0026b1f94..000000000 --- a/lib/src/data/metrics/mars.dart +++ /dev/null @@ -1,38 +0,0 @@ -import "package:rover_dashboard/data.dart"; -import "package:rover_dashboard/models.dart"; - -/// Metrics reported by the MARS subsystem. -/// -/// The MARS subsystem tracks the rover's position and orients the unidirectional antenna to face -/// the rover for a better signal. These metrics are used to track the subsystem's accuracy and -/// visualize its movements. -class MarsMetrics extends Metrics { - /// A collection of metrics relevant for monitoring the MARS subsystem. - MarsMetrics() : super(MarsData()); - - @override - String get name => "MARS"; - - @override - List get allMetrics => [ - "Swivel: ${data.swivel}", - "Tilt: ${data.tilt}", - "Teensy: ${data.status.humanName}", - "GPS:", - " Latitude: ${data.coordinates.latitude}", - " Longitude: ${data.coordinates.longitude}", - " Altitude: ${data.coordinates.altitude}", - ]; - - /// Clears [MarsData.status], because the Teensy cannot be observed when the Pi is disconnected. - void clearStatus() { - data.clearStatus(); - notifyListeners(); - } - - @override - void update(MarsData value) { - super.update(value); - models.rover.metrics.position.baseStation = data.coordinates; - } -} diff --git a/lib/src/data/metrics/metrics.dart b/lib/src/data/metrics/metrics.dart index 401722ab4..64a4838f7 100644 --- a/lib/src/data/metrics/metrics.dart +++ b/lib/src/data/metrics/metrics.dart @@ -1,8 +1,22 @@ +// import "package:math"; + +import "dart:math"; + import "package:flutter/foundation.dart"; import "package:rover_dashboard/data.dart"; import "package:rover_dashboard/services.dart"; +/// Class to construct a Metric +class MetricLine { + /// Severity of the Metric + final Severity? severity; + /// Message for the Metric + final String text; + /// Constructor for the MetricLine class + MetricLine(this.text, {this.severity}); +} + /// A readout of metrics reported by one of the rover's subsystems. /// /// To use this class, create a subclass that extends this class with [T] as the generated @@ -24,7 +38,15 @@ abstract class Metrics with ChangeNotifier { /// /// Be sure to store the actual values as fields. This property should be a list of one /// user-friendly explanation per metric. - List get allMetrics; + List get allMetrics; + + /// Fetch the overall Security + Severity? get overallSeverity { + final indexes = [for (final metric in allMetrics) metric.severity?.index ?? -1]; + final index = indexes.reduce(max); + if (index == -1) return null; + return Severity.values[index]; + } /// Updates [data] with new data. void update(T value) { diff --git a/lib/src/data/metrics/position.dart b/lib/src/data/metrics/position.dart index 88160787a..01846fe91 100644 --- a/lib/src/data/metrics/position.dart +++ b/lib/src/data/metrics/position.dart @@ -22,17 +22,31 @@ class PositionMetrics extends Metrics { notifyListeners(); } + /// Gets the severity of the rover's orientation for both pitch and roll. + Severity? getRotationSeverity(double orientation) { + final abs = orientation.abs(); + if (abs >= 30) { + return Severity.critical; + } else if (abs >= 15) { + return Severity.warning; + } else if (abs >= 10) { + return Severity.info; + } else { + return null; + } + } + @override - List get allMetrics => [ - "GPS: ", - " Latitude: ${data.gps.latitude.toStringAsFixed(6)}°", - " Longitude: ${data.gps.longitude.toStringAsFixed(6)}°", - " Altitude: ${data.gps.altitude.toStringAsFixed(2)} m", - "Orientation:", - " X: ${data.orientation.x.toStringAsFixed(2)}°", - " Y: ${data.orientation.y.toStringAsFixed(2)}°", - " Z: ${data.orientation.z.toStringAsFixed(2)}°", - "Distance: ${data.gps.distanceTo(baseStation).toStringAsFixed(2)} m", + List get allMetrics => [ + MetricLine("GPS: "), + MetricLine(" Latitude: ${data.gps.latitude.toStringAsFixed(6)}°",), + MetricLine(" Longitude: ${data.gps.longitude.toStringAsFixed(6)}°",), + MetricLine(" Altitude: ${data.gps.altitude.toStringAsFixed(2)} m"), + MetricLine("Orientation:",), + MetricLine(" X: ${data.orientation.x.toStringAsFixed(2)}°", severity: getRotationSeverity(data.orientation.x)), + MetricLine(" Y: ${data.orientation.y.toStringAsFixed(2)}°", severity: getRotationSeverity(data.orientation.y)), + MetricLine(" Z: ${data.orientation.z.toStringAsFixed(2)}°"), + MetricLine("Distance: ${data.gps.distanceTo(baseStation).toStringAsFixed(2)} m",), ]; @override diff --git a/lib/src/data/metrics/science.dart b/lib/src/data/metrics/science.dart index 29d36cc0b..17cb938d6 100644 --- a/lib/src/data/metrics/science.dart +++ b/lib/src/data/metrics/science.dart @@ -12,12 +12,12 @@ class ScienceMetrics extends Metrics { String get name => "Science"; @override - List get allMetrics => [ - "Methane: ${data.methane.toStringAsFixed(3)}", - "CO2: ${data.co2.toStringAsFixed(3)}", - "Temperature: ${data.temperature.toStringAsFixed(3)}", - "Humidity: ${data.humidity.toStringAsFixed(3)}", - "pH: ${data.pH.toStringAsFixed(3)}", + List get allMetrics => [ + MetricLine("Methane: ${data.methane.toStringAsFixed(3)}"), + MetricLine("CO2: ${data.co2.toStringAsFixed(3)}"), + MetricLine("Temperature: ${data.temperature.toStringAsFixed(3)}"), + MetricLine("Humidity: ${data.humidity.toStringAsFixed(3)}"), + MetricLine("pH: ${data.pH.toStringAsFixed(3)}"), ]; @override diff --git a/lib/src/models/rover/metrics.dart b/lib/src/models/rover/metrics.dart index a71628c86..d59b7f2db 100644 --- a/lib/src/models/rover/metrics.dart +++ b/lib/src/models/rover/metrics.dart @@ -13,9 +13,6 @@ class RoverMetrics extends Model { /// Data from the drive subsystem. final drive = DriveMetrics(); - /// Data from the MARS subsystem. - final mars = MarsMetrics(); - /// Data from the HREI subsystem about the arm base. final arm = ArmMetrics(); @@ -26,7 +23,7 @@ class RoverMetrics extends Model { /// /// NOTE: Keep this as a getter, NOT a field. If this is made a field, then it won't update /// when new data is received. As a getter, every time it is called it will use new data. - List get allMetrics => [position, mars, drive, science, arm, gripper]; + List get allMetrics => [position, drive, science, arm, gripper]; @override Future init() async { @@ -45,11 +42,6 @@ class RoverMetrics extends Model { decoder: RoverPosition.fromBuffer, handler: position.update, ); - models.messages.registerHandler( - name: MarsData().messageName, - decoder: MarsData.fromBuffer, - handler: mars.update, - ); models.messages.registerHandler( name: ArmData().messageName, decoder: ArmData.fromBuffer, diff --git a/lib/src/models/view/mars.dart b/lib/src/models/view/mars.dart deleted file mode 100644 index 601c6f7f0..000000000 --- a/lib/src/models/view/mars.dart +++ /dev/null @@ -1,50 +0,0 @@ -import "dart:math"; -import "dart:ui"; -import "package:flutter/foundation.dart"; - -import "package:rover_dashboard/models.dart"; - -/// A view model for the MARS page. -/// -/// This model tracks the rover's position on-screen and the orientation of the MARS antenna. This -/// view model gets its data from [RoverMetrics.mars] and [RoverMetrics.position]. -class MarsModel with ChangeNotifier { - /// A shorthand for accessing [Rover.metrics]. - RoverMetrics get metrics => models.rover.metrics; - - /// Listens for updates to the rover's position or the MARS antenna's orientation. - MarsModel() { - metrics.position.addListener(update); - metrics.mars.addListener(update); - } - - @override - void dispose() { - metrics.mars.removeListener(update); - metrics.position.removeListener(update); - super.dispose(); - } - - /// The position of the rover on-screen. - Offset roverOffset = Offset.zero; - - /// The position of the base station on-screen. - Offset actualOffset = Offset.zero; - - /// Updates [roverOffset] and [actualOffset] based on new data. - void update() { - final rover = metrics.position.data.gps; - final baseStation = metrics.mars.data.coordinates; - final x = rover.longitude - baseStation.longitude; - final y = rover.latitude - baseStation.latitude; - final maxDistance = max(x.abs(), y.abs()); - if (maxDistance == 0) return; - roverOffset = Offset(x / maxDistance.abs(), y / maxDistance.abs()); - - // Update [actualX] and [actualY] - final angle = metrics.mars.data.swivel; - actualOffset = Offset(cos(angle), -sin(angle)); - - notifyListeners(); - } -} diff --git a/lib/src/widgets/navigation/sidebar.dart b/lib/src/widgets/navigation/sidebar.dart index 899d8547f..9148915e5 100644 --- a/lib/src/widgets/navigation/sidebar.dart +++ b/lib/src/widgets/navigation/sidebar.dart @@ -42,15 +42,33 @@ class MetricsList extends ReusableReactiveWidget { childrenPadding: const EdgeInsets.symmetric(horizontal: 16), title: Text( model.name, - style: Theme.of(context).textTheme.headlineSmall, + style: Theme.of(context).textTheme.headlineSmall?.copyWith( + color: model.overallSeverity?.color, + ), ), children: [ - for (final String metric in model.allMetrics) Text(metric), + for (final MetricLine metric in model.allMetrics) Text( + metric.text, + style: TextStyle( + color: metric.severity?.color, + ), + ), const SizedBox(height: 4), ], ); } +/// Extension for COlors on Severity +extension SeverityUtil on Severity { + /// Fetch the color based on the severity + Color? get color => switch (this) { + Severity.info => Colors.blueGrey, + Severity.warning => Colors.orange, + Severity.error => Colors.red, + Severity.critical => Colors.red.shade900, + }; +} + /// Displays controls for the given [Controller]. class ControlsDisplay extends ReusableReactiveWidget { /// The number gamepad being used.