반응형

 

PiKVM 이란?

PiKVM은 라즈베리 파이(Raspberry Pi)를 이용한 KVM-over-IP 솔루션

  • KVM (Keyboard, Video, Mouse) 기능을 네트워크를 통해 원격으로 제어할 수 있도록 해줍니다.
  • 서버나 PC의 BIOS 설정, OS 설치, 문제 해결 등을 물리적으로 접근하지 않고도 원격에서 수행할 수 있습니다.
  • 라즈베리 파이 + HDMI 캡처 + USB 연결로 간단하게 구축 가능하며, 웹 인터페이스를 제공하여 사용이 편리합니다.

PiKVM 을 쓰려면 일단 라즈베리 파이와 영상 캡쳐를 위한 몇몇 추가 장비가 필요합니다.
성능도 꽤 좋습니다.
그런데 집에 PC가 많으니까 한 PC 에서 다른 PC를 제어하면 되지 않을까? 하면서 찾아봤습니다.
github 에 보면 옛적부터 PiKVM 을 포팅해서 x86kvm 이나 기타 등등 여러가지 있지만 원하는 형태는 잘 안보였습니다만,
최근에 OneKVM 이라고 하나 찾아서 테스트 해보니 괜찮았습니다.

 

OneKVM

일단 중국 친구가 PiKVM 포크해서 만든 것 같고 도커로 실행이 잘 됩니다.
VM 이미지도 제공하는데 테스트 해보니 그것도 잘 됬습니다.
영상 캡쳐는 USB-HDMI 로 사용하고, 키보드 마우스 입력은 USB-TTL 장치와 ch9329 를 이용해서 합니다.

 

준비물

  • 도커 실행 가능 PC
  • KVM 적용할 PC 또는 장치 (클라이언트)
  • USB-TTL (USB UART, ch340 등 아무거나), 1,000원~4,000원
  • CH9329 (UART to HID), 3,000원
  • USB HDMI 캡쳐 카드 (MS2130 등), 5,000 ~ 14,000원
  • 총합 9,000~20,000원
    (가격은 알리익스프레스 보통 판매가이며 할인하면 더 싸게 구할 수 있습니다 )

 

One-KVM 설치

도커로 설치해서 사용하면 됩니다.
저는 docker-compose.yaml 만들어서 사용했습니다.
proxmox 환경에서 lxc 데비안 설치 하였으며 /dev/video0 와 /dev/ttyUSB0 를 passthrough 해서 사용했습니다.

services:
  onekvm:
    container_name: onekvm
    image: silentwind0/kvmd
    devices:
      - /dev/video0:/dev/video0
      - /dev/ttyUSB0:/dev/ttyUSB0
    network_mode: host  # 호스트 네트워크 사용, WebRTC용?
#    ports:
#      - "8080:8080"
#      - "4430:4430"
#      - "5900:5900"
#      - "623:623"
    volumes:
      - ./kvmd_config:/etc/kvmd
    environment:
      - CH9329SPEED=115200
      - USERNAME=KVM_WEB_USER
      - PASSWORD=KVM_WEB_PASSWORD
    restart: unless-stopped

접속 주소는 https://kvm-ip:4430 이며 처음 접속 시 영어로 언어 선택하면 됩니다.

 

USB-TTL 과 CH9329 연결

USB-TTL 과 CH9329 를 연결해주면 됩니다.
TTL CH9329
rx - tx
tx - rx
gnd - gnd
세개 선만 연결하고 vcc 는 연결하지 않습니다.

제가 가진 CH9329 와 CH340 은 생각없이 연결하고 써도 문제 없더군요.
tx 전압 측정해보니 5v 로직전압인듯 합니다.

CH9329 를 원격조종할 PC 에 연결하고, CH340 을 도커 설치할 PC 에 연결,
하드웨어가 인식 잘 되었는지 확인 후
잘 사용하면 됩니다.

 

CH9329 정지 문제 해결

클라이언트 PC 를 재부팅 하거나 하면 CH9329 가 자꾸 먹통이 됩니다.
CH9329 를 reset 시키면 살아납니다. (물리적으로 USB 제거 후 다시 연결, 또는 reset 신호)

x86 pikvm 에서 ch_reset.py 를 실행하면 된다고 해서 vm에 설치하고 파이썬 파일을 가져와서 조금 수정했습니다.
장치에 맞게 device path, serial 설정을 주면 됩니다.

 

도커 내부에 들어가서
python3 ./ch_reset.py
실행해주면 CH9329 가 리셋되면서 다시 사용 가능합니다.
(host 에서 해도 되나 파이썬 라이브러리 설치가 귀찮습니다..)

 

해당 파일은 kvmd_config 에 고이 넣어둡니다. 아래에서 쓰기 쉽게 만들거거든요.

kvmd_config/ch_reset.py

#!/usr/bin/python3
import serial
import time

device_path = "/dev/ttyUSB0"
chip = serial.Serial(device_path, 115200, timeout=1)

command = [87, 171, 0, 15, 0]
sum = sum(command) % 256
command.append(sum)

print("Resetting CH9329")

chip.write(serial.to_bytes(command))

time.sleep(0.2)

data = list(chip.read(5))
print("Initial data:", data)

if data[4] :
        more_data = list(chip.read(data[4]))
        data.extend(more_data)
print("Output: ", data)

chip.close()

실행하기 쉽게 쉘스크립트로 하나 만들어줍니다.

kvmd_config/ch_reset.sh

#!/bin/bash
/usr/local/bin/python3 /etc/kvmd/ch_reset.py

저장 후 실행 가능하게 권한 설정 해주고요
chmod +x ch_reset.sh

 

CH9329 추가 설정

CH9329 통신 속도가 기본값은 9600 인데 115200 으로 바꾸면 조금 더 안정적이라고 합니다.
도커 환경변수에 넣어서 반영 시켰지만 장치 기본값도 바꿔놓으면 좀 더 안정정입니다.

 

아래 링크에서 프로그램을 받아서 CH9329만 USB 에 꽃은 후 실행합니다. (윈도우 PC)
https://www.wch.cn/downloads/CH9329EVT_ZIP.html

메뉴가 다 깨지는데 대충 추측해서 눌러봅니다.
왼쪽 위에서 USB 로 연결되었다고 선택 한 후 연결 시키면 아래 정보가 뜹니다.
오른쪽 버튼 눌러서 불러오고 통신 속도를 115200 으로 바꾸고 다시 저장하면 됩니다.

KVM 에서 mouse jiggler 를 켜놓으면 계속 통신되니 안정적이라는 내용도 있네요.

KVM 메뉴 커스텀

KVM 접속 시 상단에 불필요한 중국어로 된 메뉴가 있어서 커스텀을 조금 했습니다.
위에서 만든 CH HID Reset 을 추가해주고 개인적으로 쓰는 ATX 전원제어 장치(Rest API 동작) 명령어를 넣어줬습니다.

아래 파일을 편집하면 됩니다. 필요한 부분만 수정했습니다. 안쓰는 내용도 좀 있어요.
수정 후 도커 재시작 하고 접속하면 이제 ch_reset 를 메뉴에서 바로 사용 할 수 있습니다.

 

kvmd_config/override.yaml

kvmd:
    auth:
        enabled: true

    atx:
        type: disabled

    hid:
        type: ch9329
        device: /dev/ttyUSB0
        speed: 115200
        read_timeout: 0.3

        jiggler:
            active: true
            enabled: true

        mouse_alt:
            device: /dev/kvmd-hid-mouse-alt

    msd:
        #type: otg
        remount_cmd: /bin/true
        msd_path: /var/lib/kvmd/msd
        normalfiles_path: NormalFiles
        normalfiles_size: 256

    ocr:
        langs:
            - eng
            - chi_sim

    streamer:
        resolution:
            default: 1920x1080

        forever: true

        desired_fps:
            default: 60
            max: 60

        h264_bitrate:
            default: 8000

        cmd:
            - "/usr/bin/ustreamer"
            - "--device=/dev/video0"
            - "--persistent"
            - "--format=mjpeg"
            - "--encoder=LIBX264-VIDEO"
            - "--resolution={resolution}"
            - "--desired-fps={desired_fps}"
            - "--drop-same-frames=30"
            - "--last-as-blank=0"
            - "--unix={unix}"
            - "--unix-rm"
            - "--unix-mode=0666"
            - "--exit-on-parent-death"
            - "--process-name-prefix={process_name_prefix}"
            - "--notify-parent"
            - "--no-log-colors"
            - "--h264-sink=kvmd::ustreamer::h264"
            - "--h264-sink-mode=0660"
            - "--jpeg-sink=kvmd::ustreamer::jpeg"
            - "--jpeg-sink-mode=0660"
            - "--h264-bitrate={h264_bitrate}"
            - "--h264-gop={h264_gop}"
            - "--h264-preset=ultrafast"
            - "--slowdown"
    gpio:
        drivers:
            ch_reset:
                type: cmd
                cmd: [/etc/kvmd/ch_reset.sh, short]
            wol_server1:
                type: wol
                mac: 2c:56:dc:db:7c:1e
            short_press:
                type: cmd
                cmd: [/etc/kvmd/power_control.sh, short]
            long_press:
                type: cmd
                cmd: [/etc/kvmd/power_control.sh, long]
            reset_press:
                type: cmd
                cmd: [/etc/kvmd/atx.sh, reset]
            input1:
                type: cmd
                cmd: [/etc/kvmd/kvm_input.sh, 1]    
            input2:
                type: cmd
                cmd: [/etc/kvmd/kvm_input.sh, 2]
        scheme:
            ch_reset:
                driver: ch_reset
                pin: 0
                mode: output
                switch: false
            wol_server1:
                driver: wol_server1
                pin: 0
                mode: output
                switch: false
            short_button:
                driver: short_press
                pin: 0
                mode: output
                switch: false
            long_button:
                driver: long_press
                pin: 0
                mode: output
                switch: false
            reset_button:
                driver: reset_press
                pin: 0
                mode: output
                switch: false
            input1-button:
                driver: input1
                pin: 0
                mode: output
                switch: false
            input2-button:
                driver: input2
                pin: 0
                mode: output
                switch: false
        view:
            header:
                title: 고급설정
            table:
                - ["#CH HID Reset"]
                - ["ch9329 reset :", ch_reset|실행]
                - ["----------------"]
                - [" "]
                - [" "]
                - [" "]
                - [" "]
                - [" "]
                - ["#ATX 전원제어"]
                - ["#전원버튼(On/Off):", short_button|실행]
                - [" "]
                - [" "]
                - [" "]
                - ["#강제종료(Power 5s):", long_button|실행]


vnc:
    keymap: /usr/share/kvmd/keymaps/en-us
    mouse_output: usb

    auth:
        vncauth:
            enabled: true

    memsink:
        jpeg:
            sink: "kvmd::ustreamer::jpeg"
        h264:
            sink: "kvmd::ustreamer::h264"

media:
    memsink:
        h264:
            sink: 'kvmd::ustreamer::h264'

        jpeg:
            sink: 'kvmd::ustreamer::jpeg'
janus:
    stun:
        host: stun.cloudflare.com
        port: 3478

otgnet:
    commands:
        post_start_cmd:
            - "/bin/true"
        pre_stop_cmd:
            - "/bin/true"

nginx:
    http:
        port: 8080
    https:
        port: 4430

 

 

여기까지 커스텀 완료했습니다.

도커는 영상모드는 mjpeg 만 사용되는데 내부망에서는 빠르고 좋습니다.

HW 패스스루/드라이버/캡쳐USB 문제인 것 같아서 몇번 해보고 포기했습니다.

VM 으로 깔면 WebRTC도 되긴 하는데 별로 좋은지 모르겠습니다.

 

저는 추가로 power_control.sh 에서 전원 버튼 누를때마다 ch_reset.py 실행하는 로직까지 넣어놨습니다.
바이오스 들어가기 한결 편하더라구요.

반응형
반응형

 

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

아래쪽에 위치한 전자잉크 표시기는 마트에서 가격표로 사용하는 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

반응형
반응형

 

Proxmox 에 윈도우 VM 을 올려놓았는데

사용하지 않을때는 종료 시켜 놓는다.

그런데 짝꿍은 Proxmox 접근 권한이 없으니까...HA 에 전원 버튼을 만들어줬다.

 

 

참조 글은 아래 링크

https://www.jamescoyle.net/tag/api

 

API | JamesCoyle.net Limited

Proxmox has 2 API access points that can be used to control your Proxmox server and virtual guests. One of the API access points is using the command line, which you’re likely already familiar with. The other is the HTTP web API which is exposed as part

www.jamescoyle.net

 

 

대충 아래 내용들을 채워 넣어야 한다

  • TICKET is the authentication ticket that was produced in the Parse Proxmox Web API authentication ticket and the CSRFPreventionToken in Bash post. Ideally you would programatically call the authentication routine and then pass the values straight into the below API calls.
  • CSRF is produced in the same way as TICKET. It’s actually only required when writing data to the API but there is no harm in always including it.
  • HOST is the host or IP address of the Proxmox server.
  • NODE is the node name of the Proxmox server that the LXC Container resides on.
  • TARGET_VM  is the VMID of the LXC Container.

 

 

 

Proxmox 웹에 접속한 후에

Datacenter 선택 후

Permission -> User -> add 로 사용자를 하나 추가하고 아이디/암호 기억하자

 

 

Permission -> Roles 에서 Create 로 API 용 role 을 하나 추가해주자

 

proxmox host shell 에 접속해서 아래와 같은 명령어로 role 을 부여해주자.

pveum acl modify / -user API@pve -role APIrole

 

이제 아래와 같은 스크립트를 만들면 된다. 중간 5줄만 잘 채우자.

VM 시작 스크립트

VMwin10Start.sh

#!/bin/bash

decodeDataFromJson(){
    echo `echo $1 \
            | sed 's/{\"data\"\:{//g' \
            | sed 's/\\\\\//\//g' \
            | sed 's/[{}]//g' \
            | awk -v k="text" '{n=split($0,a,","); for (i=1; i<=n; i++) print a[i]}' \
            | sed 's/\"\:\"/\|/g' \
            | sed 's/[\,]/ /g' \
            | sed 's/\"// g' \
            | grep -w $2 \
            | awk -F "|" '{print $2}'`
}

PROX_USERNAME=user@pve
PROX_PASSWORD=password
HOST=proxmox_host_ip
NODE=proxmox_node
TARGET_VMID=VM_number

DATA=`curl -s -k -d "username=$PROX_USERNAME&password=$PROX_PASSWORD" $HOST/api2/json/access/ticket`
TICKET=$(decodeDataFromJson $DATA 'ticket')
CSRF=$(decodeDataFromJson $DATA 'CSRFPreventionToken')


START_TASK_DATA=`curl -s -k -b "PVEAuthCookie=$TICKET" -H "CSRFPreventionToken: $CSRF" -X POST $HOST/api2/json/nodes/$NODE/qemu/$TARGET_VMID/status/start`

 

VM 종료 스크립트

VMwin10Shutdown.sh

#!/bin/bash

decodeDataFromJson(){
    echo `echo $1 \
            | sed 's/{\"data\"\:{//g' \
            | sed 's/\\\\\//\//g' \
            | sed 's/[{}]//g' \
            | awk -v k="text" '{n=split($0,a,","); for (i=1; i<=n; i++) print a[i]}' \
            | sed 's/\"\:\"/\|/g' \
            | sed 's/[\,]/ /g' \
            | sed 's/\"// g' \
            | grep -w $2 \
            | awk -F "|" '{print $2}'`
}

PROX_USERNAME=user@pve
PROX_PASSWORD=password
HOST=proxmox_host_ip
NODE=proxmox_node
TARGET_VMID=VM_number

DATA=`curl -s -k -d "username=$PROX_USERNAME&password=$PROX_PASSWORD" $HOST/api2/json/access/ticket`
TICKET=$(decodeDataFromJson $DATA 'ticket')
CSRF=$(decodeDataFromJson $DATA 'CSRFPreventionToken')


