Unit file reference¶
Location: units/host.toml or units/<hardware-id>.toml. A unit selects the
services, settings, wiring and resource budgets for one target. The same service
can run on several targets with different unit settings.
micros new creates the host unit. Successful micros enroll adds a physical
board unit containing the detected identity, board definition and simulated climate services.
Keep that detected hardware information; enrollment cannot detect your peripherals.
Complete host example¶
This selects the three-service starter supplied by micros new:
# Self-contained local application: no device identity or hardware required.
[project]
name = "Simulated climate"
[services.climate_sensor]
source = "../services/climate-sensor"
contract = "service.toml"
memory = { arena = "RAM", stack_bytes = 4096 }
config = { interval_ms = 1000 }
[services.fan_controller]
source = "../services/fan-controller"
contract = "service.toml"
memory = { arena = "RAM", stack_bytes = 4096 }
config = { on_millicelsius = 28000, off_millicelsius = 24000 }
[services.status_display]
source = "../services/status-display"
contract = "service.toml"
memory = { arena = "RAM", stack_bytes = 4096 }
config = { stale_after_ms = 5000 }
[channels.climate_reading]
[channels.fan_status]
./micros compose -d host
./micros host deploy
./micros host logs
./micros host stop
The host has no [unit] identity or [board] definition and cannot use physical
GPIO. Generated output stays under the application's build/; host packages and
recovery state live under .micros/host.
Top-level sections¶
| Section | Host | Board | Purpose |
|---|---|---|---|
[project] |
Optional | Optional | Display name and advanced base-build requirements |
[unit] |
Omit | Required; generated by enrollment | Physical identity |
[board] |
Omit | Required; generated by enrollment | Hardware definition |
[services.NAME] |
Required for each selected service | Required for each selected service | One service instance |
[channels.NAME] |
When ports require it | When ports require it | Same-name channel declaration and optional depth |
[management.server] |
Normally omit | Normally omit | Advanced base-preparation server origin |
An explicitly empty [services] table is a valid declaration. Additional service
instances use additional [services.NAME] tables, not additional unit files for
the same target.
Selected services¶
Field under [services.NAME] |
Required / default | Meaning |
|---|---|---|
source |
Required path | Service source directory, relative to the unit file |
contract |
Required in this workflow | Contract path inside source, normally service.toml |
enabled |
Default true |
false omits the instance from the composed application |
name |
Default: contract's service.name |
Deployed name override, useful for multiple instances |
memory |
Required | Arena choice and optional stack budget; see below |
config |
Default {} |
Selected startup values; omitted fields use contract defaults |
pins |
Required when the service has pin roles | Exact map of role names to physical pin numbers |
expose_commands |
Default [] |
Declared commands that should be remotely callable |
Do not copy version, build, capabilities or config_schema from the contract
into this table. Those belong to the service and cannot be overridden here.
For a service with no ports, such as the SDK
hello-service example, distinct deployed
names allow two instances (copy that example into services/hello/ first):
[services.first]
source = "../services/hello"
contract = "service.toml"
name = "example.hello_first"
memory = { arena = "RAM", stack_bytes = 4096 }
[services.second]
source = "../services/hello"
contract = "service.toml"
name = "example.hello_second"
memory = { arena = "RAM", stack_bytes = 4096 }
The aliases first and second identify local instances. Both reuse the contract's
C API prefix and source, with separate state. This manual Hello example has a
fixed one-second timer and no config_schema; it does not accept an interval setting. Setting
enabled = false does not remove source files, and the referenced contract must
still exist and parse. Remove a channel too if no enabled port uses it.
Settings¶
config = { interval_ms = 250 } selects a value. It does not declare a type or a
new setting. Each key must exist in the service's config_schema, and each value
must satisfy its constraints. Unspecified values use declared defaults.
A nested table is equivalent:
[services.climate_sensor.config]
interval_ms = 250
Use either that table or the inline config map, not both. To apply a changed
value, redeploy that target. Editing a file does not change a running service.
Memory¶
Field inside memory |
Required / default | Meaning |
|---|---|---|
arena |
Required: "RAM" or "PSRAM" |
Where service arena allocations live. PSRAM requires a board profile that supports it. Use RAM for host. |
stack_bytes |
Default 4096; integer 1–16384 |
Requested worker stack budget in bytes; target admission can impose tighter requirements. |
Stack memory, service arena state, and channel payload storage are separate.
There is no unit arena_bytes field: package metadata determines arena size.
The deployment plan reports reservations, not measured runtime heap usage.
Enrollment selects 4096-byte worker stacks for ESP32, 1536 bytes for ESP8266,
and 1024 bytes for MKR. The host starter uses 4096 bytes. These are budgets for
this example, not universal defaults for every service. Keep them initially and
inspect stack headroom when changing behavior. MKR and ESP8266 have no supported
PSRAM pool; their current bases use separate fixed application and management
pools. See resource accounting.
Physical identity and board¶
[unit].device_id is exactly 12 lowercase hexadecimal digits. It must match the
unit filename and the board selected for deployment. Use the ID written by
enrollment, not a name such as ESP32 or a fabricated placeholder.
The generated [board] is target-specific:
| Field | Meaning |
|---|---|
profile |
Required hardware profile. Keep the detected profile unless deliberately correcting verified hardware information. |
flash_mb |
ESP32 only: 4 or 8; enrollment writes the detected capacity. The low-level parser default is 8, so do not remove a detected 4 MiB value. |
chip |
Optional ESP32 capability check; when supplied, must agree with the selected known profile. |
recovery_button |
Optional named input GPIO resource; "" means none. ESP8266 and MKR profiles support no configured recovery button. |
gpio |
Advanced named board resources; normally derived from service pins. |
Supported starter profiles include esp32_d0wd_v3_no_controls,
adafruit_feather_esp32_v2, esp8266_4mb, and arduino_mkr_wifi_1010.
These profiles do not share all options. See the board catalog
and use the definition produced by enrollment.
For an explicit [board.gpio.NAME], pin and direction (input/output) are
required. ESP32 also accepts pull (none by default, or up/down only when
supported). ESP8266/MKR accept only pin and direction. Do not define a resource
here and also generate it under the same name through a service's pins map.
Physical pin assignments¶
If a service declares [service.pins.led] with type = "output", add a map under
that service's unit table, using a pin you have checked against the actual board:
# Fragment inside [services.blink]; GPIO2 is only an illustration.
pins = { led = 2 }
The map must assign exactly the declared roles. Direction, pull requirements and
fixed_pin constraints come from the service contract. Composition rejects
unsupported or conflicting assignments; no host GPIO binding is available.
./micros pins -d HARDWARE_ID --all
./micros compose -d HARDWARE_ID
Channels¶
Every enabled service port needs a channel with the same name:
[channels.reading]
# depth = 4
depth is the only authored field here. Omit it to use the greatest minimum
requested by connected ports (each defaults to 1). Explicit depth must be 1–8 and
at least that minimum. latest policy always requires depth 1.
Interface identity, protobuf schema, policy and payload capacity come from the service ports' shared interfaces. Numeric keys are generated in sorted channel name order; they are not stable addresses to hard-code in application code. Channels connect services within this unit. See interface reference.
Command exposure¶
Inside the selected service table:
expose_commands = ["read", "increment", "reset"]
Every name must be declared and implemented by that service. Omit the field or
use [] for no remote access. Exposure adds a shared transport bridge and two
channels per exposed service; account for the transport budget.
The climate starter has no commands; the counter example provides these three.
Advanced project and server fields¶
Under [project], name defaults to the file stem. required_sdkconfig is a map
of ESP-IDF configuration names to required string values (for example "y");
disabled_sdkconfig is a list of names required to be disabled. Application
builds check these against the paired base's sdkconfig. They do not change the
base or act as runtime service settings.
[management.server] accepts only url, an HTTPS origin such as
https://console.example.com (no path other than /, query, fragment, or
username; fewer than 160 characters). It is an advanced base-preparation setting;
normal enrollment supplies the server. Credentials and tokens never belong here.
Validation and deployment¶
./micros compose -d HARDWARE_ID
./micros deploy -d HARDWARE_ID --dry-run
./micros deploy -d HARDWARE_ID
Composition is offline; the latter two commands use the selected device. To run
locally use ./micros host deploy. For host packages, bump the service version
when changing installed code or settings: installed name/version pairs are
immutable. Board releases are identified by content hashes, not version text alone.