mobilityhouse / ocpp

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

API doesn't go to CentralSystem Reset Function #263

Closed deepakEnercent closed 2 years ago

deepakEnercent commented 2 years ago

When your question is related to code that isn't working as expected, please enable debug logs:

import logging
import json
import asyncio
import uvicorn

from datetime import datetime
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

from datetime import datetime
from fastapi import Body, FastAPI, status, Request, WebSocket, Depends
from chargepoint import ChargePoint

app = FastAPI()

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=1000,
            status=RegistrationStatus.accepted
        )

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

    @on('StatusNotification')
    def status_notification(self, connector_id, status, error_code, **kwargs):
        global STATUS
        if connector_id == 1:
            print(f"Status of the Charger ==> {status}")

        return call_result.StatusNotificationPayload()

    @on('StartTransaction')
    def start_transaction(self, connector_id, id_tag, timestamp, meter_start, **kwargs):

        return call_result.StartTransactionPayload(
            transaction_id=int(4),
            id_tag_info={
                "status": "Accepted"
            }
        )

    @on('StopTransaction')
    def stop_transaction(self, meter_stop, timestamp, transaction_id, **kwargs):
        return call_result.StopTransactionPayload(
            id_tag_info={
                "status": 'Accepted'
            }
        )

    @on('MeterValues')
    def meter_value(self, connector_id, meter_value, **kwargs):
        # meter_values["connector_id"] = connector_id
        # meter_values["meter_value"] = meter_value
        print(f"this is meter_value payload{meter_value}, {kwargs}")
        return call_result.MeterValuesPayload()

    async def reset(self,type):
        print("reached here at chargepoint reset function")
        response = await self.call(call.ResetPayload(type=type))
        print(response)
        return "success"

class CentralSystem:
    def __init__(self):
        self._chargers = {}

    def register_charger(self, cp: ChargePoint):
        queue = asyncio.Queue(maxsize=1)
        task = asyncio.create_task(self.start_charger(cp, queue))
        self._chargers[cp] = task
        return queue

    async def start_charger(self, cp, queue):
        try:
            await cp.start()
        except Exception as error:
            print(f"Charger {cp.id} disconnected: {error}")
        finally:
            del self._chargers[cp]
            await queue.put(True)

    async def reset_fun(self, cp_id: str, rst_type: str):
        print("atleast got here")
        for cp, task in self._chargers.items():
            if cp.id == cp_id:
                print("reached here")
                get_response = await cp.reset(type)
                return get_response

class SocketAdapter:
    def __init__(self, websocket: WebSocket):
        self._ws = websocket

    async def recv(self) -> str:
        return await self._ws.receive_text()

    async def send(self, msg) -> str:
        await self._ws.send_text(msg)

