API Guides > ConceptRT 3.x
Agile programming

An Agile programming demonstration.

Introduction

By being agile, a software is expected to be able to integrate new elements (list items) and also new types of elements (polymorphism) like plug-ins. These new elements bring new functionalities for the application. Populating the lists and the mobilization of new features can be made either by code or by reading an external configuration file (no recompilation needed).

Note
Agile programming is not related to Agile software development methods

Example of agile software architecture

Agile machine structure

A machine can be composed of different parts called MachineElements. Those parts will be, in the example below, a Gripper and a RobotArm but it also could be a Laser, an Iron, etc. Here, the MachineElement abstraction is the key to make extensibility possible.

AgileProgramming-Machine.png

Machine and MachineElements

The base component ConceptComponent and its macros bring all necessary features to improve programming efficiency and agility. All the business model entities should inherit from it. See Components Declaration.

The machine element is an abstract class and will be implemented like this :

class MachineElement : public ConceptComponent
{
CONCEPT_ABSTRACT_BEGIN(MachineElement, ConceptComponent)
};

The machine class is composed by a polymorphous list of MachineElements (ConceptDynamicList).

class Machine : public ConceptComponent
{
PROPERTY_OBJECT(ConceptDynamicList<MachineElement>, MachineElements)
CONCEPT_BEGIN(Machine, "Example.Machine", ConceptComponent)
CONCEPT_OBJECT(MachineElements)
};

The MachineElements list is declared with a getter on one line

PROPERTY_OBJECT(ConceptDynamicList<MachineElement>, MachineElements)
and this object is connected to every mechanisms (dynamic invocation, serialization, visitation) through a second line
CONCEPT_OBJECT(MachineElements)

Note
The PROPERTY_OBJECT macro and the ConceptDynamicList enables us to implement the 1 to many composition arrow of the class diagram.
PROPERTY_OBJECT(ConceptDynamicList<MachineElement>, MachineElements)

The base software structure is now ready to be enriched with various machine parts.

Gripper

class Gripper : public MachineElement
{
PROPERTY_GET_SET(TimeSpan, OpeningTimeout)
CONCEPT_BEGIN(Gripper, "Example.Gripper", MachineElement)
CONCEPT_ITEM(OpeningTimeout)
CONCEPT_END
public:
void Open() { System::GetConsole().WriteLine(GetName() + " - Open()"); }
void Close(){ System::GetConsole().WriteLine(GetName() + " - Close()");}
};

The methods Open and Close simulate their behaviours by writing messages in the System::GetConsole().

The property OpeningTimeout is declared with its getter and setter on one ligne

PROPERTY_GET_SET(TimeSpan, OpeningTimeout)
and this property is connected to every mechanisms (initialization, dynamic invocation, serialization, visitation) through a second line
CONCEPT_ITEM(OpeningTimeout)

RobotArm

struct Point : public DataStructure
{
};

DataStructure and its macros enable this structure to be connected to every mechanisms (dynamic invocation, serialization, visitation). The serialization of this structure will be useful in our example for the different actions composed of points.

class RobotArm : public MachineElement
{
PROPERTY_GET_SET(Float32, MaxPositionX)
PROPERTY_GET_SET(Float32, MaxPositionY)
PROPERTY_GET_SET(Float32, MaxPositionZ)
CONCEPT_BEGIN(RobotArm, "Example.RobotArm", MachineElement)
CONCEPT_ITEM(MaxPositionX)
CONCEPT_ITEM(MaxPositionY)
CONCEPT_ITEM(MaxPositionZ)
CONCEPT_END
public:
void MoveTo(Point point)
{
System::GetConsole().WriteLine(GetName()
+ " - MoveTo(x : " + ToString(point.x)
+ " y : " + ToString(point.y) +")");
}
};

The methods MoveTo simulates its behaviour by writing a message in the System::GetConsole().

See complete example files here

If the machine is instantiated the list content will be empty. The machine needs to be defined by populating the list content.

Example machine description

The machine have an empty list of MachineElements. Populating the list is made by code or by reading a configuration file where the objects and their parameters have been serialized.

Let's describe the machine used for this example with an instance diagram:

AgileProgramming-MachineInstances.png

Machine declaration

