ocpp icon indicating copy to clipboard operation
ocpp copied to clipboard

Issue with sending msgs from central_system to charge_point

Open cyberrspiritt opened this issue 2 years ago • 7 comments

#81 I'm trying to setup an application using remote_start_transaction msg. I followed the code from the above mentioned issue and it seem to work for me in a charge_point.py script.

central_system.py

import asyncio
import logging
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.path.append('../../../')

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

from main.v16.models.hardware_model import HardwareModel
from main.v16.models.transaction_model import TransactionModel
from main.v16.helpers.common_helper import convert_datetime_str_to_unixtime
from main.v16.libraries.make_api import MakeApi

logging.basicConfig(level=logging.DEBUG)

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


class ChargePoint(cp):
    charge_point = 0
    hardware_model = HardwareModel()
    transaction_model = TransactionModel()
    current_time_utc = datetime.utcnow()

    @on(Action.BootNotification)
    def on_boot_notification(self, charge_point_vendor: str, charge_point_model: str, charge_box_serial_number: str,
                             charge_point_serial_number: str, firmware_version: str, meter_serial_number: str,
                             meter_type: str):
        boot_info = {
            "charge_point_vendor": charge_point_vendor,
            "charge_point_model": charge_point_model,
            "charge_box_serial_number": charge_box_serial_number,
            "charge_point_serial_number": charge_point_serial_number,
            "firmware_version": firmware_version,
            "meter_serial_number": meter_serial_number,
            "meter_type": meter_type
        }
        self.hardware_model.cp_id = self.charge_point
        self.hardware_model.save_on_boot_info(boot_info, self.current_time_utc.timestamp())
        return call_result.BootNotificationPayload(
            current_time=self.current_time_utc.isoformat(),
            interval=10,
            status=RegistrationStatus.accepted
        )

    @on(Action.Heartbeat)
    def on_heartbeat(self):
        current_time_utc_timestamp = self.current_time_utc.timestamp()
        self.hardware_model.cp_id = self.charge_point
        self.hardware_model.update_last_live(current_time_utc_timestamp)
        return call_result.HeartbeatPayload(
            current_time=self.current_time_utc.isoformat()
        )

    async def remote_start_transaction(self):
        request = call.RemoteStartTransactionPayload(
            id_tag='1234',
            connector_id=1
        )
        print('sending remote start')
        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
        )
        print('sending remote stop')
        response = await self.call(request)
        if response.status == RemoteStartStopStatus.accepted:
            print("Stopping transaction")

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.error(
            "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()

    connected.add(websocket)
    print(connected)

    charge_point_id = path.strip('/')
    charge_point = ChargePoint(charge_point_id, websocket)
    try:
        charge_point.charge_point = charge_point_id
        await asyncio.gather(charge_point.start(), charge_point.remote_start_transaction())
    except websockets.ConnectionClosed:
        connected.remove(websocket)
        print("Charge Point disconnected")


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

    logging.info("Server Started listening to new connections...")
    await server.wait_closed()


if __name__ == "__main__":
    # asyncio.run() is used when running this example with Python >= 3.7v
    asyncio.run(main())

The Issue I'm facing is that when i run this script with an acting charge_point script, the messages initiated from central_system gets called and it works. But when the above central_system.py script runs with an actual charger which supports OCPP v1.6, messages are sent from central_system, but I never receive a response from the charge_point.

I receive timeout errors as follows

  ERROR:websockets.server:connection handler failed
  Traceback (most recent call last):
    File "/home/ubuntu/.local/lib/python3.8/site-packages/ocpp/charge_point.py", line 274, in call
      await self._get_specific_response(call.unique_id,
    File "/home/ubuntu/.local/lib/python3.8/site-packages/ocpp/charge_point.py", line 307, in _get_specific_response
      response = await asyncio.wait_for(self._response_queue.get(),
    File "/usr/lib/python3.8/asyncio/tasks.py", line 501, in wait_for
      raise exceptions.TimeoutError()
  asyncio.exceptions.TimeoutError
  
  During handling of the above exception, another exception occurred:
  
  Traceback (most recent call last):
    File "/home/ubuntu/.local/lib/python3.8/site-packages/websockets/legacy/server.py", line 232, in handler
      await self.ws_handler(self)
    File "/home/ubuntu/.local/lib/python3.8/site-packages/websockets/legacy/server.py", line 1146, in _ws_handler
      return await cast(
    File "central_system.py", line 162, in on_connect
      await asyncio.gather(cp.start(), cp.remote_start_transaction())
    File "central_system.py", line 98, in remote_start_transaction
      response = await self.call(request)
    File "/home/ubuntu/.local/lib/python3.8/site-packages/ocpp/charge_point.py", line 277, in call
      raise asyncio.TimeoutError(
  asyncio.exceptions.TimeoutError: Waited 30s for response on [2,"f1a60db6-d387-4c79-8539-e609885a8c46","RemoteStartTransaction",{"idTag":"1234","connectorId":1}].
  DEBUG:websockets.server:! failing connection with code 1011
  DEBUG:websockets.server:= connection is CLOSING
  DEBUG:websockets.server:> CLOSE 1011 (unexpected error) [2 bytes]
  INFO:websockets.server:connection closed

cyberrspiritt avatar Jul 07 '22 10:07 cyberrspiritt

Did you ensure that the charger is plugged in to the vehicle?

soujini avatar Jul 09 '22 13:07 soujini

Yes. we have tried to start the charge via bluetooth as well, and it seem to work. Its just the problem of Remote start transaction or any msgs initiating from central_system.

cyberrspiritt avatar Jul 09 '22 13:07 cyberrspiritt

Are you receiving the boot and status notification from the charger?

soujini avatar Jul 11 '22 04:07 soujini

yes i am. i'm acknowledging and sending back response too. even heartbeat.

cyberrspiritt avatar Jul 11 '22 04:07 cyberrspiritt

do you see anything wrong with my code, in the way im sending back requests for remote start txn?

cyberrspiritt avatar Jul 11 '22 04:07 cyberrspiritt

Its not just with remoteStartTransaction, I've tried other type of messages being initiated from CSMS too, and they all hung up. Tried other ways to send msg from CSMS to CP as well.

I've tried this:

   async def get_configuration(self):
      request = call.GetConfigurationPayload(
          key=[
              'AuthorizeRemoteTxRequests'
          ]
      )
      response = await self.call(request)
      print(response)

  @on(Action.GetConfiguration)
  async def on_get_configuration(self, configuration_key):
      print('ConfigurationValue :: ', configuration_key)

and this:

    async def send_get_config(websocket):
        request = call_result.GetConfigurationPayload(
            configuration_key="AuthorizeRemoteTxRequests"
        )
        return websocket.send(request)

still got a hung up response, timeout after 30 seconds.

    asyncio.exceptions.TimeoutError: Waited 30s for response on [2,"b93c5762-b599-4180-b338-71c827e7bc06","GetConfiguration",{"key":["AuthorizeRemoteTxRequests"]}].
    DEBUG:websockets.server:! failing connection with code 1011
    DEBUG:websockets.server:= connection is CLOSING
    DEBUG:websockets.server:> CLOSE 1011 (unexpected error) [2 bytes]
    INFO:websockets.server:connection closed
    DEBUG:websockets.server:= connection is CLOSED

What could be wrong here?

cyberrspiritt avatar Jul 12 '22 06:07 cyberrspiritt

I've been able to figure out this issue. What was happening was that

await asyncio.gather(charge_point.start(), charge_point.remote_start_transaction())

this part was executing remote_start_transaction before i even received the boot_notification from the charge point.

I tried to sequence the requests in boot_notification itself before sending the response, like this:

await self.remote_start_transaction()

return call_result.BootNotificationPayload(
    current_time=self.current_time_utc.isoformat(),
    interval=10,
    status=RegistrationStatus.accepted
)

This is not the ideal solution, nor what i wanted to achieve. What i do want to do with this sequence call is set a configuration value before sending back response from boot notification:

     request = call.ChangeConfigurationPayload(
         key='AuthorizeRemoteTxRequests',
         value='0'
     )
     response = await self.call(request)
     

The issue in this is that the api call happens, but the response takes a lot of time to be back, some times it comes before timeout of 30 seconds, sometimes it takes more than that. What could be the issue? Also is this the correct way to sequence the api calls?

cyberrspiritt avatar Jul 18 '22 05:07 cyberrspiritt

Having the same issue here, I am using postmen in sending the message to the central system, and the central system would finally call the remotestarttransaction to the charge point. But what happens is that when central system have sent the call message, the charge point did not receive, and therefore the timeout error pop up. I wonder what can be the problem here

Also, if we straight away coded this way

await asyncio.gather(charge_point.start(), charge_point.remote_start_transaction())

Once the central server being run, the remote charging will start automatically, but that is definitely not what was expected. What is needed is where remote start transaction only can be triggered if the central system received message JSON from postmen to instruct central system to call remote start transaction to charge point.

What becomes more confusing is that, if I would like to send a message to the central system using JSON via WS, I would have to fill in the requirement "Action", but what I noticed is most of the time those "Action" in the code structure is more towards "call_result" as return of the function but not "call" as needed to request a call to charge point.

So I coded this way under the chargepoint class @on(Action.RemoteStartTransaction) async def on_remote_start(self, id_tag, connector_id): print("remotely starting") return await self.remote_start_transaction()

And follow by async def remote_start_transaction(self): print("Request Sent!") return await self.call(call.RemoteStartTransactionPayload(id_tag="1",connector_id=1))

And hence i only need to initiaite chargepoint.start only await asyncio.gather(charge_point.start()) Everything was ok except, the problem the central system got no response from the charge point, I wonder what could have been wrong here.

NightTalkerMY avatar Dec 07 '23 09:12 NightTalkerMY

Unfortunately, it is quite difficult to work through problems with custom implementations. This library is generally intended to provide the building blocks to build a charge station and CSMS / Central system. I will close this for now

Jared-Newell-Mobility avatar Dec 18 '23 14:12 Jared-Newell-Mobility