C# (pronounced as C-Sharp) is the language that was used to build the simulator, using the Unity platform. It is the language injected into your robot when starting a Routine, regardless of which language you used to program (BlockEduc is converted to rEduc which is finally converted to C#).
Programming in C# brings some BIG differences from rEduc, and can allow the user to use very advanced programming methods, but the learning curve is more difficult.
Tip: One of the most efficient ways to learn C# for the simulator is by building programs in rEduc and converting them to this language for reference. It is always possible to view rEduc commands in C# by clicking here.
Programming in C# in sBotics is done through Asynchronous Tasks (async Tasks), allowing the user to pause task execution through the await Time.Delay()
command.
async Task Main() { IO.Print("Printed to console!"); await Time.Delay(2000); IO.PrintLine("Two seconds have passed!"); }
Every C# sBotics program needs to have a Task Main()
, the main block that will be called at the beginning of the Routine to execute the code.
async Task Main() { // code here }
As already mentioned on the Programming page, the simulator and your code run on the same “layer” (thread). So your code DIRECTLY impacts the FPS (“frames per second”, fluidity) of the simulator. Heavy code can end up pausing the simulator for a considerable time causing a crash. You always must use await
to avoid holding the simulator too long in your code.
Example: The code below causes a crash in the simulator:
async Task Main() { while(true) { IO.Print("Crash"); } }
The code below does not cause a crash in the simulator, because of the wait:
async Task Main() { while(true) { IO.Print("Cleared the console! Note that the while true doesn't break the simulator here because of the wait, used to load the rest of the simulator"); await Time.Delay(100); } }
Depending on how heavy your code is, your program might need longer waits, and always consider restarting the simulator from time to time, as the C# compiler tends to slow down the simulator with prolonged usage sessions.
As previously mentioned, user code in sBotics operates synchronously. Therefore, functions involving robot movement, repetitions, and execution of sBotics component commands must be implemented as asynchronous tasks (async tasks), as exemplified earlier. However, simple calculations that are processed in fractions of a second by the C# compiler itself do not require such implementation.
Therefore, functions with “void” return or other return types are allowed. But due to the need to use “waits” to manage repetitions and control robot movement, methods that deal with these operations must be created as asynchronous tasks. Example below:
async Task Main() { await ReadSensors(); } async Task ReadSensors() { IO.PrintLine($"I will read {ComponentsCount()} components!"); await Time.Delay(500); // Other commands here // ... // Since I need to use Time.Delay and work with sBotics, I need this to be an async Task } // The function below is not a subroutine, it just performs calculations that can work "synchronously", and it doesn't need to be an 'async Task' int ComponentsCount() { // Returns the number of components in the robot return Bot.Components.Length; }
Every accessible component has a name, which serves as an access key. In traditional robots, the programmer has to call a function like color(1)
and then figure out which robot sensor corresponds to the sensor on port number 1
. In sBotics you name your own sensor, so you can call your color sensors LeftColorSensor and RightColorSensor, and then the code can be executed as color(“LeftColorSensor”)
or color(“RightColorSensor”)
.
(color
here represents the actual color sensor function, which is more complex than this in C#)
To access a component, just use our generic “GetComponent” method, followed by the component class and its identifier name. Component classes can be seen further down in the text.
// Turns on the LED Light component // To learn more about the "Color" support class, see "Support Classes and Enumerators" Bot.GetComponent<Light>("light name").TurnOn( new Color(255, 255, 255) ); // Wait 5 seconds // To learn about await and the time class, see the "asynchronous programming" part of the text await Time.Delay(5000); // Turn off the light Bot.GetComponent<Light>("light name").TurnOff();
Sensors are components used to receive values from the virtual world and send them to the robot. All sensors have two properties: Digital
and Analog
(properties, for those less familiar with C#, are like methods, but don't take parentheses in writing).
About the properties:
boolean
, that is, returns a true or false value.bool
bool
bool
Color
double
class ColorSensor
The color sensor, of class ColorSensor
, is a “cube”/component responsible for performing readings directly in front of its reading face. The reading is performed at a maximum distance of up to 8 “blocks”, in a 5×5 pixel area from its center. The average of the reading performed is calculated and returned to the user depending on the chosen capture method:
bool reading = Bot.GetComponent<ColorSensor>("my sensor").Digital; // Same as: (Analog.Closest() != Colors.Black);
The digital capture of the color sensor returns true if it is seeing any color that is distant from black, and false if it is seeing the color black.
Color reading = Bot.GetComponent<ColorSensor>("my sensor").Analog;
The analog capture of the color sensor returns an object of the Color support class, being able to perform other methods of this class that are better described below in the text. But already illustrating superficially:
double d = reading.Brightness; // returns the brightness value of the reading in black and white double d = reading.Red; // returns the red value of the reading double d = reading.Green; // returns the green value of the reading double d = reading.Blue; // returns the blue value of the reading string s = reading.ToString(); // returns the name of the closest color read
class UltrasonicSensor
The ultrasonic sensor emits 3 convergent rays from its surface, as in the scheme: /|\
. And if an object is being seen by one of the three rays, its distance can be captured. The ultrasonic range is relatively high, going from immediately in front of it to the convergence point of the multiple rays “blocks” away.
bool reading = Bot.GetComponent<UltrasonicSensor>("my sensor").Digital; // Same as: (Analog != -1);
The digital capture of the ultrasonic sensor returns whether it is seeing any object or not, regardless of its distance.
double reading = Bot.GetComponent<UltrasonicSensor>("my sensor").Analog;
The analog capture of the ultrasonic sensor returns the distance at which it is seeing an object, or -1 if it is not capturing anything.
class TouchSensor
The touch sensor is the simplest sensor in sBotics. Its only function is to return whether it is being pressed by contact with another object.
bool reading = Bot.GetComponent<TouchSensor>("my sensor").Digital;
The digital capture of the touch sensor returns whether it is being pressed or not at the moment of the call.
The class in question does not have analog access property.
class Buzzer
The buzzer is used to play sounds. It is possible to access it using the syntax below:
Bot.GetComponent<Buzzer>("my buzzer");
Public properties:
bool .Playing:
Returns if the buzzer is playing any sound at that moment;Public methods:
void .StopSound()
: Stops playing the sound coming from the buzzer;void .PlaySound(double hertz, WaveFormats wave = WaveFormats.Square)
: Plays a sound at the specified frequency and in a certain wave format (not mandatory);void .PlaySound(string note, WaveFormats wave = WaveFormats.Square)
: Plays a sound if the text “note” equals any item in the Solfege or Notes support enumerator, in a certain wave format (not mandatory);void .PlaySound(Solfege note, WaveFormats wave = WaveFormats.Square)
: Plays a sound from the Solfege enumerator (Do, Re, Mi, Fa, Sol, La, Si, Do) in a certain wave format (not mandatory);void .PlaySound(Notes note, WaveFormats wave = WaveFormats.Square)
: Plays a sound from the Notes enumerator (C, D, E, F, G, A, B) in a certain wave format (not mandatory).class Light
The LED light is used to display colors. It is possible to access it using the syntax below:
Bot.GetComponent<Light>("my LED");
Public properties:
bool .Lit
: Returns if the light is on or not.Color .Color
: Returns the current color (Color support class) of the lamp.Public methods:
void .TurnOn(Color color)
: Turns on the light in the specified color (Color support class);void .TurnOff()
: Turns off the lamp, returning it to the “original” blue tone.Note: To turn on the lamp using a string, see the static methods of the Color support class like Color.ToColor(“Black”).
class Servomotor
The servomotor is a component that can be rotated using a force, locked and (for not being just a motor, but a servomotor) can have its angle returned. To access a motor, use the syntax below:
Bot.GetComponent<Servomotor>("my motor");
Public properties:
double .Angle
: Use this to get the current rotation angle of the motor;bool .Locked
: Boolean that can be set to lock any motor rotation (default value = true
);double .Force
: Double to set the incremental force (positive only) that will be exerted by the motor on its connected objects to reach the Target/Desired speed.double .Target
: Maximum desired speed that the motor can reach, can be positive or negative thus determining the direction of rotation.Public Method:
void Apply(double force, double target)
: Applies a force to the motor to reach a certain speed. For most cases just use the same value but pass the first parameter as positive (see example further down with Math.Abs).class Bot = __sBotics__RobotController
Bot is not “just another” component, it only represents the reserved class sBoticsRobotController
, which cannot be directly called by the user (for having the sBotics
prefix) and is included in only a single piece in the robot, like a “central component”. However, there are some properties and methods that can be used.
Public properties:
double .Inclination
: Returns the angulation of the central component in relation to the plane;double .Compass
: Returns the angulation of the central component in relation to geographic north;double .Speed
: Returns the speed at which the central component is moving.Public methods:
T .GetComponent<T>(string name)
Already discussed countless times in this text, searches for the component of class T with the specified name;
string[] .Components()
Returns a string array (string[]
) with the name of all components.
Some support classes were created to facilitate development, offering functionalities that simplify common tasks and improve programming understanding.
The Color class is the largest support class in the simulator. With it, it is possible to perform operations with colors, check which is the closest color, pull the lighting value, etc.
Constructor:
// Format: Color Color(double, double, double) Color myColor = new Color(60, 128, 255);
Public properties:
double .Brightness
: Returns the black-and-white brightness of the seen color (from 0 to 255, where 0 is black and 255 is white), and can be used to focus on absolute brightness values more than colors for example.double .Red
: Returns the red value in the color, using the color built above, it would be 60.double .Green
: Returns the green value in the color, using the color built above, it would be 128.double .Blue
: Returns the blue value in the color, using the color built above, it would be 255.Public methods:
string ToString()
: Returns the name of the closest color, like “Red”, or “Black” (depending on your code language);Colors Closest()
: Returns the closest color to the informed color, ex. Colors closest = myColor.Closest(). For more information, see the Colors enumerator below to see the possible colors;double DistanceTo(Color)
: Returns the absolute distance between two colors. It's worth noting that the human eye sees the difference between two colors differently, since some colors have more “weight” in our brain than others. Ex. myColor.DistanceTo(new Color(255, 255, 255)).Static methods:
Color Color.ToColor(Colors color)
: Transforms the color in Colors format (see enumeration below) into a Color type color. Ex. Color myNewColor = Color.ToColor(Colors.Magenta);Color Color.ToColor(string color)
: Transforms the color in string format (depends on your code language) into Color. Ex. Color myNewColor = Color.ToColor(“Green”).The Time class has methods for time management, and can do things like stopwatches and asynchronously wait for a time interval. These methods are:
Static properties:
double Time.Timestamp
: Returns the value that corresponds to the current time in seconds, and can be used to create stopwatches.Static methods:
Time.Delay(double ms)
: Task that waits for the value in milliseconds and can be used using the await keyword in async methods.The IO class is used for printing data to a virtual console or to a local text file on the computer. It's worth noting that the functioning of the console and text file are different from how they were originally created in 2020. These methods are:
Static console writing methods:
void IO.ClearPrint()
: Clears the console;void IO.Print(string text)
: Clears the console and writes the informed text on the first line;void IO.PrintLine(string text)
: Pushes the console content one line down and writes the informed text on the new line.Static file writing methods:
void IO.ClearWrite()
: Clears the text file;void IO.Write(string text)
: Clears the text file and writes the informed text on the first line;void IO.WriteLine(string text)
: Pushes the text file content one line down and writes the informed text on the new line.Other static methods/properties:
bool IO.Timestamp
: Boolean used to define if printed messages will have time marking (hh:mm:ss);void IO.OpenConsole()
: Method used to automatically open the console through code.The Utils class has some mathematical methods to help C# and rEduc programmer development. These being:
Static methods:
double Utils.Clamp(double value, double minValue, double MaxValue)
: Returns the value between the minimum and maximum, transforming your number to the extremes if it “exceeds them”;double Utils.Map(double value, double minA, double maxA, double minB, double maxB)
: Maps the value in the first parameter that is on the scale between minA and maxA to the scale between minB and maxB;double Utils.Random(double min, double max)
: Returns a random value between the informed minimum and maximum;double Utils.SetPrecision(double value, double precision)
: Alias for Math.Round(double, int), returns the same double but with the number of decimal places specified in the second parameter;double Utils.Modulo(double value, double mod)
: Performs the value % mod operation, but with support for negative values, since in C# -1 % 5 = -1, and the expected would be -1 % 5 = 4.Some enumerators were created to help in development.
Color
class.Notes
enumerator.Although this tutorial is quite complete, it may not show all the depth that is possible to achieve with C# for sBotics (with objects like the camera or 3D pen, for example). Therefore, it is recommended that you as a user are always looking for content.
The source code of the programmable part (sensors, components, etc.) of sBotics is available on Github: sBotics/Programming Reference.
There it is possible to understand exactly how the sensors work and know all the methods, classes, properties, enums, namespaces, etc. that the user has access to.
It is also possible to see the “translation” that rEduc makes to C# at sBotics Functions, and see exactly the name of each rEduc command and its equivalent C# code for study purposes.
Below is an example code of a simple line follower for the default “Trekker” robot of sBotics using C#.
// Control Functions async Task Forward(double speed = 200) { Bot.GetComponent<Servomotor>("l").Locked = false; // Unlocks the left motor Bot.GetComponent<Servomotor>("r").Locked = false; // Unlocks the right motor Bot.GetComponent<Servomotor>("l").Apply(Math.Abs(speed), speed); // * Bot.GetComponent<Servomotor>("r").Apply(Math.Abs(speed), speed); // * } async Task Right(double speed = 200) { Bot.GetComponent<Servomotor>("r").Apply(0, 0); Bot.GetComponent<Servomotor>("r").Locked = true; // Locks the right motor Bot.GetComponent<Servomotor>("l").Locked = false; // Unlocks the left motor Bot.GetComponent<Servomotor>("l").Apply(Math.Abs(speed * 2), speed * 2); //* } async Task Left(double speed = 200) { Bot.GetComponent<Servomotor>("l").Apply(0, 0); Bot.GetComponent<Servomotor>("l").Locked = true; // Locks the left motor Bot.GetComponent<Servomotor>("r").Locked = false; // Unlocks the right motor Bot.GetComponent<Servomotor>("r").Apply(Math.Abs(speed * 2), speed * 2); // * } async Task Backward(double speed = 200) { Bot.GetComponent<Servomotor>("l").Locked = false; // Unlocks the left motor Bot.GetComponent<Servomotor>("r").Locked = false; // Unlocks the right motor Bot.GetComponent<Servomotor>("l").Apply(Math.Abs(0 - speed), 0 - speed); // * Bot.GetComponent<Servomotor>("r").Apply(Math.Abs(0 - speed), 0 - speed); // * } // * - // Math.Abs to turn any value positive, since the first parameter cannot be negative. async Task FinalLed() { for (int i = 1; i <= 5; i++) { Bot.GetComponent<Light>("led").TurnOn(new Color(255, 0, 0)); await Time.Delay(50); Bot.GetComponent<Light>("led").TurnOn(new Color(0, 255, 0)); await Time.Delay(50); Bot.GetComponent<Light>("led").TurnOn(new Color(0, 0, 255)); await Time.Delay(50); } } async Task Main() { while (true) { await Time.Delay(1); // NECESSARY SO THE APPLICATION DOESN'T CRASH IO.OpenConsole(); IO.PrintLine("Writing to Console"); if (((Bot.GetComponent<ColorSensor>("rc").Analog).ToString() == "Black") && ((Bot.GetComponent<ColorSensor>("lc").Analog).ToString() != "Black")) { Bot.GetComponent<Light>("led").TurnOn(new Color(0, 0, 255)); await Right(300); } else if (((Bot.GetComponent<ColorSensor>("rc").Analog).ToString() != "Black") && ((Bot.GetComponent<ColorSensor>("lc").Analog).ToString() == "Black")) { Bot.GetComponent<Light>("led").TurnOn(new Color(255, 0, 0)); await Left(300); } else if (((Bot.GetComponent<ColorSensor>("rc").Analog).ToString() == "Black") && ((Bot.GetComponent<ColorSensor>("lc").Analog).ToString() == "Black")) { Bot.GetComponent<Light>("led").TurnOn(new Color(0, 255, 0)); await Forward(); } else if (((Bot.GetComponent<ColorSensor>("rc").Analog).ToString() == "Red") || ((Bot.GetComponent<ColorSensor>("lc").Analog).ToString() == "Red")) { await Forward(0); await FinalLed(); } else { Bot.GetComponent<Light> ("led").TurnOn(new Color(255, 0, 255)); await Forward(); } } }
If you are a less advanced user who is just looking for an easy way to control your robot, consider switching to rEduc.