Giter Club home page Giter Club logo

ocpp's Introduction

https://github.com/mobilityhouse/ocpp/actions/workflows/pull-request.yml/badge.svg?style=svg

OCPP

Python package implementing the JSON version of the Open Charge Point Protocol (OCPP). Currently OCPP 1.6 (errata v4), OCPP 2.0.1 (Edition 2 FINAL, 2022-12-15) are supported.

You can find the documentation on rtd.

The purpose of this library is to provide the building blocks to construct a charging station/charge point and/or charging station management system (CSMS)/central system. The library does not provide a completed solution, as any implementation is specific for its intended use. The documents in this library should be inspected, as these documents provided guidance on how best to build a complete solution.

Note: "OCPP 2.0.1 contains fixes for all the known issues, to date, not only the fixes to the messages. This version replaces OCPP 2.0. OCA advises implementers of OCPP to no longer implement OCPP 2.0 and only use version 2.0.1 going forward."

Installation

You can either the project install from Pypi:

$ pip install ocpp

Or clone the project and install it manually using:

$ pip install .

Quick start

Below you can find examples on how to create a simple OCPP 1.6 or 2.0.1 Central System/CSMS as well as the respective OCPP 1.6 or 2.0.1 Charging Station/Charge Point.

Note

To run these examples the dependency websockets is required! Install it by running:

$ pip install websockets

Charging Station Management System (CSMS) / Central System

The code snippet below creates a simple OCPP 2.0.1 CSMS which is able to handle BootNotification calls. You can find a detailed explanation of the code in the Central System documentation.

import asyncio
import logging
import websockets
from datetime import datetime

from ocpp.routing import on
from ocpp.v201 import ChargePoint as cp
from ocpp.v201 import call_result
from ocpp.v201.enums import RegistrationStatusType

logging.basicConfig(level=logging.INFO)


class ChargePoint(cp):
    @on('BootNotification')
    async def on_boot_notification(self, charging_station, reason, **kwargs):
        return call_result.BootNotificationPayload(
            current_time=datetime.utcnow().isoformat(),
            interval=10,
            status=RegistrationStatusType.accepted
        )


async def on_connect(websocket, path):
    """ For every new charge point that connects, create a ChargePoint
    instance and start listening for messages.
    """
    try:
        requested_protocols = websocket.request_headers[
            'Sec-WebSocket-Protocol']
    except KeyError:
        logging.info("Client hasn't requested any Subprotocol. "
                 "Closing Connection")
        return await websocket.close()

    if websocket.subprotocol:
        logging.info("Protocols Matched: %s", websocket.subprotocol)
    else:
        # In the websockets lib if no subprotocols are supported by the
        # client and the server, it proceeds without a subprotocol,
        # so we have to manually close the connection.
        logging.warning('Protocols Mismatched | Expected Subprotocols: %s,'
                        ' but client supports  %s | Closing connection',
                        websocket.available_subprotocols,
                        requested_protocols)
        return await websocket.close()

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

    await cp.start()


async def main():
    server = await websockets.serve(
        on_connect,
        '0.0.0.0',
        9000,
        subprotocols=['ocpp2.0.1']
    )
    logging.info("WebSocket Server Started")
    await server.wait_closed()

if __name__ == '__main__':
    asyncio.run(main())

Charging Station / Charge point

import asyncio

from ocpp.v201.enums import RegistrationStatusType
import logging
import websockets

from ocpp.v201 import call
from ocpp.v201 import ChargePoint as cp

logging.basicConfig(level=logging.INFO)


class ChargePoint(cp):

    async def send_boot_notification(self):
        request = call.BootNotificationPayload(
            charging_station={
                'model': 'Wallbox XYZ',
                'vendor_name': 'anewone'
            },
            reason="PowerUp"
        )
        response = await self.call(request)

        if response.status == RegistrationStatusType.accepted:
            print("Connected to central system.")


async def main():
    async with websockets.connect(
            'ws://localhost:9000/CP_1',
            subprotocols=['ocpp2.0.1']
    ) as ws:
        cp = ChargePoint('CP_1', ws)

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


if __name__ == '__main__':
    asyncio.run(main())

Debugging

Python's default log level is logging.WARNING. As result most of the logs generated by this package are discarded. To see the log output of this package lower the log level to logging.DEBUG.

