Хочу поделиться опытом интеграции 1С с мощным электронным оборудованием.
Был у меня проект - внедрение конфигурации "Салон красоты" в сеть соляриев - 11 салонов. Клиент захотел чтобы администратор салона включал/выключал сами солярии (кабинки для загорания) из 1С. Ну и учёт и тарификацию времени работы солярия для клиента естественно.
Погуглив я остановился на платформе Arduino. По ней ОЧЕНЬ много материала в сети, на ютьюбе в частности. Язык программирования контроллера там не очень сложный подобный C++.
Это был мой первый опыт, но всё оказалось не так страшно.
Как это работает.
1. Покупается самый простейший контроллер Арудино (можно даже китайскую подделку, не обязательно оригинальный итальянский).
2. Подключается по USB. Питание контроллера может осуществляться от USB, а может от внешнего источника. Разницы я не заметил, но рекомендуют от внешнего стабилизированного источника потому что при включении соляриев (а это 2-3 кВТ мощности) бывали скачки напряжения.
3. При подключении оригинального контроллера драйвер на Win 10 ставится автоматически. Для китайского клона нужно скачать драйвер и установить - ничего сложного. При этом эмулируется com-порт.
4. Далее через системную библиотеку работы с ком-портами MsCommLib.MsComm идёт работа с контроллером через COM-объект.
5. Для 64-битной Вин 10 для сом-объекта нужно сделать 64-битную обёртку (далее распишу как это делается) чтобы работать с 32-разрядной DLL
В 1С код выглядит так:
Функция ОткрытьКомПорт() Экспорт
Попытка
ComPort = Новый COMОбъект("MsCommLib.MsComm");
Исключение
Если ЗадаватьВопросы Тогда
Предупреждение("Ошибка создания COMОбъект MsCommLib.MsComm");
КонецЕсли;
КонтроллерПодключен = Ложь;
Возврат Ложь;
КонецПопытки;
ComPort.CommPort = НомерКомПорта;
ComPort.Settings = "9600,N,8,1";
ComPort.RTSEnable = 1;
ComPort.Handshaking = 0;
ComPort.InBufferCount = 0;
ComPort.InBufferSize = 0;
ComPort.InputLen = 0;
ComPort.InputMode = 1; ////1 (массив байтов) ,по умолчанию 0 (текст)
ComPort.NullDiscard = 0;
Попытка
ComPort.PortOpen = 1;
Возврат Истина
Исключение
Если ЗадаватьВопросы Тогда
Предупреждение("Ошибка открытия com-порта.");
КонецЕсли;
Возврат Ложь;
КонецПопытки;
КонецФункции
Значения параметров подобраны экспериментально - путём проб и ошибок.
Дальше самое интересное - непосредственно чтение/запись в контроллер.
По обработке ожидания формы 1 раз в секунду происходит чтение и запись через com-объект:
Функция ОпроситьКнопки()
Перем н;
Если ComPort = Неопределено Тогда
Возврат "";
КонецЕсли;
Если НЕ КонтроллерПодключен Тогда
Возврат "";
КонецЕсли;
//
//добавлено для исключения ошибки контроллера
Если ТипЗнч(ComPort) = Тип("COMОбъект") Тогда
Data = ComPort.Input;
Если ComPort.InputMode = 0 Тогда // если режим "получение текста", а не массива байт
Возврат Data;
КонецЕсли;
Иначе
Возврат Неопределено;
КонецЕсли;
Всего = Data.GetLength(0);
Если Всего = 0 Тогда
Возврат "";
КонецЕсли;
Мас = Новый Массив(Всего);
Для н = 0 По Всего -1 Цикл
Мас.Добавить(Data.GetValue(н));
КонецЦикла;
Возврат ОбработатьРезультат(Мас);
КонецФункции // ОпроситьКнопки()
Дальше дело техники - обрабатываются значения массива Мас.
Что характерно - пока не добавил проверку переменно сом-объекта на равно Неопределено и проверки типа значения, часть точек - где-то процентов 30 жаловались на периодическое возникновение ошибки "сравнение недопустимых типов" при полном бездействии в 1С.
Сейчас всё работает стабильно и чётко.
Работает так:
1. Приходит клиент в солярий и заказывает х минут загара.
2. Оператор выбирает солярий из списка (на точке может быть до 4 разных - вертикальные, горизонтальные, разной мощности - каждый на своём канале от 1 до 4), задаёт количество минут и нажимает кнопку.
3. В этот момент солярий переходит из состояния "Остановлен" в состояние "Ожидание" и клиент идет в солярий раздеваться.
4. Когда клиент разделся, зашел или лег в кабинку - он нажимает кнопку "Старт" в солярии. Замыкание кнопки даёт команду контроллеру, который переводит солярий на данном канале в режим "Работа" и включаются лампы, вентиляторы и начинается отсчёт времени в 1С. Именно в 1С, а не в солярии. Отчет идёт через функцию Количество миллисекунд с полуночи(). Только так и никак иначе, потому что в противном случае кратковременные зависания и подтормаживания 1С искажают реальное время работы солярия.
Кроме того, в расчет времени добавлена задержка в 2-3 секунды на переходные процессы запуска. Подбирается экспериментально.
После того, как 1С отсчитала положенное время, она даёт команду контроллеру о том, что солярий на таком-то канале должен быть остановлен.
В общих чертах это всё. Если нужно подробности - пишите в комментарии, я дополню информацию.
Дальше привожу приблизительный код контроллера:
// статусы соляриев 0 - выкл., 1 - ожидание, 2 - работает
char Sol[4] = {'0','0','0','0'};
// команда на изменение статуса соляриев считанная с Serial
char SolNew[4] = {'0','0','0','0'};
boolean pressed_buttons[4] = {HIGH, HIGH, HIGH, HIGH}; // сост. нажатия кнопок
boolean last_pressed_buttons[4] = {HIGH, HIGH, HIGH, HIGH}; // пред сост. нажатия кнопок
boolean change[4] = {LOW,LOW,LOW,LOW,}; // признак изменения состоян. нажатия кнопки
int printed = 0; // флаг для одноразового вывода ответной строки
// момент (millis) последнего изменения сост. соляриев
long time[4] = {0,0,0,0};
void setup() {
Serial.begin(115200);
pinMode(2, OUTPUT); // реле для солярия 1
pinMode(3, OUTPUT); // реле для солярия 2
pinMode(4, OUTPUT); // реле для солярия 3
pinMode(5, OUTPUT); // реле для солярия 4
pinMode(13, OUTPUT); // светодиод для отладки
digitalWrite(2, HIGH);
digitalWrite(3, HIGH);
digitalWrite(4, HIGH);
digitalWrite(5, HIGH);
pinMode(6, INPUT_PULLUP); // опрос кнопки 1
pinMode(7, INPUT_PULLUP); // опрос кнопки 1
pinMode(8, INPUT_PULLUP); // опрос кнопки 1
pinMode(9, INPUT_PULLUP); // опрос кнопки 1
}
void loop() {
// ====================================
// ~~~~~~~~~~~ Опрос кнопок ~~~~~~~~~~~
while(Serial.available() == 0) { // пока в порту ничего нет - опрашиваем кнопки
for (int i = 0; i<4; i++) {
pressed_buttons[i] = digitalRead(6+i); // запоминаем состояние кнопки
change[i] = last_pressed_buttons[i] != pressed_buttons[i];
if (change[i] && pressed_buttons[i] == LOW) { // как только нажали
if (millis() - time[i] > 5000) {
switch (SolNew[i]) {
case '1':
Sol[i] = '2';
SolNew[i] = '2';
digitalWrite(i+2,LOW);
pressed_buttons[i] = HIGH;
time[i] = millis();
last_pressed_buttons[i] = pressed_buttons[i];
change[i] = LOW;
printed = 0;
break;
case '2':
// Sol[i] = '1';
// SolNew[i] = '1';
// digitalWrite(i+2, HIGH);
// pressed_buttons[i] = HIGH;
// time[i] = millis();
// last_pressed_buttons[i] = pressed_buttons[i];
// change[i] = LOW;
// printed = 0;
break;
}
} // if (millis() - time[i] > 3000) {
} // if (change[i] && pressed_buttons[i] == LOW)
} // for (int i = 0; i <4; i++)
printSerial();
} // while(Serial.available() == 0)
// ====================================
// ====================================
// ~~~~~~~~~~~ Опрос КОМ_порта ~~~~~~~~~~~
delay(10);
int i=0;
while(Serial.available() > 0)
{
SolNew[i] = Serial.read(); i++;
printed = 0;
}
// ====================================
// кнопки опросили, новые состояния соляриев из ком-порта считали
proceedRelay();
} // void loop()
void printSerial() {
if (printed == 0) {
String out = "";
for (int i = 0; i <4; i++) {
out = out + String(Sol[i]);
}
//Serial.println(Sol);
Serial.println(out);
printed = 1;
}
}
void proceedRelay() { // Включить / выключить реле по массиву состояния соляриев
for (int i=0; i < 4; i++) // выходные порты с 2 по 5 включительно
{
switch (SolNew[i]) {
case '0': // выключение
Sol[i] = '0';
digitalWrite(i+2,HIGH);
last_pressed_buttons[i] = pressed_buttons[i];
change[i] = LOW;
break;
case '1': // установка режима ожидания
if (pressed_buttons[i] == HIGH && last_pressed_buttons[i] == pressed_buttons[i]) {
Sol[i] = '1';
digitalWrite(i+2,HIGH);
last_pressed_buttons[i] = pressed_buttons[i];
change[i] = LOW;
break;
}
else { // режим ожидания и кнопка ВКЛ.
Sol[i] = '2';
digitalWrite(i+2,LOW);
pressed_buttons[i] = HIGH;
last_pressed_buttons[i] = pressed_buttons[i];
change[i] = LOW;
break;
}
case '2': // режим РАБОТЫ
if (pressed_buttons[i] == HIGH && last_pressed_buttons[i] == pressed_buttons[i]) { // И кнопка OFF
Sol[i] = '2';
//Serial.println('8');
digitalWrite(i+2,LOW);
break;
}
else { // режим РАБОТА и кнопка ВКЛ.
// Sol[i] = '1';
// digitalWrite(i+2,HIGH);
// pressed_buttons[i] = HIGH;
// last_pressed_buttons[i] = pressed_buttons[i];
// change[i] = LOW;
// break;
}
} // switch (SolNew[i]) {
} // for (int i=0; i < 4; i++)
printSerial();
}
Всё это работает сейчас на платформе 1С Предприятие 8.3.9.2170
Вызов 32-битных COM-объектов на стороне 64-битного сервера 1С