2020년 12월 5일 토요일

무료 ERD 모델링 도구 소개

이 글은 데이터베이스 모델링에 많이 사용되는 ERD 모델링 도구 중 무료 소프트웨어 소개 링크를 소개한다.

ERD는 가끔 데이터베이스나 구조 모델링할 때, 엔티티간 관계를 설계하기 위해 사용한다. 최근에는 오픈소스 및 웹 기반 실행 도구도 많이 있어, 이를 이용하면, 무료로 ERD 디자인 할 수 있다.
ERD 디자인 예

ERD 모델링 도구 예

2020년 11월 7일 토요일

간단한 카티아(Catia) 사용 방법

 이 글은 간단한 카티아(Catia) 사용 방법을 보여주는 레퍼런스를 기록한다.

카티아 사용 방법
카티아 단면 디자인 예

2020년 11월 5일 목요일

아두이노 기반 FS-IA10B RF 수신기 채널 신호 처리 및 PWM 출력 방법

이 글은 아두이노 기반 FS-IA10B RF 수신기 채널 신호 PWM(Pulse Width Modulation) 출력 방법을 간단히 정리한 것이다.

수신기에서 들어오는 PWM신호를 오실로스코프로 확인하면 다음과 같다. 

이 PWM 진폭변조 신호는 진폭에 따라 수신기와 연결된 서보모터나 ESC(Electronic Speed Controller)와 연결된 모터를 제어하는 진폭 데이터를 포함하고 있다. 수신기의 각 채널은 진폭 데이터를 송신기에서 전달받아 모터에 전달함으로서 모터의 속도나 회전각도를 제어할 수 있다. 

문제는 각종 센서나 ROS(ROBOT OPERATING SYSTEM)에서 처리한 정보에 근거해 PWM을 강제로 수정, 로봇 등을 제어해야할 때이다. 이 경우, 수신기와 아두이노 같은 임베디드 보드 GPIO를 연결해 PWM데이터를 얻어, 이를 수정한 후 PWM데이터로 출력해야 한다. 

수신기 제작 업체에서 제공하는 IBUS 등 라이브러리가 있으나, 여기서는 가장 코딩하기 쉬운 방법을 이용해 처리한다. 

다음과 같이 코딩하고, 각 채널을 수신과 아두이노의 해당 핀에 연결하여 테스트해본다. 
// title: MSR_RF2
// developed by: KTW
// date: 2020.11.5
#include <Servo.h>

#define CH1 2
#define CH2 3
#define CH3 4
#define CH4 5
#define CH5 6

Servo ESC1;     // create servo object to control the ESC
Servo ESC2;
Servo ESC3;
Servo ESC4;

// Read the number of a given channel and convert to the range provided.
// If the channel is off, return the default value
int readChannel(int channelInput, int minLimit, int maxLimit, int defaultValue){
  int ch = pulseIn(channelInput, HIGH, 30000);
  if (ch < 100) return defaultValue;
  return map(ch, 1000, 2000, minLimit, maxLimit);
}

// Red the channel and return a boolean value
bool redSwitch(byte channelInput, bool defaultValue){
  int intDefaultValue = (defaultValue)? 100: 0;
  int ch = readChannel(channelInput, 0, 100, intDefaultValue);
  return (ch > 50);
}

void setup(){
  Serial.begin(115200);
  pinMode(CH1, INPUT);
  pinMode(CH2, INPUT);
  pinMode(CH3, INPUT);
  pinMode(CH4, INPUT);
  pinMode(CH5, INPUT);

  ESC1.attach(8,1000,2000); // (pin, min pulse width, max pulse width in microseconds)   
  ESC2.attach(9,1000,2000);    
  ESC3.attach(10,1000,2000);    
  ESC4.attach(11,1000,2000);    
}

int ch1Value, ch2Value, ch3Value, ch4Value;
bool ch5Value;

void loop() {
  ch1Value = readChannel(CH1, 0, 180, 0);
  ch2Value = readChannel(CH2, 0, 180, 0);
  ch3Value = readChannel(CH3, 0, 180, 0);
  ch4Value = readChannel(CH4, 0, 180, 0);
  ch5Value = redSwitch(CH5, false);
  
  Serial.print("Ch1: ");
  Serial.print(ch1Value);
  Serial.print(" Ch2: ");
  Serial.print(ch2Value);
  Serial.print(" Ch3: ");
  Serial.print(ch3Value);
  Serial.print(" Ch4: ");
  Serial.print(ch4Value);
  Serial.print(" Ch5: ");
  Serial.println(ch5Value);

  ESC1.write(ch1Value);
  ESC2.write(ch2Value);
  ESC3.write(ch3Value);
  ESC4.write(ch4Value);
  
  delay(500);
}

이제, 오실로스코프 두개 프로브에 각각 수신기 채널, 아두이노 출력 핀을 연결한다. 아두이노 수직 채널 입력 전압을 2V로 조정하고, 수평은 1.0 ms로 설정한다. 그리고, TRIGGER AUTO 버튼으로 시그널 수평을 고정한다. 그럼, 다음과 같이 입력후 처리된 PWM 진폭이 각각 일치하는 것을 확인할 수 있다.

2020년 11월 4일 수요일

LiPO 배터리 연결잭 정리

이 글은 LiPO 배터리 연결잭을 간단히 정리한 글이다. 

배터리를 사용하다 충전을 할 때 매우 다양한 배터리 연결잭 종류로 인해 피곤한 경우가 종종 있다. 이 경우 연결잭 종류를 확인하고, 직접 납땜 제작해야 한다. 

  • XT60
  • XT90: 30mm
  • T-Plug: 
  • 바나나 잭

구입은 디바이스 마트하비킹에서 하면 된다.

2020년 10월 19일 월요일

엔비디아 나노 이미지 백업 및 복구

이 글은 엔비디아(nvidia jetson) 나노(nano) 이미지 백업 및 복구 방법을 간략히 설명한 것이다. 크게 2가지 방법이 있다. 하나는 Win32 Disk Imager, 다른 하나는 DD 명령을 사용하는 방법이다. 

Win32 Disk Imager

다음 링크에서 프로그램 다운받아 설치한 후 실행한다.

실행 후 저장할 이미지 이름과 클론(복제)할 SD memory, USB memory를 USB 포트에 연결하고, Read 버튼을 클릭한다. 그럼 복제가 시작된다. 

복구는 반대로 비어 있는 메모리를 USB 포트에 연결하고, Write 버튼을 클릭하면 된다. 

DD명령


다음 명령어로 대용량 USB 메모리 기능을 추가한다.
sudo apt-get install exfat-fuse exfat-utils

다음 명령어로 이미지를 복제할 마운트 이름을 확인한다.
sudo parted -l

다음 명령어로 특정 마운트 이미지를 저장한다. 참고로 if는 입력 파일 옵션이다.
sudo dd if=/dev/mmcblk0 conv=sync,noerror bs=64k | gzip -c > ~/backup_image.img.gz

이미지 저장에 몇 시간 걸릴 것이다. 
이미지 복구는 다음 명령을 이용한다.
gunzip -c ~/backup_image.img.gz | dd of=/dev/sda1 bs=64k

레퍼런스

2020년 10월 18일 일요일

R 스튜디오 기반 데이터 통계 신뢰도 크론바흐 알파 계산하기

이 글은 오픈소스 R 스튜디오를 사용해 데이터 신뢰도를 간단히 검증할 수 있는 방법인 크론바흐 알파(Cronbach Alpha) 계산 방법을 나눔한다. R은 통계 분석언어이다. RStudio는 무료이며 GUI방식으로 편리하게 R 언어를 사용할 수 있도록 한다. 

RStudio
머리말
크론바흐 알파는 신뢰도 계수 중 하나이다. 알파값은 다양한 항목들 간에 내적 일관성(internal consistency)가 높은지를 측정한다. 이를 위해 조사 항목의 데이터들 간 상관계수를 계산한다.  

예를 들어, 어떤 질문 항목에 비해, 응답자들이 특정 방향으로 일관된 경향의 답을 하는 지를 알아볼 수 있다. 홍길동이 여러 관계된 질문들 Q1...n에 대해 일관성있게 점수들을 준다면 알파값이 높게 나오지만, 임의적으로 답을 주면 알파값이 낮게 나온다. 특정 질문 Qi가 일관성이 없어보이면, 해당 데이터를 삭제함으로써 일관성을 높일 수 있다. 
   
보통, 알파값이 0.8이 높으면 일관성과 신뢰성이 좋다고 판단한다. 알파값이 0.9이상이면 신뢰성이 높은 데이터이다. 0.7이상이면 항목들간에 어느정도는 일관성이 있다고 판단한다.

크론바흐 알파값은 피어슨(Pearson) 상관 계수와 함께 데이터 신뢰성 계산에 많이 사용된다.

프로그램 설치
통계 패키지 R과  RStdudio를 다음 링크에서 설치한다.

프로젝트 시작 및 데이터 준비
RStduio를 실행한다. 그리고 파일에서 새 프로젝트 메뉴를 선택한다. 
폴더 기반으로 프로젝트를 생성한다. 그리고, 생성된 폴더 안에 분석할 데이터를 엑셀로 저장한다. 테스트를 위해 아래 엑셀 데이터를 다운로드한다.
엑셀에 저장된 데이터는 리커트 5점 척도로 조사된 항목들로 구성되어 있다. 

크론바흐 알파 계산하기
새 프로젝트 후 콘솔창에 다음 명령을 입력해 패키지를 설치한다. 
install.packages("psy")
library("psy")
install.packages("psych")
library("psych")

엑셀파일이 준비되어 있다면, 다음 명령으로 엑셀파일을 읽을 수 있다. 
library(readxl)
B <- read_excel("benefit.xlsx")
View(B)

결과는 다음과 같다. 

만약, 준비된 데이터가 없다면, 다음 명령을 입력한다.
B <- data.frame(
B1=c(3,4,3,4,5,2,3,5,3,1), 
B2=c(4,4,1,2,4,1,2,4,2,2), 
B3=c(3,4,1,3,3,2,2,4,3,3))

데이터를 확인한다. 
View(B)

추세선 그래프를 확인한다.
pairs(B, panel=panel.smooth)

크론바흐 알파를 계산한다.
cronbach(B)

결과는 다음과 같다. 알파값이 0.812이므로 0.8보다 크다. 보통, 알파값이 0.8이 높으면 일관성과 신뢰성이 좋다고 판단한다. 
$sample.size
[1] 15
$number.of.items
[1] 7
$alpha
[1] 0.8127667

좀 더 상세한 알파 값을 얻는다. 
alpha(B)

소숫점까지 포함된 알파 값을 확인해 본다.
q <- alpha(B)
q$total

결과는 다음과 같다.
 raw_alpha std.alpha   G6(smc) average_r      S/N
 0.8127667 0.8066837 0.8761885 0.3734824 4.172871
        ase     mean        sd  median_r
 0.07143076 3.847619 0.7392902 0.4240922

마무리
이 글에서는 R 스튜디오 기반 데이터 통계 신뢰도 크론바흐 알파를 계산하는 방법을 간략히 설명하였다. 크론바흐 알파는 통계학자 크론바흐가 제안한 데이터 신뢰성 평가 방법이다. 주로 설문지 답변 데이터 신뢰성 등을 평가할 때 많이 사용된다. 크론바흐 알파 값 등급은 정해지지 않았지만, 일반적으로 0.6 이상이면 신뢰성이 있다고 한다. 만약, 0.6 미만으로 나오면 해당 데이터는 수정 및 삭제하는 것이 좋다. 



2020년 10월 9일 금요일

sudo 암호 없이 우분투 사용 방법

어떤 경우, 우분투 암호가 너무 길어 사용하기 어려울 때가 있다. 이 글은 sudo 암호 없이 우분투 사용 방법을 간략히 설명한다.

우분투 account에서 로그인 시 암호 입력을 해제한다. 그리고, 다음 명령을 실행한다.
sudo visudo

맨 아래 행에 다음을 입력한다.
user ALL=(ALL:ALL) NOPASSWD:ALL

만약, 오류가 생긴다면, 다음과 같이 추가된 행을 수정한다.
pkexec visudo

이제 암호 없이 패키지를 설치할 수 있다.

2020년 10월 3일 토요일

우분투 부팅 시 파이썬 프로그램 자동 실행 방법

이 글은 우분투 리눅스에서 부팅 시 특정 파이썬 프로그램 등을 자동 실행(autorun)하는 방법을 간단히 설명한 것이다.

파이썬 코딩
다음과 같이 /usr/bin 폴더 아래 파이썬 파일을 코딩한다.
sudo vim /usr/bin/test_service.py

소스 코드는 다음과 같다.
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("localhost", 8877))
s.listen(1)
 
while True:
    conn, addr = s.accept()
    data = conn.recv(2048)
    conn.close()
    print(data)

서비스 등록 및 시작
서비스 설정 파일을 생성한다.
sudo vim /lib/systemd/system/test.service

내용은 다음과 같다.
[Unit]
Description=Test Service
After=multi-user.target
Conflicts=getty@tty1.service

[Service]
Type=simple
ExecStart=/usr/bin/python3 /usr/bin/test_service.py
StandardInput=tty-force

[Install]
WantedBy=multi-user.target

다음과 같이 생성된 서비스를 실행한다.
sudo cp test.service /lib/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable test.service
sudo systemctl start test.service

다음 명령으로 재실행할 수도 있다
sudo systemctl restart test.service 

정상적인 실행 결과는 다음과 같다.

레퍼런스

2020년 9월 26일 토요일

간단하게 포트포워드로 외부 접속 가능한 공개 웹서버 인터넷 서비스 및 DNS 설정 방법

이 글은 간단한 포트포워드로 외부 접속 가능한 공개 웹서버 인터넷 서비스 및 DNS 설정 방법을 정리한다.

본인이 개발한 프로그램을 인터넷으로 서비스하고 싶을 때가 있다. 이때, 인터넷 제공자로부터 비싼 고정IP를 설치하거나, 임시로 할당된 IP주소를 매번 입력하는 방식은 그리 편해 보이지 않는다. 이 경우, 집에 있는 공유기의 포트포워드(port forward) 를 이용해 공개 웹서버를 만들 수 있다.

공유기의 포트포워드 기능을 이용하면, 집에서도 서버를 쉽게 운영할 수 있다.

IP 정보 얻기

우선 아래 링크로 내 공개 IP를 얻는다.

그리고, 내부 네크워크 내 서버로 사용할 컴퓨터의 IP주소를 ifconfig 로 얻는다. 화면에서 inet 이 IP 주소이다.

공유기 포트 포워드 설정

이제 이 두개를 연결해줘서, 외부 공개 IP > 내부 IP 서버로 접속될 수 있게 포트 포워드를 설정한다. 이 과정은 공유기 종류별로 약간 다르나 메뉴는 거의 동일하다.

우선 아래와 같이 공유기 설정 주소를 입력하여, 로긴한 후, 포트 포워드 설정 메뉴를 선택한다.

다음과 같이 포트포워드할 내부 IP주소를 입력하고 설정한다.
다음과 같이 DMZ를 설정한다.
방화벽 설정

