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: .dll

  • Win64: _64.dll (recommended), .dll

  • Linux: .so

  • Find 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.so and My_HAWC2_dll.so) in the same folder.
    Note: With thousands of parallel simulations this behaviour may be problematic for the file system. Every use of find-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 filename

  • htc lines starting with continue_in_file

  • Note 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 initstring before calling the initialization one.

*

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;