How to use OCPP in Node-Red

Search for id 06247e5e01a10f5c and use debug nodes set to show full message to see why/where these kind of messages are coming from (NOTE: my flows dont have that ID)

Depending on what you modify and what deploy mode you use in node-red, connections and other entities will be destroyed and recreated. This is normal.

That will depend on deploy mode and/or what you are changing in flows before you hit deploy.

It looks like I found the issue with the missing msgid. I implemented the same logic as catching the ONLINE and OFFLINE messages with a function node, just did it slighly differently. It was missing setting the msg.msgId property. Now it is fixed:

if (msg.ocpp.websocket === "ONLINE") {
    msg.payload = { "command" : "OnlineNotification", "data": {} };
    msg.ocpp.command = "OnlineNotification";
    msg.msgId = msg._msgid;
}

if (msg.ocpp.websocket === "OFFLINE") {
    msg.payload = { "command": "OfflineNotification", "data": {} };
    msg.ocpp.command = "OfflineNotification";
    msg.msgId = msg._msgid;
}

And in the switch node it is wired into the Generic Status=Accepted route.

But this did not change the end result. I still getting the OFFLINE message after some time (after 14 seconds). So it looks like I am still missing something.
And it also looks like the response to the OFFLINE message fails (shows message failed on the sender node), but the Heartbeats are still coming after 2 minutes.

I could be doing something different from your flow, I just can't see the difference. I try to disable my flow, import yours and see if that makes a difference.
If not, I guess there is something special about this particular CP.

OK, Steve's flow is working. I am not getting the offline messages. I have to admit, I am still not sure what is the difference, why my flow was not working. I also started from the same sample flow, and I have not taken anything off of it. And as far as I can see, Steve's flow does nothing with the ONLINE and OFFLINE messages, just goes to logging.

Let me ask a few question to make sure I understand this flow correctly:

  • what is the purpose of the function node between the CS request and the command switch. My understanding is that it is mainly preparing the data for logging, but does not really influence the request-response communication. What is the real purpose of the msg.ocppData object besides logging?
  • @Paul-Reed can you send a sample data that you send as a charging profile? Are you keep changing profile 0?

@nygma2004 - I'll be charging my car tomorrow, so I'll grab the profiles and post them here.

@nygma2004 - Here is the charging profile for Steve's flow, which charges at maximum possible;

{
    "_msgid": "d7e2b9164bbbdff0",
        "topic": "RemoteStartTransaction",
            "payload": {
        "command": "RemoteStartTransaction",
            "data": {
            "connectorId": 1,
                "timestamp": "2025-05-14T11:49:39.079Z",
                    "idTag": "MYTAG",
                        "meterStart": 0
        }
    }
}

And this is the profile that I use to charge at a variable current (to match my solar surplus).
The initial profile sent to the charger sets the chargingProfilePurpose to the TxDefaultProfile

{
  "_msgid": "ba259d12d2062aa3",
  "payload": {
    "command": "SetChargingProfile",
    "data": {
      "connectorId": 1,
      "csChargingProfiles": {
        "chargingProfileId": 1,
        "transactionId": 69357,
        "stackLevel": 0,
        "chargingProfilePurpose": "TxDefaultProfile",
        "chargingProfileKind": "Relative",
        "chargingSchedule": {
          "chargingRateUnit": "A",
          "chargingSchedulePeriod": [
            {
              "startPeriod": 0,
              "limit": 32,
              "numberPhases": 1
            }
          ]
        }
      }
    }
  },
  "topic": "SetChargingProfile",
  "_event": "node:2ee04367c8ee7e11"
}

...and all of the subsequent 15 second updated profiles changes the limit amount and also the chargingProfilePurpose to TxProfile. This is to avoid the charger running through it's initialization each time I send a new profile, so the charging rate continues smoothly.

{
  "_msgid": "ba259d12d2062aa3",
  "payload": {
    "command": "SetChargingProfile",
    "data": {
      "connectorId": 1,
      "csChargingProfiles": {
        "chargingProfileId": 1,
        "transactionId": 69357,
        "stackLevel": 0,
        "chargingProfilePurpose": "TxProfile",
        "chargingProfileKind": "Relative",
        "chargingSchedule": {
          "chargingRateUnit": "A",
          "chargingSchedulePeriod": [
            {
              "startPeriod": 0,
              "limit": 12,
              "numberPhases": 1
            }
          ]
        }
      }
    }
  },
  "topic": "SetChargingProfile",
  "_event": "node:2ee04367c8ee7e11"
}

Note also that a session transactionId is required before submitting the profiles otherwise it will fail.
It's obtained by running a RemoteStartTransaction first (posted above), which generates the transaction ID, which is saved to context.
I allow 20 seconds (as it doesn't arrive quickly!) using a delay node, then populate the profile by getting it from context thereafter.

Thanks. So far I configured the RemoteStartTransaction to be issued once the Preparing StatusNotification is received. The guys are at holiday right now, so I had no change to test it yet.
But now I understand why the transactionId is required, because I don't see that being sent when you start the charging manually.

Thanks for the details. I only had time to play around with it today. So far only triggering this manually for testing.

The charging is in progress now, and I get the transaction id from the MeterValues messages which is now 34844. With the two inject nodes I send in a TxDefaultProfile and later a TxProfile and also make sure that I inject the proper transaction ID. In both cases te response shows that it is accepted, but I am not seeing any change in the charging speed.

The only change I made is to set the limit to 16A on all 3 phases (as this is a 3 phase CP):

            "chargingSchedule": {
                "chargingRateUnit": "A",
                "chargingSchedulePeriod": [
                    {
                        "startPeriod": 0,
                        "limit": 16,
                        "numberPhases": 3
                    }
                ]
            }

The only thing I can see that this charging session was started manually, not my RemoteStartTransaction. But that should not make a difference, right?

I also found this site which explains the different profiles: How to use smart charging with OCPP 1.6J

Here it menions the Maximum Profile:

{
    "command": "SetChargingProfile",
    "data": {
        "connectorId": 0,
        "csChargingProfiles": {
            "chargingProfileId": 1,
            "stackLevel": 0,
            "chargingProfilePurpose": "ChargePointMaxProfile",
            "chargingProfileKind": "Relative",
            "chargingSchedule": {
                "chargingRateUnit": "A",
                "chargingSchedulePeriod": [
                    {
                        "startPeriod": 0,
                        "limit": 16,
                        "numberPhases": 3
                    }
                ]
            }
        }
    }
}

I though maybe there is a default 10A limit in this charger, which is overwriting the 16A given for the TxProfile. But it does absolutely nothing for me. The charger accepts all chargning profiles but the charge level remains 10A on each phase. Btw, the ChargePointMaxProfile needs to be sent to connectorId: 0 and does not need a transactionId.

I also thought that maybe the charger has some other profiles in higher stackLevel. Therefore I also tried clearing the profiles:

{
    "command": "ClearChargingProfile",
    "data": {
        "connectorId": 0,
        "chargingProfilePurpose": "ChargePointMaxProfile"
    }
}

Interestingly, the CP only accpeted clearing the MaxProfile and not the Tx profiles. For those it sent an unknown command. But regardless there is no change in the charging speed, it remains 3x10A.
I am starting considering that the OCPP implementation in these chargers is a bit buggy.

I'm not familiar with 3 phase... but...

Try

{
    "command": "ClearChargingProfile",
    "data": {
        "connectorId": 0,
        "chargingProfilePurpose": "TxProfile"
    }
}

Yes, I did and I get unknown as a response.

But I just realized ChargePointMaxProfile needs to be sent to connectorId: 0 as this applies to the entire CP, TxProfile needs to go to the actual connector. Therefore the command maybe should be sent to connector 1:

{
    "command": "ClearChargingProfile",
    "data": {
        "connectorId": 1,
        "chargingProfilePurpose": "TxProfile"
    }
}

I will try tomorrow, getting late for me.

Yes, connector 1 works for me.

Although I never need to clear the profile as I'm constantly overwriting it using chargingProfilePurpose: TXProfile in the charging profile itself.
I use that because it gives me control over the charging limit, so I can change it without ending the current session.
For example, if there isn't much 'free' solar power, the flow will drop the charging rate accordingly.

Just an update: it is still not working, I raised a service request with the manufacturer. Let's see if there is a trick with this CP I am not aware of.