update version to 0.2.0

This commit is contained in:
Terrence
2024-09-10 16:04:28 +08:00
parent 3a97934b15
commit 0fc9f551ec
18 changed files with 63 additions and 834 deletions

1
.gitignore vendored
View File

@@ -7,3 +7,4 @@ build/
sdkconfig.old
sdkconfig
dependencies.lock
.env

View File

@@ -4,7 +4,7 @@
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
set(PROJECT_VER "0.1.1")
set(PROJECT_VER "0.2.0")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(xiaozhi)

View File

@@ -6,9 +6,8 @@
#include "model_path.h"
#include "SystemInfo.h"
#include "cJSON.h"
#include "silk_resampler.h"
#define TAG "application"
#define TAG "Application"
Application::Application() {
@@ -29,7 +28,7 @@ Application::Application() {
opus_encoder_.Configure(CONFIG_AUDIO_INPUT_SAMPLE_RATE, 1);
opus_decoder_ = opus_decoder_create(opus_decode_sample_rate_, 1, NULL);
if (opus_decode_sample_rate_ != CONFIG_AUDIO_OUTPUT_SAMPLE_RATE) {
assert(0 == silk_resampler_init(&resampler_state_, opus_decode_sample_rate_, CONFIG_AUDIO_OUTPUT_SAMPLE_RATE, 1));
opus_resampler_.Configure(opus_decode_sample_rate_, CONFIG_AUDIO_OUTPUT_SAMPLE_RATE);
}
}
@@ -461,7 +460,7 @@ void Application::AudioDecodeTask() {
if (opus_decode_sample_rate_ != CONFIG_AUDIO_OUTPUT_SAMPLE_RATE) {
int target_size = frame_size * CONFIG_AUDIO_OUTPUT_SAMPLE_RATE / opus_decode_sample_rate_;
std::vector<int16_t> resampled(target_size);
assert(0 == silk_resampler(&resampler_state_, resampled.data(), packet->pcm.data(), frame_size));
opus_resampler_.Process(packet->pcm.data(), frame_size, resampled.data(), target_size);
packet->pcm = std::move(resampled);
}
}
@@ -479,7 +478,7 @@ void Application::SetDecodeSampleRate(int sample_rate) {
opus_decode_sample_rate_ = sample_rate;
opus_decoder_ = opus_decoder_create(opus_decode_sample_rate_, 1, NULL);
if (opus_decode_sample_rate_ != CONFIG_AUDIO_OUTPUT_SAMPLE_RATE) {
assert(0 == silk_resampler_init(&resampler_state_, opus_decode_sample_rate_, CONFIG_AUDIO_OUTPUT_SAMPLE_RATE, 1));
opus_resampler_.Configure(opus_decode_sample_rate_, CONFIG_AUDIO_OUTPUT_SAMPLE_RATE);
}
}

View File

@@ -3,6 +3,7 @@
#include "AudioDevice.h"
#include "OpusEncoder.h"
#include "OpusResampler.h"
#include "WebSocketClient.h"
#include "FirmwareUpgrade.h"
@@ -74,7 +75,7 @@ private:
int opus_duration_ms_ = 60;
int opus_decode_sample_rate_ = CONFIG_AUDIO_OUTPUT_SAMPLE_RATE;
silk_resampler_state_struct resampler_state_;
OpusResampler opus_resampler_;
TaskHandle_t wake_word_encode_task_ = nullptr;
StaticTask_t wake_word_encode_task_buffer_;

View File

@@ -1,12 +1,7 @@
set(SOURCES "AudioDevice.cc"
"SystemInfo.cc"
"SystemReset.cc"
"WebSocketClient.cc"
"OpusEncoder.cc"
"Application.cc"
"main.cc"
"FirmwareUpgrade.cc"
)
idf_component_register(SRCS ${SOURCES}

View File

@@ -1,174 +0,0 @@
#include "FirmwareUpgrade.h"
#include "SystemInfo.h"
#include "esp_log.h"
#include "esp_ota_ops.h"
#include "esp_https_ota.h"
#include "esp_partition.h"
#include "esp_http_client.h"
#include "esp_crt_bundle.h"
#include "cJSON.h"
#define TAG "FirmwareUpgrade"
FirmwareUpgrade::FirmwareUpgrade() {
}
FirmwareUpgrade::~FirmwareUpgrade() {
}
void FirmwareUpgrade::CheckVersion() {
std::string current_version = esp_app_get_description()->version;
ESP_LOGI(TAG, "Current version: %s", current_version.c_str());
// Get device info and request the latest firmware from the server
std::string device_info = SystemInfo::GetJsonString();
esp_http_client_config_t config = {};
config.url = CONFIG_OTA_UPGRADE_URL;
config.crt_bundle_attach = esp_crt_bundle_attach;
esp_http_client_handle_t client = esp_http_client_init(&config);
esp_http_client_set_method(client, HTTP_METHOD_POST);
esp_http_client_set_header(client, "Content-Type", "application/json");
esp_http_client_set_header(client, "Device-Id", SystemInfo::GetMacAddress().c_str());
esp_err_t err = esp_http_client_open(client, device_info.length());
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to perform HTTP request: %s", esp_err_to_name(err));
esp_http_client_cleanup(client);
return;
}
auto written = esp_http_client_write(client, device_info.data(), device_info.length());
if (written < 0) {
ESP_LOGE(TAG, "Failed to write request body: %s", esp_err_to_name(err));
esp_http_client_cleanup(client);
return;
}
int content_length = esp_http_client_fetch_headers(client);
if (content_length <= 0) {
ESP_LOGE(TAG, "Failed to fetch headers");
esp_http_client_cleanup(client);
return;
}
std::string response;
response.resize(content_length);
int ret = esp_http_client_read_response(client, (char*)response.data(), content_length);
if (ret <= 0) {
ESP_LOGE(TAG, "Failed to read response content_length=%d", content_length);
esp_http_client_cleanup(client);
return;
}
esp_http_client_close(client);
esp_http_client_cleanup(client);
// Response: { "firmware": { "version": "1.0.0", "url": "http://" } }
// Parse the JSON response and check if the version is newer
// If it is, set has_new_version_ to true and store the new version and URL
cJSON *root = cJSON_Parse(response.c_str());
if (root == NULL) {
ESP_LOGE(TAG, "Failed to parse JSON response");
return;
}
cJSON *firmware = cJSON_GetObjectItem(root, "firmware");
if (firmware == NULL) {
ESP_LOGE(TAG, "Failed to get firmware object");
cJSON_Delete(root);
return;
}
cJSON *version = cJSON_GetObjectItem(firmware, "version");
if (version == NULL) {
ESP_LOGE(TAG, "Failed to get version object");
cJSON_Delete(root);
return;
}
cJSON *url = cJSON_GetObjectItem(firmware, "url");
if (url == NULL) {
ESP_LOGE(TAG, "Failed to get url object");
cJSON_Delete(root);
return;
}
new_version_ = version->valuestring;
new_version_url_ = url->valuestring;
cJSON_Delete(root);
has_new_version_ = new_version_ != current_version;
if (has_new_version_) {
ESP_LOGI(TAG, "New version available: %s", new_version_.c_str());
} else {
ESP_LOGI(TAG, "No new version available");
}
return;
}
void FirmwareUpgrade::MarkValid() {
auto partition = esp_ota_get_running_partition();
if (strcmp(partition->label, "factory") == 0) {
ESP_LOGI(TAG, "Running from factory partition, skipping");
return;
}
ESP_LOGI(TAG, "Running partition: %s", partition->label);
esp_ota_img_states_t state;
if (esp_ota_get_state_partition(partition, &state) != ESP_OK) {
ESP_LOGE(TAG, "Failed to get state of partition");
return;
}
if (state == ESP_OTA_IMG_PENDING_VERIFY) {
ESP_LOGI(TAG, "Marking firmware as valid");
esp_ota_mark_app_valid_cancel_rollback();
}
}
void FirmwareUpgrade::Upgrade(std::string firmware_url) {
ESP_LOGI(TAG, "Upgrading firmware from %s", firmware_url.c_str());
esp_http_client_config_t config = {};
config.url = firmware_url.c_str();
config.crt_bundle_attach = esp_crt_bundle_attach;
config.event_handler = [](esp_http_client_event_t *evt) {
switch (evt->event_id) {
case HTTP_EVENT_ERROR:
ESP_LOGD(TAG, "HTTP_EVENT_ERROR");
break;
case HTTP_EVENT_ON_CONNECTED:
ESP_LOGD(TAG, "HTTP_EVENT_ON_CONNECTED");
break;
case HTTP_EVENT_HEADER_SENT:
ESP_LOGD(TAG, "HTTP_EVENT_HEADER_SENT");
break;
case HTTP_EVENT_ON_HEADER:
ESP_LOGD(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value);
break;
case HTTP_EVENT_ON_DATA:
ESP_LOGD(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
break;
case HTTP_EVENT_ON_FINISH:
ESP_LOGD(TAG, "HTTP_EVENT_ON_FINISH");
break;
case HTTP_EVENT_DISCONNECTED:
ESP_LOGD(TAG, "HTTP_EVENT_DISCONNECTED");
break;
case HTTP_EVENT_REDIRECT:
ESP_LOGD(TAG, "HTTP_EVENT_REDIRECT");
break;
}
return ESP_OK;
};
config.keep_alive_enable = true;
esp_https_ota_config_t ota_config = {};
ota_config.http_config = &config;
esp_err_t err = esp_https_ota(&ota_config);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to upgrade firmware: %s", esp_err_to_name(err));
return;
}
ESP_LOGI(TAG, "Firmware upgrade successful, rebooting in 3 seconds...");
vTaskDelay(pdMS_TO_TICKS(3000));
esp_restart();
}

View File

@@ -1,24 +0,0 @@
#ifndef _FIRMWARE_UPGRADE_H
#define _FIRMWARE_UPGRADE_H
#include <string>
class FirmwareUpgrade {
public:
FirmwareUpgrade();
~FirmwareUpgrade();
void CheckVersion();
bool HasNewVersion() { return has_new_version_; }
void StartUpgrade() { Upgrade(new_version_url_); }
void MarkValid();
private:
bool has_new_version_ = false;
std::string new_version_;
std::string new_version_url_;
void Upgrade(std::string firmware_url);
};
#endif // _FIRMWARE_UPGRADE_H

View File

@@ -1,11 +1,5 @@
menu "Xiaozhi Assistant"
config OTA_UPGRADE_URL
string "OTA Upgrade URL"
default "https://"
help
The application will access this URL to check for updates when it starts and every 24 hours.
config WEBSOCKET_URL
string "Websocket URL"
default "wss://"

View File

@@ -1,72 +0,0 @@
#include "OpusEncoder.h"
#include "esp_err.h"
#include "esp_log.h"
#define TAG "OpusEncoder"
OpusEncoder::OpusEncoder() {
}
OpusEncoder::~OpusEncoder() {
if (audio_enc_ != nullptr) {
opus_encoder_destroy(audio_enc_);
}
}
void OpusEncoder::Configure(int sample_rate, int channels, int duration_ms) {
if (audio_enc_ != nullptr) {
opus_encoder_destroy(audio_enc_);
audio_enc_ = nullptr;
}
int error;
audio_enc_ = opus_encoder_create(sample_rate, channels, OPUS_APPLICATION_VOIP, &error);
if (audio_enc_ == nullptr) {
ESP_LOGE(TAG, "Failed to create audio encoder, error code: %d", error);
return;
}
// Set DTX
opus_encoder_ctl(audio_enc_, OPUS_SET_DTX(1));
SetComplexity(5);
frame_size_ = sample_rate / 1000 * duration_ms;
out_buffer_.resize(sample_rate * channels * sizeof(int16_t));
}
void OpusEncoder::SetComplexity(int complexity) {
if (audio_enc_ != nullptr) {
opus_encoder_ctl(audio_enc_, OPUS_SET_COMPLEXITY(complexity));
}
}
void OpusEncoder::Encode(const iovec pcm, std::function<void(const iovec opus)> handler) {
if (audio_enc_ == nullptr) {
ESP_LOGE(TAG, "Audio encoder is not configured");
return;
}
auto pcm_data = (int16_t*)pcm.iov_base;
in_buffer_.insert(in_buffer_.end(), pcm_data, pcm_data + pcm.iov_len / sizeof(int16_t));
while (in_buffer_.size() >= frame_size_) {
auto ret = opus_encode(audio_enc_, in_buffer_.data(), frame_size_, out_buffer_.data(), out_buffer_.size());
if (ret < 0) {
ESP_LOGE(TAG, "Failed to encode audio, error code: %ld", ret);
return;
}
if (handler != nullptr) {
handler(iovec { out_buffer_.data(), (size_t)ret });
}
in_buffer_.erase(in_buffer_.begin(), in_buffer_.begin() + frame_size_);
}
}
void OpusEncoder::ResetState() {
if (audio_enc_ != nullptr) {
opus_encoder_ctl(audio_enc_, OPUS_RESET_STATE);
}
in_buffer_.clear();
}

View File

@@ -1,31 +0,0 @@
#ifndef _OPUS_ENCODER_H_
#define _OPUS_ENCODER_H_
#include <functional>
#include <string>
#include <vector>
#include <memory>
#include "lwip/sockets.h"
#include "opus.h"
class OpusEncoder {
public:
OpusEncoder();
~OpusEncoder();
void Configure(int sample_rate, int channels, int duration_ms = 60);
void SetComplexity(int complexity);
void Encode(const iovec pcm, std::function<void(const iovec opus)> handler);
bool IsBufferEmpty() const { return in_buffer_.empty(); }
void ResetState();
private:
struct OpusEncoder* audio_enc_ = nullptr;
int frame_size_;
std::vector<uint8_t> out_buffer_;
std::vector<int16_t> in_buffer_;
};
#endif // _OPUS_ENCODER_H_

View File

@@ -1,234 +0,0 @@
#include "SystemInfo.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_flash.h"
#include "esp_mac.h"
#include "esp_chip_info.h"
#include "esp_system.h"
#include "esp_partition.h"
#include "esp_app_desc.h"
#include "esp_psram.h"
#include "esp_wifi.h"
#include "esp_ota_ops.h"
#define TAG "SystemInfo"
size_t SystemInfo::GetFlashSize() {
uint32_t flash_size;
if (esp_flash_get_size(NULL, &flash_size) != ESP_OK) {
ESP_LOGE(TAG, "Failed to get flash size");
return 0;
}
return (size_t)flash_size;
}
size_t SystemInfo::GetMinimumFreeHeapSize() {
return esp_get_minimum_free_heap_size();
}
size_t SystemInfo::GetFreeHeapSize() {
return esp_get_free_heap_size();
}
std::string SystemInfo::GetMacAddress() {
uint8_t mac[6];
esp_read_mac(mac, ESP_MAC_WIFI_STA);
char mac_str[18];
snprintf(mac_str, sizeof(mac_str), "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
return std::string(mac_str);
}
std::string SystemInfo::GetChipModelName() {
return std::string(CONFIG_IDF_TARGET);
}
std::string SystemInfo::GetJsonString() {
/*
{
"flash_size": 4194304,
"psram_size": 0,
"minimum_free_heap_size": 123456,
"mac_address": "00:00:00:00:00:00",
"chip_model_name": "esp32s3",
"chip_info": {
"model": 1,
"cores": 2,
"revision": 0,
"features": 0
},
"application": {
"name": "my-app",
"version": "1.0.0",
"compile_time": "2021-01-01T00:00:00Z"
"idf_version": "4.2-dev"
"elf_sha256": ""
},
"partition_table": [
"app": {
"label": "app",
"type": 1,
"subtype": 2,
"address": 0x10000,
"size": 0x100000
}
],
"ota": {
"label": "ota_0"
},
"wifi": {
"ssid": "my-wifi",
"rss": -50,
"channel": 6
}
}
*/
std::string json = "{";
json += "\"flash_size\":" + std::to_string(GetFlashSize()) + ",";
json += "\"psram_size\":" + std::to_string(esp_psram_get_size()) + ",";
json += "\"minimum_free_heap_size\":" + std::to_string(GetMinimumFreeHeapSize()) + ",";
json += "\"mac_address\":\"" + GetMacAddress() + "\",";
json += "\"chip_model_name\":\"" + GetChipModelName() + "\",";
json += "\"chip_info\":{";
esp_chip_info_t chip_info;
esp_chip_info(&chip_info);
json += "\"model\":" + std::to_string(chip_info.model) + ",";
json += "\"cores\":" + std::to_string(chip_info.cores) + ",";
json += "\"revision\":" + std::to_string(chip_info.revision) + ",";
json += "\"features\":" + std::to_string(chip_info.features);
json += "},";
json += "\"application\":{";
auto app_desc = esp_app_get_description();
json += "\"name\":\"" + std::string(app_desc->project_name) + "\",";
json += "\"version\":\"" + std::string(app_desc->version) + "\",";
json += "\"compile_time\":\"" + std::string(app_desc->date) + "T" + std::string(app_desc->time) + "Z\",";
json += "\"idf_version\":\"" + std::string(app_desc->idf_ver) + "\",";
char sha256_str[65];
for (int i = 0; i < 32; i++) {
snprintf(sha256_str + i * 2, sizeof(sha256_str) - i * 2, "%02x", app_desc->app_elf_sha256[i]);
}
json += "\"elf_sha256\":\"" + std::string(sha256_str) + "\"";
json += "},";
json += "\"partition_table\": [";
esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, NULL);
while (it) {
const esp_partition_t *partition = esp_partition_get(it);
json += "{";
json += "\"label\":\"" + std::string(partition->label) + "\",";
json += "\"type\":" + std::to_string(partition->type) + ",";
json += "\"subtype\":" + std::to_string(partition->subtype) + ",";
json += "\"address\":" + std::to_string(partition->address) + ",";
json += "\"size\":" + std::to_string(partition->size);
json += "},";
it = esp_partition_next(it);
}
json.pop_back(); // Remove the last comma
json += "],";
json += "\"ota\":{";
auto ota_partition = esp_ota_get_running_partition();
json += "\"label\":\"" + std::string(ota_partition->label) + "\"";
json += "},";
wifi_ap_record_t ap_info;
esp_wifi_sta_get_ap_info(&ap_info);
json += "\"wifi\":{";
json += "\"ssid\":\"" + std::string(reinterpret_cast<char*>(ap_info.ssid)) + "\",";
json += "\"rssi\":" + std::to_string(ap_info.rssi) + ",";
json += "\"channel\":" + std::to_string(ap_info.primary);
json += "}";
// Close the JSON object
json += "}";
return json;
}
esp_err_t SystemInfo::PrintRealTimeStats(TickType_t xTicksToWait) {
#define ARRAY_SIZE_OFFSET 5
TaskStatus_t *start_array = NULL, *end_array = NULL;
UBaseType_t start_array_size, end_array_size;
configRUN_TIME_COUNTER_TYPE start_run_time, end_run_time;
esp_err_t ret;
uint32_t total_elapsed_time;
//Allocate array to store current task states
start_array_size = uxTaskGetNumberOfTasks() + ARRAY_SIZE_OFFSET;
start_array = (TaskStatus_t*)malloc(sizeof(TaskStatus_t) * start_array_size);
if (start_array == NULL) {
ret = ESP_ERR_NO_MEM;
goto exit;
}
//Get current task states
start_array_size = uxTaskGetSystemState(start_array, start_array_size, &start_run_time);
if (start_array_size == 0) {
ret = ESP_ERR_INVALID_SIZE;
goto exit;
}
vTaskDelay(xTicksToWait);
//Allocate array to store tasks states post delay
end_array_size = uxTaskGetNumberOfTasks() + ARRAY_SIZE_OFFSET;
end_array = (TaskStatus_t*)malloc(sizeof(TaskStatus_t) * end_array_size);
if (end_array == NULL) {
ret = ESP_ERR_NO_MEM;
goto exit;
}
//Get post delay task states
end_array_size = uxTaskGetSystemState(end_array, end_array_size, &end_run_time);
if (end_array_size == 0) {
ret = ESP_ERR_INVALID_SIZE;
goto exit;
}
//Calculate total_elapsed_time in units of run time stats clock period.
total_elapsed_time = (end_run_time - start_run_time);
if (total_elapsed_time == 0) {
ret = ESP_ERR_INVALID_STATE;
goto exit;
}
printf("| Task | Run Time | Percentage\n");
//Match each task in start_array to those in the end_array
for (int i = 0; i < start_array_size; i++) {
int k = -1;
for (int j = 0; j < end_array_size; j++) {
if (start_array[i].xHandle == end_array[j].xHandle) {
k = j;
//Mark that task have been matched by overwriting their handles
start_array[i].xHandle = NULL;
end_array[j].xHandle = NULL;
break;
}
}
//Check if matching task found
if (k >= 0) {
uint32_t task_elapsed_time = end_array[k].ulRunTimeCounter - start_array[i].ulRunTimeCounter;
uint32_t percentage_time = (task_elapsed_time * 100UL) / (total_elapsed_time * CONFIG_FREERTOS_NUMBER_OF_CORES);
printf("| %-16s | %8lu | %4lu%%\n", start_array[i].pcTaskName, task_elapsed_time, percentage_time);
}
}
//Print unmatched tasks
for (int i = 0; i < start_array_size; i++) {
if (start_array[i].xHandle != NULL) {
printf("| %s | Deleted\n", start_array[i].pcTaskName);
}
}
for (int i = 0; i < end_array_size; i++) {
if (end_array[i].xHandle != NULL) {
printf("| %s | Created\n", end_array[i].pcTaskName);
}
}
ret = ESP_OK;
exit: //Common return path
free(start_array);
free(end_array);
return ret;
}

View File

@@ -1,20 +0,0 @@
#ifndef _SYSTEM_INFO_H_
#define _SYSTEM_INFO_H_
#include <string>
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
class SystemInfo {
public:
static size_t GetFlashSize();
static size_t GetMinimumFreeHeapSize();
static size_t GetFreeHeapSize();
static std::string GetMacAddress();
static std::string GetChipModelName();
static std::string GetJsonString();
static esp_err_t PrintRealTimeStats(TickType_t xTicksToWait);
};
#endif // _SYSTEM_INFO_H_

View File

@@ -1,132 +0,0 @@
#include "WebSocketClient.h"
#include <cstring>
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_crt_bundle.h"
#define TAG "WebSocket"
#define TIMEOUT_TICKS pdMS_TO_TICKS(3000)
WebSocketClient::WebSocketClient(bool auto_reconnect) {
event_group_ = xEventGroupCreate();
esp_websocket_client_config_t config = {};
config.task_prio = 1;
config.disable_auto_reconnect = !auto_reconnect;
config.crt_bundle_attach = esp_crt_bundle_attach;
client_ = esp_websocket_client_init(&config);
assert(client_ != NULL);
esp_websocket_register_events(client_, WEBSOCKET_EVENT_ANY, [](void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
WebSocketClient* ws = (WebSocketClient*)arg;
esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data;
switch (event_id)
{
case WEBSOCKET_EVENT_BEFORE_CONNECT:
break;
case WEBSOCKET_EVENT_CONNECTED:
if (ws->on_connected_) {
ws->on_connected_();
}
xEventGroupSetBits(ws->event_group_, WEBSOCKET_CONNECTED_BIT);
break;
case WEBSOCKET_EVENT_DISCONNECTED:
xEventGroupSetBits(ws->event_group_, WEBSOCKET_DISCONNECTED_BIT);
break;
case WEBSOCKET_EVENT_DATA:
if (data->data_len != data->payload_len) {
ESP_LOGE(TAG, "Payload segmentating is not supported, data_len: %d, payload_len: %d", data->data_len, data->payload_len);
break;
}
if (data->op_code == 8) { // Websocket close
ESP_LOGI(TAG, "Websocket closed");
if (ws->on_closed_) {
ws->on_closed_();
}
} else if (data->op_code == 9) {
// Websocket ping
} else if (data->op_code == 10) {
// Websocket pong
} else if (data->op_code == 1) {
// Websocket text
if (ws->on_data_) {
ws->on_data_(data->data_ptr, data->data_len, false);
}
} else if (data->op_code == 2) {
// Websocket binary
if (ws->on_data_) {
ws->on_data_(data->data_ptr, data->data_len, true);
}
} else {
ESP_LOGI(TAG, "Unknown opcode: %d", data->op_code);
}
break;
case WEBSOCKET_EVENT_ERROR:
if (ws->on_error_) {
ws->on_error_(data->error_handle.error_type);
}
xEventGroupSetBits(ws->event_group_, WEBSOCKET_ERROR_BIT);
break;
case WEBSOCKET_EVENT_CLOSED:
break;
default:
ESP_LOGI(TAG, "Event %ld", event_id);
}
}, this);
}
WebSocketClient::~WebSocketClient() {
esp_websocket_client_close(client_, TIMEOUT_TICKS);
ESP_LOGI(TAG, "Destroying websocket client");
esp_websocket_client_destroy(client_);
}
void WebSocketClient::SetHeader(const char* key, const char* value) {
esp_websocket_client_append_header(client_, key, value);
}
bool WebSocketClient::Connect(const char* uri) {
esp_websocket_client_set_uri(client_, uri);
esp_websocket_client_start(client_);
// Wait for the connection to be established or an error
EventBits_t bits = xEventGroupWaitBits(event_group_, WEBSOCKET_CONNECTED_BIT | WEBSOCKET_ERROR_BIT, pdFALSE, pdFALSE, TIMEOUT_TICKS);
return bits & WEBSOCKET_CONNECTED_BIT;
}
void WebSocketClient::Send(const void* data, size_t len, bool binary) {
if (binary) {
esp_websocket_client_send_bin(client_, (const char*)data, len, portMAX_DELAY);
} else {
esp_websocket_client_send_text(client_, (const char*)data, len, portMAX_DELAY);
}
}
void WebSocketClient::Send(const std::string& data) {
Send(data.c_str(), data.size(), false);
}
void WebSocketClient::OnClosed(std::function<void()> callback) {
on_closed_ = callback;
}
void WebSocketClient::OnData(std::function<void(const char*, size_t, bool binary)> callback) {
on_data_ = callback;
}
void WebSocketClient::OnError(std::function<void(int)> callback) {
on_error_ = callback;
}
void WebSocketClient::OnConnected(std::function<void()> callback) {
on_connected_ = callback;
}
void WebSocketClient::OnDisconnected(std::function<void()> callback) {
on_disconnected_ = callback;
}
bool WebSocketClient::IsConnected() const {
return esp_websocket_client_is_connected(client_);
}

View File

@@ -1,40 +0,0 @@
#ifndef _WEBSOCKET_CLIENT_H_
#define _WEBSOCKET_CLIENT_H_
#include <functional>
#include <string>
#include "esp_websocket_client.h"
#include "freertos/event_groups.h"
#define WEBSOCKET_CONNECTED_BIT BIT0
#define WEBSOCKET_DISCONNECTED_BIT BIT1
#define WEBSOCKET_ERROR_BIT BIT2
class WebSocketClient {
public:
WebSocketClient(bool auto_reconnect = false);
~WebSocketClient();
void SetHeader(const char* key, const char* value);
bool IsConnected() const;
bool Connect(const char* uri);
void Send(const std::string& data);
void Send(const void* data, size_t len, bool binary = false);
void OnConnected(std::function<void()> callback);
void OnDisconnected(std::function<void()> callback);
void OnData(std::function<void(const char*, size_t, bool binary)> callback);
void OnError(std::function<void(int)> callback);
void OnClosed(std::function<void()> callback);
private:
esp_websocket_client_handle_t client_ = NULL;
EventGroupHandle_t event_group_;
std::function<void(const char*, size_t, bool binary)> on_data_;
std::function<void(int)> on_error_;
std::function<void()> on_closed_;
std::function<void()> on_connected_;
std::function<void()> on_disconnected_;
};
#endif // _WEBSOCKET_CLIENT_H_

View File

@@ -1,10 +1,10 @@
## IDF Component Manager Manifest File
dependencies:
78/esp-opus: "^1.0.2"
78/esp-builtin-led: "^1.0.0"
78/esp-wifi-connect: "^1.0.0"
espressif/esp_websocket_client: "^1.2.3"
espressif/led_strip: "*"
78/esp-ota: "^1.0.0"
78/esp-websocket: "^1.0.0"
78/esp-opus-encoder: "^1.0.0"
espressif/esp-sr: "^1.9.0"
## Required IDF version
idf:

View File

@@ -1,60 +0,0 @@
/***********************************************************************
Copyright (c) 2006-2011, Skype Limited. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
- Neither the name of Internet Society, IETF or IETF Trust, nor the
names of specific contributors, may be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
***********************************************************************/
#ifndef SILK_RESAMPLER_STRUCTS_H
#define SILK_RESAMPLER_STRUCTS_H
#ifdef __cplusplus
extern "C" {
#endif
#define SILK_RESAMPLER_MAX_FIR_ORDER 36
#define SILK_RESAMPLER_MAX_IIR_ORDER 6
typedef struct _silk_resampler_state_struct{
opus_int32 sIIR[ SILK_RESAMPLER_MAX_IIR_ORDER ]; /* this must be the first element of this struct */
union{
opus_int32 i32[ SILK_RESAMPLER_MAX_FIR_ORDER ];
opus_int16 i16[ SILK_RESAMPLER_MAX_FIR_ORDER ];
} sFIR;
opus_int16 delayBuf[ 48 ];
opus_int resampler_function;
opus_int batchSize;
opus_int32 invRatio_Q16;
opus_int FIR_Order;
opus_int FIR_Fracs;
opus_int Fs_in_kHz;
opus_int Fs_out_kHz;
opus_int inputDelay;
const opus_int16 *Coefs;
} silk_resampler_state_struct;
#ifdef __cplusplus
}
#endif
#endif /* SILK_RESAMPLER_STRUCTS_H */

View File

@@ -1,25 +0,0 @@
#ifndef _SILK_RESAMPLER_H_
#define _SILK_RESAMPLER_H_
/*!
* Initialize/reset the resampler state for a given pair of input/output sampling rates
*/
extern "C" opus_int silk_resampler_init(
silk_resampler_state_struct *S, /* I/O Resampler state */
opus_int32 Fs_Hz_in, /* I Input sampling rate (Hz) */
opus_int32 Fs_Hz_out, /* I Output sampling rate (Hz) */
opus_int forEnc /* I If 1: encoder; if 0: decoder */
);
/*!
* Resampler: convert from one sampling rate to another
*/
extern "C" opus_int silk_resampler(
silk_resampler_state_struct *S, /* I/O Resampler state */
opus_int16 out[], /* O Output signal */
const opus_int16 in[], /* I Input signal */
opus_int32 inLen /* I Number of input samples */
);
#endif // _SILK_RESAMPLER_H_

51
publish.py Normal file
View File

@@ -0,0 +1,51 @@
#! /usr/bin/env python3
from dotenv import load_dotenv
load_dotenv()
import os
import oss2
import json
def get_version():
with open('CMakeLists.txt', 'r') as f:
for line in f:
if line.startswith('set(PROJECT_VER'):
return line.split('"')[1]
return '0.0.0'
def upload_bin_to_oss(bin_path, oss_key):
auth = oss2.Auth(os.environ['OSS_ACCESS_KEY_ID'], os.environ['OSS_ACCESS_KEY_SECRET'])
bucket = oss2.Bucket(auth, os.environ['OSS_ENDPOINT'], os.environ['OSS_BUCKET_NAME'])
bucket.put_object(oss_key, open(bin_path, 'rb'))
if __name__ == '__main__':
# 获取版本号
version = get_version()
print(f'version: {version}')
# 上传 bin 文件到 OSS
upload_bin_to_oss('build/xiaozhi.bin', f'firmwares/xiaozhi-{version}.bin')
# File URL
file_url = os.path.join(os.environ['OSS_BUCKET_URL'], f'firmwares/xiaozhi-{version}.bin')
print(f'Uploaded bin to OSS: {file_url}')
firmware_json = {
"version": version,
"url": file_url
}
with open(f"build/firmware.json", "w") as f:
json.dump(firmware_json, f, indent=4)
# copy firmware.json to server
firmware_config_path = os.environ['FIRMWARE_CONFIG_PATH']
ret = os.system(f'scp build/firmware.json {firmware_config_path}')
if ret != 0:
print(f'Failed to copy firmware.json to server')
exit(1)
print(f'Copied firmware.json to server: {firmware_config_path}')