ESP32: добавить диагностику подключения SHiNE и починить WS handshake

This commit is contained in:
AidarKC 2026-06-13 13:09:32 +04:00
parent 477ab3b580
commit 3b8ea70d3c
3 changed files with 76 additions and 53 deletions

View File

@ -16,7 +16,6 @@
6. Если действие завершается ошибкой, проверить, что:
- текст ошибки показан на экране результата;
- команда `last_error` по USB возвращает полный сохранённый payload.
7. Во временной диагностической сборке после старта устройства подождать 15 секунд и проверить, что при наличии жёлтой homeserver-кнопки устройство само делает попытку `ADD/FIX HOMESERVER`, а результат пишет в USB-диагностику.
- ожидаемый результат:
устройство после обычной регистрации пользователя способно отдельной транзакцией добавить или исправить homeserver-сессию в `shine_users` PDA, а ошибки этого шага сохраняются в ту же USB/NVS-диагностику.

View File

@ -304,10 +304,6 @@ static String gLastRegisterDiagSummary;
static String gLastRegisterDiagDetails;
static String gLastRegisterDiagTime;
static String gRegisterTriggerSource = "manual";
static unsigned long gBootMillis = 0;
static bool gAutoHomeserverTestArmed = true;
static bool gAutoHomeserverTestStarted = false;
static unsigned long gLastAutoHomeserverAttemptMs = 0;
static String gSerialCommandBuffer;
static String gShineSessionId;
static String gShineSessionKey;
@ -498,6 +494,7 @@ static bool readShineUserPda(const String &login, ShinePdaUserState &outState, S
static bool parseShineUserPdaBytes(const std::vector<uint8_t> &bytes, ShinePdaUserState &outState, String &errorOut);
static void refreshAccountPdaStatus();
static void manageAccountPdaRefresh();
static void saveShineConnectDiag(const String &status, const String &summary, const String &details);
static bool ensureWebSocketConnected(SimpleWebSocketClient &ws, const String &url, String &errorOut);
static void closeWebSocket(SimpleWebSocketClient &ws);
static bool wsSendText(SimpleWebSocketClient &ws, const String &payload);
@ -2921,7 +2918,7 @@ static bool ensureWebSocketConnected(SimpleWebSocketClient &ws, const String &ur
}
ws.client.setInsecure();
ws.client.setTimeout(5);
ws.client.setTimeout(5000);
if (!ws.client.connect(ws.host.c_str(), ws.port)) {
errorOut = "WS connect failed";
return false;
@ -2944,7 +2941,7 @@ static bool ensureWebSocketConnected(SimpleWebSocketClient &ws, const String &ur
String statusLine = ws.client.readStringUntil('\n');
if (statusLine.indexOf("101") < 0) {
errorOut = "WS handshake failed";
errorOut = String("WS handshake failed: ") + statusLine;
closeWebSocket(ws);
return false;
}
@ -3100,41 +3097,58 @@ static bool shineWsRequest(SimpleWebSocketClient &ws, const String &op, const St
static bool ensureShineSessionAuthenticated(String &errorOut) {
errorOut = "";
String diagDetails;
auto failWithDiag = [&](const String &summary) -> bool {
errorOut = summary;
saveShineConnectDiag("error", summary, diagDetails);
printRegisterDiagToSerial();
return false;
};
diagDetails += "kind=shine_connect\n";
diagDetails += String("uptime_ms=") + String(millis()) + "\n";
diagDetails += String("login=") + gLoginValue + "\n";
diagDetails += String("homeserver=") + gHomeserverValue + "\n";
diagDetails += String("server_url=") + gShineServerUrl + "\n";
diagDetails += String("ws_url=") + shineWsUrl() + "\n";
diagDetails += String("pda_status=") + gAccountPdaStatusMessage + "\n";
if (WiFi.status() != WL_CONNECTED) {
errorOut = "Wi-Fi disconnected";
return false;
diagDetails += "wifi=disconnected\n";
return failWithDiag("Wi-Fi disconnected");
}
diagDetails += String("wifi=connected:") + WiFi.localIP().toString() + "\n";
if (gLoginValue.isEmpty() || !gSecretConfigured || gHomeserverValue.isEmpty()) {
errorOut = "account not configured";
return false;
return failWithDiag("account not configured");
}
if (gAccountPdaStatus != ACCOUNT_PDA_OK) {
errorOut = "account not ready";
return false;
return failWithDiag("account not ready");
}
String wsUrl = shineWsUrl();
if (wsUrl.isEmpty()) {
errorOut = "shine server not set";
return false;
return failWithDiag("shine server not set");
}
if (!ensureWebSocketConnected(gShineWs, wsUrl, errorOut)) {
return false;
diagDetails += String("ws_connect_error=") + errorOut + "\n";
return failWithDiag(errorOut.isEmpty() ? "WS connect failed" : errorOut);
}
diagDetails += "ws_connected=true\n";
{
String pingResp;
if (!shineWsRequest(gShineWs, "Ping", "{\"ts\":0}", pingResp, SHINE_RPC_TIMEOUT_MS)) {
errorOut = "Ping failed";
return false;
diagDetails += "ping_error=request_failed\n";
return failWithDiag("Ping failed");
}
diagDetails += "ping_response<<\n";
diagDetails += pingResp;
diagDetails += "\n>>ping_response\n";
uint64_t pingStatus = 0;
uint64_t serverTs = 0;
if (!jsonInt64Field(pingResp, "status", pingStatus) || pingStatus != 200 || !jsonInt64Field(pingResp, "ts", serverTs)) {
errorOut = "Ping rejected";
return false;
return failWithDiag("Ping rejected");
}
gShineServerTimeOffsetMs = (int64_t)serverTs - (int64_t)millis();
diagDetails += String("server_time_offset_ms=") + String((long long)gShineServerTimeOffsetMs) + "\n";
}
uint8_t deviceSeed[32] = {};
@ -3145,21 +3159,26 @@ static bool ensureShineSessionAuthenticated(String &errorOut) {
uint8_t subSec[64] = {};
if (!deriveSeedKeypairFromBase58(gDevicePrivB58, deviceSeed, devicePub, deviceSec)
|| !deriveSeedKeypairFromBase58(gHomeserverPrivB58, subSeed, subPub, subSec)) {
errorOut = "local key derive failed";
return false;
return failWithDiag("local key derive failed");
}
String sessionKey = buildSessionKeyStringFromPublicBase64(bytesToBase64String(subPub, 32));
diagDetails += String("session_key=") + sessionKey + "\n";
if (!gShineSessionKey.isEmpty() && gShineSessionKey != sessionKey) {
clearShineSessionState(true);
diagDetails += "stored_session_key_reset=true\n";
}
if (!gShineSessionId.isEmpty()) {
diagDetails += String("stored_session_id=") + gShineSessionId + "\n";
String response;
if (shineWsRequest(gShineWs,
"SessionChallenge",
String("{\"sessionId\":\"") + jsonEscape(gShineSessionId) + "\"}",
response)) {
diagDetails += "session_challenge_response<<\n";
diagDetails += response;
diagDetails += "\n>>session_challenge_response\n";
uint64_t statusCode = 0;
String nonce;
if (jsonInt64Field(response, "status", statusCode) && statusCode == 200 && jsonStringField(response, "nonce", nonce)) {
@ -3176,6 +3195,9 @@ static bool ensureShineSessionAuthenticated(String &errorOut) {
+ "\",\"clientInfo\":\"ESP32 homeserver\"}";
String loginResp;
if (shineWsRequest(gShineWs, "SessionLogin", loginReq, loginResp)) {
diagDetails += "session_login_response<<\n";
diagDetails += loginResp;
diagDetails += "\n>>session_login_response\n";
if (jsonInt64Field(loginResp, "status", statusCode) && statusCode == 200) {
String storagePwd;
if (jsonStringField(loginResp, "storagePwd", storagePwd)) {
@ -3184,6 +3206,7 @@ static bool ensureShineSessionAuthenticated(String &errorOut) {
gShineSessionKey = sessionKey;
gShineAuthenticated = true;
saveShineSessionPrefs();
saveShineConnectDiag("ok", "SessionLogin success", diagDetails);
return true;
}
}
@ -3191,8 +3214,10 @@ static bool ensureShineSessionAuthenticated(String &errorOut) {
}
clearShineSessionState(true);
if (!ensureWebSocketConnected(gShineWs, wsUrl, errorOut)) {
return false;
diagDetails += String("ws_reconnect_error=") + errorOut + "\n";
return failWithDiag(errorOut.isEmpty() ? "WS reconnect failed" : errorOut);
}
diagDetails += "stored_session_login_failed=true\n";
}
if (gShineStoragePwd.isEmpty()) {
@ -3207,14 +3232,16 @@ static bool ensureShineSessionAuthenticated(String &errorOut) {
if (!shineWsRequest(gShineWs, "AuthChallenge",
String("{\"login\":\"") + jsonEscape(gLoginValue) + "\"}",
authResp)) {
errorOut = "AuthChallenge failed";
return false;
diagDetails += "auth_challenge_error=request_failed\n";
return failWithDiag("AuthChallenge failed");
}
diagDetails += "auth_challenge_response<<\n";
diagDetails += authResp;
diagDetails += "\n>>auth_challenge_response\n";
uint64_t statusCode = 0;
String authNonce;
if (!jsonInt64Field(authResp, "status", statusCode) || statusCode != 200 || !jsonStringField(authResp, "authNonce", authNonce)) {
errorOut = "AuthChallenge rejected";
return false;
return failWithDiag("AuthChallenge rejected");
}
uint64_t timeMs = shineNowMs();
@ -3233,17 +3260,21 @@ static bool ensureShineSessionAuthenticated(String &errorOut) {
+ "\",\"clientInfo\":\"ESP32 homeserver\"}";
String createResp;
if (!shineWsRequest(gShineWs, "CreateAuthSession", createReq, createResp)) {
errorOut = "CreateAuthSession failed";
return false;
diagDetails += "create_auth_session_error=request_failed\n";
return failWithDiag("CreateAuthSession failed");
}
diagDetails += "create_auth_session_response<<\n";
diagDetails += createResp;
diagDetails += "\n>>create_auth_session_response\n";
if (!jsonInt64Field(createResp, "status", statusCode) || statusCode != 200 || !jsonStringField(createResp, "sessionId", gShineSessionId)) {
errorOut = "CreateAuthSession rejected";
return false;
return failWithDiag("CreateAuthSession rejected");
}
gShineSessionKey = sessionKey;
gShineAuthenticated = true;
saveShineSessionPrefs();
diagDetails += String("new_session_id=") + gShineSessionId + "\n";
saveShineConnectDiag("ok", "CreateAuthSession success", diagDetails);
return true;
}
@ -3292,6 +3323,15 @@ static void manageShineConnection() {
return;
}
}
String details;
details += "kind=shine_ping\n";
details += String("uptime_ms=") + String(millis()) + "\n";
details += String("server_url=") + gShineServerUrl + "\n";
details += "ping_response<<\n";
details += pingResp;
details += "\n>>ping_response\n";
saveShineConnectDiag("error", "Ping keepalive failed", details);
printRegisterDiagToSerial();
gShineStatusLine = String("SHiNE: ") + serverLabel + " unavailable";
clearShineSessionState(false);
}
@ -3562,6 +3602,10 @@ static void clearRegisterDiag() {
clearRegisterDiagDetailsFromPrefs();
}
static void saveShineConnectDiag(const String &status, const String &summary, const String &details) {
saveRegisterDiag(status, summary, details);
}
static void printRegisterDiagToSerial() {
Serial.println("LAST_REGISTER_DIAG_BEGIN");
Serial.println(String("status=") + gLastRegisterDiagStatus);
@ -5053,7 +5097,6 @@ static void handleSwipe(SwipeDirection swipe) {
void setup() {
Serial.begin(115200);
sodium_init();
gBootMillis = millis();
Wire.begin(PIN_I2C_SDA, PIN_I2C_SCL);
initPowerManagement();
@ -5115,25 +5158,6 @@ void loop() {
manageAccountPdaRefresh();
manageShineConnection();
if (gAutoHomeserverTestArmed
&& !gTouchDown
&& millis() - gBootMillis >= 15000UL
&& millis() - gLastAutoHomeserverAttemptMs >= 45000UL
&& gShowHomeserverPdaActionButton
&& (gHomeserverPdaCanAdd || gHomeserverPdaCanFix)) {
gAutoHomeserverTestStarted = true;
gLastAutoHomeserverAttemptMs = millis();
gRegisterTriggerSource = gHomeserverPdaCanFix ? "auto-boot-fix-homeserver" : "auto-boot-add-homeserver";
String updateMessage;
Serial.println("AUTO_HOMESERVER_PDA_BEGIN");
if (!updateHomeserverSessionOnSolana(gHomeserverPdaCanFix, updateMessage)) {
gHomeserverPdaResultSuccess = false;
gHomeserverPdaResultMessage = updateMessage;
gHomeserverPdaResultDetails = "";
}
Serial.println("AUTO_HOMESERVER_PDA_END");
}
static unsigned long lastHomeRefreshMs = 0;
if (gCurrentScreen == SCREEN_HOME && !gTouchDown && millis() - lastHomeRefreshMs >= HOME_REFRESH_MS) {
lastHomeRefreshMs = millis();

View File

@ -1,2 +1,2 @@
client.version=1.2.177
server.version=1.2.166
client.version=1.2.179
server.version=1.2.168