Предыстория:
Предыстория: после очередной оптимизации затрат в моем предприятии был прекращен контракт с организацией, которая предоставляла аналитические услуги по режимам работы оборудования и этот весь «нехорошее слово» перевели в задачу на системного администратора (все равно ему зарплату платим :)) и в перспективе аналитика. Как результат фирма отозвала лицензию и права на использования ПО, о чем предупредила письменно, а у меня на руках оказалось около 20 «черных ящиков» и пожелания руководства о том, что информацию все равно нужно получать в главный офис.
Дано:
-
ICP DAS I-7080
-
ICP DAS I-7019
-
ICOP VDX2 на Vortex86 c RS-232, RS-485, USB2, VGA, PS/2 и промышленным накопителем на 4Gb
Проанализировав состояние накопителя, который уже потрудился 10 лет (установлена Windows XP, которая так и не стала нашей), было принято решение что следующей ОС будет Linux (дополнительная задача, это работа с модемом, отправка данных по стандартным протоколам и т.п.), тем более что она прекрасно работает с обычными флэш-накопителя и как оказалось ядро 586 совместимо с 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 '#020r' > $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 '#020r' > $TTY; sleep $SLEEP4; #первая попытка считать ответ
echo -e "ncount0.2 "`date +"%H:%M:%S"`"t*" >> $fls1; #пишем в файл время попытки
echo -e '#020r' > $TTY; sleep $SLEEP4; #вторая попытка считать в ответ
kill %cat) # принудительно завершаем диалог
…
Особенности обработки текстовых данных, в которых модуль упорно отправляет данные без переноса строки рассматривать в данной статье не будем, перейдем к следующим проблемам.
Кроме опроса счетчика его нужно обнулять, а это команда «$0260» для счетчика 0 и «$0261» для счетчика 1. Идея перенести опрос порта в отдельный файл и получать только строку с успешным результатом не покидала и поэтому код модифицировал до следующего:
#!/bin/bash
s_cmd=$1
case $s_cmd in # для примера перечислим несколько команд
data20) # получаем данные счетчика0 от модуля2
s_cmd ='#020r'
;;
reset20) # обнуляем данные счетчика0 от модуля2
s_cmd ='$0260r'
;;
data7019) # получаем данные АЦП от модуля1
s_cmd ='#01r'
;;
*)
s_cmd ='$02Mr' //выводим информацию о имени модуля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++ программист). Пока существует масса других задач как по бэкенду, так и фронтенду.
Автор: Olexandr Kovalenko