mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-02-18 15:16:18 +00:00
organize and improve examples
This commit is contained in:
@@ -1,51 +1,56 @@
|
||||
import socket
|
||||
import pickle
|
||||
import json
|
||||
import subprocess
|
||||
from aln.Notifications import Battery
|
||||
import threading
|
||||
import time
|
||||
import os
|
||||
import logging
|
||||
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
|
||||
# Colorful logging
|
||||
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.WARNING, "\033[1;33m%s\033[1;0m" % logging.getLevelName(logging.WARNING))
|
||||
logging.addLevelName(logging.ERROR, "\033[1;31m%s\033[1;0m" % logging.getLevelName(logging.ERROR))
|
||||
logging.addLevelName(logging.CRITICAL, "\033[1;41m%s\033[1;0m" % logging.getLevelName(logging.CRITICAL))
|
||||
|
||||
SOCKET_PATH = "/tmp/airpods_daemon.sock"
|
||||
|
||||
class MediaController:
|
||||
def __init__(self):
|
||||
self.wasMusicPlaying = False
|
||||
self.earStatus = "Both out"
|
||||
self.status = "Stopped"
|
||||
self.stop_thread_event = threading.Event()
|
||||
self.wasMusicPlayingInSingle = False
|
||||
self.wasMusicPlayingInBoth = False
|
||||
self.firstEarOutTime = 0
|
||||
self.stop_thread_event = threading.Event()
|
||||
|
||||
def playMusic(self):
|
||||
print("Playing music")
|
||||
subprocess.call(("playerctl", "play", "--ignore-player", "OnePlus_7"))
|
||||
|
||||
def pauseMusic(self):
|
||||
print("Pausing music")
|
||||
subprocess.call(("playerctl", "pause", "--player", "spotify"))
|
||||
subprocess.call(("playerctl", "pause", "--ignore-player", "OnePlus_7"))
|
||||
|
||||
def isPlaying(self):
|
||||
status = subprocess.check_output(["playerctl", "status", "--player", "spotify"]).decode("utf-8").strip()
|
||||
print(f"Music status: {status}")
|
||||
return status == "Playing"
|
||||
return subprocess.check_output(["playerctl", "status", "--player", "spotify"]).decode("utf-8").strip() == "Playing"
|
||||
|
||||
def handlePlayPause(self, data):
|
||||
primary_status = data[0]
|
||||
secondary_status = data[1]
|
||||
|
||||
print(f"Handle play/pause called with data: {data}, previousStatus: {self.status}, wasMusicPlaying: {self.wasMusicPlaying}")
|
||||
logging.debug(f"Handle play/pause called with data: {data}, previousStatus: {self.earStatus}, wasMusicPlaying: {self.wasMusicPlayingInSingle or self.wasMusicPlayingInBoth}")
|
||||
|
||||
def delayed_action(s):
|
||||
if not self.stop_thread_event.is_set():
|
||||
print("Delayed action")
|
||||
if self.wasMusicPlayingInSingle:
|
||||
self.playMusic()
|
||||
self.wasMusicPlayingInBoth = False
|
||||
elif self.wasMusicPlayingInBoth or s:
|
||||
self.wasMusicPlayingInBoth = True
|
||||
self.wasMusicPlayingInSingle = False
|
||||
print(self.wasMusicPlayingInSingle, self.wasMusicPlayingInBoth)
|
||||
|
||||
if primary_status and secondary_status:
|
||||
if self.earStatus != "Both out":
|
||||
@@ -54,7 +59,6 @@ class MediaController:
|
||||
os.system("pacmd set-card-profile bluez_card.28_2D_7F_C2_05_5B off")
|
||||
if self.earStatus == "Only one in":
|
||||
if self.firstEarOutTime != 0 and time.time() - self.firstEarOutTime < 0.3:
|
||||
print("Only one in called with both out")
|
||||
self.wasMusicPlayingInSingle = True
|
||||
self.wasMusicPlayingInBoth = True
|
||||
self.stop_thread_event.set()
|
||||
@@ -106,7 +110,7 @@ def read():
|
||||
try:
|
||||
# Create a socket connection to the daemon
|
||||
client_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
print("Connecting to daemon...")
|
||||
logging.info("Connecting to daemon...")
|
||||
client_socket.connect(SOCKET_PATH)
|
||||
|
||||
media_controller = MediaController()
|
||||
@@ -116,28 +120,21 @@ def read():
|
||||
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}")
|
||||
media_controller.handlePlayPause(data)
|
||||
else:
|
||||
print(f"Received unknown data: {data}")
|
||||
except pickle.UnpicklingError as e:
|
||||
print(f"Error deserializing data: {e}")
|
||||
data: dict = json.loads(d.decode('utf-8'))
|
||||
if data["type"] == "ear_detection":
|
||||
logging.debug(f"Ear detection: {data['primary']} - {data['secondary']}")
|
||||
media_controller.handlePlayPause([data['primary'], data['secondary']])
|
||||
except json.JSONDecodeError as e:
|
||||
logging.error(f"Error deserializing data: {e}")
|
||||
else:
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error communicating with daemon: {e}")
|
||||
logging.error(f"Error communicating with daemon: {e}")
|
||||
finally:
|
||||
if client_socket:
|
||||
client_socket.close()
|
||||
print("Socket closed")
|
||||
logging.warning("Socket closed")
|
||||
|
||||
if __name__ == "__main__":
|
||||
read()
|
||||
@@ -1,5 +1,5 @@
|
||||
import socket
|
||||
import pickle
|
||||
import json
|
||||
from aln.Notifications import Battery
|
||||
|
||||
SOCKET_PATH = "/tmp/airpods_daemon.sock"
|
||||
@@ -18,18 +18,15 @@ def read():
|
||||
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}")
|
||||
data: dict = json.loads(d.decode('utf-8'))
|
||||
if data["type"] == "battery":
|
||||
for b in data.keys():
|
||||
print(f"Received battery status: {b} - {data[b]}")
|
||||
elif data["type"] == "ear_detection":
|
||||
print(f"Ear detection: {data['primary']} - {data['secondary']}")
|
||||
else:
|
||||
print(f"Received unknown data: {data}")
|
||||
all(isinstance(b, Battery.Battery) for b in data)
|
||||
except pickle.UnpicklingError as e:
|
||||
print(f"Received data: {data}")
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"Error deserializing data: {e}")
|
||||
else:
|
||||
break
|
||||
|
||||
205
examples/daemon/start.py
Normal file
205
examples/daemon/start.py
Normal file
@@ -0,0 +1,205 @@
|
||||
import socket
|
||||
import threading
|
||||
import signal
|
||||
import sys
|
||||
import logging
|
||||
from aln import Connection, enums
|
||||
from aln.Notifications import Notifications
|
||||
import os
|
||||
from aln.Notifications.Battery import Battery
|
||||
import bluetooth
|
||||
|
||||
connection = None
|
||||
|
||||
AIRPODS_MAC = '28:2D:7F:C2:05:5B'
|
||||
SOCKET_PATH = '/tmp/airpods_daemon.sock'
|
||||
LOG_FOLDER = '.'
|
||||
LOG_FILE = os.path.join(LOG_FOLDER, 'airpods_daemon.log')
|
||||
|
||||
# Global flag to control the server loop
|
||||
running = True
|
||||
|
||||
# Configure logging to write to a file
|
||||
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')
|
||||
|
||||
from json import JSONEncoder
|
||||
|
||||
def handle_client(connection, client_socket):
|
||||
"""Handle client requests by forwarding all received data to aln.Connection, send data back to the client."""
|
||||
|
||||
def send_status():
|
||||
while running:
|
||||
try:
|
||||
for notif_key in list(globals().keys()):
|
||||
if notif_key.startswith("notif_"):
|
||||
data = globals().get(notif_key)
|
||||
if data:
|
||||
if notif_key == "notif_battery":
|
||||
data: list[Battery] = data
|
||||
batteryJSON = {"type": "battery"}
|
||||
for i in data:
|
||||
batteryJSON[i.get_component()] = {
|
||||
"status": i.get_status(),
|
||||
"level": i.get_level()
|
||||
}
|
||||
data: str = JSONEncoder().encode(batteryJSON)
|
||||
elif notif_key == "notif_ear_detection":
|
||||
data: list[int] = data
|
||||
earDetectionJSON = {
|
||||
"type": "ear_detection",
|
||||
"primary": data[0],
|
||||
"secondary": data[1]
|
||||
}
|
||||
data: str = JSONEncoder().encode(earDetectionJSON)
|
||||
else:
|
||||
logging.warning(f"Unhandled notification type: {notif_key}")
|
||||
continue
|
||||
|
||||
if not client_socket or not isinstance(client_socket, socket.socket):
|
||||
logging.error("Invalid client socket")
|
||||
break
|
||||
logging.info(f'Sending {notif_key} status: {data}')
|
||||
client_socket.sendall(data.encode('utf-8'))
|
||||
logging.info(f'Sent {notif_key} status: {data}')
|
||||
globals()[notif_key] = 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()
|
||||
logging.info("Client socket closed")
|
||||
|
||||
def start_socket_server(connection):
|
||||
"""Start a UNIX domain socket server."""
|
||||
global running
|
||||
|
||||
# Set up the socket
|
||||
server_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
try:
|
||||
server_socket.bind(SOCKET_PATH)
|
||||
except OSError:
|
||||
logging.error(f"Socket already in use or unavailable: {SOCKET_PATH}")
|
||||
sys.exit(1)
|
||||
|
||||
server_socket.listen(1)
|
||||
logging.info(f"Socket server listening on {SOCKET_PATH}")
|
||||
|
||||
while running:
|
||||
try:
|
||||
client_socket, _ = server_socket.accept()
|
||||
logging.info("Client connected")
|
||||
|
||||
# Handle the client connection in a separate thread
|
||||
client_thread = threading.Thread(target=handle_client, args=(connection, client_socket))
|
||||
client_thread.start()
|
||||
except Exception as e:
|
||||
logging.error(f"Error accepting connection: {e}")
|
||||
|
||||
# Close the server socket when stopped
|
||||
server_socket.close()
|
||||
logging.info("Socket server stopped")
|
||||
|
||||
def stop_daemon(signum, frame):
|
||||
"""Signal handler to stop the daemon."""
|
||||
global running
|
||||
logging.info("Received termination signal. Stopping daemon...")
|
||||
running = False # Set running flag to False to stop the loop
|
||||
|
||||
# Close the socket gracefully by removing the file path
|
||||
try:
|
||||
socket.socket(socket.AF_UNIX, socket.SOCK_STREAM).connect(SOCKET_PATH)
|
||||
except socket.error:
|
||||
pass
|
||||
finally:
|
||||
# Remove the socket file
|
||||
if os.path.exists(SOCKET_PATH):
|
||||
os.remove(SOCKET_PATH)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
def notification_handler(notification_type: int):
|
||||
global connection
|
||||
|
||||
logging.debug(f"Received notification: {notification_type}")
|
||||
if notification_type == Notifications.BATTERY_UPDATED:
|
||||
logger = logging.getLogger("Battery Status")
|
||||
battery = connection.notificationListener.BatteryNotification.getBattery()
|
||||
globals()["notif_battery"] = battery
|
||||
for i in battery:
|
||||
logger.debug(f'{i.get_component()} - {i.get_status()}: {i.get_level()}')
|
||||
elif notification_type == Notifications.EAR_DETECTION_UPDATED:
|
||||
logger = logging.getLogger("In-Ear Status")
|
||||
earDetection = connection.notificationListener.EarDetectionNotification.getEarDetection()
|
||||
globals()["notif_ear_detection"] = earDetection
|
||||
logger.debug(earDetection)
|
||||
|
||||
def main():
|
||||
global running
|
||||
logging.info("Starting AirPods daemon")
|
||||
connection = Connection(AIRPODS_MAC)
|
||||
globals()['connection'] = connection
|
||||
|
||||
# Connect to the AirPods and send the handshake
|
||||
try:
|
||||
connection.connect()
|
||||
except bluetooth.btcommon.BluetoothError as e:
|
||||
logging.error(f"Failed to connect to {AIRPODS_MAC}: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
connection.send(enums.HANDSHAKE)
|
||||
logging.info("Handshake sent")
|
||||
connection.initialize_notifications(notification_handler)
|
||||
|
||||
# Start the socket server to listen for client connections
|
||||
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__":
|
||||
# Daemonize the process
|
||||
if os.fork():
|
||||
sys.exit()
|
||||
|
||||
os.setsid()
|
||||
|
||||
if os.fork():
|
||||
sys.exit()
|
||||
|
||||
sys.stdout.flush()
|
||||
sys.stderr.flush()
|
||||
|
||||
with open('/dev/null', 'r') as devnull:
|
||||
os.dup2(devnull.fileno(), sys.stdin.fileno())
|
||||
|
||||
with open(LOG_FILE, 'a+') as logfile:
|
||||
os.dup2(logfile.fileno(), sys.stdout.fileno())
|
||||
os.dup2(logfile.fileno(), sys.stderr.fileno())
|
||||
|
||||
main()
|
||||
Reference in New Issue
Block a user