Compare commits

..

No commits in common. "49fce9c584d0e57d45b8b83d2e5ee513110a17fb" and "7275d1c30e01d31e92d11cc343af8031ef50c48d" have entirely different histories.

8 changed files with 14 additions and 733 deletions

View File

@ -17,10 +17,6 @@ pub fn build(b: *std.Build) !void {
.link_libc = true,
});
if (target.result.os.tag == .windows) {
libljacklm.defineCMacro("LJACKLM_USE_WINDOWS_MUTEX_SHIM", "1");
}
libljacklm.addCSourceFile(.{ .file = b.path("libljacklm/ljacklm.c") });
libljacklm.installHeader(b.path("libljacklm/ljacklm.h"), "ljacklm.h");

View File

@ -23,14 +23,7 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#if defined(LJACKLM_USE_WINDOWS_MUTEX_SHIM)
#include <windows.h>
#include "windows_mutex_shim.h"
#else
#include <pthread.h>
#endif // LJACKLM_USE_WINDOWS_MUTEX_SHIM
#include <pthread.h>
#include "labjackusb.h"
@ -293,9 +286,7 @@ long GetU12Information( HANDLE hDevice,
long *fcddMaxSize,
long *hvcMaxSize);
#if !defined(LJACKLM_USE_WINDOWS_MUTEX_SHIM)
unsigned long GetTickCount( void);
#endif
__attribute__((constructor))
@ -7275,7 +7266,6 @@ long GetU12Information( HANDLE hDevice,
}
#if !defined(LJACKLM_USE_WINDOWS_MUTEX_SHIM)
//======================================================================
//GetTickCount: Implementation of GetTickCount() for Unix. Returns the
// current time, expressed as millisconds since the Epoch
@ -7286,4 +7276,3 @@ unsigned long GetTickCount( void)
gettimeofday(&tv, NULL);
return (tv.tv_sec * 1000) + (tv.tv_usec / 1000);
}
#endif

View File

@ -1,28 +0,0 @@
#ifndef LJACKLM_WIN_MUTEX
#define LJACKLM_WIN_MUTEX
typedef CRITICAL_SECTION pthread_mutex_t;
static inline int pthread_mutex_init(pthread_mutex_t *mutex, void *attr) {
InitializeCriticalSection(mutex);
return 0;
}
static inline void pthread_mutex_lock(pthread_mutex_t *mutex) {
EnterCriticalSection(mutex);
}
static inline int pthread_mutex_unlock(pthread_mutex_t *mutex) {
LeaveCriticalSection(mutex);
return 0;
}
static inline int pthread_mutex_trylock(pthread_mutex_t *mutex) {
return TryEnterCriticalSection(mutex) != 0;
}
static inline void pthread_mutex_destroy(pthread_mutex_t *mutex) {
DeleteCriticalSection(mutex);
}
#endif // LJACKLM_WIN_MUTEX

View File

@ -1,88 +0,0 @@
const std = @import("std");
const AzEl = @import("./LabjackYaesu.zig").AzEl;
const lj = @import("./labjack.zig");
const Config = @This();
var global_internal: Config = undefined;
pub const global: *const Config;
pub fn load(allocator: std.mem.Allocator, reader: anytype) !void {
var jread = std.json.Reader(1024, @TypeOf(reader)).init(allocator, reader);
defer jread.deinit();
global_internal = try std.json.parseFromTokenSourceLeaky(allocator, &jread, .{});
}
labjack: LabjackConfig = .{
.device = .autodetect,
.feedback_calibration = .{
.azimuth = .{
.minimum = .{ .voltage = 0.0, .angle = 0.0 },
.maximum = .{ .voltage = 5.0, .angle = 450.0 },
},
.elevation = .{
.minimum = .{ .voltage = 0.0, .angle = 0.0 },
.maximum = .{ .voltage = 5.0, .angle = 180.0 },
},
},
},
controller: ControllerConfig = .{
.azimuth_input = .{ .channel = .diff_01, .gain_index = 2 },
.elevation_input = .{ .channel = .diff_23, .gain_index = 2 },
.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 },
},
pub const VoltAngle = struct { voltage: f64, angle: f64 };
pub const MinMax = struct {
minimum: VoltAngle,
maximum: VoltAngle,
pub inline fn slope(self: MinMax) f64 {
return self.angleDiff() / self.voltDiff();
}
pub inline fn voltDiff(self: MinMax) f64 {
return self.maximum.voltage - self.minimum.voltage;
}
pub inline fn angleDiff(self: MinMax) f64 {
return self.maximum.angle - self.minimum.angle;
}
};
const LabjackConfig = struct {
device: union(enum) {
autodetect,
serial_number: i32,
},
// Very basic two-point calibration for each degree of freedom. All other angles are
// linearly interpolated from these two points. This assumes the feedback is linear,
// which seems to be a mostly reasonable assumption in practice.
feedback_calibration: struct {
azimuth: MinMax,
elevation: MinMax,
},
};
const ControllerConfig = struct {
azimuth_input: lj.AnalogInput,
elevation_input: lj.AnalogInput,
azimuth_outputs: OutPair,
elevation_outputs: OutPair,
loop_interval_ns: u64,
parking_posture: AzEl,
angle_tolerance: AzEl,
const OutPair = struct {
increase: lj.DigitalOutputChannel,
decrease: lj.DigitalOutputChannel,
};
};

View File

