iOS만 11년... 하지만 구글 픽셀 3(Google Pixel 3)을 사다

아이폰 SE의 두번째 버전이 나오면 사려고 일 년을 기다렸다. 하지만 애플은 ‘그런거 없다’를 시전했다.

나는 핸드폰으로 게임도 안하고 영상도 거의 보지 않아서 작고 가벼운 폰이 필요하다. 그래서 아이폰 5를 6년동안 쓸 수 있었다. 여전히 속도와 카메라, 배터리 빼고는 불만이 없다.

아이팟터치부터 iOS만 11년을 썼다. 몇 년동안 아이폰과 iOS의 신기능들은 더 이상 나에게 새로움을 주지 않았다. 그저 좀 더 나은 아이폰을 위해서 익숙함에 130만원을 지불하고 싶진 않았다.

구매

미국 구글스토어에서 $799에 구매했다. 핸드폰 가격이 백만원에 달하는 2018년, 픽셀3도 이전의 픽셀2와 크게 변한게 없음에도 가격을 $150 더 받는다.

픽셀3은 한국에서 정식발매하지 않았다. 아마 앞으로도 하지 않을 것 같다. 수리를 받을 수 없어서 고장나면 끝이다. 고장나면 그냥 익숙함에 130만원을 쓸 껄하고 후회할지도 모른다.

픽셀3의 패키지는 아이폰보다 알찬 구성에 15W 충전기를 제공한다.

무게

픽셀 3는 최근 출시된 플래그십 폰 중에서 아마 제일 가벼운 폰이다. 여차하면 사오려고 대만가서 아이폰 XS를 꽤 오래 만져봤는데 무겁긴 하더라.

  • 아이폰 5: 112g
  • 아이폰 8: 148g
  • 픽셀 3: 149g
  • 아이폰 XS: 177g

픽셀3도 아이폰5에 비하면 여전히 많이 크고 무겁지만, 최선의 선택이다.

안드로이드

iOS와는 다르게 안드로이드는 제조사와 통신사의 입맛에 맞게 개량된다. 픽셀은 아이폰처럼 하드웨어와 소프트웨어가 같은 곳에서 만들어진다. 그러니까 픽셀을 사용하면 OS 개발 의도에 맞는 순수한 안드로이드를 경험할 수 있다. UI 뿐만 아니라 성능까지도. 그래서 애플처럼 구글도 램크루지가 되었다.

하지만 키보드와 스크롤은 적응을 잘 못하고 있다.

구글 홈과 구글 홈 미니를 사다

구입하려고 생각하고 있었는데 마침 한국 정식발매 소식에 바로 질렀다.

미니는 거실에 두고 부모님 음악감상용. 홈은 내 방에 두고 블루투스 스피커 + Hue 조명 조절용.

블루투스 스피커로 사용하거나 또는 다른 블루투스 스피커로 출력할 수 있기 때문에, 부모님 쓰시는거 봐서 마이크와 스피커가 좋은 홈을 거실로 옮기고 미니는 블루투스 스피커를 연결해서 내 방에 둘 예정이다.

Home Assistant와 함께 제대로 사용하려면 구글 홈에 내장된 구글 어시스턴트와 크롬캐스트 모두 연결하는게 좋다.

크롬캐스트와 연결은 아주 쉽지만 구글 어시스턴트 연결을 위해선 Actions on Google에서 프로젝트를 만들어서 연결해야한다.

연결을 하고나면 구글 홈에게 음성으로 명령해서 Home Assistant를 통해 Hue를 조정할 수 있다. Home Assistant를 통하지 않고 바로 연결할 수도 있지만 추후에 자동화 및 타 기기 연동을 위해선 모든 기기를 한 곳에서 관리할 수 있는 장치가 필요하다.

Hass.io에 커스텀 도메인 SSL 인증서를 설치하다

문제

Hass.io에서 DuckDNS 애드온은 SSL 인증서도 쉽게 설치할 수 있도록 해준다. 하지만 커스텀 도메인을 지원하지 않는다. DuckDNS를 쓰면서 커스텀 도메인으로 접속하면 이런 메시지를 볼 수 있다. SSL 인증서의 도메인과 접속하려는 도메인이 다르기 때문이다.

해결

커스텀 도메인의 SSL 인증서를 만들면 해결할 수 있다. Let’s Encrypt 애드온을 설치한다. 애드온 문서 앞에 DuckDNS 애드온과 함께 사용하지말라는 경고는 무시한다.

시작하기 앞서 hass.io가 설치된 머신의 80번 포트가 포트포워딩되어있는지 확인한다.

Let’s Encrypt 애드온의 config를 수정한다. certfilekeyfile의 이름을 변경한다. DuckDNS 애드온의 파일명과 같으면 안된다.

1
2
3
4
5
6
7
8
{
"email": "your@email.com",
"domains": [
"your.domain"
],
"certfile": "custom-fullchain.pem",
"keyfile": "custom-privkey.pem"
}

config를 저장하고, 애드온을 실행한다. log에서 진행상황을 볼 수 있다.

인증서 설치가 끝났으면, DuckDNS 애드온을 제외하고 ssl 인증서를 사용하는 모든 설정을 위에서 변경한 키 파일로 수정한다.

1
2
3
4
5
# configuration.yaml
http:
base_url: https://your.domain
ssl_certificate: /ssl/custom-fullchain.pem
ssl_key: /ssl/custom-privkey.pem

마지막으로 홈 어시스턴트를 재부팅한다. 인증서를 갱신하는 자동화도 마련하는 것으로 정말 끝.

원인

DuckDNS이 Let’s Encrypt로부터 SSL 인증서를 발급받기 위해 사용하는 도메인 소유 증명 방법으로는 커스텀 도메인을 소유 증명할 수 없기 때문이다.

Let’s Encrypt 문서를 보면 도메인 소유를 증명하는 방법은 dns-01과 http-01이 있

잘 설계된 DynamoDB 앱은 단 하나의 테이블만 필요합니다

어그로 오지는 이 제목은 아마존 문서에서 발췌했다.

데이터를 어떻게 정의할 것인가

DynamoDB를 처음 접하면서 가장 어려웠던 부분이다. NoSQL 테이블 디자인에 대해 구글링하면서 가장 많이 봤던 문장은

서비스를 먼저 디자인하고 어떤 쿼리가 필요한지 파악한 후 테이블을 디자인한다.

서비스를 운영하면 기능을 변경하거나 추가할 필요가 생긴다. RDB라면 필요한 데이터를 미리 정의해두고 쿼리만 바꾸면서도 기능을 변경하거나 추가할 수 있다. 그런데 저 문장은 마치 NoSQL은 고정된 디자인을 가진 서비스를 위한 데이터베이스라고 말하는 듯 했다.

며칠을 고민하면서 겨우 실마리를 찾은 것 같다. RDB를 쓸 때도 필요하다면 쿼리를 바꾸는 것보다는 비용이 더 들긴 하지만 스키마를 변경할 수 있다. NoSQL을 쓰는 것도 필요하다면 쿼리를 바꿀 수 있다. 다만 이미 저장된 데이터는 기존 쿼리에 최적화되어 있으니, 새로운 형태의 데이터를 저장하면 된다. RDB와는 다르게 중복 저장도 환영이다.

자원이 많다면 가능한 모든 쿼리를 고려하여 데이터를 중복 저장할 수 있다. RDB를 사용할 때처럼 하나의 데이터타입에 하나의 테이블을 사용할 수도 있다. 그러나 DynamoDB의 과금 정책상 이 방법은 효율적이지 않다.

데이터를 어떻게 저장할 것인가

이제 간단한 게시판 서비스를 하나의 테이블만으로 만들 것이다. 물론 코드는 한 줄도 없고, 과정 속에서 서로 다른 형태의 데이터가 어떻게 하나의 테이블에 저장될 것인지 설명한다.


1:1

STORY: 사용자는 이메일로 계정을 생성하고 로그인할 수 있다

사용자 정보를 저장할 테이블을 하나 구상한다.

id (PK) createdAt (SK) email hashed_password
USER-1 createdAt user@email.com password

‘id’ 속성의 USER-1은 데이터의 타입도 함께 포함한다. 이것은 하나의 테이블에서 서로 다른 데이터를 구별하기 위한 방법으로 아래와 같은 형태를 가진다.

1
[DATA_TYPE

라즈베리파이에 Home Assistant를 설치하다

집에서 놀고 있는 라즈베리파이에 hass.io를 설치한다.

내가 가지고 있는 라즈베리파이는 메모리가 512메가인 model B 버전이다. 이 모델은 와이파이가 없어서 유선랜을 연결해주어야 한다.

내 방에 들어오는 인터넷 선은 바로 데스크탑으로 연결된다. 이걸 데스크탑 뿐만아니라 라즈베리파이에도 연결해야한다.

집에서 놀고 있는 iptime 공유기를 가져다가 스위칭허브 모드로 전환하고 연결한다.

SD카드에 모델에 맞는 이미지를 다운받아 설치한다. 다른 모델의 이미지를 사용하면 라즈베리파이가 부팅이 되지 않는다.

라즈베리파이에 SD카드와 이더넷 선을 꼽고 전원을 연결한다. 그리고 http://hassio.local:8123/ 에 접속하면

20분 정도 기다려 달라고 한다. 구형 모델이라서 그런지 30분 걸렸다.

계정을 생성. 이런 류의 컨트롤 시스템은 망 외부에서도 조작할 수 있도록 대부분 계정으로도 연동한다.

필립스 휴 브릿지를 연결.

이제 홈 어시스턴트를 통해서도 전구를 조작할 수 있다.

필립스 휴 3.0 스타터 킷을 사다

스마트홈 구축의 입문격이라는 필립스 휴를 샀다.

몇 년 전에는 분명 스타터 킷이 삼십만원 정도 했던 것 같은데, 지금은 그 가격의 반도 안한다.

전구 세 개와 휴 브릿지가 들어있다. 저 브릿지를 통해서 각 전구를 조종한다.

hue 앱으로도 조종할 수 있고, 애플 홈으로도 조종할 수 있다. 시리에게 요청할 수도 있다.

앱애서는 간단한 루틴 설정이 가능한데,

내가 제일 원했던 기능인 기상 루틴을 설정했다. 유독 겨울에는 아침에 어두워서 알람을 들어도 일어나기 힘들다. 겨울을 미리 준비하기 위함이라고 스스로에게 구매를 핑계삼았다.

알람 시간 삼십분 전부터 서서히 밝아지다가 알람이 지나고 10분 후에 자동으로 꺼지도록 세팅했다.

애플 홈킷과도 연동할 수 있는데, 휴 앱에서 설정하면 동기화하는 방식이다.

  • “불 꺼줘”
  • “내방 불 켜줘”
  • “환하게 해줘”
  • “거실 조명 보라색으로 바꿔줘”

같은 명령을 시리에게 시킬 수도 있다.

전구는 세 갠데, 내 방에 소켓은 책상 스탠드 하나 뿐이다. 이제 스탠드를 사야한다.

원래는 아침에 일어날 때 좀 더 기분 좋게 일어나고 싶어서, 일출을 시뮬레이션하는 필립스의 wake up light를 구매할 생각이었다. 하지만 wake up light는 매 번 자기 전에 알람을 켜줘야하는 불편함이 있어서 이걸 자동화할 방법을 찾다가 없길래 Hue로 눈을 돌렸다.

아이디어 구현하기

난 만들어보면 재미있을 것 같은 아이템 목록을 적어둔다. 이 아이디어들은 혼자서 심심할 때 혹은 해커톤에 나갈 때 하나씩 구현된다. 바쁘다는 핑계로 목록 개수만 늘이고 있었는데 요새 시간이 많아져서 몇 가지 만들어보기로 했다. 좀 정상적인 아이템들은 해커톤 나가서 다른 사람들과 같이 만들어 볼 수 있기 때문에, 그렇지 않은 걸로만 몇 개 골랐다.

Dynamic Dictation

https://scon.io/dynamic-dictation/

음성과 음량을 글자로 바꿔준다. SpeechRecognition APIAudio API를 적절히 섞어서 목소리의 음량과 표시되는 글자의 크기가 비례하도록 했다.

Uncertainty

https://github.com/scon-io/uncertainty

측정될 때 값이 정해지는 데이터 타입. 현재 양자역학의 주류인 코펜하겐 해석에 의하면 입자의 상태는 관측하는 그 순간에 정해진다. Uncertainty 타입의 데이터도 값이 정해지지 않은 상태로 메모리에 존재하지만 사용하는 순간에 값이 특정된다. Object.defineProperty()로 구현할 수 있었다.

idiots.io

https://idiots.io/

익명 직장 채팅 어플. 블라인드라는 앱의 채팅버전이다. 아이템 자체로 특별한 건 없고 GraphQL Subscription을 써보고 싶어서 만들었다. 한 일주일이면 서버와 앱까지 다 만들 수 있을 줄 알았는데, React Native가 발목을 잡아서 며칠 더 걸렸다. 역시 프론트엔드는 어려워.

jongfill

https://github.com/scon-io/jongfill

한글에 종성을 채워준다. 숫자를 문자로 출력할 때 고정된 길이로 0을 붙혀서 출력하는 것을 zerofill 또는 zfill이라고 한다. 또 ‘곣맋웏욗’처럼 종성을 분리해서 문장을 쓰는게 인터넷에서 유행했던 적이 있었다. jongfill이란 이름의 이 라이브러리는 얼핏 듣기에는 마치 사람 이름 같지만 어떤 기능을 하는지 직관적으로 표현해주는 가장 완벽한 이름

프로그래밍에서 창의성이란 무엇인가?

넷플릭스에 알파고 다큐멘터리가 올라와서 봤다. 영상은 이세돌과 알파고의 Google Deepmind Challenge match의 비하인드 스토리를 흥미롭게 풀어냈다. 재미있게 시청중에 이세돌의 멘트에 정신이 번쩍했다.

바둑에 정말 창의성이 필요한가?
바둑에서 창의성이란 무엇인가?

프로그래밍에 정말 창의성이 필요한걸까? 프로그래밍에서 창의성이란 무엇인가?

이런 고민들을 앞서 해본 다른 사람들의 생각을 구글링하여 읽어봤다. 대체적인 의견은 프로그래밍은 예술활동에 비견될 수 있다는 것이다. 이 둘은 결과물을 만들기 위해 여러가지 도구를 다양한 방법으로 사용하여 무엇인가를 만드는데에 그 공통점이 있다.

그렇다면 프로그래밍에서 도구는 무엇이고, 다양한 방법은 어떤 것들이 있으며, 작품은 무엇이란 말이지? 장비와 에디터등 개발 환경이 도구라면, 그 위에서 사용하는 언어와 프레임워크, 라이브러리는 개발 방법인가? 아니면 이런것들 모두 도구라고 볼 수 있으니, OOP나 functional programming 같은 개발 패러다임이 개발 방법인가? 심지어 완성된 코드는 누군가에겐 또 다른 도구가 될 수 있다. 이런 것들을 명확하게 구분지을 순 있는 것인가? 구분지을 필요는 있을까?

Socket.io로 카운터 만들기

웹에서 전통적인 HTTP는 클라이언트가 요청을 하면 서버가 응답하는 방법으로 통신한다. 내 게시글에 새 댓글이 달렸는지 확인하기 위해서 새로고침을 눌러봐야 한다. 즉, 요청-응답이 끝나면 연결을 종료하기 때문에 클라이언트가 다시 요청하기 전까지는 서버에서 업데이트된 데이터를 받아올 수 없다. 내놓으라고 하기 전까진 스스로 토해내지 않는게 마치 통신사 환급금을 보는 것 같다.

정말로?

CS 전공을 한 사람이라면 네트워크 실습시간에 소켓 통신으로 TCP와 UDP 채팅 프로그램을 만들어 본 적이 있을 것이다. TCP는 연결된 상대가 데이터를 잘 받았는지 확인하고, UDP는 받던 말던 그냥 보내고 본다는 차이가 있으며, TCP는 전화, UDP는 편지에 비유했던 것으로 기억한다. TCP와 UDP에서 중요한 것은 서버와 클라이언트가 소켓으로 연결되어 있다는 점이다. 소켓은 상대방의 주소(목적지)를 담고 있기 때문에 내가 누구에게 데이터를 보내야하는지 명확하다. 소켓으로 연결되어 있다면 서로 누구나 먼저 데이터를 보낼 수 있다. HTTP는 TCP 프로토콜을 이용한다. 그러니까 웹에서도 TCP를 이용하여 소켓 통신을 할 수 있다. HTML5의 웹소켓(Web Socket)으로 말이다.

Socket.io는 웹소켓을 이용하여 서버와 클라이언트 간의 실시간 양방향 통신을 가능케하는 라이브러리다. Socket.io가 지원하는 브라우저 목록을 보면 웹소켓을 지원하는 브라우저가 아닌 것들이 있다. 웹 소켓을 사용할 수 없는 브라우저는 다른 방식을 이용해서 마치 웹소켓이 작동하는 것처럼 보이게 한단다. 많은 Socket.io를 주제로 작성된 블로그 포스트들이 이 내용을 언급하고 있지만 난 그 내용이 어디있는지 찾지 못했다.


Socket.io를 이용해서 간단한 웹 애플리케이션을 만들어보자. 채팅 프로그램을 구현한 튜토리얼은 이미 아주 많으니까 나는 더 단순한 앱을 만들어보려고 한다.

버튼에 숫자가 적혀있다. 버튼을 누르면 숫자가 1 증가한다. 이 숫자는 모든 클라이언트에게 동일하게 보

GraphQL 기초

추석 전 마지막 스프린트 진행이 생각보다 빨라서 enhancement로 API 서버에 GraphQL을 구성했다.

GraphQL은 API 쿼리 랭귀지다. RESTful한 API는 endpoint마다 출력결과 형태가 고정되어서, API client는 필요한 데이터를 위해 API를 여러번 호출하거나, 필요없는 데이터까지 받아올 수 밖에 없는 경우가 많다. GraphQL은 API client가 원하는 데이터를 원하는 모양으로 출력할 수 있게 한다.

API client 입장에서 GraphQL을 이용하여 어떻게 요청하는지부터 알아보자. 서버가 어떻게 만들어지는지, 데이터를 어떻게 불러오는지는 지금 몰라도 좋다. 왼쪽과 같은 GraphQL 쿼리를 POST body로 서버에 요청하면, 오른쪽 데이터가 그 응답으로 반환된다. 쿼리와 결과의 모양을 보자.

1
2
3
4
5
query {
hero {
name
}
}
1
2
3
4
5
6
7
{
"data": {
"hero": {
"name": "R2-D2"
}
}
}

GraphQL API를 사용하는 입장에서는 원하는 모양 그대로 요청하면 되니 아주 직관적이다. 데이터를 읽는 요청을 query라 하고, 데이터를 변경하는 요청을 mutation이라 한다.

Query

query는 데이터를 불러오는 구문이다. RESTful API에서의 GET 메소드에 해당한다.

1
2
3
4
5
6
{
human(id: "1000") {
name
height(unit: FOOT)
}
}
1
2
3
4
5
6
7
8
{
"data": {
"human": {
"name": "Luke Skywalker",
"height": 5.6430448
}
}
}

처음 예제와 다르게 시작 부분에 query가 생략되었다. query문의 경우는 생략이 가능하고, mutation문일 경우는 명시 해주어야 한다.