Page 1 of 1

【分享】如何将Ubuntu转变为蓝牙网关

Posted: 2023年 Nov 17日 10:35
by Kyson

本文将介绍基于网关开发框架,把 Ubuntu 作为蓝牙网关接入涂鸦 IoT 平台,能够接入涂鸦生态的 Bluetooth Mesh & LE 子设备。

硬件准备

硬件连接

参考 ZS3L 模组规格书,按照下表的对应关系把 ZS3L 和 USB 转串口引脚连接起来。

ZS3LUSB 转串口
TXRX
RXTX
RTSCTS
CTSRTS

硬件连接好之后,把 USB 转串口插入到 PC 的 USB 接口上,使用 附录 的代码读取 ZS3L 模组的固件版本号,用于检测硬件连接是否正常。

创建产品

接入到涂鸦 IoT 平台的设备都有唯一的产品 ID(PID),PID 是在涂鸦 IoT 平台上创建产品获得的。因此,您需要为网关创建产品,定义网关的功能并且选择网关的 App 面板,详细的操作步骤请参考 创建产品

注意,创建蓝牙网关产品的时候,协议类型要选择 蓝牙Mesh(SIG)。

程序开发

获取开发包

获取网关开发包的步骤如下:

  1. 在 Visual Studio Code 安装 IDE 插件并登录,具体操作请参考 Tuya Wind IDE

  2. 在 Tuya Wind IDE 的主页单击 新建开发框架。开发模式选择 TuyaOS SDK 模式,类型开发包选择 网关设备开发包,开发平台选择 Soc/ubuntu/Linux/X86_64

    Image

  3. 选择最新版本,单击 完成 则开始下载开发包。

开发包下载完成后,会自动在 Visual Studio Code 打开工程,其目录结构:

名称说明
apps应用示例,即产品开发包
build编译配置目录,存放编译配置文件(您无需关注)。
build_app.sh编译脚本
docsDoxygen 接口文档
include头文件
libs库文件
MakefileMakefile 文件
output编译输出目录,其中生成的程序在 output/<应用工程名称>_<版本号> 路径下。
scripts编译框架(您无需关注)。
vendor开发环境。开发环境是执行编译时在线下载到本地的。

编译应用

您可以使用 tuyaos_demo_blemesh 来开发蓝牙网关产品,该应用示例包含了蓝牙网关的功能。在 IDE 右击 apps 目录下的 tuyaos_demo_blemesh,单击 Build Project 进行编译。

Image

编译成功,会在 output/tuyaos_demo_blemesh_<版本号> 目录下生成可执行程序。

移植适配

TuyaOS 定义了一套标准化的 TuyaOS Kernel Layer(TKL),旨在屏蔽硬件和系统的差异性,实现跨平台兼容性。因此,您需要完成 TuyaOS 在 Ubuntu 平台上的适配,具体请参考 移植指南

移植完成后,重新编译应用。

运行应用

可执行程序的同一级目录下有一个配置文件,您需要修改配置文件,修改的内容:

  • 把串口设备名称改成您的 PC 识别 USB 转串口的串口设备名称。
  • pid 改成您创建产品获得的 PID。
  • uuidauthkey 改成您测试的授权信息。

修改配置文件并保存,直接运行可执行程序。通过涂鸦智慧生活 App 添加网关,添加并控制 Bluetooth Mesh 或者 Bluetooth LE 子设备。

注意事项

  • 需要先验证硬件的联通性,能获取 ZS3L 模组的版本号,否则运行应用无法工作。
  • TuyaOS 业务相关的 TKL 接口在不同产品上都可能有差异,需要移植适配,主要是选择网卡。
  • pid、uuid 和 authkey 要改成您自己的,并且 uuid 和 authkey 不能重复使用,必须保证一机一密。

<a id="附录"></a>

附录

Code: Select all

