65.9K
CodeProject 正在变化。 阅读更多。
Home

LightBlue Beans - 魔法豆和无豆[通话]?

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.98/5 (19投票s)

2014年11月4日

CPOL

16分钟阅读

viewsIcon

46443

downloadIcon

251

低功耗蓝牙遇上Arduino兼容微控制器,小巧的尺寸,这难道只是一个童话故事吗?

警告:本文及相关代码可能被归类为实验/研究性质。随着事情的进展,您将明白原因……

物联网竞赛第二阶段 - 前言

当您开始更深入地接触设备,或者正在处理新上市的设备时,身边可能没有现成的人来指导您。也可能没有“第一步、第二步、第三步”之类的文章和教程,即使有,也可能离您想做的东西还有很长的距离,您常常不得不卷起袖子,亲自动手。可能会有陡峭的学习曲线,需要钻研类似领域的示例代码,看看是否能获取到一点点数据,哪怕只是一点点流量,也许就足以说明“我们正在取得进展”。也许您正在处理从未开发过的新事物,或者需要将大量事物整合在一起以获取结果,无论是什么,最好的方法就是 **动手去做!**

请继续阅读,了解其中一篇此类教程/文章……

引言

微控制器无处不在,无论是树莓派、Arduino、Beaglebone(仅举几个常见的例子),它们无疑已经激发了业余爱好者、创客、发明家或您想称呼他们的任何人的想象力。您只需搜索网络,就能看到有多少网站专门介绍这些开发板以及在此基础上构建项目的各位大神。

您只需看看我的文章列表,就能发现一些与此主题相关的文章。

有一天,我在网上闲逛时,偶然发现了 LightBlue Beans,这些小巧的设备看起来很有趣,所以为什么不成为支持者,获取一些东西来进一步开发呢。

Bean,登场!

我为“创客套件”支付了预生产支持者付款,套件包含 4 个 Beans、备用电池、电阻、LED、蜂鸣器、双节 AA 电池盒、一些插针、粘性垫和扎带。包括运费到英国,总计 80 美元。现在(一年后)查看他们的网站,Beans 的零售价为每个 30 美元。下面是收到的创客套件

Beans 本身装在一个紧凑的填充盒子里,每个都不比火柴盒大,是的,就是那个大小单位,虽然有很多变化,但每个人都大致知道!拿出 Bean,您就会发现这些东西确实很紧凑!每个 Bean 的尺寸为 45 x 20 x 8 毫米。正如您在下面看到的,与 AA 电池相比,它们确实很小。

在上图中,您可以看到 IC 芯片在一侧,电池在一侧。并且有许多用于连接元件的焊盘。所有焊盘都通过电路板连接。

输入-输出和通信

这些开发板的关键卖点在于无线通信。它们设计用于通过低功耗蓝牙 (BT LE) 进行编程和与外部世界通信。这都没问题,但您确实需要将它们连接到其他东西,无论是传感器还是开关。正如您在上图中所见,PCB 上的一些焊盘标记为 A0、A1、0、1、2、3、4、5。您会正确地认为这些是模拟和数字引脚。然而,它们是多功能的,并且还支持脉冲宽度调制 (PWM) 以及通过集成电路 (I2C)、串行外设接口 (SPI) 和在线串行编程 (ICSP) 协议进行通信。下图显示了引脚的多功能性以及它们的分配方式;

除了这些焊盘,还有一些标记为 BAT、GND、R (Reset) 和 VCC 的焊盘,PCB 上集成了一个 RGB LED。该 LED 位于上图 BAT 焊盘(底部中间)的左侧。

注意:当您第一次为 Bean 上电时,LED 会短暂闪烁,因为开发板正在自我初始化。

但是等等!还有更多,它还内置了温度传感器和加速度计,并且还可以读取电池的电压状态。提供温度传感器和加速度计的 IC 是博世 BMA250,有关芯片规格的更多信息可在数据手册 [0] 中找到。

入门

