최종프로젝트에서 뭘 했는지 시간이 지나 까먹기 전에, 기록 차원에서 글을 남겨 본다.

 

0. 기술 스택 변경 

도저히 진도가 나가지 않는 Node Js 기반의 서버 활용을 그만두고, JSP/Servlet 기반으로 돌아가기로 했다.

간단한 기능 구현 조차도 자꾸 막히고, 시간은 촉박하게 다가오는지라 만장일치로 변경하게 되었다.

일단 전반적인 숙련도가 너무 부족해서 기능 구현을 위해선 끊임없이 배워야 하는데, 하나 해결하면 막히고 하나 해결하면 막히고...정말 미칠 뻔 했다.

 

1. 개발환경 재 셋팅

앞서 사용했던 라즈베리파이를 DB Server로 사용하고 있기에 그에 맞춰 JDBC 드라이버를 변경해서 사용했다.

드라이버로는 mariadb-java-client-2.7.4 사용, 프로젝트 내 WEB-INF/lib 폴더에 복사하여 사용.

 

연결코드는 다음과 같이 변하게 되었다.

ERD 다이어그램, device_seq로 기본 키가 잡혀있는데...이걸 신경 못 쓰고 넘어가 막판에 후회 진짜 많이 했다.

 

2. 센서별 테스트 

초기에는 ESP32 개발보드를 사용하기로 했었다.

다만 주력으로 사용하는 MQ 계열 센서, ZE08-CH2O 센서와의 라이브러리 호환성이 맞지 않고, 

Node MCU ESP-32S 보드의 경우, 보드 리버전에 따라 버그가 있는 경우가 있는데 하필 또 해당되는 보드라

업로드 할 때마다 버튼을 눌러 업로드 모드로 전환시켜줘야 코드가 들어간다고 한다.

 

그래서 결국 돌고 돌아 메가로 도착...

도착한 센서들을 같이 구매했던 도서, 구글 검색결과로 나오는 자료들과 함께 개별 테스트를 먼저 진행했다.

 

포름알데히드 센서의 경우 ZE08-CH2O라는 센서를 사용했는데

연결 방법엔 세가지가 있다..DAC, UART, DFRobot등등..

DAC 방식이 간단해서 그 방법으로 했는데, 멀쩡한 환경인데 자꾸만 Bad가 나와 UART 방식으로 선회,

또다른 문제 봉착으로 인해(코드 통합 시 측정이 진행되지 않음) 결국 DFRobot 방식 썼음.

 

3. MQTT 통신 접목

 

자꾸만 멘토분이 이야기하는 방향과 기술이 멀어저가던 중, 팀장 형이 MQTT에 대해 공부해보더니 이걸 써보자고 했다.

그래서 ESP 8266에서 돌아가게 할려고...꼬박 하루를 고생하던 중 펌웨어 업데이트를 다음과 같이 진행했더니 써먹을 수 있게 되었다.

 

https://mie96.tistory.com/entry/%EA%B4%91%EC%A3%BC%EB%B9%85%EB%8D%B0%EC%9D%B4%ED%84%B0%ED%95%99%EC%9B%90-ESP-8266-%EB%AA%A8%EB%93%88%EC%9D%84-%EC%89%BD%EA%B2%8C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-%EC%9C%84%ED%95%9C-%EA%B0%80%EC%9D%B4%EB%93%9C

 

(광주빅데이터학원) ESP-8266 모듈을 쉽게 사용하기 위한 가이드

2차 프로젝트에서 기존 1차에서 쓰던 방식 (AT 커맨드를 통해 ESP-8266 모듈을 직접 제어하고, GET 방식으로 센서 값 전송) 을 그대로 쓸려다 MQTT 통신을 써 볼려니 문제가 생겼다. ESP8266과 ESP 01 어댑

mie96.tistory.com

PubSubClient 라이브러리에서도 통신기능을 위해 와이파이 라이브러리를 가져다 쓰니 작업이 필수였다. 

 

MQTT 통신의 경우 다음과 같은 도식도를 따른다.

 

우리 시스템의 경우

아두이노에서 각종 환경센서에 대한 정보를 Sensor/total라는 topic으로 publish 하면, 

중간에 라즈베리파이가 이를 중계해주고, 웹 서버에서 topic를 구독해서 값을 받아오게끔 했다.

타 팀이 하고 있던 통합 모니터링 시스템에서는 이러한 topic를 구독만 해 오면 값을 받아올 수 있게 되는 것이다.

 

반대로 각 센서별로 제어하기 위해 마일스 함수를 사용하고, 센서별로 토픽을 각각 만들어 publish 했고, 반대로 웹에선 이러한 내용을 구독한 후, 토픽을 실어 (측정 주기) 보내면 적용되게끔 했다.

 

4. Broker Server 구축 및 통신 테스트 

 

다음과 같은 명령어를 차례대로 입력해 설치를 했다. 

$ wget http://repo.mosquitto.org/debian/mosquitto-repo.gpg.key
$ sudo apt-key add mosquitto-repo.gpg.key 
$ cd /etc/apt/sources.list.d/
$ sudo wget http://repo.mosquitto.org/debian/mosquitto-bullseye.list
$ sudo apt-get update
$ sudo apt-get install mosquitto mosquitto-clients

+로컬 네트워크 환경이면 별다른 설정 필요 없으나 외부에서 접속 시 설정파일 수정이 필수다!

 

mosquitto_sub -t sensor/#

#의 경우 해당 토픽 아래의 모든 내용을 가져오는 명령이다.

 

원래는 센서별로 토픽을 지정했으나, 분류하는 과정이 복잡하고, 오류가 자꾸 발생해서 JSON 형식으로 토픽을 보내도록 했다. 

 

mid는 원래 +"mid_1" 식으로 붙혔는데, 자바에서 파싱할 때는 괜찮았으나 통합모니터링시스템의 Node Js 환경에선

문제가 발생해 "\"mid_1\"" 식으로 수정했다.

 

 

5. 외형 조립 및 발표 준비 

 

조립이야 어렵진 않았다. 플라스틱 케이스에 폼 양면테이프 사용.

 

하지만 발표 준비가 진짜 어려웠는데, 6인의 팀원이 절반 절반 나눠 3인은 프론트엔드 (PC/모바일 웹), 3인은 IoT 파트를 담당해서 진행하다보니 프론트엔드 팀은 IoT를 아예 모르고, IoT 팀은 프론트엔드 파트에 약점이 생겨버릴 수 밖에 없었다.

 발표자가 프론트엔드 팀이였는데, 할 말이 너무 많아 IoT 팀원들이 같이 붙어서 원고 봐주고 설명해줬는데도

말을 줄이자니 이해하기 어려울 것 같고, 그렇다고 넣자니 발표자가 대본 암기하기 어려울 것 같고...

그래서 대본을 들고 올라갔는데 불이 꺼지는 바람에 대본도 보기 힘들었다고 한다. 발표자가 진짜 고생이 많았지..

 

 

6. 대망의 발표 당일

 

02:11:04 부터 보면 된다. 팀원 소개 시간 때 내 역활에 대해서 잘 써 놨는데 너무 떨린 나머지 얼버무리고 말았다. 어휴..

 

 

 

7. 후기

 

 그냥...일주일만 더 빨리 Node Js 포기하고 JSP/Servlet으로 선회할껄 그랬다. 개발 범위 설정과 시간 분배에 성공해서 

남들 프로젝트할 때 미리 끝내고 다른 조 도와주고, 발표준비했던 1차 프로젝트와는 달리, 개발 범위 설정과 시간 분배에

실패했던 것 같다. 멘토의 방향을 잘못 이해하다보니 MQTT 도입도 늦어졌고, 기능 구현도 쉽지는 않았다.

일단 예제도 없고, 심화로 갈려니 정말...머리 터지는 줄 알았다. 

사실 만든 것도 버그 투성이다. ESP8266을 쓴 건 좋은데 너무 불안정하고, 웹서버에서 넘어오는 토픽도 받아질 때도 있고 자기 맘대로 동작한다. 이것저것 코드를 다 집어넣어놔 분리시켜 라이브러리화 시키면 좋은데 C++과 자바의 클래스 구조에는 차이도 있고, 분리해보려는 데 시간이 너무 촉박했다. 

 사실 DB도 1차처럼 내가 좀 봐줬어야 하는데 (기기번호(자동증가 컬럼)으로 식별하기 보단 기기 SN로 구별하는 게 좋은데, 신경 못 쓴 사이에 기기번호로 식별하도록 모든 VO, DAO가 짜여져서 그 부분이 좀 아쉬웠음) 그러질 못했다.

 

운이 좋아 최우수상까지 수상했지만 시간관리 측면에서 참 생각이 많아진 프로젝트였다. 

 

 

 

 

실습 환경 : mariadb, 우분투 리눅스, 아파치 웹 서버, PHP 7.4

실습 보드 : ESP8266 개발 보드 + DHT22 센서

 

설정 간 문제 해결 과정

mariadb 설치 후 sudo mysql -u root 실행 시 Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2) 오류 발생

 

0. 서비스 재실행

sudo service mysql restart 또는 sudo systemctl restart mariadb 입력하여 서비스 재실행

 

1. 서비스 재실행 자체가 실패할 경우 (+삭제 후 실행시 동일증상 발생해도 마찬가지)

/var/lib/mysql 폴더 강제 삭제 후 데이터 재생성 해주기,

cd var/lib/ 입력하여 경로 이동,

sudo rm -r mysql mysql 폴더 강제 삭제(* rm 명령어로 폴더 삭제할 경우 -r 옵션을 rm 명령어 뒤에 붙힌다)

sudo mysql_install_db --user=mysql 

sudo service mysql start 또는 sudo systemctl start mariadb 입력하여 서비스 정상 동작 확인하기

 

 

GET 방식으로 웹서버 전송

받는 측 PHP 파일에서 별도의 수신 확인 응답을 보내지 않기 때문에

시리얼 모니터 상으론 에러코드 -11번이 출력되나 정상적으로 DB에 값이 저장되는 것으로 보임.

//GET 방식으로 웹서버로 전송.

#include <ESP8266HTTPClient.h>
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include "DHT.h"
#define DHTPIN D2     // Digital pin connected to the DHT sensor
#define DHTTYPE DHT22   // DHT 22  (AM2302), AM2321
DHT dht(DHTPIN, DHTTYPE);
float h;
float t;
const char* ssid = "";
const char* password = "";

//Your Domain name with URL path or IP address with path
String serverName = "<http://192.168.2.175/test2.php>";

// the following variables are unsigned longs because the time, measured in
// milliseconds, will quickly become a bigger number than can be stored in an int.
unsigned long lastTime = 0;
// Timer set to 10 minutes (600000)
//unsigned long timerDelay = 600000;
// Set timer to 5 seconds (5000)
unsigned long timerDelay = 5000;

void setup() {
  Serial.begin(115200); 
  dht.begin();
   WiFi.begin(ssid, password);
 
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
   Serial.println("Connecting to WiFi..");
  }
 
  Serial.println("Connected to the WiFi network");
}

void loop() {
  readtemphum();
  
  
  //Send an HTTP POST request every 10 minutes
  if ((millis() - lastTime) > timerDelay) {
    //Check WiFi connection status
    if(WiFi.status()== WL_CONNECTED){
      HTTPClient http;

      String serverPath = serverName + "?temp="+String(t)+"&hum="+String(h);
      
      // Your Domain name with URL path or IP address with path
      http.begin(serverPath.c_str());
      
      // Send HTTP GET request
      int httpResponseCode = http.GET();
      
      if (httpResponseCode>0) {
        Serial.print("HTTP Response code: ");
        Serial.println(httpResponseCode);
        String payload = http.getString();
        Serial.println(payload);
      }
      else {
        Serial.print("Error code: ");
        Serial.println(httpResponseCode);
      }
      // Free resources
      http.end();
    }
    else {
      Serial.println("WiFi Disconnected");
    }
    lastTime = millis();
  }
}

void readtemphum(){
    delay(2000);
   h = dht.readHumidity();
  t = dht.readTemperature();
}
  • POST 방식으로 전송하는 아두이노 코드
#include <ESP8266HTTPClient.h>
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include "DHT.h"
#define DHTPIN D2     // Digital pin connected to the DHT sensor
#define DHTTYPE DHT22   // DHT 22  (AM2302), AM2321
DHT dht(DHTPIN, DHTTYPE);
float h;
float t;
const char* ssid = "";
const char* password = "";
const char*  serverName = "<http://192.168.2.175/posttest.php>";
String url = "/post/";
unsigned long lastTime = 0;
unsigned long timerDelay = 5000;

void setup() {
  Serial.begin(115200);
  dht.begin();
  WiFi.begin(ssid, password);
  Serial.println("Connecting");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to WiFi network with IP Address: ");
  Serial.println(WiFi.localIP());

  Serial.println("Timer set to 5 seconds (timerDelay variable), it will take 5 seconds before publishing the first reading.");
}

void loop() {
  readtemphum();
  //Send an HTTP POST request every 10 minutes
  if ((millis() - lastTime) > timerDelay) {
    //Check WiFi connection status
    if (WiFi.status() == WL_CONNECTED) {
      WiFiClient client;
      HTTPClient http;
      // Specify content-type header
      http.addHeader("Content-Type", "application/x-www-form-urlencoded");
      String postData = "temp=" + String(t) + "&hum=" + String(h);
      String address = serverName + url;
      http.begin(address);
      auto httpCode = http.POST(postData);
      Serial.println("전송 데이터 :: " + postData);
      Serial.println(httpCode); //Print HTTP return code
      String payload = http.getString();
      Serial.println(payload); //Print request response payload
      // Free resources
      http.end();
    }
    else {
      Serial.println("WiFi Disconnected");
    }
    lastTime = millis();
  }
}

void readtemphum() {
  delay(2000);
  h = dht.readHumidity();
  t = dht.readTemperature();
}
  • 네트워크 연결 후 5초 뒤 전송
  • 성공한 경우 HTTP 코드 200번 반환 (성공) 연결에 문제 있을 경우 500번 반환(실패)

WEB SERVER 측 코드

https://wikidocs.net/116936 (PHP 3분 핵심 요약집 참고하여 실습)

<?php
function db_get_pdo()
{
    $host = 'localhost';
    $port = '3306';
    $dbname = 'sensor';
    $charset = 'utf8mb4';
    $username = 'ggk_test';
    $db_pw = "";
    $dsn = "mysql:host=$host;port=$port;dbname=$dbname;charset=$charset";
    $pdo = new PDO($dsn, $username, $db_pw);
    return $pdo;
}

function db_select($query, $param=array()){
    $pdo = db_get_pdo();
    try {
        $st = $pdo->prepare($query);
        $st->execute($param);
        $result =$st->fetchAll(PDO::FETCH_ASSOC);
        $pdo = null;
        return $result;
    } catch (PDOException $ex) {
        return false;
    } finally {
        $pdo = null;
    }
}

function db_insert($query, $param = array())
{
    $pdo = db_get_pdo();
    try {
        $st = $pdo->prepare($query);
        $result = $st->execute($param);
        $last_id = $pdo->lastInsertId();
        $pdo = null;
        if ($result) {
            return $last_id;
        } else {
            return false;
        }
    } catch (PDOException $ex) {
        return false;
    } finally {
        $pdo = null;
    }
}

function db_update_delete($query, $param = array())
{
    $pdo = db_get_pdo();
    try {
        $st = $pdo->prepare($query);
        $result = $st->execute($param);
        $pdo = null;
        return $result;
    } catch (PDOException $ex) {
        return false;
    } finally {
        $pdo = null;
    }
}
?>
  • 범용적인 활용을 위해 PDO 방식 사용.
<?php
require_once("inc/db_pdo.php");

$humidity = isset($_POST['hum']) ? $_POST['hum'] : null;
$temperature = isset($_POST['temp']) ? $_POST['temp'] : null;
//$login_name = isset($_POST['login_name']) ? $_POST['login_name'] : null;

// 데이터 저장
db_insert("insert into save (hum, temp) values (:humidity, :temperature)",
    array(
        'humidity' => $humidity,
        'temperature' => $temperature,
    )
);

'Tech' 카테고리의 다른 글

Rocky Linux/Oracle Linux ) Semanage 사용하기  (0) 2022.03.16

Selinux 활성화 상태에선 포트변경이 막혀있기 때문에 (그렇다고 selinux를 꺼버리는 건 보안상 좋지 않으니)

Semanage를 사용해서 포트정보를 확인하고, 정책을 변경해야 한다.

 

리눅스 버전에 따라 semanage 사용 시 command not found 메시지가 뜨는 경우가 있는데,

yum whatprovides semanage 를 입력하여 찾을 수 있다.

명령어, 혹은 패키지명으로 확인할 수 있으며, 설명까지 확인할 수 있다.

여기서 최신버전 패키지명을 복사한 후

Sudo yum install 패키지명 으로 설치하면 완료.

'Tech' 카테고리의 다른 글

아두이노 호환보드와 웹서버간 GET, POST 방식 실습하기  (0) 2022.12.20

 

학원을 다니면서 정보처리기사 실기도 응시하긴 했지만, 아무래도 학원 수업과 같이 준비하니 공부하기가 많이 벅차

결국엔 떨어지고 말았다. 50점을 간신히 넘긴거라 더 아깝기도 했고.

 

그래서 대신 SQLD를 응시하기로 마음 먹고 학원에서 책을 수령했다. 꽤 일찍 수령했지만 학원의 일정 상 바로 수업은 들어가지 않고, 1차 프로젝트가 마무리 된 후 부터 특강을 잡아주었다.

 

 

 

 

나름 일학습병행제로 회사 생활 할 때도 오라클 DB를 배웠었고, 학원 수업에서도 열심히 들어 익숙한 편이였지만, 

아무래도 MS-SQL + Oracle SQL 이 혼재된 형태인지라 책을 풀면서도 많이 어려웠다.

특히나 학원에서 수령한 책이 기출문제 문제은행 도서라 해설, 또는 기초 개념 설명이 아쉬운 게 더 많기도 했고.

 

 

 

SQLD란?

 

K-DATA(한국데이터산업진흥원)에서 주관하는 시험 중 Structured Query Language Developer의 줄임말로,

SQL 개발자를 의미한다. 상위 자격증으로는 SQLP(여기서 P는 Professional의 약자)가 있다.

2013년 부터 민간 자격증에서 국가 공인 민간 자격증으로 승격되었다고 한다.

 

응시료는 작년 기준으로 5만원 정도 냈던걸로 기억한다. 싸지 않은 금액이라 망설여질 수도 있긴 했는데..

떨어저도 공부했다 셈 치자 생각하고 응시했다. 

 

https://www.dataq.or.kr/www/main.do 

 

데이터자격시험

카드결제/계좌이체 환불 환불 요청시 즉시환불

www.dataq.or.kr

 

 

출제 문항은 다음과 같다.  

각 과목별로 과락이 존재하니 그걸 조심해야 한다. 총점 60점 이상에 과목별 40% 이상 득점을 해야 한다.

SQL은 좀 알 거 같은데 용어가 애매하다 싶으면 1과목 과락으로 탈락 할 수 있으니 준비를 해 두는 것이 좋다.

SQLD 시험을 준비하면서 정보처리기사 필/실기의 데이터베이스 과목 준비도 같이 되니 정말 좋았다.

시험 준비 과정

 

1차 프로젝트가 끝나고, 저녁시간에 조금씩 시험에 나오는 부분에 해당하는 문제를 한번 쫙 풀었다.

 

틀린게 많았고, 답지를 봐도 왜? 라는 생각이 드는 문제도 있었다.

이론도 까다로웠지만 쿼리문의 결과를 유추하는 문제나 보기 중 다른 결과가 나오는 쿼리문을 골라내는 게 제일 

힘들었다. 확실히 직접 써 보면서 결과를 확인해보는 것이 좋겠다 싶었다.

 

인터넷에 공개된 준비 자료도 한번 훑어 보았다. 

 

다행히 DB 강사 분이 다 같이 풀이를 해 주면서 열심히 받아 적었고, 따로 질문도 하면서 정리를 했다.

오라클과 MS SQL 과는 차이가 있기 때문에 그 부분을 같이 비교해서 정리도 하고, 구글 검색을 통해 정리하기도 했다.

동일한 기능을 다른 명칭으로 부르기도 하는 부분도 많았는데, 그 부분을 잘 짚어주셔서 도움이 많이 됐다.

 

 

 

시험날 당일

준비물 :: 컴퓨터용 사인팬과 검정색 볼펜, 신분증, 수험표(?)

특히 다른 건 문제가 안 되지만 실물 신분증은 꼭 챙겨 갈 것! 

이거 안챙겨서 시험장까지 왔다가 다시 돌아간 사람이 몇명 있었다..

 

전날 밤에 문제 풀다가 컨디션이 안 좋아 조금 늦게 잤고, 힘들게 일어났다.

시험지 배부를 기다리며 문제를 다시 풀기 보다는 개념이나 용어를 한번 더 봤다.

다행이도 이번 회차 시험이 그리 어렵지 않아 생각보다는 쉽다는 느낌으로 답을 썼고, 주관식 또한 다 채울 수 있었다.

 

 

 

그리고 12월 17일 결과 발표....

 

 

모델링 부분은 확실하게 답을 쓴 게 많아서 그런지 꽤나 득점을 많이 했고,

활용부분에서는 결과를 잘못 유추한 것들이 있어 저런 결과가 나온 것 같았다. 주관식도 하나였나 두개였나 잘못 쓴 게 있었고..

 

앞으로도 더 배워야 겠다는 생각이 들었다. 그래도 정말 증명할 수 있는 게 하나 생긴 건 기분이 정말 좋았다.

 

 

 

 

'학원 수업 관련 > Study - DB' 카테고리의 다른 글

DB Insert 시 특정 값 select + insert하기  (0) 2021.11.02

 

Activity란?

안드로이드의 4대 컴포넌트 (Activity, Serivce, Broadcast Receiver, Content Provider) 중 하나로

어플리케이션이 실행되었을 때 보여지는 화면을 구성하는 역활을 한다.

 

 

 

엑티비티는 다음과 같은 생명주기를 가진다. 어플리케이션이 시작되면 가장 먼저 onCreate()가 호출이 되며,

onCreate() 이전에 추가하는 경우 표시되지 않거나 오류가 발생하는 걸 확인할 수 있을 것이다.

 

하지만 하나의 엑티비티 가지고 어플리케이션을 표현하기엔 무리가 많기에 Intent를 활용하게 된다!

 

Intent란?

하나의 액티비티가 다른 액티비티를 실행시킬 수 있는 메세지 시스템을 의미.

 

특정 액티비티를 콕 찝어 시작시킬 수 있고 (명시적 Intent),

액티비티 간의 데이터 전달 목적으로 사용 할 수 있고,

특정 기능을 하는 액티비티를 시작 시킬 수 있다. (암시적 Intent),

 

메인 액티비티에 카메라와 전화, 서브 엑티비티에 표시할 텍스트를 입력받는 란을 만들고,

 

메인엑티비티에서 입력한 문자열을 표시해줄 TextView와  메인으로 가는 버튼을 만들어 주었다.

 

 

다만 카메라 위치정보 연락처 등의 민감 권한의 경우 메니페스트 파일을 다음과 같이 수정해주어야한다.

<application></application> 상단에 <uses-permission> 태그를 다음과 같이 작성해주었다.

코드는 다음과 같이 작성했다.

버튼에 클릭리스너를 달아주고, 퍼미션 여부를 확인한다. 그런 뒤 권한이 없으면 요청을 보내고, 

승인되어있다면 인자로 주어진 전화번호로 연결되도록 else 문을 작성했다.

 

서브액티비티로 가는 버튼에는 다음과 같이 클릭리스너를 달아주었다. 

getText()의 경우 타입이 Editable이기 때문에 안전하게 ToString()을 사용해서 문자열 형식으로 형변환 시켜주어야 한다.

 

서브 액티비티에서는 getIntent()를 사용해서 넘겨준 값을 받고, 

input이란 이름으로 edt_input의 문자열을 보내주었기 때문에 꺼내올땐 intent.getStringExtra를 통해

꺼내올 수 있다. 

메인 화면으로 돌아가는 버튼에는 finish();를 달아주었다. 

 

문자열이 잘 넘어가는 걸 확인할 수 있다.

