通过动画QR将数据传输到Gomobile和GopherJS

在本文中,我想讨论一个小型且有趣的周末项目,该项目通过动画QR代码传输文件。 该项目使用Gomobile和Gopherjs(这是Web应用程序的最新版本,可自动测量数据传输率)用Go语言编写。 如果您对通过可视代码传输数据的想法感兴趣,请开发不在JS或真正的跨平台Go-惠康到Cat上的Web应用程序。


txqr演示


该项目的想法源于移动应用程序的一项特定任务-如何在网络阻塞的情况下最简单,最快速地将一小部分数据(〜15KB)传输到另一台设备。 最初的想法是使用蓝牙,但是它似乎并不方便-检测和配对设备的过程相对较长且并非总是工作,因此很难完成任务。 一个好主意是使用NFC(近场通信),但是仍然有太多设备限制或不存在NFC支持。 我们需要更简单,更实惠的东西。


二维码呢?


QR码


QR(快速响应)代码是世界上最流行的可视代码类型。 它允许您对多达3KB的任意数据进行编码,并具有各种纠错级别,甚至可以使您自信地读取关闭或脏代码的三分之一。


但是,QR码存在两个问题:


  • 3KB是不够的
  • 编码的数据越多,扫描图像的质量要求就越高

这是第40版(最高记录密度)的1276个字节的QR码:


qrv40


对于我的任务,我需要学习如何在标准设备(智能手机/平板电脑)上传输约15KB的数据,所以这个问题本身就浮现了-为什么不对QR码序列进行动画处理并分段传输数据?


快速搜索现成的实现导致了几个 这样的 项目 -主要是关于hackathon的项目(尽管也有一个论点 )-但是它们都是用Java,Python或JavaScript编写的,不幸的是,这些代码实际上使它们难以移植和未使用。 但是,鉴于QR码的广泛普及以及该想法的技术复杂性较低,因此决定从头开始使用Go(一种跨平台,可读性和快速的语言)进行编写。 通常,跨平台意味着可以为Windows,Mac和Linux构建二进制代码,但就我而言,为Web(gopherjs)和移动系统(iOS / Android)构建二进制代码也很重要。 Go以最低的成本开箱即用。


我还考虑了可视代码的替代选项,例如HCCBJAB代码 ,但对于它们,我将不得不编写OpenCV扫描器,从头开始实现编码器/解码器,这对于一个周末的项目来说实在太多了。 循环 QR码( shotcodes )以及在Facebook,Kik和Snapchat上使用的对应QR码使您可以编码少得多的信息,并且Apple配对Apple Watch和iPhone的非常酷的专利方法- 彩色颗粒动画云 -还针对哇效果进行了优化,并且不在最大带宽下。 QR码已集成到移动OS的本机SDK相机中,极大地简化了它们的工作。


TXQR


因此, txqr项目诞生了(来自Tx-传输和QR),该项目实现了用于在纯Go上对QR进行编码/解码的库以及用于传输数据的协议。


其主要思想如下:一个客户端选择要发送的文件或数据,设备上的程序将文件分解为多个部分,将每个文件编码为QR帧,并以给定的帧速率无休止地显示它们,直到接收者接收到所有数据为止。 协议的制定方式使得接收者可以从任何帧开始,可以以任何顺序接收QR帧-这样就无需同步动画频率和扫描频率。 接收器可以是旧设备,其功能可以让您每秒解码2帧,发送器可以使用产生120Hz动画的新智能手机,反之亦然,而这对于协议来说并不是根本问题。


这是通过以下方式实现的-当文件被分成几段(进一步的 )时,带有有关相对于所有数据的偏移量和总长度的信息的前缀OFFSET/TOTAL| (其中OFFSET和TOTAL分别是offset和length的整数值)。 二进制数据当前是在Base64中编码的,但这并不是必须的-QR规范不仅允许将数据编码为二进制,而且还可以针对不同的编码优化数据的不同部分(例如,可以将具有微小变化的前缀编码为字母数字 ,其余的内容-类似于binary ),但为简单起见,Base64完美地执行了其功能。


此外,帧大小和频率甚至可以动态更改,以适应接收者的能力。


协议


