From d4468086e6614f38239fd9564f807514ecb1e31f Mon Sep 17 00:00:00 2001 From: Cellie Date: Tue, 10 Jun 2025 20:27:45 +0200 Subject: [PATCH 01/14] Fix file fetcher --- src/OpenStreetMap-esp32.cpp | 3 +- src/ReusableTileFetcher.cpp | 65 ++++++++++++++++++++----------------- src/ReusableTileFetcher.hpp | 2 +- 3 files changed, 37 insertions(+), 33 deletions(-) diff --git a/src/OpenStreetMap-esp32.cpp b/src/OpenStreetMap-esp32.cpp index 19ad77c..7595998 100644 --- a/src/OpenStreetMap-esp32.cpp +++ b/src/OpenStreetMap-esp32.cpp @@ -431,7 +431,6 @@ void OpenStreetMap::PNGDraw(PNGDRAW *pDraw) bool OpenStreetMap::fetchTile(ReusableTileFetcher &fetcher, CachedTile &tile, uint32_t x, uint32_t y, uint8_t zoom, String &result) { - String url = currentProvider->urlTemplate; url.replace("{x}", String(x)); url.replace("{y}", String(y)); @@ -502,7 +501,7 @@ void OpenStreetMap::tileFetcherTask(void *param) job.tile->busy = false; --osm->pendingJobs; if (!uxQueueMessagesWaiting(osm->jobQueue)) - fetcher.close(); + fetcher.disconnect(); } log_d("task on core %i exiting", xPortGetCoreID()); xTaskNotifyGive(osm->ownerTask); diff --git a/src/ReusableTileFetcher.cpp b/src/ReusableTileFetcher.cpp index 32084d2..e0cfdfe 100644 --- a/src/ReusableTileFetcher.cpp +++ b/src/ReusableTileFetcher.cpp @@ -24,7 +24,23 @@ #include "ReusableTileFetcher.hpp" ReusableTileFetcher::ReusableTileFetcher() {} -ReusableTileFetcher::~ReusableTileFetcher() { client.stop(); } +ReusableTileFetcher::~ReusableTileFetcher() { disconnect(); } + +void ReusableTileFetcher::sendHttpRequest(const String &host, const String &path) +{ + client.print(String("GET ") + path + " HTTP/1.1\r\n"); + client.print(String("Host: ") + host + "\r\n"); + client.print("User-Agent: OpenStreetMap-esp32/1.0 (+https://github.com/CelliesProjects/OpenStreetMap-esp32)\r\n"); + client.print("Connection: keep-alive\r\n"); + client.print("\r\n"); +} + +void ReusableTileFetcher::disconnect() +{ + client.stop(); + currentHost = ""; + currentPort = 80; +} std::unique_ptr ReusableTileFetcher::fetchToBuffer(const String &url, String &result) { @@ -80,7 +96,8 @@ bool ReusableTileFetcher::ensureConnection(const String &host, uint16_t port, St { if (!client.connected() || host != currentHost || port != currentPort) { - client.stop(); // Close old connection if mismatched + disconnect(); + client.setConnectionTimeout(100); if (!client.connect(host.c_str(), port)) { result = "Connection failed to " + host; @@ -92,23 +109,27 @@ bool ReusableTileFetcher::ensureConnection(const String &host, uint16_t port, St return true; } -void ReusableTileFetcher::sendHttpRequest(const String &host, const String &path) -{ - client.print(String("GET ") + path + " HTTP/1.1\r\n"); - client.print(String("Host: ") + host + "\r\n"); - client.print("User-Agent: OpenStreetMap-esp32/1.0 (+https://github.com/CelliesProjects/OpenStreetMap-esp32)\r\n"); - client.print("Connection: keep-alive\r\n"); - client.print("\r\n"); -} - bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, String &result) { String line; contentLength = 0; + bool start = true; while (client.connected()) { line = client.readStringUntil('\n'); line.trim(); + + if (start) + { + if (!line.startsWith("HTTP/1.1")) + { + result = "Bad HTTP response: " + line; + disconnect(); + return false; + } + start = false; + } + if (line.length() == 0) break; // End of headers @@ -118,22 +139,12 @@ bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, String &result) val.trim(); contentLength = val.toInt(); } - - else if (line.startsWith("HTTP/1.1")) - { - if (!line.startsWith("HTTP/1.1 200")) - { - result = "HTTP error: " + line; - client.stop(); - return false; - } - } } if (contentLength == 0) { result = "Missing or invalid Content-Length"; - client.stop(); + disconnect(); return false; } @@ -158,7 +169,7 @@ bool ReusableTileFetcher::readBody(MemoryBuffer &buffer, size_t contentLength, S else if (len < 0) { result = "Read error"; - client.stop(); + disconnect(); return false; } else @@ -168,15 +179,9 @@ bool ReusableTileFetcher::readBody(MemoryBuffer &buffer, size_t contentLength, S if (remaining > 0) { result = "Incomplete read"; - client.stop(); + disconnect(); return false; } return true; } - -void ReusableTileFetcher::close() -{ - if (client) - client.stop(); -} diff --git a/src/ReusableTileFetcher.hpp b/src/ReusableTileFetcher.hpp index 55f6cc7..059c591 100644 --- a/src/ReusableTileFetcher.hpp +++ b/src/ReusableTileFetcher.hpp @@ -37,7 +37,7 @@ class ReusableTileFetcher ReusableTileFetcher &operator=(const ReusableTileFetcher &) = delete; std::unique_ptr fetchToBuffer(const String &url, String &result); - void close(); + void disconnect(); private: WiFiClient client; From 69d7d6e8c0ddde2fb3a5cbd7f5de9e0ea0de0a51 Mon Sep 17 00:00:00 2001 From: Cellie Date: Tue, 10 Jun 2025 21:01:38 +0200 Subject: [PATCH 02/14] Avoid blocking in `readHttpHeaders()` by implementing timeout-aware line read --- src/OpenStreetMap-esp32.cpp | 2 +- src/ReusableTileFetcher.cpp | 28 ++++++++++++++++++++++++++-- src/ReusableTileFetcher.hpp | 1 + 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/OpenStreetMap-esp32.cpp b/src/OpenStreetMap-esp32.cpp index 7595998..2ead5b7 100644 --- a/src/OpenStreetMap-esp32.cpp +++ b/src/OpenStreetMap-esp32.cpp @@ -205,7 +205,7 @@ void OpenStreetMap::updateCache(const tileList &requiredTiles, uint8_t zoom, Til if (!jobs.empty()) { runJobs(jobs); - log_i("Updated %i tiles in %lu ms - %i ms/tile", jobs.size(), millis() - startMS, (millis() - startMS) / jobs.size()); + log_i("Ran %i jobs in %lu ms - %i ms/job", jobs.size(), millis() - startMS, (millis() - startMS) / jobs.size()); } } diff --git a/src/ReusableTileFetcher.cpp b/src/ReusableTileFetcher.cpp index e0cfdfe..7212cdd 100644 --- a/src/ReusableTileFetcher.cpp +++ b/src/ReusableTileFetcher.cpp @@ -116,9 +116,14 @@ bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, String &result) bool start = true; while (client.connected()) { - line = client.readStringUntil('\n'); - line.trim(); + if (!readLineWithTimeout(line, 100)) + { + result = "Header timeout"; + disconnect(); + return false; + } + line.trim(); if (start) { if (!line.startsWith("HTTP/1.1")) @@ -185,3 +190,22 @@ bool ReusableTileFetcher::readBody(MemoryBuffer &buffer, size_t contentLength, S return true; } + +bool ReusableTileFetcher::readLineWithTimeout(String &line, uint32_t timeoutMs) +{ + line = ""; + uint32_t start = millis(); + while ((millis() - start) < timeoutMs) + { + while (client.available()) + { + char c = client.read(); + if (c == '\n') + return true; + if (c != '\r') + line += c; + } + taskYIELD(); + } + return false; +} diff --git a/src/ReusableTileFetcher.hpp b/src/ReusableTileFetcher.hpp index 059c591..5738d50 100644 --- a/src/ReusableTileFetcher.hpp +++ b/src/ReusableTileFetcher.hpp @@ -49,4 +49,5 @@ class ReusableTileFetcher void sendHttpRequest(const String &host, const String &path); bool readHttpHeaders(size_t &contentLength, String &result); bool readBody(MemoryBuffer &buffer, size_t contentLength, String &result); + bool readLineWithTimeout(String &line, uint32_t timeoutMs); }; From 5372cf0dfc972218153d21609ddb89308c9d5b8b Mon Sep 17 00:00:00 2001 From: Cellie Date: Wed, 11 Jun 2025 10:08:39 +0200 Subject: [PATCH 03/14] Delete HTTPClient related code --- src/HTTPClientRAII.hpp | 63 ------------------------------ src/OpenStreetMap-esp32.cpp | 78 ------------------------------------- src/OpenStreetMap-esp32.hpp | 3 -- 3 files changed, 144 deletions(-) delete mode 100644 src/HTTPClientRAII.hpp diff --git a/src/HTTPClientRAII.hpp b/src/HTTPClientRAII.hpp deleted file mode 100644 index 5f71daf..0000000 --- a/src/HTTPClientRAII.hpp +++ /dev/null @@ -1,63 +0,0 @@ -/* - Copyright (c) 2025 Cellie https://github.com/CelliesProjects/OpenStreetMap-esp32 - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - SPDX-License-Identifier: MIT - */ -#ifndef HTTPCLIENTRAII_HPP_ -#define HTTPCLIENTRAII_HPP_ - -#include -#include -#include - -class HTTPClientRAII -{ -public: - HTTPClientRAII(const HTTPClientRAII &) = delete; - HTTPClientRAII &operator=(const HTTPClientRAII &) = delete; - HTTPClientRAII(HTTPClientRAII &&) = delete; - HTTPClientRAII &operator=(HTTPClientRAII &&) = delete; - - HTTPClientRAII() noexcept : http(new HTTPClient()) {} - - ~HTTPClientRAII() noexcept - { - if (http) - http->end(); - } - - bool begin(const String &url) - { - if (!http) - return false; - http->setUserAgent("OpenStreetMap-esp32/1.0 (+https://github.com/CelliesProjects/OpenStreetMap-esp32)"); - return http->begin(url); - } - - int GET() { return http ? http->GET() : -1; } - size_t getSize() const { return http ? http->getSize() : 0; } - WiFiClient *getStreamPtr() { return http ? http->getStreamPtr() : nullptr; } - bool isInitialized() const { return static_cast(http); } - -private: - std::unique_ptr http; -}; - -#endif diff --git a/src/OpenStreetMap-esp32.cpp b/src/OpenStreetMap-esp32.cpp index 2ead5b7..8649fbe 100644 --- a/src/OpenStreetMap-esp32.cpp +++ b/src/OpenStreetMap-esp32.cpp @@ -345,84 +345,6 @@ bool OpenStreetMap::fetchMap(LGFX_Sprite &mapSprite, double longitude, double la return true; } -bool OpenStreetMap::fillBuffer(WiFiClient *stream, MemoryBuffer &buffer, size_t contentSize, String &result) -{ - size_t readSize = 0; - unsigned long lastReadTime = millis(); - while (readSize < contentSize) - { - const size_t availableData = stream->available(); - if (!availableData) - { - if (millis() - lastReadTime >= OSM_TILE_TIMEOUT_MS) - { - result = "Timeout: " + String(OSM_TILE_TIMEOUT_MS) + " ms"; - return false; - } - taskYIELD(); - continue; - } - - const size_t remaining = contentSize - readSize; - const size_t toRead = std::min(availableData, remaining); - if (toRead == 0) - continue; - - const int bytesRead = stream->readBytes(buffer.get() + readSize, toRead); - if (bytesRead > 0) - { - readSize += bytesRead; - lastReadTime = millis(); - } - else - taskYIELD(); - } - return true; -} - -std::unique_ptr OpenStreetMap::urlToBuffer(const char *url, String &result) -{ - HTTPClientRAII http; - if (!http.begin(url)) - { - result = "Failed to initialize HTTP client"; - return nullptr; - } - - const int httpCode = http.GET(); - if (httpCode != HTTP_CODE_OK) - { - result = "HTTP Error: " + String(httpCode); - return nullptr; - } - - const size_t contentSize = http.getSize(); - if (contentSize < 1) - { - result = "Empty or chunked response"; - return nullptr; - } - - WiFiClient *stream = http.getStreamPtr(); - if (!stream) - { - result = "Failed to get HTTP stream"; - return nullptr; - } - - auto buffer = std::make_unique(contentSize); - if (!buffer->isAllocated()) - { - result = "Failed to allocate buffer"; - return nullptr; - } - - if (!fillBuffer(stream, *buffer, contentSize, result)) - return nullptr; - - return buffer; -} - void OpenStreetMap::PNGDraw(PNGDRAW *pDraw) { uint16_t *destRow = currentInstance->currentTileBuffer + (pDraw->y * currentInstance->currentProvider->tileSize); diff --git a/src/OpenStreetMap-esp32.hpp b/src/OpenStreetMap-esp32.hpp index 9edf0af..913dd15 100644 --- a/src/OpenStreetMap-esp32.hpp +++ b/src/OpenStreetMap-esp32.hpp @@ -36,7 +36,6 @@ #include "CachedTile.hpp" #include "TileJob.hpp" #include "MemoryBuffer.hpp" -#include "HTTPClientRAII.hpp" #include "ReusableTileFetcher.hpp" #include "fonts/DejaVu9-modded.h" @@ -109,9 +108,7 @@ class OpenStreetMap void runJobs(const std::vector &jobs); CachedTile *findUnusedTile(const tileList &requiredTiles, uint8_t zoom); CachedTile *isTileCached(uint32_t x, uint32_t y, uint8_t z); - std::unique_ptr urlToBuffer(const char *url, String &result); bool fetchTile(ReusableTileFetcher &fetcher, CachedTile &tile, uint32_t x, uint32_t y, uint8_t zoom, String &result); - bool fillBuffer(WiFiClient *stream, MemoryBuffer &buffer, size_t contentSize, String &result); bool composeMap(LGFX_Sprite &mapSprite, TileBufferList &tilePointers); static void tileFetcherTask(void *param); static void PNGDraw(PNGDRAW *pDraw); From 12b809484301f54835c67dbed21117f94cf040e3 Mon Sep 17 00:00:00 2001 From: Cellie Date: Wed, 11 Jun 2025 11:46:01 +0200 Subject: [PATCH 04/14] Add RenderMode support to control tile fetching behavior (FAST vs ACCURATE) - Introduced RenderMode enum (FAST, ACCURATE) - Added mode parameter to ReusableTileFetcher::fetchToBuffer - Adjusted timeouts in connection, header parsing, and body reading based on mode - Enables user to choose between speed-optimized or reliability-optimized rendering --- src/OpenStreetMap-esp32.cpp | 11 ++++++++--- src/OpenStreetMap-esp32.hpp | 4 ++++ src/RenderMode.hpp | 7 +++++++ src/ReusableTileFetcher.cpp | 10 ++++++---- src/ReusableTileFetcher.hpp | 4 +++- 5 files changed, 28 insertions(+), 8 deletions(-) create mode 100644 src/RenderMode.hpp diff --git a/src/OpenStreetMap-esp32.cpp b/src/OpenStreetMap-esp32.cpp index 8649fbe..1c33dd6 100644 --- a/src/OpenStreetMap-esp32.cpp +++ b/src/OpenStreetMap-esp32.cpp @@ -360,7 +360,7 @@ bool OpenStreetMap::fetchTile(ReusableTileFetcher &fetcher, CachedTile &tile, ui if (currentProvider->requiresApiKey && strstr(url.c_str(), "{apiKey}")) url.replace("{apiKey}", currentProvider->apiKey); - const std::unique_ptr buffer = fetcher.fetchToBuffer(url, result); + const std::unique_ptr buffer = fetcher.fetchToBuffer(url, result, renderMode); if (!buffer) return false; @@ -422,8 +422,8 @@ void OpenStreetMap::tileFetcherTask(void *param) job.tile->busy = false; --osm->pendingJobs; - if (!uxQueueMessagesWaiting(osm->jobQueue)) - fetcher.disconnect(); + //if (!uxQueueMessagesWaiting(osm->jobQueue)) + //fetcher.disconnect(); } log_d("task on core %i exiting", xPortGetCoreID()); xTaskNotifyGive(osm->ownerTask); @@ -498,3 +498,8 @@ bool OpenStreetMap::setTileProvider(int index) log_i("provider changed to '%s'", currentProvider->name); return true; } + +void OpenStreetMap::setRenderMode(RenderMode mode) +{ + renderMode = mode; +} diff --git a/src/OpenStreetMap-esp32.hpp b/src/OpenStreetMap-esp32.hpp index 913dd15..fa0e492 100644 --- a/src/OpenStreetMap-esp32.hpp +++ b/src/OpenStreetMap-esp32.hpp @@ -38,6 +38,7 @@ #include "MemoryBuffer.hpp" #include "ReusableTileFetcher.hpp" #include "fonts/DejaVu9-modded.h" +#include "RenderMode.hpp" constexpr uint16_t OSM_BGCOLOR = lgfx::color565(32, 32, 128); constexpr uint16_t OSM_TILE_TIMEOUT_MS = 1000; @@ -47,6 +48,7 @@ constexpr uint32_t OSM_JOB_QUEUE_SIZE = 50; constexpr bool OSM_FORCE_SINGLECORE = false; constexpr int OSM_SINGLECORE_NUMBER = 1; + static_assert(OSM_SINGLECORE_NUMBER < 2, "OSM_SINGLECORE_NUMBER must be 0 or 1 (ESP32 has only 2 cores)"); using tileList = std::vector>; @@ -93,6 +95,7 @@ class OpenStreetMap bool fetchMap(LGFX_Sprite &sprite, double longitude, double latitude, uint8_t zoom); inline void freeTilesCache(); + void setRenderMode(RenderMode mode); bool setTileProvider(int index); const char *getProviderName() { return currentProvider->name; }; int getMinZoom() const { return currentProvider->minZoom; }; @@ -115,6 +118,7 @@ class OpenStreetMap static inline thread_local OpenStreetMap *currentInstance = nullptr; static inline thread_local uint16_t *currentTileBuffer = nullptr; + RenderMode renderMode = RenderMode::ACCURATE; const TileProvider *currentProvider = &tileProviders[0]; std::vector tilesCache; diff --git a/src/RenderMode.hpp b/src/RenderMode.hpp new file mode 100644 index 0000000..77f4a0c --- /dev/null +++ b/src/RenderMode.hpp @@ -0,0 +1,7 @@ +#pragma once + +enum class RenderMode +{ + FAST, + ACCURATE +}; \ No newline at end of file diff --git a/src/ReusableTileFetcher.cpp b/src/ReusableTileFetcher.cpp index 7212cdd..3fe67fe 100644 --- a/src/ReusableTileFetcher.cpp +++ b/src/ReusableTileFetcher.cpp @@ -42,8 +42,9 @@ void ReusableTileFetcher::disconnect() currentPort = 80; } -std::unique_ptr ReusableTileFetcher::fetchToBuffer(const String &url, String &result) +std::unique_ptr ReusableTileFetcher::fetchToBuffer(const String &url, String &result, RenderMode mode) { + renderMode = mode; String host, path; uint16_t port; if (!parseUrl(url, host, path, port)) @@ -97,7 +98,7 @@ bool ReusableTileFetcher::ensureConnection(const String &host, uint16_t port, St if (!client.connected() || host != currentHost || port != currentPort) { disconnect(); - client.setConnectionTimeout(100); + client.setConnectionTimeout(renderMode == RenderMode::FAST ? 100 : 1000); if (!client.connect(host.c_str(), port)) { result = "Connection failed to " + host; @@ -116,7 +117,7 @@ bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, String &result) bool start = true; while (client.connected()) { - if (!readLineWithTimeout(line, 100)) + if (!readLineWithTimeout(line, renderMode == RenderMode::FAST ? 100 : 1000)) { result = "Header timeout"; disconnect(); @@ -163,7 +164,8 @@ bool ReusableTileFetcher::readBody(MemoryBuffer &buffer, size_t contentLength, S size_t offset = 0; unsigned long start = millis(); - while (remaining > 0 && millis() - start < 3000) + const int timeoutMS = renderMode == RenderMode::FAST ? 300 : 5000; + while (remaining > 0 && millis() - start < timeoutMS) { int len = client.read(dest + offset, remaining); if (len > 0) diff --git a/src/ReusableTileFetcher.hpp b/src/ReusableTileFetcher.hpp index 5738d50..27807b3 100644 --- a/src/ReusableTileFetcher.hpp +++ b/src/ReusableTileFetcher.hpp @@ -26,6 +26,7 @@ #include #include #include "MemoryBuffer.hpp" +#include "RenderMode.hpp" class ReusableTileFetcher { @@ -36,13 +37,14 @@ class ReusableTileFetcher ReusableTileFetcher(const ReusableTileFetcher &) = delete; ReusableTileFetcher &operator=(const ReusableTileFetcher &) = delete; - std::unique_ptr fetchToBuffer(const String &url, String &result); + std::unique_ptr fetchToBuffer(const String &url, String &result, RenderMode mode); void disconnect(); private: WiFiClient client; String currentHost; uint16_t currentPort = 80; + RenderMode renderMode; bool parseUrl(const String &url, String &host, String &path, uint16_t &port); bool ensureConnection(const String &host, uint16_t port, String &result); From 370439f607ac3125d1f875d025a7e6b7ca763c71 Mon Sep 17 00:00:00 2001 From: Cellie Date: Wed, 11 Jun 2025 14:47:57 +0200 Subject: [PATCH 05/14] Dont disconnect after every run --- src/OpenStreetMap-esp32.cpp | 2 -- src/OpenStreetMap-esp32.hpp | 1 - 2 files changed, 3 deletions(-) diff --git a/src/OpenStreetMap-esp32.cpp b/src/OpenStreetMap-esp32.cpp index 1c33dd6..34d46e6 100644 --- a/src/OpenStreetMap-esp32.cpp +++ b/src/OpenStreetMap-esp32.cpp @@ -422,8 +422,6 @@ void OpenStreetMap::tileFetcherTask(void *param) job.tile->busy = false; --osm->pendingJobs; - //if (!uxQueueMessagesWaiting(osm->jobQueue)) - //fetcher.disconnect(); } log_d("task on core %i exiting", xPortGetCoreID()); xTaskNotifyGive(osm->ownerTask); diff --git a/src/OpenStreetMap-esp32.hpp b/src/OpenStreetMap-esp32.hpp index fa0e492..027bc87 100644 --- a/src/OpenStreetMap-esp32.hpp +++ b/src/OpenStreetMap-esp32.hpp @@ -48,7 +48,6 @@ constexpr uint32_t OSM_JOB_QUEUE_SIZE = 50; constexpr bool OSM_FORCE_SINGLECORE = false; constexpr int OSM_SINGLECORE_NUMBER = 1; - static_assert(OSM_SINGLECORE_NUMBER < 2, "OSM_SINGLECORE_NUMBER must be 0 or 1 (ESP32 has only 2 cores)"); using tileList = std::vector>; From 4a099979bb82964710cef603e857bdb2c4db4313 Mon Sep 17 00:00:00 2001 From: Cellie Date: Wed, 11 Jun 2025 14:51:33 +0200 Subject: [PATCH 06/14] Use `OSM_MAX_HEADERLENGTH` to limit header mem allocations Fix codacy? --- src/RenderMode.hpp | 2 +- src/ReusableTileFetcher.cpp | 5 +++++ src/ReusableTileFetcher.hpp | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/RenderMode.hpp b/src/RenderMode.hpp index 77f4a0c..50d0960 100644 --- a/src/RenderMode.hpp +++ b/src/RenderMode.hpp @@ -4,4 +4,4 @@ enum class RenderMode { FAST, ACCURATE -}; \ No newline at end of file +}; diff --git a/src/ReusableTileFetcher.cpp b/src/ReusableTileFetcher.cpp index 3fe67fe..5ba85fd 100644 --- a/src/ReusableTileFetcher.cpp +++ b/src/ReusableTileFetcher.cpp @@ -106,6 +106,7 @@ bool ReusableTileFetcher::ensureConnection(const String &host, uint16_t port, St } currentHost = host; currentPort = port; + log_i("(Re)connected on core %i", xPortGetCoreID()); } return true; } @@ -113,6 +114,7 @@ bool ReusableTileFetcher::ensureConnection(const String &host, uint16_t port, St bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, String &result) { String line; + line.reserve(OSM_MAX_HEADERLENGTH); contentLength = 0; bool start = true; while (client.connected()) @@ -206,6 +208,9 @@ bool ReusableTileFetcher::readLineWithTimeout(String &line, uint32_t timeoutMs) return true; if (c != '\r') line += c; + + if (line.length() >= OSM_MAX_HEADERLENGTH) + return false; } taskYIELD(); } diff --git a/src/ReusableTileFetcher.hpp b/src/ReusableTileFetcher.hpp index 27807b3..b9f6092 100644 --- a/src/ReusableTileFetcher.hpp +++ b/src/ReusableTileFetcher.hpp @@ -28,6 +28,8 @@ #include "MemoryBuffer.hpp" #include "RenderMode.hpp" +constexpr int OSM_MAX_HEADERLENGTH = 256; + class ReusableTileFetcher { public: From 87385231c570d127d216666878fc57609f9784a0 Mon Sep 17 00:00:00 2001 From: Cellie Date: Wed, 11 Jun 2025 19:29:52 +0200 Subject: [PATCH 07/14] Clean up MemoryBuffer use --- src/MemoryBuffer.cpp | 5 ++++ src/MemoryBuffer.hpp | 53 +------------------------------------ src/OpenStreetMap-esp32.cpp | 6 ++--- src/ReusableTileFetcher.cpp | 18 ++++++------- src/ReusableTileFetcher.hpp | 2 +- 5 files changed, 19 insertions(+), 65 deletions(-) diff --git a/src/MemoryBuffer.cpp b/src/MemoryBuffer.cpp index 56a617e..d3bb001 100644 --- a/src/MemoryBuffer.cpp +++ b/src/MemoryBuffer.cpp @@ -44,3 +44,8 @@ bool MemoryBuffer::isAllocated() { return buffer_.get() != nullptr; } + +MemoryBuffer MemoryBuffer::empty() +{ + return MemoryBuffer(0); +} \ No newline at end of file diff --git a/src/MemoryBuffer.hpp b/src/MemoryBuffer.hpp index 9d0f551..d242284 100644 --- a/src/MemoryBuffer.hpp +++ b/src/MemoryBuffer.hpp @@ -27,66 +27,15 @@ #include #include -/** - * @class MemoryBuffer - * @brief A class that handles memory allocation and deallocation for a buffer. - * - * This class provides an RAII approach to manage a dynamically allocated buffer. It ensures that memory is - * allocated during object creation and automatically freed when the object goes out of scope. - * - * @note It is recommended to use the `MemoryBuffer` class when dealing with dynamic memory allocation, - * to avoid memory leaks and ensure proper memory management. - * - * Example use: - * ```cpp - * { - * MemoryBuffer buffer(512); - * if (buffer.isAllocated()) { // Check if allocated! - * // Access buffer here... - * } else { - * // Handle error (e.g., log error, retry later) - * } - * } // buffer automatically freed - * - * ``` - */ class MemoryBuffer { public: - /** - * @brief Constructs a `MemoryBuffer` object and allocates memory of the specified size. - * - * The constructor allocates memory of the specified size for the buffer. If allocation fails, - * the buffer will not be valid. - * - * @param size The size of the buffer in bytes. - * - * @example - * // Example usage of the constructor - * MemoryBuffer buffer(512); // Allocates a buffer of 512 bytes - */ explicit MemoryBuffer(size_t size); - /** - * @brief Returns a pointer to the allocated memory buffer. - * - * @return A pointer to the allocated memory, or `nullptr` if memory allocation failed. - */ uint8_t *get(); - - /** - * @brief Returns the size of the allocated buffer. - * - * @return The size of the allocated buffer in bytes. - */ size_t size() const; - - /** - * @brief Checks whether memory allocation was successful. - * - * @return `true` if memory was successfully allocated, `false` if the buffer is `nullptr`. - */ bool isAllocated(); + static MemoryBuffer empty(); private: size_t size_; diff --git a/src/OpenStreetMap-esp32.cpp b/src/OpenStreetMap-esp32.cpp index 34d46e6..c37c24b 100644 --- a/src/OpenStreetMap-esp32.cpp +++ b/src/OpenStreetMap-esp32.cpp @@ -360,12 +360,12 @@ bool OpenStreetMap::fetchTile(ReusableTileFetcher &fetcher, CachedTile &tile, ui if (currentProvider->requiresApiKey && strstr(url.c_str(), "{apiKey}")) url.replace("{apiKey}", currentProvider->apiKey); - const std::unique_ptr buffer = fetcher.fetchToBuffer(url, result, renderMode); - if (!buffer) + MemoryBuffer buffer = fetcher.fetchToBuffer(url, result, renderMode); + if (!buffer.isAllocated()) return false; PNG *png = getPNGCurrentCore(); - const int16_t rc = png->openRAM(buffer->get(), buffer->size(), PNGDraw); + const int16_t rc = png->openRAM(buffer.get(), buffer.size(), PNGDraw); if (rc != PNG_SUCCESS) { result = "PNG Decoder Error: " + String(rc); diff --git a/src/ReusableTileFetcher.cpp b/src/ReusableTileFetcher.cpp index 5ba85fd..b750ee7 100644 --- a/src/ReusableTileFetcher.cpp +++ b/src/ReusableTileFetcher.cpp @@ -42,7 +42,7 @@ void ReusableTileFetcher::disconnect() currentPort = 80; } -std::unique_ptr ReusableTileFetcher::fetchToBuffer(const String &url, String &result, RenderMode mode) +MemoryBuffer ReusableTileFetcher::fetchToBuffer(const String &url, String &result, RenderMode mode) { renderMode = mode; String host, path; @@ -50,26 +50,26 @@ std::unique_ptr ReusableTileFetcher::fetchToBuffer(const String &u if (!parseUrl(url, host, path, port)) { result = "Invalid URL"; - return nullptr; + return MemoryBuffer::empty(); } if (!ensureConnection(host, port, result)) - return nullptr; + return MemoryBuffer::empty(); sendHttpRequest(host, path); size_t contentLength = 0; if (!readHttpHeaders(contentLength, result)) - return nullptr; + return MemoryBuffer::empty(); - auto buffer = std::make_unique(contentLength); - if (!buffer->isAllocated()) + auto buffer = MemoryBuffer(contentLength); + if (!buffer.isAllocated()) { result = "Buffer allocation failed"; - return nullptr; + return MemoryBuffer::empty(); } - if (!readBody(*buffer, contentLength, result)) - return nullptr; + if (!readBody(buffer, contentLength, result)) + return MemoryBuffer::empty(); return buffer; } diff --git a/src/ReusableTileFetcher.hpp b/src/ReusableTileFetcher.hpp index b9f6092..37b3716 100644 --- a/src/ReusableTileFetcher.hpp +++ b/src/ReusableTileFetcher.hpp @@ -39,7 +39,7 @@ class ReusableTileFetcher ReusableTileFetcher(const ReusableTileFetcher &) = delete; ReusableTileFetcher &operator=(const ReusableTileFetcher &) = delete; - std::unique_ptr fetchToBuffer(const String &url, String &result, RenderMode mode); + MemoryBuffer fetchToBuffer(const String &url, String &result, RenderMode mode); void disconnect(); private: From 920e8afd273db56991d909145c210c252aad1181 Mon Sep 17 00:00:00 2001 From: Cellie Date: Wed, 11 Jun 2025 20:02:50 +0200 Subject: [PATCH 08/14] Cleanup --- src/ReusableTileFetcher.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ReusableTileFetcher.cpp b/src/ReusableTileFetcher.cpp index b750ee7..c334803 100644 --- a/src/ReusableTileFetcher.cpp +++ b/src/ReusableTileFetcher.cpp @@ -165,11 +165,11 @@ bool ReusableTileFetcher::readBody(MemoryBuffer &buffer, size_t contentLength, S size_t remaining = contentLength; size_t offset = 0; - unsigned long start = millis(); - const int timeoutMS = renderMode == RenderMode::FAST ? 300 : 5000; + const unsigned long start = millis(); + const int timeoutMS = (renderMode == RenderMode::FAST) ? 300 : 5000; while (remaining > 0 && millis() - start < timeoutMS) { - int len = client.read(dest + offset, remaining); + const int len = client.read(dest + offset, remaining); if (len > 0) { remaining -= len; @@ -198,12 +198,12 @@ bool ReusableTileFetcher::readBody(MemoryBuffer &buffer, size_t contentLength, S bool ReusableTileFetcher::readLineWithTimeout(String &line, uint32_t timeoutMs) { line = ""; - uint32_t start = millis(); + const uint32_t start = millis(); while ((millis() - start) < timeoutMs) { while (client.available()) { - char c = client.read(); + const char c = client.read(); if (c == '\n') return true; if (c != '\r') From 9b2e765b7bae48084e28290ed3827f17a47cdc38 Mon Sep 17 00:00:00 2001 From: Cellie Date: Wed, 11 Jun 2025 20:23:28 +0200 Subject: [PATCH 09/14] Cleanup --- src/OpenStreetMap-esp32.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenStreetMap-esp32.cpp b/src/OpenStreetMap-esp32.cpp index c37c24b..a3183d6 100644 --- a/src/OpenStreetMap-esp32.cpp +++ b/src/OpenStreetMap-esp32.cpp @@ -205,7 +205,7 @@ void OpenStreetMap::updateCache(const tileList &requiredTiles, uint8_t zoom, Til if (!jobs.empty()) { runJobs(jobs); - log_i("Ran %i jobs in %lu ms - %i ms/job", jobs.size(), millis() - startMS, (millis() - startMS) / jobs.size()); + log_d("Finished %i jobs in %lu ms - %i ms/job", jobs.size(), millis() - startMS, (millis() - startMS) / jobs.size()); } } From a6d78ccc8a819b30f2062e23fa6f8f599412c45d Mon Sep 17 00:00:00 2001 From: Cellie Date: Wed, 11 Jun 2025 21:40:35 +0200 Subject: [PATCH 10/14] Refactor `readBody()` --- src/ReusableTileFetcher.cpp | 61 +++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/src/ReusableTileFetcher.cpp b/src/ReusableTileFetcher.cpp index c334803..2738fe7 100644 --- a/src/ReusableTileFetcher.cpp +++ b/src/ReusableTileFetcher.cpp @@ -159,60 +159,69 @@ bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, String &result) return true; } +// TODO: Check this implementation with the one at https://github.com/CelliesProjects/OpenStreetMap-esp32/blob/3b933b792e5b9d7551d8154e9c5780f45af85bd7/src/OpenStreetMap-esp32.cpp bool ReusableTileFetcher::readBody(MemoryBuffer &buffer, size_t contentLength, String &result) { uint8_t *dest = buffer.get(); - size_t remaining = contentLength; - size_t offset = 0; + size_t readSize = 0; + unsigned long lastReadTime = millis(); + const unsigned long timeoutMs = (renderMode == RenderMode::FAST) ? 300 : 5000; - const unsigned long start = millis(); - const int timeoutMS = (renderMode == RenderMode::FAST) ? 300 : 5000; - while (remaining > 0 && millis() - start < timeoutMS) + while (readSize < contentLength) { - const int len = client.read(dest + offset, remaining); - if (len > 0) + size_t availableData = client.available(); + if (availableData == 0) { - remaining -= len; - offset += len; + if (millis() - lastReadTime >= timeoutMs) + { + result = "Timeout: " + String(timeoutMs) + " ms"; + disconnect(); + return false; + } + taskYIELD(); + continue; } - else if (len < 0) + + size_t remaining = contentLength - readSize; + size_t toRead = std::min(availableData, remaining); + + int bytesRead = client.readBytes(dest + readSize, toRead); + if (bytesRead > 0) { - result = "Read error"; - disconnect(); - return false; + readSize += bytesRead; + lastReadTime = millis(); } else taskYIELD(); } - if (remaining > 0) - { - result = "Incomplete read"; - disconnect(); - return false; - } - return true; } bool ReusableTileFetcher::readLineWithTimeout(String &line, uint32_t timeoutMs) { line = ""; - const uint32_t start = millis(); - while ((millis() - start) < timeoutMs) + line.reserve(OSM_MAX_HEADERLENGTH); + + const uint32_t deadline = millis() + timeoutMs; + + while (millis() < deadline) { while (client.available()) { - const char c = client.read(); + char c = client.read(); if (c == '\n') return true; if (c != '\r') + { + if (line.length() >= OSM_MAX_HEADERLENGTH) + return false; line += c; - - if (line.length() >= OSM_MAX_HEADERLENGTH) - return false; + } } taskYIELD(); } + return false; } + From ad26c0f767dd43f7a4ce37693cff3f61b27772fa Mon Sep 17 00:00:00 2001 From: Cellie Date: Thu, 12 Jun 2025 10:34:40 +0200 Subject: [PATCH 11/14] Cleanup --- src/OpenStreetMap-esp32.cpp | 15 +++++---------- src/ReusableTileFetcher.cpp | 4 ---- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/OpenStreetMap-esp32.cpp b/src/OpenStreetMap-esp32.cpp index a3183d6..cb87356 100644 --- a/src/OpenStreetMap-esp32.cpp +++ b/src/OpenStreetMap-esp32.cpp @@ -378,11 +378,15 @@ bool OpenStreetMap::fetchTile(ReusableTileFetcher &fetcher, CachedTile &tile, ui return false; } + tile.busy = false; currentInstance = this; currentTileBuffer = tile.buffer; const int decodeResult = png->decode(0, PNG_FAST_PALETTE); if (decodeResult != PNG_SUCCESS) { + tile.valid = false; + const size_t tileByteCount = currentProvider->tileSize * currentProvider->tileSize * 2; + memset(tile.buffer, 0, tileByteCount); result = "Decoding " + url + " failed with code: " + String(decodeResult); return false; } @@ -390,6 +394,7 @@ bool OpenStreetMap::fetchTile(ReusableTileFetcher &fetcher, CachedTile &tile, ui tile.x = x; tile.y = y; tile.z = zoom; + tile.valid = true; return true; } @@ -408,19 +413,9 @@ void OpenStreetMap::tileFetcherTask(void *param) String result; if (!osm->fetchTile(fetcher, *job.tile, job.x, job.y, job.z, result)) - { - const size_t tileByteCount = osm->currentProvider->tileSize * osm->currentProvider->tileSize * 2; - memset(job.tile->buffer, 0, tileByteCount); - job.tile->valid = false; log_e("Tile fetch failed: %s", result.c_str()); - } else - { - job.tile->valid = true; log_d("core %i fetched tile z=%u x=%lu, y=%lu in %lu ms", xPortGetCoreID(), job.z, job.x, job.y, millis() - startMS); - } - - job.tile->busy = false; --osm->pendingJobs; } log_d("task on core %i exiting", xPortGetCoreID()); diff --git a/src/ReusableTileFetcher.cpp b/src/ReusableTileFetcher.cpp index 2738fe7..c15e82b 100644 --- a/src/ReusableTileFetcher.cpp +++ b/src/ReusableTileFetcher.cpp @@ -159,7 +159,6 @@ bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, String &result) return true; } -// TODO: Check this implementation with the one at https://github.com/CelliesProjects/OpenStreetMap-esp32/blob/3b933b792e5b9d7551d8154e9c5780f45af85bd7/src/OpenStreetMap-esp32.cpp bool ReusableTileFetcher::readBody(MemoryBuffer &buffer, size_t contentLength, String &result) { uint8_t *dest = buffer.get(); @@ -194,15 +193,12 @@ bool ReusableTileFetcher::readBody(MemoryBuffer &buffer, size_t contentLength, S else taskYIELD(); } - return true; } bool ReusableTileFetcher::readLineWithTimeout(String &line, uint32_t timeoutMs) { line = ""; - line.reserve(OSM_MAX_HEADERLENGTH); - const uint32_t deadline = millis() + timeoutMs; while (millis() < deadline) From 0cc18b23591214b1c00d315c9e06364c051ab1b3 Mon Sep 17 00:00:00 2001 From: Cellie Date: Thu, 12 Jun 2025 15:05:00 +0200 Subject: [PATCH 12/14] Fix codacy complaints --- src/MemoryBuffer.cpp | 2 +- src/ReusableTileFetcher.cpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/MemoryBuffer.cpp b/src/MemoryBuffer.cpp index d3bb001..f07db98 100644 --- a/src/MemoryBuffer.cpp +++ b/src/MemoryBuffer.cpp @@ -48,4 +48,4 @@ bool MemoryBuffer::isAllocated() MemoryBuffer MemoryBuffer::empty() { return MemoryBuffer(0); -} \ No newline at end of file +} diff --git a/src/ReusableTileFetcher.cpp b/src/ReusableTileFetcher.cpp index c15e82b..b39537b 100644 --- a/src/ReusableTileFetcher.cpp +++ b/src/ReusableTileFetcher.cpp @@ -23,7 +23,7 @@ #include "ReusableTileFetcher.hpp" -ReusableTileFetcher::ReusableTileFetcher() {} +ReusableTileFetcher::ReusableTileFetcher() { renderMode = RenderMode::ACCURATE; } ReusableTileFetcher::~ReusableTileFetcher() { disconnect(); } void ReusableTileFetcher::sendHttpRequest(const String &host, const String &path) @@ -220,4 +220,3 @@ bool ReusableTileFetcher::readLineWithTimeout(String &line, uint32_t timeoutMs) return false; } - From 1649bc8afa85e20482325514005ce5565c5852d4 Mon Sep 17 00:00:00 2001 From: Cellie Date: Thu, 12 Jun 2025 15:28:12 +0200 Subject: [PATCH 13/14] Update README.md --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index d967425..2ff3352 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,23 @@ If you encounter a problem or want to request support for a new provider, please char *getProviderName() ``` +### Set the render mode + +```c++ +void setRenderMode(RenderMode mode) +``` + +Available modes: + +- `RenderMode::ACCURATE` (default) +Downloads map tiles **without a timeout**, ensuring a complete map with **no missing tiles** in most cases. +Best suited for reliability and full-quality rendering. + +- `RenderMode::FAST` +Downloads map tiles **with a timeout**. +This mode can produce the map **more quickly**, but some **tiles may be missing** if a request times out. +Ideal when operating under time constraints. + ## Example code ### Example returning the default 320x240 map From 53c555d36cee7633fbc3554cb330023bf14ebb96 Mon Sep 17 00:00:00 2001 From: Cellie Date: Thu, 12 Jun 2025 16:42:20 +0200 Subject: [PATCH 14/14] Fixed tile handling --- src/OpenStreetMap-esp32.cpp | 14 +++++++++----- src/ReusableTileFetcher.cpp | 27 +++++++++++++-------------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/OpenStreetMap-esp32.cpp b/src/OpenStreetMap-esp32.cpp index cb87356..0594164 100644 --- a/src/OpenStreetMap-esp32.cpp +++ b/src/OpenStreetMap-esp32.cpp @@ -378,15 +378,11 @@ bool OpenStreetMap::fetchTile(ReusableTileFetcher &fetcher, CachedTile &tile, ui return false; } - tile.busy = false; currentInstance = this; currentTileBuffer = tile.buffer; const int decodeResult = png->decode(0, PNG_FAST_PALETTE); if (decodeResult != PNG_SUCCESS) { - tile.valid = false; - const size_t tileByteCount = currentProvider->tileSize * currentProvider->tileSize * 2; - memset(tile.buffer, 0, tileByteCount); result = "Decoding " + url + " failed with code: " + String(decodeResult); return false; } @@ -394,7 +390,6 @@ bool OpenStreetMap::fetchTile(ReusableTileFetcher &fetcher, CachedTile &tile, ui tile.x = x; tile.y = y; tile.z = zoom; - tile.valid = true; return true; } @@ -413,9 +408,18 @@ void OpenStreetMap::tileFetcherTask(void *param) String result; if (!osm->fetchTile(fetcher, *job.tile, job.x, job.y, job.z, result)) + { log_e("Tile fetch failed: %s", result.c_str()); + job.tile->valid = false; + const size_t tileByteCount = osm->currentProvider->tileSize * osm->currentProvider->tileSize * 2; + memset(job.tile->buffer, 0, tileByteCount); + } else + { + job.tile->valid = true; log_d("core %i fetched tile z=%u x=%lu, y=%lu in %lu ms", xPortGetCoreID(), job.z, job.x, job.y, millis() - startMS); + } + job.tile->busy = false; --osm->pendingJobs; } log_d("task on core %i exiting", xPortGetCoreID()); diff --git a/src/ReusableTileFetcher.cpp b/src/ReusableTileFetcher.cpp index b39537b..f43193c 100644 --- a/src/ReusableTileFetcher.cpp +++ b/src/ReusableTileFetcher.cpp @@ -98,8 +98,8 @@ bool ReusableTileFetcher::ensureConnection(const String &host, uint16_t port, St if (!client.connected() || host != currentHost || port != currentPort) { disconnect(); - client.setConnectionTimeout(renderMode == RenderMode::FAST ? 100 : 1000); - if (!client.connect(host.c_str(), port)) + client.setConnectionTimeout(renderMode == RenderMode::FAST ? 100 : 5000); + if (!client.connect(host.c_str(), port, renderMode == RenderMode::FAST ? 100 : 5000)) { result = "Connection failed to " + host; return false; @@ -119,7 +119,7 @@ bool ReusableTileFetcher::readHttpHeaders(size_t &contentLength, String &result) bool start = true; while (client.connected()) { - if (!readLineWithTimeout(line, renderMode == RenderMode::FAST ? 100 : 1000)) + if (!readLineWithTimeout(line, renderMode == RenderMode::FAST ? 300 : 5000)) { result = "Header timeout"; disconnect(); @@ -203,20 +203,19 @@ bool ReusableTileFetcher::readLineWithTimeout(String &line, uint32_t timeoutMs) while (millis() < deadline) { - while (client.available()) + if (client.available()) { - char c = client.read(); - if (c == '\n') - return true; - if (c != '\r') - { - if (line.length() >= OSM_MAX_HEADERLENGTH) - return false; - line += c; - } + String part = client.readStringUntil('\n'); + if ((line.length() + part.length()) >= OSM_MAX_HEADERLENGTH) + return false; + + line += part; + return true; // Found end of line } taskYIELD(); } - return false; + return false; // Timed out } + +