ตอนแทรก 3.5-2: Two Way Communication with Painlessmesh & MQTT

ใกล้วันหยุดยาวสงกรานต์ละ มาต่อกันที่ตอน 3.5-2 กันครับ จากตอนที่แล้วเราก็จะมี MQTT Server ของเราไว้ใช้เองและทดลองการรับส่งข้อมูลระหว่างกันรวมถึงส่งคำสั่งไปควบคุมเปิดปิดไฟ LED สำหรับในตอนนี้เราจะมาประยุกต์ใช้งานร่วมกับ Mesh Network ในลักษณะ Two Way Communication กันครับ เพราะด้วยธรรมชาติของ Mesh Network จากตอนที่ผ่านๆมา ทุก Node จะเชื่อมต่อกันผ่าน WiFi และจะมี Gateway Node ที่ทำหน้าที่ Bridge ระหว่าง Mesh Network กับ Network ภายนอก

เพราะการส่งข้อมูลจาก Sensor Node ใน Mesh Network ขึ้น Server จากตอนที่ผ่านๆมันอาจจะน่าเบื่อเกินไป การสื่อสารแบบ Two Way ก็จะสนุกมากยิ่งขึ้นละ ไม่ใช่แค่อ่านข้อมูลจาก Sensor แล้วส่งต่อข้อมูลอย่างเดียว เรายังสามารถที่จะส่งคำสั่งไปให้เปิดปิดอุปกรณ์อื่นที่ต่อกับ Node ไหนๆก็ได้ผ่านทางมือถือ โดยอุปกรณ์ Hardware ที่จะใช้สำหรับตอนนี้จะประกอบไปด้วย

  • Gateway Node (Nodemcu)
  • Server Node (Nodemcu)
  • Temp/Humidity Node1-2 (Nodemcu + DHT22 + Led)

 

ในการ Debug โปรแกรมซักโปรแกรมนึง หรือ Service นึงเราก็อาจไล่ไปทีละบรรทัด ดูค่าตัวแปรต่างๆว่าเป็นไปตามที่เราต้องการหรือเปล่า อีกวีธีหนึ่งที่ช่วยลดการผิดพลาดในการทำงานของโปรแกรมที่เราเขียน ก็คือการเขียน Flow Chart หรือ Workflow เพราะพองานเราซับซ้อนขึ้น หรือมีหลาย Service หลาย Function ที่ทำงานร่วมกันแล้ว ถ้าไม่มี Flow Chart หรือ  Work Flow คอยกำกับ ก็อาจทำให้เราหลงทางได้ ในส่วนของ การเขียน Diagram เหล่านี้จริงๆก็มีหลายมาตรฐาน อย่างเช่น UML 2.0 ที่ประกอบด้วยหลาย Diagram แต่ถ้าเราไม่ใช่ Software House ที่ต้อง Compile ตามมาตรฐาน ขอให้ทีมที่พัฒนาเข้าใจใน Diagram ที่เขียนขึ้นมาร่วมกันก็พอแล้วครับ

Work Flow ที่เขียนขึ้นมาก็แบ่งออกเป็น 4 Swim Lane ด้วยกันตามหน้าที่และ Device ทั้งในส่วนส่งข้อมูลขึ้นไปยัง MQTT Server และส่วนที่รับ command จากมือถือมาเพื่อควบคุม Node ต่างๆที่อยู่ใน Mesh Network เมื่อมี Diagram เราก็จะเห็นแล้วว่าการเขียนโปรแกรมของเราจะประกอบด้วยการทำงานอะไรบ้าง ซึ่งก็จะง่ายขึ้นละทีนี้

 

Two Way Communication with Painlessmesh & MQTT

เมื่อได้ Work Flow การทำงานของแต่ละส่วนเพื่อที่จะจำลอง…….. อืมมม จะว่าจำลองก็ไม่ใช่นะเพราะถ้าเราแทนที่ LED ด้วยการต่อกับ Relay และต่อ ปั้มน้ำ หลอดไฟ เครื่องผลิตความชื้น หรือพัดลมดูดอากาศก็สามารถที่จะใช้งานใน application จริงอย่างใน Smart farm หรือโรงงานกันได้เลย โดยที่ปลายทางแทนที่จะเป็นการส่ง command จากมือถือ ก็สามารถเป็น Node อีกตัวหนึ่งที่วิเคราะห์ภาพรวมของ environment ที่ได้รับมาจากหลายๆ Node แล้วส่งคำสั่งกลับไปก็ได้ เรามาดู Code การทำงานของ Nodemcu ในแต่ละตัวของเรากันดีกว่าครับ

 

Code for Temp/Humidity Node1-2 (Nodemcu + DHT22 + Led)

ในส่วนของ Node1-2 เราก็ใช้ Code ในตอนที่ 3 มาโมได้เลยครับโดยเพิ่มในส่วนของ receivedCallback เพื่อดูคำสั่ง command ที่ได้รับมาแล้วก็อ่านค่าจาก json ในส่วนของ exeCmd

#include "painlessMesh.h"
#include "DHT.h"

#define DHTPIN D4
#define LED_PIN D2
#define DHTTYPE DHT22

#define   MESH_PREFIX     "HelloMyMesh"
#define   MESH_PASSWORD   "hellomymeshnetwork"
#define   MESH_PORT       5555

DHT dht(DHTPIN, DHTTYPE);

void receivedCallback( uint32_t from, String &msg );

painlessMesh  mesh;

size_t logServerId = 0;

// Send message to the logServer every 5 seconds
Task myLoggingTask(5000, TASK_FOREVER, []() {

  float h = dht.readHumidity();
  float t = dht.readTemperature();


  DynamicJsonBuffer jsonBuffer;
  JsonObject& msg = jsonBuffer.createObject();
  msg["nodename"] = "mcu-t2";  //change for identify for the node that send data
  msg["NodeID"] = mesh.getNodeId();
  msg["Temp"] = String(t) + "C";
  msg["Humidity"] = String(h) + "%";

  String str;
  msg.printTo(str);
  if (logServerId == 0) // If we don't know the logServer yet
    mesh.sendBroadcast(str);
  else
    mesh.sendSingle(logServerId, str);

  // log to serial
  msg.printTo(Serial);
  Serial.printf("\n");
});

void setup() {

  pinMode(LED_PIN, OUTPUT);

  Serial.begin(115200);
  Serial.println("Begin DHT22 Mesh Network test!");

  dht.begin();

  mesh.setDebugMsgTypes( ERROR | STARTUP | CONNECTION );  // set before init() so that you can see startup messages

  mesh.init( MESH_PREFIX, MESH_PASSWORD, MESH_PORT, STA_AP, AUTH_WPA2_PSK, 6 );
  mesh.onReceive(&receivedCallback);

  // Add the task to the mesh scheduler
  mesh.scheduler.addTask(myLoggingTask);
  myLoggingTask.enable();

}

void loop() {


  mesh.update();


}

void receivedCallback( uint32_t from, String &msg ) {
  Serial.printf("logClient: Received from %u msg=%s\n", from, msg.c_str());

  // Saving logServer
  DynamicJsonBuffer jsonBuffer;
  JsonObject& root = jsonBuffer.parseObject(msg);
  if (root.containsKey("topic"))
  {
    if (String("logServer").equals(root["topic"].as<String>()))
    {
      // check for on: true or false
      logServerId = root["nodeId"];
      Serial.printf("logServer detected!!!\n");
    }

      // check for command
    else if (String("exeNode").equals(root["topic"].as<String>())) //ส่วนที่เพิ่มเข้ามาในการตรวจสอบว่ามีชุดคำสั่งที่เราระบุมั้ย
    {

      String exeCmd = root["exeCmd"].as<String>();

      if (exeCmd == "1")
      {
        digitalWrite(LED_PIN, HIGH);
      } else
      {
        digitalWrite(LED_PIN, LOW);
      }

    }

  }

}

 

 

Code for Server Node

ในส่วนของ Server Node ก็เช่นเดียวกับ Node อื่นๆเราโมในส่วนของการรับคำสั่งจาก Gateway Node ผ่านทาง Software Serial แล้วส่งไปให้ Node ปลายทางพร้อมคำสั่งที่ได้รับมา

#include "painlessMesh.h"
#include <SoftwareSerial.h>

#define   MESH_PREFIX     "HelloMyMesh"
#define   MESH_PASSWORD   "hellomymeshnetwork"
#define   MESH_PORT       5555

String DataString;

size_t exeNodeID = 0;
int   exeCMD;

SoftwareSerial swSerial(D1, D2, false, 128); //Define hardware connections RX, TX

painlessMesh  mesh;

// Send my ID every 20 seconds to inform others
Task logServerTask(20000, TASK_FOREVER, []() {
  DynamicJsonBuffer jsonBuffer;
  JsonObject& msg = jsonBuffer.createObject();
  msg["topic"] = "logServer";
  msg["nodeId"] = mesh.getNodeId();

  String str;
  msg.printTo(str);
  mesh.sendBroadcast(str);

  // log to serial
  msg.printTo(Serial);
  Serial.printf("\n");
});

void setup() {

  pinMode(D1, INPUT);
  pinMode(D2, OUTPUT);

  Serial.begin(57600); //Initialize hardware serial with baudrate of 57600
  swSerial.begin(57600);    //Initialize software serial with baudrate of 57600


  mesh.setDebugMsgTypes( ERROR | CONNECTION | S_TIME );  // set before init() so that you can see startup messages

  mesh.init( MESH_PREFIX, MESH_PASSWORD, MESH_PORT, STA_AP, AUTH_WPA2_PSK, 6 );
  mesh.onReceive(&receivedCallback);

  mesh.onNewConnection([](size_t nodeId) {
    Serial.printf("New Connection %u\n", nodeId);
  });

  mesh.onDroppedConnection([](size_t nodeId) {
    Serial.printf("Dropped Connection %u\n", nodeId);
  });

  // Add the task to the mesh scheduler
  mesh.scheduler.addTask(logServerTask);
  logServerTask.enable();
}

void loop()
{


  mesh.update();


  while (swSerial.available() > 0)
  {
    char c = swSerial.read();  //gets one byte from serial buffer
    DataString += c; //makes the string DataString

  }


  //ส่วนที่เพิ่มเข้ามาเพื่ออ่านค่าที่ผ่านทาง Software Serial ที่ส่งมาจาก Gateway Node
  if (DataString  != "")
  {
    Serial.println(DataString);

    DynamicJsonBuffer jsonBuffer;
    JsonObject& root = jsonBuffer.parseObject(DataString);
    if (root.containsKey("topic"))
    {
      if (String("exeNode").equals(root["topic"].as<String>()))
      {

        exeNodeID = root["nodeId"];
        mesh.sendSingle(exeNodeID, DataString);  //send to specific Node with execute command
      }
    }

  }


  DataString = "";
  delay(100);


}


void receivedCallback( uint32_t from, String & msg ) {
  Serial.printf("logServer: Received from %u msg=%s\n", from, msg.c_str());

  swSerial.print(msg.c_str()); // Print received data in Mesh Network to Software Serial Port

}

 

Code for Gateway Node

ในส่วนของ Gateway Node แทนที่ก่อนหน้านี้จะรับข้อมูลจาก Server Node ผ่านทาง Software Serial แล้วส่งต่อไปยัง MQTT Server เราก็จะโมโค้ดเพื่อให้รับคำสั่งในส่วนของ Topic Command จาก MQTT Server เพื่อส่งต่อเป็นทอดๆไปยัง Server Node และ Node ปลายทางที่เราต้องการสั่งงาน

#include <SoftwareSerial.h>
#include <ESP8266WiFi.h>
#include <PubSubClient.h>

const char* ssid = "xxxx";
const char* password = "xxxx";

IPAddress mqtt_server(xxx, xxx, xxx, xxx); //ip ของ mqtt server ใครใช้เป็น nameserver ดูตัวอย่างตอนที่ 3.5-1 ได้ครับ
  


WiFiClient espClient;

long lastMsg = 0;
char msg[100];
int value = 0;

String DataString;


SoftwareSerial swSerial(D1, D2, false, 128); //Define hardware connections RX, TX


void callback(char* topic, byte* payload, unsigned int length) 
{
  String s ="";
  
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    //Serial.print((char)payload[i]);
    s += (char)payload[i];
  }  
  Serial.println(s);

  swSerial.print(s.c_str()); //ส่วนที่เพิ่มเข้ามาในการส่งข้อมูลกลับไปยัง Server Node ผ่านทาง Software Serial

  

}

PubSubClient client(mqtt_server, 1883, callback, espClient);

void setup() {

  pinMode(D1, INPUT);
  pinMode(D2, OUTPUT);

  Serial.begin(57600);   //Initialize hardware serial with baudrate of 57600
  swSerial.begin(57600);    //Initialize software serial with baudrate of 57600

  Serial.println("Bridget Node Gateway to MQTT");

  Serial.println();
  Serial.print("connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

  client.connect("MeshGateway", "joe1", "joe1");
  client.setCallback(callback);
  client.subscribe("command");


}

void loop() {


  while (swSerial.available() > 0)
  {
    char c = swSerial.read();  //gets one byte from serial buffer
    DataString += c; //makes the string DataString

  }

  if (DataString  != "")
  {
    Serial.println(DataString);

    if (!client.connected()) {
      reconnect();
    }

    client.loop();

    DataString.toCharArray(msg, 100);
    client.publish("envMesh", msg);

  }



  DataString = "";
  delay(1000);


}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect("MeshGateway", "joe1", "joe1")) {
      Serial.println("connected");
      // Once connected, publish an announcement...
      client.publish("outTopic", "hello world");
      // ... and resubscribe
      client.subscribe("inTopic");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(10000);
    }
  }
}

 

ทดสอบ Two way communication กับ Mesh Network ผ่าน MQTT

การส่งข้อมูลให้ MQTT Server นั้นก็สามารถทำได้หลายรูปแบบเหมือนที่ทำผ่าน ESP8266/ESP32 ถ้าบน PC ผมก็แนะนำ Software MQTT.fx เหมือนที่แสดงให้เห็นในการดูข้อมูลจากตอนที่ 3 ไปแล้ว ซึ่งใครยังไม่โหลดก็สามารถโหลดได้ที่ลิงค์นี้ครับ (MQTT.fx) ส่วนคนที่ใช้ Android ก็สามารถใช้งานผ่าน MQTT Dash เหมือนตอนที่ 3.5-1 ที่ผ่านมา

 

เมื่อ Upload Code ไปยัง Nodemcu ทั้งสี่ตัวเรียบร้อยแล้วก็เปิด Serial Monitor ของ Server Node ขึ้นมา จะเห็น mcu-t1 และ mcu-t2 ส่งข้อมูลเข้ามาที่ Server Node ซึ่งเป็นการส่งแบบระบุปลายทาง เหมือนที่เขียนไปใน ตอนที่ 2 ซึ่งข้อมูลนี้ก็จะส่งต่อไปให้ Gateway Node  ผ่าน Software Serial แล้วก็ส่งไปยัง MQTT Server อีกทอดหนึ่ง

 

มาดูขาย้อนกลับกันบ้างโดยเราใช้ MQTT.fx ในการส่งคำสั่งออกไปที่ MQTT Server ซึ่งชุดคำสั่งในรูปแบบ JSON ที่ผมตั้งไว้จะเป็นลักษณะนี้

mcu-t1 NodeID:3896713776
{"topic":"exeNode", "nodeId":3896713776, "exeCmd":1} 

mcc-t2 NodeID:3896710322
{"topic":"exeNode", "nodeId":3896710322, "exeCmd":1}

โดยที่ exeCmd 1 คือเปิด 0 คือปิด LED

 

ผลลัพท์ที่ได้จะเป็นอย่างในรูปด้านล่างเลยครับ ถ้าใครได้ทดลองทำตามจะสังเกตุอย่างหนึ่งว่าเมื่อเรากด Publish คำสั่งของเราออกไปแล้ว ต้องรอสักพักถึงจะขึ้นมาที่หน้า Serial Monitor ที่เราต่อกับ Server Node อยู่จากนั้นแว้บบบนึง LED ของ Node ปลายทางของเราถึงจะเริ่มเปิด-ปิด LED ตามชุดคำสั่งของเรา

จากการทดสอบจะเห็นเลยว่าสิ่งหนึ่งที่เจอเมื่อระบบของเราซับซ้อนขึ้นก็คือเรื่องของ latency ซึ่งข้อมูลกว่าจะเดินทางไปถึง Node ปลายทางนั้นต้องผ่านหลาย hop มาก เริ่มจาก

  1. อุปกรณ์ต้นทางส่งข้อมูลขึ้นไปที่ MQTT Server
  2. Gateway Node  ได้ข้อมูลจาก MQTT Server แล้วส่งไปยัง Software Serial Port
  3. Server Node ได้ข้อมูลจาก Gateway Node ผ่านทาง Software Serial Port
  4. Server Node อ่านค่าจากข้อมูลที่ได้รับมาแล้วส่งให้ Node ปลายทางใน Mesh Network
  5. Node ปลายทางได้รับข้อมูลแล้ว execute command ตามที่ได้รับมา

ถ้าเทียบกับการใช้งานตอนที่ 3.5-1 ที่ผ่านมาที่อุปกรณ์ต่อตรงเข้ากับ MQTT Server ทำงานได้ค่อนข้างเร็วเพราะ hop น้อยกว่าเยอะ ซึ่งหลักการก็จะคล้ายกับ Blynk Server หรือ Cayenne iot ลองคิดดูว่าถ้าใน Mesh Network ของเรามี Node อยู่จำนวนมากและขนาดของ Mesh ใหญ่มากๆ การเดินทางของข้อมูลกว่าจะถึง Node ปลายทางๆที่ต้องผ่านหลาย hop ก็จะทำให้ delay เพิ่มขึ้น

 

