9/18/2025

HA + Pi สั่งปิดเปิด Air Auto แทนเครื่อง MN-SERVER

 HA + Pi สั่งปิดเปิด Air Auto แทนเครื่อง MN-SERVER

คำสั่ง Restart service HA docker

  1. docker restart Docker-ID

ตั้งค่า Timezone ให้ Debian เพื่อให้เวลาตรง
  1. timedatectl set-timezone Asia/Bangkok
  2. timedatectl
  3. date


1. ติดตั้ง Home Assistant with Docker
https://intranet.scivalve.com/blog.php?u=281

2. HA และ PI สร้าง Private Key เพื่อให้ HA สามารถ SSH เข้าเครื่อง PI โดยไม่ต้องใช้รหัส
จะทำให้ HA สามารถเรียกคำสั่งโปรแกรมที่เครื่อง PI ได้
2.1. ใช้ Terminal ล็อคอินเข้าไปที่ เครื่อง Server ที่รัน Docker และ Home Assistant ของคุณ
2.2. รันคำสั่งนี้เพื่อสร้าง Key คู่ใหม่:
  1. ssh-keygen -t rsa -b 4096

2.3. ระบบจะถามคำถาม 2-3 ข้อ ให้ กด Enter ผ่านไปทั้งหมด เพื่อยอมรับค่าเริ่มต้น และ ไม่ต้องใส่รหัสผ่าน (passphrase) นะครับ
2.4. คำสั่งนี้จะสร้างไฟล์ id_rsa (กุญแจลับ) และ id_rsa.pub (กุญแจสาธารณะ) ขึ้นมาในโฟลเดอร์ .ssh ของ user ที่คุณกำลังล็อคอินอยู่บน Server (เช่น /root/.ssh/ หากคุณล็อคอินเป็น root)

[บนเครื่อง HA Server] ขั้นตอนที่ 2: คัดลอกเนื้อหา Public Key

2.5. รันคำสั่งนี้เพื่อแสดงเนื้อหาของ Public Key:
  1. cat ~/.ssh/id_rsa.pub

2.6. คัดลอก (Copy) ข้อความทั้งหมด ที่แสดงขึ้นมา มันจะขึ้นต้นด้วย ssh-rsa และลงท้ายด้วยชื่อ user@hostname ของคุณ

[บนเครื่อง Raspberry Pi] ขั้นตอนที่ 3: ติดตั้ง Public Key

2.7. ตอนนี้ ให้ใช้ Terminal ล็อคอินเข้าไปที่ เครื่อง Raspberry Pi ของคุณ
2.8. รันคำสั่งนี้เพื่อสร้างโฟลเดอร์และไฟล์ที่จำเป็น (หากยังไม่มี) และตั้งค่า permission ให้ถูกต้อง:
  1. mkdir -p ~/.ssh && chmod 700 ~/.ssh && touch ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys

2.9. เปิดไฟล์ authorized_keys ด้วยโปรแกรม nano:
  1. nano ~/.ssh/authorized_keys

2.10. วาง (Paste) Public Key ที่คุณคัดลอกมาจากขั้นตอนที่ 2 ลงในไฟล์นี้ จากนั้นกด Ctrl+X > กด Y > กด Enter เพื่อบันทึกและออกจากโปรแกรม

[บนเครื่อง HA Server] ขั้นตอนที่ 4: คัดลอก Private Key ให้ Docker
2.11. กลับมาที่ Terminal ของ เครื่อง HA Server
2.12. ตอนนี้ให้ทำขั้นตอนที่เราคุยกันล่าสุด คือคัดลอก Private Key (id_rsa) ที่เพิ่งสร้างในขั้นตอนที่ 1 ไปยังโฟลเดอร์คอนฟิกของ HA ที่แชร์กับ Docker

# แก้ /root/.ssh/id_rsa หาก Key ของคุณอยู่ที่อื่น
# แก้ /path/to/your/ha/config/ ให้เป็น Path จริง
เครื่องจริงอยู่ที่ /config/id_rsa
  1. cp /root/.ssh/id_rsa /path/to/your/ha/config/

2.13. ตั้งค่า permission ให้ไฟล์ที่คัดลอกไป:
  1. chmod 600 /path/to/your/ha/config/id_rsa

2.14. ทดสอบ SSH จากเครื่อง HA ไปเครื่อง PI จะต้องไม่ขึ้นถามรหัส Login

3. แก้ไข ไฟล์ configuration.yaml เพื่อให้มี Switch ปิดเปิด Air manual เพิ่ม
  1. ### Air ####
  2. shell_command:
  3.   # !!! สำคัญ: แก้ไข <IP_ของ_RPi> และ path ไปยังไฟล์ .py ของคุณให้ถูกต้อง !!!
  4.   #### TEST #####
  5.   #air1_on:  "sshpass -p 'XXX' ssh pi@192.168.0.3 'python3 /home/pi/Test.py 2302 4'"
  6.   #air1_off: "sshpass -p 'XXX' ssh pi@192.168.0.3 'python3 /home/pi/Test.py 2302 4'"
  7.   #air1_on:  "ssh -i /HA/id_rsa -o 'StrictHostKeyChecking=no' pi@192.168.0.3 'python3 /home/pi/Test.py 2302 4'"
  8.   #air1_off: "ssh -i /HA/id_rsa -o 'StrictHostKeyChecking=no' pi@192.168.0.3 'python3 /home/pi/Test.py 2302 4'"
  9.   #air1_on:  "ssh pi@192.168.0.3 'python3 /home/pi/Test.py 2302 4'"
  10.   #air1_off: "ssh pi@192.168.0.3 'python3 /home/pi/Test.py 2302 4'"
  11.   ##### Test Rocketchat #######
  12.   #air1_on:  "ssh -i /config/id_rsa -o 'StrictHostKeyChecking=no' pi@192.168.0.3 'python3 /home/pi/Test.py 2302 4'"
  13.   #air1_off: "ssh -i /config/id_rsa -o 'StrictHostKeyChecking=no' pi@192.168.0.3 'python3 /home/pi/Test.py 2302 4'"
  14.  
  15.   air1_on:  "ssh -i /config/id_rsa -o 'StrictHostKeyChecking=no' pi@192.168.0.3 'python3 /home/pi/On_air1.py'"
  16.   air1_off: "ssh -i /config/id_rsa -o 'StrictHostKeyChecking=no' pi@192.168.0.3 'python3 /home/pi/Off_air1.py'"
  17.  
  18.   air2_on:  "ssh -i /config/id_rsa -o 'StrictHostKeyChecking=no' pi@192.168.0.3 'python3 /home/pi/On_air2.py'"
  19.   air2_off: "ssh -i /config/id_rsa -o 'StrictHostKeyChecking=no' pi@192.168.0.3 'python3 /home/pi/Off_air2.py'"
  20.  
  21.   # --- DEBUG COMMANDS ---
  22.   #debug_find_path: "pwd > pwd.txt && ls -la >> pwd.txt"
  23.  
  24. # New sensor for human-readable status
  25. sensor:
  26.   - platform: template
  27.     sensors:
  28.       air_status:
  29.         friendly_name: "สถานะแอร์"
  30.         value_template: >-
  31.           {% set state = states('input_select.active_air') %}
  32.           {% if state == 'air1' %}
  33.             Air 1 กำลังทำงาน
  34.           {% elif state == 'air2' %}
  35.             Air 2 กำลังทำงาน
  36.           {% elif state == 'both' %}
  37.             Air 1 และ Air 2 กำลังทำงาน
  38.           {% else %}
  39.             แอร์ทุกเครื่องปิดอยู่
  40.           {% endif %}
  41.  
  42. switch:
  43.   - platform: template
  44.     switches:
  45.       air1_switch:
  46.         friendly_name: "Air 1"
  47.         value_template: "{{ states('input_select.active_air') in ['air1', 'both'] }}"
  48.         turn_on:
  49.           - service: shell_command.air1_on
  50.           - service: input_select.select_option
  51.             target:
  52.               entity_id: input_select.active_air
  53.             data:
  54.               option: >-
  55.                 {% if is_state('input_select.active_air', 'air2') %}
  56.                   both
  57.                 {% else %}
  58.                   air1
  59.                 {% endif %}
  60.         turn_off:
  61.           - service: shell_command.air1_off
  62.           - service: input_select.select_option
  63.             target:
  64.               entity_id: input_select.active_air
  65.             data:
  66.               option: >-
  67.                 {% if is_state('input_select.active_air', 'both') %}
  68.                   air2
  69.                 {% else %}
  70.                   none
  71.                 {% endif %}
  72.  
  73.       air2_switch:
  74.         friendly_name: "Air 2"
  75.         value_template: "{{ states('input_select.active_air') in ['air2', 'both'] }}"
  76.         turn_on:
  77.           - service: shell_command.air2_on
  78.           - service: input_select.select_option
  79.             target:
  80.               entity_id: input_select.active_air
  81.             data:
  82.               option: >-
  83.                 {% if is_state('input_select.active_air', 'air1') %}
  84.                   both
  85.                 {% else %}
  86.                   air2
  87.                 {% endif %}
  88.         turn_off:
  89.           - service: shell_command.air2_off
  90.           - service: input_select.select_option
  91.             target:
  92.               entity_id: input_select.active_air
  93.             data:
  94.               option: >-
  95.                 {% if is_state('input_select.active_air', 'both') %}
  96.                   air1
  97.                 {% else %}
  98.                   none
  99.                 {% endif %}
  100.  
  101.       # New switch for controlling both
  102.       all_air_switch:
  103.         friendly_name: "Air1+Air2"
  104.         value_template: "{{ is_state('input_select.active_air', 'both') }}"
  105.         turn_on:
  106.           - service: shell_command.air1_on
  107.           - service: shell_command.air2_on
  108.           - service: input_select.select_option
  109.             target:
  110.               entity_id: input_select.active_air
  111.             data:
  112.               option: 'both'
  113.         turn_off:
  114.           - service: shell_command.air1_off
  115.           - service: shell_command.air2_off
  116.           - service: input_select.select_option
  117.             target:
  118.               entity_id: input_select.active_air
  119.             data:
  120.               option: 'none'
  121.  
  122. input_select:
  123.   active_air:
  124.     name: Air ที่ทำงานอยู่
  125.     options:
  126.       - none
  127.       - air1
  128.       - air2
  129.       - both # Added 'both' state
  130.     initial: none


4. ไฟล์ automation.yaml เพื่อให้สลับ Air อัตโนมัติ เพิ่ม
  1. #### Off ก่อนแล้วค่อย ON เพื่อไม่ให้ Switch เปิดทั้ง 2
  2. #### delay 15 วิ รอให้ คำสั่งแรกทำงานเสร็จก่อน ถ้าทำต่อเลย คำสั่งแรกยังทำไม่เสร็จ Status จะไม่ถูก
  3. - id: air1_0000
  4.   alias: "Air1 00:00 - 02:59"
  5.   trigger:
  6.     - platform: time
  7.       at: "00:00:00"
  8.   action:
  9.     - service: switch.turn_off
  10.       target:
  11.         entity_id: switch.all_air_switch
  12.     - delay: "00:00:15"   # รอ 15 วินาที      
  13.     - service: switch.turn_on
  14.       target:
  15.         entity_id: switch.air1_switch
  16.  
  17. - id: air2_0300
  18.   alias: "Air2 03:00 - 05:59"
  19.   trigger:
  20.     - platform: time
  21.       at: "03:00:00"
  22.   action:
  23.     - service: switch.turn_off
  24.       target:
  25.         entity_id: switch.all_air_switch
  26.     - delay: "00:00:15"   # รอ 15 วินาที      
  27.     - service: switch.turn_on
  28.       target:
  29.         entity_id: switch.air2_switch
  30.  
  31. - id: air1_0600
  32.   alias: "Air1 06:00 - 08:59"
  33.   trigger:
  34.     - platform: time
  35.       at: "06:00:00"
  36.   action:
  37.     - service: switch.turn_off
  38.       target:
  39.         entity_id: switch.all_air_switch
  40.     - delay: "00:00:15"   # รอ 15 วินาที      
  41.     - service: switch.turn_on
  42.       target:
  43.         entity_id: switch.air1_switch
  44.  
  45. - id: air2_0900
  46.   alias: "Air2 09:00 - 11:59"
  47.   trigger:
  48.     - platform: time
  49.       at: "09:00:00"
  50.   action:
  51.     - service: switch.turn_off
  52.       target:
  53.         entity_id: switch.all_air_switch
  54.     - delay: "00:00:15"   # รอ 15 วินาที      
  55.     - service: switch.turn_on
  56.       target:
  57.         entity_id: switch.air2_switch
  58.  
  59. - id: air1_1200
  60.   alias: "Air1 12:00 - 14:59"
  61.   trigger:
  62.     - platform: time
  63.       at: "12:00:00"
  64.   action:
  65.     - service: switch.turn_off
  66.       target:
  67.         entity_id: switch.all_air_switch
  68.     - delay: "00:00:15"   # รอ 15 วินาที      
  69.     - service: switch.turn_on
  70.       target:
  71.         entity_id: switch.air1_switch
  72.  
  73. - id: air2_1500
  74.   alias: "Air2 15:00 - 17:59"
  75.   trigger:
  76.     - platform: time
  77.       at: "15:00:00"
  78.   action:
  79.     - service: switch.turn_off
  80.       target:
  81.         entity_id: switch.all_air_switch
  82.     - delay: "00:00:15"   # รอ 15 วินาที    
  83.     - service: switch.turn_on
  84.       target:
  85.         entity_id: switch.air2_switch
  86.  
  87. - id: air1_1800
  88.   alias: "Air1 18:00 - 20:59"
  89.   trigger:
  90.     - platform: time
  91.       at: "18:00:00"
  92.   action:
  93.     - service: switch.turn_off
  94.       target:
  95.         entity_id: switch.all_air_switch
  96.     - delay: "00:00:15"   # รอ 15 วินาที      
  97.     - service: switch.turn_on
  98.       target:
  99.         entity_id: switch.air1_switch
  100.  
  101. - id: air2_2100
  102.   alias: "Air2 21:00 - 23:59"
  103.   trigger:
  104.     - platform: time
  105.       at: "21:00:00"
  106.   action:
  107.     - service: switch.turn_off
  108.       target:
  109.         entity_id: switch.all_air_switch
  110.     - delay: "00:00:15"   # รอ 15 วินาที      
  111.     - service: switch.turn_on
  112.       target:
  113.         entity_id: switch.air2_switch

5. ที่เครื่อง PI เพิ่มไฟล์ 4 ไฟล์ สำหรับ HA เรียกใช้
On_air1.py, On_air2.py, Off_air1.py, Off_air2.py
Code
On_air1.py
  1. #!/usr/bin/python3
  2. import time
  3. import RPi.GPIO as GPIO
  4.  
  5. GPIO.setmode(GPIO.BCM)
  6.  
  7. GPIO.setwarnings(False)
  8. GPIO.setup(20, GPIO.OUT)
  9. GPIO.output(20, GPIO.LOW)
  10. #GPIO.setup(21, GPIO.OUT)
  11. #GPIO.output(21, GPIO.LOW)
  12.  
  13. ##### Rocketchat #####
  14. import requests
  15. from datetime import datetime
  16. def send_to_rocketchat(message):
  17.    url = "http://xx.xx.xx.xx:3000/api/v1/chat.postMessage"
  18.  
  19.    headers = {
  20.       "Content-type": "application/json",
  21.       "X-Auth-Token": "XXX",
  22.       "X-User-Id": "XXX"
  23.    }
  24.  
  25.    payload = {
  26.        "channel": "#Test", #IT_Notification
  27.        "text": message
  28.    }
  29.    response = requests.post(url, json=payload, headers=headers)
  30.  
  31. message = "แจ้งเตือน เปิด AIR1 ปิด AIR2 " + datetime.today().strftime('%Y-%m-%d %H:%M:%S')
  32.  
  33. send_to_rocketchat(message)
  34.  

On_air2.py
  1. #!/usr/bin/python3
  2. import time
  3. import RPi.GPIO as GPIO
  4.  
  5. GPIO.setmode(GPIO.BCM)
  6.  
  7. GPIO.setwarnings(False)
  8. #GPIO.setup(20, GPIO.OUT)
  9. #GPIO.output(20, GPIO.LOW)
  10. GPIO.setup(21, GPIO.OUT)
  11. GPIO.output(21, GPIO.LOW)
  12.  
  13.  
  14. ##### Rocketchat #####
  15. import requests
  16. from datetime import datetime
  17. def send_to_rocketchat(message):
  18.    url = "http://192.168.2.76:3000/api/v1/chat.postMessage"
  19.  
  20.    headers = {
  21.       "Content-type": "application/json",
  22.       "X-Auth-Token": "XXX",
  23.       "X-User-Id": "XXX"
  24.    }
  25.  
  26.    payload = {
  27.        "channel": "#Test", #IT_Notification
  28.        "text": message
  29.    }
  30.    response = requests.post(url, json=payload, headers=headers)
  31.  
  32. message = "แจ้งเตือน เปิด AIR2 ปิด AIR1 " + datetime.today().strftime('%Y-%m-%d %H:%M:%S')
  33.  
  34. send_to_rocketchat(message)
  35.  

Off_air1.py

  1. import time
  2. import RPi.GPIO as GPIO
  3.  
  4. GPIO.setmode(GPIO.BCM)
  5.  
  6. GPIO.setwarnings(False)
  7. GPIO.setup(20, GPIO.OUT)
  8. GPIO.output(20, GPIO.HIGH)
  9. #GPIO.setup(21, GPIO.OUT)
  10. #GPIO.output(21, GPIO.HIGH)
  11. GPIO.cleanup()
  12.  

Off_air2.py
  1. #!/usr/bin/python3
  2. import time
  3. import RPi.GPIO as GPIO
  4.  
  5. GPIO.setmode(GPIO.BCM)
  6.  
  7. GPIO.setwarnings(False)
  8. #GPIO.setup(20, GPIO.OUT)
  9. #GPIO.output(20, GPIO.HIGH)
  10. GPIO.setup(21, GPIO.OUT)
  11. GPIO.output(21, GPIO.HIGH)
  12. GPIO.cleanup()

 

No comments:

Post a Comment