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

Implement en- and decoding of SYSTEM parameters #347

Merged
merged 2 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 51 additions & 8 deletions odxtools/parameters/systemparameter.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
# SPDX-License-Identifier: MIT
import getpass
from dataclasses import dataclass
from datetime import datetime
from typing import List, Optional
from xml.etree import ElementTree

from typing_extensions import override

from ..decodestate import DecodeState
from ..encodestate import EncodeState
from ..exceptions import odxrequire
from ..exceptions import odxraise, odxrequire
from ..odxlink import OdxDocFragment
from ..odxtypes import ParameterValue
from ..utils import dataclass_fields_asdict
from .parameter import ParameterType
from .parameterwithdop import ParameterWithDOP

# The SYSTEM parameter types mandated by the ODX 2.2 standard. Users
# are free to specify additional types, but these must be handled
# (cf. table 5 in section 7.3.5.4 of the ASAM ODX 2.2 specification
# document.)
PREDEFINED_SYSPARAM_VALUES = [
"TIMESTAMP", "SECOND", "MINUTE", "HOUR", "TIMEZONE", "DAY", "WEEK", "MONTH", "YEAR", "CENTURY",
"TESTERID", "USERID"
kayoub5 marked this conversation as resolved.
Show resolved Hide resolved
]


@dataclass
class SystemParameter(ParameterWithDOP):
Expand All @@ -38,18 +48,51 @@ def parameter_type(self) -> ParameterType:
@property
@override
def is_required(self) -> bool:
raise NotImplementedError("SystemParameter.is_required is not implemented yet.")
# if a SYSTEM parameter is not specified explicitly, its value
# can be determined from the operating system if it is type is
# predefined
return self.sysparam not in PREDEFINED_SYSPARAM_VALUES

@property
@override
def is_settable(self) -> bool:
raise NotImplementedError("SystemParameter.is_settable is not implemented yet.")
return True

@override
def _encode_positioned_into_pdu(self, physical_value: Optional[ParameterValue],
encode_state: EncodeState) -> None:
raise NotImplementedError("Encoding a SystemParameter is not implemented yet.")
if physical_value is None:
# determine the value to be encoded automatically
now = datetime.now()
if self.sysparam == "TIMESTAMP":
kayoub5 marked this conversation as resolved.
Show resolved Hide resolved
physical_value = round(now.timestamp() * 1000).to_bytes(8, "big")
elif self.sysparam == "SECOND":
physical_value = now.second
elif self.sysparam == "MINUTE":
physical_value = now.minute
elif self.sysparam == "HOUR":
physical_value = now.hour
elif self.sysparam == "TIMEZONE":
if (utc_offset := now.astimezone().utcoffset()) is not None:
physical_value = utc_offset.seconds // 60
else:
physical_value = 0
elif self.sysparam == "DAY":
physical_value = now.day
elif self.sysparam == "WEEK":
physical_value = now.isocalendar()[1]
elif self.sysparam == "MONTH":
physical_value = now.month
elif self.sysparam == "YEAR":
physical_value = now.year
elif self.sysparam == "CENTURY":
physical_value = now.year // 100
elif self.sysparam == "TESTERID":
physical_value = "odxtools".encode("latin1")
elif self.sysparam == "USERID":
physical_value = getpass.getuser().encode("latin1")
else:
odxraise(f"Unknown system parameter type '{self.sysparam}'")
physical_value = 0

@override
def _decode_positioned_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
raise NotImplementedError("Decoding SystemParameter is not implemented yet.")
self.dop.encode_into_pdu(physical_value, encode_state=encode_state)
72 changes: 72 additions & 0 deletions tests/test_encoding.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: MIT
import unittest
from datetime import datetime
from typing import List

from odxtools.compumethods.compuinternaltophys import CompuInternalToPhys
Expand All @@ -10,6 +11,7 @@
from odxtools.compumethods.linearcompumethod import LinearCompuMethod
from odxtools.database import Database
from odxtools.dataobjectproperty import DataObjectProperty
from odxtools.decodestate import DecodeState
from odxtools.description import Description
from odxtools.diagdatadictionaryspec import DiagDataDictionarySpec
from odxtools.diaglayers.diaglayertype import DiagLayerType
Expand All @@ -18,6 +20,7 @@
from odxtools.diagnostictroublecode import DiagnosticTroubleCode
from odxtools.diagservice import DiagService
from odxtools.dtcdop import DtcDop
from odxtools.encodestate import EncodeState
from odxtools.environmentdata import EnvironmentData
from odxtools.environmentdatadescription import EnvironmentDataDescription
from odxtools.exceptions import EncodeError
Expand All @@ -27,6 +30,7 @@
from odxtools.parameters.codedconstparameter import CodedConstParameter
from odxtools.parameters.nrcconstparameter import NrcConstParameter
from odxtools.parameters.parameter import Parameter
from odxtools.parameters.systemparameter import SystemParameter
from odxtools.parameters.valueparameter import ValueParameter
from odxtools.physicaltype import PhysicalType, Radix
from odxtools.request import Request
Expand Down Expand Up @@ -410,6 +414,74 @@ def test_encode_nrc_const(self) -> None:
# NRC-CONST parameters cannot be directly specified
resp.encode(param2=0xEF, param3=0xAB)

def test_encode_system_parameter(self) -> None:
diag_coded_type = StandardLengthType(
base_data_type=DataType.A_UINT32,
base_type_encoding=None,
bit_length=16,
bit_mask=None,
is_highlow_byte_order_raw=None,
is_condensed_raw=None,
)
dop = DataObjectProperty(
odx_id=OdxLinkId("dop.year", doc_frags),
oid=None,
short_name="dop_year_sn",
long_name=None,
description=None,
admin_data=None,
diag_coded_type=diag_coded_type,
physical_type=PhysicalType(DataType.A_UINT32, display_radix=None, precision=None),
compu_method=IdenticalCompuMethod(
category=CompuCategory.IDENTICAL,
compu_internal_to_phys=None,
compu_phys_to_internal=None,
internal_type=DataType.A_UINT32,
physical_type=DataType.A_UINT32),
unit_ref=None,
sdgs=[],
internal_constr=None,
physical_constr=None,
)
param1 = SystemParameter(
oid=None,
short_name="year_param",
long_name=None,
description=None,
semantic=None,
dop_ref=OdxLinkRef.from_id(dop.odx_id),
dop_snref=None,
sysparam="YEAR",
kayoub5 marked this conversation as resolved.
Show resolved Hide resolved
byte_position=0,
bit_position=None,
sdgs=[],
)

odxlinks = OdxLinkDatabase()
odxlinks.update(dop._build_odxlinks())

param1._resolve_odxlinks(odxlinks)

encode_state = EncodeState()
param1.encode_into_pdu(physical_value=2024, encode_state=encode_state)
self.assertEqual(encode_state.coded_message, b'\x07\xe8')

# test auto-determination of parameter value
cur_year = datetime.now().year
encode_state = EncodeState()
param1.encode_into_pdu(physical_value=None, encode_state=encode_state)

# there is a (rather theoretical) race condition here: if the
# cur_year variable was assigned before the year of the system
# date changes (e.g., because it is new-year's eve) and the
# encoding was done after that, we will get an incorrect value
# here. (good luck exploiting this!)
self.assertEqual(encode_state.coded_message, cur_year.to_bytes(2, 'big'))

# ensure that decoding works as well
decode_state = DecodeState(coded_message=encode_state.coded_message)
self.assertEqual(param1.decode_from_pdu(decode_state), cur_year)

def test_encode_env_data_desc(self) -> None:
dct = StandardLengthType(
base_data_type=DataType.A_UINT32,
Expand Down
Loading