Skip to content

Commit

Permalink
Clean up state management
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewda committed Jul 8, 2023
1 parent b953006 commit a1695a4
Show file tree
Hide file tree
Showing 8 changed files with 384 additions and 183 deletions.
27 changes: 16 additions & 11 deletions lib/components/telemetry_table.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import 'package:flutter/material.dart';

import '../domain/dtos/telemetry_message.dart';
import '../models/robot_state_model.dart';

class TelemetryTable extends StatelessWidget {
final TelemetryMessage? telemetryMessage;
final RobotStateModel robotState;

const TelemetryTable({super.key, required this.telemetryMessage});
const TelemetryTable({super.key, required this.robotState});

TableRow createTableRow(String label, String value) {
return TableRow(
Expand All @@ -31,14 +31,19 @@ class TelemetryTable extends StatelessWidget {
},
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
children: <TableRow>[
createTableRow('Enabled', telemetryMessage?.enabled.toString() ?? 'no data'),
createTableRow('Tipped', telemetryMessage?.tipped.toString() ?? 'no data'),
createTableRow('Voltage', telemetryMessage?.voltage.toString() ?? 'no data'),
createTableRow('Pitch', telemetryMessage?.pitch.toStringAsFixed(2) ?? 'no data'),
createTableRow('Pitch Target', telemetryMessage?.pitchTarget.toStringAsFixed(2) ?? 'no data'),
createTableRow('Pitch Offset', telemetryMessage?.pitchOffset.toStringAsFixed(2) ?? 'no data'),
createTableRow('Left Speed', telemetryMessage?.leftMotorSpeed.toStringAsFixed(2) ?? 'no data'),
createTableRow('Right Speed', telemetryMessage?.rightMotorSpeed.toStringAsFixed(2) ?? 'no data'),
createTableRow('Enabled', robotState.enabled?.toString() ?? 'no data'),
createTableRow('Tipped', robotState.tipped?.toString() ?? 'no data'),
createTableRow('Voltage', robotState.voltage?.toString() ?? 'no data'),
createTableRow(
'Pitch', robotState.pitch?.toStringAsFixed(2) ?? 'no data'),
createTableRow('Pitch Target',
robotState.pitchTarget?.toStringAsFixed(2) ?? 'no data'),
createTableRow('Pitch Offset',
robotState.pitchOffset?.toStringAsFixed(2) ?? 'no data'),
createTableRow('Left Speed',
robotState.leftMotorSpeed?.toStringAsFixed(2) ?? 'no data'),
createTableRow('Right Speed',
robotState.rightMotorSpeed?.toStringAsFixed(2) ?? 'no data'),
],
);
}
Expand Down
203 changes: 78 additions & 125 deletions lib/domain/connection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,161 +2,114 @@ import 'dart:async';
import 'dart:io';

import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter_joystick/flutter_joystick.dart';

import '../models/connection_model.dart';
import '../models/telemetry_model.dart';
import '../models/desired_state_model.dart';
import '../models/robot_state_model.dart';
import 'dtos/command_message.dart';
import 'dtos/telemetry_message.dart';
import 'networking.dart';

CommandMessage commandMessage = CommandMessage();
CommandMessage _previousCommandMessage = CommandMessage();
class ConnectionManager {
Timer? connectionTimeoutTimer;

Timer? connectionTimeoutTimer;
RawDatagramSocket? socket;
Timer? sendLoop;
StreamSubscription? subscription;

RawDatagramSocket? socket;
Timer? sendLoop;
StreamSubscription? subscription;
DateTime? lastMessageSend;

DateTime? lastMessageSend;
ConnectionManager() {
initConnection();
}

Future initConnection() async {
Connectivity().onConnectivityChanged.listen(connectivityChangedListener);
Future initConnection() async {
Connectivity().onConnectivityChanged.listen(connectivityChangedListener);

Timer.periodic(const Duration(seconds: 5), (_) async {
ConnectivityResult result = await Connectivity().checkConnectivity();
connectivityChangedListener(result);
});
}
Timer.periodic(const Duration(seconds: 5), (_) async {
ConnectivityResult result = await Connectivity().checkConnectivity();
connectivityChangedListener(result);
});
}

Future connectivityChangedListener(ConnectivityResult result) async {
bool robotConnected = await isRobotConnected();
InternetAddress? robotAddress = await getRobotAddress();
Future connectivityChangedListener(ConnectivityResult result) async {
bool robotConnected = await isRobotConnected();
InternetAddress? robotAddress = await getRobotAddress();

if (robotConnected) {
await initSocket(robotAddress ?? InternetAddress.anyIPv4);
initSendLoop();
receiveData(telemetryMessageListener);
if (robotConnected) {
await initSocket(robotAddress ?? InternetAddress.anyIPv4);
initSendLoop();
receiveData(telemetryMessageListener);

TelemetryMessage? latestMessage = TelemetryModel().telemetryMessage;
DateTime? latestMessageTime = RobotStateModel().lastMessageTime;

// if telemetryMessage is older than 2 seconds...
if (latestMessage == null ||
latestMessage.messageTime
.isBefore(DateTime.now().subtract(const Duration(seconds: 2)))) {
ConnectionModel().setStatus(ConnectionStatus.networkConnected);
// if telemetryMessage is older than 2 seconds...
if (latestMessageTime == null ||
latestMessageTime
.isBefore(DateTime.now().subtract(const Duration(seconds: 2)))) {
ConnectionModel().setStatus(ConnectionStatus.networkConnected);
}
} else {
ConnectionModel().setStatus(ConnectionStatus.disconnected);
}
} else {
ConnectionModel().setStatus(ConnectionStatus.disconnected);
}
}

Future initSocket(InternetAddress host) async {
socket?.close();
Future initSocket(InternetAddress host) async {
socket?.close();

socket = await RawDatagramSocket.bind(host, 0);
}
socket = await RawDatagramSocket.bind(host, 0);
}

void initSendLoop() {
sendLoop?.cancel();

sendLoop = Timer.periodic(
const Duration(milliseconds: 10),
(Timer t) {
// Send message if...
// 1. The message has changed AND we haven't sent a message in the past 20ms, OR
// 2. We haven't sent a message in the past 500ms
//
// TODO: We should replace #2 with a heartbeat message

bool messageChanged = commandMessage != _previousCommandMessage;
bool messageSentRecently = lastMessageSend != null &&
lastMessageSend!.isAfter(
DateTime.now().subtract(const Duration(milliseconds: 20)),
);
bool messageSentLongAgo = lastMessageSend == null ||
lastMessageSend!.isBefore(
DateTime.now().subtract(const Duration(milliseconds: 500)),
);

if ((messageChanged && !messageSentRecently) || messageSentLongAgo) {
sendData(commandMessage.getBytes());

lastMessageSend = DateTime.now();
_previousCommandMessage = CommandMessage.from(commandMessage);
}
},
);
}
void initSendLoop() {
sendLoop?.cancel();

void receiveData(Function(TelemetryMessage) onDataReceived) async {
assert(socket != null);
sendLoop = Timer.periodic(
const Duration(milliseconds: 50),
(Timer t) {
sendData(CommandMessage.from(DesiredStateModel()).getBytes());
},
);
}

await subscription?.cancel();
void receiveData(Function(TelemetryMessage) onDataReceived) async {
assert(socket != null);

subscription = socket?.listen((RawSocketEvent e) {
Datagram? d = socket?.receive();
if (d == null) return;
await subscription?.cancel();

onDataReceived(TelemetryMessage(d.data));
});
}
subscription = socket?.listen((RawSocketEvent e) {
Datagram? d = socket?.receive();
if (d == null) return;

void sendData(List<int> message) {
assert(socket != null);

try {
socket?.send(message, robotAddress, robotPort);
commandMessage.saveRecallState = 1;
} catch (e) {
print('Could not send data, ensure robot is connected');
onDataReceived(TelemetryMessage(d.data));
});
}
}

void setEnabledCommand(bool enabled) {
commandMessage.enabled = enabled;
}
void sendData(List<int> message) {
assert(socket != null);

void setJoystickCommand(StickDragDetails details) {
commandMessage.speed = (details.y * -100).round();
commandMessage.turn = (details.x * 100).round();
}
try {
socket?.send(message, robotAddress, robotPort);
DesiredStateModel().saveRecallState = 1;
} catch (e) {
print('Could not send data, ensure robot is connected');
}
}

void setPidCommand(
double angleP,
double angleI,
double angleD,
double speedP,
double speedI,
double speedD, {
bool save = false,
}) {
commandMessage.advanced = true;
commandMessage.saveRecallState = save ? 2 : 1;

commandMessage.angleP = angleP;
commandMessage.angleI = angleI;
commandMessage.angleD = angleD;
commandMessage.speedP = speedP;
commandMessage.speedI = speedI;
commandMessage.speedD = speedD;
}
void telemetryMessageListener(TelemetryMessage message) {
RobotStateModel().setFromTelemetry(message);
ConnectionModel().setStatus(ConnectionStatus.connected);

void telemetryMessageListener(TelemetryMessage message) {
TelemetryModel().setTelemetry(message);
ConnectionModel().setStatus(ConnectionStatus.connected);
// If the enabled state has changed, update the UI
// if (message.enabled != _previousCommandMessage.enabled) {
// DesiredStateModel().setEnabled(message.enabled);
// }

// If the enabled state has changed, update the UI
if (message.enabled != _previousCommandMessage.enabled) {
setEnabledCommand(message.enabled);
// Expect a message at least every 2 seconds
connectionTimeoutTimer?.cancel();
connectionTimeoutTimer = Timer(const Duration(milliseconds: 2000), () {
if (ConnectionModel().status == ConnectionStatus.connected) {
ConnectionModel().setStatus(ConnectionStatus.networkConnected);
}
});
}

// Expect a message at least every 2 seconds
connectionTimeoutTimer?.cancel();
connectionTimeoutTimer = Timer(const Duration(milliseconds: 2000), () {
if (ConnectionModel().status == ConnectionStatus.connected) {
ConnectionModel().setStatus(ConnectionStatus.networkConnected);
}
});
}
50 changes: 37 additions & 13 deletions lib/domain/dtos/command_message.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import 'dart:typed_data';

import '../../models/desired_state_model.dart';

double remap(
double value, double low1, double high1, double low2, double high2) {
return low2 + (value - low1) * (high2 - low2) / (high1 - low1);
}

class CommandMessage {
final BytesBuilder _builder = BytesBuilder();

Expand Down Expand Up @@ -69,21 +76,38 @@ class CommandMessage {
return _builder.takeBytes();
}

static CommandMessage from(CommandMessage message) {
static CommandMessage from(Object object) {
var commandMessage = CommandMessage();

commandMessage.enabled = message.enabled;
commandMessage.speed = message.speed;
commandMessage.turn = message.turn;
commandMessage.auxiliary = message.auxiliary;
commandMessage.advanced = message.advanced;
commandMessage.angleP = message.angleP;
commandMessage.angleI = message.angleI;
commandMessage.angleD = message.angleD;
commandMessage.speedP = message.speedP;
commandMessage.speedI = message.speedI;
commandMessage.speedD = message.speedD;
commandMessage.saveRecallState = message.saveRecallState;
if (object is CommandMessage) {
commandMessage.enabled = object.enabled;
commandMessage.speed = object.speed;
commandMessage.turn = object.turn;
commandMessage.auxiliary = object.auxiliary;
commandMessage.advanced = object.advanced;
commandMessage.angleP = object.angleP;
commandMessage.angleI = object.angleI;
commandMessage.angleD = object.angleD;
commandMessage.speedP = object.speedP;
commandMessage.speedI = object.speedI;
commandMessage.speedD = object.speedD;
commandMessage.saveRecallState = object.saveRecallState;
} else if (object is DesiredStateModel) {
commandMessage.enabled = object.enabled;
commandMessage.speed = remap(object.speed, -1, 1, 0, 200).round();
commandMessage.turn = remap(object.turn, -1, 1, 0, 200).round();
commandMessage.auxiliary = object.auxiliary;
commandMessage.advanced = object.advanced;
commandMessage.angleP = object.angleP;
commandMessage.angleI = object.angleI;
commandMessage.angleD = object.angleD;
commandMessage.speedP = object.speedP;
commandMessage.speedI = object.speedI;
commandMessage.speedD = object.speedD;
commandMessage.saveRecallState = object.saveRecallState;
} else {
throw Exception('Object must be CommandMessage or DesiredStateModel');
}

return commandMessage;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ void main() async {
),
);

initConnection();
ConnectionManager();
}

class UrsaApp extends StatelessWidget {
Expand Down
Loading

0 comments on commit a1695a4

Please sign in to comment.