Machine machine;
machine.SetName("Machine");
ConceptRegistry::GetInstance().RegisterReferential(machine);
The root object machine needs to be named and to be registered in the ConceptRegistry so that the links (aggregations) can work with this node.

Machine description by code (hardcoded)

// Machine configuration (hard coded) : Populate the machine elements list
Gripper & gripperA = machine.GetMachineElements().AddNew<Gripper>("GripperA");
gripperA.SetOpeningTimeout(Seconds(2)); // sets the properties
RobotArm & armA = machine.GetMachineElements().AddNew<RobotArm>("ArmA");
armA.SetMaxPositionX(45.6f); // sets the properties
armA.SetMaxPositionY(87.2f); // sets the properties
armA.SetMaxPositionZ(28.1f); // sets the properties
Gripper & gripperB = machine.GetMachineElements().AddNew<Gripper>("GripperB");
gripperB.SetOpeningTimeout(Milliseconds(250)); // sets the properties
RobotArm & armB = machine.GetMachineElements().AddNew<RobotArm>("ArmB");
armB.SetMaxPositionX(34.7f); // sets the properties
armB.SetMaxPositionY(88.4f); // sets the properties
armB.SetMaxPositionZ(62.3f); // sets the properties

Machine description loaded from file

// Machine configuration loaded from filesystem
// Build factories list
ConceptFactoriesDynamic machineFactories;
machineFactories.RegisterFactory(Gripper::GetClassFactory());
machineFactories.RegisterFactory(RobotArm::GetClassFactory());
// Open the file
FileStream machineInfile(filePath, FileModeTextRead);
XmlDataStoreReader xmlReader(machineInfile);
// Create and configure dataStore for polymorphous model deserialization
ConceptDataStore reader(xmlReader);
reader.SetFactories(machineFactories);
// Read the machine
reader.ReadValue(machine.GetName(), machine);
machine.ResolveLinks();
machineInfile.Close();

In order to deserialize polymorphous content, the ConceptDataStore will use the design pattern Factory. The factory enables us to create an instance of a class. The factories are thus used to populate the polymorphous items of the list in our example.

Configuration file
The configuration file to create the machine example is the following:
1 <Machine>
2  <MachineElements>
3  <Count>4</Count>
4  <Item0>
5  <Factory>Example.Gripper</Factory>
6  <Name>GripperA</Name>
7  <Data>
8  <OpeningTimeout>2s</OpeningTimeout>
9  </Data>
10  </Item0>
11  <Item1>
12  <Factory>Example.RobotArm</Factory>
13  <Name>ArmA</Name>
14  <Data>
15  <MaxPositionX>45.6</MaxPositionX>
16  <MaxPositionY>87.2</MaxPositionY>
17  <MaxPositionZ>28.1</MaxPositionZ>
18  </Data>
19  </Item1>
20  <Item2>
21  <Factory>Example.Gripper</Factory>
22  <Name>GripperB</Name>
23  <Data>
24  <OpeningTimeout>250ms</OpeningTimeout>
25  </Data>
26  </Item2>
27  <Item3>
28  <Factory>Example.RobotArm</Factory>
29  <Name>ArmB</Name>
30  <Data>
31  <MaxPositionX>34.7</MaxPositionX>
32  <MaxPositionY>88.4</MaxPositionY>
33  <MaxPositionZ>62.3</MaxPositionZ>
34  </Data>
35  </Item3>
36  </MachineElements>
37 </Machine>
Note
The xml file can be edited through a tool ConceptEditor (todo link).
Note
Adding a new machine part type is easy. Implement a new class and add one single line such as
machineFactories.RegisterFactory(Gripper::GetClassFactory());
Adapt the configuration file to populate items of the new object type (Laser, Iron, etc.) and load it.

Agile Process structure

The Process is composed of a list of Action. The execution of the Process will execute each Action one after another. It is possible to integrate new type of Action such as ActionDrill, etc. Here, the Action abstraction is the key to make extensibility possible.

Process and Actions

AgileProgramming-Process.png
class Action : public ConceptComponent
{
CONCEPT_ABSTRACT_BEGIN(Action, ConceptComponent)
CONCEPT_ABSTRACT_END
public:
virtual void Execute()
{
System::GetConsole().WriteLine("==== > Execute action : " + GetName());
}
};

