Вспомнилась статья на Хабре Электронная книжка в качестве дисплея, решено было достать с полки старенький полуживой ридер 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 SCRYPT\n' >> /dev/console
#TODO «here need Kernel Event instead while and sleep bottom placed»
function waitnewdata
{
echo $'\n===============\nWait new data\n' >> /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 received\n' >> /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: %d\n", numread)
// fmt.Printf("Buffer: %x\n", 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 для электронных книг тут.
Комментарии (6)
dlinyj
08.02.2017 12:54+1Вот, наконец-то было доделано то, что я не смог сделать. Как я правильно понял книжку вы даже не разбирали :). Я не совсем уловил суть, была использована родная прошивка или сторонняя? И каким образом удалось сделать виртуальный жёсткий диск? Понимаю, что тут важно было написать кратко, как инструкцию, но очень интересны поиски, решения и т.п.
Очень круто! Ещё, можете показать видео с загрузкой изображения, как быстро это происходит?alexshnup
10.02.2017 19:08+1Разбор первой книжки
В наличии было две книжки, первую я разобрал и воспользовавшись идеями из Вашей полезной статьи, вывел отладочную консоль на гнездо наушников. Это была отличная идея, отдельное спасибо! На этой книжке я все тестировал и отлаживал. А вот вторую книжку уже даже разбирать не пришлось.
Quick start
Кстати выложил все что нужно в одном архиве showpic.zip. Там в нем все что нужно. Прошивка PRSPlus и скрипт с бинарником для отображения. Нужно просто распаковать содержимое архива во внутреннюю память книжки и перезагрузить ее. Он попросит обновить прошивку, обновляем потом reset. Когда книжка включается или перезагружается USB должен быть выключен. Потом когда загрузится, подключаем USB, проверяем что скрипт «prsp.sh» и бинарник «showpic» лежат на своем месте. В директорииdirectory/database/system/PRSPlus
Виртуальный жёсткий диск
При всех последующих загрузках книжки, если USB не подключен, загрузится скрипт, выгрузит модуль ядра «g_file_storage» и загрузит его уже с нужными нам параметрами (подробности в статье), это и добавляет нам новый диск который ссылается на файл лежащий в оперативной памяти tmpfs. При подключенном USB во время включения или перезагрузке, скрипт не выполняется и книжка запускается обычным способом без нашего скрипта. Это позволяет безболезненно экспериментировать.
Скорость загрузки
Из компьютера jpg картинка передается и меняется в течении одной секунды. Отправка единым блоком занимает примерно 200мс. Мне этого достаточно для вывода необходимой информации.dlinyj
11.02.2017 17:40Так хочется повторить ваши опыты. Книжку отдал другу, а он её сломал. Как появится такая, обязательно повторю!
mgremlin
красиво, спасибо.