From 0abbad113b1f209c5f31e99efa2970d92286f74d Mon Sep 17 00:00:00 2001 From: reubenmiller Date: Wed, 11 Nov 2026 11:44:13 +1000 Subject: [PATCH] feat: run cloud shell commands on the ESP32-C6 Modbus full image The C6 Modbus tedge-full image had no c8y_Command: the shell-diagnostics extra costs 16 KB of RAM and 120 KB of flash there, past the image's 80 % flash budget. release/extras/shell-lean.conf goes after it and trims the shell to what a cloud command uses (no history, completion, terminal handling, log backend or network and Wi-Fi shells), with smaller output buffers, since a result is cut at about 220 bytes anyway. `tedge net` (CONFIG_APP_NET_SHELL_CMD) replaces `net iface` and `wifi status` with one short answer: Wi-Fi state, SSID, channel, RSSI, address, gateway, DNS server and gateway pings. `kernel heap` joins the C6 allow-list to show the kernel heap the Wi-Fi driver uses. Cost against 0.11.1: +9,360 B RAM (malloc arena 74 KB -> 65 KB), +6.5 KB flash (73.7 % of the slot). Other builds are unchanged. Co-Authored-By: Claude Opus 5.5 --- DEVICES.md | 43 +++++++++++++++++++ README.md | 5 ++- cumulocity/README.md | 15 +++++-- lib/common/Kconfig | 11 +++++ lib/common/net.c | 75 ++++++++++++++++++++++++++++++++++ release/devices.yml | 6 ++- release/extras/shell-lean.conf | 48 ++++++++++++++++++++++ release/size-baseline.json | 8 ++-- 8 files changed, 201 insertions(+), 10 deletions(-) create mode 100644 release/extras/shell-lean.conf diff --git a/DEVICES.md b/DEVICES.md index 40ec980..0260795 100644 --- a/DEVICES.md +++ b/DEVICES.md @@ -263,6 +263,49 @@ Test note: a TCP probe of a local `c8y remoteaccess server` port opens a tunnel of its own, which holds the device's single session for a moment; an SSH connection straight after it can find the slot busy. +### Lean cloud shell on the ESP32-C6 (2026-11-11) + +The C6 Modbus `tedge-full` image now carries the cloud shell command too, +with `release/extras/shell-lean.conf` after `shell-diagnostics.conf`. The +full extra did not fit: +15.9 KB of RAM, and +120 KB of flash, which put the +image at 82.4 % of its slot (budget 80 %). The lean extra keeps the +allow-list's purpose and drops what a cloud command cannot use: + +- No network or Wi-Fi shell (about 65 KB of flash). `net iface` and + `wifi status` print more than a result holds (it is cut at about 220 + bytes), so `tedge net` (lib/common/net.c, `CONFIG_APP_NET_SHELL_CMD`) + answers instead: Wi-Fi state, SSID, channel and RSSI, address and prefix, + gateway, DNS server and the gateway ping count. +- No history, completion, VT100, metakeys, wildcards, stats, shell log + backend, device shell, log commands or stack painting; a 1.5 KB shell + thread (it only starts the dummy backend; commands run on the client's + command thread). +- Output buffers of 256 B instead of 512 B (the result queue holds two). +- `kernel heap` joins the allow-list: it reports the kernel heap that the + Wi-Fi driver and the tunnel's websocket allocate from. + +Against `main` (0.11.1), measured with `scripts/release/build.sh`: + +| Image | Flash | RAM (sram0) | malloc arena | +|---|---|---|---| +| modbus `tedge-full` | 959,692 B | 435,312 B | 74,128 B | +| + full shell extra | 1,080,508 B (82.4 %) | +15,928 B | 63,280 B | +| + lean shell extra | 966,202 B (73.7 %) | +9,360 B | 64,768 B | + +Of the 9.4 KB: the command thread's stack 4,096 B (tiny-edge's default, not +yet measured), the shell thread's stack 1,536 B, the dummy backend 528 B, the +shell instance 512 B, the result queue 520 B, two thread objects 592 B, the +job 176 B, and about 1.4 KB of small objects and code in RAM. The Wi-Fi +driver allocates from the kernel heap (`k_malloc`, about 55 KB, fixed by the +drivers' minimums), which none of this touches; the shell takes its RAM from +the libc malloc arena. + +On the board (eu-latest, `tedge-modbuse8f60afb8198`): the image installed +over the air and confirmed, and the device lists `c8y_Command`. The commands, +a refused command, and a 30-minute run with a firmware download and a tunnel +(where the full extra failed on the agent image in September) are still to +be recorded here. + ### Identify measured (2026-10-06) `device-identify` adds `tedge identify [seconds]` (cloud shell) and the sw0 diff --git a/README.md b/README.md index a9bc78f..9838ca4 100644 --- a/README.md +++ b/README.md @@ -1392,8 +1392,9 @@ c8y operations create --device \ This needs an image with the cloud shell command, which today means the images built with `tiny-edge/ports/zephyr/profiles/extras/shell-diagnostics.conf` -(the S3-DevKitC and QT Py modbus-server and tedge-agent full builds; see -DEVICES.md). That file puts `tedge identify` on the allow-list. On other +(the S3-DevKitC and QT Py modbus-server and tedge-agent full builds, and the +ESP32-C6 modbus-server full build with `release/extras/shell-lean.conf`; see +DEVICES.md). Both put `tedge identify` on the allow-list. On other images the cloud has no shell, so only the button works. The same command works on a local console where the image has one. diff --git a/cumulocity/README.md b/cumulocity/README.md index 8e06a1f..09f4de3 100644 --- a/cumulocity/README.md +++ b/cumulocity/README.md @@ -116,7 +116,16 @@ the device runs: `net ping` is not offered: Zephyr prints the replies after the command returns, so the cloud would only ever see the `PING ` line. +The ESP32-C6 Modbus `tedge-full` image carries a leaner set +(`release/extras/shell-lean.conf`), because the full one costs too much RAM +and flash there (DEVICES.md, "Lean cloud shell on the ESP32-C6"). In place +of `net iface`, `net conn` and `wifi status` it has: + +| Command | Answers | +|---|---| +| `tedge net` | Wi-Fi state, SSID, channel and RSSI, address, gateway, DNS server | +| `kernel heap` | free and peak use of the kernel heap (Wi-Fi driver, tunnel) | + Anything else is refused with the reason. Output travels in one SmartREST -field, so a long answer is cut. The other boards' images leave it out: on -the ESP32-C6 its ~16 KB of RAM left too little for the Wi-Fi driver and the -firmware download (DEVICES.md, "Shell diagnostics measured"). +field of about 220 bytes, so a long answer is cut. The other boards' images +leave the shell out. diff --git a/lib/common/Kconfig b/lib/common/Kconfig index 337019d..cb2b951 100644 --- a/lib/common/Kconfig +++ b/lib/common/Kconfig @@ -346,6 +346,17 @@ config APP_WIFI_PROV_ERASE_HOLD_S endif # APP_PROV_HANDOFF +config APP_NET_SHELL_CMD + bool "Shell command `tedge net`: the network state in one short answer" + depends on TEDGE && SHELL && !NET_SOCKETS_OFFLOAD + help + Prints the Wi-Fi network, signal and channel, the address, gateway + and DNS server, and the gateway ping count, in less than a cloud + command's result holds (about 220 bytes). For builds that leave out + the network and Wi-Fi shells to save flash (release/extras/ + shell-lean.conf); the cloud runs it once it is on the + TEDGE_SHELL_COMMAND_ALLOW_LIST. + config APP_IDENTIFY bool "Identify the device from the cloud and from its button" default y diff --git a/lib/common/net.c b/lib/common/net.c index e2f87bf..5442c36 100644 --- a/lib/common/net.c +++ b/lib/common/net.c @@ -998,3 +998,78 @@ int app_net_init(void) } #endif + +#if defined(CONFIG_APP_NET_SHELL_CMD) +#include +#if defined(CONFIG_DNS_RESOLVER) +#include +#endif + +/* `tedge net`: the network state in one short answer. A cloud command's + * result is cut at about 220 bytes, which `net iface` and `wifi status` + * overrun on their own, and those shells cost about 65 KB of flash. This + * prints what a support call asks first. */ +static int cmd_net(const struct shell *sh, size_t argc, char **argv) +{ + struct net_if *iface = net_if_get_default(); + char ip[16], gw[16]; + int prefix = 0; + + ARG_UNUSED(argc); + ARG_UNUSED(argv); + +#if defined(CONFIG_WIFI) + struct net_if *wifi = net_if_get_first_wifi(); + struct wifi_iface_status st = {0}; + + if (wifi != NULL && + net_mgmt(NET_REQUEST_WIFI_IFACE_STATUS, wifi, &st, sizeof(st)) == 0) { + if (st.state >= WIFI_STATE_ASSOCIATED) { + shell_print(sh, "wifi %s \"%.32s\" ch %u rssi %d dBm", + wifi_state_txt(st.state), st.ssid, + st.channel, st.rssi); + } else { + shell_print(sh, "wifi %s", wifi_state_txt(st.state)); + } + } +#endif + fmt_ip(ip, sizeof(ip), current_ipv4); + if (iface != NULL) { + struct net_in_addr self = { .s_addr = current_ipv4 }; + uint32_t mask = net_ntohl( + net_if_ipv4_get_netmask_by_addr(iface, &self).s_addr); + + fmt_ip(gw, sizeof(gw), net_if_ipv4_get_gw(iface).s_addr); + while (mask & 0x80000000U) { + prefix++; + mask <<= 1; + } + shell_print(sh, "ip %s/%d gw %s", ip, prefix, gw); + } +#if defined(CONFIG_DNS_RESOLVER) + struct dns_resolve_context *dns = dns_resolve_get_default(); + + for (int i = 0; dns != NULL && i < DNS_RESOLVER_MAX_SERVERS; i++) { + const struct net_sockaddr *sa = &dns->servers[i].dns_server; + + if (dns->servers[i].is_mdns || sa->sa_family != NET_AF_INET) { + continue; + } + fmt_ip(ip, sizeof(ip), net_sin(sa)->sin_addr.s_addr); + shell_print(sh, "dns %s", ip); + } +#endif +#if defined(CONFIG_NET_IPV4) + shell_print(sh, "gateway ping %d/%u", (int)atomic_get(&ping_ok), + ping_sent); +#endif + if (g_last_reason >= 0) { + shell_print(sh, "last wifi reason %d", g_last_reason); + } + return 0; +} + +/* Hangs off the client's own "tedge" root, which tiny-edge defines. */ +SHELL_SUBCMD_ADD((tedge), net, NULL, + "network state: Wi-Fi, address, gateway, DNS", cmd_net, 1, 0); +#endif /* CONFIG_APP_NET_SHELL_CMD */ diff --git a/release/devices.yml b/release/devices.yml index 73c12f1..16f062c 100644 --- a/release/devices.yml +++ b/release/devices.yml @@ -56,7 +56,11 @@ devices: - { app: snmp-agent, variant: standalone } - app: modbus-server variant: tedge-full - measured: DEVICES.md#release-builds-measured-on-the-boards-2026-09-21 + pr: true + extra_conf: + - "{tiny-edge}/ports/zephyr/profiles/extras/shell-diagnostics.conf" + - release/extras/shell-lean.conf + measured: DEVICES.md#lean-cloud-shell-on-the-esp32-c6-2026-11-11 - app: modbus-server variant: tedge-ota measured: DEVICES.md#release-builds-measured-on-the-boards-2026-09-21 diff --git a/release/extras/shell-lean.conf b/release/extras/shell-lean.conf new file mode 100644 index 0000000..e8af746 --- /dev/null +++ b/release/extras/shell-lean.conf @@ -0,0 +1,48 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# The cloud shell command (c8y_Command) for boards without room for the full +# shell-diagnostics extra: goes after +# {tiny-edge}/ports/zephyr/profiles/extras/shell-diagnostics.conf in a build's +# extra_conf (release/devices.yml), and trims it. Measured on the +# ESP32-C6 Modbus tedge-full image in DEVICES.md. +# +# A cloud command runs on the client's command thread against the dummy +# backend, and its result is cut at about 220 bytes. So the shell needs no +# history, completion, terminal handling or log backend, and its own thread +# only starts the backend. +CONFIG_SHELL_STACK_SIZE=1536 +CONFIG_SHELL_LOG_BACKEND=n +CONFIG_SHELL_HISTORY=n +CONFIG_SHELL_TAB=n +CONFIG_SHELL_WILDCARD=n +CONFIG_SHELL_VT100_COMMANDS=n +CONFIG_SHELL_VT100_COLORS=n +CONFIG_SHELL_METAKEYS=n +CONFIG_SHELL_STATS=n +CONFIG_SHELL_CMDS=n +CONFIG_SHELL_CMD_BUFF_SIZE=160 +CONFIG_SHELL_ARGC_MAX=12 +CONFIG_DEVICE_SHELL=n +CONFIG_LOG_CMDS=n +CONFIG_LOG_RUNTIME_FILTERING=n +# Only "kernel uptime", "kernel version" and "kernel heap" are allowed, not +# the thread commands that stack painting serves. +CONFIG_INIT_STACKS=n +# What a command printed is cut to one result of about 220 bytes, so +# capturing more only fills the result queue (two entries). +CONFIG_SHELL_BACKEND_DUMMY_BUF_SIZE=256 +CONFIG_TEDGE_SHELL_COMMAND_OUTPUT_BYTES=256 + +# `tedge net` (lib/common/net.c) answers in one short result instead of the +# network and Wi-Fi shells (`net iface`, `wifi status`), which print more +# than a result holds and cost about 65 KB of flash. +CONFIG_NET_SHELL=n +CONFIG_NET_L2_WIFI_SHELL=n +CONFIG_APP_NET_SHELL_CMD=y + +# kernel uptime / kernel version how long it has run, what it runs +# kernel heap the kernel heap (Wi-Fi driver, tunnel) +# tedge net Wi-Fi, address, gateway, DNS +# tedge params list / tedge diag the client's parameter sets and state +# tedge identify [seconds] blink the status LED to find the board +CONFIG_TEDGE_SHELL_COMMAND_ALLOW_LIST="kernel uptime,kernel version,kernel heap,tedge net,tedge params list,tedge diag,tedge identify" diff --git a/release/size-baseline.json b/release/size-baseline.json index 657b0ff..4447cef 100644 --- a/release/size-baseline.json +++ b/release/size-baseline.json @@ -74,13 +74,13 @@ } }, "modbus-server-tedge-full-esp32c6-devkitc": { - "image": 959692, - "content": 839144, + "image": 966203, + "content": 862607, "ram": { - "sram0_0_seg": 435312, + "sram0_0_seg": 444672, "lp_ram_seg": 36 }, - "arena": 74128, + "arena": 64768, "provisioner": { "image": 1013963, "content": 909467,