为esp-hosted添加私有命令

Creative Commons
本作品采用知识共享署名

本文说明如何为ESP-hosted添加私有命令.

背景简介

ESP-hosted是乐鑫推出的软件栈,可以将esp32系列的芯片作为无线/BT网卡接入到主机系统,
提供三种硬件接口作为传输层:

  • SDIO
  • SPI
  • UART

基于传输层上实现提供两种软件接口:

以太网接口为非标,接口简单数量少,更适合于MCU主机。以太网接口中,ESP-hosted提供control-path用于控制wifi的连接,提供以太网接口的data-path用于传输网络数据,提供HCI接口用于蓝牙通信。
ESP-Hosted的硬件载体ESP32系列的芯片中除了wifi和蓝牙外设还有很多其它的外设,在主机MCU外设不足的情况下希望能利用ESP32上的其它外设,在control-path下建立私有命令由主机MCU去控制使用ESP32外设。

方法

本文基于ESP-hosted 0.4 release进行说明

1. 安装工具

ESP-Hosted的命令是通过protobuf进行序列化后传输的,因此先安装protobuf-c的编译器,WSL ubuntu下执行

1
sudo apt-get install protobuf-c-compiler

2. 下载代码

1
git clone https://github.com/espressif/esp-hosted.git -b release/v0.4

3. 增加protobuf接口

common/proto/esp_hosted_config.proto添加要传送message, 这里我要增加新的私有命令接口,当中有一个cmd字段表示私有命令,一串二进制数据payload用于传递私有命令要附带的数据
增加message
添加Cmd和Resp对,Cmd message为MCU主机发送给ESP-hosted,其中中指定了私有的cmd和payload字段,Resp message为ESP-hosted响应Cmd message。下列等式右边的数字为该成员变量在结构体中的位置

1
2
3
4
5
6
7
8
message EspHostedCmdSetPrivCmd {
int32 cmd = 1;
bytes payload = 2;
}

message EspHostedRespSetPrivCmd {
int32 resp = 1;
}

增加message type
EspHostedConfigMsgType中添加新的消息的类型

1
2
3
4
5
6
7
8
9
enum EspHostedConfigMsgType {
TypeCmdGetMACAddress = 0;
TypeRespGetMACAddress = 1;
TypeCmdGetWiFiMode = 2;
.....
TypeRespGetWiFiCurrTXPower = 39;
TypeCmdSetPrivCmd = 40; //新加的消息类型,数字一定要顺延
TypeRespSetPrivCmd = 41;
}

增加为config的payload

1
2
3
4
5
6
7
8
9
10
11
message EspHostedConfigPayload {
EspHostedConfigMsgType msg = 1;
oneof payload {
EspHostedCmdGetMacAddress cmd_get_mac_address = 10;
EspHostedRespGetMacAddress resp_get_mac_address = 11;
....
EspHostedRespGetWiFiCurrTXPower resp_get_wifi_curr_tx_power = 49;
EspHostedCmdSetPrivCmd cmd_set_priv_cmd =50; //新加config的payload
EspHostedRespSetPrivCmd resp_set_priv_cmd =51;
}
}

4. 生成c文件

执行下面命令

1
2
cd common/proto/
protoc-c esp_hosted_config.proto --c_out=.

会生成c文件,再执行下面命令将其替换到对应位置

1
2
mv esp_hosted_config.pb-c.c ../
mv esp_hosted_config.pb-c.h ../include/

其中esp_hosted_config.pb-c.h包含了产生的结构体和初始化结构体的宏以及序列化结构体的函数声明,而esp_hosted_config.pb-c.c中则是对这些函数的实现

5. 发送命令函数

