ocpp icon indicating copy to clipboard operation
ocpp copied to clipboard

websocket for an ASGI server?

Open lsaavedr opened this issue 4 years ago • 11 comments

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?

lsaavedr avatar Jun 15 '20 05:06 lsaavedr

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:

  • ~async def recv() -> bytes~ async def recv() -> str
  • ~async def send(msg: bytes)~ async def send(msg: str)

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?

OrangeTux avatar Jun 15 '20 05:06 OrangeTux

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 avatar Jun 15 '20 05:06 lsaavedr

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'

lsaavedr avatar Jun 15 '20 05:06 lsaavedr

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

OrangeTux avatar Jun 15 '20 06:06 OrangeTux

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

lsaavedr avatar Jun 15 '20 06:06 lsaavedr

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

tropxy avatar Jun 15 '20 06:06 tropxy

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

lsaavedr avatar Jun 15 '20 07:06 lsaavedr

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

tropxy avatar Jun 15 '20 07:06 tropxy

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!

lsaavedr avatar Jun 28 '20 02:06 lsaavedr

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

andcan86 avatar May 21 '21 08:05 andcan86

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

dinkopehar avatar Jun 23 '21 08:06 dinkopehar