import warnings from typing import List from UniTAP.common import Timing from UniTAP.libs.lib_tsi.tsi_types import TSI_EDID_SELECT_STREAM, TSI_EDID_TE_INPUT, TSI_EDID_TE_OUTPUT, \ TSI_EDID_TE_OUTPUT_REMOTE_R, TSI_DID_TE_OUTPUT, TSI_DID_TE_INPUT, TSI_DID_SELECT_STREAM, \ TSI_DID_TE_OUTPUT_REMOTE_R, TSI_DPRX_DISPLAYID_CTRL, TSI_DPTX_DISPLAYID_CTRL, TSI_EDID_DUT_TIMINGS_COUNT, \ TSI_EDID_DUT_TIMINGS_DATA from UniTAP.libs.lib_tsi.tsi_io import PortIO from UniTAP.dev.ports.modules.device_constants import MAX_EDID_SIZE from ctypes import c_ubyte, c_uint32 from .edid_utils import save_file, load_file, EdidFileType from .edid_parser import EdidParser from .edid_types import DisplayIDReadMode from .edid_private_types import ParsedTimingStruct class Edid: """ Main class for working with EDID. Allows reading and saving EDID. This functionality is used by child classes `EdidSource` and `EdidSink`. You cannot use a class `Edid` object directly. """ def __init__(self, port_io: PortIO, control_ci: int, max_stream_count: int, select_stream_ci: int): self._io = port_io self._control_ci = control_ci self.__select_stream_ci = select_stream_ci self._max_stream_count = max_stream_count self.__parser = EdidParser() def read_i2c(self) -> bytearray: """ Allows reading from DUT `EdidSource` or TE `EdidSink` side EDID block(s) over connecting signal cable. Returns: object of bytearray """ result, edid_data, size = self._io.get(self._control_ci, c_ubyte, MAX_EDID_SIZE) if result > 0: return bytearray(edid_data[:result]) else: return bytearray() def save_edid(self, path: str, file_type: EdidFileType, data: bytearray): """ Save received EDID data into file. Supported formats: - BIN. - HEX. Args: path (str) - full path to file file_type (`EdidFileType`) - one of the Supported formats. data (bytearray) - EDID data for saving """ if len(data) > 0: save_file(data, path, file_type) else: warnings.warn("EDID is empty.") def read_timings(self) -> List[Timing]: """ """ timings_count = self._io.get(TSI_EDID_DUT_TIMINGS_COUNT, c_uint32)[1] if timings_count == 0: return [] timings = self._io.get(TSI_EDID_DUT_TIMINGS_DATA, ParsedTimingStruct, timings_count)[1] parsed_timings = [] for i in range(timings_count): timing = Timing() timing.htotal = timings[i].h_total timing.vtotal = timings[i].v_total timing.hactive = timings[i].h_active timing.vactive = timings[i].v_active timing.hstart = timings[i].h_start timing.vstart = timings[i].v_start timing.hswidth = timings[i].h_sync timing.vswidth = timings[i].v_sync # timing.id = timings[i].id # timing.aspect_ratio = timings[i].aspect_ratio timing.frame_rate = timings[i].frame_rate_millihz # timing.standard = timings[i].standard # timing.reduce_blanking = timings[i].reduce_blanking parsed_timings.append(timing) return parsed_timings def _select_stream(self, stream): """ Set Virtual Sink's EDID index. stream = 0 - UCD local EDID stream > 0 - Virtual Sink's EDID Args: stream (int) - Virtual Sink's EDID index. """ if not (0 <= stream < self._max_stream_count): warnings.warn(f"Stream must be in range 0 - {self._max_stream_count}. Current value: {stream}") return self._io.set(self.__select_stream_ci, stream, c_uint32) @property def _parser(self) -> EdidParser: return self.__parser class EdidSource(Edid): """ Class `EdidSource` inherited from class `Edid`. Allows read EDID from remote devices `read_sbm`, save received EDID data to file `save_edid` and read EDID from DUT `read_i2c` """ def __init__(self, port_io: PortIO, max_stream_count: int): super().__init__(port_io, TSI_EDID_TE_OUTPUT, max_stream_count, TSI_EDID_SELECT_STREAM) def read_sbm(self, stream: int): """ Allows reading remote EDID block(s) of remote device(s) attached to DUT over connecting signal cable. stream = 0 - UCD local EDID stream > 0 - Virtual Sink's EDID Args: stream (int) - Virtual Sink index. """ self._select_stream(stream) result, edid_data, size = self._io.get(TSI_EDID_TE_OUTPUT_REMOTE_R, c_ubyte, MAX_EDID_SIZE) if result > 0: return bytearray(edid_data[:result]) else: return bytearray() class EdidSink(Edid): """ Class `EdidSink` inherited from class `Edid`. Allows writing EDID to device `write_edid`, load EDID data from file `load_edid`, save received EDID data to file `save_edid` and read EDID from TE `read_i2c`. """ def __init__(self, port_io: PortIO, max_stream_count: int): super().__init__(port_io, TSI_EDID_TE_INPUT, max_stream_count, TSI_EDID_SELECT_STREAM) def write_edid(self, data: bytearray, stream: int = 0): """ Write transferred EDID to device. Args: data (bytearray) - EDID data for writing stream (int) - Virtual Sink's EDID index """ self._select_stream(stream) self._io.set(self._control_ci, data, c_ubyte, len(data)) def load_edid(self, path: str, load_on_device: bool, stream: int = 0) -> bytearray: """ Read EDID data from file. If needed to write data to device, select load_on_device = True. Supported formats: - BIN. - HEX. Args: path (str) - full path to file. load_on_device (bool) - write loaded data to device or not. stream (int) - Virtual Sink's EDID index Returns: object of bytearray """ data = load_file(path) if len(data) > 0: if load_on_device: self.write_edid(data, stream) return data else: warnings.warn("File is empty.") return bytearray() def read_sbm(self, stream: int): """ Allows reading remote EDID block(s) of remote device(s) attached to DUT over connecting signal cable. stream = 0 - UCD local EDID stream > 0 - Virtual Sink's EDID Args: stream (int) - Virtual Sink index. """ self._select_stream(stream) return self.read_i2c() class DisplayIdSource(Edid): """ Class `DisplayIdSource` inherited from class `Edid`. Allows read DisplayId from remote devices `read_sbm`, save received DisplayId data to file `save_edid` and read DisplayId from DUT `read_i2c` """ def __init__(self, port_io: PortIO, max_stream_count: int): super().__init__(port_io, TSI_DID_TE_OUTPUT, max_stream_count, TSI_DID_SELECT_STREAM) def read_sbm(self, stream: int): """ Allows reading remote DisplayId block(s) of remote device(s) attached to DUT over connecting signal cable. stream = 0 - UCD local EDID stream > 0 - Virtual Sink's EDID Args: stream (int) - Virtual Sink index. """ self._select_stream(stream) result, data, size = self._io.get(TSI_DID_TE_OUTPUT_REMOTE_R, c_ubyte, MAX_EDID_SIZE) if result > 0: return bytearray(data[:result]) else: return bytearray() def set_display_id_mode(self, mode: DisplayIDReadMode): """ Set DisplayID read mode Args: mode (`DisplayIDReadMode`) """ self._io.set(TSI_DPTX_DISPLAYID_CTRL, mode.value, c_uint32) def get_display_id_mode(self) -> DisplayIDReadMode: """ Returns DisplayID read mode. Returns: object of `DisplayIDReadMode` type. """ result = self._io.get(TSI_DPTX_DISPLAYID_CTRL, c_uint32)[1] return DisplayIDReadMode(result) class DisplayIdSink(Edid): """ Class `DisplayIdSink` inherited from class `Edid`. Allows writing DisplayId to device `write_display_id`, load DisplayId data from file `load_display_id`, save received DisplayId data to file `save_edid` and read DisplayId from TE `read_i2c`. """ def __init__(self, port_io: PortIO, max_stream_count: int): super().__init__(port_io, TSI_DID_TE_INPUT, max_stream_count, TSI_DID_SELECT_STREAM) def is_enabled(self) -> bool: """ Returns status of DisplayId, is enabled or not. Returns: object of `bool` type. """ result = self._io.get(TSI_DPRX_DISPLAYID_CTRL, c_uint32) status_display_id = ((result[1] & 0x1) != 0) return bool(status_display_id) def enable(self, enable: bool): """ Enable/Disable DisplayId. Args: enable (bool) - enable (True) or disable (False) """ val = 0x1 if enable else 0x0 self._io.set(TSI_DPRX_DISPLAYID_CTRL, val) def write_display_id(self, data: bytearray): """ Write transferred EDID to device. Args: data (bytearray) - EDID data for writing """ self._io.set(self._control_ci, data, c_ubyte, len(data)) def load_display_id(self, path: str, load_on_device: bool) -> bytearray: """ Read EDID data from file. If needed to write data to device, select load_on_device = True. Supported formats: - BIN. - HEX. Args: path (str) - full path to file. load_on_device (bool) - write loaded data to device or not. Returns: object of bytearray """ data = load_file(path) if len(data) > 0: if load_on_device: self.write_display_id(data) return data else: warnings.warn("File is empty.") return bytearray()