Skip to content
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

refactor DiagLayer into a class hierarchy #326

Merged
merged 4 commits into from
Aug 12, 2024

Conversation

andlaus
Copy link
Member

@andlaus andlaus commented Jul 31, 2024

Instead of a single class for all types of diagnostic layers, ODX specifies a hierarchy of types: ECU shared datas, protocols, functional groups, base variants and ECU variants where all but ECU shared data layers derive from a "hierarchy element" to indicate that these may be a member of a inheritance hierarchy defined using PARENT-REFs. This patch implements this approach.

Andreas Lauser <andreas.lauser@mercedes-benz.com>, on behalf of MBition GmbH.
Provider Information

@andlaus andlaus requested a review from kayoub5 July 31, 2024 09:38
@andlaus andlaus force-pushed the diag_layer_hierarchy branch from 5e33bbb to 545e7d6 Compare July 31, 2024 10:22
return self._diag_variables

@property
def variable_groups(self) -> NamedItemList[VariableGroup]:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

those are exclusive to ecu variants?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope, but the XSD defines them separately for ECU-SHARED-DATA, FUNCTIONAL-GROUP, BASE-VARIANT and ECU-VARIANT layers (note: not for PROTOCOL). To avoid confusion, I decided to simply match the spec even if I think that their approach is rather inelegant...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

python allows multi inheritance, so move them to shared class

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this would be quite confusing for people who try to match the XSD with the python code. (if the specification did it this way, I would be all for it...)



@dataclass
class EcuVariantRaw(HierarchyElementRaw):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the concept of raw diaglayer was introduced to reduce the complexity of the diag layer api

if diag layer was split, then that concept should have been no longer needed, instead it's getting duplicate, thus making the diaglayer api more complex, and negating its original purpose

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not quite: the reason why we have the split between "raw" and "full" layers is that the "full" layers provide more objects than they define directly because of the various inheritance mechanisms. IOW, it is impossible to reconstruct the contents of the original XML file using just full layers. I agree this makes it quite complex and error prone, but so far I could not come up with a better solution...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the "raw" part could be shared as far as I have seen the code, you don't need a new class for each "raw" type

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to keep it simple and stupid even if this comes with a few copy-and-pasted lines. (who knows, maybe ODX 2.3 will modify one of the layers but not the others..)

# as self.diag_layer_raw.
result.protocol_raw = deepcopy(self.protocol_raw, memo)

return result
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am seeing almost identical copy-pasted classes, and still don't see the benefit in term of making things simpler

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the purpose of this PR is not to make this stuff simpler, it is to cling to the spec and allowing to eliminate a few systematic errors this way. E.g., if the spec says that a protocol ought to be referenced, we can now expect a Protocol class instead of a DiagLayer. Also, attributes which are not present in the specification do no longer exist in our classes (e.g., parent refs in ECU-SHARED-DATA)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ODX model is for serialization, for actual diagnostic encoding/decoding, you may want to take a look at the standard D-PDU API model.

Copy link
Member Author

@andlaus andlaus Aug 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I use this ODX data to talk to ECUs all the time. as far as I know, the PDX files are even the canonical source of diagnostics data upon which all tools except candela studio build in my company...

tests/test_decoding.py Outdated Show resolved Hide resolved
@andlaus andlaus force-pushed the diag_layer_hierarchy branch from 545e7d6 to f315456 Compare August 2, 2024 11:01
@andlaus andlaus force-pushed the diag_layer_hierarchy branch 3 times, most recently from 7a00f2b to 0f9e950 Compare August 2, 2024 19:47
num_dops.append(len(ddds.data_object_props))
else:
num_dops.append(0)
num_comparam_refs.append(len(getattr(variant, "comparams_refs", [])))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's going on here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not all diag layers provide .comparam_refs anymore. (Also, the compare tool is in serious need of refactoring...)

