#!/usr/bin/env python
import numpy as np
from . import constants
from . import csi
# Helper module defining board revision-specific constants
[docs]
class BoardRevision:
@property
def identification(self) -> tuple:
raise NotImplementedError
@property
def type_header(self) -> int:
raise NotImplementedError
@property
def serialized_csi_t(self) -> type:
raise NotImplementedError
@property
def calib_trace_delays(self) -> list:
"""Calibration trace signal delays on ESPARGOS PCB [in s]"""
effective_dielectric_constant = (self._calib_trace_dielectric_constant + 1) / 2 + (self._calib_trace_dielectric_constant - 1) / 2 * (1 + 12 * (self._calib_trace_height / self._calib_trace_width)) ** (-1 / 2)
group_velocity = constants.SPEED_OF_LIGHT / effective_dielectric_constant**0.5
return self._calib_trace_lengths / group_velocity
[docs]
def esp_num_to_row_col(self, esp_num: int) -> tuple:
"""Convert ESP number to (row, column) on the board"""
raise NotImplementedError
[docs]
def antid_to_row_col(self, antid: int) -> tuple:
"""Convert firmware antenna id to (row, column) on this board revision."""
return self.esp_num_to_row_col(self.antid_to_esp_num[antid])
[docs]
def sensor_values_to_antid_list(self, values, name: str = "values") -> list:
"""Convert a board-local (row, column) array to firmware antenna-id order."""
array = np.asarray(values)
expected_shape = (constants.ROWS_PER_BOARD, constants.ANTENNAS_PER_ROW)
if array.shape != expected_shape:
raise ValueError(f"{name} must use (row, column) shape {expected_shape}, got {array.shape}")
values_by_antid = []
for antid in range(constants.ANTENNAS_PER_BOARD):
row, col = self.antid_to_row_col(antid)
value = array[row, col]
values_by_antid.append(value.item() if hasattr(value, "item") else value)
return values_by_antid
# Private, (potentially) revision-specific properties
@property
def _calib_trace_dielectric_constant(self) -> float:
"""Dielectric constant of the PCB material"""
return NotImplementedError
@property
def _calib_trace_lengths(self) -> list:
"""Lengths of the calibration traces on the PCB [in m]"""
return NotImplementedError
@property
def _calib_trace_width(self) -> float:
"""Width of the calibration trace [in mm]"""
return NotImplementedError
@property
def _calib_trace_height(self) -> float:
"""Height of the calibration trace (distance between GND plane and microstrip) [in mm]"""
return 0.119
# Codename "ESPARGOS-DENSIFLORUS" (2025/2026 PCB)
[docs]
class BoardRevisionDensiflorus(BoardRevision):
@property
def identification(self) -> tuple:
return ("espargos", "densiflorus")
@property
def type_header(self) -> int:
return 0xE4CD0BAC
@property
def serialized_csi_t(self) -> type:
return csi.serialized_csi_tlv_t
[docs]
def esp_num_to_row_col(self, esp_num: int) -> tuple:
row = 1 - esp_num // 4
col = 3 - esp_num % 4
return (row, col)
@property
def antid_to_esp_num(self) -> dict:
return {
0: 3, # Sensor 0 -> ESP 0
1: 2, # Sensor 1 -> ESP 1
2: 1, # Sensor 2 -> ESP 2
3: 0, # Sensor 3 -> ESP 3
4: 7, # Sensor 4 -> ESP 4
5: 6, # Sensor 5 -> ESP 5
6: 5, # Sensor 6 -> ESP 6
7: 4, # Sensor 7 -> ESP 7
}
# Private, (potentially) revision-specific properties
@property
def _calib_trace_dielectric_constant(self) -> float:
return 4.3
@property
def _calib_trace_lengths(self) -> list:
return np.asarray(
[
[0.0604561, 0.0373554, 0.1070395, 0.1770280],
[0.1076842, 0.0554654, 0.0806678, 0.1462569],
]
)
@property
def _calib_trace_width(self) -> float:
return 0.2
@property
def _calib_trace_height(self) -> float:
return 0.119
all_revisions = [BoardRevisionDensiflorus()]