mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-05 23:27:38 +03:00
working!
This commit is contained in:
parent
23e87f0a62
commit
471dcc0b09
@ -40,6 +40,7 @@ void CalibreWirelessActivity::onEnter() {
|
|||||||
currentFileSize = 0;
|
currentFileSize = 0;
|
||||||
bytesReceived = 0;
|
bytesReceived = 0;
|
||||||
inBinaryMode = false;
|
inBinaryMode = false;
|
||||||
|
recvBuffer.clear();
|
||||||
|
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
|
|
||||||
@ -276,13 +277,11 @@ void CalibreWirelessActivity::handleTcpClient() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we're receiving binary data
|
|
||||||
if (inBinaryMode) {
|
if (inBinaryMode) {
|
||||||
receiveBinaryData();
|
receiveBinaryData();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read JSON message
|
|
||||||
std::string message;
|
std::string message;
|
||||||
if (readJsonMessage(message)) {
|
if (readJsonMessage(message)) {
|
||||||
// Parse opcode from JSON array format: [opcode, {...}]
|
// Parse opcode from JSON array format: [opcode, {...}]
|
||||||
@ -302,7 +301,7 @@ void CalibreWirelessActivity::handleTcpClient() {
|
|||||||
data = message.substr(dataStart, dataEnd - dataStart);
|
data = message.substr(dataStart, dataEnd - dataStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [CAL] Received opcode %d\n", millis(), opcode);
|
Serial.printf("[%lu] [CAL] Opcode %d, data=%zu bytes\n", millis(), opcode, data.size());
|
||||||
handleCommand(opcode, data);
|
handleCommand(opcode, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -310,70 +309,103 @@ void CalibreWirelessActivity::handleTcpClient() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool CalibreWirelessActivity::readJsonMessage(std::string& message) {
|
bool CalibreWirelessActivity::readJsonMessage(std::string& message) {
|
||||||
if (!tcpClient.available()) {
|
// Read available data into buffer
|
||||||
return false;
|
int available = tcpClient.available();
|
||||||
}
|
if (available > 0) {
|
||||||
|
// Limit buffer growth to prevent memory issues
|
||||||
// Protocol: 4-byte length prefix (as string) followed by JSON
|
if (recvBuffer.size() > 100000) {
|
||||||
// Actually, Calibre uses variable-length ASCII number followed by JSON array
|
Serial.printf("[%lu] [CAL] Buffer too large (%zu), clearing\n", millis(), recvBuffer.size());
|
||||||
// Read until we get a '[' character
|
recvBuffer.clear();
|
||||||
|
return false;
|
||||||
// Read length prefix (digits until we hit '[')
|
|
||||||
std::string lengthStr;
|
|
||||||
while (tcpClient.available()) {
|
|
||||||
const char c = tcpClient.read();
|
|
||||||
if (c == '[') {
|
|
||||||
// Start of JSON
|
|
||||||
message = "[";
|
|
||||||
break;
|
|
||||||
} else if (c >= '0' && c <= '9') {
|
|
||||||
lengthStr += c;
|
|
||||||
} else {
|
|
||||||
// Unexpected character, skip
|
|
||||||
}
|
}
|
||||||
}
|
// Read in chunks
|
||||||
|
char buf[1024];
|
||||||
if (message.empty()) {
|
while (available > 0) {
|
||||||
return false;
|
int toRead = std::min(available, static_cast<int>(sizeof(buf)));
|
||||||
}
|
int bytesRead = tcpClient.read(reinterpret_cast<uint8_t*>(buf), toRead);
|
||||||
|
if (bytesRead > 0) {
|
||||||
// Parse expected length
|
recvBuffer.append(buf, bytesRead);
|
||||||
const size_t expectedLen = lengthStr.empty() ? 0 : std::stoul(lengthStr);
|
available -= bytesRead;
|
||||||
|
} else {
|
||||||
// Read rest of the JSON message
|
break;
|
||||||
// We already read '[', so we need expectedLen - 1 more chars (if length was specified)
|
|
||||||
// But Calibre's length includes the '[', so read expectedLen - 1 more
|
|
||||||
size_t bytesToRead = expectedLen > 0 ? expectedLen - 1 : 4096;
|
|
||||||
size_t bytesRead = 0;
|
|
||||||
|
|
||||||
const unsigned long timeout = millis() + 5000;
|
|
||||||
while (bytesRead < bytesToRead && millis() < timeout) {
|
|
||||||
if (tcpClient.available()) {
|
|
||||||
const char c = tcpClient.read();
|
|
||||||
message += c;
|
|
||||||
bytesRead++;
|
|
||||||
|
|
||||||
// If no length specified, check for end of JSON
|
|
||||||
if (expectedLen == 0 && c == ']') {
|
|
||||||
// Check if this is the matching closing bracket
|
|
||||||
int depth = 0;
|
|
||||||
for (char ch : message) {
|
|
||||||
if (ch == '[' || ch == '{')
|
|
||||||
depth++;
|
|
||||||
else if (ch == ']' || ch == '}')
|
|
||||||
depth--;
|
|
||||||
}
|
|
||||||
if (depth == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
vTaskDelay(1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [CAL] Read JSON (%zu bytes): %.100s...\n", millis(), message.length(), message.c_str());
|
if (recvBuffer.empty()) {
|
||||||
return !message.empty();
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find '[' which marks the start of JSON
|
||||||
|
size_t bracketPos = recvBuffer.find('[');
|
||||||
|
if (bracketPos == std::string::npos) {
|
||||||
|
// No '[' found - if buffer is getting large, something is wrong
|
||||||
|
if (recvBuffer.size() > 1000) {
|
||||||
|
Serial.printf("[%lu] [CAL] No '[' in buffer (%zu bytes), clearing\n", millis(), recvBuffer.size());
|
||||||
|
recvBuffer.clear();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to extract length from digits before '['
|
||||||
|
// Calibre ALWAYS sends a length prefix, so if it's not valid digits, it's garbage
|
||||||
|
size_t msgLen = 0;
|
||||||
|
bool validPrefix = false;
|
||||||
|
|
||||||
|
if (bracketPos > 0 && bracketPos <= 12) {
|
||||||
|
// Check if prefix is all digits
|
||||||
|
bool allDigits = true;
|
||||||
|
for (size_t i = 0; i < bracketPos; i++) {
|
||||||
|
char c = recvBuffer[i];
|
||||||
|
if (c < '0' || c > '9') {
|
||||||
|
allDigits = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (allDigits) {
|
||||||
|
msgLen = std::stoul(recvBuffer.substr(0, bracketPos));
|
||||||
|
validPrefix = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validPrefix) {
|
||||||
|
// Not a valid length prefix - discard everything up to '[' and treat '[' as start
|
||||||
|
if (bracketPos > 0) {
|
||||||
|
Serial.printf("[%lu] [CAL] Invalid prefix, discarding %zu bytes before '['\n", millis(), bracketPos);
|
||||||
|
recvBuffer = recvBuffer.substr(bracketPos);
|
||||||
|
bracketPos = 0;
|
||||||
|
}
|
||||||
|
// Without length prefix, we can't reliably parse - wait for more data
|
||||||
|
// that hopefully starts with a proper length prefix
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanity check the message length
|
||||||
|
if (msgLen > 1000000) {
|
||||||
|
Serial.printf("[%lu] [CAL] Message length too large: %zu, discarding\n", millis(), msgLen);
|
||||||
|
recvBuffer = recvBuffer.substr(bracketPos + 1); // Skip past this '[' and try again
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we have the complete message
|
||||||
|
size_t totalNeeded = bracketPos + msgLen;
|
||||||
|
if (recvBuffer.size() < totalNeeded) {
|
||||||
|
// Not enough data yet - wait for more
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the message
|
||||||
|
message = recvBuffer.substr(bracketPos, msgLen);
|
||||||
|
|
||||||
|
// Keep the rest in buffer (may contain binary data or next message)
|
||||||
|
if (recvBuffer.size() > totalNeeded) {
|
||||||
|
recvBuffer = recvBuffer.substr(totalNeeded);
|
||||||
|
} else {
|
||||||
|
recvBuffer.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.printf("[%lu] [CAL] Got message (%zu bytes): %.80s...\n", millis(), message.length(), message.c_str());
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CalibreWirelessActivity::sendJsonResponse(int opcode, const std::string& data) {
|
void CalibreWirelessActivity::sendJsonResponse(int opcode, const std::string& data) {
|
||||||
@ -390,8 +422,6 @@ void CalibreWirelessActivity::sendJsonResponse(int opcode, const std::string& da
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CalibreWirelessActivity::handleCommand(int opcode, const std::string& data) {
|
void CalibreWirelessActivity::handleCommand(int opcode, const std::string& data) {
|
||||||
Serial.printf("[%lu] [CAL] handleCommand: opcode=%d, data_len=%zu\n", millis(), opcode, data.length());
|
|
||||||
|
|
||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
case OP_GET_INITIALIZATION_INFO:
|
case OP_GET_INITIALIZATION_INFO:
|
||||||
handleGetInitializationInfo(data);
|
handleGetInitializationInfo(data);
|
||||||
@ -510,41 +540,64 @@ void CalibreWirelessActivity::handleGetBookCount() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CalibreWirelessActivity::handleSendBook(const std::string& data) {
|
void CalibreWirelessActivity::handleSendBook(const std::string& data) {
|
||||||
// Parse the SEND_BOOK data to get lpath and length
|
// Manually extract lpath and length from SEND_BOOK data
|
||||||
// Format: {"lpath": "path/to/book.epub", "length": 12345, ...}
|
// Full JSON parsing crashes on large metadata, so we just extract what we need
|
||||||
|
|
||||||
// Simple JSON parsing for lpath and length
|
Serial.printf("[%lu] [CAL] handleSendBook: data size=%zu, free heap=%lu\n",
|
||||||
|
millis(), data.size(), (unsigned long)ESP.getFreeHeap());
|
||||||
|
|
||||||
|
// Extract "lpath" field - format: "lpath": "value"
|
||||||
std::string lpath;
|
std::string lpath;
|
||||||
size_t length = 0;
|
|
||||||
|
|
||||||
// Find lpath
|
|
||||||
size_t lpathPos = data.find("\"lpath\"");
|
size_t lpathPos = data.find("\"lpath\"");
|
||||||
if (lpathPos != std::string::npos) {
|
if (lpathPos != std::string::npos) {
|
||||||
size_t colonPos = data.find(':', lpathPos);
|
size_t colonPos = data.find(':', lpathPos + 7);
|
||||||
size_t quoteStart = data.find('"', colonPos);
|
if (colonPos != std::string::npos) {
|
||||||
size_t quoteEnd = data.find('"', quoteStart + 1);
|
size_t quoteStart = data.find('"', colonPos + 1);
|
||||||
if (quoteStart != std::string::npos && quoteEnd != std::string::npos) {
|
if (quoteStart != std::string::npos) {
|
||||||
lpath = data.substr(quoteStart + 1, quoteEnd - quoteStart - 1);
|
size_t quoteEnd = data.find('"', quoteStart + 1);
|
||||||
|
if (quoteEnd != std::string::npos) {
|
||||||
|
lpath = data.substr(quoteStart + 1, quoteEnd - quoteStart - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find length
|
// Extract top-level "length" field - must track depth to skip nested objects
|
||||||
size_t lengthPos = data.find("\"length\"");
|
// The metadata contains nested "length" fields (e.g., cover image length)
|
||||||
if (lengthPos != std::string::npos) {
|
size_t length = 0;
|
||||||
size_t colonPos = data.find(':', lengthPos);
|
int depth = 0;
|
||||||
size_t numStart = colonPos + 1;
|
for (size_t i = 0; i < data.size(); i++) {
|
||||||
while (numStart < data.length() && (data[numStart] == ' ' || data[numStart] == '\t')) {
|
char c = data[i];
|
||||||
numStart++;
|
if (c == '{' || c == '[') {
|
||||||
}
|
depth++;
|
||||||
size_t numEnd = numStart;
|
} else if (c == '}' || c == ']') {
|
||||||
while (numEnd < data.length() && data[numEnd] >= '0' && data[numEnd] <= '9') {
|
depth--;
|
||||||
numEnd++;
|
} else if (depth == 1 && c == '"') {
|
||||||
}
|
// At top level, check if this is "length"
|
||||||
if (numEnd > numStart) {
|
if (i + 9 < data.size() && data.substr(i, 8) == "\"length\"") {
|
||||||
length = std::stoul(data.substr(numStart, numEnd - numStart));
|
// Found top-level "length" - extract the number after ':'
|
||||||
|
size_t colonPos = data.find(':', i + 8);
|
||||||
|
if (colonPos != std::string::npos) {
|
||||||
|
size_t numStart = colonPos + 1;
|
||||||
|
while (numStart < data.size() && (data[numStart] == ' ' || data[numStart] == '\t')) {
|
||||||
|
numStart++;
|
||||||
|
}
|
||||||
|
size_t numEnd = numStart;
|
||||||
|
while (numEnd < data.size() && data[numEnd] >= '0' && data[numEnd] <= '9') {
|
||||||
|
numEnd++;
|
||||||
|
}
|
||||||
|
if (numEnd > numStart) {
|
||||||
|
length = std::stoul(data.substr(numStart, numEnd - numStart));
|
||||||
|
Serial.printf("[%lu] [CAL] Found top-level length=%zu at pos %zu\n", millis(), length, i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Serial.printf("[%lu] [CAL] Parsed: lpath=%s, length=%zu\n", millis(), lpath.c_str(), length);
|
||||||
|
|
||||||
if (lpath.empty() || length == 0) {
|
if (lpath.empty() || length == 0) {
|
||||||
Serial.printf("[%lu] [CAL] Invalid SEND_BOOK data\n", millis());
|
Serial.printf("[%lu] [CAL] Invalid SEND_BOOK data\n", millis());
|
||||||
sendJsonResponse(OP_ERROR, "{\"message\":\"Invalid book data\"}");
|
sendJsonResponse(OP_ERROR, "{\"message\":\"Invalid book data\"}");
|
||||||
@ -585,6 +638,17 @@ void CalibreWirelessActivity::handleSendBook(const std::string& data) {
|
|||||||
// Switch to binary mode
|
// Switch to binary mode
|
||||||
inBinaryMode = true;
|
inBinaryMode = true;
|
||||||
binaryBytesRemaining = length;
|
binaryBytesRemaining = length;
|
||||||
|
|
||||||
|
// Check if recvBuffer has leftover data (binary file data that arrived with the JSON)
|
||||||
|
if (!recvBuffer.empty()) {
|
||||||
|
size_t toWrite = std::min(recvBuffer.size(), binaryBytesRemaining);
|
||||||
|
size_t written = currentFile.write(reinterpret_cast<const uint8_t*>(recvBuffer.data()), toWrite);
|
||||||
|
Serial.printf("[%lu] [CAL] Wrote %zu bytes from buffer to file\n", millis(), written);
|
||||||
|
bytesReceived += written;
|
||||||
|
binaryBytesRemaining -= written;
|
||||||
|
recvBuffer = recvBuffer.substr(toWrite);
|
||||||
|
updateRequired = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CalibreWirelessActivity::handleSendBookMetadata(const std::string& data) {
|
void CalibreWirelessActivity::handleSendBookMetadata(const std::string& data) {
|
||||||
|
|||||||
@ -69,6 +69,7 @@ class CalibreWirelessActivity final : public Activity {
|
|||||||
bool inBinaryMode = false;
|
bool inBinaryMode = false;
|
||||||
size_t binaryBytesRemaining = 0;
|
size_t binaryBytesRemaining = 0;
|
||||||
FsFile currentFile;
|
FsFile currentFile;
|
||||||
|
std::string recvBuffer; // Buffer for incoming data (like KOReader)
|
||||||
|
|
||||||
// Calibre protocol opcodes (from calibre/devices/smart_device_app/driver.py)
|
// Calibre protocol opcodes (from calibre/devices/smart_device_app/driver.py)
|
||||||
static constexpr int OP_OK = 0;
|
static constexpr int OP_OK = 0;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user