Pada artikel ini saya ingin berbicara tentang proyek akhir pekan yang kecil dan lucu untuk mentransfer file melalui kode QR animasi. Proyek ini ditulis dalam Go, menggunakan Gomobile dan Gopherjs - yang terbaru untuk aplikasi web untuk secara otomatis mengukur kecepatan transfer data. Jika Anda tertarik dengan gagasan mentransmisikan data melalui kode visual, kembangkan aplikasi web bukan pada JS atau lintas platform nyata Go - to Wellcome to Cat.

Gagasan proyek ini lahir dari tugas khusus untuk aplikasi seluler - cara mentransfer sebagian kecil data (~ 15KB) ke perangkat lain yang paling sederhana dan cepat, dalam kondisi pemblokiran jaringan. Pikiran pertama adalah menggunakan Bluetooth, tetapi tampaknya tidak senyaman - proses yang relatif lama dan tidak selalu berfungsi untuk mendeteksi dan memasangkan perangkat terlalu sulit untuk tugas tersebut. Ide yang bagus adalah menggunakan NFC (Near Field Communication), tetapi masih terlalu banyak perangkat yang dukungan NFCnya terbatas atau tidak ada sama sekali. Kami membutuhkan sesuatu yang lebih sederhana dan lebih terjangkau.
Bagaimana dengan kode QR?
Kode QR
Kode QR (Respon Cepat) adalah jenis kode visual paling populer di dunia. Ini memungkinkan Anda untuk menyandikan hingga 3KB data sewenang-wenang dan memiliki berbagai tingkat koreksi kesalahan, memungkinkan Anda untuk percaya diri membaca bahkan sepertiga dari kode yang tertutup atau kotor.
Tetapi dengan kode QR ada dua masalah:
- 3KB tidak cukup
- semakin banyak data yang disandikan, semakin tinggi persyaratan kualitas gambar yang akan dipindai
Berikut adalah kode QR dari versi ke-40 (kepadatan perekaman tertinggi) dengan 1.276 byte:

Untuk tugas saya, saya perlu belajar cara mentransfer ~ 15KB data pada perangkat standar (smartphone / tablet), jadi pertanyaannya muncul dengan sendirinya - mengapa tidak menganimasikan urutan kode QR dan mentransfer data dalam beberapa bagian?
Pencarian cepat dari implementasi yang sudah jadi mengarah ke beberapa proyek seperti itu - terutama proyek hackathon (walaupun ada juga tesis ) - tetapi semuanya ditulis dalam Java, Python atau JavaScript, yang, sayangnya, membuat kode praktis tidak dapat diakses dan tidak digunakan. Tetapi mengingat popularitas besar kode QR dan kompleksitas teknis yang rendah dari ide tersebut, diputuskan untuk menulis dari awal di Go - sebuah lintas-platform, bahasa yang mudah dibaca dan cepat. Biasanya, cross-platform menyiratkan kemampuan untuk membangun kode biner untuk Windows, Mac dan Linux, tetapi dalam kasus saya itu juga penting untuk membangunnya untuk web (gopherjs) dan untuk sistem seluler (iOS / Android). Go memberikan semuanya di luar kotak dengan biaya minimal.
Saya juga mempertimbangkan opsi alternatif untuk kode visual - seperti HCCB atau JAB Code , tetapi bagi mereka saya harus menulis pemindai OpenCV, mengimplementasikan encoder / decoder dari awal dan ini terlalu banyak untuk proyek selama satu minggu. Kode QR bulat ( shotcodes ), dan rekan-rekan mereka yang digunakan di Facebook, Kik dan Snapchat, memungkinkan Anda untuk menyandikan informasi yang jauh lebih sedikit, dan pendekatan paten Apple yang sangat keren untuk memasangkan Apple Watch dan iPhone - awan animasi partikel warna-warni - juga dioptimalkan untuk efek wow, dan tidak di bawah bandwidth maksimum. Kode QR diintegrasikan ke dalam kamera SDK asli OS mobile, yang sangat memudahkan pekerjaan dengan mereka.
TXQR
Jadi proyek txqr lahir (dari Tx - transmission, dan QR), yang mengimplementasikan perpustakaan untuk encoding / decoding QR pada Go murni dan protokol untuk mentransmisikan data.
Gagasan utamanya adalah sebagai berikut: satu klien memilih file atau data yang akan dikirim, program pada perangkat memecah file menjadi potongan-potongan, mengkodekan masing-masing ke dalam frame QR dan menunjukkan mereka dalam loop tanpa akhir dengan frame rate yang diberikan sampai penerima menerima semua data. Protokol dibuat sedemikian rupa sehingga penerima dapat mulai dari bingkai apa pun, menerima bingkai QR dalam urutan apa pun - ini menghilangkan kebutuhan untuk menyinkronkan frekuensi animasi dan frekuensi pemindaian. Penerima dapat berupa perangkat lama, yang kekuatannya memungkinkan Anda untuk memecahkan kode 2 frame per detik, dan pengirim dengan smartphone baru yang menghasilkan animasi 120Hz, atau sebaliknya, dan ini tidak akan menjadi masalah mendasar untuk protokol.
Ini dicapai sebagai berikut - ketika file dibagi menjadi beberapa bagian ( frame lebih lanjut), awalan dengan informasi tentang offset relatif terhadap semua data dan total panjang - OFFSET/TOTAL|
(di mana OFFSET dan TOTAL masing-masing adalah nilai integer offset dan panjang). Data biner masih dikodekan dalam Base64, tetapi ini tidak benar-benar diperlukan - spesifikasi QR memungkinkan tidak hanya untuk menyandikan data sebagai biner, tetapi juga untuk mengoptimalkan berbagai bagian data untuk pengkodean yang berbeda (misalnya, awalan dengan perubahan kecil dapat dikodekan sebagai alfanumerik , dan seluruh konten - seperti biner ), tetapi untuk kesederhanaan, Base64 menjalankan fungsinya dengan sempurna.
Selain itu, ukuran dan frekuensi bingkai bahkan dapat diubah secara dinamis, beradaptasi dengan kemampuan penerima.