参考其它已实现的函数在host/host_common/commands.c中添加用于发送私有命令的函数,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
int priv_set_cmd (int type, char *payload, int32_t length)
{
EspHostedConfigPayload req, *resp = NULL;
uint32_t tx_len = 0, rx_len = 0;
uint8_t *tx_data = NULL, *rx_data = NULL;

//初始化req
esp_hosted_config_payload__init (&req);
req.msg = ESP_HOSTED_CONFIG_MSG_TYPE__TypeCmdSetPrivCmd;
req.payload_case = ESP_HOSTED_CONFIG_PAYLOAD__PAYLOAD_CMD_SET_PRIV_CMD;

EspHostedCmdSetPrivCmd *req_payload = (EspHostedCmdSetPrivCmd*)
esp_hosted_calloc(1, sizeof(EspHostedCmdSetPrivCmd));
if (!req_payload) {
command_log("Failed to allocate memory for req_payload\n");
return FAILURE;
}

//初始化payload
esp_hosted_cmd_set_priv_cmd__init(req_payload);
req_payload->type = type;
req_payload->payload.len = length;
req_payload->payload.data = (uint8_t *)payload;
req.cmd_set_priv_cmd = req_payload;

//计算payload大小
tx_len = esp_hosted_config_payload__get_packed_size(&req);
if (!tx_len) {
command_log("Invalid tx length\n");
goto err3;
}

//分配payload空间
tx_data = (uint8_t *)esp_hosted_calloc(1, tx_len);
if (!tx_data) {
command_log("Failed to allocate memory for tx_data\n");
goto err3;
}

//打包payload
esp_hosted_config_payload__pack(&req, tx_data);

//传送
rx_data = transport_pserial_data_handler(tx_data, tx_len,
TIMEOUT_PSERIAL_RESP, &rx_len);
if (!rx_data || !rx_len) {
command_log("Failed to process rx_data\n");
goto err2;
}

//解包响应
resp = esp_hosted_config_payload__unpack(NULL, rx_len, rx_data);
if ((!resp) || (!resp->resp_set_priv_cmd)) {
command_log("Failed to unpack rx_data\n");
goto err1;
}

if (resp->resp_set_priv_cmd->resp) {
command_log("Failed to set mad swift priv \n");
goto err1;
}

//释放资源
mem_free(tx_data);
mem_free(rx_data);
mem_free(req_payload);
esp_hosted_config_payload__free_unpacked(resp, NULL);
return SUCCESS;

err1:
mem_free(rx_data);
esp_hosted_config_payload__free_unpacked(resp, NULL);
err2:
mem_free(tx_data);
err3:
mem_free(req_payload);
return FAILURE;
}

MCU主机就可以通过priv_set_cmd来发送私有命令到ESP-Hosted

6. ESP-Hosted响应

esp/esp_driver/network_adapter/main/slave_commands.c中添加对私有命令的响应:
增加响应函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
static esp_err_t cmd_set_priv_cmd_handler (EspHostedConfigPayload *req,
EspHostedConfigPayload *resp, void *priv_data)
{
EspHostedRespSetPrivCmd *resp_payload = NULL;

if (!req || !resp || !req->cmd_set_priv_cmd) {
ESP_LOGE(TAG," Invalid command request");
return ESP_FAIL;
}

//准备对私有命令的响应数据
resp_payload = (EspHostedRespSetPrivCmd *)
calloc(1,sizeof(EspHostedRespSetPrivCmd));
if (!resp_payload) {
ESP_LOGE(TAG,"Failed to allocate memory");
return ESP_ERR_NO_MEM;
}
esp_hosted_resp_set_priv_cmd__init(resp_payload);
resp->payload_case = ESP_HOSTED_CONFIG_PAYLOAD__PAYLOAD_RESP_SET_MAD_SWIFT_PRIV;
resp->resp_set_priv_cmd = resp_payload;

//处理私有命令
ESP_LOGI(TAG, "mad swift priv type %d", req->cmd_set_priv_cmd->type);
ESP_LOGI(TAG, "mad swift priv data %p", req->cmd_set_priv_cmd->payload.data);
ESP_LOGI(TAG, "mad swift priv data length %d", req->cmd_set_priv_cmd->payload.len);

resp_payload->resp = SUCCESS;
return ESP_OK;
}

在配置表的末尾添加响应函数映射

1
2
3
4
5
6
7
static esp_hosted_config_cmd_t cmd_table[] = {
...
{
.cmd_num = ESP_HOSTED_CONFIG_MSG_TYPE__TypeCmdSetPrivCmd,
.command_handler = cmd_set_priv_cmd_handler
},
};

释放响应申请的资源
esp_hosted_config_cleanup末尾添加代码释放cmd_set_priv_cmd_handler中申请的resp资源

1
2
3
4
5
6
7
8
9
10
11
12
static void esp_hosted_config_cleanup(EspHostedConfigPayload *resp)
{
....
case (ESP_HOSTED_CONFIG_MSG_TYPE__TypeRespSetPrivCmd) : {
mem_free(resp->resp_set_priv_cmd);
break;
}
default:
ESP_LOGE(TAG, "Unsupported response type");
break;
}
}

问题处理

ESP-Hosted release 0.4上原来的esp_hosted_config.pb-c.cesp_hosted_config.pb-c.h是较老版本的protobuf-c编译器产生的,里面带有has_的结构体变量代表对应的字段存在,但在新的protobuf-c编译器下这些字段被移除,而这些字段在command.cslave_command.c中都存在而出现编译错误。处理方法为直接移除使用has_的部分即可。