feat(data) undefined: Added new HTML file for piano spectrum analyzer. fix(Config.h): Updated WiFi settings and web configuration portal details. refactor(Arduino sketch): Added WiFi and WebSocket support, enabling real-time spectrum data transmission over the
- New HTML file added to the `data` directory for a piano spectrum analyzer. - Updated `Config.h` with WiFi settings and web configuration portal details. - Update the Arduino sketch with WiFi and WebSocket support, enabling real-time spectrum data transmission over the web.
This commit is contained in:
91
data/index.html
Normal file
91
data/index.html
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>ESP32 Piano Spectrum Analyzer</title>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
margin: 20px;
|
||||||
|
background: #f0f0f0;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#spectrum-container {
|
||||||
|
position: relative;
|
||||||
|
height: 400px;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>Piano Spectrum Analyzer</h1>
|
||||||
|
<div id="spectrum-container">
|
||||||
|
<canvas id="spectrumChart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const ctx = document.getElementById('spectrumChart').getContext('2d');
|
||||||
|
const chart = new Chart(ctx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: Array.from({length: 512}, (_, i) => i * (8000 / 1024)),
|
||||||
|
datasets: [{
|
||||||
|
label: 'Frequency Spectrum',
|
||||||
|
data: Array(512).fill(0),
|
||||||
|
borderColor: 'rgb(75, 192, 192)',
|
||||||
|
tension: 0.1,
|
||||||
|
fill: false
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
animation: {
|
||||||
|
duration: 0
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
max: 5000
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Frequency (Hz)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const wsUrl = `ws://${window.location.hostname}/ws`;
|
||||||
|
const ws = new WebSocket(wsUrl);
|
||||||
|
|
||||||
|
ws.onmessage = function(event) {
|
||||||
|
const spectrum = JSON.parse(event.data);
|
||||||
|
chart.data.datasets[0].data = spectrum;
|
||||||
|
chart.update();
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onclose = function() {
|
||||||
|
console.log('WebSocket connection closed');
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.reload();
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -6,6 +6,12 @@ namespace Config {
|
|||||||
// Serial Configuration
|
// Serial Configuration
|
||||||
constexpr int SERIAL_BAUD_RATE = 115200; // Serial communication baud rate
|
constexpr int SERIAL_BAUD_RATE = 115200; // Serial communication baud rate
|
||||||
|
|
||||||
|
// WiFi Configuration
|
||||||
|
constexpr const char* WIFI_AP_NAME = "ESP32Piano"; // AP name when in configuration mode
|
||||||
|
constexpr const char* WIFI_AP_PASSWORD = "12345678"; // AP password when in configuration mode
|
||||||
|
constexpr uint32_t WIFI_CONFIG_TIMEOUT = 180; // Seconds to wait for WiFi configuration
|
||||||
|
constexpr uint32_t WIFI_CONFIG_PORT = 80; // Web configuration portal port
|
||||||
|
|
||||||
// I2S Pin Configuration
|
// I2S Pin Configuration
|
||||||
constexpr int I2S_MIC_SERIAL_CLOCK = 8; // SCK
|
constexpr int I2S_MIC_SERIAL_CLOCK = 8; // SCK
|
||||||
constexpr int I2S_MIC_LEFT_RIGHT_CLOCK = 9; // WS/LRC
|
constexpr int I2S_MIC_LEFT_RIGHT_CLOCK = 9; // WS/LRC
|
||||||
|
|||||||
91
src/main.cpp
91
src/main.cpp
@@ -1,4 +1,9 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <WiFiManager.h>
|
||||||
|
#include <ESPAsyncWebServer.h>
|
||||||
|
#include <AsyncTCP.h>
|
||||||
|
#include <SPIFFS.h>
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "I2SConfig.h"
|
#include "I2SConfig.h"
|
||||||
#include "AudioLevelTracker.h"
|
#include "AudioLevelTracker.h"
|
||||||
@@ -9,15 +14,55 @@
|
|||||||
static int16_t raw_samples[Config::SAMPLE_BUFFER_SIZE];
|
static int16_t raw_samples[Config::SAMPLE_BUFFER_SIZE];
|
||||||
static AudioLevelTracker audioLevelTracker;
|
static AudioLevelTracker audioLevelTracker;
|
||||||
static NoteDetector noteDetector;
|
static NoteDetector noteDetector;
|
||||||
|
WiFiManager wifiManager;
|
||||||
|
AsyncWebServer server(80);
|
||||||
|
AsyncWebSocket ws("/ws");
|
||||||
|
|
||||||
// Timing and state variables
|
// Timing and state variables
|
||||||
static uint32_t lastNotePrintTime = 0;
|
static uint32_t lastNotePrintTime = 0;
|
||||||
static uint32_t lastSpectrumPrintTime = 0;
|
static uint32_t lastSpectrumPrintTime = 0;
|
||||||
|
static uint32_t lastWebUpdateTime = 0;
|
||||||
static bool showSpectrum = false;
|
static bool showSpectrum = false;
|
||||||
|
|
||||||
// Note names for display
|
// Note names for display
|
||||||
const char* noteNames[] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
|
const char* noteNames[] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
|
||||||
|
|
||||||
|
void onWebSocketEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
|
||||||
|
switch (type) {
|
||||||
|
case WS_EVT_CONNECT:
|
||||||
|
Serial.printf("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str());
|
||||||
|
break;
|
||||||
|
case WS_EVT_DISCONNECT:
|
||||||
|
Serial.printf("WebSocket client #%u disconnected\n", client->id());
|
||||||
|
break;
|
||||||
|
case WS_EVT_DATA:
|
||||||
|
break;
|
||||||
|
case WS_EVT_ERROR:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void initWebServer() {
|
||||||
|
if (!SPIFFS.begin(true)) {
|
||||||
|
Serial.println("An error occurred while mounting SPIFFS");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.onEvent(onWebSocketEvent);
|
||||||
|
server.addHandler(&ws);
|
||||||
|
|
||||||
|
// Serve static files from SPIFFS
|
||||||
|
server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.html");
|
||||||
|
|
||||||
|
// Handle not found
|
||||||
|
server.onNotFound([](AsyncWebServerRequest *request) {
|
||||||
|
request->send(404, "text/plain", "Not found");
|
||||||
|
});
|
||||||
|
|
||||||
|
server.begin();
|
||||||
|
Serial.println("HTTP server started");
|
||||||
|
}
|
||||||
|
|
||||||
void handleSerialCommands() {
|
void handleSerialCommands() {
|
||||||
if (Serial.available()) {
|
if (Serial.available()) {
|
||||||
char cmd = Serial.read();
|
char cmd = Serial.read();
|
||||||
@@ -55,19 +100,54 @@ void printNoteInfo(const DetectedNote& note) {
|
|||||||
noteNames[noteIndex], octave, note.frequency, note.magnitude, duration);
|
noteNames[noteIndex], octave, note.frequency, note.magnitude, duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void initWiFi() {
|
||||||
|
// Set configuration portal timeout
|
||||||
|
wifiManager.setConfigPortalTimeout(Config::WIFI_CONFIG_TIMEOUT);
|
||||||
|
|
||||||
|
// Set custom portal settings
|
||||||
|
wifiManager.setAPStaticIPConfig(IPAddress(192,168,4,1), IPAddress(192,168,4,1), IPAddress(255,255,255,0));
|
||||||
|
|
||||||
|
// Try to connect to saved WiFi credentials
|
||||||
|
if(!wifiManager.autoConnect(Config::WIFI_AP_NAME, Config::WIFI_AP_PASSWORD)) {
|
||||||
|
Serial.println("Failed to connect and hit timeout");
|
||||||
|
ESP.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.println("Successfully connected to WiFi");
|
||||||
|
Serial.print("IP Address: ");
|
||||||
|
Serial.println(WiFi.localIP());
|
||||||
|
}
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
Serial.begin(Config::SERIAL_BAUD_RATE);
|
Serial.begin(Config::SERIAL_BAUD_RATE);
|
||||||
while(!Serial) {
|
while(!Serial) {
|
||||||
delay(10);
|
delay(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initWiFi();
|
||||||
|
initWebServer();
|
||||||
initI2S();
|
initI2S();
|
||||||
|
|
||||||
Serial.println("Piano Note Detection Ready (C2-C6)");
|
Serial.println("Piano Note Detection Ready (C2-C6)");
|
||||||
Serial.println("Press 'h' for help");
|
Serial.println("Press 'h' for help");
|
||||||
noteDetector.beginCalibration();
|
noteDetector.beginCalibration();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sendSpectrumData() {
|
||||||
|
if (ws.count() > 0 && !noteDetector.isCalibrating()) {
|
||||||
|
const auto& spectrum = noteDetector.getSpectrum();
|
||||||
|
String json = "[";
|
||||||
|
for (size_t i = 0; i < Config::FFT_SIZE/2; i++) {
|
||||||
|
if (i > 0) json += ",";
|
||||||
|
json += String(spectrum[i], 2);
|
||||||
|
}
|
||||||
|
json += "]";
|
||||||
|
ws.textAll(json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
|
ws.cleanupClients();
|
||||||
handleSerialCommands();
|
handleSerialCommands();
|
||||||
|
|
||||||
size_t bytes_read = 0;
|
size_t bytes_read = 0;
|
||||||
@@ -88,11 +168,16 @@ void loop() {
|
|||||||
uint32_t currentTime = millis();
|
uint32_t currentTime = millis();
|
||||||
const auto& detectedNotes = noteDetector.getDetectedNotes();
|
const auto& detectedNotes = noteDetector.getDetectedNotes();
|
||||||
|
|
||||||
|
// Update web clients with spectrum data
|
||||||
|
if (currentTime - lastWebUpdateTime >= 50) { // Update web clients every 50ms
|
||||||
|
sendSpectrumData();
|
||||||
|
lastWebUpdateTime = currentTime;
|
||||||
|
}
|
||||||
|
|
||||||
// Show spectrum if enabled
|
// Show spectrum if enabled
|
||||||
if (showSpectrum &&
|
if (showSpectrum &&
|
||||||
!noteDetector.isCalibrating() &&
|
!noteDetector.isCalibrating() &&
|
||||||
currentTime - lastSpectrumPrintTime >= Config::DEBUG_INTERVAL_MS) {
|
currentTime - lastSpectrumPrintTime >= Config::DEBUG_INTERVAL_MS) {
|
||||||
|
|
||||||
SpectrumVisualizer::visualizeSpectrum(noteDetector.getSpectrum(), Config::FFT_SIZE);
|
SpectrumVisualizer::visualizeSpectrum(noteDetector.getSpectrum(), Config::FFT_SIZE);
|
||||||
lastSpectrumPrintTime = currentTime;
|
lastSpectrumPrintTime = currentTime;
|
||||||
}
|
}
|
||||||
@@ -106,6 +191,6 @@ void loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Small delay to prevent serial buffer overflow
|
// Small delay to prevent WDT reset
|
||||||
delay(10);
|
delay(1);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user