반응형

 

냉장고에 부착한 날씨 알리미들

아래쪽에 위치한 전자잉크 표시기는 마트에서 가격표로 사용하는 ESL(전자 가격 표시기) 인데 홈어시스턴트에서 날씨정보를 보내서 사용하고 있습니다.

준비물

  • Gicisky 4.2인치 bluetooth ESL tag (4~5만원)
  • ESP32-S3 N16R8 보드 (1만원)
  • USB C 케이블과 연결할 컴퓨터
  • Home Assistant
  • 무선 홈 네트워크 (공유기)
  • USB 충전기와 케이블
  • 양면테이프 또는 자석

BLE ESL용 AP 만들기

ESP32-S3 보드를 PC 에 연결하고 아래 펌웨어 설치 페이지에서 BLE only AP 를 선택하고 설치합니다.
브라우저에서 시리얼 포트로 바로 연결하고 펌웨어 설치 진행. 문제가 생길 경우 장치관리자 등에서 ESP32 시리얼 포트를 확인해야 합니다.
설치 완료 후 ESP32가 받은 IP로 접근해보면 아래와 같은 화면 확인 가능합니다.

몇분동안 기다리면 ESL tag 가 인식되고 기본 컨텐츠 적용할 수 있습니다.

기타 설정이나 AP 펌웨어 OTA 업그레이드 기능 등등이 있으니 사용해보면 됩니다.

홈어시스턴트에 OpenEPaperLink 설치

홈어시스턴트 HACS 에서 OPenEPaperLink 찾아서 설치하면 됩니다.
설명 및 소스코드는 아래 링크 있습니다. 설치/재부팅 후 통합구성요소에 추가 해주면 됩니다.
https://github.com/OpenEPaperLink/Home_Assistant_Integration

날씨 정보 만들기

날씨 정보 표시하기

다른 사용자가 만든 코드를 참조해서 수정해서 사용중입니다.
원본은 아래 링크에 있습니다.
https://github.com/chunkysteveo/OpenEPaperLink-HA-Weatherman

저는 아래와 같이 수정해서 사용중입니다.
기본적으로 날씨는 담다날씨에서 가져오고, 풍향 풍속은 met 에서가져오고 이런식으로 짬뽕되어 있습니다.
이부분이 좀 귀찮은데 개인화가 필요한거라 한번 노가다 하는 방법밖에 없습니다.
표시하고 싶은 시간대, 간격, 날짜, 표기법, 풍향풍속 유무, 기타 문자열 표기 등등..

그리고 짧은 날씨 정보를 문자로 표시했는데 이건 다른 센서로 기상청 데이터 긁어온거라 생략하겠습니다.
( sensor.today_home_weather 와 sensor.today_weather_txt 항목 )

자동화 편집 시 yaml 모드로 할것 (변수가 있어서...)
위 github 에서 큰 변경점으로는 target 에 device_id 를 넣어줘야 합니다.
개발자 도구에서 OpenEPaperLink: Draw Custom Image 선택 -> 기기 에서 ESL 태그 선택 -> YAML 로 변경 해서 확인하면 됩니다.
target:
device_id: bbb79065b21976e867755ad10f1e8b6f

자동화 yaml

alias: EPaper Tags - 날씨 전자잉크 eink Data - 4.2
description: Created by @svenove, based on @chunkysteveo's Weatherman
triggers:
  - minutes: "11"
    trigger: time_pattern
conditions:
  - condition: time
    after: "05:00:00"
    before: "23:00:00"
actions:
  - data:
      background: white
      rotate: 0
      dry-run: false
      payload:
        - type: icon
          value: "{{ state_attr('sensor.weatherman_data_tag','moon_phase') | string }}"
          x: 5
          "y": 3
          size: 24
          color: black
        - type: icon
          value: blur
          x: 8
          "y": 124
          size: 18
          color: black
        - type: text
          value: "{{  states('sensor.today_weather_txt') }}"
          font: ../../media/NotoSans-Bold.ttf
          x: 8
          "y": 278
          size: 17
          color: "{{'black'}}"
        - type: text
          value: "{{  states('sensor.today_home_weather') }}"
          font: ../../media/NotoSans-Bold.ttf
          x: 88
          "y": 257
          size: 17
          color: "{{'black'}}"
        - type: text
          value: "{{ states('sensor.wn_seongseongdong_cai')| string }} "
          font: ../../media/GothamRnd-Bold.ttf
          x: 50
          "y": 125
          size: 18
          color: black
          anchor: mt
        - type: text
          value: >-
            {{ state_attr('sensor.weatherman_data_tag','wm_temp_0') | round }}{{
            state_attr('sensor.weatherman_data_tag','temperature_unit') }}
          font: ../../media/GothamRnd-Bold.ttf
          x: 140
          "y": 80
          size: 24
          color: >-
            {{'black' if state_attr('sensor.weatherman_data_tag','wm_temp_0') |
            round > severe_temp_low else 'red' }}
          anchor: mt
        - type: icon
          value: >-
            weather-{{ state_attr('sensor.weatherman_data_tag','wm_cond_0') |
            string }}
          x: 116
          "y": 25
          size: 54
          color: >-
            {{'red' if state_attr('sensor.weatherman_data_tag','wm_cond_0') in
            severe_weather else 'black' }}
        - type: text
          value: >-
            {{ '' if
            state_attr('sensor.weatherman_data_tag','wm_precipitation_0') == 0
            else state_attr('sensor.weatherman_data_tag','wm_precipitation_0') |
            string + ' ' +
            state_attr('sensor.weatherman_data_tag','precipitation_unit') }}
          font: ../../media/GothamRnd-Bold.ttf
          x: 140
          "y": 105
          size: 17
          color: >-
            {{'black' if
            state_attr('sensor.weatherman_data_tag','wm_precipitation_0') | int
            < severe_precipitation_hour else 'red' }}
          anchor: mt
        - type: text
          value: >-
            {{  state_attr('sensor.weatherman_data_tag','wm_wind_speed_0') |
            round }}
          font: ../../media/GothamRnd-Bold.ttf
          x: 130
          "y": 125
          size: 17
          color: >-
            {{'black' if
            state_attr('sensor.weatherman_data_tag','wm_wind_speed_0') | int <
            severe_wind_speed else 'red' }}
          anchor: mt
        - type: icon
          value: >-
            {{ "%s" %
            (["arrow-down","arrow-bottom-left","arrow-left","arrow-top-left","arrow-up","arrow-top-right","arrow-right",
            "arrow-bottom-right","arrow-down"][(state_attr('sensor.weatherman_data_tag','wm_wind_dir_0')/45)
            | round ]) }}
          x: 140
          "y": 122
          size: 20
          color: black
        - type: text
          value: >-
            {{ state_attr('sensor.weatherman_data_tag','wm_temp_1') | string
            }}{{ state_attr('sensor.weatherman_data_tag','temperature_unit') }}
          font: ../../media/GothamRnd-Bold.ttf
          x: 216
          "y": 80
          size: 24
          color: >-
            {{'black' if state_attr('sensor.weatherman_data_tag','wm_temp_1') |
            int > severe_temp_low else 'red' }}
          anchor: mt
        - type: icon
          value: >-
            weather-{{ state_attr('sensor.weatherman_data_tag','wm_cond_1') |
            string }}
          x: 192
          "y": 25
          size: 54
          color: >-
            {{'red' if state_attr('sensor.weatherman_data_tag','wm_cond_1') in
            severe_weather else 'black' }}
        - type: text
          value: >-
            {{ '' if
            state_attr('sensor.weatherman_data_tag','wm_precipitation_1') == 0
            else state_attr('sensor.weatherman_data_tag','wm_precipitation_1') |
            string + ' ' +
            state_attr('sensor.weatherman_data_tag','precipitation_unit') }}
          font: ../../media/GothamRnd-Bold.ttf
          x: 216
          "y": 105
          size: 17
          color: >-
            {{'black' if
            state_attr('sensor.weatherman_data_tag','wm_precipitation_1') | int
            < severe_precipitation_hour else 'red' }}
          anchor: mt
        - type: text
          value: >-
            {{  state_attr('sensor.weatherman_data_tag','wm_wind_speed_1') |
            round }}
          font: ../../media/GothamRnd-Bold.ttf
          x: 206
          "y": 125
          size: 17
          color: >-
            {{'black' if
            state_attr('sensor.weatherman_data_tag','wm_wind_speed_1') | int <
            severe_wind_speed else 'red' }}
          anchor: mt
        - type: icon
          value: >-
            {{ "%s" %
            (["arrow-down","arrow-bottom-left","arrow-left","arrow-top-left","arrow-up","arrow-top-right","arrow-right",
            "arrow-bottom-right","arrow-down"][(state_attr('sensor.weatherman_data_tag','wm_wind_dir_1')/45)
            | round ]) }}
          x: 216
          "y": 122
          size: 20
          color: black
        - type: text
          value: >-
            {{ state_attr('sensor.weatherman_data_tag','wm_temp_2') | string
            }}{{ state_attr('sensor.weatherman_data_tag','temperature_unit') }}
          font: ../../media/GothamRnd-Bold.ttf
          x: 292
          "y": 80
          size: 24
          color: >-
            {{'black' if state_attr('sensor.weatherman_data_tag','wm_temp_2') |
            int > severe_temp_low else 'red' }}
          anchor: mt
        - type: icon
          value: >-
            weather-{{ state_attr('sensor.weatherman_data_tag','wm_cond_2') |
            string }}
          x: 268
          "y": 25
          size: 54
          color: >-
            {{'red' if state_attr('sensor.weatherman_data_tag','wm_cond_2') in
            severe_weather else 'black' }}
        - type: text
          value: >-
            {{ '' if
            state_attr('sensor.weatherman_data_tag','wm_precipitation_2') == 0
            else state_attr('sensor.weatherman_data_tag','wm_precipitation_2') |
            string + ' ' +
            state_attr('sensor.weatherman_data_tag','precipitation_unit') }}
          font: ../../media/GothamRnd-Bold.ttf
          x: 292
          "y": 105
          size: 17
          color: >-
            {{'black' if
            state_attr('sensor.weatherman_data_tag','wm_precipitation_2') | int
            < severe_precipitation_hour else 'red' }}
          anchor: mt
        - type: text
          value: >-
            {{  state_attr('sensor.weatherman_data_tag','wm_wind_speed_2') |
            round }}
          font: ../../media/GothamRnd-Bold.ttf
          x: 282
          "y": 125
          size: 17
          color: >-
            {{'black' if
            state_attr('sensor.weatherman_data_tag','wm_wind_speed_2') | int <
            severe_wind_speed else 'red' }}
          anchor: mt
        - type: icon
          value: >-
            {{ "%s" %
            (["arrow-down","arrow-bottom-left","arrow-left","arrow-top-left","arrow-up","arrow-top-right","arrow-right",
            "arrow-bottom-right","arrow-down"][(state_attr('sensor.weatherman_data_tag','wm_wind_dir_2')/45)
            | round ]) }}
          x: 292
          "y": 122
          size: 20
          color: black
        - type: text
          value: >-
            {{ state_attr('sensor.weatherman_data_tag','wm_temp_3') | string
            }}{{ state_attr('sensor.weatherman_data_tag','temperature_unit') }}
          font: ../../media/GothamRnd-Bold.ttf
          x: 368
          "y": 80
          size: 24
          color: >-
            {{'black' if state_attr('sensor.weatherman_data_tag','wm_temp_3') |
            int > severe_temp_low else 'red' }}
          anchor: mt
        - type: icon
          value: >-
            weather-{{ state_attr('sensor.weatherman_data_tag','wm_cond_3') |
            string }}
          x: 344
          "y": 25
          size: 54
          color: >-
            {{'red' if state_attr('sensor.weatherman_data_tag','wm_cond_3') in
            severe_weather else 'black' }}
        - type: text
          value: >-
            {{ '' if
            state_attr('sensor.weatherman_data_tag','wm_precipitation_3') == 0
            else state_attr('sensor.weatherman_data_tag','wm_precipitation_3') |
            string + ' ' +
            state_attr('sensor.weatherman_data_tag','precipitation_unit') }}
          font: ../../media/GothamRnd-Bold.ttf
          x: 368
          "y": 105
          size: 17
          color: >-
            {{'black' if
            state_attr('sensor.weatherman_data_tag','wm_precipitation_3') | int
            < severe_precipitation_hour else 'red' }}
          anchor: mt
        - type: text
          value: >-
            {{  state_attr('sensor.weatherman_data_tag','wm_wind_speed_3') |
            round }}
          font: ../../media/GothamRnd-Bold.ttf
          x: 358
          "y": 125
          size: 17
          color: >-
            {{'black' if
            state_attr('sensor.weatherman_data_tag','wm_wind_speed_3') | int <
            severe_wind_speed else 'red' }}
          anchor: mt
        - type: icon
          value: >-
            {{ "%s" %
            (["arrow-down","arrow-bottom-left","arrow-left","arrow-top-left","arrow-up","arrow-top-right","arrow-right",
            "arrow-bottom-right","arrow-down"][(state_attr('sensor.weatherman_data_tag','wm_wind_dir_3')/45)
            | round ]) }}
          x: 368
          "y": 122
          size: 20
          color: black
        - type: text
          value: >-
            {{ state_attr('sensor.weatherman_data_tag','wm_temp_4_low') | string
            }}
          font: ../../media/GothamRnd-Bold.ttf
          x: 43
          "y": 232
          size: 20
          color: >-
            {{'black' if
            state_attr('sensor.weatherman_data_tag','wm_temp_4_low') | int >
            severe_temp_low else 'red' }}
          anchor: rt
        - type: text
          value: " | "
          font: ../../media/GothamRnd-Bold.ttf
          x: 50
          "y": 230
          size: 20
          color: black
          anchor: mt
        - type: text
          value: "{{ state_attr('sensor.weatherman_data_tag','wm_temp_4') | string }} "
          font: ../../media/GothamRnd-Bold.ttf
          x: 55
          "y": 232
          size: 20
          color: >-
            {{'black' if state_attr('sensor.weatherman_data_tag','wm_temp_4') |
            int > severe_temp_low else 'red' }}
          anchor: lt
        - type: icon
          value: >-
            weather-{{ state_attr('sensor.weatherman_data_tag','wm_cond_4') |
            string }}
          x: 24
          "y": 175
          size: 54
          color: >-
            {{'red' if state_attr('sensor.weatherman_data_tag','wm_cond_4') in
            severe_weather else 'black' }}
        - type: text
          value: >-
            {{ '' if
            state_attr('sensor.weatherman_data_tag','wm_precipitation_4') == 0
            else state_attr('sensor.weatherman_data_tag','wm_precipitation_4') |
            string + ' ' +
            state_attr('sensor.weatherman_data_tag','precipitation_unit') }}
          font: ../../media/GothamRnd-Bold.ttf
          x: 48
          "y": 259
          size: 17
          color: >-
            {{'black' if
            state_attr('sensor.weatherman_data_tag','wm_precipitation_4') | int
            < severe_precipitation_day else 'red' }}
          anchor: mt
        - type: text
          value: >-
            {{ state_attr('sensor.weatherman_data_tag','wm_temp_5_low') | string
            }}
          font: ../../media/GothamRnd-Bold.ttf
          x: 143
          "y": 232
          size: 20
          color: >-
            {{'black' if
            state_attr('sensor.weatherman_data_tag','wm_temp_5_low') | int >
            severe_temp_low else 'red' }}
          anchor: rt
        - type: text
          value: " | "
          font: ../../media/GothamRnd-Bold.ttf
          x: 150
          "y": 230
          size: 20
          color: black
          anchor: mt
        - type: text
          value: "{{ state_attr('sensor.weatherman_data_tag','wm_temp_5') | string }} "
          font: ../../media/GothamRnd-Bold.ttf
          x: 155
          "y": 232
          size: 20
          color: >-
            {{'black' if state_attr('sensor.weatherman_data_tag','wm_temp_5') |
            int > severe_temp_low else 'red' }}
          anchor: lt
        - type: icon
          value: >-
            weather-{{ state_attr('sensor.weatherman_data_tag','wm_cond_5') |
            string }}
          x: 124
          "y": 175
          size: 54
          color: >-
            {{'red' if state_attr('sensor.weatherman_data_tag','wm_cond_5') in
            severe_weather else 'black' }}
        - type: text
          value: >-
            {{ state_attr('sensor.weatherman_data_tag','wm_temp_6_low') | string
            }}
          font: ../../media/GothamRnd-Bold.ttf
          x: 243
          "y": 232
          size: 20
          color: >-
            {{'black' if
            state_attr('sensor.weatherman_data_tag','wm_temp_6_low') | int >
            severe_temp_low else 'red' }}
          anchor: rt
        - type: text
          value: " | "
          font: ../../media/GothamRnd-Bold.ttf
          x: 250
          "y": 230
          size: 20
          color: black
          anchor: mt
        - type: text
          value: "{{ state_attr('sensor.weatherman_data_tag','wm_temp_6') | string }} "
          font: ../../media/GothamRnd-Bold.ttf
          x: 255
          "y": 232
          size: 20
          color: >-
            {{'black' if state_attr('sensor.weatherman_data_tag','wm_temp_6') |
            int > severe_temp_low else 'red' }}
          anchor: lt
        - type: icon
          value: >-
            weather-{{ state_attr('sensor.weatherman_data_tag','wm_cond_6') |
            string }}
          x: 224
          "y": 175
          size: 54
          color: >-
            {{'red' if state_attr('sensor.weatherman_data_tag','wm_cond_6') in
            severe_weather else 'black' }}
        - type: text
          value: >-
            {{ state_attr('sensor.weatherman_data_tag','wm_temp_7_low') | string
            }}
          font: ../../media/GothamRnd-Bold.ttf
          x: 342
          "y": 232
          size: 20
          color: >-
            {{'black' if
            state_attr('sensor.weatherman_data_tag','wm_temp_7_low') | int >
            severe_temp_low else 'red' }}
          anchor: rt
        - type: text
          value: " | "
          font: ../../media/GothamRnd-Bold.ttf
          x: 350
          "y": 230
          size: 20
          color: black
          anchor: mt
        - type: text
          value: "{{ state_attr('sensor.weatherman_data_tag','wm_temp_7') | string }} "
          font: ../../media/GothamRnd-Bold.ttf
          x: 355
          "y": 232
          size: 20
          color: >-
            {{'black' if state_attr('sensor.weatherman_data_tag','wm_temp_7') |
            int > severe_temp_low else 'red' }}
          anchor: lt
        - type: icon
          value: >-
            weather-{{ state_attr('sensor.weatherman_data_tag','wm_cond_7') |
            string }}
          x: 324
          "y": 175
          size: 54
          color: >-
            {{'red' if state_attr('sensor.weatherman_data_tag','wm_cond_7') in
            severe_weather else 'black' }}
        - type: text
          value: >-
            {{ state_attr('sensor.weatherman_data_tag','wm_time_0') | string |
            upper }}
          font: ../../media/GothamRnd-Bold.ttf
          x: 140
          "y": 5
          size: 24
          color: black
          anchor: mt
        - type: text
          value: >-
            {{ state_attr('sensor.weatherman_data_tag','wm_time_1') | string |
            upper }}
          font: ../../media/GothamRnd-Bold.ttf
          x: 216
          "y": 5
          size: 24
          color: black
          anchor: mt
        - type: text
          value: >-
            {{ state_attr('sensor.weatherman_data_tag','wm_time_2') | string |
            upper }}
          font: ../../media/GothamRnd-Bold.ttf
          x: 292
          "y": 5
          size: 24
          color: black
          anchor: mt
        - type: text
          value: >-
            {{ state_attr('sensor.weatherman_data_tag','wm_time_3') | string |
            upper }}
          font: ../../media/GothamRnd-Bold.ttf
          x: 368
          "y": 5
          size: 24
          color: black
          anchor: mt
        - type: text
          value: >-
            {{ state_attr('sensor.weatherman_data_tag','wm_time_4') | string |
            upper }}
          font: ../../media/NotoSans-Bold.ttf
          x: 50
          "y": 154
          size: 24
          color: black
          anchor: mt
        - type: text
          value: >-
            {{ state_attr('sensor.weatherman_data_tag','wm_time_5') | string |
            upper }}
          font: ../../media/NotoSans-Bold.ttf
          x: 150
          "y": 154
          size: 24
          color: black
          anchor: mt
        - type: text
          value: >-
            {{ state_attr('sensor.weatherman_data_tag','wm_time_6') | string |
            upper }}
          font: ../../media/NotoSans-Bold.ttf
          x: 250
          "y": 154
          size: 24
          color: black
          anchor: mt
        - type: text
          value: >-
            {{ state_attr('sensor.weatherman_data_tag','wm_time_7') | string |
            upper }}
          font: ../../media/NotoSans-Bold.ttf
          x: 350
          "y": 154
          size: 24
          color: black
          anchor: mt
        - type: line
          fill: black
          width: 2
          x_start: 20
          y_start: 144
          x_end: 380
          y_end: 144
        - type: text
          value: >-
            {{ state_attr('sensor.weatherman_data_tag','wm_temp_now') | round
            }}{{ state_attr('sensor.weatherman_data_tag','temperature_unit') }}
          font: ../../media/GothamRnd-Bold.ttf
          x: 52
          "y": 94
          size: 38
          anchor: mt
          color: >-
            {{'black' if state_attr('sensor.weatherman_data_tag','wm_temp_now')
            | round > severe_temp_low else 'red' }}
        - type: icon
          value: >-
            weather-{{ state_attr('sensor.weatherman_data_tag','wm_cond_now') |
            string }}
          x: 6
          "y": 2
          size: 92
          color: >-
            {{'red' if state_attr('sensor.weatherman_data_tag','wm_cond_now') in
            severe_weather else 'black' }}
    target:
      device_id: bbb79065b21976e867755ad10f1e8b6f
    action: open_epaper_link.drawcustom
variables:
  severe_weather:
    - hail
    - rainy
    - snowy
    - pouring
    - lightning
    - exceptional
  severe_temp_low: 0
  severe_wind_speed: 10
  severe_precipitation_hour: 5
  severe_precipitation_day: 10
mode: restart

configuration.yaml 추가

홈어시스턴트 templete.yaml에 아래와 같이 추가해서 10분마다 날씨 데이터 갱신 시키고 있습니다.
물론 configutaion.yaml 에서 templete.yaml 이 추가되어 있죠.

# Bundle up all the data to send over to Weatherman.
# Will auto-detect the weather integration entity to use.
# If that doesn't work, set the value for the forecast_entity variable manually.
- trigger:
    platform: time_pattern
    minutes: "/10"
    variables:
      forecast_entity: weather.forecast_home
      forecast_kor: weather.damdanalssi_seongseongdong
  action:
    - service: weather.get_forecasts
      target:
        entity_id: "{{forecast_entity}}"
      data:
        type: hourly
      response_variable: weather_home_hourly
    - service: weather.get_forecasts
      target:
        entity_id: "{{forecast_entity}}"
      data:
        type: daily
      response_variable: weather_home_daily
    - service: weather.get_forecasts
      target:
        entity_id: "{{forecast_kor}}"
      data:
        type: hourly
      response_variable: weather_kor_hourly
    - service: weather.get_forecasts
      target:
        entity_id: "{{forecast_kor}}"
      data:
        type: daily
      response_variable: weather_kor_daily
  sensor:
    - name: Weatherman Data Tag
      state: "{{ now().isoformat() }}"
      attributes:
        moon_phase: >
          {% set cond_moon = states('sensor.moon_phase') %}
          {% if cond_moon == 'new_moon' %}
          moon-new
          {% elif cond_moon == 'full_moon' %}
          moon-full
          {% else %}
          {{ "moon-" + cond_moon | replace("_", "-") }}
          {%endif%}
        sun_next_rising: >
          {{ as_timestamp(state_attr("sun.sun", "next_rising")) | timestamp_custom('%-I:%M %p') }}
        sun_next_setting: >
          {{ as_timestamp(state_attr("sun.sun", "next_setting")) | timestamp_custom('%-I:%M %p') }}  
        temperature_unit: "{{ state_attr(forecast_kor,'temperature_unit') }}"
        wind_speed_unit: "{{ state_attr(forecast_kor,'wind_speed_unit') }}"
        precipitation_unit: "{{ state_attr(forecast_kor,'precipitation_unit') }}"  
        pressure_unit: "{{ state_attr(forecast_kor,'pressure_unit') }}"
        wm_cond_now: >
          {% set cond_now = states(forecast_kor) %}
          {% if cond_now == 'partlycloudy' %}{% set cond_now = 'partly-cloudy' %}{% endif %}
          {% if cond_now == 'clear-night' %}{% set cond_now = 'night' %}{% endif %}
          {% if states('sun.sun') == 'below_horizon' %}
              {% if cond_now == 'sunny' %} night {% elif cond_now == 'partly-cloudy' %} night-partly-cloudy {% else %} {{ cond_now }} {% endif %}
          {% else %}
              {{ cond_now }}
          {% endif %}
        wm_temp_now: >
          {{ state_attr(forecast_kor,'temperature') }}  
        wm_wind_speed_now: >
          {{ state_attr(forecast_kor,'wind_speed') | round }}
        wm_wind_dir_now: >
          {{ state_attr(forecast_entity,'wind_bearing') | round }}    
        wm_dew_point_now: >
          {{ state_attr(forecast_kor,'dew_point') }}
        wm_humidity_now: >
          {{ state_attr(forecast_kor,'humidity') }}
        wm_cloud_coverage_now: >
          {{ state_attr(forecast_entity,'cloud_coverage') }}
        wm_pressure_now: >
          {{ state_attr(forecast_kor,'pressure') }}  
        wm_cond_0: >
          {% set cond0 = weather_kor_hourly[forecast_kor]['forecast'][0].condition %}
          {% if cond0 == 'partlycloudy' %}{% set cond0 = 'partly-cloudy' %}{% endif %}
          {% if cond0 == 'clear-night' %}{% set cond0 = 'night' %}{% endif %}
          {% set next_setting = as_timestamp(state_attr('sun.sun', 'next_setting')) %}
          {% set next_rising = as_timestamp(state_attr('sun.sun', 'next_rising')) %}
          {% set cond0_time = as_timestamp(weather_kor_hourly[forecast_kor]['forecast'][0].datetime) %}
          {% if states('sun.sun') == 'above_horizon' and cond0_time > next_setting %}
              {% if cond0 == 'sunny' %} night {% elif cond0 == 'partly-cloudy' %} night-partly-cloudy {% else %} {{ cond0 }} {% endif %}
          {% elif states('sun.sun') == 'below_horizon' and cond0_time < next_rising %}
              {% if cond0 == 'sunny' %} night {% elif cond0 == 'partly-cloudy' %} night-partly-cloudy {% else %} {{ cond0 }} {% endif %}    
          {% else %}
              {{ cond0 }}
          {% endif %}
        wm_temp_0: >
          {{ weather_kor_hourly[forecast_kor]['forecast'][0].temperature | round }}
        wm_precipitation_0: >
          {{ weather_kor_hourly[forecast_kor]['forecast'][0].precipitation }}
        wm_wind_speed_0: >
          {{ weather_kor_hourly[forecast_kor]['forecast'][0].wind_speed | round }}
        wm_wind_dir_0: >
          {{ weather_home_hourly[forecast_entity]['forecast'][0].wind_bearing | round }}
        wm_time_0: >
          {{ as_timestamp(weather_kor_hourly[forecast_kor]['forecast'][0].datetime) | timestamp_custom('%I') | int }} {{ as_timestamp(weather_home_hourly[forecast_entity]['forecast'][0].datetime) | timestamp_custom('%p') }}    
        wm_cond_1: >
          {% set cond1 = weather_kor_hourly[forecast_kor]['forecast'][1].condition %}
          {% if cond1 == 'partlycloudy' %}{% set cond1 = 'partly-cloudy' %}{% endif %}
          {% if cond1 == 'clear-night' %}{% set cond1 = 'night' %}{% endif %}
          {% set next_setting = as_timestamp(state_attr('sun.sun', 'next_setting')) %}
          {% set next_rising = as_timestamp(state_attr('sun.sun', 'next_rising')) %}
          {% set cond1_time = as_timestamp(weather_kor_hourly[forecast_kor]['forecast'][1].datetime) %}
          {% if states('sun.sun') == 'above_horizon' and cond1_time > next_setting %}
              {% if cond1 == 'sunny' %} night {% elif cond1 == 'partly-cloudy' %} night-partly-cloudy {% else %} {{ cond1 }} {% endif %}
          {% elif states('sun.sun') == 'below_horizon' and cond1_time < next_rising %}
              {% if cond1 == 'sunny' %} night {% elif cond1 == 'partly-cloudy' %} night-partly-cloudy {% else %} {{ cond1 }} {% endif %}
          {% else %}
              {{ cond1 }}
          {% endif %}
        wm_temp_1: >
          {{ weather_kor_hourly[forecast_kor]['forecast'][1].temperature | round }}
        wm_precipitation_1: >
          {{ weather_kor_hourly[forecast_kor]['forecast'][1].precipitation }}
        wm_wind_speed_1: >
          {{ weather_kor_hourly[forecast_kor]['forecast'][1].wind_speed | round }}
        wm_wind_dir_1: >
          {{ weather_home_hourly[forecast_entity]['forecast'][1].wind_bearing | round }}
        wm_time_1: >
          {{ as_timestamp(weather_kor_hourly[forecast_kor]['forecast'][1].datetime) | timestamp_custom('%I') | int }} {{ as_timestamp(weather_home_hourly[forecast_entity]['forecast'][3].datetime) | timestamp_custom('%p') }}    
        wm_cond_2: >
          {% set cond2 = weather_kor_hourly[forecast_kor]['forecast'][4].condition %}
          {% if cond2 == 'partlycloudy' %}{% set cond2 = 'partly-cloudy' %}{% endif %}
          {% if cond2 == 'clear-night' %}{% set cond2 = 'night' %}{% endif %}
          {% set next_setting = as_timestamp(state_attr('sun.sun', 'next_setting')) %}
          {% set next_rising = as_timestamp(state_attr('sun.sun', 'next_rising')) %}
          {% set cond2_time = as_timestamp(weather_kor_hourly[forecast_kor]['forecast'][4].datetime) %}
          {% if states('sun.sun') == 'above_horizon' and cond2_time > next_setting %}
              {% if cond2 == 'sunny' %} night {% elif cond2 == 'partly-cloudy' %} night-partly-cloudy {% else %} {{ cond2 }} {% endif %}
          {% elif states('sun.sun') == 'below_horizon' and cond2_time < next_rising %}
              {% if cond2 == 'sunny' %} night {% elif cond2 == 'partly-cloudy' %} night-partly-cloudy {% else %} {{ cond2 }} {% endif %}
          {% else %}
              {{ cond2 }}
          {% endif %}
        wm_temp_2: >
          {{ weather_kor_hourly[forecast_kor]['forecast'][4].temperature | round }}
        wm_precipitation_2: >
          {{ weather_kor_hourly[forecast_kor]['forecast'][4].precipitation }}
        wm_wind_speed_2: >
          {{ weather_kor_hourly[forecast_kor]['forecast'][4].wind_speed | round }}
        wm_wind_dir_2: >
          {{ weather_home_hourly[forecast_entity]['forecast'][4].wind_bearing | round }}
        wm_time_2: >
          {{ as_timestamp(weather_kor_hourly[forecast_kor]['forecast'][4].datetime) | timestamp_custom('%I') | int }} {{ as_timestamp(weather_home_hourly[forecast_entity]['forecast'][6].datetime) | timestamp_custom('%p') }}    
        wm_cond_3: >
          {% set cond3 = weather_kor_hourly[forecast_kor]['forecast'][8].condition %}
          {% if cond3 == 'partlycloudy' %}{% set cond3 = 'partly-cloudy' %}{% endif %}
          {% if cond3 == 'clear-night' %}{% set cond3 = 'night' %}{% endif %}
          {% set next_setting = as_timestamp(state_attr('sun.sun', 'next_setting')) %}
          {% set next_rising = as_timestamp(state_attr('sun.sun', 'next_rising')) %}
          {% set cond3_time = as_timestamp(weather_kor_hourly[forecast_kor]['forecast'][8].datetime) %}
          {% if states('sun.sun') == 'above_horizon' and cond3_time > next_setting %}
              {% if cond3 == 'sunny' %} night {% elif cond3 == 'partly-cloudy' %} night-partly-cloudy {% else %} {{ cond3 }} {% endif %}
          {% elif states('sun.sun') == 'below_horizon' and cond3_time < next_rising %}
              {% if cond3 == 'sunny' %} night {% elif cond3 == 'partly-cloudy' %} night-partly-cloudy {% else %} {{ cond3 }} {% endif %}
          {% else %}
              {{ cond3 }}
          {% endif %}
        wm_temp_3: >
          {{ weather_kor_hourly[forecast_kor]['forecast'][8].temperature | round }}
        wm_precipitation_3: >
          {{ weather_kor_hourly[forecast_kor]['forecast'][8].precipitation }}
        wm_wind_speed_3: >
          {{ weather_kor_hourly[forecast_kor]['forecast'][8].wind_speed | round }}
        wm_wind_dir_3: >
          {{ weather_home_hourly[forecast_entity]['forecast'][8].wind_bearing | round }}
        wm_time_3: >
          {{ as_timestamp(weather_kor_hourly[forecast_kor]['forecast'][8].datetime) | timestamp_custom('%I') | int }} {{ as_timestamp(weather_home_hourly[forecast_entity]['forecast'][8].datetime) | timestamp_custom('%p') }}    
        wm_cond_4: >
          {% set cond4 = weather_kor_daily[forecast_kor]['forecast'][1].condition %}
          {% if cond4 == 'partlycloudy' %}{% set cond4 = 'partly-cloudy'%}{% endif %}
          {% if cond4 == 'clear-night' %}{% set cond4 = 'night' %}{% endif %}
          {{ cond4 }}
        wm_temp_4: >
          {{ weather_kor_daily[forecast_kor]['forecast'][1].temperature | round }}
        wm_precipitation_4: >
          {{ weather_kor_daily[forecast_kor]['forecast'][1].precipitation }}
        wm_temp_4_low: >
          {{ weather_kor_daily[forecast_kor]['forecast'][1].templow | round }}
        wm_wind_speed_4: >
          {{ weather_kor_daily[forecast_kor]['forecast'][1].wind_speed | round }}
        wm_wind_dir_4: >
          {{ weather_home_daily[forecast_entity]['forecast'][1].wind_bearing | round }}
        wm_time_4: >
          {{ (as_timestamp(weather_kor_daily[forecast_kor]['forecast'][1].datetime) | timestamp_custom('%-d')) ~ " " ~ (["일","월","화","수","목","금","토"][as_timestamp(weather_kor_daily[forecast_kor]['forecast'][1].datetime) | timestamp_custom('%w') | int]) | string }}
        wm_cond_5: >
          {% set cond5 = weather_kor_daily[forecast_kor]['forecast'][2].condition %}
          {% if cond5 == 'partlycloudy' %}{% set cond5 = 'partly-cloudy'%}{% endif %}
          {% if cond5 == 'clear-night' %}{% set cond5 = 'night' %}{% endif %}
          {{ cond5 }}
        wm_temp_5: >
          {{ weather_kor_daily[forecast_kor]['forecast'][2].temperature | round }}
        wm_precipitation_5: >
          {{ weather_kor_daily[forecast_kor]['forecast'][2].precipitation }}
        wm_temp_5_low: >
          {{ weather_kor_daily[forecast_kor]['forecast'][2].templow | round }}
        wm_wind_speed_5: >
          {{ weather_kor_daily[forecast_kor]['forecast'][2].wind_speed | round }}
        wm_wind_dir_5: >
          {{ weather_home_daily[forecast_entity]['forecast'][2].wind_bearing | round }}
        wm_time_5: >
          {{ (as_timestamp(weather_kor_daily[forecast_kor]['forecast'][2].datetime) | timestamp_custom('%-d')) ~ " " ~ (["일","월","화","수","목","금","토"][as_timestamp(weather_kor_daily[forecast_kor]['forecast'][2].datetime) | timestamp_custom('%w') | int]) | string }}
        wm_cond_6: >
          {% set cond6 = weather_kor_daily[forecast_kor]['forecast'][3].condition %}
          {% if cond6 == 'partlycloudy' %}{% set cond6 = 'partly-cloudy'%}{% endif %}
          {% if cond6 == 'clear-night' %}{% set cond6 = 'night' %}{% endif %}
          {{ cond6 }}
        wm_temp_6: >
          {{ weather_kor_daily[forecast_kor]['forecast'][3].temperature | round }}
        wm_precipitation_6: >
          {{ weather_kor_daily[forecast_kor]['forecast'][3].precipitation }}
        wm_temp_6_low: >
          {{ weather_kor_daily[forecast_kor]['forecast'][3].templow | round }}
        wm_wind_speed_6: >
          {{ weather_kor_daily[forecast_kor]['forecast'][3].wind_speed | round }}
        wm_wind_dir_6: >
          {{ weather_home_daily[forecast_entity]['forecast'][3].wind_bearing | round }}
        wm_time_6: >
          {{ (as_timestamp(weather_kor_daily[forecast_kor]['forecast'][3].datetime) | timestamp_custom('%-d')) ~ " " ~ (["일","월","화","수","목","금","토"][as_timestamp(weather_kor_daily[forecast_kor]['forecast'][3].datetime) | timestamp_custom('%w') | int]) | string }}
        wm_cond_7: >
          {% set cond7 = weather_kor_daily[forecast_kor]['forecast'][4].condition %}
          {% if cond7 == 'partlycloudy' %}{% set cond7 = 'partly-cloudy'%}{% endif %}
          {% if cond7 == 'clear-night' %}{% set cond7 = 'night' %}{% endif %}
          {{ cond7 }}
        wm_temp_7: >
          {{ weather_kor_daily[forecast_kor]['forecast'][4].temperature | round }}
        wm_precipitation_7: >
          {{ weather_kor_daily[forecast_kor]['forecast'][4].precipitation }}
        wm_temp_7_low: >
          {{ weather_kor_daily[forecast_kor]['forecast'][4].templow | round }}
        wm_wind_speed_7: >
          {{ weather_home_daily[forecast_entity]['forecast'][4].wind_speed | round }}
        wm_wind_dir_7: >
          {{ weather_home_daily[forecast_entity]['forecast'][4].wind_bearing | round }}
        wm_time_7: >
          {{ (as_timestamp(weather_kor_daily[forecast_kor]['forecast'][4].datetime) | timestamp_custom('%-d')) ~ " " ~ (["일","월","화","수","목","금","토"][as_timestamp(weather_kor_daily[forecast_kor]['forecast'][4].datetime) | timestamp_custom('%w') | int]) | string }}
        wm_cond_8: >
          {% set cond8 = weather_kor_daily[forecast_kor]['forecast'][5].condition %}
          {% if cond8 == 'partlycloudy' %}{% set cond8 = 'partly-cloudy'%}{% endif %}
          {% if cond8 == 'clear-night' %}{% set cond8 = 'night' %}{% endif %}
          {{ cond8 }}
        wm_temp_8: >
          {{ weather_kor_daily[forecast_kor]['forecast'][5].temperature | round }}
        wm_temp_8_low: >
          {{ weather_kor_daily[forecast_kor]['forecast'][5].templow | round }}    
        wm_precipitation_8: >
          {{ weather_kor_daily[forecast_kor]['forecast'][5].precipitation }}
        wm_wind_speed_8: >
          {{ weather_home_daily[forecast_entity]['forecast'][5].wind_speed | round }}
        wm_wind_dir_8: >
          {{ weather_home_daily[forecast_entity]['forecast'][5].wind_bearing | round }}
        wm_time_8: >
          {{ (as_timestamp(weather_kor_daily[forecast_kor]['forecast'][5].datetime) | timestamp_custom('%-d')) ~ " " ~ (["일","월","화","수","목","금","토"][as_timestamp(weather_kor_daily[forecast_kor]['forecast'][5].datetime) | timestamp_custom('%w') | int]) | string }}

 

