#!/usr/bin/env python3 import os import json import logging from datetime import datetime import requests from .abcsender import AbcSender from utils import config, LoopingTimer from urllib.parse import urljoin import time from threading import Lock """ Send a sound sample """ __author__ = "@tormakris" __copyright__ = "Copyright 2020, Birbnetes Team" __module_name__ = "soundsender" __version__text__ = "1" class SoundSender(AbcSender): """ SoundSender class, responsible for sending sound samples to the cloud. """ super_global_multi_sender_preventer = Lock() def __init__(self): self._feed_url = None self._reassign_timer = None if not config.NO_AUTODISCOVER: retry = 0 timeout = 30 while True: try: r = requests.post( urljoin(config.API_URL, "/assignment"), json={"device_id": config.DEVICE_ID}, timeout=timeout ) except requests.exceptions.Timeout: logging.warning(f"/assignment timed out after {timeout} sec! Retrying...") continue if r.status_code not in [200, 404]: logging.warning( f"Unexpected status code while acquiring an assignment: {r.status_code}. Retrying..." ) time.sleep(5) continue if r.status_code == 404: logging.info( "/assignment returned 404 error. Assuming missing capability... Defaulting to use API_URL" ) self._feed_url = config.API_URL break if r.json()['hard_default']: retry += 1 if retry < 10: logging.info(f"Still waiting for non hard-default url... Attempt {retry}/10") time.sleep(6) continue else: logging.info(f"Given up waiting for non hard-default url... accepting hard default.") logging.info(f"Assigned to {r.json()['site']}") self._feed_url = r.json()['url'] break self._reassign_timer = LoopingTimer(config.REASSIGN_INTERVAL, lambda: self.reassign()) self._reassign_timer.start() # If the above block failed to assign a feed_url if not self._feed_url: logging.info("Using API_URL as feed url") self._feed_url = config.API_URL def reassign(self): logging.debug("Asking for reassign...") timeout = 30 try: r = requests.post( urljoin(config.API_URL, "/assignment"), json={"device_id": config.DEVICE_ID}, timeout=timeout ) except requests.exceptions.Timeout: logging.warning(f"/assignment timed out after {timeout} sec! Ignoring...") return if r.status_code == 404: logging.debug("/assignment returned 404 status code. Assuming missing capability, and doing nothing...") return if r.status_code != 200: logging.debug(f"/assignment returned {r.status_code} status code. Ignoring...") return logging.debug(f"Received assignment to {r.json()['site']}") if r.json()['url'] != self._feed_url: logging.info(f"Reassigned to {r.json()['site']}!") # Because of GIL and because this is a reference, we don't really have to be careful assigning this self._feed_url = r.json()['url'] def sendvalue(self, value: str, decision: bool) -> None: """ Send a sound sample to the cloud. value: is the file name decision: if true the sample is sent, if false then not :return: """ if decision: files = { "file": ( os.path.basename(value), open(value, 'rb').read(), 'audio/wave', {'Content-length': os.path.getsize(value)} ), "description": ( None, json.dumps({'date': datetime.now().isoformat(), 'device_id': config.DEVICE_ID}), "application/json" ) } with SoundSender.super_global_multi_sender_preventer: r = requests.post(urljoin(self._feed_url, config.FEED_TYPE), files=files) logging.debug(f"Content: {r.content.decode()}") logging.debug(f"Headers: {r.headers}") r.raise_for_status() def __del__(self): if self._reassign_timer: self._reassign_timer.stop()