Как реализовать опрос весов и датчиков COM-порта в 1С 7.7 без зависания интерфейса?

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

При автоматизации рабочих мест на платформе 1С:Предприятие 7.7 часто возникает задача динамического считывания данных с внешнего оборудования: электронных весов, сканеров или контроллеров температуры через последовательный интерфейс (RS-232 / COM-порт). Когда опрос выполняется по схеме «отправить команду — подождать ответ — прочитать данные», задержки на ожидание сигнала приводят к регулярному зависанию диалоговых форм. Если процедура опроса запускается каждые 1–2 секунды, оператор физически не может непрерывно вводить текст в табличные части или поля документов. Разберем архитектурные причины этой проблемы и подробно рассмотрим практические способы ее решения.

Причины зависания формы и сброса ввода в 1С 7.7

Проанализируем, почему типовой синхронный опрос оборудования нарушает работу пользователя:

  1. Однопоточная архитектура среды исполнения: В 1С 7.7 ядро системы, виртуальная машина встроенного языка и отрисовка оконных элементов управления выполняются строго в рамках одного системного потока (Single-Threaded Apartment). Вызов любых функций ожидания вроде глСистема.Уснуть(), циклов Пока ... Цикл с замером времени или функции Sleep полностью останавливает обработку очереди сообщений Windows. В эти миллисекунды окно перестает реагировать на действия пользователя.
  2. Сброс каретки и выделение текста: Если опрос выносится во внешнюю компоненту, генерирующую ВнешнееСобытие(), либо в штатную процедуру ОбработкаОжидания(), при срабатывании обработчика платформа выполняет валидацию и перерисовку текущего активного элемента диалога. Стандартное текстовое поле Windows при получении таких системных команд выделяет весь набранный текст целиком, и следующее нажатие клавиши приводит к стиранию ранее введенных символов.
  3. Особенности работы в RDP-сессиях: При постоянном циклическом открытии и закрытии дескриптора порта через RDP-клиент виртуальный порт склонен самопроизвольно отваливаться (особенно при использовании переходников USB-to-COM). Дескриптор порта должен удерживаться открытым на протяжении всей сессии.

Способ 1. Организация опроса через конечный автомат без пауз (штатные средства)

Самая распространенная ошибка — использование жесткой паузы внутри одной итерации между отправкой команды в порт и чтением буфера. Разберем, как избавиться от зависаний штатными средствами платформы с помощью разделения процессов на фазы. Мы отправляем запрос в порт на одном шаге, а проверяем наличие байтов в буфере на следующем, не блокируя поток оператора.

Посмотрим на практическую реализацию логики конечного автомата в глобальном модуле:


Перем глВесы;
Перем глВес;
Перем глЖдемПоказаний;

Процедура глОпросВесов()
    Если глВесы.PortOpen = 0 Тогда
        Возврат;
    КонецЕсли;

    // Шаг 1: если ответ уже ожидается, проверяем наполнение входного буфера
    Если глЖдемПоказаний = 1 Тогда
        Если глВесы.InBufferCount >= 13 Тогда
            Буфер = СокрЛП(глВесы.Input);
            // Извлекаем полезные данные веса из пакета
            пВес = Сред(Буфер, 2, 8);
            глВес = Число(пВес);
            глЖдемПоказаний = 0;
        КонецЕсли;
    КонецЕсли;

    // Шаг 2: если порт свободен, отправляем следующую команду запроса
    Если глЖдемПоказаний = 0 Тогда
        глВесы.Settings = "19200,N,8,1";
        // Отправка команды P и символа возврата каретки (CR)
        глВесы.Output = Симв(080) + Симв(013);
        глЖдемПоказаний = 1;
    КонецЕсли;
КонецПроцедуры

Процедура НачатьМониторингВесов()
    глЖдемПоказаний = 0;
    // Штатная процедура запускает выполнение процедуры каждые N секунд
    ОбработкаОжидания("глОпросВесов", 1);
КонецПроцедуры

Разберем важный нюанс вывода данных: чтобы динамический вес отображался на форме без принудительного вызова Форма.Обновить() (который сбивает курсор оператора), на форме документа размещается текстовое поле, в формуле которого указывается вызов глобальной функции:


Функция глПолучитьТекущийВес()
    Возврат глВес;
КонецФункции

При таком подходе значение в текстовом поле будет актуализироваться при естественных интерфейсных событиях без зависания основного интерфейса ввода.

Способ 2. Реализация опроса через класс «Таймер» библиотеки FormEx

Штатная ОбработкаОжидания() в 1С 7.7 имеет дискретность не менее 1 секунды и может быть назначена только одна на всю информационную базу. Если требуется опрашивать весы чаще (например, каждые 300–500 мс), используем класс Таймер внешней компоненты FormEx. При этом логика проверки состояния также должна оставаться неблокирующей.

Рассмотрим пример кода в модуле формы документа:


Перем оТаймер;
Перем фБлокировкаТаймера;
Перем нСчетчикТаймаута;
Перем СостояниеОпроса; // "Старт", "Ожидание"

Процедура ОбработкаТаймера()
    // Защита от повторного входа, если предыдущий шаг еще выполняется
    Если фБлокировкаТаймера = 1 Тогда
        Возврат;
    КонецЕсли;
    фБлокировкаТаймера = 1;

    Если СостояниеОпроса = "Старт" Тогда
        // Очищаем буфер и посылаем запрос веса
        Весы.Output = Симв(080) + Симв(013);
        СостояниеОпроса = "Ожидание";
        нСчетчикТаймаута = 10; // Лимит тактов ожидания
    ИначеЕсли СостояниеОпроса = "Ожидание" Тогда
        Если Весы.InBufferCount >= 13 Тогда
            Буфер = СокрЛП(Весы.Input);
            ТекущийВесНаФорме = Число(Сред(Буфер, 2, 8));
            СостояниеОпроса = "Старт";
        Иначе
            нСчетчикТаймаута = нСчетчикТаймаута - 1;
            Если нСчетчикТаймаута <= 0 Тогда
                // Таймаут ответа: сбрасываем состояние и пробуем снова
                СостояниеОпроса = "Старт";
            КонецЕсли;
        КонецЕсли;
    КонецЕсли;

    фБлокировкаТаймера = 0;
КонецПроцедуры

Процедура ПриОткрытии()
    СостояниеОпроса = "Старт";
    фБлокировкаТаймера = 0;
    нСчетчикТаймаута = 0;
    
    // Инициализация таймера FormEx
    оТаймер = СоздатьОбъект("Таймер");
    оТаймер.ЗадатьОбработчикВМодулеФормы(Контекст, "ОбработкаТаймера");
    оТаймер.Запустить(500); // Опрос каждые 500 миллисекунд
КонецПроцедуры

Процедура ПриЗакрытии()
    Если ПустоеЗначение(оТаймер) = 0 Тогда
        оТаймер.Остановить();
    КонецЕсли;
КонецПроцедуры

Способ 3. Вынос опроса в отдельный системный поток (Внешняя компонента)

Для гарантированного устранения влияния задержек ввода-вывода низкоуровневая работа с COM-портом выносится в отдельный рабочий поток (worker thread) внешней компоненты на C++, Delphi или .NET.

Разберем правильный архитектурный паттерн взаимодействия компоненты и 1С 7.7:

  1. Фоновый поток библиотеки монопольно открывает последовательный порт один раз при старте системы и удерживает дескриптор открытым, исключая сбои перенаправления в RDP.
  2. Поток выполняет цикл: отправка команды в порт, ожидание байтов до символа-терминатора (например, CR — код 0x0D) с заданным таймаутом, чтение строки и запись результата в защищенную критической секцией переменную памяти.
  3. Поток засыпает на заданный интервал (например, 200–500 мс) средствами операционной системы, никак не затрагивая интерфейс 1С.
  4. Внутренняя логика компоненты не генерирует событие ВнешнееСобытие() на каждый такт опроса, чтобы не вызывать сброс курсора ввода на форме 1С.
  5. Платформа 1С обращается к компоненте синхронно через вызов свойства или метода (например, ВК.ПолучитьВес()) только тогда, когда это действительно необходимо: по нажатию кнопки «Зафиксировать вес» или при переходе на новую строку.

Посмотрим на пример вызова подобной компоненты в коде конфигурации:


// Инициализация при старте рабочего места
// Параметры: Порт, Настройки (Скорость, БитДанных, Четность, СтопБит), Таймаут, Интервал, Команда, Терминатор
Если ЗагрузитьВнешнююКомпоненту("vkcomm.dll") = 1 Тогда
    ВК = СоздатьОбъект("AddIn.commScales");
    // Настройка: 19200 бод, 8 бит данных, без четности, 1 стоп-бит, ожидание символа CR
    ВК.Старт("COM1", "19200,8,N,0", 5000, 500, Симв(080) + Симв(013), Симв(013));
КонецЕсли;

// Получение данных по кнопке на форме без задержек и подвисаний
Процедура ПоКнопкеПолучитьВес()
    СтрокаОтвета = ВК.ПолучитьТекущийВес();
    // Обработка полученной строки до нужной разрядности
    ТабличнаяЧасть.Вес = Число(СокрЛП(СтрокаОтвета));
КонецПроцедуры

Альтернативные архитектурные решения

Если доработка конфигурации ограничена или на рабочем месте используется комплекс разнородных датчиков (весы, RFID-сканеры, датчики температуры), проанализируем альтернативные способы интеграции:

  1. Переключение весового терминала в потоковый режим (Stream Mode): Большинство промышленных весовых контроллеров поддерживают режим постоянной трансляции. В этом режиме прибор сам непрерывно передает данные в порт несколько раз в секунду. Программе в 1С не требуется посылать управляющие команды и ждать ответа: достаточно в любой момент просто вычитать последний актуальный кадр из буфера драйвера.
  2. Локальная фоновая служба (Windows Service): Создание независимого сервиса на рабочем месте, который ведет постоянный опрос датчиков. 1С опрашивает службу локально через HTTP-запрос или именованный канал (Named Pipe). Это полностью изолирует нестабильность внешних устройств от бизнес-логики программы.
  3. Внешнее плавающее окно индикации: Если оператору необходимо постоянно видеть живой вес на мониторе, создается компактное окно индикации средствами внешней библиотеки. Окно создается с флагом WS_EX_NOACTIVATE, располагается поверх окна 1С и обновляет показания в реальном времени, физически не перехватывая фокус клавиатуры у редактируемых документов.
← На главную