Skip to content

Commit

Permalink
Refactor and add tests (#8)
Browse files Browse the repository at this point in the history
Co-authored-by: Binghamton University Rover Team <[email protected]>
  • Loading branch information
Levi-Lesches and Bing-Rover authored Apr 17, 2024
1 parent ca760ac commit 4546504
Show file tree
Hide file tree
Showing 20 changed files with 239 additions and 357 deletions.
3 changes: 3 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Levi has to review everything

* @Levi-Lesches
1 change: 0 additions & 1 deletion analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ analyzer:

exclude:
- lib/generated/**.dart
- test/**.dart

linter:
rules:
Expand Down
47 changes: 47 additions & 0 deletions bin/proto.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import "dart:convert";
import "dart:io";

import "package:csv/csv.dart";
import "package:burt_network/burt_network.dart";
import "package:subsystems/subsystems.dart";

const filename = r"C:\Users\Levi\Downloads\RoverPosition.log";
const outputName = "imu.csv";

extension <E> on Iterable<E> {
Iterable<(int, E)> get enumerate sync* {
int i = 0;
for (final e in this) {
yield (i++, e);
}
}
}

RoverPosition fromBuffer(List<int> buffer) => RoverPosition.fromBuffer(buffer);
bool filter(RoverPosition data) => data.hasOrientation()
&& data.orientation.hasX() && data.orientation.hasY() && data.orientation.hasZ()
&& data.orientation.x.abs() < 400
&& data.orientation.y.abs() < 400
&& data.orientation.z.abs() < 400;
List<dynamic> expand(RoverPosition data) => [data.orientation.x, data.orientation.y, data.orientation.z];

void main() async {
logger.info("Reading log file...");
final file = File(filename);
final lines = await file.readAsLines();
final data = <List<double>>[];
logger.info("Parsing...");
for (final (index, line) in lines.enumerate) {
final buffer = base64Decode(line);
final wrapper = WrappedMessage.fromBuffer(buffer);
final entry = fromBuffer(wrapper.data);
if (!filter(entry)) continue;
data.add([index.toDouble(), ...expand(entry)]);
}
logger.info("Writing...");
const converter = ListToCsvConverter();
final csv = converter.convert(data);
final outputFile = File(outputName);
await outputFile.writeAsString(csv);
logger.info("Wrote ${data.length} lines to ${outputFile.absolute}");
}
10 changes: 6 additions & 4 deletions bin/serial.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import "dart:typed_data";
import "package:subsystems/subsystems.dart";
import "package:burt_network/logging.dart";
import "package:burt_network/generated.dart";
import "package:burt_network/burt_network.dart";
import "package:libserialport/libserialport.dart";

final logger = BurtLogger();
Expand All @@ -26,8 +24,12 @@ void main(List<String> args) async {
final device = SerialDevice(
portName: port,
readInterval: const Duration(milliseconds: 100),
logger: logger,
);
device.open();
if (!await device.init()) {
logger.critical("Could not connect to $port");
return;
}
logger.info("Connected. Listening...");
device.stream.listen(process);
device.startListening();
Expand Down
4 changes: 2 additions & 2 deletions bin/subsystems.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ import "package:subsystems/subsystems.dart";
import "package:burt_network/logging.dart";

void main() async {
Logger.level = LogLevel.info;
await collection.init();
Logger.level = LogLevel.trace;
if (!await collection.init()) await collection.dispose();
}
18 changes: 9 additions & 9 deletions lib/src/can/socket_ffi.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ class CanFFI implements CanSocket {
bool hasError = false;

/// Fills [incomingMessages] with new messages by calling [_checkForMessages].
late StreamController<CanMessage> _controller;
late final StreamController<CanMessage> _controller = StreamController<CanMessage>.broadcast(
onListen: () => _startListening,
onCancel: () => _stopListening,
);

void _startListening() => _timer = Timer.periodic(readInterval, _checkForMessages);
void _stopListening() => _timer?.cancel();
Expand All @@ -48,27 +51,24 @@ class CanFFI implements CanSocket {
Timer? _timer;

@override
Future<void> init() async {
Future<bool> init() async {
_can = nativeLib.BurtCan_create(canInterface.toNativeUtf8(), canTimeout, canType);
await Process.run("sudo", ["ip", "link", "set", "can0", "down"]);
final result = await Process.run("sudo", ["ip", "link", "set", "can0", "up", "type", "can", "bitrate", "500000"]);
if (result.exitCode != 0) {
logger.critical("Could not start can0", body: "sudo ip link set can0 up type can bitrate 500000 failed:\n${result.stderr}");
hasError = true;
return;
return false;
}
final error = getCanError(nativeLib.BurtCan_open(_can!));
if (error != null) {
hasError = true;
logger.critical("Could not start the CAN bus", body: error);
return;
return false;
}
_controller = StreamController<CanMessage>.broadcast(
onListen: () => _startListening,
onCancel: () => _stopListening,
);
_startListening();
logger.info("Listening on CAN interface $canInterface");
return true;
}

@override
Expand All @@ -84,7 +84,7 @@ class CanFFI implements CanSocket {

@override
void sendMessage({required int id, required List<int> data}) {
if (hasError) return;
if (hasError || _can == null) return;
final message = CanMessage(id: id, data: data);
final error = getCanError(nativeLib.BurtCan_send(_can!, message.pointer));
if (error != null) logger.warning("Could not send CAN message", body: "ID=$id, Data=$data, Error: $error");
Expand Down
12 changes: 4 additions & 8 deletions lib/src/can/socket_interface.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import "dart:io";

import "package:burt_network/burt_network.dart";

import "ffi.dart";
import "message.dart";
import "socket_stub.dart";
import "socket_ffi.dart";

/// An exception that occurred while working with the CAN bus -- see [BurtCanStatus].
class CanException implements Exception {
/// The error that occured, using [getCanError].
/// The error that occurred, using [getCanError].
final String message;
/// A const constructor
const CanException(this.message);
Expand All @@ -25,16 +27,10 @@ class CanException implements Exception {
///
/// - Use [sendMessage] to send a message to all devices on the bus
/// - Listen to [incomingMessages] to receive messages from other devices on the bus
abstract class CanSocket {
abstract class CanSocket extends Service {
/// Chooses the right implementation for the platform. Uses a stub on non-Linux platforms.
factory CanSocket() => Platform.isLinux ? CanFFI() : CanStub();

/// Starts listening for CAN messages.
Future<void> init();

/// Disposes of native resources allocated to this object, and stops listening for CAN messages.
Future<void> dispose() async { }

/// Sends a CAN message with the given ID and data.
void sendMessage({required int id, required List<int> data}) { }

Expand Down
3 changes: 2 additions & 1 deletion lib/src/can/socket_stub.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ class CanStub implements CanSocket {
CanStub();

@override
Future<void> init() async {
Future<bool> init() async {
logger.warning("Using a mock CAN service");
return true;
}

@override
Expand Down
37 changes: 23 additions & 14 deletions lib/src/serial/gps.dart → lib/src/devices/gps.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import "package:burt_network/burt_network.dart";
import "package:subsystems/subsystems.dart";

/// The port/device file to listen to the GPS on.
const serialPort = "/dev/rover-gps";
const gpsPort = "/dev/rover-gps";

/// Listens to the GPS and sends its output to the Dashboard.
///
/// Call [init] to start listening and [dispose] to stop.
class GpsReader {
class GpsReader extends Service {
/// Parses an NMEA sentence into a [GpsCoordinates] object.
///
/// See https://shadyelectronics.com/gps-nmea-sentence-structure.
Expand Down Expand Up @@ -45,12 +45,17 @@ class GpsReader {
}

/// The serial device representing the GPS.
SerialDevice device = SerialDevice(portName: serialPort, readInterval: const Duration(seconds: 1));
final SerialDevice device = SerialDevice(
portName: gpsPort,
readInterval: const Duration(seconds: 1),
logger: logger,
);

/// The subscription to the serial port.
StreamSubscription<List<int>>? _subscription;

/// Parses a line of NMEA output and sends the GPS coordinates to the dashboard.
void handleLine(String line) {
void _handleLine(String line) {
final coordinates = parseNMEA(line);
if (coordinates == null) return;
if (coordinates.latitude == 0 || coordinates.longitude == 0 || coordinates.altitude == 0) {
Expand All @@ -62,26 +67,30 @@ class GpsReader {
}

/// Parses a packet into several NMEA sentences and handles them.
void handlePacket(List<int> bytes) {
void _handlePacket(List<int> bytes) {
final string = utf8.decode(bytes);
final lines = string.split("\n");
lines.forEach(handleLine);
lines.forEach(_handleLine);
}

/// Starts reading the GPS (on [serialPort]) through the `cat` Linux program.
Future<void> init() async {
logger.info("Reading GPS on port $serialPort");
@override
Future<bool> init() async {
try {
device.open();
_subscription = device.stream.listen(handlePacket);
if (!await device.init()) {
logger.critical("Could not open GPS on port $gpsPort");
return false;
}
_subscription = device.stream.listen(_handlePacket);
return true;
} catch (error) {
logger.critical("Could not open GPS", body: "Port $serialPort, Error=$error");
logger.critical("Could not open GPS", body: "Port $gpsPort, Error=$error");
return false;
}
}

/// Closes the [device] and stops listening to the GPS.
@override
Future<void> dispose() async {
await _subscription?.cancel();
device.dispose();
await device.dispose();
}
}
33 changes: 21 additions & 12 deletions lib/src/serial/imu.dart → lib/src/devices/imu.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,26 @@ import "package:subsystems/subsystems.dart";
import "package:burt_network/burt_network.dart";

/// The serial port that the IMU is connected to.
const port = "/dev/rover-imu";
const imuPort = "/dev/rover-imu";

extension on double {
bool isZero([double epsilon = 0.001]) => abs() < epsilon;
}

/// A service to read orientation data from the connected IMU.
class ImuReader {
class ImuReader extends Service {
/// The device that reads from the serial port.
final serial = SerialDevice(portName: port, readInterval: const Duration(milliseconds: 10));
final serial = SerialDevice(
portName: imuPort,
readInterval: const Duration(milliseconds: 10),
logger: logger,
);

/// The subscription that will be notified when a new serial packet arrives.
StreamSubscription<List<int>>? subscription;

/// Parses an OSC bundle from a list of bytes.
void handleOsc(List<int> data) {
void _handleOsc(List<int> data) {
try {
final message = OSCMessage.fromBytes(data.sublist(20));
final orientation = Orientation(
Expand All @@ -36,21 +40,26 @@ class ImuReader {
} catch (error) { /* Ignore corrupt data */ }
}

