Compare commits
5 Commits
011f300f0a
...
8d9fee7796
Author | SHA1 | Date | |
---|---|---|---|
8d9fee7796 | |||
b12a44b88b | |||
8ccd292fed | |||
2afb88ef5f | |||
e5d8a716b0 |
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),
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
62
src/main.zig
62
src/main.zig
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user