This commit is contained in:
Pascal RIGAUD
2026-02-09 07:38:28 +01:00
parent 0d73fb0cd6
commit 1e258f6805
9 changed files with 724 additions and 62 deletions

413
src/lichessboard.cpp Normal file
View File

@@ -0,0 +1,413 @@
#include "lichessboard.h"
LichessBoard::LichessBoard()
{
streaming = false;
lastHeartbeat = 0;
onMoveReceived = nullptr;
onGameStateChanged = nullptr;
}
void LichessBoard::setToken(String token)
{
apiToken = token;
}
void LichessBoard::setMoveCallback(MoveCallback callback)
{
onMoveReceived = callback;
}
void LichessBoard::setGameStateCallback(GameStateCallback callback)
{
onGameStateChanged = callback;
}
bool LichessBoard::streamGame(String gameId)
{
if (streaming)
{
stop();
}
currentGameId = gameId;
client.setInsecure(); // Pour HTTPS sans certificat
Serial.println("Connexion à Lichess...");
if (!client.connect("lichess.org", 443))
{
Serial.println("Échec connexion");
return false;
}
// Requête HTTP pour le stream
String request = "GET /api/board/game/stream/" + gameId + " HTTP/1.1\r\n";
request += "Host: lichess.org\r\n";
if (apiToken.length() > 0)
{
request += "Authorization: Bearer " + apiToken + "\r\n";
}
request += "Accept: application/x-ndjson\r\n";
request += "Connection: keep-alive\r\n";
request += "\r\n";
client.print(request);
Serial.println("Requête envoyée, attente réponse...");
// Attendre les headers HTTP
unsigned long timeout = millis();
while (client.connected() && !client.available())
{
if (millis() - timeout > 5000)
{
Serial.println("Timeout headers");
client.stop();
return false;
}
delay(10);
}
// Lire les headers
bool headersEnded = false;
while (client.available() && !headersEnded)
{
String line = client.readStringUntil('\n');
if (line == "\r" || line.length() == 0)
{
headersEnded = true;
}
Serial.println("Header: " + line);
}
streaming = true;
lastHeartbeat = millis();
Serial.println("Stream démarré!");
return true;
}
bool LichessBoard::streamMyGames()
{
if (apiToken.length() == 0)
{
Serial.println("Token requis pour streamMyGames");
return false;
}
if (streaming)
{
stop();
}
client.setInsecure();
if (!client.connect("lichess.org", 443))
{
Serial.println("Échec connexion");
return false;
}
String request = "GET /api/board/game/stream HTTP/1.1\r\n";
request += "Host: lichess.org\r\n";
request += "Authorization: Bearer " + apiToken + "\r\n";
request += "Accept: application/x-ndjson\r\n";
request += "Connection: keep-alive\r\n";
request += "\r\n";
client.print(request);
// Skip headers
while (client.connected())
{
String line = client.readStringUntil('\n');
if (line == "\r" || line.length() == 0)
break;
}
streaming = true;
lastHeartbeat = millis();
return true;
}
bool LichessBoard::streamEvents()
{
if (apiToken.length() == 0)
{
Serial.println("Token requis");
return false;
}
if (streaming)
{
stop();
}
client.setInsecure();
if (!client.connect("lichess.org", 443))
{
Serial.println("Échec connexion");
return false;
}
String request = "GET /api/stream/event HTTP/1.1\r\n";
request += "Host: lichess.org\r\n";
request += "Authorization: Bearer " + apiToken + "\r\n";
request += "Accept: application/x-ndjson\r\n";
request += "Connection: keep-alive\r\n";
request += "\r\n";
client.print(request);
// Skip headers
while (client.connected())
{
String line = client.readStringUntil('\n');
if (line == "\r" || line.length() == 0)
break;
}
streaming = true;
lastHeartbeat = millis();
return true;
}
void LichessBoard::parseLine(String line)
{
line.trim();
if (line.length() == 0)
{
// Heartbeat (ligne vide)
lastHeartbeat = millis();
return;
}
Serial.println("JSON reçu: " + line);
StaticJsonDocument<256> doc;
DeserializationError error = deserializeJson(doc, line);
if (error)
{
Serial.print("Erreur JSON: ");
Serial.println(error.c_str());
return;
}
// Type d'événement
const char *type = doc["type"];
if (strcmp(type, "gameFull") == 0)
{
// État complet de la partie au début
Serial.println("=== Partie complète reçue ===");
const char *gameId = doc["id"];
const char *state = doc["state"]["status"];
Serial.print("Game ID: ");
Serial.println(gameId);
Serial.print("État: ");
Serial.println(state);
if (onGameStateChanged)
{
onGameStateChanged(String(state));
}
// Récupérer tous les coups déjà joués
const char *moves = doc["state"]["moves"];
if (moves && strlen(moves) > 0)
{
Serial.print("Coups joués: ");
Serial.println(moves);
}
// FEN initial si disponible
const char *initialFen = doc["initialFen"];
if (initialFen)
{
Serial.print("FEN: ");
Serial.println(initialFen);
}
}
else if (strcmp(type, "gameState") == 0)
{
// Mise à jour de l'état (nouveau coup)
Serial.println("=== Nouveau coup ===");
const char *moves = doc["moves"];
const char *status = doc["status"];
Serial.print("Tous les coups: ");
Serial.println(moves);
Serial.print("État: ");
Serial.println(status);
if (onGameStateChanged)
{
onGameStateChanged(String(status));
}
// Extraire le dernier coup
if (moves && strlen(moves) > 0)
{
String allMoves = String(moves);
int lastSpace = allMoves.lastIndexOf(' ');
String lastMove = (lastSpace >= 0) ? allMoves.substring(lastSpace + 1) : allMoves;
// Format UCI: e2e4
if (lastMove.length() >= 4)
{
String from = lastMove.substring(0, 2);
String to = lastMove.substring(2, 4);
Serial.print("Dernier coup: ");
Serial.print(from);
Serial.print(" -> ");
Serial.println(to);
if (onMoveReceived)
{
onMoveReceived(from, to, lastMove, "");
}
}
}
}
else if (strcmp(type, "gameStart") == 0)
{
// Nouvelle partie démarrée
JsonObject game = doc["game"];
const char *gameId = game["id"];
Serial.print("Nouvelle partie: ");
Serial.println(gameId);
if (onGameStateChanged)
{
onGameStateChanged("started");
}
}
else if (strcmp(type, "gameFinish") == 0)
{
// Partie terminée
JsonObject game = doc["game"];
const char *gameId = game["id"];
Serial.print("Partie terminée: ");
Serial.println(gameId);
if (onGameStateChanged)
{
onGameStateChanged("finished");
}
}
}
void LichessBoard::loop()
{
if (!streaming)
{
return;
}
// Vérifier timeout
if (millis() - lastHeartbeat > heartbeatTimeout)
{
Serial.println("Timeout stream!");
stop();
return;
}
// Lire les données disponibles
while (client.available())
{
String line = client.readStringUntil('\n');
parseLine(line);
}
// Vérifier connexion
if (!client.connected())
{
Serial.println("Connexion perdue");
stop();
}
}
void LichessBoard::stop()
{
if (client.connected())
{
client.stop();
}
streaming = false;
currentGameId = "";
Serial.println("Stream arrêté");
}
bool LichessBoard::isStreaming()
{
return streaming;
}
String LichessBoard::getCurrentGameId()
{
return currentGameId;
}
bool LichessBoard::getGameState(String gameId, String &fen, String &lastMove)
{
WiFiClientSecure httpClient;
httpClient.setInsecure();
if (!httpClient.connect("lichess.org", 443))
{
return false;
}
String request = "GET /game/export/" + gameId + "?moves=true&clocks=false HTTP/1.1\r\n";
request += "Host: lichess.org\r\n";
request += "Accept: application/json\r\n";
request += "Connection: close\r\n";
request += "\r\n";
httpClient.print(request);
// Skip headers
while (httpClient.connected())
{
String line = httpClient.readStringUntil('\n');
if (line == "\r")
break;
}
// Lire JSON
String json = httpClient.readString();
httpClient.stop();
StaticJsonDocument<4096> doc;
DeserializationError error = deserializeJson(doc, json);
if (error)
{
return false;
}
const char *moves = doc["moves"];
if (moves)
{
lastMove = String(moves);
// Extraire le dernier coup
int lastSpace = lastMove.lastIndexOf(' ');
if (lastSpace >= 0)
{
lastMove = lastMove.substring(lastSpace + 1);
}
}
return true;
}