Текст я писал с языковой моделью: она собирала черновик и вычитывала формулировки. Решения, замеры и разбор находок мои. Это моя вторая публикация здесь; в первой читатели нашли шесть расхождений между текстом и кодом, и с тех пор я цитирую цифры только из файлов и журналов, а не по памяти.
Меня зовут Дмитрий Груздев, я главный конструктор АСУТП. Занимаюсь системами управления испытательными стендами — теми, где изделие после ремонта гоняют по режимам, снимают характеристики и решают, годно ли оно к эксплуатации. Эта статья — про случай, когда пришлось восстанавливать работающую систему без единой строки исходного кода, и про то, что при этом нашлось в оригинале.
Названий предприятий, обозначений изделий и номеров документов здесь не будет — только инженерная суть: архитектура, цифры технических решений, методика сверки, найденные дефекты.
Постановка: система жива, а сопровождать её нечем
Есть действующий испытательный стенд с энергоёмкой нагрузочной установкой. Он работает: ПЛК крутит программу, оператор смотрит на HMI-панель, режимы отрабатываются. И при этом:
лицензия на HMI-панель утрачена: панель работает, пока работает, но перенести проект, отредактировать экран или восстановить конфигурацию после отказа железа невозможно;
исходников программы ПЛК нет — ни архива, ни резервной копии, ни у эксплуатанта, ни у того, кто это когда-то делал;
выгрузить проект из ПЛК нельзя: блоки защищены, а восстановление байт-кода до вменяемого исходника здесь экономически бессмысленно.
Что есть: бумага. Распечатка проекта среды программирования, распечатка экранов HMI, руководство по эксплуатации и сканы электрических схем — наследие приёмки, когда бумажный комплект сдавали как часть документации. Тогда формальность, теперь единственный источник истины.
Задача сформулирована жёстко: восстановить и переписать систему заново так, чтобы поведение полностью совпало с оригиналом. Не «лучше» и не «современнее», а именно совпало: стенд аттестован, методики привязаны к его поведению, любое расхождение — расхождение с методикой.
Почему нельзя было «просто написать заново»
Первая реакция инженера — «напишем с нуля, там всего-то краны, дроссели и генератор». Я её сам испытал, поэтому объясню, почему она неверна.
Стенд — не абстрактный технологический объект, а средство испытаний, чьё поведение зафиксировано в аттестационных документах и методиках. Выбирая режим, оператор ожидает конкретную циклограмму: последовательность включения ступеней, выдержки, пороги перехода. Напиши я «логично и правильно», но с другими выдержками — получится другой стенд, формально работающий, фактически не тот, на котором получены зачётные результаты.
Второй аргумент — аварии. Каталог аварийных ситуаций не декоративный список сообщений: за каждым условием стоит либо физика (перегрев, превышение тока, потеря давления), либо инцидент. Придумать набор заново — выбросить накопленный опыт эксплуатации.
Третий аргумент — монтаж: перебирать шкафы и переназначать клеммы никто не собирался, значит новая программа обязана «сесть» на существующую электрику один в один.
Отсюда решение: не писать новую систему, а восстановить эталон по документации и затем реализовать его заново на современном стеке. Два этапа, путать их нельзя: первый — археология, второй — инженерия.
Когда объект аттестован, «переписать лучше» и «переписать эквивалентно» — разные проекты с разной приёмкой. Отвечать на вопрос, какой из двух вы делаете, надо до начала, а не в середине.
Как читаются 4 900 страниц
Объём эталонного комплекта в цифрах:
Источник |
Объём |
Что даёт |
|---|---|---|
Распечатка проекта среды программирования |
~900 страниц |
Все блоки, теги, сети LAD и SCL |
Экспорт экранов HMI |
3 907 страниц, 26 экранов |
Каждая кнопка с действием и привязкой к тегам |
Руководство по эксплуатации |
93 страницы |
Семантика режимов, таблицы, циклограммы |
Электрические схемы |
сканы |
Физическая привязка I/O |
Итого 4 900 страниц. Цифра сложена из четырёх слагаемых, и одно из них неточное: объём распечатки проекта я оценил на глаз по толщине пачки, страницы там не пронумерованы сквозной нумерацией. Три остальных посчитаны точно. Сканы схем пришлось прогонять через OCR с русским языком: иначе поиск по ним невозможен, а листать схему глазами ради одного адреса — гарантированная ошибка.
Читать такой массив подряд бессмысленно: к пятисотой странице забудешь первую. Поэтому я строил не конспект, а реестры — таблицы, каждая из которых закрывает один аспект поведения:
Реестр функций нагрузки — 17 функций: что делает каждая, какими параметрами управляется, какие блокировки.
Каталог аварий — 13 позиций: код, условие возникновения, приоритет, план действий оператора, условие снятия.
Циклограммы — 6 штук с точными числами: пороги, выдержки, шаги.
Ступени модуля нагрузки — 27 ступеней: 14 по одному вводу и 13 по другому, с составом включаемых силовых элементов.
Таблица I/O — 60 каналов с адресами и назначением: 27 дискретных входов, 32 дискретных выхода, 1 аналоговый выход.
Отдельно про ступени. В оригинале выбор ступени представлен маской в виде единого 32-битного слова: каждый бит — конкретный коммутационный элемент. Удобно для передачи по шине и ужасно для чтения человеком: чтобы понять, что делает значение вроде 16#0004_A801, его нужно разложить в биты и сверить с монтажной схемой. На этом шаге позже вскрылась половина расхождений.
Ключевой методический момент: основным эталоном я сделал руководство по эксплуатации, а не распечатку кода. Код — это то, что кто-то реализовал, возможно с ошибкой; РЭ — то, что было согласовано и что описывает требуемое поведение. Когда источники расходятся, расхождение надо не «примирить», а вынести на решение. Возьми я за эталон распечатку кода — перенёс бы в новую систему все её дефекты, и один из них, как покажет аудит, был опасным.
Эталоном я сделал описание требуемого поведения. Код — одна из его реализаций, и проверять надо её.
Архитектура новой программы ПЛК
Целевая платформа — Siemens S7-1500, CPU 1513-1 PN, TIA Portal V18: ровно та линейка, что стоит на объекте и которую сопровождает служба эксплуатации.
Оригинал — около 95 разрозненных FC/FB с обильным дублированием и кириллицей в именах блоков плюс «плоский» DB, где всё лежало вперемешку. Типичная картина проекта, росшего итерациями: понадобился второй кран — скопировали блок первого и поправили адреса; понадобился третий — скопировали второй. Проблема тут не эстетическая: любое изменение логики нужно вносить N раз, и на N-м разе кто-то ошибётся. Собственно, ошибки там и нашлись.
Что я сделал:
вынес повторяющиеся объекты в UDT и multi-instance FB: три крана — один FB, инстанцированный трижды; восемь дросселей — один FB на восемь экземпляров. Логика описана в одном месте, экземпляры отличаются только данными;
сделал один движок циклограмм вместо шести реализаций. Циклограмма перестала быть кодом и стала данными — таблицей шагов, которую движок исполняет;
разделил данные и логику: ступени нагрузки и параметры циклограмм лежат в retain-DB, поэтому правка выдержки или состава ступени на стенде не требует перепрошивки ПЛК.
Результат: 23 программные единицы — 9 внешних SCL-источников на 2 156 строк, 10 UDT и 4 DB, плюс таблица на 60 каналов I/O. Как считал: строки — wc -l по папке импорта; типы и блоки данных — grep -c '^TYPE' UDT_all.scl даёт 10, grep -c '^DATA_BLOCK' DB_global.scl даёт 4.
Сравнивать 23 с 95 напрямую нельзя: 95 — это FC и FB оригинала, а 23 — все программные единицы новой реализации вместе с типами и блоками данных. Корректное сравнение такое: три крана в оригинале были тремя почти одинаковыми блоками, стали одним FB на три экземпляра; восемь дросселей — восемью блоками, стали одним на восемь; шесть циклограмм были шестью ветками кода, стали одним движком и таблицей.
Data-driven подход я считаю главным архитектурным решением, поэтому покажу не пересказ, а сами файлы. Ниже — куски проекта как они лежат на диске. Единственная правка: обозначения приборов и генераторов в комментариях заменены на нейтральные, потому что публиковать их я не могу. Всё остальное — копипаста.
Ступень нагрузки:
TYPE "udtGenStage" VERSION : 0.1 STRUCT id : Int; // Уникальный ID ступени. Заводские: три группы по три, // по одной группе на каждый источник питания. // Пользовательские добавляйте с 900+ (не пересекаться с заводскими). mode : Int; // Фильтр экрана: 1 = режим первого насоса, 2 = режим второго. gen : Int; // Источник (подпись/группировка): 1 и 2 — Ввод 1 (перем. ток), // 3 — Ввод 2 (пост. ток 28,5 В). mask1 : Word; // Маска ступеней ВВОДА 1 (перем.ток). Бит k => ступень (101+k), // биты 0..13 = ступени 101..114. Пишется в рег.4 модуля нагрузки. mask2 : Word; // Маска ступеней ВВОДА 2 (пост.ток). Бит k => ступень (201+k), // биты 0..12 = ступени 201..213. Пишется в рег.5 модуля нагрузки. limitMs : UDInt; // Лимит удержания, мс. 0 = ПОСТОЯННАЯ. >0 = ВРЕМЕННАЯ: // через limitMs ступень снимается и блокируется до «Сброса». // Диапазон по требованию: 1000 (1 с) … часы. isTemp : Bool; // TRUE=временная (учитывать limitMs+блокировать), FALSE=постоянная. currentA : Real; // Ток ступени, А — только ОТОБРАЖЕНИЕ (на логику не влияет). kw : Real; // Мощность, кВт — только отображение. used : Bool; // TRUE = строка действующая; FALSE = свободная (под добавление). // fbGenLoad игнорирует строки used=FALSE. END_STRUCT; END_TYPE
Здесь видно две вещи, ради которых всё и затевалось. Биты маски объяснены прямо в поле — не надо лезть в монтажную схему, чтобы понять, что означает 16#1911. И limitMs — тот самый таймер, о котором пойдёт речь в разделе про аудит, — не константа в коде, а число в строке таблицы: у ступени на максимальный ток там стоит 10000. Чтобы его поправить, TIA Portal не нужен.
Циклограмма разложена на шаг и массив шагов:
TYPE "udtCycloStep" VERSION : 0.1 STRUCT solMask : Word; // маска СОЛЕНОИДОВ/дросселей шага (бит k = СОЛ k+1) durMs : UDInt; // длительность шага, мс (1000 = 1 с) targetFlow : Real; // целевой расход на шаге, л/мин END_STRUCT; END_TYPE TYPE "udtCyclogram" VERSION : 0.1 STRUCT stepCount : Int; // число активных шагов used : Bool; // TRUE = циклограмма существует cycName : String[20]; // имя для отображения step : Array[1..16] of "udtCycloStep"; END_STRUCT; END_TYPE
Шестнадцать шагов на циклограмму — не расчёт, а запас: в самой длинной из шести штатных семь шагов. Массив самих циклограмм объявлен как Array[1..12]: шесть заводских и шесть свободных строк под то, что заказчик придумает потом.
А исполняет их всех один блок. Вот он целиком, 53 строки из 04_functions.scl:
FUNCTION_BLOCK "fbCyclogram" { S7_Optimized_Access := 'TRUE' } VERSION : 0.1 VAR_INPUT start : Bool; // запустить цикл (уровень) cycleNo : Int; // номер циклограммы 1..3 END_VAR VAR_OUTPUT solMask : Word; stepIdx : Int; finished : Bool; END_VAR VAR sw : "fbStopwatch"; idx : Int; running : Bool; startPrev : Bool; END_VAR VAR_TEMP nSteps : Int; stepDone : Bool; dummyEl : UDInt; END_VAR BEGIN IF (#cycleNo < 1) OR (#cycleNo > 12) THEN // 1..6 заводские + 7..12 пользовательские #solMask := 0; #finished := FALSE; RETURN; END_IF; #nSteps := "gConfig".cyclo[#cycleNo].stepCount; // фронт запуска -> инициализация IF #start AND NOT #startPrev THEN #idx := 1; #running := TRUE; #finished := FALSE; END_IF; IF NOT #start THEN #running := FALSE; #solMask := 0; #idx := 0; END_IF; #startPrev := #start; IF #running AND (#idx >= 1) AND (#idx <= #nSteps) THEN #sw(run := TRUE, reset := FALSE, preset := "gConfig".cyclo[#cycleNo].step[#idx].durMs, elapsed => #dummyEl, done => #stepDone); #solMask := "gConfig".cyclo[#cycleNo].step[#idx].solMask; IF #stepDone THEN #sw(run := FALSE, reset := TRUE, preset := 0, elapsed => #dummyEl, done => #stepDone); #idx := #idx + 1; IF #idx > #nSteps THEN #running := FALSE; #finished := TRUE; #solMask := 0; END_IF; END_IF; END_IF; #stepIdx := #idx; END_FUNCTION_BLOCK
Комментарий // номер циклограммы 1..3 во входной переменной устарел — проверка ниже пропускает 1…12. Не заметил, пока не вставлял сюда. Ошибки в этом нет, блок работает по проверке, а не по комментарию, но это ровно тот сорт мусора, который копится в проекте и через год вводит в заблуждение следующего. Поправлю в файле; здесь оставляю как есть, потому что цитата — это цитата.
Пятьдесят три строки вместо шести веток кода. Добавление режима перестало быть задачей программиста: строка в таблице, а не блок в проекте.
Смысл не в том, что стало «красивее». Логика крана теперь живёт в одном месте, и правка вносится один раз, а не трижды.
Карта Modbus как контракт
Между ПК верхнего уровня и ПЛК нужен был явный и стабильный интерфейс. Я сделал его картой Holding-регистров 0…499 с жёсткой разбивкой по зонам:
0 – 99 статус системы (режим, состояние приводов, флаги готовности) 100 – 199 телеметрия (токи, напряжения, температуры, давления, расходы) 200 – 253 команды (54 регистра, пишутся одним блоком) 300 – 399 уставки 400 – 431 аварии (битовые слова по каталогу)
Зонирование даёт читаемость (по номеру регистра сразу понятен класс, при отладке не нужно лезть в таблицу) и пакетность обмена: статус с телеметрией читаются непрерывными блоками, команды пишутся одним блоком из 54 регистров.
Последнее принципиально: при записи по одному регистру ПЛК какое-то время видит несогласованное состояние — например, новую ступень со старым признаком ввода. Запись блоком делает набор команд атомарным для прикладной логики.
ПЛК в этой схеме работает одновременно как Modbus TCP сервер и как клиент: сервером он отдаёт данные верхнему уровню, клиентом опрашивает периферию через шлюз RS-485↔TCP. Совмещение ролей — обычная практика, но требует аккуратности с тайм-аутами: медленный опрос периферии не должен приводить к «залипшей» телеметрии наверху, поэтому у каждой группы данных в зоне статуса есть признак актуальности.
Карта регистров — контракт между двумя командами. Зонирование, атомарность записи и признак протухания данных в нём такие же обязательные пункты, как сами адреса.
Замена HMI: вместо панели — ПК и браузер
Лицензия на панель утрачена, а покупать её заново означало через несколько лет оказаться в той же ловушке, поэтому человеко-машинный интерфейс переехал на ПК.
Стек: приложение .NET 8, где оболочка Avalonia служит мостом и держит жизненный цикл, внутри поднимается веб-сервер Kestrel (HTTP + WebSocket), рядом — драйвер Modbus TCP, журналирование и мониторинг ПК через WMI. Отдельно написан симулятор ПЛК (Modbus TCP slave), позволяющий запускать приложение целиком без единого куска железа: забегая вперёд, именно он сделал возможной верификацию.
Интерфейс оператора — браузер в режиме киоска, веб-HMI полностью офлайн, чистый JS без сборщиков и внешних зависимостей: на стенде нет интернета, а зависимость от CDN или node_modules — отложенная проблема, которая проявится через два года при переустановке.
Объём: 19 экранов, включая WYSIWYG-конструктор экранов и редактор карты Modbus. Инженерная часть — 21 файл C#, около 2 145 строк; клиентская — app.js на 1 693 строки и 157 обработчиков.
Из эксплуатационных требований заложено:
три уровня доступа и 8 функций, закрытых правами: оператор не должен иметь возможности переписать карту регистров;
журналы append-only с пофайловой хеш-цепочкой: каждая запись содержит хеш предыдущей, отредактировать журнал задним числом без разрыва цепочки нельзя. Журнал — часть доказательной базы испытаний;
аварийная подсистема по ISA-18.2: приоритизация, shelving — временное отключение надоедливой сигнализации с фиксацией факта и причины в журнале, детектирование дребезга — чтобы сигнализация, мигающая раз в секунду, не превращала список аварий в мусор;
NAMUR NE 107 для диагностики оборудования: отказ, требуется обслуживание, вне спецификации, проверка функции. Оператор различает «датчик врёт» и «параметр вышел за границы» — это разные действия.
Аудит: 60 совпадений и 7 расхождений
Когда новая система была написана, я сверил «оригинал ↔ новая программа» построчно по всем реестрам: I/O, ступени, циклограммы, аварии, карта регистров, экраны.
Сразу про границы, чтобы не выглядело сильнее, чем есть. I/O, аварии, циклограммы и карту регистров я прошёл целиком. Маски ступеней — 9 из 27: на каждую уходило минут двадцать ручной раскладки в биты, и после девятой, где восемь оказались с расхождениями, стало ясно, что проверять надо все, но времени до сдачи уже не было. Остальные 18 масок не проверены до сих пор. Это самая большая дыра в моей же методике, и я не знаю, сколько там ещё расхождений.
Хорошая новость: I/O сошлись 60 из 60 — 27 дискретных входов, 32 дискретных выхода и 1 аналоговый выход; адреса, назначение и логика (нормально открытый / нормально закрытый) совпали полностью. Практический смысл: перемонтаж не требуется — новая программа садится на существующие шкафы без единого перекинутого провода.
Плохая: нашлось 7 расхождений, из них 4 критических.
1. Таймер удержания максимального тока: 5 минут вместо 10 секунд
В режиме выхода на максимальный ток нагрузки — 1 200 А — руководство по эксплуатации предписывает удержание не более 10 секунд, после чего система обязана сбросить нагрузку. Ограничение физическое: обмотки на таком токе греются быстро, запас по времени определяется тепловой постоянной, а не удобством оператора.
В коде оригинала на этом таймере стояло 5 минут.
Тридцатикратное превышение допустимого времени.
Что за этим стоит физически — я не считал. Тепловой расчёт обмоток не делал, постоянную нагрева не измерял, к оборудованию доступа не было. Опираюсь на то, что написано в руководстве: десять секунд, дальше сброс нагрузки. Почему именно десять и что будет на трёхсотой — знает тот, кто это ограничение вносил. Мне достаточно того, что защита, рассчитанная на десять секунд, физически не сработает раньше пяти минут.
Дальше самое неприятное: дефект не в новом коде. Он унаследованный и жил в работающей системе, не проявляясь, потому что операторы, зная стенд, снимали режим руками задолго до срабатывания таймера. Защита существовала формально, а фактическую обеспечивала дисциплина персонала.
Обнаружился он ровно по одной причине: сверка велась построчно с руководством по эксплуатации, а не с исходным кодом. Возьми я за эталон распечатку проекта — а это интуитивно кажется правильным, ведь там «как оно реально работает», — я добросовестно перенёс бы 5 минут в новую программу и был бы уверен в полном соответствии оригиналу. Формально да, фактически — воспроизвёл бы отказ защиты.
Здесь общий принцип: реверс-инжиниринг неявно предполагает, что существующая система правильна — она работает, её приняли, на ней трудятся годами. Презумпция сильная и опасная. Работающая система доказывает лишь то, что она не отказала в тех сценариях, которые случались.
2. Маски ступеней нагрузки: 8 несовпадений из 9 проверенных
При раскладке 32-битных масок в биты и сверке с монтажной схемой выяснилось, что 8 из 9 проверенных масок не совпадают с оригинальной таблицей. Различия в отдельных битах: при выборе ступени включался не совсем тот набор коммутационных элементов.
Причина типична для копипастной архитектуры: маски правились в разных местах и в разное время, часть правок не доехала до всех копий. Все приведены к точным битам по таблице. Нашлось только потому, что маска была разложена в биты и сопоставлена с физикой — в шестнадцатеричном виде расхождение в одном бите глазом не видно.
3. Коллизия адресов HR220/221
В карте регистров команды калибровки и продувки оказались назначены на те же адреса, что выбор расходомера и выбор шкафа: два разных смысла на одном регистре. Следствие наглядное: нажатие «Продувка» ложно переключало генераторный шкаф во время испытаний. Команда обслуживающего характера меняла силовую конфигурацию стенда под нагрузкой. Раньше не замечали, потому что продувку в ходе испытаний обычно не жмут: сценарий не встречался — значит, дефект «не существовал».
Обе команды перенесены на свободные адреса, карта зафиксирована как документ и автоматически проверяется на уникальность назначений: теперь коллизия ловится проверкой, а не наблюдательностью.
Чего я так и не понял: как эта коллизия пережила приёмку. Либо продувку при сдаче не нажимали, либо нажимали и не связали с переключением шкафа. Спросить некого — тех, кто сдавал стенд, я не нашёл.
4. Двенадцать необъявленных символов в SCL
В восстановленных SCL-источниках нашлось 12 обращений к необъявленным символам — при импорте в TIA Portal проект упал бы на первой же компиляции. Найдены статическим анализом до импорта и объявлены.
Остальные три
Таймеры других режимов: в нескольких режимах вместо предписанных 5 минут стояло фактически бесконечное удержание. Тот же класс дефекта, что и первый, но с меньшим риском.
HMI показывал 6 приводов вместо физических 8. Два привода существовали в железе и в программе, но не были представлены на экране. Оператор не видел их состояния.
Блок записи команд моста: 34 регистра вместо 54. Верхний уровень писал в ПЛК усечённый блок, и часть правок, сделанных в редакторах, молча не доходила до ПЛК — не с ошибкой, не с диагностикой, просто не доходила. Человек менял уставку, видел её на экране и был уверен, что она применена.
Последний пункт я считаю вторым по неприятности после таймера: ошибка, приводящая к отказу, обнаруживается, а ошибка, приводящая к молчаливому игнорированию части команд, живёт годами и портит результаты испытаний.
Здесь я в первой редакции этого текста написал, что все расхождения найдены сверкой и ни одно — тестированием. Перечитал собственную статью и понял, что это неправда. Честная разбивка:
Расхождение |
Чем найдено |
|---|---|
Таймер 5 минут вместо 10 секунд |
сверка кода с руководством |
Маски ступеней, 8 из 9 |
раскладка масок в биты и сверка с монтажной схемой |
Таймеры других режимов |
сверка кода с руководством |
HMI показывал 6 приводов вместо 8 |
сверка экранов с реестром I/O |
Коллизия HR220/221 |
глазами, при чтении карты регистров |
12 необъявленных символов |
статический анализ, поймал бы и компилятор |
34 регистра вместо 54 |
прокликивание интерфейса со снимком состояния симулятора |
Четыре из семи — сверка двух независимых описаний. Три остальных нашлись инструментами и вниманием. Тестирование «нажали — работает» действительно не нашло бы ни одного из первых четырёх, и это существенно. Но утверждать, что сверка — единственный способ, было преувеличением.
Верификация без доступа к железу
Стенд в эксплуатации, останавливать его ради отладки никто не даст — значит, всё проверяемое должно быть проверено до выезда.
Статический анализ. Балансы SCL (парность конструкций, полнота ветвлений, объявленность символов), node --check по всему клиентскому JS, автоматический рендер всех экранов веб-HMI с контролем ошибок консоли. Дёшево и ловит целый класс дефектов, включая те 12 необъявленных символов.
Автономные смоук-тесты в браузере — два набора, четыре сценария: подключение, обмен, отрисовка, потеря связи. Все прошли.
Сплошная UI-проверка. Все 19 экранов и около 90 элементов управления прокликаны реальными кликами через браузерную автоматизацию — не просмотрены, а нажаты. После каждого действия контролировались снимок состояния симулятора ПЛК (что ушло в регистры) и защищённый журнал (появилась ли запись и та ли).
Именно она вскрыла дефект с 34 регистрами вместо 54: нажатие проходило, экран показывал изменение, а снимок ПЛК — что часть регистров не изменилась.
Целостность журнала. Хеш-цепочка проверена на 181 записи, разрывов нет.
Если объект недоступен, первым делом пишется его модель. Симулятор ПЛК обошёлся мне в два дня и окупился на первом же выезде, которого не пришлось делать.
Ложный след: полдня на несуществующую проблему
Один SCL-источник — тот, что отвечал за Modbus-клиент — при импорте в TIA давал 37 ошибок компиляции, после правок их стало 51. Все концентрировались вокруг вызова инструкции Modbus-клиента: несоответствие типов, неизвестный формальный параметр, неверное число аргументов.
Гипотеза родилась мгновенно и выглядела убедительно: несовместимость версии инструкции. Версии библиотек коммуникации в TIA действительно различаются по сигнатурам, это известная боль с документированными проявлениями. Я честно пошёл по этому пути: сверял версии, читал описание параметров, пробовал разные варианты вызова, менял типы. Полдня.
Настоящая причина оказалась другой. В TIA импортировалась устаревшая копия файла. В ней отсутствовало приведение типа, которое я добавил, и присутствовал формальный параметр, которого в актуальной версии инструкции нет. Я правил один файл, а компилировался другой. Все ошибки были абсолютно корректными — просто относились к тексту, который я уже исправил.
Мораль из тех, что усваиваются только через собственные полдня: прежде чем чинить код, убедись, что компилируется именно тот файл, который ты правил. Проверяется за минуту — вставить заведомо ошибочную строку и посмотреть, сообщит ли о ней компилятор. Не сообщил — вы отлаживаете не тот файл. И отдельно: правдоподобная гипотеза опаснее неправдоподобной — «несовместимость версий» объясняла симптомы так хорошо, что я перестал проверять предпосылки.
Условия работы и сборка без интернета
Работа шла с 3 июня по 11 июля, по вечерам и выходным, на кухонном столе: два монитора, распечатки схем на полу, потому что на столе они не помещаются.
Часть сеансов шла через удалённый доступ к машине на объекте, и канал рвался каждые 1–3 минуты. Нормальная работа в IDE в таких условиях невозможна — не успеваешь набрать строку. Процесс пришлось перестроить: правки файлов делались однострочниками PowerShell, а ввод текста дробился на куски не длиннее 14 символов — эмпирически подобранная длина, успевавшая уходить между обрывами. Медленно, но детерминированно: операция либо прошла целиком, либо не прошла вовсе.
Целевая машина не имела доступа в интернет, как и положено машине технологического контура. Под сборку .NET-приложения был подготовлен офлайн-кэш NuGet: 39 пакетов, около 138 МБ. При первой сборке с нуля вылезли 3 реальные ошибки компиляции — не проблемы окружения, а мои ошибки в коде, маскировавшиеся тем, что часть кода писалась без сборки. Исправлены, итоговая сборка — 0 ошибок, 0 предупреждений.
Об инструменте
Работа велась с использованием LLM-ассистента как основного инструмента разработки. Что он дал: скорость чтения 4 900 страниц (вручную построение пяти реестров заняло бы месяцы), генерацию значительной части SCL и C# по уже принятым решениям и построчную сверку — монотонное сопоставление таблиц, на котором человеческое внимание отказывает первым, и именно оно дало четыре расхождения из семи. Чего не дал: ни одного архитектурного решения — переход от 95 блоков к 23, data-driven хранение ступеней и циклограмм, зонирование карты регистров, выбор веб-HMI, решение написать симулятор ставились человеком; разрешение противоречий в эталоне, где выбор делается из понимания физики; постановку задачи — что считать эталоном и что вообще является дефектом. Таймер на 5 минут ассистент нашёл не сам: он нашёл его потому, что я поставил задачу сверять код с руководством по эксплуатации, а не с распечаткой кода.
Что бы я сделал иначе
Начал бы с OCR и реестров, а не с чтения. Первые дни я читал документы «как книгу», составляя общее впечатление. Полезной работой стало только построение реестров — начинать надо было сразу с них.
Раньше сделал бы симулятор ПЛК. Он появился ближе к концу, когда HMI уже был написан. Существуй он с самого начала, часть интеграционных дефектов (те же 34 регистра вместо 54) обнаружилась бы сразу.
Сразу автоматизировал бы проверку карты регистров на коллизии. HR220/221 я нашёл глазами, и это удача. Проверка уникальности назначений пишется за полчаса и должна была появиться в первый же день существования карты. Туда же — проверка тождественности правимого и компилируемого файла: полдня на ложный след это её цена.
Сообщал бы о расхождениях сразу, а не пакетом. Первые находки я копил, чтобы предъявить единым перечнем. Это ошибка коммуникации: про таймер на 1 200 А надо было сообщать в тот же час, а не в составе отчёта.
Не переоценивал бы «оно же работает». Я потратил заметное время, объясняя себе, что расхождения могут быть не ошибками, а сознательными решениями предшественников. По части — правда, по критическим четырём — нет.
Выводы
Бумажный комплект документации — не формальность. Именно распечатки, сданные когда-то по требованию приёмки, позволили восстановить систему. Если есть выбор между осмысленной распечаткой и отпиской, сдавайте осмысленную.
Эталоном должно быть требуемое поведение, а не существующая реализация. Это дало четыре критические находки, включая тридцатикратное превышение допустимого времени удержания максимального тока.
Data-driven архитектура окупается на объекте. Ступени и циклограммы данными в retain-DB означают, что настройка стенда не требует ни TIA Portal, ни перепрошивки, ни программиста.
Работающая система ничего не доказывает про сценарии, которые не случались. Пять минут вместо десяти секунд прожили в эксплуатации ровно потому, что операторы были дисциплинированы. Защита, работающая за счёт дисциплины персонала, защитой не является.
Итог одной строкой: 4 900 страниц эталона, 17 функций нагрузки, 13 аварий, 6 циклограмм, 27 ступеней (проверено 9), 95 блоков оригинала против 23 программных единиц новой реализации, 2 156 строк SCL, 19 экранов веб-HMI, 60 из 60 совпавших каналов I/O, 7 расхождений, 4 критических, 0 ошибок при сборке.
Обновления
Раздел заведён заранее и пуст. Найдёте расхождение между тем, что здесь написано, и тем, что следует из цифр, — напишите в комментариях: правка появится здесь с датой и вашим ником. В прошлый раз этот раздел собрал шесть пунктов, и статья от этого стала лучше.
Методическая часть применима далеко за пределами стендов, и обсудить мне интереснее всего именно её.
Вопрос к читателям: если на объекте работающая система расходится с документацией — что вы считаете эталоном и почему? Мне встречались обе позиции, обе аргументированные.
Комментарии (6)

optemist
26.08.2026 22:31Пробежал текст, как АСУТПшник с 15 летним опытом могу сказать - Вы создали монстра который будет довольно сложно поддерживать если только не сами будете его фиксить... Cross reference утерян из за не прямой адресации, так же я бы рекомендовал вывести циклы индексации в отдельное прерывания исполняемое с постоянной цикличностью. Так же надеюсь в DB с поинтерами не используется temp переменные, они могут просачиваться между циклами.

toolowinmydev
26.08.2026 22:31По мере прочтения сразу становилось понятно, что не только текст писали при помощи ллм. Я, как человек учившийся работать с тиа портал и автоматизацией при помощи ИИ, сразу увидел, что чуть ли не все решения как из под учебника были предложены моделью. Потомучто сам не раз с ними сталкивался и переучивал, т.к. есть более правильные, простые и уже общепринятые пути решения задачи. Сейчас нет сил всё расписывать, т.к. сижу тридцатый час на производстве, но по ощущениям, там, где нужно было использовать ИИ - вы думали сами, а там где надо было делать самому - делегировали агенту. Даже в этом случае, польза принесенная им, конечно, очень велика и я ничего против этого не имею, но всё же вы действительно всё очень усложнили (иишки в целях безопасности и непонимания контекста чересчур сильно всё усложняют в этом деле). Будущему рядовому специалисту, который возможно придет что-то переделывать или менять или ещё черт знает что, будет сложновато разобраться, даже с такой, казалось бы подробной описательной базой и подготовленностью. Всё же соглашусь с другим комментатором, вы создали монстра. В этой сфере нужно с ИИ быть аккуратнее, и чуть ли не полностью контролировать процесс и направлять его, а не наоборот. Но тут бесспорно, вы явно сделали лучше, чем было до этого, по описанию прочитанному, это просто дикий ужас и позорище, а не ПО для стенда было
Lev3250
Честно начал читать, но иишность текста просто не даёт продвинуться дальше нескольких абзацев.
Меня этот клод слог бесит. Чатгпт намного более человечно разговаривает, особенно после доп инструкций.