Compare commits

...

4 Commits

Author SHA1 Message Date
b0aac111a2
controller: implement elevation mask
There's not really any analogous concept for azimuth. This is for a
specific piece of hardware, so there's no real point in making it more
generic. This is respected at the 180 degree point as well, for
software that can handle flipped rotation configurations.

This doesn't currently play well with the elevation offset setting,
which should be applied after this clamping operation. Either this
needs to be moved to the API layer or (more appropriately) the input
range validation needs to move in the controller.
2024-07-06 13:04:55 -07:00
7fbfe1c5f7
rotctl: do range validation
This is at the interface layer though it should arguably be done at the
controller layer. Oh well.
2024-07-06 12:59:48 -07:00
7105775426
labjack: use symbolic gain values
This reads better in the config file.
2024-07-06 12:58:27 -07:00
0e88022a8d
config: change default listen port
This now matches the rotctld default listen port
2024-07-06 12:56:10 -07:00
4 changed files with 70 additions and 8 deletions

View File

@ -32,11 +32,20 @@ pub fn destroy(allocator: std.mem.Allocator) void {
rotctl: RotControlConfig = .{
.listen_address = "127.0.0.1",
.listen_port = 5432,
.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 },
@ -48,14 +57,17 @@ labjack: LabjackConfig = .{
},
},
controller: ControllerConfig = .{
.azimuth_input = .{ .channel = .diff_01, .gain_index = 2 },
.elevation_input = .{ .channel = .diff_23, .gain_index = 2 },
.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 },
// this is a symmetric mask, so the minimum usable elevation is elevation_mask deg
// and the maximum usable elevation is 180 - elevation_mask deg
.elevation_mask = 0.0,
},
pub const VoltAngle = struct { voltage: f64, angle: f64 };
@ -106,6 +118,7 @@ const ControllerConfig = struct {
parking_posture: AzEl,
angle_tolerance: AzEl,
angle_offset: AzEl,
elevation_mask: f64,
const OutPair = struct {
increase: lj.DigitalOutputChannel,

View File

@ -40,7 +40,13 @@ pub fn setTarget(self: LabjackYaesu, target: AzEl) void {
defer self.lock.unlock();
const controller = @constCast(self.controller);
controller.target = target;
controller.target = .{
.azimuth = target.azimuth,
.elevation = @min(
@max(target.elevation, config.controller.elevation_mask),
180.0 - config.controller.elevation_mask,
),
};
controller.requested_state = .running;
}

View File

@ -1,6 +1,7 @@
const std = @import("std");
const config = @import("./Config.zig").global;
const Config = @import("./Config.zig");
const config = Config.global;
const LabjackYaesu = @import("./LabjackYaesu.zig");
const RotCtl = @This();
@ -113,6 +114,27 @@ fn getPosition(self: *RotCtl, _: []const u8, tokens: *TokenIter) CommandError!vo
self.printReply("{d:.1}\n{d:.1}", .{ pos.azimuth, pos.elevation }) catch return error.BadOutput;
}
fn inRange(request: f64, comptime dof: enum { azimuth, elevation }) bool {
return switch (dof) {
// zig fmt: off
.azimuth => request >= (
config.labjack.feedback_calibration.azimuth.minimum.angle
+ config.controller.angle_offset.azimuth
) and request <= (
config.labjack.feedback_calibration.azimuth.maximum.angle
+ config.controller.angle_offset.azimuth
),
.elevation => request >= (
config.labjack.feedback_calibration.elevation.minimum.angle
+ config.controller.angle_offset.elevation
) and request <= (
config.labjack.feedback_calibration.elevation.maximum.angle
+ config.controller.angle_offset.elevation
),
// zig fmt: on
};
}
fn setPosition(self: *RotCtl, _: []const u8, tokens: *TokenIter) CommandError!void {
const azimuth = std.fmt.parseFloat(f64, tokens.next() orelse {
return self.replyStatus(.invalid_parameter) catch error.BadOutput;
@ -120,12 +142,18 @@ fn setPosition(self: *RotCtl, _: []const u8, tokens: *TokenIter) CommandError!vo
return self.replyStatus(.invalid_parameter) catch error.BadOutput;
};
if (!inRange(azimuth, .azimuth))
return self.replyStatus(.invalid_parameter) catch error.BadOutput;
const elevation = std.fmt.parseFloat(f64, tokens.next() orelse {
return self.replyStatus(.invalid_parameter) catch error.BadOutput;
}) catch {
return self.replyStatus(.invalid_parameter) catch error.BadOutput;
};
if (!inRange(elevation, .elevation))
return self.replyStatus(.invalid_parameter) catch error.BadOutput;
self.rotator.setTarget(.{ .azimuth = azimuth, .elevation = elevation });
return self.replyStatus(.okay) catch error.BadOutput;
}

View File

@ -50,7 +50,7 @@ pub const Labjack = struct {
&id,
@intFromBool(self.demo),
input.channelNumber(),
input.gain_index,
input.gainIndex(),
&over_v,
&res.voltage,
);
@ -93,7 +93,7 @@ pub const Labjack = struct {
var gains: [incount]c_long = undefined;
for (inputs, &in_channels, &gains) |from, *inc, *gain| {
inc.* = from.channelNumber();
gain.* = from.gain_index;
gain.* = from.gainIndex();
}
var v_out: [4]f32 = .{0} ** 4;
var over_v: c_long = 0;
@ -133,11 +133,15 @@ pub const Labjack = struct {
pub const AnalogInput = struct {
channel: AnalogInputChannel,
gain_index: GainIndex = 0,
range: InputRange = .@"20 V",
pub fn channelNumber(self: AnalogInput) u4 {
return @intFromEnum(self.channel);
}
pub fn gainIndex(self: AnalogInput) GainIndex {
return @intFromEnum(self.range);
}
};
pub const AnalogReadResult = struct {
@ -207,6 +211,17 @@ pub const DigitalOutputChannel = union(enum) {
// 7 => G=20 ±1 volt
pub const GainIndex = u3;
pub const InputRange = enum(GainIndex) {
@"20 V" = 0,
@"10 V" = 1,
@"5 V" = 2,
@"4 V" = 3,
@"2.5 V" = 4,
@"2 V" = 5,
@"1.25 V" = 6,
@"1 V" = 7,
};
pub const PackedOutput = packed struct(u4) {
io0: bool,
io1: bool,