DLL control#
This block contains the possible Dynamic Link Library formats accessible for the user. The DLL’s are mainly used to control the turbine speed and pitch, but since the DLL format is very general, other use is possible too e.g. external loading of the turbine. Since the HAWC2 core has no information about external stiffness or inertia we have experienced some issues with the solver if the DLL includes high stiffness terms or especially large inertia terms. The new type2_dll interface is slightly more stable related to the solver than the hawc_dll interface.
Main command block – dll#
There are two DLL mechanisms available: hawc_dll and type2_dll. Both have two different interfaces (as documented in more detailed in the following sections Sub command block – hawc_dll and Sub command block – type2_dll) and have one other important distinction: a hawc_dll is updated in each aero-structure iteration, i.e. typically multiple times per time step while the type2_dll is only updated once per time step.
Important note about DLL file names#
For both DLL interfaces the user needs to refer to the location of the specific DLL in use. Since version 12.9 HAWC2 is available for 3 different architectures (Windows 32-bit, Windows 64-bit and Linux 64-bit). To facilitate easy use of the same htc file across the different architectures, the intention is that with a single htc input file a user should be able to run on win32, win64 and linux without modifications. To this end, HAWC2 is using the following strategy:
Determine what file name extension to use:
Win32:
.dllWin64:
_64.dll(recommended),.dllLinux:
.soFind the correct path of the dll:
Absolute path (if the absolute path is specified)
Relative path relative to:
* Current working directory (cwd)
* The location of the HAWC2 executable
On Linux, paths and file names are case sensitive (in contrast to Windows). Functionality to mimic the Windows behaviour on Linux has therefore been added. This functionality tries the following:
Load the exact specified filename (Note the automatic conversion to lower case and the exceptions described below)
Find the first filename that case-insensitively matches the specified filename. This is done using
find /my/dir -maxdepth 1 -type f -ipath '*my_dll_name.so'.
Note: “first” may be arbitrary. Hence, avoid to have multiple files with the same name except for their case (e.g.my_hawc2_dll.soandMy_HAWC2_dll.so) in the same folder.
Note: With thousands of parallel simulations this behaviour may be problematic for the file system. Every use offind-command is therefore printed to the log file and in case the usage can be avoided by specifying the correct case-sensitive filename a warning is printed too.All input in the htc file(s) are converted to lower case with the following exceptions:
single-quoted strings, e.g.
'dont_CHANGE_case.dll'htc lines starting with
filenamehtc lines starting with
continue_in_fileNote that the log file will always report which files have been loaded so in case of doubt inspect that.
Each DLL needs to be compiled for each of the three different platforms independently, but with this functionality, the same input htc file, e.g.
...
begin dll;
begin type2_dll;
name 'MyDLL';
filename ./my_folder/MyDLL.dll ;
...
end type2_dll;
end dll;
...
will load and use the correct dll on all platforms if the three files, MyDLL.dll (win32 compilation), MyDLL_64.dll (win64 compilation) and MyDLL.so (linux compilation) is put in my_folder.
Sub command block – hawc_dll#
In the hawc_dll format a subroutine within an externally written DLL is setup. In this subroutine call two one-dimensional arrays are transferred between the HAWC2 core and the DLL procedure. The first contains data going from the HAWC2 core to the DLL and the other contains data going from the DLL to the core. It is very important to notice that the data is transferred between HAWC2 and the DLL in every time step and every iteration. The user should handle the iteration inside the DLL.
Two more subroutines are called if they are present inside the dll file:
The first is an initialisation call including a text string written in the init_string in the commands below. This could be the name of a file holding local input parameters to the data transfer subroutine. This call is only performed once. The name of this subroutine is the same name as the data transfer subroutine defined with the command dll_subroutine below with the extra name ’_init’, hence is the data transfer subroutine is called ’test’, the initialisation subroutine will be ’test_init’.
The second subroutine is a message exchange subroutine, where messages written in the DLL can be send to the HAWC2 core for logfile writing. The name of this subroutine is the same name as the data transfer subroutine defined with the command dll_subroutine below with the extra name ’_message’, hence is the data transfer subroutine is called ’test’, the initialisation subroutine will be ’test_message’.
The command block can be repeated as many times as desired. Reference number to DLL is same order as listed, starting with number 1. However it is recommended to refer the DLL using the name feature which in many cases can avoid confusion.
Obl. |
Command name |
Explanation |
|---|---|---|
name |
1. Reference name of this DLL (to be used with DLL output commands) |
|
* |
filename |
1. Filename incl. relative path of the DLL |
(example ./DLL/control.dll) |
||
* |
dll_subroutine |
1. Name of subroutine in DLL that is addressed (remember to specify the name in the DLL with small letters!) |
* |
arraysizes |
1. size of array with outgoing data |
2. size of array with ingoing data |
||
deltat |
1. Time between dll calls. Must correspond to the simulation sample frequency or be a multiple of the time step size. If deltat=0.0 or the deltat command line is omitted the HAWC2 code calls the dll subroutine at every time step. |
|
init_string |
1. Text string (max 256 characters) that will be transferred to the DLL through the subroutine ’subroutine_init’. Subroutine is the name given in in the command dll_subroutine. No blanks can be included. |
Sub command block – type2_dll#
This dll interface is an updated slightly modified version of the hawc_dll interface. In the type2_dll format a subroutine within an externally written DLL is setup. In this subroutine call two one-dimensional arrays are transferred between the HAWC2 core and the DLL procedure. The first contains data going from the HAWC2 core to the DLL and the other contains data going from the DLL to the core. It is very important to notice that the data are transferred between HAWC2 and the DLL in the first call of every time step where the out-going variables are based on last iterated values from previous time step. The sub command output and actions are identical for both the hawc_dll and the type2_dll interfaces.
In the dll connected with using the type2_dll interface two subroutines should be present. An initialization routine called only once before the time simulation begins, and an update routine called in every time step. The format in the calling of these two subroutines are identical where two arrays of double precision is exchanged. The subroutine uses the cdecl calling convention.
Obl. |
Command name |
Explanation |
|---|---|---|
name |
1. Reference name of this DLL (to be used with DLL output commands) |
|
* |
filename |
1. Filename incl. relative path of the DLL |
(example ./DLL/control.dll) |
||
* |
dll_subroutine_init |
1. Name of initialization subroutine in DLL that is addressed (remember to specify the name in the DLL with small letters!) |
* |
dll_subroutine_update |
1. Name of subroutine in DLL that is addressed at every time step (remember to specify the name in the DLL with small letters!) |
init_string |
1. String that HAWC2 will pass to a subroutine named |
|
* |
arraysizes_init |
1. size of array with outgoing data in the initialization call |
2. size of array with ingoing data in the initialization call |
||
* |
arraysizes_update |
1. size of array with outgoing data in the update call |
2. size of array with ingoing data in the update call |
||
deltat |
1. Time between dll calls. Must correspond to the simulation sample frequency or be a multiple of the time step size. If deltat=0.0 or the deltat command line is omitted the HAWC2 code calls the dll subroutine at every time step. |
when using the type2_dll interface the values transferred to the DLL in the initialization phase is done using a sub command block called init. The commands for this subcommand block is identical to the output subcommand explained below, but only has the option of having the constant output sensor available. An example is given for a small dll that is used for converting rotational speed between high speed and low speed side of a gearbox:
begin dll;
begin type2_dll;
name hss_convert;
filename ./control/hss_convert.dll ;
arraysizes_init 3 1 ;
arraysizes_update 2 2 ;
begin init;
constant 1 2.0 ; number of used sensors - in this case only 1
constant 2 35.110; gearbox ratio
constant 3 35.110; gearbox ratio
end init;
begin output;
constraint bearing1 shaft_rot 2 only 2 ; rotor speed in rpm
constraint bearing1 shaft_rot 3 only 2 ; rotor speed in rad/s
end output;
;
begin actions;
; rotor speed in rpm * gear_ratio
; rotor speed in rad/s * gear_ratio
end actions;
end type2_dll;
end dll;
Sub command block - init#
In this block type2_dlls can be initialized by passing constants to specific channels.
Obl. |
Command name |
Explanation |
|---|---|---|
* |
constant |
Constants passed to the dll. |
1. Channel number |
||
2. Constant value |
Sub command block – output#
In this block the same sensors are available as when data results are written to a file with the main block command output, see section Output. The order of the sensors in the data array is continuously increased as more sensors are added.
Sub command block – actions#
In this command block variables inside the HAWC2 code is changed depending of the specifications. This command block can be used for the hawc_dll interface as well as the type2_dll interface. An action commands creates a handle to the HAWC2 model to which a variable in the input array from the DLL is linked.
!NB in the command name two separate words are present.
Obl. |
Command name |
Explanation |
|---|---|---|
aero beta |
The flap angle beta is set for a trailing edge flap section (if the mhhmagf stall model is used). The angle is positive towards the pressure side of the profile. Unit is [deg] |
|
1. Blade number |
||
2. Flap section number |
||
aero bem_grid_a |
1. Number of points |
|
body force_ext |
An external force is placed on the structure. Unit is [N]. |
|
1. body name |
||
2. node number |
||
3. component (1 = \(F_x\), 2 = \(F_y\), 3 = \(F_z\)) |
||
body moment_ext |
An external moment is placed on the structure. Unit is [Nm]. |
|
1. body name |
||
2. node number |
||
3. component (1 = \(M_x\), 2 = \(M_y\), 3 = \(M_z\)) |
||
body force_int |
An external force with a reaction component is placed on the structure. Unit is [N]. |
|
1. body name for action force |
||
2. node number |
||
3. component (1 = \(F_x\), 2 = \(F_y\), 3 = \(F_z\)) |
||
4. body name for reaction force |
||
5. Node number |
||
body moment_int |
An external moment with a reaction component is placed on the structure. Unit is [N]. |
|
1. body name for action moment |
||
2. node number |
||
3. component (1 = \(M_x\), 2 = \(M_y\), 3 = \(M_z\)) |
||
4. body name for reaction moment |
||
5. Node number |
||
body bearing_angle |
A bearing either defined through the new structure format through bearing2 or through the old structure format (spitch1=pitch angle for blade 1, spitch2=pitch angle for blade 2,…). The angle limits are so far [0-90deg]. |
|
1. Bearing name |
||
mbdy force_ext |
An external force is placed on the structure. Unit is [N]. |
|
1. main body name |
||
2. node number on main body |
||
3. component (1 = \(F_x\), 2 = \(F_y\), 3 = \(F_z\)), if negative number the force is inserted with opposite sign. |
||
4. coordinate system (possible options are: mbdy name, ”global”, ”local”). “local” means local element coo on the inner element (on the element indexed 1 lower that the node number). One exception if node number =1 then the element nr. also equals 1. |
||
mbdy moment_ext |
An external moment is placed on the structure. Unit is [Nm]. |
|
1. main body name |
||
2. node number on main body |
||
3. component (1 = \(M_x\), 2 = \(M_y\), 3 = \(M_z\)), if negative number the moment is inserted with opposite sign. |
||
4. coordinate system (possible options are: mbdy name,”global”,”local”). “local” means local element coo on the inner element (on the element indexed 1 lower that the node number). One exception if node number =1 then the element nr. also equals 1. |
||
mbdy force_int |
An internal force with a reaction component is placed on the structure. Unit is [N]. |
|
1. main body name for action force |
||
2. node number on main body |
||
3. component (1 = \(F_x\), 2 = \(F_y\), 3 = \(F_z\)), if negative number the force is inserted with opposite sign. |
||
4. coordinate system (possible options are: mbdy name, ”global”, ”local”). “local” means local element coo on the inner element (on the element indexed 1 lower that the node number). One exception if node number =1 then the element nr. also equals 1. |
||
5. main body name for reaction force |
||
6. Node number on this main body |
||
mbdy moment_int |
An internal force with a reaction component is placed on the structure. Unit is [Nm]. |
|
1. main body name for action moment |
||
2. node number on main body |
||
3. component (1 = \(M_x\), 2 = \(M_y\), 3 = \(M_z\)), if negative number the moment is inserted with opposite sign. |
||
4. coordinate system (possible options are: mbdy name,”global”,”local”). “local” means local element coo on the inner element (on the element indexed 1 lower that the node number). One exception if node number =1 then the element nr. also equals 1. |
||
5. main body name for reaction moment |
||
6. Node number on this main body |
||
constraint bearing2 angle_deg |
The angle of a bearing2 constraint is set. The angle limits are so far [± 90 deg]. |
|
1. Bearing name |
||
constraint bearing3 omegas |
The angular velocity of a bearing3 constraint is set. |
|
1. Bearing name |
||
body printvar |
Variable is just echoed on the screen. No parameters. |
|
body ignore |
1. Number of consecutive array spaces that will be ignored |
|
mbdy printvar |
Variable is just echoed on the screen. No parameters. |
|
mbdy ignore |
1. Number of consecutive array spaces that will be ignored |
|
general printvar |
Variable is just echoed on the screen. No parameters. |
|
general ignore |
1. Number of consecutive array spaces that will be ignored |
|
general stop_simulation |
Logical switch. If value is 1 the simulation will be stopped and output written. |
|
wind printvar |
Variable is just echoed on the screen. No parameters. |
|
wind windspeed_u |
External contribution to wind speed in u-direction [m/s] |
|
wind winddir_deg |
External contribution to the wind direction (turb. box is also rotated) [deg] |
|
wind winddir_rad |
External contribution to the wind direction (turb. box is also rotated). The legacy action wind winddir can also be used to apply a windfield rotation in radians. [rad] |
|
quake comp |
1. Degree of freedom |
|
ext_sys control |
1. Name of external system |
hawc_dll format example written in FORTRAN 90#
subroutine test(n1,array1,n2,array2)
implicit none
!DEC$ ATTRIBUTES DLLEXPORT, ALIAS:'test'::test
integer*4 :: n1, & ! Dummy integer value containing the array size of array1
n2 ! Dummy integer value containing the array size of array2
real*4,dimension(10) :: array1 ! fixed-length array, data from HAWC2 to DLL
! – in this case with length 10
real*4,dimension(5) :: array2 ! fixed-length array, data from DLL to HAWC2
! – in this case with length 5
! Code is written here
end subroutine test
!-------------------------------------------------------
Subroutine test_init(string256)
Implicit none
!DEC$ ATTRIBUTES DLLEXPORT, ALIAS:'test_init'::test_init
Character*256 :: string256
! Code is written here
End subroutine test_init
!-------------------------------------------------------
Subroutine test_message(string256)
Implicit none
!DEC$ ATTRIBUTES DLLEXPORT, ALIAS:'test_message'::test_message
Character*256 :: string256
! Code is written here
End subroutine test_message
hawc_dll format example written in Delphi / Lazarus / Pascal#
library test_dll;
type
array_10 = array[1..10] of single;
array_5 = array[1..5] of single;
ts = array[0..255] of char;
Procedure test(var n1:integer;var array1 : array_10;
var n2:integer;var array2 : array_5);stdcall;
// n1 is a dummy integer value containing the size of array1
// n2 is a dummy integer value containing the size of array2
begin
// Code is written here
end;
//----------------------------------------------------------
Procedure test_init(var string256:ts; length:integer);stdcall;
var
init_str:string[255]
begin
init_str=strpas(string256);
// Code is written here
writeln(init_str);
end;
//----------------------------------------------------------
Procedure test_message(var string256:ts; length:integer);stdcall;
var
message_str:string;
begin
// Code is written here
message_str:='''This is a test message';
strPCopy(string256,message_str);
end;
exports test,test_init,test_message;
begin
writeln('The DLL pitchservo.dll is loaded with success');
// Initialization of variables can be performed here
end;
end.
hawc_dll format example written in C#
extern "C" void __declspec(dllexport) __stdcall test(int size_of_Data_in,
float Data_in[], int size_of_Data_out, float Data_out[])
{
for (int i=0; i<size_of_Data_out; i++) Data_out[i]=0.0;
//
printf("size_of_Data_in %d: \n",size_of_Data_in);
printf("Data_in %g: \n",Data_in[0]);
printf("size_of_Data_out %d: \n",size_of_Data_out);
printf("Data_out %g: \n",Data_out[0]);
}
extern "C" void __declspec(dllexport) __stdcall test_init(char* pString, int length)
{
// Define buffer (make room for NULL-char)
const int max_length = 256;
char buffer[max_length+1];
//
// Print the length of pString
printf("test_init::length = %d\n",length);
//
// Transfer string
int nchar = min(max_length, length);
memcpy(buffer, pString, nchar);
//
// Add NULL-char
buffer[nchar] = '\0';
//
// Print it...
printf("%s\n",buffer);
}
extern "C" void __declspec(dllexport) __stdcall test_message(char* pString, int max_length)
{
// test message (larger than max_length)
char pmessage[] = "This is a test message "
"and it continues and it continues and it continues "
"and it continues and it continues and it continues "
"and it continues and it continues and it continues "
"and it continues and it continues and it continues "
"and it continues and it continues and it continues "
"and it continues and it continues and it continues ";
// Check max length - transfer only up to max_length number of chars
int nchar = min((size_t)max_length, strlen(pmessage)); // nof chars to transfer
// (<= max_length)
memcpy(pString, pmessage, nchar);
//
// Add NULL-char if string space allows it (FORTRAN interprets a NULL-char as
// the end of the string)
if (nchar < max_length) pString[nchar] = '\0';
}
type2_dll written in Delphi / Lazarus / Delphi#
library hss_convert;
uses
SysUtils,
Classes,
Dialogs;
Type
array_1000 = array[0..999] of double;
Var
factor : array of double;
nr : integer;
{$R *.res}
procedure initialize(var InputSignals: array_1000;var OutputSignals: array_1000); cdecl;
var
i : integer;
begin
nr:=trunc(inputsignals[0]);
if nr>0 then begin
setlength(factor,nr);
for i:=1 to nr do
factor[i-1]:=Inputsignals[i];
outputsignals[0]:=1.0;
end else outputsignals[0]:=0.0;
end;
procedure update(var InputSignals: array_1000;var OutputSignals: array_1000); cdecl;
var
i : integer;
begin
for i:=0 to nr-1 do begin
OutputSignals[i] := InputSignals[i]*factor[i];
end;
end;
exports Initialize,Update;
begin
// Main body
end.
type2_dll written in C#
#include <stdio.h>
void __declspec(dllexport) __cdecl initialize(double * Data_in, double * Data_out)
{
for (int i = 0; i < 2; i++) {
Data_out[i] = Data_in[i] * 2 + i;
printf("INIT \n");
printf("Data_in: %f \n", Data_in[i]);
printf("Data out: %f \n", Data_out[i]);
}
}
void __declspec(dllexport) __cdecl update(double * Data_in, double * Data_out)
{
for (int i = 0; i < 2; i++) {
Data_out[i] = Data_in[i] * 2 + i;
printf("Update\n");
printf("Data_in: %f \n", Data_in[i]);
printf("Data out: %f \n", Data_out[i]);
}
}
void __declspec(dllexport) __cdecl get_version(char * version)
{
printf("Empty HAWC2 Controller (ver. 0.1)\n");
}
void __declspec(dllexport) __cdecl message(char * message)
{
printf("Message\n");
}
void __declspec(dllexport) __cdecl initstring(char* str)
{
printf("%s\n", str);
}
The compile command on Windows for the example above example is given below, for GCC and Intel c classic respectively:
gcc .\source.c -o ExampleHAWCController.dll -shared
icl .\source.c /LD /FeExampleHAWCController.dll
The compile command for Linux systems is given below, for GCC and Intel c classic. It should be noted that there may be missing dependencies if the compiler used to build the controller is not installed on the system which is running. This is known to happen on Linux systems for GCC.
gcc ./source.c -o ./ExampleHAWCController.so -shared -fPIC
icx ./source.c -o ./ExampleHAWCController.so -shared -fPIC
type2_dll format example written in FORTRAN 90#
subroutine update(array1,array2) bind(C, name="update")
implicit none
!DEC$ ATTRIBUTES DLLEXPORT :: update
!gcc$ attributes DLLEXPORT :: update
!gcc$ attributes cdecl :: update
real*8,dimension(2) :: array1 ! fixed-length array, data from HAWC2 to DLL
! in this case with length 2
real*8,dimension(2) :: array2 ! fixed-length array, data from DLL to HAWC2
! in this case with length 2
! Code is written here
print *, "Update", array1(1)
end subroutine update
!-------------------------------------------------------
Subroutine initialize(array1,array2) bind(C, name="initialize")
use iso_c_binding, only: C_CHAR
Implicit none
!DEC$ ATTRIBUTES DLLEXPORT :: initialize
!gcc$ attributes DLLEXPORT :: initialize
!gcc$ attributes cdecl :: initialize
real*8,dimension(2) :: array1 ! fixed-length array, data from HAWC2 to DLL
! in this case with length 2
real*8,dimension(2) :: array2 ! fixed-length array, data from DLL to HAWC2
! in this case with length 2
! Code is written here
print *, "Initialize", array1(1)
End subroutine initialize
!-------------------------------------------------------
Subroutine message(string256) bind(C, name="message")
use iso_c_binding, only: C_CHAR
Implicit none
!DEC$ ATTRIBUTES DLLEXPORT :: message
!gcc$ attributes DLLEXPORT :: message
!gcc$ attributes cdecl :: message
character(len=256) :: s
Character(kind=C_CHAR) :: string256(256)
integer :: i
! Code is written here
s = "Message from controller DLL"
! copy to C character array
do i=1,256
string256(i) = s(i:i)
enddo
End subroutine message
!-------------------------------------------------------
Subroutine get_version(string256) bind(C, name="get_version")
use iso_c_binding, only: C_CHAR
Implicit none
!DEC$ ATTRIBUTES DLLEXPORT :: get_version
!gcc$ attributes DLLEXPORT :: get_version
!gcc$ attributes CDECL :: get_version
Character(kind=C_CHAR) :: string256(256)
! Code is written here
string256(1:3) = (/"0",".","1"/)
End subroutine get_version
!-------------------------------------------------------
Subroutine initstring(istring) bind(C, name='initstring')
!DEC$ ATTRIBUTES DLLEXPORT :: initstring
use iso_c_binding, only: C_CHAR
Implicit none
!! Print a string before the initialization.
integer(kind=1), dimension(*), intent(in) :: istring
!! Input from HAWC2.
!! The string is passed in decimal format.
!! This is how the interface is defined in HAWC2 and must not be changed.
character(kind=C_CHAR, len=1), dimension(256) :: char_array
!! The whole string from HAWC2, converted to character array.
character(kind=C_CHAR, len=256) :: string
!! The whole string from HAWC2, converted to string.
! Convert decimal to character array.
char_array = char(istring(1:256))
! Convert character array to string.
string = transfer(char_array, string)
! Print the initialization string.
write(*, '(A)') string
end Subroutine initstring
The compile command for the example above example is given below, for GCC and intel fortran classic respectively:
gfortran .\source.f90 \
-o .\ExampleHAWCController.dll -shared -cpp -fno-underscoring
ifort .\source.f90 /FeExampleHAWCController.dll /fpp /dll
The compile command for Linux systems is given below, for GCC and Intel c classic. It should be noted that there may be missing dependencies if the compiler used to build the controller is not installed on the system which is running. This is known to happen on Linux systems for GCC.
gfortran ./source.f90 -o ./ExampleHAWCController.so -shared -fPIC -cpp
ifort ./source.f90 -o ExampleHAWCController.so -shared -fPIC -fpp
In order to import the controller into HAWC2, the two sections should be added to the .htc file. A section similar to the one directly below should be added in the dll section of the htc file.
begin type2_dll;
name empty_hawc_controller ;
filename ./PATH/TO/THE/CUSTOM/CONTROLLER.dll;
;
dll_subroutine_init initialize ;
dll_subroutine_update update ;
;
arraysizes_init 2 2 ;
arraysizes_update 2 2 ;
begin init ;
constant 1 2.3;
constant 2 3;
end init ;
;
begin output ;
general time;
general time;
end output;
end type2_dll;
Additionally, lines should be added in the output section specifying what data from the interface between the controller and HAWC2 is to be saved in the output file. Details on this can be found in the Output chapter (Chapter Output) of the manual.
dll inpvec 6 1 # Data into the controller;
dll outvec 6 1 # Data out of the controller;