Each Action can be executed. Here a simulation is made by writing the Action name into console.

class Process : public ConceptComponent
{
PROPERTY_OBJECT(ConceptDynamicList<Action>, Actions)
CONCEPT_BEGIN(Process, "Example.Process", ConceptComponent)
CONCEPT_OBJECT(Actions)
CONCEPT_END
public:
void Execute()
{
Int32 count = GetActions().GetCount();
for (Int32 index = 0; index < count; index++)
{
GetActions()[index].Execute();
}
}
};

The Process is composed of a polymorphous list of Actions. The execution of the Process will execute the Actions one after another.

Process dependencies towards the MachineElements

As the ActionClose and ActionOpen need the gripper to be executed, dependencies are made between these items. The Actions cannot resolve their dependencies through a static relation or similar approach because the Machine composition is not known at the compile time. The machine composition depends on the MachineElements list (which is empty at the start). These dependencies are represented by aggregations in UML. When the Process content is saved and restore, these aggregations must be saved and restored as well.

ProcessToMachine.png

Actions

ActionOpen
class ActionOpen : public Action
{
PROPERTY_OBJECT(ConceptSingleLink<Gripper>, Gripper) // aggregation
CONCEPT_BEGIN(ActionOpen, "Example.ActionOpen", Action)
CONCEPT_OBJECT(Gripper)
CONCEPT_END
public:
void Execute() override
{
baseClass::Execute();
if (GetGripper().IsValidReference())
{
GetGripper().GetReference()->Open();
}
}
};
ActionMove
class ActionMove : public Action
{
PROPERTY_OBJECT(ConceptSingleLink<RobotArm>, Arm) // aggregation
PROPERTY_OBJECT(Point, Point)
CONCEPT_BEGIN(ActionMove, "Example.ActionMove", Action)
CONCEPT_END
public:
void Execute() override
{
baseClass::Execute();
if (GetArm().IsValidReference())
{
GetArm().GetReference()->MoveTo(GetPoint());
}
}
};
ActionPickPlace
class ActionPickPlace : public Action
{
PROPERTY_OBJECT(ConceptSingleLink<Gripper>, Gripper) // aggregation
PROPERTY_OBJECT(ConceptSingleLink<RobotArm>, Arm) // aggregation
PROPERTY_OBJECT(Point, PointPick)
PROPERTY_OBJECT(Point, PointPlace)
CONCEPT_BEGIN(ActionPickPlace, "Example.ActionPickPlace", Action)
CONCEPT_OBJECT(Gripper)
CONCEPT_OBJECT(PointPick)
CONCEPT_OBJECT(PointPlace)
CONCEPT_END
public:
void Execute() override
{
baseClass::Execute();
if (!GetGripper().IsValidReference() || !GetArm().IsValidReference())
{
System::GetConsole().WriteLine("Bad dependencies");
return;
}
// pick
GetGripper().GetReference()->Open();
GetArm().GetReference()->MoveTo(GetPointPick());
GetGripper().GetReference()->Close();
// place
GetArm().GetReference()->MoveTo(GetPointPlace());
GetGripper().GetReference()->Open();
}
};
See complete example files here

Process description and configuration

The Process often changes, depending on the production. It's not possible to hardcode the Process content (Variants) since it is the end user who describes what the machine does. The Process content (objects, parameters and dependencies) should be saved and restored from a file on the hard disk.

AgileProgramming-ProcessInstances.png

Process declaration

// Process declaration
Process process;
process.SetName("Process");

Process description by code (hardcoded)

// process configuration (hard coded)
Gripper & gripperUsed = *gripper;
RobotArm & armUsed = *arm;
ActionOpen & action1 = process.GetActions().AddNew<ActionOpen>("Action1");
// action dependencies
action1.GetGripper().SetReference(gripperUsed);
ActionPickPlace & action2 = process.GetActions().AddNew<ActionPickPlace>("Action2");
// action dependencies
action2.GetGripper().SetReference(gripperUsed);
action2.GetArm().SetReference(armUsed);
// Point pick
action2.GetPointPick().x = 10;
action2.GetPointPick().y = 20;
action2.GetPointPick().z = 15;
// Point place
action2.GetPointPlace().x = 30;
action2.GetPointPlace().y = -20;
action2.GetPointPlace().z = 0;
ActionMove & action3 = process.GetActions().AddNew<ActionMove>("Action3");
// action dependencies
action3.GetArm().SetReference(armUsed);
// Point pick
action3.GetPoint().x = 0;
action3.GetPoint().y = 0;
action3.GetPoint().z = 0;

Process description loaded from file

// Process configuration loaded from filesystem
// Build factories list
ConceptFactoriesDynamic processFactories;
processFactories.RegisterFactory(ActionOpen::GetClassFactory());
processFactories.RegisterFactory(ActionPickPlace::GetClassFactory());
processFactories.RegisterFactory(ActionMove::GetClassFactory());
// Open the file
FileStream processInfile(filePath, FileModeTextRead);
XmlDataStoreReader xmlReader(processInfile);
// Create and configure dataStore for polymorphous model deserialization
ConceptDataStore reader(xmlReader);
reader.SetFactories(processFactories);
// Read the machine
reader.ReadValue(process.GetName(), process);
process.ResolveLinks();
processInfile.Close();

As well as for the machine, the process has polymorphous content. The ConceptDataStore has to be used and the factories have to be registered in order to give them the ability to create the different type of instances.

Process description file
The process description file could be the following but it can be adapted in order to change the global machine behaviour.
1 <Process>
2  <Actions>
3  <Count>3</Count>
4  <Item0>
5  <Factory>Example.ActionOpen</Factory>
6  <Name>Action1</Name>
7  <Data>
8  <Gripper>
9  <LinkPath>\Machine\MachineElements\GripperA</LinkPath>
10  </Gripper>
11  </Data>
12  </Item0>
13  <Item1>
14  <Factory>Example.ActionPickPlace</Factory>
15  <Name>Action2</Name>
16  <Data>
17  <Gripper>
18  <LinkPath>\Machine\MachineElements\GripperA</LinkPath>
19  </Gripper>
20  <Arm>
21  <LinkPath>\Machine\MachineElements\ArmA</LinkPath>
22  </Arm>
23  <PointPick>
24  <x>10</x>
25  <y>20</y>
26  <z>15</z>
27  </PointPick>
28  <PointPlace>
29  <x>30</x>
30  <y>-20</y>
31  <z>0</z>
32  </PointPlace>
33  </Data>
34  </Item1>
35  <Item2>
36  <Factory>Example.ActionMove</Factory>
37  <Name>Action3</Name>
38  <Data>
39  <Arm>
40  <LinkPath>\Machine\MachineElements\ArmA</LinkPath>
41  </Arm>
42  <Point>
43  <x>0</x>
44  <y>0</y>
45  <z>0</z>
46  </Point>
47  </Data>
48  </Item2>
49  </Actions>
50 </Process>
Note
The xml file can be edited through a tool ConceptEditor (todo link).
Note
Adding a new process action type is easy. Implement a new class and add one single line such as
processFactories.RegisterFactory(ActionOpen::GetClassFactory());

Execution

In the example the process is created after the machine. The following lines will execute the process.

// Execution of the process
System::GetConsole().WriteLine();
System::GetConsole().WriteLine("Process execution");
System::GetConsole().WriteLine("-----------------");
process.Execute();
System::GetConsole().WriteLine("Process execution end.");

On the first execution of the application the hardcoded description of the machine and the process are used. The execution displays the following output.

MachineExecution1.png

When the hardcoded descriptions are used the corresponding XML files are created.

MachineFiles.png

On the next execution (as the files are found), the software will use the description file. Changing the machine or the process file enables us to adapt software behaviours without recompiling and as shown before, adding a new machine part or a new action is handled easily with this kind of agile software architecture.

MachineExecution2.png

ConceptRT and agile programming

Note
In embedded world the Machine could be a System and the MachineElements could be some Peripherals. The Process of action could be an activation with parameter set for each peripheral.

To be able to develop such a software architecture there are several needs to be met :

  • Polymorphous list
  • Serialization / de-serialization of polymorphous content
  • Serialisation / de-serialization of aggregations

As shown in the previous example, ConceptRT has been especially designed to handle such a complexity. Moreover there are more advanced features available to manage multiple layers structure and handling functionalities brought by underneath layers (todo link).