Louie NRT Story

[나인와트] 사설 LoRa 망을 활용한 무선 원격검침 시스템 본문

에너지

[나인와트] 사설 LoRa 망을 활용한 무선 원격검침 시스템

hyeok0724.kim@gmail.com 2020. 3. 1. 12:30
반응형

작성일: 20년 2월 28일

 

Index

1. 개발 필요 부품

2. 개발 시스템 구성도

3. 무선원격 검침 서버 구조

4. 데이터 프로토콜

5. 실험 환경 구축

6. 계량기, 원격지시부, LoRa Device 데이터, 검침서버 데이터 계측 검증

7. 거리에 따른 LoRa Device와 LoRa GateWay 통신 테스트

8. 안테나 거리에 따른 데이터 송수신 성공률 결과

9. 통신 테스트 결론

10. 사용한 디바이스의 스펙

11. 계량기와 신호 분산 회로도 구성

12. 코드

 

1. 개발 필요 부품

 

2. 개발 시스템 구성도

 

3. 무선원격 검침 서버 구조

 

4. 데이터 프로토콜

 

5. 실험 환경 구축

1) 수중펌프를 이용하여 지속적으로 물을 순환 시켜 계량기의 값이 올라가도록 함 
2) 계량기에서 계측된 데이터를 원격지시부와 LoRa Device에서 수집 되는 것을 확인함 
3) 수집 된 데이터가 LoRa Gateway를 통해 서버에 올라가는 것을 확인함

 

 

6. 계량기, 원격지시부, LoRa Device 데이터, 검침서버 데이터 계측 검증

* 근거치 3일간 테스트

1) 계량기의 처음 값 확인 후 흐르는 유량에 따라 변경되는 계측 값 확인
2) 계량기의 계측 된 값에 따라 원격지시부 값 변경 되는 값 확인
3) 계량기의 계측 된 값에 따라 LoRa Device 값 변경 되는 값 확인

4) 계량기의 계측 된 값에 따라 검침 서버의 값 변경 되는 값 확인

5) 결과: 계량기 계측 값, 원격지시부 값, LoRa Device, 원격검침 서버 값 100% 일치함

 

7. 거리에 따른 LoRa Device와 LoRa GateWay 통신 테스트

1) 계량기가 주로 설치환경과 유사한 배선단자함에 LoRa Device 설치

2) LoRa GateWay를 들고 거리를 늘려가며 데이터 수신율 확인

3) 안테나와 측정 거리에 대한 관계를 알기 위해 안테나를 변경하여 실험

- 900MHz 대역의 안테나 길이 3가지 비교 -> A(45mm), B(115mm), C(125mm)

실험에 쓰일 3가지 안테나
LoRa Device가 설치된 단자함
LoRa Device를 설치한 연구실 주변환경

 

8. 안테나 거리에 따른 데이터 송수신 성공률 결과

- 특정 거리에서 데이터 성공율이 확연히 떨어지는 것을 알 수 있음

 

9. 통신 테스트 결론

1) 아파트 대상으로 100m 이하의 거리에서 통신을 하게 될 경우 가격이 비교적 저렴한 안테나A를 사용하여도 문제가 되지 않음
2) 아파트의 하나의 동에 대해서 LoRa GateWay 한 대 가지고 100세대 이상의 계량기 계측 데이터를 무선 원격으로 계측 할 수 있음

 

10. 사용한 디바이스의 스펙

1) SX1278 ESP32 0.96 OLED Module (LiLYGO TTGO Lora Series)

* 여기의 회사는 기능에 따라서 필요한 칩들을 가져다가 하나의 모듈로 만들어놓아서 판매하고 있음

* About Lilygo
- Dedicated to the development of the IOT, Making development become easier

- Release series of  open source hardware products, very from microcontroller to IOT Modules and to STEM education kits in order to make development become easier.

 

- ESP32 Feather (제조사명: ESPRESSIF)

 .Ultra-Low-Power Solution
 .Wi-Fi, BlueTooth
 .Xtensa single or Dual Core 32Bit LX6 Microprocessors
 .ROM(448kB), SRAM(520kB), SRAM in RTC(16kB), QSPI(Quad SPI)
 .Internal 8MHz oscillator
 .Internal RC oscillator
 .64-bit timers x2 , main watchdog x1
 .One RTC Timer
 .RTC Watchdog
 .GPIO x34
 .12-bit SAR ADC up to 18 channels
 .8-bit DAC x2
 .touch sensors x10
 .SPI x4
 .I2C x2
 .UART x3
 .SD/eMMC/SDIO 1 host
 .SDIO/SPI 1 Slave
 .Ethernet MAC interface with dedicated DMA and IEEE 1588 support
 .CAN 2.0
 .IR(Tx/Rx)
 .Moter PWM
 .LED PWM up to 16 channels
 .Hall Sensor

 .Secure boot
 .Flash encryption
 .1024-bit OTP, up to 768-bit for customers
 .Cryptographic hardware acceleration
  - AES, HASH(SHA-2), RSA), ECC, Random Number Generator

- sx1276 (제조사명: SEMTECH)

* 처음에 Spread Spectrum 변조 기술을 이용하여 프랑스의 Cycleo 사에 의해 개발 되었는데 SEMTECT 사에 의해 인수되었음. LoRa 관련 Hardware는 독점적으로 생산하고 있음

- CP2102 ( SILICON LABORATORIES )

* USB to UART Bridge

- 25Q32BS ( BOYAMICRO )

* Serial Peripheral Interface

2) Amazon Water Pump

3) 원격검침기(15mm)

4) 원격지시부

 

11. 계량기와 신호 분산 회로도 구성

 

12. 코드

1) Send

/*********
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/ttgo-lora32-sx1276-arduino-ide/
*********/

//Libraries for LoRa
#include <SPI.h>
#include <LoRa.h>

//Libraries for OLED Display
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

//define the pins used by the LoRa transceiver module
#define SCK 5
#define MISO 19
#define MOSI 27
#define SS 18
#define RST 14
#define DIO0 26

//433E6 for Asia
//866E6 for Europe
//915E6 for North America
#define BAND 923E6

//OLED pins
#define OLED_SDA 4
#define OLED_SCL 15 
#define OLED_RST 16
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

//ninewatt
#define SIG 34

//packet counter
long counter = 0;

// ninewatt
int state = 0;
int count0 = 0;
int count1 = 0;
int mode = 0;
char data[18] = ""; 
int pulse_temp = 999;
int mode_temp = 999;
long pulse = 105;  //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

//address
int apt = 101;  //sung-san
int dong = 108;
int ho = 1606;

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RST);

void setup() {

  //reset OLED display via software
  pinMode(OLED_RST, OUTPUT);
  digitalWrite(OLED_RST, LOW);
  delay(20);
  digitalWrite(OLED_RST, HIGH);

  //initialize OLED
  Wire.begin(OLED_SDA, OLED_SCL);
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3c, false, false)) { // Address 0x3C for 128x32
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever

  // ninewatt
  pinMode(SIG, INPUT);
  }
  
  display.clearDisplay();
  display.setTextColor(WHITE);
  display.setTextSize(1);
  display.setCursor(0,0);
  display.print("LORA SENDER ");
  display.display();
  
  //initialize Serial Monitor
  Serial.begin(115200);
  
  Serial.println("LoRa Sender Test");

  //SPI LoRa pins
  SPI.begin(SCK, MISO, MOSI, SS);
  //setup LoRa transceiver module
  LoRa.setPins(SS, RST, DIO0);
  
  if (!LoRa.begin(BAND)) {
    Serial.println("Starting LoRa failed!");
    while (1);
  }
  Serial.println("LoRa Initializing OK!");
  display.setCursor(0,10);
  display.print("LoRa Initializing OK!");
  display.display();
  delay(2000);
}

void loop() {

  while(1)
  {
    // ninewatt
    /************** 1 **************/
    if(analogRead(SIG) < 2000) state = 0;
    else state = 1;

    /************** 2 **************/
    //if(digitalRead(SIG) == 0) state = 0;
    //else if(digitalRead(SIG) == 1) state = 1;

    /************** 3 **************/
    //Serial.println(analogRead(SIG));

    //pulse += 1;
    delay(10); 
    
    if(pulse != pulse_temp)
    {
      data[0]  = '0' + (apt/100) % 10;
      data[1]  = '0' + (apt/10) % 10;
      data[2]  = '0' + (apt/1) % 10;

      data[3]  = 'A';

      data[4]  = '0' + (dong/100) % 10;
      data[5]  = '0' + (dong/10) % 10;
      data[6]  = '0' + (dong/1) % 10;

      data[7]  = '0' + (ho/1000) % 10;
      data[8]  = '0' + (ho/100) % 10;
      data[9]  = '0' + (ho/10) %10;
      data[10] = '0' + (ho/1) %10;

      data[11] = 'P';

      data[12] = '0' + (pulse/100000) % 10;
      data[13] = '0' + (pulse/10000) % 10;
      data[14] = '0' + (pulse/1000) % 10;
      data[15] = '0' + (pulse/100) % 10;
      data[16] = '0' + (pulse/10) % 10;
      data[17] = '0' + (pulse/1) % 10;

      
      //Serial.print("Sending packet: ");
      Serial.println(data);

      //Send LoRa packet to receiver
      LoRa.beginPacket();
      LoRa.print(data);
      LoRa.endPacket();

      display.clearDisplay();
      display.setCursor(0,0);
      display.println("LORA SENDER");
      display.setCursor(0,20);
      display.setTextSize(1);
      display.print("LoRa packet sent.");
      
      display.setCursor(0,30);
      display.print(data);
      
      //display.setCursor(0,40);
      //display.print("Mode:");    
      //display.setCursor(50,40);
      //display.print(mode);  
      display.display();
        
      pulse_temp = pulse;
      mode_temp = mode;
    }
    
    if(mode == 0) //Low level count
    {
      if(state == 0)
      {
        count0 += 1;
        if(count0 > 20)
        {
          count0 = 0;
          mode = 1;
        }
      }
      else count0 = 0;
    }
    
    if(mode == 1) //High level count
    {
      if(state == 1)
      {
        count1 += 1;
        if(count1 > 20)
        {
          count1 = 0;
          mode = 2;
        }
      }
      else count1 = 0;
    }

    if(mode == 2) //Falling edge check
    {
      if(state == 0)
      {
        pulse += 1;
        mode = 0;   // 수정
      }
    }
  }
}

2) Receive

/*********
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/ttgo-lora32-sx1276-arduino-ide/
*********/

//Libraries for LoRa
#include <SPI.h>
#include <LoRa.h>

//Libraries for OLED Display
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

//define the pins used by the LoRa transceiver module
#define SCK 5
#define MISO 19
#define MOSI 27
#define SS 18
#define RST 14
#define DIO0 26

//433E6 for Asia
//866E6 for Europe
//915E6 for North America
#define BAND 923E6

//OLED pins
#define OLED_SDA 4
#define OLED_SCL 15 
#define OLED_RST 16
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RST);

String LoRaData;

void setup() { 
  
  //reset OLED display via software
  pinMode(OLED_RST, OUTPUT);
  digitalWrite(OLED_RST, LOW);
  delay(20);
  digitalWrite(OLED_RST, HIGH);
  
  //initialize OLED
  Wire.begin(OLED_SDA, OLED_SCL);
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3c, false, false)) { // Address 0x3C for 128x32
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }

  display.clearDisplay();
  display.setTextColor(WHITE);
  display.setTextSize(1);
  display.setCursor(0,0);
  display.print("LORA RECEIVER ");
  display.display();
  
  //initialize Serial Monitor
  Serial.begin(115200);

  Serial.println("LoRa Receiver Test");
  
  //SPI LoRa pins
  SPI.begin(SCK, MISO, MOSI, SS);
  //setup LoRa transceiver module
  LoRa.setPins(SS, RST, DIO0);

  if (!LoRa.begin(BAND)) {
    Serial.println("Starting LoRa failed!");
    while (1);
  }
  Serial.println("LoRa Initializing OK!");
  display.setCursor(0,10);
  display.println("LoRa Initializing OK!");
  display.display();  
}

void loop() {

  //try to parse packet
  int packetSize = LoRa.parsePacket();
  if (packetSize) {
    //received a packet
    //Serial.print("Received packet ");

    //read packet
    while (LoRa.available()) {
      LoRaData = LoRa.readString();
      Serial.print(LoRaData);
      Serial.print("\n");
    }

    //print RSSI of packet
    int rssi = LoRa.packetRssi();
    //Serial.print(" with RSSI ");    
    //Serial.println(rssi);

   // Dsiplay information
   display.clearDisplay();
   display.setCursor(0,0);
   display.print("LORA RECEIVER");
   display.setCursor(0,20);
   display.print("Received packet:");
   display.setCursor(0,30);
   display.print(LoRaData);
   display.setCursor(0,40);
   display.print("RSSI:");
   display.setCursor(30,40);
   display.print(rssi);
   display.display();   
  }
}

3) Raspberry Pi

'''
/*
 * Copyright 2010-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file is distributed
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */
 '''

from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTClient
import logging
import time
import argparse
import json

#############
import serial
ser = serial.Serial("/dev/ttyUSB0", 115200, timeout=3)
#############

AllowedActions = ['both', 'publish', 'subscribe']

# Custom MQTT message callback
def customCallback(client, userdata, message):
    print("Received a new message: ")
    print(message.payload)
    print("from topic: ")
    print(message.topic)
    print("--------------\n\n")


# Read in command-line parameters
parser = argparse.ArgumentParser()
parser.add_argument("-e", "--endpoint", action="store", required=True, dest="host", help="Your AWS IoT custom endpoint")
parser.add_argument("-r", "--rootCA", action="store", required=True, dest="rootCAPath", help="Root CA file path")
parser.add_argument("-c", "--cert", action="store", dest="certificatePath", help="Certificate file path")
parser.add_argument("-k", "--key", action="store", dest="privateKeyPath", help="Private key file path")
parser.add_argument("-p", "--port", action="store", dest="port", type=int, help="Port number override")
parser.add_argument("-w", "--websocket", action="store_true", dest="useWebsocket", default=False,
                    help="Use MQTT over WebSocket")
parser.add_argument("-id", "--clientId", action="store", dest="clientId", default="basicPubSub",
                    help="Targeted client id")
parser.add_argument("-t", "--topic", action="store", dest="topic", default="lora/test01/pub", help="Targeted topic")
parser.add_argument("-m", "--mode", action="store", dest="mode", default="both",
                    help="Operation modes: %s"%str(AllowedActions))
parser.add_argument("-M", "--message", action="store", dest="message", default="Hello World!",
                    help="Message to publish")

args = parser.parse_args()
host = args.host
rootCAPath = args.rootCAPath
certificatePath = args.certificatePath
privateKeyPath = args.privateKeyPath
port = args.port
useWebsocket = args.useWebsocket
clientId = args.clientId
topic = args.topic

if args.mode not in AllowedActions:
    parser.error("Unknown --mode option %s. Must be one of %s" % (args.mode, str(AllowedActions)))
    exit(2)

if args.useWebsocket and args.certificatePath and args.privateKeyPath:
    parser.error("X.509 cert authentication and WebSocket are mutual exclusive. Please pick one.")
    exit(2)

if not args.useWebsocket and (not args.certificatePath or not args.privateKeyPath):
    parser.error("Missing credentials for authentication.")
    exit(2)

# Port defaults
if args.useWebsocket and not args.port:  # When no port override for WebSocket, default to 443
    port = 443
if not args.useWebsocket and not args.port:  # When no port override for non-WebSocket, default to 8883
    port = 8883

# Configure logging
logger = logging.getLogger("AWSIoTPythonSDK.core")
logger.setLevel(logging.DEBUG)
streamHandler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
streamHandler.setFormatter(formatter)
logger.addHandler(streamHandler)

# Init AWSIoTMQTTClient
myAWSIoTMQTTClient = None
if useWebsocket:
    myAWSIoTMQTTClient = AWSIoTMQTTClient(clientId, useWebsocket=True)
    myAWSIoTMQTTClient.configureEndpoint(host, port)
    myAWSIoTMQTTClient.configureCredentials(rootCAPath)
else:
    myAWSIoTMQTTClient = AWSIoTMQTTClient(clientId)
    myAWSIoTMQTTClient.configureEndpoint(host, port)
    myAWSIoTMQTTClient.configureCredentials(rootCAPath, privateKeyPath, certificatePath)

# AWSIoTMQTTClient connection configuration
myAWSIoTMQTTClient.configureAutoReconnectBackoffTime(1, 32, 20)
myAWSIoTMQTTClient.configureOfflinePublishQueueing(-1)  # Infinite offline Publish queueing
myAWSIoTMQTTClient.configureDrainingFrequency(2)  # Draining: 2 Hz
myAWSIoTMQTTClient.configureConnectDisconnectTimeout(10)  # 10 sec
myAWSIoTMQTTClient.configureMQTTOperationTimeout(5)  # 5 sec

# Connect and subscribe to AWS IoT
myAWSIoTMQTTClient.connect()
if args.mode == 'both' or args.mode == 'subscribe':
    myAWSIoTMQTTClient.subscribe(topic, 1, customCallback)
time.sleep(2)

# Publish to the same topic in a loop forever
loopCount = 0
while True:
    if args.mode == 'both' or args.mode == 'publish':

        response = ser.readline()
        if response != b'':
            decodeReceiveData = response.decode('ascii')

            decodeReceiveData = decodeReceiveData.replace("\n", "")

            listSplite_recData = decodeReceiveData.split('A')
            buildNum = listSplite_recData[0]
            houseNum = listSplite_recData[1].split('P')[0]
            pulseNum = listSplite_recData[1].split('P')[1]
            
            message = {}
            message['buildNum'] = buildNum
            message['houseNum'] = houseNum
            message['pulseNum'] = pulseNum

            messageJson = json.dumps(message)
            myAWSIoTMQTTClient.publish(topic, messageJson, 1)

        if args.mode == 'publish':
            print('Published topic %s: %s\n' % (topic, messageJson))
        loopCount += 1
    time.sleep(1)

4) Arduino Uno 이용해서 테스트 할 때 코드

int state = 0;
int count0 = 0;
int count1 = 0;
int mode = 0;
long data = 0;

int temp = 5;


void setup() {
  Serial.begin(9600);
  
  pinMode(A0, INPUT);
  //pinMode(2, OUTPUT); //Check LED
}

void loop() {
  while(1)
  {
    state = analogRead(A0);
    Serial.print(state);
    Serial.print("\n");
    delay(10);

    
/*
    if(state == 0)
    {
      Serial.print("@@@@@@@@@@");
      Serial.print("\n"); 
    }
    else
    {
      Serial.print("          @@@@@@@@@@");
      Serial.print("\n"); 
    }
    */
     
    if(mode != temp)
    {
      switch(mode)
      {
        case 0 : Serial.print("\nLow level counting..."); break;
        case 1 : Serial.print("OK!\nHigh level counting..."); break;
        case 2 : Serial.print("OK!\nFalling edge checking...\n"); break;
      }
      temp = mode;
    }
      
    if(mode == 0) //Low level count
    {
      if(state == 0)
      {
        count0 += 1;
        if(count0 > 33)
        {
          count0 = 0;
          mode = 1;
        }
      }
      else count0 = 0;
    }
    
    if(mode == 1) //High level count
    {
      if(state == 1)
      {
        count1 += 1;
        if(count1 > 33)
        {
          count1 = 0;
          mode = 2;
        }
      }
      else count1 = 0;
    }

    if(mode == 2) //Falling edge check
    {
      if(state == 0)
      {
        data += 1;
        mode = 3;
      }
    }

    if(mode == 3) //Display
    {
      Serial.print("                              ");
      Serial.print("Pulse : ");
      Serial.print(data);
      Serial.print(" / Usage : ");
      Serial.print(data*10);
      Serial.print(" L / Weight : ");
      Serial.print(data*0.01);
      Serial.print(" T");

      mode = 0; //return Low level count
    }
  }
  Serial.end();
}


Reference:

https://RandomNerdTutorials.com/ttgo-lora32-sx1276-arduino-ide/

 

TTGO LoRa32 SX1276 OLED with Arduino IDE | Random Nerd Tutorials

The TTGO LoRa32 SX1276 is an ESP32 development board with a built-in LoRa chip and OLED display. Learn how to send and receive LoRa packets using Arduino IDE.

randomnerdtutorials.com

https://www.espressif.com

 

Espressif Systems - Wi-Fi and Bluetooth chipsets and solutions

Welcome to Espressif's Meeting Series at CES 2019 Jan 8 - 11, 2019 CES attendees are invited to book one-to-one meetings with Espressif's representatives at CES 2019, so they can have a more structured and productive business discussion. To book a meeting,

www.espressif.com

https://www.semtech.com

 

Analog and Mixed-Signal Semiconductors | Semtech

 

www.semtech.com

https://www.silabs.com

 

Silicon Labs

Development Platform Simplicity Studio™ Get up and running quickly with precompiled demos, application notes and examples. Use advanced tools including energy profiling and network analysis to optimize your MCU and wireless systems.

www.silabs.com

http://www.boyamicro.com

 

珠海博雅科技有限公司- 首页

   珠海博雅科技有限公司于2014年12月落户珠海高新技术开发区,由资深存储器研发团队创建而成。致力于高端闪型存储器芯片研发设计和产业化,公司立足珠海,放眼世界,立志于“中国梦”的崛起,奋发创新。 博雅科技依靠国际领先的技术和勇于创新的专业团队,秉承做“中国芯”,实现“中国梦”的伟大...详细>>

www.boyamicro.com

http://hanseot.co.kr/

 

한서테크(주) – 변화와 혁신으로 고객만족 극대화

변화와 혁신으로 고객만족 극대화

hanseot.co.kr

http://www.lilygo.cn/

 

Shenzhen Xin Yuan Electronic Technology Co., Ltd

 

www.lilygo.cn

 

반응형

'에너지' 카테고리의 다른 글

[Arduino] CT Sensor  (0) 2020.03.12
[제품리뷰] EnerTalk( EncoredTech )  (0) 2020.03.11
[Wireless] LoRa  (0) 2020.02.23
[LoRa] SystemBase Inc. uLory  (0) 2020.02.04
[BlueTooth] HC-06  (0) 2020.02.03
Comments