Esp-01 & Display SSD1306

Inviare una stringa tramite socket e visualizzarla su display SSD1306

In questo articolo illustro come inviare una stringa ad un ESP01 tramite socket (C#) e visualizzarla su un display 64x128 pixel di tipo SSD1306.

L'utilità potrebbe essere quella di segnalare a chi sta fuori da una stanza se chi sta dentro è libero o occupato. Un po come la tipica scritta "ON AIR". Per comandare il display ho scritto un'applicazione MAUI.

Per l'invio della stringa ho creato un'applicazione Wpf che poi ho trasformato in MAUI.

I collegamenti tra l'ESp-01 e il display sono i seguenti.

L' LM1117 ha il compito di stabilizzare la tensione a 3.3 volt fornita dalla batteria che, in caso di batteria completamente carica può aggirarsi sui 4.5 volt e potrebbe danneggiare esp8266 contenuto nel modulo ESP01.

Il condensatore elettrolitico ha invece il compito di soddisfare eventuali picchi di corrente dell' ESP-01.

Per vedere il dispositivo in funzione guarda il video su YouTube

L'hardware

Inizialmente avevo costruito il circuito con un Raspberry Pi Pico W, ma poi ho scelto l'ESP01.

Con questa soluzione, oltre a risparmiare qualche euro, è possibile creare un circuito di dimensioni più contenute e che consuma meno energia.

Lo svantaggio è che il Raspberry Pi Pico W (ovvero l' RP2040) avendo due core può fare girare due thread contemporaneamente. In particolare ciò mi è stato utile per avviare un timer direttamente sul "device". Nel dettaglio, il primo thread stava sempre in ascolto sulla porta 80. Il secondo visualizzava il tempo trascorso sul display. Se il primo thread riceveva uno "stop" interrompeva il secondo thread e quindi il timer.

Altro svantaggio potrebbe essere che sul Raspberry utilizzavo Microphyton mentre qui C/C++.

Detto tra noi per me è uno svantaggio utilizzare Microphyton perchè ho molta più dimestichezza con il caro vecchio C o C++.

Wiring Esp01- SSD1306

Giusto per curiosità ecco come appaiono i due device:

Esp01 and Raspberry Pi Pico W

Il software

Il software si compone di due parti. La prima scritta in C/C++ da caricare sull' ESP-01 e la seconda che è una solution di Visual Studio 2022 che contiene una banalissima applicazione MAUI così da poterla eseguire su Android, iPhone e Windows.

La comunicazione tra i due software avviene tramite socket e via WiFi.

ESP-01

Il codice da caricare sull' ESp01 è il seguente:

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <ESP8266WiFi.h>
#include <time.h>

const char* ssid = "IMMETTERE QUI IL NOME DELLA RETE";
const char* password = "IMMETTERE QUI LA PASSWORD";

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

WiFiServer server(80);

void setup()
{
  Serial.begin(115200);
  Wire.pins(0, 2);
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println("SSD1306 allocation failed");
    for(;;); 
  }
  resetDisplay();
  display.display();
  delay(100);
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE); 
  display.cp437(true);        

  Serial.println();

  Serial.printf("Connecting to %s ", ssid);
  display.write(ssid);
  display.display();

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
    display.write(".");
    display.display();
  }

  resetDisplay();
  Serial.println(" connected");

  server.begin();
  Serial.printf("Web server started, open %s in a web browser\n", WiFi.localIP().toString().c_str());

  resetDisplay();
  display.setTextSize(2);
  drawCentreString("Connected",64,12);
  display.setTextSize(1);
  drawCentreString(WiFi.localIP().toString().c_str(),64,42);
  display.display();

  delay(100);

}

void loop()
{
  WiFiClient client = server.available();
  i f (client)
  {
    Serial.println("\n[Client connected]");
    while (client.connected())
    {
      if (client.available())
      {
        resetDisplay();

        String line = client.readStringUntil('\n');
        i f(line.startsWith("cmd:start")){
          startCall();
        }else if(line.startsWith("cmd:end")){
            endCall();
        }else i f(line.startsWith("cmd:displayon")){
            displayOn();
        }else i f(line.startsWith("cmd:displayoff")){
            displayOff();
        }else{
          drawCentreString(line.c_str(), 64, 32);
        }

        Serial.print(line);
        client.println("ok");
                  
        break;
      }
      
    }
    delay(1); 
    client.stop();
    Serial.println("[Client disonnected]");
  }
}


void startCall(){
  displayOn();
  display.setTextSize(2);
  drawCentreString("Occupato!", 64, 32);
}

void endCall(){
  displayOn();
  display.setTextSize(2);
  drawCentreString("Libero :)", 64, 32);
}

void displayOff(){
  display.ssd1306_command(SSD1306_DISPLAYOFF);
}

void displayOn(){
  display.ssd1306_command(SSD1306_DISPLAYON);
}

void resetDisplay(){
  display.clearDisplay();
  display.setCursor(0, 0);
}

void println(const char * str){
  Serial.println(str);
  display.println(str);
  display.display();
}

void print(char * str){
  Serial.print(str);
  display.write(str);
  display.display();
}

void drawCentreString(const char *buf, int x, int y)
{
    int16_t x1, y1;
    uint16_t w, h;
    display.getTextBounds(buf, x, y, &x1, &y1, &w, &h); //calc width of new string
    display.setCursor(x - w / 2, y);
    display.print(buf);
    Serial.print(buf);
    display.display();
}

Per la gestione del display è necessario installare la libreria:

 

SSD1306 Adafruit library

C#

Il codice C# è il seguente.

Per semplicità riporto solo il codice del servizio e del ViewModel.

Il funzionamento è davvero molto semplice e penso che non sia necessario dilungarmi oltre.

SocketCommunicationService
public class SocketCommunicationService
    {
        //string _ip = "192.168.99.124";
        string _ip = "192.168.99.140";
        int _port = 80;

        public void Setup(string ip, int port)
        {
            _ip = ip;
            _port = port;
        }
        public async Task<string> Send(string message)
        {
            return await Send(_ip, _port, message);
        }

        public async Task<bool> Test()
        {
            var result = await Send(_ip, _port, "test\n");
            return result == "ok";
        }
        public async Task<bool> Test(string ip, int port)
        {
            var result = await Send(ip, port, "test\n");
            return result == "ok";
        }

        private async Task<string> Send(string remoteIp, int remotePort, string message)
        {
            try
            {
                await Task.Yield();
                CancellationTokenSource cts = new CancellationTokenSource();
                CancellationToken token = cts.Token;
                cts.CancelAfter(500);
                token.ThrowIfCancellationRequested();
                
                TcpClient tcpClient = new TcpClient(); 
                await tcpClient.ConnectAsync(remoteIp, remotePort, token);

                byte[] data = Encoding.ASCII.GetBytes(message);
                NetworkStream stream = tcpClient.GetStream();

                stream.Write(data, 0, data.Length);
                data = new byte[256];
                string responseData = string.Empty;


                int bytes = stream.Read(data, 0, data.Length);
                responseData = Encoding.ASCII.GetString(data, 0, bytes);
                Console.WriteLine("Received: {0}", responseData);
                               
                stream.Close();
                tcpClient.Close();

                return responseData;
            }
            catch (Exception ex)
            {
                await App.Current.MainPage.DisplayAlert("Error", ex.Message, "cancel");
                return string.Empty;
            }
        }
    }
CallViewModel
public partial class CallViewModel : ObservableObject, INotifyPropertyChanged
    {
        public ICommand SendCommand { get; set; }

        SocketCommunicationService _socketCommunicationService;

        public CallViewModel(SocketCommunicationService socketCommunicationService)
        {
            _socketCommunicationService = socketCommunicationService;

            SendCommand = new Command(() =>
            {
                _socketCommunicationService.Send(TextToSend + "\n");
            },
            () =>
            {
                return !string.IsNullOrWhiteSpace(textToSend);
            });
        }

        string textToSend;
        public string TextToSend
        {
            get => textToSend;
            set
            {
                textToSend = value;
                RaisePropertyChanged(nameof(TextToSend));
                (SendCommand as Command).ChangeCanExecute();
            }
        }

        public ICommand StartCommand => new Command(() =>
        {
            _socketCommunicationService.Send("cmd:start\n");
        });            

        public ICommand StopCommand => new Command(() =>
        {
            try
            {
                _socketCommunicationService.Send("cmd:end\n");
            }
            catch (Exception ex)
            {

            }
        });

        public ICommand DisplayOnCommand => new Command(() =>
        {
            try
            {
                _socketCommunicationService.Send("cmd:displayon\n");
            }
            catch (Exception ex)
            {

            }
        });

        public ICommand DisplayOffCommand => new Command(() =>
        {
            try
            {
                _socketCommunicationService.Send("cmd:displayoff\n");
            }
            catch (Exception ex)
            {

            }
        });

        public event PropertyChangedEventHandler PropertyChanged;

        public void RaisePropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

Link al video su YouTube

Buon divertimento!

Se hai voglia di lasciarmi un commento mi trovi su Linkedin.