Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding more docstrings, high level interface improvements #18

Merged
merged 26 commits into from
May 24, 2024
Merged
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
085c599
docstrings for server.jl add node functions (higher level interface m…
thomvet Apr 24, 2024
79949a0
More server and client docstrings + docs
thomvet Apr 24, 2024
5316618
distribute docstrings onto different api pages
thomvet Apr 24, 2024
f13fcf9
adjust purpose text in server_read.jl
thomvet Apr 25, 2024
055dd51
add readme and further docs
thomvet May 3, 2024
8490407
more docs; more tests; worked on high level interface.
thomvet May 9, 2024
ff4ae37
test more types in variable nodes (incl. string).
thomvet May 12, 2024
07d8ddf
fix array tests for complex numbers and strings
thomvet May 13, 2024
5a7c578
more high level interface
thomvet May 13, 2024
f5fc1b1
format
thomvet May 13, 2024
58b3bb7
remove callback tests for mac os x (LLVM limitation)
thomvet May 13, 2024
28f8647
remove method node test on mac os x
thomvet May 13, 2024
c9d9db6
mac os again
thomvet May 13, 2024
2caf7fd
shorter test strings
thomvet May 13, 2024
9f235c1
wait longer
thomvet May 13, 2024
f167c82
restructure files; interface improvements
thomvet May 14, 2024
01811f6
Move UA_Variant_new methods into JUA_Variant and adapt tests.
thomvet May 16, 2024
5e70700
started high level add node tests.
thomvet May 16, 2024
a0aa7bb
docstring; interface improvements
thomvet May 21, 2024
8f3ba6d
fix typo; add methodattributes to high level
thomvet May 21, 2024
2f06e35
complete high level types for now; need some more tests
thomvet May 21, 2024
d1fa9ce
test fallback methods
thomvet May 22, 2024
5b20e47
align client/server writevalue functions incl. jua_variant case
thomvet May 22, 2024
f9c6300
remove UA_Client_addMethodNode (unuseable also in open62541)
thomvet May 22, 2024
3bc82ae
clean up
thomvet May 22, 2024
a9b34b2
bugfix JUA_Server_writeValue
thomvet May 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
more docs; more tests; worked on high level interface.
thomvet committed May 9, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
commit 8490407ecdf9116cbf4a27f1e17b59aea4a49457
4 changes: 3 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -17,6 +17,7 @@ Dates = "1.6"
Distributed = "1.6"
OffsetArrays = "1"
Pkg = "1.6"
Random = "1.6"
SafeTestsets = "0.1.0"
Test = "1.6"
julia = "1.6"
@@ -27,8 +28,9 @@ Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b"
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Aqua", "Base64", "Distributed", "Pkg", "SafeTestsets", "Test"]
test = ["Aqua", "Base64", "Distributed", "Pkg", "Random", "SafeTestsets", "Test"]
39 changes: 35 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -47,6 +47,7 @@ you can install by executing:
Starting up a server with a default configuration in open62541.jl is very simple.
Just execute the following code in the REPL or as a script:
```julia
using open62541
server = JUA_Server()
config = JUA_ServerConfig(server)
JUA_ServerConfig_setDefault(config)
@@ -55,10 +56,40 @@ Just execute the following code in the REPL or as a script:
This will configure a server with the default configuration (address: opc.tcp://localhost:4840/)
and start it. The server can be shut down by pressing CTRL+C multiple times.

While running the server it can be accessed, for example, either via the Client
API of open62541.jl or it can be browsed and accessed with a graphical client,
such as [UA Expert website](https://www.unified-automation.com/products/development-tools/uaexpert.html).
While the server is running, it can be accessed via the Client API of open62541.jl
or it can be browsed and accessed with a graphical client, such as [UA Expert](https://www.unified-automation.com/products/development-tools/uaexpert.html).

## Basic client example
In order to showcase the Client API functionality, we will use the above server
and read some basic information from it, namely the software version number and
the current time. Note that this information should be contained in all OPC UA
servers, so you could also connect to a different server that you know is running.


```julia
using open62541
using Printf

#initiate client, configure it and connect to server
client = JUA_Client()
config = JUA_ClientConfig(client)
JUA_ClientConfig_setDefault(config)
JUA_Client_connect(client, "opc.tcp://localhost:4840")

#define nodeids that we are interested in
nodeid_currenttime = JUA_NodeId(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME)
nodeid_version = JUA_NodeId(0, UA_NS0ID_SERVER_SERVERSTATUS_BUILDINFO_SOFTWAREVERSION)

#read data from nodeids
currenttime = JUA_Client_readValueAttribute(client, nodeid_currenttime) #Int64 which represents the number of 100 nanosecond intervals since January 1, 1601 (UTC)
version = JUA_Client_readValueAttribute(client, nodeid_version) #String containing open62541 version number

#Convert current time into human understandable format
dts = UA_DateTime_toStruct(currenttime)

#Print results to terminal
Printf.@printf("current date and time (UTC) is: %u-%u-%u %u:%u:%u.%03u\n", dts.day, dts.month, dts.year, dts.hour, dts.min, dts.sec, dts.milliSec)
Printf.@printf("The server is running open62541 version %s.", version)

#disconnect the client (good housekeeping practice)
JUA_Client_disconnect(client)
```
6 changes: 5 additions & 1 deletion docs/make.jl
Original file line number Diff line number Diff line change
@@ -22,7 +22,11 @@ makedocs(;
"manual/attributegeneration.md",
"manual/server.md",
"manual/client.md",
]
],
"Tutorials" => [
"tutorials/server_first_steps.md",
"tutorials/client_first_steps.md"
]
],
warnonly = Documenter.except(
:autodocs_block,
34 changes: 34 additions & 0 deletions docs/src/tutorials/client_first_steps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# First steps with an open62541 client
In this tutorial we will connect an OPC client to the server started up in [First steps with an open62541 server](@ref)
and read some basic information from it, namely the software version number and
the current time. Note that this information should be contained in all OPC UA
servers, so you could also connect to a different server that you know is running.

```julia
using open62541
using Printf

#initiate client, configure it and connect to server
client = JUA_Client()
config = JUA_ClientConfig(client)
JUA_ClientConfig_setDefault(config)
JUA_Client_connect(client, "opc.tcp://localhost:4840")

#define nodeids that we are interested in
nodeid_currenttime = JUA_NodeId(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME)
nodeid_version = JUA_NodeId(0, UA_NS0ID_SERVER_SERVERSTATUS_BUILDINFO_SOFTWAREVERSION)

#read data from nodeids
currenttime = JUA_Client_readValueAttribute(client, nodeid_currenttime) #Int64 which represents the number of 100 nanosecond intervals since January 1, 1601 (UTC)
version = JUA_Client_readValueAttribute(client, nodeid_version) #String containing open62541 version number

#Convert current time into human understandable format
dts = UA_DateTime_toStruct(currenttime)

#Print results to terminal
Printf.@printf("current date and time (UTC) is: %u-%u-%u %u:%u:%u.%03u\n", dts.day, dts.month, dts.year, dts.hour, dts.min, dts.sec, dts.milliSec)
Printf.@printf("The server is running open62541 version %s.", version)

#disconnect the client (good housekeeping practice)
JUA_Client_disconnect(client)
```
15 changes: 5 additions & 10 deletions docs/src/tutorials/server_first_steps.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
```@meta
CurrentModule = open62541
```

# First steps with an open62541 server
Starting up a server with a default configuration in open62541.jl is very simple.
Just execute the following code in the REPL or as a script:
```julia
using open62541
server = JUA_Server()
config = JUA_ServerConfig(server)
JUA_ServerConfig_setDefault(config)
@@ -14,10 +11,8 @@ Just execute the following code in the REPL or as a script:
This will configure a server with the default configuration (address: opc.tcp://localhost:4840/)
and start it. The server can be shut down by pressing CTRL+C multiple times.

While running the server it can be accessed, for example, either via the Client
API of open62541.jl or it can be browsed and accessed with a graphical client,
such as [UA Expert website](https://www.unified-automation.com/products/development-tools/uaexpert.html).

How to add your own variables, objects, methods, and objects is explained in
subsequent tutorials.
While the server is running, it can be accessed via the Client API of open62541.jl
or it can be browsed and accessed with a graphical client, such as [UA Expert](https://www.unified-automation.com/products/development-tools/uaexpert.html).

Subsequent tutorials will explain how to add your own variables, objects, and
methods to the server.
22 changes: 13 additions & 9 deletions src/attribute_generation.jl
Original file line number Diff line number Diff line change
@@ -195,7 +195,7 @@ function __set_generic_attributes!(attr,
end

function __set_scalar_attributes!(attr, value::T,
valuerank) where {T <: Union{AbstractFloat, Integer}}
valuerank) where {T <: Union{AbstractFloat, Integer, Ptr{UA_String}}}
type_ptr = ua_data_type_ptr_default(T)
attr.valueRank = valuerank
UA_Variant_setScalarCopy(attr.value, wrap_ref(value), type_ptr)
@@ -204,15 +204,22 @@ end

function __set_scalar_attributes!(attr, value::AbstractString, valuerank)
ua_s = UA_STRING(value)
type_ptr = ua_data_type_ptr_default(UA_String)
attr.valueRank = valuerank
UA_Variant_setScalarCopy(attr.value, ua_s, type_ptr)
__set_scalar_attributes!(attr, ua_s, valuerank)
UA_String_delete(ua_s)
return nothing
end

function __set_array_attributes!(attr, value::AbstractArray{<:AbstractString}, valuerank)
a = similar(value, UA_String)
for i in eachindex(a)
a[i] = UA_String_fromChars(value[i])
end
__set_array_attributes!(attr, a, valuerank)
return nothing
end

function __set_array_attributes!(attr, value::AbstractArray{T, N},
valuerank) where {T, N}
valuerank) where {T <: Union{AbstractFloat, Integer, UA_String}, N}
type_ptr = ua_data_type_ptr_default(T)
attr.valueRank = valuerank
#Note: need array dims twice, once to put into the variant, i.e., attr.value
@@ -224,10 +231,7 @@ function __set_array_attributes!(attr, value::AbstractArray{T, N},
attr.arrayDimensions = arraydims_attr
attr.arrayDimensionsSize = length(arraydims_attr)
ua_arr = UA_Array_new(vec(permutedims(value, reverse(1:N))), type_ptr) # Allocate new UA_Array from value with C style indexing
UA_Variant_setArray(attr.value,
ua_arr,
length(value),
type_ptr)
UA_Variant_setArray(attr.value, ua_arr, length(value), type_ptr)
attr.value.arrayDimensions = arraydims_variant
attr.value.arrayDimensionsSize = length(arraydims_variant)
return nothing
20 changes: 4 additions & 16 deletions src/client.jl
Original file line number Diff line number Diff line change
@@ -234,12 +234,9 @@ for att in attributes_UA_Client_read
ua_attr_name = Symbol("UA_ATTRIBUTEID_", uppercase(att[2]))

@eval begin
#TODO: check whether function signature is correct here; decision on whether carrying wrap-ref signature is desirable.
"""
```
$($(fun_name))(client::Union{Ref{UA_Client}, Ptr{UA_Client}, UA_Client},
nodeId::Union{Ref{UA_NodeId}, Ptr{UA_NodeId}, UA_NodeId},
out::Ptr{$($(att[3]))} = $($(String(returnobject)))())
$($(fun_name))(client::Ptr{UA_Client}, nodeId::Ptr{UA_NodeId}, out::Ptr{$($(att[3]))} = $($(String(returnobject)))())
```

Uses the UA Client API to read the value of attribute $($(String(attr_name))) from the NodeId `nodeId` accessed through the client `client`.
@@ -280,17 +277,13 @@ for att in attributes_UA_Client_write
@eval begin
"""
```
$($(fun_name))(client::Union{Ref{UA_Client}, Ptr{UA_Client}, UA_Client},
nodeId::Union{Ref{UA_NodeId}, Ptr{UA_NodeId}, UA_NodeId},
new_val::Union{Ref{$($(String(attr_type)))},Ptr{$($(String(attr_type)))}, $($(String(attr_type)))})
$($(fun_name))(client::Ptr{UA_Client}, nodeId::Ptr{UA_NodeId}, new_val::Ptr{$($(String(attr_type)))})
```

Uses the UA Client API to write the value `new_val` to the attribute $($(String(attr_name))) of the NodeId `nodeId` accessed through the client `client`.

"""
function $(fun_name)(client::Ref{UA_Client},
nodeId::Ref{UA_NodeId},
new_attr::Ref{$attr_type})
function $(fun_name)(client, nodeId, new_attr)
data_type_ptr = UA_TYPES_PTRS[$(attr_type_ptr)]
statuscode = __UA_Client_writeAttribute(client,
nodeId,
@@ -311,12 +304,6 @@ for att in attributes_UA_Client_write
throw(err)
end
end
#function fallback that wraps any non-ref arguments into refs:
function $(fun_name)(client, nodeId, new_attr)
return ($fun_name)(wrap_ref(client),
wrap_ref(nodeId),
wrap_ref(new_attr))
end
end
end

@@ -512,6 +499,7 @@ for att in attributes_UA_Client_write_async
end
end
#function fallback that wraps any non-ref arguments into refs:
#TODO: check whether we still need this or whether we have in the meantime managed to organize things well.
function $(fun_name)(client, nodeId, out, callback, userdata, reqId)
return $(fun_name)(wrap_ref(client),
wrap_ref(nodeId),
2 changes: 0 additions & 2 deletions src/exceptions.jl
Original file line number Diff line number Diff line change
@@ -47,8 +47,6 @@ function Base.showerror(io::IO, e::CallbackGeneratorArgumentError)
unclear which one should be used as basis of the callback."
else
args_in = fieldtypes(first(methods(e.f)).sig)
@show args_in[2:end]
@show e.f, e.argtuple, methods(e.f), args_in
ret = Base.return_types(e.f, args_in[2:end])[1]
msg = "Callback generator expected a method with f($(join([e.argtuple...], ", ")))::$(string(e.returntype)),
but received a method f($(join(args_in[2:end], ", ")))::$(string(ret))." #TODO: does the job, but it's an ugly error message
2 changes: 1 addition & 1 deletion src/server.jl
Original file line number Diff line number Diff line change
@@ -207,7 +207,7 @@ for att in attributes_UA_Server_write
@eval begin
"""
```
$($(fun_name))(server, nodeId, new_val)
$($(fun_name))(server::Ptr{UA_Server}, nodeId::Ptr{UA_NodeId}, new_val::Ptr{$($(String(attr_type)))})
```
Uses the Server API to write the value `new_val` to the attribute $($(String(attr_name)))
of the NodeId `nodeId` located on the `server`.
58 changes: 34 additions & 24 deletions src/types.jl
Original file line number Diff line number Diff line change
@@ -65,7 +65,7 @@
function UA_Array_new(v::AbstractArray{T},
type_ptr::Ptr{UA_DataType} = ua_data_type_ptr_default(T)) where {T}
v_typed = convert(Vector{juliadatatype(type_ptr)}, vec(v)) # Implicit check if T can be converted to type_ptr
arr_ptr = convert(Ptr{T}, UA_Array_new(length(v), type_ptr))
arr_ptr = convert(Ptr{juliadatatype(type_ptr)}, UA_Array_new(length(v), type_ptr))
GC.@preserve v_typed arr_ptr unsafe_copyto!(arr_ptr, pointer(v_typed), length(v))
return UA_Array(arr_ptr, length(v))
end
@@ -98,6 +98,7 @@
ua_data_type_ptr(::$(val_type)) = UA_TYPES_PTRS[$(i - 1)]
if !(type_names[$(i)] in types_ambiguous_ignorelist)
ua_data_type_ptr_default(::Type{$(julia_type)}) = UA_TYPES_PTRS[$(i - 1)]
ua_data_type_ptr_default(::Type{Ptr{$julia_type}}) = ua_data_type_ptr_default($julia_type)
Base.show(io::IO, ::MIME"text/plain", v::$(julia_type)) = print(io, UA_print(v))
end

@@ -281,10 +282,16 @@
return UA_STRING_ALLOC(s)
end

#TODO: think whether this can be cleaned up further
Base.unsafe_string(s::UA_String) = unsafe_string(s.data, s.length)
Base.unsafe_string(s::Ref{UA_String}) = unsafe_string(s[])
Base.unsafe_string(s::Ptr{UA_String}) = unsafe_string(unsafe_load(s))
function Base.unsafe_string(s::UA_String)
if s.length == 0 #catch NullString
u = ""
else
u = unsafe_string(s.data, s.length)
end
return u
end
Base.unsafe_string(s::Ptr{UA_String}) = Base.unsafe_string(unsafe_load(s))


## UA_BYTESTRING
"""
@@ -777,39 +784,42 @@
Base.length(v::UA_Variant) = Int(v.arrayLength)
Base.length(p::Ref{UA_Variant}) = length(unsafe_load(p))

function UA_Variant_new_copy(value::AbstractArray{T, N},
type_ptr::Ptr{UA_DataType} = ua_data_type_ptr_default(T)) where {T, N}
function UA_Variant_new(value::AbstractArray{T, N},
type_ptr::Ptr{UA_DataType} = ua_data_type_ptr_default(T)) where {T <: Union{AbstractFloat, UA_String, Integer}, N}
var = UA_Variant_new()
var.type = type_ptr
var.storageType = UA_VARIANT_DATA
var.arrayLength = length(value)
ua_arr = UA_Array_new(vec(permutedims(value, reverse(1:N))), type_ptr) # Allocate new UA_Array from value with C style indexing
UA_Variant_setArray(var, ua_arr, length(value), type_ptr)
var.arrayDimensionsSize = length(size(value))
var.data = UA_Array_new(vec(permutedims(value, reverse(1:N))), type_ptr)
var.arrayDimensions = UA_UInt32_Array_new(reverse(size(value)))
return var
end

function UA_Variant_new_copy(value::Ref{T},
function UA_Variant_new(value::T,
type_ptr::Ptr{UA_DataType} = ua_data_type_ptr_default(T)) where {T <: Union{
AbstractFloat, Integer}}
AbstractFloat, Integer, Ptr{UA_String}}}
var = UA_Variant_new()
var.type = type_ptr
var.storageType = UA_VARIANT_DATA
var.arrayLength = 0
var.arrayDimensionsSize = length(size(value))
UA_Variant_setScalarCopy(var, value, type_ptr)
var.arrayDimensions = C_NULL
UA_Variant_setScalarCopy(var, wrap_ref(value), type_ptr)
return var
end

function UA_Variant_new_copy(value::T,
type_ptr::Ptr{UA_DataType} = ua_data_type_ptr_default(T)) where {T <: Union{
AbstractFloat, Integer}}
return UA_Variant_new_copy(Ref(value), type_ptr)
function UA_Variant_new(value::AbstractString)
ua_s = UA_STRING(value)
v = UA_Variant_new(ua_s)
UA_String_delete(ua_s)
return v

Check warning on line 814 in src/types.jl

Codecov / codecov/patch

src/types.jl#L810-L814

Added lines #L810 - L814 were not covered by tests
end

function UA_Variant_new_copy(value, type_sym::Symbol)
UA_Variant_new_copy(value, ua_data_type_ptr(Val(type_sym)))
function UA_Variant_new(value::AbstractArray{<:AbstractString})
a = similar(value, UA_String)
for i in eachindex(a)
a[i] = UA_String_fromChars(value[i])
end
return UA_Variant_new(a)
end

function Base.unsafe_wrap(v::UA_Variant)
@@ -824,11 +834,11 @@
end
end

Base.unsafe_wrap(p::Ref{UA_Variant}) = unsafe_wrap(unsafe_load(p))
Base.unsafe_wrap(p::Ptr{UA_Variant}) = unsafe_wrap(unsafe_load(p))
UA_Variant_isEmpty(v::UA_Variant) = v.type == C_NULL
UA_Variant_isEmpty(p::Ref{UA_Variant}) = UA_Variant_isEmpty(unsafe_load(p))
UA_Variant_isScalar(v::UA_Variant) = v.arrayLength == 0 && v.data > UA_EMPTY_ARRAY_SENTINEL
UA_Variant_isScalar(p::Ref{UA_Variant}) = UA_Variant_isScalar(unsafe_load(p))
UA_Variant_isEmpty(p::Ptr{UA_Variant}) = UA_Variant_isEmpty(unsafe_load(p))

Check warning on line 839 in src/types.jl

Codecov / codecov/patch

src/types.jl#L839

Added line #L839 was not covered by tests
UA_Variant_isScalar(v::UA_Variant) = v.arrayLength == 0 #TODO: this fails when strings are present in the variant, why? && v.data > UA_EMPTY_ARRAY_SENTINEL
UA_Variant_isScalar(p::Ptr{UA_Variant}) = UA_Variant_isScalar(unsafe_load(p))

Check warning on line 841 in src/types.jl

Codecov / codecov/patch

src/types.jl#L841

Added line #L841 was not covered by tests

function UA_Variant_hasScalarType(v::UA_Variant, type::Ref{UA_DataType})
return UA_Variant_isScalar(v) && type == v.type
Loading