Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 44 additions & 2 deletions include/graphics/common/SdCard.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ class ISdCard

virtual uint64_t usedBytes(void) = 0;
virtual uint64_t freeBytes(void) = 0;
// false while used/free are not known yet (e.g. a background scan on a
// co-processor has not finished); local cards always know them
virtual bool statsValid(void) { return true; }

enum StatsResult {
eStatsValid, // used/free are up to date
eStatsPending, // still being computed, ask again later
eStatsUnavailable, // card or link gone, stop asking
};
// Re-read used/free without re-detecting the card
virtual StatsResult refreshStats(void) { return eStatsValid; }
virtual uint64_t cardSize(void) = 0;
virtual bool format(void) = 0;

Expand All @@ -60,7 +71,9 @@ class ISdCard
bool updated = false;
};

#if defined(ARCH_PORTDUINO) || defined(HAS_SD_MMC)
// SENSECAP_INDICATOR takes precedence: the SD card sits behind the
// co-processor even when a generic SD define is also set
#if (defined(ARCH_PORTDUINO) || defined(HAS_SD_MMC)) && !defined(SENSECAP_INDICATOR)
class SDCard : public ISdCard
{
public:
Expand All @@ -78,7 +91,7 @@ class SDCard : public ISdCard
virtual ~SDCard(void);
};

#elif defined(HAS_SDCARD)
#elif defined(HAS_SDCARD) && !defined(SENSECAP_INDICATOR)
class SdFsCard : public ISdCard
{
public:
Expand All @@ -96,6 +109,35 @@ 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; }
bool statsValid(void) override { return info.statsValid; }
StatsResult refreshStats(void) override;
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
106 changes: 106 additions & 0 deletions include/graphics/map/RemoteSDService.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#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;
// usedBytes/freeBytes are only meaningful when true; the scan behind
// them runs in the background on the co-processor after mount
bool statsValid = false;
};

/**
* 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;
/**
* Delete a file. Returns false on transport error or when the file
* does not exist.
*/
virtual bool remove(const char *path) = 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 carry real values once
* statsValid is true; a later call returns them when the background
* scan has finished. 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;
};
8 changes: 8 additions & 0 deletions include/graphics/view/TFT/TFTView_320x240.h
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,14 @@ class TFTView_320x240 : public MeshtasticView
virtual void updateTime(void);
// update SD card slot info
virtual bool updateSDCard(void);
// re-read only the card statistics (a co-processor may compute them in
// the background), polling a bounded number of times until they arrive
void refreshSDCardStats(void);
void armSDCardStatsPoll(void);
#if defined(HAS_SDCARD) || defined(SENSECAP_INDICATOR)
void formatSDCardLabel(char *buf, size_t len);
#endif
uint16_t sdStatsPolls = 0;
// format SD card if invalid
virtual void formatSDCard(void);
// update time display on home screen
Expand Down
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
};
1 change: 1 addition & 0 deletions locale/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ en:
'%d new messages': ~
'uptime: %02d:%02d:%02d': ~
"%s: %d GB (%s)\nUsed: %0.2f GB (%d%%)": ~
'Used: ...': ~
SD slot empty: ~
SD invalid format: ~
SD mbr not found: ~
Expand Down
Loading
Loading