banner

Ir para o Forum

Arduino IDE com Dual Core: Controle Remoto



Esse vídeo de hoje, posso dizer que ele é “Multicoisas”! Ou seja, tratamos de Multitarefas, Multicore e Multiclients. Há um tempo atrás eu fiz um controle remoto com dois ESPs: um client e um access point. E, baseado nisto, vamos, neste projeto de hoje, montar um servidor multiclient, o que quer dizer que teremos vários clientes conectados em um único ESP.


Nesta aula, portanto, nós iremos criar um server no ESP32 e adicionar novos clientes no loop, além de tratar as requisições em outro core. Os clientes irão enviar informação sobre a mudança de estado dos seus pinos e o server irá reproduzir estas mudanças de estado.


Demonstração




Montagem Server




Montagem Client



Fluxo

Server




Client




Client.ino

Declarações e variáveis

#include <WiFi.h>

//Dados da rede
//Deve ser giual no Server
#define SSID "ESP32Server"
#define PASSWORD "87654321"
#define SERVER_PORT 5000

//Objeto que vai fazer a conexão com o server
WiFiClient client;

//Struct que define os dados que vamos enviar (deve ser igual no server)
typedef struct{
    int number;
    int status;
}Pin;

//Quantidade de pinos que iremos ler e enviar o status
#define PIN_COUNT 2

//Array com os pinos definidos 
//No caso vamos trabalhar com os 21 e 19 mas você pode alterar para os pinos que desejar
Pin pins[PIN_COUNT] = {
    {.number = 21},
    {.number = 19}
};



Setup

void setup(){
    Serial.begin(115200);

    //Tempo para considerar a conexão como perdida
    client.setTimeout(5000);

    //Conectamos à rede WiFi e conectamos ao server
    setupWiFi();
    connectClient();
    
    for(int i=0; i<PIN_COUNT; i++){
        pinMode(pins[i].number, INPUT);
        sendPinStatus(i); //Enviamos para o server o estado atual dos pinos
    }
    
    //Geramos uma nova tarefa
    xTaskCreatePinnedToCore(
        handleConnection,   //Função que será executada
        "handleConnection", //Nome da tarefa
        10000,      //Tamanho da pilha
        NULL,       //Parâmetro da tarefa (no caso não usamos)
        2,          //Prioridade da tarefa
        NULL,       //Caso queria manter uma referência para a tarefa que vai ser criada (no caso não precisamos)
        0);         //Número do core que será executada a tarefa (usamos o core 0 para o loop ficar livre com o core 1)
}



Setup WiFi

void setupWiFi()
{
    Serial.print("Connecting to " + String(SSID));
    //Conectamos à rede WiFi criado pelo outro ESP
    WiFi.begin(SSID, PASSWORD);

    //Esperamos conectar
    while (WiFi.status() != WL_CONNECTED)
    {
        Serial.print(".");
        delay(500);
    }

    //Se chegou aqui está conectado à rede WiFi
    Serial.println();
    Serial.println("Connected!");
}



ConnectClient

void connectClient()
{
    Serial.println("Connecting client");
    //Esperamos conectar com o server
    while (!client.connect(WiFi.gatewayIP(), SERVER_PORT))
    {
        Serial.print(".");
        delay(500);
    }

    //Se chegou aqui está conectado com o server
    Serial.println();
    Serial.println("Client connected!");
}



Loop

void loop()
{ 
    //Se não estiver conectado à rede WiFi, mandamos conectar
    if(WiFi.status() != WL_CONNECTED)
    {
        setupWiFi();
    }
}



HandleConnection

void handleConnection(void* pvParameters)
{
    //IMPORTANTE: A tarefa não pode terminar, deve ficar presa em um loop infinito
    while(true)
    {
        //Se não estiver conectado com o server, mandamos conectar
        if(!client.connected())
        {
            connectClient();
        }

        //Para cada pino, verificamos se mudou o estado. Se mudou enviamos para o server o novo estado
        for(int i=0; i<PIN_COUNT; i++)
        {
            if(hasPinStatusChanged(i))
            {
                sendPinStatus(i);
            }
        }

        //Delay de 5ms da tarefa. É feita em ticks. Para executar em millis dividimos pela constante portTICK_PERIOD_MS
        TickType_t taskDelay = 5 / portTICK_PERIOD_MS;
        vTaskDelay(taskDelay);
    }
}