접속하는 윈도우에서 방화벽 설정을 다음과 같이 진행한다.

제어판 > window 방화벽 > 고급설정 > 인바운드 규칙 클릭 > 새규칙 > 포트 클릭 > tcp, 특정 로컬 포트 예) 80, 5000 > 연결 허용 체크 > 도메인, 개인 공용 모두 체크 > 마침

우분투 서버에서는 다음과 같이 서버 포트를 허용해 준다.

sudo ufw allow 5000

서버 실행

다음과 같이 개발한 서버를 실행한다(웹서버 개발 방법 참고).

도메인 획득

이제 외부 공개 IP로 접속하면 서버가 외부에서도 연결될 것이다. 이제, 도메인을 설정해 특정 주소로 서버가 외부에 공개되도록 한다. 무료 DNS 서버를 이용해 이를 설정한다. 여기에서는 freenom을 이용한다. 

다음과 같이, 원하는 DNS 이름을 입력하고, 로그인 한 후, DNS를 구입하고, 해당 DNS에 앞의 서버 외부 공용 IP를 설정한다.



이제 DNS와 설정한 서버 공개 외부 IP가 네트워크를 통해 반영될 수 있도록 5~10분 정도 기다린다.

외부 DNS IP로 서버 접속 

외부 DNS IP로 서버 접속해 본다. 결과는 다음과 같다. 테스트를 위해, 집 밖에서 노트북, 스마트폰을 이용해 촬영한 이미지를 개발한 웹서버로 업로드해 보았다.

외부에서 업로드한 이미지는 다음과 같이 서버에 잘 저장된다.

마무리

공유기의 포트포워드 기능을 이용하면, 집에서도 서버를 쉽게 운영할 수 있다.

레퍼런스

2020년 9월 23일 수요일

LIO SAM 기반 SLAM 기술 소개, 빌드 및 활용

이 글은 LIO SAM 기반 SLAM 기술 소개 및 활용에 대해 간략히 설명한다.

개요

LIO SAM은 LOAM개발자가 기존 방법을 개선해 개발한 슬램 기술이다. 

LIO-SAM은 라이더 관성 주행 거리 측정법을 개발해, 루프 폐쇄를 포함한 다양한 상대 및 절대 포인트 측정을 여러 소스에서 통합한다. 관성 측정 장치 (IMU) 통합에서 추정된 모션은 포인트 클라우드의 왜곡을 제거하고 라이더 주행 거리 측정 최적화를 위한 초기 추측 데이터를 생성한다. 라이더 주행 거리 측정 솔루션은 IMU의 편향을 추정하는 데 사용된다. 이 결과로 다음과 같이 기존 LOAM보다 좋은 결과를 얻을 수 있다.

시스템 아키텍처는 다음과 같다. 

이 기술은 다음과 같이 IMU, LiDAR가 미리 준비되어 있어야 한다.

소스 빌드 및 실행

다음과 같이 실행해 소스코드를 빌드한다.

sudo apt-get install -y ros-kinetic-navigation
sudo apt-get install -y ros-kinetic-robot-localization
sudo apt-get install -y ros-kinetic-robot-state-publisher
wget -O ~/Downloads/gtsam.zip https://github.com/borglab/gtsam/archive/4.0.2.zip
cd ~/Downloads/ && unzip gtsam.zip -d ~/Downloads/
cd ~/Downloads/gtsam-4.0.2/
mkdir build && cd build
cmake -DGTSAM_BUILD_WITH_MARCH_NATIVE=OFF ..
sudo make install -j8
cd ~/catkin_ws/src
cd ..
catkin_make

실행은 다음과 같다. 

roslaunch lio_sam run.launch

미리 준비된 데이터로 스캔 데이터를 시뮬레이션할 수 있다.

rosbag play your-bag.bag -r 3

결과는 다음과 같다. 

마무리

LIO SAM은 LOAM과 유사한 방식(참고)으로 개발되었다. 특징점을 계산하기 위해, GSTAM 패키지를 사용한다. 성능에 대한 설명은 여기를 참고한다.

레퍼런스



2020년 9월 22일 화요일

ROS기반 로봇 다중 좌표 프레임 개념 및 사용 방법

이 글은 ROS 좌표 프레임 개념 및 사용 방법에 대해 간략히 정리한다. 

머리말

로봇 시스템은 다양한 로컬 좌표계 프레임으로 구성된다. 다음 그림과 같이 로봇의 각 파트가 움직일 때 하위 부분은 같이 움직어야 한다. 이들의 위치나 방향을 각각 계산하면 매우 복잡할 것이다. 그러므로, 상위 파트가 움직이면, 해당되는 좌표 그래프부터 부속된 하위 노드까지 좌표변환행렬을 계산 및 전파한다. 이를 scene graph라 한다.

scene graph

씬 그래프는 컴퓨터 그래픽에서 고전적인 솔류션이다.

다중 좌표 프레임(ROS)

ROS는 분산 처리 방식이라 메시지를 퍼블리쉬하는 방식으로 좀 복잡하게 처리된다. 

좌표계 프레임 정의
다음 그림은 로봇이 거친 표면을 이동할 때, 로봇 플렛폼의 좌표계와 roll, pitch 모션 등을 보여준다.  

맵, odom, base_link 프레임은 Coordinate Frames for Mobile Platforms ROS (REP 0120)문서에 기술되어 있다. 다음은 프레임 각 구성요소 설명이다.
  • map: world fixed frame(세계 고정 좌표 프레임)이다. Z축은 위를 향한다. 모바일 플랫폼 위치는 map 프레임에서 상대적이고, 시간에 따라 이동하지 말아야 한다. map 프레임은 연속적이지 않고, 맵 프레임 내 모바일 플랫폼 위치는 변화될 수 있다. 일반적 설정에서, 맵 프레임에서 센서 관찰 지점 기반 로봇 위치는 재계산된다. 맵 프레임은 장기간 전역 참조로써 유용하다. 맵 프레임의 z위치는 해발 고도와 동일하다. 
  • odom: odom은 world fixed frame이다. odom 프레임에서 모바일 플랫폼 위치는 시간에 따라 이동될 수 있다. odom 프레임 안에서 로봇 위치는 연속적이다. 일반적 설정에서 odom 프레임은 odometry(주행계) 소스를 기반으로 계산된다. 소스의 예는 휠 주행계(wheel odometry), 비쥬얼 오도메트리(visual odometry), IMU(inertial measurement unit) 센서 등이 된다. odom 프레임은 단기적 지역 참조에 유용하다.
  • base_footprint: base_footprint는 바닥에 대한 로봇 위치를 나타낸다. 바닥은 보통 다리를 지지한느 수평 지반을 말한다. 프레임 변환 요소는 
  • base_link: base_link는 모바일 로봇 베이스에 부착된다. base_link는 임의 위치 및 방향을 가진다. 모든 로봇 하드웨어는 서로 다른 base_link를 가진다. odom 프레임에 상대적이며, 롤 및 피치 각도는 0이고, yaw 각도는 base_link yaw각도와 일치한다. 이 좌표계는 인간형 로봇 이동시 안정적인 평면 정보를 제공한다.
  • gaze: head 위치 및 방향을 정의한 좌표 프레임이다. 카메라같은 센서와 관련해 보는 방향을 정의한다.
  • earth: ECEF의 원점이다. 이 프레임은 서로 다른 맵 프레임에서 여러 로봇의 상호 작용을 허용하도록 설계되었다. 
지구 같은 전역 참조와 관련해 좌표 프레임을 정의할 때, x축은 동쪽, y축은 북쪽, z축은 위로 정렬된다. 건물 실내와 같이 구조화된 환경에서는 건물 레이아웃 지도와 좌표계를 정렬하는 것이 좋다. 여러 층에서 작업하는 경우, 각 층에 좌표 프레임이 있는 것이 합리적일 수 있다. 

각 프레임요소는 다음과 같은 연결관계를 가진다. 참고로, 드론과 같은 로봇은 pressure_altitude 를 추가해 고도 좌표 프레임을 삽입할 수 있다.

다음 프레임 그래프는 earth에 기반한 다중 로봇의 좌표계 그래프를 보여준다.

참고로, 로봇이 원거리 이동하는 경우, map 을 전환해야 할 수도 있다.

이 SLAM에서는 odom, base_link 두 프레임을 사용한다. base_footprint는 높이 정보는 없으며, 2D pose 정보만 제공한다(position, orientation), base_stabilized는 로봇 높이에 대한 추가 정보를 제공한다. 로봇에 대한 AHRS, INS에서 얻은 로봇 자세(attitude. roll/pitch)를 추정하여 사용할 수 있다(hector_imu_attitute_to_tf 노드). base_link에서 laser_link로 변환은 static transform publisher와 robot state publisher에서 제공한다. 

ROS TF(Tranformation) 정의
hector_mapping와 같은 SLAM에서 tf는 스캔 데이터 변환을 위해 사용한다. tf는 다중 좌표 프레임을 유지하기위한 역할을 한다. tf는 특정 시점에서 두 좌표 프레임간의 점, 벡터 등을 계산한다. tf2는 tf를 개선한 것이다. 로봇 시스템은 월드 프레임, 기본 프레임 등과 같이 시간이 지만에 따라 변경되는 3차원 좌표 프레임이 있다. tf는 시간 경과에 따라 이런 모든 프레임을 추적하여 다음과 같은 계산을 할 수 있다. 
  • Where was the head frame relative to the world frame, 5 seconds ago?
  • What is the pose of the object in my gripper relative to my base?
  • What is the current pose of the base frame in the map frame?
tf는 분산 시스템에서 작동할 수 있어, 각 로봇 파트가 개별 컴퓨터에서 네트웍을 통해 통합된 좌표 변환 기반 동작을 수행할 수 있다(참고. tf 데모).

좌표 프레임 설정 예
좌표 프레임은 odom을 사용하지 않는다면, 다음과 같이 설정된다. 
    <param name="pub_map_odom_transform" value="true"/>
    <param name="map_frame" value="map" />
    <param name="base_frame" value="base_frame" />
    <param name="odom_frame" value="base_frame" />

