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

View File

@ -1,7 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:helm/ntutils.dart'; import 'package:helm/ntutils.dart';
import 'package:helm/widgets/base_card.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 'package:responsive_grid/responsive_grid.dart';
import '../widgets/text_card.dart'; import '../widgets/text_card.dart';
@ -34,7 +35,10 @@ class _DashboardState extends State<Dashboard> {
// The Flutter framework has been optimized to make rerunning build methods // The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather // fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets. // than having to individually change instances of widgets.
return Scaffold( return DefaultTabController(
length: 1,
initialIndex: 0,
child: Scaffold(
appBar: AppBar( appBar: AppBar(
// TRY THIS: Try changing the color here to a specific color (to // TRY THIS: Try changing the color here to a specific color (to
// Colors.amber, perhaps?) and trigger a hot reload to see the AppBar // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar
@ -43,8 +47,11 @@ class _DashboardState extends State<Dashboard> {
// Here we take the value from the MyHomePage object that was created by // 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. // the App.build method, and use it to set our appbar title.
title: Text("Helm - $connectionState"), title: Text("Helm - $connectionState"),
bottom: TabBar(tabs: <Widget>[Tab(icon: Icon(Icons.home))]),
), ),
body: ResponsiveGridRow( body: TabBarView(
children: <Widget>[
ResponsiveGridRow(
children: [ children: [
ResponsiveGridCol( ResponsiveGridCol(
md: 12, md: 12,
@ -69,10 +76,7 @@ class _DashboardState extends State<Dashboard> {
md: 6, md: 6,
child: TextCard(cardTitle: "Match Time"), child: TextCard(cardTitle: "Match Time"),
), ),
ResponsiveGridCol( ResponsiveGridCol(md: 6, child: PostyResultsCard()),
md: 6,
child: TextCard(cardTitle: "Robot State"),
),
], ],
), ),
), ),
@ -80,7 +84,10 @@ class _DashboardState extends State<Dashboard> {
md: 6, md: 6,
child: ResponsiveGridRow( child: ResponsiveGridRow(
children: [ children: [
ResponsiveGridCol(child: TextCard(cardTitle: "Blah")), ResponsiveGridCol(
child: TextCard(cardTitle: "Blah"),
),
ResponsiveGridCol(child: PostySummaryCard()),
], ],
), ),
), ),
@ -89,6 +96,9 @@ class _DashboardState extends State<Dashboard> {
), ),
], ],
), ),
],
),
),
); );
} }
} }

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

View File

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