c5lab/m5monsterc5-cardputeradv/janos-uart-app

Claude Code and agent skill by c5lab/m5monsterc5-cardputeradv

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Build LVGL touchscreen applications that communicate with JanOS ESP32C5 firmware over UART. Use when creating screens for Tab5, CoreS3, Cardputer, or any ESP-IDF device that sends UART commands like scan_networks, start_deauth, list_hosts, show_pass. Covers screen building, UART parsing, attack flows, and navigation patterns.

Install

npx vskill@latest install c5lab/m5monsterc5-cardputeradv/janos-uart-app

SKILL.md

JanOS UART Application Builder

Guide for building ESP-IDF + LVGL applications that control JanOS firmware on ESP32C5 via UART. Reference implementation: /Users/janulrich/kod/dev/tabgit/M5MonsterC5-Tab5/main/main.c

For the complete command list, see commands-reference.md.

Architecture

┌────────────────────────────────┐ UART (115200 8N1) ┌──────────────────────┐
│ UI Device (Tab5/CoreS3/etc) │ ──────────────────────────────> │ JanOS on ESP32C5 │
│ - LVGL touchscreen │ <────────────────────────────── │ - WiFi radio │
│ - Sends text commands │ Text responses │ - BLE radio │
│ - Parses text responses │ │ - SD card │
│ - FreeRTOS tasks for I/O │ │ - GPS (optional) │
└────────────────────────────────┘ └──────────────────────┘

JanOS exposes a text CLI over UART. The UI app sends commands like scan_networks\r\n and parses the multi-line text responses to update the screen.

UART Setup

c
#define UART_NUM UART_NUM_1
#define UART_BAUD_RATE 115200
#define UART_BUF_SIZE 4096
static void uart_init(void) {
const uart_config_t uart_config = {
.baud_rate = UART_BAUD_RATE,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.source_clk = UART_SCLK_DEFAULT,
};
uart_driver_install(UART_NUM, UART_BUF_SIZE * 2, 0, 0, NULL, 0);
uart_param_config(UART_NUM, &uart_config);
uart_set_pin(UART_NUM, tx_pin, rx_pin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
}

Pin assignments (configurable, stored in NVS):

ConnectorTXRXUsage
Grove5354UART1 (primary)
M5Bus3738UART2 (Kraken secondary)

For CoreS3: use the appropriate GPIO for your board. The Tab5 reference stores pin config in NVS and reads with get_uart1_pins().

Sending Commands

Always append \r\n. Log every command sent.

c
static void uart_send_command(const char *cmd) {
uart_write_bytes(UART_NUM, cmd, strlen(cmd));
uart_write_bytes(UART_NUM, "\r\n", 2);
ESP_LOGI(TAG, "Sent: %s", cmd);
}

Reading & Parsing Responses

Core Pattern: Line-by-Line Accumulation

All UART parsing follows the same pattern -- read bytes, accumulate into a line buffer character by character, parse on newline:

c
char rx_buffer[UART_BUF_SIZE];
char line_buffer[512];
int line_pos = 0;
while (!done && !timed_out) {
int len = uart_read_bytes(UART_NUM, rx_buffer, UART_BUF_SIZE - 1, pdMS_TO_TICKS(100));
if (len > 0) {
rx_buffer[len] = '\0';
for (int i = 0; i < len; i++) {
char c = rx_buffer[i];
if (c == '\n' || c == '\r') {
if (line_pos > 0) {
line_buffer[line_pos] = '\0';
// === PARSE THIS LINE ===
if (strstr(line_buffer, "COMPLETION_MARKER")) {
done = true;
break;
}
parse_data_line(line_buffer);
line_pos = 0;
}
} else if (line_pos < sizeof(line_buffer) - 1) {
line_buffer[line_pos++] = c;
}
}
}
}
Completion Markers

Every command response has a known end marker. Wait for it before proceeding:

CommandCompletion Marker
scan_networks"Scan results printed"
wifi_connect"SUCCESS" or "FAILED" or "TIMEOUT"
start_nmap"Scanned" + "open ports" (final summary line)
list_hosts"Discovered Hosts" (header line, data follows)
list_sd"HTML files found" (header, data follows), or timeout
show_passTimeout (no explicit end marker)
list_probesTimeout (no explicit end marker)
wpasec_upload"Done:"
start_pcap"PCAP radio capture started" or "PCAP net capture started" (initial); on stop: "PCAP saved:"
start_beacon_spam"Beacon spam started. Use 'stop' to end."
start_beacon_spam_ssids"Beacon spam started. Use 'stop' to end." (same as start_beacon_spam)
list_ssidsTimeout (no explicit end marker, list ends after last indexed line)
add_ssid"Added SSID:"
remove_ssid"SSID removed."
version"JanOS version: X.Y.Z" (single line, immediate)

For commands without explicit end markers, use a timeout with empty-read detection (e.g., 3 consecutive empty reads of 500ms each).

Key Parsing Recipes

Network scan CSV -- fields: index, SSID, (empty), BSSID, channel, security, RSSI, band:

c
// Line: "1","AX3","","C4:2B:44:12:29:20","112","WPA2","-59","5GHz"
static bool parse_network_line(const char *line, wifi_network_t *net) {
if (line[0] != '"') return false;
char temp[256];
strncpy(temp, line, sizeof(temp) - 1);
char *fields[8] = {NULL};
int field_idx = 0;
char *p = temp;
while (*p && field_idx < 8) {
if (*p == '"') {
p++;
fields[field_idx] = p;
while (*p && *p != '"') p++;
if (*p == '"') { *p = '\0'; p++; }
field_idx++;
if (*p == ',') p++;
} else { p++; }
}
if (field_idx < 8) return false;
net->index = atoi(fields[0]);
strncpy(net->ssid, fields[1], sizeof(net->ssid) - 1);
strncpy(net->bssid, fields[3], sizeof(net->bssid) - 1);
net->channel = atoi(fields[4]);
strncpy(net->security, fields[5], sizeof(net->security) - 1);
net->rssi = atoi(fields[6]);
strncpy(net->band, fields[7], sizeof(net->band) - 1);
return true;
}

Host list -- parse " IP -> MAC" lines:

c
if (strstr(line, "Our IP:") != NULL) {
// Extract IP after "Our IP: " up to ","
} else if (strstr(line, "->") != NULL) {
char *p = line;
while (*p == ' ') p++;
// read IP until space, skip " -> ", read MAC
}

Evil twin passwords -- "SSID", "password":

c
if (line[0] == '"') {
char *ssid_start = line + 1;
char *ssid_end = strchr(ssid_start, '"');
if (ssid_end && *(ssid_end+1) == ',' && *(ssid_end+3) == '"') {
*ssid_end = '\0';
char *pass_start = ssid_end + 4;
char *pass_end = strchr(pass_start, '"');
if (pass_end) { *pass_end = '\0'; /* ssid_start and pass_start are your values */ }
}
}

HTML file list -- "N filename.html":

c
if (strstr(line_buffer, "HTML files found") != NULL) {
header_found = true;
} else if (header_found && line_pos > 2) {
int file_num;
char filename[64];
if (sscanf(line_buffer, "%d %63s", &file_num, filename) == 2) {
// store filename at index file_num
}
}

Probe list -- "N SSID":

c
char *p = line;
while (*p == ' ') p++;
if (isdigit((unsigned char)*p)) {
int idx = 0;
while (isdigit((unsigned char)*p)) { idx = idx * 10 + (*p - '0'); p++; }
while (*p == ' ') p++;
if (*p != '\0') {
char ssid[33];
strncpy(ssid, p, sizeof(ssid) - 1);
// trim trailing whitespace
}
}

Deauth detector -- "[DEAUTH] CH: N | AP: name (BSSID) | RSSI: N":

c
static bool parse_deauth_line(const char *line, deauth_entry_t *entry) {
if (!strstr(line, "[DEAUTH]")) return false;
const char *ch = strstr(line, "CH:");
entry->channel = atoi(ch + 3);
const char *ap = strstr(line, "AP:"); ap += 3; while (*ap == ' ') ap++;
const char *paren = strchr(ap, '(');
size_t ap_len = paren - ap;
while (ap_len > 0 && ap[ap_len-1] == ' ') ap_len--;
memcpy(entry->ap_name, ap, ap_len);
const char *paren_end = strchr(paren + 1, ')');
memcpy(entry->bssid, paren + 1, paren_end - paren - 1);
entry->rssi = atoi(strstr(line, "RSSI:") + 5);
return true;
}

Handshake success:

c
if (strstr(line, "HANDSHAKE IS COMPLETE AND VALID")) { /* handshake validated */ }
if (strstr(line, "PCAP saved:")) { /* extract filename from path */ }
if (strstr(line, "handshake saved for SSID:")) { /* extract SSID after "SSID: " */ }

Evil twin password capture:

c
if (strstr(line, "connected to SSID=")) { /* extract SSID between ' quotes */ }
if (strstr(line, "password=")) { /* extract password after = */ }
if (strstr(line, "Password verified!")) { /* attack succeeded */ }

wifi_connect result (password is optional -- omit for open networks):

c
if (strstr(rx_buffer, "SUCCESS")) { connected = true; }
if (strstr(rx_buffer, "FAILED") || strstr(rx_buffer, "TIMEOUT")) { failed = true; }
// Extract DHCP IP from: "DHCP IP: 192.168.0.5, Netmask: 255.255.255.0, GW: 192.168.0.1"
const char *dhcp = strstr(rx_buffer, "DHCP IP:");
if (dhcp) { /* parse IP, Netmask, GW */ }

start_nmap progress and results:

c
// Progress line: " Scanning 192.168.0.4 ports 21-143 [1/100] ..."
if (strstr(line, "Scanning") && strstr(line, "ports") && strchr(line, '[')) {
char ip[16]; int port_from, port_to, current, total;
sscanf(line, " Scanning %15s ports %d-%d [%d/%d]",
ip, &port_from, &port_to, &current, &total);
int pct = (current * 100) / total;
// update progress bar
}
// New host: "Host: 192.168.0.4 (00:C0:CA:B4:E6:3F)"
if (strncmp(trimmed, "Host:", 5) == 0) {
char ip[16], mac[18];
if (sscanf(trimmed, "Host: %15s (%17[^)])", ip, mac) == 2) {
// new host with MAC
} else if (sscanf(trimmed, "Host: %15s (MAC unknown)", ip) == 1) {
// new host without MAC
}
}
// Open port: " 135/tcp open MSRPC"
int port; char service[32];
if (sscanf(trimmed, "%d/tcp open %31s", &port, service) == 2 ||
sscanf(trimmed, "%d/tcp open %31s", &port, service) == 2) {
// found open port
}
// Completion: "Scanned 4 hosts, found 4 open ports"
if (strstr(line, "Scanned") && strstr(line, "open ports")) {
int hosts, ports;
sscanf(line, "Scanned %d hosts, found %d open ports", &hosts, &ports);
// scan complete
}

Screen Building

Screen Lifecycle
show_*_page()
├── Create LVGL objects (container, labels, buttons, table)
├── Register event callbacks
├── Start background UART monitor task (if needed)
└── Set as current visible page
[User interacts / UART data arrives]
cleanup (on back/close):
├── Set monitoring_active = false
├── Send "stop" via UART
├── Wait for task to finish (furi_thread_join or vTaskDelete)
├── Delete LVGL objects (lv_obj_del)
├── NULL all pointers
└── Free allocated memory
Tile (Button) Creation
c
static lv_obj_t *create_tile(lv_obj_t *parent, const char *icon_text,
const char *label_text, lv_color_t bg_color,
lv_event_cb_t cb, const char *user_data) {
lv_obj_t *tile = lv_btn_create(parent);
lv_obj_set_size(tile, TILE_WIDTH, TILE_HEIGHT);
lv_obj_set_style_bg_color(tile, bg_color, LV_STATE_DEFAULT);
lv_obj_set_style_bg_color(tile, lv_color_lighten(bg_color, 50), LV_STATE_PRESSED);
lv_obj_set_style_radius(tile, 12, 0);
lv_obj_set_style_shadow_width(tile, 0, 0);
lv_obj_set_flex_flow(tile, LV_FLEX_FLOW_COLUMN);
lv_obj_set_flex_align(tile, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
lv_obj_t *icon = lv_label_create(tile);
lv_label_set_text(icon, icon_text);
lv_obj_set_style_text_font(icon, &lv_font_montserrat_28, 0);
lv_obj_set_style_text_color(icon, lv_color_white(), 0);
lv_obj_t *label = lv_label_create(tile);
lv_label_set_text(label, label_text);
lv_obj_set_style_text_color(label, lv_color_white(), 0);
if (cb) lv_obj_add_event_cb(tile, cb, LV_EVENT_CLICKED, (void *)user_data);
return tile;
}

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 95 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
108
Repository forks
10
7-day trend score
23

Stars and forks are for the whole GitHub repository, not this skill alone. Refreshed 30 Mar 2026, 7 months ago.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/c5lab/m5monsterc5-cardputeradv/janos-uart-app/badge)](https://verified-skill.com/skills/c5lab/m5monsterc5-cardputeradv/janos-uart-app)

Something wrong with this skill or its page? Tell us.