Skip to content

Commit

Permalink
#153 WCLI screenshot
Browse files Browse the repository at this point in the history
153 wcli screenshot
  • Loading branch information
jgauchia authored Jun 18, 2024
2 parents 60fd9d6 + 7dd7386 commit 517653e
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 51 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@
.vscode/launch.json
.vscode/ipch
.vscode/settings.json

assets
tools/*.raw
tools/*.png
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,37 @@ Please follow the instructions provided by [OSM_Extract](https://github.com/ares
> [!NOTE]
> For production version don't forget unset these environment variables.
## CLI
IceNav has a basic CLI accessible via Serial and optionally via Telnet if enabled. When you access the CLI and type `help`, you should see the following commands:
```bash
clear: clear shell
info: get device information
nmcli: network manager CLI.
reboot: perform a ESP32 reboot
scshot: screenshot to SD or sending a PC
```
Some extra details:
**nmcli**: IceNav use a `wcli` network manager library. For more details of this command and its sub commands please refer to [here](https://github.com/hpsaturn/esp32-wifi-cli?tab=readme-ov-file#readme)
**schot**: This utility can save a screenshot to the root of your SD, with the name: `screenshot.raw`. You can convert it to png using the `convert.py` script in the `tools` folder.
Additionally, this screenshot command can send the screenshot over WiFi using the following syntax:
```bash
scshot 192.168.1.10 8123
```
Ensure your PC has the specified port open and firewall access enabled to receive the screenshot via the `netcat` command, like this:
```bash
nc -l -p 8123 > screenshot.raw
```
### TO DO
- [X] LVGL 9 Integration
Expand Down
83 changes: 34 additions & 49 deletions lib/cli/cli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@

#ifndef DISABLE_CLI
#include "cli.hpp"
#include "storage.hpp"
#include "tft.hpp"
#include "utils.h"

const char logo[] =
"\r\n"
Expand All @@ -24,39 +23,7 @@ const char logo[] =
""
;

// Capture the screenshot and save it to the SD card
void captureScreenshot(const char* filename, Stream *response) {
File file = SD.open(filename, FILE_WRITE);
if (!file) {
response->println("Failed to open file for writing");
return;
}

// Allocate memory to store the screen data
response->printf("Allocating memory for: %ix%i image\r\n",tft.width(),tft.height());
uint8_t* buffer = (uint8_t*)ps_malloc(tft.width() * tft.height() * sizeof(uint8_t));
if (!buffer) {
response->println("Failed to allocate memory for buffer");
file.close();
return;
}

// Read the screen data into the buffer
tft.readRect(0, 0, tft.width(), tft.height(), buffer);

// Write the buffer data to the file
for (int y = 0; y < tft.height(); y++) {
for (int x = 0; x < tft.width(); x++) {
uint16_t color = buffer[y * tft.width() + x];
file.write((const uint8_t*)&color, sizeof(color));
}
}

// Clean up
free(buffer);
file.close();
response->println("Screenshot saved");
}

void wcli_reboot(char *args, Stream *response) {
ESP.restart();
Expand All @@ -72,32 +39,50 @@ void wcli_clear(char *args, Stream *response){
}

void wcli_scshot(char *args, Stream *response){
captureScreenshot("/screenshot.raw",response);
Pair<String, String> operands = wcli.parseCommand(args);
String ip = operands.first();
uint16_t port = operands.second().toInt();

if (ip.isEmpty()){
response->println("Saving to SD..");
captureScreenshot(SCREENSHOT_TEMP_FILE, response);
response->println("Note: is possible to send it to a PC using: scshot ip port");
}
else {
response->printf("Sending screenshot to %s:%i..\r\n", ip.c_str(), port);
captureScreenshot(SCREENSHOT_TEMP_FILE, response);
captureScreenshot(SCREENSHOT_TEMP_FILE, ip.c_str(), port);
}
}

/**
* @brief WiFi CLI init and IceNav custom commands
**/
void initCLI() {
#ifndef ARDUINO_USB_CDC_ON_BOOT
Serial.begin(115200);
#endif
Serial.println("init CLI");
void initRemoteShell(){
#ifndef DISABLE_CLI_TELNET
if (wcli.isTelnetEnable()) wcli.shellTelnet->attachLogo(logo);
#endif
}

void initShell(){
wcli.shell->attachLogo(logo);
wcli.setSilentMode(true);
wcli.disableConnectInBoot();
// Main Commands:
wcli.add("reboot", &wcli_reboot, "\tperform a ESP32 reboot");
wcli.add("info", &wcli_info, "\t\tget device information");
wcli.add("clear", &wcli_clear, "\t\tclear shell");
wcli.add("scshot", &wcli_scshot, "\ttake screen shot");

wcli.add("scshot", &wcli_scshot, "\tscreenshot to SD or sending a PC");
wcli.begin("IceNav");
}

#ifndef DISABLE_CLI_TELNET
wcli.enableTelnet();
wcli.shellTelnet->attachLogo(logo);
#endif
/**
* @brief WiFi CLI init and IceNav custom commands
**/
void initCLI() {
#ifndef ARDUINO_USB_CDC_ON_BOOT
Serial.begin(115200);
#endif
log_v("init CLI");
initShell();
initRemoteShell();
}

#endif
76 changes: 76 additions & 0 deletions lib/cli/utils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#ifndef DISABLE_CLI
#include "cli.hpp"
#include "storage.hpp"
#include "tft.hpp"

#define SCREENSHOT_TEMP_FILE "/screenshot.raw"

// Capture the screenshot and save it to the SD card
void captureScreenshot(const char* filename, Stream *response) {
File file = SD.open(filename, FILE_WRITE);
if (!file) {
response->println("Failed to open file for writing");
return;
}

// Allocate memory to store the screen data (1 byte per segment)
uint8_t* buffer = (uint8_t*)malloc(tft.width() * tft.height() * 2); // 2 bytes per pixel
if (!buffer) {
response->println("Failed to allocate memory for buffer");
file.close();
return;
}

// Read the screen data into the buffer using readRect
tft.readRect(0, 0, tft.width(), tft.height(), (uint16_t*)buffer);

// Write the buffer data to the file
for (int y = 0; y < tft.height(); y++) {
for (int x = 0; x < tft.width(); x++) {
// Combine the two 8-bit segments into a 16-bit value
uint8_t highByte = buffer[(y * tft.width() + x) * 2];
uint8_t lowByte = buffer[(y * tft.width() + x) * 2 + 1];
uint16_t color = (highByte << 8) | lowByte;
file.write((const uint8_t*)&color, sizeof(color));
}
}

// Clean up
free(buffer);
file.close();
response->println("Screenshot saved");
}

// WiFi client
WiFiClient client;

void captureScreenshot(const char* filename, const char* pc_ip, uint16_t pc_port) {
if (!client.connect(pc_ip, pc_port)) {
Serial.println("Connection to server failed");
return;
}

Serial.println("Connected to server");

File file = SD.open(filename, FILE_READ);
if (!file) {
Serial.println("Failed to open file for reading");
client.stop();
return;
}

// Send the file data to the PC
while (file.available()) {
size_t size = 0;
uint8_t buffer[512];
size = file.read(buffer, sizeof(buffer));
if (size > 0) {
client.write(buffer, size);
}
}

file.close();
client.stop();
Serial.println("Screenshot sent over WiFi");
}
#endif
2 changes: 1 addition & 1 deletion lib/lvgl/src/lvglSetup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ void initLVGL()
DRAW_BUF_SIZE = TFT_WIDTH * TFT_HEIGHT * sizeof(lv_color_t);
else
// 2Mb PSRAM
DRAW_BUF_SIZE = ( TFT_WIDTH * TFT_HEIGHT * sizeof(lv_color_t) / 4);
DRAW_BUF_SIZE = ( TFT_WIDTH * TFT_HEIGHT * sizeof(lv_color_t) / 8);

log_v("LVGL: allocating %u bytes PSRAM for draw buffer",DRAW_BUF_SIZE * 2);
lv_color_t * drawBuf1 = (lv_color_t *)heap_caps_malloc(DRAW_BUF_SIZE, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
Expand Down
30 changes: 30 additions & 0 deletions tools/convert.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from PIL import Image
import numpy as np

# Define image dimensions
width = 320
height = 480

# Read the raw RGB565 file
with open("screenshot.raw", "rb") as f:
raw_data = f.read()

# Convert the raw data to an array of 16-bit values (RGB565)
pixels = np.frombuffer(raw_data, dtype=np.uint16)

# Convert RGB565 to RGB888
def rgb565_to_rgb888(pixel):
r = (pixel & 0xF800) >> 8
g = (pixel & 0x07E0) >> 3
b = (pixel & 0x001F) << 3
return (r, g, b)

# Apply the conversion to all pixels
rgb_data = np.array([rgb565_to_rgb888(pixel) for pixel in pixels], dtype=np.uint8)

# Reshape to the correct dimensions
rgb_data = rgb_data.reshape((height, width, 3))

# Create an image from the RGB data
image = Image.fromarray(rgb_data)
image.save("screenshot.png")

0 comments on commit 517653e

Please sign in to comment.