This commit is contained in:
Tyler Wilcox 2025-12-18 18:30:30 -05:00
parent cfbad36dd7
commit 7971bf8201
6 changed files with 244 additions and 78 deletions

View File

@ -10,12 +10,9 @@ class NTUtils {
}
bool isConnected = false;
bool _isAttemptingToConnect = false;
String _serverAddress = "";
NT4Client? ntClient;
final Map<String, NT4Subscription> _nt4Subscriptions = {};
NT4Client? _ntClient;
final List<VoidCallback> connectCallbacks = [];
final List<VoidCallback> disconnectCallbacks = [];
@ -30,12 +27,10 @@ class NTUtils {
void connect(String serverAddress) {
_serverAddress = serverAddress;
_isAttemptingToConnect = true;
ntClient = NT4Client(
_ntClient = NT4Client(
serverBaseAddress: serverAddress,
onConnect: () {
isConnected = true;
_isAttemptingToConnect = false;
for (VoidCallback cb in connectCallbacks) {
cb();
@ -52,33 +47,26 @@ class NTUtils {
}
String connectionStatus() {
if (_isAttemptingToConnect) {
return "Attempting to connect to $_serverAddress";
}
if (isConnected) {
return "Connected";
} else {
return "Not Connected";
return "Not Connected. Attempting to connect to $_serverAddress";
}
}
NT4Subscription subscribe(String topic) {
if (!_nt4Subscriptions.containsKey(topic)) {
NT4Subscription subscrip = ntClient!.subscribe(
topic,
NT4SubscriptionOptions(),
);
_nt4Subscriptions[topic] = subscrip;
}
return _nt4Subscriptions[topic]!;
NT4Subscription subscrip = _ntClient!.subscribe(
topic,
NT4SubscriptionOptions(),
);
return subscrip;
}
final Map<String, NT4Topic> _nt4Topics = {};
NT4Topic getPublicTopic(String topic, String type) {
if (!_nt4Topics.containsKey(topic)) {
NT4Topic top = ntClient!.publishNewTopic(topic, type);
NT4Topic top = _ntClient!.publishNewTopic(topic, type);
_nt4Topics[topic] = top;
}

View File

@ -1,7 +1,8 @@
import 'package:flutter/material.dart';
import 'package:helm/ntutils.dart';
import 'package:helm/widgets/base_card.dart';
import 'package:helm/widgets/posty_results_card.dart';
import 'package:helm/widgets/posty/posty_results_card.dart';
import 'package:helm/widgets/posty/posty_summary_card.dart';
import 'package:responsive_grid/responsive_grid.dart';
import '../widgets/text_card.dart';
@ -34,60 +35,69 @@ class _DashboardState extends State<Dashboard> {
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// TRY THIS: Try changing the color here to a specific color (to
// Colors.amber, perhaps?) and trigger a hot reload to see the AppBar
// change color while the other colors stay the same.
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text("Helm - $connectionState"),
),
body: ResponsiveGridRow(
children: [
ResponsiveGridCol(
md: 12,
child: ResponsiveGridRow(
return DefaultTabController(
length: 1,
initialIndex: 0,
child: Scaffold(
appBar: AppBar(
// TRY THIS: Try changing the color here to a specific color (to
// Colors.amber, perhaps?) and trigger a hot reload to see the AppBar
// change color while the other colors stay the same.
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text("Helm - $connectionState"),
bottom: TabBar(tabs: <Widget>[Tab(icon: Icon(Icons.home))]),
),
body: TabBarView(
children: <Widget>[
ResponsiveGridRow(
children: [
ResponsiveGridCol(
md: 6,
child: BaseCard(cardTitle: "Reef Picker", height: 4),
),
ResponsiveGridCol(
md: 6,
md: 12,
child: ResponsiveGridRow(
children: [
ResponsiveGridCol(
md: 6,
child: TextCard(
cardTitle: "Match State",
ntTopic: "/FMSInfo/StationNumber",
child: BaseCard(cardTitle: "Reef Picker", height: 4),
),
ResponsiveGridCol(
md: 6,
child: ResponsiveGridRow(
children: [
ResponsiveGridCol(
md: 6,
child: TextCard(
cardTitle: "Match State",
ntTopic: "/FMSInfo/StationNumber",
),
),
ResponsiveGridCol(
md: 6,
child: TextCard(cardTitle: "Match Time"),
),
ResponsiveGridCol(md: 6, child: PostyResultsCard()),
],
),
),
ResponsiveGridCol(
md: 6,
child: TextCard(cardTitle: "Match Time"),
child: ResponsiveGridRow(
children: [
ResponsiveGridCol(
child: TextCard(cardTitle: "Blah"),
),
ResponsiveGridCol(child: PostySummaryCard()),
],
),
),
ResponsiveGridCol(
md: 6,
child: TextCard(cardTitle: "Robot State"),
),
],
),
),
ResponsiveGridCol(
md: 6,
child: ResponsiveGridRow(
children: [
ResponsiveGridCol(child: TextCard(cardTitle: "Blah")),
],
),
),
],
),
),
],
],
),
),
);
}

View File

@ -0,0 +1,81 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:helm/ntutils.dart';
import 'package:helm/widgets/base_card.dart';
import 'package:nt4/nt4.dart';
class PostyResultsCard extends StatefulWidget {
const PostyResultsCard({super.key});
@override
State<StatefulWidget> createState() => _PostyResultsCardState();
}
class _PostyResultsCardState extends State<PostyResultsCard> {
final String _ntTopic = "/Posty/results/post/resultsJson";
List<PostyResultModel> resultsData = [];
_PostyResultsCardState() {
NT4Subscription sub = NTUtils().subscribe(_ntTopic);
sub.listen((data) {
setState(() {
List<dynamic> tmp = jsonDecode(data.toString());
for (dynamic json in tmp) {
resultsData.add(PostyResultModel.fromJson(json));
}
});
});
}
@override
Widget build(BuildContext context) {
List<Widget> children = [];
resultsData.sort((a, b) => a.subsystemName.compareTo(b.subsystemName));
children =
resultsData.map((result) {
Color statusColor = Colors.green;
if (result.result == "FAILED") {
statusColor = Colors.red;
} else if (result.result == "SKIPPED") {
statusColor = Colors.grey;
}
return Text(
"${result.testName} -- ${result.result}",
style: TextStyle(color: statusColor),
);
}).toList();
return BaseCard(
width: 6,
height: 2,
cardTitle: "Self Test Results",
children: children,
);
}
}
class PostyResultModel {
String testName = "";
String subsystemName = "";
String result = "";
String failReason = "";
PostyResultModel(
this.testName,
this.subsystemName,
this.result,
this.failReason,
);
PostyResultModel.fromJson(Map<String, dynamic> json)
: testName = json['testName'] as String,
subsystemName = json['subsystemName'] as String,
result = json['result'] as String,
failReason = json['failReason'] as String;
}

View File

@ -0,0 +1,86 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:helm/ntutils.dart';
import 'package:helm/widgets/base_card.dart';
import 'package:nt4/nt4.dart';
class PostySummaryCard extends StatefulWidget {
const PostySummaryCard({super.key});
@override
State<StatefulWidget> createState() => _PostySummaryCardState();
}
class _PostySummaryCardState extends State<PostySummaryCard> {
final String _ntTopic = "/Posty/results/post/resultsJson";
List<PostyResultModel> resultsData = [];
_PostySummaryCardState() {}
@override
Widget build(BuildContext context) {
NT4Subscription sub = NTUtils().subscribe(_ntTopic);
sub.listen((data) {
setState(() {
List<dynamic> tmp = jsonDecode(data.toString());
for (dynamic json in tmp) {
resultsData.add(PostyResultModel.fromJson(json));
}
});
});
bool systemNotReady = false;
for (PostyResultModel result in resultsData) {
systemNotReady |= result.result == "FAILED";
}
List<Widget> children = [
Center(
child: Container(
width: 50,
height: 50,
decoration: BoxDecoration(color: Colors.grey, shape: BoxShape.circle),
child: Padding(
padding: EdgeInsets.all(2),
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: !systemNotReady ? Colors.green : Colors.red,
),
),
),
),
),
];
return BaseCard(
width: 1,
height: 1,
cardTitle: "System Status",
children: children,
);
}
}
class PostyResultModel {
String testName = "";
String subsystemName = "";
String result = "";
String failReason = "";
PostyResultModel(
this.testName,
this.subsystemName,
this.result,
this.failReason,
);
PostyResultModel.fromJson(Map<String, dynamic> json)
: testName = json['testName'] as String,
subsystemName = json['subsystemName'] as String,
result = json['result'] as String,
failReason = json['failReason'] as String;
}

View File

@ -4,16 +4,16 @@ import 'package:nt4/nt4.dart';
import 'package:helm/ntutils.dart';
class TextCard extends StatefulWidget {
const TextCard({
TextCard({
super.key,
required this.cardTitle,
this.ntTopic = '',
this.ntTopic = "",
this.height = 1,
this.width = 1,
});
final String cardTitle;
final String? ntTopic;
String ntTopic = "";
final int width, height;
@ -24,6 +24,8 @@ class TextCard extends StatefulWidget {
class _TextCardState extends State<TextCard> {
String _val = "";
_TextCardState() {}
void setVal(String newVal) {
setState(() {
_val = newVal;
@ -32,9 +34,8 @@ class _TextCardState extends State<TextCard> {
@override
Widget build(BuildContext context) {
NT4Subscription sub = NTUtils().subscribe(widget.ntTopic!);
NT4Subscription sub = NTUtils().subscribe(widget.ntTopic);
sub.listen((data) => setVal(data.toString()));
return BaseCard(
cardTitle: widget.cardTitle,
width: widget.width,

View File

@ -87,26 +87,26 @@ packages:
dependency: transitive
description:
name: leak_tracker
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
url: "https://pub.dev"
source: hosted
version: "10.0.9"
version: "11.0.2"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
url: "https://pub.dev"
source: hosted
version: "3.0.9"
version: "3.0.10"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
version: "3.0.2"
lints:
dependency: transitive
description:
@ -143,10 +143,10 @@ packages:
dependency: transitive
description:
name: meta
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev"
source: hosted
version: "1.16.0"
version: "1.17.0"
msgpack_dart:
dependency: transitive
description:
@ -228,10 +228,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
url: "https://pub.dev"
source: hosted
version: "0.7.4"
version: "0.7.7"
typed_data:
dependency: transitive
description:
@ -244,10 +244,10 @@ packages:
dependency: transitive
description:
name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
url: "https://pub.dev"
source: hosted
version: "2.1.4"
version: "2.2.0"
vm_service:
dependency: transitive
description:
@ -273,5 +273,5 @@ packages:
source: hosted
version: "2.4.5"
sdks:
dart: ">=3.7.0 <4.0.0"
dart: ">=3.8.0-0 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"