套接字适配层SAL
简介
为了适配更多的网络协议栈类型,和不同网络连接芯片的能力,YoC平台提供了一套 SAL(套接字适配层)开发组件,来加速主控芯片+通信连接芯片的应用场景开发和部署。
该组件完成对不同网络实现接口的抽象并对上层提供一组标准的 BSD Socket API,这样开发者只需要关心和使用标准网络接口,而无需关心底层具体实现,极大的提高了系统的兼容性,方便开发者完成协议栈的适配和网络通信相关的开发。
在此类应用场景中,主控芯片通过UART或SDIO与WiFi、GPRS、NB-IoT等通信芯片相连,YoC平台和用户APP运行在主控芯片中,需要网络数据访问时,通过外接的通信芯片进行网络负载的接收和发送。主控芯片和外接通信芯片之间的通信,可以是AT Command通道,也可以是厂商私有协议通道。
在YoC移植过程中,如果需要支持外接Wifi/BLE等模,且TCP/IP协议栈运行模组侧时
,则需要SAL和底层模组控制模块进行对接。SAL功能对上层提供标准socket接口,TCP/IP协议栈具体运行在哪一侧对上层是透明的。
SAL网络框架如下图所示:
SAL模块适配接口
SAL模块注册及初始化
SAL网络模块注册
int sal_module_register(sal_op_t *module);
将指定网络设备注册进SAL框架中。此函数可在网络连接芯片驱动注册时调用。开发者需要根据sal_op_t结构体中的定义实现对应网络连接芯片的相关接口。sal_op_t结构体详细定义如下:
typedef struct sal_op_s {
char *version; ///< 版本,自行定义
int (*init)(void); ///< 初始化指定网络连接芯片
int (*start)(sal_conn_t *c); ///< 开始/打开一个网络连接,如通过UDP/TCP连接到远端服务器
int (*send)(int fd, uint8_t *data, uint32_t len, char remote_ip[16], int32_t remote_port, int32_t timeout); ///< 通过网络设备发送网络数据
int (*recv)(int fd, uint8_t *data, uint32_t len, char remote_ip[16], int32_t remote_port); ///< 通过网络设备接收网络数据
int (*domain_to_ip)(char *domain, char ip[16]); ///< 用于域名解析
int (*close)(int fd, int32_t remote_port); ///< 关闭网络连接
int (*deinit)(void); ///< 反初始化指定网络连接芯片
int (*register_netconn_data_input_cb)(netconn_data_input_cb_t cb); ///< 注册网络数据输入回调接口,底层网络连接芯片接收到数据时回调此接口到上层
int (*register_netconn_close_cb)(netconn_close_cb_t cb); ///< 当网络连接断开时,回调此接口到上层
#ifdef SAL_SERVER
int (*register_netconn_client_status_notify)(netconn_client_status_notify_t cb); ///< 当主控芯片作为服务端时,当回调用于通知连接的客户端的状态
#endif
} sal_op_t;
返回值:
调用成功时返回0,否则返回-1。
注意事项:
当前系统中最多仅支持一个网络连接芯片存在。暂时不支持多网卡操作
SAL网络模块释放注册
int sal_module_unregister(void);
释放注册进SAL框架中指定的网络设备
调用成功时返回0,否则返回-1。
SAL网络模块初始化
int sal_module_init(void);
此函数调用注册的init函数初始化低层网络连接芯片。初始化完成后,该网络连接芯片即准备好套接字连接。
调用成功时返回0,否则返回-1。
注意事项:
调用此函数前,需保证SAL网络模块已注册
SAL模块套接字操作
开始/打开网络连接
int sal_module_start(sal_conn_t *conn);
此函数调用注册的start函数开始/打开一个网络连接。开发者在实现的start函数中,可将conn中的定义的fd转化为内部连接id并向远端服务器发起连接。sal_conn_t结构体的详细定义如下:
typedef struct {
int fd; ///< 在套接字层使用的fd,开发者可根据此fd和内部连接id自行转化
CONN_TYPE type; ///< 网络连接类型
char *addr; ///< 套接字对应的远端ip地址
int32_t r_port; ///< 远端端口号
int32_t l_port; ///< 本地端口号
uint32_t tcp_keep_alive; ///< tcp连接是否需要保活
} sal_conn_t;
返回值:
调用成功时返回0,否则返回-1。
注意事项:
当前对于esp8266网络连接芯片,仅支持TCP/UDP的单播连接
发送网络数据
int sal_module_send(int fd, uint8_t *data, uint32_t len, char remote_ip[16],
int32_t remote_port, int32_t timeout);
此函数调用注册的send函数在超时时间timeout(ms)内向指定remote_ip和remote_port的远端发送len字节长度的负载数据data。
一般情况下remote_ip和remote_port可不使用。开发者直接根据fd转化的内部连接id发送数据。
返回值:
调用成功时返回0,否则返回-1。
注册接收网络数据回调
int sal_module_register_netconn_data_input_cb(netconn_data_input_cb_t cb);
注册网络数据输入回调接口,底层网络连接芯片接收到数据时回调此接口递交到SAL协议栈内部缓存中等待上层应用读取。
返回值:
调用成功时返回0,否则返回-1。
注册套接字关闭回调
int sal_module_register_netconn_close_cb(netconn_close_cb_t cb);
当网络连接断开时,回调此接口到SAL协议栈中销毁相关资源。
返回值:
调用成功时返回0,否则返回-1。
域名解析
int sal_module_domain_to_ip(char *domain, char ip[16]);
此函数调用注册的domain_to_ip函数将域名domain解析为点分十进制的ip网络地址。
返回值:
调用成功时返回0,否则返回-1。
关闭套接字
int sal_module_close(int fd, int32_t remote_port);
此函数调用注册的close函数主动断开套接字fd,销毁相关资源。remote_port字段暂时可作为预留使用。
返回值:
调用成功时返回0,否则返回-1。
SAL网络芯片反初始化
int sal_module_deinit(void);
调用注册的deinit函数释放网卡相关资源。
返回值:
调用成功时返回0,否则返回-1。
注册远程客户端状态
int sal_module_register_client_status_notify_cb(netconn_client_status_notify_t cb);
注册远程客户端状态。
返回值:
调用成功时返回0,否则返回-1。
SAL网络芯片适配示例
esp8266适配
esp8266移植适配
static net_ops_t esp8266_net_driver = {
.set_mac_addr = esp8266_set_mac_addr,
.start_dhcp = NULL,/*esp8266_start_dhcp*/
.stop_dhcp = NULL,/*esp8266_stop_dhcp*/
.set_ipaddr = esp8266_set_ipaddr,
.get_ipaddr = esp8266_get_ipaddr,
.get_mac_addr = esp8266_get_mac_addr,
.set_dns_server = esp8266_set_dns_server,
.get_dns_server = esp8266_get_dns_server,
.ping = esp8266_ping_remote,
.subscribe = esp8266_subscribe,
};
static wifi_driver_t esp8266_wifi_driver = {
.init = esp8266_init,
.deinit = esp8266_deinit,
.start = esp8266_start,
.stop = esp8266_stop,
.reset = esp8266_reset,
.set_mode = esp8266_set_mode,
.get_mode = esp8266_get_mode,
.sta_get_link_status = esp8266_drv_get_link_status,
.set_smartcfg = esp8266_smartconfig,
};
static sal_op_t esp8266_sal_driver = {
.version = "1.0.0",
.init = esp8266_wifi_module_init,
.start = esp8266_wifi_module_conn_start,
.send = esp8266_wifi_module_send,
.domain_to_ip = esp8266_wifi_module_domain_to_ip,
.close = esp8266_wifi_module_conn_close,
.deinit = esp8266_wifi_module_deinit,
.register_netconn_data_input_cb = esp8266_wifi_packet_input_cb_register,
.register_netconn_close_cb = esp8266_wifi_close_cb_register,
};
static netdev_driver_t esp8266_driver = {
.drv =
{
.name = "wifi",
.init = esp8266_dev_init,
.uninit = esp8266_dev_uninit,
.open = esp8266_dev_open,
.close = esp8266_dev_close,
},
.link_type = NETDEV_TYPE_WIFI,
.net_ops = &esp8266_net_driver, ///< 网络层相关接口,如dhcp、mac/ip地址配置与获取等
.link_ops = &esp8266_wifi_driver, ///< 链路层相关接口,实现配网、复位、模式配置等功能。
};
static int esp8266_dev_open(aos_dev_t *dev)
{
// 将esp8266实现的sal_op_t结构注册进SAL框架中
sal_module_register(&esp8266_sal_driver);
// 初始化SAL协议栈
sal_init();
return 0;
}
void wifi_esp8266_register(utask_t *task, esp_wifi_param_t *param)
{
//初始化esp8266用到的atparser、串口等相关资源
int ret = esp8266_module_init(task, param);
if (ret < 0) {
LOGE(TAG, "driver init error");
return;
}
//配置smartconfig管脚复用
if (param->smartcfg_pin) {
esp8266_pin_init(param->smartcfg_pin);
}
//基于YoC框架,注册8266驱动。注册时会调用esp8266_driver中注册的esp8266_dev_init函数
ret = driver_register(&esp8266_driver.drv, NULL, 0);
if (ret < 0) {
LOGE(TAG, "device register error");
}
memcpy(&esp8266_param, param, sizeof(esp_wifi_param_t));
}
esp8266在SAL层适配时,开发者需要实现sal_op_t结构对应的esp8266_sal_driver。在上层应用打开该wifi网络设备时,调用esp8266_dev_open将esp8266_sal_driver注册进SAL框架中,同时初始化SAL协议栈。
同时需要实现netdev_driver_t结构中的网络层接口net_ops和链路层接口link_ops。网络管理器在配网时需要这两层的相关接口,详细请参考此文档中说明。
esp8266上层使用
static void network_init()
{
aos_kv_setint("wifi_en", 1); ///< 使能wifi连接
aos_kv_setint("gprs_en", 0);
aos_kv_setint("eth_en", 0);
esp_wifi_param_t esp_param;
// 初始化esp8266所使用的串口,用于at协议交互。配置复位和smartconfig使用的管脚
esp_param.device_name = "uart2";
esp_param.baud = 115200;
esp_param.buf_size = 4096;
esp_param.enable_flowctl = 0;
esp_param.reset_pin = WIFI_ESP8266_RESET;
esp_param.smartcfg_pin = WIFI_ESP8266_SMARTCFG;
wifi_esp8266_register(NULL, &esp_param); ///< 注册esp8266驱动
app_netmgr_hdl = netmgr_dev_wifi_init(); ///< wifi设备初始化,打开esp8266
if (app_netmgr_hdl) {
utask_t *task = utask_new("netmgr", 1 * 1024, QUEUE_MSG_COUNT, AOS_DEFAULT_APP_PRI);
netmgr_service_init(task); ///< 网络管理器初始化
netmgr_start(app_netmgr_hdl); ///< 开始配网连接
}
}
根据esp8266适配采用的相关配置注册对应驱动。注册成功后,通过网络管理器打开wifi设备并初始化相关服务开始配网连接。当配网连接成功后,开发者即可通过标注BSD网络套接字开发相关网络应用了。