Skip to content

Commit

Permalink
handling of number types clarified now; documentation added.
Browse files Browse the repository at this point in the history
  • Loading branch information
thomvet committed Jul 10, 2024
1 parent e3b90b7 commit 35040c8
Show file tree
Hide file tree
Showing 11 changed files with 129 additions and 108 deletions.
36 changes: 23 additions & 13 deletions docs/src/manual/numbertypes.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
# Supported number types
It is noteworthy that the open62541 library does not support all number types
included within Julia natively. Open62541.jl supports the same number types as its
C counterpart. Julia types that are not supported will throw an exception, rather
than silently performing an automated conversion for you.

It is noteworthy that the open62541 library supports the following Julia
number types natively. Open62541.jl provides support for the same number types.
Adding other types is possible, but must rely on a custom datatype. See the [open62541 documentation](https://github.com/open62541/open62541/tree/master/examples/custom_datatype).
If you want to store a Julia type that is not on the list below (for example:
`Float32`, `Complex{Int64}` or `Rational{Bool}`) in an OPC UA server, you should
consciously convert it to a supported number type beforehand.

**Real:**
- Boolean: Bool
- Integers: Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, UInt64.
- Float: Float32 and Float64.

**Complex:**
Furthermore `JUA_Client_readValueAttribute(client, nodeid)` will return numbers
in one of the supported formats below. You can specify the conversion to be used
via its typed equivalent if you know a `Float16` value should be returned, you
can call `JUA_Client_readValueAttribute(client, nodeid, Float16)`. This conversion
obviously only works if implemented in Julia.

- Complex{Float32}, Complex{Float64}
Adding other number types is possible, but relies on introducing a custom
datatype. See the [open62541 documentation](https://github.com/open62541/open62541/tree/master/examples/custom_datatype)
for details about this.

**Complex:**
## Real numbers:
- Boolean: Bool
- Integers: Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, UInt64
- Float: Float32, Float64
- Rational: Rational{Int32}, Rational{UInt32}

- Rational{Int32}
- Rational{UInt32}
## Complex numbers:
- Complex{Float32}
- Complex{Float64}
2 changes: 1 addition & 1 deletion src/Open62541.jl
Original file line number Diff line number Diff line change
Expand Up @@ -32511,7 +32511,7 @@ const UA_GUID_NULL = UA_Guid(0, 0, 0, Tuple(zeros(UA_Byte, 8)))
const UA_NODEID_NULL = UA_NodeId(Tuple(zeros(UA_Byte, 24)))
const UA_EXPANDEDNODEID_NULL = UA_ExpandedNodeId(UA_NODEID_NULL, UA_STRING_NULL, 0)

#Julia number types that are rare built directly into open62541
#Julia number types that are built directly into open62541
#Does NOT include ComplexF32/64 - these have to be treated differently.
const UA_NUMBER_TYPES = Union{Bool, Int8, Int16, Int32, Int64, UInt8, UInt16,
UInt32, UInt64, Float32, Float64}
Expand Down
90 changes: 49 additions & 41 deletions src/attribute_generation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ function __set_generic_attributes!(attr,
end

function __set_scalar_attributes!(attr, value::T,
valuerank) where {T <: Union{AbstractFloat, Integer, Ptr{UA_String},
valuerank) where {T <: Union{UA_NUMBER_TYPES, Ptr{UA_String},
UA_ComplexNumberType, UA_DoubleComplexNumberType, UA_RationalNumber, UA_UnsignedRationalNumber}}
type_ptr = ua_data_type_ptr_default(T)
attr.valueRank = valuerank
Expand All @@ -211,20 +211,16 @@ function __set_scalar_attributes!(attr, value::AbstractString, valuerank)
end

function __set_scalar_attributes!(
attr, value::Complex{T}, valuerank) where {T <: AbstractFloat}
if sizeof(T) <= 4 #Float32 has length of 4 bytes; everything smaller gets upconverted.
UA_T = UA_ComplexNumberType
else #for everything larger conversion to Float64 is attempted.
UA_T = UA_DoubleComplexNumberType
end
ua_c = UA_T(reim(value)...)
attr, value::Complex{T}, valuerank) where {T <: Union{Float32, Float64}}
f = T == Float32 ? UA_ComplexNumberType : UA_DoubleComplexNumberType
ua_c = f(reim(value)...)
__set_scalar_attributes!(attr, ua_c, valuerank)
return nothing
end

function __set_scalar_attributes!(
attr, value::Rational{T}, valuerank) where {T <: Integer}
f = T <: Signed ? UA_RationalNumber : UA_UnsignedRationalNumber
attr, value::Rational{T}, valuerank) where {T <: Union{Int32, UInt32}}
f = T == Int32 ? UA_RationalNumber : UA_UnsignedRationalNumber
ua_c = f(value.num, value.den)
__set_scalar_attributes!(attr, ua_c, valuerank)
return nothing
Expand All @@ -240,23 +236,19 @@ function __set_array_attributes!(attr, value::AbstractArray{<:AbstractString}, v
end

function __set_array_attributes!(attr, value::AbstractArray{<:Complex{T}},
valuerank) where {T <: AbstractFloat}
if sizeof(T) <= 4 #Float32 has length of 4 bytes; everything smaller gets upconverted.
UA_T = UA_ComplexNumberType
else #for everything larger conversion to Float64 is attempted.
UA_T = UA_DoubleComplexNumberType
end
a = similar(value, UA_T)
valuerank) where {T <: Union{Float32, Float64}}
f = T == Float32 ? UA_ComplexNumberType : UA_DoubleComplexNumberType
a = similar(value, f)
for i in eachindex(a)
a[i] = UA_T(reim(value[i])...) #implicit conversion to Float32/64
a[i] = f(reim(value[i])...) #implicit conversion to Float32/64
end
__set_array_attributes!(attr, a, valuerank)
return nothing
end

function __set_array_attributes!(attr, value::AbstractArray{<:Rational{T}},
valuerank) where {T <: Integer}
f = T <: Signed ? UA_RationalNumber : UA_UnsignedRationalNumber
valuerank) where {T <: Union{Int32, UInt32}}
f = T == Int32 ? UA_RationalNumber : UA_UnsignedRationalNumber
a = similar(value, f)
for i in eachindex(a)
a[i] = f(value[i].num, value[i].den) #implicit conversion to (U)Int32
Expand All @@ -267,7 +259,7 @@ end

function __set_array_attributes!(attr, value::AbstractArray{T, N},
valuerank) where {
T <: Union{AbstractFloat, Integer, UA_String,
T <: Union{UA_NUMBER_TYPES, UA_String,
UA_ComplexNumberType, UA_DoubleComplexNumberType, UA_RationalNumber,
UA_UnsignedRationalNumber},
N}
Expand Down Expand Up @@ -299,7 +291,7 @@ UA_VariableAttributes_generate(; value::Union{AbstractArray{T}, T},
useraccesslevel::Union{Nothing, UInt8} = nothing,
minimumsamplinginterval::Union{Nothing, Float64} = nothing,
historizing::Union{Nothing, Bool} = nothing,
valuerank::Union{Integer, Nothing} = nothing)::Ptr{UA_VariableAttributes} where {T <: Union{Rational, Complex, AbstractFloat, Integer, AbstractString}}
valuerank::Union{Integer, Nothing} = nothing)::Ptr{UA_VariableAttributes} where {T <: Union{UA_NUMBER_TYPES, Complex{Float32}, Complex{Float64}, Rational{Int32}, Rational{UInt32}, AbstractString}}
```
generates a `UA_VariableAttributes` object. Memory for the object is allocated
Expand All @@ -324,11 +316,18 @@ function UA_VariableAttributes_generate(; value::Union{AbstractArray{T}, T},
useraccesslevel::Union{Nothing, UInt8} = nothing,
minimumsamplinginterval::Union{Nothing, Float64} = nothing,
historizing::Union{Nothing, Bool} = nothing,
valuerank::Union{Nothing, Integer} = nothing) where
{T <: Union{Rational, Complex, AbstractFloat, Integer, AbstractString}}
attr = __generate_variable_attributes(value, displayname, description,
valuerank::Union{Nothing, Integer} = nothing) where {T <: Union{Number, AbstractString}}
if T <: Union{UA_NUMBER_TYPES, Complex{Float32}, Complex{Float64}, Rational{Int32}, Rational{UInt32}, AbstractString}
attr = __generate_variable_attributes(value, displayname, description,
localization, writemask, userwritemask, accesslevel, useraccesslevel,
minimumsamplinginterval, historizing, valuerank)
else
#if number type not specifically reported (see union above) throw an informative exception.
err = UnsupportedNumberTypeError(T)
throw(err)

Check warning on line 327 in src/attribute_generation.jl

View check run for this annotation

Codecov / codecov/patch

src/attribute_generation.jl#L326-L327

Added lines #L326 - L327 were not covered by tests
end


return attr
end

Expand Down Expand Up @@ -380,13 +379,13 @@ function __generic_variable_attributes(displayname, description, localization,
end
if type <: AbstractString
attr.dataType = unsafe_load(UA_TYPES_PTRS[UA_TYPES_STRING].typeId)
elseif type <: Complex && sizeof(type) <= 8
elseif type == Complex{Float32}
attr.dataType = unsafe_load(UA_TYPES_PTRS[UA_TYPES_COMPLEXNUMBERTYPE].typeId)
elseif type <: Complex && sizeof(type) > 8
elseif type == Complex{Float64}
attr.dataType = unsafe_load(UA_TYPES_PTRS[UA_TYPES_DOUBLECOMPLEXNUMBERTYPE].typeId)
elseif type <: Rational && type.parameters[1] <: Signed
elseif type == Rational{Int32}
attr.dataType = unsafe_load(UA_TYPES_PTRS[UA_TYPES_RATIONALNUMBER].typeId)
elseif type <: Rational && type.parameters[1] <: Union{Unsigned, Bool}
elseif type == Rational{UInt32}
attr.dataType = unsafe_load(UA_TYPES_PTRS[UA_TYPES_UNSIGNEDRATIONALNUMBER].typeId)
else
attr.dataType = unsafe_load(ua_data_type_ptr_default(type).typeId)
Expand All @@ -406,7 +405,7 @@ UA_VariableTypeAttributes_generate(; value::Union{Nothing, AbstractArray{T}, T}
writemask::Union{Nothing, UInt32} = nothing,
userwritemask::Union{Nothing, UInt32} = nothing,
valuerank::Union{Nothing, Integer} = nothing,
isabstract::Union{Nothing, Bool})::Ptr{UA_VariableTypeAttributes} where {T <: Union{Rational, Complex, AbstractFloat, Integer, AbstractString}}
isabstract::Union{Nothing, Bool})::Ptr{UA_VariableTypeAttributes} where {T <: Union{Nothing, UA_NUMBER_TYPES, Complex{Float32}, Complex{Float64}, Rational{Int32}, Rational{UInt32}, AbstractString}}
```
generates a `UA_VariableTypeAttributes` object. Memory for the object is allocated
Expand All @@ -428,16 +427,24 @@ function UA_VariableTypeAttributes_generate(; value::Union{AbstractArray{T}, T}
writemask::Union{Nothing, UInt32} = nothing,
userwritemask::Union{Nothing, UInt32} = nothing,
valuerank::Union{Nothing, Integer} = nothing,
isabstract::Union{Nothing, Bool} = nothing) where {T <: Union{Nothing, Rational, Complex, AbstractFloat, Integer, AbstractString}}
attr = __generate_variabletype_attributes(value, displayname, description,
isabstract::Union{Nothing, Bool} = nothing) where {T <: Union{Nothing, Number, AbstractString}}
if T <: Union{Nothing, UA_NUMBER_TYPES, Complex{Float32}, Complex{Float64}, Rational{Int32}, Rational{UInt32}, AbstractString}
attr = __generate_variabletype_attributes(value, displayname, description,
localization, writemask, userwritemask, valuerank, isabstract)
return attr
return attr
else
#if number type not specifically reported (see union above) throw an informative exception.
err = UnsupportedNumberTypeError(T)
throw(err)

Check warning on line 438 in src/attribute_generation.jl

View check run for this annotation

Codecov / codecov/patch

src/attribute_generation.jl#L437-L438

Added lines #L437 - L438 were not covered by tests
end
end

function __generate_variabletype_attributes(value::AbstractArray{T, N}, displayname,
description, localization, writemask, userwritemask, valuerank,
isabstract) where {
T <: Union{AbstractString, AbstractFloat, Integer, Complex, Rational}, N}
isabstract) where {T <: Union{UA_NUMBER_TYPES, Complex{Float32},
Complex{Float64}, Rational{Int32}, Rational{UInt32},
AbstractString},
N}
if isnothing(valuerank)
valuerank = UA_VALUERANK(N)
end
Expand All @@ -449,8 +456,9 @@ end

