Files
yaes/src/labjack.zig
torque f1480bca45 build: link windows-native driver when targeting windows
This is completely experimental, but since the API is the same, it's a
drop-in replacement. The Windows driver is not apparently open source,
so I am vendoring the static libraries provided. In theory, this will
obviate the need to replace the HID driver on Windows and will thus
require no initial setup, but I cannot currently test it beyond making
sure it compiles.
2024-07-10 18:22:47 -07:00

881 lines
25 KiB
Zig

const std = @import("std");
const builtin = @import("builtin");
pub fn getDriverVersion() f32 {
return c_api.GetDriverVersion();
}
pub const Labjack = struct {
id: ?i32 = null,
demobit: 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);
return if (version == 0)
@as(c_api.LabjackCError, @bitCast(id)).toError()
else
version;
}
pub fn setAllDigitalOutputLow(self: Labjack) LabjackError!void {
var id = self.cId();
// bitmask. D0 to D15 (LSB is D0). 0 is input, 1 is output
var d_modes: c_long = 0xFF_FF;
// bitmask. D0 to D15 (LSB is D0). 0 is output low, 1 is output high
var d_outputs: c_long = 0;
// bitmask. D0 to D15 (LSB is D0). 0 is output low, 1 is output high
// the actual pin states read back from the device. an outvar from the API call.
var d_states: c_long = 0;
// bitmask. IO0 to IO3 (LSB is IO0). 0 is input, 1 is output
const io_modes: c_long = 0b1111;
// bitmask. IO0 to IO3 (LSB is IO0). 0 is output low, 1 is output high
var io_outputs: c_long = 0;
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
pub fn analogReadOne(self: Labjack, input: AnalogInput) LabjackError!AnalogReadResult {
if (!input.channel.isDifferential() and input.gain_index != 0) {
return error.InvalidGain;
}
var id = self.cId();
var over_v: c_long = 0;
var res: AnalogReadResult = undefined;
const status = c_api.EAnalogIn(
&id,
self.demo(),
input.channelNumber(),
input.gainIndex(),
&over_v,
&res.voltage,
);
if (status.okay()) {
res.over_voltage = over_v > 0;
return res;
} else {
return status.toError();
}
}
pub fn digitalWriteOne(self: Labjack, output: DigitalOutput) LabjackError!void {
var id = self.cId();
const status = c_api.EDigitalOut(
&id,
self.demo(),
output.channelNumber(),
@intFromBool(output.isDLine()),
@intFromBool(output.level),
);
if (!status.okay())
return status.toError();
}
pub fn readAnalogWriteDigital(
self: Labjack,
comptime incount: u3,
inputs: [incount]AnalogInput,
outputs: ?[4]bool,
ledOn: bool,
) LabjackError![incount]AnalogReadResult {
var id = self.cId();
var out_states: c_long = if (outputs) |out|
PackedOutput.fromBoolArray(out).toCLong()
else
0;
var in_channels: [incount]c_long = undefined;
var gains: [incount]c_long = undefined;
for (inputs, &in_channels, &gains) |from, *inc, *gain| {
inc.* = from.channelNumber();
gain.* = from.gainIndex();
}
var v_out: [4]f32 = .{0} ** 4;
var over_v: c_long = 0;
const status = c_api.AISample(
&id,
self.demo(),
&out_states,
@intFromBool(outputs != null),
@intFromBool(ledOn),
incount,
&in_channels,
&gains,
@intFromBool(false),
&over_v,
&v_out,
);
if (!status.okay())
return status.toError();
var result: [incount]AnalogReadResult = undefined;
for (v_out[0..incount], &result) |raw, *res| {
res.voltage = raw;
// there's no information about which channel had over voltage, so
// maybe we should return a different type
res.over_voltage = over_v > 0;
}
return result;
}
fn cId(self: Labjack) c_long {
return self.id orelse -1;
}
fn demo(self: Labjack) c_long {
return @intFromBool(self.demobit);
}
};
pub const AnalogInput = struct {
channel: AnalogInputChannel,
range: InputRange = .@"20 V",
pub fn channelNumber(self: AnalogInput) u4 {
return @intFromEnum(self.channel);
}
pub fn gainIndex(self: AnalogInput) GainIndex {
return @intFromEnum(self.range);
}
};
pub const AnalogReadResult = struct {
voltage: f32,
over_voltage: bool,
};
pub const DigitalOutput = struct {
channel: DigitalOutputChannel,
level: bool,
pub fn channelNumber(self: DigitalOutput) u4 {
return self.channel.channelNumber();
}
pub fn isDLine(self: DigitalOutput) bool {
return self.channel.isDLine();
}
};
pub const AnalogInputChannel = enum(u4) {
ai0 = 0,
ai1 = 1,
ai2 = 2,
ai3 = 3,
ai4 = 4,
ai5 = 5,
ai6 = 6,
ai7 = 7,
diff_01 = 8,
diff_23 = 9,
diff_45 = 10,
diff_67 = 11,
pub fn isDifferential(self: AnalogInputChannel) bool {
return switch (self) {
.diff_01, .diff_23, .diff_45, .diff_67 => true,
else => false,
};
}
};
pub const DigitalOutputChannel = union(enum) {
io: u2,
d: u4,
pub fn channelNumber(self: DigitalOutputChannel) u4 {
return switch (self) {
inline else => |val| val,
};
}
pub fn isDLine(self: DigitalOutputChannel) bool {
return self == .d;
}
};
// differential only
// 0 => G=1 ±20 volts
// 1 => G=2 ±10 volts
// 2 => G=4 ±5 volts
// 3 => G=5 ±4 volts
// 4 => G=8 ±2.5 volts
// 5 => G=10 ±2 volts
// 6 => G=16 ±1.25 volts
// 7 => G=20 ±1 volt
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) {
io0: bool,
io1: bool,
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],
.io1 = states[1],
.io2 = states[2],
.io3 = states[3],
};
}
pub fn toCLong(self: PackedOutput) c_long {
return @as(u4, @bitCast(self));
}
};
const Call: std.builtin.CallingConvention = if (builtin.os.tag == .windows and builtin.cpu.arch == .x86)
.Stdcall
else
.C;
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,
) callconv(Call) c_long;
pub extern fn CloseAll(local_id: c_long) callconv(Call) 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,
) callconv(Call) c_long;
pub extern fn EAnalogIn(
idnum: *c_long,
demo: c_long,
channel: c_long,
gain: c_long,
overVoltage: *c_long,
voltage: *f32,
) callconv(Call) LabjackCError;
pub extern fn EAnalogOut(
idnum: *c_long,
demo: c_long,
analogOut0: f32,
analogOut1: f32,
) callconv(Call) LabjackCError;
pub extern fn ECount(
idnum: *c_long,
demo: c_long,
resetCounter: c_long,
count: *f64,
ms: *f64,
) callconv(Call) LabjackCError;
pub extern fn EDigitalIn(
idnum: *c_long,
demo: c_long,
channel: c_long,
readD: c_long,
state: *c_long,
) callconv(Call) LabjackCError;
pub extern fn EDigitalOut(
idnum: *c_long,
demo: c_long,
channel: c_long,
writeD: c_long,
state: c_long,
) callconv(Call) LabjackCError;
pub extern fn AsynchConfig(
idnum: *c_long,
demo: c_long,
timeoutMult: c_long,
configA: c_long,
configB: c_long,
configTE: c_long,
fullA: c_long,
fullB: c_long,
fullC: c_long,
halfA: c_long,
halfB: c_long,
halfC: c_long,
) callconv(Call) LabjackCError;
pub extern fn Asynch(
idnum: *c_long,
demo: c_long,
portB: c_long,
enableTE: c_long,
enableTO: c_long,
enableDel: c_long,
baudrate: c_long,
numWrite: c_long,
numRead: c_long,
data: [*]c_long,
) callconv(Call) LabjackCError;
pub extern fn AISample(
idnum: *c_long,
demo: c_long,
stateIO: *c_long,
updateIO: c_long,
ledOn: c_long,
numChannels: c_long,
channels: [*]c_long, // numChannels length
gains: [*]c_long, // numChannels length
disableCal: c_long,
overVoltage: *c_long,
voltages: *[4]f32,
) callconv(Call) LabjackCError;
pub extern fn AIBurst(
idnum: *c_long,
demo: c_long,
stateIOin: c_long,
updateIO: c_long,
ledOn: c_long,
numChannels: c_long,
channels: [*]c_long, // numChannels length
gains: [*]c_long, // numChannels length
scanRate: *f32,
disableCal: c_long,
triggerIO: c_long, // 0=none, 1=IO0, or 2=IO1
triggerState: c_long,
numScans: c_long,
timeout: c_long,
voltages: *[4096][4]f32,
stateIOout: *[4096]c_long,
overVoltage: *c_long,
transferMode: c_long, // 0=auto,1=normal,2=turbo
) callconv(Call) LabjackCError;
pub extern fn AIStreamStart(
idnum: *c_long,
demo: c_long,
stateIOin: c_long,
updateIO: c_long,
ledOn: c_long,
numChannels: c_long,
channels: [*]c_long, // numChannels length
gains: [*]c_long, // numChannels length
scanRate: *f32,
disableCal: c_long,
reserved1: c_long, // always 0
readCount: c_long,
) callconv(Call) LabjackCError;
pub extern fn AIStreamRead(
localID: c_long,
numScans: c_long,
timeout: c_long,
voltages: *[4096][4]f32,
stateIOout: *[4096]c_long,
reserved: ?*c_long, // unused
ljScanBacklog: *c_long,
overVoltage: *c_long,
) callconv(Call) LabjackCError;
pub extern fn AIStreamClear(
localID: c_long,
) callconv(Call) LabjackCError;
pub extern fn AOUpdate(
idnum: *c_long,
demo: c_long,
trisD: c_long,
trisIO: c_long,
stateD: [*c]c_long,
stateIO: [*c]c_long,
updateDigital: c_long,
resetCounter: c_long,
count: [*c]c_ulong,
analogOut0: f32,
analogOut1: f32,
) callconv(Call) LabjackCError;
pub extern fn BitsToVolts(
chnum: c_long,
chgain: c_long,
bits: c_long,
volts: [*c]f32,
) callconv(Call) LabjackCError;
pub extern fn VoltsToBits(
chnum: c_long,
chgain: c_long,
volts: f32,
bits: [*c]c_long,
) callconv(Call) LabjackCError;
pub extern fn Counter(
idnum: *c_long,
demo: c_long,
stateD: [*c]c_long,
stateIO: [*c]c_long,
resetCounter: c_long,
enableSTB: c_long,
count: [*c]c_ulong,
) callconv(Call) LabjackCError;
pub extern fn DigitalIO(
idnum: *c_long,
demo: c_long,
trisD: *c_long, // 16 bits
trisIO: c_long, // 4 bits
stateD: *c_long, // 16 bits
stateIO: *c_long, // 4 bits
updateDigital: c_long,
outputD: *c_long, // 16 bits
) callconv(Call) LabjackCError;
pub extern fn GetDriverVersion() callconv(Call) f32;
pub extern fn StaticErrorString(errorcode: c_long) callconv(Call) [*:0]const u8;
pub extern fn GetErrorString(errorcode: c_long, errorString: *[50]u8) callconv(Call) void;
pub extern fn GetFirmwareVersion(idnum: *c_long) callconv(Call) f32;
pub extern fn ListAll(
productIDList: *[127]c_long,
serialnumList: *[127]c_long,
localIDList: *[127]c_long,
powerList: *[127]c_long,
calMatrix: *[127][20]c_long,
numberFound: *c_long,
fcddMaxSize: *c_long,
hvcMaxSize: *c_long,
) callconv(Call) LabjackCError;
pub extern fn LocalID(
idnum: *c_long,
localID: c_long,
) callconv(Call) LabjackCError;
pub extern fn PulseOut(
idnum: *c_long,
demo: c_long,
lowFirst: c_long,
bitSelect: c_long,
numPulses: c_long,
timeB1: c_long,
timeC1: c_long,
timeB2: c_long,
timeC2: c_long,
) callconv(Call) LabjackCError;
pub extern fn PulseOutStart(
idnum: *c_long,
demo: c_long,
lowFirst: c_long,
bitSelect: c_long,
numPulses: c_long,
timeB1: c_long,
timeC1: c_long,
timeB2: c_long,
timeC2: c_long,
) callconv(Call) LabjackCError;
pub extern fn PulseOutFinish(
idnum: *c_long,
demo: c_long,
timeoutMS: c_long,
) callconv(Call) LabjackCError;
pub extern fn PulseOutCalc(
frequency: *f32,
timeB: *c_long,
timeC: *c_long,
) callconv(Call) LabjackCError;
pub extern fn ReEnum(idnum: *c_long) callconv(Call) LabjackCError;
pub extern fn Reset(idnum: *c_long) callconv(Call) LabjackCError;
pub extern fn ResetLJ(idnum: *c_long) callconv(Call) LabjackCError;
pub extern fn CloseLabJack(localID: c_long) callconv(Call) LabjackCError;
pub extern fn SHT1X(
idnum: *c_long,
demo: c_long,
softComm: c_long,
mode: c_long,
statusReg: c_long,
tempC: *f32,
tempF: *f32,
rh: *f32,
) callconv(Call) LabjackCError;
pub extern fn SHTComm(
idnum: *c_long,
softComm: c_long,
waitMeas: c_long,
serialReset: c_long,
dataRate: c_long,
numWrite: c_long,
numRead: c_long,
datatx: [*]u8, // numWrite length
datarx: [*]u8, // numRead length
) callconv(Call) LabjackCError;
pub extern fn SHTCRC(
statusReg: c_long,
numWrite: c_long,
numRead: c_long,
datatx: *[4]u8,
datarx: *[4]u8,
) callconv(Call) LabjackCError;
pub extern fn Synch(
idnum: *c_long,
demo: c_long,
mode: c_long,
msDelay: c_long,
husDelay: c_long,
controlCS: c_long,
csLine: c_long,
csState: c_long,
configD: c_long,
numWriteRead: c_long,
data: *[18]c_long,
) callconv(Call) LabjackCError;
pub extern fn Watchdog(
idnum: *c_long,
demo: c_long,
active: c_long,
timeout: c_long,
reset: c_long,
activeD0: c_long,
activeD1: c_long,
activeD8: c_long,
stateD0: c_long,
stateD1: c_long,
stateD8: c_long,
) callconv(Call) LabjackCError;
pub extern fn ReadMem(
idnum: *c_long,
address: c_long,
data3: *c_long,
data2: *c_long,
data1: *c_long,
data0: *c_long,
) callconv(Call) LabjackCError;
pub extern fn WriteMem(
idnum: *c_long,
unlocked: c_long,
address: c_long,
data3: c_long,
data2: c_long,
data1: c_long,
data0: c_long,
) callconv(Call) LabjackCError;
pub const LabjackCError = packed struct(c_ulong) {
code: LabjackErrorCode,
stream_thread_error_flag: u1,
firmware_version_error_flag: u1,
_unused: switch (@typeInfo(c_ulong).Int.bits) {
32 => u22,
64 => u54,
else => @compileError("c_ulong has a mystery number of bits"),
},
pub fn okay(self: LabjackCError) bool {
return self.code == .no_error;
}
pub fn toError(self: LabjackCError) LabjackError {
return switch (self.code) {
.no_error => error.UnknownError,
.unknown_error => error.UnknownError,
.no_devices_found => error.NoDevicesFound,
.device_n_not_found => error.DeviceNNotFound,
.set_buffer_error => error.SetBufferError,
.open_handle_error => error.OpenHandleError,
.close_handle_error => error.CloseHandleError,
.invalid_id => error.InvalidId,
.array_size_or_value_error => error.ArraySizeOrValueError,
.invalid_power_index => error.InvalidPowerIndex,
.fcdd_size => error.FcddSize,
.hvc_size => error.HvcSize,
.read_error => error.ReadError,
.read_timeout_error => error.ReadTimeoutError,
.write_error => error.WriteError,
.feature_error => error.FeatureError,
.illegal_channel => error.IllegalChannel,
.illegal_gain_index => error.IllegalGainIndex,
.illegal_ai_command => error.IllegalAiCommand,
.illegal_ao_command => error.IllegalAoCommand,
.bits_out_of_range => error.BitsOutOfRange,
.illegal_number_of_channels => error.IllegalNumberOfChannels,
.illegal_scan_rate => error.IllegalScanRate,
.illegal_num_samples => error.IllegalNumSamples,
.ai_response_error => error.AiResponseError,
.ram_cs_error => error.RamCsError,
.ai_sequence_error => error.AiSequenceError,
.num_streams_error => error.NumStreamsError,
.ai_stream_start => error.AiStreamStart,
.pc_buff_overflow => error.PcBuffOverflow,
.lj_buff_overflow => error.LjBuffOverflow,
.stream_read_timeout => error.StreamReadTimeout,
.illegal_num_scans => error.IllegalNumScans,
.no_stream_found => error.NoStreamFound,
.illegal_input_error => error.IllegalInput,
.echo_error => error.EchoError,
.data_echo_error => error.DataEchoError,
.response_error => error.ResponseError,
.asynch_timeout_error => error.AsynchTimeout,
.asynch_start_error => error.AsynchStartError,
.asynch_frame_error => error.AsynchFrameError,
.asynch_dio_config_error => error.AsynchDioConfigError,
.input_caps_error => error.InputCapsError,
.output_caps_error => error.OutputCapsError,
.feature_caps_error => error.FeatureCapsError,
.num_caps_error => error.NumCapsError,
.get_attributes_warning => error.GetAttributesWarning,
.wrong_firmware_version => error.WrongFirmwareVersion,
.dio_config_error => error.DioConfigError,
.claim_all_devices => error.ClaimAllDevices,
.release_all_devices => error.ReleaseAllDevices,
.claim_device => error.ClaimDevice,
.release_device => error.ReleaseDevice,
.claimed_abandoned => error.ClaimedAbandoned,
.localid_neg => error.LocalidNeg,
.stop_thread_timeout => error.StopThreadTimeout,
.terminate_thread => error.TerminateThread,
.feature_handle => error.FeatureHandle,
.create_mutex => error.CreateMutex,
.synch_csstatetris_error => error.SynchCsstatetrisError,
.synch_scktris_error => error.SynchScktrisError,
.synch_misotris_error => error.SynchMisotrisError,
.synch_mositris_error => error.SynchMositrisError,
.sht1x_crc_error => error.Sht1xCrcError,
.sht1x_measready_error => error.Sht1xMeasreadyError,
.sht1x_ack_error => error.Sht1xAckError,
.sht1x_serial_reset_error => error.Sht1xSerialResetError,
_ => error.UnknownErrorCode,
};
}
pub const LabjackErrorCode = enum(u8) {
no_error = 0, // must be 0
unknown_error = 1,
no_devices_found = 2,
device_n_not_found = 3,
set_buffer_error = 4,
open_handle_error = 5,
close_handle_error = 6,
invalid_id = 7,
array_size_or_value_error = 8,
invalid_power_index = 9,
fcdd_size = 10,
hvc_size = 11,
read_error = 12,
read_timeout_error = 13,
write_error = 14,
feature_error = 15,
illegal_channel = 16,
illegal_gain_index = 17,
illegal_ai_command = 18,
illegal_ao_command = 19,
bits_out_of_range = 20,
illegal_number_of_channels = 21,
illegal_scan_rate = 22,
illegal_num_samples = 23,
ai_response_error = 24,
ram_cs_error = 25,
ai_sequence_error = 26,
num_streams_error = 27,
ai_stream_start = 28,
pc_buff_overflow = 29,
lj_buff_overflow = 30,
stream_read_timeout = 31,
illegal_num_scans = 32,
no_stream_found = 33,
illegal_input_error = 40,
echo_error = 41,
data_echo_error = 42,
response_error = 43,
asynch_timeout_error = 44,
asynch_start_error = 45,
asynch_frame_error = 46,
asynch_dio_config_error = 47,
input_caps_error = 48,
output_caps_error = 49,
feature_caps_error = 50,
num_caps_error = 51,
get_attributes_warning = 52,
wrong_firmware_version = 57,
dio_config_error = 58,
claim_all_devices = 64,
release_all_devices = 65,
claim_device = 66,
release_device = 67,
claimed_abandoned = 68,
localid_neg = 69,
stop_thread_timeout = 70,
terminate_thread = 71,
feature_handle = 72,
create_mutex = 73,
synch_csstatetris_error = 80,
synch_scktris_error = 81,
synch_misotris_error = 82,
synch_mositris_error = 83,
sht1x_crc_error = 89,
sht1x_measready_error = 90,
sht1x_ack_error = 91,
sht1x_serial_reset_error = 92,
_,
pub fn describe(self: LabjackErrorCode) []const u8 {
const res = StaticErrorString(@intFromEnum(self));
return std.mem.sliceTo(res, 0);
}
};
};
};
pub const LabjackError = error{
UnknownError,
NoDevicesFound,
DeviceNNotFound,
SetBufferError,
OpenHandleError,
CloseHandleError,
InvalidId,
ArraySizeOrValueError,
InvalidPowerIndex,
FcddSize,
HvcSize,
ReadError,
ReadTimeoutError,
WriteError,
FeatureError,
IllegalChannel,
IllegalGainIndex,
IllegalAiCommand,
IllegalAoCommand,
BitsOutOfRange,
IllegalNumberOfChannels,
IllegalScanRate,
IllegalNumSamples,
AiResponseError,
RamCsError,
AiSequenceError,
NumStreamsError,
AiStreamStart,
PcBuffOverflow,
LjBuffOverflow,
StreamReadTimeout,
IllegalNumScans,
NoStreamFound,
IllegalInput,
EchoError,
DataEchoError,
ResponseError,
AsynchTimeout,
AsynchStartError,
AsynchFrameError,
AsynchDioConfigError,
InputCapsError,
OutputCapsError,
FeatureCapsError,
NumCapsError,
GetAttributesWarning,
WrongFirmwareVersion,
DioConfigError,
ClaimAllDevices,
ReleaseAllDevices,
ClaimDevice,
ReleaseDevice,
ClaimedAbandoned,
LocalidNeg,
StopThreadTimeout,
TerminateThread,
FeatureHandle,
CreateMutex,
SynchCsstatetrisError,
SynchScktrisError,
SynchMisotrisError,
SynchMositrisError,
Sht1xCrcError,
Sht1xMeasreadyError,
Sht1xAckError,
Sht1xSerialResetError,
UnknownErrorCode,
//
InvalidGain,
};