Transfert de données via QR animé vers Gomobile et GopherJS

Dans cet article, je veux parler d'un petit et drôle projet de week-end pour transférer des fichiers via des codes QR animés. Le projet est écrit en Go, en utilisant Gomobile et Gopherjs - le dernier d'une application web pour mesurer automatiquement les taux de transfert de données. Si vous êtes intéressé par l'idée de transmettre des données via des codes visuels, de développer des applications Web qui ne sont pas sur JS ou sur la véritable plateforme multiplateforme Go - vers Wellcome to Cat.


démo txqr


L'idée du projet est née d'une tâche spécifique pour une application mobile - comment transférer une petite partie de données (~ 15 Ko) vers un autre appareil de la manière la plus simple et rapide, dans des conditions de blocage du réseau. La première pensée était d'utiliser Bluetooth, mais ce n'est pas aussi pratique qu'il y paraît - le processus de détection et de couplage relativement long et pas toujours fonctionnel est trop difficile pour la tâche. Une bonne idée serait d'utiliser NFC (Near Field Communication), mais il y a encore trop d'appareils dans lesquels la prise en charge NFC est limitée ou inexistante. Nous avions besoin de quelque chose de plus simple et plus abordable.


Qu'en est-il des codes QR?


Codes QR


Le code QR (réponse rapide) est le type de code visuel le plus populaire au monde. Il vous permet d'encoder jusqu'à 3 Ko de données arbitraires et possède différents niveaux de correction d'erreurs, vous permettant de lire en toute confiance même un tiers d'un code fermé ou sale.


Mais avec les codes QR, il y a deux problèmes:


  • 3 Ko ne suffisent pas
  • plus les données sont encodées, plus les exigences de qualité pour l'image à numériser sont élevées

