mobilityhouse / ocpp

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

Help with getting started #85

Closed jacobsorme closed 4 years ago

jacobsorme commented 4 years ago

I have my charger connected via ethernet to my laptop. I can verify that it is connected, via the charger configuration page. If I want to get started with OCPP, what is the next step? How could I read some variable such as status or voltage from the charger into my program? What settings do I need to set up on the charger configuration page? I can set some values on the configuration page, such as model name or vendor. What values should I put there?

I am not new to Python, but new to chargers and OCPP. Thanks!

OrangeTux commented 4 years ago

Hello @jacobsorme, it totally depends to what you want from the connection with your charge point. Also which OCPP version does your charge point use?

jacobsorme commented 4 years ago

For starters I would like to read some data, some variable. The charger is OCPP version 1.6. In README a Central system and a Charge point is mentioned, what is the difference there? Could some comment be added to the section about Charge point?

Would the Central system refer to something running on my laptop? I am not now interested continuously listening for new chargers, but rather to talk with the one I have. Thank you.

OrangeTux commented 4 years ago

You already mentioned the 2 parties involved in the OCPP protocol. First you have the charge point (CP). The CP creates a websocket connection to the central system (CSMS). A CP can only connect to 1 CSMS, while 1 CSMS can have many CPs connected.

So you're correct by stating that you need you want CSMS running on your laptop.

A sequence of message sequence start with a call (or request) being send by one party. The other party responds with a call result (response) or a call error (erroneous response). You can read more about the message types in chapter 4 of the OCPP-J 1.6 specification.

Most calls can only be send initiated from one of the two sides. For example only the CP can send a BootNotification to the CSMS. While a Reset is only allowed to be send by the CSMS to the CP.

Back to your question of reading variables. The CSMS can use a call.GetConfigurationPayload to read configuration settings of the CP. Upon receiving the CP will respond with a call_result.GetConfigurationPayload

For an example on how to send a this request you can see this code:

import asyncio
from datetime import datetime

try:
    import websockets
except ModuleNotFoundError:
    print("This example relies on the 'websockets' package.")
    print("Please install it by running: ")
    print()
    print(" $ pip install websockets")
    import sys
    sys.exit(1)

from ocpp.routing import on
from ocpp.v16 import ChargePoint as cp
from ocpp.v16.enums import Action, RegistrationStatus
from ocpp.v16 import call_result, call

class ChargePoint(cp):
    @on(Action.BootNotification)
    def on_boot_notitication(self, charge_point_vendor, charge_point_model, **kwargs):
        return call_result.BootNotificationPayload(
        current_time=datetime.utcnow().isoformat(),
        interval=10,
        status=RegistrationStatus.accepted
    )

    async def get_configuration(self):
        response  = await self.call(call.GetConfigurationPayload())

        # Setting is a dict with 3 keys: key, value and readonly. 
    # See section 7.29 in https://github.com/mobilityhouse/ocpp/blob/master/docs/v16/ocpp-1.6.pdf
        for setting in response.configuration_key:
            print(f"{setting['key']}: {setting['value']}")

async def on_connect(websocket, path):
    """ For every new charge point that connects, create a ChargePoint instance
    and start listening for messages.

    """
    charge_point_id = path.strip('/')
    cp = ChargePoint(charge_point_id, websocket)

    await asyncio.gather(cp.start(), cp.get_configuration())

async def main():
    server = await websockets.serve(
        on_connect,
        '0.0.0.0',
        9000,
        subprotocols=['ocpp1.6']
    )

    await server.wait_closed()

if __name__ == '__main__':
    try:
        # asyncio.run() is used when running this example with Python 3.7 and
        # higher.
        asyncio.run(main())
    except AttributeError:
        # For Python 3.6 a bit more code is required to run the main() task on
        # an event loop.
        loop = asyncio.get_event_loop()
        loop.run_until_complete(main())
        loop.close()
OrangeTux commented 4 years ago

Another set of messages you might be interested in are:

A transaction is a charging session. It begins with a StartTransaction, followed by 0 or more MeterValues. The charging sessions ends with a StopTransaction.

Al these 3 messages are initiated by the CP.

The StartTransaction call and StopTransaction call contains readings from the electric meter inside the CP at the time of start and stop. So you can use those metrics to calculate the amount of energy exchanged during the charging session. See section 3.3, 6.45 and 6.49 of the OCPP specification.

The MeterValues calls are a bit more comples. These messages contain the charge metrics sampled at a certain point in time. This call could be used to show display the charge rate over time in a graph. An example of a MeterValue call is:

[
  2,
  "985",
  "MeterValues",
  {
    "connectorId": 1,
    "transactionId": 4866398,
    "meterValue": [
      {
        "timestamp": "2020-01-10T09:53:46Z",
        "sampledValue": [
          {
            "value": "3677.200",
            "context": "Sample.Periodic",
            "measurand": "Power.Active.Import",
            "location": "Outlet",
            "unit": "W"
          },
          {
            "value": "111853.000",
            "context": "Sample.Periodic",
            "measurand": "Energy.Active.Import.Register",
            "location": "Outlet",
            "unit": "Wh"
          },
          {
            "value": "0.000",
            "context": "Sample.Periodic",
            "measurand": "Current.Import",
            "location": "Outlet",
            "unit": "A",
            "phase": "L1"
          },
          {
            "value": "0.000",
            "context": "Sample.Periodic",
            "measurand": "Current.Import",
            "location": "Outlet",
            "unit": "A",
            "phase": "L2"
          },
          {
            "value": "16.054",
            "context": "Sample.Periodic",
            "measurand": "Current.Import",
            "location": "Outlet",
            "unit": "A",
            "phase": "L3"
          }
        ]
      }
    ]
  }
]

Different CPs send different metrics, but most CPs can send you the current per phase and amount of energy transferred during the current charging session.

jacobsorme commented 4 years ago

Thank you very much for the replies. I will look into the specification of OCPP you mentioned as well.

The CP creates a websocket connection to the central system

How is this done? How does the charger find the Central system running on my laptop? Is this central system always needed? I know websocket is "persistent", that the connection is kept alive for longer than one request+response, right? Does this mean that I can not do something like a sort of HTTP request/response, but rather I have to set up the connection first?

async def on_connect(websocket, path):
    """ For every new charge point that connects, create a ChargePoint instance
    and start listening for messages.
    """
    # ... 

async def main():
    server = await websockets.serve(
        on_connect,
        '0.0.0.0',
        9000,
        subprotocols=['ocpp1.6']
    )

I don't really understand how a charge point would go about connecting to me. In the code there it looks like the program is initiated by someone connecting. Or is the IP and port there the address of the CP, the charger?

… message sequence start with a call (or request) being send by one party.

In the code example you give, is it the CP that sends the starting call or request?

Ossada commented 4 years ago

Your intuition is correct, CP is the one that initiates the connection. Usually the charger has some web interface where you can specify the OCPP server address. It is not always clear how to set this, so consult your charger's manual (for example, with Schneider chargers you have to first change the "type" of the charger from standalone to managed so that the option to set the OCPP server address even appears).
Since the connection is initiated by the CP, the first message (BootNotification) is also send by the CP. After that, you should get a HeartbeatNotification every 10 seconds (if using the above code, where interval=10).

OrangeTux commented 4 years ago

I consider this question answered and therefore close the issue. Feel free to reopen if needed.

jacobsorme commented 4 years ago

Thank you all for great answers. I think I understand the principles and mindset of OCPP. Will look more into this and try it out!

garima8 commented 3 years ago

You already mentioned the 2 parties involved in the OCPP protocol. First you have the charge point (CP). The CP creates a websocket connection to the central system (CSMS). A CP can only connect to 1 CSMS, while 1 CSMS can have many CPs connected.

So you're correct by stating that you need you want CSMS running on your laptop.

A sequence of message sequence start with a call (or request) being send by one party. The other party responds with a call result (response) or a call error (erroneous response). You can read more about the message types in chapter 4 of the OCPP-J 1.6 specification.

Most calls can only be send initiated from one of the two sides. For example only the CP can send a BootNotification to the CSMS. While a Reset is only allowed to be send by the CSMS to the CP.

Back to your question of reading variables. The CSMS can use a call.GetConfigurationPayload to read configuration settings of the CP. Upon receiving the CP will respond with a call_result.GetConfigurationPayload

For an example on how to send a this request you can see this code:

import asyncio
from datetime import datetime

try:
    import websockets
except ModuleNotFoundError:
    print("This example relies on the 'websockets' package.")
    print("Please install it by running: ")
    print()
    print(" $ pip install websockets")
    import sys
    sys.exit(1)

from ocpp.routing import on
from ocpp.v16 import ChargePoint as cp
from ocpp.v16.enums import Action, RegistrationStatus
from ocpp.v16 import call_result, call

class ChargePoint(cp):
    @on(Action.BootNotification)
    def on_boot_notitication(self, charge_point_vendor, charge_point_model, **kwargs):
        return call_result.BootNotificationPayload(
      current_time=datetime.utcnow().isoformat(),
      interval=10,
      status=RegistrationStatus.accepted
  )

    async def get_configuration(self):
        response  = await self.call(call.GetConfigurationPayload())

        # Setting is a dict with 3 keys: key, value and readonly. 
  # See section 7.29 in https://github.com/mobilityhouse/ocpp/blob/master/docs/v16/ocpp-1.6.pdf
        for setting in response.configuration_key:
            print(f"{setting['key']}: {setting['value']}")

async def on_connect(websocket, path):
    """ For every new charge point that connects, create a ChargePoint instance
    and start listening for messages.

    """
    charge_point_id = path.strip('/')
    cp = ChargePoint(charge_point_id, websocket)

    await asyncio.gather(cp.start(), cp.get_configuration())

async def main():
    server = await websockets.serve(
        on_connect,
        '0.0.0.0',
        9000,
        subprotocols=['ocpp1.6']
    )

    await server.wait_closed()

if __name__ == '__main__':
    try:
        # asyncio.run() is used when running this example with Python 3.7 and
        # higher.
        asyncio.run(main())
    except AttributeError:
        # For Python 3.6 a bit more code is required to run the main() task on
        # an event loop.
        loop = asyncio.get_event_loop()
        loop.run_until_complete(main())
        loop.close()

This didn't work for me, Is there a way to not call cp.get_configuration() with cp.start(). But call it only when required.

quentinalt-dintec commented 5 months ago

I cannot get my CP to connect to my CSMS, I do not know what to set in the Server URL on my configuration interface of my CP :

image

I tried a lot of things such as :

Some help would be very much appreciated !