套接字适配层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网络套接字开发相关网络应用了。

results matching ""

    No results matching ""