WiFi-Mesh-Solutions

ด้วยเหตุที่มีโจทย์ปัญหาให้ศึกษาทั้งสำหรับ Smart Farm, Smart Industry  ในพื้นที่ๆ ไม่มี WiFi ครอบคุลมถึง  หรือยาก หรือต้องลงทุนมากในการที่จะต้องติดตั้ง WiFi ให้ทั่วทั้งโครงการ   โดยที่ device จะต้องอาศัยการส่งต่อจากตัวนึงไปยังอีกตัวนึง  จนกระทั่งส่งไปเข้าตัว Server
WiFi-Mesh, LoRa Mesh ดูจะเป็นอีกตัวสำคัญ ที่จะตอบโจทย์นี้  ก็ลองมาลองศึกษากัน
WiFi-Mesh  นิยมที่จะใช้  “PainlessMesh”  Library  ตามลิงก์  และเมื่อทำการติดตั้งแล้วจะได้  Example ที่พร้อมใช้มากมายหลายตัวตามแต่กรณีศึกษา ซึ่งคนพัฒนา Library ก็ทำได้สุดยอด  ทำให้ WiFi  device  คุยกันอย่างมีระบบระเบียบ
ในกรณีของเรานั้  มีความต้องการสร้าง  WiFi Mesh-Network ที่มี node ประมาณ 5   node และต้องการส่งข้อมูลเข้า Server และให้สามารถสั่งกาควบคุมระบบจากภายนอกได้

  • Code สำหรับ Nodes  ที่มีความเหมือนกันเด๊ะในด้านการสื่อสาร  ส่วนการจะวัด Sensor หรือทำอะไร ที่ตัวมันก็ว่ากันไปละครับ
  • Code สำหรับ Server Node (Bridge)  ส่วนนี้จะเป็นเหมือนประธานการประชุมที่จะได้ข้อสรุปว่า  ที่ mesh device คุยกันมานั้น ได้ข้อสรุปอย่างไร  จะส่งต่อไปไหนอย่างไร หรือจะมีการการทำงานภายใน mesh อย่างไรบ้าง
  • Code สำหรับ Gateway  โดย Gateway จะสื่อสารกับ Server Node  ผ่านทาง Serial Port  โดยใช้ Library Software Serial

 

มาดู Code สำหรับ  Node (V1 )

//************************************************************
// this is a simple example that uses the easyMesh library
//
// 1. blinks led once for every node on the mesh
// 2. blink cycle repeats every BLINK_PERIOD
// 3. sends a silly message to every node on the mesh at a random time between 1 and 5 seconds
// 4. prints anything it receives to Serial.print
//
//
//************************************************************
#include <painlessMesh.h>
// some gpio pin that is connected to an LED...
// on my rig, this is 5, change to the right number of your LED.
#define   LED             2       // GPIO number of connected LED, ON ESP-12 IS GPIO2
#define   BLINK_PERIOD    3000 // milliseconds until cycle repeat
#define   BLINK_DURATION  100  // milliseconds LED is on for
#define   MESH_SSID       "whateverYouLike"
#define   MESH_PASSWORD   "somethingSneaky"
#define   MESH_PORT       5555
// Prototypes
void sendMessage();
void receivedCallback(uint32_t from, String & msg);
void newConnectionCallback(uint32_t nodeId);
void changedConnectionCallback();
void nodeTimeAdjustedCallback(int32_t offset);
void delayReceivedCallback(uint32_t from, int32_t delay);
Scheduler     userScheduler; // to control your personal task
painlessMesh  mesh;
bool calc_delay = false;
SimpleList<uint32_t> nodes;
String msgtosend ;
String receivestr ;
int car_in ;
void sendMessage() ; // Prototype
Task taskSendMessage( TASK_SECOND * 1, TASK_FOREVER, &sendMessage ); // start with a one second interval
// Task to blink the number of nodes
Task blinkNoNodes;
bool onFlag = false;
void setup() {
  Serial.begin(115200);
  pinMode(LED, OUTPUT);
  mesh.setDebugMsgTypes(ERROR | DEBUG);  // set before init() so that you can see error messages
  mesh.init(MESH_SSID, MESH_PASSWORD, &userScheduler, MESH_PORT);
  mesh.onReceive(&receivedCallback);
  mesh.onNewConnection(&newConnectionCallback);
  mesh.onChangedConnections(&changedConnectionCallback);
  mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);
  mesh.onNodeDelayReceived(&delayReceivedCallback);
  userScheduler.addTask( taskSendMessage );
  taskSendMessage.enable();
  blinkNoNodes.set(BLINK_PERIOD, (mesh.getNodeList().size() + 1) * 2, []() {
      // If on, switch off, else switch on
      if (onFlag)
        onFlag = false;
      else
        onFlag = true;
      blinkNoNodes.delay(BLINK_DURATION);
      if (blinkNoNodes.isLastIteration()) {
        // Finished blinking. Reset task for next run
        // blink number of nodes (including this node) times
        blinkNoNodes.setIterations((mesh.getNodeList().size() + 1) * 2);
        // Calculate delay based on current mesh time and BLINK_PERIOD
        // This results in blinks between nodes being synced
        blinkNoNodes.enableDelayed(BLINK_PERIOD -
            (mesh.getNodeTime() % (BLINK_PERIOD*1000))/1000);
      }
  });
  userScheduler.addTask(blinkNoNodes);
  blinkNoNodes.enable();
  randomSeed(analogRead(A0));
}
void loop() {
  mesh.update();
  digitalWrite(LED, !onFlag);
}
void sendMessage() {
  mycarcheck(); // check car count at my node sensor
  String msg = msgtosend;
  //msg += mesh.getNodeId();
  //msg += " myFreeMemory: " + String(ESP.getFreeHeap());
  mesh.sendBroadcast(msg);  // send only short messge 31
  if (calc_delay) {
    SimpleList<uint32_t>::iterator node = nodes.begin();
    while (node != nodes.end()) {
      mesh.startDelayMeas(*node);
      node++;
    }
    calc_delay = false;
  }
  Serial.printf("Sending message: %s\n", msg.c_str());
  taskSendMessage.setInterval( random(TASK_SECOND * 1, TASK_SECOND * 5));  // between 1 and 5 seconds
}
void receivedCallback(uint32_t from, String & msg) {
  Serial.printf("startHere: Received from %u msg=%s\n", from, msg.c_str());
}
void newConnectionCallback(uint32_t nodeId) {
  // Reset blink task
  onFlag = false;
  blinkNoNodes.setIterations((mesh.getNodeList().size() + 1) * 2);
  blinkNoNodes.enableDelayed(BLINK_PERIOD - (mesh.getNodeTime() % (BLINK_PERIOD*1000))/1000);
  Serial.printf("--> startHere: New Connection, nodeId = %u\n", nodeId);
  Serial.printf("--> startHere: New Connection, %s\n", mesh.subConnectionJson(true).c_str());
}
void changedConnectionCallback() {
  Serial.printf("Changed connections\n");
  // Reset blink task
  onFlag = false;
  blinkNoNodes.setIterations((mesh.getNodeList().size() + 1) * 2);
  blinkNoNodes.enableDelayed(BLINK_PERIOD - (mesh.getNodeTime() % (BLINK_PERIOD*1000))/1000);
  nodes = mesh.getNodeList();
  Serial.printf("Num nodes: %d\n", nodes.size());
  Serial.printf("Connection list:");
  SimpleList<uint32_t>::iterator node = nodes.begin();
  while (node != nodes.end()) {
    Serial.printf(" %u", *node);
    node++;
  }
  Serial.println();
  calc_delay = true;
}
void nodeTimeAdjustedCallback(int32_t offset) {
  Serial.printf("Adjusted time %u. Offset = %d\n", mesh.getNodeTime(), offset);
}
void delayReceivedCallback(uint32_t from, int32_t delay) {
  Serial.printf("Delay to node %u is %d us\n", from, delay);
}
void mycarcheck()
{
  car_in = 1 ; // digitalRead(Sig_Output);
  msgtosend= "2"+String(car_in);
}

