INA_TI
145 строк · 7.9 Кб
1# micropython
2# MIT license
3# Copyright (c) 2024 Roman Shevchik goctaprog@gmail.com
4"""Представление битового поля"""
5from collections import namedtuple6from sensor_pack_2.base_sensor import check_value, get_error_str7
8# информация о битовом поле в виде именованного кортежа
9# name: str - имя
10# position: range - место в номерах битах. position.start = первый бит, position.stop-1 - последний бит
11# valid_values: [range, tuple] - диапазон допустимых значений, если проверка не требуется, следует передать None
12# description: str - читаемое описание значения, хранимого в битовом поле, если описания не требуется, следует передать None
13bit_field_info = namedtuple("bit_field_info", "name position valid_values description")14
15
16def _bitmask(bit_rng: range) -> int:17"""возвращает битовую маску по занимаемым битам"""18# if bit_rng.step < 0 or bit_rng.start <= bit_rng.stop:19# raise ValueError(f"_bitmask: {bit_rng.start}; {bit_rng.stop}; {bit_rng.step}")20return sum(map(lambda x: 2 ** x, bit_rng))21
22
23class BitFields:24"""Хранилище информации о битовых полях с доступом по индексу.25_source - кортеж именованных кортежей, описывающих битовые поля;"""
26def _check(self, fields_info: tuple[bit_field_info, ...]):27"""Проверки на правильность информации!"""28for field_info in fields_info:29if 0 == len(field_info.name):30raise ValueError(f"Нулевая длина строки имени битового поля!; position: {field_info.position}")31if 0 == len(field_info.position):32raise ValueError(f"Нулевая длина ('в битах') битового поля!; name: {field_info.name}")33
34def __init__(self, fields_info: tuple[bit_field_info, ...]):35self._check(fields_info)36self._fields_info = fields_info37self._idx = 038# имя битового поля, которое будет параметром у методов get_value/set_value39self._active_field_name = fields_info[0].name40# значение, из которого будут извлекаться битовые поля41self._source_val = 042
43def _by_name(self, name: str) -> [bit_field_info, None]:44"""возвращает информацию о битовом поле по его имени (поле name именованного кортежа) или None"""45items = self._fields_info46for item in items:47if name == item.name:48return item49
50def _get_field(self, key: [str, int, None]) -> [bit_field_info, None]:51"""для внутреннего использования"""52fi = self._fields_info53_itm = None54if isinstance(key, int):55_itm = fi[key]56if isinstance(key, str):57_itm = self._by_name(key)58return _itm59
60def get_field_value(self, field_name: str = None, validate: bool = False) -> [int, bool]:61"""возвращает значение битового поля, по его имени(self.field_name), из self.source."""62f_name = self.field_name if field_name is None else field_name63item = self._get_field(f_name)64if item is None:65raise ValueError(f"get_field_value. Поле с именем {f_name} не существует!")66pos = item.position67bitmask = _bitmask(pos)68val = (self.source & bitmask) >> pos.start # выделение маской битового диапазона и его сдвиг вправо69if item.valid_values and validate:70raise NotImplemented("Если вы решили проверить значение поля при его возвращении, то делайте это самостоятельно!!!")71if 1 == len(pos):72return 0 != val # bool73return val # int74
75def set_field_value(self, value: int, source: [int, None] = None, field: [str, int, None] = None,76validate: bool = True) -> int:77"""Записывает value в битовый диапазон, определяемый параметром field, в source.78Возвращает значение с измененным битовым полем.
79Если field is None, то имя поля берется из свойства self._active_field_name.
80Если source is None, то значение поля, подлежащее изменению, изменяется в свойстве self._source_val"""
81item = self._get_field(key=field) # *82rng = item.valid_values83if rng and validate:84# print(f"DBG: value: {value}; rng: {rng}")85check_value(value, rng, get_error_str(self.field_name, value, rng))86pos = item.position87bitmask = _bitmask(pos)88src = self._get_source(source) & ~bitmask # чистка битового диапазона89src |= (value << pos.start) & bitmask # установка битов в заданном диапазоне90# print(f"DBG:set_field_value: {value}; {source}; {field}")91if source is None:92self._source_val = src93# print(f"DBG:set_field_value: self._source_val: {self._source_val}")94return src95
96def __getitem__(self, key: [int, str]) -> [int, bool]:97"""возвращает значение битового поля из значения в self.source по его имени/индексу"""98_bfi = self._get_field(key)99return self.get_field_value(_bfi.name)100
101def __setitem__(self, field_name: str, value: [int, bool]):102"""Волшебный метод, вызывает set_field_value.103До его вызова нужно установить свойства BitField source"""
104self.set_field_value(value=value, source=None, field=field_name, validate=True) # *105
106def _get_source(self, source: [int, None]) -> int:107return source if source else self._source_val108
109@property110def source(self) -> int:111"""значение, из которого будут извлекаться/в котором будут изменятся битовые поля"""112return self._source_val113
114@source.setter115def source(self, value):116"""значение, из которого будут извлекаться/изменятся битовые поля"""117self._source_val = value118
119@property120def field_name(self) -> str:121"""имя битового поля, значение которого извлекается/изменяется методами get_value/set_value, если их122параметр field is None"""
123return self._active_field_name124
125@field_name.setter126def field_name(self, value):127"""имя битового поля, значение которого извлекается/изменяется методами get_value/set_value, если их128параметр field is None"""
129self._active_field_name = value130
131def __len__(self) -> int:132return len(self._fields_info)133
134# протокол итератора135def __iter__(self):136return self137
138def __next__(self) -> bit_field_info:139ss = self._fields_info140try:141self._idx += 1142return ss[self._idx - 1]143except IndexError:144self._idx = 0 # для возможности выполнения повторной итерации!145raise StopIteration146