1.1.0版本

This commit is contained in:
xinzhu.yin
2026-04-16 16:51:05 +08:00
commit c157e774e5
333 changed files with 70759 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
from .edid_parser import EdidParser, MainBlockType, AdditionalBlockType
from .edid import EdidFileType, DisplayIDReadMode

View File

@@ -0,0 +1,323 @@
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()

View File

@@ -0,0 +1,21 @@
from .edid_types import *
class EdidParser:
__BLOCK_SIZE = 128
def __init__(self):
pass
def find_main_block(self, block_type: MainBlockType, data: bytearray) -> bytearray:
for i in range(0, len(data), self.__BLOCK_SIZE):
if data[i] == block_type.value:
return data[i: i + self.__BLOCK_SIZE]
return bytearray()
def find_additional_block(self, block_type: AdditionalBlockType, data: bytearray) -> bytearray:
for i in range(0, len(data)):
if data[i] == block_type.value:
return data[i:]
return bytearray()

View File

@@ -0,0 +1,54 @@
from ctypes import Structure, c_uint32, c_bool
class ParsedTimingStruct(Structure):
_fields_ = [
("pixel_clock_khz",c_uint32),
("h_active", c_uint32),
("v_active", c_uint32),
("h_total", c_uint32),
("h_start", c_uint32),
("h_sync", c_uint32),
("v_total", c_uint32),
("v_start", c_uint32),
("v_sync", c_uint32),
("is_hsync_neg", c_bool),
("is_vsync_neg", c_bool),
("interlaced", c_bool)
]
@property
def total_pixels(self) -> int:
return self.v_total * self.h_total
@property
def frame_rate_hz(self):
return self.pixel_clock_khz * 1000 / self.total_pixels
@property
def frame_rate_millihz(self):
return self.pixel_clock_khz * 1000000 / self.total_pixels
@property
def interlaced_frame_rate_hz(self):
return (2 * self.frame_rate_hz) if self.interlaced else self.frame_rate_hz
@property
def interlaced_frame_rate_millihz(self):
return (2 * self.frame_rate_millihz) if self.interlaced else self.frame_rate_millihz
@property
def is_rb(self):
return not self.is_hsync_neg and self.is_vsync_neg and self.h_sync == 32
@property
def is_ovt(self):
return not self.is_hsync_neg and not self.is_vsync_neg and self.h_sync == 32 and self.v_sync == 8
@property
def line_duration_ms(self):
return self.h_total / self.pixel_clock_khz
@property
def pixel_clock_hz(self):
return self.pixel_clock_khz * 1000

View File

@@ -0,0 +1,22 @@
from enum import IntEnum
class MainBlockType(IntEnum):
VESA = 0x0
CTA = 0x2
DisplayID = 0x70
class AdditionalBlockType(IntEnum):
AdaptiveSync = 0x2B
class DisplayIDReadMode(IntEnum):
"""
Disabled - Disable native DisplayID read
Try - Prefer native DisplayID (if failed to read use EDID instead)
Both - Read both DisplayID and EDID
"""
Disabled = 0
Try = 1
Both = 2

View File

@@ -0,0 +1,44 @@
import os
from enum import IntEnum
class EdidFileType(IntEnum):
BIN = 0
HEX = 1
def save_file(data: bytearray, path: str, file_type: EdidFileType):
if file_type == EdidFileType.BIN:
file_dpd = open(path + '.bin', 'bw+')
file_dpd.write(data)
file_dpd.close()
elif file_type == EdidFileType.HEX:
file = open(path + '.hex', 'w')
str_for_save = ''
for i in data:
if i < 16:
str_for_save += str(hex(i)).replace("0x", "0")
else:
str_for_save += str(hex(i)).replace("0x", "")
file.write(str_for_save)
file.close()
def load_file(path: str) -> bytearray:
filename, file_extension = os.path.splitext(path)
if file_extension.find('.hex') != -1:
file = open(path, 'r')
data = file.read()
file.close()
i = 0
size = len(data)
byte_array = bytearray()
while i < size:
byte_array.append(int('0x' + data[i] + data[i + 1], 16))
i += 2
return byte_array
elif file_extension.find('.bin') != -1:
file = open(path, 'rb')
data = file.read()
file.close()
return bytearray(data)