• Skip to main content
  • Skip to header right navigation
  • Skip to site footer
CDP Studio logo

CDP Studio

The no-code and full-code software development tool for distributed control systems and HMI

  • Doc
  • Why CDP
    • Software developers
    • Automation engineers
    • Managers
  • Products
    • Automation Designer
    • HMI Designer
    • Maritime HMIs
  • Services
  • Use cases
  • Pricing
  • Try CDP

CDP Studio Documentation

  • Examples and Tutorials
  • Making an Automation System with HMI GUI

Creating a Professional HMI GUI How to Use Composite Interfaces

Making an Automation System with HMI GUI

The purpose of this example is to show a practical project that make use of many functions and general concepts that are important when developing solutions with CDP Studio, the independent automation software for open PC-based real-time distributed control systems.

Included are step-by-step guide, code examples to develop the application and a description on how to create a more advanced GUI.

This example demonstrates:

  • A practical approach and steps required to create a solution
  • How to create libraries and reusable components with state machines and logic
  • How to use multiple instances of a component in a project
  • How to build a system with routing signals between component instances
  • How to create a comprehensive GUI with layouts, spacers and different widgets
  • How to connect the GUI widgets and control system components

Creating this example from scratch using the step-by-step guides is a good way of learning how to develop solutions with CDP Studio.

How to Run the Example

To run the example from CDP Studio, open Welcome mode and find it under Examples. Next, in Configure mode right-click on the library project and select Build, then right-click on the system project and select Run & Connect. See the Running the Example Project tutorial for more information.

Project overview

The project is a fish feeding system where you can feed fish from 2 food tanks. The feeding system allows you to specify feeding rates, amounts, delayed start independently for the 2 tanks. The solution allows you to fill the tanks, gives a status overview and different alarms.

The tanks have individual screw pumps that moves the food from the tank into a pipe and they share a blower that blows the food through the pipes and into the fish cage.

Project Description

The typical steps to take when developing a CDP Solution are:

  • Decide the structure and which components that is required and must be developed (FishTank, Blower, GuiInterface)
  • Create a library and develop these components using C++
  • Create the system, add the required components (2 FishTank, 1 Blower, 1 GuiInterface)
  • Connect the components together to make a working system in Configure mode using the Block Diagram (drag-and-drop graphic view) or System Configurator (table view)
  • Design and create the GUI, connect the widgets to the components

Tip Use the Model Editor to create the signals, parameters etc to simplify the C++ coding instead of using the wizards from the Code mode. The Model Editor will autogenerate the required C++ code.

The following chapters show how to create the project from scratch.

Create New Library

First we need to create a new library project. See: How to Create a Library. Name it "TankControlLib". We have now created an empty library, but it has nothing useful in it quite yet.

Create a FishTank Component in Your Newly Created Library

In this section we will be making a new component called "FishTank".

Create the Component

First we need to create the component itself. See How to add a component model to see how it is done. When the component is created it will open in Code Mode. If in the future you need to open the component source files manually, you can do it by navigating to it from the Project tree while being in the Code mode:

  • Expand "TankControlLib"
  • Expand "Sources"
  • Double-click on the file "FishTank.cpp"

Add CDP Members to the Component

We will now populate our component. Here we add signals, parameters, states (CDP Studio is a state machine based tool) and the state transitions we will need. We will first disregard the code, but add all states, signals, parameters we want. We'll come back to the code later when all components are placed.

Go through following sections and add CDP members to the FishTank component.

See also: How to add CDP members to class

Signals

Add following Signals to the FishTank component.

It is smart to tick the "Create Another" checkbox, then it will automatically pop up a new signal generation wizard after clicking "Ok".

NameTypeInputValue
PauseButtonboolx
FeedingInProgressbool
FillScrewActivebool
ScrewActivebool
MaxLevelAlarmbool
AlarmLowbool
AlarmExtremeLowbool
AlarmStatusint
BlowerActiveboolx
TankLeveldouble100
ScrewSpeeddouble1
FillAmountdouble50
FeedAmountdouble50
RemainingTimedouble
StartTimedouble
TimeFeededdouble