Voici le code QR de la 40e version (la densité d'enregistrement la plus élevée) avec 1276 octets:


qrv40


Pour ma tâche, j'avais besoin d'apprendre à transférer ~ 15 Ko de données sur des appareils standard (smartphones / tablettes), donc la question s'est posée d'elle-même - pourquoi ne pas animer la séquence de codes QR et transférer les données en morceaux?


Une recherche rapide d'implémentations prêtes à l'emploi a conduit à plusieurs de ces projets - principalement des projets sur les hackathons (bien qu'il y ait également une thèse ) - mais ils ont tous été écrits en Java, Python ou JavaScript, ce qui, malheureusement, a rendu le code pratiquement non transférable et inutilisé. Mais étant donné la grande popularité des codes QR et la faible complexité technique de l'idée, il a été décidé d'écrire à partir de zéro dans Go - un langage multiplateforme, lisible et rapide. Habituellement, la multiplateforme implique la possibilité de construire du code binaire pour Windows, Mac et Linux, mais dans mon cas, il était également important de le construire pour le web (gopherjs) et pour les systèmes mobiles (iOS / Android). Go vous permet de tout sortir de la boîte avec un coût minimal.


J'ai également envisagé des options alternatives pour les codes visuels - tels que le code HCCB ou JAB , mais pour eux, je devrais écrire un scanner OpenCV, implémenter un encodeur / décodeur à partir de zéro et c'était trop pour un projet pour un week-end. Les codes QR à tour de rôle ( Shotcodes ), et leurs homologues utilisés sur Facebook, Kik et Snapchat, vous permettent d'encoder beaucoup moins d'informations, et l'approche brevetée incroyablement cool d'Apple pour coupler Apple Watch et iPhone - un nuage animé de particules colorées - est également optimisée pour l'effet wow, et pas sous la bande passante maximale. Les codes QR sont intégrés dans les caméras SDK natives du système d'exploitation mobile, ce qui facilite considérablement leur travail.


TXQR


Ainsi est né le projet txqr (issu de la transmission Tx et QR), qui implémente une bibliothèque d'encodage / décodage QR sur Pure Go et un protocole de transmission de données.


L'idée principale est la suivante: un client sélectionne un fichier ou des données à envoyer, le programme sur l'appareil décompose le fichier en morceaux, les code chacun en trames QR et les affiche dans une boucle sans fin avec une fréquence d'images donnée jusqu'à ce que le destinataire reçoive toutes les données. Le protocole est conçu de telle sorte que le destinataire puisse commencer à partir de n'importe quelle image, recevoir des images QR dans n'importe quel ordre - cela évite d'avoir à synchroniser la fréquence d'animation et la fréquence de balayage. Le récepteur peut être un ancien appareil, dont la puissance vous permet de décoder 2 images par seconde, et l'expéditeur avec un nouveau smartphone qui produit une animation 120Hz, ou vice versa, et ce ne sera pas un problème fondamental pour le protocole.


Ceci est réalisé comme suit - lorsque le fichier est divisé en morceaux ( cadres plus loin), un préfixe avec des informations sur le décalage par rapport à toutes les données et la longueur totale - OFFSET/TOTAL| (où OFFSET et TOTAL sont des valeurs entières de décalage et de longueur, respectivement). Les données binaires sont actuellement codées en Base64, mais ce n'est pas vraiment nécessaire - la spécification QR permet non seulement de coder les données sous forme binaire, mais également d'optimiser différentes parties des données pour différents codages (par exemple, un préfixe avec des modifications mineures peut être codé en alphanumérique et le reste du contenu - comme binaire ), mais pour simplifier, Base64 a parfaitement rempli sa fonction.


De plus, la taille et la fréquence des trames peuvent même être modifiées dynamiquement, en s'adaptant aux capacités du destinataire.


protocole


Le protocole lui-même est très simple, et son principal inconvénient est que pour les fichiers volumineux (bien que cela dépasse la portée de la tâche, mais quand même), une image ignorée pendant la numérisation doublera le temps de numérisation - le destinataire devra attendre à nouveau le cycle complet. Dans la théorie du codage, il existe des solutions pour de tels cas - les codes fontaine , mais je vais laisser cela pour certains des jours libres suivants.


Le point le plus intéressant était d'écrire une application mobile pouvant utiliser ce protocole.


Gomobile


Si vous n'avez pas entendu parler de gomobile , alors c'est un projet qui vous permet d'utiliser les bibliothèques Go dans les projets iOS et Android et en fait une procédure simple obscène.


Le processus standard est le suivant:


  • vous écrivez du code Go normal
  • exécuter gomobile bind ...
  • copiez l'artefact (s) yourpackage.framework. ( yourpackage.framework. ou yourpackage.aar ) dans votre projet mobile
  • importez votre yourpackage et travaillez avec comme avec une bibliothèque régulière

Vous pouvez essayer à quel point c'est facile.


Par conséquent, j'ai rapidement écrit une application sur Swift qui scanne les codes QR (grâce à ce merveilleux article ) et les décode, les colle ensemble et, lorsque le fichier entier est reçu, l'affiche dans la fenêtre d'aperçu.


En tant que débutant chez Swift (même si j'ai lu le livre Swift 4), il y a eu pas mal de moments où je me suis retrouvé coincé sur quelque chose de simple, essayant de comprendre comment le faire correctement et, au final, la meilleure solution était d'implémenter cette fonctionnalité sur Go et d'utiliser via gomobile. Ne vous méprenez pas, Swift est à bien des égards un langage merveilleux, mais, comme la plupart des autres langages de programmation, il fournit trop de façons de faire la même chose, et a déjà une histoire décente de changements incompatibles en amont. Par exemple, je devais faire une chose simple: mesurer la durée d'un événement avec une précision d'une milliseconde. Une recherche sur Google et StackOverflow a conduit à une multitude de solutions différentes, conflictuelles et souvent obsolètes, dont aucune, en fin de compte, ne m'a semblé belle ou correcte pour le compilateur. Après 40 minutes de temps passé, je viens de time.Since(start) / time.Millisecond une autre méthode dans le package Go qui a appelé time.Since(start) / time.Millisecond et utilisé directement le résultat de Swift.


J'ai également écrit l'utilitaire de console txqr-ascii pour tester rapidement les applications. Il encode le fichier et anime les QR codes dans le terminal. Dans l'ensemble, cela a étonnamment bien fonctionné - j'ai pu envoyer une petite image en quelques secondes, mais dès que j'ai commencé à tester différentes valeurs de la fréquence d'images, le nombre d'octets dans chaque trame QR et le niveau de correction d'erreur dans l'encodeur QR, il est devenu clair que la solution du terminal n'était pas fait face à la haute fréquence (plus de 10) de l'animation, et que tester et mesurer les résultats manuellement est une chose désastreuse.


Testeur TXQR



Pour trouver la combinaison optimale de la fréquence d'images, de la taille des données dans une trame QR et du niveau de correction d'erreur parmi les limites raisonnables de ces valeurs, j'ai dû exécuter plus de 1000 tests, modifier manuellement les paramètres, attendre un cycle complet avec le téléphone dans ma main et écrire les résultats sur une plaque. Bien sûr, cela doit être automatisé!


L'idée de la prochaine application txqr-tester - txqr-tester . Initialement, j'avais prévu d'utiliser x / exp / shiny - un cadre d'interface utilisateur expérimental pour les applications de bureau natives sur Go, mais il semble être abandonné. Il y a environ un an, je l'ai essayé, et l'impression n'était pas mauvaise - il convenait parfaitement aux choses de bas niveau. Mais aujourd'hui, la branche maître n'a même pas été compilée. Il semble qu'il n'y ait plus d'incitation à investir dans le développement de frameworks de bureau - une tâche complexe et lourde, avec une demande presque nulle de nos jours, toutes les solutions d'interface utilisateur ont migré sur le Web il y a longtemps.


En programmation Web, comme vous le savez, les langages de programmation viennent de commencer à entrer, grâce à WebAssembly, mais ce sont encore les premières étapes pour les enfants. Bien sûr, il y a toujours du JavaScript et des modules complémentaires, mais les amis ne permettent pas à leurs amis d'écrire des applications en JavaScript, j'ai donc décidé d'utiliser ma récente découverte - le cadre Vecty , qui vous permet d'écrire des frontends sur Pure Go, qui sont automatiquement convertis en JavaScript à l'aide d'un adulte très et fonctionnant étonnamment bien. Projet GopherJS .


Vecty et GopherJS


vecty


Dans ma vie, je n'ai pas reçu un tel plaisir du développement d'interfaces frontales.


Un peu plus tard, je prévois d'écrire quelques articles supplémentaires sur mon expérience dans le développement de frontends sur Vecty, y compris les applications WebGL, mais le fait est qu'après plusieurs projets sur React, Angulars et Ember, écrire un frontend dans un langage de programmation simple et réfléchi est un vent de fraîcheur l'air! Je peux écrire de jolis frontends en peu de temps sans écrire une seule ligne en JavaScript!


Pour commencer, c'est ainsi que vous démarrez un nouveau projet sur Vecty (pas de générateurs de code "projet initial" créant des tonnes de fichiers et de dossiers) - juste main.go:


 ackage main import ( "github.com/gopherjs/vecty" ) func main() { app := NewApp() vecty.SetTitle("My App") vecty.AddStylesheet(/* ... add your css... */) vecty.RenderBody(app) } 

Une application, comme tout composant d'interface utilisateur, n'est qu'un type: une structure qui inclut le type vecty.Core et doit implémenter l'interface vecty.Component (constituée d'une méthode Render() ). Et c'est tout! Ensuite, vous travaillez avec des types, des méthodes, des fonctions, des bibliothèques pour travailler avec le DOM, etc. - pas de magie cachée et de nouveaux termes et concepts. Voici le code simplifié de la page principale:


 / App is a top-level app component. type App struct { vecty.Core session *Session settings *Settings // any other stuff you need, // it's just a struct } // Render implements the vecty.Component interface. func (a *App) Render() vecty.ComponentOrHTML { return elem.Body( a.header(), elem.Div( vecty.Markup( vecty.Class("columns"), ), // Left half elem.Div( vecty.Markup( vecty.Class("column", "is-half"), ), elem.Div(a.QR()), // QR display zone ), // Right half elem.Div( vecty.Markup( vecty.Class("column", "is-half"), ), vecty.If(!a.session.Started(), elem.Div( a.settings, )), vecty.If(a.session.Started(), elem.Div( a.resultsTable, )), ), ), vecty.Markup( event.KeyDown(a.KeyListener), ), ) } 

Vous êtes probablement en train de regarder le code et de penser - combien est un travail non fondé avec le DOM! Je le pensais aussi au début, mais dès que j'ai commencé à travailler, j'ai réalisé à quel point c'était pratique:


  1. Il n'y a pas de magie - chaque bloc (Markup ou HTML) est juste une variable du type souhaité, avec des limites claires où vous pouvez mettre quelque chose, grâce au typage statique.
  2. Il n'y a pas de balises d'ouverture / fermeture que vous devez penser à modifier lors de la refactorisation, ou utiliser l'IDE qui le fait pour vous.
  3. La structure devient soudainement claire - par exemple, je n'ai jamais compris pourquoi dans React jusqu'à la 16ème version, il était impossible de renvoyer plusieurs balises d'un composant - c'est "juste une chaîne". En voyant comment cela se fait à Vecty, il est soudainement devenu clair où les racines de cette contrainte ont grandi dans React. Cependant, on ne sait pas pourquoi, après React 16, cela est devenu possible, mais pas nécessaire.

En général, dès que vous essayez cette approche pour travailler avec le DOM, ses avantages deviendront très évidents. Il y a aussi des inconvénients, bien sûr, mais après les inconvénients des méthodes habituelles, ils sont invisibles.


Vecty est appelé un framework de type React, mais ce n'est pas entièrement vrai. Il existe une bibliothèque native GopherJS pour React - myitcv.io/react , mais je ne pense pas que ce soit une bonne idée de répéter les solutions architecturales React pour Go. Lorsque vous écrivez un frontend sur Vecty, il devient soudain clair à quel point c'est vraiment plus facile. Soudain, toute cette magie cachée et les nouveaux termes et concepts que chaque framework JavaScript invente deviennent superflus - ils ne sont que de la complexité supplémentaire , rien de plus. Il suffit de décrire clairement et clairement les composants, leur comportement et de les connecter ensemble - types, méthodes et fonctions, et c’est tout.


Pour CSS, j'ai utilisé un framework Bulma étonnamment décent - il a un nom de classe très clair et une bonne structure, et le code d'interface utilisateur déclaratif avec lui est très lisible.


La vraie magie, cependant, commence lorsque vous compilez du code Go en JavaScript. Cela semble très intimidant, mais, en fait, vous appelez simplement gopherjs build et en moins d'une seconde, vous avez un fichier JavaScript généré automatiquement prêt à être inclus dans votre page HTML de base (une application régulière se compose uniquement d'une balise de corps vide et de l'inclusion de ce Script JS). Lorsque j'ai exécuté cette commande pour la première fois, je m'attendais à voir beaucoup de messages, d'avertissements et d'erreurs, mais non - cela fonctionne de manière incroyablement rapide et silencieuse, il n'imprime des fichiers sur une seule ligne qu'en cas d'erreurs de compilation générées par le compilateur Go, ils sont donc très clairs. Mais c'était encore plus cool de voir des erreurs dans la console du navigateur, avec des traces de pile pointant vers des fichiers .go et la ligne correcte! C'est très cool.


Test des paramètres d'animation QR


Pendant plusieurs heures, j'ai eu une application web prête qui m'a permis de changer rapidement les paramètres de test:


  • FPS - fréquence d'images
  • Taille de trame QR - combien d'octets doivent être dans chaque trame
  • Niveau de récupération QR - Niveau de correction d'erreur QR

et exécutez le test automatiquement.


app


L'application mobile, bien sûr, devait également être automatisée - elle devait comprendre quand le prochain tour commence avec de nouveaux paramètres, comprendre quand la réception prend trop de temps et interrompre le tour, envoyer les résultats à l'application, etc.


Le hic était que l'application web, lancée dans le sandbox du navigateur, ne pouvait pas créer de nouvelles connexions, et si je ne me trompe pas, la seule possibilité d'une véritable connexion peer-to-peer au navigateur est uniquement via WebRTC (je n'ai pas besoin de passer par NAT ), mais c'était trop lourd. L'application Web ne peut être qu'un client.


La solution était simple: le service Web Go qui a fourni l'application Web (et lancé le navigateur à l'URL souhaitée) a également lancé le proxy WebSocket pour deux clients. Dès que deux clients le rejoignent, il envoie en toute transparence des messages d'une connexion à une autre, permettant aux clients (application web et client mobile) de communiquer directement. Ils doivent être pour cela, dans un seul réseau WIFI, bien sûr.


Il y avait un problème de savoir comment dire à un appareil mobile où, en fait, se connecter, et cela a été résolu en utilisant ... le code QR!


Le processus de test ressemble à ceci:


  • une application mobile recherche un code QR avec un marqueur de début et un lien vers un proxy WebSocket
  • dès que le jeton est lu, l'application se connecte à ce proxy WebSocket
  • l'application Web (déjà connectée au proxy) comprend que l'application mobile est prête et affiche un code QR avec le marqueur "prêt pour le prochain tour?"
  • l'application mobile reconnaît le signal, réinitialise le décodeur et envoie un message "yep" via WebSocket.
  • l'application web, après avoir reçu la confirmation, génère une nouvelle animation QR et la tord jusqu'à ce qu'elle reçoive les résultats ou le timeout.
  • les résultats sont ajoutés à une plaque à côté de laquelle vous pouvez immédiatement télécharger au format CSV

conception


En conséquence, tout ce qui me restait à faire était simplement de mettre le téléphone sur un trépied, de lancer l'application, puis les deux programmes eux-mêmes faisaient tout le sale boulot, communiquant poliment via les codes QR et WebSocket :)


démo de testeur


À la fin, j'ai téléchargé le fichier CSV avec les résultats, l'ai conduit vers RStudio et Plotly Online Chart Maker et analysé les résultats.


Résultats


Le cycle de test complet prend environ 4 heures (malheureusement, la partie la plus difficile du processus - générer une image GIF animée avec des trames QR, a dû s'exécuter dans le navigateur, et, puisque le code résultant est toujours en JS, un seul processeur est utilisé), pendant qui, vous avez dû regarder pour que l'écran ne soit pas soudainement vide ou qu'une application ne ferme pas la fenêtre avec l'application Web. Les paramètres suivants ont été testés:


  • FPS - 3 à 12
  • La taille de la trame QR est de 100 à 1000 octets (par incréments de 50)
  • Les 4 niveaux de correction d'erreur QR (faible, moyen, élevé, le plus élevé)
  • Taille du fichier transféré - 13 Ko octets générés aléatoirement

Quelques heures plus tard, j'ai téléchargé CSV et j'ai commencé à analyser les résultats.


Une image est plus importante que mille mots, mais les visualisations 3D interactives sont plus importantes que mille images. Voici une visualisation des résultats (cliquable):


qr_scan_results


Le meilleur résultat a été de 1,4 seconde, soit environ 9 Ko / s! Ce résultat a été enregistré à une fréquence de 11 trames par seconde, une taille de trame de 850 octets et un niveau moyen de correction d'erreur. Dans la plupart des cas, cependant, à cette vitesse, le décodeur de la caméra a sauté certaines images et a dû attendre la prochaine répétition de l'image manquée, ce qui a eu un effet très négatif sur les résultats - au lieu de deux secondes, il pourrait facilement arriver à 15, ou un délai d'expiration défini sur 30 secondes.


Voici les graphiques de la dépendance des résultats sur les variables variables:


Temps d'image / taille


Temps vs taille


Comme vous pouvez le voir, à des valeurs faibles du nombre d'octets dans chaque trame, le codage excessif est trop grand et le temps de lecture total, respectivement, aussi. Il y a un minimum local de 500 à 600 octets par trame, mais les valeurs à côté entraînent toujours des trames perdues. Le meilleur résultat a été observé à 900 octets, mais 1000 et plus sont presque une perte de trame garantie.


Temps / FPS


Time vs FPS


À ma grande surprise, la valeur du nombre d'images par seconde n'a pas eu un effet très important - de petites valeurs augmentaient trop le temps de transmission global, et les grandes augmentaient la probabilité d'une image manquée. La valeur optimale, à en juger par ces tests, est de l'ordre de 6-7 images par seconde pour les appareils sur lesquels j'ai testé.


Temps / Niveau de correction d'erreur


Time vs Lvl


Le niveau de correction d'erreur a montré une relation claire entre le temps de transfert de fichier et le niveau de redondance, ce qui n'est pas surprenant. Le gagnant clair ici est le faible niveau (L) de correction - les données moins redondantes, plus le code QR du scanner avec la même taille de données est plus lisible. En fait, la redondance n'est pas du tout nécessaire pour cette expérience, mais la norme n'offre pas une telle option.


Bien sûr, pour des données plus objectives, ce test devrait être exécuté des centaines et des milliers de fois, sur différents appareils et différents écrans, mais pour mon expérience de week-end, c'était un résultat largement suffisant.


Conclusions


Ce projet amusant a prouvé que le transfert de données à sens unique via des codes animés est certainement possible, et pour les situations où vous devez transférer une petite quantité en l'absence de tout type de réseaux, il est tout à fait approprié. Bien que mon résultat maximum soit d'environ 9 Ko / s, dans la plupart des cas, la vitesse réelle était de 1 à 2 Ko / s.


J'ai également beaucoup aimé utiliser Gomobile et GopherJS avec Vecty comme outil de résolution de problèmes de routine. Ce sont des projets très matures, avec une excellente rapidité de travail, et, dans la plupart des cas, donnant de l'expérience "ça marche".


En fin de compte, j'admire toujours à quel point vous pouvez être productif avec Go lorsque vous savez clairement ce que vous voulez implémenter - le cycle extrêmement court "changer" - "assembler" - "vérifier" vous permet d'expérimenter beaucoup et souvent, du code simple et l'absence d'une hiérarchie de classes dans la structure Les programmes permettent de les refactoriser facilement et sans douleur lors de vos déplacements, et la fantastique plateforme multiplateforme intégrée au langage depuis le tout début vous permet d'écrire le code une fois et de l'utiliser sur le serveur, sur le client Web et dans l'application mobile native. Dans le même temps, malgré des performances largement suffisantes, il reste encore beaucoup d'espace pour l'optimisation et l'accélération.


Donc, si vous n'avez jamais essayé Gomobile ou GopherJS - je vous recommande d'essayer à la prochaine occasion. Cela prendra une heure de votre temps, mais cela peut ouvrir une toute nouvelle couche d'opportunités dans le développement Web ou mobile. N'hésitez pas à essayer!


Les références


Source: https://habr.com/ru/post/fr430688/


All Articles