关键词:OSDK telemetry sample,遥测订阅demo
之前有记录了两篇关于飞行控制sample,在sample代码中有设计到遥测数据的获取,用来判定飞行状态或者获取位置信息。
关于DJI OSDK提供的遥测数据还是比较重要的一个功能,机载计算平台可以实时的获取到飞机的相关数据,以便调整控制或其他功能。比如GPS信息就是通过遥测功能来获取的。
整体了解下遥测功能,先放一个官网链接:
https://developer.dji.com/cn/document/1365372a-db46-4777-b16e-1b6657217683
官网是广播和订阅部分,也就是说OSDK获取飞机的遥测数据是通过广播或者订阅的方式来获取的,具体广播和订阅的差异,当前官方提供的资料没有看得很清晰,欢迎大佬补充。
通过查看demo,看起来对应用端的功能有相通的,且官方当前推荐使用订阅功能,除了比较老的机型(M100)仅支持广播。所以重点看看有关订阅功能部分的使用demo。
demo github官方链接:
https://www.github.com/dji-sdk/Onboard-SDK/tree/4.0.1/sample/platform/linux/telemetry
跟代码,先看M100机型使用的接口函数:
通读过一遍代码:
getBroadcastData(DJI::OSDK::Vehicle* vehicle, int responseTimeout)
-->vehicle->broadcast->setBroadcastFreqDefaults(TIMEOUT)
-->vehicle->broadcast->get*
看起来没有什么复杂逻辑,直接调API即可,
1、设置频率
2、获取数据
我们找到SDK的头文件:dji_broadcast.hpp
所以这就是我们使用广播功能必须参考的头文件了,再往底层走也还可以看到源码,就看咱们有没有必要继续修改底层的处理了。
订阅部分:
先了解一下订阅的时间方式,通过官方资料不难看出来,OSDK的订阅数据项是与TOPIC值对应的,也就是在应用中我们需要获取什么数据,先告诉飞控,而TOPIC就是对应着飞控的数据定义ID。也就相当于与飞控约定的一个协议,查看代码:dji_telemetry.hpp中,TOPIC是枚举值,后续可以看到该枚举值对应着UID值,实际与飞控对应的是这个UID(有点绕,哈哈)。
代码依旧没什么复杂逻辑,大概拆解一下涉及到的API和相关参数,先从sample中看到的关于订阅的API:
/*!
* @brief This is the interface for the end user to generate a package for
* subscription.
*
* @platforms M210V2, M300
* @param packageID: The ID of package it'll generate
* @param numberOfTopics:
* @param topicList: List of Topic Names to subscribe in the package
* @param sendTimeStamp: Note that timestamp is the time of package transmission, not data acquisition from sensor.
* @param freq
* @return
*/
bool initPackageFromTopicList(int packageID, int numberOfTopics,
Telemetry::TopicName* topicList,
bool sendTimeStamp, uint16_t freq);
显然是初始化一个订阅数据包,先理解一下订阅的含义,订阅意味这我先告诉你我需要什么,然后我不再向你请求了,后续你直接将数据送过来即可。
这个API就相当于是我先设置我的需求,需求那就对应着API中的参数,
pkgIndex:索引ID先不管,
freq : 订阅频率
numTopic: 这个ID对应TOPIC的数量
topicList1Hz :TOPIC数组
该初始化API将一组TOPIC 那么组成一个订阅包,这个包的ID为pkgIndex, 后续对订阅包的操作直接操作该ID。那既然可以组成一个package包或多个package包,应用端该怎么划分呢?
其实多考虑一下,init成的一个package包有一个比较明显的特点,就是订阅的频率是一样的,根据官网提供的资料TOPIC因为数据源不同,可支持的最高订阅频率也是不一样的。(实测遇到过当设置TOPIC的频率高于TOPIC的最高频率后,初始化就会报错,如果遇到初始化失败,可以检查一下此参数。)
所以OSDK支持打包成多个package让应用端的使用变得相对灵活。当然也没必要一个TOPIC打一个包,这样封装成包就没有意义了,另外貌似打包太多SDK支持上也会有问题。
建议:
1、需要相同订阅频率的TOPIC封装到一个package
2、将同一类数据TOPIC封装成一个package。
参数:enableTimestamp,实测无效,底层源码可以看到未对timestamp值解析,该timestamp再对齐获取时间应该还是挺有用的,希望官方将来能够支持,毕竟API都已经预留了。
/*!
* @brief Blocking call for start package
*
* @platforms M210V2, M300
* @param packageID
* @param timeout
* @return
*/
ACK::ErrorCode startPackage(int packageID, int timeout);
开始订阅,在这我们就可以看到了是将打包的package中的所有TOPIC一起开始,运行这一步后。OSDK端就可以不断地以指定的频率收到数据。下一步就是去取数据了。
template <Telemetry::TopicName topic>
typename Telemetry::TypeMap<topic>::type getValue()
{
typename Telemetry::TypeMap<topic>::type ans;
void* p = Telemetry::TopicDataBase[topic].latest;
lockMSG();
if (p)
{
ans = *reinterpret_cast<typename Telemetry::TypeMap<topic>::type*>(p);
freeMSG();
return ans;
}
else
{
DERROR("Topic 0x%X value memory not initialized, return default", topic);
}
freeMSG();
memset(&ans, 0xFF, sizeof(ans));
return ans;
}
看到这个地方,结合demo的应用,如何使用订阅功能基本已经明确了,在这继续聊一聊SDK是如何取数据的。万一后续有问题,我们可以先自己来排查问题。
关于订阅的源码文件:
dji_subscription.hpp
dji_subscription.cpp
vehicle中初始化订阅功能时,会注册一个decode回调函数
bool ret = this->legacyLinker->registerCMDCallback(
OpenProtocolCMD::CMDSet::Broadcast::subscribe[0],
OpenProtocolCMD::CMDSet::Broadcast::subscribe[1],
this->subscribe->subscriptionDataDecodeHandler.callback,
this->subscribe->subscriptionDataDecodeHandler.userData);
void
DataSubscription::decodeCallback(Vehicle* vehiclePtr,
RecvContainer rcvContainer, UserData subPtr)
{
DataSubscription* subscriptionHandle = (DataSubscription*)subPtr;
// uint8_t pkgID = *(((uint8_t *)header) + sizeof(OpenHeader) + 2);
uint8_t pkgID = rcvContainer.recvData.subscribeACK;
if (pkgID >= MAX_NUMBER_OF_PACKAGE)
{
DERROR("Unexpected package id %d received.", pkgID);
return;
}
SubscriptionPackage* p = &subscriptionHandle->package[pkgID];
/*
* TODO: handle the case that the FC is already sending subscription packages
* when the program starts,
*/
subscriptionHandle->extractOnePackage(&rcvContainer, p);
VehicleCallBackHandler h = p->getUnpackHandler();
if (NULL != h.callback)
{
(*(h.callback))(vehiclePtr, rcvContainer, h.userData);
}
}
看代码到这里我们可以看到一个新的用法,除了通过getValue来主动获取值外,还可以通过registerUserPackageUnpackCallback注册一个回调,解析包后直接抛到应用端使用。即代码:
VehicleCallBackHandler h = p->getUnpackHandler();
if (NULL != h.callback)
{
(*(h.callback))(vehiclePtr, rcvContainer, h.userData);
}
看代码是这样,我就没有仔细验证了,毕竟当前提供的方式也已经够用了。有需要的可以尝试一下新的实现方案来适配需求。
评论
0 条评论
请登录写评论。