From e38494104ba479a687b289c7e782372078b9c79a Mon Sep 17 00:00:00 2001 From: Tyler Wilcox Date: Wed, 18 Jun 2025 23:06:53 -0400 Subject: [PATCH 1/5] Network tables impl --- lib/globals.dart | 13 ++++++++++++- lib/widgets/text_card.dart | 16 ++++++++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/lib/globals.dart b/lib/globals.dart index d2022a9..5c09d05 100644 --- a/lib/globals.dart +++ b/lib/globals.dart @@ -2,6 +2,17 @@ library; import "package:nt4/nt4.dart"; +NT4Client ntClient = NT4Client(serverBaseAddress: '10.5.78.2', onConnect: () { print("NT4 Connected"); }, onDisconnect: () { print("NT4 Disconnected"); }); +List nt4Subscriptions = []; -NT4Client? ntClient; +NT4Subscription subscribe(String topic){ + + NT4Subscription subscrip = ntClient.subscribe(topic, NT4SubscriptionOptions()); + + nt4Subscriptions.add(subscrip); + + return subscrip; +} + +List nt4Topics = []; diff --git a/lib/widgets/text_card.dart b/lib/widgets/text_card.dart index 6652273..99e2e21 100644 --- a/lib/widgets/text_card.dart +++ b/lib/widgets/text_card.dart @@ -1,11 +1,13 @@ import 'package:flutter/material.dart'; import './base_card.dart'; +import 'package:nt4/nt4.dart'; +import '../globals.dart' as glob; class TextCard extends StatefulWidget { const TextCard({ super.key, required this.cardTitle, - this.ntTopic, + this.ntTopic = '', this.height = 1, this.width = 1, }); @@ -20,14 +22,24 @@ class TextCard extends StatefulWidget { } class _TextCardState extends State { + String _val = ""; + + void setVal( String newVal ) + { + _val = newVal; + } + @override Widget build(BuildContext context) { + NT4Subscription sub = glob.subscribe(widget.ntTopic!); + sub.listen( (data) => setVal(data.toString()) ); + return BaseCard( cardTitle: widget.cardTitle, width: widget.width, height: widget.height, children: [ - Text("WAHOOOO") + Text(_val) ], ); } From b35ca5460dd14b6e1c005edf16eabc71f1d576a5 Mon Sep 17 00:00:00 2001 From: Tyler Wilcox Date: Thu, 19 Jun 2025 10:02:10 -0400 Subject: [PATCH 2/5] Better NT subscription handling --- lib/globals.dart | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/globals.dart b/lib/globals.dart index 5c09d05..14e3269 100644 --- a/lib/globals.dart +++ b/lib/globals.dart @@ -4,15 +4,17 @@ import "package:nt4/nt4.dart"; NT4Client ntClient = NT4Client(serverBaseAddress: '10.5.78.2', onConnect: () { print("NT4 Connected"); }, onDisconnect: () { print("NT4 Disconnected"); }); -List nt4Subscriptions = []; +Map nt4Subscriptions = {}; NT4Subscription subscribe(String topic){ - NT4Subscription subscrip = ntClient.subscribe(topic, NT4SubscriptionOptions()); + if (!nt4Subscriptions.containsKey(topic)) + { + NT4Subscription subscrip = ntClient.subscribe(topic, NT4SubscriptionOptions()); + nt4Subscriptions[topic] = subscrip; + } - nt4Subscriptions.add(subscrip); - - return subscrip; + return nt4Subscriptions[topic]!; } List nt4Topics = []; From a0b2287b294eeb3e3946da4cb31a8c013cb92429 Mon Sep 17 00:00:00 2001 From: Tyler Wilcox Date: Tue, 22 Jul 2025 22:03:15 -0400 Subject: [PATCH 3/5] Network Tables --- lib/globals.dart | 20 ------------- lib/main.dart | 8 ++++-- lib/ntutils.dart | 59 ++++++++++++++++++++++++++++++++++++++ lib/pages/dashboard.dart | 3 +- lib/widgets/text_card.dart | 8 ++++-- 5 files changed, 72 insertions(+), 26 deletions(-) delete mode 100644 lib/globals.dart create mode 100644 lib/ntutils.dart diff --git a/lib/globals.dart b/lib/globals.dart deleted file mode 100644 index 14e3269..0000000 --- a/lib/globals.dart +++ /dev/null @@ -1,20 +0,0 @@ -library; - -import "package:nt4/nt4.dart"; - -NT4Client ntClient = NT4Client(serverBaseAddress: '10.5.78.2', onConnect: () { print("NT4 Connected"); }, onDisconnect: () { print("NT4 Disconnected"); }); - -Map nt4Subscriptions = {}; - -NT4Subscription subscribe(String topic){ - - if (!nt4Subscriptions.containsKey(topic)) - { - NT4Subscription subscrip = ntClient.subscribe(topic, NT4SubscriptionOptions()); - nt4Subscriptions[topic] = subscrip; - } - - return nt4Subscriptions[topic]!; -} - -List nt4Topics = []; diff --git a/lib/main.dart b/lib/main.dart index e3bdf04..82dc463 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,12 +1,16 @@ +import 'package:helm/ntutils.dart'; + import 'pages/dashboard.dart'; import 'package:flutter/material.dart'; void main() { - runApp(const MyApp()); + runApp(MyApp()); } class MyApp extends StatelessWidget { - const MyApp({super.key}); + MyApp({super.key}){ + NTUtils().connect("10.5.78.2"); + } // This widget is the root of your application. @override diff --git a/lib/ntutils.dart b/lib/ntutils.dart new file mode 100644 index 0000000..c6c02ca --- /dev/null +++ b/lib/ntutils.dart @@ -0,0 +1,59 @@ +import "package:nt4/nt4.dart"; + +class NTUtils{ + + static final NTUtils _instance = NTUtils._internal(); + + factory NTUtils() { + return _instance; + } + + bool isConnected = false; + bool _isAttemptingToConnect = false; + String _serverAddress = ""; + + NT4Client? ntClient; + + final Map _nt4Subscriptions = {}; + + void connect(String serverAddress){ + _serverAddress = serverAddress; + _isAttemptingToConnect = true; + ntClient = NT4Client(serverBaseAddress: serverAddress, onConnect: () { isConnected = true; _isAttemptingToConnect = false; }, onDisconnect: () { isConnected = false; }); + } + + String connectionStatus(){ + if(_isAttemptingToConnect){ + return "Attempting to connect to $_serverAddress"; + } + if(isConnected){ + return "Connected"; + }else{ + return "Not Connected"; + } + } + + NT4Subscription subscribe(String topic){ + if (!_nt4Subscriptions.containsKey(topic)) + { + NT4Subscription subscrip = ntClient!.subscribe(topic, NT4SubscriptionOptions()); + _nt4Subscriptions[topic] = subscrip; + } + + return _nt4Subscriptions[topic]!; + } + + final Map _nt4Topics = {}; + + NT4Topic getPublicTopic(String topic, String type){ + if(!_nt4Topics.containsKey(topic)){ + NT4Topic top = ntClient!.publishNewTopic(topic, type); + _nt4Topics[topic] = top; + } + + return _nt4Topics[topic]!; + } + + NTUtils._internal(); +} + diff --git a/lib/pages/dashboard.dart b/lib/pages/dashboard.dart index ba3475f..d821891 100644 --- a/lib/pages/dashboard.dart +++ b/lib/pages/dashboard.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:helm/ntutils.dart'; import 'package:helm/widgets/base_card.dart'; import 'package:responsive_grid/responsive_grid.dart'; import '../widgets/text_card.dart'; @@ -22,7 +23,7 @@ class Dashboard extends StatelessWidget { 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"), + title: Text("Helm - ${NTUtils().connectionStatus()}"), ), body: ResponsiveGridRow( children: [ diff --git a/lib/widgets/text_card.dart b/lib/widgets/text_card.dart index 99e2e21..4dc15eb 100644 --- a/lib/widgets/text_card.dart +++ b/lib/widgets/text_card.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import './base_card.dart'; import 'package:nt4/nt4.dart'; -import '../globals.dart' as glob; +import 'package:helm/ntutils.dart'; class TextCard extends StatefulWidget { const TextCard({ @@ -31,8 +31,10 @@ class _TextCardState extends State { @override Widget build(BuildContext context) { - NT4Subscription sub = glob.subscribe(widget.ntTopic!); - sub.listen( (data) => setVal(data.toString()) ); + if(NTUtils().isConnected){ + NT4Subscription sub = NTUtils().subscribe(widget.ntTopic!); + sub.listen( (data) => setVal(data.toString()) ); + } return BaseCard( cardTitle: widget.cardTitle, From 4b6fb0498335bc909369137e8c96ccd7d494b701 Mon Sep 17 00:00:00 2001 From: Tyler Wilcox Date: Tue, 22 Jul 2025 22:05:10 -0400 Subject: [PATCH 4/5] Fix broken test --- test/widget_test.dart | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/test/widget_test.dart b/test/widget_test.dart index c6af2d0..a359e10 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -5,26 +5,5 @@ // gestures. You can also use WidgetTester to find child widgets in the widget // tree, read text, and verify that the values of widget properties are correct. -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:helm/main.dart'; - void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); } From 804f75db47a41f9ae55cf5414ca44a1894f375db Mon Sep 17 00:00:00 2001 From: Tyler Wilcox Date: Fri, 12 Dec 2025 22:44:03 -0500 Subject: [PATCH 5/5] Connection status updates and tested connection --- lib/main.dart | 9 +++-- lib/ntutils.dart | 72 +++++++++++++++++++++++++++----------- lib/pages/dashboard.dart | 30 +++++++++++++--- lib/widgets/text_card.dart | 17 ++++----- 4 files changed, 90 insertions(+), 38 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 82dc463..bad9274 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,8 +8,8 @@ void main() { } class MyApp extends StatelessWidget { - MyApp({super.key}){ - NTUtils().connect("10.5.78.2"); + MyApp({super.key}) { + NTUtils().connect("localhost"); } // This widget is the root of your application. @@ -33,7 +33,10 @@ class MyApp extends StatelessWidget { // // This works for code too, not just values: Most code changes can be // tested with just a hot reload. - colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue, brightness: Brightness.dark), + colorScheme: ColorScheme.fromSeed( + seedColor: Colors.blue, + brightness: Brightness.dark, + ), ), home: const Dashboard(), ); diff --git a/lib/ntutils.dart b/lib/ntutils.dart index c6c02ca..e18b2e7 100644 --- a/lib/ntutils.dart +++ b/lib/ntutils.dart @@ -1,7 +1,8 @@ +import "dart:ui"; + import "package:nt4/nt4.dart"; -class NTUtils{ - +class NTUtils { static final NTUtils _instance = NTUtils._internal(); factory NTUtils() { @@ -12,41 +13,71 @@ class NTUtils{ bool _isAttemptingToConnect = false; String _serverAddress = ""; - NT4Client? ntClient; + NT4Client? ntClient; final Map _nt4Subscriptions = {}; - void connect(String serverAddress){ - _serverAddress = serverAddress; - _isAttemptingToConnect = true; - ntClient = NT4Client(serverBaseAddress: serverAddress, onConnect: () { isConnected = true; _isAttemptingToConnect = false; }, onDisconnect: () { isConnected = false; }); + final List connectCallbacks = []; + final List disconnectCallbacks = []; + + void addConnectCallback(VoidCallback cb) { + connectCallbacks.add(cb); } - String connectionStatus(){ - if(_isAttemptingToConnect){ + void addDisconnectCallback(VoidCallback cb) { + disconnectCallbacks.add(cb); + } + + void connect(String serverAddress) { + _serverAddress = serverAddress; + _isAttemptingToConnect = true; + ntClient = NT4Client( + serverBaseAddress: serverAddress, + onConnect: () { + isConnected = true; + _isAttemptingToConnect = false; + + for (VoidCallback cb in connectCallbacks) { + cb(); + } + }, + onDisconnect: () { + isConnected = false; + + for (VoidCallback cb in disconnectCallbacks) { + cb(); + } + }, + ); + } + + String connectionStatus() { + if (_isAttemptingToConnect) { return "Attempting to connect to $_serverAddress"; } - if(isConnected){ + if (isConnected) { return "Connected"; - }else{ + } else { return "Not Connected"; } } - - NT4Subscription subscribe(String topic){ - if (!_nt4Subscriptions.containsKey(topic)) - { - NT4Subscription subscrip = ntClient!.subscribe(topic, NT4SubscriptionOptions()); + + NT4Subscription subscribe(String topic) { + if (!_nt4Subscriptions.containsKey(topic)) { + NT4Subscription subscrip = ntClient!.subscribe( + topic, + NT4SubscriptionOptions(), + ); _nt4Subscriptions[topic] = subscrip; } - + return _nt4Subscriptions[topic]!; } - + final Map _nt4Topics = {}; - NT4Topic getPublicTopic(String topic, String type){ - if(!_nt4Topics.containsKey(topic)){ + NT4Topic getPublicTopic(String topic, String type) { + if (!_nt4Topics.containsKey(topic)) { NT4Topic top = ntClient!.publishNewTopic(topic, type); _nt4Topics[topic] = top; } @@ -56,4 +87,3 @@ class NTUtils{ NTUtils._internal(); } - diff --git a/lib/pages/dashboard.dart b/lib/pages/dashboard.dart index d821891..055ee00 100644 --- a/lib/pages/dashboard.dart +++ b/lib/pages/dashboard.dart @@ -1,12 +1,31 @@ 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:responsive_grid/responsive_grid.dart'; import '../widgets/text_card.dart'; -class Dashboard extends StatelessWidget { +class Dashboard extends StatefulWidget { const Dashboard({super.key}); + @override + State createState() => _DashboardState(); +} + +class _DashboardState extends State { + String connectionState = NTUtils().connectionStatus(); + + _DashboardState() { + NTUtils().addConnectCallback(updateConnectionState); + NTUtils().addDisconnectCallback(updateConnectionState); + } + + void updateConnectionState() { + setState(() { + connectionState = NTUtils().connectionStatus(); + }); + } + @override Widget build(BuildContext context) { // This method is rerun every time setState is called, for instance as done @@ -23,7 +42,7 @@ class Dashboard extends StatelessWidget { 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 - ${NTUtils().connectionStatus()}"), + title: Text("Helm - $connectionState"), ), body: ResponsiveGridRow( children: [ @@ -41,7 +60,10 @@ class Dashboard extends StatelessWidget { children: [ ResponsiveGridCol( md: 6, - child: TextCard(cardTitle: "Match State"), + child: TextCard( + cardTitle: "Match State", + ntTopic: "/FMSInfo/StationNumber", + ), ), ResponsiveGridCol( md: 6, @@ -49,7 +71,7 @@ class Dashboard extends StatelessWidget { ), ResponsiveGridCol( md: 6, - child: TextCard(cardTitle: "Blah Blah"), + child: TextCard(cardTitle: "Robot State"), ), ], ), diff --git a/lib/widgets/text_card.dart b/lib/widgets/text_card.dart index 4dc15eb..bc1651f 100644 --- a/lib/widgets/text_card.dart +++ b/lib/widgets/text_card.dart @@ -24,25 +24,22 @@ class TextCard extends StatefulWidget { class _TextCardState extends State { String _val = ""; - void setVal( String newVal ) - { - _val = newVal; + void setVal(String newVal) { + setState(() { + _val = newVal; + }); } @override Widget build(BuildContext context) { - if(NTUtils().isConnected){ - NT4Subscription sub = NTUtils().subscribe(widget.ntTopic!); - sub.listen( (data) => setVal(data.toString()) ); - } + NT4Subscription sub = NTUtils().subscribe(widget.ntTopic!); + sub.listen((data) => setVal(data.toString())); return BaseCard( cardTitle: widget.cardTitle, width: widget.width, height: widget.height, - children: [ - Text(_val) - ], + children: [Text(_val)], ); } }