@app.websocket("/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: str, csms: CentralSystem = Depends(CentralSystem)):
    await websocket.accept(subprotocol='ocpp1.6')
    cp_id = websocket.url.path.strip('/')
    cp = ChargePoint(cp_id, SocketAdapter(websocket))
    print(f"charger {cp.id} connected.")

    queue = csms.register_charger(cp)
    await queue.get()

@app.post("/reset")
async def reset(request: Request, cms: CentralSystem = Depends(CentralSystem)):
    data = await request.json()
    print(f"API DATA to confirm {data}")
    get_response = await cms.reset_fun(data["cp_id"], data["type"])
    print(f"==> The response from charger==> {get_response}")
    return "sucess"

if __name__ == '__main__':
    uvicorn.run(app, host='0.0.0.0', port=2500)
''' OUTPUT '''
charger TestUnit connected.
INFO:     ('192.168.137.56', 57856) - "WebSocket /TestUnit" [accepted]
INFO:     connection open
Status of the Charger ==> Available
API DATA to confirm {'cp_id': 'TestUnit', 'type': 'Hard'}
atleast got here
==> The response from charger==> None
INFO:     127.0.0.1:63999 - "POST /reset HTTP/1.1" 200 OK

I am getting stuck here and not able to go beyond this as my knowledge on Asyncio is limited and I am just hitting here and there to get the grasp of that asyncio topic.

So according to the output the Reset API is going to Reset function which is in CentralSystem but when it goes inside the function it just comes back out the function and doesn't carry the whole reset function which in turn doesn't go to the reset function of the ChargePoint which will actually reset the Charger(Physoacal charger). Can someone guide me where I am going wrong or if my code is wrong and am not understanding the code the way it should be.

OrangeTux commented 2 years ago

Please provide debug logs, see https://github.com/mobilityhouse/ocpp#debugging

deepakEnercent commented 2 years ago
API DATA to confirm {'type': 'Hard'}
atleast got here
==> The response from charger==> None
INFO:     127.0.0.1:53587 - "POST /reset HTTP/1.1" 200 OK
DEBUG:    %% sending keepalive ping
DEBUG:uvicorn.error:%% sending keepalive ping
DEBUG:    > PING '\x11QsS' [text, 4 bytes]
DEBUG:uvicorn.error:> PING '\x11QsS' [text, 4 bytes]
DEBUG:    < PONG '\x11QsS' [text, 4 bytes]
DEBUG:uvicorn.error:< PONG '\x11QsS' [text, 4 bytes]
DEBUG:    %% received keepalive pong
DEBUG:uvicorn.error:%% received keepalive pong
DEBUG:    < PING '' [0 bytes]
DEBUG:uvicorn.error:< PING '' [0 bytes]
DEBUG:    > PONG '' [0 bytes]
DEBUG:uvicorn.error:> PONG '' [0 bytes]
DEBUG:    %% sending keepalive ping
DEBUG:uvicorn.error:%% sending keepalive ping
DEBUG:    > PING db 97 a8 1f [binary, 4 bytes]
DEBUG:uvicorn.error:> PING db 97 a8 1f [binary, 4 bytes]
DEBUG:    < PONG db 97 a8 1f [binary, 4 bytes]
DEBUG:uvicorn.error:< PONG db 97 a8 1f [binary, 4 bytes]
DEBUG:    %% received keepalive pong
DEBUG:uvicorn.error:%% received keepalive pong

it's beyond my understanding. please can you guide me to the correct path.

OrangeTux commented 2 years ago

reset_fun() looks like this:

    async def reset_fun(self, cp_id: str, rst_type: str):
        print("atleast got here")
        for cp, task in self._chargers.items():
            if cp.id == cp_id:
                print("reached here")
                get_response = await cp.reset(type)
                return get_response

The "Reset" message is never send. That means either self._chargers is empty or this dictionary doesn't contain an item where cp.id == cp_id.

deepakEnercent commented 2 years ago

Yeah, the part self._chargers is empty. But where I am going wrong I am not able to find it. Can you help me in finding this error? I am kind of stuck here and not able to think as my python knowledge in this thing is not that good. I need help in this scenario.

And it only happens when I send a request to Central System from any of the routes. There the self._chargers is left empty.

deepakEnercent commented 2 years ago

Hey, thanks for the help and I really appreciate you found out the issue and now I have solved that issue and the issue was I was giving different objects to the same class that was made to connect to the server. You can now close this issue.

ishan3199 commented 2 years ago

art self._chargers is empty. But where I am going wrong I am not able to find it. Can you

Hi deepak!!

I am also using FastAPI and uvicorn ...

I am runnining uvicorn.run(app, host='0.0.0.0', port=2500)

so the server starts up perfectly but when I connect via a chargepoint script to the server, the server shows following error:

handle: <Handle _ProactorReadPipeTransport._loop_reading(<_OverlappedF.../9.1\r\n\r\n'>)> Traceback (most recent call last): File "C:\Users\admin\AppData\Local\Programs\Python\Python39\lib\asyncio\events.py", line 80, in _run self._context.run(self._callback, *self._args) File "C:\Users\admin\AppData\Local\Programs\Python\Python39\lib\asyncio\proactor_events.py", line 318, in _loop_reading self._data_received(data) File "C:\Users\admin\AppData\Local\Programs\Python\Python39\lib\asyncio\proactor_events.py", line 269, in _data_received self._protocol.data_received(data) File "C:\Users\admin\Desktop\u\OCPP\venv\lib\site-packages\uvicorn\protocols\http\h11_impl.py", line 124, in data_received self.handle_events() File "C:\Users\admin\Desktop\u\OCPP\venv\lib\site-packages\uvicorn\protocols\http\h11_impl.py", line 173, in handle_events self.handle_upgrade(event) File "C:\Users\admin\Desktop\u\OCPP\venv\lib\site-packages\uvicorn\protocols\http\h11_impl.py", line 245, in handle_upgrade protocol = self.ws_protocol_class( File "C:\Users\admin\Desktop\u\OCPP\venv\lib\site-packages\uvicorn\protocols\websockets\websockets_impl.py", line 62, in init super().init( File "C:\Users\admin\Desktop\u\OCPP\venv\lib\site-packages\websockets\legacy\server.py", line 201, in init super().init(**kwargs) TypeError: init() got an unexpected keyword argument 'logger'

Any idea about this why this happening?? Thanks in advance!!

ishan3199 commented 2 years ago

art self._chargers is empty. But where I am going wrong I am not able to find it. Can you

Hi deepak!!

I am also using FastAPI and uvicorn ...

I am runnining uvicorn.run(app, host='0.0.0.0', port=2500)

so the server starts up perfectly but when I connect via a chargepoint script to the server, the server shows following error:

handle: <Handle _ProactorReadPipeTransport._loop_reading(<_OverlappedF.../9.1\r\n\r\n'>)> Traceback (most recent call last): File "C:\Users\admin\AppData\Local\Programs\Python\Python39\lib\asyncio\events.py", line 80, in _run self._context.run(self._callback, *self._args) File "C:\Users\admin\AppData\Local\Programs\Python\Python39\lib\asyncio\proactor_events.py", line 318, in _loop_reading self._data_received(data) File "C:\Users\admin\AppData\Local\Programs\Python\Python39\lib\asyncio\proactor_events.py", line 269, in _data_received self._protocol.data_received(data) File "C:\Users\admin\Desktop\u\OCPP\venv\lib\site-packages\uvicorn\protocols\http\h11_impl.py", line 124, in data_received self.handle_events() File "C:\Users\admin\Desktop\u\OCPP\venv\lib\site-packages\uvicorn\protocols\http\h11_impl.py", line 173, in handle_events self.handle_upgrade(event) File "C:\Users\admin\Desktop\u\OCPP\venv\lib\site-packages\uvicorn\protocols\http\h11_impl.py", line 245, in handle_upgrade protocol = self.ws_protocol_class( File "C:\Users\admin\Desktop\u\OCPP\venv\lib\site-packages\uvicorn\protocols\websockets\websockets_impl.py", line 62, in init super().init( File "C:\Users\admin\Desktop\u\OCPP\venv\lib\site-packages\websockets\legacy\server.py", line 201, in init super().init(kwargs) TypeError: init**() got an unexpected keyword argument 'logger'

Any idea about this why this happening?? Thanks in advance!!

art self._chargers is empty. But where I am going wrong I am not able to find it. Can you

Hi deepak!!

I am also using FastAPI and uvicorn ...

I am runnining uvicorn.run(app, host='0.0.0.0', port=2500)

so the server starts up perfectly but when I connect via a chargepoint script to the server, the server shows following error:

handle: <Handle _ProactorReadPipeTransport._loop_reading(<_OverlappedF.../9.1\r\n\r\n'>)> Traceback (most recent call last): File "C:\Users\admin\AppData\Local\Programs\Python\Python39\lib\asyncio\events.py", line 80, in _run self._context.run(self._callback, *self._args) File "C:\Users\admin\AppData\Local\Programs\Python\Python39\lib\asyncio\proactor_events.py", line 318, in _loop_reading self._data_received(data) File "C:\Users\admin\AppData\Local\Programs\Python\Python39\lib\asyncio\proactor_events.py", line 269, in _data_received self._protocol.data_received(data) File "C:\Users\admin\Desktop\u\OCPP\venv\lib\site-packages\uvicorn\protocols\http\h11_impl.py", line 124, in data_received self.handle_events() File "C:\Users\admin\Desktop\u\OCPP\venv\lib\site-packages\uvicorn\protocols\http\h11_impl.py", line 173, in handle_events self.handle_upgrade(event) File "C:\Users\admin\Desktop\u\OCPP\venv\lib\site-packages\uvicorn\protocols\http\h11_impl.py", line 245, in handle_upgrade protocol = self.ws_protocol_class( File "C:\Users\admin\Desktop\u\OCPP\venv\lib\site-packages\uvicorn\protocols\websockets\websockets_impl.py", line 62, in init super().init( File "C:\Users\admin\Desktop\u\OCPP\venv\lib\site-packages\websockets\legacy\server.py", line 201, in init super().init(kwargs) TypeError: init**() got an unexpected keyword argument 'logger'

Any idea about this why this happening?? Thanks in advance!!

Yo!! solved it!

So it was about websockets we need websockets >= 10 for this.

ishan3199 commented 2 years ago

Hey, thanks for the help and I really appreciate you found out the issue and now I have solved that issue and the issue was I was giving different objects to the same class that was made to connect to the server. You can now close this issue.

Hey deepak !! I am confused can you please elaborate? I am also not able to send remote start PDU same way as you were not able to send reset here. I am too using FASTapi.

Thanks in advance buddy!!

deepakEnercent commented 2 years ago

Sorry for this late reply.Hey did you got anything.?

ishan3199 commented 2 years ago

Sorry for this late reply.Hey did you got anything.?

No brother It is not getting triggered ! So the http request is received but the reset function doesnot get triggered. The self._chargers remains empty. any resource or help will be great

ishan3199 commented 2 years ago

@deepakEnercent Can you just tell me why is it not storing the charger instance in self._chargers?? Rest I will figure it out !!

deepakEnercent commented 2 years ago

Hey hi.Can you share here how you have wrote the program.Just show that part. and i want toi understand the program.can you share those.

ishan3199 commented 2 years ago

Hey hi.Can you share here how you have wrote the program.Just show that part. and i want toi understand the program.can you share those.

Yes sure! It is the same program that you raised the issue with! Just changed it to the Remote start PDU as I require it for my case !!! Below is the code

`

import logging
import json
import asyncio
import uvicorn
import asgiref
from enums import OcppMisc as oc
from enums import ConfigurationKey as ck

from datetime import datetime
from ocpp.routing import on
from ocpp.v16 import ChargePoint as cp
from ocpp.v16.enums import Action, RegistrationStatus, AuthorizationStatus, ResetType, ResetStatus
from ocpp.v16 import call_result, call
import os
import sys
from datetime import datetime
from fastapi import Body, FastAPI, status, Request, WebSocket, Depends
from chargepoint import ChargePoint

app = FastAPI()
#print(asgiref.__version__)
logging.basicConfig(level=logging.INFO)

class ChargePoint(cp):

    @on(Action.Authorize)
    async def on_auth(self, id_tag, **kwargs):
        if id_tag == "test_cp2" or id_tag == "test_cp5":
            print("authorized")
            #cur.execute("INSERT INTO id(id_tag) VALUES (?)", (id_tag,))
            #con.commit()
            return call_result.AuthorizePayload(
                id_tag_info={oc.status.value: AuthorizationStatus.accepted.value}
            )
        else:
            print("Not Authorized")
            return call_result.AuthorizePayload(
                id_tag_info={oc.status.value: AuthorizationStatus.invalid.value}
            )

    @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=1000,
            status=RegistrationStatus.accepted
        )

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

    @on(Action.StatusNotification)
    def status_notification(self, connector_id, status, error_code, **kwargs):
        global STATUS
        if connector_id == 1:
            print(f"Status of the Charger ==> {status}")

        return call_result.StatusNotificationPayload()

    @on(Action.StartTransaction)
    def start_transaction(self, connector_id, id_tag, timestamp, meter_start, **kwargs):

        return call_result.StartTransactionPayload(
            transaction_id=int(4),
            id_tag_info={
                "status": "Accepted"
            }
        )

    @on(Action.StopTransaction)
    def stop_transaction(self, meter_stop, timestamp, transaction_id, **kwargs):
        return call_result.StopTransactionPayload(
            id_tag_info={
                "status": 'Accepted'
            }
        )

    @on(Action.MeterValues)
    def meter_value(self, connector_id, meter_value, **kwargs):
        # meter_values["connector_id"] = connector_id
        # meter_values["meter_value"] = meter_value
        print(f"this is meter_value payload{meter_value}, {kwargs}")
        return call_result.MeterValuesPayload()

#used remote start with fast api to trigger it

    async def remote_start(self):
        request = call.RemoteStartTransactionPayload(id_tag="test_cp2")
        response=await  self.call(request)
        print(response)
        return "success"

class CentralSystem:
    def __init__(self):
        self._chargers = {}

    def register_charger(self, cp: ChargePoint):
        queue = asyncio.Queue(maxsize=1)
        task = asyncio.create_task(self.start_charger(cp, queue))
        self._chargers[cp] = task
        print(self._chargers)
        return queue

    async def start_charger(self, cp, queue):
        try:
            await cp.start()
        except Exception as error:
            print(f"Charger {cp.id} disconnected: {error}")
        finally:
            del self._chargers[cp]
            await queue.put(True)

    def disconnect_charger(self, id: str):
        for cp, task in self._chargers.items():
            if cp.id == id:
                task.cancel()
                print("disconnected")
                return

        raise ValueError(f"Charger {id} not connected.")

    async def remote_start_function(self,id: str):

        print(self._chargers.items())
        for cp, task in self._chargers.items():
            if cp.id == cp_id:
                await cp.remote_start(id)

class SocketAdapter:
    def __init__(self, websocket: WebSocket):
        self._ws = websocket

    async def recv(self) -> str:
        return await self._ws.receive_text()

    async def send(self, msg) -> str:
        await self._ws.send_text(msg)

@app.websocket("/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: str, csms: CentralSystem = Depends(CentralSystem)):
    await websocket.accept(subprotocol='ocpp1.6')
    cp_id = websocket.url.path.strip('/')
    cp = ChargePoint(cp_id, SocketAdapter(websocket))
    print(f"charger {cp.id} connected.")

    queue = csms.register_charger(cp)
    await queue.get()

@app.post("/remote")
async def reset(request: Request, cms: CentralSystem = Depends(CentralSystem)):
    data = await request.json()
    get_response = await cms.remote_start_function(data["id"])
    print(f"==> The response from charger==> {get_response}")
    return "success"

if __name__ == '__main__':
    uvicorn.run(app, host='0.0.0.0', port=2500)
`

Any help would be great @deepakEnercent

deepakEnercent commented 2 years ago

Remove all the Depends that will not work.do just one thing just create central system object i.e centralsystem = CentralSystem() and send the command from app to central system like central.remote_stop(response[6], request.transaction_id) and try

ishan3199 commented 2 years ago

@deepakEnercent So I tried it !! It worked nicely...... big thanks to you my friend!!!!!!

Now after deploying this do we curl using the same port??

ishan3199 commented 2 years ago

Thanks for all the help deepak!! @deepakEnercent It means alot !!

deepakEnercent commented 2 years ago

Thanks for all the help deepak!! @deepakEnercent It means alot !!

It's okay.you tried and got the result.yeah you can do curl or just use postman.

ishan3199 commented 2 years ago

Thanks for all the help deepak!! @deepakEnercent It means alot !!

It's okay.you tried and got the result.yeah you can do curl or just use postman.

Thanks man !! sure I am thinking of using postman :) :) +1:

auggie246 commented 2 years ago

Remove all the Depends that will not work.do just one thing just create central system object i.e centralsystem = CentralSystem() and send the command from app to central system like central.remote_stop(response[6], request.transaction_id) and try

I don't quite understand, what you mean by "create central system object i.e centralsystem = CentralSystem() and send the command from app to central system"

Could you show an example? I am terribly stuck @deepakEnercent

g-a-pet commented 2 years ago

Remove all the Depends that will not work.do just one thing just create central system object i.e centralsystem = CentralSystem() and send the command from app to central system like central.remote_stop(response[6], request.transaction_id) and try

I don't quite understand, what you mean by "create central system object i.e centralsystem = CentralSystem() and send the command from app to central system"

Could you show an example? I am terribly stuck @deepakEnercent

@auggie246 Hi. So the guys above were creating a new instance of CentralSystem each time. @deepakEnercent instruction is to create an instance of CentralSystem: centralsystem = CentralSystem() Use this in websocket connection to register:

Old: csms.register_charger(cp) New: centralsystem.register_charger(cp)

What you are basically doing is using this instance of CentralSystem to store your instances of ChargePoint to be able to access them in your calls. Now in your api functions remove csms and use centralsystem in your calls:

NOT cam.remote_start_function(data["id"]) USE centralsystem.remote_start_function(data["id"])

Let me know if it works