Open-Agriculture / AgIsoStack-plus-plus

AgIsoStack++ is the completely free open-source C++ ISOBUS library for everyone
https://agisostack.com/
MIT License
187 stars 41 forks source link

[Core]: Add more concrete ways of sending data #345

Closed GwnDaan closed 7 months ago

GwnDaan commented 11 months ago

Ultimately, the goal would be to be able to pass in any data structure to send_can_message, and it will figure out how to send the data. This PR should be a good base for that vision. The DataSpan class allows us to wrap any data structure and 'view' it's contents.

What's changed:

Future plans:

ad3154 commented 11 months ago

Separated calls with 'data pointer' and 'chunk callback' parameters. There won't be a use case where both are used at the same time

This seems OK to me conceptually. Maybe its a bit easier to read and maintain, plus fewer arguments for people to think about.

But, I'm not really bought into much of this for a few reasons...

  1. Adding a function for explicitly sending to the global address slightly reduces the abstraction away from addresses to CFs and PGNs. One of my main goals has always been to minimize the need for the awareness of addresses at all.
  2. Adding a function for sending to global doesn't reduce the chance of failure. A user could easily call send_can_message_global with a PGN of EF24 and it would have to fail, same as now where if they tried to send EFFF with a destination it would fail. So I don't think it really gains us anything.
  3. As long as we only have DataSpan<const std::uint8_t> I don't think it really gains us anything except being able to also accept std::array without the user doing .data() and .size(). Seems like not enough to start deprecating parts of our public API.
  4. Adding the Data span adds confusion for the user I think if we add more than just uint8_t containers as valid types. I am not sure if this exact scenario is in your intention for this, but, for example, if down the road someone passes in a vector of float or uint16_t because they are trying to send a UDS message (ISO 14229) or something, the endianness is backwards from ISO11783 if I recall correctly, but explicitly tolerated on an ISO11783 bus. So chances are high we'd corrupt their CAN message, which may not be clear to them or desirable from our public interface. Likewise, most people I've seen using other CAN stacks also only use either uint8_t *, vector<uint8_t>, or unsigned char *, so it's what people are probably familiar with, and if we're stuck with uint8_t I'm back to "I don't know how much this really gains us".
  5. This one is just personal preference but I like using standard types and not wrapper templates where possible... I think it reduces the learning curve for people with less C++ experience and minimizes the binary size.
GwnDaan commented 11 months ago

Hmm, I think all your points are valid. Let's have a little discussion haha

  1. Adding a function for explicitly sending to the global address slightly reduces the abstraction away from addresses to CFs and PGNs. One of my main goals has always been to minimize the need for the awareness of addresses at all.

I agree, and that should still be the main goal. How it currently is, is that there is a function where destination is optional, meaning that someone may forget to put a destination, or not understanding. Though I agree this is pretty unlikely, it was the sole reason why I added the _global function and made the destination required for the normal one. I'm fine with removing it if you still think it is unnecessary.

  1. Adding a function for sending to global doesn't reduce the chance of failure. A user could easily call send_can_message_global with a PGN of EF24 and it would have to fail, same as now where if they tried to send EFFF with a destination it would fail. So I don't think it really gains us anything.

Yes, that's true, it was simply a synonym for the normal one but then without a destination. It might only give us better user experience if my reasoning above holds for some people haha.

  1. As long as we only have DataSpan I don't think it really gains us anything except being able to also accept std::array without the user doing .data() and .size(). Seems like not enough to start deprecating parts of our public API.

Okay, deprecating parts of the public API is maybe too much indeed and can be removed. The main reason for DataSpan is that you make sure after construction of the Span, the pointer and size never changes. Hence it's integrity is held after passing it around everywhere. We can't make mistakes altering the data, since we implicitly can't. So by converting any data structure into the DataSpan using it's factory methods, we only have to test the factory methods once, and we are certain that everywhere the data is still unchanged. Ideally, I would've used std::span but that is only available with C++20.

Adding the Data span adds confusion for the user I think if we add more than just uint8_t containers as valid types. I am not sure if this exact scenario is in your intention for this, but, for example, if down the road someone passes in a vector of float or uint16_t because they are trying to send a UDS message (ISO 14229) or something, the endianness is backwards from ISO11783 if I recall correctly, but explicitly tolerated on an ISO11783 bus. So chances are high we'd corrupt their CAN message, which may not be clear to them or desirable from our public interface. Likewise, most people I've seen using other CAN stacks also only use either uint8_t , vector, or unsigned char , so it's what people are probably familiar with, and if we're stuck with uint8_t I'm back to "I don't know how much this really gains us".

True, it's probably better to refactor it to CANDataSpan instead that only allows for std::uint8_t values.

This one is just personal preference but I like using standard types and not wrapper templates where possible... I think it reduces the learning curve for people with less C++ experience and minimizes the binary size.

I see, I can change that to just be std::uint8_t instead, if we still want any of the changes hahah. Let me know 😄

GwnDaan commented 11 months ago

Oh and, basically the main reason I did these changes is to facilitate:

combining 'dataspan' and 'chunk callback' by using a common interface between the two

What are your thoughts on that? It would be really nice to also use it higher up in the transport protocols I think

sonarcloud[bot] commented 10 months ago

SonarCloud Quality Gate failed.    Quality Gate failed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 12 Code Smells

57.9% 57.9% Coverage
0.0% 0.0% Duplication

idea Catch issues before they fail your Quality Gate with our IDE extension sonarlint SonarLint