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.
This commit is contained in:
2024-07-01 23:55:56 -07:00
parent be819363b9
commit c8bec6a7c5
5 changed files with 669 additions and 24 deletions

824
src/labjack.zig Normal file
View File

@@ -0,0 +1,824 @@
const std = @import("std");
pub fn getDriverVersion() f32 {
return c_api.GetDriverVersion();
}
pub const Labjack = struct {
id: ?i32 = 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);
return if (version == 0)
@as(c_api.LabjackCError, @bitCast(id)).toError()
else
version;
}
/// 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,
@intFromBool(self.demo),
input.channelNumber(),
input.gain_index,
&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,
@intFromBool(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.gain_index;
}
var v_out: [4]f32 = .{0} ** 4;
var over_v: c_long = 0;
const status = c_api.AISample(
&id,
@intFromBool(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;
}
};
pub const AnalogInput = struct {
channel: AnalogInputChannel,
gain_index: GainIndex = 0,
pub fn channelNumber(self: AnalogInput) u4 {
return @intFromEnum(self.channel);
}
};
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 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));
}
};
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,
channel: c_long,
gain: c_long,
overVoltage: *c_long,
voltage: *f32,
) LabjackCError;
pub extern fn EAnalogOut(
idnum: *c_long,
demo: c_long,
analogOut0: f32,
analogOut1: f32,
) LabjackCError;
pub extern fn ECount(
idnum: *c_long,
demo: c_long,
resetCounter: c_long,
count: *f64,
ms: *f64,
) LabjackCError;
pub extern fn EDigitalIn(
idnum: *c_long,
demo: c_long,
channel: c_long,
readD: c_long,
state: *c_long,
) LabjackCError;
pub extern fn EDigitalOut(
idnum: *c_long,
demo: c_long,
channel: c_long,
writeD: c_long,
state: c_long,
) 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,
) 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,
) 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,
) 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
) 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,
) 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,
) LabjackCError;
pub extern fn AIStreamClear(
localID: c_long,
) 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,
) LabjackCError;
pub extern fn BitsToVolts(
chnum: c_long,
chgain: c_long,
bits: c_long,
volts: [*c]f32,
) LabjackCError;
pub extern fn VoltsToBits(
chnum: c_long,
chgain: c_long,
volts: f32,
bits: [*c]c_long,
) 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,
) 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
) LabjackCError;
pub extern fn GetDriverVersion() f32;
pub extern fn StaticErrorString(errorcode: c_long) [*:0]const u8;
pub extern fn GetErrorString(errorcode: c_long, errorString: *[50]u8) void;
pub extern fn GetFirmwareVersion(idnum: *c_long) 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,
) LabjackCError;
pub extern fn LocalID(
idnum: *c_long,
localID: c_long,
) 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,
) 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,
) LabjackCError;
pub extern fn PulseOutFinish(
idnum: *c_long,
demo: c_long,
timeoutMS: c_long,
) LabjackCError;
pub extern fn PulseOutCalc(
frequency: *f32,
timeB: *c_long,
timeC: *c_long,
) LabjackCError;
pub extern fn ReEnum(idnum: *c_long) LabjackCError;
pub extern fn Reset(idnum: *c_long) LabjackCError;
pub extern fn ResetLJ(idnum: *c_long) LabjackCError;
pub extern fn CloseLabJack(localID: c_long) 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,
) 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
) LabjackCError;
pub extern fn SHTCRC(
statusReg: c_long,
numWrite: c_long,
numRead: c_long,
datatx: *[4]u8,
datarx: *[4]u8,
) 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,
) 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,
) LabjackCError;
pub extern fn ReadMem(
idnum: *c_long,
address: c_long,
data3: *c_long,
data2: *c_long,
data1: *c_long,
data0: *c_long,
) 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,
) 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,
};