blueretro-ble 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hudson Brendon
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,50 @@
1
+ Metadata-Version: 2.4
2
+ Name: blueretro-ble
3
+ Version: 0.1.0
4
+ Summary: BLE library for the BlueRetro retro-console Bluetooth adapter
5
+ Author-email: Hudson Brendon <contato.hudsonbrendon@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/hudsonbrendon/blueretro-homeassistant
8
+ Project-URL: Repository, https://github.com/hudsonbrendon/blueretro-homeassistant
9
+ Project-URL: Issues, https://github.com/hudsonbrendon/blueretro-homeassistant/issues
10
+ Keywords: bluetooth,ble,blueretro,home-assistant,retro-gaming
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Home Automation
16
+ Requires-Python: >=3.12
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: bleak>=0.22.0
20
+ Requires-Dist: bleak-retry-connector>=3.5.0
21
+ Provides-Extra: test
22
+ Requires-Dist: pytest>=8.0; extra == "test"
23
+ Requires-Dist: pytest-asyncio>=0.23; extra == "test"
24
+ Requires-Dist: pytest-homeassistant-custom-component>=0.13; extra == "test"
25
+ Requires-Dist: pyserial>=3.5; extra == "test"
26
+ Requires-Dist: dbus-fast>=2.0; extra == "test"
27
+ Dynamic: license-file
28
+
29
+ # BlueRetro for Home Assistant
30
+
31
+ Autodiscovers a [BlueRetro](https://github.com/darthcloud/BlueRetro) adapter over Bluetooth
32
+ and exposes read-only sensors plus reboot/deep-sleep controls.
33
+
34
+ ## Limits
35
+
36
+ - Works only while the adapter is **idle** (no controller connected). During gameplay the
37
+ config BLE is unavailable, so entities show `unavailable` — by design, to protect gameplay.
38
+ - No battery or live controller input (the hardware does not expose them).
39
+
40
+ ## Install
41
+
42
+ 1. Add this repo as a HACS custom repository (category: Integration).
43
+ 2. Install **BlueRetro** and restart Home Assistant.
44
+ 3. Power the BlueRetro with no controller connected; Home Assistant discovers it automatically.
45
+
46
+ ## Entities
47
+
48
+ - Sensors: Firmware, Game ID, Game, Config source, ABI version (diag), BD address (diag).
49
+ - Binary sensor: Config available (connectivity).
50
+ - Buttons: Reboot, Deep sleep.
@@ -0,0 +1,22 @@
1
+ # BlueRetro for Home Assistant
2
+
3
+ Autodiscovers a [BlueRetro](https://github.com/darthcloud/BlueRetro) adapter over Bluetooth
4
+ and exposes read-only sensors plus reboot/deep-sleep controls.
5
+
6
+ ## Limits
7
+
8
+ - Works only while the adapter is **idle** (no controller connected). During gameplay the
9
+ config BLE is unavailable, so entities show `unavailable` — by design, to protect gameplay.
10
+ - No battery or live controller input (the hardware does not expose them).
11
+
12
+ ## Install
13
+
14
+ 1. Add this repo as a HACS custom repository (category: Integration).
15
+ 2. Install **BlueRetro** and restart Home Assistant.
16
+ 3. Power the BlueRetro with no controller connected; Home Assistant discovers it automatically.
17
+
18
+ ## Entities
19
+
20
+ - Sensors: Firmware, Game ID, Game, Config source, ABI version (diag), BD address (diag).
21
+ - Binary sensor: Config available (connectivity).
22
+ - Buttons: Reboot, Deep sleep.
@@ -0,0 +1,16 @@
1
+ """BlueRetro BLE library."""
2
+
3
+ from .const import SERVICE_UUID
4
+ from .device import BlueRetroDevice
5
+ from .discovery import supports
6
+ from .models import BlueRetroState
7
+
8
+ __version__ = "0.1.0"
9
+
10
+ __all__ = [
11
+ "SERVICE_UUID",
12
+ "BlueRetroDevice",
13
+ "BlueRetroState",
14
+ "supports",
15
+ "__version__",
16
+ ]
@@ -0,0 +1,18 @@
1
+ """BlueRetro BLE protocol constants (from darthcloud/BlueRetroWebCfg)."""
2
+
3
+ SERVICE_UUID = "56830f56-5180-fab0-314b-2fa176799a00"
4
+
5
+ # Directly readable characteristics
6
+ CHAR_ABI = "56830f56-5180-fab0-314b-2fa176799a06"
7
+ CHAR_CMD = "56830f56-5180-fab0-314b-2fa176799a07"
8
+ CHAR_APP = "56830f56-5180-fab0-314b-2fa176799a09"
9
+ CHAR_BDADDR = "56830f56-5180-fab0-314b-2fa176799a0c"
10
+
11
+ # Command bytes written to CHAR_CMD, response read back from CHAR_CMD
12
+ CMD_GET_GAMEID = 0x04
13
+ CMD_GET_CFG_SRC = 0x05
14
+ CMD_SYS_DEEP_SLEEP = 0x37
15
+ CMD_SYS_RESET = 0x38
16
+
17
+ # Advertised BLE name prefix used for discovery
18
+ NAME_PREFIX = "BlueRetro"
@@ -0,0 +1,89 @@
1
+ """High-level BlueRetro device operations over BLE."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+
7
+ from bleak import BleakClient
8
+ from bleak.backends.device import BLEDevice
9
+ from bleak.exc import BleakError
10
+ from bleak_retry_connector import establish_connection
11
+
12
+ from . import const
13
+ from .gameid import lookup_game_name
14
+ from .models import BlueRetroState
15
+ from .parser import decode_abi, decode_bdaddr, decode_string
16
+
17
+ _LOGGER = logging.getLogger(__name__)
18
+
19
+
20
+ class BlueRetroDevice:
21
+ """Connects to a BlueRetro adapter to read state and send commands."""
22
+
23
+ def __init__(self) -> None:
24
+ self.last_state = BlueRetroState()
25
+
26
+ async def _connect(self, ble_device: BLEDevice) -> BleakClient:
27
+ return await establish_connection(
28
+ BleakClient, ble_device, ble_device.address
29
+ )
30
+
31
+ async def async_update(self, ble_device: BLEDevice) -> BlueRetroState:
32
+ """Connect, read all fields, disconnect. Never raises."""
33
+ try:
34
+ client = await self._connect(ble_device)
35
+ except (BleakError, TimeoutError, OSError) as err:
36
+ _LOGGER.debug("BlueRetro connect failed: %s", err)
37
+ self.last_state = BlueRetroState(available=False)
38
+ return self.last_state
39
+
40
+ try:
41
+ abi = decode_abi(await client.read_gatt_char(const.CHAR_ABI))
42
+ fw = decode_string(await client.read_gatt_char(const.CHAR_APP))
43
+ bdaddr = decode_bdaddr(await client.read_gatt_char(const.CHAR_BDADDR))
44
+ game_id = decode_string(
45
+ await self._command(client, const.CMD_GET_GAMEID)
46
+ )
47
+ cfg_src = decode_abi(
48
+ await self._command(client, const.CMD_GET_CFG_SRC)
49
+ )
50
+ except (BleakError, TimeoutError, OSError, Exception) as err: # noqa: BLE001
51
+ _LOGGER.debug("BlueRetro read failed: %s", err)
52
+ self.last_state = BlueRetroState(available=False)
53
+ return self.last_state
54
+ finally:
55
+ await client.disconnect()
56
+
57
+ state = BlueRetroState(
58
+ available=True,
59
+ fw_version=fw,
60
+ abi_version=abi,
61
+ bdaddr=bdaddr,
62
+ game_id=game_id,
63
+ cfg_src=cfg_src,
64
+ game_name=lookup_game_name(game_id),
65
+ )
66
+ self.last_state = state
67
+ return state
68
+
69
+ async def _command(self, client: BleakClient, command: int) -> bytes:
70
+ """Write a command byte to CHAR_CMD then read the response."""
71
+ await client.write_gatt_char(const.CHAR_CMD, bytes([command]), response=True)
72
+ return await client.read_gatt_char(const.CHAR_CMD)
73
+
74
+ async def async_reboot(self, ble_device: BLEDevice) -> None:
75
+ """Reboot the adapter."""
76
+ await self._send_command(ble_device, const.CMD_SYS_RESET)
77
+
78
+ async def async_deep_sleep(self, ble_device: BLEDevice) -> None:
79
+ """Put the adapter into deep sleep."""
80
+ await self._send_command(ble_device, const.CMD_SYS_DEEP_SLEEP)
81
+
82
+ async def _send_command(self, ble_device: BLEDevice, command: int) -> None:
83
+ client = await self._connect(ble_device)
84
+ try:
85
+ await client.write_gatt_char(
86
+ const.CHAR_CMD, bytes([command]), response=True
87
+ )
88
+ finally:
89
+ await client.disconnect()
@@ -0,0 +1,20 @@
1
+ """Detect a BlueRetro device from its BLE advertisement."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Protocol
6
+
7
+ from .const import NAME_PREFIX, SERVICE_UUID
8
+
9
+
10
+ class _AdvertisementLike(Protocol):
11
+ name: str | None
12
+ service_uuids: list[str]
13
+
14
+
15
+ def supports(info: _AdvertisementLike) -> bool:
16
+ """True if the advertisement looks like a BlueRetro adapter."""
17
+ name = info.name or ""
18
+ if name.startswith(NAME_PREFIX):
19
+ return True
20
+ return SERVICE_UUID.lower() in {u.lower() for u in info.service_uuids}
@@ -0,0 +1,32 @@
1
+ """Resolve a BlueRetro Game ID to a human-readable game name."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sqlite3
6
+ from importlib import resources
7
+ from pathlib import Path
8
+
9
+
10
+ def _bundled_db_path() -> Path:
11
+ return Path(str(resources.files("blueretro_ble") / "gameid.db"))
12
+
13
+
14
+ def lookup_game_name(game_id: str | None, db_path: Path | None = None) -> str | None:
15
+ """Look up a game name in the SQLite game database.
16
+
17
+ Returns None for unknown IDs, missing DB, or any read error.
18
+ """
19
+ if not game_id:
20
+ return None
21
+ path = db_path or _bundled_db_path()
22
+ if not Path(path).exists():
23
+ return None
24
+ try:
25
+ uri = f"file:{path}?mode=ro"
26
+ with sqlite3.connect(uri, uri=True) as conn:
27
+ row = conn.execute(
28
+ "SELECT name FROM games WHERE id = ? LIMIT 1", (game_id,)
29
+ ).fetchone()
30
+ except sqlite3.Error:
31
+ return None
32
+ return row[0] if row else None
@@ -0,0 +1,18 @@
1
+ """State container for a BlueRetro device."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+
8
+ @dataclass(slots=True)
9
+ class BlueRetroState:
10
+ """Snapshot of a BlueRetro adapter read over BLE."""
11
+
12
+ available: bool = False
13
+ fw_version: str | None = None
14
+ abi_version: int | None = None
15
+ bdaddr: str | None = None
16
+ game_id: str | None = None
17
+ game_name: str | None = None
18
+ cfg_src: int | None = None
@@ -0,0 +1,23 @@
1
+ """Pure decoders for BlueRetro characteristic payloads."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ def decode_bdaddr(raw: bytes) -> str | None:
7
+ """Decode a 6-byte BD address (little-endian, byte 5 first)."""
8
+ if len(raw) < 6:
9
+ return None
10
+ return ":".join(f"{raw[i]:02x}" for i in range(5, -1, -1))
11
+
12
+
13
+ def decode_string(raw: bytes) -> str | None:
14
+ """Decode a UTF-8 string, stripping trailing NULs. Empty -> None."""
15
+ text = raw.decode("utf-8", errors="replace").rstrip("\x00").strip()
16
+ return text or None
17
+
18
+
19
+ def decode_abi(raw: bytes) -> int | None:
20
+ """Decode a small integer carried in the first byte."""
21
+ if not raw:
22
+ return None
23
+ return raw[0]
@@ -0,0 +1,50 @@
1
+ Metadata-Version: 2.4
2
+ Name: blueretro-ble
3
+ Version: 0.1.0
4
+ Summary: BLE library for the BlueRetro retro-console Bluetooth adapter
5
+ Author-email: Hudson Brendon <contato.hudsonbrendon@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/hudsonbrendon/blueretro-homeassistant
8
+ Project-URL: Repository, https://github.com/hudsonbrendon/blueretro-homeassistant
9
+ Project-URL: Issues, https://github.com/hudsonbrendon/blueretro-homeassistant/issues
10
+ Keywords: bluetooth,ble,blueretro,home-assistant,retro-gaming
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Home Automation
16
+ Requires-Python: >=3.12
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: bleak>=0.22.0
20
+ Requires-Dist: bleak-retry-connector>=3.5.0
21
+ Provides-Extra: test
22
+ Requires-Dist: pytest>=8.0; extra == "test"
23
+ Requires-Dist: pytest-asyncio>=0.23; extra == "test"
24
+ Requires-Dist: pytest-homeassistant-custom-component>=0.13; extra == "test"
25
+ Requires-Dist: pyserial>=3.5; extra == "test"
26
+ Requires-Dist: dbus-fast>=2.0; extra == "test"
27
+ Dynamic: license-file
28
+
29
+ # BlueRetro for Home Assistant
30
+
31
+ Autodiscovers a [BlueRetro](https://github.com/darthcloud/BlueRetro) adapter over Bluetooth
32
+ and exposes read-only sensors plus reboot/deep-sleep controls.
33
+
34
+ ## Limits
35
+
36
+ - Works only while the adapter is **idle** (no controller connected). During gameplay the
37
+ config BLE is unavailable, so entities show `unavailable` — by design, to protect gameplay.
38
+ - No battery or live controller input (the hardware does not expose them).
39
+
40
+ ## Install
41
+
42
+ 1. Add this repo as a HACS custom repository (category: Integration).
43
+ 2. Install **BlueRetro** and restart Home Assistant.
44
+ 3. Power the BlueRetro with no controller connected; Home Assistant discovers it automatically.
45
+
46
+ ## Entities
47
+
48
+ - Sensors: Firmware, Game ID, Game, Config source, ABI version (diag), BD address (diag).
49
+ - Binary sensor: Config available (connectivity).
50
+ - Buttons: Reboot, Deep sleep.
@@ -0,0 +1,16 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ blueretro_ble/__init__.py
5
+ blueretro_ble/const.py
6
+ blueretro_ble/device.py
7
+ blueretro_ble/discovery.py
8
+ blueretro_ble/gameid.db
9
+ blueretro_ble/gameid.py
10
+ blueretro_ble/models.py
11
+ blueretro_ble/parser.py
12
+ blueretro_ble.egg-info/PKG-INFO
13
+ blueretro_ble.egg-info/SOURCES.txt
14
+ blueretro_ble.egg-info/dependency_links.txt
15
+ blueretro_ble.egg-info/requires.txt
16
+ blueretro_ble.egg-info/top_level.txt
@@ -0,0 +1,9 @@
1
+ bleak>=0.22.0
2
+ bleak-retry-connector>=3.5.0
3
+
4
+ [test]
5
+ pytest>=8.0
6
+ pytest-asyncio>=0.23
7
+ pytest-homeassistant-custom-component>=0.13
8
+ pyserial>=3.5
9
+ dbus-fast>=2.0
@@ -0,0 +1 @@
1
+ blueretro_ble
@@ -0,0 +1,50 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "blueretro-ble"
7
+ version = "0.1.0"
8
+ description = "BLE library for the BlueRetro retro-console Bluetooth adapter"
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.12"
12
+ authors = [{ name = "Hudson Brendon", email = "contato.hudsonbrendon@gmail.com" }]
13
+ keywords = ["bluetooth", "ble", "blueretro", "home-assistant", "retro-gaming"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3.12",
19
+ "Topic :: Home Automation",
20
+ ]
21
+ dependencies = [
22
+ "bleak>=0.22.0",
23
+ "bleak-retry-connector>=3.5.0",
24
+ ]
25
+
26
+ [project.urls]
27
+ Homepage = "https://github.com/hudsonbrendon/blueretro-homeassistant"
28
+ Repository = "https://github.com/hudsonbrendon/blueretro-homeassistant"
29
+ Issues = "https://github.com/hudsonbrendon/blueretro-homeassistant/issues"
30
+
31
+ [project.optional-dependencies]
32
+ test = [
33
+ "pytest>=8.0",
34
+ "pytest-asyncio>=0.23",
35
+ "pytest-homeassistant-custom-component>=0.13",
36
+ # Pulled in transitively by Home Assistant's bluetooth stack on Linux, but
37
+ # required explicitly to run the HA bluetooth integration under tests on macOS.
38
+ "pyserial>=3.5",
39
+ "dbus-fast>=2.0",
40
+ ]
41
+
42
+ [tool.setuptools]
43
+ packages = ["blueretro_ble"]
44
+
45
+ [tool.setuptools.package-data]
46
+ blueretro_ble = ["gameid.db"]
47
+
48
+ [tool.pytest.ini_options]
49
+ asyncio_mode = "auto"
50
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+