Skip to content

Commit f8d480d

Browse files
committed
Support SD cards behind a co-processor (SenseCAP Indicator)
RemoteSDService implements an LVGL filesystem driver that fetches map tiles chunk-wise through an IRemoteFS backend registered by the firmware. RemoteSdCard provides map style detection, url providers and card statistics over the same backend. The I2CKeyboardScanner secondary bus is injectable so the bus 1 rescan can use a bridged TwoWire implementation instead of the local Wire1.
1 parent effbb92 commit f8d480d

7 files changed

Lines changed: 413 additions & 6 deletions

File tree

include/graphics/common/SdCard.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,33 @@ class SdFsCard : public ISdCard
9696
virtual ~SdFsCard(void) {}
9797
};
9898

99+
#elif defined(SENSECAP_INDICATOR)
100+
#include "graphics/map/RemoteSDService.h"
101+
102+
/**
103+
* SD card behind a co-processor, accessed through the RemoteSDService
104+
* IRemoteFS backend. Statistics are fetched once over the link on init().
105+
*/
106+
class RemoteSdCard : public ISdCard
107+
{
108+
public:
109+
bool init(void) override;
110+
CardType cardType(void) override;
111+
FatType fatType(void) override;
112+
ErrorType errorType(void) override { return info.present ? eNoError : eSlotEmpty; }
113+
uint64_t usedBytes(void) override { return info.usedBytes; }
114+
uint64_t freeBytes(void) override { return info.freeBytes; }
115+
uint64_t cardSize(void) override { return info.cardSize; }
116+
bool format(void) override { return false; }
117+
118+
std::set<std::string> loadMapStyles(const char *folder) override;
119+
std::string getUrlProvider(const char *folder, const char *style) override;
120+
virtual ~RemoteSdCard(void) {}
121+
122+
private:
123+
RemoteSdInfo info;
124+
};
125+
99126
#endif
100127

101128
/**
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
#pragma once
2+
3+
#include "graphics/map/TileService.h"
4+
#include "lvgl.h"
5+
#include <set>
6+
#include <stdint.h>
7+
#include <string>
8+
9+
/**
10+
* Statistics of the remote SD card; numeric values match the
11+
* meshtastic.SdCardInfo interdevice protobuf enums.
12+
*/
13+
struct RemoteSdInfo {
14+
bool present = false;
15+
uint8_t cardType = 0; // 0 none, 1 MMC, 2 SD, 3 SDHC, 4 SDXC, 5 unknown
16+
uint8_t fatType = 0; // 0 unknown, 1 FAT16, 2 FAT32, 3 exFAT
17+
uint64_t cardSize = 0;
18+
uint64_t usedBytes = 0;
19+
uint64_t freeBytes = 0;
20+
};
21+
22+
/**
23+
* Backend transport for RemoteSDService: chunked file access on a filesystem
24+
* that lives on another MCU (e.g. the SD card behind the SenseCAP Indicator
25+
* RP2040). Implemented and registered by the firmware before the view loads
26+
* the map.
27+
*/
28+
class IRemoteFS
29+
{
30+
public:
31+
/**
32+
* Read up to len bytes at offset. Returns false on transport error or
33+
* missing file. *bytesRead receives the chunk size actually read,
34+
* *fileSize the total size of the file.
35+
*/
36+
virtual bool readChunk(const char *path, uint32_t offset, uint8_t *buf, uint32_t len, uint32_t *bytesRead,
37+
uint32_t *fileSize) = 0;
38+
/**
39+
* Sequential chunked write; create=true starts a new file (offset 0),
40+
* create=false appends the chunk at offset == current file size.
41+
*/
42+
virtual bool writeChunk(const char *path, uint32_t offset, const uint8_t *buf, uint32_t len, bool create) = 0;
43+
/**
44+
* List all entries of a directory; subdirectories carry a trailing
45+
* slash. Returns false on transport error or when path is not a
46+
* directory.
47+
*/
48+
virtual bool listDir(const char *path, std::set<std::string> &entries) = 0;
49+
/**
50+
* Card statistics; may take seconds on FAT16/32 cards (FAT scan).
51+
* Returns false on transport error.
52+
*/
53+
virtual bool sdInfo(RemoteSdInfo &info) = 0;
54+
virtual ~IRemoteFS() {}
55+
};
56+
57+
/**
58+
* Map tile service for a remote SD card, accessed chunk-wise through an
59+
* IRemoteFS backend. Registers an LVGL filesystem driver so the image
60+
* decoder reads tiles like from a local drive.
61+
*/
62+
class RemoteSDService : public ITileService
63+
{
64+
public:
65+
static void setBackend(IRemoteFS *fs);
66+
static IRemoteFS *backend(void);
67+
68+
RemoteSDService();
69+
virtual ~RemoteSDService();
70+
71+
bool load(const char *name, void *img) override;
72+
bool save(const char *name, void *img, size_t len) override;
73+
74+
protected:
75+
static void *fs_open(lv_fs_drv_t *drv, const char *path, lv_fs_mode_t mode);
76+
static lv_fs_res_t fs_close(lv_fs_drv_t *drv, void *file_p);
77+
static lv_fs_res_t fs_read(lv_fs_drv_t *drv, void *file_p, void *buf, uint32_t btr, uint32_t *br);
78+
static lv_fs_res_t fs_write(lv_fs_drv_t *drv, void *file_p, const void *buf, uint32_t btw, uint32_t *bw);
79+
static lv_fs_res_t fs_seek(lv_fs_drv_t *drv, void *file_p, uint32_t pos, lv_fs_whence_t whence);
80+
static lv_fs_res_t fs_tell(lv_fs_drv_t *drv, void *file_p, uint32_t *pos_p);
81+
82+
private:
83+
static IRemoteFS *remoteFS;
84+
85+
enum { CHUNK_SIZE = 4096 }; // matches FileTransfer.filedata max_size
86+
87+
typedef struct RemoteFile {
88+
char path[128];
89+
uint32_t pos;
90+
uint32_t size;
91+
// single chunk read-ahead cache
92+
uint32_t chunkOffset;
93+
uint32_t chunkLen;
94+
uint8_t chunk[CHUNK_SIZE];
95+
} RemoteFile;
96+
};

