1# Copyright (C) 2024 The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15# Lint as: python3 16 17# String transformations 18 19from .other import classproperty 20 21 22def snake_to_camel(string, lower=True) -> str: 23 """Converts snake_case string to camelcase""" 24 pref, *other = string.split("_") 25 return (pref if lower else pref.capitalize()) + "".join( 26 x.capitalize() or "_" for x in other 27 ) 28 29 30# Time conversion 31def ns_to_ms(t): 32 """Converts nanoseconds (10^−9) to milliseconds (10^−3)""" 33 return round(t / 1000000) 34 35 36def ns_to_us(t): 37 """Converts nanoseconds (10^−9) to microseconds (10^−6)""" 38 return round(t / 1000) 39 40 41def us_to_ms(t): 42 """Converts microseconds (10^−6) to milliseconds (10^−3)""" 43 return round(t / 1000) 44 45 46def s_to_us(t): 47 """Converts seconds (10^0) to microseconds (10^−6)""" 48 return round(t * 1000000) 49 50 51class ByteStruct(int): 52 """This class enables an ability to assign attribute names to specific bit 53 offsets in a byte, making access more approachable 54 """ 55 56 def __new__(cls, *args, **kwargs): 57 fields: dict = {**cls.fields} 58 59 for kwarg, _ in kwargs.items(): 60 if kwarg not in fields: 61 raise ValueError(f"{cls.__name__} does not have field {kwarg}") 62 63 value = 0 64 if len(args) == 1 and isinstance(args[0], int): 65 # Initialize from bitmask 66 value = args[0] 67 else: 68 for key, bit_position in fields.items(): 69 start, end = bit_position 70 bit_value = int(kwargs.get(key, 0)) 71 bit_size = start - end + 1 72 if bit_value > 2**bit_size - 1: 73 raise ValueError(f"{key} size in bits exceeds {bit_size}") 74 value |= (bit_value & ((1 << bit_size) - 1)) << end 75 76 values = {} 77 for name in fields.keys(): 78 start, end = fields[name] 79 values[name] = (value >> end) & ((1 << (start - end + 1)) - 1) 80 81 instance = super().__new__(cls, value) 82 instance._values = values 83 84 return instance 85 86 def replace(self, **kwargs): 87 """Return a new instance with specific values replaced by name.""" 88 return self.__class__(**{**self.values, **kwargs}) 89 90 def __getattribute__(self, item): 91 values = super().__getattribute__("_values") 92 if item == "values": 93 return {**values} 94 if item not in values: 95 return super().__getattribute__(item) 96 return values[item] 97 98 @classmethod 99 def of(cls, name=None, **kwargs): 100 """Create a subclass with the specified name and parameters""" 101 if name is None: 102 name = "ByteStructOf" + ''.join(k.upper() for k in kwargs) 103 subclass = type(name, (cls,), kwargs) 104 return subclass 105 106 @classproperty 107 def fields(cls) -> dict: # pylint: disable=no-self-argument 108 return { 109 k: sorted((v, v) if isinstance(v, int) else v)[::-1] 110 for k, v in cls.__dict__.items() 111 if not k.startswith("_") 112 } 113 114 def __repr__(self): 115 fields = self.fields 116 result = [] 117 for name, value in self.values.items(): 118 start, end = fields[name] 119 length = start - end + 1 120 result.append(f"{name}={bin(value)[2:].zfill(length)}") 121 return f"{self.__class__.__name__}({', '.join(result)})" 122 123 124# CRC Calculation 125def crc16a(data): 126 w_crc = 0x6363 127 for byte in data: 128 byte = byte ^ (w_crc & 0x00FF) 129 byte = (byte ^ (byte << 4)) & 0xFF 130 w_crc = ( 131 (w_crc >> 8) ^ (byte << 8) ^ (byte << 3) ^ (byte >> 4) 132 ) & 0xFFFF 133 return bytes([w_crc & 0xFF, (w_crc >> 8) & 0xFF]) 134 135 136def with_crc16a(data): 137 return bytes(data) + crc16a(data) 138