/**
 * 使用指南:
 *   1. 拷贝内容保存到 ncp_test.c 文件中
 *   2. 修改宏定义 TEST_UART_NAME,改成连接 ZS3L 模组对应的串口设备
 *   3. 编译
 *     $ gcc ncp_test.c -o ncp_test -lpthread
 *   4. 运行 ncp_test 程序,能读取到模组版本则表示通讯正常,否则需要检查串口
 *     成功输入打印示例:
 *     Send data[5:5]: 1a c0 38 bc 7e
 *     Recv data[128:7]: 1a c1 02 0b 0a 52 7e
 *     Send data[4:4]: 80 70 78 7e
 *     Ncp reset success.
 *     Send data[8:8]: 00 42 21 a8 53 dd 4f 7e
 *     Recv data[128:11]: 01 42 a1 a8 53 28 15 d7 c1 bf 7e
 *     Send data[4:4]: 81 60 59 7e
 *     Ncp set version success.
 *     Send data[9:9]: 11 43 21 57 54 39 51 2b 7e
 *     Recv data[128:14]: 12 43 a1 57 54 39 15 b3 59 9c 5a 7a 1c 7e
 *     Send data[4:4]: 82 50 3a 7e
 *     COO manuId:0001, version:1.0.8.
 */
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>

#include <termios.h>
#include <sys/ttydefaults.h>

#define TEST_UART_NAME      "/dev/ttyS1"
#define TEST_UART_SPEED     115200
#define TEST_UART_STIO_BIT  1
#define TEST_UART_RTSCTS    TRUE
#define TEST_UART_FLOWCTR   TRUE

#define UART_DEBUG_PRINTF   1

typedef unsigned int bool;
#ifndef FALSE
#define FALSE 0
#endif

#ifndef TRUE
#define TRUE 1
#endif

#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif

static const struct { int bps; speed_t posixBaud; } baudTable[] =
{ { 600, B600    },
  { 1200, B1200   },
  { 2400, B2400   },
  { 4800, B4800   },
  { 9600, B9600   },
  { 19200, B19200  },
  { 38400, B38400  },
  { 57600, B57600  },
  { 115200, B115200 },
  { 230400, B230400 } };

#define BAUD_TABLE_SIZE (sizeof(baudTable) / sizeof(baudTable[0]) )

// Define CRTSCTS for both ingoing and outgoing hardware flow control
// Try to resolve the numerous aliases for the bit flags
#if defined(CCTS_OFLOW) && defined(CRTS_IFLOW) && !defined(__NetBSD__)
  #undef CRTSCTS
  #define CRTSCTS (CCTS_OFLOW | CRTS_IFLOW)
#endif
#if defined(CTSFLOW) && defined(RTSFLOW)
  #undef CRTSCTS
  #define CRTSCTS (CTSFLOW | RTSFLOW)
#endif
#ifdef CNEW_RTSCTS
  #undef CRSTCTS
  #define CRTSCTS CNEW_RTSCTS
#endif
#ifndef CRTSCTS
  #define CRTSCTS 0
#endif

#define ASCII_XON   0x11
#define ASCII_XOFF  0x13

// Define the termios bit fields modified by ezspSerialInit
// (CREAD is omitted as it is often not supported by modern hardware)
#define CFLAG_MASK (CLOCAL | CSIZE | PARENB | HUPCL | CRTSCTS)
#define IFLAG_MASK (IXON | IXOFF | IXANY | BRKINT | INLCR | IGNCR | ICRNL \
                    | INPCK | ISTRIP | IMAXBEL)
#define LFLAG_MASK (ICANON | ECHO | IEXTEN | ISIG)
#define OFLAG_MASK (OPOST)

static int  g_uart_fd    = -1;
static char g_ezspSeq    = 0;
static char g_ashFramNum = 0;
static char g_ashAckNum  = 0;
static char gcur_ezspSeq = 0;

static unsigned short halCommonCrc16(unsigned char newByte, unsigned short prevResult);

