Compare commits

...

5 Commits

Author SHA1 Message Date
8d9fee7796
build: disable libusb logging by default
It is quite verbose and not very useful.
2024-07-11 22:14:20 -07:00
b12a44b88b
main: hook up calibration stubs
I guess I will be finishing this later.
2024-07-11 22:01:16 -07:00
8ccd292fed
config: make it possible not to leak
Using the GPA is a bit annoying sometimes. The other option would be to
just use page allocator to allocate the config and bypass the GPA.
2024-07-11 21:55:34 -07:00
2afb88ef5f
controller: make controller info printout more useful
This has a lot more relevant information now. Anyway, this has been
tested on real hardware, and it appears to work pretty well. I am
considering changing the control loop so that it isn't always
operating on stale feedback (two LabJack calls per loop when actively
controlling pointing). Also the calibration routines need to be
implemented.
2024-07-11 21:55:34 -07:00
e5d8a716b0
improve a name 2024-07-11 21:55:34 -07:00
8 changed files with 209 additions and 57 deletions

View File

@ -4,12 +4,24 @@ pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const use_udev = b.option(
const libusb_use_udev = b.option(
bool,
"use_udev",
"link and use udev (Linux only. Default: false)",
) orelse false;
const libusb_enable_logging = b.option(
bool,
"libusb_enable_logging",
"enable libusb's built-in logging (Default: false)",
) orelse false;
const libusb_enable_debug_logging = b.option(
bool,
"libusb_enable_debug_logging",
"enable libusb's debug logging (Default: false)",
) orelse false;
const exe = b.addExecutable(.{
.name = "yaes",
.root_source_file = b.path("src/main.zig"),
@ -38,7 +50,13 @@ pub fn build(b: *std.Build) void {
} else {
const ljacklm_dep = b.dependency(
"ljacklm",
.{ .target = target, .optimize = optimize, .use_udev = use_udev },
.{
.target = target,
.optimize = optimize,
.libusb_use_udev = libusb_use_udev,
.libusb_enable_logging = libusb_enable_logging,
.libusb_enable_debug_logging = libusb_enable_debug_logging,
},
);
exe.linkLibrary(ljacklm_dep.artifact("ljacklm"));
}

View File

@ -4,10 +4,22 @@ pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const use_udev = b.option(
const libusb_use_udev = b.option(
bool,
"use_udev",
"link and use udev (Linux only. Default: false)",
"libusb_use_udev",
"libusb: link and use udev (Linux only. Default: false)",
) orelse false;
const libusb_enable_logging = b.option(
bool,
"libusb_enable_logging",
"enable libusb's built-in logging (Default: false)",
) orelse false;
const libusb_enable_debug_logging = b.option(
bool,
"libusb_enable_debug_logging",
"enable libusb's debug logging (Default: false)",
) orelse false;
const liblabjackusb = b.addStaticLibrary(.{
@ -31,7 +43,13 @@ pub fn build(b: *std.Build) !void {
const usb_dep = b.dependency(
"usb",
.{ .target = target, .optimize = optimize, .use_udev = use_udev },
.{
.target = target,
.optimize = optimize,
.use_udev = libusb_use_udev,
.enable_logging = libusb_enable_logging,
.enable_debug_logging = libusb_enable_debug_logging,
},
);
liblabjackusb.linkLibrary(usb_dep.artifact("usb"));

View File

@ -4,10 +4,22 @@ pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const use_udev = b.option(
const libusb_use_udev = b.option(
bool,
"use_udev",
"link and use udev (Linux only. Default: false)",
"libusb_use_udev",
"libusb: link and use udev (Linux only. Default: false)",
) orelse false;
const libusb_enable_logging = b.option(
bool,
"libusb_enable_logging",
"enable libusb's built-in logging (Default: false)",
) orelse false;
const libusb_enable_debug_logging = b.option(
bool,
"libusb_enable_debug_logging",
"enable libusb's debug logging (Default: false)",
) orelse false;
const libljacklm = b.addStaticLibrary(.{
@ -30,7 +42,13 @@ pub fn build(b: *std.Build) !void {
const usb_dep = b.dependency(
"labjackusb",
.{ .target = target, .optimize = optimize, .use_udev = use_udev },
.{
.target = target,
.optimize = optimize,
.libusb_use_udev = libusb_use_udev,
.libusb_enable_logging = libusb_enable_logging,
.libusb_enable_debug_logging = libusb_enable_debug_logging,
},
);
libljacklm.linkLibrary(usb_dep.artifact("labjackusb"));

16
deps/libusb/build.zig vendored
View File

@ -10,6 +10,18 @@ pub fn build(b: *std.Build) !void {
"link and use udev (Linux only. Default: false)",
) orelse false;
const enable_logging = b.option(
bool,
"enable_logging",
"enable libusb's built-in logging (Default: false)",
) orelse false;
const enable_debug_logging = b.option(
bool,
"enable_debug_logging",
"enable libusb's debug logging (Default: false)",
) orelse false;
const libusb = b.addStaticLibrary(.{
.name = "usb",
.target = target,
@ -59,8 +71,8 @@ pub fn build(b: *std.Build) !void {
.{ .style = .{ .autoconf = b.path("config.h.in") } },
.{
.DEFAULT_VISIBILITY = .@"__attribute__ ((visibility (\"default\")))",
.ENABLE_DEBUG_LOGGING = oneOrNull(optimize == .Debug),
.ENABLE_LOGGING = oneOrNull(optimize == .Debug),
.ENABLE_DEBUG_LOGGING = oneOrNull(enable_debug_logging),
.ENABLE_LOGGING = oneOrNull(enable_logging),
.HAVE_ASM_TYPES_H = null,
.HAVE_CLOCK_GETTIME = oneOrNull(linux_target),
.HAVE_DECL_EFD_CLOEXEC = oneOrNull(linux_target),

View File

@ -1,35 +1,41 @@
const std = @import("std");
const AzEl = @import("./LabjackYaesu.zig").AzEl;
const AzEl = @import("./YaesuController.zig").AzEl;
const lj = @import("./labjack.zig");
const Config = @This();
var global_internal: Config = undefined;
pub const global: *const Config = &global_internal;
var global_internal: std.json.Parsed(Config) = undefined;
pub const global: *const Config = &global_internal.value;
pub fn load(allocator: std.mem.Allocator, reader: anytype, err_writer: anytype) !void {
var jread = std.json.Reader(1024, @TypeOf(reader)).init(allocator, reader);
defer jread.deinit();
global_internal = try std.json.parseFromTokenSourceLeaky(
global_internal = try std.json.parseFromTokenSource(
Config,
allocator,
&jread,
.{},
);
try global_internal.validate(err_writer);
try global.validate(err_writer);
}
pub fn loadDefault(allocator: std.mem.Allocator) void {
_ = allocator;
global_internal = .{};
const arena = allocator.create(std.heap.ArenaAllocator) catch unreachable;
arena.* = std.heap.ArenaAllocator.init(allocator);
global_internal = .{
.arena = arena,
.value = .{},
};
}
pub fn destroy(allocator: std.mem.Allocator) void {
pub fn deinit() void {
// TODO: implement this probably
_ = allocator;
const allocator = global_internal.arena.child_allocator;
global_internal.arena.deinit();
allocator.destroy(global_internal.arena);
}
pub fn validate(self: Config, err_writer: anytype) !void {

View File

@ -2,7 +2,7 @@ const std = @import("std");
const Config = @import("./Config.zig");
const config = Config.global;
const LabjackYaesu = @import("./LabjackYaesu.zig");
const YaesuController = @import("./YaesuController.zig");
const RotCtl = @This();
@ -10,7 +10,7 @@ const log = std.log.scoped(.RotCtl);
writer: std.io.BufferedWriter(512, std.net.Stream.Writer),
running: bool,
rotator: LabjackYaesu,
rotator: YaesuController,
pub fn run(allocator: std.mem.Allocator) !void {
// var server = std.net.StreamServer.init(.{ .reuse_address = true });
@ -30,7 +30,7 @@ pub fn run(allocator: std.mem.Allocator) !void {
var interface: RotCtl = .{
.writer = undefined,
.running = true,
.rotator = try LabjackYaesu.init(allocator),
.rotator = try YaesuController.init(allocator),
};
while (true) {

View File

@ -4,9 +4,9 @@ const lj = @import("./labjack.zig");
const Config = @import("./Config.zig");
const config = Config.global;
const log = std.log.scoped(.labjack_yaesu);
const log = std.log.scoped(.yaesu_controller);
const LabjackYaesu = @This();
const YaesuController = @This();
control_thread: std.Thread,
lock: *std.Thread.Mutex,
@ -17,7 +17,25 @@ pub const AzEl = struct {
elevation: f64,
};
pub fn init(allocator: std.mem.Allocator) !LabjackYaesu {
pub const CalibrationRoutine = enum {
feedback,
orientation,
};
pub fn calibrate(allocator: std.mem.Allocator, routine: CalibrationRoutine) !void {
const controller = try YaesuController.init(allocator);
defer {
controller.quit();
controller.control_thread.join();
}
switch (routine) {
.feedback => try controller.calibrate_feedback(),
.orientation => try controller.calibrate_orientation(),
}
}
pub fn init(allocator: std.mem.Allocator) !YaesuController {
const lock = try allocator.create(std.Thread.Mutex);
errdefer allocator.destroy(lock);
lock.* = .{};
@ -56,7 +74,7 @@ fn inRange(request: f64, comptime dof: enum { azimuth, elevation }) bool {
};
}
pub fn setTarget(self: LabjackYaesu, target: AzEl) error{OutOfRange}!void {
pub fn setTarget(self: YaesuController, target: AzEl) error{OutOfRange}!void {
self.lock.lock();
defer self.lock.unlock();
@ -76,14 +94,14 @@ pub fn setTarget(self: LabjackYaesu, target: AzEl) error{OutOfRange}!void {
controller.requested_state = .running;
}
pub fn currentPosition(self: LabjackYaesu) AzEl {
pub fn currentPosition(self: YaesuController) AzEl {
self.lock.lock();
defer self.lock.unlock();
return self.controller.position;
}
pub fn startCalibration(self: LabjackYaesu) void {
pub fn startCalibration(self: YaesuController) void {
// there are two different types of calibration:
// 1. feedback calibration, running to the extents of the rotator
// 2. sun calibration, which determines the azimuth and elevation angle
@ -95,7 +113,7 @@ pub fn startCalibration(self: LabjackYaesu) void {
_ = self;
}
pub fn quit(self: LabjackYaesu) void {
pub fn quit(self: YaesuController) void {
self.lock.lock();
defer self.lock.unlock();
@ -103,7 +121,7 @@ pub fn quit(self: LabjackYaesu) void {
controller.requested_state = .stopped;
}
pub fn stop(self: LabjackYaesu) void {
pub fn stop(self: YaesuController) void {
self.lock.lock();
defer self.lock.unlock();
@ -112,14 +130,26 @@ pub fn stop(self: LabjackYaesu) void {
controller.requested_state = .idle;
}
pub fn startPark(self: LabjackYaesu) void {
pub fn startPark(self: YaesuController) void {
self.setTarget(config.controller.parking_posture) catch unreachable;
}
fn calibrate_feedback(self: YaesuController) !void {
_ = self;
log.err("this isn't implemented yet, sorry.", .{});
return error.NotImplemented;
}
fn calibrate_orientation(self: YaesuController) !void {
_ = self;
log.err("this isn't implemented yet, sorry.", .{});
return error.NotImplemented;
}
fn runController(controller: *Controller) void {
controller.run() catch {
log.err(
"the labjack control loop has terminated unexpectedly!!!!",
"the rotator control loop has terminated unexpectedly!!!!",
.{},
);
};
@ -180,7 +210,21 @@ const Controller = struct {
};
}
fn signDeadzone(offset: f64, deadzone: f64) enum { negative, zero, positive } {
const Sign = enum {
negative,
zero,
positive,
pub fn symbol(self: Sign) u8 {
return switch (self) {
.negative => '-',
.zero => '=',
.positive => '+',
};
}
};
fn signDeadzone(offset: f64, deadzone: f64) Sign {
return if (@abs(offset) < deadzone)
.zero
else if (offset < 0)
@ -224,11 +268,23 @@ const Controller = struct {
drive_signal[config.controller.elevation_outputs.increase.io] = elsign == .positive;
drive_signal[config.controller.elevation_outputs.decrease.io] = elsign == .negative;
log.info("drive: az = {s}, el = {s}. outputs: {any}", .{ @tagName(azsign), @tagName(elsign), drive_signal });
const raw = try self.labjack.readAnalogWriteDigital(2, inputs, drive_signal, true);
const angles = lerpAndOffsetAngles(raw);
return lerpAndOffsetAngles(raw);
log.info(
"az: {d:.1}° ({d:.2} V) {d:.1}° => {c}, el: {d:.1}° ({d:.2} V) {d:.1}° => {c}",
.{
angles.azimuth,
raw[0].voltage,
pos_error.azimuth,
azsign.symbol(),
angles.elevation,
raw[1].voltage,
pos_error.elevation,
elsign.symbol(),
},
);
return angles;
}
fn run(self: *Controller) !void {

View File

@ -3,6 +3,7 @@ const std = @import("std");
const Config = @import("./Config.zig");
const lj = @import("./labjack.zig");
const RotCtl = @import("./RotCtl.zig");
const YaesuController = @import("./YaesuController.zig");
const udev = @import("udev_rules");
@ -47,37 +48,42 @@ pub fn main() !u8 {
}
Config.loadDefault(allocator);
defer Config.deinit();
return writeDefaultConfig(if (args.len == 3) args[2] else null);
} else if (std.mem.eql(u8, args[1], commands.run)) {
if (args.len > 3) {
printHelp(exename, .run);
return 1;
}
blk: {
const confpath = if (args.len == 3) args[2] else "yaes.json";
const conf_file = std.fs.cwd().openFile(confpath, .{}) catch {
log.warn("Could not load config file '{s}'. Using default config.", .{confpath});
Config.loadDefault(allocator);
break :blk;
};
defer conf_file.close();
loadConfigOrDefault(allocator, if (args.len == 3) args[2] else null) catch
return 1;
Config.load(allocator, conf_file.reader(), std.io.getStdErr().writer()) catch |err| {
log.err("Could not parse config file '{s}': {s}.", .{ confpath, @errorName(err) });
return 1;
};
}
defer Config.destroy(allocator);
const ver = lj.getDriverVersion();
std.debug.print("Driver version: {d}\n", .{ver});
defer Config.deinit();
RotCtl.run(allocator) catch |err| {
log.err("rotator controller ceased unexpectedly! {s}", .{@errorName(err)});
return 1;
};
} else if (std.mem.eql(u8, args[1], commands.calibrate)) {
if (args.len < 3 or args.len > 4) {
printHelp(exename, .calibrate);
return 1;
}
return 0;
loadConfigOrDefault(allocator, if (args.len == 4) args[3] else null) catch
return 1;
defer Config.deinit();
const routine = std.meta.stringToEnum(YaesuController.CalibrationRoutine, args[2]) orelse {
log.err("{s} is not a known calibration routine.", .{args[2]});
printHelp(exename, .calibrate);
return 1;
};
YaesuController.calibrate(allocator, routine) catch |err| {
log.err("Calibration failed: {s}", .{@errorName(err)});
return 1;
};
} else if (std.mem.eql(u8, args[1], commands.help)) {
if (args.len != 3) {
printHelp(exename, .help);
@ -97,6 +103,24 @@ pub fn main() !u8 {
printHelp(exename, .main);
return 1;
}
return 0;
}
fn loadConfigOrDefault(allocator: std.mem.Allocator, path: ?[]const u8) !void {
const confpath = path orelse "yaes.json";
const conf_file = std.fs.cwd().openFile(confpath, .{}) catch {
log.warn("Could not load config file '{s}'. Using default config.", .{confpath});
Config.loadDefault(allocator);
return;
};
defer conf_file.close();
Config.load(allocator, conf_file.reader(), std.io.getStdErr().writer()) catch |err| {
log.err("Could not parse config file '{s}': {s}.", .{ confpath, @errorName(err) });
return error.InvalidConfig;
};
log.info("Loaded config from '{s}'.", .{confpath});
}
fn installUdevRules(outpath: ?[]const u8) u8 {
@ -261,7 +285,7 @@ const command_help = .{
\\ Perform a calibration routine and write an updated configuration with its results.
\\
\\Arguments:
\\ routine Must be one of `feedback` or `orientation`. The different calibration routines have
\\ routine Must be either `feedback` or `orientation`. The different calibration routines have
\\ different requirements. `orientation` calibration is a sun-pointing-based routine and
\\ should be performed after `feedback` calibration is complete.
\\ config_file [Optional] the path of a config file to load. This file will be updated with the