Embedded Hosts¶
An embedded host is a firmware or RTOS target reached over a serial console — typically via a telnet connection, often through an SSH hop. Shell I/O is wrapped in a command frame that encodes each command and parses the output/return-code back from the plain telnet byte stream.
Embedded hosts expose the same Host API as Unix hosts (run / oneshot /
send / expect / put / get), so test code does not branch on host type.
The key differences are:
One console. An embedded target exposes a single shell. There is no second channel and no stateless exec primitive, so
oneshotshares the persistent session withrunand is not concurrency-safe.No bash. No
$?, no command substitution, noscp/ftp/nc. Command framing and file transfer use a device-shell protocol, not Unix tools.Telnet only. The shell is reached over telnet (optionally through an SSH hop), never SSH directly.
Host class taxonomy¶
Host (Protocol) / BaseHost (ABC)
├── LocalHost(BaseHost) local machine, no network
├── RemoteHost(BaseHost) abstract base for networked hosts
│ ├── UnixHost(RemoteHost) SSH/Telnet shell; SCP/SFTP/FTP/nc transfer
│ └── EmbeddedHost(RemoteHost) console-framed RTOS/firmware target;
│ │ OS-agnostic — fails loud with no command_frame
│ └── ZephyrHost(EmbeddedHost) concrete Zephyr defaults
└── DockerContainerHost(BaseHost) container host
Selecting an embedded host¶
Set osType in the host’s hosts.json entry to select the host class:
osType: "zephyr"builds aZephyrHostwith Zephyr-specific defaults (thezephyrcommand frame,osName: "Zephyr"). UseosVersionto record the exact kernel version —"2.7","3.7", and"4.4"appear in the in-tree test fixture.osType: "embedded"builds a bareEmbeddedHostwith no OS-specific defaults. BecauseEmbeddedHostcarries no defaultcommand_frame, it fails loud at construction if none is supplied:
EmbeddedHost '<name>' has no command_frame. A bare 'embedded' host carries no
shell-framing dialect. Set osType to a profile that supplies one (e.g.
"zephyr"), or pass an explicit command_frame.
Supply a frame via a profile (see OS Profiles & Custom Host Classes), or by setting
command_frame directly in the host entry.
Command frames¶
A command frame is a small stateless strategy object that:
wraps each command in unique sentinels (BEGIN/END markers, a return-code probe), and
parses the echoed byte stream back into
(output, retcode).
How the wrapping and parsing work differs per target OS — that variation is the frame’s dialect.
Built-in frames¶
Name |
Class |
Notes |
|---|---|---|
|
|
Stock Zephyr |
|
|
Same framing as |
|
|
POSIX bash; used internally by SSH/telnet Unix sessions. |
Declare a frame by name in lab data:
{
"ne": "sprout_no_fs",
"osType": "zephyr",
"command_frame": "zephyr-serial"
}
Custom frames¶
Projects can register additional dialects from an init module:
from otto.host.command_frame import register_command_frame, ZephyrFrame
class ZephyrInlineRetcodeFrame(ZephyrFrame):
type_name = "zephyr-inline"
# ... override parse_retcode / parse_output for 2.7 inline retcode
register_command_frame(ZephyrInlineRetcodeFrame.type_name, ZephyrInlineRetcodeFrame)
See Extending otto for new embedded targets for a full walkthrough of writing and registering a command frame.
Embedded filesystems¶
The filesystem field declares the on-device filesystem variant. It controls
the mount path, the optional fs mount command issued before the first
transfer, and the command-formation hooks the file transfer code drives.
Built-in filesystems¶
Name |
Class |
Mount path |
Notes |
|---|---|---|---|
|
|
— |
No filesystem. Transfer and disk metrics short-circuit to a clear no-op / error. |
|
|
|
FAT on a RAM disk. Otto issues |
|
|
|
LittleFS on simulated flash. Auto-mounted at boot via |
The max_filename_len field caps the basename length (including extension)
accepted by the target filesystem. Defaults to 255. Override per-host
when the firmware enforces a tighter limit — for example 32 for a build with
CONFIG_FS_FATFS_MAX_LFN=32 or CONFIG_FS_LITTLEFS_NAME_MAX=32.
Custom filesystems¶
Projects can register custom variants from an init module:
from otto.host.embedded_filesystem import register_filesystem, EmbeddedFileSystem
class MyFlashFs(EmbeddedFileSystem):
type_name = "my-flash"
mount = "/flash"
register_filesystem(MyFlashFs.type_name, MyFlashFs)
File transfer¶
The transfer field selects the file-transfer backend for an embedded host:
Value |
Description |
|---|---|
|
Default. Drives the device shell’s |
|
Reserved; not yet implemented. |
This is distinct from the Unix transfer set (scp, sftp, ftp, nc), which
are unavailable on embedded hosts — they require a POSIX shell.
Example¶
A sprout entry from the test fixture, annotated:
{
"ip": "192.0.2.1",
"ne": "sprout",
"osType": "zephyr",
"osVersion": "3.7",
"transfer": "console",
"filesystem": "fat-ram",
"max_filename_len": 32,
"is_virtual": true,
"hop": "basil_seed",
"snmp": {
"address": "10.10.200.14",
"port": 16101,
"community": "public",
"oids": [
"1.3.6.1.2.1.1.3.0",
"1.3.6.1.4.1.63245.1.1.0",
"1.3.6.1.4.1.63245.1.2.0",
"1.3.6.1.4.1.63245.1.3.0",
"1.3.6.1.4.1.63245.1.4.0"
]
},
"resources": [
"sprout"
],
"labs": [
"embedded"
]
}
Key fields:
osType: "zephyr"— buildsZephyrHostwith thezephyrcommand frame andosName: "Zephyr".osVersion: "3.7"— recorded on the host; selects the correct test paths (e.g. coverage SDK version).transfer: "console"— file I/O over the device’sfsshell commands.filesystem: "fat-ram"— FAT on RAM disk, mounted at/RAM:.max_filename_len: 32— firmwareCONFIG_FS_FATFS_MAX_LFNceiling.hop: "basil_seed"— all connections route through thebasil_seedjump host.snmpblock — enables SNMP-based metric collection alongside the telnet console (a separate channel).
See also¶
Lab Configuration — full
hosts.jsonschema referenceOS Profiles & Custom Host Classes — custom host classes and data profile bundles
Extending otto for new embedded targets — writing custom command frames and filesystems
Coverage Collection — cross-toolchain configuration for embedded coverage
otto monitor — SNMP monitoring configuration