lorenzodonini / ocpp-go

Open Charge Point Protocol implementation in Go
MIT License
262 stars 125 forks source link

Issue with Websockets using Browser Chargepoint Simulator? #103

Closed daviid5 closed 2 years ago

daviid5 commented 3 years ago

I'm starting to integrate ocpp-go in my own application. For testing I wanted to use one of the HTML/JS chargepoint simulators.

So I'm running the example/1.6/cs application on my pc, if I run also the examples/1.6/cp simulator everything works. The simulated chagrpoint registers in the central system example, and so on.

I'm for example tried this one: https://github.com/zzerk/OCPP-ChargePoint-Simulator

I entered 'ws://127.0.0.1:8887' as OCCP server, and the click 'connect'. The log in the simulator states the following error: OCPP Simulator ready connection cannot be opened: error Connection error: 1006 [OCPP] Connection error: 1006

An the console in Chrome gives following error: grafik

But I don't understand where the problem could come from.

Could this be a problem of the Simulator, or maybe of the occp-go package?

I'm on commit 017c9dc76f2408bf522a91e93727a67c57660af5

lorenzodonini commented 3 years ago

Hello, I just tested this out locally and the browser is sending out an Origin header, which makes the websocket upgrade fail with websocket: request origin not allowed by Upgrader.CheckOrigin.

The library allows you to use a custom CORS handler for you websocket server, by using this method https://github.com/lorenzodonini/ocpp-go/blob/master/ws/websocket.go#L213. So, for example, you could do:

myCORSHandler := func(r *http.Request) bool {
   return true // please don't do this in production
}
myWsServer.SetCheckOriginHandler(myCORSHandler)

and the browser would connect correctly.

Two additional considerations:

daviid5 commented 3 years ago

Hi, thank you for your fast response. Sorry for the dumb question, but where should I add the code for the handler you posted above?

Do you could suggest other simulators which don't have this problem? Prefereable with kind of gui, so I don't have to script to much?

lorenzodonini commented 3 years ago

where should I add the code for the handler you posted above?

The easy way to do it would be to modify the example code in central_system_sim.go:

func setupCentralSystem() ocpp16.CentralSystem {
    wsServer := ws.NewServer() // creates your own websocket server
    wsServer.SetCheckOriginHandler(func(r *http.Request) bool {
        return true
    })
    // pass your customized websocket server to the default constructor
    return ocpp16.NewCentralSystem(nil, wsServer)
}

you could suggest other simulators which don't have this problem?

I haven't really used other GUI-based simulators. Maybe you can give https://github.com/nenecmrf/OCPP-J-CP-Simulator a shot?

Just as a side-note, since you are running simulators in the browser: afaik the default websocket library in Javascript doesn't support sending ping messages. The library, however, expects the clients to send pings, and not the other way around. If you need to temporarily circumvent this, just set a very long ping timeout on the server (see SetTimeoutConfig). I'm thinking of supporting server-initiated pings in the future, but so far it hasn't been a strong requirement (clients are typically run on embedded devices, so they're not limited by the JS API).

daviid5 commented 3 years ago

Thank you! I tried the https://github.com/nenecmrf/OCPP-J-CP-Simulator already, it spits out quite a few Javascript errors (beside that, the https://github.com/zzerk/OCPP-ChargePoint-Simulator simulator is a fork from https://github.com/nenecmrf/OCPP-J-CP-Simulator) Currently I'm using the chargepoint example from you ocpp-go repo to simulate. Working fine so far.

But I encountered another interesting thin. When I run the charpepoint example, I set the CLIENT-ID to 'cp123' But in the callback here:

centralSystem.SetNewChargePointHandler(func(chargePointId string) {
    log.Printf("new charge point %v connected", chargePointId)
})

chargePointId = "/cp123" I expected "cp123" Is this a bug, or a feature?

lorenzodonini commented 3 years ago

Let's say it's a bit of both. The original idea was to return the full websocket path (starting from the slash), in case a vendor needs it for whatever reason. This is obviously a bit annoying, since you have to strip the prefix.

The upcoming v.0.15.0 release fixes this and only the actual ID of the charge point will returned (in your example, cp123).

xBlaz3kx commented 2 years ago

Hi,

I had a similar issue, when using the Charge Point simulator. After I fixed the Central System CORS check with the suggestion above, another issue persists. Websocket connection keeps shutting down, might be related to #41.

The funny thing is, the connection was persisted just fine when connecting a Charge Point written with ocpp-go. Also tried connecting with a Phoenixcontact AC controller, it also randomly shut down the connection. Any ideas why it does that?

{"chargePointId":"1234","level":"info","msg":"Charge point disconnected","protocolVersion":"1.6","time":"2022-01-03T20:26:13Z"}
{"chargePointId":"1234","level":"info","msg":"Charge point connected","protocolVersion":"1.6","time":"2022-01-03T20:43:00Z"}
daviid5 commented 2 years ago

@xBlaz3kx Your problem sounds a bit like one I also encountered. Our chargepoints use the Bender CC613 Controller. It happens quite often that the chargepoint disconnects, and then again connects to CS. Until know I have not really looked into this.

But your problem sounds familiar to me.

xBlaz3kx commented 2 years ago

@daviid5 I think it might be the Websocket ping that disconnects the Charge Point, which was mentioned here:

Just as a side-note, since you are running simulators in the browser: afaik the default websocket library in Javascript doesn't support sending ping messages. The library, however, expects the clients to send pings, and not the other way around. If you need to temporarily circumvent this, just set a very long ping timeout on the server (see SetTimeoutConfig). I'm thinking of supporting server-initiated pings in the future, but so far it hasn't been a strong requirement (clients are typically run on embedded devices, so they're not limited by the JS API).

Guess I need to read thoroughly next time 🤣 Any way of getting rid of this issue pernamently?

daviid5 commented 2 years ago

@xBlaz3kx I guess this problem with the ping takes only affect when using browser based simulators, correct?

But I'm using real Charge-Controllers.

xBlaz3kx commented 2 years ago

@daviid5 I think it affects both simulators and real controllers. I've tried Phoenix Contact AC charging controller, and it also gets randomly disconnected from my Central System.

lorenzodonini commented 2 years ago

As @xBlaz3kx correctly noticed, it doesn't matter whether the charge controller is running in the browser or not. If it doesn't send pings, the connection will eventually time out.

I suggest using the SetTimeoutConfig to set a longer ping timeout. This way, you can easily verify if that's the cause for the connections being dropped in your case. Let me know.

xBlaz3kx commented 2 years ago

@lorenzodonini SetTimeoutConfig definetly works, but is rather a temporary fix for this issue. It would be nice to add an option to disable the ping check to support Websocket implementations without Ping/Pong.

lorenzodonini commented 2 years ago

To effectively disable the ping/pong, you can simply set a timeout value of 0.

The websocket server runs this after every received message:

_ = conn.SetReadDeadline(time.Now().Add(server.timeoutConfig.PingWait))

The gorilla websocket docs clearly state that A zero value for t means reads will not time out..

Unless you need to selectively disable pings for specific connections, I believe this would do the trick.

xBlaz3kx commented 2 years ago

@lorenzodonini thanks for the clarification, I didn't go into much detail in the Websocket library.

DerArtem commented 2 years ago

Hello,

the problem is that the ping/pong is implemented the wrong way:

Right now the WS-Server is waiting for a ping from the WS-Client and answers it with a pong. If it does not receive a ping from the client the connection will time out.

However most WS-Clients are not able to send out a ping at all. Epecially Web-Browsers can not send a WS-Ping. The can only reply to a ping with a pong.

The correct way for the WS-Server is to send out the pings and wait for a reply from the client... If it receives a ping from a client it sould also reply to it with a pong. But this usually will not happen...

lorenzodonini commented 2 years ago

RFC 6455 doesn't specify who should send the ping. Any of the endpoints may initiate it. Mozilla docs confirm this. Other libraries (e.g. python websockets) also support client-initiated pings.

Gorilla websockets use a client-only-initiated ping in their chat example, which is what I took over for simplicity.

I can definitely add a two-way ping pong mechanism, which will solve browser simulators timing out. This will increase the complexity and I'll have to test it a bit longer.

xBlaz3kx commented 2 years ago

@lorenzodonini

I've set the PingWait to 0 as proposed to disable the Ping/Pong, however, this did not achieve the desired effect; now it just keeps disconnecting my simulated charge point, and I'm quite sure it does that on a controller as well. Not sure if this is the Gorilla Websocket issue or ocpp-go issue.

        server := ws.NewServer()

    // To avoid unnecessary reconnects
    timeoutConfig := ws.NewServerTimeoutConfig()
    timeoutConfig.PingWait = 0 // Disable ping/pong
    server.SetTimeoutConfig(timeoutConfig)

    // Disable CORS check
    server.SetCheckOriginHandler(func(r *http.Request) bool {
        return true
    })

    centralSystem := ocpp16.NewCentralSystem(nil, server)

Since I'm posting this, I'd like to ask about an unrelated issue: MeterValues are not being received on my Central System from both a charging controller and the simulated charge point. Any way of logging/looking into received messages (not just errors)?

lorenzodonini commented 2 years ago

@xBlaz3kx I fixed a bug in the websocket layer that most likely was the issue you are encountering. I also added support for debug logs:

yourLogger.SetLevel(DEBUG)
ws.SetLogger(yourLogger)

You can enable logs to see exactly what's happening under the hood.

Let me know if the problem persists.

xBlaz3kx commented 2 years ago

@lorenzodonini Appreciate your hard work! So far I've been impressed with overall experience and code quality of this library. Thank you!

rogeralsing commented 2 years ago

Not using this lib but had related issues in .NET recently. What is interesting is that the WebSocket specification does not say that a connection should timeout if a ping doesn't get a pong. It only says a ping should be replied to with a pong, but not what should happen if it doesn't

What is also relevant is that apparently in some cases websocket pings and pongs are filtered out by various web proxies.

In the end, we decided to rely on Ocpp heartbeats instead, and force the configuration to be at most 60 seconds intervals.

Maybe this is of value to someone else getting bitten by the same issues

lorenzodonini commented 2 years ago

@rogeralsing I'm very well aware that websocket ping/pongs can be annoying. Relying on heartbeats is definitely a good alternative.

I will try to mitigate some related issues in the future and make it less painful for people using a web simulator. For the time being, disabling the ping/pong entirely should do the trick though.