험난한 산을 넘고 요렇게 정보를 가득 채운 날씨 알리미 창을 만들었습니다.

 

사담을 더 붙이자면 7인치 제품 사고싶은데 너무 비싸더군요.
openepaperlink 디스코드에서 보면 국산 solum 제품 중고로 구해서 많이들 쓰더라구요. 펌웨어도 다시 라이팅 하구요.
AP는 esp32-s3 + esp32-c6 로 만들구요.

 

아니면 타오바오에 hanshow(汉朔) 제품이 중고로 조금 있던데, 요녀석도 호환 펌웨어 만들어 놓은게 조금 있더라구요.
그런데 펌웨어 올리려면 부숴서 뜯어서 펌웨어 라이팅 위치 확인하고 뒷판 구멍만 살짝 뚫고 포고핀 만든거로 라이팅 하더랍니다.
한두개 쓰기에는 좀 안맞고 대여섯개 만들어야 하는데.... 어렵습니다. 나중에 도전해봐야겠어요.

 

참조 링크들

https://install.openepaperlink.de/
https://github.com/OpenEPaperLink/OpenEPaperLink/wiki
https://github.com/OpenEPaperLink/Home_Assistant_Integration
https://github.com/OpenEPaperLink/Home_Assistant_Integration/wiki
https://github.com/chunkysteveo/OpenEPaperLink-HA-Weatherman

반응형
반응형

 

아파트 월패드에서 보면 에너지 사용량이 잘 정리되어 있습니다.

그렇지만 Home assitant 에서 데이터를 가져와서 보관하는게 더 다양하게 활용 할 수 있어서 삽질을 해봤습니다.

 

준비물

  • Home assistant ( MQTT 애드온 설치 완료 )
  • python3 을 실행할 수 있는 서버
  • 현대통신 사이트 주소와 아이디/암호

 

 

아파트에서 아래와 같은 사이트에 접속 가능해야 합니다.

보통 관리사무소에 문의해보시면 사용 가능한지 확인 가능 합니다.

 

월패드 원격제어용 아파트 사이트

 

 

크롤링 방법

beatufulsuop 을 이용한 크롤링 방법은 생략하겠습니다.

대충 설명하면 브라우저로 열심히 탐색하고 로그인 방법, 날짜 지정 방법, 필요한 데이터 xpath 확인하고 이거저거 해보면 됩니다. 크롤링중 난이도 낮으니 다른 문서 찾아보시면 됩니다.

 

 

해당 스크립트 사용을 위해서는 python 3.x 이 실행되어야 합니다.

저는 debian 을 사용중이라서 아래 명령어로 설치하고 준비했습니다. (home assistant 를 debian 에 올려서 사용중입니다.)

apt install python3 pip

 

필요한 python 패키지는 pip로 설치 필요합니다.

pip install requests json beautifulsoup4 paho-mqtt

 

 

파이썬 스크립트

homeUsage.py 내용 아래와 같이 작성해서 원하는 위치에 저장해주세요.

수정 필요한 내용은

홈페이지 주소, 아이디, 암호, mqtt 용 ip/아이디/암호 총 6가지입니다. 

import requests
import json
from bs4 import BeautifulSoup
import paho.mqtt.publish as publish

login_url = 'http://1xx.xxx.xx.xx/index_iframe.aspx'   # 아파트마다 ip 다름 수정 필요

user = 'UserID'
password = 'UserPassword'

mqttIP = 'mqtt서버 IP 주소'
mqttID = 'mqtt아이디'
mqttPW = 'mqtt암호!'

# requests.session 메서드는 해당 reqeusts를 사용하는 동안 cookie를 header에 유지하도록 하여
# 세션이 필요한 HTTP 요청에 사용됩니다.
session = requests.session()

params = dict()
params['uid'] = user
params['upwd'] = password


# javascrit(jQuery) 코드를 분석해보니, 결국 index_iframe.aspx 를 uid 와 upwd 값과 함께
# POST로 호출하기 때문에 다음과 같이 requests.session.post() 메서드를 활용하였습니다.

res = session.post(login_url, data = params)

# "응답코드가 200 즉, OK가 아닌 경우 에러를 발생시키는 메서드입니다."
res.raise_for_status()

# 'Set-Cookie'로 PHPSESSID 라는 세션 ID 값이 넘어옴을 알 수 있다.
# print(res.headers)

# cookie로 세션을 로그인 상태를 관리하는 상태를 확인해보기 위한 코드입니다.
# print(session.cookies.get_dict())

