Wemos Lolin ESP32 OLED Module For Arduino ESP32 OLED WiFi + Bluetooth

Hi, how can we add this board to the Arduino IDE ? thanks!

gilalmogy: Hi, how can we add this board to the Arduino IDE ? thanks!

As stated in 1st posting of this thread, it can be just programmed via Arduino IDE as well, see "Installation Instructions": https://github.com/espressif/arduino-esp32

It seems that the ESP32 CPU clock is 160MHz, by above done measurement and the spec:

I stumbled over an integer performance comparison for many microcontrollers I did in an Arduino Due thread:

The sketch used is attached to this posting.

I did compute both values (-Os and -O3) for the ESP32 board this thread is about.
And I added Pi Zero W row, as well as W541 row because of 6μs time:

model -Os -O3 speed processor Wifi
MKR1000 1038 825 48MHz SAMD21 Cortex-M0+ yes
Zero - - 48MHz ATSAMD21G18 -
101 846 791 32MHz Intel® Curie -
Due 548 494 84MHz ATSAM3X8E -
-
ESP8266-12E 612 304 80MHz Tensilica Xtensa LX106 yes
Lolin ESP32 191 149 160MHz Dual core Xtensa LX6 yes
-
Raspberry Pi Zero [W] - 30 1000MHz Broadcom BCM2835 [yes]
Raspberry Pi 2B - 27 900MHz Quad core BCM2835 -
Raspberry Pi 3B - 17 1200MHz Quad core BCM2835 yes
Raspberry Pi 3B+ - 14 1400MHz Quad core BCM2835 yes
-
(W541) - 6 2.8GHz Intel i7-4810MQ

added Pi 3B+, corrected all Pi values after having forced CPU to run at highest speed
added Pi 3B
corrected Pi Zero [W] times, added Pi 2B

 47| 29|101|
113| 59|  5|
 17| 89| 71|

149us

Hermann.

Below sketch might be useful as starter for both, Wifi processing as well as OLED display output. I learned from several sketches out there and combined what I needed. Multiple .addAP() calls allow the Lolin32 to work with several access points without recompile. And display.setLogBuffer(5,30) seems to be a nice alternative to Serial output. Sketch connects to Wifi AP, reports its IP address and then logs RSSI (Received Signal Strength Indicator) in a new line every second.

I had problems to take a focused photo of OLED with my Android cameras, only fuzzy photos. So I used PS3 Eye as webcam. Same problem with that in both settings of its adjustable fixed focus zoom lens. Then I learned that I can manually set focus continuously and so I got this photo I wanted: |500x375

Hermann.

#include 
#include 

WiFiMulti wifiMulti;

#include "SSD1306.h"

SSD1306  display(0x3c, 5, 4);

void setup() {
  display.init();

  display.flipScreenVertically();

  display.setContrast(255);

  display.clear();

  wifiMulti.addAP("FRITZ!Box ... 1", "verySecret");
  wifiMulti.addAP("FRITZ!Box ... 2", "foobar");

  display.setLogBuffer(5, 30);
  display.println("Connecting Wifi...");
  display.drawLogBuffer(0, 0);
  display.display();
  
  if(wifiMulti.run() == WL_CONNECTED) {
    display.println("WiFi connected");
    display.println("IP address: ");
    display.println(WiFi.localIP());
    display.drawLogBuffer(0, 0);
    display.display();
  }
}

void loop() {
  if(wifiMulti.run() != WL_CONNECTED) {
    display.println("WiFi not connected!");
    display.clear();
    display.drawLogBuffer(0, 0);
    display.display();
    delay(1000);
  }
  
  long rssi = WiFi.RSSI();
  display.clear();
  display.print("RSSI:");
  display.println(rssi);
  display.drawLogBuffer(0, 0);
  display.display();
  delay(1000);
}

P.S: This diff gives a more compact display:

$ diff sketch_oct06e/sketch_oct06e.ino sketch_oct06f/sketch_oct06f.ino 
9a10,11
> int i=0;
> 
29c31
<     display.println("IP address: ");
---
>     display.print("IP ");
47,48c49,51
<   display.print("RSSI:");
<   display.println(rssi);
---
>   display.print(rssi);
>   display.print(" ");
>   if (i++%7==6) display.println();
$

|500x375

|500x375

I made one ESP32 the AP, and the other connect to it. Best signal strength (-10dBm) happens when placing both antennas side by side.

