MicroOcpp icon indicating copy to clipboard operation
MicroOcpp copied to clipboard

OCMF / Eichrecht transactionData hooks

Open razvanphp opened this issue 4 months ago • 1 comments

We're implementing the OCPP 1.6 version of SignedMeterValues in our firmware according to this document: https://openchargealliance.org/wp-content/uploads/2025/02/signed_meter_values-v10.pdf

I saw your response on https://github.com/matth-x/MicroOcpp/issues/193#issuecomment-1732340875 and wanted to ask if there is already support for tapping into the StopTransaction.req process, blocking it until the signature is ready and putting the signed data into the transactionData key.

Currently, we also generate one at the start of the transaction and would be great to store it via the internal mechanism of MicroOcpp so that it's automatically picked up at the end.

We would love to contribute a more straightforward way of doing this back as Pull request, but wanted to ask for your opinion @matth-x on how to implement this. Documentation or examples should also be added, as we could not find much about this online.

Thank you in advance! R

razvanphp avatar Aug 29 '25 23:08 razvanphp

~PS: would something like this work?~

I have vibe-coded a working class, validated successfully with Transparenzsoftware, but I'm pretty sure it could be simplified even further (maybe extending the addMeterValueInput with class SampledValueDeSerializer<String> {} template, like in #112 ?), but here is the current scheleton:

#include "SignedMeterValueSampler.h"
#include <MicroOcpp/Debug.h>
#include <Arduino.h>

using namespace MicroOcpp;

// forward declaration from main.cpp (use global ::String to reference Arduino String)
extern ::String buildMeterBillingDataset(int connectorId);

SignedMeterValueSampler::SignedMeterValueSampler(int connectorId) : 
    SampledValueSampler(SampledValueProperties()),
    connectorId(connectorId) {
    
    properties.setFormat("SignedData");
    properties.setMeasurand("Energy.Active.Import.Register.Signed");
    properties.setUnit("kWh");
    properties.setLocation("Outlet");
}

std::unique_ptr<SampledValue> SignedMeterValueSampler::takeValue(ReadingContext context) {
    MicroOcpp::String resultVal("");

    if (context == ReadingContext_TransactionBegin) {
        ::String signedJson;
        // do execute signing and save into String
        resultVal = MicroOcpp::String(signedJson.c_str());
    } else if (context == ReadingContext_TransactionEnd) {
        ::String signedJson;
        // do execute signing and save into String
        resultVal = MicroOcpp::String(signedJson.c_str());
    }

    properties.setMeasurand("Energy.Active.Import.Register");

    return std::make_unique<SampledValueConcrete<MicroOcpp::String, SampledValueDeSerializer<MicroOcpp::String>>>(
        properties, context, std::move(resultVal));
}

std::unique_ptr<SampledValue> SignedMeterValueSampler::deserializeValue(JsonObject svJson) {
    using DeSerializer = SampledValueDeSerializer<MicroOcpp::String>;
    MicroOcpp::String val = DeSerializer::deserialize(svJson["value"] | "");
    ReadingContext ctx = deserializeReadingContext(svJson["context"] | "NOT_SET");
    return std::unique_ptr<SampledValueConcrete<MicroOcpp::String, DeSerializer>>(
        new SampledValueConcrete<MicroOcpp::String, DeSerializer>(properties, ctx, std::move(val)));
}

in your main.cpp:

#include "Ocpp/SignedMeterValueSampler.h"

  addMeterValueInput(
      std::unique_ptr<SignedMeterValueSampler>(new SignedMeterValueSampler(connectorId)),
      connectorId);

the only requirement is to set StopTxnSampledData = Energy.Active.Import.Register.Signed to get the two sampledValue for context Transaction.Begin and Transaction.End.

One also need to take care, since this Sampler will also be called for intermediate values (MeterValues) with context = ReadingContext_SamplePeriodic, not sure how to handle those or skip them nicely, returning nullptr is causing a panic, returning empty JSON is also not cool as it will produce empty values:

[06:04:58][OCPP] [MO] debug (MeterValue.cpp:158):
[06:04:58][OCPP] Updating observed samplers due to config change or samplers added
[06:04:58][OCPP] [MO] info (SignedMeterValueSampler.cpp:56):
[06:04:58][OCPP] SignedMeterValueSampler::takeValue: called with context Sample.Periodic - returning benign Raw sample
[06:04:58][OCPP] [MO] debug (MeteringConnector.cpp:254):
[06:04:58][OCPP] advance MV front
[06:04:58][OCPP] [MO] ERROR (MeterValues.cpp:55):
[06:04:58][OCPP] Energy meter reading not convertible to JSON
[06:04:58][OCPP] [MO] Send: [2,"bca4fd0c-44d3-247d-c557-06eaa7f13889","MeterValues",{"connectorId":1,"transactionId":130913664,"meterValue":[]}]
[06:04:58][OCPP] [MO] Recv: [3,"bca4fd0c-44d3-247d-c557-06eaa7f13889",{}]
[06:04:58][OCPP] [MO] debug (MeterValues.cpp:79):
[06:04:58][OCPP] Request has been confirmed
[06:04:58][OCPP] [MO] debug (MeteringConnector.cpp:299):
[06:04:58][OCPP] drop MV front

Would you accept a pull request for this feature? I have a feeling it will become mandatory in the whole EU soon and many people would benefit from this, without fiddling with custom samplers. I would also add this to the readme in the PR.

Thank you for your work! R

razvanphp avatar Aug 29 '25 23:08 razvanphp