Added server assisted roaming capability
This commit is contained in:
		@@ -5,8 +5,9 @@ import logging
 | 
				
			|||||||
from datetime import datetime
 | 
					from datetime import datetime
 | 
				
			||||||
import requests
 | 
					import requests
 | 
				
			||||||
from .abcsender import AbcSender
 | 
					from .abcsender import AbcSender
 | 
				
			||||||
from utils import config
 | 
					from utils import config, LoopingTimer
 | 
				
			||||||
from urllib.parse import urljoin
 | 
					from urllib.parse import urljoin
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
Send a sound sample
 | 
					Send a sound sample
 | 
				
			||||||
@@ -23,6 +24,87 @@ class SoundSender(AbcSender):
 | 
				
			|||||||
    SoundSender class, responsible for sending sound samples to the cloud.
 | 
					    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:
 | 
					    def sendvalue(self, value: str, decision: bool) -> None:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Send a sound sample to the cloud.
 | 
					        Send a sound sample to the cloud.
 | 
				
			||||||
@@ -32,14 +114,24 @@ class SoundSender(AbcSender):
 | 
				
			|||||||
        """
 | 
					        """
 | 
				
			||||||
        if decision:
 | 
					        if decision:
 | 
				
			||||||
            files = {
 | 
					            files = {
 | 
				
			||||||
                "file": (os.path.basename(value), open(value, 'rb').read(), 'audio/wave',
 | 
					                "file": (
 | 
				
			||||||
                         {'Content-length': os.path.getsize(value)}),
 | 
					                    os.path.basename(value),
 | 
				
			||||||
 | 
					                    open(value, 'rb').read(),
 | 
				
			||||||
 | 
					                    'audio/wave',
 | 
				
			||||||
 | 
					                    {'Content-length': os.path.getsize(value)}
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
                "description": (
 | 
					                "description": (
 | 
				
			||||||
                    None, json.dumps({'date': datetime.now().isoformat(), 'device_id': config.DEVICE_ID}),
 | 
					                    None,
 | 
				
			||||||
                    "application/json")
 | 
					                    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"Content: {r.content.decode()}")
 | 
				
			||||||
            logging.debug(f"Headers: {r.headers}")
 | 
					            logging.debug(f"Headers: {r.headers}")
 | 
				
			||||||
            r.raise_for_status()
 | 
					            r.raise_for_status()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __del__(self):
 | 
				
			||||||
 | 
					        if self._reassign_timer:
 | 
				
			||||||
 | 
					            self._reassign_timer.stop()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -41,3 +41,8 @@ FEED_TYPE = os.environ.get("FEED_TYPE", "input") # probably the worst naming the
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
if FEED_TYPE not in ['input', 'filter']:
 | 
					if FEED_TYPE not in ['input', 'filter']:
 | 
				
			||||||
    raise ValueError("FEED_TYPE must be either input or 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))
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user