I placed the ESP32 acting as AP on window sill in 1st floor of our house and moved with the 2nd connected to the AP far away outdoor. I got RSSI values (updated each second on OLED) even at 200m distance (with few trees in line of sight), connection dropped at 208m distance. These long distances don't make much sense though since I did not test sending data sofar. And signal strength was in the high -80s and low -90s dBm range which makes functionality unlikely, see table here: https://supportforums.cisco.com/t5/small-business-support-documents/why-is-almost-everything-negative-in-wireless/ta-p/3159743

After power cycling, the sketch on mobile ESP32 tried to connect to AP, and I walked back on the field direction our house. It was able to connect to AP and display its IP address on the OLED at distance 168m, but again with signal strength in the -80s dBm range. I walked further back direction house until signal level was -70dBm (with a small tree in line of sight). The distance to AP on window sill at that point was 87m.

Will use advanced sketches to transfer data (over HTTP request) for investigating how far reliable packet delivery is possible. And will move the AP ESP32 (battery/Lipo powered) on the field as well for direct line of sight connection long distance measurements.

Hermann.

As the table pointed to indicates, anything more than -70dBm should not be considered. I tested on distances with around -75dBm and every 20th-30th request is slow (>1000ms), but successful.

I built a 1-URL Web server and access point on one Lolin ESP32, and a HTTP Client on 2nd, you can find sketches below.

It turned out that I got best connectivity if ESP32 Wifi antennas pointing up and the ESP32s point to each other. This shows where I took the measurements yesterday evening, web server and access point on window sill of my secondary living place, HTTP client on rail of Schönbuchbahn: |500x327

I was not able to get a focused photo with Android camera. But viewing the photo on Android I was able to see that this is displayed:

67-69#0 52-69#1 66-69#2
66-69#0 52-69#1 66-69#2
67-69#0 51-69#1 67-69#2
66-69#0 50-70#1 67-69#2
67-69#0 51-70#1

The first number is millisecond time to send GET request and receive response. The 2nd (negative) number is RSSI connectivity, and the number after '#' is a 0-2 conter value received from web server. The RSSI value was always 69-70 (I watched 600 requests) without any slow, all requests take <70ms, "middle" always <55ms, distance was 58m in this case: http://www.gmap-pedometer.com/?r=7153997

Hermann.

1-URL Web server and access point:

/*
 *  access point & 1-URL HTTP server (http://192.168.4.1/foobar)
 */
#include 

#include "SSD1306.h"

SSD1306  display(0x3c, 5, 4);

WiFiServer server(80);

char req[]={'G','E','T',' ','/','f','o','o','b','a','r',' '};
char plain[]="HTTP/1.1 200 OK\nContent-type:text/plain\n\n";

int c=0;
#define N 3

void setup() {
  display.init();
  display.flipScreenVertically();
  display.setContrast(255);

  display.setLogBuffer(5, 30);
  
  WiFi.softAP("ESP32_TestAP", "esp32example");

  display.clear();
  display.println("AP");
  display.drawLogBuffer(0, 0);
  display.display();

  server.begin();
}