และให้  เป็นต้นแบบสำหรับ   node อื่นๆ ที่ไม่ใช่  Server Node และ Gateway
ส่วนที่เพิ่มเติมมาจาก  Code ตัวอย่างคือ
========================
bool calc_delay = false;
SimpleList<uint32_t> nodes;
String msgtosend ;
String receivestr ; int car_in ;
======================
ที่เป็นการกำหนดตัวแปรเพิ่มเติมขึ้นมาสำหรับเตรียม message (msg) ที่จะส่งไปยัง  node ต่อๆ ไป  ไปจนถึง Server Node
=====================================
void sendMessage() {
mycarcheck(); // check car count at my node sensor  เป็น void สำหรับตรวจสอบสภาพการกจราจรที่บริเวณแยกนั้น ๆ
String msg = msgtosend;  //   โดย msg ที่จะส่งคือค่า ในตัวแปร msgtosend ที่เกิดมาจากการทำงานของ void mycarcheck
//msg += mesh.getNodeId();
//msg += ” myFreeMemory: ” + String(ESP.getFreeHeap());
mesh.sendBroadcast(msg); // send only short messge 31  ทำการส่ง  ค่า ใน msgtosend  ไปแบบ boradcast
=====================================
void mycarcheck() {
car_in = 1 ; //
digitalRead(Sig_Output);
msgtosend= “2”+String(car_in);
}
ส่วนนี้ก็จะเป็น algorithm สำหรับการสร้าง msg ว่าสถานะของรถบริเวณตรงนั้น ว่ามีรถรอตรงทางแยกหรือไม่  และส่งนำหน้าด้วยเลข 2 ที่แสดงว่ามาจาก node 2  แยก 2  นั่นเอง
=====================================
มาดู Code สำหรับ  Server ( V1 )

//************************************************************
// this is a simple example that uses the easyMesh library
//
// 1. blinks led once for every node on the mesh
// 2. blink cycle repeats every BLINK_PERIOD
// 3. sends a silly message to every node on the mesh at a random time between 1 and 5 seconds
// 4. prints anything it receives to Serial.print
//
//
//************************************************************
// #include <ESP8266WiFi.h>
// #include <ESP8266HTTPClient.h>
#include <Arduino_JSON.h>
#include <painlessMesh.h>
#include <SoftwareSerial.h>
// some gpio pin that is connected to an LED...
// on my rig, this is 5, change to the right number of your LED.
#define   LED             2       // GPIO number of connected LED, ON ESP-12 IS GPIO2
#define   BLINK_PERIOD    3000 // milliseconds until cycle repeat
#define   BLINK_DURATION  100  // milliseconds LED is on for
#define   MESH_SSID       "whateverYouLike"
#define   MESH_PASSWORD   "somethingSneaky"
#define   MESH_PORT       5555
SoftwareSerial swSerial(4, 5);//, false, 128);
// Prototypes
void sendMessage();
void receivedCallback(uint32_t from, String & msg);
void newConnectionCallback(uint32_t nodeId);
void changedConnectionCallback();
void nodeTimeAdjustedCallback(int32_t offset);
void delayReceivedCallback(uint32_t from, int32_t delay);
Scheduler     userScheduler; // to control your personal task
painlessMesh  mesh;
bool calc_delay = false;
SimpleList<uint32_t> nodes;
void sendMessage() ; // Prototype
Task taskSendMessage( TASK_SECOND * 1, TASK_FOREVER, &sendMessage ); // start with a one second interval
//
// Task to blink the number of nodes
Task blinkNoNodes;
bool onFlag = false;
String receivestr ; // สร้างตัวแปรการรับข้อความจาก node
String receivestr2 ; // สร้งตัวแปรการรับข้อความจาก node ในลำดับใกล้ๆ กัน
String msgtosend ; // สร้างตัวแปรข้อความที่จะสรุปส่ง
String msg ;  // ตัวแปรข้อความ
String decisionstr ; // คำการตัดสินใจ
int countreceive =0; // ตัวนับจำนวนครั้งที่พบสถานการณ์หนึ่ง
int counter = 0;
void setup() {
  Serial.begin(115200);
  pinMode(LED, OUTPUT);
  swSerial.begin(57600);  // สร้างการเชื่อมต่อกับ  gateway
  mesh.setDebugMsgTypes(ERROR | DEBUG);  // set before init() so that you can see error messages
  mesh.init(MESH_SSID, MESH_PASSWORD, &userScheduler, MESH_PORT);
  mesh.onReceive(&receivedCallback);
  mesh.onNewConnection(&newConnectionCallback);
  mesh.onChangedConnections(&changedConnectionCallback);
  mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);
  mesh.onNodeDelayReceived(&delayReceivedCallback);
  userScheduler.addTask( taskSendMessage );
  taskSendMessage.enable();
  blinkNoNodes.set(BLINK_PERIOD, (mesh.getNodeList().size() + 1) * 2, []() {
      // If on, switch off, else switch on
      if (onFlag)
        onFlag = false;
      else
        onFlag = true;
      blinkNoNodes.delay(BLINK_DURATION);
      if (blinkNoNodes.isLastIteration()) {
        // Finished blinking. Reset task for next run
        // blink number of nodes (including this node) times
        blinkNoNodes.setIterations((mesh.getNodeList().size() + 1) * 2);
        // Calculate delay based on current mesh time and BLINK_PERIOD
        // This results in blinks between nodes being synced
        blinkNoNodes.enableDelayed(BLINK_PERIOD -
            (mesh.getNodeTime() % (BLINK_PERIOD*1000))/1000);
      }
  });
  userScheduler.addTask(blinkNoNodes);
  blinkNoNodes.enable();
  randomSeed(analogRead(A0));
}
void loop() {
  mesh.update();
  digitalWrite(LED, !onFlag);
}
void sendMessage() {
  String msg = msgtosend;  // จะถูกกำหนดให้เรียกใช้ผลลัพธ์ที่ได้มาก่อนหน้า
  //msg += mesh.getNodeId(); // คอมเม้นท์จากของเดิมเอาไว้
  //msg += " myFreeMemory: " + String(ESP.getFreeHeap());
  mesh.sendBroadcast(msg);
  if (calc_delay) {
    SimpleList<uint32_t>::iterator node = nodes.begin();
    while (node != nodes.end()) {
      mesh.startDelayMeas(*node);
      node++;
    }
    calc_delay = false;
  }
  Serial.printf("Sending message: %s\n", msg.c_str());
  taskSendMessage.setInterval( random(TASK_SECOND * 1, TASK_SECOND * 5));  // between 1 and 5 seconds
  // msg.c_str() = "";
  msg = ""; // รีเซ็ทกำหนดให้เป็นตัวว่าง
  msgtosend = ""; // รีเซ็ทกำหนดให้เป็นตัวว่าง
}
void receivedCallback(uint32_t from, String & msg) {
  Serial.printf("startHere: Received from %u msg=%s\n", from, msg.c_str());
// ส่วนนี้ที่เพิ่มมาทั้งหมด
  if (countreceive >= 2)
  {
    receivestr2 = msg.c_str();  // ข้อความที่่รับมาจาก  node ต่างๆ อยู่ตรงนี้
    countreceive == 0 ; // รีเซ็ท ในกรณีที่พบครังที่ 1
  }
  else
  {
    receivestr = msg.c_str();  // ข้อความที่ได้รับตัวแรกในรอบใหม่
    countreceive = countreceive +1; //
  }
  //receivestr = msg.c_str(); // receiver from node
  Serial.print("My receive  ");Serial.println(receivestr+"--"+receivestr2);
  decision();  // เรียกไปทำงาน decision
  // msgtosend = decisionstr;
  // receivestr ="";
}
//
void newConnectionCallback(uint32_t nodeId) {
  // Reset blink task
  onFlag = false;
  blinkNoNodes.setIterations((mesh.getNodeList().size() + 1) * 2);
  blinkNoNodes.enableDelayed(BLINK_PERIOD - (mesh.getNodeTime() % (BLINK_PERIOD*1000))/1000);
  Serial.printf("--> startHere: New Connection, nodeId = %u\n", nodeId);
  Serial.printf("--> startHere: New Connection, %s\n", mesh.subConnectionJson(true).c_str());
}
void changedConnectionCallback() {
  Serial.printf("Changed connections\n");
  // Reset blink task
  onFlag = false;
  blinkNoNodes.setIterations((mesh.getNodeList().size() + 1) * 2);
  blinkNoNodes.enableDelayed(BLINK_PERIOD - (mesh.getNodeTime() % (BLINK_PERIOD*1000))/1000);
  nodes = mesh.getNodeList();
  Serial.printf("Num nodes: %d\n", nodes.size());
  Serial.printf("Connection list:");
  SimpleList<uint32_t>::iterator node = nodes.begin();
  while (node != nodes.end()) {
    Serial.printf(" %u", *node);
    node++;
  }
  Serial.println();
  calc_delay = true;
}
void nodeTimeAdjustedCallback(int32_t offset) {
  Serial.printf("Adjusted time %u. Offset = %d\n", mesh.getNodeTime(), offset);
}
void delayReceivedCallback(uint32_t from, int32_t delay) {
  Serial.printf("Delay to node %u is %d us\n", from, delay);
}
void decision()  // ส่วนการตัดสินใจ  ที่เขียนเพิ่มขึ้นมา  ส่วนนี้แหละคือส่วนสำคัญที่จะต้องกำหนด อัลกอริทึมของงาน
{
  msgtosend=String(receivestr)+String(receivestr2);
  if (receivestr == "31" and receivestr2 == "21" )
  {
    decisionstr = "Car from both nodes";
  }
  else
  {
    decisionstr = "Free road...";
  }
  Serial.println(decisionstr+"--"+msgtosend);
  Serial.println("Doing serial send ");
  if (swSerial.readString()){  // ถ้าพบว่ามีการทำงานอยู่
     // chat.print(1);
     if(swSerial.readString() == "Input1" ) // มีการถามคำถาม ด้วย input1 มาจาก Gateway
     swSerial.print(String(msgtosend));  // แล้วส่งผ่านไปทาง serial
   }
  msgtosend ="";  //  รีเซ็ทค่าว่าง
}

และสุดท้าย  Code สำหรับ  Gateway ที่เราจะให้มันยิงไปที่  IFTTT.com

#include <SoftwareSerial.h>
#include <ESP8266WiFi.h>
// #include <PubSubClient.h>
#include <ESP8266HTTPClient.h>
const char* ssid = "Arsenal2019_2.4G";  //wifi ssid
const char* password = "kb75699212"; //wifi passwd
long lastMsg = 0;
char msg[100];
int value = 0;
String DataString;
String a ;
int counter = 0 ;
// ตั้งค่า IFTTT
String event = "pps-wifi-mesh"; // ชื่อ event
String key = "deVrnlBciWxTu3MOqanL9v"; // Key
String sheets = "";
SoftwareSerial swSerial(4, 5); // , false, 256); //Define hardware connections RX, TX
// กำหนด port สำหรับการสื่อสารกับ gateway
void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();
}
void setup() {
  pinMode(4, INPUT);
  pinMode(5, OUTPUT);
  Serial.begin(115200);   //Initialize hardware serial with baudrate of 115200
  swSerial.begin(57600);    //Initialize software serial with baudrate of 115200
  // Serial.println("Bridget Node Gateway to MQTT");
  Google_setup();
  Serial.println();
  Serial.print("connecting to ");
  Serial.println(ssid);
  WiFi_Setup();
  //client.connect("MeshGateway", "xxx", "xxx"); //MeshGateway is node name, change xxx to user/passs of your server
  //client.setCallback(callback);
  //client.subscribe("command");
}
void loop() {
  Serial.println("Loop...");
  if (WiFi.status() != WL_CONNECTED) {  // wifi อาจจะหลุดเลยต้องต่อซ้ำ ๆ ตรงนี้ทำให้การทำงานช้า  สามารถลองเอาออกได้  แต่ก็มีโอกาสพลาด
        delay(1000);
        Serial.println("Failed to connected and will restart WiFi setup ");
        WiFi_Setup(); // หากหลุดก็จะต่อซ้ำ
    }
  // เริ่มการสื่อสารกับ Gateway
  while (a == "") {
    swSerial.print("Input1"); // ส่งหัวข้อคำถาม ว่า Question1 ไปยัง Arduino
    a = swSerial.readString();  // อ่าน Serial และนำไปเก็บในตัวแปร A
    delay(100);
    Serial.print(".");
  }
  Serial.print(" Answer1 ");Serial.println(a);
  a = String(a); // ข้อความที่จะใช้ส่ง
  delay(1000);
  Google_Send(); // เรียกใช้ void ทำการส่งไปยัง IFTTT และ Google
  a="";
}
void reconnect() {
}
void Google_setup() {
  //สำหรับ esp8266 url ตรง https ให้เปลี่ยนเป็น http
  //sheets = "http://maker.ifttt.com/trigger/TEST_SENSOR/with/key/dscczn_EeNwjyMrBgRfvK_?value1="+String(a); // url ที่ส่งค่าลง google sheet
    sheets = "http://maker.ifttt.com/trigger/pps-wifi-mesh/with/key/deVrnlBciWxTu3MOqanL9v?value1="+String(a); // ส่ง string ไปยัง Google
  // pinMode(Sen1_input, OUTPUT);
  // pinMode(Sen2_input, OUTPUT);
  // Serial.begin(115200);
  Serial.println(sheets);
  /*
  WiFi.begin(ssid, password);
  Serial.print("Connecting...");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
 */
}
void Google_Send()
{
  counter = 2; // ++;  กำหนดให้ส่งเลย  อันนี้ก็ต้องไปปรับเอานิดนึง
  Serial.println(counter);
  /*
  WiFi.begin(ssid, password);
  Serial.print("Connecting...");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  */
  // WiFiClient client;
  Serial.println("Google Send ");
   // while(client.available()) {
   // sheets = "http://maker.ifttt.com/trigger/TEST_SENSOR/with/key/dscczn_EeNwjyMrBgRfvK_?value1="+String(a);
   sheets = "http://maker.ifttt.com/trigger/pps-wifi-mesh/with/key/deVrnlBciWxTu3MOqanL9v?value1="+String(a);
    if(counter == 2){
       HTTPClient http;
       http.begin(sheets); //กำหนด url เพื่อเซฟข้อมูลลง google sheets
       int httpCode = http.GET(); //ส่งค่า url
       String payload = http.getString();// อ่านค่าผลลัพธ์
       Serial.println(payload);
    }
    // String line = client.readStringUntil('\r');
    //Serial.print(line);  ถอดคอมเม้น? ออก เพื่อดูการตอบสนองจาก Server
}
void WiFi_Setup()
{
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());
}

 
Code สุดท้ายนี้จะมีองค์ประกอบที่สำคัญคือ Software Serial connection
 
Thank to website : https://randomnerdtutorials.com/esp-mesh-esp32-esp8266-painlessmesh/