Hopsan
Writing The Actual Component Code

Components are written in header-only C++ files (.hpp). In this section we will look at "MyExampleConstantOrifice.hpp" from the exampleComponentLibrary. This is a very simple component that only includes a subset of functions. This is convenient when "getting started" but you should also see ComponentAuthorFunctions for a list of all functions that can be useful when creating components.

Step by Step, MyExampleConstantOrifice.hpp

This part goes through the basic structure of the component file. You should look in the actual file for the full code and more detailed comments. You should also have a look at the MyExampleOrifice.hpp file. The difference between the files illustrate the difference between having a constant parameter or a variable parameter. (Input Variable).

Include Directives and Header Guard

http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
The full license is available in the file LICENSE.
For details about the 'Hopsan Group' or information about Authors and
Contributors see the HOPSANGROUP and AUTHORS files that are located in
the Hopsan source code root directory.
-----------------------------------------------------------------------------*/
// Header guard to avoid inclusion of the same code twice
// Every hpp file in your library need to have its own UNIQUE header guard
#ifndef MYEXAMPLECONSTANTORIFICE_H
#define MYEXAMPLECONSTANTORIFICE_H
// Include the necessary header files from Hopsan
#include "ComponentEssentials.h"

This first lines set a header guard to avoid including the same code twice. Technically you do not need the header guards if you can guarantee that you do not include the same file twice (usually not a problem in component libraries). Then we include the essential functions for the component from the HopsanCore. It may be necessary to include more files, for example "ComponentUtilities.h" for accessing built-in component utilities in Hopsan. You may also include other external header files if you wish to include function from other external libraries.

Class Declaration and Component CQS Type Selection

class MyExampleConstantOrifice : public ComponentQ

Next the component class is declared. We inherit from a ComponentQ as this is a Q-type component. This could also be ComponentC or ComponentSignal for C-type, Signal-type components. It is also possible to inherit ComponentSystem in order to write programmed subsystems. But this is VERY advanced and complicated and will not be covered in the documentation.

Private Class Members

private:
// Private member variables
double mKc;
Port *mpP1, *mpP2;

First the private part of the component is specified. Here we declare member variables, (variables that should be persistent in the component). In this case we have a restriction coefficient called mKc and two port pointers called mpP1 and mpP2 ("m" is an abbreviation of "member" and "mp" means "member and pointer"). The names do not actually matter, but using a naming convention makes the code easier to read.

Public Class Members

public:
// The creator function that is registered when a component lib is loaded into Hopsan
// This static function is mandatory
static Component *Creator()
{
return new MyExampleConstantOrifice();
}

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.

configure()

void configure()
{
// Add ports to the component
mpP1 = addPowerPort("P1", "NodeHydraulic", "Port 1");
mpP2 = addPowerPort("P2", "NodeHydraulic", "Port 2");
// Add constant
addConstant("Kc", "Pressure-Flow Coefficient", "[m^5/Ns]", 1.0e-11, mKc);
}

The second member function you need to define is the configure function for the component. This function is run every time a new object of the class is created (added to the model). The function is used to register ports, input variables, output variables and constants, and to configure default values for member variables. First we create the ports used for communication with the surrounding components, in this case two hydraulic PowerPorts. Finally, we register the restrictor coefficient as constant parameter with a name, description and unit. This will make it available to the user in the graphical interface.

Adding Ports: The following functions are available for port creation: You need to specify a unique (within the component) portname, which nodetype it should use, and whether it must be connected: Port::Required or Port::NotRequired. If the final argument is omitted it will default to Port::Required. Description is optional (but recommended) The currently available built-in node types can be found here: NodeTypes
New node types can be added through external libraries, but this should be avoided if possible.

Port* addPowerPort(const HString &rPortName, const HString &rNodeType, const HString &rDescription, const Port::RequireConnectionEnumT reqConnect=Port::Required);
Port* addReadPort(const HString &rPortName, const HString &rNodeType, const HString &rDescription, const Port::RequireConnectionEnumT reqConnect=Port::Required);
Port* addPowerMultiPort(const HString &rPortName, const HString &rNodeType, const HString &rDescription, const Port::RequireConnectionEnumT reqConnect=Port::Required);
Port* addReadMultiPort(const HString &rPortName, const HString &rNodeType, const HString &rDescription, const Port::RequireConnectionEnumT reqConnect=Port::Required);
Port* addWritePort(const HString &rPortName, const HString &rNodeType, const HString &rDescription, const Port::RequireConnectionEnumT reqConnect=Port::Required);
RequireConnectionEnumT
This enum specifies the RequiredConnection enums.
Definition Port.h:65

