lorenzodonini / ocpp-go

Open Charge Point Protocol implementation in Go
MIT License
277 stars 126 forks source link

Issue with DataTransfer on both ocpp 1.6 and 2.0.1 #257

Open KatyushaScarlet opened 8 months ago

KatyushaScarlet commented 8 months ago

Hi, I'm trying to develop a program to convert OCPP 1.6 requests to OCPP 2.0.1 requests and I'm running into a strange problem: Filling the data field in DataTransferRequest with bytes[] obtained from json or string conversion is converted to base64 on the receiving end. This problem occurs in my attempts to use either 1.6 or 2.0.1 examples.

For example, on the charge point my code like this:

    type DataSample struct {
        SampleString string `json: "sample_string"`
        SampleValue float64 `json: "sample_value"`
    }

......

    data := DataSample{SampleString: "dummyData", SampleValue: 42}
    jsonData, _ := json.Marshal(data)
    dataConf, err := chargePoint.DataTransfer("vendor1", func(request *core.DataTransferRequest) {
        request.MessageId = "message1"
        request.Data = jsonData
    })

However, in the DataTransferRequest on the receiving end, I will get an object of type {interface{} | string} with this value:

`eyJzYW1wbGVfc3RyaW5nIjoiZHVtbXlEYXRhIiwic2FtcGxlX3ZhbHVlIjo0Mn0=`

It is a string using base64 encoding with the original value:

{"sample_string": "dummyData", "sample_value":42}

If this is normal? but when I look at the 2.0.1 example, I see that the csms code is like this:

func (c *CSMSHandler) OnDataTransfer(chargingStationID string, request *data.DataTransferRequest) (response *data.DataTransferResponse, err error) {
    var dataSample DataSample
    err = json.Unmarshal(request.Data.([]byte), &dataSample)
    if err ! = nil {
        logDefault(chargingStationID, request.GetFeatureName()).
            Errorf("invalid data received: %v", request.Data)
        return nil, err
    }
    logDefault(chargingStationID, request.GetFeatureName()).
        Infof("data received: %v, %v", dataSample.SampleString, dataSample.SampleValue)
    return data.NewDataTransferResponse(data.DataTransferStatusAccepted), nil
}

When I tested this on example, I found that both 1.6 and 2.0.1 exmaple get a base64 string for request.Data and cause a fatal exception on csms (due to json.Unmarshal decoding failure). I've also looked at the test package, but in 2.0.1 I only see suite.chargingStation.DataTransfer(vendorId) , and I don't find a place to set request.Data.

Since I don't have access to a physical charging post at the moment, I don't know which type of data is the correct one to pass in a real usage scenario. I'm not sure if I'm missing some setting somewhere, or if there's something wrong with my code.

Thanks!

KatyushaScarlet commented 8 months ago

Now, I'm using this code on csms, referencing the OCPP 1.6 test package, so that it can successfully receive DataTransfer requests from charge point.

type DataSample struct {
    SampleString string  `json:"sample_string"`
    SampleValue  float64 `json:"sample_value"`
}

func parseDataSample(req *data.DataTransferRequest) (DataSample, error) {
    jsonString, _ := json.Marshal(req.Data)
    var result DataSample
    err := json.Unmarshal(jsonString, &result)
    return result, err
}

func (c *CSMSHandler) OnDataTransfer(chargingStationID string, request *data.DataTransferRequest) (response *data.DataTransferResponse, err error) {
    dataSample, err := parseDataSample(request)
    if err != nil {
        logDefault(chargingStationID, request.GetFeatureName()).
            Errorf("invalid data received: %v", request.Data)
        return nil, err
    }
    logDefault(chargingStationID, request.GetFeatureName()).
        Infof("data received: %v, %v", dataSample.SampleString, dataSample.SampleValue)
    return data.NewDataTransferResponse(data.DataTransferStatusAccepted), nil
}

But I'm still not sure if it will work on the real scenario.

lorenzodonini commented 8 months ago

Hey @KatyushaScarlet , you don't need to marshal anything beforehand. If pre-marshaled, the data field will effectively be a byte array, which will be transformed to base64 when marshaled, which is probably not what you want.

Just pass the following:

data := DataSample{SampleString: "dummyData", SampleValue: 42}
dataConf, err := chargePoint.DataTransfer("vendor1", func(request *core.DataTransferRequest) {
    request.MessageId = "message1"
    request.Data = &data
})

The go struct will be automatically marshaled to JSON.

On the receiving end, since the parser has no idea what type to expect when initially unmarshaling the message, the data is unmarshaled into an interface type (typically a map[string]interface{}). To parse it into a specific type, you'll need to resort to the workaround you correctly pointed out in the parseDataSample function. This will work in a real-world scenario just fine, assuming you know what data type is being received.

Nevertheless, I'll add a utility function to the data transfer feature to help with unmarshaling the payload into arbitrary types.