Protokol itu sendiri sangat sederhana, dan minus utamanya adalah untuk file-file besar (walaupun ini di luar lingkup tugas, tetapi masih), satu frame yang dilewati selama pemindaian akan menggandakan waktu pemindaian - penerima harus menunggu siklus penuh lagi. Dalam teori pengkodean ada solusi untuk kasus-kasus seperti - kode air mancur , tapi saya akan meninggalkan ini untuk beberapa hari libur berikut.
Poin paling menarik adalah menulis aplikasi seluler yang bisa menggunakan protokol ini.
Gomobile
Jika Anda belum pernah mendengar tentang gomobile , maka ini adalah proyek yang memungkinkan Anda untuk menggunakan Go libraries di proyek iOS dan Android dan menjadikannya prosedur sederhana yang cabul.
Proses standar adalah sebagai berikut:
- Anda menulis kode Go biasa
- jalankan
gomobile bind ...
- salin artefak (s) yang
yourpackage.framework.
( yourpackage.framework.
atau yourpackage.aar
) ke proyek seluler Anda - impor
yourpackage
dan bekerja dengannya seperti dengan perpustakaan biasa
Anda dapat mencoba betapa mudahnya itu.
Oleh karena itu, saya segera menulis aplikasi pada Swift yang memindai kode QR (terima kasih untuk artikel yang luar biasa ini ) dan menerjemahkannya, menempelkannya bersama-sama, dan ketika seluruh file diterima, menunjukkannya di jendela pratinjau.
Sebagai pemula untuk Swift (meskipun saya membaca buku Swift 4), ada beberapa saat ketika saya terjebak pada sesuatu yang sederhana, mencoba mencari cara untuk melakukannya dengan benar dan, pada akhirnya, solusi terbaik adalah untuk mengimplementasikan fungsi ini di Go dan gunakan via gomobile. Jangan salah paham, Swift dalam banyak hal merupakan bahasa yang luar biasa, tetapi, seperti kebanyakan bahasa pemrograman lainnya, Swift menyediakan terlalu banyak cara untuk melakukan hal yang sama, dan sudah memiliki riwayat perubahan yang tidak kompatibel yang terbelakang. Sebagai contoh, saya perlu melakukan hal sederhana - untuk mengukur durasi acara dengan akurasi milidetik. Pencarian di Google dan StackOverflow mengarah ke sejumlah solusi yang berbeda, saling bertentangan, dan sering usang, tidak ada yang, pada akhirnya, tampak indah untuk saya atau tepat untuk kompiler. Setelah menghabiskan waktu 40 menit, saya hanya membuat metode lain dalam paket Go yang disebut waktu. time.Since(start) / time.Millisecond
dan menggunakan hasilnya dari Swift secara langsung.
Saya juga menulis utilitas konsol txqr-ascii
untuk pengujian aplikasi cepat. Ini mengkodekan file dan menjiwai kode QR di terminal. Semuanya bekerja dengan sangat baik - saya dapat mengirim gambar kecil dalam beberapa detik, tetapi segera setelah saya mulai menguji nilai frame rate yang berbeda, jumlah byte di setiap frame QR dan tingkat koreksi kesalahan pada encoder QR, menjadi jelas bahwa solusi terminal tidak berupaya dengan frekuensi tinggi (lebih dari 10) dari animasi, dan bahwa menguji dan mengukur hasilnya secara manual adalah hal yang berbahaya.
Penguji TXQR

