Предыстория:

Предыстория: после очередной оптимизации затрат в моем предприятии был прекращен контракт с организацией, которая предоставляла аналитические услуги по режимам работы оборудования и этот весь «нехорошее слово» перевели в задачу на системного администратора (все равно ему зарплату платим :)) и в перспективе аналитика. Как результат фирма отозвала лицензию и права на использования ПО, о чем предупредила письменно, а у меня на руках оказалось около 20 «черных ящиков» и пожелания руководства о том, что информацию все равно нужно получать в главный офис.

Дано:

Схема обычного тривиального подключения по RS-485
Схема обычного тривиального подключения по RS-485
  1. ICP DAS I-7080

  2. ICP DAS I-7019

  3. ICOP VDX2 на Vortex86 c RS-232, RS-485, USB2, VGA, PS/2 и промышленным накопителем на 4Gb

Проанализировав состояние накопителя, который уже потрудился 10 лет (установлена Windows XP, которая так и не стала нашей), было принято решение что следующей ОС будет Linux (дополнительная задача, это работа с модемом, отправка данных по стандартным протоколам и т.п.), тем более что она прекрасно работает с обычными флэш-накопителями и как оказалось ядро i586 совместимо с Vortex86. Но ложкой дегтя стала версия совместимого с платой дистрибутива Debian8, который уже можно причислять к ряду некро-, а также наличие всего 256 Мб ОЗУ на плате.
Ничто не предвещало проблем: было вывялено что rs485 расположен в /dev/ttyS1. Модуль I-7019 под адресом #1, I-7080 - #2, контрольная сумма не используется и документация на модули в сети есть. Через minicom модули отзывчиво давали результаты, единственное что это было не в классическом стиле листинга, а в одну строку. Сам minicom модифицирует настройки порта, которые можно увидеть командой
stty -a -F /dev/ttyS0

Тут для нас важным является отключение аппаратного управления потоком параметром
-crtsdts.

В моем варианте после определенных тестирований было принято использовать следующую строку инициализации порта:

stty 9600 min 1 time 10 -echo -crtscts ignbrk -brkint icrnl -imaxbel -opost -isig -icanon -iexten -echo echok -echoctl -echoke -F /dev/ttyS1

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

Стандартный вариант диалога с модулем по RS-485 в режиме команд:

#!/bin/bash
TTY="/dev/ttyS1"
SLEEP=6
RESULT=$(echo -e '#020\r' > $TTY; sleep $SLEEP; kill %cat) //счит. значений счетчика 0 i7080
echo $result

И нельзя сказать, что это работает, так как модуль может просто «забить» на команду, путем опытных испытаний было выявлена «отзывчивость» модулей: I-7019 около 95%, а вот I-7080 около 75%. Были проведены эксперименты с разными значениями SLEEP от 1 до 12, лучшие результаты на значениях 3, 6 и 7.

Так не бывает!? Может порт проблемный? Чтобы исключить проблему с портом, был использован самый простой свисток с AliExpress на контроллере HL-340, который проявил себя лучше встроенного, доведя значения «отзывчивости» для I-7019 около 97%, а для I-7080 до 90%.

Без боязни «запачкать код нечистотами» было принято решение скидывать опрос модуля в файл, а потом уже обрабатывать результаты как промежуточный вариант средствами того же bash, дабы результаты начальству все таки нужны в срок.

Пример части скрипта:

…
fls1="/data/log.1";
#TTY="/dev/ttyS1"
TTY="/dev/ttyUSB1"
SLEEP4=4; #задержка
RESULT=$(cat $TTY >> $fls1 & # ответы пишем в файл
echo -e "\ncount0.1 "`date +"%H:%M:%S"`"\t*" >> $fls1; #пишем в файл время попытки
echo -e '#020\r' > $TTY; sleep $SLEEP4; #первая попытка считать ответ
echo -e "\ncount0.2 "`date +"%H:%M:%S"`"\t*" >> $fls1; #пишем в файл время попытки
echo -e '#020\r' > $TTY; sleep $SLEEP4; #вторая попытка считать в ответ
kill %cat) # принудительно завершаем диалог
…

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

Кроме опроса счетчика его нужно обнулять, а это команда «$0260» для счетчика 0 и «$0261» для счетчика 1. Идея перенести опрос порта в отдельный файл и получать только строку с успешным результатом не покидала и поэтому код модифицировал до следующего:

#!/bin/bash
s_cmd=$1
case $s_cmd in # для примера перечислим несколько команд
data20) # получаем данные счетчика0 от модуля2
s_cmd ='#020\r'
;;
reset20) # обнуляем данные счетчика0 от модуля2
s_cmd ='$0260\r'
;;
data7019) # получаем данные АЦП от модуля1
s_cmd ='#01\r'
;;
*)
s_cmd ='$02M\r' //выводим информацию о имени модуля2 если ничего нет
;;
esac
#TTY="/dev/ttyS1" – со встроенным портом нет адекватной работы
TTY="/dev/ttyUSB0"
SLEEP=5
RESULT=""
while [ ! -n "$RESULT" ]; do # пока модуль не ответит, «дергаем» его
RESULT=$(cat $TTY & echo -e "$s_cmd " > $TTY; sleep $SLEEP; kill %cat) #обычно на первый нет ответа
RESULT=$(cat $TTY & echo -e "$s_cmd " > $TTY; sleep $SLEEP; kill %cat)
done
echo "$RESULT"

ну и вызов, собственно, по типу:
 ./rs485.bash data20

Не покидала мысль что существующий метод «кривой», и все-таки нужно сделать по-другому. Я стал пробовать другие варианты, и случаем, читая информацию про очередные жалобы о проблемах общения с последовательным портом набрел на обсуждение >тут<. Увидел другой вариант, и оказалось, что встроенный контроллер дал вменяемую устойчивость адекватных ответов, и моя часть кода диалога по rs485 приобрела вид:

#блок выбора команд в s_cmd из вышестоящего примера…
…строки 1-16 вышестоящего примера…
TTY="/dev/ttyS1" #порт rs485 
fil="/tmp/ttyDump.dat"
rm $fil  #удаляем результаты предыдущего ответа
while [ ! -s $fil ]; do   # пока нет ответа модуля в файле
exec 3<$TTY               #перенаправляем ответ порта в файловый дескриптор
  cat <&3 > $fil &        #перенаправляем ответ порта из дескриптора в файл
  PID=$!                  #получаем PID для последующего забоя CAT
    echo -e $s_cmd > $TTY #отправляем команду в порт
    sleep 7
  kill $PID               #KILL наш CAT
  wait $PID 2>/dev/null   #ждем завершения CAT
exec 3<&-                 #освобождаем файловый дескриптор 3
done
cat $fil                  #показываем полученные данные
echo "" #делаем перенос строки для ответа скрипта

Возможно, мои мытарства кому-то позволят сэкономить время и облегчить труд bash программирования «на коленке» без использования сторонних библиотек.

По поводу советов что писать надо было сразу на C/C++ могу заметить, что львиная доля уже существующих обсуждений и вопросов по программированию rs485 на просторах сети, позволяют заподозрить что и там не все так гладко как хотелось бы, попадаются советы вплоть до перекомпиляции ядра. А эксперименты с Python учитывая устаревшую версию ОС сразу же закончились отсутвием поддержки современных библиотек. Что тут можно сказать? Будет время на эти задачи – попробуем (я не активный C/C++ программист). Пока существует масса других задач как по бэкенду, так и фронтенду.

Комментарии (5)


  1. Sfinx88
    01.01.2022 13:31
    +3

    Вот что бывает когда системный администратор лезет в АСУТП.

    Нет бы поставить любую SCADA систему, там все уже есть в комплекте. И сбор, и обработка и отправка данных и отчеты....

    Если прямо хочется бесплатно и под LINUX то OpenSCADA  вам в помощь. http://oscada.org/ru/glavnaja/


    1. avk013 Автор
      01.01.2022 14:00

      Спасибо за наводку. Иронично: мою страну погубит некомпетентность как тех кто сверху, так и их подчиненных....


      1. Sfinx88
        01.01.2022 17:42
        +1

        Это не некомпетентность. Просто промышленная автоматизация - это очень специфическая часть IT.


  1. F0iL
    01.01.2022 22:43

    Про то, что можно было не изобретать велосипед, а взять существующий OPC-сервер/SCADA-систему выше уже сказали :) В случае со SCADA у вас, как правило, будет не только сбор данных, но и визуализация, логгирование в БД, построение отчётов, и т.д. Если хочется что-то своё, то у ICP DAS раньше на сайте лежали всякие библиотечки для разных платформ для работы с DCON-протоколом.

    Немного странно читать ремарку про "проблемы с RS-485 если написать программку на C++": если на вашей системе уже все нормально работает простым перенаправлением потоков ввода-вывода в/из символьного устройства (TTY), то в программе на C или C++ оно будет работать точно так же, никакой пересборки ядра для этого не нужно будет вообще. Аналогично с Python: просто открываете /dev/*tty* как файл и вперёд :)

    Почему теряются запросы - вопрос сложный. Возможно проблема с глючным конвертером, практика показала, что лучше всех в плане отсутствия проблем с таймингами и буферами работают конвертеры на чипах от FTDI. Возможно требуется специальная настройка порта чтобы передатчик включался-выключался как надо. Возможно будет полезным прицепиться на RS-485 линию ещё одним портом и послушать, что там летает и чем отличается первый и второй запрос, не добавляются ли какие-нибудь дополнительные символы, например, в одном из случаев. Ещё может помочь повышение/понижение скорости передачи данных, проверка терминирующих резисторов и переход на 4-х проводную сеть RS-485 вместо двухпроводной для полного дуплекса (если модули такое умеют).


    1. avk013 Автор
      02.01.2022 07:23

      спасибо за внимательность, проблема в том что по наследству уже досталось живая система с чужим ПО (которым мы уже не имеем право пользоваться), плата для сбора данных весьма посредственной производительности, 2 последних приведенных варианта кода практически одинаковы по логике, хотя результативность разная. Есть всетаки програмный момент, т.к. minicom работал адекватно как и "cu". Интернет пестрит советами что и в С/С++ порт нужно "передергивать" каждый раз при отправке даннных, даже в случае что переключение режимов аппаратно предусмотрено и работает на 2х проводной схеме. Получается что это проблема самой ОС.