Появился дисплей покупателя. Интересно стало попробовать его использовать в качестве информационного табло для вывода информации о текущем дне, времени до конца рабочего дня/недели, информации о погоде, курсе валют.
При этом не хотел использовать ресурсоёмкие приложения и свой ПК. Остановился на связке мини-ПК Raspberry + Linux + Дисплей покупателя.
Необходимые замечания
Установку и настройку ОС Linux на устройство Raspberry в данном материале не рассматриваю.
Для редактирования текста в среде Linux использовал редакторы nano и mcedit.
Для доступа к мини-ПК на базе ОС Linux из среды Windows пользовался клиентами для удаленного доступа по протоколу SSH — KiTTY/PuTTY.
Для передачи файлов между ОС Windows и Linux использовал программу WinSCP.
Bash — командный интерпретатор (командная оболочка).
Bash — аббревиатура от "Bourne-Again Shell" ("возрождённая" оболочка). Ключевые слова, синтаксис и другие основные особенности языка были заимствованы из другого командного интерпретатора sh (сокращение от shell).
Bash — это ещё и мощный язык программирования.
Я занимаюсь сопровождением программных продуктов на базе 1С и для меня это было возможностью самому познакомится с программированием в среде Linux.
В меру своего понимания буду разъяснять выполняемые команды. Это сделано с целью большого охвата аудитории.
Что использовал?
- Одноплатный компьютер Raspberry Pi 2 Model B v1.1 с установленной ОС Raspbian GNU/Linux 9.4 (stretch).
- Дисплей покупателя POSUA LPOS-VFD USB.
- Командный интерпретатор bash.
1 этап. Подключение и настройка дисплея покупателя
После того как присоединили к USB-порту дисплей покупателя (ДП) выясним параметры подключенного устройства. В терминале выполним команду:
usb-devices
Получим список присоединенных USB устройств к Raspberry:
T: Bus=01 Lev=00 Prnt=00 Port=00 Cnt=00 Dev#= 1 Spd=480 MxCh= 1
D: Ver= 2.00 Cls=09(hub ) Sub=00 Prot=01 MxPS=64 #Cfgs= 1
P: Vendor=1d6b ProdID=0002 Rev=04.14
S: Manufacturer=Linux 4.14.69-v7+ dwc_otg_hcd
S: Product=DWC OTG Controller
S: SerialNumber=3f980000.usb
C: #Ifs= 1 Cfg#= 1 Atr=e0 MxPwr=0mA
I: If#= 0 Alt= 0 #EPs= 1 Cls=09(hub ) Sub=00 Prot=00 Driver=hub
T: Bus=01 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#= 2 Spd=480 MxCh= 5
D: Ver= 2.00 Cls=09(hub ) Sub=00 Prot=02 MxPS=64 #Cfgs= 1
P: Vendor=0424 ProdID=9514 Rev=02.00
C: #Ifs= 1 Cfg#= 1 Atr=e0 MxPwr=2mA
I: If#= 0 Alt= 1 #EPs= 1 Cls=09(hub ) Sub=00 Prot=02 Driver=hub
T: Bus=01 Lev=02 Prnt=02 Port=00 Cnt=01 Dev#= 3 Spd=480 MxCh= 0
D: Ver= 2.00 Cls=ff(vend.) Sub=00 Prot=01 MxPS=64 #Cfgs= 1
P: Vendor=0424 ProdID=ec00 Rev=02.00
C: #Ifs= 1 Cfg#= 1 Atr=e0 MxPwr=2mA
I: If#= 0 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=00 Prot=ff Driver=smsc95xx
T: Bus=01 Lev=02 Prnt=02 Port=01 Cnt=02 Dev#= 4 Spd=12 MxCh= 0
D: Ver= 2.00 Cls=00(>ifc ) Sub=00 Prot=00 MxPS= 8 #Cfgs= 1
P: Vendor=0000 ProdID=0131 Rev=01.00
S: Manufacturer=www.posua.com
S: Product=POSua LPOS-II-VFD USB CDC
C: #Ifs= 2 Cfg#= 1 Atr=a0 MxPwr=16mA
I: If#= 0 Alt= 0 #EPs= 3 Cls=02(commc) Sub=02 Prot=01 Driver=usbserial_generic
I: If#= 1 Alt= 0 #EPs= 2 Cls=03(HID ) Sub=00 Prot=00 Driver=usbhid
Из информации полученной командой находим строку Product=POSua LPOS-II-VFD USB CDC. Это наш дисплей покупателя. В этой секции нам нужна строка Vendor=0000 ProdID=0131 Rev=01.00. А конкретно vendor=0000 prodID=0131. Так идентифицирует себя устройство.
Для корректной работы с ДП необходимо загрузить модуль работы с USB в ядро системы. Выполняем команду с повышенными правами:
sudo modprobe usbserial vendor=0x0000 product=0x0131
modprobe
— программа для добавления модулей в ядро Linux. usbserial
— модуль ядра который обеспечивает эмуляцию COM-порта на USB устройствах. 0x – означает шестнадцатеричный формат.
Так как у меня подключено одно USB-устройство, то в системе Linux автоматически получает файл ttyUSB0. Такова важная особенность взаимодействия с устройствами в ОС Linux – работа с устройством как с файлом. Файлы устройств хранятся в каталоге /dev
.
Для корректной работы с ДП установим скорость передачи данных:
stty -F /dev/ttyUSB0 9600
stty
— команда устанавливает параметры терминального ввода/вывода для устройства. -F
— устройство. В нашем случае дисплей покупателя /dev/ttyUSB0
. И для данного устройства устанавливается скорость 9600 бод.
Теперь можно попробовать вывести приветственное сообщение (пока на английском):
echo "Hello!" > /dev/ttyUSB0
Если всё сделали правильно, то на экране появится наше сообщение. Подробнее о команде ниже.
2 этап. Программирование
На предыдущем этапе мы вывели сообщение на английском языке поверх заставочного сообщения устройства. И это не очень красиво.
Для очистки экрана дисплея выполним команду:
echo -e -n "\x0c\x0b" > /dev/ttyUSB0
echo
— команда вывода в терминал. Опция -e
— включает поддержку вывода escape-последовательностей, -n
— указывает, что не надо выводить перевод строки. Допускается запись -en
.
Сочетания символов, состоящих из обратной косой черты \
, за которой следует буква или набор цифр, называются escape-последовательностями.
0с
— очистка экрана дисплея и отмена строчного режима, 0b
— перемещает курсор в верхнюю крайнюю левую позицию. Символ >
— управление потоком (перенаправляет вывод). В данном случае в файл /dev/ttyUSB0 нашего устройства. Если просто выполнить команду echo "Hello!"
, то в окне терминала появится текст, указанный в кавычках.
Кстати, команду изменяющую скорость для передачи данных на устройство можно было записать и так:
stty 9600 < /dev/ttyUSB0
Ну и для вывода сообщений на русском языке выполним:
echo -n "Привет!" | iconv -f UTF-8 -t CP866 > /dev/ttyUSB0
|
— перенаправляет вывод одной команды на вход другой (конвейер). В нашем случае последовательность символов "Привет!" не выводится в файл устройства сразу, а передаётся на "конвертирование" утилите iconv. iconv
— преобразует из одной кодировки в другую.
Командный интерпретатор bash позволяет не только выполнять команды непосредственно в терминале, но и писать файлы-скрипты.
Скрипт — обычный текстовый файл с последовательностью выполняемых команд.
Для того, чтобы bash понимал, что это "его" в начале файла указывается #!/bin/bash. А для непосредственного выполнения скрипта надо выполнить команду:
sudo chmod u+x namefile.sh
Где namefile.sh — файл скрипта. Расширение sh — означает, что это файл-скрипт bash. chmod
– программа для изменения прав доступа к файлам и каталогам. u+x
— устанавливает право на выполнение файла для текущего пользователя.
Решение задачи выполним двумя скриптами. Первый скрипт — основной (dispos.sh). Он выводит всю необходимую информацию на дисплей покупателя. Второй — вспомогательный (parse.sh) получает данные погоды, сервисов котировок валют и записывает данные в промежуточные файлы. Промежуточные файлы с данными используются в первом скрипте.
Для того, чтобы скрипты могли выполняться, необходимо выполнить команды:
sudo chmod +x dispos.sh
sudo chmod +x parse.sh
Обратите внимание, что используется просто +x
. Это "укороченная" версия u+x
.
Скрипты нужно запускать с определённой периодичностью. Для этого воспользуемся стандартным планировщиком crontab. Для редактирования служит команда:
crontab -e
В планировщик добавим две строки:
*/20 * * * * /home/pi/parse.sh
*/1 * * * * /home/pi/dispos.sh
Скрипт parse.sh выполняется каждые 20 минут, а скрипт dispos.sh каждую минуту.
Перед первоначальным выводом на дисплей покупателя прежде надо выполнить скрипт parse.sh который получит первичные данные о погоде и валюте.
./parse.sh
Далее я приведу полные тексты скриптов с короткими комментариями.
Описание файлов-скриптов
Файл dispos.sh
#!/bin/bash
# Данный скрипт выводит информацию на дисплей покупателя POSua LPOS-VFD.
# По-умолчанию порт ttyUSB0.
# Для определения устройства как tty используется команда:
# modprobe usbserial vendor=0x0000 product=0x0131.
# Где 0x0000 и 0x0131 адрес устройства, определяемый командами
# usb-devices, lsusb или dmesg.
# Установка скорости передачи stty 9600 < /dev/ttyUSB0.
# Получение данных о погоде и валюте реализовано отдельным скриптом parse.sh
# Оба скрипта прописаны в crontab и выполняются с разными временными интервалами.
# ****************************************************************
# Глобальные переменные
# ttyUSB - устройство на которое выводим информацию (POS-дисплей)
DEV_DISPLAY="/dev/ttyUSB0"
# Определение дня недели и конца рабочего дня
# Все рабочие дни, кроме пятницы, заканчиваются в 18:00:00
# В пятницу в 17:00:00
TIME_OF_WORKDAY="18:00:00"
if (( $(date "+%u") >= 5 )); then
TIME_OF_WORKDAY="17:00:00"
fi
# Определяем сколько до конца недели (пятница 17:00:00)
# с текущей даты в секундах
DAY_OF_WEEKEND=`date +"%s" --date="friday 17:00:00"`
# ****************************************************************
# Функции для работы с дисплеем покупателя
# Функция очистки дисплея
disp_clear(){
echo -en "\x0c\x0b" > "${DEV_DISPLAY}"
}
# Функция перевода курсора на следующую строку
disp_cr(){
echo -e "\x0b" > "${DEV_DISPLAY}"
}
# Функция непосредственного вывода строки на дисплей
disp_print(){
echo -n $1 | iconv -f UTF-8 -t CP866 > "${DEV_DISPLAY}"
}
# ****************************************************************
# Основная часть скрипта
# 1. Вывод текущей даты
disp_clear # Очистка дисплея
disp_print "Сегодня: `date "+%A"`"
disp_cr # Перевод на следующую строку
disp_print " `date "+%d.%m.%Y %H:%M"`"
sleep 8
# ****************************************************************
# 2. Вывод информации до конца рабочего дня
disp_clear
disp_print " До конца раб. дня:"
disp_cr
HOURS=$(( ( $(date +%s --date=$TIME_OF_WORKDAY) - $(date +%s) ) / 3600 ))
MINUTES=$(( (( $(date +%s --date=$TIME_OF_WORKDAY) - $(date +%s) ) - $HOURS * 3600) / 60 ))
# Определяем закончился рабочий день или нет
if (( $MINUTES > -1 )); then
OUTPUT_TIME=" ${HOURS} час. ${MINUTES} мин."
else
OUTPUT_TIME=" ЗАКОНЧИЛСЯ!"
fi
# Вывод время до конца рабочего дня
disp_print "${OUTPUT_TIME}"
sleep 8
# ****************************************************************
# 3. Вывод информации до конца рабочей недели
disp_clear
disp_print "До конца раб. недели:"
disp_cr
DAYS=$(( ($DAY_OF_WEEKEND-$(date +%s)) / (24*3600) ))
HOURS=$(( (($DAY_OF_WEEKEND-$(date +%s)) - ($DAYS*24*3600)) / 3600 ))
MINUTES=$(( (($DAY_OF_WEEKEND-$(date +%s)) - ($DAYS*24*3600) - ($HOURS*60*60)) / 60 ))
# Определяем закончилась рабочая неделя или нет
if (( $MINUTES > -1 )); then
OUTPUT_TIME="${DAYS} дн. ${HOURS} час. ${MINUTES} мин"
else
OUTPUT_TIME=" ЗАКОНЧИЛАСЬ!"
fi
# Вывод времени до конца рабочей недели
disp_print "${OUTPUT_TIME}"
sleep 8
# ****************************************************************
# 4. Вывод информации о погоде
# 4.1. Считываем данные из временного файла
LINE1=$(sed -n '1{p;q}' /tmp/weather.txt)
DISPLAY_LINE1=${LINE1:0:19}
DISPLAY_LINE2=${LINE1:19:19}
# Вывод погоды на дисплей (2 строками)
disp_clear
disp_print "${DISPLAY_LINE1}"
disp_cr
disp_print "${DISPLAY_LINE2}"
sleep 4
# 4.2. Вторая часть погоды
LINE1=$(sed -n '2{p;q}' /tmp/weather.txt)
DISPLAY_LINE1=${LINE1:0:14}
DISPLAY_LINE2=${LINE1:14:19}
# Вывод погоды на дисплей (2 строками)
disp_clear
disp_print "Ожид ${DISPLAY_LINE1}"
disp_cr
disp_print "${DISPLAY_LINE2}"
sleep 8
# ****************************************************************
# 5. Вывод информации о банковской валюте
# Считываем данные из временного файла
# Доллар
DOLLAR=$(sed -n '1{p;q}' /tmp/ex.txt)
DOLLAR=${DOLLAR//–/-}
# Евро
EURO=$(sed -n '2{p;q}' /tmp/ex.txt)
EURO=${EURO//–/-}
# Вывод валюты на дисплей
disp_clear
disp_print "Доллар: ${DOLLAR}"
disp_cr
disp_print "Евро: ${EURO}"
sleep 8
# ****************************************************************
# 6. Вывод информации о электронной валюте
# Считываем данные из временных файлов
# BTC
while read line
do
BTC=${line:0:13}
done </tmp/bitcoin.txt
# ETH
while read line
do
ETH=${line:0:13}
done </tmp/ethereum.txt
# Вывод валюты на дисплей
# Заменяем разделитель точка на запятую
disp_clear
disp_print "BTC: ${BTC//./,}"
disp_cr
disp_print "ETH: ${ETH//./,}"
#sleep 8
# ****************************************************************
# 7. Вывод произвольной информации на дисплей
# Здесь необходимо написать любую строку (макс. длина 20 символов)
#DISPLAY_LINE1="С Новым Годом!"
#DISPLAY_LINE2="С Днём России!"
# Вывод произвольной информации на экран
#disp_clear
#disp_print "${DISPLAY_LINE1:0:19}"
#disp_cr
#disp_print "${DISPLAY_LINE2:0:19}"
Комментарии
Для вывода текущей даты служит команда date
. Пример,
echo `date "+%d.%m.%Y %H:%M"`
После выполнения получаем дату вида: 20.05.2019 12:11.
Для расчёта времени до конца дня воспользуемся дополнительной переменной TIME_OF_WORKDAY
и установим значение TIME_OF_WORKDAY="18:00:00"
. Ну а далее рассчитаем часы и минуты до конца рабочего дня:
HOURS=$(( ( $(date +%s --date=$TIME_OF_WORKDAY) - $(date +%s) ) / 3600 ))
MINUTES=$(( (( $(date +%s --date=$TIME_OF_WORKDAY) - $(date +%s) ) - $HOURS * 3600) / 60 ))
Символ $
— указывает на то, что это переменная.
Символ #
— комментарий.
date +%s
— получаем текущую дату и время в секундах.
date +%s --date=$TIME_OF_WORKDAY
— получаем время в секундах до TIME_OF_WORKDAY ("18:00:00")
.
Расчет времени до конца рабочей недели:
DAYS=$(( ($DAY_OF_WEEKEND-$(date +%s)) / (24*3600) ))
HOURS=$(( (($DAY_OF_WEEKEND-$(date +%s)) - ($DAYS*24*3600)) / 3600 ))
MINUTES=$(( (($DAY_OF_WEEKEND-$(date +%s)) - ($DAYS*24*3600) - ($HOURS*60*60)) / 60 ))
Где DAY_OF_WEEKEND=`date +"%s" --date="friday 17:00:00"`
— время в секундах с текущего момента времени до пятницы 17:00:00.
Часть скрипта реализована с помощью функций. Например,
# Функция очистки дисплея
disp_clear(){
echo -en "\x0c\x0b" > "${DEV_DISPLAY}"
}
disp_clear()
— название функции. В {}
указываются выполняемые команды.
Переменная DEV_DISPLAY
является "глобальной" и задаётся вначале скрипта и соответственно DEV_DISPLAY="/dev/ttyUSB0"
.
Чтение данных из файла, например конкретной строки (1):
LINE1=$(sed -n '1{p;q}' /tmp/weather.txt)
sed
— это текстовый редактор, выполняющий операции редактирования над информацией в стандартном потоке ввода или файле. Параметр -n
– выводит текущую выбранную строку. ‘1{p;q}’
— печатает 1 строку и выходит не читая остальные (p
— печать, q
— выход).
Ещё вариант чтения из файла (построчно):
while read line
do
BTC=${line:0:13}
done </tmp/bitcoin.txt
А таким образом DISPLAY_LINE1=${LINE1:0:14}
из строки LINE1
извлекаем подстроку длиной 14 символов начиная с 0.
Замена символов производится комбинацией //
, например, так DOLLAR//–/-
. Заменяется символ "–" на "-".
Файл parse.sh
#!/bin/bash
# Чтение данных погоды в формате RSS с сайта http://rp5.ru/rss/1859/ru
# 1859 - код города Брянск
# Функция конвертирования текстовой строки с валютой
conv(){
# Переворачиваем последовательность строк и читаем указанные строки
CURRENCY=$(sed -n '1!G;h;$p' /tmp/ex.xml | sed -n "${1}{p;q}")
CURRENCY=${CURRENCY//[^,^(^)^0-9^–^+]/}
echo $CURRENCY
}
# Сохраняем файлы c данными во временный каталог
# 1. Погода
wget -q -O /tmp/rp5weather.xml http://rp5.ru/rss/1859/ru
# 2. Получение курса доллара и евро
wget -q -O /tmp/ex.xml http://currr.ru/rss/
# 3. Получение данных bitcoin/ethereum
wget -q -O /tmp/bitcoin.json https://api.coinmarketcap.com/v1/ticker/bitcoin/
wget -q -O /tmp/ethereum.json https://api.coinmarketcap.com/v1/ticker/ethereum/
# Разбор погоды
# Читаем построчно файл, находим маркер, подготавливаем строки погоды
# и сохраняем в файл
LINE31=$(sed -n '31{p;q}' /tmp/rp5weather.xml)
LINE33=$(sed -n '33{p;q}' /tmp/rp5weather.xml)
WEATHER1=${LINE31//"</title>"}
WEATHER1=${WEATHER1//" °C"}
WEATHER1=${WEATHER1//" было "}
WEATHER1=${WEATHER1:29}
WEATHER2=${LINE33##*ожидается}
WEATHER2=${WEATHER2//"°"}
echo "${WEATHER1}С" > /tmp/weather.txt
echo ${WEATHER2%.*} >> /tmp/weather.txt
# Получаем значение Bitcoin
LINEBTC=$(sed -n '7{p;q}' /tmp/bitcoin.json)
echo "${LINEBTC//[^.^0-9]/}" > /tmp/bitcoin.txt
# Получаем значение Ethereum
LINEETH=$(sed -n '7{p;q}' /tmp/ethereum.json)
echo "${LINEETH//[^.^0-9]/}" > /tmp/ethereum.txt
# Разбор валюты
DOLLAR=$(conv 8)
echo $DOLLAR > /tmp/ex.txt
EURO=$(conv 6)
echo $EURO >> /tmp/ex.txt
Комментарии
Команда wget
позволяет скачивать из сети файлы, страницы и т.д. Опция -q
— выводит минимум информации, -O
— сохраняет в указанный файл.
В строках ниже производится запись в файл:
echo "${WEATHER1}С" > /tmp/weather.txt
echo ${WEATHER2%.*} >> /tmp/weather.txt
Причем, если используется перенаправление потока вывода в файл >
, то содержимое файла перезаписывается, а использование >>
дозаписывает данные в файл.
Пример использования параметра в функции:
conv 6
Непосредственно в функции:
CURRENCY=$(sed -n '1!G;h;$p' /tmp/ex.xml | sed -n "${1}{p;q}")
Где {1}
— параметр. Передаётся число 6.
Обратите внимание на сложную функцию замены подстроки, например:
LINEBTC//[^.^0-9]/
В строке остаются только символ "." и все цифры от 0 до 9.
Послесловие
В языке bash доступны практически все возможности "обычных" языков программирования. А некоторые команды, по сравнению с аналогами в 1С, удивляют своей лаконичностью и функциональностью.
На данный момент дисплей покупателя в качестве информационного табло стабильно работает больше полугода.
Список ресурсов
- Страница дисплея покупателя LPOS-VFD
- Программируем символы валют для дисплея покупателя
- Основы BASH (часть 1)
- Основы BASH (часть 2)
- Как пользоваться PuTTY
- Текстовый редактор nano в Linux для новичков
- Инструкция для пользователей WinSCP