Skip to content

Bug Report: GetCompositeSchedule with duration=0 returns default limits instead of active charging profile #1169

@fernandoaguilar

Description

@fernandoaguilar

OCPP Version

OCPP1.6, OCPP2.0.1, OCPP2.1

Describe the bug

Summary

When calling GetCompositeSchedule with duration: 0, the response returns hardware default limits instead of the currently active ChargePointMaxProfile (OCPP 1.6) / ChargingStationMaxProfile (OCPP 2.0.1) limit. Using any non-zero duration correctly returns the profile limit.

Affected Versions

  • OCPP 1.6 (lib/ocpp/v16/profile.cpp)
  • OCPP 2.0.1 (lib/ocpp/v2/profile.cpp)

To Reproduce

Steps to Reproduce

  1. Set a ChargePointMaxProfile on connector 0:
{
  "connectorId": 0,
  "csChargingProfiles": {
    "chargingProfileId": 0,
    "chargingProfileKind": "Absolute",
    "chargingProfilePurpose": "ChargePointMaxProfile",
    "chargingSchedule": {
      "chargingRateUnit": "W",
      "chargingSchedulePeriod": [{"limit": 50000, "startPeriod": 0}]
    },
    "stackLevel": 0
  }
}
  1. Call GetCompositeSchedule with duration: 0:
{
  "connectorId": 0,
  "duration": 0,
  "chargingRateUnit": "W"
}
  1. Observe the response returns hardware default limit (e.g., 66240W) instead of the profile limit (50000W).

  2. Call GetCompositeSchedule with duration: 3600:

{
  "connectorId": 0,
  "duration": 3600,
  "chargingRateUnit": "W"
}
  1. Observe the response correctly returns the profile limit (50000W).

Expected Behavior

GetCompositeSchedule with duration: 0 should return the currently active charging profile limit at that instant (50000W).

Actual Behavior

GetCompositeSchedule with duration: 0 returns the hardware default limit (66240W), ignoring the active charging profile.

Anything else?

Root Cause Analysis

The bug is in the generate_profile_from_periods() function in both v16/profile.cpp (line 428) and v2/profile.cpp (line 411).

The Problem

DateTime current = now;

while (current < end) {  // <-- BUG: When duration=0, now == end, so this is immediately false
    // ... find and process schedules ...
}

When duration: 0:

  • start_time == end_time (after flooring to seconds)
  • now == end
  • The condition current < end evaluates to false
  • The while loop never executes
  • Returns an empty combined profile
  • Empty profile → NO_LIMIT_SPECIFIED → falls back to hardware default limit

Secondary Issue

Even if the loop executed, the schedule matching condition would fail:

if (schedule.end > current)  // Excludes schedules where end == current

Proposed Fix

Change the while loop to a do-while loop to ensure at least one iteration, and fix the schedule matching to include exact time matches:

// Use do-while to ensure at least one iteration for duration=0 case
do {
    // find schedule to use for time: current
    DateTime earliest = end;
    DateTime next_earliest = end;
    const period_entry_t* chosen{nullptr};

    for (const auto& schedule : periods) {
        if (schedule.start <= earliest) {
            // Use >= instead of > to include schedules active at exactly 'current' time
            if (schedule.end >= current && schedule.start <= current) {
                next_earliest = earliest;
                earliest = schedule.start;
                chosen = &schedule;
                break;
            } else if (schedule.end > current) {
                next_earliest = earliest;
                earliest = schedule.start;
                chosen = &schedule;
                if (earliest <= current) {
                    break;
                }
            }
        }
    }
    // ... rest of loop body unchanged ...
} while (current < end);

Affected Files

File Line Issue
lib/ocpp/v16/profile.cpp 428 while (current < end) never executes when now == end
lib/ocpp/v2/profile.cpp 411 Same issue

Additional Context

The OCPP specification allows duration: 0 in GetCompositeSchedule requests. While semantically ambiguous (a schedule of 0 seconds), it's reasonable to interpret this as "what is the current limit at this instant?" and return the active profile's limit rather than falling back to defaults.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions