Обновлены вспомогательные файлы и скрипты
This commit is contained in:
+15
-1
@@ -41,7 +41,6 @@ _CompileInfo/
|
|||||||
*.git_tmp/
|
*.git_tmp/
|
||||||
|
|
||||||
# ---- Boot-приложение ----
|
# ---- Boot-приложение ----
|
||||||
|
|
||||||
*.app
|
*.app
|
||||||
*.crc
|
*.crc
|
||||||
Boot Project/
|
Boot Project/
|
||||||
@@ -83,6 +82,21 @@ venv/
|
|||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
|
|
||||||
|
# ---- Прочее ----
|
||||||
|
**/old/*
|
||||||
|
tools/
|
||||||
|
libs/
|
||||||
|
/tmp/*
|
||||||
|
/.kilo/*
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
.pytest_cache/
|
||||||
|
.coverage/
|
||||||
|
.coverage.*
|
||||||
|
.coverage.xml
|
||||||
|
.coverage.json
|
||||||
|
.coverage.yaml
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# НЕ ИГНОРИРОВАТЬ БЕЗ ПРОВЕРКИ:
|
# НЕ ИГНОРИРОВАТЬ БЕЗ ПРОВЕРКИ:
|
||||||
# *.xml
|
# *.xml
|
||||||
|
|||||||
+164
@@ -0,0 +1,164 @@
|
|||||||
|
# Проект разработки для ПЛК210 (ОВЕН)
|
||||||
|
|
||||||
|
## Описание проекта
|
||||||
|
|
||||||
|
- **ПЛК:** ОВЕН ПЛК210
|
||||||
|
- **Среда разработки:** CODESYS 3.5 SP17 Patch 3
|
||||||
|
- **Основной язык программирования:** ST (Structured Text / МЭК 61131-3)
|
||||||
|
|
||||||
|
## Правила работы ИИ-ассистента с этим репозиторием (Strict Rules)
|
||||||
|
|
||||||
|
1. **Бинарник — источник истины:** Файлы `.st` в `plc_src/` — это лишь зеркало. Никакие правки в `.st` не считаются примененными, пока пользователь не прогонит скрипт импорта в CODESYS.
|
||||||
|
2. **Паспорт файла неизменен:** Секция `(* ... *)` в начале `.st` файлов содержит служебные метаданные для Python-скриптов. **Категорически запрещено** удалять или изменять структуры этих заголовков.
|
||||||
|
3. **Запрет гадания API:** Использовать ТОЛЬКО те функциональные блоки, функции и типы из внешних библиотек, которые явно описаны в `libs/<ИмяБиблиотеки>_API.md`. Если блока нет — запросить сигнатуру у пользователя.
|
||||||
|
4. **Безопасность циклов:** Никогда не использовать неограниченные циклы `WHILE` или `REPEAT`. Вся циклическая логика должна опираться на естественный цикл задачи CODESYS (Main Task), иначе сработает **Watchdog** ПЛК.
|
||||||
|
5. **Атомарность:** При модификации файла вносить изменения минимально необходимыми диффами. Не переписывать весь блок без необходимости.
|
||||||
|
6. **Новые объекты — только с подтверждением:** Создание нового POU/GVL/DUT (объекта, которого раньше не было в проекте) — необратимое изменение структуры проекта при реальном импорте. Не создавай новый файл с новым объектом молча в рамках другой задачи — явно предупреди пользователя и дождись подтверждения.
|
||||||
|
7. **Не гадать по существующим символам проекта:** Если нужно сослаться на переменную/POU/тип, которые предположительно уже есть в проекте (не создаются заново) — проверь их в `plc_src/` (grep/поиск по имени), а не полагайся на память или правдоподобное совпадение.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Архитектура проекта и структуры данных
|
||||||
|
|
||||||
|
### Иерархия вызова программ (Main Task)
|
||||||
|
|
||||||
|
- `PLC_PRG`: Главный цикл управления. Содержит только вызов подпрограмм (POU).
|
||||||
|
|
||||||
|
### Сеть и протоколы
|
||||||
|
|
||||||
|
- **Modbus TCP Master и Slave:** Порты `Ethernet1 / Ethernet2`
|
||||||
|
- **Modbus RTU:** Интерфейсы `RS-485-1 / RS-485-2`
|
||||||
|
- Modbus не могут быть экспортированы в .st файл
|
||||||
|
|
||||||
|
### Глоссарий сокращений
|
||||||
|
|
||||||
|
<!-- Расшифровка предметных сокращений в именах POU/переменных, которые не покрываются Hungarian-нотацией типов ниже. -->
|
||||||
|
|
||||||
|
-
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Структура репозитория
|
||||||
|
|
||||||
|
```text
|
||||||
|
.
|
||||||
|
├── <Проект>.project # бинарный проект CODESYS — источник истины для компиляции
|
||||||
|
├── plc_src/ # текстовый экспорт POU/GVL/DUT — ИМЕННО ЭТИ ФАЙЛЫ читает и правит ИИ-ассистент
|
||||||
|
│ ├── <Устройство>/
|
||||||
|
│ │ ├── POU/
|
||||||
|
│ │ ├── GVL/
|
||||||
|
│ │ └── DUT/
|
||||||
|
│ └── _common/
|
||||||
|
├── tools/
|
||||||
|
│ ├── export_plc_src.py # скрипт экспорта .project -> plc_src (текст)
|
||||||
|
│ └── import_plc_src.py # скрипт импорта plc_src (текст) -> .project
|
||||||
|
├── libs/
|
||||||
|
│ └── <ИмяБиблиотеки>_API.md # справочник по FB/FUNCTION самописной библиотеки (файл может отсутствовать)
|
||||||
|
└── Algoritm.md # Основной алгоритм работы (файл может отсутствовать)
|
||||||
|
```
|
||||||
|
|
||||||
|
`plc_src/` генерируется скриптом `tools/export_plc_src.py` из `.project` через CODESYS Scripting API.
|
||||||
|
Это НЕ исходный код в привычном смысле — это зеркало текущего состояния `.project`, нужное для того, чтобы у git был человекочитаемый дифф, а у ИИ-ассистента — текстовые файлы для правки.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Рабочий цикл правки логики (важно!)
|
||||||
|
|
||||||
|
Перенос правок туда-обратно автоматизирован через пару скриптов, но применяется он **не мгновенно** — между правкой `.st`-файла и тем, что окажется в проекте, есть шаг, который делает пользователь руками в CODESYS. Порядок действий:
|
||||||
|
|
||||||
|
1. ИИ-ассистент правит `.st`-файл(ы) в `plc_src/...`. Заголовок-паспорт файла (блок `(* ... *)` в начале, с полями Project/Device/Path/Name/Type) **не трогать и не удалять** — по нему `import_plc_src.py` определяет, в какой POU/METHOD/GVL/DUT проекта записать текст.
|
||||||
|
2. Пользователь открывает проект в CODESYS и запускает `tools/import_plc_src.py`.
|
||||||
|
3. Компилирует и тестирует проект в CODESYS как обычно.
|
||||||
|
4. **Перед коммитом перезапускает `tools/export_plc_src.py`**, чтобы `plc_src/` снова точно соответствовал `.project` — иначе дифф в git будет показывать код, которого уже нет в бинарнике (или наоборот — не будет показывать то, что уже есть).
|
||||||
|
5. Коммитит `.project` и `plc_src/` вместе.
|
||||||
|
|
||||||
|
ИИ-ассистент: никогда не считай правку `.st`-файла уже применённой к `.project` — это только предложение изменения. Реальный эффект появится только после того, как пользователь прогонит `import_plc_src.py` (сам, руками) и подтвердит результат.
|
||||||
|
|
||||||
|
**ИИ-ассистент никогда не запускает `import_plc_src.py` (и команду CODESYS.exe, вызывающую его) самостоятельно — это исключительно ручной шаг пользователя.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Особенности формата .st файлов (важно при правке)
|
||||||
|
|
||||||
|
- Один `.st`-файл может содержать несколько блоков подряд: основной POU и дописанные METHOD/PROPERTY/ACTION (каждый со своим заголовком-паспортом). Если добавляешь новый METHOD/ACTION в существующий POU — дописывай его в конец того же файла со своим заголовком, а не создавай отдельный файл.
|
||||||
|
- Для ACTION в заголовке не бывает декларации, только implementation — так и должно быть.
|
||||||
|
- `END_PROGRAM` / `END_FUNCTION_BLOCK` / `END_METHOD` и т.п. в конце блока — оставляй, их пишет `export_plc_src.py`, а `import_plc_src.py` сам их убирает перед отправкой в API. Не убирай их сам и не убирай случайно при правке.
|
||||||
|
- Новый POU/GVL/DUT, которого раньше не было — можно создавать новым файлом с правильным заголовком (см. правило 6 выше про обязательное подтверждение); `import_plc_src.py` создаст объект в проекте автоматически (`get_or_create_*`). Но: сигнатуры создания через API в скрипте помечены как версионно-хрупкие — если создание упадёт с ошибкой, это ожидаемо, надо смотреть Tools → Scripting → API нужной версии.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Что нельзя трогать без согласования
|
||||||
|
|
||||||
|
<!-- Конкретные POU/цепи/переменные, критичные для безопасности — правки в них только после явного обсуждения, а не как побочный эффект другой задачи. -->
|
||||||
|
|
||||||
|
-
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Библиотека (внешний проект)
|
||||||
|
|
||||||
|
Есть самописная библиотека — отдельный CODESYS-проект, её исходники в этот репозиторий **не включаются**.
|
||||||
|
|
||||||
|
- Справочник по публичному API лежит в `libs/<ИмяБиблиотеки>_API.md`
|
||||||
|
- Там для каждого FB/FUNCTION: сигнатура, VAR_INPUT/VAR_OUTPUT/VAR_IN_OUT с типами, краткое описание
|
||||||
|
- **ИИ-ассистент: используй только то, что описано в `libs/<ИмяБиблиотеки>_API.md`.** Не придумывай сигнатуры блоков библиотеки, которых нет в справочнике — если нужного блока/параметра там нет, спроси, а не предполагай
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Соглашения по коду
|
||||||
|
|
||||||
|
### Именование элементов (Hungarian Notation / Prefix System)
|
||||||
|
|
||||||
|
**Физические входы/выходы**, привязанные к модулям, — в `VAR_GLOBAL` RAW_IO:
|
||||||
|
- `RDI_` — Raw Digital Input
|
||||||
|
- `RDO_` — Raw Digital Output
|
||||||
|
- `RAI_` — Raw Analog Input
|
||||||
|
- `RAO_` — Raw Analog Output
|
||||||
|
|
||||||
|
**Входы/выходы после присвоения и нормировки** — в `VAR_GLOBAL` IO:
|
||||||
|
- `DI_` — Digital Input
|
||||||
|
- `DO_` — Digital Output
|
||||||
|
- `AI_` — Analog Input
|
||||||
|
- `AO_` — Analog Output
|
||||||
|
|
||||||
|
**Типы POU:**
|
||||||
|
- `FB_` — Функциональный блок
|
||||||
|
- `FC_` — Функция
|
||||||
|
- `DUT_` — Пользовательский тип данных (Struct, Enum)
|
||||||
|
|
||||||
|
### Правила форматирования и стиля
|
||||||
|
|
||||||
|
1. **Язык:** код, имена переменных и блоков — на **английском языке**; комментарии и документация — на **русском**.
|
||||||
|
2. **Автоматы состояний (State Machines):** всегда использовать `CASE ... OF`, не заменять его цепочкой `IF ... ELSIF`, если по сути нужен автомат.
|
||||||
|
3. **Безопасность:**
|
||||||
|
- деление на ноль и выход за границы массива должны быть явно заблокированы проверками *до* операции.
|
||||||
|
4. **Таймеры:** использовать только стандартные `TON`, `TOF`, `TP` из библиотеки `Standard`.
|
||||||
|
5. **Циклы:** без неограниченных `WHILE`/`REPEAT` — см. правило 4 в разделе Strict Rules выше (иначе сработает Watchdog).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Особенности оборудования ПЛК210
|
||||||
|
|
||||||
|
- **Retain-память:** Энергонезависимые переменные объявлять строго в секции `VAR RETAIN` или `VAR PERSISTENT`. Использовать экономно.
|
||||||
|
- **Встроенные входы/выходы:** Использовать таргетные переменные из дерева `ПЛК210 -> Входы/Выходы`. Сырые переменные в RAW_IO, именно их привязку делать через `IO_Mapping`.
|
||||||
|
- **Флеш-память:** Избегать частой циклической записи файлов во внутреннюю память ПЛК (использовать буферизацию в RAM).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Полезные команды
|
||||||
|
|
||||||
|
Запуск экспорта из командной строки (без открытия GUI CODESYS):
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
"C:\Program Files (x86)\CODESYS 3.5.17.30\CODESYS\Common\CODESYS.exe" ^
|
||||||
|
--Profile="CODESYS V3.5 SP17 Patch 3" --runscript="tools/export_plc_src.py" ^
|
||||||
|
--project="<путь к .project>"
|
||||||
|
```
|
||||||
|
|
||||||
|
Запуск импорта из командной строки (без открытия GUI CODESYS):
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
"C:\Program Files (x86)\CODESYS 3.5.17.30\CODESYS\Common\CODESYS.exe" ^
|
||||||
|
--Profile="CODESYS V3.5 SP17 Patch 3" --runscript="tools/import_plc_src.py" ^
|
||||||
|
--project="<путь к .project>"
|
||||||
|
```
|
||||||
@@ -0,0 +1,668 @@
|
|||||||
|
# PLC_Library — API Reference (для использования LLM/ИИ)
|
||||||
|
|
||||||
|
> Формат: справочник интерфейсов (аналог API-документации). Для каждого POU указаны: тип, путь в дереве проекта, входы/выходы с типами, значениями по умолчанию и назначением, а также краткое описание поведения и особые примечания. Тела реализации (код) намеренно не включены — только то, что нужно, чтобы **вызвать** блок правильно.
|
||||||
|
>
|
||||||
|
> Язык: CoDeSys 3.5, Structured Text (ST).
|
||||||
|
> Скармливайте этот файл ИИ вместо исходников — этого достаточно, чтобы сгенерировать корректные вызовы блоков библиотеки.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Индекс
|
||||||
|
|
||||||
|
- [Filters — Фильтры](#filters--фильтры)
|
||||||
|
- [Fastwel — Модули ввода/вывода](#fastwel--модули-вводавывода)
|
||||||
|
- [Преобразование значений](#преобразование-значений)
|
||||||
|
- [Общие — Common](#общие--common)
|
||||||
|
- [Дискретные сигналы / кнопки / таймеры](#дискретные-сигналы--кнопки--таймеры)
|
||||||
|
- [Пороговые сигналы / гистерезис](#пороговые-сигналы--гистерезис)
|
||||||
|
- [Статус аналоговых сигналов](#статус-аналоговых-сигналов)
|
||||||
|
- [Диагностика связи](#диагностика-связи)
|
||||||
|
- [Регулирование](#регулирование)
|
||||||
|
- [Структуры данных (очереди/стеки)](#структуры-данных-очередистеки)
|
||||||
|
- [Иное](#иное)
|
||||||
|
- [Structs](#structs)
|
||||||
|
- [Общие правила / соглашения библиотеки](#общие-правила--соглашения-библиотеки)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Filters — Фильтры
|
||||||
|
`Path: Filters`
|
||||||
|
|
||||||
|
### ExpRunAverage — FUNCTION : REAL
|
||||||
|
Экспоненциальное скользящее среднее с постоянным коэффициентом.
|
||||||
|
|
||||||
|
```
|
||||||
|
FUNCTION ExpRunAverage : REAL
|
||||||
|
VAR_INPUT
|
||||||
|
In : REAL; // входное значение
|
||||||
|
Old : REAL; // предыдущее значение EMA (нужно хранить самому вызывающему)
|
||||||
|
K : REAL; // коэффициент сглаживания [0..1], чем больше — тем быстрее реакция
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
Формула: `Out := (1-K)*Old + K*In`. Без внутреннего состояния — вызывающий код сам хранит `Old` между циклами.
|
||||||
|
|
||||||
|
### ExpRunAverageAdaptive — FUNCTION : REAL
|
||||||
|
Как `ExpRunAverage`, но коэффициент переключается на увеличенный `K2`, если скачок входа больше `Delta` (быстрая реакция на резкие изменения).
|
||||||
|
|
||||||
|
```
|
||||||
|
FUNCTION ExpRunAverageAdaptive : REAL
|
||||||
|
VAR_INPUT
|
||||||
|
In : REAL;
|
||||||
|
Old : REAL;
|
||||||
|
K1 : REAL; // обычный коэффициент
|
||||||
|
Delta : REAL; // порог |In-Old| для переключения на K2
|
||||||
|
K2 : REAL; // ускоренный коэффициент (обычно K2 > K1)
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
|
||||||
|
### RunMiddleArifm — FUNCTION_BLOCK
|
||||||
|
Скользящее среднее арифметическое по кольцевому буферу (собственное состояние, хранит буфер сам).
|
||||||
|
|
||||||
|
```
|
||||||
|
FUNCTION_BLOCK RunMiddleArifm
|
||||||
|
VAR_INPUT
|
||||||
|
In : REAL; // входное значение
|
||||||
|
N : INT := 32; // размер окна усреднения, диапазон 0..32
|
||||||
|
RST : BOOL; // TRUE => переинициализация буфера текущим In
|
||||||
|
END_VAR
|
||||||
|
VAR_OUTPUT
|
||||||
|
Y : REAL; // результат — скользящее среднее
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
Особенность: при `RST` или на первом скане буфер заполняется значением `In`, а `Y := In`.
|
||||||
|
|
||||||
|
### MultipleClick — FUNCTION_BLOCK
|
||||||
|
Детектор серии кликов кнопки (одиночный/двойной/тройной).
|
||||||
|
|
||||||
|
```
|
||||||
|
FUNCTION_BLOCK MultipleClick
|
||||||
|
VAR_INPUT
|
||||||
|
In : BOOL; // сигнал кнопки
|
||||||
|
TimeToWait : TIME := T#500MS; // макс. пауза между нажатиями, после — фиксация результата
|
||||||
|
TimeRelease : TIME := T#0S; // время удержания результата на выходах
|
||||||
|
END_VAR
|
||||||
|
VAR_OUTPUT
|
||||||
|
Click1 : BOOL; // TRUE — зафиксирован 1 клик
|
||||||
|
Click2 : BOOL; // TRUE — зафиксировано 2 клика
|
||||||
|
Click3 : BOOL; // TRUE — зафиксировано 3 клика
|
||||||
|
Click : WORD; // число нажатий (0, если результат уже "отпущен")
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fastwel — Модули ввода/вывода
|
||||||
|
`Path: Fastwel`
|
||||||
|
Преобразование "сырых" значений АЦП/кодов модулей Fastwel в физические единицы и обратно.
|
||||||
|
|
||||||
|
### AIM721_AI — FUNCTION : REAL
|
||||||
|
```
|
||||||
|
FUNCTION AIM721_AI : REAL
|
||||||
|
VAR_INPUT
|
||||||
|
ADC : DINT; // код АЦП
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
АЦП → 0…20 мА (коэффициент модуля AIM721).
|
||||||
|
|
||||||
|
### AIM722_AI — FUNCTION : REAL
|
||||||
|
```
|
||||||
|
FUNCTION AIM722_AI : REAL
|
||||||
|
VAR_INPUT
|
||||||
|
ADC : DINT;
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
АЦП → 0…20 мА (коэффициент модуля AIM722, отличается от AIM721).
|
||||||
|
|
||||||
|
### AIM723_AI — FUNCTION : REAL
|
||||||
|
```
|
||||||
|
FUNCTION AIM723_AI : REAL
|
||||||
|
VAR_INPUT
|
||||||
|
ADC : DINT;
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
АЦП → 4…20 мА.
|
||||||
|
|
||||||
|
### AIM726_AI — FUNCTION : REAL
|
||||||
|
```
|
||||||
|
FUNCTION AIM726_AI : REAL
|
||||||
|
VAR_INPUT
|
||||||
|
ADC : DWORD; // код АЦП (DWORD!)
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
АЦП → 0…40 В.
|
||||||
|
|
||||||
|
### AIM727_AI — FUNCTION : REAL
|
||||||
|
```
|
||||||
|
FUNCTION AIM727_AI : REAL
|
||||||
|
VAR_INPUT
|
||||||
|
ADC : DINT; // код АЦП (DINT — отличие от AIM726)
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
АЦП → 0…40 В.
|
||||||
|
|
||||||
|
### AIM791_AI — FUNCTION : REAL
|
||||||
|
```
|
||||||
|
FUNCTION AIM791_AI : REAL
|
||||||
|
VAR_INPUT
|
||||||
|
ADC : WORD;
|
||||||
|
RANGE : BYTE := 0; // 0=0..5мА; 1=0..20мА; 2=4..20мА
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
Универсальный вход, диапазон выбирается `RANGE`. Значение по умолчанию `RANGE` вне 0/1/2 → возвращает `0.0`.
|
||||||
|
|
||||||
|
### AIM730_AO — FUNCTION : WORD
|
||||||
|
```
|
||||||
|
FUNCTION AIM730_AO : WORD
|
||||||
|
VAR_INPUT
|
||||||
|
DAC : REAL; // миллиамперы
|
||||||
|
RANGE : BYTE; // 0=0..20мА; 1=4..20мА
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
мА → код ЦАП. При `RANGE=1` и `DAC<4` принудительно возвращает код, соответствующий 4 мА.
|
||||||
|
|
||||||
|
### AIM731_AO — FUNCTION : WORD
|
||||||
|
```
|
||||||
|
FUNCTION AIM731_AO : WORD
|
||||||
|
VAR_INPUT
|
||||||
|
DAC : REAL; // вольты
|
||||||
|
RANGE : BYTE; // 0 = -10..+10В; 1 = 0..+10В
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
Вольты → код ЦАП модуля AIM731.
|
||||||
|
|
||||||
|
### DIM764_DI — FUNCTION : REAL
|
||||||
|
```
|
||||||
|
FUNCTION DIM764_DI : REAL
|
||||||
|
VAR_INPUT
|
||||||
|
IN : DWORD; // период/интервал от модуля, единицы АЦП
|
||||||
|
RANGE : BYTE; // 0=Period; 1=Interval
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
Возвращает частоту в Гц. При `IN=0` и `RANGE=0` — возвращает 0 (защита от деления на 0).
|
||||||
|
|
||||||
|
### AIM725_Status — FUNCTION_BLOCK
|
||||||
|
Формирование статуса аналогового канала с гистерезисом/задержками + диагностика по битам статуса модуля.
|
||||||
|
|
||||||
|
```
|
||||||
|
FUNCTION_BLOCK AIM725_Status
|
||||||
|
VAR_INPUT
|
||||||
|
Input : REAL; // контролируемое значение
|
||||||
|
fail_hi : REAL := 3.402823466E+38;
|
||||||
|
delta_fail_hi : REAL := 0;
|
||||||
|
time_fail_hi : TIME := T#0S;
|
||||||
|
warn_hi : REAL := 3.402823466E+38;
|
||||||
|
delta_warn_hi : REAL := 0;
|
||||||
|
time_warn_hi : TIME := T#0S;
|
||||||
|
warn_low : REAL := -3.402823466E+38;
|
||||||
|
delta_warn_low : REAL := 0;
|
||||||
|
time_warn_low : TIME := T#0S;
|
||||||
|
fail_low : REAL := -3.402823466E+38;
|
||||||
|
delta_fail_low : REAL := 0;
|
||||||
|
time_fail_low : TIME := T#0S;
|
||||||
|
time_no_signal : TIME := T#0S;
|
||||||
|
Modul_status : BYTE; // байт диагностики модуля АЦП (побитовые флаги ошибок канала)
|
||||||
|
Chanel_num : BYTE; // номер канала модуля (1 или 2) — выбирает, какие биты Modul_status проверять
|
||||||
|
END_VAR
|
||||||
|
VAR_OUTPUT
|
||||||
|
Status : AnalogValueState; // итоговый статус (см. раздел Structs)
|
||||||
|
T_mod : INT; // Input, приведённое к INT; -500 при no_signal
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
Приоритет статусов: `no_signal > fail_hi > fail_low > warn_hi > warn_low > normal`.
|
||||||
|
`no_signal` срабатывает если: `Input <= -200`, либо соответствующие биты `Modul_status` (зависящие от `Chanel_num`), либо биты 2/3 (общие ошибки модуля).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Преобразование значений
|
||||||
|
`Path: Преобразование значений`
|
||||||
|
|
||||||
|
### DW_TO_REAL — FUNCTION : REAL
|
||||||
|
```
|
||||||
|
FUNCTION DW_TO_REAL : REAL
|
||||||
|
VAR_INPUT
|
||||||
|
X : DWORD;
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
**Побитовая** реинтерпретация DWORD как REAL через указатель (`ADR`) — НЕ конвертация значения. Платформозависимо.
|
||||||
|
|
||||||
|
### REAL_TO_DW — FUNCTION : DWORD
|
||||||
|
```
|
||||||
|
FUNCTION REAL_TO_DW : DWORD
|
||||||
|
VAR_INPUT
|
||||||
|
X : REAL;
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
Обратная операция к `DW_TO_REAL`. Побитовая реинтерпретация.
|
||||||
|
|
||||||
|
### DWORD_OF_2WORD — FUNCTION : DWORD
|
||||||
|
```
|
||||||
|
FUNCTION DWORD_OF_2WORD : DWORD
|
||||||
|
VAR_INPUT
|
||||||
|
W1 : WORD; // старшее слово, биты 16..31 результата
|
||||||
|
W0 : WORD; // младшее слово, биты 0..15 результата
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
|
||||||
|
### WORD_OF_DWORD — FUNCTION : WORD
|
||||||
|
```
|
||||||
|
FUNCTION WORD_OF_DWORD : WORD
|
||||||
|
VAR_INPUT
|
||||||
|
in : DWORD; // исходное значение
|
||||||
|
N : BYTE; // 0 = младшее слово (биты 0..15), 1 = старшее (биты 16..31)
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
|
||||||
|
### Norm_real — FUNCTION : REAL
|
||||||
|
Линейная нормировка (масштабирование по двум опорным точкам).
|
||||||
|
```
|
||||||
|
FUNCTION Norm_real : REAL
|
||||||
|
VAR_INPUT
|
||||||
|
X : REAL; // входное (ненормированное) значение
|
||||||
|
X1 : REAL; // точка 1, вход
|
||||||
|
Y1 : REAL; // точка 1, выход
|
||||||
|
X2 : REAL; // точка 2, вход
|
||||||
|
Y2 : REAL; // точка 2, выход
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
Если `X2=X1` — возвращает `Y1` (защита от деления на 0).
|
||||||
|
|
||||||
|
### NTC_termistor_T — FUNCTION : REAL
|
||||||
|
Перевод сопротивления NTC-термистора в температуру (уравнение Beta).
|
||||||
|
```
|
||||||
|
FUNCTION NTC_termistor_T : REAL
|
||||||
|
VAR_INPUT
|
||||||
|
Res : REAL; // измеренное сопротивление
|
||||||
|
R0 : REAL; // сопротивление при температуре T0
|
||||||
|
T0 : REAL; // опорная температура, °C
|
||||||
|
b : REAL; // коэффициент Beta термистора
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
Возвращает температуру в °C. При `Res<=0`, `R0<=0` или `b=0` возвращает `0.0`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Общие — Common
|
||||||
|
`Path: Общие`
|
||||||
|
|
||||||
|
### Дискретные сигналы / кнопки / таймеры
|
||||||
|
|
||||||
|
#### Button_acceleration — FUNCTION_BLOCK
|
||||||
|
Ускоренное изменение значения при удержании кнопки (для регулировки уставок и т.п.).
|
||||||
|
```
|
||||||
|
FUNCTION_BLOCK Button_acceleration
|
||||||
|
VAR_INPUT
|
||||||
|
Button : BOOL;
|
||||||
|
D_0 : REAL := 0; // приращение при одиночном (коротком) нажатии
|
||||||
|
T_W_1 : TIME := T#0S; // время до начала "короткого удержания"
|
||||||
|
T_D_1 : TIME := T#200MS; // шаг приращения при коротком удержании
|
||||||
|
D_1 : REAL := 0; // величина приращения на шаге T_D_1
|
||||||
|
T_W_2 : TIME := T#0S; // время до начала "длинного удержания"
|
||||||
|
T_D_2 : TIME := T#200MS; // шаг приращения при длинном удержании
|
||||||
|
D_2 : REAL := 0; // величина приращения на шаге T_D_2
|
||||||
|
Zeroes : BOOL := FALSE; // если TRUE — обнулять Out, когда |Out| < |D_0|
|
||||||
|
END_VAR
|
||||||
|
VAR_IN_OUT
|
||||||
|
Out : REAL; // изменяемое значение (передаётся и изменяется по ссылке!)
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
⚠️ Нет `VAR_OUTPUT` — результат только через `VAR_IN_OUT Out`.
|
||||||
|
|
||||||
|
#### Pulse_Switch — FUNCTION_BLOCK
|
||||||
|
```
|
||||||
|
FUNCTION_BLOCK Pulse_Switch
|
||||||
|
VAR_INPUT
|
||||||
|
Switch_ctrl : BOOL;
|
||||||
|
END_VAR
|
||||||
|
VAR_OUTPUT
|
||||||
|
Switch_on : BOOL; // импульс 500мс при фронте 0->1
|
||||||
|
Switch_off : BOOL; // импульс 500мс при фронте 1->0
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
|
||||||
|
#### TONOF — FUNCTION_BLOCK
|
||||||
|
Комбинированная задержка включения (TON) + выключения (TOF) для одного сигнала.
|
||||||
|
```
|
||||||
|
FUNCTION_BLOCK TONOF
|
||||||
|
VAR_INPUT
|
||||||
|
IN : BOOL;
|
||||||
|
T_ON : TIME := T#0S; // задержка появления TRUE на Q
|
||||||
|
T_OFF : TIME := T#0S; // задержка снятия TRUE с Q
|
||||||
|
END_VAR
|
||||||
|
VAR_OUTPUT
|
||||||
|
Q : BOOL;
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
|
||||||
|
#### TOF_RST — FUNCTION_BLOCK
|
||||||
|
Таймер выключения (TOF) с принудительным сбросом.
|
||||||
|
```
|
||||||
|
FUNCTION_BLOCK TOF_RST
|
||||||
|
VAR_INPUT
|
||||||
|
IN : BOOL;
|
||||||
|
PT : TIME; // задержка выключения
|
||||||
|
RST : BOOL; // немедленный сброс Q и таймера, приоритет над IN
|
||||||
|
END_VAR
|
||||||
|
VAR_OUTPUT
|
||||||
|
Q : BOOL;
|
||||||
|
ET : TIME; // текущее время таймера
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
|
||||||
|
#### TP_RST — FUNCTION_BLOCK
|
||||||
|
Одиночный импульс (TP) с принудительным сбросом.
|
||||||
|
```
|
||||||
|
FUNCTION_BLOCK TP_RST
|
||||||
|
VAR_INPUT
|
||||||
|
IN : BOOL; // фронт 0->1 запускает импульс (повторный фронт во время импульса игнорируется)
|
||||||
|
PT : TIME; // длительность импульса
|
||||||
|
RST : BOOL; // немедленно прерывает импульс
|
||||||
|
END_VAR
|
||||||
|
VAR_OUTPUT
|
||||||
|
Q : BOOL;
|
||||||
|
ET : TIME;
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
|
||||||
|
#### BLINK_LMP — FUNCTION_BLOCK
|
||||||
|
Управление лампой по коду состояния.
|
||||||
|
```
|
||||||
|
FUNCTION_BLOCK BLINK_LMP
|
||||||
|
VAR_INPUT
|
||||||
|
State : WORD := 0; // 0=выкл; 1=вкл; 2/3/4=мигание с частотой 1/2/3
|
||||||
|
Inp_BL_1 : BOOL := FALSE; // внешний "пульсар" (меандр) частоты 1
|
||||||
|
Inp_BL_2 : BOOL := FALSE; // частоты 2
|
||||||
|
Inp_BL_3 : BOOL := FALSE; // частоты 3
|
||||||
|
END_VAR
|
||||||
|
VAR_OUTPUT
|
||||||
|
LMP : BOOL;
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
|
||||||
|
#### BUZZER_CTRL — FUNCTION_BLOCK
|
||||||
|
Управление звонком/зуммером по коду состояния с отключением звука (временным и перманентным).
|
||||||
|
```
|
||||||
|
FUNCTION_BLOCK BUZZER_CTRL
|
||||||
|
VAR_INPUT
|
||||||
|
State : WORD := 0; // 0=выкл;1=вкл;2/3/4=мигание частота1/2/3
|
||||||
|
Sound_off : BOOL; // кнопка отключения звука
|
||||||
|
Inp_BL_1 : BOOL := FALSE; // пульсар частоты 1
|
||||||
|
Inp_BL_2 : BOOL := FALSE; // пульсар частоты 2
|
||||||
|
Inp_BL_3 : BOOL := FALSE; // пульсар частоты 3
|
||||||
|
PermanentOFF_Enable : BOOL := FALSE; // разрешить перманентное отключение звука
|
||||||
|
T_PermanentOFF : TIME := T#10S; // время удержания Sound_off для перманентного откл.
|
||||||
|
END_VAR
|
||||||
|
VAR_OUTPUT
|
||||||
|
BUZZER : BOOL;
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
|
||||||
|
#### CONFIRMATION — FUNCTION_BLOCK
|
||||||
|
Логика квитирования аварии (лампы + звонок).
|
||||||
|
```
|
||||||
|
FUNCTION_BLOCK CONFIRMATION
|
||||||
|
VAR_INPUT
|
||||||
|
Alarm : BOOL; // авария активна
|
||||||
|
ControlState_On : BOOL; // управление ведётся с данного поста
|
||||||
|
Sound_Off : BOOL; // сигнал снятия звука
|
||||||
|
Confirmation : BOOL; // сигнал квитирования
|
||||||
|
END_VAR
|
||||||
|
VAR_OUTPUT
|
||||||
|
Lamp_Alarm_C : WORD; // код для лампы аварии (0/1/2)
|
||||||
|
Lamp_Confirmation_C : WORD; // код для лампы квитирования (0/1)
|
||||||
|
Buzzer_C : WORD; // код для зуммера (0/2)
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
Коды ламп совместимы с входом `State`/`Inp_BL_*` блоков `BLINK_LMP`/`BUZZER_CTRL` (2 = "мигание частота 1").
|
||||||
|
|
||||||
|
### Пороговые сигналы / гистерезис
|
||||||
|
|
||||||
|
#### HYST — FUNCTION_BLOCK
|
||||||
|
Гистерезисный компаратор с раздельными порогами включения/выключения (прямая и обратная логика).
|
||||||
|
```
|
||||||
|
FUNCTION_BLOCK HYST
|
||||||
|
VAR_INPUT
|
||||||
|
In : REAL; // контролируемое значение
|
||||||
|
ON : REAL; // порог срабатывания
|
||||||
|
OFF : REAL; // порог возврата
|
||||||
|
END_VAR
|
||||||
|
VAR_OUTPUT
|
||||||
|
Q : BOOL; // состояние компаратора
|
||||||
|
win : BOOL; // TRUE — значение внутри зоны гистерезиса (неопределённая зона)
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
Если `ON >= OFF` — прямая логика (растущий сигнал включает); если `ON < OFF` — обратная (падающий сигнал включает).
|
||||||
|
|
||||||
|
#### ALARM_1 — FUNCTION_BLOCK
|
||||||
|
Контроль выхода `X` за диапазон `[LO_1..HI_1]` с гистерезисом.
|
||||||
|
```
|
||||||
|
FUNCTION_BLOCK ALARM_1
|
||||||
|
VAR_INPUT
|
||||||
|
X : REAL;
|
||||||
|
LO_1 : REAL;
|
||||||
|
HI_1 : REAL;
|
||||||
|
HYS : REAL; // полный гистерезис (берётся ±HYS/2 от границы)
|
||||||
|
END_VAR
|
||||||
|
VAR_OUTPUT
|
||||||
|
Q1_LO : BOOL; // X ниже LO_1
|
||||||
|
Q1_HI : BOOL; // X выше HI_1
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
|
||||||
|
#### ALARM_2 — FUNCTION_BLOCK
|
||||||
|
То же, для двух независимых диапазонов одновременно.
|
||||||
|
```
|
||||||
|
FUNCTION_BLOCK ALARM_2
|
||||||
|
VAR_INPUT
|
||||||
|
X : REAL;
|
||||||
|
LO_1, HI_1 : REAL;
|
||||||
|
LO_2, HI_2 : REAL;
|
||||||
|
HYS : REAL;
|
||||||
|
END_VAR
|
||||||
|
VAR_OUTPUT
|
||||||
|
Q1_LO, Q1_HI : BOOL;
|
||||||
|
Q2_LO, Q2_HI : BOOL;
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
|
||||||
|
#### ALARM_3 — FUNCTION_BLOCK
|
||||||
|
То же, для трёх диапазонов одновременно.
|
||||||
|
```
|
||||||
|
FUNCTION_BLOCK ALARM_3
|
||||||
|
VAR_INPUT
|
||||||
|
X : REAL;
|
||||||
|
LO_1, HI_1 : REAL;
|
||||||
|
LO_2, HI_2 : REAL;
|
||||||
|
LO_3, HI_3 : REAL;
|
||||||
|
HYS : REAL;
|
||||||
|
END_VAR
|
||||||
|
VAR_OUTPUT
|
||||||
|
Q1_LO, Q1_HI : BOOL;
|
||||||
|
Q2_LO, Q2_HI : BOOL;
|
||||||
|
Q3_LO, Q3_HI : BOOL;
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
|
||||||
|
### Статус аналоговых сигналов
|
||||||
|
|
||||||
|
#### STATUS_ANALOG_M — FUNCTION_BLOCK
|
||||||
|
Статус аналогового значения REAL с гистерезисом возврата и задержками срабатывания по каждому уровню; поддерживает принудительную установку статуса извне.
|
||||||
|
```
|
||||||
|
FUNCTION_BLOCK STATUS_ANALOG_M
|
||||||
|
VAR_INPUT
|
||||||
|
Input : REAL;
|
||||||
|
fail_hi : REAL := 3.402823466E+38;
|
||||||
|
delta_fail_hi : REAL := 0;
|
||||||
|
time_fail_hi : TIME := T#0S;
|
||||||
|
warn_hi : REAL := 3.402823466E+38;
|
||||||
|
delta_warn_hi : REAL := 0;
|
||||||
|
time_warn_hi : TIME := T#0S;
|
||||||
|
warn_low : REAL := -3.402823466E+38;
|
||||||
|
delta_warn_low : REAL := 0;
|
||||||
|
time_warn_low : TIME := T#0S;
|
||||||
|
fail_low : REAL := -3.402823466E+38;
|
||||||
|
delta_fail_low : REAL := 0;
|
||||||
|
time_fail_low : TIME := T#0S;
|
||||||
|
no_signal : REAL := -3.402823466E+38;
|
||||||
|
delta_no_signal : REAL := 0;
|
||||||
|
time_no_signal : TIME := T#0S;
|
||||||
|
In_fail_hi : BOOL := FALSE; // принудительно выставить статус fail_hi
|
||||||
|
In_warn_hi : BOOL := FALSE;
|
||||||
|
In_warn_low : BOOL := FALSE;
|
||||||
|
In_fail_low : BOOL := FALSE;
|
||||||
|
In_no_signal : BOOL := FALSE;
|
||||||
|
END_VAR
|
||||||
|
VAR_OUTPUT
|
||||||
|
Status : AnalogValueState;
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
Приоритет: `no_signal > fail_hi > fail_low > warn_hi > warn_low > normal`. Возврат из состояния — при пересечении порога `± delta_*`, срабатывание — с задержкой `time_*` (TON).
|
||||||
|
|
||||||
|
#### STATUS_ANALOG_M_I — FUNCTION_BLOCK
|
||||||
|
Полный аналог `STATUS_ANALOG_M`, но все пороги/входное значение типа **INT**, а не REAL. Сигнатура идентична по составу переменных (см. выше), только типы `Input`, `fail_hi`, `delta_fail_hi`, `warn_hi`, `delta_warn_hi`, `warn_low`, `delta_warn_low`, `fail_low`, `delta_fail_low`, `no_signal`, `delta_no_signal` — `INT` (диапазон по умолчанию ±32767/-32768).
|
||||||
|
|
||||||
|
### Диагностика связи
|
||||||
|
|
||||||
|
#### Status_SU_Connect — FUNCTION_BLOCK
|
||||||
|
Контроль связи по "сердцебиению" (сигнал должен периодически менять состояние).
|
||||||
|
```
|
||||||
|
FUNCTION_BLOCK Status_SU_Connect
|
||||||
|
VAR_INPUT
|
||||||
|
HeartBit : BOOL; // меняющийся сигнал от удалённого устройства
|
||||||
|
T_Pulse : TIME; // макс. допустимая длительность одного состояния HeartBit
|
||||||
|
END_VAR
|
||||||
|
VAR_OUTPUT
|
||||||
|
ERROR_CONNECT : BOOL; // TRUE — HeartBit не менялся дольше T_Pulse
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Status_Connect_Counter — FUNCTION_BLOCK
|
||||||
|
Контроль связи по инкрементируемому счётчику от удалённого устройства.
|
||||||
|
```
|
||||||
|
FUNCTION_BLOCK Status_Connect_Counter
|
||||||
|
VAR_INPUT
|
||||||
|
Counter : WORD; // счётчик, инкрементируемый удалённым устройством
|
||||||
|
T_Error : TIME; // время без изменений счётчика для фиксации обрыва связи
|
||||||
|
END_VAR
|
||||||
|
VAR_OUTPUT
|
||||||
|
CounterIsChanged : BOOL; // TRUE в цикле, где счётчик изменился
|
||||||
|
ERROR_CONNECT : BOOL; // TRUE — счётчик не менялся дольше T_Error
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
|
||||||
|
### Регулирование
|
||||||
|
|
||||||
|
#### PIDcontrol — FUNCTION_BLOCK
|
||||||
|
ПИД-регулятор с фильтрацией D-составляющей, зоной нечувствительности, антивиндапом и ручным режимом.
|
||||||
|
```
|
||||||
|
FUNCTION_BLOCK PIDcontrol
|
||||||
|
VAR_INPUT
|
||||||
|
ProcessVariable : REAL := 0.0; // измеренное значение (обратная связь)
|
||||||
|
Setpoint : REAL := 0.0; // уставка
|
||||||
|
Kp : REAL := 0.001; // пропорциональный коэффициент
|
||||||
|
Ki : REAL := 0.001; // интегральный коэффициент
|
||||||
|
Kd : REAL := 0.0; // дифференциальный коэффициент
|
||||||
|
Kdf : REAL := 1.0; // коэффициент фильтра D-части (1/Tdf)
|
||||||
|
DBmax : REAL := 0.01; // верхняя граница зоны нечувствительности (по ошибке)
|
||||||
|
DBmin : REAL := -0.01; // нижняя граница зоны нечувствительности
|
||||||
|
OutMax : REAL := 100.0; // верхний предел выхода
|
||||||
|
OutMin : REAL := 0.0; // нижний предел выхода
|
||||||
|
Ts : REAL := 0.1; // период дискретизации, с
|
||||||
|
Manual : REAL := 25.0; // значение выхода в ручном режиме
|
||||||
|
ManOn : BOOL := FALSE; // TRUE — ручной режим
|
||||||
|
Reset : BOOL := FALSE; // сброс интегратора и D-части
|
||||||
|
AntiWindup : BOOL := TRUE; // включить компенсацию накопления интегратора
|
||||||
|
END_VAR
|
||||||
|
VAR_OUTPUT
|
||||||
|
Out : REAL := 0.0; // выходной сигнал регулятора
|
||||||
|
Err : REAL := 0.0; // текущая ошибка регулирования (Setpoint-ProcessVariable)
|
||||||
|
OutLimited : BOOL := FALSE; // TRUE — выход ограничен (насыщение) или некорректны параметры
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
Если `OutMin>=OutMax`, `DBmin>=DBmax` или `Ts<=0` — блок сразу выставляет `Out:=0`, `OutLimited:=TRUE` и выходит.
|
||||||
|
|
||||||
|
### Структуры данных (очереди/стеки)
|
||||||
|
|
||||||
|
Общая сигнатура для всех 4 блоков (`QUEUE_I16`, `QUEUE_I32`, `STACK_I16`, `STACK_I32`) — идентична, отличаются только ёмкость и дисциплина (FIFO/LIFO):
|
||||||
|
|
||||||
|
```
|
||||||
|
FUNCTION_BLOCK <Имя>
|
||||||
|
VAR_INPUT
|
||||||
|
Din : INT; // значение для записи
|
||||||
|
E : BOOL := TRUE; // включение блока (при FALSE — операции не выполняются)
|
||||||
|
POP : BOOL; // извлечь элемент
|
||||||
|
PUSH: BOOL; // добавить элемент
|
||||||
|
RST : BOOL; // сброс (очистка)
|
||||||
|
END_VAR
|
||||||
|
VAR_OUTPUT
|
||||||
|
Dout : INT; // извлечённое значение (при POP)
|
||||||
|
EMPTY : BOOL := TRUE; // TRUE — пусто
|
||||||
|
FULL : BOOL; // TRUE — заполнено (достигнут предел)
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
|
||||||
|
| Имя | Дисциплина | Ёмкость |
|
||||||
|
|---|---|---|
|
||||||
|
| `QUEUE_I16` | FIFO (очередь) | 16 элементов INT |
|
||||||
|
| `QUEUE_I32` | FIFO (очередь) | 32 элемента INT |
|
||||||
|
| `STACK_I16` | LIFO (стек) | 16 элементов INT |
|
||||||
|
| `STACK_I32` | LIFO (стек) | 32 элемента INT |
|
||||||
|
|
||||||
|
Примечание: при одновременном `PUSH` и `POP` в одном скане — оба выполняются (POP обрабатывается первым в теле, затем PUSH).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Иное
|
||||||
|
`Path: Иное`
|
||||||
|
|
||||||
|
### DFS — PROGRAM
|
||||||
|
Обход графа в глубину (Depth-First Search) по матрице смежности фиксированного размера.
|
||||||
|
```
|
||||||
|
PROGRAM DFS
|
||||||
|
VAR_INPUT
|
||||||
|
graph : POINTER TO ARRAY[1..n, 1..n] OF BOOL; // матрица смежности, редактируется извне: DFS.graph[i,j]
|
||||||
|
connected : POINTER TO ARRAY[1..n] OF INT; // выход: список посещённых вершин (заполняется программой)
|
||||||
|
Start : INT := 1; // вершина, с которой начинается обход
|
||||||
|
END_VAR
|
||||||
|
VAR CONSTANT
|
||||||
|
n : INT := 12; // фиксированное количество вершин графа
|
||||||
|
END_VAR
|
||||||
|
```
|
||||||
|
⚠️ Это **PROGRAM**, а не FUNCTION_BLOCK — не инстанцируется через `:` в декларации переменной, а копируется целиком в проект пользователя (как указано в исходном комментарии `Копировать в свою программу`). Использует `STACK_I16` внутри. Результат — заполненный массив `connected^[1..k-1]` с номерами вершин, достижимых из `Start`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Structs
|
||||||
|
`Path: Structs`
|
||||||
|
|
||||||
|
### AnalogValueState — DUT (перечисление)
|
||||||
|
```
|
||||||
|
{attribute 'qualified_only'}
|
||||||
|
{attribute 'strict'}
|
||||||
|
TYPE AnalogValueState :
|
||||||
|
(
|
||||||
|
undefined := INT#0,
|
||||||
|
normal := INT#1,
|
||||||
|
warn_low := INT#2,
|
||||||
|
fail_low := INT#3,
|
||||||
|
warn_hi := INT#4,
|
||||||
|
fail_hi := INT#5,
|
||||||
|
no_signal := -INT#1
|
||||||
|
) := undefined;
|
||||||
|
END_TYPE
|
||||||
|
```
|
||||||
|
`qualified_only` — обращаться только как `AnalogValueState.normal` и т.п. (без квалификатора не компилируется).
|
||||||
|
Используется как тип выхода `Status` в `AIM725_Status`, `STATUS_ANALOG_M`, `STATUS_ANALOG_M_I`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Общие правила / соглашения библиотеки
|
||||||
|
|
||||||
|
1. **Приоритет статусов** во всех блоках статуса аналоговых сигналов (`AIM725_Status`, `STATUS_ANALOG_M`, `STATUS_ANALOG_M_I`) одинаков:
|
||||||
|
`no_signal > fail_hi > fail_low > warn_hi > warn_low > normal`.
|
||||||
|
2. **Гистерезис срабатывания/возврата**: срабатывание уровня — по достижению порога с задержкой `time_*` (аналог TON), возврат — при пересечении `порог ± delta_*` (без задержки).
|
||||||
|
3. Блоки `TOF_RST`/`TP_RST` — это стандартные МЭК TOF/TP с добавленным принудительным входом `RST` (сброс приоритетнее `IN`).
|
||||||
|
4. `DW_TO_REAL`/`REAL_TO_DW` — это **не** преобразование значения (не `DWORD_TO_REAL`), а побитовая реинтерпретация памяти через указатель. Использовать только когда явно нужна побитовая переупаковка (например, чтение REAL-регистра Modbus, пришедшего как два WORD → DWORD → REAL).
|
||||||
|
5. `QUEUE_I16`/`STACK_I16` — фиксированный размер 16 (`n:=15` внутри), `QUEUE_I32`/`STACK_I32` — размер 32. Чтобы изменить ёмкость — нужно менять исходник (константу `n` и объявление массива).
|
||||||
|
6. `DFS` — не библиотечный вызываемый блок, а шаблон программы под конкретный размер графа (`n:=12`); предназначен для копирования и адаптации в проекте пользователя.
|
||||||
|
7. Все входы с `VAR_IN_OUT` (только у `Button_acceleration.Out`) передаются **по ссылке** — вызывающая сторона должна сама объявить и инициализировать переменную, изменения применяются напрямую.
|
||||||
|
8. Единицы времени — везде тип `TIME` (`T#...`), единицы физических величин указаны в комментариях к каждому входу/выходу (мА, В, Гц, °C, Ом, °C и т.д.).
|
||||||
+133
-41
@@ -45,18 +45,21 @@ END_KEYWORDS = {
|
|||||||
"ACTION": "END_ACTION",
|
"ACTION": "END_ACTION",
|
||||||
}
|
}
|
||||||
|
|
||||||
IMPL_MARKER = "(*----- IMPLEMENTATION -----*)"
|
# Терпимо к вариациям пробелов/кол-ва дефисов, которые может внести
|
||||||
|
# автоформатирование редактора, напр. "(* ----- IMPLEMENTATION ----- *)"
|
||||||
|
# вместо канонического "(*----- IMPLEMENTATION -----*)".
|
||||||
|
IMPL_MARKER_RE = re.compile(r"\(\*\s*-{3,}\s*IMPLEMENTATION\s*-{3,}\s*\*\)")
|
||||||
|
|
||||||
HEADER_RE = re.compile(
|
HEADER_RE = re.compile(
|
||||||
r"^\(\*\n"
|
r"^\(\*[ \t]*\n"
|
||||||
r"=+\n"
|
r"=+[ \t]*\n"
|
||||||
r" Project\s*: (?P<project>[^\n]*)\n"
|
r"[ \t]*Project[ \t]*:[ \t]*(?P<project>[^\n]*?)[ \t]*\n"
|
||||||
r" Device\s*: (?P<device>[^\n]*)\n"
|
r"[ \t]*Device[ \t]*:[ \t]*(?P<device>[^\n]*?)[ \t]*\n"
|
||||||
r" Path\s*: (?P<path>[^\n]*)\n"
|
r"[ \t]*Path[ \t]*:[ \t]*(?P<path>[^\n]*?)[ \t]*\n"
|
||||||
r" Name\s*: (?P<name>[^\n]*)\n"
|
r"[ \t]*Name[ \t]*:[ \t]*(?P<name>[^\n]*?)[ \t]*\n"
|
||||||
r" Type\s*: (?P<type>[^\n]*)\n"
|
r"[ \t]*Type[ \t]*:[ \t]*(?P<type>[^\n]*?)[ \t]*\n"
|
||||||
r"=+\n"
|
r"=+[ \t]*\n"
|
||||||
r"\*\)\n",
|
r"[ \t]*\*\)[ \t]*\n",
|
||||||
re.MULTILINE,
|
re.MULTILINE,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -216,8 +219,9 @@ def parse_block_text(body, type_label):
|
|||||||
impl = parts[1].strip("\n") if len(parts) > 1 else ""
|
impl = parts[1].strip("\n") if len(parts) > 1 else ""
|
||||||
return None, (to_crlf(impl) + "\r\n" if impl else "")
|
return None, (to_crlf(impl) + "\r\n" if impl else "")
|
||||||
|
|
||||||
if IMPL_MARKER in body:
|
m = IMPL_MARKER_RE.search(body)
|
||||||
decl, impl = body.split(IMPL_MARKER, 1)
|
if m:
|
||||||
|
decl, impl = body[:m.start()], body[m.end():]
|
||||||
decl = decl.strip("\n")
|
decl = decl.strip("\n")
|
||||||
impl = impl.strip("\n")
|
impl = impl.strip("\n")
|
||||||
return (to_crlf(decl) + "\r\n" if decl else ""), (to_crlf(impl) + "\r\n" if impl else "")
|
return (to_crlf(decl) + "\r\n" if decl else ""), (to_crlf(impl) + "\r\n" if impl else "")
|
||||||
@@ -227,6 +231,41 @@ def parse_block_text(body, type_label):
|
|||||||
return (to_crlf(decl) + "\r\n" if decl else ""), None
|
return (to_crlf(decl) + "\r\n" if decl else ""), None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def extract_return_type(declaration, type_label, object_name):
|
||||||
|
"""Извлекает return type из FUNCTION/METHOD/PROPERTY declaration."""
|
||||||
|
if not declaration:
|
||||||
|
return None
|
||||||
|
|
||||||
|
text = normalize_newlines(declaration)
|
||||||
|
# Удаляем только ведущие прагмы для анализа первой строки.
|
||||||
|
while True:
|
||||||
|
new_text = re.sub(r"^\s*\{[^}]*\}\s*", "", text, count=1)
|
||||||
|
if new_text == text:
|
||||||
|
break
|
||||||
|
text = new_text
|
||||||
|
|
||||||
|
for line in text.split("\n"):
|
||||||
|
line = line.strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Для анализа нам не нужны inline block-comments.
|
||||||
|
line = re.sub(r"\(\*.*?\*\)", "", line).strip()
|
||||||
|
m = re.match(
|
||||||
|
r"^" + re.escape(type_label) + r"\s+\S+\s*:\s*(.+?)\s*$",
|
||||||
|
line,
|
||||||
|
re.IGNORECASE
|
||||||
|
)
|
||||||
|
if m:
|
||||||
|
return m.group(1).strip()
|
||||||
|
|
||||||
|
# Если это нужный заголовок, но return type отсутствует.
|
||||||
|
if line.upper().startswith(type_label + " "):
|
||||||
|
return None
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
# ---------- запись в объекты проекта ----------
|
# ---------- запись в объекты проекта ----------
|
||||||
|
|
||||||
def set_declaration(obj, text):
|
def set_declaration(obj, text):
|
||||||
@@ -247,63 +286,116 @@ def set_implementation(obj, text):
|
|||||||
print(" ! ошибка записи implementation: %s" % e)
|
print(" ! ошибка записи implementation: %s" % e)
|
||||||
|
|
||||||
|
|
||||||
def get_or_create_pou(parent, name, type_label):
|
def get_or_create_pou(parent, name, type_label, declaration_text):
|
||||||
|
"""Создаёт PROGRAM/FB/FUNCTION/INTERFACE через специализированный API."""
|
||||||
existing = find_child(parent, name)
|
existing = find_child(parent, name)
|
||||||
if existing is not None:
|
if existing is not None:
|
||||||
|
print(" = %s '%s' уже существует" % (type_label, name))
|
||||||
return existing, False
|
return existing, False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if type_label == "INTERFACE":
|
if type_label == "PROGRAM":
|
||||||
obj = parent.create_interface(name)
|
create_program = getattr(parent, "create_program", None)
|
||||||
else:
|
if create_program is not None:
|
||||||
pou_kind = {
|
return create_program(name, ImplementationLanguages.st), True
|
||||||
"PROGRAM": "Program",
|
return parent.create_pou(name, PouType.Program, ImplementationLanguages.st), True
|
||||||
"FUNCTION_BLOCK": "FunctionBlock",
|
|
||||||
"FUNCTION": "Function",
|
if type_label == "FUNCTION_BLOCK":
|
||||||
}[type_label]
|
create_fb = getattr(parent, "create_function_block", None)
|
||||||
|
if create_fb is not None:
|
||||||
|
return create_fb(name, ImplementationLanguages.st), True
|
||||||
|
return parent.create_pou(name, PouType.FunctionBlock, ImplementationLanguages.st), True
|
||||||
|
|
||||||
|
if type_label == "FUNCTION":
|
||||||
|
return_type = extract_return_type(declaration_text, "FUNCTION", name)
|
||||||
|
if not return_type:
|
||||||
|
raise RuntimeError("не удалось определить return type FUNCTION '%s'" % name)
|
||||||
|
|
||||||
|
print(" -> FUNCTION '%s' : %s" % (name, return_type))
|
||||||
|
create_function = getattr(parent, "create_function", None)
|
||||||
|
if create_function is not None:
|
||||||
|
return create_function(name, return_type, ImplementationLanguages.st), True
|
||||||
|
|
||||||
|
# Fallback для старых версий API.
|
||||||
try:
|
try:
|
||||||
pou_type_enum = getattr(PouType, pou_kind) # noqa: F821 (глобал CODESYS)
|
return parent.create_pou(name, PouType.Function, return_type, ImplementationLanguages.st), True
|
||||||
except Exception:
|
except TypeError:
|
||||||
pou_type_enum = pou_kind
|
return parent.create_pou(name, PouType.Function, ImplementationLanguages.st, return_type), True
|
||||||
obj = parent.create_pou(name, pou_type_enum, ImplementationLanguages.st) # noqa: F821
|
|
||||||
return obj, True
|
if type_label == "INTERFACE":
|
||||||
|
return parent.create_interface(name), True
|
||||||
|
|
||||||
|
raise RuntimeError("неизвестный тип POU '%s'" % type_label)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(" ! не удалось создать %s '%s': %s" % (type_label, name, e))
|
print(" ! НЕ УДАЛОСЬ создать %s '%s': %s" % (type_label, name, e))
|
||||||
return None, False
|
return None, False
|
||||||
|
|
||||||
|
|
||||||
def get_or_create_method(parent_pou, name):
|
def get_or_create_method(parent_pou, name, declaration_text):
|
||||||
existing = find_child(parent_pou, name)
|
existing = find_child(parent_pou, name)
|
||||||
if existing is not None:
|
if existing is not None:
|
||||||
|
print(" = METHOD '%s' уже существует" % name)
|
||||||
return existing, False
|
return existing, False
|
||||||
|
|
||||||
|
return_type = extract_return_type(declaration_text, "METHOD", name)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
obj = parent_pou.create_method(name, "", "", ImplementationLanguages.st) # noqa: F821
|
return parent_pou.create_method(
|
||||||
return obj, True
|
name, return_type, ImplementationLanguages.st
|
||||||
|
), True
|
||||||
|
except TypeError:
|
||||||
|
try:
|
||||||
|
return parent_pou.create_method(name, return_type), True
|
||||||
|
except Exception as e:
|
||||||
|
print(" ! НЕ УДАЛОСЬ создать METHOD '%s': %s" % (name, e))
|
||||||
|
return None, False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(" ! не удалось создать METHOD '%s': %s" % (name, e))
|
print(" ! НЕ УДАЛОСЬ создать METHOD '%s': %s" % (name, e))
|
||||||
return None, False
|
return None, False
|
||||||
|
|
||||||
|
|
||||||
def get_or_create_property(parent_pou, name):
|
def get_or_create_property(parent_pou, name, declaration_text):
|
||||||
existing = find_child(parent_pou, name)
|
existing = find_child(parent_pou, name)
|
||||||
if existing is not None:
|
if existing is not None:
|
||||||
|
print(" = PROPERTY '%s' уже существует" % name)
|
||||||
return existing, False
|
return existing, False
|
||||||
|
|
||||||
|
return_type = extract_return_type(declaration_text, "PROPERTY", name)
|
||||||
|
if not return_type:
|
||||||
|
print(" ! НЕ УДАЛОСЬ определить return type PROPERTY '%s'" % name)
|
||||||
|
return None, False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
obj = parent_pou.create_property(name, "", ImplementationLanguages.st) # noqa: F821
|
return parent_pou.create_property(
|
||||||
return obj, True
|
name, return_type, ImplementationLanguages.st
|
||||||
|
), True
|
||||||
|
except TypeError:
|
||||||
|
try:
|
||||||
|
return parent_pou.create_property(name, return_type), True
|
||||||
|
except Exception as e:
|
||||||
|
print(" ! НЕ УДАЛОСЬ создать PROPERTY '%s': %s" % (name, e))
|
||||||
|
return None, False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(" ! не удалось создать PROPERTY '%s': %s" % (name, e))
|
print(" ! НЕ УДАЛОСЬ создать PROPERTY '%s': %s" % (name, e))
|
||||||
return None, False
|
return None, False
|
||||||
|
|
||||||
|
|
||||||
def get_or_create_action(parent_pou, name):
|
def get_or_create_action(parent_pou, name):
|
||||||
existing = find_child(parent_pou, name)
|
existing = find_child(parent_pou, name)
|
||||||
if existing is not None:
|
if existing is not None:
|
||||||
|
print(" = ACTION '%s' уже существует" % name)
|
||||||
return existing, False
|
return existing, False
|
||||||
try:
|
try:
|
||||||
obj = parent_pou.create_action(name, ImplementationLanguages.st) # noqa: F821
|
return parent_pou.create_action(name, ImplementationLanguages.st), True
|
||||||
return obj, True
|
except TypeError:
|
||||||
|
try:
|
||||||
|
return parent_pou.create_action(name), True
|
||||||
|
except Exception as e:
|
||||||
|
print(" ! НЕ УДАЛОСЬ создать ACTION '%s': %s" % (name, e))
|
||||||
|
return None, False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(" ! не удалось создать ACTION '%s': %s" % (name, e))
|
print(" ! НЕ УДАЛОСЬ создать ACTION '%s': %s" % (name, e))
|
||||||
return None, False
|
return None, False
|
||||||
|
|
||||||
|
|
||||||
@@ -374,7 +466,7 @@ def process_st_file(proj, filepath, dry_run):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if type_label in POU_TYPES:
|
if type_label in POU_TYPES:
|
||||||
obj, _ = get_or_create_pou(target_parent, name, type_label)
|
obj, _ = get_or_create_pou(target_parent, name, type_label, decl_text)
|
||||||
if obj is None:
|
if obj is None:
|
||||||
parent_pou = None
|
parent_pou = None
|
||||||
continue
|
continue
|
||||||
@@ -387,9 +479,9 @@ def process_st_file(proj, filepath, dry_run):
|
|||||||
print(" ! нет родительского POU в этом файле, пропуск")
|
print(" ! нет родительского POU в этом файле, пропуск")
|
||||||
continue
|
continue
|
||||||
if type_label == "METHOD":
|
if type_label == "METHOD":
|
||||||
obj, _ = get_or_create_method(parent_pou, name)
|
obj, _ = get_or_create_method(parent_pou, name, decl_text)
|
||||||
else:
|
else:
|
||||||
obj, _ = get_or_create_property(parent_pou, name)
|
obj, _ = get_or_create_property(parent_pou, name, decl_text)
|
||||||
if obj is None:
|
if obj is None:
|
||||||
continue
|
continue
|
||||||
set_declaration(obj, decl_text)
|
set_declaration(obj, decl_text)
|
||||||
|
|||||||
Reference in New Issue
Block a user