Compare commits
4 Commits
bd465af30d
...
b0aac111a2
Author | SHA1 | Date | |
---|---|---|---|
b0aac111a2 | |||
7fbfe1c5f7 | |||
7105775426 | |||
0e88022a8d |
@ -32,11 +32,20 @@ pub fn destroy(allocator: std.mem.Allocator) void {
|
|||||||
|
|
||||||
rotctl: RotControlConfig = .{
|
rotctl: RotControlConfig = .{
|
||||||
.listen_address = "127.0.0.1",
|
.listen_address = "127.0.0.1",
|
||||||
.listen_port = 5432,
|
.listen_port = 4533,
|
||||||
},
|
},
|
||||||
labjack: LabjackConfig = .{
|
labjack: LabjackConfig = .{
|
||||||
.device = .autodetect,
|
.device = .autodetect,
|
||||||
.feedback_calibration = .{
|
.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 = .{
|
.azimuth = .{
|
||||||
.minimum = .{ .voltage = 0.0, .angle = 0.0 },
|
.minimum = .{ .voltage = 0.0, .angle = 0.0 },
|
||||||
.maximum = .{ .voltage = 5.0, .angle = 450.0 },
|
.maximum = .{ .voltage = 5.0, .angle = 450.0 },
|
||||||
@ -48,14 +57,17 @@ labjack: LabjackConfig = .{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
controller: ControllerConfig = .{
|
controller: ControllerConfig = .{
|
||||||
.azimuth_input = .{ .channel = .diff_01, .gain_index = 2 },
|
.azimuth_input = .{ .channel = .diff_01, .range = .@"5 V" },
|
||||||
.elevation_input = .{ .channel = .diff_23, .gain_index = 2 },
|
.elevation_input = .{ .channel = .diff_23, .range = .@"5 V" },
|
||||||
.azimuth_outputs = .{ .increase = .{ .io = 0 }, .decrease = .{ .io = 1 } },
|
.azimuth_outputs = .{ .increase = .{ .io = 0 }, .decrease = .{ .io = 1 } },
|
||||||
.elevation_outputs = .{ .increase = .{ .io = 2 }, .decrease = .{ .io = 3 } },
|
.elevation_outputs = .{ .increase = .{ .io = 2 }, .decrease = .{ .io = 3 } },
|
||||||
.loop_interval_ns = 50_000_000,
|
.loop_interval_ns = 50_000_000,
|
||||||
.parking_posture = .{ .azimuth = 180, .elevation = 90 },
|
.parking_posture = .{ .azimuth = 180, .elevation = 90 },
|
||||||
.angle_tolerance = .{ .azimuth = 1, .elevation = 1 },
|
.angle_tolerance = .{ .azimuth = 1, .elevation = 1 },
|
||||||
.angle_offset = .{ .azimuth = 0, .elevation = 0 },
|
.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 };
|
pub const VoltAngle = struct { voltage: f64, angle: f64 };
|
||||||
@ -106,6 +118,7 @@ const ControllerConfig = struct {
|
|||||||
parking_posture: AzEl,
|
parking_posture: AzEl,
|
||||||
angle_tolerance: AzEl,
|
angle_tolerance: AzEl,
|
||||||
angle_offset: AzEl,
|
angle_offset: AzEl,
|
||||||
|
elevation_mask: f64,
|
||||||
|
|
||||||
const OutPair = struct {
|
const OutPair = struct {
|
||||||
increase: lj.DigitalOutputChannel,
|
increase: lj.DigitalOutputChannel,
|
||||||
|
@ -40,7 +40,13 @@ pub fn setTarget(self: LabjackYaesu, target: AzEl) void {
|
|||||||
defer self.lock.unlock();
|
defer self.lock.unlock();
|
||||||
|
|
||||||
const controller = @constCast(self.controller);
|
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;
|
controller.requested_state = .running;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
const std = @import("std");
|
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 LabjackYaesu = @import("./LabjackYaesu.zig");
|
||||||
|
|
||||||
const RotCtl = @This();
|
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;
|
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 {
|
fn setPosition(self: *RotCtl, _: []const u8, tokens: *TokenIter) CommandError!void {
|
||||||
const azimuth = std.fmt.parseFloat(f64, tokens.next() orelse {
|
const azimuth = std.fmt.parseFloat(f64, tokens.next() orelse {
|
||||||
return self.replyStatus(.invalid_parameter) catch error.BadOutput;
|
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;
|
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 {
|
const elevation = std.fmt.parseFloat(f64, tokens.next() orelse {
|
||||||
return self.replyStatus(.invalid_parameter) catch error.BadOutput;
|
return self.replyStatus(.invalid_parameter) catch error.BadOutput;
|
||||||
}) catch {
|
}) catch {
|
||||||
return self.replyStatus(.invalid_parameter) catch error.BadOutput;
|
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 });
|
self.rotator.setTarget(.{ .azimuth = azimuth, .elevation = elevation });
|
||||||
return self.replyStatus(.okay) catch error.BadOutput;
|
return self.replyStatus(.okay) catch error.BadOutput;
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@ pub const Labjack = struct {
|
|||||||
&id,
|
&id,
|
||||||
@intFromBool(self.demo),
|
@intFromBool(self.demo),
|
||||||
input.channelNumber(),
|
input.channelNumber(),
|
||||||
input.gain_index,
|
input.gainIndex(),
|
||||||
&over_v,
|
&over_v,
|
||||||
&res.voltage,
|
&res.voltage,
|
||||||
);
|
);
|
||||||
@ -93,7 +93,7 @@ pub const Labjack = struct {
|
|||||||
var gains: [incount]c_long = undefined;
|
var gains: [incount]c_long = undefined;
|
||||||
for (inputs, &in_channels, &gains) |from, *inc, *gain| {
|
for (inputs, &in_channels, &gains) |from, *inc, *gain| {
|
||||||
inc.* = from.channelNumber();
|
inc.* = from.channelNumber();
|
||||||
gain.* = from.gain_index;
|
gain.* = from.gainIndex();
|
||||||
}
|
}
|
||||||
var v_out: [4]f32 = .{0} ** 4;
|
var v_out: [4]f32 = .{0} ** 4;
|
||||||
var over_v: c_long = 0;
|
var over_v: c_long = 0;
|
||||||
@ -133,11 +133,15 @@ pub const Labjack = struct {
|
|||||||
|
|
||||||
pub const AnalogInput = struct {
|
pub const AnalogInput = struct {
|
||||||
channel: AnalogInputChannel,
|
channel: AnalogInputChannel,
|
||||||
gain_index: GainIndex = 0,
|
range: InputRange = .@"20 V",
|
||||||
|
|
||||||
pub fn channelNumber(self: AnalogInput) u4 {
|
pub fn channelNumber(self: AnalogInput) u4 {
|
||||||
return @intFromEnum(self.channel);
|
return @intFromEnum(self.channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn gainIndex(self: AnalogInput) GainIndex {
|
||||||
|
return @intFromEnum(self.range);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const AnalogReadResult = struct {
|
pub const AnalogReadResult = struct {
|
||||||
@ -207,6 +211,17 @@ pub const DigitalOutputChannel = union(enum) {
|
|||||||
// 7 => G=20 ±1 volt
|
// 7 => G=20 ±1 volt
|
||||||
pub const GainIndex = u3;
|
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) {
|
pub const PackedOutput = packed struct(u4) {
|
||||||
io0: bool,
|
io0: bool,
|
||||||
io1: bool,
|
io1: bool,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user