Untuk menemukan kombinasi optimal frame rate, ukuran data dalam bingkai QR dan tingkat koreksi kesalahan di antara batas-batas yang wajar dari nilai-nilai ini, saya harus menjalankan lebih dari 1000 tes, secara manual mengubah parameter, menunggu siklus lengkap dengan telepon di tangan saya dan menulis hasilnya ke piring. Tentu saja, ini harus otomatis!
Di sini muncul ide aplikasi berikutnya - txqr-tester
. Awalnya, saya berencana untuk menggunakan x / exp / shiny - kerangka UI eksperimental untuk aplikasi desktop asli di Go, tetapi tampaknya ditinggalkan. Sekitar setahun yang lalu saya mencobanya, dan kesan itu tidak buruk - sangat cocok untuk hal-hal tingkat rendah. Tetapi hari ini, cabang utama bahkan belum dikompilasi. Tampaknya tidak ada lagi insentif untuk berinvestasi dalam pengembangan kerangka kerja desktop - tugas yang rumit dan rumit, dengan hampir nol permintaan saat ini, semua solusi UI telah pindah ke web sejak lama.
Dalam pemrograman web, seperti yang Anda ketahui, bahasa pemrograman baru saja mulai masuk, berkat WebAssembly, tetapi ini masih merupakan langkah pertama bagi anak-anak. Tentu saja, ada juga JavaScript dan add-on, tetapi teman tidak mengizinkan teman untuk menulis aplikasi dalam JavaScript, jadi saya memutuskan untuk menggunakan penemuan saya baru-baru ini - kerangka kerja Vecty , yang memungkinkan Anda untuk menulis frontends di Go murni, yang secara otomatis dikonversi ke JavaScript menggunakan JavaScript yang sangat dewasa dan secara mengejutkan berfungsi dengan baik Proyek GopherJS .
Vecty dan GopherJS

