本文将介绍基于网关开发框架,把 Ubuntu 作为蓝牙网关接入涂鸦 IoT 平台,能够接入涂鸦生态的 Bluetooth Mesh & LE 子设备。
硬件准备
硬件连接
参考 ZS3L 模组规格书,按照下表的对应关系把 ZS3L 和 USB 转串口引脚连接起来。
ZS3L | USB 转串口 |
---|---|
TX | RX |
RX | TX |
RTS | CTS |
CTS | RTS |
硬件连接好之后,把 USB 转串口插入到 PC 的 USB 接口上,使用 附录 的代码读取 ZS3L 模组的固件版本号,用于检测硬件连接是否正常。
创建产品
接入到涂鸦 IoT 平台的设备都有唯一的产品 ID(PID),PID 是在涂鸦 IoT 平台上创建产品获得的。因此,您需要为网关创建产品,定义网关的功能并且选择网关的 App 面板,详细的操作步骤请参考 创建产品。
注意,创建蓝牙网关产品的时候,协议类型要选择 蓝牙Mesh(SIG)。
程序开发
获取开发包
获取网关开发包的步骤如下:
在 Visual Studio Code 安装 IDE 插件并登录,具体操作请参考 Tuya Wind IDE 。
在 Tuya Wind IDE 的主页单击 新建开发框架。开发模式选择 TuyaOS SDK 模式,类型开发包选择 网关设备开发包,开发平台选择 Soc/ubuntu/Linux/X86_64。
选择最新版本,单击 完成 则开始下载开发包。
开发包下载完成后,会自动在 Visual Studio Code 打开工程,其目录结构:
名称 | 说明 |
---|---|
apps | 应用示例,即产品开发包 |
build | 编译配置目录,存放编译配置文件(您无需关注)。 |
build_app.sh | 编译脚本 |
docs | Doxygen 接口文档 |
include | 头文件 |
libs | 库文件 |
Makefile | Makefile 文件 |
output | 编译输出目录,其中生成的程序在 output/<应用工程名称>_<版本号> 路径下。 |
scripts | 编译框架(您无需关注)。 |
vendor | 开发环境。开发环境是执行编译时在线下载到本地的。 |
编译应用
您可以使用 tuyaos_demo_blemesh 来开发蓝牙网关产品,该应用示例包含了蓝牙网关的功能。在 IDE 右击 apps 目录下的 tuyaos_demo_blemesh,单击 Build Project 进行编译。
编译成功,会在 output/tuyaos_demo_blemesh_<版本号>
目录下生成可执行程序。
移植适配
TuyaOS 定义了一套标准化的 TuyaOS Kernel Layer(TKL),旨在屏蔽硬件和系统的差异性,实现跨平台兼容性。因此,您需要完成 TuyaOS 在 Ubuntu 平台上的适配,具体请参考 移植指南。
移植完成后,重新编译应用。
运行应用
可执行程序的同一级目录下有一个配置文件,您需要修改配置文件,修改的内容:
- 把串口设备名称改成您的 PC 识别 USB 转串口的串口设备名称。
- 把
pid
改成您创建产品获得的 PID。 - 把
uuid
和authkey
改成您测试的授权信息。
修改配置文件并保存,直接运行可执行程序。通过涂鸦智慧生活 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;
}