villekr / ocpp-asgi

ocpp-asgi extends ocpp library to provide ASGI compliant interface for implementing OCPP Central System.
MIT License
20 stars 4 forks source link

Send OCPP message through RouterContext #3

Closed ChaitanyaYeole02 closed 1 year ago

ChaitanyaYeole02 commented 2 years ago

I want to send RemoteStartTransaction from a function called in on_connect. But as my context: RouterContext and send is implemented in HandlerContext. What can I do?

ChaitanyaYeole02 commented 2 years ago

@villekr To date, I have started a thread inside my on_connect. Inside the thread I have created an async loop that triggers remote_start_transaction. When I call my remote_start_transaction inside the thread, it waits for the response for a completed 30 seconds and when I call my remote_start_transaction inside any after action I get my response within seconds.

_onconnect

   async def on_connect(self, context: RouterContext) -> bool:  
        print( 
            f"(CentralSystem) Charging Station id: {context.charging_station_id} subprotocol: "
            f"{context.subprotocol} connected."
            # noqa: E501
        ) 
        # You can inspect context.scope["headers"] and perform eg. basic authentication

        ocpp_id = context.charging_station_id
        print('Connected to:', ocpp_id)

        thread = threading.Thread(target=self.some_function, args=(context))
        thread.start()
        return True 

_somefunction

    def some_function(self, context: RouterContext):
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)

        loop.run_until_complete(self.async_function(context,))
        loop.close()

_asyncfunction

    async def async_function(self, context: RouterContext):
            request = call.RemoteStartTransactionPayload(id_tag=user_id, connector_id=connector_id)
            async with context.call_lock:
                response = await v16_provisioning_router.call(message=request, context=context)
villekr commented 1 year ago

I think the simplest is to use asyncio.create_task to run any logic when Charging Station connects. I briefly tested with the following code in class CentralSystem(ASGIApplication):

    async def on_connect(self, context: RouterContext) -> bool:
        print(
            f"(CentralSystem) Charging Station id: {context.charging_station_id} subprotocol: {context.subprotocol} connected."  # noqa: E501
        )
        # You can inspect context.scope["headers"] and perform e.g. basic authentication
        allow_connection = True

        if allow_connection:
            # Create task for running any logic that happens during connection setup
            asyncio.create_task(self.after_on_connect(context))
        return allow_connection
    async def after_on_connect(self, context: RouterContext):
        # Example on how to send message to Charging Station e.g. after connection setup
        await asyncio.sleep(1)  # Give Charging Station some time once connected
        if context.subprotocol == Subprotocol.ocpp16.value:
            message = call16.RemoteStartTransactionPayload(id_tag="abc")
            router = self.routers[context.subprotocol]
        elif context.subprotocol == Subprotocol.ocpp20.value:
            id_token = {"idToken": "abc", "type": "Central"}
            message = call20.RequestStartTransactionPayload(
                id_token=id_token, remote_start_id=123
            )
            router = self.routers[context.subprotocol]
        elif context.subprotocol == Subprotocol.ocpp201.value:
            id_token = {"idToken": "abc", "type": "Central"}
            message = call201.RequestStartTransactionPayload(
                id_token=id_token, remote_start_id=123
            )
            router = self.routers[context.subprotocol]
        else:
            raise ValueError(f"Unknown sub-protocol value: {context.subprotocol=}")
        response = await router.call(message=message, context=context)
        print(f"(Central System) Charging Station {id=} {response=}")

This does look a little strange, why CentralSystem should care about different ocpp protocol versions. So alternatively you could implement some generic functions in routers for all Central System initiated ocpp messages.