diff --git a/.gitignore b/.gitignore index 4789f92..69db872 100644 --- a/.gitignore +++ b/.gitignore @@ -10,12 +10,7 @@ __pycache__ *.xls *.torrent -/config.py tts-gc-key.json -jwt_access_token -jwt_refresh_token -jwt.key.pub -jwt.key audio/ downloads/ diff --git a/README.md b/README.md index c52040b..3f0377b 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,14 @@ ## Voice assistant, smart home hub, media center and smart tv ### Project structure: - - #### ArchieCore - Core, base classes + - #### VICore - Core, base classes - Command - CommandsManager - SearchResult - Response - ThreadData - Pattern - - ACObject and subclasses + - VIObject and subclasses - #### Controls - Responsible for user interaction - Control(ABC) - VoiceAssistant diff --git a/SmartHome/AUID.py b/SmartHome/AUID.py deleted file mode 100644 index acfaadf..0000000 --- a/SmartHome/AUID.py +++ /dev/null @@ -1,55 +0,0 @@ -from typing import NamedTuple -from enum import Enum, IntEnum, auto -from uuid import UUID -import time -import qrcode -from qrcode.image.pil import PilImage - - -__all__ = ['UUID',] - -class URDI(int): - def __repr__(self) -> str: - return f'URDI({super().__repr__()})' - -class Model(IntEnum): - zero = 0 - hub = 1 - relay = 2 - -class Version(tuple[int, int], Enum): - zero = (0, 0) - dev = (0, 1) - test = (0, 2) - prod = (1, 1) - -class UUIDItems(NamedTuple): - time: int - time_low: int - model: Model - version: Version - urdi: URDI - -class AUID(UUID): - - @classmethod - def new(cls, model: Model = None, version: Version = None, urdi: int = None, now=time.time()) -> 'UUID': - now = now or 0 - model = model or Model.zero - version = version or Version.zero - urdi = urdi or 0 - now = now or 0 - - time = int(now) - time_low = int((now % 1) * 255 * 2) - - return cls(fields=(time, time_low, model.value, *version.value, urdi)) - - @property - def items(self) -> UUIDItems: - time, time_low, model, *ver, urdi = self.fields - return UUIDItems(time, time_low, Model(model), Version(tuple(ver)), URDI(urdi)) - - @property - def qrcode(self) -> PilImage: - return qrcode.make(str(self)) diff --git a/SmartHome/client/api.py b/SmartHome/client/api.py deleted file mode 100644 index 9981945..0000000 --- a/SmartHome/client/api.py +++ /dev/null @@ -1,37 +0,0 @@ -from typing import Type -from aiohttp import ClientSession -from pydantic import BaseModel -from pydantic.error_wrappers import ValidationError -from schemas.ws import RestRequest -import config - - -headers = {'Authorization': f'Bearer {config.access_token}'} -client_session: ClientSession = None - -async def start_client(): - global client_session - if client_session: - client_session.close() - client_session = ClientSession() - -async def get(url: str, - model: Type[BaseModel]) -> BaseModel | None: - try: - async with client_session.get(f'{config.api_url}/{url}') as response: - text = await response.text() - return model.parse_raw(text) - except ValidationError: - print(f'\nAPI ValidationError Error: {url}\nModel: {model}\n{text}\n') - return None - -async def local_request(request: RestRequest) -> str: - method = getattr(client_session, request.method) - - url = f'{config.localhost}/{request.path}' - json = request.body - headers = {} - - async with method(url, json = json, headers = headers) as r: - if 200 <= response.status < 300: - return await response.text() diff --git a/SmartHome/client/client.py b/SmartHome/client/client.py deleted file mode 100644 index 6b8da33..0000000 --- a/SmartHome/client/client.py +++ /dev/null @@ -1,46 +0,0 @@ -import asyncio -from exceptions.Internal import InternalException -from schemas.hub import HubPatch -from schemas.house import HousePatch -from schemas.device import DeviceModelInfo -from managers import HubManager, HouseManager, DeviceModelManager -from database import create_async_session, AsyncSession -from . import api - - -def fetch(): - asyncio.run(fetch_all()) - -async def fetch_all(): - await api.start_client() - async with create_async_session() as session: - await asyncio.gather( - fetch_house(session), - fetch_hub(session), - fetch_device_models(session) - ) - -async def fetch_house(session: AsyncSession): - manager = HouseManager(session) - try: - house = await manager.get() - except InternalException: - return - if patch_house := await api.get(f'house/{house.id}', HousePatch): - await manager.patch(patch_house) - -async def fetch_hub(session: AsyncSession): - manager = HubManager(session) - try: - hub = await manager.get() - except InternalException: - return - if patch_hub := await api.get(f'hub/{hub.id}', HubPatch): - await manager.patch(patch_hub) - -async def fetch_device_models(session: AsyncSession): - manager = DeviceModelManager(session) - if devicemodels := await api.get(f'device_model', DeviceModelInfo): - print(devicemodels) - for devicemodel in devicemodels: - manager.save(devicemodel) diff --git a/SmartHome/client/ws.py b/SmartHome/client/ws.py deleted file mode 100644 index 8b92437..0000000 --- a/SmartHome/client/ws.py +++ /dev/null @@ -1,40 +0,0 @@ -from threading import Thread -import websocket -import config -from server.endpoints.ws import WSManager - - -ws: websocket.WebSocketApp = None -ws_thread: Thread = None -ws_manager = WSManager() - -headers = {'Authorization': f'Bearer {config.access_token}'} - -def on_message(ws, msg): - if response := ws_manager.handle_message(msg): - ws.send(response) - -def on_open(ws): - pass - -def on_close(ws, status_code, reason): - start() - -def on_error(ws, error): - pass # TODO: log - -def start(): - global ws, ws_thread - - if ws: ws.close() - - ws = websocket.WebSocketApp(config.ws_url, - on_open=on_open, - on_message=on_message, - on_error=on_error, - on_close=on_close, - header=headers - ) - - ws_thread = Thread(target=ws.run_forever) - ws_thread.start() diff --git a/SmartHome/config.py b/SmartHome/config.py deleted file mode 100644 index 2e1cbf2..0000000 --- a/SmartHome/config.py +++ /dev/null @@ -1,43 +0,0 @@ -import pathlib -path = str(pathlib.Path(__file__).parent.absolute()) -del pathlib - - -src: str = path + '/resources' - -# DB - -db_url: str = f'sqlite:///{src}/database.sqlite3' -db_async_url: str = f'sqlite+aiosqlite:///{src}/database.sqlite3' - -# WiFi - -# TODO: hostapd.conf -# wifi_ssid: str = 'Archie Hub' -# wifi_password: str = '12345678' - -# API - -api_url = 'https://home.parker-programs.com/api' -ws_url = 'wss://home.parker-programs.com/ws/hub' -localhost = 'http://localhost:8000' - -try: - with open(f'{src}/jwt.key.pub', 'r') as f: - public_key = f.read() -except FileNotFoundError: - public_key = '' - -try: - with open(f'{src}/jwt_access_token', 'r') as f: - access_token = f.read() -except FileNotFoundError: - access_token = '' - -try: - with open(f'{src}/jwt_refresh_token', 'r') as f: - refresh_token = f.read() -except FileNotFoundError: - refresh_token = '' - -algorithm = 'RS256' diff --git a/SmartHome/database.py b/SmartHome/database.py deleted file mode 100644 index 0c731b5..0000000 --- a/SmartHome/database.py +++ /dev/null @@ -1,27 +0,0 @@ -from typing import Optional -from pydantic import BaseModel -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker, Session -from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine -import config - - -# sync - -engine = create_engine( - config.db_url, connect_args={'check_same_thread': False} -) - -create_session = sessionmaker( - autocommit=False, autoflush=False, bind=engine -) - -# async - -async_engine = create_async_engine( - config.db_async_url, connect_args={'check_same_thread': False} -) - -create_async_session = sessionmaker( - async_engine, class_ = AsyncSession, expire_on_commit = False -) diff --git a/SmartHome/exceptions/Internal.py b/SmartHome/exceptions/Internal.py deleted file mode 100644 index 39ce8bd..0000000 --- a/SmartHome/exceptions/Internal.py +++ /dev/null @@ -1,19 +0,0 @@ -from enum import IntEnum - - -class ExceptionCode(IntEnum): - undefined = 1000 - unauthorized = 1001 - access_denied = 1003 - not_found = 1004 - already_exist = 1005 - not_initialized = 1006 - invalid_format = 1022 - -class InternalException(Exception): - Code = ExceptionCode - - def __init__(self, code: Code | int = Code.undefined, msg: str = '', debug: str = ''): - self.code = code - self.msg = msg - self.debug = debug or msg diff --git a/SmartHome/hardware/Merlin/Merlin.py b/SmartHome/hardware/Merlin/Merlin.py deleted file mode 100644 index f555ee9..0000000 --- a/SmartHome/hardware/Merlin/Merlin.py +++ /dev/null @@ -1,77 +0,0 @@ -from typing import List -from threading import Thread - -try: - import RPi.GPIO as GPIO - import spidev - GPIO.setmode(GPIO.BCM) - is_rpi = True -except ModuleNotFoundError: - is_rpi = False - -from .MerlinMessage import MerlinMessage -from .lib_nrf24 import NRF24 - - -class Merlin(): - radio: NRF24 - send_queue: List[MerlinMessage] = [] - my_urdi = [0xff, 0x00, 0x00, 0x00, 0x01] - - def __init__(self): - if not is_rpi: return - - radio = NRF24(GPIO, spidev.SpiDev()) - radio.begin(0, 17) - radio.setPALevel(NRF24.PA_HIGH) - radio.setDataRate(NRF24.BR_250KBPS) - radio.setChannel(0x60) - radio.setPayloadSize(2) - radio.setAutoAck(True) - radio.setRetries(10, 20) - #radio.setAddressWidth(4) - radio.enableAckPayload() - radio.openReadingPipe(1, self.my_urdi) - - radio.startListening() - radio.stopListening() - - radio.startListening() - - self.radio = radio - - def send(self, message: MerlinMessage): - self.send_queue.append(message) - - def _send(self, message: MerlinMessage): - if not is_rpi: - # TODO: Log - print(message.urdi, message.data) - return - self.radio.stopListening() - pipe = [0xFF] + list(message.urdi.to_bytes(4, 'big')) - self.radio.openWritingPipe(pipe) - self.radio.write(message.data) - self.radio.startListening() - - def receiveAndTransmit(self): - while True: - # send messages from queue - while self.send_queue and (message := self.send_queue.pop()): - self._send(message) - - if not is_rpi: return - - # receiving messages - if not self.radio.available(): - continue - - rawData = [] - self.radio.read(rawData, self.radio.getPayloadSize()) - func, arg = rawData - print(f'Received {func=} {arg=}') # TODO: Log - -merlin = Merlin() - -_receiveAndTransmitThread = Thread(target = Merlin().receiveAndTransmit) -_receiveAndTransmitThread.start() diff --git a/SmartHome/hardware/Merlin/MerlinMessage.py b/SmartHome/hardware/Merlin/MerlinMessage.py deleted file mode 100644 index f0764bb..0000000 --- a/SmartHome/hardware/Merlin/MerlinMessage.py +++ /dev/null @@ -1,15 +0,0 @@ -from typing import List - -class MerlinMessage: - urdi: int - func: int # unsigned 1 byte int 0...255 - arg: int # unsigned 1 byte int 0...255 - - def __init__(self, urdi, func, arg): - self.urdi = urdi - self.func = func - self.arg = arg - - @property - def data(self) -> List[int]: - return [self.func, self.arg] diff --git a/SmartHome/hardware/Merlin/__init__.py b/SmartHome/hardware/Merlin/__init__.py deleted file mode 100644 index dbb64d2..0000000 --- a/SmartHome/hardware/Merlin/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .Merlin import merlin -from .MerlinMessage import MerlinMessage diff --git a/SmartHome/hardware/Merlin/lib_nrf24.py b/SmartHome/hardware/Merlin/lib_nrf24.py deleted file mode 100644 index fafad43..0000000 --- a/SmartHome/hardware/Merlin/lib_nrf24.py +++ /dev/null @@ -1,788 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# - - -# This file lib_nrf24.py is a slightly tweaked version of Barraca's "pynrf24". - -# So this is my tweak for hardware Pi and "Virtual GPIO" ... -# ... of Barraca's port to BeagleBone python ... (Joao Paulo Barraca ) -# ... of maniacbug's NRF24L01 C++ library for Arduino. -# Brian Lavery Oct 2014 - - - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# - - -import sys -import time - -if __name__ == '__main__': - print (sys.argv[0], 'is an importable module:') - print ("... from", sys.argv[0], "import lib_nrf24") - print ("") - - exit() - -def _BV(x): - return 1 << x - - -class NRF24: - MAX_CHANNEL = 127 - MAX_PAYLOAD_SIZE = 32 - - # PA Levels - PA_MIN = 0 - PA_LOW = 1 - PA_HIGH = 2 - PA_MAX = 3 - PA_ERROR = 4 - - # Bit rates - BR_1MBPS = 0 - BR_2MBPS = 1 - BR_250KBPS = 2 - - # CRC - CRC_DISABLED = 0 - CRC_8 = 1 - CRC_16 = 2 - CRC_ENABLED = 3 - - # Registers - CONFIG = 0x00 - EN_AA = 0x01 - EN_RXADDR = 0x02 - SETUP_AW = 0x03 - SETUP_RETR = 0x04 - RF_CH = 0x05 - RF_SETUP = 0x06 - STATUS = 0x07 - OBSERVE_TX = 0x08 - CD = 0x09 - RX_ADDR_P0 = 0x0A - RX_ADDR_P1 = 0x0B - RX_ADDR_P2 = 0x0C - RX_ADDR_P3 = 0x0D - RX_ADDR_P4 = 0x0E - RX_ADDR_P5 = 0x0F - TX_ADDR = 0x10 - RX_PW_P0 = 0x11 - RX_PW_P1 = 0x12 - RX_PW_P2 = 0x13 - RX_PW_P3 = 0x14 - RX_PW_P4 = 0x15 - RX_PW_P5 = 0x16 - FIFO_STATUS = 0x17 - DYNPD = 0x1C - FEATURE = 0x1D - - - # Bit Mnemonics */ - MASK_RX_DR = 6 - MASK_TX_DS = 5 - MASK_MAX_RT = 4 - EN_CRC = 3 - CRCO = 2 - PWR_UP = 1 - PRIM_RX = 0 - ENAA_P5 = 5 - ENAA_P4 = 4 - ENAA_P3 = 3 - ENAA_P2 = 2 - ENAA_P1 = 1 - ENAA_P0 = 0 - ERX_P5 = 5 - ERX_P4 = 4 - ERX_P3 = 3 - ERX_P2 = 2 - ERX_P1 = 1 - ERX_P0 = 0 - AW = 0 - ARD = 4 - ARC = 0 - PLL_LOCK = 4 - RF_DR = 3 - RF_PWR = 6 - RX_DR = 6 - TX_DS = 5 - MAX_RT = 4 - RX_P_NO = 1 - TX_FULL = 0 - PLOS_CNT = 4 - ARC_CNT = 0 - TX_REUSE = 6 - FIFO_FULL = 5 - TX_EMPTY = 4 - RX_FULL = 1 - RX_EMPTY = 0 - DPL_P5 = 5 - DPL_P4 = 4 - DPL_P3 = 3 - DPL_P2 = 2 - DPL_P1 = 1 - DPL_P0 = 0 - EN_DPL = 2 - EN_ACK_PAY = 1 - EN_DYN_ACK = 0 - - # Instruction Mnemonics - R_REGISTER = 0x00 - W_REGISTER = 0x20 - REGISTER_MASK = 0x1F - ACTIVATE = 0x50 - R_RX_PL_WID = 0x60 - R_RX_PAYLOAD = 0x61 - W_TX_PAYLOAD = 0xA0 - W_ACK_PAYLOAD = 0xA8 - FLUSH_TX = 0xE1 - FLUSH_RX = 0xE2 - REUSE_TX_PL = 0xE3 - NOP = 0xFF - - - # Non-P omissions - LNA_HCURR = 0x00 - - # P model memory Map - RPD = 0x09 - - # P model bit Mnemonics - RF_DR_LOW = 5 - RF_DR_HIGH = 3 - RF_PWR_LOW = 1 - RF_PWR_HIGH = 2 - - # Signal Mnemonics - LOW = 0 - HIGH = 1 - - datarate_e_str_P = ["1MBPS", "2MBPS", "250KBPS"] - model_e_str_P = ["nRF24L01", "nRF24l01+"] - crclength_e_str_P = ["Disabled", "8 bits", "16 bits"] - pa_dbm_e_str_P = ["PA_MIN", "PA_LOW", "PA_MED", "PA_HIGH"] - child_pipe = [RX_ADDR_P0, RX_ADDR_P1, RX_ADDR_P2, RX_ADDR_P3, RX_ADDR_P4, RX_ADDR_P5] - - child_payload_size = [RX_PW_P0, RX_PW_P1, RX_PW_P2, RX_PW_P3, RX_PW_P4, RX_PW_P5] - child_pipe_enable = [ERX_P0, ERX_P1, ERX_P2, ERX_P3, ERX_P4, ERX_P5] - - GPIO = None - spidev = None - - def __init__(self, gpio, spidev): - # It should be possible to instantiate multiple objects, with different GPIO / spidev - # EG on hardware, one could be RPI GPIO & spidev module, other could be virtual-GPIO - # On rpi, only bus 0 is supported here, not bus 1 of the model B plus - self.GPIO = gpio # the GPIO module - self.spidev = spidev # the spidev object/instance - self.channel = 76 - self.data_rate = NRF24.BR_1MBPS - self.wide_band = False # 2Mbs data rate in use? - self.p_variant = False # False for RF24L01 and true for RF24L01P (nrf24l01+) - self.payload_size = 5 #*< Fixed size of payloads - self.ack_payload_available = False #*< Whether there is an ack payload waiting - self.dynamic_payloads_enabled = False #*< Whether dynamic payloads are enabled. - self.ack_payload_length = 5 #*< Dynamic size of pending ack payload. - self.pipe0_reading_address = None #*< Last address set on pipe 0 for reading. - - def ce(self, level): - if self.ce_pin == 0: - return - # rf24-CE is optional. Tie to HIGH if not used. (Altho, left floating seems to read HIGH anyway??? - risky!) - # Some RF24 modes may NEED control over CE. - # non-powerdown, fixed PTX or RTX role, dynamic payload size & ack-payload: does NOT need CE. - if level == NRF24.HIGH: - self.GPIO.output(self.ce_pin, self.GPIO.HIGH) - else: - self.GPIO.output(self.ce_pin, self.GPIO.LOW) - return - - - - def read_register(self, reg, blen=1): - buf = [NRF24.R_REGISTER | ( NRF24.REGISTER_MASK & reg )] - for col in range(blen): - buf.append(NRF24.NOP) - - resp = self.spidev.xfer2(buf) - if blen == 1: - return resp[1] - - return resp[1:blen + 1] - - def write_register(self, reg, value, length=-1): - buf = [NRF24.W_REGISTER | ( NRF24.REGISTER_MASK & reg )] - ###if isinstance(value, (int, long)): # ng for python3. but value should never be long anyway - if isinstance(value, int): - if length < 0: - length = 1 - - length = min(4, length) - for i in range(length): - buf.insert(1, int(value & 0xff)) - value >>= 8 - - elif isinstance(value, list): - if length < 0: - length = len(value) - - for i in range(min(len(value), length)): - buf.append(int(value[len(value) - i - 1] & 0xff)) - else: - raise Exception("Value must be int or list") - - return self.spidev.xfer2(buf)[0] - - - def write_payload(self, buf): - data_len = min(self.payload_size, len(buf)) - blank_len = 0 - if not self.dynamic_payloads_enabled: - blank_len = self.payload_size - data_len - - txbuffer = [NRF24.W_TX_PAYLOAD] - for n in buf: - t = type(n) - if t is str: - txbuffer.append(ord(n)) - elif t is int: - txbuffer.append(n) - else: - raise Exception("Only ints and chars are supported: Found " + str(t)) - - if blank_len != 0: - blank = [0x00 for i in range(blank_len)] - txbuffer.extend(blank) - - return self.spidev.xfer2(txbuffer) - - def read_payload(self, buf, buf_len=-1): - if buf_len < 0: - buf_len = self.payload_size - data_len = min(self.payload_size, buf_len) - blank_len = 0 - if not self.dynamic_payloads_enabled: - blank_len = self.payload_size - data_len - - txbuffer = [NRF24.NOP for i in range(0, blank_len + data_len + 1)] - txbuffer[0] = NRF24.R_RX_PAYLOAD - - payload = self.spidev.xfer2(txbuffer) - del buf[:] - buf.extend(payload[1:data_len + 1]) - return data_len - - def flush_rx(self): - return self.spidev.xfer2([NRF24.FLUSH_RX])[0] - - def flush_tx(self): - return self.spidev.xfer2([NRF24.FLUSH_TX])[0] - - def get_status(self): - return self.spidev.xfer2([NRF24.NOP])[0] - - def print_status(self, status): - status_str = "STATUS\t = 0x{0:02x} RX_DR={1:x} TX_DS={2:x} MAX_RT={3:x} RX_P_NO={4:x} TX_FULL={5:x}".format( - status, - 1 if status & _BV(NRF24.RX_DR) else 0, - 1 if status & _BV(NRF24.TX_DS) else 0, - 1 if status & _BV(NRF24.MAX_RT) else 0, - ((status >> NRF24.RX_P_NO) & 7), - 1 if status & _BV(NRF24.TX_FULL) else 0) - - print (status_str) - - def print_observe_tx(self, value): - print ("Observe Tx: %02x Lost Pkts: %d Retries: %d" % (value, value >> NRF24.PLOS_CNT, value & 15)) - - - def print_byte_register(self, name, reg, qty=1): - extra_tab = '\t' if len(name) < 8 else 0 - print ("%s\t%c =" % (name, extra_tab)), - while qty > 0: - print ("0x%02x" % (self.read_register(reg))), - qty -= 1 - reg += 1 - - print ("") - - def print_address_register(self, name, reg, qty=1): - extra_tab = '\t' if len(name) < 8 else 0 - print ("%s\t%c =" % (name, extra_tab)), - - while qty > 0: - qty -= 1 - buf = reversed(self.read_register(reg, 5)) - reg += 1 - sys.stdout.write(" 0x"), - for i in buf: - sys.stdout.write("%02x" % i) - - print ("") - - - def setChannel(self, channel): - self.channel = min(max(0, channel), NRF24.MAX_CHANNEL) - self.write_register(NRF24.RF_CH, self.channel) - - def getChannel(self): - return self.read_register(NRF24.RF_CH) - - def setPayloadSize(self, size): - self.payload_size = min(max(size, 1), NRF24.MAX_PAYLOAD_SIZE) - - def getPayloadSize(self): - return self.payload_size - - def printDetails(self): - self.print_status(self.get_status()) - self.print_address_register("RX_ADDR_P0-1", NRF24.RX_ADDR_P0, 2) - self.print_byte_register("RX_ADDR_P2-5", NRF24.RX_ADDR_P2, 4) - self.print_address_register("TX_ADDR", NRF24.TX_ADDR) - - self.print_byte_register("RX_PW_P0-6", NRF24.RX_PW_P0, 6) - self.print_byte_register("EN_AA", NRF24.EN_AA) - self.print_byte_register("EN_RXADDR", NRF24.EN_RXADDR) - self.print_byte_register("RF_CH", NRF24.RF_CH) - self.print_byte_register("RF_SETUP", NRF24.RF_SETUP) - self.print_byte_register("CONFIG", NRF24.CONFIG) - self.print_byte_register("DYNPD/FEATURE", NRF24.DYNPD, 2) - - # - print ("Data Rate\t = %s" % NRF24.datarate_e_str_P[self.getDataRate()]) - print ("Model\t\t = %s" % NRF24.model_e_str_P[self.isPVariant()]) - print ("CRC Length\t = %s" % NRF24.crclength_e_str_P[self.getCRCLength()]) - print ("PA Power\t = %s" % NRF24.pa_dbm_e_str_P[self.getPALevel()]) - - def begin(self, csn_pin, ce_pin=0): # csn & ce are RF24 terminology. csn = SPI's CE! - # Initialize SPI bus.. - # ce_pin is for the rx=listen or tx=trigger pin on RF24 (they call that ce !!!) - # CE optional (at least in some circumstances, eg fixed PTX PRX roles, no powerdown) - # CE seems to hold itself as (sufficiently) HIGH, but tie HIGH is safer! - self.spidev.open(0, csn_pin) - self.spidev.max_speed_hz = 4000000 - self.ce_pin = ce_pin - - if ce_pin: - self.GPIO.setup(self.ce_pin, self.GPIO.OUT) - - time.sleep(5 / 1000000.0) - - # Set 1500uS (minimum for 32B payload in ESB@250KBPS) timeouts, to make testing a little easier - # WARNING: If this is ever lowered, either 250KBS mode with AA is broken or maximum packet - # sizes must never be used. See documentation for a more complete explanation. - self.write_register(NRF24.SETUP_RETR, (0b0100 << NRF24.ARD) | 0b1111) - - # Restore our default PA level - self.setPALevel(NRF24.PA_MAX) - - # Determine if this is a p or non-p RF24 module and then - # reset our data rate back to default value. This works - # because a non-P variant won't allow the data rate to - # be set to 250Kbps. - if self.setDataRate(NRF24.BR_250KBPS): - self.p_variant = True - - # Then set the data rate to the slowest (and most reliable) speed supported by all - # hardware. - self.setDataRate(NRF24.BR_1MBPS) - - # Initialize CRC and request 2-byte (16bit) CRC - self.setCRCLength(NRF24.CRC_16) - - # Disable dynamic payloads, to match dynamic_payloads_enabled setting - self.write_register(NRF24.DYNPD, 0) - - # Reset current status - # Notice reset and flush is the last thing we do - self.write_register(NRF24.STATUS, _BV(NRF24.RX_DR) | _BV(NRF24.TX_DS) | _BV(NRF24.MAX_RT)) - - # Set up default configuration. Callers can always change it later. - # This channel should be universally safe and not bleed over into adjacent - # spectrum. - self.setChannel(self.channel) - - # Flush buffers - self.flush_rx() - self.flush_tx() - - def end(self): - if self.spidev: - self.spidev.close() - self.spidev = None - - def startListening(self): - self.write_register(NRF24.CONFIG, self.read_register(NRF24.CONFIG) | _BV(NRF24.PWR_UP) | _BV(NRF24.PRIM_RX)) - self.write_register(NRF24.STATUS, _BV(NRF24.RX_DR) | _BV(NRF24.TX_DS) | _BV(NRF24.MAX_RT)) - - # Restore the pipe0 address, if exists - if self.pipe0_reading_address: - self.write_register(self.RX_ADDR_P0, self.pipe0_reading_address, 5) - - # Go! - self.ce(NRF24.HIGH) - - # wait for the radio to come up (130us actually only needed) - time.sleep(130 / 1000000.0) - - def stopListening(self): - self.ce(NRF24.LOW) - self.flush_tx() - self.flush_rx() - - def powerDown(self): - self.write_register(NRF24.CONFIG, self.read_register(NRF24.CONFIG) & ~_BV(NRF24.PWR_UP)) - - def powerUp(self): - self.write_register(NRF24.CONFIG, self.read_register(NRF24.CONFIG) | _BV(NRF24.PWR_UP)) - time.sleep(150 / 1000000.0) - - def write(self, buf): - # Begin the write - self.startWrite(buf) - - timeout = self.getMaxTimeout() #s to wait for timeout - sent_at = time.time() - - while True: - #status = self.read_register(NRF24.OBSERVE_TX, 1) - status = self.get_status() - if (status & (_BV(NRF24.TX_DS) | _BV(NRF24.MAX_RT))) or (time.time() - sent_at > timeout ): - break - time.sleep(10 / 1000000.0) - #obs = self.read_register(NRF24.OBSERVE_TX) - #self.print_observe_tx(obs) - #self.print_status(status) - # (for debugging) - - what = self.whatHappened() - - result = what['tx_ok'] - if what['tx_fail']: - self.flush_tx(); # bl - dont jam up the fifo - # Handle the ack packet - if what['rx_ready']: - self.ack_payload_length = self.getDynamicPayloadSize() - self.ack_payload_available = True ## bl - - return result - - def startWrite(self, buf): - # Transmitter power-up - self.write_register(NRF24.CONFIG, (self.read_register(NRF24.CONFIG) | _BV(NRF24.PWR_UP) ) & ~_BV(NRF24.PRIM_RX)) - - # Send the payload - self.write_payload(buf) - - # Allons! - if self.ce_pin: - if self.GPIO.RPI_REVISION > 0: - self.ce(self.GPIO.HIGH) - time.sleep(10 / 1000000.0) - self.ce(self.GPIO.LOW) - else: - # virtGPIO is slower. A 10 uSec pulse is better done with pulseOut(): - self.GPIO.pulseOut(self.ce_pin, self.GPIO.HIGH, 10) - - - - def getDynamicPayloadSize(self): - return self.spidev.xfer2([NRF24.R_RX_PL_WID, NRF24.NOP])[1] - - def available(self, pipe_num=None): - if not pipe_num: - pipe_num = [] - - status = self.get_status() - result = False - - # Sometimes the radio specifies that there is data in one pipe but - # doesn't set the RX flag... - if status & _BV(NRF24.RX_DR) or (status & 0b00001110 != 0b00001110): - result = True - - if result: - # If the caller wants the pipe number, include that - if len(pipe_num) >= 1: - pipe_num[0] = ( status >> NRF24.RX_P_NO ) & 0b00000111 - - # Clear the status bit - - # ??? Should this REALLY be cleared now? Or wait until we - # actually READ the payload? - self.write_register(NRF24.STATUS, _BV(NRF24.RX_DR)) - - # Handle ack payload receipt - if status & _BV(NRF24.TX_DS): - self.write_register(NRF24.STATUS, _BV(NRF24.TX_DS)) - - return result - - def read(self, buf, buf_len=-1): - # Fetch the payload - self.read_payload(buf, buf_len) - - # was this the last of the data available? - return self.read_register(NRF24.FIFO_STATUS) & _BV(NRF24.RX_EMPTY) - - def whatHappened(self): - # Read the status & reset the status in one easy call - # Or is that such a good idea? - status = self.write_register(NRF24.STATUS, _BV(NRF24.RX_DR) | _BV(NRF24.TX_DS) | _BV(NRF24.MAX_RT)) - - # Report to the user what happened - tx_ok = status & _BV(NRF24.TX_DS) - tx_fail = status & _BV(NRF24.MAX_RT) - rx_ready = status & _BV(NRF24.RX_DR) - return {'tx_ok': tx_ok, "tx_fail": tx_fail, "rx_ready": rx_ready} - - def openWritingPipe(self, value): - # Note that the NRF24L01(+) - # expects it LSB first. - - self.write_register(NRF24.RX_ADDR_P0, value, 5) - self.write_register(NRF24.TX_ADDR, value, 5) - - max_payload_size = 32 - self.write_register(NRF24.RX_PW_P0, min(self.payload_size, max_payload_size)) - - def openReadingPipe(self, child, address): - # If this is pipe 0, cache the address. This is needed because - # openWritingPipe() will overwrite the pipe 0 address, so - # startListening() will have to restore it. - if child == 0: - self.pipe0_reading_address = address - - if child <= 6: - # For pipes 2-5, only write the LSB - if child < 2: - self.write_register(NRF24.child_pipe[child], address, 5) - else: - self.write_register(NRF24.child_pipe[child], address, 1) - - self.write_register(NRF24.child_payload_size[child], self.payload_size) - - # Note it would be more efficient to set all of the bits for all open - # pipes at once. However, I thought it would make the calling code - # more simple to do it this way. - self.write_register(NRF24.EN_RXADDR, - self.read_register(NRF24.EN_RXADDR) | _BV(NRF24.child_pipe_enable[child])) - - - def closeReadingPipe(self, pipe): - self.write_register(NRF24.EN_RXADDR, - self.read_register(EN_RXADDR) & ~_BV(NRF24.child_pipe_enable[pipe])) - - - def toggle_features(self): - buf = [NRF24.ACTIVATE, 0x73] - self.spidev.xfer2(buf) - - def enableDynamicPayloads(self): - # Enable dynamic payload throughout the system - self.write_register(NRF24.FEATURE, self.read_register(NRF24.FEATURE) | _BV(NRF24.EN_DPL)) - - # If it didn't work, the features are not enabled - if not self.read_register(NRF24.FEATURE): - # So enable them and try again - self.toggle_features() - self.write_register(NRF24.FEATURE, self.read_register(NRF24.FEATURE) | _BV(NRF24.EN_DPL)) - - # Enable dynamic payload on all pipes - - # Not sure the use case of only having dynamic payload on certain - # pipes, so the library does not support it. - self.write_register(NRF24.DYNPD, self.read_register(NRF24.DYNPD) | _BV(NRF24.DPL_P5) | _BV(NRF24.DPL_P4) | _BV( - NRF24.DPL_P3) | _BV(NRF24.DPL_P2) | _BV(NRF24.DPL_P1) | _BV(NRF24.DPL_P0)) - - self.dynamic_payloads_enabled = True - - - def enableAckPayload(self): - # enable ack payload and dynamic payload features - self.write_register(NRF24.FEATURE, - self.read_register(NRF24.FEATURE) | _BV(NRF24.EN_ACK_PAY) | _BV(NRF24.EN_DPL)) - - # If it didn't work, the features are not enabled - if not self.read_register(NRF24.FEATURE): - # So enable them and try again - self.toggle_features() - self.write_register(NRF24.FEATURE, - self.read_register(NRF24.FEATURE) | _BV(NRF24.EN_ACK_PAY) | _BV(NRF24.EN_DPL)) - - # Enable dynamic payload on pipes 0 & 1 - self.write_register(NRF24.DYNPD, self.read_register(NRF24.DYNPD) | _BV(NRF24.DPL_P1) | _BV(NRF24.DPL_P0)) - - def writeAckPayload(self, pipe, buf, buf_len): - txbuffer = [NRF24.W_ACK_PAYLOAD | ( pipe & 0x7 )] - - max_payload_size = 32 - data_len = min(buf_len, max_payload_size) - txbuffer.extend(buf[0:data_len]) - - self.spidev.xfer2(txbuffer) - - def isAckPayloadAvailable(self): - result = self.ack_payload_available - self.ack_payload_available = False - return result - - def isPVariant(self): - return self.p_variant - - def setAutoAck(self, enable): - if enable: - self.write_register(NRF24.EN_AA, 0b111111) - else: - self.write_register(NRF24.EN_AA, 0) - - def setAutoAckPipe(self, pipe, enable): - if pipe <= 6: - en_aa = self.read_register(NRF24.EN_AA) - if enable: - en_aa |= _BV(pipe) - else: - en_aa &= ~_BV(pipe) - - self.write_register(NRF24.EN_AA, en_aa) - - def testCarrier(self): - return self.read_register(NRF24.CD) & 1 - - def testRPD(self): - return self.read_register(NRF24.RPD) & 1 - - def setPALevel(self, level): - setup = self.read_register(NRF24.RF_SETUP) - setup &= ~( _BV(NRF24.RF_PWR_LOW) | _BV(NRF24.RF_PWR_HIGH)) - # switch uses RAM (evil!) - if level == NRF24.PA_MAX: - setup |= (_BV(NRF24.RF_PWR_LOW) | _BV(NRF24.RF_PWR_HIGH)) - elif level == NRF24.PA_HIGH: - setup |= _BV(NRF24.RF_PWR_HIGH) - elif level == NRF24.PA_LOW: - setup |= _BV(NRF24.RF_PWR_LOW) - elif level == NRF24.PA_MIN: - nop = 0 - elif level == NRF24.PA_ERROR: - # On error, go to maximum PA - setup |= (_BV(NRF24.RF_PWR_LOW) | _BV(NRF24.RF_PWR_HIGH)) - - self.write_register(NRF24.RF_SETUP, setup) - - - def getPALevel(self): - power = self.read_register(NRF24.RF_SETUP) & (_BV(NRF24.RF_PWR_LOW) | _BV(NRF24.RF_PWR_HIGH)) - - if power == (_BV(NRF24.RF_PWR_LOW) | _BV(NRF24.RF_PWR_HIGH)): - return NRF24.PA_MAX - elif power == _BV(NRF24.RF_PWR_HIGH): - return NRF24.PA_HIGH - elif power == _BV(NRF24.RF_PWR_LOW): - return NRF24.PA_LOW - else: - return NRF24.PA_MIN - - def setDataRate(self, speed): - result = False - setup = self.read_register(NRF24.RF_SETUP) - - # HIGH and LOW '00' is 1Mbs - our default - self.wide_band = False - setup &= ~(_BV(NRF24.RF_DR_LOW) | _BV(NRF24.RF_DR_HIGH)) - - if speed == NRF24.BR_250KBPS: - # Must set the RF_DR_LOW to 1 RF_DR_HIGH (used to be RF_DR) is already 0 - # Making it '10'. - self.wide_band = False - setup |= _BV(NRF24.RF_DR_LOW) - else: - # Set 2Mbs, RF_DR (RF_DR_HIGH) is set 1 - # Making it '01' - if speed == NRF24.BR_2MBPS: - self.wide_band = True - setup |= _BV(NRF24.RF_DR_HIGH) - else: - # 1Mbs - self.wide_band = False - - self.write_register(NRF24.RF_SETUP, setup) - - # Verify our result - if self.read_register(NRF24.RF_SETUP) == setup: - result = True - else: - self.wide_band = False - return result - - def getDataRate(self): - dr = self.read_register(NRF24.RF_SETUP) & (_BV(NRF24.RF_DR_LOW) | _BV(NRF24.RF_DR_HIGH)) - # Order matters in our case below - if dr == _BV(NRF24.RF_DR_LOW): - # '10' = 250KBPS - return NRF24.BR_250KBPS - elif dr == _BV(NRF24.RF_DR_HIGH): - # '01' = 2MBPS - return NRF24.BR_2MBPS - else: - # '00' = 1MBPS - return NRF24.BR_1MBPS - - - def setCRCLength(self, length): - config = self.read_register(NRF24.CONFIG) & ~( _BV(NRF24.CRC_16) | _BV(NRF24.CRC_ENABLED)) - - if length == NRF24.CRC_DISABLED: - # Do nothing, we turned it off above. - self.write_register(NRF24.CONFIG, config) - return - elif length == NRF24.CRC_8: - config |= _BV(NRF24.CRC_ENABLED) - config |= _BV(NRF24.CRC_8) - else: - config |= _BV(NRF24.CRC_ENABLED) - config |= _BV(NRF24.CRC_16) - - self.write_register(NRF24.CONFIG, config) - - def getCRCLength(self): - result = NRF24.CRC_DISABLED - config = self.read_register(NRF24.CONFIG) & ( _BV(NRF24.CRCO) | _BV(NRF24.EN_CRC)) - - if config & _BV(NRF24.EN_CRC): - if config & _BV(NRF24.CRCO): - result = NRF24.CRC_16 - else: - result = NRF24.CRC_8 - - return result - - def disableCRC(self): - disable = self.read_register(NRF24.CONFIG) & ~_BV(NRF24.EN_CRC) - self.write_register(NRF24.CONFIG, disable) - - def setRetries(self, delay, count): - # see specs. Delay code below 5 can conflict with some ACK lengths - # and count should be set = 0 for non-ACK modes - self.write_register(NRF24.SETUP_RETR, (delay & 0xf) << NRF24.ARD | (count & 0xf)) - - def getRetries(self): - return self.read_register(NRF24.SETUP_RETR) - - def getMaxTimeout(self): # seconds - retries = self.getRetries() - tout = (((250+(250*((retries& 0xf0)>>4 ))) * (retries & 0x0f)) / 1000000.0 * 2) + 0.008 - # Fudged up to about double Barraca's calculation - # Was too short & was timeing out wrongly. BL - return tout diff --git a/SmartHome/hardware/WiFi.py b/SmartHome/hardware/WiFi.py deleted file mode 100644 index 4486e36..0000000 --- a/SmartHome/hardware/WiFi.py +++ /dev/null @@ -1,171 +0,0 @@ -import os -import subprocess -import re -from dataclasses import dataclass -import time - - -__all__ = [ - 'networks', - 'start_hotspot_if_needed', - 'scan', - 'is_connected', - 'save', - 'start_wps', - 'start_hotspot', - 'stop_hotspot', - 'InterfaceBusyException' -] - -network_regex = re.compile( - r'network=\{\s+ssid="(?P.*)"\s+psk="(?P.*)"\s+id_str="(?P.*)"\s+\}' -) - -cell_regex = re.compile( - r'Cell(?:.|\n)*?Frequency:(?P.+)(?:.|\n)*?Quality=(?P.+)(?:.|\n)*?Encryption key:(?P.+)(?:.|\n)*?ESSID:"(?P.+)"' -) - -wpa_supplicant_path = r'/etc/wpa_supplicant/wpa_supplicant.conf' -interfaces_path = r'/etc/network/interfaces' - -wpa_supplicant_header = ''' -country=DE -ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev -ap_scan=1 -update_config=1 -''' - -interfaces_header = ''' -source-directory /etc/network/interfaces.d - -auto lo -auto eth0 -auto wlan0 -auto ap0 - -iface eth0 inet dhcp -iface lo inet loopback - -allow-hotplug ap0 -iface ap0 inet static - address 192.168.10.1 - netmask 255.255.255.0 - hostapd /etc/hostapd/hostapd.conf - -allow-hotplug wlan0 -iface wlan0 inet manual - wpa-roam /etc/wpa_supplicant/wpa_supplicant.conf -''' - -class InterfaceBusyException(Exception): - pass - -@dataclass -class Cell(): - ssid: str - quality: float - frequency: str - encrypted: bool - #encryption_type: str - -@dataclass -class Network: - ssid: str - psk: str - id_str: str = '' - -networks: Network = [] -is_hotspot_running = False - -def read_saved_networks(): - with open(wpa_supplicant_path, 'r') as f: - for match in network_regex.finditer(f.read()): - networks.append( - Network( - **match.groupdict() - ) - ) - -def write_networks(): - with open(wpa_supplicant_path, 'w') as f: - f.write(wpa_supplicant_header) - for i, network in enumerate(networks): - f.write(f''' -network={{ - ssid="{network.ssid}" - psk="{network.psk}" - id_str="AP{i}" -}}''') - with open(interfaces_path, 'w') as f: - f.write(interfaces_header) - for i in range(len(networks)): - f.write(f'iface AP{i} inet dhcp') - -def reconnect(): - os.system('sudo wpa_cli -i wlan0 reconfigure') - -def is_connected() -> bool: - try: - return bool(subprocess.check_output(['iwgetid',])) - except subprocess.CalledProcessError: - return False - -def start_hotspot(): - os.system('service hostapd stop') - os.system('service dnsmasq stop') - os.system('service dhcpcd stop') - os.system('iw dev wlan0 interface add ap0 type __ap') - os.system('service hostapd start') - os.system('service dnsmasq start') - os.system('service dhcpcd start') - -def stop_hotspot(): - os.system('sudo service hostapd stop') - -# Public - -def start_hotspot_if_needed(): - global is_hotspot_running - connected = is_connected() - if not connected and not is_hotspot_running: - start_hotspot() - is_hotspot_running = True - elif connected and is_hotspot_running: - stop_hotspot() - time.sleep(10) - is_hotspot_running = False - -def save(ssid: str, password: str): - # read_saved_networks() - networks.append(Network(ssid, password)) - write_networks() - reconnect() - -def start_wps(): - os.system('wpa_cli -i wlan0 wps_pbc') - -def scan() -> list[Cell]: - try: - output = subprocess.check_output('sudo iwlist wlan0 scan'.split(' '), text = True) - except subprocess.CalledProcessError as exc: - raise InterfaceBusyException from exc - - for match in cell_regex.finditer(output): - match = match.groupdict() - - quality, max = map(float, match['quality'].split(' ')[0].split('/')) - - ssid = match['ssid'].strip() - quality = round(quality / max, 2) - frequency = re.sub(r'\(Channel.*\)', '', match['frequency']).strip() - encrypted = match['encrypted'] == 'on' - - yield Cell(ssid, quality, frequency, encrypted) - -# Main - -if __name__ == '__main__': - read_saved_networks() - if not networks: - start_hotspot() - start_wps() diff --git a/SmartHome/hardware/pi_temp.py b/SmartHome/hardware/pi_temp.py deleted file mode 100644 index 2a4deb0..0000000 --- a/SmartHome/hardware/pi_temp.py +++ /dev/null @@ -1,22 +0,0 @@ -import os -import time -import RPi.GPIO as GPIO - -pin = 3 -cooling = False - -def current_temp(): - temp = os.popen("vcgencmd measure_temp").readline() - return temp.replace("temp=","") - -GPIO.setmode(GPIO.BCM) -GPIO.setup(pin, GPIO.OUT) -while True: - temp = float(current_temp()[:4]) - if temp > 55: - cooling = True - elif temp < 40: - cooling = False - GPIO.output(pin, cooling) - print(f'temp={temp}, cooling={cooling}') - time.sleep(1) diff --git a/SmartHome/main.py b/SmartHome/main.py deleted file mode 100644 index b78d8f5..0000000 --- a/SmartHome/main.py +++ /dev/null @@ -1,21 +0,0 @@ -import uvicorn -from server import app -from client import client, ws -from hardware import WiFi - - -def run(): - ws.start() - client.fetch() - - # try: - # WiFi.connect_first() - # except: # TODO: specify wifi exception - # WiFi.start_hotspot() - # finally: - # print('complete') - - uvicorn.run('main:app', host = '0.0.0.0', port = 8000, reload = False) - -if __name__ == '__main__': - run() diff --git a/SmartHome/managers/DeviceModelManager.py b/SmartHome/managers/DeviceModelManager.py deleted file mode 100644 index 550c09d..0000000 --- a/SmartHome/managers/DeviceModelManager.py +++ /dev/null @@ -1,33 +0,0 @@ -from uuid import UUID -from sqlalchemy import select, update, delete -from sqlalchemy.ext.asyncio import AsyncSession - -import database -from models import DeviceModel, Parameter, DeviceModelParameter -from schemas.device import DeviceModelInfo, DeviceModel as DeviceModelScheme - - -class DeviceModelManager: - session: AsyncSession - - def __init__(self, session: AsyncSession): - self.session = session - - async def save(self, scheme: DeviceModelInfo | DeviceModelScheme): - db: AsyncSession = self.session - - device_model = DeviceModel(id = scheme.id, name = scheme.name) - db.add(device_model) - # if device_model := await db.get(DeviceModel, scheme.id): - # device_model.name = scheme.name - # else: - - if isinstance(scheme, DeviceModelScheme): - for parameter_scheme in scheme.parameters: - # if parameter := await db.get(Parameter, parameter_scheme.id): - # pass - # else: - db.add(Parameter(parameter_scheme.id, parameter_scheme.name, parameter_scheme.value_type)) - db.add(DeviceModelParameter(devicemodel_id = scheme.id, parameter_id = parameter_scheme.id)) - - await db.commit() diff --git a/SmartHome/managers/DevicesManager.py b/SmartHome/managers/DevicesManager.py deleted file mode 100644 index fcee435..0000000 --- a/SmartHome/managers/DevicesManager.py +++ /dev/null @@ -1,122 +0,0 @@ -from uuid import UUID -from fastapi import Depends -from sqlalchemy import select, update, delete -from sqlalchemy.orm import selectinload -from sqlalchemy.ext.asyncio import AsyncSession - -from exceptions.Internal import InternalException, ExceptionCode -from AUID import AUID -import database -from models import ( - User, - Device, - DeviceModel, - DeviceParameterAssociation, - DeviceParameterAssociation -) -from schemas.device import ( - DeviceParameter, - Parameter, - DeviceState, - DevicePatch, - DeviceCreate, - Device as DeviceScheme -) - - -class DevicesManager: - session: AsyncSession - - def __init__(self, session: AsyncSession): - self.session = session - - async def get(self, id: UUID) -> Device: - db: AsyncSession = self.session - if device := await db.get(Device, id): - return device - else: - raise InternalException(ExceptionCode.not_found, 'Device not found') - - async def state(self, id: UUID) -> DeviceState | None: - db: AsyncSession = self.session - device = await self.get(id) - - device_state = DeviceState(**DeviceScheme.from_orm(device).dict()) - - async with database.async_engine.begin() as conn: - parameters = await conn.run_sync(DevicesManager._read_parameters, device) - - device_state.parameters = list(map(DevicesManager._map_parameter, parameters)) - - return device_state - - async def create(self, create_device: DeviceCreate) -> Device: - db: AsyncSession = self.session - - id = AUID(bytes=create_device.id.bytes) - model_id = AUID.new(model=id.items.model, now=None) - urdi = id.items.urdi - - model = await db.get(DeviceModel, model_id) - - if not model: - raise InternalException( - code = ExceptionCode.invalid_format, - msg = 'Unknown device id', - debug = f'Unknown model in auid"{id}"' - ) - - if (await db.scalars(select(Device).where(Device.urdi == urdi))).first(): - raise InternalException( - code = ExceptionCode.invalid_format, - msg = 'Device with this id already exist' - ) - - device = Device( - id = create_device.id, - name = create_device.name, - urdi = urdi, - room_id = create_device.room_id, - model_id = model_id - ) - - for model_parameter in model.parameters: - device_parameter = DeviceParameterAssociation( - device_id = device.id, - parameter_id = model_parameter.parameter.id - ) - db.add(device_parameter) - - db.add(device) - await db.commit() - await db.refresh(device) - - return device - - async def patch(self, id: UUID, device: DevicePatch): - db: AsyncSession = self.session - values = {key: value for key, value in device.dict().items() if key != 'id'} - await db.execute(update(Device).values(**values).where(Device.id == id)) - await db.commit() - - async def delete(self, device_id: UUID): - db: AsyncSession = self.session - device = await self.get(device_id) - if device: - await db.delete(device) - await db.commit() - else: - raise InternalException( - code = ExceptionCode.not_found, - msg = 'Device not found' - ) - - @staticmethod - def _read_parameters(_, device: Device) -> list[DeviceParameter]: - return device.parameters - - @staticmethod - def _map_parameter(association: DeviceParameterAssociation) -> DeviceParameter: - parameter = Parameter.from_orm(association.parameter) - device_parameter = DeviceParameter(**{**parameter.dict(), 'value': association.value}) - return device_parameter diff --git a/SmartHome/managers/HouseManager.py b/SmartHome/managers/HouseManager.py deleted file mode 100644 index e9bb625..0000000 --- a/SmartHome/managers/HouseManager.py +++ /dev/null @@ -1,45 +0,0 @@ -from uuid import UUID -from fastapi import Depends -from sqlalchemy import select, delete, update -from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.exc import NoResultFound - -from exceptions.Internal import InternalException, ExceptionCode -from models import House -from server.dependencies import database -from schemas.house import HousePatch - - -class HouseManager: - session: AsyncSession - - def __init__(self, session: AsyncSession): - self.session = session - - async def get(self) -> House: - db: AsyncSession = self.session - try: - result = await db.scalars(select(House)) - return result.one() - except NoResultFound: - raise InternalException(ExceptionCode.not_initialized) - - async def create(self, house_id: UUID) -> House: - db: AsyncSession = self.session - - try: - await db.delete(await self.get()) - except InternalException: - pass - - house = House(id = house_id, name = '') - db.add(house) - await db.commit() - return house - - async def patch(self, house: HousePatch): - db: AsyncSession = self.session - await db.execute( - update(House) - .values(**house.dict()) - ) diff --git a/SmartHome/managers/HubManager.py b/SmartHome/managers/HubManager.py deleted file mode 100644 index 036d7c1..0000000 --- a/SmartHome/managers/HubManager.py +++ /dev/null @@ -1,124 +0,0 @@ -from uuid import UUID -from fastapi import Depends -from sqlalchemy import select, update -from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.exc import NoResultFound - -import config -from exceptions.Internal import InternalException, ExceptionCode -from hardware import WiFi -from models import User, Hub -from schemas import hub as schemas -from managers import HouseManager -from server.dependencies import database, auth -from server.auth import UserToken, UserAuthManager, AuthException - - -class HubManager: - session: AsyncSession - token: auth.UserToken | None - user: User | None = None - - def __init__(self, session: AsyncSession, token: UserToken = None): - self.session = session - self.token = token - - async def get(self) -> Hub | None: - db: AsyncSession = self.session - try: - result = await db.scalars(select(Hub)) - return result.one() - except NoResultFound: - raise InternalException(ExceptionCode.not_initialized) - - async def parse_token(self, raw_token: str): - db: AsyncSession = self.session - try: - self.token = UserAuthManager().validate_access(raw_token) - except AuthException: - raise InternalException(ExceptionCode.unauthorized) - self.user = await db.get(User, self.token.user_id) - - async def init(self, create_hub: schemas.HubInit, raw_token: str) -> Hub: - db: AsyncSession = self.session - - try: - hub = await self.get() - except InternalException: - hub = None - - if hub: - await self.parse_token(raw_token) - await self.check_access() - await db.delete(hub) - self.save_tokens(create_hub) - else: - self.save_credentials(create_hub) - await self.parse_token(raw_token) - - house_manager = HouseManager(db) - await house_manager.create(create_hub.house_id) - - hub = Hub(id = create_hub.id, name = create_hub.name, house_id = create_hub.house_id) - db.add(hub) - - if not self.user: - user = User(id = self.token.user_id, name = '') - db.add(user) - - await db.commit() - return hub - - async def patch(self, hub: schemas.HubPatch): - db: AsyncSession = self.session - values = {key: value for key, value in hub.dict().items() if value != None} - - await db.execute(update(Hub).values(**values)) - await db.commit() - - def wifi(self, ssid: str, password: str): - WiFi.save(ssid, password) - - def start_wps(self): - WiFi.start_wps() - - def is_connected(self) -> bool: - return WiFi.is_connected() - - def stop_hotspot(self): - WiFi.stop_hotspot() - - def get_hotspots(self) -> list[schemas.Hotspot]: - try: - return list(WiFi.scan()) - except WiFi.InterfaceBusyException: - return [] - - def save_credentials(self, credentials: schemas.HubAuthItems): - self.save_tokens(credentials) - - config.public_key = credentials.public_key - with open(f'{config.src}/jwt.key.pub', 'w') as f: - f.write(credentials.public_key) - - def save_tokens(self, tokens_pair: schemas.HubAuthItems): - config.access_token = tokens_pair.access_token - with open(f'{config.src}/jwt_access_token', 'w') as f: - f.write(tokens_pair.access_token) - - config.refresh_token = tokens_pair.refresh_token - with open(f'{config.src}/jwt_refresh_token', 'w') as f: - f.write(tokens_pair.refresh_token) - - async def check_access(self): - if self.user: - return - - if not self.token: - raise InternalException(ExceptionCode.access_denied) - - db: AsyncSession = self.session - self.user = await db.get(User, self.token.user_id) - - if not self.user: - raise InternalException(ExceptionCode.access_denied) diff --git a/SmartHome/managers/RoomsManager.py b/SmartHome/managers/RoomsManager.py deleted file mode 100644 index 88ec3ca..0000000 --- a/SmartHome/managers/RoomsManager.py +++ /dev/null @@ -1,49 +0,0 @@ -from uuid import UUID - -from fastapi import Depends -from sqlalchemy import select, update, delete -from sqlalchemy.orm import selectinload -from sqlalchemy.ext.asyncio import AsyncSession - -from exceptions.Internal import InternalException, ExceptionCode -from models import User, Room -from schemas import room as schemas -from server import endpoints - - -class RoomsManager: - session: AsyncSession - - def __init__(self, session: AsyncSession): - self.session = session - - async def get(self, id: UUID) -> Room: - db: AsyncSession = self.session - result = await db.scalars( - select(Room).where(Room.id == id).options(selectinload(Room.devices)) - ) - if room := result.first(): - return room - else: - raise InternalException(ExceptionCode.not_found, 'Room not found') - - async def create(self, create_room: schemas.RoomCreate) -> Room: - db: AsyncSession = self.session - house = await endpoints.house.HouseManager(db).get() - room = Room(name = create_room.name, house_id = house.id, devices = []) - db.add(room) - await db.commit() - - return room - - async def patch(self, id: UUID, room: schemas.RoomPatch): - db: AsyncSession = self.session - values = {key: value for key, value in room.dict().items() if key != 'id'} - await db.execute(update(Room).values(**values).where(Room.id == id)) - await db.commit() - - async def delete(self, room_id: UUID): - db: AsyncSession = self.session - room = await self.get(room_id) - await db.delete(room) - await db.commit() diff --git a/SmartHome/managers/WSManager.py b/SmartHome/managers/WSManager.py deleted file mode 100644 index f2c6a5d..0000000 --- a/SmartHome/managers/WSManager.py +++ /dev/null @@ -1,71 +0,0 @@ -from sqlalchemy import select -from sqlalchemy.ext.asyncio import AsyncSession -from pydantic.error_wrappers import ValidationError - -import database -from hardware.Merlin import merlin, MerlinMessage -from models import Device, DeviceModelParameter -from schemas.ws import ( - SocketType, - SocketData, - MerlinData, - RestRequest -) -from client import api - - -class WSManager: - session: AsyncSession - - def __inti__(self, session: AsyncSession): - self.session = session - - async def handle_message(self, msg: str) -> dict | None: - socket = SocketData.parse_raw(msg) - try: - socket = SocketData.parse_raw(msg) - except ValidationError: - return - - match socket.type: - case SocketType.merlin: - try: - merlin_data = MerlinData(**socket.data) - except ValidationError: - return - await self.merlin_send(merlin_data) - - case SocketType.rest: - try: - rest_request = RestRequest(**socket.data) - except ValidationError: - return - if local_response := await api.local_request(rest_request): - # TODO: pydantic model - return {'path': rest_request.path, 'data': local_response} - - async def merlin_send(self, data: MerlinData): - db: AsyncSession = self.session - try: - if message := await self._get_message(db, data): - merlin.send(message) - finally: - await db.close() - - async def _get_message(self, db: AsyncSession, data: MerlinData) -> MerlinMessage | None: - device = await db.get(Device, data.device_id) - - if not device: - return None - - result = await db.execute( - select(DeviceModelParameter) - .where( - DeviceModelParameter.devicemodel_id == device.model.id, - DeviceModelParameter.parameter_id == data.parameter_id - ) - ) - - model_parameter = result.scalar_one() - - return MerlinMessage(device.urdi, model_parameter.f, int(data.value)) diff --git a/SmartHome/managers/__init__.py b/SmartHome/managers/__init__.py deleted file mode 100644 index e2a484d..0000000 --- a/SmartHome/managers/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from .DeviceModelManager import DeviceModelManager -from .DevicesManager import DevicesManager -from .HouseManager import HouseManager -from .HubManager import HubManager -from .RoomsManager import RoomsManager -from .WSManager import WSManager diff --git a/SmartHome/models/Base.py b/SmartHome/models/Base.py deleted file mode 100644 index 28df86d..0000000 --- a/SmartHome/models/Base.py +++ /dev/null @@ -1,13 +0,0 @@ -from sqlalchemy.ext.declarative import declarative_base, declared_attr - - -class BaseClass(object): - - @declared_attr - def __tablename__(cls): - return f'{cls.__name__.lower()}s' - - def __str__(self): - return f'{type(self).__name__}({self.id.hex[:8]})' - -Base = declarative_base(cls = BaseClass) diff --git a/SmartHome/models/Device.py b/SmartHome/models/Device.py deleted file mode 100644 index e38c23c..0000000 --- a/SmartHome/models/Device.py +++ /dev/null @@ -1,34 +0,0 @@ -from uuid import uuid1 -from sqlalchemy import Column, String, Integer, ForeignKey, UniqueConstraint -from sqlalchemy_utils import UUIDType -from sqlalchemy.orm import relationship -from .Base import Base - - -class DeviceParameterAssociation(Base): # TODO: remove - id = Column(UUIDType, index = True, primary_key = True, default = uuid1) - device_id = Column(ForeignKey('devices.id'), primary_key = True) - parameter_id = Column(ForeignKey('parameters.id'), primary_key = True) - value = Column(Integer, nullable = False, default = 0) - parameter = relationship('Parameter', lazy = 'selectin') - device = relationship('Device') - __table_args__ = (UniqueConstraint('device_id', 'parameter_id'),) - - def __str__(self): - try: - return self.parameter.name or super().__str__() - except: - return super().__str__() - -class Device(Base): - id = Column(UUIDType, index = True, primary_key = True, default = uuid1) - name = Column(String) - urdi = Column(Integer, unique = True, nullable = False) - room_id = Column(UUIDType, ForeignKey('rooms.id'), nullable = False) - model_id = Column(UUIDType, ForeignKey('devicemodels.id'), nullable = False) - room = relationship('Room', back_populates = 'devices') - model = relationship('DeviceModel', lazy = 'selectin') - parameters = relationship('DeviceParameterAssociation', back_populates = 'device', cascade = 'all, delete-orphan') - - def __str__(self): - return self.name or super().__str__() diff --git a/SmartHome/models/DeviceModel.py b/SmartHome/models/DeviceModel.py deleted file mode 100644 index a8b4e80..0000000 --- a/SmartHome/models/DeviceModel.py +++ /dev/null @@ -1,26 +0,0 @@ -from uuid import uuid1 -from sqlalchemy import Table, Column, String, Integer, ForeignKey, UniqueConstraint -from sqlalchemy_utils import UUIDType -from sqlalchemy.orm import relationship -from .Base import Base - - -class DeviceModelParameter(Base): - id = Column(UUIDType, index = True, primary_key = True, default = uuid1) # TODO: remove - devicemodel_id = Column(ForeignKey('devicemodels.id'), primary_key = True) - parameter_id = Column(ForeignKey('parameters.id'), primary_key = True) - f = Column(Integer, nullable = False, default = 0) - parameter = relationship('Parameter', lazy = 'selectin') - devicemodel = relationship('DeviceModel') - __table_args__ = (UniqueConstraint('devicemodel_id', 'parameter_id'),) - - def __str__(self): - return self.parameter.name or super().__str__() - -class DeviceModel(Base): - id = Column(UUIDType, index = True, primary_key = True, default = uuid1) - name = Column(String) - parameters = relationship('DeviceModelParameter', back_populates = 'devicemodel', cascade = 'all, delete-orphan', lazy = 'selectin') - - def __str__(self): - return self.name or super().__str__() diff --git a/SmartHome/models/House.py b/SmartHome/models/House.py deleted file mode 100644 index af80d53..0000000 --- a/SmartHome/models/House.py +++ /dev/null @@ -1,15 +0,0 @@ -from uuid import uuid1 -from sqlalchemy import Column, String, ForeignKey -from sqlalchemy_utils import UUIDType -from sqlalchemy.orm import relationship -from .Base import Base - - -class House(Base): - id = Column(UUIDType, index = True, primary_key = True, default = uuid1) - name = Column(String) - hubs = relationship('Hub', back_populates = 'house', cascade = 'all, delete-orphan', lazy = 'selectin') - rooms = relationship('Room', back_populates = 'house', cascade = 'all, delete-orphan', lazy = 'selectin') - - def __str__(self): - return self.name or super().__str__() diff --git a/SmartHome/models/Hub.py b/SmartHome/models/Hub.py deleted file mode 100644 index f5c2ace..0000000 --- a/SmartHome/models/Hub.py +++ /dev/null @@ -1,15 +0,0 @@ -from uuid import uuid1 -from sqlalchemy import Column, String, ForeignKey -from sqlalchemy_utils import UUIDType -from sqlalchemy.orm import relationship -from .Base import Base - - -class Hub(Base): - id = Column(UUIDType, index = True, primary_key = True, default = uuid1) - name = Column(String) - house_id = Column(UUIDType, ForeignKey('houses.id')) - house = relationship('House', back_populates = 'hubs') - - def __str__(self): - return self.name or super().__str__() diff --git a/SmartHome/models/Parameter.py b/SmartHome/models/Parameter.py deleted file mode 100644 index 1a6fadc..0000000 --- a/SmartHome/models/Parameter.py +++ /dev/null @@ -1,16 +0,0 @@ -from uuid import uuid1 -from sqlalchemy import Column, String, Integer, ForeignKey -from sqlalchemy.orm import relationship -from sqlalchemy_utils import UUIDType -from .Base import Base - - -class Parameter(Base): # TODO: remove - id = Column(UUIDType, index = True, primary_key = True, default = uuid1) - name = Column(String, nullable = False) - value_type = Column(String, nullable = False) - device_parameters = relationship('DeviceParameterAssociation', back_populates = 'parameter', cascade = 'all, delete-orphan') - devicemodel_parameters = relationship('DeviceModelParameter', back_populates = 'parameter', cascade = 'all, delete-orphan') - - def __str__(self): - return self.name or super().__str__() diff --git a/SmartHome/models/Room.py b/SmartHome/models/Room.py deleted file mode 100644 index 130a5dd..0000000 --- a/SmartHome/models/Room.py +++ /dev/null @@ -1,16 +0,0 @@ -from uuid import uuid1 -from sqlalchemy import Column, String, ForeignKey -from sqlalchemy_utils import UUIDType -from sqlalchemy.orm import relationship -from .Base import Base - - -class Room(Base): - id = Column(UUIDType, index = True, primary_key = True, default = uuid1) - name = Column(String) - house_id = Column(UUIDType, ForeignKey('houses.id'), nullable = False) - house = relationship('House', back_populates = 'rooms') - devices = relationship('Device', back_populates = 'room', cascade = 'all, delete-orphan') - - def __str__(self): - return self.name or super().__str__() diff --git a/SmartHome/models/User.py b/SmartHome/models/User.py deleted file mode 100644 index ede55a6..0000000 --- a/SmartHome/models/User.py +++ /dev/null @@ -1,9 +0,0 @@ -from uuid import uuid1 -from sqlalchemy import Column, String -from sqlalchemy_utils import UUIDType -from .Base import Base - - -class User(Base): - id = Column(UUIDType, index = True, primary_key = True, default = uuid1) - name = Column(String) diff --git a/SmartHome/models/__init__.py b/SmartHome/models/__init__.py deleted file mode 100644 index 1b0b854..0000000 --- a/SmartHome/models/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -from .Base import Base -from .User import User -from .House import House -from .Hub import Hub -from .Room import Room -from .Device import Device, DeviceParameterAssociation -from .DeviceModel import DeviceModel, DeviceModelParameter -from .Parameter import Parameter diff --git a/SmartHome/schemas/device.py b/SmartHome/schemas/device.py deleted file mode 100644 index 166b23a..0000000 --- a/SmartHome/schemas/device.py +++ /dev/null @@ -1,32 +0,0 @@ -from typing import Optional -from uuid import UUID -from pydantic import BaseModel -from AUID import AUID -from .parameters import DeviceParameter, Parameter - - -class DeviceModelInfo(BaseModel): - id: UUID - name: str - - class Config: - orm_mode = True - -class DeviceModel(DeviceModelInfo): - parameters: list[Parameter] - -class DevicePatch(BaseModel): - name: str - room_id: UUID - -class DeviceCreate(DevicePatch): - id: AUID - -class Device(DeviceCreate): - model: DeviceModel - - class Config: - orm_mode = True - -class DeviceState(Device): - parameters: list[DeviceParameter] = [] diff --git a/SmartHome/schemas/house.py b/SmartHome/schemas/house.py deleted file mode 100644 index b1dc0c4..0000000 --- a/SmartHome/schemas/house.py +++ /dev/null @@ -1,16 +0,0 @@ -from uuid import UUID -from pydantic import BaseModel -from .hub import Hub -from .room import RoomInfo - - -class HousePatch(BaseModel): - id: UUID - name: str - - class Config: - orm_mode = True - -class House(HousePatch): - hubs: list[Hub] - rooms: list[RoomInfo] diff --git a/SmartHome/schemas/hub.py b/SmartHome/schemas/hub.py deleted file mode 100644 index 7c91bb8..0000000 --- a/SmartHome/schemas/hub.py +++ /dev/null @@ -1,44 +0,0 @@ -from uuid import UUID -from pydantic import BaseModel - - -# Auth - -class TokensPair(BaseModel): - access_token: str - refresh_token: str - -class HubAuthItems(TokensPair): - public_key: str - -# Hub - -class Hub(BaseModel): - id: UUID - name: str - house_id: UUID - - class Config: - orm_mode = True - -class HubPatch(BaseModel): - name: str - -class HubInit(Hub, HubAuthItems): - ... - -# WiFi - -class Hotspot(BaseModel): - ssid: str - quality: float - frequency: str - encrypted: bool - #encryption_type: str - - class Config: - orm_mode = True - -class WifiConnection(BaseModel): - ssid: str - password: str diff --git a/SmartHome/schemas/parameters.py b/SmartHome/schemas/parameters.py deleted file mode 100644 index 4a80bea..0000000 --- a/SmartHome/schemas/parameters.py +++ /dev/null @@ -1,22 +0,0 @@ -from uuid import UUID -from pydantic import BaseModel -from models import DeviceModelParameter - - -class Parameter(BaseModel): - id: UUID - name: str - value_type: str - - class Config: - orm_mode = True - - @classmethod - def from_orm(cls, obj): - if isinstance(obj, DeviceModelParameter): - return super().from_orm(obj.parameter) - else: - return super().from_orm(obj) - -class DeviceParameter(Parameter): - value: int diff --git a/SmartHome/schemas/room.py b/SmartHome/schemas/room.py deleted file mode 100644 index 09d7c09..0000000 --- a/SmartHome/schemas/room.py +++ /dev/null @@ -1,20 +0,0 @@ -from uuid import UUID -from pydantic import BaseModel -from .device import Device - - -class RoomCreate(BaseModel): - name: str - -class RoomPatch(RoomCreate): - pass - -class RoomInfo(BaseModel): - id: UUID - name: str - - class Config: - orm_mode = True - -class Room(RoomInfo): - devices: list[Device] diff --git a/SmartHome/schemas/ws.py b/SmartHome/schemas/ws.py deleted file mode 100644 index 1c8e3ae..0000000 --- a/SmartHome/schemas/ws.py +++ /dev/null @@ -1,36 +0,0 @@ -from typing import Any -from enum import Enum -from pydantic import BaseModel - - -# MARK: - Bridge - -class HttpMethod(str, Enum): - get = 'get' - post = 'post' - patch = 'patch' - delete = 'delete' - -BridgeRequestBody = dict[str, Any] | BaseModel - -class RestRequest(BaseModel): - path: str - method: HttpMethod - body: BridgeRequestBody - -# MARK: - Merlin - -class MerlinData(BaseModel): - device_id: str - parameter_id: str - value: str - -# MARK: - Socket - -class SocketType(str, Enum): - merlin = 'merlin' - rest = 'rest' - -class SocketData(BaseModel): - type: SocketType - data: dict[str, Any] diff --git a/SmartHome/server/__init__.py b/SmartHome/server/__init__.py deleted file mode 100644 index 8da23d2..0000000 --- a/SmartHome/server/__init__.py +++ /dev/null @@ -1,39 +0,0 @@ -from fastapi import FastAPI, APIRouter -from fastapi.staticfiles import StaticFiles - -import models -import database -from server import exceptions_handlers, endpoints - -models.Base.metadata.create_all(bind = database.engine) - -description = ''' -[**Admin**](/admin) - -[**GitHub**](https://github.com/MarkParker5/ArchieCloud) - -[**FastAPI**](https://fastapi.tiangolo.com) -''' - -app = FastAPI( - title = 'Archie Hub', - description = description, - version = '0.0.1', - swagger_ui_parameters = { - 'syntaxHighlight.theme': 'obsidian', - 'docExpansion': 'none', - } -) - -exceptions_handlers.setup(app) - -api = APIRouter(prefix = '/api') -api.include_router(endpoints.house.router) -api.include_router(endpoints.hub.router) -api.include_router(endpoints.room.router) -api.include_router(endpoints.device.router) - -app.include_router(api) -app.include_router(endpoints.ws.router) - -endpoints.admin.setup(app) diff --git a/SmartHome/server/auth/BaseAuth.py b/SmartHome/server/auth/BaseAuth.py deleted file mode 100644 index 6ca49c4..0000000 --- a/SmartHome/server/auth/BaseAuth.py +++ /dev/null @@ -1,60 +0,0 @@ -import time -from abc import ABC, abstractmethod -from uuid import UUID -from enum import Enum - -from pydantic import BaseModel -from jose import jwt, JWTError -from jose.exceptions import JWKError - -import config -from .exceptions import AuthException - - -class TokenType(str, Enum): - access = 'access' - refresh = 'refresh' - -class TokensPair(BaseModel): - access_token: str - refresh_token: str - -class HubAuthItem(TokensPair): - public_key: str - -class BaseToken(BaseModel): - type: TokenType - exp: float - -class BaseAuthManager(ABC): - - @abstractmethod - def _get_parsed_token(self, payload: dict) -> BaseToken: - pass - - def validate_access(self, raw_token: str) -> BaseToken: - token = self._parse_token(raw_token) - - if not token: - raise AuthException() - - if token.type != TokenType.access: - raise AuthException() - - if not self._is_valid_token(token): - raise AuthException() - - return token - - def _parse_token(self, raw_token: str) -> BaseToken: - try: - payload = jwt.decode(raw_token, config.public_key, algorithms = [config.algorithm]) - token = self._get_parsed_token(payload) - except JWTError: - raise AuthException() - except JWKError: - raise AuthException() - return token - - def _is_valid_token(self, token: BaseToken) -> bool: - return time.time() <= token.exp diff --git a/SmartHome/server/auth/UserAuth.py b/SmartHome/server/auth/UserAuth.py deleted file mode 100644 index d7b3a30..0000000 --- a/SmartHome/server/auth/UserAuth.py +++ /dev/null @@ -1,21 +0,0 @@ -import time -from uuid import UUID -from datetime import timedelta -from jose import jwt -import config -from .BaseAuth import BaseAuthManager, BaseToken, TokenType - - -class UserToken(BaseToken): - user_id: UUID - is_admin: bool - -class UserAuthManager(BaseAuthManager): - - # abstract impl - def _get_parsed_token(self, payload: dict) -> UserToken: - return UserToken(**payload) - - # override - def _is_valid_token(self, token: UserToken) -> bool: - return super()._is_valid_token(token) and token.user_id != None diff --git a/SmartHome/server/auth/__init__.py b/SmartHome/server/auth/__init__.py deleted file mode 100644 index 3a5c7e4..0000000 --- a/SmartHome/server/auth/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -from .BaseAuth import ( - TokenType, - TokensPair -) -from .UserAuth import ( - UserToken, - UserAuthManager -) -from .exceptions import AuthException diff --git a/SmartHome/server/auth/exceptions.py b/SmartHome/server/auth/exceptions.py deleted file mode 100644 index 189cbc0..0000000 --- a/SmartHome/server/auth/exceptions.py +++ /dev/null @@ -1,2 +0,0 @@ -class AuthException(Exception): - pass diff --git a/SmartHome/server/dependencies/__init__.py b/SmartHome/server/dependencies/__init__.py deleted file mode 100644 index 0473d69..0000000 --- a/SmartHome/server/dependencies/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from . import database -from . import auth diff --git a/SmartHome/server/dependencies/auth.py b/SmartHome/server/dependencies/auth.py deleted file mode 100644 index 484bb7f..0000000 --- a/SmartHome/server/dependencies/auth.py +++ /dev/null @@ -1,37 +0,0 @@ -from fastapi import Depends -from fastapi.security import OAuth2PasswordBearer - -from exceptions.Internal import InternalException, ExceptionCode -from models import User -from ..auth import ( - UserToken, - UserAuthManager, - AuthException -) -from .database import get_async_session - - -oauth2_scheme = OAuth2PasswordBearer(tokenUrl = '/api/user/login') -optional_oauth2_scheme = OAuth2PasswordBearer(tokenUrl = '/api/user/login', auto_error=False) -userAuthManager = UserAuthManager() - -async def validate_user(raw_token: str = Depends(oauth2_scheme), session = Depends(get_async_session)) -> User: - token = userAuthManager.validate_access(raw_token) - user = await session.get(User, token.user_id) - if not user: - raise AuthException() - return user - -def validate_token(token: str = Depends(oauth2_scheme)) -> UserToken: - return userAuthManager.validate_access(token) - -def optional_token(token: str = Depends(optional_oauth2_scheme)) -> UserToken | None: - if not token: - return None - try: - return userAuthManager.validate_access(token) - except AuthException: - return None - -def raw_token(token: str = Depends(oauth2_scheme)) -> str: - return token diff --git a/SmartHome/server/dependencies/database.py b/SmartHome/server/dependencies/database.py deleted file mode 100644 index 053b754..0000000 --- a/SmartHome/server/dependencies/database.py +++ /dev/null @@ -1,15 +0,0 @@ -from database import ( - create_session, - create_async_session, - Session, - AsyncSession -) - -def get_session() -> Session: - with create_session() as session: - yield session - - -async def get_async_session() -> AsyncSession: - async with create_async_session() as session: - yield session diff --git a/SmartHome/server/endpoints/__init__.py b/SmartHome/server/endpoints/__init__.py deleted file mode 100644 index cbb7c27..0000000 --- a/SmartHome/server/endpoints/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from . import admin -from . import device -from . import house -from . import hub -from . import room -from . import ws diff --git a/SmartHome/server/endpoints/admin.py b/SmartHome/server/endpoints/admin.py deleted file mode 100644 index baaf667..0000000 --- a/SmartHome/server/endpoints/admin.py +++ /dev/null @@ -1,83 +0,0 @@ -from fastapi import FastAPI -from sqladmin import Admin, ModelAdmin, SidebarLink -import database -from models import ( - Base, - House, - Hub, - Room, - DeviceModel, - DeviceModelParameter, - Device, - Parameter, - DeviceParameterAssociation -) - - -class HouseAdmin(ModelAdmin, model = House): - icon = 'fa-solid fa-house' - column_list = [House.name, House.rooms] - column_details_exclude_list = [House.id] - form_excluded_columns = [House.id] - -class HubAdmin(ModelAdmin, model = Hub): - icon = 'fa-solid fa-wifi' - column_list = [Hub.name, Hub.house] - column_details_exclude_list = [Hub.id, Hub.house_id] - form_excluded_columns = [Hub.id, Hub.house_id] - -class RoomAdmin(ModelAdmin, model = Room): - icon = 'fa-solid fa-house-user' - column_list = [Room.name, Room.house, Room.devices] - column_details_list = [Room.name, Room.house, Room.devices] - form_excluded_columns = [Room.house_id] - -class DeviceModelAdmin(ModelAdmin, model = DeviceModel): - icon = 'fa-solid fa-lightbulb' - column_list = [DeviceModel.name, DeviceModel.parameters] - column_details_list = [DeviceModel.name, DeviceModel.parameters] - -class DeviceModelParameterAdmin(ModelAdmin, model = DeviceModelParameter): - icon = 'fa-solid fa-arrow-down-1-9' - column_list = [DeviceModelParameter.f, DeviceModelParameter.parameter, DeviceModelParameter.devicemodel] - column_details_list = column_list - form_columns = column_details_list - -class DeviceAdmin(ModelAdmin, model = Device): - icon = 'fa-solid fa-microchip' - column_list = [Device.name, Device.urdi, Device.model, Device.room, Device.parameters] - column_details_list = [Device.name, Device.urdi, Device.model, Device.room, Device.parameters] - form_excluded_columns = [Device.room_id, Device.model_id, Device.parameters] - -class ParameterAdmin(ModelAdmin, model = Parameter): - icon = 'fa-solid fa-sliders' - column_list = [Parameter.name, Parameter.value_type] - column_details_list = [Parameter.name, Parameter.value_type] - form_columns = [Parameter.name, Parameter.value_type] - -class DeviceParameterAdmin(ModelAdmin, model = DeviceParameterAssociation): - name = 'Device Parameter' - name_plural = 'Device Parameters' - icon = 'fa-solid fa-gears' - column_list = [DeviceParameterAssociation.device, DeviceParameterAssociation.parameter, DeviceParameterAssociation.value] - column_details_list = column_list - form_columns = column_list - -links = [ - SidebarLink('API', '/docs', True), -] - -def setup(app: FastAPI): - admin = Admin(app, - engine = database.engine, - title = 'Archie Hub - Admin', - links = links - ) - admin.register_model(HouseAdmin) - admin.register_model(RoomAdmin) - admin.register_model(HubAdmin) - admin.register_model(DeviceModelAdmin) - admin.register_model(DeviceModelParameterAdmin) - admin.register_model(ParameterAdmin) - admin.register_model(DeviceParameterAdmin) - admin.register_model(DeviceAdmin) diff --git a/SmartHome/server/endpoints/device.py b/SmartHome/server/endpoints/device.py deleted file mode 100644 index db8dc1c..0000000 --- a/SmartHome/server/endpoints/device.py +++ /dev/null @@ -1,43 +0,0 @@ -from uuid import UUID -from fastapi import APIRouter, Depends - -from exceptions.Internal import InternalException, ExceptionCode -from managers import DevicesManager -from schemas.device import ( - Device, - DeviceState, - DeviceCreate, - DevicePatch -) -from server.dependencies.database import get_async_session, AsyncSession -from server.dependencies.auth import validate_user - - -router = APIRouter( - prefix = '/device', - tags = ['device'], -) - -# MARK: - dependencies - -async def manager(session = Depends(get_async_session), - user = Depends(validate_user)) -> DevicesManager: - return DevicesManager(session) - -# MARK: - endpoints - -@router.post('', response_model = Device) -async def create_device(device: DeviceCreate, manager = Depends(manager)): - return await manager.create(device) - -@router.get('/{id}', response_model = DeviceState) -async def get_device(id: UUID, manager = Depends(manager)): - return await manager.state(id) - -@router.patch('/{id}') -async def patch_device(id: UUID, device: DevicePatch, manager = Depends(manager)): - await manager.patch(id, device) - -@router.delete('/{id}') -async def delete_device(id: UUID, manager = Depends(manager)): - await manager.delete(id) diff --git a/SmartHome/server/endpoints/house.py b/SmartHome/server/endpoints/house.py deleted file mode 100644 index 177afc2..0000000 --- a/SmartHome/server/endpoints/house.py +++ /dev/null @@ -1,25 +0,0 @@ -from uuid import UUID -from fastapi import APIRouter, Depends -from managers import HouseManager -from schemas.house import House -from server.dependencies.database import get_async_session, AsyncSession -from server.dependencies.auth import validate_user - - -router = APIRouter( - prefix = '/house', - tags = ['house'], -) - -# MARK: - dependencies - -async def session(session = Depends(get_async_session), - user = Depends(validate_user)) -> AsyncSession: - return session - -# MARK: - endpoints - -@router.get('', response_model = House) -async def get_house(session = Depends(session)): - manager = HouseManager(session) - return await manager.get() diff --git a/SmartHome/server/endpoints/hub.py b/SmartHome/server/endpoints/hub.py deleted file mode 100644 index a13ce87..0000000 --- a/SmartHome/server/endpoints/hub.py +++ /dev/null @@ -1,62 +0,0 @@ -from fastapi import APIRouter, Depends, BackgroundTasks -from managers import HubManager -from schemas.hub import ( - HubInit, - Hub, - HubPatch, - TokensPair, - Hotspot, - WifiConnection -) -from server.dependencies.database import get_async_session, AsyncSession -from server.dependencies.auth import validate_user, raw_token - - -router = APIRouter( - prefix = '/hub', - tags = ['hub'], -) - -# MARK: - dependencies - -async def manager(session = Depends(get_async_session)) -> HubManager: - return HubManager(session) - -async def manager_auth(manager = Depends(manager), - user = Depends(validate_user)) -> HubManager: - return manager - -# MARK: - endpoints - -@router.post('', response_model = Hub) -async def init_hub(hub_init: HubInit, - manager = Depends(manager), - raw_token: str = Depends(raw_token)): - return await manager.init(hub_init, raw_token) - -@router.get('', response_model = Hub) -async def get_hub(manager = Depends(manager_auth)): - return await manager.get() - -@router.patch('') -async def patch_hub(hub: HubPatch, manager = Depends(manager_auth)): - await manager.patch(hub) - -@router.post('/connect') -async def connect_to_wifi(wifi: WifiConnection, manager = Depends(manager_auth)): - manager.wifi(wifi.ssid, wifi.password) - -@router.get('/hotspots', response_model = list[Hotspot]) -def get_hub_hotspots(manager = Depends(manager_auth)): - return manager.get_hotspots() - -@router.get('/is_connected', response_model=bool) -def is_connected(bg_tasks: BackgroundTasks, manager = Depends(manager_auth)): - connected = manager.is_connected() - if connected: - bg_tasks.add_task(manager.stop_hotspot) - return connected - -@router.post('/set_tokens') -async def set_tokens(tokens: TokensPair, manager = Depends(manager_auth)): - manager.save_tokens(tokens) diff --git a/SmartHome/server/endpoints/room.py b/SmartHome/server/endpoints/room.py deleted file mode 100644 index b32dfdc..0000000 --- a/SmartHome/server/endpoints/room.py +++ /dev/null @@ -1,36 +0,0 @@ -from uuid import UUID -from fastapi import APIRouter, Depends -from managers import RoomsManager -from schemas.room import Room, RoomCreate, RoomPatch -from server.dependencies.database import get_async_session, AsyncSession -from server.dependencies.auth import validate_user, raw_token - - -router = APIRouter( - prefix = '/room', - tags = ['room'], -) - -# MARK: - dependencies - -async def manager(session = Depends(get_async_session), - user = Depends(validate_user)) -> RoomsManager: - return RoomsManager(session) - -# MARK: - endpoints - -@router.post('', response_model = Room) -async def create_room(room: RoomCreate, manager = Depends(manager)): - return await manager.create(room) - -@router.get('/{id}', response_model = Room) -async def get_room(id: UUID, manager = Depends(manager)): - return await manager.get(id) - -@router.patch('/{id}') -async def patch_room(id: UUID, room: RoomPatch, manager = Depends(manager)): - await manager.patch(id, room) - -@router.delete('/{id}') -async def delete_room(id: UUID, manager = Depends(manager)): - await manager.delete(id) diff --git a/SmartHome/server/endpoints/ws.py b/SmartHome/server/endpoints/ws.py deleted file mode 100644 index 82d63a9..0000000 --- a/SmartHome/server/endpoints/ws.py +++ /dev/null @@ -1,37 +0,0 @@ -from fastapi import APIRouter, WebSocket, WebSocketDisconnect -from managers import WSManager - - -class ConnectionManager: - active_connections: list[WebSocket] - wsmanager: WSManager - - def __init__(self): - self.active_connections: list[WebSocket] = [] - self.wsmanager = WSManager() - - async def connect(self, websocket: WebSocket): - await websocket.accept() - self.active_connections.append(websocket) - - def disconnect(self, websocket: WebSocket): - self.active_connections.remove(websocket) - - async def handle_socket(self, websocket: WebSocket, msg: str): - await self.wsmanager.handle_message(msg) - -connection = ConnectionManager() -router = APIRouter( - prefix = '/ws', - tags = ['ws',] -) - -@router.websocket('/') -async def websocket_endpoint(websocket: WebSocket): - await connection.connect(websocket) - try: - while True: - msg = await websocket.receive_text() - await connection.handle_socket(websocket, msg) - except WebSocketDisconnect: - connection.disconnect(websocket) diff --git a/SmartHome/server/exceptions_handlers.py b/SmartHome/server/exceptions_handlers.py deleted file mode 100644 index e25f092..0000000 --- a/SmartHome/server/exceptions_handlers.py +++ /dev/null @@ -1,23 +0,0 @@ -from fastapi import FastAPI, Request, Response -from fastapi.responses import PlainTextResponse, JSONResponse -from exceptions.Internal import InternalException - - -http_codes = { - 1000: 400, - 1001: 401, - 1003: 403, - 1004: 404, - 1005: 400, - 1006: 400, - 1022: 422, -} - -def internal(request: Request, exc: InternalException) -> Response: - return JSONResponse({ - 'msg': exc.msg, - 'detail': exc.debug - }, status_code = http_codes.get(exc.code) or 400) - -def setup(app: FastAPI): - app.exception_handler(InternalException)(internal) diff --git a/SmartHome/server/http_exceptions.py b/SmartHome/server/http_exceptions.py deleted file mode 100644 index b6ae509..0000000 --- a/SmartHome/server/http_exceptions.py +++ /dev/null @@ -1,44 +0,0 @@ -# from fastapi import status -# from fastapi.responses import JSONResponse -# -# -# class Response(JSONResponse): -# def __init__(self, msg: str, dev: str, status_code: int) -# -# http_exceptions: dict[str, JSONResponse] = { -# 1000: JSONResponse({ -# 'Undefined exception', -# }, status_code = status.HTTP_400_BAD_REQUEST, -# ), -# -# 1001: HTTPException( -# status_code = status.HTTP_401_UNAUTHORIZED, -# detail = 'Token invalid, expired, or belongs to unknown user', -# headers = {'WWW-Authenticate': 'Bearer'}, -# ), -# -# 1003: HTTPException( -# status_code = status.HTTP_403_FORBIDDEN, -# detail = 'Access denied', -# ), -# -# 1004: HTTPException( -# status_code = status.HTTP_404_NOT_FOUND, -# detail = 'Not found', -# ), -# -# 1005: HTTPException( -# status_code = 400, -# detail = 'Resource already exist' -# ), -# -# 1006: HTTPException( -# status_code = 400, -# detail = 'Hub not initialized' -# ), -# -# 1022: HTTPException ( -# status_code = status.HTTP_422_UNPROCESSABLE_ENTITY, -# detail = 'Data in invalid format' -# ) -# } diff --git a/SmartHome/server/view/static/css/login.css b/SmartHome/server/view/static/css/login.css deleted file mode 100644 index a91c345..0000000 --- a/SmartHome/server/view/static/css/login.css +++ /dev/null @@ -1,127 +0,0 @@ -body { - background: #222D32; - font-family: 'Roboto', sans-serif; -} - -.login-box { - margin-top: 75px; - height: auto; - background: #1A2226; - text-align: center; - box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23); -} - -.login-key { - height: 100px; - font-size: 80px; - line-height: 100px; - background: -webkit-linear-gradient(#27EF9F, #0DB8DE); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; -} - -.login-title { - margin-top: 15px; - text-align: center; - font-size: 30px; - letter-spacing: 2px; - margin-top: 15px; - font-weight: bold; - color: #ECF0F5; -} - -.login-form { - margin-top: 25px; - text-align: left; -} - -input[type=text] { - background-color: #1A2226; - border: none; - border-bottom: 2px solid #0DB8DE; - border-top: 0px; - border-radius: 0px; - font-weight: bold; - outline: 0; - margin-bottom: 20px; - padding-left: 0px; - color: #ECF0F5; -} - -input[type=password] { - background-color: #1A2226; - border: none; - border-bottom: 2px solid #0DB8DE; - border-top: 0px; - border-radius: 0px; - font-weight: bold; - outline: 0; - padding-left: 0px; - margin-bottom: 20px; - color: #ECF0F5; -} - -.form-group { - margin-bottom: 40px; - outline: 0px; -} - -.form-control:focus { - border-color: inherit; - -webkit-box-shadow: none; - box-shadow: none; - border-bottom: 2px solid #0DB8DE; - outline: 0; - background-color: #1A2226; - color: #ECF0F5; -} - -input:focus { - outline: none; - box-shadow: 0 0 0; -} - -label { - margin-bottom: 0px; -} - -.form-control-label { - font-size: 10px; - color: #6C6C6C; - font-weight: bold; - letter-spacing: 1px; -} - -.btn-outline-primary { - border-color: #0DB8DE; - color: #0DB8DE; - border-radius: 0px; - font-weight: bold; - letter-spacing: 1px; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24); -} - -.btn-outline-primary:hover { - background-color: #0DB8DE; - right: 0px; -} - -.login-btm { - float: left; -} - -.login-button { - padding-right: 0px; - text-align: right; - margin-bottom: 25px; -} - -.login-text { - text-align: left; - padding-left: 0px; - color: #A2A4A4; -} - -.loginbttm { - padding: 0px; -} \ No newline at end of file diff --git a/SmartHome/server/view/static/css/swagger-ui-dark.css b/SmartHome/server/view/static/css/swagger-ui-dark.css deleted file mode 100644 index ddc96a8..0000000 --- a/SmartHome/server/view/static/css/swagger-ui-dark.css +++ /dev/null @@ -1,835 +0,0 @@ -@import url('https://cdn.jsdelivr.net/npm/swagger-ui-dist@3/swagger-ui.css'); - -button:focus { - outline: 0 !important; -} - -a { color: #8c8cfa; } - -::-webkit-scrollbar-track-piece { background-color: rgba(255, 255, 255, .2) !important; } - -::-webkit-scrollbar-track { background-color: rgba(255, 255, 255, .3) !important; } - -::-webkit-scrollbar-thumb { background-color: rgba(255, 255, 255, .5) !important; } - -embed[type="application/pdf"] { filter: invert(90%); } - -html { - background: #1f1f1f !important; - box-sizing: border-box; - filter: contrast(100%) brightness(100%) saturate(100%); - overflow-y: scroll; -} - -body { - background: #1f1f1f; - background-color: #1f1f1f; - background-image: none !important; -} - -button, input, select, textarea { - background-color: #1f1f1f; - color: #bfbfbf; -} - -font, html { color: #bfbfbf; } - -.swagger-ui, .swagger-ui section h3 { color: #b5bac9; } - -.swagger-ui a { background-color: transparent; } - -.swagger-ui mark { - background-color: #664b00; - color: #bfbfbf; -} - -.swagger-ui legend { color: inherit; } - -.swagger-ui .debug * { outline: #e6da99 solid 1px; } - -.swagger-ui .debug-white * { outline: #fff solid 1px; } - -.swagger-ui .debug-black * { outline: #bfbfbf solid 1px; } - -.swagger-ui .debug-grid { background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MTRDOTY4N0U2N0VFMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTRDOTY4N0Q2N0VFMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3NjY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3NzY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PsBS+GMAAAAjSURBVHjaYvz//z8DLsD4gcGXiYEAGBIKGBne//fFpwAgwAB98AaF2pjlUQAAAABJRU5ErkJggg==) 0 0; } - -.swagger-ui .debug-grid-16 { background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODYyRjhERDU2N0YyMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODYyRjhERDQ2N0YyMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3QTY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3QjY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PvCS01IAAABMSURBVHjaYmR4/5+BFPBfAMFm/MBgx8RAGWCn1AAmSg34Q6kBDKMGMDCwICeMIemF/5QawEipAWwUhwEjMDvbAWlWkvVBwu8vQIABAEwBCph8U6c0AAAAAElFTkSuQmCC) 0 0; } - -.swagger-ui .debug-grid-8-solid { background: url(data:image/jpeg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAAAAAD/4QMxaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzExMSA3OS4xNTgzMjUsIDIwMTUvMDkvMTAtMDE6MTA6MjAgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE1IChNYWNpbnRvc2gpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkIxMjI0OTczNjdCMzExRTZCMkJDRTI0MDgxMDAyMTcxIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkIxMjI0OTc0NjdCMzExRTZCMkJDRTI0MDgxMDAyMTcxIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6QjEyMjQ5NzE2N0IzMTFFNkIyQkNFMjQwODEwMDIxNzEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6QjEyMjQ5NzI2N0IzMTFFNkIyQkNFMjQwODEwMDIxNzEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7/7gAOQWRvYmUAZMAAAAAB/9sAhAAbGhopHSlBJiZBQi8vL0JHPz4+P0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHAR0pKTQmND8oKD9HPzU/R0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0f/wAARCAAIAAgDASIAAhEBAxEB/8QAWQABAQAAAAAAAAAAAAAAAAAAAAYBAQEAAAAAAAAAAAAAAAAAAAIEEAEBAAMBAAAAAAAAAAAAAAABADECA0ERAAEDBQAAAAAAAAAAAAAAAAARITFBUWESIv/aAAwDAQACEQMRAD8AoOnTV1QTD7JJshP3vSM3P//Z) 0 0 #1c1c21; } - -.swagger-ui .debug-grid-16-solid { background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NzY3MkJEN0U2N0M1MTFFNkIyQkNFMjQwODEwMDIxNzEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NzY3MkJEN0Y2N0M1MTFFNkIyQkNFMjQwODEwMDIxNzEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3QzY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3RDY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pve6J3kAAAAzSURBVHjaYvz//z8D0UDsMwMjSRoYP5Gq4SPNbRjVMEQ1fCRDg+in/6+J1AJUxsgAEGAA31BAJMS0GYEAAAAASUVORK5CYII=) 0 0 #1c1c21; } - -.swagger-ui .b--black { border-color: #000; } - -.swagger-ui .b--near-black { border-color: #121212; } - -.swagger-ui .b--dark-gray { border-color: #333; } - -.swagger-ui .b--mid-gray { border-color: #545454; } - -.swagger-ui .b--gray { border-color: #787878; } - -.swagger-ui .b--silver { border-color: #999; } - -.swagger-ui .b--light-silver { border-color: #6e6e6e; } - -.swagger-ui .b--moon-gray { border-color: #4d4d4d; } - -.swagger-ui .b--light-gray { border-color: #2b2b2b; } - -.swagger-ui .b--near-white { border-color: #242424; } - -.swagger-ui .b--white { border-color: #1c1c21; } - -.swagger-ui .b--white-90 { border-color: rgba(28, 28, 33, .9); } - -.swagger-ui .b--white-80 { border-color: rgba(28, 28, 33, .8); } - -.swagger-ui .b--white-70 { border-color: rgba(28, 28, 33, .7); } - -.swagger-ui .b--white-60 { border-color: rgba(28, 28, 33, .6); } - -.swagger-ui .b--white-50 { border-color: rgba(28, 28, 33, .5); } - -.swagger-ui .b--white-40 { border-color: rgba(28, 28, 33, .4); } - -.swagger-ui .b--white-30 { border-color: rgba(28, 28, 33, .3); } - -.swagger-ui .b--white-20 { border-color: rgba(28, 28, 33, .2); } - -.swagger-ui .b--white-10 { border-color: rgba(28, 28, 33, .1); } - -.swagger-ui .b--white-05 { border-color: rgba(28, 28, 33, .05); } - -.swagger-ui .b--white-025 { border-color: rgba(28, 28, 33, .024); } - -.swagger-ui .b--white-0125 { border-color: rgba(28, 28, 33, .01); } - -.swagger-ui .b--black-90 { border-color: rgba(0, 0, 0, .9); } - -.swagger-ui .b--black-80 { border-color: rgba(0, 0, 0, .8); } - -.swagger-ui .b--black-70 { border-color: rgba(0, 0, 0, .7); } - -.swagger-ui .b--black-60 { border-color: rgba(0, 0, 0, .6); } - -.swagger-ui .b--black-50 { border-color: rgba(0, 0, 0, .5); } - -.swagger-ui .b--black-40 { border-color: rgba(0, 0, 0, .4); } - -.swagger-ui .b--black-30 { border-color: rgba(0, 0, 0, .3); } - -.swagger-ui .b--black-20 { border-color: rgba(0, 0, 0, .2); } - -.swagger-ui .b--black-10 { border-color: rgba(0, 0, 0, .1); } - -.swagger-ui .b--black-05 { border-color: rgba(0, 0, 0, .05); } - -.swagger-ui .b--black-025 { border-color: rgba(0, 0, 0, .024); } - -.swagger-ui .b--black-0125 { border-color: rgba(0, 0, 0, .01); } - -.swagger-ui .b--dark-red { border-color: #bc2f36; } - -.swagger-ui .b--red { border-color: #c83932; } - -.swagger-ui .b--light-red { border-color: #ab3c2b; } - -.swagger-ui .b--orange { border-color: #cc6e33; } - -.swagger-ui .b--purple { border-color: #5e2ca5; } - -.swagger-ui .b--light-purple { border-color: #672caf; } - -.swagger-ui .b--dark-pink { border-color: #ab2b81; } - -.swagger-ui .b--hot-pink { border-color: #c03086; } - -.swagger-ui .b--pink { border-color: #8f2464; } - -.swagger-ui .b--light-pink { border-color: #721d4d; } - -.swagger-ui .b--dark-green { border-color: #1c6e50; } - -.swagger-ui .b--green { border-color: #279b70; } - -.swagger-ui .b--light-green { border-color: #228762; } - -.swagger-ui .b--navy { border-color: #0d1d35; } - -.swagger-ui .b--dark-blue { border-color: #20497e; } - -.swagger-ui .b--blue { border-color: #4380d0; } - -.swagger-ui .b--light-blue { border-color: #20517e; } - -.swagger-ui .b--lightest-blue { border-color: #143a52; } - -.swagger-ui .b--washed-blue { border-color: #0c312d; } - -.swagger-ui .b--washed-green { border-color: #0f3d2c; } - -.swagger-ui .b--washed-red { border-color: #411010; } - -.swagger-ui .b--transparent { border-color: transparent; } - -.swagger-ui .b--gold, .swagger-ui .b--light-yellow, .swagger-ui .b--washed-yellow, .swagger-ui .b--yellow { border-color: #664b00; } - -.swagger-ui .shadow-1 { box-shadow: rgba(0, 0, 0, .2) 0 0 4px 2px; } - -.swagger-ui .shadow-2 { box-shadow: rgba(0, 0, 0, .2) 0 0 8px 2px; } - -.swagger-ui .shadow-3 { box-shadow: rgba(0, 0, 0, .2) 2px 2px 4px 2px; } - -.swagger-ui .shadow-4 { box-shadow: rgba(0, 0, 0, .2) 2px 2px 8px 0; } - -.swagger-ui .shadow-5 { box-shadow: rgba(0, 0, 0, .2) 4px 4px 8px 0; } - -@media screen and (min-width: 30em) { - .swagger-ui .shadow-1-ns { box-shadow: rgba(0, 0, 0, .2) 0 0 4px 2px; } - - .swagger-ui .shadow-2-ns { box-shadow: rgba(0, 0, 0, .2) 0 0 8px 2px; } - - .swagger-ui .shadow-3-ns { box-shadow: rgba(0, 0, 0, .2) 2px 2px 4px 2px; } - - .swagger-ui .shadow-4-ns { box-shadow: rgba(0, 0, 0, .2) 2px 2px 8px 0; } - - .swagger-ui .shadow-5-ns { box-shadow: rgba(0, 0, 0, .2) 4px 4px 8px 0; } -} - -@media screen and (max-width: 60em) and (min-width: 30em) { - .swagger-ui .shadow-1-m { box-shadow: rgba(0, 0, 0, .2) 0 0 4px 2px; } - - .swagger-ui .shadow-2-m { box-shadow: rgba(0, 0, 0, .2) 0 0 8px 2px; } - - .swagger-ui .shadow-3-m { box-shadow: rgba(0, 0, 0, .2) 2px 2px 4px 2px; } - - .swagger-ui .shadow-4-m { box-shadow: rgba(0, 0, 0, .2) 2px 2px 8px 0; } - - .swagger-ui .shadow-5-m { box-shadow: rgba(0, 0, 0, .2) 4px 4px 8px 0; } -} - -@media screen and (min-width: 60em) { - .swagger-ui .shadow-1-l { box-shadow: rgba(0, 0, 0, .2) 0 0 4px 2px; } - - .swagger-ui .shadow-2-l { box-shadow: rgba(0, 0, 0, .2) 0 0 8px 2px; } - - .swagger-ui .shadow-3-l { box-shadow: rgba(0, 0, 0, .2) 2px 2px 4px 2px; } - - .swagger-ui .shadow-4-l { box-shadow: rgba(0, 0, 0, .2) 2px 2px 8px 0; } - - .swagger-ui .shadow-5-l { box-shadow: rgba(0, 0, 0, .2) 4px 4px 8px 0; } -} - -.swagger-ui .black-05 { color: rgba(191, 191, 191, .05); } - -.swagger-ui .bg-black-05 { background-color: rgba(0, 0, 0, .05); } - -.swagger-ui .black-90, .swagger-ui .hover-black-90:focus, .swagger-ui .hover-black-90:hover { color: rgba(191, 191, 191, .9); } - -.swagger-ui .black-80, .swagger-ui .hover-black-80:focus, .swagger-ui .hover-black-80:hover { color: rgba(191, 191, 191, .8); } - -.swagger-ui .black-70, .swagger-ui .hover-black-70:focus, .swagger-ui .hover-black-70:hover { color: rgba(191, 191, 191, .7); } - -.swagger-ui .black-60, .swagger-ui .hover-black-60:focus, .swagger-ui .hover-black-60:hover { color: rgba(191, 191, 191, .6); } - -.swagger-ui .black-50, .swagger-ui .hover-black-50:focus, .swagger-ui .hover-black-50:hover { color: rgba(191, 191, 191, .5); } - -.swagger-ui .black-40, .swagger-ui .hover-black-40:focus, .swagger-ui .hover-black-40:hover { color: rgba(191, 191, 191, .4); } - -.swagger-ui .black-30, .swagger-ui .hover-black-30:focus, .swagger-ui .hover-black-30:hover { color: rgba(191, 191, 191, .3); } - -.swagger-ui .black-20, .swagger-ui .hover-black-20:focus, .swagger-ui .hover-black-20:hover { color: rgba(191, 191, 191, .2); } - -.swagger-ui .black-10, .swagger-ui .hover-black-10:focus, .swagger-ui .hover-black-10:hover { color: rgba(191, 191, 191, .1); } - -.swagger-ui .hover-white-90:focus, .swagger-ui .hover-white-90:hover, .swagger-ui .white-90 { color: rgba(255, 255, 255, .9); } - -.swagger-ui .hover-white-80:focus, .swagger-ui .hover-white-80:hover, .swagger-ui .white-80 { color: rgba(255, 255, 255, .8); } - -.swagger-ui .hover-white-70:focus, .swagger-ui .hover-white-70:hover, .swagger-ui .white-70 { color: rgba(255, 255, 255, .7); } - -.swagger-ui .hover-white-60:focus, .swagger-ui .hover-white-60:hover, .swagger-ui .white-60 { color: rgba(255, 255, 255, .6); } - -.swagger-ui .hover-white-50:focus, .swagger-ui .hover-white-50:hover, .swagger-ui .white-50 { color: rgba(255, 255, 255, .5); } - -.swagger-ui .hover-white-40:focus, .swagger-ui .hover-white-40:hover, .swagger-ui .white-40 { color: rgba(255, 255, 255, .4); } - -.swagger-ui .hover-white-30:focus, .swagger-ui .hover-white-30:hover, .swagger-ui .white-30 { color: rgba(255, 255, 255, .3); } - -.swagger-ui .hover-white-20:focus, .swagger-ui .hover-white-20:hover, .swagger-ui .white-20 { color: rgba(255, 255, 255, .2); } - -.swagger-ui .hover-white-10:focus, .swagger-ui .hover-white-10:hover, .swagger-ui .white-10 { color: rgba(255, 255, 255, .1); } - -.swagger-ui .hover-moon-gray:focus, .swagger-ui .hover-moon-gray:hover, .swagger-ui .moon-gray { color: #ccc; } - -.swagger-ui .hover-light-gray:focus, .swagger-ui .hover-light-gray:hover, .swagger-ui .light-gray { color: #ededed; } - -.swagger-ui .hover-near-white:focus, .swagger-ui .hover-near-white:hover, .swagger-ui .near-white { color: #f5f5f5; } - -.swagger-ui .dark-red, .swagger-ui .hover-dark-red:focus, .swagger-ui .hover-dark-red:hover { color: #e6999d; } - -.swagger-ui .hover-red:focus, .swagger-ui .hover-red:hover, .swagger-ui .red { color: #e69d99; } - -.swagger-ui .hover-light-red:focus, .swagger-ui .hover-light-red:hover, .swagger-ui .light-red { color: #e6a399; } - -.swagger-ui .hover-orange:focus, .swagger-ui .hover-orange:hover, .swagger-ui .orange { color: #e6b699; } - -.swagger-ui .gold, .swagger-ui .hover-gold:focus, .swagger-ui .hover-gold:hover { color: #e6d099; } - -.swagger-ui .hover-yellow:focus, .swagger-ui .hover-yellow:hover, .swagger-ui .yellow { color: #e6da99; } - -.swagger-ui .hover-light-yellow:focus, .swagger-ui .hover-light-yellow:hover, .swagger-ui .light-yellow { color: #ede6b6; } - -.swagger-ui .hover-purple:focus, .swagger-ui .hover-purple:hover, .swagger-ui .purple { color: #b99ae4; } - -.swagger-ui .hover-light-purple:focus, .swagger-ui .hover-light-purple:hover, .swagger-ui .light-purple { color: #bb99e6; } - -.swagger-ui .dark-pink, .swagger-ui .hover-dark-pink:focus, .swagger-ui .hover-dark-pink:hover { color: #e699cc; } - -.swagger-ui .hot-pink, .swagger-ui .hover-hot-pink:focus, .swagger-ui .hover-hot-pink:hover, .swagger-ui .hover-pink:focus, .swagger-ui .hover-pink:hover, .swagger-ui .pink { color: #e699c7; } - -.swagger-ui .hover-light-pink:focus, .swagger-ui .hover-light-pink:hover, .swagger-ui .light-pink { color: #edb6d5; } - -.swagger-ui .dark-green, .swagger-ui .green, .swagger-ui .hover-dark-green:focus, .swagger-ui .hover-dark-green:hover, .swagger-ui .hover-green:focus, .swagger-ui .hover-green:hover { color: #99e6c9; } - -.swagger-ui .hover-light-green:focus, .swagger-ui .hover-light-green:hover, .swagger-ui .light-green { color: #a1e8ce; } - -.swagger-ui .hover-navy:focus, .swagger-ui .hover-navy:hover, .swagger-ui .navy { color: #99b8e6; } - -.swagger-ui .blue, .swagger-ui .dark-blue, .swagger-ui .hover-blue:focus, .swagger-ui .hover-blue:hover, .swagger-ui .hover-dark-blue:focus, .swagger-ui .hover-dark-blue:hover { color: #99bae6; } - -.swagger-ui .hover-light-blue:focus, .swagger-ui .hover-light-blue:hover, .swagger-ui .light-blue { color: #a9cbea; } - -.swagger-ui .hover-lightest-blue:focus, .swagger-ui .hover-lightest-blue:hover, .swagger-ui .lightest-blue { color: #d6e9f5; } - -.swagger-ui .hover-washed-blue:focus, .swagger-ui .hover-washed-blue:hover, .swagger-ui .washed-blue { color: #f7fdfc; } - -.swagger-ui .hover-washed-green:focus, .swagger-ui .hover-washed-green:hover, .swagger-ui .washed-green { color: #ebfaf4; } - -.swagger-ui .hover-washed-yellow:focus, .swagger-ui .hover-washed-yellow:hover, .swagger-ui .washed-yellow { color: #fbf9ef; } - -.swagger-ui .hover-washed-red:focus, .swagger-ui .hover-washed-red:hover, .swagger-ui .washed-red { color: #f9e7e7; } - -.swagger-ui .color-inherit, .swagger-ui .hover-inherit:focus, .swagger-ui .hover-inherit:hover { color: inherit; } - -.swagger-ui .bg-black-90, .swagger-ui .hover-bg-black-90:focus, .swagger-ui .hover-bg-black-90:hover { background-color: rgba(0, 0, 0, .9); } - -.swagger-ui .bg-black-80, .swagger-ui .hover-bg-black-80:focus, .swagger-ui .hover-bg-black-80:hover { background-color: rgba(0, 0, 0, .8); } - -.swagger-ui .bg-black-70, .swagger-ui .hover-bg-black-70:focus, .swagger-ui .hover-bg-black-70:hover { background-color: rgba(0, 0, 0, .7); } - -.swagger-ui .bg-black-60, .swagger-ui .hover-bg-black-60:focus, .swagger-ui .hover-bg-black-60:hover { background-color: rgba(0, 0, 0, .6); } - -.swagger-ui .bg-black-50, .swagger-ui .hover-bg-black-50:focus, .swagger-ui .hover-bg-black-50:hover { background-color: rgba(0, 0, 0, .5); } - -.swagger-ui .bg-black-40, .swagger-ui .hover-bg-black-40:focus, .swagger-ui .hover-bg-black-40:hover { background-color: rgba(0, 0, 0, .4); } - -.swagger-ui .bg-black-30, .swagger-ui .hover-bg-black-30:focus, .swagger-ui .hover-bg-black-30:hover { background-color: rgba(0, 0, 0, .3); } - -.swagger-ui .bg-black-20, .swagger-ui .hover-bg-black-20:focus, .swagger-ui .hover-bg-black-20:hover { background-color: rgba(0, 0, 0, .2); } - -.swagger-ui .bg-white-90, .swagger-ui .hover-bg-white-90:focus, .swagger-ui .hover-bg-white-90:hover { background-color: rgba(28, 28, 33, .9); } - -.swagger-ui .bg-white-80, .swagger-ui .hover-bg-white-80:focus, .swagger-ui .hover-bg-white-80:hover { background-color: rgba(28, 28, 33, .8); } - -.swagger-ui .bg-white-70, .swagger-ui .hover-bg-white-70:focus, .swagger-ui .hover-bg-white-70:hover { background-color: rgba(28, 28, 33, .7); } - -.swagger-ui .bg-white-60, .swagger-ui .hover-bg-white-60:focus, .swagger-ui .hover-bg-white-60:hover { background-color: rgba(28, 28, 33, .6); } - -.swagger-ui .bg-white-50, .swagger-ui .hover-bg-white-50:focus, .swagger-ui .hover-bg-white-50:hover { background-color: rgba(28, 28, 33, .5); } - -.swagger-ui .bg-white-40, .swagger-ui .hover-bg-white-40:focus, .swagger-ui .hover-bg-white-40:hover { background-color: rgba(28, 28, 33, .4); } - -.swagger-ui .bg-white-30, .swagger-ui .hover-bg-white-30:focus, .swagger-ui .hover-bg-white-30:hover { background-color: rgba(28, 28, 33, .3); } - -.swagger-ui .bg-white-20, .swagger-ui .hover-bg-white-20:focus, .swagger-ui .hover-bg-white-20:hover { background-color: rgba(28, 28, 33, .2); } - -.swagger-ui .bg-black, .swagger-ui .hover-bg-black:focus, .swagger-ui .hover-bg-black:hover { background-color: #000; } - -.swagger-ui .bg-near-black, .swagger-ui .hover-bg-near-black:focus, .swagger-ui .hover-bg-near-black:hover { background-color: #121212; } - -.swagger-ui .bg-dark-gray, .swagger-ui .hover-bg-dark-gray:focus, .swagger-ui .hover-bg-dark-gray:hover { background-color: #333; } - -.swagger-ui .bg-mid-gray, .swagger-ui .hover-bg-mid-gray:focus, .swagger-ui .hover-bg-mid-gray:hover { background-color: #545454; } - -.swagger-ui .bg-gray, .swagger-ui .hover-bg-gray:focus, .swagger-ui .hover-bg-gray:hover { background-color: #787878; } - -.swagger-ui .bg-silver, .swagger-ui .hover-bg-silver:focus, .swagger-ui .hover-bg-silver:hover { background-color: #999; } - -.swagger-ui .bg-white, .swagger-ui .hover-bg-white:focus, .swagger-ui .hover-bg-white:hover { background-color: #1c1c21; } - -.swagger-ui .bg-transparent, .swagger-ui .hover-bg-transparent:focus, .swagger-ui .hover-bg-transparent:hover { background-color: transparent; } - -.swagger-ui .bg-dark-red, .swagger-ui .hover-bg-dark-red:focus, .swagger-ui .hover-bg-dark-red:hover { background-color: #bc2f36; } - -.swagger-ui .bg-red, .swagger-ui .hover-bg-red:focus, .swagger-ui .hover-bg-red:hover { background-color: #c83932; } - -.swagger-ui .bg-light-red, .swagger-ui .hover-bg-light-red:focus, .swagger-ui .hover-bg-light-red:hover { background-color: #ab3c2b; } - -.swagger-ui .bg-orange, .swagger-ui .hover-bg-orange:focus, .swagger-ui .hover-bg-orange:hover { background-color: #cc6e33; } - -.swagger-ui .bg-gold, .swagger-ui .bg-light-yellow, .swagger-ui .bg-washed-yellow, .swagger-ui .bg-yellow, .swagger-ui .hover-bg-gold:focus, .swagger-ui .hover-bg-gold:hover, .swagger-ui .hover-bg-light-yellow:focus, .swagger-ui .hover-bg-light-yellow:hover, .swagger-ui .hover-bg-washed-yellow:focus, .swagger-ui .hover-bg-washed-yellow:hover, .swagger-ui .hover-bg-yellow:focus, .swagger-ui .hover-bg-yellow:hover { background-color: #664b00; } - -.swagger-ui .bg-purple, .swagger-ui .hover-bg-purple:focus, .swagger-ui .hover-bg-purple:hover { background-color: #5e2ca5; } - -.swagger-ui .bg-light-purple, .swagger-ui .hover-bg-light-purple:focus, .swagger-ui .hover-bg-light-purple:hover { background-color: #672caf; } - -.swagger-ui .bg-dark-pink, .swagger-ui .hover-bg-dark-pink:focus, .swagger-ui .hover-bg-dark-pink:hover { background-color: #ab2b81; } - -.swagger-ui .bg-hot-pink, .swagger-ui .hover-bg-hot-pink:focus, .swagger-ui .hover-bg-hot-pink:hover { background-color: #c03086; } - -.swagger-ui .bg-pink, .swagger-ui .hover-bg-pink:focus, .swagger-ui .hover-bg-pink:hover { background-color: #8f2464; } - -.swagger-ui .bg-light-pink, .swagger-ui .hover-bg-light-pink:focus, .swagger-ui .hover-bg-light-pink:hover { background-color: #721d4d; } - -.swagger-ui .bg-dark-green, .swagger-ui .hover-bg-dark-green:focus, .swagger-ui .hover-bg-dark-green:hover { background-color: #1c6e50; } - -.swagger-ui .bg-green, .swagger-ui .hover-bg-green:focus, .swagger-ui .hover-bg-green:hover { background-color: #279b70; } - -.swagger-ui .bg-light-green, .swagger-ui .hover-bg-light-green:focus, .swagger-ui .hover-bg-light-green:hover { background-color: #228762; } - -.swagger-ui .bg-navy, .swagger-ui .hover-bg-navy:focus, .swagger-ui .hover-bg-navy:hover { background-color: #0d1d35; } - -.swagger-ui .bg-dark-blue, .swagger-ui .hover-bg-dark-blue:focus, .swagger-ui .hover-bg-dark-blue:hover { background-color: #20497e; } - -.swagger-ui .bg-blue, .swagger-ui .hover-bg-blue:focus, .swagger-ui .hover-bg-blue:hover { background-color: #4380d0; } - -.swagger-ui .bg-light-blue, .swagger-ui .hover-bg-light-blue:focus, .swagger-ui .hover-bg-light-blue:hover { background-color: #20517e; } - -.swagger-ui .bg-lightest-blue, .swagger-ui .hover-bg-lightest-blue:focus, .swagger-ui .hover-bg-lightest-blue:hover { background-color: #143a52; } - -.swagger-ui .bg-washed-blue, .swagger-ui .hover-bg-washed-blue:focus, .swagger-ui .hover-bg-washed-blue:hover { background-color: #0c312d; } - -.swagger-ui .bg-washed-green, .swagger-ui .hover-bg-washed-green:focus, .swagger-ui .hover-bg-washed-green:hover { background-color: #0f3d2c; } - -.swagger-ui .bg-washed-red, .swagger-ui .hover-bg-washed-red:focus, .swagger-ui .hover-bg-washed-red:hover { background-color: #411010; } - -.swagger-ui .bg-inherit, .swagger-ui .hover-bg-inherit:focus, .swagger-ui .hover-bg-inherit:hover { background-color: inherit; } - -.swagger-ui .shadow-hover { transition: all .5s cubic-bezier(.165, .84, .44, 1) 0s; } - -.swagger-ui .shadow-hover::after { - border-radius: inherit; - box-shadow: rgba(0, 0, 0, .2) 0 0 16px 2px; - content: ""; - height: 100%; - left: 0; - opacity: 0; - position: absolute; - top: 0; - transition: opacity .5s cubic-bezier(.165, .84, .44, 1) 0s; - width: 100%; - z-index: -1; -} - -.swagger-ui .bg-animate, .swagger-ui .bg-animate:focus, .swagger-ui .bg-animate:hover { transition: background-color .15s ease-in-out 0s; } - -.swagger-ui .nested-links a { - color: #99bae6; - transition: color .15s ease-in 0s; -} - -.swagger-ui .nested-links a:focus, .swagger-ui .nested-links a:hover { - color: #a9cbea; - transition: color .15s ease-in 0s; -} - -.swagger-ui .opblock-tag { - border-bottom: 1px solid rgba(58, 64, 80, .3); - color: #b5bac9; - transition: all .2s ease 0s; -} - -.swagger-ui .opblock-tag svg, .swagger-ui section.models h4 svg { transition: all .4s ease 0s; } - -.swagger-ui .opblock { - border: 1px solid #000; - border-radius: 4px; - box-shadow: rgba(0, 0, 0, .19) 0 0 3px; - margin: 0 0 15px; -} - -.swagger-ui .opblock .tab-header .tab-item.active h4 span::after { background: gray; } - -.swagger-ui .opblock.is-open .opblock-summary { border-bottom: 1px solid #000; } - -.swagger-ui .opblock .opblock-section-header { - background: rgba(28, 28, 33, .8); - box-shadow: rgba(0, 0, 0, .1) 0 1px 2px; -} - -.swagger-ui .opblock .opblock-section-header > label > span { padding: 0 10px 0 0; } - -.swagger-ui .opblock .opblock-summary-method { - background: #000; - color: #fff; - text-shadow: rgba(0, 0, 0, .1) 0 1px 0; -} - -.swagger-ui .opblock.opblock-post { - background: rgba(72, 203, 144, .1); - border-color: #48cb90; -} - -.swagger-ui .opblock.opblock-post .opblock-summary-method, .swagger-ui .opblock.opblock-post .tab-header .tab-item.active h4 span::after { background: #48cb90; } - -.swagger-ui .opblock.opblock-post .opblock-summary { border-color: #48cb90; } - -.swagger-ui .opblock.opblock-put { - background: rgba(213, 157, 88, .1); - border-color: #d59d58; -} - -.swagger-ui .opblock.opblock-put .opblock-summary-method, .swagger-ui .opblock.opblock-put .tab-header .tab-item.active h4 span::after { background: #d59d58; } - -.swagger-ui .opblock.opblock-put .opblock-summary { border-color: #d59d58; } - -.swagger-ui .opblock.opblock-delete { - background: rgba(200, 50, 50, .1); - border-color: #c83232; -} - -.swagger-ui .opblock.opblock-delete .opblock-summary-method, .swagger-ui .opblock.opblock-delete .tab-header .tab-item.active h4 span::after { background: #c83232; } - -.swagger-ui .opblock.opblock-delete .opblock-summary { border-color: #c83232; } - -.swagger-ui .opblock.opblock-get { - background: rgba(42, 105, 167, .1); - border-color: #2a69a7; -} - -.swagger-ui .opblock.opblock-get .opblock-summary-method, .swagger-ui .opblock.opblock-get .tab-header .tab-item.active h4 span::after { background: #2a69a7; } - -.swagger-ui .opblock.opblock-get .opblock-summary { border-color: #2a69a7; } - -.swagger-ui .opblock.opblock-patch { - background: rgba(92, 214, 188, .1); - border-color: #5cd6bc; -} - -.swagger-ui .opblock.opblock-patch .opblock-summary-method, .swagger-ui .opblock.opblock-patch .tab-header .tab-item.active h4 span::after { background: #5cd6bc; } - -.swagger-ui .opblock.opblock-patch .opblock-summary { border-color: #5cd6bc; } - -.swagger-ui .opblock.opblock-head { - background: rgba(140, 63, 207, .1); - border-color: #8c3fcf; -} - -.swagger-ui .opblock.opblock-head .opblock-summary-method, .swagger-ui .opblock.opblock-head .tab-header .tab-item.active h4 span::after { background: #8c3fcf; } - -.swagger-ui .opblock.opblock-head .opblock-summary { border-color: #8c3fcf; } - -.swagger-ui .opblock.opblock-options { - background: rgba(36, 89, 143, .1); - border-color: #24598f; -} - -.swagger-ui .opblock.opblock-options .opblock-summary-method, .swagger-ui .opblock.opblock-options .tab-header .tab-item.active h4 span::after { background: #24598f; } - -.swagger-ui .opblock.opblock-options .opblock-summary { border-color: #24598f; } - -.swagger-ui .opblock.opblock-deprecated { - background: rgba(46, 46, 46, .1); - border-color: #2e2e2e; - opacity: .6; -} - -.swagger-ui .opblock.opblock-deprecated .opblock-summary-method, .swagger-ui .opblock.opblock-deprecated .tab-header .tab-item.active h4 span::after { background: #2e2e2e; } - -.swagger-ui .opblock.opblock-deprecated .opblock-summary { border-color: #2e2e2e; } - -.swagger-ui .filter .operation-filter-input { border: 2px solid #2b3446; } - -.swagger-ui .tab li:first-of-type::after { background: rgba(0, 0, 0, .2); } - -.swagger-ui .download-contents { - background: #7c8192; - color: #fff; -} - -.swagger-ui .scheme-container { - background: #1c1c21; - box-shadow: rgba(0, 0, 0, .15) 0 1px 2px 0; -} - -.swagger-ui .loading-container .loading::before { - animation: 1s linear 0s infinite normal none running rotation, .5s ease 0s 1 normal none running opacity; - border-color: rgba(0, 0, 0, .6) rgba(84, 84, 84, .1) rgba(84, 84, 84, .1); -} - -.swagger-ui .response-control-media-type--accept-controller select { border-color: #196619; } - -.swagger-ui .response-control-media-type__accept-message { color: #99e699; } - -.swagger-ui .version-pragma__message code { background-color: #3b3b3b; } - -.swagger-ui .btn { - background: 0 0; - border: 2px solid gray; - box-shadow: rgba(0, 0, 0, .1) 0 1px 2px; - color: #b5bac9; -} - -.swagger-ui .btn:hover { box-shadow: rgba(0, 0, 0, .3) 0 0 5px; } - -.swagger-ui .btn.authorize, .swagger-ui .btn.cancel { - background-color: transparent; - border-color: #a72a2a; - color: #e69999; -} - -.swagger-ui .btn.authorize { - border-color: #48cb90; - color: #9ce3c3; -} - -.swagger-ui .btn.authorize svg { fill: #9ce3c3; } - -.swagger-ui .btn.execute { - background-color: #5892d5; - border-color: #5892d5; - color: #fff; -} - -.swagger-ui .copy-to-clipboard { background: #7c8192; } - -.swagger-ui .copy-to-clipboard button { background: url("data:image/svg+xml;charset=utf-8,") 50% center no-repeat; } - -.swagger-ui select { - background: url("data:image/svg+xml;charset=utf-8,") right 10px center/20px no-repeat #212121; - background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgaW5rc2NhcGU6dmVyc2lvbj0iMS4wICg0MDM1YTRmYjQ5LCAyMDIwLTA1LTAxKSIKICAgc29kaXBvZGk6ZG9jbmFtZT0iZG93bmxvYWQuc3ZnIgogICBpZD0ic3ZnNCIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMjAgMjAiPgogIDxtZXRhZGF0YQogICAgIGlkPSJtZXRhZGF0YTEwIj4KICAgIDxyZGY6UkRGPgogICAgICA8Y2M6V29yawogICAgICAgICByZGY6YWJvdXQ9IiI+CiAgICAgICAgPGRjOmZvcm1hdD5pbWFnZS9zdmcreG1sPC9kYzpmb3JtYXQ+CiAgICAgICAgPGRjOnR5cGUKICAgICAgICAgICByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIiAvPgogICAgICA8L2NjOldvcms+CiAgICA8L3JkZjpSREY+CiAgPC9tZXRhZGF0YT4KICA8ZGVmcwogICAgIGlkPSJkZWZzOCIgLz4KICA8c29kaXBvZGk6bmFtZWR2aWV3CiAgICAgaW5rc2NhcGU6Y3VycmVudC1sYXllcj0ic3ZnNCIKICAgICBpbmtzY2FwZTp3aW5kb3ctbWF4aW1pemVkPSIxIgogICAgIGlua3NjYXBlOndpbmRvdy15PSItOSIKICAgICBpbmtzY2FwZTp3aW5kb3cteD0iLTkiCiAgICAgaW5rc2NhcGU6Y3k9IjEwIgogICAgIGlua3NjYXBlOmN4PSIxMCIKICAgICBpbmtzY2FwZTp6b29tPSI0MS41IgogICAgIHNob3dncmlkPSJmYWxzZSIKICAgICBpZD0ibmFtZWR2aWV3NiIKICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSIxMDAxIgogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTkyMCIKICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIgogICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwIgogICAgIGd1aWRldG9sZXJhbmNlPSIxMCIKICAgICBncmlkdG9sZXJhbmNlPSIxMCIKICAgICBvYmplY3R0b2xlcmFuY2U9IjEwIgogICAgIGJvcmRlcm9wYWNpdHk9IjEiCiAgICAgYm9yZGVyY29sb3I9IiM2NjY2NjYiCiAgICAgcGFnZWNvbG9yPSIjZmZmZmZmIiAvPgogIDxwYXRoCiAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZiIKICAgICBpZD0icGF0aDIiCiAgICAgZD0iTTEzLjQxOCA3Ljg1OWEuNjk1LjY5NSAwIDAxLjk3OCAwIC42OC42OCAwIDAxMCAuOTY5bC0zLjkwOCAzLjgzYS42OTcuNjk3IDAgMDEtLjk3OSAwbC0zLjkwOC0zLjgzYS42OC42OCAwIDAxMC0uOTY5LjY5NS42OTUgMCAwMS45NzggMEwxMCAxMWwzLjQxOC0zLjE0MXoiIC8+Cjwvc3ZnPgo=) right 10px center/20px no-repeat #1c1c21; - border: 2px solid #41444e; -} - -.swagger-ui select[multiple] { background: #212121; } - -.swagger-ui button.invalid, .swagger-ui input[type=email].invalid, .swagger-ui input[type=file].invalid, .swagger-ui input[type=password].invalid, .swagger-ui input[type=search].invalid, .swagger-ui input[type=text].invalid, .swagger-ui select.invalid, .swagger-ui textarea.invalid { - background: #390e0e; - border-color: #c83232; -} - -.swagger-ui input[type=email], .swagger-ui input[type=file], .swagger-ui input[type=password], .swagger-ui input[type=search], .swagger-ui input[type=text], .swagger-ui textarea { - background: #1c1c21; - border: 1px solid #404040; -} - -.swagger-ui textarea { - background: rgba(28, 28, 33, .8); - color: #b5bac9; -} - -.swagger-ui input[disabled], .swagger-ui select[disabled] { - background-color: #1f1f1f; - color: #bfbfbf; -} - -.swagger-ui textarea[disabled] { - background-color: #41444e; - color: #fff; -} - -.swagger-ui select[disabled] { border-color: #878787; } - -.swagger-ui textarea:focus { border: 2px solid #2a69a7; } - -.swagger-ui .checkbox input[type=checkbox] + label > .item { - background: #303030; - box-shadow: #303030 0 0 0 2px; -} - -.swagger-ui .checkbox input[type=checkbox]:checked + label > .item { background: url("data:image/svg+xml;charset=utf-8,") 50% center no-repeat #303030; } - -.swagger-ui .dialog-ux .backdrop-ux { background: rgba(0, 0, 0, .8); } - -.swagger-ui .dialog-ux .modal-ux { - background: #1c1c21; - border: 1px solid #2e2e2e; - box-shadow: rgba(0, 0, 0, .2) 0 10px 30px 0; -} - -.swagger-ui .dialog-ux .modal-ux-header .close-modal { background: 0 0; } - -.swagger-ui .model .deprecated span, .swagger-ui .model .deprecated td { color: #bfbfbf !important; } - -.swagger-ui .model-toggle::after { background: url("data:image/svg+xml;charset=utf-8,") 50% center/100% no-repeat; } - -.swagger-ui .model-hint { - background: rgba(0, 0, 0, .7); - color: #ebebeb; -} - -.swagger-ui section.models { border: 1px solid rgba(58, 64, 80, .3); } - -.swagger-ui section.models.is-open h4 { border-bottom: 1px solid rgba(58, 64, 80, .3); } - -.swagger-ui section.models .model-container { background: rgba(0, 0, 0, .05); } - -.swagger-ui section.models .model-container:hover { background: rgba(0, 0, 0, .07); } - -.swagger-ui .model-box { background: rgba(0, 0, 0, .1); } - -.swagger-ui .prop-type { color: #aaaad4; } - -.swagger-ui table thead tr td, .swagger-ui table thead tr th { - border-bottom: 1px solid rgba(58, 64, 80, .2); - color: #b5bac9; -} - -.swagger-ui .parameter__name.required::after { color: rgba(230, 153, 153, .6); } - -.swagger-ui .topbar .download-url-wrapper .select-label { color: #f0f0f0; } - -.swagger-ui .topbar .download-url-wrapper .download-url-button { - background: #63a040; - color: #fff; -} - -.swagger-ui .info .title small { background: #7c8492; } - -.swagger-ui .info .title small.version-stamp { background-color: #7a9b27; } - -.swagger-ui .auth-container .errors { - background-color: #350d0d; - color: #b5bac9; -} - -.swagger-ui .errors-wrapper { - background: rgba(200, 50, 50, .1); - border: 2px solid #c83232; -} - -.swagger-ui .markdown code, .swagger-ui .renderedmarkdown code { - background: rgba(0, 0, 0, .05); - color: #c299e6; -} - -.swagger-ui .model-toggle:after { background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgaW5rc2NhcGU6dmVyc2lvbj0iMS4wICg0MDM1YTRmYjQ5LCAyMDIwLTA1LTAxKSIKICAgc29kaXBvZGk6ZG9jbmFtZT0iZG93bmxvYWQyLnN2ZyIKICAgaWQ9InN2ZzQiCiAgIHZlcnNpb249IjEuMSIKICAgaGVpZ2h0PSIyNCIKICAgd2lkdGg9IjI0Ij4KICA8bWV0YWRhdGEKICAgICBpZD0ibWV0YWRhdGExMCI+CiAgICA8cmRmOlJERj4KICAgICAgPGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPgogICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PgogICAgICAgIDxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz4KICAgICAgPC9jYzpXb3JrPgogICAgPC9yZGY6UkRGPgogIDwvbWV0YWRhdGE+CiAgPGRlZnMKICAgICBpZD0iZGVmczgiIC8+CiAgPHNvZGlwb2RpOm5hbWVkdmlldwogICAgIGlua3NjYXBlOmN1cnJlbnQtbGF5ZXI9InN2ZzQiCiAgICAgaW5rc2NhcGU6d2luZG93LW1heGltaXplZD0iMSIKICAgICBpbmtzY2FwZTp3aW5kb3cteT0iLTkiCiAgICAgaW5rc2NhcGU6d2luZG93LXg9Ii05IgogICAgIGlua3NjYXBlOmN5PSIxMiIKICAgICBpbmtzY2FwZTpjeD0iMTIiCiAgICAgaW5rc2NhcGU6em9vbT0iMzQuNTgzMzMzIgogICAgIHNob3dncmlkPSJmYWxzZSIKICAgICBpZD0ibmFtZWR2aWV3NiIKICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSIxMDAxIgogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTkyMCIKICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIgogICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwIgogICAgIGd1aWRldG9sZXJhbmNlPSIxMCIKICAgICBncmlkdG9sZXJhbmNlPSIxMCIKICAgICBvYmplY3R0b2xlcmFuY2U9IjEwIgogICAgIGJvcmRlcm9wYWNpdHk9IjEiCiAgICAgYm9yZGVyY29sb3I9IiM2NjY2NjYiCiAgICAgcGFnZWNvbG9yPSIjZmZmZmZmIiAvPgogIDxwYXRoCiAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZiIKICAgICBpZD0icGF0aDIiCiAgICAgZD0iTTEwIDZMOC41OSA3LjQxIDEzLjE3IDEybC00LjU4IDQuNTlMMTAgMThsNi02eiIgLz4KPC9zdmc+Cg==) 50% no-repeat; } - -.swagger-ui .expand-operation svg, .swagger-ui section.models h4 svg { fill: #fff; } - -::-webkit-scrollbar-track { background-color: #646464 !important; } - -::-webkit-scrollbar-thumb { - background-color: #242424 !important; - border: 2px solid #3e4346 !important; -} - -::-webkit-scrollbar-button:vertical:start:decrement { - background: linear-gradient(130deg, #696969 40%, rgba(255, 0, 0, 0) 41%), linear-gradient(230deg, #696969 40%, transparent 41%), linear-gradient(0deg, #696969 40%, transparent 31%); - background-color: #b6b6b6; -} - -::-webkit-scrollbar-button:vertical:end:increment { - background: linear-gradient(310deg, #696969 40%, transparent 41%), linear-gradient(50deg, #696969 40%, transparent 41%), linear-gradient(180deg, #696969 40%, transparent 31%); - background-color: #b6b6b6; -} - -::-webkit-scrollbar-button:horizontal:end:increment { - background: linear-gradient(210deg, #696969 40%, transparent 41%), linear-gradient(330deg, #696969 40%, transparent 41%), linear-gradient(90deg, #696969 30%, transparent 31%); - background-color: #b6b6b6; -} - -::-webkit-scrollbar-button:horizontal:start:decrement { - background: linear-gradient(30deg, #696969 40%, transparent 41%), linear-gradient(150deg, #696969 40%, transparent 41%), linear-gradient(270deg, #696969 30%, transparent 31%); - background-color: #b6b6b6; -} - -::-webkit-scrollbar-button, ::-webkit-scrollbar-track-piece { background-color: #3e4346 !important; } - -.swagger-ui .black, .swagger-ui .checkbox, .swagger-ui .dark-gray, .swagger-ui .download-url-wrapper .loading, .swagger-ui .errors-wrapper .errors small, .swagger-ui .fallback, .swagger-ui .filter .loading, .swagger-ui .gray, .swagger-ui .hover-black:focus, .swagger-ui .hover-black:hover, .swagger-ui .hover-dark-gray:focus, .swagger-ui .hover-dark-gray:hover, .swagger-ui .hover-gray:focus, .swagger-ui .hover-gray:hover, .swagger-ui .hover-light-silver:focus, .swagger-ui .hover-light-silver:hover, .swagger-ui .hover-mid-gray:focus, .swagger-ui .hover-mid-gray:hover, .swagger-ui .hover-near-black:focus, .swagger-ui .hover-near-black:hover, .swagger-ui .hover-silver:focus, .swagger-ui .hover-silver:hover, .swagger-ui .light-silver, .swagger-ui .markdown pre, .swagger-ui .mid-gray, .swagger-ui .model .property, .swagger-ui .model .property.primitive, .swagger-ui .model-title, .swagger-ui .near-black, .swagger-ui .parameter__extension, .swagger-ui .parameter__in, .swagger-ui .prop-format, .swagger-ui .renderedmarkdown pre, .swagger-ui .response-col_links .response-undocumented, .swagger-ui .response-col_status .response-undocumented, .swagger-ui .silver, .swagger-ui section.models h4, .swagger-ui section.models h5, .swagger-ui span.token-not-formatted, .swagger-ui span.token-string, .swagger-ui table.headers .header-example, .swagger-ui table.model tr.description, .swagger-ui table.model tr.extension { color: #bfbfbf; } - -.swagger-ui .hover-white:focus, .swagger-ui .hover-white:hover, .swagger-ui .info .title small pre, .swagger-ui .topbar a, .swagger-ui .white { color: #fff; } - -.swagger-ui .bg-black-10, .swagger-ui .hover-bg-black-10:focus, .swagger-ui .hover-bg-black-10:hover, .swagger-ui .stripe-dark:nth-child(2n + 1) { background-color: rgba(0, 0, 0, .1); } - -.swagger-ui .bg-white-10, .swagger-ui .hover-bg-white-10:focus, .swagger-ui .hover-bg-white-10:hover, .swagger-ui .stripe-light:nth-child(2n + 1) { background-color: rgba(28, 28, 33, .1); } - -.swagger-ui .bg-light-silver, .swagger-ui .hover-bg-light-silver:focus, .swagger-ui .hover-bg-light-silver:hover, .swagger-ui .striped--light-silver:nth-child(2n + 1) { background-color: #6e6e6e; } - -.swagger-ui .bg-moon-gray, .swagger-ui .hover-bg-moon-gray:focus, .swagger-ui .hover-bg-moon-gray:hover, .swagger-ui .striped--moon-gray:nth-child(2n + 1) { background-color: #4d4d4d; } - -.swagger-ui .bg-light-gray, .swagger-ui .hover-bg-light-gray:focus, .swagger-ui .hover-bg-light-gray:hover, .swagger-ui .striped--light-gray:nth-child(2n + 1) { background-color: #2b2b2b; } - -.swagger-ui .bg-near-white, .swagger-ui .hover-bg-near-white:focus, .swagger-ui .hover-bg-near-white:hover, .swagger-ui .striped--near-white:nth-child(2n + 1) { background-color: #242424; } - -.swagger-ui .opblock-tag:hover, .swagger-ui section.models h4:hover { background: rgba(0, 0, 0, .02); } - -.swagger-ui .checkbox p, .swagger-ui .dialog-ux .modal-ux-content h4, .swagger-ui .dialog-ux .modal-ux-content p, .swagger-ui .dialog-ux .modal-ux-header h3, .swagger-ui .errors-wrapper .errors h4, .swagger-ui .errors-wrapper hgroup h4, .swagger-ui .info .base-url, .swagger-ui .info .title, .swagger-ui .info h1, .swagger-ui .info h2, .swagger-ui .info h3, .swagger-ui .info h4, .swagger-ui .info h5, .swagger-ui .info li, .swagger-ui .info p, .swagger-ui .info table, .swagger-ui .loading-container .loading::after, .swagger-ui .model, .swagger-ui .opblock .opblock-section-header h4, .swagger-ui .opblock .opblock-section-header > label, .swagger-ui .opblock .opblock-summary-description, .swagger-ui .opblock .opblock-summary-operation-id, .swagger-ui .opblock .opblock-summary-path, .swagger-ui .opblock .opblock-summary-path__deprecated, .swagger-ui .opblock-description-wrapper, .swagger-ui .opblock-description-wrapper h4, .swagger-ui .opblock-description-wrapper p, .swagger-ui .opblock-external-docs-wrapper, .swagger-ui .opblock-external-docs-wrapper h4, .swagger-ui .opblock-external-docs-wrapper p, .swagger-ui .opblock-tag small, .swagger-ui .opblock-title_normal, .swagger-ui .opblock-title_normal h4, .swagger-ui .opblock-title_normal p, .swagger-ui .parameter__name, .swagger-ui .parameter__type, .swagger-ui .response-col_links, .swagger-ui .response-col_status, .swagger-ui .responses-inner h4, .swagger-ui .responses-inner h5, .swagger-ui .scheme-container .schemes > label, .swagger-ui .scopes h2, .swagger-ui .servers > label, .swagger-ui .tab li, .swagger-ui label, .swagger-ui select, .swagger-ui table.headers td { color: #b5bac9; } - -.swagger-ui .download-url-wrapper .failed, .swagger-ui .filter .failed, .swagger-ui .model-deprecated-warning, .swagger-ui .parameter__deprecated, .swagger-ui .parameter__name.required span, .swagger-ui table.model tr.property-row .star { color: #e69999; } - -.swagger-ui .opblock-body pre.microlight, .swagger-ui textarea.curl { - background: #41444e; - border-radius: 4px; - color: #fff; -} - -.swagger-ui .expand-methods svg, .swagger-ui .expand-methods:hover svg { fill: #bfbfbf; } - -.swagger-ui .auth-container, .swagger-ui .dialog-ux .modal-ux-header { border-bottom: 1px solid #2e2e2e; } - -.swagger-ui .topbar .download-url-wrapper .select-label select, .swagger-ui .topbar .download-url-wrapper input[type=text] { border: 2px solid #63a040; } - -.swagger-ui .info a, .swagger-ui .info a:hover, .swagger-ui .scopes h2 a { color: #99bde6; } - -/* Dark Scrollbar */ -::-webkit-scrollbar { - width: 14px; - height: 14px; -} - -::-webkit-scrollbar-button { - background-color: #3e4346 !important; -} - -::-webkit-scrollbar-track { - background-color: #646464 !important; -} - -::-webkit-scrollbar-track-piece { - background-color: #3e4346 !important; -} - -::-webkit-scrollbar-thumb { - height: 50px; - background-color: #242424 !important; - border: 2px solid #3e4346 !important; -} - -::-webkit-scrollbar-corner {} - -::-webkit-resizer {} - -::-webkit-scrollbar-button:vertical:start:decrement { - background: - linear-gradient(130deg, #696969 40%, rgba(255, 0, 0, 0) 41%), - linear-gradient(230deg, #696969 40%, rgba(0, 0, 0, 0) 41%), - linear-gradient(0deg, #696969 40%, rgba(0, 0, 0, 0) 31%); - background-color: #b6b6b6; -} - -::-webkit-scrollbar-button:vertical:end:increment { - background: - linear-gradient(310deg, #696969 40%, rgba(0, 0, 0, 0) 41%), - linear-gradient(50deg, #696969 40%, rgba(0, 0, 0, 0) 41%), - linear-gradient(180deg, #696969 40%, rgba(0, 0, 0, 0) 31%); - background-color: #b6b6b6; -} - -::-webkit-scrollbar-button:horizontal:end:increment { - background: - linear-gradient(210deg, #696969 40%, rgba(0, 0, 0, 0) 41%), - linear-gradient(330deg, #696969 40%, rgba(0, 0, 0, 0) 41%), - linear-gradient(90deg, #696969 30%, rgba(0, 0, 0, 0) 31%); - background-color: #b6b6b6; -} - -::-webkit-scrollbar-button:horizontal:start:decrement { - background: - linear-gradient(30deg, #696969 40%, rgba(0, 0, 0, 0) 41%), - linear-gradient(150deg, #696969 40%, rgba(0, 0, 0, 0) 41%), - linear-gradient(270deg, #696969 30%, rgba(0, 0, 0, 0) 31%); - background-color: #b6b6b6; -} diff --git a/SmartHome/server/view/static/js/login.js b/SmartHome/server/view/static/js/login.js deleted file mode 100644 index e5bbfc1..0000000 --- a/SmartHome/server/view/static/js/login.js +++ /dev/null @@ -1,50 +0,0 @@ - -let error_div = document.getElementById('error-message') - -function setCookie(name, value, minutes) { - let expires = ""; - let date = new Date(); - date.setTime(date.getTime() + (minutes * 60 * 1000)); - expires = "; expires=" + date.toUTCString(); - document.cookie = name + "=" + (value || "") + expires + "; path=/"; -} - -function processForm(e) { - if (e.preventDefault) e.preventDefault(); - - const data = Object.fromEntries(new FormData(e.target).entries()); - - let urlEncodedData = ""; - let urlEncodedDataPairs = []; - let name; - - for(name in data) { - urlEncodedDataPairs.push(encodeURIComponent(name) + '=' + encodeURIComponent(data[name])); - } - - urlEncodedData = urlEncodedDataPairs.join('&').replace( /%20/g, '+'); - - fetch('/api/user/login', { - method: 'POST', - headers: {'Content-Type': 'application/x-www-form-urlencoded'}, - body: urlEncodedData - }).then(response => { - response.json().then(data => { - if (data.access_token) { - setCookie('access_token', data.access_token, 30) - window.location.reload(); - } else { - error_div.innerHTML = data.detail - } - }); - }); - - return false; -} - -let form = document.forms[0]; -if (form.attachEvent) { - form.attachEvent('submit', processForm); -} else { - form.addEventListener('submit', processForm); -} diff --git a/SmartHome/server/view/templates/login.jinja b/SmartHome/server/view/templates/login.jinja deleted file mode 100644 index 36d207e..0000000 --- a/SmartHome/server/view/templates/login.jinja +++ /dev/null @@ -1,62 +0,0 @@ - - - - - Admin Panel - - - - - - - - - - - - - - - - - -
-
-
- -
- - - diff --git a/SmartHome/tests/faker.py b/SmartHome/tests/faker.py deleted file mode 100644 index 4238a8e..0000000 --- a/SmartHome/tests/faker.py +++ /dev/null @@ -1,136 +0,0 @@ -from uuid import UUID, uuid1 -from typing import Callable, Any -import random - -from fastapi.testclient import TestClient -from sqlalchemy.orm import Session - -import models - - -room_names = [ 'Atrium', 'Ballroom', 'Bathroom', 'Bedroom', 'Billiard room', 'Cabinet', 'Computer lab', - 'Conference hall', 'Control room', 'Corner office', 'Dining room', 'Drawing room', 'Office', - 'Roomsharing', 'Root cellar', 'Rotting room', 'Rotunda', 'Small office', 'Student lounge', - 'Studio', 'Sunroom', 'Throne room', 'Toolroom', 'Waiting room', 'Workshop'] -model_names = ['Lamp', 'Led', 'Light', 'Lock', 'Door', 'Window rollets', 'Cleaner', 'TV', 'Speaker', 'Spot'] -parameter_names = ['Is on', 'Color', 'Power', 'Brightness', 'Mode', 'Target', 'Timer', 'Volume', 'Channel'] -parameter_value_types = ['bool', 'decimal', 'enum'] - -class Faker: - client: TestClient - create_session: Callable[[], Session] - - house_id = uuid1() - - # inject - user_access_token: str = '' - hub_access_token: str = '' - hub_refresh_token: str = '' - public_key: str = '' - - def __init__(self, client: TestClient, create_session: Callable[[], Session]): - self.client = client - self.create_session = create_session - - def init_hub(self): - hub_id = uuid1() - default_name = 'Inited Hub' - - response = self.client.post('/api/hub', json = { - 'id': str(hub_id), - 'name': default_name, - 'house_id': str(self.house_id), - 'access_token': self.hub_access_token, - 'refresh_token': self.hub_refresh_token, - 'public_key': self.public_key, - }, headers = { - 'Authorization': f'Bearer {self.user_access_token}' - }) - - return response.json() - - def get_house(self) -> dict[str, Any]: - response = self.client.get(f'/api/house/') - assert response.status_code == 200 - return response.json() - - def create_room(self): - with self.create_session() as session: - room = models.Room(house_id = self.house_id, name = random.choice(room_names)) - session.add(room) - session.commit() - session.refresh(room) - return room - - def create_device_model(self, id: UUID = uuid1()) -> models.DeviceModel: - with self.create_session() as session: - device_model = models.DeviceModel(id=id, name=random.choice(model_names)) - session.add(device_model) - session.commit() - session.refresh(device_model) - return device_model - - def create_parameter(self) -> models.Parameter: - with self.create_session() as session: - parameter = models.Parameter(name = random.choice(parameter_names), - value_type = random.choice(parameter_value_types)) - session.add(parameter) - session.commit() - session.refresh(parameter) - return parameter - - def create_device(self, name: str, room_id: UUID, model_id: UUID) -> models.Device: - with self.create_session() as session: - device = models.Device( - name = name, - urdi = random.randint(0, 2**32), - room_id = room_id, - model_id = model_id, - ) - session.add(device) - session.commit() - session.refresh(device) - return device - - def associate_devicemodel_parameter(self, - devicemodel_id: UUID, - parameter_id: UUID, - f: int) -> models.DeviceModelParameter: - - with self.create_session() as session: - parameter = models.DeviceModelParameter( - devicemodel_id = devicemodel_id, - parameter_id = parameter_id, - f = f - ) - session.add(parameter) - session.commit() - session.refresh(parameter) - return parameter - - def fill_db(self) -> models.House: - self.init_hub() - - with self.create_session() as session: - house = session.get(models.House, self.house_id) - - device_models = [self.create_device_model() for _ in range(7)] - parameters = [self.create_parameter() for _ in range(14)] - - for model in device_models: - random.shuffle(parameters) - for i in range(random.randint(1, 9)): - parameter = parameters[i] - self.associate_devicemodel_parameter( - model.id, parameter.id, i - ) - - for r in range(5): - room = self.create_room() - house.rooms.append(room) - for d in range(5): - device_model = random.choice(device_models) - name = f'{device_model.name} in {room.name.lower()}' - device = self.create_device(name, room.id, device_model.id) - - return house diff --git a/SmartHome/tests/setup.py b/SmartHome/tests/setup.py deleted file mode 100644 index d32a72d..0000000 --- a/SmartHome/tests/setup.py +++ /dev/null @@ -1,97 +0,0 @@ -from typing import Any -from uuid import uuid1, UUID -import os -import time - -from jose import jwt - -from fastapi import FastAPI -from fastapi.testclient import TestClient - -from sqlalchemy import create_engine, select -from sqlalchemy.orm import sessionmaker, Session -from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine - -from main import app -from server.dependencies.database import get_session, get_async_session -import models -import config - -from .faker import Faker - - -# Settings - -with open(f'{config.path}/tests/jwt.key', 'r') as f: - secret_key = f.read() -with open(f'{config.path}/tests/jwt.key.pub', 'r') as f: - public_key = f.read() - -user_id = UUID('f085ef73-f599-11ec-acda-58961df87e73') - -access_token = '' -hub_access_token = '' -hub_refresh_token = '' - -db_url = f'sqlite:///{config.src}/test_database.sqlite3' -async_db_url = f'sqlite+aiosqlite:///{config.src}/test_database.sqlite3' - -# Simulate Cloud Auth - -access_token = jwt.encode({ - 'type': 'access', - 'user_id': str(user_id), - 'is_admin': False, - 'exp': time.time() + 60 -}, secret_key, algorithm = 'RS256') - -# Sync DB - -engine = create_engine( - db_url, connect_args = {'check_same_thread': False} -) - -create_session = sessionmaker( - autocommit = False, autoflush = False, bind = engine -) - -def override_get_session() -> Session: - with create_session() as session: - yield session - -# Async DB - -async_engine = create_async_engine( - async_db_url, connect_args = {'check_same_thread': False} -) - -create_async_session = sessionmaker( - async_engine, class_ = AsyncSession, expire_on_commit = False -) - -async def override_get_async_session() -> AsyncSession: - async with create_async_session() as session: - yield session - -# App - -models.Base.metadata.drop_all(bind = engine) -models.Base.metadata.create_all(bind = engine) - -app.dependency_overrides[get_session] = override_get_session -app.dependency_overrides[get_async_session] = override_get_async_session -client = TestClient(app) - -# Faker - -faker = Faker(client, create_session) -faker.hub_access_token = hub_access_token -faker.hub_refresh_token = hub_refresh_token -faker.public_key = public_key -faker.user_access_token = access_token - -# - -auth_headers = { - 'Authorization': f'Bearer {access_token}' -} diff --git a/SmartHome/tests/test_endpoints/__init__.py b/SmartHome/tests/test_endpoints/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/SmartHome/tests/test_endpoints/test_device.py b/SmartHome/tests/test_endpoints/test_device.py deleted file mode 100644 index 2fb462c..0000000 --- a/SmartHome/tests/test_endpoints/test_device.py +++ /dev/null @@ -1,54 +0,0 @@ -from tests.setup import * - - -faker.init_hub() -id = '62ff3e2b-0130-0002-0001-000000000001' -room_id = faker.create_room().id -faker.create_device_model('00000000-0000-0002-0000-000000000000') -device = { - 'id': id, - 'name': 'New Room', - 'room_id': str(room_id), -} - -def test_get_device_null(): - response = client.get(f'/api/device/', headers = auth_headers) - assert response.status_code == 405, response.text - assert response.json().get('detail') == 'Method Not Allowed' - -def test_get_device_404(): - response = client.get(f'/api/device/{id}', headers = auth_headers) - assert response.status_code == 404, response.text - assert response.json().get('detail') == 'Device not found' - -def test_delete_device_404(): - response = client.delete(f'/api/device/{id}', headers = auth_headers) - assert response.status_code == 404, response.text - assert response.json().get('detail') == 'Device not found' - -def test_create_device(): - response = client.post(f'/api/device', json = device, headers = auth_headers) - new_device = response.json() - assert response.status_code == 200, response.text - assert new_device.get('model') - device['model'] = new_device.get('model') - assert new_device == device - -def test_get_device(): - response = client.get(f'/api/device/{id}', headers = auth_headers) - device['parameters'] = [] - assert response.status_code == 200, response.text - assert response.json() == device - -def test_patch_device(): - device['name'] = 'Patched Device' - response = client.patch(f'/api/device/{id}', json = device, headers = auth_headers) - assert response.status_code == 200, response.text - assert client.get(f'/api/device/{id}', headers = auth_headers).json() == device - -def test_delete_device(): - response = client.delete(f'/api/device/{id}', json = device, headers = auth_headers) - assert response.status_code == 200, response.text - -def test_get_deleted_device(): - test_get_device_404() diff --git a/SmartHome/tests/test_endpoints/test_house.py b/SmartHome/tests/test_endpoints/test_house.py deleted file mode 100644 index 3b36863..0000000 --- a/SmartHome/tests/test_endpoints/test_house.py +++ /dev/null @@ -1,10 +0,0 @@ -from tests.setup import * - - -def get_house() -> dict[str, Any]: - response = client.get(f'/api/house/', headers = auth_headers) - assert response.status_code == 200 - return response.json() - -def test_get_house(): - get_house() diff --git a/SmartHome/tests/test_endpoints/test_hub.py b/SmartHome/tests/test_endpoints/test_hub.py deleted file mode 100644 index fa48d60..0000000 --- a/SmartHome/tests/test_endpoints/test_hub.py +++ /dev/null @@ -1,52 +0,0 @@ -from tests.setup import * - - -id = uuid1() -default_name = 'Inited Hub' -hub_init_json = { - 'id': str(id), - 'name': default_name, - 'house_id': str(faker.house_id), - 'access_token': hub_access_token, - 'refresh_token': hub_refresh_token, - 'public_key': public_key, -} - -def test_get_hub_401(): - response = client.get('/api/hub') - assert response.status_code in [401, 403], response.text - assert response.json() in [{'detail': 'Not authenticated'}, {'detail': 'Access denied'}] - -def test_init_hub_401(): - response = client.post('/api/hub', json = hub_init_json) - assert response.status_code == 401, response.text - assert response.json() == {'detail': 'Not authenticated'} - -def _test_get_hub_404(): - response = client.get('/api/hub', headers = auth_headers) - assert response.status_code == 404 - assert response.json() == {'detail': 'Not found'} - -def test_init_hub(): - response = client.post('/api/hub', json = hub_init_json, headers = auth_headers) - assert response.status_code == 200 - -def test_get_hub(): - response = client.get('/api/hub', headers = auth_headers) - assert response.status_code == 200 - assert response.json() == { - 'id': str(id), - 'name': default_name, - 'house_id': str(faker.house_id), - } - -def test_patch_hub(): - hub = client.get('/api/hub', headers = auth_headers).json() - hub['name'] = 'Patched Hub' - - response = client.patch('/api/hub', json = { - 'name': 'Patched Hub', - }, headers = auth_headers) - - assert response.status_code == 200 - assert client.get('/api/hub', headers = auth_headers).json() == hub diff --git a/SmartHome/tests/test_endpoints/test_room.py b/SmartHome/tests/test_endpoints/test_room.py deleted file mode 100644 index 3d705e9..0000000 --- a/SmartHome/tests/test_endpoints/test_room.py +++ /dev/null @@ -1,51 +0,0 @@ -from tests.setup import * - - -id = uuid1() -room = { - 'name': 'New Room', - 'devices': [] -} - -def test_get_room_null(): - response = client.get(f'/api/room/', headers = auth_headers) - assert response.status_code == 405 - assert response.json().get('detail') == 'Method Not Allowed' - -def test_get_room_404(): - response = client.get(f'/api/room/{id}', headers = auth_headers) - assert response.status_code == 404 - assert response.json().get('detail') == 'Room not found' - -def test_delete_room_404(): - response = client.delete(f'/api/room/{id}', headers = auth_headers) - assert response.status_code == 404 - assert response.json().get('detail') == 'Room not found' - -def test_create_room(): - response = client.post(f'/api/room', json = room, headers = auth_headers) - new_room = response.json() - assert response.status_code == 200 - global id - id = new_room.get('id') - assert id - room['id'] = id - assert new_room == room - -def test_get_room(): - response = client.get(f'/api/room/{id}', headers = auth_headers) - assert response.status_code == 200 - assert response.json() == room - -def test_patch_room(): - room['name'] = 'Patched Room' - response = client.patch(f'/api/room/{id}', json = room, headers = auth_headers) - assert response.status_code == 200 - assert client.get(f'/api/room/{id}', headers = auth_headers).json() == room - -def test_delete_room(): - response = client.delete(f'/api/room/{id}', json = room, headers = auth_headers) - assert response.status_code == 200 - -def test_get_deleted_room(): - test_get_room_404() diff --git a/SmartHome/tests/test_endpoints/test_ws.py b/SmartHome/tests/test_endpoints/test_ws.py deleted file mode 100644 index 2bbbd91..0000000 --- a/SmartHome/tests/test_endpoints/test_ws.py +++ /dev/null @@ -1,26 +0,0 @@ -from starlette.websockets import WebSocketDisconnect -from tests.setup import * - - -def get_device() -> models.Device: - with create_session() as session: - return session.scalars( - select(models.Device) - ).first() - -def _test_ws(): - faker.fill_db() - device = get_device() - parameter_id = device.model.parameters[0].parameter_id - try: - with client.websocket_connect('/ws/', headers = auth_headers) as ws: - ws.send_json({ - 'type': 'merlin', - 'data': { - 'device_id': str(device.id), - 'parameter_id': str(parameter_id), - 'value': 0 - } - }, mode = 'text') - except WebSocketDisconnect: - pass diff --git a/TelegramBot/CallbackTeleBot.py b/TelegramBot/CallbackTeleBot.py deleted file mode 100644 index 06fcb4d..0000000 --- a/TelegramBot/CallbackTeleBot.py +++ /dev/null @@ -1,47 +0,0 @@ -from telebot import TeleBot, util, apihelper - - -# added callback for __threaded_polling -class CallbackTeleBot(TeleBot): - def polling(self, none_stop=False, interval=0, timeout=20, callback = lambda *args: None, args = () ): - if self.threaded: - self.__threaded_polling(none_stop, interval, timeout, callback, args) - else: - self._TeleBot__non_threaded_polling(none_stop, interval, timeout) - - def __threaded_polling(self, none_stop=False, interval=0, timeout=20, callback = lambda *args: None, args = () ): - self._TeleBot__stop_polling.clear() - error_interval = 0.25 - - polling_thread = util.WorkerThread(name="PollingThread") - or_event = util.OrEvent( - polling_thread.done_event, - polling_thread.exception_event, - self.worker_pool.exception_event - ) - - while not self._TeleBot__stop_polling.wait(interval): - callback(*args) # added by Parker - or_event.clear() - try: - polling_thread.put(self._TeleBot__retrieve_updates, timeout) - - or_event.wait() # wait for polling thread finish, polling thread error or thread pool error - - polling_thread.raise_exceptions() - self.worker_pool.raise_exceptions() - - error_interval = 0.25 - except apihelper.ApiException as e: - if not none_stop: - self._TeleBot__stop_polling.set() - else: - polling_thread.clear_exceptions() - self.worker_pool.clear_exceptions() - time.sleep(error_interval) - error_interval *= 2 - except KeyboardInterrupt: - self._TeleBot__stop_polling.set() - break - - polling_thread.stop() diff --git a/TelegramBot/TelegramBot.py b/TelegramBot/TelegramBot.py deleted file mode 100644 index fcf5a2c..0000000 --- a/TelegramBot/TelegramBot.py +++ /dev/null @@ -1,73 +0,0 @@ -import time -import os - -import config -from ArchieCore import Command, CommandsContextManager, CommandsContextManagerDelegate -from IO import Text2Speech -from Features.Media import YoutubePlayer, TorrentPlayer -from CallbackTeleBot import CallbackTeleBot - - -class TelegramBot(CommandsContextManagerDelegate): - - online = True - voids = 0 - voice = Text2Speech.Engine() - bot = CallbackTeleBot(config.telebot) - commandsContext: CommandsContextManager - - # Control - - def __init__(self): - self.commandsContext = CommandsContextManager(delegate = self) - - def start(self): - while True: - try: - print("Start polling...") - self.bot.polling(callback = self.commandsContext.checkThreads) - except Exception as e: - print(e, "\nPolling failed") - time.sleep(10) - - def stop(self): - raise NotImplementedError - - # CommandsContextManagerDelegate - - def commandsContextDidReceiveResponse(self, response): - id = response.data.get('id') - if not id: return - - if response.text: - self.bot.send_message(id, response.text) - if response.voice: - path = self.voice.generate(response.voice).path - voiceFile = open(path, 'rb') - try: - self.bot.send_voice(id, voiceFile) - finally: - voiceFile.close() - - # Telebot - - @bot.message_handler(commands = ['vlc', 'queue', 'cmd']) - def simple_commands(msg): - command = msg.text.replace('/cmd', '').replace('/vlc', 'vlc') - if '/queue' in msg.text: command = 'vlc ' + command.replace('/queue', '') + '--playlist-enqueue' - os.system(f'lxterminal --command="{command}"') - - @bot.message_handler(commands=['terminal']) - def terminal(msg): - command = msg.text.replace('/terminal', '') - output = os.popen(command).read() - bot.send_message(msg.chat.id, output) - - @bot.message_handler(content_types = ['text']) - def execute(msg): - if 'youtu' in msg.text: - YoutubePlayer(msg.text).play() - elif '.torrent' in msg.text: - TorrentPlayer.playUrl(msg.text) - else: - TelegramBot().commandsContext.processString(msg.text.lower(), data = {'id': msg.chat.id}) diff --git a/TelegramBot/main.py b/TelegramBot/main.py deleted file mode 100644 index ce4a949..0000000 --- a/TelegramBot/main.py +++ /dev/null @@ -1,11 +0,0 @@ -import os, sys - -# root = os.path.dirname(os.path.dirname(__file__)) -# sys.path.append(root) -# sys.path.append(root + '/VoiceAssistant') - -from TelegramBot import TelegramBot - - -if __name__ == '__main__': - TelegramBot().start() diff --git a/VoiceAssistant/ArchieCore/ACObjects/ACTime.py b/VoiceAssistant/ArchieCore/ACObjects/ACTime.py deleted file mode 100644 index 9bd3e9f..0000000 --- a/VoiceAssistant/ArchieCore/ACObjects/ACTime.py +++ /dev/null @@ -1,31 +0,0 @@ -from __future__ import annotations -from typing import Optional -from datetime import datetime, timedelta - -from .ACObject import ACObject, Pattern, classproperty -from .ACTimeInterval import ACTimeInterval - -class ACTime(ACObject): - value: datetime - - def __init__(self, value: Optional[datetime] = None): - self.value = value or datetime.now() - - def addingInterval(self, timeinterval: Union[ACObject, Number]) -> ACTime: - seconds = timeinterval.value if isinstance(timeinterval, ACTimeInterval) else timeinterval - return ACTime(self.value + timedelta(seconds = seconds)) - - def addInterval(self, timeinterval: Union[ACObject, Number]): - seconds = timeinterval.value if isinstance(timeinterval, ACTimeInterval) else timeinterval - self.value += timedelta(seconds = seconds) - - @classmethod - def parse(cls, fromString: str) -> ACObject: - raise NotImplementedError - - @classproperty - def pattern() -> Pattern: - raise NotImplementedError - - def __sub__(self, other: ACTime) -> ACTimeInterval: - return ACTimeInterval((self.value - other.value).total_seconds()) diff --git a/VoiceAssistant/ArchieCore/ACObjects/ACTimeInterval.py b/VoiceAssistant/ArchieCore/ACObjects/ACTimeInterval.py deleted file mode 100644 index 35a1b3b..0000000 --- a/VoiceAssistant/ArchieCore/ACObjects/ACTimeInterval.py +++ /dev/null @@ -1,75 +0,0 @@ -from __future__ import annotations -from typing import Union -from numbers import Number - -from .ACObject import ACObject, Pattern, classproperty - -class ACTimeInterval(ACObject): - value: float # seconds - - @classmethod - def initWith(seconds: float = 0, - minutes: float = 0, - hours: float = 0, - days: float = 0, - weeks: float = 0) -> ACTimeInterval: - - days += weeks * 7 - hours += days * 24 - minutes += hours * 60 - seconds += minutes * 60 - return ACTimeInterval(value = seconds) - - @classmethod - def parse(cls, fromString: str) -> ACObject: - raise NotImplementedError - - @classproperty - def pattern() -> Pattern: - raise NotImplementedError - - def __lt__(self, other: ACObject) -> bool: - value = other.value if isinstance(other, ACTimeInterval) else other - return self.value < value - - def __le__(self, other: ACObject) -> bool: - value = other.value if isinstance(other, ACTimeInterval) else other - return self.value <= value - - def __gt__(self, other: ACObject) -> bool: - value = other.value if isinstance(other, ACTimeInterval) else other - return self.value > value - - def __ge__(self, other: ACObject) -> bool: - value = other.value if isinstance(other, ACTimeInterval) else other - return self.value >= value - - def __eq__(self, other: ACObject) -> bool: - value = other.value if isinstance(other, ACTimeInterval) else other - return self.value == value - - def __ne__(self, other: ACObject) -> bool: - value = other.value if isinstance(other, ACTimeInterval) else other - return self.value != value - - def __add__(self, other: Union[ACObject, Number]) -> ACTimeInterval: - value = other.value if isinstance(other, ACTimeInterval) else other - return ACTimeInterval(self.value + value) - - def __sub__(self, other: Union[ACObject, Number]) -> ACTimeInterval: - value = other.value if isinstance(other, ACTimeInterval) else other - return ACTimeInterval(self.value - value) - - def __mul__(self, other: Union[ACObject, Number]) -> ACTimeInterval: - value = other.value if isinstance(other, ACTimeInterval) else other - return ACTimeInterval(self.value * value) - - def __mod__(self, other: Union[ACObject, Number]) -> ACTimeInterval: - value = other.value if isinstance(other, ACTimeInterval) else other - return ACTimeInterval(self.value % value) - - def __floordiv__(self, other: Union[ACObject, Number]) -> Union[ACObject, Number]: - return self.value // other.value if isinstance(other, ACTimeInterval) else ACTimeInterval(self.value // other) - - def __truediv__(self, other: Union[ACObject, Number]) -> Union[ACObject, Number]: - return self.value / other.value if isinstance(other, ACTimeInterval) else ACTimeInterval(self.value / other) diff --git a/VoiceAssistant/ArchieCore/ACObjects/ACWord.py b/VoiceAssistant/ArchieCore/ACObjects/ACWord.py deleted file mode 100644 index b047870..0000000 --- a/VoiceAssistant/ArchieCore/ACObjects/ACWord.py +++ /dev/null @@ -1,8 +0,0 @@ -from .ACObject import Pattern, classproperty -from . import ACString - -class ACWord(ACString): - - @classproperty - def pattern() -> Pattern: - return Pattern('[:word:]') diff --git a/VoiceAssistant/ArchieCore/ACObjects/__init__.py b/VoiceAssistant/ArchieCore/ACObjects/__init__.py deleted file mode 100644 index 0008973..0000000 --- a/VoiceAssistant/ArchieCore/ACObjects/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from .ACObject import ACObject -from .ACNumber import ACNumber -from .ACString import ACString -from .ACWord import ACWord -from .ACTime import ACTime -from .ACTimeInterval import ACTimeInterval diff --git a/VoiceAssistant/Middlewares/NumeralsParser.py b/VoiceAssistant/Middlewares/NumeralsParser.py deleted file mode 100644 index eb4c26e..0000000 --- a/VoiceAssistant/Middlewares/NumeralsParser.py +++ /dev/null @@ -1,101 +0,0 @@ -def is_number(x): - if type(x) == str: - x = x.replace(',', '') - try: - float(x) - except: - return False - return True - -def text2int (textnum, numwords={}): - units = [ - 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', - 'nine', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', - 'sixteen', 'seventeen', 'eighteen', 'nineteen', - ] - tens = ['', '', 'twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety'] - scales = ['hundred', 'thousand', 'million', 'billion', 'trillion'] - ordinal_words = {'first':1, 'second':2, 'third':3, 'fifth':5, 'eighth':8, 'ninth':9, 'twelfth':12} - ordinal_endings = [('ieth', 'y'), ('th', '')] - - if not numwords: - numwords['and'] = (1, 0) - for idx, word in enumerate(units): numwords[word] = (1, idx) - for idx, word in enumerate(tens): numwords[word] = (1, idx * 10) - for idx, word in enumerate(scales): numwords[word] = (10 ** (idx * 3 or 2), 0) - - textnum = textnum.replace('-', ' ') - - current = result = 0 - curstring = '' - onnumber = False - lastunit = False - lastscale = False - - def is_numword(x): - if is_number(x): - return True - if word in numwords: - return True - return False - - def from_numword(x): - if is_number(x): - scale = 0 - increment = int(x.replace(',', '')) - return scale, increment - return numwords[x] - - for word in textnum.split(): - if word in ordinal_words: - scale, increment = (1, ordinal_words[word]) - current = current * scale + increment - if scale > 100: - result += current - current = 0 - onnumber = True - lastunit = False - lastscale = False - else: - for ending, replacement in ordinal_endings: - if word.endswith(ending): - word = "%s%s" % (word[:-len(ending)], replacement) - - if (not is_numword(word)) or (word == 'and' and not lastscale): - if onnumber: - # Flush the current number we are building - curstring += repr(result + current) + " " - curstring += word + " " - result = current = 0 - onnumber = False - lastunit = False - lastscale = False - else: - scale, increment = from_numword(word) - onnumber = True - - if lastunit and (word not in scales): - # Assume this is part of a string of individual numbers to - # be flushed, such as a zipcode "one two three four five" - curstring += repr(result + current) - result = current = 0 - - if scale > 1: - current = max(1, current) - - current = current * scale + increment - if scale > 100: - result += current - current = 0 - - lastscale = False - lastunit = False - if word in scales: - lastscale = True - elif word in units: - lastunit = True - - if onnumber: - curstring += repr(result + current) - - return curstring diff --git a/VoiceAssistant/Middlewares/__init__.py b/VoiceAssistant/Middlewares/__init__.py deleted file mode 100644 index 3a52af5..0000000 --- a/VoiceAssistant/Middlewares/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .NumeralsParser import * diff --git a/config.example.py b/config.example.py deleted file mode 100644 index 7025cae..0000000 --- a/config.example.py +++ /dev/null @@ -1,37 +0,0 @@ -import pathlib -path = str(pathlib.Path(__file__).parent.absolute()) -del pathlib - - -src: str = path + '/resources' - -# Api keys - -telebot: str -goole_tts_json_key: str = src + '/tts-gc-key.json' - -# Speech Recognition - -vosk_model: str = src + '/model-small-rus' - -# TTS - -language_code: str = 'ru-RU' -voice_volume: float = 1 - -# Archie settings - -double_clap_activation: bool = False - -# Archie Core - -names: list[str] = ['арчи', 'archie'] - -# DB - -db_url: str = 'sqlite:///./sql_app.db' - -# WiFi - -wifi_ssid: str = 'Archie Hub' -wifi_password: str = '12345678' diff --git a/VoiceAssistant/Features/Media/MediaPlayer.py b/majordom_va/Features/Media/MediaPlayer.py similarity index 100% rename from VoiceAssistant/Features/Media/MediaPlayer.py rename to majordom_va/Features/Media/MediaPlayer.py diff --git a/VoiceAssistant/Features/Media/TorrentPlayer.py b/majordom_va/Features/Media/TorrentPlayer.py similarity index 100% rename from VoiceAssistant/Features/Media/TorrentPlayer.py rename to majordom_va/Features/Media/TorrentPlayer.py diff --git a/VoiceAssistant/Features/Media/YoutubePlayer.py b/majordom_va/Features/Media/YoutubePlayer.py similarity index 100% rename from VoiceAssistant/Features/Media/YoutubePlayer.py rename to majordom_va/Features/Media/YoutubePlayer.py diff --git a/VoiceAssistant/Features/Media/__init__.py b/majordom_va/Features/Media/__init__.py similarity index 100% rename from VoiceAssistant/Features/Media/__init__.py rename to majordom_va/Features/Media/__init__.py diff --git a/VoiceAssistant/Features/Media/playfilm.py b/majordom_va/Features/Media/playfilm.py similarity index 84% rename from VoiceAssistant/Features/Media/playfilm.py rename to majordom_va/Features/Media/playfilm.py index 73c9869..682be4e 100644 --- a/VoiceAssistant/Features/Media/playfilm.py +++ b/majordom_va/Features/Media/playfilm.py @@ -2,14 +2,14 @@ import requests from bs4 import BeautifulSoup as BS import os from .TorrentPlayer import TorrentPlayer -from ArchieCore import Command, Response +from VICore import Command, Response ################################################################################ -@Command.new(['$name:ACString']) +@Command.new(['$name:VIString']) def playfilm_cb(params): return playfilm(params) -@Command.new(['включ* фильм $name:ACString', 'включ* фильм*']) +@Command.new(['включ* фильм $name:VIString', 'включ* фильм*']) def playfilm(params): name = params.get('name') diff --git a/VoiceAssistant/Features/QA/QA.py b/majordom_va/Features/QA/QA.py similarity index 98% rename from VoiceAssistant/Features/QA/QA.py rename to majordom_va/Features/QA/QA.py index d150fff..d31184a 100644 --- a/VoiceAssistant/Features/QA/QA.py +++ b/majordom_va/Features/QA/QA.py @@ -1,5 +1,5 @@ from bs4 import BeautifulSoup as BS -from ArchieCore import CommandsManager, Command, Response, ResponseAction +from VICore import CommandsManager, Command, Response, ResponseAction import wikipedia as wiki import requests import random diff --git a/VoiceAssistant/Features/Raspi/Raspi.py b/majordom_va/Features/Raspi/Raspi.py similarity index 100% rename from VoiceAssistant/Features/Raspi/Raspi.py rename to majordom_va/Features/Raspi/Raspi.py diff --git a/VoiceAssistant/Features/Raspi/__init__.py b/majordom_va/Features/Raspi/__init__.py similarity index 100% rename from VoiceAssistant/Features/Raspi/__init__.py rename to majordom_va/Features/Raspi/__init__.py diff --git a/VoiceAssistant/Features/Raspi/gitpull.py b/majordom_va/Features/Raspi/gitpull.py similarity index 95% rename from VoiceAssistant/Features/Raspi/gitpull.py rename to majordom_va/Features/Raspi/gitpull.py index 86b0e05..41760c0 100644 --- a/VoiceAssistant/Features/Raspi/gitpull.py +++ b/majordom_va/Features/Raspi/gitpull.py @@ -1,6 +1,6 @@ from .Raspi import Raspi import os -from ArchieCore import Command, CommandsManager, Response +from VICore import Command, CommandsManager, Response import config ################################################################################ diff --git a/VoiceAssistant/Features/Raspi/tv.py b/majordom_va/Features/Raspi/tv.py similarity index 97% rename from VoiceAssistant/Features/Raspi/tv.py rename to majordom_va/Features/Raspi/tv.py index 293b8f4..66103a4 100644 --- a/VoiceAssistant/Features/Raspi/tv.py +++ b/majordom_va/Features/Raspi/tv.py @@ -1,5 +1,5 @@ from .Raspi import * -from ArchieCore import Command, Response +from VICore import Command, Response ################################################################################ diff --git a/VoiceAssistant/Features/SmallTalk/SmallTalk.py b/majordom_va/Features/SmallTalk/SmallTalk.py similarity index 100% rename from VoiceAssistant/Features/SmallTalk/SmallTalk.py rename to majordom_va/Features/SmallTalk/SmallTalk.py diff --git a/VoiceAssistant/Features/SmallTalk/__init__.py b/majordom_va/Features/SmallTalk/__init__.py similarity index 100% rename from VoiceAssistant/Features/SmallTalk/__init__.py rename to majordom_va/Features/SmallTalk/__init__.py diff --git a/VoiceAssistant/Features/SmallTalk/ctime.py b/majordom_va/Features/SmallTalk/ctime.py similarity index 98% rename from VoiceAssistant/Features/SmallTalk/ctime.py rename to majordom_va/Features/SmallTalk/ctime.py index 00f597a..067925c 100644 --- a/VoiceAssistant/Features/SmallTalk/ctime.py +++ b/majordom_va/Features/SmallTalk/ctime.py @@ -2,7 +2,7 @@ import datetime, time import requests from bs4 import BeautifulSoup as BS import math -from ArchieCore import Command, Response +from VICore import Command, Response @Command.new([ 'который * час в $text', diff --git a/VoiceAssistant/Features/SmallTalk/hello.py b/majordom_va/Features/SmallTalk/hello.py similarity index 76% rename from VoiceAssistant/Features/SmallTalk/hello.py rename to majordom_va/Features/SmallTalk/hello.py index 5078a22..de28a26 100644 --- a/VoiceAssistant/Features/SmallTalk/hello.py +++ b/majordom_va/Features/SmallTalk/hello.py @@ -1,4 +1,4 @@ -from ArchieCore import Command, Response +from VICore import Command, Response @Command.new(['привет*',]) def hello(params): diff --git a/VoiceAssistant/Features/Zieit/Zieit.py b/majordom_va/Features/Zieit/Zieit.py similarity index 100% rename from VoiceAssistant/Features/Zieit/Zieit.py rename to majordom_va/Features/Zieit/Zieit.py diff --git a/VoiceAssistant/Features/Zieit/__init__.py b/majordom_va/Features/Zieit/__init__.py similarity index 100% rename from VoiceAssistant/Features/Zieit/__init__.py rename to majordom_va/Features/Zieit/__init__.py diff --git a/VoiceAssistant/Features/Zieit/myshedule.py b/majordom_va/Features/Zieit/myshedule.py similarity index 98% rename from VoiceAssistant/Features/Zieit/myshedule.py rename to majordom_va/Features/Zieit/myshedule.py index 4c70b0c..f4cae05 100644 --- a/VoiceAssistant/Features/Zieit/myshedule.py +++ b/majordom_va/Features/Zieit/myshedule.py @@ -1,4 +1,4 @@ -from ArchieCore import Command, Response +from VICore import Command, Response from .Zieit import Zieit def formatLesson(lesson): diff --git a/VoiceAssistant/Features/__init__.py b/majordom_va/Features/__init__.py similarity index 100% rename from VoiceAssistant/Features/__init__.py rename to majordom_va/Features/__init__.py diff --git a/General/DispatchQueue/DispatchQueue.py b/majordom_va/General/DispatchQueue/DispatchQueue.py similarity index 81% rename from General/DispatchQueue/DispatchQueue.py rename to majordom_va/General/DispatchQueue/DispatchQueue.py index 08c2032..62c4f31 100644 --- a/General/DispatchQueue/DispatchQueue.py +++ b/majordom_va/General/DispatchQueue/DispatchQueue.py @@ -1,35 +1,35 @@ from typing import Callable, Optional from time import sleep -from ArchieCore.ACObjects import ACTime, ACTimeInterval +from VICore.VIObjects import VITime, VITimeInterval from .. import Singleton, UUID, threadingFunction from .DispatchQueueItem import DispatchQueueItem class DispatchQueue(Singleton): _queue: [DispatchQueueItem] = [] - _nearestItemTime: Optional[ACTime] = None + _nearestItemTime: Optional[VITime] = None - def asyncAt(self, time: ACTime, execute: Callable, *args, **kwargs) -> UUID: + def asyncAt(self, time: VITime, execute: Callable, *args, **kwargs) -> UUID: item = DispatchQueueItem(execute = execute, time = time, args = args, kwargs = kwargs) self.insert(item) return item.id - def asyncAfter(self, timeinterval: ACTimeInterval, execute: Callable, *args, **kwargs) -> UUID: - time = ACTime().addingInterval(timeinterval) + def asyncAfter(self, timeinterval: VITimeInterval, execute: Callable, *args, **kwargs) -> UUID: + time = VITime().addingInterval(timeinterval) item = DispatchQueueItem(execute = execute, time = time, timeinterval = timeinterval, args = args, kwargs = kwargs) self.insert(item) return item.id - def repeatEvery(self, timeinterval: ACTimeInterval, execute: Callable, *args, **kwargs) -> UUID: - time = ACTime().addingInterval(timeinterval) + def repeatEvery(self, timeinterval: VITimeInterval, execute: Callable, *args, **kwargs) -> UUID: + time = VITime().addingInterval(timeinterval) item = DispatchQueueItem(execute = execute, time = time, timeinterval = timeinterval, repeat = True, args = args, kwargs = kwargs) self.insert(item) return item.id def insert(self, item: DispatchQueueItem): - now = ACTime() + now = VITime() low = 0 high = len(self._queue) @@ -51,7 +51,7 @@ class DispatchQueue(Singleton): def loop(self): while True: - if not self._queue or not self._nearestItemTime or self._nearestItemTime > ACTime(): + if not self._queue or not self._nearestItemTime or self._nearestItemTime > VITime(): sleep(0.1) continue diff --git a/General/DispatchQueue/DispatchQueueItem.py b/majordom_va/General/DispatchQueue/DispatchQueueItem.py similarity index 72% rename from General/DispatchQueue/DispatchQueueItem.py rename to majordom_va/General/DispatchQueue/DispatchQueueItem.py index fd6ddd7..cb96b76 100644 --- a/General/DispatchQueue/DispatchQueueItem.py +++ b/majordom_va/General/DispatchQueue/DispatchQueueItem.py @@ -1,16 +1,16 @@ from typing import Callable, Any -from ArchieCore import ACTime, ACTimeInterval +from VICore import VITime, VITimeInterval from .. import Identifable, threadingFunction class DispatchQueueItem(Identifable): - time: ACTime - timeinterval: ACTimeInterval + time: VITime + timeinterval: VITimeInterval worker: Callable args: list[Any] kwargs: dict[Any, Any] repeat: bool - def __init__(self, execute: Callable, time: ACTime = ACTime(), timeinterval: ACTimeInterval = ACTimeInterval(0), + def __init__(self, execute: Callable, time: VITime = VITime(), timeinterval: VITimeInterval = VITimeInterval(0), repeat: bool = False, args: list[Any] = [], kwargs: dict[Any, Any] = []): self.time = time self.timeinterval = timeinterval diff --git a/General/DispatchQueue/__init__.py b/majordom_va/General/DispatchQueue/__init__.py similarity index 100% rename from General/DispatchQueue/__init__.py rename to majordom_va/General/DispatchQueue/__init__.py diff --git a/General/Identifable/Identifable.py b/majordom_va/General/Identifable/Identifable.py similarity index 100% rename from General/Identifable/Identifable.py rename to majordom_va/General/Identifable/Identifable.py diff --git a/General/Identifable/UUID.py b/majordom_va/General/Identifable/UUID.py similarity index 100% rename from General/Identifable/UUID.py rename to majordom_va/General/Identifable/UUID.py diff --git a/General/Identifable/__init__.py b/majordom_va/General/Identifable/__init__.py similarity index 100% rename from General/Identifable/__init__.py rename to majordom_va/General/Identifable/__init__.py diff --git a/General/Notifications/Notification.py b/majordom_va/General/Notifications/Notification.py similarity index 100% rename from General/Notifications/Notification.py rename to majordom_va/General/Notifications/Notification.py diff --git a/General/Notifications/NotificationCenter.py b/majordom_va/General/Notifications/NotificationCenter.py similarity index 100% rename from General/Notifications/NotificationCenter.py rename to majordom_va/General/Notifications/NotificationCenter.py diff --git a/General/Notifications/NotificationName.py b/majordom_va/General/Notifications/NotificationName.py similarity index 100% rename from General/Notifications/NotificationName.py rename to majordom_va/General/Notifications/NotificationName.py diff --git a/General/Notifications/NotificationObserver.py b/majordom_va/General/Notifications/NotificationObserver.py similarity index 100% rename from General/Notifications/NotificationObserver.py rename to majordom_va/General/Notifications/NotificationObserver.py diff --git a/General/Notifications/__init__.py b/majordom_va/General/Notifications/__init__.py similarity index 100% rename from General/Notifications/__init__.py rename to majordom_va/General/Notifications/__init__.py diff --git a/General/Singleton.py b/majordom_va/General/Singleton.py similarity index 100% rename from General/Singleton.py rename to majordom_va/General/Singleton.py diff --git a/General/ThreadingFunction.py b/majordom_va/General/ThreadingFunction.py similarity index 100% rename from General/ThreadingFunction.py rename to majordom_va/General/ThreadingFunction.py diff --git a/General/__init__.py b/majordom_va/General/__init__.py similarity index 100% rename from General/__init__.py rename to majordom_va/General/__init__.py diff --git a/VoiceAssistant/IO/SpeechRecognition/SpeechRecognition.py b/majordom_va/IO/SpeechRecognition/SpeechRecognition.py similarity index 100% rename from VoiceAssistant/IO/SpeechRecognition/SpeechRecognition.py rename to majordom_va/IO/SpeechRecognition/SpeechRecognition.py diff --git a/VoiceAssistant/IO/SpeechRecognition/__init__.py b/majordom_va/IO/SpeechRecognition/__init__.py similarity index 100% rename from VoiceAssistant/IO/SpeechRecognition/__init__.py rename to majordom_va/IO/SpeechRecognition/__init__.py diff --git a/VoiceAssistant/IO/Text2Speech/TTS.py b/majordom_va/IO/Text2Speech/TTS.py similarity index 100% rename from VoiceAssistant/IO/Text2Speech/TTS.py rename to majordom_va/IO/Text2Speech/TTS.py diff --git a/VoiceAssistant/IO/Text2Speech/__init__.py b/majordom_va/IO/Text2Speech/__init__.py similarity index 100% rename from VoiceAssistant/IO/Text2Speech/__init__.py rename to majordom_va/IO/Text2Speech/__init__.py diff --git a/VoiceAssistant/ArchieCore/Commands/Command.py b/majordom_va/VICore/Commands/Command.py similarity index 92% rename from VoiceAssistant/ArchieCore/Commands/Command.py rename to majordom_va/VICore/Commands/Command.py index c0c4906..5bb14ea 100644 --- a/VoiceAssistant/ArchieCore/Commands/Command.py +++ b/majordom_va/VICore/Commands/Command.py @@ -1,7 +1,7 @@ from typing import Callable from abc import ABC -from ..ACObjects import ACObject +from ..VIObjects import VIObject from ..Pattern import Pattern class Command(ABC): @@ -17,7 +17,7 @@ class Command(ABC): from .CommandsManager import CommandsManager CommandsManager().append(self) - def start(self, params: dict[str, ACObject]): + def start(self, params: dict[str, VIObject]): raise Exception(f'Method start is not implemented for command with name {name}') def setStart(self, function): # define start (required) diff --git a/VoiceAssistant/ArchieCore/Commands/CommandsContext.py b/majordom_va/VICore/Commands/CommandsContext.py similarity index 97% rename from VoiceAssistant/ArchieCore/Commands/CommandsContext.py rename to majordom_va/VICore/Commands/CommandsContext.py index a12d21b..187c9d3 100644 --- a/VoiceAssistant/ArchieCore/Commands/CommandsContext.py +++ b/majordom_va/VICore/Commands/CommandsContext.py @@ -2,7 +2,7 @@ from abc import ABC, abstractmethod import asyncio from typing import Any -from ArchieCore import ACTime, ACString +from VICore import VITime, VIString from .CommandsManager import CommandsManager from .Command import Command from .Response import Response, ResponseAction @@ -25,7 +25,7 @@ class CommandsContextManager: delegate: CommandsContextManagerDelegate commandsManager = CommandsManager() - lastInteractTime: ACTime = ACTime() + lastInteractTime: VITime = VITime() contextQueue: list[CommandContext] = [] threads: list[ThreadData] = [] @@ -93,7 +93,7 @@ class CommandsContextManager: if not threadData.finishEvent.is_set(): continue response = threadData.thread.join() - self.parse(response, delaysReports = delaysReports and ACTime() - self.lastInteractTime > 30) + self.parse(response, delaysReports = delaysReports and VITime() - self.lastInteractTime > 30) threadData.finishEvent.clear() del threadData diff --git a/VoiceAssistant/ArchieCore/Commands/CommandsManager.py b/majordom_va/VICore/Commands/CommandsManager.py similarity index 83% rename from VoiceAssistant/ArchieCore/Commands/CommandsManager.py rename to majordom_va/VICore/Commands/CommandsManager.py index 34167b8..ab0cb59 100644 --- a/VoiceAssistant/ArchieCore/Commands/CommandsManager.py +++ b/majordom_va/VICore/Commands/CommandsManager.py @@ -1,6 +1,6 @@ from typing import Type, Optional from .Command import Command -from ..ACObjects import * +from ..VIObjects import * from .RThread import RThread, Event import config @@ -8,9 +8,9 @@ from ..Pattern import Pattern class SearchResult: command: Command - parameters: dict[str, ACObject] + parameters: dict[str, VIObject] - def __init__(self, command: Command, parameters: dict[str, ACObject] = {}): + def __init__(self, command: Command, parameters: dict[str, VIObject] = {}): self.command = command self.parameters = parameters @@ -25,7 +25,7 @@ class CommandsManager: def search(self, string: str, commands: list[Command]) -> list[SearchResult]: results: list[SearchResult] = [] - acstring = ACString(string.lower()) + acstring = VIString(string.lower()) # find command obj by pattern for command in commands: @@ -34,13 +34,13 @@ class CommandsManager: if groupdict != None: - parameters: dict[str: ACObject] = {'string': acstring} + parameters: dict[str: VIObject] = {'string': acstring} for key, value in groupdict.items(): name, typeName = key.split(':') - ACType: Type[ACObject] = CommandsManager.classFromString(typeName) + VIType: Type[VIObject] = CommandsManager.classFromString(typeName) - try: parameters[name] = ACType.parse(string = value) + try: parameters[name] = VIType.parse(string = value) except: break else: results.append(SearchResult(command, parameters)) @@ -64,12 +64,12 @@ class CommandsManager: match = Pattern( f'({"|".join(config.names)})' ).match( - ACString(string.lower()) + VIString(string.lower()) ) return match != None @staticmethod - def classFromString(className: str) -> ACObject: + def classFromString(className: str) -> VIObject: return getattr(sys.modules[__name__], className) @staticmethod diff --git a/VoiceAssistant/ArchieCore/Commands/RThread.py b/majordom_va/VICore/Commands/RThread.py similarity index 100% rename from VoiceAssistant/ArchieCore/Commands/RThread.py rename to majordom_va/VICore/Commands/RThread.py diff --git a/VoiceAssistant/ArchieCore/Commands/Response.py b/majordom_va/VICore/Commands/Response.py similarity index 100% rename from VoiceAssistant/ArchieCore/Commands/Response.py rename to majordom_va/VICore/Commands/Response.py diff --git a/VoiceAssistant/ArchieCore/Commands/ThreadData.py b/majordom_va/VICore/Commands/ThreadData.py similarity index 100% rename from VoiceAssistant/ArchieCore/Commands/ThreadData.py rename to majordom_va/VICore/Commands/ThreadData.py diff --git a/VoiceAssistant/ArchieCore/Commands/__init__.py b/majordom_va/VICore/Commands/__init__.py similarity index 100% rename from VoiceAssistant/ArchieCore/Commands/__init__.py rename to majordom_va/VICore/Commands/__init__.py diff --git a/VoiceAssistant/ArchieCore/Pattern/Pattern.py b/majordom_va/VICore/Pattern/Pattern.py similarity index 87% rename from VoiceAssistant/ArchieCore/Pattern/Pattern.py rename to majordom_va/VICore/Pattern/Pattern.py index b8f1aca..47fb8ca 100644 --- a/VoiceAssistant/ArchieCore/Pattern/Pattern.py +++ b/majordom_va/VICore/Pattern/Pattern.py @@ -2,7 +2,7 @@ from typing import Type, Optional import re from .expressions import expressions -from ..ACObjects import * +from ..VIObjects import * class Pattern: origin: str @@ -26,13 +26,13 @@ class Pattern: match = reMatch.pop(0) arg: str = match[1:] argName, argTypeName = arg.split(':') - argType: Type[ACObject] = classFromString(argTypeName) + argType: Type[VIObject] = classFromString(argTypeName) pattern = re.sub('\\'+link[0], f'(?P<{arg}>{argType.pattern.compiled})', pattern) return re.compile(pattern) - def match(self, string: ACString) -> Optional[dict[str, str]]: + def match(self, string: VIString) -> Optional[dict[str, str]]: if match := re.search(self.compiled, string.value): return match.groupdict() return None diff --git a/VoiceAssistant/ArchieCore/Pattern/__init__.py b/majordom_va/VICore/Pattern/__init__.py similarity index 52% rename from VoiceAssistant/ArchieCore/Pattern/__init__.py rename to majordom_va/VICore/Pattern/__init__.py index fb31ca7..05e742d 100644 --- a/VoiceAssistant/ArchieCore/Pattern/__init__.py +++ b/majordom_va/VICore/Pattern/__init__.py @@ -1,2 +1,2 @@ from .Pattern import Pattern -from ..ACObjects import * +from ..VIObjects import * diff --git a/VoiceAssistant/ArchieCore/Pattern/expressions.py b/majordom_va/VICore/Pattern/expressions.py similarity index 100% rename from VoiceAssistant/ArchieCore/Pattern/expressions.py rename to majordom_va/VICore/Pattern/expressions.py diff --git a/VoiceAssistant/ArchieCore/Pattern/synonyms.py b/majordom_va/VICore/Pattern/synonyms.py similarity index 100% rename from VoiceAssistant/ArchieCore/Pattern/synonyms.py rename to majordom_va/VICore/Pattern/synonyms.py diff --git a/VoiceAssistant/ArchieCore/ACObjects/ACNumber.py b/majordom_va/VICore/VIObjects/VINumber.py similarity index 82% rename from VoiceAssistant/ArchieCore/ACObjects/ACNumber.py rename to majordom_va/VICore/VIObjects/VINumber.py index 10ab1df..bb19144 100644 --- a/VoiceAssistant/ArchieCore/ACObjects/ACNumber.py +++ b/majordom_va/VICore/VIObjects/VINumber.py @@ -1,6 +1,6 @@ -from .ACObject import ACObject, Pattern, classproperty +from .VIObject import VIObject, Pattern, classproperty -class ACNumber(ACObject): +class VINumber(VIObject): value: float isPercentage: bool = False @@ -16,7 +16,7 @@ class ACNumber(ACObject): value /= 100 isPercentage = True - acNumber = ACNumber(value = value) + acNumber = VINumber(value = value) acNumber.isPercentage = isPercentage acNumber.stringValue = stringValue diff --git a/VoiceAssistant/ArchieCore/ACObjects/ACObject.py b/majordom_va/VICore/VIObjects/VIObject.py similarity index 66% rename from VoiceAssistant/ArchieCore/ACObjects/ACObject.py rename to majordom_va/VICore/VIObjects/VIObject.py index 43ae9fe..b1b6fb2 100644 --- a/VoiceAssistant/ArchieCore/ACObjects/ACObject.py +++ b/majordom_va/VICore/VIObjects/VIObject.py @@ -10,7 +10,7 @@ class classproperty(property): def __get__(self, cls, owner): return classmethod(self.fget).__get__(None, owner)() -class ACObject(ABC): +class VIObject(ABC): pattern: Pattern # static getonly stringValue: str value: Any @@ -20,7 +20,7 @@ class ACObject(ABC): self.value = value @classmethod - def parse(cls, fromString: str) -> ACObject: + def parse(cls, fromString: str) -> VIObject: object = cls() object.stringValue = fromString return cls() @@ -33,59 +33,59 @@ class ACObject(ABC): def pattern() -> Pattern: return Pattern('*') - def __lt__(self, other: ACObject) -> bool: + def __lt__(self, other: VIObject) -> bool: return self.value < other.value - def __le__(self, other: ACObject) -> bool: + def __le__(self, other: VIObject) -> bool: return self.value <= other.value - def __gt__(self, other: ACObject) -> bool: + def __gt__(self, other: VIObject) -> bool: return self.value > other.value - def __ge__(self, other: ACObject) -> bool: + def __ge__(self, other: VIObject) -> bool: return self.value >= other.value - def __eq__(self, other: ACObject) -> bool: + def __eq__(self, other: VIObject) -> bool: return self.value == other.value - def __ne__(self, other: ACObject) -> bool: + def __ne__(self, other: VIObject) -> bool: return self.value != other.value - def __neg__(self) -> ACObject: + def __neg__(self) -> VIObject: return type(self).__init__(value = -self.value) - def __abs__(self) -> ACObject: + def __abs__(self) -> VIObject: return type(self).__init__(value = abs(self.value)) - def __round__(self, n) -> ACObject: + def __round__(self, n) -> VIObject: return type(self).__init__(value = round(self.value)) - def __floor__(self) -> ACObject: + def __floor__(self) -> VIObject: return type(self).__init__(value = floor(self.value)) - def __ceil__(self) -> ACObject: + def __ceil__(self) -> VIObject: return type(self).__init__(value = ceil(self.value)) - def __trunc__(self) -> ACObject: + def __trunc__(self) -> VIObject: return type(self).__init__(value = trunc(self.value)) - def __add__(self, other: ACObject) -> ACObjet: + def __add__(self, other: VIObject) -> VIObject: return type(self).__init__(value = self.value + other.value) - def __sub__(self, other: ACObject) -> ACObjet: + def __sub__(self, other: VIObject) -> VIObject: return type(self).__init__(value = self.value - other.value) - def __mul__(self, other: ACObject) -> ACObjet: + def __mul__(self, other: VIObject) -> VIObject: return type(self).__init__(value = self.value * other.value) - def __floordiv__(self, other: ACObject) -> ACObjet: + def __floordiv__(self, other: VIObject) -> VIObject: return type(self).__init__(value = self.value // other.value) - def __truediv__(self, other: ACObject) -> ACObjet: + def __truediv__(self, other: VIObject) -> VIObject: return type(self).__init__(value = self.value / other.value) - def __mod__(self, other: ACObject) -> ACObjet: + def __mod__(self, other: VIObject) -> VIObject: return type(self).__init__(value = self.value % other.value) - def __pow__(self, other: ACObject) -> ACObjet: + def __pow__(self, other: VIObject) -> VIObject: return type(self).__init__(value = self.value ** other.value) diff --git a/VoiceAssistant/ArchieCore/ACObjects/ACString.py b/majordom_va/VICore/VIObjects/VIString.py similarity index 79% rename from VoiceAssistant/ArchieCore/ACObjects/ACString.py rename to majordom_va/VICore/VIObjects/VIString.py index dfa3881..4e7c384 100644 --- a/VoiceAssistant/ArchieCore/ACObjects/ACString.py +++ b/majordom_va/VICore/VIObjects/VIString.py @@ -1,6 +1,6 @@ -from .ACObject import ACObject, Pattern, classproperty +from .VIObject import VIObject, Pattern, classproperty -class ACString(ACObject): +class VIString(VIObject): value: str def __init__(self, value: str): diff --git a/majordom_va/VICore/VIObjects/VITime.py b/majordom_va/VICore/VIObjects/VITime.py new file mode 100644 index 0000000..0fa2e0c --- /dev/null +++ b/majordom_va/VICore/VIObjects/VITime.py @@ -0,0 +1,31 @@ +from __future__ import annotations +from typing import Optional +from datetime import datetime, timedelta + +from .VIObject import VIObject, Pattern, classproperty +from .VITimeInterval import VITimeInterval + +class VITime(VIObject): + value: datetime + + def __init__(self, value: Optional[datetime] = None): + self.value = value or datetime.now() + + def addingInterval(self, timeinterval: Union[VIObject, Number]) -> VITime: + seconds = timeinterval.value if isinstance(timeinterval, VITimeInterval) else timeinterval + return VITime(self.value + timedelta(seconds = seconds)) + + def addInterval(self, timeinterval: Union[VIObject, Number]): + seconds = timeinterval.value if isinstance(timeinterval, VITimeInterval) else timeinterval + self.value += timedelta(seconds = seconds) + + @classmethod + def parse(cls, fromString: str) -> VIObject: + raise NotImplementedError + + @classproperty + def pattern() -> Pattern: + raise NotImplementedError + + def __sub__(self, other: VITime) -> VITimeInterval: + return VITimeInterval((self.value - other.value).total_seconds()) diff --git a/majordom_va/VICore/VIObjects/VITimeInterval.py b/majordom_va/VICore/VIObjects/VITimeInterval.py new file mode 100644 index 0000000..786a60a --- /dev/null +++ b/majordom_va/VICore/VIObjects/VITimeInterval.py @@ -0,0 +1,75 @@ +from __future__ import annotations +from typing import Union +from numbers import Number + +from .VIObject import VIObject, Pattern, classproperty + +class VITimeInterval(VIObject): + value: float # seconds + + @classmethod + def initWith(seconds: float = 0, + minutes: float = 0, + hours: float = 0, + days: float = 0, + weeks: float = 0) -> VITimeInterval: + + days += weeks * 7 + hours += days * 24 + minutes += hours * 60 + seconds += minutes * 60 + return VITimeInterval(value = seconds) + + @classmethod + def parse(cls, fromString: str) -> VIObject: + raise NotImplementedError + + @classproperty + def pattern() -> Pattern: + raise NotImplementedError + + def __lt__(self, other: VIObject) -> bool: + value = other.value if isinstance(other, VITimeInterval) else other + return self.value < value + + def __le__(self, other: VIObject) -> bool: + value = other.value if isinstance(other, VITimeInterval) else other + return self.value <= value + + def __gt__(self, other: VIObject) -> bool: + value = other.value if isinstance(other, VITimeInterval) else other + return self.value > value + + def __ge__(self, other: VIObject) -> bool: + value = other.value if isinstance(other, VITimeInterval) else other + return self.value >= value + + def __eq__(self, other: VIObject) -> bool: + value = other.value if isinstance(other, VITimeInterval) else other + return self.value == value + + def __ne__(self, other: VIObject) -> bool: + value = other.value if isinstance(other, VITimeInterval) else other + return self.value != value + + def __add__(self, other: Union[VIObject, Number]) -> VITimeInterval: + value = other.value if isinstance(other, VITimeInterval) else other + return VITimeInterval(self.value + value) + + def __sub__(self, other: Union[VIObject, Number]) -> VITimeInterval: + value = other.value if isinstance(other, VITimeInterval) else other + return VITimeInterval(self.value - value) + + def __mul__(self, other: Union[VIObject, Number]) -> VITimeInterval: + value = other.value if isinstance(other, VITimeInterval) else other + return VITimeInterval(self.value * value) + + def __mod__(self, other: Union[VIObject, Number]) -> VITimeInterval: + value = other.value if isinstance(other, VITimeInterval) else other + return VITimeInterval(self.value % value) + + def __floordiv__(self, other: Union[VIObject, Number]) -> Union[VIObject, Number]: + return self.value // other.value if isinstance(other, VITimeInterval) else VITimeInterval(self.value // other) + + def __truediv__(self, other: Union[VIObject, Number]) -> Union[VIObject, Number]: + return self.value / other.value if isinstance(other, VITimeInterval) else VITimeInterval(self.value / other) diff --git a/majordom_va/VICore/VIObjects/VIWord.py b/majordom_va/VICore/VIObjects/VIWord.py new file mode 100644 index 0000000..606dc52 --- /dev/null +++ b/majordom_va/VICore/VIObjects/VIWord.py @@ -0,0 +1,8 @@ +from .VIObject import Pattern, classproperty +from . import VIString + +class VIWord(VIString): + + @classproperty + def pattern() -> Pattern: + return Pattern('[:word:]') diff --git a/majordom_va/VICore/VIObjects/__init__.py b/majordom_va/VICore/VIObjects/__init__.py new file mode 100644 index 0000000..65b2856 --- /dev/null +++ b/majordom_va/VICore/VIObjects/__init__.py @@ -0,0 +1,6 @@ +from .VIObject import VIObject +from .VINumber import VINumber +from .VIString import VIString +from .VIWord import VIWord +from .VITime import VITime +from .VITimeInterval import VITimeInterval diff --git a/VoiceAssistant/ArchieCore/__init__.py b/majordom_va/VICore/__init__.py similarity index 65% rename from VoiceAssistant/ArchieCore/__init__.py rename to majordom_va/VICore/__init__.py index 1c4e294..1b11aee 100644 --- a/VoiceAssistant/ArchieCore/__init__.py +++ b/majordom_va/VICore/__init__.py @@ -1,3 +1,3 @@ from .Pattern import * from .Commands import * -from .ACObjects import * +from .VIObjects import * diff --git a/VoiceAssistant/VoiceAssistant.py b/majordom_va/VoiceAssistant.py similarity index 92% rename from VoiceAssistant/VoiceAssistant.py rename to majordom_va/VoiceAssistant.py index 31e9e42..b82402a 100644 --- a/VoiceAssistant/VoiceAssistant.py +++ b/majordom_va/VoiceAssistant.py @@ -1,8 +1,7 @@ -from typing import Optional import asyncio import config -from ArchieCore import ACTime, CommandsContextManager, CommandsContextManagerDelegate +from VICore import VITime, CommandsContextManager, CommandsContextManagerDelegate from IO.SpeechRecognition import SpeechRecognizer, SpeechRecognizerDelegate from IO import Text2Speech @@ -37,7 +36,7 @@ class VoiceAssistant(SpeechRecognizerDelegate, CommandsContextManagerDelegate): def speechRecognizerReceiveFinalResult(self, result: str): self.voids = 0 - self.commandsContext.lastInteractTime = ACTime() + self.commandsContext.lastInteractTime = VITime() print(f'\rYou: {result}') self.commandsContext.processString(result) diff --git a/SmartHome/tests/__init__.py b/majordom_va/__init__.py similarity index 100% rename from SmartHome/tests/__init__.py rename to majordom_va/__init__.py diff --git a/VoiceAssistant/main.py b/majordom_va/__main__.py similarity index 51% rename from VoiceAssistant/main.py rename to majordom_va/__main__.py index 37880ea..d6bb3f8 100644 --- a/VoiceAssistant/main.py +++ b/majordom_va/__main__.py @@ -1,10 +1,6 @@ -import os, sys - -# root = os.path.dirname(os.path.dirname(__file__)) -# sys.path.append(root) - from VoiceAssistant import VoiceAssistant +# TODO: typer if __name__ == '__main__': VoiceAssistant().start() diff --git a/majordom_va/config.py b/majordom_va/config.py new file mode 100644 index 0000000..40937ee --- /dev/null +++ b/majordom_va/config.py @@ -0,0 +1,12 @@ +# src: str = path + '/resources' + + +# Speech Recognition + +vosk_model: str = 'model-small-rus' + +# TTS + +# voice_volume: float = 1 +language_code: str = 'ru-RU' +goole_tts_json_key: str = 'tts-gc-key.json' \ No newline at end of file diff --git a/resources/model-small-rus/README b/model-small-rus/README similarity index 100% rename from resources/model-small-rus/README rename to model-small-rus/README diff --git a/resources/model-small-rus/am/final.mdl b/model-small-rus/am/final.mdl similarity index 100% rename from resources/model-small-rus/am/final.mdl rename to model-small-rus/am/final.mdl diff --git a/resources/model-small-rus/conf/mfcc.conf b/model-small-rus/conf/mfcc.conf similarity index 100% rename from resources/model-small-rus/conf/mfcc.conf rename to model-small-rus/conf/mfcc.conf diff --git a/resources/model-small-rus/conf/model.conf b/model-small-rus/conf/model.conf similarity index 100% rename from resources/model-small-rus/conf/model.conf rename to model-small-rus/conf/model.conf diff --git a/resources/model-small-rus/graph/Gr.fst b/model-small-rus/graph/Gr.fst similarity index 100% rename from resources/model-small-rus/graph/Gr.fst rename to model-small-rus/graph/Gr.fst diff --git a/resources/model-small-rus/graph/HCLr.fst b/model-small-rus/graph/HCLr.fst similarity index 100% rename from resources/model-small-rus/graph/HCLr.fst rename to model-small-rus/graph/HCLr.fst diff --git a/resources/model-small-rus/graph/disambig_tid.int b/model-small-rus/graph/disambig_tid.int similarity index 100% rename from resources/model-small-rus/graph/disambig_tid.int rename to model-small-rus/graph/disambig_tid.int diff --git a/resources/model-small-rus/graph/phones/word_boundary.int b/model-small-rus/graph/phones/word_boundary.int similarity index 100% rename from resources/model-small-rus/graph/phones/word_boundary.int rename to model-small-rus/graph/phones/word_boundary.int diff --git a/resources/model-small-rus/ivector/final.dubm b/model-small-rus/ivector/final.dubm similarity index 100% rename from resources/model-small-rus/ivector/final.dubm rename to model-small-rus/ivector/final.dubm diff --git a/resources/model-small-rus/ivector/final.ie b/model-small-rus/ivector/final.ie similarity index 100% rename from resources/model-small-rus/ivector/final.ie rename to model-small-rus/ivector/final.ie diff --git a/resources/model-small-rus/ivector/final.mat b/model-small-rus/ivector/final.mat similarity index 100% rename from resources/model-small-rus/ivector/final.mat rename to model-small-rus/ivector/final.mat diff --git a/resources/model-small-rus/ivector/global_cmvn.stats b/model-small-rus/ivector/global_cmvn.stats similarity index 100% rename from resources/model-small-rus/ivector/global_cmvn.stats rename to model-small-rus/ivector/global_cmvn.stats diff --git a/resources/model-small-rus/ivector/online_cmvn.conf b/model-small-rus/ivector/online_cmvn.conf similarity index 100% rename from resources/model-small-rus/ivector/online_cmvn.conf rename to model-small-rus/ivector/online_cmvn.conf diff --git a/resources/model-small-rus/ivector/splice.conf b/model-small-rus/ivector/splice.conf similarity index 100% rename from resources/model-small-rus/ivector/splice.conf rename to model-small-rus/ivector/splice.conf diff --git a/requirments.txt b/requirments.txt index 51cb5f2..9a42e57 100644 --- a/requirments.txt +++ b/requirments.txt @@ -1,4 +1,4 @@ -# Python 3.10 +# Python 3.10+ # download model from https://alphacephei.com/vosk/models # sudo apt-get install libssl-dev @@ -16,47 +16,13 @@ spidev sounddevice soundfile numpy + +# STT https://github.com/alphacep/vosk-api/releases/download/v0.3.42/vosk-0.3.42-py3-none-linux_aarch64.whl # vosk -# tts -# google-cloud-texttospeech - -# telegram -PyTelegramBotApi # aiogram +# TTS +google-cloud-texttospeech # QA bs4 -wikipedia - -# Zieit -#xlrd -#xlwt -#xlutils - - -# Media -#pafy -#screeninfo -#psutil -#yt_dlp #pip install youtube-dl - - -# API -uvicorn -fastapi -sqlalchemy -sqlalchemy_utils -pydantic -passlib -python-dotenv -python-jose -bcrypt -git+https://github.com/MarkParker5/sqladmin.git # sqladmin - -# Client -websocket - - -# QRcode -qrcode -pillow +wikipedia \ No newline at end of file diff --git a/resources/crontab b/resources/crontab deleted file mode 100644 index c615003..0000000 --- a/resources/crontab +++ /dev/null @@ -1,2 +0,0 @@ -# sudo -@reboot python3.10 ~/ArchieHub/SmartHome/hardware/WiFi.py diff --git a/resources/reset.py b/resources/reset.py deleted file mode 100644 index 008313b..0000000 --- a/resources/reset.py +++ /dev/null @@ -1,25 +0,0 @@ -import os -import config -import pathlib -from SmartHome.hardware import WiFi - -path = str(pathlib.Path(__file__).parent.absolute()) -src: str = path + '/resources' - -files = [] -files.append(f'{src}/jwt.key.pub') -files.append(f'{src}/jwt_access_token') -files.append(f'{src}/jwt_refresh_token') -files.append(f'{src}/database.sqlite3') - -for file in files: - try: - os.remove(config.access_token) - except: - pass - -WiFi.networks = [] -WiFi.write_networks() -WiFi.reconnect() -WiFi.start_hotspot() -WiFi.start_wps() diff --git a/resources/splash.png b/resources/splash.png deleted file mode 100644 index 712021b..0000000 Binary files a/resources/splash.png and /dev/null differ