const std = @import("std"); const AzEl = @import("./LabjackYaesu.zig").AzEl; const lj = @import("./labjack.zig"); const Config = @This(); var global_internal: Config = undefined; pub const global: *const Config = &global_internal; pub fn load(allocator: std.mem.Allocator, reader: anytype) !void { var jread = std.json.Reader(1024, @TypeOf(reader)).init(allocator, reader); defer jread.deinit(); global_internal = try std.json.parseFromTokenSourceLeaky( Config, allocator, &jread, .{}, ); } pub fn loadDefault(allocator: std.mem.Allocator) void { _ = allocator; global_internal = .{}; } pub fn destroy(allocator: std.mem.Allocator) void { // TODO: implement this probably _ = allocator; } rotctl: RotControlConfig = .{ .listen_address = "127.0.0.1", .listen_port = 4533, }, labjack: LabjackConfig = .{ .device = .autodetect, .feedback_calibration = .{ // NOTE: these min and max angles are treated as hardware limits. This serves // two purposes: first, it means that feedback is always interpolated, // never extrapolated (though with a two point calibration, that doesn't // matter much). Second, it prevents having a redundant set of bounds // values that could potentially desync from these and cause problems. // // The functional min and max are these plus the angle offset values. For // example, given controller.angle_offset.azimuth = -6, the practical minimum // azimuth would be -6 deg and the practical maximum would be 444 deg. .azimuth = .{ .minimum = .{ .voltage = 0.0, .angle = 0.0 }, .maximum = .{ .voltage = 5.0, .angle = 450.0 }, }, .elevation = .{ .minimum = .{ .voltage = 0.0, .angle = 0.0 }, .maximum = .{ .voltage = 5.0, .angle = 180.0 }, }, }, }, controller: ControllerConfig = .{ .azimuth_input = .{ .channel = .diff_01, .range = .@"5 V" }, .elevation_input = .{ .channel = .diff_23, .range = .@"5 V" }, .azimuth_outputs = .{ .increase = .{ .io = 0 }, .decrease = .{ .io = 1 } }, .elevation_outputs = .{ .increase = .{ .io = 2 }, .decrease = .{ .io = 3 } }, .loop_interval_ns = 50_000_000, .parking_posture = .{ .azimuth = 180, .elevation = 90 }, .angle_tolerance = .{ .azimuth = 1, .elevation = 1 }, .angle_offset = .{ .azimuth = 0, .elevation = 0 }, }, pub const VoltAngle = struct { voltage: f64, angle: f64 }; pub const MinMax = struct { minimum: VoltAngle, maximum: VoltAngle, pub inline fn slope(self: MinMax) f64 { return self.angleDiff() / self.voltDiff(); } pub inline fn voltDiff(self: MinMax) f64 { return self.maximum.voltage - self.minimum.voltage; } pub inline fn angleDiff(self: MinMax) f64 { return self.maximum.angle - self.minimum.angle; } }; const RotControlConfig = struct { listen_address: []const u8, listen_port: u16, }; const LabjackConfig = struct { device: union(enum) { autodetect, serial_number: i32, }, // Very basic two-point calibration for each degree of freedom. All other angles are // linearly interpolated from these two points. This assumes the feedback is linear, // which seems to be a mostly reasonable assumption in practice. feedback_calibration: struct { azimuth: MinMax, elevation: MinMax, }, }; const ControllerConfig = struct { azimuth_input: lj.AnalogInput, elevation_input: lj.AnalogInput, azimuth_outputs: OutPair, elevation_outputs: OutPair, loop_interval_ns: u64, parking_posture: AzEl, angle_tolerance: AzEl, angle_offset: AzEl, const OutPair = struct { increase: lj.DigitalOutputChannel, decrease: lj.DigitalOutputChannel, }; };