분류

2021년 8월 21일 토요일

두번째 zwift trainer : MAGENE T300 구매 및 연결 후기

개요 

ALIEXPRESS 에서 줄곧 새로운 스마트 트레이너를 검색하고 있었습니다. 이유는 1년 2개월간 사용한 tacx vortex 제품의 경우 소음이 제법 거슬리기도 했고, 전용 타이어와 전용 휠까지 준비했는데 구동계 변경 이후 전용 휠 이 맞지 않는 상태였기 때문입니다. 

기존 9단의 스램구동계에서 11단 중국계 SENSAH EMPIRE PRO 로 변경 하였기 때문에 생긴 일입니다. 


1. 구매 

https://www.aliexpress.com/ 에서 자전거 트레이너를 심심찮게 검색 하던 중 3개월만에 MAGENE T300이 550달러에 판매하여 바로 구매했습니다. 550$+배송비 39$ 한화 약 64만9천원에 관부가세 143,650원을 결제하였는데 관부가세는 세관의 tlft 약 10만원 더 비싸게 책정되어 2만원 가량을 더 냈습니다. 하지만 그간 세관에서 제법 비싼 것도 그냥 통과시켜 주었기에 관세는 한번 내보고 싶었던 터라 오히려 기분이 좋았습니다. 

관부가세 내역 


2. 배송 

판매자 녀석이 여름 휴가를 다녀왔는지 4일에 주문한 것을 12일에 출고 받았습니다. 

16일 걸린 배송 
배송은 원래 7,8일이면 도착한다던 것이 출고가 늦는 바람에 16일이 소요 되었습니다. 

배송상태
배송받고 보니 박스는 약간 중고냄새가 날 만큼 상태가 안좋아서 제법 기분이 묘했습니다. 

3. 언박싱 




상자 포장은 위 사진들과 같이 약간은 허술한 포장상태로 진짜 중고 냄새 나는 박스로 왔습니다. 아마도 거부라도 한번 당한 제품인 것 같습니다. 포장 상자는 가로 65, 세로 60, 높이 30cm 의 제법 큰 화물로 반송 보내려면 국제 택배 비용이 장난 아니게 들 것 같습니다. 

다음은 내부 포장입니다. 

알아먹을 수 없는 중국어 설명서 

완충재를 열자 나오는 T300

T300 내부 구성품(왼쪽)과 기존 스마트로러 탁스 보텍스 (오른쪽)

스티로폼 비슷한 재질로 포장이 잘 되어있어 제법 안전하게 온 것 같습니다. 내부 구성품은 아답터, 본체, QR레버로 이루어져 있습니다. 기존 트레이너 대비 접으면 공간을 덜 차지하게 되어 좋을 것 같지만. 실제로 들어보면 약 20kg에 육박하는 무게 때문에 자주 들고싶은 생각이 없어집니다. 탁스만 못한 점은 앞바퀴 받침이 없다. 정도 입니다.




펼치면 가로 70cm x 세로 45cm x 높이 60cm 가량이 됩니다. 크기는 제법 컴팩트한 것 같습니다. 

구성폼중에는 위 사진과 같이 스프라켓 스페이서, QR연결부, 정체를 알 수 없는 렌치, USB 블루투스 동글 등이 들어있습니다. 대부분 불필요하지만 PC에 USB 동글이 없는 분이라면 유용할지도 모르겠습니다. 전 이미 있어서 그냥 보관. 


4. 주의사항  

1) 콘센트 

플러그가 중국 내수 용 형태입니다. 그나마 다행인 것은 컴퓨터 전원 케이블과 완벽하게 호환이 됩니다. 그리고 전압이 250V되어있어 당황할 수 있으나 220V 국내와 같다고 보시면 됩니다. (오차 범위 내에 있습니다.) 

전 이게 컴퓨터 케이블과 같다는 생각을 못해서 여행 용 플러그를 사러 2시간을 돌아다녀 구매했으나. 집에 와서 아답터와 연결되는 곳을 보는 순간 컴퓨터 용 전원 케이블로 해결 가능 하다는 걸 알았습니다. (집에 컴퓨터 케이블은 남아 돕니다..)

위 사진과 같이 내수용 220V 컴퓨터 파워 케이블과 모양이 정확히 일치합니다. 

2) ZWIFT 연결 (기기 활성화) 

전원 연결과 동시에 매우 빠르게 블루투스로 PC ZWIFT와 연결됩니다. 하지만 그렇다고 사용 가능한 게 아닙니다. 기기를 활성화 시켜줘야 합니다. 
ZWIFT 연결하기 전에 Magene Utility라는 것을 휴대폰으로 다운 받으셔서 열어보시면 장치가 연결 됩니다. 여기서 activate를 누르면 email 주소를 넣으라고 하고 e-mail 인증 을 받은 후에 ZWIFT와 연동 할 수 있습니다. activate 하지 않으면 연결은 되나 페달링을 해도 센서가 작동하지 않습니다. 활성화 상태는 기기의 상태로 알 수 있습니다. 활성화가 안됐을땐 블루투스의 파란불만 들어오고 활성화가 되면 기계 바닥면의 무드 조명이 켜졌다 꺼졌다 합니다. 

3) 기타 블루투스 연결 문제 


불루 투스 연결이 되지 않는다면 상위 링크를 통해 해결하실 수 있습니다. 

5. 연결 및 사용 

연결은 별다른 설정 없이 ZWIFT를 실행하자마자 바로 연결이 됩니다. TACX VOTEX는 연결이 오래 걸리고 종종 연결이 안되어 프로그램을 몇 번 다시 시작 했던 것과 대비됩니다. 
연결은 TACX 제품과 다르게 POWER SOURCE와 SMART TRAINER 두 가지만 됩니다. TACX의 RPM 게이지는 제법 비슷하게 나와 유용했는데 말이쥬. 

