ESP32: добавить диагностику подключения SHiNE и починить WS handshake
This commit is contained in:
parent
477ab3b580
commit
3b8ea70d3c
@ -16,7 +16,6 @@
|
|||||||
6. Если действие завершается ошибкой, проверить, что:
|
6. Если действие завершается ошибкой, проверить, что:
|
||||||
- текст ошибки показан на экране результата;
|
- текст ошибки показан на экране результата;
|
||||||
- команда `last_error` по USB возвращает полный сохранённый payload.
|
- команда `last_error` по USB возвращает полный сохранённый payload.
|
||||||
7. Во временной диагностической сборке после старта устройства подождать 15 секунд и проверить, что при наличии жёлтой homeserver-кнопки устройство само делает попытку `ADD/FIX HOMESERVER`, а результат пишет в USB-диагностику.
|
|
||||||
|
|
||||||
- ожидаемый результат:
|
- ожидаемый результат:
|
||||||
устройство после обычной регистрации пользователя способно отдельной транзакцией добавить или исправить homeserver-сессию в `shine_users` PDA, а ошибки этого шага сохраняются в ту же USB/NVS-диагностику.
|
устройство после обычной регистрации пользователя способно отдельной транзакцией добавить или исправить homeserver-сессию в `shine_users` PDA, а ошибки этого шага сохраняются в ту же USB/NVS-диагностику.
|
||||||
|
|||||||
@ -304,10 +304,6 @@ static String gLastRegisterDiagSummary;
|
|||||||
static String gLastRegisterDiagDetails;
|
static String gLastRegisterDiagDetails;
|
||||||
static String gLastRegisterDiagTime;
|
static String gLastRegisterDiagTime;
|
||||||
static String gRegisterTriggerSource = "manual";
|
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 gSerialCommandBuffer;
|
||||||
static String gShineSessionId;
|
static String gShineSessionId;
|
||||||
static String gShineSessionKey;
|
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 bool parseShineUserPdaBytes(const std::vector<uint8_t> &bytes, ShinePdaUserState &outState, String &errorOut);
|
||||||
static void refreshAccountPdaStatus();
|
static void refreshAccountPdaStatus();
|
||||||
static void manageAccountPdaRefresh();
|
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 bool ensureWebSocketConnected(SimpleWebSocketClient &ws, const String &url, String &errorOut);
|
||||||
static void closeWebSocket(SimpleWebSocketClient &ws);
|
static void closeWebSocket(SimpleWebSocketClient &ws);
|
||||||
static bool wsSendText(SimpleWebSocketClient &ws, const String &payload);
|
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.setInsecure();
|
||||||
ws.client.setTimeout(5);
|
ws.client.setTimeout(5000);
|
||||||
if (!ws.client.connect(ws.host.c_str(), ws.port)) {
|
if (!ws.client.connect(ws.host.c_str(), ws.port)) {
|
||||||
errorOut = "WS connect failed";
|
errorOut = "WS connect failed";
|
||||||
return false;
|
return false;
|
||||||
@ -2944,7 +2941,7 @@ static bool ensureWebSocketConnected(SimpleWebSocketClient &ws, const String &ur
|
|||||||
|
|
||||||
String statusLine = ws.client.readStringUntil('\n');
|
String statusLine = ws.client.readStringUntil('\n');
|
||||||
if (statusLine.indexOf("101") < 0) {
|
if (statusLine.indexOf("101") < 0) {
|
||||||
errorOut = "WS handshake failed";
|
errorOut = String("WS handshake failed: ") + statusLine;
|
||||||
closeWebSocket(ws);
|
closeWebSocket(ws);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -3100,41 +3097,58 @@ static bool shineWsRequest(SimpleWebSocketClient &ws, const String &op, const St
|
|||||||
|
|
||||||
static bool ensureShineSessionAuthenticated(String &errorOut) {
|
static bool ensureShineSessionAuthenticated(String &errorOut) {
|
||||||
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) {
|
if (WiFi.status() != WL_CONNECTED) {
|
||||||
errorOut = "Wi-Fi disconnected";
|
diagDetails += "wifi=disconnected\n";
|
||||||
return false;
|
return failWithDiag("Wi-Fi disconnected");
|
||||||
}
|
}
|
||||||
|
diagDetails += String("wifi=connected:") + WiFi.localIP().toString() + "\n";
|
||||||
if (gLoginValue.isEmpty() || !gSecretConfigured || gHomeserverValue.isEmpty()) {
|
if (gLoginValue.isEmpty() || !gSecretConfigured || gHomeserverValue.isEmpty()) {
|
||||||
errorOut = "account not configured";
|
return failWithDiag("account not configured");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
if (gAccountPdaStatus != ACCOUNT_PDA_OK) {
|
if (gAccountPdaStatus != ACCOUNT_PDA_OK) {
|
||||||
errorOut = "account not ready";
|
return failWithDiag("account not ready");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String wsUrl = shineWsUrl();
|
String wsUrl = shineWsUrl();
|
||||||
if (wsUrl.isEmpty()) {
|
if (wsUrl.isEmpty()) {
|
||||||
errorOut = "shine server not set";
|
return failWithDiag("shine server not set");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
if (!ensureWebSocketConnected(gShineWs, wsUrl, errorOut)) {
|
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;
|
String pingResp;
|
||||||
if (!shineWsRequest(gShineWs, "Ping", "{\"ts\":0}", pingResp, SHINE_RPC_TIMEOUT_MS)) {
|
if (!shineWsRequest(gShineWs, "Ping", "{\"ts\":0}", pingResp, SHINE_RPC_TIMEOUT_MS)) {
|
||||||
errorOut = "Ping failed";
|
diagDetails += "ping_error=request_failed\n";
|
||||||
return false;
|
return failWithDiag("Ping failed");
|
||||||
}
|
}
|
||||||
|
diagDetails += "ping_response<<\n";
|
||||||
|
diagDetails += pingResp;
|
||||||
|
diagDetails += "\n>>ping_response\n";
|
||||||
uint64_t pingStatus = 0;
|
uint64_t pingStatus = 0;
|
||||||
uint64_t serverTs = 0;
|
uint64_t serverTs = 0;
|
||||||
if (!jsonInt64Field(pingResp, "status", pingStatus) || pingStatus != 200 || !jsonInt64Field(pingResp, "ts", serverTs)) {
|
if (!jsonInt64Field(pingResp, "status", pingStatus) || pingStatus != 200 || !jsonInt64Field(pingResp, "ts", serverTs)) {
|
||||||
errorOut = "Ping rejected";
|
return failWithDiag("Ping rejected");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
gShineServerTimeOffsetMs = (int64_t)serverTs - (int64_t)millis();
|
gShineServerTimeOffsetMs = (int64_t)serverTs - (int64_t)millis();
|
||||||
|
diagDetails += String("server_time_offset_ms=") + String((long long)gShineServerTimeOffsetMs) + "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t deviceSeed[32] = {};
|
uint8_t deviceSeed[32] = {};
|
||||||
@ -3145,21 +3159,26 @@ static bool ensureShineSessionAuthenticated(String &errorOut) {
|
|||||||
uint8_t subSec[64] = {};
|
uint8_t subSec[64] = {};
|
||||||
if (!deriveSeedKeypairFromBase58(gDevicePrivB58, deviceSeed, devicePub, deviceSec)
|
if (!deriveSeedKeypairFromBase58(gDevicePrivB58, deviceSeed, devicePub, deviceSec)
|
||||||
|| !deriveSeedKeypairFromBase58(gHomeserverPrivB58, subSeed, subPub, subSec)) {
|
|| !deriveSeedKeypairFromBase58(gHomeserverPrivB58, subSeed, subPub, subSec)) {
|
||||||
errorOut = "local key derive failed";
|
return failWithDiag("local key derive failed");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String sessionKey = buildSessionKeyStringFromPublicBase64(bytesToBase64String(subPub, 32));
|
String sessionKey = buildSessionKeyStringFromPublicBase64(bytesToBase64String(subPub, 32));
|
||||||
|
diagDetails += String("session_key=") + sessionKey + "\n";
|
||||||
if (!gShineSessionKey.isEmpty() && gShineSessionKey != sessionKey) {
|
if (!gShineSessionKey.isEmpty() && gShineSessionKey != sessionKey) {
|
||||||
clearShineSessionState(true);
|
clearShineSessionState(true);
|
||||||
|
diagDetails += "stored_session_key_reset=true\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!gShineSessionId.isEmpty()) {
|
if (!gShineSessionId.isEmpty()) {
|
||||||
|
diagDetails += String("stored_session_id=") + gShineSessionId + "\n";
|
||||||
String response;
|
String response;
|
||||||
if (shineWsRequest(gShineWs,
|
if (shineWsRequest(gShineWs,
|
||||||
"SessionChallenge",
|
"SessionChallenge",
|
||||||
String("{\"sessionId\":\"") + jsonEscape(gShineSessionId) + "\"}",
|
String("{\"sessionId\":\"") + jsonEscape(gShineSessionId) + "\"}",
|
||||||
response)) {
|
response)) {
|
||||||
|
diagDetails += "session_challenge_response<<\n";
|
||||||
|
diagDetails += response;
|
||||||
|
diagDetails += "\n>>session_challenge_response\n";
|
||||||
uint64_t statusCode = 0;
|
uint64_t statusCode = 0;
|
||||||
String nonce;
|
String nonce;
|
||||||
if (jsonInt64Field(response, "status", statusCode) && statusCode == 200 && jsonStringField(response, "nonce", nonce)) {
|
if (jsonInt64Field(response, "status", statusCode) && statusCode == 200 && jsonStringField(response, "nonce", nonce)) {
|
||||||
@ -3176,6 +3195,9 @@ static bool ensureShineSessionAuthenticated(String &errorOut) {
|
|||||||
+ "\",\"clientInfo\":\"ESP32 homeserver\"}";
|
+ "\",\"clientInfo\":\"ESP32 homeserver\"}";
|
||||||
String loginResp;
|
String loginResp;
|
||||||
if (shineWsRequest(gShineWs, "SessionLogin", loginReq, 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) {
|
if (jsonInt64Field(loginResp, "status", statusCode) && statusCode == 200) {
|
||||||
String storagePwd;
|
String storagePwd;
|
||||||
if (jsonStringField(loginResp, "storagePwd", storagePwd)) {
|
if (jsonStringField(loginResp, "storagePwd", storagePwd)) {
|
||||||
@ -3184,6 +3206,7 @@ static bool ensureShineSessionAuthenticated(String &errorOut) {
|
|||||||
gShineSessionKey = sessionKey;
|
gShineSessionKey = sessionKey;
|
||||||
gShineAuthenticated = true;
|
gShineAuthenticated = true;
|
||||||
saveShineSessionPrefs();
|
saveShineSessionPrefs();
|
||||||
|
saveShineConnectDiag("ok", "SessionLogin success", diagDetails);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3191,8 +3214,10 @@ static bool ensureShineSessionAuthenticated(String &errorOut) {
|
|||||||
}
|
}
|
||||||
clearShineSessionState(true);
|
clearShineSessionState(true);
|
||||||
if (!ensureWebSocketConnected(gShineWs, wsUrl, errorOut)) {
|
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()) {
|
if (gShineStoragePwd.isEmpty()) {
|
||||||
@ -3207,14 +3232,16 @@ static bool ensureShineSessionAuthenticated(String &errorOut) {
|
|||||||
if (!shineWsRequest(gShineWs, "AuthChallenge",
|
if (!shineWsRequest(gShineWs, "AuthChallenge",
|
||||||
String("{\"login\":\"") + jsonEscape(gLoginValue) + "\"}",
|
String("{\"login\":\"") + jsonEscape(gLoginValue) + "\"}",
|
||||||
authResp)) {
|
authResp)) {
|
||||||
errorOut = "AuthChallenge failed";
|
diagDetails += "auth_challenge_error=request_failed\n";
|
||||||
return false;
|
return failWithDiag("AuthChallenge failed");
|
||||||
}
|
}
|
||||||
|
diagDetails += "auth_challenge_response<<\n";
|
||||||
|
diagDetails += authResp;
|
||||||
|
diagDetails += "\n>>auth_challenge_response\n";
|
||||||
uint64_t statusCode = 0;
|
uint64_t statusCode = 0;
|
||||||
String authNonce;
|
String authNonce;
|
||||||
if (!jsonInt64Field(authResp, "status", statusCode) || statusCode != 200 || !jsonStringField(authResp, "authNonce", authNonce)) {
|
if (!jsonInt64Field(authResp, "status", statusCode) || statusCode != 200 || !jsonStringField(authResp, "authNonce", authNonce)) {
|
||||||
errorOut = "AuthChallenge rejected";
|
return failWithDiag("AuthChallenge rejected");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t timeMs = shineNowMs();
|
uint64_t timeMs = shineNowMs();
|
||||||
@ -3233,17 +3260,21 @@ static bool ensureShineSessionAuthenticated(String &errorOut) {
|
|||||||
+ "\",\"clientInfo\":\"ESP32 homeserver\"}";
|
+ "\",\"clientInfo\":\"ESP32 homeserver\"}";
|
||||||
String createResp;
|
String createResp;
|
||||||
if (!shineWsRequest(gShineWs, "CreateAuthSession", createReq, createResp)) {
|
if (!shineWsRequest(gShineWs, "CreateAuthSession", createReq, createResp)) {
|
||||||
errorOut = "CreateAuthSession failed";
|
diagDetails += "create_auth_session_error=request_failed\n";
|
||||||
return false;
|
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)) {
|
if (!jsonInt64Field(createResp, "status", statusCode) || statusCode != 200 || !jsonStringField(createResp, "sessionId", gShineSessionId)) {
|
||||||
errorOut = "CreateAuthSession rejected";
|
return failWithDiag("CreateAuthSession rejected");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gShineSessionKey = sessionKey;
|
gShineSessionKey = sessionKey;
|
||||||
gShineAuthenticated = true;
|
gShineAuthenticated = true;
|
||||||
saveShineSessionPrefs();
|
saveShineSessionPrefs();
|
||||||
|
diagDetails += String("new_session_id=") + gShineSessionId + "\n";
|
||||||
|
saveShineConnectDiag("ok", "CreateAuthSession success", diagDetails);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3292,6 +3323,15 @@ static void manageShineConnection() {
|
|||||||
return;
|
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";
|
gShineStatusLine = String("SHiNE: ") + serverLabel + " unavailable";
|
||||||
clearShineSessionState(false);
|
clearShineSessionState(false);
|
||||||
}
|
}
|
||||||
@ -3562,6 +3602,10 @@ static void clearRegisterDiag() {
|
|||||||
clearRegisterDiagDetailsFromPrefs();
|
clearRegisterDiagDetailsFromPrefs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void saveShineConnectDiag(const String &status, const String &summary, const String &details) {
|
||||||
|
saveRegisterDiag(status, summary, details);
|
||||||
|
}
|
||||||
|
|
||||||
static void printRegisterDiagToSerial() {
|
static void printRegisterDiagToSerial() {
|
||||||
Serial.println("LAST_REGISTER_DIAG_BEGIN");
|
Serial.println("LAST_REGISTER_DIAG_BEGIN");
|
||||||
Serial.println(String("status=") + gLastRegisterDiagStatus);
|
Serial.println(String("status=") + gLastRegisterDiagStatus);
|
||||||
@ -5053,7 +5097,6 @@ static void handleSwipe(SwipeDirection swipe) {
|
|||||||
void setup() {
|
void setup() {
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
sodium_init();
|
sodium_init();
|
||||||
gBootMillis = millis();
|
|
||||||
Wire.begin(PIN_I2C_SDA, PIN_I2C_SCL);
|
Wire.begin(PIN_I2C_SDA, PIN_I2C_SCL);
|
||||||
initPowerManagement();
|
initPowerManagement();
|
||||||
|
|
||||||
@ -5115,25 +5158,6 @@ void loop() {
|
|||||||
manageAccountPdaRefresh();
|
manageAccountPdaRefresh();
|
||||||
manageShineConnection();
|
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;
|
static unsigned long lastHomeRefreshMs = 0;
|
||||||
if (gCurrentScreen == SCREEN_HOME && !gTouchDown && millis() - lastHomeRefreshMs >= HOME_REFRESH_MS) {
|
if (gCurrentScreen == SCREEN_HOME && !gTouchDown && millis() - lastHomeRefreshMs >= HOME_REFRESH_MS) {
|
||||||
lastHomeRefreshMs = millis();
|
lastHomeRefreshMs = millis();
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
client.version=1.2.177
|
client.version=1.2.179
|
||||||
server.version=1.2.166
|
server.version=1.2.168
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user