import logging
logging.basicConfig(level=logging.DEBUG)

However, this approach defines the log level for the complete logging system. In other words: the log level of all dependencies is set to logging.DEBUG.

To lower the logs for this package only use the following code:

import logging
logging.getLogger('ocpp').setLevel(level=logging.DEBUG)
logging.getLogger('ocpp').addHandler(logging.StreamHandler())

License

Except from the documents in docs/v16 and docs/v201 everything is licensed under MIT. © The Mobility House

The documents in docs/v16 and docs/v201 are licensed under Creative Commons Attribution-NoDerivatives 4.0 International Public License.

ocpp's People

Contributors

adamchainz avatar alexmclarty avatar bengarrett1971 avatar bigduzy avatar chan-vince avatar darander avatar dependabot[bot] avatar drc38 avatar esiebert avatar hugojp1 avatar isabelle-tmh avatar jared-newell-mobility avatar klimaschkas avatar laysauchoa avatar lsaavedr avatar mdwcrft avatar me-sosa avatar orangetux avatar oskrk avatar proelke avatar santiagosalamandri avatar scorpioprise avatar shadowsith avatar shiwei-shen avatar tmh-azinhal avatar tmh-grunwald-markus avatar tropxy avatar villekr avatar wafa-yah avatar will-afs avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ocpp's Issues

Add support for OCPP 2.0

@tropxy Already did some good work on adding OCPP 2.0 the enums and structures needed for OCPP 2.0.

But more work has to be done OCPP 2.0 support can be merged to master.

This issue keeps track of the issues related to that goal:

  • - Add data classes for the payloads for all call and call results. And implement all enums.
  • - Refactor ocpp.v16.charge_point.ChargePoint and abstract out all code that can be shared across both v1.6 and v2.0 chargers. And than implement ocpp.v20.charge_point.ChargePoint based on that shared code.
  • - Create OCPP 2.0 examples for both charge point and central system.
  • - Update documentation about OCPP 2.0

I suggest to create a feature branch ocpp-20-support. As well created individual tickets for each of points stated above. Each ticket will be worked on in it's on branch. Those branches will me merged into ocpp-20-support. When all tickets are done ocpp-20-support can be merged into master.

How to return JSON response to the main task?

class ChargePoint(cp):
  async def send_boot_notification(self):
        request = call.BootNotificationPayload(
                charging_station={
                    'model': 'Wallbox XYZ',
                    'vendor_name': 'anewone'
                },
                reason="PowerUp"
        )
        response = await self.call(request)
         print("Connected to central system.")



async def main():
    async with websockets.connect(
        'ws://localhost:9000/CP',
        subprotocols=['ocpp2.0']
    ) as ws:

        cp = ChargePoint('CP', ws)

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

how to return the response JSON to main for post processing

StopTransactionPayload field type error

v16.call StopTransactionPayload
id_tag: int = None -> id_tag: str = None
Also 'reason' is an optional parameter, therefore:
reason: str -> reason: str = None

Sending a SetChargeProfile call fails with TypeError

Version 0.6.0 forces validation of Calls before the are send to the other end. The validation of call.SetChargingProfilePayload fails with a TypeError. The charging profile contains a value of type decimal.Decimal and this value cannot be serialized to JSON. The problem can be demonstrated like this:

>>> import decimal
>>> import json
>>> >>> json.dumps(decimal.Decimal(3))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/developer/.pyenv/versions/3.7.0/lib/python3.7/json/__init__.py", line 231, in dumps
    return _default_encoder.encode(obj)
  File "/home/developer/.pyenv/versions/3.7.0/lib/python3.7/json/encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/home/developer/.pyenv/versions/3.7.0/lib/python3.7/json/encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
  File "/home/developer/.pyenv/versions/3.7.0/lib/python3.7/json/encoder.py", line 179, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type Decimal is not JSON serializable

Auto Generate Enums Class based on json schemas

The idea is to follow the already implemented generator for the call and call result payloads and generate classes for all the OCPP spec enums. After a quick research, I found that there are schemas like the Firmware Status Notification:

that does not include a definition of the Enumeration and just adds the enums to the property itself which is not the way used in other schemas like in GetVariablesRequest

First, we can build a script to generate Enums based on the schemas like the GetVariablesRequest one and see which ones were generated and if there are Enums missing

How to properly implement RemoteStartTransaction?

Hi, for a couple of days im trying to implement RemoteStartTransaction. I tried to look for examples of how central system sending request on charge point but didn't find anything, so i tried myself and here what i got

central_system.py:

import asyncio
from datetime import datetime
#from simple_charge.config.settings import *
#from simple_charge.config.handlers import *
from pymongo import MongoClient
from bson import ObjectId
import json
from ocpp.routing import on
from ocpp.v16 import ChargePoint as cp
from ocpp.v16.enums import *
from ocpp.v16 import call_result, call

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)


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

    @on(Action.Heartbeat)
    def on_heartbeat(self):
        return call_result.HeartbeatPayload(
            current_time=datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S') + "Z"
        )

    @on(Action.Authorize)
    def on_authorize(self, id_tag):
        return call_result.AuthorizePayload(
            id_tag_info={
                "status": 'Accepted'
            }
        )

    @on(Action.StartTransaction)
    def on_start_transaction(self, connector_id, id_tag, timestamp, meter_start, reservation_id):
        return call_result.StartTransactionPayload(
            id_tag_info={
                "status": 'Accepted'
            },
            transaction_id=int(1)
        )

    @on(Action.StopTransaction)
    def on_stop_transaction(self, transaction_id, id_tag, timestamp, meter_stop):
        return call_result.StopTransactionPayload()

    @on(Action.MeterValues)
    def on_meter_value(self):
        return call_result.MeterValuesPayload()

    @on(Action.StatusNotification)
    def on_status_notification(self, connector_id, error_code, status):
        return call_result.StatusNotificationPayload()

    @on(Action.DataTransfer)
    def on_data_transfer(self, vendor_id, message_id, data):
        return call_result.DataTransferPayload(
            status='Accepted'
        )

    async def remote_start_transaction(self):
        request = call.RemoteStartTransactionPayload(
            id_tag='1'
        )
        response = await self.call(request)
        if response.status == RemoteStartStopStatus.accepted:
            print("Transaction Started!!!")

    async def remote_stop_transaction(self):
        request = call.RemoteStopTransactionPayload(
            transaction_id=1
        )
        response = await self.call(request)

        if response.status == RemoteStartStopStatus.accepted:
            print("Stopping transaction")


connected = set()
clients = dict()
ping_counter = 0
clients_couter = 0


@asyncio.coroutine
async def on_connect(websocket, path):
    charge_point_id = path.strip('/')
    cp = ChargePoint(charge_point_id, websocket)
    try:
        await asyncio.gather(cp.start(), register(websocket, path))
    except websockets.exceptions.ConnectionClosed:
        connected.remove(websocket)
        print("Charge Point disconnected")


@asyncio.coroutine
async def actions(websocket, path):
    charge_point_id = path.strip('/')
    cp = ChargePoint(charge_point_id, websocket)
    if websocket:
        print("B action", websocket)
        await cp.remote_start_transaction()


@asyncio.coroutine
async def register(websocket, path):
    await asyncio.sleep(2)
    connected.add(websocket)
    await actions(websocket, path)

charge_point.py:

import asyncio
from datetime import datetime
from protocols.central_system import actions, on_connect


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.v16 import call, call_result
from ocpp.v16 import ChargePoint as cp
from ocpp.v16.enums import *
from ocpp.routing import on, after


class ChargePoint(cp):
    async def send_boot_notification(self):
        request = call.BootNotificationPayload(
            charge_point_model="Optimus",
            charge_point_vendor="The Mobility House"
        )
        response = await self.call(request)
        if response.status == RegistrationStatus.accepted:
            print("Connected to central system.")

    @on(Action.RemoteStartTransaction)
    def remote_start_transaction(self, id_tag):
        return call_result.RemoteStartTransactionPayload(status=RemoteStartStopStatus.accepted)

    @on(Action.RemoteStopTransaction)
    def remote_stop_transaction(self, transaction_id):
        return call_result.RemoteStopTransactionPayload(status=RemoteStartStopStatus.accepted)


async def main():
    async with websockets.connect(
        'ws://localhost:9000/CP_1',
            subprotocols=['ocpp1.6']
    ) as ws:

        cp = ChargePoint('CP_1', ws)
        await asyncio.gather(cp.start(), cp.send_boot_notification())


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()