실제 주행 연습을 하며 찍어봤는데 적당히 흔들리며 프레임의 운동 충격을 흡수해줍니다.  무게가 너무 무겁기 때문에 이런 부분은 지원되지 않을 줄 알았는데 촬영해보니 제법 괜찮습니다. 
기존 트레이너는 약 2초간의 지연시간이 있던 것에 반해 새 트레이너는 페달링 운동이 즉시 파워 소스로 반영되고 언덕에 대한 시뮬레이션도 실시간으로 반영 됩니다. 센서는 하나 없어졌지만 사용성은 여러 면에서 더 좋아졌습니다. 


6. 기존 트레이너 대비 장단점

1) 장점 

공간 차지가 적다. 
이동이 편하다. 
zwift와 연결이 빠르고 반응 속도가 즉각적이다. 
소음이 적다. 
연습 용 타이어와 휠 비용이 삭감된다. 
페달링이 매우 부드럽다. 
22% 경사 시뮬레이션이 가능해지면서 보다 현실적인 라이딩 연습이 가능하다. 

2) 단점 
기존 트레이너 대비 전기를 많이 먹는다. (최대출력 2200w) 
기존 트레이너 대비 비싸다. 
무겁다. 
기존 트레이너와 다르게 페달링을 멈추면 관성 주행 거리가 짧다. 

2021년 6월 6일 일요일

poco m3 pro 5G 구매 후기

 개요

작년 8월 구매한 poco f2 pro는 완성도가 매우 떨어지는 제품이었습니다. 게이밍 성능이나 카메라는 뛰어났지만, 수시로 끊기는 LTE 문제와 업데이트 이후 전화나 WIFI 가 먹통이 되는 문제. 사진 성능이 뛰어난 것은 인정하지만, 디지털 카메라를 산 것이 아니기에 그 불편함을 참을 수 없게 되는데 걸리는 시간이 그리 길지 않았습니다. 

적당한 휴대폰을 알아보던 중 LG벨벳이 무상에 가까운 가격으로 판매가 되고 있었지만, 약정 기간이 길고, 재고를 구하기 쉽지 않다는 점은 다시 또 다른 POCO 제품을 구매하는 계기가 되었습니다. 

휴대폰으로 하는 일이 주로 전화와 주식거래 검색, 유투브, 웹툰 밖에 없기에 고급 사향이 필요하지 않은 관계로 좋은 최신폰이 필요가 없기도 하고 가장 필요한 건 끊김 없는 LTE 환경입니다. 비싼 녀석을 살 이유가 없습니다. 


1. 외형 

1) 배송 및 가격

알리 직구는 대체로 2주 이내에 배송이 완료되는데 최근 중국에서 발송되는 패키지나, 항공 운송을 통해 7일 이내에 발송이 완료되는 경우가 많았습니다. 하지만 M3 PRO는 2주를 꽉 채워서 도착했습니다. 

출시 기념 10$ 쿠폰을 포함해서 159$ 약 18만원에 구매했습니다. 

2) 패키징 

패키징은 지난번과 동일하게 케이스, 휴대폰, 충전기의 작은 패키지로 도착했습니다. 간결하고 잡다한 것이 없는 패키징이 여전히 마음에 듭니다. 

외부박스

내부 1단 
박스를 열면 노란색 종이 상자에 휴대폰 케이스가 최 상단에 있습니다. 

내부 하단 충전기

패키지의 물품은 포코 기본 케이스, 휴대폰, 설명서, 유심교체 핀, 충전기와 케이블로 구성되어있습니다. 
케이스, 설명서, 스티커
케이스가 들어있는 상자에는 설명서와 스티커가 있는데 스티커가 들어있는 이유는 도무지 모르겠습니다. ; 뭔가 포코 이니셜이나 디자인이 퍼지길 원하는 것 같기도 합니다. 

3) 외형

외형은 요즘 보편적인 전면 카메라에 핀홀 형태의 셀피 카메라가 달린 구성입니다. 
전면 디자인

후면디자인
후면의 경우 기존엔 없던 POCO라는 글자가 매우 커졌습니다. 카메라는 F2 PRO에는 4개였는데 M3는 3개 달려있으며 2MP 2개 48MP 1개로 사진 성능은 기존에 비해 대폭 줄어들었습니다. 기존의 F2 PRO가 374 $ 였으니 절반의 가격에 이정도만 해도 딱히 불평할 사양은 아닙니다. 

우측면
우측면에 전원 겸 지문인식기와 볼륨버튼이 있습니다. 

왼편 상단에는 USIM 및 SD카드가 들어가는 자리가 있습니다. 

유심 집 삽입부는 이런식으로 2개의 유심과 1개의 마이크로SD가 들어갈 수 있는 형태입니다.

케이스 하단 충전
충전홀에 이물질을 막는 형태로 케이스가 구성되어있는데 저는 불편해서 제거하였습니다. 

POCO F2 PRO와 비교하자면 전면 지문인식이 파워버튼으로 이동했고, 팝업 셀피 카메라가 없으며 카메라 성능이 낮아졌다. 입니다. 그리고 LTE 연결 성능은 향상되었다. 

2. 성능 

1) 기기성능 

성능은 제가 사용하는 프로그램들은 지연 없이 잘 되기에 밴치마크로 대체하겠습니다. 저가형 기기인 만큼 점수는 높지 않습니다. 

왼쪽이 F2 PRO , 오른쪽이 M3 PRO
전년도에 나온 F2 PRO에 비해서 절반 수준이며, 멀티 코어 점수는 1/3밖에 되지 않습니다. 
geekbench5 를 사용했고 국내 폰으로는 LG 벨벳하고 유사한 수준의 성능입니다. 

2) 배터리 성능

전화, 웹툰, 주식, 유투브 등 별다른 배터리를 쓸 일을 만들지 않는 저로서는 충전 없이 2일간 사용할 수 있었습니다. 

3) 사진 성능 

사진 성능은 F2 PRO에 비해 상당히 안좋아졌습니다. 2mp 카메라 두대는 존재 의미를 모르겠고 광각 성능도 낮아져서 정말 그냥 카메라가 있다는 위안 정도 수준입니다. 마침 같은 장소의 같은 메뉴를 서로 다른날 찍은 사진이 있습니다. 아래 사진들을 비교해보시면 사진 성능의 차이를 체감할 수 있을 것 입니다. 

poco m3 pro로 찍은 사진

