Tabela de conteúdos
C#
C# (pronunciado como C-Sharp) é a linguagem que foi utilizada para construir o simulador, usando a plataforma Unity. Ela é a linguagem injetada no seu robô ao iniciar uma Rotina, independente de que linguagem utilizou para programar (BlockEduc é convertido em rEduc que por fim é convertido em C#).
Programar em C# traz algumas GRANDES diferenças do rEduc, e pode permitir o usuário a usar métodos bem avançados de programação, porém a curva de aprendizado é mais difícil.
Dica: Uma das formas mais eficientes de se aprender o C# do simulador é construindo programas em rEduc e convertendo-os para esta linguagem, para usar de referência. É sempre possível visualizar os comandos do rEduc em C# clicando aqui.
A programação em C# no sBotics é dada através de Tarefas Assíncronas (async Tasks), permitindo que o usuário possa pausar a execução das tarefas através do comando await Time.Delay()
.
async Task Main() { IO.Print("Imprimi no console!"); await Time.Delay(2000); IO.PrintLine("Dois segundos se passaram!"); }
Todo programa C# sBotics precisa ter uma Task Main()
, bloco principal que será chamado no início da Rotina para executar o código.
async Task Main() { // código aqui }
Aviso Importante sobre Busy-Waiting
Como já informado na página de Programação, o simulador e o seu código rodam na mesma “camada” (thread). Então o seu código impacta DIRETAMENTE no FPS (“quadros por segundo”, fluidez) do simulador. Códigos pesados podem acabar pausando o simulador por um tempo considerável causando um crash. Você sempre deve usar await
para evitar segurar o simulador por tempo demais no seu código.
Exemplo: O código abaixo causa um crash no simulador:
async Task Main() { while(true) { IO.Print("Crash"); } }
Já o código abaixo não causa um crash no simulador, por conta da espera:
async Task Main() { while(true) { IO.Print("Limpei o console! Note que o while true não quebra o simulador aqui por causa da espera, usada para carregar o resto do simulador"); await Time.Delay(100); } }
Dependendo do quão pesado seu código for, pode ser que seu programa precise de esperas maiores, e sempre considere reiniciar o simulador de tempos em tempos, já que o compilador C# tende a deixar o simulador lento com sessões prolongadas de uso.
Funções Síncronas e Assíncronas
Conforme previamente mencionado, o código do usuário no sBotics opera de forma síncrona. Sendo assim, funções que envolvem movimentação do robô, repetições e a execução de comandos de componentes sBotics devem ser implementadas como tarefas assíncronas (async tasks), conforme exemplificado anteriormente. Entretanto, cálculos simples, que são processados em frações de segundo pelo próprio compilador C#, não exigem tal implementação.
Portanto, funções com retorno “void” ou outros tipos de retorno são permitidas. Porém devido à necessidade de utilizar “waits” para gerenciar repetições e controlar a movimentação do robô, métodos que lidam com essas operações devem ser criadas como tarefas assíncronas. Exemplo abaixo:
async Task Main() { await LerSensores(); } async Task LerSensores() { IO.PrintLine($"Irei ler {ComponentsCount()} componentes!"); await Time.Delay(500); // Outros comandos aqui // ... // Como preciso usar Time.Delay e mexer com o sBotics, preciso que seja uma async Task } // Já a função abaixo não é uma subrotina, apenas realiza cálculos que podem funcionar de forma "sícrona", e ela não precisa ser uma 'aync Task' int ComponentsCount() { // Retorna a quantidade de componentes no robô return Bot.Components.Length; }
Acessando Componentes
Todo componente acessível possui um nome, que serve como chave de acesso. Em robôs, tradicionais o programador tem que chamar uma função como cor(1)
e então descobrir qual sensor do robô corresponde ao sensor na porta de número 1
. Já no sBotics você nomeia seu próprio sensor, então você pode chamar seus sensores de cor como SensorCorEsquerdo e SensorCorDireito, e assim o código pode ser executado como cor(“SensorCorEsquerdo”)
ou cor(“SensorCorDireito”)
.
(cor
aqui representa a função real do sensor de cor, que é mais complexa que isso em C#)
Para acessar um componente, basta utilizar nosso método genérico “GetComponent”, seguido pela classe do componente e seu nome identificador. As classes dos componentes podem ser vistas mais abaixo no texto.
// Liga o componente Luz LED // Para saber mais sobre a classe de apoio "Color", ver "Classes de apoio e Enumeradores" Bot.GetComponent<Light>("nome da luz").TurnOn( new Color(255, 255, 255) ); // Espera 5 segundos // Para aprender sobre o await e a classe time, ver a parte de "programação assíncrona" do texto await Time.Delay(5000); // Desliga a luz Bot.GetComponent<Light>("nome da luz").TurnOff();
Sensores
Sensores são componentes utilizados para receber valores do mundo virtual e envia-los para o robô. Todos os sensores possuem duas propriedades: Digital
e Analog
(propriedades, para os menos familiarizados com C#, são como métodos, porém não levam parênteses na escrita).
Sobre as propriedades:
- Digital: É do tipo
boolean
, ou seja, retorna um valor de verdadeiro ou falso.- Sensor Cor:
bool
- Sensor Ultrassônico:
bool
- Sensor Toque:
bool
- Analógico: Retorna de uma forma diferente dependendo do sensor.
- Sensor Cor:
Color
- Sensor Ultrassônico:
double
- Sensor Toque: Não Possui
Classes dos Componentes
Sensor de Cor
class ColorSensor
O sensor de cor, de classe ColorSensor
, é um “cubo”/componente responsável por realizar leituras diretamente a frente de sua face de leitura. A leitura é realizada a uma distancia máxima de até 8 “blocos”, em uma área de 5×5 pixels a partir de seu centro. É calculada a média da leitura realizada e retornada ao usuário dependendo da forma de captura escolhida:
Digital
bool leitura = Bot.GetComponent<ColorSensor>("meu sensor").Digital; // Mesma coisa de: (Analog.Closest() != Colors.Black);
A captura digital do sensor de cor retorna verdadeiro caso o mesmo esteja vendo qualquer cor que seja distante de preto, e falso caso o mesmo esteja vendo a cor preta.
Analog
Color leitura = Bot.GetComponent<ColorSensor>("meu sensor").Analog;
Já a captura analógica do sensor de cor, retorna um objeto da classe de apoio Color, podendo realizar outros métodos dessa classe que são melhores descritos abaixo no texto. Mas já ilustrando superficialmente:
double d = leitura.Brightness; // retorna o valor de luminosidade da leitura em preto e branco double d = leitura.Red; // retorna o valor de vermelho da leitura double d = leitura.Green; // retorna o valor de verde da leitura double d = leitura.Blue; // retorna o valor de azul da leitura string s = leitura.ToString(); // retorna o nome da cor mais próxima lida
Sensor Ultrassônico
class UltrasonicSensor
O sensor ultrassônico emite 3 raios convergentes de sua superfície, como no esquema: /|\
. E caso um objeto esteja sendo visto por um dos três raios, sua distância pode ser captada. O alcance do ultrassônico é relativamente alto, indo de imediatamente a sua frente até o ponto de convergência dos raios múltiplos “blocos” de distância.
Digital
bool leitura = Bot.GetComponent<UltrasonicSensor>("meu sensor").Digital; // Mesma coisa de: (Analog != -1);
A captura digital do sensor ultrassônico retorna se o mesmo está vendo ou não algum objeto, independente de sua distância.
Analog
double leitura = Bot.GetComponent<UltrasonicSensor>("meu sensor").Analog;
Já a captura analógica do sensor ultrassônico retorna a distância no qual está vendo um objeto, ou -1 caso não esteja captando nada.
Sensor de Toque
class TouchSensor
O sensor de toque é o sensor mais simples do sBotics. Sua única função é retornar se o mesmo está sendo pressionado pelo contato de outro objeto.
Digital
bool leitura = Bot.GetComponent<TouchSensor>("meu sensor").Digital;
A captura digital do sensor de toque retorna se o mesmo está sendo pressionado ou não no momento da chamada.
Analog
A classe em questão não possui propriedade de acesso analógica.
Buzzer
class Buzzer
O buzzer é utilizado para tocar sons. É possível acessá-lo utilizando a sintaxe abaixo:
Bot.GetComponent<Buzzer>("meu buzzer");
Propriedades públicas:
bool .Playing:
Retorna se o buzzer está tocando algum som naquele momento;
Métodos públicos:
void .StopSound()
: Para de tocar o som saíndo do buzzer;void .PlaySound(double hertz, WaveFormats wave = WaveFormats.Square)
: Toca um som caso na frequencia especificada e em um determinado formato de onda (não obrigatório);void .PlaySound(string note, WaveFormats wave = WaveFormats.Square)
: Toca um som caso o texto “note” equivaler a algum item no enumerador de apoio Solfege ou Notes, em um determinado formato de onda (não obrigatório);void .PlaySound(Solfege note, WaveFormats wave = WaveFormats.Square)
: Toca um som do enumerador Solfege (Do, Re, Mi, Fa, Sol, La, Si, Do) em um determinado formato de onda (não obrigatório);void .PlaySound(Notes note, WaveFormats wave = WaveFormats.Square)
: Toca um som do enumerador Notes (C, D, E, F, G, A, B) em um determinado formato de onda (não obrigatório).
Luz LED
class Light
A luz LED (light) é utilizado para mostrar cores. É possível acessá-la utilizando a sintaxe abaixo:
Bot.GetComponent<Light>("meu LED");
Propriedades públicas:
bool .Lit
: Retorna se a luz está ligada ou não.Color .Color
: Retorna a cor (classe de apoio Color) atual da lâmpada.
Métodos públicos:
void .TurnOn(Color color)
: Liga a luz na cor (classe de apoio Color) especificada;void .TurnOff()
: Desliga a lâmpada, a retornando para a tonalidade azul “original”.
Ps.: Para ligar a lâmpada usando uma string, ver os métodos estáticos da classe de apoio Color como Color.ToColor(“Preto”).
Servomotor
class Servomotor
O servomotor é um componente que pode ser rotacionado utilizando uma força, travado e (por não ser apenas um motor, mas sim um servomotor) pode ter seu ângulo retornado. Para acessar um motor, utilize a sintaxe abaixo:
Bot.GetComponent<Servomotor>("meu motor");
Propriedades públicas:
double .Angle
: Utilize isso para pegar o ângulo atual de rotação do motor;bool .Locked
: Booleano que pode ser definido para travar qualquer rotação do motor (valor padrão= true
);double .Force
: Double para definir a força (positiva apenas) incremental que será exercida pelo motor aos seus objetos conectados para alcançar a velocidade Target/Desejada.double .Target
: Velocidade máxima desejada que o motor pode alcançar, pode ser positiva ou negativa determinando assim o sentido de rotação.
Método Público:
void Apply(double force, double target)
: Aplica uma força ao motor para alcançar uma determinada velocidade. Para a maioria dos casos basta usar o mesmo valor porém passar o primeiro parâmetro como positivo (ver exemplo mais abaixo com Math.Abs).
Sensores Internos do Robô / Controlador
class Bot = __sBotics__RobotController
Bot não é “só mais um” componente, ela representa apenas a classe reservada sBoticsRobotController
, que não pode ser diretamente chamada pelo usuário (por possuir o prefixo sBotics
) e está inclusa apenas em uma única peça no robô, como um “componente central”. Entretanto, há algumas propriedades e métodos que podem ser utilizados.
Propriedades públicas:
double .Inclination
: Retorna a angulação do componente central em relação ao plano;double .Compass
: Retorna a angulação do componente central em relação ao norte geográfico;double .Speed
: Retorna a velocidade no qual o componente central está se deslocando.
Métodos públicos:
T .GetComponent<T>(string name)
Já discutido inúmeras vezes neste texto, busca o componente da classe T com o nome especificado;
string[] .Components()
Retorna um array de string (string[]
) com o nome de todos os componentes.
Classes de Apoio
Algumas classes de apoio foram criadas para facilitar o desenvolvimento, oferecendo funcionalidades que simplificam tarefas comuns e melhoram o entendimento da programação.
Color
A classe Color é a maior classe de apoio do simulador. Com ela é possível realizar operações com cores, verificar qual a cor mais próxima, puxar o valor de iluminação, etc.
Construtor:
// Formato: Color Color(double, double, double) Color minhaCor = new Color(60, 128, 255);
Propriedades públicas:
double .Brightness
: Retorna a luminosidade em preto-e-branco da cor vista (de 0 a 255, onde 0 é preto e 255 é branco), podendo ser utilizado para focar em valores absolutos de luminosidade mais do que cores por ex.double .Red
: Retorna o valor de vermelho na cor, utilizando a cor construída acima, seria 60.double .Green
: Retorna o valor de verde na cor, utilizando a cor construída acima, seria 128.double .Blue
: Retorna o valor de azul na cor, utilizando a cor construída acima, seria 255.
Métodos públicos:
string ToString()
: Retorna o nome da cor mais próxima, como “Vermelho”, ou “Black” (dependendo do idioma do seu código);Colors Closest()
: Retorna a cor mais próxima da cor informada, ex. Colors maisProxima = minhaCor.Closest(). Para mais informações, ver o enumerador Colors abaixo para ver as cores possíveis;double DistanceTo(Color)
: Retorna a distância absoluta entre duas cores. Vale ressaltar que o olho humano vê a diferença entre duas cores de forma diferente, já que algumas cores possuem mais “peso” no nosso cérebro que outras. Ex. minhaCor.DistanceTo(new Color(255, 255, 255)).
Métodos estáticos:
Color Color.ToColor(Colors color)
: Transforma a cor no formato Colors (ver enumeração abaixo) em uma cor do tipo Color. Ex. Color minhaNovaCor = Color.ToColor(Colors.Magenta);Color Color.ToColor(string color)
: Transforma a cor no formato string (depende do idioma do seu código) em Color. Ex. Color minhaNovaCor = Color.ToColor(“Verde”).
Time
A classe Time possui métodos para gerenciamento de tempo, e poder realizar coisas como cronômetros e esperar assíncronamente um intervalo de tempo. Esses métodos são:
Propriedades estáticas:
double Time.Timestamp
: Retorna o valor que corresponde ao tempo atual em segundos, podendo ser utilizado para criar cronômetros.
Métodos estáticos:
Time.Delay(double ms)
: Tarefa que espera o valor em milissegundos e pode ser utilizado utilizando a palavra-chave await em métodos async.
IO
A classe IO é utilizada para impressão de dados em um console virtual ou em um arquivo de texto local no computador. Vale ressaltar que o funcionamento do console e do arquivo de texto estão diferentes da forma que foram originalmente criados em 2020. Esses métodos são:
Métodos estáticos de escrita em console:
void IO.ClearPrint()
: Limpa o console;void IO.Print(string text)
: Limpa o console e escreve na primeira linha o texto informado;void IO.PrintLine(string text)
: Empurra o conteúdo do console uma linha abaixo e escreve na nova linha o texto informado.
Métodos estáticos de escrita em arquivo:
void IO.ClearWrite()
: Limpa o arquivo de texto;void IO.Write(string text)
: Limpa o arquivo de texto e escreve na primeira linha o texto informado;void IO.WriteLine(string text)
: Empurra o conteúdo do arquivo de texto uma linha abaixo e escreve na nova linha o texto informado.
Outros métodos/propiedades estáticas:
bool IO.Timestamp
: Booleano usado para definir se mensagens impressas terão marcação de horário (hh:mm:ss);void IO.OpenConsole()
: Método utilizado para abrir o console automaticamente atravez do código.
Utils
A classe Utils possui alguns métodos matemáticos para ajudar o desenvolvimento do programador C# e rEduc. Sendo esses:
Métodos estáticos:
double Utils.Clamp(double value, double minValue, double MaxValue)
: Retorna o valor entre o mínimo e o máximo, transformando seu número nos extremos caso ele “ultrapasse-os”;double Utils.Map(double value, double minA, double maxA, double minB, double maxB)
: Mapeia o valor no primeiro parâmetro que está na escala entre minA e maxA para a escala entre minB e maxB;double Utils.Random(double min, double max)
: Retorna um valor aleatório entre o minimo e o máximo informado;double Utils.SetPrecision(double value, double precision)
: Alias para Math.Round(double, int), retorna o mesmo double porém com o valor de casas decimais especificadas no segundo parâmetro;double Utils.Modulo(double value, double mod)
: Realiza a operação value % mod, porém com suporte a valores negativos, já que no C# -1 % 5 = -1, e o esperado seria -1 % 5 = 4.
Enumeradores
Alguns enumeradores foram criados para ajudar no desenvolvimento.
- Colors: Colors representam algumas cores padrão, e podem ser utilizados para algumas comparações e métodos estáticos da classe
Color
.- Black
- White
- Green
- Red
- Blue
- Yellow
- Magenta
- Cyan
- Notes: Notes podem ser utilizadas para tocar sons utilizando o Buzzer.
- C
- D
- E
- F
- G
- A
- B
- Solfege: O solfège realiza a mesma função das notas, porém línguas que vêm do latim utilizam esse sistema em oposição ao sistema de letras visto no enumerador
Notes
.- Do
- Re
- Mi
- Fa
- Sol
- La
- Si
- WaveFormats: Os formatos de onda, ou WaveFormats são utilizados para tocar sons no buzzer, eles dão uma “textura” diferente ao som, podendo ser utilizados como percursão ou 'outros instrumentos'.
- Mute
- Square
- Sawtooth
- Noise
Buscando Comandos
Embora este tutorial seja bastante completo, ele pode não mostrar toda a profundidade que é possível alcançar com o C# para sBotics (com objetos como a câmera ou caneta 3D, por exemplo). Sendo assim, é recomendável que você como usuário esteja sempre atrás de conteúdos.
Código Fonte
O código fonte da parte programável (sensores, componentes, etc) do sBotics está disponível em Github: sBotics/Programming Reference.
Lá é possível entender como funcionam os sensores exatamente e saber todos os métodos, classes, propriedades, enums, namespaces, etc. que o usuário tem acesso.
Do rEduc
É possível ver também a “tradução” que o rEduc faz para C# em Funções sBotics, e ver exatamente o nome de cada comando rEduc e o seu código C# equivalente para fins de estudo.
Exemplo
Segue abaixo um código de exemplo de um simples seguidor de linha para o robô “Trekker” padrão do sBotics usando rEduc.
// Funções de Controle async Task Frente(double velocidade = 200) { Bot.GetComponent<Servomotor>("l").Locked = false; // Destrava o motor da esquerda Bot.GetComponent<Servomotor>("r").Locked = false; // Destrava o motor da direita Bot.GetComponent<Servomotor>("l").Apply(Math.Abs(velocidade), velocidade); // * Bot.GetComponent<Servomotor>("r").Apply(Math.Abs(velocidade), velocidade); // * } async Task Direita(double velocidade = 200) { Bot.GetComponent<Servomotor>("r").Apply(0, 0); Bot.GetComponent<Servomotor>("r").Locked = true; // Trava o motor da direita Bot.GetComponent<Servomotor>("l").Locked = false; // Destrava o motor da esquerda Bot.GetComponent<Servomotor>("l").Apply(Math.Abs(velocidade * 2), velocidade * 2); //* } async Task Esquerda(double velocidade = 200) { Bot.GetComponent<Servomotor>("l").Apply(0, 0); Bot.GetComponent<Servomotor>("l").Locked = true; // Trava o motor da esquerda Bot.GetComponent<Servomotor>("r").Locked = false; // Destrava o motor da direita Bot.GetComponent<Servomotor>("r").Apply(Math.Abs(velocidade * 2), velocidade * 2); // * } async Task Tras(double velocidade = 200) { Bot.GetComponent<Servomotor>("l").Locked = false; // Destrava o motor da esquerda Bot.GetComponent<Servomotor>("r").Locked = false; // Destrava o motor da direita Bot.GetComponent<Servomotor>("l").Apply(Math.Abs(0 - velocidade), 0 - velocidade); // * Bot.GetComponent<Servomotor>("r").Apply(Math.Abs(0 - velocidade), 0 - velocidade); // * } // * - // Math.Abs para tornar qualquer valor em positivo, já que o primeiro parâmetro não pode ser negativo. async Task LedFinal() { 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); // NECESSÁRIO PARA A APLICAÇÃO NÃO DAR CRASH IO.OpenConsole(); IO.PrintLine("Escrevendo no Console"); if (((Bot.GetComponent<ColorSensor>("rc").Analog).ToString() == "Preto") && ((Bot.GetComponent<ColorSensor>("lc").Analog).ToString() != "Preto")) { Bot.GetComponent<Light>("led").TurnOn(new Color(0, 0, 255)); await Direita(300); } else if (((Bot.GetComponent<ColorSensor>("rc").Analog).ToString() != "Preto") && ((Bot.GetComponent<ColorSensor>("lc").Analog).ToString() == "Preto")) { Bot.GetComponent<Light>("led").TurnOn(new Color(255, 0, 0)); await Esquerda(300); } else if (((Bot.GetComponent<ColorSensor>("rc").Analog).ToString() == "Preto") && ((Bot.GetComponent<ColorSensor>("lc").Analog).ToString() == "Preto")) { Bot.GetComponent<Light>("led").TurnOn(new Color(0, 255, 0)); await Frente(); } else if (((Bot.GetComponent<ColorSensor>("rc").Analog).ToString() == "Vermelho") || ((Bot.GetComponent<ColorSensor>("lc").Analog).ToString() == "Vermelho")) { await Frente(0); await LedFinal(); } else { Bot.GetComponent<Light> ("led").TurnOn(new Color(255, 0, 255)); await Frente(); } } }
Caso seja um usuário menos avançado que busca apenas uma forma fácil de controlar o seu robô, cogite alterar para rEduc.