@ -1,250 +0,0 @@
const std = @import("std");
const lj = @import("./labjack.zig");
const Config = @import("./Config.zig");
const config = Config.global;
const LabjackYaesu = @This();
control_thread: std.Thread,
lock: *std.Thread.Mutex,
controller: *const Controller,
pub const AzEl = struct {
azimuth: f64,
elevation: f64,
};
pub fn init(allocator: std.mem.Allocator) !LabjackYaesu {
const lock = try allocator.create(std.Thread.Mutex);
errdefer allocator.destroy(lock);
lock.* = .{};
const controller = try allocator.create(Controller);
errdefer allocator.destroy(controller);
controller.init(lock);
// do this in the main thread so we can throw the error about it synchronously.
try controller.connectLabjack();
return .{
.control_thread = try std.Thread.spawn(.{}, runController, .{controller}),
.lock = lock,
.controller = controller,
};
}
pub fn setTarget(self: LabjackYaesu, target: AzEl) void {
self.lock.lock();
defer self.lock.unlock();
const controller = @constCast(self.controller);
controller.target = target;
controller.requested_state = .running;
}
pub fn startCalibration(self: LabjackYaesu) void {
// there are two different types of calibration:
// 1. feedback calibration, running to the extents of the rotator
// 2. sun calibration, which determines the azimuth and elevation angle
// offset between the rotator's physical stops and geodetic north
//
// The former is (fairly) trivial to automate, just run until stall
// (assuming there's no deadband in the feedback). The latter requires
// manual input as the human is the feedback hardware in the loop.
}
pub fn idle(self: LabjackYaesu) void {
self.lock.lock();
defer self.lock.unlock();
controller.requested_state = .idle;
}
pub fn stop(self: LabjackYaesu) void {
self.lock.lock();
defer self.lock.unlock();
controller.requested_state = .stopped;
}
fn runController(controller: *Controller) void {
controller.run() catch {};
}
const Controller = struct {
target: AzEl,
position: AzEl,
current_state: ControllerState,
requested_state: ControllerState,
lock: *std.Thread.Mutex,
labjack: lj.Labjack,
const ControllerState = enum {
initializing,
idle,
calibration,
running,
stopped,
};
fn init(self: *Controller, lock: *std.Thread.Mutex) void {
self.* = .{
.target = .{ .azimuth = 0, .elevation = 0 },
.position = .{ .azimuth = 0, .elevation = 0 },
.state = .stopped,
.requested_state = .idle,
.lock = lock,
.labjack = switch (config.labjack.device) {
.autodetect => try labjack.Labjack.autodetect(),
.serial_number => |sn| labjack.Labjack.with_serial_number(sn),
},
};
}
fn connectLabjack(self: *Controller) !void {
const info = try controller.labjack.connect();
controller.labjack.id = info.local_id;
}
fn lerpOne(input: f64, cal_points: Config.MinMax) f64 {
return (input - cal_points.minimum.voltage) * cal_points.slope() + cal_points.minimum.angle;
}
fn lerpAngles(input: [2]lj.AnalogReadResult) AzEl {
return .{
.azimuth = lerpOne(input[0].voltage, config.labjack.feedback_calibration.azimuth),
.elevation = lerpOne(input[1].voltage, config.labjack.feedback_calibration.elevation),
};
}
fn signDeadzone(offset: f64, deadzone: f64) enum { negative, zero, positive } {
return if (@abs(offset) < deadzone)
.zero
else if (offset < 0)
.negative
else
.positive;
}
fn updateAzEl(self: *const Controller) !AzEl {
const inputs = .{ config.controller.azimuth_input, config.controller.elevation_input };
const raw = try self.labjack.readAnalogWriteDigital(
2,
inputs,
.{false} ** 4,
true,
);
return lerpAngles(raw);
}
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(
pos_error.azimuth,
config.controller.angle_tolerance.azimuth,
);
const elsign = signDeadzone(
pos_error.elevation,
config.controller.angle_tolerance.elevation,
);
drive_signal[config.azimuth_outputs.increase.io] = azsign == .positive;
drive_signal[config.azimuth_outputs.decrease.io] = azsign == .negative;
drive_signal[config.elevation_outputs.increase.io] = elsign == .positive;
drive_signal[config.elevation_outputs.decrease.io] = elsign == .negative;
const raw = try self.labjack.readAnalogWriteDigital(2, inputs, drive_signal, true);
return lerpAngles(raw);
}
fn run(self: *Controller) !void {
self.state = .initializing;
var timer: LoopTimer = .{ .interval_ns = config.controller.loop_interval_ns };
while (timer.mark()) : (timer.sleep()) switch (self.state) {
.initializing, .idle => {
const pos = self.updateAzEl() catch {
self.lock.lock();
defer self.lock.unlock();
self.state = .stopped;
continue;
};
self.lock.lock();
defer self.lock.unlock();
self.position = pos;
self.state = self.requested_state;
},
.calibration => {
self.lock.lock();
defer self.lock.unlock();
// run calibration routine. psych, this does nothing. gottem
self.state = .idle;
self.requested_state = self.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,
};
};
const pos = self.drive(pos_error) catch {
self.lock.lock();
defer self.lock.unlock();
self.state = .stopped;
continue;
};
self.lock.lock();
defer self.lock.unlock();
self.position = pos;
self.state = self.requested_state;
},
.stopped => {
// attempt to reset the drive outputs
_ = self.updateAzEl() catch {};
break;
},
};
}
};
pub const LoopTimer = struct {
interval_ns: u64,
start: i128 = 0,
pub fn mark(self: *LoopTimer) bool {
self.start = std.time.nanoTimestamp();
return true;
}
pub fn sleep(self: *LoopTimer) void {
const now = std.time.nanoTimestamp();
const elapsed: u64 = @intCast(now - self.start);
std.time.sleep(interval_ns - elapsed);
}
};

View File

@ -1,288 +0,0 @@
const std = @import("std");
const LabjackYaesu = @import("./LabjackYaesu.zig");
const RotCtl = @This();
const log = std.log.scoped(.RotCtl);
writer: std.io.BufferedWriter(512, std.net.Stream.Writer),
running: bool,
rotator: LabjackYaesu,
pub fn run() !void {
var server = std.net.StreamServer.init(.{ .reuse_address = true });
defer server.deinit();
const listen_addr = try std.net.Address.parseIp(
config.gpredict_listen_address,
config.gpredict_listen_port,
);
server.listen(listen_addr) catch {
log.err("Could not listen on {}. Is it already in use?", .{listen_addr});
return;
};
log.info("Listening for client on: {}", .{listen_addr});
var interface: RotCtl = .{
.writer = undefined,
.running = true,
.rotator = LabjackYaesu.init(),
};
while (true) {
const client = try server.accept();
defer {
log.info("disconnecting client", .{});
client.stream.close();
}
interface.writer = .{ .unbuffered_writer = client.stream.writer() };
interface.running = true;
defer interface.running = false;
log.info("client connected from {}", .{client.address});
var readbuffer = [_]u8{0} ** 512;
var fbs = std.io.fixedBufferStream(&readbuffer);
const reader = client.stream.reader();
while (interface.running) : (fbs.reset()) {
reader.streamUntilDelimiter(fbs.writer(), '\n', readbuffer.len) catch break;
try radio.handleHamlibCommand(fbs.getWritten());
}
}
}
fn write(self: *const Hamlibber, buf: []const u8) !void {
try self.writer.writeAll(buf);
}
fn replyStatus(self: *RadioProxy, comptime status: HamlibErrorCode) !void {
try self.write(comptime status.replyFrame() ++ "\n");
}
fn handleHamlibCommand(
self: *const Hamlibber,
command: []const u8,
) !void {
var tokens = std.mem.tokenizeScalar(u8, command, ' ');
const first = tokens.next().?;
if (first.len == 1 or 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' => {
self.running = false;
self.replyStatus(.okay) catch return;
},
'\\' => return self.parseLongCommand(first[1..], &tokens),
// zig fmt: off
'*', '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| {
log.err("unknown command {}", .{cmd});
try self.replyStatus(.not_implemented);
},
}
} else if (std.mem.eql(u8, first, "AOS")) {
// gpredict just kind of shoves this message in on top of the HamLib
// protocol.
log.info("Received AOS message from gpredict", .{});
self.setFrequency(self.base_freq) catch return self.replyStatus(.io_error);
try self.replyStatus(.okay);
} 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);
} else try self.replyStatus(.not_supported);
}
fn parseLongCommand(
self: *RadioProxy,
command: []const u8,
tokens: *std.mem.TokenIterator(u8, .scalar),
) !void {
_ = tokens;
for (hamlib_commands) |check| {
if (command.len >= check.long.len and std.mem.eql(u8, check.long, command[0..check.long.len])) {
log.warn("Unsupported command {s}", .{command});
break;
}
} else {
log.warn("Unknown command {s}", .{command});
}
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) {
okay = 0,
invalid_parameter = 1,
invalid_configuration = 2,
out_of_memory = 3,
not_implemented = 4,
timeout = 5,
io_error = 6,
internal_error = 7,
protocol_error = 8,
command_rejected = 9,
parameter_truncated = 10,
not_supported = 11,
not_targetable = 12,
bus_error = 13,
bus_busy = 14,
invalid_arg = 15,
invalid_vfo = 16,
domain_error = 17,
deprecated = 18,
security = 19,
power = 20,
fn replyFrame(comptime self: HamlibErrorCode) []const u8 {
return std.fmt.comptimePrint(
"RPRT {d}",
.{-@as(i8, @intCast(@intFromEnum(self)))},
);
}
};
const HamlibCommand = struct {
short: ?u8 = null,
long: ?[]const u8 = null,
mode: enum { rotator, radio, both },
};
const hamlib_commands = [_]HamlibCommand{
.{ .short = 'F', .long = "set_freq", .mode = .radio },
.{ .short = 'f', .long = "get_freq", .mode = .radio },
.{ .short = 'T', .long = "set_ptt", .mode = .radio },
.{ .short = 't', .long = "get_ptt", .mode = .radio },
.{ .short = 'q', .mode = .both }, // quit
.{ .short = 'Q', .mode = .both }, // quit
.{ .short = 'P', .long = "set_pos", .mode = .rotator }, // azimuth: f64, elevation: f64
.{ .short = 'p', .long = "get_pos", .mode = .rotator }, // return az: f64, el: f64
.{ .short = 'M', .long = "move", .mode = .rotator }, // direction: enum { up=2, down=4, left=8, right=16 }, speed: i8 (0-100 or -1)
.{ .short = 'S', .long = "stop", .mode = .rotator },
.{ .short = 'K', .long = "park", .mode = .rotator },
.{ .short = 'C', .long = "set_conf", .mode = .rotator }, // token: []const u8, value: []const u8
.{ .short = 'R', .long = "reset", .mode = .rotator }, // u1 (1 is reset all)
.{ .short = '_', .long = "get_info", .mode = .rotator }, // return Model name
.{ .short = 'K', .long = "park", .mode = .rotator },
.{ .long = "dump_state", .mode = .rotator }, // ???
.{ .short = '1', .long = "dump_caps", .mode = .rotator }, // ???
.{ .short = 'w', .long = "send_cmd", .mode = .rotator }, // []const u8, send serial command directly to the rotator
.{ .short = 'L', .long = "lonlat2loc", .mode = .rotator }, // return Maidenhead locator for given long: f64 and lat: f64, locator precision: u4 (2-12)
.{ .short = 'l', .long = "loc2lonlat", .mode = .rotator }, // the inverse of the above
.{ .short = 'D', .long = "dms2dec", .mode = .rotator }, // deg, min, sec, 0 (positive) or 1 (negative)
.{ .short = 'd', .long = "dec2dms", .mode = .rotator },
.{ .short = 'E', .long = "dmmm2dec", .mode = .rotator },
.{ .short = 'e', .long = "dec2dmmm", .mode = .rotator },
.{ .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'
// Returns 'Dec Degrees', a signed floating point value.
// 'Degrees' and 'Minutes' are integer values.
// 'Seconds' is a floating point value.
// 'S/W' is a flag with 1 indicating South latitude or West longitude and 0 North or East (the flag is needed as computers dont recognize a signed zero even though only the 'Degrees' value is typically signed in DMS notation).
// d, dec2dms 'Dec Degrees'
// Returns 'Degrees' 'Minutes' 'Seconds' 'S/W'.
// Values are as in dms2dec above.
// E, dmmm2dec 'Degrees' 'Dec Minutes' 'S/W'
// Returns 'Dec Degrees', a signed floating point value.
// 'Degrees' is an integer value.
// 'Dec Minutes' is a floating point value.
// 'S/W' is a flag as in dms2dec above.
// e, dec2dmmm 'Dec Deg'
// Returns 'Degrees' 'Minutes' 'S/W'.
// Values are as in dmmm2dec above.
// B, qrb 'Lon 1' 'Lat 1' 'Lon 2' 'Lat 2'
// Returns 'Distance' and 'Azimuth'.
// 'Distance' is in km.
// 'Azimuth' is in degrees.
// Supplied Lon/Lat values are signed floating point numbers.
// A, a_sp2a_lp 'Short Path Deg'
// Returns 'Long Path Deg'.
// Both the supplied argument and returned value are floating point values within the range of 0.00 to 360.00.
// Note: Supplying a negative value will return an error message.
// a, d_sp2d_lp 'Short Path km'
// Returns 'Long Path km'.
// Both the supplied argument and returned value are floating point values.
// pause 'Seconds'
// Pause for the given whole (integer) number of 'Seconds' before sending the next command to the rotator.

View File

@ -5,27 +5,13 @@ pub fn getDriverVersion() f32 {
}
pub const Labjack = struct {
id: ?i32 = null,
id: ?u32 = null,
demo: bool = false,
pub fn autodetect() Labjack {
return .{};
}
pub fn with_serial_number(sn: i32) Labjack {
return .{ .id = sn };
}
pub fn connect(self: Labjack) LabjackError!struct { local_id: i32, firmware_version: f32 } {
var id = self.cId();
const version = c_api.GetFirmwareVersion(&id);
return if (version == 0)
@as(c_api.LabjackCError, @bitCast(id)).toError()
else
.{ .local_id = @intCast(id), .firmware_version = version };
}
pub fn firmwareVersion(self: Labjack) LabjackError!f32 {
var id = self.cId();
const version = c_api.GetFirmwareVersion(&id);
@ -38,7 +24,7 @@ pub const Labjack = struct {
/// Read one analog input channel, either single-ended or differential
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 != 0) {
return error.InvalidGain;
}
@ -50,7 +36,7 @@ pub const Labjack = struct {
&id,
@intFromBool(self.demo),
input.channelNumber(),
input.gain_index,
input.gain,
&over_v,
&res.voltage,
);
@ -93,7 +79,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.gain;
}
var v_out: [4]f32 = .{0} ** 4;
var over_v: c_long = 0;
@ -133,7 +119,7 @@ pub const Labjack = struct {
pub const AnalogInput = struct {
channel: AnalogInputChannel,
gain_index: GainIndex = 0,
gain: Gain = 0,
pub fn channelNumber(self: AnalogInput) u4 {
return @intFromEnum(self.channel);
@ -205,7 +191,7 @@ pub const DigitalOutputChannel = union(enum) {
// 5 => G=10 ±2 volts
// 6 => G=16 ±1.25 volts
// 7 => G=20 ±1 volt
pub const GainIndex = u3;
pub const Gain = u3;
pub const PackedOutput = packed struct(u4) {
io0: bool,
@ -213,18 +199,6 @@ pub const PackedOutput = packed struct(u4) {
io2: bool,
io3: bool,
pub fn setOut(self: *PackedOutput, output: DigitalOutput) !void {
switch (output) {
.io => |num| switch (num) {
0 => self.io0 = output.level,
1 => self.io1 = output.level,
2 => self.io2 = output.level,
3 => self.io3 = output.level,
},
.d => return error.Invalid,
}
}
pub fn fromBoolArray(states: [4]bool) PackedOutput {
return .{
.io0 = states[0],
@ -240,30 +214,6 @@ pub const PackedOutput = packed struct(u4) {
};
pub const c_api = struct {
pub const vendor_id: u16 = 0x0CD5;
pub const u12_product_id: u16 = 0x0001;
pub extern fn OpenLabJack(
errorcode: *LabjackCError,
vendor_id: c_uint,
product_id: c_uint,
idnum: *c_long,
serialnum: *c_long,
caldata: *[20]c_long,
) c_long;
pub extern fn CloseAll(local_id: c_long) c_long;
pub extern fn GetU12Information(
handle: *anyopaque,
serialnum: *c_long,
local_id: *c_long,
power: *c_long,
cal_data: *[20]c_long,
fcdd_max_size: *c_long,
hvc_max_size: *c_long,
) c_long;
pub extern fn EAnalogIn(
idnum: *c_long,
demo: c_long,

View File

@ -1,20 +1,20 @@
const std = @import("std");
const lj = @import("./labjack.zig");
const ljack = @import("./ljacklm.zig");
pub fn main() !void {
const ver = lj.getDriverVersion();
const ver = ljack.getDriverVersion();
std.debug.print("Driver version: {d}\n", .{ver});
const labjack = lj.Labjack.autodetect();
const device = ljack.Labjack.autodetect();
const in = try labjack.analogReadOne(.{ .channel = .diff_01, .gain_index = 2 });
const in = try device.analogReadOne(.{ .channel = .diff_01, .gain = 2 });
std.debug.print("Read voltage: {d}. Overvolt: {}\n", .{ in.voltage, in.over_voltage });
try labjack.digitalWriteOne(.{ .channel = .{ .io = 0 }, .level = true });
try device.digitalWriteOne(.{ .channel = .{ .io = 0 }, .level = true });
const sample = try labjack.readAnalogWriteDigital(
const sample = try device.readAnalogWriteDigital(
2,
.{ .{ .channel = .diff_01, .gain_index = 2 }, .{ .channel = .diff_23, .gain_index = 2 } },
.{ .{ .channel = .diff_01, .gain = 2 }, .{ .channel = .diff_23, .gain = 2 } },
.{false} ** 4,
true,
);