Каталог решений - Пример реализации обмена с оборудованием через winsocket на управляемых формах по протоколу TCP

Пример реализации обмена с оборудованием через winsocket на управляемых формах по протоколу TCP

Пример реализации обмена с оборудованием через winsocket на управляемых формах по протоколу TCP

В наличии

Объединяя опыт коллег с данного ресурса, хочу поделиться своим решением с подключением контроллера контроля доступа к 1С УНФ. Здесь описан метод без использования ActiveX, который не работает на УФ.

Категория:

Описание

Задача была по поводу наладки обмена информацией между 1С УНФ и контроллера управления доступа клиентов. Имеется описание протокола для связи с контроллером по TCP. Контроллер может работать в режиме сервера, прослушивая определенный порт принимая с него команды и отправляя обратно ответы. Решил использовать WinSocket, благо информации много.

Вот неплохая публикация, где рассматриваются режимы клиента и сервера с использованием ActiveX://sale.itcity.ru/public/119982/

Одна беда — УФ. ActiveX  — не поддерживается.

Итак приступим.

Прикручиваем к УФ
ActiveX не прижился на УФ. Метод с офисом, который рассматривался здесь — сложноват, поэтому подключаем Winsocket как COMобъект.

Как же его хранить? Будем хранить его в параметрах сеанса. Оттуда он доступен и клиенту и серверу. 

Процедура ПриОткрытииНаСервере()
    Контроллер = Новый COMОбъект("mswinsock.winsock"); 
    ПараметрыКонтроллера = Новый Структура("Адрес, Порт");
    ПараметрыКонтроллера.Адрес = "xxx.xxx.xxx.xxx";
    ПараметрыКонтроллера.Порт = "pppp";
    
    СтруктОбъекта = Новый Структура("Контроллер",Контроллер);
    ПараметрыСеанса.Z5=ПоместитьВоВременноеХранилище(СтруктОбъекта,Новый УникальныйИдентификатор());
    ИнициализироватьПодключение(ПараметрыКонтроллера);
КонецПроцедуры

Заметим, что COMобъект мы спрятали в структуру. Как уже обсуждалось на Инфостарте, COMобъекты не поддерживают сериализацию. А если «спрятать» его в структуру, ошибок не будет:

 СтруктОбъекта = Новый Структура("Контроллер",Контроллер);
    ПараметрыСеанса.Z5=ПоместитьВоВременноеХранилище(СтруктОбъекта,Новый УникальныйИдентификатор());

Рассмотрим подключение. Отдельное спасибо Андрею, автору публикации: //sale.itcity.ru/public/119982/ и участникам обсуждения за знакомство с winsocket.

&НаСервере
Процедура ИнициализироватьПодключение(ПараметрыПодключения)    
    ws = ПолучитьИзВременногоХранилища(ПараметрыСеанса.Z5).Контроллер;
    Если ws.State <> 7 тогда
         Если ws.State <> 0 Тогда
            ws.Close();
        КонецЕсли;
        
        ws.RemoteHost = СокрЛП(ПараметрыПодключения.Адрес);
        ws.RemotePort = СокрЛП(ПараметрыПодключения.Порт);
        ws.Connect();
    Иначе
        ws.Close();
        
        ws.RemoteHost = СокрЛП(ПараметрыПодключения.Адрес);
        //        ws.RemoteHostIP = СокрЛП(ПараметрыПодключения.Адрес);
        ws.RemotePort = СокрЛП(ПараметрыПодключения.Порт);
        
        ws.Connect();
    КонецЕсли;
    Объект.Статус = ws.state; //Сохраняем статус чтобы отображать его на форме
    
КонецПроцедуры

Все, подключились. При открытии формы нашей обработки мы хотим получить некоторые данные с контроллера.

Работа с контроллером будет происходить асинхронно. Отправляем запрос, затем ждем ответ. Дождавшись обрабатываем его и отправляем новый запрос и т.д.

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

&НаКлиенте
Процедура ПриОткрытии(Отказ) 

//Мы будем использовать сканер и считыватель
	ИспользоватьПодключаемоеОборудование=Истина;
	// ПодключаемоеОборудование
	МенеджерОборудованияКлиентПереопределяемый.НачатьПодключениеОборудованиеПриОткрытииФормы(ЭтаФорма, "СканерШтрихкода,СчитывательМагнитныхКарт");
	// Конец ПодключаемоеОборудование
//--------------------------------------------
	ПриОткрытииНаСервере();
	
	//Заблокируем форму от изменений
	УправлениеДоступностью(Ложь);
	
	//Пошлем первую команду на получение списка контроллеров
	ПодключитьОбработчикОжидания("ПолучитьСтатус",10);
	Объект.ТипКоманды = "20";
	Объект.Команда = "ПолучитьАдресКонтроллера";
	Объект.ТекстСообщения = "00 08 08 01 00 00 00 00";
	ПодключитьОбработчикОжидания("ВыполнитьКоманду",1);
	
	//Подключим периодический опрос контроллеров
	Объект.НомерВыполняемойКомандыОпроса = 1;
	ПодключитьОбработчикОжидания("ПериодическийОпрос",1);   //здесь могут крутиться несколько команд, которые получают события и отправляют изменения
	
КонецПроцедуры

Сама процедура отправки команды, которая в нашем случае вызывается многократно 1 раз в секунду, на случай, если контроллер чем-то занят.

&НаКлиенте
Процедура ВыполнитьКоманду()
    
    ПолучитьСтатус();
    Элементы.Статус.Заголовок = Объект.Статус;
    Если Объект.Статус = 0 Тогда   //Не подключено 
        ПриОткрытииНаСервере();
        Возврат;
    ИначеЕсли Объект.Статус = 6 Тогда // еще идет подключение
        Возврат;
    КонецЕсли;
    УправлениеДоступностью(Ложь); //ничего нельзя делать на форме
    Результат = ОтправитьДанные();   // Ниже функция отправки
    Элементы.Статус.Заголовок = Объект.Статус;
    Если Результат Тогда
        Объект.Лог = Формат(ТекущаяДата(),"ДЛФ=DT") + ": " + Объект.Команда +" --> " + Символы.ПС + Объект.Лог;
        Объект.Лог = Объект.ТекстСообщения + Символы.ПС + Объект.Лог;
        ОтключитьОбработчикОжидания("ВыполнитьКоманду"); //Команда выполнилась, отключаем этот обработчик
        ПодключитьОбработчикОжидания("ПолучитьОтвет",1); //Подключаем ожидание ответа Многократно    
    Иначе
        Если Объект.Статус = 9 Тогда //ошибка, не расшифровать нафиг
            ПриОткрытииНаСервере(); //реконнект (кстати не помогает. Только отключение кабеля)
        КонецЕсли;
    КонецЕсли;       
    
КонецПроцедуры

Функцию ОтправитьДанные() мы рассмотрим ниже, а пока из кода видно, что после отправки команды на контроллер мы подключаем второй обработчик ожидания на получение ответа контроллера. ActiveX winsocket, который мы в силу обстоятельств не можем использовать, имеет очень важное событие WinSocketDataArrival, которое возникает при получении данных. Нам придется обходиться без него. Мы просто 1 раз в секунду будем читать возможно полученные ответы.

&НаКлиенте
Процедура ПолучитьОтвет()
    Элементы.Статус.Заголовок = Объект.Статус;
    Результат = ПолучитьОтветНаСервере(); //ф-я ниже    
    Элементы.Статус.Заголовок = Объект.Статус;
    Если Результат Тогда
        ОтключитьОбработчикОжидания("ПолучитьОтвет"); //ответ получен, отключаем обработчик
        Объект.Лог = Формат(ТекущаяДата(),"ДЛФ=DT") + ": " +Объект.Команда +" <-- " + Символы.ПС + Объект.Лог;
        Объект.Лог = Объект.ОтветКонтроллера + Символы.ПС + Объект.Лог;
        ВыполнитьОбработкуОтвета(); // Здесь много условий которые получают название команды и соответственно ее обрабатывают
    Иначе
        ПриОткрытииНаСервере();  //реконнект (не знаю, зачем я его делаю))))
    КонецЕсли;       
    
КонецПроцедуры

Собственно функция отправки:

&НаСервере
Функция ОтправитьДанные()
    Результат = Ложь;
    Контроллер = ПолучитьИзВременногоХранилища(ПараметрыСеанса.Z5).Контроллер; //получили winsocket из параметров сеанса
    buff = ПреобразоватьДляОтправки(Объект.ТекстСообщения);  // преобразование в массив HEX требуется для моего контроллера
    buff[0] = "&H" + Объект.ТипКоманды;
    ВОтправку = "";
    Для н = 0 По buff.Количество()-1 Цикл
        ВОтправку =ВОтправку + buff[н] + " ";    
    КонецЦикла;

    Объект.Статус = Контроллер.State;
    
    Если Контроллер.State = 7 тогда //Подключились удачно, отправляем данные
        Попытка
            Для н = 0 По buff.Количество()-1 Цикл
                Контроллер.SendData(Chr(buff[н])); // отправляю посимвольно из буфера  Chr(Код) - функция с VBScript которая преобразует из HEX в символ.
            КонецЦикла;
            Результат = Истина;
        Исключение
        КонецПопытки;
      КонецЕсли;    
    Возврат Результат;    
КонецФункции

..и функции получения ответа, которые вызываются ежесекундным обработчиком получения ответов контроллера:

&НаСервере
Функция ПолучитьОтветНаСервере()
	Результат = "";
	Попытка
		Результат = ПолучитьДанныеСКонтроллера();
		Объект.ОтветКонтроллера = Результат;
		Возврат Истина;
	Исключение
		Возврат ложь;
	КонецПопытки;
КонецФункции

&НаСервере
Функция ПолучитьДанныеСКонтроллера()
	Контроллер = ПолучитьИзВременногоХранилища(ПараметрыСеанса.Z5).Контроллер;
	Ответ = "";
	Пока Истина Цикл
		Результат = "";
		Контроллер.GetData(Результат);
		Если НЕ Результат = "" Тогда
			Ответ = Ответ + Результат;
		Иначе
			//Преобразовать
			БуферПриема = СтрокуВМассивы(Ответ);
			Масс = БуферПриема[0];
			м4 = Неопределено;
			Результат = Преобразовать5_4(Масс,М4);
			Расшифровано = "";
			Для н = 0 По м4.Количество()-1 Цикл
				Расшифровано = Расшифровано + М4[н] + " ";
			КонецЦикла;
			Объект.ОтветКонтроллера = Расшифровано;
			Прервать;
		КонецЕсли;
	КонецЦикла;	
	Возврат Объект.ОтветКонтроллера;
КонецФункции


Так, что мы уже видим, что чтобы отправить команду на контроллер мы должны выполнить следующий код:

Объект.ТипКоманды = "20";
    Объект.Команда = "ПолучитьАдресКонтроллера";
    Объект.ТекстСообщения = "00 08 08 01 00 00 00 00";
    ПодключитьОбработчикОжидания("ВыполнитьКоманду",1);

Дальше все выполнит пара обработчиков ожидания «ВыполнитьКоманду» и «ПолучитьОтвет».

По окончании выполнения команды и получения ответа контроллера будет вызвана процедура ВыполнитьОбработкуОтвета(), которая выполнит требуемые действия с полученным ответом. Не секрет, что некоторые команды зависят от предыдущих. Например, в моем случае для получения всех карт зашитых в контроллер требуется выполнить следующие действия:

1. Запросить адрес последней карты. Выяснить сколько байт занимают все карты, разбить на блоки по 64 байта (1 условный блок хранения в контроллере)

2. Запросить 1 блок

3. Получить ответ — обработать

4. Запросить 2 блок — обработать

……..

n. Запросить неполный последний блок

n+1. Обработать.

В этом случае в процедуре ВыполнитьОбработкуОтвета() мы зацикливаем выполнение команд, прямо в этой процедуре запуская на выполнение новую команду.

Функция обработки полученных ответов:

&НаКлиенте
Процедура ВыполнитьОбработкуОтвета()
//    БуферПриема = СтрокуВМассивы(Объект.ОтветКонтроллера);
    Если Объект.Команда = "ПолучитьАдресКонтроллера" Тогда
        М4 = ОтветВМассив();
        ФлагПриема = Истина;
        //Расшифруем полученные данные
        i = 8;
        Пока i < 20 Цикл
            текАдр = АдресКонтроллера(М4[i], i - 8);
            Если ТекАдр.Количество()>0 Тогда
                Для Каждого Эл Из ТекАдр Цикл
                    Объект.СписокКонтроллеров.Добавить(Эл.Значение);
                КонецЦикла;
            КонецЕсли;
            i = i + 1;
        КонецЦикла;
        Объект.Команда = "";
    ИначеЕсли Объект.Команда = "СписокКарт" Тогда
..........................................
    КонецЕсли;
    УправлениеДоступностью(Истина);    
КонецПроцедуры

Почему все так сложно? Дело в том, что SendData() отправляет только когда прекращается активность. Объясню.

Контроллер.sendData("что-то-там");
текВремя  = ТекущаяДата();
Пока Истина Цикл
            Если ТекущаяДата() > текВремя + 10;//Цикл задержки 10 секунд Тогда
                 Прервать;
            КонецЕсли;
КонецЦикла;
Получаем = Контроллер.GetData();

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

Итак в заключении.

Предложенный вариант не претендует на оригинальность, но зато работает. Медленно, но работает.

Надеюсь, что мой опыт кому-нибудь пригодится.

Однако, есть мнение. Кому не жалко немного времени, создать ВК с методами:
1. Подключить(Адрес,Порт) Возвращает Истина, Ложь
2. ОтправитьСтроку(Строка) Возвращает Истина Ложь
3. ПрочитатьОтвет() Возвращает ответы, накопленные в буфере Возможен вариант, В какой-либо переменной появляется ответ, который на стороне 1С можно прочитать и/или удалить, если прочитали
4. Отключить 

Там строк 50 текста будет, которые повысят стабильность, избавят от асинхронности в 1С, которая требует обработчиков, которые работают 1 раз в секунду и замедляют общение с устройствами.

Понимаю, что можно было организовать иначе, но основное требование — минимум изменений. Добавлена в конфигурацию только: переменная Z5 в параметры сеансов.

Если есть что сказать, присоединяйтесь к общению. Я мог что-то упустить. 1С на месте не стоит.

Видео с примером работы с контроллером из 1С.

has been added to your cart:
Оформление заказа