int ezspSetupSerialPort(unsigned char *uart_name, unsigned int bps, unsigned char stopBits, bool rtsCts, bool flowControl)
{
    int i = 0;
    struct termios tios, checkTios;
    speed_t baud;

    // Setting this up front prevents us from closing an unset serial port FD
    // when breaking on error.
    g_uart_fd = -1;

    while (TRUE) { // break out of while on any error
        for (i = 0; i < BAUD_TABLE_SIZE; i++) {
            if (baudTable[i].bps == bps) {
                break;
            }
        }

        if (i < BAUD_TABLE_SIZE) {
            baud = baudTable[i].posixBaud;
        } else {
            printf("Invalid baud rate %d bps\r\n", bps);
            break;
        }

        if ((stopBits != 1) && (stopBits != 2)) {
             printf("Invalid number of stop bits:%d.\r\n", stopBits);
            break;
        }

        g_uart_fd = open(uart_name, O_RDWR | O_NOCTTY | O_NONBLOCK);

        if (g_uart_fd == -1) {
             printf("Serial port open failed:%s\r\n", strerror(errno));
            break;
        }

        tcflush(g_uart_fd, TCIOFLUSH); // flush all input and output data

        //#define ANDROID_PLATEM
    #if 0//#ifdef ANDROID_PLATEM

        fcntl(*serialPortFdReturn, F_SETFL, 04000);
    #else
        fcntl(g_uart_fd, F_SETFL, FNDELAY);
    #endif

        tcgetattr(g_uart_fd, &tios); // get current serial port options

        cfsetispeed(&tios, baud);
        cfsetospeed(&tios, baud);

        tios.c_cflag |= CLOCAL; // ignore modem inputs
        tios.c_cflag |= CREAD; // receive enable (a legacy flag)
        tios.c_cflag &= ~CSIZE; // 8 data bits
        tios.c_cflag |= CS8;
        tios.c_cflag &= ~PARENB; // no parity
        if (stopBits == 1) {
            tios.c_cflag &= ~CSTOPB;
        } else {
            tios.c_cflag |= CSTOPB;
        }
        if (flowControl && rtsCts) {
            tios.c_cflag |= CRTSCTS;
        } else {
            tios.c_cflag &= ~CRTSCTS;
        }

        tios.c_iflag &= ~(BRKINT | INLCR | IGNCR | ICRNL | INPCK | ISTRIP | IMAXBEL | IXON | IXOFF | IXANY);

        if (flowControl && !rtsCts) {
            tios.c_iflag |= (IXON | IXOFF); // SW flow control
        } else {
            tios.c_iflag &= ~(IXON | IXOFF);
        }

        tios.c_lflag &= ~(ICANON | ECHO | IEXTEN | ISIG); // raw input

        tios.c_oflag &= ~OPOST; // raw output

        memset(tios.c_cc, _POSIX_VDISABLE, NCCS); // disable all control chars

    #if 0//#ifdef ANDROID_PLATEM
        tios.c_cc[VSTART] = ('q'&037);           // define XON and XOFF
        tios.c_cc[VSTOP] = ('s'&037);
    #else
        tios.c_cc[VSTART] = CSTART;           // define XON and XOFF
        tios.c_cc[VSTOP] = CSTOP;
    #endif

        tios.c_cc[VMIN] = 1;
        tios.c_cc[VTIME] = 0;

        memset(checkTios.c_cc, _POSIX_VDISABLE, NCCS);
        tcsetattr(g_uart_fd, TCSAFLUSH, &tios);  // set EZSP serial port options
        tcgetattr(g_uart_fd, &checkTios); // and read back the result

        // Verify that the fields written have the right values
        i = (tios.c_cflag ^ checkTios.c_cflag) & CFLAG_MASK;
        if (i) {
            // Try again since macOS mojave seems to not have hardware flow control enabled
            tios.c_cflag &= ~CRTSCTS;
            tcsetattr(g_uart_fd, TCSAFLUSH, &tios);  // set EZSP serial port options
            tcgetattr(g_uart_fd, &checkTios);      // and read back the result
            i = (tios.c_cflag ^ checkTios.c_cflag) & CFLAG_MASK;
            if (i) {
                 printf("Termios cflag(s) in error: 0x%04X\r\n", i);
                break;
            }
        }

        i = (tios.c_iflag ^ checkTios.c_iflag) & IFLAG_MASK;
        if (i) {
             printf("Termios c_iflag(s) in error: 0x%04X\r\n", i);
            break;
        }
        i = (tios.c_lflag ^ checkTios.c_lflag) & LFLAG_MASK;
        if (i) {
             printf("Termios c_lflag(s) in error: 0x%04X\r\n", i);
            break;
        }
        i = (tios.c_oflag ^ checkTios.c_oflag) & OFLAG_MASK;
        if (i) {
             printf("Termios c_oflag(s) in error: 0x%04X\r\n", i);
            break;
        }

        for (i = 0; i < NCCS; i++) {
            if (tios.c_cc[i] != checkTios.c_cc[i]) {
                break;
            }
        }
        if (i != NCCS) {
             printf("Termios c_cc(s) in error: 0x%04X\r\n", i);
            break;
        }

        if (  (cfgetispeed(&checkTios) != baud)|| (cfgetospeed(&checkTios) != baud)) {
             printf("Could not set baud rate to %d bps\r\n", bps);
            break;
        }

        return 0;
    }

    if (g_uart_fd != -1) {
        close(g_uart_fd);
        g_uart_fd = -1;
    }

    return -1;
}

