Skip to content

service.toml reference

Location: services/NAME/service.toml. This file describes reusable behavior: its build inputs, settings, resource requirements, ports and commands. Select it from a unit file to run an instance.

The starter contains three complete hook-based contracts. Add declarations only for resources your implementation uses; pins, ports and commands also need their corresponding implementation and unit configuration.

Working climate sensor definition

This is services/climate-sensor/service.toml from micros new:

[service]
name = "example.climate-sensor"
version = "1.0.0"
timers = ["sample"]
[service.build]
api = "climate"
lifecycle = "hooks"
sources = ["src/service.c", "src/simulated_sensor.c"]
descriptor = "micros_descriptor"

[service.config_schema.interval_ms]
type = "uint32"
default = 1000
min = 100
max = 60000

[service.publications.climate_reading]
interface = "../../protos/climate/interface.toml"

The timer calls the simulated driver; the publication sends its reading to the fan controller and status display. See the starter flow.

Service identity

Key under [service] Required / default Meaning
name Required string Deployed service name, such as example.climate-sensor. A unit can override the name for a separate instance.
version Required string Three numeric components, such as 1.0.0.
timers Optional; none Unique C identifier names, such as ["greeting", "sample"].
types Optional; none Inline named type tables, or a path to a service-local types file. Choose one form.

All sections below belong to [service]. Unknown keys fail validation. For C-facing names—API prefix, setting, timer, pin role, port, and command—use lowercase names such as sample_interval, beginning with a letter. C keywords are not permitted. Named types use UpperCamelCase, such as ReadingCount.

Build inputs

Key under [service.build] Required / default Meaning
api Required C identifier Prefix of generated headers, context types, and hooks. Keep stable when changing an instance alias.
sources Required nonempty list Source paths relative to the service source directory. C and C++ files are supported.
lifecycle Default "manual"; starter explicitly uses "hooks" hooks generates lifecycle plumbing and resource cleanup. manual requires your ABI lifecycle implementation.
descriptor Default "micros_descriptor" Exported C descriptor symbol. Keep the starter value for generated hooks.
defines Default [] Compiler definitions without -D, e.g. ["VERBOSE=1"].

To add a file or definition, edit the existing build table:

[service.build]
api = "climate"
lifecycle = "hooks"
sources = ["src/service.c", "src/simulated_sensor.c", "src/format.c"]
defines = ["VERBOSE=1"]
descriptor = "micros_descriptor"

Create src/format.c before building. See Service API for implementation rules; a declaration alone does not implement a service.

Startup settings

Each [service.config_schema.NAME] defines one setting. A unit selects its value through config = { NAME = VALUE }. Values are fixed for that service instance's lifetime; a running command's mutable state is a separate concept.

Field Required / default Accepted values
type Required "uint32", "enum", or a named type resolving to one of these
default Required Integer within bounds, or an enum choice string
min / max Optional for inline uint32 Inclusive bounds; omitted ends default to 0 / 4294967295
choices Required for inline enum 1–32 unique C identifier strings; count is reserved

Partial additions to a service:

[service.config_schema.mode]
type = "enum"
choices = ["quiet", "normal", "verbose"]
default = "normal"

[service.config_schema.initial_count]
type = "Count"
default = 0

Count must be declared as below. Named references reuse their constraints; do not add new min, max, or choices beside a named type. Enum order defines its numeric identity. Startup settings do not accept signed integers, booleans, or strings. Those kinds are available to commands.

Reusable types

This optional table demonstrates every supported type kind:

[service.types]
Count = { type = "uint32", min = 0, max = 1000 }
Offset = { type = "sint32", min = -100, max = 100 }
Enabled = { type = "bool" }
Label = { type = "string", max_bytes = 32 }
Mode = { type = "enum", choices = ["quiet", "normal", "verbose"] }
Kind Constraints Usable in
uint32 Optional inclusive min / max, within 0…4294967295 Settings and command fields
sint32 Optional inclusive min / max, within −2147483648…2147483647 Command fields
bool No extra fields Command fields
string Required max_bytes, 1–64 UTF-8 bytes; values cannot contain NUL Command fields
enum Required choices, 1–32 unique names Settings and command fields

At most 32 named types may be declared. Each named type defines a concrete kind; chaining one named type to another is not supported.

For a larger library, replace the inline types with types = "types.toml" under [service], before the next table header. In services/NAME/types.toml, use [types] instead of [service.types]. The referenced file must remain inside this service directory. Types in other services are independent; shared messages use interfaces.

Pins and timers

Pin roles are optional. This is a declaration fragment, not assumed board wiring:

[service.pins.led]
type = "output"
# fixed_pin = 2

[service.pins.button]
type = "input"
Pin field Required / default Meaning
type Required input, pullup, pulldown, output, or analog
fixed_pin Optional; unrestricted by the service Driver constraint requiring that exact pin; currently 0–39. It is not a default assignment.

The unit must assign each role. Pulls need verified external bias or supported runtime configuration; the current ESP32 runtime does not configure internal pulls. Analog input requires an available ADC1 mapping. Host units cannot use GPIO. Run ./micros pins -d HARDWARE_ID --all before choosing physical pins.

Declare named timers with timers = ["greeting"] under [service]. This reserves a timer handle; C code chooses delay, period and callback using the generated helper. Hook cleanup closes declared timers. Do not add timer intervals to TOML unless they are actual settings that your code reads.

Publications and subscriptions

[service.publications.reading]
interface = "../../protos/reading/interface.toml"
min_depth = 1

A consumer declares [service.subscriptions.reading] with the same interface. Port names must be unique across publications and subscriptions within a service.

Port field Required / default Meaning
interface Required Path to a shared interface.toml, relative to this service contract
min_depth Default 1 Minimum queue depth, 1–8; latest delivery requires 1

Policy, message schema, payload size and optional coordination kind belong in the interface, not in the port. The unit needs a same-name [channels.reading]. Use the complete connection example to create all files and implement the producer/subscriber behavior.

Commands

This optional command uses the Count type defined above:

[service.commands.set_count]
description = "Set the counter value"
args = { value = "Count" }
returns = { value = "Count" }
Command field Required / default Meaning
description Default: command name with underscores replaced by spaces Help text, 1–80 characters
args Default {} Named input fields; at most 8, all required when called
returns Default {} Named output fields; at most 8

Field values may be a built-in type string ("bool"), a named type ("Count"), or an inline type table ({ type = "uint32", min = 0, max = 10 }). Bounded strings and enums need a named or inline definition. Empty arguments/results are valid. At most 8 commands can be declared; names and field names are at most 48 characters. Transport-reserved names such as request_id, result, and command are rejected.

Implement the generated command hook and select remote access in the unit's expose_commands. Declaring a command does not expose it automatically. See Service commands for the transport budget and invocation workflow.

Advanced: explicit capabilities

Most services should omit this table: pins, timers and ports already declare their resource requirements. For a manual ABI service with no named timers:

[service.capabilities]
timers = 1
gpio = []

timers is an integer count from 0–8; do not combine it with nonempty named timers. gpio, when present, must list every declared pin role exactly once. Its default is derived from the pin declarations. Handle counts are always derived; a handles option is not accepted. This table does not open resources or implement cleanup for a manual service.

Validate

./micros generate --service services/climate-sensor
./micros scaffold-service services/climate-sensor --preview
./micros compose -d host

Use the first command after declaration changes, the second to inspect required hook changes, and composition to check the selected unit's settings and bindings. See the configuration index for limits and troubleshooting.