ocpp icon indicating copy to clipboard operation
ocpp copied to clipboard

Examples on integrating with Django Channels ?

Open dinkopehar opened this issue 3 years ago • 6 comments

Are there any examples on integrating this library with Django Channels ?

I see examples on how to integrate with WebSockets library, but Django Channels and it's "Consumers" would be also nice. At least small example on how to create few handlers on Consumer. I'm trying something, but it turned out to be a really long method on JsonWebsocketConsumer

Thank you.

dinkopehar avatar Feb 04 '22 22:02 dinkopehar

The connection argument of ChargePoint must satisfy the interface async def send(msg: str) and async def recv() -> str. See https://github.com/mobilityhouse/ocpp/issues/94#issuecomment-643911096 . I don't know Django Channels, but I guess it would be easy to create a wrapper around this type that satisfies the interface.

OrangeTux avatar Feb 09 '22 14:02 OrangeTux

In Django Channels, there is Consumer. It looks in basic form like this:

class Consumer(WebsocketConsumer):
    def connect(self):
        self.accept() # Accept connection (for example only ocpp1.6)

    def receive(self, text_data):
        payload = json.loads(text_data)

        self.send(text_data=json.dumps({
            'status': "OK"
        }))

I'm looking into something that could in Consumer's receive method, when payload is loaded, to validate automatically payload if it matches certain JSON Schema of OCPP messages and create a response. Something like:

    ...
    def receive(self, text_data):
        payload = json.loads(text_data)
        
        # Pseudocode only
        if BootNotification(payload).validate():
             # Check fields
             # Create response
             response = BootNotificationResponse()
             self.send(text_data=json.dumps(response))
        elif HeartBeat(payload).validate():
            pass
        else:
            self.send(text_data=json.dumps({"status": "NOT_OK"}))

So I'm just looking to validate payloads with ocpp library. All examples provide websockets module examples with decorators, but I'm only looking to validate received payload and create a response using ocpp library.

NOTE: Sorry for long response, I saw your comment but didn't had time to respond properly due to some troubles. Feel free to respond when you have time.

dinkopehar avatar Mar 05 '22 07:03 dinkopehar

I managed to create basic "Consumer" for OCPP. If anyone searches for Django Channels integration, I have used something like (only "ocpp1.6" is accepted):

from dataclasses import asdict
from datetime import datetime

from channels.generic.websocket import JsonWebsocketConsumer
from ocpp.messages import pack, unpack
from ocpp.v16 import call_result, enums


class ChargepointConsumer(JsonWebsocketConsumer):
    def connect(self):
        # TODO: Check if CP exists.

        if "ocpp1.6" not in self.scope["subprotocols"]:
            self.close(1002)

        self.accept("ocpp1.6")

    def decode_json(self, text_data):
        return unpack(text_data)

    def encode_json(self, content):
        return pack(content)

    def receive_json(self, content):

        # For related "Call", create proper "CallResult".
        if content.action == "BootNotification":
            # Handle received payload.
            # TODO: Save to database ?

            # Create result payload.
            payload = call_result.BootNotificationPayload(
                current_time=datetime.utcnow().isoformat(),
                interval=10,
                status=enums.RegistrationStatus.accepted,
            )
        elif content.action == "Heartbeat":
            # Create result payload.
            payload = call_result.HeartbeatPayload(
                current_time=datetime.utcnow().isoformat()
            )

        # Send "CallResult"
        self.send_json(content.create_call_result(asdict(payload)))

There are some pieces missing, but if anyone has better approach, feel free to write.

dinkopehar avatar Mar 08 '22 18:03 dinkopehar

Just picking up where you left off. Have you managed making calls to the charge point ?

g-a-pet avatar Aug 03 '22 05:08 g-a-pet

Just picking up where you left off. Have you managed making calls to the charge point ?

@Gabriel-Petrescu Yes, I've managed to integrate with this library Django-Channels. What this OCPP library provides is:

  1. JSON Schemas converted to Data Classes (for python)
  2. Integrating with WebSockets python library

If you decide to use Django Channel, you will only benefit from Data Classes for validation of request (Call) and sending proper response (CallResult) back to Charger. Receving a payload from charger and responding back to charger was easy. However, to trigger from Central System to Charger, that was also possible, but I didn't managed to work on how to trigger an operation, wait for response from charger and continue. I did some prevalidation steps to make sure that charger will handle properly message to be sent, but I'm not sure if this was best approach or was Django Channels designed to work like that. If you decide to use WebSockets, you will benefit from this library. Your WebSocket server will run as side service and there is article on how to integrate WebSockets library with Django.


If I would start over again, I would maybe choose WebSockets because a lot of issues or problems are already solved on integrating with WebSockets. As for Django channels, I didn't had any problem integrating it, it is also great extension and really well documented. The main downsides of Channels were that I didn't found some articles or examples of someone else using it in production somehow.

dinkopehar avatar Aug 04 '22 14:08 dinkopehar

@dinko-pehar Thanks, I ran into the same problems. Mainly sending calls from Central system to charger. I also found a pretty rudimentary solution. Gonna leave this here if someone else ever runs into this problem:

        #Check if connection from CP 
        if "ocpp1.6" in self.scope["subprotocols"]:
            #Check for protocol
            self.id=self.scope['url_route']['kwargs']['device_id']
            #Verify evcharger against database and add to private channel layer
            try:
                evcharger=EvCharger.objects.get(cpsn=self.id)
                self.accept("ocpp1.6")
                evcharger.active=True
                evcharger.save()
                #Add to CP Group
                async_to_sync(self.channel_layer.group_add)(
                self.id,
                self.channel_name,
                )
            except:
                LOGGER.info('ocpp1.6 device attempted to connect but was not in registered database')
                self.close()
            LOGGER.info('Connected device: %s', self.scope['url_route']['kwargs']['device_id'])
        #check if it is a user
        else:
            self.user=self.scope['user']
            if self.user.is_authenticated:
                self.accept()
                #Add to CP Group for  device id 
                async_to_sync(self.channel_layer.group_add)(
                self.scope['url_route']['kwargs']['device_id'],
                self.channel_name,
                )
                #Add to Browser group device id + browser
                async_to_sync(self.channel_layer.group_add)(
                self.scope['url_route']['kwargs']['device_id']+"_browser",
                self.channel_name,
                ) 

I then route messages based of conditions and comparing isinstance(content,Call) ,isinstance(content,CallResult) , etc..

g-a-pet avatar Aug 05 '22 11:08 g-a-pet

@g-a-pet could you please share an example, I have been trying to do the same thing but I'm stuck

Sadeeed avatar Sep 28 '23 08:09 Sadeeed