poco f2 pro는 카메라에 전화가 달려있다는 느낌이었다면. 휴대폰에 카메라가 달려있다는 수준이라. 근접한 사진에 대한 품질이나. 뛰어난 화각을 기대하기엔 어렵습니다. 

POCO F2 PRO 사진 

위 2개의 사진을 비교해보면 알 수 있듯 화각, 색의 깊이, 선명도, 좌 우 사이드의 뭉개짐 등 모든 카메라의 성능은 정말 열악합니다. 

3. 오류

샤오미폰을 두번째 사는 저로서는 오류에 대해 기술하지 않을 수 없습니다. POCO F2 PRO를 샀을때는 LTE가 기지국 변경시 5분간 접속이 되지 않는 문제가 있었기 때문 입니다. 

다행이도 LTE는 끊김없이 연결이 잘 됩니다. 5G 폰이지만 실제 5G요금제는 고객센터에 전화해도 사용하지 말라고 권고를 하는 관계로 아직은 먼 이야기 입니다. 

LG UPLUS에서는 별다른 등록이나 변경 없이 LTE 환경을 사용할 수 있습니다. 

오류 1) WIFI 

WIFI 신호를 잘 잡지 못합니다. MODEM 이미지 파일의 오류가 있는 것 같습니다. 한번 연결되면 끊김 없이 잘 써지지만, 기본적으로 연결이 잘 안됩니다. 

오류 2) 충전 

퀠컴 퀵 차지 3.0은 매우 잘 인식하지만 퀵차지가 아닌 2.0A, 2.4A 고속 충전은 잘 안식하지 못해서 충전이 더디던지 끊겼다 다시 되는 현상이 있습니다. 따라서 충전기는 퀵차지 3.0 제품이나 기본 제공 충전기를 권장합니다. 


오류3) usim 

샤오미 계열의 다른 휴대폰에 usim을 꽂아서 활성화 했다가 다시 돌아 올 경우 역시 타 통신사와 통화가 안되는 경우가 발생 합니다. 이 경우 FASTBOOT ROM을 재설치 하면 해결 됩니다. 

2021년 5월 24일 월요일

spring boot기반 batch 프로젝트 생성부터 mybatis 연동 단순하게.

 개요 

회사에서 배치 프로세스를 분리해야 하는 일이 생겼습니다. 인터넷으로 이런 저런 조사를 해보는데 정상적인 문서를 찾기 힘든 것이 현실이고, 구했다 하더라도 입맛에 맞는 구조가 아닌 경우가 많아 최소한의 soruce를 통해 mybatis 연동(MariaDB)까지 정리 해보려 합니다. 

본문의 순서는 다음과 같이 진행 하겠습니다.

1. Eclipse spring Boot 설치 

2. Project 생성 

3. Project 환경 설정 

4. application.properties 설정 

5. Source 코드 생성 

6. 발생할 수 있는 오류

순으로 정리되어있습니다. 캡처를 할 경우 너무 여러 장을 찍어야 하기에 설치 순서는 동영상으로 대체하였습니다. 

그럼 시작하겠습니다. 


1. Eclipse spring Boot 설치 

일단 spring boot 프로젝트를 생성하기 위해서는 Eclipse의 Spring Tool이 필요합니다. Eclipse의 Help 버튼을 눌러 Market Place에 들어가서  Spring Tool을 검색해서 Install 하고 Eclipse를 다시 시작 합니다. 

Spring Tool 인스톨 영상 

2. Project 생성 

Project의 생성은 create a new project -> spring 검색 -> Spring Starter Project -> java 버전 선택 -> project 및 package 명 변경 -> spring batch 선택 -> Mybatis Framework 선택 -> finish 순으로 진행하였습니다. 

project 생성 영상 


3. Project 환경 설정


환경설정에 연관된 파일
환경설정에 연관된 파일은 2가지입니다. 
1) pom.xml을 통한 라이브러리 환경 설정과 
2) 프로젝트 환경 설정의 application.properties 파일입니다. 

1) 라이브러리 환경설정 

우선 저는 mariadb를 연결해야 하기에 mariadb의 maven 라이브러리를 pom.xml에 추가하겠습니다. maven을 통한 라이브러리 추가는 google 에서 maven +라이브러리 를 검색하거나 
메이븐 사이트 https://mvnrepository.com/

위 링크로 접속하셔서 해당 라이브러리를 검색하시면 쉽게 dependency 정보를 찾을 수 있습니다. 

mysql 드라이버 정보


검색된 메이븐 라이브러리중 자신에게 해당하는 버전을 클릭하면 이렇게 하단에 디팬던시 정보가 표기되어 해당 정보를 pom.xml에 붙여넣기 하면 됩니다.

pom.xml에 붙여넣기



4. application.properties 설정 

application.properties에 프로젝트에 사용할 환경 설정을 추가해줍니다. application.properties 는 spring toole에서 자동 완성을 지원합니다. 구동 화면은 아래와 같습니다. 
application.property 설정 영상

실제 application.properties에 수록된 내용은 다음과 같습니다. 
#데이터 베이스 연결에 사용할 드라이버 
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#데이터베이스 url 
spring.datasource.url=jdbc:mysql://192.168.0.1:6330/test?serverTimezone=UTC&characterEncoding=UTF-8
#계정 
spring.datasource.username=testUser
#비밀번호
spring.datasource.password=testUser