이 경우, 퍼블리쉬 설정을 다음과 같이 처리한다.
    <param name="pub_map_odom_transform" value="false"/>

마무리 
이 글에서는 ROS 다중 좌표 프레임에 대해 간략히 정리해 보았다.

2020년 9월 20일 일요일

간단한 Flask, mysql, 파이썬 기반 Open API 웹서버 개발 방법

이 글은 Flask, mysql, 파이썬 기반 Open API 웹서버 개발 방법을 간략히 정리한다. 이 방법은 Open API를 쉽게 만드는 기술 중 하나로, 파이썬 기반 오픈 API 개발 시 가장 많이 사용되는 구조이다. 

  • 간단한 Open API 서버 개발
  • 서버에서 특정 프로그램 실행 결과 API 로 호출해 리턴
  • MySQL 데이터와 Flask 연동을 통한 객체 관계 맵핑 API 자동 생성
  • 이미지 업로드 서버 개발
머리말
Flask는 마이크로 웹 프레임웍(microframework)으로 파이썬으로 개발되어 있다. 플라스크는 다양한 애드인을 지원하여, ORM(object relational mapping), 폼 검사(form validation), 파일 업로드 처리, 다양한 개방형 인증, 도구 등을 사용할 수 있다.  플라스크는 국제 파이썬 그룹의  Armin Ronacher에 의해 개발되어 2010년 첫버전이 발표되었고, 2020년에는 GitHub에서 가장 인기있는 파이썬 웹 개발 프레임웍에 선정되었다. 플라스크는 Pocoo 프로젝트 기반이며, WSGI(Web Server Gateway Interface) 지원을 위한 Werkzeug, Django와 유사한 템플릿 엔진을 제공하는 Jinja 를 사용한다. 

본 개방형 API 서버는 다음과 같은 구조를 가지게 되다.

 
참고로, 다음 구글에서 Flask서버를 이용해 Open API 서버를 만든 아키텍처이다.
구글 Open Restful API 서버 구조

환경 준비

다음과 같이 관련 환경을 설정한다.

sudo apt-get install mysql-server
sudo ufw allow mysql
sudo systemctl start mysql
sudo systemctl enable mysql

이제 마이SQL을 실행한다.

sudo /usr/bin/mysql -u root -p


mysql 프롬프트에서 아래 명령을 입력한다. 
SHOW VARIABLES LIKE "%version%";

기본 정보를 확인해 본다.
SELECT User, Host, authentication_string FROM mysql.user;

필요하다면, 루트 계정으로 암호를 수정한다.
ALTER USER 'root'@'localhost' IDENTIFIED BY 'password';

데이터베이스를 생성한다.
create database study;
show databases;

암호정책은 약하게 우선 설정한다. 그리고 계정을 만든다.

select plugin_name, plugin_status from information_schema.plugins where plugin_name like 'validate%';

install plugin validate_password soname 'validate_password.so';

select plugin_name, plugin_status from information_schema.plugins where plugin_name like 'validate%';

SET GLOBAL validate_password_policy=LOW;

password에 해당하는 암호는 8자 이상이어야 한다. 
CREATE USER 'ktw'@'localhost' IDENTIFIED BY 'password';
FLUSH PRIVILEGES;

생성한 데이터베이스 모든 권한을 본인 계정에 전달한다. 
GRANT ALL PRIVILEGES ON study.* to ktw@localhost;
FLUSH PRIVILEGES;
 
만약, mysql 서버를 다른 PC에서 제한없이 접속하고자 한다면 다음 명령을 입력한다.
CREATE USER 'root'@'%' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON * . * TO 'root'@'%' WITH GRANT OPTION;
FLUSH PRIVILEGES;
 
참고로, 주로 많이 사용되는 mysql 명령어는 다음과 같다. MySQL workbench 를 설치해 UI에서 명령을 실행할 수도 있다.
  • 데이터 테이블 정보 조회: describe [table name];
  • 원하는 필드 조회: select [field1] [field2] from [table];
  • 조건 검색: select * from [table] where ([field] = 'value') OR ([field] < 'value');
  • 글자 검색: select * from [table] where [field] like 'Kang%' order by [field] ASC;
  • 범위 검색: select * from [table] limit 2, 4;
  • 갱신: update [table] set [field1] = 'value', [field2]='value' where [field] = 'value';
  • 삭제: delete from [table] where [field] = 'value';
  • 조인: select [table1].[field1], [table2].[field1] from [table1], [table2] where [table1].[field1] = [table2].[field1];
  • 정보 확인: select version(), user(), database();
  • 외부 엑셀 파일 입력: 링크 참고
  • 기타: 테이블 생성 시 필드에 NOT NULL, AUTO_INCREMENT, PRIMARY KEY 등을 지정할 수 있다. 예시는 다음과 같다.

create table study(no int, subject varchar(100), memo varchar(50), writedate date, count int, primary key(no));

MySQL workbench 실행 시 접근 권한 에러날 경우 Software center에서 Permissions을 다음과 같이 변경해 주어야 한다.

MySQL workbench permissions
MySQL workbench 실행 모습
MySQL 외부 데이터 파일 임포트

테이블 스키마 및 데이터 생성
다음 명령어를 이용해 테스트용 데이터를 생성한다. 이 결과로 점수 테이블에 3개 레코드가 추가된다. 
use study;
create table score (subject char(20) NOT NULL, score float(15, 4) NOT NULL);
insert into score(subject, score) values('math', 100);
insert into score(subject, score) values('eng', 90);
insert into score(subject, score) values('kor', 95);
 

간단한 MySQL 기반 Open API 서버 개발

다음과 같이 실행해 플라스크와 MySQL 테이블을 바로 API로 만드는 flask-mysql을 설치한다. 

pip install Flask flask-mysql
pip freeze > requirements.txt

다음과 같이 my_app.py 파일을 코딩한다.

from flask import Flask, jsonify
from flaskext.mysql import MySQL

app = Flask(__name__)
mysql = MySQL()

# MySQL configurations
app.config['MYSQL_DATABASE_USER'] = 'my_user'
app.config['MYSQL_DATABASE_PASSWORD'] = 'my_password'
app.config['MYSQL_DATABASE_DB'] = 'my_database'
app.config['MYSQL_DATABASE_HOST'] = 'localhost'

mysql.init_app(app)

@app.route('/')
def get():
    cur = mysql.connect().cursor()
    cur.execute('''select * from study.score''')
    r = [dict((cur.description[i][0], value)
                for i, value in enumerate(row)) for row in cur.fetchall()]
    return jsonify({'myCollection' : r})

if __name__ == '__main__':
    app.run()

다음과 같이 실행한다.

pip install cryptography

python my_app.py

그리고, http://127.0.0.1:5000 링크를 브라우저로 열어 본다.

그럼, 다음과 같이 생성된 데이터베이스 테이블에 대한 myCollection Open API가 자동 생성되 동작되는 것을 확인할 수 있다. json파일을 리턴하기 때문에 컨텐츠 유형이 appliation/json 이다.

간단한 Open API 서버 개발

이번에는 간단한 Open API 서버를 개발하겠다.  다음을 open_api.py에 코딩한다. 
from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
    return 'Hello, World!'

if __name__ == '__main__':
    app.run()

다음과 같이 실행한다.
export FLASK_APP=open_api.py
flask run
 * Running on http://localhost:5000/

혹은 다음과 같이 실행한다.
python open_api.py

