Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions bin/rover.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ void main(List<String> cliArgs) async {
final parser = ArgParser();
final programNames = { for (final program in programs) program.name };
parser.addOption("only", help: "Only compile the given program", allowed: programNames);
parser.addOption("config", help: "Path to JSON config file for device mappings");
parser.addFlag("compile", help: "Compile all rover programs", defaultsTo: true);
parser.addFlag("udev", help: "Generate udev rules", defaultsTo: true);
parser.addFlag("offline", help: "Skip any steps that require internet", negatable: false);
Expand All @@ -21,11 +22,12 @@ void main(List<String> cliArgs) async {
final udev = args.flag("udev");
final compile = args.flag("compile");
final only = args.option("only");
final configPath = args.option("config");
Logger.level = verbose ? LogLevel.all : LogLevel.info;

if (showHelp) {
// ignore: avoid_print
print("\nUsage: dart run :rover [--offline] [--verbose] [--only <program>] [--help]\n${parser.usage}");
print("\nUsage: dart run :rover [--offline] [--verbose] [--only <program>] [--config <path>] [--help]\n${parser.usage}");
return;
}

Expand All @@ -35,7 +37,7 @@ void main(List<String> cliArgs) async {

if (udev) {
logger.info("Generating udev rules...");
await writeUdevFile();
await writeUdevFile(configPath: configPath);
}

logger.info("Done!");
Expand Down
51 changes: 51 additions & 0 deletions camera_config.example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"devices": [
{
"humanName": "Front Camera",
"alias": "rover_cam_front",
"type": "camera",
"port": "3-2:1.0",
"index": 0
},
{
"humanName": "Rear Camera",
"alias": "rover_cam_rear",
"type": "camera",
"port": "1-2.4:1.0",
"index": 0
},
{
"humanName": "Bottom Left Camera",
"alias": "rover_cam_bottom_left",
"type": "camera",
"port": "1-2.3:1.0",
"index": 0
},
{
"humanName": "Bottom Right Camera",
"alias": "rover_cam_bottom_right",
"type": "camera",
"port": "1-2.2:1.0",
"index": 0
},
{
"humanName": "RealSense RGB",
"alias": "rover_realsense_rgb",
"type": "camera",
"interface": "Intel(R) RealSense(TM) Depth Camera 435i RGB",
"index": 0
},
{
"humanName": "GPS",
"alias": "rover_gps",
"type": "serial",
"manufacturer": "u-blox AG - www.u-blox.com"
},
{
"humanName": "IMU",
"alias": "rover_imu",
"type": "serial",
"product": "CDC + MSD Demo"
}
]
}
28 changes: 28 additions & 0 deletions lib/src/device.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import "package:burt_network/burt_network.dart";

/// The type of USB-device, which determines what subsystem it is.
enum DeviceType {
/// A camera.
Expand Down Expand Up @@ -59,4 +61,30 @@ class Device {
this.interface,
this.serialNumber,
});

/// Creates a USB device from a JSON configuration.
factory Device.fromJson(Json json) => Device(
humanName: json["humanName"] as String,
alias: json["alias"] as String,
type: DeviceType.values.byName(json["type"] as String),
manufacturer: json["manufacturer"] as String?,
product: json["product"] as String?,
index: json["index"] as int?,
port: json["port"] as String?,
interface: json["interface"] as String?,
serialNumber: json["serialNumber"] as String?,
);

/// Converts this device to a JSON map.
Json toJson() => {
"humanName": humanName,
"alias": alias,
"type": type.name,
if (manufacturer != null) "manufacturer": manufacturer,
if (product != null) "product": product,
if (index != null) "index": index,
if (port != null) "port": port,
if (interface != null) "interface": interface,
if (serialNumber != null) "serialNumber": serialNumber,
};
}
40 changes: 36 additions & 4 deletions lib/src/utils.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import "dart:convert";
import "dart:io";

import "package:burt_network/logging.dart";
import "package:burt_network/burt_network.dart" hide Device;
import "package:rover/rover.dart";

/// The logger for all scripts.
Expand Down Expand Up @@ -65,10 +66,41 @@ Future<void> writeSystemdFile(RoverProgram program) async {
}

/// Writes a udev rules file to `/etc/udev/rules.d` with all devices at once.
Future<void> writeUdevFile() async {
Future<void> writeUdevFile({String? configPath}) async {
// Load devices from config or use defaults
var deviceList = devices;

if (configPath != null) {
logger.info("Loading device config from $configPath");
final configFile = File(configPath);
if (!configFile.existsSync()) {
logger.error("Config file not found: $configPath");
exit(1);
}

try {
final configContent = await configFile.readAsString();
final configJson = jsonDecode(configContent) as Json;
final devicesJson = configJson["devices"] as List<dynamic>;

deviceList = [
for (final json in devicesJson)
Device.fromJson(json as Json),
];

logger.info("Loaded ${deviceList.length} devices from config");
} on FormatException catch (error) {
logger.error("Invalid JSON in config file: $error");
exit(1);
} catch (error) {
logger.error("Failed to parse config file: $error");
exit(1);
}
}

final buffer = StringBuffer();
buffer.writeln(udevHeader);
for (final device in devices) {
for (final device in deviceList) {
logger.debug("Generating udev rules for ${device.humanName}");
final rule = device.udevRule;
logger.trace(rule);
Expand All @@ -84,5 +116,5 @@ Future<void> writeUdevFile() async {
await runCommand("sudo", ["udevadm", "control", "--reload-rules"]);
await runCommand("sudo", ["udevadm", "trigger"]);
await udevFile.delete();
logger.debug("Generated udev rules with ${devices.length} devices");
logger.debug("Generated udev rules with ${deviceList.length} devices");
}