Dalam hidup saya, saya belum menerima kesenangan seperti itu dari pengembangan antarmuka front-end.
Beberapa saat kemudian saya berencana untuk menulis beberapa artikel lagi tentang pengalaman saya dalam mengembangkan tampilan depan pada Vecty, termasuk aplikasi WebGL, tetapi intinya adalah bahwa setelah beberapa proyek tentang React, Angulars dan Ember, menulis sebuah frontend dalam bahasa pemrograman yang bijaksana dan sederhana adalah sebuah napas segar udara! Saya dapat menulis frontend yang cukup bagus dalam waktu singkat tanpa menulis satu baris pun dalam JavaScript!
Sebagai permulaan, ini adalah bagaimana Anda memulai proyek baru di Vecty (tidak ada generator kode "proyek awal" yang menciptakan banyak file dan folder) - hanya main.go:
ackage main import ( "github.com/gopherjs/vecty" ) func main() { app := NewApp() vecty.SetTitle("My App") vecty.AddStylesheet() vecty.RenderBody(app) }
Aplikasi, seperti komponen UI lainnya, hanyalah tipe: struktur yang menyertakan tipe vecty.Core
dan harus mengimplementasikan antarmuka vecty.Component
(terdiri dari satu metode Render()
). Dan itu saja! Kemudian Anda bekerja dengan tipe, metode, fungsi, perpustakaan untuk bekerja dengan DOM, dan sebagainya - tanpa sihir tersembunyi dan istilah dan konsep baru. Berikut adalah kode sederhana untuk halaman utama:
/ App is a top-level app component. type App struct { vecty.Core session *Session settings *Settings
Anda mungkin melihat kode sekarang dan berpikir - berapa banyak pekerjaan yang tidak berdasar dengan DOM! Saya juga berpikir begitu pada awalnya, tetapi begitu saya mulai bekerja, saya menyadari betapa nyamannya itu:
- Tidak ada keajaiban - setiap blok (Markup atau HTML) hanyalah variabel dari jenis yang diinginkan, dengan batas yang jelas di mana Anda dapat meletakkan sesuatu, berkat pengetikan statis.
- Tidak ada tag pembuka / penutup yang harus Anda ingat untuk berubah saat refactoring, atau gunakan IDE yang melakukan ini untuk Anda.
- Struktur tiba-tiba menjadi jelas - misalnya, saya tidak pernah mengerti mengapa dalam Bereaksi sampai versi ke-16 tidak mungkin untuk mengembalikan beberapa tag dari suatu komponen - ini adalah "hanya sebuah string". Melihat bagaimana hal ini dilakukan di Vecty, tiba-tiba menjadi jelas di mana akar kendala itu tumbuh di Bereaksi. Meski begitu, tidak jelas, mengapa, setelah Bereaksi 16 itu menjadi mungkin, tetapi tidak perlu.
Secara umum, segera setelah Anda mencoba pendekatan ini untuk bekerja dengan DOM, keuntungannya akan menjadi sangat jelas. Tentu saja ada juga kerugiannya, tetapi setelah kerugian dari metode yang biasa, mereka tidak terlihat.
Vecty disebut kerangka kerja React-like, tetapi ini tidak sepenuhnya benar. Ada pustaka GopherJS asli untuk React - myitcv.io/react , tapi saya rasa itu bukan ide yang baik untuk mengulangi solusi arsitektur React untuk Go. Ketika Anda menulis sebuah frontend di Vecty, tiba-tiba menjadi jelas betapa lebih mudahnya sebenarnya. Tiba-tiba, semua sihir tersembunyi ini serta istilah dan konsep baru yang diciptakan oleh setiap kerangka kerja JavaScript menjadi tidak berguna - semuanya hanya menambah kompleksitas , tidak lebih. Semua yang diperlukan adalah untuk dengan jelas dan jelas menggambarkan komponen, perilaku mereka, dan menghubungkan mereka bersama - jenis, metode dan fungsi, dan itu saja.
Untuk CSS, saya menggunakan kerangka kerja Bulma yang sangat baik - ia memiliki penamaan kelas yang sangat jelas dan struktur yang baik, dan kode UI deklaratif dengannya sangat mudah dibaca.
Namun, keajaiban yang sebenarnya dimulai ketika Anda mengkompilasi kode Go dalam JavaScript. Kedengarannya sangat menakutkan, tetapi, pada kenyataannya, Anda hanya memanggil gopherjs build
dan dalam waktu kurang dari satu detik, Anda memiliki file JavaScript yang dibuat secara otomatis yang siap dimasukkan dalam halaman HTML dasar Anda (aplikasi reguler hanya terdiri dari tag tubuh kosong dan penyertaan ini Skrip JS). Ketika saya pertama kali menjalankan perintah ini, saya berharap untuk melihat banyak pesan, peringatan dan kesalahan, tetapi tidak - itu bekerja dengan sangat cepat dan diam-diam, itu hanya mencetak file satu-baris dalam kasus kesalahan kompilasi yang dihasilkan oleh kompiler Go, sehingga mereka sangat jelas. Tapi itu bahkan lebih keren untuk melihat kesalahan di konsol browser, dengan jejak tumpukan menunjuk ke file .go dan baris yang benar! Ini sangat keren.
Menguji parameter animasi QR
Selama beberapa jam saya memiliki aplikasi web siap yang memungkinkan saya untuk dengan cepat mengubah parameter untuk pengujian:
- FPS - frame rate
- Ukuran Bingkai QR - berapa byte yang harus ada di setiap frame
- QR Recovery Level - Level koreksi kesalahan QR
dan jalankan tes secara otomatis.

Aplikasi seluler, tentu saja, juga harus otomatis - harus memahami kapan putaran berikutnya dimulai dengan parameter baru, memahami kapan penerimaan terlalu banyak waktu dan memutus putaran, mengirim hasilnya ke aplikasi, dan seterusnya.
Masalahnya adalah bahwa aplikasi web, saat berjalan di kotak pasir browser, tidak dapat membuat koneksi baru, dan jika saya tidak salah, satu-satunya kemungkinan koneksi peer-to-peer nyata ke browser hanya melalui WebRTC (saya tidak perlu membobol NAT ), tapi itu terlalu rumit. Aplikasi web hanya bisa menjadi klien.
Solusinya sederhana - layanan web Go yang mengirimkan aplikasi web (dan meluncurkan browser ke URL yang diinginkan) juga meluncurkan proxy WebSocket untuk dua klien. Segera setelah dua klien bergabung, secara transparan mengirim pesan dari satu koneksi ke yang lain, memungkinkan klien (aplikasi web dan klien seluler) untuk berkomunikasi secara langsung. Mereka harus melakukannya, dalam satu jaringan WIFI, tentu saja.
Ada masalah tentang cara memberi tahu perangkat seluler di mana, pada kenyataannya, untuk terhubung, dan dipecahkan menggunakan ... QR code!
Proses pengujian terlihat seperti ini:
- aplikasi seluler mencari kode QR dengan penanda awal dan tautan ke proksi WebSocket
- segera setelah token dibaca, aplikasi terhubung ke proxy WebSocket ini
- aplikasi web (sudah terhubung ke proksi) memahami bahwa aplikasi seluler siap dan menampilkan kode QR dengan penanda "siap untuk putaran selanjutnya?"
- aplikasi seluler mengenali sinyal, mengatur ulang dekoder, dan mengirim pesan "ya" melalui WebSocket.
- aplikasi web, setelah menerima konfirmasi, menghasilkan animasi QR baru dan memelintirnya hingga menerima hasil atau batas waktu.
- hasilnya ditambahkan ke piring di sebelah mana Anda dapat langsung mengunduh sebagai CSV

Akibatnya, yang tersisa bagi saya hanyalah meletakkan telepon di atas tripod, meluncurkan aplikasi, dan kemudian kedua program itu sendiri melakukan semua pekerjaan kotor, berkomunikasi dengan sopan melalui kode QR dan WebSocket :)

Pada akhirnya, saya mengunduh file CSV dengan hasilnya, membawanya ke RStudio dan Plotly Online Chart Maker dan menganalisis hasilnya.
Hasil
Siklus pengujian penuh membutuhkan waktu sekitar 4 jam (sayangnya, bagian tersulit dari proses - menghasilkan gambar GIF animasi dengan bingkai QR, harus dijalankan di browser, dan karena kode yang dihasilkan masih dalam JS, hanya satu prosesor yang digunakan), selama yang mana, Anda harus menonton agar layar tidak tiba-tiba menjadi kosong atau beberapa aplikasi tidak menutup jendela dengan aplikasi web. Parameter berikut diuji:
- FPS - 3 hingga 12
- Ukuran bingkai QR adalah dari 100 hingga 1000 byte (dengan penambahan 50)
- Semua 4 level koreksi kesalahan QR (Rendah, Sedang, Tinggi, Tertinggi)
- Ukuran file yang ditransfer - 13KB byte yang dihasilkan secara acak
Beberapa jam kemudian saya mengunduh CSV dan mulai menganalisis hasilnya.
Gambar lebih penting daripada seribu kata, tetapi visualisasi 3D interaktif lebih penting daripada seribu gambar. Berikut adalah visualisasi hasil (dapat diklik):

