按摩椅项目

原理

此按摩椅由四部分组成:

  1. 按摩椅硬件
  2. 单片机(后续文章中以U表示)
  3. 4G模块(后续文章中以M表示)
  4. 阿里云服务器(后续文章中以S表示)

单片机的功能主要是控制按摩椅操作,比如力度,手法等。使用串口与4G模块通讯。

4G模块的功能主要是与服务器进行通讯,接收服务器指令,通过串口下发指令到下位机

硬件接口

4G模块实物图

单片机实物图

数据通信格式

单片机->4G模块

指令概括

SOI(1)+ACK/CMD(1)+PARAMETER(10)+EOI(1)

解析:

SOI:开始字节,固定为0X7E

CMD:(0x00:获取设备运行状态,0x01:零重力,0x02:升降调节,0x03:气压档位调节),0x04:背部按摩力度

PARAMETER 参数

EOI:结束字节, 固定为0X5A

ACK:应答最高2位表示用户单片机对命令的执行结果:00:执行成功 01:命令错误 10:参数错误 11:其它错误低6位的值与接收到的CMD值相等。

具体通信协议

功能 方向 SOI CMD P1 P2 P3 P4 P5 P6 P7 P8 P9 P10 EOI
获取设备运行状态 M->U 7E 0 0 0 0 0 0 0 0 0 0 0 5A
设备运行状态响应 U->M 7E 0 TI T2 气压(1-10) 按摩力度(1-10) 零重力(0-3) 背部升降(0-2) 腿部升降(0-2) 按摩状态(0-2) 有人检测(0-1) 设备音量(0-10) 5A
零重力 M->U 7E 01 0-3 0 0 0 0 0 0 0 0 0 5A
升降调节 M->U 7E 02 0-3 0 0 0 0 0 0 0 0 0 5A
气压 M->U 7E 03 1-10 0 0 0 0 0 0 0 0 0 5A
按摩力度 M->U 7E 04 1-10 0 0 0 0 0 0 0 0 0 5A

4G模块->服务器

指令概括

代码中封装了一个结构体,作为一个MSG在两者之间传输。

1
2
3
4
5
6
struct uart_msg{
int type; //类型
int hex_no; //指令编号
int value; // 指令数值
int data; //每条指令的调节,比如按摩力度的各种档位
};

具体通信协议

指令 type no value data 描述
授权指令 1 100 100 \ 指令1为授权指令,时间为value
重启指令 2 0 0 \ 指令2为重启指令,no和value无效
暂停/启动 3 0 0/1 \ 指令3为暂停启动指令,value为1时暂停,为0时启动
心跳包 102 0 xx \ 第一个x表示椅子是否在动,第二个x表示椅子上是否有人
零重力指令 999 0 2 0-3 零重力角度0-3
升降调节 999 0 3 0-3 背部以及腿部调节0-3
气压调节 999 0 4 1-10 数值越大,力度越强
背部摩擦 999 0 5 1-10 数值越大,力度越强
音量调节 999 0 6 0-10 数值越大,音量越大
按摩手法 999 0 7 1-10 表示不同手法

代码简述

