mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-05-01 10:35:46 +00:00
add example daemon read/write programs
This commit is contained in:
31
README.md
31
README.md
@@ -10,6 +10,12 @@ sudo apt install python3 python3-pip
|
|||||||
pip3 install pybluez
|
pip3 install pybluez
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you want to run it as a daemon (Refer to the [Daemonizing](#Daemonizing) section), you will need to install the `python-daemon` package.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip3 install python-daemon
|
||||||
|
```
|
||||||
|
|
||||||
## 2. Clone the repository
|
## 2. Clone the repository
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -36,3 +42,28 @@ python3 main.py
|
|||||||
```bash
|
```bash
|
||||||
python3 standalone.py
|
python3 standalone.py
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Daemonizing
|
||||||
|
If you want to run this as a deamon, you can use the `airpods_daemon.py` script. This creates a standard UNIX socket at `/tmp/airpods_daemon.sock` and listens for commands (and sends battery/in-ear info, soon!).
|
||||||
|
|
||||||
|
You can run it as follows:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 airpods_daemon.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sending data to the daemon
|
||||||
|
You can send data to the daemon using the `send_data.py` script. Since it's a standard UNIX socket, you can send data to it using any programming language that supports UNIX sockets.
|
||||||
|
|
||||||
|
This package includes a demo script that sends a command to turn off the ANC. You can run it as follows:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 example_daemon_send.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Reading data from the daemon
|
||||||
|
Youhcan listen to the daemon's output by running the `example_daemon_read.py` script. This script listens to the UNIX socket and prints the data it receives. Currenty, it only prints the battery percentage and the in-ear status.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 example_daemon_read.py
|
||||||
|
```
|
||||||
@@ -6,6 +6,9 @@ import logging
|
|||||||
from aln import Connection, enums
|
from aln import Connection, enums
|
||||||
from aln.Notifications import Notifications
|
from aln.Notifications import Notifications
|
||||||
import os
|
import os
|
||||||
|
import pickle
|
||||||
|
|
||||||
|
connection = None
|
||||||
|
|
||||||
AIRPODS_MAC = '28:2D:7F:C2:05:5B'
|
AIRPODS_MAC = '28:2D:7F:C2:05:5B'
|
||||||
SOCKET_PATH = '/tmp/airpods_daemon.sock'
|
SOCKET_PATH = '/tmp/airpods_daemon.sock'
|
||||||
@@ -17,24 +20,63 @@ running = True
|
|||||||
|
|
||||||
# Configure logging to write to a file
|
# Configure logging to write to a file
|
||||||
logging.basicConfig(filename=LOG_FILE, level=logging.DEBUG, format='%(asctime)s %(levelname)s : %(message)s')
|
logging.basicConfig(filename=LOG_FILE, level=logging.DEBUG, format='%(asctime)s %(levelname)s : %(message)s')
|
||||||
|
# logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)s : %(message)s')
|
||||||
|
|
||||||
def handle_client(connection, client_socket):
|
def handle_client(connection, client_socket):
|
||||||
"""Handle client requests by forwarding all received data to aln.Connection."""
|
"""Handle client requests by forwarding all received data to aln.Connection, send data back to the client."""
|
||||||
while running:
|
|
||||||
try:
|
|
||||||
data = client_socket.recv(1024) # Receive data in bytes
|
|
||||||
if not data:
|
|
||||||
break # Client disconnected
|
|
||||||
|
|
||||||
# Forward the raw data to aln.Connection
|
def send_status():
|
||||||
connection.send(data)
|
while running:
|
||||||
logging.info(f'Forwarded data: {data}')
|
try:
|
||||||
|
data = globals().get("battery")
|
||||||
|
if data:
|
||||||
|
if not client_socket or not isinstance(client_socket, socket.socket):
|
||||||
|
logging.error("Invalid client socket")
|
||||||
|
break
|
||||||
|
logging.info(f'Sending battery status: {data}')
|
||||||
|
client_socket.send(pickle.dumps(data))
|
||||||
|
logging.info(f'Sent battery status: {data}')
|
||||||
|
globals()["battery"] = None
|
||||||
|
|
||||||
except Exception as e:
|
data = globals().get("earDetection")
|
||||||
logging.error(f"Error handling client: {e}")
|
if data:
|
||||||
break
|
if not client_socket or not isinstance(client_socket, socket.socket):
|
||||||
|
logging.error("Invalid client socket")
|
||||||
|
break
|
||||||
|
logging.info(f'Sending ear detection status: {data}')
|
||||||
|
client_socket.send(pickle.dumps(data))
|
||||||
|
logging.info(f'Sent ear detection status: {data}')
|
||||||
|
globals()["earDetection"] = None
|
||||||
|
except socket.error as e:
|
||||||
|
logging.error(f"Socket error sending status: {e}")
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error sending status: {e}")
|
||||||
|
break
|
||||||
|
|
||||||
|
def receive_commands():
|
||||||
|
while running:
|
||||||
|
try:
|
||||||
|
data = client_socket.recv(1024)
|
||||||
|
if not data:
|
||||||
|
break
|
||||||
|
logging.info(f'Received command: {data}')
|
||||||
|
connection.send(data)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error receiving command: {e}")
|
||||||
|
break
|
||||||
|
|
||||||
|
# Start two threads to handle sending and receiving data
|
||||||
|
send_thread = threading.Thread(target=send_status)
|
||||||
|
send_thread.start()
|
||||||
|
receive_thread = threading.Thread(target=receive_commands)
|
||||||
|
receive_thread.start()
|
||||||
|
|
||||||
|
send_thread.join()
|
||||||
|
receive_thread.join()
|
||||||
|
|
||||||
client_socket.close()
|
client_socket.close()
|
||||||
|
logging.info("Client socket closed")
|
||||||
|
|
||||||
def start_socket_server(connection):
|
def start_socket_server(connection):
|
||||||
"""Start a UNIX domain socket server."""
|
"""Start a UNIX domain socket server."""
|
||||||
@@ -42,8 +84,6 @@ def start_socket_server(connection):
|
|||||||
|
|
||||||
# Set up the socket
|
# Set up the socket
|
||||||
server_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
server_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
|
|
||||||
# Bind the socket to the path
|
|
||||||
try:
|
try:
|
||||||
server_socket.bind(SOCKET_PATH)
|
server_socket.bind(SOCKET_PATH)
|
||||||
except OSError:
|
except OSError:
|
||||||
@@ -88,27 +128,27 @@ def stop_daemon(signum, frame):
|
|||||||
|
|
||||||
def notification_handler(notification_type: int):
|
def notification_handler(notification_type: int):
|
||||||
global connection
|
global connection
|
||||||
|
|
||||||
logging.debug(f"Received notification: {notification_type}")
|
logging.debug(f"Received notification: {notification_type}")
|
||||||
if notification_type == Notifications.BATTERY_UPDATED:
|
if notification_type == Notifications.BATTERY_UPDATED:
|
||||||
logger = logging.getLogger("Battery Status")
|
logger = logging.getLogger("Battery Status")
|
||||||
for i in connection.notificationListener.BatteryNotification.getBattery():
|
battery = connection.notificationListener.BatteryNotification.getBattery()
|
||||||
|
globals()["battery"] = battery
|
||||||
|
for i in battery:
|
||||||
logger.debug(f'{i.get_component()} - {i.get_status()}: {i.get_level()}')
|
logger.debug(f'{i.get_component()} - {i.get_status()}: {i.get_level()}')
|
||||||
elif notification_type == Notifications.EAR_DETECTION_UPDATED:
|
elif notification_type == Notifications.EAR_DETECTION_UPDATED:
|
||||||
logger = logging.getLogger("In-Ear Status")
|
logger = logging.getLogger("In-Ear Status")
|
||||||
logger.debug(f'{connection.notificationListener.EarDetectionNotification.getEarDetection()}')
|
earDetection = connection.notificationListener.EarDetectionNotification.getEarDetection()
|
||||||
|
globals()["earDetection"] = earDetection
|
||||||
|
logger.debug(earDetection)
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
global running
|
global running
|
||||||
|
|
||||||
# Set up signal handlers to handle termination signals
|
|
||||||
signal.signal(signal.SIGINT, stop_daemon) # Handle Ctrl+C
|
|
||||||
signal.signal(signal.SIGTERM, stop_daemon) # Handle kill signal
|
|
||||||
|
|
||||||
logging.info("Starting AirPods daemon")
|
logging.info("Starting AirPods daemon")
|
||||||
|
|
||||||
# Initialize the connection to the AirPods
|
|
||||||
global connection
|
|
||||||
connection = Connection(AIRPODS_MAC)
|
connection = Connection(AIRPODS_MAC)
|
||||||
|
globals()['connection'] = connection
|
||||||
|
|
||||||
|
# Connect to the AirPods and send the handshake
|
||||||
connection.connect()
|
connection.connect()
|
||||||
connection.send(enums.HANDSHAKE)
|
connection.send(enums.HANDSHAKE)
|
||||||
logging.info("Handshake sent")
|
logging.info("Handshake sent")
|
||||||
@@ -117,6 +157,10 @@ def main():
|
|||||||
# Start the socket server to listen for client connections
|
# Start the socket server to listen for client connections
|
||||||
start_socket_server(connection)
|
start_socket_server(connection)
|
||||||
|
|
||||||
|
# Set up signal handlers to handle termination signals
|
||||||
|
signal.signal(signal.SIGINT, stop_daemon) # Handle Ctrl+C
|
||||||
|
signal.signal(signal.SIGTERM, stop_daemon) # Handle kill signal
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Daemonize the process
|
# Daemonize the process
|
||||||
if os.fork():
|
if os.fork():
|
||||||
|
|||||||
45
example_deamon_read.py
Normal file
45
example_deamon_read.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import socket
|
||||||
|
import pickle
|
||||||
|
from aln.Notifications import Battery
|
||||||
|
|
||||||
|
SOCKET_PATH = "/tmp/airpods_daemon.sock"
|
||||||
|
|
||||||
|
def read():
|
||||||
|
"""Send a command to the daemon via UNIX domain socket."""
|
||||||
|
client_socket = None
|
||||||
|
try:
|
||||||
|
# Create a socket connection to the daemon
|
||||||
|
client_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
|
print("Connecting to daemon...")
|
||||||
|
client_socket.connect(SOCKET_PATH)
|
||||||
|
|
||||||
|
# Receive data
|
||||||
|
while True:
|
||||||
|
d = client_socket.recv(1024)
|
||||||
|
if d:
|
||||||
|
try:
|
||||||
|
data = pickle.loads(d)
|
||||||
|
if isinstance(data, str):
|
||||||
|
print(f"Received data: {data}")
|
||||||
|
elif isinstance(data, list) and all(isinstance(b, Battery.Battery) for b in data):
|
||||||
|
for b in data:
|
||||||
|
print(f"Received battery status: {b.get_component()} is {b.get_status()} at {b.get_level()}%")
|
||||||
|
elif isinstance(data, list) and len(data) == 2 and all(isinstance(i, int) for i in data):
|
||||||
|
print(f"Received ear detection status: Is in-ear? Primary: {data[0] == 0}, Secondary: {data[1] == 0}")
|
||||||
|
else:
|
||||||
|
print(f"Received unknown data: {data}")
|
||||||
|
all(isinstance(b, Battery.Battery) for b in data)
|
||||||
|
except pickle.UnpicklingError as e:
|
||||||
|
print(f"Error deserializing data: {e}")
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error communicating with daemon: {e}")
|
||||||
|
finally:
|
||||||
|
if client_socket:
|
||||||
|
client_socket.close()
|
||||||
|
print("Socket closed")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
read()
|
||||||
4
main.py
4
main.py
@@ -70,10 +70,6 @@ def main():
|
|||||||
# Set up logging
|
# Set up logging
|
||||||
handler = ConsoleHandler()
|
handler = ConsoleHandler()
|
||||||
|
|
||||||
log_format = (
|
|
||||||
"%(asctime)s - %(levelname)s - %(name)s - %(message)s"
|
|
||||||
)
|
|
||||||
|
|
||||||
logging.addLevelName(logging.DEBUG, "\033[1;34m%s\033[1;0m" % logging.getLevelName(logging.DEBUG))
|
logging.addLevelName(logging.DEBUG, "\033[1;34m%s\033[1;0m" % logging.getLevelName(logging.DEBUG))
|
||||||
logging.addLevelName(logging.INFO, "\033[1;32m%s\033[1;0m" % logging.getLevelName(logging.INFO))
|
logging.addLevelName(logging.INFO, "\033[1;32m%s\033[1;0m" % logging.getLevelName(logging.INFO))
|
||||||
logging.addLevelName(logging.WARNING, "\033[1;33m%s\033[1;0m" % logging.getLevelName(logging.WARNING))
|
logging.addLevelName(logging.WARNING, "\033[1;33m%s\033[1;0m" % logging.getLevelName(logging.WARNING))
|
||||||
|
|||||||
Reference in New Issue
Block a user