-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathtutorial_writing_components.tex
433 lines (365 loc) · 20 KB
/
tutorial_writing_components.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
\documentclass[a4paper,pdftex]{article}
\usepackage{array}
\usepackage{mathtools}
\usepackage{hopsantut}
\usepackage{listings}
\usepackage{todonotes}
\usepackage{xcolor}
\newcommand\code[1]{\setlength\emergencystretch{3cm} \texttt{\color{blue} #1}}
\hypersetup{pdfauthor={Robert Braun and Peter Nordin}, pdftitle={Hopsan Tutorial - Writing Component Libraries}, pdfsubject={Hopsan Tutorial}}
\lstset{ %
numbers = left,
numberstyle = \scriptsize, % the style that is used for the line-numbers
backgroundcolor =\color{yellow!10}
}
\begin{document}
\maketitle{Writing Component Libraries}
\section*{Introduction}
This tutorial will show how to create your own component libraries from C++ by using the builtin editor in the Hopsan graphical interface. In the first section, the requirements are listed. In the second section, all files in a component library are explained. Finally, creating a library is demonstrated with a step-by-step guide in section three.
\section*{Requirements}
It is strongly recommended to complete the \textit{Getting Started} tutorial before undertaking this one. Some basic knowledge of programming languages are also recommended.
The following tools are also required:
%
\begin{itemize} %
\itemsep -3pt
\item Hopsan
\item Inkscape (optional, for creating component icons)
\end{itemize} %
\section*{Hopsan Component Libraries}
A component library consists of one or more components that can be loaded from Hopsan. It is written in C++ and then compiled to a shared library file (\texttt{.dll} in Windows or \texttt{.so} in Linux). A complete library constists of the files listen below. First, there is a \textit{library description file} in \texttt{.xml} format, which contains basic information about the library. Second, there is a source code file of the library with the \texttt{.cpp} extension.\vspace{10pt}\\
$\mathrm{Component\ Library} \begin{cases}
\mathtt{\textbf{MyLibrary.xml}} \mathrm{\ -\ Library\ description\ file}\\
\mathtt{\textbf{MyLibrary.cpp}} \mathrm{\ -\ Library\ source\ code}\vspace{7pt} \\
\begin{rcases}
\mathtt{\textbf{MyFirstComponent.hpp}} \\
\mathtt{\textbf{MyFirstComponent.xml}} \\
\mathtt{\textbf{MyFirstComponent.svg}}
\end{rcases} \mathrm{Component}\vspace{7pt} \\
\begin{rcases}
\mathtt{\textbf{MySecondComponent.hpp}} \\
\mathtt{\textbf{MySecondComponent.xml}} \\
\mathtt{\textbf{MySecondComponent.svg}}
\end{rcases} \mathrm{Component} \\
\end{cases}$
\vspace{10pt}\\
Each component in the library consist of three files. The first one has the \texttt{.hpp} extension, and contains the source code of the component. The second is an \texttt{.xml} file, which contains specifications about the component and its appearance. Finally there is a \texttt{.svg} file, which contains the graphical icon.
\subsection*{Library Description File}
The library description file contains general information, such as name, version and source files.
With this file it is possible to recompile libraries from inside Hopsan.
It also works as a project file for editing the library.
It can for example look like this:
\begin{minipage}{\linewidth}
\begin{lstlisting}[language=xml, basicstyle=\small\ttfamily]
<?xml version="1.0" encoding="UTF-8"?>
<hopsancomponentlibrary version="0.3">
<id>34992911-0c43-485a-abd8-ccf754bac212</id>
<name>LibName</name>
<lib>LibName</lib>
<source>LibName.cpp</source>
</buildflags>
<component>Component1.hpp</component>
<component>Component2.hpp</component>
<componentxml>MyComponent1.xml</componentxml>
<componentxml>MyComponent2.xml</componentxml>
</hopsancomponentlibrary>
\end{lstlisting}
\end{minipage}
\noindent The first line contains general information about the XML format. It always looks the same. The remaining tags are explained below:
\begin{itemize}
\item \textbf{hopsancomponentlibrary} - Main tag for a component library
\begin{itemize}
\item \textbf{version} - Version of the library xml, should be 0.3 with this XML structure
\end{itemize}
\item \textbf{id} - Unique identifier for the library. Can be any random text.
\item \textbf{name} - Name of the library to be shown in Hopsan
\item \textbf{lib} - Base file name of the library file (without prefix, suffix and extension)
\item \textbf{source} - Source file used to compile the library
\item \textbf{buildflags} - Define custom flags for the compilation, unused in this tutorial
\item \textbf{component} - Source file for a component in the library
\item \textbf{componentxml} - Appearance \texttt{.xml} file for a component in the library
\end{itemize}
\subsection*{Library Source File}
The library source file can be viewed as the wrapper file, that takes the source code for each component and puts them together to a library.
It contains code that is used to register you library components in the HopsanCore, and provides general information about the library for Hopsan.
\begin{minipage}{\linewidth}
\begin{lstlisting}[basicstyle=\footnotesize\ttfamily]
#include "Component1.hpp"
#include "Component2.hpp"
#include "ComponentEssentials.h"
using namespace hopsan;
extern "C" DLLEXPORT void register_contents(ComponentFactory* pComponentFactory,
NodeFactory* pNodeFactory)
{
pComponentFactory->registerCreatorFunction("Component1", Component1::Creator);
pComponentFactory->registerCreatorFunction("Component2", Component2::Creator);
HOPSAN_UNUSED(pNodeFactory)
}
extern "C" DLLEXPORT void get_hopsan_info(...)
{
pHopsanExternalLibInfo->libName = (char*)"LibName";
pHopsanExternalLibInfo->hopsanCoreVersion = (char*)HOPSANCOREVERSION;
pHopsanExternalLibInfo->libCompiledDebugRelease = (char*)HOPSAN_BUILD_TYPE_STR;
}
\end{lstlisting}
\end{minipage}
\newpage
\noindent First of all, all component code files must be included. This is shown in lines 1 and 2 in the example below.
Second, each component needs to be registered in the HopsanCore by the \code{registerCreatorFunction()}, as shown in lines lines 10 and 11.
The first argument must be a unique \textit{type name} that identifies you component.
The second argument is the \code{Creator()} function, which must exist in each component.
Finally, the \code{get\_hopsan\_info()} function must provide a unique \textit{library name}, here called "LibName" at line 18.
The next two lines should be left as they are.
\subsection*{Component Source Files}
Components are written in C++ files with the .hpp file extension.
We will now go through the fundamental pars of a component file.\\
\begin{minipage}{\linewidth}
\begin{lstlisting}[basicstyle=\footnotesize\ttfamily]
#ifndef LAMINARORIFICE_H
#define LAMINARORIFICE_H
#include "ComponentEssentials.h"
#include "ComponentUtilities.h"
namespace hopsan {
class LaminarOrifice : public ComponentQ
{
private:
double *mpP1_p1, *mpP1_q1, *mpP1_c1, *mpP1_Zc1;
double *mpP2_p2, *mpP2_q2, *mpP2_c2, *mpP2_Zc2, *mpKc;
Port *mpP1, *mpP2;
public:
...
\end{lstlisting}
\end{minipage}
\noindent The first two lines sets a header guard to avoid including the same code twice.
Then we include the essential functions for the component from \texttt{ComponentEssentials.h}, and all built-in utilities from \texttt{ComponentUtilities.h}.
You may also include other external header files if you wish to use functions from external libraries.\\
\newline
On line 9 the component class is declared.
We inherit from the \texttt{ComponentQ} class, since this is a Q-type component.
Similarly we would have used \texttt{ComponentC} for C-type or \texttt{ComponentSignal} for signal type.\\
\newline
The first contents in the class is the private variables, which will be persistent in the component.
In this case we have nine node data pointers and two ports.
A pointer is a variable that points to another variable located somewhere else.
It is used in components to improve performance.
After the private section, the public section begins.
Public variables and functions can be accessed by the outside world, while private are only allowed to be accessed from functions belonging to the class.
\begin{minipage}{\linewidth}
\begin{lstlisting}[firstnumber=18, basicstyle=\footnotesize\ttfamily]
static Component *Creator()
{
return new LaminarOrifice();
}
\end{lstlisting}
\end{minipage}
\noindent In the public part we first define a static creator function, which is used to create instances of the component in the simulation core.
Nothing needs to be changed except the name of the class.
\begin{minipage}{\linewidth}
\begin{lstlisting}[firstnumber=22, basicstyle=\footnotesize\ttfamily]
void configure()
{
mpP1 = addPowerPort("P1", "NodeHydraulic");
mpP2 = addPowerPort("P2", "NodeHydraulic");
addInputVariable("Kc","Coefficient","m^5/Ns", 1.0e-11, &mpIn);
}
\end{lstlisting}
\end{minipage}
\noindent The second function you need to define is the \texttt{configure()} function for the component.
This function is run every time a new instance of the component is added to the model.
The function is used to to register ports, input variables, output variables and constants, and to configure default values for persistant variables.
First we create the ports used for communication with the surrounding components, in this case two hydraulic power ports, see line 24 and 25.
Then on line 26 we register the restrictor coefficient as an input variable with name, description, unit and default value.
Input variables can be used either as signal inputs or as parameters.
It is also possible to add constant parameters and output variables using the \texttt{addConstant()} and \texttt{addOutputVariable()} functions.
\begin{minipage}{\linewidth}
\begin{lstlisting}[firstnumber=28, basicstyle=\footnotesize\ttfamily]
void initialize()
{
mpND_p1 = getSafeNodeDataPtr(mpP1, NodeHydraulic::Pressure);
mpND_q1 = getSafeNodeDataPtr(mpP1, NodeHydraulic::Flow);
mpND_c1 = getSafeNodeDataPtr(mpP1, NodeHydraulic::WaveVariable);
mpND_Zc1 = getSafeNodeDataPtr(mpP1, NodeHydraulic::CharImpedance);
mpND_p2 = getSafeNodeDataPtr(mpP2, NodeHydraulic::Pressure);
mpND_q2 = getSafeNodeDataPtr(mpP2, NodeHydraulic::Flow);
mpND_c2 = getSafeNodeDataPtr(mpP2, NodeHydraulic::WaveVariable);
mpND_Zc2 = getSafeNodeDataPtr(mpP2, NodeHydraulic::CharImpedance);
}
\end{lstlisting}
\end{minipage}
\noindent The next member function that must be defined is the initialize function.
This function is run once before each simulation starts.
As this function is run after connections have been establish you can read or write to/from connected components.
If needed you can use this information to initialize your component properly.
This is also the place to allocate additional memory if needed.
In this case we initialize the component by using the \texttt{getSafeNodeDataPtr()} function to set the node data pointers to point to the variables in the node.
These lines are always the same for hydraulic nodes, and similar for other node types such as mechanical, pneumatic and electric.
\begin{minipage}{\linewidth}
\begin{lstlisting}[firstnumber=40, basicstyle=\footnotesize\ttfamily]
void simulateOneTimestep()
{
double p1, q1, c1, Zc1, p2, q2, c2, Zc2;
//Get variable values from nodes
c1 = (*mpND_c1);
Zc1 = (*mpND_Zc1);
c2 = (*mpND_c2);
Zc2 = (*mpND_Zc2);
Kc = (*mpND_Kc);
//Orifice equations
q2 = Kc*(c1-c2)/(1.0+Kc*(Zc1+Zc2));
q1 = -q2;
p1 = c1 + q1*Zc1;
p2 = c2 + q2*Zc2;
//Write new variables to nodes
(*mpND_p1) = p1;
(*mpND_q1) = q1;
(*mpND_p2) = p2;
(*mpND_q2) = q2;
}
\end{lstlisting}
\end{minipage}
\noindent The next function, \texttt{simulateOneTimestep()}, is the most important function.
It contains the model equations that are executed each time step.
We begin on line 42 by creating a local variable for each node data pointer.
The purpose of this is to make the code more readable.
The node data pointers could have been used directly, but this would have been difficult to understand.
Then on line 45-49 we assign all input variables with the values from their respective node data pointer.
Line 52-55 consists of the actual equations.
In this case we calculate flow and pressure through the orifice from wave variables and impedance in the neighboring C-type components.
We then end by assigning the output node data pointers with the value of their respective local variable.
\begin{minipage}{\linewidth}
\begin{lstlisting}[firstnumber=63, basicstyle=\footnotesize\ttfamily]
void finalize()
{
//Finalize code
}
void deconfigure()
{
//Deconfigure code
}
\end{lstlisting}
\end{minipage}
\noindent The next function, \texttt{finalize()}, is optional.
It is only useful if you want some code to be run after each simulation has finished.
This is usually only needed if you want to free memory that was additionally allocated in the initialize function.\\
\newline
The last function, \texttt{deconfigure()}, is also optional.
This code is run once the component is deleted.
Here you can cleanup any memory allocation or similar that you have done in the configure function.
\subsection*{Component Appearance Files}
Information about the component for the graphical interface, such as icon, port positions and component name, is stored in a \texttt{.xml} file. This file contains information about the component that is not part of the actual simulation code. This information can be changed without the need to recompile the actual component code. A typical appearance file looks like this:
\begin{minipage}{\linewidth}
\begin{lstlisting}[basicstyle=\small\ttfamily]
<?xml version="1.0" encoding="UTF-8"?>
<hopsanobjectappearance version="0.3">
<modelobject typename="LaminarOrifice" displayname="Orifice"
sourcecode="LaminarOrifice.hpp">
<icons>
<icon type="user" path="orifice.svg"
scale="1.0" iconrotation="ON" />
</icons>
<help>
<text>Help Text</text>
<picture>helpPicture.svg</picture>
<link>externalHelpDocumentation.pdf</link>
</help>
<ports>
<port x="1,0" y="0.5" a="0" name="P1" visible="true"//>
<port x="0,0" y="0.5" a="180" name="P2" visible="true"//>
<port x="0.5" y="0,0" a="270" name="Kc" visible="false"//>
</ports>
</modelobject>
</hopsanobjectappearance>
\end{lstlisting}
\end{minipage}
The first line contains basic information about the XML code, and should always look the same.
A description of the remaining tags follows:
\begin{itemize}
\item \textbf{hopsanobjectappearance} - Main tag for appearance file
\begin{itemize}
\item \textbf{version} - Should always be 0.3 with this XML layout
\end{itemize}
\item \textbf{modelobject} - Main tag for the component
\begin{itemize}
\item \textbf{typename} - Unique type name of the component
\item \textbf{subtypename} - Specific version of a type name component (optional)
\item \textbf{displayname} - Name for the component shown in the graphical interface
\end{itemize}
\item \textbf{icons} - Contains information about icons. At least one type, user or iso is required
\begin{itemize}
\item \textbf{type} - The icon type, user or iso (for ISO 1219 graphics)
\item \textbf{path} - Relative path from the \texttt{.xml} file to the \texttt{.svg} icon
\item \textbf{scale} - Lets you adjust the scale of the \texttt{.svg} icon (default = 1.0)
\item \textbf{iconrotation} - Tells whether or not the icon rotates when the component is rotated
\end{itemize}
\item \textbf{help} - Allows you to specify help information about the component (optional)
\begin{itemize}
\item \textbf{text} - The help text (optional)
\item \textbf{picture} - The path to the \texttt{.svg} help picture (optional)
\item \textbf{link} - Link to external document relative this \texttt{.xml} file (optional)
\end{itemize}
\item \textbf{ports} - Defines the positions and orientations for each port
\begin{itemize}
\item \textbf{name} - Name of the port as defined in the code
\item \textbf{x} - X-position as fraction of component icon width (0.0 = left, 1.0 = right)
\item \textbf{y} - Y-position as fraction of component icon height (0.0 = top, 1.0 = bottom)
\item \textbf{a} - Angle of port, 0 = right, 90 = down, 180 = left, 270 = up
\item \textbf{visible} - Default visibility state
\end{itemize}
\end{itemize}
\subsection*{Component Icon Files}
Icons for the graphical interface are stored in the \texttt{.svg} (Scalable Vector Graphics) format. A good and free tool for creating and editing such files is \textit{Inkscape}. If you want to use bitmaps graphics, \texttt{.jpg}, \texttt{.png} or similar formats, such graphics can be embedded in a \texttt{.svg} file.
\section*{Example}
We shall now demonstrate how to build a simple library using the built-in editing capabilities in Hopsan. The library will contain only one simple arithmetic component.
\begin{tutenumerate}
\tutitem{Open the Hopsan Graphical User Interface}
Start Hopsan by running hopsangui.exe, located in the \texttt{bin} folder under the installation directory.
\tutitem{Configure compiler path}
First, we must configure the compiler. Click on he options icon:
\icon{0}{gfx/Hopsan-Options.png}{Options}
Hopsan needs access to either a 32-bit or a 64-bit compiler, depending on the Hopsan architecture.
Click on the "Compilers" tab.
Then make sure a required compiler, or the included compiler, is specified. It should look something like this:
\includegraphics{gfx/writingcomponents/options.png}
\tutitem{Create a new library}
Now we will create a new empty library.
Click on \texttt{Create external library} in the library widget to the left in the program:
\icon{0}{gfx/Hopsan-Add.png}{Create external library}
A dialog appears that asks you to name the library. Give the library a name without spaces, for exampe "MyComponentLibrary":
\includegraphics[scale=0.85]{gfx/writingcomponents/newlibrarydialog.png}
When clicking "OK", the program asks you to choose a folder for the library. Make sure the folder you choose is empty.
Two files have now been created in the folder, one \texttt{.xml} file and one .cpp file.
\tutitem{Add a component}
A component library is not very useful without any components.
We now want to create a component that solves the equation $y = A*x+b$.
Right-click on the component library and choose "Add New Component".
A component generator dialog appears.
We need to choose a name.
We also need 2 constants ($A$ and $B$), 1 input variable ($x$) and 1 output variable ($y$).
Variables are added by clicking on the green plus icons to the right.
Give $A$ and $B$ the default values 1 and 0.
This means that with default values, $y = x$.
The dialog should then look like this:
\includegraphics[width=\linewidth]{gfx/writingcomponents/newcomponent.png}
Ignore units and descriptions for now and click "OK".
Two new files will be created, one \texttt{.hpp} and one \texttt{.xml}.
\tutitem{Write component code}
Now it is time to implement the equation in the component.
The source code file \texttt{MyComponent.hpp} should have opened automatically. If not, right-click on the component and choose "Edit Source Code".
As you can see, all functions mentioned in the previous chapter has been generated.
In this case we only care about the \code{simulateOneTimeStep()} function.
Add the following under the line "//Simulation code":
\code{y = mA*x+mB;}
Note that in the code, the constants A and B are renamed to \code{mA} and \code{mB}. This is to indicate that they are \textit{member} variables of the class.
Remember the semicolon!
C++ requires each line to end with a semicolon.\\
\newline\noindent
The component is now ready to be compiled.
It is possible to modify the port positions in the \texttt{.xml} file, and to create a graphical icon.
Let's ignore that for now.
\tutitem{Compile library}
Right-click on the library in the list to the left and select "Recompile". If compiler path is correct and there are no mistakes in the code, the library should now be successfully compiled. Otherwise, look for errors in the compilation dialog.
\tutitem{Test your new component}
Build a new model and verify that your own first component works!
\vspace{12pt}
\includegraphics[width=0.6\textwidth]{gfx/writingcomponents/testmodel.png}
\end{tutenumerate}
\end{document}