Thanapon Tapala

Backend Developer

Embedded Developer

Smart Farmer

Maker

Thanapon Tapala

Backend Developer

Embedded Developer

Smart Farmer

Maker

Blog Post

[LoRa] สร้าง single channel gateway [TTN] ด้วย Raspberry Pi + RFM95w

January 21, 2019 Arduino, LoRa, RaspberryPi
[LoRa] สร้าง single channel gateway [TTN] ด้วย Raspberry Pi + RFM95w

What is LoRa ?

LoRa ย่อมาจาก Long Range เป็นรูปแบบการสื่อสารไร้สายโดยใช้เทคนิคที่เรียกว่า Spread spectum modulation ที่ถูกพัฒนาโดย Semtech Corporation ซึ่งข้อดีก็คือ สามารถส่งได้ในระยะไกล(หน่วยกิโลเมตร)และใช้พลังงานต่ำ แต่ก็ตามมาด้วยการส่งที่มี bitrate น้อยเหมือนกันครับ

What is LoRaWAN ?

ความหมายของ LoRaWAN เป็นรูปแบบการสื่อสารโดยใช้พื้นฐานจาก LoRa นั้นเอง หรือที่เรียกว่า LoRaWAN Protocol ครับ

เมื่อพูดถึง LoRaWAN นั้น ก็จะมีข้อเกี่ยวเนื่องกับการออกแบบระบบสำหรับ IoT ซึ่งจะมีทั้งหมด 4 ส่วนด้วยกัน

1.End-Device หรือ Node เป็นอุปกรณ์ Embedded Device ที่เชื่อมต่อกับ LoRa Module อย่างพวก Arduino เชื่อมต่อกับ RFM95w

2.Gateway เป็นอุปกรณ์ที่ทำหน้าที่รับ package ข้อมูลที่ส่งมาจาก End-Device แล้วทำการ forward ต่อไปยังส่วนของ Network Server ซึ่งจะเป็นในรูปแบบ network protocol

3.Network Server ในส่วนนี้เมื่อได้รับข้อมูลจาก Gateway แล้ว server จะมีหน้าที่จัดการ Package ที่มาจาก End-Device ซึ่งอาจจะเป็นการคัดกรอง package, Authentication และการจัดการข้อมูลต่างๆ

4.Application server จะเป็นที่ส่วนที่นำข้อมูลไปประยุกต์เข้ากับโปรเจคต่างๆ เช่นอาจจะเป็น Machine Learning หรือใช้ข้อมูลสำหรับการตัดสินใจในการรดน้ำต้นไม้เมื่อข้อมูลที่ได้รับเป็นอุณหภูมิในสวน

Install [TTN] Single Channel Gateway on Raspberry Pi3 + RFM95w

Overview

Raspberry Pi จะรับข้อมูลจาก End-Device แล้วทำการส่งข้อมูลต่อไปยัง The Things Network ซึ่งเป็นเครือข่ายที่ให้บริการเกี่ยวกับ IoT ซึ่งเราสามารถ integrate เข้ากับ application อื่นๆได้ด้วยไม่ว่าจะเป็น myDevices Cayenne หรือ AWS IoT

https://www.thethingsnetwork.org/

Hardware Component

Image for post

Wiring

Image for post

จากตารางข้างต้นนี้ผมเป็นการต่อสายระหว่าง RFM95w Shield และ RPi 3 นะครับ

Software Installation

  • ตั้งค่าให้ RPi ให้สามารถใช้งาน Hardware SPI
sudo raspi-config > Interfacing Options > SPI
  • ดาวน์โหลด Single Channel Gateway จาก repository
git clone http://github.com/hallard/single_chan_pkt_fwd
  • หลังจากนั้นทำการ Reboot Raspberry Pi
sudo reboot
  • เมื่อเริ่มต้นระบบใหม่แล้วให้ทำการติดตั้ง Wiring Pi เพื่อใช้ในการ compile code ที่ดาวน์โหลดมาจาก Git
sudo install wiringpi -y
  • เข้าไปยังโฟลเดอร์ single_chan_pkt_fwd เพื่อแก้ไขไฟล์ global_conf.json
cd single_chan_pkt_fwd
nano global_conf.json
{
  "SX127x_conf":
  {
    "freq": 923200000,
    "spread_factor": 7,
    "pin_nss": 10,
    "pin_dio0": 24,
    "pin_rst": 0,
    "pin_led1":5
  },
  "gateway_conf":
  {
    "ref_latitude": 0.0,
    "ref_longitude": 0.0,
    "ref_altitude": 10,

    "name": "SC Gateway",
    "email": "[email protected]",
    "desc": "Single Channel Gateway on RPI",

    "servers":
    [
      {
        "address": "router.eu.staging.thethings.network",
        "port": 1700,
        "enabled": false
      },
      {
        "address": "router.eu.thethings.network",
        "port": 1700,
        "enabled": true
      }
    ]
  }
}
  • หลังจากนั้นทำการ compile code
make
  • เมื่อ compile เสร็จแล้วเราจะได้ไฟล์ที่ใช้ในการรัน single_chan_pkt_fwd
sudo ./single_chan_pkt_fwd
  • ถ้าได้ Output ตามรูปนี้ก็ถือว่าติดตั้งเสร็จเรียบร้อยแล้วครับ
Image for post

Caution!! การต่อสายระหว่าง RFM95w กับ Raspberry Pi3 สำคัญมากครับเพราะเราจะต้อง Map ขาของ Raspberry Pi 3 ให้อยู่ในรูปแบบของ wPi ซึ่ง GPIO pin ที่เราใช้ปกตินั้นจะเป็นในรูปแบบของ BCM

Image for post

Configure a gateway on The Things Network

  • ล็อกอินเข้าไปยัง https://console.thethingsnetwork.org/ [จำเป็นต้อง register นะครับ]
  • หลังจากนั้นเลือก Gateway ซึ่งเราจะต้องทำการ register gateway นะครับ
Image for post
  • กำหนด parameter สำหรับ Gateway ของเรา
    Gateway EUI คือ Gateway ID ที่เราได้จากการรันไฟล์ single_chan_pkt_fwd
    Frequency Plan คือคลื่นความถี่ที่สามารถใช้ได้ในแต่ละประเทศ ในที่นี้ให้กำหนดเป็น Asia 920–923MHz
Image for post
Image for post
  • หลังจากนั้นก็กด Register Gateway เราก็จะได้ข้อมูล overview ของ gateway เราครับ
Image for post

Add Application and Register Device

  • เริ่มต้นทำการสร้าง application ในหน้า console ของ The Things Network
Image for post
  • หลังจากนั้นกำหนดค่า parameter ที่ต้องการซึ่งจะได้แบบนี้ครับแล้วก็กด Add application
Image for post
  • เมื่อสร้างเสร็จแล้วเราจะได้หน้าต่าง overview ของ application ประมาณนี้
Image for post
  • จากนั้นทำการเพิ่ม End-Device โดยกดที่ register device
Image for post
  • แล้วก็เพิ่ม parameter ตามที่เราต้องการครับ เสร็จแล้วก็กด Register
Image for post
  • เมื่อ register device เสร็จเราจะได้ overview ของ device ซึ่งหากสังเกต Activation Method จะเป็นแบบ OTAA เราจำเป็นต้องเปลี่ยนให้เป็นแบบ ABP โดยเลือก setting แล้วเลือก ABP ได้เลยครับ
Image for post
Image for post

ถึงขั้นตอนนี้แล้วฝั่งของ The Things Network ถือว่าเสร็จสิ้นแล้ว ต่อไปเราต้องไปทำฝั่งของ End-Device ซึ่งผมใช้ Arduino Promini 8MHz + RFM95w

Test Single Channel Gateway with Arduino&RFM95w

Image for post
  • ดาวน์โหลด Library[LMIC-AS923] แล้วทำการเพิ่ม Library เข้าไปใน ArduinoIDE (ผมใช้ ArduinoIDE 1.8.9)
  • กำหนดค่า Network Session Key, App Session Key และ Device Address ซึ่ง parameter พวกนี้จะได้จาก The Things Network เมื่อเรากำหนดค่าการ Activation เป็น ABP ครับ
Image for post
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>

// LoRaWAN NwkSKey, network session key
// This is the default Semtech key, which is used by the early prototype TTN
// network.
static const PROGMEM u1_t NWKSKEY[16] = {}; // Insert Network Session Key of The Device
//Example static const PROGMEM u1_t NWKSKEY[16] = { 0x00, 0xD0, 0x90, 0xEF, 0x7F, 0x1A, 0x50, 0xF3, 0xA8, 0xFA, 0xED, 0x34, 0xD3, 0x46, 0xDC, 0xDF };

// LoRaWAN AppSKey, application session key
// This is the default Semtech key, which is used by the early prototype TTN
// network.
static const u1_t PROGMEM APPSKEY[16] = {}; // Insert App Session Key of The Device
//Example static const u1_t PROGMEM APPSKEY[16] = { 0x07, 0x32, 0x00, 0xCE, 0xEF, 0x0F, 0x9A, 0x3B, 0x5F, 0xED, 0x3B, 0x1F, 0xEB, 0x88, 0x64, 0x24 };

// LoRaWAN end-device address (DevAddr)
static const u4_t DEVADDR = 0x26011900 ; // <-- Change this address for every node!

// These callbacks are only used in over-the-air activation, so they are
// left empty here (we cannot leave them out completely unless
// DISABLE_JOIN is set in config.h, otherwise the linker will complain).
void os_getArtEui (u1_t* buf) { }
void os_getDevEui (u1_t* buf) { }
void os_getDevKey (u1_t* buf) { }

static uint8_t mydata[] = "Hello, world!scsc";
static osjob_t sendjob;

// Schedule TX every this many seconds (might become longer due to duty
// cycle limitations).
const unsigned TX_INTERVAL = 60;

// Pin mapping
const lmic_pinmap lmic_pins = {
  .nss = 10,
  .rxtx = LMIC_UNUSED_PIN,
  .rst = 6,
  .dio = {2, 4, 5},
};

void onEvent (ev_t ev) {
  Serial.print(os_getTime());
  Serial.print(": ");
  switch (ev) {
    case EV_SCAN_TIMEOUT:
      Serial.println(F("EV_SCAN_TIMEOUT"));
      break;
    case EV_BEACON_FOUND:
      Serial.println(F("EV_BEACON_FOUND"));
      break;
    case EV_BEACON_MISSED:
      Serial.println(F("EV_BEACON_MISSED"));
      break;
    case EV_BEACON_TRACKED:
      Serial.println(F("EV_BEACON_TRACKED"));
      break;
    case EV_JOINING:
      Serial.println(F("EV_JOINING"));
      break;
    case EV_JOINED:
      Serial.println(F("EV_JOINED"));
      break;
    case EV_RFU1:
      Serial.println(F("EV_RFU1"));
      break;
    case EV_JOIN_FAILED:
      Serial.println(F("EV_JOIN_FAILED"));
      break;
    case EV_REJOIN_FAILED:
      Serial.println(F("EV_REJOIN_FAILED"));
      break;
    case EV_TXCOMPLETE:
      Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
      if (LMIC.txrxFlags & TXRX_ACK)
        Serial.println(F("Received ack"));
      if (LMIC.dataLen) {
        Serial.println(F("Received "));
        Serial.println(LMIC.dataLen);
        Serial.println(F(" bytes of payload"));
      }
      // Schedule next transmission
      os_setTimedCallback(&sendjob, os_getTime() + sec2osticks(TX_INTERVAL), do_send);
      break;
    case EV_LOST_TSYNC:
      Serial.println(F("EV_LOST_TSYNC"));
      break;
    case EV_RESET:
      Serial.println(F("EV_RESET"));
      break;
    case EV_RXCOMPLETE:
      // data received in ping slot
      Serial.println(F("EV_RXCOMPLETE"));
      break;
    case EV_LINK_DEAD:
      Serial.println(F("EV_LINK_DEAD"));
      break;
    case EV_LINK_ALIVE:
      Serial.println(F("EV_LINK_ALIVE"));
      break;
    default:
      Serial.println(F("Unknown event"));
      break;
  }
}

void do_send(osjob_t* j) {
  // Check if there is not a current TX/RX job running
  if (LMIC.opmode & OP_TXRXPEND) {
    Serial.println(F("OP_TXRXPEND, not sending"));
  } else {
    // Prepare upstream data transmission at the next possible time.
    LMIC_setTxData2(1, mydata, sizeof(mydata) - 1, 0);
    Serial.println(F("Packet queued"));
  }
  // Next TX is scheduled after TX_COMPLETE event.
}

void setup() {
  Serial.begin(115200);
  Serial.println(F("Starting"));

#ifdef VCC_ENABLE
  // For Pinoccio Scout boards
  pinMode(VCC_ENABLE, OUTPUT);
  digitalWrite(VCC_ENABLE, HIGH);
  delay(1000);
#endif

  // LMIC init
  os_init();
  // Reset the MAC state. Session and pending data transfers will be discarded.
  LMIC_reset();

  // Set static session parameters. Instead of dynamically establishing a session
  // by joining the network, precomputed session parameters are be provided.
#ifdef PROGMEM
  // On AVR, these values are stored in flash and only copied to RAM
  // once. Copy them to a temporary buffer here, LMIC_setSession will
  // copy them into a buffer of its own again.
  uint8_t appskey[sizeof(APPSKEY)];
  uint8_t nwkskey[sizeof(NWKSKEY)];
  memcpy_P(appskey, APPSKEY, sizeof(APPSKEY));
  memcpy_P(nwkskey, NWKSKEY, sizeof(NWKSKEY));
  LMIC_setSession (0x1, DEVADDR, nwkskey, appskey);
#else
  // If not running an AVR with PROGMEM, just use the arrays directly
  LMIC_setSession (0x1, DEVADDR, NWKSKEY, APPSKEY);
#endif

  LMIC_setupChannel(0, 923200000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);  // g-band
//  LMIC_setupChannel(1, 923400000, DR_RANGE_MAP(DR_SF12, DR_SF7B), BAND_CENTI);  // g-band
//  LMIC_setupChannel(2, 923600000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);  // g-band
//  LMIC_setupChannel(3, 923800000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);  // g-band
//  LMIC_setupChannel(4, 924000000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);  // g-band
//  LMIC_setupChannel(5, 924200000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);  // g-band
//  LMIC_setupChannel(6, 924400000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);  // g-band
//  LMIC_setupChannel(7, 924600000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);  // g-band
//  LMIC_setupChannel(8, 924800000, DR_RANGE_MAP(DR_FSK,  DR_FSK),  BAND_MILLI);     // g2-band

  // Disable link check validation
  LMIC_setLinkCheckMode(0);

  // TTN uses SF9 for its RX2 window.
  LMIC.dn2Dr = DR_SF9;

  // Set data rate and transmit power for uplink (note: txpow seems to be ignored by the library)
  LMIC_setDrTxpow(DR_SF7, 14);

  // Start job
  do_send(&sendjob);
}

void loop() {
  os_runloop_once();
}
  • หลังจากนั้นให้ map ขาของ Arduino Board กับ RFM95w
    (โปรเจคนี้ผมได้ใช้ Arduino Board ที่สร้างขึ้นมาเองสามารถดาวน์โหลดไฟล์ schematic หรือ gerber ได้ตามนี้เลยครับ iotmodules/Piglet)
Image for post
  • เมื่อเรากำหนดค่าต่างๆเสร็จแล้วก็ทำการทดสอบ Program ไปยัง Arduino Board ได้เลยครับ
Image for post
Image for post
Image for post

ข้อมูลที่เราส่งไปจะอยู่ในส่วนของ Payload ซึ่งมันจะอยู่ในรูปของ Hex ถ้าเราอยากทราบว่าถูกต้องไหมก็ให้แปลงจาก Hex to String ซึ่งก็คือคำว่า Hello, world! ที่เราส่งไปนั้นเองครับ

Activation Method (Ref: LoRA, LoRaWAN คืออะไร มารู้จักกันดีกว่า)

  1. Over-the-Air Activation ( OTAA ) เป็นกระบวนการที่ใช้ Globally Unique Identifier และมีการแชร์ key ผ่านกระบวนการ Hand shaking ในระหว่างการเชื่อมต่อ กระบวนการในขั้นตอนแรกจึงยุ่งยากกว่าแบบ ABP โดยมีขั้นตอนในการเชื่อมต่อดังต่อไปนี้
  • End-device จะส่ง Join Request ไปที่ Server โดยในข้อมูลประกอบไปด้วย Globally Unique End-Device Identifier ( DevEUI ), Application Identifier ( AppEUI ) และ Application key ( AppKey ) เพื่อขอ Session Key จาก Server
  • Server จะคำนวณ session keys เช่น NetworkSKey, AppSKey และส่ง Join Accept กลับไป
  • End-device จะได้รับ Join Accept
  • End-device ถอดข้อมูล Join Accept
  • End-device นำข้อมูล DevAddr ที่ได้รับมาเก็บไว้ในหน่วยความจำ
  • End-device ได้รับ Network Session Key และ Application Session Key

2. Activation By Rationalization ( ABP ) กระบวนการนี้จำเป็นจะต้อง share key ลง ไปที่อุปกรณ์ ในตอนผลิตหรือตอนดาวน์โหลดโปรแกรม วิธีการนี้เป็นวิธีที่สะดวกในการเชื่อมต่อเข้าระบบเน็ตเวิค เพราะ End-Device สามารถเชื่อมต่อเข้าระบบได้เลยโดยไม่ต้อง hand shaking เพื่อเชื่อมต่อ แต่ข้อเสียจะเป็นการ Locked ระบบ Network ให้ใช้ได้เฉพาะ Key นี้เท่านั้น

เท่านี้ก็เป็นอันว่าจบการทำ Single Channel Gateway บน The Things Network ครับ

Download Library: LMIC Library

Taggs: