Merge pull request #3 from FairportRobotics/1-add-network-tables-api

Network tables impl
This commit is contained in:
Tyler Wilcox 2025-12-12 22:46:06 -05:00 committed by GitHub
commit cfbad36dd7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 141 additions and 39 deletions

View File

@ -1,7 +0,0 @@
library;
import "package:nt4/nt4.dart";
NT4Client? ntClient;

View File

@ -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("localhost");
}
// This widget is the root of your application.
@override
@ -29,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(),
);

89
lib/ntutils.dart Normal file
View File

@ -0,0 +1,89 @@
import "dart:ui";
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<String, NT4Subscription> _nt4Subscriptions = {};
final List<VoidCallback> connectCallbacks = [];
final List<VoidCallback> disconnectCallbacks = [];
void addConnectCallback(VoidCallback cb) {
connectCallbacks.add(cb);
}
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) {
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<String, NT4Topic> _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();
}

View File

@ -1,11 +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<Dashboard> createState() => _DashboardState();
}
class _DashboardState extends State<Dashboard> {
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
@ -22,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"),
title: Text("Helm - $connectionState"),
),
body: ResponsiveGridRow(
children: [
@ -40,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,
@ -48,7 +71,7 @@ class Dashboard extends StatelessWidget {
),
ResponsiveGridCol(
md: 6,
child: TextCard(cardTitle: "Blah Blah"),
child: TextCard(cardTitle: "Robot State"),
),
],
),

View File

@ -1,11 +1,13 @@
import 'package:flutter/material.dart';
import './base_card.dart';
import 'package:nt4/nt4.dart';
import 'package:helm/ntutils.dart';
class TextCard extends StatefulWidget {
const TextCard({
super.key,
required this.cardTitle,
this.ntTopic,
this.ntTopic = '',
this.height = 1,
this.width = 1,
});
@ -20,15 +22,24 @@ class TextCard extends StatefulWidget {
}
class _TextCardState extends State<TextCard> {
String _val = "";
void setVal(String newVal) {
setState(() {
_val = newVal;
});
}
@override
Widget build(BuildContext context) {
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("WAHOOOO")
],
children: [Text(_val)],
);
}
}

View File

@ -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);
});
}