If you checked the "Create another" checkbox earlier then in order to end creating signals, simply press "Cancel".

Parameters

Here we enter some values that determine when the alarms should be activated. The parameters represent physical sensors, that would be in the tank but since we are simulating this, we use them to compare with the tank level and base the alarm states from that. Add parameters in this table:

NameUnitValue
AlarmLevelLowlitre (l)30
AlarmLevelExtremeLowlitre (l)10
MaxValuelitre (l)100
FillingSpeedlitre/s (l/s)2

States

Add following states to the FishTank component.

NameDescription
FillingFill up the tank with the desired amount of food
FeedingSystem will start feeding
FeedingStoppedFeeding and filling are both stopped
AutoStartingWaiting to start after a given time delay

State Transitions

We have now created Signals and States. We will now create transitions from one state to another.

  • Right click on empty space in FishTank.cpp or FishTank.h
  • Select "CDP" -> "Add" -> "State Transitions"

You should now see a window showing all the states you have created in both the left and the right box.

From the left box choose the state to transition from, and from the right box, the state to transition to.

CDP Studio always auto-generates and adds a state "Null" This is the first state the system enters when the application starts. Remember to tick the "Create another" for convenience.

Add your transitions according to the table:

FromTo
NullFeedingStopped
FeedingStoppedFilling
FeedingStoppedFeeding
FeedingStoppedAutoStarting
FillingFeedingStopped
FeedingFeedingStopped
AutoStartingFeedingStopped
AutoStartingFeeding

Message Handlers

Later we will want to control the FishTanks using GUI. The most logical way to activate certain processes or state transitions would be to do them based on some event that happened (for example a button was pressed). For event based control, CDP provides Messages that can be sent from one CDP object to another. As the FishTank is the object to be controlled, it is at the receiving end of messages and should therefore have a way to handle each message. Next we will be creating Message Handlers for each message, that we will later be sending from the GUI.

  • Right click on empty space in FishTank.cpp or FishTank.h
  • Select "CDP" -> "Add" -> "Message"

Create the following message handlers:

Message text commandDescription
StartFeedingSwitches the state to Feeding
StartFillingSwitches the state to Filling
AutoStartFeedingSwitches the state to AutoStartFeeding
StopSwitches the state to FeedingStopped
SpeedUpIncreases the screw speed
SpeedDownDecreases the screw speed

Now we have created everything we need to start writing the functionality for the FishTank.

Functionality

States

Let's start by implementing each state.

When we generated states for the component, CDP automatically generated a method for each state with a name similar to this...

void FishTank::ProcessMyStateName()
{
}

...where the "MyStateName" part of the method name is replaced with the name of the actual state generated.

When in a particular state, the associated method gets periodically called and the code in it executed. The frequency of the calling is defined by the components property 'fs', which is inherited from the parent class of FishTank which for components is always CDPComponent. 'fs' can be configured in Configure mode.

The implementation of each state is initially empty, so we need to provide our own functionality here.

State “Null”

Since state "Null" actually makes no sense for an actual system (as "null" basically means "no state") we will leave the implementation of method FishTank::ProcessNull() empty.

void FishTank::ProcessNull()
{
}

State "Filling"

When the FishTank is in the state "Filling" the tank level should rise and the fill amount should drop at the speed specified by FillingSpeed parameter. Also the filling screw should be turned on. To achieve the correct filling speed, we have to divide the FillingSpeed value by 'fs' value, to make the filling independent of the processing frequency of the component.

        FillScrewActive = true;
        TankLevel = TankLevel + FillingSpeed/GetFrequency();
        FillAmount = FillAmount - FillingSpeed/GetFrequency();

