1
0
mirror of https://github.com/MarkParker5/STARK.git synced 2025-02-17 11:55:35 +02:00

structure refactor

This commit is contained in:
MarkParker5 2022-12-09 00:52:40 +01:00
parent 3576d793e4
commit c089d1dd29
No known key found for this signature in database
GPG Key ID: C87FA4BD47B5A169
154 changed files with 210 additions and 4718 deletions

5
.gitignore vendored
View File

@ -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/

View File

@ -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

View File

@ -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))

View File

@ -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()

View File

@ -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)

View File

@ -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()

View File

@ -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'

View File

@ -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
)

View File

@ -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

View File

@ -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()

View File

@ -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]

View File

@ -1,2 +0,0 @@
from .Merlin import merlin
from .MerlinMessage import MerlinMessage

View File

@ -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 <jpbarraca@gmail.com>)
# ... 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

View File

@ -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<ssid>.*)"\s+psk="(?P<psk>.*)"\s+id_str="(?P<id_str>.*)"\s+\}'
)
cell_regex = re.compile(
r'Cell(?:.|\n)*?Frequency:(?P<frequency>.+)(?:.|\n)*?Quality=(?P<quality>.+)(?:.|\n)*?Encryption key:(?P<encrypted>.+)(?:.|\n)*?ESSID:"(?P<ssid>.+)"'
)
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()

View File

@ -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)

View File

@ -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()

View File

@ -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()

View File

@ -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

View File

@ -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())
)

View File

@ -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)

View File

@ -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()

View File

@ -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))

View File

@ -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

View File

@ -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)

View File

@ -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__()

View File

@ -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__()

View File

@ -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__()

View File

@ -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__()

View File

@ -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__()

View File

@ -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__()

View File

@ -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)

View File

@ -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

View File

@ -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] = []

View File

@ -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]

View File

@ -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

View File

@ -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

View File

@ -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]

View File

@ -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]

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -1,9 +0,0 @@
from .BaseAuth import (
TokenType,
TokensPair
)
from .UserAuth import (
UserToken,
UserAuthManager
)
from .exceptions import AuthException

View File

@ -1,2 +0,0 @@
class AuthException(Exception):
pass

View File

@ -1,2 +0,0 @@
from . import database
from . import auth

View File

@ -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

View File

@ -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

View File

@ -1,6 +0,0 @@
from . import admin
from . import device
from . import house
from . import hub
from . import room
from . import ws

View File

@ -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)

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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'
# )
# }

View File

@ -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;
}

View File

@ -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,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill=\"%23fff\" fill-rule=\"evenodd\" d=\"M2 13h4v1H2v-1zm5-6H2v1h5V7zm2 3V8l-3 3 3 3v-2h5v-2H9zM4.5 9H2v1h2.5V9zM2 12h2.5v-1H2v1zm9 1h1v2c-.02.28-.11.52-.3.7-.19.18-.42.28-.7.3H1c-.55 0-1-.45-1-1V4c0-.55.45-1 1-1h3c0-1.11.89-2 2-2 1.11 0 2 .89 2 2h3c.55 0 1 .45 1 1v5h-1V6H1v9h10v-2zM2 5h8c0-.55-.45-1-1-1H8c-.55 0-1-.45-1-1s-.45-1-1-1-1 .45-1 1-.45 1-1 1H3c-.55 0-1 .45-1 1z\"/></svg>") 50% center no-repeat; }
.swagger-ui select {
background: url("data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 20 20\"><path d=\"M13.418 7.859a.695.695 0 01.978 0 .68.68 0 010 .969l-3.908 3.83a.697.697 0 01-.979 0l-3.908-3.83a.68.68 0 010-.969.695.695 0 01.978 0L10 11l3.418-3.141z\"/></svg>") 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,<svg width=\"10\" height=\"8\" viewBox=\"3 7 10 8\" xmlns=\"http://www.w3.org/2000/svg\"><path fill=\"%2341474E\" fill-rule=\"evenodd\" d=\"M6.333 15L3 11.667l1.333-1.334 2 2L11.667 7 13 8.333z\"/></svg>") 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,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\"><path d=\"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z\"/></svg>") 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;
}

View File

@ -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);
}

View File

@ -1,62 +0,0 @@
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="UTF-8">
<title>Admin Panel</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- Tell the browser to be responsive to screen width -->
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
<!-- Bootstrap 3.3.6 -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.5.0/css/font-awesome.min.css">
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
<link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-beta/css/bootstrap.min.css'>
<link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.5.0/css/font-awesome.min.css'>
<link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Roboto'>
<link rel="stylesheet" href="/static/css/login.css">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-lg-3 col-md-2"></div>
<div class="col-lg-6 col-md-8 login-box">
<div class="col-lg-12 login-key">
<i class="fa fa-key" aria-hidden="true"></i>
</div>
<div class="col-lg-12 login-title">
ADMIN PANEL
</div>
<div class="col-lg-12 login-form">
<div class="col-lg-12 login-form">
<form>
<div class="form-group">
<label class="form-control-label">USERNAME</label>
<input type="text" class="form-control" name="username">
</div>
<div class="form-group">
<label class="form-control-label">PASSWORD</label>
<input type="password" class="form-control" name="password">
</div>
<div class="col-lg-12 loginbttm d-flex justify-content-between">
<div class="login-btm login-text" id="error-message">
{{ error }}
</div>
<div class="login-btm login-button">
<button type="submit" class="btn btn-outline-primary">LOGIN</button>
</div>
</div>
</form>
</div>
</div>
<div class="col-lg-3 col-md-2"></div>
</div>
</div>
<script type="text/javascript" src="/static/js/login.js"></script>
</body>
</html>

View File

@ -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

View File

@ -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}'
}

View File

@ -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()

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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})

View File

@ -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()

View File

@ -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())

View File

@ -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)

View File

@ -1,8 +0,0 @@
from .ACObject import Pattern, classproperty
from . import ACString
class ACWord(ACString):
@classproperty
def pattern() -> Pattern:
return Pattern('[:word:]')

View File

@ -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

View File

@ -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

View File

@ -1 +0,0 @@
from .NumeralsParser import *

View File

@ -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'

View File

@ -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')

View File

@ -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

View File

@ -1,6 +1,6 @@
from .Raspi import Raspi
import os
from ArchieCore import Command, CommandsManager, Response
from VICore import Command, CommandsManager, Response
import config
################################################################################

View File

@ -1,5 +1,5 @@
from .Raspi import *
from ArchieCore import Command, Response
from VICore import Command, Response
################################################################################

View File

@ -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',

View File

@ -1,4 +1,4 @@
from ArchieCore import Command, Response
from VICore import Command, Response
@Command.new(['привет*',])
def hello(params):

View File

@ -1,4 +1,4 @@
from ArchieCore import Command, Response
from VICore import Command, Response
from .Zieit import Zieit
def formatLesson(lesson):

View File

@ -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

View File

@ -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

Some files were not shown because too many files have changed in this diff Show More