Привет! В процессе разработки иногда приходится изобретать велосипеды инструменты для тестирования приложений и бывает что некоторые из них могут быть полезными например в домашней автоматизации. Вот случай из моего личного опыта. При разработке приложения для мобильных терминалов сбора данных, появилась необходимость тестирования лазерных сканеров, без печати штрихкодов на бумаге. К сожалению эти сканеры не умеют считывать с обычных экранов. Бумагу им подавай, но и электронная бумага им тоже очень понравилась.
Вспомнилась статья на Хабре Электронная книжка в качестве дисплея, решено было достать с полки старенький полуживой ридер Sony PRS-505 и дать ему вторую жизнь в роли экрана для контроллера автоматизации. Но слать картинки через флеш память плохая идея. Нужно было научиться работать напрямую с оперативной памятью электронной книги. Это увеличивает скорость отображения и надежность. Позвольте поделиться опытом рисования в Go на примере генератора штрихкодов и отображении на электронной книге через контроллер Wirenboar 5.
Задачи
- Ничего не сломать. Мы только добавляем новую функцию.
- Электронная книжка должна уметь слушать порт и в течении 1 секунды выводить изображение
- Работа только через буфер в оперативной памяти, никакой flash памяти
- Протестировать вывод великой командой dd
- Рисование штрихкода нужного размера в Go и размещение его в центре холста
- Передача изображения в буфер электронной книжки
- Наслаждаться полученным результатом
RAM Drive через USB
В читалке Sony PRS-505 нет wi-fi и к тому же из модулей ядра USB Gadget имеется только g_file_storage, поэтому это единственный способ быстро передать изображение. К счастью прошивка от PRSPlus умеет запускать любой скрипт при включении электронной книги. Все что нам нужно, это просто положить нужные файлы в папку directory/database/system/PRSPlus и при загрузке будет запущен скрипт prsp.sh.
В качестве буфера использовать Flash память нельзя, поэтому нам нужен небольшой tmpfs диск в оперативной памяти который будет доступен по USB, следует выгрузить модуль ядра g_file_storage и загрузить его с нужными параметрами, для публикации нашего созданного RAM диска по USB. Далее мы должны отслеживать изменения в заданной области и выводить изображение на e-ink дисплей.
echo $'n===============nSTART SCRYPTn' >> /dev/console
#TODO «here need Kernel Event instead while and sleep bottom placed»
function waitnewdata
{
echo $'n===============nWait new datan' >> /dev/console
#Show only modify time the image file
MODIFYTIMEOLD=`ls -l --full-time /tmp/raw.img | awk ' {print $9} '`
MODIFYTIMENEW=$MODIFYTIMEOLD
while [ "$MODIFYTIMEOLD" == "$MODIFYTIMENEW" ]
do
MODIFYTIMENEW=`ls -l --full-time /tmp/raw.img | awk ' {print $9} '`
sleep 0.2
done
if [ "$MODIFYTIMEOLD" != "$MODIFYTIMENEW" ]
then
showpic
fi
}
function showpic
{
echo $'n===============nNew data receivedn' >> /dev/console
#Generating Back Screen for best clear e-ink (optional)
dd if=/dev/zero of=/tmp/img.raw bs=1k count=480
/tmp/showpic /tmp/img.raw
dd if=/tmp/raw.img of=/tmp/img.raw bs=1k count=480
/tmp/showpic /tmp/img.raw
waitnewdata
}
#ldconfig
PATH="/usr/local/bin:/usr/bin:/bin:/usr/bin/X11:/usr/games:/usr/local/sony/bin:/usr/sbin:/sbin"
LD_LIBRARY_PATH="/Data/opt/sony/ebook/application:/lib:/usr/lib:/usr/local/sony/lib:/opt/sony/ebook/lib"
export PATH LD_LIBRARY_PATH
# set initial date
/bin/date 0101000007
#Unload kernel module
rmmod g_file_storage
#Create raw file 1Mb
dd if=/dev/zero of=/tmp/raw.img bs=1k count=1k
grep Data /proc/mtd > /dev/null
if [ $? == 0 ]; then
NUM=`grep Data /proc/mtd | awk -F: '{print $1}' | awk -Fd '{print$2}'`
insmod /lib/modules/2.4.17_n12/kernel/drivers/usb/g_file_storage.o file=/dev/mtdblock$NUM,/dev/sdmscard/r5c807b,/dev/sdmscard/r5c807a,/tmp/raw.img ProductID=$MODEL VendorSpecific=$VENDOR sn_select=0 iSerialNumber=$ID
else
insmod /lib/modules/2.4.17_n12/kernel/drivers/usb/g_file_storage.o file=/dev/sdmscard/r5c807b,/dev/sdmscard/r5c807a,/tmp/raw.img ProductID=$MODEL VendorSpecific=$VENDOR sn_select=0 iSerialNumber=$ID
fi
#start kbook application
nohup /opt/sony/ebook/application/tinyhttp > /dev/null &
cp /Data/database/system/PRSPlus/showpic /tmp/
waitnewdata
Основные моменты, что делает скрипт psrp.sh
- Сначала мы выгружаем модуль:
rmmod g_file_storage
- Создаем пустой файл в /tmp размером в 1Мб. Предварительно подключившись к UART консоли ридера, я имел возможность удостовериться что /tmp примонтирован именно в tmpfs. Это то что нам нужно.
dd if=/dev/zero of=/tmp/raw.img bs=1k count=1k
- Загружаем модуль g_file_storage добавив /tmp/raw.img
insmod /lib/modules/2.4.17_n12/kernel/drivers/usb/g_file_storage.o file=/dev/mtdblock$NUM,/dev/sdmscard/r5c807b,/dev/sdmscard/r5c807a,/tmp/raw.img
- Запускаем родную оболочку tinyhttp, к сожалению без этого диски не будут расшарены по USB
nohup /opt/sony/ebook/application/tinyhttp > /dev/null &
- Запускаем функцию waitnewdata она в цикле с паузой 200мс следит за изменениями в файле образа нашего виртуального диска. По времени изменения
while [ "$MODIFYTIMEOLD" == "$MODIFYTIMENEW" ] do MODIFYTIMENEW=`ls -l --full-time /tmp/raw.img | awk ' {print $9} '` sleep 0.2 done'
Да, согласен, знатный велосипедик, но к сожалению в прошивке нет inotify, а с Kernel Event мне было лениво разбираться. Тем более что fps в 1 секунду меня устроит.
- Если обнаружены изменения, то выводим изображение бинарником, полученным путем кросс компиляции специальным тулчейном под архитектуру armv4l (подробная информация в статье указанной выше, так же готовый бинарник и исходник вы можете найти тут.)
Теперь наша электронная книжка умеет слушать и при этом, на ней как и раньше можно читать книги. Главное нужно иметь ввиду для того чтобы скрипт запустился и мы могли передавать изображения, при включении электронной книжки USB кабель не должен быть подключен. Иначе книжка загрузится без нашего скрипта prsp.sh. То есть сначала включаем книжку, ждем когда загрузится оболочка потом подключаем USB кабель. (эта особенность по умолчанию прописана в прошивке PRSPlus, при желании это тоже можно изменить, сделав свой собственный образ)
Проверим
Нажимаем reset на электронной книжке, ждем окончания загрузки, подключаем USB кабель. Для проверки мы можем отправить тестовое изображение. Например из Ubuntu это можно сделать так:
Если при загрузке ридера скрипт успешно запустился, то при подключению по USB мы увидим устройство с размером 1Мб.
fdisk -l
Находим вот такую строку:
Disk /dev/sdx: 1 MB, 1048576 bytes
Теперь мы знаем, вот он наш кусочек оперативной памяти электронной книги /dev/sdx
.
Для конвертации из jpeg, нам понадобится djpeg, установим нужные пакеты:
apt-get install libjpeg-turbo-progs
Далее создаем JPEG файл в Вашем любимом редакторе, размером 600x800 и отправляем его на электронную книжку.
djpeg -pnm -grayscale test.jpg | dd bs=1 skip=15 | dd of=/dev/sdx bs=480k
В этом конвейере мы преобразуем jpeg в монохромный pgm, пропускаем заголовок и передаем 480Кб единым блоком на устройство /dev/sdx. И тут же видим результат.
Генератор штрих кода и отправка его в устройство
Для рисования штрихкода в Golang нам понадобятся дополнительные библиотеки:
go get github.com/boombuler/barcode
go get golang.org/x/image/bmp
package main
import (
"bytes"
"fmt"
"image"
"log"
"os"
"image/color"
"image/draw"
"golang.org/x/image/bmp"
"syscall"
"github.com/boombuler/barcode"
"github.com/boombuler/barcode/ean"
"github.com/boombuler/barcode/qr"
)
func main() {
switch string(os.Args[2]) {
case "qr":
base64 := os.Args[3]
log.Println("Original data:", base64)
code1pixel, err := qr.Encode(base64, qr.L, qr.Unicode)
if err != nil {
log.Fatal(err)
}
log.Println("Encoded data: ", code1pixel.Content())
if base64 != code1pixel.Content() {
log.Fatal("data differs")
}
log.Println("Encoded data: ", code1pixel.Content())
if base64 != code1pixel.Content() {
log.Fatal("data differs")
}
codeScalled, err := barcode.Scale(code1pixel, 300, 200)
if err != nil {
log.Fatal(err)
}
drtest(codeScalled)
case "ean":
// code, err := ean.Encode("123456789012")
code1pixel, err := ean.Encode(os.Args[3])
if err != nil {
log.Fatal(err)
}
log.Println("Encoded data: ", code1pixel.Content())
codeScalled, err := barcode.Scale(code1pixel, 300, 300)
if err != nil {
log.Fatal(err)
}
drtest(codeScalled)
}
}
func drtest(imgSrc image.Image) {
// create a new Image with the same dimension of image
newImg := image.NewGray(image.Rect(0, 0, 600, 800))
// we will use white background to replace background
// you can change it to whichever color you want with
// a new color.RGBA{} and use image.NewUniform(color.RGBA{<fill in color>}) function
draw.Draw(newImg, newImg.Bounds(), &image.Uniform{color.White}, image.Point{}, draw.Src)
// paste image OVER to newImage
draw.Draw(newImg, newImg.Bounds().Add(image.Point{150, 300}), imgSrc, imgSrc.Bounds().Min, draw.Over)
buf := new(bytes.Buffer)
err := bmp.Encode(buf, newImg)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
send_s3 := buf.Bytes()
fmt.Println("OK")
if os.Args[1] != "false" {
devout(send_s3[1078:])
}
}
func devout(buffer []byte) {
// disk := "/dev/sde"
disk := os.Args[1]
var fd, numread int
var err error
fd, err = syscall.Open(disk, syscall.O_RDWR, 0777)
if err != nil {
fmt.Print(err.Error(), "n")
return
}
//WRITE
numread, err = syscall.Write(fd, buffer)
if err != nil {
fmt.Print(err.Error(), "n")
}
fmt.Printf("Numbytes write: %dn", numread)
// fmt.Printf("Buffer: %xn", buffer[:1000])
err = syscall.Close(fd)
if err != nil {
fmt.Print(err.Error(), "n")
}
}
Основные моменты в коде:
- На примере EAN, сначала мы рисуем штрих код толщиной в 1 пиксель:
code1pixel, err := ean.Encode(os.Args[3])
- Растянем его до нужных размеров:
codeScalled, err := barcode.Scale(code1pixel, 300, 300)
- Создаем холст 600х800 под размер экрана:
newImg := image.NewGray(image.Rect(0, 0, 600, 800))
- Заполняем его нужным цветом:
draw.Draw(newImg, newImg.Bounds(), &image.Uniform{color.White}, image.Point{}, draw.Src)
- Накладываем на наш холст изображение штрихкода:
draw.Draw(newImg, newImg.Bounds().Add(image.Point{150, 300}), imgSrc, imgSrc.Bounds().Min, draw.Over)
- Далее мы открываем устройство на запись и отправляем туда данные исключив заголовок BMP:
devout(send_s3[1078:])
Кросс компиляция под Wirenboard 5
Разработчики Wirenboard, предоставляют нам очень удобный инструмент кросс компиляции на базе Docker контейнера. Но в рамках данной статьи его мы рассматривать не будем. Под ARMv5 наше простое приложение можно собрать одной командой.
GOOS=linux GOARCH=arm GOARM=5 go build main.go
Переносим все на Wirenboard 5:
scp main root@192.168.x.x:/tmp
Переходим на Wirenboard, смотрим имя устройства размером в 1 Мб, в моем примере /dev/sdd.
Запускаем:
/tmp/main /dev/sdd qr "Privet Habr"
Выводы
Использование электронной книги в качестве экрана вполне реально. Своим потенциалом технология электронных чернил побуждает к использованию в дизайне интерьера. E-ink экран будет хорошо смотреться особенно на светлой стене. Можно выводить полезную информацию с домашнего контроллера.
Спасибо за внимание!
P. S. Исходники, можно посмотреть тут и тут. Прошивка PRSPlus для электронных книг тут.
Автор: alexshnup