diff --git a/src/FMI2/cfunc.jl b/src/FMI2/cfunc.jl index b1eb004..eff4706 100644 --- a/src/FMI2/cfunc.jl +++ b/src/FMI2/cfunc.jl @@ -91,23 +91,12 @@ Source: FMISpec2.0.2[p.22]: 2.1.5 Creation, Destruction and Logging of FMU Insta The function controls debug logging that is output via the logger function callback. If loggingOn = fmi2True, debug logging is enabled, otherwise it is switched off. """ -function fmi2SetDebugLogging( - cfunc::Ptr{Cvoid}, - c::fmi2Component, - logginOn::fmi2Boolean, - nCategories::Csize_t, - categories::Union{Ptr{fmi2String},AbstractArray{fmi2String}}, -)::fmi2Status - status = ccall( - cfunc, - fmi2Status, - (fmi2Component, fmi2Boolean, Csize_t, Ptr{fmi2String}), - c, - logginOn, - nCategories, - categories, - ) - @debug "fmi2SetDebugLogging(c: $(c), logginOn: $(loggingOn), nCategories: $(nCategories), categories: $(categories)) → $(status)" +function fmi2SetDebugLogging(cfunc::Ptr{Cvoid}, c::fmi2Component, loggingOn::fmi2Boolean, nCategories::Csize_t, categories::Union{Ptr{fmi2String}, AbstractArray{fmi2String}}) + status = ccall(cfunc, + fmi2Status, + (fmi2Component, fmi2Boolean, Csize_t, Ptr{fmi2String}), + c, loggingOn, nCategories, categories) + @debug "fmi2SetDebugLogging(c: $(c), loggingOn: $(loggingOn), nCategories: $(nCategories), categories: $(categories)) → $(status)" return status end export fmi2SetDebugLogging diff --git a/test/FMI2/CS.jl b/test/FMI2/CS.jl new file mode 100644 index 0000000..39a2979 --- /dev/null +++ b/test/FMI2/CS.jl @@ -0,0 +1,72 @@ +# Copyright (c) 2021 Tobias Thummerer, Lars Mikelsons, Josef Kircher +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +using Libdl, Suppressor + +function test_CS(lib, cblibpath) + component = fmi2Instantiate(dlsym(lib, :fmi2Instantiate), pointer("test_cs"), fmi2TypeCoSimulation, pointer("{3c564ab6-a92a-48ca-ae7d-591f819b1d93}"), pointer("file:///"), Ptr{fmi2CallbackFunctions}(pointer_from_objref(get_callbacks(cblibpath))), fmi2Boolean(false), fmi2Boolean(false)) + + startpoint = fmi2Real(0.0) + @test fmi2StatusOK == fmi2SetupExperiment(dlsym(lib, :fmi2SetupExperiment),component, fmi2Boolean(true), fmi2Real(0.01), startpoint, fmi2Boolean(false), fmi2Real(1.0)) + + + fmi2EnterInitializationMode(dlsym(lib, :fmi2EnterInitializationMode), component) + fmi2ExitInitializationMode(dlsym(lib, :fmi2ExitInitializationMode), component) + if Sys.WORD_SIZE == 64 + @test fmi2StatusOK == fmi2DoStep(dlsym(lib, :fmi2DoStep), component, fmi2Real(0.0), fmi2Real(0.01), fmi2False) + end + + + status = fmi2Real(0.0) + statusptr = pointer([status]) + @test fmi2StatusOK == fmi2GetRealStatus!(dlsym(lib, :fmi2GetRealStatus), component, fmi2StatusKindLastSuccessfulTime, Ptr{fmi2Real}(statusptr)) + + @suppress begin + # Suppressing the IllegalFunctionCall Warnings, as they are expected here + # Async is not supported in this FMU, so the status should be fmi2StatusDiscard + status = fmi2Status(fmi2StatusOK) + statusptr = pointer([status]) + @test fmi2StatusDiscard == fmi2GetStatus!(dlsym(lib, :fmi2GetStatus), component, fmi2StatusKindDoStepStatus, Ptr{fmi2Status}(statusptr)) + + + status = fmi2Integer(0) + statusptr = pointer([status]) + # Async is not supported in this FMU, so the status should be fmi2StatusDiscard + @test fmi2StatusDiscard == fmi2GetIntegerStatus!(dlsym(lib, :fmi2GetIntegerStatus), component, Cuint(2), Ptr{fmi2Integer}(statusptr)) + + status = fmi2Boolean(false) + statusptr = pointer([status]) + # Async is not supported in this FMU, so the status should be fmi2StatusDiscard + @test fmi2StatusDiscard == fmi2GetBooleanStatus!(dlsym(lib, :fmi2GetBooleanStatus), component, Cuint(2), Ptr{fmi2Boolean}(statusptr)) + + status = "test" + statusptr = pointer(status) + # Async is not supported in this FMU, so the status should be fmi2StatusDiscard + @test fmi2StatusDiscard == fmi2GetStringStatus!(dlsym(lib, :fmi2GetStringStatus), component, Cuint(2), Ptr{fmi2String}(statusptr)) + end + + fmi2Terminate(dlsym(lib, :fmi2Terminate), component) + +end + +function test_CS_IO(lib, cblibpath) + component = fmi2Instantiate(dlsym(lib, :fmi2Instantiate), pointer("test_generic_io"), fmi2TypeCoSimulation, pointer("{95a6399d-38c5-4504-b3f3-98319bd94ce6}"), pointer("file:///"), Ptr{fmi2CallbackFunctions}(pointer_from_objref(get_callbacks(cblibpath))), fmi2Boolean(false), fmi2Boolean(false)) + @test component != C_NULL + @test fmi2StatusOK == fmi2SetupExperiment(dlsym(lib, :fmi2SetupExperiment),component, fmi2Boolean(false), fmi2Real(0.0), fmi2Real(0.0), fmi2Boolean(false), fmi2Real(1.0)) + + @test fmi2StatusOK == fmi2EnterInitializationMode(dlsym(lib, :fmi2EnterInitializationMode), component) + @test fmi2StatusOK == fmi2ExitInitializationMode(dlsym(lib, :fmi2ExitInitializationMode), component) + + + fmireference = [fmi2ValueReference(352321536)] + @test fmi2StatusOK == fmi2SetRealInputDerivatives(dlsym(lib, :fmi2SetRealInputDerivatives), component, fmireference, Csize_t(1), [fmi2Integer(1)], fmi2Real.([1.0])) + + fmireference = [fmi2ValueReference(335544320)] + values = zeros(fmi2Real, 1) + @test fmi2StatusOK == fmi2GetRealOutputDerivatives!(dlsym(lib, :fmi2GetRealOutputDerivatives), component, fmireference, Csize_t(1), [fmi2Integer(1)], values) + + + + +end \ No newline at end of file diff --git a/test/FMI2/ME.jl b/test/FMI2/ME.jl new file mode 100644 index 0000000..dca2d9f --- /dev/null +++ b/test/FMI2/ME.jl @@ -0,0 +1,56 @@ +# Copyright (c) 2021 Tobias Thummerer, Lars Mikelsons, Josef Kircher +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +using Libdl + +function test_ME(lib, cblibpath) + component = fmi2Instantiate(dlsym(lib, :fmi2Instantiate), pointer("test_me"), fmi2TypeModelExchange, pointer("{3c564ab6-a92a-48ca-ae7d-591f819b1d93}"), pointer("file:///"), Ptr{fmi2CallbackFunctions}(pointer_from_objref(get_callbacks(cblibpath))), fmi2Boolean(false), fmi2Boolean(false)) + @test component != C_NULL + + fmi2EnterInitializationMode(dlsym(lib, :fmi2EnterInitializationMode), component) + fmi2ExitInitializationMode(dlsym(lib, :fmi2ExitInitializationMode), component) + + @test fmi2StatusOK == fmi2EnterEventMode(dlsym(lib, :fmi2Instantiate), component) + + eventInfo = fmi2EventInfo() + ptr = Ptr{fmi2EventInfo}(pointer_from_objref(eventInfo)) + + @test fmi2StatusOK == fmi2NewDiscreteStates!(dlsym(lib, :fmi2NewDiscreteStates), component, ptr) + + @test fmi2StatusOK == fmi2EnterContinuousTimeMode(dlsym(lib, :fmi2EnterContinuousTimeMode), component) + + enterEventMode = fmi2Boolean(false) + terminateSimulation = fmi2Boolean(false) + @test fmi2StatusOK == fmi2CompletedIntegratorStep!(dlsym(lib, :fmi2CompletedIntegratorStep), component, fmi2Boolean(false), pointer([enterEventMode]), pointer([terminateSimulation])) + + @test fmi2StatusOK == fmi2SetTime(dlsym(lib, :fmi2SetTime), component, fmi2Real(0.0)) + + n_states = Csize_t(2) + state_arr = zeros(fmi2Real, 2) + @test fmi2StatusOK == fmi2GetContinuousStates!(dlsym(lib, :fmi2GetContinuousStates), component,state_arr, n_states) + + state_arr[2] = 2.0 + @test fmi2StatusOK == fmi2SetContinuousStates(dlsym(lib, :fmi2SetContinuousStates), component,state_arr, n_states) + + state_arr = zeros(fmi2Real, 2) + @test fmi2StatusOK == fmi2GetContinuousStates!(dlsym(lib, :fmi2GetContinuousStates), component,state_arr, n_states) + @test state_arr[2] == 2.0 + + n_indicators = Csize_t(2) + indicator_arr = zeros(fmi2Real, 2) + @test fmi2StatusOK == fmi2GetEventIndicators!(dlsym(lib, :fmi2GetEventIndicators), component,indicator_arr, n_indicators) + + nom_state_arr = zeros(fmi2Real, 2) + @test fmi2StatusOK == fmi2GetNominalsOfContinuousStates!(dlsym(lib, :fmi2GetNominalsOfContinuousStates), component, nom_state_arr, n_states) + + der_arr = zeros(fmi2Real, 2) + @test fmi2StatusOK == fmi2GetDerivatives!(dlsym(lib, :fmi2GetDerivatives), component,der_arr, n_states) + # Acceleration should be equal to Gravity in this FMU + if Sys.WORD_SIZE == 64 + # on 32 Bit this returns 9.81 * 10^16 which is not equal to -9.81 + @test der_arr[2] ≈ -9.81 + end + + @test fmi2StatusOK == fmi2Terminate(dlsym(lib, :fmi2Terminate), component) +end diff --git a/test/FMI2/cfunc.jl b/test/FMI2/cfunc.jl index 5d29584..67f665e 100644 --- a/test/FMI2/cfunc.jl +++ b/test/FMI2/cfunc.jl @@ -3,4 +3,24 @@ # Licensed under the MIT license. See LICENSE file in the project root for details. # -# [ToDo] tests for FMI2 +using Libdl + +include.(["utils.jl", "ME.jl", "CS.jl", "generic.jl"]) + + +binarypath, fmu_path, cblibpath = get_os_binaries() +iopath, io_fmu_path, iocblibpath = get_os_binaries("IO") +if binarypath != "" + lib = dlopen(binarypath) + libio = dlopen(iopath) + # Missing Tests for fmi2 because the FMU we are testing with doesnt variables of these types + @testset "Generic Functions in ME Mode" begin test_generic(lib,cblibpath, fmi2TypeModelExchange) end + @testset "Generic Functions in ME Mode with IO FMU" begin test_generic_io(libio,iocblibpath, fmi2TypeModelExchange) end + @testset "Generic Functions in CS Mode with IO FMU" begin test_generic_io(libio,iocblibpath, fmi2TypeCoSimulation) end + @testset "Generic Functions in CS Mode" begin test_generic(lib,cblibpath, fmi2TypeCoSimulation) end + @testset "ME-specific Functions" begin test_ME(lib, cblibpath) end + @testset "CS-specific Functions" begin test_CS(lib, cblibpath) end + @testset "CS-specific Functions with IO FMU" begin test_CS_IO(libio, iocblibpath) end +else + @warn "No valid FMU binaries found for this OS. Skipping tests." +end \ No newline at end of file diff --git a/test/FMI2/generic.jl b/test/FMI2/generic.jl new file mode 100644 index 0000000..28084e4 --- /dev/null +++ b/test/FMI2/generic.jl @@ -0,0 +1,148 @@ +# Copyright (c) 2021 Tobias Thummerer, Lars Mikelsons, Josef Kircher +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +using Libdl, Suppressor + +function test_generic(lib, cblibpath, type::fmi2Type) + # Tests missing for fmi2 because the FMU we are testing with doesnt have such variables + + cGetTypesPlatform = dlsym(lib, :fmi2GetTypesPlatform) + test = fmi2GetTypesPlatform(cGetTypesPlatform) + @test unsafe_string(test) == "default" + + ## fmi2GetVersion TODO get Version from modelDescription.xml + vers = fmi2GetVersion(dlsym(lib, :fmi2GetVersion)) + @test unsafe_string(vers) == "2.0" + + # fmi2Instantiate + + component = fmi2Instantiate(dlsym(lib, :fmi2Instantiate), pointer("test_generic"), type, pointer("{3c564ab6-a92a-48ca-ae7d-591f819b1d93}"), pointer("file:///"), Ptr{fmi2CallbackFunctions}(pointer_from_objref(get_callbacks(cblibpath))), fmi2Boolean(false), fmi2Boolean(false)) + @test component != C_NULL + + @test fmi2StatusOK == fmi2SetDebugLogging(dlsym(lib, :fmi2SetDebugLogging), component, fmi2False, Unsigned(0), AbstractArray{fmi2String}([])) + @test fmi2StatusOK == fmi2SetDebugLogging(dlsym(lib, :fmi2SetDebugLogging), component, fmi2True, Unsigned(0), AbstractArray{fmi2String}([])) + + # fmi2SetupExperiment + + @test fmi2StatusOK == fmi2SetupExperiment(dlsym(lib, :fmi2SetupExperiment),component, fmi2Boolean(false), fmi2Real(0.0), fmi2Real(0.0), fmi2Boolean(false), fmi2Real(1.0)) + + + + # get, set and free State + state = fmi2FMUstate() + stateRef = Ref(state) + + @test fmi2StatusOK == fmi2GetFMUstate!(dlsym(lib, :fmi2GetFMUstate), component, stateRef) + state = stateRef[] + + @test typeof(state) == fmi2FMUstate + + @test fmi2StatusOK == fmi2SetFMUstate(dlsym(lib, :fmi2SetFMUstate), component, state) + stateRef = Ref(state) + + size_ptr = Ref{Csize_t}(0) + @test fmi2StatusOK == fmi2SerializedFMUstateSize!(dlsym(lib, :fmi2SerializedFMUstateSize), component, state, size_ptr) + size = size_ptr[] + + serializedState = Array{fmi2Byte}(zeros(size)) + @test fmi2StatusOK == fmi2SerializeFMUstate!(dlsym(lib,:fmi2SerializeFMUstate), component, state, serializedState, size) + + @test fmi2StatusOK == fmi2DeSerializeFMUstate!(dlsym(lib, :fmi2DeSerializeFMUstate), component, serializedState, size, stateRef) + + @test stateRef[] != C_NULL + @test fmi2StatusOK == fmi2FreeFMUstate(dlsym(lib,:fmi2FreeFMUstate), component, stateRef) + @test stateRef[] == C_NULL + + + + # # Initialization Mode + + @test fmi2StatusOK == fmi2EnterInitializationMode(dlsym(lib, :fmi2EnterInitializationMode), component) + + fmireference = [fmi2ValueReference(16777220)] + @test fmi2StatusOK == fmi2SetReal(dlsym(lib, :fmi2SetReal), component, fmireference, Csize_t(1), fmi2Real.([0.8])) + + fmireference = [fmi2ValueReference(16777220)] + value = zeros(fmi2Real, 1) + @test fmi2StatusOK == fmi2GetReal!(dlsym(lib, :fmi2GetReal), component, fmireference, Csize_t(1), value) + @test value == fmi2Real.([0.8]) + + fmireference = [fmi2ValueReference(637534208)] + value = zeros(fmi2Integer, 1) + @test fmi2StatusOK == fmi2GetInteger!(dlsym(lib, :fmi2GetInteger), component, fmireference, Csize_t(1), value) + + fmireference = [fmi2ValueReference(637534208)] + @test fmi2StatusOK == fmi2SetInteger(dlsym(lib, :fmi2SetInteger), component, fmireference, Csize_t(1), fmi2Integer.([typemin(fmi2Integer)])) + + fmireference = [fmi2ValueReference(637534208)] + value = zeros(fmi2Integer, 1) + @test fmi2StatusOK == fmi2GetInteger!(dlsym(lib, :fmi2GetInteger), component, fmireference, Csize_t(1), value) + @test value == fmi2Integer.([typemin(fmi2Integer)]) + + # calculate ∂p/∂p (x) + posreference = [fmi2ValueReference(33554432)] + delta_x = fmi2Real.([randn()]) + result = zeros(fmi2Real, 1) + @test fmi2StatusOK == fmi2GetDirectionalDerivative!(dlsym(lib,:fmi2GetDirectionalDerivative), component, posreference, Csize_t(1), posreference, Csize_t(1), delta_x, result) + # ∂p/∂p(x) should be just x for any x + if Sys.WORD_SIZE == 64 + # GetDirectionalDerivative behaves weirdly on 32 Bit + @test result ≈ delta_x + end + + @test fmi2StatusOK == fmi2ExitInitializationMode(dlsym(lib, :fmi2ExitInitializationMode), component) + @suppress begin + # Suppressing the CVODE-Stats that are printed here in CS Mode + @test fmi2StatusOK == fmi2Reset(dlsym(lib, :fmi2Reset), component) + end + + # # # fmi2FreeInstance + @test isnothing(fmi2FreeInstance(dlsym(lib, :fmi2FreeInstance), component)) + +end + +function test_generic_io(lib, cblibpath, type::fmi2Type) + component = fmi2Instantiate(dlsym(lib, :fmi2Instantiate), pointer("test_generic_io"), type, pointer("{95a6399d-38c5-4504-b3f3-98319bd94ce6}"), pointer("file:///"), Ptr{fmi2CallbackFunctions}(pointer_from_objref(get_callbacks(cblibpath))), fmi2Boolean(false), fmi2Boolean(false)) + @test component != C_NULL + @test fmi2StatusOK == fmi2SetupExperiment(dlsym(lib, :fmi2SetupExperiment),component, fmi2Boolean(false), fmi2Real(0.0), fmi2Real(0.0), fmi2Boolean(false), fmi2Real(1.0)) + + @test fmi2StatusOK == fmi2EnterInitializationMode(dlsym(lib, :fmi2EnterInitializationMode), component) + + fmireference = [fmi2ValueReference(16777216)] + @test fmi2StatusOK == fmi2SetReal(dlsym(lib, :fmi2SetReal), component, fmireference, Csize_t(1), fmi2Real.([0.8])) + + value = zeros(fmi2Real, 1) + @test fmi2StatusOK == fmi2GetReal!(dlsym(lib, :fmi2GetReal), component, fmireference, Csize_t(1), value) + @test value == fmi2Real.([0.8]) + + fmireference = [fmi2ValueReference(16777217)] + value = zeros(fmi2Integer, 1) + @test fmi2StatusOK == fmi2GetInteger!(dlsym(lib, :fmi2GetInteger), component, fmireference, Csize_t(1), value) + + @test fmi2StatusOK == fmi2SetInteger(dlsym(lib, :fmi2SetInteger), component, fmireference, Csize_t(1), fmi2Integer.([typemin(fmi2Integer)])) + + value = zeros(fmi2Integer, 1) + @test fmi2StatusOK == fmi2GetInteger!(dlsym(lib, :fmi2GetInteger), component, fmireference, Csize_t(1), value) + @test value == fmi2Integer.([typemin(fmi2Integer)]) + + fmireference = [fmi2ValueReference(16777218)] + @test fmi2StatusOK == fmi2SetBoolean(dlsym(lib, :fmi2SetBoolean), component, fmireference, Csize_t(1), fmi2Boolean.([false])) + + value = zeros(fmi2Boolean, 1) + @test fmi2StatusOK == fmi2GetBoolean!(dlsym(lib, :fmi2GetBoolean), component, fmireference, Csize_t(1), value) + @test value == fmi2Boolean.([false]) + + fmireference = [fmi2ValueReference(134217728)] + + value = ["anything"] + valueptr = pointer.(value) + @test fmi2StatusOK == fmi2SetString(dlsym(lib, :fmi2SetString), component, fmireference, Csize_t(1), valueptr) + + value = Vector{fmi2String}(undef, 1) + values = string.(zeros(1)) + @test fmi2StatusOK == fmi2GetString!(dlsym(lib, :fmi2GetString), component, fmireference, Csize_t(1), value) + values[:] = unsafe_string.(value) + @test values == ["anything"] + +end \ No newline at end of file diff --git a/test/FMI2/utils.jl b/test/FMI2/utils.jl new file mode 100644 index 0000000..7caae0b --- /dev/null +++ b/test/FMI2/utils.jl @@ -0,0 +1,165 @@ +# +# Copyright (c) 2021 Tobias Thummerer, Lars Mikelsons, Josef Kircher +# Licensed under the MIT license. See LICENSE file in the project root for details. +# +using ZipFile + + +function getFMU(fmuname::String="BouncingBallGravitySwitch1D") + if fmuname == "IO" + dlurl = "https://github.com/ThummeTo/FMIZoo.jl/raw/main/models/bin/Dymola/2023x/2.0/IO.fmu" + else + dlurl = "https://github.com/ThummeTo/FMIZoo.jl/raw/main/models/bin/Dymola/2023x/2.0/BouncingBallGravitySwitch1D.fmu" + fmuname = "BouncingBallGravitySwitch1D" + end + dlpath = Downloads.download(dlurl) + unpackPath = mktempdir(; prefix="fmijl_", cleanup=true) + unzippedPath = joinpath(unpackPath, fmuname) + unzippedAbsPath = isabspath(unzippedPath) ? unzippedPath : joinpath(pwd(), unzippedPath) + numFiles = 0 + if !isdir(unzippedAbsPath) + mkpath(unzippedAbsPath) + + zarchive = ZipFile.Reader(dlpath) + for f in zarchive.files + fileAbsPath = normpath(joinpath(unzippedAbsPath, f.name)) + if endswith(f.name,"/") || endswith(f.name,"\\") + mkpath(fileAbsPath) # mkdir(fileAbsPath) + @assert isdir(fileAbsPath) ["fmi2Unzip(...): Can't create directory `$(f.name)` at `$(fileAbsPath)`."] + else + # create directory if not forced by zip file folder + mkpath(dirname(fileAbsPath)) + + numBytes = write(fileAbsPath, read(f)) + + if numBytes == 0 + @debug "fmi2Unzip(...): Written file `$(f.name)`, but file is empty." + end + + @assert isfile(fileAbsPath) ["fmi2Unzip(...): Can't unzip file `$(f.name)` at `$(fileAbsPath)`."] + numFiles += 1 + end + end + close(zarchive) + end + unzippedAbsPath +end + + +function get_os_binaries(fmuname::String="BouncingBallGravitySwitch1D") + path = getFMU(fmuname) + binarypath = joinpath(path, "binaries") + if Sys.WORD_SIZE == 64 + if Sys.islinux() + binarypath = joinpath(binarypath, "linux64") + cblibpath = Downloads.download("https://github.com/ThummeTo/FMIImport.jl/raw/main/src/FMI2/callbackFunctions/binaries/linux64/libcallbackFunctions.so") + os_supported = true + elseif Sys.iswindows() + binarypath = joinpath(binarypath, "win64") + cblibpath = Downloads.download("https://github.com/ThummeTo/FMIImport.jl/raw/main/src/FMI2/callbackFunctions/binaries/win64/callbackFunctions.dll") + cblibpath = cblibpath * "." + os_supported = true + elseif Sys.isapple() + binarypath = joinpath(binarypath, "darwin64") + cblibpath = Downloads.download("https://github.com/ThummeTo/FMIImport.jl/raw/main/src/FMI2/callbackFunctions/binaries/darwin64/libcallbackFunctions.dylib") + os_supported = false # the FMU we are testing with only contains Binaries for win<32,64> and linux64 + else + os_supported = false + end + elseif Sys.iswindows() + binarypath = joinpath(binarypath, "win32") + cblibpath = Downloads.download("https://github.com/ThummeTo/FMIImport.jl/raw/main/src/FMI2/callbackFunctions/binaries/win32/callbackFunctions.dll") + cblibpath = cblibpath * "." + os_supported = true + else + os_supported = false + end + if !os_supported + @warn "The OS or Architecture used for Testing is not compatible with the FMU used for Testing." + binarypath = "" + cblibpath = "" + else + binarypath = joinpath(binarypath, fmuname) + perm = filemode(cblibpath) + permRWX = 16895 + if perm != permRWX + chmod(cblibpath, permRWX; recursive=true) + end + end + (binarypath, path, cblibpath) +end + +mutable struct FMU2ComponentEnvironment + logStatusOK::Bool + logStatusWarning::Bool + logStatusDiscard::Bool + logStatusError::Bool + logStatusFatal::Bool + logStatusPending::Bool + + function FMU2ComponentEnvironment() + inst = new() + inst.logStatusOK = true + inst.logStatusWarning = true + inst.logStatusDiscard = true + inst.logStatusError = true + inst.logStatusFatal = true + inst.logStatusPending = true + return inst + end +end + +function get_callbacks(cblibpath) + compEnv = FMU2ComponentEnvironment() + ptrComponentEnvironment = Ptr{FMU2ComponentEnvironment}(pointer_from_objref(compEnv)) + callbacklib = dlopen(cblibpath) + ptrLogger = dlsym(callbacklib, :logger) + callbackFunctions = fmi2CallbackFunctions(ptrLogger, C_NULL, C_NULL, C_NULL, ptrComponentEnvironment) + callbackFunctions +end + +function fmi2StatusToString(status::Union{fmi2Status, Integer}) + if status == fmi2StatusOK + return "OK" + elseif status == fmi2StatusWarning + return "Warning" + elseif status == fmi2StatusDiscard + return "Discard" + elseif status == fmi2StatusError + return "Error" + elseif status == fmi2StatusFatal + return "Fatal" + elseif status == fmi2StatusPending + return "Pending" + else + @assert false "fmi2StatusToString($(status)): Unknown FMU status `$(status)`." + end +end +export fmi2StatusToString + +function fmi2CallbackLogger(_componentEnvironment::Ptr{FMU2ComponentEnvironment}, + _instanceName::Ptr{Cchar}, + _status::Cuint, + _category::Ptr{Cchar}, + _message::Ptr{Cchar}) + + message = unsafe_string(_message) + category = unsafe_string(_category) + status = fmi2StatusToString(_status) + instanceName = unsafe_string(_instanceName) + componentEnvironment = unsafe_load(_componentEnvironment) + + if status == fmi2StatusOK && componentEnvironment.logStatusOK + @info "[$status][$category][$instanceName]: $message" + elseif (status == fmi2StatusWarning && componentEnvironment.logStatusWarning) || + (status == fmi2StatusPending && componentEnvironment.logStatusPending) + @warn "[$status][$category][$instanceName]: $message" + elseif (status == fmi2StatusDiscard && componentEnvironment.logStatusDiscard) || + (status == fmi2StatusError && componentEnvironment.logStatusError) || + (status == fmi2StatusFatal && componentEnvironment.logStatusFatal) + @error "[$status][$category][$instanceName]: $message" + end + + return nothing +end + diff --git a/test/FMI3/cfunc.jl b/test/FMI3/cfunc.jl index e83d334..466d8d8 100644 --- a/test/FMI3/cfunc.jl +++ b/test/FMI3/cfunc.jl @@ -3,4 +3,4 @@ # Licensed under the MIT license. See LICENSE file in the project root for details. # -# [ToDo] tests for FMI3 +# [ToDo] tests for FMI3 \ No newline at end of file diff --git a/test/Project.toml b/test/Project.toml index da83f97..ec121db 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,5 +1,9 @@ [deps] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +ZipFile = "a5390f91-8eb1-5f08-bee0-b1d1ffed6cea" +Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" +Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb" [compat] julia = "1.6" diff --git a/test/runtests.jl b/test/runtests.jl index 2a6df73..1dcae87 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -5,6 +5,7 @@ using FMICore using Test +using Downloads, ZipFile @testset "FMICore.jl" begin @testset "FMI2" begin