Compare commits

..

5 Commits

Author SHA1 Message Date
a9e8819b3e readme: the Windows situation has been altered
Pray I do not alter it further.
2024-07-15 16:58:33 -07:00
42e15a813a build: disable libusb logging by default
It is quite verbose and not very useful.
2024-07-15 16:58:33 -07:00
01b66150a3 main: hook up calibration stubs
I guess I will be finishing this later.
2024-07-15 16:58:33 -07:00
5ad5be9c4d config: make it possible not to leak
Using the GPA is a bit annoying sometimes. The other option would be to
just use page allocator to allocate the config and bypass the GPA.
2024-07-15 16:58:33 -07:00
d0d03b5088 controller: make controller info printout more useful
This has a lot more relevant information now. Anyway, this has been
tested on real hardware, and it appears to work pretty well. I am
considering changing the control loop so that it isn't always
operating on stale feedback (two LabJack calls per loop when actively
controlling pointing). Also the calibration routines need to be
implemented.
2024-07-15 16:58:33 -07:00
5 changed files with 112 additions and 294 deletions

View File

@@ -92,7 +92,6 @@ pub fn validate(self: Config, err_writer: anytype) !void {
rotctl: RotControlConfig = .{ rotctl: RotControlConfig = .{
.listen_address = "127.0.0.1", .listen_address = "127.0.0.1",
.listen_port = 4533, .listen_port = 4533,
.autopark = false,
}, },
labjack: LabjackConfig = .{ labjack: LabjackConfig = .{
.device = .autodetect, .device = .autodetect,
@@ -128,7 +127,6 @@ controller: ControllerConfig = .{
// this is a symmetric mask, so the minimum usable elevation is elevation_mask deg // this is a symmetric mask, so the minimum usable elevation is elevation_mask deg
// and the maximum usable elevation is 180 - elevation_mask deg // and the maximum usable elevation is 180 - elevation_mask deg
.elevation_mask = 0.0, .elevation_mask = 0.0,
.feedback_window_samples = 3,
}, },
pub const VoltAngle = struct { voltage: f64, angle: f64 }; pub const VoltAngle = struct { voltage: f64, angle: f64 };
@@ -152,7 +150,6 @@ pub const MinMax = struct {
const RotControlConfig = struct { const RotControlConfig = struct {
listen_address: []const u8, listen_address: []const u8,
listen_port: u16, listen_port: u16,
autopark: bool,
}; };
const LabjackConfig = struct { const LabjackConfig = struct {
@@ -182,8 +179,6 @@ const ControllerConfig = struct {
angle_offset: AzEl, angle_offset: AzEl,
elevation_mask: f64, elevation_mask: f64,
feedback_window_samples: u8,
const OutPair = struct { const OutPair = struct {
increase: lj.DigitalOutputChannel, increase: lj.DigitalOutputChannel,
decrease: lj.DigitalOutputChannel, decrease: lj.DigitalOutputChannel,

View File

@@ -10,9 +10,12 @@ const log = std.log.scoped(.RotCtl);
writer: std.io.BufferedWriter(512, std.net.Stream.Writer), writer: std.io.BufferedWriter(512, std.net.Stream.Writer),
running: bool, running: bool,
rotator: *YaesuController, rotator: YaesuController,
pub fn run(allocator: std.mem.Allocator) !void { pub fn run(allocator: std.mem.Allocator) !void {
// var server = std.net.StreamServer.init(.{ .reuse_address = true });
// defer server.deinit();
const listen_addr = try std.net.Address.parseIp( const listen_addr = try std.net.Address.parseIp(
config.rotctl.listen_address, config.rotctl.listen_address,
config.rotctl.listen_port, config.rotctl.listen_port,
@@ -27,19 +30,20 @@ pub fn run(allocator: std.mem.Allocator) !void {
var interface: RotCtl = .{ var interface: RotCtl = .{
.writer = undefined, .writer = undefined,
.running = true, .running = true,
.rotator = try YaesuController.create(allocator), .rotator = try YaesuController.init(allocator),
}; };
while (interface.running) { while (true) {
const client = try server.accept(); const client = try server.accept();
defer { defer {
log.info("disconnecting client", .{}); log.info("disconnecting client", .{});
if (!config.rotctl.autopark) interface.rotator.stop();
interface.rotator.stop();
client.stream.close(); client.stream.close();
} }
interface.writer = .{ .unbuffered_writer = client.stream.writer() }; interface.writer = .{ .unbuffered_writer = client.stream.writer() };
interface.running = true;
defer interface.running = false;
log.info("client connected from {}", .{client.address}); log.info("client connected from {}", .{client.address});
@@ -56,13 +60,7 @@ pub fn run(allocator: std.mem.Allocator) !void {
std.mem.trim(u8, fbs.getWritten(), &std.ascii.whitespace), std.mem.trim(u8, fbs.getWritten(), &std.ascii.whitespace),
) catch break; ) catch break;
} }
// loop ended due to client disconnect
if (interface.running and config.rotctl.autopark)
interface.rotator.startPark();
} }
interface.rotator.control_thread.join();
} }
fn write(self: *RotCtl, buf: []const u8) !void { fn write(self: *RotCtl, buf: []const u8) !void {
@@ -174,6 +172,7 @@ fn handleHamlibCommand(
if (first.len == 1 or first[0] == '\\') { if (first.len == 1 or first[0] == '\\') {
switch (first[0]) { switch (first[0]) {
// NOTE: this is not technically supported by rotctld. // NOTE: this is not technically supported by rotctld.
'q', 'Q' => try self.quit(first, &tokens),
'S' => try self.stop(first, &tokens), 'S' => try self.stop(first, &tokens),
'K' => try self.park(first, &tokens), 'K' => try self.park(first, &tokens),
'p' => try self.getPosition(first, &tokens), 'p' => try self.getPosition(first, &tokens),
@@ -244,8 +243,8 @@ const HamlibCommand = struct {
}; };
const rotctl_commands = [_]HamlibCommand{ const rotctl_commands = [_]HamlibCommand{
.{ .long = "quit", .callback = quit }, .{ .short = 'q', .callback = quit }, // quit
.{ .long = "exit", .callback = quit }, .{ .short = 'Q', .callback = quit }, // quit
.{ .long = "AOS", .callback = blindAck }, .{ .long = "AOS", .callback = blindAck },
.{ .long = "LOS", .callback = blindAck }, .{ .long = "LOS", .callback = blindAck },
.{ .short = 'P', .long = "set_pos", .callback = setPosition }, // azimuth: f64, elevation: f64 .{ .short = 'P', .long = "set_pos", .callback = setPosition }, // azimuth: f64, elevation: f64

View File

@@ -8,8 +8,6 @@ const log = std.log.scoped(.yaesu_controller);
const YaesuController = @This(); const YaesuController = @This();
pub var singleton: ?*YaesuController = null;
control_thread: std.Thread, control_thread: std.Thread,
lock: *std.Thread.Mutex, lock: *std.Thread.Mutex,
controller: *const Controller, controller: *const Controller,
@@ -25,7 +23,7 @@ pub const CalibrationRoutine = enum {
}; };
pub fn calibrate(allocator: std.mem.Allocator, routine: CalibrationRoutine) !void { pub fn calibrate(allocator: std.mem.Allocator, routine: CalibrationRoutine) !void {
const controller = try YaesuController.create(allocator); const controller = try YaesuController.init(allocator);
defer { defer {
controller.quit(); controller.quit();
controller.control_thread.join(); controller.control_thread.join();
@@ -37,29 +35,22 @@ pub fn calibrate(allocator: std.mem.Allocator, routine: CalibrationRoutine) !voi
} }
} }
pub fn create(allocator: std.mem.Allocator) !*YaesuController { pub fn init(allocator: std.mem.Allocator) !YaesuController {
if (singleton) |_| { const lock = try allocator.create(std.Thread.Mutex);
log.err("Controller singleton already exists.", .{}); errdefer allocator.destroy(lock);
return error.AlreadyInitialized; lock.* = .{};
}
const controller = try allocator.create(Controller); const controller = try allocator.create(Controller);
errdefer allocator.destroy(controller); errdefer allocator.destroy(controller);
controller.* = try Controller.init(allocator); controller.init(lock);
errdefer controller.deinit(allocator);
// do this in the main thread so we can throw the error about it synchronously. // do this in the main thread so we can throw the error about it synchronously.
try controller.connectLabjack(); try controller.connectLabjack();
const self = try allocator.create(YaesuController); return .{
errdefer allocator.destroy(self);
self.* = .{
.control_thread = try std.Thread.spawn(.{}, runController, .{controller}), .control_thread = try std.Thread.spawn(.{}, runController, .{controller}),
.lock = &controller.lock, .lock = lock,
.controller = controller, .controller = controller,
}; };
singleton = self;
return self;
} }
fn inRange(request: f64, comptime dof: enum { azimuth, elevation }) bool { fn inRange(request: f64, comptime dof: enum { azimuth, elevation }) bool {
@@ -100,7 +91,7 @@ pub fn setTarget(self: YaesuController, target: AzEl) error{OutOfRange}!void {
const controller = @constCast(self.controller); const controller = @constCast(self.controller);
controller.target = masked_target; controller.target = masked_target;
controller.requestState(.running); controller.requested_state = .running;
} }
pub fn currentPosition(self: YaesuController) AzEl { pub fn currentPosition(self: YaesuController) AzEl {
@@ -110,15 +101,16 @@ pub fn currentPosition(self: YaesuController) AzEl {
return self.controller.position; return self.controller.position;
} }
pub fn waitForUpdate(self: YaesuController) AzEl { pub fn startCalibration(self: YaesuController) void {
const controller = @constCast(self.controller); // there are two different types of calibration:
// 1. feedback calibration, running to the extents of the rotator
self.lock.lock(); // 2. sun calibration, which determines the azimuth and elevation angle
defer self.lock.unlock(); // offset between the rotator's physical stops and geodetic north
//
controller.condition.wait(self.lock); // The former is (fairly) trivial to automate, just run until stall
// (assuming there's no deadband in the feedback). The latter requires
return controller.position; // manual input as the human is the feedback hardware in the loop.
_ = self;
} }
pub fn quit(self: YaesuController) void { pub fn quit(self: YaesuController) void {
@@ -126,7 +118,7 @@ pub fn quit(self: YaesuController) void {
defer self.lock.unlock(); defer self.lock.unlock();
const controller = @constCast(self.controller); const controller = @constCast(self.controller);
controller.requestState(.stopped); controller.requested_state = .stopped;
} }
pub fn stop(self: YaesuController) void { pub fn stop(self: YaesuController) void {
@@ -135,7 +127,7 @@ pub fn stop(self: YaesuController) void {
const controller = @constCast(self.controller); const controller = @constCast(self.controller);
controller.target = controller.position; controller.target = controller.position;
controller.requestState(.idle); controller.requested_state = .idle;
} }
pub fn startPark(self: YaesuController) void { pub fn startPark(self: YaesuController) void {
@@ -163,77 +155,16 @@ fn runController(controller: *Controller) void {
}; };
} }
const FeedbackBuffer = struct {
samples: []f64,
index: usize = 0,
fn initZero(allocator: std.mem.Allocator, samples: usize) !FeedbackBuffer {
const buf = try allocator.alloc(f64, samples * 2);
@memset(buf, 0);
return .{ .samples = buf };
}
fn deinit(self: FeedbackBuffer, allocator: std.mem.Allocator) void {
allocator.free(self.samples);
}
fn push(self: *FeedbackBuffer, sample: [2]lj.AnalogReadResult) void {
const halfpoint = @divExact(self.samples.len, 2);
defer self.index = (self.index + 1) % halfpoint;
self.samples[self.index] = sample[0].voltage;
self.samples[self.index + halfpoint] = sample[1].voltage;
}
inline fn mean(data: []f64) f64 {
var accum: f64 = 0;
for (data) |pt| {
accum += pt;
}
return accum / @as(f64, @floatFromInt(data.len));
}
fn lerp(input: f64, cal_points: Config.MinMax) f64 {
return (input - cal_points.minimum.voltage) * cal_points.slope() + cal_points.minimum.angle;
}
fn get(self: FeedbackBuffer) AzEl {
const halfpoint = @divExact(self.samples.len, 2);
return .{
.azimuth = lerp(
mean(self.samples[0..halfpoint]),
config.labjack.feedback_calibration.azimuth,
) + config.controller.angle_offset.azimuth,
.elevation = lerp(
mean(self.samples[halfpoint..]),
config.labjack.feedback_calibration.elevation,
) + config.controller.angle_offset.elevation,
};
}
fn getRaw(self: FeedbackBuffer) AzEl {
const halfpoint = @divExact(self.samples.len, 2);
return .{
.azimuth = mean(self.samples[0..halfpoint]),
.elevation = mean(self.samples[halfpoint..]),
};
}
};
const Controller = struct { const Controller = struct {
target: AzEl, target: AzEl,
position: AzEl, position: AzEl,
feedback_buffer: FeedbackBuffer,
current_state: ControllerState, current_state: ControllerState,
requested_state: ControllerState, requested_state: ControllerState,
lock: *std.Thread.Mutex,
labjack: lj.Labjack, labjack: lj.Labjack,
lock: std.Thread.Mutex = .{},
condition: std.Thread.Condition = .{},
const ControllerState = enum { const ControllerState = enum {
initializing, initializing,
idle, idle,
@@ -242,13 +173,13 @@ const Controller = struct {
stopped, stopped,
}; };
fn init(allocator: std.mem.Allocator) !Controller { fn init(self: *Controller, lock: *std.Thread.Mutex) void {
return .{ self.* = .{
.target = .{ .azimuth = 0, .elevation = 0 }, .target = .{ .azimuth = 0, .elevation = 0 },
.position = .{ .azimuth = 0, .elevation = 0 }, .position = .{ .azimuth = 0, .elevation = 0 },
.feedback_buffer = try FeedbackBuffer.initZero(allocator, config.controller.feedback_window_samples),
.current_state = .stopped, .current_state = .stopped,
.requested_state = .idle, .requested_state = .idle,
.lock = lock,
.labjack = switch (config.labjack.device) { .labjack = switch (config.labjack.device) {
.autodetect => lj.Labjack.autodetect(), .autodetect => lj.Labjack.autodetect(),
.serial_number => |sn| lj.Labjack.with_serial_number(sn), .serial_number => |sn| lj.Labjack.with_serial_number(sn),
@@ -256,28 +187,12 @@ const Controller = struct {
}; };
} }
fn deinit(self: Controller, allocator: std.mem.Allocator) void {
self.feedback_buffer.deinit(allocator);
}
fn connectLabjack(self: *Controller) !void { fn connectLabjack(self: *Controller) !void {
const info = try self.labjack.connect(); const info = try self.labjack.connect();
try self.labjack.setAllDigitalOutputLow(); try self.labjack.setAllDigitalOutputLow();
self.labjack.id = info.local_id; self.labjack.id = info.local_id;
} }
// this function is run with the lock already acquired
fn propagateState(self: *Controller) void {
if (self.current_state == .stopped) return;
self.current_state = self.requested_state;
}
// this function is run with the lock already acquired
fn requestState(self: *Controller, request: ControllerState) void {
if (self.current_state == .stopped) return;
self.requested_state = request;
}
fn lerpOne(input: f64, cal_points: Config.MinMax) f64 { fn lerpOne(input: f64, cal_points: Config.MinMax) f64 {
return (input - cal_points.minimum.voltage) * cal_points.slope() + cal_points.minimum.angle; return (input - cal_points.minimum.voltage) * cal_points.slope() + cal_points.minimum.angle;
} }
@@ -318,23 +233,26 @@ const Controller = struct {
.positive; .positive;
} }
fn updateFeedback(self: *Controller) !void { fn updateAzEl(self: *const Controller) !AzEl {
const inputs = .{ const inputs = .{ config.controller.azimuth_input, config.controller.elevation_input };
config.controller.azimuth_input,
config.controller.elevation_input,
};
const raw = try self.labjack.readAnalogWriteDigital( const raw = try self.labjack.readAnalogWriteDigital(
2, 2,
inputs, inputs,
null, .{false} ** 4,
true, true,
); );
self.feedback_buffer.push(raw); return lerpAndOffsetAngles(raw);
} }
fn drive(self: *const Controller, pos_error: AzEl) !void { fn drive(self: *const Controller, pos_error: AzEl) !AzEl {
// NOTE: feedback will be roughly config.controller.loop_interval_ns out of
// date. For high loop rates, this shouldn't be an issue.
const inputs = .{ config.controller.azimuth_input, config.controller.elevation_input };
var drive_signal: [4]bool = .{false} ** 4;
const azsign = signDeadzone( const azsign = signDeadzone(
pos_error.azimuth, pos_error.azimuth,
config.controller.angle_tolerance.azimuth, config.controller.angle_tolerance.azimuth,
@@ -345,111 +263,108 @@ const Controller = struct {
config.controller.angle_tolerance.elevation, config.controller.angle_tolerance.elevation,
); );
var drive_signal: [4]bool = .{false} ** 4;
drive_signal[config.controller.azimuth_outputs.increase.io] = azsign == .positive; drive_signal[config.controller.azimuth_outputs.increase.io] = azsign == .positive;
drive_signal[config.controller.azimuth_outputs.decrease.io] = azsign == .negative; drive_signal[config.controller.azimuth_outputs.decrease.io] = azsign == .negative;
drive_signal[config.controller.elevation_outputs.increase.io] = elsign == .positive; drive_signal[config.controller.elevation_outputs.increase.io] = elsign == .positive;
drive_signal[config.controller.elevation_outputs.decrease.io] = elsign == .negative; drive_signal[config.controller.elevation_outputs.decrease.io] = elsign == .negative;
const raw = self.feedback_buffer.getRaw(); const raw = try self.labjack.readAnalogWriteDigital(2, inputs, drive_signal, true);
const angles = lerpAndOffsetAngles(raw);
log.info( log.info(
// -180.1 is 6 chars. -5.20 is 5 chars // -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}", "az: {d: >6.1}° ({d: >5.2} V) Δ {d: >6.1}° => {u}, el: {d: >6.1}° ({d: >5.2} V) Δ {d: >6.1}° => {u}",
.{ .{
self.position.azimuth, angles.azimuth,
raw.azimuth, raw[0].voltage,
pos_error.azimuth, pos_error.azimuth,
azsign.symbol(), azsign.symbol(),
self.position.elevation, angles.elevation,
raw.elevation, raw[1].voltage,
pos_error.elevation, pos_error.elevation,
elsign.symbol(), elsign.symbol(),
}, },
); );
return angles;
try self.labjack.writeIoLines(drive_signal);
}
fn setPosition(self: *Controller, position: AzEl) void {
self.position = position;
self.condition.broadcast();
} }
fn run(self: *Controller) !void { fn run(self: *Controller) !void {
self.current_state = .initializing; self.current_state = .initializing;
var timer = LoopTimer.init(config.controller.loop_interval_ns); var timer: LoopTimer = .{ .interval_ns = config.controller.loop_interval_ns };
while (timer.mark()) : (timer.sleep()) { while (timer.mark()) : (timer.sleep()) switch (self.current_state) {
const fbfail = if (self.updateFeedback()) |_| false else |_| true; .initializing, .idle => {
const pos = self.updateAzEl() catch {
{
self.lock.lock();
defer self.lock.unlock();
self.setPosition(self.feedback_buffer.get());
if (fbfail) self.requestState(.stopped);
self.propagateState();
}
switch (self.current_state) {
.initializing, .idle => {},
.calibration => {
self.lock.lock(); self.lock.lock();
defer self.lock.unlock(); defer self.lock.unlock();
// run calibration routine. psych, this does nothing. gottem self.current_state = .stopped;
self.current_state = .idle; continue;
self.requestState(.idle); };
},
.running => {
const pos_error: AzEl = blk: {
self.lock.lock();
defer self.lock.unlock();
break :blk .{ self.lock.lock();
.azimuth = self.target.azimuth - self.position.azimuth, defer self.lock.unlock();
.elevation = self.target.elevation - self.position.elevation,
}; self.position = pos;
self.current_state = self.requested_state;
},
.calibration => {
self.lock.lock();
defer self.lock.unlock();
// run calibration routine. psych, this does nothing. gottem
self.current_state = .idle;
self.requested_state = self.current_state;
},
.running => {
const pos_error: AzEl = blk: {
self.lock.lock();
defer self.lock.unlock();
break :blk .{
.azimuth = self.target.azimuth - self.position.azimuth,
.elevation = self.target.elevation - self.position.elevation,
}; };
};
self.drive(pos_error) catch { const pos = self.drive(pos_error) catch {
self.lock.lock(); self.lock.lock();
defer self.lock.unlock(); defer self.lock.unlock();
self.current_state = .stopped; self.current_state = .stopped;
continue; continue;
}; };
},
.stopped => { self.lock.lock();
// attempt to reset the drive outputs defer self.lock.unlock();
try self.labjack.writeIoLines(.{false} ** 4);
break; self.position = pos;
}, self.current_state = self.requested_state;
} },
} .stopped => {
// attempt to reset the drive outputs
_ = self.updateAzEl() catch {};
break;
},
};
} }
}; };
pub const LoopTimer = struct { pub const LoopTimer = struct {
interval_ns: u64, interval_ns: u64,
timer: std.time.Timer,
pub fn init(interval_ns: u64) LoopTimer { start: i128 = 0,
return .{
.interval_ns = interval_ns,
.timer = std.time.Timer.start() catch @panic("Could not create timer"),
};
}
pub fn mark(self: *LoopTimer) bool { pub fn mark(self: *LoopTimer) bool {
self.timer.reset(); self.start = std.time.nanoTimestamp();
return true; return true;
} }
pub fn sleep(self: *LoopTimer) void { pub fn sleep(self: *LoopTimer) void {
const elapsed = self.timer.read(); const now = std.time.nanoTimestamp();
std.time.sleep(self.interval_ns -| elapsed); const elapsed: u64 = @intCast(now - self.start);
std.time.sleep(self.interval_ns - elapsed);
} }
}; };

View File

@@ -68,30 +68,6 @@ pub const Labjack = struct {
return status.toError(); return status.toError();
} }
pub fn writeIoLines(self: Labjack, out: [4]bool) LabjackError!void {
var id = self.cId();
var d_modes: c_long = 0xFF_FF;
var d_outputs: c_long = 0;
var d_states: c_long = 0;
const io_modes: c_long = 0b1111;
var io_outputs: c_long = PackedOutput.fromBoolArray(out).toCLong();
const status = c_api.DigitalIO(
&id,
self.demo(),
&d_modes,
io_modes,
&d_outputs,
&io_outputs,
1, // actually update the pin modes
&d_states,
);
if (!status.okay())
return status.toError();
}
/// Read one analog input channel, either single-ended or differential /// Read one analog input channel, either single-ended or differential
pub fn analogReadOne(self: Labjack, input: AnalogInput) LabjackError!AnalogReadResult { pub fn analogReadOne(self: Labjack, input: AnalogInput) LabjackError!AnalogReadResult {
if (!input.channel.isDifferential() and input.gain_index != 0) { if (!input.channel.isDifferential() and input.gain_index != 0) {

View File

@@ -1,5 +1,4 @@
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin");
const Config = @import("./Config.zig"); const Config = @import("./Config.zig");
const lj = @import("./labjack.zig"); const lj = @import("./labjack.zig");
@@ -10,62 +9,6 @@ const udev = @import("udev_rules");
const log = std.log.scoped(.main); const log = std.log.scoped(.main);
fn quit() noreturn {
if (YaesuController.singleton) |controller| {
controller.quit();
controller.control_thread.join();
}
std.process.exit(1);
}
const moreposix = struct {
pub extern "c" fn sigaddset(set: *std.posix.sigset_t, signo: c_int) c_int;
pub extern "c" fn sigdelset(set: *std.posix.sigset_t, signo: c_int) c_int;
pub extern "c" fn sigemptyset(set: *std.posix.sigset_t) c_int;
pub extern "c" fn sigfillset(set: *std.posix.sigset_t) c_int;
pub extern "c" fn sigismember(set: *const std.posix.sigset_t, signo: c_int) c_int;
// stdlib prototype is wrong, it doesn't take optional pointers.
pub extern "c" fn pthread_sigmask(how: c_int, noalias set: ?*const std.posix.sigset_t, noalias oldset: ?*std.posix.sigset_t) c_int;
};
const psigs = [_]c_int{ std.posix.SIG.INT, std.posix.SIG.HUP, std.posix.SIG.QUIT };
fn posixSignalHandlerThread() void {
var set: std.posix.sigset_t = undefined;
_ = moreposix.sigemptyset(&set);
for (psigs) |sig|
_ = moreposix.sigaddset(&set, sig);
var sig: c_int = 0;
_ = std.posix.system.sigwait(&set, &sig);
log.info("Got exit signal", .{});
quit();
}
// Windows runs this handler in a thread, so calling quit directly should be safe.
fn windowsEventHandler(code: std.os.windows.DWORD) callconv(std.os.windows.WINAPI) std.os.windows.BOOL {
_ = code;
log.info("Got exit signal", .{});
quit();
}
fn addExitHandler() !void {
if (comptime builtin.os.tag == .windows) {
try std.os.windows.SetConsoleCtrlHandler(windowsEventHandler, true);
} else if (comptime std.Thread.use_pthreads) {
var set: std.posix.sigset_t = undefined;
_ = moreposix.sigemptyset(&set);
for (psigs) |sig|
_ = moreposix.sigaddset(&set, sig);
_ = moreposix.pthread_sigmask(std.posix.SIG.BLOCK, &set, null);
// nobody cares about the thread
_ = try std.Thread.spawn(.{}, posixSignalHandlerThread, .{});
} else {
log.err("not windows and not pthreads = disaster", .{});
}
}
fn printStderr(comptime fmt: []const u8, args: anytype) void { fn printStderr(comptime fmt: []const u8, args: anytype) void {
std.debug.print(fmt ++ "\n", args); std.debug.print(fmt ++ "\n", args);
} }
@@ -122,14 +65,9 @@ pub fn main() !u8 {
defer Config.deinit(); defer Config.deinit();
addExitHandler() catch {
log.err("Could not install quit handler.", .{});
return 1;
};
RotCtl.run(allocator) catch |err| { RotCtl.run(allocator) catch |err| {
log.err("rotator controller ceased unexpectedly! {s}", .{@errorName(err)}); log.err("rotator controller ceased unexpectedly! {s}", .{@errorName(err)});
quit(); return 1;
}; };
} else if (std.mem.eql(u8, args[1], commands.calibrate)) { } else if (std.mem.eql(u8, args[1], commands.calibrate)) {
if (args.len < 3 or args.len > 4) { if (args.len < 3 or args.len > 4) {
@@ -147,14 +85,9 @@ pub fn main() !u8 {
return 1; return 1;
}; };
addExitHandler() catch {
log.err("Could not install quit handler.", .{});
return 1;
};
YaesuController.calibrate(allocator, routine) catch |err| { YaesuController.calibrate(allocator, routine) catch |err| {
log.err("Calibration failed: {s}", .{@errorName(err)}); log.err("Calibration failed: {s}", .{@errorName(err)});
quit(); return 1;
}; };
} else if (std.mem.eql(u8, args[1], commands.help)) { } else if (std.mem.eql(u8, args[1], commands.help)) {
if (args.len != 3) { if (args.len != 3) {