wyzeapy.services.update_manager API documentation

# wyzeapy.services.update_manager

View Source

      1from asyncio import sleep
      2from dataclasses import dataclass, field
      3from heapq import heappush, heappop
      4from typing import Any
      5from math import ceil
      6from ..types import Device
      7import logging
      8import threading
      9
     10_LOGGER = logging.getLogger(__name__)
     11
     12INTERVAL = 300
     13MAX_SLOTS = 225
     14
     15@dataclass(order=True)
     16class DeviceUpdater(object):
     17    device: Device=field(compare=False)  # The device that will be updated
     18    service: Any=field(compare=False)
     19    update_in: int # A countdown to zero that will tell the priority queue that it is time to update this device
     20    updates_per_interval: int=field(compare=False) # The number of updates that should happen every 5 minutes
     21
     22    def __init__(self, service, device: Device, update_interval: int):
     23        """
     24        This function initializes a DeviceUpdater object
     25        :param service: The WyzeApy service connected to a device
     26        :param device: A WyzeApy device that needs to be in the update que
     27        :param update_interval: How many seconds should be targeted between updates. **Note this value may shift based on 1 call per sec and load.
     28        """
     29        self.service = service
     30        self.device = device
     31        self.update_in = 0  # Always initialize at 0 so that we get the first update ASAP. The items will shift based on priority after this.
     32        self.updates_per_interval = ceil(INTERVAL / update_interval)
     33
     34    async def update(self, mutex: threading.Lock):
     35        # We only want to update if the update_in counter is zero
     36        if self.update_in <= 0:
     37            _LOGGER.debug("Updating device: " + self.device.nickname)
     38            # Acquire the mutex before making the async call
     39            mutex.acquire()
     40            try:
     41                # Get the updated info for the device from Wyze's API
     42                self.device = await self.service.update(self.device)
     43                # Callback to provide the updated info to the subscriber
     44                self.device.callback_function(self.device)
     45            except:
     46                _LOGGER.exception("Unknown error happened during updating device info")
     47            finally:
     48                # Release the mutex after the async call
     49                mutex.release()
     50            # Once it reaches zero and we update the device we want to reset the update_in counter
     51            self.update_in = ceil(INTERVAL / self.updates_per_interval)
     52        else:
     53            # Don't update and instead just reduce the counter by 1
     54            self.tick_tock()
     55
     56    def tick_tock(self):
     57        # Every time we update a device we want to reduce the update_in counter so that it will get closer to updating
     58        if self.update_in > 0:
     59            self.update_in -= 1
     60
     61    def delay(self):
     62        # This should be called to reduce the number of updates per interval so that new devices can be added into the queue fairly
     63        if self.updates_per_interval > 1:
     64            self.updates_per_interval -= 1
     65
     66class UpdateManager:
     67    # Holds all the logic for when to update the devices
     68    updaters = []
     69    removed_updaters = []
     70    mutex = threading.Lock()  # Create a lock object as a class variable
     71
     72    def check_if_removed(self, updater: DeviceUpdater):
     73        for item in self.removed_updaters:
     74            if updater is item:
     75                return True
     76        return False
     77
     78    # This function should be called once every second
     79    async def update_next(self):
     80        # If there are no updaters in the queue we don't need to do anything
     81        if (len(self.updaters) == 0):
     82            _LOGGER.debug("No devices to update in queue")
     83            return
     84        while True:
     85            # First we get the next updater off the queue
     86            updater = heappop(self.updaters)
     87            # if the updater has been removed, pop the next and clear it from the removed updaters
     88            if self.removed_updaters:
     89                while self.check_if_removed(updater):
     90                    self.removed_updaters.remove(updater)
     91                    updater = heappop(self.updaters)
     92            # We then reduce the counter for all the other updaters
     93            self.tick_tock()
     94            # Then we update the target device
     95            await updater.update(self.mutex) # It will only update if it is time for it to update. Otherwise it just reduces its update_in counter.
     96            # Then we put it back at the end of the queue. Or the front again if it wasn't ready to update
     97            heappush(self.updaters, updater)
     98            await sleep(1)
     99
    100
    101    def filled_slots(self):
    102        # This just returns the number of available slots
    103        current_slots = 0
    104        for a_updater in self.updaters:
    105            current_slots += a_updater.updates_per_interval
    106
    107        return current_slots
    108
    109    def decrease_updates_per_interval(self):
    110        # This will add a delay for all devices so we can squeeze more in there
    111        for a_updater in self.updaters:
    112            a_updater.delay()
    113
    114    def tick_tock(self):
    115        # This will reduce the update_in counter for all devices
    116        for a_updater in self.updaters:
    117            a_updater.tick_tock()
    118
    119    def add_updater(self, updater: DeviceUpdater):
    120        if len(self.updaters) >= MAX_SLOTS:
    121            _LOGGER.exception("No more devices can be updated within the rate limit")
    122            raise Exception("No more devices can be updated within the rate limit")
    123
    124        # When we add a new updater it has to fit within the max slots or we will not add it
    125        while (self.filled_slots() + updater.updates_per_interval) > MAX_SLOTS:
    126            _LOGGER.debug("Reducing updates per interval to fit new device as slots are full: %s", self.filled_slots())
    127            # If we are overflowing the available slots we will reduce the frequency of updates evenly for all devices until we can fit in one more.
    128            self.decrease_updates_per_interval()
    129            updater.delay()
    130
    131        # Once it fits we will add the new updater to the queue
    132        heappush(self.updaters, updater)
    133
    134    def del_updater(self, updater: DeviceUpdater):
    135        self.removed_updaters.append(updater)
    136        _LOGGER.debug("Removing device from update queue")

