일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- flask
- YMODEM
- 펌웨어
- raspberry
- AWS
- 플라스크
- 완속충전기
- 안드로이드
- 파이썬
- 에버온
- thread
- lambda
- Android
- 급속충전기
- 전기차충전
- 전기차충전기
- 전기차
- 보안
- 서버리스
- OCPP
- IOT Core
- 홈어시스턴트
- 디자인패턴
- dynamodb
- 라즈베리파이
- 충전기
- homeassistant
- everon
- STM32
- esp8266
- Today
- Total
Louie NRT Story
[나인와트] 사설 LoRa 망을 활용한 무선 원격검침 시스템 본문
작성일: 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)
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/
'에너지' 카테고리의 다른 글
[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 |