# 여기서부터는 로그인이 된 세션이 유지됩니다. session 에 header에는 Cookie에 PHPSESSID가 들어갑니다.

# 주소 형식 http://11.x.x.x/hwork/iframe_DayValue.aspx?txtFDate=2022-08-21

dateParams = dict()
# 특정 날짜 데이터가 필요하면 아래와 같이 사용
#dateParams['txtFDate'] = "2022-09-02"


usage_url = 'http://11.x.x.x/hwork/iframe_DayValue.aspx'
res = session.post(usage_url, data = dateParams)

# 검침량 가져오기
if res.status_code == 200:
    soup = BeautifulSoup(res.text, 'html.parser')
    electricUasge = soup.select_one('#DayForm > table:nth-child(2) > tr:nth-child(4) > td:nth-child(3)').get_text()
    tapWaterUsage = soup.select_one('#DayForm > table:nth-child(2) > tr:nth-child(6) > td:nth-child(3)').get_text()
    gasUsage = soup.select_one('#DayForm > table:nth-child(2) > tr:nth-child(10) > td:nth-child(3)').get_text()

    print(electricUasge, tapWaterUsage, gasUsage)

else :
    print(res.status_code)

usage = {"electric":electricUasge, "tapwater":tapWaterUsage, "gas":gasUsage}

publish.single('homeUsage/now', json.dumps(usage, indent=2, ensure_ascii=False), hostname=mqttIP, auth={'username':mqttID, 'password':mqttPW})

 

 

MQTT 토픽 확인

이제 python3 homeUsage.py 명령어를 실행하면 현재 검침량이 화면에 출력되고

동시에 mqtt 서버로 전송됩니다.

mqtt 토픽은 homeUsage/now 로 전송됩니다.

 

HA 에서 확인 방법은 아래와 같습니다.

설정 -> 기기 및 서비스 -> Mosquitto broker -> 구성 -> 토픽내용 들어보기 -> 청취토픽에 아래 입력 후 청취시작

homeUsage/#

 

 

일정 주기 실행 방법

crontab 에 아래와 같이 스크립트가 주기적으로 실행 될 수 있도록 추가

아래 예시는 10분에 한번씩 읽어오는 내용. 

(데이터 업데이트가 느려서 더 짧아도 의미 없음...)

 

sudo crontab -e 

*/10 * * * * python3 /root/script/homeUsage.py

 

 

Home assistant 구성요소 추가

일단 mqtt로 받은 내용을 sensor 로 대충 넣어놓고

에너지 대시보드에 넣기 위해서 특정 포멧을 만족시켜줘야 합니다. 

자세한 내용은 아래 내용 참조 바랍니다.

https://www.home-assistant.io/more-info/statistics/

 

더 아름답게? 추가 가능할 거 같은데 저는 그냥 덕지덕지 붙였습니다..

 

mqtt.yaml 내용 추가 

sensor:
  - unique_id: "home_power_usage"
    name: power_usage_now
    icon: mdi:flash
    unit_of_measurement: kWh
    state_topic: "homeUsage/now"
    value_template: |-
      {% if value_json.electric == "0.00" %}
        {{ "unknown" }}
      {% else %}
        {{ value_json.electric | round(3) }}
      {% endif %}
      
  - unique_id: "home_tapwater_usage"
    name: tapwater_usage_now
    icon: mdi:water-pump
    unit_of_measurement: m³
    state_topic: "homeUsage/now"
    value_template: |-
      {% if value_json.tapwater == "0.00" %}
        {{ "unknown" }}
      {% else %}
        {{ value_json.tapwater | round(3) }}
      {% endif %}
    device_class: water
    state_class: total_increasing

  - unique_id: "home_gas_usage"
    name: gas_usage_now
    icon: mdi:meter-gas
    state_topic: "homeUsage/now"
    unit_of_measurement: m³
    value_template: |-
      {% if value_json.gas == "0.00" %}
        {{ "unknown" }}
      {% else %}
        {{ value_json.gas | round(3) }}
      {% endif %}

 

template.yaml 내용 추가

- sensor:
    - name: "Total_power_consumption_kWh_energy"
      unit_of_measurement: kWh
      state: >
        {{ states('sensor.power_usage_now')|float() }}
      device_class: energy
      state_class: total_increasing
      attributes:         
        last_reset: '1970-01-01T00:00:00+00:00'

    - name: "gas_usage_m3"
      unit_of_measurement: m³
      state: >
        {{ states('sensor.gas_usage_now')|float() }}
      device_class: gas
      state_class: total_increasing
      attributes:         
        last_reset: '1970-01-01T00:00:00+00:00'

    - name: "water_usage_m3"
      unit_of_measurement: m³
      state: >
        {{ states('sensor.tapwater_usage_now')|float() }}
      device_class: water
      state_class: total_increasing
      attributes:         
        last_reset: '1970-01-01T00:00:00+00:00'

 

configuration.yaml  내용 추가

mqtt: !include mqtt.yaml
template: !include template.yaml

 

이제 에너지 대시보드에 추가할 수 있습니다.

 

대시보드에 추가

설정 -> 대시보드 -> 에너지 

 

대시보드 눌러보면 아래와 같이 나와요.

중간중간 업데이트가 안되는 구간이 꽤 있습니다.

업데이트 안되다 몰아서 되면 한방에 소모량이 많은 것 처럼 보이네요.

작년 데이터 열어보면 몇일간 안된적도 있더라구요.

 

 

 

 

 

크롤링 관련 참조 글

https://www.fun-coding.org/crawl_advance2.html#gsc.tab=0

반응형
반응형

부제: usb 선풍기를 wifi 로 제어하고 home assistant 에 연동하기 (ESPHome, Wemos d1 mini)

 

집에 장미를 들였는데 흰가루 병에 걸렸다.

수국 흰가루병

 

 

바람을 자주 쐬어줘야 좋다는 이야기를 듣고 굴러다니는 usb 선풍기를 찾아서 켜주곤 했는데,

매일 켜고 끄는것도 일이어서 자동화를 시동해 보았다.

 

먼저 선풍기를 뜯어서 어떤식으로 동작을 해야할지 생각해야 한다.

딱 보니까 뜯어서 푸쉬버튼을 누르는 동작을 릴레이나 트랜지스터로 구현하면 될 것 같다.

 

선풍기

 

 

뜯어봤다.

 

usb를 통해서 5v 입력을 받는데 여기서 선을 따서 wemos d1 mini(esp8266) 을 구동하면 될듯 싶다.

그리고 푸쉬버튼과 기판 프린팅을 보고 연결해야 할 부위를 찾는다.

 

아래 그림에서 보듯이 푸쉬버튼을 누를때 하나가 빨간선 두쌍을 연결해준다.

 

 

준비물

  1. 인두
  2. wemos d1 mini  ( + 롬라이팅용 usb 케이블 )
  3. 2n2222A BJT 트렌지스터 3개 또는 필요 수량
  4. 저항 1k옴 전후 3개 또는 필요 수량
  5. 연결용 전선
  6. 잘 셋팅된 Home assistant 와 esphome 애드온

 

 

 

작업사진

wemos d1 mini d5 와 d6 를 켜줄때만 push 버튼 양단이 통전 되도록 해준다.

보통때는 잘 열리지 않도록 d5, d6에 저항을 물려줬다.

 

 

 

D5(또는 D6) 가 트랜지스터 2번 핀에 연결되어야 하고, 1,2 는 push 버튼 양단에 연결해주면 된다.

보통은 3->1 방향으로 전류가 흐르기 때문에 3번을 ground 에 연결해주면 된다.

(반대로 해도 큰 상관 없을 것 같다.)

 

트랜지스터 구조

 

 

건전지 넣는 위치를 뜯어서 선을 빼주자.

 

 

 

이제 esphome 롬파일을 만들자.

 

esphome 에 들어가서 우측 하단 +를 눌러서 Create New node 를 해보자.

1. node name은 원하는 내용을

2. device type 은 wemos D1 mini 를 고르고

3. wifi는 사용하는 와이파이를 누르고 

4. finish 를 하면 완성이 된다.

 

그리고 아래와 같이 넣어줬다. 버튼 하나는 켜고 하나는 끄는 버튼을 할당해줬다.

당연히 계속 버튼이 눌려있으면 안되니까 시간 간격을 두고, 혹시 모르니 두번씩 눌리게 했다.

 

 

esphome:
  name: usb_fan
  platform: ESP8266
  board: d1_mini

wifi:
  ssid: "YOUR_SSID"
  password: "YOUR_PASSWORD"

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Mini-Usb-Fan Fallback Hotspot"
    password: "YByZ8eeeekW"

captive_portal:

# Enable logging
logger:
  level: DEBUG #기본값

# Enable Home Assistant API
api:
  password: "YOUR_API_PASSWORD"

ota:
  password: "YOUR_API_PASSWORD"


switch:
- platform: gpio
  pin: D6
  id: btn_off
- platform: gpio
  pin: D5
  id: btn_on

- platform: template
  name: "Usb Fan"
  optimistic: true
  turn_on_action:
  - switch.turn_on: btn_on
  - delay: 0.1s
  - switch.turn_off: btn_on
  - delay: 0.3s
  - switch.turn_on: btn_on
  - delay: 0.1s
  - switch.turn_off: btn_on
  turn_off_action:
  - switch.turn_on: btn_off
  - delay: 0.1s
  - switch.turn_off: btn_off
  - delay: 0.3s
  - switch.turn_on: btn_off #press off button twice just in case
  - delay: 0.1s
  - switch.turn_off: btn_off

 

 

 

이제 home assistant 에서 통합구성요소를 추가하면 된다.

esphome 을 찾아서 선택하고 위에서 만들어진 녀석 ip를 넣어주면 된다.

 

 

이제 home assitant 에서 switch 로 인식이 된다.

끄고 켜는것 모두 잘 된다. 이제 자동화나 스케쥴 등록이 가능하다.

 

 

반응형
반응형

HA+ESP8266+CC2530+CC2591 = Zigbee2Mqtt

 

 

home assistant 에서 zigbee2mqtt 를 사용중이다.

zigbee 장치는 보통 전용 브랜드의 게이트웨이나 브릿지에 연결된다 (휴 브릿지, 트로드프리 게이트웨이 등)

당연히 다른 회사 장비들 끼리는 호환이 제대로 되지 않고 브릿지도 비싸다.

Home assistant 를 꾸미고 범용으로 사용할 수 있는 zigbee coordinator 를 사용하는 방법이 몇가지 있는데 지원하는 장치가 많은 방식으로 가장 유명한게 ZHA와 Zigbee2mqtt 가 있다.

이번에 꾸민 HA+ESP8266+CC2530+CC2591 의 대략적인 동작 형태를 먼저 설명해보자면 아래와 같다.

동작 형태 HA+ESP8266+CC2530+CC2591

  1. Coordinator (CC2530+CC2591)를 serial 로 ESP8266 에 연결
  2. ESP8266 은 tcp serial server 로 동작
  3. zigbee2mqtt 는 hass.io 애드온으로 동작, ESP8266 tcp 서버에서 데이터 받아서 mqtt 서버로 보내줌
  4. hass.io 에 설치한 mqtt broker(서버) 에서 수신
  5. Home asssitant 통합구성요소에서 mqtt 장치 확인 및 사용
    + zigbee 장치 coordinator 와 페어링. zigbee2mqtt 프론트 페이지에서 페어링 모드 설정 및 결과 확인 가능

보통 CC2531 을 USB에 꽃아서 사용하는데 위와 같이 진행 한 이유는 NAS는 신발장 안에 있어서 zigbee 장치를 사용하 는 위치가 너무 멀어서다. 이렇게 esp8266과 합체하면 wifi로 연결되니 멀리 던져놓아도 된다. 그리고 CC2530+CC2591 이 무선 신호 증폭이 되어서 사용 가능 범위가 훨씬 넓다. (대략 2~3배)


생각보다 해야 할 일들이 많다.

준비물

  1. CC2530+CC2591 : 알리 익스프레스에서 구입 (약 8$ + 배송비 2$)
  2. ESP8266 또는 ESP32 구입 : ESP32가 더 좋긴 한데 집에 굴러다니는 wemos d1 mini(esp8266) 사용. (약 2$ + 배송비 0~2$)
  3. 점퍼 케이블, 인두와 납 : ESP8266 핀이 납땜 안되있을 경우 납땜이 필요하다.
  4. CC 디버거 또는 라즈베리파이 또는 ESP8266 : CC2530 에 펌웨어를 넣기 위해 필요하다. 나는 라즈베리파이 사용

진행방법

  1. 라즈베리파이로 펌웨어를 넣어준다.
    아래와 같이 연결해줘야 한다. 핀맵 그림을 참조하자.
Raspberry Pi        CC2531
GND            GND
Pin 38            DD
Pin 36            DC
Pin 35            RST

  1. ESP8266 펌웨어를 TASMOTA ZBbridge 버전으로 넣어준다. (이 버전만 tcp server 사용 가능하다)
    아래 사이트에서 tasmota-zbbridge.bin.gz 를 받아서 라이팅 하면 된다.
    http://ota.tasmota.com/tasmota/release/
  2. 라이팅은 https://github.com/tasmota/tasmotizer 를 설치하고 esp8266(wemos d1 mini) 를 usb로 연결하고 바로 라이팅 하면 된다.
  3. wemos d1 mini가 재부팅 되면 wifi 로 TASMOTA 에 접속한 후 사용할 와이파이 SSID 와 비밀번호를 넣어준다.
    그리고 재부팅 되는데 config에서 module 설정, General(18)로 재빨리 바꾸고 저장해준다.
    1분쯤 지나면 wemos d1 mini가 초기화된다. zigbee 장치가 아니라서 그런 듯 싶다.
  4. wemos d1 mini 와 CC2531을 연결해준다
    여기서는 wemos d1 mini 의 D7, D8 을 TX RX 로 활용하였다.
    ESP8266 CC2530
    3v3 VCC
    GND GND
    TX P02
    RX P03
    GND P20

  1. TASMOTA 설정
    라이팅된 wemos d1 mini의 TASMOTA 로 접속해서 "Configuration", "configure Module" 에서 Rx, Tx pin을 맞게 지정해준다.
    그리고 Tasmota 메인 화면에서 Console 로 들어가서 아래와 같이 입력해놓자.
  2. Rule1 ON System#Boot do TCPStart 8888 endon Rule1 1
  3. 이제 Zigbee2mqtt 의 configuration.yaml 파일을 열어서 해당 wemos d1 mini 에 연결되도록 ip와 포트를 지정해준다.
  4. serial: port: 'tcp://192.168.2.13:20108'

 

이상하게도 Wemos d1 mini는 espeasy 에서 잘 안되더라....

기본 rx, tx pin을 사용해도 안되서 TASMOTA 로 진행했다.

 

이제 원하는 장소에서 usb 전원만 공급하면 원격 zigbee2mqtt 코디네이터로 사용하면 된다.

 

 

참조 링크 : https://www.zigbee2mqtt.io/information/connecting_cc2530.html

반응형
반응형

home asssitant 사용 중 센서나 전등 등의 상태 변화나(로그북) 그래프를 확인하고 싶을때가 많다.

처음에는 별 상관 없는데 등록된 디바이스들이 많을 수록 데이터베이스 용량이 커져서 검색이 오래걸린다.

 

내 경우에는 전등 20개, 스위치 10개, 디바이스 트래커 30개, 센서류 20개 뭐 이정도 등록된거 같은데 home-asistant_v2.db 파일 용량이 2gb 가 넘었다. 물론 7일마다 삭제 되도록 옵션을 넣었지만...

 

해결 방법으로는 recoder 에서 include, exclude를 적절히 사용해서 필요 없는 내용을 기록하지 않거나(근데 지정하는것도 귀찮다)

www.home-assistant.io/integrations/recorder/

 

db 를 바꿔서 검색 속도를 빠르게 하는 방법이 있다.

 

 

당연히 귀찮으니까 db를 사용해보자.

 

물론 편리함을 위해 hass.io 쓰고 있다는 가정하에..

 

supervisor 에서 MariaDB add-ons 을 깔아준다.

 

config 는 아주 간단하게만 적용해주자.

자동시작 등록해놓고, 시작해주자.

databases:
  - homeassistant
logins:
  - username: yourID
    password: yourPassword
rights:
  - username: homeassistant
    database: homeassistant

 

이제 home assistant 에서 config 파일을 수정해보자.

 

HA 설정 폴더에서 configuration.yaml 파일을 열고 아래처럼만 넣어주면 된다.

물론 다른 recoder 옵션들을 사용해도 된다.

recorder:
  db_url: mysql://yourID:yourPassword@core-mariadb/homeassistant?charset=utf8mb4

 

이제 home assistant 를 재시작 하면 mariaDB가 적용되서 모든 기록들 검색이 매우매우 빨라진다.

 

 

 

꼭 적용해둘것!

반응형
반응형

다원 WIFI 스마트 플러그를 사용자 컴포넌트 만들어 주신분이 있어서 Home Assistant 에서 직접 사용이 가능 했었는데

나름(?) 보안 패치를 했는지 사용이 막힌지 꽤 오래 되었다.

그냥 저냥 smart things 와 home assistant 를 연결해서 썼는데 이건 전력량 모니터링, 전력량에 따른 동작 등등 다양한 활용 방법이 막혀있다.

 

찾아보니 벌써 해결책이 나와 있다.

저작권을 위해 링크만 걸겠다.

 

방법은 초기화 후 서버로 연결을 안시키고 집에서 만들어놓은 mqtt 서버에 연결시키는 방법이다.

 

cafe.naver.com/koreassistant/1977

 

다원 WIFI 스마트 플러그 (B530-WF/B540-WF) 로컬 등록 (이것도 너프해보시지)

대한민국 모임의 시작, 네이버 카페

cafe.naver.com

 

부족한 내용을 조금 설명하자면

