欢迎光临散文网 会员登陆 & 注册

10-对 part8-breakout-ble 的翻译和搬运

2023-08-15 22:59 作者:Xdz-2333  | 我要投稿

非黑色字体均为我自己添加

图均为原文中的图

原文在文章末尾

接收蓝牙数据

        所以我们掌握了广播技术并且向外广播了我们的数据.但那只是故事的一部分!在这个部分中,我们将要探索如何从另外一个源接收数据.这个更令人兴奋,因为我们要开始使用其他设备作为远程控制器.

        实际上,我的主意是通过接收来自我的MacBook Pro的触控板上的蓝牙信息来控制part6中我们写的breakout游戏.很好,不是吗?

建立一个蓝牙Breakout控制器

        让我们首先建立用于从电脑上广播的代码.我们本质上需要在代码中创建一个BLE外设.

        我们不用从草稿开始建立起所有东西,因为我们不再是裸机了!我使用Bleno库(https://github.com/noble/bleno 这玩意好像停止更新了),它在这种工作中是极为流行的.它需要一些Node.js(https://nodejs.org/en/download)的知识,但是我再开始之前也没有并且我相信你也可以.

        你装好Node.js之后,使用npm命令 npm install bleno 来安装Bleno .因为我用的是Mac,并且运行最新的Mac OS X 版本(大于Catalina),我需要使用如下三步(https://punchthrough.com/how-to-use-node-js-to-speed-up-ble-app-development/):

  • npm install github:notjosh/bleno#inject-bindings

  • npm install github:notjosh/bleno-mac

  • npm install github:sandeepmistry/node-xpc-connection#pull/26/head

        我是用Bleno库中的echo的例子(https://github.com/noble/bleno/tree/master/examples/echo)作为我的基本代码.这个例子实现了蓝牙外设,暴露了这些服务:

  • 让一个连接上的设备读取一个本地存储的字节

  • 让一个连接上的设备更新本地存储的字节(它也能被从本地修改)

  • 当本地存储的字节发生变化时,让一个连接上的设备接收更新

  • 让一个连接上的设备取消接收更新

        当你知道我的设计是让树莓派接收在我电脑上运行的服务的更新时并不会惊讶.本地存储的字节的值会在本地更新来反映当前变化时的鼠标状态.每当我移动我MacBookPro的鼠标时,树莓派都会被通知,就像有魔法一样!

        你可以看到我对Bleno的echo 例子所做的改动,它在part8-breakou-ble中的controller子文件夹中.它终究是使用 iohook(https://github.com/wilix-team/iohook),所以我使用 npm install iohook 安装了iohook.这些是有趣的位(其他的比较无聊).:

var ioHook = require('iohook');


var buf = Buffer.allocUnsafe(1);

var obuf = Buffer.allocUnsafe(1);

const scrwidth = 1440;

const divisor = scrwidth / 100;


ioHook.on( 'mousemove', event => {

   buf.writeUInt8(Math.round(event.x / divisor), 0);


   if (Buffer.compare(buf, obuf)) {

      e._value = buf;

      if (e._updateValueCallback) e._updateValueCallback(e._value);

      buf.copy(obuf);

   }

});


ioHook.start();

        在这里,我捕捉了鼠标的x坐标,并且把它转换成0(屏幕左边)到100(屏幕右边)之间的数字.如果它从我们之前看到的数值变化,我们更新回调的值.(我们的树莓派只需要知道什么时候位置更新了).只要回调函数更新,那么任何连上的设备都将被通知.

        我们有一个自己的蓝牙游戏控制器.尽管有些粗糙.你可以使用命令 node main.js 来运行它,但是它不会做什么,如果没有东西连上的话.

让树莓派连上Breakout控制器

        main.js,当它运行的时候,在那里广播蓝牙调制调解器是空闲可用的,和我们的埃迪斯通灯塔是一样的技术.现在我们树莓派这边的任务是:

  • 开始监听(或者说扫描)这些广播,这样我们可以知道哪里echo服务可以被找到

  • 找到它就连到echo服务上

  • 订阅来接收鼠标位置的更新

  • 监听这些更新,一旦接收到后就做些事情

        记得我提到了我们在part7-Bluetooth中所讨论过的 run_search函数吗?它就是做这个的.这次注释掉 run_eddystone() 函数并且让 run_search() 运行,当你的echo服务在电脑上运行时.当你的手指在触控板上滑动时,你应该在树莓派上看到更新!

       run_search() 使蓝牙控制器进入扫描模式而不是广播(看到part7-Bluetooth中的链接来了解这是怎么做到的). kernel.c 中的 bt_search() 被反复调用直到它接收到了特殊的广播数据 -- 即注意到了echo 服务的服务ID空闲(十六进制代码位 0xEC00),即它的广播名字"echo" .如果它看见了相同的广播报告,然后它就假设它找到目标.原始的蓝牙设备地址会被记录.

        我们发送 connect() 来要求地址(TI的文档中LE创造一个链接)并且现在反复调用 bt_conn() 知道我们被通知链接成功完成.当这个完成后,我们得到一个非零的 connection_handle . 我们从现在使用这个来确认 来自/到 echo 服务的通信.

        接下来我们使用在 bt.c 文件里的sendACLsubscribe() 中的这个句柄来发送订阅请求.我们告诉他对接收它存储的值(或者字符)的更新 感兴趣.我实际上做了很多反向工程来得到这些代码.HCL之上的ACL 数据包并没有很多文档.读一下这个(https://forums.raspberrypi.com//viewtopic.php?t=233140)来了解我是如何成功的.树莓派官方操作系统上的 gettool 和 hcitool 被证明是我的好朋友!

        最后,我们反复调用 acl_poll() 来看这里是否有更新在等待.到达我们这里的数据是 ACL 包的形式,除了其他东西,它识别它向其发送/使用的通信的句柄(值得与我们记录的句柄进行核对,这样我们直到它是为我们发的)和长度以及操作码.

原作者给出的ACL格式说明图

        操作码 0x1B 代表了 "ATT 句柄值提醒" (TI 文档中的 ATT_HandleValueNoti ).这就是我们要找的更新 在part7里面只是单纯的打印出更新值来展示它已经被接收到了.

最后一公里

        有了这个,这样part6和part7的代码就能很好的运行,合并他们来创造一个可以运行的通过蓝牙控制的Breakout实现!毕竟这正是我最终得到part8代码的原因……如果你卡住了,都在我的仓库里.

        好运!下次,我们将着眼于在游戏玩法中添加声音……


Receiving Bluetooth data

So we've mastered advertising and we're broadcasting data out into the World. But that's only half the story! In this part, we'll be exploring how to receive data from an external source. This is much more exciting as we can begin to use other devices as remote controllers.


In fact, my idea for this part is to control the Breakout game we wrote in part6 by receiving data from the trackpad on my MacBook Pro over Bluetooth! Neat, eh?


Building a Bluetooth Breakout controller

Let's first build the controller code for broadcasting from the laptop. We essentially need to create a BLE peripheral in code.


We don't need to build everything from scratch since we're not on bare metal any more! I used the [Bleno library](https://github.com/noble/bleno), which is extremely popular for this kind of work. It requires some [Node.js](https://nodejs.org/en/download/) knowledge, but I didn't have any before I started and so I'm sure you'll be fine too.


Once you've got Node.js installed, use `npm` to install Bleno with `npm install bleno`. Because I'm on a Mac and running a recent version of Mac OS X (> Catalina), I needed to do this using [these three steps](https://punchthrough.com/how-to-use-node-js-to-speed-up-ble-app-development/) instead:


 * `npm install github:notjosh/bleno#inject-bindings`

 * `npm install github:notjosh/bleno-mac`

 * `npm install github:sandeepmistry/node-xpc-connection#pull/26/head`


I used the [echo example](https://github.com/noble/bleno/tree/master/examples/echo) in the Bleno repository as my base code. This example implements a Bluetooth peripheral, exposing a service which:


 * lets a connected device read a locally stored byte value

 * lets a connected device update the locally stored byte value (it can be changed locally too...!)

 * lets a connected device subscribe to receive updates when the locally stored byte value changes

 * lets a connected device unsubscribe from receiving updates


You won't be surprised to know that my design is for our Raspberry Pi to subscribe to receive updates from this service running on my laptop. That locally stored byte value will be updated locally to reflect the current mouse cursor position as it changes. Our Raspberry Pi will then be notified every time I move the mouse on my MacBook Pro as if by magic!


You can see the changes I made to the Bleno echo example to implement this in the _controller_ subdirectory of this part8-breakout-ble. They boil down to making use of [iohook](https://github.com/wilix-team/iohook), which I installed using `npm install iohook`. Here's the interesting bit (the rest is just plumbing):


```c

var ioHook = require('iohook');


var buf = Buffer.allocUnsafe(1);

var obuf = Buffer.allocUnsafe(1);

const scrwidth = 1440;

const divisor = scrwidth / 100;


ioHook.on( 'mousemove', event => {

   buf.writeUInt8(Math.round(event.x / divisor), 0);


   if (Buffer.compare(buf, obuf)) {

      e._value = buf;

      if (e._updateValueCallback) e._updateValueCallback(e._value);

      buf.copy(obuf);

   }

});


ioHook.start();

```


Here, I'm capturing the x coordinate of the mouse cursor and translating it into a number between 0 (far left of the screen) and 100 (far right of the screen). If it changes from the previous value we saw, we update the callback value (our Raspberry Pi only needs to know when the position has changed). As the callback value is updated, so any subscribed devices will be notified.


And we have ourselves a working, albeit a bit hacky, Bluetooth game controller! You can run it with the command `node main.js`, but it won't do much without something to connect to it.


Connecting to our Breakout controller from the Raspberry Pi

_main.js_, when running, is sitting there broadcasting the Breakout controller's availability publicly, using exactly the same techniques that our Eddystone beacon used. Our tasks on the Pi are now:


 * to start listening (called "scanning") for these adverts so we know where the echo service can be found

 * to connect to the echo service, having found it

 * to subscribe to receive its updates on cursor position

 * to listen for these updates and do something upon receipt


Remember I mentioned that we'd discuss the `run_search()` function in part7-bluetooth? Well, this is exactly what it does. This time comment out the `run_eddystone()` function and let `run_search()` run whilst your echo service is running on the laptop. As you wiggle your finger on the trackpad, you should see the updates coming through on the Pi!


Instead of advertising, `run_search()` puts the Bluetooth controller into scanning mode (see links in part7-bluetooth to learn more about how this is done). `bt_search()` in _kernel.c_ is then called repeatedly until it receives some specific advertising data - namely a notification of the availability of the echo service's Service ID (hexadecimal number 0xEC00), as well as its broadcast name 'echo'. If it sees both in the same advertising report then it assumes it's found what it's looking for. The originating Bluetooth Device Address is noted.


We send a `connect()` request to that address (LE Create Connection in the TI docs) and now call `bt_conn()` repeatedly until we're notified that the connection has completed successfully. When this happens, we get a non-zero `connection_handle`. We'll use this to identify communications from/to the echo service from now on.


Next we send a subscription request to the service using that handle in `sendACLsubscribe()` in _bt.c_. We tell it that we're interested in receiving updates to its stored value (or "characteristic"). I actually did a lot of reverse-engineering to get to this code. ACL data packets over HCI are not widely documented. Have a read of [this forum thread](https://www.raspberrypi.org/forums/viewtopic.php?t=233140) to see the kind of things I did to succeed. `gatttool` and `hcitool` on Raspbian turned out to be my very good friends!


Finally, we call `acl_poll()` repeatedly to see if there are any updates waiting. The data comes to us in the form of an ACL packet, which identifies, amongst other things, the connection handle it was sent to/using (worth checking against our recorded handle so we know it's for us) as well as data length and an opcode. 


![ATT handle value notification opcode 1b](images/8-opcode-1b.png)


The opcode 0x1B represents a "ATT handle value notification" (ATT_HandleValueNoti in the TI docs). Those are the updates we're looking for. In part7 we simply print the update to debug to show it's been received.


The last mile

With this, it's a good exercise to take part6 and part7 code and merge them to form a working Breakout implementation that's controlled via Bluetooth! After all, that's exactly how I ended up with the part8 code... If you get stuck, it's all in my repo.


Good luck! Next time, we'll look at adding sound to the gameplay...



10-对 part8-breakout-ble 的翻译和搬运的评论 (共 条)

分享到微博请遵守国家法律