User:Riviera/Mosquito net
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:
- It connects to the
moz
network - It checks for new OSC Messages
- 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)