บทความในตอนนี้ก็จะใช้เวลาเขียนนานหน่อย เนื่องด้วยงานที่ทำ แล้วก็อุปกรณ์เจ้ากรรม Heltec ESP32+OLED 915MHz ที่สั่งมาดันเสียไปทั้งคู่ ก็เลยเว้นช่วงไว้รออุปกรณ์มาให้ครบเพื่อทดสอบ ซึ่งจากประสบการณ์ที่สั่งของจากจีนมาทดลอง กรณีที่ไม่ได้สั่งแบบนำเข้ามาทดสอบแบบเป็นทางการ ก็ควรสั่งเข้ามาอย่างน้อย 2-3 ตัวด้วยกัน เผื่อตัวหนึ่งใช้งานไม่ได้จะได้ทดสอบอีกตัวหนึ่ง จะได้รู้ว่าเอ๊ะมันเป็นที่ code ของเราหรือว่าเป็นที่อุปกรณ์กันแน่ ซึ่งสุดท้ายตัวนึง OLED เสีย อีกตัวนึง LoRa Module เสีย จบกันกับย่าน 915MHz
เนื้อหาหลักๆในตอนนี้ก็จะเกี่ยวกับเรื่องของ LPWAN (Low Power Wide Area Network) อย่าง LoRa ทั้งการใช้งาน ข้อดีข้อเสีย กฎหมายที่เกี่ยวข้องในการนำมาใช้งาน เพราะถ้าติดตามอ่านมาตั้งแต่ตอนที่ 1 (ESP8266/ESP32: Introduction & Painlessmesh) ใจความหลักในการใช้งาน Mesh Network ด้วย Painlessmesh นั้นจบไปตั้งแต่ตอนที่ 3 แล้วครับ ที่เหลือเป็นการประยุกต์มากกว่า ว่าเราจะใช้งาน Mesh Network ให้เหมาะสมยังไง หรือเพื่อจุดประสงค์ไหน เช่น
- เพื่อลด Single Point of Failure
- เพื่อขยายพื้นที่การครอบคลุมในการส่งข้อมูล
- ลดต้นทุนในการขยายจุดกระจายสัญญาณ
จากนั้นการส่งข้อมูลจาก Mesh Network ซึ่งเปรียบเหมือน Local Network ของเรา เราจะ Bridge ยังไงเพื่อให้ข้อมูลเหล่านั้นไปยัง Server, Database, Cloud, MQTT Broker หรืออะไรก็แล้วแต่ที่อยู่ Network อีกวงหนึ่งหรือผ่าน Internet ซึ่งก็จะได้ทดลองทำกันไปในตอนที่ 3 (ESP8266/ESP32: Painlessmesh Bridge) ซึ่งถ้าใครจะไม่ใช้ Software Serial ในการ Bridge ก็ยังสามารถทำได้อีกหลายวิธี ซึ่งวันนี้เราก็จะมา Bridge ผ่าน LoRa กัน
LoRa vs LoRaWan
ผู้ให้บริการทางด้านโทรคมนาคมในบ้านเราช่วงนี้ ทั้ง AIS (AIS NB-IoT) และ True (True IoT) ก็เปิดตัวบริการ NB-IoT ออกมา ส่วน CAT ก็มาทางสาย LoRa (LoRa IoT by CAT) ซึ่งทั้งคู่ก็ออกมาเพื่อรองรับการสื่อสารสำหรับบริการประเภท IoT ในส่วนของ AIS และ True ที่เป็นผู้ให้บริการมือถืออยู่แล้ว มีความถี่ที่ใช้ในการจัดสรรสำหรับบริการ LTE การมาให้บริการ NB-IoT ก็จะประหยัดต้นทุนไปได้เยอะแถมเป็นการ Utilize ความถี่ที่ตัวเองได้รับอนุญาตมาด้วย เท่าที่เห็นก็เป็น 3 ผู้บริการหลักๆที่เปิดตัวออกมา ซึ่งหัวข้อของเราในวันนี้ก็จะคุยกันในเรื่องของ LoRa และการใช้งานร่วมกัน Mesh Network ฉะนั้นเรามาดูข้อแตกต่างกันระหว่าง LoRa และ LoRaWan กันก่อน
LoRa จะเป็นการสื่อสารในระดับ Physical Layer ซึ่งเหมาะที่จะใช้งานในลักษณะแบบ P2P ไม่มี Encryption ส่งข้อมูลกันแบบ Broadcast ออกไปเลย ฉะนั้นถ้ามีคนตั้ง LoRa Node ขึ้นมาแล้วใช้ค่า Setting เดียวกับเราก็สามารถที่จะเห็นข้อมูลได้ ซึ่ง LoRa ถูกพัฒนาเป็น Wireless Data Communication โดยบริษัท Cycleo (Grenoble, France) จากนั้นผู้ผลิตชิป Semtech ก็เข้าไปซื้อบริษัทนี้มาในปี 2012 ถ้าดูตามสเปคของชิปยอดฮิตอย่าง SX1276 ที่รองรับย่านความถี่ 920-925MHz ตามประกาศของ กสทช ก็จะเห็นว่าที่ LoRa นั้นส่งได้ไกลนั้นส่วนนึงก็เพราะ High sensitivity ที่รองรับถึง -148dBm เลย (ข้อมูล Semtech SX1276) แต่ใช้งานจริงจะได้ที่เท่าไหร่ต้องดูกัน
ส่วน LoRaWan นั้นจะอยู่ Ontop ของการสื่อสารแบบ LoRa Radio อีกทีซึ่งจะมีในส่วนของ Protocol การรับส่งข้อมูลเข้ามา รองรับ Encryption ซึ่งใช้ AES-128 ในการเข้ารหัส สามารถทำ QOS ได้ด้วย โดยรายละเอียดของ LoRaWan นั้นจะมีอยู่ค่อนข้างเยอะพอสมควร รวมถึงในส่วนของ Device End Node ก็ยังสามารถจำแนกได้ออกเป็น Class A B C อีก และการทำงานก็จะแบ่งออกเป็น Tier ตามรูปด้านล่างนี้เลยครับ
ฉะนั้นการจะเลือกใช้ LoRa หรือ LoRaWan ก็ควรเลือกให้เหมาะสมกับ Application ที่จะใช้งานด้วย และที่สำคัญควรอยู่ตามข้อกำหนดของ กสทช ด้วยครับเนื่องจากคลื่นความถี่เป็นทรัพยากรส่วนรวมที่มีการใช้งานร่วมกัน ถ้าใช้ LoRa ก็ต้องดูในเรื่องของ Duty Cycle เองด้วยไม่ใช่ส่งข้อมูลตลอดเวลา จองการใช้งานคลื่นความถี่ตลอด รวมถึงกำลังส่งที่ถ้ามากไป ก็จะไปรบกวนการใช้งานของอุปกรณ์หรือ Application ที่ใช้งานในความถี่เดียวกัน
ข้อมูลเพิ่มเติมจาก LoRa Alliance ที่น่าศึกษา ซึ่งทาง LoRa Alliance ก็เป็น Non Profit Organization ที่ผลักดันในเรื่องของการใช้ LoRa/LoRaWan เพื่อใช้ในงาน IoT
- LoRaWan What is it? (Download Link)
- LoRaWan 101: A Technical Introduction (Download Link)
Limitations
เรามาดูข้อดีข้อเสียของการใช้ LoRaWan กันบ้าง ไม่ใช่ว่าเทคโนโลยีที่ดีอย่าง LoRa/LoRaWan จะเหมาะสมครอบจักรวาลสำหรับทุก Application ฉันใดก็ฉันนั้น Mesh Network ก็จะเหมาะกับ Application ในบางประเภท หรือบางงาน เพราะถ้าเราต้องการเอา LoRa/LoRaWan ไปส่งข้อมูลแบบ Realtime ก็คงไม่เหมาะแน่นอนจะกลายเป็นการจองช่องสัญญาตลอดเวลา หรือให้ส่งข้อมูลขนาดใหญ่ๆก็คงไม่เหมาะเช่นกัน เพราะถูกจำกัดด้วยข้อมูลที่ส่งขนาดไม่กี่ byte และความเร็วในการส่งที่ต่ำ
Suitable use-cases for LoRaWAN:
- Long range – multiple kilometers
- Low power – can last months (or even years) on a battery
- Low cost – less than 20€ CAPEX per node, almost no OPEX
- Low bandwidth – something like 400 bytes per hour
- Coverage everywhere – you are the network! Just install your own gateways
- Secure – 128bit end-to-end encrypted
- Geolocation / Triangulation – you should probably use GPS for this, but we’re doing our best to make it work with just LoRa. Check out Collos.
Not Suitable for LoRaWAN:
- Realtime data – you can only send small packets every couple of minutes
- Phone calls – you can do that with GPRS/3G/LTE
- Controlling lights in your house – check out ZigBee or BlueTooth
- Sending photos, watching Netflix – check out WiFi
ที่มา: (Limitations of LoRaWan:The Things Network)
Regulation & NBTC
เนื่องจากคลื่นความถี่ถือเป็นทรัพยากรที่ต้องถูกจัดสรรเพราะมีอยู่อย่างจำกัด และต้องมีกำกับดูแลในเรื่องของการใช้งาน ในประเทศไทยก็จะมีองค์กรที่ทันสมัยมว้ากๆ องค์กรหนึ่งชื่อว่า กสทช (สำนักงานคณะกรรมการกิจการกระจายเสียง กิจการโทรทัศน์ และกิจการโทรคมนาคมแห่งชาติ) ที่เดี่ยวๆก็จะชอบมีคนออกมาให้ข่าวว่า เด่วยึดใบอนุญาตมั่ง ปรับนู่นนี่นั่นมั่ง แล้วสุดท้ายเรื่องก็เงียบไป มาดูเรื่องการใช้งานความถี่ของเรากันดีกว่าครับ
จากข้อมูลที่หามาจากเวปของหน่วยงาน กสทช เองบอกได้เลยว่าเล่นเอางงทีเดียว บางทีค้นหาด้วยเลขความถี่ต้องใช้เลขไทย ไม่งั้นไม่เจอ ซึ่งผมรวบรวมมาได้ดังนี้โดยอิงตามความถี่ย่านที่เรามักจะเห็นคุ้นเคยเห็นการใช้งานกันสำหรับ LoRa, RFID และ RC นั่นก็คือ EU433 ISM Band, AS923-925MHz
- ตารางกำหนดคลื่นความถี่แห่งชาติ 2560 (Download Link)
- กรกฎาคม 2560: เอกสารประกอบการรับฟังความคิดเห็นสาธารณะ ร่างประกาศเกี่ยวกับการใช้คลื่นความถี่ 920-925MHz (Download Link)
- สิงหาคม 2560: ร่างประกาศเกี่ยวกับการใช้คลื่นความถี่ 920-925MHz และแนวทางการอนุญาตเพื่อประกอบกิจการ iot (Download Link)
- พฤษจิกายน 2560: ราชกิจจานุเบกษา – หลักเกณฑ์การอนุญาตให้ใช้คลื่นความถี่ย่าน 920-925MHz (Download Link)
- พฤษจิกายน 2560: ราชกิจจานุเบกษา – มาตรฐานทางเทคนิคของเครื่องโทรคมนาคมและอุปกรณ์สําหรับเครื่องวิทยุคมนาคมที่ไม่ใช่ประเภท RFIDซึ่งใช้คลื่นความถี่ย่าน 920-925MHz (Download Link)
- มกราคม 2561: ราชกิจจานุเบกษา – หลักเกณฑ์และเงื่อนไขการอนุญาตให้ใช้คลื่นความถี่สําหรับอากาศยานซึ่งไม่มีนักบินสําหรับใช้งานเป็นการทั่วไป (Download Link)
- กุมภาพันธ์ 2561 ราชกิจานุเบกษา – เครื่องวิทยุคมนาคมและสถานีวิทยุคมนาคมที่ได้รับการยกเว้นไม่ต้องได้รับใบอนุญาตตามพระราชบัญญัติวิทยุคมนาคม (Download Link)
- ความถี่วิทยุและใบอนุญาตวิทยุโทรคมนาคมประเภท RFID (Download Link)
- การปรับปรุงกฎระเบียบด้านการบริหารคลื่นความถี่และทรัพยากรโทรคมนาคมเพื่อรองรับการพัฒนาของ Internet of Thingsในประเทศไทย (Download Link)
ฉะนั้นสำหรับส่วนของคลื่นความถี่ 433MHz ซึ่งที่เราจะทดสอบกันสำหรับ LoRa นั้น ก็จะไม่ได้เกี่ยวกับการใช้งานทั้ง RFID หรือการใช้งานในด้าน RC กับอากาศยานไร้คนขับ แต่จะทดสอบโดยอาศัยความตามเครื่องวิทยุคมนาคมและสถานีวิทยุคมนาคมที่ได้รับการยกเว้นไม่ต้องได้รับใบอนุญาต ซึ่งย่าน 433MHz นั้นห้ามส่งเกิน 10 มิลลิวัตต์
สำหรับย่าน 920-925MHz นั้นซึ่งมีข้อกำหนดในส่วนของ มาตรฐานทางเทคนิคของเครื่องโทรคมนาคมและอุปกรณ์ กสทช.มท. 1033 – 2560 ว่าด้วยเรื่องเครื่องวิทยุคมนาคมที่ไม่ใช่ประเภทRadio Frequency Identification: RFIDซึ่งใช้คลื่นความถี่ย่าน920-925เมกะเฮิรตซ์ ก็กำหนดไว้ว่า ไม่เกิน 50 มิลลิวัตต์ได้รับยกเว้นไม่ต้องได้รับใบอนุญาตให้ทํามีใช้นําเข้าและนําออกซึ่งเครื่องวิทยุคมนาคมและใบอนุญาตให้ตั้งสถานีวิทยุคมนาคมแต่ไม่ได้รับยกเว้นใบอนุญาตให้ค้าซึ่งเครื่องวิทยุคมนาคม
ถ้ายึดกันตาม Frequency Plan และตามช่วงความถีที่ กสทช ปลดล้อคให้กับอุปกรณ์และบริการ IoT ก็สามารถใช้ได้สองช่วงเลยครับ ทั้ง AS920 กับ AS923 (The Things Network Frequency Plan)
จากข้อมูลด้านบนเนี่ย ก็ต้องอ่านกันดีๆ หลักๆก็ควรยึดตามคลื่นความถี่ที่ใช้งานและจุดประสงค์ที่ใช้งาน จากนั้นก็ดูตามหลักเกณฑ์และเงื่อนไขที่ประกาศในราชกิจจานุเบกษา และก็ยังต้องดูด้วยว่าในการประกาศนั้นมีการยกเลิกประกาศตัวเก่าไว้หรือไม่ หรือพวกประโยคที่ว่าเว้นแต่กำหนดเป็นอย่างอื่น พวกร่างกับการรับฟังความคิดเห็นสาธารณะก็อาจใช้ประกอบได้ กรณีดีที่สุดหากมีข้อสงสัยในเรื่องของการใช้งานความถี่ การผลิตหรือนำเข้าเรื่องอุปกรณ์คมนาคมที่ใช้ความถี่ ก็ควรขอคำตอบจาก กสทช เป็นหลักซึ่งก็ควรทำเป็นหนังสือแจ้งไปเพื่อให้ กสทช ตอบกลับว่าสามารถทำได้ หรือไม่สามารถทำได้ หรือให้ยึดตามข้อกำหนดไหน
Scenario
#define SCK 5 // GPIO5 -- SX127x's SCK #define MISO 19 // GPIO19 -- SX127x's MISO #define MOSI 27 // GPIO27 -- SX127x's MOSI #define SS 18 // GPIO18 -- SX127x's CS #define RST 14 // GPIO14 -- SX127x's RESET #define DI0 26 // GPIO26 -- SX127x's IRQ(Interrupt Request) #define BAND 915.2E6 //you can set band here directly,e.g. 868E6,915E6
ติดตั้ง Arduino core for ESP32 WiFi chip
ขั้นตอนนี้อาจแตกต่างไปจากตอนที่เราติดตั้ง ESP8266, ATTiny85 หรือ STM32 F103C Series ไปจากตอนก่อนหน้านี้ ซึ่ง Source ที่ใช้ในการติดตั้งมีอยู่ 2 แหล่งด้วยกันคือ
จาก Espressif ผู้ผลิตชิป ESP32
https://github.com/espressif/arduino-esp32#installation-instructions
จาก Heltec ผู้ผลิตบอร์ด Heltec ESP32 LoRa
https://github.com/Heltec-Aaron-Lee/WiFi_Kit_series#installation-instructions
ข้อแตกต่างระหว่างสองบอร์ดนี้ก็คือ ถ้า Clone มาจาก Heltec จะมี Library LoRa มาด้วยซึ่งก็เป็นตัวที่โมมาจากของคุณ Sandeep Mistry แต่ถ้ากรณีมีการ Update ก็จะไม่ถูก Update ผ่านทาง Library manager ต้องคอยตามจากทาง Heltec เอง ผมเลยเลือก Clone มาจากทาง Espressif แล้วทำการติดตั้ง LoRa Library เอง ส่วนวิธีการติดตั้งก็ขึ้นกับ OS ของแต่ละท่านที่ใช้อยู่ หลังจากตัดสินใจเลือกทางได้แล้วก็อาศัยลิงค์ข้างบนนี่แหละครับทำตาม Instruction ในแต่ละ OS ได้เลย
มาดูในส่วนของ Code ที่จะใช้ทดสอบระยะกันบ้าง ตัวโปรแกรมก็จะแบ่งเป็นสองชุดคือฝั่งส่งและฝั่งรับ ฝั่งส่งก็ Count Number แล้วส่งออกไปเรื่อยๆ ส่วนฝั่งรับนั้นเนื่องด้วยไม่มี GPS Module ก็เลยอาศัย GPS Stream จาก Blynk มาใช้ในการเก็บค่าพิกัดแทน เพื่อที่จะได้มา plot ดูว่าได้ระยะเป็นอย่างไรบ้าง ซึ่งถ้าใช้บอร์ดที่ใช้ชิป SX1278 ในส่วนของ Band จะกำหนดเป็น 433.175MHz และถ้าเป็นบอร์ดที่ใช้ชิป SX1276 ในส่วนของ Band จะกำหนดเป็น 923.2MHz นอกนั้นไม่มีอะไรแตกต่าง ส่วนใครที่จะทดสอบโดยที่ไม่ได้สนใจเรื่องของ พิกัดก็ตัด code ในส่วนของ Blynk ออกได้ครับ
Code for Sender:
#include <SPI.h> #include <LoRa.h> #include "SSD1306.h" int counter = 0; // GPIO5 -- SX1278's SCK // GPIO19 -- SX1278's MISO // GPIO27 -- SX1278's MOSI // GPIO18 -- SX1278's CS // GPIO14 -- SX1278's RESET // GPIO26 -- SX1278's IRQ(Interrupt Request) //OLED pins to ESP32 0.96OLEDGPIOs via this connecthin: //OLED_SDA -- GPIO4 //OLED_SCL -- GPIO15 //OLED_RST -- GPIO16 SSD1306 display(0x3c, 4, 15); #define SS 18 #define RST 14 #define DI0 26 #define BAND 433.175E6 //915E6 void setup() { Serial.begin(115200); pinMode(25, OUTPUT); //Send success, LED will bright 1 second while (!Serial); pinMode(16, OUTPUT); digitalWrite(16, LOW); // set GPIO16 low to reset OLED delay(50); digitalWrite(16, HIGH); // while OLED is running, must set GPIO16 in high Serial.println("LoRa Sender"); SPI.begin(5, 19, 27, 18); LoRa.setPins(SS, RST, DI0); if (!LoRa.begin(BAND)) { Serial.println("Starting LoRa failed!"); while (1); } Serial.println("LoRa Initial OK!"); // Initialising the UI will init the display too. display.init(); display.flipScreenVertically(); display.setFont(ArialMT_Plain_10); } void loop() { Serial.print("Sending packet: "); Serial.println(counter); // send packet LoRa.beginPacket(); LoRa.print("hello "); LoRa.print(counter); LoRa.endPacket(); display.clear(); display.setTextAlignment(TEXT_ALIGN_LEFT); display.drawString(10, 5, "Sending:"); display.drawString(10, 20, "hello " + String(counter)); // write the buffer to the display display.display(); counter++; digitalWrite(25, HIGH); // turn the LED on (HIGH is the voltage level) delay(1000); // wait for a second digitalWrite(25, LOW); // turn the LED off by making the voltage LOW delay(1000); // wait for a second delay(5000); }
Code for Receiver:
#include <SPI.h> #include <LoRa.h> #include "SSD1306.h" /* Comment this out to disable prints and save space */ #define BLYNK_PRINT Serial #include <WiFi.h> #include <WiFiClient.h> #include <BlynkSimpleEsp32.h> char auth[] = "xxxxxxxxx"; //blynk auth code // Your WiFi credentials. // Set password to "" for open networks. char ssid[] = "xxx xxxx"; char pass[] = "xxxx"; float lat; float lon; // GPIO5 -- SX1278's SCK // GPIO19 -- SX1278's MISO // GPIO27 -- SX1278's MOSI // GPIO18 -- SX1278's CS // GPIO14 -- SX1278's RESET // GPIO26 -- SX1278's IRQ(Interrupt Request) //OLED pins to ESP32 0.96OLEDGPIOs //OLED_SDA -- GPIO4 //OLED_SCL -- GPIO15 //OLED_RST -- GPIO16 SSD1306 display(0x3c, 4, 15); #define SS 18 #define RST 14 #define DI0 26 #define BAND 433.175E6 //915E6 WidgetMap myMap(V1); BLYNK_WRITE(V0) { GpsParam gps(param); // Print 6 decimal places for Lat, Lon lat = String(gps.getLat(),6).toFloat(); lon = String(gps.getLon(),6).toFloat(); Serial.print("Lat: "); Serial.println(lat,7); Serial.print("Lon: "); Serial.println(lon,7); Serial.println(); } void setup() { Serial.begin(115200); while (!Serial); pinMode(16, OUTPUT); digitalWrite(16, LOW); // set GPIO16 low to reset OLED delay(50); digitalWrite(16, HIGH); // while OLED is running, must set GPIO16 in high Serial.println("LoRa Receiver"); SPI.begin(5, 19, 27, 18); LoRa.setPins(SS, RST, DI0); if (!LoRa.begin(BAND)) { Serial.println("Starting LoRa failed!"); while (1); } Serial.println("LoRa Initial OK!"); // Initialising the UI will init the display too. display.init(); display.flipScreenVertically(); display.setFont(ArialMT_Plain_10); Blynk.begin(auth, ssid, pass); myMap.clear(); } void loop() { String tmp_string, tmp_rssi; // try to parse packet int packetSize = LoRa.parsePacket(); if (packetSize) { // received a packet Serial.print("Received packet '"); // read packet while (LoRa.available()) { //Serial.print((char)LoRa.read()); tmp_string += (char)LoRa.read(); } Serial.print(tmp_string); tmp_rssi = LoRa.packetRssi(); // print RSSI of packet Serial.println("' with RSSI " + tmp_rssi); //Serial.println(LoRa.packetRssi()); display.clear(); display.setTextAlignment(TEXT_ALIGN_LEFT); display.drawString(10, 0, "Received:"); display.drawString(10, 15, tmp_string+" RSSI: "+tmp_rssi); display.drawString(10, 30, String(lat,7)); display.drawString(10, 45, String(lon,7)); // write the buffer to the display display.display(); //BLYNK_WRITE(V0); Blynk.syncVirtual(V0); myMap.location(1, lat, lon, "value"); } tmp_string =""; tmp_rssi = ""; Blynk.run(); }
LoRa Range Test Result: 433.175MHz vs 923.2MHz
ในความโชคร้ายยังพอมีโชคดีอยู่บ้าง ถึงแม้ตัว Heltec ESP32 LoRa SX1276 ที่ใช้งานย่าน 915MHz จะเสียแต่ก็ได้บอร์ดใหม่มาทันพอให้ทดสอบ ซึ่งเป็นของ TTGO เป็น ESP32 LoRa SX1276 เหมือนกันเพียงแต่แค่ไม่มี OLED มาด้วยเท่านั้น ฉะนั้นการวัดระยะ และค่า RSSI ก็เลยอาศัยอ่านค่า GPS จาก App Blynk บนโทรศัพท์มือถือแทน
ผลการทดสอบระยะของแต่ละความถี่ที่ใช้ก็ตามรูปด้านบนเลยครับ ซึ่งถ้าเอาระยะหวังผลไกลสุดซึ่งอยู่ใน Line of Sight ก็จะอยู่ที่ประมาณ 700-750 เมตรสำหรับการรับ-ส่งที่ความถี่ 923.2MHz ส่วนคลื่น 433.175MHz ระยะหวังผลก็จะอยู่ที่ประมาณ 600 เมตร ทำให้การใช้งาน LoRa นั้นค่อนข้างสมชื่อ Long Range เลย เพราะนี่คือใช้สายอากาศที่แถมมา ถ้าใช้สายอากาศดีๆ และตั้งเสาสูงน่าจะไปได้ไกลกว่านี้ แต่ทั้งนี้ทั้งนั้นกำลังส่งก็ไม่ควรเกิน กสทช กำหนดนะครับ ซึ่งเคสที่เขียนนี้ “สำหรับการทดลองเท่านั้น” เพราะจาก Code ได้มีการกำหนด TX Power ไว้ที่ 17dBm บวกกับเสาที่แถมมาน่าจะไม่เกิน +3dBm ตีกลมๆก็ 20dBm หรือ 100 milliwatts แล้ว (แต่ส่วนตัวคิดว่าไม่น่าจะถึง น่าจะมี loss เยอะ ถ้าอยากรู้จริงๆคงต้องเอาอุปกรณ์มาวัด)
Painlessmesh Bridge with Lora
เกริ่นมาซะยาวสุดๆก่อนที่จะเข้าเรื่องของเรากันจริงๆในการใช้ Painlessmesh สร้าง Mesh Network และ Bridge ข้อมูลผ่าน LoRa กัน ซึ่ง Configuration ก็จะเป็นลักษณะดัง diagram ด้านล่างนี้ครับ โดยมี Heltec ESP32 Lora SX1278 เป็นตัว Bridge เพื่อส่งข้อมูลไปยัง MQTT Server
เพิ่งเห็นว่ามี Dependency Library เพิ่มเข้ามาสำหรับ Painlessmesh อีกตัวคือ AsyncTCP สำหรับ ESP32 และ ESPAsyncTCP สำหรับ ESP8266 ถ้าใคร Update เป็น version ล่าสุดของ Painlessmesh ก็อย่าลืมติดตั้งเพิ่มเข้าไปนะครับ
สำหรับ ESP32 https://github.com/me-no-dev/AsyncTCP สำหรับ ESP8266 https://github.com/me-no-dev/ESPAsyncTCP
Nodemcu + DHT22 (mcu-t1, mcu-t2, mcu-t3)
Code:
#include "painlessMesh.h" #include "DHT.h" #define DHTPIN D4 #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 10 seconds Task myLoggingTask(10000, TASK_FOREVER, []() { float h = dht.readHumidity(); float t = dht.readTemperature(); DynamicJsonBuffer jsonBuffer; JsonObject& msg = jsonBuffer.createObject(); msg["nodename"] = "mcu-t1"; //change for identify for the node that send data mcu-t1 to mcu-t3 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() { 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() { // put your main code here, to run repeatedly: 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"); } Serial.printf("Handled from %u msg=%s\n", from, msg.c_str()); } }
Heltec ESP32 SX1276 (Server Node + LoRa)
Code:
#include "painlessMesh.h" #include <SPI.h> #include <LoRa.h> #include "SSD1306.h" // GPIO5 -- SX1278's SCK // GPIO19 -- SX1278's MISO // GPIO27 -- SX1278's MOSI // GPIO18 -- SX1278's CS // GPIO14 -- SX1278's RESET // GPIO26 -- SX1278's IRQ(Interrupt Request) //OLED pins to ESP32 0.96OLEDGPIOs : //OLED_SDA -- GPIO4 //OLED_SCL -- GPIO15 //OLED_RST -- GPIO16 #define MESH_PREFIX "HelloMyMesh" #define MESH_PASSWORD "hellomymeshnetwork" #define MESH_PORT 5555 SSD1306 display(0x3c, 4, 15); #define SS 18 #define RST 14 #define DI0 26 #define BAND 433.175E6 //915E6 painlessMesh mesh; // Send my ID every 10 seconds to inform others Task logServerTask(10000, 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() { Serial.begin(115200); pinMode(25, OUTPUT); //Send success, LED will bright 1 second while (!Serial); pinMode(16, OUTPUT); digitalWrite(16, LOW); // set GPIO16 low to reset OLED delay(50); digitalWrite(16, HIGH); // while OLED is running, must set GPIO16 in high Serial.println("LoRa PainlessMesh Server"); SPI.begin(5, 19, 27, 18); LoRa.setPins(SS, RST, DI0); if (!LoRa.begin(BAND)) { Serial.println("Starting LoRa failed!"); while (1); } Serial.println("LoRa Initial OK!"); // Initialising the UI will init the display too. display.init(); display.flipScreenVertically(); display.setFont(ArialMT_Plain_10); display.clear(); display.setTextAlignment(TEXT_ALIGN_LEFT); display.drawString(10, 5, "Mesh Server Node:"); display.display(); mesh.setDebugMsgTypes( ERROR | CONNECTION | S_TIME ); mesh.init( MESH_PREFIX, MESH_PASSWORD, MESH_PORT, STA_AP, WIFI_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(); } void receivedCallback( uint32_t from, String &msg ) { String tmp_string = msg.c_str(); Serial.printf("logServer: Received from %u msg=%s\n", from, tmp_string); Serial.println(""); Serial.println("Sending LoRa packet: "+tmp_string); //เมื่อได้รับข้อความจากใน mesh network ก็ส่งต่อผ่านไปยัง LoRa LoRa.beginPacket(); LoRa.print(tmp_string); LoRa.endPacket(); display.clear(); display.setTextAlignment(TEXT_ALIGN_LEFT); display.drawString(10, 5, "Sending: "+tmp_string.substring(13,19)); display.drawString(10, 20, "Temp: "+tmp_string.substring(49,55)); display.drawString(10, 35, "Humid: "+tmp_string.substring(69,75)); // write the buffer to the display display.display(); }
Heltec ESP32 SX1276 (Gateway Node + LoRa)
Code:
#include <SPI.h> #include <LoRa.h> #include "SSD1306.h" #include <WiFi.h> #include <WiFiClient.h> #include <PubSubClient.h> const char* ssid = "xxxWiFi-SSID"; const char* password = "xxxWiFi Password"; const char* mqtt_server = "xxx.xxx.xxx.xxx"; //<-- IP หรือ Domain ของ Server MQTT long lastMsg = 0; char msg[100]; int value = 0; WiFiClient espClient; 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(); } PubSubClient client(mqtt_server, 1883, callback, espClient); // GPIO5 -- SX1278's SCK // GPIO19 -- SX1278's MISO // GPIO27 -- SX1278's MOSI // GPIO18 -- SX1278's CS // GPIO14 -- SX1278's RESET // GPIO26 -- SX1278's IRQ(Interrupt Request) //OLED pins to ESP32 0.96OLEDGPIOs //OLED_SDA -- GPIO4 //OLED_SCL -- GPIO15 //OLED_RST -- GPIO16 SSD1306 display(0x3c, 4, 15); #define SS 18 #define RST 14 #define DI0 26 #define BAND 433.175E6 //915E6 void setup() { Serial.begin(115200); while (!Serial); pinMode(16, OUTPUT); digitalWrite(16, LOW); // set GPIO16 low to reset OLED delay(50); digitalWrite(16, HIGH); // while OLED is running, must set GPIO16 in high Serial.println("LoRa Receiver"); SPI.begin(5, 19, 27, 18); LoRa.setPins(SS, RST, DI0); if (!LoRa.begin(BAND)) { Serial.println("Starting LoRa failed!"); while (1); } Serial.println("LoRa Initial OK!"); // Initialising the UI will init the display too. display.init(); display.flipScreenVertically(); display.setFont(ArialMT_Plain_10); setup_wifi(); client.connect("ESP32Gateway", "joe1", "joe1"); client.setCallback(callback); client.subscribe("command"); } void setup_wifi() { delay(10); // We start by connecting to a WiFi network Serial.println(); Serial.print("Connecting to "); Serial.println(ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP()); } void reconnect() { // Loop until we're reconnected while (!client.connected()) { Serial.print("Attempting MQTT connection..."); // Attempt to connect if (client.connect("ESP32Gateway")) { Serial.println("connected"); // Once connected, publish an announcement... client.publish("outTopic", "hello world"); // ... and resubscribe client.subscribe("command"); } else { Serial.print("failed, rc="); Serial.print(client.state()); Serial.println(" try again in 5 seconds"); // Wait 5 seconds before retrying delay(5000); } } } void loop() { String tmp_string, tmp_rssi; if (!client.connected()) { reconnect(); } client.loop(); // try to parse packet int packetSize = LoRa.parsePacket(); if (packetSize) { // received a packet Serial.print("Received packet '"); // read packet while (LoRa.available()) { //Serial.print((char)LoRa.read()); tmp_string += (char)LoRa.read(); } Serial.print(tmp_string); tmp_rssi = LoRa.packetRssi(); // print RSSI of packet Serial.println("' with RSSI " + tmp_rssi); display.clear(); display.setTextAlignment(TEXT_ALIGN_LEFT); display.drawString(10, 0, "Received:"); display.drawString(10, 15, "From: " + tmp_string.substring(13, 19) + " RSSI: " + tmp_rssi); display.drawString(10, 30, "Temp: " + tmp_string.substring(49, 55)); display.drawString(10, 45, "Humid: " + tmp_string.substring(69, 75)); // write the buffer to the display display.display(); tmp_string.toCharArray(msg, 100); Serial.print("Publish message: "); Serial.println(msg); client.publish("env", msg); //ส่งข้อความ Temp + Humidity ออกไปที่ Topic "env" } tmp_string = ""; tmp_rssi = ""; }
ผลลัพท์ที่ได้
ถ้าทำตามขั้นตอนที่ว่ามาทั้งหมดได้ ถึงบรรทัดนี้แล้วผลที่ได้ก็จะเป็นอย่างในคลิปด้านล่างนี้แหละครับ mcu-t1, mcu-t2, mcu-t3 จะอ่านค่าจาก DHT22 แล้วส่งผ่าน Mesh Network ไปยัง Server Node: Heltec ESP32 LoRa SX1278 จากนั้นเมื่อ Server Node ได้รับข้อความผ่านทาง Mesh Network ก็เอาข้อความนั้นส่งต่อผ่านไปยัง LoRa
ในอีกฟากหนึ่ง Gateway Node: Heltec ESP32 LoRa SX1278 เมื่อได้รับข้อความจาก LoRa ก็จะเอาข้อความนั้น Publish ต่อไปยัง MQTT Server ที่เราได้สร้างกันไว้ตั้งแต่ตอนที่ 3.5 ถ้าดูจากคลิปวีดีโอจะเห็นว่าระยะเวลาที่ใช้นั้นค่อนข้างเร็วมาก ซึ่งหลังจากนี้การจะนำข้อมูลจากภายใน Mesh Network ไปใช้งานก็ง่ายแล้ว จะเขียนลง Time Series Database เพื่อเอาไปแสดงผล หรือวิเคราะห์ข้อมูลต่อก็ไม่ยากแล้ว
สรุป
เนื้อหาในตอนนี้จะเน้นในเรื่องของ Data Communication อีกรูปแบบหนึ่งนั่นก็คือ LoRa ในการนำมาใช้งานร่วมกับ Mesh Network ซึ่งอย่างที่เกริ่นไปตอนแรกอะครับ ถ้าผ่านตอนที่ 3 มาแล้วก็ขึ้นอยู่กับเราว่าจะไป bridge กับอะไรเพื่อส่งต่อข้อมูลภายใน Mesh Network ไปยังอีก Network หนึ่ง ถ้าตัว Server Node ของเราต่อเข้ากับ Ethernet Module ENC28J60 ก็สามารถเป็น Bridge ในตัวมันเองได้เลยโดยส่งข้อมูลผ่านสาย Lan ก็ได้ ดังนั้นที่เขียนมายาวหลายตอนมากก็เพื่ออยากให้เข้าใจหลักการณ์ก่อน ที่เหลือก็จะขึ้นอยู่กับการประยุกต์ใช้ให้เหมาะสมนั่นเองครับ ใช่ว่าจำเป็นจะต้องใช้งาน Mesh Network สำหรับงาน IoT ทุกเคส หรือว่าจำเป็นต้องใช้งาน LoRa เพื่อให้ได้ระยะไกลๆ บางครั้งต่อพ่วงอุปกรณ์ของเราเข้ากับ GPRS/LTE Module ก็อาจจะทำให้ไปได้ไกลกว่าถึงชายขอบของผู้ให้บริการมือถือได้เลยด้วยซ้ำ ไกลกว่า NB-IoT ตอนนี้ด้วย
ส่วนในตอนหน้าที่น่าจะเป็นตอนสุดท้ายของ Series เรื่อง ESP8266/ESP32 กับ Painlessmesh ก็จะเป็นตอนปลีกย่อยละ เพราะในตอนที่ 3.5 ได้สอนในเรื่องของการติดตั้ง MQTT Server บน Google Cloud ไปแล้ว ก็คงปิด Series ด้วยการใช้งาน Telegraf, InfluxDB และ Grafana ไปเลย เพื่อที่จะทำให้ข้อมูลที่ไหลมาจาก Sensor Node ต่างๆ ไปอยู่ในรูปของ Data Visualization ที่สวยงามและเข้าใจง่าย
เรื่องเล่าท้ายตอน
ตอนนี้เป็นตอนที่ใช้พลังงานและเวลาในการเขียนเยอะมากจริงๆ ทั้งเรื่องการรออุปกรณ์ งานที่ทำอยู่ คลาสเรียนที่เพิ่มเข้ามา ต้องเขียนสารนิพนธ์ด้วย ยังไม่รวมถึงวันศุกร์ที่จะถึงนี้จะมีการประชุมผู้ถือหุ้น อีก T_T แต่เอาจริงๆเขียนเล่าเรื่องพวกนี้ก็สนุกดีเหมือนกันได้ทบทวนสิ่งที่ตัวเองได้ทดลองทำมา มีคนถามว่าเขียนแล้วได้อะไร อย่างแรกเลยก็คงเอาไว้เป็น Reference เพื่อไว้ทบทวนนั่นแหละครับเพราะไม่งั้นผ่านไปนานๆจำไม่ได้แน่ๆว่าตอนนั้นเราทำยังไง ขั้นตอนไหนก่อนหลัง แล้วก็เมื่อมีคนรู้เรื่องพวกนี้มากๆเราก็จะสนุกมากขึ้น มีคนถาม มีคนศึกษา มีคนทดลองต่อ บางคนเอาไปต่อยอดแล้วก็มาพูดคุยกัน เพราะจริงๆแล้วเราไม่ได้รู้ทุกๆเรื่อง บางเรื่องที่เขียนผมอาจจะเขียนไม่ถูกก็ได้ หรือบางท่านหลังไมค์หัวข้อมาให้เขียนก็ถ้ามีโอกาสก็จะไปหาข้อมูลมาแชร์กัน วันนี้ก็ขอจบตอนนี้ก่อนละกันครับ
ขอบคุณที่เขียนครับ 😀
Congratulations and thank you for sharing !!!
I will try to replicate and then use an ultrasonic sensor
ขอบคุณสำหรับบทความที่ดี
ได้อ่านบทนี้ถือว่ามีค่ามาก ผมกำลังออกแบบ ระบบวัดความชื้นในสวนกล้วยไม้ มีพื้นที่เกือบ 20 ไร่ ปัญหาของเราเริ่มจากระยะ wifi ที่ไปไม่ถึง mcu ปลายสวนคิดมานานว่าจะแก้อย่างไร มาบรรเจิดมากที่ได้อ่านเกี่ยวกับ การใช้งาน LoRa ซึ่งวิเคราะห์แล้วว่าถือเป็นทางออกที่สวยงามอย่างยิ่งครับ รวมทั้งประหยัดต้นทุนด้วย
ไม่ทราบว่า..จะนำ Lora มาทำในส่วน Mesh network แทนที่ wifi จะเป็นไปได้หรือเปล่าครับ???..
ได้ครับ แต่จะไม่ค่อยเหมาะ เพราะมันจะจองข่องสัญญาณการส่งตลอดเวลา ลองค้น lora mesh ดูครับ
ขอบคุณสำหรับบทความดีๆครับ ตอนนี้ทดลองทำตาม Node ทั้ง 3 ตัว สามารถส่งค่ามายัง Gateway ได้แล้ว ทีนี้อยากนำค่าอุณหภูมิที่ node ทั้งสามตัวส่งมาขึ้นเว็บแสดงผล UI เป็นตัว Gauge โดยเป็นคนละตัวกัน พอมีคำแนะนำในการเขียนโค้ดส่วนนี้บ้างมั้ยครับ ทดลองแล้วคือค่าอุณหภูมิจะถูกส่งเข้ามาที่ Gauge ตัวเดียวกันตลอด ไม่รู้จะดึงค่าทั้ง 3 ตัวแยกออกจากกันยังไง เพราะเหมือนว่าจะส่งมาตำแหน่งเดิมสลับๆกันครับ
ก็ต้อง Identify ให้ได้ว่า Node3ตัวที่ว่า อาจมี ID ที่ต่างกัน ฉะนั้นข้อมูลที่ส่งมาก็ควรมี ID ของ Node ติดมาด้วย พอเอาไปแยกสแกน ก็แค่เขียน Code Filter ในการเลือกแสดงของแต่ละ Node ID ก็น่าจะได้ครับ