include/input/I2CKeyboardScanner.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
22

33
class I2CKeyboardInputDriver;
4+
class TwoWire;
45

56
/**
67
* @brief This class is used to scan I2C connected keyboards and creates
@@ -14,4 +15,15 @@ class I2CKeyboardScanner
1415
I2CKeyboardScanner(void);
1516
virtual I2CKeyboardInputDriver *scan(void);
1617
virtual ~I2CKeyboardScanner(void) {}
18+
19+
/**
20+
* Override the bus used for the secondary (bus 1) keyboard scan, e.g. a
21+
* bridged TwoWire implementation on devices whose second bus lives behind
22+
* a co-processor (SenseCAP Indicator). Must be set before input driver
23+
* initialization; when unset the local Wire1 is used.
24+
*/
25+
static void setSecondaryBus(TwoWire *bus);
26+
27+
private:
28+
static TwoWire *secondaryBus;
1729
};

source/graphics/TFT/TFTView_320x240.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ fs::FS &fileSystem = LittleFS;
4545
#include "graphics/map/SDCardService.h"
4646
#elif defined(HAS_SD_MMC)
4747
#include "graphics/map/SDCardService.h"
48+
#elif defined(SENSECAP_INDICATOR)
49+
#include "graphics/map/RemoteSDService.h"
4850
#else
4951
#include "graphics/map/SdFatService.h"
5052
#endif
@@ -2524,6 +2526,12 @@ void TFTView_320x240::loadMap(void)
25242526
map = new MapPanel(objects.raw_map_panel, tileService);
25252527
map->setBackupService(
25262528
new URLService([tileService](const char *name, void *img, size_t len) { return tileService->save(name, img, len); }));
2529+
#elif defined(SENSECAP_INDICATOR)
2530+
// tiles live on the SD card behind the RP2040, fetched chunk-wise over the interdevice link
2531+
auto tileService = new RemoteSDService();
2532+
map = new MapPanel(objects.raw_map_panel, tileService);
2533+
map->setBackupService(
2534+
new URLService([tileService](const char *name, void *img, size_t len) { return tileService->save(name, img, len); }));
25272535
#elif defined(HAS_SDCARD)
25282536
auto tileService = new SdFatService();
25292537
map = new MapPanel(objects.raw_map_panel, tileService);
@@ -7212,10 +7220,12 @@ bool TFTView_320x240::updateSDCard(void)
72127220
delete sdCard;
72137221
sdCard = nullptr;
72147222
}
7215-
#ifdef HAS_SDCARD
7223+
#if defined(HAS_SDCARD) || defined(SENSECAP_INDICATOR)
72167224
char buf[64];
72177225
#ifdef HAS_SD_MMC
72187226
sdCard = new SDCard;
7227+
#elif defined(SENSECAP_INDICATOR)
7228+
sdCard = new RemoteSdCard; // SD card behind the co-processor
72197229
#else
72207230
sdCard = new SdFsCard;
72217231
#endif
@@ -7281,8 +7291,13 @@ bool TFTView_320x240::updateSDCard(void)
72817291
// allow backup/restore only if there is an SD card detected
72827292
lv_obj_add_state(objects.basic_settings_backup_restore_button, LV_STATE_DISABLED);
72837293
} else {
7294+
#if defined(SENSECAP_INDICATOR)
7295+
// backup/restore writes locally, which the bridged SD does not support yet
7296+
lv_obj_add_state(objects.basic_settings_backup_restore_button, LV_STATE_DISABLED);
7297+
#else
72847298
// enable backup/restore
72857299
lv_obj_clear_state(objects.basic_settings_backup_restore_button, LV_STATE_DISABLED);
7300+
#endif
72867301
}
72877302
lv_label_set_text(objects.home_sd_card_label, buf);
72887303
#else

source/graphics/common/SdCard.cpp

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,4 +322,102 @@ std::string SdFsCard::getUrlProvider(const char *folder, const char *style)
322322
}
323323
return {};
324324
}
325+
326+
#elif defined(SENSECAP_INDICATOR)
327+
328+
#include "graphics/map/RemoteSDService.h"
329+
#include <cstring>
330+
331+
bool RemoteSdCard::init(void)
332+
{
333+
IRemoteFS *fs = RemoteSDService::backend();
334+
if (!fs)
335+
return false;
336+
if (!fs->sdInfo(info))
337+
return false;
338+
return info.present;
339+
}
340+
341+
ISdCard::CardType RemoteSdCard::cardType(void)
342+
{
343+
// numeric values follow the meshtastic.SdCardInfo protobuf enum
344+
switch (info.cardType) {
345+
case 1:
346+
return eMMC;
347+
case 2:
348+
return eSD;
349+
case 3:
350+
return eSDHC;
351+
case 4:
352+
return eSDXC;
353+
case 5:
354+
return eUnknown;
355+
default:
356+
return eNone;
357+
}
358+
}
359+
360+
ISdCard::FatType RemoteSdCard::fatType(void)
361+
{
362+
switch (info.fatType) {
363+
case 1:
364+
return eFat16;
365+
case 2:
366+
return eFat32;
367+
case 3:
368+
return eExFat;
369+
default:
370+
return eNA;
371+
}
372+
}
373+
374+
std::set<std::string> RemoteSdCard::loadMapStyles(const char *folder)
375+
{
376+
std::set<std::string> styles;
377+
IRemoteFS *fs = RemoteSDService::backend();
378+
if (fs) {
379+
std::set<std::string> entries;
380+
if (fs->listDir(folder, entries)) {
381+
for (auto &entry : entries) {
382+
if (entry.empty() || entry[0] == '.')
383+
continue;
384+
std::string dir = entry;
385+
if (dir.back() == '/') // subdirectories carry a trailing slash
386+
dir.pop_back();
387+
ILOG_DEBUG("remote SD: found map style: %s", dir.c_str());
388+
styles.insert(dir);
389+
}
390+
}
391+
if (styles.empty()) {
392+
std::set<std::string> mapDir;
393+
if (fs->listDir("/map", mapDir)) {
394+
ILOG_DEBUG("remote SD: found /map dir");
395+
styles.insert("/map");
396+
} else {
397+
ILOG_INFO("remote SD: no maps found");
398+
}
399+
}
400+
}
401+
updated = true;
402+
return styles;
403+
}
404+
405+
std::string RemoteSdCard::getUrlProvider(const char *folder, const char *style)
406+
{
407+
IRemoteFS *fs = RemoteSDService::backend();
408+
if (!fs)
409+
return {};
410+
std::string filename = std::string(folder) + "/" + style + "/.url";
411+
uint8_t buf[256];
412+
uint32_t bytesRead = 0, fileSize = 0;
413+
if (!fs->readChunk(filename.c_str(), 0, buf, sizeof(buf) - 1, &bytesRead, &fileSize) || bytesRead == 0)
414+
return {};
415+
buf[bytesRead] = '\0';
416+
// first line only
417+
char *nl = strpbrk((char *)buf, "\r\n");
418+
if (nl)
419+
*nl = '\0';
420+
return std::string{(char *)buf};
421+
}
422+
325423
#endif // HAS_SDCARD

0 commit comments

Comments
 (0)