static void tuya_uart_send(unsigned char *buf, unsigned int len)
{
    int count = 0;
    int i = 0;
    count = write(g_uart_fd, buf, len);
#ifdef UART_DEBUG_PRINTF
    printf( "Send data[%d:%d]: ", len, count);
    for(i = 0; i < len; i++) {
        printf("%02x ", buf[i]&0xFF);
    }
    printf("\n");
#endif
}

static int tuya_uart_recv(unsigned char *buf, unsigned int len)
{
    int read_len = 0;
    int i = 0;
    read_len = read(g_uart_fd, buf, len);
#ifdef UART_DEBUG_PRINTF
    if(read_len > 0) {
        printf("Recv data[%d:%d]: ", len, read_len);
        for(i = 0; i < read_len; i++){
            printf("%02x ", buf[i]&0xFF);
        }
        printf("\n");
    }
#endif
    return read_len;
}

static void ashCrc(unsigned char *buf, int len, unsigned short *crcData)
{
    unsigned short crcDataTmp = 0xFFFF;
    int i = 0;
    for(i=0; i<len; i++) {
        crcDataTmp = halCommonCrc16(buf[i], crcDataTmp);
    }
    *crcData  = crcDataTmp;
}

// Define constants for the LFSR in ashRandomizeBuffer()
#define LFSR_POLY   0xB8 // polynomial
#define LFSR_SEED   0x42 // initial value (seed)

static unsigned char ashRandomizeArray(unsigned char seed, unsigned char *buf, unsigned char len)
{
  if (seed == 0) {
    seed = LFSR_SEED;
  }
  while (len--) {
    *buf++ ^= seed;
    seed = (seed & 1) ? ((seed >> 1) ^ LFSR_POLY) : (seed >> 1);
  }
  return seed;
}

static unsigned short halCommonCrc16(unsigned char newByte, unsigned short prevResult)
{
  prevResult = ((unsigned short) (prevResult >> 8)) | ((unsigned short) (prevResult << 8));
  prevResult ^= newByte;
  prevResult ^= (prevResult & 0xff) >> 4;
  prevResult ^= (unsigned short) (((unsigned short) (prevResult << 8)) << 4);

  prevResult ^= ((unsigned char) (((unsigned char) (prevResult & 0xff)) << 5))
                | ((unsigned short) ((unsigned short) ((unsigned char) (((unsigned char) (prevResult & 0xff)) >> 3)) << 8));

  return prevResult;
}

static void paraRandom(unsigned char *inoutStr, int inLen)
{
    ashRandomizeArray(0, inoutStr, inLen);
}

