资讯详情

树莓派如何与物联网平台交互(搭建一个树莓派网关)(二)

树莓派如何与物联网平台互动(搭建树莓派网关)(2)

六、程序设计

程序设计大致分为两部分,一部分是树莓派和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等,至少保证产品下唯一)。

image-20211209105522297

  • 在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接口,会出现显示器无法显示的情况。

(5)拆插设备前需要先断电。

软件注意事项:

(1)由于树莓派跑了两个进程,因此整个过程中需要两个程序同时运行。

(2)软件调试过程中其它注意事项已在“程序设计”中具体指出。

标签: cf7自启动继电器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台