Работа с торговым, медицинским и промышленным оборудованием через последовательный интерфейс RS-232 (COM-порт) — классическая задача в практике разработчика 1С. Однако при переходе на современные операционные системы (Windows 10/11 x64) и платформу 1С:Предприятие 8.3 (особенно при использовании 64-битного клиента) старые проверенные инструменты начинают давать сбои. Популярные ранее библиотеки, такие как MSCOMM32.ocx или rs232.dll, либо отказываются регистрироваться в системе, либо требуют прав администратора, либо работают нестабильно, дублируя принимаемые данные.
Особую сложность представляют протоколы обмена, требующие аппаратного или программного подтверждения связи (квитирования) с использованием управляющих символов ASCII. Например, устройство отправляет запрос связи ENQ (Enquiry, код 0x05), на который программа 1С должна незамедлительно ответить сигналом подтверждения приема ACK (Acknowledge, код 0x06). Если этого не сделать вовремя, устройство прекратит передачу данных.
В этой статье мы подробно разберем четыре практических метода реализации надежного обмена данными с COM-портом в 1С 8.3, поддерживающих отправку любых ASCII и HEX-команд на современных операционных системах x64.
Мало кто знает, что операционная система Windows позволяет обращаться к COM-портам как к обычным файлам. Мы можем использовать этот механизм непосредственно в платформе 1С:Предприятие, задействовав стандартные объекты ЗаписьТекста и ЧтениеТекста.
Главная проблема этого подхода заключается в том, что стандартные файловые объекты 1С не имеют инструментов для настройки физических параметров порта: скорости передачи (Baud rate), четности (Parity), количества бит данных и стоповых бит. Чтобы обойти это ограничение, мы можем использовать системную утилиту Windows mode, предварительно настроив порт через командную строку.
Рассмотрим по шагам пример отправки управляющего символа ENQ (код 5) и последующего чтения ответа из порта COM5:
// 1. Инициализируем параметры COM-порта в операционной системе
ИмяПорта = "COM5";
ПараметрыНастройки = "baud=9600 parity=N data=8 stop=1";
ЗапуститьПриложение("mode " + ИмяПорта + " " + ПараметрыНастройки, , Истина);
// 2. Открываем порт для отправки управляющей команды ENQ (ASCII 0x05)
ЗаписьПорта = Новый ЗаписьТекста("\\.\" + ИмяПорта, КодировкаТекста.ANSI);
УправляющийСимвол = Символ(5); // Код 5 соответствует символу ENQ
Попытка
ЗаписьПорта.Записать(УправляющийСимвол);
ЗаписьПорта.Закрыть();
Исключение
Предупреждение("Не удалось отправить данные в порт " + ИмяПорта);
Возврат;
КонецПопытки;
// 3. Читаем ответ от устройства
ЧтениеПорта = Новый ЧтениеТекста("\\.\" + ИмяПорта, КодировкаТекста.ANSI);
Попытка
ОтветУстройства = ЧтениеПорта.Прочитать();
ЧтениеПорта.Закрыть();
Если КодСимвола(ОтветУстройства, 1) = 6 Тогда
// Получен символ ACK (ASCII 0x06) - устройство подтвердило готовность
ВыполнитьОбработкуДанных();
КонецЕсли;
Исключение
// Обработка ошибок чтения
КонецПопытки;
Важные нюансы метода:
\\.\COM[НомерПорта]. Это позволяет операционной системе Windows корректно ассоциировать строковый путь с физическим устройством.КодировкаТекста.ANSI. Если использовать UTF-8 или UTF-16, однобайтовый ASCII-символ будет преобразован платформой в многобайтовую последовательность, которую внешнее устройство не сможет распознать.Чтобы избежать блокировки интерфейса и не зависеть от системных утилит настройки портов, рекомендуется использовать внешние компоненты, разработанные по технологии Native API. В отличие от устаревших COM/ActiveX компонентов (таких как MSCOMM32.ocx), Native API компоненты обладают следующими преимуществами:
regsvr32.ОбработкаВнешнегоСобытия.Проанализируем пример подключения такой компоненты и отправки ответа ACK (код 6) при получении внешнего события:
&НаКлиенте
Процедура ПриОткрытии(Отказ)
// Подключаем компоненту из макета конфигурации
ПутьККомпоненте = "ОбщийМакет.КомпонентаCOMПорта";
Если ПодключитьВнешнююКомпоненту(ПутьККомпоненте, "SerialPort", ТипВнешнейКомпоненты.Native) Тогда
ОбъектПорта = Новый("AddIn.SerialPort.PortControl");
// Настраиваем и открываем порт
Если ОбъектПорта.ОткрытьПорт("COM5", 9600, 8, 1, 0) Тогда
Сообщить("Порт успешно открыт и готов к работе.");
Иначе
Сообщить("Ошибка открытия порта!");
КонецЕсли;
Иначе
Сообщить("Не удалось загрузить внешнюю компоненту.");
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура ОбработкаВнешнегоСобытия(Источник, Событие, Данные)
Если Источник = "SerialPort" И Событие = "DataReceived" Тогда
// Проверяем, что прислало устройство
Если Данные = Символ(5) Тогда // Пришел запрос ENQ
// Отправляем ответ ACK (квитирование связи)
ОбъектПорта.ЗаписатьБайт(6);
Иначе
// Обрабатываем полезную нагрузку
ОбработатьДанныеУстройства(Данные);
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Такой подход полностью исключает "зависание" программы и гарантирует мгновенную реакцию 1С на входящие запросы от оборудования.
Если готовой внешней компоненты Native API под рукой нет, а написать собственную на C++ сложно, отличным выходом будет использование стандартных классов .NET Framework, который предустановлен на любой системе Windows 10/11. В пространстве имен System.IO.Ports есть класс SerialPort, который идеально подходит для нашей задачи.
Для интеграции необходимо создать небольшую библиотеку классов (DLL) на C# в среде Visual Studio, обернув вызовы класса SerialPort, и сделать ее видимой для COM-соединения (COM-Interop).
Пример кода на стороне C# для отправки массива байт:
using System;
using System.IO.Ports;
using System.Runtime.InteropServices;
namespace MySerialLibrary
{
[Guid("D1C89F3A-827B-4BFA-9FBE-C674D43F1234")]
[ClassInterface(ClassInterfaceType.AutoDual)]
[ComVisible(true)]
public class SerialManager
{
private SerialPort _port;
public void Open(string portName, int baudRate)
{
_port = new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One);
_port.Open();
}
public void SendByte(byte value)
{
if (_port != null && _port.IsOpen)
{
byte[] buffer = new byte[] { value };
_port.Write(buffer, 0, 1);
}
}
public void Close()
{
if (_port != null && _port.IsOpen)
{
_port.Close();
}
}
}
}
После компиляции библиотеки ее необходимо зарегистрировать на целевой машине под управлением Windows 10 x64. Делать это нужно с помощью утилиты RegAsm.exe соответствующей разрядности. Для 64-битного клиента 1С регистрация выполняется командой:
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe MySerialLibrary.dll /codebase /tlb
После успешной регистрации библиотека становится доступна в среде 1С как обычный COM-объект:
// Создаем объект .NET библиотеки
Порт = Новый COMОбъект("MySerialLibrary.SerialManager");
Порт.Open("COM5", 9600);
// Отправляем команду ENQ (0x05)
Порт.SendByte(5);
// Выполняем действия...
Порт.Close();
Этот способ полностью решает проблему искажения кодировок, так как передача происходит на уровне байтовых массивов напрямую через API операционной системы.
Бывают ситуации, когда развертывание полноценных библиотек или регистрация компонент нецелесообразны (например, в рамках разовой административной задачи или при жестких ограничениях прав доступа). В этом случае мы можем применить "хитрость" — использовать встроенный интерпретатор PowerShell, вызвав его из 1С через объект WScript.Shell.
PowerShell имеет прямой доступ к классам .NET "из коробки" и позволяет инициализировать COM-порт, отправить один или несколько байтов и корректно закрыть соединение всего за одно выполнение. Разберем пример реализации такой схемы:
НомерПорта = "COM5";
СкоростьПорта = 9600;
КодКоманды = 6; // Код подтверждения ACK (0x06)
// Формируем PowerShell команду для открытия порта и отправки точного байта
ШаблонСкрипта = "powershell -NoProfile -ExecutionPolicy Bypass -Command """
+ "$p = New-Object System.IO.Ports.SerialPort '%1', %2, 'None', 8, 'One'; "
+ "$p.Open(); "
+ "$buf = [byte[]](%3); "
+ "$p.Write($buf, 0, 1); "
+ "$p.Close();""";
КомандаВыполнения = СтрШаблон(ШаблонСкрипта, НомерПорта, Формат(СкоростьПорта, "ЧГ=0"), Формат(КодКоманды, "ЧГ=0"));
// Запускаем PowerShell в скрытом режиме без вывода черного окна консоли
ЗапускWsh = Новый COMОбъект("WScript.Shell");
КодВозврата = ЗапускWsh.Run(КомандаВыполнения, 0, Истина);
Если КодВозврата = 0 Тогда
Сообщить("Символ ACK успешно отправлен в " + НомерПорта);
Иначе
Сообщить("Произошла ошибка при выполнении скрипта отправки.");
КонецЕсли;
Преимущества использования PowerShell:
Один из пользователей форума упомянул проблему: "данные приходят по много раз одно и то же". Выясним причину этого явления.
При использовании низкоуровневых драйверов или самописных библиотек буфер приема COM-порта не очищается автоматически после вызова команды чтения. Если программа считывает данные быстрее, чем устройство успевает обнулить свой буфер отправки, или если библиотека считывает данные порциями, не сдвигая указатель чтения, 1С будет повторно получать одну и ту же информацию.
Для предотвращения дублирования данных всегда придерживайтесь следующих правил:
DiscardInBuffer() внешнего объекта).Выбор конкретного метода зависит от архитектуры вашей конфигурации 1С. Для постоянной работы в режиме реального времени лучше всего использовать технологию Native API (Метод 2). Если же требуется быстрая интеграция со сложным промышленным прибором — связка с .NET через COM-Interop (Метод 3) предоставит максимальный контроль над аппаратной частью.