如何使用粒子氩气进行位置跟踪

Ever want to add presence or location tracking to a project? Frustrated by the solutions (or lack thereof)?

是否曾经想向项目添加状态或位置跟踪? 对解决方案感到沮丧(或缺乏解决方案)?

Do not worry, you're not the only one!

别担心,您不是唯一的一个!

In this post you'll learn how to implement a very basic tracking and notification application. We'll be using a Particle Argon and a Tile Mate.

在本文中,您将学习如何实现一个非常基本的跟踪和通知应用程序。 我们将使用粒子氩和平铺配合。

By the end you'll be able to tell when the Tile is present or not. Plus we'll use Pushover to send push notifications to the devices of your choosing.

到最后,您将能够知道该Tile是否存在。 另外,我们将使用Pushover将推送通知发送到您选择的设备。

Let's get going!

我们走吧!

Note before we get started, this post is lengthy. You can download the PDF version so you can save and view it later.

请注意,在我们开始之前,这篇文章很您可以下载PDF版本,以便以后保存和查看。

初步调查 (Initial investigation)

The idea of using a Tile wasn't obvious at first glance. Ideally, using a phone seemed to make more sense. Unfortunately, this wasn't as a viable option. It would require some more research and the creation of a Bluetooth iOS app.

乍一看,使用Tile的想法并不明显。 理想情况下,使用电话似乎更有意义。 不幸的是,这不是一个可行的选择。 这将需要更多的研究并创建蓝牙iOS应用。

So, the idea of using a phone was out.

因此,使用电话的想法就出来了。

Then I thought, "What devices do advertise all the time?"

然后我想:“什么设备一直在做广告?”

That is what led me down the path of a tracker like Tile.

这就是导致我走到诸如Tile之类的跟踪器的道路上的原因。

After it arrived there was some customary testing. First stop, the Tile application.

到达之后,进行了一些常规测试。 第一站,Tile应用程序。

I was able to connect and use the device. I even made it play a catchy tune. 🎶

我能够连接并使用该设备。 我什至使它起了吸引人的声调。 🎶

Then, I moved on to using one of the Bluetooth scanner apps. I scrolled through all the results and Bingo. There was the Tile!

然后,我继续使用其中一种蓝牙扫描仪应用程序。 我浏览了所有结果和宾果游戏。 有瓷砖!

I even waited a few hours and checked it again. I wanted to make sure it didn't go to sleep after a while. Turns out, it's always advertising. As far as I can tell, about every 8 seconds.

我什至等了几个小时再检查一次。 我想确保一段时间后它不会进入睡眠状态。 事实证明,它始终是广告。 据我所知,大约每8秒一次。

All of this testing lead to one conclusion: it could be easily used for presence detection.

所有这些测试都得出一个结论:它可以轻松地用于存在检测。

The next step in the process was trying to figure out how to get it working with an Argon.

该过程的下一步是尝试弄清楚如何使其与Argon一起使用。

广告 (Advertising)

As we had gathered in the previous step, we know that the Tile is advertising about every 8 seconds. That means it should be easily scanned for using any device including an Argon, Zenon or Boron.

正如我们在上一步中收集的那样,我们知道Tile大约每8秒发布一次广告。 这意味着应该使用包括Argon,Zenon或Boron在内的任何设备轻松对其进行扫描。

For this example I suggest you use an Argon. This is because Bluetooth and Mesh share the same radio. When scanning for the Tile, the Xenon connected to Mesh would often miss the advertising packets. This would lead to false negatives (and frustration!).

对于本示例,我建议您使用Argon。 这是因为Bluetooth和Mesh共享同一无线电。 扫描图块时,连接到网格的氙气经常会丢失广告包。 这将导致假阴性(和沮丧!)。

Along the same lines, you'll want to make sure your Argon is connected to no mesh network. You can remove it using the CLI. Connect your device to your computer and run the following command:

同样,您需要确保Argon没有连接到任何网状网络。 您可以使用CLI删除它。 将设备连接到计算机,然后运行以下命令:

particle mesh remove <device name/ID>

Make sure that you replace <device name/ID> with your device's name or ID.

确保用设备的名称或ID替换<设备名称/ ID>

Alright, back to the good stuff.

好了,回到好东西。

Advertising can have a few different purposes in Bluetooth. Typically though, it marks the beginning of the pairing phase. That way other devices know that the advertising device is available.

在蓝牙中,广告可以有一些不同的用途。 但是通常,它标志着配对阶段的开始。 这样,其他设备就会知道广告设备可用。

Additionally, the advertising device will indicate what services it has. We can use this knowledge to  filter out devices that don't match.

另外,广告设备将指示其具有的服务。 我们可以使用这些知识来筛选出不匹配的设备。

For example, here's a screenshot of the services available on the Tile device:

例如,以下是Tile设备上可用服务的屏幕截图:

When scanning we'll double check that the device we're connecting to has the service UUID of 0xfeed.

扫描时,我们将再次检查要连接的设备的服务UUID为0xfeed

Before we get deep into Bluetooth land though, let's set up our app for debugging using the Logger.