for prot_snref in self.protocol_snrefs
])
else:
diag_layer = odxrequire(context.diag_layer)
self._protocols = NamedItemList([
resolve_snref(prot_snref, diag_layer.protocols)
resolve_snref(prot_snref, getattr(diag_layer, "protocols", []))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid getattr, it allows for typing bugs that mypy can't catch, use isinstance instead

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just tried this: This is pretty difficult to do as part of a list comprehension and it would not come with a major benefit because resolve_snref(..., Protocol) returns a protocol object from the type checking perspective.



@dataclass
class EcuVariant(HierarchyElement):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anything that BaseVariant have, should also be available in EcuVariant

Suggested change
class EcuVariant(HierarchyElement):
class EcuVariant(BaseVariant):

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, but unfortunately this is not how the spec defines things:

Screenshot_20240805_183755

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(note that base variants contain "BASE-VARIANT-PATTERNS" instead of "ECU-VARIANT-PATTERNS".)

class EcuVariant(HierarchyElement):

@property
def ecu_variant_raw(self) -> EcuVariantRaw:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why expose the raw data?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because some low-level code might need to know what data was defined by the XML and in order to make typecheckers (mypy) happy, we cannot use .diag_layer to access EcuVariant-specific attributes...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"might" or "do need" ? if "might" then delete, you are not gonna need it, see https://en.m.wikipedia.org/wiki/You_aren't_gonna_need_it

Copy link
Member Author

@andlaus andlaus Aug 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for code which is currently in the main branch odxtools it is not needed, but I have some private scripts which fumble with the internals of ODX databases, and they would become even hackier than they already are...

return cast(EcuVariantRaw, self.diag_layer_raw)

@property
def diag_variables_raw(self) -> List[Union[DiagVariable, OdxLinkRef]]:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The raw part should be internal, why expose it as a property ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

basically just to make the API of EcuVariant a superset of EcuVariantRaw (i.e., substituting EcuVariantRaw objects by EcuVariant ones should just work without any changes.)


return self._compute_available_objects(get_local_objects_fn, not_inherited_fn)

def _compute_available_variable_groups(self,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see copy-paste of this function and similar "compute" functions, move them to a helper class

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I also don't like the copy and paste across diag layers, given that this is not too much code, the added complexity of the cure would IMO be worse than the disease...

@@ -17,6 +18,14 @@
from .variablegroup import VariableGroup


@runtime_checkable
class HasDiagVariables(typing.Protocol):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What layers don't have diag variables?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAICS just Protocol.

@andlaus andlaus force-pushed the diag_layer_hierarchy branch from d15213b to 9da8569 Compare August 5, 2024 16:19
andlaus added 2 commits August 5, 2024 18:56
Instead of a single class for all types of diagnostic layers, ODX
specifies a hierarchy of types: ECU shared datas, protocols,
functional groups, base variants and ECU variants whereas all but ECU
shared data layers derive from a "hierarchy element" to indicate that
these may be a member of a inheritance hierarchy defined using
`PARENT-REF`s. This patch implements this approach.

Signed-off-by: Andreas Lauser <andreas.lauser@mbition.io>
Signed-off-by: Florian Jost <florian.jost@mbition.io>
instead, let's use type checking protocols. Note that this trick
cannot be used for the `hasattr("protocol")` instances of `DiagComm`
because this would lead to cyclic imports.

thanks to [at]kayoub5 for suggesting this!

Signed-off-by: Andreas Lauser <andreas.lauser@mbition.io>
Signed-off-by: Florian Jost <florian.jost@mbition.io>
@andlaus andlaus force-pushed the diag_layer_hierarchy branch from 5038cf6 to 124e949 Compare August 5, 2024 16:56
andlaus added 2 commits August 6, 2024 11:21
…g layers

If the underlying raw layer does not provide a DDDS, and inheritance
does not apply either, an empty object is created. This should help to
avoid having to handle a few special cases in user code.

Thanks to [at]kayoub for the catch!

Signed-off-by: Andreas Lauser <andreas.lauser@mbition.io>
Signed-off-by: Florian Jost <florian.jost@mbition.io>
this slightly increases the coverage of the tests. (With the exception
of the variant pattern functionality, `BaseVariant`s are supposed to
exhibit the same API ase `EcuVariant`s.)

Signed-off-by: Andreas Lauser <andreas.lauser@mbition.io>
Signed-off-by: Florian Jost <florian.jost@mbition.io>
@andlaus andlaus force-pushed the diag_layer_hierarchy branch from 124e949 to bbd05fd Compare August 6, 2024 09:24
@andlaus
Copy link
Member Author

andlaus commented Aug 6, 2024

@kayoub5: do you consider this ready to be merged? (after merging this, I'll do an v8.0 release.)

@andlaus
Copy link
Member Author

andlaus commented Aug 12, 2024

@kayoub5: if no new issues crop up until then, I'll merge this PR on Thursday or Friday...

@kayoub5
Copy link
Collaborator

kayoub5 commented Aug 12, 2024

@kayoub5: if no new issues crop up until then, I'll merge this PR on Thursday or Friday...

I am on the beach, with little access to the pc, I believe I already reviewed most of this PR.

If I find issues later, I will create hotfix PRs.

@andlaus
Copy link
Member Author

andlaus commented Aug 12, 2024

great, thanks. Let's get this merged. (have a nice vacation :))

@andlaus andlaus merged commit 61943f0 into mercedes-benz:main Aug 12, 2024
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants