Skip to content
Open
27 changes: 27 additions & 0 deletions include/graphics/common/SdCard.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,33 @@ class SdFsCard : public ISdCard
virtual ~SdFsCard(void) {}
};

#elif defined(SENSECAP_INDICATOR)
#include "graphics/map/RemoteSDService.h"

/**
* SD card behind a co-processor, accessed through the RemoteSDService
* IRemoteFS backend. Statistics are fetched once over the link on init().
*/
class RemoteSdCard : public ISdCard
{
public:
bool init(void) override;
CardType cardType(void) override;
FatType fatType(void) override;
ErrorType errorType(void) override { return info.present ? eNoError : eSlotEmpty; }
uint64_t usedBytes(void) override { return info.usedBytes; }
uint64_t freeBytes(void) override { return info.freeBytes; }
uint64_t cardSize(void) override { return info.cardSize; }
bool format(void) override { return false; }

std::set<std::string> loadMapStyles(const char *folder) override;
std::string getUrlProvider(const char *folder, const char *style) override;
virtual ~RemoteSdCard(void) {}

private:
RemoteSdInfo info;
};

#endif

/**
Expand Down
98 changes: 98 additions & 0 deletions include/graphics/map/RemoteSDService.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#pragma once

#include "graphics/map/TileService.h"
#include "lvgl.h"
#include <set>
#include <stdint.h>
#include <string>

/**
* Statistics of the remote SD card; numeric values match the
* meshtastic.SdCardInfo interdevice protobuf enums.
*/
struct RemoteSdInfo {
bool present = false;
uint8_t cardType = 0; // 0 none, 1 MMC, 2 SD, 3 SDHC, 4 SDXC, 5 unknown
uint8_t fatType = 0; // 0 unknown, 1 FAT16, 2 FAT32, 3 exFAT
uint64_t cardSize = 0;
uint64_t usedBytes = 0;
uint64_t freeBytes = 0;
};

/**
* Backend transport for RemoteSDService: chunked file access on a filesystem
* that lives on another MCU (e.g. the SD card behind the SenseCAP Indicator
* RP2040). Implemented and registered by the firmware before the view loads
* the map.
*/
class IRemoteFS
{
public:
/**
* Read up to len bytes at offset. Returns false on transport error or
* missing file. *bytesRead receives the chunk size actually read,
* *fileSize the total size of the file.
*/
virtual bool readChunk(const char *path, uint32_t offset, uint8_t *buf, uint32_t len, uint32_t *bytesRead,
uint32_t *fileSize) = 0;
/**
* Sequential chunked write; create=true starts a new file (offset 0),
* create=false appends the chunk at offset == current file size.
*/
virtual bool writeChunk(const char *path, uint32_t offset, const uint8_t *buf, uint32_t len, bool create) = 0;
/**
* List all entries of a directory; subdirectories carry a trailing
* slash. Returns false on transport error or when path is not a
* directory.
*/
virtual bool listDir(const char *path, std::set<std::string> &entries) = 0;
/**
* Card statistics, answered from a cache on the co-processor so the
* call stays fast. usedBytes/freeBytes may still read zero while the
* background FAT scan runs; a later call returns real values.
* Returns false on transport error.
*/
virtual bool sdInfo(RemoteSdInfo &info) = 0;
virtual ~IRemoteFS() {}
};

/**
* Map tile service for a remote SD card, accessed chunk-wise through an
* IRemoteFS backend. Registers an LVGL filesystem driver so the image
* decoder reads tiles like from a local drive.
*/
class RemoteSDService : public ITileService
{
public:
static void setBackend(IRemoteFS *fs);
static IRemoteFS *backend(void);

RemoteSDService();
virtual ~RemoteSDService();

bool load(const char *name, void *img) override;
bool save(const char *name, void *img, size_t len) override;

protected:
static void *fs_open(lv_fs_drv_t *drv, const char *path, lv_fs_mode_t mode);
static lv_fs_res_t fs_close(lv_fs_drv_t *drv, void *file_p);
static lv_fs_res_t fs_read(lv_fs_drv_t *drv, void *file_p, void *buf, uint32_t btr, uint32_t *br);
static lv_fs_res_t fs_write(lv_fs_drv_t *drv, void *file_p, const void *buf, uint32_t btw, uint32_t *bw);
static lv_fs_res_t fs_seek(lv_fs_drv_t *drv, void *file_p, uint32_t pos, lv_fs_whence_t whence);
static lv_fs_res_t fs_tell(lv_fs_drv_t *drv, void *file_p, uint32_t *pos_p);

private:
static IRemoteFS *remoteFS;

enum { CHUNK_SIZE = 4096 }; // matches FileTransfer.filedata max_size

typedef struct RemoteFile {
char path[256]; // full FAT LFN paths round-trip
uint32_t pos;
uint32_t size;
// single chunk read-ahead cache
uint32_t chunkOffset;
uint32_t chunkLen;
uint8_t chunk[CHUNK_SIZE];
} RemoteFile;
};
17 changes: 17 additions & 0 deletions include/input/I2CKeyboardScanner.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
#pragma once

#ifdef SENSECAP_INDICATOR
#include <Wire.h> // TwoWire is a typedef on some cores, no forward declaration
#endif

class I2CKeyboardInputDriver;

/**
Expand All @@ -14,4 +18,17 @@ class I2CKeyboardScanner
I2CKeyboardScanner(void);
virtual I2CKeyboardInputDriver *scan(void);
virtual ~I2CKeyboardScanner(void) {}

#ifdef SENSECAP_INDICATOR
/**
* Override the bus used for the secondary (bus 1) keyboard scan, e.g. a
* bridged TwoWire implementation on devices whose second bus lives behind
* a co-processor. Must be set before input driver initialization; when
* unset the local Wire1 is used.
*/
static void setSecondaryBus(TwoWire *bus);

