452 lines
14 KiB
C++
452 lines
14 KiB
C++
#include "device_manager.h"
|
||
#include <esp_log.h>
|
||
#include <esp_mac.h>
|
||
#include <nvs_flash.h>
|
||
#include <nvs.h>
|
||
#include <cJSON.h>
|
||
#include "board.h"
|
||
#include "server_config.h"
|
||
|
||
#define TAG "DeviceManager"
|
||
|
||
DeviceManager& DeviceManager::GetInstance() {
|
||
static DeviceManager instance;
|
||
return instance;
|
||
}
|
||
|
||
DeviceManager::DeviceManager() : is_bound_(false) {
|
||
// 初始化NVS(如果尚未初始化)
|
||
esp_err_t ret = nvs_flash_init();
|
||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||
ret = nvs_flash_init();
|
||
}
|
||
ESP_ERROR_CHECK(ret);
|
||
|
||
// 获取MAC地址
|
||
uint8_t mac[6];
|
||
esp_err_t err = esp_efuse_mac_get_default(mac);
|
||
if (err == ESP_OK) {
|
||
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]);
|
||
mac_address_ = std::string(mac_str);
|
||
ESP_LOGI(TAG, "Device MAC Address: %s", mac_address_.c_str());
|
||
} else {
|
||
ESP_LOGE(TAG, "Failed to get MAC address");
|
||
}
|
||
|
||
// 从NVS加载配置
|
||
LoadFromNVS();
|
||
|
||
// 如果没有 token,尝试从服务器自动获取(可能已在网页端绑定)
|
||
if (device_token_.empty() && !mac_address_.empty()) {
|
||
ESP_LOGI(TAG, "No token found, trying to fetch from server...");
|
||
TryFetchTokenFromServer();
|
||
}
|
||
}
|
||
|
||
DeviceManager::~DeviceManager() {
|
||
}
|
||
|
||
std::string DeviceManager::GetMACAddress() {
|
||
return mac_address_;
|
||
}
|
||
|
||
void DeviceManager::LoadFromNVS() {
|
||
nvs_handle_t nvs_handle;
|
||
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &nvs_handle);
|
||
|
||
if (err == ESP_OK) {
|
||
// 读取Token
|
||
size_t token_len = 0;
|
||
err = nvs_get_str(nvs_handle, NVS_KEY_TOKEN, nullptr, &token_len);
|
||
if (err == ESP_OK && token_len > 0) {
|
||
char* token_buf = new char[token_len];
|
||
err = nvs_get_str(nvs_handle, NVS_KEY_TOKEN, token_buf, &token_len);
|
||
if (err == ESP_OK) {
|
||
device_token_ = std::string(token_buf);
|
||
is_bound_ = true;
|
||
ESP_LOGI(TAG, "Loaded device token from NVS (length: %d)", token_len - 1);
|
||
}
|
||
delete[] token_buf;
|
||
}
|
||
|
||
// 读取用户名
|
||
size_t username_len = 0;
|
||
err = nvs_get_str(nvs_handle, NVS_KEY_USERNAME, nullptr, &username_len);
|
||
if (err == ESP_OK && username_len > 0) {
|
||
char* username_buf = new char[username_len];
|
||
err = nvs_get_str(nvs_handle, NVS_KEY_USERNAME, username_buf, &username_len);
|
||
if (err == ESP_OK) {
|
||
bound_username_ = std::string(username_buf);
|
||
ESP_LOGI(TAG, "Device bound to user: %s", bound_username_.c_str());
|
||
}
|
||
delete[] username_buf;
|
||
}
|
||
|
||
nvs_close(nvs_handle);
|
||
} else if (err == ESP_ERR_NVS_NOT_FOUND) {
|
||
ESP_LOGI(TAG, "No device binding found in NVS");
|
||
} else {
|
||
ESP_LOGE(TAG, "Failed to open NVS: %s", esp_err_to_name(err));
|
||
}
|
||
}
|
||
|
||
bool DeviceManager::SaveDeviceToken(const std::string& token) {
|
||
nvs_handle_t nvs_handle;
|
||
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle);
|
||
|
||
if (err != ESP_OK) {
|
||
ESP_LOGE(TAG, "Failed to open NVS for writing: %s", esp_err_to_name(err));
|
||
return false;
|
||
}
|
||
|
||
err = nvs_set_str(nvs_handle, NVS_KEY_TOKEN, token.c_str());
|
||
if (err != ESP_OK) {
|
||
ESP_LOGE(TAG, "Failed to write token to NVS: %s", esp_err_to_name(err));
|
||
nvs_close(nvs_handle);
|
||
return false;
|
||
}
|
||
|
||
err = nvs_commit(nvs_handle);
|
||
nvs_close(nvs_handle);
|
||
|
||
if (err == ESP_OK) {
|
||
device_token_ = token;
|
||
is_bound_ = true;
|
||
ESP_LOGI(TAG, "Device token saved to NVS");
|
||
return true;
|
||
} else {
|
||
ESP_LOGE(TAG, "Failed to commit NVS: %s", esp_err_to_name(err));
|
||
return false;
|
||
}
|
||
}
|
||
|
||
std::string DeviceManager::GetDeviceToken() {
|
||
return device_token_;
|
||
}
|
||
|
||
bool DeviceManager::ClearDeviceToken() {
|
||
nvs_handle_t nvs_handle;
|
||
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle);
|
||
|
||
if (err != ESP_OK) {
|
||
ESP_LOGE(TAG, "Failed to open NVS for erasing: %s", esp_err_to_name(err));
|
||
return false;
|
||
}
|
||
|
||
nvs_erase_key(nvs_handle, NVS_KEY_TOKEN);
|
||
nvs_erase_key(nvs_handle, NVS_KEY_USERNAME);
|
||
err = nvs_commit(nvs_handle);
|
||
nvs_close(nvs_handle);
|
||
|
||
if (err == ESP_OK) {
|
||
device_token_.clear();
|
||
bound_username_.clear();
|
||
is_bound_ = false;
|
||
ESP_LOGI(TAG, "Device token cleared");
|
||
return true;
|
||
} else {
|
||
ESP_LOGE(TAG, "Failed to clear device token: %s", esp_err_to_name(err));
|
||
return false;
|
||
}
|
||
}
|
||
|
||
bool DeviceManager::BindDevice(const std::string& binding_code, const std::string& device_name) {
|
||
ESP_LOGI(TAG, "Starting device binding with code: %s", binding_code.c_str());
|
||
|
||
if (mac_address_.empty()) {
|
||
ESP_LOGE(TAG, "MAC address not available");
|
||
return false;
|
||
}
|
||
|
||
// 构建请求JSON
|
||
cJSON* request = cJSON_CreateObject();
|
||
cJSON_AddStringToObject(request, "mac", mac_address_.c_str());
|
||
cJSON_AddStringToObject(request, "binding_code", binding_code.c_str());
|
||
if (!device_name.empty()) {
|
||
cJSON_AddStringToObject(request, "device_name", device_name.c_str());
|
||
} else {
|
||
cJSON_AddStringToObject(request, "device_name", "ESP32音乐播放器");
|
||
}
|
||
|
||
char* request_str = cJSON_PrintUnformatted(request);
|
||
std::string request_body(request_str);
|
||
cJSON_Delete(request);
|
||
cJSON_free(request_str);
|
||
|
||
// 发送HTTP请求到服务器
|
||
auto& board = Board::GetInstance();
|
||
auto network = board.GetNetwork();
|
||
auto http = network->CreateHttp(0);
|
||
|
||
std::string bind_url = DEVICE_BIND_API_URL;
|
||
|
||
http->SetHeader("Content-Type", "application/json");
|
||
http->SetHeader("User-Agent", "ESP32-Music-Player/1.0");
|
||
|
||
ESP_LOGI(TAG, "Sending bind request to: %s", bind_url.c_str());
|
||
ESP_LOGD(TAG, "Request body: %s", request_body.c_str());
|
||
|
||
if (!http->Open("POST", bind_url)) {
|
||
ESP_LOGE(TAG, "Failed to connect to bind API");
|
||
return false;
|
||
}
|
||
|
||
// 发送请求体
|
||
http->Write(request_body.c_str(), request_body.length());
|
||
|
||
int status_code = http->GetStatusCode();
|
||
ESP_LOGI(TAG, "Bind request status code: %d", status_code);
|
||
|
||
if (status_code != 200) {
|
||
ESP_LOGE(TAG, "Bind request failed with status: %d", status_code);
|
||
http->Close();
|
||
return false;
|
||
}
|
||
|
||
// 读取响应
|
||
std::string response = http->ReadAll();
|
||
http->Close();
|
||
|
||
ESP_LOGD(TAG, "Bind response: %s", response.c_str());
|
||
|
||
// 解析响应JSON
|
||
cJSON* response_json = cJSON_Parse(response.c_str());
|
||
if (!response_json) {
|
||
ESP_LOGE(TAG, "Failed to parse bind response");
|
||
return false;
|
||
}
|
||
|
||
cJSON* success = cJSON_GetObjectItem(response_json, "success");
|
||
if (!success || !cJSON_IsBool(success) || !cJSON_IsTrue(success)) {
|
||
ESP_LOGE(TAG, "Bind request was not successful");
|
||
cJSON_Delete(response_json);
|
||
return false;
|
||
}
|
||
|
||
cJSON* token = cJSON_GetObjectItem(response_json, "token");
|
||
cJSON* username = cJSON_GetObjectItem(response_json, "username");
|
||
|
||
if (!token || !cJSON_IsString(token)) {
|
||
ESP_LOGE(TAG, "No token in bind response");
|
||
cJSON_Delete(response_json);
|
||
return false;
|
||
}
|
||
|
||
std::string token_str = token->valuestring;
|
||
std::string username_str = username && cJSON_IsString(username) ? username->valuestring : "";
|
||
|
||
cJSON_Delete(response_json);
|
||
|
||
// 保存Token到NVS
|
||
if (!SaveDeviceToken(token_str)) {
|
||
ESP_LOGE(TAG, "Failed to save token");
|
||
return false;
|
||
}
|
||
|
||
// 保存用户名到NVS
|
||
if (!username_str.empty()) {
|
||
nvs_handle_t nvs_handle;
|
||
if (nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle) == ESP_OK) {
|
||
nvs_set_str(nvs_handle, NVS_KEY_USERNAME, username_str.c_str());
|
||
nvs_commit(nvs_handle);
|
||
nvs_close(nvs_handle);
|
||
bound_username_ = username_str;
|
||
}
|
||
}
|
||
|
||
ESP_LOGI(TAG, "Device successfully bound to user: %s", username_str.c_str());
|
||
|
||
return true;
|
||
}
|
||
|
||
bool DeviceManager::VerifyDevice() {
|
||
if (device_token_.empty()) {
|
||
ESP_LOGW(TAG, "No device token available for verification");
|
||
return false;
|
||
}
|
||
|
||
auto& board = Board::GetInstance();
|
||
auto network = board.GetNetwork();
|
||
auto http = network->CreateHttp(0);
|
||
|
||
std::string verify_url = DEVICE_VERIFY_API_URL;
|
||
|
||
http->SetHeader("X-Device-Token", device_token_.c_str());
|
||
http->SetHeader("User-Agent", "ESP32-Music-Player/1.0");
|
||
|
||
if (!http->Open("GET", verify_url)) {
|
||
ESP_LOGE(TAG, "Failed to connect to verify API");
|
||
return false;
|
||
}
|
||
|
||
int status_code = http->GetStatusCode();
|
||
http->Close();
|
||
|
||
if (status_code == 200) {
|
||
ESP_LOGI(TAG, "Device verification successful");
|
||
return true;
|
||
} else {
|
||
ESP_LOGW(TAG, "Device verification failed with status: %d", status_code);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
std::string DeviceManager::GetFavorites() {
|
||
if (device_token_.empty()) {
|
||
return "";
|
||
}
|
||
|
||
auto& board = Board::GetInstance();
|
||
auto network = board.GetNetwork();
|
||
auto http = network->CreateHttp(0);
|
||
|
||
std::string url = FAVORITE_LIST_API_URL;
|
||
|
||
http->SetHeader("X-Device-Token", device_token_.c_str());
|
||
http->SetHeader("User-Agent", "ESP32-Music-Player/1.0");
|
||
|
||
if (!http->Open("GET", url)) {
|
||
ESP_LOGE(TAG, "Failed to connect to favorites API");
|
||
return "";
|
||
}
|
||
|
||
int status_code = http->GetStatusCode();
|
||
std::string response = http->ReadAll();
|
||
http->Close();
|
||
|
||
if (status_code == 200) {
|
||
return response;
|
||
} else {
|
||
ESP_LOGW(TAG, "GetFavorites failed with status: %d", status_code);
|
||
return "";
|
||
}
|
||
}
|
||
|
||
std::string DeviceManager::GetUserPlaylists() {
|
||
if (device_token_.empty()) {
|
||
return "";
|
||
}
|
||
|
||
auto& board = Board::GetInstance();
|
||
auto network = board.GetNetwork();
|
||
auto http = network->CreateHttp(0);
|
||
|
||
std::string url = PLAYLIST_LIST_API_URL;
|
||
|
||
http->SetHeader("X-Device-Token", device_token_.c_str());
|
||
http->SetHeader("User-Agent", "ESP32-Music-Player/1.0");
|
||
|
||
if (!http->Open("GET", url)) {
|
||
ESP_LOGE(TAG, "Failed to connect to playlist API");
|
||
return "";
|
||
}
|
||
|
||
int status_code = http->GetStatusCode();
|
||
std::string response = http->ReadAll();
|
||
http->Close();
|
||
|
||
if (status_code == 200) {
|
||
return response;
|
||
} else {
|
||
ESP_LOGW(TAG, "GetUserPlaylists failed with status: %d", status_code);
|
||
return "";
|
||
}
|
||
}
|
||
|
||
bool DeviceManager::IsDeviceBound() {
|
||
return is_bound_ && !device_token_.empty();
|
||
}
|
||
|
||
std::string DeviceManager::GetBoundUsername() {
|
||
return bound_username_;
|
||
}
|
||
|
||
bool DeviceManager::TryFetchTokenFromServer() {
|
||
ESP_LOGI(TAG, "Attempting to fetch token from server using MAC: %s", mac_address_.c_str());
|
||
|
||
auto& board = Board::GetInstance();
|
||
auto network = board.GetNetwork();
|
||
auto http = network->CreateHttp(0);
|
||
|
||
// 构建请求 JSON
|
||
cJSON* request = cJSON_CreateObject();
|
||
cJSON_AddStringToObject(request, "mac", mac_address_.c_str());
|
||
char* request_str = cJSON_PrintUnformatted(request);
|
||
std::string request_body(request_str);
|
||
cJSON_Delete(request);
|
||
cJSON_free(request_str);
|
||
|
||
std::string sync_url = MUSIC_SERVER_URL;
|
||
sync_url += "/api/esp32/sync";
|
||
|
||
http->SetHeader("Content-Type", "application/json");
|
||
http->SetHeader("User-Agent", "ESP32-Music-Player/1.0");
|
||
|
||
if (!http->Open("POST", sync_url)) {
|
||
ESP_LOGW(TAG, "Failed to connect to sync API");
|
||
return false;
|
||
}
|
||
|
||
http->Write(request_body.c_str(), request_body.length());
|
||
|
||
int status_code = http->GetStatusCode();
|
||
std::string response = http->ReadAll();
|
||
http->Close();
|
||
|
||
if (status_code == 404) {
|
||
ESP_LOGI(TAG, "Device not bound on server yet");
|
||
return false;
|
||
}
|
||
|
||
if (status_code != 200) {
|
||
ESP_LOGW(TAG, "Token sync failed with status: %d", status_code);
|
||
return false;
|
||
}
|
||
|
||
// 解析响应
|
||
cJSON* response_json = cJSON_Parse(response.c_str());
|
||
if (!response_json) {
|
||
ESP_LOGE(TAG, "Failed to parse sync response");
|
||
return false;
|
||
}
|
||
|
||
cJSON* token = cJSON_GetObjectItem(response_json, "token");
|
||
cJSON* username = cJSON_GetObjectItem(response_json, "username");
|
||
|
||
if (!token || !cJSON_IsString(token)) {
|
||
ESP_LOGE(TAG, "Invalid sync response: missing token");
|
||
cJSON_Delete(response_json);
|
||
return false;
|
||
}
|
||
|
||
std::string token_str = token->valuestring;
|
||
std::string username_str = username && cJSON_IsString(username) ? username->valuestring : "";
|
||
|
||
cJSON_Delete(response_json);
|
||
|
||
// 保存 Token 到 NVS
|
||
if (!SaveDeviceToken(token_str)) {
|
||
ESP_LOGE(TAG, "Failed to save synced token");
|
||
return false;
|
||
}
|
||
|
||
// 保存用户名到 NVS
|
||
if (!username_str.empty()) {
|
||
nvs_handle_t nvs_handle;
|
||
if (nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle) == ESP_OK) {
|
||
nvs_set_str(nvs_handle, NVS_KEY_USERNAME, username_str.c_str());
|
||
nvs_commit(nvs_handle);
|
||
nvs_close(nvs_handle);
|
||
bound_username_ = username_str;
|
||
is_bound_ = true;
|
||
}
|
||
}
|
||
|
||
ESP_LOGI(TAG, "✅ Token synced successfully for user: %s", username_str.c_str());
|
||
|
||
return true;
|
||
}
|