For an experiment I need for a Julia process to communicate with a LabView process and exchange data every few seconds (1-5 s). Since (I think) there is no package to directly bind Julia and LabView I was thinking of connecting both with a TCP-socket and exchange the data in a common format (maybe HDF5?).
Another possibility could be using Python as middleware via PyJulia. LabVIEW 2018+ supports some Python communication natively (however I tried it and somehow coudnāt get numpy). There is also Python Intergartion Toolkit from Enthought. I didnāt try it, but they have a 30-day eval version.
Take a look at TableView.jl, the lazy loading yields big performance gains that are non-trivial to work out. I think it works by writing JSON as an intermediary.
That was kind of the initial implementation: reading and writing CSV files. I just wanted to try a more āelegantā solution if I had to increase the data exchange rate.
Installed ZMQ.jl and labview-zmq without a glitch. Getting data from LabVIEW client to Julia ZMQ server and back turned out to be trivial. Launching and stopping Julia ZMQ server from LV already done, now Iām going to add the JSON part and then would publish on the Github.
This is a very minimal implementation of a Julia client to LabView server interaction
using Sockets
using JSON
# Establish connection
lv = connect(5050) # TCP-Socket on port 5050 (localhost)
# Send data
data = Dict("A" => rand(5), "B" => rand(5))
msg = JSON.json(data)
write(lv, msg)
# Receive data
str = String(readavailable(lv))
data = JSON.parse(str; dicttype=Dict{String,Vector{Float64}})
I think the most important and tricky step to consider is implementing readavailable to receive the JSON-string from LabView, else Julia doesnāt know how many bytes to read from the buffer.
We start server from LV by launching a Julia script in a Terminal:
and stop it by sending it a command to stop.
The VI in the first diagram is a Functional Global Variable, saving the state of the client and server. Upon initialization it can be then called independently from different parts of the LV program (first come first served).
I have then specified the data exchange protocoll:
The Request (from LV) contains JSON string and an optional binary part. The JSON dict contains the name of the function to be called by Julia server and (optionally) keyword arguments to be passed to this function. The binary data, if there are any, will be passed as an argument, too.
The Response contains a JSON encoded dict corresponding to LV error cluster, optional JSON encoded data, and optional binary data.
But as this project has little to do with my current work, and the weekend happened to be just 2 days long, I didnāt manage to get it at least half-ready . Maybe next weekend
This is a working example (on the LabVIEW side). The both loops access the Julia server concurrently. The roundtrip time for light loads is well below 1 ms on the average and below 20 ms max
For large binary data sent from LV to Julia the roundtrip time grows by approx. 4 ms/MB.
Install ZMQ for LV and ZMQ.jl and JSON3.jl on Julia side.
On the LV side, you define a cluster with field names corresponding to the arguments you want to pass to your Julia function, and an additional string field named āfun2callā for the name of your Julia function. You may additionaly create a byte array to be passed as binary data
On the Julia side you define your functions with corresponding names, which must accept keyword arguments only, argument names see above. For binary data, if any, additional keyword argument ābin_dataā. The result must be a named tuple or anything else, convertable to JSON.
On the LV side you parse the JSON string to a cluster. Additionally it is your responsibility to parse binary data returned by Julia server (returning binary data not implemented yet).
TODO:
Clean-up
Documentation
Error handling
Returning binary data
Utilities to encode/decode some common data types to binary data (arrays of different floats and integer types)
Timeouts
LabVIEW server and Julia client
Timescale undefined (and depends on the interest), but I hope to deal with the first two items of the list on a reasonable timescale.
The LabVIEW part is LV2018. If anybody interested, I could try to save the library for an earlier version.
Receiving processed data (2 arrays, one scalar value):
On Julia side, user provides a script file like following:
function foo(;arg1, arg2_string, arg3_arr_Cx32, arg4_arr_i16)
# doing some stuff, e.g. check if we got the right data type
@assert typeof(arg3_arr_Cx32) == Array{Complex{Float32},2}
@assert typeof(arg4_arr_i16) == Vector{Int16}
# some more stuff
# get data to return, e.g.
resp1 = rand()
resp_arr_I32 = Int32.(vcat(arg4_arr_i16, [1,2,3]))
resp_arr_F64 = arg1.*abs.(vec(arg3_arr_Cx32)) .+ sqrt(2)
return (;resp1, bigarrs=(;resp_arr_I32, resp_arr_F64))
end
function bar(;kwargs...)
# doing some stuff
end
fns = (;foo, bar)
Path to this script to be passed to LabVIEW, which starts Julia server and passes user functions to it.
P.S. LabVIEW is statically typed, which means a lot of boilerplate
P.P.S. Thanks a due to Covid-19 pandemic, which provided me with sufficient spare time.