User:Riviera/Mosquito net

From XPUB & Lens-Based wiki
< User:Riviera
Revision as of 21:38, 8 June 2024 by Riviera (talk | contribs) (created page)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

This wiki post details how I combined hypertext with OpenSoundControl to send messages between ESP32 microcontrollers wirelessly via UDP. The code is split into two sections: one for the OSC Client microcontroller and another for the OSC server microcontroller. These microcontrollers have different roles to play in the network as illustrated in the table below:

Lolin D32 Pro MH-ET LIVE ESP32 DEVKIT
Receives OSC messages Sends OSC messages
Is not a WiFi hotspot Is a WiFi hotspot
Executes tone() Hosts a web interface

MH-ET LIVE ESP32 DEVKIT

I found this board in the XPUB studio. Stephen was using it a while ago but said it would be alright if I made use of it.

Firstly, a bunch of libraries are included to provide additional functionality.

#include <Arduino.h>
#include <WiFi.h>
#include <NetworkClient.h>
#include <WiFiAP.h>
#include <ESPmDNS.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <OSCMessage.h>

Next, some variables are initialised.

WiFiUDP Udp;
const IPAddress clientIP(192,168,4,2);

// Replace with your network credentials
const char* ssid = "moz";
// const char* password = "";

unsigned int localPort = 1337;

const char* PARAM_INPUT = "value";
// Set web server port number to 80
AsyncWebServer server(80); 

String pitchSliderValue = "0";