代码从阿里云sdk/examples/linux/mqtt/demo.c开始

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
enum {
TYPE_SERVER_CTL = 1, //请求授权
TYPE_SERVER_CMD_REBOOT = 2, //重启
TYPE_SERVER_START_OR_PAUSE = 3, //开启或暂停

TYPE_CLIENT_CTL_RESPONED = 101,
TYPE_CLIENT_HEARTBEAT = 102,
TYPE_CLIENT_READY = 103,

TYPE_SERVER_OPERATE = 999, //新增第三方指令集

};
//第三方功能集(服务器->模块)ser_msg.value
enum {
ZERO_GRAVITY = 2, //零重力
ELEVATING_REGULATION = 3, //升降调节
BAROMETRIC_GEAR_REGULATION = 4, //气压档位调节
BACK_MASSAGE_STRENRTH = 5, //背部按摩调节
REMOTE_VOLUME_CONTROL = 6, //音量调节
MASSAGE_TECHNIQUE = 7, //按摩手法
// REMOTE_VOICE_SWITCH = 8, //远程语音播放内容

上述为定义的指令集

messageArrived函数

服务器与4G模块的通讯函数是massageArived函数

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
79
80
81
82
83
84
85
86
87
88
/**********************************************************************
* 接收消息的回调函数
*
* 说明:当其它设备的消息到达时,此函数将被执行。
* 注意:此回调函数中用户在做业务处理时不要使用耗时操作,否则会阻塞接收通道
**********************************************************************/
//Server -> Client包 ,服务器下发的控制报(重启包/授权包...)
static void messageArrived(MessageData *md)
{
//接收消息缓存
char msg[MSG_LEN_MAX] = {0};
struct uart_msg ser_msg;
struct uart_msg respond_msg;
//发送第三方操作指令调用
char cmd[11] = {0};
char parm[AMY_PARM_LEN] = {0};
int status = -1;
MQTTMessage *message = md->message;
if(message->payloadlen > MSG_LEN_MAX - 1)
{
printf("process part of receive message\n");
message->payloadlen = MSG_LEN_MAX - 1;
}
//复制接收消息到本地缓存
memcpy(msg,message->payload,message->payloadlen);
//to-do此处可以增加用户自己业务逻辑,例如:开关灯等操作
//打印接收消息
IOT_LOG("###################################\n");
IOT_LOG("subscribe Message: [%s]\n", msg);
IOT_LOG("###################################\n");
if (parse_server_message(msg, &ser_msg) != TRUE_IOT)
{
printf("subscribe Message: not a valied uart cmd or data messages, ignore this\n");
return;
}
//授权指令,value有效,data无效
else if (ser_msg.type == TYPE_SERVER_CTL)
{
printf("subscribe Message: authorization or not\n");
respond_msg.type = TYPE_CLIENT_CTL_RESPONED;
//respond_msg.hex_no = 0x100;
respond_msg.hex_no = ser_msg.hex_no;
respond_msg.value = 0; //1: process successful, 0:process failed.
if (uart_ctl_trans(uart_fd, ser_msg.value) >= 0)
{ respond_msg.value = 1;
}
pub_uart_msg(&client, &respond_msg);
}
//暂停与继续指令
else if(ser_msg.type == TYPE_SERVER_START_OR_PAUSE)
{
printf("subscribe Message: start or pause!\n");
cmd[0] = 0x07;
cmd[1] = ser_msg.value;
status = uart_data_trans(uart_fd, cmd, parm);
if (status < 0)
{
IOT_LOG("Third-party directives send error: please check!\n");
return ;
}
}
//第三方指令
else if(ser_msg.type == TYPE_SERVER_OPERATE)
{
printf("Operate Message: ser_msg.value = %d,ser_msg.hex_no = %d,ser_msg.data = %d\n",
ser_msg.value,ser_msg.hex_no,ser_msg.data);
//模块到单片机的指令值比服务器到模块的指令少1
//第三方指令2,3,4,5,6,7对应于C->U:1,2,3,4,5,6
//ZERO_GRAVITY = 2, //零重力
//ELEVATING_REGULATION = 3, //升降调节
//BAROMETRIC_GEAR_REGULATION = 4, //气压档位调节
//BACK_MASSAGE_STRENRTH = 5, //背部按摩调节
//REMOTE_VOLUME_CONTROL = 6, //音量调节
//MASSAGE_TECHNIQUE = 7, //按摩手法
if(ser_msg.value == ZERO_GRAVITY || ser_msg.value == ELEVATING_REGULATION || ser_msg.value == BAROMETRIC_GEAR_REGULATION
|| ser_msg.value == BACK_MASSAGE_STRENRTH || ser_msg.value == REMOTE_VOLUME_CONTROL || ser_msg.value == MASSAGE_TECHNIQUE)
{
cmd[0] = ser_msg.value - 1;
cmd[1] = ser_msg.data;
status = uart_data_trans(uart_fd, cmd, parm);
if (status < 0)
{
IOT_LOG("Third-party directives send error: please check!\n");
return ;
}
}
}
}

uart_data_trans函数

这里面涉及的就是将服务器发送过来的数据重新封装从串口发送给下位单片机!
uart_data_trans()函数: 将数据包发送到单片机

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
/************************
send to uart len is 24Bytes. = 1+ 22 + 1
but parse format like: 0x7E(1)+CMD|ACK(1)+PARM(10)+0x5A(1) = 13Bytes
**************************/
int uart_data_trans(int fd, char *cmd, char* out)
{
int ret = 0;
char command = 0;
char ack = 0;
char buf[UART_DATA_SIZE] = {0};
int i = 0;
buf[0] = 0x7E; //SOI
buf[UART_DATA_SIZE-1] = 0x5A; //EOI
if (fd < 0 || !cmd)
return -1;
command = cmd[0];
//memcpy(buf+1, cmd, UART_DATA_SIZE-2);//CMD(1Byte) + PARM(10Bytes)
for (i =0; i < (UART_DATA_SIZE-2)/2; i++)
{
buf[1+i*2] = cmd[i] >> 4;
buf[1+i*2+1] = cmd[i]&0xf;
}

ret = uart_send(fd, buf, UART_DATA_SIZE);
if (ret < 0)
{
printf("%s(): uart_send() failed\n" ,__FUNCTION__);
return ret;
}
memset(buf, 0 , UART_DATA_SIZE);
ret = uart_recv(fd, buf, UART_DATA_SIZE);
if (ret < 0)
{
printf("%s(): uart_recv() failed\n",__FUNCTION__);
return ret;
}

if (buf[0] != 0x7E || buf[UART_DATA_SIZE-1] != 0x5A)
{
printf("uart data recv format error: first bytes=0x%02x, end byte=[0x%02x]", buf[0],buf[UART_DATA_SIZE-1]);
return -1;
}
ack = (buf[1]<<4) | (buf[2]&0xf); if ((ack & ACK_CMD_MASK) != command)
{
printf("uart data ack format error: command =0x%02x, ack cmd =[0x%02x]", command, (ack & ACK_CMD_MASK));
return -1;
}

if ((ack>>6) != ACK_AMY_SUCCESS)
{
printf("uart data ack error: [%d(1:cmd err, 2:parm err, 3:other err)]", (ack>>6));
return -1;
}

ret = 0;
for (i =0; i < AMY_PARM_LEN; i++)
{
out[i] = (buf[3+i*2] << 4) | (buf[3+i*2+1]&0xf);
if (debug)
printf("(%02x%02x)[%02x] ",buf[3+i*2], buf[3+i*2+1], out[i]);
}
if (debug)
{
printf("\n#########parse parms 20Bytes to 10Byte\n");
}

IOT_LOG("%s(): successful, ret=[%d]\n" ,__FUNCTION__, ret);
return ret;
}

这上面的函数就是发送数据包到单片机

parse_server_message函数

parse_server_message()函数:解析服务器发送过来的原始数据填充到struct uart_msg结构体

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
int parse_server_message(char* msg_buf, struct uart_msg *msg)
{
if(!msg_buf || !msg)
return FALSE_IOT;
//"{\"type\":1, \"no\":10, \"value\":\"0\", \"data\":10, \"code\":null}"
char *p = NULL;
char *dilem = ",";
char *remain = NULL;
char *src = msg_buf;
char *tmp = NULL;
char *p2 = NULL;
int i = 0;

while(NULL != ( p = strtok_r(src, dilem, &remain) ))
{
//printf("p[%s],remain[%s]\n",p, remain);
src = NULL;
if ((tmp = strchr(p, ':')) != NULL)
{
i++;
if (strstr(p, "type"))
{
msg->type = atoi(tmp+1);
}
else if (strstr(p, "no"))
{
msg->hex_no = strtol(tmp+1, NULL, 16);//hex type
}
else if (strstr(p, "value"))
{
if ((p2 = strchr(tmp+1, '\"')) != NULL || (p2 = strchr(tmp+1, '\'')) != NULL)
{//value parsed type string.
msg->value = atoi(p2+1);
}
else
{//value parsed type int.
msg->value = atoi(tmp+1);
}
}
else if(strstr(p, "data"))
{
msg->data = atoi(tmp+1);
}
else
{
//invalid json key
i--;
}
}
}
if (i < 3)
{
printf("%s(): Error:lose parameter, i=[%d] less than 3!\n", __FUNCTION__, i);
return FALSE_IOT;
}
IOT_LOG("%s(): parse end:type=[%d], no=[%x], value:[\"%d\"], data:[%x]\n", __FUNCTION__, msg->type, msg->hex_no, msg->value, msg->data);
return TRUE_IOT;
}

原始数据是 type:1, no:10, value:0, data:10, code:null

所以以“,”作为分隔,“:”再次分隔可得到相应的数据

总结

本次修改是基于阿里云iot-sdk下运行的,所以关于mqtt代码都是现成的,有兴趣可以查看相关的源码