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
//************************************************************ // 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/