Adding Input and Output Variables: The following functions are available for input and output variable creation: If the last argument ppNodeData is given it will auto register and set the data pointer so that you can use it safely without having to worry about it dangling. In the output variable case, the defaultValue is optional. If a default vale is not given, then the output value will not get a start value. This is usefully in cases where the component should calculate its own initial values. Example: In a Sum component you want the initial output value to be the sum of the initial input values. Being able to set a different start value on the output does not make sense.

Port *addInputVariable(const HString &rName, const HString &rDescription, const HString &rUnit, const double defaultValue, double **ppNodeData=0);
Port *addOutputVariable(const HString &rName, const HString &rDescription, const HString &rUnit, double **ppNodeData=0);
Port *addOutputVariable(const HString &rName, const HString &rDescription, const HString &rUnit, const double defaultValue, double **ppNodeData=0);

Adding Constants: The following functions are available for creating constants The correct function will automatically be selected depending on the rData type. The function will register the given name, unit and description and connect this information to a pointer pointing at the variable you specify as the rData argument. The conditional Constant associates description strings to integer numbers and is shown as a drop-down selection box in the HopsanGUI.

void addConstant(const HString &rName, const HString &rDescription, const HString &rUnit, const double defaultValue, double &rData);
void addConstant(const HString &rName, const HString &rDescription, const HString &rUnit, const int defaultValue, int &rData);
void addConstant(const HString &rName, const HString &rDescription, const HString &rUnit, const HString &defaultValue, HString &rData);
void addConstant(const HString &rName, const HString &rDescription, const HString &rUnit, const bool defaultValue, bool &rData);
void addConditionalConstant(const HString &rName, const HString &rDescription, std::vector<HString> &rConditions, int &rData);

initialize()

void initialize()
{
// Nothing to initialize
}

The next member function that must be defined is the initialize function. This function is run once before simulation starts. This function runs after connections have been established, 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 function you also typically initialize more advanced utilities such as (but not limited to) Delays, Integrators or Transfer-functions. In this example case we have nothing that needs to be initialized.

simulateOneTimestep()

void simulateOneTimestep()
{
//Get variable values from nodes
const double c1 = mpP1->readNode(NodeHydraulic::WaveVariable);
const double Zc1 = mpP1->readNode(NodeHydraulic::CharImpedance);
const double c2 = mpP2->readNode(NodeHydraulic::WaveVariable);
const double Zc2 = mpP2->readNode(NodeHydraulic::CharImpedance);
//Orifice equations
double q2 = mKc*(c1-c2)/(1.0+mKc*(Zc1+Zc2));
double q1 = -q2;
double p1 = c1 + q1*Zc1;
double p2 = c2 + q2*Zc2;
//Write new values to nodes
mpP1->writeNode(NodeHydraulic::Pressure, p1);
mpP1->writeNode(NodeHydraulic::Flow, q1);
mpP2->writeNode(NodeHydraulic::Pressure, p2);
mpP2->writeNode(NodeHydraulic::Flow, q2);
}

The next function simulateOneTimestep, is the most important member function. It contains the model equations that are executed each time step. We begin by creating local variables with copies of the values from the connected ports, this is intended to make the actual equations more human readable. If you know that one of these local variables will not change, then you should make it const. This will prevent you from accidentally assigning it and may allow the compiler to optimize the code better. The middle part 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 end by writing back the new values that were calculated.

finalize()

void finalize()
{
// Nothing to finalize
}

The next member function finalize, is optional. It is only useful if you want some code to be run after simulation has finished. This is usually only needed if you want to free memory that was additionally allocated in the initialize function.

deconfigure()

void deconfigure()
{
// Nothing to deconfigure
}

The last member function deconfigure, is also optional. This code is run once the component is deleted. Here you can cleanup any custom memory allocation or similar that you have don in the configure function. Note! You do not need to remove ports, constants or variables. Hopsan will handle that automatically. Usually you never need to implement anything in this function.