0. PROJECT DELIVERABLES
This section is available in this report for making easy the access to the source code and executables. Notice that, due to the nature of the language framework used in this implementation, it requires either a Windows OS with .NET Framework 4.0 or a system with Mono and a compatible .NET Framework 4.0 available.
The project source code is available for downlading here.
The executable files can be found for downlading here.
The VREP Scene is also available for downlading here.
For running the project perform the following steps:
1. INTRODUCTION
The Virtual Robot Experimentation Platform (VREP) is a simulation environment used for prototyping that helps creating embedded applications for robots without the need of the actual hardware. It contains an extensive library with many types of robots, sensors and objects for supporting creating the simulation scene. It provides APIs for interacting with the available robots and sensors that can be accessed remotely or locally. The picture following shows a screenshop of VREP smiulation environment. VREP is available for download here. More information about the available VREP features can be found here.
Figure 1 - The VREP simulation environment.
The Connectionist Learning with Adaptive Rule Induction On-line (Clarion) is a cognitive framework that simulate several cognitive tasks supporting the implementation of artificial intelligent applications. More information about Clarion framework can be found here.
The objective of this project is to connect a Clarion cognitive application to a VREP robot, using the available VREP remote API, so that the Robot can perform actions received from the cognitive application based on the robot's perceptions of the surrounding environment.
2. THE SOLUTION OVERVIEW
VREP has APIs for remote connection with libraries available for languages like Java and C++ but does not provide libraries for C#. Clarion framework, on the other hand, is developed in C# and for this project a different solution for remote connection had to be used.
When creating a scene in VREP, the environment allows the use of some LUA scripts, attached to the scene's objects, that run when the scene simulation is executed. For this project, it was developed a script that was attached to the robot for starting a UDP server and later listening to received requests.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
-- Initialization code area if (sim_call_type==sim_childscriptcall_initialization) then ... -------------------------------------------------- -- UDP -------------------------------------------------- local socket = require("socket") server = assert(socket.udp()) assert(server:setsockname(address,port)) server:settimeout(0) send = function(...) server:sendto(..., clientIp, clientPort) end sendln = function(...) server:sendto(... .. "\n", clientIp, clientPort) end -------------------------------------------------- ... end |
Code Snippet 1 - UDP server implemented in VREP script.
The script has an initialization region, called sim_childscriptcall_initialization, that runs only once then the script is started, and it is in that region that the UDP server code is implemented.
Once the connection is started, all the received commands are received inside the sim_childscriptcall_actuation code region that is executed all the time during the simulation in a sort of loop. As show by the code snippet that follows, the remote commands are received by the command server:receiveFrom().
1 2 3 4 5 |
if (sim_call_type==sim_childscriptcall_actuation) then ... command, clientIp, clientPort = server:receivefrom() ... end |
Code Snippet 2 - UDP server receive data implementation.
Together with the remote connectivity code, many other lines of code are implemented in VREP Scene script for receiving remote commands and translating them to moviments and sensor readings during the simulation. The complete script code is available for donwload here.
A very basic picture of the connectivity architecture of Clarion application and VREP environment is shown in the picture following. Apart from the LUA scripts in VREP Scene, there is also a library in C# developed for enabling the connectivity between the framework and the simulation environment. The C# library contains high-level abstractions that represent objects, sensors and actions in the Scence.
Figure 2 - Clarion-VREP proxy architecture.
The main piece of code in C# for sending and receiving commands to VREP Scene is shown in the following code snippet.
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 |
using System; using System.Diagnostics; using System.Net; using System.Net.Sockets; using System.Text; using VRepProxy.Objects; namespace VRepProxy.Proxy { public class VRepProxy { private static UdpClient udpClient; private static IPEndPoint endPoint; private static String host; private static int port; private const int socketReadDelay = 30; private Scene scene; public VRepProxy(String host, int port){ VRepProxy.host = host; VRepProxy.port = port; Connect (); } public static void Connect() { if (udpClient == null || endPoint == null) { udpClient = new UdpClient (); udpClient.Client.ReceiveBufferSize = 1024; endPoint = new IPEndPoint (IPAddress.Parse (host), port); } udpClient.Connect (endPoint); } public static void ReleaseConnection() { if (udpClient == null || endPoint == null) { udpClient.Close (); } } public Scene GetScene(){ if (scene == null) { scene = new Scene (); } return scene; } public static Boolean Perform(String command){ byte[] sendByte = Encoding.ASCII.GetBytes (command); udpClient.Send (sendByte, sendByte.Length); return true; } public static String Get(String command){ String sensors = null; byte[] sendByte = Encoding.ASCII.GetBytes (command); udpClient.Send (sendByte, sendByte.Length); while (true) { try { var receivedData = udpClient.Receive (ref endPoint); String data = Encoding.ASCII.GetString (receivedData); sensors = data.Substring (0, data.IndexOf (Environment.NewLine)); break; } catch (System.Net.Sockets.SocketException ex) { if (ex.ErrorCode != 10060) { break; } } System.Threading.Thread.Sleep (socketReadDelay); } return sensors; } } } |
Code Snippet 3 - VRepProxy librayr VRepProxy.cs implementation.
The main piece of code in C# for sending and receiving commands to VREP Scene is shown in the following code snippet. The other proxy objects are shown in the picture following. As shown in the picture, the proxy library is called VRepProxy. All the additional objects can also be seen in the picture that follows. As mentioned before, each object in the Objects package represents an object or a set of objects in VRep Scene.
Figure 3 - VRepProxy project structure.
The Clarion agent responsible for receiving the perceptions from sensorial inputs and provide output actions based on cognitive decisions is implemented in VRepAgent library project. The library is composed of a single class, which the code is available following.
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 |
using System; using System.Collections.Generic; using System.Linq; using Clarion; using Clarion.Framework; using Clarion.Framework.Core; using Clarion.Framework.Templates; using VRepProxy.Objects; using System.Threading; namespace ClarionSimulation { public delegate Perceptions InputVisualSensorialInformationEventHandler(); public delegate void OutputActionChunkEventHandler(ClarionAgentActionType externalAction); public enum ClarionAgentActionType { DO_NOTHING, ROTATE, MOVE, LEFT, RIGHT, BACK, } public class ClarionAgent { #region Constants private String DIMENSION_SENSOR = "SensorialInformation"; private String DIMENSION_TARGET_AHEAD = "TargetAhead"; private String DIMENSION_SOMETHING_AT_FRONT = "SomethingAtFront"; private String DIMENSION_SOMETHING_AT_LEFT = "SomethingAtLeft"; private String DIMENSION_SOMETHING_AT_RIGHT = "SomethingAtRight"; private String DIMENSION_SOMETHING_AT_BACK = "SomethingAtBack"; #endregion #region Properties #region Simulation public double MaxNumberOfCognitiveCycles = -1; private double CurrentCognitiveCycle = 0; public Int32 TimeBetweenCognitiveCycles = 5; private Thread runThread; #endregion #region Agent private Clarion.Framework.Agent CurrentAgent; /// For each cognitive cycle, this event will /// be called in order to the agent /// receives the current sensorial information public event InputVisualSensorialInformationEventHandler OnNewVisualSensorialInformation; /// For each cognitive cycle, this event will /// be called when the agent selects one action public event OutputActionChunkEventHandler OnNewExternalActionSelected; #endregion #region Perception Input private DimensionValuePair InputTargetAhead; private DimensionValuePair InputSomethingAtFront; #endregion #region Action Output private ExternalActionChunk OutputMove; private ExternalActionChunk OutputLeft; private ExternalActionChunk OutputRight; private ExternalActionChunk OutputBack; #endregion #endregion #region Constructor public ClarionAgent() { // Initialize the agent CurrentAgent = World.NewAgent("Current Agent"); // Initialize Input Information InputTargetAhead = World.NewDimensionValuePair(DIMENSION_SENSOR, DIMENSION_TARGET_AHEAD); InputSomethingAtFront = World.NewDimensionValuePair(DIMENSION_SENSOR, DIMENSION_SOMETHING_AT_FRONT); // Initialize Output actions OutputMove = World.NewExternalActionChunk(ClarionAgentActionType.MOVE.ToString()); OutputLeft = World.NewExternalActionChunk(ClarionAgentActionType.LEFT.ToString()); OutputRight = World.NewExternalActionChunk(ClarionAgentActionType.RIGHT.ToString()); OutputBack = World.NewExternalActionChunk(ClarionAgentActionType.BACK.ToString()); //Create thread to simulation runThread = new Thread(RunThread); Console.WriteLine("Agent started"); } #endregion #region Public Methods public void Run() { Console.WriteLine ("Running Simulation ..."); // Setup Agent to run if (runThread != null && !runThread.IsAlive) { SetupAgentInfraStructure(); if (OnNewVisualSensorialInformation != null) { // Start Simulation Thread runThread.Start(null); } } } public void Abort(Boolean deleteAgent) { Console.WriteLine ("Aborting ..."); if (runThread != null && runThread.IsAlive) { runThread.Abort(); } if (CurrentAgent != null && deleteAgent) { CurrentAgent.Die(); } } #endregion #region Setup Agent Methods private void SetupAgentInfraStructure() { // Setup the ACS Subsystem SetupACS(); } private void SetupMS() { //RichDrive } private void SetupACS() { SupportCalculator moveSupportCalculator = FixedRuleDelegateMove; FixedRule ruleMove = AgentInitializer.InitializeActionRule(CurrentAgent, FixedRule.Factory, OutputMove, moveSupportCalculator); CurrentAgent.Commit(ruleMove); SupportCalculator avoidCollisionFrontSupportCalculator = FixedRuleDelegateAvoidColissionAtFront; FixedRule ruleAvoidCollisionFront = AgentInitializer.InitializeActionRule(CurrentAgent, FixedRule.Factory, OutputBack, avoidCollisionFrontSupportCalculator); CurrentAgent.Commit(ruleAvoidCollisionFront); // Disable Rule Refinement CurrentAgent.ACS.Parameters.PERFORM_RER_REFINEMENT = false; // The selectio type will be probabilistic CurrentAgent.ACS.Parameters.LEVEL_SELECTION_METHOD = ActionCenteredSubsystem.LevelSelectionMethods.STOCHASTIC; // The selection of the action will be fixed (not variable) i.e. only the statement defined above. CurrentAgent.ACS.Parameters.LEVEL_SELECTION_OPTION = ActionCenteredSubsystem.LevelSelectionOptions.FIXED; // Define Probabilistic values CurrentAgent.ACS.Parameters.FIXED_FR_LEVEL_SELECTION_MEASURE = 1; CurrentAgent.ACS.Parameters.FIXED_IRL_LEVEL_SELECTION_MEASURE = 0; CurrentAgent.ACS.Parameters.FIXED_BL_LEVEL_SELECTION_MEASURE = 0; CurrentAgent.ACS.Parameters.FIXED_RER_LEVEL_SELECTION_MEASURE = 0; } //Robot robot; /// <summary> /// Make the agent perception. In other words, translate the information that came /// from sensors to a new type that the agent can understand /// </summary> /// <param name="sensorialInformation">The information that came from server</param> /// <returns>The perceived information</returns> private SensoryInformation MakePerceptionFromSensorialInput(Perceptions sensorialInformation) { SensoryInformation si = World.NewSensoryInformation(CurrentAgent); double[] d = sensorialInformation.getPerceivedDistance (); //Coligion at Front // 00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11, 12, 13, 14, 15 // 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 //Coligion at Left // 00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11, 12, 13, 14, 15 // 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0 //Coligion at Right // 00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11, 12, 13, 14, 15 // 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1 //Go to Target Double minFront = (new Double [6] {d[1], d[2], d[3], d[4], d[5], d[6]}).Min (); String color = sensorialInformation.getPerceivedColor (); Boolean target = ((color == Shape.SHAPE_BLUE) || (color == Shape.SHAPE_RED) || (color == Shape.SHAPE_PINK)) && (minFront > 0.2); double targetAheadActivationValue = target ? CurrentAgent.Parameters.MAX_ACTIVATION : CurrentAgent.Parameters.MIN_ACTIVATION; si.Add(InputTargetAhead, targetAheadActivationValue); Boolean somethingAtFront = minFront < 0.2 ; double somethingAtFrontActivationValue = somethingAtFront ? CurrentAgent.Parameters.MAX_ACTIVATION : CurrentAgent.Parameters.MIN_ACTIVATION; si.Add (InputSomethingAtFront, somethingAtFrontActivationValue); return si; } #endregion #region Delegate Methods #region Fixed Rules private double FixedRuleDelegateAvoidColissionAtFront(ActivationCollection currentInput, Rule target) { return ((currentInput.Contains(InputSomethingAtFront, CurrentAgent.Parameters.MAX_ACTIVATION))) ? 1.0 : 0.0; } private double FixedRuleDelegateMove(ActivationCollection currentInput, Rule target) { return ((currentInput.Contains(InputTargetAhead, CurrentAgent.Parameters.MAX_ACTIVATION))) ? 1.0 : 0.0; } #endregion #region Run Thread Method private void RunThread(object obj) { Console.WriteLine("Starting Cognitive Cycle ... press CTRL-C to finish !"); // Cognitive Cycle starts here getting sensorial information while (CurrentCognitiveCycle != MaxNumberOfCognitiveCycles) { // Get current sensorial information Perceptions sensorialInformation = OnNewVisualSensorialInformation(); // Make the perception SensoryInformation si = MakePerceptionFromSensorialInput(sensorialInformation); //Perceive the sensory information CurrentAgent.Perceive(si); //Choose an action ExternalActionChunk chosen = CurrentAgent.GetChosenExternalAction(si); // Get the selected action String actionLabel = chosen.LabelAsIComparable.ToString(); ClarionAgentActionType actionType = (ClarionAgentActionType)Enum.Parse(typeof(ClarionAgentActionType), actionLabel, true); // Call the output event handler if (OnNewExternalActionSelected != null) { OnNewExternalActionSelected(actionType); } // Increment the number of cognitive cycles CurrentCognitiveCycle++; //Wait to the agent accomplish his job if (TimeBetweenCognitiveCycles > 0) { Thread.Sleep(TimeBetweenCognitiveCycles); } } } #endregion #endregion } } |
Code Snippet 4 - VRepAgent library ClarionAgent.cs implementation.
ClarionDEMO is the main project that makes use of both VRepProxy and VRepAgent libraries. It perform the Robot sensor reading and translate them into actions according to the decisions made by the cognitive system. The main class of the project is called Main.cs and its code is shown in the code snippet that follows.
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 |
using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading; using ClarionSimulation; using VRepProxy; using VRepProxy.Objects; using System.Text.RegularExpressions; namespace ClarionDEMO { class MainClass { #region properties private ClarionAgent agent; private static Scene scene; private Robot robot; private static int nextSide = 0; private static int ucounter = 0; private static int currentSide = 0; #endregion #region constructor //Dictionary<string, LeafletItem> Consolidatedleaflets; //Dictionary<string,int> leafletsTotal = new Dictionary<string,int>(); //Dictionary<string,int> leafletsCollected = new Dictionary<string,int>(); public MainClass () { Random rnd = new Random (); int r = rnd.Next (1, 10); nextSide = r > 4 ? (r < 7 ? 1 : 2) : 0; Console.WriteLine ("Clarion-VRep Demo V0.4"); agent = new ClarionAgent (); agent.OnNewVisualSensorialInformation += new InputVisualSensorialInformationEventHandler (agent_OnNewVisualSensorialInformationHandler); agent.OnNewExternalActionSelected += new OutputActionChunkEventHandler (agent_OnNewExternalActionSelectedHandler); scene = new Scene(); robot = scene.GetRobot (); agent.Run (); } #endregion #region Methods public static void Main (string[] args) { new MainClass (); } Perceptions agent_OnNewVisualSensorialInformationHandler () { //Console.Out.WriteLine ("Sensorial Info Handler"); Perceptions perceptions = null; if (scene != null) { perceptions = robot.GetPerceptions (); switch (perceptions.getPerceivedColor ()) { case Shape.SHAPE_BLIND: Console.Out.WriteLine ("I am blind. "); Console.Out.WriteLine ("Cannot connect to the body."); Environment.Exit (-1); break; case Shape.SHAPE_NOTHING: Console.Out.WriteLine ("I see " + perceptions.getPerceivedColor ()); break; case Shape.SHAPE_GRAY: break; case Shape.SHAPE_BLUE: case Shape.SHAPE_PINK: case Shape.SHAPE_RED: Console.Out.WriteLine ("I see " + perceptions.getPerceivedColor () + " color" + " (--> target)"); break; case Shape.SHAPE_GREEN: case Shape.SHAPE_WHITE: case Shape.SHAPE_YELLOW: Console.Out.WriteLine ("I see " + perceptions.getPerceivedColor () + " color"); break; default: Console.Out.WriteLine ("I don't know what I see..."); break; } } return perceptions; } void agent_OnNewExternalActionSelectedHandler (ClarionAgentActionType externalAction) { Thread.CurrentThread.CurrentCulture = new CultureInfo ("en-US"); if (scene != null) { robot.GetWheels ().stop (); switch (externalAction) { case ClarionAgentActionType.DO_NOTHING: if (nextSide == 0) { robot.GetWheels ().RotateRight (0.5); } else if (nextSide == 1) { robot.GetWheels ().RotateLeft (0.5); } else { robot.GetWheels ().Move (1); } if (currentSide != nextSide) ucounter = 0; if (ucounter > 30) { ucounter = 0; Random rnd = new Random (); int r = rnd.Next (1, 10); nextSide = r > 4 ? (r < 8 ? 1 : 2) : 0; } currentSide = nextSide; ucounter++; break; case ClarionAgentActionType.ROTATE: { robot.GetWheels ().RotateRight (0.5); Random rnd = new Random (); int r = rnd.Next (1, 10); nextSide = r > 4 ? (r < 7 ? 1 : 2) : 0; } break; case ClarionAgentActionType.MOVE: { robot.GetWheels ().Move (2); Random rnd = new Random (); int r = rnd.Next (1, 10); nextSide = r > 4 ? (r < 7 ? 1 : 2) : 0; } break; case ClarionAgentActionType.LEFT: { robot.GetWheels ().Move (1); robot.GetWheels ().RotateLeft (1); Random rnd = new Random (); int r = rnd.Next (1, 10); nextSide = r > 4 ? (r < 7 ? 1 : 2) : 0; } break; case ClarionAgentActionType.RIGHT: { robot.GetWheels ().Move (1); robot.GetWheels ().RotateRight (1); Random rnd = new Random (); int r = rnd.Next (1, 10); nextSide = r > 4 ? (r < 7 ? 1 : 2) : 0; } break; case ClarionAgentActionType.BACK: { robot.GetWheels ().Move (-1); robot.GetWheels ().RotateLeft (1); Random rnd = new Random (); int r = rnd.Next (1, 10); nextSide = r > 4 ? (r < 7 ? 1 : 2) : 0; } break; default: break; } } } #endregion } } |
Code Snippet 5 - ClarionDEMO project Main.cs implementation.
3. CONCLUSIONS
Clarion is in general simple to understand and to implement but it can become complex depending on the complexity of the cognitive objectives. For developing this project, it was not required a profound knowledge of Clarion Framework but it required some extra effort to understand and develop the control dynamics of a real robot simulated in VRep environment.
Different from other simulation environmen where macro functions like move and distance to object are already implemented, VRep requires the implementation of all mechanisms involved in a moviment and that is the why the Pioneer P3-DX was choosen. The Pioneer P3-DS has enough complexibility for developing a solution but not too complex to control and implement a workable prototype.
The project phases can be listed as three as follows:
Due to the complexity of the robot engine, as mentioned before, the more time consuming phase above was the Proxy implemention.
Theme by Danetsoft and Danang Probo Sayekti inspired by Maksimer