Merge pull request #3 from FairportRobotics/1-add-network-tables-api
Network tables impl
This commit is contained in:
commit
cfbad36dd7
|
|
@ -1,7 +0,0 @@
|
||||||
library;
|
|
||||||
|
|
||||||
import "package:nt4/nt4.dart";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
NT4Client? ntClient;
|
|
||||||
|
|
@ -1,12 +1,16 @@
|
||||||
|
import 'package:helm/ntutils.dart';
|
||||||
|
|
||||||
import 'pages/dashboard.dart';
|
import 'pages/dashboard.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
runApp(const MyApp());
|
runApp(MyApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatelessWidget {
|
||||||
const MyApp({super.key});
|
MyApp({super.key}) {
|
||||||
|
NTUtils().connect("localhost");
|
||||||
|
}
|
||||||
|
|
||||||
// This widget is the root of your application.
|
// This widget is the root of your application.
|
||||||
@override
|
@override
|
||||||
|
|
@ -29,7 +33,10 @@ class MyApp extends StatelessWidget {
|
||||||
//
|
//
|
||||||
// This works for code too, not just values: Most code changes can be
|
// This works for code too, not just values: Most code changes can be
|
||||||
// tested with just a hot reload.
|
// 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(),
|
home: const Dashboard(),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,31 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.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:responsive_grid/responsive_grid.dart';
|
import 'package:responsive_grid/responsive_grid.dart';
|
||||||
import '../widgets/text_card.dart';
|
import '../widgets/text_card.dart';
|
||||||
|
|
||||||
class Dashboard extends StatelessWidget {
|
class Dashboard extends StatefulWidget {
|
||||||
const Dashboard({super.key});
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// This method is rerun every time setState is called, for instance as done
|
// 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,
|
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
||||||
// 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"),
|
title: Text("Helm - $connectionState"),
|
||||||
),
|
),
|
||||||
body: ResponsiveGridRow(
|
body: ResponsiveGridRow(
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -40,7 +60,10 @@ class Dashboard extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
ResponsiveGridCol(
|
ResponsiveGridCol(
|
||||||
md: 6,
|
md: 6,
|
||||||
child: TextCard(cardTitle: "Match State"),
|
child: TextCard(
|
||||||
|
cardTitle: "Match State",
|
||||||
|
ntTopic: "/FMSInfo/StationNumber",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
ResponsiveGridCol(
|
ResponsiveGridCol(
|
||||||
md: 6,
|
md: 6,
|
||||||
|
|
@ -48,7 +71,7 @@ class Dashboard extends StatelessWidget {
|
||||||
),
|
),
|
||||||
ResponsiveGridCol(
|
ResponsiveGridCol(
|
||||||
md: 6,
|
md: 6,
|
||||||
child: TextCard(cardTitle: "Blah Blah"),
|
child: TextCard(cardTitle: "Robot State"),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import './base_card.dart';
|
import './base_card.dart';
|
||||||
|
import 'package:nt4/nt4.dart';
|
||||||
|
import 'package:helm/ntutils.dart';
|
||||||
|
|
||||||
class TextCard extends StatefulWidget {
|
class TextCard extends StatefulWidget {
|
||||||
const TextCard({
|
const 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,
|
||||||
});
|
});
|
||||||
|
|
@ -20,15 +22,24 @@ class TextCard extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _TextCardState extends State<TextCard> {
|
class _TextCardState extends State<TextCard> {
|
||||||
|
String _val = "";
|
||||||
|
|
||||||
|
void setVal(String newVal) {
|
||||||
|
setState(() {
|
||||||
|
_val = newVal;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
NT4Subscription sub = NTUtils().subscribe(widget.ntTopic!);
|
||||||
|
sub.listen((data) => setVal(data.toString()));
|
||||||
|
|
||||||
return BaseCard(
|
return BaseCard(
|
||||||
cardTitle: widget.cardTitle,
|
cardTitle: widget.cardTitle,
|
||||||
width: widget.width,
|
width: widget.width,
|
||||||
height: widget.height,
|
height: widget.height,
|
||||||
children: [
|
children: [Text(_val)],
|
||||||
Text("WAHOOOO")
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,26 +5,5 @@
|
||||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
// 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.
|
// 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() {
|
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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue