You've already forked STARK
mirror of
https://github.com/MarkParker5/STARK.git
synced 2025-07-12 22:50:22 +02:00
client and bridge in progress
This commit is contained in:
8
.gitignore
vendored
8
.gitignore
vendored
@ -11,10 +11,10 @@ __pycache__
|
|||||||
*.torrent
|
*.torrent
|
||||||
|
|
||||||
/config.py
|
/config.py
|
||||||
resources/tts-gc-key.json
|
tts-gc-key.json
|
||||||
resources/jwt_access_token
|
jwt_access_token
|
||||||
resources/jwt_refresh_token
|
jwt_refresh_token
|
||||||
resources/jwt-key.pub
|
jwt-key.pub
|
||||||
|
|
||||||
audio/
|
audio/
|
||||||
downloads/
|
downloads/
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
- Django
|
- Django
|
||||||
- #### Features - Possibilities, set of functions
|
- #### Features - Possibilities, set of functions
|
||||||
- #### General - For helper classes
|
- #### General - For helper classes
|
||||||
- #### Raspberry - Control system and hardware
|
- #### hardware - Control system and hardware
|
||||||
|
|
||||||
### Root files:
|
### Root files:
|
||||||
- **start.py** - entry point
|
- **start.py** - entry point
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
from . import (
|
|
||||||
ws,
|
|
||||||
house,
|
|
||||||
hub,
|
|
||||||
room,
|
|
||||||
device,
|
|
||||||
admin
|
|
||||||
)
|
|
@ -1,2 +0,0 @@
|
|||||||
from .DevicesManager import DevicesManager
|
|
||||||
from .router import router
|
|
@ -1,30 +0,0 @@
|
|||||||
from uuid import UUID
|
|
||||||
from fastapi import APIRouter, Depends
|
|
||||||
from API import exceptions
|
|
||||||
from .DevicesManager import DevicesManager
|
|
||||||
from .schemas import Device, DeviceState, CreateDevice, PatchDevice
|
|
||||||
|
|
||||||
|
|
||||||
router = APIRouter(
|
|
||||||
prefix = '/device',
|
|
||||||
tags = ['device'],
|
|
||||||
)
|
|
||||||
|
|
||||||
@router.post('', response_model = Device)
|
|
||||||
async def create_device(device: CreateDevice, manager: DevicesManager = Depends()):
|
|
||||||
return await manager.create(device)
|
|
||||||
|
|
||||||
@router.get('/{id}', response_model = DeviceState)
|
|
||||||
async def get_device(id: UUID, manager: DevicesManager = Depends()):
|
|
||||||
if device := await manager.state(id):
|
|
||||||
return device
|
|
||||||
else:
|
|
||||||
raise exceptions.not_found
|
|
||||||
|
|
||||||
@router.patch('/{id}')
|
|
||||||
async def patch_device(id: UUID, device: PatchDevice, manager: DevicesManager = Depends()):
|
|
||||||
await manager.patch(id, device)
|
|
||||||
|
|
||||||
@router.delete('/{id}')
|
|
||||||
async def delete_device(id: UUID, manager: DevicesManager = Depends()):
|
|
||||||
await manager.delete(id)
|
|
@ -1,22 +0,0 @@
|
|||||||
from typing import Optional
|
|
||||||
from uuid import UUID
|
|
||||||
from pydantic import BaseModel
|
|
||||||
from AUID import AUID
|
|
||||||
from ..schemas import DeviceModel, DeviceParameter
|
|
||||||
|
|
||||||
|
|
||||||
class PatchDevice(BaseModel):
|
|
||||||
name: str
|
|
||||||
room_id: UUID
|
|
||||||
|
|
||||||
class CreateDevice(PatchDevice):
|
|
||||||
id: AUID
|
|
||||||
|
|
||||||
class Device(CreateDevice):
|
|
||||||
model: DeviceModel
|
|
||||||
|
|
||||||
class Config:
|
|
||||||
orm_mode = True
|
|
||||||
|
|
||||||
class DeviceState(Device):
|
|
||||||
parameters: list[DeviceParameter] = []
|
|
@ -1,32 +0,0 @@
|
|||||||
from uuid import UUID
|
|
||||||
|
|
||||||
from fastapi import Depends
|
|
||||||
from sqlalchemy import select, delete
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
|
||||||
|
|
||||||
from API.models import House
|
|
||||||
from API.dependencies import database
|
|
||||||
from . import schemas
|
|
||||||
|
|
||||||
|
|
||||||
class HouseManager:
|
|
||||||
session: AsyncSession
|
|
||||||
|
|
||||||
def __init__(self, session = Depends(database.get_async_session)):
|
|
||||||
self.session = session
|
|
||||||
|
|
||||||
async def get(self) -> House:
|
|
||||||
db: AsyncSession = self.session
|
|
||||||
result = await db.scalars(select(House))
|
|
||||||
return result.first()
|
|
||||||
|
|
||||||
async def create(self, house_id: UUID) -> House: # TODO: remove
|
|
||||||
db: AsyncSession = self.session
|
|
||||||
|
|
||||||
if house := await self.get():
|
|
||||||
await db.delete(house)
|
|
||||||
|
|
||||||
house = House(id = house_id, name = '')
|
|
||||||
db.add(house)
|
|
||||||
await db.commit()
|
|
||||||
return house
|
|
@ -1,2 +0,0 @@
|
|||||||
from .HouseManager import HouseManager
|
|
||||||
from .router import router
|
|
@ -1,18 +0,0 @@
|
|||||||
from uuid import UUID
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends
|
|
||||||
|
|
||||||
from API import exceptions
|
|
||||||
from API.dependencies import auth
|
|
||||||
from .HouseManager import HouseManager
|
|
||||||
from .schemas import House
|
|
||||||
|
|
||||||
|
|
||||||
router = APIRouter(
|
|
||||||
prefix = '/house',
|
|
||||||
tags = ['house'],
|
|
||||||
)
|
|
||||||
|
|
||||||
@router.get('', response_model = House)
|
|
||||||
async def get_house(manager: HouseManager = Depends(), user = Depends(auth.validate_user)):
|
|
||||||
return await manager.get()
|
|
@ -1,2 +0,0 @@
|
|||||||
from .HubManager import HubManager
|
|
||||||
from .router import router
|
|
@ -1,56 +0,0 @@
|
|||||||
from fastapi import APIRouter, Depends, BackgroundTasks
|
|
||||||
from API import exceptions
|
|
||||||
from API.dependencies import auth
|
|
||||||
from .HubManager import HubManager
|
|
||||||
from .schemas import HubInit, Hub, HubPatch, TokensPair, Hotspot, WifiConnection
|
|
||||||
|
|
||||||
|
|
||||||
router = APIRouter(
|
|
||||||
prefix = '/hub',
|
|
||||||
tags = ['hub'],
|
|
||||||
)
|
|
||||||
|
|
||||||
@router.post('', response_model = Hub)
|
|
||||||
async def init_hub(hub_init: HubInit, manager: HubManager = Depends(), raw_token: str = Depends(auth.raw_token)):
|
|
||||||
manager.save_credentials(hub_init)
|
|
||||||
await manager.parse_token(raw_token)
|
|
||||||
return await manager.init(hub_init)
|
|
||||||
|
|
||||||
@router.get('', response_model = Hub)
|
|
||||||
async def get_hub(manager: HubManager = Depends()):
|
|
||||||
await manager.check_access()
|
|
||||||
hub = await manager.get()
|
|
||||||
if hub:
|
|
||||||
return hub
|
|
||||||
raise exceptions.not_found
|
|
||||||
|
|
||||||
@router.patch('')
|
|
||||||
async def patch_hub(hub: HubPatch, manager: HubManager = Depends()):
|
|
||||||
await manager.check_access()
|
|
||||||
await manager.patch(hub)
|
|
||||||
|
|
||||||
@router.post('/connect')
|
|
||||||
async def connect_to_wifi(wifi: WifiConnection, manager: HubManager = Depends()):
|
|
||||||
await manager.check_access()
|
|
||||||
manager.wifi(wifi.ssid, wifi.password)
|
|
||||||
|
|
||||||
@router.post('/wps')
|
|
||||||
async def start_wps(manager: HubManager = Depends()):
|
|
||||||
await manager.check_access()
|
|
||||||
manager.start_wps()
|
|
||||||
|
|
||||||
@router.get('/hotspots', response_model = list[Hotspot])
|
|
||||||
def get_hub_hotspots(manager: HubManager = Depends()):
|
|
||||||
return manager.get_hotspots()
|
|
||||||
|
|
||||||
@router.get('/is_connected', response_model=bool)
|
|
||||||
def is_connected(bg_tasks: BackgroundTasks, manager: HubManager = Depends()):
|
|
||||||
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: HubManager = Depends()):
|
|
||||||
await manager.check_access()
|
|
||||||
manager.save_tokens(tokens)
|
|
@ -1,2 +0,0 @@
|
|||||||
from .RoomsManager import RoomsManager
|
|
||||||
from .router import router
|
|
@ -1,30 +0,0 @@
|
|||||||
from uuid import UUID
|
|
||||||
from fastapi import APIRouter, Depends
|
|
||||||
from API import exceptions
|
|
||||||
from .RoomsManager import RoomsManager
|
|
||||||
from .schemas import Room, CreateRoom, PatchRoom
|
|
||||||
|
|
||||||
|
|
||||||
router = APIRouter(
|
|
||||||
prefix = '/room',
|
|
||||||
tags = ['room'],
|
|
||||||
)
|
|
||||||
|
|
||||||
@router.post('', response_model = Room)
|
|
||||||
async def create_room(room: CreateRoom, manager: RoomsManager = Depends()):
|
|
||||||
return await manager.create(room)
|
|
||||||
|
|
||||||
@router.get('/{id}', response_model = Room)
|
|
||||||
async def get_room(id: UUID, manager: RoomsManager = Depends()):
|
|
||||||
room = await manager.get(id)
|
|
||||||
if not room:
|
|
||||||
raise exceptions.not_found
|
|
||||||
return room
|
|
||||||
|
|
||||||
@router.patch('/{id}')
|
|
||||||
async def patch_room(id: UUID, room: PatchRoom, manager: RoomsManager = Depends()):
|
|
||||||
await manager.patch(id, room)
|
|
||||||
|
|
||||||
@router.delete('/{id}')
|
|
||||||
async def delete_room(id: UUID, manager: RoomsManager = Depends()):
|
|
||||||
await manager.delete(id)
|
|
@ -1,37 +0,0 @@
|
|||||||
from sqlalchemy import select
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
|
||||||
|
|
||||||
from Merlin import Merlin, MerlinMessage
|
|
||||||
from API.dependencies import database
|
|
||||||
from API.models import Device, DeviceModelParameter
|
|
||||||
from .schemas import MerlinData
|
|
||||||
|
|
||||||
|
|
||||||
class WSManager:
|
|
||||||
merlin = Merlin()
|
|
||||||
|
|
||||||
async def merlin_send(self, data: MerlinData):
|
|
||||||
db: AsyncSession = database.create_async_session()
|
|
||||||
try:
|
|
||||||
if message := await self._get_message(db, data):
|
|
||||||
self.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
|
|
||||||
|
|
||||||
response = await db.execute(
|
|
||||||
select(DeviceModelParameter)
|
|
||||||
.where(
|
|
||||||
DeviceModelParameter.devicemodel_id == device.model.id,
|
|
||||||
DeviceModelParameter.parameter_id == data.parameter_id
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
model_parameter = response.scalar_one()
|
|
||||||
|
|
||||||
return MerlinMessage(device.urdi, model_parameter.f, int(data.value))
|
|
@ -1 +0,0 @@
|
|||||||
from .router import router
|
|
@ -1,39 +0,0 @@
|
|||||||
from fastapi import HTTPException, status
|
|
||||||
|
|
||||||
|
|
||||||
credentials = HTTPException(
|
|
||||||
status_code = status.HTTP_401_UNAUTHORIZED,
|
|
||||||
detail = 'Incorrect username or password',
|
|
||||||
headers = {'WWW-Authenticate': 'Bearer'},
|
|
||||||
)
|
|
||||||
|
|
||||||
invalid_token = HTTPException(
|
|
||||||
status_code = status.HTTP_401_UNAUTHORIZED,
|
|
||||||
detail = 'Token invalid or expired',
|
|
||||||
headers = {'WWW-Authenticate': 'Bearer'},
|
|
||||||
)
|
|
||||||
|
|
||||||
access_denied = HTTPException(
|
|
||||||
status_code = status.HTTP_403_FORBIDDEN,
|
|
||||||
detail = 'Access denied',
|
|
||||||
)
|
|
||||||
|
|
||||||
not_found = HTTPException(
|
|
||||||
status_code = status.HTTP_404_NOT_FOUND,
|
|
||||||
detail = 'Not found',
|
|
||||||
)
|
|
||||||
|
|
||||||
already_exist = HTTPException(
|
|
||||||
status_code = 400,
|
|
||||||
detail = 'Resource already exist'
|
|
||||||
)
|
|
||||||
|
|
||||||
not_initialized = HTTPException(
|
|
||||||
status_code = 400,
|
|
||||||
detail = 'Hub not initialized'
|
|
||||||
)
|
|
||||||
|
|
||||||
incorrect_format = HTTPException (
|
|
||||||
status_code = status.HTTP_422_UNPROCESSABLE_ENTITY,
|
|
||||||
detail = 'Data in incorrect format'
|
|
||||||
)
|
|
@ -1,5 +1,5 @@
|
|||||||
from typing import NamedTuple
|
from typing import NamedTuple
|
||||||
from enum import Enum, auto
|
from enum import Enum, IntEnum, auto
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
import time
|
import time
|
||||||
import qrcode
|
import qrcode
|
||||||
@ -12,7 +12,7 @@ class URDI(int):
|
|||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f'URDI({super().__repr__()})'
|
return f'URDI({super().__repr__()})'
|
||||||
|
|
||||||
class Model(int, Enum):
|
class Model(IntEnum):
|
||||||
zero = 0
|
zero = 0
|
||||||
hub = 1
|
hub = 1
|
||||||
relay = 2
|
relay = 2
|
||||||
|
24
SmartHome/client/api.py
Normal file
24
SmartHome/client/api.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
from typing import Type
|
||||||
|
from aiohttp import ClientSession
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from pydantic.error_wrappers import ValidationError
|
||||||
|
import config
|
||||||
|
|
||||||
|
|
||||||
|
headers = {'Auth': f'Bearer {config.access_token}'}
|
||||||
|
|
||||||
|
async def get(url: str,
|
||||||
|
model: Type[BaseModel],
|
||||||
|
client_session: ClientSession = None) -> BaseModel | None:
|
||||||
|
try:
|
||||||
|
session = client_session or ClientSession()
|
||||||
|
async with session.get(f'{config.api_url}/{url}') as response:
|
||||||
|
text = await response.text()
|
||||||
|
return model.parse_raw(text)
|
||||||
|
except ValidationError:
|
||||||
|
print(f'\nAPI Error: {url}\nModel: {model}\n{text}\n')
|
||||||
|
return None
|
||||||
|
finally:
|
||||||
|
if not client_session:
|
||||||
|
await session.close()
|
||||||
|
response.close()
|
45
SmartHome/client/client.py
Normal file
45
SmartHome/client/client.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
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():
|
||||||
|
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)
|
42
SmartHome/client/ws.py
Normal file
42
SmartHome/client/ws.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
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 = {'Auth': f'Bearer {config.access_token}'}
|
||||||
|
|
||||||
|
def on_message(ws, msg):
|
||||||
|
print('WS: ', msg)
|
||||||
|
ws_manager.handle_message(msg)
|
||||||
|
|
||||||
|
def on_open(ws):
|
||||||
|
print('WS Open')
|
||||||
|
pass
|
||||||
|
|
||||||
|
def on_close(ws, status_code, reason):
|
||||||
|
start_ws()
|
||||||
|
|
||||||
|
def on_error(ws, error):
|
||||||
|
# start_ws()
|
||||||
|
pass
|
||||||
|
|
||||||
|
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()
|
42
SmartHome/config.py
Normal file
42
SmartHome/config.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
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 = 'http://home.parker-programs.com/api'
|
||||||
|
ws_url = 'ws://home.parker-programs.com/ws/hub'
|
||||||
|
|
||||||
|
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'
|
@ -16,10 +16,6 @@ create_session = sessionmaker(
|
|||||||
autocommit=False, autoflush=False, bind=engine
|
autocommit=False, autoflush=False, bind=engine
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_session() -> Session:
|
|
||||||
with create_session() as session:
|
|
||||||
yield session
|
|
||||||
|
|
||||||
# async
|
# async
|
||||||
|
|
||||||
async_engine = create_async_engine(
|
async_engine = create_async_engine(
|
||||||
@ -29,7 +25,3 @@ async_engine = create_async_engine(
|
|||||||
create_async_session = sessionmaker(
|
create_async_session = sessionmaker(
|
||||||
async_engine, class_ = AsyncSession, expire_on_commit = False
|
async_engine, class_ = AsyncSession, expire_on_commit = False
|
||||||
)
|
)
|
||||||
|
|
||||||
async def get_async_session() -> AsyncSession:
|
|
||||||
async with create_async_session() as session:
|
|
||||||
yield session
|
|
19
SmartHome/exceptions/Internal.py
Normal file
19
SmartHome/exceptions/Internal.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
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
|
@ -71,5 +71,7 @@ class Merlin():
|
|||||||
func, arg = rawData
|
func, arg = rawData
|
||||||
print(f'Received {func=} {arg=}') # TODO: Log
|
print(f'Received {func=} {arg=}') # TODO: Log
|
||||||
|
|
||||||
receiveAndTransmitThread = Thread(target = Merlin().receiveAndTransmit)
|
merlin = Merlin()
|
||||||
receiveAndTransmitThread.start()
|
|
||||||
|
_receiveAndTransmitThread = Thread(target = Merlin().receiveAndTransmit)
|
||||||
|
_receiveAndTransmitThread.start()
|
@ -1,2 +1,2 @@
|
|||||||
from .Merlin import Merlin
|
from .Merlin import merlin
|
||||||
from .MerlinMessage import MerlinMessage
|
from .MerlinMessage import MerlinMessage
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
# This file lib_nrf24.py is a slightly tweaked version of Barraca's "pynrf24".
|
# This file lib_nrf24.py is a slightly tweaked version of Barraca's "pynrf24".
|
||||||
|
|
||||||
# So this is my tweak for Raspberry Pi and "Virtual GPIO" ...
|
# 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 Barraca's port to BeagleBone python ... (Joao Paulo Barraca <jpbarraca@gmail.com>)
|
||||||
# ... of maniacbug's NRF24L01 C++ library for Arduino.
|
# ... of maniacbug's NRF24L01 C++ library for Arduino.
|
||||||
# Brian Lavery Oct 2014
|
# Brian Lavery Oct 2014
|
||||||
@ -182,7 +182,7 @@ class NRF24:
|
|||||||
|
|
||||||
def __init__(self, gpio, spidev):
|
def __init__(self, gpio, spidev):
|
||||||
# It should be possible to instantiate multiple objects, with different GPIO / spidev
|
# It should be possible to instantiate multiple objects, with different GPIO / spidev
|
||||||
# EG on Raspberry, one could be RPI GPIO & spidev module, other could be virtual-GPIO
|
# 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
|
# On rpi, only bus 0 is supported here, not bus 1 of the model B plus
|
||||||
self.GPIO = gpio # the GPIO module
|
self.GPIO = gpio # the GPIO module
|
||||||
self.spidev = spidev # the spidev object/instance
|
self.spidev = spidev # the spidev object/instance
|
@ -1,19 +1,21 @@
|
|||||||
import os, sys
|
|
||||||
|
|
||||||
root = os.path.dirname(os.path.dirname(__file__))
|
|
||||||
sys.path.append(root)
|
|
||||||
|
|
||||||
import uvicorn
|
import uvicorn
|
||||||
from API.main import app
|
from server import app
|
||||||
from API.endpoints.hub import HubManager
|
from client import client, ws
|
||||||
from Raspberry import WiFi
|
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__':
|
if __name__ == '__main__':
|
||||||
# try:
|
run()
|
||||||
# hub = HubManager.default().get()
|
|
||||||
# WiFi.connect_first()
|
|
||||||
# except:
|
|
||||||
# WiFi.start_hotspot()
|
|
||||||
|
|
||||||
uvicorn.run('main:app', host = '0.0.0.0', port = 8000, reload = False, reload_dirs=[root,])
|
|
||||||
|
33
SmartHome/managers/DeviceModelManager.py
Normal file
33
SmartHome/managers/DeviceModelManager.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
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()
|
@ -4,40 +4,44 @@ from sqlalchemy import select, update, delete
|
|||||||
from sqlalchemy.orm import selectinload
|
from sqlalchemy.orm import selectinload
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from exceptions.Internal import InternalException, ExceptionCode
|
||||||
from AUID import AUID
|
from AUID import AUID
|
||||||
from API.models import (
|
import database
|
||||||
|
from models import (
|
||||||
User,
|
User,
|
||||||
Device,
|
Device,
|
||||||
DeviceModel,
|
DeviceModel,
|
||||||
DeviceParameterAssociation,
|
DeviceParameterAssociation,
|
||||||
DeviceParameterAssociation
|
DeviceParameterAssociation
|
||||||
)
|
)
|
||||||
from API.dependencies import database, auth
|
from schemas.device import (
|
||||||
from API import exceptions
|
DeviceParameter,
|
||||||
from . import schemas
|
Parameter,
|
||||||
from ..schemas import DeviceParameter, Parameter
|
DeviceState,
|
||||||
|
DevicePatch,
|
||||||
|
DeviceCreate,
|
||||||
|
Device as DeviceScheme
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DevicesManager:
|
class DevicesManager:
|
||||||
session: AsyncSession
|
session: AsyncSession
|
||||||
user: User
|
|
||||||
|
|
||||||
def __init__(self, session = Depends(database.get_async_session), user = Depends(auth.validate_user)):
|
def __init__(self, session: AsyncSession):
|
||||||
self.session = session
|
self.session = session
|
||||||
self.user = user
|
|
||||||
|
|
||||||
async def get(self, id: UUID) -> Device | None:
|
async def get(self, id: UUID) -> Device:
|
||||||
db: AsyncSession = self.session
|
db: AsyncSession = self.session
|
||||||
return await db.get(Device, id)
|
if device := await db.get(Device, id):
|
||||||
|
return device
|
||||||
|
else:
|
||||||
|
raise InternalException(ExceptionCode.not_found, 'Device not found')
|
||||||
|
|
||||||
async def state(self, id: UUID) -> schemas.DeviceState | None:
|
async def state(self, id: UUID) -> DeviceState | None:
|
||||||
db: AsyncSession = self.session
|
db: AsyncSession = self.session
|
||||||
device = await db.get(Device, id)
|
device = await self.get(id)
|
||||||
|
|
||||||
if not device:
|
device_state = DeviceState(**DeviceScheme.from_orm(device).dict())
|
||||||
return None
|
|
||||||
|
|
||||||
device_state = schemas.DeviceState(**schemas.Device.from_orm(device).dict())
|
|
||||||
|
|
||||||
async with database.async_engine.begin() as conn:
|
async with database.async_engine.begin() as conn:
|
||||||
parameters = await conn.run_sync(DevicesManager._read_parameters, device)
|
parameters = await conn.run_sync(DevicesManager._read_parameters, device)
|
||||||
@ -46,7 +50,7 @@ class DevicesManager:
|
|||||||
|
|
||||||
return device_state
|
return device_state
|
||||||
|
|
||||||
async def create(self, create_device: schemas.CreateDevice) -> Device:
|
async def create(self, create_device: DeviceCreate) -> Device:
|
||||||
db: AsyncSession = self.session
|
db: AsyncSession = self.session
|
||||||
|
|
||||||
id = AUID(bytes=create_device.id.bytes)
|
id = AUID(bytes=create_device.id.bytes)
|
||||||
@ -56,10 +60,17 @@ class DevicesManager:
|
|||||||
model = await db.get(DeviceModel, model_id)
|
model = await db.get(DeviceModel, model_id)
|
||||||
|
|
||||||
if not model:
|
if not model:
|
||||||
raise exceptions.incorrect_format
|
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():
|
if (await db.scalars(select(Device).where(Device.urdi == urdi))).first():
|
||||||
raise exceptions.already_exist
|
raise InternalException(
|
||||||
|
code = ExceptionCode.invalid_format,
|
||||||
|
msg = 'Device with this id already exist'
|
||||||
|
)
|
||||||
|
|
||||||
device = Device(
|
device = Device(
|
||||||
id = create_device.id,
|
id = create_device.id,
|
||||||
@ -82,7 +93,7 @@ class DevicesManager:
|
|||||||
|
|
||||||
return device
|
return device
|
||||||
|
|
||||||
async def patch(self, id: UUID, device: schemas.PatchDevice):
|
async def patch(self, id: UUID, device: DevicePatch):
|
||||||
db: AsyncSession = self.session
|
db: AsyncSession = self.session
|
||||||
values = {key: value for key, value in device.dict().items() if key != 'id'}
|
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.execute(update(Device).values(**values).where(Device.id == id))
|
||||||
@ -91,11 +102,14 @@ class DevicesManager:
|
|||||||
async def delete(self, device_id: UUID):
|
async def delete(self, device_id: UUID):
|
||||||
db: AsyncSession = self.session
|
db: AsyncSession = self.session
|
||||||
device = await self.get(device_id)
|
device = await self.get(device_id)
|
||||||
if device: # and device.house.owner_id == self.owner_id:
|
if device:
|
||||||
await db.delete(device)
|
await db.delete(device)
|
||||||
await db.commit()
|
await db.commit()
|
||||||
else:
|
else:
|
||||||
raise exceptions.not_found
|
raise InternalException(
|
||||||
|
code = ExceptionCode.not_found,
|
||||||
|
msg = 'Device not found'
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _read_parameters(_, device: Device) -> list[DeviceParameter]:
|
def _read_parameters(_, device: Device) -> list[DeviceParameter]:
|
45
SmartHome/managers/HouseManager.py
Normal file
45
SmartHome/managers/HouseManager.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
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())
|
||||||
|
)
|
@ -1,46 +1,62 @@
|
|||||||
from __future__ import annotations
|
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from fastapi import Depends
|
from fastapi import Depends
|
||||||
from sqlalchemy import select, update
|
from sqlalchemy import select, update
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.exc import NoResultFound
|
from sqlalchemy.exc import NoResultFound
|
||||||
|
|
||||||
from Raspberry import WiFi
|
|
||||||
import config
|
import config
|
||||||
from API import exceptions
|
from exceptions.Internal import InternalException, ExceptionCode
|
||||||
from API.models import User, Hub
|
from hardware import WiFi
|
||||||
from API.dependencies import database, auth
|
from models import User, Hub
|
||||||
from API.auth import UserToken, UserAuthManager, AuthException
|
from schemas import hub as schemas
|
||||||
from API import endpoints
|
from managers import HouseManager
|
||||||
from . import schemas
|
from server.dependencies import database, auth
|
||||||
|
from server.auth import UserToken, UserAuthManager, AuthException
|
||||||
|
|
||||||
|
|
||||||
class HubManager:
|
class HubManager:
|
||||||
session: AsyncSession
|
session: AsyncSession
|
||||||
token: auth.UserToken
|
token: auth.UserToken | None
|
||||||
user: User | None = None
|
user: User | None = None
|
||||||
|
|
||||||
def __init__(self, session = Depends(database.get_async_session), token = Depends(auth.optional_token)):
|
def __init__(self, session: AsyncSession, token: UserToken = None):
|
||||||
self.session = session
|
self.session = session
|
||||||
self.token = token
|
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):
|
async def parse_token(self, raw_token: str):
|
||||||
db: AsyncSession = self.session
|
db: AsyncSession = self.session
|
||||||
try:
|
try:
|
||||||
self.token = UserAuthManager().validate_access(raw_token)
|
self.token = UserAuthManager().validate_access(raw_token)
|
||||||
except AuthException:
|
except AuthException:
|
||||||
raise exceptions.access_denied
|
raise InternalException(ExceptionCode.unauthorized)
|
||||||
self.user = await db.get(User, self.token.user_id)
|
self.user = await db.get(User, self.token.user_id)
|
||||||
|
|
||||||
async def init(self, create_hub: schemas.HubInit) -> Hub:
|
async def init(self, create_hub: schemas.HubInit, raw_token: str) -> Hub:
|
||||||
db: AsyncSession = self.session
|
db: AsyncSession = self.session
|
||||||
|
|
||||||
if hub := await self.get():
|
try:
|
||||||
|
hub = await self.get()
|
||||||
|
except InternalException:
|
||||||
|
hub = None
|
||||||
|
|
||||||
|
if hub:
|
||||||
|
await self.parse_token(raw_token)
|
||||||
await self.check_access()
|
await self.check_access()
|
||||||
await db.delete(hub)
|
await db.delete(hub)
|
||||||
|
self.save_tokens(create_hub)
|
||||||
|
else:
|
||||||
|
self.save_credentials(create_hub)
|
||||||
|
await self.parse_token(raw_token)
|
||||||
|
|
||||||
house_manager = endpoints.house.HouseManager(db)
|
house_manager = HouseManager(db)
|
||||||
await house_manager.create(create_hub.house_id)
|
await house_manager.create(create_hub.house_id)
|
||||||
|
|
||||||
hub = Hub(id = create_hub.id, name = create_hub.name, house_id = create_hub.house_id)
|
hub = Hub(id = create_hub.id, name = create_hub.name, house_id = create_hub.house_id)
|
||||||
@ -53,12 +69,7 @@ class HubManager:
|
|||||||
await db.commit()
|
await db.commit()
|
||||||
return hub
|
return hub
|
||||||
|
|
||||||
async def get(self) -> Hub | None:
|
async def patch(self, hub: schemas.HubPatch):
|
||||||
db: AsyncSession = self.session
|
|
||||||
result = await db.scalars(select(Hub))
|
|
||||||
return result.first()
|
|
||||||
|
|
||||||
async def patch(self, hub: schemas.PatchHub):
|
|
||||||
db: AsyncSession = self.session
|
db: AsyncSession = self.session
|
||||||
values = {key: value for key, value in hub.dict().items() if value != None}
|
values = {key: value for key, value in hub.dict().items() if value != None}
|
||||||
|
|
||||||
@ -104,10 +115,10 @@ class HubManager:
|
|||||||
return
|
return
|
||||||
|
|
||||||
if not self.token:
|
if not self.token:
|
||||||
raise exceptions.access_denied
|
raise InternalException(ExceptionCode.access_denied)
|
||||||
|
|
||||||
db: AsyncSession = self.session
|
db: AsyncSession = self.session
|
||||||
self.user = await db.get(User, self.token.user_id)
|
self.user = await db.get(User, self.token.user_id)
|
||||||
|
|
||||||
if not self.user:
|
if not self.user:
|
||||||
raise exceptions.access_denied
|
raise InternalException(ExceptionCode.access_denied)
|
@ -5,42 +5,38 @@ from sqlalchemy import select, update, delete
|
|||||||
from sqlalchemy.orm import selectinload
|
from sqlalchemy.orm import selectinload
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from API import exceptions
|
from exceptions.Internal import InternalException, ExceptionCode
|
||||||
from API import endpoints
|
from models import User, Room
|
||||||
from API.models import User, Room
|
from schemas import room as schemas
|
||||||
from API.dependencies import database, auth
|
from server import endpoints
|
||||||
from . import schemas
|
|
||||||
|
|
||||||
|
|
||||||
class RoomsManager:
|
class RoomsManager:
|
||||||
session: AsyncSession
|
session: AsyncSession
|
||||||
user: User
|
|
||||||
|
|
||||||
def __init__(self, session = Depends(database.get_async_session), user = Depends(auth.validate_user)):
|
def __init__(self, session: AsyncSession):
|
||||||
self.session = session
|
self.session = session
|
||||||
self.user = user
|
|
||||||
|
|
||||||
async def get(self, id: UUID) -> Room | None:
|
async def get(self, id: UUID) -> Room:
|
||||||
db: AsyncSession = self.session
|
db: AsyncSession = self.session
|
||||||
response = await db.scalars(
|
result = await db.scalars(
|
||||||
select(Room).where(Room.id == id).options(selectinload(Room.devices))
|
select(Room).where(Room.id == id).options(selectinload(Room.devices))
|
||||||
)
|
)
|
||||||
return response.first()
|
if room := result.first():
|
||||||
|
return room
|
||||||
|
else:
|
||||||
|
raise InternalException(ExceptionCode.not_found, 'Room not found')
|
||||||
|
|
||||||
async def create(self, create_room: schemas.CreateRoom) -> Room:
|
async def create(self, create_room: schemas.RoomCreate) -> Room:
|
||||||
db: AsyncSession = self.session
|
db: AsyncSession = self.session
|
||||||
house = await endpoints.house.HouseManager(db).get()
|
house = await endpoints.house.HouseManager(db).get()
|
||||||
|
|
||||||
if not house:
|
|
||||||
raise exceptions.not_initialized
|
|
||||||
|
|
||||||
room = Room(name = create_room.name, house_id = house.id, devices = [])
|
room = Room(name = create_room.name, house_id = house.id, devices = [])
|
||||||
db.add(room)
|
db.add(room)
|
||||||
await db.commit()
|
await db.commit()
|
||||||
|
|
||||||
return room
|
return room
|
||||||
|
|
||||||
async def patch(self, id: UUID, room: schemas.PatchRoom):
|
async def patch(self, id: UUID, room: schemas.RoomPatch):
|
||||||
db: AsyncSession = self.session
|
db: AsyncSession = self.session
|
||||||
values = {key: value for key, value in room.dict().items() if key != 'id'}
|
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.execute(update(Room).values(**values).where(Room.id == id))
|
||||||
@ -49,8 +45,5 @@ class RoomsManager:
|
|||||||
async def delete(self, room_id: UUID):
|
async def delete(self, room_id: UUID):
|
||||||
db: AsyncSession = self.session
|
db: AsyncSession = self.session
|
||||||
room = await self.get(room_id)
|
room = await self.get(room_id)
|
||||||
if room:
|
await db.delete(room)
|
||||||
await db.delete(room)
|
await db.commit()
|
||||||
await db.commit()
|
|
||||||
else:
|
|
||||||
raise exceptions.not_found
|
|
57
SmartHome/managers/WSManager.py
Normal file
57
SmartHome/managers/WSManager.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
class WSManager:
|
||||||
|
session: AsyncSession
|
||||||
|
|
||||||
|
def __inti__(self, session: AsyncSession):
|
||||||
|
self.session = session
|
||||||
|
|
||||||
|
async def handle_message(self, msg: str):
|
||||||
|
socket = SocketData.parse_raw(msg)
|
||||||
|
try:
|
||||||
|
socket = SocketData.parse_raw(msg)
|
||||||
|
except ValidationError:
|
||||||
|
return
|
||||||
|
|
||||||
|
match socket.type:
|
||||||
|
case SocketType.merlin:
|
||||||
|
merlin_data = MerlinData(**socket.data)
|
||||||
|
# try:
|
||||||
|
# merlin_data = MerlinData(**socket.data)
|
||||||
|
# except: # TODO: specify exception
|
||||||
|
# return
|
||||||
|
await self.merlin_send(merlin_data)
|
||||||
|
|
||||||
|
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))
|
6
SmartHome/managers/__init__.py
Normal file
6
SmartHome/managers/__init__.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from .DeviceModelManager import DeviceModelManager
|
||||||
|
from .DevicesManager import DevicesManager
|
||||||
|
from .HouseManager import HouseManager
|
||||||
|
from .HubManager import HubManager
|
||||||
|
from .RoomsManager import RoomsManager
|
||||||
|
from .WSManager import WSManager
|
32
SmartHome/schemas/device.py
Normal file
32
SmartHome/schemas/device.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
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] = []
|
@ -1,14 +1,16 @@
|
|||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from ..hub.schemas import Hub
|
from .hub import Hub
|
||||||
from ..room.schemas import RoomInfo
|
from .room import RoomInfo
|
||||||
|
|
||||||
|
|
||||||
class House(BaseModel):
|
class HousePatch(BaseModel):
|
||||||
id: UUID
|
id: UUID
|
||||||
name: str
|
name: str
|
||||||
hubs: list[Hub]
|
|
||||||
rooms: list[RoomInfo]
|
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
|
||||||
|
class House(HousePatch):
|
||||||
|
hubs: list[Hub]
|
||||||
|
rooms: list[RoomInfo]
|
@ -2,6 +2,8 @@ from uuid import UUID
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
# Auth
|
||||||
|
|
||||||
class TokensPair(BaseModel):
|
class TokensPair(BaseModel):
|
||||||
access_token: str
|
access_token: str
|
||||||
refresh_token: str
|
refresh_token: str
|
||||||
@ -9,6 +11,8 @@ class TokensPair(BaseModel):
|
|||||||
class HubAuthItems(TokensPair):
|
class HubAuthItems(TokensPair):
|
||||||
public_key: str
|
public_key: str
|
||||||
|
|
||||||
|
# Hub
|
||||||
|
|
||||||
class Hub(BaseModel):
|
class Hub(BaseModel):
|
||||||
id: UUID
|
id: UUID
|
||||||
name: str
|
name: str
|
||||||
@ -23,6 +27,8 @@ class HubPatch(BaseModel):
|
|||||||
class HubInit(Hub, HubAuthItems):
|
class HubInit(Hub, HubAuthItems):
|
||||||
...
|
...
|
||||||
|
|
||||||
|
# WiFi
|
||||||
|
|
||||||
class Hotspot(BaseModel):
|
class Hotspot(BaseModel):
|
||||||
ssid: str
|
ssid: str
|
||||||
quality: float
|
quality: float
|
@ -1,6 +1,6 @@
|
|||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from API.models import DeviceModelParameter
|
from models import DeviceModelParameter
|
||||||
|
|
||||||
|
|
||||||
class Parameter(BaseModel):
|
class Parameter(BaseModel):
|
||||||
@ -20,11 +20,3 @@ class Parameter(BaseModel):
|
|||||||
|
|
||||||
class DeviceParameter(Parameter):
|
class DeviceParameter(Parameter):
|
||||||
value: int
|
value: int
|
||||||
|
|
||||||
class DeviceModel(BaseModel):
|
|
||||||
id: UUID
|
|
||||||
name: str
|
|
||||||
parameters: list[Parameter]
|
|
||||||
|
|
||||||
class Config:
|
|
||||||
orm_mode = True
|
|
@ -1,12 +1,12 @@
|
|||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from ..device.schemas import Device
|
from .device import Device
|
||||||
|
|
||||||
|
|
||||||
class CreateRoom(BaseModel):
|
class RoomCreate(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
|
|
||||||
class PatchRoom(CreateRoom):
|
class RoomPatch(RoomCreate):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class RoomInfo(BaseModel):
|
class RoomInfo(BaseModel):
|
@ -1,12 +1,11 @@
|
|||||||
from fastapi import FastAPI, APIRouter
|
from fastapi import FastAPI, APIRouter
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
|
||||||
from . import models
|
import models
|
||||||
from . import endpoints
|
import database
|
||||||
from . import dependencies
|
from server import exceptions_handlers, endpoints
|
||||||
|
|
||||||
|
models.Base.metadata.create_all(bind = database.engine)
|
||||||
models.Base.metadata.create_all(bind = dependencies.database.engine)
|
|
||||||
|
|
||||||
description = '''
|
description = '''
|
||||||
[**Admin**](/admin)
|
[**Admin**](/admin)
|
||||||
@ -26,11 +25,14 @@ app = FastAPI(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
exceptions_handlers.setup(app)
|
||||||
|
|
||||||
api = APIRouter(prefix = '/api')
|
api = APIRouter(prefix = '/api')
|
||||||
api.include_router(endpoints.house.router)
|
api.include_router(endpoints.house.router)
|
||||||
api.include_router(endpoints.hub.router)
|
api.include_router(endpoints.hub.router)
|
||||||
api.include_router(endpoints.room.router)
|
api.include_router(endpoints.room.router)
|
||||||
api.include_router(endpoints.device.router)
|
api.include_router(endpoints.device.router)
|
||||||
|
|
||||||
app.include_router(api)
|
app.include_router(api)
|
||||||
app.include_router(endpoints.ws.router)
|
app.include_router(endpoints.ws.router)
|
||||||
|
|
@ -32,8 +32,8 @@ class BaseAuthManager(ABC):
|
|||||||
def _get_parsed_token(self, payload: dict) -> BaseToken:
|
def _get_parsed_token(self, payload: dict) -> BaseToken:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def validate_access(self, token: str) -> BaseToken:
|
def validate_access(self, raw_token: str) -> BaseToken:
|
||||||
token = self._parse_token(token)
|
token = self._parse_token(raw_token)
|
||||||
|
|
||||||
if not token:
|
if not token:
|
||||||
raise AuthException()
|
raise AuthException()
|
||||||
@ -46,9 +46,9 @@ class BaseAuthManager(ABC):
|
|||||||
|
|
||||||
return token
|
return token
|
||||||
|
|
||||||
def _parse_token(self, token: str) -> BaseToken:
|
def _parse_token(self, raw_token: str) -> BaseToken:
|
||||||
try:
|
try:
|
||||||
payload = jwt.decode(token, config.public_key, algorithms = [config.algorithm])
|
payload = jwt.decode(raw_token, config.public_key, algorithms = [config.algorithm])
|
||||||
token = self._get_parsed_token(payload)
|
token = self._get_parsed_token(payload)
|
||||||
except JWTError:
|
except JWTError:
|
||||||
raise AuthException()
|
raise AuthException()
|
@ -1,8 +1,8 @@
|
|||||||
from fastapi import Depends
|
from fastapi import Depends
|
||||||
from fastapi.security import OAuth2PasswordBearer
|
from fastapi.security import OAuth2PasswordBearer
|
||||||
|
|
||||||
from API import exceptions
|
from exceptions.Internal import InternalException, ExceptionCode
|
||||||
from API.models import User
|
from models import User
|
||||||
from ..auth import (
|
from ..auth import (
|
||||||
UserToken,
|
UserToken,
|
||||||
UserAuthManager,
|
UserAuthManager,
|
||||||
@ -15,21 +15,15 @@ oauth2_scheme = OAuth2PasswordBearer(tokenUrl = '/api/user/login')
|
|||||||
optional_oauth2_scheme = OAuth2PasswordBearer(tokenUrl = '/api/user/login', auto_error=False)
|
optional_oauth2_scheme = OAuth2PasswordBearer(tokenUrl = '/api/user/login', auto_error=False)
|
||||||
userAuthManager = UserAuthManager()
|
userAuthManager = UserAuthManager()
|
||||||
|
|
||||||
async def validate_user(token: str = Depends(oauth2_scheme), session = Depends(get_async_session)) -> User:
|
async def validate_user(raw_token: str = Depends(oauth2_scheme), session = Depends(get_async_session)) -> User:
|
||||||
try:
|
token = userAuthManager.validate_access(raw_token)
|
||||||
token = userAuthManager.validate_access(token)
|
user = await session.get(User, token.user_id)
|
||||||
user = await session.get(User, token.user_id)
|
if not user:
|
||||||
if not user:
|
raise AuthException()
|
||||||
raise AuthException
|
return user
|
||||||
return user
|
|
||||||
except AuthException:
|
|
||||||
raise exceptions.access_denied
|
|
||||||
|
|
||||||
def validate_token(token: str = Depends(oauth2_scheme)) -> UserToken:
|
def validate_token(token: str = Depends(oauth2_scheme)) -> UserToken:
|
||||||
try:
|
return userAuthManager.validate_access(token)
|
||||||
return userAuthManager.validate_access(token)
|
|
||||||
except AuthException:
|
|
||||||
raise exceptions.access_denied
|
|
||||||
|
|
||||||
def optional_token(token: str = Depends(optional_oauth2_scheme)) -> UserToken | None:
|
def optional_token(token: str = Depends(optional_oauth2_scheme)) -> UserToken | None:
|
||||||
if not token:
|
if not token:
|
15
SmartHome/server/dependencies/database.py
Normal file
15
SmartHome/server/dependencies/database.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
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
|
6
SmartHome/server/endpoints/__init__.py
Normal file
6
SmartHome/server/endpoints/__init__.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from . import admin
|
||||||
|
from . import device
|
||||||
|
from . import house
|
||||||
|
from . import hub
|
||||||
|
from . import room
|
||||||
|
from . import ws
|
@ -1,7 +1,7 @@
|
|||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from sqladmin import Admin, ModelAdmin, SidebarLink
|
from sqladmin import Admin, ModelAdmin, SidebarLink
|
||||||
from API.dependencies import database
|
import database
|
||||||
from API.models import (
|
from models import (
|
||||||
Base,
|
Base,
|
||||||
House,
|
House,
|
||||||
Hub,
|
Hub,
|
43
SmartHome/server/endpoints/device.py
Normal file
43
SmartHome/server/endpoints/device.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
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)
|
25
SmartHome/server/endpoints/house.py
Normal file
25
SmartHome/server/endpoints/house.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
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()
|
62
SmartHome/server/endpoints/hub.py
Normal file
62
SmartHome/server/endpoints/hub.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
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)
|
36
SmartHome/server/endpoints/room.py
Normal file
36
SmartHome/server/endpoints/room.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
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)
|
@ -1,6 +1,5 @@
|
|||||||
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
|
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
|
||||||
from .schemas import SocketType, SocketData, MerlinData
|
from managers import WSManager
|
||||||
from .WSManager import WSManager
|
|
||||||
|
|
||||||
|
|
||||||
class ConnectionManager:
|
class ConnectionManager:
|
||||||
@ -19,20 +18,7 @@ class ConnectionManager:
|
|||||||
self.active_connections.remove(websocket)
|
self.active_connections.remove(websocket)
|
||||||
|
|
||||||
async def handle_socket(self, websocket: WebSocket, msg: str):
|
async def handle_socket(self, websocket: WebSocket, msg: str):
|
||||||
socket = SocketData.parse_raw(msg)
|
await self.wsmanager.handle_message(msg)
|
||||||
# try:
|
|
||||||
# socket = SocketData.parse_raw(msg)
|
|
||||||
# except: # TODO: specify exception
|
|
||||||
# return
|
|
||||||
|
|
||||||
match socket.type:
|
|
||||||
case SocketType.merlin:
|
|
||||||
merlin_data = MerlinData(**socket.data)
|
|
||||||
# try:
|
|
||||||
# merlin_data = MerlinData(**socket.data)
|
|
||||||
# except: # TODO: specify exception
|
|
||||||
# return
|
|
||||||
await self.wsmanager.merlin_send(merlin_data)
|
|
||||||
|
|
||||||
connection = ConnectionManager()
|
connection = ConnectionManager()
|
||||||
router = APIRouter(
|
router = APIRouter(
|
23
SmartHome/server/exceptions_handlers.py
Normal file
23
SmartHome/server/exceptions_handlers.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
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)
|
44
SmartHome/server/http_exceptions.py
Normal file
44
SmartHome/server/http_exceptions.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# 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'
|
||||||
|
# )
|
||||||
|
# }
|
@ -5,7 +5,7 @@ import random
|
|||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from API import models
|
import models
|
||||||
|
|
||||||
|
|
||||||
room_names = [ 'Atrium', 'Ballroom', 'Bathroom', 'Bedroom', 'Billiard room', 'Cabinet', 'Computer lab',
|
room_names = [ 'Atrium', 'Ballroom', 'Bathroom', 'Bedroom', 'Billiard room', 'Cabinet', 'Computer lab',
|
||||||
@ -46,7 +46,7 @@ class Faker:
|
|||||||
}, headers = {
|
}, headers = {
|
||||||
'Authorization': f'Bearer {self.user_access_token}'
|
'Authorization': f'Bearer {self.user_access_token}'
|
||||||
})
|
})
|
||||||
|
|
||||||
return response.json()
|
return response.json()
|
||||||
|
|
||||||
def get_house(self) -> dict[str, Any]:
|
def get_house(self) -> dict[str, Any]:
|
||||||
@ -62,9 +62,9 @@ class Faker:
|
|||||||
session.refresh(room)
|
session.refresh(room)
|
||||||
return room
|
return room
|
||||||
|
|
||||||
def create_device_model(self) -> models.DeviceModel:
|
def create_device_model(self, id: UUID = uuid1()) -> models.DeviceModel:
|
||||||
with self.create_session() as session:
|
with self.create_session() as session:
|
||||||
device_model = models.DeviceModel(name = random.choice(model_names))
|
device_model = models.DeviceModel(id=id, name=random.choice(model_names))
|
||||||
session.add(device_model)
|
session.add(device_model)
|
||||||
session.commit()
|
session.commit()
|
||||||
session.refresh(device_model)
|
session.refresh(device_model)
|
||||||
|
@ -13,8 +13,8 @@ from sqlalchemy.orm import sessionmaker, Session
|
|||||||
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
|
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
|
||||||
|
|
||||||
from main import app
|
from main import app
|
||||||
from API.dependencies.database import get_session, get_async_session
|
from server.dependencies.database import get_session, get_async_session
|
||||||
from API import models
|
import models
|
||||||
import config
|
import config
|
||||||
|
|
||||||
from .faker import Faker
|
from .faker import Faker
|
||||||
@ -22,9 +22,9 @@ from .faker import Faker
|
|||||||
|
|
||||||
# Settings
|
# Settings
|
||||||
|
|
||||||
with open(f'{config.path}/SmartHome/tests/jwt-key', 'r') as f:
|
with open(f'{config.path}/tests/jwt-key', 'r') as f:
|
||||||
secret_key = f.read()
|
secret_key = f.read()
|
||||||
with open(f'{config.path}/SmartHome/tests/jwt-key.pub', 'r') as f:
|
with open(f'{config.path}/tests/jwt-key.pub', 'r') as f:
|
||||||
public_key = f.read()
|
public_key = f.read()
|
||||||
|
|
||||||
user_id = UUID('f085ef73-f599-11ec-acda-58961df87e73')
|
user_id = UUID('f085ef73-f599-11ec-acda-58961df87e73')
|
||||||
|
@ -2,35 +2,34 @@ from tests.setup import *
|
|||||||
|
|
||||||
|
|
||||||
faker.init_hub()
|
faker.init_hub()
|
||||||
id = uuid1()
|
id = '62ff3e2b-0130-0002-0001-000000000001'
|
||||||
room_id = faker.create_room().id
|
room_id = faker.create_room().id
|
||||||
model_id = faker.create_device_model().id
|
faker.create_device_model('00000000-0000-0002-0000-000000000000')
|
||||||
device = {
|
device = {
|
||||||
'id': str(id),
|
'id': id,
|
||||||
'name': 'New Room',
|
'name': 'New Room',
|
||||||
'room_id': str(room_id),
|
'room_id': str(room_id),
|
||||||
'model_id': str(model_id),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def test_get_device_null():
|
def test_get_device_null():
|
||||||
response = client.get(f'/api/device/', headers = auth_headers)
|
response = client.get(f'/api/device/', headers = auth_headers)
|
||||||
assert response.status_code == 405
|
assert response.status_code == 405, response.text
|
||||||
assert response.json() == {'detail': 'Method Not Allowed'}
|
assert response.json().get('detail') == 'Method Not Allowed'
|
||||||
|
|
||||||
def test_get_device_404():
|
def test_get_device_404():
|
||||||
response = client.get(f'/api/device/{id}', headers = auth_headers)
|
response = client.get(f'/api/device/{id}', headers = auth_headers)
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404, response.text
|
||||||
assert response.json() == {'detail': 'Not found'}
|
assert response.json().get('detail') == 'Device not found'
|
||||||
|
|
||||||
def test_delete_device_404():
|
def test_delete_device_404():
|
||||||
response = client.delete(f'/api/device/{id}', headers = auth_headers)
|
response = client.delete(f'/api/device/{id}', headers = auth_headers)
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404, response.text
|
||||||
assert response.json() == {'detail': 'Not found'}
|
assert response.json().get('detail') == 'Device not found'
|
||||||
|
|
||||||
def test_create_device():
|
def test_create_device():
|
||||||
response = client.post(f'/api/device', json = device, headers = auth_headers)
|
response = client.post(f'/api/device', json = device, headers = auth_headers)
|
||||||
new_device = response.json()
|
new_device = response.json()
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200, response.text
|
||||||
assert new_device.get('model')
|
assert new_device.get('model')
|
||||||
device['model'] = new_device.get('model')
|
device['model'] = new_device.get('model')
|
||||||
assert new_device == device
|
assert new_device == device
|
||||||
@ -38,18 +37,18 @@ def test_create_device():
|
|||||||
def test_get_device():
|
def test_get_device():
|
||||||
response = client.get(f'/api/device/{id}', headers = auth_headers)
|
response = client.get(f'/api/device/{id}', headers = auth_headers)
|
||||||
device['parameters'] = []
|
device['parameters'] = []
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200, response.text
|
||||||
assert response.json() == device
|
assert response.json() == device
|
||||||
|
|
||||||
def test_patch_device():
|
def test_patch_device():
|
||||||
device['name'] = 'Patched Device'
|
device['name'] = 'Patched Device'
|
||||||
response = client.patch(f'/api/device/{id}', json = device, headers = auth_headers)
|
response = client.patch(f'/api/device/{id}', json = device, headers = auth_headers)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200, response.text
|
||||||
assert client.get(f'/api/device/{id}', headers = auth_headers).json() == device
|
assert client.get(f'/api/device/{id}', headers = auth_headers).json() == device
|
||||||
|
|
||||||
def test_delete_device():
|
def test_delete_device():
|
||||||
response = client.delete(f'/api/device/{id}', json = device, headers = auth_headers)
|
response = client.delete(f'/api/device/{id}', json = device, headers = auth_headers)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200, response.text
|
||||||
|
|
||||||
def test_get_deleted_device():
|
def test_get_deleted_device():
|
||||||
test_get_device_404()
|
test_get_device_404()
|
||||||
|
@ -14,12 +14,12 @@ hub_init_json = {
|
|||||||
|
|
||||||
def test_get_hub_401():
|
def test_get_hub_401():
|
||||||
response = client.get('/api/hub')
|
response = client.get('/api/hub')
|
||||||
assert response.status_code == 401
|
assert response.status_code in [401, 403], response.text
|
||||||
assert response.json() == {'detail': 'Not authenticated'}
|
assert response.json() in [{'detail': 'Not authenticated'}, {'detail': 'Access denied'}]
|
||||||
|
|
||||||
def test_init_hub_401():
|
def test_init_hub_401():
|
||||||
response = client.post('/api/hub', json = hub_init_json)
|
response = client.post('/api/hub', json = hub_init_json)
|
||||||
assert response.status_code == 401
|
assert response.status_code == 401, response.text
|
||||||
assert response.json() == {'detail': 'Not authenticated'}
|
assert response.json() == {'detail': 'Not authenticated'}
|
||||||
|
|
||||||
def _test_get_hub_404():
|
def _test_get_hub_404():
|
||||||
|
@ -10,17 +10,17 @@ room = {
|
|||||||
def test_get_room_null():
|
def test_get_room_null():
|
||||||
response = client.get(f'/api/room/', headers = auth_headers)
|
response = client.get(f'/api/room/', headers = auth_headers)
|
||||||
assert response.status_code == 405
|
assert response.status_code == 405
|
||||||
assert response.json() == {'detail': 'Method Not Allowed'}
|
assert response.json().get('detail') == 'Method Not Allowed'
|
||||||
|
|
||||||
def test_get_room_404():
|
def test_get_room_404():
|
||||||
response = client.get(f'/api/room/{id}', headers = auth_headers)
|
response = client.get(f'/api/room/{id}', headers = auth_headers)
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
assert response.json() == {'detail': 'Not found'}
|
assert response.json().get('detail') == 'Room not found'
|
||||||
|
|
||||||
def test_delete_room_404():
|
def test_delete_room_404():
|
||||||
response = client.delete(f'/api/room/{id}', headers = auth_headers)
|
response = client.delete(f'/api/room/{id}', headers = auth_headers)
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
assert response.json() == {'detail': 'Not found'}
|
assert response.json().get('detail') == 'Room not found'
|
||||||
|
|
||||||
def test_create_room():
|
def test_create_room():
|
||||||
response = client.post(f'/api/room', json = room, headers = auth_headers)
|
response = client.post(f'/api/room', json = room, headers = auth_headers)
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import os, sys
|
import os, sys
|
||||||
|
|
||||||
root = os.path.dirname(os.path.dirname(__file__))
|
# root = os.path.dirname(os.path.dirname(__file__))
|
||||||
sys.path.append(root)
|
# sys.path.append(root)
|
||||||
sys.path.append(root + '/VoiceAssistant')
|
# sys.path.append(root + '/VoiceAssistant')
|
||||||
|
|
||||||
from TelegramBot import TelegramBot
|
from TelegramBot import TelegramBot
|
||||||
|
|
||||||
|
@ -139,7 +139,7 @@ class YoutubeAPI:
|
|||||||
'Accept':'application/json'
|
'Accept':'application/json'
|
||||||
}
|
}
|
||||||
async with ClientSession() as session:
|
async with ClientSession() as session:
|
||||||
response = await session.get(url, params = params, headers = headers)
|
result = await session.get(url, params = params, headers = headers)
|
||||||
json = await response.json()
|
json = await response.json()
|
||||||
return json
|
return json
|
||||||
return None
|
return None
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import os, sys
|
import os, sys
|
||||||
|
|
||||||
root = os.path.dirname(os.path.dirname(__file__))
|
# root = os.path.dirname(os.path.dirname(__file__))
|
||||||
sys.path.append(root)
|
# sys.path.append(root)
|
||||||
|
|
||||||
from VoiceAssistant import VoiceAssistant
|
from VoiceAssistant import VoiceAssistant
|
||||||
|
|
||||||
|
@ -4,11 +4,11 @@
|
|||||||
# sudo apt-get install libssl-dev
|
# sudo apt-get install libssl-dev
|
||||||
|
|
||||||
|
|
||||||
# general
|
# General
|
||||||
requests
|
requests
|
||||||
aiohttp
|
aiohttp
|
||||||
|
|
||||||
#Rpi
|
# Rpi
|
||||||
RPi.GPIO
|
RPi.GPIO
|
||||||
spidev
|
spidev
|
||||||
|
|
||||||
@ -22,24 +22,23 @@ https://github.com/alphacep/vosk-api/releases/download/v0.3.42/vosk-0.3.42-py3-n
|
|||||||
# google-cloud-texttospeech
|
# google-cloud-texttospeech
|
||||||
|
|
||||||
# telegram
|
# telegram
|
||||||
PyTelegramBotApi
|
PyTelegramBotApi # aiogram
|
||||||
|
|
||||||
# QA
|
# QA
|
||||||
bs4
|
bs4
|
||||||
wikipedia
|
wikipedia
|
||||||
|
|
||||||
# Zieit
|
# Zieit
|
||||||
# xlrd
|
#xlrd
|
||||||
# xlwt
|
#xlwt
|
||||||
# xlutils
|
#xlutils
|
||||||
|
|
||||||
|
|
||||||
# Media
|
# Media
|
||||||
aiohttp
|
#pafy
|
||||||
pafy
|
#screeninfo
|
||||||
screeninfo
|
#psutil
|
||||||
psutil
|
#yt_dlp #pip install youtube-dl
|
||||||
yt_dlp #pip install youtube-dl
|
|
||||||
|
|
||||||
|
|
||||||
# API
|
# API
|
||||||
@ -54,7 +53,10 @@ python-jose
|
|||||||
bcrypt
|
bcrypt
|
||||||
git+https://github.com/MarkParker5/sqladmin.git # sqladmin
|
git+https://github.com/MarkParker5/sqladmin.git # sqladmin
|
||||||
|
|
||||||
|
# Client
|
||||||
|
websocket
|
||||||
|
|
||||||
# qrcode
|
|
||||||
|
# QRcode
|
||||||
qrcode
|
qrcode
|
||||||
pillow
|
pillow
|
@ -1,2 +1,2 @@
|
|||||||
# sudo
|
# sudo
|
||||||
@reboot python3.10 ~/ArchieHub/SmartHome/Raspberry/WiFi.py
|
@reboot python3.10 ~/ArchieHub/SmartHome/hardware/WiFi.py
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
import config
|
import config
|
||||||
import pathlib
|
import pathlib
|
||||||
from SmartHome.Raspberry import WiFi
|
from SmartHome.hardware import WiFi
|
||||||
|
|
||||||
path = str(pathlib.Path(__file__).parent.absolute())
|
path = str(pathlib.Path(__file__).parent.absolute())
|
||||||
src: str = path + '/resources'
|
src: str = path + '/resources'
|
Reference in New Issue
Block a user