ESP32 streaming data via WiFi to Android

It it possible to stream data acquired from sensors / inputs to an Android app, via WiFi, once connection between the two has been established. It is easy to do via Bluetooth and I have done that before but I want to try WiFi.

This depends on the Android App.

You can send UDP or TCP-messages.
For UDP-messages there are universal send/receiver-apps in the App-store
Probably there are also apps for sending/receiving TCP-messages.

It really depends on what the Android App can do.
As long as you don't specifiy which Android app you want to use the generalised question
is YESNO NOYES

One way to use WiFi is ESP-DASH. This is an arduino-library that makes programming
interaction between a website GUI and your ESP very easy because you don't have to deal with the html-stuff. The disadvantage is the design of the webinterface is not changeable.

You should describe your project pretty detailed.
What does streaming data mean?
streaming music at 192 kbs?
update sensor-values to show them on a smartphone / tablet?

best regards Stefan

Thanks.

It is an automotive data acquisition / dashboard system I am busy with and in process of upgrading from an Arduino to ESP32

The ESP32 is mounted in the vehicle / bike, reading things like RPM, speed, temperatures, etc.

I have developed the Android app myself. With the bluetooth version, the Arduino sends the data, as a CSV string, via a bluetooth module (serial port) to the app, which then splits it and applied the various values to the relevant gauges.

So I was wondering if with the ESP32, the app could connect via WiFi and the ESP32 then continously sends the data strings to the app. So basically like establishing a serial port connection but only via WiFi

The ESP32 can easily do both sending UDP or TCP.
UDP is a protocol that has - in itself - no acknwledgement mechanism that checks if a message was really received by the receiver. If a message gets lost for what reason ever it is lost.

TCP has mechanisms to ensure that the data will be really received though I don't know how far this goes.

So if your App is able to receive UDP or TCP-messages it is possible do do.
The ESP32 has enough RAM and speed to send messages with a lot of bytes.

So here is a demo-code that shows the basic principle for sending UDP-messages
including a small python-script that receives the messages on a computer running the python-script

// start of macros dbg and dbgi
#define dbg(myFixedText, variableName) \
  Serial.print( F(#myFixedText " "  #variableName"=") ); \
  Serial.println(variableName);
// usage: dbg("1:my fixed text",myVariable);
// myVariable can be any variable or expression that is defined in scope

#define dbgi(myFixedText, variableName,timeInterval) \
  do { \
    static unsigned long intervalStartTime; \
    if ( millis() - intervalStartTime >= timeInterval ){ \
      intervalStartTime = millis(); \
      Serial.print( F(#myFixedText " "  #variableName"=") ); \
      Serial.println(variableName); \
    } \
  } while (false);
// end of macros dbg and dbgi


// I wrote some basic documentation about receiving the UDP-messages with python at the end of the file
#include <WiFi.h>
#include <SafeString.h>

#define MaxMsgLength 1024
createSafeString(UDP_Msg_SS,MaxMsgLength); 
uint8_t UDP_Msg_uint8_Buffer[MaxMsgLength + 1]; // for some strange reasons on ESP32 the udp.write-function needs an uint8_t-array

#define MaxHeaderLength 32 
createSafeString(Header_SS,MaxHeaderLength);

#define MaxTimeStampLength 64 
createSafeString(TimeStamp_SS,MaxTimeStampLength);


char HeaderDelimiter = '$'; // must match the delimiter defined in the python-code 


const char *ssid     = ""; 

const char *password = "";

IPAddress    remoteIP     (192, 168, 178, 160); // receiver-IP
unsigned int remotePort = 4210;                 // receiver port to listen on must match the portnumber the receiver is listening to

WiFiUDP Udp;

const char* ntpServer = "fritz.box";
const long  gmtOffset_sec = 0;
const int   daylightOffset_sec = 7200;

#include <time.h>                   // time() ctime()
time_t now;                         // this is the epoch
tm myTimeInfo;                      // the structure tm holds time information in a more convient way


boolean TimePeriodIsOver (unsigned long &expireTime, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - expireTime >= TimePeriod )
  {
    expireTime = currentMillis; // set new expireTime
    return true;                // more time than TimePeriod) has elapsed since last time if-condition was true
  }
  else return false;            // not expired
}

const byte OnBoard_LED = 2;
int BlinkTime = 500;

void BlinkHeartBeatLED(int IO_Pin, int BlinkPeriod) {
  static unsigned long MyBlinkTimer;
  pinMode(IO_Pin, OUTPUT);

  if ( TimePeriodIsOver(MyBlinkTimer, BlinkPeriod) ) {
    digitalWrite(IO_Pin, !digitalRead(IO_Pin) );
  }
}

unsigned long TestTimer;
unsigned long UDP_SendTimer;

int myCounter = 0;
int HeaderNr  = 0;


void PrintFileNameDateTime()
{
  Serial.print("Code running comes from file ");
  Serial.println(__FILE__);
  Serial.print(" compiled ");
  Serial.print(__DATE__);
  Serial.println(__TIME__);
}

void showTime() {
  time(&now);                       // read the current time
  localtime_r(&now, &myTimeInfo);           // update the structure tm with the current time
  Serial.print("year:");
  Serial.print(myTimeInfo.tm_year + 1900);  // years since 1900
  Serial.print("\tmonth:");
  Serial.print(myTimeInfo.tm_mon + 1);      // January = 0 (!)
  Serial.print("\tday:");
  Serial.print(myTimeInfo.tm_mday);         // day of month
  Serial.print("\thour:");
  Serial.print(myTimeInfo.tm_hour);         // hours since midnight  0-23
  Serial.print("\tmin:");
  Serial.print(myTimeInfo.tm_min);          // minutes after the hour  0-59
  Serial.print("\tsec:");
  Serial.print(myTimeInfo.tm_sec);          // seconds after the minute  0-61*
  Serial.print("\twday");
  Serial.print(myTimeInfo.tm_wday);         // days since Sunday 0-6
  if (myTimeInfo.tm_isdst == 1)             // Daylight Saving Time flag
    Serial.print("\tDST");
  else
    Serial.print("\tstandard");
    
  Serial.println();
}


void StoreTimeStampIntoSS(SafeString& p_RefToSS, tm p_myTimeInfo) {

  time(&now);                               // read the current time
  localtime_r(&now, &myTimeInfo);           // update the structure tm with the current time

  //p_RefToSS = " ";
  p_RefToSS  = myTimeInfo.tm_year + 1900;
  p_RefToSS += ".";

  // month
  if (p_myTimeInfo.tm_mon + 1 < 10) {
    p_RefToSS += "0";
  }  
  p_RefToSS += myTimeInfo.tm_mon + 1;

  p_RefToSS += ".";

  // day
  if (p_myTimeInfo.tm_mday + 1 < 10) {
    p_RefToSS += "0";
  }    
  p_RefToSS += myTimeInfo.tm_mday;

  p_RefToSS += "; ";

  // hour
  if (p_myTimeInfo.tm_hour < 10) {
    p_RefToSS += "0";
  }    
  p_RefToSS += myTimeInfo.tm_hour;
  p_RefToSS += ":";

  // minute
  if (p_myTimeInfo.tm_min < 10) {
    p_RefToSS += "0";
  }    
  p_RefToSS += myTimeInfo.tm_min;
  
  p_RefToSS += ":";

  // second
  if (p_myTimeInfo.tm_sec < 10) {
    p_RefToSS += "0";
  }    
  p_RefToSS += myTimeInfo.tm_sec;
  //p_RefToSS += ",";  
}


void connectToWifi() {
  Serial.print("Connecting to "); 
  Serial.println(ssid);

  WiFi.persistent(false);
  WiFi.mode(WIFI_STA);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    BlinkHeartBeatLED(OnBoard_LED, 333);
    delay(332);
    Serial.print(".");
  }
  Serial.print("\n connected.");
  Serial.println(WiFi.localIP() );

}

void synchroniseWith_NTP_Time() {
  Serial.print("configTime uses ntpServer ");
  Serial.println(ntpServer);
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
  Serial.print("synchronising time");
  
  while (myTimeInfo.tm_year + 1900 < 2000 ) {
    time(&now);                       // read the current time
    localtime_r(&now, &myTimeInfo);
    BlinkHeartBeatLED(OnBoard_LED, 100);
    delay(100);
    Serial.print(".");
  }
  Serial.print("\n time synchronsized \n");
  showTime();    
}

void setup() {
  Serial.begin(115200);
  Serial.println("\n Setup-Start \n");
  PrintFileNameDateTime();
  Serial.print("InitSensor() done \n");
  
  connectToWifi();
  synchroniseWith_NTP_Time();
  Header_SS = "Header"; 
}

void PrintMsg() {
  Serial.print("UDP_Msg_SS #");
  Serial.print(UDP_Msg_SS);
  Serial.println("#");
}

void loop() {
  BlinkHeartBeatLED(OnBoard_LED, BlinkTime);

  if (TimePeriodIsOver(UDP_SendTimer, 2000) ) {
    Serial.print("Send Message to #");
    Serial.print(remoteIP);
    Serial.print(":");
    Serial.println(remotePort);

    UDP_Msg_SS = "";

    UDP_Msg_SS = Header_SS;
    UDP_Msg_SS += HeaderDelimiter;

    StoreTimeStampIntoSS(TimeStamp_SS,myTimeInfo);
    UDP_Msg_SS += TimeStamp_SS;

    UDP_Msg_SS += ",my Testdata1,";
    UDP_Msg_SS += 123;
    UDP_Msg_SS += ",my Testdata2,";

    UDP_Msg_SS += 789;
    UDP_Msg_SS += ",";
    
    UDP_Msg_SS += myCounter++; 

    dbg("Send UDP_Msg #",UDP_Msg_SS);
    dbg("length:",UDP_Msg_SS.length());
    Udp.beginPacket(remoteIP, remotePort);
    Udp.write((const uint8_t*)UDP_Msg_SS.c_str(), UDP_Msg_SS.length() );  
    Udp.endPacket();
  }    
}

/*

This is a democode that demonstrates how to send UDP-messages with a timestamp 
The time is synchronized using a NTP-server. Most local routers like Fritz!Box can be used as the NTP-timer-server

The message has a userdefinable header which could be used for identifying the sender on the recieverside
There is a user-definable Header-delimiter that can be used to identify which characters of the
UDP-message belong to the header and which to the userdata

The code makes use of the PString-library. PStrings don't cause memory-problems like datatype "Strings"
and are easier to use than arrays of char. Example adding an integer to a PString-variable is as easy as
MyPString = myInteger;

The userdata has commas between each data so you can import the textfile 
into table-calculation-software or databases as CSV-file comma separated values

the code has some additional useful functions as there are
- PrintFileNameDateTime() printing the path and filename of sourcecode file this program was generated with

- boolean TimePeriodIsOver  a non-blocking timing-function based on millis which is suitable for 
  timed execution in a regular manner (repeat every n milliseconds)

- BlinkHeartBeatLED() blinks the onboard-LED of ESP32 nodeMCU-boards. Gives visual feedback if the code is running  

The lines of code are grouped by functionality into several functions
The functions name says what the function does

I use this code for easy datalogging on a computer with a python-code that acts as the UDP-listener for 
incoming messages. Inside your ESP32-code you have to adjust the IP-adress to the receiver 
and the portnumber must be the same on sender (ESP32) and receiver-side 

In the python-code The header is used to create a file with the header as filename and extension ".txt" or if file 
is already existant to append the actual received UDP-message at the end of the file.

here is the python-code that does this. I tested it with python 3.9.2 for windows
#Start of python-code
# very simple and short upd-receiver based on python-code I found here
# https://www.studytonight.com/network-programming-in-python/working-with-udp-sockets#

import socket

sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)      # For UDP

udp_host = socket.gethostname()            # Host IP
udp_port = 4210                     # specified port to connect

sock.bind((udp_host,udp_port))
print ("Waiting for client...")

HeaderDelimitChar = "$"

while True:
  data,addr = sock.recvfrom(1024)         #receive data from client
  print("data #",data,"#")
  Msg = data.decode('utf-8')
  print ("Received Message: #",Msg,"# from",addr)
  EndOfHeader = Msg.find(HeaderDelimitChar)
  HeaderBytes = Msg[0:EndOfHeader]
  FileName = HeaderBytes + ".txt"
  print("Filename #",FileName,"#")
  myFile = open(FileName, "a+")
  EndOfStr = data.find(0)
  MsgToWrite = Msg[EndOfHeader + 1 :1024] + '\r'
  myFile.write(MsgToWrite);
  myFile.close()
  print ("Data #",MsgToWrite,"#")

#End of python-code

For learning more about python just google with keyword "python" "your keyword of interest"  
 */

and here is a very basic demo-code that demonstrates using TCP with the ESP32 acting as the accesspoint.

written by user @J-M-L

#ifdef ESP32              // depending on the microcontroller-type 
#include <WiFi.h>         // include the right library for ESP32
#elif defined(ESP8266)
#include <ESP8266WiFi.h>  // or ESP8266
#endif

// this tcp_server demo-code creates its own WiFi-network 
// where the tcp_client demo-code connects to
// the ssid and the portNumber must be the same to make it work

const char* ssid     = "ESP32-AP";
const uint16_t portNumber = 50000; // System Ports 0-1023, User Ports 1024-49151, dynamic and/or Private Ports 49152-65535

WiFiServer server(portNumber);
WiFiClient client;
bool connected = false;

void setup() {
  Serial.begin(115200);
  Serial.println( F("Setup-Start") );
  Serial.print("Creating AP (Access Point) with name#");
  Serial.print(ssid);
  Serial.println("#");
  WiFi.softAP(ssid);

  IPAddress IP = WiFi.softAPIP();
  Serial.print(" -> softAP with IP address: ");
  Serial.println(IP);
  server.begin();
  Serial.print("TCP-Server on port ");
  Serial.print(portNumber);
  Serial.print(" started");
}

void loop() {

  char TCP_Char;
  char serialChar;
  
  if (!connected) {
    // listen for incoming clients
    client = server.available();
    if (client) {
      Serial.println("\n Got a client connected to my WiFi !");
      if (client.connected()) {
        Serial.println("an now this client has connected over TCP!");
        Serial.println("if client sends characters");
        Serial.println("they were printed to the serial monitor");
        connected = true;
      } else {
        Serial.println("but it's not connected over TCP!");        
        client.stop();  // close the connection:
      }
    }
  } 
  else {
    if (client.connected()) {
      
      // if characters sended from client is in the buffer
      while ( client.available() ) { 
        TCP_Char = client.read(); // take one character out of the TCP-receive-buffer
        Serial.write(TCP_Char);   // print it to the serial monitor
      }  

      // if characters have been typed into the serial monitor  
      while (Serial.available()) {
        char serialChar = Serial.read(); // take character out of the serial buffer
        Serial.write(serialChar); // print local echo
        client.write(serialChar); // send character over TCP to client
      }
    } 
    else {
      Serial.println("Client has disconnected the TCP-connection");
      client.stop();  // close the connection:
      connected = false;
    }
  }
}

best regards Stefan

2 Likes

Awesome! Thanks so much for the help and the tips. I will investigate this in the next day or 2 to see if I can get the app to receive either UDP or TCP messages

After doing a quick search it seems that with Xamarin I will be able to do either UDP or TCP. But that is for another day to figure all of that out. At least I now have an idea of what to work with.

Another question (which I hope you don't think is ignorant as it is late and I'm tired) : will TCP and UDP both be faster than bluetooth? I know bluetooth is not very fast and usb is faster but I could not get good libraries for Xamarin for USB comms.

Wow Stephan,
You are awesome! Thanks for the demo code. Do you have something similar for Bluetooth?
Flavio

Awesome! How about in microPython?

That's pretty easy.
This sketch is used to get the temperatures from an MLX90614 and knock those out on BT - use any BT Serial app out there.

#include <Adafruit_MLX90614.h>
#include "BluetoothSerial.h"

#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
#error Bluetooth is not enabled! Please run `make menuconfig` to and enable it
#endif

BluetoothSerial SerialBT;

Adafruit_MLX90614 mlx = Adafruit_MLX90614();

void setup() 
{
  Serial.begin(115200);
  SerialBT.begin("ESP32_MLX");  // Gives Bluetooth xcvr a name
  Serial.println("Device started, now you can pair it!");

  mlx.begin();
}

void loop() 
{
  SerialBT.print("Obj ");
  SerialBT.print(mlx.readObjectTempF());
  SerialBT.println(" F");
  SerialBT.print("Pkg ");
  SerialBT.print(mlx.readAmbientTempF());
  SerialBT.println(" F");
  
  delay(10000);
}

well all the honor goes to user @J-M-L who has written this code

best regards Stefan

Wow! I am both impressed and grateful. I'll give it a try and let you know how it goes.

Thanks
Flavio

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.