Compare commits
5 Commits
a9e8819b3e
...
153dde40aa
Author | SHA1 | Date | |
---|---|---|---|
153dde40aa | |||
4777d04594 | |||
eb7ad4ef9e | |||
2194dd4a8c | |||
de76cce706 |
22
build.zig
22
build.zig
@ -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"));
|
||||
}
|
||||
|
26
deps/labjack/exodriver/build.zig
vendored
26
deps/labjack/exodriver/build.zig
vendored
@ -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"));
|
||||
|
||||
|
26
deps/labjack/ljacklm/build.zig
vendored
26
deps/labjack/ljacklm/build.zig
vendored
@ -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
16
deps/libusb/build.zig
vendored
@ -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),
|
||||
|
@ -24,7 +24,7 @@ Unfortunately, all platforms have additional steps that must be taken (some easi
|
||||
|
||||
#### Windows
|
||||
|
||||
This compiles for, and runs on, Windows. However, the Labjack U12 by default gets assigned the Windows USB HID driver, which causes the USB report descriptor control transfer read to get mangled for mystery reasons that presumably made sense to some egghead at Microsoft at some point in time. Fortunately, this can be relatively easily fixed by using a tool like [zadig] to set the Labjack U12 to use the WinUSB driver instead of the HID driver. This is not an endorsement of the above outlined process, but rather an explanation for persons either foolish or desperate.
|
||||
This compiles for, and runs on, Windows. However, in order for it to work out of the box, it has to link against the (proprietary) Windows LabJack U12 driver instead of the open source libUSB driver. This has two main limitations: the Windows driver is only available for the `x86` and `x86-64` architectures, and it is exclusively distributed as a dynamic library, meaning that `yaes.exe` must be distributed alongside `ljackuw.dll`. When building for Windows targets, the appropriate library file will be copied to the binary installation directory (`zig-out/bin` by default).
|
||||
|
||||
#### macOS
|
||||
|
||||
@ -33,5 +33,3 @@ This works on macOS, though it has to be run with `sudo`, as access to the USB h
|
||||
#### Linux
|
||||
|
||||
You probably need to install the included udev rules file in order for the USB device to be accessible as a user. This is buried in the source tree as `deps/labjack/exodriver/90-labjack.rules`. These should probably go in `/etc/udev/rules.d` if you are installing them manually and I have properly understood the various Linux folder conventions.
|
||||
|
||||
[zadig]: https://zadig.akeo.ie
|
||||
|
@ -5,31 +5,37 @@ 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 {
|
||||
|
@ -17,6 +17,24 @@ pub const AzEl = struct {
|
||||
elevation: f64,
|
||||
};
|
||||
|
||||
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);
|
||||
@ -116,6 +134,18 @@ 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(
|
||||
@ -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) u21 {
|
||||
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,24 @@ 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(
|
||||
// -180.1 is 6 chars. -5.20 is 5 chars
|
||||
"az: {d: >6.1}° ({d: >5.2} V) Δ {d: >6.1}° => {u}, el: {d: >6.1}° ({d: >5.2} V) Δ {d: >6.1}° => {u}",
|
||||
.{
|
||||
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 {
|
||||
|
68
src/main.zig
68
src/main.zig
@ -1,8 +1,10 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
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");
|
||||
|
||||
@ -13,6 +15,11 @@ fn printStderr(comptime fmt: []const u8, args: anytype) void {
|
||||
}
|
||||
|
||||
pub fn main() !u8 {
|
||||
if (comptime builtin.os.tag == .windows) {
|
||||
// set output to UTF-8 on Windows
|
||||
_ = std.os.windows.kernel32.SetConsoleOutputCP(65001);
|
||||
}
|
||||
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer _ = gpa.deinit();
|
||||
const allocator = gpa.allocator();
|
||||
@ -47,37 +54,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 +109,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 +291,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
|
||||
|
Loading…
x
Reference in New Issue
Block a user