From a65dee89625912e3f88bbb0328ad675fc6b83304 Mon Sep 17 00:00:00 2001 From: BluemediaGER Date: Fri, 19 Apr 2024 00:08:29 +0200 Subject: [PATCH] Implement transaction handling --- .gitignore | 3 +- app/main.py | 22 ++-- app/models/chargepoint.py | 8 +- app/models/meter_value.py | 10 +- app/models/transaction.py | 17 ++- app/models/user.py | 1 + app/ocpp_proto/chargepoint.py | 184 +++++++++++++++++++++-------- app/routers/chargepoint_v1.py | 78 +++++++++++- app/routers/meter_value_v1.py | 22 ++++ app/routers/ocpp_v1.py | 10 +- app/routers/transaction_v1.py | 22 ++++ app/schemas/chargepoint.py | 7 +- app/schemas/id_token.py | 12 +- app/schemas/meter_value.py | 8 +- app/schemas/transaction.py | 17 +-- app/util/websocket_auth_backend.py | 4 +- 16 files changed, 331 insertions(+), 94 deletions(-) create mode 100644 app/routers/meter_value_v1.py create mode 100644 app/routers/transaction_v1.py diff --git a/.gitignore b/.gitignore index 9a5ab14..097cd22 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ **/__pycache__ -simple-ocpp-cs.db \ No newline at end of file +simple-ocpp-cs.db +.env \ No newline at end of file diff --git a/app/main.py b/app/main.py index 5b7dfbd..c4cfbce 100644 --- a/app/main.py +++ b/app/main.py @@ -1,13 +1,20 @@ -from fastapi import FastAPI, Request -from fastapi.exceptions import RequestValidationError -from fastapi.responses import JSONResponse +from dotenv import load_dotenv +from fastapi import FastAPI from starlette.middleware.authentication import AuthenticationMiddleware -import uvicorn + +load_dotenv() from app.database import engine, Base from app.models import * -from app.routers import chargepoint_v1, id_token_v1, ocpp_v1, user_v1 +from app.routers import ( + chargepoint_v1, + id_token_v1, + meter_value_v1, + ocpp_v1, + transaction_v1, + user_v1 +) from app.util.websocket_auth_backend import BasicAuthBackend Base.metadata.create_all(bind=engine) @@ -29,11 +36,10 @@ def create_app(): app.include_router(chargepoint_v1.router, prefix="/v1") app.include_router(id_token_v1.router, prefix="/v1") app.include_router(user_v1.router, prefix="/v1") + app.include_router(meter_value_v1.router, prefix="/v1") + app.include_router(transaction_v1.router, prefix="/v1") app.mount(path="/v1/ocpp", app=create_ocpp_app()) return app app = create_app() - -if __name__ == "__main__": - uvicorn.run(app, host="0.0.0.0", port=8000, log_level="info") diff --git a/app/models/chargepoint.py b/app/models/chargepoint.py index 417431e..a6f6354 100644 --- a/app/models/chargepoint.py +++ b/app/models/chargepoint.py @@ -1,5 +1,5 @@ import uuid -from sqlalchemy import Uuid, Boolean, Column, DateTime, String +from sqlalchemy import ForeignKey, Numeric, Uuid, Boolean, Column, DateTime, String from sqlalchemy.orm import relationship from app.database import Base @@ -8,9 +8,10 @@ class ChargePoint(Base): __tablename__ = "chargepoints" id = Column(Uuid, primary_key=True, default=uuid.uuid4) - friendly_name = Column(String, unique=True, index=True) + identity = Column(String, unique=True, index=True) is_active = Column(Boolean, default=True) password = Column(String) + price = Column(Numeric(10, 2)) last_seen = Column(DateTime, nullable=True) vendor_name = Column(String, nullable=True) @@ -18,4 +19,7 @@ class ChargePoint(Base): serial_number = Column(String, nullable=True) firmware_version = Column(String, nullable=True) + learn_user_id = Column(Uuid, ForeignKey("users.id"), nullable=True) + learn_until = Column(DateTime, nullable=True) + connectors = relationship("Connector", cascade="delete, delete-orphan") diff --git a/app/models/meter_value.py b/app/models/meter_value.py index 9cd0835..e2528ed 100644 --- a/app/models/meter_value.py +++ b/app/models/meter_value.py @@ -4,14 +4,14 @@ from sqlalchemy import Uuid, Column, DateTime, Enum, Float, ForeignKey, String from app.database import Base from app.schemas.meter_value import Measurand, PhaseType -class Transaction(Base): +class MeterValue(Base): __tablename__ = "meter_values" id = Column(Uuid, primary_key=True, default=uuid.uuid4) timestamp = Column(DateTime, index=True) - measurand = Column(Enum(Measurand)) - phase_type = Column(Enum(PhaseType)) - unit = Column(String) + measurand = Column(Enum(Measurand), index=True) + phase_type = Column(Enum(PhaseType), nullable=True) + unit = Column(String, nullable=True) value = Column(Float) - transaction_id = Column(Uuid, ForeignKey("transactions.id"), index=True) \ No newline at end of file + transaction_id = Column(String, ForeignKey("transactions.id"), index=True) \ No newline at end of file diff --git a/app/models/transaction.py b/app/models/transaction.py index 18cc83c..a918430 100644 --- a/app/models/transaction.py +++ b/app/models/transaction.py @@ -1,6 +1,4 @@ -import uuid -from sqlalchemy import Uuid, Column, DateTime, Enum, Float, ForeignKey -from sqlalchemy.orm import relationship, backref +from sqlalchemy import String, Uuid, Column, DateTime, Enum, Numeric, ForeignKey from app.schemas.transaction import TransactionEventTriggerReason, TransactionStatus from app.database import Base @@ -8,13 +6,14 @@ from app.database import Base class Transaction(Base): __tablename__ = "transactions" - id = Column(Uuid, primary_key=True, default=uuid.uuid4) + id = Column(String, primary_key=True) status = Column(Enum(TransactionStatus), index=True) started_at = Column(DateTime, index=True) ended_at = Column(DateTime, nullable=True, index=True) - meter_start = Column(Float) - meter_end = Column(Float, nullable=True) - end_reason = Column(Enum(TransactionEventTriggerReason)) + meter_start = Column(Numeric(10,2)) + meter_end = Column(Numeric(10,2), nullable=True) + end_reason = Column(Enum(TransactionEventTriggerReason), nullable=True) + price = Column(Numeric(10,2)) - connector_id = Column(Uuid, ForeignKey("connectors.id"), index=True) - id_token_id = Column(Uuid, ForeignKey("id_tokens.id"), index= True) + user_id = Column(Uuid, ForeignKey("users.id"), nullable=True, index=True) + chargepoint_id = Column(Uuid, ForeignKey("chargepoints.id"), index=True) diff --git a/app/models/user.py b/app/models/user.py index 339e5f7..80bff7d 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -12,3 +12,4 @@ class User(Base): is_active = Column(Boolean, default=True) id_tokens = relationship("IdToken", back_populates="owner", cascade="delete, delete-orphan") + transactions = relationship("Transaction", cascade="delete, delete-orphan") diff --git a/app/ocpp_proto/chargepoint.py b/app/ocpp_proto/chargepoint.py index 7eab494..18978a0 100644 --- a/app/ocpp_proto/chargepoint.py +++ b/app/ocpp_proto/chargepoint.py @@ -1,24 +1,61 @@ from datetime import datetime, UTC import os +from uuid import UUID from ocpp.routing import on from ocpp.v201 import ChargePoint as cp from ocpp.v201 import call_result from ocpp.v201.datatypes import IdTokenInfoType, IdTokenType -from ocpp.v201.enums import Action, RegistrationStatusType, AuthorizationStatusType, IdTokenType as IdTokenEnumType +from ocpp.v201.enums import Action, RegistrationStatusType, AuthorizationStatusType, IdTokenType as IdTokenEnumType, TransactionEventType from app.database import SessionLocal from app.models.chargepoint import ChargePoint as DbChargePoint -from app.models.connector import Connector -from app.models.id_token import IdToken +from app.models.connector import Connector as DbConnector +from app.models.id_token import IdToken as DbIdToken +from app.models.transaction import Transaction as DbTransaction +from app.models.meter_value import MeterValue as DbMeterValue from app.schemas.connector import ConnectorStatus +from app.schemas.transaction import TransactionStatus, TransactionEventTriggerReason +from app.schemas.meter_value import Measurand, PhaseType class ChargePoint(cp): + + async def __update_last_seen(self): + with SessionLocal() as db: + db_chargepoint = db.query(DbChargePoint).filter(DbChargePoint.identity == self.id).first() + db_chargepoint.last_seen = datetime.now(UTC) + db.commit() + + async def __get_id_token_info(self, id_token) -> IdTokenInfoType: + if id_token["type"] not in ["ISO14443", "ISO15693"]: + return IdTokenInfoType( + status=AuthorizationStatusType.invalid + ) + + with SessionLocal() as db: + db_id_token = db.query(DbIdToken).filter(DbIdToken.token == id_token["id_token"]).first() + if db_id_token == None: + return IdTokenInfoType( + status=AuthorizationStatusType.unknown + ) + if db_id_token.is_active == False: + id_token_info=IdTokenInfoType( + status=AuthorizationStatusType.blocked + ) + else: + id_token_info=IdTokenInfoType( + status=AuthorizationStatusType.accepted, + group_id_token=IdTokenType( + type=IdTokenEnumType.central, + id_token=str(db_id_token.owner_id) + ) + ) + return id_token_info @on(Action.BootNotification) async def on_boot_notification(self, charging_station, **kwargs): with SessionLocal() as db: - db_chargepoint = db.query(DbChargePoint).filter(DbChargePoint.friendly_name == self.id).first() + db_chargepoint = db.query(DbChargePoint).filter(DbChargePoint.identity == self.id).first() db_chargepoint.last_seen = datetime.now(UTC) for key in charging_station.keys(): if key in db_chargepoint.__dict__: @@ -32,10 +69,7 @@ class ChargePoint(cp): @on(Action.Heartbeat) async def on_heartbeat_request(self): - with SessionLocal() as db: - db_chargepoint = db.query(DbChargePoint).filter(DbChargePoint.friendly_name == self.id).first() - db_chargepoint.last_seen = datetime.now(UTC) - db.commit() + await self.__update_last_seen() return call_result.HeartbeatPayload( current_time=datetime.now(UTC).isoformat() ) @@ -43,16 +77,16 @@ class ChargePoint(cp): @on(Action.StatusNotification) async def on_status_notification(self, evse_id: int, connector_id: int, connector_status: str, **kwargs): with SessionLocal() as db: - db_chargepoint = db.query(DbChargePoint).filter(DbChargePoint.friendly_name == self.id).first() + db_chargepoint = db.query(DbChargePoint).filter(DbChargePoint.identity == self.id).first() db_chargepoint.last_seen = datetime.now(UTC) - db_connector = db.query(Connector).filter( - Connector.chargepoint_id == db_chargepoint.id, - Connector.evse == evse_id, - Connector.index == connector_id + db_connector = db.query(DbConnector).filter( + DbConnector.chargepoint_id == db_chargepoint.id, + DbConnector.evse == evse_id, + DbConnector.index == connector_id ).first() if db_connector == None: - db_connector = Connector( + db_connector = DbConnector( chargepoint_id = db_chargepoint.id, evse = evse_id, index = connector_id, @@ -68,39 +102,95 @@ class ChargePoint(cp): @on(Action.Authorize) async def on_authorize(self, id_token, **kwargs): - if id_token["type"] not in ["ISO14443", "ISO15693"]: - return call_result.AuthorizePayload( - id_token_info=IdTokenInfoType( - status=AuthorizationStatusType.invalid - ) - ) - - with SessionLocal() as db: - db_chargepoint = db.query(DbChargePoint).filter(DbChargePoint.friendly_name == self.id).first() - db_chargepoint.last_seen = datetime.now(UTC) - - db_id_token = db.query(IdToken).filter(IdToken.token == id_token["id_token"]).first() - db.commit() - - if db_id_token == None: - id_token_info=IdTokenInfoType( - status=AuthorizationStatusType.unknown - ) - else: - if db_id_token.is_active == False: - id_token_info=IdTokenInfoType( - status=AuthorizationStatusType.blocked - ) - else: - id_token_info=IdTokenInfoType( - status=AuthorizationStatusType.accepted, - group_id_token=IdTokenType( - type=IdTokenEnumType.central, - id_token=str(db_id_token.owner_id) - ) - ) - return call_result.AuthorizePayload(id_token_info) + await self.__update_last_seen() + id_token_info = await self.__get_id_token_info(id_token) + return call_result.AuthorizePayload(id_token_info) @on(Action.TransactionEvent) - async def on_transaction_event(self): + async def on_transaction_event( + self, + event_type, + timestamp, + trigger_reason, + transaction_info, + id_token, + meter_value, + **kwargs + ): + if id_token != None: + id_token_info = await self.__get_id_token_info(id_token) + + with SessionLocal() as db: + chargepoint = db.query(DbChargePoint).filter(DbChargePoint.identity == self.id).first() + chargepoint.last_seen = datetime.now(UTC) + + if event_type == str(TransactionEventType.started): + meter_start=0 + if meter_value != None: + for meter_value_entry in meter_value: + for sampled_value in meter_value_entry['sampled_value']: + if "measurand" in sampled_value.keys(): + if sampled_value['measurand'] == str(Measurand.ENERGY_ACTIVE_IMPORT_REGISTER): + meter_start = sampled_value['value'] + else: + meter_start = sampled_value['value'] + transaction = DbTransaction( + id=transaction_info["transaction_id"], + status=TransactionStatus.ONGOING, + started_at=datetime.fromisoformat(timestamp), + meter_start=meter_start, + price=chargepoint.price, + chargepoint_id=chargepoint.id + ) + if id_token != None: + if id_token_info.status == AuthorizationStatusType.accepted: + transaction.user_id = UUID(id_token_info.group_id_token.id_token) + db.add(transaction) + elif event_type == str(TransactionEventType.updated): + transaction = db.get(DbTransaction, transaction_info["transaction_id"]) + if meter_value != None: + for meter_value_entry in meter_value: + timestamp = datetime.fromisoformat(meter_value_entry['timestamp']) + for sampled_value in meter_value_entry['sampled_value']: + db_meter_value = DbMeterValue() + db_meter_value.transaction_id = transaction.id + db_meter_value.timestamp = timestamp + if "measurand" in sampled_value.keys(): + db_meter_value.measurand = Measurand(sampled_value['measurand']) + else: + db_meter_value.measurand = Measurand.ENERGY_ACTIVE_IMPORT_REGISTER + if "phase" in sampled_value.keys(): + db_meter_value.phase_type = PhaseType(sampled_value['phase']) + if "unit_of_measure" in sampled_value.keys(): + if "unit" in sampled_value['unit_of_measure']: + db_meter_value.unit = sampled_value['unit_of_measure']['unit'] + else: + db_meter_value.unit = "Wh" + db_meter_value.value = sampled_value['value'] + db.add(db_meter_value) + if id_token != None: + if id_token_info.status == AuthorizationStatusType.accepted: + transaction.user_id = UUID(id_token_info.group_id_token.id_token) + elif event_type == str(TransactionEventType.ended): + transaction = db.get(DbTransaction, transaction_info["transaction_id"]) + transaction.status = TransactionStatus.ENDED + transaction.ended_at = datetime.fromisoformat(timestamp) + transaction.end_reason = TransactionEventTriggerReason(trigger_reason) + meter_end=0 + if meter_value != None: + for meter_value_entry in meter_value: + for sampled_value in meter_value_entry['sampled_value']: + if "measurand" in sampled_value.keys(): + if sampled_value['measurand'] == str(Measurand.ENERGY_ACTIVE_IMPORT_REGISTER): + meter_end = sampled_value['value'] + else: + meter_end = sampled_value['value'] + transaction.meter_end = meter_end + if id_token != None: + if id_token_info.status == AuthorizationStatusType.accepted: + transaction.user_id = UUID(id_token_info.group_id_token.id_token) + db.commit() + + if id_token != None: + return call_result.TransactionEventPayload(id_token_info=id_token_info) return call_result.TransactionEventPayload() diff --git a/app/routers/chargepoint_v1.py b/app/routers/chargepoint_v1.py index 2b2031b..ed49fdc 100644 --- a/app/routers/chargepoint_v1.py +++ b/app/routers/chargepoint_v1.py @@ -1,5 +1,6 @@ import random import string +from datetime import datetime, timedelta, UTC from uuid import UUID from fastapi import APIRouter, HTTPException, Security from fastapi.params import Depends @@ -18,7 +19,9 @@ from app.schemas.chargepoint import ( ChargePointResetRequest, ChargePointResetResponse ) +from app.schemas.id_token import IdTokenLearnRequest, IdTokenLearnResponse from app.models.chargepoint import ChargePoint as DbChargePoint +from app.models.user import User as DbUser from app.security import get_api_key router = APIRouter( @@ -77,9 +80,10 @@ async def create_chargepoint( db: Session = Depends(get_db) ): chargepoint_db = DbChargePoint( - friendly_name=chargepoint.friendly_name, + identity=chargepoint.identity, is_active=chargepoint.is_active, - password=''.join(random.choice(string.ascii_letters + string.digits) for i in range(24)) + password=''.join(random.choice(string.ascii_letters + string.digits) for i in range(24)), + price=chargepoint.price ) db.add(chargepoint_db) db.commit() @@ -139,3 +143,73 @@ async def reset_chargepoint( return ChargePointResetResponse(status=response.status) except TimeoutError: raise HTTPException(status_code=503, detail="Chargepoint didn't respond in time.") + +@router.post(path="/{chargepoint_id}/token-learning", status_code=201, response_model=IdTokenLearnResponse) +async def create_id_token_learn_request( + chargepoint_id: UUID, + learn_request: IdTokenLearnRequest, + api_key: str = Security(get_api_key), + db: Session = Depends(get_db) +): + chargepoint = db.get(DbChargePoint, chargepoint_id) + if chargepoint is None: + raise HTTPException(status_code=404, detail="Chargepoint not found") + + owner = db.get(DbUser, learn_request.user_id) + if owner == None: + raise HTTPException(status_code=422, detail=[{ + "loc": ["body", "user_id"], + "msg": "Target user not found", + "type": "invalid_relation" + }]) + + chargepoint.learn_user_id = learn_request.user_id + + if learn_request.until == None: + chargepoint.learn_until = datetime.now(UTC) + timedelta(minutes=5) + else: + chargepoint.learn_until = learn_request.until + + db.commit() + + return IdTokenLearnResponse( + user_id=chargepoint.learn_user_id, + until=chargepoint.learn_until + ) + +@router.get(path="/{chargepoint_id}/token-learning", response_model=IdTokenLearnResponse) +async def get_id_token_learn_request( + chargepoint_id: UUID, + api_key: str = Security(get_api_key), + db: Session = Depends(get_db) +): + chargepoint = db.get(DbChargePoint, chargepoint_id) + if chargepoint is None: + raise HTTPException(status_code=404, detail="Chargepoint not found") + + if chargepoint.learn_user_id == None: + raise HTTPException(status_code=404, detail="No active learning request") + + return IdTokenLearnResponse( + user_id=chargepoint.learn_user_id, + until=chargepoint.learn_until + ) + +@router.delete(path="/{chargepoint_id}/token-learning", response_model=[]) +async def get_id_token_learn_request( + chargepoint_id: UUID, + api_key: str = Security(get_api_key), + db: Session = Depends(get_db) +): + chargepoint = db.get(DbChargePoint, chargepoint_id) + if chargepoint is None: + raise HTTPException(status_code=404, detail="Chargepoint not found") + + if chargepoint.learn_user_id == None: + raise HTTPException(status_code=404, detail="No active learning request") + + chargepoint.learn_user_id = None + chargepoint.learn_until = None + db.commit() + + return [] diff --git a/app/routers/meter_value_v1.py b/app/routers/meter_value_v1.py new file mode 100644 index 0000000..3558600 --- /dev/null +++ b/app/routers/meter_value_v1.py @@ -0,0 +1,22 @@ +from fastapi import APIRouter, Depends +from fastapi.params import Security +from sqlalchemy.orm import Session + +from app.security import get_api_key +from app.database import get_db +from app.schemas.meter_value import MeterValue +from app.models.meter_value import MeterValue as DbMeterValue + +router = APIRouter( + prefix="/meter-values", + tags=["MeterValue (v1)"] +) + +@router.get(path="", response_model=list[MeterValue]) +async def get_meter_values( + skip: int = 0, + limit: int = 20, + api_key: str = Security(get_api_key), + db: Session = Depends(get_db) +): + return db.query(DbMeterValue).offset(skip).limit(limit).all() \ No newline at end of file diff --git a/app/routers/ocpp_v1.py b/app/routers/ocpp_v1.py index 3b8ca41..dac45dd 100644 --- a/app/routers/ocpp_v1.py +++ b/app/routers/ocpp_v1.py @@ -7,19 +7,19 @@ from app.util.websocket_wrapper import WebSocketWrapper router = APIRouter() -@router.websocket("/{charging_station_friendly_name}") +@router.websocket("/{chargepoint_identity}") async def websocket_endpoint( *, websocket: WebSocket, - charging_station_friendly_name: str, + chargepoint_identity: str, ): """ For every new charging station that connects, create a ChargePoint instance and start listening for messages. """ - if (websocket.user.friendly_name != charging_station_friendly_name): + if (websocket.user.identity != chargepoint_identity): raise WebSocketException(code=1008, reason="Username doesn't match chargepoint identifier") - logging.info("Charging station '%s' (%s) connected", charging_station_friendly_name, websocket.user.id) + logging.info("Charging station '%s' (%s) connected", chargepoint_identity, websocket.user.id) # Check protocols try: @@ -43,5 +43,5 @@ async def websocket_endpoint( # Accept connection and begin communication await websocket.accept(subprotocol="ocpp2.0.1") - cp = ChargePoint(charging_station_friendly_name, WebSocketWrapper(websocket)) + cp = ChargePoint(chargepoint_identity, WebSocketWrapper(websocket)) await chargepoint_manager.start(websocket.user.id, cp) diff --git a/app/routers/transaction_v1.py b/app/routers/transaction_v1.py new file mode 100644 index 0000000..66139e1 --- /dev/null +++ b/app/routers/transaction_v1.py @@ -0,0 +1,22 @@ +from fastapi import APIRouter, Depends +from fastapi.params import Security +from sqlalchemy.orm import Session + +from app.security import get_api_key +from app.database import get_db +from app.schemas.transaction import Transaction +from app.models.transaction import Transaction as DbTransaction + +router = APIRouter( + prefix="/transactions", + tags=["Transaction (v1)"] +) + +@router.get(path="", response_model=list[Transaction]) +async def get_transactions( + skip: int = 0, + limit: int = 20, + api_key: str = Security(get_api_key), + db: Session = Depends(get_db) +): + return db.query(DbTransaction).offset(skip).limit(limit).all() \ No newline at end of file diff --git a/app/schemas/chargepoint.py b/app/schemas/chargepoint.py index 7d94118..39444a7 100644 --- a/app/schemas/chargepoint.py +++ b/app/schemas/chargepoint.py @@ -1,4 +1,5 @@ from datetime import datetime +from decimal import Decimal from typing import Optional from uuid import UUID from pydantic import BaseModel @@ -8,12 +9,14 @@ from app.schemas.connector import Connector from ocpp.v201.enums import ResetType, ResetStatusType class ChargePointBase(BaseModel): - friendly_name: str + identity: str is_active: bool + price: Decimal class ChargePointUpdate(BaseModel): - friendly_name: Optional[str] = None + identity: Optional[str] = None is_active: Optional[bool] = None + price: Optional[Decimal]= None class ChargePointCreate(ChargePointBase): pass diff --git a/app/schemas/id_token.py b/app/schemas/id_token.py index b8c5e06..c127a20 100644 --- a/app/schemas/id_token.py +++ b/app/schemas/id_token.py @@ -1,3 +1,4 @@ +from datetime import datetime from typing import Optional from uuid import UUID from pydantic import BaseModel @@ -22,4 +23,13 @@ class IdToken(IdTokenBase): id: UUID class Config: - from_attributes = True \ No newline at end of file + from_attributes = True + +class IdTokenLearnBase(BaseModel): + user_id: UUID + +class IdTokenLearnRequest(IdTokenLearnBase): + until: Optional[datetime] = None + +class IdTokenLearnResponse(IdTokenLearnBase): + until: datetime \ No newline at end of file diff --git a/app/schemas/meter_value.py b/app/schemas/meter_value.py index 5e31278..7a0343e 100644 --- a/app/schemas/meter_value.py +++ b/app/schemas/meter_value.py @@ -1,5 +1,7 @@ from datetime import datetime +from decimal import Decimal import enum +from typing import Optional from uuid import UUID from pydantic import BaseModel @@ -46,9 +48,9 @@ class MeterValue(BaseModel): id: UUID timestamp: datetime measurand: Measurand - phase_type: PhaseType - unit: str - value: float + phase_type: Optional[PhaseType] = None + unit: Optional[str] = None + value: Decimal transaction_id: str class Config: diff --git a/app/schemas/transaction.py b/app/schemas/transaction.py index 6937be7..9c9009b 100644 --- a/app/schemas/transaction.py +++ b/app/schemas/transaction.py @@ -1,5 +1,7 @@ from datetime import datetime +from decimal import Decimal import enum +from typing import Optional from uuid import UUID from pydantic import BaseModel @@ -31,15 +33,16 @@ class TransactionEventTriggerReason(enum.Enum): RESET_COMMAND = "ResetCommand" class Transaction(BaseModel): - id: UUID + id: str status: TransactionStatus started_at: datetime - ended_at: datetime - meter_start: float - meter_end: float - end_reason: TransactionEventTriggerReason - connector_id: str - id_token_id: str + ended_at: Optional[datetime] = None + meter_start: Decimal + meter_end: Optional[Decimal] = None + end_reason: Optional[TransactionEventTriggerReason] = None + price: Decimal + user_id: Optional[UUID] = None + chargepoint_id: UUID class Config: from_attributes = True \ No newline at end of file diff --git a/app/util/websocket_auth_backend.py b/app/util/websocket_auth_backend.py index cf1146a..cab395b 100644 --- a/app/util/websocket_auth_backend.py +++ b/app/util/websocket_auth_backend.py @@ -1,7 +1,7 @@ import base64 import binascii from starlette.authentication import ( - AuthCredentials, AuthenticationBackend, AuthenticationError, SimpleUser + AuthCredentials, AuthenticationBackend, AuthenticationError ) from app.database import SessionLocal @@ -24,7 +24,7 @@ class BasicAuthBackend(AuthenticationBackend): username, _, password = decoded.partition(":") with SessionLocal() as db: - chargepoint = db.query(ChargePoint).filter(ChargePoint.friendly_name == username).first() + chargepoint = db.query(ChargePoint).filter(ChargePoint.identity == username).first() if chargepoint is None: raise AuthenticationError('Invalid basic auth credentials') if chargepoint.password != password: