diff --git a/README.md b/README.md index e8dbadc..6a3d965 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Simple OCCP Central System +# LibreCharge -This is a simple implementation of a basic [OCPP](https://openchargealliance.org/protocols/open-charge-point-protocol/) 2.0.1 compliant central system (backend) for EV charging stations. +LibreCharge is a simple implementation of a basic [OCPP](https://openchargealliance.org/protocols/open-charge-point-protocol/) 2.0.1 compliant central system (backend) for EV charging stations. ## Features @@ -12,5 +12,7 @@ This is a simple implementation of a basic [OCPP](https://openchargealliance.org - ✅ RESTful API ## Tested charging stations + This project has been successfully tested with the following charging stations: + - Alfen Eve Single S-line diff --git a/backend/.gitignore b/backend/.gitignore index 097cd22..f305e97 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,3 +1,4 @@ **/__pycache__ simple-ocpp-cs.db -.env \ No newline at end of file +.env +static \ No newline at end of file diff --git a/backend/app/main.py b/backend/app/main.py index 5a69345..c3bf377 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1,6 +1,8 @@ +import os from dotenv import load_dotenv from fastapi import APIRouter, FastAPI from fastapi.middleware.cors import CORSMiddleware +from fastapi.staticfiles import StaticFiles from starlette.middleware.authentication import AuthenticationMiddleware load_dotenv() @@ -17,25 +19,46 @@ from app.routers import ( ) from app.security.websocket_auth_backend import BasicAuthBackend -def create_ocpp_app(): - app_ocpp = FastAPI( - responses={404: {"description": "Not found"}}, - ) - app_ocpp.include_router(ocpp_v1.router) - app_ocpp.add_middleware(AuthenticationMiddleware, backend=BasicAuthBackend()) - - return app_ocpp - def create_app(): - app = FastAPI( - title="simple-ocpp-cs", - version="0.2.0", - summary="Simple implementation of a basic OCPP 2.0.1 compliant central system (backend) for EV charging stations", - responses={404: {"description": "Not found"}}, + # Common app config + title="LibreCharge" + version="0.2.0" + summary="Simple implementation of a basic OCPP 2.0.1 compliant central system (backend) for EV charging stations" + responses={404: {"description": "Not found"}} + + # Root FastAPI app + root_app = FastAPI( + title=title, + version=version, + summary=summary, + responses=responses, + docs_url=None, + redoc_url=None, + ) + + # FastAPI app for OCPP handler + ocpp_app = FastAPI( + title=title, + version=version, + summary=summary, + responses=responses, + docs_url=None, + redoc_url=None, + ) + ocpp_app.include_router(ocpp_v1.router) + ocpp_app.add_middleware(AuthenticationMiddleware, backend=BasicAuthBackend()) + root_app.mount(path="/v1/ocpp", app=ocpp_app) + + # FastAPI app for REST API + api_app = FastAPI( + title=title, + version=version, + summary=summary, + responses=responses, ) api_v1_router = APIRouter( - prefix="/api/v1" + prefix="/v1" ) api_v1_router.include_router(auth_v1.router) api_v1_router.include_router(chargepoint_v1.router) @@ -45,15 +68,20 @@ def create_app(): api_v1_router.include_router(meter_value_v1.router) api_v1_router.include_router(transaction_v1.router) - app.include_router(api_v1_router) - app.mount(path="/v1/ocpp", app=create_ocpp_app()) + api_app.include_router(api_v1_router) + root_app.mount(path="/api", app=api_app) + + # Serve static files if existent + if os.path.isdir('static'): + static_files = StaticFiles(directory="static", html=True) + root_app.mount(path="/", app=static_files) origins = [ "http://localhost", "http://localhost:5173", ] - app.add_middleware( + root_app.add_middleware( CORSMiddleware, allow_origins=origins, allow_credentials=True, @@ -61,6 +89,6 @@ def create_app(): allow_headers=["*"], ) - return app + return root_app app = create_app() diff --git a/frontend/src/lib/axios.svelte.ts b/frontend/src/lib/axios.svelte.ts index d086718..f176748 100644 --- a/frontend/src/lib/axios.svelte.ts +++ b/frontend/src/lib/axios.svelte.ts @@ -55,7 +55,7 @@ function createTokenRefreshInterceptor() { // Retry failed, clean up and reject the promise clearLoginState(); axios.defaults.headers.common['Authorization'] = ""; - goto('/login?reauth') + goto('#/login?reauth') return Promise.reject(retryError); }) .finally(createTokenRefreshInterceptor); // Re-attach interceptor for future requests @@ -101,7 +101,7 @@ export const logout = function() { .then(() => { clearLoginState(); axios.defaults.headers.common['Authorization'] = ""; - goto('/login?logout') + goto('#/login?logout') }); } diff --git a/frontend/src/lib/component/TransactionTable.svelte b/frontend/src/lib/component/TransactionTable.svelte index 6b5a73e..d9c9e86 100644 --- a/frontend/src/lib/component/TransactionTable.svelte +++ b/frontend/src/lib/component/TransactionTable.svelte @@ -59,7 +59,7 @@ - + {transaction.chargepoint.identity} @@ -71,7 +71,7 @@ >{((transaction.meter_end - transaction.meter_start) * transaction.price).toFixed(2)} € - + {$i18n.t('common:transactionTable.detailButton')} diff --git a/frontend/src/routes/(navbar)/+layout.svelte b/frontend/src/routes/(navbar)/+layout.svelte index f603e37..bcc0d13 100644 --- a/frontend/src/routes/(navbar)/+layout.svelte +++ b/frontend/src/routes/(navbar)/+layout.svelte @@ -7,7 +7,7 @@ let { children } = $props() if (!$persistentSettings.loggedIn) { - goto('/login') + goto('#/login') } let drawerOpen = $state(false) @@ -44,7 +44,7 @@ onclick={() => { drawerOpen = !drawerOpen }} - href="/idtoken" + href="#/idtoken" > {$i18n.t('common:navbar.link.idtoken')} @@ -55,7 +55,7 @@ onclick={() => { drawerOpen = !drawerOpen }} - href="/transaction" + href="#/transaction" > {$i18n.t('common:navbar.link.transaction')} @@ -66,7 +66,7 @@ onclick={() => { drawerOpen = !drawerOpen }} - href="/chargepoint" + href="#/chargepoint" > {$i18n.t('common:navbar.link.chargepoint')} @@ -87,7 +87,7 @@ tabindex="-1" class="menu menu-sm dropdown-content bg-base-100 rounded-box z-1 mt-3 w-52 p-2 shadow" > -
  • {$i18n.t('common:navbar.link.profile')}
  • +
  • {$i18n.t('common:navbar.link.profile')}
  • diff --git a/frontend/src/routes/(navbar)/chargepoint/+page.svelte b/frontend/src/routes/(navbar)/chargepoint/+page.svelte index 3239812..aba4a0f 100644 --- a/frontend/src/routes/(navbar)/chargepoint/+page.svelte +++ b/frontend/src/routes/(navbar)/chargepoint/+page.svelte @@ -81,7 +81,7 @@ {chargepoint.price} € - + {$i18n.t('common:transactionTable.detailButton')} diff --git a/frontend/src/routes/+layout.ts b/frontend/src/routes/+layout.ts deleted file mode 100644 index 62ad4e4..0000000 --- a/frontend/src/routes/+layout.ts +++ /dev/null @@ -1 +0,0 @@ -export const ssr = false diff --git a/frontend/svelte.config.js b/frontend/svelte.config.js index dad5e99..d3429f9 100644 --- a/frontend/svelte.config.js +++ b/frontend/svelte.config.js @@ -11,6 +11,7 @@ const config = { adapter: adapter({ fallback: 'index.html', }), + router: { type: 'hash' }, }, }