Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

opendaq_lua_bindings

Low-level and high-level Lua bindings for openDAQ (data acquisition SDK), mirroring the architecture of the Dart bindings in blueberry_flutter_native_frameworks/dart.

  • Low levelopendaq_bindings, one portable C extension: ~1,350 generated lua_CFunctions, 1:1 with the libcopendaq C ABI (C.daqSignal_getPublic(handle)err, value), plus hand-written ownership/reader/callback plumbing. Compiles unchanged against Lua 5.1/LuaJIT through Lua 5.5; libcopendaq is dlopened at runtime.
  • High levelopendaq, pure Lua: idiomatic wrapper classes (generated from the RTGen interface model + a hand-written core), with properties, inheritance, queryInterface casting, error objects, GC finalizers and deterministic dispose().
local opendaq = require('opendaq')
opendaq.init('/path/to/libcopendaq.dylib')

local instance = opendaq.Instance.create{ modulePaths = { '/path/to/modules' } }
local root = instance.rootDevice

for _, info in ipairs(root.availableDevices) do
  print(info.name, info.connectionString)  -- e.g. Simulator  daqref://device0
end

local device = root:addDevice('daqref://device0')
local channel = device:getChannelsRecursive()[1]
channel:setPropertyValue('Frequency', 25.0)

local signal = device:getSignalsRecursive()[1]
local reader = opendaq.StreamReader.forSignal(signal)
reader:read(1000, 200)                    -- first read drains the initial event
local r = reader:readWithDomain(1000, 1000)
print(('%d samples, first tick %s'):format(r.count, tostring(r.domainTicks[1])))

Layout

codegen/            the generator (pure Lua, self-contained)
  main.lua          orchestrator: parse headers + model → emit → write-if-changed
  config.lua        ALL policy: targets, base classes, excludes, overrides
  lib/              header_parser, model, type_map, emit_c, emit_lua
  model/*.json      189 RTGen interface dumps (copied from the Dart repo)
src/                the C extension (one translation unit)
  opendaq_bindings.c  handle/buffer userdata, dlopen loader, readers,
                      trampoline pools, event queue
  compat.h          Lua 5.1↔5.5 shims        dl_compat.h  dlopen/LoadLibrary
  gen/              GENERATED: api table, dlsym loop, wrappers, enums
lua/opendaq/        the high-level package
  core/             class system, DaqObject, errors, casting, conversions
  generated/        GENERATED wrapper classes + manifest
  opendaq/          hand-written: Instance, Context, callbacks, packets, ...
  reader/           Stream/Tail/Block/Multi readers, sample types
examples/           example1..5 (tree walk, discovery+properties, stream
                    reading, producer path, core events + callbacks)
tests/              plain-Lua suite (tests/run.lua harness)
tools/              build.sh, test.sh, run.sh, check_symbols.sh

Build & test

tools/fetch_opendaq.sh  # clone + build openDAQ from GitHub (pinned release,
                        # default v3.40.0) into vendor/: libcopendaq +
                        # reference device/function-block modules
tools/build.sh          # builds build/lua5.5/ and build/luajit/ variants
tools/test.sh           # runs the suite under both interpreters
tools/run.sh examples/example1.lua           # lua 5.5
tools/run.sh --luajit examples/example3.lua  # LuaJIT

openDAQ comes from github.com/openDAQ/openDAQ at the release tag pinned in tools/fetch_opendaq.sh (OPENDAQ_TAG overrides). Headers land in vendor/openDAQ/bindings/c/include, binaries in vendor/build/bin/ — the default everywhere (codegen, CMake, tools, examples). Overrides:

  • OPENDAQ_LIB — path to libcopendaq.dylib/.so (module plugins are discovered from the same directory)
  • OPENDAQ_C_INCLUDE — copendaq header dir for the codegen
  • LUA55_INC / LUAJIT_INC — Lua header dirs for the build

Regenerating

lua codegen/main.lua            # regen src/gen + lua/opendaq/generated
lua codegen/main.lua --check    # CI gate: exits 2 if anything would change
tools/check_symbols.sh          # generated names ⊆ nm -gU libcopendaq

Inputs: the copendaq headers (--headers <dir> or OPENDAQ_C_INCLUDE, default: the blueberry native-frameworks build tree) drive the low-level C emission; codegen/model/*.json drives the high-level classes. All policy (which interfaces, base classes, excludes) lives in codegen/config.lua.

Semantics

  • Errors — low-level returns err, values... and never raises for openDAQ failures; high-level raises structured errors ({ code, symbol, operation }, opendaq.isError). DAQ_ERR_NOINTERFACE drives obj:tryCast('Signal') returning nil.
  • Ownership — every object handle is a boxed userdata owning one native reference; __gc releases it, obj:dispose() releases deterministically and neuters the box (further native use raises). Test gate: C.debug_handle_count() == 0 after dispose + GC.
  • Events — openDAQ fires from arbitrary native threads; handlers never run there. The C layer queues events (refs pre-added, bounded ring, drop-oldest); opendaq.pollEvents(timeoutMs) drains and dispatches on your thread. Cancel is race-free via slot generations.
  • CallbacksDaqFunction.fromLua(fn) / DaqProcedure.fromLua(fn) wrap closures via a static trampoline pool (the C typedefs have no context pointer). They call into Lua synchronously — only safe when openDAQ invokes them on the Lua thread; use events otherwise.
  • int64 — exact on Lua 5.3+; on 5.1/LuaJIT numbers are doubles (exact to 2^53 — ~285 years of 1 MHz domain ticks). Buffers store native int64.
  • Enums — plain integers; name tables under opendaq.enums.<daqEnum>, friendly aliases opendaq.SampleType, opendaq.LogLevel.

Parity with the Dart bindings

Scenario (Dart examples) Lua
init(path) + Instance.create (modulePaths, logLevel, loggerSinks) opendaq.init + Instance.create{}
rootDevice, availableDevices → addDevice (daqref reference device) ✅ example2
component tree walk, findComponent + cast, kind/specialize ✅ example1, tests 03/04
property get/set, hasProperty, visibleProperties (name/valueType/value) ✅ example2, test 04
device lock/unlock, operationMode + availableOperationModes ✅ test 04
function blocks: addFunctionBlock, input ports, connect ✅ generated (Device:addFunctionBlock, InputPort:connect)
StreamReader (read / readWithDomain / readInto), read-twice idiom ✅ example3, test 05
TailReader, BlockReader, MultiReader (synchronized multi-signal) ✅ test 05
producer path: descriptors, linear rule, signals, packets, sendPacket ✅ example4, test 06
core events: context-wide + per-component subscribe/cancel ✅ example5, test 07
IFunction/IProcedure from closures (+ invoke received callables) ✅ example5, test 07
deterministic dispose + GC finalizers, leak gates ✅ every test file

Known gaps (deliberate, same as or narrower than Dart): *AndStealRef members, ValueRange (bespoke), subscribeForDestructNotification, Device.getTicksSinceOrigin (UInt), C++-only factories.

About

openDAQ lua bindings

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages