diff --git a/ocpp2.0.1/charging_station.go b/ocpp2.0.1/charging_station.go index 065af9a2..31ef3831 100644 --- a/ocpp2.0.1/charging_station.go +++ b/ocpp2.0.1/charging_station.go @@ -603,6 +603,14 @@ func (cs *chargingStation) Start(csmsUrl string) error { return err } +func (cs *chargingStation) StartWithRetries(csmsUrl string) { + // Start client + cs.stopC = make(chan struct{}, 1) + cs.client.StartWithRetries(csmsUrl) + // Async response handler receives incoming responses/errors and triggers callbacks + go cs.asyncCallbackHandler() +} + func (cs *chargingStation) Stop() { cs.client.Stop() } diff --git a/ocpp2.0.1/v2.go b/ocpp2.0.1/v2.go index 6e58efed..3f9cb495 100644 --- a/ocpp2.0.1/v2.go +++ b/ocpp2.0.1/v2.go @@ -165,6 +165,13 @@ type ChargingStation interface { // // No auto-reconnect logic is implemented as of now, but is planned for the future. Start(csmsUrl string) error + + // Connects to the CSMS and starts the charging station routine, it retries if first attempt fails. + // The function doesn't block and returns right away, after having attempted to open a connection to the CSMS. + // If the connection couldn't be opened, it retries. + // + // Optional client options must be set before calling this function. Refer to NewChargingStation. + StartWithRetries(csmsUrl string) // Stops the charging station routine, disconnecting it from the CSMS. // Any pending requests are discarded. Stop() diff --git a/ocppj/client.go b/ocppj/client.go index 083f544f..731fb540 100644 --- a/ocppj/client.go +++ b/ocppj/client.go @@ -123,6 +123,17 @@ func (c *Client) Start(serverURL string) error { return err } +func (c *Client) StartWithRetries(serverURL string) { + // Set internal message handler + c.client.SetMessageHandler(c.ocppMessageHandler) + c.client.SetDisconnectedHandler(c.onDisconnected) + c.client.SetReconnectedHandler(c.onReconnected) + // Connect & run + fullUrl := fmt.Sprintf("%v/%v", serverURL, c.Id) + c.client.StartWithRetries(fullUrl) + c.dispatcher.Start() +} + // Stops the client. // The underlying I/O loop is stopped and all pending requests are cleared. func (c *Client) Stop() { diff --git a/ws/websocket.go b/ws/websocket.go index 56665a16..fd8d6831 100644 --- a/ws/websocket.go +++ b/ws/websocket.go @@ -722,6 +722,17 @@ type WsClient interface { // // To stop a running client, call the Stop function. Start(url string) error + // Starts the client and attempts to connect to the server on a specified URL. + // If the connection fails, it keeps retrying with Backoff strategy from TimeoutConfig. + // + // For example: + // client.StartWithRetries("ws://localhost:8887/ws/1234") + // + // The function returns only when the connection has been established. + // Incoming messages are passed automatically to the callback function, so no explicit read operation is required. + // + // To stop a running client, call the Stop function. + StartWithRetries(url string) // Closes the output of the websocket Channel, effectively closing the connection to the server with a normal closure. Stop() // Errors returns a channel for error messages. If it doesn't exist it es created. @@ -997,6 +1008,7 @@ func (client *Client) handleReconnection() { return } + log.Info("reconnecting... attempt", reconnectionAttempts) err := client.Start(client.url.String()) if err == nil { // Re-connection was successful @@ -1038,8 +1050,17 @@ func (client *Client) Write(data []byte) error { return nil } +func (client *Client) StartWithRetries(urlStr string) { + err := client.Start(urlStr) + if err != nil { + log.Info("Connection error:", err) + client.handleReconnection() + } +} + func (client *Client) Start(urlStr string) error { url, err := url.Parse(urlStr) + client.url = *url if err != nil { return err } @@ -1072,7 +1093,7 @@ func (client *Client) Start(urlStr string) error { // The id of the charge point is the final path element id := path.Base(url.Path) - client.url = *url + client.webSocket = WebSocket{ connection: ws, id: id, diff --git a/ws/websocket_test.go b/ws/websocket_test.go index 8267d8db..399f7259 100644 --- a/ws/websocket_test.go +++ b/ws/websocket_test.go @@ -166,6 +166,45 @@ func TestWebsocketEcho(t *testing.T) { wsServer.Stop() } +func TestWebsocketBootRetries(t *testing.T) { + verifyConnection := func(client *Client, connected bool) { + maxAttempts := 20 + for i := 0; i <= maxAttempts; i++ { + if client.IsConnected() != connected { + time.Sleep(time.Duration(2) * time.Second) + continue + } + } + assert.Equal(t, connected, client.IsConnected()) + } + wsServer := newWebsocketServer(t, func(data []byte) ([]byte, error) { + return data, nil + }) + wsClient := newWebsocketClient(t, func(data []byte) ([]byte, error) { + return nil, nil + }) + + go func() { + // Start websocket client + host := fmt.Sprintf("localhost:%v", serverPort) + u := url.URL{Scheme: "ws", Host: host, Path: testPath} + wsClient.StartWithRetries(u.String()) + }() + + assert.Equal(t, wsClient.IsConnected(), false) + + time.Sleep(time.Duration(3) * time.Second) + + go wsServer.Start(serverPort, serverPath) + verifyConnection(wsClient, true) + + wsServer.Stop() + verifyConnection(wsClient, false) + + wsServer.Stop() + wsClient.Stop() +} + func TestTLSWebsocketEcho(t *testing.T) { message := []byte("Hello Secure WebSocket!") triggerC := make(chan bool, 1)