Home Assistant 삽질기 6 (Bluetooth 연동)

자동화를 수행하려면 가장 필요한 것이 바로 정확한 수치라고 생각한다. 정확한 수치가 없으면 아무래도 자동화를 할 때 잘못된 동작을 많이 하기 때문인데, 침실의 가습기를 가동하기 위해 침실의 습도를 가습기로 부터 받아오면서 문제가 발생하기 시작했다.

침실의 습도는 가습기에 내장된 습도계를 통해 습도를 가지고 온다. 그런데 이 습도계가 아무래도 가습기의 영향을 너무 가까이 받는 것이 문제였다. 주변의 습도는 올라가지만, 방 전체의 습도가 충족되기 전에 습하다고 판단하고 꺼져버리는 문제였다. 이 문제를 해결하기 위해서는 아무래도 습도계가 필요한데 아무래도 스마트기기여야 이 정보를 홈어시스턴트로 불러올 수 있기 때문에 검색을 시작했다.

제일 처음에 후보군에 올린 것은 awair 라는 장비였다. 실제로 HA와 연동시킨 결과도 검색이 되고 해서, 해당 제품이 1순위였다. 그런데 아무래도 가격이 살인적이었다.

awair 제품 홈페이지

위 제품의 가격이 창렬하다는게 아니다. 기능이 충분히 그 가격에 맞게 들어가있고 충분히 납득할만한 가격이지만… 아무래도 온,습도만 필요한 나에게는 과분한 장비가 아닐 수 없었다. 그래서 차선책으로 wifi 지원되는 온습도계를 찾았지만 적당한 기기가 없어 결국 직접 만들까 고민하던 찰나에, HA로 블루투스 디바이스와 다이렉트로 통신이 된다는 이야기를 어디서 들은게 있어서 검색해보니 샤오미 온습도계 라이브러리가 존재했다.

샤오미 온습도계, HA 연동 라이브러리

해당 라이브러리로 통신이 가능한데, 문제는 라즐베리파이에 설치한 HA나 컴퓨터에 설치한 HA의 경우 블루투스를 인식시키기 쉽지만 Synology Docker에 연동하기 위해서는 몇가지 작업이 필요하고, 블루투스 호환성도 생각해야 한다는 점이였다.

그래서 테스트를 위한 순서를 잡아보았다.

  1. Synology NAS 에서 해당 블루투스 동글을 지원하는가?
  2. 지원하면, 어떻게 docker에 해당 정보를 던져줄 것인가?
  3. 최종 테스트
  4. 장비 구입

먼저, 1번 블루투스 동글 호환성 체크를 위해 일단 집에 굴러다니는 USB 블루투스 동글을 NAS에 연결하였다.

그 결과, 설정에 들어가서 보니 못보던 메뉴가 생긴 것을 확인할 수 있었다.

NAS 설정화면에 무선이라는 메뉴가 생겼고 하위 메뉴에 bluetooth가 잡힌 것을 확인할 수 있었다. 심지어 스캔을 수행하자 집에 있는 TV와 MIBOX 역시 스캔되는 것을 확인할 수 있었다. 이는 즉, 호환되는 동글이었다.

돈 굳었다.

그러면 2번, 문제를 해결할 차례이다. 자세한 내용을 검색해보니 이런 글을 찾을 수 있었다.

Installing Home-Assistant.io on a Synology Diskstation NAS

해당 글을 읽어보면 docker를 시작할 때, 옵션으로

-v /dev/bus/usb:/dev/bus/usb

옵션을 추가하면 되는 것이었다. 위의 옵션은 NAS 시스템 내부의 /dev/bus/usb 폴더를 HA Docker 내부의 /deb/bus/usb 폴더로 마운트 시키라는 옵션이다. 그런데 문제는 그럼 HA Docker를 재시작하거나 NAS를 재시작하면 무조건 커맨드 라인으로 접속해서 해당 명령어를 실행해야 하는 귀찮음이 남았다. 해결방법은 간단했다. 그냥 시작시 설정을 고쳐주면 되는 것이다.

하지만, Synology NAS에서 제공하는 Docker GUI에서는 해당 디렉토리를 마운트 할 수 없었다. 오로지 저장소만 마운트를 지원하기 때문에 직접 설정파일을 수정해야 한다.

첫번째로, 해당 HA Docker의 아이디를 알아야 한다. Synology NAS 콘솔에 접속한 후, root 권한으로 현재 docker에 동작하고 있는 컨테이너 목록을 확인한다.

root@name:~# docker ps -a
CONTAINER ID .... NAMES
1
1
1
fff308d8bf37 .... home-assistant

위에서 보면, 필요없는 정보는 삭제했지만 NAMES 부분을 통해서 home-assistant 를 찾을 수 있다. home-assistant에 할당된 컨테이너 ID 값을 기억해야 한다. 여기서는 fff308d8bf37 이다.

해당 아이디를 알았으면 Docker 설정파일을 찾을 수 있다.

두번째는, 설정파일을 수정하기 위해서 Docker 서비스를 정지시켜야 한다. 다양한 방법으로 정지 시킬 수 있지만 가장 쉬운 방법은 Synology 패키지 센터에서 정지를 누르는 것이다.

패키지 센터에서 설치된 항목으로 간 후, Docker를 선택하고 열기 버튼 옆에 화살표를 누르면 정지 명령이 나타난다. 정지를 눌러 정지를 시켜준다. 그리고 다시 콘솔로 돌아온다.

세번째로, 설정 파일을 열어본다. 첫번째에서 찾은 fff308d8bf37로 시작하는 디렉토리가 해당 Docker 컨테이너의 디렉토리이다. 나의 경우 해당 디렉토리는

cd /var/packages/Docker/target/docker/containers/fff308d8bf375c0d7742743a7d66b12977efa925c46fbd3f5bb78773913b0006/

위와 같았다. 위의 디렉토리로 이동한 후, config.v2.json 파일을 열어보면 설정이 적혀있는 것을 확인할 수 있다. 하지만 띄워쓰기도 없고 엔터키도 없기 때문에 보기 힘들다. 해당 정보를 이쁘게 보여 줄 웹 페이지가 있다.

JSON Beautify

위의 링크를 열고 다음을 복사해서 이쁘게 볼 수 있다. 처음 설정을 열면 아래와 같이 보인다.

{"StreamConfig":{},"State":{"Running":"-","Paused":"-","Restarting":"-","OOMKilled":"-","RemovalInProgress":"-","Dead":"-","Pid":"-","ExitCode":"-","Error":"-","StartedAt":"-","FinishedAt":"-","Health":null},"ID":"fff308d8bf375c0d7742743a7d66b12977efa925c46fbd3f5bb78773913b0006","Created":"-","Managed":"-","Path":"/bin/entry.sh","Args":["python3","-m","homeassistant","--config","/config"],"Config":{"Hostname":"home-assistant","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":true,"OpenStdin":true,"StdinOnce":false,"Env":["PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","LANG=C.UTF-8","GPG_KEY=","PYTHON_VERSION=","PYTHON_PIP_VERSION=","WHEELS_LINKS=","TZ="],"Cmd":["python3","-m","homeassistant","--config","/config"],"Image":"homeassistant/home-assistant:latest","Volumes":null,"WorkingDir":"/config","Entrypoint":["/bin/entry.sh"],"OnBuild":null,"Labels":{"io.hass.arch":"","io.hass.base.image":"","io.hass.base.name":"","io.hass.base.version":"4.0","io.hass.type":"","io.hass.version":""},"DDSM":false},"Image":"","NetworkSettings":{"Bridge":"","SandboxID":"","HairpinMode":false,"LinkLocalIPv6Address":"","LinkLocalIPv6PrefixLen":0,"Networks":{"host":{"IPAMConfig":null,"Links":null,"Aliases":null,"NetworkID":"","EndpointID":"","Gateway":"","IPAddress":"","IPPrefixLen":0,"IPv6Gateway":"","GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"MacAddress":"","DriverOpts":null,"IPAMOperational":false}},"Service":null,"Ports":{},"SandboxKey":"","SecondaryIPAddresses":null,"SecondaryIPv6Addresses":null,"IsAnonymousEndpoint":false,"HasSwarmEndpoint":false},"LogPath":"","Name":"","Driver":"btrfs","OS":"linux","MountLabel":"","ProcessLabel":"","RestartCount":0,"HasBeenStartedBefore":true,"HasBeenManuallyStopped":false,"MountPoints":{"/config":{"Source":"/volume1/docker/homeassistant/config","Destination":"/config","RW":true,"Name":"","Driver":"","Type":"bind","Relabel":"rw","Spec":{"Type":"bind","Source":"/volume1/docker/homeassistant/config","Target":"/config"},"SkipMountpointCreation":false}},"SecretReferences":null,"ConfigReferences":null,"AppArmorProfile":"docker-default","HostnamePath":"","HostsPath":"","ShmPath":"","ResolvConfPath":"","SeccompProfile":"","NoNewPrivileges":""}

위 처럼 나온 정보를 붙여넣고 “Beautify” 버튼을 눌러 아래와 같이 변경한다.

{
  "StreamConfig": {},
  "State": {
    "Running": "-",
    "Paused": "-",
    "Restarting": "-",
    "OOMKilled": "-",
    "RemovalInProgress": "-",
    "Dead": "-",
    "Pid": "-",
    "ExitCode": "-",
    "Error": "-",
    "StartedAt": "-",
    "FinishedAt": "-",
    "Health": null
  },
  "ID": "fff308d8bf375c0d7742743a7d66b12977efa925c46fbd3f5bb78773913b0006",
  "Created": "-",
  "Managed": "-",
  "Path": "/bin/entry.sh",
  "Args": [
    "python3",
    "-m",
    "homeassistant",
    "--config",
    "/config"
  ],
  "Config": {
    "Hostname": "home-assistant",
    "Domainname": "",
    "User": "",
    "AttachStdin": false,
    "AttachStdout": false,
    "AttachStderr": false,
    "Tty": true,
    "OpenStdin": true,
    "StdinOnce": false,
    "Env": [
      "PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
      "LANG=C.UTF-8",
      "GPG_KEY=",
      "PYTHON_VERSION=",
      "PYTHON_PIP_VERSION=",
      "WHEELS_LINKS=",
      "TZ="
    ],
    "Cmd": [
      "python3",
      "-m",
      "homeassistant",
      "--config",
      "/config"
    ],
    "Image": "homeassistant/home-assistant:latest",
    "Volumes": null,
    "WorkingDir": "/config",
    "Entrypoint": [
      "/bin/entry.sh"
    ],
    "OnBuild": null,
    "Labels": {
      "io.hass.arch": "",
      "io.hass.base.image": "",
      "io.hass.base.name": "",
      "io.hass.base.version": "4.0",
      "io.hass.type": "",
      "io.hass.version": ""
    },
    "DDSM": false
  },
  "Image": "",
  "NetworkSettings": {
    "Bridge": "",
    "SandboxID": "",
    "HairpinMode": false,
    "LinkLocalIPv6Address": "",
    "LinkLocalIPv6PrefixLen": 0,
    "Networks": {
      "host": {
        "IPAMConfig": null,
        "Links": null,
        "Aliases": null,
        "NetworkID": "",
        "EndpointID": "",
        "Gateway": "",
        "IPAddress": "",
        "IPPrefixLen": 0,
        "IPv6Gateway": "",
        "GlobalIPv6Address": "",
        "GlobalIPv6PrefixLen": 0,
        "MacAddress": "",
        "DriverOpts": null,
        "IPAMOperational": false
      }
    },
    "Service": null,
    "Ports": {},
    "SandboxKey": "",
    "SecondaryIPAddresses": null,
    "SecondaryIPv6Addresses": null,
    "IsAnonymousEndpoint": false,
    "HasSwarmEndpoint": false
  },
  "LogPath": "",
  "Name": "",
  "Driver": "btrfs",
  "OS": "linux",
  "MountLabel": "",
  "ProcessLabel": "",
  "RestartCount": 0,
  "HasBeenStartedBefore": true,
  "HasBeenManuallyStopped": false,
  "MountPoints": {
    "/config": {
      "Source": "/volume1/docker/homeassistant/config",
      "Destination": "/config",
      "RW": true,
      "Name": "",
      "Driver": "",
      "Type": "bind",
      "Relabel": "rw",
      "Spec": {
        "Type": "bind",
        "Source": "/volume1/docker/homeassistant/config",
        "Target": "/config"
      },
      "SkipMountpointCreation": false
    }
  },
  "SecretReferences": null,
  "ConfigReferences": null,
  "AppArmorProfile": "docker-default",
  "HostnamePath": "",
  "HostsPath": "",
  "ShmPath": "",
  "ResolvConfPath": "",
  "SeccompProfile": "",
  "NoNewPrivileges": ""
}

처럼 보여준다. 참고로 일부 정보는 혹시 몰라 삭제했으니 위의 것을 복사해서 사용하면 안된다. 다음은 MountPoints에 /dev/bus/usb를 추가해야 한다.

  "MountPoints": {
    "/config": {
      "Source": "/volume1/docker/homeassistant/config",
      "Destination": "/config",
      "RW": true,
      "Name": "",
      "Driver": "",
      "Type": "bind",
      "Relabel": "rw",
      "Spec": {
        "Type": "bind",
        "Source": "/volume1/docker/homeassistant/config",
        "Target": "/config"
      },
      "SkipMountpointCreation": false
    },
    "/dev/bus/usb": {
      "Source": "/dev/bus/usb",
      "Destination": "/dev/bus/usb",
      "RW": true,
      "Name": "",
      "Driver": "",
      "Type": "bind",
      "Relabel": "rw",
      "Spec": {
        "Type": "bind",
        "Source": "/dev/bus/usb",
        "Target": "/dev/bus/usb"
      },
      "SkipMountpointCreation": false
    }
  },

위와 같이 /dev/bus/usb 부분을 추가한 후에, 다시 이쁜 버전을 복사해서 처음 붙여 놓은 곳에 놓고 “Minify” 버튼을 눌러 아래와 같이 변형한다.

{"StreamConfig":{},"State":{"Running":"-","Paused":"-","Restarting":"-","OOMKilled":"-","RemovalInProgress":"-","Dead":"-","Pid":"-","ExitCode":"-","Error":"-","StartedAt":"-","FinishedAt":"-","Health":null},"ID":"fff308d8bf375c0d7742743a7d66b12977efa925c46fbd3f5bb78773913b0006","Created":"-","Managed":"-","Path":"/bin/entry.sh","Args":["python3","-m","homeassistant","--config","/config"],"Config":{"Hostname":"home-assistant","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":true,"OpenStdin":true,"StdinOnce":false,"Env":["PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","LANG=C.UTF-8","GPG_KEY=","PYTHON_VERSION=","PYTHON_PIP_VERSION=","WHEELS_LINKS=","TZ="],"Cmd":["python3","-m","homeassistant","--config","/config"],"Image":"homeassistant/home-assistant:latest","Volumes":null,"WorkingDir":"/config","Entrypoint":["/bin/entry.sh"],"OnBuild":null,"Labels":{"io.hass.arch":"","io.hass.base.image":"","io.hass.base.name":"","io.hass.base.version":"4.0","io.hass.type":"","io.hass.version":""},"DDSM":false},"Image":"","NetworkSettings":{"Bridge":"","SandboxID":"","HairpinMode":false,"LinkLocalIPv6Address":"","LinkLocalIPv6PrefixLen":0,"Networks":{"host":{"IPAMConfig":null,"Links":null,"Aliases":null,"NetworkID":"","EndpointID":"","Gateway":"","IPAddress":"","IPPrefixLen":0,"IPv6Gateway":"","GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"MacAddress":"","DriverOpts":null,"IPAMOperational":false}},"Service":null,"Ports":{},"SandboxKey":"","SecondaryIPAddresses":null,"SecondaryIPv6Addresses":null,"IsAnonymousEndpoint":false,"HasSwarmEndpoint":false},"LogPath":"","Name":"","Driver":"btrfs","OS":"linux","MountLabel":"","ProcessLabel":"","RestartCount":0,"HasBeenStartedBefore":true,"HasBeenManuallyStopped":false,"MountPoints":{"/config":{"Source":"/volume1/docker/homeassistant/config","Destination":"/config","RW":true,"Name":"","Driver":"","Type":"bind","Relabel":"rw","Spec":{"Type":"bind","Source":"/volume1/docker/homeassistant/config","Target":"/config"},"SkipMountpointCreation":false},"/dev/bus/usb":{"Source":"/dev/bus/usb","Destination":"/dev/bus/usb","RW":true,"Name":"","Driver":"","Type":"bind","Relabel":"rw","Spec":{"Type":"bind","Source":"/dev/bus/usb","Target":"/dev/bus/usb"},"SkipMountpointCreation":false}},"SecretReferences":null,"ConfigReferences":null,"AppArmorProfile":"docker-default","HostnamePath":"","HostsPath":"","ShmPath":"","ResolvConfPath":"","SeccompProfile":"","NoNewPrivileges":""}

자, 위의 문자열을 (그대로 복사하면 안된다. 직접 만든 것을 복사하여야 한다.) 복사해서 해당 설정 파일에 덮어 씌운다.

그리고… 조립은 분해의 역순과 동일하게, 시작은 정지의 역순으로 수행한다.

Docker가 모두 뜬것을 확인했다면, HA Docker에 접속해서 아래와 같은 명령어를 통해 블루투스가 정상동작하는지 확인할 수 있다.

docker# hcitool lescan
LE Scan ...
D8:E0:E1:A2:09:49 (unknown)
C8:28:32:2E:D6:33 (unknown)
D8:E0:E1:A2:09:49 (unknown)
C8:28:32:2E:D6:33 (unknown)
C8:28:32:2E:D6:33 (unknown)

위와 같이 블루투스 장비가 제대로 스캔되면 성공한 것이다. 이제 블루투스 모듈을 구입하면 된다!

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다