Skip to content

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.