Hardware_manager的实现基于ListBase和Callback,用于管理机器人的IO设备
Hardware_manager应当管理所有与外界交互的IO(应当更名为IO_Manager),例如can、serial、socket,而不应该是挂载在can上的motor或者挂载在serial上的imu
Hardware::Hardware_manager<IO1, IO2, ...> hardware(io1, io2, ...);IO1, IO2, ... 是你要管理的IO的类名,而 io1, io2, ... 是你要管理的IO的实例或指针
特别的,在DEBUG模式下,传入nullptr会让manager放弃对该IO的启动和管理,并自动跳过所有操作,而不会报错。你可以使用此功能方便的关闭某些IO进行debug。
而在RELEASE模式下,你不应该这么做,这会导致程序发生段错误
对于一个需要被Hardware_manager管理的IO来说,你必须实现一个名为task的public函数
void task() {
...
}该函数会在Hardware_manager构造的同时,作为一个线程被启动起来
#include "hardware_manager.hpp"
class Can
{
public:
void task() {
std::cout << "can is start!" << std::endl;
}
};
int main() {
Can can0;
Hardware::Hardware_manager<Can> hardware(can0);
hardware.join();
}如果你需要实现发送信息的功能,你可以在类中实现名为send的public函数(你可以实现多种不同的send函数)
void send(Args...) {
...
}在外部,你可以对hardware_manager的实例调用send函数,并标明设备
hardware.send<idx>(args...)hardware_manager则会直接调用第idx个被管理对象的send函数,并将参数args传入
#include "hardware_manager.hpp"
class Can
{
public:
Can(const std::string &name) : name_(name) {}
void task() {
std::cout << "can is start!" << std::endl;
}
void send(const std::string &msg) {
std::cout << name_ << " send " << msg << std::endl;
}
void send(int x) {
std::cout << name_ << " send " << x << std::endl;
}
private:
std::string name_;
};
int main() {
Can can0("can0");
Hardware::Hardware_manager<Can> hardware(can0);
hardware.join();
hardware.send<0>("msg1");
hardware.send<0>(123);
}对hardware_manager对象调用join函数会自动join它启动的所有线程(也就是所有被管理对象的task函数)
#include "hardware_manager.hpp"
class Can
{
public:
Can(const std::string &name) : name_(name) {}
void task() {
for(int i = 0; i < 10; i++) {
std::cout << name_ << " task at " << i << std::endl;
}
std::cout << name_ << " task exit!" << std::endl;
}
private:
std::string name_;
};
int main() {
Can can0("can0");
Can can1("can1");
Hardware::Hardware_manager<Can, Can> hardware(can0, can1);
hardware.join();
}在io_callback.hpp中,提供了Callback类。
如果你的IO类想要实现设置回调函数的功能,需要public继承Callback类,并传入你想要进行callback的类型。
如果你的一个IO上挂载了多个设备,并且他们所需要的回调函数类型不同,那么你可以向Callback类传入多种类型, 你的IO类就会存在多个不同类型的回调函数,并且可以被分别注册。
class IoName : public Callback<Type1, Type2, ...> {
...
};继承Callback后,你可以直接在类中(一般是在task函数中)使用callback函数,其参数是你选定的其中一种
class IoName : public Callback<Type1, Type2, ...> {
void task() {
...
Type1 val1 = ...;
...
callback(val1);
...
Type2 val2 = ...;
...
callback(val2);
...
}
};你可以对任意一个继承了Callback的IO注册回调函数,你需要对hardware_manager调用register_callback函数,指定IO编号和回调类型。
你所注册的回调函数需要是一个lambada函数或者std::function,其返回值为空,并且传入的参数是你所规定的回调类型。
hardware.register_callback<idx, Type>([&](const Type &data) {
...
});或
hardware.register_callback<idx, Type>(function);特别的,如果第idx指向的IO设备只存在一种回调类型,那么我们可以省略Type。
hardware.register_callback<idx>([&](const Type &data) {
...
});或
hardware.register_callback<idx>(function);在注册后,IO调用的该类型的回调函数,会变成你注册的回调函数
#include "io_callback.hpp"
#include "hardware_manager.hpp"
class Can : public Hardware::Callback<int, std::string>
{
public:
void task() {
while (true) {
int type;
std::cin >> type;
if (type == 0) {
int msg1;
std::cin >> msg1;
callback(msg1);
} else {
std::string msg2;
std::cin >> msg2;
callback(msg2);
}
}
}
};
int main() {
Can can0;
Hardware::Hardware_manager<Can> hardware(can0);
hardware.register_callback<0, int>(
[&](int x) { std::cout << "get callback with int, value is " << x << std::endl; });
hardware.register_callback<0, std::string>(
[&](const std::string &x) { std::cout << "get callback with string, value is " << x << std::endl; });
hardware.join();
}在io_callback.hpp中,提供了Callback_key类。
如果你的一个IO设备挂载了多个相同的设备,但有着不同的设备ID,那么你可以选择继承Callback_key类。
其参数列表中的第一个参数是作为Key(设备ID)的类型,之后的参数则是回调函数的参数类型(一般是一个,也可以传入多个)。
class IoName : public Callback_key<KeyType, Arg1, ...> {
...
};继承Callback_key后,你可以直接在类中(一般是在task函数中)使用callback_key函数,其第一个参数是KeyType类型的,表示设备id, 剩下的则是传入回调函数的参数
class IoName : public Callback<KeyType, Args1, ...> {
void task() {
...
callback_key(key, args..);
...
}
};同样的,你可以对任意一个继承了Callback_key的IO注册回调函数,你需要对hardware_manager调用register_callback函数,但只需要指定IO编号
你所注册的回调函数需要是一个lambada函数或者std::function,其返回值为空,并且传入的参数是你所规定的回调类型。
不同的是,在函数前你还需要传入本次注册的Key,也就是设备ID。
hardware.register_callback<idx>(key, [&](const Type &data) {
...
});或
hardware.register_callback<idx>(key, function);在注册后,如果IO调用了callback_key,并且传入的key和你本次注册的key匹配,则manager会调用你注册的回调函数
#include "io_callback.hpp"
#include "hardware_manager.hpp"
class Can : public Hardware::Callback_key<std::string, double>
{
public:
void task() {
while (true) {
std::string name;
double val;
std::cin >> name >> val;
callback_key(name, val);
}
}
};
int main() {
Can can0;
Hardware::Hardware_manager<Can> hardware(can0);
hardware.register_callback<0>(
"imu", [&](double x) { std::cout << "imu get callback, with value " << x << std::endl; });
hardware.register_callback<0>(
"rc", [&](double x) { std::cout << "rc get callback, with value " << x << std::endl; });
hardware.join();
}如果以上的功能还是不能满足你,那么你可以直接向hardware_manager所要某个IO对象的指针,并直接对其操作。
auto p = hardware.get<idx>();该函数会返回第idx个IO设备的指针
#include "hardware_manager.hpp"
class Can
{
public:
Can(const std::string &name) : name_(name) {}
void task() {
std::cout << "can is start!" << std::endl;
}
void send(int x) {
std::cout << name_ << " send " << x << std::endl;
}
void rename(const std::string &name) {
name_ = name;
}
private:
std::string name_;
};
int main() {
Can can0("can0");
Hardware::Hardware_manager<Can> hardware(can0);
hardware.join();
hardware.send<0>(123);
hardware.get<0>()->rename("can1");
hardware.send<0>(321);
}- 你可以同时继承Callback和Callback_key,但是不能同时继承多个Callback_key(
可能之后会改进)。 - 如果你发现了bug,你可以联系@Rhine解决。
- 请尽量不要修改本库的代码,如果你想了解该管理器的实现并修改,请你仔细阅读ListBase档和Callback的文档, 掌握模板元编程,并清晰其中的继承关系与转化。