Skip to content

Commit

Permalink
Merge pull request #31 from melianmiko/release/v0.14_rebase
Browse files Browse the repository at this point in the history
release/v0.14 rebase
  • Loading branch information
melianmiko authored Sep 23, 2024
2 parents 68b50fb + 18007a6 commit 52d0192
Show file tree
Hide file tree
Showing 84 changed files with 1,864 additions and 439 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/on_push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ jobs:
- uses: actions/checkout@v4
- name: Set up poetry environment
run: poetry install
#- name: Change in-app version
#run: bash.exe -c "./scripts/bump_version_git.sh"
- name: Change in-app version
run: python3 ./scripts/bump_version.py git
- name: Run build script
run: .\scripts/build_win32\make.cmd
- name: Upload bundle
Expand Down Expand Up @@ -64,7 +64,7 @@ jobs:
- name: Set up poetry environment
run: poetry install
- name: Change in-app version
run: bash -c "./scripts/bump_version_git.sh"
run: python3 ./scripts/bump_version.py git
- name: Run build script
run: bash ./scripts/build_debian/build.sh
- name: Upload bundle
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@
/build
/builddir
/dist
/scripts/tools
/release.json

/.idea
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# v0.14.0
- [Core] Client-server architecture, close #14;
- Now multiple instances of OpenFreebuds could be launched, for multi-user usage for example;
- Built-in HTTP-server is now used as cross process communication protocol, so it can't be fully disabled, remote access is still disallowed out-of-box;
- If you need to launch multiple instances from single user, use -c CLI flag;
- [Core] Web-server authorization
- [Core] Rewritten (mostly from scratch) to asyncio ;
- [Core] Drop pybluez from dependencies, now will use predefined port numbers instead of SDP detection;
- [UI] Rewritten from scratch to asyncio and PyQT6, introduce redesigned Settings UI;
- [UI] Disable logging to CLI / `journalctl` if -v isn't present, close #29;
- [Device compatibility] Bug fixes for modern HUAWEI Devices (5i, Pro 2, Pro 3);
- [HUAWEI FreeBuds 5i & other] Better Dual-Connect configuration;
- [HUAWEI FreeBuds 5i & other] Add low-latency mode setting;
- [HUAWEI FreeBuds 5i & other] Add triple-tap settings;
- [HUAWEI FreeBuds 5i & other] Fix SQ preference switch;
- [Device compatibility] Add HUAWEI FreeLace Pro 2 compatibility;
- Custom equalizer preset configuration (should also work with Pro 3);
- [Linux] Flatpak installation option

# Older releases
WIP
11 changes: 3 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

![Tray menu preview](docs/preview_0.png)

This application allows to control HUAWEI FreeBuds earphone settings from PC. Check exact battery level, toggle noise cancellation, control built-in equalizer, change gestures, and all other in-device settings and features are now available without official mobile application.

Features
---------

Expand Down Expand Up @@ -45,10 +47,7 @@ If your device isn't listed here, you could try to use it with profile for other
- [HUAWEI FreeLace Pro](./docs/devices/HUAWEI_FreeLace_Pro.md)
- [HUAWEI FreeLace Pro 2](./docs/devices/HUAWEI_FreeLace_Pro_2.md)

If you want to get full support of your headphones, you can [create a Bluetooth-traffic](https://mmk.pw/en/posts/ofb-contribution/)
dump from Ai Life, which will contain all requests and responses that is used by official
app to manage your headset. Then send collected file to me, I'll analyze them and try to
implement their features in OpenFreebuds.
May also work with newer/older devices in same series. If you want to get better compatibility of some model, you could [create Bluetooth traffic dump](https://mmk.pw/en/posts/ofb-contribution/) to help making OpenFreebuds better.

Download & install
-----------------
Expand Down Expand Up @@ -102,8 +101,4 @@ cd scripts/build_debian
./build.sh
```

Build from sources and prey.

TODO: Write this guides

![Extra dialogs preview](docs/preview_2.png)
Binary file added docs/app_0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/app_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/preview_0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions openfreebuds/assets/debug_profiles/huawei_5i.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
"in_ear": "false"
},
"battery": {
"global": 90,
"left": 90,
"right": 90,
"global": 50,
"left": 50,
"right": 65,
"case": 85,
"is_charging": "false"
},
Expand Down
92 changes: 92 additions & 0 deletions openfreebuds/assets/web_static/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>OpenFreebuds</title>
<link rel="stylesheet" href="https://unpkg.com/mvp.css">
</head>
<body style="max-width: 600px; margin: 64px auto">
<h2>
OpenFreebuds RPC
</h2>
<p>
Use this web-server to control earphones from your software
or scripts
</p>
<h3>
Quick actions
</h3>
<p>
Right-click on required action link and copy their URL. So you can trigger
them from any app or script, for exmaple, via curl:
</p>
<pre><code>curl -s http://localhost:19823/mode_cancellation</code></pre>
<h4>Available actions:</h4>
<div id="actions"></div>
<h3>
Direct RPC command execution
</h3>
<pre><code>fetch(
"http://localhost:19823/__rpc__/set_property", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Secret": "SECRET_KEY_IF_ENROLLED",
},
body: JSON.stringify({
"args": ["anc", "mode", "awareness"],
"kwargs": {},
}),
}
)
.then(d => d.json())
.then(r => console.log(r))</code></pre>
<h3>
Remote access
</h3>
<p>
To access this page through other devices in network,
make sure that you're enabled "Remote access" in GUI
settings.
</p>
<p>
If you plan to do that, it's strongly recommended to also
enable secret key verification, and set strong, random key,
to prevent unauthorized access to your devices.
</p>
<script>
function setupShortcutLink(a, out, shortcutName) {
a.href = location.href + shortcutName;
a.innerHTML = `<code style="color: var(--text)">${shortcutName}</code>`;

a.onclick = async (e) => {
e.preventDefault();

out.innerText = "Loading..."
const resp = await fetch(`/${shortcutName}`);
out.innerText = await resp.text();
}
}

async function listActions() {
const resp = await fetch("/list_shortcuts");
const data = await resp.json();
const root = document.getElementById("actions");

for(const shortcutName of data) {
const p = document.createElement("p");
const a = document.createElement("a");
const out = document.createElement("code");
out.style.paddingLeft = "16px";
p.appendChild(a);
p.appendChild(out);
root.appendChild(p);

setupShortcutLink(a, out, shortcutName);
}
}

listActions()
</script>
</body>
</html>
4 changes: 3 additions & 1 deletion openfreebuds/constants.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import os
import sys
from pathlib import Path

from openfreebuds_backend import get_app_storage_path


class OfbEventKind:
STATE_CHANGED = "state_changed"
Expand All @@ -12,3 +13,4 @@ class OfbEventKind:


APP_ROOT = Path(os.path.dirname(os.path.realpath(__file__))).parent
STORAGE_PATH = get_app_storage_path() / "openfreebuds"
1 change: 0 additions & 1 deletion openfreebuds/driver/generic/base.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import json
from typing import Optional

from openfreebuds.constants import OfbEventKind
Expand Down
25 changes: 11 additions & 14 deletions openfreebuds/driver/huawei/driver/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,6 @@ async def _loop_recv(self, reader: asyncio.StreamReader):
while True:
try:
await self.__recv_pacakge(reader)
except asyncio.TimeoutError:
pass
except (asyncio.CancelledError, ConnectionResetError, ConnectionAbortedError, OSError):
log.debug(f"Stop recv loop due to connection failure")
return
Expand All @@ -115,18 +113,17 @@ async def _loop_recv(self, reader: asyncio.StreamReader):
await asyncio.sleep(2)

async def __recv_pacakge(self, reader: asyncio.StreamReader):
async with asyncio.timeout(5):
heading = await reader.read(4)
if len(heading) == 0:
log.debug("Got empty package, seems like socked is closed")
raise ConnectionResetError
if heading[0:2] == b"Z\x00":
length = heading[2]
if length < 4:
await reader.read(length)
else:
pkg = heading + await reader.read(length)
await self._handle_raw_pkg(pkg)
heading = await reader.read(4)
if len(heading) == 0:
log.debug("Got empty package, seems like socked is closed")
raise ConnectionResetError
if heading[0:2] == b"Z\x00":
length = heading[2]
if length < 4:
await reader.read(length)
else:
pkg = heading + await reader.read(length)
await self._handle_raw_pkg(pkg)

def _add_on_package_handler(self, handler):
for pkg_id in handler.commands:
Expand Down
2 changes: 1 addition & 1 deletion openfreebuds/driver/huawei/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def to_table_string(self):
out += "\n"
return out

def find_param(self, *args, **kwargs) -> bytes:
def find_param(self, *args) -> bytes:
"""
Get parameter value by one of provided types
"""
Expand Down
3 changes: 0 additions & 3 deletions openfreebuds/driver/huawei/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
from array import array


def build_table_row(ln, val, description_table=None):
if description_table is not None and val in description_table:
val = f"{val} ({description_table[val]})"
Expand Down
21 changes: 17 additions & 4 deletions openfreebuds/main.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,40 @@
import asyncio
import json
import logging

from aiohttp import ClientConnectorError

from openfreebuds.manager.standalone import OfbManager
from openfreebuds.constants import STORAGE_PATH
from openfreebuds.manager.main import OfbManager
from openfreebuds.utils.logger import create_logger
from openfreebuds.utils.stupid_rpc import test_online, run_rpc_server

log = create_logger("OpenFreebudsDaemon")
# await openfreebuds.start("HUAWEI FreeBuds 5i" "DC:D4:44:28:6F:AE")


async def create():
async def create(kwargs: dict = None):
"""
Create a new manager instance, or connect to exiting if it's already spawned
"""
instance = OfbManager()

if kwargs is None:
# noinspection PyBroadException
try:
rpc_config_path = STORAGE_PATH / "openfreebuds_rpc.json"
log.info(f"Will load RPC config from {rpc_config_path}")
with open(rpc_config_path, "r") as f:
kwargs = json.load(f)
except Exception:
kwargs = {}

instance.rpc_config = kwargs

try:
await test_online()
instance.role = "client"
except ClientConnectorError:
instance.server_task = asyncio.create_task(run_rpc_server(instance, ""))
instance.server_task = asyncio.create_task(run_rpc_server(instance, **kwargs))

return instance

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import asyncio
from asyncio import Task
from contextlib import asynccontextmanager
from contextlib import asynccontextmanager, suppress
from typing import Optional

import openfreebuds_backend
from aiohttp.web_routedef import RouteTableDef

from openfreebuds import webserver
from openfreebuds.constants import OfbEventKind
from openfreebuds.driver import DEVICE_TO_DRIVER_MAP
from openfreebuds.driver.generic import OfbDriverGeneric
Expand All @@ -27,6 +29,7 @@ def __init__(self):

self._state = IOpenFreebuds.STATE_STOPPED # type: int
self.role: str = "standalone"
self.rpc_config: dict = {}
self.server_task: Task | None = None

@rpc
Expand Down Expand Up @@ -127,10 +130,8 @@ async def _set_paused(self, value: bool):
await self._driver.stop()

async def _mainloop(self):
try:
with suppress(asyncio.CancelledError):
await self._mainloop_inner()
except asyncio.CancelledError:
pass

async def _mainloop_inner(self):
log.debug(f"Started mainloop task")
Expand Down Expand Up @@ -169,6 +170,9 @@ async def _mainloop_inner(self):

await asyncio.sleep(2)

def on_rpc_server_setup(self, routes: RouteTableDef, secret: Optional[str]):
webserver.setup_routes(self, routes, secret)

async def _set_state(self, new_state: int):
if self._state == new_state:
return
Expand Down
1 change: 1 addition & 0 deletions openfreebuds/utils/event_bus.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def include_subscription(self, callback_id: str, subscription):
self._child_subs[callback_id].cancel()

async def _handler():
# noinspection PyProtectedMember
queue = subscription._new_queue(callback_id, None)
while True:
await self.send_message(*(await queue.get()))
Expand Down
Loading

0 comments on commit 52d0192

Please sign in to comment.