private:
static TwoWire *secondaryBus;
#endif
};
19 changes: 17 additions & 2 deletions source/graphics/TFT/TFTView_320x240.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ fs::FS &fileSystem = LittleFS;
#include "util/LinuxHelper.h"
// #include "graphics/map/LinuxFileSystemService.h"
#include "graphics/map/SDCardService.h"
#elif defined(SENSECAP_INDICATOR)
#include "graphics/map/RemoteSDService.h"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
#elif defined(HAS_SD_MMC)
#include "graphics/map/SDCardService.h"
#else
Expand Down Expand Up @@ -2519,6 +2521,12 @@ void TFTView_320x240::loadMap(void)
if (!map) {
#if LV_USE_FS_ARDUINO_SD
map = new MapPanel(objects.raw_map_panel);
#elif defined(SENSECAP_INDICATOR)
// tiles live on the SD card behind the RP2040, fetched chunk-wise over the interdevice link
auto tileService = new RemoteSDService();
map = new MapPanel(objects.raw_map_panel, tileService);
map->setBackupService(
new URLService([tileService](const char *name, void *img, size_t len) { return tileService->save(name, img, len); }));
#elif defined(HAS_SD_MMC)
auto tileService = new SDCardService();
map = new MapPanel(objects.raw_map_panel, tileService);
Expand Down Expand Up @@ -7212,9 +7220,11 @@ bool TFTView_320x240::updateSDCard(void)
delete sdCard;
sdCard = nullptr;
}
#ifdef HAS_SDCARD
#if defined(HAS_SDCARD) || defined(SENSECAP_INDICATOR)
char buf[64];
#ifdef HAS_SD_MMC
#if defined(SENSECAP_INDICATOR)
sdCard = new RemoteSdCard; // SD card behind the co-processor
#elif defined(HAS_SD_MMC)
sdCard = new SDCard;
#else
sdCard = new SdFsCard;
Expand Down Expand Up @@ -7281,8 +7291,13 @@ bool TFTView_320x240::updateSDCard(void)
// allow backup/restore only if there is an SD card detected
lv_obj_add_state(objects.basic_settings_backup_restore_button, LV_STATE_DISABLED);
} else {
#if defined(SENSECAP_INDICATOR)
// backup/restore writes locally, which the bridged SD does not support yet
lv_obj_add_state(objects.basic_settings_backup_restore_button, LV_STATE_DISABLED);
#else
// enable backup/restore
lv_obj_clear_state(objects.basic_settings_backup_restore_button, LV_STATE_DISABLED);
#endif
}
lv_label_set_text(objects.home_sd_card_label, buf);
#else
Expand Down
98 changes: 98 additions & 0 deletions source/graphics/common/SdCard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -322,4 +322,102 @@ std::string SdFsCard::getUrlProvider(const char *folder, const char *style)
}
return {};
}

#elif defined(SENSECAP_INDICATOR)

#include "graphics/map/RemoteSDService.h"
#include <cstring>

bool RemoteSdCard::init(void)
{
IRemoteFS *fs = RemoteSDService::backend();
if (!fs)
return false;
if (!fs->sdInfo(info))
return false;
return info.present;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

ISdCard::CardType RemoteSdCard::cardType(void)
{
// numeric values follow the meshtastic.SdCardInfo protobuf enum
switch (info.cardType) {
case 1:
return eMMC;
case 2:
return eSD;
case 3:
return eSDHC;
case 4:
return eSDXC;
case 5:
return eUnknown;
default:
return eNone;
}
}

ISdCard::FatType RemoteSdCard::fatType(void)
{
switch (info.fatType) {
case 1:
return eFat16;
case 2:
return eFat32;
case 3:
return eExFat;
default:
return eNA;
}
}

std::set<std::string> RemoteSdCard::loadMapStyles(const char *folder)
{
std::set<std::string> styles;
IRemoteFS *fs = RemoteSDService::backend();
if (fs) {
std::set<std::string> entries;
if (fs->listDir(folder, entries)) {
for (auto &entry : entries) {
if (entry.empty() || entry[0] == '.')
continue;
std::string dir = entry;
if (dir.back() == '/') // subdirectories carry a trailing slash
dir.pop_back();
ILOG_DEBUG("remote SD: found map style: %s", dir.c_str());
styles.insert(dir);
}
}
if (styles.empty()) {
std::set<std::string> mapDir;
if (fs->listDir("/map", mapDir)) {
ILOG_DEBUG("remote SD: found /map dir");
styles.insert("/map");
} else {
ILOG_INFO("remote SD: no maps found");
}
}
}
updated = true;
return styles;
}

std::string RemoteSdCard::getUrlProvider(const char *folder, const char *style)
{
IRemoteFS *fs = RemoteSDService::backend();
if (!fs)
return {};
std::string filename = std::string(folder) + "/" + style + "/.url";
uint8_t buf[256];
uint32_t bytesRead = 0, fileSize = 0;
if (!fs->readChunk(filename.c_str(), 0, buf, sizeof(buf) - 1, &bytesRead, &fileSize) || bytesRead == 0)
return {};
buf[bytesRead] = '\0';
// first line only
char *nl = strpbrk((char *)buf, "\r\n");
if (nl)
*nl = '\0';
return std::string{(char *)buf};
}

#endif // HAS_SDCARD
Loading
Loading