/// Starts listening to the IMU.
Future<void> init() async {
@override
Future<bool> init() async {
try {
serial.open();
subscription = serial.stream.listen(handleOsc);
if (!await serial.init()) {
logger.critical("Could not open IMU on port $imuPort");
return false;
}
subscription = serial.stream.listen(_handleOsc);
serial.startListening();
logger.info("Reading IMU on port $port");
logger.info("Reading IMU on port $imuPort");
return true;
} catch (error) {
logger.critical("Could not open IMU", body: "Port $port, Error: $error");
logger.critical("Could not open IMU", body: "Port $imuPort, Error: $error");
return false;
}
}

/// Stops listening to the serial port.
@override
Future<void> dispose() async {
await subscription?.cancel();
serial.dispose();
await serial.dispose();
}
}
7 changes: 4 additions & 3 deletions lib/src/messages/can.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ library;

import "dart:async";

import "package:burt_network/generated.dart";
import "package:burt_network/burt_network.dart";
import "package:subsystems/subsystems.dart";

import "service.dart";
Expand Down Expand Up @@ -43,9 +43,10 @@ class CanService extends MessageService {

/// Initializes the CAN library.
@override
Future<void> init() async {
await can.init();
Future<bool> init() async {
if (!await can.init()) return false;
_subscription = can.incomingMessages.listen(onMessage);
return true;
}

/// Disposes the native CAN library and any resources it holds.
Expand Down
Loading

0 comments on commit 4546504

Please sign in to comment.