Compare commits

..

3 Commits

Author SHA1 Message Date
e639a17424
start writing control and config functionality
In theory, this will poll the feedback lines, but in practice, it
probably crashes or catches on fire or something.
2024-07-03 00:22:40 -07:00
10c40d7d50
deps.labjack: fiddle with Windows support a little
I will probably spend more time on this than I plan to, though I am not
really writing this to run on Windows. At this point, Windows
compilation works but when the driver attempts to read the HID
descriptor, the response is only 45 bytes instead of the expected 75
(which is what it gets when run on Linux). I was under the impression
that this response was just raw data from the device itself, but it's
clearly handled differently on the different platforms, so I think it
will be somewhat interesting to dig into what is different between the
two and why. It's possible I may actually learn something along the
way, unfortunately.
2024-07-03 00:22:02 -07:00
c32390f7c1
deps.labjack: support building for windows
The only pthread functionality this seems to use is mutexes, which are
(fortunately) theoretically trivial to wrap for windows. This
compiles, though it is not clear if it actually works correctly.
2024-07-02 21:30:31 -07:00
9 changed files with 155 additions and 165 deletions

View File

@ -17,6 +17,10 @@ pub fn build(b: *std.Build) !void {
.link_libc = true, .link_libc = true,
}); });
if (optimize == .Debug) {
liblabjackusb.defineCMacro("LJ_DEBUG", "1");
}
liblabjackusb.addCSourceFile(.{ .file = b.path("liblabjackusb/labjackusb.c") }); liblabjackusb.addCSourceFile(.{ .file = b.path("liblabjackusb/labjackusb.c") });
liblabjackusb.installHeader(b.path("liblabjackusb/labjackusb.h"), "labjackusb.h"); liblabjackusb.installHeader(b.path("liblabjackusb/labjackusb.h"), "labjackusb.h");

View File

@ -505,6 +505,7 @@ static HANDLE LJUSB_OpenSpecificDevice(libusb_device *dev, const struct libusb_d
return NULL; return NULL;
} }
#if defined(_WIN32)
// Test if the kernel driver has the U12. // Test if the kernel driver has the U12.
if (desc->idProduct == U12_PRODUCT_ID && libusb_kernel_driver_active(devh, 0)) { if (desc->idProduct == U12_PRODUCT_ID && libusb_kernel_driver_active(devh, 0)) {
#if LJ_DEBUG #if LJ_DEBUG
@ -521,6 +522,7 @@ static HANDLE LJUSB_OpenSpecificDevice(libusb_device *dev, const struct libusb_d
return NULL; return NULL;
} }
} }
#endif // _WIN32
r = libusb_claim_interface(devh, 0); r = libusb_claim_interface(devh, 0);
if (r < 0) { if (r < 0) {

View File

@ -17,6 +17,10 @@ pub fn build(b: *std.Build) !void {
.link_libc = true, .link_libc = true,
}); });
if (optimize == .Debug) {
libljacklm.defineCMacro("LJ_DEBUG", "1");
}
if (target.result.os.tag == .windows) { if (target.result.os.tag == .windows) {
libljacklm.defineCMacro("LJACKLM_USE_WINDOWS_MUTEX_SHIM", "1"); libljacklm.defineCMacro("LJACKLM_USE_WINDOWS_MUTEX_SHIM", "1");
} }

View File