결과는 다음과 같다. 여기서 @app.route('/'에 해당하는 hello() 함수가 호출되어 실행된 것을 알 수 있다.

이제 다음과 같이 실행파일(내부 프로세스)를 실행해 본다.
from flask import Flask
import os
import subprocess
import time

app = Flask(__name__)

@app.route('/')
def hello():
    return 'Hello, World!'

@app.route('/list')
def list():
    out = ""
    try:
        pid = subprocess.Popen(["ls", "-l"]).pid
        out = subprocess.check_output(["ls", "-l"])
    except EnvironmentError:
        os.kill(pid)
        print('unable to run subprocess')
        return
    return out

if __name__ == '__main__':
    app.run()

결과는 다음과 같다. 내부 프로그램을 실행해 그 결과를 리턴하는list API가 동작되는 것을 확인할 수 있다.

이미지 업로드 서버 개발

같은 방식으로 이미지 업로드 서버를 개발해 본다. image.py 코딩한다.

from flask import Flask, jsonify, request, render_template, redirect
import os
import subprocess
import time

app = Flask(__name__)

app.config["IMAGE_UPLOADS"] = "/home/ktw/project/flask_simple/data"

@app.route('/')
def hello():
    return 'Hello, World!' 
 
@app.route("/upload-image", methods=["GET", "POST"])
def upload_image():
    if request.method == "POST":
        if request.files:
            image = request.files["image"]
            print(image)
            image.save(os.path.join(app.config["IMAGE_UPLOADS"], image.filename))                                                     
            return redirect(request.url)   
    return render_template("upload_image.html")

if __name__ == '__main__':
    app.run()

현재 폴더 아래에 templates 폴더를 만들고, upload_image.html 파일을 코딩한다.
{% block title %}Upload{% endblock %}
{% block main %}
<div class="container">
  <div class="row">
    <div class="col"><h1>Upload an image</h1>
      <hr><form action="/upload-image" method="POST" enctype="multipart/form-data"><div class="form-group">
          <label>Select image</label>
          <div class="custom-file">
            <input type="file" class="custom-file-input" name="image" id="image">
            <label class="custom-file-label" for="image">Select image...</label>
          </div>
        </div><button type="submit" class="btn btn-primary">Upload</button></form></div>
  </div>
</div>
{% endblock %}

다음은 이미지 업로드 서버 실행 결과이다.

다중 이미지 업로드 서버 개발

이제 여러 장의 이미지들을 업로드하고 보는 서버를 개발해 보자. 다음 multi_images.py를 코딩한다(참고. Dustin D'Avignon 관련 글)

from flask import Flask, redirect, render_template, request, session, url_for
from flask_dropzone import Dropzone
from flask_uploads import UploadSet, configure_uploads, IMAGES, https://daddynkidsmakers.blogspot.com/2020/09/dns.htmlpatch_request_class
import os

app = Flask(__name__)
dropzone = Dropzone(app)  # 드롭존 객체 획득

# Dropzone 설정
app.config['DROPZONE_UPLOAD_MULTIPLE'] = True     # 다중 업로드 app.config['DROPZONE_ALLOWED_FILE_CUSTOM'] = True
app.config['DROPZONE_ALLOWED_FILE_TYPE'] = 'image/*'
app.config['DROPZONE_REDIRECT_VIEW'] = 'results'
app.config['UPLOADED_PHOTOS_DEST'] = os.getcwd() + '/uploads'  # 파일 위치
app.config['SECRET_KEY'] = 'secretkey'

photos = UploadSet('photos', IMAGES)    # 업로드 셋 설정
configure_uploads(app, photos)
patch_request_class(app)  # 파일 크기 설정, default is 16MBhttps://daddynkidsmakers.blogspot.com/2020/09/dns.html

@app.route('/', methods=['GET', 'POST'])
def index():
    # 이미지 결과에 대한 세션 설정
    if "file_urls" not in session:
        session['file_urls'] = []
    # 이미지 URL 획득
    file_urls = session['file_urls']    # handle image upload from Dropzone
    if request.method == 'POST':   # 요청이 POST 인 경우
        file_obj = request.files
        for f in file_obj:                            # 업로드 요청 온 파일 리스트를 얻어 서버에 저장
            file = request.files.get(f)
            
            # 폴더에 저장.
            filename = photos.save(
                file,
                name=file.filename    
            )            # 이미지 URL 추가 보관
            file_urls.append(photos.url(filename))
            
        session['file_urls'] = file_urls   # 이미지 URL을 세션에 보관
        return "uploading..."    # return dropzone template on GET request    
    return render_template('index.html')   # 웹 업로드 템플릿 렌더링

@app.route('/results')
def results():간단한 포트포워드로 외부 접속 가능한 공개 웹서버 인터넷 서비스 및 DNS 설정 방법
    # 세션에 URL이 없으면 index로 redirect 처리.
    if "file_urls" not in session or session['file_urls'] == []:
        return redirect(url_for('index'))
        
    # file URL 정보를 세션에서 얻어, results 템플릿 렌더링
    file_urls = session['file_urls']
    session.pop('file_urls', None)
    
    return render_template('results.html', file_urls=file_urls)

if __name__ == '__main__':
    app.run(host = '0.0.0.0', port = 5000)

templates 폴더 아래에 index.html파일을 코딩한다. dropzone 객체를 이용해, 드래그&드롭 이미지 업로드 컴포넌트를 만들고 있다.

<!DOCTYPE html>
<html>
    <head>
        <title>Flask App</title>
        {{ dropzone.load() }}
        {{ dropzone.style('border: 2px dashed #0087F7; margin: 10%; min-height: 400px;') }}
    </head>
    <body>
        <h1>Hello from Flask!</h1>
        {{ dropzone.create(action_view='index') }}
    </body>
</html>

그리고,  results.html파일을 코딩한다. 세션의 file_url을 src로 하여, 이미지를 렌더링하게 되어있다.

<h1>Hello Results Page!</h1>
<a href="{{ url_for('index') }}">Back</a><p>
<ul>
{% for file_url in file_urls %}
    <li><img style="height: 150px" src="{{ file_url }}"></li>
{% endfor %}
</ul>

서버를 실행한 결과는 다음과 같다. 

잘 실행된다. 만약, 외부로 웹서버를 공개하고 싶다면, 다음 링크를 참고한다.

마무리

플라스크를 이용해 데이터 업로드, 프로그램 실행, 데이터 저장 및 API를 통한 데이터 획득 서버 개발 방법을 간단히 알아보았다. 앞의 예제를 따라하면 프론트, 미들, 엔드를 포함한 웹서버가 이렇게 쉽게 개발된다는 것에 깊은 인상을 느끼게 된다. 플라스크는 이런 전 과정을 매우 간단한 간단한 포트포워드로 외부 접속 가능한 공개 웹서버 인터넷 서비스 및 DNS 설정 방법문법과 기능으로 해결해 준다. 다양한 애드인을 이용해 이미지 등 다양한 파일을 처리할 수 있다. 구글에서도 Open API 서버로 플라스크를 이용할 만큼 성능과 안정성이 좋다. 

플라스크 개발에 공헌한 개발자는 오스트리아 오픈소스 프로그래머인 아민 로너커이다. 그는 1989년 생으로 소프트웨어 개발이란 유명한 블로그를 운영하고 있다. 2005년 우분투 그룹에서 오픈소스 개발자로 경력을 시작하였고,  WSGI 에 관심을 가졌다. 그는 2004년 부터 one-file microframework을 주장하였고, 플라스크 개발을 시작했다.

플라스크에 대한 상세 문서는 여기(한글 문서)를 참고하길 바란다.

참고: 가상환경 설정

플라스크 개발 시 다음과 같이 가상환경을 설정하는 것이 좋다.

python3 -m venv env
source env/bin/activate
pip install -e .

참고: [Errno 98] Address already in use

sudo lsof -i :5000
sudo kill -9 [address]

레퍼런스