Язык FSMC
Описывайте поведение устройства как конечный автомат
Справочник синтаксиса, типов, состояний, ожиданий и аппаратных входов/выходов.
Открыть справочник →Центр поддержки
Начните с языка FSMC, подготовьте firmware для своей платы или безопасно посмотрите интерфейс IDE в изолированной песочнице.
Описание языка FSM
FSMC — декларативный язык для описания конечных автоматов, исполняемых в fsm32. Он помогает выразить ожидаемые события, безопасные переходы и реакцию на таймауты.
Программа описывает начальное состояние, условия перехода и действия на выходах. Логика выполняется циклически: система фиксирует входы, исполняет правила и обновляет выходы.
codename HomeLight;
#include <ESP32-C3-MINI.hw>
fsm LightControl : Button {
initial state Idle : Button {
if (Button == true) {
STATE(Active);
}
}
}
Ниже — основной синтаксис FSMC. Квадратные скобки означают необязательную часть, фигурные — повторение одного или нескольких элементов по правилам конструкции.
program ::= codename_decl include_directive*
common_decl*
in_services_block?
unused_block?
init_block?
(fsm_decl | stateless_decl)+
codename_decl ::= "codename" identifier ";"
include_directive ::= "#include" "<" include_path ">"
fsm_decl ::= [ "unsafe" ] "fsm" identifier [ ":" trigger_list ] "{"
{ on_overflow_block | out_services_block | state_decl }
"}"
state_decl ::= [ "initial" ] "state" identifier [ ":" watch_list ] "{"
{ statement }
"}"
Идентификатор начинается с буквы; далее допускаются буквы, цифры, _ и .. Однострочный комментарий начинается с //.
bool, int8, uint8, int16, uint16, int32, uint32, float, bit.=, ==, !=, <, >, <=, >=, !, &&, ||, +, -, *, /, %, <<, >>, &, |, ^, ~, =>.#include <File.hw> подключает аппаратный каталог; #hardware_profile Name задаётся только внутри .hw и должен совпадать с идентификатором профиля в firmware контроллера.Все конструкции ниже реализованы в текущем профиле языка. Нажмите на название конструкции, чтобы перейти к отдельной теме с синтаксисом и многострочным примером.
| Конструкция | Синтаксис | Описание |
|---|---|---|
codename | codename identifier; | Задаёт имя программы. |
#include | #include <File.hw> | Подключает аппаратный каталог .hw. |
#hardware_profile | #hardware_profile Name | Задаёт имя профиля в .hw; оно должно совпадать с профилем firmware. |
in_services, in | in_services { in type Name; } | Объявляет программные входы узла. |
out_services, out | out_services { type Name; } | В .fsmc объявляет программные выходы; в .hw out задаёт направление HW-сервиса. |
common | common type Name; | Создаёт общую переменную для автоматов программы. |
unused | unused { Name; } | Скрывает HW-сервис из Composer, но сохраняет его VM-слот. |
init | init { Name = value; } | Задаёт начальные присваивания до запуска автоматов. |
| Конструкция | Синтаксис | Описание |
|---|---|---|
fsm | fsm Name [: triggers] { ... } | Объявляет конечный автомат с состояниями. |
stateless | stateless Name [: triggers] { ... } | Объявляет реактивный блок без состояний и ожиданий. |
initial state, state | [initial] state Name [: watch] { ... } | Описывает начальный или обычный режим; watch перезапускает тело при изменении сигнала. |
STATE | STATE(Name); | Переходит в состояние и прерывает текущий scan. |
STABILIZE | STABILIZE; | Прерывает текущий scan без смены состояния и локально фиксирует результат. |
any, all | fsm Name : any { ... } | Эквивалентные указатели всех in-сервисов в trigger/watch-списке. |
unsafe | unsafe fsm Name { ... } | Отключает проверки overflow/div0 и narrowing для конкретного блока. |
ON_Overflow | ON_Overflow { statements } | Обрабатывает overflow и деление либо остаток от деления на ноль. |
| Конструкция | Синтаксис | Описание |
|---|---|---|
if, else | if (condition) { ... } [else { ... }] | Выполняет ветвление по булеву условию. |
while, do | while (condition) { ... } | Повторяет блок, пока условие истинно; do гарантирует первый проход. |
for | for (init; condition; update) { ... } | Задаёт инициализацию, условие и обновление цикла. |
break, continue | break; / continue; | Завершает цикл либо начинает его следующую итерацию. |
await | await(Signal => condition|any); | Приостанавливает FSM до изменения сигнала либо выполнения условия. |
waitfor | waitfor(ms, Signal => condition|any, State); | Ожидает условие с таймаутом; по таймауту переходит в указанный state. |
sleep | sleep(milliseconds); | Приостанавливает FSM на интервал, квантуемый VM tick в 100 мс. |
true, false | Name = true; | Булевы литералы. |
| Конструкция | Синтаксис | Описание |
|---|---|---|
= | identifier = expression; | Присваивает значение сервису или переменной. |
== != < > <= >= | left operator right | Сравнивает числовые выражения и возвращает bit. |
! && || | !condition, A && B | Выполняет логическое НЕ, И и ИЛИ. |
+ - * / % | A + B, A / B | Выполняет арифметические операции; / возвращает float, % — только для целых. |
<< >> & | ^ ~ | A << 1, A | B | Сдвиги и побитовые операции для unsigned-типов. |
( ) | (numeric expression) | Группирует числовое выражение и меняет приоритет. |
CEIL | CEIL(number) | Округляет значение вверх до целого. |
FLOOR | FLOOR(number) | Округляет значение вниз до целого. |
ROUND | ROUND(number) | Округляет значение до ближайшего целого. |
Каждая тема раскрывает конструкцию из каталога: имя, синтаксис, назначение и минимальный рабочий контекст.
codename и #includeСинтаксис: codename identifier;, #include <File.hw>.
Имя открывает программу, а директива подключает аппаратный каталог.
codename HomeLight;
#include <ESP32-C3-MINI.hw>
fsm Control { ... }#hardware_profile и unusedСинтаксис: #hardware_profile Name, unused { Name; }.
Первое допустимо только в .hw и обязано совпадать с идентификатором профиля в firmware. Второе скрывает HW-сервис из System Composer, но не удаляет выделенный ему VM-слот.
#hardware_profile ESP32-C3-MINI
out bool StatusLed;
in bool BootButton;
// В .fsmc:
unused { StatusLed; }in_services, out_services, common, initСинтаксис: блоки сервисов, common type Name;, init { assignment; }.
Объявляют программные сигналы, общие данные и начальные значения. init выполняется один раз при запуске; common можно использовать в trigger/watch и ожиданиях.
common uint16 Attempts;
in_services { in bool Command; }
out_services { bool IsActive; }
init {
Attempts = 0;
IsActive = false;
}fsmСинтаксис: fsm Name [: trigger_list] { state... }.
Создаёт автомат с набором явно описанных состояний.
fsm LightControl : Button {
initial state Idle {
if (Button) { STATE(Active); }
}
}statelessСинтаксис: stateless Name [: trigger_list] { statements }.
Подходит для реактивных правил без состояний и ожиданий.
stateless Mirror : any {
IsActive = Command;
}any и allСинтаксис: fsm Name : any { ... } или state Name : Signal, Shared { ... }.
any и all эквивалентны: каждый означает маску всех in-сервисов и должен быть единственным элементом списка. Именованный список допускает конкретные in- и common-сервисы. У fsm это trigger, у state — watch; отсутствие списка запускает блок в каждом проходе стабилизации.
fsm Monitor : any {
initial state Observe {
IsActive = Command;
}
}unsafeСинтаксис: unsafe fsm Name { ... }.
Отключает checked-ветки переполнения, деления на ноль и compile-time narrowing только для следующего fsm или stateless-блока.
unsafe fsm FastPath {
initial state Run {
Counter = Counter + 1;
}
}ON_OverflowСинтаксис: ON_Overflow { statements }.
Задаёт единственный обработчик в теле fsm или stateless для арифметического overflow и деления либо остатка от деления на ноль. Без него VM фиксирует FAULT ArithmeticOverflow.
fsm Guard {
ON_Overflow {
Fault = true;
STATE(Error);
}
}initial state и stateСинтаксис: [initial] state Name [: watch_list] { ... }.
initial выбирает точку старта; обычный state описывает режим. Изменение сигнала из watch-списка перезапускает тело текущего state.
initial state Idle : Button {
Lamp = false;
}
state Active : Button {
Lamp = true;
}STATEСинтаксис: STATE(StateName);.
Переходит к названному состоянию и прерывает текущий scan: код после команды не выполняется. Целевой state может начать выполнение в том же цикле VM.
if (Button == true) {
STATE(Active);
}STABILIZEСинтаксис: STABILIZE;.
Прерывает текущий scan state без перехода в другое состояние и выполняет локальную фиксацию результата. Код после команды в этом scan не выполняется; повторный запуск зависит от масок активации.
Relay = Command;
STABILIZE;
Ready = Relay;if и elseСинтаксис: if (condition) { ... } else { ... }.
Выбирает один из блоков по булеву условию.
if (Pressure >= Minimum) {
Pump = true;
} else {
Pump = false;
}while и do ... whileСинтаксис: while (condition) { ... }, do { ... } while (condition);.
do выполняет тело хотя бы один раз.
do {
Count = Count + 1;
} while (Count < 3);forСинтаксис: for (init; condition; update) { ... }.
Собирает счётчик, условие и изменение счётчика в одном заголовке.
for (Index = 0; Index < 3; Index = Index + 1) {
Total = Total + Index;
}break и continueСинтаксис: break;, continue;.
Управляют выполнением текущего цикла.
while (Count < Limit) {
if (Stop) { break; }
Count = Count + 1;
if (!Enabled) { continue; }
Total = Total + Count;
}awaitСинтаксис: await(Signal => condition|any);.
Приостанавливает FSM в режиме Awaited. Signal — in- или common-сервис; => any ждёт его изменения, а выражение — выполнения условия.
await(Button => Button == true);
Lamp = true;
STATE(Active);waitforСинтаксис: waitfor(timeout, Signal => condition|any, TimeoutState);.
Добавляет ограниченное по времени ожидание. Третий аргумент — имя state: при таймауте VM автоматически выполняет STATE(TimeoutState), а не записывает флаг в сервис.
waitfor(5000, Pressure => Pressure == true, Error);
Pump = true;
state Error {
Alarm = true;
}sleepСинтаксис: sleep(milliseconds);.
Приостанавливает FSM на заданный интервал; длительность квантуется VM tick в 100 мс (округление вверх).
Lamp = true;
sleep(1000);
Lamp = false;true и falseСинтаксис: булевы литералы в выражениях и присваиваниях.
Задают логическое значение.
Enabled = true;
if (Enabled == false) {
STATE(Idle);
}=Синтаксис: identifier = expression;.
Присваивает вычисленный результат сервису либо переменной.
Setpoint = Target + Offset;
Ready = Pressure >= Minimum;Синтаксис: == != < > <= >=.
Сравнивают числовые операнды и возвращают bit, который можно использовать в условии. Для bool/bit используйте == true или == false.
if (Temperature >= Limit) {
Alarm = true;
}
if (Mode != PreviousMode) {
Changed = true;
}Синтаксис: !condition, A && B, A || B.
Комбинируют булевы условия.
if (!Fault && Enabled) {
Pump = true;
}
if (Stop || Timeout) {
STATE(Error);
}Синтаксис: + - * / %.
Выполняют вычисления над числовыми значениями. Деление всегда возвращает float; остаток % допустим только для целых. В checked-блоке overflow и деление на ноль передаются в ON_Overflow.
Average = Sum / Count;
Remainder = Total % 10;
Scaled = Raw * Factor + Offset;Синтаксис: << >> & | ^ ~.
Работают только с беззнаковыми типами.
Mask = Mask << 1;
Flags = (Flags | 1) & 255;
Inverted = ~Flags;( ) в числовых выраженияхСинтаксис: (numeric_expression).
Меняют порядок вычислений.
Scaled = (Raw + Offset) * Factor;
// Без скобок: Raw + (Offset * Factor)CEILСинтаксис: CEIL(number).
Округляет число вверх.
Required = CEIL(Voltage);
if (Required > Limit) {
Alarm = true;
}FLOORСинтаксис: FLOOR(number).
Округляет число вниз.
LowerBound = FLOOR(Voltage);
Status = LowerBound >= Minimum;ROUNDСинтаксис: ROUND(number).
Округляет число до ближайшего целого.
DisplayValue = ROUND(Voltage);
Output = DisplayValue * Scale;codename#include <Profile.hw>in_services / out_servicesfsmstate задаёт режим работы. Переход выполняется через STATE, когда выполнено условие.
state Active : Button {
Lamp = true
if (Button == false) {
STATE(Idle);
}
}
Такой подход подходит для освещения, блокировок, сценариев насосов и других процессов, где допустимые режимы важнее набора несвязанных команд.
После имени fsm или state можно задать список сигналов через двоеточие. У fsm это trigger, у state — watch: изменение элемента списка активирует автомат или перезапускает состояние. В списке допустимы in- и common-сервисы; без списка блок запускается в каждом проходе стабилизации. any и all эквивалентны, означают все in-сервисы и не совмещаются с именованными элементами.
common uint16 Attempts;
fsm Guard : Button, Pressure {
ON_Overflow {
STATE(Error);
}
initial state Idle : Button {
if (Button == true) {
Attempts = Attempts + 1;
STATE(CheckPressure);
}
}
}
stateless подходит для простого реактивного преобразования сигналов. В нём недоступны STATE, STABILIZE, await, waitfor и sleep. Используйте unsafe только если осознанно отключаете для блока checked-ветки overflow/div0 и compile-time narrowing.
Условия и циклы используют круглые скобки для условия, а фигурные — для тела блока. break и continue применяются внутри циклов.
for (Index = 0; Index < 3; Index = Index + 1) {
if (!Enabled) {
break;
}
if (Index == 1) {
continue;
}
Total = Total + Index;
}
Циклы выполняются в рамках логики автомата; не используйте их как замену ожиданию внешнего события. Для событий и задержек применяйте await, waitfor и sleep.
await(Signal => condition|any)in/common-сервиса.waitfor(timeout, Signal => condition|any, TimeoutState)sleep(milliseconds)ON_Overflowwaitfor(5000, Pressure => Pressure == true, Error);
Pump = true;
state Error {
Alarm = true;
}
Арифметика, сравнения и логические операторы образуют выражения в присваиваниях и условиях. Скобки меняют порядок числовых вычислений. Побитовые операции &, |, ^, ~ и сдвиги <<, >> допустимы только для беззнаковых типов.
Scaled = (Raw + Offset) * Scale;
Flags = (Flags | 1) & 255;
if ((Pressure >= Minimum) && !Fault) {
Ready = true;
}
Приоритет: скобки и унарные операции → умножение/деление/остаток → сдвиги → сложение/вычитание → побитовые операции → сравнения → && → ||.
Встроенные функции преобразования чисел принимают числовой фактор и возвращают целочисленный результат. Они используются, когда данные с плавающей точкой нужно привести к дискретному значению.
WholeUp = CEIL(Reading);
WholeDown = FLOOR(Reading);
Nearest = ROUND(Reading);
CEIL(x)x вверх.FLOOR(x)x вниз.ROUND(x)x до ближайшего целого.Файл .hw описывает доступные возможности конкретной платы. Он отделяет прикладную логику от распиновки и делает один сценарий переносимым между согласованными профилями.
Важно: FSMC управляет логикой устройства; пакет .fsmb не является прошивкой ESP-IDF. Firmware и пакет логики обновляются разными процессами.
Для полного синтаксиса и всех конструкций используйте исходную спецификацию в репозитории. Перед работой с физическими платами соберите firmware для нужного профиля.