diff --git a/CMakeLists.txt b/CMakeLists.txt index 4b9b8af..ed2de1d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,7 +72,13 @@ if(ENABLE_DOCTESTS) enable_testing() add_executable(tests ${sources_test}) target_link_libraries(tests PRIVATE DeviceUI doctest::doctest lvgl::lvgl LovyanGFX Portduino Protobufs) - target_include_directories(tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_include_directories(tests PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR}/locale + ${CMAKE_CURRENT_SOURCE_DIR}/portduino + ${CMAKE_CURRENT_SOURCE_DIR}/generated/${GENERATED_VIEW} + ) set_target_properties(tests PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) add_test(NAME tests COMMAND tests) endif() \ No newline at end of file diff --git a/cmake/Portduino.cmake b/cmake/Portduino.cmake index 91509a3..befd1e5 100644 --- a/cmake/Portduino.cmake +++ b/cmake/Portduino.cmake @@ -11,6 +11,7 @@ include_directories(${portduino_SOURCE_DIR}/cores/portduino/FS) include_directories(${portduino_SOURCE_DIR}/cores/arduino) include_directories(${portduino_SOURCE_DIR}/libraries/SPI/src) include_directories(${portduino_SOURCE_DIR}/libraries/Wire/src) +include_directories(${portduino_SOURCE_DIR}/libraries/WiFi/src) include_directories(${portduino_SOURCE_DIR}/ArduinoCore-API/api) # Specify source files for Portduino diff --git a/include/comms/EthClient.h b/include/comms/EthClient.h new file mode 100644 index 0000000..6f54774 --- /dev/null +++ b/include/comms/EthClient.h @@ -0,0 +1,37 @@ +#pragma once + +#include "Client.h" +#include "comms/SerialClient.h" +#include "util/SharedQueue.h" +#include + +#ifndef SERVER_PORT +#define SERVER_PORT 4403 +#endif + +class EthClient : public SerialClient +{ + public: + EthClient(const char *serverName = "localhost", uint16_t port = SERVER_PORT); + void init(void) override; + bool connect(void) override; + bool disconnect(void) override; + bool isConnected(void) override; + // bool send(meshtastic_ToRadio &&to) override; + meshtastic_FromRadio receive(void) override; + virtual ~EthClient(); + + protected: + // low-level send method to write the encoded buffer to ethernet + bool send(const uint8_t *buf, size_t len) override; + + // low-level receive method, periodically being called via thread + size_t receive(uint8_t *buf, size_t space_left) override; + + Client *client; + uint8_t mac[6]; + IPAddress localIP; + IPAddress serverIP; + const char *server; + uint16_t serverPort; +}; \ No newline at end of file diff --git a/include/comms/IClientBase.h b/include/comms/IClientBase.h index 524279e..e8df870 100644 --- a/include/comms/IClientBase.h +++ b/include/comms/IClientBase.h @@ -2,6 +2,7 @@ #include "mesh-pb-constants.h" #include "stdint.h" +#include /** * @brief Communication interface to be implemented by the user of the device-ui @@ -13,16 +14,24 @@ class IClientBase { public: + enum ConnectionStatus { eDisconnected = 0, eConnected, eConnecting, eDisconnecting, eError }; + + using NotifyCallback = std::function; + virtual void init(void) = 0; virtual bool connect(void) = 0; virtual bool disconnect(void) = 0; virtual bool isConnected(void) = 0; + virtual bool isStandalone(void) = 0; virtual bool sleep(int16_t pin) { return false; } virtual bool send(meshtastic_ToRadio &&to) = 0; virtual meshtastic_FromRadio receive(void) = 0; virtual ~IClientBase(){}; + virtual void task_handler(void){}; + virtual void setNotifyCallback(NotifyCallback notifyConnectionStatus) = 0; + protected: IClientBase() = default; }; diff --git a/include/comms/MeshEnvelope.h b/include/comms/MeshEnvelope.h index a9b6df2..a540ffd 100644 --- a/include/comms/MeshEnvelope.h +++ b/include/comms/MeshEnvelope.h @@ -31,6 +31,6 @@ class MeshEnvelope ~MeshEnvelope() {} - private: + protected: std::vector envelope; }; \ No newline at end of file diff --git a/include/comms/PacketClient.h b/include/comms/PacketClient.h index ef3b40d..ad5d5bd 100644 --- a/include/comms/PacketClient.h +++ b/include/comms/PacketClient.h @@ -17,12 +17,15 @@ class PacketClient : public IClientBase bool connect(void) override; bool disconnect(void) override; bool isConnected(void) override; + bool isStandalone(void) override; bool send(meshtastic_ToRadio &&to) override; meshtastic_FromRadio receive(void) override; virtual bool hasData() const; virtual bool available() const; + void task_handler(void) override{}; + void setNotifyCallback(NotifyCallback notifyConnectionStatus) override{}; virtual ~PacketClient() = default; protected: diff --git a/include/comms/SerialClient.h b/include/comms/SerialClient.h index c4b56a2..833919a 100644 --- a/include/comms/SerialClient.h +++ b/include/comms/SerialClient.h @@ -7,14 +7,18 @@ class SerialClient : public IClientBase { public: - SerialClient(void); + SerialClient(const char *name = "serial"); void init(void) override; bool sleep(int16_t pin); bool connect(void) override; bool disconnect(void) override; bool isConnected(void) override; + bool isStandalone(void) override; bool send(meshtastic_ToRadio &&to) override; meshtastic_FromRadio receive(void) override; + + void task_handler(void) override; + void setNotifyCallback(NotifyCallback notifyConnectionStatus) override; virtual ~SerialClient(); protected: @@ -30,17 +34,30 @@ class SerialClient : public IClientBase // received a full packet from serial, process it virtual void handleSendPacket(void); + // status handling, to be called by derived classes + void setConnectionStatus(ConnectionStatus status, const char *info = nullptr); + // thread handling stuff and data static void task_loop(void *); static SerialClient *instance; - volatile bool shutdown; + // local buffered data + size_t pb_size; uint8_t *buffer; - // local data - volatile bool connected; - size_t pb_size; - size_t bytes_read; + // callback for connection status + NotifyCallback notifyConnectionStatus; + // reported status + ConnectionStatus connectionStatus; + // status of client connection (set by derived class) + volatile ConnectionStatus clientStatus; + // status details (set by derived class) + const char *connectionInfo; + + // announce client shutdown + volatile bool shutdown; + // instance thread name + const char *threadName; // receiver and sender queue SharedQueue queue; diff --git a/include/comms/UARTClient.h b/include/comms/UARTClient.h index 9bb22e9..2693c40 100644 --- a/include/comms/UARTClient.h +++ b/include/comms/UARTClient.h @@ -15,9 +15,6 @@ class UARTClient : public SerialClient meshtastic_FromRadio receive(void) override; virtual ~UARTClient(); - bool isActive(void) const; - const char *getConnectionInfo(void) const; - protected: // low-level send method to write the buffer to serial bool send(const uint8_t *buf, size_t len) override; @@ -25,6 +22,7 @@ class UARTClient : public SerialClient // low-level receive method, periodically being called via thread size_t receive(uint8_t *buf, size_t space_left) override; + bool isActive; HardwareSerial *_serial; time_t lastReceived; }; \ No newline at end of file diff --git a/include/graphics/DeviceScreen.h b/include/graphics/DeviceScreen.h index 9f54bcf..c55afe0 100644 --- a/include/graphics/DeviceScreen.h +++ b/include/graphics/DeviceScreen.h @@ -26,7 +26,7 @@ class DeviceScreen int prepareSleep(void *); int wakeUp(esp_sleep_wakeup_cause_t cause); #endif - void sleep(uint32_t time = 5); + void sleep(uint32_t sleepTime = 5); private: DeviceScreen(const DisplayDriverConfig *cfg); diff --git a/include/graphics/common/MeshtasticView.h b/include/graphics/common/MeshtasticView.h index 7858369..5efd1c7 100644 --- a/include/graphics/common/MeshtasticView.h +++ b/include/graphics/common/MeshtasticView.h @@ -47,7 +47,8 @@ class MeshtasticView : public DeviceGUI eMessagesRestored, eRunning, eScreenSaving, - eRebooting + eRebooting, + eDisconnected }; enum eRole { @@ -120,8 +121,13 @@ class MeshtasticView : public DeviceGUI virtual void configCompleted(void) { - configComplete = true; - state = eConfigComplete; + if (!configComplete) { + configComplete = true; + state = eConfigComplete; + } else { + // we came here from resync + state = eRunning; + } } virtual void handleResponse(uint32_t from, uint32_t id, const meshtastic_Routing &routing, const meshtastic_MeshPacket &p) {} @@ -130,8 +136,11 @@ class MeshtasticView : public DeviceGUI virtual void packetReceived(const meshtastic_MeshPacket &p); virtual void newMessage(uint32_t from, uint32_t to, uint8_t ch, const char *msg, uint32_t &msgtime, bool restore = false) {} virtual void restoreMessage(const LogMessage &msg) {} + virtual void notifyRestoreMessages(int32_t percentage) {} virtual void notifyMessagesRestored(void); + virtual void notifyConnected(const char *info){}; + virtual void notifyDisconnected(const char *info){}; virtual void notifyResync(bool show); virtual void notifyReboot(bool show); virtual void notifyShutdown(void); diff --git a/include/graphics/common/SdCard.h b/include/graphics/common/SdCard.h index deba072..a7b43e2 100644 --- a/include/graphics/common/SdCard.h +++ b/include/graphics/common/SdCard.h @@ -70,7 +70,7 @@ class SDCard : public ISdCard uint64_t usedBytes(void) override; uint64_t freeBytes(void) override; uint64_t cardSize(void) override; - bool format(void){}; + bool format(void) override { return false; }; std::set loadMapStyles(const char *folder) override; virtual ~SDCard(void); diff --git a/include/graphics/common/ViewController.h b/include/graphics/common/ViewController.h index 0bb380b..aa8209a 100644 --- a/include/graphics/common/ViewController.h +++ b/include/graphics/common/ViewController.h @@ -14,6 +14,8 @@ class ViewController virtual void runOnce(void); virtual bool sleep(int16_t pin); virtual void processEvent(void); + virtual bool isStandalone(void); + virtual void stop(void); // device config virtual uint32_t requestDeviceUIConfig(void); @@ -105,6 +107,7 @@ class ViewController IClientBase *client; uint32_t sendId; uint32_t myNodeNum; + time_t lastrun1; time_t lastrun10; time_t restoreTimer; bool setupDone; // true if ui config has been loaded and screens are setup in the view diff --git a/include/graphics/driver/TFTDriver.h b/include/graphics/driver/TFTDriver.h index 56b0644..ab26ffa 100644 --- a/include/graphics/driver/TFTDriver.h +++ b/include/graphics/driver/TFTDriver.h @@ -31,6 +31,7 @@ template void TFTDriver::init(DeviceGUI *gui) ESP_ERROR_CHECK(esp_timer_start_periodic(lvgl_tick_timer, 20000)); #endif #elif defined(ARCH_PORTDUINO) - lv_tick_set_cb([]() -> uint32_t { return millis(); }); + // for linux we use lv_tick_inc() in DeviceGUI::task_handler() + // lv_tick_set_cb([]() -> uint32_t { return millis(); }); #endif } diff --git a/include/graphics/map/MapPanel.h b/include/graphics/map/MapPanel.h index aed97e0..08c593e 100644 --- a/include/graphics/map/MapPanel.h +++ b/include/graphics/map/MapPanel.h @@ -31,6 +31,8 @@ class MapPanel void setZoom(uint8_t zoom); // follow GPS void setLocked(bool lock); + // reset panel size to actual dimensions + void updateDimensions(void); // positioning // set new home position according current diff --git a/include/graphics/view/TFT/TFTView_320x240.h b/include/graphics/view/TFT/TFTView_320x240.h index fa197fe..c61939e 100644 --- a/include/graphics/view/TFT/TFTView_320x240.h +++ b/include/graphics/view/TFT/TFTView_320x240.h @@ -76,6 +76,8 @@ class TFTView_320x240 : public MeshtasticView void handlePositionResponse(uint32_t from, uint32_t request_id, int32_t rx_rssi, float rx_snr, bool isNeighbor) override; void notifyRestoreMessages(int32_t percentage) override; void notifyMessagesRestored(void) override; + void notifyConnected(const char *info) override; + void notifyDisconnected(const char *info) override; void notifyResync(bool show) override; void notifyReboot(bool show) override; void notifyShutdown(void) override; @@ -132,7 +134,7 @@ class TFTView_320x240 : public MeshtasticView // initialize all ui screens virtual void init_screens(void); // update custom display string on boot screen - virtual void updateBootMessage(void); + virtual void updateBootMessage(const char *); // show initial setup panel to configure region and name virtual void requestSetup(void); // patch widgets on generated screens @@ -261,6 +263,8 @@ class TFTView_320x240 : public MeshtasticView void onTracerouteCallback(const ResponseHandler::Request &, ResponseHandler::EventType, int32_t); // lvgl timer callbacks + static void timer_event_reboot(lv_timer_t *timer); + static void timer_event_shutdown(lv_timer_t *timer); static void timer_event_programming_mode(lv_timer_t *timer); // lvgl event callbacks diff --git a/source/comms/ethernet/EthClient.cpp b/source/comms/ethernet/EthClient.cpp new file mode 100644 index 0000000..8c27226 --- /dev/null +++ b/source/comms/ethernet/EthClient.cpp @@ -0,0 +1,164 @@ +#include "comms/EthClient.h" +#include "Arduino.h" +#include "comms/MeshEnvelope.h" +#include "util/ILog.h" + +#if defined(ARCH_PORTDUINO) +#include "WiFi.h" +#include "WiFiClient.h" +// as of now Portduino implements the ethernet functionality via WiFiClient +class EthernetClient : public WiFiClient +{ + public: + EthernetClient() : WiFiClient(0) {} + virtual ~EthernetClient() {} +}; +#elif HAS_ETHERNET +#include "Ethernet.h" +#include "EthernetClient.h" +#endif + +#define MAX_PACKET_SIZE 284 + +extern const uint8_t MT_MAGIC_0; + +EthClient::EthClient(const char *serverName, uint16_t port) + : SerialClient("eth"), client(nullptr), server(serverName), serverPort(port) +{ +} + +void EthClient::init(void) +{ + ILOG_DEBUG("EthClient::init()"); +#if HAS_ETHERNET + client = new EthernetClient(); + + // Ethernet.init(SS_PIN); // TODO for ESP32 + Ethernet.begin(mac, localIP); + + // Check for Ethernet hardware present + if (Ethernet.hardwareStatus() == EthernetNoHardware) { + ILOG_ERROR("Ethernet device not found!"); + } else if (Ethernet.linkStatus() == LinkOFF) { + ILOG_WARN("Ethernet cable not connected!"); + } +#elif defined(ARCH_PORTDUINO) + client = new EthernetClient(); +#elif HAS_WIFI + // client = new WiFiClient(); + // WiFi.begin(ssid); + // if (WiFi.status() != WL_CONNECTED) { + // ILOG_ERROR("WiFi/Eth device not found!"); + // } +#endif + SerialClient::init(); +} + +/** + * @brief Connect to the server via ethernet socket communication links + * + * @return true - connected + * @return false - not connected + */ +bool EthClient::connect(void) +{ + if (clientStatus != eConnected) { + setConnectionStatus(eConnecting, "Connecting..."); + if (client->connect(server, SERVER_PORT)) { + ILOG_INFO("EthClient connected!"); + setConnectionStatus(eConnected, "Connected!"); + } else { + ILOG_WARN("EthClient connection failed!"); + setConnectionStatus(eError, "Connection failed!"); + } + } + return clientStatus == eConnected; +} + +bool EthClient::disconnect(void) +{ + ILOG_DEBUG("EthClient disconnecting..."); + client->stop(); + setConnectionStatus(eDisconnected, "Disconnected!"); + return SerialClient::disconnect(); +} + +bool EthClient::isConnected(void) +{ + bool result = clientStatus == eConnected && client->connected(); + return result; +} + +meshtastic_FromRadio EthClient::receive(void) +{ + return SerialClient::receive(); +} + +EthClient::~EthClient() +{ + disconnect(); + delete client; +}; + +// --- protected part --- + +/** + * @brief Send a packet to the server + * + * @param buf - pointer to the buffer + * @param len - length of the buffer + * @return true - send ok + * @return false - send failed + */ +bool EthClient::send(const uint8_t *buf, size_t len) +{ + ILOG_TRACE("sending %d bytes to radio", len); + int32_t wrote = 0; +#ifdef ARCH_PORTDUINO + try { + wrote = client->write(buf, len); + } catch (const std::exception &e) { + ILOG_ERROR("caught exception: %s", e.what()); + } +#else + wrote = client->write(buf, len); +#endif + + if (wrote != (int32_t)len) { + if (wrote < 0) { + ILOG_ERROR("send failed, disconnecting!"); + setConnectionStatus(eDisconnected); + return false; + } + ILOG_ERROR("only %d bytes were sent this time", wrote); + return false; + } + return wrote == (int32_t)len; +} + +/** + * @brief Receive a packet from the server if available + * + * @param buf - pointer to the buffer + * @param space_left - space left in the buffer + * @return size_t - number of bytes read + */ +size_t EthClient::receive(uint8_t *buf, size_t space_left) +{ + int bytes_read = 0; + while (client->available() && bytes_read < MAX_PACKET_SIZE) { + int read = client->read(); + if (read >= 0) { + *buf++ = read & 0xff; + if (++bytes_read >= (int)space_left) { + ILOG_WARN("buffer overflow! (%d / %d)", bytes_read, space_left); + break; + } + } else + break; // error reading + } + if (bytes_read > 0) { + ILOG_TRACE("received %d bytes via tcp", bytes_read); + } + return bytes_read; +} diff --git a/source/comms/packet/PacketClient.cpp b/source/comms/packet/PacketClient.cpp index 8ea5cf8..694c740 100644 --- a/source/comms/packet/PacketClient.cpp +++ b/source/comms/packet/PacketClient.cpp @@ -31,6 +31,11 @@ bool PacketClient::isConnected(void) return is_connected; } +bool PacketClient::isStandalone(void) +{ + return false; +} + int PacketClient::connect(SharedQueue *_queue) { if (!queue) { diff --git a/source/comms/serial/MeshEnvelope.cpp b/source/comms/serial/MeshEnvelope.cpp index 5a95e7d..c509f6f 100644 --- a/source/comms/serial/MeshEnvelope.cpp +++ b/source/comms/serial/MeshEnvelope.cpp @@ -93,7 +93,7 @@ bool MeshEnvelope::validate(uint8_t *pb_buf, size_t &pb_size, size_t &payload_le } // re-align magic header to front of buffer - ILOG_TRACE("Skipping first %d bytes (%02x%02x%02x...)", startpos, (int)pb_buf[0], (int)pb_buf[1], (int)pb_buf[2]); + ILOG_WARN("Skipping first %d bytes (%02x%02x%02x...)", startpos, (int)pb_buf[0], (int)pb_buf[1], (int)pb_buf[2]); pb_size -= startpos; memmove(&pb_buf[0], &pb_buf[startpos], pb_size); } @@ -124,5 +124,6 @@ void MeshEnvelope::invalidate(uint8_t *pb_buf, size_t &pb_size, size_t &payload_ } else { pb_size -= payload_len + MT_HEADER_SIZE; } + ILOG_TRACE("Invalidating %d bytes, pb_size=%d", payload_len + MT_HEADER_SIZE, pb_size); memmove(&pb_buf[0], &pb_buf[payload_len + MT_HEADER_SIZE], pb_size); } \ No newline at end of file diff --git a/source/comms/serial/SerialClient.cpp b/source/comms/serial/SerialClient.cpp index b818421..c489d24 100644 --- a/source/comms/serial/SerialClient.cpp +++ b/source/comms/serial/SerialClient.cpp @@ -17,9 +17,18 @@ #endif #include "Arduino.h" +#ifndef SLEEP_TIME_IDLE +#define SLEEP_TIME_IDLE 50 // ms +#endif +#ifndef SLEEP_TIME_ACTIVE +#define SLEEP_TIME_ACTIVE 2 // ms +#endif + SerialClient *SerialClient::instance = nullptr; -SerialClient::SerialClient(void) : shutdown(false), connected(false), pb_size(0), bytes_read(0) +SerialClient::SerialClient(const char *name) + : pb_size(0), clientStatus(eDisconnected), connectionStatus(eDisconnected), connectionInfo(nullptr), shutdown(false), + notifyConnectionStatus(nullptr), threadName(name) { buffer = new uint8_t[PB_BUFSIZE + MT_HEADER_SIZE]; instance = this; @@ -27,11 +36,18 @@ SerialClient::SerialClient(void) : shutdown(false), connected(false), pb_size(0) void SerialClient::init(void) { - ILOG_TRACE("SerialClient::init() creating serial task"); + ILOG_TRACE("SerialClient::init() creating %s task", threadName); #if defined(HAS_FREE_RTOS) || defined(ARCH_ESP32) - xTaskCreateUniversal(task_loop, "serial", 8192, NULL, 2, NULL, 0); + xTaskCreateUniversal(task_loop, threadName, 8192, NULL, 1, NULL, 0); #elif defined(ARCH_PORTDUINO) - new std::thread([] { instance->task_loop(nullptr); }); + new std::thread([] { +#ifdef __APPLE__ + pthread_setname_np(threadName); +#else + pthread_setname_np(pthread_self(), instance->threadName); +#endif + instance->task_loop(nullptr); + }); #else // #error "unsupported architecture" #endif @@ -94,25 +110,42 @@ bool SerialClient::sleep(int16_t pin) bool SerialClient::connect(void) { - ILOG_ERROR("SerialClient::connect() not implemented"); - return false; + clientStatus = eConnected; + return clientStatus == eConnected; } bool SerialClient::disconnect(void) { - connected = false; - return connected; + clientStatus = eDisconnected; + return clientStatus == eDisconnected; } bool SerialClient::isConnected(void) { - return connected; + return clientStatus == eConnected; +} + +void SerialClient::setConnectionStatus(ConnectionStatus status, const char *info) +{ + ILOG_TRACE("SerialClient::setConnectionStatus() status=%d, info=%s", status, info); + this->clientStatus = status; + this->connectionInfo = info; +} + +void SerialClient::setNotifyCallback(NotifyCallback notifyConnectionStatus) +{ + this->notifyConnectionStatus = notifyConnectionStatus; +} + +bool SerialClient::isStandalone(void) +{ + return true; } bool SerialClient::send(meshtastic_ToRadio &&to) { static uint32_t id = 1; - ILOG_TRACE("SerialClient::send() push packet %d to queue", id); + ILOG_TRACE("SerialClient::send() push packet %d to server", id); queue.clientSend(DataPacket(id++, to)); return false; } @@ -121,15 +154,31 @@ meshtastic_FromRadio SerialClient::receive(void) { if (queue.serverQueueSize() != 0) { ILOG_TRACE("SerialClient::receive() got a packet from queue"); - auto p = queue.clientReceive()->move(); - return static_cast *>(p.get())->getData(); + auto p = queue.clientReceive(); + if (p) { + return static_cast *>(p->move().get())->getData(); + } else { + ILOG_ERROR("SerialClient::receive() no packet in queue"); + } } return meshtastic_FromRadio(); } +void SerialClient::task_handler(void) +{ + // check for connection status change + if (notifyConnectionStatus) { + if (connectionStatus != clientStatus || (connectionStatus == eConnected && !isConnected())) { + connectionStatus = clientStatus; + notifyConnectionStatus(connectionStatus, connectionInfo); + } + } +} + SerialClient::~SerialClient() { shutdown = true; + delete[] buffer; }; // --- protected part --- @@ -158,18 +207,23 @@ void SerialClient::handlePacketReceived(void) meshtastic_FromRadio fromRadio = envelope.decode(); if (fromRadio.which_payload_variant != 0) { queue.serverSend(DataPacket(fromRadio.id, fromRadio)); + ILOG_TRACE("server queue size=%d", queue.serverQueueSize()); } } void SerialClient::handleSendPacket(void) { - auto p = queue.serverReceive()->move(); - meshtastic_ToRadio toRadio = static_cast *>(p.get())->getData(); - // meshtastic_ToRadio toRadio{std::move(static_cast *>(p.get())->getData())}; - MeshEnvelope envelope; - const std::vector &pb_buf = envelope.encode(toRadio); - if (pb_buf.size() > 0) { - send(&pb_buf[0], pb_buf.size()); + auto p = queue.serverReceive(); + if (p) { + meshtastic_ToRadio toRadio = static_cast *>(p->move().get())->getData(); + // meshtastic_ToRadio toRadio{std::move(static_cast *>(p.get())->getData())}; + MeshEnvelope envelope; + const std::vector &pb_buf = envelope.encode(toRadio); + if (pb_buf.size() > 0) { + send(&pb_buf[0], pb_buf.size()); + } + } else { + ILOG_ERROR("SerialClient::handleSendPacket() no packet in queue!"); } } @@ -179,30 +233,37 @@ void SerialClient::handleSendPacket(void) */ void SerialClient::task_loop(void *) { - size_t space_left = PB_BUFSIZE - instance->pb_size; + delay(1000); ILOG_TRACE("SerialClient::task_loop running"); - while (!instance->shutdown) { - if (instance->connected) { + int sleep_time = SLEEP_TIME_IDLE; + size_t space_left = PB_BUFSIZE - instance->pb_size; + if (instance->clientStatus == eConnected) { size_t bytes_read = instance->receive(&instance->buffer[instance->pb_size], space_left); - instance->pb_size += bytes_read; - size_t payload_len; - bool valid = MeshEnvelope::validate(instance->buffer, instance->pb_size, payload_len); - - if (valid) { - instance->handlePacketReceived(); - MeshEnvelope::invalidate(instance->buffer, instance->pb_size, payload_len); + if (bytes_read > 0) { + instance->pb_size += bytes_read; + size_t payload_len; + bool valid = false; + do { + valid = MeshEnvelope::validate(instance->buffer, instance->pb_size, payload_len); + if (valid) { + instance->handlePacketReceived(); + MeshEnvelope::invalidate(instance->buffer, instance->pb_size, payload_len); + } + } while (valid && instance->pb_size > 0); + sleep_time = SLEEP_TIME_ACTIVE; } - + } + if (instance->clientStatus == eConnected) { // send a packet if available - if (instance->queue.clientQueueSize() != 0) { + if (instance->queue.clientQueueSize() > 0) { instance->handleSendPacket(); } } #if defined(HAS_FREE_RTOS) || defined(ARCH_ESP32) - vTaskDelay((TickType_t)5); // yield, do not remove + vTaskDelay((TickType_t)sleep_time); // yield, do not remove #else - delay(5); + std::this_thread::sleep_for(std::chrono::milliseconds(sleep_time)); #endif } } diff --git a/source/comms/serial/UARTClient.cpp b/source/comms/serial/UARTClient.cpp index ecd3bfc..791c6ee 100644 --- a/source/comms/serial/UARTClient.cpp +++ b/source/comms/serial/UARTClient.cpp @@ -7,13 +7,15 @@ #define SERIAL_BAUD 38400 #endif +#define CONNECTION_TIMEOUT 60 // seconds #define RX_BUFFER 1024 -#define TIMEOUT 250 -#define ACK 1 +#define TIMEOUT 250 // ms + +#define MAX_PACKET_SIZE 284 extern const uint8_t MT_MAGIC_0; -UARTClient::UARTClient(void) : _serial(nullptr), lastReceived(0) {} +UARTClient::UARTClient(void) : SerialClient("uart"), isActive(false), _serial(nullptr) {} /** * @brief init serial interface @@ -51,16 +53,20 @@ void UARTClient::init(void) _serial->setPins(SERIAL_RX, SERIAL_TX); ILOG_INFO("UARTClient::setPins rx=%d, tx=%d with %d baud", SERIAL_RX, SERIAL_TX, SERIAL_BAUD); #endif + time(&lastReceived); SerialClient::init(); } bool UARTClient::connect(void) { - if (!connected) { + if (clientStatus != eConnected) { + ILOG_DEBUG("UARTClient connecting..."); + setConnectionStatus(eConnecting, "Connecting..."); time_t timeout = millis(); while (!*_serial) { if ((millis() - timeout) > 5) { - connected = false; + setConnectionStatus(eError, "Connection failed!"); + ILOG_WARN("UARTClient connection failed!"); return false; } } @@ -73,22 +79,37 @@ bool UARTClient::connect(void) skipped++; delay(1); } + isActive = true; } - connected = true; - ILOG_TRACE("UARTClient::connect, skipped %d bytes", skipped); + if (isActive) { + setConnectionStatus(eConnected, "Connected!"); + ILOG_INFO("UARTClient connected! (skipped %d bytes)", skipped); + } else { + // pretend to be connected and start sending data + clientStatus = eConnected; + } } - return true; + return clientStatus == eConnected; } bool UARTClient::disconnect(void) { + ILOG_DEBUG("UARTClient disconnecting..."); + isActive = false; + setConnectionStatus(eDisconnected, "Disconnected"); return SerialClient::disconnect(); } bool UARTClient::isConnected(void) { - return *_serial && connected; + time_t now; + time(&now); + if (now - lastReceived > CONNECTION_TIMEOUT && isActive) { + isActive = false; + setConnectionStatus(eDisconnected, "Disconnected"); + } + return *_serial && (clientStatus == eConnected || isActive); } meshtastic_FromRadio UARTClient::receive(void) @@ -96,30 +117,13 @@ meshtastic_FromRadio UARTClient::receive(void) return SerialClient::receive(); } -UARTClient::~UARTClient(){ - +UARTClient::~UARTClient() +{ + if (_serial) { + _serial->end(); + } }; -// --- convenience interface --- - -bool UARTClient::isActive(void) const -{ - time_t now; - time(&now); - return lastReceived > 0 && now - lastReceived < 60; -} - -const char *UARTClient::getConnectionInfo(void) const -{ - static char connectionInfo[32]; -#ifdef SERIAL_RX - sprintf(connectionInfo, "serial rx=%d/tx=%d", SERIAL_RX, SERIAL_TX); -#else - strcpy(connectionInfo, "serial RX/TX/GND"); -#endif - return connectionInfo; -} - // --- protected part --- // raw write to serial UART interface @@ -137,17 +141,19 @@ bool UARTClient::send(const uint8_t *buf, size_t len) size_t UARTClient::receive(uint8_t *buf, size_t space_left) { size_t bytes_read = 0; - while (_serial->available()) { + while (_serial->available() && bytes_read < MAX_PACKET_SIZE) { uint8_t byte = _serial->read(); *buf++ = byte; if (++bytes_read >= space_left) { - ILOG_ERROR("Serial overflow!"); + // no error, but serial thread is too slow (-> reduce sleep_time) + ILOG_WARN("Serial overflow!"); break; } } if (bytes_read > 0) { ILOG_TRACE("received %d bytes from serial", bytes_read); time(&lastReceived); + isActive = true; } return bytes_read; } diff --git a/source/graphics/DeviceGUI.cpp b/source/graphics/DeviceGUI.cpp index 827ede3..04b8d2a 100644 --- a/source/graphics/DeviceGUI.cpp +++ b/source/graphics/DeviceGUI.cpp @@ -2,6 +2,7 @@ #include "graphics/driver/DisplayDriver.h" #include "graphics/driver/DisplayDriverConfig.h" #include "input/InputDriver.h" +#include #if LV_USE_LIBINPUT #include "input/LinuxInputDriver.h" @@ -85,9 +86,28 @@ void DeviceGUI::init(IClientBase *client) displaydriver->printConfig(); } +/** + * Linux: measure how long it takes to call displaydriver->task_handler(). + * Then tell the lvgl library how long it took via lv_tick_inc(). + */ void DeviceGUI::task_handler(void) { +#if defined(ARCH_PORTDUINO) + int ms = 10; + auto start = std::chrono::high_resolution_clock::now(); displaydriver->task_handler(); + auto stop = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(stop - start); + if (duration.count() < ms) { + std::this_thread::sleep_for(std::chrono::milliseconds(ms - duration.count())); + lv_tick_inc(ms); + } else { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + lv_tick_inc(duration.count() + 1); + } +#else + displaydriver->task_handler(); +#endif }; DeviceGUI::~DeviceGUI() diff --git a/source/graphics/DeviceScreen.cpp b/source/graphics/DeviceScreen.cpp index b331a14..2327c23 100644 --- a/source/graphics/DeviceScreen.cpp +++ b/source/graphics/DeviceScreen.cpp @@ -87,11 +87,11 @@ int DeviceScreen::wakeUp(esp_sleep_wakeup_cause_t cause) /** * @brief synchronisation point: here we sleep after prepareSleep() was called */ -void DeviceScreen::sleep(uint32_t time) +void DeviceScreen::sleep(uint32_t sleepTime) { -#if defined(ARDUINO_ARCH_ESP32) +#if defined(ARCH_ESP32) if (xSemaphore && xSemaphoreTake(xSemaphore, portMAX_DELAY) == pdTRUE) xSemaphoreGive(xSemaphore); + vTaskDelay((TickType_t)sleepTime); // yield, do not remove #endif - delay(time); } diff --git a/source/graphics/TFT/TFTView_320x240.cpp b/source/graphics/TFT/TFTView_320x240.cpp index 28a2398..7ca2dac 100644 --- a/source/graphics/TFT/TFTView_320x240.cpp +++ b/source/graphics/TFT/TFTView_320x240.cpp @@ -337,9 +337,10 @@ bool TFTView_320x240::setupUIConfig(const meshtastic_DeviceUIConfig &uiconfig) * @brief display custom message on boot screen * Note: currently, the firmware version field is used and set in main()/setup() */ -void TFTView_320x240::updateBootMessage(void) +void TFTView_320x240::updateBootMessage(const char *msg) { - lv_label_set_text(objects.firmware_label, firmware_version); + if (msg) + lv_label_set_text(objects.firmware_label, msg); } /** @@ -397,6 +398,10 @@ void TFTView_320x240::init_screens(void) lv_obj_clear_flag(objects.basic_settings_backup_restore_button, LV_OBJ_FLAG_HIDDEN); #endif + if (controller->isStandalone()) { + lv_obj_add_flag(objects.progmode_button, LV_OBJ_FLAG_HIDDEN); + } + // signal scanner scale #if defined(USE_SX127x) lv_label_set_text(objects.signal_scanner_rssi_scale_label, "-50\n-60\n-70\n-80\n-90\n-100\n-110\n-120\n-130\n-140\n-150"); @@ -853,6 +858,35 @@ void TDeckGUI::ui_event_HomeButton(lv_event_t * e) { } #endif +void TFTView_320x240::timer_event_reboot(lv_timer_t *timer) +{ + ILOG_INFO("Rebooting..."); + THIS->controller->stop(); + delay(4000); +#if defined(ARCH_PORTDUINO) + extern void reboot(); + reboot(); +#elif defined(ARCH_ESP32) + esp_restart(); +#else + // TODO: implement for other platforms +#endif +} + +void TFTView_320x240::timer_event_shutdown(lv_timer_t *timer) +{ + ILOG_INFO("Shutdown..."); + THIS->controller->stop(); + delay(1000); +#if defined(ARCH_PORTDUINO) + exit(0); +#elif defined(ARCH_ESP32) + esp_deep_sleep_start(); +#else + // TODO: implement for other platforms +#endif +} + void TFTView_320x240::timer_event_programming_mode(lv_timer_t *timer) { if (THIS->state == eBooting) @@ -1907,6 +1941,9 @@ void TFTView_320x240::ui_event_device_reboot_button(lv_event_t *e) THIS->controller->requestReboot(5, THIS->ownNode); lv_screen_load_anim(objects.blank_screen, LV_SCR_LOAD_ANIM_FADE_OUT, 4000, 1000, false); lv_obj_add_flag(objects.reboot_panel, LV_OBJ_FLAG_HIDDEN); + if (THIS->controller->isStandalone()) { + lv_timer_create(timer_event_reboot, 4000, NULL); + } } } @@ -1931,6 +1968,9 @@ void TFTView_320x240::ui_event_device_shutdown_button(lv_event_t *e) THIS->controller->requestShutdown(5, THIS->ownNode); lv_screen_load_anim(objects.blank_screen, LV_SCR_LOAD_ANIM_FADE_OUT, 4000, 1000, false); lv_obj_add_flag(objects.reboot_panel, LV_OBJ_FLAG_HIDDEN); + if (THIS->controller->isStandalone()) { + lv_timer_create(timer_event_shutdown, 4000, NULL); + } } } @@ -3202,11 +3242,9 @@ void TFTView_320x240::updateSignalStrength(int32_t rssi, float snr) uint32_t TFTView_320x240::role2val(meshtastic_Config_DeviceConfig_Role role) { #ifdef USE_ROUTER_ROLE - int32_t val[] = { - 0, 1, 2, -1, 3, 4, 5, 6, 7, 8, 9 }; + int32_t val[] = {0, 1, 2, -1, 3, 4, 5, 6, 7, 8, 9}; #else - int32_t val[] = { - 0, 1, -1, -1, -1, 2, 3, 4, 5, 6, 7 }; + int32_t val[] = {0, 1, -1, -1, -1, 2, 3, 4, 5, 6, 7}; #endif if (role > 10 || val[role] == -1) { ILOG_WARN("unknown role value: %d", role); @@ -3220,20 +3258,19 @@ uint32_t TFTView_320x240::role2val(meshtastic_Config_DeviceConfig_Role role) */ meshtastic_Config_DeviceConfig_Role TFTView_320x240::val2role(uint32_t val) { - meshtastic_Config_DeviceConfig_Role role[] = { - meshtastic_Config_DeviceConfig_Role_CLIENT, - meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE, + meshtastic_Config_DeviceConfig_Role role[] = {meshtastic_Config_DeviceConfig_Role_CLIENT, + meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE, #ifdef USE_ROUTER_ROLE - meshtastic_Config_DeviceConfig_Role_ROUTER, - meshtastic_Config_DeviceConfig_Role_REPEATER, + meshtastic_Config_DeviceConfig_Role_ROUTER, + meshtastic_Config_DeviceConfig_Role_REPEATER, #endif - meshtastic_Config_DeviceConfig_Role_TRACKER, - meshtastic_Config_DeviceConfig_Role_SENSOR, - meshtastic_Config_DeviceConfig_Role_TAK, - meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN, - meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND, - meshtastic_Config_DeviceConfig_Role_TAK_TRACKER, - meshtastic_Config_DeviceConfig_Role_ROUTER_LATE }; + meshtastic_Config_DeviceConfig_Role_TRACKER, + meshtastic_Config_DeviceConfig_Role_SENSOR, + meshtastic_Config_DeviceConfig_Role_TAK, + meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN, + meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND, + meshtastic_Config_DeviceConfig_Role_TAK_TRACKER, + meshtastic_Config_DeviceConfig_Role_ROUTER_LATE}; if (val > 10) { ILOG_WARN("unknown role value: %d", val); return meshtastic_Config_DeviceConfig_Role_CLIENT; @@ -3520,16 +3557,28 @@ void TFTView_320x240::storeNodeOptions(void) */ void TFTView_320x240::eraseChat(uint32_t channelOrNode) { + if (chats.find(channelOrNode) == chats.end()) { + ILOG_WARN("eraseChat: channelOrNode %d not found", channelOrNode); + return; + } if (channelOrNode < c_max_channels) { uint8_t ch = (uint8_t)channelOrNode; - lv_obj_delete_delayed(chats[ch], 500); - lv_obj_del(channelGroup[ch]); + if (state == MeshtasticView::eRunning) { + lv_obj_delete_delayed(chats.at(ch), 500); + } else { + lv_obj_del(chats.at(ch)); + } + lv_obj_del(channelGroup.at(ch)); channelGroup[ch] = nullptr; chats.erase(ch); } else { uint32_t nodeNum = channelOrNode; - lv_obj_delete_delayed(chats[nodeNum], 500); - lv_obj_del(messages[nodeNum]); + if (state == MeshtasticView::eRunning) { + lv_obj_delete_delayed(chats.at(nodeNum), 500); + } else { + lv_obj_delete(chats.at(nodeNum)); + } + lv_obj_del(messages.at(nodeNum)); messages.erase(nodeNum); chats.erase(nodeNum); } @@ -5473,17 +5522,51 @@ void TFTView_320x240::packetReceived(const meshtastic_MeshPacket &p) updateStatistics(p); } +void TFTView_320x240::notifyConnected(const char *info) +{ + if (state == MeshtasticView::eBooting) { + updateBootMessage(info); + } else { + if (state == MeshtasticView::eDisconnected) { + messageAlert(_("Connected!"), true); + // force re-sync with node + THIS->controller->setConfigRequested(true); + } + state = MeshtasticView::eRunning; + } +} + +void TFTView_320x240::notifyDisconnected(const char *info) +{ + if (state == MeshtasticView::eBooting) { + updateBootMessage(info); + } else { + if (state == MeshtasticView::eRunning) { + messageAlert(_("Disconnected!"), true); + } + state = MeshtasticView::eDisconnected; + } +} + void TFTView_320x240::notifyResync(bool show) { - messageAlert(_("Resync ..."), show); - if (!show) { - lv_screen_load_anim(objects.main_screen, LV_SCR_LOAD_ANIM_NONE, 0, 0, false); + if (controller->isStandalone()) { + if (show) + notifyReboot(true); + } else { + messageAlert(_("Resync ..."), show); + if (!show) { + lv_screen_load_anim(objects.main_screen, LV_SCR_LOAD_ANIM_NONE, 0, 0, false); + } } } void TFTView_320x240::notifyReboot(bool show) { messageAlert(_("Rebooting ..."), show); + if (controller->isStandalone()) { + lv_timer_create(timer_event_reboot, 8000, NULL); + } } void TFTView_320x240::notifyShutdown(void) @@ -6119,8 +6202,10 @@ void TFTView_320x240::newMessage(uint32_t nodeNum, lv_obj_t *container, uint8_t lv_label_set_text(msgLabel, msg); add_style_new_message_style(msgLabel); - lv_obj_scroll_to_view(hiddenPanel, LV_ANIM_ON); - lv_obj_move_foreground(objects.message_input_area); + if (state == MeshtasticView::eRunning) { + lv_obj_scroll_to_view(hiddenPanel, LV_ANIM_ON); + lv_obj_move_foreground(objects.message_input_area); + } lv_obj_add_event_cb(hiddenPanel, ui_event_chatNodeButton, LV_EVENT_CLICKED, (void *)nodeNum); } @@ -7026,13 +7111,6 @@ void TFTView_320x240::task_handler(void) if (processingFilter || nodesChanged) { updateNodesFiltered(nodesChanged); } - } else { // CYD scenario only - if (state == MeshtasticView::eBooting) { - if (curtime - lastrun1 >= 1) { // call every 1s - lastrun1 = curtime; - updateBootMessage(); - } - } } } diff --git a/source/graphics/common/MeshtasticView.cpp b/source/graphics/common/MeshtasticView.cpp index 3c3358c..e980879 100644 --- a/source/graphics/common/MeshtasticView.cpp +++ b/source/graphics/common/MeshtasticView.cpp @@ -92,6 +92,7 @@ void MeshtasticView::updateSignalStrength(uint32_t nodeNum, int32_t rssi, float void MeshtasticView::notifyMessagesRestored(void) { messagesRestored = true; + state = eRunning; } void MeshtasticView::notifyResync(bool show) {} diff --git a/source/graphics/common/ViewController.cpp b/source/graphics/common/ViewController.cpp index 41fb159..8f39071 100644 --- a/source/graphics/common/ViewController.cpp +++ b/source/graphics/common/ViewController.cpp @@ -28,10 +28,19 @@ ViewController::ViewController() void ViewController::init(MeshtasticView *gui, IClientBase *_client) { + time(&lastrun1); time(&lastrun10); view = gui; client = _client; if (client) { + // client status handler + client->setNotifyCallback([this](IClientBase::ConnectionStatus status, const char *info) { + if (status == IClientBase::eConnected) { + view->notifyConnected(info); + } else { + view->notifyDisconnected(info); + } + }); client->init(); client->connect(); } @@ -64,11 +73,17 @@ void ViewController::runOnce(void) lastrun10 = curtime; if (!client->isConnected()) client->connect(); - if (view->getState() == MeshtasticView::eBootScreenDone) { + if (client->isConnected() && view->getState() == MeshtasticView::eBootScreenDone) { requestConfigRequired = true; requestConfig(); } } + + // executed every 1s: + if (curtime - lastrun1 >= 1) { + lastrun1 = curtime; + client->task_handler(); + } } } @@ -80,6 +95,21 @@ bool ViewController::sleep(int16_t pin) return false; } +bool ViewController::isStandalone(void) +{ + if (client) + return client->isStandalone(); + else + return false; +} + +void ViewController::stop(void) +{ + if (client) { + client->disconnect(); + } +} + void ViewController::processEvent(void) {} uint32_t ViewController::requestDeviceUIConfig(void) @@ -422,6 +452,8 @@ void ViewController::sendHeartbeat(void) { if (client->isConnected()) { client->send(meshtastic_ToRadio{.which_payload_variant = meshtastic_ToRadio_heartbeat_tag}); + } else { + ILOG_DEBUG("sendHeartbeat skipped, client not connected"); } } diff --git a/source/graphics/map/MapPanel.cpp b/source/graphics/map/MapPanel.cpp index 228a620..3a53871 100644 --- a/source/graphics/map/MapPanel.cpp +++ b/source/graphics/map/MapPanel.cpp @@ -5,24 +5,14 @@ #include "util/ILog.h" #include -#define HASH(X, Y) (((X) << 16) | ((Y) & 0xFFFF)) - +#define HASH(X, Y) (((X) << 16) | ((Y)&0xFFFF)) MapPanel::MapPanel(lv_obj_t *p, ITileService *s) - : home(GeoPoint(MapTileSettings::getDefaultLat(), MapTileSettings::getDefaultLon(), MapTileSettings::getZoomLevel())), + : widthPixel(320), heightPixel(240), + home(GeoPoint(MapTileSettings::getDefaultLat(), MapTileSettings::getDefaultLon(), MapTileSettings::getZoomLevel())), current(home), scrolled(home), panel(p), homeLocationImage(nullptr), gpsPositionImage(nullptr), noTileImage(nullptr), service(new TileService(s)), objectsOnMap(0) { - if (p) { - lv_obj_update_layout(panel); - widthPixel = lv_obj_get_width(panel); - heightPixel = lv_obj_get_height(panel); - ILOG_DEBUG("panel size: %dx%d", widthPixel, heightPixel); - } else { - widthPixel = 320; - heightPixel = 240; - } - extern OSMTiles *osm; osm = OSMTiles::create([this](const char *name, void *img) -> bool { return service->load(name, img); }); @@ -53,6 +43,11 @@ void MapPanel::redraw(void) for (int x = 0; x < tilesX; x++) { for (int y = 0; y < tilesY; y++) { uint32_t hash = HASH(xStart + x, yStart + y); + if (tiles.find(hash) != tiles.end()) { + ILOG_ERROR("internal error: tile %d/%d (hash:%u) already exists", xStart + x, yStart + y, hash); + needsRedraw = true; + return; + } tiles[hash] = std::move(std::unique_ptr(new MapTile(xStart + x, yStart + y))); tiles[hash]->load(panel, x * size + xOffset, y * size + yOffset, noTileImage); } @@ -165,6 +160,7 @@ void MapPanel::drawObject(MapObject &obj, bool count) */ void MapPanel::center(void) { + updateDimensions(); int16_t size = MapTileSettings::getTileSize(); int16_t xpos = widthPixel / 2 - scrolled.xPos; int16_t ypos = heightPixel / 2 - scrolled.yPos; @@ -251,6 +247,16 @@ void MapPanel::setZoom(uint8_t zoom) } } +void MapPanel::updateDimensions(void) +{ + if (panel) { + lv_obj_update_layout(panel); + widthPixel = lv_obj_get_width(panel); + heightPixel = lv_obj_get_height(panel); + ILOG_DEBUG("panel size: %dx%d", widthPixel, heightPixel); + } +} + void MapPanel::setLocked(bool lock) { locked = lock; @@ -503,10 +509,13 @@ void MapPanel::printTiles(void) for (int x = 0; x < tilesX; x++) { for (int y = 0; y < tilesY; y++) { uint32_t hash = HASH(xStart + x, yStart + y); - ss << x << "/" << y << ": " - << "(" << (uint32_t)MapTileSettings::getZoomLevel() << "/" << tiles[hash].get()->xTile << "/" - << tiles[hash].get()->yTile << ") - " << tiles[hash].get()->xPos << "/" << tiles[hash].get()->yPos << " ==> " - << tiles[hash].get()->getX() << "/" << tiles[hash].get()->getY() << std::endl; + if (tiles.find(hash) != tiles.end()) { + ss << x << "/" << y << ": " + << "(" << (uint32_t)MapTileSettings::getZoomLevel() << "/" << tiles[hash].get()->xTile << "/" + << tiles[hash].get()->yTile << ") " << hash << " - " << tiles[hash].get()->xPos << "/" + << tiles[hash].get()->yPos << " ==> " << tiles[hash].get()->getX() << "/" << tiles[hash].get()->getY() + << std::endl; + } } } ILOG_DEBUG("tiles: %d\n%s", tiles.size(), ss.str().c_str()); diff --git a/source/graphics/map/SDCardService.cpp b/source/graphics/map/SDCardService.cpp index eddbeaa..5926d64 100644 --- a/source/graphics/map/SDCardService.cpp +++ b/source/graphics/map/SDCardService.cpp @@ -46,7 +46,7 @@ bool SDCardService::load(const char *name, void *img) { char buf[128] = DRIVE_LETTER ":"; strcat(&buf[2], name); - // ILOG_DEBUG("SDCardService::load(): %s", buf); + ILOG_DEBUG("SDCardService::load(): %s", buf); lv_image_set_src((lv_obj_t *)img, buf); if (!lv_image_get_src((lv_obj_t *)img)) { ILOG_DEBUG("Failed to load tile %s from SD", buf);