Skip to content

Commit

Permalink
feature(xgimi): Add poweron through ble
Browse files Browse the repository at this point in the history
  • Loading branch information
manymuch committed Mar 11, 2023
1 parent bb169b0 commit 9a03ba9
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 20 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ XGIMI integration for home assistant
- platform: xgimi
name: z6x # can be changed with any name you like
host: 192.168.0.115 # your xgimi projector IP
token: "12D7C7899B9F80FFFFFF3043524B544D" # BLE manufacture data
```
4. Restart home assistant
Expand All @@ -26,21 +27,20 @@ target:
Current support command:
```
play, pause, power, back, home, menu, right, left
up, down, volumedown, volumeup, poweroff, volumemute
up, down, volumedown, volumeup, poweron, poweroff, volumemute
```
### Dashboard example
See [tv-card-example.yaml](tv-card-example.yaml) for a dashboard example using [tv-card](https://github.com/marrobHD/tv-card)

## Limitation
There is no way to **power on** the xgimi projector through this integration.
The integration communicates with xigimi projector by UDP, once the projector is powered off, the only way to wake it up is using the bluetooth remote. However, I can not find any relevant threads about how to simulate the bluetooth remote.
## About Power-On with ble manufacture data
The integration communicates with xigimi projector by UDP except for the **poweron** command. Once the projector is powered off, the only way to wake it up is sending a special ble advertisement. Such a ble advertisement contains a special token called `manufacture data`. The manufacture data seems to be different for each model. Please go through issue [#5](https://github.com/manymuch/Xgimi-4-Home-Assistant/issues/5) for better understanding and how you can get manufacture data of your projector.
The **poweron** command is in beta mode, it is not guaranteed to work every time. Please let me know if you have any issue with it.

## TODO
- setup through UI
- auto discovery
- media player entity
- more command support
- publish a pyxgimi package


This integration is in very early stage, contributions and suggestions are welcome!
2 changes: 1 addition & 1 deletion custom_components/xgimi/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
NAME = "Xgimi Projector Integration"
DOMAIN = "xgimi"
DOMAIN_DATA = f"{DOMAIN}_data"
VERSION = "0.0.1"
VERSION = "0.0.2"
4 changes: 2 additions & 2 deletions custom_components/xgimi/manifest.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"domain": "xgimi",
"name": "Xgimi Projector Remote",
"version": "v0.0.1",
"version": "v0.0.2",
"documentation": "https://github.com/manymuch/Xgimi-4-Home-Assistant",
"issue_tracker": "https://github.com/manymuch/Xgimi-4-Home-Assistant/issues",
"requirements": ["asyncudp"],
"requirements": ["asyncudp", "bluez-peripheral"],
"codeowners": [
"@manymuch"
],
Expand Down
37 changes: 33 additions & 4 deletions custom_components/xgimi/pyxgimi.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import asyncudp
import asyncio
from bluez_peripheral.util import Adapter, get_message_bus
from bluez_peripheral.advert import Advertisement


class XgimiApi:
def __init__(self, ip, command_port, advance_port, alive_port) -> None:
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._command_dict = {
"ok": "KEYPRESSES:49",
"play": "KEYPRESSES:49",
"pause": "KEYPRESSES:49",
"power": "KEYPRESSES:116",
Expand Down Expand Up @@ -45,14 +49,39 @@ async def async_fetch_data(self):
except Exception:
self._is_on = False

async def async_ble_power_on(self, manufacturer_data: str, company_id: int = 0x0046, service_uuid: str = "1812"):
bus = await get_message_bus()
adapter = await Adapter.get_first(bus)
advert = Advertisement(
localName="Bluetooth 4.0 RC",
serviceUUIDs=[service_uuid],
manufacturerData={company_id: bytes.fromhex(manufacturer_data)},
timeout=15,
duration=15,
appearance=0,
)
await advert.register(bus, adapter)
await asyncio.sleep(15)
bus.disconnect()
await bus.wait_for_disconnect()


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
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
await self.async_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()
sock = await asyncudp.create_socket(remote_addr=remote_addr)
sock.sendto(msg.encode("utf-8"))
sock.close()
14 changes: 6 additions & 8 deletions custom_components/xgimi/remote.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Support for the Xgimi Projector."""

from collections.abc import Iterable
from homeassistant.const import CONF_HOST, CONF_NAME
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN
from .pyxgimi import XgimiApi
import asyncio

Expand All @@ -17,8 +17,10 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
# If a hostname is set. Discovery is skipped.
host = config.get(CONF_HOST)
name = config.get(CONF_NAME)
token = config.get(CONF_TOKEN)

xgimi_api = XgimiApi(host, 16735, 16750, 554)
xgimi_api = XgimiApi(ip=host, command_port=16735, advance_port=16750, alive_port=554,
manufacturer_data=token)
async_add_entities([XgimiRemote(xgimi_api, name)])


Expand Down Expand Up @@ -53,18 +55,14 @@ def icon(self):
async def async_turn_on(self, **kwargs):
"""Turn the Xgimi Projector On."""
# Do the turning on.
await self.xgimi_api.async_send_command("power")
await self.async_update()
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")
await asyncio.sleep(10)
await self.async_update()

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)
await self.async_update()
await self.xgimi_api.async_send_command(single_command)

0 comments on commit 9a03ba9

Please sign in to comment.