Repository: manymuch/Xgimi-4-Home-Assistant
Branch: main
Commit: d21f879f4ecc
Files: 14
Total size: 21.5 KB
Directory structure:
gitextract_cd2ah2na/
├── LICENSE
├── README.md
├── assets/
│ └── tv-card-example.yaml
├── custom_components/
│ └── xgimi/
│ ├── __init__.py
│ ├── config_flow.py
│ ├── const.py
│ ├── manifest.json
│ ├── pyxgimi.py
│ ├── remote.py
│ └── translations/
│ ├── en.json
│ ├── es.json
│ ├── fr.json
│ └── zh-Hans.json
└── hacs.json
================================================
FILE CONTENTS
================================================
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2022 Jiaxin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Xgimi-4-Home-Assistant
<img src="https://brands.home-assistant.io/xgimi/logo.png" width="360" height="120">
XGIMI integration for home assistant.
Please give me a star :star_struck: if you like it.
## 📦Install
### Manually
1. Download the latest source code ``xgimi.zip`` from [Releases](https://github.com/manymuch/Xgimi-4-Home-Assistant/releases)
2. Unzip and copy the folder `xgimi` into home assistant `custom_components`
2. Restart home assistant
### Via HACS
1. Install [HACS](https://hacs.xyz/) if you do not have that already
2. In the Home Assitant HACS Tab, click on the three dots at the top right
3. Choose `Custom repositories`
4. Paste `https://github.com/manymuch/Xgimi-4-Home-Assistant/` to the repository field, choose the `Integration` category and click `ADD`
5. Close the dialog
6. Disable the repository filter (hit `CLEAR` on the right of the `Filtering by Downloaded` text)
7. Type `Xgimi` to the Search bar and select `Xgimi Projector Remote`
8. Click `DOWNLOAD` on the bottom right
9. Click `DOWNLOAD` in the dialog that just appeared
10. Restart home assistant
## 🛜Get BLE token
The integration communicates with xgimi projector by UDP using local IP except for the **poweron** command. Once the projector is powered off, the only way to turn up is sending a special ble advertisement. Such a ble advertisement contains a special token called `manufacture data`. The `manufacture data` is **different for each device even for same model**.
This token can be obtained using Home Assistant's built-in **Bluetooth Advertisement Monitor**.
### 1. Open the Bluetooth Advertisement Monitor
You can access the monitor directly from your Home Assistant instance:
👉 [Open Bluetooth Advertisement Monitor](https://my.home-assistant.io/redirect/bluetooth_advertisement_monitor/)
Alternatively, navigate manually:
1. Go to **Settings → Devices & Services → Bluetooth → Configure**.
2. Select **Advertisement Monitor**.
This view displays all Bluetooth Low Energy (BLE) advertisement packets detected by your Bluetooth adapter.
### 2. Identify the Xgimi Remote MAC Address
Because the BLE environment may contain dozens of nearby devices, it’s helpful to filter results by MAC address.
If you don’t know the remote’s MAC address:
1. Pair your Xgimi remote temporarily with an Android phone.
1. To pair the remote, turn off the projector completely or go to another room and simultaneously press and hold the **Back** and **Home** buttons until the indicator light flashes.
2. Look for a device with the name **“XGIMI RC”** or **“BLuetooth 4.0 RC”** and pair with it.
2. In the Bluetooth settings, open **Device details** to find the **MAC address**.
3. Note down the first three bytes (the prefix). For example, in many cases XGIMI remotes begin with `1C:`.
4. After that, pair the remote with the projector again.
You can later use this prefix to narrow down advertisements in the HA monitor.
### 3. Filter and Locate the Manufacturer Data
1. In the **Advertisement Monitor**, apply a **MAC prefix filter** (e.g., `1C:`) to reduce noise.
2. Turn off your projector so that the remote control does not automatically connect to it.
3. Press the **Power** button on the remote to send a special BLE advertisement frame.
4. Look for entries where the **Manufacturer data** field is populated and has an entry with ID: `70`. This data is a hexadecimal payload broadcast by the remote and contains the BLE token required by the integration.
Example frame data:
```json
{
"name": "BLuetooth 4.0 RC",
"address": "1C:XX:XX:XX:XX:XX",
"rssi": -66,
"manufacturer_data": {
"13": "383800000001",
"70": "51f55a6d78e450ffffff0000000b000d"
},
"service_data": {},
"service_uuids": [
"00001812-0000-1000-8000-00805f9b34fb"
],
"source": "30:24:XX:XX:XX:XX",
"connectable": true,
"time": 1761087659.0729098,
"tx_power": null,
"raw": "0000000000000c0000000f0f000000000e0000000000000000000d000000"
}
```
Copy the full value from the manufacturer data with ID `70` — this is the token required for integration configuration. In this example:
``51f55a6d78e450ffffff0000000b000d``.
## 🏗️Setup
1. Prepare the following:
* ``host``: local IP of the projector, check the router or the setting in the projector's menu. (For all commands via LAN: poweroff, volume, etc.)
* ``token``: BLE token to power on the projector. (For poweron command only, via bluetooth signals)
2. Make sure your projector is **powered on** and can be reached via LAN by home assistant.
3. Add new integration, search for xgimi
4. Enter your projector information, for example:
```bash
name: z6x
host: 192.168.0.115
token: 51F55A6D78E450FFFFFF0000000B000D
```
## 📺How to use
The integration setup up a remote entity: e.g. `remote.z6x`.
Example usage of remote.send_command service:
```yaml
action: remote.send_command
data:
command: volumeup
target:
entity_id: remote.z6x
```
Available commands:
The below commands work for all models:
```
play, pause, power, back, home, menu, right, left,
up, down, volumedown, volumeup,
poweron, poweroff, volumemute
```
The below commands may only work for some models, you can have a try and good luck :-)
```
autofocus, autofocus_new,
manual_focus_left, manual_focus_right,
motor_left_overstep, motor_left_start,
motor_right_overstep, motor_right_start, motor_stop,
shortcut_setting, choose_source, hibernate, xmusic
```
### Dashboard example
See [tv-card-example.yaml](assets/tv-card-example.yaml) for a dashboard example using [tv-card](https://github.com/marrobHD/tv-card)
<img src="assets/tv_card.png" width="200" height="220">
### Troubleshoot
1. If you are running Home Assistant with docker, make sure HA is accessible to the bluetooth, see [issue #12](https://github.com/manymuch/Xgimi-4-Home-Assistant/issues/12).
2. Make sure the bluetooth signal from HA host machine can reach the projector without blockage.
3. If you can not use LAN control (volumeup, poweroff, etc..), it is likely that your XGIMI is running on native Android TV OS. Try to use [Android TV Remote](https://www.home-assistant.io/integrations/androidtv_remote/). See also [issue #40](https://github.com/manymuch/Xgimi-4-Home-Assistant/issues/40).
### More Related threads about BLE token
* [issue #5](https://github.com/manymuch/Xgimi-4-Home-Assistant/issues/5)
* [issue #31](https://github.com/manymuch/Xgimi-4-Home-Assistant/issues/31)
* [issue #54](https://github.com/manymuch/Xgimi-4-Home-Assistant/issues/54) Thanks @mik-laj for contributing usage of advertisement monitor in HA.
* [stackoverflow discussion](https://stackoverflow.com/questions/69921353/how-can-i-clone-a-non-paired-ble-signal-from-a-remote-to-trigger-a-device/75551013#75551013)
## TODO
- auto discovery
- media player entity
Contributions and suggestions are welcome!
Please give me a star :star_struck: if you like it.
================================================
FILE: assets/tv-card-example.yaml
================================================
type: custom:tv-card
entity: remote.z6x
tv: true
left:
service: remote.send_command
service_data:
command: left
entity_id: remote.z6x
right:
service: remote.send_command
service_data:
command: right
entity_id: remote.z6x
up:
service: remote.send_command
service_data:
command: up
entity_id: remote.z6x
down:
service: remote.send_command
service_data:
command: down
entity_id: remote.z6x
select:
service: remote.send_command
service_data:
command: play
entity_id: remote.z6x
back:
service: remote.send_command
service_data:
command: back
entity_id: remote.z6x
volume_up:
service: remote.send_command
service_data:
command: volumeup
entity_id: remote.z6x
volume_down:
service: remote.send_command
service_data:
command: volumedown
entity_id: remote.z6x
volume_mute:
service: remote.send_command
service_data:
command: volumemute
entity_id: remote.z6x
power:
service: remote.send_command
service_data:
command: power
entity_id: remote.z6x
home:
service: remote.send_command
service_data:
command: home
entity_id: remote.z6x
================================================
FILE: custom_components/xgimi/__init__.py
================================================
"""Xgimi Projector Integration"""
from __future__ import annotations
from typing import Final
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN, Platform
from homeassistant.core import HomeAssistant
from .const import DOMAIN
PLATFORMS: Final[list[Platform]] = [
Platform.REMOTE,
]
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Set up a config entry."""
hass.data.setdefault(DOMAIN, {})
config = {}
for k in [CONF_HOST, CONF_TOKEN, CONF_NAME]:
config[k] = config_entry.data.get(k)
hass.data[DOMAIN][config_entry.entry_id] = config
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(
config_entry, PLATFORMS
)
if unload_ok:
hass.data[DOMAIN].pop(config_entry.entry_id)
return unload_ok
================================================
FILE: custom_components/xgimi/config_flow.py
================================================
from __future__ import annotations
from typing import Any
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN
from homeassistant.data_entry_flow import FlowResult
from homeassistant.util.network import is_host_valid
from .const import (
DOMAIN,
)
class XgimiConfigFLow(config_entries.ConfigFlow, domain=DOMAIN):
VERSION = 1
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
errors: dict[str, str] = {}
if user_input is not None:
host = user_input[CONF_HOST]
name = user_input[CONF_NAME]
token = user_input[CONF_TOKEN]
if not is_host_valid(host):
errors[CONF_HOST] = "invalid_host"
else:
await self.async_set_unique_id(f"{name}-{token}")
self._abort_if_unique_id_configured()
return self.async_create_entry(title=user_input[CONF_NAME], data=user_input)
else:
user_input = {}
return self.async_show_form(
step_id="user",
data_schema=vol.Schema({
vol.Required(CONF_NAME, default=user_input.get(CONF_NAME, vol.UNDEFINED)): str,
vol.Required(CONF_HOST, default=user_input.get(CONF_HOST, vol.UNDEFINED)): str,
vol.Required(CONF_TOKEN, default=user_input.get(CONF_TOKEN, vol.UNDEFINED)): str,
}),
errors=errors,
)
================================================
FILE: custom_components/xgimi/const.py
================================================
"""Constants for Xgimi Integration."""
# Base component constants
NAME = "Xgimi Projector Integration"
DOMAIN = "xgimi"
DOMAIN_DATA = f"{DOMAIN}_data"
VERSION = "0.0.8"
================================================
FILE: custom_components/xgimi/manifest.json
================================================
{
"domain": "xgimi",
"name": "Xgimi Projector Remote",
"version": "v0.0.8",
"config_flow": true,
"integration_type": "device",
"documentation": "https://github.com/manymuch/Xgimi-4-Home-Assistant",
"issue_tracker": "https://github.com/manymuch/Xgimi-4-Home-Assistant/issues",
"requirements": ["asyncudp", "bluez-peripheral"],
"codeowners": [
"@manymuch"
],
"iot_class": "local_polling"
}
================================================
FILE: custom_components/xgimi/pyxgimi.py
================================================
import asyncudp
import asyncio
from bluez_peripheral.util import get_message_bus
from bluez_peripheral.advert import Advertisement
from time import time
class XgimiApi:
def __init__(self, ip, command_port, advance_port, alive_port, manufacturer_data) -> None:
self.ip = ip
self.command_port = command_port # 16735
self.advance_port = advance_port # 16750
self.alive_port = alive_port # 554
self.manufacturer_data = manufacturer_data
self._is_on = False
self.last_on = time()
self.last_off = time()
self._command_dict = {
"ok": "KEYPRESSES:49",
"play": "KEYPRESSES:49",
"pause": "KEYPRESSES:49",
"power": "KEYPRESSES:116",
"back": "KEYPRESSES:48",
"home": "KEYPRESSES:35",
"menu": "KEYPRESSES:139",
"right": "KEYPRESSES:37",
"left": "KEYPRESSES:50",
"up": "KEYPRESSES:36",
"down": "KEYPRESSES:38",
"volumedown": "KEYPRESSES:114",
"volumeup": "KEYPRESSES:115",
"poweroff": "KEYPRESSES:30",
"volumemute": "KEYPRESSES:113",
"autofocus": "KEYPRESSES:2099",
"autofocus_new": "KEYPRESSES:2103",
"manual_focus_left": "KEYPRESSES:2097",
"manual_focus_right": "KEYPRESSES:2098",
"motor_left_overstep": "KEYPRESSES:2095",
"motor_left_start": "KEYPRESSES:2092",
"motor_right_overstep": "KEYPRESSES:2096",
"motor_right_start": "KEYPRESSES:2093",
"motor_stop": "KEYPRESSES:2101",
"shortcut_setting": "KEYPRESSES:2094",
"choose_source": "KEYPRESSES:2102",
"hibernate": "KEYPRESSES:2106",
"xmusic": "KEYPRESSES:2108",
}
self._advance_command = str({"action": 20000, "controlCmd": {"data": "command_holder",
"delayTime": 0, "mode": 5, "time": 0, "type": 0}, "msgid": "2"})
@property
def is_on(self) -> bool:
"""Return true if the device is on."""
return self._is_on
async def async_fetch_data(self):
if time() - self.last_on < 30:
self._is_on = True
elif time() - self.last_off < 30:
self._is_on = False
else:
alive = await self.async_check_alive()
self._is_on = alive
async def async_check_alive(self):
try:
_, writer = await asyncio.open_connection(
self.ip, self.alive_port)
writer.close()
await writer.wait_closed()
return True
except ConnectionRefusedError:
return False
except Exception:
return False
async def async_ble_power_on(self, manufacturer_data: str, company_id: int = 0x0046, service_uuid: str = "1812"):
bus = await get_message_bus()
advert = Advertisement(
localName="Bluetooth 4.0 RC",
serviceUUIDs=[service_uuid],
manufacturerData={company_id: bytes.fromhex(manufacturer_data)},
timeout=1,
duration=1000,
appearance=961,
)
await advert.register(bus)
async def async_robust_ble_power_on(self, manufacturer_data: str, company_id: int = 0x0046, service_uuid: str = "1812"):
for i in range(10):
await self.async_ble_power_on(manufacturer_data, company_id, service_uuid)
await asyncio.sleep(1)
async def async_send_command(self, command) -> None:
"""Send a command to a device."""
if command in self._command_dict:
if command == "poweroff":
self._is_on = False
self.last_off = time()
msg = self._command_dict[command]
remote_addr = (self.ip, self.command_port)
sock = await asyncudp.create_socket(remote_addr=remote_addr)
sock.sendto(msg.encode("utf-8"))
sock.close()
elif command == "poweron":
self._is_on = True
self.last_on = time()
await self.async_robust_ble_power_on(self.manufacturer_data)
else:
msg = self._advance_command.replace("command_holder", command)
remote_addr = (self.ip, self.advance_port)
sock = await asyncudp.create_socket(remote_addr=remote_addr)
sock.sendto(msg.encode("utf-8"))
sock.close()
================================================
FILE: custom_components/xgimi/remote.py
================================================
"""Support for the Xgimi Projector."""
from collections.abc import Iterable
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .pyxgimi import XgimiApi
from homeassistant.components.remote import (
RemoteEntity,
)
from .const import DOMAIN
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up the Xiaomi TV platform."""
# If a hostname is set. Discovery is skipped.
host = config.get(CONF_HOST)
name = config.get(CONF_NAME)
token = config.get(CONF_TOKEN)
unique_id = f"{name}-{token}"
xgimi_api = XgimiApi(ip=host, command_port=16735, advance_port=16750, alive_port=554,
manufacturer_data=token)
async_add_entities([XgimiRemote(xgimi_api, name, unique_id)])
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
config = hass.data[DOMAIN][config_entry.entry_id]
host = config[CONF_HOST]
name = config[CONF_NAME]
token = config[CONF_TOKEN]
unique_id = config_entry.unique_id
assert unique_id is not None
xgimi_api = XgimiApi(ip=host, command_port=16735, advance_port=16750, alive_port=554,
manufacturer_data=token)
async_add_entities([XgimiRemote(xgimi_api, name, unique_id)])
class XgimiRemote(RemoteEntity):
"""An entity for Xgimi Projector
"""
def __init__(self, xgimi_api, name, unique_id):
self.xgimi_api = xgimi_api
self._name = name
self._icon = "mdi:projector"
self._unique_id = unique_id
async def async_update(self):
"""Retrieve latest state."""
await self.xgimi_api.async_fetch_data()
@property
def is_on(self):
"""Return true if remote is on."""
return self.xgimi_api._is_on
@property
def name(self):
"""Return the name of the device if any."""
return self._name
@property
def icon(self):
"""Return the icon to use for device if any."""
return self._icon
@property
def unique_id(self):
"""Return an unique ID."""
return self._unique_id
async def async_turn_on(self, **kwargs):
"""Turn the Xgimi Projector On."""
# Do the turning on.
await self.xgimi_api.async_send_command("poweron")
async def async_turn_off(self, **kwargs):
"""Turn the Xgimi Projector Off."""
# Do the turning off.
await self.xgimi_api.async_send_command("poweroff")
async def async_send_command(self, command: Iterable[str], **kwargs) -> None:
"""Send a command to one of the devices."""
for single_command in command:
await self.xgimi_api.async_send_command(single_command)
================================================
FILE: custom_components/xgimi/translations/en.json
================================================
{
"config": {
"step": {
"user": {
"title": "Configure TV info",
"description": "Please make sure your TV turned on before trying to set it up.",
"data": {
"name": "TV Name",
"host": "TV Host",
"token": "BLE Token"
}
}
},
"error": {
"invalid_host": "Invalid hostname or IP address",
"cannot_connect": "Failed to connect"
},
"abort": {
"already_configured": "Device is already configured"
}
}
}
================================================
FILE: custom_components/xgimi/translations/es.json
================================================
{
"config": {
"step": {
"user": {
"title": "Configura la información de la TV",
"description": "Por favor, has de estar seguro que tu TV está encendida antes de intentar configurarla.",
"data": {
"name": "Nombre de la TV",
"host": "Nombre del host/IP de la TV",
"token": "Token de BLE"
}
}
},
"error": {
"invalid_host": "Nombre del host o dirección IP inválidos",
"cannot_connect": "Ha fallado la conexión"
},
"abort": {
"already_configured": "El dispositivo está ya configurado"
}
}
}
================================================
FILE: custom_components/xgimi/translations/fr.json
================================================
{
"config": {
"step": {
"user": {
"title": "Configuration du projecteur XGIMI",
"description": "Assurez-vous que le projecteur XGIMI est allumé avant de démarrer la configuration.",
"data": {
"name": "Nom du projecteur",
"host": "Adresse IP du projecteur",
"token": "Token BLE"
}
}
},
"error": {
"invalid_host": "Nom ou adresse IP invalide",
"cannot_connect": "Connexion échouée"
},
"abort": {
"already_configured": "Équipement déjà configuré"
}
}
}
================================================
FILE: custom_components/xgimi/translations/zh-Hans.json
================================================
{
"config": {
"step": {
"user": {
"title": "Xgimi Projector Remote",
"description": "在配置之前,请确保电视已经打开。",
"data": {
"name": "电视名称",
"host": "电视 IP",
"token": "蓝牙 Token"
}
}
},
"error": {
"invalid_host": "IP错误",
"cannot_connect": "连接设备失败"
},
"abort": {
"already_configured": "该设备已经配置过"
}
}
}
================================================
FILE: hacs.json
================================================
{
"name": "Xgimi Projector Remote",
"render_readme": true,
"hide_default_branch": true
}
gitextract_cd2ah2na/ ├── LICENSE ├── README.md ├── assets/ │ └── tv-card-example.yaml ├── custom_components/ │ └── xgimi/ │ ├── __init__.py │ ├── config_flow.py │ ├── const.py │ ├── manifest.json │ ├── pyxgimi.py │ ├── remote.py │ └── translations/ │ ├── en.json │ ├── es.json │ ├── fr.json │ └── zh-Hans.json └── hacs.json
SYMBOL INDEX (24 symbols across 4 files)
FILE: custom_components/xgimi/__init__.py
function async_setup_entry (line 16) | async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEnt...
function async_unload_entry (line 29) | async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEn...
FILE: custom_components/xgimi/config_flow.py
class XgimiConfigFLow (line 16) | class XgimiConfigFLow(config_entries.ConfigFlow, domain=DOMAIN):
method async_step_user (line 19) | async def async_step_user(
FILE: custom_components/xgimi/pyxgimi.py
class XgimiApi (line 8) | class XgimiApi:
method __init__ (line 9) | def __init__(self, ip, command_port, advance_port, alive_port, manufac...
method is_on (line 53) | def is_on(self) -> bool:
method async_fetch_data (line 57) | async def async_fetch_data(self):
method async_check_alive (line 66) | async def async_check_alive(self):
method async_ble_power_on (line 78) | async def async_ble_power_on(self, manufacturer_data: str, company_id:...
method async_robust_ble_power_on (line 90) | async def async_robust_ble_power_on(self, manufacturer_data: str, comp...
method async_send_command (line 95) | async def async_send_command(self, command) -> None:
FILE: custom_components/xgimi/remote.py
function async_setup_platform (line 18) | async def async_setup_platform(hass, config, async_add_entities, discove...
function async_setup_entry (line 32) | async def async_setup_entry(
class XgimiRemote (line 50) | class XgimiRemote(RemoteEntity):
method __init__ (line 54) | def __init__(self, xgimi_api, name, unique_id):
method async_update (line 60) | async def async_update(self):
method is_on (line 65) | def is_on(self):
method name (line 70) | def name(self):
method icon (line 75) | def icon(self):
method unique_id (line 80) | def unique_id(self):
method async_turn_on (line 84) | async def async_turn_on(self, **kwargs):
method async_turn_off (line 89) | async def async_turn_off(self, **kwargs):
method async_send_command (line 94) | async def async_send_command(self, command: Iterable[str], **kwargs) -...
Condensed preview — 14 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (24K chars).
[
{
"path": "LICENSE",
"chars": 1063,
"preview": "MIT License\n\nCopyright (c) 2022 Jiaxin\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof "
},
{
"path": "README.md",
"chars": 6912,
"preview": "# Xgimi-4-Home-Assistant\n<img src=\"https://brands.home-assistant.io/xgimi/logo.png\" width=\"360\" height=\"120\"> \n\nXGIMI "
},
{
"path": "assets/tv-card-example.yaml",
"chars": 1154,
"preview": "type: custom:tv-card\nentity: remote.z6x\ntv: true\nleft:\n service: remote.send_command\n service_data:\n command: left\n"
},
{
"path": "custom_components/xgimi/__init__.py",
"chars": 1096,
"preview": "\"\"\"Xgimi Projector Integration\"\"\"\nfrom __future__ import annotations\n\nfrom typing import Final\n\nfrom homeassistant.confi"
},
{
"path": "custom_components/xgimi/config_flow.py",
"chars": 1531,
"preview": "from __future__ import annotations\n\nfrom typing import Any\nimport voluptuous as vol\n\nfrom homeassistant import config_en"
},
{
"path": "custom_components/xgimi/const.py",
"chars": 169,
"preview": "\"\"\"Constants for Xgimi Integration.\"\"\"\n# Base component constants\nNAME = \"Xgimi Projector Integration\"\nDOMAIN = \"xgimi\"\n"
},
{
"path": "custom_components/xgimi/manifest.json",
"chars": 414,
"preview": "{\n \"domain\": \"xgimi\",\n \"name\": \"Xgimi Projector Remote\",\n \"version\": \"v0.0.8\",\n \"config_flow\": true,\n \"integration_"
},
{
"path": "custom_components/xgimi/pyxgimi.py",
"chars": 4486,
"preview": "import asyncudp\nimport asyncio\nfrom bluez_peripheral.util import get_message_bus\nfrom bluez_peripheral.advert import Adv"
},
{
"path": "custom_components/xgimi/remote.py",
"chars": 2962,
"preview": "\"\"\"Support for the Xgimi Projector.\"\"\"\n\nfrom collections.abc import Iterable\nfrom homeassistant.config_entries import Co"
},
{
"path": "custom_components/xgimi/translations/en.json",
"chars": 512,
"preview": "{\n \"config\": {\n \"step\": {\n \"user\": {\n \"title\": \"Configure TV info\",\n \"description\": \"Please make "
},
{
"path": "custom_components/xgimi/translations/es.json",
"chars": 606,
"preview": "{\n \"config\": {\n \"step\": {\n \"user\": {\n \"title\": \"Configura la información de la TV\",\n \"description"
},
{
"path": "custom_components/xgimi/translations/fr.json",
"chars": 570,
"preview": "{\n \"config\": {\n \"step\": {\n \"user\": {\n \"title\": \"Configuration du projecteur XGIMI\",\n \"description"
},
{
"path": "custom_components/xgimi/translations/zh-Hans.json",
"chars": 408,
"preview": "{\n \"config\": {\n \"step\": {\n \"user\": {\n \"title\": \"Xgimi Projector Remote\",\n \"description\": \"在配置之前,请"
},
{
"path": "hacs.json",
"chars": 101,
"preview": "{\n \"name\": \"Xgimi Projector Remote\",\n \"render_readme\": true,\n \"hide_default_branch\": true\n}\n"
}
]
About this extraction
This page contains the full source code of the manymuch/Xgimi-4-Home-Assistant GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 14 files (21.5 KB), approximately 6.0k tokens, and a symbol index with 24 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.