GUI-O series: Building Android GUI with ASCII commands (IoT with ESP32)

INTRODUCTION

This topic aims to show (through practical examples), how GUI-O can be used as a display in various embedded projects.

GUI-O is an Android application that lets you quickly create professional graphical user interfaces (GUIs) for controlling any device using ASCII text based commands. It also enables simple interaction with various hardware of the Android device (NFC, GPS, camera, etc.).

In this series, I will mostly be using ESP32-WROOM-32 development board, which can be used for all connection types that GUI-O supports:

  1. Bluetooth SPP
  2. Bluetooth LE (NUS or custom)
  3. IoT (MQTT)
  4. USB
  5. Ethernet (TCP/IP)

Of course, you are not limited to this particular board... Any device that supports any of the listed connection types can be used (e.g., Raspberry boards, Arduino boards, STM boards, ESP32, ESP8266, HC (HM) modules, PC, etc.).

Note that GUI-O app DEMO version allows creating up to 6 widgets and can only interact with NFC, RTC (real-time clock) and GPS.
PRO version has no limitations. For more information, see www.gui-o.com.


My name is Klemen (kl3m3n) and I am the developer of the GUI-O application and GUI-O designer tool.

Let's dive in...


Just for reference, the following widgets are available. All widgets are highly customizable. Widgets can be positioned on up to five (virtual) screens.

Additionally, interaction with the following hardware is available (if supported by the device):

  • NFC (Near-field communication)
  • Audio player
  • Tone player
  • Real-time clock (RTC)
  • Position sensor (GPS)
  • Vibrator
  • Camera capture
  • File I/O
  • Screen orientation
  • Battery status
  • Brightness setting
  • Device sensors:
    • Accelerometer
    • Altimeter
    • Ambient light sensor
    • Ambient temperature sensor
    • Compass
    • Distance sensor
    • Gyroscope
    • Holster sensor
    • Humidity sensor
    • IR Proximity sensor
    • Lid sensor
    • Light sensor
    • Magnetometer
    • Orientation sensor
    • Pressure sensor
    • Proximity sensor
    • Rotation sensor
    • Tap sensor
    • Tilt sensor

Edit (13.5.2023): Replaced image to include two new widget types (Checkbox and Radio button).

1 Like

EPISODE 1: TEMPERATURE READER

I will be using a thermistor to read and display the ambient temperature. The display will show the current ambient temperature and ambient temperature over time using a graphical representation.

I will be using GUI-O IoT (MQTT) connection, but the example can be easily be ported to other connection types.

Software prerequisites:

Components needed:

The entire tutorial is split into various steps. All necessary information is given in each step.

0. DESIGN THE GUI (optional)

The best way to create a GUI layout is to use GUI-O live designer tool.

Note that the Arduino source code already includes the necessary commands, so this step is not needed, unless you want to make some visual adjustments. If you make adjustments, please include the generated ASCII code in the Arduino source code (see section 3. MODIFY AND UPLOAD THE SOURCE CODE).

First, you need to establish a TCP/IP connection between the designer tool and GUI-O application:

  1. Determine the local IP address of your PC's network interface (WiFi or Ethernet)
  • Under Windows, open the command prompt, enter ipconfig and press Enter
  • Under Linux, open the terminal, enter ifconfig and press Enter
  1. Open GUI-O application and open settings menu. Select "Connections -> Ethernet" and create a new device with IP address (determined from 1.) and any port between 49152 - 65535

  2. Open GUI-O designer and select "TCP/IP connection" tab. Set the IP address and port. Both values must match the device settings created within the GUI-O application. Click "Start server" button.

  3. Within the GUI-O application, tap the created device and wait for successful connection.

  4. In the GUI-O designer, select "File -> Load designer file" and load the TemperatureReader.gdf design file. Make the desired adjustments, if necessary. Copy / replace the GUI-O commands into the Arduino source code (see section 3. MODIFY AND UPLOAD THE SOURCE CODE).

1. CONNECT THE COMPONENTS

Connecting the components is straightforward. The "D35" is one of the ADC1 pins on ESP32 (note that ADC2 pins are not available when WiFi is enabled).

schematic.png

2. CREATE A UNIQUE MQTT CHANNEL

Open GUI-O application and navigate to settings menu. Select "Connections -> IoT" and add a new device. After adding the device, note the In and Out token (you can share this tokens e.g., to your e-mail by pressing the "share" button).

Finally, press "Connect" menu entry to establish the connection with the MQTT server.

3. MODIFY AND UPLOAD THE SOURCE CODE

The source code has inline comments, describing the important parts of the code. You can copy the source code from the snippet below, or download it here.

The only thing that needs to be done is to set the ssid and password of your router and the unique In and Out channels that were generated by the GUI-O application (see section 2. CREATE A UNIQUE MQTT CHANNEL).

After setting these values, upload the code to your board (make sure that the correct board and upload port are selected). Reset the board after upload.

/*
 * GUI-O Thermistor MQTT example (using ESP32-WROOM-32)
 *
 * Copyright (C) 2022, kl3m3n
 * last updated on 30.10.2022
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <WiFi.h>
#include <PubSubClient.h>

static const char *ssid = "<ssid>"; // router name
static const char *pass = "<pass>"; // router password

static const char *mqttServer = "mqtt.gui-o.com";   // host name
static const char *mqttUser = "gui-o-mqtt-generic"; // user name
static const char *mqttPass = "lqXeZpv5VJyv0XBT";   // password

// IMPORTANT NOTE: if optional user name was specified when adding a new IoT device,
// the user name should also be included when setting the topics (e.g., "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/<user_name>")
static const char *Out = "<Out>"; // GUI-O app publish topic
static const char *In = "<In>";  // GUI-O app subscribe topic

// mqtt client
WiFiClient wiFiClient;
PubSubClient mqttClient(wiFiClient);

// forward declare functions for mqtt messages handling
void mqttCallback(char* topic, byte* message, unsigned int length);
void parseGuioMsg(const String &msg);

// setup thermistor read settings
char buf[100];
const int thermistorPin = 35;
unsigned long startTimestampMsec, currentTimestampMsec;
const unsigned long readIntervalMsec = 500; // milliseconds

void setup() {
  // debug output
  Serial.begin(115200);
  
  // connect WiFi (keep trying...)
  Serial.print("Connecting to ");
  Serial.println(ssid);
  
  WiFi.begin(ssid, pass);
  while(WiFi.status() != WL_CONNECTED) { 
    Serial.print(".");
    delay(500); 
  }
  
  Serial.println("WiFi connected!"); 
  
  // setup mqtt
  mqttClient.setServer(mqttServer, 1883);
  mqttClient.setCallback(mqttCallback);

  startTimestampMsec = millis();
}

void loop() {
  while(!mqttClient.connected()) {
    Serial.println("MQTT connecting...");

    // mqtt client id is the mac address (AABBCCDDEEFF)
    char mqttClientId[15];    
    uint8_t mac[6];
    WiFi.macAddress(mac);

    snprintf(mqttClientId, sizeof(mqttClientId), "%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);

    if(mqttClient.connect(mqttClientId, mqttUser, mqttPass)) {
      Serial.println("MQTT connected!");
      mqttClient.subscribe(&Out[0]);
    }
    else {
      Serial.print("MQTT connection failed (");
      Serial.print(mqttClient.state());
      Serial.println(")! Retrying...");      
      delay(2500);
    }
  }
  mqttClient.loop();

  // get and display temperature 
  currentTimestampMsec = millis();
  
  if(currentTimestampMsec - startTimestampMsec > readIntervalMsec) {
    // read adc
    const int adcValue = analogRead(thermistorPin);
    // calculate voltage
    const float voltage = adcValue * 3.3 / 4096.0;
    // calculate resistance (voltage divider equation)
    const float resistance = 10.0 * voltage / (3.3 - voltage);
    // calculate temperature using thermistor equation
    // using thermistor with thermal index = 3950, nominal resistance = 10k
    float temp = 1.0 / (log(resistance / 10.0) / 3950 + 1.0 / (273.15 + 25.0));
    temp -= 273.15;

    // label
    snprintf(buf, sizeof(buf), "@temp TXT:\"%.2f °C\"\r\n", temp);
    mqttClient.publish(&In[0], &buf[0]);
    // chart
    snprintf(buf, sizeof(buf), "@tempCh PLI:\"id0\" PLC:\"#4C4C4C\" YP:\"%.1f\"\r\n", temp);
    mqttClient.publish(&In[0], &buf[0]);
    
    Serial.print("T = ");
    Serial.print(temp);
    Serial.println(" °C");

    // reset read interval
    startTimestampMsec = millis();
  }
}

// mqtt callback function
void mqttCallback(char* topic, byte* message, unsigned int length) {
  // build message string
  String msg;
  for(int i = 0; i < length; i++) {
    msg += (char) message[i];
  }
  // parse message string
  parseGuioMsg(msg);
}

void parseGuioMsg(const String &msg) {
  if(msg.startsWith("@init")) {
    Serial.println("GUI-O app is requesting INITIALIZATION!");

    // clear screen and set background
    mqttClient.publish(&In[0], "@cls\r\n");
    mqttClient.publish(&In[0], "@guis BGC:#FFFFFF\r\n");
    delay(100);

    // initialize GUI
    mqttClient.publish(&In[0], "|LB UID:title X:50 Y:20 FSZ:4 FFA:\"font8\" TXT:\"Temperature<br>reader\"\r\n");
    mqttClient.publish(&In[0], "|BSR UID:container X:50 Y:40 W:30 H:5 RAD:1 BTH:0\r\n");
    mqttClient.publish(&In[0], "|LB UID:temp X:50 Y:40 FGC:#FFFFFF TXT:\"n/a\"\r\n");
    mqttClient.publish(&In[0], "|CH UID:tempCh X:50 Y:65 YLO:15 YHI:35\r\n");
    mqttClient.publish(&In[0], "|LB UID:details X:50 Y:90 FSZ:2 TXT:\"GUI-O temperature reader<br>demonstration by kl3m3n\"\r\n");
  }  
}

4. ESTABLISH CONNECTION

Make sure that the GUI-O application is connected to the MQTT server.
Also make sure that the ESP32 board (or other Arduino supported board) is connected to the MQTT server (you can check this by observing the serial debug messages using the Arduino serial monitor).

Press the Initialize button (see image below) from the GUI-O application home screen.

initialize_button.jpg

5. THE RESULT

Image below shows the result (screen capture) on my Android device after pressing the "Initialize" button. The label and chart are updated every time the ESP32 board calculates the temperature and publishes new data to the MQTT server.


If you have any questions or run into any problems, please let me know!

Best regards,
kl3m3n

EPISODE 2: RGB LED CONTROL

I will be using a RGB LED to create various color combinations using three simple / basic slider widgets. The sliders will control each channel separately (red, green and blue) by setting their values between 0-255 through user input.

I will be using GUI-O IoT (MQTT) connection, but the example can be easily be ported to other connection types.

Software prerequisites:

Components needed:

The entire tutorial is split into various steps. All necessary information is given in each step.

0. DESIGN THE GUI (optional)

The best way to create a GUI layout is to use GUI-O live designer tool.

Note that the Arduino source code already includes the necessary commands, so this step is not needed, unless you want to make some visual adjustments. If you make adjustments, please include the generated ASCII code in the Arduino source code (see section 3. MODIFY AND UPLOAD THE SOURCE CODE).

First, you need to establish a TCP/IP connection between the designer tool and GUI-O application:

  1. Determine the local IP address of your PC's network interface (WiFi or Ethernet)
  • Under Windows, open the command prompt, enter ipconfig and press Enter
  • Under Linux, open the terminal, enter ifconfig and press Enter
  1. Open GUI-O application and open settings menu. Select "Connections -> Ethernet" and create a new device with IP address (determined from 1.) and any port between 49152 - 65535

  2. Open GUI-O designer and select "TCP/IP connection" tab. Set the IP address and port. Both values must match the device settings created within the GUI-O application. Click "Start server" button.

  3. Within the GUI-O application, tap the created device and wait for successful connection.

  4. In the GUI-O designer, select "File -> Load designer file" and load the RgbLedControl.gdf design file. Make the desired adjustments, if necessary. Copy / replace the GUI-O commands into the Arduino source code (see section 3. MODIFY AND UPLOAD THE SOURCE CODE).

1. CONNECT THE COMPONENTS

Connecting the components is straightforward. "D25", "D33" and "D32" are GPIO pins that are capable of producing PWM output. The values of the current limiting resistors are not optimal, but in my case, the LED is still bright enough.

schematic.png

2. CREATE A UNIQUE MQTT CHANNEL

Open GUI-O application and navigate to settings menu. Select "Connections -> IoT" and add a new device. After adding the device, note the In and Out token (you can share this tokens e.g., to your e-mail by pressing the "share" button).

Finally, press "Connect" menu entry to establish the connection with the MQTT server.

3. MODIFY AND UPLOAD THE SOURCE CODE

The source code has inline comments, describing the important parts of the code. You can copy the source code from the snippet below, or download it here.

The only thing that needs to be done is to set the ssid and password of your router and the unique In and Out channels that were generated by the GUI-O application (see section 2. CREATE A UNIQUE MQTT CHANNEL).

After setting these values, upload the code to your board (make sure that the correct board and upload port are selected). Reset the board after upload.

/*
 * GUI-O Rgb control MQTT example (using ESP32-WROOM-32)
 *
 * Copyright (C) 2022, kl3m3n
 * last updated on 4.11.2022
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <WiFi.h>
#include <PubSubClient.h>

static const char *ssid = "<ssid>"; // router name
static const char *pass = "<pass>"; // router password

static const char *mqttServer = "mqtt.gui-o.com";   // host name
static const char *mqttUser = "gui-o-mqtt-generic"; // user name
static const char *mqttPass = "lqXeZpv5VJyv0XBT";   // password

// IMPORTANT NOTE: if optional user name was specified when adding a new IoT device,
// the user name should also be included when setting the topics (e.g., "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/<user_name>")
static const char *Out = "<Out>"; // GUI-O app publish topic
static const char *In = "<In>";  // GUI-O app subscribe topic

// mqtt client
WiFiClient wiFiClient;
PubSubClient mqttClient(wiFiClient);

// forward declare functions for mqtt messages handling
void mqttCallback(char* topic, byte* message, unsigned int length);
void parseGuioMsg(const String &msg);

// LED setup
namespace led {
  static const uint8_t R_PIN = 25;
  static const uint8_t G_PIN = 33;
  static const uint8_t B_PIN = 32;
  //
  static const uint8_t R_CHANNEL = 0;
  static const uint8_t G_CHANNEL = 1;
  static const uint8_t B_CHANNEL = 2;
  //
  static const double LED_FREQ = 5000.0;
  static const uint8_t LED_BITS = 8;
} // namespace led

// LED rgb values
uint8_t redValue = 0;
uint8_t greenValue = 0;
uint8_t blueValue = 0;

void setup() {
  // debug output
  Serial.begin(115200);

  // setup LED
  ledcSetup(led::R_CHANNEL, led::LED_FREQ, led::LED_BITS);
  ledcSetup(led::G_CHANNEL, led::LED_FREQ, led::LED_BITS);
  ledcSetup(led::B_CHANNEL, led::LED_FREQ, led::LED_BITS);
  // attach the channels to the GPIOs
  ledcAttachPin(led::R_PIN, led::R_CHANNEL);
  ledcAttachPin(led::G_PIN, led::G_CHANNEL);
  ledcAttachPin(led::B_PIN, led::B_CHANNEL);
  // set initial values ("inverted" logic - see schematic)
  delay(100);
  ledcWrite(led::R_CHANNEL, 255 - redValue);
  ledcWrite(led::G_CHANNEL, 255 - greenValue);
  ledcWrite(led::B_CHANNEL, 255 - blueValue);
  
  // connect WiFi (keep trying...)
  Serial.print("Connecting to ");
  Serial.println(ssid);
  
  WiFi.begin(ssid, pass);
  while(WiFi.status() != WL_CONNECTED) { 
    Serial.print(".");
    delay(500); 
  }
  
  Serial.println("WiFi connected!"); 
  
  // setup mqtt
  mqttClient.setServer(mqttServer, 1883);
  mqttClient.setCallback(mqttCallback);
}

void loop() {
  while(!mqttClient.connected()) {
    Serial.println("MQTT connecting...");

    // mqtt client id is the mac address (AABBCCDDEEFF)
    char mqttClientId[15];    
    uint8_t mac[6];
    WiFi.macAddress(mac);

    snprintf(mqttClientId, sizeof(mqttClientId), "%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);

    if(mqttClient.connect(mqttClientId, mqttUser, mqttPass)) {
      Serial.println("MQTT connected!");
      mqttClient.subscribe(&Out[0]);
    }
    else {
      Serial.print("MQTT connection failed (");
      Serial.print(mqttClient.state());
      Serial.println(")! Retrying...");      
      delay(2500);
    }
  }
  mqttClient.loop();
}

// mqtt callback function
void mqttCallback(char* topic, byte* message, unsigned int length) {
  // build message string
  String msg;
  for(int i = 0; i < length; i++) {
    msg += (char) message[i];
  }
  // parse message string
  parseGuioMsg(msg);
}

void parseGuioMsg(const String &msg) {
  // lambda for LED value extraction
  auto ledValue = [](const String &msg) -> uint8_t {
    uint8_t value = 0;    
    const int idx = msg.indexOf(' ');
    if(idx > 0) {
      value = constrain(msg.substring(idx + 1).toInt(), 0, 255);
      value = (255 - value); // "inverted" logic - see schematic
    }
    return value;
  };
  
  if(msg.startsWith("@init")) {
    Serial.println("GUI-O app is requesting INITIALIZATION!");

    // clear screen and set background
    mqttClient.publish(&In[0], "@cls\r\n");
    mqttClient.publish(&In[0], "@guis BGC:#FFFFFF\r\n");
    delay(100);

    // initialize GUI
    mqttClient.publish(&In[0], "|LB UID:title X:50 Y:20 FSZ:4 FFA:\"font8\" TXT:\"RGB LED<br>control\"\r\n");
    mqttClient.publish(&In[0], "|SL UID:red X:30 Y:50 H:1 ROT:270 FGC:#FF0000 SFGC:#FF0000 HAW:7 HAH:7 HAR:3.5 HAC:#FF0000 IP:\"\" HVAL:255 RAD:0.5\r\n");
    mqttClient.publish(&In[0], "|SL UID:green X:50 Y:50 H:1 ROT:270 FGC:#00FF00 SFGC:#00FF00 HAW:7 HAH:7 HAR:3.5 HAC:#00FF00 IP:\"\" HVAL:255 RAD:0.5\r\n");
    mqttClient.publish(&In[0], "|SL UID:blue X:70 Y:50 H:1 ROT:270 FGC:#0000FF SFGC:#0000FF HAW:7 HAH:7 HAR:3.5 HAC:#0000FF IP:\"\" HVAL:255 RAD:0.5\r\n");
    mqttClient.publish(&In[0], "|LB UID:details X:50 Y:90 FSZ:2 TXT:\"GUI-O RGB LED control<br>demonstration by kl3m3n\"\r\n");    
  }
  else if(msg.startsWith("@red")) {
    redValue = ledValue(msg);
    ledcWrite(led::R_CHANNEL, redValue);
  }
  else if(msg.startsWith("@green")) {
    greenValue = ledValue(msg);
    ledcWrite(led::G_CHANNEL, greenValue);
  }
  else if(msg.startsWith("@blue")) {
    blueValue = ledValue(msg);
    ledcWrite(led::B_CHANNEL, blueValue);
  }  
}

4. ESTABLISH CONNECTION

Make sure that the GUI-O application is connected to the MQTT server.
Also make sure that the ESP32 board (or other Arduino supported board) is connected to the MQTT server (you can check this by observing the serial debug messages using the Arduino serial monitor).

Press the Initialize button (see image below) from the GUI-O application home screen.

initialize_button.jpg

5. THE RESULT

Image below shows the result (screen capture) on my Android device after pressing the "Initialize" button. The sliders control each LED channel through user input.


If you have any questions or run into any problems, please let me know!

Best regards,
kl3m3n

EPISODE 3: NFC READER

I will be using Android builtin NFC reader to read text records of NFC tags. The text records will be displayed in a scroll-able text widget.

I will be using GUI-O Bluetooth SPP connection, but the example can be easily be ported to other connection types.

Software prerequisites:

Components needed:

  • ESP32-WROOM-32 (or any other Arduino supported Bluetooth capable board)

The entire tutorial is split into various steps. All necessary information is given in each step.

0. DESIGN THE GUI (optional)

The best way to create a GUI layout is to use GUI-O live designer tool.

Note that the Arduino source code already includes the necessary commands, so this step is not needed, unless you want to make some visual adjustments. If you make adjustments, please include the generated ASCII code in the Arduino source code (see section 1. UPLOAD THE SOURCE CODE).

First, you need to establish a TCP/IP connection between the designer tool and GUI-O application:

  1. Determine the local IP address of your PC's network interface (WiFi or Ethernet)
  • Under Windows, open the command prompt, enter ipconfig and press Enter
  • Under Linux, open the terminal, enter ifconfig and press Enter
  1. Open GUI-O application and open settings menu. Select "Connections -> Ethernet" and create a new device with IP address (determined from 1.) and any port between 49152 - 65535

  2. Open GUI-O designer and select "TCP/IP connection" tab. Set the IP address and port. Both values must match the device settings created within the GUI-O application. Click "Start server" button.

  3. Within the GUI-O application, tap the created device and wait for successful connection.

  4. In the GUI-O designer, select "File -> Load designer file" and load the NfcReader.gdf design file. Make the desired adjustments, if necessary. Copy / replace the GUI-O commands into the Arduino source code (see section 1. UPLOAD THE SOURCE CODE).

1. UPLOAD THE SOURCE CODE

The source code has inline comments, describing the important parts of the code. You can copy the source code from the snippet below, or download it here.

Upload the code to your board (make sure that the correct board and upload port are selected). Reset the board after upload.

/*
 * GUI-O NFC reader Bluetooth example (using ESP32-WROOM-32)
 *
 * Copyright (C) 2022, kl3m3n
 * last updated on 5.11.2022
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "BluetoothSerial.h"

// if not enabled, the SDK must be recompiled
#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
#error Bluetooth is not enabled! Please run `make menuconfig` to and enable it
#endif

// forward declare parser for incoming messages
void parseGuioMsg(const String &msg);

// icoming data buffer
String in;

// global
BluetoothSerial btSerial;

void setup() {
  // debug output
  Serial.begin(115200);
  
  // setup bluetooth serial
  btSerial.begin("NFCReaderBluetooth");
  
  Serial.println("Bluetooth ready to pair!");
}

void loop() {
  while(btSerial.available()) {
    const char c = btSerial.read();
    in += c;
  
    if(c == '\n') {
      // parse message string
      parseGuioMsg(in);
      // clear buffer
      in = "";
    }  
  }
}

void sendMsg(const String &msg) {
  btSerial.write((const uint8_t*) msg.c_str(), msg.length());
}

void parseGuioMsg(const String &msg) {
  if(msg.startsWith("@init")) {
    Serial.println("GUI-O app is requesting INITIALIZATION!");

    // clear screen, hardware and set background
    sendMsg("@cls\r\n");
    sendMsg("@clh\r\n");
    sendMsg("@guis BGC:#FFFFFF\r\n");
    delay(100);

    // initialize GUI
    sendMsg("|LB UID:title X:50 Y:20 FSZ:4 FFA:\"font8\" TXT:\"NFC<br>reader\"\r\n");
    sendMsg("|TA UID:nfcOutput X:50 Y:50 W:70 H:40 FSZ:2 FGC:#FFFFFF\r\n");
    sendMsg("|LB UID:details X:50 Y:90 FSZ:2 TXT:\"GUI-O NFC reader<br>demonstration by kl3m3n\"\r\n");
    // initialize NFC hardware
    sendMsg("|NFC UID:nfc HID:nfc SEN:1\r\n");
  }
  else if(msg.startsWith("@nfc")) {
    const auto idx = msg.indexOf(' ');
    if(idx > 0) {
      String txt = msg.substring(idx + 1); txt.trim();
      // display NFC (text) record
      sendMsg("@nfcOutput TXT:\"" + txt + "\"\r\n");
    }
  }
}

2. ESTABLISH CONNECTION

Make sure that the ESP32 board is ready for pairing (you can check this by observing the serial debug messages using the Arduino serial monitor).

Open GUI-O application and navigate to settings menu. Select "Connections -> Bluetooth and IoT" and search for devices (enable Bluetooth and Location services, if prompted). Tap on the "NFCReaderBluetooth" device and wait for successful connection (confirm device pairing if prompted).

Close the settings menu and press the Initialize button (see image below) from the GUI-O application home screen.

initialize_button.jpg

3. THE RESULT

Image below shows the result (screen capture) on my Android device after pressing the "Initialize" button and scanning two different NFC tags (I added some arbitrary text records using a third-party application).

Note that if code "-2" is displayed after pressing the "Initialize" button, the NFC is available, but is not enabled. In this case you should enable NFC through Android system settings. If code "-1" is displayed, your device does not support NFC.


If you have any questions or run into any problems, please let me know!

Best regards,
kl3m3n

EPISODE 4: RESOURCE DOWNLOADER

This is a simple example that shows how to download videos and images from online resources, which can be incorporated into any application.

I will be using GUI-O IoT (MQTT) connection, but the example can be easily be ported to other connection types.

Software prerequisites:

Components needed:

The entire tutorial is split into various steps. All necessary information is given in each step.

0. DESIGN THE GUI (optional)

The best way to create a GUI layout is to use GUI-O live designer tool.

Note that the Arduino source code already includes the necessary commands, so this step is not needed, unless you want to make some visual adjustments. If you make adjustments, please include the generated ASCII code in the Arduino source code (see section 3. MODIFY AND UPLOAD THE SOURCE CODE).

First, you need to establish a TCP/IP connection between the designer tool and GUI-O application:

  1. Determine the local IP address of your PC's network interface (WiFi or Ethernet)
  • Under Windows, open the command prompt, enter ipconfig and press Enter
  • Under Linux, open the terminal, enter ifconfig and press Enter
  1. Open GUI-O application and open settings menu. Select "Connections -> Ethernet" and create a new device with IP address (determined from 1.) and any port between 49152 - 65535

  2. Open GUI-O designer and select "TCP/IP connection" tab. Set the IP address and port. Both values must match the device settings created within the GUI-O application. Click "Start server" button.

  3. Within the GUI-O application, tap the created device and wait for successful connection.

  4. In the GUI-O designer, select "File -> Load designer file" and load the ResourceDownloader.gdf design file. Make the desired adjustments, if necessary. Copy / replace the GUI-O commands into the Arduino source code (see section 3. MODIFY AND UPLOAD THE SOURCE CODE).

1. CREATE A UNIQUE MQTT CHANNEL

Open GUI-O application and navigate to settings menu. Select "Connections -> IoT" and add a new device. After adding the device, note the In and Out token (you can share this tokens e.g., to your e-mail by pressing the "share" button).

Finally, press "Connect" menu entry to establish the connection with the MQTT server.

2. MODIFY AND UPLOAD THE SOURCE CODE

The source code has inline comments, describing the important parts of the code. You can copy the source code from the snippet below, or download it here.

The only thing that needs to be done is to set the ssid and password of your router and the unique In and Out channels that were generated by the GUI-O application (see section 2. CREATE A UNIQUE MQTT CHANNEL).

After setting these values, upload the code to your board (make sure that the correct board and upload port are selected). Reset the board after upload.

/*
 * GUI-O Resource downloader MQTT example (using ESP32-WROOM-32)
 *
 * Copyright (C) 2022, kl3m3n
 * last updated on 12.11.2022
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <WiFi.h>
#include <PubSubClient.h>

static const char *ssid = "<ssid>"; // router name
static const char *pass = "<pass>"; // router password

static const char *mqttServer = "mqtt.gui-o.com";   // host name
static const char *mqttUser = "gui-o-mqtt-generic"; // user name
static const char *mqttPass = "lqXeZpv5VJyv0XBT";   // password

// IMPORTANT NOTE: if optional user name was specified when adding a new IoT device,
// the user name should also be included when setting the topics (e.g., "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/<user_name>")
static const char *Out = "<Out>"; // GUI-O app publish topic
static const char *In = "<In>";  // GUI-O app subscribe topic

// mqtt client
WiFiClient wiFiClient;
PubSubClient mqttClient(wiFiClient);

// forward declare functions for mqtt messages handling
void mqttCallback(char* topic, byte* message, unsigned int length);
void parseGuioMsg(const String &msg);

void setup() {
  // debug output
  Serial.begin(115200);
  
  // connect WiFi (keep trying...)
  Serial.print("Connecting to ");
  Serial.println(ssid);
  
  WiFi.begin(ssid, pass);
  while(WiFi.status() != WL_CONNECTED) { 
    Serial.print(".");
    delay(500); 
  }
  
  Serial.println("WiFi connected!"); 
  
  // setup mqtt
  mqttClient.setServer(mqttServer, 1883);
  mqttClient.setCallback(mqttCallback);
}

void loop() {
  while(!mqttClient.connected()) {
    Serial.println("MQTT connecting...");

    // mqtt client id is the mac address (AABBCCDDEEFF)
    char mqttClientId[15];    
    uint8_t mac[6];
    WiFi.macAddress(mac);

    snprintf(mqttClientId, sizeof(mqttClientId), "%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);

    if(mqttClient.connect(mqttClientId, mqttUser, mqttPass)) {
      Serial.println("MQTT connected!");
      mqttClient.subscribe(&Out[0]);
    }
    else {
      Serial.print("MQTT connection failed (");
      Serial.print(mqttClient.state());
      Serial.println(")! Retrying...");      
      delay(2500);
    }
  }
  mqttClient.loop();
}

// mqtt callback function
void mqttCallback(char* topic, byte* message, unsigned int length) {
  // build message string
  String msg;
  for(int i = 0; i < length; i++) {
    msg += (char) message[i];
  }
  // parse message string
  parseGuioMsg(msg);
}

void parseGuioMsg(const String &msg) {
  if(msg.startsWith("@init")) {
    Serial.println("GUI-O app is requesting INITIALIZATION!");

    // clear screen and set background
    mqttClient.publish(&In[0], "@cls\r\n");
    mqttClient.publish(&In[0], "@guis BGC:#FFFFFF\r\n");
    delay(100);

    // initialize GUI
    mqttClient.publish(&In[0], "|LB UID:title X:50 Y:20 FSZ:4 FFA:\"font8\" TXT:\"Resource<br>downloader\"\r\n");
    mqttClient.publish(&In[0], "|VI UID:fire X:50 Y:50 W:70 VP:\"https://i.imgur.com/lVwrTnZ.mp4\"\r\n");
    mqttClient.publish(&In[0], "|IM UID:play X:30 Y:70 W:15 IP:\"https://i.imgur.com/Gs2ERbO.png\"\r\n");
    mqttClient.publish(&In[0], "|IM UID:pause X:70 Y:70 W:15 IP:\"https://i.imgur.com/FXywHFH.png\"\r\n");
    mqttClient.publish(&In[0], "|LB UID:details X:50 Y:90 FSZ:2 TXT:\"GUI-O resource downloader<br>demonstration by kl3m3n\"\r\n");
  }
  else if(msg.startsWith("@play")) {
    mqttClient.publish(&In[0], "@fire PLS:1\r\n");
  }
  else if(msg.startsWith("@pause")) {
    mqttClient.publish(&In[0], "@fire PLS:2\r\n");    
  }
}

3. ESTABLISH CONNECTION

Make sure that the GUI-O application is connected to the MQTT server.
Also make sure that the ESP32 board (or other Arduino supported board) is connected to the MQTT server (you can check this by observing the serial debug messages using the Arduino serial monitor).

NOTE: make sure that you are connected to WiFi as the GUI-O app will require connection in order to download the resources. If mobile data connection is used, please open the settings menu and enable data usage from "General settings -> Network usage -> Use any network". The resources will be downloaded only once; after that, the resources will be loaded from local storage.

Press the Initialize button (see image below) from the GUI-O application home screen.

initialize_button.jpg

4. THE RESULT

Image below shows the result (screen capture) on my Android device after pressing the "Initialize" button. The video playback state can be controlled with the "play" and "pause" buttons. Note that the buttons are also images downloaded from online resource.


If you have any questions or run into any problems, please let me know!

Best regards,
kl3m3n

EPISODE 5: HUMIDITY AND TEMPERATURE READER

I will be using a DHT11 sensor to read and display humidity and ambient temperature. Using a drop-down selector, the indicators for each quantity can be shown or hidden.

I will be using GUI-O IoT (MQTT) connection, but the example can be easily be ported to other connection types.

Software prerequisites:

Components needed:

The entire tutorial is split into various steps. All necessary information is given in each step.

0. DESIGN THE GUI (optional)

The best way to create a GUI layout is to use GUI-O live designer tool.

Note that the Arduino source code already includes the necessary commands, so this step is not needed, unless you want to make some visual adjustments. If you make adjustments, please include the generated ASCII code in the Arduino source code (see section 3. MODIFY AND UPLOAD THE SOURCE CODE).

First, you need to establish a TCP/IP connection between the designer tool and GUI-O application:

  1. Determine the local IP address of your PC's network interface (WiFi or Ethernet)
  • Under Windows, open the command prompt, enter ipconfig and press Enter
  • Under Linux, open the terminal, enter ifconfig and press Enter
  1. Open GUI-O application and open settings menu. Select "Connections -> Ethernet" and create a new device with IP address (determined from 1.) and any port between 49152 - 65535

  2. Open GUI-O designer and select "TCP/IP connection" tab. Set the IP address and port. Both values must match the device settings created within the GUI-O application. Click "Start server" button.

  3. Within the GUI-O application, tap the created device and wait for successful connection.

  4. In the GUI-O designer, select "File -> Load designer file" and load the HumidityTemperatureReader.gdf design file. Make the desired adjustments, if necessary. Copy / replace the GUI-O commands into the Arduino source code (see section 3. MODIFY AND UPLOAD THE SOURCE CODE).

1. CONNECT THE COMPONENTS

Connecting the components is straightforward.

2. CREATE A UNIQUE MQTT CHANNEL

Open GUI-O application and navigate to settings menu. Select "Connections -> IoT" and add a new device. After adding the device, note the In and Out token (you can share this tokens e.g., to your e-mail by pressing the "share" button).

Finally, press "Connect" menu entry to establish the connection with the MQTT server.

3. MODIFY AND UPLOAD THE SOURCE CODE

The source code has inline comments, describing the important parts of the code. You can copy the source code from the snippet below, or download it here.

The only thing that needs to be done is to set the ssid and password of your router and the unique In and Out channels that were generated by the GUI-O application (see section 2. CREATE A UNIQUE MQTT CHANNEL).

After setting these values, upload the code to your board (make sure that the correct board and upload port are selected). Reset the board after upload.

/*
 * GUI-O Humidity and temperature reader MQTT example (using ESP32-WROOM-32)
 *
 * Copyright (C) 2022, kl3m3n
 * last updated on 12.11.2022
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <WiFi.h>
#include <PubSubClient.h>
#include <DHT.h>

static const char *ssid = "<ssid>"; // router name
static const char *pass = "<pass>"; // router password

static const char *mqttServer = "mqtt.gui-o.com";   // host name
static const char *mqttUser = "gui-o-mqtt-generic"; // user name
static const char *mqttPass = "lqXeZpv5VJyv0XBT";   // password

// IMPORTANT NOTE: if optional user name was specified when adding a new IoT device,
// the user name should also be included when setting the topics (e.g., "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/<user_name>")
static const char *Out = "<Out>"; // GUI-O app publish topic
static const char *In = "<In>";  // GUI-O app subscribe topic

// mqtt client
WiFiClient wiFiClient;
PubSubClient mqttClient(wiFiClient);

// forward declare functions for mqtt messages handling
void mqttCallback(char* topic, byte* message, unsigned int length);
void parseGuioMsg(const String &msg);

// using DHT11 sensor
char buf[100];
const int dhtPin = 25;
DHT dht(dhtPin, DHT11);

unsigned long startTimestampMsec, currentTimestampMsec;
const unsigned long readIntervalMsec = 2000; // milliseconds

void setup() {
  // debug output
  Serial.begin(115200);

  // dht setup
  dht.begin();
  
  // connect WiFi (keep trying...)
  Serial.print("Connecting to ");
  Serial.println(ssid);
  
  WiFi.begin(ssid, pass);
  while(WiFi.status() != WL_CONNECTED) { 
    Serial.print(".");
    delay(500); 
  }
  
  Serial.println("WiFi connected!"); 
  
  // setup mqtt
  mqttClient.setServer(mqttServer, 1883);
  mqttClient.setCallback(mqttCallback);

  startTimestampMsec = millis();
}

void loop() {
  while(!mqttClient.connected()) {
    Serial.println("MQTT connecting...");

    // mqtt client id is the mac address (AABBCCDDEEFF)
    char mqttClientId[15];    
    uint8_t mac[6];
    WiFi.macAddress(mac);

    snprintf(mqttClientId, sizeof(mqttClientId), "%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);

    if(mqttClient.connect(mqttClientId, mqttUser, mqttPass)) {
      Serial.println("MQTT connected!");
      mqttClient.subscribe(&Out[0]);
    }
    else {
      Serial.print("MQTT connection failed (");
      Serial.print(mqttClient.state());
      Serial.println(")! Retrying...");      
      delay(2500);
    }
  }
  mqttClient.loop();

  currentTimestampMsec = millis();

  if(currentTimestampMsec - startTimestampMsec > readIntervalMsec) {
    // read humidity and temperature (in °C)
    float hum = dht.readHumidity();
    float temp = dht.readTemperature();

    if(!isnan(hum) && !isnan(temp)) {
      // labels
      snprintf(buf, sizeof(buf), "@hum TXT:\"%.0f %%\"\r\n", hum);
      mqttClient.publish(&In[0], &buf[0]);
      //
      snprintf(buf, sizeof(buf), "@temp TXT:\"%.1f °C\"\r\n", temp);
      mqttClient.publish(&In[0], &buf[0]);
      
      // dial indicators
      snprintf(buf, sizeof(buf), "@humInd VAL:%.0f %%\r\n", hum);
      mqttClient.publish(&In[0], &buf[0]);
      //
      snprintf(buf, sizeof(buf), "@tempInd VAL:%.0f °C\r\n", temp);
      mqttClient.publish(&In[0], &buf[0]);

      Serial.print("H = ");
      Serial.print(hum);
      Serial.println(" %");

      Serial.print("T = ");
      Serial.print(temp);
      Serial.println(" °C");
    }
    
    startTimestampMsec = millis();
  }
}

// mqtt callback function
void mqttCallback(char* topic, byte* message, unsigned int length) {
  // build message string
  String msg;
  for(int i = 0; i < length; i++) {
    msg += (char) message[i];
  }
  // parse message string
  parseGuioMsg(msg);
}

void parseGuioMsg(const String &msg) {
  if(msg.startsWith("@init")) {
    Serial.println("GUI-O app is requesting INITIALIZATION!");

    // clear screen and set background
    mqttClient.publish(&In[0], "@cls\r\n");
    mqttClient.publish(&In[0], "@guis BGC:#FFFFFF\r\n");
    delay(100);

    // initialize GUI
    mqttClient.publish(&In[0], "|DRB UID:display X:50 Y:10 W:50 FGC:#FFFFFF DAL:\"Both,Temperature,Humidity\"\r\n");
    mqttClient.publish(&In[0], "|CB UID:tempInd X:50 Y:35 SFGC:#FF281E IP:\"\" HVAL:40 TXTC:#000000 XTC:4 YTC:0 RCA:0 CE:0 SHT:1\r\n");
    mqttClient.publish(&In[0], "|LB UID:temp X:50 Y:35 FGC:#000000 FSZ:5 FFA:\"font6\" TXT:\"\"\r\n");
    mqttClient.publish(&In[0], "|CB UID:humInd X:50 Y:65 IP:\"\" TXTC:#000000 XTC:4 YTC:0 RCA:0 CE:0 SHT:1\r\n");
    mqttClient.publish(&In[0], "|LB UID:hum X:50 Y:65 FGC:#000000 FSZ:5 FFA:\"font6\" TXT:\"\"\r\n");
    mqttClient.publish(&In[0], "|LB UID:details X:50 Y:90 FSZ:2 TXT:\"GUI-O humidity and temperature<br>reader demonstration by kl3m3n\"\r\n");
  }
  else if(msg.startsWith("@display")) {
    // could do this better, but does the job fine (don't remove the trailing spaces...)
    if(msg.indexOf("Temperature ") >= 0) {
      // show temperature only
      mqttClient.publish(&In[0], "@tempInd VIS:1\r\n");
      mqttClient.publish(&In[0], "@temp VIS:1\r\n");
      //
      mqttClient.publish(&In[0], "@humInd VIS:0\r\n");
      mqttClient.publish(&In[0], "@hum VIS:0\r\n");
    }
    else if(msg.indexOf("Humidity ") >= 0) {
      // show humidity only
      mqttClient.publish(&In[0], "@tempInd VIS:0\r\n");
      mqttClient.publish(&In[0], "@temp VIS:0\r\n");
      //
      mqttClient.publish(&In[0], "@humInd VIS:1\r\n");
      mqttClient.publish(&In[0], "@hum VIS:1\r\n");
    }
    else {
      // show all
      mqttClient.publish(&In[0], "@tempInd VIS:1\r\n");
      mqttClient.publish(&In[0], "@temp VIS:1\r\n");
      //
      mqttClient.publish(&In[0], "@humInd VIS:1\r\n");
      mqttClient.publish(&In[0], "@hum VIS:1\r\n");
    }
  }    
}

4. ESTABLISH CONNECTION

Make sure that the GUI-O application is connected to the MQTT server.
Also make sure that the ESP32 board (or other Arduino supported board) is connected to the MQTT server (you can check this by observing the serial debug messages using the Arduino serial monitor).

Press the Initialize button (see image below) from the GUI-O application home screen.

initialize_button.jpg

5. THE RESULT

Images below shows the result (screen capture) on my Android device after pressing the "Initialize" button. The humidity and temperature indicators are updated every 2 seconds. The visibility of indicators can be controlled via the drop-down box widget.




If you have any questions or run into any problems, please let me know!

Best regards,
kl3m3n

1 Like

EPISODE 6: ULTRASONIC RANGING

I will be using an ultrasonic distance sensor (HC-SR04) to measure the distance to an arbitrary object. The display will show the object distance in centimeters and a graphical indicator denoting the proximity status.

I will be using GUI-O IoT (MQTT) connection, but the example can be easily be ported to other connection types.

Software prerequisites:

Components needed:

The entire tutorial is split into various steps. All necessary information is given in each step.

0. DESIGN THE GUI (optional)

The best way to create a GUI layout is to use GUI-O live designer tool.

Note that the Arduino source code already includes the necessary commands, so this step is not needed, unless you want to make some visual adjustments. If you make adjustments, please include the generated ASCII code in the Arduino source code (see section 3. MODIFY AND UPLOAD THE SOURCE CODE).

First, you need to establish a TCP/IP connection between the designer tool and GUI-O application:

  1. Determine the local IP address of your PC's network interface (WiFi or Ethernet)
  • Under Windows, open the command prompt, enter ipconfig and press Enter
  • Under Linux, open the terminal, enter ifconfig and press Enter
  1. Open GUI-O application and open settings menu. Select "Connections -> Ethernet" and create a new device with IP address (determined from 1.) and any port between 49152 - 65535

  2. Open GUI-O designer and select "TCP/IP connection" tab. Set the IP address and port. Both values must match the device settings created within the GUI-O application. Click "Start server" button.

  3. Within the GUI-O application, tap the created device and wait for successful connection.

  4. In the GUI-O designer, select "File -> Load designer file" and load the UltrasonicRanging.gdf design file. Make the desired adjustments, if necessary. Copy / replace the GUI-O commands into the Arduino source code (see section 3. MODIFY AND UPLOAD THE SOURCE CODE).

1. CONNECT THE COMPONENTS

Connecting the components is straightforward. The "D32" and "D33" are GPIO pins on ESP32.

schematic.png

2. CREATE A UNIQUE MQTT CHANNEL

Open GUI-O application and navigate to settings menu. Select "Connections -> IoT" and add a new device. After adding the device, note the In and Out token (you can share this tokens e.g., to your e-mail by pressing the "share" button).

Finally, press "Connect" menu entry to establish the connection with the MQTT server.

3. MODIFY AND UPLOAD THE SOURCE CODE

The source code has inline comments, describing the important parts of the code. You can copy the source code from the snippet below, or download it here.

The only thing that needs to be done is to set the ssid and password of your router and the unique In and Out channels that were generated by the GUI-O application (see section 2. CREATE A UNIQUE MQTT CHANNEL).

After setting these values, upload the code to your board (make sure that the correct board and upload port are selected). Reset the board after upload.

/*
 * GUI-O Ultrasonic ranging MQTT example (using ESP32-WROOM-32)
 *
 * Copyright (C) 2022, kl3m3n
 * last updated on 19.11.2022
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <WiFi.h>
#include <PubSubClient.h>

static const char *ssid = "<ssid>"; // router name
static const char *pass = "<pass>"; // router password

static const char *mqttServer = "mqtt.gui-o.com";   // host name
static const char *mqttUser = "gui-o-mqtt-generic"; // user name
static const char *mqttPass = "lqXeZpv5VJyv0XBT";   // password

// IMPORTANT NOTE: if optional user name was specified when adding a new IoT device,
// the user name should also be included when setting the topics (e.g., "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/<user_name>")
static const char *Out = "<Out>"; // GUI-O app publish topic
static const char *In = "<In>";  // GUI-O app subscribe topic

// mqtt client
WiFiClient wiFiClient;
PubSubClient mqttClient(wiFiClient);

// forward declare functions for mqtt messages handling
void mqttCallback(char* topic, byte* message, unsigned int length);
void parseGuioMsg(const String &msg);

char buf[200];

// setup ranging settings
const int triggerPin = 32;
const int echoPin = 33;
// speed of sound in m/us
constexpr float soundSpeedUs = 340.0 / 1000000.0;

// sampling
unsigned long startTimestampMsec, currentTimestampMsec;
const unsigned long readIntervalMsec = 250; // milliseconds

void setup() {
  // debug output
  Serial.begin(115200);

  // setup pins
  pinMode(triggerPin, OUTPUT);
  pinMode(echoPin, INPUT);
  
  // connect WiFi (keep trying...)
  Serial.print("Connecting to ");
  Serial.println(ssid);
  
  WiFi.begin(ssid, pass);
  while(WiFi.status() != WL_CONNECTED) { 
    Serial.print(".");
    delay(500); 
  }
  
  Serial.println("WiFi connected!"); 
  
  // setup mqtt
  mqttClient.setServer(mqttServer, 1883);
  mqttClient.setCallback(mqttCallback);

  startTimestampMsec = millis();
}

void loop() {
  while(!mqttClient.connected()) {
    Serial.println("MQTT connecting...");

    // mqtt client id is the mac address (AABBCCDDEEFF)
    char mqttClientId[15];    
    uint8_t mac[6];
    WiFi.macAddress(mac);

    snprintf(mqttClientId, sizeof(mqttClientId), "%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);

    if(mqttClient.connect(mqttClientId, mqttUser, mqttPass)) {
      Serial.println("MQTT connected!");
      mqttClient.subscribe(&Out[0]);
    }
    else {
      Serial.print("MQTT connection failed (");
      Serial.print(mqttClient.state());
      Serial.println(")! Retrying...");      
      delay(2500);
    }
  }
  mqttClient.loop();

  // get and display temperature 
  currentTimestampMsec = millis();
  
  if(currentTimestampMsec - startTimestampMsec > readIntervalMsec) {
    // pull low, just in case...
    digitalWrite(triggerPin, LOW);
    delayMicroseconds(2);
    // output high level to trigger pin for a short duration
    digitalWrite(triggerPin, HIGH);
    delayMicroseconds(10); // as per datasheet
    digitalWrite(triggerPin, LOW);

    // get distance in cm
    unsigned long pingTime = pulseIn(echoPin, HIGH); //, pingTimeout);
    const float distance = (static_cast<float>(pingTime) * soundSpeedUs * 0.5) * 100.0;

    auto pubBuf = []() { mqttClient.publish(&In[0], &buf[0]); };

    // update distance indicator (keeping implementation simple, though not very readable...)
    // too close
    if(distance < 20.0) {
      snprintf(buf, sizeof(buf), "@dist TXT:\"%.1f cm\" FGC:#FF0000\r\n", distance);
      pubBuf();
      //
      snprintf(buf, sizeof(buf), "@distInd IDXC:\"0:#00FF00,1:#00FF00,2:#FFA500,3:#FFA500,4:#FF0000\"\r\n");
      pubBuf();
    }
    // very close
    else if(distance < 30.0) {
      snprintf(buf, sizeof(buf), "@dist TXT:\"%.1f cm\" FGC:#FFA500\r\n", distance);
      pubBuf();
      //
      snprintf(buf, sizeof(buf), "@distInd IDXC:\"0:#00FF00,1:#00FF00,2:#FFA500,3:#FFA500,4:#D3D3D3\"\r\n");
      pubBuf();
    }
    // close
    else if(distance < 50.0) {
      snprintf(buf, sizeof(buf), "@dist TXT:\"%.1f cm\" FGC:#FFA500\r\n", distance);
      pubBuf();
      //
      snprintf(buf, sizeof(buf), "@distInd IDXC:\"0:#00FF00,1:#00FF00,2:#FFA500,3:#D3D3D3,4:#D3D3D3\"\r\n");
      pubBuf();
    }
    // far
    else if(distance < 70.0) {
      snprintf(buf, sizeof(buf), "@dist TXT:\"%.1f cm\" FGC:#00FF00\r\n", distance);
      pubBuf();
      //
      snprintf(buf, sizeof(buf), "@distInd IDXC:\"0:#00FF00,1:#00FF00,2:#D3D3D3,3:#D3D3D3,4:#D3D3D3\"\r\n");
      pubBuf();
    }
    // very far
    else {
      snprintf(buf, sizeof(buf), "@dist TXT:\"%.1f cm\" FGC:#00FF00\r\n", distance);
      pubBuf();
      //
      snprintf(buf, sizeof(buf), "@distInd IDXC:\"0:#00FF00,1:#D3D3D3,2:#D3D3D3,3:#D3D3D3,4:#D3D3D3\"\r\n");
      pubBuf();
    }

    Serial.print("ping time = ");
    Serial.print(pingTime);
    Serial.println(" us");
    Serial.print("distance = ");
    Serial.print(distance);
    Serial.println(" cm\n");

    // reset read interval
    startTimestampMsec = millis();
  }
}

// mqtt callback function
void mqttCallback(char* topic, byte* message, unsigned int length) {
  // build message string
  String msg;
  for(int i = 0; i < length; i++) {
    msg += (char) message[i];
  }
  // parse message string
  parseGuioMsg(msg);
}

void parseGuioMsg(const String &msg) {
  if(msg.startsWith("@init")) {
    Serial.println("GUI-O app is requesting INITIALIZATION!");

    // clear screen and set background
    mqttClient.publish(&In[0], "@cls\r\n");
    mqttClient.publish(&In[0], "@guis BGC:#FFFFFF\r\n");
    delay(100);

    // initialize GUI
    mqttClient.publish(&In[0], "|LB UID:title X:50 Y:20 FSZ:4 FFA:\"font8\" TXT:\"Ultrasonic<br>ranging\"\r\n");
    mqttClient.publish(&In[0], "|RECG UID:distInd X:50 Y:50 W:30 H:20 HIW:0 ROWS:5 COLS:1 SPC:0.5\r\n");
    mqttClient.publish(&In[0], "|LB UID:dist X:50 Y:70 FGC:#4C4C4C FSZ:6 TXT:\"n/a\"\r\n");
    mqttClient.publish(&In[0], "|LB UID:details X:50 Y:90 FSZ:2 TXT:\"GUI-O ultrasonic ranging<br>demonstration by kl3m3n\"\r\n");

    // must send as a new message (minor GUI-O application bug)
    mqttClient.publish(&In[0], "@distInd IDXC:\"0:#D3D3D3,1:#D3D3D3,2:#D3D3D3,3:#D3D3D3,4:#D3D3D3\"\r\n");
  }  
}

4. ESTABLISH CONNECTION

Make sure that the GUI-O application is connected to the MQTT server.
Also make sure that the ESP32 board (or other Arduino supported board) is connected to the MQTT server (you can check this by observing the serial debug messages using the Arduino serial monitor).

Press the Initialize button (see image below) from the GUI-O application home screen.

initialize_button.jpg

5. THE RESULT

Image below shows the result (screen capture) on my Android device after pressing the "Initialize" button. The bars graphically represent the object proximity based on some predefined intervals (green = far, orange = close, red = collision imminent).


If you have any questions or run into any problems, please let me know!

Best regards,
kl3m3n

EPISODE 7 (INTERMEZZO): GUI-O LIVE DESIGNER

In this episode, I will show how to simply (and quickly) prototype a graphical user interface (GUI) using GUI-O application and GUI-O designer.

Software prerequisites:

What is GUI-O designer?

GUI-O designer is a PC tool (available on Windows and Linux) that helps you build graphical user interfaces on Android smart phones or tablets. It generates the necessary GUI initialization commands (in ASCII format), which can be included into any project's source code.

How does it work?

GUI-O designer works in "live" mode - all changes to the widgets' properties are immediately reflected on the Android device. Two communication options are available:

1. TCP/IP
This is the simplest approach as (in general), no additional hardware is needed. The GUI-O designer spawns a server using the IP address of the network interface (WiFi or Ethernet), listening on a specified port. The GUI-O application must then use the "Ethernet" connection type, specifying the IP address and port of the server. Both devices (PC and Android) must be on the same local network.

2. Serial port
In this case, a serial device such as USB Serial Bluetooth is required in order to connect to the GUI-O application (see wiring diagram below). Note that any other USB Serial device can also be used, as long as it can be connected to the GUI-O application - check out the manual to see which connection types are supported by the application.

Getting started

Note that I will be using the TCP/IP communication option as no additional hardware is required in this case and is therefore easier to set up.

Please watch the video tutorial below to see GUI-O designer in action. All necessary steps are also described below.

Step 1: Setup connection

Follow the steps below to setup the connection between the GUI-O designer and GUI-O application:

  1. Determine the IP address of your network interface:

    • for Windows, open the command prompt (cmd) and use "ipconfig" command to display the IP address of the WiFi (or Ethernet) interface on your local network

    • for Linux, open the terminal and use "ifconfig" command to display the IP address of the WiFi (or Ethernet) interface on your local network

  2. Open the GUI-O designer. Under Windows, run "guiodesigner.exe" and under Linux, run "guiodesigner" script.

  3. Enter the IP address and arbitrary (unused) port number in the range between 49152 - 65535.

  4. Press "Start server" button.

  5. Start the GUI-O application, open settings menu and select "Connections". Select "Ethernet" connection type and create a new device using the same IP address and port number as above.

  6. Tap on the newly created device to connect GUI-O application client to the GUI-O designer server.

Step 2: Start designing

After the successful connection is made, widget and hardware objects can be created and this is immediately reflected on the Android device. For each created object, numerous properties can be modified.

Step 3: Copy the design to your project

The initialization commands are displayed in the GUI-O command window. Commands need to be included into your project's source code and sent to the GUI-O application via any of the supported interfaces, whenever the initialization request (@init) is made. Additionally, your project's code must handle all user events that are triggered by the GUI-O application.


As always - if you have any questions or run into any problems, please let me know!

Best regards,
kl3m3n

EPISODE 8: DC MOTOR CONTROL

I will be using a dedicated motor driver IC to control the direction and speed of a DC motor via a virtual joystick widget.

I will be using GUI-O Bluetooth Low Energy (LE) connection, but the example can be easily be ported to other connection types.

Software prerequisites:

Components needed:

  • ESP32-WROOM-32 (or any other Arduino supported Bluetooth Low Energy capable board)
  • L239D motor driver
  • Small DC motor

The entire tutorial is split into various steps. All necessary information is given in each step.

0. DESIGN THE GUI (optional)

The best way to create a GUI layout is to use GUI-O live designer tool.

Note that the Arduino source code already includes the necessary commands, so this step is not needed, unless you want to make some visual adjustments. If you make adjustments, please include the generated ASCII code in the Arduino source code (see section 1. UPLOAD THE SOURCE CODE).

First, you need to establish a TCP/IP connection between the designer tool and GUI-O application:

  1. Determine the local IP address of your PC's network interface (WiFi or Ethernet)
  • Under Windows, open the command prompt, enter ipconfig and press Enter
  • Under Linux, open the terminal, enter ifconfig and press Enter
  1. Open GUI-O application and open settings menu. Select "Connections -> Ethernet" and create a new device with IP address (determined from 1.) and any port between 49152 - 65535

  2. Open GUI-O designer and select "TCP/IP connection" tab. Set the IP address and port. Both values must match the device settings created within the GUI-O application. Click "Start server" button.

  3. Within the GUI-O application, tap the created device and wait for successful connection.

  4. In the GUI-O designer, select "File -> Load designer file" and load the MotorControl.gdf design file. Make the desired adjustments, if necessary. Copy / replace the GUI-O commands into the Arduino source code (see section 1. UPLOAD THE SOURCE CODE).

1. CONNECT THE COMPONENTS

Connecting the components is straightforward. "D25" pin is used to set the motor speed (via PWM output), while pins "D33" and "D32" are used to control the motor direction.

2. UPLOAD THE SOURCE CODE

The source code has inline comments, describing the important parts of the code. You can copy the source code from the snippet below, or download it here.

Upload the code to your board (make sure that the correct board and upload port are selected). Reset the board after upload.

/*
 * GUI-O Motor control Bluetooth example (using ESP32-WROOM-32)
 *
 * Copyright (C) 2022, kl3m3n
 * last updated on 3.12.2022
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

namespace uuid {
  static const char *SERVICE_UUID = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E";
  static const char *RX_CHARACTERISTIC_UUID = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E";
  static const char *TX_CHARACTERISTIC_UUID = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E";
} // namespace uuid

// forward declare parser for incoming messages
void parseGuioMsg(const String &msg);

// setup done flag
bool setupDone = false;

// setup motor control pins
const int enablePin = 25;
const int inPin_1 = 32;
const int inPin_2 = 33;
// for controlling motor speed...
const double PWM_FREQ = 1000.0;
const uint8_t PWM_BITS = 8;
const uint8_t PWM_CHANNEL = 0;

// custom handling of server callbacks
class CustomBLEServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
      Serial.println("Connected!");
    };
    void onDisconnect(BLEServer* pServer) {
      Serial.println("Disconnected!");
      
      // restart advertising after disconnect, otherwise GUI-O cannot re-connect
      if(setupDone) {
        // restart advertising on disconnect
        delay(500);
        pServer->startAdvertising(); 
      }
    }
};

// custom handling of characteristic callbacks
class CustomBLECharacteristicCallbacks: public BLECharacteristicCallbacks {
  void onWrite(BLECharacteristic *pCharacteristic) {
    std::string msg = pCharacteristic->getValue();
    
    // parse message string
    parseGuioMsg(String(msg.c_str()));
  }      
};

// global ptr
BLECharacteristic *pTxCharacteristic;

void setup() {
  // debug output
  Serial.begin(115200);

  // motor control setup (speed, direction)
  ledcSetup(PWM_CHANNEL, PWM_FREQ, PWM_BITS);
  ledcAttachPin(enablePin, PWM_CHANNEL);
  pinMode(inPin_1, OUTPUT);
  pinMode(inPin_2, OUTPUT);

  // create device
  BLEDevice::init("MotorControl");
  // create server and register callback
  BLEServer *pServer = BLEDevice::createServer();
  pServer->setCallbacks(new CustomBLEServerCallbacks());
  // create service
  BLEService *pService = pServer->createService(uuid::SERVICE_UUID);

  // crate Tx characteristic and add descriptor
  pTxCharacteristic = pService->createCharacteristic(uuid::TX_CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_NOTIFY);
  pTxCharacteristic->addDescriptor(new BLE2902());
  
  // crate Rx characteristic and register callback
  BLECharacteristic *pRxCharacteristic = pService->createCharacteristic(uuid::RX_CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR);
  pRxCharacteristic->setCallbacks(new CustomBLECharacteristicCallbacks());

  // start the service and start advertising
  pService->start();
  pServer->getAdvertising()->start();

  // setup done flag
  setupDone = true;
}

void loop() {
  
}

void sendMsg(const String &msg) {
  pTxCharacteristic->setValue(std::string(msg.c_str()));
  pTxCharacteristic->notify();
  delay(50);
}

void parseGuioMsg(const String &msg) {
  if(msg.startsWith("@init")) {
    Serial.println("GUI-O app is requesting INITIALIZATION!");

    // clear screen and set background
    sendMsg("@cls\r\n");
    sendMsg("@guis BGC:#FFFFFF\r\n");
    delay(100);

    // initialize GUI
    sendMsg("|LB UID:title X:50 Y:20 FSZ:4 FFA:\"font8\" TXT:\"Motor<br>control\"\r\n");
    sendMsg("|JOY UID:ctrl X:50 Y:50 W:60 BGC:#00FFFFFF SHE:1 SHC:#806D9DC5 BTH:0.25\r\n");
    sendMsg("|LB UID:dir X:50 Y:75 FGC:#000000 SHE:1 FSZ:5 TXT:\"n/a\"\r\n");
    sendMsg("|LB UID:details X:50 Y:90 FSZ:2 TXT:\"GUI-O motor control<br>demonstration by kl3m3n\"\r\n");
  }
  else if(msg.startsWith("@ctrl")) {
    const auto s_idx = msg.indexOf(' ');
    const auto e_idx = msg.lastIndexOf(' ');
    
    if(s_idx > 0 && e_idx > 0) {
      String xDirStr = msg.substring(s_idx + 1, e_idx); xDirStr.trim();
      // "convert" values between 0.0 to 1.0 / -1.0 to percent 0 to 100 / -100 (two decimal places, round...)
      const int8_t motorSpeed = static_cast<int8_t>(roundf(xDirStr.toFloat() * 100.0));

      Serial.print("Motor direction: ");
      Serial.print(motorSpeed > 0 ? "CW" : "CCW");
      Serial.print(", speed [%]: ");
      Serial.println(abs(motorSpeed));
      
      // determine direction
      if(motorSpeed > 0) {
        digitalWrite(inPin_1, HIGH);
        digitalWrite(inPin_2, LOW);  
        // update label
        sendMsg("@dir TXT:\"CW\"\r\n");
      }
      else if(motorSpeed < 0) {
        digitalWrite(inPin_1, LOW);
        digitalWrite(inPin_2, HIGH);
        // update label
        sendMsg("@dir TXT:\"CCW\"\r\n");
      }
      else
        sendMsg("@dir TXT:\"OFF\"\r\n");      

      // map motor speed to duty cycle for speed control
      ledcWrite(PWM_CHANNEL, map(abs(motorSpeed), 0, 100, 0, 255));
    }    
  }    
}

3. ESTABLISH CONNECTION

Open GUI-O application and navigate to settings menu. Select "Connections -> Bluetooth LE and IoT" and search for devices (enable Bluetooth and Location services, if prompted). Tap on the "MotorControl" device, select Nordic UART service and wait for successful connection (confirm device pairing if prompted).

Close the settings menu and press the Initialize button (see image below) from the GUI-O application home screen.

initialize_button.jpg

4. THE RESULT

Image below shows the result (screen capture) on my Android device after pressing the "Initialize" button. Motor direction and speed is controlled by the virtual joystick position (only in horizontal direction). If the joystick is pushed all the way to the right (or left), the speed of the motor is at its maximum. When in this position, pushing the joystick up or down decreases the speed of the motor.


If you have any questions or run into any problems, please let me know!

Best regards,
kl3m3n

EPISODE 9: GUI-O and HM-10 BLE

The following example shows how to connect GUI-O app with HM-10 Bluetooth Low Energy (BLE) module. Arduino Uno is used as an intermediary between the HM-10 and PC serial terminal.

Software prerequisites:

Components needed:

  • HM-10 BLE module
  • Arduino Uno

The entire tutorial is split into various steps. All necessary information is given in each step.

1. CONNECT THE COMPONENTS

Connect the components as shown in the schematic below. Note that the Arduino Uno transmit pin (pin "D3") uses a voltage divider to reduce 5V to roughly 3.3V.

schematic.png

2. UPLOAD THE SOURCE CODE

The source code is really simple and has inline comments, describing the parts of the code. You can copy the source code from the snippet below, or download it here.

Upload the code to your Arduino Uno board (make sure that the correct board and upload port are selected).

/*
 * GUI-O BLE HM-10 example (using HM-10 and Arduino Uno)
 *
 * Copyright (C) 2022, GUI-O Team
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <SoftwareSerial.h>

// communicating with HM-10 using Arduino Uno 
// via software serial, so set it up (Rx - pin2, Tx - pin3)
SoftwareSerial bt(2, 3);

void setup() {
  // 1. Arduino Uno sends data to HM-10 (via PC teriminal)
  // 2. Arduino Uno receives data from HM-10
  Serial.begin(115200);
  
  // 1. HM-10 sends data Arduino Uno
  // 2. HM-10 receives data from Arduino Uno
  bt.begin(9600);
}

void loop() {
  // listen for HM-10 data
  bt.listen();

  // if HM-10 has data, send it to Arduino Uno to be displayed on PC terminal
  while(bt.available() > 0) {
    Serial.write(bt.read());
  }

  // if Arduino Uno has data (via PC terminal), send it to HM-10
  if(Serial.available()) {
    delay(10);
    bt.write(Serial.read());
  }
}

3. ESTABLISH CONNECTION

  1. Open GUI-O app, navigate to settings and select "Connections -> Bluetooth LE"

  2. Search for BLE devices (enable Bluetooth and Location services, if prompted)

  3. Select HM-10 module (named HMSoft or similar)

  4. Wait for successful connection

  5. Open any serial terminal on PC (e.g., Arduino Serial Monitor)

  6. Close the settings menu and press the Initialize button (see image below) from the GUI-O application home screen and observe the result on serial terminal on PC

initialize_button.jpg

  1. Send any valid command (see example below) via serial terminal on PC and observe the result in GUI-O app

EXAMPLE COMMAND: "|TG UID:toggle X:50 Y:50" (omitting the quotes). Make sure all commands are terminated by carriage return (\r) and line feed (\n). See the GUI-O manual for all supported commands.


If you have any questions or run into any problems, please let me know!

Best regards,
kl3m3n

EPISODE 10: MIGRATING TO HiveMQ BROKER

NOTE: The following tutorial applies to IoT (MQTT) connection mode only.

By default, GUI-O application uses a MQTT test broker that is running on our own server. The broker listens on ports 1883 (unencrypted), 8883 (encrypted with client certificates) and 8884 (encrypted). This is perfect for testing and some smaller use cases, but some IoT scenarios require using a broker platform that gives you finer control over additional settings (e.g., managing credentials for different clients).

There are numerous platforms that offer managed cloud MQTT broker, such as HiveMQ, EMQX, CloudMQTT, etc. This episode focuses on migrating from GUI-O broker to HiveMQ broker using ESP32 board.

Software prerequisites:

Components needed:

The entire tutorial is split into various steps. All necessary information is given in each step.

1. SETUP HIVEMQ

  1. Navigate to https://www.hivemq.com/downloads/ and signup to (free) HiveMQ cloud plan

  2. Login to HiveMQ cloud console and navigate to "Clusters"

  3. Create a new cluster selecting any cloud service provider

  4. Select "MANAGE CLUSTER" (NOTE: The "Cluster URL" is required later)

  5. Select "ACCESS MANAGEMENT" tab and add new credentials (at least one)

All steps are shown in the video below.

2. SETUP GUI-O FOR HIVEMQ

  1. Open GUI-O application, open settings and navigate to "Connections -> IoT -> IoT Settings"

  2. Set the "Server name" using the HiveMQ "Cluster URL" (see 1. SETUP HIVEMQ).

  3. Set the "SSL port number" to 8883

  4. Set the "User name" and "User password" based on the HiveMQ credentials (see 1. SETUP HIVEMQ)

  5. Navigate to "Connections -> IoT" and press "Connect" to test the connection

All steps are shown in the video below.

3. SETUP ESP32 FOR HIVEMQ

  1. Download BasicMQTT_HiveMQ.ino sketch and open it in Arduino IDE

  2. Get the certificate of your HiveMQ broker (see video below)

    • On Linux, open the terminal and enter command:

    openssl s_client -connect <hivemq_url>:8883 -showcerts

    replacing the <hivemq_url> with the "Cluster URL" (see 1. SETUP HIVEMQ)

    • You can alternatively use an online Linux terminal (e.g., Online Linux Terminal) to execute the same command

    • After running the command, copy the last certificate displayed in the terminal and replace the one in the downloaded Arduino sketch (keep same certificate formatting)

  1. Use the downloaded Arduino sketch (i.e., BasicMQTT_HiveMQ.ino) while referring to the video below. Make sure that the server url, user name and password are set according to HIVEMQ setup (see 1. SETUP HIVEMQ)

If you have any questions or run into any problems, please let me know!

Best regards,
kl3m3n

EPISODE 11: Pairing GUI-O with ESP32 (MQTT mode)

NOTE: The following tutorial applies to IoT (MQTT) connection mode only.

This tutorial will show how to easily pair ESP32 with GUI-O application in IoT (MQTT) connection mode. The pairing procedure is automated (guided) and requires only necessary user input (e.g., selecting WiFi router and inputting router password). You can even implement the pairing protocol yourself on your specific device (visit www.gui-o.com and download the developer manual for more information).

To see the ESP8266 pairing example (and other examples), navigate to GUI-O application: Create high-end GUI for any device - EXAMPLES.

Prerequisites:

Add ESP32 board support

  1. Open Arduino IDE

  2. Select "File -> Preferences" and under Additional Boards Manager URLs enter: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json

  3. Select "Tools -> Board -> Boards manager..."

  4. Search and install "esp32" by Espressif Systems

  5. After installation select "Tools -> Board -> ESP32 Arduino -> ESP32 Dev Module"

Include necessary libraries

  1. Select "Sketch -> Include library -> Add .ZIP library..."

  2. Navigate to location, where ESP32_GUI-O_pairing_libs.zip archive was extracted

  3. Select .zip library and confirm (repeat procedure until all .zip libraries are included)

Upload the code

  1. Select "File -> Open"

  2. Navigate to location, where ESP32_GUI-O_pairing_source.zip archive was extracted

  3. Select PairingMQTT.ino and confirm

  4. Make sure that ESP32 board is connected to your PC

  5. Make sure that the correct port is selected

  6. Press upload button to start code upload (IMPORTANT NOTE: on some ESP32 boards, the BOOT button must be pressed when the upload starts)

  7. Reset ESP32 board after code upload

The video tutorial below shows the above steps.

Pair GUI-O application with ESP32 board

The pairing procedure is fully guided within GUI-O application. The important parts are described below.

  1. Open GUI-O application and navigate to settings

  2. Make sure that IoT connection is active (select "Connections -> IoT")

  3. From main settings menu select "Quick pair -> Direct device pairing"

  4. Name your ESP32 device (setting your user name is optional) and confirm

  5. Wait while surrounding WiFi networks are scanned then select your (router) WiFi network and input your password

  6. Put the ESP32 device into pairing mode then search and connect to guio_ device (input password 12345678 when prompted)

  7. IMPORTANT NOTE: Do not switch network if prompted by Android (keep connection to guio_ device)

  8. Press "back button" and wait for pairing procedure to complete successfully

  9. Select your (router) WiFi network and press "back button" to start the connection test

The video tutorial below shows the above steps.


If you have any questions or run into any problems, please let me know!

Best regards,
kl3m3n

EPISODE 12: Adding and connecting new device from GUI-O home screen

This tutorial shows how to add and connect devices directly from GUI-O home screen. GUI-O supports various connection types and various methods to add devices.

This tutorial shows how to connect ESP32 working in Bluetooth LE (Low Energy) mode with Nordic UART Service (NUS).

  1. Download BasicBLE_NUS.ino sketch.

  2. Follow Basic BLE video tutorial below to setup ESP32 (NOTE: stop video after the sketch is uploaded to ESP32 and refer to the video under step 3. below).

Additional Boards Manager URLs: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json

  1. Check the video below to add and connect the Bluetooth LE device.

If you have any questions or run into any problems, please let me know!

Best regards,
kl3m3n

EPISODE 13: Drawing charts

This episode shows the different chart types that are supported by GUI-O application:

  1. Time chart
  2. XY chart
  3. Sweep chart

All charts are highly customizable.

I will be using GUI-O IoT (MQTT) connection, but the example can be easily be ported to other connection types.

Software prerequisites:

Components needed:

The entire tutorial is split into various steps. All necessary information is given in each step.

0. DESIGN THE GUI (optional)

The best way to create a GUI layout is to use GUI-O live designer tool.

Note that the Arduino source code already includes the necessary commands, so this step is not needed, unless you want to make some visual adjustments. If you make adjustments, please include the generated ASCII code in the Arduino source code (see section 2. MODIFY AND UPLOAD THE SOURCE CODE).

First, you need to establish a TCP/IP connection between the designer tool and GUI-O application:

  1. Determine the local IP address of your PC's network interface (WiFi or Ethernet)
  • Under Windows, open the command prompt, enter ipconfig and press Enter
  • Under Linux, open the terminal, enter ifconfig and press Enter
  1. Open GUI-O application and open settings menu. Select "Connections -> Ethernet" and create a new device with IP address (determined from 1.) and any port between 49152 - 65535

  2. Open GUI-O designer and select "TCP/IP connection" tab. Set the IP address and port. Both values must match the device settings created within the GUI-O application. Click "Start server" button.

  3. Within the GUI-O application, tap the created device and wait for successful connection.

  4. In the GUI-O designer, select "File -> Load designer file" and load the ChartDrawing.gdf design file. Make the desired adjustments, if necessary. Copy / replace the GUI-O commands into the Arduino source code (see section 3. MODIFY AND UPLOAD THE SOURCE CODE).

1. CREATE A UNIQUE MQTT CHANNEL

Open GUI-O application and press Add from home screen (top-right corner of the display). Select "IoT (Create device)" and add a new device. After adding the device, note the In and Out token (you can share this tokens e.g., to your e-mail by pressing the "share" button).

Finally, press "Connect" menu entry to establish the connection with the MQTT server.

2. MODIFY AND UPLOAD THE SOURCE CODE

The source code has inline comments, describing the important parts of the code. You can copy the source code from the snippet below, or download it here.

The only thing that needs to be done is to set the ssid and password of your router and the unique In and Out channels that were generated by the GUI-O application (see section 1. CREATE A UNIQUE MQTT CHANNEL).

After setting these values, upload the code to your board (make sure that the correct board and upload port are selected). Reset the board after upload.

/*
 * GUI-O Chart Drawing MQTT example (using ESP32-WROOM-32)
 *
 * Copyright (C) 2023, kl3m3n
 * last updated on 5.3.2023
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <WiFi.h>
#include <PubSubClient.h>

static const char *ssid = "<ssid>"; // router name
static const char *pass = "<pass>"; // router password

static const char *mqttServer = "mqtt.gui-o.com";   // host name
static const char *mqttUser = "gui-o-mqtt-generic"; // user name
static const char *mqttPass = "lqXeZpv5VJyv0XBT";   // password

// IMPORTANT NOTE: if optional user name was specified when adding a new IoT device,
// the user name should also be included when setting the topics (e.g., "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/<user_name>")
static const char *Out = "<Out>"; // GUI-O app publish topic
static const char *In = "<In>";  // GUI-O app subscribe topic

// mqtt client
WiFiClient wiFiClient;
PubSubClient mqttClient(wiFiClient);

// forward declare functions for mqtt messages handling
void mqttCallback(char* topic, byte* message, unsigned int length);
void parseGuioMsg(const String &msg);

// setup chart settings / options
enum class ChartType {
  TimeChart = 0,
  XYChart,
  SweepChart,
  Invalid = -1  
};
ChartType chartType = ChartType::Invalid;
static long int counter = 0;
char buf[100];
unsigned long startTimestampMsec, currentTimestampMsec;
const unsigned long updateIntervalMsec = 150; // milliseconds

void setup() {
  // debug output
  Serial.begin(115200);
  
  // connect WiFi (keep trying...)
  Serial.print("Connecting to ");
  Serial.println(ssid);
  
  WiFi.begin(ssid, pass);
  while(WiFi.status() != WL_CONNECTED) { 
    Serial.print(".");
    delay(500); 
  }
  
  Serial.println("WiFi connected!"); 
  
  // setup mqtt
  mqttClient.setServer(mqttServer, 1883);
  mqttClient.setCallback(mqttCallback);

  startTimestampMsec = millis();
}

void loop() {
  while(!mqttClient.connected()) {
    Serial.println("MQTT connecting...");

    // mqtt client id is the mac address (AABBCCDDEEFF)
    char mqttClientId[15];    
    uint8_t mac[6];
    WiFi.macAddress(mac);

    snprintf(mqttClientId, sizeof(mqttClientId), "%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);

    if(mqttClient.connect(mqttClientId, mqttUser, mqttPass)) {
      Serial.println("MQTT connected!");
      mqttClient.subscribe(&Out[0]);
    }
    else {
      Serial.print("MQTT connection failed (");
      Serial.print(mqttClient.state());
      Serial.println(")! Retrying...");      
      delay(2500);
    }
  }
  mqttClient.loop();

  // update chart?
  currentTimestampMsec = millis();
  if(currentTimestampMsec - startTimestampMsec > updateIntervalMsec) {
    switch(chartType) {
      case ChartType::TimeChart:
      {
        double yValue = 5 * sin(2 * M_PI * counter++ * 0.015);
        snprintf(buf, sizeof(buf), "@chart PLI:\"id0\" PLC:\"#D89D3B\" YP:\"%.1f\"\r\n", yValue);
        mqttClient.publish(&In[0], &buf[0]);
        break;
      }
      case ChartType::XYChart:
      {
        long int xValue = counter;
        double yValue = 5 * sin(2 * M_PI * counter++ * 0.015);
        snprintf(buf, sizeof(buf), "@chart PLI:\"id0\" PLC:\"#05B6EA\" XP:\"%d\" YP:\"%.1f\"\r\n", xValue, yValue);
        mqttClient.publish(&In[0], &buf[0]);
        break;
      }
      case ChartType::SweepChart:
      {
        double yValue = 5 * sin(2 * M_PI * counter++ * 0.015);
        snprintf(buf, sizeof(buf), "@chart PLI:\"id0\" PLC:\"#AE3C51\" YP:\"%.1f\"\r\n", yValue);
        mqttClient.publish(&In[0], &buf[0]);
        break;
      }
      case ChartType::Invalid:
        default:
        break;
    }
    // reset read interval
    startTimestampMsec = millis();
  }
}

// mqtt callback function
void mqttCallback(char* topic, byte* message, unsigned int length) {
  // build message string
  String msg;
  for(int i = 0; i < length; i++) {
    msg += (char) message[i];
  }
  // parse message string
  parseGuioMsg(msg);
}

void parseGuioMsg(const String &msg) {
  if(msg.startsWith("@init")) {
    Serial.println("GUI-O app is requesting INITIALIZATION!");

    // clear screen and set background
    mqttClient.publish(&In[0], "@cls\r\n");
    mqttClient.publish(&In[0], "@guis BGC:#FFFFFF\r\n");
    delay(100);

    // initialize GUI (setup time chart by default)
    mqttClient.publish(&In[0], "|LB UID:title X:50 Y:20 FSZ:4 FFA:\"font8\" TXT:\"Chart drawing\"\r\n");
    mqttClient.publish(&In[0], "|CH UID:chart X:50 Y:50 W:90 H:30 BGC:#4C4C4C FGC:#D89D3B FSZ:1.5 BTH:0 CHN:\"Value vs Time\" AT:0.2 XTC:1 XMA:8 YMA:4 YLO:-10 SHG:0 SHXA:0 SHYA:0\r\n");
    mqttClient.publish(&In[0], "|BT UID:timebt X:20 Y:70 FGC:#D89D3B BTH:0 SHE:1 TXT:\"Time\"\r\n");
    mqttClient.publish(&In[0], "|BT UID:xybt X:50 Y:70 FGC:#D89D3B BTH:0 SHE:1 TXT:\"XY\"\r\n");
    mqttClient.publish(&In[0], "|BT UID:sweepbt X:80 Y:70 FGC:#D89D3B BTH:0 SHE:1 TXT:\"Sweep\"\r\n");
    mqttClient.publish(&In[0], "|LB UID:details X:50 Y:90 FSZ:2 TXT:\"GUI-O charts demonstration by kl3m3n\"\r\n");
  }
  // time chart mode
  else if(msg.startsWith("@timebt")) {
    // clear and initialize chart
    mqttClient.publish(&In[0], "@cls UIDS:\"chart\"");
    mqttClient.publish(&In[0], "|CH UID:chart X:50 Y:50 W:90 H:30 BGC:#4C4C4C FGC:#D89D3B FSZ:1.5 BTH:0 CHN:\"Value vs Time\" AT:0.2 XTC:1 XMA:8 YMA:4 YLO:-10 SHG:0 SHXA:0 SHYA:0\r\n");
    chartType = ChartType::TimeChart;
    counter = 0;
  }
  // xy chart mode
  else if(msg.startsWith("@xybt")) {
    // clear and initialize chart
    mqttClient.publish(&In[0], "@cls UIDS:\"chart\"");
    mqttClient.publish(&In[0], "|CH UID:chart X:50 Y:50 W:90 H:30 BGC:#4C4C4C FGC:#D89D3B FSZ:1.5 BTH:0 CHT:1 CHN:\"X Value vs Y Value\" AT:0.2 YLO:-10 XTC:10 YTC:10 XMA:8 YMA:5\r\n");
    chartType = ChartType::XYChart;
    counter = 0;
  }
  // sweep chart
  else if(msg.startsWith("@sweepbt")) {
    // clear and initialize chart
    mqttClient.publish(&In[0], "@cls UIDS:\"chart\"");
    mqttClient.publish(&In[0], "|CH UID:chart X:50 Y:50 W:90 H:30 BGC:#4C4C4C FGC:#D89D3B FSZ:1.5 BTH:0 CHT:2 BSZ:256 CHN:\"Sweep\" AT:0.2 YLO:-10 XTC:4 YTC:4 XMA:8 YMA:4 XHI:100 DRA:0 SHG:0 SHXA:0 SHYA:0 SHSL:1 SHVL:0\r\n");
    chartType = ChartType::SweepChart;
    counter = 0;
  }
}

3. ESTABLISH CONNECTION

Make sure that the GUI-O application is connected to the MQTT server.
Also make sure that the ESP32 board (or other Arduino supported board) is connected to the MQTT server (you can check this by observing the serial debug messages using the Arduino serial monitor).

Press the Initialize button (see image below) from the GUI-O application home screen.

initialize_button.jpg

4. THE RESULT

Images (and video) below show the result (screen capture) on my Android device after pressing the "Initialize" button and selecting Time, XY and Sweep, respectively. The active chart is updated every time the ESP32 board published new data to the MQTT server.




If you have any questions or run into any problems, please let me know!

Best regards,
kl3m3n

EPISODE 14: Saving data to Android device

This episode shows how various data (sensor readings, user input, etc.) can be stored to the Android device. Furthermore, the stored data can then be read, opened or shared.


NOTE: This example requires PRO version of GUI-O application. Don't worry, you can still do lots of other great things using free DEMO version of GUI-O :slight_smile: !


I will be using GUI-O IoT (MQTT) connection, but the example can be easily be ported to other connection types (Bluetooth, BLE, USB...).

Software prerequisites:

Components needed:

The entire tutorial is split into various steps. All necessary information is given in each step.

0. DESIGN THE GUI (optional)

The best way to create a GUI layout is to use GUI-O live designer tool.

Note that the Arduino source code already includes the necessary commands, so this step is not needed, unless you want to make some visual adjustments. If you make adjustments, please include the generated ASCII code in the Arduino source code (see section 2. MODIFY AND UPLOAD THE SOURCE CODE).

First, you need to establish a TCP/IP connection between the designer tool and GUI-O application:

  1. Determine the local IP address of your PC's network interface (WiFi or Ethernet)
  • Under Windows, open the command prompt, enter ipconfig and press Enter
  • Under Linux, open the terminal, enter ifconfig and press Enter
  1. Open GUI-O application and open settings menu. Select "Connections -> Ethernet" and create a new device with IP address (determined from 1.) and any port between 49152 - 65535

  2. Open GUI-O designer and select "TCP/IP connection" tab. Set the IP address and port. Both values must match the device settings created within the GUI-O application. Click "Start server" button.

  3. Within the GUI-O application, tap the created device and wait for successful connection.

  4. In the GUI-O designer, select "File -> Load designer file" and load the SavingData.gdf design file. Make the desired adjustments, if necessary. Copy / replace the GUI-O commands into the Arduino source code (see section 3. MODIFY AND UPLOAD THE SOURCE CODE).

1. CREATE A UNIQUE MQTT CHANNEL

Open GUI-O application and press Add from home screen (top-right corner of the display). Select "IoT (Create device)" and add a new device. After adding the device, note the In and Out token (you can share this tokens e.g., to your e-mail by pressing the "share" button).

Finally, press "Connect" menu entry to establish the connection with the MQTT server.

2. MODIFY AND UPLOAD THE SOURCE CODE

The source code has inline comments, describing the important parts of the code. You can copy the source code from the snippet below, or download it here.

The only thing that needs to be done is to set the ssid and password of your router and the unique In and Out channels that were generated by the GUI-O application (see section 1. CREATE A UNIQUE MQTT CHANNEL).

After setting these values, upload the code to your board (make sure that the correct board and upload port are selected). Reset the board after upload.

/*
 * GUI-O Saving Data MQTT example (using ESP32-WROOM-32)
 *
 * Copyright (C) 2023, kl3m3n
 * last updated on 11.3.2023
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <WiFi.h>
#include <PubSubClient.h>
#include <string>

#define DEBUG(title, payload) \
  Serial.print(title); \
  Serial.println(payload); \

static const char *ssid = "<ssid>"; // router name
static const char *pass = "<pass>"; // router password

static const char *mqttServer = "mqtt.gui-o.com";   // host name
static const char *mqttUser = "gui-o-mqtt-generic"; // user name
static const char *mqttPass = "lqXeZpv5VJyv0XBT";   // password

// IMPORTANT NOTE: if optional user name was specified when adding a new IoT device,
// the user name should also be included when setting the topics (e.g., "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/<user_name>")
static const char *Out = "<Out>"; // GUI-O app publish topic
static const char *In = "<In>";  // GUI-O app subscribe topic

// mqtt client
WiFiClient wiFiClient;
PubSubClient mqttClient(wiFiClient);

// forward declare functions for mqtt messages handling
void mqttCallback(char* topic, byte* message, unsigned int length);
void parseGuioMsg(const String &msg);

std::string tiData{""}, niData{"1"};
char buf[100];

void setup() {
  // debug output
  Serial.begin(115200);
  
  // connect WiFi (keep trying...)
  Serial.print("Connecting to ");
  Serial.println(ssid);
  
  WiFi.begin(ssid, pass);
  while(WiFi.status() != WL_CONNECTED) { 
    Serial.print(".");
    delay(500); 
  }
  
  Serial.println("WiFi connected!"); 
  
  // setup mqtt
  mqttClient.setServer(mqttServer, 1883);
  mqttClient.setCallback(mqttCallback);
}

void loop() {
  while(!mqttClient.connected()) {
    Serial.println("MQTT connecting...");

    // mqtt client id is the mac address (AABBCCDDEEFF)
    char mqttClientId[15];    
    uint8_t mac[6];
    WiFi.macAddress(mac);

    snprintf(mqttClientId, sizeof(mqttClientId), "%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);

    if(mqttClient.connect(mqttClientId, mqttUser, mqttPass)) {
      Serial.println("MQTT connected!");
      mqttClient.subscribe(&Out[0]);
    }
    else {
      Serial.print("MQTT connection failed (");
      Serial.print(mqttClient.state());
      Serial.println(")! Retrying...");      
      delay(2500);
    }
  }
  mqttClient.loop();
}

// mqtt callback function
void mqttCallback(char* topic, byte* message, unsigned int length) {
  // build message string
  String msg;
  for(int i = 0; i < length; i++) {
    msg += (char) message[i];
  }
  // parse message string
  parseGuioMsg(msg);
}

void parseGuioMsg(const String &msg) {
  DEBUG("parseGuioMsg: ", msg);
  
  if(msg.startsWith("@init")) {
    Serial.println("GUI-O app is requesting INITIALIZATION!");

    // clear screen and set background
    mqttClient.publish(&In[0], "@cls\r\n");
    mqttClient.publish(&In[0], "@guis BGC:#FFFFFF\r\n");
    delay(100);

    // initialize GUI
    mqttClient.publish(&In[0], "|LB UID:title X:50 Y:20 FSZ:4 FFA:\"font8\" TXT:\"Saving data\"\r\n");
    mqttClient.publish(&In[0], "|BT UID:bt_save X:72.5 Y:30 BGC:#FDFDFD SBGC:#DCDFDF LOR:1 SHE:1 FSZ:2 TXT:\"Save\" BTH:0\r\n");
    mqttClient.publish(&In[0], "|TI UID:ti_data X:30 Y:30 BGC:#004C4C4C FSZ:2\r\n");
    mqttClient.publish(&In[0], "|BT UID:bt_all X:72.5 Y:38 BGC:#FDFDFD SBGC:#DCDFDF LOR:1 SHE:1 FSZ:2 TXT:\"Read all\" BTH:0\r\n");
    mqttClient.publish(&In[0], "|BT UID:bt_line X:72.5 Y:46 BGC:#FDFDFD SBGC:#DCDFDF LOR:1 SHE:1 FSZ:2 TXT:\"Read line\" BTH:0\r\n");
    mqttClient.publish(&In[0], "|NI UID:ni_data X:30 Y:46 W:30 BGC:#004C4C4C VAL:1 FSZ:2.5\r\n");
    mqttClient.publish(&In[0], "|BT UID:clear_display X:92 Y:70 W:8 H:30 BGC:#FDFDFD SBGC:#DCDFDF LOR:1 SHE:1 FSZ:2 TXT:\"C<br>L<br>E<br>A<br>R\" BTH:0\r\n");
    mqttClient.publish(&In[0], "|TA UID:display X:50 Y:70 W:70 BGC:#DCDFDF FSZ:2\r\n");
    mqttClient.publish(&In[0], "|LB UID:details X:50 Y:90 FSZ:2 TXT:\"GUI-O data saving<br>demonstration by kl3m3n\"\r\n");
    // initialize "file saver"
    mqttClient.publish(&In[0], "|EXTF UID:file HID:extf FNA:\"test.txt\" FAC:0\r\n");
  }
  else if(msg.startsWith("@bt_save")) {
    snprintf(buf, sizeof(buf), "@file FAC:1 FP:\"%s\"\r\n", tiData.c_str());
    mqttClient.publish(&In[0], &buf[0]);
    DEBUG("bt_save: ", tiData.c_str());
  }
  else if(msg.startsWith("@ti_data")) {
    auto input = std::string(msg.c_str());
    
    auto s_idx = input.find(' ');
    auto e_idx = input.find("dev:");  // NOTE: "dev" token exists only for IoT
    
    if(s_idx != std::string::npos
        && e_idx != std::string::npos
        && s_idx < e_idx) {
          tiData = input.substr(s_idx + 1, e_idx - s_idx - 1);
          DEBUG("ti_data: ", tiData.c_str());
    }
  }
  else if(msg.startsWith("@bt_all")) {
    snprintf(buf, sizeof(buf), "@file FAC:2 FP:\"-1\"\r\n");
    mqttClient.publish(&In[0], &buf[0]);
    DEBUG("bt_all:", "");
  }
  else if(msg.startsWith("@bt_line")) {
    snprintf(buf, sizeof(buf), "@file FAC:2 FP:\"%s\"\r\n", niData.c_str());
    mqttClient.publish(&In[0], &buf[0]);
    DEBUG("bt_line: ", niData.c_str());
  }
  else if(msg.startsWith("@ni_data")) {
    auto input = std::string(msg.c_str());
    
    auto s_idx = input.find(' ');
    auto e_idx = input.find("dev:"); // NOTE: "dev" token exists only for IoT
    
    if(s_idx != std::string::npos
        && e_idx != std::string::npos
        && s_idx < e_idx) {
          niData = input.substr(s_idx + 1, e_idx - s_idx - 1);
          DEBUG("ni_data: ", niData.c_str());
    }
  }
  else if(msg.startsWith("@file")) {
    auto input = std::string(msg.c_str());
    
    auto s_idx = input.find(' ');
    auto e_idx = input.find("dev:"); // NOTE: "dev" token exists only for IoT
    
    if(s_idx != std::string::npos
        && e_idx != std::string::npos
        && s_idx < e_idx) {
          auto line = input.substr(s_idx + 1, e_idx - s_idx - 1);
          snprintf(buf, sizeof(buf), "@display TXT:\"%s\"\r\n", line.c_str());
          mqttClient.publish(&In[0], &buf[0]);
          DEBUG("@file: ", line.c_str());
    }    
  }
  else if(msg.startsWith("@clear_display")) {
    mqttClient.publish(&In[0], "@display CL:1\r\n");
    DEBUG("@clear_display: ", "");
  }
}

3. ESTABLISH CONNECTION

Make sure that the GUI-O application is connected to the MQTT server.
Also make sure that the ESP32 board (or other Arduino supported board) is connected to the MQTT server (you can check this by observing the serial debug messages using the Arduino serial monitor).

Press the Initialize button (see image below) from the GUI-O application home screen.

initialize_button.jpg

4. THE RESULT

Images (and video) below show the result (screen capture) on my Android device after pressing the "Initialize" button, saving some data and reading it back. All actions are triggered through GUI interaction.


If you have any questions or run into any problems, please let me know!

Best regards,
kl3m3n

EPISODE 15: Switching screens

This example shows how to use multiple GUI-O screens and switch between them.

GUI-O supports up to five (5) screens, where widgets can be placed.

Switching between screens can be performed in two ways: using the 1. built-in flick functionality or by creating a 2. flick area widget (FLA) and reacting to user input on the ESP32 side.
Here, I will only cover only the first option. For the second option, see the GUI-O developer manual (hint: see section Multiple screen support).

I will be using GUI-O Bluetooth Low Energy (LE) connection, but the example can be easily be ported to other connection types.

Software prerequisites:

Components needed:

  • ESP32-WROOM-32 (or any other Arduino supported Bluetooth Low Energy capable board)

The entire tutorial is split into various steps. All necessary information is given in each step.

0. DESIGN THE GUI (optional)

The best way to create a GUI layout is to use GUI-O live designer tool.

Note that the Arduino source code already includes the necessary commands, so this step is not needed, unless you want to make some visual adjustments. If you make adjustments, please include the generated ASCII code in the Arduino source code (see section 1. UPLOAD THE SOURCE CODE).

First, you need to establish a TCP/IP connection between the designer tool and GUI-O application:

  1. Determine the local IP address of your PC's network interface (WiFi or Ethernet)
  • Under Windows, open the command prompt, enter ipconfig and press Enter
  • Under Linux, open the terminal, enter ifconfig and press Enter
  1. Open GUI-O application and open settings menu. Select "Connections -> Ethernet" and create a new device with IP address (determined from 1.) and any port between 49152 - 65535

  2. Open GUI-O designer and select "TCP/IP connection" tab. Set the IP address and port. Both values must match the device settings created within the GUI-O application. Click "Start server" button.

  3. Within the GUI-O application, tap the created device and wait for successful connection.

  4. In the GUI-O designer, select "File -> Load designer file" and load the SwitchScreen.gdf design file. Make the desired adjustments, if necessary. Copy / replace the GUI-O commands into the Arduino source code (see section 1. UPLOAD THE SOURCE CODE).

1. UPLOAD THE SOURCE CODE

The source code has inline comments, describing the important parts of the code. You can copy the source code from the snippet below, or download it here.

Upload the code to your board (make sure that the correct board and upload port are selected). Reset the board after upload.

/*
 * GUI-O Switch screen Bluetooth example (using ESP32-WROOM-32)
 *
 * Copyright (C) 2023, kl3m3n
 * last updated on 01.04.2023
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

namespace uuid {
  static const char *SERVICE_UUID = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E";
  static const char *RX_CHARACTERISTIC_UUID = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E";
  static const char *TX_CHARACTERISTIC_UUID = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E";
} // namespace uuid

// forward declare parser for incoming messages
void parseGuioMsg(const String &msg);

// setup done flag
bool setupDone = false;

// custom handling of server callbacks
class CustomBLEServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
      Serial.println("Connected!");
    };
    void onDisconnect(BLEServer* pServer) {
      Serial.println("Disconnected!");
      
      // restart advertising after disconnect, otherwise GUI-O cannot re-connect
      if(setupDone) {
        // restart advertising on disconnect
        delay(500);
        pServer->startAdvertising(); 
      }
    }
};

// custom handling of characteristic callbacks
class CustomBLECharacteristicCallbacks: public BLECharacteristicCallbacks {
  void onWrite(BLECharacteristic *pCharacteristic) {
    std::string msg = pCharacteristic->getValue();
    
    // parse message string
    parseGuioMsg(String(msg.c_str()));
  }      
};

// global ptr
BLECharacteristic *pTxCharacteristic;

void setup() {
  // debug output
  Serial.begin(115200);

  // create device
  BLEDevice::init("SwitchScreen");
  // create server and register callback
  BLEServer *pServer = BLEDevice::createServer();
  pServer->setCallbacks(new CustomBLEServerCallbacks());
  // create service
  BLEService *pService = pServer->createService(uuid::SERVICE_UUID);

  // crate Tx characteristic and add descriptor
  pTxCharacteristic = pService->createCharacteristic(uuid::TX_CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_NOTIFY);
  pTxCharacteristic->addDescriptor(new BLE2902());
  
  // crate Rx characteristic and register callback
  BLECharacteristic *pRxCharacteristic = pService->createCharacteristic(uuid::RX_CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR);
  pRxCharacteristic->setCallbacks(new CustomBLECharacteristicCallbacks());

  // start the service and start advertising
  pService->start();
  pServer->getAdvertising()->start();

  // setup done flag
  setupDone = true;
}

void loop() {
  
}

void sendMsg(const String &msg) {
  pTxCharacteristic->setValue(std::string(msg.c_str()));
  pTxCharacteristic->notify();
  delay(50);
}

void parseGuioMsg(const String &msg) {
  if(msg.startsWith("@init")) {
    Serial.println("GUI-O app is requesting INITIALIZATION!");

    // clear screen and set background
    sendMsg("@cls\r\n");
    sendMsg("@guis BGC:#FFFFFF PGF:1\r\n");
    delay(100);

    // initialize GUI
    sendMsg("|LB UID:title X:50 Y:20 FSZ:4 FFA:\"font8\" TXT:\"Switch<br>screen\"\r\n");
    sendMsg("|LB UID:home X:50 Y:50 FGC:#607196 SHE:1 SHC:#FFC759 FSZ:5 FFA:\"font1\" TXT:\"HOME SCREEN\"\r\n");
    sendMsg("|LB UID:screen1 X:50 Y:50 FGC:#607196 SHE:1 SHC:#FFC759 FSZ:5 FFA:\"font1\" TXT:\"SCREEN 1\" SCI:1\r\n");
    sendMsg("|LB UID:screen2 X:50 Y:50 FGC:#607196 SHE:1 SHC:#FFC759 FSZ:5 FFA:\"font1\" TXT:\"SCREEN 2\" SCI:2\r\n");
    sendMsg("|LB UID:screen3 X:50 Y:50 FGC:#607196 SHE:1 SHC:#FFC759 FSZ:5 FFA:\"font1\" TXT:\"SCREEN 3 ...\" SCI:3\r\n");
    sendMsg("|LB UID:details X:50 Y:90 FSZ:2 TXT:\"GUI-O switch screen<br>demonstration by kl3m3n\"\r\n");    
  }
}

2. ESTABLISH CONNECTION

Open GUI-O application and press Add in the upper-right corner of the home screen. Tap on Bluetooth LE and search for devices (enable Bluetooth and Location services, if prompted). Tap on the "SwitchScreen" device, select Nordic UART service and wait for successful connection (confirm device pairing if prompted).

Close the settings menu and press the Initialize button (see image below) from the GUI-O application home screen.

initialize_button.jpg

3. THE RESULT

Image below shows the result (screen capture) on my Android device after pressing the "Initialize" button. The bottom screen indicator indicates the number of screens and the currently active screen. Navigation between screens is possible by flicking left or right at the bottom of the screen. This is shown in the video below.


If you have any questions or run into any problems, please let me know!

Best regards,
kl3m3n

EPISODE 16: Initializing GUI from file

This episode shows how to use GUI-O design tool to create GUI initialization commands and transfer them to Android device (where they are stored in a file). GUI-O application can then read and parse the initialization file upon external request from MCU, PC, etc.


NOTE: This example requires PRO version of GUI-O application. Don't worry, you can still do lots of other great things using free DEMO version of GUI-O!


Software prerequisites:

1. CONFIGURE GUI-O DESIGN TOOL

In this example, I will be using TCP/IP connection, but the design tool also supports USB connection. In case of TCP/IP connection, make sure you are connected to the same local network.

  1. Determine the local IP address of your PC's network interface (WiFi or Ethernet):
  • Under Windows, open the command prompt, enter ipconfig and press Enter
  • Under Linux, open the terminal, enter ifconfig and press Enter
  1. Open GUI-O designer and select "TCP/IP connection" tab. Set the IP address and any port number between 49152 - 65535. Click "Start server" button.

  2. Open GUI-O application and tap "Add" located at the upper-right part of the screen. Tap "Etherned" and add a new device using the same IP address and port. Tap on the created device and wait for successful connection with GUI-O design tool.

2. CREATE WIDGETS

Create various widgets and observe (and interact with) them in real-time on your Android device. Modify the properties of the widgets based on your preferences.

3. TRANSFER INITIALIZATION COMMANDS

Send the initialization commands for the created widgets to your Android device, where they are stored in a file that can be parsed "on-demand" by the GUI-O application.

  1. Select "Transfer to device" tab.

  2. Specify the file name and press "Enter" or click on "Start transfer" button. Wait for the transfer to complete.

After the transfer is successfully completed, the initialization commands are located in a file on your Android device. You can test this by navigating to "Send command" tab.

First, clear the GUI by sending @cls command. Then send the command @fileinit FNA:"", where parameter denotes the full name of the file during commands transfer phase.

4. VIDEO TUTORIAL

The video tutorial for all steps is shown below.


If you have any questions or run into any problems, please let me know!

Best regards,
kl3m3n

EPISODE 17: URL image stream

This example shows how to obtain IP camera snapshots (via url request) and update image widget to produce continuous image stream.

NOTE: GUI-O application v1.0.47 fixes image "flickering" bug.

I will be using GUI-O Bluetooth Low Energy (LE) connection, but the example can be easily ported to other connection types.

Software prerequisites:

Components needed:

  • ESP32-WROOM-32 (or any other Arduino supported Bluetooth Low Energy capable board)

The entire tutorial is split into various steps. All necessary information is given in each step.

0. DESIGN THE GUI (optional)

The best way to create a GUI layout is to use GUI-O live designer tool. Please check previous episodes to see how to use the designer tool.

1. UPLOAD THE SOURCE CODE

The source code has inline comments, describing the important parts of the code. You can copy the source code from the snippet below, or download it here.

Upload the code to your board (make sure that the correct board and upload port are selected). Reset the board after upload.

/*
 * GUI-O Image Stream Bluetooth example (using ESP32-WROOM-32)
 *
 * Copyright (C) 2023, kl3m3n
 * last updated on 28.05.2023
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

namespace uuid {
  static const char *SERVICE_UUID = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E";
  static const char *RX_CHARACTERISTIC_UUID = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E";
  static const char *TX_CHARACTERISTIC_UUID = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E";
} // namespace uuid

// forward declare parser for incoming messages
void parseGuioMsg(const String &msg);

// setup done flag
bool setupDone = false;

// custom handling of server callbacks
class CustomBLEServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
      Serial.println("Connected!");
    };
    void onDisconnect(BLEServer* pServer) {
      Serial.println("Disconnected!");
      
      // restart advertising after disconnect, otherwise GUI-O cannot re-connect
      if(setupDone) {
        // restart advertising on disconnect
        delay(500);
        pServer->startAdvertising(); 
      }
    }
};

// custom handling of characteristic callbacks
class CustomBLECharacteristicCallbacks: public BLECharacteristicCallbacks {
  void onWrite(BLECharacteristic *pCharacteristic) {
    std::string msg = pCharacteristic->getValue();
    
    // parse message string
    parseGuioMsg(String(msg.c_str()));
  }      
};

// global ptr
BLECharacteristic *pTxCharacteristic;

bool stream;
unsigned long startTimestampMsec, currentTimestampMsec;
const unsigned long updateIntervalMsec = 1000; // milliseconds

void setup() {
  // debug output
  Serial.begin(115200);

  // create device
  BLEDevice::init("ImageStream");
  // create server and register callback
  BLEServer *pServer = BLEDevice::createServer();
  pServer->setCallbacks(new CustomBLEServerCallbacks());
  // create service
  BLEService *pService = pServer->createService(uuid::SERVICE_UUID);

  // crate Tx characteristic and add descriptor
  pTxCharacteristic = pService->createCharacteristic(uuid::TX_CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_NOTIFY);
  pTxCharacteristic->addDescriptor(new BLE2902());
  
  // crate Rx characteristic and register callback
  BLECharacteristic *pRxCharacteristic = pService->createCharacteristic(uuid::RX_CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR);
  pRxCharacteristic->setCallbacks(new CustomBLECharacteristicCallbacks());

  // start the service and start advertising
  pService->start();
  pServer->getAdvertising()->start();

  // setup done flag
  setupDone = true;
}

void loop() {
  currentTimestampMsec = millis();
  if(currentTimestampMsec - startTimestampMsec > updateIntervalMsec) {
    if(stream)
      sendMsg("@im IP:\"https://kamere.dars.si/kamere/Kozina/Dilce_2.jpg\"\r\n");
    // reset read interval
    startTimestampMsec = millis();
  }  
}

void sendMsg(const String &msg) {
  pTxCharacteristic->setValue(std::string(msg.c_str()));
  pTxCharacteristic->notify();
  delay(50);
}

void parseGuioMsg(const String &msg) {
  if(msg.startsWith("@init")) {
    Serial.println("GUI-O app is requesting INITIALIZATION!");

    // clear screen and set background
    sendMsg("@cls\r\n");
    sendMsg("@guis BGC:#FFFFFF\r\n");
    delay(100);

    // initialize GUI
    sendMsg("|LB UID:title X:50 Y:20 FSZ:4 FFA:\"font8\" TXT:\"Image<br>stream\"\r\n");
    sendMsg("|IM UID:im X:50 Y:50 W:80 FURL:1 IP:\"https://kamere.dars.si/kamere/Kozina/Dilce_2.jpg\"\r\n");
    sendMsg("|TG UID:tg X:50 Y:75 EN:1\r\n");
    sendMsg("|LB UID:details X:50 Y:90 FSZ:2 TXT:\"GUI-O image stream<br>demonstration by kl3m3n\"\r\n");

    // defaults to true (tg has property EN set to true)
    stream = true;
  }
  else if(msg.startsWith("@tg"))
    stream = (msg.indexOf("1") > 0);
}

2. ESTABLISH CONNECTION

Open GUI-O application and press Add in the upper-right corner of the home screen. Tap on Bluetooth LE and search for devices (enable Bluetooth and Location services, if prompted). Tap on the "ImageStream" device and wait for successful connection (confirm device pairing if prompted).

Close the settings menu and press the Initialize button (see image below) from the GUI-O application home screen.

initialize_button.jpg

3. THE RESULT

Image below shows the result (screen capture) on my Android device after pressing the "Initialize" button. The image stream is enabled by default and can be disabled by tapping on the toggle widget.


If you have any questions or run into any problems, please let me know!

Best regards,
kl3m3n

EPISODE 18: Setup custom MQTT broker for Linux

The following tutorial shows how to setup Mosquitto broker on Linux based devices.

Prerequisites:

  • OpenSSL software library (should be preinstalled by default on all Linux distributions)

  • Access to your router (via IP address using the administrator username and password)

  • External (static) IP provided by your internet service provider (ISP)

  • GUI-O application version 1.0.47 or higher

IMPORTANT NOTE #1: It is necessary to ask your ISP for static IP configuration. The certificate in the following steps will be issued for a specific IP. If you do not use static IP configuration, your ISP can change the IP address without notice and the connection to the MQTT broker will not work.

IMPORTANT NOTE #2: It is generally recommended that a host name is used instead of IP address, but this requires registering the host name with a domain name registrar and setting up some additional settings.

Step 1: Install Mosquitto broker

  1. Open the terminal and install Mosquitto broker:
sudo apt install mosquitto

Step 2: Determine your external IP address

  1. Enter the following command:
curl ifconfig.me; echo

This will output your external IP, which is needed when issuing the server certificate. You can alternatively navigate to: https://whatismyipaddress.com/.

Step 3: Create self-signed certificate for certificate authority (CA)

  1. Create a private key and CA certificate:
openssl req -new -x509 -days 365 -extensions v3_ca -keyout ca.key -out ca.crt

IMPORTANT NOTE: The "ca.key" file should be kept secure and not shared with anyone.

The "days" value is in this case set to 365 (1 year) and denotes the certificate validity. This value can be changed based on your requirements. After the command is executed, you will be prompted to enter a pass phrase and additional certificate information such as country code, state name, city, etc.

  1. (Optionally) check the certificate info:
openssl x509 -noout -text -in ca.crt

Step 4: Create a server certificate and sign it with CA

  1. Create "server.cnf" file (you can alternatively download the file here) :
nano server.cnf

Add the following content and replace the "[dn]" and "[alt_names]" sections with your information (use the external IP address obtained in Step 2):

[req]

default_bits = 2048

prompt = no

default_md = sha256

distinguished_name = dn

req_extensions = req_ext


[dn]

C = COUNTRY_CODE_HERE

ST = STATE_HERE

L = CITY_HERE

O = ORGANIZATION_HERE

OU = ORGANIZATION_UNIT_HERE

CN = IP_ADDRESS_HERE


[req_ext]

subjectAltName = @alt_names


[alt_names]

IP.1 = IP_ADDRESS_HERE

NOTE: To save and exit "nano" text editor, press "Ctrl+x", then "y" and "Enter" key.

  1. Create a private key:
openssl genrsa -out server.key 2048
  1. Create certificate signing request:
openssl req -new -key server.key -out server.csr -config server.cnf
  1. Create self-signed certificate using the signing request
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 365 -sha256

The "days" value is in this case set to 365 (1 year) and denotes the certificate validity. This value can be changed based on your requirements. After the command is executed, you will be prompted to enter the pass phrase for "ca.key" (this is the pass phrase that was set in Step 3).

  1. Change the permissions for "server.key" file:
sudo chmod a+r server.key

Step 5: Copy the certificate to your Android device

  1. Copy the "ca.crt" to your Android device (e.g., Documents folder), where GUI-O application is installed. You can send the certificate via email, transfer it via USB cable, etc.

Step 6: Move the files to proper locations

  1. Move the "ca.key" and "ca.crt" file to "/etc/mosquitto/ca_certificates" folder:
sudo mv ca.crt ca.key /etc/mosquitto/ca_certificates/
  1. Move the "server.key" and "server.crt" file to "/etc/mosquitto/certs" folder:
sudo mv server.crt server.key /etc/mosquitto/certs/

Step 7: Setup broker configuration

  1. Create "default.conf" file in "/etc/mosquitto/conf.d" (you can alternatively download the file here) :
sudo nano /etc/mosquitto/conf.d/default.conf

Add the following content:

# Listener

listener 8883

cafile /etc/mosquitto/ca_certificates/ca.crt

certfile /etc/mosquitto/certs/server.crt

keyfile /etc/mosquitto/certs/server.key

require_certificate false

use_identity_as_username false


# TLS

tls_version tlsv1.2


# Security

allow_anonymous false

password_file /etc/mosquitto/passwd


# Logging

#log_type error

#log_type warning

log_type all

NOTE: To save and exit "nano" text editor, press "Ctrl+x", then "y" and "Enter" key.

  1. Create user name and password for authentication when connecting to the broker (replace the "USER" and "PASS" with your user name and password - both will be required by the GUI-O application when connecting to the broker):
sudo mosquitto_passwd -b /etc/mosquitto/passwd USER PASS

NOTE: You can add more users with different credentials using this command.

  1. Restart the Mosquitto service:
sudo systemctl restart mosquitto.service
  1. (Optionally) check the status of the Mosquitto service:
sudo systemctl status mosquitto.service

Step 8: Configure port forwarding rules for your router

  1. Determine the MAC (hardware) address of your device, where the Mosquitto broker is running:
ifconfig

This command will output the MAC address after the "ether" keyword (formatted as xx:xx:xx:xx:xx:xx).

  1. Open your browser and enter the router IP address into the address bar (the router IP is usually printed on the back of the router). Enter router user name and password when prompted.

  2. Set a local static IP based on the MAC address of the device. Note that the procedure for setting a local static IP varies depending on the make and model of your router.

  3. Setup port forwarding by using the local static IP and setting the internal port range from 8883 to 8883 (this is the port that the Mosquitto service is listening on). Set the external port range to any valid value based on your preferences (e.g., from 43519 to 43519). Use TCP protocol and save the settings, making sure that the newly added port forwarding rule is enabled.

  4. Reboot the router.

  5. Restart the device, where the Mosquitto broker is running. After the restart, the device should have obtained the local static IP (you can check this by running the "ifconfig" command).

Step 9: Setup GUI-O application and connect to Mosquitto broker

  1. Open GUI-O application on your Android device and navigate to "Settings -> Connections IoT -> IoT Settings".

  2. Tap on "Server name" and set the value to your external IP, which was determined in Step 2.

  3. Tap on "SSL port number" and set the value to your external port number, which was set in Step 8 (e.g., 43519).

  4. Tap on "User name" and set the user name for authentication, which was created in Step 7.

  5. Tap on "User password" and set the password for authentication, which was created in Step 7.

  6. Tap on "Import certificate" and select the "ca.crt", which was transferred to the device in Step 5.

  7. Return to the previous menu and tap "Connect". If everything was setup correctly, the connection to the Mosquitto broker should be established successfully.

(Optional) Step 10: Setup ESP32 and connect to Mosquitto broker

  1. Download BasicMQTT_Mosquitto.ino sketch and open it in Arduino IDE.

  2. Get the "ca.crt" data in plain text format. Open the terminal on the device where the Mosquitto broker is running and run the following command:

cat /etc/mosquitto/ca_certificates/ca.crt

Copy the certificate displayed in the terminal and replace the one in BasicMQTT_Mosquitto.ino source code (keep same certificate formatting).

  1. Finally, refer to ESP32 MQTT video example, while using the BasicMQTT_Mosquitto.ino sketch. Make sure that the external IP (mqttIP), external port number (mqttPort), user name (mqttUser) and password (mqttPass) variables are set according to Step 9.

NOTE: Do not forget to generate and set the publish and subscribe topics!


If you have any questions or run into any problems, please let me know!

Best regards,
kl3m3n

EPISODE 19: Setup custom MQTT broker for Windows

The following tutorial shows how to setup Mosquitto broker on Windows based devices.

Prerequisites

  • OpenSSL software library:

Download light version here and install. Install OpenSSL directly to the main partition (e.g., "C:\OpenSSL-Win64"). When prompted copy DLLs to OpenSSL binaries directory.

  • Access to your router (via IP address using the administrator username and password)

  • External (static) IP provided by your internet service provider (ISP)

  • GUI-O application version 1.0.47 or higher

IMPORTANT NOTE #1: It is necessary to ask your ISP for static IP configuration. The certificate in the following steps will be issued for a specific IP. If you do not use static IP configuration, your ISP can change the IP address without notice and the connection to the MQTT broker will not work.

IMPORTANT NOTE #2: It is generally recommended that a host name is used instead of IP address, but this requires registering the host name with a domain name registrar and setting up some additional settings.

Step 1: Download and install Mosquitto broker

  1. Download Mosquitto broker here and perform full installation directly to the main partition (e.g., "C:\mosquitto")

Step 2: Determine your external IP address

  1. Open the command prompt (cmd) and enter the following command:
curl ifconfig.me

This will output your external IP, which is needed when issuing the server certificate. You can alternatively navigate to: https://whatismyipaddress.com/.

Step 3: Create self-signed certificate for certificate authority (CA)

  1. Navigate to Mosquitto folder:
cd C:\mosquitto
  1. Create a new folder:
mkdir certs
  1. Navigate to newly created folder
cd certs
  1. Create a private key and CA certificate:
C:\OpenSSL-Win64\bin\openssl req -new -x509 -days 365 -extensions v3_ca -keyout ca.key -out ca.crt

IMPORTANT NOTE: The "ca.key" file should be kept secure and not shared with anyone.

The "days" value is in this case set to 365 (1 year) and denotes the certificate validity. This value can be changed based on your requirements. After the command is executed, you will be prompted to enter a pass phrase and additional certificate information such as country code, state name, city, etc.

  1. (Optionally) check the certificate info:
C:\OpenSSL-Win64\bin\openssl x509 -noout -text -in ca.crt

Step 4: Create a server certificate and sign it with CA

  1. Create "server.cnf" file (you can alternatively download the file here) :
cd. > server.cnf

Add the following content and replace the "[dn]" and "[alt_names]" sections with your information (use the external IP address obtained in Step 2):

[req]

default_bits = 2048

prompt = no

default_md = sha256

distinguished_name = dn

req_extensions = req_ext


[dn]

C = COUNTRY_CODE_HERE

ST = STATE_HERE

L = CITY_HERE

O = ORGANIZATION_HERE

OU = ORGANIZATION_UNIT_HERE

CN = IP_ADDRESS_HERE


[req_ext]

subjectAltName = @alt_names


[alt_names]

IP.1 = IP_ADDRESS_HERE
  1. Create a private key:
C:\OpenSSL-Win64\bin\openssl genrsa -out server.key 2048
  1. Create certificate signing request:
C:\OpenSSL-Win64\bin\openssl req -new -key server.key -out server.csr -config server.cnf
  1. Create self-signed certificate using the signing request
C:\OpenSSL-Win64\bin\openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 365 -sha256

The "days" value is in this case set to 365 (1 year) and denotes the certificate validity. This value can be changed based on your requirements. After the command is executed, you will be prompted to enter the pass phrase for "ca.key" (this is the pass phrase that was set in Step 3).

Step 5: Copy the certificate to your Android device

  1. Copy the "ca.crt" to your Android device (e.g., Documents folder), where GUI-O application is installed. You can send the certificate via email, transfer it via USB cable, etc.

Step 6: Setup broker configuration

  1. Use any text editor to open "mosquitto.conf" file located under "C:\mosquitto" (you can alternatively download the file here) :

Find the "Listeners" section and set:

listener 8883

Find the "Certificate based SSL/TLS support" section and set:

cafile C:\mosquitto\certs\ca.crt

certfile C:\mosquitto\certs\server.crt

keyfile C:\mosquitto\certs\server.key

require_certificate false

use_identity_as_username false

tls_version tlsv1.2

Find the "Security" section and set:

allow_anonymous false

password_file C:\mosquitto\pwfile.example

Save the file.

  1. Create user name and password for authentication when connecting to the broker (replace the "USER" and "PASS" with your user name and password - both will be required by the GUI-O application when connecting to the broker):
C:\mosquitto\mosquitto_passwd -b C:\mosquitto\pwfile.example USER PASS

NOTE: You can add more users with different credentials using this command.

  1. Restart Windows

  2. If the Mosquitto service is not started after the restart, open the cmd as administrator and run:

C:\mosquitto\mosquitto install

Step 7: Configure port forwarding rules for your router

  1. Open cmd and determine the MAC (hardware) address of your device, where the Mosquitto broker is running:
ipconfig /all

This command will output the MAC address / "Physical Address" (formatted as xx-xx-xx-xx-xx-xx).

  1. Open your browser and enter the router IP address into the address bar (the router IP is usually printed on the back of the router). Enter router user name and password when prompted.

  2. Set a local static IP based on the MAC address of the device. Note that the procedure for setting a local static IP varies depending on the make and model of your router.

  3. Setup port forwarding by using the local static IP and setting the internal port range from 8883 to 8883 (this is the port that the Mosquitto service is listening on). Set the external port range to any valid value based on your preferences (e.g., from 43520 to 43520). Use TCP protocol and save the settings, making sure that the newly added port forwarding rule is enabled.

  4. Reboot the router.

  5. Restart the device, where the Mosquitto broker is running. After the restart, the device should have obtained the local static IP (you can check this by running the "ipconfig" command).

Step 8: Setup GUI-O application and connect to Mosquitto broker

  1. Open GUI-O application on your Android device and navigate to "Settings -> Connections IoT -> IoT Settings".

  2. Tap on "Server name" and set the value to your external IP, which was determined in Step 2.

  3. Tap on "SSL port number" and set the value to your external port number, which was set in Step 7 (e.g., 43520).

  4. Tap on "User name" and set the user name for authentication, which was created in Step 6.

  5. Tap on "User password" and set the password for authentication, which was created in Step 6.

  6. Tap on "Import certificate" and select the "ca.crt", which was transferred to the device in Step 5.

  7. Return to the previous menu and tap "Connect". If everything was setup correctly, the connection to the Mosquitto broker should be established successfully.

(Optional) Step 9: Setup ESP32 and connect to Mosquitto broker

  1. Download BasicMQTT_Mosquitto.ino sketch and open it in Arduino IDE.

  2. Open the "ca.crt" file using a text editor and copy / replace the certificate with the one in BasicMQTT_Mosquitto.ino source code (keep same certificate formatting)

  3. Finally, refer to ESP32 MQTT video example, while using the BasicMQTT_Mosquitto.ino sketch. Make sure that the external IP (mqttIP), external port number (mqttPort), user name (mqttUser) and password (mqttPass) variables are set according to Step 8.

NOTE: Do not forget to generate and set the publish and subscribe topics!


If you have any questions or run into any problems, please let me know!

Best regards,
kl3m3n