پرش به مطلب اصلی

آموزش اتصال به سرور MQTT

Clinet id

یکی از بهترین راه های اتصال شما به سرور های ایرانجین از طریق MQTT می باشد.

قبل از مطالعه این مطلب نیاز است شما MQTT را مطالعه کرده باشید و ابزارهای خود را مجهز به این پروتکل نموده باشید.

درصورتی که در هر قسمت نیاز به کمک داشتید از طریق پنل کاربری با بخش پشتیبانی در تماس باشید.

ابتدا نیاز است مفاهیم اولیه را بدانید:

ProjectCode : این در واقع کد هر پروژه است که کاربر در نرم افزار موبایل به محض ایجاد یک مکان به آن اختصاص می یابد و دارای 16 کاراکتر است.

DeviceSerial : سریال ابزار شما می باشد که باید آن را در پنل کاربری خود به عنوان تولید کننده یا وارد کننده ثبت نمایید و دارای ۱۴ کاراکتر است.

Bucket : زمان ذخیره و ماندگاری داده های هر ابزار در سرور می باشد که فعلا تنها 10 روز امکان پذیر است و به زودی قابلیت 1 ماهه و 1 ساله نیز اضافه می شود و دارای 2 کاراکتر است.

UserCode : مانند ProjectCode است که برای هر کاربر که ثبت نام می نماید یک کد منحصر به فرد تولید می شود و دارای 14 کاراکتر است.

ClientId : شما به واسطه این کلاینت آی دی به سیستم شناسانده می شوید و نحوه ساخت آن به شرح زیر است :

توجه : از آنجایی که بعضی از تنظیمات در کلاینت آی دی ها مربوط به سرور می باشد، حتما از کلاینت آیدی که در بخش "آموزش اتصال به سرور MQTT" برای ابزار شما ارسال می شود استفاده نمایید و به هیچ عنوان به صورت دستی آن را نسازید.


Dvc-ProjectCode-DeviceSerial-Bucket

Dvc : همیشه برای ابزار ثابت است.

Bucket : فعلا تنها عدد 10 قابل قبول می باشد.

به طور مثال :


Dvc-123456789ASDFGHJ–AIRN123456789010

در بالا کلمه Dvc که همیشه برای ابزارها موجود است و کلمات قرمز رنگ همان کد پروژه هستند. همچنین رنگ آبی سریال ابزار است و رنگ سبز کد باکت است که ۱۰ صرفا مورد قبول است و در آینده نزدیک این امکان می باشد در هنگام تعریف یک مکان جدید توسط کاربر نهایی مدت زمان خود را تغییر دهد.

کد نمونه اتصال به سرور MQTT

می توانید توسط چت جی پی تی، کد زیر را به راحتی به هر زبان دیگر از برنامه نویسی تبدیل نمایید

اگر توضیحات ما در ادامه نیز کم می باشد، می توانید علاوه بر درخواست کمک از بخش پشتیبانی، از چت جی پی تی بخواهید کد زیر را خط به خط برای شما توضیج دهد

در دنیای برنامه نویسی بیش از 1000 راه برای یک موضوع می باشد و نیازی نیست کدهای شما دقیقا مانند کدهای نمونه باشد. این نمونه ها صرفا برای درک بهتر و الگو بردای شما می باشد

در جاهایی که do somethings نوشته شده است یعنی ادامه کد بستگی به سیاست های شما دارد.


bool IOT__Trying = false;
bool _MqttRcv_IsWorking = false;

