Source code for cantools.database.can.signal

# A CAN signal.
from typing import TYPE_CHECKING, List, Optional, Union

from ...typechecking import ByteOrder, Choices, Comments, SignalValueType
from ..conversion import BaseConversion, IdentityConversion
from ..namedsignalvalue import NamedSignalValue

if TYPE_CHECKING:
    from ...database.can.formats.dbc import DbcSpecifics

[docs]class Signal: """A CAN signal with position, size, unit and other information. A signal is part of a message. Signal bit numbering in a message: .. code:: text Byte: 0 1 2 3 4 5 6 7 +--------+--------+--------+--------+--------+--------+--------+--------+--- - - | | | | | | | | | +--------+--------+--------+--------+--------+--------+--------+--------+--- - - Bit: 7 0 15 8 23 16 31 24 39 32 47 40 55 48 63 56 Big endian signal with start bit 2 and length 5 (0=LSB, 4=MSB): .. code:: text Byte: 0 1 2 3 +--------+--------+--------+--- - - | |432|10| | | +--------+--------+--------+--- - - Bit: 7 0 15 8 23 16 31 Little endian signal with start bit 2 and length 9 (0=LSB, 8=MSB): .. code:: text Byte: 0 1 2 3 +--------+--------+--------+--- - - |543210| | |876| | +--------+--------+--------+--- - - Bit: 7 0 15 8 23 16 31 """ def __init__( self, name: str, start: int, length: int, byte_order: ByteOrder = "little_endian", is_signed: bool = False, raw_initial: Optional[Union[int, float]] = None, raw_invalid: Optional[Union[int, float]] = None, conversion: Optional[BaseConversion] = None, minimum: Optional[float] = None, maximum: Optional[float] = None, unit: Optional[str] = None, dbc_specifics: Optional["DbcSpecifics"] = None, comment: Optional[Union[str, Comments]] = None, receivers: Optional[List[str]] = None, is_multiplexer: bool = False, multiplexer_ids: Optional[List[int]] = None, multiplexer_signal: Optional[str] = None, spn: Optional[int] = None, ) -> None: # avoid using properties to improve encoding/decoding performance #: The signal name as a string. self.name: str = name #: The conversion instance, which is used to convert #: between raw and scaled/physical values. self.conversion: BaseConversion = conversion or IdentityConversion(is_float=False) #: The scaled minimum value of the signal, or ``None`` if unavailable. self.minimum: Optional[float] = minimum #: The scaled maximum value of the signal, or ``None`` if unavailable. self.maximum: Optional[float] = maximum #: The start bit position of the signal within its message. self.start: int = start #: The length of the signal in bits. self.length: int = length #: Signal byte order as ``'little_endian'`` or ``'big_endian'``. self.byte_order: ByteOrder = byte_order #: ``True`` if the signal is signed, ``False`` otherwise. Ignore this #: attribute if :attr:`is_float` is ``True``. self.is_signed: bool = is_signed #: The internal representation of the initial value of the signal, #: or ``None`` if unavailable. self.raw_initial: Optional[Union[int, float]] = raw_initial #: The initial value of the signal in units of the physical world, #: or ``None`` if unavailable. self.initial: Optional[SignalValueType] = ( self.conversion.raw_to_scaled(raw_initial) if raw_initial is not None else None ) #: The raw value representing that the signal is invalid, #: or ``None`` if unavailable. self.raw_invalid: Optional[Union[int, float]] = raw_invalid #: The scaled value representing that the signal is invalid, #: or ``None`` if unavailable. self.invalid: Optional[SignalValueType] = ( self.conversion.raw_to_scaled(raw_invalid) if raw_invalid is not None else None ) #: The unit of the signal as a string, or ``None`` if unavailable. self.unit: Optional[str] = unit #: An object containing dbc specific properties like e.g. attributes. self.dbc: Optional["DbcSpecifics"] = dbc_specifics #: A list of all receiver nodes of this signal. self.receivers: List[str] = receivers or [] #: ``True`` if this is the multiplexer signal in a message, ``False`` #: otherwise. self.is_multiplexer: bool = is_multiplexer #: The multiplexer ids list if the signal is part of a multiplexed #: message, ``None`` otherwise. self.multiplexer_ids: Optional[List[int]] = multiplexer_ids #: The multiplexer signal if the signal is part of a multiplexed #: message, ``None`` otherwise. self.multiplexer_signal: Optional[str] = multiplexer_signal #: The J1939 Suspect Parameter Number (SPN) value if the signal #: has this attribute, ``None`` otherwise. self.spn: Optional[int] = spn #: The dictionary with the descriptions of the signal in multiple #: languages. ``None`` if unavailable. self.comments: Optional[Comments] # if the 'comment' argument is a string, we assume that is an # english comment. this is slightly hacky because the # function's behavior depends on the type of the passed # argument, but it is quite convenient... if isinstance(comment, str): # use the first comment in the dictionary as "The" comment self.comments = {None: comment} else: # assume that we have either no comment at all or a # multilingual dictionary self.comments = comment
[docs] def raw_to_scaled( self, raw_value: Union[int, float], decode_choices: bool = True ) -> SignalValueType: """Convert an internal raw value according to the defined scaling or value table. :param raw_value: The raw value :param decode_choices: If `decode_choices` is ``False`` scaled values are not converted to choice strings (if available). :return: The calculated scaled value """ return self.conversion.raw_to_scaled(raw_value, decode_choices)
[docs] def scaled_to_raw(self, scaled_value: SignalValueType) -> Union[int, float]: """Convert a scaled value to the internal raw value. :param scaled_value: The scaled value. :return: The internal raw value. """ return self.conversion.scaled_to_raw(scaled_value)
@property def scale(self) -> Union[int, float]: """The scale factor of the signal value.""" return self.conversion.scale @scale.setter def scale(self, value: Union[int, float]) -> None: self.conversion = self.conversion.factory( scale=value, offset=self.conversion.offset, choices=self.conversion.choices, is_float=self.conversion.is_float, ) @property def offset(self) -> Union[int, float]: """The offset of the signal value.""" return self.conversion.offset @offset.setter def offset(self, value: Union[int, float]) -> None: self.conversion = self.conversion.factory( scale=self.conversion.scale, offset=value, choices=self.conversion.choices, is_float=self.conversion.is_float, ) @property def choices(self) -> Optional[Choices]: """A dictionary mapping signal values to enumerated choices, or ``None`` if unavailable.""" return self.conversion.choices @choices.setter def choices(self, choices: Optional[Choices]) -> None: self.conversion = self.conversion.factory( scale=self.conversion.scale, offset=self.conversion.offset, choices=choices, is_float=self.conversion.is_float, ) @property def is_float(self) -> bool: """``True`` if the raw signal value is a float, ``False`` otherwise.""" return self.conversion.is_float @is_float.setter def is_float(self, is_float: bool) -> None: self.conversion = self.conversion.factory( scale=self.conversion.scale, offset=self.conversion.offset, choices=self.conversion.choices, is_float=is_float, ) @property def comment(self) -> Optional[str]: """The signal comment, or ``None`` if unavailable. Note that we implicitly try to return the English comment if multiple languages were specified. """ if self.comments is None: return None elif self.comments.get(None) is not None: return self.comments.get(None) elif self.comments.get("FOR-ALL") is not None: return self.comments.get("FOR-ALL") return self.comments.get("EN") @comment.setter def comment(self, value: Optional[str]) -> None: if value is None: self.comments = None else: self.comments = {None: value} def choice_to_number(self, choice: Union[str, NamedSignalValue]) -> int: try: return self.conversion.choice_to_number(choice) except KeyError as exc: err_msg = f"Choice {choice} not found in Signal {self.name}." raise KeyError(err_msg) from exc def __repr__(self) -> str: if self.choices is None: choices = None else: list_of_choices = ", ".join( [f"{value}: '{text}'" for value, text in self.choices.items()] ) choices = f"{{{list_of_choices}}}" return ( f"signal(" f"'{self.name}', " f"{self.start}, " f"{self.length}, " f"'{self.byte_order}', " f"{self.is_signed}, " f"{self.raw_initial}, " f"{self.conversion.scale}, " f"{self.conversion.offset}, " f"{self.minimum}, " f"{self.maximum}, " f"'{self.unit}', " f"{self.is_multiplexer}, " f"{self.multiplexer_ids}, " f"{choices}, " f"{self.spn}, " f"{self.comments})" )