Електронні чорнило для Wirenboard 5 або малюємо штрих-коди на Go

Привіт, Хабр! У процесі розробки іноді доводиться винаходити велосипеди інструменти для тестування додатків і буває, що деякі з них можуть бути корисними наприклад в домашньої автоматизації. Ось випадок з мого особистого досвіду. При розробці програми для мобільних терміналів збору даних, з'явилася необхідність тестування лазерних сканерів, без друку штрих-кодів на папері. На жаль, ці сканери не вміють зчитувати з звичайних екранів. Папір їм подавай, але і електронний папір їм теж дуже сподобалася.

image

Згадалася стаття на Хабре Електронна книжка в якості дисплея, вирішено було дістати з полиці старенький напівживий рідер Sony PRS-505 і дати йому друге життя в ролі екрану контролера автоматизації. Але слати картинки через флеш пам'ять погана ідея. Потрібно було навчитися працювати безпосередньо з оперативною пам'яттю електронної книги. Це збільшує швидкість відображення і надійність. Дозвольте поділитися досвідом малювання в Go на прикладі генератора штрихкодів та відображення на електронній книзі через контролер Wirenboar 5.

Завдання

  1. Нічого не зламати. Ми лише додаємо нову функцію.
  2. Електронна книжка повинна вміти слухати порт і протягом 1 секунди виводити зображення
  3. Робота тільки через буфер в оперативній пам'яті, ніякої flash пам'яті
  4. Протестувати висновок великою командою dd
  5. Малювання штрих-код потрібного розміру в Go і розміщення його в центрі полотна
  6. Передача зображення в буфер електронної книжки
  7. Насолоджуватися отриманим результатом

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 дисплей.

prsp.sh#!/bin/sh
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 the time 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 received data\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

main.go
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 "дружба":
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 with you want

// 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")
}
}

Основні моменти в коді:

  1. На прикладі EAN, спочатку ми малюємо штрих код товщиною в 1 піксель:

    code1pixel, err := ean.Encode(os.Args[3])

  2. Розтягнемо його до потрібних розмірів:

    codeScalled, err := barcode.Scale(code1pixel, 300, 300)

  3. Створюємо полотно 600х800 під розмір екрану:

    newImg := image.NewGray(image.Rect(0, 0, 600, 800))

  4. Заповнюємо його потрібним кольором:

    draw.Draw(newImg, newImg.Bounds(), &image.Uniform{color.White}, image.Point{}, draw.Src)

  5. Накладаємо на наш полотно зображення штрих-коду:

    draw.Draw(newImg, newImg.Bounds().Add(image.Point{150, 300}), imgSrc, imgSrc.Bounds().Min, draw.Over)

  6. Далі ми відкриваємо пристрій на запис і відправляємо туди дані виключивши заголовок 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 для електронних книг тут.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.