Beans 出厂时是通电的,所以从技术上讲,在您将它们从包装袋中取出之前,您应该就能与它们通信了。

这时我遇到了第一个问题。我的 Google Nexus 手机和 Google Nexus 10 平板电脑都无法检测到 Beans。也许电池没电了,不太可能但有可能。事实上,这是由于蓝牙的规格问题,它们与这些设备不兼容。它们不算太旧,但可惜,已经老到无法满足要求了!我的电脑有一个旧的 Belkin 蓝牙适配器,再次尝试,它也无法检测到 Beans,尽管这在意料之中,因为它太老了!

最后,我订购了一个新的 Asus USB-BT400 兼容适配器;

编程和固件更新

通过蓝牙(目前)在 Windows 上不可行

从一开始就很清楚,Beans 的开发者最初似乎只专注于一个平台,那就是 iOS。我的 Apple 产品很少,只有几台 iPod 和一台 AppleTV,这些设备都不适合为 Beans 编程。开发者声称将在 2014 年 11 月发布适合在 Windows 电脑上运行的 BeanLoader 软件。

由于目前无法通过无线方式编程,我不得不寻找替代方法来编程它们。最终这意味着要考虑 ISP 编程选项。

我购买了一个 Deek Robot USBtinyISP 编程器(上图),这些编程器可以在 eBay 和各种其他电子产品网站上找到。

幸运的是,已经有一份指南 [1] 整理好了,因此按照指南说明,从 Arduino 网站 [2] 下载最新软件并将其解压到 PC 上的一个文件夹中,然后获取必要的支持文件和编程器驱动程序 [3],我们就可以开始工作了!

Arduino 测试草图下载

如果我们连简单的草图都无法下载到 Arduino,那么我们将面临一段颠簸的旅程,所以第一次尝试时,让我们保持简单。对于初始的设置测试,我将只对 Bean 进行编程,使其在红-绿-蓝-熄灭之间循环,每 1 秒切换一次。

执行此操作的代码如下;

//Demo Program 
// rotate the onboard LED through Red - Green - Blue - Off
// 1 second interval between steps
void setup(){
  Serial.begin(57600); 
}
void loop(){
 Bean.setLed(128,0,0);
 delay(1000);
 Bean.setLed(0,128,0);
 delay(1000);
 Bean.setLed(0,0,128);
 delay(1000);
 Bean.setLed(0,0,0);
 delay(1000);
}

Serial.Begin(57600) 配置 Bean 上的串行通信。串行总线不仅允许 Arduino 与外部世界通信,还允许与蓝牙模块进行内部消息传递。

Bean 类用于告诉 Bean 硬件执行特定操作。有许多可用函数,范围涵盖蓝牙特定功能、LED 控制、电池状态、温度传感器和加速度计传感器。

setLed 命令接受 3 个参数,分别代表红色、绿色和蓝色值。这些值可以在 0(关闭)到 255(完全开启)之间。我只是将其设置为中间值,这可以让我了解板载 LED 的亮度。结果证明,在正常日照条件下,128 亮度刚刚好。

delay 命令是一个标准的 Arduino 函数,它需要指定的毫秒数来暂停执行。有一个类似的 Bean.sleep() 方法,但它会将 Arduino 芯片物理睡眠,而 Bean 的其余部分会保持唤醒状态。

代码随后被验证并成功下载到 Bean,板载 LED 开始循环变色。

TinyISP 编程器通过 6 针插头使用跳线连接到 Bean,如下所示;

 AVR  | Bean
------|------
RST  -> R
SCK  -> 5
MISO -> 4
MOSI -> 3
GND  -> GND

警告:在此实例中,我将电池连接到了 Bean,因此没有使用编程器提供的电源。它已断开电源跳线,并且没有从 VCC 到 Bean 的跳线。如果您最终连接了一个插座等以便于上传,请小心电源状态,否则您可能会损坏 Bean。

下面是下载过程的动画 GIF 录像;

注意: 上图缺少代码中显示的 Serial.Begin(57600) 指令。这是我的疏忽,但草图仍然成功下载并运行,因为不需要与蓝牙模块通信。可以肯定地说,我应该包含该语句,所以这是以后需要记住的。

第一次测试竟然如此顺利。希望下一次测试也能同样顺利!

固件更新

在研究和阅读 Beantalk 论坛时,我注意到一个帖子讨论了更新 Bean 上 AT-Mega 的固件。事实证明,可以使用与下载 Arduino 草图相同的连接方法来实现这一点。然而,区别在于我们使用的是 AVR 编程应用程序。在此实例中,我使用了 AVR-Dude。

我遵循了 LadyAda [4] 提供的教程,并使用 Windows 安装程序将软件安装到一个名为 AVR 的文件夹中,该文件夹位于 Arduino Tools 文件夹下。

下载了 Beantalk 论坛上的新固件后,我打开了一个命令提示符,并使用了以下命令来证明我与设备通信良好;

C:\Users\Dave\Desktop\LightBlueBean\arduino-1.0.6-windows\arduino-1.0.6\tools\avr\bin>avrdude -c usbtiny -p m328p

您可以在上面的命令中看到,我们必须指定要连接的编程器类型以及要连接的处理器类型。

控制台回复如下;

avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.01s

avrdude: Device signature = 0x1e950f

avrdude: safemode: Fuses OK

avrdude done.  Thank you.

现在我们可以使用以下命令安装固件;

C:\Users\Dave\Desktop\LightBlueBean\arduino-1.0.6-windows\arduino-1.0.6\tools\avr\bin>avrdude -p m328p -c usbtiny -v -v -v -v  -U flash:w:C:\Users\Dave\Desktop\LightBlueBean\Bean_Arduino_FW_1_1_0\ATM328P_PTD_PTM_RELEASE_1_1_0.hex:i -U lfuse:w:0xff:m -U hfuse:w:0xdc:m -U efuse:w:0x07:m

这里有很多操作,我并不完全理解。幸运的是,固件文件附带了一个自述文件,其中说明了如何设置各种熔断器参数等。

下面是一个捕捉固件编程和验证阶段的动画 GIF。固件更新大约需要 2 分钟。

配对与 Windows 通信

现在第一个 Bean 已经运行并能接受 Arduino 草图,下一个测试是尝试通过蓝牙从 Bean 获取数据。

由于该设备内置温度传感器,我想将其用于监控我的服务器机架或外部温度等,所以我们将首先尝试这样做。

安装蓝牙适配器后,您需要将 Bean 与 Windows 配对。这可以通过蓝牙图标上下文菜单中的“添加蓝牙设备”来完成;

这将打开 Windows 蓝牙屏幕,并开始搜索设备。默认名称是“Bean”,一旦出现,选择 Bean 并单击配对。默认 PIN 码是“00000”。配对 Bean 后,它将显示为已连接。

在 Windows 上,整个蓝牙体验感觉有点“笨拙”,Beans 有时无法配对,需要拔插蓝牙适配器才能解决问题。

我还注意到,如果一个 Bean 已配对并显示为已连接,如果您断开了 Bean(例如通过取出电池),它将不会自动重新连接,除非您“删除设备”并重新进行配对过程。

Windows 演示应用程序

阅读 Bean 网站上的各种消息,它提到可以使用 BeanLoader 软件为 Bean 启用虚拟串行端口。遗憾的是,此功能在 Windows 上尚未提供。这让我别无选择,只能尝试使用蓝牙串行服务与设备通信,然后读取和解码消息。串行消息协议的基本文档 [5] 仍然可用,但仍然很难理解。

理解串行消息协议

在我们开始实际的 Windows 演示应用程序之前,让我们花点时间来理解串行协议。起初,我使用了演示 Arduino 草图,它只输出了温度。设备传感器上的摄氏度以单字节表示。

应用程序收到的消息是;

C0-03-00-00-00-1C-63-2C
E0-03-00-00-00-1B-84-5C

结果发现 1B1C 只是 27 28 Degrees C 的十六进制值。起初,我完全不知道字节序列中的内容,所以我做了一些其他测试来尝试理解消息。

我发送了一个简单的循环在 Bean 中运行,并输出了 1 到 9 的值。返回的消息是;

A0-03-00-00-00-01-FF-EF
C0-03-00-00-00-02-9C-DF
E0-03-00-00-00-03-BD-CF
80-03-00-00-00-04-5A-BF
A0-03-00-00-00-05-7B-AF
C0-03-00-00-00-06-18-9F
E0-03-00-00-00-07-39-8F
80-03-00-00-00-08-D6-7E
A0-03-00-00-00-09-F7-6E

接下来我输出了一个字符串;

Sending: Serial.write("Hello");
Received:
C0-07-00-00-00-48-65-6C-6C-6F-0F-62
               H  e  l  l  o

您清楚地看到了 ASCII 字符的十六进制值,开始有意义了!

接下来,我输出了一个稍长的字符串,看看当超过 20 字节的数据包长度时会发生什么;

Sending: Serial.write("Hello World. This is a Bean!");
Received:
81-1E-00-00-00-48-65-6C-6C-6F-20-57-6F-72-6C-64-2E-20-54-68
               H  e  l  l  o     W  o  r  l  d  .     T  h 
00-69-73-20-69-73-20-61-20-42-65-61-6E-21-25-F4
   i  s     i  s     a     B  e  a  n  !

太好了,现在我可以看到多数据包消息是如何处理头部信息和校验和的。

好的,让我们进一步推动。最大消息大小为 70 字节,这意味着我需要 4 个数据包才能看到完整的规范实现,即消息计数器向下滚动以及头部等的位置;

Sending Loop of: Serial.write("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"); 
Received:
C3-40-00-00-00-30-31-32-33-34-35-36-37-38-39-41-42-43-44-45
42-46-47-48-49-4A-4B-4C-4D-4E-4F-50-51-52-53-54-55-56-57-58
41-59-5A-61-62-63-64-65-66-67-68-69-6A-6B-6C-6D-6E-6F-70-71
40-72-73-74-75-76-77-78-79-7A-E9-6B
E3-40-00-00-00-30-31-32-33-34-35-36-37-38-39-41-42-43-44-45
62-46-47-48-49-4A-4B-4C-4D-4E-4F-50-51-52-53-54-55-56-57-58
61-59-5A-61-62-63-64-65-66-67-68-69-6A-6B-6C-6D-6E-6F-70-71
60-72-73-74-75-76-77-78-79-7A-E9-6B
83-40-00-00-00-30-31-32-33-34-35-36-37-38-39-41-42-43-44-45
02-46-47-48-49-4A-4B-4C-4D-4E-4F-50-51-52-53-54-55-56-57-58
01-59-5A-61-62-63-64-65-66-67-68-69-6A-6B-6C-6D-6E-6F-70-71
00-72-73-74-75-76-77-78-79-7A-E9-6B
A3-40-00-00-00-30-31-32-33-34-35-36-37-38-39-41-42-43-44-45
22-46-47-48-49-4A-4B-4C-4D-4E-4F-50-51-52-53-54-55-56-57-58
21-59-5A-61-62-63-64-65-66-67-68-69-6A-6B-6C-6D-6E-6F-70-71
20-72-73-74-75-76-77-78-79-7A-E9-6B

很好,对于最后的测试,我将长度推到了 70 字节以上,结果只是多余的字符简单地开始了一个新的消息数据包序列。

利用从这些测试中获取的信息,我能够完全理解头部、开始消息、消息计数器以及校验和的位置,从而能够提取发送的数据。

提取实际数据的规则是;

IF: 1) Byte 1, Bit 8 (MSB) = 1, this is a start message, first 5 bytes are headers
 &  2) Byte 1, Bits 1,2,3,4,5 = 0  then last 2 Bytes are checksums
    3) Remaining bytes are data
  OR
IF: 1) Byte 1, Bit 8 (MSB) = 1, this is a start message, first 5 bytes are headers
 &  2) Byte 1, Bits 1,2,3,4,5 > 0, then remaining bytes are data
  OR
IF: 1) Byte 1, Bit 8 (MSB) = 0, this is a secondary packet, first byte is header
    2) Byte 1, Bits 1,2,3,4,5 = 0 then last 2 Bytes are checksums
    3) Remaining bytes are data
  OR
IF: 1) Byte 1, Bit 8 (MSB) = 0, this is a secondary packet, first byte is header
 &  2) Byte 1, Bits 1,2,3,4,5 > 0, then remaining bytes are data

演示应用程序

这将是一个简单的应用程序……非常简单的应用程序!其目的无非是能够通过蓝牙 GATT 串行传输读取数据。

应用程序 GUI 如下所示,运行后点击“查找设备”按钮,它将使用串行服务 GUID 来定位 Beans。然后单击 Bean 将开始在标签中显示数据。

下面是应用程序监听 Bean 消息的演示。Bean 正在运行简单的 getTemperature() 代码,并将字节通过串行链路发送。您将看到字节最初被读取,然后下降到 0,然后在我用反向的“空气除尘器”对其进行冷却以使液体推进剂低于 0°C 时回绕。

该传感器的规格是 8 位,范围是 -40 至 + 87.5°C,getTemperature() 方法返回摄氏度而不是范围内的值,即 0 = -40 且 255 = 87.5,这会导致回绕。希望开发者能修改此实现,使其更易于使用。

图形用户界面

在“查找 Beans”按钮的点击事件中,我们使用了服务文档页面提供的串行服务 GUID 来填充所有找到的 Beans 的列表。

private async void buttonAction_Click(object sender, EventArgs e)
{ 
  buttonAction.Enabled = false; 
 
  var devices = await DeviceInformation.FindAllAsync(
                GattDeviceService.GetDeviceSelectorFromUuid(
                                            BeanMessageService.Instance.BeanSerialServiceID), 
                new string[] { "System.Devices.ContainerId" }); 
 
  listboxDevices.Items.Clear(); 
 
  if (devices.Count > 0) 
  { 
    foreach (var device in devices) 
    { 
      listboxDevices.Items.Add(device); 
    } 
  } 
  else 
  {
    labelStatus.Text = "No Beans Founds!";
    System.Diagnostics.Debug.WriteLine("Could not find any Beans. Please make sure your device is paired and powered on!"); 
  } 
  buttonAction.Enabled = true; 
}

当您在列表框中单击 Bean 设备时,我创建的 Bean Message Service 的一个新实例将被初始化,GUI 将开始监听 Characteristic Value Change 事件,然后更新显示;

private async void listboxDevices_SelectedIndexChanged(object sender, EventArgs e)
{
  buttonAction.Enabled = false;
  var device = listboxDevices.SelectedItem as DeviceInformation;
  labelDeviceName.Text = device.Name;
  labelStatus.Text = "Initializing Devices...";

  BeanMessageService.Instance.DeviceConnectionUpdated += OnDeviceConnectionUpdated;
  await BeanMessageService.Instance.InitializeServiceAsync(device);

  try
  {
    var deviceObject = await PnpObject.CreateFromIdAsync(PnpObjectType.DeviceContainer, device.Properties["System.Devices.ContainerId"].ToString(), new string[] { "System.Devices.Connected" });

    bool isConnected;
    if (Boolean.TryParse(deviceObject.Properties["System.Devices.Connected"].ToString(), out isConnected))
    {
      OnDeviceConnectionUpdated(isConnected);
    }
  }
  catch (Exception ex)
  {
    System.Diagnostics.Debug.WriteLine("Retreiving device properties failed with message: " + ex.Message);
  }
}

以及值更改;

private void Instance_ValueChangeCompleted(BeanMessage data)
{
  //Do Something with the Message Value
  System.Diagnostics.Debug.WriteLine("Client: Value Changed Event");
  System.Diagnostics.Debug.WriteLine("Client RCVD:" + BitConverter.ToString(data.data));

  //Check if we need to invoke a delegate to handle cross threading issues.
  if (this.InvokeRequired)
  {
    this.BeginInvoke(new MethodInvoker(delegate() { Instance_ValueChangeCompleted(data); }));
    return;
  }

  labelStatus.Text = "Bean Sent Data!";
  labelData.Text = BitConverter.ToString(data.data);
}

您会看到,我们还检查是否需要通过代理调用来更新 GUI 组件,因为消息是从另一个线程调用的。

Bean 消息服务

这是一个处理所有蓝牙 GATT 消息的服务。

首先,为了简化将接收到的数据传递给 GUI(或实现 Bean 消息服务的任何内容)的过程,我首先创建了一个 BeanMessage 类,其中包含字节数组。

public class BeanMessage
{
  public byte[] data { get; private set; }

  public BeanMessage(byte[] inData)
  {
    data = inData;
  }
}

在 Bean Message Service 中,我们首先声明所有 GUID、设备服务、特征处理程序和事件。

//Serial Service: A495-FF10-C5B1-4B44-B512-1370F02D74DE
//Bean Transport Characteristic A495-FF11-C5B1-4B44-B512-1370F02D74DE
//Strings below adjusted to the correct format, otherwise Guid() throws a hissy fit.
private const String SerialService = "A495FF10-C5B1-4B44-B512-1370F02D74DE";
private const String BeanTransportCharacteristic = "A495FF11-C5B1-4B44-B512-1370F02D74DE";

//Convert to GUIds
public Guid BeanSerialServiceID = new Guid(SerialService);
private Guid CHARACTERISTIC_UUID = new Guid(BeanTransportCharacteristic);

//The specification at url above state only 1 Characteristic defined.
private const int CHARACTERISTIC_INDEX = 0;

//Setup the Characteristic Configuration to Notify
private const GattClientCharacteristicConfigurationDescriptorValue CHARACTERISTIC_NOTIFICATION_TYPE = GattClientCharacteristicConfigurationDescriptorValue.Notify;

private static BeanMessageService instance = new BeanMessageService();
private GattDeviceService service;
private GattCharacteristic characteristic;
private PnpObjectWatcher watcher;
private String deviceContainerId;

//Events
public event ValueChangedCompletedHandler ValueChangeCompleted;
public event DeviceConnectionUpdatedHandler DeviceConnectionUpdated;

当 Bean Message Service 初始化时,我们必须获取设备的必要服务,然后将其配置为“通知”。将处理通知的事件处理程序附加,并告知我们感兴趣的特征的 GUID。

public async Task InitializeServiceAsync(DeviceInformation device)
{
  try
  {
    deviceContainerId = "{" + device.Properties["System.Devices.ContainerId"] + "}";

    service = await GattDeviceService.FromIdAsync(device.Id);
    if (service != null)
    {
      IsServiceInitialized = true;
      await ConfigureServiceForNotificationsAsync();
    }
    else
    {
      System.Diagnostics.Debug.WriteLine("Access to the device is denied, because the application was not granted access, " +
      "or the device is currently in use by another application.");
    }
  }
  catch (Exception e)
  {
    System.Diagnostics.Debug.WriteLine("ERROR: Accessing your device failed." + Environment.NewLine + e.Message);
  }
}

private async Task ConfigureServiceForNotificationsAsync()
{
  try
  {
  // Obtain the characteristic for which notifications are to be received 
  characteristic = service.GetCharacteristics(CHARACTERISTIC_UUID)[CHARACTERISTIC_INDEX];

  // While encryption is not required by all devices, if encryption is supported by the device, 
  // it can be enabled by setting the ProtectionLevel property of the Characteristic object. 
  // All subsequent operations on the characteristic will work over an encrypted link. 
  characteristic.ProtectionLevel = GattProtectionLevel.EncryptionRequired;

  // Register the event handler for receiving notifications 
  characteristic.ValueChanged += Characteristic_ValueChanged;

  // In order to avoid unnecessary communication with the device, determine if the device is already  
  // correctly configured to send notifications. 
  // By default ReadClientCharacteristicConfigurationDescriptorAsync will attempt to get the current 
  // value from the system cache and communication with the device is not typically required. 
  var currentDescriptorValue = await characteristic.ReadClientCharacteristicConfigurationDescriptorAsync();

  if ((currentDescriptorValue.Status != GattCommunicationStatus.Success) ||
     (currentDescriptorValue.ClientCharacteristicConfigurationDescriptor != CHARACTERISTIC_NOTIFICATION_TYPE))
  {
    // Set the Client Characteristic Configuration Descriptor to enable the device to send notifications    // when the Characteristic value changes 
    GattCommunicationStatus status =
                        await characteristic.WriteClientCharacteristicConfigurationDescriptorAsync(
                        CHARACTERISTIC_NOTIFICATION_TYPE);

    if (status == GattCommunicationStatus.Unreachable)
    {
      // Register a PnpObjectWatcher to detect when a connection to the device is established, 
      // such that the application can retry device configuration. 
      StartDeviceConnectionWatcher();
    }
    }
  }
  catch (Exception e)
  {
    System.Diagnostics.Debug.WriteLine("ERROR: Accessing your device failed." + Environment.NewLine + e.Message);
  }
}

一旦接收到特征值并引发事件,我们就处理单个数据包/消息,以确定它们是否已完成或分解为更小的数据包。这是使用本节开头描述的规则完成的。

private async void Characteristic_ValueChanged(
            GattCharacteristic sender,
            GattValueChangedEventArgs args)
{
  //Pull the data off the event args into a databuffer
  var buffer = new byte[args.CharacteristicValue.Length];
  DataReader.FromBuffer(args.CharacteristicValue).ReadBytes(buffer);

  //buffer to hold the stripped out data.
  var data = new byte[0];

            //Rules:
            //IF: 1) Byte 1, Bit 8 (MSB) = 1, this is a start message, first 5 bytes are headers
            // &  2) Byte 1, Bits 1,2,3,4,5 = 0  then last 2 Bytes are checksums
            //    3) Remaining bytes are data
            // OR
            //IF: 1) Byte 1, Bit 8 (MSB) = 1, this is a start message, first 5 bytes are headers
            // &  2) Byte 1, Bits 1,2,3,4,5 > 0, then remaining bytes are data
            // OR
            //IF: 1) Byte 1, Bit 8 (MSB) = 0, this is a secondary packet, first byte is header
            // &  2) Byte 1, Bits 1,2,3,4,5 = 0 then last 2 Bytes are checksums
            //    3) REmaining bytes are data
            // OR
            //IF: 1) Byte 1, Bit 8 (MSB) = 0, this is a secondary packet, first byte is header
            // &  2) Byte 1, Bits 1,2,3,4,5 > 0, then remaining bytes are data

  if (IsBitSet(buffer[0], 7))
  {
    //Primary message
    if (IsBitSet(buffer[0],0) || IsBitSet(buffer[0],1)|| IsBitSet(buffer[0],2)|| IsBitSet(buffer[0],3)|| IsBitSet(buffer[0],4))
    {
      //Not Last one
      //Ignore the first 5 bytes (header) and the rest is data
      data = new Byte[buffer.Length - 5];
      int index = 0;
      for (int position = 5; position < buffer.Length;position++)
      {
        data[index] = buffer[position];
        index++;
      }
    }
    else
    {
      //Last One
      //Ignore the first 5 bytes(header) and the last 2 bytes (checksum)
      data = new Byte[buffer.Length - 7];
      int index = 0;
      for (int position = 5; position < buffer.Length-2; position++)
      {
        data[index] = buffer[position];
        index++;
      }
    }
  }
  else
  {
    //Secondary Message
    if (IsBitSet(buffer[0], 0) || IsBitSet(buffer[0], 1) || IsBitSet(buffer[0], 2) || IsBitSet(buffer[0], 3) || IsBitSet(buffer[0], 4))
    {
      //Not Last one
      //Ignore the first 1 byte (header) and the rest is data
      data = new Byte[buffer.Length - 1];
      int index = 0;
      for (int position = 1; position < buffer.Length; position++)
      {
        data[index] = buffer[position];
        index++;
      }
    }
    else
    {
      //Last One
      //Ignore the first 1 bytes(header) and the last 2 bytes (checksum)
      data = new Byte[buffer.Length - 3];
      int index = 0;
      for (int position = 1; position < buffer.Length-2; position++)
      {
        data[index] = buffer[position];
        index++;
      }
    }
  }

  //Write out some diagnostics to compare in and out data
  System.Diagnostics.Debug.WriteLine("Buffer: " + BitConverter.ToString(buffer));
  System.Diagnostics.Debug.WriteLine("Data  : " + BitConverter.ToString(data));

  //Generate a new data message
  BeanMessage dataMessage = new BeanMessage(data);
  //Raise CompleteEvent containing the data
  this.ValueChangeCompleted(dataMessage);
}

IsBitSet 是一个小型辅助方法,它执行位移和比较,如果位被设置,则返回 true。

bool IsBitSet(byte b, int pos)
{
  return (b & (1 << pos)) != 0;
}

这是我第一次做这些事情,说它令人震惊都不过分。代码是从 Bluetooth Generic Attribute Profile - Heart Rate Service 示例中大量修改而来的。可以说,没有它,我将一事无成。

仅读取,无写入

是的,这仅仅是从 Bean 到 PC 的单向读取数据。我甚至不想考虑双向写入数据。整个设置还不够健壮。目前不值得进一步研究。在 Bean 开发者完成 Windows SDK 并解决了一些底层的 Windows 蓝牙小问题之前,尝试使其成为一个可靠的解决方案并不值得我花费精力。

Windows Runtime 和 Windows 8.1 / Windows Phone 8.1

目前,Windows 上对低功耗蓝牙的支持仅限于有限的平台。因此,要启用 Windows Runtime 在 Windows Forms 等环境下的使用,您需要修改 Visual Studio 项目,正如我在另一篇文章中所述:https://codeproject.org.cn/Articles/526154/UltraDynamo-Part-It-all-starts-here

修改项目并添加以下属性组,并设置适当的版本;

  <PropertyGroup>
    <TargetPlatformVersion>8.1</TargetPlatformVersion>
  </PropertyGroup>

别忘了向项目和相关代码文件添加必要的引用。

结论

一个非常能干的小设备,但直到 Windows 的完整官方支持可用之前,使用起来会有些麻烦,希望开发者能尽快推出,因为能够正常使用这些设备而无需使用 AVR 方法进行编程将是很棒的。时间会证明一切。

一旦完整的 SDK 可用,我可能会回来重新审视这篇文章。

目前来看,存在太多不稳定因素,在官方 Windows SDK 发布之前,不值得为此进行研究。

此外,还有一个非官方的 Android SDK 正在开发中,但由于我的主要开发环境是 Windows,我乐于等待先让它正常运行。

也许您是 iOS 用户,那么您可以深入研究一下 Beans,并分享您的发现!

参考文献

[0]- http://ae-bst.resource.bosch.com/media/products/dokumente/bma250/bst-bma250-ds002-05.pdf

[1] - http://beantalk.punchthrough.com/t/bean-loader-options-for-windows-users/48/10

[2] - http://arduino.cc/en/Main/Software

[3] - https://learn.adafruit.com/usbtinyisp/drivers

[4] - http://www.ladyada.net/learn/avr/avrdude.html

[5] - https://github.com/PunchThrough/bean-documentation/blob/master/serial_message_protocol.md

历史

  • 2014年11月9日,添加物联网第二阶段前言并修复一些格式问题。
  • 2014年11月6日,添加额外的包图和传感器数据手册链接,以及修正拼写错误。
  • 2014年11月4日,首次发布。
© . All rights reserved.