该协议本身非常简单,它的主要缺点是对于大文件(尽管这超出了任务范围,但仍然如此),在扫描过程中跳过一帧将使扫描时间增加一倍-收件人将不得不再次等待整个周期。 在编码理论中,有针对这种情况的解决方案- 源代码 ,但是我将在接下来的一些空闲日中将其保留。


最有趣的一点是编写一个可以使用此协议的移动应用程序。


Gomobile


如果您还没有听说过gomobile ,那么这是一个项目,可让您在iOS和Android项目中使用Go库,并使该过程变得简单易懂。


标准流程如下:


  • 您编写常规的Go代码
  • 运行gomobile bind ...
  • 将生成的工件( yourpackage.framework.yourpackage.aaryourpackage.aar到您的移动项目中
  • 导入您的yourpackage并像常规库一样使用它

您可以尝试这是多么容易。


因此,我迅速在Swift上编写了一个应用程序,该应用程序扫描QR码(由于这篇精彩的文章 )并将其解码,然后将它们粘合在一起,并在接收到整个文件时将其显示在预览窗口中。


作为Swift的新手(尽管我读过Swift 4的书),有好一会儿,我陷入了一些简单的问题,试图找出正确的方法,最后,最好的解决方案是在Go上实现该功能并使用通过gomobile。 别误会,Swift在许多方面都是一种很棒的语言,但是,与大多数其他编程语言一样,Swift提供了太多的方法来完成相同的事情,并且已经具有向后不兼容更改的良好历史记录。 例如,我需要做一个简单的事情-以毫秒为单位测量事件的持续时间。 在Google和StackOverflow上进行的搜索导致产生了许多不同的,相互矛盾的且常常过时的解决方案,最终,这些解决方案对我来说看起来都不是很漂亮,或者对于编译器来说都是正确的。 经过40分钟的时间之后,我在Go包中time.Since(start) / time.Millisecond了另一个方法,称为time.Since(start) / time.Millisecond并直接使用了Swift的结果。


我还编写了txqr-ascii控制台实用程序来进行快速的应用程序测试。 它对文件进行编码,并在终端中对QR码进行动画处理。 总体来说效果很好,我可以在几秒钟内发送一张小图片,但是一旦我开始测试不同的帧率值,每个QR帧中的字节数以及QR编码器中的纠错级别,就很明显,终端解决方案并不是应付动画的高频(超过10个),手动测试和测量结果是一件灾难性的事情。


TXQR测试仪



为了在这些值的合理范围内找到帧速率,QR帧中的数据大小和纠错级别的最佳组合,我必须运行1000多次测试,手动更改参数,等待手机在手的整个周期并将结果写到板上。 当然,这必须是自动化的!


在这里提出下一个应用程序的想法txqr-tester 。 最初,我计划使用x / exp / shiny-用于Go上本机桌面应用程序的实验性UI框架,但它似乎已被放弃。 大约一年前,我尝试了一下,印象还不错-非常适合低级的东西。 但是今天,master分支甚至还没有被编译。 似乎不再有任何动机去投资开发桌面框架了-一项复杂而繁琐的任务,如今的需求几乎为零,所有UI解决方案早已在网上出现。


如您所知,在Web编程中,由于WebAssembly,编程语言才刚刚开始普及,但这仍然是儿童的第一步。 当然,也有JavaScript和附加组件,但是朋友不允许朋友使用JavaScript编写应用程序,因此我决定使用我的最新发现-Vecty框架,该框架使您可以在纯Go上编写前端,使用非常成熟且工作正常的Go自动将其转换为JavaScript。 GopherJS项目。


Vecty和GopherJS


Vecty


在我的一生中,前端接口的开发并没有让我如此高兴。


过一会儿,我计划再写几篇关于我在Vecty上开发前端(包括WebGL应用程序)的经验的文章,但是最重要的是,在React,Angulars和Ember上进行了多个项目之后,用一种周到且简单的编程语言编写前端是一种新鲜的呼吸。空气! 我可以在短时间内编写漂亮的前端,而无需在JavaScript中编写任何一行!


对于初学者,这是您在Vecty上启动新项目的方式(没有“初始项目”代码生成器会创建大量文件和文件夹),而仅是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) } 

应用程序就像任何UI组件一样,只是一种类型:一种结构,其中包含vecty.Core类型,并且必须实现vecty.Component接口(由一个Render()方法组成)。 仅此而已! 然后,您可以使用类型,方法,函数,用于DOM的库等,而无需任何隐藏的魔术和新的术语和概念。 这是主页的简化代码:


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

您可能现在正在看代码并思考-与DOM无关的工作有多少! 一开始我也这么认为,但是当我开始工作时,我意识到这是多么的方便:


  1. 没有魔术-每个块(标记或HTML)只是所需类型的变量,由于使用了静态键入,因此可以在其中放置内容的限制很明确。
  2. 没有任何开始/结束标签,您必须记住在重构时进行更改,或者使用为您执行此操作的IDE。
  3. 结构突然变得清晰起来-例如,我一直不明白为什么在React中,直到第16版,才不可能从组件返回多个标签-这仅仅是“一个字符串”。 看到在Vecty中是如何完成的,突然变得很清楚,该约束的根源出现在React中。 都是一样的,但是还不清楚为什么在React 16之后它成为可能,但不是必需的。

通常,一旦尝试使用这种方法来处理DOM,它的优势就会变得非常明显。 当然也有缺点,但是在常规方法的缺点之后,它们是不可见的。


Vecty被称为类似React的框架,但这并非完全正确。 有一个用于React的本地GopherJS库-myitcv.io/react ,但我认为对Go重复React架构解决方案不是一个好主意。 当您在Vecty上编写前端时,突然变得很容易理解。 突然,所有这些隐藏的魔术以及每个JavaScript框架发明的新术语和概念变得多余了-它们只是增加了复杂性 ,仅此而已。 所有需要做的就是清楚,清楚地描述组件,它们的行为,并将它们连接在一起-类型,方法和功能,仅此而已。


对于CSS,我使用了一个令人惊讶的体面的Bulma框架-它具有非常清晰的类命名和良好的结构,并且声明性UI代码非常易于阅读。


但是,真正的魔力始于在JavaScript中编译Go代码时。 听起来很吓人,但实际上,您只需调用gopherjs build然后不到一秒钟的时间,您就可以将自动生成的JavaScript文件包含在基本HTML页面中(常规应用程序仅包含一个空的body标签,并且包含了此标签JS脚本)。 当我第一次运行此命令时,我希望看到很多消息,警告和错误,但是不会-它以超乎寻常的速度和静音运行,仅在Go编译器产生编译错误的情况下才打印单行代码,因此非常清楚。 但是在浏览器控制台中看到错误的感觉更酷,堆栈跟踪指向.go文件和正确的行! 这很酷。


测试QR动画参数


我花了几个小时准备好一个Web应用程序,该应用程序使我能够快速更改测试参数:


  • FPS-帧率
  • QR帧大小-每帧应有多少字节
  • QR恢复级别-QR错误校正级别

并自动运行测试。


应用程式


当然,移动应用程序也必须是自动化的-它必须了解下一轮何时以新参数开始,了解接收何时花费太多时间并中断回合,将结果发送给应用程序,依此类推。


问题在于,Web应用程序在浏览器沙箱中运行时无法创建新的连接,如果我没记错的话,与浏览器建立真正的对等连接的唯一可能性是仅通过WebRTC(我不需要对NAT进行打孔) ),但太麻烦了。 该Web应用程序只能是一个客户端。


解决方案很简单-交付Web应用程序(并将浏览器启动到所需URL)的Go Web服务还启动了两个客户端的WebSocket代理。 一旦有两个客户端加入,它将透明地从一个连接发送消息到另一个连接,从而允许客户端(Web应用程序和移动客户端)直接通信。 当然,它们必须在一个WIFI网络中。


问题是如何告诉移动设备实际上在哪里连接,并使用... QR代码解决了!


测试过程如下:


  • 移动应用程序正在寻找带有开始标记和WebSocket代理链接的QR码
  • 读取令牌后,应用程序将立即连接到此WebSocket代理
  • Web应用程序(已连接到代理)理解该移动应用程序已准备就绪,并显示带有标记“准备下一轮吗?”的QR码。
  • 移动应用程序识别该信号,重置解码器,并通过WebSocket发送消息“是”。
  • 收到确认的Web应用程序将生成新的QR动画并将其扭曲,直到收到结果或超时为止。
  • 结果将添加到板中,您可以在其旁边立即将其以CSV格式下载

设计图


结果,我所要做的就是简单地将手机放在三脚架上,启动应用程序,然后两个程序自己完成所有肮脏的工作,通过QR码和WebSocket礼貌地进行交流:)


测试仪演示


最后,我下载了包含结果的CSV文件,将其驱动到RStudio和Plotly Online Chart Maker并分析了结果。


结果


整个测试周期大约需要4个小时(不幸的是,该过程中最困难的部分-生成带有QR帧的GIF动画图像,必须在浏览器中运行,并且由于生成的代码仍在JS中,因此仅使用一个处理器)您必须注意其中的内容,以免屏幕突然变黑或某些应用程序无法关闭Web应用程序的窗口。 测试了以下参数:


  • FPS-3至12
  • QR帧的大小为100到1000字节(以50为增量)
  • QR纠错的所有4个级别(低,中,高,最高)
  • 传输的文件大小-13KB随机生成的字节

几个小时后,我下载了CSV并开始分析结果。


图片比一千个单词更重要,但是交互式3D可视化比一千个图片更重要。 这是结果的可视化(可单击):


qr_scan_results


最好的结果是1.4秒,大约是9KB / s! 记录此结果的频率为每秒11帧,850字节的帧大小以及平均错误校正级别。 但是,在大多数情况下,以这种速度,摄像机解码器会跳过一些帧,而不得不等待丢失帧的下一个重复,这对结果产生了非常不利的影响-而不是两秒钟,它很容易变成15,或者将超时设置为30秒。


以下是结果对变量变量的依存关系图:


帧时间/大小


时间与大小


如您所见,在每帧字节数较低的情况下,多余的编码分别太大,总读取时间也太大。 每个帧有一些局部最小值500-600字节,但是它旁边的值仍然会导致丢失帧。 在900字节处观察到最佳结果,但几乎可以保证1000及以上的帧丢失。


时间/ FPS


时间与FPS


令我惊讶的是,每秒帧数的值并没有太大影响-小值会增加整体传输时间,而大值会增加丢失帧的可能性。 通过这些测试判断,对于我测试过的那些设备,最佳值在每秒6-7帧的范围内。


时间/纠错等级


时间vs等级


错误纠正级别显示了文件传输时间与冗余级别之间的明确关系,这并不奇怪。 明显的赢家是低(L)校正级别-冗余数据越少,具有相同数据大小的扫描仪的QR码越可读。 实际上,此实验完全不需要冗余,但是标准不提供这种选择。


当然,对于更客观的数据,该测试应该在不同的设备和不同的屏幕上运行成千上万次,但是对于我的周末实验来说,结果绰绰有余。


结论


这个有趣的项目证明了通过动画代码进行单向数据传输肯定是可能的,并且对于需要在没有任何类型的网络的情况下进行少量传输的情况,这是非常合适的。 尽管我的最大结果约为9KB / s,但在大多数情况下,实际速度为1-2KB / s。


我也非常喜欢将Gomobile和GopherJS与Vecty一起用作常规的问题解决工具。 这些都是非常成熟的项目,具有出色的工作速度,并且在大多数情况下,可以提供“确实有效”的经验。


最后,当您清楚地知道要实现的目标时,我仍然很欣赏Go的生产力-极短的周期“更改”-“汇编”-“检查”使您可以进行很多次而且很频繁的尝试,简单的代码和结构中没有类层次结构程序使您可以在旅途中轻松轻松地进行重构,而从一开始就嵌入在该语言中的出色跨平台使您只需编写一次代码,即可在服务器,Web客户端和本机移动应用程序中使用它。 同时,尽管开箱即用的性能超过了足够的性能,但仍有大量空间可用于优化和加速。


因此,如果您从未尝试过Gomobile或GopherJS-我建议您尝试下一个机会。 这将花费一个小时的时间,但可能会为Web或移动开发带来全新的机会。 随时尝试!


参考文献


Source: https://habr.com/ru/post/zh-CN430688/


All Articles