void Mqtt__Connect() {
if (_Mqtt_TryConnecting) return;
_Mqtt_TryConnecting = true;
try {
if (!_MqttObj.connected() || _MqttObj.state() < 0) {
if (SOFTWARE_DEBUGMODE) Debug_ConsolePrintln("Mqtt_Reconnecting...");
delay(1500);
if (_MqttObj.connect(_MqttClientId.c_str(), _MqttUser.c_str(), _MqttPass.c_str())) {
//........................................................... Reconnected
delay(10);
_MqttCon_IsConnected = true;
if (SOFTWARE_DEBUGMODE) Debug_ConsolePrintln("Mqtt_Connect Success Status :: " + String(_MqttObj.state()));
_MqttObj.subscribe((_ProjectCode + "/DeviceToDevice").c_str());
_MqttObj.subscribe((_ProjectCode + "/ServerToDevice").c_str());
_MqttObj.subscribe((_ProjectCode + "/DeviceSetting").c_str());
_MqttObj.subscribe((_ProjectCode + "/Share").c_str());
_MqttObj.subscribe("Time/Tehran");
Dvc__CloudSendAllStatus(); // send last status
}
//........................................................... Cant Reconnect
else {
_MqttCon_IsConnected = false;
if (SOFTWARE_DEBUGMODE) Debug_ConsolePrintln("Mqtt_Connect Fail Status :: " + String(_MqttObj.state()));
delay(119000); // 2 Min Delay If Retry Failed (To Prevent Hanging Manual Touch)
}
delay(500);
}
} catch (...) {
if (SOFTWARE_DEBUGMODE) Debug_ConsolePrintln("Mqtt_Connect Error");
}
_Mqtt_TryConnecting = false;
}



void Mqtt__OnRecieve(char *topic, uint8_t *payload, unsigned int length) {
try {
if (SOFTWARE_DEBUGMODE) Debug_ConsolePrintln("\r\nMqtt Rcv...\r\n length : " + String(length) + "\r\n Project/Topic: " + topic);
//----------------------------------------------------------------------------- Cloud MqttBroker
//.................................... Check Project
String projectTopic = CA_ToString(topic);
int p = projectTopic.indexOf('/');
if (projectTopic.substring(0, p) != _ProjectCode) return;
projectTopic = projectTopic.substring(p + 1);
DynamicJsonDocument doc(1024 + (2 * length));
doc.clear();
deserializeJson(doc, payload);
//.................................... Check Topic
if (projectTopic == "DeviceSetting") {
String opr = doc["operationName"].as<String>();
if (opr == "save_scenario" || opr == "delete_scenario") {
// do somethings
} else if (opr == "save_setting") {
if (doc["deviceSerial"].as<String>() == _SerialCloud) {
String cmd, d = doc["value"].as<String>();
if (d != "") {
deserializeJson(doc, d);
if (doc["request"]["commandName"] && doc["request"]["commandData"]) {
cmd = doc["request"]["commandName"].as<String>();
if (cmd == "saveScenarioOperation") {
JsonVariant inp = doc["scenarioOperation"].as<JsonVariant>();
// do somethings
} else if (cmd == "saveCommandOperation") {
JsonVariant inp = doc["commandOperation"].as<JsonVariant>();
// do somethings
} else {
d = doc["request"]["commandData"].as<String>();
// do somethings
}
}
}
}
}
} else if (projectTopic == "ServerToDevice") {
String deviceSerial = doc["deviceSerial"].as<String>();
String cmd = "";
try {
if (doc["operationName"]) cmd = doc["operationName"].as<String>();
} catch (...) {}
if (cmd != "") {
if (cmd == "disable_scenario" || cmd == "enable_scenario" || cmd == "run_scenario") {
// do somethings
} else if (cmd == "debugger") {
if (doc["value"]) {
String value = doc["value"].as<String>();
if (value == "reboot") Tools__Reboot();

else if (value == "clean") {
// do somethings
Tools__Reboot();
} else if (value == "reset_factory") {
// do somethings
Tools__Reboot();
} else if (value == "config") {
Tools__SetMode("config_panel", true);
} else if (value == "special")
;
}
}
} else if (doc["data"]) {
cmd = doc["data"].as<String>();
if (cmd != "") {
//do somethings
}
}
} else if (projectTopic == "DeviceToDevice") {
//.................................... Extarct Data
//do somethings
// do somethings (if need to react to other devices. for example pass data to analizer : Dvc__Analize(data); )
}
doc.clear();
} catch (...) {}
}

void Mqtt__Send(String topic, String data) {
try {
//................................. Reject If Mesh Not Connected
if (data == "") return;
topic = _ProjectCode + "/" + topic;
//----------------------------------------------------------------------------- Cloud SMS
if (!_MqttCon_IsConnected || !_MqttObj.connected() || _MqttObj.state() < 0) {
return;
}
//----------------------------------------------------------------------------- Cloud MqttBroker
else {
if (SOFTWARE_DEBUGMODE) Debug_ConsolePrintln("Mqtt Send... " + topic + " > " + data);
_MqttObj.publish(topic.c_str(), data.c_str());
}
} catch (...) {
if (SOFTWARE_DEBUGMODE) Debug_ConsolePrintln("MqttSend Error ");
}
}


void IOT__WiFiStart() {
_WiFi_ConnectWorking = true;
try {
if (SOFTWARE_DEBUGMODE) Debug_ConsolePrintln("Connecting to Wi-Fi...");
Tools__WifiPower(true);
const char *ssid = _ModemSSID.c_str();
const char *password = _ModemPass.c_str();
WiFi.begin(ssid, password);
} catch (...) {}
Dvc__SetPinout();
delay(100);
_WiFi_ConnectWorking = false;
}


void IOT__WiFiEvent(WiFiEvent_t event) {
if (SOFTWARE_DEBUGMODE) Debug_ConsolePrintln("[WiFi-event] event: " + String(event));
switch (event) {
case SYSTEM_EVENT_STA_GOT_IP:
if (SOFTWARE_DEBUGMODE) Debug_ConsolePrintln("WiFi connected, IP: " + WiFi.localIP().toString());
_WiFi_IsConnected = true;
_MqttCon_Steps = 2;
break;

case SYSTEM_EVENT_STA_DISCONNECTED:
_WiFi_IsConnected = false;
_MqttCon_IsConnected = false;
_MqttCon_Steps = 0;
if (SOFTWARE_DEBUGMODE) Debug_ConsolePrintln("WiFi lost connection");
break;
// default:
// break;
}
delay(100);
}


void IOT__Checker(void *param) {
while (true) {
//......................................
try {
if (!_MqttCon_IsConnected && !_WiFi_ConnectWorking && !_Mqtt_TryConnecting) {
if (_MqttCon_Steps == 0) {
IOT__WiFiStart();
if (SOFTWARE_DEBUGMODE) Debug_ConsolePrintln("############ _MqttCon_Steps 0 :: WiFiStart()");
_MqttCon_Steps = 1;
} else if (_MqttCon_Steps == 2) {
const char *mqtt_broker = _MqttBroker.c_str();
const int mqtt_port = 1883; //5000
_MqttObj.setCallback(Mqtt__OnRecieve);
_MqttObj.setServer(mqtt_broker, mqtt_port);
_MqttObj.setBufferSize(11000);
_MqttObj.setSocketTimeout(7);
_MqttObj.setKeepAlive(7);
_MqttCon_Steps = 3;
if (SOFTWARE_DEBUGMODE) Debug_ConsolePrintln("############ _MqttCon_Steps 1 :: MqttObj.setServer()");
//......................................
} else if (_MqttCon_Steps == 3) {
Mqtt__Connect();
delay(2000);
}
}
} catch (...) {}
delay(1000);
}
}




void IOT__Loop() {
while (true) {
try {
if (_MqttCon_IsConnected) {
_MqttObj.loop();
delay(30);
} else {
delay(100);
}
} catch (...) {
if (SOFTWARE_DEBUGMODE) Debug_ConsolePrintln("IOT__Loop Error ");
}
delay(TASK_LOOP_DELAY);
}
}


void IOT__Setup(void *param) {
if (SOFTWARE_DEBUGMODE) Debug_ConsolePrintln("\r\nIOT__Setup Start....");
//........................................... WiFi Settings
_WiFi_ConnectWorking = true;
if (WiFi.isConnected()) {
WiFi.disconnect(true);
delay(100);
}
_WiFi_IsConnected = false;
WiFi.onEvent(IOT__WiFiEvent);
//............................................ Mqtt Settings
_MqttClientId = _CloudClientId;
_MqttCon_IsConnected = false;
_WiFi_ConnectWorking = false;
_Mqtt_TryConnecting = false;
_MqttCon_Steps = 0;
IOT__Trying = false;

//........................................... Stay In Loop
xTaskCreatePinnedToCore(
IOT__Checker, // Function that should be called
"IOT__Checker", // Name of the task (for debugging)
TASK_STACKSIZE_LARGE, // Stack size (bytes)
NULL, // Parameter to pass
TASK_PRIORITY_HIGH, // Task priority (Max:1)
NULL, // Task handle
TASK_CORE_1); // Core (0/1)
//...........................................

if (SOFTWARE_DEBUGMODE) Debug_ConsolePrintln("IOT__Setup End\r\n");
//........................................... Stay In Loop
IOT__Loop();
}


Mqtt__Connect:

Func اصلی برای اتصال به سرور ما می باشد.

_Mqtt_TryConnecting : برای جلوگیری از فراخوانی دوباره این متد در زمان اجرا می باشد. درواقع توسط این متغییر ما از دوباره اجرا شدن این متد را توسط هر نقطه از نرم افزار شما برای میکروکنترلرها آن هم در حالی که اجرای قبلی به پایان نرسیده است جلوگیری می نماییم.

_MqttObj : این obejct اصلی از کلاس و یا Dll شما از mqt می باشد. (کدهای سمپل را در ادامه برای دانلود قرار می دهیم)

SOFTWARE_DEBUGMODE : یک متغییر سراسری می باشد تا بتوانید پس از تست های نهایی آن را تغییر دهید تا عملیات print دیگر رخ ندهد. زیرا print بسیار میکروکنترل را کند و ممکن است در روند پایداری میکرو اختلال ایجاد نماید.

Debug_ConsolePrintln : یک Func اصلی برای print کردن در حالت Debug است.

Dvc__CloudSendAllStatus : توسط این متد تمامی وضعیت ابزار را پس از اتصال به سرور جهت بروز رسانی وضعیت ابزار استفاده می نماییم.

Mqtt__OnRecieve:

Func اصلی می باشد که به صورت خودکار پس از دریافت هرگونه داده اجرا می شود.

کد زیر برای دریافت topic دریافتی اطلاعات است. این مورد برای ما بسیار مهم می باشد زیرا توسط آن می توانیم متوجه شویم باید چه عملیاتی انجام دهیم.

String projectTopic = CA_ToString(topic);
int p = projectTopic.indexOf('/');
if (projectTopic.substring(0, p) != _ProjectCode) return;
projectTopic = projectTopic.substring(p + 1);
DynamicJsonDocument doc(1024 + (2 * length));
doc.clear();
deserializeJson(doc, payload);
DynamicJsonDocument doc(1024 + (2 * length));
doc.clear();
deserializeJson(doc, payload);

در ادامه چک می کنیم تاپیک دریافتی چه بوده است ؟ اگر تاپیک DeviceSetting باشد سراغ تنظیم ابزار می رویم.

بعضی از تاپیک ها شاید تا این قسمت از آموزش برای شما شرح داده نشده باشد و در ادامه آموزش شرح خواهیم داد. لطفا در مطالعه آموزش عجله نکنید


if (projectTopic == "DeviceSetting")

یکی از مهمترین نکات برای شما این است که operationName ها خود را دریافت نمایید و متناسب با آن عملیات را انجام دهید.

بطور مثال : شما یک کلید لمسی 4 کاناله (یا رله ، یا یک اینپوت آنالوگ برای خواندن انواع سنسور آنالوگ یا ...) دارید. در پنل کاربری به هنگام تعریف برای جداسازی این کانال ها از هم برای آنها operationName تعریف می نمایید.

ch1 : مثلا این همان کانال 1 است و چک می کنید از Ch1 بود یعنی پایه 20 میکرو کنترلر high یا low یا همان true یا false شود. یا temp1 : شما متوجه می شوید اگر temp1 بود یعنی کانال 1 آنالوگ شما در دستگاه می باشد و متناسب با آن عملیات انجام می دهید.


String opr = doc["operationName"].as<String>();

احتمالا از آموزش های قبل یادتان هست که گزینه مهم بعدی ما value می باشد. توسط آن از مقدار ار کانال و یا operationName باخبر می شوید.


String value = doc["value"].as<String>();

از شرط if (projectTopic == "DeviceSetting") گذر می نماییم و در بخش آموزش تنظبمات اختصاصی برای ابزار، این قسمت از کد را کامل توضیح می دهیم.

ادامه کد از if (projectTopic == "ServerToDevice"):

همانطور که اطلاع دارید زمانی که اطلاعات ارسال می شود، به دلیل آنکه تمام ابزارها یک پروژه (یا همان مکان مثل خانه) داده های ارسالی را دریافت می کنند، نیاز است متوجه شویم آیا داده دریافتی برای خود ابزار می باشد یا خیر. این کار را توسط شناسایی سریال از داده ارسالی که قبلا در آموزش ها گفته ایم انجام می دهیم. سریال را توسط خط دستوری زیر بدست می آوریم :


String deviceSerial = doc["deviceSerial"].as<String>();

ابتدا چک می نماییم اگر operationName خالی نباشد


if (doc["operationName"])

operationName را دریافت می نماییم


cmd = doc["operationName"].as<String>()

در شرط زیر چک می شود آیا این operationName مربوط به سناریو ها است یا خیر. (نیاز است بخش سناریو مطالعه شود)

متناسب با این operationName شما وضعیت سناریو های خود را بروز می نمایید و در حافظه میکرو یعنی (eeprom) یا هرجای دیگر این داده را نگه می دارید تا متوجه شوید در زمانی که یک سناریو درخواست اجرا می دهد، باید توسط آن ابزار اجرا شود یا خیر.


if (cmd == "disable_scenario" || cmd == "enable_scenario" || cmd == "run_scenario")

operaionName مهم دیگر ما debugger می باشد که صرفا در بخش آزمایشگاه آنهم برای تولید کنندگان و آنهم با اجازه دارنده ابزار امکان این را دارند که یک سری از عملیات ها را جهت راحتی در پشتیبانی از راه دور داشته باشند.


if (cmd == "debugger") {
if (doc["value"]) {
String value = doc["value"].as<String>();
if (value == "reboot") Tools__Reboot();

else if (value == "clean") {
// do somethings
Tools__Reboot();
} else if (value == "reset_factory") {
// do somethings
Tools__Reboot();
} else if (value == "config") {
Tools__SetMode("config_panel", true);
} else if (value == "special")
;
}
}

reboot : این متد را متناسب ب میکرو کنترل خود توسعه دهید تا بتوانید در مواقع ضروری ابزار خود را در پشتیبانی آنلاین و برخط ریبوت نمایید.

clean : اگر نیاز داشتید می توانید این قابلیت را توسعه دهید تا بعضی از تنظیمات کاربر را پاک نمایید.

reset_factory : در صورت نیاز توسعه دهید.

config : ممکن است کاربر از بردن ابزار به پنل کانفیگ عاجز باشد. توسط توسعه این قابلیت می توانید ابزار را به حالت config انتقال دهید.

special : در صورت نیاز توسعه دهید.

متناسب با آموزش های گذشته دیدید که ما از طریق generate analyze مقدار داده را ا شما دریافت می نماییم و آن را برای شما ارسال می کنیم. در اینجا نیاز است شما داده ای که در فایل js تولید کرده اید را دریافت نمایید.

if (doc["data"]) {
cmd = doc["data"].as<String>();
if (cmd != "") {
//do somethings
}
}

Mqtt__Send:

Func اصلی برای ارسال داده از ابزار به سرور می باشد.

IOT__WiFiStart:

روش اتصال به مودم توسط میکروکنترلر ESP32 است که طبیعته شما می توانید متناسب با روش اتصال خود این کدها را تغییر دهید.

IOT__WiFiEvent:

طرفا Func مربوط به رویداد ها در wifi برای Esp32 است.

IOT__Checker:

Func اصلی که وظیفه مواظبت از اتصال دائم به سرور را بر عهده دارد.

IOT__Setup:

Func مربوط به تنظیمات اولیه Mqtt

IOT__Loop:

حلقه اصلی در میکروکنترلر

xTaskCreatePinnedToCore:

یک Task می باشد که حلقه اصلی را همیشه زنده نگه می دارد.

دانلود نمونه کد در گیت هاب