@ -7220,6 +7220,17 @@ long GetU12Information( HANDLE hDevice,
temp = (unsigned long)LJUSB_GetDeviceDescriptorReleaseNumber(hDevice) * 65536; //upper two bytes of serial # temp = (unsigned long)LJUSB_GetDeviceDescriptorReleaseNumber(hDevice) * 65536; //upper two bytes of serial #
result = LJUSB_GetHIDReportDescriptor(hDevice, repDesc, 75); result = LJUSB_GetHIDReportDescriptor(hDevice, repDesc, 75);
#if defined(LJ_DEBUG)
fprintf(stderr, "U12 HID ReportDescriptor (hex, %lu B): ", result);
for (int idx = 0; idx < result; idx++) {
fprintf(stderr, "%02hhX", repDesc[idx]);
}
fprintf(stderr, "\n");
#endif // LJ_DEBUG
if(result < 75) if(result < 75)
{ {
//Failed getting descriptor. First capability would of been input, so //Failed getting descriptor. First capability would of been input, so

View File

@ -18,7 +18,7 @@ static inline int pthread_mutex_unlock(pthread_mutex_t *mutex) {
} }
static inline int pthread_mutex_trylock(pthread_mutex_t *mutex) { static inline int pthread_mutex_trylock(pthread_mutex_t *mutex) {
return TryEnterCriticalSection(mutex) != 0; return TryEnterCriticalSection(mutex) == 0;
} }
static inline void pthread_mutex_destroy(pthread_mutex_t *mutex) { static inline void pthread_mutex_destroy(pthread_mutex_t *mutex) {

View File

@ -6,15 +6,34 @@ const lj = @import("./labjack.zig");
const Config = @This(); const Config = @This();
var global_internal: Config = undefined; var global_internal: Config = undefined;
pub const global: *const Config; pub const global: *const Config = &global_internal;
pub fn load(allocator: std.mem.Allocator, reader: anytype) !void { pub fn load(allocator: std.mem.Allocator, reader: anytype) !void {
var jread = std.json.Reader(1024, @TypeOf(reader)).init(allocator, reader); var jread = std.json.Reader(1024, @TypeOf(reader)).init(allocator, reader);
defer jread.deinit(); defer jread.deinit();
global_internal = try std.json.parseFromTokenSourceLeaky(allocator, &jread, .{}); 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 = 5432,
},
labjack: LabjackConfig = .{ labjack: LabjackConfig = .{
.device = .autodetect, .device = .autodetect,
.feedback_calibration = .{ .feedback_calibration = .{
@ -56,6 +75,11 @@ pub const MinMax = struct {
} }
}; };
const RotControlConfig = struct {
listen_address: []const u8,
listen_port: u16,
};
const LabjackConfig = struct { const LabjackConfig = struct {
device: union(enum) { device: union(enum) {
autodetect, autodetect,

View File

@ -4,6 +4,8 @@ const lj = @import("./labjack.zig");
const Config = @import("./Config.zig"); const Config = @import("./Config.zig");
const config = Config.global; const config = Config.global;
const log = std.log.scoped(.labjack_yaesu);
const LabjackYaesu = @This(); const LabjackYaesu = @This();
control_thread: std.Thread, control_thread: std.Thread,
@ -51,12 +53,14 @@ pub fn startCalibration(self: LabjackYaesu) void {
// The former is (fairly) trivial to automate, just run until stall // The former is (fairly) trivial to automate, just run until stall
// (assuming there's no deadband in the feedback). The latter requires // (assuming there's no deadband in the feedback). The latter requires
// manual input as the human is the feedback hardware in the loop. // manual input as the human is the feedback hardware in the loop.
_ = self;
} }
pub fn idle(self: LabjackYaesu) void { pub fn idle(self: LabjackYaesu) void {
self.lock.lock(); self.lock.lock();
defer self.lock.unlock(); defer self.lock.unlock();
const controller = @constCast(self.controller);
controller.requested_state = .idle; controller.requested_state = .idle;
} }
@ -64,11 +68,17 @@ pub fn stop(self: LabjackYaesu) void {
self.lock.lock(); self.lock.lock();
defer self.lock.unlock(); defer self.lock.unlock();
const controller = @constCast(self.controller);
controller.requested_state = .stopped; controller.requested_state = .stopped;
} }
fn runController(controller: *Controller) void { fn runController(controller: *Controller) void {
controller.run() catch {}; controller.run() catch {
log.err(
"the labjack control loop has terminated unexpectedly!!!!",
.{},
);
};
} }
const Controller = struct { const Controller = struct {
@ -93,19 +103,19 @@ const Controller = struct {
self.* = .{ self.* = .{
.target = .{ .azimuth = 0, .elevation = 0 }, .target = .{ .azimuth = 0, .elevation = 0 },
.position = .{ .azimuth = 0, .elevation = 0 }, .position = .{ .azimuth = 0, .elevation = 0 },
.state = .stopped, .current_state = .stopped,
.requested_state = .idle, .requested_state = .idle,
.lock = lock, .lock = lock,
.labjack = switch (config.labjack.device) { .labjack = switch (config.labjack.device) {
.autodetect => try labjack.Labjack.autodetect(), .autodetect => lj.Labjack.autodetect(),
.serial_number => |sn| labjack.Labjack.with_serial_number(sn), .serial_number => |sn| lj.Labjack.with_serial_number(sn),
}, },
}; };
} }
fn connectLabjack(self: *Controller) !void { fn connectLabjack(self: *Controller) !void {
const info = try controller.labjack.connect(); const info = try self.labjack.connect();
controller.labjack.id = info.local_id; self.labjack.id = info.local_id;
} }
fn lerpOne(input: f64, cal_points: Config.MinMax) f64 { fn lerpOne(input: f64, cal_points: Config.MinMax) f64 {
@ -158,10 +168,10 @@ const Controller = struct {
config.controller.angle_tolerance.elevation, config.controller.angle_tolerance.elevation,
); );
drive_signal[config.azimuth_outputs.increase.io] = azsign == .positive; drive_signal[config.controller.azimuth_outputs.increase.io] = azsign == .positive;
drive_signal[config.azimuth_outputs.decrease.io] = azsign == .negative; drive_signal[config.controller.azimuth_outputs.decrease.io] = azsign == .negative;
drive_signal[config.elevation_outputs.increase.io] = elsign == .positive; drive_signal[config.controller.elevation_outputs.increase.io] = elsign == .positive;
drive_signal[config.elevation_outputs.decrease.io] = elsign == .negative; drive_signal[config.controller.elevation_outputs.decrease.io] = elsign == .negative;
const raw = try self.labjack.readAnalogWriteDigital(2, inputs, drive_signal, true); const raw = try self.labjack.readAnalogWriteDigital(2, inputs, drive_signal, true);
@ -169,17 +179,17 @@ const Controller = struct {
} }
fn run(self: *Controller) !void { fn run(self: *Controller) !void {
self.state = .initializing; self.current_state = .initializing;
var timer: LoopTimer = .{ .interval_ns = config.controller.loop_interval_ns }; var timer: LoopTimer = .{ .interval_ns = config.controller.loop_interval_ns };
while (timer.mark()) : (timer.sleep()) switch (self.state) { while (timer.mark()) : (timer.sleep()) switch (self.current_state) {
.initializing, .idle => { .initializing, .idle => {
const pos = self.updateAzEl() catch { const pos = self.updateAzEl() catch {
self.lock.lock(); self.lock.lock();
defer self.lock.unlock(); defer self.lock.unlock();
self.state = .stopped; self.current_state = .stopped;
continue; continue;
}; };
@ -187,15 +197,15 @@ const Controller = struct {
defer self.lock.unlock(); defer self.lock.unlock();
self.position = pos; self.position = pos;
self.state = self.requested_state; self.current_state = self.requested_state;
}, },
.calibration => { .calibration => {
self.lock.lock(); self.lock.lock();
defer self.lock.unlock(); defer self.lock.unlock();
// run calibration routine. psych, this does nothing. gottem // run calibration routine. psych, this does nothing. gottem
self.state = .idle; self.current_state = .idle;
self.requested_state = self.state; self.requested_state = self.current_state;
}, },
.running => { .running => {
const pos_error: AzEl = blk: { const pos_error: AzEl = blk: {
@ -212,7 +222,7 @@ const Controller = struct {
self.lock.lock(); self.lock.lock();
defer self.lock.unlock(); defer self.lock.unlock();
self.state = .stopped; self.current_state = .stopped;
continue; continue;
}; };
@ -220,7 +230,7 @@ const Controller = struct {
defer self.lock.unlock(); defer self.lock.unlock();
self.position = pos; self.position = pos;
self.state = self.requested_state; self.current_state = self.requested_state;
}, },
.stopped => { .stopped => {
// attempt to reset the drive outputs // attempt to reset the drive outputs
@ -245,6 +255,6 @@ pub const LoopTimer = struct {
const now = std.time.nanoTimestamp(); const now = std.time.nanoTimestamp();
const elapsed: u64 = @intCast(now - self.start); const elapsed: u64 = @intCast(now - self.start);
std.time.sleep(interval_ns - elapsed); std.time.sleep(self.interval_ns - elapsed);
} }
}; };

View File

@ -1,5 +1,6 @@
const std = @import("std"); const std = @import("std");
const config = @import("./Config.zig").global;
const LabjackYaesu = @import("./LabjackYaesu.zig"); const LabjackYaesu = @import("./LabjackYaesu.zig");
const RotCtl = @This(); const RotCtl = @This();
@ -10,15 +11,16 @@ writer: std.io.BufferedWriter(512, std.net.Stream.Writer),
running: bool, running: bool,
rotator: LabjackYaesu, rotator: LabjackYaesu,
pub fn run() !void { pub fn run(allocator: std.mem.Allocator) !void {
var server = std.net.StreamServer.init(.{ .reuse_address = true }); // var server = std.net.StreamServer.init(.{ .reuse_address = true });
defer server.deinit(); // defer server.deinit();
const listen_addr = try std.net.Address.parseIp( const listen_addr = try std.net.Address.parseIp(
config.gpredict_listen_address, config.rotctl.listen_address,
config.gpredict_listen_port, config.rotctl.listen_port,
); );
server.listen(listen_addr) catch {
var server = listen_addr.listen(.{ .reuse_address = true }) catch {
log.err("Could not listen on {}. Is it already in use?", .{listen_addr}); log.err("Could not listen on {}. Is it already in use?", .{listen_addr});
return; return;
}; };
@ -27,7 +29,7 @@ pub fn run() !void {
var interface: RotCtl = .{ var interface: RotCtl = .{
.writer = undefined, .writer = undefined,
.running = true, .running = true,
.rotator = LabjackYaesu.init(), .rotator = try LabjackYaesu.init(allocator),
}; };
while (true) { while (true) {
@ -49,21 +51,22 @@ pub fn run() !void {
while (interface.running) : (fbs.reset()) { while (interface.running) : (fbs.reset()) {
reader.streamUntilDelimiter(fbs.writer(), '\n', readbuffer.len) catch break; reader.streamUntilDelimiter(fbs.writer(), '\n', readbuffer.len) catch break;
try radio.handleHamlibCommand(fbs.getWritten()); try interface.handleHamlibCommand(fbs.getWritten());
} }
} }
} }
fn write(self: *const Hamlibber, buf: []const u8) !void { fn write(self: *RotCtl, buf: []const u8) !void {
try self.writer.writeAll(buf); try self.writer.writer().writeAll(buf);
try self.writer.flush();
} }
fn replyStatus(self: *RadioProxy, comptime status: HamlibErrorCode) !void { fn replyStatus(self: *RotCtl, comptime status: HamlibErrorCode) !void {
try self.write(comptime status.replyFrame() ++ "\n"); try self.write(comptime status.replyFrame() ++ "\n");
} }
fn handleHamlibCommand( fn handleHamlibCommand(
self: *const Hamlibber, self: *RotCtl,
command: []const u8, command: []const u8,
) !void { ) !void {
var tokens = std.mem.tokenizeScalar(u8, command, ' '); var tokens = std.mem.tokenizeScalar(u8, command, ' ');
@ -71,43 +74,13 @@ fn handleHamlibCommand(
const first = tokens.next().?; const first = tokens.next().?;
if (first.len == 1 or first[0] == '\\') { if (first.len == 1 or first[0] == '\\') {
switch (first[0]) { switch (first[0]) {
'F' => {
const freqt = tokens.next() orelse
return self.replyStatus(.invalid_parameter);
const new = std.fmt.parseInt(i32, freqt, 10) catch
return self.replyStatus(.invalid_parameter);
self.setFrequency(new) catch
return self.replyStatus(.io_error);
try self.replyStatus(.okay);
},
'f' => {
const freq = self.getFrequency() catch
return self.replyStatus(.io_error);
try self.print("{d}", .{freq});
},
't' => {
try self.print("{d}", .{@intFromBool(self.ptt_state)});
},
'q' => { 'q' => {
self.running = false; self.running = false;
self.replyStatus(.okay) catch return; self.replyStatus(.okay) catch return;
}, },
'\\' => return self.parseLongCommand(first[1..], &tokens), '\\' => {
// zig fmt: off return try self.parseLongCommand(first[1..], &tokens);
'*', '1', '2', '4', '_', 'A', 'a', 'B', 'b', 'C', 'c', 'D',
'd', 'E', 'e', 'G', 'g', 'H', 'h', 'I', 'i', 'J', 'j', 'L',
'l', 'M', 'm', 'N', 'n', 'O', 'o', 'P', 'p', 'R', 'r', 'S',
's', 'T', 'U', 'u', 'V', 'v', 'X', 'x', 'Y', 'y', 'Z', 'z',
0x87, 0x88, 0x89, 0x8A, 0x8B, 0x90, 0x91, 0x92, 0x93, 0xF3, 0xF5
=> |cmd| {
log.warn("Unsupported command {c}", .{cmd});
try self.replyStatus(.not_supported);
}, },
// zig fmt: on
else => |cmd| { else => |cmd| {
log.err("unknown command {}", .{cmd}); log.err("unknown command {}", .{cmd});
try self.replyStatus(.not_implemented); try self.replyStatus(.not_implemented);
@ -116,30 +89,25 @@ fn handleHamlibCommand(
} else if (std.mem.eql(u8, first, "AOS")) { } else if (std.mem.eql(u8, first, "AOS")) {
// gpredict just kind of shoves this message in on top of the HamLib // gpredict just kind of shoves this message in on top of the HamLib
// protocol. // protocol.
log.info("Received AOS message from gpredict", .{});
self.setFrequency(self.base_freq) catch return self.replyStatus(.io_error);
try self.replyStatus(.okay); try self.replyStatus(.okay);
} else if (std.mem.eql(u8, first, "LOS")) { } else if (std.mem.eql(u8, first, "LOS")) {
log.info("Received LOS message from gpredict", .{});
if (self.stop_on_los) {
self.running = false;
self.setFrequency(self.base_freq) catch return self.replyStatus(.io_error);
}
try self.replyStatus(.okay); try self.replyStatus(.okay);
} else try self.replyStatus(.not_supported); } else try self.replyStatus(.not_supported);
} }
fn parseLongCommand( fn parseLongCommand(
self: *RadioProxy, self: *RotCtl,
command: []const u8, command: []const u8,
tokens: *std.mem.TokenIterator(u8, .scalar), tokens: *std.mem.TokenIterator(u8, .scalar),
) !void { ) !void {
_ = tokens; _ = tokens;
for (hamlib_commands) |check| { for (rotctl_commands) |check| {
if (command.len >= check.long.len and std.mem.eql(u8, check.long, command[0..check.long.len])) { if (check.long) |long| {
log.warn("Unsupported command {s}", .{command}); if (command.len >= long.len and std.mem.eql(u8, long, command)) {
break; log.warn("Unsupported command {s}", .{command});
break;
}
} }
} else { } else {
log.warn("Unknown command {s}", .{command}); log.warn("Unknown command {s}", .{command});
@ -147,47 +115,6 @@ fn parseLongCommand(
return self.replyStatus(.not_supported); return self.replyStatus(.not_supported);
} }
fn resetWatchdog(self: *RadioProxy) !void {
const wd = spacecraft.shared.commands.watchdog;
const reqheader = csp.Header{
.priority = 1,
.source = Config.global.doppler_shift.gs_source_address,
.destination = Config.global.doppler_shift.gs_radio_address,
.destination_port = @intFromEnum(wd.Reset.port),
.source_port = self.reqPort(),
};
const request = wd.Reset{};
const packet = reqheader.packBig() ++ request.packLittle();
try self.nats.publish(self.nats_up, &packet);
while (true) {
const res = self.nats_down.nextMessage(reply_timeout) catch |err| {
log.err("Could not reset ground UHF radio watchdog. Is the radio on and bridged to NATS?", .{});
return err;
};
const data = res.getData() orelse continue;
if (data.len != csp.Header.pack_size + wd.Reset.Response.pack_size)
continue;
const resheader = csp.Header.unpackBig(data[0..csp.Header.pack_size].*);
if (!resheader.isReply(reqheader)) continue;
const reply = wd.Reset.Response.unpackLittle(
data[csp.Header.pack_size..][0..wd.Reset.Response.pack_size].*,
);
if (reply.err != .okay) {
log.err("Got error response for ground UHF watchdog reset: {}", .{reply.err.code()});
return error.Failure;
}
break;
}
log.info("Successfully reset the ground UHF radio watchdog timer.", .{});
}
const HamlibErrorCode = enum(u8) { const HamlibErrorCode = enum(u8) {
okay = 0, okay = 0,
invalid_parameter = 1, invalid_parameter = 1,
@ -222,38 +149,33 @@ const HamlibErrorCode = enum(u8) {
const HamlibCommand = struct { const HamlibCommand = struct {
short: ?u8 = null, short: ?u8 = null,
long: ?[]const u8 = null, long: ?[]const u8 = null,
mode: enum { rotator, radio, both },
}; };
const hamlib_commands = [_]HamlibCommand{ const rotctl_commands = [_]HamlibCommand{
.{ .short = 'F', .long = "set_freq", .mode = .radio }, .{ .short = 'q' }, // quit
.{ .short = 'f', .long = "get_freq", .mode = .radio }, .{ .short = 'Q' }, // quit
.{ .short = 'T', .long = "set_ptt", .mode = .radio }, .{ .short = 'P', .long = "set_pos" }, // azimuth: f64, elevation: f64
.{ .short = 't', .long = "get_ptt", .mode = .radio }, .{ .short = 'p', .long = "get_pos" }, // return az: f64, el: f64
.{ .short = 'q', .mode = .both }, // quit .{ .short = 'M', .long = "move" }, // direction: enum { up=2, down=4, left=8, right=16 }, speed: i8 (0-100 or -1)
.{ .short = 'Q', .mode = .both }, // quit .{ .short = 'S', .long = "stop" },
.{ .short = 'P', .long = "set_pos", .mode = .rotator }, // azimuth: f64, elevation: f64 .{ .short = 'K', .long = "park" },
.{ .short = 'p', .long = "get_pos", .mode = .rotator }, // return az: f64, el: f64 .{ .short = 'C', .long = "set_conf" }, // token: []const u8, value: []const u8
.{ .short = 'M', .long = "move", .mode = .rotator }, // direction: enum { up=2, down=4, left=8, right=16 }, speed: i8 (0-100 or -1) .{ .short = 'R', .long = "reset" }, // u1 (1 is reset all)
.{ .short = 'S', .long = "stop", .mode = .rotator }, .{ .short = '_', .long = "get_info" }, // return Model name
.{ .short = 'K', .long = "park", .mode = .rotator }, .{ .short = 'K', .long = "park" },
.{ .short = 'C', .long = "set_conf", .mode = .rotator }, // token: []const u8, value: []const u8 .{ .long = "dump_state" }, // ???
.{ .short = 'R', .long = "reset", .mode = .rotator }, // u1 (1 is reset all) .{ .short = '1', .long = "dump_caps" }, // ???
.{ .short = '_', .long = "get_info", .mode = .rotator }, // return Model name .{ .short = 'w', .long = "send_cmd" }, // []const u8, send serial command directly to the rotator
.{ .short = 'K', .long = "park", .mode = .rotator }, .{ .short = 'L', .long = "lonlat2loc" }, // return Maidenhead locator for given long: f64 and lat: f64, locator precision: u4 (2-12)
.{ .long = "dump_state", .mode = .rotator }, // ??? .{ .short = 'l', .long = "loc2lonlat" }, // the inverse of the above
.{ .short = '1', .long = "dump_caps", .mode = .rotator }, // ??? .{ .short = 'D', .long = "dms2dec" }, // deg, min, sec, 0 (positive) or 1 (negative)
.{ .short = 'w', .long = "send_cmd", .mode = .rotator }, // []const u8, send serial command directly to the rotator .{ .short = 'd', .long = "dec2dms" },
.{ .short = 'L', .long = "lonlat2loc", .mode = .rotator }, // return Maidenhead locator for given long: f64 and lat: f64, locator precision: u4 (2-12) .{ .short = 'E', .long = "dmmm2dec" },
.{ .short = 'l', .long = "loc2lonlat", .mode = .rotator }, // the inverse of the above .{ .short = 'e', .long = "dec2dmmm" },
.{ .short = 'D', .long = "dms2dec", .mode = .rotator }, // deg, min, sec, 0 (positive) or 1 (negative) .{ .short = 'B', .long = "grb" },
.{ .short = 'd', .long = "dec2dms", .mode = .rotator }, .{ .short = 'A', .long = "a_sp2a_lp" },
.{ .short = 'E', .long = "dmmm2dec", .mode = .rotator }, .{ .short = 'a', .long = "d_sp2d_lp" },
.{ .short = 'e', .long = "dec2dmmm", .mode = .rotator }, .{ .long = "pause" },
.{ .short = 'B', .long = "grb", .mode = .rotator },
.{ .short = 'A', .long = "a_sp2a_lp", .mode = .rotator },
.{ .short = 'a', .long = "d_sp2d_lp", .mode = .rotator },
.{ .long = "pause", .mode = .rotator },
}; };
// D, dms2dec 'Degrees' 'Minutes' 'Seconds' 'S/W' // D, dms2dec 'Degrees' 'Minutes' 'Seconds' 'S/W'

View File

@ -1,25 +1,38 @@
const std = @import("std"); const std = @import("std");
const Config = @import("./Config.zig");
const lj = @import("./labjack.zig"); const lj = @import("./labjack.zig");
const RotCtl = @import("./RotCtl.zig");
const log = std.log.scoped(.main);
pub fn main() !u8 {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
blk: {
const conf_file = std.fs.cwd().openFile("yaes.json", .{}) catch {
log.warn("Could not load config file yaes.json. Using default config.", .{});
Config.loadDefault(allocator);
break :blk;
};
defer conf_file.close();
Config.load(allocator, conf_file.reader()) catch {
log.err("Could not parse config file yaes.json. Good luck figuring out why.", .{});
return 1;
};
}
defer Config.destroy(allocator);
pub fn main() !void {
const ver = lj.getDriverVersion(); const ver = lj.getDriverVersion();
std.debug.print("Driver version: {d}\n", .{ver}); std.debug.print("Driver version: {d}\n", .{ver});
const labjack = lj.Labjack.autodetect(); RotCtl.run(allocator) catch |err| {
log.err("rotator controller ceased unexpectedly! {s}", .{@errorName(err)});
return 1;
};
const in = try labjack.analogReadOne(.{ .channel = .diff_01, .gain_index = 2 }); return 0;
std.debug.print("Read voltage: {d}. Overvolt: {}\n", .{ in.voltage, in.over_voltage });
try labjack.digitalWriteOne(.{ .channel = .{ .io = 0 }, .level = true });
const sample = try labjack.readAnalogWriteDigital(
2,
.{ .{ .channel = .diff_01, .gain_index = 2 }, .{ .channel = .diff_23, .gain_index = 2 } },
.{false} ** 4,
true,
);
for (sample, 0..) |input, idx| {
std.debug.print(" channel {d}: {d} V\n", .{ idx, input.voltage });
}
} }