Skip to content
Draft
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
2 changes: 1 addition & 1 deletion components/lvgl/env_support/cmake/esp.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ else()
idf_component_register(SRCS ${SOURCES} ${EXAMPLE_SOURCES} ${DEMO_SOURCES}
INCLUDE_DIRS ${LVGL_ROOT_DIR} ${LVGL_ROOT_DIR}/src ${LVGL_ROOT_DIR}/../
${LVGL_ROOT_DIR}/examples ${LVGL_ROOT_DIR}/demos
REQUIRES esp_timer)
REQUIRES esp_timer heap)
endif()

target_compile_definitions(${COMPONENT_LIB} PUBLIC "-DLV_CONF_INCLUDE_SIMPLE")
Expand Down
33 changes: 33 additions & 0 deletions components/lvgl/src/extra/libs/png/lodepng.c
Original file line number Diff line number Diff line change
Expand Up @@ -71,23 +71,56 @@ lodepng source code. Don't forget to remove "static" if you copypaste them
from here.*/

#ifdef LODEPNG_COMPILE_ALLOCATORS

/* ESP-IDF: LVGL's lv_mem pool is too small for full-tile PNG decode; prefer PSRAM when enabled. */
#if defined(ESP_PLATFORM) && defined(CONFIG_SPIRAM)
#include "esp_heap_caps.h"
#define LODEPNG_ALLOC_ESP_PSRAM 1
#else
#define LODEPNG_ALLOC_ESP_PSRAM 0
#endif

static void* lodepng_malloc(size_t size) {
#ifdef LODEPNG_MAX_ALLOC
if(size > LODEPNG_MAX_ALLOC) return 0;
#endif
#if LODEPNG_ALLOC_ESP_PSRAM
{
void *p = heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if(p) return p;
p = heap_caps_malloc(size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
return p;
}
#else
return lv_mem_alloc(size);
#endif
}

/* NOTE: when realloc returns NULL, it leaves the original memory untouched */
static void* lodepng_realloc(void* ptr, size_t new_size) {
#ifdef LODEPNG_MAX_ALLOC
if(new_size > LODEPNG_MAX_ALLOC) return 0;
#endif
#if LODEPNG_ALLOC_ESP_PSRAM
if(ptr == NULL) {
return lodepng_malloc(new_size);
}
if(new_size == 0) {
free(ptr);
return NULL;
}
return heap_caps_realloc(ptr, new_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
#else
return lv_mem_realloc(ptr, new_size);
#endif
}

static void lodepng_free(void* ptr) {
#if LODEPNG_ALLOC_ESP_PSRAM
free(ptr);
#else
lv_mem_free(ptr);
#endif
}
#else /*LODEPNG_COMPILE_ALLOCATORS*/
/* TODO: support giving additional void* payload to the custom allocators */
Expand Down
11 changes: 7 additions & 4 deletions configs/sdkconfig.somethingsomething
Original file line number Diff line number Diff line change
Expand Up @@ -3619,7 +3619,7 @@ CONFIG_LV_DRAW_COMPLEX=y
CONFIG_LV_SHADOW_CACHE_SIZE=0
CONFIG_LV_CIRCLE_CACHE_SIZE=4
CONFIG_LV_LAYER_SIMPLE_BUF_SIZE=4800
CONFIG_LV_IMG_CACHE_DEF_SIZE=0
CONFIG_LV_IMG_CACHE_DEF_SIZE=16
CONFIG_LV_GRADIENT_MAX_STOPS=2
CONFIG_LV_GRAD_CACHE_DEF_SIZE=0
# CONFIG_LV_DITHER_GRADIENT is not set
Expand Down Expand Up @@ -3823,14 +3823,17 @@ CONFIG_LV_USE_GRID=y
#
# 3rd Party Libraries
#
# CONFIG_LV_USE_FS_STDIO is not set
CONFIG_LV_USE_FS_STDIO=y
CONFIG_LV_FS_STDIO_LETTER=83
CONFIG_LV_FS_STDIO_PATH=""
CONFIG_LV_FS_STDIO_CACHE_SIZE=0
# CONFIG_LV_USE_FS_POSIX is not set
# CONFIG_LV_USE_FS_WIN32 is not set
# CONFIG_LV_USE_FS_FATFS is not set
# CONFIG_LV_USE_FS_LITTLEFS is not set
# CONFIG_LV_USE_PNG is not set
CONFIG_LV_USE_PNG=y
CONFIG_LV_USE_SJPG=y
# CONFIG_LV_USE_BMP is not set
# CONFIG_LV_USE_SJPG is not set
# CONFIG_LV_USE_GIF is not set
# CONFIG_LV_USE_QRCODE is not set
# CONFIG_LV_USE_FREETYPE is not set
Expand Down
4 changes: 4 additions & 0 deletions docs/hugo docs/content/latest/flipper-app/gps-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ keywords: ["gps", "wardriving", "location", "tracking", "coordinates"]
- **BLE Wardriving**: Log BLE devices with GPS coordinates (CSV export)
- **Combined Mapping**: Map both networks and devices on a single view

## Offline map tiles (device SD)

Display firmware can show **cached map imagery** from the SD card (not live map downloads on the device). Open **Apps** → **Maps** after copying Slippy tiles under `/mnt/ghostesp/maps/<style>/tiles/{z}/{x}/{y}.png` (or `.jpg` / `.jpeg`), where each **`<style>`** folder under `maps/` contains its own `tiles/` tree (multiple styles are supported; cycle with the on-map control, **`M`**, or the encoder button). PNGs are decoded by LVGL; JPEGs are decoded to RGB565 with stb when using 16-bit color, with an SJPG fallback for baseline JPEG. Optional `metadata.json` can supply bounds; **zoom limits follow the tile folders on disk** when the card can be scanned (so template min/max in JSON does not cap the viewer). Details: [Offline maps (SD tiles)](../maps/<Style>).

## Common Workflow: GPS Wardriving

1. Ensure GPS module is connected and receiving signal
Expand Down
2 changes: 2 additions & 0 deletions docs/hugo docs/content/latest/gps/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ aliases:
---

GhostESP supports GPS modules for location tracking and wardriving. Capture wireless network data with GPS coordinates and upload to WiGLE for mapping and research.

On display builds with an SD card, you can also view **offline raster tiles** (Slippy layout under `maps/<style>/tiles/`, PNG or JPEG) from the app gallery **Maps** entry. Multiple styles are folders under `maps/` that contain a `tiles/` directory. See [Offline maps (SD tiles)](offline-maps) for layout, `metadata.json`, zoom discovery, and controls.
102 changes: 102 additions & 0 deletions docs/hugo docs/content/latest/gps/offline-maps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
---
title: "Offline maps (SD tiles)"
description: "View cached Slippy map tiles from the SD card in the Maps app."
weight: 8
---

Display builds can show **raster map tiles** loaded from the SD card. This is separate from wardriving CSV uploads to WiGLE: it is a local tile viewer for map imagery you copy onto the card.

## Open the viewer

1. Insert an SD card with a compatible tile tree (see below).
2. On the device: **Apps** (app gallery) → **Maps**.

If the SD card is missing, not mounted, or no tiles are found under the active style path, the screen explains the error.

## Map styles (folders under `maps/`)

Each **style** is a subfolder of `maps/` on the SD card that contains its own **`tiles/`** directory (Slippy layout inside that folder). Style names are the folder basenames (for example `satellite`, `streets`).

- Paths look like: `ghostesp/maps/<style>/tiles/{z}/{x}/{y}.<ext>` on the card (mounted as `/mnt/ghostesp/...` on device).
- Styles are discovered at runtime, **sorted alphabetically** by folder name.
- **Cycle style**: use the on-map **style control** (touch where supported), keyboard **`M`**, or **encoder button press** (turning the encoder zooms in or out instead).

Example for a style named `satellite`:

```text
/mnt/ghostesp/maps/satellite/tiles/{z}/{x}/{y}.<ext>
```

## Tile layout

Tiles use the usual **Slippy map** / XYZ layout (same idea as `tdeck-meshtastic_tiles` and many desktop tools):

```text
/mnt/ghostesp/maps/<style>/tiles/{z}/{x}/{y}.<ext>
```

Each zoom level must be a **numeric directory** `z`, with **numeric** `x` column directories under it, and tile files named by tile **`y`** (see extensions below). Layouts that omit the `x/` level cannot be scanned for zoom extent or focused the same way.

### File extensions

For each cell the viewer tries, in order:

1. `{y}.png`
2. `{y}.jpg`
3. `{y}.jpeg`

**PNG** tiles are passed to LVGL as compressed PNG in RAM (decoded by the PNG decoder). **JPEG** tiles are handled differently:

- With **16-bit color** (`LV_COLOR_DEPTH` 16), JPEG bytes are decoded with **stb_image** to uncompressed **RGB565** in RAM, then shown as a normal true-color image. This supports **baseline and progressive** JPEG.
- If that decode fails, the firmware can fall back to LVGL’s **SJPG** path (compressed JPEG in `lv_img_dsc_t`), which is suitable for **baseline** JPEG only.
- Some caches store JPEG data in a file named **`.png`**. The viewer detects JPEG by the file header (`FF D8 FF`) and treats it as JPEG, not PNG.

### Optional `metadata.json`

Place a file next to the zoom folders:

```text
/mnt/ghostesp/maps/<style>/tiles/metadata.json
```

Supported fields (when valid JSON):

| Field | Meaning |
|-------|--------|
| `minzoom`, `maxzoom` | Optional hints from many export tools |
| `bounds` | Array `[west, south, east, north]` in degrees; used to pick an initial center tile |

**Zoom range in the viewer:** whenever the firmware can read the SD layout, it **derives allowed zoom from the numeric `z/` directories** under `tiles/` (levels that have at least one numeric `x/` subdirectory). That **on-disk pyramid overrides** `minzoom` / `maxzoom` in `metadata.json`, which are often a small template range (for example 10–12) while the real cache spans a wider band. If the card cannot be read, metadata or built-in defaults apply.

If `metadata.json` is missing, built-in defaults apply and the view can still **auto-focus** on zoom levels that contain tiles (bounded scan so large caches stay responsive).

## Build and runtime requirements

Full **Maps** tile rendering needs LVGL **PNG** support and the **stdio filesystem** driver (same pattern as the rest of GhostESP for `S:` paths), for example:

- `CONFIG_LV_USE_PNG=y`
- `CONFIG_LV_USE_FS_STDIO=y`
- `CONFIG_LV_FS_STDIO_LETTER=83` (ASCII `S` for `S:`)

**JPEG misnamed as `.png`** or raw `.jpg` / `.jpeg` tiles work best with **16-bit color** so the stb decode path is available; **SJPG** (`CONFIG_LV_USE_SJPG=y`) remains useful as a fallback for baseline JPEG when the RGB565 path does not apply.

## Shared SPI / “stream” mode

On boards where the display and SD card share SPI, the UI may **mount the SD only to read tiles**, copy tile bytes into RAM, then **unmount** so the panel keeps working. In that mode you still need real tile files on the card at the paths above; nothing is fetched over the network on device. Opening **Maps**, switching **style**, or refreshing tiles may **JIT-mount** the card briefly to read metadata, discover zoom levels, or reload the 3×3 grid.

## Pan and zoom

Controls depend on the device (touch, keyboard, joystick, encoder). Typical bindings where a keyboard is exposed:

- **`[`** / **`]`** — zoom out / in (map stays centered when possible)
- **Arrow keys**, **`h` `j` `k` `l`**, or related scancodes — pan
- **`M`** — cycle map style
- **Esc**, **`q`**, backtick — leave **Maps** for the app gallery

The viewer keeps a **3×3** grid of tiles, **reuses tiles already in RAM** when you pan so cells that stay on screen avoid extra SD reads, and keeps the map **geographically centered** when the zoom level changes when possible.

## See also

- [GPS features in the Flipper companion](../flipper-app/gps-features) — high-level GPS and mapping features
- [Wardriving](wardriving) — logging networks to CSV (WiGLE workflow)
- [SD card configuration](../getting-started/sd-card-config) — `config.cfg` and SD usage
1 change: 1 addition & 0 deletions docs/wiki/Features.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ GhostESP comes packed with various features for wireless network exploration and

- Wardriving with GPS logging (WiFi and BLE)
- Live GPS info display
- Offline raster map viewer (**Apps** → **Maps**): Slippy tiles on SD under `maps/<style>/tiles/...` (each `maps/` subfolder with a `tiles/` dir is a style; cycle via HUD / **M** / encoder); PNG or JPEG (including JPEG data in files named `.png`); optional `metadata.json` for bounds; zoom range follows on-disk `z/` levels when the card is readable

### Port Scanning

Expand Down
3 changes: 3 additions & 0 deletions include/managers/sd_card_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
#include "esp_err.h"
#include <stdbool.h>

/** VFS mount point for the SD card (also used in paths for fopen, stat, etc.). */
#define GHOSTESP_SD_ROOT "/mnt/ghostesp"

#define MAX_PORTALS 32
#define MAX_PORTAL_NAME 64

Expand Down
2 changes: 0 additions & 2 deletions include/managers/views/app_gallery_screen.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@

void apps_menu_create(void);
void apps_menu_destroy(void);
static void apps_menu_event_handler(InputEvent *event);
void get_apps_menu_callback(void **callback);
static void select_app_item(int index, bool slide_left);
void update_app_item_styles(void);

extern View apps_menu_view;
Expand Down
18 changes: 18 additions & 0 deletions include/managers/views/offline_map_jpeg_stb.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#pragma once

#include "lvgl.h"
#include <stddef.h>
#include <stdint.h>

/**
* Decode JPEG bytes (baseline or progressive) to uncompressed RGB565 for LVGL (LV_COLOR_DEPTH 16).
* On success: frees @p jpeg_data and returns a new lv_img_dsc_t (free dsc and dsc->data in free_tile_ram_slot).
* On failure: leaves @p jpeg_data intact and returns NULL.
*/
lv_img_dsc_t *offline_map_try_decode_jpeg_to_rgb565_dsc(uint8_t *jpeg_data, size_t len, const char *path_for_log);

/**
* Decode PNG (including 8-bit palette) to RGB565 via lodepng (heap decode; safe on small LVGL task stacks).
* On success: frees @p png_data. On failure: leaves @p png_data for the caller (compressed fallback).
*/
lv_img_dsc_t *offline_map_try_decode_png_to_rgb565_dsc(uint8_t *png_data, size_t len, const char *path_for_log);
11 changes: 11 additions & 0 deletions include/managers/views/offline_map_screen.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#ifndef OFFLINE_MAP_SCREEN_H
#define OFFLINE_MAP_SCREEN_H

#include "managers/display_manager.h"

void offline_map_view_create(void);
void offline_map_view_destroy(void);

extern View offline_map_view;

#endif
3 changes: 3 additions & 0 deletions main/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ list(APPEND app_sources
)
# Explicitly include infrared view implementation
list(APPEND app_sources "${CMAKE_SOURCE_DIR}/main/managers/views/infrared_view.c")
# Not picked up until CMake reconfigure if relying on GLOB_RECURSE alone; defines offline_map_try_decode_jpeg_to_rgb565_dsc
list(APPEND app_sources "${CMAKE_SOURCE_DIR}/main/managers/views/offline_map_jpeg_stb.c")
list(APPEND app_sources "${CMAKE_SOURCE_DIR}/main/managers/settings_sd_backup.c")
# Exclude i80_display.c from the glob as it's conditionally included later
list(REMOVE_ITEM app_sources "${CMAKE_SOURCE_DIR}/main/vendor/i80_display.c")
Expand Down Expand Up @@ -163,5 +165,6 @@ idf_build_get_property(idf_path IDF_PATH)
target_include_directories(${COMPONENT_LIB} PRIVATE
"${idf_path}/components/wpa_supplicant/src"
"${idf_path}/components/wpa_supplicant/esp_supplicant/src"
"${CMAKE_SOURCE_DIR}/components/lvgl/src/extra/libs/png"
)

3 changes: 2 additions & 1 deletion main/managers/display_manager.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@
uint32_t theme_palette_get_surface_alt(uint8_t theme);
uint32_t theme_palette_get_text_muted(uint8_t theme);

#define LVGL_TICK_TASK_STACK_SIZE 8192
/* Maps / lodepng / img decode need headroom; 8K was marginal with 9× tile work on same task. */
#define LVGL_TICK_TASK_STACK_SIZE 14336

#ifndef CONFIG_JC3248W535EN_LCD
static TaskHandle_t hardware_input_task_handle = NULL;
Expand Down
5 changes: 4 additions & 1 deletion main/managers/views/app_gallery_screen.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "managers/views/ghostchi_screen.h"
#include "managers/views/main_menu_screen.h"
#include "managers/views/music_visualizer.h"
#include "managers/views/offline_map_screen.h"
#include "managers/views/terminal_screen.h"

#include "managers/settings_manager.h"
Expand Down Expand Up @@ -38,6 +39,7 @@ static app_item_t app_items[] = {
{"Visualizer", &rave, 4, {{0}}, &music_visualizer_view},
{"Terminal", &terminal_icon, 5, {{0}}, &terminal_view},
{"Ghostchi", &ghost, 2, {{0}}, &ghostchi_view},
{"Maps", &Map, 2, {{0}}, &offline_map_view},
{"Back", NULL, 0, {{0}}, NULL},
};

Expand Down Expand Up @@ -76,6 +78,8 @@ static void refresh_apps_surface_colors(void) {
apps_text_color = lv_color_hex(theme_palette_get_text(theme));
}

static void select_app_item(int index, bool slide_left);

// Use the same theme palettes as the main menu to color app borders
static void init_app_colors(void) {
uint8_t theme = settings_get_menu_theme(&G_Settings);
Expand Down Expand Up @@ -315,7 +319,6 @@ static void create_apps_grid_menu(void) {

int cols = num_apps < 3 ? num_apps : 3;
if (cols <= 0) cols = 1;
int rows = (num_apps + cols - 1) / cols;
int margin = 6;
if (screen_width <= 240 || avail_height <= 120) {
margin = 0;
Expand Down
Loading
Loading