static void get_ncp_info_cmd(unsigned char *buf, unsigned int *len)
{
    unsigned char cmdBuf[9] = {0x00, 0x00, 0x21, 0x57, 0x54, 0x39, 0x00, 0x00, 0x7E};
    unsigned short crcData = 0;
    unsigned char ezspSeqTmp = 0;

    cmdBuf[0] =  ((g_ashFramNum & 0x07) << 4) | (g_ashFramNum & 0x07);
    ezspSeqTmp = g_ezspSeq;
    paraRandom(&ezspSeqTmp, 1);
    cmdBuf[1] = ezspSeqTmp;

    ashCrc(cmdBuf, 6, &crcData);
    cmdBuf[6] = ((crcData >> 8)& 0xff);
    cmdBuf[7] = (crcData & 0xff);

    memcpy(buf, cmdBuf, 9);
    *len = 9;

    gcur_ezspSeq = g_ezspSeq;
    g_ashFramNum++;
    g_ezspSeq++;
}

static void get_ncp_reset_cmd(unsigned char *buf, unsigned int *len)
{
    unsigned char cmdBuf[9] = {0x1A, 0xC0, 0x00, 0x00, 0x7E};
    unsigned short crcData = 0;

    ashCrc(&cmdBuf[1], 1, &crcData);
    cmdBuf[2] = ((crcData >> 8)& 0xff);
    cmdBuf[3] = (crcData & 0xff);

    memcpy(buf, cmdBuf, 5);
    *len = 5;
}

static void get_ncp_setver_cmd(unsigned char *buf, unsigned int *len)
{
    unsigned char cmdBuf[9] = {0x00, 0x00, 0x21, 0xa8, 0x53, 0x00, 0x00, 0x7E};
    unsigned short crcData = 0;
    unsigned char ezspSeqTmp = 0;

    cmdBuf[0] =  ((g_ashFramNum & 0x07) << 4) | (g_ashFramNum & 0x07);
    ezspSeqTmp = g_ezspSeq;
    paraRandom(&ezspSeqTmp, 1);
    cmdBuf[1] = ezspSeqTmp;

    ashCrc(cmdBuf, 5, &crcData);
    cmdBuf[5] = ((crcData >> 8)& 0xff);
    cmdBuf[6] = (crcData & 0xff);

    memcpy(buf, cmdBuf, 8);
    *len = 8;

    gcur_ezspSeq = g_ezspSeq;
    g_ashFramNum++;
    g_ezspSeq++;
}

static void get_ack_cmd(unsigned char *buf, unsigned int *len)
{
    unsigned char cmdBuf[4] = {0x00, 0x00, 0x00, 0x7E};
    unsigned short crcData = 0;
    cmdBuf[0] = ((0x1<<7)| (g_ashAckNum & 0x07));

    ashCrc(cmdBuf, 1, &crcData);
    cmdBuf[1] = ((crcData >> 8)& 0xff);
    cmdBuf[2] = (crcData & 0xff);

    memcpy(buf, cmdBuf, 4);
    *len = 4;

    g_ashAckNum++;
}

static void tuya_send_ack(void)
{
    unsigned char send_buf[128]={0};
    unsigned int send_len = 0;

    get_ack_cmd(send_buf,&send_len);
    tuya_uart_send(send_buf,send_len);
}

static void paraNcpData(unsigned char *inAsh, int inAshLen, unsigned char *outEzsp, int *outEzspLen)
{
    *outEzspLen = inAshLen-1-2-1; // 1byte ASH header + 2byte CRC + 1byte tail
    memcpy(outEzsp, inAsh+1, *outEzspLen);
    paraRandom(outEzsp, *outEzspLen);
}

static void para_ncp_info(unsigned short  *manuId, unsigned short  *verNUm,unsigned char *inAsh, int inAshLen)
{
    unsigned char outEzsp[32] = {0};
    int outEzspLen = 0;
    paraNcpData(inAsh, inAshLen, outEzsp, &outEzspLen);
    *manuId = ((outEzsp[7]&0xFF)<<8) | (outEzsp[6] & 0xFF);
    *verNUm = ((outEzsp[9]&0xFF)<<8) | (outEzsp[8] & 0xFF);
}

