Open thorbeenwiedemann opened 5 months ago
Fixed it so far
`import appdaemon.plugins.hass.hassapi as hass import requests from requests.auth import HTTPBasicAuth from requests.exceptions import RequestException import subprocess from io import BytesIO from time import sleep, time
class DoorbirdException(Exception): """An exception for doorbirds"""
class Doorbird: CHUNK_SIZE = 4096 # Moderate chunk size in bytes RATE_LIMIT = 8192 # Bytes per second (8KB per second) CHUNK_INTERVAL = 0.5 # Interval in seconds (500 ms for larger chunks)
def __init__(self, device_ip, username, password):
"""
Connect to a Doorbird
Args:
device_ip (str): Doorbird device IP address.
username (str): Doorbird HTTP username.
password (str): Doorbird HTTP password.
Raises:
DoorbirdException
"""
self.device_ip = device_ip
self.username = username
self.password = password
self.session_id = self._get_session_id()
def _get_session_id(self):
"""
Get session ID from Doorbird device.
Raises:
DoorbirdException: If unable to obtain session ID.
"""
get_session_url = f"http://{self.device_ip}/bha-api/getsession.cgi"
auth = (self.username, self.password)
try:
response = requests.get(get_session_url, auth=auth)
response.raise_for_status()
data = response.json()
return data["BHA"]["SESSIONID"]
except RequestException as e:
raise DoorbirdException(f"Failed to obtain session ID: {e}")
def _convert_audio(self, input_file):
"""
Convert audio to 8000Hz mono PCM mu-law format.
Args:
input_file (str): Path to the input audio file.
Returns:
bytes: Converted audio data.
"""
try:
process = subprocess.run(
[
'ffmpeg', '-y', '-i', input_file, '-ar', '8000', '-ac', '1', '-f', 'mulaw', 'pipe:1'
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=True
)
return process.stdout
except subprocess.CalledProcessError as e:
raise DoorbirdException(f"FFmpeg conversion failed: {e.stderr.decode()}")
def _generate_audio_chunks(self, audio_data):
"""
Generator function to yield chunks of audio data from a file.
Doorbird is rate-limited to 8KB per second.
Args:
audio_data: Converted audio data
Yields:
bytes: Chunks of audio data.
"""
stream = BytesIO(audio_data)
next_chunk_time = time()
while True:
start_time = time()
chunk = stream.read(self.CHUNK_SIZE)
if not chunk:
break
yield chunk
elapsed_time = time() - start_time
next_chunk_time += self.CHUNK_INTERVAL
sleep_time = max(0, next_chunk_time - time())
self._log_timing(self.CHUNK_SIZE, elapsed_time, sleep_time)
sleep(sleep_time)
def _log_timing(self, chunk_size, elapsed_time, sleep_time):
"""
Log the timing details for each chunk.
Args:
chunk_size (int): Size of the chunk.
elapsed_time (float): Time taken to process the chunk.
sleep_time (float): Time to sleep before sending the next chunk.
"""
print(f"Chunk Size: {chunk_size}, Elapsed Time: {elapsed_time:.6f}, Sleep Time: {sleep_time:.6f}")
def send_audio(self, audio_url):
"""
Send audio to Doorbird device.
Args:
audio_url (str): URL of the audio file.
Raises:
DoorbirdException: If any step in the process fails.
"""
try:
# Download the audio file
audio_response = requests.get(audio_url)
audio_response.raise_for_status()
audio_file_path = '/config/downloaded_audio.mp3'
with open(audio_file_path, 'wb') as audio_file:
audio_file.write(audio_response.content)
# Convert the audio file
audio_data = self._convert_audio(audio_file_path)
# Transmit the audio file in chunks
audio_transmit_url = f"http://{self.device_ip}/bha-api/audio-transmit.cgi?sessionid={self.session_id}"
auth = HTTPBasicAuth(self.username, self.password)
def audio_stream():
for chunk in self._generate_audio_chunks(audio_data):
yield chunk
response = requests.post(
audio_transmit_url,
headers={"Content-Type": "audio/basic", "Connection": "Keep-Alive", "Cache-Control": "no-cache"},
data=audio_stream(),
auth=auth,
timeout=60
)
response.raise_for_status()
print("Audio transmission completed successfully.")
except RequestException as e:
raise DoorbirdException(f"Failed to send audio: {e}")
class DoorbirdAudio(hass.Hass): """AppDaemon app to handle Doorbird audio events."""
def initialize(self):
"""Initialize the AppDaemon app."""
self.listen_event(self.doorbird_audio, "doorbird_audio")
def doorbird_audio(self, event_name, data, kwargs):
"""Handle Doorbird audio event."""
try:
self.log(f"Received event: {event_name} with data: {data}")
doorbird = Doorbird(data["device_ip"], data["username"], data["password"])
doorbird.send_audio(data["audio_url"])
self.log("Audio transmission completed successfully.")
except DoorbirdException as e:
self.log(f"Failed to send audio: {e}")
`
Can you please explain what you have changed?
Sound output is really choppy, no chance to understand a single bit on the doorbird.
Tried multiple files mp3/wav and preconverted into ulaw 8khz aswell
Please see the AppDaemon Log attached
[13:09:08] INFO: Starting AppDaemon... s6-rc: info: service legacy-services successfully started 2024-05-27 13:09:10.149301 INFO AppDaemon: AppDaemon Version 4.4.2 starting 2024-05-27 13:09:10.149421 INFO AppDaemon: Python version is 3.11.9 2024-05-27 13:09:10.149524 INFO AppDaemon: Configuration read from: /config/appdaemon.yaml 2024-05-27 13:09:10.149628 INFO AppDaemon: Added log: AppDaemon 2024-05-27 13:09:10.149736 INFO AppDaemon: Added log: Error 2024-05-27 13:09:10.149839 INFO AppDaemon: Added log: Access 2024-05-27 13:09:10.149941 INFO AppDaemon: Added log: Diag 2024-05-27 13:09:10.244897 INFO AppDaemon: Loading Plugin HASS using class HassPlugin from module hassplugin 2024-05-27 13:09:10.339916 INFO HASS: HASS Plugin Initializing 2024-05-27 13:09:10.340002 WARNING HASS: ha_url not found in HASS configuration - module not initialized 2024-05-27 13:09:10.340069 INFO HASS: HASS Plugin initialization complete 2024-05-27 13:09:10.340260 INFO AppDaemon: Initializing HTTP 2024-05-27 13:09:10.340410 INFO AppDaemon: Using 'ws' for event stream 2024-05-27 13:09:10.342993 INFO AppDaemon: Starting API 2024-05-27 13:09:10.344546 INFO AppDaemon: Starting Admin Interface 2024-05-27 13:09:10.344722 INFO AppDaemon: Starting Dashboards 2024-05-27 13:09:10.352267 INFO HASS: Connected to Home Assistant 2024.5.5 2024-05-27 13:09:10.356045 INFO AppDaemon: App 'hello_world' added 2024-05-27 13:09:10.356712 INFO AppDaemon: App 'doorbird_audio' added 2024-05-27 13:09:10.357177 INFO AppDaemon: Found 2 active apps 2024-05-27 13:09:10.357285 INFO AppDaemon: Found 0 inactive apps 2024-05-27 13:09:10.357373 INFO AppDaemon: Found 0 global libraries 2024-05-27 13:09:10.357468 INFO AppDaemon: Starting Apps with 2 workers and 2 pins 2024-05-27 13:09:10.357880 INFO AppDaemon: Running on port 5050 2024-05-27 13:09:10.384307 INFO HASS: Evaluating startup conditions 2024-05-27 13:09:10.388683 INFO HASS: Startup condition met: hass state=RUNNING 2024-05-27 13:09:10.388926 INFO HASS: All startup conditions met 2024-05-27 13:09:10.421501 INFO AppDaemon: Got initial state from namespace default 2024-05-27 13:09:12.361120 INFO AppDaemon: Scheduler running in realtime 2024-05-27 13:09:12.362035 INFO AppDaemon: Adding /config/apps to module import path 2024-05-27 13:09:12.362838 INFO AppDaemon: Loading App Module: /config/apps/doorbird_audio.py 2024-05-27 13:09:12.396271 INFO AppDaemon: Loading App Module: /config/apps/hello.py 2024-05-27 13:09:12.396822 INFO AppDaemon: Loading app hello_world using class HelloWorld from module hello 2024-05-27 13:09:12.397383 INFO AppDaemon: Loading app doorbird_audio using class DoorbirdAudio from module doorbird_audio 2024-05-27 13:09:12.398044 INFO AppDaemon: Calling initialize() for hello_world 2024-05-27 13:09:12.422741 INFO hello_world: Hello from AppDaemon 2024-05-27 13:09:12.423230 INFO hello_world: You are now ready to run Apps! 2024-05-27 13:09:12.423692 INFO AppDaemon: Calling initialize() for doorbird_audio 2024-05-27 13:09:12.424552 INFO AppDaemon: App initialization complete 2024-05-27 13:09:51.088991 WARNING doorbird_audio: ------------------------------------------------------------ 2024-05-27 13:09:51.089135 WARNING doorbird_audio: Unexpected error in worker for App doorbird_audio: 2024-05-27 13:09:51.089257 WARNING doorbird_audio: Worker Ags: {'id': '61125763d71a48fc9732bb56e4d94e7c', 'name': 'doorbird_audio', 'objectid': 'c0d6aa6e93fb4c4a8b2e7141909ff011', 'type': 'event', 'event': 'doorbird_audio', 'function': <bound method DoorbirdAudio.doorbird_audio of <doorbird_audio.DoorbirdAudio object at 0x7fc31b40e210>>, 'data': {'device_ip': '192.168.1.220', 'username': 'XXXXX', 'password': 'XXXXX', 'audio_url': 'http://192.168.1.114:8123/local/doorbird.mp3', 'metadata': {'origin': 'LOCAL', 'time_fired': '2024-05-27T11:09:37.438571+00:00', 'context': {'id': '01HYWVVNRYM361TH1NWNN170QN', 'parent_id': None, 'user_id': '26262bdb3712457486cf0ee731a79cce'}}}, 'pin_app': True, 'pin_thread': 1, 'kwargs': {'__thread_id': 'thread-1'}} 2024-05-27 13:09:51.089370 WARNING doorbird_audio: ------------------------------------------------------------ 2024-05-27 13:09:51.091348 WARNING doorbird_audio: Traceback (most recent call last): File "/usr/lib/python3.11/site-packages/appdaemon/threading.py", line 1095, in worker funcref(args["event"], data, self.AD.events.sanitize_event_kwargs(app, args["kwargs"])) File "/config/apps/doorbird_audio.py", line 119, in doorbird_audio doorbird.send_audio(data["audio_url"]) File "/config/apps/doorbird_audio.py", line 97, in send_audio with requests.post( ^^^^^^^^^^^^^^ File "/usr/lib/python3.11/site-packages/requests/api.py", line 115, in post return request("post", url, data=data, json=json, kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.11/site-packages/requests/api.py", line 59, in request return session.request(method=method, url=url, kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.11/site-packages/requests/sessions.py", line 587, in request resp = self.send(prep, send_kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.11/site-packages/requests/sessions.py", line 701, in send r = adapter.send(request, kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.11/site-packages/requests/adapters.py", line 531, in send r = low_conn.getresponse() ^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.11/http/client.py", line 1395, in getresponse response.begin() File "/usr/lib/python3.11/http/client.py", line 325, in begin version, status, reason = self._read_status() ^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.11/http/client.py", line 307, in _read_status raise BadStatusLine(line) http.client.BadStatusLine:
2024-05-27 13:09:51.091472 WARNING doorbird_audio: ------------------------------------------------------------ 2024-05-27 13:09:51.092131 WARNING AppDaemon: Excessive time spent in callback 'doorbird_audio() in doorbird_audio', Thread 'thread.thread-1' - now complete after 14.091939 seconds (limit=10)