Added server assisted roaming capability
This commit is contained in:
		@@ -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()
 | 
			
		||||
 
 | 
			
		||||
@@ -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))
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user