Obviously we have to stop filling the tank, when it is full, or if fill amount runs to 0. At either of those cases, we will be turning off the filling screw and changing the state to "FeedingStopped". To make sure that FillAmount does not go below 0, we will explicitly set it to 0 and for the TankLevel we will implement something similar.

    if (FillAmount <= 0)
    {
        FillAmount = 0;
        FillScrewActive = false;
        requestedState = "FeedingStopped";
    }
    else if (TankLevel >= MaxValue)
    {
        TankLevel = MaxValue;
        FillScrewActive = false;
        requestedState = "FeedingStopped";
    }

Finally we have to make sure that the alarms are updated, so we will call a method named FishTank::processAlarms() which we will look into later.

In conclusion the method will look like this:

void FishTank::ProcessFilling()
{
    if (FillAmount <= 0)
    {
        FillAmount = 0;
        FillScrewActive = false;
        requestedState = "FeedingStopped";
    }
    else if (TankLevel >= MaxValue)
    {
        TankLevel = MaxValue;
        FillScrewActive = false;
        requestedState = "FeedingStopped";
    }
    else
    {
        FillScrewActive = true;
        TankLevel = TankLevel + FillingSpeed/GetFrequency();
        FillAmount = FillAmount - FillingSpeed/GetFrequency();
        processAlarms();
    }
}

State "Feeding"

When the FishTank is in the state "Feeding", then the TankLevel should lower at the rate, specified by ScrewSpeed.

  • When in "Feeding" state, set FeedingInProgress to true
  • If TankLevel or FeedAmount reaches 0, then end feeding
  • When first entering the Feeding state, then start counting total feeding time
  • If Pause is active, then stop FeedingScrew
  • Reduce TankLevel and FeedAmount and also calculate remaining time estimation
  • When exiting "Feeding" state, then set FeedingInProgress and ScrewActive to false
  • Alarms need to be processed.
  • m_feedingInitialized should be declared in header file and initialized to false in constructor

Taking all the requirements into account, the code looks like this:

void FishTank::ProcessFeeding()
{
    if (TankLevel <= 0)
    {
        TankLevel = 0;
        FeedingInProgress = false;
        ScrewActive = false;
        m_feedingInitialized = false;
        requestedState = "FeedingStopped";
        return;
    }
    else if (FeedAmount <= 0)
    {
        FeedAmount = 0;
        FeedingInProgress = false;
        ScrewActive = false;
        m_feedingInitialized = false;
        requestedState = "FeedingStopped";
        return;
    }

    if (!m_feedingInitialized)
    {
        TimeFeeded = 0;
        m_feedingInitialized = true;
    }

    FeedingInProgress = true;
    TimeFeeded = TimeFeeded + 1.0/GetFrequency();
    if (PauseButton)
    {
        ScrewActive = false;
        return;
    }

    ScrewActive = true;
    RemainingTime = (FeedAmount/ScrewSpeed);

    TankLevel = TankLevel - ScrewSpeed/GetFrequency();
    if (TankLevel<0)
        TankLevel = 0;
    FeedAmount = FeedAmount - ScrewSpeed/GetFrequency();
    if (FeedAmount<0)
        FeedAmount = 0;
    processAlarms();
}

State "FeedingStopped"

When in state "FeedingStopped" there are no active processes. All we need to do, is calculate the remaining time estimation for when the user changes the ScrewSpeed. Also we need to process alarms.

void FishTank::ProcessFeedingStopped()
{
    FillScrewActive = false;
    ScrewActive = false;
    FeedingInProgress = false;
    RemainingTime = (FeedAmount/ScrewSpeed);
    processAlarms();
}

State "AutoStarting"

In the state "AutoStarting" we need to count down from the StartTime, set by the user and enter the "Feeding" state when the countdown reaches 0.

void FishTank::ProcessAutoStarting()
{
    if (StartTime > 0)
        StartTime = StartTime - 1.0/GetFrequency();
    else
    {
        StartTime = 0;
        requestedState = "Feeding";
    }
}

State Transitions

When a component is in a certain state, then all state transition methods, that are from that state, are called. It is the state transition method's job to return true, if the automatically created variable "requestedState" has the correct value. The default implementations for state transitions are therefore suitable for our application with one exception. We want the state to change from "Null" to "FeedingStopped" as soon as possible regardless of whether the requestedState has been set to "FeedingStopped" or not. The easiest way to achieve this is to return true from FishTank::TransitionNullToFeedingStopped().

bool FishTank::TransitionNullToFeedingStopped()
{
    return true;
}

Processing Alarms

Every time the tank level changes, we need to check if we need to activate an alarm. For that we will create a method for this called "processAlarms". As you saw earlier, we already have added the calls for the method so now we only need to provide an implementation.

void FishTank::processAlarms()
{
    MaxLevelAlarm = TankLevel >= (MaxValue * 0.999);
    AlarmLow = TankLevel <= AlarmLevelLow;
    AlarmExtremeLow = TankLevel <= AlarmLevelExtremeLow;

    AlarmStatus = 0;
    if(MaxLevelAlarm)
        AlarmStatus = 3;
    else if(AlarmExtremeLow)
        AlarmStatus = 1;
    else if(AlarmLow)
        AlarmStatus = 2;
}

Don't forget to add the declaration of the method in the header file.

Message Handlers

All we have left in the FishTank component are the message handlers. 4 of the message handlers that we created are used for changing the state of the component. Therefore we only need to set a new value for the "requestedState".

int FishTank::MessageAutoStartFeeding(void* /*message*/)
{
    requestedState="AutoStarting";
    return 1;
}

int FishTank::MessageStartFeeding(void* /*message*/)
{
    requestedState="Feeding";
    return 1;
}

int FishTank::MessageStop(void* /*message*/)
{
    requestedState="FeedingStopped";
    return 1;
}

int FishTank::MessageStartFilling(void* /*message*/)
{
    requestedState="Filling";
    return 1;
}

The other 2 message handlers we are going to use for increasing and decreasing the ScrewSpeed variable:

int FishTank::MessageSpeedUp(void* /*message*/)
{
    if (ScrewSpeed < 5)
        ScrewSpeed = ScrewSpeed + 0.5;
    return 1;
}

int FishTank::MessageSpeedDown(void* /*message*/)
{
    if (ScrewSpeed > 0.5)
        ScrewSpeed = ScrewSpeed - 0.5;
    return 1;
}

Create Blower Component in TankControlLib

In this section we will be making a new component called "Blower". The process is similar to how we created the "FishTank" component here.

Add CDP Members to the Blower Component

Signals

NameTypeInput
FeedingActiveTank1boolx
FeedingActiveTank2boolx
BlowerActivebool

Timer

To add a timer, we first need to add a parameter and then rename the type "CDPParameter" to "CDPParameterTimer". So add a regular CDPParameter called "DelayTime" and change its type to "CDPParameterTimer". You also need to change the include from <CDPParameter/CDPParameter.h> to <OSAPI/Timer/CDPParameterTimer.h>.

States

NameDescription
RunningBlower running
StopBlower is turned off after a time delay

State Transitions

Add the following state transitions:

FromTo
NullStop
StopRunning
RunningStop

Functionality

States

State "Null"

Just like with the "FishTank" component, the "Null" state does not make much sense for the Blower component, so we will leave the ProcessNull method empty.

void Blower::ProcessNull()
{
}

State "Stop"

When the Blower is stopped, we will be checking if any one of the FishTanks has started working, so we can also turn on the Blower. We will be using the signals "FeedingActiveTank1" and "FeedingActiveTank2" that we added earlier for this. Also we set the "BlowerActive" signal to false. This will be showed on the GUI later on.

void Blower::ProcessStop()
{
    BlowerActive = false;
    if (FeedingActiveTank1 || FeedingActiveTank2)
        requestedState = "Running";
}

State "Running"

When the Blower is running, we set the signal "BlowerActive" to true. We also check if both of the FishTanks have been off for over 15 seconds and if they have then we will also change the Blower's state to "Stop".