#mapper 파일이 적재될 위치 sql mapper 파일과 동일하게 맞춰준다.
mybatis.mapper-locations=com/springBatchTest/mapper/*.xml
#커스텀 환경변수 
custom.test=test-value


5. Source 코드 생성 

우선 application.properties에 설정한 위치에 mapper xml파일을 만들어줍니다. 
환경설정 순서(클릭시 커짐)
현재 프로젝트의 application 설정이 마이바티스 까지 연동되는 구조는 다음 순서에 따라 이루어집니다. 
application에서 모델을 연결해주는 것은 mapper location.
모델과 컨트롤러의 연결은 xml 파일내의 namespace가 classPath로 역할을 하게 되더군요. 

수정 및 생성한 소스프로그램은 총 4개의 파일입니다. 
1) testMapper.xml 
2) TestDataMapper.java
3) TestDataService.java
4) SpringBatchTestApplication.java
차례대로 기술하겠습니다. 

1) testMapper.xml 

mysql DB에서 데이터를 조회할 sql 문을 입력합니다. 또한 namespace에 java에서 연동할 인터페이스의 패키지 경로와 파일명을 입력합니다. 
<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 인터페이스의 패키지 경로와 클래스명 -->
<mapper namespace="com.springBatchTest.mapper.TestDataMapper">

<!-- 수행할 sql -->
    <select id="getTestDataList" resultType="java.util.Map">
<![CDATA[
SELECT 1 AS COL1 
UNION ALL 
SELECT 2 AS COL1
]]>
    </select>
</mapper>
sql은 간단하게 테스트를 수행할 수 있게 정의하였습니다. 

2) TestDataMapper.java

mapper.xml 에 정의된 위치에 정의된 명칭으로 생성해줍니다. 인터페이스 내부에는 xml에 정의된 sql과 동일한 명칭 그리고 동일한 데이터 형식을 갖고 있는 함수가 있어야 합니다. 
package com.springBatchTest.mapper;

import java.util.List;
import java.util.Map;

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface TestDataMapper {
List<Map<String, Object>> getTestDataList();
}
mybatis framework과 java Application간의 인터페이스 역할만 해주므로 정말 내용이 없습니다. SQL이 늘어나는 만큼 증가합니다. 

3) TestDataService.java

서비스에는 인터페이스에 정의된 형식과 동일한 서비스를 구성해줍니다. 이 파일 역시 자동 연결을 위한 선언 ,  서비스 선언 이외에 특별한 내용이 없습니다. 
package com.springBatchTest.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Map;

import com.springBatchTest.mapper.TestDataMapper;

@Service
public class TestDataService {
    @Autowired
    TestDataMapper testData ; 

    public List<Map<String, Object>> getTestDataList() {
        return testData.getTestDataList(); 
    }
}


4) SpringBatchTestApplication.java

package  com.springBatchTest;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

import java.util.List;
import  java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import  com.springBatchTest.service.TestDataService;

@SpringBootApplication
public class SpringBatchTestApplication {
public static void main(String[] args) {
try (ConfigurableApplicationContext ctx = SpringApplication.run(SpringBatchTestApplication.class , args)) {
SpringBatchTestApplication m = ctx.getBean(SpringBatchTestApplication.class );
m.selectDataf(); 
}catch (Exception e) {
// TODO: handle exception
System.out.println(e.getMessage());
}
}
  @Autowired
  TestDataService  testDataService;
    public void selectDataf() {
    List<Map<String, Object>> tmp = testDataService.getTestDataList();
    System.out.println(tmp.size());
    }
}


자 이제 테스트용 코드 생성은 끝났습니다. 

6. 발생할 수 있는 오류

1) 정상 처리 시 디버깅 콘솔 내용 


2021-05-24 19:28:03.528  INFO 10964 --- [           main] c.s.SpringBatchTestApplication           : Starting SpringBatchTestApplication using Java 15.0.1 on DESKTOP-3QN7REN with PID 10964 (D:\blogger\springBatchTest\target\classes started by jinyboys in D:\blogger\springBatchTest)
2021-05-24 19:28:03.533  INFO 10964 --- [           main] c.s.SpringBatchTestApplication           : No active profile set, falling back to default profiles: default
2021-05-24 19:28:06.259  INFO 10964 --- [           main] c.s.SpringBatchTestApplication           : Started SpringBatchTestApplication in 3.553 seconds (JVM running for 4.553)
2021-05-24 19:28:06.277  INFO 10964 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...

2021-05-24 19:28:06.771  INFO 10964 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2
2021-05-24 19:28:06.897  INFO 10964 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
945  INFO 10964 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

수행할 로직이 없기 때문에 약 3,4초만에 수행되고 종료되며 임시로 생성된 sql에서 받는 데이터 2건에 대한 사이즈 정보가 표기됩니다. 

2) application.properties내의 mapper 경로가 잘못 된 경우  

빨간색 글자처럼 파일을 찾을 수 없다는 메시지가 나옵니다. 
2021-05-24 19:31:39.543  INFO 7172 --- [           main] c.s.SpringBatchTestApplication           : Starting SpringBatchTestApplication using Java 15.0.1 on DESKTOP-3QN7REN with PID 7172 (D:\blogger\springBatchTest\target\classes started by jinyboys in D:\blogger\springBatchTest)
2021-05-24 19:31:39.553  INFO 7172 --- [           main] c.s.SpringBatchTestApplication           : No active profile set, falling back to default profiles: default
2021-05-24 19:31:42.462  INFO 7172 --- [           main] c.s.SpringBatchTestApplication           : Started SpringBatchTestApplication in 3.802 seconds (JVM running for 5.327)
2021-05-24 19:31:42.479  INFO 7172 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...

2021-05-24 19:31:42.987  INFO 7172 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2021-05-24 19:31:43.074  INFO 7172 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2021-05-24 19:31:43.086  INFO 7172 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.
Invalid bound statement (not found): com.springBatchTest.mapper.TestDataMapper.getTestDataList

3) mapper.xml 파일 내부 SQL구문의 ID와 mapper.java 인터페이스가 불일치 할 경우

위와 동일한 오류가 발생합니다. 
 Invalid bound statement (not found): com.springBatchTest.mapper.TestDataMapper.getTestDataList

4) service.java 클래스 내에 @Autowired 설정이 없는 경우

Cannot invoke "com.springBatchTest.mapper.TestDataMapper.getTestDataList()" because "this.testData" is null

5) mapper.java 클래스가 없거나 비정상 , @mapper 설정이  없는 경우

Description:

Field testData in com.springBatchTest.service.TestDataService required a bean of type 'com.springBatchTest.mapper.TestDataMapper' that could not be found.

The injection point has the following annotations:
- @org.springframework.beans.factory.annotation.Autowired(required=true)


Action:

Consider defining a bean of type 'com.springBatchTest.mapper.TestDataMapper' in your configuration.

Error creating bean with name 'springBatchTestApplication': Unsatisfied dependency expressed through field 'testService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'testDataService': Unsatisfied dependency expressed through field 'testData'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.springBatchTest.mapper.TestDataMapper' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}


이상 스프링 배치 프로젝트 생성부터 mybatis 연동까지 그리고  발생할 수 있는 오류까지 알아봤습니다. 

이렇게 쉽게 4개의 파일만 설정하면 연동할 수 있는 기본적인 형태를 이해하지 못하고 엉뚱하게 정리 해 놓은 글들이 너무 많아 직접 정리를 해 보았습니다. 누군가에게 도움이 되면 좋겠네요 ㅎㅎ. 

2021년 5월 23일 일요일

자전거 코스 대전 -> 청주 무심천

개요 

지난번에 약 80km의 거리 주행 이후 이번주에는 100km를 넘겨보겠다는 생각에 대전에서 청주의 무심천을 잇는 약 110km 주행 코스를 만들어보았습니다. 참고로 청주의 무심천은 봄철에 벚꽃이 이쁘게 피어 사람들이 많이 다니는 곳 입니다. 

코스 난이도 기난이도 기준은 3가지이며 다음과 같습니다. 

1. 거리 20km 마다 1점 

2. 업힐 200m 마다 1점 

3. 보급 20km 마다 확보되지 않을 경우 보급 주의를 표시하도록 하겠습니다. 보급은 편의점을 기준입니다. 보급 상태가 주의 일 경우 50km 당 음료수 1리터, 에너지바 1개는 있어야 달릴 만 합니다. 

점수가 높을수록 난이도가 어렵다고 생각 하시면 됩니다. 평균은 거리와 업힐의 평균입니다. 

평균 점수 기준  1~3점 초급, 4~6점 중급 7~9점 고급  10점 이상 특급 되겠습니다.

현 코스의 난이도는 다음과 같습니다. 

1. 거리 :  6

2. 업힐 :  3

3. 보급 : 주의 

평균 : 4.5점 중급


1. 코스파일 

대전 -> 청주 무심천.tcx

대전 -> 청주 무심천.gpx

코스 파일은 두 가지 중 본인에게 맞는 파일을 선택해서 다운로드 받으시면 됩니다.


2. 코스 개요 

대전에서 강변을 따라 청주를 일주하는  코스이며 중간에 약 5km 가량의 국도가 포함되어있습니다. 보급포인트는 약 40km 언저리에서 편의점과 짬뽕집(개인적인 욕심), 식당가 등을 표기하여 놓았습니다. 

코스의 특징은 경유 4에 해당하는 대청댐을 통과 할 때 약 12%의 경사가 있고 해당 지역에 고도가 몰려 있습니다. 따라서 후반 체력이 딸린다면 우측으로 1회전 후반 체력에 문제가 없다면 좌측으로 1회전을 도시면 되겠습니다. 

대전 - 청주 무심천 1회전 코스

해당 구간 레포트
획득고도는 643m 로 대전 -> 세종 호수공원 코스에 비해 약 250m 밖에 높지 않으나 무심천 -> 대청댐 구간이 약한 오르막이 지속되는 형태입니다. 
초반에 강오르막 하나 지나고 쭉 편하게 가느냐. 후반을 힘들게 달리느냐는 개인 취향에 맡기겠습니다. 


3. 코스 주행 후기 

해당 코스 주행기록 
시작 위치를 약간 조정하는 바람에 gpx 리포트보다 20km 짧게 다녀왔습니다. 매도 먼저 맞는 편이 좋을 것 같아 대청댐 방향으로 출발했습니다. 원래 코스보다 짧아졌지만 업힐의 변화는 없기에 실제 오르막은 427이 맞을 것 같습니다. 

100km 가까운 주행인데 저녁 약속이 있어 중간 보급지점을 모두 건너뛰었더니 후반부에 칼로리 부족으로 힘이 딸립니다. 에너지바 2개정도는 보급품으로 챙겨가는 것이 어떤가 합니다. 실수로 포도당 캔디도 안 챙기는 바람에 더욱 힘들었습니다. 

또한 여름에 가까운 기온 때문에 땀을 많이 흘려 물도 1리터로는 부족했습니다. 

주의사항 
1. 지도상으로 12시 방향 정도 되는 부근에 공사현장이 있습니다. 청주와 세종의 중간 점. 길이 좁아지고 코너가 안보이니 속도를 줄이세요. 오늘 길막 당해서 사고났습니다. 블랙박스 안달고 갔는데 길막 한 분이 넘어지는 걸 보시고 튀더라구요. 보험 가입해야겠습니다. ; 

2. 2회 주행 결과 해당 길에 노인분들이 MTB로 많이 다니시는데 오르막에서 와리가리를 심하게 합니다. 내리막 추돌 주의하셔야 합니다. 

2. 청주방면은 화장실과 휴식공간이 별로 없고, 청주와 세종 사이에 보급을 할 수 있는 포인트가 약 1시간 가량 찾을 수 없습니다.  무엇보다 물 확보를 꼭 하셔야 합니다. 그리고 2회 다녀온결과 청주엔 화장실이 없습니다. (있긴 한데 없다고 할만큼 적습니다. ) 

대청댐 현암정 전경

대청댐 현암정 전경

청주 무심천



2021년 5월 9일 일요일

자전거 코스 대전 -> 세종호수공원

 개요 

어김없이 봄은 왔고, 자전거 타기 좋은 날씨가 계속 되고 있습니다. 오랜만의 외출이기도 해서 큰 무리 없이 다녀올 수 있는 80km 의 코스를 만들어보았습니다. 획득 고도는 약 450m로 거리에 비해 업힐이 적은 편이며, 중간에 보급 포인트로 편의점과 식당의 위치를 찍어 놓았습니다. 

코스 난이도 기준을 잡아보았습니다. 난이도 기준은 3가지이며 다음과 같습니다. 

1. 거리 20km 마다 1점 

2. 업힐 200m 마다 1점 

3. 보급 20km 마다 확보되지 않을 경우 보급 주의를 표시하도록 하겠습니다. 보급은 편의점을 기준입니다. 보급 상태가 주의 일 경우 50km 당 음료수 1리터, 에너지바 1개는 있어야 달릴 만 합니다. 

점수가 높을수록 난이도가 어렵다고 생각 하시면 됩니다. 평균은 거리와 업힐의 평균입니다. 

평균 점수 기준  1~3점 초급, 4~6점 중급 7~9점 고급  10점 이상 특급 되겠습니다.

현 코스의 난이도는 다음과 같습니다. 

1. 거리 :  4

2. 업힐 :  2

3. 보급 : 주의 

평균 : 3점 초급


1. 코스파일

대전 -> 세종호수공원 tcx

대전 -> 세종 호수공원 gpx

코스 파일은 두 가지 중 본인에게 맞는 파일을 선택해서 다운로드 받으시면 됩니다. 

2. 코스 개요 

엑스포에서 대청댐을 가는 방면에 있는 원촌교에서 시작합니다. 원촌교는 벚꽃이 약 2km 가량 이어지는 벚꽃 시즌에 꼭 가볼만한 장소기도 하구요 주변에 주차할 수 있는 공간이 제법 있는 편입니다. 그래서 종종 활용합니다. 

1) 원촌교 

2) 보급 

3) 세종 호수 공원 서측 출입구

4) 세종 호수 공원 북측 출입구 

5) 보급 

6) 원촌교

의 코스입니다.

길은 100% 자전거길로 갈수도 있고, 천변은 자전거길과 국도가 같이 있어 선택해서 달릴 수 있습니다. 코스 자체는 자전거 전용도로로 구성되어있습니다. 

코스 레포트
대부분 평지로 이루어져 있고 대부분 4% 경사각이 낮은 업힐입니다. 또한 업힐이 길게 이어지지 않기 때문에 별로 힘들지 않고 다녀올 수 있습니다. 


3. 주행 후기 

평소 자주 다니던 길 이기에 별 무리 없이 다녀왔지만, 그간 체력 훈련을 게을리 한 상태여서 다녀오고 나니 조금 아프긴 합니다. 자전거 도로 좌우로 쉴 곳이 자주 있어 체력이 약한 분들도 어렵지 않게 왕복 할 수 있을 거라 보입니다. 

왕복 85km 업힐은 378m 왕복 이후 엑스포 남문 광장을 조금 돌아 100km를 채웠습니다. gps는 가끔 보며 자전거 길만 따라가면 끝이라 딱히 안내할게 없습니다. 길 찾기 난이도 슈퍼 이지 입니다. 


1) 보급 진입로 
세종시에 거의 도착할 때 쯤 합강공원 언저리에 2개의 보급 포인트가 있습니다. 하나는 GS 편의점이고 다른 하나는 식당과 까페가 모여있는 곳입니다. 참고로 까페에서는 음료 외에 볶음밥 같은 몇 가지 요리를 판매합니다. 다만 일 하시는 분이 주문을 잘 못 알아듣습니다.
그래도 밥 달라면 밥 주고 빵 달라면 빵 주니 대충 먹으면 됩니다. 빵 달라는 데 국 주는 정도는 아닙니다.

편의점은 자전거 도로와는 50m 가량 떨어져있어 지도를 보고 찾아가셔야 합니다. 

식당은 아래 나온 동영상 대로 진입로 입간판이 세워져있어 보고 들어가시면 됩니다. 다만 너무 늦은시간에는 영업하지 않는 것 같습니다. 따라서 야간 라이딩시에는 편의점을 이용하시면 되겠습니다. 

대전 -> 세종 호수공원 풀코스 파일입니다. 2배속 할걸 그랬네요 ㅎㅎ;;;



이상 안전하고 즐거운 자전거 여행 되시길 기원합니다. 

2021년 4월 2일 금요일

java runtime exec() 에서 curl post 사용 오류 및 해결

 개요 

오픈 소스 기반 프로그램 수행을 위해 curl 커맨드를 외부 데이터베이스와 연결하여 결과를 받는데 windows 환경에서는 잘 되었지만 linux 기반에 동일한 tomcat과 java 환경에서 수행 시 오류가 발생하였습니다. 오류 메시지는 종류도 다양하지만 주로 아래와 같은 메시지가 나왔습니다. 

보내는 메시지는 다음과 같았습니다. 

String queryTable = "curl -X POST -H \"Content-Type:application/json\" " + " -d  @" + filePath + " " + serverUrl;

형태의 비교적 단순한 문장이었고, 쿼리를 담은 파일을 포함하여 발송합니다. 

여기서 filePath 는 데이터를 전송하기 위해 생성한 데이터 파일의 절대경로입니다.  serverUrl은 전송 대상 서버의 전체 url 입니다. 

옵션 중 -d 뒤의 @ 옵션을 제거하면 파일이 아닌 문자 데이터 형태로 전송할 수 있습니다. 

에러 메시지는 대략 모두 비슷한 유형으로 <h1>이 H4로 바뀌는 정도의 오류였습니다. 

 illegal character varchar='" 

<h1>bad message 400

windows 기반의 tomcat9에서 수도 없이 많은 테스트를 거친 프로그램이기에 믿고 리눅스에 맡겼더니 배신도 이런 배신이 없습니다. 


문제코드 

여러 검색 결과 문제는 java 자체의 Process 클래스에 의한 처리였습니다. 외부 명령을 실행하기 위해 다음과 같은 코드를 작성하여 사용해왔습니다. 

public static String executeCommand(String command) {
StringBuffer output = new StringBuffer();
Process p;
String outString = ""; 
try {
p = Runtime.getRuntime().exec(command);
BufferedReader reader = new BufferedReader(new  InputStreamReader(p.getInputStream()));
String line = "";
while ((line = reader.readLine()) != null) {
output.append(line + "\n");
}
p.waitFor();
outString= output.toString();
            if (p.exitValue() != 0) {
                // shell 실행이 비정상 종료되었을 경우
            BufferedReader errorBufferReader = null; 
            StringBuffer errorOutput = new StringBuffer();
                errorBufferReader = new BufferedReader(new InputStreamReader(p.getErrorStream(), "EUC-KR"));
                String msg  = "";
                while ((msg = errorBufferReader.readLine()) != null) {
                    errorOutput .append(msg + System.getProperty("line.separator"));
                }
            }
 
} catch (Exception e) {
logger.error(e.getMessage());
}
return output.toString();
}

상위 스크립트 처럼 java 자체의 process runtime exec를 사용 할 경우 linux나 macos 환경에서 알 수 없는 오류로 고생하게 됩니다. 또한 java 자체의 processbuilder를 활용하더라도 동일한 현상이 나타나는 것을 확인하였습니다. 

여러 문서들을 검토 한 결과 Apache commons common-exec 를 사용할 경우 해당 문제가 해결된다고 합니다. 


https://commons.apache.org/proper/commons-exec/

위 라이브러리를 직접 다운 받아 사용하시면 됩니다. maven 환경이라면 dependency에
아래와 같은 항목을 추가해주세요
pom.xml
<dependency>  
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-exec</artifactId>
    <version>1.3</version>
</dependency>  

해결 코드 

package Controller;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.PumpStreamHandler;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class apCmdLine {

private static final Logger logger = LoggerFactory.getLogger(apCmdLine.class);
 
/**
  * linux 에서 shell script가 비정상 실행되는 오류에 대한 fix 
  * @param command
  * @return
  * @throws Exception
  */

 public String execAndRtnResult(CommandLine command) throws Exception {
      String rtnStr = "";
              DefaultExecutor executor = new DefaultExecutor();
      ByteArrayOutputStream bs = new ByteArrayOutputStream();
      PumpStreamHandler streamHandler = new PumpStreamHandler(bs);
      executor.setStreamHandler(streamHandler);
      int exitCode = executor.execute(new CommandLine(command));
      rtnStr = bs .toString();
      logger.info("exitCode : " + exitCode);
      logger.debug("outputStr : " + rtnStr );
      return rtnStr;
}
}