The board hosts a webpage using an Asynchronous Web Server library for ESP32 chips. The following code contains the HTML/CSS/JS for the index.html page. The HTML page contains two interactive elements including a slider and a button. The slider allows webpage visitors to adjust the pitch of the tone whilst the button is used to turn off the sound.

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Portable Mosquito</title>
  <style>
    html {font-family: Arial; display: inline-block; text-align: center;}
    h2 {font-size: 2.3rem;}
    p {font-size: 1.9rem;}
    body {max-width: 400px; margin:0px auto; padding-bottom: 25px;}
    .button { background-color: #4CAF50; border: none; color: white; padding: 16px 40px; text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}
    .button2 { background-color: #FFD65C; border: none; color: #003249; padding: 16px 40px; text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}
    .slider { -webkit-appearance: none; margin: 14px; width: 360px; height: 25px; background: #FFD65C;
      outline: none; -webkit-transition: .2s; transition: opacity .2s;}
    .slider::-webkit-slider-thumb {-webkit-appearance: none; appearance: none; width: 35px; height: 35px; background: #003249; cursor: pointer;}
    .slider::-moz-range-thumb { width: 35px; height: 35px; background: #003249; cursor: pointer; } 
  </style>
</head>
<body>
  <h2>Portable Mosquito</h2>
  <p><span id="pitchSliderValue">%PITCHSLIDERVALUE%</span></p>
  <p><input type="range" onchange="updatePitchSlider(this)" id="pitchSlider" min="14000" max="17000" value="%PITCHSLIDERVALUE%" step="500" class="slider"></p>
    <p><button class="button2" id="pitchButton">OFF</button></a></p>
<script>
function updatePitchSlider(element) {
    var pitchSliderValue = document.getElementById("pitchSlider").value;
  document.getElementById("pitchSliderValue").innerHTML = pitchSliderValue;
  console.log(pitchSliderValue);
  var xhr = new XMLHttpRequest();
  xhr.open("GET", "/pitchSlider?value="+pitchSliderValue, true);
  xhr.send();
}

document.getElementById("pitchButton").onclick = function() {
    var pitchSliderValue = 0;
    document.getElementById("pitchSliderValue").innerHTML = "Mosquito is Off";
    var xhr = new XMLHttpRequest();
  xhr.open("GET", "/pitchSlider?value="+pitchSliderValue, true);
  xhr.send();
}
    
</script>
</body>
</html>
)rawliteral";

I don’t understand what the purpose of the next function is in relation to the code as a whole. I have included it for thoroughness

String processor(const String& var){
  //Serial.println(var);
  if (var == "PITCHSLIDERVALUE"){
    return pitchSliderValue;
  }
  return String();
}

Next comes void setup(), the code that is executed once at boot.

void setup() {

The serial baud rate is set to 115200 and a WiFi access point is created.

  Serial.begin(115200);

  if (!WiFi.softAP(ssid)) {
    log_e("Soft AP creation failed.");
    while (1);
  }
  IPAddress myIP = WiFi.softAPIP();
  Serial.print("AP IP address: ");
  Serial.println(myIP);

Multicast DNS (mDNS) is employed next. It’s a protocol similar to DNS which makes network addresses accessible via human-friendly names. In this case, the webpage above can be accessed by pointing a browser to mosquito.local when connected to the moz WiFi network.

    // Initialize mDNS
  if (!MDNS.begin("mosquito")) {   // Set the hostname to "mosquito.local"
    Serial.println("Error setting up MDNS responder!");
    while(1) {
      delay(1000);
    }
  }

Towards the end of void setup() are two code blocks which handle HTTP requests. The first of these listens for connections to the root of the web server. When a client visits this page the server sends a 200 OK response code, followed by the contents of index.html. This ensures the webpage is displayed properly.

// HTTP requests
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
            request->send_P(200, "text/html", index_html, processor);
        });

Subsequently, the server listens for connections to mosquito.local/pitchSlider. Requests to this address are created when interacting with the slider. This is primarily a consequence of the JavaScript function updatePitchSlider(element). That function appears towards the end of index.html and uses XMLHttpRequest() to process state changes. The piece of code below instructs the ESP32 to send OSC messages when an HTTP request is made to those addresses. The User Datagram Protocol is used to facilitate doing so via a wireless connection.

  server.on("/pitchSlider", HTTP_GET, [] (AsyncWebServerRequest *request) {
            int32_t freq;
            // GET input value on <ESP_IP>/pitchSlider?value=<freq>
            if (request->hasParam(PARAM_INPUT)) {
                Serial.println(PARAM_INPUT);
                freq = request->getParam(PARAM_INPUT)->value().toInt();
                OSCMessage msg("/pitch");
                msg.add(freq);
                Udp.beginPacket(clientIP, localPort);
                msg.send(Udp);
                Udp.endPacket();  // Mark the end of the OSC Packet
                Serial.println("Freeing up space");
                msg.empty();   // Free space occupied by message    
            }
            else {
                freq = 0;
            }
            Serial.println(freq);
            request->send(200, "text/plain", "OK");
        });

finally, the UDP and WiFi servers are instantiated..

    Udp.begin(localPort);
    Serial.printf("UDP server : %s:%i \n ", WiFi.localIP().toString().c_str(), localPort);
  server.begin();
}

void loop() contains nothing, probably because the web server is Asynchronous.

void loop(){

}

Lolin D32 Pro

In this setup the Lolin D32 Pro does the following:

  1. It connects to the moz network
  2. It checks for new OSC Messages
  3. It executes tone() when messages are received

Fewer libraries need to be included as the board is not hosting a website, nor creating a WiFi access point.

#include <Arduino.h>
#include <WiFi.h>
#include <NetworkClient.h>
#include <OSCMessage.h>
#include <WiFiUdp.h>

Then, some variables are initalised:

WiFiUDP Udp;
const IPAddress serverIP(192,168,4,1);
const IPAddress clientIP(192,168,4,2);

const char* ssid = "moz";
// const char* password = "";

unsigned int localPort = 1337;

const int speaker = 33;

The setPitch function executes tone when OSC messages arrive. The OSC message contains an array of values of mixed types. The if statement checks whether the zeroth item in the array is an integer. OSC can handle floats, strings and other types, but tone() requires integers. The value of the integer is retrieved from msg and employed in the tone function.

void setPitch(OSCMessage &msg){
    //do something with the OSCMessage...
    if (msg.isInt(0)){
        int val = msg.getInt(0);
        tone(speaker,val);
    }
}

receiveMessage() is called repeatedly by void loop() with a 200ms delay to accommodate for network latency. The function states inmsg is a variable of the OSCMessage type. The WiFiUdp library is utilised to retrieve data from the UDP stream. Information is printed to the serial bus connection when packets are delivered. The code reads the packet bit by bit, filling up the contents of the OSC message as it does so. Once all the data has been read, the function dispatches a call to the setPitch function at the “/pitch” OSC address.

void receiveMessage() {
    OSCMessage inmsg;
    int size = Udp.parsePacket();
    Serial.print(" Received packet from : ");
    Serial.println(Udp.remoteIP());
  Serial.print(" Size : ");
    Serial.println(size);
    if (size > 0) {
        while (size--) {
            inmsg.fill(Udp.read());
        }
        if (!inmsg.hasError()) {
            inmsg.dispatch("/pitch", setPitch);
        } 
    }
}

void setup() and void loop() are quite simple in this scenario. The former function instructs the board to connect to the moz network, prints the local IP address and gets the UDP server going. The latter function calls receiveMessage() repeatedly.

void setup() {
  Serial.begin(115200);
    WiFi.begin(ssid);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

    Serial.println("");
  Serial.println("WiFi connected.");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

    Udp.begin(localPort);
    // OSC Messages
    // Udp.listen(localPort);
    
}

void loop(){
    Serial.println("Checking for OSC messages");
    receiveMessage();
    delay(200);
}

Based on code by
Rui Santos (https://randomnerdtutorials.com)
Tristan Schulze (https://turboflip.de/esp32-meets-osc-communication/)
Elochukwu Ifediora (fedy0)