Hasil terbaik adalah 1,4 detik, yaitu sekitar 9KB / dtk! Hasil ini direkam pada frekuensi 11 frame per detik, ukuran frame 850 byte, dan tingkat koreksi kesalahan rata-rata. Dalam kebanyakan kasus, bagaimanapun, pada kecepatan ini, decoder kamera melewatkan beberapa frame, dan harus menunggu pengulangan berikutnya dari frame yang terlewat, yang memiliki efek yang sangat negatif pada hasilnya - alih-alih dua detik dapat dengan mudah berubah menjadi 15, atau timeout yang diatur ke 30 detik.
Berikut adalah grafik ketergantungan hasil pada variabel variabel:
Waktu Bingkai / Ukuran

Seperti yang Anda lihat, pada nilai rendah dari jumlah byte di setiap frame, kelebihan encoding terlalu besar dan total waktu baca, masing-masing juga. Ada beberapa minimum lokal 500-600 byte per frame, tetapi nilai di sebelahnya masih mengarah ke frame yang hilang. Hasil terbaik diamati pada 900 byte, tetapi 1000 ke atas hampir dijamin kehilangan bingkai.
Waktu / FPS

Nilai jumlah frame per detik, yang mengejutkan saya, tidak memiliki efek yang sangat besar - nilai-nilai kecil meningkatkan waktu transmisi keseluruhan terlalu banyak, dan yang besar meningkatkan kemungkinan frame terjawab. Nilai optimal, dilihat dari tes ini, berada di wilayah 6-7 frame per detik untuk perangkat yang saya uji.
Level Koreksi Waktu / Kesalahan

Level koreksi kesalahan menunjukkan hubungan yang jelas antara waktu transfer file dan level redundansi, yang tidak mengejutkan. Pemenang yang jelas di sini adalah tingkat koreksi rendah (L) - semakin sedikit data yang berlebihan, semakin mudah dibaca kode QR untuk pemindai dengan ukuran data yang sama. Sebenarnya, redundansi tidak diperlukan sama sekali untuk percobaan ini, tetapi standar tidak menawarkan opsi seperti itu.
Tentu saja, untuk data yang lebih objektif, tes ini harus dijalankan ratusan dan ribuan kali, pada perangkat yang berbeda dan layar yang berbeda, tetapi untuk eksperimen akhir pekan saya, hasilnya lebih dari cukup.
Kesimpulan
Proyek lucu ini membuktikan bahwa transfer data satu arah melalui kode animasi tentu dimungkinkan, dan untuk situasi di mana Anda perlu mentransfer sejumlah kecil tanpa adanya jaringan apa pun, itu sangat cocok. Meskipun hasil maksimum saya sekitar 9KB / s, dalam kebanyakan kasus kecepatan sebenarnya adalah 1-2KB / s.
Saya juga sangat menikmati menggunakan Gomobile dan GopherJS dengan Vecty sebagai alat pemecahan masalah rutin. Ini adalah proyek yang sangat matang, dengan kecepatan kerja yang sangat baik, dan, dalam banyak kasus, memberikan pengalaman "itu hanya bekerja."
Pada akhirnya, saya masih mengagumi betapa produktifnya Anda dengan Go ketika Anda jelas tahu apa yang ingin Anda terapkan - siklus "perubahan" yang sangat singkat - "berkumpul" - "centang" memungkinkan Anda untuk banyak bereksperimen dan sering kali, kode sederhana dan tidak adanya hierarki kelas dalam struktur program memungkinkan untuk dengan mudah dan tanpa rasa sakit refactor mereka saat bepergian, dan cross-platform fantastis yang tertanam dalam bahasa sejak awal memungkinkan Anda untuk menulis kode sekali dan menggunakannya di server, di klien web dan di aplikasi mobile asli. Pada saat yang sama, meskipun kinerja lebih dari cukup, masih ada banyak ruang untuk optimasi dan akselerasi.
Jadi, jika Anda belum pernah mencoba Gomobile atau GopherJS - Saya sarankan Anda mencoba pada kesempatan berikutnya. Ini akan memakan waktu satu jam dari waktu Anda, tetapi mungkin membuka seluruh lapisan peluang baru dalam pengembangan web atau seluler. Jangan ragu untuk mencoba!
Referensi