추가오류 

1. 전송 메시지 출력 

linux 환경에서 정상적으로 데이터 처리가 되고 난 이후 rtnStr로 받은 결과에 위 사진과 같은 메시지가 중간중간에 끼어드는 문제가 발생하였습니다. 
r -   % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
100    90  100    70  100    20  14629   4179 --:--:-- --:--:-- --:--:-- 17500

이 문제를 해결 하기 위해 JSON 스트링이 아닌 데이터에 대한 필터를 고려해보았으나, 라인단위의 중간에 해당 데이터가 끼어드는 경우도 발생하였습니다. 검토 결과 CURL로 데이터를 전송할 경우 전송 결과에 대한 알림이 발생한다고 합니다. 이것 역시 WINDOWS 환경에선 발생하지 않습니다. 

검색에 시간은 제법 들었고 필터도 해보고 여러 삽질을 해봤지만 실제 고치는 방법은 매우 단순했습니다. URL 앞에 -s만 붙여주면 되는 일이었습니다.  

보내는 메시지에 대상 서버 주소 앞에 -s 옵션을 붙여줍니다. -s 는 silent 시스템 메시지를 보내지 마라는 명령어입니다. 
String queryTable = "curl -X POST -H \"Content-Type:application/json\" " + " -d  @" + filePath + " -s " + queryServer; 

비슷한 유형을 정리해 놓은 글을 링크합니다. (영문입니다.)

2. 메시지 길이가 길어 스트림을 초과 

이런 경우는 흔치 않을 것 같습니다. json 형태의 데이터를 보내고 그 결과를 받다보니 curl에서 전송할 수 있는 범위를 넘어가더군요. 길이가 길 경우 교환 데이터를 파일 형식으로 보내면 해당 문제는 사라집니다. 하지만 이 방법 역시 문제가 있습니다. json 파일 형태로 보내다보니 파일을 작성한 시점에 동시 트랜잭션이 여러 건 일어나고 서로 다른 데이터를 요구했을 경우 해당 트랜잭션에서 내가 원하는 데이터가 정말 갔다는 것을 보장할 수 없죠. 결국은 runtime exec를 통해 데이터를 교환하는 것 보단 httpconnection을 통해 데이터를 교환하는 것이 적절한 처리 방법으로 생각됩니다. 


이상입니다. 
읽어주셔서 감사합니다. 
읽으신 분에게 도움이 되면 좋겠네요. 

2021년 3월 16일 화요일

로봇 청소기 샤오미 드리미 F9 개봉, 로봇청소기 4세대 비교

 개요

샤오미 로봇 청소기를 산지도 벌써 3년이 다되었습니다. 그간 기술 진보도 제법 있었을 거란 생각도 들고, 옮긴 회사 사무실에 청소하시는 분이 따로 없어 로봇 겸사겸사 로봇 청소기를 신규 구매 하였습니다. 

