FIX: 修复双声道声波配网失效, 添加屏幕打印SSID/密码 (#971)

* debug: 添加声波配网的log打印点display

* fix: 修复双声道下声波配网失效的问题

* fix: codec可能为nullptr的问题(需要从单例board获取)

* Update afsk_demod.cc

fix coding style

---------

Co-authored-by: yangkaiyue <yangkaiyue1@tenclass.com>
Co-authored-by: Xiaoxia <terrence@tenclass.com>
This commit is contained in:
Ky1eYang
2025-07-23 22:59:07 +08:00
committed by GitHub
parent ca35b0761b
commit 656bf3c7fa
3 changed files with 72 additions and 104 deletions

View File

@@ -2,6 +2,7 @@
#include <cstring> #include <cstring>
#include <algorithm> #include <algorithm>
#include "esp_log.h" #include "esp_log.h"
#include "display.h"
#ifndef M_PI #ifndef M_PI
#define M_PI 3.14159265358979323846 #define M_PI 3.14159265358979323846
@@ -12,7 +13,10 @@ namespace audio_wifi_config
static const char *kLogTag = "AUDIO_WIFI_CONFIG"; static const char *kLogTag = "AUDIO_WIFI_CONFIG";
void ReceiveWifiCredentialsFromAudio(Application *app, void ReceiveWifiCredentialsFromAudio(Application *app,
WifiConfigurationAp *wifi_ap) WifiConfigurationAp *wifi_ap,
Display *display,
size_t input_channels
)
{ {
const int kInputSampleRate = 16000; // Input sampling rate const int kInputSampleRate = 16000; // Input sampling rate
const float kDownsampleStep = static_cast<float>(kInputSampleRate) / static_cast<float>(kAudioSampleRate); // Downsampling step const float kDownsampleStep = static_cast<float>(kInputSampleRate) / static_cast<float>(kAudioSampleRate); // Downsampling step
@@ -36,28 +40,30 @@ namespace audio_wifi_config
continue; continue;
} }
if (input_channels == 2) { // 如果是双声道输入,转换为单声道
auto mono_data = std::vector<int16_t>(audio_data.size() / 2);
for (size_t i = 0, j = 0; i < mono_data.size(); ++i, j += 2) {
mono_data[i] = audio_data[j];
}
audio_data = std::move(mono_data);
}
// Downsample the audio data // Downsample the audio data
std::vector<float> downsampled_data; std::vector<float> downsampled_data;
size_t last_index = 0; size_t last_index = 0;
if (kDownsampleStep > 1.0f) if (kDownsampleStep > 1.0f) {
{
downsampled_data.reserve(audio_data.size() / static_cast<size_t>(kDownsampleStep)); downsampled_data.reserve(audio_data.size() / static_cast<size_t>(kDownsampleStep));
for (size_t i = 0; i < audio_data.size(); ++i) for (size_t i = 0; i < audio_data.size(); ++i) {
{
size_t sample_index = static_cast<size_t>(i / kDownsampleStep); size_t sample_index = static_cast<size_t>(i / kDownsampleStep);
if ((sample_index + 1) > last_index) if ((sample_index + 1) > last_index) {
{
downsampled_data.push_back(static_cast<float>(audio_data[i])); downsampled_data.push_back(static_cast<float>(audio_data[i]));
last_index = sample_index + 1; last_index = sample_index + 1;
} }
} }
} } else {
else
{
downsampled_data.reserve(audio_data.size()); downsampled_data.reserve(audio_data.size());
for (int16_t sample : audio_data) for (int16_t sample : audio_data) {
{
downsampled_data.push_back(static_cast<float>(sample)); downsampled_data.push_back(static_cast<float>(sample));
} }
} }
@@ -66,35 +72,28 @@ namespace audio_wifi_config
auto probabilities = signal_processor.ProcessAudioSamples(downsampled_data); auto probabilities = signal_processor.ProcessAudioSamples(downsampled_data);
// Feed probability data to the data buffer // Feed probability data to the data buffer
if (data_buffer.ProcessProbabilityData(probabilities, 0.5f)) if (data_buffer.ProcessProbabilityData(probabilities, 0.5f)) {
{
// If complete data was received, extract WiFi credentials // If complete data was received, extract WiFi credentials
if (data_buffer.decoded_text.has_value()) if (data_buffer.decoded_text.has_value()) {
{
ESP_LOGI(kLogTag, "Received text data: %s", data_buffer.decoded_text->c_str()); ESP_LOGI(kLogTag, "Received text data: %s", data_buffer.decoded_text->c_str());
display->SetChatMessage("system", data_buffer.decoded_text->c_str());
// Split SSID and password by newline character // Split SSID and password by newline character
std::string wifi_ssid, wifi_password; std::string wifi_ssid, wifi_password;
size_t newline_position = data_buffer.decoded_text->find('\n'); size_t newline_position = data_buffer.decoded_text->find('\n');
if (newline_position != std::string::npos) if (newline_position != std::string::npos) {
{
wifi_ssid = data_buffer.decoded_text->substr(0, newline_position); wifi_ssid = data_buffer.decoded_text->substr(0, newline_position);
wifi_password = data_buffer.decoded_text->substr(newline_position + 1); wifi_password = data_buffer.decoded_text->substr(newline_position + 1);
ESP_LOGI(kLogTag, "WiFi SSID: %s, Password: %s", wifi_ssid.c_str(), wifi_password.c_str()); ESP_LOGI(kLogTag, "WiFi SSID: %s, Password: %s", wifi_ssid.c_str(), wifi_password.c_str());
} } else {
else
{
ESP_LOGE(kLogTag, "Invalid data format, no newline character found"); ESP_LOGE(kLogTag, "Invalid data format, no newline character found");
continue; continue;
} }
if (wifi_ap->ConnectToWifi(wifi_ssid, wifi_password)) if (wifi_ap->ConnectToWifi(wifi_ssid, wifi_password)) {
{
wifi_ap->Save(wifi_ssid, wifi_password); // Save WiFi credentials wifi_ap->Save(wifi_ssid, wifi_password); // Save WiFi credentials
esp_restart(); // Restart device to apply new WiFi configuration esp_restart(); // Restart device to apply new WiFi configuration
} } else {
else
{
ESP_LOGE(kLogTag, "Failed to connect to WiFi with received credentials"); ESP_LOGE(kLogTag, "Failed to connect to WiFi with received credentials");
} }
data_buffer.decoded_text.reset(); // Clear processed data data_buffer.decoded_text.reset(); // Clear processed data
@@ -115,8 +114,7 @@ namespace audio_wifi_config
// FrequencyDetector implementation // FrequencyDetector implementation
FrequencyDetector::FrequencyDetector(float frequency, size_t window_size) FrequencyDetector::FrequencyDetector(float frequency, size_t window_size)
: frequency_(frequency), window_size_(window_size) : frequency_(frequency), window_size_(window_size) {
{
frequency_bin_ = std::floor(frequency_ * static_cast<float>(window_size_)); frequency_bin_ = std::floor(frequency_ * static_cast<float>(window_size_));
angular_frequency_ = 2.0f * M_PI * frequency_; angular_frequency_ = 2.0f * M_PI * frequency_;
cos_coefficient_ = std::cos(angular_frequency_); cos_coefficient_ = std::cos(angular_frequency_);
@@ -128,17 +126,14 @@ namespace audio_wifi_config
state_buffer_.push_back(0.0f); state_buffer_.push_back(0.0f);
} }
void FrequencyDetector::Reset() void FrequencyDetector::Reset() {
{
state_buffer_.clear(); state_buffer_.clear();
state_buffer_.push_back(0.0f); state_buffer_.push_back(0.0f);
state_buffer_.push_back(0.0f); state_buffer_.push_back(0.0f);
} }
void FrequencyDetector::ProcessSample(float sample) void FrequencyDetector::ProcessSample(float sample) {
{ if (state_buffer_.size() < 2) {
if (state_buffer_.size() < 2)
{
return; return;
} }
@@ -153,10 +148,8 @@ namespace audio_wifi_config
state_buffer_.push_back(s_current); // Add new S[0] state_buffer_.push_back(s_current); // Add new S[0]
} }
float FrequencyDetector::GetAmplitude() const float FrequencyDetector::GetAmplitude() const {
{ if (state_buffer_.size() < 2) {
if (state_buffer_.size() < 2)
{
return 0.0f; return 0.0f;
} }
@@ -172,10 +165,8 @@ namespace audio_wifi_config
// AudioSignalProcessor implementation // AudioSignalProcessor implementation
AudioSignalProcessor::AudioSignalProcessor(size_t sample_rate, size_t mark_frequency, size_t space_frequency, AudioSignalProcessor::AudioSignalProcessor(size_t sample_rate, size_t mark_frequency, size_t space_frequency,
size_t bit_rate, size_t window_size) size_t bit_rate, size_t window_size)
: input_buffer_size_(window_size), output_sample_count_(0) : input_buffer_size_(window_size), output_sample_count_(0) {
{ if (sample_rate % bit_rate != 0) {
if (sample_rate % bit_rate != 0)
{
// On ESP32 we can continue execution, but log the error // On ESP32 we can continue execution, but log the error
ESP_LOGW(kLogTag, "Sample rate %zu is not divisible by bit rate %zu", sample_rate, bit_rate); ESP_LOGW(kLogTag, "Sample rate %zu is not divisible by bit rate %zu", sample_rate, bit_rate);
} }
@@ -189,28 +180,21 @@ namespace audio_wifi_config
samples_per_bit_ = sample_rate / bit_rate; // Number of samples per bit samples_per_bit_ = sample_rate / bit_rate; // Number of samples per bit
} }
std::vector<float> AudioSignalProcessor::ProcessAudioSamples(const std::vector<float> &samples) std::vector<float> AudioSignalProcessor::ProcessAudioSamples(const std::vector<float> &samples) {
{
std::vector<float> result; std::vector<float> result;
for (float sample : samples) for (float sample : samples) {
{ if (input_buffer_.size() < input_buffer_size_) {
if (input_buffer_.size() < input_buffer_size_)
{
input_buffer_.push_back(sample); // Just add, don't process yet input_buffer_.push_back(sample); // Just add, don't process yet
} } else {
else
{
// Input buffer is full, process the data // Input buffer is full, process the data
input_buffer_.pop_front(); // Remove oldest sample input_buffer_.pop_front(); // Remove oldest sample
input_buffer_.push_back(sample); // Add new sample input_buffer_.push_back(sample); // Add new sample
output_sample_count_++; output_sample_count_++;
if (output_sample_count_ >= samples_per_bit_) if (output_sample_count_ >= samples_per_bit_) {
{
// Process all samples in the window using Goertzel algorithm // Process all samples in the window using Goertzel algorithm
for (float window_sample : input_buffer_) for (float window_sample : input_buffer_) {
{
mark_detector_->ProcessSample(window_sample); mark_detector_->ProcessSample(window_sample);
space_detector_->ProcessSample(window_sample); space_detector_->ProcessSample(window_sample);
} }
@@ -239,8 +223,7 @@ namespace audio_wifi_config
: current_state_(DataReceptionState::kInactive), : current_state_(DataReceptionState::kInactive),
start_of_transmission_(kDefaultStartTransmissionPattern), start_of_transmission_(kDefaultStartTransmissionPattern),
end_of_transmission_(kDefaultEndTransmissionPattern), end_of_transmission_(kDefaultEndTransmissionPattern),
enable_checksum_validation_(true) enable_checksum_validation_(true) {
{
identifier_buffer_size_ = std::max(start_of_transmission_.size(), end_of_transmission_.size()); identifier_buffer_size_ = std::max(start_of_transmission_.size(), end_of_transmission_.size());
max_bit_buffer_size_ = 776; // Preset bit buffer size, 776 bits = (32 + 1 + 63 + 1) * 8 = 776 max_bit_buffer_size_ = 776; // Preset bit buffer size, 776 bits = (32 + 1 + 63 + 1) * 8 = 776
@@ -252,48 +235,39 @@ namespace audio_wifi_config
: current_state_(DataReceptionState::kInactive), : current_state_(DataReceptionState::kInactive),
start_of_transmission_(start_identifier), start_of_transmission_(start_identifier),
end_of_transmission_(end_identifier), end_of_transmission_(end_identifier),
enable_checksum_validation_(enable_checksum) enable_checksum_validation_(enable_checksum) {
{
identifier_buffer_size_ = std::max(start_of_transmission_.size(), end_of_transmission_.size()); identifier_buffer_size_ = std::max(start_of_transmission_.size(), end_of_transmission_.size());
max_bit_buffer_size_ = max_byte_size * 8; // Bit buffer size in bytes max_bit_buffer_size_ = max_byte_size * 8; // Bit buffer size in bytes
bit_buffer_.reserve(max_bit_buffer_size_); bit_buffer_.reserve(max_bit_buffer_size_);
} }
uint8_t AudioDataBuffer::CalculateChecksum(const std::string &text) uint8_t AudioDataBuffer::CalculateChecksum(const std::string &text) {
{
uint8_t checksum = 0; uint8_t checksum = 0;
for (char character : text) for (char character : text) {
{
checksum += static_cast<uint8_t>(character); checksum += static_cast<uint8_t>(character);
} }
return checksum; return checksum;
} }
void AudioDataBuffer::ClearBuffers() void AudioDataBuffer::ClearBuffers() {
{
identifier_buffer_.clear(); identifier_buffer_.clear();
bit_buffer_.clear(); bit_buffer_.clear();
} }
bool AudioDataBuffer::ProcessProbabilityData(const std::vector<float> &probabilities, float threshold) bool AudioDataBuffer::ProcessProbabilityData(const std::vector<float> &probabilities, float threshold) {
{ for (float probability : probabilities) {
for (float probability : probabilities)
{
uint8_t bit = (probability > threshold) ? 1 : 0; uint8_t bit = (probability > threshold) ? 1 : 0;
if (identifier_buffer_.size() >= identifier_buffer_size_) if (identifier_buffer_.size() >= identifier_buffer_size_) {
{
identifier_buffer_.pop_front(); // Maintain buffer size identifier_buffer_.pop_front(); // Maintain buffer size
} }
identifier_buffer_.push_back(bit); identifier_buffer_.push_back(bit);
// Process received bit based on state machine // Process received bit based on state machine
switch (current_state_) switch (current_state_) {
{
case DataReceptionState::kInactive: case DataReceptionState::kInactive:
if (identifier_buffer_.size() >= start_of_transmission_.size()) if (identifier_buffer_.size() >= start_of_transmission_.size()) {
{
current_state_ = DataReceptionState::kWaiting; // Enter waiting state current_state_ = DataReceptionState::kWaiting; // Enter waiting state
ESP_LOGI(kLogTag, "Entering Waiting state"); ESP_LOGI(kLogTag, "Entering Waiting state");
} }
@@ -301,8 +275,7 @@ namespace audio_wifi_config
case DataReceptionState::kWaiting: case DataReceptionState::kWaiting:
// Waiting state, possibly waiting for transmission end // Waiting state, possibly waiting for transmission end
if (identifier_buffer_.size() >= start_of_transmission_.size()) if (identifier_buffer_.size() >= start_of_transmission_.size()) {
{
std::vector<uint8_t> identifier_snapshot(identifier_buffer_.begin(), identifier_buffer_.end()); std::vector<uint8_t> identifier_snapshot(identifier_buffer_.begin(), identifier_buffer_.end());
if (identifier_snapshot == start_of_transmission_) if (identifier_snapshot == start_of_transmission_)
{ {
@@ -315,11 +288,9 @@ namespace audio_wifi_config
case DataReceptionState::kReceiving: case DataReceptionState::kReceiving:
bit_buffer_.push_back(bit); bit_buffer_.push_back(bit);
if (identifier_buffer_.size() >= end_of_transmission_.size()) if (identifier_buffer_.size() >= end_of_transmission_.size()) {
{
std::vector<uint8_t> identifier_snapshot(identifier_buffer_.begin(), identifier_buffer_.end()); std::vector<uint8_t> identifier_snapshot(identifier_buffer_.begin(), identifier_buffer_.end());
if (identifier_snapshot == end_of_transmission_) if (identifier_snapshot == end_of_transmission_) {
{
current_state_ = DataReceptionState::kInactive; // Enter inactive state current_state_ = DataReceptionState::kInactive; // Enter inactive state
// Convert bits to bytes // Convert bits to bytes
@@ -328,22 +299,18 @@ namespace audio_wifi_config
uint8_t received_checksum = 0; uint8_t received_checksum = 0;
size_t minimum_length = 0; size_t minimum_length = 0;
if (enable_checksum_validation_) if (enable_checksum_validation_) {
{
// If checksum is required, last byte is checksum // If checksum is required, last byte is checksum
minimum_length = 1 + start_of_transmission_.size() / 8; minimum_length = 1 + start_of_transmission_.size() / 8;
if (bytes.size() >= minimum_length) if (bytes.size() >= minimum_length)
{ {
received_checksum = bytes[bytes.size() - start_of_transmission_.size() / 8 - 1]; received_checksum = bytes[bytes.size() - start_of_transmission_.size() / 8 - 1];
} }
} } else {
else
{
minimum_length = start_of_transmission_.size() / 8; minimum_length = start_of_transmission_.size() / 8;
} }
if (bytes.size() < minimum_length) if (bytes.size() < minimum_length) {
{
ClearBuffers(); ClearBuffers();
ESP_LOGW(kLogTag, "Data too short, clearing buffer"); ESP_LOGW(kLogTag, "Data too short, clearing buffer");
return false; // Data too short, return failure return false; // Data too short, return failure
@@ -356,11 +323,9 @@ namespace audio_wifi_config
std::string result(text_bytes.begin(), text_bytes.end()); std::string result(text_bytes.begin(), text_bytes.end());
// Validate checksum if required // Validate checksum if required
if (enable_checksum_validation_) if (enable_checksum_validation_) {
{
uint8_t calculated_checksum = CalculateChecksum(result); uint8_t calculated_checksum = CalculateChecksum(result);
if (calculated_checksum != received_checksum) if (calculated_checksum != received_checksum) {
{
// Checksum mismatch // Checksum mismatch
ESP_LOGW(kLogTag, "Checksum mismatch: expected %d, got %d", ESP_LOGW(kLogTag, "Checksum mismatch: expected %d, got %d",
received_checksum, calculated_checksum); received_checksum, calculated_checksum);
@@ -372,9 +337,7 @@ namespace audio_wifi_config
ClearBuffers(); ClearBuffers();
decoded_text = result; decoded_text = result;
return true; // Return success return true; // Return success
} } else if (bit_buffer_.size() >= max_bit_buffer_size_) {
else if (bit_buffer_.size() >= max_bit_buffer_size_)
{
// If not end identifier and bit buffer is full, reset // If not end identifier and bit buffer is full, reset
ClearBuffers(); ClearBuffers();
ESP_LOGW(kLogTag, "Buffer overflow, clearing buffer"); ESP_LOGW(kLogTag, "Buffer overflow, clearing buffer");
@@ -388,19 +351,16 @@ namespace audio_wifi_config
return false; return false;
} }
std::vector<uint8_t> AudioDataBuffer::ConvertBitsToBytes(const std::vector<uint8_t> &bits) const std::vector<uint8_t> AudioDataBuffer::ConvertBitsToBytes(const std::vector<uint8_t> &bits) const {
{
std::vector<uint8_t> bytes; std::vector<uint8_t> bytes;
// Ensure number of bits is a multiple of 8 // Ensure number of bits is a multiple of 8
size_t complete_bytes_count = bits.size() / 8; size_t complete_bytes_count = bits.size() / 8;
bytes.reserve(complete_bytes_count); bytes.reserve(complete_bytes_count);
for (size_t i = 0; i < complete_bytes_count; ++i) for (size_t i = 0; i < complete_bytes_count; ++i) {
{
uint8_t byte_value = 0; uint8_t byte_value = 0;
for (size_t j = 0; j < 8; ++j) for (size_t j = 0; j < 8; ++j) {
{
byte_value |= bits[i * 8 + j] << (7 - j); byte_value |= bits[i * 8 + j] << (7 - j);
} }
bytes.push_back(byte_value); bytes.push_back(byte_value);

View File

@@ -19,7 +19,8 @@ const size_t kWindowSize = 64;
namespace audio_wifi_config namespace audio_wifi_config
{ {
// Main function to receive WiFi credentials through audio signal // Main function to receive WiFi credentials through audio signal
void ReceiveWifiCredentialsFromAudio(Application *app, WifiConfigurationAp *wifi_ap); void ReceiveWifiCredentialsFromAudio(Application *app, WifiConfigurationAp *wifi_ap, Display *display,
size_t input_channels = 1);
/** /**
* Goertzel algorithm implementation for single frequency detection * Goertzel algorithm implementation for single frequency detection

View File

@@ -52,7 +52,14 @@ void WifiBoard::EnterWifiConfigMode() {
application.Alert(Lang::Strings::WIFI_CONFIG_MODE, hint.c_str(), "", Lang::Sounds::P3_WIFICONFIG); application.Alert(Lang::Strings::WIFI_CONFIG_MODE, hint.c_str(), "", Lang::Sounds::P3_WIFICONFIG);
#if CONFIG_USE_ACOUSTIC_WIFI_PROVISIONING #if CONFIG_USE_ACOUSTIC_WIFI_PROVISIONING
audio_wifi_config::ReceiveWifiCredentialsFromAudio(&application, &wifi_ap); auto display = Board::GetInstance().GetDisplay();
auto codec = Board::GetInstance().GetAudioCodec();
int channel = 1;
if (codec) {
channel = codec->input_channels();
}
ESP_LOGI(TAG, "Start receiving WiFi credentials from audio, input channels: %d", channel);
audio_wifi_config::ReceiveWifiCredentialsFromAudio(&application, &wifi_ap, display, channel);
#endif #endif
// Wait forever until reset after configuration // Wait forever until reset after configuration