run.py:

from protocols.central_system import actions, on_connect, connected
import websockets
import asyncio


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

    await server.wait_closed()


if __name__ == '__main__':
    #loop = asyncio.get_event_loop()
    #asyncio.ensure_future(main())
    #asyncio.ensure_future(actions())
    #loop.run_until_complete(main())
    #loop.run_forever()

    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()

Basicly im able to send from central system id_tag to charge point, but it seems charge point sending his response somewhere else, becouse im getting TimeoutErrore on central system side. Can you please help? Or provide an example of how central system sending requests to charge point?

setup.py file missing

Hi - I want to download and install the package manually and all over internet my research showing this project is missing setup.py file. Please guide if the request is meaningless.

Typo in action enum DiagnosticsStatusNotification

In ocpp/v16/enums.py on line 11:

DiagnosticStatusNotification = "DiagnosticStatusNotification"

It should be

DiagnosticsStatusNotification = "DiagnosticsStatusNotification"

(Diagnostics instead of Diagnostic). This is validated by the naming of the respective json schema in ocpp/v16/schemas/

The same error occurs in ocpp/v16/call_result.py on line 35:
class DiagnosticsStatusNotificationPayload:
and in ocpp/v16/call.py on line 158:
class DiagnosticsStatusNotificationPayload:

The latter causes an error in a call, as the call action gets parsed from the payload class name (ocpp/v16/charge_point.py, line 238):

action=payload.__class__.__name__[:-7],

Schema DiagnosticStatusNotification.json doesn't exist which causes a ValidationError in ocpp\messages.py", line 85, in validate_payload:
ocpp.exceptions.ValidationError: Failed to load validation schema for action 'DiagnosticStatusNotification': [Errno 2] No such file or directory: '.../ocpp/v16/schemas/DiagnosticStatusNotification.json'

How can I implement create a new thread to send heartbeat continuously

How can I implement create a new thread to send heartbeat continuously?
I had tried multiple methods but always stuck in "asyncio". I don't understand "asyncio" clearly really.
Would you write a sample code?

class ChargePoint(cp):

    async def heartbeatFunc(self):

        while True:
            try:
                request = call.HeartbeatPayload() 
                #raise runtime error attached to a different loop                               
                response = await self.call(request)
                await asyncio.sleep(60*5)
            except:
                raise
  
    async def send_boot_notification(self):

        request = call.BootNotificationPayload(
            charge_point_model="sample",
            charge_point_vendor="test"
        )
        response = await self.call(request)
        #heartbeat on main thread ok
        request = call.HeartbeatPayload()
        response = await self.call(request)
        #send beartbeat on new thread time interval continuously ng
        _thread.start_new_thread(self.heartbeatFunc, ())

OCPP 2.0 call payloads RequestStartTransactionPayload and RequestStopTransactionPayload are missing

 async def send_start_transaction(self):
        request = call.RequestStartTransactionPayload(
            id_token =   {
                    "id_token": "123",
                    "type": "KeyCode"
                },
              remote_start_id = 1               
        )

by invoking RequestStartTransaction, i am getting following error

AttributeError: module 'ocpp.v20.call' has no attribute 'RequestStartTransactionPayload'

whether the payload is RequestStartTransactionPayload or StartTransactionPayload...
in ocpp.v20.call StartTransactionPayload is found but in schema it is RequestStartTransactionPayload ..

ChargePoint.call() fails with: " RuntimeError: No active exception to reraise"

v0.4.2 introduces a fix for a potential deadlock, see #46 . Unfortunately the fix introduces a new bigger problem.

        try:
            await self._send(call.to_json())
            response = \
                await self._get_specific_response(call.unique_id,
                                                  self._response_timeout)
        finally:
            self._call_lock.release()
>           raise
E           RuntimeError: No active exception to reraise

ocpp/ocpp/charge_point.py

Lines 252 to 259 in 223636a

try:
await self._send(call.to_json())
response = \
await self._get_specific_response(call.unique_id,
self._response_timeout)
finally:
self._call_lock.release()
raise

Fix validation of payloads containing floats

What are the todo and special actions in validate_payload() supposed to be? Are they to deal with the 'multipleof' problem of jsonschema check?

ocpp/ocpp/messages.py

Lines 118 to 124 in 75dd91f

if message.action in [
'RemoteStartTransaction',
'SetChargingProfile',
'RequestStartTransaction',
]:
# todo: special actions
pass

Besides, I am not sure if it is proper to ask this question here, if not, please feel free to delete this issue.

Thank you.

Charge Point reconnects every 40 seconds

Hi there
I have created a minimal server example with OCPP in python which works. It's basically built like this:

import ...
class ChargePoint(cp):
    async def startcp(self):
        while True:
            message = await self._connection.recv()
            await self.route_message(message)

    @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=100,status="Accepted")

    @on(Action.StatusNotification)
    ....
    ....

    ....


async def send_periodically():
    while True:
        await asyncio.sleep(4)
        # Execute some code periodically...


async def on_connect(websocket, path):
    # Code for new charging pile....


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

if __name__ ==  '__main__':
    asyncio.async(main())
    asyncio.async(send_periodically())
    asyncio.get_event_loop().run_forever()

My problem is that charging piles get disconnected exactly 40 secs after they connect. On the charging point I see no settings correlated with this reconnecting loop. It basically works because instantly after disconnecting, they connect again. But certain functions behave not very smooth with this behavior (eg. if I want to use my function send_periodically to make sure all charging piles are read out periodically. It often fails when hitting exactly the reconnect time of a charging pile.)

Does anyone know this behavior? Could this be kind of an OCPP feature? Am I not correctly handling some kind of message to cause the pile to reconnect (because it does not receive some signal in a certain time?) I can see that it could make sense that the charging piles reconnect in preset intervals to allow reconnection after failures. But every 40 secs seems far too often for me.
Unfortunately I have only one type of charging pile to test this.
Thanks for your inputs!

Can I catch websockets exception?

HI,
I try to catch an exception "Connection reset by peer" when server-client is on heartbeat phase;
unfortunately, I can not get the exception of websockets object via class ChargePoint.
How can I do?
Thanks
I try to modify the code like this

    async def start(self):
        while True:
            try:
                message = await self._connection.recv()
                LOGGER.info('%s: receive message %s', self.id, message)               
                await self.route_message(message)
            except self._connection.exceptions as e: #fail
                print('websockets.exceptions')
            except IOError as err:
                print(self.id, 'disconnected')
            finally:
                pass

error message like this
File "C:\Users\W8-PC\AppData\Local\Programs\Python\Python37\lib\asyncio\selector_events.py", line 801, in _read_ready__data_received data = self._sock.recv(self.max_size) ConnectionResetError: [WinError 10054]
and
websockets.exceptions.ConnectionClosed: WebSocket connection is closed: code = 1006 (connection closed abnormally [internal]), no reason

SetChargingProfile.req construction fails

Hi there,

thanks for your work. So far this library works great for me. I set up the following code to limit my charging station to 8A:

#!/usr/bin/python
# -*- coding: utf-8 -*-

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
import ocpp.v16.enums as enums
import ocpp.messages as messages


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

    @on(Action.StatusNotification)
    def on_status_notification(self, **kwargs):
        print('status notification')
        return call_result.StatusNotificationPayload()

    @on(Action.Heartbeat)
    def on_heartbeat(self, **kwargs):
        print('heartbeat')
        return call_result.HeartbeatPayload(
            current_time=datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S') + "Z"
        )

    async def send_limitation(self, limit):
        response = await self.call(call.SetChargingProfilePayload(
            connector_id=0,
            cs_charging_profiles={
                'chargingProfileId': 1,
                'stackLevel': 0,
                'chargingProfilePurpose': enums.ChargingProfilePurposeType.chargepointmaxprofile,
                'chargingProfileKind': enums.ChargingProfileKindType.absolute,
                'chargingSchedule': {
                    'startSchedule': datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S') + "Z",
                    'chargingRateUnit': enums.ChargingRateUnitType.amps,
                    'chargingSchedulePeriod': [{
                        'startPeriod': 0,
                        'limit': limit
                    }]
                }
            }
        ))

        print(response.status)


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.send_limitation(8.0))


async def main(ip, port):
    server = await websockets.serve(
        on_connect,
        ip,
        port,
        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('192.168.0.22', 9000))
    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('192.168.0.22', 9000))
        loop.close()

Without send_limitiation() everything works as expected.
After adding send_limitiation() the charging station receives it. But it seems like it is not correct. Here is what that chargingStation receives (The charging station provides a web interface with a last message received field):

[2, "9bb54222-ab26-4551-94a1-03b2576885c1", "SetChargingProfile", {
"connectorId": 0,
"csChargingProfiles": {
"chargingProfileId": 1,
"stackLevel": 0,
"chargingProfilePurpose": "ChargePointMaxProfile",
"chargingProfileKind": "Absolute",
"chargingSchedule": {
    "startSchedule": "2019-11-27T13:38:01Z",
    "chargingRateUnit": "A",
    "chargingSchedulePeriod": [{
        "startPeriod": 0,
        "limit": 8.0
    }]
}
}}]: 0, "limit": 8.0}]}}}]

The response from the charging station for this message is:

[3,"9bb54222-ab26-4551-94a1-03b2576885c1",{"status":"Accepted"}]

Did I do something wrong? Thanks for your help!

Help with getting started

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!

Importing certain modules leads to: ImportError: cannot import name 'Call'

Import some modules of ocpp lead to import errors.

E.g.:

$ python -m ocpp.messages.Call
Traceback (most recent call last):
  File "/usr/lib/python3.6/runpy.py", line 183, in _run_module_as_main
    mod_name, mod_spec, code = _get_module_details(mod_name, _Error)
  File "/usr/lib/python3.6/runpy.py", line 109, in _get_module_details
    __import__(pkg_name)
  File "/tmp/.env/lib/python3.6/site-packages/ocpp/messages.py", line 10, in <module>
    from ocpp.v16.enums import MessageType
  File "/tmp/.env/lib/python3.6/site-packages/ocpp/v16/__init__.py", line 1, in <module>
    from ocpp.v16.charge_point import ChargePoint  # noqa: F401
  File "/tmp/.env/lib/python3.6/site-packages/ocpp/v16/charge_point.py", line 9, in <module>
    from ocpp.messages import Call
ImportError: cannot import name 'Call'

Implement errata v4 for OCPP 1.6

In the OCPP 1.6 schemas for MeterValues.req, OCA misspelled Celsius as Celcius.

In the OCPP 1.6 erratas, the recommended action to handle this is:

  • For ChargingStations: send Celsius (ie the correct spelling), Celcius is also allowed.
  • For CentralSystems: accept both Celsius and Celcius.

There are new schema files available from OCA.

How should RemoteStartTransaction be implemented?

Hello, I'm trying to trigger remote start transactions, for that, using the example on the docs, I have:
`class MyChargePoint(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
        )

    # @on(Action.RemoteStartTransaction)
    def remote_start(self, id_tag, **kwargs):
        """
            Send remote start transaction to CP
        """
        return call.RemoteStartTransactionPayload(
            id_tag=id_tag
        )

I implemented other method that calls remote_start under certain conditions, however when this method is called (remote_start) nothing is sent to the CP websocket. Is this normal? How can I check if the remote start action was received by the Charger?
I'm running both ends to simulate the behaviour (Central System and CP) to check everything works fine.

Thanks

Cannot send MeterValuePayload

When I try to send some EVSE meter values to the central system it says that cannot parse with JSON schema. Can somebody write a simple example how I should send meter_values parameter from MeterValuePayload data class.

Connection closing after a while.

I get this error after connecting a simulated charge_point from the examples to our custom server

code = 1011 (unexpected error), no reason

I can't figure out if I'm doing something wrong or that the behavior is normal.

Documentation how CALL messages are handled

Currently the docs are lacking proper documentation about how routing of CALL messages work, how to use the @on() and @after() decorator should be used and how the route map is build.

Add option to disable validation against JSON schemas

TMH has integrated a charge point which isn't fully compliant with the OCPP 1.6 specification. The MeterValues requests send by the charge point contains an 'illegal' measurand. Receiving a MeterValues request will fail with:

Traceback (most recent call last):
File "/venv/lib/python3.7/site-packages/ocpp/messages.py", line 127, in validate_payload
     validate(message.payload, schema)
    File "/venv/lib/python3.7/site-packages/jsonschema/validators.py", line 899, in validate
      raise error
  jsonschema.exceptions.ValidationError: 'Request.Energy' is not one of ['Energy.Active.Export.Register', 'Energy.Active.Import.Register', 'Energy.Reactive.Export.Register', 'Energy.Reactive.Import.Register', 'Energy.Active.Export.Interval', 'Energy.Active.Import.Interval', 'Energy.Reactive.Export.Interval', 'Energy.Reactive.Import.Interval', 'Power.Active.Export', 'Power.Active.Import', 'Power.Offered', 'Power.Reactive.Export', 'Power.Reactive.Import', 'Power.Factor', 'Current.Import', 'Current.Export', 'Current.Offered', 'Voltage', 'Frequency', 'Temperature', 'SoC', 'RPM']

In order to support chargers like this it will be possible to disable JSON schema validation of incoming requests. The validation can be disabled by passing skip_schema_validation=True to a @on decorator. This will disable validation for that specific request and the response.

@on(MeterValues, skip_schema_validation=True)
def on_meter_values(self, *args, **kwargs):
    pass

ocpp.message.unpack can crash with UnicodeDecodeError

If ocpp.messages.unpack is provided with some special data it can crash with a UnicodeDecodeError.

I found this bug while using Hypothesis with the following test:

@given(binary())
def test_unpack_and_pack(data):
    try:
        assert unpack(data) == pack(data)
    except Exception as e:
        assert type(e) in [FormatViolationError, ProtocolError, PropertyConstraintViolationError]

The tests fails if data holds b'\x80.

data = b'\x80'

    @given(binary())
    def test_unpack_and_pack(data):
        try:
            assert unpack(data) == pack(data)
        except Exception as e:
>           assert type(e) in [FormatViolationError, ProtocolError,
                    PropertyConstraintViolationError]
E           AssertionError: assert <class 'UnicodeDecodeError'> in [<class 'ocpp.exceptions.FormatViolationError'>, <class 'ocpp.exceptions.ProtocolError'>, <class 'ocpp.exceptions.PropertyConstraintViolationError'>]
E            +  where <class 'UnicodeDecodeError'> = type(UnicodeDecodeError('utf-8', b'\x80', 0, 1, 'invalid start byte'))

tests/test_messages.py:189: AssertionError
-------------------------------------------------------------------------------- Hypothesis ---------------------------------------------------------------------------------
Falsifying example: test_unpack_and_pack(data=b'\x80')

Add support for Python3.6

Python 3.6 can be supported by installing the dataclasses package from Pypi.

Note that the classifiers in setup.py must be updated to reflect the support for Python 3.6

invoke charge point functions using message queue

I need to invoke charge point functions(like AuthorizeToken) depending on the messages from message queue send by charge point GUI..
like

  mq = sysv_ipc.MessageQueue(1234, sysv_ipc.IPC_CREAT)
   while True:
       message = mq.receive()
       print(message)
       Here need to invoke functions accordingly

is it feasible here?

Payloads field type error

Although Type hints are not utilized in this project yet, I found some potential errors in the field type of payloads. I have double checked all these field types with json shcemas.

call.py

  • GetConfigurationPayload.key: str -> List
  • RemoteStartTransactionPayload.id_tag: int -> str
  • MeterValuesPayload.connector_id: str -> int
    MeterValuesPayload.meter_value: Dict -> List
    MeterValuesPayload.transaction_id: str -> int
  • StopTransactionPayload.transaction_data: Dict -> List

call_result.py

  • GetConfigurationPayload.configuration_key: Dict -> List
    GetConfigurationPayload.unknown_key: str -> List

If these are wrong, please just ignore my issue. Thanks.

Use this package together with Flask

I have developed OCPP client and server with this awesome library. I wanted to add GUI to the client and server. I intend to use React as frontend and Flask as backend for the application.
How to integrate OCPP python client with Flask framework. As the OCPP has its event loop running continuously blocking the flask code. Please help.
git_up

Remove depricated use of "@coroutine" decorator

Running the test for Python 3.8 yields the following warnings:

tests/v16/test_v16_charge_point.py::test_route_message_with_existing_route
tests/v20/test_v20_charge_point.py::test_route_message_with_existing_route
  /home/developer/projects/tmh-ocpp/ocpp/charge_point.py:186: DeprecationWarning: "@coroutine" decorator is deprecated since Python 3.8, use "async def" instead
    response = await asyncio.coroutine(handler)(**snake_case_payload)

tests/v16/test_v16_charge_point.py::test_route_message_with_existing_route
tests/v20/test_v20_charge_point.py::test_route_message_with_existing_route
  /home/developer/.pyenv/versions/3.8.0/envs/ocpp-py38/lib/python3.8/site-packages/asynctest/mock.py:599: DeprecationWarning: "@coroutine" decorator is deprecated since Python 3.8, use "async def" instead
    def proxy():

tests/v16/test_v16_charge_point.py::test_route_message_with_existing_route
tests/v20/test_v20_charge_point.py::test_route_message_with_existing_route
  /home/developer/projects/tmh-ocpp/ocpp/charge_point.py:217: DeprecationWarning: "@coroutine" decorator is deprecated since Python 3.8, use "async def" instead
    asyncio.coroutine(handler)(**snake_case_payload))

The calls to asyncio.coroutine() should be replaced with something else.

Inject payload into handler decorated with `@after`

Currently methods decorated with the @after() decorated don't take any arguments.
This will change. The @after() handler will receive the same arguments as the corresponding @on() handler.

This is a backwards incompatible change. Users of ocpp <= 0.2.2 should modify there @after() handlers so that they can take arguments. Users can use *args and/or **kwargs if they don't want to use the arguments, like:

@after(Action.BootNotification)
def after_boot_notification(self, *args, **kwargs):
    pass

on send_authorize error

i tried with

call.AuthorizePayload({    
    'id_token': '123',
    'type': 'KeyCode'
})

it throws the following error at server

Error while handling request '<Call - unique_id=bb109810-d0ef-44d3-b939-9766fd217d6e, action=Authorize, **payload={'idToken': {'idToken': '123', 'type': 'KeyCode'}}>'**
Traceback (most recent call last):
  File "C:\Users\x\AppData\Local\Programs\Python\Python37-32\lib\site-packages\ocpp\charge_point.py", line 184, in _handle_call
    response = await asyncio.coroutine(handler)(**snake_case_payload)
  File "C:\Users\x\AppData\Local\Programs\Python\Python37-32\lib\asyncio\coroutines.py", line 120, in coro
    res = func(*args, **kw)
  File "C:\Users\x\AppData\Local\Programs\Python\Python37-32\lib\site-packages\ocpp\routing.py", line 31, in inner
    return func(*args, **kwargs)
**TypeError: on_authorize() got an unexpected keyword argument 'id_token'**

Add support for OCPP 2.0 erratta 2

"transactionData": {
      "$ref": "#/definitions/TransactionType"
    },

"required": [
    "eventType",
    "timestamp",
    "triggerReason",
    "seqNo",
    "transactionData"
  ]

Field "transactionData" should be "transactionInfo" in ocpp 2.0. Could you help to update the schema with lastest specification (v2.0.1)? Thanks

Action.Heartbeat has no payload response and causes handler to break

When class handles Action.Heartbeat, it returns no payload, causing charge_point.py to break:

ChargePoint Vendor is: Virtual Charge Point
ChargePoint Model is: Generic
Got heartbeat
Error in connection handler
Traceback (most recent call last):
  File "/home/esiebert/anaconda3/lib/python3.7/site-packages/websockets/server.py", line 195, in handler
    await self.ws_handler(self, path)
  File "./central.py", line 40, in on_connect
    await cp.start()
  File "/home/esiebert/anaconda3/lib/python3.7/site-packages/ocpp/v16/charge_point.py", line 126, in start
    await self.route_message(message)
  File "/home/esiebert/anaconda3/lib/python3.7/site-packages/ocpp/v16/charge_point.py", line 144, in route_message
    await self._handle_call(msg)
  File "/home/esiebert/anaconda3/lib/python3.7/site-packages/ocpp/v16/charge_point.py", line 189, in _handle_call
    temp_response_payload = asdict(response)
  File "/home/esiebert/anaconda3/lib/python3.7/dataclasses.py", line 1043, in asdict
    raise TypeError("asdict() should be called on dataclass instances")
TypeError: asdict() should be called on dataclass instances

The class:

class Central(ChargePoint):
    @on(Action.Heartbeat)
    def on_heartbeat(self, **kwargs):
        print("Got heartbeat")

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.