介绍如何通过红外和局域网协议将TCL电视接入HomeAssistant,让 TCL 电视同时支持:
- iPhone 家庭 App / 控制中心遥控器
- Home Assistant
media_player 实体
- 小爱同学语音按键
普通按键通过 TCL 电视的局域网控制协议发送;电源键使用红外发送,因为很多电视关机或待机时无法稳定接受局域网控制。
方案概览
iPhone 控制中心遥控器
-> HomeKit Bridge
-> media_player.tcl_tv
-> homekit_tv_remote_key_pressed
-> shell_command.tcl_tv_key
-> tcl_remote.py
-> TCL TV
小爱同学
-> 米家虚拟事件
-> Home Assistant 自动化
-> shell_command.tcl_tv_key
-> tcl_remote.py
-> TCL TV
电源开关
-> Home Assistant script
-> 和家亲 GK01 红外发射器
-> TCL TV
硬件与软件要求
- 一台支持 TCL Smart Remote / TCL+ 局域网遥控协议的 TCL 电视
- 一台 Home Assistant 主机
- 一台红外发射器
- 本文使用便宜的“和家亲 GK01”红外设备,并刷入 / 接入 ESPHome
- iPhone 遥控器需要 Home Assistant 的 HomeKit Bridge
- 小爱语音控制需要米家中枢虚拟事件,或其它能在 HA 中产生事件的等价方案
建议给电视设置固定 IP,例如:
192.168.1.100
下文用 <TV_IP> 表示电视 IP,请替换成你自己的地址。
放置脚本
把 tcl_remote.py 放到 Home Assistant 配置目录:
/config/scripts/tcl_tv/tcl_remote.py
确保 Home Assistant 主机可以执行:
python3 /config/scripts/tcl_tv/tcl_remote.py --ip <TV_IP> send home
如果电视要求 AES 加密,脚本需要 pycryptodome,或系统中有 openssl 作为 fallback。Home Assistant OS / Alpine 环境通常已经带有 openssl。
启用 Packages
如果你使用 package 管理配置,在 configuration.yaml 中加入:
homeassistant:
packages: !include_dir_named packages
然后创建:
/config/packages/tcl_tv.yaml
电视在线状态
这里用 ping 判断电视是否在线。多数电视开机后会响应 ICMP,关机后不会响应。
command_line:
- binary_sensor:
name: TCL TV Power
unique_id: tcl_tv_power_ping
command: "ping -W 1 -c 1 <TV_IP> >/dev/null 2>&1 && echo on || echo off"
command_timeout: 3
device_class: connectivity
payload_on: "on"
payload_off: "off"
scan_interval: 10
TCL 局域网按键服务
创建一个 shell_command,把 HA 服务调用转成 tcl_remote.py send <key>:
shell_command:
tcl_tv_key: >-
python3 /config/scripts/tcl_tv/tcl_remote.py --ip <TV_IP> send {{ key }}
可以在开发者工具中测试:
service: shell_command.tcl_tv_key
data:
key: home
常用按键:
up
down
left
right
ok
back
menu
home
vol_up
vol_down
mute
ch_up
ch_down
红外电源开关
电视电源建议用红外。本文使用“和家亲 GK01”作为廉价红外发射器,接入 ESPHome 后,可以在 HA 中通过服务发送红外码。
示例服务名:
esphome.gk01_send_ir_data
不同 ESPHome 节点的服务名会不同,请在 HA 的“开发者工具 -> 服务”中查找实际名称。
先学习或录制电视电源键红外码,然后创建脚本:
script:
tcl_tv_power_toggle:
alias: TCL TV Power Toggle
mode: single
sequence:
- service: esphome.gk01_send_ir_data
data:
ir_data: "<POWER_IR_CODE>"
其中 <POWER_IR_CODE> 替换成你的电视电源红外码。
为了避免 HomeKit 的“开机 / 关机”被当成无条件 toggle,可以结合 ping 状态做保护:
script:
tcl_tv_turn_on:
alias: TCL TV Turn On
mode: single
sequence:
- condition: state
entity_id: binary_sensor.tcl_tv_power
state: "off"
- service: script.tcl_tv_power_toggle
- delay: "00:00:08"
- service: homeassistant.update_entity
target:
entity_id: binary_sensor.tcl_tv_power
tcl_tv_turn_off:
alias: TCL TV Turn Off
mode: single
sequence:
- condition: state
entity_id: binary_sensor.tcl_tv_power
state: "on"
- service: script.tcl_tv_power_toggle
- delay: "00:00:08"
- service: homeassistant.update_entity
target:
entity_id: binary_sensor.tcl_tv_power
创建 Home Assistant 电视实体
使用 Universal Media Player 包装成一个 media_player:
media_player:
- platform: universal
name: TCL TV
unique_id: tcl_tv_homekit_remote
device_class: tv
state_template: >-
{% if is_state('binary_sensor.tcl_tv_power', 'on') %}
on
{% else %}
off
{% endif %}
commands:
turn_on:
service: script.tcl_tv_turn_on
turn_off:
service: script.tcl_tv_turn_off
volume_up:
service: shell_command.tcl_tv_key
data:
key: vol_up
volume_down:
service: shell_command.tcl_tv_key
data:
key: vol_down
volume_mute:
service: shell_command.tcl_tv_key
data:
key: mute
media_play:
service: shell_command.tcl_tv_key
data:
key: ok
media_pause:
service: shell_command.tcl_tv_key
data:
key: ok
暴露到 iPhone HomeKit
把电视实体作为单独 HomeKit accessory 暴露:
homekit:
- name: TCL TV
mode: accessory
port: 21064
filter:
include_entities:
- media_player.tcl_tv
然后重启 Home Assistant,在 iPhone 家庭 App 中添加 TCL TV 配件。添加后,iPhone 控制中心的遥控器就可以看到这台电视。
iPhone 遥控器方向键
iPhone 遥控器的方向键、确认、返回等会在 HA 中触发 homekit_tv_remote_key_pressed 事件。添加自动化:
automation:
- id: tcl_tv_homekit_remote_keys
alias: TCL TV - HomeKit Remote Keys
mode: queued
triggers:
- trigger: event
event_type: homekit_tv_remote_key_pressed
variables:
key_map:
arrow_up: up
arrow_down: down
arrow_left: left
arrow_right: right
select: ok
back: back
exit: back
information: menu
play_pause: ok
event_entity: "{{ trigger.event.data.get('entity_id') }}"
key_name: "{{ trigger.event.data.get('key_name') }}"
conditions:
- condition: template
value_template: "{{ key_name in key_map }}"
- condition: template
value_template: "{{ event_entity in [none, 'media_player.tcl_tv'] }}"
- condition: state
entity_id: binary_sensor.tcl_tv_power
state: "on"
actions:
- service: shell_command.tcl_tv_key
data:
key: "{{ key_map[key_name] }}"
小爱同学语音按键
小爱同学可以通过“米家虚拟事件”转发到 HA。思路是:
小爱语音短句 -> 米家自动化 -> 触发虚拟事件 -> HA 自动化 -> TCL 按键
先确认你的虚拟事件实体 ID,例如:
event.xiaomi_virtual_event
下文用 <XIAOMI_EVENT_ENTITY> 表示。
HA 自动化:
automation:
- id: tcl_tv_xiaoai_remote_keys
alias: TCL TV - Xiaoai Remote Keys
mode: queued
triggers:
- trigger: state
entity_id: <XIAOMI_EVENT_ENTITY>
not_from:
- unavailable
not_to:
- unavailable
- unknown
variables:
event_name: "{{ trigger.to_state.attributes.get('事件名称') }}"
key_map:
电视上: up
电视下: down
电视左: left
电视右: right
电视确定: ok
电视返回: back
电视首页: home
电视菜单: menu
电视静音: mute
电视音量加: vol_up
电视音量减: vol_down
电视频道加: ch_up
电视频道减: ch_down
conditions:
- condition: template
value_template: >-
{% set age = as_timestamp(now()) - as_timestamp(trigger.to_state.state, 0) %}
{{ age >= 0 and age <= 5 }}
- condition: template
value_template: "{{ event_name in key_map }}"
- condition: state
entity_id: binary_sensor.tcl_tv_power
state: "on"
actions:
- service: shell_command.tcl_tv_key
data:
key: "{{ key_map[event_name] }}"
然后在米家 / 小爱里创建语音自动化:
听到“电视上” -> 触发虚拟事件,事件名称:电视上
听到“电视下” -> 触发虚拟事件,事件名称:电视下
听到“电视左” -> 触发虚拟事件,事件名称:电视左
听到“电视右” -> 触发虚拟事件,事件名称:电视右
听到“电视确定” -> 触发虚拟事件,事件名称:电视确定
听到“电视返回” -> 触发虚拟事件,事件名称:电视返回
听到“电视首页” -> 触发虚拟事件,事件名称:电视首页
听到“电视菜单” -> 触发虚拟事件,事件名称:电视菜单
听到“电视静音” -> 触发虚拟事件,事件名称:电视静音
听到“电视音量加” -> 触发虚拟事件,事件名称:电视音量加
听到“电视音量减” -> 触发虚拟事件,事件名称:电视音量减
电源开关建议继续用红外单独处理,例如“小爱同学,打开电视 / 关闭电视”触发 HA 的 script.tcl_tv_turn_on 或 script.tcl_tv_turn_off。
完整示例
把下面内容保存为 /config/packages/tcl_tv.yaml,并替换:
<TV_IP>
<POWER_IR_CODE>
esphome.gk01_send_ir_data
<XIAOMI_EVENT_ENTITY>
command_line:
- binary_sensor:
name: TCL TV Power
unique_id: tcl_tv_power_ping
command: "ping -W 1 -c 1 <TV_IP> >/dev/null 2>&1 && echo on || echo off"
command_timeout: 3
device_class: connectivity
payload_on: "on"
payload_off: "off"
scan_interval: 10
shell_command:
tcl_tv_key: >-
python3 /config/scripts/tcl_tv/tcl_remote.py --ip <TV_IP> send {{ key }}
script:
tcl_tv_power_toggle:
alias: TCL TV Power Toggle
mode: single
sequence:
- service: esphome.gk01_send_ir_data
data:
ir_data: "<POWER_IR_CODE>"
tcl_tv_turn_on:
alias: TCL TV Turn On
mode: single
sequence:
- condition: state
entity_id: binary_sensor.tcl_tv_power
state: "off"
- service: script.tcl_tv_power_toggle
- delay: "00:00:08"
- service: homeassistant.update_entity
target:
entity_id: binary_sensor.tcl_tv_power
tcl_tv_turn_off:
alias: TCL TV Turn Off
mode: single
sequence:
- condition: state
entity_id: binary_sensor.tcl_tv_power
state: "on"
- service: script.tcl_tv_power_toggle
- delay: "00:00:08"
- service: homeassistant.update_entity
target:
entity_id: binary_sensor.tcl_tv_power
media_player:
- platform: universal
name: TCL TV
unique_id: tcl_tv_homekit_remote
device_class: tv
state_template: >-
{% if is_state('binary_sensor.tcl_tv_power', 'on') %}
on
{% else %}
off
{% endif %}
commands:
turn_on:
service: script.tcl_tv_turn_on
turn_off:
service: script.tcl_tv_turn_off
volume_up:
service: shell_command.tcl_tv_key
data:
key: vol_up
volume_down:
service: shell_command.tcl_tv_key
data:
key: vol_down
volume_mute:
service: shell_command.tcl_tv_key
data:
key: mute
media_play:
service: shell_command.tcl_tv_key
data:
key: ok
media_pause:
service: shell_command.tcl_tv_key
data:
key: ok
homekit:
- name: TCL TV
mode: accessory
port: 21064
filter:
include_entities:
- media_player.tcl_tv
automation:
- id: tcl_tv_homekit_remote_keys
alias: TCL TV - HomeKit Remote Keys
mode: queued
triggers:
- trigger: event
event_type: homekit_tv_remote_key_pressed
variables:
key_map:
arrow_up: up
arrow_down: down
arrow_left: left
arrow_right: right
select: ok
back: back
exit: back
information: menu
play_pause: ok
event_entity: "{{ trigger.event.data.get('entity_id') }}"
key_name: "{{ trigger.event.data.get('key_name') }}"
conditions:
- condition: template
value_template: "{{ key_name in key_map }}"
- condition: template
value_template: "{{ event_entity in [none, 'media_player.tcl_tv'] }}"
- condition: state
entity_id: binary_sensor.tcl_tv_power
state: "on"
actions:
- service: shell_command.tcl_tv_key
data:
key: "{{ key_map[key_name] }}"
- id: tcl_tv_xiaoai_remote_keys
alias: TCL TV - Xiaoai Remote Keys
mode: queued
triggers:
- trigger: state
entity_id: <XIAOMI_EVENT_ENTITY>
not_from:
- unavailable
not_to:
- unavailable
- unknown
variables:
event_name: "{{ trigger.to_state.attributes.get('事件名称') }}"
key_map:
电视上: up
电视下: down
电视左: left
电视右: right
电视确定: ok
电视返回: back
电视首页: home
电视菜单: menu
电视静音: mute
电视音量加: vol_up
电视音量减: vol_down
电视频道加: ch_up
电视频道减: ch_down
conditions:
- condition: template
value_template: >-
{% set age = as_timestamp(now()) - as_timestamp(trigger.to_state.state, 0) %}
{{ age >= 0 and age <= 5 }}
- condition: template
value_template: "{{ event_name in key_map }}"
- condition: state
entity_id: binary_sensor.tcl_tv_power
state: "on"
actions:
- service: shell_command.tcl_tv_key
data:
key: "{{ key_map[event_name] }}"
生效与验证
- 保存 package。
- 在 HA 中执行配置检查。
- 重启 Home Assistant,让
command_line、media_player 和 homekit 完整加载。
- 检查实体:
binary_sensor.tcl_tv_power
media_player.tcl_tv
automation.tcl_tv_homekit_remote_keys
automation.tcl_tv_xiaoai_remote_keys
- 手动测试:
service: shell_command.tcl_tv_key
data:
key: home
- 在 iPhone 家庭 App 中添加
TCL TV 配件。
- 在小爱 / 米家中创建虚拟事件自动化。
注意事项
ping 判断状态不是绝对可靠。如果电视待机仍响应 ping,会显示为开机;如果开机但阻止 ICMP,会显示为关机。
shell_command 中不要使用 {{ key | quote }},Home Assistant 的模板环境不一定提供 quote filter。这里的 key 来自固定映射,不应直接暴露给任意输入。
- HomeKit 的 TV accessory 建议使用
mode: accessory 单独暴露,避免和其它 HomeKit Bridge 实体混在一起。
- 语音方向键体验会比遥控器慢,适合作为补充控制。