不过,在深入了解蓝牙领域之前,让我们使用Logger设置应用程序进行调试。

记录中 (Logging)

In this tutorial we'll be using the Logger. It allows you to display log messages from your app using particle serial monitor.

在本教程中,我们将使用记录器。 它允许您使用particle serial monitor显示来自应用程序的日志消息。

One of the cooler features about the logger is the idea of message hierarchy. This allows you, the designer, to selectively mute messages that may not be necessary.

记录器的较酷功能之一是消息层次结构的概念。 设计人员可以使用此选项选择性地使不必要的消息静音。

For example, if you have messages used for debugging. You could remove them or comment them out. Or, you could increase the LOG_LEVEL so they're effectively ignored.

例如,如果您有用于调试的消息。 您可以删除它们或将其注释掉。 或者,您可以增加LOG_LEVEL以便有效地将其忽略。

Here are the logging levels which are available in logging.h in Particle's device-os repository:

以下是Particle的device-os存储库中的logging.h中可用的日志记录级别:

// Log level. Ensure log_level_name() is updated for newly added levels
typedef enum LogLevel {
    LOG_LEVEL_ALL = 1, // Log all messages
    LOG_LEVEL_TRACE = 1,
    LOG_LEVEL_INFO = 30,
    LOG_LEVEL_WARN = 40,
    LOG_LEVEL_ERROR = 50,
    LOG_LEVEL_PANIC = 60,
    LOG_LEVEL_NONE = 70, // Do not log any messages
    // Compatibility levels
    DEFAULT_LEVEL = 0,
    ALL_LEVEL = LOG_LEVEL_ALL,
    TRACE_LEVEL = LOG_LEVEL_TRACE,
    LOG_LEVEL = LOG_LEVEL_TRACE, // Deprecated
    DEBUG_LEVEL = LOG_LEVEL_TRACE, // Deprecated
    INFO_LEVEL = LOG_LEVEL_INFO,
    WARN_LEVEL = LOG_LEVEL_WARN,
    ERROR_LEVEL = LOG_LEVEL_ERROR,
    PANIC_LEVEL = LOG_LEVEL_PANIC,
    NO_LOG_LEVEL = LOG_LEVEL_NONE
} LogLevel;

Cool, log levels. But how do we use them?

很酷,日志级别。 但是我们如何使用它们呢?

We can use them by invoking one of these functions:

我们可以通过调用以下功能之一来使用它们:

Log.trace, Log.info, Log.warn, Log.error.

Log.traceLog.infoLog.warnLog.error

For example:

例如:

Log.trace("This is a TRACE message.");

If we set the log level to LOG_LEVEL_INFO we'll only see messages from Log.info, Log.warn, and Log.error. LOG_LEVEL_WARN? Only Log.warn and Log.error will show up. (Hopefully you get the idea.)

如果我们将日志级别设置为LOG_LEVEL_INFO我们将仅看到来自Log.infoLog.warnLog.errorLOG_LEVEL_WARN ? 仅显示Log.warnLog.error 。 (希望您能明白。)

To set it up, we'll set the default level to LOG_LEVEL_ERROR. We'll also set the app specific LOG_LEVEL to LOG_LEVEL_TRACE. The end result should look something like this

要进行设置,我们将默认级别设置为LOG_LEVEL_ERROR 。 我们还将特定于应用程序的LOG_LEVEL设置为LOG_LEVEL_TRACE 。 最终结果应如下所示

// For logging
SerialLogHandler logHandler(115200, LOG_LEVEL_ERROR, {
    { "app", LOG_LEVEL_TRACE }, // enable all app messages
});

This way we don't get spammed with DeviceOS log messages. Plus, we get all the applicable messages from the app itself.

这样,我们就不会收到DeviceOS日志消息的垃圾邮件。 另外,我们从应用程序本身获取所有适用的消息。

By the way, if you want to set your device to a single LOG_LEVEL you can set it up like this:

顺便说一句,如果要将设备设置为单个LOG_LEVEL ,则可以按以下方式进行设置:

SerialLogHandler logHandler(LOG_LEVEL_INFO);

As you continue your journey using Particle's DeviceOS you'll soon realize how handy it can be. Now, let's move on to the good stuff!

当您继续使用Particle的DeviceOS进行旅行时,您很快就会意识到它有多方便。 现在,让我们继续前进吧!

设定 (Setting it up)

First, we'll want to make sure we're using the correct version of DeviceOS. Any version after 1.3 will have Bluetooth. You can get the instructions here.

首先,我们要确保使用的是正确版本的DeviceOS。 1.3之后的任何版本都将具有蓝牙。 您可以在此处获取说明

Next we'll want to start scanning for the Tile. We'll want do do this in the loop() function at a specified interval. We'll use a millis() timer in this case:

接下来,我们将开始扫描Tile。 我们希望在loop()函数中以指定的间隔执行此操作。 在这种情况下,我们将使用millis()计时器:

// Scan for devices
if( (millis() > lastSeen + TILE_RE_CHECK_MS) ){
    BLE.scan(scanResultCallback, NULL);
}

Make sure you define lastSeen at the top of the file like so:

确保像这样在文件顶部定义lastSeen

system_tick_t lastSeen = 0;

We'll use it to track the last time the Tile has been "seen". i.e. when the last time the Argon saw an advertising packet from the Tile.

我们将使用它来跟踪“平铺”的最后一次看到时间。 也就是说,上一次Argon从Tile看到广告包时。

TILE_RE_CHECK_MS can be defined as

TILE_RE_CHECK_MS可以定义为

#define TILE_RE_CHECK_MS 7500

This way we're checking, at the very minimum, every 7.5 seconds for advertising packets.

这样,我们至少每7.5秒检查一次广告数据包。

In order to find the Tile device we'll use BLE.scan. When we call it, It will start the scanning process.  As devices are found scanResultCallback will fire.

为了找到Tile设备,我们将使用BLE.scan 。 当我们调用它时,它将开始扫描过程。 找到设备后,将触发scanResultCallback

For now, we can define scanResultCallback at the top of the file:

现在,我们可以在文件顶部定义scanResultCallback

void scanResultCallback(const BleScanResult *scanResult, void *context) {
}

You notice that it includes a BleScanResult. This will contain the address, RSSI and device name (if available) and available service information. This will come in handy later when we're looking for our Tile device!

您会注意到它包含一个BleScanResult 。 这将包含地址,RSSI和设备名称(如果有)以及可用的服务信息。 稍后我们在寻找Tile设备时,这将派上用场!

Remember, that BLE.scan does not return until scanning has been completed. The default timeout for scanning is 5 seconds. You can change that value using BLE.setScanTimeout(). setScanTimeout takes units in 10ms increments. So, for a 500ms timeout would require a value of 50.

请记住,直到扫描完成, BLE.scan才会返回。 扫描的默认超时为5秒。 您可以使用BLE.setScanTimeout()更改该值。 setScanTimeout以10ms为单位递增。 因此,对于500毫秒的超时,将需要50的值。

For the case of this app, I'd recommend using a value of 8s (8000ms). You can set it like this:

对于此应用程序,我建议使用8s(8000ms)的值。 您可以这样设置:

BLE.setScanTimeout(800);

In this case, the device will scan for as long as it takes the Tile to advertise. That way it's less likely to miss an advertising packet.

在这种情况下,设备将扫描需要Tile进行广告的时间。 这样,就不太可能错过广告包。

处理扫描结果 (Handling Scan Results)

Now that we have scanResultCallback lets define what's going on inside.

现在我们有了scanResultCallback可以定义内部发生的事情。

We first want to get the service information inside the advertising data. The best way is to use scanResult->advertisingData.serviceUUID. We'll pass in an array of UUIDs what will be copied for our use.

我们首先要在广告数据中获取服务信息。 最好的方法是使用scanResult->advertisingData.serviceUUID 。 我们将传递一个UUID数组,这些数组将被复制以供我们使用。

BleUuid uuids[4];
int uuidsAvail = scanResult->advertisingData.serviceUUID(uuids,sizeof(uuids)/sizeof(BleUuid));

This will populate uuids that way you can iterate over them. uuidsAvail will equal the amount of available UUIDs.

这将填充uuids ,以便您可以遍历它们。 uuidsAvail将等于可用UUID的数量。

On our case we're looking for a particular UUID. We'll define it a the top of the file:

在我们的案例中,我们正在寻找特定的UUID。 我们将其定义为文件的顶部:

#define TILE_UUID 0xfeed

Normally UUIDs are much longer. A short UUID like this means it has been reserved or is part of the Bluetooth specification. In either case we'll be checking for it in the same way we would check a 32bit or 128bit version.

通常的UUID是更长的时间。 像这样的短UUID表示它已被保留或属于蓝牙规范。 无论哪种情况,我们都将以检查32位或128位版本的相同方式进行检查。

For diagnostic reasons we can also print out the device information. In this case the RSSI and the device MAC address is handy:

出于诊断原因,我们还可以打印出设备信息。 在这种情况下,RSSI和设备MAC地址很方便:

// Print out mac info
BleAddress addr = scanResult->address;
Log.trace("MAC: %02X:%02X:%02X:%02X:%02X:%02X", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
Log.trace("RSSI: %dBm", scanResult->rssi);

Finally let's set up a loop to see if the device found has the UUID:

最后,让我们建立一个循环,以查看找到的设备是否具有UUID:

// Loop over all available UUIDs
// For tile devices there should only be one
for(int i = 0; i < uuidsAvail; i++){

    // Print out the UUID we're looking for
    if( uuids[i].shorted() == TILE_UUID ) {
        Log.trace("UUID: %x", uuids[i].shorted());

        // Stop scanning
        BLE.stopScanning();

        return;
    }
}

We can easily compare the "shorted" version of the UUID with TILE_UUID. It's a simple integer so no complicated memory compare operations are necessary. So, using if( uuids[i].shorted() == TILE_UUID ) works just fine.

我们可以轻松地将UUID的“ TILE_UUID ”版本与TILE_UUID 。 这是一个简单的整数,因此不需要复杂的内存比较操作。 因此,使用if( uuids[i].shorted() == TILE_UUID )很好。

You can also use Log.trace to print out diagnostic information. In this case we're using it to print out the shorted() version of the UUID.

您还可以使用Log.trace打印诊断信息。 在这种情况下,我们将使用它来打印UUID的shorted()版本。

测试一下! (Test It!)

Let's test what we have so far!

让我们测试一下目前为止!

Program the app to your Argon. Open the terminal and run particle serial monitor to view the debug messages. Heres an example of what you may see:

将应用编程到您的Argon中。 打开终端并运行particle serial monitor以查看调试消息。 以下是您可能看到的示例:

0000005825 [app] TRACE: MAC: 65:C7:B3:AF:73:5C
0000005827 [app] TRACE: RSSI: -37Bm
0000005954 [app] TRACE: MAC: B3:D9:F1:F0:5D:7E
0000005955 [app] TRACE: RSSI: -62Bm
0000006069 [app] TRACE: MAC: C5:F0:74:3D:13:77
0000006071 [app] TRACE: RSSI: -62Bm
0000006217 [app] TRACE: MAC: 65:C7:B3:AF:73:5C
0000006219 [app] TRACE: RSSI: -39Bm
0000006224 [app] TRACE: MAC: B3:D9:F1:F0:5D:7E
0000006225 [app] TRACE: RSSI: -62Bm
0000006296 [app] TRACE: MAC: D7:E7:FE:0C:A5:C0
0000006298 [app] TRACE: RSSI: -60Bm
0000006299 [app] TRACE: UUID: feed

Notice how the message includes TRACE and also [app]? That means it's a trace message originating from the application code. Handy right?

注意消息中如何包含TRACE以及[app] ? 这意味着它是源自应用程序代码的跟踪消息。 方便吗?

This code does get spammy quick, especially if you're in an environment with lots of advertising Bluetooth devices. If you're Tile is on and running eventually you'll  see a message UUID: feed. That means your Argon found the Tile!

这段代码确实很快就会成为垃圾邮件,尤其是当您处于有大量广告蓝牙设备的环境中时。 如果您启用了Tile并开始运行,则最终会看到一条消息UUID: feed 。 这意味着您的氩气找到了瓷砖!

Next we'll use the onboard Mode button to "program" the Tile's address to memory. That way we can filter out all the devices we don't care about.

接下来,我们将使用板载的“模式”按钮将图块的地址“编程”到内存中。 这样,我们可以过滤掉所有我们不关心的设备。

按按钮添加设备 (Add Device On Button Push)

First we need to figure out how to monitor the Mode button. The best bet, according to the documentation is to use System.on.

首先,我们需要弄清楚如何监视“模式”按钮。 根据文档,最好的选择是使用System.on

System.on(button_click, eventHandler);

The first argument is the name of the system event. In our case it's button_click. The second argument is an event handler function. We'll call it eventHandler for now.

第一个参数是系统事件的名称。 在我们的例子中是button_click 。 第二个参数是事件处理函数。 我们现在将其称为eventHandler

Now let's create eventHandler

现在让我们创建eventHandler

void eventHandler(system_event_t event, int duration, void* )
{

}

Important: you can't use the Log function inside eventHandler. An easy way to test it is to toggle the LED on D7. Let's set it up!

重要提示:您不能在eventHandler使用Log函数。 一种简单的测试方法是切换D7上的LED。 让我们进行设置!

Initialize the LED in setup()

setup()初始化LED

// Set LED pin
pinMode(D7,OUTPUT);

Then we can add this inside eventHandler

然后我们可以在eventHandler添加它

if( event == button_click ) {
    if( digitalRead(D7) ) {
        digitalWrite(D7,LOW);
    } else {
        digitalWrite(D7,HIGH);
    }
}

We can then write to D7 (the onboard blue LED). We can even use digitalRead to read what the state of the LED is. It will respond with HIGH or LOW depending on the situation.

然后,我们可以写入D7(板载蓝色LED)。 我们甚至可以使用digitalRead来读取LED的状态。 它将与应对HIGHLOW视情况而定。

Load the firmware onto the device and we'll have nice control over the blue LED!

将固件加载到设备上,我们将可以很好地控制蓝色LED!

In the next section, we'll use the Mode button to put the device into a "learning" mode. This will allow us to do a one touch setup with the target Tile device.

在下一节中,我们将使用“模式”按钮将设备置于“学习”模式。 这将使我们能够与目标Tile设备进行一键式设置。

将地址存储到EEPROM (Storing Address to EEPROM)

In this next step we'll store the address of the Tile into EEPROM. That way when the device is restarted or loses power we'll still be able to identify the Tile later on.

在下一步中,我们将图块的地址存储到EEPROM中。 这样,当设备重新启动或断电时,以后我们仍然可以识别Tile。

There is one lingering question though. How do we get it to save the address in the first place?

不过,还有一个挥之不去的问题。 我们如何获得它以首先保存地址?

By monitoring the button press, we can put the device into a "learning" mode. The device will scan for a Tile, and save the address if it finds one.

通过监视按钮的按下,我们可以使设备进入“学习”模式。 设备将扫描图块,如果找到该图块,则保存该地址。

First let's add a conditional within if( uuids[i].shorted() == TILE_UUID ):

首先让我们在if( uuids[i].shorted() == TILE_UUID )添加一个条件:

// If we're in learning mode. Save to EEPROM
if( isLearningModeOn() ) {
    searchAddress = scanResult->address;
    EEPROM.put(TILE_EEPROM_ADDRESS, searchAddress);
    setLearningModeOff();
}

We'll use the status of D7 as a way of knowing we're in "learning mode". We do this by reading D7 using digitalRead(D7). Let's create a function that makes this more clear:

我们将使用D7的状态来了解我们处于“学习模式”。 我们通过使用digitalRead(D7)读取D7来做到这一点。 让我们创建一个使它更清晰的函数:

bool isLearningModeOn() {
    return (digitalRead(D7) == HIGH);
}

We can also replace the digitalWrite(D7,LOW); and digitalWrite(D7,HIGH); with similar functions. That way it's more straight forward what we're doing.

我们也可以替换digitalWrite(D7,LOW);digitalWrite(D7,HIGH); 具有相似的功能。 这样一来,我们所做的事情就变得更加直接。

// Set "Learning mode" on
void setLearningModeOn() {
    digitalWrite(D7,HIGH);
}

// Set "Learning mode" off
void setLearningModeOff() {
    digitalWrite(D7,LOW);
}

Then, we assign a global variable searchAddress as the scan result. We setup searchAddress like this at the top of the file:

然后,我们分配一个全局变量searchAddress作为扫描结果。 我们在文件顶部这样设置searchAddress

BleAddress searchAddress;

Next we want to save it to non-volatile memory using EEPROM.put. TILE_EEPROM_ADDRESS is defined as 0xa. You can define  TILE_EEPROM_ADDRESS to use whatever memory address tickles your fancy. Here's the full definition placed at the top of the file.

接下来,我们要使用EEPROM.put将其保存到非易失性存储器中。 TILE_EEPROM_ADDRESS定义为0xa 。 您可以定义TILE_EEPROM_ADDRESS来使用任何让您喜欢的内存地址。 这是完整定义,位于文件顶部。

#define TILE_EEPROM_ADDRESS 0xa

Finally, we turn off the LED and "learning mode" using setLearningModeOff()

最后,我们使用setLearningModeOff()关闭LED和“学习模式”

Every time a device is found we'll use millis() to set lastSeen. Additionally, we can track the last RSSI using lastRSSI. It's a cheap way to to know approximately how close the device is. We'll use scanResult->rssi to get this information and set it to the lastRSSI variable.

每次找到设备时,我们将使用millis()设置lastSeen 。 另外,我们可以使用lastRSSI跟踪最后的RSSI。 这是一种便宜的方法,可以大致了解设备的近距离。 我们将使用scanResult->rssi获取此信息并将其设置为lastRSSI变量。

Overall, your changes should look something like this:

总体而言,您所做的更改应如下所示:

...

// Print out the UUID we're looking for
if( uuids[i].shorted() == TILE_UUID ) {
    Log.trace("UUID: %x", uuids[i].shorted());

    // If we're in learning mode. Save to EEprom
    if( isLearningModeOn() ) {
        searchAddress = scanResult->address;
        EEPROM.put(TILE_EEPROM_ADDRESS, searchAddress);
        setLearningModeOff();
    }

    // Save info
    lastSeen = millis();
    lastRSSI = scanResult->rssi;

    // Stop scanning
    BLE.stopScanning();

    return;
}

Before this function, we can filter out devices that don't match our searchAddress. Add the following before if( uuids[i].shorted() == TILE_UUID ):

在使用此功能之前,我们可以过滤出与searchAddress不匹配的searchAddress 。 在if( uuids[i].shorted() == TILE_UUID )之前添加以下内容:

// If device address doesn't match or we're not in "learning mode"
if( !(searchAddress == scanResult->address) && !isLearningModeOn() ) {
    return;
}

This will skip over devices that don't match. It will only proceed if the address matches or we're in "learning mode".

这将跳过不匹配的设备。 仅当地址匹配或我们处于“学习模式”时,它才会继续。

Now, in order for us to load searchAddress on startup, we'll have to load it from flash. Add this line to your setup():

现在,为了让我们在启动时加载searchAddress ,我们必须从Flash加载它。 将此行添加到您的setup():

EEPROM.get(TILE_EEPROM_ADDRESS, searchAddress);

Then, check to make sure the address is valid. It won't be valid if all the bytes are 0xFF:

然后,检查以确保该地址有效。 如果所有字节均为0xFF ,则无效:

// Warning about address
if( searchAddress == BleAddress("ff:ff:ff:ff:ff:ff") ) {
    Log.warn("Place this board into learning mode");
    Log.warn("and keep your Tile near by.");
}

We should be able to "teach" our Argon the address of our Tile. Let's test it out!

我们应该能够“教”我们的Argon瓷砖的地址。 让我们测试一下!

测试一下。 (Test it.)

Now if we compile and run the app, notice how there's no more log output? We have to "teach" the Tile address to the Particle Device. So, hit the mode button. The blue LED should turn on.

现在,如果我们编译并运行该应用程序,请注意如何不再有日志输出? 我们必须将“ Tile”地址“教导”到粒子设备。 因此,点击模式按钮。 蓝色LED应该亮起。

Once your Tile has been found the LED will turn off and you'll see some output on the command line. Similar to what we've seen before:

找到您的图块后,LED将会熄灭,您将在命令行上看到一些输出。 与我们之前看到的类似:

0000006296 [app] TRACE: MAC: D7:E7:FE:0C:A5:C0
0000006298 [app] TRACE: RSSI: -60Bm
0000006299 [app] TRACE: UUID: feed

The device has been committed to memory!

设备已提交给内存!

You can also check if it's still saved after a reset. Hit the reset button and check for the same output as above. If it's showing up, we're still good!

您还可以检查重置后是否仍然保存。 点击重置按钮,检查是否有与上述相同的输出。 如果它显示出来,我们仍然很好!

更新云 (Update the Cloud)

Finally let's set up a function called checkTileStateChanged. We'll use it to check for changes to the state of the Tile on a regular interval.

最后,我们设置一个名为checkTileStateChanged的函数。 我们将使用它定期检查Tile的状态更改。

bool checkTileStateChanged( TilePresenceType *presence ) {

}

The main purpose of this function is to compare the lastSeen variable with the "timeout" duration. In our case, our timeout duration is TILE_NOT_HERE_MS which should be set to

此功能的主要目的是将lastSeen变量与“超时”持续时间进行比较。 在我们的示例中,超时时间为TILE_NOT_HERE_MS ,应将其设置为

#define TILE_NOT_HERE_MS 30000

near the top of your program. There's also two more conditions to look for. One where lastSeen is equal to 0. This is usually because the app hasn't found the Tile yet after startup.

在程序顶部附近。 还有另外两个条件要寻找。 一个其中lastSeen等于0的值。这通常是因为该应用在启动后尚未找到Tile。

The last case would be if the device has been seen and lastSeen is not 0. So within checkTileStateChanged let's put everything together.

最后一种情况是如果该设备已经被查看并且lastSeen不为0。那么在checkTileStateChanged我们将所有内容放在一起。

// Check to see if it's here.
if( millis() > lastSeen+TILE_NOT_HERE_MS ) {

} else if ( lastSeen == 0 ) {

} else {

}

return false;

Now we only want this function to return true if the state has changed. So we'll need to take advantage of the TilePresenceType pointer in the agreement.

现在我们只希望状态改变后该函数返回true。 因此,我们需要利用协议中的TilePresenceType指针。

TilePresenceType is simply an enumeration of all the possible states. You can stick it at the top of your file as well. Here it is:

TilePresenceType只是所有可能状态的枚举。 您也可以将其粘贴在文件的顶部。 这里是:

typedef enum {
    PresenceUnknown,
    Here,
    NotHere
} TilePresenceType;

You'll also need a global variable that we can pass to the function. Set this at the top of your file as well:

您还需要一个可以传递给函数的全局变量。 也将其设置在文件顶部:

// Default status
TilePresenceType present = PresenceUnknown;

Now, we can compare at each stage. Does it meet the criteria? Is the state different than the last one? If so, return true.

现在,我们可以在每个阶段进行比较。 是否符合标准? 状态与上一个不同吗? 如果是这样,则返回true。

Remember, we'll want to set presence to the new updated value. So each condition should update the presence value. For example:

请记住,我们会想集presence新的更新值。 因此,每个条件都应更新状态值。 例如:

*presence = NotHere;

Here's what the fully flushed out function looks like:

这是完全刷新功能的样子:

bool checkTileStateChanged( TilePresenceType *presence ) {

    // Check to see if it's here.
    if( millis() > lastSeen+TILE_NOT_HERE_MS ) {
        if( *presence != NotHere ) {
            *presence = NotHere;
            Log.trace("not here!");
            return true;
        }
    // Case if we've just started up
    } else if ( lastSeen == 0 ) {
        if( *presence != PresenceUnknown ) {
            *presence = PresenceUnknown;
            Log.trace("unknown!");
            return true;
        }
    // Case if lastSeen is < TILE_NOT_HERE_MS
    } else {
        if( *presence != Here ) {
            *presence = Here;
            Log.trace("here!");
            return true;
        }
    }

    return false;
}

We can now use this function in the main loop underneath the timer to start Ble.scan(). We can use it to send a JSON payload. In this case we'll include important information like the Bluetooth Address, lastSeen data, lastRSSI data and a message.

现在,我们可以在计时器下方的主循环中使用此函数来启动Ble.scan() 。 我们可以使用它来发送JSON有效负载。 在这种情况下,我们将包括重要信息,例如蓝牙地址, lastSeen数据, lastRSSI数据和消息。

// If we have a change
if( checkTileStateChanged(&present) ) {

}

We'll use an array of char to get our address in a string format. You can chain together toString() with toCharArray to get what we need.

我们将使用char数组来获取字符串格式的地址。 您可以将toString()toCharArray链接在一起,以获得我们需要的东西。

// Get the address string
char address[18];
searchAddress.toString().toCharArray(address,sizeof(address));

An example payload string could look something like this:

示例有效负载字符串可能看起来像这样:

// Create payload
status = String::format("{\"address\":\"%s\",\"lastSeen\":%d,\"lastRSSI\":%i,\"status\":\"%s\"}",
    address, lastSeen, lastRSSI, messages[present]);

status is simply a String defined at the top of the file:

status只是在文件顶部定义的字符串:

// The payload going to the cloud
String status;

You notice that there's also a variable called messages. This is a static const array of strings. They're mapped to the values from the TilePresenceType. Here's what it looks like

您会注意到还有一个名为messages的变量。 这是字符串的静态const数组。 它们被映射到TilePresenceType的值。 这是它的样子

const char * messages[] {
    "unknown",
    "here",
    "not here"
};

That way PresenceUnknown matches to "unknown", Here matches to "here", etc. It's a cheap easy way to associate a string with an enum.

这样, PresenceUnknown匹配"unknown"Here匹配"here" ,等等。这是一种将字符串与枚举关联的便宜方法。

Finally we'll publish and process. This allows us to send the update immediately.

最后,我们将进行发布和处理。 这使我们可以立即发送更新。

// Publish the RSSI and Device Info
Particle.publish("status", status, PRIVATE, WITH_ACK);

// Process the publish event immediately
Particle.process();

The overall function should look something like this in the end:

最后,整体功能应如下所示:

// If we have a change
if( checkTileStateChanged(&present) ) {

    // Get the address string
    char address[18];
    searchAddress.toString().toCharArray(address,sizeof(address));

    // Create payload
    status = String::format("{\"address\":\"%s\",\"lastSeen\":%d,\"lastRSSI\":%i,\"status\":\"%s\"}",
        address, lastSeen, lastRSSI, messages[present]);

    // Publish the RSSI and Device Info
    Particle.publish("status", status, PRIVATE, WITH_ACK);

    // Process the publish event immediately
    Particle.process();

}

Now, let's test it!

现在,让我们对其进行测试!

测试一下! (Testing it!)

We can test to make sure our Publish events are occurring without event leaving Particle Workbench. Open a new terminal by going to View → Terminal. Then use the following command:

我们可以进行测试以确保发生发布事件时没有事件离开粒子工作台。 转到视图→终端,打开一个新终端 然后使用以下命令:

particle subscribe --device <device_name> <event_name>

Replace <device_name> with the name or ID of your device.

<device_name>替换为设备的名称或ID。

Replace <event_name> with the name of the event. In our case it's status.

<event_name>替换为事件的名称。 在我们的情况下是status

You can then test it all by removing the battery and waiting for the  "not here" alert. Plug the battery back in and you should get a "here" alert.

然后,您可以通过取出电池并等待“不在这里”警报来进行全部测试。 重新插入电池,您将收到“此处”警报。

Here's an example of the output

这是输出示例

> particle subscribe --device hamster_turkey status

Subscribing to "status" from hamster_turkey's stream
Listening to: /v1/devices/hamster_turkey/events/status
{"name":"status","data":"{\"address\":\"C0:A5:0C:FE:E7:D7\",\"lastSeen\":40154002,\"lastRSSI\":-82,\"status\":\"not here\"}","ttl":60,"published_at":"2019-09-07T02:29:42.232Z","coreid":"e00fce68d36c42ef433428eb"}
{"name":"status","data":"{\"address\":\"C0:A5:0C:FE:E7:D7\",\"lastSeen\":40193547,\"lastRSSI\":-83,\"status\":\"here\"}","ttl":60,"published_at":"2019-09-07T02:29:50.352Z","coreid":"e00fce68d36c42ef433428eb"}

配置Webhook (Configuring Webhook)

In the last part of this tutorial we'll set up push notifications using a webhook. As mentioned before, we'll use Pushover and their handy API to send push notification(s) to the device(s) of your choice.

在本教程的最后一部分,我们将使用Webhook设置推送通知。 如前所述,我们将使用Pushover及其方便的API将推送通知发送到您选择的设备。

Pushover has a fantastically easy API to use. Their application is a Swiss army knife for situations where you don't want to code an app to send push notifications.

Pushover具有非常容易使用的API。 他们的应用程序是瑞士军刀,适用于您不想编写应用程序来发送推送通知的情况。

The first thing that you'll have to take note is your user key. You can get that by logging into Pushover. Note: you'll need to set up an account first if you haven't already.

您需要注意的第一件事是您的用户密钥。 您可以通过登录Pushover来获得。 注意:如果尚未注册,则需要先设置一个帐户。

It should look something like this:

它看起来应该像这样:

If you're logged in and don't see this page, click on the Pushover logo and that should bring you back.

如果您已登录但没有看到此页面,请单击“ Pushover”徽标 ,这将带您返回。

Next we'll want to create an application. Click on the Apps & Plugins at the top of the screen.

接下来,我们要创建一个应用程序。 单击屏幕顶部的“ 应用程序和插件 ”。

You should then click Create a New Application. This will allow us to get an API Token that will be needed in the Particle Webhook setup.

然后,您应该单击创建新应用程序。 这将使我们获得在“粒子Webhook”设置中需要的API令牌

Set a name as you see fit. Fill in the description if you want a reminder. Click the box and then click Create Application.

根据需要设置一个名称。 如果需要提醒,请填写说明。 单击框 ,然后单击创建应用程序。

You should go to the next page. Copy and save the API Token/Key we'll need this also in a few steps.

您应该转到下一页。 复制并保存API令牌/密钥,我们还需要几个步骤。

Now, let's setup the Webhook. Jump over to https://console.particle.io and create a new integration.

现在,让我们设置Webhook。 跳转到https://console.particle.io并创建一个新的集成。

We'll set the Event Name to status.

我们将事件名称设置为status

The URL to https://api.pushover.net/1/messages.json

指向https://api.pushover.net/1/messages.jsonURL

Also, if you want to filter by a specific device make sure you select it in the Device dropdown.

另外,如果要按特定设备过滤,请确保在“ 设备”下拉列表中选择它

Under Advanced Settings we'll finish up by setting a few fields.

在“ 高级设置”下,我们将通过设置一些字段来完成。

Create the following fields: token, user, title, and message. Then set token to the API Token we got earlier. Do the same for the User Key.

创建以下字段: token, usertitlemessage 。 然后将令牌设置为我们之前获得的API令牌 。 对用户密钥执行相同的操作。

The title will show up as the title of your message. Make it whatever makes sense for you.

标题将显示为您的消息标题。 使它对您有意义。

You can set the message as The Tile is currently {{{status}}}. RSSI: {{{lastRSSI}}}.

您可以将消息设置为The Tile is currently {{{status}}}. RSSI: {{{lastRSSI}}} The Tile is currently {{{status}}}. RSSI: {{{lastRSSI}}}

We are using mustache templates here. They allow you to use the data in the published payload and reformat it to your liking. In our case, we're using them to "fill in the blanks." The message once processed would look something like this:

我们在这里使用胡子模板。 它们使您可以使用已发布的有效负载中的数据,并根据自己的喜好对其进行重新格式化。 在我们的案例中,我们使用它们来“填补空白”。 处理后的消息如下所示:

The Tile is currently here. RSSI: -77

The Tile is currently here. RSSI: -77

As a side note, i'll be talking more about these templates in my guide. So stay tuned for that!

作为附带说明,我将在指南中进一步讨论这些模板。 因此,请继续关注!

测试一下 (Test it)

Once your integration is in place, you can test doing what we did in the earlier step. Remove the battery, and wait for the "not here" message. Put it back and wait for the "here" message.

集成到位后,您可以测试我们在上一步中所做的工作。 取出电池,然后等待“不在这里”消息。 放回去,等待“这里”消息。

Here's what it would look like on an iPhone:

这是在iPhone上的外观:

As you can see, I tested it a bunch! 😬

如您所见,我对其进行了一堆测试! 😬

If you've made it this far and everything is working, great work. You now have a Tile tracker for your house, office or wherever.

如果您已经做到了这一点,并且一切正常,那就太好了。 现在,您可以在家中,办公室或任何地方使用Tile跟踪器。

代码 (The Code)

Looking for the finished code for this example? I would be too! It's hosted on Github and is available here.

寻找该示例的完成代码? 我也是! 它托管在Github上,可在此处获得

结论 (Conclusion)

As you can imagine, the techniques and technologies used in this article can be used in many ways. Let's summarize some of the key take aways:

您可以想象,本文中使用的技术可以以多种方式使用。 让我们总结一下一些关键要点:

  1. Using Bluetooth Central to scan for and identify an off-the-shelf Tile device

    使用Bluetooth Central扫描并识别现成的Tile设备
  2. Storing the Tile identifying information to EEPROM. That way it can be retrieved on startup.

    将图块标识信息存储到EEPROM。 这样就可以在启动时检索它。
  3. Using our familiar Particle.publish to push updates to the cloud.

    使用我们熟悉的Particle.publish将更新推送到云。

  4. Using a Particle Integration Webhook to create push notifications on state change.

    使用粒子集成Webhook创建状态更改的推送通知。

Now that you have it all working, expand on it, hack it and make it yours. Oh and don't forget to share! I'd love to hear from you. hello@jaredwolff.com

现在,一切都正常了,对其进行扩展,修改并使其成为您自己的。 哦,别忘了分享! 我希望收到您的来信。 hello@jaredwolff.com

Like this post? Click one of the share links below and share it with the world. :)

喜欢这个职位吗? 点击下面的共享链接之一,然后与世界分享。 :)

This is a cross post from my blog. You can check out the original here.

这是我博客的交叉帖子。 您可以在此处查看原始文件。

Interested in learning more? I'm writing a guide on how to get the most out of the Particle Platform. Learn more about it here.

有兴趣了解更多吗? 我正在编写有关如何充分利用粒子平台的指南。 点击此处了解详情。

翻译自: https://www.freecodecamp.org/news/how-to-use-particle-argon-for-location-tracking/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值