void uart_recv_hand(bool is_skip, char *data_buf,  int *data_len)
{
    unsigned char recv_cmd[128]={0};

    unsigned char total_recv_cmd[128]={0};
    int read_result = 0;
    int total_len = 0;
    int i = 0;
    int data_index = 0;
    sleep(1);

    do {
        read_result = tuya_uart_recv(recv_cmd, 128);
        if(read_result > 0){
            memcpy(total_recv_cmd+total_len, recv_cmd, read_result);
            total_len += read_result;
            memset(recv_cmd, 0, 128);
        }
    } while(read_result > 0);

    if (FALSE == is_skip && total_len > 0) {

        if (total_len <= 4) // is ack
            return;

        for (i = 0; i < total_len; i++) {
            if (0x7E == total_recv_cmd[i]) {
                break;
            }
        }
        if (i == 3) { // skip ack
            data_index += 4;
        }
        memcpy(data_buf, total_recv_cmd+data_index, total_len-data_index);
        *data_len = total_len-data_index;
    }
    if (total_len > 4) {
        tuya_send_ack();
    }
}

static int ncp_reset(void)
{
    unsigned char send_cmd[20]={0};
    unsigned char recv_cmd[128]={0};
    unsigned int send_len = 0;
    unsigned int recv_len = 0;
    int send_num = 0;
    int recv_num = 0;
    do {
        get_ncp_reset_cmd(send_cmd,&send_len);
        tuya_uart_send(send_cmd, send_len);
        recv_num = 0;
        do {
            usleep(10*1000);
            uart_recv_hand(FALSE, recv_cmd, &recv_len);
            recv_num++;
        } while ((recv_len <= 0) && (recv_num <= 200));
        send_num++;
    } while ((recv_len <= 0) && (send_num <= 3));

    if(recv_len > 0)
        return 0;
    else
        return -1;
}

static int ncp_ver_set(void)
{
    unsigned char send_cmd[20] = {0};
    unsigned char recv_cmd[128] = {0};
    unsigned int send_len = 0;
    unsigned int recv_len = 0;
    int send_num = 0;
    int recv_num = 0;

    do {
        get_ncp_setver_cmd(send_cmd, &send_len);
        tuya_uart_send(send_cmd, send_len);
        recv_num = 0;
        do {
            usleep(10*1000);
            uart_recv_hand(FALSE, recv_cmd, &recv_len);
            recv_num++;
        } while((recv_len <= 0) && (recv_num <= 200));
        send_num++;
    } while ((recv_len <= 0) && (send_num <= 3));

    if (recv_len > 0)
        return 0;
    else
        return -1;
}

static int check_zigbee_is_ok(void)
{
    unsigned char send_cmd[20]={0};
    unsigned char recv_cmd[128]={0};
    unsigned int send_len = 0;
    unsigned int recv_len = 0;
    int read_result = 0;
    unsigned short  manuId = 0;
    unsigned short  verNUm = 0;
    unsigned char ver_str[11]={0};
    int ret = 0;

    ret = ncp_reset();
    if (ret < 0) {
        printf("Ncp reset error.\n");
        return -1;
    }else{
        printf("Ncp reset success.\n");
    }

    ret = ncp_ver_set();
    if (ret < 0) {
        printf("Ncp set version error.\n");
        return -1;
    } else {
        printf("Ncp set version success.\n");
    }

    get_ncp_info_cmd(send_cmd,&send_len);
    tuya_uart_send(send_cmd, send_len);
    uart_recv_hand(FALSE, recv_cmd, &recv_len);

    if (recv_len > 0) {
        para_ncp_info(&manuId, &verNUm, recv_cmd, recv_len);
        sprintf(ver_str, "%d.%d.%d", ((verNUm>>12) & 0xF),((verNUm>>8) & 0xF),(verNUm&0xFF));
        printf("COO manuId:%04x, version:%s.\n", manuId, ver_str);
    } else {
        printf("COO error.\n");
    }

    return 0;
}

int main(char argc, char *argv[])
{
    int ret = 0;

    // open UART
    ret = ezspSetupSerialPort(TEST_UART_NAME, TEST_UART_SPEED, TEST_UART_STIO_BIT, TEST_UART_RTSCTS, TEST_UART_FLOWCTR);
    if (0 == ret) {
        check_zigbee_is_ok();
    } else {
         printf("ezspSetupSerialPort err: %d\n", ret);
    }

    return ret;
}