void Blower::ProcessRunning()
{
    BlowerActive = true;

    if (FeedingActiveTank1 || FeedingActiveTank2)
        DelayTime.Restart();
    else if(DelayTime.TimeElapsed() > 15.0)
        requestedState = "Stop";
}

State Transitions

We will do the exact same thing with state transitions as we did for the "FishTank" component, leaving all state transitions, except for the one originating from state "Null", to their default implementations. And again we will simply return true from the transition from state "Null" to state "Stop".

bool Blower::TransitionNullToStop()
{
    return true;
}

Create GuiInterface Component in TankControlLib

In this section we will be making a new component called "GuiInterface". The process is similar to how we created the "FishTank" component here. The purpose of this component is to forward the user inputs received from the GUI to the correct components.

Add CDP Members to the GuiInterface Component

Signals

NameTypeInput
Pauseboolx

Connectors

  • Right click on empty space in GuiInterface.cpp or GuiInterface.h
  • Select "CDP" -> "Add" -> "Connectors"

The connectors will be used to forward CDPMessages from the GUI to other components. Add 2 connectors called FishTank1 and FishTank2.

Message Handlers

We will add 1 message handler to the component:

Message text commandDescription
StartFeedingSwitches the state to Feeding

Functionality

States

Although CDP provides each component with a state-machine, we will not be using it for the GUIInterface component. That is also the reason, why we did not create any states for the component. As for the default "Null" state, we will just leave it's implementation empty.

void GuiInterface::ProcessNull()
{
}

Connectors

We need to specify what the connectors should be connected to. As this can not yet be done from the Configure mode we will have to hard-code the routings in the source code of the component. We will do this in the Configure() method.

void GuiInterface::Configure(const char* componentXML)
{
    CDPComponent::Configure(componentXML);
    FishTank1.ConnectTo("FishTankControlApp.FishTank1");
    FishTank2.ConnectTo("FishTankControlApp.FishTank2");
}

Note that "FishTankControlApp.FishTank1" and "FishTankControlApp.FishTank2" refer to actual instances of the FishTank component, that we have not yet created. We will do that later in the tutorial.

Message Handler

Earlier we created a single message handler for a message "StartFeeding". CDP automatically generated a message handler method called GuiInterface::MessageStartFeeding() and a message registration command in the GuiInterface::CreateModel() method:

RegisterMessage(CM_TEXTCOMMAND,"StartFeeding","",(CDPOBJECT_MESSAGEHANDLER)&GuiInterface::MessageStartFeeding);

However, we will need to forward 3 other messages beside the "StartFeeding" message. Because the GuiInterface component does not need to differentiate between all those messages, we will be using a single message handler for all messages named GuiInterface::MessageForwardMessage(). So we need to rename the method GuiInterface::MessageStartFeeding() to GuiInterface::MessageForwardMessage().

  • Right click on the method name
  • Move the cursor to "Refactor"
  • Choose "Rename Symbol Under Cursor"
  • Name the method "ForwardMessage"

This will change the method name in both the .cpp and .h files as well as in the RegisterMessage call in the CreateModel method. Next we need to register the remaining 3 messages to use this message handler. For this we will simply copy the RegisterMessage commands 3 times and change the message name as follows:

void GuiInterface::CreateModel()
{
    CDPComponent::CreateModel();

    RegisterStateProcess("Null",(CDPCOMPONENT_STATEPROCESS)&GuiInterface::ProcessNull,"Initial Null state");
    RegisterMessage(CM_TEXTCOMMAND,"StartFeeding","",(CDPOBJECT_MESSAGEHANDLER)&GuiInterface::MessageForwardMessage);
    RegisterMessage(CM_TEXTCOMMAND,"Stop","",(CDPOBJECT_MESSAGEHANDLER)&GuiInterface::MessageForwardMessage);
    RegisterMessage(CM_TEXTCOMMAND,"StartFilling","",(CDPOBJECT_MESSAGEHANDLER)&GuiInterface::MessageForwardMessage);
    RegisterMessage(CM_TEXTCOMMAND,"AutoStartFeeding","",(CDPOBJECT_MESSAGEHANDLER)&GuiInterface::MessageForwardMessage);
}

