Smart Component State Cache example
The "CodeBehind" class that the SmartComponents make use of should be seen as a service provider, rather than an instance of the component itself. Regardless of the number of instances of SmartComponents called, there is only a single "CodeBehind" code executing. This makes it erroneous to save any state information in the "CodeBehind" class. The "Component.StateCache" should be used for saving information regarding the state of the SmartComponent.
This tutorial creates a very simple SmartComponent that gets a property string called "Name", and when the user clicks an execution signal, the component displays its name in RobotStudio.
The SmartComponent is coded in two ways. The Naive, incorrect one, and the correct one using the "Component.StateCache" to help the developer understand its correct usage. The developer will then see that, although trivial in appearance, this SmartComponent can cause issues when means other than the "StateCache" are used for saving the SmartComponents information.
Prerequisites
"RobotStudio" and "RobotStudio SDK 6.03 " or later and "Visual Studio 2015".
Download
Download the Visual Studio projects of both solutions by clicking on this link.
"Naive" Solution
In the SmartComponent's XML file, add a Dynamic property called "Name" of type string. For simplicity make it auto-applicable. In a similar fashion, add a Digital Input called "SayHello", make it auto reset itself.
<SmartComponent name="SmartComponentMultiple" description="Sample Component Description"> <DynamicProperty name="Name" description="The 'name' of this SmartComponent instance."/> <IOSignal name="SayHello" description="Makes the SmartComponent introduce itself to the message window."/> </SmartComponent>
In the "Naive" solution, declare a string member called "name" that will hold the name of the SmartComponent and show it later in RobotStudio.
public class CodeBehind : SmartComponentCodeBehind { private string name = ""; }
In the "CodeBehind.cs" file, override the "OnPropertyValueChanged" and "OnIOSignalValueChanged" methods as shown below.
public class CodeBehind : SmartComponentCodeBehind { private string name = ""; public override void OnPropertyValueChanged( SmartComponent component, DynamicProperty changedProperty, Object oldValue) { if (changedProperty.Name == "Name") { name = changedProperty.Value.ToString(); } } public override void OnIOSignalValueChanged( SmartComponent component, IOSignal changedSignal) { if (changedSignal.Name == "SayHello" && changedSignal.Value.Equals(1)) { Logger.AddMessage(new LogMessage("- - - Hello, my name is " + name + " - - -")); } base.OnIOSignalValueChanged(component, changedSignal); } }
The final versions of the XML and CS files are shown below:
using System; using System.Collections.Generic; using System.Text; using ABB.Robotics.Math; using ABB.Robotics.RobotStudio; using ABB.Robotics.RobotStudio.Stations; using System.Diagnostics; namespace SmartComponentMultiple { public class CodeBehind : SmartComponentCodeBehind { private string name = ""; public override void OnPropertyValueChanged( SmartComponent component, DynamicProperty changedProperty, Object oldValue) { if (changedProperty.Name == "Name") { name = changedProperty.Value.ToString(); } } public override void OnIOSignalValueChanged( SmartComponent component, IOSignal changedSignal) { if (changedSignal.Name == "SayHello" && changedSignal.Value.Equals(1)) { Logger.AddMessage(new LogMessage("- - - Hello, my name is " + name + " - - -")); } base.OnIOSignalValueChanged(component, changedSignal); } } }
<?xml version="1.0" encoding="utf-8" ?> <lc:LibraryCompiler xmlns:lc="urn:abb-robotics-robotstudio-librarycompiler" xmlns="urn:abb-robotics-robotstudio-graphiccomponent" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:abb-robotics-robotstudio-librarycompiler file:///C:\Program%20Files%20(x86)\ABB\SDK\RobotStudio%202020%20SDK\LibraryCompilerSchema.xsd urn:abb-robotics-robotstudio-graphiccomponent file:///C:\Program%20Files%20(x86)\ABB\SDK\RobotStudio%202020%20SDK\GraphicComponentSchema.xsd"> <lc:Library fileName="SmartComponentMultiple.rslib"> <lc:DocumentProperties> <lc:Author>SEERVIE</lc:Author> <lc:Image source="SmartComponentMultiple.png"/> </lc:DocumentProperties> <SmartComponent name="SmartComponentMultiple" icon="SmartComponentMultiple.png" codeBehind="SmartComponentMultiple.CodeBehind,SmartComponentMultiple.dll" canBeSimulated="false"> <Properties> <DynamicProperty name="Name" valueType="System.String"> <Attribute key="AutoApply" value="true"/> </DynamicProperty> </Properties> <Bindings> </Bindings> <Signals> <IOSignal name="SayHello" signalType="DigitalInput" autoReset="true"/> </Signals> <GraphicComponents> </GraphicComponents> <Assets> <Asset source="SmartComponentMultiple.dll"/> </Assets> </SmartComponent> </lc:Library> </lc:LibraryCompiler>
After verifying the correctness of the files, start the debugger from Visual Studio. Once the RobotStudio window has loaded, load the SmartComponent into an Empty Station. Operating the component (i.e. writing a name and making it output it to the message window should work as intended).
Copy the component several times and make them "Introduce themselves" through the message window (give them different names to distinguish between instances). You will notice that all the names of the Components get changed at the same time, that is, all the Components will output the same last name inputed.
The problem consists on the fact that all the components are being controlled from a single CodeBehind instance, therefore all of them share the same "name" string member.
Correct Solution
In the SmartComponent's XML file, add a Dynamic property called "Name" of type string. For simplicity make it auto-applicable. In a similar fashion, add a Digital Input called "SayHello", make it auto reset itself.
<SmartComponent name="SmartComponentMultiple" description="Sample Component Description"> <DynamicProperty name="Name" description="The 'name' of this SmartComponent instance."/> <IOSignal name="SayHello" description="Makes the SmartComponent introduce itself to the message window."/> </SmartComponent>
In the "CodeBehind.cs" file, override the "OnPropertyValueChanged" and "OnIOSignalValueChanged" methods as shown below.
public class CodeBehind : SmartComponentCodeBehind { public override void OnPropertyValueChanged( SmartComponent component, DynamicProperty changedProperty, object oldValue) { if (changedProperty.Name == "Name") { // Check if the Key "NameKey" already exists and, if not... if (!component.StateCache.ContainsKey("NameKey")) { // Make a new entry with that key. component.StateCache.Add("NameKey", ""); } // Update the entry with the new element. component.StateCache["NameKey"] = changedProperty.Value.ToString(); } } public override void OnIOSignalValueChanged( SmartComponent component, IOSignal changedSignal) { if (changedSignal.Name == "SayHello" && changedSignal.Value.Equals(1)) { Logger.AddMessage(new LogMessage("- - - Hello, my name is " + component.StateCache["NameKey"] + " - - -")); } base.OnIOSignalValueChanged(component, changedSignal); } }
In our code, a "Component.StateCache" entry, with the key "NameKey" is created the first time the name property of the SmartComponent changes and it gets updated as the user types with the contents of the property.
The Component.StateCache property is a member that every component instance has individually, therefore, it wont produce issues when used in this way.
Finally, every time the "SayHello" Signal is activated, the component sends a message to the message window displaying the name stored in its own StateCache.
The final versions of the XML and CS files are shown below:
using System; using System.Collections.Generic; using System.Text; using ABB.Robotics.Math; using ABB.Robotics.RobotStudio; using ABB.Robotics.RobotStudio.Stations; using System.Diagnostics; namespace SmartComponentMultipleCorrect { public class CodeBehind : SmartComponentCodeBehind { public override void OnPropertyValueChanged( SmartComponent component, DynamicProperty changedProperty, object oldValue) { if (changedProperty.Name == "Name") { // Check if the Key "NameKey" already exists and, if not... if (!component.StateCache.ContainsKey("NameKey")) { // Make a new entry with that key. component.StateCache.Add("NameKey", ""); } // Update the entry with the new element. component.StateCache["NameKey"] = changedProperty.Value.ToString(); } } public override void OnIOSignalValueChanged( SmartComponent component, IOSignal changedSignal) { if (changedSignal.Name == "SayHello" && changedSignal.Value.Equals(1)) { Logger.AddMessage(new LogMessage("- - - Hello, my name is " + component.StateCache["NameKey"] + " - - -")); } base.OnIOSignalValueChanged(component, changedSignal); } } }
<?xml version="1.0" encoding="utf-8" ?> <lc:LibraryCompiler xmlns:lc="urn:abb-robotics-robotstudio-librarycompiler" xmlns="urn:abb-robotics-robotstudio-graphiccomponent" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:abb-robotics-robotstudio-librarycompiler file:///C:\Program%20Files%20(x86)\ABB\SDK\RobotStudio%202020%20SDK\LibraryCompilerSchema.xsd urn:abb-robotics-robotstudio-graphiccomponent file:///C:\Program%20Files%20(x86)\ABB\SDK\RobotStudio%202020%20SDK\GraphicComponentSchema.xsd"> <lc:Library fileName="SmartComponentMultipleCorrect.rslib"> <lc:DocumentProperties> <lc:Author>SEERVIE</lc:Author> <lc:Image source="SmartComponentMultipleCorrect.png"/> </lc:DocumentProperties> <SmartComponent name="SmartComponentMultipleCorrect" icon="SmartComponentMultipleCorrect.png" codeBehind="SmartComponentMultipleCorrect.CodeBehind,SmartComponentMultipleCorrect.dll" canBeSimulated="false"> <Properties> <DynamicProperty name="Name" valueType="System.String"> <Attribute key="AutoApply" value="true"/> </DynamicProperty> </Properties> <Bindings> </Bindings> <Signals> <IOSignal name="SayHello" signalType="DigitalInput" autoReset="true"/> </Signals> <GraphicComponents> </GraphicComponents> <Assets> <Asset source="SmartComponentMultipleCorrect.dll"/> </Assets> </SmartComponent> </lc:Library> </lc:LibraryCompiler>
After verifying the correctness of the files, start the component from Visual Studio. Once the RobotStudio window has loaded, load the SmartComponent into an Empty Station. Operating the component (i.e. writing a name and making it output it to the message window should work as intended).
Copy the component several times and make them "Introduce themselves" through the message window (give them different names to distinguish between instances).
You will notice that every component displays correctly the name that has been given to it, just as it was intended from the beginning.