树莓派如何与物联网平台交互(搭建一个树莓派网关)
Posted 三明治开发社区
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了树莓派如何与物联网平台交互(搭建一个树莓派网关)相关的知识,希望对你有一定的参考价值。
树莓派如何与物联网平台交互(搭建一个树莓派网关)(二)
六、程序设计
程序设计大致分为两部分,一部分是树莓派和485子设备通信的程序,另外一部分是树莓派和涂鸦云平台进行交互的程序。
1、连接涂鸦云平台
(1)程序设计简单概述
树莓派和涂鸦云平台进行数据交互的时候采用c语言编写的。
(2)程序设计逻辑分析
在平台上创建网关设备时,下载“C TuyaLink SDK”开发包,在此demo上移植自己想要实现的功能。
在data_model_basic_demo.c实现整个控制逻辑:
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include "cJSON.h"
#include "tuya_cacert.h"
#include "tuya_log.h"
#include "tuya_error_code.h"
#include "system_interface.h"
#include "mqtt_client_interface.h"
#include "tuyalink_core.h"
const char productId[] = "t1tlm6p13aouheta";
const char deviceId[] = "6cf918e90b12f7b1ffwiuz";
const char deviceSecret[] = "a5f23a3fb341edbd";
tuya_mqtt_context_t client_instance;
//写文件
int write_file(char str[])
FILE *fp = NULL;
fp = fopen("/tmp/from_platform.txt", "w");
fprintf(fp, "%s", str);
fclose(fp);
printf("deviceid_action_time: %s\\n", str);
return 0;
//读文件
int read_file(int num,char return_data[])
FILE *fp = NULL;
switch (num)
case 1:
fp = fopen("/tmp/1.txt", "r");
if(fp == NULL)
printf("open /tmp/1.txt error!\\n");
return 0;
fgets(return_data, 255, (FILE*)fp);
fclose(fp);
break;
case 2:
fp = fopen("/tmp/2.txt", "r");
if(fp == NULL)
printf("open /tmp/2.txt error!\\n");
return 0;
fgets(return_data, 255, (FILE*)fp);
fclose(fp);
break;
case 3:
fp = fopen("/tmp/3.txt", "r");
if(fp == NULL)
printf("open /tmp/3.txt error!\\n");
return 0;
fgets(return_data, 255, (FILE*)fp);
fclose(fp);
break;
default:
break;
printf("return_data:%s\\n",return_data);
return 0;
//写一个线程函数 void *函数名(void *arg)
void *thread_worker1(void *arg)
char json_temp_hum[255];
char json_door_state[255];
char json_data[255];
char json_data1[255];
while(1)
if(read_file(2,json_data) == 0)
sprintf(json_temp_hum,"%s",json_data);
printf("json_temp_hum:%s\\r\\n",json_temp_hum);
tuyalink_thing_property_report_with_ack(arg, NULL, json_temp_hum);
memset(json_data, 0, sizeof(json_data));
else
printf("read temp_hum error\\r\\n");
if(read_file(3,json_data1) == 0)
sprintf(json_door_state,"%s",json_data1);
printf("json_door_state:%s\\r\\n",json_door_state);
tuyalink_thing_event_trigger(arg, NULL, json_door_state);
memset(json_data1, 0, sizeof(json_data1));
else
printf("read door_state error\\r\\n");
sleep(2);
void on_connected(tuya_mqtt_context_t* context, void* user_data)
int error=0;
pthread_t t1;
tuyalink_subdevice_bind(context, "[\\"productId\\":\\"snigjkwkheaxueqa\\",\\"nodeId\\":\\"255\\",\\"clientId\\":\\"1\\"]");//继电器
tuyalink_subdevice_bind(context, "[\\"productId\\":\\"dtoqgbr5azgwvga3\\",\\"nodeId\\":\\"254\\",\\"clientId\\":\\"2\\"]");//门磁
tuyalink_subdevice_bind(context, "[\\"productId\\":\\"6jmmnuwavyxkcv1x\\",\\"nodeId\\":\\"1\\",\\"clientId\\":\\"3\\"]");//温湿度
error=pthread_create(&t1,NULL,thread_worker1,context);
if(error)
printf("create pthread error!\\n");
return;
void on_disconnect(tuya_mqtt_context_t* context, void* user_data)
TY_LOGI("on disconnect");
void on_messages(tuya_mqtt_context_t* context, void* user_data, const tuyalink_message_t* msg)
char json_relay_state[255];
TY_LOGI("on message id:%s, type:%d, code:%d", msg->msgid, msg->type, msg->code);
switch (msg->type)
case THING_TYPE_MODEL_RSP:
TY_LOGI("Model data:%s", msg->data_string);
break;
case THING_TYPE_PROPERTY_SET:
TY_LOGI("property set:%s", msg->data_string);
break;
case THING_TYPE_PROPERTY_REPORT_RSP:
break;
case THING_TYPE_ACTION_EXECUTE:
TY_LOGI("action execute:%s", msg->data_string);
if(write_file(msg->data_string) == 0)
printf ("write ok\\r\\n");
printf ("data_string:%s\\r\\n",msg->data_string);
else
printf ("write error\\r\\n");
if(read_file(1,json_relay_state) == 0)
sprintf(msg->data_string,"%s",json_relay_state);
printf("json_relay_state:%s\\r\\n",json_relay_state);
tuyalink_thing_property_report(context, NULL, msg->data_string);
memset(json_relay_state, 0, sizeof(json_relay_state));
else
printf ("read relay_state error\\r\\n");
break;
default:
break;
printf("\\r\\n");
int main(int argc, char** argv)
int ret = OPRT_OK;
tuya_mqtt_context_t* client = &client_instance;
ret = tuya_mqtt_init(client, &(const tuya_mqtt_config_t)
.host = "m2.tuyacn.com",
.port = 8883,
.cacert = tuya_cacert_pem,
.cacert_len = sizeof(tuya_cacert_pem),
.device_id = deviceId,
.device_secret = deviceSecret,
.keepalive = 60,
.timeout_ms = 2000,
.on_connected = on_connected,
.on_disconnect = on_disconnect,
.on_messages = on_messages
);
assert(ret == OPRT_OK);
ret = tuya_mqtt_connect(client);
assert(ret == OPRT_OK);
for (;;)
/* Loop to receive packets, and handles client keepalive */
tuya_mqtt_loop(client);
return ret;
(3)程序函数功能解析
注意:下文会提到"树莓派网关",以及"c端"和"python端"词,具体所指含义如下:
树莓派做成网关,里面跑了两个进程,一个是采用python语言处理子设备数据收发,以及json数据解析(python端);另外一个进程是采用c语言处理和涂鸦云平台进行交互(c端)。
- 下面是在平台上创建网关设备时候的参数,填入对应的位置即可。
const char productId[] = "t1tlm6p13aouheta";
const char deviceId[] = "6cf918e90b12f7b1ffwiuz";
const char deviceSecret[] = "a5f23a3fb341edbd";
- 在main函数里面实例化和初始化一个设备对象 tuya_mqtt_context_t,用来初始化产品 ID 和授权信息等配置参数以及循环接收数据包,并处理客户端保持连接。
int main(int argc, char** argv)
int ret = OPRT_OK;
tuya_mqtt_context_t* client = &client_instance;
ret = tuya_mqtt_init(client, &(const tuya_mqtt_config_t)
.host = "m2.tuyacn.com",
.port = 8883,
.cacert = tuya_cacert_pem,
.cacert_len = sizeof(tuya_cacert_pem),
.device_id = deviceId,
.device_secret = deviceSecret,
.keepalive = 60,
.timeout_ms = 2000,
.on_connected = on_connected,
.on_disconnect = on_disconnect,
.on_messages = on_messages
);
assert(ret == OPRT_OK);
ret = tuya_mqtt_connect(client);
assert(ret == OPRT_OK);
for (;;)
/* Loop to receive packets, and handles client keepalive */
tuya_mqtt_loop(client);
return ret;
启动 TuyaOS SDK 服务。
ret = tuya_mqtt_connect(client);
//TuyaOS SDK 服务任务,数据接收处理,设备在线保活等任务处理:
循环调用将当前线程产生给底层的 Link SDK 客户端。
tuya_mqtt_loop(client);
- 定义应用层事件回调,on_messages回调函数用于应用层接收 SDK 事件通知,如数据功能点(DP)下发,云端连接状态通知。平台下发指令在此函数中实现。
void on_messages(tuya_mqtt_context_t* context, void* user_data, const tuyalink_message_t* msg)
char json_relay_state[255];
TY_LOGI("on message id:%s, type:%d, code:%d", msg->msgid, msg->type, msg->code);
switch (msg->type)
case THING_TYPE_MODEL_RSP:
TY_LOGI("Model data:%s", msg->data_string);
break;
case THING_TYPE_PROPERTY_SET:
TY_LOGI("property set:%s", msg->data_string);
break;
case THING_TYPE_PROPERTY_REPORT_RSP:
break;
case THING_TYPE_ACTION_EXECUTE:
TY_LOGI("action execute:%s", msg->data_string);
if(write_file(msg->data_string) == 0)
printf ("write ok\\r\\n");
printf ("data_string:%s\\r\\n",msg->data_string);
else
printf ("write error\\r\\n");
if(read_file(1,json_relay_state) == 0)
sprintf(msg->data_string,"%s",json_relay_state);
printf("json_relay_state:%s\\r\\n",json_relay_state);
tuyalink_thing_property_report(context, NULL, msg->data_string);
memset(json_relay_state, 0, sizeof(json_relay_state));
else
printf ("read relay_state error\\r\\n");
break;
default:
break;
printf("\\r\\n");
- THING_TYPE_ACTION_EXECUTE主题中,树莓派网关从平台上获取的指令(json格式)存入文件中,供python端调用(python端处理后控制继电器动作)。树莓派网关同时从相应文件中读取设备数据值(python端获取设备值,处理成json格式存入文件中供c端调用),C端处理成json格式的字符串上报到云端。
case THING_TYPE_ACTION_EXECUTE:
TY_LOGI("action execute:%s", msg->data_string);
if(write_file(msg->data_string) == 0)
printf ("write ok\\r\\n");
printf ("data_string:%s\\r\\n",msg->data_string);
else
printf ("write error\\r\\n");
if(read_file(1,json_relay_state) == 0)
sprintf(msg->data_string,"%s",json_relay_state);
printf("json_relay_state:%s\\r\\n",json_relay_state);
tuyalink_thing_property_report(context, NULL, msg->data_string);
memset(json_relay_state, 0, sizeof(json_relay_state));
else
printf ("read relay_state error\\r\\n");
break;
- 网关发现子设备,请求云端激活子设备并建立topo关系。适用于设备无法预先在云端注册,也无法烧录,网关发现子设备后,请求云端注册并绑定到当前网关下。在mqtt连接成功回调函数里面绑定了三个子设备,子设备的productId从平台上获取;nodeId是设备的节点id(至少保证网关下唯一,可以是子设备的地址);clientId是设备端唯一id(子设备硬件的唯一标示,可以是设备的 uuid、mac、sn等,至少保证产品下唯一)。
- 在mqtt连接成功回调函数中创建了一个子线程。
线程函数具体实现功能:用于不断获取温湿度的数据以及门磁状态的数据,同时上报到云平台。
//写一个线程函数 void *函数名(void *arg)
void *thread_worker1(void *arg)
char json_temp_hum[255];
char json_door_state[255];
char json_data[255];
char json_data1[255];
while(1)
if(read_file(2,json_data) == 0)
sprintf(json_temp_hum,"%s",json_data);
printf("json_temp_hum:%s\\r\\n",json_temp_hum);
tuyalink_thing_property_report_with_ack(arg, NULL, json_temp_hum);
memset(json_data, 0, sizeof(json_data));
else
printf("read temp_hum error\\r\\n");
if(read_file(3,json_data1) == 0)
sprintf(json_door_state,"%s",json_data1);
printf("json_door_state:%s\\r\\n",json_door_state);
tuyalink_thing_event_trigger(arg, NULL, json_door_state);
memset(json_data1, 0, sizeof(json_data1));
else
printf("read door_state error\\r\\n");
sleep(2);
- 下面是读文件函数,主要用于读取python端获取子设备的数据值,然后放于数组 return_data中。read_file函数传入了两个参数,一个是num,用于区分读取的文件(三个文件分别存入三个不同设备的数据值);另外一个是数组return_data,用于存储读取文件的数据(供c端调用)。
//读文件
int read_file(int num,char return_data[])
FILE *fp = NULL;
switch (num)
case 1:
fp = fopen("/tmp/1.txt", "r");
if(fp == NULL)
printf("open /tmp/1.txt error!\\n");
return 0;
fgets(return_data, 255, (FILE*)fp);
fclose(fp);
break;
case 2:
fp = fopen("/tmp/2.txt", "r");
if(fp == NULL)
printf("open /tmp/2.txt error!\\n");
return 0;
fgets(return_data, 255, (FILE*)fp);
fclose(fp);
break;
case 3:
fp = fopen("/tmp/3.txt", "r");
if(fp == NULL)
printf("open /tmp/3.txt error!\\n");
return 0;
fgets(return_data, 255, (FILE*)fp);
fclose(fp);
break;
default:
break;
printf("return_data:%s\\n",return_data);
return 0;
- 下面是写文件函数,主要存储从云端获取的指令,然后供python端调用。
//写文件
int write_file(char str[])
FILE *fp = NULL;
fp = fopen("/tmp/from_platform.txt", "w");
fprintf(fp, "%s", str);
fclose(fp);
printf("deviceid_action_time: %s\\n", str);
return 0;
(4)调试过程中注意点
- 如果调试过程中报段错误,首先应该想到段错误的定义,从它出发考虑引发错误的原因。
a.在使用指针时,定义了指针后记得初始化指针,在使用的时候记得判断是否为NULL。
b.在使用数组时,注意数组是否被初始化,数组下标是否越界,数组元素是否存在等。
c.在访问变量时,注意变量所占地址空间是否已经被程序释放掉。
d.在处理变量时,注意变量的格式控制是否合理等。
- 在调试的过程中由于没有对打开文件为空时进行处理,导致出现了段错误,后面如下图修改解决了此问题。
2、485子设备通信
(1)程序设计简单概述
树莓派和485子设备通信采用python语言来编写的。
(2)程序设计逻辑分析
在new_temp_hum_door.py文件中实现整个控制逻辑:
- 温湿度和门磁状态获取函数里面引用类(从new_relay_control文件中引用relay ),执行相应的控制逻辑;While循环读取文件中存储的数据(平台下发的指令,json格式),指令解析之后,控制继电器动作以及循环获取温湿度数据和门磁的状态,处理成json格式的字符串存入文件中(供c端读取)。
# -*- coding: utf-8 -*-
from new_relay_control import relay
from time import sleep
import json
#温湿度获取
def temp_hum_sensor_get():
temp_hum = relay()
temp_hum.all_relay = 3
temp_hum.relay_all_on_order = ['01 04 00 00 00 02 71 CB']
return_str = temp_hum.ALL_ON()
return return_str
#门磁状态获取
def door_sensor_get():
door_sensor = relay()
door_sensor.all_relay = 3
door_sensor.relay_all_on_order = ['FE 01 00 00 00 02 A9 C4']
return_str = door_sensor.ALL_ON()
return return_str
id_value = 2
time = 1
while True:
try:
#读文件(读平台下发的指令)
fp = open('/tmp/from_platform.txt', 'r')
str_read = fp.read()
# "inputParams":"relay_action":true,"actionCode":"relay"
recv_json = json.loads(str_read)
relay_action = recv_json['inputParams']['action']
action_code = recv_json['actionCode']
print(relay_action)
print(action_code)
print("read from platform: %s " % str_read)
print("data type: %s" % (type(str_read)))
if action_code == "relay":
relay_open = relay()
if relay_action == True:
return_str = relay_open.ALL_ON()
else:
return_str = relay_open.ALL_OFF()
relay_str = return_str[8:12]
upload_to_platform = 0
print(relay_str)
if relay_str != "0000":
upload_to_platform = 1
string = "\\"actionCode\\": \\"relay\\", \\"actionTime\\": 1626197189630,\\"outputParams\\": \\"relaystate\\":0"
new_json = json.loads(string)
new_json['outputParams']['relaystate'] = upload_to_platform
final_str3 = json.dumps(new_json)
print(final_str3)
f = open('/tmp/1.txt', 'w')
f.write(final_str3)
f.close()
except FileNotFoundError:
print ("File is not found")
if id_value == 2:
return_str = temp_hum_sensor_get()
print(return_str)
get_str1 = return_str[6:10]
get_str2 = return_str[10:14]
try:
# temp_value = (int(get_str1, 16))/10
temp_value = int(get_str1, 16)
# hum_value = (int(get_str2, 16)) / 10
hum_value = int(get_str2, 16)
except ValueError:
pass
print(temp_value)
print(hum_value)
print(get_str1)
print(get_str2)
string1 = "\\"temp\\":\\"value\\":\\"temp_value\\",\\"time\\":1631708204231,\\"hum\\":\\"value\\":\\"hum_value\\",\\"time\\":1631708204231"
new_json1 = json.loads(string1)
new_json1['temp']['value'] = temp_value
new_json1['hum']['value'] = hum_value
final_str4 = json.dumps(new_json1)
print(final_str4)
f = open('/tmp/2.txt', 'w')
f.write(final_str4)
f.close()
#if id_value == 3:
return_str = door_sensor_get()
get_str3 = return_str[6:8]
door_upload_to_platform = 0
if get_str3 == "00":
door_upload_to_platform = 0
else:
door_upload_to_platform = 1
string2 = "\\"eventCode\\":\\"door\\",\\"eventTime\\":1626197189630,\\"outputParams\\":\\"doorsate\\":0"
new_json2 = json.loads(string2)
new_json2['outputParams']['doorsate'] = door_upload_to_platform
final_str5 = json.dumps(new_json2)
print(get_str3)
print(final_str5)
f = open('/tmp/3.txt', 'w')
f.write(final_str5)
f.close()
sleep(time)
- 使用class定义类,实现继电器的控制逻辑。类里面定义了串口收发的函数relay_send(self, send_order)以及继电器打开与关闭的控制逻辑。
# -*- coding: utf-8 -*-
import RPi.GPIO as GPIO
import serial
from time import sleep
'''2路继电器开关控制函数,单独继电器开关控制和全部开关控制'''
class relay(object):
def __init__(self):
self.relay_all_on_order = ['02 05 00 00 FF 00 8C 09', '02 05 00 01 FF 00 DD C9', '02 0F 00 00 00 08 01 FF FE C0']
self.relay_all_off_order = ['02 05 00 00 00 00 CD F9', '02 05 00 01 00 00 9C 39', '02 0F 00 00 00 08 01 00 BE 80']
self.relay1 = 1
self.relay2 = 2
self.all_relay = 3
self.port = '/dev/ttyAMA0'
def relay_send(self, send_order):
if self.port:
relay_serial = serial.Serial(self.port, 9600)
GPIO.setmode(GPIO.BCM)
GPIO.setup(17, GPIO.OUT)
if not relay_serial.isOpen():
relay_serial.Open()
while True:
GPIO.output(17, GPIO.HIGH)
sleep(0.01)
relay_serial.write(bytes.fromhex(send_order))
#relay_serial.write(bytes(send_order))
sleep(0.01)
GPIO.output(17, GPIO.LOW)
count = relay_serial.inWaiting()
if count > 0:
GPIO.output(17, GPIO.LOW)
sleep(0.01)
recv = relay_serial.read(count)
GPIO.output(17, GPIO.HIGH)
sleep(0.01)
print("recv: ", recv)
# recv_bytes = binascii.b2a_hex(recv)
# recv_str = binascii.b2a_hex(recv_bytes).decode('utf-8')
recv_str = str(recv.hex())
print("recv_str: ", recv_str)
print("recv_str: ", recv_str)
if recv_str == "00":
print("error")
else:
return recv_str
sleep(0.5)
#relay_serial.close()
def ALL_ON(self):
send_order = self.relay_all_on_order[self.all_relay - 3]
print(send_order)
get_return = self.relay_send(send_order)
print("继电器控制: ALL_RELAY_ON")
return get_return
def ALL_OFF(self):
send_order = self.relay_all_off_order[self.all_relay - 3]
get_return = self.relay_send(send_order)
print("继电器控制: ALL_RELAY_OFF")
return get_return
def RELAY1_ON(self):
send_order = self.relay_all_on_order[self.relay1 - 1]
get_return = self.relay_send(send_order)
print("继电器控制: RELAY1_ON")
return get_return
def RELAY1_OFF(self):
send_order = self.relay_all_off_order[self.relay1 - 1]
get_return = self.relay_send(send_order)
print("继电器控制: RELAY1_OFF")
return get_return
def RELAY2_ON(self):
send_order = self.relay_all_on_order[self.relay2 - 1]
get_return = self.relay_send(send_order)
print("继电器控制: RELAY2_ON")
return get_return
def RELAY2_OFF(self):
send_order = self.relay_all_off_order[self.relay2 - 1]
get_return = self.relay_send(send_order)
print("继电器控制: RELAY2_OFF")
return get_return
if __name__ == "__main__":
relay = relay()
relay.port = '/dev/ttyAMA0'
(3)调试过程中注意点
子设备通信程序需要在python3以上版本进行编译运行,如果低版本,编译运行时会报错。
有些写法,低版本的不支持。例如下面写法,python2.7版本会报错,python3以上版本则不会。
temp_value = int(get_str1, 16)
hum_value = int(get_str2, 16)
下面编码声明在python3以上版本则不用,在低版本需要声明,不然运行会报错。
# -*- coding: utf-8 -*-
下面写法同样在python3以上版本适用,在低版本中则不支持,不然运行会报错。
recv_str = str(recv.hex())
若出现下面读文件错误,导致编译运行中断,则是由于当文件不存在时,没有进行异常处理。(由于收到平台指令开始创建文件的,所以没有指令下发时,文件则没有创建,导致运行python端程序时会报错)
可以参照下图进行解决:(添加捕获异常处理)
七、编译执行(linux)
1、安装 make
等相关环境依赖。
sudo apt-get install make cmake
2、新建一个文件夹开始编译。
mkdir build && cd build
cmake ..
make
3、运行 Demo。
./bin/data_model_basic_demo
4、在设备端查看运行接口。
以下日志显示设备与 Tuya 云连接成功。
5、设备成功连接到涂鸦云平台后,单击进行刷新,设备状态会显示为在线。
八、在线调试
(1)在平台上查看上报的消息以 及对设备下发相应指令
2、温湿度值上报云平台(属性)
数据有没有上报成功,看code值:
3、门磁设备上报数据到云平台(事件)
数据有没有上报成功,看code值:
4、平台下发指令,继电器动作(动作)
九、结果展示
十、注意事项
硬件注意事项:
(1)树莓派的引脚短路,特别是VCC和GND,短路会造成芯片烧毁无法恢复。
(2)树莓派启动需要几十秒时间,打开电源后1分钟内不可以关闭电源,会影响树莓派的使用寿命。
(3)SD卡烧录系统完成时,系统会提示格式化,此时不需要格式化,点击取消即可。若点了格式化后树莓派会提示缺失文件,需要重新烧录系统。
(4)树莓派4B的HDMI接口变成两个micro-HDMI接口(hdmi0和hdmi1),可以接入两个显示器。如果只连接一个显示器,一定要插入hdmi0接口,也就是靠近type-C电源接口的那一个,才可以正常显示;如果只插入hdmi1接口,会出现显示器无法显示的情况。
注意!!hdmi线反接会导致接口损坏!!!
(5)拆插设备前需要先断电。
软件注意事项:
(1)由于树莓派跑了两个进程,因此整个过程中需要两个程序同时运行。
(2)软件调试过程中其它注意事项已在“程序设计”中具体指出。
以上是关于树莓派如何与物联网平台交互(搭建一个树莓派网关)的主要内容,如果未能解决你的问题,请参考以下文章
markdown 树莓派零usb + wlan双网络添加网关联网