The GuiInterface::MessageForwardMessage() method should be like this:

int GuiInterface::MessageForwardMessage(void* message)
{
    FishTank1.SendMessage(static_cast<MessageTextCommand*>(message)->textCommand);
    FishTank2.SendMessage(static_cast<MessageTextCommand*>(message)->textCommand);
    return 1;
}

Build the Library

After we have created the components, we need to build the library. Doing this will make the components available in the Configure mode. There they can be added to applications.

See also: How to build a library

You will see a status bar down to the right in the screen which shows the progress of building. This should not take long. When the building is finished, go to Configure mode.

Create New System

  • In Welcome mode create a new system and call it "FishTankControl".

See also: How to Create a System

Add Components to the Application

After you created a new system called "FishTankControl" you should have an application under it called "FishTankControlApp" by default. Now we will add the previously created components to that application.

  • Add two FishTanks
  • Add one Blower
  • Add one GuiInterface

See also: How to Add Components

Once this is done you should see the components in your application. If you click on one of them, you will see all the configuration options that are associated with this component.

Routing the Signals

We will now set up what are called routings. Simply put, a routing is an address from where an input signal should get it's value. See also: How to set up routing

FishTank Components' Configurations

Do the following for both the FishTank components we added earlier:

  • Switch to Configure mode (if you are not already)
  • Select one of the FishTank components from the Project tree
  • Navigate to the heading "Signals" in the Configuration editor
  • For the "BlowerActive" signal set the routing to: "FishTankControlApp.Blower.BlowerActive"
  • For the "PauseButton" signal set the routing to: "FishTankControlApp.GuiInterface.Pause"
Signal's nameRouting
BlowerActiveFishTankControlApp.Blower.BlowerActive
PauseButtonFishTankControlApp.GuiInterface.Pause

Section Blower Component's Configuration Do the following for the Blower component:

  • Switch to Configure mode (if you are not already)
  • Select the Blower component from the Project tree
  • Navigate to the heading "Signals" in the Configuration editor
  • For the "FeedingActiveTank1" signal set the routing to: "FishTankControlApp.FishTank1.FeedingInProgress"
  • For the "FeedingActiveTank2" signal set the routing to: "FishTankControlApp.FishTank2.FeedingInProgress"
Signal's nameRouting
FeedingActiveTank1FishTankControlApp.FishTank1.FeedingInProgress
FeedingActiveTank2FishTankControlApp.FishTank2.FeedingInProgress

Create a GUI

To create a GUI we must first add a new GUI application.

  • Switch to Configure mode (if you are not already)
  • Right click on the System name in the Project tree
  • Select "Add Gui Application..."
  • Give the new GUI application a name and click "Create"

Once created, the new application will be placed under your system in the Project tree. Clicking on a GUI application will make the Design mode available. Click on the Design mode button, to enter the designer.

Adding Widgets to the GUI

Look at the image above and create a similar GUI. The following table lists the widgets along with configuration tips:

WidgetDescription
WidgetThe Widget is a transparent container that includes the most basic of widget functionality. We often use it to group content instead of ordinary layouts as it includes properties for controlling maximum and minimum size. To set a layout on the widget, click it and then select layout type from the tool menu above the form. This is also how you'll set the main layout on the CDPBaseMainWindow (the main widget in the form). Start your design by adding two QWidgets in a vertical layout. Make the top widget expanding by adjusting the size policy. Then add two more QWidgets to the top widget in a horizontal layout. Now, we can start adding elements.

Note: Holding down CTRL while dragging an element with the left mouse button will

make a copy of the dragged element.

