記事ディレクトリ
1. データを onenet クラウド プラットフォームにアップロードするための STM32 と EPS01 の共同デバッグ
最終的な目標は、STM32 が ESP01 を Wi-Fi モジュールとして使用して、温度と湿度のデータを測定し、LED ライトをセットアップし、HTTP または MQTT プロトコルを介して ONENET クラウド プラットフォームに接続し、クラウド プラットフォーム上で温度と湿度のデータを表示し、LED をリモート制御することです。オンとオフ。
ここで作成する必要があるライブラリは主に次の 3 つです。
Onenet
MQTTKit
ESP8266
ここでは、KEIL のコード記述が好きではないので、強力なコード補完と機能を備えた CH32V307 をベースにした Qinheng の MRS に置き換えました。検索機能。このセクションでは、ONENET プラットフォームによって提供されるルーチン、ESP8266-MQTT-温度と湿度のみを分析し、その動作ロジックを分析します。さらに、このルーチンのソース コードは ONENET で検索してダウンロードできます。私のコードは ONENET-ベアメタル-基本ルーチン-ESP8266-MQTT-TYPE5-温度と湿度 です。
1. メインプログラムから始めて、最初にヘッダーファイルを確認します。
明らかに、これにはセンサー層、ネットワークデバイス層、プロトコル層、C ライブラリ、および STM32 ライブラリが含まれます。
2. メイン関数内:
ここではunsigned short型の変数とchar型のポインタ変数が定義されていますが、とりあえず用途が分からないのでまずは振り返ってみましょう。
(後でわかるように、timeCount は数秒ごとにデータを送信するために使用されるカウント単位であり、dataPtr は処理するデータを受信するために使用されます)
3. ハードウェア デバイスを初期化します。
多くを語る必要はなく、とてもシンプルです。シリアル ポート 2 の初期化が ESP8266 との通信を担当することに注意してください。
4. 次に、ESP8266 を初期化します。
ESP8266_Init(); //初始化ESP8266
飛び込む:
void ESP8266_Init(void)
{
GPIO_InitTypeDef GPIO_Initure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//ESP8266复位引脚
GPIO_Initure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Initure.GPIO_Pin = GPIO_Pin_14; //GPIOC14-复位
GPIO_Initure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_Initure);
GPIO_WriteBit(GPIOC, GPIO_Pin_14, Bit_RESET);
DelayXms(250);
GPIO_WriteBit(GPIOC, GPIO_Pin_14, Bit_SET);
DelayXms(500);
ESP8266_Clear();
UsartPrintf(USART_DEBUG, "1. AT\r\n");
while(ESP8266_SendCmd("AT\r\n", "OK"))
DelayXms(500);
UsartPrintf(USART_DEBUG, "2. CWMODE\r\n");
while(ESP8266_SendCmd("AT+CWMODE=1\r\n", "OK"))
DelayXms(500);
UsartPrintf(USART_DEBUG, "3. AT+CWDHCP\r\n");
while(ESP8266_SendCmd("AT+CWDHCP=1,1\r\n", "OK"))
DelayXms(500);
UsartPrintf(USART_DEBUG, "4. CWJAP\r\n");
while(ESP8266_SendCmd(ESP8266_WIFI_INFO, "GOT IP"))
DelayXms(500);
UsartPrintf(USART_DEBUG, "5. CIPSTART\r\n");
while(ESP8266_SendCmd(ESP8266_ONENET_INFO, "CONNECT"))
DelayXms(500);
UsartPrintf(USART_DEBUG, "6. ESP8266 Init OK\r\n");
}
ESP8266 初期化の主なプロセスは次のとおりです。
① ESP8266 リセット ピンを構成する: GPIO_InitTypeDef 構造体を使用して ESP8266 リセット ピンを構成し、出力モードに設定します。初期レベルは Low です。次に、ピンを High に引き上げ、しばらく待ってから Low に引き下げて、ESP8266 モジュールのリセット動作を完了します。
最初にリセットしてから通常どおりに動作する必要があるのはなぜですか?
ESP8266 モジュールを使用する場合、ネットワーク環境の不安定性や ESP8266 モジュールの動作原理などの要因により、ESP8266 モジュールが正しく動作しない可能性があります。この場合、ESP8266 モジュールをリセットして通常の動作状態に戻す必要があります。
②Call ESP8266_Clear(); ESP8266 キャッシュを空にする: ESP8266_Clear() 関数を呼び出して、ESP8266 モジュールのキャッシュをクリアします。
③AT コマンドの送信: AT : ESP8266_SendCmd() 関数を使用して AT コマンドを送信し、ESP8266 モジュールが「OK」を応答するまで待ち、ESP8266 モジュールが正常に動作していることを確認します。
④ ESP8266 モジュールの動作モードを設定します。ESP8266_SendCmd() 関数を使用して AT+CWMODE コマンドを送信し、ESP8266 モジュールの動作モードを「ステーション モード」に設定して WiFi ネットワークに接続します。AT+CWMODE=1 は AT コマンドで、ESP8266 モジュールの動作モードを設定するために使用されます。
ESP8266 モジュールは、「ステーション モード」、「AP モード」、「AP+ステーション モード」という 3 つの動作モードをサポートします。このうち「ステーションモード」は、ESP8266モジュールをクライアントとしてWiFiネットワークに接続することで、WiFiネットワークを介してインターネットに接続する機能を実現できます。「AP モード」は、ESP8266 モジュールを WiFi ホットスポットとして使用し、他のデバイスはそれが提供する WiFi ネットワークに接続できます。最後に、「AP+Station モード」は、上記 2 つの機能を同時にサポートします。
したがって、MQTT プロトコルを使用する場合、WiFi ネットワークに接続し、WiFi ネットワーク経由で OneNET サーバーに接続するには、ESP8266 モジュールの動作モードを「ステーション モード」に設定する必要があります。
⑤ESP8266 モジュールの DHCP を設定します。ESP8266_SendCmd() 関数を使用して AT+CWDHCP=1,1 コマンドを送信し、ESP8266 モジュールの DHCP 機能を有効にし、IP アドレスを自動的に取得します。
⑥WiFi ネットワークに接続します。ESP8266_SendCmd() 関数を使用して AT+CWJAP コマンドを送信し、WiFi ネットワークに接続し、ESP8266 モジュールが IP アドレスを取得するのを待ちます。
⑦OneNET サーバーに接続: ESP8266_SendCmd() 関数を使用して AT+CIPSTART コマンドを送信し、OneNET サーバーに接続し、ESP8266 モジュールが TCP 接続を確立するのを待ちます。
⑧初期化完了:ESP8266モジュールの初期化が完了したことを示すデバッグ情報を出力します。
ESP8266 を初期化する関数の場合、このより重要な関数が内部的に呼び出されます。
_Bool ESP8266_SendCmd(char *cmd, char *res)
まず、ESP8266で定義されている各種マクロを機能解析の前提条件とします。(これらの定義は 1 つのファイルではなく、統合されているだけです)
//AT+CWJAP指令用于连接WiFi网络,后面的参数中,ONENET表示WiFi网络的名称,而IOT@Chinamobile123则是WiFi网络的密码。
#define ESP8266_WIFI_INFO "AT+CWJAP=\"ONENET\",\"IOT@Chinamobile123\"\r\n"
//AT+CIPSTART指令用于连接TCP服务器,后面的参数中,TCP表示使用TCP协议连接服务器,183.230.40.39表示OneNET服务器的IP地址,而6002则是OneNET服务器的端口号。
#define ESP8266_ONENET_INFO "AT+CIPSTART=\"TCP\",\"183.230.40.39\",6002\r\n"
unsigned char esp8266_buf[128];
//cnt是接收计数器
unsigned short esp8266_cnt = 0, esp8266_cntPre = 0;
#define REV_OK 0 //接收完成标志
#define REV_WAIT 1 //接收未完成标志
ESP8266_SendCmd() はコマンド関数を送信します。具体的なコードを参照してください。
_Bool ESP8266_SendCmd(char *cmd, char *res)
{
unsigned char timeOut = 200;
Usart_SendString(USART2, (unsigned char *)cmd, strlen((const char *)cmd));
while(timeOut--)
{
if(ESP8266_WaitRecive() == REV_OK) //如果收到数据
{
if(strstr((const char *)esp8266_buf, res) != NULL)//如果检索到关键词
{
ESP8266_Clear(); //清空缓存
return 0;
}
}
DelayXms(10);
}
return 1;
}
機能は次のとおりです。
AT コマンドを ESP8266 モジュールに送信します。Usart_SendString 関数を使用して、AT コマンドを ESP8266 モジュールに送信します。
ESP8266 モジュールが応答を返すのを待ちます。ESP8266_WaitRecive 関数を使用して、ESP8266 モジュールが応答を返すのを待ち、応答を esp8266_buf バッファに保存します。
Bool ESP8266_WaitRecive(void)
{
if(esp8266_cnt == 0) //如果接收计数为0 则说明没有处于接收数据中,所以直接跳出,结束函数
return REV_WAIT;
if(esp8266_cnt == esp8266_cntPre) //如果上一次的值和这次相同,则说明接收完毕
{
esp8266_cnt = 0; //清0接收计数
return REV_OK; //返回接收完成标志
}
esp8266_cntPre = esp8266_cnt; //置为相同
return REV_WAIT; //返回接收未完成标志
}
ESP8266_WaitRecive()関数:
データ受信状態かどうかを判定します。現在の受信回数が0の場合はデータ受信状態ではないことを意味し、受信待ちフラグ(REV_WAIT)をそのまま返します。
受信完了の判定:今回の受信回数が前回と同じであれば、データを受信したことになるので、受信回数をクリアし、受信完了フラグ(REV_OK)を返します。
受信待ちフラグを返す:データをまだ受信していない場合は、受信待ちフラグを返します。
期待された応答が受信されたかどうかを判断します。strstr関数を使用して esp8266_buf バッファ内のキーワードを取得します。期待された応答が取得された場合は、キャッシュをクリアして 0 を返します。それ以外の場合は、応答を待ち続けます。各コマンドを送信するため、sendCMD 関数でも res=OK が定義されています。ESP8266 自体は AT コマンドを受信した後に OK を返します。 res=OK と一致する限り、復帰は成功したことを意味し、検索キーワードは OK です。
関数の実行結果を返す: 指定された時間内に期待した応答が受信されなかった場合は、1 を返します。コマンド送信機能です。
したがって、ESP8266 の初期化では、AT コマンドが送信されるたびに、この関数が渡されます。例: ESP8266_SendCmd(
“AT+CWDHCP=1,1\r\n”, “OK”);
ESP8266_SendCmd(ESP8266_WIFI_INFO, “GOT ”) IP”);
ESP8266_WIFI_INFO はマクロで定義されており、WIFI 固有の情報をいつでも簡単に変更できます。
現時点では、ESP8266 はすでに AT コマンドの要件に基づいて WIFI 経由で ONENET クラウド プラットフォームに接続しています。
5. ONENET にアクセスする
ESP8266 デバイス側を設定した後、ネットワーク プロトコルを設定する必要があります。まず、説明をわかりやすくするために、関連するマクロ定義を統合する必要があります。
//产品ID(PROID)是OneNET平台上创建的产品的唯一标识符,用于区分不同的产品;
#define PROID "77247"
//鉴权信息(AUTH_INFO)是用于验证设备身份的密钥,用于确保只有经过授权的设备可以连接到OneNET平台
#define AUTH_INFO "test"
//设备ID(DEVID)是OneNET平台上创建的设备的唯一标识符,用于区分不同的设备。
#define DEVID "5616839"
extern unsigned char esp8266_buf[128];
メイン プログラム main.c に移動し、下を見ていきます。
while(OneNet_DevLink()) //接入OneNET
DelayXms(500);
Beep_Set(BEEP_ON); //鸣叫提示接入成功
DelayXms(250);
Beep_Set(BEEP_OFF);
重要なのはこのOneNet_DevLink() 関数であり、この関数の分析に重点を置いています。
_Bool OneNet_DevLink(void)
{
MQTT_PACKET_STRUCTURE mqttPacket = {
NULL, 0, 0, 0}; //协议包
unsigned char *dataPtr;
_Bool status = 1;
UsartPrintf(USART_DEBUG, "OneNet_DevLink\r\n"
"PROID: %s, AUIF: %s, DEVID:%s\r\n"
, PROID, AUTH_INFO, DEVID);
if(MQTT_PacketConnect(PROID, AUTH_INFO, DEVID, 256, 0, MQTT_QOS_LEVEL0, NULL, NULL, 0, &mqttPacket) == 0)
{
ESP8266_SendData(mqttPacket._data, mqttPacket._len); //上传平台
dataPtr = ESP8266_GetIPD(250); //等待平台响应
if(dataPtr != NULL)
{
if(MQTT_UnPacketRecv(dataPtr) == MQTT_PKT_CONNACK)
{
switch(MQTT_UnPacketConnectAck(dataPtr))
{
case 0:UsartPrintf(USART_DEBUG, "Tips: 连接成功\r\n");status = 0;break;
case 1:UsartPrintf(USART_DEBUG, "WARN: 连接失败:协议错误\r\n");break;
case 2:UsartPrintf(USART_DEBUG, "WARN: 连接失败:非法的clientid\r\n");break;
case 3:UsartPrintf(USART_DEBUG, "WARN: 连接失败:服务器失败\r\n");break;
case 4:UsartPrintf(USART_DEBUG, "WARN: 连接失败:用户名或密码错误\r\n");break;
case 5:UsartPrintf(USART_DEBUG, "WARN: 连接失败:非法链接(比如token非法)\r\n");break;
default:UsartPrintf(USART_DEBUG, "ERR: 连接失败:未知错误\r\n");break;
}
}
}
MQTT_DeleteBuffer(&mqttPacket); //删包
}
else
UsartPrintf(USART_DEBUG, "WARN: MQTT_PacketConnect Failed\r\n");
return status;
}
複雑に見えますが、実際は非常に複雑です。文ごとに:
(1) プロトコルパケット構造体のメンバ変数を初期化する
MQTT_PACKET_STRUCTURE mqttPacket = {
NULL, 0, 0, 0}; //协议包
このコードは主に、MQTT_PACKET_STRUCTURE 構造体型の変数 mqttPacket を定義し、そのメンバー変数を初期化します。
MQTT_PACKET_STRUCTURE 構造体タイプは、次のメンバー変数を含む MQTT プロトコル パケットを格納するために使用されます。
typedef struct Buffer
{
uint8 *_data; //协议数据
uint32 _len; //写入的数据长度
uint32 _size; //缓存总大小
uint8 _memFlag; //内存使用的方案:0-未分配 1-使用的动态分配 2-使用的固定内存
} MQTT_PACKET_STRUCTURE;
このコードでは、変数 mqttPacket は空のプロトコル パケットとして初期化され、data メンバー変数にはプロトコル パケット データがないことを示す NULL の値が割り当てられ、len メンバー変数にはプロトコル パケット データがないことを示す 0 の値が割り当てられます。パケット長は 0、サイズとメモリフラグは両方とも値 0 が割り当てられ、キャッシュ サイズが 0 でメモリが割り当てられていないことを示します。
(2) MQTT_PacketConnect()関数を入力します
次の 2 行は識別子を定義していますが、今のところ有用性はわかりません。
if(MQTT_PacketConnect(PROID, AUTH_INFO, DEVID, 256, 0, MQTT_QOS_LEVEL0, NULL, NULL, 0, &mqttPacket) == 0)
{
ESP8266_SendData(mqttPacket._data, mqttPacket._len); //上传平台
dataPtr = ESP8266_GetIPD(250); //等待平台响应
if(dataPtr != NULL)
{
if(MQTT_UnPacketRecv(dataPtr) == MQTT_PKT_CONNACK)
{
switch(MQTT_UnPacketConnectAck(dataPtr))
{
case 0:UsartPrintf(USART_DEBUG, "Tips: 连接成功\r\n");status = 0;break;
case 1:UsartPrintf(USART_DEBUG, "WARN: 连接失败:协议错误\r\n");break;
case 2:UsartPrintf(USART_DEBUG, "WARN: 连接失败:非法的clientid\r\n");break;
case 3:UsartPrintf(USART_DEBUG, "WARN: 连接失败:服务器失败\r\n");break;
case 4:UsartPrintf(USART_DEBUG, "WARN: 连接失败:用户名或密码错误\r\n");break;
case 5:UsartPrintf(USART_DEBUG, "WARN: 连接失败:非法链接(比如token非法)\r\n");break;
default:UsartPrintf(USART_DEBUG, "ERR: 连接失败:未知错误\r\n");break;
}
}
}
MQTT_DeleteBuffer(&mqttPacket); //删包
}
else
UsartPrintf(USART_DEBUG, "WARN: MQTT_PacketConnect Failed\r\n");
確立されている場合は、次のように実行します。
MQTT_PacketConnect(PROID, AUTH_INFO, DEVID, 256, 0, MQTT_QOS_LEVEL0, NULL, NULL, 0, &mqttPacket) == 0
MQTT_PacketConnect 関数を入力します。
uint8 MQTT_PacketConnect(const int8 *user, const int8 *password, const int8 *devid,
uint16 cTime, uint1 clean_session, uint1 qos,
const int8 *will_topic, const int8 *will_msg, int32 will_retain,
MQTT_PACKET_STRUCTURE *mqttPacket)
{
uint8 flags = 0;
uint8 will_topic_len = 0;
uint16 total_len = 15;
int16 len = 0, devid_len = strlen(devid);
if(!devid)
return 1;
total_len += devid_len + 2;
//断线后,是否清理离线消息:1-清理 0-不清理--------------------------------------------
if(clean_session)
{
flags |= MQTT_CONNECT_CLEAN_SESSION;
}
//异常掉线情况下,服务器发布的topic------------------------------------------------------
if(will_topic)
{
flags |= MQTT_CONNECT_WILL_FLAG;
will_topic_len = strlen(will_topic);
total_len += 4 + will_topic_len + strlen(will_msg);
}
//qos级别--主要用于PUBLISH(发布态)消息的,保证消息传递的次数-----------------------------
switch((unsigned char)qos)
{
case MQTT_QOS_LEVEL0:
flags |= MQTT_CONNECT_WILL_QOS0; //最多一次
break;
case MQTT_QOS_LEVEL1:
flags |= (MQTT_CONNECT_WILL_FLAG | MQTT_CONNECT_WILL_QOS1); //最少一次
break;
case MQTT_QOS_LEVEL2:
flags |= (MQTT_CONNECT_WILL_FLAG | MQTT_CONNECT_WILL_QOS2); //只有一次
break;
default:
return 2;
}
//主要用于PUBLISH(发布态)的消息,表示服务器要保留这次推送的信息,如果有新的订阅者出现,就把这消息推送给它。如果不设那么推送至当前订阅的就释放了
if(will_retain)
{
flags |= (MQTT_CONNECT_WILL_FLAG | MQTT_CONNECT_WILL_RETAIN);
}
//账号为空 密码为空---------------------------------------------------------------------
if(!user || !password)
{
return 3;
}
flags |= MQTT_CONNECT_USER_NAME | MQTT_CONNECT_PASSORD;
total_len += strlen(user) + strlen(password) + 4;
//分配内存-----------------------------------------------------------------------------
MQTT_NewBuffer(mqttPacket, total_len);
if(mqttPacket->_data == NULL)
return 4;
memset(mqttPacket->_data, 0, total_len);
/*************************************固定头部***********************************************/
//固定头部----------------------连接请求类型---------------------------------------------
mqttPacket->_data[mqttPacket->_len++] = MQTT_PKT_CONNECT << 4;
//固定头部----------------------剩余长度值-----------------------------------------------
len = MQTT_DumpLength(total_len - 5, mqttPacket->_data + mqttPacket->_len);
if(len < 0)
{
MQTT_DeleteBuffer(mqttPacket);
return 5;
}
else
mqttPacket->_len += len;
/*************************************可变头部***********************************************/
//可变头部----------------------协议名长度 和 协议名--------------------------------------
mqttPacket->_data[mqttPacket->_len++] = 0;
mqttPacket->_data[mqttPacket->_len++] = 4;
mqttPacket->_data[mqttPacket->_len++] = 'M';
mqttPacket->_data[mqttPacket->_len++] = 'Q';
mqttPacket->_data[mqttPacket->_len++] = 'T';
mqttPacket->_data[mqttPacket->_len++] = 'T';
//可变头部----------------------protocol level 4-----------------------------------------
mqttPacket->_data[mqttPacket->_len++] = 4;
//可变头部----------------------连接标志(该函数开头处理的数据)-----------------------------
mqttPacket->_data[mqttPacket->_len++] = flags;
//可变头部----------------------保持连接的时间(秒)----------------------------------------
mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(cTime);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(cTime);
/*************************************消息体************************************************/
//消息体----------------------------devid长度、devid-------------------------------------
mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(devid_len);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(devid_len);
strncat((int8 *)mqttPacket->_data + mqttPacket->_len, devid, devid_len);
mqttPacket->_len += devid_len;
//消息体----------------------------will_flag 和 will_msg---------------------------------
if(flags & MQTT_CONNECT_WILL_FLAG)
{
unsigned short mLen = 0;
if(!will_msg)
will_msg = "";
mLen = strlen(will_topic);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(mLen);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(mLen);
strncat((int8 *)mqttPacket->_data + mqttPacket->_len, will_topic, mLen);
mqttPacket->_len += mLen;
mLen = strlen(will_msg);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(mLen);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(mLen);
strncat((int8 *)mqttPacket->_data + mqttPacket->_len, will_msg, mLen);
mqttPacket->_len += mLen;
}
//消息体----------------------------use---------------------------------------------------
if(flags & MQTT_CONNECT_USER_NAME)
{
unsigned short user_len = strlen(user);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(user_len);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(user_len);
strncat((int8 *)mqttPacket->_data + mqttPacket->_len, user, user_len);
mqttPacket->_len += user_len;
}
//消息体----------------------------password----------------------------------------------
if(flags & MQTT_CONNECT_PASSORD)
{
unsigned short psw_len = strlen(password);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(psw_len);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(psw_len);
strncat((int8 *)mqttPacket->_data + mqttPacket->_len, password, psw_len);
mqttPacket->_len += psw_len;
}
return 0;
}
この関数はさらに複雑で、名前からもわかるように、クライアントからサーバーに CONNECT メッセージを送信する関数です。CONNECT メッセージについては、最初のセクションに説明を書きます。
まず、仮パラメータと実パラメータを分析します。
形式パラメータ:const int8 *user, const int8 *password, const int8 *devid,
uint16 cTime, uint1 clean_session, uint1 qos,
const int8 *will_topic, const int8 *will_msg, int32 will_retain,
MQTT_PACKET_STRUCTURE *mqttPacket
PUBLIC: PROID、AUTH_INFO、DEVID、256、0、MQTT_QOS_LEVEL0、NULL、NULL、0、&mqttPacket
関数パラメータには次のものが含まれます:
user: 接続のユーザー名、タイプは int8 ポインター (つまり、文字ポインター)、製品 ID (PROID)、password: 接続のパスワード、タイプは int8
ポインター、および認証キー;
devid: デバイス ID、タイプ Int8 ポインター、デバイス ID;
cTime: 接続タイムアウト時間、タイプは uint16 (符号なし 16 ビット整数)、256ms に設定;
clean_session: セッションをクリアするかどうか、タイプは uint1 (符号なし 1 ビット整数)、0 は false、つまり、クライアントは未受信メッセージを保存することを要求します。qos:
サービス品質レベル、タイプは uint1、ここでは 0、最大 1 回 (最大 1 回) 、メッセージ発行者はメッセージを 1 回だけ送信し、メッセージが受信側で受信できるという保証はありません; will_topic
: will メッセージのトピック、型は int8 ポインター、ここは空です;
will_msg: コンテンツwill メッセージのタイプは int8 ポインタで空です;
will_retain: will メッセージが保持されるかどうか、タイプは int32 (符号付き 32 ビット整数);
mqttPacket: MQTT プロトコル パケット構造体、タイプは MQTT_PACKET_STRUCTURE ポインタです。
この関数の機能は、MQTT プロトコルの接続パケットを構築し、mqttPacket が指す構造体に接続パケットのデータを格納することです。関数が正常に実行された場合は 0 を返し、それ以外の場合はエラー コードを返します。
簡単に言うと、この関数は次のようになります。
まず、フラグ、will_topic_len、total_len などを含むいくつかの変数を定義します。次に、パラメータに従って接続パケットの各フィールドを構築し、接続パケットの合計長 total_len を計算します。
フラグ ビットと QOS レベルに応じて、接続パケットのさまざまなフィールドを設定します。たとえば、切断後にオフライン メッセージをクリーンアップするかどうか、ウィル メッセージの QOS レベル、ウィル メッセージを保持するかどうかなどです。
ユーザー名またはパスワードが空の場合、エラー コードが返されます。
メモリを動的に割り当てる:
接続パケットの固定ヘッダー、可変ヘッダー、メッセージ本文を構築し、
最後に接続パケットのデータを mqttPacket が指す構造体に格納します。関数が正常に実行された場合は 0 を返し、それ以外の場合はエラー コードを返します。現時点ではこの機能を理解できず、使用することしかできず、書き出すこともできず、説明することもできません。一言で言えば、私は張吉瑞を尊敬したいと思います!
(3) MQTT_PacketConnect 関数が成功したら、ESP8266_SendData(mqttPacket._data, mqttPacket._len) を呼び出します。
まず、ESP8266_SendData() 関数に移動して、これら 2 つのパラメーターが何であるかを確認します。
void ESP8266_SendData(unsigned char *data, unsigned short len)
{
char cmdBuf[32];
ESP8266_Clear(); //清空接收缓存
sprintf(cmdBuf, "AT+CIPSEND=%d\r\n", len); //发送命令
if(!ESP8266_SendCmd(cmdBuf, ">")) //收到‘>’时可以发送数据
{
Usart_SendString(USART2, data, len); //发送设备连接请求数据
}
}
これは関数の実装であり、その役割は ESP8266 モジュールを通じてリモート サーバーにデータを送信することです。以下に実装プロセスを簡単に説明します。
まず、送信コマンドを格納する文字配列 cmdBuf を定義します。
ESP8266_Clear 関数を呼び出して、後続のデータ交換のために ESP8266 モジュールの受信バッファをクリアします。
sprintf 関数を使用して、AT+CIPSEND=len\r\n の形式で送信コマンドをフォーマットします。ここで、len は送信するデータの長さを表します。
ESP8266_SendCmd 関数を呼び出して ESP8266 モジュールにコマンドを送信し、モジュールがデータを送信できることを示す「>」記号を返すまで待ちます。「>」記号を受け取った場合は、Usart_SendString 関数を呼び出してデータを送信します。この関数は、USART シリアル ポート経由でデータを送信します。
したがって、この関数は実際にmqttPacket._data に格納されている MQTT 接続パケット データをリモート サーバーに送信します。
(4) プラットフォームが応答するまで待ちます
dataPtr = ESP8266_GetIPD(250); //等待平台响应
if(dataPtr != NULL)
{
if(MQTT_UnPacketRecv(dataPtr) == MQTT_PKT_CONNACK)
{
switch(MQTT_UnPacketConnectAck(dataPtr))
{
case 0:UsartPrintf(USART_DEBUG, "Tips: 连接成功\r\n");status = 0;break;
case 1:UsartPrintf(USART_DEBUG, "WARN: 连接失败:协议错误\r\n");break;
case 2:UsartPrintf(USART_DEBUG, "WARN: 连接失败:非法的clientid\r\n");break;
case 3:UsartPrintf(USART_DEBUG, "WARN: 连接失败:服务器失败\r\n");break;
case 4:UsartPrintf(USART_DEBUG, "WARN: 连接失败:用户名或密码错误\r\n");break;
case 5:UsartPrintf(USART_DEBUG, "WARN: 连接失败:非法链接(比如token非法)\r\n");break;
default:UsartPrintf(USART_DEBUG, "ERR: 连接失败:未知错误\r\n");break;
}
}
}
このコードの機能は、ESP8266 モジュールがプラットフォーム応答を返すのを待ち、応答に従って MQTT 接続応答パケットの内容を分析することです。具体的なプロセスは次のとおりです。
ESP8266_GetIPD 関数を呼び出して、ESP8266 モジュールがプラットフォームの応答を返すのを待ち、250 ミリ秒待ちます。指定された時間内に応答が受信された場合は、応答データのポインター dataPtr が返され、それ以外の場合は NULL が返されます。
dataPtr が NULL でない場合は、MQTT_UnPacketRecv 関数を呼び出して応答データをアンパックし、応答データの型が MQTT_PKT_CONNACK であるかどうかを判断し、YES であれば MQTT 接続応答パケットを受信したことを意味します。
次に、MQTT_UnPacketConnectAck 関数を呼び出して MQTT 接続応答パケットの内容を解析し、戻り値に応じて接続が成功したかどうかを判断します。戻り値が 0 の場合、接続が成功したことを意味し、ステータス変数が 0 に設定されます。戻り値がその他の値の場合、接続が失敗したことを意味し、戻り値に従って対応するエラー プロンプトが出力されます。価値。
MQTT 接続応答パケットの形式と内容は MQTT 接続パケットとは異なるため、応答パケットを解析する場合は、MQTT_UnPacketConnect 関数を直接使用するのではなく、MQTT_UnPacketConnectAck 関数を使用する必要があることに注意してください。
また、ESP8266_GetIPD 関数によって返される dataPtr は ESP8266 モジュールの受信バッファを指しているため、dataPtr を使用した後は時間内にバッファを解放する必要があり、そうしないと受信バッファがオーバーフローする可能性があります。
(5) MQTT_DeleteBuffer(&mqttPacket)删包
MQTT パケット バッファを削除してメモリ領域を解放します。最後に、送信に失敗すると、シリアル プリントの送信も失敗します。この時点で、OneNet_DevLink() 関数は終了です。その後、メインプログラムに戻ります。
6. while(1) ループに入る
(1) 温度と湿度のデータをMQTTパケットにカプセル化して送信
if(++timeCount >= 500) //发送间隔5s
{
SHT20_GetValue();
UsartPrintf(USART_DEBUG, "OneNet_SendData\r\n");
OneNet_SendData(); //发送数据
timeCount = 0;
ESP8266_Clear();
}
timeCount が 500 以上であるかどうかを確認し、500 以上である場合は、SHT20_GetValue 関数を呼び出して温度および湿度センサーの現在の値を取得し、それを MQTT パケットにカプセル化して、ESP8266 モジュールを通じて OneNet プラットフォームに送信します。同時に、次のカウントのために timeCount 変数を 0 にリセットします。次のデータ送信操作に影響を与えないように、ESP8266_Clear 関数を呼び出して ESP8266 モジュールの受信バッファをクリアします。OneNet_SendData() 関数
の具体的な例を次に示します。
void OneNet_SendData(void)
{
MQTT_PACKET_STRUCTURE mqttPacket = {
NULL, 0, 0, 0}; //协议包
char buf[128];
short body_len = 0, i = 0;
UsartPrintf(USART_DEBUG, "Tips: OneNet_SendData-MQTT\r\n");
memset(buf, 0, sizeof(buf));
body_len = OneNet_FillBuf(buf); //获取当前需要发送的数据流的总长度
if(body_len)
{
if(MQTT_PacketSaveData(DEVID, body_len, NULL, 5, &mqttPacket) == 0) //封包
{
for(; i < body_len; i++)
mqttPacket._data[mqttPacket._len++] = buf[i];
ESP8266_SendData(mqttPacket._data, mqttPacket._len); //上传数据到平台
UsartPrintf(USART_DEBUG, "Send %d Bytes\r\n", mqttPacket._len);
MQTT_DeleteBuffer(&mqttPacket); //删包
}
else
UsartPrintf(USART_DEBUG, "WARN: EDP_NewBuffer Failed\r\n");
}
}
このコードの機能は、温度および湿度センサーのデータを MQTT パケットにカプセル化し、ESP8266 モジュールを通じて OneNet プラットフォームにアップロードすることです。具体的なプロセスは次のとおりです。
MQTT パケットをカプセル化するために、タイプ MQTT_PACKET_STRUCTURE の構造体 mqttPacket を定義します。
送信する温度と湿度のデータを格納する buf 配列を定義します。同時に、memset 関数を呼び出して buf 配列をクリアし、初期化されていないデータを回避します。
OneNet_FillBuf 関数を呼び出して buf 配列を埋め、送信する必要がある現在の温度と湿度のデータ ストリームの全長を取得します。
body_len が 0 より大きい場合、送信するデータが存在することを意味し、MQTT データ パケットのカプセル化と送信操作を実行できます。MQTT_PacketSaveData 関数を呼び出して MQTT データ パケットをカプセル化し、カプセル化されたデータを mqttPacket 構造体に格納します。このうち、DEVID はデバイス ID を示し、5 は QoS レベルが 1 であることを示します。
温度と湿度のデータ ストリームをバイト単位で mqttPacket._data にコピーし、データ長を mqttPacket._len に加算してパケット長を記録します。
ESP8266_SendData 関数を呼び出して、ESP8266 モジュールを通じて mqttPacket._data のデータを OneNet プラットフォームにアップロードします。
アップロードされたデータの長さを出力します。
MQTT_DeleteBuffer 関数を呼び出して、mqttPacket 構造体に格納されているデータを削除し、メモリ空間を解放します。
body_len が 0 に等しい場合、送信するデータがなく、操作は実行されないことを意味します。
これがコアです: OneNet_FillBuf 関数
unsigned char OneNet_FillBuf(char *buf)
{
char text[32];
memset(text, 0, sizeof(text));
strcpy(buf, ",;");
memset(text, 0, sizeof(text));
sprintf(text, "Tempreture,%f;", sht20_info.tempreture);
strcat(buf, text);
memset(text, 0, sizeof(text));
sprintf(text, "Humidity,%f;", sht20_info.humidity);
strcat(buf, text);
return strlen(buf);
}
このコードの機能は、OneNet プラットフォームにアップロードするために、温度および湿度センサーのデータを文字列ストリームにカプセル化することです。具体的なプロセスは次のとおりです。
各データ ポイントの名前と値を保存するためのテキスト配列を定義します。
memset 関数を呼び出してテキスト配列をクリアし、初期化されていないデータを回避します。
「,;」文字列を buf 配列に追加して、データ ストリームの開始を示します。
関数 sprintf を呼び出して、温度データの名前と値をテキスト配列に保存します。
関数 strcat を呼び出して、テキスト配列に格納されている温度データを buf 配列に追加します。
memset 関数を呼び出してテキスト配列をクリアし、初期化されていないデータを回避します。
手順 4 ~ 6 を繰り返して、湿度データの名前と値を text 配列に保存し、buf 配列に追加します。
buf 配列の長さを返します。これは、アップロードされるデータ ストリームの合計長です。
つまり、これまでの関数とコードはすべて ESP8266 と ONENET、MQTT の初期化と接続の作成に関連しており、変更する必要のない部分に属しており、実際に変更する必要があるのはこの関数です。sht20_info.tempreture と sht20_info.humidity は温度と湿度のデータです. したがって、温度と湿度、距離、光の強さ、空気の質の測定のいずれであっても、この関数で変更することができます. 原理も非常に簡単です。 sprintf 関数を呼び出して温度を変換します。データの名前と値はテキスト配列に格納されます。strcat 関数を呼び出して、テキスト配列に格納されている温度データを buf 配列に追加し、memset 関数を呼び出してテキスト配列をクリアして、初期化されていないデータを回避します。このステップは、転送される測定量の数だけ繰り返されます。
(2) 受信データの取得と処理
dataPtr = ESP8266_GetIPD(0);
if(dataPtr != NULL)
OneNet_RevPro(dataPtr);
DelayXms(10);
このコードの機能は、ESP8266 モジュールが受信したデータを取得し、データがある場合は OneNet_RevPro 関数を呼び出して処理および分析することです。具体的なプロセスは次のとおりです。
ESP8266_GetIPD 関数を呼び出して、ESP8266 モジュールが受信したデータを取得します。このうち、0は受信ユニットのIDを表し、受信ユニットが複数ある場合は異なるIDで区別できます。
dataPtr が NULL でない場合は、処理および分析する必要があるデータがあり、データを処理するために OneNet_RevPro 関数が呼び出されることを意味します。特定の処理プロセスは、特定の要件とアプリケーション シナリオによって異なります。
DelayXms 関数を呼び出して 10 ミリ秒の遅延を実現し、プログラムが CPU リソースを過剰に占有するのを防ぎます。
この時点で、プロセス全体が終了します。STM32 が ESP8266 と ONENET を介して温度と湿度をアップロードするルーチンは非常に複雑であり、どちらも実際のアプリケーションで理解するのが難しいことがわかります。このブログを書いたのは、MQTT と ONENET を始めるには何をすればよいのかわからず、ソース コードから始める必要があったからです。次のセクションでは、ルーチン コードを変更し、独自のクラウド プラットフォーム アプリケーションを実装する様子を記録します。