로봇청소기와 그리움

설에 가족 모두 울산에 같이 내려갔다가, 설 연휴 마지막 날에 아내와 첫째, 둘째를 놔두고 회사일로 나만 올라오게 되었다. 일주일 동안 회사일도 바쁘고, 감기 기운도 조금 있어서 일주일이 상당히 빠르게 흘렀다. 오늘 올라오고 있다는 전화 통화를 하고, 불현듯 일주일 동안 로봇청소기의 먼지통을 비우지 않았다는 생각이 들었다.
전기밥을 먹어야 하는 곳에서 열심히 충전하고 있는 로봇청소기의 뚜껑을 열고, 먼지통을 꺼내서, 다용도실로 걸어가면서 먼지통의 뚜껑을 열었는데, 먼지가 거의 모이지 않았다는 것을 알게 되었다. 비우려고 걸어가던 발걸음이 허탈하기도 했는데, 가장 먼저 든 생각은…

‘거봐, 우리 딸들이 그동안 먼지를 다 만드셨군!!’

조금 모인 먼지라도 쓰레기봉투에 버리고 청소솔로 내부를 깨끗하게 쓸어내면서, 다시 이런 생각이…

‘그래, 딸들이 독립을 하면, 로봇청소기 비울 일도 잘 없겠군!’

비운 먼지통을 들고 다시 로봇청소기에게 가서 먼지통을 넣으면서는 이런 생각이…

‘그런데, 딸들이 독립을 하고, 집에 아내와 둘 밖에 없으면, 먼지통의 먼지가 참 그립겠구나… ‘

Spring boot JPA

source code 는 아래 github 에서 받을 수 있습니다.
https://github.com/nanbean/springReact 의 spring-jpa branch해당 source code 받지 말고 아래대로 차근 차근 scratch 부터 만들어 보는 것을 추천합니다.

# 해당 source code 를 통해서 동작성을 빠르게 확인하려면,
$ git clone -b spring-jpa https://github.com/nanbean/springReact.git lcms
$ cd lcms
$ mvn spring-boot:run

이후 REST API 동작성 확인 으로 이동하시면 됩니다.

이전 spriing react 최신 기준으로 시작합니다.(Spring boot React)

spring-boot-devtools, h2, JPA, hsqldb, lombok 을 Dependency 에 추가

1. 이전 project 에서 pom.xml 의 dependency 에 spring-boot-devtools, h2, jpa, hsqldb, lombok 을 추가한다. dependencies 하위에 추가합니다.

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
		</dependency>

		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>


		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>

		<dependency>
			<groupId>org.hsqldb</groupId>
			<artifactId>hsqldb</artifactId>
			<scope>runtime</scope>
		</dependency>

		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>

Content.java 추가

1. src/main/java/com/lge/lcms/content 디렉토리를 만들고 Content.java 파일을 하위에 생성합니다.

title 과 genre 만 가진 간단한 Entity 입니다.

package com.lge.lcms.content;

import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Column;

@NoArgsConstructor
@Table
@Getter
@Entity
public class Content {

	@Id
	@GeneratedValue
	private Long id;

	@Column
	private String title;

	@Column
	private String genre;

	@Builder
	public Content(String title, String genre) {
		this.title = title;
		this.genre = genre;
	}
}

ContentRepository.java 파일 추가

1. src/main/java/com/lge/lcms/content  하위에 ContentRepository.java 파일을 추가합니다.

package com.lge.lcms.content;

import org.springframework.data.jpa.repository.JpaRepository;

public interface ContentRepository extends JpaRepository<Content, Long> {

}

ContentRepositoryTest.java 파일 추가

1. src/test/java/com/lge/lcms/content  하위에 ContentRepository.java 파일을 추가합니다.

package com.lge.lcms.content;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

import org.junit.Test;
import org.junit.After;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
public class ContentRepositoryTest {

		@Autowired
		ContentRepository contentRepository;

		@After
		public void cleanup() {
			contentRepository.deleteAll();
		}

		@Test
		public void getTest() {
			contentRepository.save(Content.builder()
				.title("Incredible")
				.genre("Action")
				.build());

			List<Content> contentList = contentRepository.findAll();

			Content content = contentList.get(0);
			assertThat(content.getTitle(), is("Incredible"));
			assertThat(content.getGenre(), is("Action"));
		}
}

Test 실행

1. Test 를 통해서 Build 가 정상적으로 되는지 Test Result 가 정상인지 확인 합니다.

$ mvn test
...
...
[INFO] 
[INFO] Results:
[INFO] 
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 29.654 s
[INFO] Finished at: 2019-01-30T13:36:29+09:00
[INFO] Final Memory: 42M/576M
[INFO] ------------------------------------------------------------------------

Rest API 추가

1. src/main/java/com/lge/lcms/web 디렉토리를 만들고 ContentRestController.java 파일을 하위에 생성합니다.

rest/content REST API 를 만듭니다.

package com.lge.lcms.web;

import com.lge.lcms.dto.ContentSaveRequestDto;
import com.lge.lcms.content.ContentRepository;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.GetMapping;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
@AllArgsConstructor
@RequestMapping("/rest")
public class ContentRestController {

	private ContentRepository contentRepository;

	@PostMapping("/content")
	public void savePosts(@RequestBody ContentSaveRequestDto dto){
		contentRepository.save(dto.toEntity());
	}
}

DTO 추가

1. src/main/java/com/lge/lcms/dto 디렉토리를 만들고 ContentSaveRequestDto.java 파일을 하위에 생성합니다.

View 를 생성합니다. View 와 Entity 는 구분하는 것이 좋습니다.

package com.lge.lcms.dto;

import com.lge.lcms.content.Content;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.Getter;

@Getter
@Setter
@NoArgsConstructor
public class ContentSaveRequestDto {

		private String title;
		private String genre;

		public Content toEntity(){
			return Content.builder()
				.title(title)
				.genre(genre)
				.build();
		}
}

REST API 동작성 확인

1. H2 활성화, src/main/resources/application.properties 에 아래 내용을 추가합니다.

H2 DB 를 웹브라우저를 통해서 접근하기 위해서입니다.

spring:
	h2:
		console:
			enabled: true

2. spring boot 실행

$ mvn spring-boot:run

3. post 수행

$ curl --header "Content-Type: application/json" --request POST --data '{"title": "Incredible","genre": "Action"}' http://localhost:8080/rest/content

4. 입베디드 H2 consle 진입

브라우저 주소창에 http://localhost:8080/h2-console 을 입력합니다.

5. connect

JDBc URL 에 jdbc:h2:mem:testdb 을 입력하고 connect 를 눌러서 진입합니다.

7. query 를 통해서 POST API 동작 확인합니다.

아래 query 를 입력하고 Run 버튼을 누릅니다.

SELECT * FROM CONTENT; 

Spring boot React

source code 는 아래 github 에서 받을 수 있습니다.
https://github.com/nanbean/springReact
해당 source code 받지 말고 아래대로 차근 차근 scratch 부터 만들어 보는 것을 추천합니다.

# 해당 source code 를 통해서 동작성을 빠르게 확인하려면,
$ git clone https://github.com/nanbean/springReact.git lcms
$ cd lcms
$ mvn clean install
$ java -jar target/lcms-0.0.1-SNAPSHOT.jar
후
browser 에서 http://localhost:8080/ 접속하면 됩니다.

Bootstrap 제작

spring 에서 제공하는 기본을 이용해서 skeleton code 를 만드는 과정입니다.

1. https://start.spring.io 에서 Gropu 에 com.lge, Artifact 에 lcms 입력, Web Dependencies 추가해서 Generate Project

2. 다운 받은 파일을 특정 폴더(spring-react)에 압축 풀기

tree 구조는 아래와 같습니다.

jeongsim.kim@ubuntu-n:~/work/lcms$ tree
.
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── lge
    │   │           └── lcms
    │   │               └── LcmsApplication.java
    │   └── resources
    │       ├── application.properties
    │       ├── static
    │       └── templates
    └── test
        └── java
            └── com
                └── lge
                    └── lcms
                        └── LcmsApplicationTests.java

14 directories, 6 files

기본 Controller 추가

Rest API 를 추가하는 작업입니다. (/api/hello)

1. src/main/java/com/lge/lcms/HelloController.java 파일 생성

package com.lge.lcms;
 
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
 
import java.util.Date;
 
@RestController
public class HelloController {
    @GetMapping("/api/hello")
    public String hello() {
        return "Hello, the time at the server is now " + new Date() + "\n";
    }
}

2. 실행

$ mvn spring-boot:run

3. 아래 curl 로 정상 동작 여부 확인 (다른 터미널에서)

$ curl http://localhost:8080/api/hello
Hello, the time at the server is now Tue Jan 29 14:41:10 KST 2019

React 추가

Front-end 로 React 를 사용하기 위해서 facebook 에서 만든 create-react-app 을 이용해서 React bootstrap 을 만드는 과정입니다.

1. 최상위 디렉토리에서 아래와 같이 frontend React Application 추가 (mvn spring-boot:run 종료하고)

$ create-react-app frontend

create-react-app 이 설치되어 있지 않다면?

$ npm install -g create-react-app

React 에 API 연결 추가

Front-end 인 React App 에서 spring 으로 Rest API 를 호출하여, 페이지에 그려주는 코드를 작성하는 것입니다.

1. frontend/src/App.js 파일을 아래와 같이 수정

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
 
class App extends Component {
    state = {};
 
    componentDidMount() {
        setInterval(this.hello, 250);
    }
 
    hello = () => {
        fetch('/api/hello')
            .then(response => response.text())
            .then(message => {
                this.setState({message: message});
            });
    }
 
    render() {
        return (
            <div className="App">
                <header className="App-header">
                    <img src={logo} className="App-logo" alt="logo" />
                    <h1 className="App-title">{this.state.message}</h1>
                    <p>
                        Edit <code>src/App.js</code> and save to reload.
                    </p>
                    <a
                        className="App-link"
                        href="https://reactjs.org"
                        target="_blank"
                        rel="noopener noreferrer"
                    >
                        Learn React
                    </a>
                </header>
            </div>
        );
    }
}
 
export default App;

Maven 으로 React Packaging 추가

maven 을 통해서 Front-end 인 React 를 build 하는 것입니다.

1. pom.xml 파일에 아래와 같이 plugin 추가(build/plugins 하위에 추가하면 됩니다.)

		<plugin>
			<groupId>com.github.eirslett</groupId>
			<artifactId>frontend-maven-plugin</artifactId>
			<version>1.6</version>
			<configuration>
				<workingDirectory>frontend</workingDirectory>
				<installDirectory>target</installDirectory>
			</configuration>
			<executions>
				<execution>
					<id>install node and npm</id>
					<goals>
						<goal>install-node-and-npm</goal>
					</goals>
					<configuration>
						<nodeVersion>v8.9.4</nodeVersion>
						<npmVersion>5.6.0</npmVersion>
					</configuration>
				</execution>
				<execution>
					<id>npm install</id>
					<goals>
						<goal>npm</goal>
					</goals>
					<configuration>
						<arguments>install</arguments>
					</configuration>
				</execution>
				<execution>
					<id>npm run build</id>
					<goals>
						<goal>npm</goal>
					</goals>
					<configuration>
						<arguments>run build</arguments>
					</configuration>
				</execution>
			</executions>
		</plugin>

2. mvn clean install 이 정상적으로 되는지 확인 (Error 가 뜨는지 안 뜨는지?)

$ mvn clean install

3. tree 구조 확인

frontend 하위에 build 가 정상적으로 생성되는지?(maven 이 npm 을 호출해서 build 를 잘 했는지 확인)

$ tree frontend/build
frontend/build
├── asset-manifest.json
├── favicon.ico
├── index.html
├── manifest.json
├── precache-manifest.18b2b7a023755c5bb0bcd527b6c170b9.js
├── service-worker.js
└── static
    ├── css
    │   ├── main.24e815be.chunk.css
    │   └── main.24e815be.chunk.css.map
    ├── js
    │   ├── 1.fa92c112.chunk.js
    │   ├── 1.fa92c112.chunk.js.map
    │   ├── main.21cc7467.chunk.js
    │   ├── main.21cc7467.chunk.js.map
    │   ├── runtime~main.229c360f.js
    │   └── runtime~main.229c360f.js.map
    └── media
        └── logo.5d5d9eef.svg

4 directories, 15 files

Spring boot jar 에 React 추가

maven 을 통해서 만들어진 Front-end 리소스들을 jar 에 추가하는 작업입니다.

1. pom.xml 에 plugin 추가

			<plugin>
				<artifactId>maven-antrun-plugin</artifactId>
				<executions>
					<execution>
						<phase>generate-resources</phase>
						<configuration>
							<target>
								<copy todir="${project.build.directory}/classes/public">
									<fileset dir="${project.basedir}/frontend/build"/>
								</copy>
							</target>
						</configuration>
						<goals>
							<goal>run</goal>
						</goals>
					</execution>
				</executions>
			</plugin>

2. mvn clean install 이 정상적으로 되는지 확인 (Error 가 뜨는지 안 뜨는지?)

$ mvn clean install

3. jar 파일에 public resource 가 정상적으로 포함되어 있는지 확인

$ jar tvf target/lcms-0.0.1-SNAPSHOT.jar | grep public
     0 Tue Jan 29 15:00:30 KST 2019 BOOT-INF/classes/public/
     0 Tue Jan 29 15:00:30 KST 2019 BOOT-INF/classes/public/static/
     0 Tue Jan 29 15:00:30 KST 2019 BOOT-INF/classes/public/static/media/
     0 Tue Jan 29 15:00:30 KST 2019 BOOT-INF/classes/public/static/css/
     0 Tue Jan 29 15:00:30 KST 2019 BOOT-INF/classes/public/static/js/
   306 Tue Jan 29 15:00:30 KST 2019 BOOT-INF/classes/public/manifest.json
322072 Tue Jan 29 15:00:30 KST 2019 BOOT-INF/classes/public/static/js/1.fa92c112.chunk.js.map
  1041 Tue Jan 29 15:00:30 KST 2019 BOOT-INF/classes/public/service-worker.js
  7959 Tue Jan 29 15:00:30 KST 2019 BOOT-INF/classes/public/static/js/main.21cc7467.chunk.js.map
  2828 Tue Jan 29 15:00:30 KST 2019 BOOT-INF/classes/public/static/css/main.24e815be.chunk.css.map
  1502 Tue Jan 29 15:00:30 KST 2019 BOOT-INF/classes/public/static/js/runtime~main.229c360f.js
  1706 Tue Jan 29 15:00:30 KST 2019 BOOT-INF/classes/public/static/js/main.21cc7467.chunk.js
   606 Tue Jan 29 15:00:30 KST 2019 BOOT-INF/classes/public/precache-manifest.18b2b7a023755c5bb0bcd527b6c170b9.js
   779 Tue Jan 29 15:00:30 KST 2019 BOOT-INF/classes/public/asset-manifest.json
  2062 Tue Jan 29 15:00:30 KST 2019 BOOT-INF/classes/public/index.html
  2671 Tue Jan 29 15:00:30 KST 2019 BOOT-INF/classes/public/static/media/logo.5d5d9eef.svg
  3870 Tue Jan 29 15:00:30 KST 2019 BOOT-INF/classes/public/favicon.ico
   984 Tue Jan 29 15:00:30 KST 2019 BOOT-INF/classes/public/static/css/main.24e815be.chunk.css
  7996 Tue Jan 29 15:00:30 KST 2019 BOOT-INF/classes/public/static/js/runtime~main.229c360f.js.map
112436 Tue Jan 29 15:00:30 KST 2019 BOOT-INF/classes/public/static/js/1.fa92c112.chunk.js

실행 확인

Front-end 와 Back-end 가 정상적으로 동작하는지 확인하는 것입니다.

1. java 로 실행 확인

$ java -jar target/lcms-0.0.1-SNAPSHOT.jar 

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.2.RELEASE)
...

2. browser 에서 정상 동작 확인

Zsh, Oh My ZSH 사용하기

zsh 설치

$ sudo apt install zsh

oh my zsh 설치

// curl 을 통해 설치
$ sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
// wget 을 통해 설치
$ sh -c "$(wget https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh -O -)"
// 둘 중 하나를 고르면 되고, 해당 내용은 https://ohmyz.sh/ 에 가이드가 있음

Oh My ZSH Theme 변경

$ vi ~/.zshrc
// ZSH_THEME 부분을 변경
ZSH_THEME="agnoster"

Garmin Watch Face 수정

Motivation

최근 Garmin Watch Face 로 Crystal을 사용하고 있는데, 한글이 출력되면 더 사용하기 좋을 것 같다는 생각에, 한글을 추가해 보았다.

한글이 지원되지 않는 Crystal Watch Face
한글이 지원되는 Crystal Watch Face

개발 환경 설정

Eclipse 설치 (생략)

Connect IQ SDK 설치

https://developer.garmin.com/connect-iq/sdk/

PATH 설정

export PATH=$PATH:path/to/connectiq-sdk/bin

Eclipse Plug-in 설치

  1. In Eclipse, click the Help menu
  2. Choose Install New Software…
  3. Click the Add… button
  4. Add https://developer.garmin.com/downloads/connect-iq/eclipse/ to the Location field and click Add
  5. Check the box next to Connect IQ in the Available Software window and click Next
  6. Review the license agreement and click Finish
  7. Once the installation completes, restart Eclipse

SDK 설치

  1. In Eclipse, click the Connect IQ menu
  2. Choose Open SDK Manager
  3. Browse to or specify a path to a directory where the SDK will be saved
  4. Click the Download button next to the latest SDK release
  5. Review the license agreement, and click Agree
  6. Once the download completes, click Yes when prompted to use the new SDK version as your active SDK
  7. Close the SDK Manager

Eclipse 에서 디버깅, 실행

Start Simulator

Connect IQ -> Start Simulator

Run

Run -> Run As -> Connect IQ App

개발 참고 사항

Font 관련

Bitmap font 생성

  • http://www.angelcode.com/products/bmfont/ 에서 설치
  • Font Setting 에서 Font 선택, 사이즈 선택
  • Export Options 에서 Texture 를 png 로 변경
  • 화면에서 추출할 모든 글자 선택
  • Visualize 로 확인
  • Save bitmap font as… 후 nanum-gothic-kor-24.fnt 형태로 저장
  • 각각의 사이즈(16, 20, 24, 26, 28, 39) 별로 모두 fnt, png 파일 생성

Bitmap font 복사

각각의 Resource directory 에 복사할 필요가 있음

Font Resource 반영

XML 파일 수정이 필요함 (https://github.com/warmsound/crystal-face/pull/154/files 를 참고할 것)

References