Example #1
0
 def __init__(self, parameters=PARAMETERS):
     """
     __init__(parameters=PARAMETERS)
     
     Initialize a new :class:`TelegramBuilder`.
     
     Args:
         parameters: The object to be assigned to :attr:`parameters`.
     """
     self.parameters = parameters
     
     # Keyword arguments used to create a telegram.
     self._kwargs = {
         'parameter_code':   Bin(4 * '0', bits=4),
         'parameter_number': Uint(0,  bits=11),
         'parameter_index':  Uint(0,  bits=8),
         'parameter_value':  Uint(0,  bits=32),
         'flag_bits':        Bin(16 * '0', bits=16),
         'frequency':        Uint(0,  bits=16),
         'temperature':      Sint(0,  bits=16),
         'current':          Uint(0,  bits=16,),
         'voltage':          Uint(0,  bits=16),
     }
     # parameter_value and parameter_mode are special cases.
     # These variables store the values given by the user, and the final
     # values used as arguments are only determined when the telegram is
     # created.
     
     # __init__ and set_parameter_value set this to a int or a float,
     # and from_bytes to a bytes object.
     self._parameter_value = 0
     
     # __init__ and set_parameter_mode set this to a string, and from_bytes
     # to a Bin object.
     self._parameter_mode = 'none'
Example #2
0
    def test_bin_from_str_sets_attributes_correctly(self, str_and_bits):
        s, bits = str_and_bits

        # Bits specified.
        bin_ = Bin(s, bits)
        self.assertEqual(bin_.value, s)
        self.assertEqual(bin_.bits, bits)

        # Bits not specified.
        bin_ = Bin(s)
        self.assertEqual(bin_.value, s)
        self.assertEqual(bin_.bits, len(s))
Example #3
0
    def test_negative_bits_raises_ValueError_for_bin_from_int(
            self, i_and_bits, negative_bits):

        i, _ = i_and_bits

        with self.assertRaises(ValueError):
            Bin(i, negative_bits)
Example #4
0
    def test_bin_from_int_sets_attributes_correctly(self, str_and_bits):
        s, bits = str_and_bits
        # '' is a valid 0-bit representation for 0.
        i = int(s, 2) if s else 0

        bin_ = Bin(i, bits)
        self.assertEqual(bin_.value, s)
        self.assertEqual(bin_.bits, bits)
Example #5
0
    def from_bytes(self, bytes_):
        """Read the contents of the telegram from a :class:`bytes` object.
        
        The type of :attr:`parameter_value <Telegram.parameter_value>`
        depends on :attr:`parameter_number <Telegram.parameter_number>`,
        and is assigned automatically. If *bytes_* contains a parameter 
        number that doesn't exist, a :class:`ValueError` is raised.
        
        If the parameter isn't accessed (i.e. the parameter mode is set to 
        ``'none'`` or code to ``'0000'``), invalid parameter numbers, such as
        the default value of 0, are permitted.
        In that case, the parameter type is set to
        :class:`~turboctl.telegram.datatypes.Uint`.
        
        Note that this isn't a class method; a :class:`TelegramBuilder` must
        first be initialized normally with :meth:`__init__`, after which
        this method may be called.
        
        Raises:
            ValueError: If *bytes_* doesn't represent a valid telegram.
        """
        self._check_valid_telegram(bytes_)
        
        code_and_number_bits = Bin(bytes_[3:5])    
        self._kwargs = {
            'parameter_number':     Uint(code_and_number_bits[5:16]),
            'parameter_index':      Uint(bytes_[6]),
            'flag_bits':            Bin(bytes_[11:13])[::-1],
            'frequency':            Uint(bytes_[13:15]),
            'temperature':          Sint(bytes_[15:17]),
            'current':              Uint(bytes_[17:19]),
            'voltage':              Uint(bytes_[21:23])        
        }
        
        self._parameter_value = bytes_[7:11]
        self._parameter_mode = Bin(code_and_number_bits[0:4])

        return self
Example #6
0
 def set_flag_bits(self, bits):
     """Set the control or status bits.
     
     *bits* should be an iterable of those 
     :class:`~turboctl.telegram.codes.ControlBits` or 
     :class:`~turboctl.telegram.codes.StatusBits` members that should be 
     included in the telegram.
     """
     bitlist = 16 * ['0']
     for bit in bits:
         bitlist[bit.value] = '1'
     string = ''.join(bitlist)
     self._kwargs['flag_bits'] = Bin(string, bits=16)
     return self
