-
Notifications
You must be signed in to change notification settings - Fork 20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Databroker API V2 #33
Databroker API V2 #33
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #33 +/- ##
==========================================
- Coverage 50.92% 50.77% -0.15%
==========================================
Files 31 31
Lines 11878 11913 +35
==========================================
Hits 6049 6049
- Misses 5829 5864 +35 ☔ View full report in Codecov by Sentry. |
proto/kuksa/val/v2/val.proto
Outdated
// Unique request id that can be used to identify the response. | ||
int32 request_id = 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need that in the unary publish call?
proto/kuksa/val/v2/types.proto
Outdated
message Error { | ||
uint32 code = 1; | ||
string reason = 2; | ||
string message = 3; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
message Error { | |
uint32 code = 1; | |
string reason = 2; | |
string message = 3; | |
} | |
message Error { | |
ErrorCode code = 1; | |
string message = 2; | |
} | |
message ErrorCode { | |
INVALID_ARGUMENT = 3; | |
NOT_FOUND = 5; | |
PERMISSION_DENIED = 7; | |
} |
a3e7b29
to
6f12990
Compare
} | ||
|
||
message ListValuesResponse { | ||
repeated Datapoint datapoints = 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think, here we need a map<string, Datapoint> as we cannot know beforehand which and how many datapoints will be returned
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, and I have to admit that I had to double check the Datapoint that it does not include the signal name because this would somehow be my assumption when reading "Datapoint".
} | ||
|
||
message GetValueRequest { | ||
SignalID signal_id = 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it would be better having two separate requests: GetValue(ByName)Request
and GetValueByIdRequest
.
I mean, we introduced the single signal get for making life easier for a developer, but know we put "the burden" on them to make this extra set step for the oneof ...
} | ||
|
||
message GetValuesRequest { | ||
repeated SignalID signal_ids = 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same
proto/kuksa/val/v2/val.proto
Outdated
PublishValuesRequest publish_values_request = 2; | ||
// Sent to acknowledge the acceptance of a batch actuate | ||
// request. | ||
BatchActuateResponse batch_actuate_response = 3; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure, if we should re-use the actuate requests/responses for the provider stream: At least for the response of the provider we need to define an error message in case the provider needs to reject something.
// Returns (GRPC error code): | ||
// NOT_FOUND if any of the signals are non-existant. | ||
// PERMISSION_DENIED | ||
// - if access is denied for any of the signals. | ||
// - if the signal is already provided by another provider. | ||
// INVALID_ARGUMENT | ||
// - if the data type used in the request does not match | ||
// the data type of the addressed signal | ||
// - if the published value is not accepted, | ||
// e.g. if sending an unsupported enum value | ||
rpc PublishValue(PublishValueRequest) returns (PublishValueResponse); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like to have a "oneshot" publish for multiple values: With that a provider can easily publish a whole bunch of attributes (I'd prefer the "batch" version over the single one on the provider side)
proto/kuksa/val/v2/val.proto
Outdated
|
||
message PublishValuesResponse { | ||
int32 request_id = 1; | ||
map<string, Error> status = 2; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we return errors per signal in this operation? In the other cases we said, we just want to have one overall error ...
|
||
import "kuksa/val/v2/types.proto"; | ||
|
||
service VAL { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ugly service name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what would you suggest :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Databroker
, Broker
, ValBroker
(in that order of preferance)
proto/kuksa/val/v2/val.proto
Outdated
|
||
message PublishValuesRequest { | ||
int32 request_id = 1; /// Unique request id for the stream that can be used to identify the response. | ||
map<string, Datapoint> datapoints = 2; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To allow both, id-based and path-based signal referencing, could we have something like this:
map<string, Datapoint> datapoints = 2; | |
oneof mapping { | |
map<string, Datapoint> datapointsByPPath = 2; | |
map<int32, Datapoint> datapointsById = 3; | |
} |
or a separate request called PublishValuesByIdRequest
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not map<SignalD, Datapoint> datapoints ? Maybe I am thinking too easy here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In protobuf the key of a map must be either an integral type or a string; see: https://protobuf.dev/programming-guides/proto3/#maps
proto/kuksa/val/v2/val.proto
Outdated
// Response to a provide actuator request. | ||
ProvideActuatorResponse provide_actuator_response = 1; | ||
// Acknowledgement that a published value was received. | ||
PublishValuesResponse publish_value_response = 2; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be publish_values_response with an s?
} | ||
} | ||
|
||
message SignalID { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we call the message signal? For me it was hard to follow during the usage of the SingalID message that this actually either an id or the path.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The term "signal" would be more than just the identifier of it. Therefore, I like the name "SignalId": "Id" is short for "identifier" which not necessarily has to be a number. It could be anything capable of serving as a unique id - therefore also a string.
message BatchActuateStreamResponse { | ||
} | ||
|
||
message OpenProviderStreamRequest { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the rational of cramping multiple concerns into a single function call or stream? Is the idea to "save" on the number of open (TCP) connections?
To me this sounds like there could be problems on both side of the stream of correlating the different operations handled over the same stream.
That is why it would feel more intuitive for me to have PublishValues as Stream and a ProvideActuation function here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But these calls most likely would be transmitted over the same gRCP channel anyway when they originate from the same client, right?
proto/kuksa/val/v2/val.proto
Outdated
} | ||
|
||
message PublishValueResponse { | ||
Error error = 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does error make sense here? We give back a general grpc error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is a "request in a request". If using a gRPC error, it would terminate the overall ProviderStream ...
|
||
message PublishValuesResponse { | ||
int32 request_id = 1; | ||
map<int32, Error> status = 2; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does error make sense here? We give back a general grpc error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is a "request in a request". If using a gRPC error, it would terminate the overall ProviderStream ...
Hi I have looked into upgradeing on of our use cases to this API, and either the API misses efficient actuation or I missed it :D Let's say I have a control loop controlling e.g. the SteeringAngle, I understand on the Provider Side I can use I am unclear how it works on application level. I see there is an |
As long as the data broker is not "realtime capable" imho we shouldn't do something like control loops via data broker. I think, consumers should set target values with a low frequency only. Otherwise we would have to guarantee a max latency. |
I recall that this topic was on our minds for future discussion. However, we initially decided that the first version would only actuate a signal using a unary call, as there weren't that many use cases for real time actuating on an actuator using a gRPC stream. I remember we discussed also an example of raising the car window by pressing a button, but that was the extent of our conversation. |
It is not about realtime (as in guaranteed latency), and I think not so mich different whether operating open loop or closed loop. It it is more a CPU load topic: In the usecase we had a factor 6 reduction in CPU load (comparing unary set to streams), so it would be bad if the "new" API would be 6 times more inefficient. For example there is also a subscribe call, instead of forcing users to poll via get. |
One specific example is the control of the FlexCar Chassis (https://arena2036.de/de/flexcar) via a remote connection. This is done in the https://sofdcar.de/ project via KUKSA. This has
|
Yes, understood. But even in an stream-optimized version we would not be able to guarantee latency, which means safety-critical - or at least "timing-critical" - operations like remote steering or control loops should not be done via the broker. What happens if you want to stop something, but for some reason the latency is temporary > 1 second? I think, it's better setting a target value and trust on the local ECUs trying to gain that in a reliable and safe way. But maybe there are use cases, I'm not aware of, which would require these frequent actuator updates. |
oneof value_state { | ||
ValueFailure failure = 2; | ||
Value value = 3; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@argerus @rafaeling
Why I like the "oneof":
Assuming the ValueFailure represents the reason why a "real" value is missing (maybe we should rename it to "MissingReason"?), the oneof has some advantages over two separate fields (e.g. value and value_state):
- If having a separate status with the additional state "OK" - meaning a valid value is present -, this could potentially be in mis-sync with the value field:
- What, if status is "OK", but no value is present?
- Or vice versa, status is not "OK", but a value is present?
- On server side it's not possible to set both in parallel (prevented by the generated code - at least in C++)
- It gives a small optimization during transmission via grpc: There either the value or the "MissingReason" is transferred.
I don't see many disadvantages: - Code-wise (imho) it is easier, because I can better trust, that those invalid combinations of value and value_state described above cannot occur.
Just checking if a value is there and otherwise ignoring the reason why it's missing is as easy as just having a value field or having an additional value_state field. At least in C++:
kuksa::val::v2::Datapoint datapoint;
if (datapoint.has_value()) {
auto value =datapoint.value();
} else {
// handle missing value, e.g.:
throw MissingValueException();
}
This C++ code would be the same - regardless if we have:
(1)
message Datapoint {
google.protobuf.Timestamp timestamp = 1;
Value value = 2;
}
or (2)
message Datapoint {
google.protobuf.Timestamp timestamp = 1;
Value value = 2;
ValueState state = 3;
}
or (3)
message Datapoint {
google.protobuf.Timestamp timestamp = 1;
oneof value_state {
Value value = 2;
MissingReason reason = 3;
}
}
Besides that, I think transporting additional information about a present value (like some quality information (like "outdated" or other things we discussed today), doesn't belong into an information field describing reasons why a value is missing. Those informations aren't related.
Closing this one as it has an obsolete source branch. Current API v2 work is ongoing in https://github.com/eclipse-kuksa/kuksa-databroker/tree/feature/databroker-api-v2 but no PR back to main branch exist as of today |
No description provided.