void loop(){
  WiFiClient client = server.available();

  if (client) {
    int i=0;
    while (client.connected() && i

Measuring HTTP client:

/*
 *  HTTP client endlessly doing GET(http://192.168.4.1/foobar)
 *  "67-45#2" means: t(GET)=67ms, RSSI=-45, 2 returned counter
 */
#include 

#include 
#include 

#include 

#include "SSD1306.h"

SSD1306  display(0x3c, 5, 4);

WiFiMulti wifiMulti;
HTTPClient http;

unsigned long t0, t1, c=0, r;
#define N 3

void setup() {
  display.init();
  display.flipScreenVertically();
  display.setContrast(255);

  display.setLogBuffer(5, 30);
  
  wifiMulti.addAP("ESP32_TestAP", "esp32example");

  while (wifiMulti.run() != WL_CONNECTED) {
    delay(500);
    display.clear();
    display.print(".");
    display.drawLogBuffer(0, 0);
    display.display();
  }


  display.clear();
  display.println(WiFi.localIP());
  display.drawLogBuffer(0, 0);
  display.display();
}

void loop() {
  if((wifiMulti.run() == WL_CONNECTED)) {
    long rssi = WiFi.RSSI();
    t0 = millis();
    http.begin("http://192.168.4.1/foobar");
    if(http.GET() == HTTP_CODE_OK) {
      r = http.getString().toInt();
    } else {
      r = 9;
    }
    http.end();
    t1 = millis();
    display.clear(); display.print(t1-t0); display.print(rssi);
    display.print("#"); display.print(r); display.print(" ");
    if (c % N == N-1)
      display.println();
    display.drawLogBuffer(0, 0);
    display.display();
    if (r<9)
      c++;
  }

  delay(250);
}

Wow, saw video to this code in action with >200fps on 2.4" OLED display. The display dimension was same as for the Lolin OLED (128x64). I wanted to try, but had no MPU6050 at hand here in secondary living place. So I just deleted all MPU calls, added missing variable definitions and fixed OLED pin numbers, see small diff below. I awaited to see a fixed view display, but to my surprise a complete 3D movie got displayed with >300fps! Maximum reported was 334fps: |500x2454

As always I had problems with focus of my Android phone camera, but you get the idea on how this looks. Here is a 8s 720p youtube video I took: https://www.youtube.com/watch?v=ciEU4yL3m2I&feature=youtu.be

Hermann.

$ diff Rotatey_Balls.ino sketch_oct12a/sketch_oct12a.ino 
8c8
< #include "mpu6050rotatey.h" /* Source http://tocknlab.hatenablog.com/entry/2017/03/11/182703 */
---
> float angleX,angleY,angleZ,lastAngleX,lastAngleY,lastAngleZ;
18,19c18,19
< int sdaPin = 21;
< int sclPin = 22;
---
> int sdaPin = 5;
> int sclPin = 4;
164,165d163
<   mpu_init(sdaPin, sclPin);// sda, scl
<   mpu_calibrate();
168d165
<   calcRotation(); // read from MPU
186d182
<   calcRotation(); // read from MPU
374a371
> 
$

I got the original demo with MPU6050 running on Lolin ESP32 with OLED.

Only this minimal set of changes is needed compared to github: https://github.com/tobozo/Rotatey_Balls

$ diff Rotatey_Balls.ino sketch_oct13a/sketch_oct13a.ino 
18,19c18,19
< int sdaPin = 21;
< int sclPin = 22;
---
> int sdaPin = 5;
> int sclPin = 4;
$ 
$ diff mpu6050rotatey.h sketch_oct13a/mpu6050rotatey.h 
42c42
<   Wire.begin(21, 22);
---
>   Wire.begin(sda, scl);
$

Connections:

6050  Lolin ESP32
VCC   3V3
GND   GND
SCL   4
SDA   5
XDA   nc
XCL   nc
AD0   nc
INT   14

Short youtube video: https://www.youtube.com/watch?v=AwaW011vPOI&feature=youtu.be |500x361

P.S. I2cScanner from Arduino playground is helpful, for Lolin ESP32 this is the only change needed:

$ diff i2c_scanner.ino sketch_oct13b/sketch_oct13b.ino 
35c35
<   Wire.begin();
---
>   Wire.begin(5,4);
$

The scanner detects the OLED (0x3C) as well as MPU6050 (0x68):

⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮
I2C Scanner
Scanning...
I2C device found at address 0x3C  !
I2C device found at address 0x68  !
done

Scanning...
...

P.P.S. The same diff makes short MPU6050 example from Arduino Playground work:

$ diff mpu6050_test.ino sketch_oct13c/sketch_oct13c.ino 
9c9
<   Wire.begin();
---
>   Wire.begin(5,4);
$

I use http://bitlash.net/ as debug shell for my other Arduinos (for Raspberry Pi Zero I use my Android as Terminal https://www.raspberrypi.org/forums/viewtopic.php?f=36&t=196057 for bash shell).

Because bitlash works for AVR processors only, it is not an option for ESP32. I was guided to the solution for ESP32 debug shell on esp32.org forum. Micropython allows to read/write GPIO pins, set direction and even access OLED with few commands: https://www.esp32.com/viewtopic.php?f=2&t=3476

Micropython can be flashed with two esptool.py commands, and easily used with "screen /dev/ttyUSB0 115200". Of course I will do normal programming of the ESP32 module with Arduino IDE -- I will use Micropython for debugging only, eg. after soldering.

P.S: I just learned how Arduino IDE Blink Example sketch looks like in Micropython:

import machine, time
pin12 = machine.Pin(12, machine.Pin.OUT)
while True:
    pin12.value(1)
    time.sleep(1)
    pin12.value(0)
    time.sleep(1)

I got help and made much progress, now it is easily possible to debug+dev the ESP32 board mobile wireless in Android JuiceSSH telnet session (who needs a laptop for that?). Details can be found in this posting, the MicroPython version I used is attached to that posting: https://www.esp32.com/viewtopic.php?f=2&t=3476&p=16515#p16515

MicroPython is cool on its own (I was able to let boot.py connect to Android AP, start Telnet and FTP server, and do initial OLED screen). Especially it allows you to start jobs on both of the ESP32 240MHz cores.

Currently I will continue to program that module via Arduino IDE though. Only if I need a (wireless) debug session (eg. after soldering) I will flash that MicroPython in between for debugging.

Hermann. |500x354

OK, now I want to use the ESP32 module to control 12V motors and soldered male headers to the module.

Without any change I powered the module, and the boot display from last posting appeared. Next I enabled wireless hotspot on my Android, and after 2 seconds the ESP32 was shown as connected. Next I connected to the wireless Android AP from my laptop and did "telnet 192.168.43.128" into the ESP32 (I got the ESP32 IP address from Android as described here):

$ telnet 192.168.43.128
Trying 192.168.43.128...
Connected to 192.168.43.128.
Escape character is '^]'.
MicroPython ESP32_LoBo_v2.0.8 - 2017-11-04 on ESP32 board with ESP32
Login as: micro
Password: 
Login succeeded!
Type "help()" for more information.

>>>

After having verified voltages on 5V, GND and 3V3 pins I skipped RX/TX and connected to pin 15. This simple code verified that I did solder pin 15 correctly:

>>> pin = machine.Pin(15, machine.Pin.OUT)
>>> pin.value(0)
>>> pin.value(1)

Next I need to find out how to verify the non-number pins.

Since analogWrite() is not available on the module's Arduino IDE I will have have to use LEDC for 16 channel PWM: https://github.com/espressif/arduino-esp32#development-status But I want to find out how to test PWM from MicroPython as well.

|500x375

On the robot I wanted to power the ESP32 from a Lipo different to the 1000mAh 25C 3S Lipo used to power the motors. So I tried a 3.8V loaded Lipo with male micro connector I soldered for Raspberry PIs with this 0.36$ USB-5-P-Port-Male-Plug-Socket-Connector: https://www.aliexpress.com/wholesale?SearchText=Right+Angle+Micro+USB+5+P+Port+Male+Plug

While even 3.4V from Lipo are enough to power a Pi Zero, 3.8V did not make Lolin ESP32 module boot.

So I remembered that I have a Polulu 5V step-up converter https://www.pololu.com/product/2562

and decided to create a female/male micro USB plugin between ESP32 module and Lipo micro USB connector I already had. And it works, from the 5.09V generated by step up converter when not connected to ESP32 module I can measure 4.77V between GND and 5V pins of ESP32 module when connected as in this photo: |500x375

P.S: Lolin ESP32 module found new home as wireless motor control for testing new steering omni wheel robot: |500x375

I did connect two unidirectional MOSFETs that were mounted on robot already to the three 1500rpm gear motors (two back wheels are controlled from same MOSFET). I could have used the Lipo for powering the ESP32 module, but for testing cable was fine for me.

This is wireless test start (robot is jacked on Lego platform):

Login succeeded!
Type "help()" for more information.

>>> p12 = machine.Pin(12); pwm12 = machine.PWM(p12); pwm12.duty(0)
>>> p13 = machine.Pin(13); pwm13 = machine.PWM(p13); pwm13.duty(0)
>>> pwm12.duty(512)
>>> pwm12.duty(0)

Unidirectional motor controller is OK for two back wheels, but steering wheel motor will need a bidirectional controller: https://www.youtube.com/watch?v=h-C2UAxJ_kw

P.S: This robot already has weight 533g, and Raspberry Pi Zero with camera for high framerate (≥180fps) video processing is yet missing ...

P.P.S: "So this is kind of another level of debugging, not single pins anymore, but motor functionality." https://www.esp32.com/viewtopic.php?f=2&t=3476&p=19087#p19096 |500x375

I wanted to use a (Bluetooth) BLE remote gamepad to controll the robot ESP32 module:

But I found out that (Bluetooth) BLE support is unlikely to be available in ESP32 MicroPython or ESP32 Arduino IDE in the near future.

Then I wanted to use the other ESP32+Oled module I had to connect an Arduino joystick and send telnet commands to the MicroPython on robot ESP32 module in order to be able to remotely control the robot. But this time my soldering was bad and I killed the 2nd ESP32 module :-( Immediately after that I did order 2 new ESP32+Oled modules with free shipping for <10$ each from banggood.com (because there shipping time is only 7-20 days).

Next I remembered that I do have two Wemos D1 modules (ESP8266 in Arduino Uno form factor):

While waiting for the new ESP32+Oleds modules I decided to give the D1 modules a try (did cost 6.50$ with free shipping two years ago, now 3.50$). Most important thing I had to remember was to use "D5" in sketch instead of "5" for Arduino pin D5 ...

First I installed a temperature and humidity sensor in a location of interest in our house and connected to a D1 module over 3m of three thin cables. I only modified one function of "Examples->ESP8266WebServer->HelloServer" and used DHT11 library for the sensor:

void handleRoot() {
  digitalWrite(led, 1);
  String message = "DHT11, \t";
  int chk = DHT.read11(DHT11_PIN);
  switch (chk)
  {
    case DHTLIB_OK:  
    message += "OK,\t"; 
    break;
    case DHTLIB_ERROR_CHECKSUM: 
    message += "Checksum error,\t"; 
    break;
    case DHTLIB_ERROR_TIMEOUT: 
    message += "Time out error,\t"; 
    break;
    default: 
    message += "Unknown error,\t"; 
    break;
  }
  // DISPLAY DATA
  message += String(DHT.humidity);
  message += "% r.F.\t";
  message += String(DHT.temperature);
  message += "°C [" + String(millis()) + "]";
  server.send(200, "text/html", message);
  digitalWrite(led, 0);
}

Surprisingly easy, now I have my 1st IOT device in our house, and it refreshes every 5 seconds in browser: DHT11, OK, 66.00% r.F. 18.90°C [16381020]

(r.F. is German for r.H. or relative humidity).

Now I try to get "Examples->Ethernet(esp8266)->TelnetClient" to connect to robot ESP32 module wirelessly for robot remote control.

Bad news first on the steering omni wheel robot. I played with telnet commands "right()" and "sbrake()" with maximal speed "pwmb.duty(1023)". While the robot turned quite fast on carpet, I tried on floor tiles and the results was really bad. Turning on floor tile without moving forward just does not work well (unclear whether it is as bad with moving forward).

Then I tried to run robot "blind" across big free space in our living room. And that with maximal forward speed, timed and with full brake after one second:

pwma.duty(1023)
forward();  time.sleep(1); brake();

Braking distance was something in 5-15cm range only. And the robot had to accelerate from stand still. Despite that the robot moved 10 tiles forward and 4 tiles to the right, with 31.5cm tile lenght. This demonstrated an average robot forward speed of 3.4m/s of >12km/h already!!

$ bc -ql
sqrt(10^2+4^2)*0.315
3.39265382849473753968
sqrt(10^2+4^2)*0.315*3.6
12.21355378258105514284

All three motors are 12V 1500rpm gear motors, back wheel diameter is 65mm ➫ in theory 5.10m/s maximal robot forward speed:

$ echo "pi=4*a(1); 1500/60*pi*0.065" | bc -ql
5.10508806208341401246
$

I got some of these a while back, and keep one on my keychain as a ssid scanner, here's my code:

https://github.com/mikerr/arduino-oled

I recently bought and recieved a Wemos Lolin32 OLED Module board. It took me a lot of effort to put the little online documentation together but I got it all working.

Now it runs a little webserver with DS18B20 temperature server. It displays the startup proces on the display and at the end of the startup proces it displayes the associated accespoint and IP-adres. On the little internal website I got the ability to read the temperature and operate the attached red power-led and green activity-led.

In the future I want to add UI cards with the temperature, access point, IP-adres, digital clock and analog clock.

While searching for information about the board I discovered this topic. It is nice to see the information about the board and DIY projects of others.

This pos got my attention:

mikerr: I got some of these a while back, and keep one on my keychain as a ssid scanner, here's my code:

https://github.com/mikerr/arduino-oled

These sketches seems to be very usefull. Thank you for sharing! I definately will try them sometime!

One last thing: can anyone tell me whether it is possible to connect one or more buttons to this board? And if so: does the board have internal pullup or pulldown resistors? If not: what would be the steps to do this by my self. At the moment I only have a little experience with a Teensy 2.0 which has internal pullups resistors and a nice Bounce library to nicely interact with momentary push buttons.

I too am beginning my planning for an altimeter for my PPC. The additional features are quite interesting also. Please be so kind as to send any info on your success with your altimeter, etc project.

This is a bit off topic, but I posted on using MicroPython with that ESP32 and Oled before in this thread. It is even possible to redirect the MicroPython REPL (Read, Evaluate, Print, Loop) to the OLED. Yes, a bit small, but for me 16x8 console is really impressive: https://esp32.com/viewtopic.php?f=2&t=7069&p=31710#p31710 |500x263

This is from matching "screen" session, the posting is from thread on keyboard missing to make this ESP32 with Oled a full blown computer when running from lipo:

...
>>> import sys
>>> sys.implementation
(name='micropython', version=(1, 9, 4))
>>> 5**4**3
542101086242752217003726400434970855712890625
>>>

This is my fork that makes the for Raspberry FBConsole usable for ESP32 again: https://github.com/Hermann-SW/micropython-raspberrypi#fork-mission-statement

A colleague told me that V-USB project is the way to go to connect a USB keyboard, have not tried that. This would be the (5$) USB wireless keyboard I would connect: |500x375

I ordered one of these boards (hasn't arrived yet) but I couldn't find a spec sheet or something that could tell me how to power it from something else than the USB port.

I see it has some pins named 3V3, 5V and Vin. Since it can be run from USB (5V) but seems to use 3.3V internally, I assume it has an onboard voltage regulator. Is it safe to connect a battery to Vin? What's the allowed range of the voltage connected to Vin? Is it possible to connect a 1 or 2 cell LiPo?

marcobinz: I see it has some pins named 3V3, 5V and Vin. Since it can be run from USB (5V) but seems to use 3.3V internally, I assume it has an onboard voltage regulator. Is it safe to connect a battery to Vin? What's the allowed range of the voltage connected to Vin? Is it possible to connect a 1 or 2 cell LiPo?

I did power with 1S lipo, but the 3.7V or 4.2V fully loaded are too much. I used 1.8V-5V step converter to 3.3V and powered via 3V3 and GND: https://de.aliexpress.com/item/32817719981.html

It is more than a year ago that I last used the OLED ESP32, and at that time mostly with MicroPython.

Today I looked up the start of this thread and brought everything up and running again. Then I added Keypad library and made a small OLED keypad demo. Since pins 4 and 5 are used for display I2C, I had to use the only left 8 consecutive pins GPIO0..GPIO1 for keypad connector. Since GPIO1 is used, no "Serial" output can be done, but that is fine since output is done on OLED.

This is the 43 lines sketch used:

#include 
#include "SSD1306Wire.h"

SSD1306Wire display(0x3c, 5, 4);

#include 

const byte ROWS = 4;
const byte COLS = 4;
char keys[ROWS][COLS] = {
  {'1','2','3','A'},
  {'4','5','6','B'},
  {'7','8','9','C'},
  {'*','0','#','D'}
};
byte rowPins[ROWS] = {0, 2, 14, 12};
byte colPins[COLS] = {13, 15, 3, 1};

Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );

String line = "Press keys:";
char key;

void draw(String &s) {
  display.clear();
  display.drawStringMaxWidth(0, 0, 128, s);
  display.display();
}

void setup() {
  display.init();
  display.flipScreenVertically();
  display.setTextAlignment(TEXT_ALIGN_LEFT);
  display.setFont(ArialMT_Plain_16);
  draw(line);
}

void loop() {
  if (key = keypad.getKey()){
    if (line.length()%11==0) line += ' '; // drawStringMaxWidth() workaround
    draw(line += key);
  }
}

Here you can see it in action: |500x375

I am doing this as contract work for my son. He does not like SmartLife app for controlling smart socket or smart light, and wants to use 4x4 keypad instead. His brother 3D-printed keypad cover for him already: https://www.thingiverse.com/thing:4135043

Now I have to reverse engineer the protocol used, and let eg. the ESP32 turn smart plug on/off. I used Ettercap MITM ARP poisoning to capture traffic between Android (192.168.178.56) and plug (192.168.178.109) with Wireshark on a Raspberry Pi. Surprisingly traffic is in clear, the highlighted line is MQTT message from amazonws server 35.158.232.97 to the plug. 0x4002008[56] messages turn plug on/off. A good start sofar, but more analysis needed. Especially later the smart lights allow to set color values. Protocol is TUYA against port 13968 of the plug. Initially only port 6668 is open on the plug: |500x282