모델명 400 은 일단 안되었다. 뭐가 다른지 모르겠는데 안되니까 슬프게도..

 

그리고 hass.io 에서 add-on 으로 mqtt borker 를 사용중이면 간단하게 config를 사용중일텐데,,

플러그 한개당 한개씩 아이디가 필요해서 사용자 추가 어떻게 해야 하나 고민된다.

그런데 그냥 사용자/비번을 여러줄 넣어주면 된다.  (아래 스샷처럼)

 

 

이제 요녀석을 이용해서 여러가지 활용이 가능하다. 전력 모니터링, 과부하시 종료, 한개 켜면 다른 스위치 자동으로 켜고 끄기 등등..

 

반응형
반응형

 하루에 한번이상 꼭 가게 되는 화장실이다. 들어갈 때 향기로웠으면 좋겠지만 항상 물을 사용하는 장소라서 습기로 가득차 있어서 향기는 커녕 곰팡이 피지 않게 관리하는 것도 쉽지 않다.

 환풍기를 하루종일 켜놓으면 조금 나을 텐데 그렇다고 하루종일 켜놓는건 전기세도 아깝고 시끄럽고 잘못하면 환풍기에서 전기 화재가 날 수 도 있는거 아닌가? 불안한 마음이 한가득이다.

 

 그래서 IoT로 꾸며볼까 했는데, 아서라 화장실은 IoT 에서 나름 중간보스 이상급이다. 사람이 들고 나고 카운팅 하는것도 쉽지 않고 화장실에 있는지 없는지 확인하기도 쉽지 않다.

 

타임렉

 가장 쉬운 방법으로 환풍기 스위치를 끄면 약 5분 또는 10분정도 더 돌다가 꺼지도록 해주는 손쉬운 제품이 있다고 해서 설치해봤다.

 이름은 '위너스 타임랙' 이며 스위치를 뜯어서 저 두개만 끼워 넣으면 된다.

 

https://coupa.ng/bLnXdL

 

위너스 조명 환풍기 겸용 시간지연 모듈 타임랙

COUPANG

www.coupang.com

 

 행복하게 주문하고 배송이 온 후 설치했다.

 스위치 박스를 힘줘서 대충 뜯어내고 (일자 드라이버나 비슷한거로 아래쪽 홈에 걸쳐서 뜯어내면 됨) 나사를 풀고 설치완료.

작업중이라 공통선이 빠져있다. 암튼 설명서 보고 연결하면 됨
설명서 이미지 나는 오른쪽 타입

 

 어 그런데 스위치가 토글 스위치다. 환풍기를 내가 정말로 끈건지 아니면 타임랙이 동작하는지 구분할 수 없다...

 너무 슬픈 현실에 다시 스위치를 샀다. 나는 소비형 인간인가.

 

르그랑 엑셀

클래식한 엑셀 스위치

 사진은 정말 못찍었는데 블링블링하면서 레트로한 엑셀 스위치다. 진짜 호주에서 수입해오는 제품. 엄청 튼튼하고 한국형이 아니라 귀찮다. 심지어 고정 나사가 저 제품에 쓰기에는 짧아서 나사도 따로 샀다. 제품 설명서에 나온 연결 방법도 잘못되서 힘들었다. 1번과 2번에 각자 선을 꽃아야 한다. 스위치에 따라 1,2 가 연결되었다 끊어졌다 한다. G 인가 하는 단자는 아에 안쓰는 단자...

 

https://coupa.ng/bLnYms

 

르그랑 엑셀 / 3구스위치 / 블랙

COUPANG

www.coupang.com

 

 어쨋거나 이번에도 눈꼽만큼 집안 살림살이 업그레이드 완료!

 

 

이 게시물은 “파트너스 활동을 통해 일정액의 수수료를 제공받을 수 있음"

반응형
반응형

 새로 이사온 아파트는 요새 트렌드인지 베란다는 확장되어 있고 조명과 벽지는 밝은 하얀색-베이지색으로 가득 차 있습니다. 어두침침한 예전 집들과는 다른 분위기입니다. 다만 저는 저녁때 집에서 쉴때는 은은한 전구색을 선호해서 많이 아쉬웠습니다. 대신 스탠드 조명이나 벽에 쏘는 스팟 조명같은걸 구해서 전구색과 주광색으로 사용하고 있었지요.

 

 예전 집에서 사용했던 조명들은 이케아 조명과 필립스 휴 스마트 전구 조합으로 만들어서 썼습니다.

qsurf.tistory.com/34

 

IKEA 스탠드 조명을 스마트 조명으로 - hue bridge

 제작년에 거실을 꾸미고 있을 때 이케아에서 스탠드 조명을 하나 샀습니다.  집에 오는 사람마다 이 조명 예쁘다고 한마디씩 해줬었는데요. 기존에는 스마트 플러그를 이용해서 on/off 만 자동�

qsurf.tistory.com

 

 그래도 조명이 계속 아쉬웠는데 그중 가장 아까운게 거실 창문쪽에 있는 천장 다운라이트 조명이었습니다. 기본으로 들어있는 조명은 아주 밝은 흰색이어서 그걸 켜놓으면 거실에서 도무지 아무것도 할 수 없었거든요. 어찌나 쨍하고 밝은지 눈을 자꾸 찔러대는 요녀석 때문에 힘들었습니다.

 

눈을 찌르는 천장 다운라이트

 도저히 이건 아니겠다 싶어서 천장 다운라이트를 빼내고 지금 타공되어있는 사이즈에 맞는 조명을 검색했습니다. 조건은 더 깊은 위치에서 조명을 쏘아주는것과 밝기와 색온도 조절이 가능할 것 입니다. 사실 밝기 조절만 하는 경우에 디머  를 설치하면 되는데 문제는 저 세개의 조명이 한줄로 연결되어 있는데 디머 한개로 3개 조명을 제어할 수 있도록 설치하려고 했는데 배선이 애매합니다. 스위치쪽에 설치하기엔 공간이 부족하고 천장에서 뜯어서 보면 각자 선이 하나씩만 나와있어서 공통 배선을 찾기가 힘들더라구요.

 거기다 색온도 조절까지 하려면 스마트 조명이 아니면 선택지가 없습니다. 현재 사용하고 있는 필립스 휴 조명중 적용할 수 있는 항목이 있는지 확인해 봤습니다. 아펠리온이라는 조명이 있네요? 가격은 오만원정도. 많이 비싸지만 필립스 조명이 연색성이나 편의성이 나쁘지 않아서 고민해봤습니다.

휴 아펠리온, 5만원

 문제는 현재 타공 지름이 9cm 인데 이녀석은 12.5cm 입니다. 타공을 다시 하려면 장비가 있어야 하는데 참 난감합니다. 거기다 요녀석도 천장 안쪽으로 깊숙히 들어가지는 않아서요.

 

 다시 검색을 시작합니다. 없는거 빼고 다 있는 알리 익스프레스에 들어가서 한참 검색하니 하나 보이네요.

 일단 사용 가능한 전구 타입은 Gu10 입니다. 필립스 휴와 이케아 트로드프리에서 나오는 스마트 조명은 요너석이거든요.

 

 

 타공 사이즈 완벽하고 원하는대로 깊이 들어가서 눈부심 방지도 되고 딱 좋습니다.

 

 이제 전구를 구입합니다. 일단 필립스 휴 Gu10 을 찾아보는데 한국에서는 정식 발매가 되지 않았습니다. 한국과 전압이 동일한 아마존 독일에서 주문을 해봅니다. 2개에 33.6 유로에 배송비 하면 한개에 3만 5천원 정도 하네요.

 

뭐지? 왜 Refund?

 무려 2주를 기다렸습니다. 배송이 되서 프랑스로 날아가더니.. 갑자기 배송 문제가 생겼다고 환불해버립니다. 기다리다 환장합니다. 물품 파손이 된건지 도무지 알 수 없습니다. 너무 지루한 기다림이 싫어서 이번엔 이케아 트로드프리 Gu10 조명을 구매했습니다. 가격은 조금 더 싸네요. 한개에 2만원. 다만 요너석은 블루투스 연결은 안되고 필립스 휴 브릿지와 제한적으로 연결될겁니다.

 

 그리고 다시 시작된 기다림

 

 

 다 왔습니다! 

 

 설치완료 후 모습입니다. 원하는대로 조도 변화가 잘 되네요. 눈부심이 훨씬 줄어들고 전구색으로 분위기도 좋아져서 행복합니다.

 

 

 성공적인 설치!

 하지만 실제로는 필립스 휴 브릿지와 연결이 잘 안되서 고생했습니다. 터치링크로 해야만 조명 추가가 되더라구요. 거기다 휴에 있는 씬이 비 정상적으로 적용됩니다. 색온도와 밝기가 동시에 바뀌어야 하는데 색온도만 바뀌고 밝기는 따로 신호를 줘야만 바뀌더라구요.

 휴 어플을 버리고 Home assistant 카드 뷰를 예쁘게 만든후에 요녀석을 이용해야 할거 같네요. 아쉽습니다.

반응형

+ Recent posts