Compare commits
3 Commits
master
...
38a286da11
Author | SHA1 | Date | |
---|---|---|---|
38a286da11
|
|||
6bd401d9c8
|
|||
0ccc65fd10
|
@@ -92,7 +92,7 @@ pub fn validate(self: Config, err_writer: anytype) !void {
|
||||
rotctl: RotControlConfig = .{
|
||||
.listen_address = "127.0.0.1",
|
||||
.listen_port = 4533,
|
||||
.autopark = false,
|
||||
.autopark = true,
|
||||
},
|
||||
labjack: LabjackConfig = .{
|
||||
.device = .autodetect,
|
||||
|
@@ -10,9 +10,12 @@ const log = std.log.scoped(.RotCtl);
|
||||
|
||||
writer: std.io.BufferedWriter(512, std.net.Stream.Writer),
|
||||
running: bool,
|
||||
rotator: *YaesuController,
|
||||
rotator: YaesuController,
|
||||
|
||||
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(
|
||||
config.rotctl.listen_address,
|
||||
config.rotctl.listen_port,
|
||||
@@ -27,15 +30,14 @@ pub fn run(allocator: std.mem.Allocator) !void {
|
||||
var interface: RotCtl = .{
|
||||
.writer = undefined,
|
||||
.running = true,
|
||||
.rotator = try YaesuController.create(allocator),
|
||||
.rotator = try YaesuController.init(allocator),
|
||||
};
|
||||
|
||||
while (interface.running) {
|
||||
const client = try server.accept();
|
||||
defer {
|
||||
log.info("disconnecting client", .{});
|
||||
if (!config.rotctl.autopark)
|
||||
interface.rotator.stop();
|
||||
interface.rotator.stop();
|
||||
client.stream.close();
|
||||
}
|
||||
|
||||
@@ -58,11 +60,10 @@ pub fn run(allocator: std.mem.Allocator) !void {
|
||||
}
|
||||
|
||||
// loop ended due to client disconnect
|
||||
if (interface.running and config.rotctl.autopark)
|
||||
if (interface.running and config.rotctl.autopark) {
|
||||
interface.rotator.startPark();
|
||||
}
|
||||
}
|
||||
|
||||
interface.rotator.control_thread.join();
|
||||
}
|
||||
|
||||
fn write(self: *RotCtl, buf: []const u8) !void {
|
||||
@@ -174,6 +175,7 @@ fn handleHamlibCommand(
|
||||
if (first.len == 1 or first[0] == '\\') {
|
||||
switch (first[0]) {
|
||||
// NOTE: this is not technically supported by rotctld.
|
||||
'q', 'Q' => try self.quit(first, &tokens),
|
||||
'S' => try self.stop(first, &tokens),
|
||||
'K' => try self.park(first, &tokens),
|
||||
'p' => try self.getPosition(first, &tokens),
|
||||
@@ -244,8 +246,8 @@ const HamlibCommand = struct {
|
||||
};
|
||||
|
||||
const rotctl_commands = [_]HamlibCommand{
|
||||
.{ .long = "quit", .callback = quit },
|
||||
.{ .long = "exit", .callback = quit },
|
||||
.{ .short = 'q', .callback = quit }, // quit
|
||||
.{ .short = 'Q', .callback = quit }, // quit
|
||||
.{ .long = "AOS", .callback = blindAck },
|
||||
.{ .long = "LOS", .callback = blindAck },
|
||||
.{ .short = 'P', .long = "set_pos", .callback = setPosition }, // azimuth: f64, elevation: f64
|
||||
|
@@ -8,8 +8,6 @@ const log = std.log.scoped(.yaesu_controller);
|
||||
|
||||
const YaesuController = @This();
|
||||
|
||||
pub var singleton: ?*YaesuController = null;
|
||||
|
||||
control_thread: std.Thread,
|
||||
lock: *std.Thread.Mutex,
|
||||
controller: *const Controller,
|
||||
@@ -25,7 +23,7 @@ pub const CalibrationRoutine = enum {
|
||||
};
|
||||
|
||||
pub fn calibrate(allocator: std.mem.Allocator, routine: CalibrationRoutine) !void {
|
||||
const controller = try YaesuController.create(allocator);
|
||||
const controller = try YaesuController.init(allocator);
|
||||
defer {
|
||||
controller.quit();
|
||||
controller.control_thread.join();
|
||||
@@ -37,12 +35,7 @@ pub fn calibrate(allocator: std.mem.Allocator, routine: CalibrationRoutine) !voi
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create(allocator: std.mem.Allocator) !*YaesuController {
|
||||
if (singleton) |_| {
|
||||
log.err("Controller singleton already exists.", .{});
|
||||
return error.AlreadyInitialized;
|
||||
}
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator) !YaesuController {
|
||||
const controller = try allocator.create(Controller);
|
||||
errdefer allocator.destroy(controller);
|
||||
controller.* = try Controller.init(allocator);
|
||||
@@ -50,16 +43,11 @@ pub fn create(allocator: std.mem.Allocator) !*YaesuController {
|
||||
// do this in the main thread so we can throw the error about it synchronously.
|
||||
try controller.connectLabjack();
|
||||
|
||||
const self = try allocator.create(YaesuController);
|
||||
errdefer allocator.destroy(self);
|
||||
self.* = .{
|
||||
return .{
|
||||
.control_thread = try std.Thread.spawn(.{}, runController, .{controller}),
|
||||
.lock = &controller.lock,
|
||||
.controller = controller,
|
||||
};
|
||||
|
||||
singleton = self;
|
||||
return self;
|
||||
}
|
||||
|
||||
fn inRange(request: f64, comptime dof: enum { azimuth, elevation }) bool {
|
||||
@@ -100,7 +88,7 @@ pub fn setTarget(self: YaesuController, target: AzEl) error{OutOfRange}!void {
|
||||
|
||||
const controller = @constCast(self.controller);
|
||||
controller.target = masked_target;
|
||||
controller.requestState(.running);
|
||||
controller.requested_state = .running;
|
||||
}
|
||||
|
||||
pub fn currentPosition(self: YaesuController) AzEl {
|
||||
@@ -126,7 +114,7 @@ pub fn quit(self: YaesuController) void {
|
||||
defer self.lock.unlock();
|
||||
|
||||
const controller = @constCast(self.controller);
|
||||
controller.requestState(.stopped);
|
||||
controller.requested_state = .stopped;
|
||||
}
|
||||
|
||||
pub fn stop(self: YaesuController) void {
|
||||
@@ -135,7 +123,7 @@ pub fn stop(self: YaesuController) void {
|
||||
|
||||
const controller = @constCast(self.controller);
|
||||
controller.target = controller.position;
|
||||
controller.requestState(.idle);
|
||||
controller.requested_state = .idle;
|
||||
}
|
||||
|
||||
pub fn startPark(self: YaesuController) void {
|
||||
@@ -266,18 +254,6 @@ const Controller = struct {
|
||||
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 {
|
||||
return (input - cal_points.minimum.voltage) * cal_points.slope() + cal_points.minimum.angle;
|
||||
}
|
||||
@@ -379,29 +355,33 @@ const Controller = struct {
|
||||
fn run(self: *Controller) !void {
|
||||
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()) {
|
||||
const fbfail = if (self.updateFeedback()) |_| false else |_| true;
|
||||
|
||||
{
|
||||
self.updateFeedback() catch {
|
||||
self.lock.lock();
|
||||
defer self.lock.unlock();
|
||||
|
||||
self.setPosition(self.feedback_buffer.get());
|
||||
if (fbfail) self.requestState(.stopped);
|
||||
self.propagateState();
|
||||
}
|
||||
self.current_state = .stopped;
|
||||
continue;
|
||||
};
|
||||
|
||||
self.lock.lock();
|
||||
defer self.lock.unlock();
|
||||
|
||||
self.setPosition(self.feedback_buffer.get());
|
||||
|
||||
switch (self.current_state) {
|
||||
.initializing, .idle => {},
|
||||
.initializing, .idle => {
|
||||
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.requestState(.idle);
|
||||
self.requested_state = self.current_state;
|
||||
},
|
||||
.running => {
|
||||
const pos_error: AzEl = blk: {
|
||||
@@ -434,22 +414,18 @@ const Controller = struct {
|
||||
|
||||
pub const LoopTimer = struct {
|
||||
interval_ns: u64,
|
||||
timer: std.time.Timer,
|
||||
|
||||
pub fn init(interval_ns: u64) LoopTimer {
|
||||
return .{
|
||||
.interval_ns = interval_ns,
|
||||
.timer = std.time.Timer.start() catch @panic("Could not create timer"),
|
||||
};
|
||||
}
|
||||
start: i128 = 0,
|
||||
|
||||
pub fn mark(self: *LoopTimer) bool {
|
||||
self.timer.reset();
|
||||
self.start = std.time.nanoTimestamp();
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn sleep(self: *LoopTimer) void {
|
||||
const elapsed = self.timer.read();
|
||||
std.time.sleep(self.interval_ns -| elapsed);
|
||||
const now = std.time.nanoTimestamp();
|
||||
const elapsed: u64 = @intCast(now - self.start);
|
||||
|
||||
std.time.sleep(self.interval_ns - elapsed);
|
||||
}
|
||||
};
|
||||
|
70
src/main.zig
70
src/main.zig
@@ -10,62 +10,6 @@ const udev = @import("udev_rules");
|
||||
|
||||
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 {
|
||||
std.debug.print(fmt ++ "\n", args);
|
||||
}
|
||||
@@ -122,14 +66,9 @@ pub fn main() !u8 {
|
||||
|
||||
defer Config.deinit();
|
||||
|
||||
addExitHandler() catch {
|
||||
log.err("Could not install quit handler.", .{});
|
||||
return 1;
|
||||
};
|
||||
|
||||
RotCtl.run(allocator) catch |err| {
|
||||
log.err("rotator controller ceased unexpectedly! {s}", .{@errorName(err)});
|
||||
quit();
|
||||
return 1;
|
||||
};
|
||||
} else if (std.mem.eql(u8, args[1], commands.calibrate)) {
|
||||
if (args.len < 3 or args.len > 4) {
|
||||
@@ -147,14 +86,9 @@ pub fn main() !u8 {
|
||||
return 1;
|
||||
};
|
||||
|
||||
addExitHandler() catch {
|
||||
log.err("Could not install quit handler.", .{});
|
||||
return 1;
|
||||
};
|
||||
|
||||
YaesuController.calibrate(allocator, routine) catch |err| {
|
||||
log.err("Calibration failed: {s}", .{@errorName(err)});
|
||||
quit();
|
||||
return 1;
|
||||
};
|
||||
} else if (std.mem.eql(u8, args[1], commands.help)) {
|
||||
if (args.len != 3) {
|
||||
|
Reference in New Issue
Block a user