function __generate_variabletype_attributes(value::T, displayname,
description, localization, writemask, userwritemask, valuerank,
isabstract) where {T <: Union{
AbstractString, AbstractFloat, Integer, Complex, Rational}}
isabstract) where {T <: Union{Nothing, UA_NUMBER_TYPES, Complex{Float32},
Complex{Float64}, Rational{Int32}, Rational{UInt32},
AbstractString}}
if isnothing(valuerank)
valuerank = UA_VALUERANK_SCALAR
end
Expand Down Expand Up @@ -483,13 +491,13 @@ function __generic_variabletype_attributes(displayname, description, localizatio
if !isnothing(type)
if type <: AbstractString
attr.dataType = unsafe_load(UA_TYPES_PTRS[UA_TYPES_STRING].typeId)
elseif type <: Complex && sizeof(type) <= 8
elseif type == Complex{Float32}
attr.dataType = unsafe_load(UA_TYPES_PTRS[UA_TYPES_COMPLEXNUMBERTYPE].typeId)
elseif type <: Complex && sizeof(type) > 8
elseif type == Complex{Float64}
attr.dataType = unsafe_load(UA_TYPES_PTRS[UA_TYPES_DOUBLECOMPLEXNUMBERTYPE].typeId)
elseif type <: Rational && type.parameters[1] <: Signed
elseif type == Rational{Int32}
attr.dataType = unsafe_load(UA_TYPES_PTRS[UA_TYPES_RATIONALNUMBER].typeId)
elseif type <: Rational && type.parameters[1] <: Union{Unsigned, Bool}
elseif type == Rational{UInt32}
attr.dataType = unsafe_load(UA_TYPES_PTRS[UA_TYPES_UNSIGNEDRATIONALNUMBER].typeId)
else
attr.dataType = unsafe_load(ua_data_type_ptr_default(type).typeId)
Expand Down
12 changes: 12 additions & 0 deletions src/exceptions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ function Base.showerror(io::IO, e::AttributeCopyError)
print(io, msg)
end

#Unsupported Number type
struct UnsupportedNumberTypeError <: Exception
type::DataType
end

function Base.showerror(io::IO, e::UnsupportedNumberTypeError)
msg = "The supplied number type ($(e.type)) is not supported within Open62541.jl

Check warning on line 41 in src/exceptions.jl

View check run for this annotation

Codecov / codecov/patch

src/exceptions.jl#L40-L41

Added lines #L40 - L41 were not covered by tests
(nor open62541 for that matter). Natively supported number types are:
$UA_NUMBER_TYPES, Complex{Float32}, Complex{Float64}, Rational{Int32}, Rational{UInt32}."
print(io, msg)

Check warning on line 44 in src/exceptions.jl

View check run for this annotation

Codecov / codecov/patch

src/exceptions.jl#L44

Added line #L44 was not covered by tests
end

#Callback generator argument error
#fields intentionally kept abstract; no specialization needed.
struct CallbackGeneratorArgumentError <: Exception
Expand Down
43 changes: 14 additions & 29 deletions src/highlevel_types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@ function Base.setproperty!(x::AbstractOpen62541Wrapper, f::Symbol, v::AbstractOp
end

#Sets a field of Ptr{UA_XXX} to a JUA_YYY item.
#This creates a opy of the object to be assigned, so that the JUA_YYY object
#This creates a copy of the object to be assigned, so that the JUA_YYY object
#can be safely used multiple times in assignments without getting freed multiple
#times.
for i in unique_julia_types_ind
@eval begin
function Base.setproperty!(x::Ptr{$(julia_types[i])}, f::Symbol, v::T, nowarn::Bool = false) where T <: AbstractOpen62541Wrapper
type_ptr = ua_data_type_ptr_default(typeof(Jpointer(v)))
UA_clear(getproperty(x, f), type_ptr)
UA_copy(Jpointer(v), getproperty(x, f), type_ptr)
if nowarn == false
@warn "Assigning a $(typeof(v)) as content of field $(String(f)) in a $(typeof(x)) leads to a copy of
Expand Down Expand Up @@ -656,6 +657,13 @@ mutable struct JUA_Variant <: AbstractOpen62541Wrapper
return obj
end

function JUA_Variant(value::Union{AbstractArray{T}, T}) where T <: Number
#if not specifically handled by one of the methods below, the number type
#is not natively supported; hence throw an informative exception.
err = UnsupportedNumberTypeError(T)
throw(err)
end

function JUA_Variant(value::AbstractArray{T, N},
type_ptr::Ptr{UA_DataType} = ua_data_type_ptr_default(T)) where {
T <: Union{UA_NUMBER_TYPES, UA_String, UA_ComplexNumberType, UA_DoubleComplexNumberType, UA_RationalNumber, UA_UnsignedRationalNumber}, N}
Expand Down Expand Up @@ -690,7 +698,7 @@ mutable struct JUA_Variant <: AbstractOpen62541Wrapper
return obj
end

function JUA_Variant(value::Complex{T}) where {T <: AbstractFloat}
function JUA_Variant(value::Complex{T}) where {T <: Union{Float32, Float64}}
if sizeof(T) <= 4
f = UA_ComplexNumberType
else
Expand All @@ -700,11 +708,6 @@ mutable struct JUA_Variant <: AbstractOpen62541Wrapper
return JUA_Variant(ua_c)
end

function JUA_Variant(value::Complex{T}) where {T <: Union{Integer, Rational}}
v = float(value)
return JUA_Variant(v)
end

function JUA_Variant(value::Rational{<:Unsigned})
v = UA_UnsignedRationalNumber(value.num, value.den)
return JUA_Variant(v)
Expand All @@ -715,11 +718,6 @@ mutable struct JUA_Variant <: AbstractOpen62541Wrapper
return JUA_Variant(v)
end

function JUA_Variant(value::Rational{Bool})
v = UA_UnsignedRationalNumber(value.num, value.den)
return JUA_Variant(v)
end

function JUA_Variant(value::AbstractArray{<:AbstractString})
a = similar(value, UA_String)
for i in eachindex(a)
Expand All @@ -728,36 +726,23 @@ mutable struct JUA_Variant <: AbstractOpen62541Wrapper
return JUA_Variant(a)
end

function JUA_Variant(value::AbstractArray{<:Complex{T}}) where {T <: AbstractFloat}
if sizeof(T) <= 4
f = UA_ComplexNumberType
else
f = UA_DoubleComplexNumberType
end
function JUA_Variant(value::AbstractArray{<:Complex{T}}) where {T <: Union{Float32, Float64}}
f = T == Float32 ? UA_ComplexNumberType : UA_DoubleComplexNumberType
a = similar(value, f)
for i in eachindex(a)
a[i] = f(reim(value[i])...)
end
return JUA_Variant(a)
end

function JUA_Variant(value::AbstractArray{<:Rational{T}}) where {T <: Integer}
if T <: Signed
f = UA_RationalNumber
else #decision to include Bool here.
f = UA_UnsignedRationalNumber
end
function JUA_Variant(value::AbstractArray{<:Rational{T}}) where {T <: Union{Int32, UInt32}}
f = T == Int32 ? UA_RationalNumber : UA_UnsignedRationalNumber
a = similar(value, f)
for i in eachindex(a)
a[i] = f(value[i].num, value[i].den)
end
return JUA_Variant(a)
end

function JUA_Variant(value::AbstractArray{<:Complex{T}}) where {T <: Union{Integer, Rational}}
a = float(value)
return JUA_Variant(a)
end
end

function release_handle(obj::JUA_Variant)
Expand Down
5 changes: 3 additions & 2 deletions test/add_change_var_array.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ Distributed.@everywhere begin
include("test_helpers.jl") #this will cause method re-definition warnings, because it has been included already in the scalar case, but it's not important here.

# What types and sizes we are testing for:
types = [Bool, Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32,
UInt64, Float32, Float64, String, ComplexF16, ComplexF32, ComplexF64, Rational{Int16}, Rational{Int32}, Rational{UInt16}, Rational{UInt32}]
types = [Bool, Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, UInt64,
Float32, Float64, String, ComplexF32, ComplexF64, Rational{Int32},
Rational{UInt32}]
array_sizes = (11, (2, 5), (3, 4, 5), (3, 4, 5, 6))

# Generate random input values and generate nodeid names
Expand Down
6 changes: 3 additions & 3 deletions test/add_change_var_scalar.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ Distributed.@everywhere begin
include("test_helpers.jl")

# What types we are testing for:
types = [Bool, Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32,
UInt64, Float32, Float64, String, ComplexF16, ComplexF32, ComplexF64, Rational{Int16},
Rational{Int32}, Rational{UInt16}, Rational{UInt32}]
types = [Bool, Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, UInt64,
Float32, Float64, String, ComplexF32, ComplexF64, Rational{Int32},
Rational{UInt32}]

# Generate random input values and generate nodeid names
input_data = Tuple(customrand(type) for type in types)
Expand Down
Loading

0 comments on commit 35040c8

Please sign in to comment.