hasPinStatusChanged

//Verifica se o estado do pino na posição 'i' do array mudou
//Retorna 'true' se mudou ou 'false' caso contrário
boolean hasPinStatusChanged(int i)
{
    //Faz a leitura do pino
    int pinStatus = digitalRead(pins[i].number);
    
    //Se o estado do pino for diferente
    if(pins[i].status != pinStatus)
    {
        //Guardamos o novo estado e retornamos true
        pins[i].status = pinStatus;
        return true;
    }

    //Só chegará aqui se o estado não foi alterado
    //Então retornamos falso
    return false;
}



sendPinStatus

//Envia para o server os dados do pino na posição 'i' do array
void sendPinStatus(int i)
{
    client.write((uint8_t*)&pins[i], sizeof(Pin));
    client.flush();
}


Server.ino

Declarações e variáveis

#include <WiFi.h>
#include <vector>

//Dados da rede
//Deve ser igual no Client
#define SSID "ESP32Server"
#define PASSWORD "87654321"
#define SERVER_PORT 5000

//Criamos um server na porta definida por 'SERVER_PORT'
WiFiServer server(SERVER_PORT);
//Vector onde vamos adicionar os clients conforme eles forem conectando
std::vector<WiFiClient> clients;

//Struct que define os dados que vamos enviar (deve ser igual no client)
typedef struct{
    int number;
    int status;
}Pin;



Setup

void setup()
{
    Serial.begin(115200);

    //Criamos a rede WiFi e iniciamos o server
    setupWiFi();
    server.begin();

    xTaskCreatePinnedToCore(
        handleClients,   //Função que será executada
        "handleClients", //Nome da tarefa
        10000,           //Tamanho da pilha
        NULL,            //Parâmetro da tarefa (no caso não usamos)
        2,               //Prioridade da tarefa
        NULL,            //Caso queria manter uma referência para a tarefa que vai ser criada (no caso não precisamos)
        0);              //Número do core que será executada a tarefa (usamos o core 0 para o loop ficar livre com o core 1)
}



SetupWiFi

void setupWiFi()
{
    //Coloca este ESP como Access Point
    WiFi.mode(WIFI_AP);
    //SSID e Senha para se conectarem a este ESP
    WiFi.softAP(SSID, PASSWORD);
}



Loop

void loop()
{   
    //Verifica se um novo client está tentando se conectar
    WiFiClient client = server.available();

    //Se sim colocamos ele no vector
    if(client) 
    {     
        clients.push_back(client);
    }
}



HandleClients

void handleClients(void* pvParameters)
{ 
    //IMPORTANTE: A tarefa não pode terminar, deve ficar presa em um loop infinito
    while(true)
    {
        //Para cada client que temos no vector
        for(int i=0; i<clients.size(); i++)
        {
            //Guardamos a referência ao client
            WiFiClient client = clients[i];

            //Se o client está tentando enviar algo
            if(client.available())
            {
                //Fazemos a leitura dos dados e colocamos na saída do pino o estado que recebemos
                Pin pin;
                client.read((uint8_t*)&pin, sizeof(pin));
                pinMode(pin.number, OUTPUT);
                digitalWrite(pin.number, pin.status);
            }
        }

        //Delay de 5ms da tarefa. É feita em ticks. Para executar em millis dividimos pela constante portTICK_PERIOD_MS
        TickType_t taskDelay = 5 / portTICK_PERIOD_MS;
        vTaskDelay(taskDelay);
    }
}




Faça o download dos arquivos




Um comentário:

  1. Bom dia.
    Sigo o canal e vi ontem o vídeo deste tema, mas fiquei com uma dúvida, de como é que o server indexa os pinos de saída? Alguém me pode indicar?

    Continuação de bom trabalho e obrigado. (de Portugal)

    ResponderExcluir

Tecnologia do Blogger.