Horizontal Line and LabelCreate a title element by adding two Horizontal Line elements and one Label into a widget. Set a vertical layout and adjust all margins to zero. Note that we sometimes add a maximum size to the Horizontal Line elements. This is to ensure a fixed size no matter the size calculations done by the framework, but it shouldn't be necessary in this example.
Vertical BarConfigure the bar similar to the image. Use the numPos property to enable numbers and adjust the amount of numbers by adjusting majorTicks. This works even though the ticks are not visible. Also, note properties like numTickOffset, tickBarSpacing and end margins that can be used to position texts and bar endings.

Note: The color of the bar can be changed by

adjusting the widget style.

Text SelectorThis widget is used for showing different texts based on text or numeric value. Add texts to the selector by right-clicking and selecting "Edit Items..." in the context menu. By default, index numbers are used to trigger the different texts, starting at value 0. Enable text coloring by toggling the useIndexColor property and setting colors to the color properties named index0 and so on.
LabelA Label configured with text aligned to the left and a postfix that will get appended to any received value.

Note: To add an ordinary layout, outlined in red strokes, select the widgets by left-clicking with the mouse while holding down CTRL. Then, select one of the layouts from the toolbox just above the form. Be aware that the widgets cannot already be in a layout if wanting to do this.

SpacerUse spacers to allow certain areas in the form to expand when the size of the form is resized.

Note: To add an ordinary layout, outlined with red strokes, select widgets by left-clicking with the mouse while holding down CTRL. Then, select one of the layouts from the toolbox just above the form. Be aware that the widgets cannot already be in a layout if wanting to do this.

Line EditThe Line Edit is configured similar to the Label, but can also take input from the user.

Note: Use the padType property if you want to pop up a numeric GUI keyboard instead of using a physical one. When padType is specified, the relevant keyboard appears whenever the user selects the Line Edit.

SliderThe Slider is configured with Horizontal orientation and type set to TriangleKnob. The knob text requires some tweaking to get the look we want. Use properties like minimumSize, knobTextRect, endMargins and tickRailEndAlignment to make it look like the example.
Message ButtonAll buttons except the one in are configured in the same way, to send messages to the control system.
Message ButtonThis button is configured to be checkable and to toggle the paused state of the control application. Make sure that checkableByClick is set and use the property named cdpCheckedRouting when adding the path to the remote object.
LampThe Lamp is a simple lamp to show on and off states. Use the maximumSize property to set a proper size.

Route the Tank Widgets

We have now added the various widgets, but they are not yet associated with our CDP components in any way. The different display and input widgets will all be connected to specific CDP signals via routings.

  • Select a widget that you want to route to a signal
  • In the menu on the right hand side, enter “cdpRouting” in the Filter textbox
  • Set the "cdpRouting" property value to the appropriate CDP signal, so that the routing follows this structure: "ApplicationName.ComponentName.SignalName.Value"

For example to display the tank level of FishTank1 with the Vertical Bar widget, we set the routing to be FishTankControlApp.FishTank1.TankLevel.Value

Note: Make sure to set to routing to be the full path to the signal that you wish to route to. For example if the signal is located in a component, that is not directly under the application but has a parent component of it's own (see schema below), the routing might look something like this instead: "ApplicationName.ComponentName.SubComponent.SignalName.Value".

SystemName
    ∟ApplicationName
        ∟ComponentName
            ∟SubComponent
                ∟SignalName
                ∟SignalName2
                ∟SignalName3
            ∟SubComponent2
            ∟SubComponent3
        ∟ComponentName2
            ∟SubComponent4

Also note that the system's name is never in the routing.

Do this for all tank widgets and refer to the following tables for guidance.

Tank 1 (replace FishTank1 with FishTank2 when routing the widget for Tank 2):

WidgetcdpRouting
StatusFishTankControlApp.FishTank1.AlarmStatus.Value
Tank level (bar and text)FishTankControlApp.FishTank1.TankLevel.Value
Time leftFishTankControlApp.FishTank1.RemainingTime.Value
Total timeFishTankControlApp.FishTank1.TimeFeeded.Value
Auger fillFishTankControlApp.FishTank1.FillScrewActive.Value
Auger feedFishTankControlApp.FishTank1.ScrewActive.Value
Start delayFishTankControlApp.FishTank1.StartTime.Value
Fill amountFishTankControlApp.FishTank1.FillAmount.Value
Feed amountFishTankControlApp.FishTank1.FeedAmount.Value
Auger speedFishTankControlApp.FishTank1.ScrewSpeed.Value

Route the Buttons

All buttons except the one that pauses feeding are set up to send messages. This is done using the following properties:

  • Set the "cdpRouting" property to the component we want to send a message to
  • Set the "cdpTextCommand" property to the message we want to send to the component

All buttons, that are common for both FishTanks send messages through the GuiInterface component. Other buttons send messages directly to the associated FishTank. The following table lists the different routings should you get stuck:

WidgetcdpRoutingcdpTextCommand
Delayed StartFishTankControlApp.GuiInterfaceAutoStartFeeding
Start FeedingFishTankControlApp.GuiInterfaceStartFeeding
StopFishTankControlApp.GuiInterfaceStop
Start FillingFishTankControlApp.GuiInterfaceStartFilling

The button for pausing feeding does not send messages like the other buttons. Instead it toggles a signal when it gets checked. This is done using the following properties:

WidgetcdpCheckedRouting
PauseFishTankControlApp.GuiInterface.Pause.Value

The blower lamp is not a button, but a plain lamp. The property for routing this widget depends on how the SVG file is structured. The properties, cdpRouting and cdpStyleRouting are used to select row and column in the svg. In the current version of the lamp widget, we use the following:

WidgetcdpStyleRouting
Blower lampFishTankControlApp.FishTank.BlowerActive.Value

How to Run the Example

To run the example from CDP Studio, open Welcome mode and find it under Examples. Next, in Configure mode right-click on the library project and select Build, then right-click on the system project and select Run & Connect. See the Running the Example Project tutorial for more information.

Test Your System

After you have completed all the steps above, you should be ready to test out your new fishtank controller.

  • Set the "Start delay" time to 5 seconds
  • Click the "Delayed Start" button
  • After 5 seconds the tank levels should start to decrease and augers should be running.
  • If you then click the "Pause" button, then the tank levels should stop decreasing
  • Clicking the "Pause" button again will resume the filling process
  • Change the feeding speed by adjusting the auger speed slider
  • Click "Stop" to end the feeding process
  • Enter 10 for the first tank's fill amount and 20 for the second tank's fill amount.
  • Click the "Start Filling" button and verify that the tank levels increase.
  • Click "Stop" to end the filling process

As a result the GUI you created should appear in a newly opened windows and after a couple of seconds it should connect to the FishTankControlApp. You can then try out if you system works as expected by playing around in the GUI. If it does not behave as expected, make sure you have done everything that was explained in this tutorial and if you are still having problems, then take a look at the source code of the example that is provided along with CDP.

Files

  • Blower.cpp
  • Blower.h
  • FishTank.cpp
  • FishTank.h
  • GuiInterface.cpp
  • GuiInterface.h

Creating a Professional HMI GUI How to Use Composite Interfaces

The content of this document is confidential information not to be published without the consent of CDP Technologies AS.

CDP Technologies AS, www.cdpstudio.com

Get started with CDP Studio today

Let us help you take your great ideas and turn them into the products your customer will love.

Try CDP Studio for free
Why CDP Studio?

CDP Technologies AS
Hundsværgata 8,
P.O. Box 144
6001 Ålesund, Norway

Tel: +47 990 80 900
E-mail: info@cdptech.com

Company

About CDP

Contact us

Services

Partners

Blog

Developers

Get started

User manuals

Support

Document download

Release notes

My account

Follow CDP

  • LinkedIn
  • YouTube
  • GitHub

© Copyright 2025 CDP Technologies. Privacy and cookie policy.

Return to top