diff --git a/src/sender/soundsender.py b/src/sender/soundsender.py index 66c97a6..ab2d50b 100644 --- a/src/sender/soundsender.py +++ b/src/sender/soundsender.py @@ -5,8 +5,9 @@ import logging from datetime import datetime import requests from .abcsender import AbcSender -from utils import config +from utils import config, LoopingTimer from urllib.parse import urljoin +import time """ Send a sound sample @@ -23,6 +24,87 @@ class SoundSender(AbcSender): SoundSender class, responsible for sending sound samples to the cloud. """ + 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. @@ -32,14 +114,24 @@ class SoundSender(AbcSender): """ if decision: files = { - "file": (os.path.basename(value), open(value, 'rb').read(), 'audio/wave', - {'Content-length': os.path.getsize(value)}), + "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") + None, + json.dumps({'date': datetime.now().isoformat(), 'device_id': config.DEVICE_ID}), + "application/json" + ) } - r = requests.post(urljoin(config.API_URL, config.FEED_TYPE), files=files) + 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() diff --git a/src/utils/config.py b/src/utils/config.py index f385f81..c119b52 100644 --- a/src/utils/config.py +++ b/src/utils/config.py @@ -37,7 +37,12 @@ DISABLE_AI = os.environ.get("DISABLE_AI", 'no').lower() in ['yes', '1', 'true'] PLATFORM = os.environ.get("PLATFORM", "raspberry") -FEED_TYPE = os.environ.get("FEED_TYPE", "input") # probably the worst naming the type of the accepting service. +FEED_TYPE = os.environ.get("FEED_TYPE", "input") # probably the worst naming the type of the accepting service. if FEED_TYPE not in ['input', 'filter']: raise ValueError("FEED_TYPE must be either input or filter") + +# Skip fetching /assignment endpoint +NO_AUTODISCOVER = os.environ.get("NO_AUTODISCOVER", 'no').lower() in ['yes', '1', 'true'] + +REASSIGN_INTERVAL = int(os.environ.get("REASSIGN_INTERVAL", 120))