첫 로봇 청소기는 3년 전 알리익스프레스를 통해 236$ (약 25만원) 에 구매했습니다. 당시 국내 구매는 50만원 선으로 제법 비쌌습니다. 따라서 직구가 가능한 사람이면 다들 직구로 구매 했을 거라 생각됩니다. 

샤오미 로봇 청소기 4세대 구매내역

이번에 산 드리미는 티몬을 통해 이전보다 저렴한 21만4천원에 기능까지 업그레이드 된 AI 학습 기반 제품입니다. 

샤오미 드리미 F9 구매 내역

최근은 직구족이 많다 보니 국내 사이트에서도 해외에서 구매하는 것 보다 더 저렴하게 구매할 수 있는 제품들이 늘어가고 있는 추세입니다. 이런 점은 제법 긍정적입니다. 예전에 B모 사이트의 미밴드 물량 독점 횡포 같은 일이 어디선가는 일어나고 있겠지만, 점점 더 나아지겠지요. 국내 AS를 위해 최저가 보다 3만원 비싸게 구매했습니다. 하지만 알리익스프레스에도 210달러 인 것을 감안하면 국내가 저렴했습니다. 

3월 9일에 주문했고 15일에 도착했으니 구매 소요 기간은 약 일주일입니다. 오래 걸리지 않고 금세 도착하는 걸 보면 직구 정말 할만합니다. 

첫 번째와 동일하게 샤오미를 선택하게 된 이유는 청소 능력과 내구도 입니다. 3년 사용 기간 동안 아무 문제 없이 사용했습니다. 하지만 제법 띨 해서 종종 혼자 함정에 빠지곤 했는데 매주 1회 정도 문턱에 걸리곤 했습니다.  F9는 그러지 않기를 빌면서 비교 시작하겠습니다. 


1. 스펙 비교 


스펙만 봐서는 스마트 맵핑과 배터리 용량 , 청소 흡입 압력 이외에 큰 차이는 없어 보입니다. 하지만 까보면 제법 세세한 곳에 차이가 있습니다. 문턱 등반도 두 기종 모두 2cm입니다. 

2. 제품 비교 

 포장 비교 

4세대의 포장과 크게 달라진 점 없이 2중 포장에 종이상자입니다. 

4세대 포장 

외부박스

내부박스

 드리미 F9 포장 

외부박스 
내부박스

포장지에 로봇청소기의 모습과 박스 색감만 바뀐 정도. 별 차이 없습니다. 

구성 비교 

샤오미 4세대 

청소기, 충전기, 전원케이블, 필터, 먼지통 청소 브러시 물통
 걸레받이

샤오미 드리미 F9

청소기, 충전기, 전원케이블, 사이드브러시, 물통
샤오미 4세대에는 필터가 하나 추가로 들어가 있고, 먼지 제거용 브러시가 별도로 있는 데 드리미 F9에서는 먼지통 위치에 내장되어있으며, 4세대 까지는 물걸레에 물을 공급하는 부분이 삼투압 필터였던 반면 F9에서는 급수 펌프로 교체되었기 때문에 걸레받이가 존재하지 않습니다. 

물통 차이점 

샤오미 4세대 


삼투압 필터 

샤오미 드리미 F9

급수 모터 장착
이외에도 물통의 용량이 2배가량 늘었습니다. 다만 급수모터의 경우 연속 사용 시간이 290시간이란 점을 보면.... 수명이 길지 않은 소모품인 것 같습니다. 다만 모터가 생겨 수량 조절이 가능합니다. 물이 부족할 경우 알림을 주지는 않습니다. 삼투압 필터와의 차이는 삼투압 필터는 늘 물이 새어 나오기에 물 충전 후 몇 일 지나면 않으면 걸레에서 썩은 내가 납니다. 하지만 급수 모터는 필요한 양 만큼만 분사 하기에 이 부분이 개선 되었습니다. 

사이드 브러시 스테이
4세대에선 먼지통 청소용 브러시를 잃어버리지 않게 잘 보관해야 했던 반면 드리미는 내장되는 위치가 있어 잃어버릴 염려가 없습니다. 

충전 도크 차이점 


왼쪽이 드리미, 오른쪽이 4세대
충전 도크는 모양이 살짝 바뀌었고, 걸레받이가 없는 점이 다릅니다. 

사이드 브러시 차이점 

나사 고정 샤오미 4세대 

나사 없이 끼우는 형태의 드리미 F9
외형의 다른 점만 비교하자면 아주 약간 편리해졌고 아주 약간 좋아진 것 같습니다. 

3. 청소 능력 차이 

가장 큰 차이는 기존 시각 내비게이션 1.0이 2.0으로 업그레이드되었다는 부분인데요 기존에는 한번 청소한 맵을 기억하지 못해서 청소기가 매번 부딪히고 깨졌던 반면 이제 학습을 통해 경로를 자동 지원한다고 합니다. 

두 장비의 청소와 장애물 넘는 모습에 대한 영상을 기록해보았습니다.  저희 집에서 찍을 수 있는 영상의 특징이 있다면. 바로 문턱을 제거하지 않고 문턱에 경사판을 설치한 곳과 경사판이 없는 2cm의 낮은 문턱이 있어 청소기의 경사로 등판 성능을 점검할 수 있다는 것이네요. 

샤오미 4세대 



샤오미 드리미 F9

장애물이 모여있는 작은 방 쪽인데요 두 장비 모두 벼랑 감지를 통해 추락하지 않고, 문턱도 경사로를 통해 원활하게 넘습니다. 그런데... 구형이 6분30초 신형이 5분에 작은방 청소를 완료한 영상을 보면 경사로에 걸렸을 때 탈출하는 알고리즘이 개선된 것으로 보이며 신형의 경우 코너 청소 시 회전이 빨라 전체적인 청소 시간이 단축되는 경향이 있는 것 같습니다. 

4세대도 저에게는 우렁각시처럼 안보일 때 청소해주고 늘 바닥을 깨끗하게 유지해주는 고마운 녀석이었습니다. 조금 띨 한게 문제였죠. 가야 할 곳을 가지 못하고, 특정 지역만 청소 시킬 수 없으며, 벽에 대가리를 항상 쿵쿵 찌어대는... 조금 많이 모자란 자학적인 아이였죠. 드리미는 그 부분이 제법 해소된 것 같습니다. 

다음 영상은 AI 학습이 완료된 상태의 모습입니다. (3회 청소를 완료하면 AI학습이 완료됩니다.)

샤오미 드리미 F9 AI학습 이후 

학습이 완료된 이후는 모서리부의 장애물여부를 판단하기 위해 가볍게 부딛히는 것 이외에는 쿵쿵 찍어대는 현상도 없어졌고, 학습한 지역에 대한 청소 계획을 미리 세우고 시작하는 것 처럼 자연스럽게 움직여 청소 시간이 단축되는 편입니다. 

구역 청소를 수행하는 모습


이전에는 없던 소프트웨어 가상 벽과 지역 청소 기능 덕분에 이제 이물질을 닦기 위해 청소기가 40분 동안 앵앵 거리는 소리를 듣지 않아도 되어서 더욱 좋습니다. 

우리 집 우렁각시 더 예뻐졌네요. ㅎㅎㅎ

이상 샤오미 4세대에 대한 구매기를 마칠게요. 
새로운 로봇 청소기를 구매하시는 분에게 도움이 되면 좋겠네요. 
긴 글 읽어주셔서 감사합니다.