mobilityhouse / ocpp

Python implementation of the Open Charge Point Protocol (OCPP).
MIT License
800 stars 321 forks source link

websocket for an ASGI server? #94

Closed lsaavedr closed 1 year ago

lsaavedr commented 4 years ago

Hello, I'm started to work with starlette. And like they say:

Starlette is a lightweight ASGI framework/toolkit, which is ideal for building high performance asyncio services.

but with ocpp we need the standard websocket library, so I'm searching on what part of websocket library are in use with ocpp and the only two functions that I'm found are recv and send so I did writing an interface to satisfy this with starlette-websocket:

# websocketInterface.py file
from starlette.websockets import WebSocket

class WebSocketInterface():
    def __init__(self, websocket: WebSocket):
        self._websocket = websocket

    async def recv(self):
        receive_msg = await self._websocket.receive_text()
        return receive_msg

    async def send(self, text_message):
        await self._websocket.send_text(text_message)

now this object can be use like an standard websocket object, for example:

# simple starlette server.py file
import asyncio

from starlette.applications import Starlette

from websocketInterface import WebSocketInterface
from central_system import on_connect

app = Starlette()

@app.websocket_route('/{client_id:path}')
async def websocket_handler(websocket):
    interface = WebSocketInterface(websocket)

    await websocket.accept(subprotocol='ocpp1.6')
    await on_connect(interface, websocket.path_params['client_id'])
    await websocket.close()

this is with blob/master/examples/v16/central_system.py. Finally we can run this app with all python ASGI server (for example uvicorn):

myuser@mymachine:~: uvicorn --port 9000 server:app

What do you think? with some like this you could user straightforward all ASGI frameworks?

OrangeTux commented 4 years ago

Thanks for your effort @lsaavedr!

This library tries not to be coupled to a specific implementation of websockets. Exactly for this reason: that you choose whatever implementation you want.

You correctly figured out that a websocket implementation should implement following interface:

As you've shown it's easy to create a small wrapper around a websocket implementation which API is slightly different. In fact I've used this library successfully with Quart.

I think your code can be part of the documentation. What do you think?

lsaavedr commented 4 years ago

mmm... the frame type are bytes or text? because if are bytes the interface must be:

# websocketInterface.py file
from starlette.websockets import WebSocket

class WebSocketInterface():
    def __init__(self, websocket: WebSocket):
        self._websocket = websocket

    async def recv(self):
        receive_msg = await self._websocket.receive_bytes()
        return receive_msg

    async def send(self, msg: bytes):
        await self._websocket.send_bytes(msg)

are you sure?

lsaavedr commented 4 years ago

I'm testing and with text frame type are ok, but with byte frame type fail on:

  File ".../envSc/lib/python3.7/site-packages/ocpp/charge_point.py", line 122, in start
    message = await self._connection.recv()
  File "./websocketInterface.py", line 49, in recv
    receive_msg = await self._websocket.receive_bytes()
  File ".../envSc/lib/python3.7/site-packages/starlette/websockets.py", line 92, in receive_bytes
    return message["bytes"]
KeyError: 'bytes'
OrangeTux commented 4 years ago

You're right. The interface operates on str instead of bytes.

lsaavedr commented 4 years ago

now I have a big doubt... the ocpp1.6 standard are websocket with text frames or binary frames :'(

tropxy commented 4 years ago

now I have a big doubt... the ocpp1.6 standard are websocket with text frames or binary frames :'(

OCPP 1.6J defines the use of UTF-8 for character encoding, so in the end binary frames are used

lsaavedr commented 4 years ago

yes, in the ocpp1.6-j specification (section 4.1.2) say that MUST be UTF-8 characters then the websocket frame type is text! thanks!!!

tropxy commented 4 years ago

yes, in the ocpp1.6-j specification (section 4.1.2) say that MUST be UTF-8 characters then the websocket frame type is text! thanks!!!

Yes, sorry, I meant text :P

lsaavedr commented 4 years ago

A more complete interface with close and exceptions:

# websocketInterface.py
from starlette.websockets import WebSocket, WebSocketDisconnect
from websockets.exceptions import ConnectionClosed

# transform starlette websocket to standard websocket
class WebSocketInterface():
    def __init__(self, websocket: WebSocket):
        self._websocket = websocket

    async def recv(self) -> str:
        try:
            return await self._websocket.receive_text()
        except WebSocketDisconnect as e:
            raise ConnectionClosed(e.code, 'WebSocketInterface')

    async def send(self, msg: str) -> None:
        await self._websocket.send_text(msg)

    async def close(self, code: int, reason: str) -> None:
        await self._websocket.close(code)

regards!

andcan86 commented 3 years ago

I don't see this as part of the official documentation, but I would strongly support the initiative, I think more and more people are going to be interested in using a different websocket implementation. BTW, great work, so glad to have found it. thanks

dinkopehar commented 3 years ago

I was also looking for this. I'm looking to integrate this into FastAPI since it's built on top of Starlette. Great work !