민감권한의 경우 다음과 같이 권한을 요청하는 창이 뜬다. 허용된 이후에는 앱을 종료했다가 켜도 권한을 더 이상 요구하지 않는다.

 

 

 

 

 

2차 프로젝트에서 기존 1차에서 쓰던 방식

(AT 커맨드를 통해 ESP-8266 모듈을 직접 제어하고, GET 방식으로 센서 값 전송)

을 그대로 쓸려다 MQTT 통신을 써 볼려니 문제가 생겼다. 

ESP8266과 ESP 01 어댑터를 조합하여 아두이노에 연결하고,

WiFiEsp 라이브러리를 같이 사용했는데,

테스트 코드를 업로드 했을 때 unsupported firmware version / module initialize failed

등등의 오류를 많이 구경하다...해결 방법을 찾아서 정리해본다.

 

0. 펌웨어 교체 - 통신속도(보레이트) 확인

 

최근 나오는 모델은 보통 기본 통신속도가 9600으로 되어있는 경우가 많다. 

또는 아두이노에서 실습을 할려고 통신속도를 변경하는 경우가 많은데, 

 

공식적으론 115200으로 출고가 된다. 

이를 115200으로 원상복구 시켜줘야 하는데

AT+CIOBAUD=115200 또는 AT+UART_DEF=115200,8,1,0,0 명령으로 변경할 수 있다.

사전 설치된 모듈의 펌웨어에 따라 사용 할 수 있는 명령어가 다르니 확인하도록 하자!

#include <SoftwareSerial.h>

SoftwareSerial mySerial(2, 3); // RX, TX

void setup() {
  Serial.begin(9600);

  mySerial.begin(9600); // 115200인 경우 변경해주면 됨
}

void loop() { 
  if (mySerial.available()) {
    Serial.write(mySerial.read());
  }
  if (Serial.available()) {
    mySerial.write(Serial.read());
  }
}

 

코드를 업로드 한 후 라인 인코딩은 Both NL & CL, 보레이트는 9600으로 설정하고

AT를 입력하여 OK 응답이 날라오는지 확인한다.

 

아두이노와의 배선은 다음과 같이 연결한다.

https://funyphp.com/archive/arduino/20 의 배선도 참고

다만 업데이트 하기 전에 3.3V 핀 위에 있는 RESET 핀과 GND 핀을 쇼트시킨 상태에서 진행해야 성공률이 높다.

(아두이노에 내장된 MCU를 비활성화 시키는 역활을 한다.)

 

1. 펌웨어 파일 업로드하기

esp_iot_sdk_v1.5.0_15_11_27.zip
2.08MB
FLASH_DOWNLOAD_TOOLS_v2.4_150924.rar
5.44MB

상단의 두 파일을 받아 적당한 위치에 압축을 해제한다.

ESP_DOWNLOAD_TOOL_V2.4.exe 파일을 실행하면 다음과 같은 화면이 표시된다.

 

1-1 Download Path Config 설정

순서는 상관 없지만 파일 명과 주소는 반드시 맟줘주어야 한다! 

 

  bin\at\nobooteagle\flash.bin  , ADDR : 0x00000

  bin\at\nobooteagle\irom0text.bin , ADDR :  0x40000

  bin\blank.bin  , ADDR :  0xfe000

  bin\blank.bin  , ADDR : 0x7e000

추가한 뒤 좌측의 체크 표시를 모두 활성화 시켜준다.

 

1-2 기타 설정

 

SPI SPEED : 40MHz

SPI MODE : QIO OR DOUT

FLASH SIZE : DETECTED INFO 표시되는 정보대로 맞춰주기 (대부분 8Mbit이긴 함)

COM PORT & BAUDRATE : 장치관리자에 표시되는 아두이노의 시리얼 포트 / 115200으로 설정

 

START를 누른 뒤 완료되길 기다리면 된다. 설정 값에 문제가 없다면 완료 메세지를 확인할 수 있을 것이다.

 

 

 

다만...ESP-8266 모듈의 단점이 자체 전원 소비량이 많다는 것 (어디까지나 아두이노에 붙여 사용할때의 문제)

인지, 완벽히 호환되지는 않는 듯 하다. 아두이노에 종속적인 센서들을 많이 쓰는 지라

ESP 32로의 이동은 어려워서 아두이노 메가를 사용중에 있는데, 

MQTT 라이브러리와 이런저런 센서를 붙혀서 올리고 하는 과정이 꽤나 손이 많이 간다.

분명 저장 공간상으론 남아도는데 틈만 나면 커넥션이 끊겼다가 다시 붙거나 / 아예 못잡거나 / 무한정 시도하거나 이 세가지의 동작이 지속되고 있다. MQTT 로직이 섞여들어가서 그런건지는 모르겠으나 의도한 동작 때문에

코드를 그렇게 짰는데..  머리가 너무 복잡하다.

 

2차 프로젝트를 위해 DB 멘토링을 받으면서 강사분이 객체지향에 대해 야무지게 설명을 해주셔서..

복습 겸 옮겨보고자 한다.

 

 

네이버 IT 용어사전 객체 검색 결과는 다음과 같다.

 

어렵긴 하지만...요약해보면 다음과 같다. 

 

객체란?

개념적인 것들 (사람/동물/사물/주문/예약/감정....등등)을 의미한다.

 

고유한 속성을 가지고 있다.

타인이 알 수 있는 것.

사전에 등록된 명사.

 

고유한 속성이란게 애매모호하면 타인이 알 수 있는 것, 사전에 등록된 명사 둘만을 생각해도 좋다.

 

즉 "asdlkfjasdflkklsdfwe"는 객체가 될 수 없다. 타인도 모르고 사전에도 없기 때문에!

하지만, 속성과 기능이 포함된 객체를 표현하려면 다른 게 필요한데, 이럴때 Class를 사용한다.

클래스는 크게 두 가지로 나눌 수 있다.

 

사용자 정의 클래스(User Defined Class) vs 내장 클래스 (Built-In-Class)

 

이름과 나이를 가지는 학생이란 클래스를 만들어보면 다음과 같다. 

public class Student                             // 클래스 이름
{
   private String name;                          // 속성:Property:멤버필드
   private int age;

   public Student()                              // 생성자 :: 기본생성자
   {
   }
   
   public Student(String name, int age)          // 생성자 1
   {
      this.name = name;
      this.age  = age;
   }
   public void setName(String name)              // 기능:메소드:멤버함수
   {
      this.name = name;
   }
   public String getName()
   {
      return this.name;
   }
   public void setAge(int age)
   {
      this.age = age;
   }
   public String getName()
   {
      return this.name;
   }
}

 

이름과 나이 속성을 필드로 선언을 하고, 두 종류의 생성자를 호출한 모습이다.

 

생성자의 특징 : 클래스 명과 같은 이름을 가지고 반환타입이 없다. 

처음 선언된 생성자에는 매개변수가 없는데, 이는 기본 생성자라고 해서 클래스가 생성될 때(인스턴스화)

자동으로 호출이 되며, 

두 번째 생성자는 전달인자와 속성이 동일하기 때문에 자기 자신을 가르키는 this 키워드를 사용한 것이다. 

 

//객체 인스턴스화 (메모리 할당) 
Student s1 = new Student();

 

객체지향의 기초 요소 4가지

(1) 캡슐화(encapsulation) : 속성과 기능을 클래스 내부에 집어넣는 행위 (변수와 함수를 하나의 단위로 묶는 것.)
     1.1정보은닉(information hiding)
: private / protected / public (접근자 제한)

     프로그램의 세부 구현을 외부로 드러나지 않도록 모듈 내부에 감추는 것.

 

(2) 상속(inheritance)  : 클래스나 추상 클래스를 상속받는 행위

자식 클래스가 부모 클래스의 속성과 기능을 그대로 물려받는 것.

     2.1 오버로딩 vs 오버라이딩


 (3) 다형성(polymorphism)
: poly = multi, numerous; morph = form

   하나의 변수, 또는 함수가 상황에 따라 다른 의미로 해석될 수 있는 것을 의미한다.
     (3.1) subtype polymorphism   (서브타입 다형성) - 메소드 오버라이딩
     (3.2) parametric polymorphism (매개변수 다형성) - 제네릭 메소드

     (3.3) ad-hoc polymorphism (임시 다형성) - 함수 오버로딩

     (3.4) coercion polymorphism (강제 다형성) - 묵시적(자동)/명시적(수동) 형변환


 (4) 직렬화(Serializtion) :
네트워크 상에서 전송되는 코드를 바이트 코드 형태로 변환해서 전송의 효율성 높임. 

     (4.1) 역직렬화(Deserializtion) : 전송받은 바이트 코드를 다시 객체화 시키는 작업.

 

 

 

 

 

기업 연계프로젝트 참여와 팀 내 아이디어를 기반으로 하여 프로젝트를 진행할 수 있었는데,

개인적으론 반대했지만, 팀원들의 뜻에 따르기로 하고, 기업 연계 프로젝트에 참여하게 되었다.

(마음고생도 되게 많이 했다..)

 

 

 

 

요구되는 기술 스택들에 대해 별 안내 없이 들어서 더 거부감이 컸던것도 있다. (이걸 다 써야 하는거야?)

 

같은 과정을 듣는 다른 분반 팀들과 협업 형식으로 진행이 되는데,

 

크게는 대립이

 

"각자 전체 서비스를 소형화 시켜 구현하는 것이다. vs 센서 수집 시스탬과 전체 관제 시스템을 두 팀이 나눠 진행한다."

"프로젝트 발표를 한 팀이 전담하는 것은 부당하다. vs 굳이 두 팀 각자 같은 주제로 발표할 필요가 있나?"

 

라는 내용에서 발생했지만

 

"센서 수집 시스템은 A분반 팀이 진행하고, 통합 관제 시스템 및 비콘 출입관리 시스템은 B분반 팀이 진행한다."

"발표는 각자가 개발한 기능에 초점을 맞춰 진행하고, 기타 문서는 통합이 가능하면 통합하는 순으로 한다."

 

로 Fix가 되어 얼마 전 부품 선정을 마치고, 학습에 필요한 도서도 주문 완료 했다.

 

그래서....내가 맡은 웹서버 구현 및 데이터베이스 구축, 라즈베리파이 셋팅 및 환경 백업의 과정을 요약해보고자 한다.

 

 

0. 라즈베리파이 원격 접속 설정

 

자주 쓰게 될 SSH와 VNC를 활성화 해주었다. VNC가 연결되었을 때의 해상도도 별도로 설정함.

 

1. mariaDB 설정 및 node.js 최신버전 설치

 

//mariadb 설치
sudo apt-get install mariadb-server

//mariadb 보안 설정, 입력 한 후 내용 읽어보며 진행하기.
sudo mysql_secure_installation

//관리자 계정으로 접속, 모든 ip에 대한 허용 진행
sudo mysql -u root -p 설정했던 비밀번호

//계정 생성 및 비밀번호 설정
create user'사용자명'@'호스트명' identified by '패스워드';

//모든 데이터베이스에 대한 접근권한을 특정 사용자에게 부여하는 명령
GRANT ALL PRIVILEGES ON *.* TO '사용자명'@'호스트명' IDENTIFIED BY '패스워드';
//호스트 명을 *.*로 해두면 모든 IP에서 접속이 가능하다. 다만 보안설정은 필수!

flush privileges;

// mariadb 서비스 종료, 시작, 재시작
sudo service mysql stop
sudo service mysql start
sudo service mysql restart

 

다음 경로의 파일을 열어준다.

sudo nano /etc/mysql/mariadb.conf.d/50-server.cnf

 

bind-address 가 기본 값으론 127.0.0.1로 되어있는데, 이를 *로 바꾸어 준다.

Ctrl +x로 나가면서 y 입력하고 빠져나간 후 서비스를 다시 시작해준다.

 

기존 개발환경과 일치시키기 위해 nodejs의 버전을 업데이트 시켜준다. 현재 기준 최신 LTS 버전은 

16.13.1이지만, 라즈베리파이에는 이보다 이전의 버전이 설치되어 있을 것이다.

 

//설치된 nodejs의 버전 확인
node -v

//구버전 node.js 삭제
sudo apt-get remove nodejs
sudo apr-get autoremove -y

//패키지 저장소와 버전 비교
apt list | grep nodejs

//nodejs 패키지 저장소 최신화
sudo curl -sL https://deb.nodesource.com/setup_16.x | sudo -E bash -

//갱신 여부 확인
apt list | grep nodejs

//최신버전 nodejs 설치
sudo apt-get install nodejs -y

 

2. vsCode 설치 및 ufw(리눅스 방화벽) 설치, DB 및 자주 쓰는 포트 열기

//vsCode 설치 
sudo apt-get install code -y

//방화벽 설치
sudo apt-get install ufw -y

일단 확실하게 열어야 할 포트는 SSH 사용을 위한 22번 포트, VNC Viewer를 위한 5900포트, mysql에서 사용되는

3306 포트가 되겠다. (필요한 포트가 있다면 포트번호만 바꾸는 방식으로 열어줄 수 있으니 걱정 마시라..!)

기본 정책상으론 들어오는 포트는 모두 닫혀있기에 필요한 포트만 열어주는 식으로 작업했다.

//방화벽 활성화
sudo ufw enable

//방화벽 끄기
sudo ufw disable

//포트 허용
sudo ufw allow '포트번호'

//포트 차단
sudo ufw deny '포트번호'

모든 작업이 되었다면 한번 방화벽을 껐다가 다시 켜 주자.

 

테스트 해 본 결과이다. 

 

 

번외 :: 라즈베리파이 환경 백업하기

저장장치로 마이크로 SD카드가 아닌 외장 SSD를 사용중에 있는데, 스냅샷 겸 현재 상태를 이미지 화 시키고 싶었다.

Win32diskimger32의 경우 모든 섹터를 저장하니 저장장치 크기만큼 점유한다는 문제가 있어 알아보던중...

Acronis True Image를 이용하기로 했다. 마침 학원 PC의 SSD가 WD 제품인지라..for Western Digital 버전을 다운로드, 

백업을 진행하고 테스트를 해 봤는데...정상동작한다!

 

ex)단, 사양차이가 나는 라즈베리파이의 모델 간 작업 시에는 오류가 발생할 가능이 높다.

특히 상위 모델에서 백업한 이미지를 구형 모델에 복원 시킬 경우 오류 발생하는 빈도가 높았다.(직접 당함)

라즈베리파이 3/3+에서 백업한 자료를 라즈베리파이 4에 작업 할 경우 - 정상 동작

라즈베리파이 4 4기가 모델에서 백업한 자료를 라즈베리파이 4 2기가 모델에 작업할 경우 - 오류 발생

하지만 동일 기기의 백업 목적으로는 사용하기가 좋다. 

 

 

+ Recent posts