START_TASK_DATA=`curl -s -k -b "PVEAuthCookie=$TICKET" -H "CSRFPreventionToken: $CSRF" -X POST $HOST/api2/json/nodes/$NODE/qemu/$TARGET_VMID/status/shutdown`

 

 

해당 스크립트를 HA config 폴더에 넣고

configuration.yaml 파일에 아래와 같은 shell 커맨드를 넣어준다.

shell_command:
  win10start: sh VMwin10Start.sh
  win10stop: sh VMwin10Shutdown.sh

 

이제 요렇게 추가 완료

전원 켜짐은 device_tracker 로 알아서 만들기

반응형
반응형

아래 링크대로 진행하면 잘 된다.

 

https://www.kreaweb.be/diy-home-server-2021-software-proxmox-ups/

 

DIY HOME SERVER 2021 - Software - PROXMOX - NUT UPS Monitoring | KREAWEB

When building a home lab server, you’re almost always going to use some kind of Uninterruptible Power Supply (UPS). Let's install some tools to monitor the system.

www.kreaweb.be

 

 

usb 가 porxmox 장치에 연결되어 있는 환경에서

 

대충 요약하면

 

USB 포트에 맞게 연결되어 있는지 확인하고

lsusb
lsusb -v -s [bus]:[device]

 

NUT 를 설치해주고

apt install nut -y

NUT 디바이스를 확인한다. 

nut-scanner -U

 

 

기본 설정 파일 백업은 생략한다.

 

파일 수정

nano /etc/nut/nut.conf

아래 내용만 입력 

MODE=netserver

 

파일 수정

nano /etc/nut/ups.conf

 

pollinterval = 15
maxretry = 3

offdelay = 120
ondelay = 240

[ups] 
# APC Back-UPS BX1400U-FR
driver = usbhid-ups
port = auto
desc = "APC Back-UPS BX1400U-FR"
vendorid = 051D
productid = 0002
serial = secret

 

ups 드라이버 시작

upsdrvctl start

 

파일 수정

nano /etc/nut/upsd.conf
LISTEN 0.0.0.0 3493
LISTEN :: 3493

 

nano /etc/nut/upsd.users
[upsadmin]
# Administrative user
password = ********
# Allow changing values of certain variables in the UPS.
actions = SET
# Allow setting the "Forced Shutdown" flag in the UPS.
actions = FSD
# Allow all instant commands
instcmds = ALL
upsmon master

[monuser]
# Normal user
password = ********
upsmon slave

 시놀로지에서 쓰려면 monuser 암호를 secret 으로 하면 된다.

 

nano /etc/nut/upsmon.conf
RUN_AS_USER root
MONITOR ups@localhost 1 upsadmin ******* master

MINSUPPLIES 1
SHUTDOWNCMD "/sbin/shutdown -h"
NOTIFYCMD /usr/sbin/upssched
POLLFREQ 4
POLLFREQALERT 2
HOSTSYNC 15
DEADTIME 24
MAXAGE 24
POWERDOWNFLAG /etc/killpower

NOTIFYMSG ONLINE "UPS %s on line power"
NOTIFYMSG ONBATT "UPS %s on battery"
NOTIFYMSG LOWBATT "UPS %s battary is low"
NOTIFYMSG FSD "UPS %s: forced shutdown in progress"
NOTIFYMSG COMMOK "Communications with UPS %s established"
NOTIFYMSG COMMBAD "Communications with UPS %s lost"
NOTIFYMSG SHUTDOWN "Auto logout and shutdown proceeding"
NOTIFYMSG REPLBATT "UPS %s battery needs to be replaced"
NOTIFYMSG NOCOMM "UPS %s is unavailable"
NOTIFYMSG NOPARENT "upsmon parent process died - shutdown impossible"

NOTIFYFLAG ONLINE   SYSLOG+WALL+EXEC
NOTIFYFLAG ONBATT   SYSLOG+WALL+EXEC
NOTIFYFLAG LOWBATT  SYSLOG+WALL+EXEC
NOTIFYFLAG FSD      SYSLOG+WALL+EXEC
NOTIFYFLAG COMMOK   SYSLOG+WALL+EXEC
NOTIFYFLAG COMMBAD  SYSLOG+WALL+EXEC
NOTIFYFLAG SHUTDOWN SYSLOG+WALL+EXEC
NOTIFYFLAG REPLBATT SYSLOG+WALL
NOTIFYFLAG NOCOMM   SYSLOG+WALL+EXEC
NOTIFYFLAG NOPARENT SYSLOG+WALL

RBWARNTIME 43200
NOCOMMWARNTIME 600

FINALDELAY 5

 

 

nano /etc/nut/upssched.conf
CMDSCRIPT /etc/nut/upssched-cmd
PIPEFN /etc/nut/upssched.pipe
LOCKFN /etc/nut/upssched.lock

AT ONBATT * START-TIMER onbatt 30
AT ONLINE * CANCEL-TIMER onbatt online
AT ONBATT * START-TIMER earlyshutdown 30
AT LOWBATT * EXECUTE onbatt
AT COMMBAD * START-TIMER commbad 30
AT COMMOK * CANCEL-TIMER commbad commok
AT NOCOMM * EXECUTE commbad
AT SHUTDOWN * EXECUTE powerdown
AT SHUTDOWN * EXECUTE powerdown

 

nano /etc/nut/upssched-cmd
#!/bin/sh
 case $1 in
       onbatt)
          logger -t upssched-cmd "UPS running on battery"
          ;;
       earlyshutdown)
          logger -t upssched-cmd "UPS on battery too long, early shutdown"
          /usr/sbin/upsmon -c fsd
          ;;
       shutdowncritical)
          logger -t upssched-cmd "UPS on battery critical, forced shutdown"
          /usr/sbin/upsmon -c fsd
          ;;
       upsgone)
          logger -t upssched-cmd "UPS has been gone too long, can't reach"
          ;;
       *)
          logger -t upssched-cmd "Unrecognized command: $1"
          ;;
 esac
chmod +x /etc/nut/upssched-cmd

 

아래 명령어로 이제 시작해주자.

service nut-server restart
service nut-client restart
systemctl restart nut-monitor
upsdrvctl stop
upsdrvctl start

 

ups 연결 확인 가능

upsc ups@localhost

 

 

이제 synology 에서도 ip 확인 가능...

반응형
반응형

 

서브로 사용하는 시놀로지(헤놀)에 duckdns의 dns challenge 로 인증서를 설정해봤다.

 

최근 acme 기본 서버가 zerossl 인데 뭔가 설정이 잘 안되서 삽질을 한참 했다.

 

 

테스트 환경은

synology 920+ 에 duckdns 얹어서 letsencrypt 로 인증서 받기

 

 

참조 문서 목록

https://www.clien.net/service/board/cm_nas/17436459

 

ACME를 이용한 와일드카드 인증서 시놀로지 발급 관련 자료 모음 : 클리앙

이번에 나스를 옮기면서 삽질을 여러번 했습니다. 1. 기존 나스에 설정해두었던 ACME설치방법을 완전히 까먹었었다는 것이 사건의 발단이었고 2. ACME 방식이 약간 변경이 있었다는 것 3. DSM7에서는

www.clien.net

 

https://github.com/acmesh-official/acme.sh/tree/master/dnsapi

 

GitHub - acmesh-official/acme.sh: A pure Unix shell script implementing ACME client protocol

A pure Unix shell script implementing ACME client protocol - GitHub - acmesh-official/acme.sh: A pure Unix shell script implementing ACME client protocol

github.com

 

https://github.com/acmesh-official/acme.sh/wiki/Synology-NAS-Guide

 

Synology NAS Guide

A pure Unix shell script implementing ACME client protocol - acmesh-official/acme.sh

github.com

 

https://siane.tistory.com/267

 

[Synology] 시놀로지에서 클라우드플레어 DDNS 설정하고 와일드카드 인증서 받기 #2

2021.10.01 - [Synology] 시놀로지에서 클라우드플레어 DDNS 설정하고 와일드카드 인증서 받기 #1 이 게시글의 연장입니다. 사용 환경은 DS1821+, DSM 7.0.1 입니다. 6.2 호환성에 대해서는 확인되지 않았습니

siane.tistory.com

 

 

  1. DSM 인증서 발급을 위한 사용자를 추가하고 시작하자. administrator 그룹에 포함 필요.
    (아니면 관리자 권한 있는 기존 사용자 사용해도 된다)
  2. DSM 에서 ssh 포트를 사용 가능하도록 하고 putty나 기타 등등 이용하여 ssh 접속해준다.
  3. 이제 ssh 에서 아래 명령어를 순서대로 입력한다. 중간에 sudo 명령어로 인해 암호를 다시 물어봄.
wget -O /tmp/acme.sh.zip https://github.com/acmesh-official/acme.sh/archive/master.zip


#acme 설치
sudo 7z x -o/usr/local/share /tmp/acme.sh.zip
sudo mv /usr/local/share/acme.sh-master/ /usr/local/share/acme.sh
sudo chown -R certadmin /usr/local/share/acme.sh/
cd /usr/local/share/acme.sh


#acme 업데이트
./acme.sh --force --upgrade --nocron --home /usr/local/share/acme.sh

# 이메일 계정 추가
./acme.sh --install --nocron --home /usr/local/share/acme.sh --accountemail "email@gmailcom"

#acme 기본 서버 letsencrypt 로 변경 
./acme.sh --set-default-ca --server letsencrypt

# 개인 도메인, 토큰 입력
export CERT_DNS='dns_duckdns'
export DuckDNS_Token='xxx-xxx'
export $CERT_Domain='your_domain'

#인증서 받아오기
./acme.sh --server letsencrypt --issue --home . -d '$CERT_Domain' --dns '$CERT_DNS'

#시놀로지 user id 와 암호를 입력해준다.
#만약 DSM 포트를 바꾸었으면 바꾼 포트도 넣어준다.
# certificate 항목 내용과 동일한 인증서를 갱신해준다.
export SYNO_Username='username'
export SYNO_Password='password'
export SYNO_Certificate='설명'
export SYNO_Port='DSM_http_port'
export SYNO_Create=1

# DSM 에 기본 인증서로 등록해준다. 
./acme.sh --deploy --home . -d ksdrive.duckdns.org --deploy-hook synology_dsm

 

 

인증서 갱신은 아래와 같은 명령어를 쓰면 된다.

제어판 -> 작업 스케쥴러 에서 생성해서 추가하면된다. 대충 매주 한번씩 체크하도록 하면 될듯 하다.

/usr/local/share/acme.sh/acme.sh --cron --home /usr/local/share/acme.sh/
반응형
반응형

 

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

그렇지만 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

반응형
반응형

 

Proxmox 7.0 환경에서 컨테이너로 ubuntu 20.04 를 설치하고 Plex를 사용중에

HW 트랜스코딩이 잘 안되서 찾아보니 HDR 영상을 SDR 로 톤 매핑하는 과정에 문제가 있었다.

 

아래 글을 참조해서 필요한 파일을 설치하니 문제 없다.

https://support.plex.tv/articles/hdr-to-sdr-tone-mapping/

 

HDR to SDR Tone Mapping | Plex Support

Tip!: HDR to SDR tone mapping support is currently available as a Plex Pass preview and requires an active Plex...

support.plex.tv

 

 

반응형
반응형

 

Proxmox 7.0 CT 컨테이너에서 Intel CPU 내장 GPU 가속을 이용하여 plex hw 트랜스코딩 사용을 해보았다.

 

테스트 기기는 j4005 CPU 사용으로 4k 10bit 까지 디코딩이 된다고는 써있는데 잘 안되더란...

 

 

일단 host 에서 정상적으로 그래픽카드가 인식이 되어야 한다.

vainfo 명령어로 봤을 때 아래처럼 나왔다.

root@nuc:~# vainfo
error: can't connect to X server!
libva info: VA-API version 1.10.0
libva info: Trying to open /usr/lib/x86_64-linux-gnu/dri/iHD_drv_video.so
libva info: Found init function __vaDriverInit_1_10
libva info: va_openDriver() returns 0
vainfo: VA-API version: 1.10 (libva 2.10.0)
vainfo: Driver version: Intel iHD driver for Intel(R) Gen Graphics - 21.1.1 ()
vainfo: Supported profile and entrypoints
      VAProfileMPEG2Simple            : VAEntrypointVLD
      VAProfileMPEG2Main              : VAEntrypointVLD
      VAProfileH264Main               : VAEntrypointVLD
      VAProfileH264Main               : VAEntrypointEncSliceLP
      VAProfileH264High               : VAEntrypointVLD
      VAProfileH264High               : VAEntrypointEncSliceLP
      VAProfileJPEGBaseline           : VAEntrypointVLD
      VAProfileJPEGBaseline           : VAEntrypointEncPicture
      VAProfileH264ConstrainedBaseline: VAEntrypointVLD
      VAProfileH264ConstrainedBaseline: VAEntrypointEncSliceLP
      VAProfileVP8Version0_3          : VAEntrypointVLD
      VAProfileHEVCMain               : VAEntrypointVLD
      VAProfileHEVCMain10             : VAEntrypointVLD
      VAProfileVP9Profile0            : VAEntrypointVLD
      VAProfileVP9Profile2            : VAEntrypointVLD

아래 내용도 확인한다.

root@nuc:~# ls -l /dev/dri
total 0
drwxr-xr-x 2 root root         80 Aug 16 20:47 by-path
crw-rw---- 1 root video  226,   0 Aug 16 20:47 card0
crw-rw---- 1 root render 226, 128 Aug 16 20:47 renderD128

 

ubuntu 20.04 버전 CT templet 을 받아서 설치한다. priviliged 모드를 사용할 것. 

이제 설정 파일에 아래 내용을 추가해준다. (내 경우에는 CT번호가 101이다)

nano /etc/pve/lxc/101.conf 

lxc.cgroup2.devices.allow: c 226:0 rwm
lxc.cgroup2.devices.allow: c 226:128 rwm
lxc.cgroup2.devices.allow: c 29:0 rwm
lxc.mount.entry: /dev/dri dev/dri none bind,optional,create=dir
lxc.mount.entry: /dev/fb0 dev/fb0 none bind,optional,create=file
lxc.autodev: 1
lxc.hook.autodev: sh -c "mknod -m 0666 ${LXC_ROOTFS_MOUNT}/dev/fuse c 10 229"

위에 다섯줄은 intel gpu passthrough 을 위한 내용이고

마지막 두줄은 컨테이너 내에서 fuse 를 사용하기 위한 내용이다. (mount 용도)

 

중요한점은 proxmox 7.0 에서는 lxc.cgroup2 를 사용해야 한다는 내용이다..

하루종일 삽질했네..

 

 

참조 글

https://forum.proxmox.com/threads/pve-7-0-lxc-intel-quick-sync-passtrough-not-working-anymore.92025/

 

[SOLVED] - PVE 7.0 LXC Intel Quick Sync passtrough not working anymore

TLDR: lxc.cgroup.devices.allow MUST be changed to lxc.cgroup2.devices.allow https://forum.proxmox.com/threads/pve-7-0-lxc-intel-quick-sync-passtrough-not-working-anymore.92025/post-400916 Hi, with PVE 6.4, adding these lines to /etc/pve/lxc/.conf was enoug

forum.proxmox.com

 

https://forum.proxmox.com/threads/solved-nuc10-gpu-passthrough-pve-6-3.82023/

 

[SOLVED] - NUC10 GPU Passthrough (PVE 6.3)

Hi folks, I've one last niggle with my proxmox setup, and I'm hoping someone here can guide me to a solution. I have a cluster of 3 x NUC10 boxes, these are Frost Canyon CPUs with Intel UHD Graphics. I want to pass the Intel GPU down to one of my LXC conta

forum.proxmox.com

 

반응형

+ Recent posts