สรุปท้ายตอน

ถ้าใครอ่านมาถึงบรรทัดนี้แล้วตามมาตั้งแต่ ตอนที่1 ก็ปาเข้าไป 5 ตอนแล้วกับบทความของการใช้งาน ESP8266/ESP32 ในการสร้าง Mesh Network ด้วย Painlessmesh Library น่าจะพอเห็นภาพการใช้งานกันไปบ้างแล้วไม่มากก็น้อยตั้งแต่

  • การสร้าง Mesh Network เพื่อการรับส่งข้อมูล
  • การควบคุม Node ต่างๆภายใน Mesh Network
  • การ Bridge ผ่าน Software Serial และใช้งานร่วมกับ MQTT
  • การสร้าง Mosquitto MQTT Server ไว้ใช้งานเอง

 

ซึ่งก็เพิ่มความซับซ้อนให้กับ Mesh Network ของเราเข้าไปมากขึ้น ยกตัวอย่างเช่นการเชื่อมต่อ internet ผ่าน router หลายๆตัวกว่าจะวิ่งจากโครงข่ายของเราไปยังผู้ให้บริการที่แคนาดาได้เพื่อให้ได้ข้อมูลที่ต้องการ ก็ต้องผ่านหลาย hop เช่นกัน และนั่นหมายถึง latency ที่มากขึ้นด้วย ฉะนั้นถ้าต้องการอะไรที่เป็น real time มากๆ การใช้ Mesh Network และ Painless อาจต้องพิจารณาดีๆ แต่ถ้าต้องการส่งข้อมูลขึ้น Server หรือ ควบคุมกันภายใน Mesh Network โดยที่ยอมรับ Delay ได้ 1-5 วิ ก็ถือว่าเหมาะและประหยัดทีเดียว

สำหรับตอนหน้าก็จะเป็นตอนที่ 4 แล้วครับ คราวนี้เราจะมา Bridge Mesh Network ของเราผ่านทาง Lora กันครับ ซึ่งเราจะได้ใช้ความรู้ทั้งเรื่องการใช้งาน Painlessmesh + MQTT + Lora จะได้เห็นภาพการประยุกต์ใช้งานเทคโลโลยีหลายๆด้านเข้าด้วยกัน เพื่อที่ว่าเวลาเจอหน้างานจริงนั้นจะได้หยิบจับมาใช้งานได้ถูกและไม่ถูกจำกัดไว้ด้วยเทคนิคใดเทคนิคหนึ่ง

 

เรื่องเล่าท้ายตอน

เล่าอะไรดีหละ ช่วงวันหยุดสงกรานต์ปีนี้ก็อยู่กับครอบครัว เดินทางกลับสวนที่บางคนที สมุทรสงคราม หลังจากเที่ยวติดกันมาสองสงกรานต์ละ ที่แน่ๆคงต้องเริ่มเขียนสารนิพนธ์เกี่ยวกับ Enterprise Architecture และ Digital Transformation แล้วไม่งั้นเด่วไม่จบแน่ และคงเอาบอร์ด Lora ESP32 ย่าน 915MHz ไปลองที่สวนด้วย ใครที่เดินทางกลับต่างจังหวัดก็วางแผนการเดินทางกันดีๆนะครับ ใจเย็นๆ เผื่อเวลาด้วย และเมาไม่ขับ ง่วงไม่ขับ เดินทางปลอดภัยและสวัสดีปีใหม่ไทย ขอให้มีความสุขเนื่องในวันสงกรานต์กันทุกคนครับ ^_^

Author: Joe D.S.
Just a man on earth

1 thought on “ตอนแทรก 3.5-2: Two Way Communication with Painlessmesh & MQTT

  1. โดยที่ปลายทางแทนที่จะเป็นการส่ง command จากมือถือ ก็สามารถเป็น Node อีกตัวหนึ่งที่วิเคราะห์ภาพรวมของ environment ที่ได้รับมาจากหลายๆ Node
    ความหมายตรงนี้ คือ ใช้ คนสั่งงาน คนเป็นคนตัดสินใจ

    แต่ในความเป็นจริง เรา จะตั้งเงือนไขว่า อุณหภูมืเท่าไร ความชื้นเท่าไร ให้ ไปเปิด พัดลม
    หรือ ปั้มน้ำ
    ซึ่งในการคำนวนนี้ เราจะเห็น ความแตกต่างระหว่าง micro controller กับ micro computer

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.