Main Content

本页采用了机器翻译。点击此处可查看最新英文版本。

使用安全 MQTT 发布和订阅进行远程传感器控制

此示例展示了如何在 ThingSpeak 中使用 MQTT 发布和订阅架构。MQTT 协议是一种低开销的设备消息传递系统。使用 MQTT,订阅控制通道的字段 1。当您更新控制通道时,发布的值将发送到您的设备。舵机旋转到指定角度。设备测量网络强度并更新存储通道。代码中的注释指示如何将此示例改编为非安全连接。

支持的硬件

  • ESP8266、NodeMCU、WeMOS

  • Arduino MKR1000

  • 具有无线网络连接的 Arduino Uno、Mega、Due 或 Leonardo

  • 粒子光子(稍微调整代码和原理图)

该示例设计为仅使用几个额外组件,即单个伺服电机。您可以使用板载 Wi-Fi 天线进行 Wi-Fi 强度测量。

这些图像显示了存储通道的示例通道输出。字段 1 存储从控制通道设置的伺服电机角度,字段 2 显示测量的 Wi-Fi 强度值。

数据分析表明,WeMOS硬件的方向对测量的信号强度有方向性影响。

先决条件

1) 为订阅控件创建 ThingSpeak 通道,如 Collect Data in a New Channel 所示。订阅通道保存伺服电机的角度。当伺服电机角度更新时,订阅设备会收到 MQTT 消息形式的角度。该设备设置伺服角度并测量该角度下的新无线网络强度。

2) 为发布的数据创建另一个 ThingSpeak通道。发布通道记录设置的角度和信号强度数据。

3) 在“通道设置”“”视图中,为发布通道启用字段 1 和 2。为了区分这些字段,请为每个字段指定一个描述性名称。

4) 注意“通道设置”视图中“API 密钥”选项卡上的读取和写入 API 密钥(图中圈出)。

5) 点击“设备”创建 MQTT 设备>页面顶部的“MQTT”,然后“添加新设备”。设置设备时,授权两个通道进行发布和订阅。详情请参见创建 ThingSpeak MQTT 设备

6) 添加新设备时,点击“下载凭据”> Arduino (mqtt_secrets.h) .保留此下载的机密文件,以便在下面的代码部分中访问。

所需硬件

  • WeMOS D1 Mini 或以下设备之一(对所使用的库进行了更改):NodeMCU、ESP8266-01、ESP8266-04、ESP8266-12、ESP8266-12E、Arduino® MKR1000 或其他具有以太网或无线网络连接的 Arduino

  • 伺服电机(示例Futaba S3003)

  • 跳线(至少 3 条)

  • USB电缆

原理图和连接

1)将WeMOS D1 Mini上的D5连接到舵机的信号线。

2)将舵机的地线连接到WeMOS板上的地。

3) 将伺服电源连接至3.3V。直接使用5V在某些情况下可能会超出USB功率限制。

为您编程 Arduino

使用 Arduino IDE 对您的设备进行编程。您可以下载最新的 Arduino IDE here

1)添加ESP8266板包:

a.在“文件”下>“预设项”,在“其他板卡管理器 URL”中输入 https://arduino.esp8266.com/stable/package_esp8266com_index.json

b.选择“工具”> > 板卡管理器 。在搜索栏中输入 ESP8266 并安装该软件包。

2) 修改允许的数据包大小。

a.导航到pub sub库头文件文件所在的文件夹,一般为Documents\Arduino\libraries\PubSubClient\src

b.编辑 PubSubClient.h to c将最大数据包大小更改为 4096。完成后,该行应显示为:

#define MQTT_MAX_PACKET_SIZE 4096

3)创建应用:

a.在 Arduino IDE 中打开一个新窗口,然后保存文件。

b.添加代码部分中提供的代码。

C。请务必编辑代码中的无线网络信息和通道ID。

4) 将库和机密文件添加到代码中:

a.如果尚不存在,请通过选择“代码”将以下库添加到库管理器中> 包括库 > 管理库 。对于每个库,搜索其名称并选择“安装”。

  • PubSubClient

  • ESP8266Wifi

  • servo

b.添加 mqtt_secrets.h 文件。

测试您的设备

成功上传程序后,您可以使用串行监视器监视输出。将 0 到 175 范围内的值上传到 ThingSpeak 控制通道。您可以从“API 密钥”选项卡复制 GET请求格式,或使用您编写的 API 密钥修改此文本。将每个 URL 直接输入到浏览器的地址栏中,将 YOUR WRITE API KEY 更改为您通道的 write API 密钥。

https://api.thingspeak.com/update?api_key=YOUR_WRITE_API_KEY&field1=ANGLE_VALUE

每当您发布到订阅通道时,设备都会将角度和 Wi-Fi 信号强度发布到存储通道。确保角度值在 0 到 175 范围内。

代码

1) 包含所需的库并定义数据字段:

#include <PubSubClient.h>
#include <WiFiClientSecure.h>                         // Needed only if using secure connection.
#include <ESP8266WiFi.h>
#include <Servo.h> 
#include "mqtt_secrets.h"
#define ANGLE_FIELD 0   
#define DATA_FIELD 1                                  // Data field to post the signal strength to.

2) 定义并初始化变量。请务必编辑无线网络信息、通道ID 和凭据。在您的通道主页顶部找到您的通道ID。

char ssid[] = "YOUR_SSID";                   // Change to your network SSID (name).
char pass[] = "YOUR_WIFI_PASSWORD";          // Change to your network password.
const char* server = "mqtt3.thingspeak.com";
char mqttUserName[] = SECRET_MQTT_USERNAME;  // Change to your MQTT device username.    
char mqttPass[] = SECRET_MQTT_PASSWORD;      // Change to your MQTT device password.
char clientID[] = SECRET_MQTT_CLIENT_ID;     // Change to your MQTT device clientID.
long readChannelID = 85;
long writeChannelID = 86;

// Here's how to get ThingSpeak server fingerprint: https://www.a2hosting.com/kb/security/ssl/a2-hostings-ssl-certificate-fingerprints
const char* thingspeak_server_fingerprint = "27 18 92 dd a4 26 c3 07 09 b9 7a e6 c5 21 b9 5b 48 f7 16 e1";

// WiFiClient client;                                 // Initialize the Wi-Fi client library. Uncomment for nonsecure connection.
WiFiClientSecure client;                              // Uncomment for secure connection.  
PubSubClient mqttClient( client );                    // Initialize the PuBSubClient library.
Servo myservo;  // Create servo object to control a servo .

int fieldsToPublish[8]={1,1,0,0,0,0,0,0};             // Change to allow multiple fields.
float dataToPublish[8];                               // Holds your field data.
int changeFlag=0;                                     // Let the main loop know there is new data to set.
int servo_pos=0;                                      // Servo position

3) 定义此代码中的函数原型。

//  
// Prototypes
//

// Handle messages from MQTT subscription.
void mqttSubscriptionCallback(char* topic, byte* payload, unsigned int length);  

// Generate a unique client ID and connect to MQTT broker.
void mqttConnect();  

// Subscribe to a field or feed from a ThingSpeak channel.
int mqttSubscribe( long subChannelID,int field, int unSub);

// Publish messages to a channel feed.

// Connect to a given Wi-Fi SSID.
int connectWifi();

// Measure the Wi-Fi signal strength.
void updateRSSIValue();

4) 初始化输入和输出引脚,启动串行监视器,并在 setup 例程中初始化 MQTT 客户端。

void setup() {
Serial.begin( 115200 );
Serial.println( "Start" );
int status = WL_IDLE_STATUS; // Set temporary Wi-Fi status.
       
    connectWifi();                                        // Connect to Wi-Fi network.
    // mqttClient.setServer( server, 1883 );              // Set the MQTT broker details, nonsecure port. Uncomment for nonsecure connection.
    mqttClient.setServer( server, 8883 );                 // Set the MQTT broker details, secure port. Uncomment for secure connection.
    mqttClient.setCallback( mqttSubscriptionCallback );   // Set the MQTT message handler function.
    myservo.attach(14);                                   // Attach the servo on GIO2 to the servo object. 
    myservo.write(90);                                    // Start in the middle.
}

5) 每次执行主循环时,检查来自 MQTT 订阅的数据是否可用于处理。然后设置伺服位置以匹配数据。确保无线和 MQTT 客户端处于活动状态并保持与客户端服务器的连接。

void loop() {
    
    if (WiFi.status() != WL_CONNECTED) {
        connectWifi();
    }
    
    if (!mqttClient.connected())
    {
       
       mqttConnect(); // Connect if MQTT client is not connected.
        
         if(mqttSubscribe( readChannelID,1,0 )==1 ){
                Serial.println( " Subscribed " );
            }
    }
    
    mqttClient.loop(); // Call the loop to maintain connection to the server.                         

    if ((servo_pos>175)||(servo_pos<0)){
    servo_pos=0;
    }
   
    if (changeFlag){
      
        changeFlag=0;
        myservo.write(servo_pos);
        dataToPublish[ANGLE_FIELD]=servo_pos;
        delay(1100);                       // Wait for ThingSpeak to publish.
        Serial.println( "Servo value " + String( servo_pos ) );
        mqttPublish( writeChannelID, dataToPublish, fieldsToPublish );
    }
    
    delay(1);
}

6) 使用mqttSubscriptionCallback函数处理传入的MQTT消息。如果主循环执行处理步骤而不是回调,程序运行会更顺畅。在此函数中,使用标志来引起主循环中的更改。

/**
 * Process messages received from subscribed channel via MQTT broker.
 *   topic - Subscription topic for message.
 *   payload - Field to subscribe to. Value 0 means subscribe to all fields.
 *   mesLength - Message length.
 */

void mqttSubscriptionCallback( char* topic, byte* payload, unsigned int mesLength ) {
    
    char p[mesLength + 1];
    memcpy( p, payload, mesLength );
    p[mesLength] = NULL;
    Serial.print( "Answer: " );
    Serial.println( String(p) );
    servo_pos=atoi( p );
    changeFlag=1;
}

7) 使用MQTTConnect函数建立和维护与MQTT的连接。

void mqttConnect()
{
    // Loop until connected.
    while ( !mqttClient.connected() )
    {
      Serial.println(String( mqttUserName)+ " , " + mqttPass + " , " + clientID);
   
        // Connect to the MQTT broker.
        Serial.print( "Attempting MQTT connection..." );
        if ( mqttClient.connect( clientID, mqttUserName, mqttPass ) )
        {
            Serial.println( "Connected with Client ID:  " + String( clientID ) + " User "+ String( mqttUserName ) + " Pwd "+String( mqttPass ) );
           
        } else
        {
            Serial.print( "failed, rc = " );
            // See https://pubsubclient.knolleary.net/api.html#state for the failure code explanation.
            Serial.print( mqttClient.state() );
            Serial.println( " Will try again in 5 seconds" );
            delay( 5000 );
        }
    }
}

8) 使用 mqttSubscribe 接收来自 LED 控制字段的更新。在此示例中,您订阅了一个字段,但您也可以使用此函数来订阅整个通道源。使用 field = 0 调用该函数来订阅整个源。

/**
 * Subscribe to fields of a channel.
 *   subChannelID - Channel to subscribe to.
 *   field - Field to subscribe to. Value 0 means subscribe to all fields.
 *   readKey - Read API key for the subscribe channel.
 *   unSub - Set to 1 for unsubscribe.
 */
 
int mqttSubscribe( long subChannelID, int field, int unsubSub ){
    String myTopic;
    
    // There is no field zero, so if field 0 is sent to subscribe to, then subscribe to the whole channel feed.
    if (field==0){
        myTopic="channels/"+String( subChannelID )+"/subscribe";
    }
    else{
        myTopic="channels/"+String( subChannelID )+"/subscribe/fields/field"+String( field );
    }
    
    Serial.println( "Subscribing to " +myTopic );
    Serial.println( "State= " + String( mqttClient.state() ) );

    if ( unsubSub==1 ){
        return mqttClient.unsubscribe(myTopic.c_str());
    }
    return mqttClient.subscribe( myTopic.c_str() ,0 );
}

9) 代码中未使用mqttUnsubscribe函数,但您可以使用它来结束订阅。

/**
 * Unsubscribe channel
 *   subChannelID - Channel to unsubscribe from.
 *   field - Field to unsubscribe subscribe from. The value 0 means subscribe to all fields.
 *   readKey - Read API key for the subscribe channel.
 */

int mqttUnSubscribe(long subChannelID,int field,char* readKey){
    String myTopic;
    
    if (field==0){
         myTopic="channels/"+String( subChannelID )+"/subscribe";
    }
    else{
        myTopic="channels/"+String( subChannelID )+"/subscribe/fields/field"+String( field );
    }
    return mqttClient.unsubscribe( myTopic.c_str() );   
}

10) 使用 mqttPublish 函数将您的角度和 Wi-Fi RSSI 数据发送到 ThingSpeak通道。

/**
 * Publish to a channel
 *   pubChannelID - Channel to publish to.
 *   pubWriteAPIKey - Write API key for the channel to publish to.
 *   dataArray - Binary array indicating which fields to publish to, starting with field 1.
 *   fieldArray - Array of values to publish, starting with field 1.
 */

void mqttPublish(long pubChannelID, float dataArray[], int fieldArray[]) {
    int index=0;
    String dataString="";
    
    updateRSSIValue();  // Make sure the stored value is updated.
    
    // 
    while (index<8){
        
        // Look at the field array to build the posting string to send to ThingSpeak.
        if (fieldArray[ index ]>0){
          
            dataString+="&field" + String( index+1 ) + "="+String( dataArray [ index ] );
        }
        index++;
    }
    
    Serial.println( dataString );
    
    // Create a topic string and publish data to ThingSpeak channel feed.
     String topicString ="channels/" + String( pubChannelID ) + "/publish";
    mqttClient.publish( topicString.c_str(), dataString.c_str() );
    Serial.println( "Published to channel " + String( pubChannelID ) );
}

11) 使用 connectWiFi 功能将您的设备连接到无线网络。

int connectWifi()
{
    while ( WiFi.status() != WL_CONNECTED ) {
        WiFi.begin( ssid, pass );
        delay( 8500 );
        Serial.println( "Connecting to Wi-Fi" ); 
    }
    Serial.println( "Connected" );
    client.setFingerprint(thingspeak_server_fingerprint);  // Comment this line if using nonsecure connection.
}

12) 使用updateRSSIValue 函数读取您当前连接的网络的信号强度。

void updateRSSIValue(){

   long rssi = WiFi.RSSI();  
   Serial.print( "RSSI:" );
   Serial.println(rssi);
   dataToPublish[ DATA_FIELD ]=float( rssi );

}

另请参阅

|

相关主题