INTERVAL = 300
MAX_SLOTS = 225
@dataclass(order=True)
class DeviceUpdater: View Source

    16@dataclass(order=True)
    17class DeviceUpdater(object):
    18    device: Device=field(compare=False)  # The device that will be updated
    19    service: Any=field(compare=False)
    20    update_in: int # A countdown to zero that will tell the priority queue that it is time to update this device
    21    updates_per_interval: int=field(compare=False) # The number of updates that should happen every 5 minutes
    22
    23    def __init__(self, service, device: Device, update_interval: int):
    24        """
    25        This function initializes a DeviceUpdater object
    26        :param service: The WyzeApy service connected to a device
    27        :param device: A WyzeApy device that needs to be in the update que
    28        :param update_interval: How many seconds should be targeted between updates. **Note this value may shift based on 1 call per sec and load.
    29        """
    30        self.service = service
    31        self.device = device
    32        self.update_in = 0  # Always initialize at 0 so that we get the first update ASAP. The items will shift based on priority after this.
    33        self.updates_per_interval = ceil(INTERVAL / update_interval)
    34
    35    async def update(self, mutex: threading.Lock):
    36        # We only want to update if the update_in counter is zero
    37        if self.update_in <= 0:
    38            _LOGGER.debug("Updating device: " + self.device.nickname)
    39            # Acquire the mutex before making the async call
    40            mutex.acquire()
    41            try:
    42                # Get the updated info for the device from Wyze's API
    43                self.device = await self.service.update(self.device)
    44                # Callback to provide the updated info to the subscriber
    45                self.device.callback_function(self.device)
    46            except:
    47                _LOGGER.exception("Unknown error happened during updating device info")
    48            finally:
    49                # Release the mutex after the async call
    50                mutex.release()
    51            # Once it reaches zero and we update the device we want to reset the update_in counter
    52            self.update_in = ceil(INTERVAL / self.updates_per_interval)
    53        else:
    54            # Don't update and instead just reduce the counter by 1
    55            self.tick_tock()
    56
    57    def tick_tock(self):
    58        # Every time we update a device we want to reduce the update_in counter so that it will get closer to updating
    59        if self.update_in > 0:
    60            self.update_in -= 1
    61
    62    def delay(self):
    63        # This should be called to reduce the number of updates per interval so that new devices can be added into the queue fairly
    64        if self.updates_per_interval > 1:
    65            self.updates_per_interval -= 1

DeviceUpdater(service, device: wyzeapy.types.Device, update_interval: int) View Source

    23    def __init__(self, service, device: Device, update_interval: int):
    24        """
    25        This function initializes a DeviceUpdater object
    26        :param service: The WyzeApy service connected to a device
    27        :param device: A WyzeApy device that needs to be in the update que
    28        :param update_interval: How many seconds should be targeted between updates. **Note this value may shift based on 1 call per sec and load.
    29        """
    30        self.service = service
    31        self.device = device
    32        self.update_in = 0  # Always initialize at 0 so that we get the first update ASAP. The items will shift based on priority after this.
    33        self.updates_per_interval = ceil(INTERVAL / update_interval)

This function initializes a DeviceUpdater object

###### Parameters

- service: The WyzeApy service connected to a device
- device: A WyzeApy device that needs to be in the update que
- update_interval: How many seconds should be targeted between updates. **Note this value may shift based on 1 call per sec and load.

device: wyzeapy.types.Device
service: Any
update_in: int
updates_per_interval: int
async def update(self, mutex: _thread.lock): View Source

    35    async def update(self, mutex: threading.Lock):
    36        # We only want to update if the update_in counter is zero
    37        if self.update_in <= 0:
    38            _LOGGER.debug("Updating device: " + self.device.nickname)
    39            # Acquire the mutex before making the async call
    40            mutex.acquire()
    41            try:
    42                # Get the updated info for the device from Wyze's API
    43                self.device = await self.service.update(self.device)
    44                # Callback to provide the updated info to the subscriber
    45                self.device.callback_function(self.device)
    46            except:
    47                _LOGGER.exception("Unknown error happened during updating device info")
    48            finally:
    49                # Release the mutex after the async call
    50                mutex.release()
    51            # Once it reaches zero and we update the device we want to reset the update_in counter
    52            self.update_in = ceil(INTERVAL / self.updates_per_interval)
    53        else:
    54            # Don't update and instead just reduce the counter by 1
    55            self.tick_tock()

def tick_tock(self): View Source

    57    def tick_tock(self):
    58        # Every time we update a device we want to reduce the update_in counter so that it will get closer to updating
    59        if self.update_in > 0:
    60            self.update_in -= 1

def delay(self): View Source

    62    def delay(self):
    63        # This should be called to reduce the number of updates per interval so that new devices can be added into the queue fairly
    64        if self.updates_per_interval > 1:
    65            self.updates_per_interval -= 1

class UpdateManager: View Source

     67class UpdateManager:
     68    # Holds all the logic for when to update the devices
     69    updaters = []
     70    removed_updaters = []
     71    mutex = threading.Lock()  # Create a lock object as a class variable
     72
     73    def check_if_removed(self, updater: DeviceUpdater):
     74        for item in self.removed_updaters:
     75            if updater is item:
     76                return True
     77        return False
     78
     79    # This function should be called once every second
     80    async def update_next(self):
     81        # If there are no updaters in the queue we don't need to do anything
     82        if (len(self.updaters) == 0):
     83            _LOGGER.debug("No devices to update in queue")
     84            return
     85        while True:
     86            # First we get the next updater off the queue
     87            updater = heappop(self.updaters)
     88            # if the updater has been removed, pop the next and clear it from the removed updaters
     89            if self.removed_updaters:
     90                while self.check_if_removed(updater):
     91                    self.removed_updaters.remove(updater)
     92                    updater = heappop(self.updaters)
     93            # We then reduce the counter for all the other updaters
     94            self.tick_tock()
     95            # Then we update the target device
     96            await updater.update(self.mutex) # It will only update if it is time for it to update. Otherwise it just reduces its update_in counter.
     97            # Then we put it back at the end of the queue. Or the front again if it wasn't ready to update
     98            heappush(self.updaters, updater)
     99            await sleep(1)
    100
    101
    102    def filled_slots(self):
    103        # This just returns the number of available slots
    104        current_slots = 0
    105        for a_updater in self.updaters:
    106            current_slots += a_updater.updates_per_interval
    107
    108        return current_slots
    109
    110    def decrease_updates_per_interval(self):
    111        # This will add a delay for all devices so we can squeeze more in there
    112        for a_updater in self.updaters:
    113            a_updater.delay()
    114
    115    def tick_tock(self):
    116        # This will reduce the update_in counter for all devices
    117        for a_updater in self.updaters:
    118            a_updater.tick_tock()
    119
    120    def add_updater(self, updater: DeviceUpdater):
    121        if len(self.updaters) >= MAX_SLOTS:
    122            _LOGGER.exception("No more devices can be updated within the rate limit")
    123            raise Exception("No more devices can be updated within the rate limit")
    124
    125        # When we add a new updater it has to fit within the max slots or we will not add it
    126        while (self.filled_slots() + updater.updates_per_interval) > MAX_SLOTS:
    127            _LOGGER.debug("Reducing updates per interval to fit new device as slots are full: %s", self.filled_slots())
    128            # If we are overflowing the available slots we will reduce the frequency of updates evenly for all devices until we can fit in one more.
    129            self.decrease_updates_per_interval()
    130            updater.delay()
    131
    132        # Once it fits we will add the new updater to the queue
    133        heappush(self.updaters, updater)
    134
    135    def del_updater(self, updater: DeviceUpdater):
    136        self.removed_updaters.append(updater)
    137        _LOGGER.debug("Removing device from update queue")

updaters = []
removed_updaters = []
mutex = <unlocked _thread.lock object>
def check_if_removed(self, updater: DeviceUpdater): View Source

    73    def check_if_removed(self, updater: DeviceUpdater):
    74        for item in self.removed_updaters:
    75            if updater is item:
    76                return True
    77        return False

async def update_next(self): View Source

    80    async def update_next(self):
    81        # If there are no updaters in the queue we don't need to do anything
    82        if (len(self.updaters) == 0):
    83            _LOGGER.debug("No devices to update in queue")
    84            return
    85        while True:
    86            # First we get the next updater off the queue
    87            updater = heappop(self.updaters)
    88            # if the updater has been removed, pop the next and clear it from the removed updaters
    89            if self.removed_updaters:
    90                while self.check_if_removed(updater):
    91                    self.removed_updaters.remove(updater)
    92                    updater = heappop(self.updaters)
    93            # We then reduce the counter for all the other updaters
    94            self.tick_tock()
    95            # Then we update the target device
    96            await updater.update(self.mutex) # It will only update if it is time for it to update. Otherwise it just reduces its update_in counter.
    97            # Then we put it back at the end of the queue. Or the front again if it wasn't ready to update
    98            heappush(self.updaters, updater)
    99            await sleep(1)

def filled_slots(self): View Source

    102    def filled_slots(self):
    103        # This just returns the number of available slots
    104        current_slots = 0
    105        for a_updater in self.updaters:
    106            current_slots += a_updater.updates_per_interval
    107
    108        return current_slots

def decrease_updates_per_interval(self): View Source

    110    def decrease_updates_per_interval(self):
    111        # This will add a delay for all devices so we can squeeze more in there
    112        for a_updater in self.updaters:
    113            a_updater.delay()

def tick_tock(self): View Source

    115    def tick_tock(self):
    116        # This will reduce the update_in counter for all devices
    117        for a_updater in self.updaters:
    118            a_updater.tick_tock()

def add_updater(self, updater: DeviceUpdater): View Source

    120    def add_updater(self, updater: DeviceUpdater):
    121        if len(self.updaters) >= MAX_SLOTS:
    122            _LOGGER.exception("No more devices can be updated within the rate limit")
    123            raise Exception("No more devices can be updated within the rate limit")
    124
    125        # When we add a new updater it has to fit within the max slots or we will not add it
    126        while (self.filled_slots() + updater.updates_per_interval) > MAX_SLOTS:
    127            _LOGGER.debug("Reducing updates per interval to fit new device as slots are full: %s", self.filled_slots())
    128            # If we are overflowing the available slots we will reduce the frequency of updates evenly for all devices until we can fit in one more.
    129            self.decrease_updates_per_interval()
    130            updater.delay()
    131
    132        # Once it fits we will add the new updater to the queue
    133        heappush(self.updaters, updater)

def del_updater(self, updater: DeviceUpdater): View Source

    135    def del_updater(self, updater: DeviceUpdater):
    136        self.removed_updaters.append(updater)
    137        _LOGGER.debug("Removing device from update queue")