Skip to content

Commit

Permalink
Merge pull request #347 from andlaus/implement_system_parameters
Browse files Browse the repository at this point in the history
Implement en- and decoding of SYSTEM parameters
  • Loading branch information
andlaus authored Sep 25, 2024
2 parents 7296617 + 61c25a8 commit 2dbcf7a
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 8 deletions.
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"
]


@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":
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",
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

0 comments on commit 2dbcf7a

Please sign in to comment.