1С
Итак, система состоит из 3 потоков (сеансов):
1. Поток, который управляет МК по ЮСБ (серийный порт).
2. Поток, который управляет МК, подключенными по сети.
3. Основной поток, осуществляющий обработку логической части кода.
В конфигурации есть 2 регистра сведений, через которые происходит управление микроконтроллерами (COM, NET). А в общем модуле "К" есть большая часть стандартных функций, с помощью которых можно напрямую управлять МК, однако время выполнения значительно дольше времени выполнения этих команд в мк.
Поверхностно систему можно описать следующим образом: регламентное задание с небольшим интервалом проверяет заданные в регистре условия для старта процедур, если условие выполняется, то в отдельном потоке запускается указанная процедура. Процедуры могут напрямую работать с оборудованием с помощью вызова спец. функций из модуля "К".
Процедура запускается спустя 5 секунд после завершения предыдущей итерации. Процедура считывает информацию с датчика температуры и если значение меньше 15 то включает обограветель, если больше 20 - выключает его.
-------------------------------
Исходные данные:
МК висит в диспетчере устройств как COM8
Реле включающее обогреватель подключено к пину 3
Датчитк Температуры DHT11 подключен к пину 4
-------------------------------
//Инициализация
К.ОткрытьCOMПорт(8, 115200);
К.pinMode(8, 3, 1); //переводим 3 пин в состояние OUTPUT
К.pinMode(8, 4, 0); //переводим 4 пин в состояние INPUT
//Код который будет в процедуре вызываемой каждые 5 сек и сеанса основного потока
ДанныеТемпературыИВлажности = К._ТемператураВлажностьDHT11(8, 4);
Если ДанныеТемпературыИВлажности.Температура < 15 Тогда
К.digitalWrite(8, 3, 1); // Включаем реле
ИначеЕсли Если ДанныеТемпературыИВлажности.Температура > 20 Тогда
К.digitalWrite(8, 3, 0); // Выключаем реле
КонецЕсли;
В этом разделе описан процесс протекающий между вызовом функции модуля "К" и выполнением действия микроконтроллером. Для использования конфигурации и разработки своих алгоритмов это знать не обязательно.
Описание функции из модуля "К"
1. Вызываемая из модуля "К" функция в зависимости от типа подключения МК делает запись в регистр сведений COM или NET, после чего подключает компоненту из внешнего макета в которой задействует функцию ожидания. С небольшим интервалом времени она проверяет изменения статуса записанной в регистр команды.
2. В другом потоке (том который работает с МК подключенным по ЮСБ или через сеть) постоянно происходит проверка этого регистра на наличие новых поставленных команд. Когда там появляется новая запись он конвертирует 3 поля (котоыре передаются на МК) в шестнадцатиричный вид и отправляет их в серийный порт или на сетевой адрес (в виде 3 пар байт) с номером указанным в записи регистра, к ним дабавляется 4-я пара байт с контрольной суммой. Далее ожидает ответа от МК с помощью той же компоненты ожидания.
3. Тем временм МК получив код проверяет корректность полученных данных и определяет соответствующую команду которую нужно выполнить (в скетче есть стандартные команды, также есть зарезервированные коды для написания своих алгоритмов). После выполнения МК возвращает ответ и статус выполнения (также добавляется пара байт контрольной суммы).
4. Поток работы с МК получает ответ, проверяет контрольную сумму, конвертирует его в 10-ю систему и записывает в регистр COM или NET.
5. Процедура поставившая задачу в регистр видя, что команда выполнена получает запись, конвертирует коды ответа в адаптированный вид и возвращает в процедуру вызвавшую её.
Для контроллеров подключенных по сети возможно отправить дополнительную информацию.
Пример для кода команды pinMode, последовательно вставлены выполняемые процедуры
// pinMode - (2) Перевести ПИН в состояние INPUT или OUTPUT.
// Возвращает: Истина - Команда выполнена, Ложь - Ошибка, Неопределено - Сбой внутри 1С
// Параметры:
// НомерCOMАдресПорт - Число/Строка - Номер ком порта к которому подключен контроллер или сетевой адрес контроллера (Если Число то COM если Строка то NET)
// НомерПина - Число - Номер пина
// Состояние - Число - Состояние в которое нужно перевести 0=INPUT 1=OUTPUT
Функция pinMode(НомерCOMАдресПорт, НомерПина, Состояние) Экспорт
Попытка
СтруктураОтвета = ВыполнитьКоманду(НомерCOMАдресПорт, НомерПина, 2, Состояние);
Если СтруктураОтвета.КомандаВыполнена И СтруктураОтвета.ОтветСтатус = 254 Тогда
Ответ = Истина;
ИначеЕсли СтруктураОтвета.Ошибка ИЛИ СтруктураОтвета.ОтветСтатус <> 254 Тогда
Ответ = Ложь;
Иначе
Ответ = Неопределено;
КонецЕсли;
Возврат Ответ;
Исключение
ОписаниеОшибки = "Сбой при выполнении функции для команды. Описание ошибки: " + ОписаниеОшибки();
ЗаписьВЖурналОшибок("К", "pinMode", ОписаниеОшибки);
Возврат Неопределено;
КонецПопытки;
КонецФункции
// ВыполнитьКоманду - Выполнить указанную команду.
// Возвращает: Структура с ответом, содержащая ключи: ОтветСтатус, ОтветАргумент, ОтветАргумент2, КомандаВыполнена, Ошибка.
// Неопределено - Ошибка формата переменной.
// Параметры:
// НомерCOMАдресПорт - Число/Строка - Номер ком порта к которому подключен контроллер или сетевой адрес контроллера (Если Число то COM если Строка то NET)
// НомерПина - Число - Номер пина
// Команда - Число - Номер команды которую требуется выполнить
// Аргумент - Число - Аргумент для команды
Функция ВыполнитьКоманду(НомерCOMАдресПорт, НомерПина, Команда, Аргумент) Экспорт
Если ТипЗнч(НомерCOMАдресПорт) = Тип("Число") Тогда // Команда в COM порт
Ответ = ВыполнитьКомандуCOM(НомерCOMАдресПорт, НомерПина, Команда, Аргумент);
ИначеЕсли ТипЗнч(НомерCOMАдресПорт) = Тип("Строка") Тогда // Команда на сетевой адрес
Ответ = ВыполнитьКомандуNET(НомерCOMАдресПорт, НомерПина, Команда, Аргумент);
Иначе // Ошибка первой переменной
Ответ = Неопределено;
ОписаниеОшибки = "Ошибка формата переменной ""НомерCOMАдресПорт"". НомерCOMАдресПорт = """ + Строка(НомерCOMАдресПорт) + """, текущая команда = " + Команда;
ЗаписьВЖурналОшибок("К", "ВыполнитьКоманду", ОписаниеОшибки);
КонецЕсли;
Возврат Ответ;
КонецФункции
// Для COM (МК подключенные через ЮСБ или напрямую)
Функция ВыполнитьКомандуCOM(НомерCOM, НомерПина, Команда, Аргумент)
NativeS = NativeПодключитьКомпоненту();
Если NativeS = Неопределено Тогда
Возврат Неопределено;
КонецЕсли;
Запрос = Новый Запрос();
Запрос.Текст = "ВЫБРАТЬ
| COM.Команда
|ИЗ
| РегистрСведений.COM КАК COM
|ГДЕ
| COM.НомерCOMПорта = &НомерCOMПорта
| И COM.НомерПина = &НомерПина
| И (НЕ COM.КомандаВыполнена
| ИЛИ COM.ДатаВыполнения > &ТекущаяДатаМинус5Сек)
| И (НЕ COM.Ошибка
| ИЛИ COM.ДатаВыполнения > &ТекущаяДатаМинус5Сек)";
Запрос.УстановитьПараметр("НомерCOMПорта", НомерCOM);
Запрос.УстановитьПараметр("НомерПина", НомерПина);
Запрос.УстановитьПараметр("ТекущаяДатаМинус5Сек", ТекущаяДата()-5);
// Запрос проверяет есть ли в очереди не выполненные задания
// или задания выполненные в течении последних 5 секунд
ВремяПрекращенияОжидания = ТекущаяДата()+15;
Пока НЕ Запрос.Выполнить().Пустой() Цикл // Ждём завершения заданий
Если ТекущаяДата() > ВремяПрекращенияОжидания Тогда // произошёл сбой
ОписаниеОшибки = "Превышен лимит ожидания выполнения команды из очереди перед текущей. Текущая команда = " + Команда;
ЗаписьВЖурналОшибок("К", "ВыполнитьКомандуCOM", ОписаниеОшибки);
Возврат Неопределено;
КонецЕсли;
NativeS.Задержка(30);
Запрос.УстановитьПараметр("ТекущаяДатаМинус5Сек", ТекущаяДата()-5);
КонецЦикла;
Менеджер = РегистрыСведений.COM.СоздатьМенеджерЗаписи();
Менеджер.НомерCOMПорта = НомерCOM;
Менеджер.НомерПина = НомерПина;
Менеджер.Команда = Команда;
Менеджер.Аргумент = Аргумент;
Менеджер.Записать(Истина);
Запрос = Новый Запрос();
Запрос.Текст = "ВЫБРАТЬ
| COM.Команда
|ИЗ
| РегистрСведений.COM КАК COM
|ГДЕ
| COM.НомерCOMПорта = &НомерCOMПорта
| И COM.НомерПина = &НомерПина
| И (COM.КомандаВыполнена
| ИЛИ COM.Ошибка)";
Запрос.УстановитьПараметр("НомерCOMПорта", НомерCOM);
Запрос.УстановитьПараметр("НомерПина", НомерПина);
// Запрос Проверяет выполнена ли наше задание
ВремяПрекращенияОжидания = ТекущаяДата()+15;
Пока Запрос.Выполнить().Пустой() Цикл // Ждём завершения задания
Если ТекущаяДата() > ВремяПрекращенияОжидания Тогда // произошёл сбой
ОписаниеОшибки = "Превышен лимит ожидания выполнения команды. Текущая команда = " + Команда;
ЗаписьВЖурналОшибок("К", "ВыполнитьКомандуCOM", ОписаниеОшибки);
Возврат Неопределено;
КонецЕсли;
NativeS.Задержка(30);
КонецЦикла;
Менеджер = РегистрыСведений.COM.СоздатьМенеджерЗаписи();
Менеджер.НомерCOMПорта = НомерCOM;
Менеджер.НомерПина = НомерПина;
Менеджер.Прочитать();
СтруктураОтвета = Новый Структура;
СтруктураОтвета.Вставить("ОтветСтатус", Менеджер.ОтветСтатус);
СтруктураОтвета.Вставить("ОтветАргумент1", Менеджер.ОтветАргумент1);
СтруктураОтвета.Вставить("ОтветАргумент2", Менеджер.ОтветАргумент2);
СтруктураОтвета.Вставить("КомандаВыполнена", Менеджер.КомандаВыполнена);
СтруктураОтвета.Вставить("Ошибка", Менеджер.Ошибка);
Менеджер.Удалить();
Возврат СтруктураОтвета;
КонецФункции
// Для NET (МК подключённые по сети/вайфай)
Функция ВыполнитьКомандуNET(АдресПорт, НомерПина, Команда, Аргумент)
NativeS = NativeПодключитьКомпоненту();
Если NativeS = Неопределено Тогда
Возврат Неопределено;
КонецЕсли;
Запрос = Новый Запрос();
Запрос.Текст = "ВЫБРАТЬ
| NET.Команда
|ИЗ
| РегистрСведений.NET КАК NET
|ГДЕ
| NET.АдресПорт = &АдресПорт
| И NET.НомерПина = &НомерПина
| И (НЕ NET.КомандаВыполнена
| ИЛИ NET.ДатаВыполнения > &ТекущаяДатаМинус5Сек)
| И (НЕ NET.Ошибка
| ИЛИ NET.ДатаВыполнения > &ТекущаяДатаМинус5Сек)";
Запрос.УстановитьПараметр("АдресПорт", АдресПорт);
Запрос.УстановитьПараметр("НомерПина", НомерПина);
Запрос.УстановитьПараметр("ТекущаяДатаМинус5Сек", ТекущаяДата()-5);
// Запрос проверяет есть ли в очереди не выполненные задания
// или задания выполненные в течении последних 5 секунд
ВремяПрекращенияОжидания = ТекущаяДата()+15;
Пока НЕ Запрос.Выполнить().Пустой() Цикл // Ждём завершения заданий
Если ТекущаяДата() > ВремяПрекращенияОжидания Тогда // произошёл сбой
ОписаниеОшибки = "Превышен лимит ожидания выполнения команды из очереди перед текущей. Текущая команда = " + Команда;
ЗаписьВЖурналОшибок("К", "ВыполнитьКомандуNET", ОписаниеОшибки);
Возврат Неопределено;
КонецЕсли;
NativeS.Задержка(30);
Запрос.УстановитьПараметр("ТекущаяДатаМинус5Сек", ТекущаяДата()-5);
КонецЦикла;
Менеджер = РегистрыСведений.NET.СоздатьМенеджерЗаписи();
Менеджер.АдресПорт = АдресПорт;
Менеджер.НомерПина = НомерПина;
Менеджер.Команда = Команда;
Менеджер.Аргумент = Аргумент;
Менеджер.Записать(Истина);
Запрос = Новый Запрос();
Запрос.Текст = "ВЫБРАТЬ
| NET.Команда
|ИЗ
| РегистрСведений.NET КАК NET
|ГДЕ
| NET.АдресПорт = &АдресПорт
| И NET.НомерПина = &НомерПина
| И (NET.КомандаВыполнена
| ИЛИ NET.Ошибка)";
Запрос.УстановитьПараметр("АдресПорт", АдресПорт);
Запрос.УстановитьПараметр("НомерПина", НомерПина);
// Запрос Проверяет выполнена ли наше задание
ВремяПрекращенияОжидания = ТекущаяДата()+15;
Пока Запрос.Выполнить().Пустой() Цикл // Ждём завершения задания
Если ТекущаяДата() > ВремяПрекращенияОжидания Тогда // произошёл сбой
ОписаниеОшибки = "Превышен лимит ожидания выполнения команды. Текущая команда = " + Команда;
ЗаписьВЖурналОшибок("К", "ВыполнитьКомандуNET", ОписаниеОшибки);
Возврат Неопределено;
КонецЕсли;
NativeS.Задержка(30);
КонецЦикла;
Менеджер = РегистрыСведений.NET.СоздатьМенеджерЗаписи();
Менеджер.АдресПорт = АдресПорт;
Менеджер.НомерПина = НомерПина;
Менеджер.Прочитать();
СтруктураОтвета = Новый Структура;
СтруктураОтвета.Вставить("ОтветСтатус", Менеджер.ОтветСтатус);
СтруктураОтвета.Вставить("ОтветАргумент1", Менеджер.ОтветАргумент1);
СтруктураОтвета.Вставить("ОтветАргумент2", Менеджер.ОтветАргумент2);
СтруктураОтвета.Вставить("КомандаВыполнена", Менеджер.КомандаВыполнена);
СтруктураОтвета.Вставить("Ошибка", Менеджер.Ошибка);
Менеджер.Удалить();
Возврат СтруктураОтвета;
КонецФункции
// Подключение компоненты (Используется для задержки)
Функция NativeПодключитьКомпоненту() Экспорт
Попытка
ДрайверЕстьВоВременныхФайлах = (НайтиФайлы(КаталогВременныхФайлов(), "AddInNative.dll").Количество()>0);
ИмяФайлаДравера = КаталогВременныхФайлов()+"AddInNative.dll";
Если ДрайверЕстьВоВременныхФайлах = Ложь Тогда // Создаём
МакетДрайвера = ПолучитьОбщийМакет("AddInNative");
МакетДрайвера.Записать(ИмяФайлаДравера);
КонецЕсли;
ПодключитьВнешнююКомпоненту(ИмяФайлаДравера, "NativeVK", ТипВнешнейКомпоненты.Native);
Native = Новый("AddIn.NativeVK.SomeName");
Возврат Native;
Исключение
ОписаниеОшибки = "При подключении NativeVK произошла ошибка. Описание ошибки: " + ОписаниеОшибки();
ЗаписьВЖурналОшибок("К", "NativeПодключитьКомпоненту", ОписаниеОшибки);
Возврат Неопределено;
КонецПопытки
КонецФункции
Так же этом модуле есть функции работы с портом МК и компьютером на котором запущен сервер 1С:
// ОткрытьCOMПорт - (256) Открыть/Переоткрыть указанный COM порт на указанной скорости.
// Возвращает: Истина - Порт открыт, Ложь - Порт занят либо несуществует, Неопределено - Сбой внутри 1С
// Параметры:
// НомерCOM - Число - Номер ком порта к которому подключен контроллер
// Скорость - Число - Скорость в БИТАХ
Функция ОткрытьCOMПорт(НомерCOM, Скорость) Экспорт
Попытка
СтруктураОтвета = ВыполнитьКоманду(НомерCOM, 0, 256, Скорость);
Если СтруктураОтвета.КомандаВыполнена Тогда
Ответ = Истина;
ИначеЕсли СтруктураОтвета.Ошибка Тогда
Ответ = Ложь;
Иначе
Ответ = Неопределено;
КонецЕсли;
Возврат Ответ;
Исключение
ОписаниеОшибки = "Сбой при открытии порта. Номер порта = " + НомерCOM + ". Описание ошибки: " + ОписаниеОшибки();
ЗаписьВЖурналОшибок("К", "ОткрытьCOMПорт", ОписаниеОшибки);
Возврат Неопределено;
КонецПопытки;
КонецФункции
// ЗакрытьCOMПорт - (257) Закрыть указанный COM порт.
// Возвращает: Истина - Порт Закрыт, Ложь - Ошибка, Неопределено - Сбой внутри 1С
// Параметры:
// НомерCOM - Число - Номер ком порта к которому подключен контроллер
Функция ЗакрытьCOMПорт(НомерCOM) Экспорт
Попытка
СтруктураОтвета = ВыполнитьКоманду(НомерCOM, 0, 257, 0);
Если СтруктураОтвета.КомандаВыполнена Тогда
Ответ = Истина;
ИначеЕсли СтруктураОтвета.Ошибка Тогда
Ответ = Ложь;
Иначе
Ответ = Неопределено;
КонецЕсли;
Возврат Ответ;
Исключение
ОписаниеОшибки = "Сбой при закрытии порта. Номер порта = " + НомерCOM + ". Описание ошибки: " + ОписаниеОшибки();
ЗаписьВЖурналОшибок("К", "ЗакрытьCOMПорт", ОписаниеОшибки);
Возврат Неопределено;
КонецПопытки;
КонецФункции
// ПересоздатьКомпонентуИОткрытьCOMПорт - (258) Закрыть комп порт если открыт,
// пересоздать компоненту и открыть указанный COM порт на указанной скорости.
// Возвращает: Истина - Порт открыт, Ложь - Порт занят либо несуществует, Неопределено - Сбой внутри 1С
// Параметры:
// НомерCOM - Число - Номер ком порта к которому подключен контроллер
// Скорость - Число - Скорость в БИТАХ
Функция ПересоздатьКомпонентуИОткрытьCOMПорт(НомерCOM, Скорость) Экспорт
Попытка
СтруктураОтвета = ВыполнитьКоманду(НомерCOM, 0, 258, Скорость);
Если СтруктураОтвета.КомандаВыполнена Тогда
Ответ = Истина;
ИначеЕсли СтруктураОтвета.Ошибка Тогда
Ответ = Ложь;
Иначе
Ответ = Неопределено;
КонецЕсли;
Возврат Ответ;
Исключение
ОписаниеОшибки = "Сбой при переоткрытии COM порта. Номер порта = " + НомерCOM + ". Описание ошибки: " + ОписаниеОшибки();
ЗаписьВЖурналОшибок("К", "ПересоздатьКомпонентуИОткрытьCOMПорт", ОписаниеОшибки);
Возврат Неопределено;
КонецПопытки;
КонецФункции
// ПроверитьКомпонентуИСвязьCOMПорта - (259) Выполнить проверку наличия компоненты
// для указанного COM порта и (Необязательно) проверить связь с контроллером.
// Возвращает: 1 - Успешно, 2 - Компоненты нет, 3 - Компонента есть, но проверка связи завершилась неудачей,
// Неопределено - Сбой внутри 1С
// Параметры:
// НомерCOM - Число - Номер ком порта к которому подключен контроллер
// НужнаПроверкаСвязи - Булево - По умолчанию Ложь
Функция ПроверитьКомпонентуИСвязьCOMПорта(НомерCOM, НужнаПроверкаСвязи = Ложь) Экспорт
Попытка
СтруктураОтвета = ВыполнитьКоманду(НомерCOM, 0, 259, ?(НужнаПроверкаСвязи, 1, 0));
Если СтруктураОтвета.КомандаВыполнена Тогда
Ответ = СтруктураОтвета.ОтветАргумент1;
Иначе
Ответ = Неопределено;
КонецЕсли;
Возврат Ответ;
Исключение
ОписаниеОшибки = "Сбой при проверки Компоненты/Связи COM порта. Номер порта = " + НомерCOM + ". Описание ошибки: " + ОписаниеОшибки();
ЗаписьВЖурналОшибок("К", "ПроверитьКомпонентуИСвязьCOMПорта", ОписаниеОшибки);
Возврат Неопределено;
КонецПопытки;
КонецФункции
///////////////////////////////////////////////////////////////////////////////////////////////
// ПерезапуститьАгентСервера1С - () Завершает и снова запускает службу Агент сервера 1С, при выполнение завершаются все сеансы
// Параметры:
// Ошибка - Булево - Если процедура вызывается из-за ошибки, по умолчанию ложь (Необязательный)
Процедура ПерезапуститьАгентСервера1С(Ошибка = Ложь) Экспорт
Если Ошибка Тогда
ОписаниеОшибки = "Перезапуск службы агент сервера 1С";
ЗаписьВЖурналОшибок("К", "ПерезапуститьАгентСервера1С", ОписаниеОшибки);
КонецЕсли;
ИмяСценарияПерезапуска = КаталогВременныхФайлов() + "CMDRestartProg.cmd";
СценарийПерезапуска = Новый ЗаписьТекста(ИмяСценарияПерезапуска, КодировкаТекста.ANSI);
СценарийПерезапуска.ЗаписатьСтроку("net stop ""1C:Enterprise 8.3 Server Agent"" && net start ""1C:Enterprise 8.3 Server Agent""");
СценарийПерезапуска.Закрыть();
ИмяСценарияПриложения = КаталогВременныхФайлов() + "CMDRunProg.cmd";
СценарийПриложения = Новый ЗаписьТекста(ИмяСценарияПриложения, КодировкаТекста.ANSI);
СценарийПриложения.ЗаписатьСтроку("mshta ""vbscript:CreateObject(""Shell.Application"").ShellExecute(""" + ИмяСценарияПерезапуска + """, """", """", ""runas"", 1) & Close()""");
СценарийПриложения.Закрыть();
ЗапуститьПриложение(ИмяСценарияПриложения); // останавливает агент сервера 1С, завершаются сеансы
// изменить имя службы = "1C:Enterprise 8.3 Server Agent" на свою
// у пользователя сервера 1С должно быть отключено окно блокировки запуска программ под администатором
КонецПроцедуры
// ПерезагрузитьСервер1С - () Перезагружает компьютер на котором запущен сервер 1С, при выполнение завершаются все сеансы
// Параметры:
// Ошибка - Булево - Если процедура вызывается из-за ошибки, по умолчанию ложь (Необязательный)
Процедура ПерезагрузитьСервер1С(Ошибка = Ложь) Экспорт
Если Ошибка Тогда
ОписаниеОшибки = "Перезагрузка компьютера";
ЗаписьВЖурналОшибок("К", "ПерезагрузитьСервер1С", ОписаниеОшибки);
КонецЕсли;
ИмяСценарияПерезагрузки = КаталогВременныхФайлов() + "CMDRestartAppor.cmd";
СценарийПерезагрузки = Новый ЗаписьТекста(ИмяСценарияПерезагрузки, КодировкаТекста.ANSI);
СценарийПерезагрузки.ЗаписатьСтроку("shutdown /r /t 1");
СценарийПерезагрузки.Закрыть();
ИмяСценарияПриложения = КаталогВременныхФайлов() + "CMDRunAppor.cmd";
СценарийПриложения = Новый ЗаписьТекста(ИмяСценарияПриложения, КодировкаТекста.ANSI);
СценарийПриложения.ЗаписатьСтроку("mshta ""vbscript:CreateObject(""Shell.Application"").ShellExecute(""" + ИмяСценарияПерезагрузки + """, """", """", ""runas"", 1) & Close()""");
СценарийПриложения.Закрыть();
ЗапуститьПриложение(ИмяСценарияПриложения); // останавливает агент сервера 1С, завершаются сеансы
// у пользователя сервера 1С должно быть отключено окно блокировки запуска программ под администатором
КонецПроцедуры
Функции перезапуска агента сервера и перезагрузки сервера реализованы сценариями командной строки (.cmd .bat). Они должны выполняться от имени администратора, поэтому учётная запись под которой работает север 1С должна быть административной и в ней необходимо отключить окно запроса пывышенных привелегий при запуске приложений с правами администратора. (это можно сделать в настройках уровня безопасности, опустив ползунок вниз)
Описание работы сеанса (потока) работающего с COM.
Процедура работы с COM портами находится в модуле "СерверПривилегированный" и представляет из себя вечный цикл с небольшим интервалом между повторениями. Логика работы довольно простая. Постоянно происходит проверка регистра с командами для МК подключенного по COM, если есть команда то отправляет её на МК и дождавшись ответа дописывает соответствующие поля записи регистра. Регистр представлен следующим образом:
--------------------------------------- |
НомерCOMПорта - число, номер порта как в диспетчере устройств НомерПина - число, номер пина для которого будет выполнена команда Команда - число, соответствие команды номеру можно посмотреть в скетче для МК. Так же можно добавлять свои. Аргумент - число передаваемое на МК, обрабатывается в зависимости от функции МК Последние 3 ресурса заполняются полученными с МК данными, для каждой функции они свои. Посмотреть их можно в скетче. Первые 2 реквизиты - булево, последний - дата их назначение очевидно. |
Код:
Процедура ПостоянноВыполняющаясяCOM() Экспорт // эта процедура стартует при запуске регл задания СеансСервера
Перем Native;
NativeS = К.NativeПодключитьКомпоненту(); // Для управления задержкой
Если NativeS = Неопределено Тогда
Возврат;
КонецЕсли;
NativeСтруктура = Новый Структура();
ЗапросНевыполненыхКоманд = Новый Запрос();
ЗапросНевыполненыхКоманд.Текст = "ВЫБРАТЬ
| COM.НомерCOMПорта,
| COM.Команда КАК Команда,
| COM.НомерПина,
| COM.Аргумент
|ИЗ
| РегистрСведений.COM КАК COM
|ГДЕ
| НЕ COM.КомандаВыполнена
| И НЕ COM.Ошибка
|
|УПОРЯДОЧИТЬ ПО
| Команда УБЫВ";
// бесконечный цикл для поддержки открытых COM соединений
Пока Истина Цикл
РезультатКомандыКВыполнению = ЗапросНевыполненыхКоманд.Выполнить();
Если НЕ РезультатКомандыКВыполнению.Пустой() Тогда
ВыборкаКоманды = РезультатКомандыКВыполнению.Выбрать();
Пока ВыборкаКоманды.Следующий() Цикл
НомерCOMПорта = ВыборкаКоманды.НомерCOMПорта;
Команда = ВыборкаКоманды.Команда;
НомерПина = ВыборкаКоманды.НомерПина;
Аргумент = ВыборкаКоманды.Аргумент;
Если Команда = 256 Тогда // Открыть/Переоткрыть COM порт
Если NativeСтруктура.Свойство("Native"+НомерCOMПорта, Native) Тогда
Попытка
Native.Закрыть();
Исключение
ОписаниеОшибки = "При переоткрытии COM порта не удалось его закрыть. Описание ошибки: " + ОписаниеОшибки();
ЗаписьВЖурналОшибок("СерверПривилегированный", "ПостоянноВыполняющаясяCOM", ОписаниеОшибки);
КонецПопытки;
NativeS.Задержка(1500);
Иначе
Native = К.NativeПодключитьКомпоненту();
NativeСтруктура.Вставить("Native"+НомерCOMПорта, Native);
КонецЕсли;
Если NativeОткрытьCOMПорт(Native, НомерCOMПорта, Аргумент)Тогда // успешно
NativeS.Задержка(1500);
ЗаписатьРезультатВыполненияКомандыCOM(НомерCOMПорта, НомерПина, Команда, Аргумент, Неопределено, Истина, Ложь);
Иначе // неудача
ЗаписатьРезультатВыполненияКомандыCOM(НомерCOMПорта, НомерПина, Команда, Аргумент, Неопределено, Ложь, Истина);
КонецЕсли;
ИначеЕсли Команда = 257 Тогда // Закрыть COM порт
Если NativeСтруктура.Свойство("Native"+НомерCOMПорта, Native) Тогда
Попытка
Native.Закрыть();
Native = Неопределено;
NativeСтруктура.Удалить("Native"+НомерCOMПорта);
ЗаписатьРезультатВыполненияКомандыCOM(НомерCOMПорта, НомерПина, Команда, Аргумент, Неопределено, Истина, Ложь);
Исключение
ОписаниеОшибки = "При закрытии COM порта произошла ошибка. Описание ошибки: " + ОписаниеОшибки();
ЗаписьВЖурналОшибок("СерверПривилегированный", "ПостоянноВыполняющаясяCOM", ОписаниеОшибки);
ЗаписатьРезультатВыполненияКомандыCOM(НомерCOMПорта, НомерПина, Команда, Аргумент, Неопределено, Ложь, Истина);
КонецПопытки;
NativeS.Задержка(1500);
Иначе
ОписаниеОшибки = "Не удалось закрыть COM порт т.к. он не открыт (нет в структуре)";
ЗаписьВЖурналОшибок("СерверПривилегированный", "ПостоянноВыполняющаясяCOM", ОписаниеОшибки);
ЗаписатьРезультатВыполненияКомандыCOM(НомерCOMПорта, НомерПина, Команда, Аргумент, Неопределено, Ложь, Истина);
КонецЕсли;
ИначеЕсли Команда = 258 Тогда // Пересоздать компоненту и открыть COM порт
Если NativeСтруктура.Свойство("Native"+НомерCOMПорта, Native) Тогда
Попытка
Native.Закрыть();
Исключение
ОписаниеОшибки = "При пересоздании компоненты COM порта не удалось закрыть порт. Описание ошибки: " + ОписаниеОшибки();
ЗаписьВЖурналОшибок("СерверПривилегированный", "ПостоянноВыполняющаясяCOM", ОписаниеОшибки);
КонецПопытки;
NativeS.Задержка(1500);
КонецЕсли;
Native = Неопределено;
Native = К.NativeПодключитьКомпоненту();
NativeСтруктура.Вставить("Native"+НомерCOMПорта, Native);
Если NativeОткрытьCOMПорт(Native, НомерCOMПорта, Аргумент)Тогда // успешно
NativeS.Задержка(1500);
ЗаписатьРезультатВыполненияКомандыCOM(НомерCOMПорта, НомерПина, Команда, Аргумент, Неопределено, Истина, Ложь);
Иначе // неудача
ЗаписатьРезультатВыполненияКомандыCOM(НомерCOMПорта, НомерПина, Команда, Аргумент, Неопределено, Ложь, Истина);
КонецЕсли;
ИначеЕсли Команда = 259 Тогда // Проверить есть ли компонента и выполнить проверку COM порта
Ответ = Новый Структура();
Если NativeСтруктура.Свойство("Native"+НомерCOMПорта, Native) Тогда //Компонента для указанного COM порта есть
Если Аргумент = 1 Тогда //Выполняем проверку связи
Ответ = NativeОтправитьКомандуИПолучитьОтвет(Native, НомерCOMПорта, 254, 0, Аргумент);
Если Ответ.ОтветАргумент1 = Аргумент Тогда // Проверка успешно пройдена
Ответ.Вставить("ОтветАргумент1", 1); // Код 1 означает успех
Иначе // Проверка не пройдена
Ответ.Вставить("ОтветАргумент1", 3); // Код 3 означает, что компоненты есть, но проверка связи завершилась неудачей
КонецЕсли;
Иначе // Нужно только проверить наличие компоненты, без проверки связи
Ответ.Вставить("ОтветАргумент1", 1); // Код 1 означает успех
КонецЕсли;
Иначе // Компоненты Нет
Ответ.Вставить("ОтветАргумент1", 2); // Код 2 означает, что компоненты нет
КонецЕсли;
Ответ.Вставить("ОтветСтатус", 254);
Ответ.Вставить("ОтветАргумент2", 0);
ЗаписатьРезультатВыполненияКомандыCOM(НомерCOMПорта, НомерПина, Команда, Аргумент, Ответ, Истина, Ложь);
Иначе // Отправить команду на устройство
Если NativeСтруктура.Свойство("Native"+НомерCOMПорта, Native) Тогда
Ответ = NativeОтправитьКомандуИПолучитьОтвет(Native, НомерCOMПорта, Команда, НомерПина, Аргумент);
Если Ответ = Неопределено Тогда // Ошибка
ЗаписатьРезультатВыполненияКомандыCOM(НомерCOMПорта, НомерПина, Команда, Аргумент, Неопределено, Ложь, Истина);
Иначе // Всё ОК
ЗаписатьРезультатВыполненияКомандыCOM(НомерCOMПорта, НомерПина, Команда, Аргумент, Ответ, Истина, Ложь);
КонецЕсли;
Иначе
ОписаниеОшибки = "Не удалось выполнить команду т.к. COM порт не открыт (нет в структуре)";
ЗаписьВЖурналОшибок("СерверПривилегированный", "ПостоянноВыполняющаясяCOM", ОписаниеОшибки);
ЗаписатьРезультатВыполненияКомандыCOM(НомерCOMПорта, НомерПина, Команда, Аргумент, Неопределено, Ложь, Истина);
КонецЕсли;
КонецЕсли;
КонецЦикла;
КонецЕсли;
NativeS.Задержка(30);
КонецЦикла;
КонецПроцедуры
Функция NativeОткрытьCOMПорт(Native, НомерCOMПорта, СкоростьCOMПорта)
Попытка
Native.Открыть(НомерCOMПорта, СкоростьCOMПорта);
Успешно = Native.Открыт;
Исключение
Native.Закрыть();
ОписаниеОшибки = "Не удалось открыть COM порт ("+НомерCOMПорта+", "+СкоростьCOMПорта+"). Описание ошибки: " + ОписаниеОшибки();
ЗаписьВЖурналОшибок("СерверПривилегированный", "NativeОткрытьCOMПорт", ОписаниеОшибки);
Успешно = Ложь;
КонецПопытки;
Возврат Успешно;
КонецФункции
Функция NativeОтправитьКомандуИПолучитьОтвет(Native, НомерCOMПорта, Команда, НомерПина, Аргумент) Экспорт
Попытка
КонтрольнаяСумма = Команда + НомерПина + Аргумент;
Пока КонтрольнаяСумма > 255 Цикл
КонтрольнаяСумма = КонтрольнаяСумма - 256;
КонецЦикла;
СтрокаОтправки = "" + Из_10_В_16(Команда) + Из_10_В_16(НомерПина)
+ Из_10_В_16(Аргумент) + Из_10_В_16(КонтрольнаяСумма);
Native.Получить(); // для очистки буфера КОМ порта
Native.Отправить(СтрокаОтправки);
рет = 0;
Ответ = "";
ВремяСброса = ТекущаяДата()+5;
Пока рет < 8 Цикл
рет = рет + Native.Получить();
Ответ = Ответ + Native.Ответ;
Native.Задержка(1);
Если ТекущаяДата() > ВремяСброса Тогда // Нет ответа или сбой передачи
ОписаниеОшибки = "Не дождались ответа от контроллера. рет= " + рет + ", Ответ = """ + Ответ + """, = " + Команда + ", " + НомерПина + ", " + Аргумент + " порт = " + НомерCOMПорта;
ЗаписьВЖурналОшибок("СерверПривилегированный", "NativeОтправитьКомандуИПолучитьОтвет", ОписаниеОшибки);
Прервать;
КонецЕсли;
КонецЦикла;
Если рет = 8 Тогда
а = Из_16_В_10(Сред(Ответ,1,2));
б = Из_16_В_10(Сред(Ответ,3,2));
в = Из_16_В_10(Сред(Ответ,5,2));
г = Из_16_В_10(Сред(Ответ,7,2));
абв = а+б+в;
Пока абв > 255 Цикл
абв = абв - 256;
КонецЦикла;
Если абв = г Тогда // Проверка контрольной суммы пройдена успешна
Ответ = Новый Структура();
Ответ.Вставить("ОтветСтатус", а);
Ответ.Вставить("ОтветАргумент1", б);
Ответ.Вставить("ОтветАргумент2", в);
Иначе
ОписаниеОшибки = "Контрольная сумма не правильная. Количество полученных байт = " + рет + ", Ответ = """ + Ответ + """, = " + Команда + ", " + НомерПина + ", " + Аргумент + " в порт = " + НомерCOMПорта;
ЗаписьВЖурналОшибок("СерверПривилегированный", "NativeОтправитьКомандуИПолучитьОтвет", ОписаниеОшибки);
Ответ = Неопределено;
КонецЕсли;
Иначе
ОписаниеОшибки = "Полученный ответ не = 8 байтам. Количество полученных байт = " + рет + ", Ответ = """ + Ответ + """, = " + Команда + ", " + НомерПина + ", " + Аргумент + " в порт = " + НомерCOMПорта;
ЗаписьВЖурналОшибок("СерверПривилегированный", "NativeОтправитьКомандуИПолучитьОтвет", ОписаниеОшибки);
Ответ = Неопределено;
КонецЕсли;
Исключение
ОписаниеОшибки = "Произошёл сбой во время отправки команды или получения ответа = " + Команда + ", " + НомерПина + ", " + Аргумент + " в порт = " + НомерCOMПорта + ". Описание ошибки: " + ОписаниеОшибки();
ЗаписьВЖурналОшибок("СерверПривилегированный", "NativeОтправитьКомандуИПолучитьОтвет", ОписаниеОшибки);
Ответ = Неопределено;
КонецПопытки;
Возврат Ответ;
КонецФункции
Процедура ЗаписатьРезультатВыполненияКомандыCOM(НомерCOMПорта, НомерПина, Команда, Аргумент, Ответ, КомандаВыполнена, Ошибка)
Менеджер = РегистрыСведений.COM.СоздатьМенеджерЗаписи();
Менеджер.НомерCOMПорта = НомерCOMПорта;
Менеджер.НомерПина = НомерПина;
Менеджер.Прочитать();
Если Менеджер.Команда = Команда И Менеджер.Аргумент = Аргумент Тогда // Проверяем что команда за время исполнения не изменилась
Если Ответ = Неопределено Тогда
Менеджер.ОтветСтатус = 0;
Менеджер.ОтветАргумент1 = 0;
Менеджер.ОтветАргумент2 = 0;
Иначе
ЗаполнитьЗначенияСвойств(Менеджер, Ответ);
КонецЕсли;
Менеджер.КомандаВыполнена = КомандаВыполнена;
Менеджер.Ошибка = Ошибка;
Менеджер.ДатаВыполнения = ТекущаяДата();
Менеджер.Записать(Истина);
КонецЕсли;
КонецПроцедуры
Функция Из_10_В_16(Аргумент)
Значение = Аргумент;
Результат="";
Пока Значение>0 Цикл
Результат=Сред("0123456789:;<=>?",Значение%16+1,1)+Результат;
Значение=Цел(Значение/16) ;
КонецЦикла;
Пока СтрДлина(Результат)<2 Цикл Результат = "0"+Результат; КонецЦикла;
Возврат Результат;
КонецФункции
Функция Из_16_В_10(Аргумент)
Значение = Аргумент;
Результат=0;
Для Х=1 По 2 Цикл
М=1;
Для У=1 По 2-Х Цикл М=М*16 КонецЦикла;
Результат=Результат+(Найти("0123456789:;<=>?",Сред(Значение,Х,1))-1)*М;
КонецЦикла;
Возврат Результат;
КонецФункции
Непосредственно передача команды и получение ответа происходит в функции "NativeОтправитьКомандуИПолучитьОтвет", после передачи команды ответ ожидается в течении 5 секунд (это время можно увеличить изменив строчку "ВремяСброса = ТекущаяДата()+5;"). Команда отправляется в виде 4 пар байт (8 байт):
1. Первая пара - Номер Команды;
2. Вторая - Номер пина;
3. Третья - Аргумент;
4. Четвёртая - Контрольная сумма;
Полученный ответ также представлен в виде 8 байт (4 пары):
1. Статус выполнения;
2. Аргумент1;
3. Аргумент2;
4. Контрольная сумма;
После записи результата в регистр процесс продолжает свой цикл преверяя в регистре наличее новых команд.
Описание работы сеанса (потока) работающего с NET.
Регистр NET устроен так же как и COM, отличие лишь в 2 дополнительных полях:
ДопАргументNET и ОтветДопАргументNET созданы для обмена дополнительной информацией которая не укладывается в число 1 байт для передачи и 2 байта для ответа. Значения этих регистров - Строка длиной 255 символов.
Механизм работы аналогичен COM за исключением процедуры передачи данных на МК, она представлена следующим образом:
Функция HTTPОтправитьКомандуИПолучитьОтвет(АдресПорт, Команда, НомерПина, Аргумент, ДопАргументNET) Экспорт
Попытка
КонтрольнаяСумма = Команда + НомерПина + Аргумент;
Пока КонтрольнаяСумма > 255 Цикл
КонтрольнаяСумма = КонтрольнаяСумма - 256;
КонецЦикла;
СтрокаОтправки = "?a=" + Из_10_В_16(Команда) + "&b=" + Из_10_В_16(НомерПина)
+ "&c=" + Из_10_В_16(Аргумент) + "&d=" + Из_10_В_16(КонтрольнаяСумма)
+ "&dopArg=" + ДопАргументNET;
NETЗапрос = Новый HTTPЗапрос(СтрокаОтправки);
NETСоединение = Новый HTTPСоединение(АдресПорт,,,,,5);
HTTPОтвет = NETСоединение.Получить(NETЗапрос);
NETОтвет = HTTPОтвет.ПолучитьТелоКакСтроку(КодировкаТекста.ANSI);
Ответ = NETОтвет;
Если СтрДлина(Ответ)>= 8 Тогда
а = Из_16_В_10(Сред(Ответ,1,2));
б = Из_16_В_10(Сред(Ответ,3,2));
в = Из_16_В_10(Сред(Ответ,5,2));
г = Из_16_В_10(Сред(Ответ,7,2));
д = Сред(Ответ, 9);
абв = а+б+в;
Пока абв > 255 Цикл
абв = абв - 256;
КонецЦикла;
Если абв = г Тогда // Проверка контрольной суммы пройдена успешна
Ответ = Новый Структура();
Ответ.Вставить("ОтветСтатус", а);
Ответ.Вставить("ОтветАргумент1", б);
Ответ.Вставить("ОтветАргумент2", в);
Ответ.Вставить("ОтветДопАргументNET", д);
Иначе
ОписаниеОшибки = "Контрольная сумма не правильная. Количество полученных символов = " + СтрДлина(Ответ) + ", Ответ = """ + Ответ + """, = " + Команда + ", " + НомерПина + ", " + Аргумент + " на адрес = " + АдресПорт;
ЗаписьВЖурналОшибок("СерверПривилегированный", "HTTPОтправитьКомандуИПолучитьОтвет", ОписаниеОшибки);
Ответ = Неопределено;
КонецЕсли;
Иначе
ОписаниеОшибки = "Полученный ответ меньше 8 символо. Количество полученных символов = " + СтрДлина(Ответ) + ", Ответ = """ + Ответ + """, = " + Команда + ", " + НомерПина + ", " + Аргумент + " на адрес = " + АдресПорт;
ЗаписьВЖурналОшибок("СерверПривилегированный", "HTTPОтправитьКомандуИПолучитьОтвет", ОписаниеОшибки);
Ответ = Неопределено;
КонецЕсли;
Исключение
ОписаниеОшибки = "Произошёл сбой во время отправки команды или получения ответа = " + Команда + ", " + НомерПина + ", " + Аргумент + " на адрес = " + АдресПорт + ". Описание ошибки: " + ОписаниеОшибки();
ЗаписьВЖурналОшибок("СерверПривилегированный", "HTTPОтправитьКомандуИПолучитьОтвет", ОписаниеОшибки);
Ответ = Неопределено;
КонецПопытки;
Возврат Ответ;
КонецФункции
Пердача параметров происходит GET запросом, ответ возвращается как код странички, который состоит из 8 символов, аналогично 8 байтам при работе с серийным портом и строки до 255 знаков в качестве доп информации.
Добавлять свои обработки можно напрямую в конфигурацию, а можно как внешние обработки в справочник "Обработки". В справочники "КонтроллерыCOM" и КонтроллерыNET добавляются подключенные МК, в коде лучше использовать эти справочники как промежуточное звено, чтобы в случае изменения номера COM порта или IP адреса МК достаточно было поменять значения в этих справочниках. Однако можно указывать данные подключения МК напрямую.
Для поддержания того или иного процесса обработка должна выполняться переодически, а не разого. Для этого в конфигурации есть Регистр сведений "Процессы":
--------------------------------------- |
Наименование- Строка(100) - Произвольное название процесса. РасположениеПроцедуры - (Строка, ссылка на справочник Обработки) Если обработка встроенная то указывается её название. НазваниеПроцедуры - (Строка) Название экспортной процедуры в обработке. Оборудование - (Ссылка на справочник оборудование) так как одна обработка может использоваться для нескольких устройств у которых могут быть разные МК ( или один МК но разные пины) нужно указывать с чем именно она будет работать (пример обработка управления светом одна, а лампочек 4) ГруппаУсловий - Ссылка на соответствующий справочник. Условия выполнения тех или иных действий можно прописать в коде самой выполняемой процедуры, однако если они простые то лучше указать их здесь, чтобы процедура вообще не запускалась. Остальные поля очевидны. |
Для определения условий запуска той или иной обработки есть 2 справочника:
- Условия
- ГруппыУсловий
В первом справочнике можно написать код проверки, он должен содержать переменную "Результат" (Булево), если её нет или она равна ЛОЖЬ то проверка не пройдера. Во втором справочнике создаётся набор из нужных условий, там же их можно объеденять в группы ИЛИ.
Для хранения фиксированных параметров есть справочник "Настройки", в нём указывается имя параметра и значение, элемент справочника привязывается к оборудованию и параметры могут использоваться в процедурах обработок для этого оборудования.
Для монитринга какого либо процесса используется регистр сведений "ТекущиеДанныеОборудования" обработки в него записывают текущую информацию которую должны получить процессы других сеансов. Например вы с телефона окрываете веб клиент и хотите видеть текущую температуру в комнате (из первого примера), через этот регистр процесс обработки передаст нужную информацию.
Список стандартных функций доступных из 1С:
-pinMode
-digitalWrite
-analogWrite
-digitalRead
-analogRead
-tone
-noTone
-analogReference
Их описание в 1С можно посмотреть в общем модуле "К", а для МК на сайте http://arduino.ru/Reference
Список специфических функций:
-ОткрытьCOMПорт
-ЗакрытьCOMПорт
-ПересоздатьКомпонентуИОткрытьCOMПорт
-ПроверитьКомпонентуИСвязьCOMПорта
-ПерезапуститьАгентСервера1С
-ПерезагрузитьСервер1С
-АвтоСброс
Цифра в скобочках - код команды для регистра сведений COM/NET.
// ОткрытьCOMПорт - (256) Открыть/Переоткрыть указанный COM порт на указанной скорости.
// Возвращает: Истина - Порт открыт, Ложь - Порт занят либо несуществует, Неопределено - Сбой внутри 1С
// Параметры:
// НомерCOM - Число - Номер ком порта к которому подключен контроллер
// Скорость - Число - Скорость в БИТАХ
// ЗакрытьCOMПорт - (257) Закрыть указанный COM порт.
// Возвращает: Истина - Порт Закрыт, Ложь - Ошибка, Неопределено - Сбой внутри 1С
// Параметры:
// НомерCOM - Число - Номер ком порта к которому подключен контроллер
// ПересоздатьКомпонентуИОткрытьCOMПорт - (258) Закрыть комп порт если открыт,
// пересоздать компоненту и открыть указанный COM порт на указанной скорости.
// Возвращает: Истина - Порт открыт, Ложь - Порт занят либо несуществует, Неопределено - Сбой внутри 1С
// Параметры:
// НомерCOM - Число - Номер ком порта к которому подключен контроллер
// Скорость - Число - Скорость в БИТАХ
// ПроверитьКомпонентуИСвязьCOMПорта - (259) Выполнить проверку наличия компоненты
// для указанного COM порта и (Необязательно) проверить связь с контроллером.
// Возвращает: 1 - Успешно, 2 - Компоненты нет, 3 - Компонента есть, но проверка связи завершилась неудачей,
// Неопределено - Сбой внутри 1С
// Параметры:
// НомерCOM - Число - Номер ком порта к которому подключен контроллер
// НужнаПроверкаСвязи - Булево - По умолчанию Ложь
// ПерезапуститьАгентСервера1С - () Завершает и снова запускает службу Агент сервера 1С, при выполнение завершаются все сеансы
// Параметры:
// Ошибка - Булево - Если процедура вызывается из-за ошибки, по умолчанию ложь (Необязательный)
// ПерезагрузитьСервер1С - () Перезагружает компьютер на котором запущен сервер 1С, при выполнение завершаются все сеансы
// Параметры:
// Ошибка - Булево - Если процедура вызывается из-за ошибки, по умолчанию ложь (Необязательный)
// АвтоСброс - (1) Особая команда по сбросу всех значений в случае потери связи.
// Ардуина будет перезагружена (программно, осуществив переход на 0 точку) если по истечению
// переданного времени не получит эту команду повторно. Передача 0 отключает проверку связи
// и перезагрузку ардуины, 1-255 включает. После перезагрузки все пины будут переведены в
// исходное состояние, а эта команда отключена.
// Возвращает: Истина - Команда выполнена, Ложь - Ошибка, Неопределено - Сбой внутри 1С
// Параметры:
// НомерCOMАдресПорт - Число/Строка - Номер ком порта к которому подключен контроллер или сетевой адрес контроллера (Если Число то COM если Строка то NET)
// Время - Число - Время в секундах, диапазон [0-255], 0 - выключить, 1-255 - включить и передать время.
Для сложных действий состоящих из ряда простых команд были сделаны дополнительные функции:
-_pinModeOUTPUT_digitalWrite
-_pinModeINPUT_digitalRead
-_ТемператураВлажностьDHT11
-_analogRead_СреднееАрифметическое
Цифра в скобочках - код команды для регистра сведений COM/NET.
// _pinMode_digitalWrite - (101) Перевести ПИН в состояние OUTPUT и задать на нём напряжение НИЗКОЕ или ВЫСОКОЕ вольт.
// Возвращает: Истина - Команда выполнена, Ложь - Ошибка, Неопределено - Сбой внутри 1С
// Параметры:
// НомерCOMАдресПорт - Число/Строка - Номер ком порта к которому подключен контроллер или сетевой адрес контроллера (Если Число то COM если Строка то NET)
// НомерПина - Число - Номер пина
// Значение - Число - Напряжение 0=LOW 1=HIGH
// _pinModeINPUT_digitalRead - (102) Перевести ПИН в состояние INPUT и прочитать напряжение на нём НИЗКОЕ или ВЫСОКОЕ.
// Возвращает: 0 - НИЗКОЕ, 1 - ВЫСОКОЕ, Ложь - Ошибка, Неопределено - Сбой внутри 1С
// Параметры:
// НомерCOMАдресПорт - Число/Строка - Номер ком порта к которому подключен контроллер или сетевой адрес контроллера (Если Число то COM если Строка то NET)
// НомерПина - Число - Номер пина
// _ТемператураВлажностьDHT11 - (103) ИзмеритьТемпературу и Влажность датчиком DHT11.
// Возвращает: Структура с температурой в Цельсиях и относительной влажностью в %, Ложь - Ошибка, Неопределено - Сбой внутри 1С
// Параметры:
// НомерCOMАдресПорт - Число/Строка - Номер ком порта к которому подключен контроллер или сетевой адрес контроллера (Если Число то COM если Строка то NET)
// НомерПина - Число - Номер пина
// _analogRead_СреднееАрифметическое - (104) Провести ряд последовательных измерений и вернуть среднее арифметическое.
// Возвращает: 0 - 1023, Ложь - Ошибка, Неопределено - Сбой внутри 1С
// Параметры:
// НомерCOMАдресПорт - Число/Строка - Номер ком порта к которому подключен контроллер или сетевой адрес контроллера (Если Число то COM если Строка то NET)
// НомерПина - Число - Номер пина
// Количество100 - количество измерений в сотнях (Измерение будет сделано 100*Количество раз, одно измерение длится 112 микросекунд)(1-255)
Конфигурация содержит обработку для управления пинами МК
С её помощью можно протестировать или отладить подключение какого либо устройства, а так же посмотреть примеры вывода в интерфейс элементом управления.
ARDUINO
Скетч для ардуины будет следующий:
#include <dht11.h>
dht11 DHT;
// входящие данные
byte a = 0;
byte b = 0;
byte c = 0;
byte d = 0; // проверочная сумма d = a+b+c <=255
// исходящие данные
byte e = 48;
byte f = 48;
byte g = 48;
byte h = 48;
byte e1 = 48;
byte f1 = 48;
byte g1 = 48;
byte h1 = 48;
//внутренние переменные для работы
int val1 = 0;
int val2 = 0;
int val3 = 0;
bool aOff;
long tOff;
int tUp;
void(* resetFunc) (void) = 0; // Функция перезагрузки ( переход на 0 адрес)
void setup() {
aOff = false;
tOff = 0;
tUp = 255;
Serial.begin(115200);
}
void loop() {
if (Serial.available() > 7) {
a = 0; b = 0; c = 0; d = 0; // обнуляем переменные перед началом
e = 0; f = 0; g = 0;
val1 = 0; val2 = 0; val3 = 0;
a = byte((Serial.read()-48)*16 + (Serial.read()-48));
b = byte((Serial.read()-48)*16 + (Serial.read()-48));
c = byte((Serial.read()-48)*16 + (Serial.read()-48));
d = byte((Serial.read()-48)*16 + (Serial.read()-48));
while(Serial.available() > 0){Serial.read();}// очищаем буфер
if( d==byte(a+b+c)){ // проверка пройдена при передачи ничего не потерялось
if(a==0){ // проверка
e=254;f=0;g=0;
}
else if(a==1){ // Особая команда по сбросу всех значений в случае потери связи
if(c==0){
aOff = false; // отключаем автосброс
}
else{ // иначе включаем автосброс в случае превышения таймера
aOff = true; // включаем автосброс
tUp = c; // задаём tUp, оно также используется во время сброса таймера
tOff = millis()/1000 + tUp; // увеливаем tOff - оно в секундах
}
e=254;f=0;g=0;
}
else if(a==2){ // перевести пин =b в состояние =c (входа/выхода)
pinMode(b, c); // 0=INPUT 1=OUTPUT
e=254;f=0;g=0;
}
else if(a==3){ // задать для выхода =b значение =c
digitalWrite(b, c); // 0=LOW 1=HIGH
e=254;f=0;g=0;
}
else if(a==4){ // задать для выхода =b значение =c (ШИМ)
analogWrite(b, c); // c=ШИМ
e=254;f=0;g=0;
}
else if(a==5){ // считать значени с =b (цифровой)
f = byte(digitalRead(b)); // 0=LOW 1=HIGH
e=254;g=0;
}
else if(a==6){ // считать значени с =b (аналоговый)
val1 = analogRead(b); //
e=254;f=val1/16;g=val1%16;
}
else if(a==7){ // сгенерировать на пине =b волну с частотой =c герц.
tone(b, c); //
e=254;f=0;g=0;
}
else if(a==8){ // Останавливает сигнал, генерируемый на порту Tone().
noTone(b); //
e=254;f=0;g=0;
}
else if(a==9){ // Задаёт опорное напряжение относительно которого происходят аналоговые измерения.
analogReference(c); // 1=DEFAULT, 2=INTERNAL1V1, 3=INTERNAL2V56, 0=EXTERNAL
e=254;f=0;g=0;
}
// комбинированные действия (минипрограммы) от 101 -----------------------------------
else if(a==101){
pinMode(b, OUTPUT); // перевести пин в состояние выхода
digitalWrite(b, c); // задать для выхода =b значение =c
e=254;f=0;g=0;
}
else if(a==102){
pinMode(b, INPUT); // перевести пин в состояние входа
f = byte(digitalRead(b)); // считать значени с =b (цифровой)
e=254;g=0;
}
else if(a==103){ //Считать с датчика dht11 температуру и влажность
DHT.read(b);
delay(5);
f = byte(DHT.temperature); // вставить температуру в аргумент1
g = byte(DHT.humidity); // вставить влажность в аргумент1
e=254;
}
else if(a==104){ // считывать значени с =b в количестве =c*100 раз и считает
// среднее. Одно считывание происходит за 112 мксек(аналоговый)
long aread=0;
int i1 = c*100;
for (int i = 0; i < i1; i++) {
aread = aread + analogRead(b);
}
val1 = aread/i1;
e=254;f=val1/16;g=val1%16;
}
else if(a==254){ // Вернуть полученный аргумент первым аргументом
e=254;f=c;g=254;
}
else{ //нет действия для команды =a
e=1;f=0;g=0;
}
}
else{e=0;f=0;g=0;} // если проверка не пройдена возвращаем нули
//Составление ответа
h = byte(e+f+g); // контрольная сумма
e1 = (e%16); e = ((e-e1)/16)+48; e1 = e1+48;
f1 = (f%16); f = ((f-f1)/16)+48; f1 = f1+48;
g1 = (g%16); g = ((g-g1)/16)+48; g1 = g1+48;
h1 = (h%16); h = ((h-h1)/16)+48; h1 = h1+48;
Serial.write(e);Serial.write(e1);Serial.write(f);Serial.write(f1);
Serial.write(g);Serial.write(g1);Serial.write(h);Serial.write(h1); // отправка ответа
}
if(aOff==true){
if((millis()/1000 + 1000)< tOff){ // буфер переполнился, сбрасываем tOff
tOff = millis()/1000 + tUp;
}
if(millis()/1000 > tOff){ // не произошло увеличение tOff, предпологаем что сбой связи.
resetFunc(); // Сбрасываем всё
}
}
}
// входящие данные a-что делаем, b-номер пина, c-значение аргумента,
// d-сумма a+b+c приведённая в byte тоесть < 255
// исходящие данные e-статус выполнения (254-команда выполнена, 0-ошибка получения данных,
// 1-нет действия для команды =a, )
// f-значение ответа, g-дополнительное значение ответа, i-контрольная сумма в byte
// Вставить скетч для сетевого МК
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <dht11.h>
dht11 DHT;
const char *ssid = "MGTS_GPON_6192";
const char *password = "U7UBT8XG";
IPAddress ip(192,168,1,175); //Node static IP
IPAddress gateway(192,168,1,1);
IPAddress subnet(255,255,255,0);
ESP8266WebServer server ( 80 );
// входящие данные
byte a = 0;
byte b = 0;
byte c = 0;
byte d = 0; // проверочная сумма d = a+b+c <=255
String dopArg = "";
// исходящие данные
byte e = 0;
byte f = 0;
byte g = 0;
byte h = 0;
byte e1 = 0;
byte f1 = 0;
byte g1 = 0;
byte h1 = 0;
String dopOtv = "";
//внутренние переменные для работы
int val1 = 0;
int val2 = 0;
int val3 = 0;
bool aOff;
long tOff;
int tUp;
void(* resetFunc) (void) = 0; // Функция перезагрузки ( переход на 0 адрес)
void handleAll() {
a = (byte(server.arg(0)[0])-48)*16 + (byte(server.arg(0)[1])-48);
b = (byte(server.arg(1)[0])-48)*16 + (byte(server.arg(1)[1])-48);
c = (byte(server.arg(2)[0])-48)*16 + (byte(server.arg(2)[1])-48);
d = (byte(server.arg(3)[0])-48)*16 + (byte(server.arg(3)[1])-48);
dopArg = server.arg(4);
if( d==byte(a+b+c)){ // проверка пройдена при передачи ничего не потерялось
if(a==0){ // проверка
e=254;f=0;g=0;
}
else if(a==1){ // Особая команда по сбросу всех значений в случае потери связи
if(c==0){
aOff = false; // отключаем автосброс
}
else{ // иначе включаем автосброс в случае превышения таймера
aOff = true; // включаем автосброс
tUp = c; // задаём tUp, оно также используется во время сброса таймера
tOff = millis()/1000 + tUp; // увеливаем tOff - оно в секундах
}
e=254;f=0;g=0;
}
else if(a==2){ // перевести пин =b в состояние =c (входа/выхода)
pinMode(b, c); // 0=INPUT 1=OUTPUT
e=254;f=0;g=0;
}
else if(a==3){ // задать для выхода =b значение =c
digitalWrite(b, c); // 0=LOW 1=HIGH
e=254;f=0;g=0;
}
else if(a==4){ // задать для выхода =b значение =c (ШИМ)
analogWrite(b, c); // c=ШИМ
e=254;f=0;g=0;
}
else if(a==5){ // считать значени с =b (цифровой)
f = byte(digitalRead(b)); // 0=LOW 1=HIGH
e=254;g=0;
}
else if(a==6){ // считать значени с =b (аналоговый)
val1 = analogRead(b); //
e=254;f=val1/16;g=val1%16;
}
else if(a==7){ // сгенерировать на пине =b волну с частотой =c герц.
tone(b, c); //
e=254;f=0;g=0;
}
else if(a==8){ // Останавливает сигнал, генерируемый на порту Tone().
noTone(b); //
e=254;f=0;g=0;
}
else if(a==9){ // Задаёт опорное напряжение относительно которого происходят аналоговые измерения.
analogReference(c); // 1=DEFAULT, 2=INTERNAL1V1, 3=INTERNAL2V56, 0=EXTERNAL
e=254;f=0;g=0;
}
// комбинированные действия (минипрограммы) от 101 -----------------------------------
else if(a==101){
pinMode(b, OUTPUT); // перевести пин в состояние выхода
digitalWrite(b, c); // задать для выхода =b значение =c
e=254;f=0;g=0;
}
else if(a==102){
pinMode(b, INPUT); // перевести пин в состояние входа
f = byte(digitalRead(b)); // считать значени с =b (цифровой)
e=254;g=0;
}
else if(a==103){ //Считать с датчика dht11 температуру и влажность
DHT.read(b);
delay(5);
f = byte(DHT.temperature); // вставить температуру в аргумент1
g = byte(DHT.humidity); // вставить влажность в аргумент1
e=254;
}
else if(a==104){ // считывать значени с =b в количестве =c*100 раз и считает
// среднее. Одно считывание происходит за 112 мксек(аналоговый)
long aread=0;
int i1 = c*100;
for (int i = 0; i < i1; i++) {
aread = aread + analogRead(b);
}
val1 = aread/i1;
e=254;f=val1/16;g=val1%16;}
else if(a==254){ // Вернуть полученный аргумент первым аргументом
e=254;f=c;g=254;
}
else{ //нет действия для команды =a
e=1;f=0;g=0;
}
}
else{e=0;f=0;g=0;} // если проверка не пройдена возвращаем нули
//Составление ответа
h = byte(e+f+g); // контрольная сумма
e1 = (e%16); e = ((e-e1)/16)+48; e1 = e1+48;
f1 = (f%16); f = ((f-f1)/16)+48; f1 = f1+48;
g1 = (g%16); g = ((g-g1)/16)+48; g1 = g1+48;
h1 = (h%16); h = ((h-h1)/16)+48; h1 = h1+48;
String message = "" + String(char(e)) + String(char(e1))
+ String(char(f)) + String(char(f1)) + String(char(g))
+ String(char(g1)) + String(char(h)) + String(char(h1))
+ dopOtv + "";
server.send ( 200, "text/plain", message );
}
void allOoff(){
//Устанавливаем важные пины в состояние по умолчанию, для сброса
for (int nPin = 0; nPin<=17; nPin++){
pinMode(nPin, 0);
digitalWrite(nPin,0);
}
aOff = false;
tOff = 0;
tUp = 255;
}
void setup ( void ) {
//allOoff();
WiFi.begin ( ssid, password );
WiFi.config(ip, gateway, subnet);
// Wait for connection
while ( WiFi.status() != WL_CONNECTED ) {
delay ( 500 );
}
server.onNotFound( handleAll );
server.begin();
}
void loop ( void ) {
server.handleClient();
if(aOff==true){
if((millis()/1000 + 1000)< tOff){ // буфер переполнился, сбрасываем tOff
tOff = millis()/1000 + tUp;
}
if(millis()/1000 > tOff){ // не произошло увеличение tOff, предпологаем что сбой связи.
allOoff();
resetFunc(); // Сбрасываем всё
}
}
}
#include <UIPEthernet.h>
#include "DHT.h"
#include <EEPROM.h>
int aR = 1;
byte value;
boolean incoming = 0;
String readString;
int an8;
// входящие данные
byte a = 0;
byte b = 0;
byte c = 0;
byte d = 0; // проверочная сумма d = a+b+c <=255
String dopArg = "";
// исходящие данные
byte e = 0;
byte f = 0;
byte g = 0;
byte h = 0;
byte e1 = 0;
byte f1 = 0;
byte g1 = 0;
byte h1 = 0;
String dopOtv = "";
//внутренние переменные для работы
int val1 = 0;
int val2 = 0;
int val3 = 0;
bool aOff;
long tOff;
int tUp;
EthernetServer server = EthernetServer(80);
//////////////////////
void(* resetFunc) (void) = 0; // Функция перезагрузки ( переход на 0 адрес)
void setup()
{
aOff = false;
tOff = 0;
tUp = 255;
uint8_t mac[6] = {0x00,0x01,0xc2,0x03,0xa4,0xf5};
IPAddress myIP(192,168,1,206);
Ethernet.begin(mac,myIP);
server.begin();
//Serial.println(Ethernet.localIP());
}
void loop() {
// listen for incoming clients
EthernetClient client = server.available();
if (client) {
//Serial.println("new client");
// an http request ends with a blank line
boolean currentLineIsBlank = true;
while (client.connected()) {
if (client.available()) {
char cstr = client.read();
if (readString.length() < 150) {readString += cstr;}
if (cstr == '\n' && currentLineIsBlank) {
readString.replace("%3C", "<");
readString.replace("%3E", ">");
readString.replace(" HTTP/1.1", "");
a = (byte(readString[8])-48)*16 + (byte(readString[9])-48);
b = (byte(readString[13])-48)*16 + (byte(readString[14])-48);
c = (byte(readString[18])-48)*16 + (byte(readString[19])-48);
d = (byte(readString[23])-48)*16 + (byte(readString[24])-48);
dopArg = readString[33];
if( d==byte(a+b+c)){ // проверка пройдена при передачи ничего не потерялось
if(a==0){ // проверка
e=254;f=0;g=0;
}
else if(a==1){ // Особая команда по сбросу всех значений в случае потери связи
if(c==0){
aOff = false; // отключаем автосброс
}
else{ // иначе включаем автосброс в случае превышения таймера
aOff = true; // включаем автосброс
tUp = c; // задаём tUp, оно также используется во время сброса таймера
tOff = millis()/1000 + tUp; // увеливаем tOff - оно в секундах
}
e=254;f=0;g=0;
}
else if(a==2){ // перевести пин =b в состояние =c (входа/выхода)
pinMode(b, c); // 0=INPUT 1=OUTPUT
e=254;f=0;g=0;
}
else if(a==3){ // задать для выхода =b значение =c
digitalWrite(b, c); // 0=LOW 1=HIGH
e=254;f=0;g=0;
}
else if(a==4){ // задать для выхода =b значение =c (ШИМ)
analogWrite(b, c); // c=ШИМ
e=254;f=0;g=0;
}
else if(a==5){ // считать значени с =b (цифровой)
f = byte(digitalRead(b)); // 0=LOW 1=HIGH
e=254;g=0;
}
else if(a==6){ // считать значени с =b (аналоговый)
val1 = analogRead(b); //
e=254;f=val1/16;g=val1%16;
}
else if(a==7){ // сгенерировать на пине =b волну с частотой =c герц.
tone(b, c); //
e=254;f=0;g=0;
}
else if(a==8){ // Останавливает сигнал, генерируемый на порту Tone().
noTone(b); //
e=254;f=0;g=0;
}
else if(a==9){ // Задаёт опорное напряжение относительно которого происходят аналоговые измерения.
analogReference(c); // 1=DEFAULT, 2=INTERNAL1V1, 3=INTERNAL2V56, 0=EXTERNAL
e=254;f=0;g=0;
}
// комбинированные действия (минипрограммы) от 101 -----------------------------------
else if(a==101){
pinMode(b, OUTPUT); // перевести пин в состояние выхода
digitalWrite(b, c); // задать для выхода =b значение =c
e=254;f=0;g=0;
}
else if(a==102){
pinMode(b, INPUT); // перевести пин в состояние входа
f = byte(digitalRead(b)); // считать значени с =b (цифровой)
e=254;g=0;
}
else if(a==103){ //Считать с датчика dht11 температуру и влажность
DHT dht(b, DHT11);
dht.begin();
f = byte(dht.readTemperature()); // вставить температуру в аргумент1
g = byte(dht.readHumidity()); // вставить влажность в аргумент2
e=254;
}
else if(a==104){ // считывать значени с =b в количестве =c*100 раз и считает
// среднее. Одно считывание происходит за 112 мксек(аналоговый)
long aread=0;
int i1 = c*100;
for (int i = 0; i < i1; i++) {
aread = aread + analogRead(b);
}
val1 = aread/i1;
e=254;f=val1/16;g=val1%16;
}
else if(a==105){ //Считать с датчика dht21 температуру и влажность
DHT dht(b, DHT21);
dht.begin();
f = byte(dht.readTemperature())+100; // вставить температуру смещённую на +100 в аргумент1
g = byte(dht.readHumidity()); // вставить влажность в аргумент2
e=254;
}
else if(a==254){ // Вернуть полученный аргумент первым аргументом
e=254;f=c;g=254;
}
else{ //нет действия для команды =a
e=1;f=0;g=0;
}
}
else{e=0;f=0;g=0;} // если проверка не пройдена возвращаем нули
//Составление ответа
h = byte(e+f+g); // контрольная сумма
e1 = (e%16); e = ((e-e1)/16)+48; e1 = e1+48;
f1 = (f%16); f = ((f-f1)/16)+48; f1 = f1+48;
g1 = (g%16); g = ((g-g1)/16)+48; g1 = g1+48;
h1 = (h%16); h = ((h-h1)/16)+48; h1 = h1+48;
String message = "" + String(char(e)) + String(char(e1))
+ String(char(f)) + String(char(f1)) + String(char(g))
+ String(char(g1)) + String(char(h)) + String(char(h1))
+ dopOtv + "";
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println();
client.print(message);
delay(5);
// client.stop();
readString="";
break;
}
if (cstr == '\n') {
// you're starting a new line
currentLineIsBlank = true;
} else if (cstr != '\r') {
// you've gotten a character on the current line
currentLineIsBlank = false;
}
}
}
// give the web browser time to receive the data
delay(1);
// close the connection:
client.stop();
}
if(aOff==true){
if((millis()/1000 + 1000)< tOff){ // буфер переполнился, сбрасываем tOff
tOff = millis()/1000 + tUp;
}
if(millis()/1000 > tOff){ // не произошло увеличение tOff, предпологаем что сбой связи.
for(int nPin = 0; nPin<=21;){
pinMode(nPin, INPUT);
nPin++;
}
resetFunc(); // Сбрасываем всё
}
}
}
Среду для разработки можно скачать с официального сайта http://arduino.ru/Arduino_environment
ВАЖНО!!! Работает только в серверном режиме. Сервер 1С должен быть 32 разрядным. МК подключается к серверу.
П.С. Конфигурация недоделана, выкладываю как есть. Внешние обработки пока что не функционируют, свои поделки вставляйте в основную конфигурацию. Проект дорабатывается, обновления буду выкладывать сюда.
Архив с барахлом https://cloud.mail.ru/public/JsdY/87bMekTef
05.02.2019 Добавлена масса функционала (опишу позже).
02.03.2019 Новое:
- Почти полноценный телеграм бот который умеет:
- Принимать любые сообщения (загружает все типы вложений)
- Принимать трансляцию геоданных
- Отправлять Текстовые сообщения + с клавиатурой, фотографии, файлы
- Редактировать ранее отправленные текстовые сообщения, клавиатуры, фотографии
- Механизмы взаимодействия подсистем конфигурации, можно несколькими кликами сделать новую команду телеграм бота и привязать к ней нужное действие (команда контроллера), так же можно получить ответ от телеграм бота о результате выполненного действия (правда это уже посложнее)
- Веб хуки. Приём и обработка Get запросов, можно настроить шаблоны и совершать нужные действия при получении команды по http (это даёт возможность инициирования события на контроллере, пригодится например для умных выключателей)
- Добавлены обработки работы с IP камерами изображение с которых можно получить в пост запросе Get методом и камеры Dahua, видео запись реализовать не вышло, однако возможна запись фотографий с интервалом 1 сек. (так же есть механизм "трансляции видео" в телеграм, по сути это слайд шоу, в телеграм отправляется фотография которая обновляется раз в секунду но всё же
- Допишу позже)
Последняя версия на Гит Хабе https://github.com/sasha777666/SmartHome
При разработке были использованы материалы:
1. infostart.ru/public/372352/
Архив с барахлом https://cloud.mail.ru/public/JsdY/87bMekTef