Example #7
0
 def __bytes__(self):
     """Return the telegram as a :class:`bytes` object.
     
     The checksum is computed automatically and added to the end.
     """
     bytes_ = bytes(
         Uint(2, 8) +
         Uint(22, 8) +
         Uint(0, 8) +
         self.parameter_code + 
         Bin('0') + 
         self.parameter_number + 
         Uint(0, 8) + 
         self.parameter_index + 
         self.parameter_value +
         self.flag_bits[::-1] +    
         self.frequency +
         self.temperature +
         self.current + 
         Uint(0, 16) +
         self.voltage
     )
     return bytes_ + bytes([checksum(bytes_)])
Example #8
0
    def test_invalid_bits_raises_ValueError_for_bin_from_int(self, i_and_bits):
        i, bits = i_and_bits

        with self.assertRaises(ValueError):
            Bin(i, bits)
Example #9
0
 def test_bin_from_invalid_str_raises_ValueError(self):
     with self.assertRaises(ValueError):
         Bin('10201')
Example #10
0
 def test_bits_default_value(self):
     self.assertEqual(Uint(1).bits, 8)
     self.assertEqual(Sint(-1).bits, 8)
     self.assertEqual(Float(1.0).bits, 32)
     self.assertEqual(Float(1).bits, 32)
     self.assertEqual(Bin(1).bits, 8)
Example #11
0
 def test_getitem_slices_binary_value(self, obj, slice_):
     self.assertEqual(Bin(obj[slice_]), Bin(obj)[slice_])
Example #12
0
 def test_add_concatenates_binary_representations(self, obj1, obj2):
     sum_ = obj1 + obj2
     sum_of_bins = Bin(obj1) + Bin(obj2)
     self.assertEqual(Bin(sum_), sum_of_bins)
Example #13
0
    def build(self, type_='query'):
        """Build and return a :class:`Telegram` object.
        
        Args:
            type_: ``query`` if the telegram represents a message to the pump, 
                ``reply`` if it represents a message from the pump.
        
        Raises:
            :class:`ValueError` or :class:`TypeError`: If a telegram cannot
                be created with the specified attributes.
        """
        
        # Make sure type_ is valid to avoid bugs caused by a misspelled
        # argument.
        if type_ not in ['query', 'reply']:
            raise ValueError(f'invalid type_: {type_}')
        
        # Determine parameter access code.
        
        none_code = '0000'
        error_code = ParameterResponse.ERROR.value
        
        # __init__() and set_parameter_mode set self._parameter_mode to a
        # string, while from_bytes() sets it to a Bin object.
        mode_is_none = self._parameter_mode in ['none', Bin(none_code)]
        mode_is_error = (type_ == 'reply' and
                      self._parameter_mode in ['error', Bin(error_code)])
        
        if mode_is_none:
            self._kwargs['parameter_code'] = Bin(none_code)
        elif mode_is_error:
            self._kwargs['parameter_code'] = Bin(error_code)
        else:
            # parameter_number must be valid if the access mode isn't 'none'
            # or 'error'.
            number = self._kwargs['parameter_number'].value
            try:
                parameter = self.parameters[number]
            except KeyError:
                raise ValueError(f'invalid parameter number: {number}') 

            if isinstance(self._parameter_mode, Bin):
                # If this object was created from a Bin object,
                # self._parameter_mode will be a bytes object.
                self._kwargs['parameter_code'] = self._parameter_mode
            else:
                code = get_parameter_code(
                    type_,  self._parameter_mode,
                    bool(parameter.indices), parameter.bits
                ).value
                self._kwargs['parameter_code'] = Bin(code)

        # Determine parameter value.
        
        if mode_is_none or mode_is_error:
            # If the mode is 'none', there is no parameter access and the
            # datatype doesn't matter.
            # If the mode is 'error', the parameter value is replaced by an
            # Uint error code.
            datatype = Uint
        else:
            datatype = parameter.datatype
                
        if isinstance(self._parameter_value, bytes):
            # If self._parameter_value was set by from_bytes(),
            # self._parameter_value will be a bytes object and bits cannot be
            # specified.
            self._kwargs['parameter_value'] = datatype(self._parameter_value)
        else:
            # If _parameter_value was specified by the user, its type should
            # match the type of the parameter.
            # The default value of 0 is accepted by all parameter types.
            self._kwargs['parameter_value'] = datatype(self._parameter_value,
                                                       bits=32)

        return Telegram(**self._kwargs)