# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
"""Helpers for parsing iperf RSSI/ATT sweep ranges."""
import logging
from typing import List, Sequence, Union
ExpectedRangeInput = Union[str, Sequence[int], None]
def _sort_expected_rssi_list(values: Sequence[int], original_value: ExpectedRangeInput) -> List[int]:
sorted_values = sorted(values, reverse=True)
if sorted_values[0] > 0:
raise ValueError(f'expected_rssi_list first value must be <= 0, got: {original_value!r}')
return sorted_values
def _expand_range_values(start: int, end: int, step: int) -> List[int]:
step = abs(step) if start <= end else -abs(step)
expanded = []
cur = start
if step > 0:
while cur <= end:
expanded.append(cur)
cur += step
else:
while cur >= end:
expanded.append(cur)
cur += step
return expanded
def _parse_expected_range_string(value: str) -> List[int]:
values = []
for token in _split_top_level_commas(value):
token = token.strip()
if not token:
continue
if token.startswith('(') and token.endswith(')'):
values.extend(_expand_range_token(token[1:-1], value))
else:
values.append(int(token))
return values
def _split_top_level_commas(value: str) -> List[str]:
tokens = []
start = 0
depth = 0
for idx, char in enumerate(value):
if char == '(':
depth += 1
elif char == ')':
depth -= 1
if depth < 0:
raise ValueError(f'unmatched ")" in expected_range: {value!r}')
elif char == ',' and depth == 0:
tokens.append(value[start:idx])
start = idx + 1
if depth != 0:
raise ValueError(f'unmatched "(" in expected_range: {value!r}')
tokens.append(value[start:])
return tokens
def _expand_range_token(token: str, original_value: str) -> List[int]:
parts = [int(v.strip()) for v in token.split(',') if v.strip()]
if len(parts) != 3:
raise ValueError(f'range segment needs 3 numbers "(start,end,step)", got: {original_value!r}')
start, end, step = parts
if step == 0:
raise ValueError(f'range segment step must not be 0, got: {original_value!r}')
return _expand_range_values(start, end, step)
def _rssi_points_to_att_list(initial_rssi: int, rssi_values: Sequence[int], max_att: int) -> List[int]:
atts: List[int] = []
min_rssi = min(rssi_values)
if min_rssi > initial_rssi:
raise ValueError(f'minimum RSSI {min_rssi} is greater than initial RSSI {initial_rssi}')
expected_max_att = initial_rssi - min_rssi
if expected_max_att > max_att + 2:
raise ValueError(f'expected maximum ATT {expected_max_att} is greater than max ATT {max_att}')
for target_rssi in rssi_values:
att = initial_rssi - target_rssi
if att > max_att:
logging.warning(
'[Utility] Stop at RSSI target %s; required ATT %s exceeds max %s',
target_rssi,
att,
max_att,
)
break
atts.append(att)
logging.info('[Utility] Expected RSSI list: %s', list(rssi_values))
logging.info('[Utility] Converted ATT list: %s', atts)
return atts
[docs]
def limit_att_range(initial_rssi: int, max_att: int, expected_rssi_list: List[int]) -> List[int]:
"""
Convert parsed expected_rssi_list to ATT values capped by max_att.
"""
return _rssi_points_to_att_list(initial_rssi, expected_rssi_list, max_att)