commit c157e774e5fc40353f4b09ffb5f96d57fc6c98f3 Author: xinzhu.yin Date: Thu Apr 16 16:51:05 2026 +0800 1.1.0版本 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..406b4fc --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +# Python cache +__pycache__/ +*.pyc +*.pyo +*.pyd + +# Virtual environments +.venv/ +venv/ +env/ + +# Build outputs +build/ +dist/ +*.spec.bak + +# Test and runtime outputs +*.log +*.tmp +*.bak +*.csv +*.xlsx + +# IDE files +.vscode/ +.idea/ + +# OS files +Thumbs.db +Desktop.ini + +# Local configuration overrides +settings/*.local.json \ No newline at end of file diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000..ea6231c --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,94 @@ +# 发布说明 + +## 版本入口 + +软件版本统一维护在 `app_version.py`: + +- `APP_NAME`:软件名称 +- `APP_VERSION`:软件发布版本 + +发布新版本时,优先修改这里,不要直接改界面标题字符串。 + +## 版本规则 + +建议使用语义化版本:`主版本.次版本.修订号` + +- `1.0.0`:首个正式版 +- `1.1.0`:兼容性新功能 +- `1.1.1`:问题修复 +- `2.0.0`:不兼容变更 + +## Git 管理建议 + +- `main`:始终保持可发布状态 +- 功能开发使用独立分支 +- 每次发布打 Git Tag,例如 `v1.1.0` + +建议命令: + +```powershell +git checkout main +git pull +git checkout -b feature/xxx +``` + +完成功能后: + +```powershell +git add . +git commit -m "feat: xxx" +git checkout main +git merge --no-ff feature/xxx +``` + +发布时: + +```powershell +git add app_version.py RELEASE.md +git commit -m "release: v1.1.0" +git tag v1.1.0 +git push +git push origin v1.1.0 +``` + +## 打包流程 + +当前打包入口:`pqAutomationApp.spec` + +推荐发布步骤: + +1. 修改 `app_version.py` 中的 `APP_VERSION` +2. 更新本文件中的变更说明 +3. 提交代码并打 Tag +4. 执行打包命令 +5. 将 `dist/pqAutomationApp` 复制为带版本号的发布目录 + +打包命令: + +```powershell +pyinstaller pqAutomationApp.spec +``` + +建议发布目录命名: + +- `PQAutomationApp_v1.1.0` +- `PQAutomationApp_v1.1.0_20260416` + +## 每次发布前检查 + +1. 版本号是否已更新 +2. 关键功能是否回归测试 +3. `dist/` 和 `build/` 是否未提交到 Git +4. 发布说明是否已更新 +5. Tag 是否与版本号一致 + +## 变更记录模板 + +每次发布可按下面格式补充: + +### v0.1.0 + +- 首次建立版本发布机制 +- 软件标题显示版本号 +- 增加 `.gitignore` +- 增加发布流程文档 \ No newline at end of file diff --git a/UniTAP/.pylintrc b/UniTAP/.pylintrc new file mode 100644 index 0000000..a11f546 --- /dev/null +++ b/UniTAP/.pylintrc @@ -0,0 +1,644 @@ +[MAIN] + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Clear in-memory caches upon conclusion of linting. Useful if running pylint +# in a server-like mode. +clear-cache-post-run=no + +# Load and enable all available extensions. Use --list-extensions to see a list +# all available extensions. +#enable-all-extensions= + +# In error mode, messages with a category besides ERROR or FATAL are +# suppressed, and no reports are done by default. Error mode is compatible with +# disabling specific errors. +#errors-only= + +# Always return a 0 (non-error) status code, even if lint errors are found. +# This is primarily useful in continuous integration scripts. +#exit-zero= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-allow-list= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. (This is an alternative name to extension-pkg-allow-list +# for backward compatibility.) +extension-pkg-whitelist= + +# Return non-zero exit code if any of these messages/categories are detected, +# even if score is above --fail-under value. Syntax same as enable. Messages +# specified are enabled, while categories only check already-enabled messages. +fail-on= + +# Specify a score threshold under which the program will exit with error. +fail-under=10 + +# Interpret the stdin as a python script, whose filename needs to be passed as +# the module_or_package argument. +#from-stdin= + +# Files or directories to be skipped. They should be base names, not paths. +ignore=CVS + +# Add files or directories matching the regular expressions patterns to the +# ignore-list. The regex matches against paths and can be in Posix or Windows +# format. Because '\\' represents the directory delimiter on Windows systems, +# it can't be used as an escape character. +ignore-paths= + +# Files or directories matching the regular expression patterns are skipped. +# The regex matches against base names, not paths. The default value ignores +# Emacs file locks +ignore-patterns=^\.# + +# List of module names for which member attributes should not be checked and +# will not be imported (useful for modules/projects where namespaces are +# manipulated during runtime and thus existing member attributes cannot be +# deduced by static analysis). It supports qualified module names, as well as +# Unix pattern matching. +ignored-modules= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use, and will cap the count on Windows to +# avoid hangs. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Resolve imports to .pyi stubs if available. May reduce no-member messages and +# increase not-an-iterable messages. +prefer-stubs=no + +# Minimum Python version to use for version dependent checks. Will default to +# the version used to run pylint. +py-version=3.10 + +# Discover python modules and packages in the file system subtree. +recursive=no + +# Add paths to the list of the source roots. Supports globbing patterns. The +# source root is an absolute path or a path relative to the current working +# directory used to determine a package namespace for modules located under the +# source root. +source-roots= + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + +# In verbose mode, extra non-checker-related info will be displayed. +#verbose= + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. If left empty, argument names will be checked with the set +# naming style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. If left empty, attribute names will be checked with the set naming +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. If left empty, class attribute names will be checked +# with the set naming style. +#class-attribute-rgx= + +# Naming style matching correct class constant names. +class-const-naming-style=UPPER_CASE + +# Regular expression matching correct class constant names. Overrides class- +# const-naming-style. If left empty, class constant names will be checked with +# the set naming style. +#class-const-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. If left empty, class names will be checked with the set naming style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. If left empty, constant names will be checked with the set naming +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. If left empty, function names will be checked with the set +# naming style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. If left empty, inline iteration names will be checked +# with the set naming style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. If left empty, method names will be checked with the set naming style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. If left empty, module names will be checked with the set naming style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Regular expression matching correct type alias names. If left empty, type +# alias names will be checked with the set naming style. +#typealias-rgx= + +# Regular expression matching correct type variable names. If left empty, type +# variable names will be checked with the set naming style. +#typevar-rgx= + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. If left empty, variable names will be checked with the set +# naming style. +#variable-rgx= + + +[CLASSES] + +# Warn about protected attribute access inside special methods +check-protected-access-in-special-methods=no + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + asyncSetUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make,os._exit + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + + +[DESIGN] + +# List of regular expressions of class ancestor names to ignore when counting +# public methods (see R0903) +exclude-too-few-public-methods= + +# List of qualified class names to ignore when counting class parents (see +# R0901) +ignored-parents= + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of positional arguments for function / method. +max-positional-arguments=5 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when caught. +overgeneral-exceptions=builtins.BaseException,builtins.Exception + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow explicit reexports by alias from a package __init__. +allow-reexport-from-package=no + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules= + +# Output a graph (.gv or any supported image format) of external dependencies +# to the given file (report RP0402 must not be disabled). +ext-import-graph= + +# Output a graph (.gv or any supported image format) of all (i.e. internal and +# external) dependencies to the given file (report RP0402 must not be +# disabled). +import-graph= + +# Output a graph (.gv or any supported image format) of internal dependencies +# to the given file (report RP0402 must not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[LOGGING] + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, +# UNDEFINED. +confidence=HIGH, + CONTROL_FLOW, + INFERENCE, + INFERENCE_FAILURE, + UNDEFINED + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then re-enable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=all + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=syntax-error, + no-member, + method-hidden, + unreachable, + attribute-defined-outside-init, + no-name-in-module, + undefined-variable + + +[METHOD_ARGS] + +# List of qualified names (i.e., library.method) which require a timeout +# parameter e.g. 'requests.api.get,requests.api.post' +timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +notes-rgx= + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit,argparse.parse_error + +# Let 'consider-using-join' be raised when the separator to join on would be +# non-empty (resulting in expected fixes of the type: ``"- " + " - +# ".join(items)``) +suggest-join-with-non-empty-separator=yes + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'fatal', 'error', 'warning', 'refactor', +# 'convention', and 'info' which contain the number of messages in each +# category, as well as 'statement' which is the total number of statements +# analyzed. This score is used by the global evaluation report (RP0004). +evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +msg-template= + +# Set the output format. Available formats are: text, parseable, colorized, +# json2 (improved json format), json (old json format) and msvs (visual +# studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +#output-format= + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[SIMILARITIES] + +# Comments are removed from the similarity computation +ignore-comments=yes + +# Docstrings are removed from the similarity computation +ignore-docstrings=yes + +# Imports are removed from the similarity computation +ignore-imports=yes + +# Signatures are removed from the similarity computation +ignore-signatures=yes + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. No available dictionaries : You need to install +# both the python package and the system dependency for enchant to work. +spelling-dict= + +# List of comma separated words that should be considered directives if they +# appear at the beginning of a comment and should not be checked. +spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of symbolic message names to ignore for Mixin members. +ignored-checks-for-mixins=no-member, + not-async-context-manager, + not-context-manager, + attribute-defined-outside-init + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# Regex pattern to define which classes are considered mixins. +mixin-class-rgx=.*[Mm]ixin + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of names allowed to shadow builtins +allowed-redefined-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io diff --git a/UniTAP/__init__.py b/UniTAP/__init__.py new file mode 100644 index 0000000..c86273d --- /dev/null +++ b/UniTAP/__init__.py @@ -0,0 +1,11 @@ +from UniTAP.tsi_lib import * +from UniTAP.common import * +from UniTAP.version import __version__ +from UniTAP import dev +from UniTAP import utils + +# +# Logging configuration. +# +from UniTAP.utils import tsi_logging as logging +logging.set_enabled(False) diff --git a/UniTAP/__main__.py b/UniTAP/__main__.py new file mode 100644 index 0000000..c859cc3 --- /dev/null +++ b/UniTAP/__main__.py @@ -0,0 +1,6 @@ +from UniTAP import TsiLib + +if __name__ == '__main__': + tsi = TsiLib() + + # UniTAP.print_device_list() diff --git a/UniTAP/common/__init__.py b/UniTAP/common/__init__.py new file mode 100644 index 0000000..bf6b509 --- /dev/null +++ b/UniTAP/common/__init__.py @@ -0,0 +1,9 @@ +from .color_info import ColorInfo +from .data_info import DataInfo +from .timing import Timing +from .video_mode import VideoMode +from .video_frame import VideoFrame, ImageFileFormat, get_vf_from_image +from .audio_mode import AudioMode, AudioFileFormat, AudioFrameData, AudioFormat +from .timestamp import Timestamp +from .dsc_video_frame import VideoFrameDSC, CompressionInfo +from .dsc_compression_info import create_from_pps diff --git a/UniTAP/common/audio_mode.py b/UniTAP/common/audio_mode.py new file mode 100644 index 0000000..3ecb91d --- /dev/null +++ b/UniTAP/common/audio_mode.py @@ -0,0 +1,271 @@ +from enum import IntEnum +from UniTAP.common.timestamp import Timestamp + + +class AudioFileFormat(IntEnum): + """ + Describe all supported audio file formats for saving audio: + - BIN. + - WAV. + """ + UNKNOWN = -1 + BIN = 0 + WAV = 1 + + +class AudioFormat(IntEnum): + """ + Describe all supported audio formats: + - PCMAudio. + """ + Unknown = -1 + L_PCM = 0xFFFF + + +class AudioMode: + """ + Class `AudioMode` contains part information of audio: sample rate, count of bits and channel count. + """ + def __init__(self, sample_rate: int = 44100, bits: int = 16, channel_count: int = 2): + self.sample_rate = sample_rate + self.bits = bits + self.channel_count = channel_count + + def __str__(self): + return f"Sample rate: {self.sample_rate}\n" \ + f"Bits: {self.bits}\n" \ + f"Channel count: {self.channel_count}\n" + + def __eq__(self, other): + return self.sample_rate == other.sample_rate and \ + self.bits == other.bits and \ + self.channel_count == other.channel_count + + def is_valid(self) -> bool: + """ + + Check that information is valid (all values more than 0). + + Returns: + object of bool type. + """ + return self.sample_rate > 0 and \ + self.bits > 0 and \ + self.channel_count > 0 + + +class AudioFrameData: + """ + Class `AudioFrameData` describes captured frame from Sink (RX - receiver) side. Contains information of audio: + `AudioMode`, samples, `AudioFormat`, frame counter, `Timestamp`, audio data. + """ + def __init__(self, audio_mode: AudioMode = AudioMode(), samples: int = 0, + sample_format: AudioFormat = AudioFormat.Unknown, frame_counter: int = 0, + timestamp: Timestamp = Timestamp(0), data: bytearray = bytearray()): + self.__audio_mode = audio_mode + self.__samples = samples + self.__sample_format = sample_format + self.__frame_counter = frame_counter + self.__timestamp = timestamp + self.__data = data + + @property + def channel_count(self) -> int: + """ + + Returns channel count. + + Returns: + object of int type. + """ + return self.__audio_mode.channel_count + + @property + def samples(self) -> int: + """ + + Returns samples. + + Returns: + object of int type. + """ + return self.__samples + + @property + def sample_size(self) -> int: + """ + + Returns sample size. + + Returns: + object of int type. + """ + return self.__audio_mode.bits + + @property + def sample_rate(self) -> int: + """ + + Returns sample rate. + + Returns: + object of int type. + """ + return self.__audio_mode.sample_rate + + @property + def sample_format(self) -> AudioFormat: + """ + + Returns sample format. + + Returns: + object of AudioFormat type. + """ + return self.__sample_format + + @property + def frame_counter(self) -> int: + """ + + Returns frame counter. + + Returns: + object of int type. + """ + return self.__frame_counter + + @property + def timestamp(self) -> Timestamp: + """ + + Returns timestamp. + + Returns: + object of Timestamp type. + """ + return self.__timestamp + + @property + def data(self) -> bytearray: + """ + + Returns data. + + Returns: + object of bytearray type. + """ + return self.__data + + @channel_count.setter + def channel_count(self, channel_count: int): + """ + + Allows setting new value to channel count. + + Args: + channel_count (int) - must be more than 0 + """ + if channel_count <= 0: + raise ValueError(f"Channel count must be more than 0.") + self.__audio_mode.channel_count = channel_count + + @samples.setter + def samples(self, samples: int): + """ + + Allows setting new value to samples. + + Args: + samples (int) - must be more than 0 + """ + if samples <= 0: + raise ValueError(f"Samples must be more than 0.") + self.__samples = samples + + @sample_size.setter + def sample_size(self, sample_size: int): + """ + + Allows setting new value to sample size. + + Args: + sample_size (int) - must be more than 0 + """ + if sample_size <= 0: + raise ValueError(f"Sample size must be more than 0.") + self.__audio_mode.bits = sample_size + + @sample_rate.setter + def sample_rate(self, sample_rate: int): + """ + + Allows setting new value to sample rate. + + Args: + sample_rate (int) - must be more than 0 + """ + if sample_rate <= 0: + raise ValueError(f"Sample rate must be more than 0.") + self.__audio_mode.sample_rate = sample_rate + + @sample_format.setter + def sample_format(self, sample_format: int): + """ + + Allows setting new value to sample format. + + Args: + sample_format (int) - must be more than 0 + """ + if sample_format <= 0: + raise ValueError(f"Sample format must be more than 0.") + self.__sample_format = AudioFormat(sample_format) + + @timestamp.setter + def timestamp(self, timestamp: int): + """ + + Allows setting new value to timestamp. + + Args: + timestamp (int) - must be more than 0 + """ + if timestamp <= 0: + raise ValueError(f"Timestamp must be more than 0.") + self.__timestamp.value = timestamp + + @frame_counter.setter + def frame_counter(self, frame_counter: int): + """ + + Allows setting new value to frame counter. + + Args: + frame_counter (int) - must be more than -1 + """ + if frame_counter < 0: + raise ValueError(f"Frame counter must be more than 0.") + self.__frame_counter = frame_counter + + @data.setter + def data(self, value: bytearray): + """ + + Allows setting new value to data. + + Args: + value (bytearray) - length of value must be more than 0 + """ + if len(value) <= 0: + raise ValueError(f"Audio data length must be more than 0.") + self.__data = value + + def __str__(self): + return f"Sample rate: {self.sample_rate}\n" \ + f"Bits (Sample size): {self.sample_size}\n" \ + f"Channel count: {self.channel_count}\n" \ + f"Sample format: {self.sample_format.name}\n" \ + f"Timestamp: {self.timestamp.to_n_sec} n sec\n" \ + f"Frame counter: {self.frame_counter}\n" \ + f"Length of data: {self.data} bytes\n" diff --git a/UniTAP/common/color_info.py b/UniTAP/common/color_info.py new file mode 100644 index 0000000..bba2814 --- /dev/null +++ b/UniTAP/common/color_info.py @@ -0,0 +1,111 @@ +from enum import IntEnum + + +class ColorInfo: + """ + + Class contains information of frame `ColorFormat`, `DynamicRange`, `Colorimetry`. + + """ + class ColorFormat(IntEnum): + """ + Contains values of possible color format. + """ + CF_NONE = 0 + CF_UNKNOWN = 1 + CF_RGB = 2 + CF_YCbCr_422 = 3 + CF_YCbCr_444 = 4 + CF_YCbCr_420 = 5 + CF_IDO_DEFINED = 6 + CF_Y_ONLY = 7 + CF_RAW = 8 + CF_DSC = 9 + + class DynamicRange(IntEnum): + """ + Contains values of possible dynamic range. + """ + DR_UNKNOWN = -1 + DR_VESA = 0 + DR_CTA = 1 + + class Colorimetry(IntEnum): + """ + Contains values of possible colorimetry. + """ + CM_NONE = 0 + CM_RESERVED = 1 + CM_sRGB = 2 + CM_SMPTE_170M = 3 + CM_ITUR_BT601 = 4 + CM_ITUR_BT709 = 5 + CM_xvYCC601 = 6 + CM_xvYCC709 = 7 + CM_sYCC601 = 8 + CM_AdobeYCC601 = 9 + CM_AdobeRGB = 10 + CM_ITUR_BT2020_YcCbcCrc = 11 + CM_ITUR_BT2020_YCbCr = 12 + CM_ITUR_BT2020_RGB = 13 + CM_RGB_WIDE_GAMUT_FIX = 14 + CM_RGB_WIDE_GAMUT_FLT = 15 + CM_DCI_P3 = 16 + CM_DICOM_1_4_GRAY_SCALE = 17 + CM_CUSTOM_COLOR_PROFILE = 18 + + CM_opYCC601 = CM_AdobeYCC601 + CM_opRGB = CM_AdobeRGB + + __COMPONENT_MULTIPLIER = { + ColorFormat.CF_RGB: 3, + ColorFormat.CF_YCbCr_422: 2, + ColorFormat.CF_YCbCr_444: 3, + ColorFormat.CF_YCbCr_420: 3 / 2, + ColorFormat.CF_Y_ONLY: 1, + ColorFormat.CF_RAW: 3 + } + + def __init__(self): + self.colorimetry = self.Colorimetry.CM_NONE + self.color_format = self.ColorFormat.CF_NONE + self.dynamic_range = self.DynamicRange.DR_VESA + self.bpc = 0 + + def __str__(self): + return f"Color format: {self.color_format.name}\n" \ + f"Colorimetry: {self.colorimetry.name}\n" \ + f"Dynamic Range: {self.dynamic_range.name}\n" \ + f"BPC: {self.bpc}" + + def __eq__(self, other): + return self.bpc == other.bpc and\ + self.color_format == other.color_format and\ + self.colorimetry == other.colorimetry and\ + self.dynamic_range == other.dynamic_range + + def is_valid(self) -> bool: + """ + + Check that information is valid (not equal NONE state and bpc more than 0). + + Returns: + object of bool type. + """ + return self.bpc > 0 and\ + self.color_format != self.ColorFormat.CF_NONE and\ + self.colorimetry != self.Colorimetry.CM_NONE + + @property + def bpp(self) -> int: + """ + + Returns calculated bits per pixel for this color info (except DSC). 0 if color info is not valid. + + Returns: + object of int type. + """ + if self.is_valid() and self.color_format != self.ColorFormat.CF_DSC: + return round(self.bpc * self.__COMPONENT_MULTIPLIER.get(self.color_format, 1)) + else: + return 0 diff --git a/UniTAP/common/data_info.py b/UniTAP/common/data_info.py new file mode 100644 index 0000000..42f8485 --- /dev/null +++ b/UniTAP/common/data_info.py @@ -0,0 +1,64 @@ +from enum import IntEnum + + +class DataInfo: + """ + + Class contains information of frame `Packing`, `ComponentOrder`, `Alignment`. + + """ + class Packing(IntEnum): + """ + Contains values of possible packing. + """ + P_UNKNOWN = 0 + P_PLANAR = 1 + P_PACKED = 2 + + class ComponentOrder(IntEnum): + """ + Contains values of possible component order. + """ + CO_UNKNOWN = 0 + CO_UCDRX = 1 + CO_RGB = 2 + CO_RGBA = 3 + CO_BGR = 4 + CO_BGRA = 5 + CO_YCbCr = 6 + CO_CbY0CrY1 = 7 + + class Alignment(IntEnum): + """ + Contains values of possible alignment. + """ + A_UNKNOWN = 0 + A_MSB = 1 + A_LSB = 2 + + def __init__(self): + self.packing = self.Packing.P_UNKNOWN + self.component_order = self.ComponentOrder.CO_UNKNOWN + self.alignment = self.Alignment.A_UNKNOWN + + def is_valid(self) -> bool: + """ + + Check that information is valid (not equal UNKNOWN state). + + Returns: + object of bool type. + """ + return self.packing != self.Packing.P_UNKNOWN and\ + self.component_order != self.ComponentOrder.CO_UNKNOWN and\ + self.alignment != self.Alignment.A_UNKNOWN + + def __str__(self): + return f"Packing: {self.packing.name}\n" \ + f"Component Order: {self.component_order.name}\n" \ + f"Alignment: {self.alignment.name}\n" + + def __eq__(self, other): + return self.packing == other.packing and \ + self.component_order == other.component_order and \ + self.alignment == other.alignment \ No newline at end of file diff --git a/UniTAP/common/dsc_compression_info.py b/UniTAP/common/dsc_compression_info.py new file mode 100644 index 0000000..6075074 --- /dev/null +++ b/UniTAP/common/dsc_compression_info.py @@ -0,0 +1,96 @@ +from enum import IntEnum + + +class DscCompressionInfo: + """ + + Class contains information about DSC compression used on frame. + + """ + class DscColorFormat(IntEnum): + """ + Contains values of possible color format. + """ + CF_NONE = -1 + CF_RGB = 0 + CF_YCbCr_422 = 1 + CF_YCbCr_444 = 2 + CF_YCbCr_420 = 3 + CF_Simple_422 = 4 + + def __init__(self): + self.color_format = DscCompressionInfo.DscColorFormat.CF_NONE + self.bpp = 0 + self.is_block_prediction_enabled = False + self.h_slice_size = 0 + self.v_slice_size = 0 + self.buffer_bit_depth = 0 + self.version = (0, 0) + self.is_simple_as_444 = False + + def is_valid(self) -> bool: + """ + Return state of the video frame and check color_format, bpp, h and v slice_size and DSC version. + If everything ok, return True, otherwise - False. + + Returns: + object of `bool` type. + """ + return self.color_format is not None and\ + self.bpp > 0 and\ + self.h_slice_size > 0 and\ + self.v_slice_size > 0 and\ + self.version in [(1, 2), (1, 1)] + + +def create_from_pps(pps_bytearray: bytearray) -> DscCompressionInfo: + """ + Fill structure 'DscCompressionInfo' from PPS header of the DSC image. + + Returns: + object of `DscCompressionInfo` type. + """ + if b'DSCF' not in pps_bytearray: + if len(pps_bytearray) < 128: + raise ValueError("Incorrect PPS size!") + pps = pps_bytearray + else: + if len(pps_bytearray) < 132: + raise ValueError("Incorrect PPS size!") + pps = pps_bytearray[4:] + + is_yuv = ((pps[4] >> 4) & 1) == 0 + is_simple422 = ((pps[4] >> 3) & 1) == 1 + is_native422 = (pps[88] & 1) == 1 + is_native420 = ((pps[88] >> 1) & 1) == 1 + width = (pps[8] << 8) | pps[9] + height = (pps[6] << 8) | pps[7] + slice_height = (pps[10] << 8) | pps[11] + slice_width = (pps[12] << 8) | pps[13] + + info = DscCompressionInfo() + + info.version = (pps[0] >> 4 & 0xf, pps[0] & 0xf) + info.buffer_bit_depth = pps[3] & 0xf + info.is_block_prediction_enabled = bool(pps[4] >> 5 & 0x1) + info.h_slice_size = int(width / slice_width) + info.v_slice_size = int(height / slice_height) + + if is_native422 or is_native420: + info.bpp = int(pps[4] & 0x3 << 8 | pps[5]) / 2 + else: + info.bpp = int(pps[4] & 0x3 << 8 | pps[5]) + + if is_yuv: + if is_simple422: + info.color_format = DscCompressionInfo.DscColorFormat.CF_Simple_422 + elif is_native422: + info.color_format = DscCompressionInfo.DscColorFormat.CF_YCbCr_422 + elif is_native420: + info.color_format = DscCompressionInfo.DscColorFormat.CF_YCbCr_420 + else: + info.color_format = DscCompressionInfo.DscColorFormat.CF_YCbCr_444 + else: + info.color_format = DscCompressionInfo.DscColorFormat.CF_RGB + + return info diff --git a/UniTAP/common/dsc_video_frame.py b/UniTAP/common/dsc_video_frame.py new file mode 100644 index 0000000..942e5a7 --- /dev/null +++ b/UniTAP/common/dsc_video_frame.py @@ -0,0 +1,29 @@ +from .dsc_compression_info import DscCompressionInfo as CompressionInfo +from .video_frame import VideoFrame + + +class VideoFrameDSC(VideoFrame): + + """ + Class `VideoFrameDSC` contains base information about DSC compressed video frame: + - Height (int). + - Width (int). + - Data (bytearray). + - Color info (object of `ColorInfo`). + - Data info (object of `DataInfo`). + - Timestamp (object of `Timestamp`). + - CompressionInfo (object of `CompressionInfo`) + """ + + def __init__(self): + super().__init__() + self.compression_info = CompressionInfo() + + def is_compressed(self) -> bool: + """ + Return state of the video frame, compressed it or not. + + Returns: + object of `bool` type. + """ + return not self._compressed diff --git a/UniTAP/common/timestamp.py b/UniTAP/common/timestamp.py new file mode 100644 index 0000000..2212e2e --- /dev/null +++ b/UniTAP/common/timestamp.py @@ -0,0 +1,64 @@ +class Timestamp: + + """ + + Class contains information about timestamp in several representation variant: + - Seconds `to_sec`. + - Milliseconds `to_m_sec`. + - Microseconds `to_u_sec`. + - Nanoseconds `to_n_sec` or `value`. + + """ + + def __init__(self, nano_secs: int): + self.__value = nano_secs + + @property + def to_sec(self) -> float: + """ + Returns time in seconds. + """ + return self.__value / (10 ** 9) + + @property + def to_m_sec(self) -> float: + """ + Returns time milliseconds seconds. + """ + return self.__value / (10 ** 6) + + @property + def to_u_sec(self) -> float: + """ + Returns time microseconds seconds. + """ + return self.__value / (10 ** 3) + + @property + def to_n_sec(self) -> float: + """ + Returns time nanoseconds seconds. + """ + return self.__value + + @property + def value(self) -> float: + """ + Returns time nanoseconds seconds. + """ + return self.__value + + @value.setter + def value(self, value: int): + """ + Set time in nanoseconds seconds. + """ + if value <= 0: + raise ValueError(f"Value of timestamp cannot be less than 0.") + self.__value = value + + def __str__(self): + return f"{self.to_u_sec} milliseconds" + + def __eq__(self, other): + return self.__value == other.__value diff --git a/UniTAP/common/timing.py b/UniTAP/common/timing.py new file mode 100644 index 0000000..be70a94 --- /dev/null +++ b/UniTAP/common/timing.py @@ -0,0 +1,90 @@ +from enum import IntEnum + + +class Timing: + + """ + Class `Timing` contains information about Timing: all resolutions, timing id, frame rate, `AspectRatio`, + `Standard`, `ReduceBlanking`. + """ + + class Standard(IntEnum): + """ + Class `Standard` contains all possible variants of timing standards. + """ + SD_NONE = 0 + SD_CVT = 1 + SD_DMT = 2 + SD_CTA = 3 + SD_UGF = 4 + SD_OVT = 5 + + class AspectRatio(IntEnum): + """ + Class `AspectRatio` contains all possible variants of timing aspect ratio. + """ + AR_NONE = 0 + AR_4_3 = 1 + AR_16_9 = 2 + + class ReduceBlanking(IntEnum): + """ + Class `ReduceBlanking` contains all possible variants of timing reduce blanking. + """ + RB_NONE = 0 + RB1 = 1 + RB2 = 2 + RB3 = 3 + + def __init__(self): + self.frame_rate = 0.0 + self.hactive = 0 + self.vactive = 0 + self.htotal = 0 + self.vtotal = 0 + self.hstart = 0 + self.vstart = 0 + self.hswidth = 0 + self.vswidth = 0 + self.id = 0 + self.aspect_ratio = self.AspectRatio.AR_NONE + self.standard = self.Standard.SD_NONE + self.reduce_blanking = self.ReduceBlanking.RB_NONE + + def __str__(self): + return f"{self.frame_rate / 1000:03} " \ + f"{self.htotal} {self.hstart} {self.hactive} {self.hswidth:+} " \ + f"{self.vtotal} {self.vstart} {self.vactive} {self.vswidth:+}" + + def __eq__(self, other) -> bool: + return self.hactive == other.hactive and self.vactive == other.vactive and self.htotal == other.htotal and \ + self.vtotal == other.vtotal and self.hstart == other.hstart and self.vstart == other.vstart and \ + self.hswidth == other.hswidth and self.vswidth == other.vswidth + + def is_valid(self) -> bool: + """ + Check that timing is correct (Resolutions and frame rate more than 0) + + Returns: + is valid (bool) - valid (True) or not (False) + """ + return self.hactive > 0 and \ + self.vactive > 0 and \ + self.htotal > 0 and \ + self.vtotal > 0 and \ + self.hstart > 0 and \ + self.vstart > 0 and \ + self.frame_rate > 0 + + @property + def pixel_clock(self) -> float: + """ + Returns calculated pixel clock required for this video mode in MHz. 0.0 if video mode is not valid. + + Returns: + pixel clock (float) + """ + if self.is_valid(): + return round(self.htotal * self.vtotal * self.frame_rate / 1000000000.0, 3) + else: + return 0.0 diff --git a/UniTAP/common/video_frame.py b/UniTAP/common/video_frame.py new file mode 100644 index 0000000..26a0b71 --- /dev/null +++ b/UniTAP/common/video_frame.py @@ -0,0 +1,94 @@ +import os + +from enum import IntEnum +from .color_info import ColorInfo +from .data_info import DataInfo +from .timestamp import Timestamp +from PIL import Image + + +class ImageFileFormat(IntEnum): + """ + Describe all supported image file formats for saving `VideoFrame`: + - BIN. + - PPM. + - BMP. + - DSC. + """ + IFF_BIN = 0 + IFF_PPM = 1 + IFF_BMP = 2 + IFF_DSC = 3 + + +class VideoFrame: + + """ + Class `VideoFrame` contains base information about video frame: + - Height (int). + - Width (int). + - Data (bytearray). + - Color info (object of `ColorInfo`). + - Data info (object of `DataInfo`). + - Timestamp (object of `Timestamp`). + """ + + def __init__(self): + self.width = 0 + self.height = 0 + self.data = bytearray() + self.color_info = ColorInfo() + self.data_info = DataInfo() + self.timestamp = Timestamp(0) + self._compressed = False + + def __str__(self): + return f"Resolution: {self.width}x{self.height}\n" \ + f"Data length: {len(self.data)}\n" \ + f"Color Info:\n{self.color_info}\n" \ + f"Data Info:\n{self.data_info}\n" \ + f"Timestamp:\n{self.timestamp}\n" + + def is_compressed(self) -> bool: + return self._compressed + + +def get_vf_from_image(path: str, width: int, height: int) -> VideoFrame: + """ + + Function allows getting prepared object of `VideoFrame` from external (custom) image. + + Args: + path (str) - full path to image. + width (int) - width of image. + height (int) - height of image. + + """ + if not os.path.exists(path): + raise FileNotFoundError(f"Image: {path} is missing!") + + image = Image.open(path) + + if width < image.size[0] and height < image.size[1]: + image = image.crop((0, 0, width, height)) + + size = image.size + + if size == [0, 0]: + raise ValueError("Invalid image size.") + + convert_image = image.convert('RGB').resize(size=(width, height), resample=Image.Resampling.LANCZOS) + + video_frame = VideoFrame() + video_frame.data = bytearray(convert_image.tobytes()) + video_frame.width = width + video_frame.height = height + video_frame.color_info.bpc = 8 + video_frame.color_info.color_format = ColorInfo.ColorFormat.CF_RGB + video_frame.color_info.colorimetry = ColorInfo.Colorimetry.CM_sRGB + # video_frame.color_info.dynamic_range = ColorInfo.DynamicRange.DR_VESA + video_frame.data_info.packing = DataInfo.Packing.P_PACKED + video_frame.data_info.component_order = DataInfo.ComponentOrder.CO_RGB + video_frame.data_info.alignment = DataInfo.Alignment.A_LSB + + return video_frame diff --git a/UniTAP/common/video_mode.py b/UniTAP/common/video_mode.py new file mode 100644 index 0000000..96be19f --- /dev/null +++ b/UniTAP/common/video_mode.py @@ -0,0 +1,52 @@ +from .timing import Timing +from .color_info import ColorInfo + + +class VideoMode: + + """ + + Class `VideoMode` combines information about `Timing` and `ColorInfo`. + + """ + + def __init__(self, timing: Timing = Timing(), color_info: ColorInfo = ColorInfo()): + self.timing = timing + self.color_info = color_info + + def __str__(self): + return f"{self.timing.frame_rate / 1000:03} " \ + f"{self.timing.htotal} {self.timing.hstart} " \ + f"{self.timing.hactive} {self.timing.hswidth:+} " \ + f"{self.timing.vtotal} {self.timing.vstart} " \ + f"{self.timing.vactive} {self.timing.vswidth:+} " \ + f"{self.color_info.color_format.name}/" \ + f"{self.color_info.colorimetry.name}/" \ + f"{self.color_info.dynamic_range.name} {self.color_info.bpc}" + + def __eq__(self, other): + return self.timing == other.timing and self.color_info == other.color_info + + def is_valid(self) -> bool: + """ + + Check that `Timing` and `ColorInfo` of Video mode is valid. + + Returns: + object of bool type - Video mode valid or not + """ + return self.timing.is_valid() and self.color_info.is_valid() + + @property + def bit_rate(self) -> float: + """ + + Returns calculated bit rate required for this video mode in Gbps. 0 if video mode is not valid + + Returns: + object of float type + """ + if self.is_valid(): + return round(self.timing.pixel_clock * self.color_info.bpp / 1000.0, 3) + else: + return 0.0 diff --git a/UniTAP/dev/__init__.py b/UniTAP/dev/__init__.py new file mode 100644 index 0000000..8c42c11 --- /dev/null +++ b/UniTAP/dev/__init__.py @@ -0,0 +1,2 @@ +from .device import * +from .ports import * diff --git a/UniTAP/dev/dev_3xx_roles.py b/UniTAP/dev/dev_3xx_roles.py new file mode 100644 index 0000000..d510aa7 --- /dev/null +++ b/UniTAP/dev/dev_3xx_roles.py @@ -0,0 +1,459 @@ +from .ports import DPRX, DPTX, HDRX, HDTX, PDC340 +from .modules import MemoryManager, Capturer, DUTTests +from UniTAP.libs.lib_tsi.tsi_io import DeviceIO, PortProtocol + + +class UCD340: + """ + Class `UCD340` describes of device UCD-340. Device has several possible role: + - 'USB-C, DP Alt Mode Reference Sink' `USBCSink` + - 'USB-C, DP Alt Mode Reference Source' `USBCSource`. + """ + class USBCSink: + """ + Class `USBCSink` contains information of available functionality modules for role USB-C Sink (RX - receiver) + role: + - `DPRX`. + - `DUTTests`. + - `PDC`. + """ + def __init__(self, dev_io: DeviceIO, memory_manager: MemoryManager, capturer: Capturer): + self.__port_io = dev_io.create_port_io(0, PortProtocol.DisplayPortThrowUSBC) + self.__dprx = DPRX(self.__port_io, memory_manager, capturer) + self.__dut_tests = DUTTests(dev_io) + self.__pdcrx = PDC340(self.__port_io) + + @property + def dprx(self) -> DPRX: + """ + Returns DP Sink (RX - receiver) role. + + Returns: + object of DPRX type. + """ + return self.__dprx + + @property + def pdcrx(self) -> PDC340: + """ + Returns PDC Sink (RX - receiver) role. + + Returns: + object of `PDC340` type. + """ + return self.__pdcrx + + @property + def dut_tests(self) -> DUTTests: + """ + Returns DUT Test module. + + Returns: + object of DUTTests type. + """ + return self.__dut_tests + + @property + def name(self) -> str: + """ + Returns name of role. + + Returns: + object of str type. + """ + return list(UCD340.ROLE_DICT.keys())[list(UCD340.ROLE_DICT.values()).index(type(self))] + + class USBCSource: + """ + Class `USBCSource` contains information of available functionality modules for role USB-C Source + (TX - transmitter) role: + - `DPTX`. + - `DUTTests`. + - `PDC`. + """ + def __init__(self, dev_io: DeviceIO, memory_manager: MemoryManager, capturer: Capturer): + self.__port_io = dev_io.create_port_io(0, PortProtocol.DisplayPortThrowUSBC) + self.__dptx = DPTX(self.__port_io, memory_manager, capturer) + self.__dut_tests = DUTTests(dev_io) + self.__pdctx = PDC340(self.__port_io) + + @property + def dptx(self) -> DPTX: + """ + Returns DP Source (TX - transmitter) role. + + Returns: + object of DPTX type. + """ + return self.__dptx + + @property + def pdctx(self) -> PDC340: + """ + Returns PDC Source (TX - transmitter) role. + + Returns: + object of `PDC340` type. + """ + return self.__pdctx + + @property + def dut_tests(self) -> DUTTests: + """ + Returns DUT Test module. + + Returns: + object of DUTTests type. + """ + return self.__dut_tests + + @property + def name(self) -> str: + """ + Returns name of role. + + Returns: + object of str type. + """ + return list(UCD340.ROLE_DICT.keys())[list(UCD340.ROLE_DICT.values()).index(type(self))] + + ROLE_DICT = { + "USB-C, DP Alt Mode Reference Sink": USBCSink, + "USB-C, DP Alt Mode Reference Source": USBCSource + } + + +class UCD240(UCD340): + """ + Class `UCD240` describes of device UCD-240 and have the same functionality as the class 'UCD340'. + Device has several possible role: + - 'USB-C, DP Alt Mode Reference Sink' `USBCSink` + - 'USB-C, DP Alt Mode Reference Source' `USBCSource`. + """ + + +class UCD323: + """ + Class `UCD323` describes of device UCD-323. Device has several possible role: + - 'DisplayPort Reference Sink (SST, HDCP 2.3)' `DPSink` + - 'DisplayPort Reference Source (SST, HDCP 2.3)' `DPSource` + - 'HDMI Reference Sink (HDCP 2.3)' `HDMISink` + - 'HDMI Reference Source (HDCP 2.3)' `HDMISource` + """ + class DPSink: + """ + Class `DPSink` contains information of available functionality modules for role DP Sink (RX - receiver) role: + - `DPRX`. + - `DUTTests`. + """ + def __init__(self, dev_io: DeviceIO, memory_manager: MemoryManager, capturer: Capturer): + self.__dprx = DPRX(dev_io.create_port_io(0, PortProtocol.DisplayPort), memory_manager, capturer) + self.__dut_tests = DUTTests(dev_io) + + @property + def dprx(self) -> DPRX: + """ + Returns DP Sink (RX - receiver) role. + + Returns: + object of DPRX type. + """ + return self.__dprx + + @property + def dut_tests(self) -> DUTTests: + """ + Returns DUT Test module. + + Returns: + object of DUTTests type. + """ + return self.__dut_tests + + @property + def name(self) -> str: + """ + Returns name of role. + + Returns: + object of str type. + """ + return list(UCD323.ROLE_DICT.keys())[list(UCD323.ROLE_DICT.values()).index(type(self))] + + class DPSource: + """ + Class `DPSink` contains information of available functionality modules for role DP Source (TX - transmitter) + role: + - `DPTX`. + - `DUTTests`. + """ + def __init__(self, dev_io: DeviceIO, memory_manager: MemoryManager, capturer: Capturer): + self.__dptx = DPTX(dev_io.create_port_io(0, PortProtocol.DisplayPort), memory_manager, capturer) + self.__dut_tests = DUTTests(dev_io) + + @property + def dptx(self) -> DPTX: + """ + Returns DP Source (TX - transmitter) role. + + Returns: + object of DPTX type. + """ + return self.__dptx + + @property + def dut_tests(self) -> DUTTests: + """ + Returns DUT Test module. + + Returns: + object of DUTTests type. + """ + return self.__dut_tests + + @property + def name(self) -> str: + """ + Returns name of role. + + Returns: + object of str type. + """ + return list(UCD323.ROLE_DICT.keys())[list(UCD323.ROLE_DICT.values()).index(type(self))] + + class HDMISink: + """ + Class `HDMISink` contains information of available functionality modules for role HDMI Sink (RX - receiver) + role: + - `HDRX`. + - `DUTTests`. + """ + def __init__(self, dev_io: DeviceIO, memory_manager: MemoryManager, capturer: Capturer): + self.__hdrx = HDRX(dev_io.create_port_io(0, PortProtocol.HDMI), memory_manager, capturer) + self.__dut_tests = DUTTests(dev_io) + + @property + def hdrx(self) -> HDRX: + """ + Returns HDMI Sink (RX - receiver) role. + + Returns: + object of HDRX type. + """ + return self.__hdrx + + @property + def dut_tests(self) -> DUTTests: + """ + Returns DUT Test module. + + Returns: + object of DUTTests type. + """ + return self.__dut_tests + + @property + def name(self) -> str: + """ + Returns name of role. + + Returns: + object of str type. + """ + return list(UCD323.ROLE_DICT.keys())[list(UCD323.ROLE_DICT.values()).index(type(self))] + + class HDMISource: + """ + Class `HDMISource` contains information of available functionality modules for role HDMI Source + (TX - transmitter) role: + - `HDTX`. + - `DUTTests`. + """ + def __init__(self, dev_io: DeviceIO, memory_manager: MemoryManager, capturer: Capturer): + self.__hdtx = HDTX(dev_io.create_port_io(0, PortProtocol.HDMI), memory_manager, capturer) + self.__dut_tests = DUTTests(dev_io) + + @property + def hdtx(self) -> HDTX: + """ + Returns HDMI Source (TX - transmitter) role. + + Returns: + object of HDTX type. + """ + return self.__hdtx + + @property + def dut_tests(self) -> DUTTests: + """ + Returns DUT Test module. + + Returns: + object of DUTTests type. + """ + return self.__dut_tests + + @property + def name(self) -> str: + """ + Returns name of role. + + Returns: + object of str type. + """ + return list(UCD323.ROLE_DICT.keys())[list(UCD323.ROLE_DICT.values()).index(type(self))] + + ROLE_DICT = { + "DisplayPort Reference Sink (SST, HDCP 2.3)": DPSink, + "DisplayPort Reference Source (SST, HDCP 2.3)": DPSource, + "HDMI Reference Sink (HDCP 2.3)": HDMISink, + "HDMI Reference Source (HDCP 2.3)": HDMISource, + } + + +class UCD301: + """ + Class `UCD301` describes of device UCD-301. Device has several possible role: + - 'DisplayPort Reference Sink (SST, HDCP 2.3)' `DPSink` + - 'HDMI Reference Sink (HDCP 2.3)' `HDMISink` + """ + class DPSink: + """ + Class `DPSink` contains information of available functionality modules for role DP Sink (RX - receiver) role: + - `DPRX`. + - `DUTTests`. + """ + def __init__(self, dev_io: DeviceIO, memory_manager: MemoryManager, capturer: Capturer): + self.__dprx = DPRX(dev_io.create_port_io(0, PortProtocol.DisplayPort), memory_manager, capturer) + self.__dut_tests = DUTTests(dev_io) + + @property + def dprx(self) -> DPRX: + """ + Returns DP Sink (RX - receiver) role. + + Returns: + object of DPRX type. + """ + return self.__dprx + + @property + def dut_tests(self) -> DUTTests: + """ + Returns DUT Test module. + + Returns: + object of DUTTests type. + """ + return self.__dut_tests + + @property + def name(self) -> str: + """ + Returns name of role. + + Returns: + object of str type. + """ + return list(UCD301.ROLE_DICT.keys())[list(UCD301.ROLE_DICT.values()).index(type(self))] + + class HDMISink: + """ + Class `HDMISink` contains information of available functionality modules for role HDMI Sink (RX - receiver) + role: + - `HDRX`. + - `DUTTests`. + """ + def __init__(self, dev_io: DeviceIO, memory_manager: MemoryManager, capturer: Capturer): + self.__hdrx = HDRX(dev_io.create_port_io(0, PortProtocol.HDMI), memory_manager, capturer) + self.__dut_tests = DUTTests(dev_io) + + @property + def hdrx(self) -> HDRX: + """ + Returns HDMI Sink (RX - receiver) role. + + Returns: + object of HDRX type. + """ + return self.__hdrx + + @property + def dut_tests(self) -> DUTTests: + """ + Returns DUT Test module. + + Returns: + object of DUTTests type. + """ + return self.__dut_tests + + @property + def name(self) -> str: + """ + Returns name of role. + + Returns: + object of str type. + """ + return list(UCD301.ROLE_DICT.keys())[list(UCD301.ROLE_DICT.values()).index(type(self))] + + class DPSinkHDMISink: + """ + Class `DPSinkHDMISink` contains information of available functionality modules for role DP and HDMI Sink + (RX - receiver) role: + - `DPRX`. + - `HDRX`. + - `DUTTests`. + """ + + def __init__(self, dev_io: DeviceIO, memory_manager: MemoryManager, capturer: Capturer): + self.__dprx = DPRX(dev_io.create_port_io(0, PortProtocol.DisplayPort), memory_manager, capturer) + self.__hdrx = HDRX(dev_io.create_port_io(0, PortProtocol.HDMI), memory_manager, capturer) + self.__dut_tests = DUTTests(dev_io) + + @property + def hdrx(self) -> HDRX: + """ + Returns HDMI Sink (RX - receiver) role. + + Returns: + object of HDRX type. + """ + return self.__hdrx + + @property + def dprx(self) -> DPRX: + """ + Returns DP Sink (RX - receiver) role. + + Returns: + object of DPRX type. + """ + return self.__dprx + + @property + def dut_tests(self) -> DUTTests: + """ + Returns DUT Test module. + + Returns: + object of DUTTests type. + """ + return self.__dut_tests + + @property + def name(self) -> str: + """ + Returns name of role. + + Returns: + object of str type. + """ + return list(UCD301.ROLE_DICT.keys())[list(UCD301.ROLE_DICT.values()).index(type(self))] + + ROLE_DICT = { + "DisplayPort Reference Sink (SST, HDCP 2.3)": DPSink, + "HDMI, DisplayPort (SST), SPDIF Reference Sink": DPSinkHDMISink, + "HDMI Reference Sink (HDCP 2.3)": HDMISink, + } diff --git a/UniTAP/dev/dev_4xx_roles.py b/UniTAP/dev/dev_4xx_roles.py new file mode 100644 index 0000000..3a4b27a --- /dev/null +++ b/UniTAP/dev/dev_4xx_roles.py @@ -0,0 +1,220 @@ +from UniTAP.dev.ports import DPRX4xx, DPTX4xx, HDRX4xx, HDTX4xx, PDC424 +from .modules import MemoryManager, Capturer, DUTTests +from UniTAP.libs.lib_tsi.tsi_io import DeviceIO, PortProtocol + + +class UCD400: + """ + Class `UCD400` describes of device UCD-400. Device has one possible role: + - 'DisplayPort Source and Sink' `DPSourceDPSink` + """ + class DPSourceDPSink: + """ + Class `DPSourceDPSink` contains information of available functionality modules for role DP Sink + (RX - receiver) and DP Source (TX - transmitter) roles: + - `DPRX4xx`. + - `DPTX4xx`. + - `DUTTests`. + """ + def __init__(self, dev_io: DeviceIO, memory_manager: MemoryManager, capturer: Capturer): + super().__init__() + self.__dprx = DPRX4xx(dev_io.create_port_io(0, PortProtocol.DisplayPort), memory_manager, capturer) + self.__dptx = DPTX4xx(dev_io.create_port_io(1, PortProtocol.DisplayPort), memory_manager, capturer) + self.__dut_tests = DUTTests(dev_io) + + @property + def dprx(self) -> DPRX4xx: + """ + Returns DP Sink (RX - receiver) role. + + Returns: + object of DPRX4xx type. + """ + return self.__dprx + + @property + def dptx(self) -> DPTX4xx: + """ + Returns DP Source (TX - transmitter) role. + + Returns: + object of DPTX4xx type. + """ + return self.__dptx + + @property + def dut_tests(self) -> DUTTests: + """ + Returns DUT Test module. + + Returns: + object of DUTTests type. + """ + return self.__dut_tests + + @property + def name(self) -> str: + """ + Returns name of role. + + Returns: + object of str type. + """ + return list(UCD400.ROLE_DICT.keys())[list(UCD400.ROLE_DICT.values()).index(type(self))] + + ROLE_DICT = { + "DisplayPort Source and Sink": DPSourceDPSink + } + + +class UCD422: + """ + Class `UCD422` describes of device UCD-422. Device has one possible role: + - 'HDMI Source and Sink' `HDMISourceHDMISink` + """ + class HDMISourceHDMISink: + """ + Class `DPSourceDPSink` contains information of available functionality modules for role HDMI Sink + (RX - receiver) and HDMI Source (TX - transmitter) roles: + - `HDRX4xx`. + - `HDTX4xx`. + - `DUTTests`. + """ + def __init__(self, dev_io: DeviceIO, memory_manager: MemoryManager, capturer: Capturer): + super().__init__() + self.__hdrx = HDRX4xx(dev_io.create_port_io(0, PortProtocol.HDMI), memory_manager, capturer) + self.__hdtx = HDTX4xx(dev_io.create_port_io(1, PortProtocol.HDMI), memory_manager, capturer) + self.__dut_tests = DUTTests(dev_io) + + @property + def hdtx(self) -> HDTX4xx: + """ + Returns HDMI Source (TX - transmitter) role. + + Returns: + object of HDTX4xx type. + """ + return self.__hdtx + + @property + def hdrx(self) -> HDRX4xx: + """ + Returns HDMI Sink (RX - receiver) role. + + Returns: + object of HDRX4xx type. + """ + return self.__hdrx + + @property + def dut_tests(self) -> DUTTests: + """ + Returns DUT Test module. + + Returns: + object of DUTTests type. + """ + return self.__dut_tests + + @property + def name(self) -> str: + """ + Returns name of role. + + Returns: + object of str type. + """ + return list(UCD422.ROLE_DICT.keys())[list(UCD422.ROLE_DICT.values()).index(type(self))] + + ROLE_DICT = { + "HDMI Source and Sink": HDMISourceHDMISink + } + + +class UCD424: + """ + Class `UCD424` describes of device UCD-424. Device has one possible role: + - 'USB-C Source and Sink' `USBCSourceUSBCSink`. + """ + class USBCSourceUSBCSink: + """ + Class `DPSourceDPSink` contains information of available functionality modules for role HDMI Sink + (RX - receiver) and HDMI Source (TX - transmitter) roles: + - `DPRX4xx`. + - `DPTX4xx`. + - `DUTTests`. + - `PDC`. + """ + def __init__(self, dev_io: DeviceIO, memory_manager: MemoryManager, capturer: Capturer): + super().__init__() + self.__port_rx = dev_io.create_port_io(0, PortProtocol.DisplayPortThrowUSBC) + self.__port_tx = dev_io.create_port_io(1, PortProtocol.DisplayPortThrowUSBC) + self.__dprx = DPRX4xx(self.__port_rx, memory_manager, capturer) + self.__dptx = DPTX4xx(self.__port_tx, memory_manager, capturer) + self.__dut_tests = DUTTests(dev_io) + self.__pdcrx = PDC424(self.__port_rx) + self.__pdctx = PDC424(self.__port_tx) + + @property + def dprx(self) -> DPRX4xx: + """ + Returns DP Sink (RX - receiver) role. + + Returns: + object of DPRX4xx type. + """ + return self.__dprx + + @property + def dptx(self) -> DPTX4xx: + """ + Returns DP Source (TX - transmitter) role. + + Returns: + object of DPTX4xx type. + """ + return self.__dptx + + @property + def pdcrx(self) -> PDC424: + """ + Returns PDC Sink (RX - receiver) role. + + Returns: + object of `PDC424` type. + """ + return self.__pdcrx + + @property + def pdctx(self) -> PDC424: + """ + Returns PDC source (TX - transmitter) role. + + Returns: + object of `PDC424` type. + """ + return self.__pdctx + + @property + def dut_tests(self) -> DUTTests: + """ + Returns DUT Test module. + + Returns: + object of DUTTests type. + """ + return self.__dut_tests + + @property + def name(self) -> str: + """ + Returns name of role. + + Returns: + object of str type. + """ + return list(UCD424.ROLE_DICT.keys())[list(UCD424.ROLE_DICT.values()).index(type(self))] + + ROLE_DICT = { + "USB-C Source and Sink": USBCSourceUSBCSink + } diff --git a/UniTAP/dev/dev_5xx_roles.py b/UniTAP/dev/dev_5xx_roles.py new file mode 100644 index 0000000..fcde995 --- /dev/null +++ b/UniTAP/dev/dev_5xx_roles.py @@ -0,0 +1,258 @@ +from UniTAP.dev.ports import DPRX5xx, DPTX5xx, PDC500 +from .modules import MemoryManager, Capturer, DUTTests +from UniTAP.libs.lib_tsi.tsi_io import DeviceIO, PortProtocol + + +class UCD500: + """ + Class `UCD500` describes of device UCD-500. Device has one possible role: + - 'DisplayPort Source and Sink' `DPSourceDPSink`. + - 'DisplayPort Source and USB-C, DP Alt Mode Sink' `DPSourceUSBCSink`. + - 'DisplayPort Sink and USB-C, DP Alt Mode Source' `USBCSourceDPSink`. + - 'USB-C, DP Alt Mode Source and Sink' `USBCSourceUSBCSink`. + """ + class DPSourceDPSink: + """ + Class `DPSourceDPSink` contains information of available functionality modules for role DP Sink + (RX - receiver) and DP Source (TX - transmitter) roles: + - `DPRX5xx`. + - `DPTX5xx`. + - `DUTTests`. + """ + def __init__(self, dev_io: DeviceIO, memory_manager: MemoryManager, capturer: Capturer): + self.__dprx = DPRX5xx(dev_io.create_port_io(0, PortProtocol.DisplayPort), memory_manager, capturer) + self.__dptx = DPTX5xx(dev_io.create_port_io(1, PortProtocol.DisplayPort), memory_manager, capturer) + self.__dut_tests = DUTTests(dev_io) + + @property + def dprx(self) -> DPRX5xx: + """ + Returns DP Sink (RX - receiver) role. + + Returns: + object of DPRX5xx type. + """ + return self.__dprx + + @property + def dptx(self) -> DPTX5xx: + """ + Returns DP Source (TX - transmitter) role. + + Returns: + object of DPTX5xx type. + """ + return self.__dptx + + @property + def dut_tests(self) -> DUTTests: + """ + Returns DUT Test module. + + Returns: + object of DUTTests type. + """ + return self.__dut_tests + + @property + def name(self) -> str: + """ + Returns name of role. + + Returns: + object of str type. + """ + return list(UCD500.ROLE_DICT.keys())[list(UCD500.ROLE_DICT.values()).index(type(self))] + + class DPSourceUSBCSink: + def __init__(self, dev_io: DeviceIO, memory_manager: MemoryManager, capturer: Capturer): + self.__port_rx = dev_io.create_port_io(0, PortProtocol.DisplayPortThrowUSBC) + self.__dptx = DPTX5xx(dev_io.create_port_io(1, PortProtocol.DisplayPort), memory_manager, capturer) + self.__dprx = DPRX5xx(self.__port_rx, memory_manager, capturer) + self.__pdcrx = PDC500(self.__port_rx) + self.__dut_tests = DUTTests(dev_io) + + @property + def dprx(self) -> DPRX5xx: + """ + Returns DP Sink (RX - receiver) role. + + Returns: + object of DPRX5xx type. + """ + return self.__dprx + + @property + def dptx(self) -> DPTX5xx: + """ + Returns DP Source (TX - transmitter) role. + + Returns: + object of DPTX5xx type. + """ + return self.__dptx + + @property + def pdcrx(self) -> PDC500: + """ + Returns PDC Sink (RX - receiver) role. + + Returns: + object of `PDC500` type. + """ + return self.__pdcrx + + @property + def dut_tests(self) -> DUTTests: + """ + Returns DUT Test module. + + Returns: + object of DUTTests type. + """ + return self.__dut_tests + + @property + def name(self) -> str: + """ + Returns name of role. + + Returns: + object of str type. + """ + return list(UCD500.ROLE_DICT.keys())[list(UCD500.ROLE_DICT.values()).index(type(self))] + + class USBCSourceDPSink: + def __init__(self, dev_io: DeviceIO, memory_manager: MemoryManager, capturer: Capturer): + self.__port_tx = dev_io.create_port_io(1, PortProtocol.DisplayPortThrowUSBC) + self.__dptx = DPTX5xx(self.__port_tx, memory_manager, capturer) + self.__dprx = DPRX5xx(dev_io.create_port_io(0, PortProtocol.DisplayPort), memory_manager, capturer) + self.__pdctx = PDC500(self.__port_tx) + self.__dut_tests = DUTTests(dev_io) + + @property + def dprx(self) -> DPRX5xx: + """ + Returns DP Sink (RX - receiver) role. + + Returns: + object of DPRX5xx type. + """ + return self.__dprx + + @property + def dptx(self) -> DPTX5xx: + """ + Returns DP Source (TX - transmitter) role. + + Returns: + object of DPTX5xx type. + """ + return self.__dptx + + @property + def pdctx(self) -> PDC500: + """ + Returns PDC source (TX - transmitter) role. + + Returns: + object of `PDC500` type. + """ + return self.__pdctx + + @property + def dut_tests(self) -> DUTTests: + """ + Returns DUT Test module. + + Returns: + object of DUTTests type. + """ + return self.__dut_tests + + @property + def name(self) -> str: + """ + Returns name of role. + + Returns: + object of str type. + """ + return list(UCD500.ROLE_DICT.keys())[list(UCD500.ROLE_DICT.values()).index(type(self))] + + class USBCSourceUSBCSink: + def __init__(self, dev_io: DeviceIO, memory_manager: MemoryManager, capturer: Capturer): + self.__port_rx = dev_io.create_port_io(0, PortProtocol.DisplayPortThrowUSBC) + self.__port_tx = dev_io.create_port_io(1, PortProtocol.DisplayPortThrowUSBC) + self.__dprx = DPRX5xx(self.__port_rx, memory_manager, capturer) + self.__dptx = DPTX5xx(self.__port_tx, memory_manager, capturer) + self.__pdcrx = PDC500(self.__port_rx) + self.__pdctx = PDC500(self.__port_tx) + self.__dut_tests = DUTTests(dev_io) + + @property + def dprx(self) -> DPRX5xx: + """ + Returns DP Sink (RX - receiver) role. + + Returns: + object of DPRX5xx type. + """ + return self.__dprx + + @property + def dptx(self) -> DPTX5xx: + """ + Returns DP Source (TX - transmitter) role. + + Returns: + object of DPTX5xx type. + """ + return self.__dptx + + @property + def pdcrx(self) -> PDC500: + """ + Returns PDC Sink (RX - receiver) role. + + Returns: + object of `PDC500` type. + """ + return self.__pdcrx + + @property + def pdctx(self) -> PDC500: + """ + Returns PDC source (TX - transmitter) role. + + Returns: + object of `PDC500` type. + """ + return self.__pdctx + + @property + def dut_tests(self) -> DUTTests: + """ + Returns DUT Test module. + + Returns: + object of DUTTests type. + """ + return self.__dut_tests + + @property + def name(self) -> str: + """ + Returns name of role. + + Returns: + object of str type. + """ + return list(UCD500.ROLE_DICT.keys())[list(UCD500.ROLE_DICT.values()).index(type(self))] + + ROLE_DICT = { + "DisplayPort Source and Sink": DPSourceDPSink, + "DisplayPort Source and USB-C, DP Alt Mode Sink": DPSourceUSBCSink, + "DisplayPort Sink and USB-C, DP Alt Mode Source": USBCSourceDPSink, + "USB-C, DP Alt Mode Source and Sink": USBCSourceUSBCSink + } diff --git a/UniTAP/dev/device.py b/UniTAP/dev/device.py new file mode 100644 index 0000000..feece27 --- /dev/null +++ b/UniTAP/dev/device.py @@ -0,0 +1,154 @@ +import copy +import weakref +from typing import TypeVar, Type, List + +from .dev_5xx_roles import UCD500 +from .dev_4xx_roles import UCD400, UCD422, UCD424 +from .dev_3xx_roles import UCD240, UCD340, UCD323, UCD301 +from .modules import * + +from UniTAP.libs.lib_tsi.tsi_io import DeviceIO +from UniTAP.utils import tsi_logging as logging + +RoleType = TypeVar("RoleType", + UCD500.DPSourceUSBCSink, + UCD500.DPSourceDPSink, + UCD500.USBCSourceUSBCSink, + UCD500.USBCSourceDPSink, + + UCD424.USBCSourceUSBCSink, + UCD422.HDMISourceHDMISink, + UCD400.DPSourceDPSink, + + UCD340.USBCSink, + UCD340.USBCSource, + + UCD323.DPSink, + UCD323.DPSource, + UCD323.HDMISink, + UCD323.HDMISource, + + UCD301.DPSink, + UCD301.HDMISink, + + UCD240.USBCSink, + UCD240.USBCSource) + +MODEL_TO_CLASS = { + "UCD-500": UCD500, + "UCD-424": UCD424, + "UCD-422": UCD422, + "UCD-400": UCD400, + "UCD-340": UCD340, + "UCD-323": UCD323, + "UCD-301": UCD301, + "UCD-240": UCD240 +} + + +class NotExistingRoleSelected(Exception): + """ + Redefinition of base exception. + Define error of 'not existing role selected'. + """ + pass + + +class DeviceRoleIsAlreadySelected(Exception): + """ + Redefinition of base exception. + Define error of 'device role already selected'. + """ + pass + + +class TSIDevice: + """ + Class `TSIDevice` ... Allows: + - Select role of the device `select_role`. + - Get and redefine OPF (Operator feedback dialog) handler `opf_handler`. + """ + + def __init__(self, io: DeviceIO, name: str, serial_number: str, roles: List[RoleType]): + self.__io = io + self.__io_proxy = weakref.proxy(self.__io) + self.__memory_manager = MemoryManager(self.__io_proxy) + self.__capturer = Capturer(self.__io_proxy) + self.__opf_handler = OperatorFeedbackHandler(self.__io_proxy) + self.__terminal = Terminal(self.__io_proxy) + self.__name = name + self.__serial_number = serial_number + self.__role_list = roles + logging.info(f"[UniTAP] TSIDevice.init: {self}") + + def __del__(self): + del self.__io + logging.info(f"[UniTAP] TSIDevice.del: {self}") + + def __str__(self): + return f"{self.__name} [{self.__serial_number}]" + + def __define_internal_handler(self, role, role_type): + if role_type in [UCD500.DPSourceUSBCSink, UCD500.DPSourceDPSink, UCD500.USBCSourceUSBCSink, + UCD500.USBCSourceDPSink, UCD424.USBCSourceUSBCSink, UCD400.DPSourceDPSink]: + self.opf_handler._set_roles(role.dptx, role.dprx) + elif role_type in [UCD422.HDMISourceHDMISink]: + self.opf_handler._set_roles(role.hdtx, role.hdrx) + + def select_role(self, role_type: Type[RoleType]) -> RoleType: + """ + Function allows selecting role of the chosen device. + For example: `UniTAP.dev.UCD500.DPSourceDPSink` - will be opened UCD-500 in role DisplayPort Source and Sink. + + Args: + role_type (RoleType) - one of the possible roles. + + Returns: + object of `RoleType` type. Selected role. + """ + # TODO: Remove this limitation. Caused by TSI lib problem. + if self.__io.get_role() is not None: + raise DeviceRoleIsAlreadySelected(f"Device role is already selected.") + if role_type not in self.__role_list: + raise NotExistingRoleSelected(f"Role: {role_type} is not available on this device.") + self.__io.select_role(self.__role_list.index(role_type)) + self.__io.set_role(role_type(self.__io_proxy, + weakref.proxy(self.__memory_manager), + weakref.proxy(self.__capturer))) + self.__define_internal_handler(self.__io.get_role(), role_type) + + return self.__io.get_role() + + @property + def available_roles(self) -> list: + """ + Returns available list of roles. + + Returns: + object of list type. + """ + return copy.deepcopy(self.__role_list) + + @property + def opf_handler(self): + """ + Returns current OPF handler. + + Returns: + object of handler type. + """ + return self.__opf_handler.handler + + @opf_handler.setter + def opf_handler(self, value): + """ + Set new OPF handler. + + Args: + value - new OPF handler + """ + self.__opf_handler.handler = value + + @property + def _terminal(self) -> Terminal: + return self.__terminal diff --git a/UniTAP/dev/modules/__init__.py b/UniTAP/dev/modules/__init__.py new file mode 100644 index 0000000..b97b1d8 --- /dev/null +++ b/UniTAP/dev/modules/__init__.py @@ -0,0 +1,5 @@ +from .memory_manager import MemoryManager +from .capturer import * +from .opf import * +from .dut_tests import * +from .terminal import * diff --git a/UniTAP/dev/modules/capturer/__init__.py b/UniTAP/dev/modules/capturer/__init__.py new file mode 100644 index 0000000..ca06179 --- /dev/null +++ b/UniTAP/dev/modules/capturer/__init__.py @@ -0,0 +1,2 @@ +from .capture import Capturer, CaptureConfig, CaptureStatus, CaptureError, AudioCaptureStatus,\ + EventCaptureStatus, VideoCaptureStatus diff --git a/UniTAP/dev/modules/capturer/capture.py b/UniTAP/dev/modules/capturer/capture.py new file mode 100644 index 0000000..3a96b14 --- /dev/null +++ b/UniTAP/dev/modules/capturer/capture.py @@ -0,0 +1,550 @@ +import time +from typing import Union + +from UniTAP.libs.lib_tsi.tsi_io import DeviceIO +from UniTAP.libs.lib_tsi.tsi import * +from UniTAP.dev.modules.capturer.statuses import CaptureStatus, AudioCaptureStatus, EventCaptureStatus, \ + VideoCaptureStatus, BulkCaptureStatus +from UniTAP.common import AudioFrameData, VideoFrameDSC, create_from_pps +from UniTAP.dev.ports.modules.capturer.event.event_types import EventData +from threading import Lock +from UniTAP.utils import function_scheduler +from UniTAP.dev.ports.modules.capturer.bulk.private_bulk_types import * +from UniTAP.dev.ports.modules.capturer.bulk.bulk_types import * +from .types import * + + +TIMEOUT = 10 + + +class CaptureConfig: + class Type(IntEnum): + NONE = -1 + LIVE = 0 + BUFFERED = 1 + + def __init__(self): + self.audio = False + self.video = False + self.event = False + self.type = CaptureConfig.Type.NONE + self.frame_count = 0 + + def from_int(self, value: int): + self.video = bool(value & 1 << 3) + self.audio = bool(value & 1 << 4) + self.event = bool(value & 1 << 5) + self.type = CaptureConfig.Type.LIVE if value & (1 << 2) else CaptureConfig.Type.BUFFERED + self.frame_count = value >> 8 + + def to_int(self) -> int: + value = 0 + + if self.video: + value |= (1 << 3) + + if self.audio: + value |= (1 << 4) + + if self.event: + value |= (1 << 5) + + if self.video or self.audio: + if self.type == CaptureConfig.Type.LIVE: + value |= (1 << 2) + else: + value |= (self.frame_count << 8) + + return value + + +class Capturer: + + def __init__(self, port_io: DeviceIO): + self.__io = port_io + self.__config = CaptureConfig() + self.__status = CaptureStatus.Unknown + self.__mutex = Lock() + + @property + def config(self): + return self.__config + + @property + def status(self): + return self.__status + + @status.setter + def status(self, value: CaptureStatus): + self.__status = value + + @property + def device(self): + return self.__io + + @property + def video_capturer_status(self) -> VideoCaptureStatus: + return VideoCaptureStatus(self.__io.get(TSI_VIDCAP_CAPTURE_STATUS_R, c_int)[1]) + + @property + def audio_capturer_status(self) -> AudioCaptureStatus: + return AudioCaptureStatus(self.__io.get(TSI_R_AUDCAP_STATUS, c_int)[1]) + + @property + def event_capturer_status(self) -> EventCaptureStatus: + return EventCaptureStatus(self.__io.get(TSI_EVCAP_CTRL, c_uint32)[1] & 0xFF) + + @property + def bulk_capturer_status(self) -> BulkCaptureStatus: + return BulkCaptureStatus(self.__io.get(TSI_BULK_CAPTURE_STATUS_R, c_uint32)[1] & 0x3) + + def start_capture(self, config: CaptureConfig): + self.__mutex.acquire() + + self.__config.from_int(self.__current_config().to_int() | config.to_int()) + self.__io.set(TSI_CAP_CONFIG, self.__config.to_int()) + + if self.__io.set(TSI_W_CAP_COMMAND, 1) == 0: + self.__status = CaptureStatus.Running + else: + self.__status = CaptureStatus.Unknown + + self.__mutex.release() + + def stop_capture(self, config: CaptureConfig): + self.__mutex.acquire() + + self.__config.from_int(self.__current_config().to_int() & ~config.to_int()) + self.__io.set(TSI_CAP_CONFIG, self.__config.to_int()) + + if self.__io.set(TSI_W_CAP_COMMAND, 2) == 0: + self.__status = CaptureStatus.Stop + else: + self.__status = CaptureStatus.Unknown + + self.__mutex.release() + + def __capture_events(self, event_count=0): + event = EventData() + + if event_count > 0: + event_size = self.__io.get(TSI_R_EVCAP_DATA, None, 0)[0] + if event_size > 0: + event.data = bytearray(self.__io.get(TSI_R_EVCAP_DATA, c_ubyte, event_size)[1]) + + return event + + def get_buffer_capacity(self, stream_number: int = None): + if stream_number is not None: + self.__io.set(TSI_DPRX_STREAM_SELECT, stream_number, c_uint32) + self.__io.set(TSI_DPRX_MSA_COMMAND_W, 2, c_uint32) + + width = self.__io.get(TSI_R_INPUT_WIDTH , c_uint32)[1] + height = self.__io.get(TSI_R_INPUT_HEIGHT , c_uint32)[1] + total_memory_bytes = self.__io.get(TSI_MEMORY_SIZE_R, c_uint64)[1] + bpc = self.__io.get(TSI_INPUT_COLOR_DEPTH_R , c_uint32)[1] + color_format = self.__io.get(TSI_INPUT_COLOR_MODE_R , c_uint32)[1] + + bpp = self._get_bits_per_pixel(bpc, color_format) + line_alignment = 1023 + pixel_size = self._get_pixel_size(color_format, bpp) + line_size = width * pixel_size + line_pitch = self._align(line_size, line_alignment) + one_frame_size_bytes = (height + 1) * line_pitch + + if one_frame_size_bytes == 0: + return 0 + else: + return int(total_memory_bytes / one_frame_size_bytes) + + @staticmethod + def _get_bits_per_pixel(bpc, color_format) -> int: + if color_format in [0, 1, 6]: + return 0 + elif color_format in [2, 4, 9]: + return 3 * bpc + elif color_format == 3: + return 2 * bpc + elif color_format == 5: + return 3 * bpc / 2 + elif color_format == 7: + return bpc + elif color_format == 8: + return bpc + + @staticmethod + def _get_pixel_size(color_format, bpp): + if color_format == 5: + return 4 if bpp >= 18 else 2 + elif color_format in [7, 8]: + return 6 if bpp > 10 else 4 + else: + return 6 if bpp > 32 else 4 + + @staticmethod + def _align(value, alignment): + return (value + alignment) & ~alignment + + def get_available_events_count(self) -> int: + return self.__io.get(TSI_R_EVCAP_COUNT, c_uint32)[1] + + def capture_n_events(self, events_count: int): + if events_count <= 0: + raise ValueError(f"Events count must be more than 0.") + + buffer = [] + + def is_enough_events(): + return self.get_available_events_count() >= events_count + + function_scheduler(is_enough_events, interval=1, timeout=TIMEOUT) + + for i in range(events_count): + buffer.append(self.__capture_events(self.get_available_events_count())) + + return buffer + + def read_all_events(self): + buffer = [] + while self.get_available_events_count() > 0: + buffer.append(self.__capture_events(self.get_available_events_count())) + + return buffer + + def __capture_audio(self, m_sec=1000): + + audio_frame = AudioFrameData() + + audio_frame.channel_count = self.__io.get(TSI_R_AUDCAP_CHANNEL_COUNT, c_int)[1] + audio_frame.sample_size = self.__io.get(TSI_R_AUDCAP_SAMPLE_SIZE, c_int)[1] * 8 + audio_frame.sample_rate = self.__io.get(TSI_R_AUDCAP_SAMPLE_RATE, c_int)[1] + audio_frame.timestamp = self.__io.get(TSI_R_AUDCAP_TIMESTAMP, c_uint64)[1] + audio_frame.samples = self.__io.get(TSI_R_AUDCAP_SAMPLE_COUNT, c_int)[1] + audio_frame.frame_counter = self.__io.get(TSI_R_AUDCAP_FRAME_COUNTER, c_int)[1] + audio_frame.sample_format = self.__io.get(TSI_R_AUDCAP_SAMPLE_FORMAT, c_int)[1] + + min_buff_size = self.__io.get(TSI_R_AUDCAP_MIN_BUFFER_SIZE, c_uint32)[1] + audio_frame.data = self.__io.get(TSI_R_AUDCAP_SAMPLE_DATA, c_uint8, min_buff_size)[1] + + m_sec -= 1000 * len(audio_frame.data) / 2 / audio_frame.channel_count / audio_frame.sample_rate + + return audio_frame, m_sec + + def capture_audio_by_n_frames(self, frames_count: int, timeout: int = None): + if frames_count <= 0: + raise ValueError(f"Frames count must be more than 0.") + + buffer = [] + time_break = False + + timeout = timeout if timeout is not None else TIMEOUT + + while not time_break and len(buffer) < frames_count: + captured = 0 + start_time = time.time() + while captured < 10: + status = self.audio_capturer_status + current_time = time.time() + if current_time - start_time > timeout: + time_break = True + break + if status == AudioCaptureStatus.Stop: + continue + + audio_frame, m_sec = self.__capture_audio() + if len(audio_frame.data) > 0: + captured += 1 + buffer.append(audio_frame) + + return buffer + + def capture_audio_by_m_sec(self, m_sec: int): + if m_sec <= 0: + raise ValueError(f"Seconds count must be more than 0.") + + buffer = [] + time_break = False + + while m_sec > 0 and not time_break: + captured = 0 + start_time = time.time() + while captured < 10 and m_sec > 0: + status = self.audio_capturer_status + current_time = time.time() + if current_time - start_time > TIMEOUT: + time_break = True + break + if status == AudioCaptureStatus.Stop: + continue + + audio_frame, m_sec = self.__capture_audio(m_sec=m_sec) + if len(audio_frame.data) > 0: + captured += 1 + buffer.append(audio_frame) + + return buffer + + def get_available_video_frame_count(self): + return self.__io.get(TSI_VIDCAP_AVAILABLE_FRAME_COUNT, c_int)[1] + + def __check_available_video(self, timeout) -> bool: + def is_video_available(capturer): + return capturer.video_capturer_status == VideoCaptureStatus.LiveModeActive + + return function_scheduler(is_video_available, self, interval=1, timeout=timeout) + + def __check_available_buffered_video(self, timeout) -> bool: + def is_video_available(capturer): + return capturer.video_capturer_status == VideoCaptureStatus.Transferring + + return function_scheduler(is_video_available, self, interval=1, timeout=timeout) + + def __check_available_bulk_data(self, timeout) -> bool: + def is_bulk_available(capturer): + return capturer.bulk_capturer_status == BulkCaptureStatus.Transferring + + return function_scheduler(is_bulk_available, self, interval=1, timeout=timeout) + + def __capture_video(self, timeout=TIMEOUT, + capture_type=CaptureConfig.Type.LIVE) -> Union[VideoFrame, VideoFrameDSC]: + if timeout <= 0: + raise ValueError(f"Timeout must be more than 0.") + + if capture_type == CaptureConfig.Type.BUFFERED: + if not self.__check_available_buffered_video(timeout): + raise BufferedCaptureError( + f"Cannot get frames from buffer. " + f"Current buffered capture status is {self.video_capturer_status.name}" + ) + else: + if not self.__check_available_video(timeout): + raise CaptureError( + f"Cannot start to capture video. Current video capture status {self.video_capturer_status.name}") + + try: + result = self.__io.set(TSI_VIDCAP_CAPTURE_NEXT_W, 0) + if result == TSI_ERROR_DATA_PROTECTION_ENABLED: + raise CaptureError("Video data is HDCP protected. Capturing is not available.") + except AssertionError as e: + raise CaptureError(f"Error: {e}") + + try: + min_buffer_size = self.__io.get(TSI_R_VIDCAP_MIN_BUFFER_SIZE, c_int)[1] + if min_buffer_size <= 0: + raise ValueError("Minimum buffer size must be more than 0") + except AssertionError as e: + raise CaptureError(f"Error: {e}") + + try: + frame_data = bytearray(self.__io.get(TSI_R_VIDCAP_FRAME_DATA, c_uint8, min_buffer_size)[1]) + if len(frame_data) <= 0: + raise ValueError("Minimum length of captured data must be more than 0") + except AssertionError as e: + raise CaptureError(f"Error: {e}") + + frame_attributes = self.__io.get(TSI_VIDCAP_FRAME_HEADER_R, VideoFrameHeader)[1] + + if frame_attributes.is_dsc() and len(frame_data) > 128: + vf = VideoFrameDSC() + vf.compression_info = create_from_pps(frame_data[:128]) + else: + vf = VideoFrame() + + vf.width = frame_attributes.width + vf.height = frame_attributes.height + vf.color_info.bpc = frame_attributes.bpc + vf.color_info.dynamic_range = frame_attributes.dynamic_range + vf.color_info.color_format = frame_attributes.color_format + vf.color_info.colorimetry = frame_attributes.colorimetry + vf.data_info.component_order = DataInfo.ComponentOrder.CO_UCDRX + vf.data_info.alignment = DataInfo.Alignment.A_MSB + vf.data_info.packing = DataInfo.Packing.P_PACKED + vf.timestamp = frame_attributes.timestamp + vf.data = frame_data + + return vf + + def capture_video_by_n_frames(self, frames_count: int, capture_type: CaptureConfig.Type = CaptureConfig.Type.LIVE): + if frames_count <= 0: + raise ValueError(f"Frames count must be more than 0.") + + buffer = [] + + if capture_type == CaptureConfig.Type.BUFFERED: + timeout = max(10, round(0.006 * frames_count)) + try: + for i in range(frames_count): + buffer.append(self.__capture_video(timeout=timeout, capture_type=capture_type)) + except BufferedCaptureError as e: + return buffer + else: + for i in range(frames_count): + buffer.append(self.__capture_video()) + + return buffer + + def capture_video_by_n_sec(self, sec: int): + if sec <= 0: + raise ValueError(f"Seconds count must be more than 0.") + + buffer = [] + + time_start = time.time() + while time.time() - time_start < sec: + buffer.append(self.__capture_video()) + + return buffer + + def set_video_stream_number(self, number: int): + self.__mutex.acquire() + self.__io.set(TSI_DPRX_STREAM_SELECT, number, c_uint32) + self.__mutex.release() + + def __current_config(self) -> CaptureConfig: + config = CaptureConfig() + config.from_int(self.__io.get(TSI_CAP_CONFIG, c_uint)[1]) + return config + + # Capture CRC + def capture_crc(self, crc_frame_count: int = 1) -> List[tuple[int, int, int]]: + if crc_frame_count <= 0: + raise ValueError(f"Incorrect crc frame count: {crc_frame_count}") + + crc_values = self.__io.get(TSI_VIDCAP_SIGNAL_CRC_R, CrcStruct, crc_frame_count)[1] + crc_list = [] + if crc_frame_count == 1: + crc_list = [(crc_values.r, crc_values.g, crc_values.b)] + else: + [crc_list.append((crc.r, crc.g, crc.b)) for crc in crc_values] + return crc_list + + # Bulk Capturer + def read_bulk_capture_caps(self) -> CaptureCaps: + return self.__io.get(TSI_BULK_CAPTURE_CAPS_R, CaptureCaps)[1] + + def read_bulk_trigger_caps(self) -> int: + return self.__io.get(TSI_BULK_TRIGGER_CAPS_R, c_uint32)[1] + + def write_bulk_trigger_settings(self, trigger_mask: int, trigger_config: list, trigger_config_ext: list): + self.__io.set(TSI_BULK_TRIGGER_MASK_W, trigger_mask, c_uint32) + self.__io.set(TSI_BULK_TRIGGER_CONFIGURATION_W, trigger_config, c_uint32, data_count=len(trigger_config)) + self.__io.set(TSI_BULK_TRIGGER_CONFIGURATION_EXT_W, trigger_config_ext, c_uint32, + data_count=len(trigger_config_ext)) + + def write_bulk_size(self, size: int): + data = SBlock() + data.BLOCK = 0 + data.OFFSET = 0 + data.SIZE = size + self.__io.set(TSI_BULK_CAPTURE_BLOCK, data, SBlock) + + def write_encoding_type(self, value: EncodingTypeEnum): + self.__io.set(TSI_BULK_CAPTURE_TYPE, value.value, c_uint32) + + def read_encoding_type(self) -> EncodingTypeEnum: + return EncodingTypeEnum(self.__io.get(TSI_BULK_CAPTURE_TYPE, c_uint32)[1]) + + def write_lane_count(self, value: LaneCountEnum): + self.__io.set(TSI_BULK_CAPTURE_LANE_COUNT, value.value, c_uint32) + + def read_lane_count(self) -> LaneCountEnum: + return LaneCountEnum(self.__io.get(TSI_BULK_CAPTURE_LANE_COUNT, c_uint32)[1]) + + def write_bulk_gpio(self, gpio: bool): + self.__io.set(TSI_BULK_CAPTURE_GPIO_W, TSI_BULK_CAPTURE_GPIO_5BIT if gpio else TSI_BULK_CAPTURE_GPIO_OFF, + c_uint32) + + def write_bulk_trigger_position(self, position: int): + self.__io.set(TSI_BULK_TRIGGER_POS, position) + + def start_bulk_capture(self): + + self.__mutex.acquire() + + self.__io.set(TSI_EVCAP_CTRL, 1) + self.__io.set(TSI_BULK_CAPTURE_CONTROL_W, TSI_BULK_CAPTURE_START) + + self.__mutex.release() + + def stop_bulk_capture(self): + self.__mutex.acquire() + + self.__io.set(TSI_EVCAP_CTRL, 0) + self.__io.set(TSI_BULK_CAPTURE_CONTROL_W, TSI_BULK_CAPTURE_STOP) + + self.__mutex.release() + + def start_event_capture(self): + self.__mutex.acquire() + + self.__io.set(TSI_EVCAP_CTRL, 1) + self.__io.set(TSI_EVCAP_EVENT_SRC_EN, UCD_ALL_EVENTS) + + self.__mutex.release() + + def stop_event_capture(self): + self.__mutex.acquire() + + self.__io.set(TSI_EVCAP_CTRL, 0) + + self.__mutex.release() + + def clear_bulk_buffer(self): + max_time_waiting = 5 + value = -1 + time_waited = time.time() + + while value != TSI_BULK_STATUS_IDLE and time.time() - time_waited < max_time_waiting: + self.__io.set(TSI_BULK_CAPTURE_CLEAR_W, 0) + value = self.__io.get(TSI_BULK_CAPTURE_STATUS_R)[1] + + def bulk_capture(self, all_size: int, trigger_enabled: Optional[TriggerVarType]) -> list: + buffer = [] + iterations = int(all_size / (1024 * 1024)) + prev_status = 0 + last_bulk_capture_time = time.time() + for i in range(iterations): + event_count = self.__io.get(TSI_EVCAP_COUNT_R, c_uint32)[1] + if event_count > 0: + event_number = 0 + prev_timestamp = 0 + events_captured = 0 + while event_count > 0 and events_captured < 500: + event_size = self.__io.get(TSI_R_EVCAP_DATA, None, 0)[0] + if event_size > 0: + cap_data = CapturedData() + cap_data.data = bytearray(self.__io.get(TSI_R_EVCAP_DATA, c_ubyte, event_size)[1]) + cap_data.data = cap_data.data[3:] + cap_data.type = CapturedDataType.Event + cap_data.timestamp = int.from_bytes(bytes=cap_data.data[:8], byteorder='big') + if cap_data.timestamp == prev_timestamp: + event_number += 1 + else: + event_number = 0 + buffer.append(cap_data) + prev_timestamp = cap_data.timestamp + events_captured += 1 + event_count -= 1 + + bulk_status = self.__io.get(TSI_BULK_CAPTURE_STATUS_R, c_int32)[1] + if bulk_status == TSI_BULK_STATUS_IDLE and prev_status == TSI_BULK_STATUS_TRANSFERRING: + return buffer + prev_status = bulk_status + + now = time.time() + if trigger_enabled is not None and now - last_bulk_capture_time >= TIMEOUT: + return buffer + + if not self.__check_available_bulk_data(TIMEOUT): + return buffer + + self.__io.set(TSI_EVCAP_CTRL, 0) + cap_data = CapturedData() + cap_data.type = CapturedDataType.Bulk + result, data, _ = self.__io.get(TSI_BULK_CAPTURE_DATA_R, c_ubyte, 1024 * 1024) + cap_data.data = bytearray(data) + if result >= TSI_SUCCESS and len(cap_data.data) > 0: + buffer.append(cap_data) + last_bulk_capture_time = time.time() + + return buffer diff --git a/UniTAP/dev/modules/capturer/result_object.py b/UniTAP/dev/modules/capturer/result_object.py new file mode 100644 index 0000000..34e67e7 --- /dev/null +++ b/UniTAP/dev/modules/capturer/result_object.py @@ -0,0 +1,108 @@ +from UniTAP.common.timestamp import Timestamp + + +class ResultObject: + """ + The base class of all capture results. + Contains information about `start_capture_time`, `end_capture_time`, `timestamp` and `buffer` with captured data. + """ + def __init__(self): + self.__start_capture_time = 0 + self.__end_capture_time = 0 + self.__timestamp = Timestamp(0) + self.__buffer = [] + + @property + def start_capture_time(self) -> int: + """ + Return start capture time. + + Returns: + object of `int` type + """ + return self.__start_capture_time + + @property + def end_capture_time(self) -> int: + """ + Return end capture time. + + Returns: + object of `int` type + """ + return self.__end_capture_time + + @property + def timestamp(self) -> Timestamp: + """ + Return timestamp. + + Returns: + object of `Timestamp` type + """ + return self.__timestamp + + @property + def buffer(self) -> list: + """ + Return buffer with captured data. + + Returns: + object of list type + """ + return self.__buffer + + @buffer.setter + def buffer(self, value): + """ + Set data to buffer + + Args: + value - any type of object + """ + self.__buffer.append(value) + + @start_capture_time.setter + def start_capture_time(self, start_capture_time: int): + """ + Set start capture time. + + Args: + start_capture_time (int) - must be more than 0. + """ + if start_capture_time <= 0: + raise ValueError(f"Start capture time cannot be less than 0.") + self.__start_capture_time = start_capture_time + + @end_capture_time.setter + def end_capture_time(self, end_capture_time: int): + """ + Set end capture time. + + Args: + end_capture_time (int) - must be more than 0. + """ + if end_capture_time <= 0: + raise ValueError(f"End capture time cannot be less than 0.") + self.__end_capture_time = end_capture_time + + @timestamp.setter + def timestamp(self, timestamp: int): + """ + Set timestamp. + + Args: + timestamp (int) - must be more than 0. + """ + if timestamp <= 0: + raise ValueError(f"Timestamp cannot be less than 0.") + self.__timestamp.value = timestamp + + def clear(self): + """ + Clear all data. + """ + self.__start_capture_time = 0 + self.__end_capture_time = 0 + self.__timestamp = Timestamp(0) + self.__buffer = [] diff --git a/UniTAP/dev/modules/capturer/statuses.py b/UniTAP/dev/modules/capturer/statuses.py new file mode 100644 index 0000000..4d129b4 --- /dev/null +++ b/UniTAP/dev/modules/capturer/statuses.py @@ -0,0 +1,100 @@ +from enum import IntEnum + + +class CaptureStatus(IntEnum): + Unknown = -1 + Stop = 0 + Running = 1 + + def __str__(self): + if self.value == CaptureStatus.Unknown: + return f"Capture status: {self.value}" + elif self.value == CaptureStatus.Stop: + return f"Capture status: is not working" + elif self.value == CaptureStatus.Running: + return f"Capture status: working" + else: + return f"Capture status: Unknown state" + + +class BulkCaptureStatus(IntEnum): + Unknown = -1 + Idle = 0 + Waiting = 1 + Capturing = 2 + Transferring = 3 + + def __str__(self): + if self.value == BulkCaptureStatus.Unknown: + return f"Bulk capture status: {self.value}" + elif self.value == BulkCaptureStatus.Idle: + return f"Bulk capture status: Doing nothing" + elif self.value == BulkCaptureStatus.Waiting: + return f"Bulk capture status: Waiting" + elif self.value == BulkCaptureStatus.Capturing: + return f"Bulk capture status: Capturing in progress" + elif self.value == BulkCaptureStatus.Transferring: + return f"Bulk capture status: Transferring" + else: + return f"Bulk capture status: Unknown state" + + +class AudioCaptureStatus(IntEnum): + Unknown = -1 + Stop = 0 + Running = 1 + + def __str__(self): + if self.value == AudioCaptureStatus.Unknown: + return f"Audio capture status: {self.value}" + elif self.value == AudioCaptureStatus.Stop: + return f"Audio capture status: is not working" + elif self.value == AudioCaptureStatus.Running: + return f"Audio capture status: working" + else: + return f"Video capture status: Unknown state" + + +class VideoCaptureStatus(IntEnum): + Unknown = -1 + Idle = 0 # Doing nothing + Capturing = 1 # Capturing in progress + Transferring = 2 # Transferring in progress + LiveModeActive = 3 # Live mode active + Malfunction = 4 # Malfunction (requires restart) + Done = 7 # Capturing and transferring done + + def __str__(self): + if self.value == VideoCaptureStatus.Unknown: + return f"Video capture status: {self.value}" + elif self.value == VideoCaptureStatus.Idle: + return f"Video capture status: Doing nothing" + elif self.value == VideoCaptureStatus.Capturing: + return f"Video capture status: Capturing in progress" + elif self.value == VideoCaptureStatus.Transferring: + return f"Video capture status: Transferring in progress" + elif self.value == VideoCaptureStatus.LiveModeActive: + return f"Video capture status: Live mode active" + elif self.value == VideoCaptureStatus.Malfunction: + return f"Video capture status: Malfunction (requires restart)" + elif self.value == VideoCaptureStatus.Done: + return f"Video capture status: Capturing and transferring done" + else: + return f"Video capture status: Unknown state" + + +class EventCaptureStatus(IntEnum): + Unknown = -1 + Stop = 0 + Running = 1 + Invalid = 0xFF + + def __str__(self): + if self.value == EventCaptureStatus.Unknown: + return f"Event capture status: {self.value}" + elif self.value == EventCaptureStatus.Stop: + return f"Event capture status: is not working" + elif self.value == EventCaptureStatus.Running: + return f"Event capture status: working" + else: + return f"Video capture status: Unknown state" diff --git a/UniTAP/dev/modules/capturer/types.py b/UniTAP/dev/modules/capturer/types.py new file mode 100644 index 0000000..12c8b83 --- /dev/null +++ b/UniTAP/dev/modules/capturer/types.py @@ -0,0 +1,180 @@ +from UniTAP.common.timestamp import Timestamp +from ctypes import c_uint8, c_ubyte, c_uint16, c_uint32, c_uint64, Structure +from enum import IntEnum +from UniTAP.common import ColorInfo, DataInfo, VideoFrame + + +class CapturedDataType(IntEnum): + Unknown = 0 + Video = 1 + Audio = 2 + Event = 3 + Bulk = 4 + + +class CapturedData: + + def __init__(self): + self.frame_number = 0 + self.timestamp = 0 + self.width = 0 + self.height = 0 + self.dataFormat = 0 + self.colorimetry = 0 + self.colorMode = 0 + self.bpc = 0 + self.type = 0 + self.data = 0 + + +class CrcStruct(Structure): + _fields_ = [ + ("r", c_uint16), + ("g", c_uint16), + ("b", c_uint16), + ] + + +class VideoFrameHeader(Structure): + __BPC_PA_TO_INT = { + 0: 6, + 1: 8, + 2: 10, + 3: 12, + 4: 16, + 5: 7, + 6: 14 + } + + __PA_PACKING_TO_VF_PACKING = { + 0: DataInfo.Packing.P_PLANAR, + 1: DataInfo.Packing.P_PLANAR + } + + __PA_CF_TO_CI_CF = { + 0: ColorInfo.ColorFormat.CF_RGB, + 1: ColorInfo.ColorFormat.CF_YCbCr_422, + 2: ColorInfo.ColorFormat.CF_YCbCr_444, + 3: ColorInfo.ColorFormat.CF_YCbCr_420, + 4: ColorInfo.ColorFormat.CF_Y_ONLY, + 5: ColorInfo.ColorFormat.CF_RAW, + 6: ColorInfo.ColorFormat.CF_DSC, + 7: ColorInfo.ColorFormat.CF_IDO_DEFINED + } + __PA_CR_TO_CI_CR = { + 0: ColorInfo.Colorimetry.CM_NONE, + 1: ColorInfo.Colorimetry.CM_SMPTE_170M, + 2: ColorInfo.Colorimetry.CM_ITUR_BT709, + 3: ColorInfo.Colorimetry.CM_NONE + } + + __PA_EXT_CR_TO_CI_CR = { + 0: ColorInfo.Colorimetry.CM_xvYCC601, + 1: ColorInfo.Colorimetry.CM_xvYCC709, + 2: ColorInfo.Colorimetry.CM_sYCC601, + 3: ColorInfo.Colorimetry.CM_AdobeYCC601, + 4: ColorInfo.Colorimetry.CM_AdobeRGB, + 5: ColorInfo.Colorimetry.CM_ITUR_BT2020_YcCbcCrc, + 6: ColorInfo.Colorimetry.CM_ITUR_BT2020_RGB, + 7: ColorInfo.Colorimetry.CM_ITUR_BT601 + } + + class PixelAttributes(Structure): + _fields_ = [ + ("component_format", c_uint32, 4), + ("", c_uint32, 1), + ("bpc", c_uint32, 3), + ("video_mode", c_uint32, 1), + ("stereo_mode", c_uint32, 1), + ("field_id", c_uint32, 1), + ("stereo_id", c_uint32, 1), + ("blanked", c_uint32, 1), + ("encrypted", c_uint32, 1), + ("pixel_packing_format", c_uint32, 2), + ("color_format", c_uint32, 3), + ("dynamic_range", c_uint32, 1), + ("colorimetry", c_uint32, 2), + ("ext_colorimetry", c_uint32, 3), + ("", c_uint32, 1), + ("dsc_compressed", c_uint32, 1), + ("ext_color_format", c_uint32, 2), + ("video_footer", c_uint32, 1), + ("video_packet_crc", c_uint32, 1), + ] + + _fields_ = [ + ('sync', c_uint32), + ('number', c_uint32), + ('', c_uint32), + ('size_words', c_uint32), + ('frame_timestamp', c_uint64, 62), + ('time_unit', c_uint64, 2), + ('attributes', PixelAttributes), + ("f_width", c_uint32, 16), + ("f_height", c_uint32, 16) + ] + + @property + def bpc(self) -> int: + return self.__BPC_PA_TO_INT.get(self.attributes.bpc, 0) + + @property + def dynamic_range(self) -> ColorInfo.DynamicRange: + if self.attributes.dynamic_range: + return ColorInfo.DynamicRange.DR_CTA + else: + return ColorInfo.DynamicRange.DR_VESA + + @property + def color_format(self) -> ColorInfo.ColorFormat: + return self.__PA_CF_TO_CI_CF.get(self.attributes.color_format, + ColorInfo.ColorFormat.CF_UNKNOWN) + + @property + def colorimetry(self) -> ColorInfo.Colorimetry: + if self.attributes.colorimetry == 3: + return self.__PA_EXT_CR_TO_CI_CR.get(self.attributes.ext_colorimetry, + ColorInfo.Colorimetry.CM_NONE) + else: + return self.__PA_CR_TO_CI_CR.get(self.attributes.colorimetry, + ColorInfo.Colorimetry.CM_NONE) + + @property + def timestamp(self) -> Timestamp: + value = self.frame_timestamp + value &= 0x7FFFFFFFFFFFFFFF + value *= 100 if (self.time_unit & 0x1) else 1000 + return Timestamp(value) + + @property + def packing(self) -> DataInfo.Packing: + return self.__PA_PACKING_TO_VF_PACKING.get(self.attributes.pixel_packing_format, + DataInfo.Packing.P_UNKNOWN) + + def is_dsc(self) -> bool: + return self.attributes.color_format == 6 + + @property + def width(self): + if self.color_format == ColorInfo.ColorFormat.CF_YCbCr_420: + return self.f_width << 1 + else: + return self.f_width + + @property + def height(self): + return self.f_height + + +class CaptureError(Exception): + + def __init__(self, message: str): + self.__message = message + super().__init__(self.__message) + + +class BufferedCaptureError(Exception): + + def __init__(self, message: str): + self.__message = message + super().__init__(self.__message) \ No newline at end of file diff --git a/UniTAP/dev/modules/capturer/utils.py b/UniTAP/dev/modules/capturer/utils.py new file mode 100644 index 0000000..77e46f0 --- /dev/null +++ b/UniTAP/dev/modules/capturer/utils.py @@ -0,0 +1 @@ +from UniTAP.libs.lib_uicl.uicl_utils import * diff --git a/UniTAP/dev/modules/dut_tests/__init__.py b/UniTAP/dev/modules/dut_tests/__init__.py new file mode 100644 index 0000000..0883ad8 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/__init__.py @@ -0,0 +1,4 @@ +from .dut_tests import DUTTests, TestGroupId, SubTestResultObject, TestResultObject, TestResult, \ + PackedTimings1Lane, PackedTimings2Lane, PackedTimings4Lane, EventIndication +from .dut_default_params import * +from .report import * diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x01]src_dut_crc.json b/UniTAP/dev/modules/dut_tests/cfg/[0x01]src_dut_crc.json new file mode 100644 index 0000000..78ebca5 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x01]src_dut_crc.json @@ -0,0 +1,195 @@ +{ + "descriptions": [ + { + "configId": "0x10300", + "defaultValue": "10000", + "description": "Defines timeout for all CRC based video tests, in milliseconds. Default setting is 10000ms.\n", + "flag": 3, + "id": "TSI_CRC_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x10301", + "defaultValue": "200", + "description": "Total number of frames to be tested. After calculation of CRC for total number of frames test will be completed. If total number is equal to 0, then test will be executing up to test timeout. Default setting is 200.\n", + "flag": 3, + "id": "TSI_CRC_FRAMES_TO_TEST", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test duration, in frames", + "type": 4 + }, + { + "configId": "0x10302", + "defaultValue": "20", + "description": "Number of reference frames. Default setting is 20.\n", + "flag": 3, + "id": "TSI_CRC_REF_FRAME_COUNT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Reference frames", + "type": 4 + }, + { + "configId": "0x10303", + "defaultValue": "20", + "description": "Number of bad frames allowed in single CRC tests. Default setting is 20\n", + "flag": 3, + "id": "TSI_CRC_LIM_FRAME_MISMATCHES", + "maxValue": 2147483647, + "minValue": 0, + "name": "Allowed mismatches, in frames", + "type": 4 + }, + { + "configId": "0x10304", + "defaultValue": "1920", + "description": "Defined the expected video width, in pixels. If the video being received does not match this setting, the test will fail. Default setting is 1920.\n", + "flag": 3, + "id": "TSI_CRC_REF_WIDTH", + "maxValue": 2147483647, + "minValue": 0, + "name": "Expected video width, in pixels", + "type": 4 + }, + { + "configId": "0x10305", + "defaultValue": "1080", + "description": "Defines the expected video height, in pixels. If the video being received does not match this setting, the test will fail. Default setting is 1080.\n", + "flag": 3, + "id": "TSI_CRC_REF_HEIGHT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Expected video weight, in pixels", + "type": 4 + }, + { + "configId": "0x10306", + "defaultValue": "24", + "description": "Defines the color depth as bits per pixel. If the input video color depth does not match this setting, the test will fail. Default setting is 24.\n", + "enumerationVariants": "12\n15\n16\n18\n20\n21\n24\n30\n32\n36\n48", + "flag": 3, + "id": "TSI_CRC_REF_COLORDEPTH", + "maxValue": 2147480000, + "minValue": 0, + "name": "Expected color depth, as bits per pixel", + "type": 4 + }, + { + "configId": "0x10307", + "defaultValue": "0", + "description": "Defines the required frame rate for CRC based tests, in millihertz. Setting of zero (0) disables the frame-rate requirement. Default setting is 0.\n", + "flag": 3, + "id": "TSI_CRC_REQUIRED_FRAME_RATE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Expected frame rate, in millihertz (mHz)", + "type": 4 + }, + { + "configId": "0x10308", + "defaultValue": "0", + "description": "Defines the maximum allowed deviation of input frame-rate from the required frame rate (TSI_CRC_REQUIRED_FRAME_RATE), in millihertz. When this setting is non-zero, it defines the range of allowed frame rate as requirements ± tolerance. If the frame-rate requirement is set to zero, this setting has no effect. Default setting is 0 mHz.\n", + "flag": 3, + "id": "TSI_CRC_FRAME_RATE_TOLERANCE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Frame rate tolerance, in millihertz (mHz)", + "type": 4 + }, + { + "configId": "0x10309", + "defaultValue": [46750, 45886, 6835], + "description": "Contains CRC reference values. Each CRC set consists of 3 16-bit words; One word for each color channel. Red / Cr color channel CRC is at the lowest address (first word), followed by Green / Y channel (second word) and then Blue / Cb channel (third word). Maximum number of CRC value sets is 65535. Default CRC set is empty (=no default value).", + "flag": 3, + "id": "TSI_CRC_REFERENCE_CRC_VALUES", + "name": "Provided CRC value sets", + "type": 6 + }, + { + "configId": "0x1030c", + "defaultValue": "1", + "description": "Defines the number of iterations the defined CRC sequence must be found in order to pass the test. Default is 1.\n", + "flag": 3, + "id": "TSI_CRC_MOTION_TEST_ITERATIONS", + "maxValue": 2147483647, + "minValue": 0, + "name": "Motion test iterations (# loops)", + "type": 4 + }, + { + "configId": "0x1030d", + "defaultValue": "0", + "description": "Color format. Default setting is 0.\n", + "flag": 3, + "id": "TSI_CRC_COLOR_FORMAT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Color format", + "type": 4 + }, + { + "configId": "0x10317", + "defaultValue": "0", + "description": "Data transfer timeout in milliseconds. Default setting is 0.\n", + "flag": 3, + "id": "TSI_CRC_DATA_TRANSFER_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Data transfer timeout.", + "type": 4 + }, + { + "configId": "0x10320", + "defaultValue": "", + "description": "Contains the full path to the folder where failed frames are to be saved without trailing backslash (‘\\’). No default. Failed frame file-name will be “Failed_<#>.ppm”, where <#> is replaced with an auto-incremented number.", + "flag": 3, + "id": "TSI_CRC_FAILED_FRAME_TARGET_FOLDER", + "maxLength": 10000, + "name": "Location where the failed frames are to be saved", + "type": 11 + }, + { + "configId": "0x10322", + "defaultValue": "0", + "description": "Defines the number of failed frames to be exported from the video test. Default setting is 0. If the setting is 0, no frames are exported.", + "flag": 3, + "id": "TSI_CRC_MAX_EXPORT_FAILED", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum number of exported frames", + "type": 4 + }, + { + "configId": "0x10323", + "defaultValue": "0", + "description": "Export format", + "flag": 3, + "enumerationVariants": "0 # Binary file\n1 # PPM image\n2 # BMP image", + "maxValue": 2147483647, + "minValue": 0, + "id": "TSI_CRC_EXPORT_FORMAT", + "maxLength": 10000, + "name": "Export format", + "type": 4 + }, + { + "configId": "0x10321", + "defaultValue": "0", + "description": "Align 12", + "flag": 3, + "id": "TSI_CRC_ALIGN_12", + "mask": 1, + "name": "Align 12", + "type": 3 + } + ], + "id": "0x01", + "name": "CRC Video Tests", + "old_names": [ + "CRC based Video Test Set" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x02]src_dut_audio.json b/UniTAP/dev/modules/dut_tests/cfg/[0x02]src_dut_audio.json new file mode 100644 index 0000000..3d668f8 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x02]src_dut_audio.json @@ -0,0 +1,86 @@ +{ + "descriptions": [ + { + "configId": "0x2020", + "defaultValue": "44100", + "description": "Sample rate that should be present when running the test, in Hz. Default setting is 44100 Hz. If the audio stream sample rate does not match, the test will result fail.", + "flag": 3, + "id": "TSI_EXPECTED_SAMPLE_RATE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Expected sampling rate of audio signal", + "type": 4 + }, + { + "configId": "0x2021", + "defaultValue": "1000", + "description": "Expected audible signal frequency that should be present when running the test, in Hz. Default setting is 1000 Hz.", + "flag": 3, + "id": "TSI_EXPECTED_AUDIO_FREQUENCY", + "maxValue": 2147483647, + "minValue": 0, + "name": "Expected audible (sine) frequency as Hz", + "type": 4 + }, + { + "configId": "0x2022", + "defaultValue": "1", + "description": "Maximum allowed frequency deviation for the audible signal from the reference frequency, in Hz. Default setting is 1 Hz.", + "flag": 3, + "id": "TSI_AUDIO_FREQUENCY_TOLERANCE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Allowed deviation from expected frequency as Hz", + "type": 4 + }, + { + "configId": "0x2023", + "defaultValue": "5", + "description": "This value defines the accepted RDV range by adding/subtracting it from the calculated base RDV when performing glitch detection. Lower values mean more sensitive to glitches – please note that setting this value too low will cause even perfectly good signal to fail the test. Valid range for this setting is 0 to 32767.0; The default setting is 5.0 (327680 scaled). Important: FIXED POINT ENCODING. When setting this value parameter, the value being set must be multiplied by 65536 and set as a 32-bit integer. When reading the value, the received value must be divided by 65536 and shown as a floating point quantity.", + "flag": 3, + "id": "TSI_AUDIO_GLITCH_DETECT_TRESHOLD", + "maxValue": 100, + "minValue": 0, + "name": "The percentage deviation from the ideal sine", + "type": 4 + }, + { + "configId": "0x2024", + "defaultValue": "0", + "description": "Defines how many glitches are allowed before the audio test is considered failed. Default setting is 0. Important: Due to implementation specific characteristics, a single (but very audible) glitch is probably detected multiple times. The number of times a glitch is detected depends greatly on the severity of the glitch, and it's location respective to the sine waveform. Because of this, setting a non-zero but very low value may not make sense.", + "flag": 3, + "id": "TSI_AUDIO_GLITCHES_ALLOWED", + "maxValue": 2147483647, + "minValue": 0, + "name": "Number of audio glitches allowed per test", + "type": 4 + }, + { + "configId": "0x2025", + "defaultValue": "0", + "description": "Maximum number of frames failed frames saved per test run. Default setting is 0. If the setting is “0”, no frames are saved.", + "flag": 3, + "enumerationVariants": "0 # Save none\n1 # Save failed\n2 # Save all", + "id": "TSI_AUDIO_TEST_SAVE_CONDITIONS", + "maxValue": 2147483647, + "minValue": 0, + "name": "Tested audio save conditions", + "type": 4 + }, + { + "configId": "0x2026", + "defaultValue": "", + "description": "Contains the full path to the folder where failed frames are to be saved without trailing backslash (‘\\’). No default. Failed frame file-name will be “Failed_<#>.ppm”, where <#> is replaced with an auto-incremented number.", + "flag": 3, + "id": "TSI_AUDIO_TEST_STORAGE_FOLDER", + "maxLength": 10000, + "name": "Location where the captured audio is to be saved", + "type": 11 + } + ], + "id": "0x02", + "name": "Audio Test", + "old_names": [ + "Validate audio signal frequency and glitch-free audio reproduction" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x03]src_dut_dp_8b10b_ll_cts.json b/UniTAP/dev/modules/dut_tests/cfg/[0x03]src_dut_dp_8b10b_ll_cts.json new file mode 100644 index 0000000..6717eb2 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x03]src_dut_dp_8b10b_ll_cts.json @@ -0,0 +1,2041 @@ +{ + "descriptions": [ + { + "configId": "0x10e0000", + "defaultValue": "5000", + "description": "Defines the timeout for DP LL CTS Source DUT tests. The timeout is defined as milliseconds, and has a default setting of 5000ms. Please note that if the test contains internal iterations, this timeout is re-used for each iteration. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP14_SRCCTS_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout in milliseconds.", + "type": 4 + }, + { + "configId": "0x10e0001", + "defaultValue": "4", + "description": "Defines the maximum number of lanes supported by the DUT. Typical settings are 1, 2 and 4. Default value is 4. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.\n", + "enumerationVariants": "1\n2\n4", + "flag": 3, + "id": "TSI_DP14_SRCCTS_MAX_LANES", + "maxValue": 2147480000, + "minValue": 0, + "name": "Maximum number of lanes supported by the DUT.", + "type": 4 + }, + { + "configId": "0x10e0002", + "defaultValue": "30", + "description": "Defines the maximum link rate as multiplier for 0.27Gbps. Typical settings are 6 (RBR), 10 (HBR), 20 (HBR2) and 30 (HBR3). Notice that HBR3 link rate is usable only with UCD-400. The default setting is 30 (HBR3). Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "6 # RBR (1.62 Gbps)\n10 # HBR (2.7 Gbps)\n20 # HBR2 (5.4 Gbps)\n30 # HBR3 (8.10 Gbps)", + "flag": 3, + "id": "TSI_DP14_SRCCTS_MAX_LINK_RATE", + "maxValue": 30, + "minValue": 6, + "name": "Maximum link rate supported by the DUT as multiplier for 0.27Gbps.", + "type": 4 + }, + { + "configId": "0x10e0003", + "defaultValue": "0x00F03A53", + "description": "Defines the DUT capabilities as flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Voltage swing level 3 (1.2V) supported"}, + {"mask": "0x00000002", "description": "Pre-emphasis level 3 (9.5dB) supported"}, + {"mask": "0x00000004", "description": "Fixed timing DUT supported"}, + {"mask": "0x00000008", "description": "Spread Spectrum support"}, + {"mask": "0x00000010", "description": "Video format change without LT supported"}, + {"mask": "0x00000020", "description": "Lane count reduction without LT supported"}, + {"mask": "0x00000040", "description": "E-DDC protocol supported"}, + {"mask": "0x00000080", "description": "Audio Info Frame for 2 channel audio transmission supported"}, + {"mask": "0x00000100", "description": "DUT is Type-C device"}, + {"mask": "0x00000200", "description": "FEC supported"}, + {"mask": "0x00000400", "description": "FEC disable sequence supported"}, + {"mask": "0x00000800", "description": "Audio without Video supported"}, + {"mask": "0x00001000", "description": "DSC supported"}, + {"mask": "0x00002000", "description": "DSC block prediction supported"}, + {"mask": "0x00080000", "description": "DUT supports native Display ID read"}, + {"mask": "0x00100000", "description": "DisplayID Type VII Detailed Timing Descriptor supported"}, + {"mask": "0x00200000", "description": "DisplayID Type VIII Detailed Timing Descriptor supported"}, + {"mask": "0x00400000", "description": "DisplayID Type IX Detailed Timing Descriptor supported"}, + {"mask": "0x00800000", "description": "DisplayID Type X Detailed Timing Descriptor supported"}, + {"mask": "0x01000000", "description": "2x1 tiled display and DisplayID Tiled Display Topology data block supported"}, + {"mask": "0x02000000", "description": "Field sequential stereo and DisplayID Tiled Stereo Display Interface data block supported"}, + {"mask": "0x04000000", "description": "Stacked frame stereo and DisplayID Tiled Stereo Display Interface data block supported"} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_DUT_CAPS", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x10e0004", + "defaultValue": "0x0000001A", + "description": "Defines the DUT Test automation capabilities as flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "TEST_LINK_TRAINING"}, + {"mask": "0x00000002", "description": "TEST_EDID_READ"}, + {"mask": "0x00000004", "description": "TEST_VIDEO_PATTERN"}, + {"mask": "0x00000020", "description": "TEST_AUDIO_PATTERN"}, + {"mask": "0x00000018", "description": "Event indicating DUT ready", "enumerationVariants": "0 # Always ready\n1 # EDID read\n2 # Link Training end\n3 # Active Video"} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_DUT_TA", + "name": "DUT Test automation flags.", + "type": 12 + }, + { + "configId": "0x10e0005", + "defaultValue": "1000", + "description": "Defines the duration of long HPD pulses generated by the tests. The duration is defined in millieconds. Default setting is 1000ms. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.\n", + "flag": 3, + "id": "TSI_DP14_SRCCTS_LONG_HPD_PULSE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Long HPD pulse duration, in milliseconds.", + "type": 4 + }, + { + "configId": "0x10e0006", + "defaultValue": "5000", + "description": "Defines a timeout for Link Training start, in milliseconds. Default setting is 5000ms. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP14_SRCCTS_LT_START_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Link Training start timeout, in milliseconds.", + "type": 4 + }, + { + "configId": "0x10e0007", + "defaultValue": "5000", + "description": "Defines an internal cycle delay used within tests that repeat a number of test steps. The delay is defined in milliseconds. Default setting is 3000ms. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_TEST_CYCLE_DELAY", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test cycle delay, in milliseconds", + "type": 4 + }, + { + "configId": "0x10e0008", + "defaultValue": "2", + "description": "Defines number of color formats programmed into the CI’s TSI_DP14_SRCCTS_COLOR_MODE_0 to TSI_DP14_SRCCTS_COLOR_MODE_31. The default setting is 2. Important: The CTS Specification defines two mandatory color formats: 0x0 and 0x20. These color-format definitions must be present in the color mode table. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_FORMATS", + "maxValue": 2147483647, + "minValue": 0, + "name": "Number of valid color format definitions.", + "type": 4 + }, + { + "configId": "0x10e0009", + "defaultValue": "1", + "description": "Defines the fail-safe mode ID. The default setting is 1 ( = 640 x 480 @ 60 Hz, 6 BPC). Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This setting should not be modified. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "enumerationVariants": "1 # 640 x 480 @ 60Hz 6 BPC", + "flag": 3, + "id": "TSI_DP14_SRCCTS_FAILSAFE_MODE", + "maxValue": 2147480000, + "minValue": 0, + "name": "Fail-safe video mode ID.", + "type": 4 + }, + { + "configId": "0x10e000a", + "defaultValue": "22", + "description": "Defines the maximum resolution supported by the DUT. Default setting is 22 ( = 1920 x 1080 @ 60Hz, 8 BPC). Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC\n5 # 1920 x 1080 @ 60Hz 8 BPC\n6 # 1920 x 1440 @ 60Hz 8 BPC\n7 # 1920 x 1080 @ 120Hz 8 BPC\n8 # 1280 x 800 @ 60Hz RB1 6 BPC\n9 # 1280 x 768 @ 60Hz RB1 6 BPC\n10 # 800 x 600 @ 60Hz 10 BPC\n11 # 1024 x 768 @ 60Hz 10 BPC\n12 # 1280 x 1024 @ 60Hz 8 BPC\n13 # 1360 x 768 @ 60Hz 10 BPC\n14 # 1280 x 800 @ 60Hz RB1 10 BPC\n15 # 1400 x 1050 @ 60Hz RB1 8 BPC\n16 # 1280 x 768 @ 60Hz RB1 10 BPC\n17 # 1600 x 1200 @ 60Hz RB1 6 BPC\n18 # 2048 x 1536 @ 60Hz RB1 8 BPC\n19 # 1792 x 1344 @ 60Hz 8 BPC\n20 # 1600 x 1200 @ 60Hz RB1 10 BPC\n21 # 3840 x 2160 @ 30Hz 8 BPC\n22 # 3840 x 2160 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP14_SRCCTS_MAX_RESOLUTION", + "maxValue": 2147480000, + "minValue": 0, + "name": "Maximum resolution supported by the DUT.", + "type": 4 + }, + { + "configId": "0x10e000b", + "defaultValue": "0x00000001", + "description": "Defines link rates supported by DUT as it defined in the DP 2.0 specification.\n ", + "flag": 3, + "bitList": + [ + {"mask": "0x00000001", "description": "10 Gbps per lane support. Default setting is 1."}, + {"mask": "0x00000002", "description": "20 Gbps per lane support. Default setting is 0."}, + {"mask": "0x00000004", "description": "13.5 Gbps per lane support. Default setting is 0."} + ], + "id": "TSI_DP14_SRCCTS_DUT_LINK_RATES", + "name": "Link rates supported by DUT", + "type": 12 + }, + { + "configId": "0x10e000c", + "defaultValue": "0", + "description": "Defines number of LTTPR devices present between Source DUT and Reference Sink. Up to the eight devices. Default setting is 0.\n ", + "flag": 3, + "id": "TSI_DP14_SRCCTS_LTTPR_DEVICE_COUNT", + "maxValue": 8, + "minValue": 0, + "name": "Number of LTTPR devices present between Source DUT and Reference Sink", + "type": 4 + }, + { + "configId": "0x10e000e", + "defaultValue": "0x00000000", + "description": "Defines the Debug mode configuration.", + "bitList": + [ + {"mask": "0x00000001", "description": "Continue on fail enabled"} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_DUT_DEBUG_CONF", + "name": "Debug mode configuration.", + "type": 12 + }, + { + "configId": "0x10e0010", + "defaultValue": "32000", + "description": "Defines the minimum sample rate supported by the DUT in Hz. Allowed values are 32000, 44100, 48000, 88200, 96000, 176400 or 192000 Hz. Default setting is 44100 Hz.\n ", + "enumerationVariants": "32000\n44100\n48000\n88200\n96000\n176400\n192000", + "flag": 3, + "id": "TSI_DP14_SRCCTS_MIN_SAMPLE_RATE", + "maxValue": 2147480000, + "minValue": 0, + "name": "Minimum supported sample rate", + "type": 4 + }, + { + "configId": "0x10e0011", + "defaultValue": "192000", + "description": "Defines the maximum sample rate supported by the DUT in Hz. Allowed values are 32000, 44100, 48000, 88200, 96000, 176400 or 192000 Hz. Default setting is 44100 Hz.\n ", + "enumerationVariants": "32000\n44100\n48000\n88200\n96000\n176400\n192000", + "flag": 3, + "id": "TSI_DP14_SRCCTS_MAX_SAMPLE_RATE", + "maxValue": 2147480000, + "minValue": 0, + "name": "Maximum supported sample rate", + "type": 4 + }, + { + "configId": "0x10e0012", + "defaultValue": "2", + "description": "Defines the minimum number of channels for minimum sample rate. Default is 2 channels (Stereo). Highest valid channel count is 8. If the number is different from 2 channels, then the appropriate channel allocation must be provided through the CI 7.9.19 TSI_DP14_SRCCTS_CH_ALLOC1.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_CHANNELS1", + "maxValue": 8, + "minValue": 0, + "name": "Minimum number of channels for minimum sample rate", + "type": 4 + }, + { + "configId": "0x10e0013", + "defaultValue": "1", + "description": "The audio parameters contain four pairs of audio channel count and channel allocation values. Please notice that maximum channel count allowed is 8. For two channels, the channel allocation of zero is considered valid; In this case, stereo is assumed (FL/FR).\nAllocation value\nChannel count/bit\nChannel assignments\n0x001 2 FL/FR. (Front Left, Front Right)\n0x002 1 LFE. (Low Frequency Effects)\n0x004 1 FC. (Front Center) \n0x008 2 RL/RR. (Rear Left, Rear Right).\n0x010 1 RC. (Rear Center). \n0x020 2 FLC/FRC. (Front Left off Center, Front Right off Center).\n0x040 2 RLC/RRC (Rear Left off Center, Rear Right off Center). \n0x080 2 FLW/FRW (Front Left Wide, Front Right Wide) \n0x100 2 FLH/FRH (Front Left High, Front Right High)\n0x200 1 TC (Top Center) \n0x400 1 FCH (Front Center High)\n", + "flag": 3, + "id": "TSI_DP14_SRCCTS_CH_ALLOC1", + "maxValue": 2147480000, + "minValue": 0, + "name": "Channel allocation bits", + "type": 4 + }, + { + "configId": "0x10e0014", + "defaultValue": "6", + "description": "Defines the maximum number of channels for minimum sample rate. Default is 2 channels (Stereo). Highest valid channel count is 8. If the number is different from 2 channels, then the appropriate channel allocation must be provided through the CI 7.9.21 TSI_DP14_SRCCTS_CH_ALLOC2.\n ", + "flag": 3, + "id": "TSI_DP14_SRCCTS_CHANNELS2", + "maxValue": 8, + "minValue": 0, + "name": "Maximum number of channels for minimum sample rate", + "type": 4 + }, + { + "configId": "0x10e0015", + "defaultValue": "15", + "description": "The audio parameters contain four pairs of audio channel count and channel allocation values. Please notice that maximum channel count allowed is 8. For two channels, the channel allocation of zero is considered valid; In this case, stereo is assumed (FL/FR).\nAllocation value\nChannel count/bit\nChannel assignments\n0x001 2 FL/FR. (Front Left, Front Right)\n0x002 1 LFE. (Low Frequency Effects)\n0x004 1 FC. (Front Center) \n0x008 2 RL/RR. (Rear Left, Rear Right).\n0x010 1 RC. (Rear Center). \n0x020 2 FLC/FRC. (Front Left off Center, Front Right off Center).\n0x040 2 RLC/RRC (Rear Left off Center, Rear Right off Center). \n0x080 2 FLW/FRW (Front Left Wide, Front Right Wide) \n0x100 2 FLH/FRH (Front Left High, Front Right High)\n0x200 1 TC (Top Center) \n0x400 1 FCH (Front Center High)\n", + "flag": 3, + "id": "TSI_DP14_SRCCTS_CH_ALLOC2", + "maxValue": 2147480000, + "minValue": 0, + "name": "Channel allocation bits 2", + "type": 4 + }, + { + "configId": "0x10e0016", + "defaultValue": "2", + "description": "Defines the minimum number of channels for maximum sample rate. Default is 2 channels (Stereo). Highest valid channel count is 8. If the number is different from 2 channels, then the appropriate channel allocation must be provided through the CI 7.9.23 TSI_DP14_SRCCTS_CH_ALLOC3.\n ", + "flag": 3, + "id": "TSI_DP14_SRCCTS_CHANNELS3", + "maxValue": 8, + "minValue": 0, + "name": "Minimum number of channels for maximum sample rate", + "type": 4 + }, + { + "configId": "0x10e0017", + "defaultValue": "1", + "description": "The audio parameters contain four pairs of audio channel count and channel allocation values. Please notice that maximum channel count allowed is 8. For two channels, the channel allocation of zero is considered valid; In this case, stereo is assumed (FL/FR).\nAllocation value\nChannel count/bit\nChannel assignments\n0x001 2 FL/FR. (Front Left, Front Right)\n0x002 1 LFE. (Low Frequency Effects)\n0x004 1 FC. (Front Center) \n0x008 2 RL/RR. (Rear Left, Rear Right).\n0x010 1 RC. (Rear Center). \n0x020 2 FLC/FRC. (Front Left off Center, Front Right off Center).\n0x040 2 RLC/RRC (Rear Left off Center, Rear Right off Center). \n0x080 2 FLW/FRW (Front Left Wide, Front Right Wide) \n0x100 2 FLH/FRH (Front Left High, Front Right High)\n0x200 1 TC (Top Center) \n0x400 1 FCH (Front Center High)\n", + "flag": 3, + "id": "TSI_DP14_SRCCTS_CH_ALLOC3", + "maxValue": 2147480000, + "minValue": 0, + "name": "Channel allocations bits 3", + "type": 4 + }, + { + "configId": "0x10e0018", + "defaultValue": "6", + "description": "Defines the maximum number of channels for maximum sample rate. Default is 2 channels (Stereo). Highest valid channel count is 8. If the number is different from 2 channels, then the appropriate channel allocation must be provided through the CI 7.9.25 TSI_DP14_SRCCTS_CH_ALLOC4.\n ", + "flag": 3, + "id": "TSI_DP14_SRCCTS_CHANNELS4", + "maxValue": 8, + "minValue": 0, + "name": "Maximum number of channels for maximum sample rate", + "type": 4 + }, + { + "configId": "0x10e0019", + "defaultValue": "15", + "description": "The audio parameters contain four pairs of audio channel count and channel allocation values. Please notice that maximum channel count allowed is 8. For two channels, the channel allocation of zero is considered valid; In this case, stereo is assumed (FL/FR).\nAllocation value\nChannel count/bit\nChannel assignments\n0x001 2 FL/FR. (Front Left, Front Right)\n0x002 1 LFE. (Low Frequency Effects)\n0x004 1 FC. (Front Center) \n0x008 2 RL/RR. (Rear Left, Rear Right).\n0x010 1 RC. (Rear Center). \n0x020 2 FLC/FRC. (Front Left off Center, Front Right off Center).\n0x040 2 RLC/RRC (Rear Left off Center, Rear Right off Center). \n0x080 2 FLW/FRW (Front Left Wide, Front Right Wide) \n0x100 2 FLH/FRH (Front Left High, Front Right High)\n0x200 1 TC (Top Center) \n0x400 1 FCH (Front Center High)\n", + "flag": 3, + "id": "TSI_DP14_SRCCTS_CH_ALLOC4", + "maxValue": 2147480000, + "minValue": 0, + "name": "Channel allocation bits 4", + "type": 4 + }, + { + "configId": "0x10e001a", + "defaultValue": "0", + "description": "Defines the audio test pattern. Default setting is zero. Please see table below for allowed settings:\nValue Description \n0 Operator specific waveform \n1 Sawtooth waveform", + "enumerationVariants": "0 # Operator specific waveform\n1 # Sawtooth waveform", + "flag": 3, + "id": "TSI_DP14_SRCCTS_AUDIO_TEST_PATTERN", + "maxValue": 2147480000, + "minValue": 0, + "name": "Audio test pattern", + "type": 4 + }, + { + "configId": "0x10e001b", + "defaultValue": "16", + "description": "Defines sample size used for EDID configuration while testing. The size is indicated as number of bits per sample. Default setting is 16. Valid settings are 16, 20 or 24.\n ", + "enumerationVariants": "16 # 16 bits / sample\n20 # 20 bits / sample\n24 # 24 bits / sample", + "flag": 3, + "id": "TSI_DP14_SRCCTS_EDID_SAMPLE_SIZE", + "maxValue": 2147480000, + "minValue": 0, + "name": "Sample size for EDID while testing", + "type": 4 + }, + { + "configId": "0x10e0020", + "defaultValue": "9", + "description": "These three CI’s define the most packed timings for 1, 2 and 4 lane modes. Default for 1 lane is 8 ( = 1280 x 800 @ 60Hz RB1, 18BPP). Default 2 lanes is 12 ( = 1280 x 1024 @ 60Hz, 24BPP). Default for 4 lanes is 18 ( = 2048 x 1536 @ 60Hz RB1, 24BPP). Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n10 # DMT 800 x 600p @ 60.317Hz, 10 bpc\n11 # DMT 1024 x 768p @ 60Hz, 6 bpc\n8 # CVT 1280 x 800p @ 60Hz, RB1, 6 bpc\n9 # DMT 1280 x 768p @ 60Hz, RB1, 6 bpc\n23 # CTA 1440 x 480p @ 59.94Hz, 8 bpc\n24 # CTA 1440 x 576p @ 50Hz, 8 bpc", + "flag": 3, + "id": "TSI_DP14_SRCCTS_1L_MOST_PACKED", + "maxValue": 2147480000, + "minValue": 0, + "name": "Most packed video timing definitions for 1 lanes links", + "type": 4 + }, + { + "configId": "0x10e0021", + "defaultValue": "15", + "description": "These three CI’s define the most packed timings for 1, 2 and 4 lane modes. Default for 1 lane is 8 ( = 1280 x 800 @ 60Hz RB1, 18BPP). Default 2 lanes is 12 ( = 1280 x 1024 @ 60Hz, 24BPP). Default for 4 lanes is 18 ( = 2048 x 1536 @ 60Hz RB1, 24BPP). Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n16 # DMT 1280 x 768p @ 60Hz, 10 bpc\n14 # CVT 1280 x 800p @ 60Hz, 10 bpc\n4 # DMT 1280 x 960p @ 60Hz, 8 bpc\n12 # DMT 1280 x 1024p @ 60Hz, 8 bpc\n13 # DMT 1360 x 768p @ 60Hz, 10 bpc\n15 # DMT 1400 x 1050p @ 60Hz, RB1, 8 bpc\n17 # CVT 1600 x 1200p @ 60Hz, RB1, 6 bpc\n13 # DMT 1360 x 768p @ 60Hz, 8 bpc\n26 # CVT 1280 x 800p @ 60Hz, 8 bpc\n25 # DMT 1280 x 768p @ 60Hz, 8 bpc", + "flag": 3, + "id": "TSI_DP14_SRCCTS_2L_MOST_PACKED", + "maxValue": 2147480000, + "minValue": 0, + "name": "Most packed video timing definitions for 2 lanes links", + "type": 4 + }, + { + "configId": "0x10e0022", + "defaultValue": "29", + "description": "These three CI’s define the most packed timings for 1, 2 and 4 lane modes. Default for 1 lane is 8 ( = 1280 x 800 @ 60Hz RB1, 18BPP). Default 2 lanes is 12 ( = 1280 x 1024 @ 60Hz, 24BPP). Default for 4 lanes is 18 ( = 2048 x 1536 @ 60Hz RB1, 24BPP). Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n20 # DMT 1600 x 1200p @ 60Hz, 10 bpc\n28 # DMT 1600 x 1200p @ 60Hz, 8 bpc\n19 # DMT 1792 x 1344p @ 60Hz, 8 bpc\n29 # CTA 1920 x 1080p @ 60Hz, 10 bpc\n5 # CTA 1920 x 1080p @ 60Hz, 8 bpc\n18 # CVT 2048 x 1536p @ 60Hz, RB1, 8 bpc", + "flag": 3, + "id": "TSI_DP14_SRCCTS_4L_MOST_PACKED", + "maxValue": 2147480000, + "minValue": 0, + "name": "Most packed video timing definitions for 4 lanes links", + "type": 4 + }, + { + "configId": "0x10e0023", + "defaultValue": "2", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP14_SRCCTS_1L_RBB", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for Reduced Bitrate (RBR, 1.62Gbps) testing for 1 lanes.", + "type": 4 + }, + { + "configId": "0x10e0024", + "defaultValue": "3", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP14_SRCCTS_2L_RBB", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for Reduced Bitrate (RBR, 1.62Gbps) testing for 2 lanes.", + "type": 4 + }, + { + "configId": "0x10e0025", + "defaultValue": "5", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC\n5 # 1920 x 1080 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP14_SRCCTS_4L_RBB", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for Reduced Bitrate (RBR, 1.62Gbps) testing for 4 lanes.", + "type": 4 + }, + { + "configId": "0x10e0026", + "defaultValue": "3", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP14_SRCCTS_1L_HBR", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate (HBR, 2.7Gbps) testing for 1 lanes", + "type": 4 + }, + { + "configId": "0x10e0027", + "defaultValue": "4", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP14_SRCCTS_2L_HBR", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate (HBR, 2.7Gbps) testing for 2 lanes", + "type": 4 + }, + { + "configId": "0x10e0028", + "defaultValue": "5", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC\n5 # 1920 x 1080 @ 60Hz 8 BPC\n6 # 1920 x 1440 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP14_SRCCTS_4L_HBR", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate (HBR, 2.7Gbps) testing for 4 lanes", + "type": 4 + }, + { + "configId": "0x10e0029", + "defaultValue": "4", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP14_SRCCTS_1L_HBR2", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate-2 (HBR2, 5.4Gbps) testing for 1 lanes.", + "type": 4 + }, + { + "configId": "0x10e002a", + "defaultValue": "5", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC\n5 # 1920 x 1080 @ 60Hz 8 BPC\n6 # 1920 x 1440 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP14_SRCCTS_2L_HBR2", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate-2 (HBR2, 5.4Gbps) testing for 2 lanes.", + "type": 4 + }, + { + "configId": "0x10e002b", + "defaultValue": "7", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC\n5 # 1920 x 1080 @ 60Hz 8 BPC\n6 # 1920 x 1440 @ 60Hz 8 BPC\n7 # 1920 x 1080 @ 120Hz 8 BPC", + "flag": 3, + "id": "TSI_DP14_SRCCTS_4L_HBR2", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate-2 (HBR2, 5.4Gbps) testing for 4 lanes.", + "type": 4 + }, + { + "configId": "0x10e002c", + "defaultValue": "6", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC\n5 # 1920 x 1080 @ 60Hz 8 BPC\n6 # 1920 x 1440 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP14_SRCCTS_1L_HBR3", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate-3 (HBR3, 8.1Gbps) testing for 1 lanes.", + "type": 4 + }, + { + "configId": "0x10e002d", + "defaultValue": "21", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC\n5 # 1920 x 1080 @ 60Hz 8 BPC\n6 # 1920 x 1440 @ 60Hz 8 BPC\n7 # 1920 x 1080 @ 120Hz 8 BPC\n8 # 1280 x 800 @ 60Hz RB1 6 BPC\n9 # 1280 x 768 @ 60Hz RB1 6 BPC\n10 # 800 x 600 @ 60Hz 10 BPC\n11 # 1024 x 768 @ 60Hz 10 BPC\n12 # 1280 x 1024 @ 60Hz 8 BPC\n13 # 1360 x 768 @ 60Hz 10 BPC\n14 # 1280 x 800 @ 60Hz RB1 10 BPC\n15 # 1400 x 1050 @ 60Hz RB1 8 BPC\n16 # 1280 x 768 @ 60Hz RB1 10 BPC\n17 # 1600 x 1200 @ 60Hz RB1 6 BPC\n18 # 2048 x 1536 @ 60Hz RB1 8 BPC\n19 # 1792 x 1344 @ 60Hz 8 BPC\n20 # 1600 x 1200 @ 60Hz RB1 10 BPC\n21 # 3840 x 2160 @ 30Hz 8 BPC", + "flag": 3, + "id": "TSI_DP14_SRCCTS_2L_HBR3", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate-3 (HBR3, 8.1Gbps) testing for 2 lanes.", + "type": 4 + }, + { + "configId": "0x10e002e", + "defaultValue": "22", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC\n5 # 1920 x 1080 @ 60Hz 8 BPC\n6 # 1920 x 1440 @ 60Hz 8 BPC\n7 # 1920 x 1080 @ 120Hz 8 BPC\n8 # 1280 x 800 @ 60Hz RB1 6 BPC\n9 # 1280 x 768 @ 60Hz RB1 6 BPC\n10 # 800 x 600 @ 60Hz 10 BPC\n11 # 1024 x 768 @ 60Hz 10 BPC\n12 # 1280 x 1024 @ 60Hz 8 BPC\n13 # 1360 x 768 @ 60Hz 10 BPC\n14 # 1280 x 800 @ 60Hz RB1 10 BPC\n15 # 1400 x 1050 @ 60Hz RB1 8 BPC\n16 # 1280 x 768 @ 60Hz RB1 10 BPC\n17 # 1600 x 1200 @ 60Hz RB1 6 BPC\n18 # 2048 x 1536 @ 60Hz RB1 8 BPC\n19 # 1792 x 1344 @ 60Hz 8 BPC\n20 # 1600 x 1200 @ 60Hz RB1 10 BPC\n21 # 3840 x 2160 @ 30Hz 8 BPC\n22 # 3840 x 2160 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP14_SRCCTS_4L_HBR3", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate-3 (HBR3, 8.1Gbps) testing for 4 lanes.", + "type": 4 + }, + { + "configId": "0x10e0038", + "defaultValue": "0", + "description": "These parameters are considered mandatory and should not be changed.", + "flag": 1, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_0", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_0", + "type": 4 + }, + { + "configId": "0x10e0039", + "defaultValue": "32", + "description": "These parameters are considered mandatory and should not be changed.", + "flag": 1, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_1", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_1", + "type": 4 + }, + { + "configId": "0x10e003a", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_2", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_2", + "type": 4 + }, + { + "configId": "0x10e003b", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_3", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_3", + "type": 4 + }, + { + "configId": "0x10e003c", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_4", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_4", + "type": 4 + }, + { + "configId": "0x10e003d", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_5", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_5", + "type": 4 + }, + { + "configId": "0x10e003e", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_6", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_6", + "type": 4 + }, + { + "configId": "0x10e003f", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_7", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_7", + "type": 4 + }, + { + "configId": "0x10e0040", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_8", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_8", + "type": 4 + }, + { + "configId": "0x10e0041", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_9", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_9", + "type": 4 + }, + { + "configId": "0x10e0042", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_10", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_10", + "type": 4 + }, + { + "configId": "0x10e0043", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_11", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_11", + "type": 4 + }, + { + "configId": "0x10e0044", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_12", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_12", + "type": 4 + }, + { + "configId": "0x10e0045", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_13", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_13", + "type": 4 + }, + { + "configId": "0x10e0046", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_14", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_14", + "type": 4 + }, + { + "configId": "0x10e0047", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_15", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_15", + "type": 4 + }, + { + "configId": "0x10e0048", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_16", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_16", + "type": 4 + }, + { + "configId": "0x10e0049", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_17", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_17", + "type": 4 + }, + { + "configId": "0x10e004a", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_18", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_18", + "type": 4 + }, + { + "configId": "0x10e004b", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_19", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_19", + "type": 4 + }, + { + "configId": "0x10e004c", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_20", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_20", + "type": 4 + }, + { + "configId": "0x10e004d", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_21", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_21", + "type": 4 + }, + { + "configId": "0x10e004e", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_22", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_22", + "type": 4 + }, + { + "configId": "0x10e004f", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_23", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_23", + "type": 4 + }, + { + "configId": "0x10e0050", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_24", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_24", + "type": 4 + }, + { + "configId": "0x10e0051", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_25", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_25", + "type": 4 + }, + { + "configId": "0x10e0052", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_26", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_26", + "type": 4 + }, + { + "configId": "0x10e0053", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_27", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_27", + "type": 4 + }, + { + "configId": "0x10e0054", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_28", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_28", + "type": 4 + }, + { + "configId": "0x10e0055", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_29", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_29", + "type": 4 + }, + { + "configId": "0x10e0056", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_30", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_30", + "type": 4 + }, + { + "configId": "0x10e0057", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x010e0008 (DP14_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_COLOR_MODE_31", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_COLOR_MODE_31", + "type": 4 + }, + { + "configId": "0x10e0058", + "defaultValue": "1", + "description": "Defines DUT DSC maximum supported slice. Default setting is 1. Valid settings are 1, 2, 4, 8, 10, 12, 16, 20, 24.\n ", + "enumerationVariants": "1\n2\n4\n8\n10\n12\n16\n20\n24", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_DUT_MAX_SLICE", + "maxValue": 24, + "minValue": 1, + "name": "DUT DSC maximum supported slice", + "type": 4 + }, + { + "configId": "0x10e0059", + "defaultValue": "65538", + "description": "Defines DSC version. Default setting is 1.2. Valid settings are 1.1 and 1.2.\n ", + "enumerationVariants": "65537 # 1.1\n65538 # 1.2", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VERSION", + "maxValue": 65538, + "minValue": 65537, + "name": "DSC version", + "type": 4 + }, + { + "configId": "0x10e005a", + "defaultValue": "0", + "description": "1920x1080 @ 30Hz", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_0_ID", + "maxValue": 0, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_VMT_0_ID", + "type": 4 + }, + { + "configId": "0x10e005b", + "defaultValue": "1", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_0_LC_LR", + "mask": 1, + "name": "Lane count and Link rate flags", + "type": 4 + }, + { + "configId": "0x10e005c", + "defaultValue": "0x00000001", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_0_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x10e005d", + "defaultValue": "1", + "description": "1920x1080 @ 60Hz", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_1_ID", + "maxValue": 1, + "minValue": 1, + "name": "TSI_DP14_SRCCTS_DSC_VMT_1_ID", + "type": 4 + }, + { + "configId": "0x10e005e", + "defaultValue": "1", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_1_LC_LR", + "mask": 1, + "name": "Lane count and Link rate flags", + "type": 4 + }, + { + "configId": "0x10e005f", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_1_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x10e0060", + "defaultValue": "2", + "description": "1920x1080 @ 120Hz", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_2_ID", + "maxValue": 2, + "minValue": 2, + "name": "TSI_DP14_SRCCTS_DSC_VMT_2_ID", + "type": 4 + }, + { + "configId": "0x10e0061", + "defaultValue": "292", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_2_LC_LR", + "mask": 292, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x10e0062", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_2_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x10e0063", + "defaultValue": "3", + "description": "3840x2160 @ 30Hz", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_3_ID", + "maxValue": 3, + "minValue": 3, + "name": "TSI_DP14_SRCCTS_DSC_VMT_3_ID", + "type": 4 + }, + { + "configId": "0x10e0064", + "defaultValue": "292", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_3_LC_LR", + "mask": 292, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x10e0065", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_3_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x10e0066", + "defaultValue": "4", + "description": "3840x2160 @ 60Hz", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_4_ID", + "maxValue": 4, + "minValue": 4, + "name": "TSI_DP14_SRCCTS_DSC_VMT_4_ID", + "type": 4 + }, + { + "configId": "0x10e0067", + "defaultValue": "328", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_4_LC_LR", + "mask": 328, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x10e0068", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_4_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x10e0069", + "defaultValue": "5", + "description": "3840x2160 @ 120Hz", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_5_ID", + "maxValue": 5, + "minValue": 5, + "name": "TSI_DP14_SRCCTS_DSC_VMT_5_ID", + "type": 4 + }, + { + "configId": "0x10e006a", + "defaultValue": "1152", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_5_LC_LR", + "mask": 1152, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x10e006b", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_5_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x10e006c", + "defaultValue": "6", + "description": "5120x2160 @ 30Hz", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_6_ID", + "maxValue": 6, + "minValue": 6, + "name": "TSI_DP14_SRCCTS_DSC_VMT_6_ID", + "type": 4 + }, + { + "configId": "0x10e006d", + "defaultValue": "292", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_6_LC_LR", + "mask": 292, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x10e006e", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_6_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x10e006f", + "defaultValue": "7", + "description": "5120x2160 @ 60Hz", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_7_ID", + "maxValue": 7, + "minValue": 7, + "name": "TSI_DP14_SRCCTS_DSC_VMT_6_ID", + "type": 4 + }, + { + "configId": "0x10e0070", + "defaultValue": "584", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_7_LC_LR", + "mask": 584, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x10e0071", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_7_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x10e0072", + "defaultValue": "8", + "description": "5120x2160 @ 120Hz", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_8_ID", + "maxValue": 8, + "minValue": 8, + "name": "TSI_DP14_SRCCTS_DSC_VMT_8_ID", + "type": 4 + }, + { + "configId": "0x10e0073", + "defaultValue": "1152", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_8_LC_LR", + "mask": 1152, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x10e0074", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_8_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x10e0075", + "defaultValue": "9", + "description": "7680x4320 @ 30Hz", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_9_ID", + "maxValue": 9, + "minValue": 9, + "name": "TSI_DP14_SRCCTS_DSC_VMT_9_ID", + "type": 4 + }, + { + "configId": "0x10e0076", + "defaultValue": "1728", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_9_LC_LR", + "mask": 1728, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x10e0077", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_9_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x10e0078", + "defaultValue": "10", + "description": "7680x4320 @ 60Hz", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_10_ID", + "maxValue": 10, + "minValue": 10, + "name": "TSI_DP14_SRCCTS_DSC_VMT_10_ID", + "type": 4 + }, + { + "configId": "0x10e0079", + "defaultValue": "3072", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_10_LC_LR", + "mask": 3072, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x10e007a", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_10_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x10e007b", + "defaultValue": "11", + "description": "7680x4320 @ 100Hz", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_11_ID", + "maxValue": 11, + "minValue": 11, + "name": "TSI_DP14_SRCCTS_DSC_VMT_11_ID", + "type": 4 + }, + { + "configId": "0x10e007c", + "defaultValue": "2048", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_11_LC_LR", + "mask": 2048, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x10e007d", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_VMT_11_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x10e007e", + "defaultValue": "1", + "description": "Defined the number of items in DSC color format table.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_FORMATS", + "maxValue": 2147483647, + "minValue": 0, + "name": "Number of items in DSC color format table", + "type": 4 + }, + { + "configId": "0x10e007f", + "defaultValue": "32", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 1, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_0", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_0", + "type": 4 + }, + { + "configId": "0x10e0080", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_1", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_1", + "type": 4 + }, + { + "configId": "0x10e0081", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_2", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_2", + "type": 4 + }, + { + "configId": "0x10e0082", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_3", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_3", + "type": 4 + }, + { + "configId": "0x10e0083", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_4", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_4", + "type": 4 + }, + { + "configId": "0x10e0084", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_5", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_5", + "type": 4 + }, + { + "configId": "0x10e0085", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_6", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_6", + "type": 4 + }, + { + "configId": "0x10e0086", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_7", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_7", + "type": 4 + }, + { + "configId": "0x10e0087", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_8", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_8", + "type": 4 + }, + { + "configId": "0x10e0088", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_9", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_9", + "type": 4 + }, + { + "configId": "0x10e0089", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_10", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_10", + "type": 4 + }, + { + "configId": "0x10e008a", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_11", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_11", + "type": 4 + }, + { + "configId": "0x10e008b", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_12", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_12", + "type": 4 + }, + { + "configId": "0x10e008c", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_13", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_13", + "type": 4 + }, + { + "configId": "0x10e008d", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_14", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_14", + "type": 4 + }, + { + "configId": "0x10e008e", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_15", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_15", + "type": 4 + }, + { + "configId": "0x10e008f", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_16", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_16", + "type": 4 + }, + { + "configId": "0x10e0090", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_17", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_17", + "type": 4 + }, + { + "configId": "0x10e0091", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_18", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_18", + "type": 4 + }, + { + "configId": "0x10e0092", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_19", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_19", + "type": 4 + }, + { + "configId": "0x10e0093", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_20", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_20", + "type": 4 + }, + { + "configId": "0x10e0094", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_21", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_21", + "type": 4 + }, + { + "configId": "0x10e0095", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_22", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_22", + "type": 4 + }, + { + "configId": "0x10e0096", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_23", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_23", + "type": 4 + }, + { + "configId": "0x10e0097", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_24", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_24", + "type": 4 + }, + { + "configId": "0x10e0098", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_25", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_25", + "type": 4 + }, + { + "configId": "0x10e0099", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_26", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_26", + "type": 4 + }, + { + "configId": "0x10e009a", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_27", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_27", + "type": 4 + }, + { + "configId": "0x10e009b", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_28", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_28", + "type": 4 + }, + { + "configId": "0x10e009c", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_29", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_29", + "type": 4 + }, + { + "configId": "0x10e009d", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_30", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_30", + "type": 4 + }, + { + "configId": "0x10e009e", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_31", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_DSC_COLOR_MODE_31", + "type": 4 + }, + { + "configId": "0x10e009f", + "defaultValue": "5120", + "description": "Defines maximum HActive per stream.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_MAX_STREAM_HACTIVE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum HActive per stream", + "type": 4 + }, + { + "configId": "0x10e00a0", + "defaultValue": "2160", + "description": "Defines maximum VActive per stream.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_MAX_STREAM_VACTIVE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum VActive per stream", + "type": 4 + }, + { + "configId": "0x10e00a1", + "defaultValue": "742500", + "description": "Defines maximum pixel clock per stream, kHz.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_MAX_STREAM_PIXEL_CLOCK", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum pixel clock per stream, in kHz", + "type": 4 + }, + { + "configId": "0x10e00a2", + "defaultValue": "1297", + "description": "Defines tested audio mode. Bytes 2...0 of the word match data bytes 3...1 of a corresponding Audio InfoFrame (See CTA-861-G, Section 6.6). Byte 3 is set to 0. Unused entries are set to 0.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_0", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_0", + "type": 4 + }, + { + "configId": "0x10e00a3", + "defaultValue": "2321", + "description": "Defines tested audio mode. Bytes 2...0 of the word match data bytes 3...1 of a corresponding Audio InfoFrame (See CTA-861-G, Section 6.6). Byte 3 is set to 0. Unused entries are set to 0.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_1", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_1", + "type": 4 + }, + { + "configId": "0x10e00a4", + "defaultValue": "3345", + "description": "Defines tested audio mode. Bytes 2...0 of the word match data bytes 3...1 of a corresponding Audio InfoFrame (See CTA-861-G, Section 6.6). Byte 3 is set to 0. Unused entries are set to 0.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_2", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_2", + "type": 4 + }, + { + "configId": "0x10e00a5", + "defaultValue": "2833", + "description": "Defines tested audio mode. Bytes 2...0 of the word match data bytes 3...1 of a corresponding Audio InfoFrame (See CTA-861-G, Section 6.6). Byte 3 is set to 0. Unused entries are set to 0.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_3", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_3", + "type": 4 + }, + { + "configId": "0x10e00a6", + "defaultValue": "3857", + "description": "Defines tested audio mode. Bytes 2...0 of the word match data bytes 3...1 of a corresponding Audio InfoFrame (See CTA-861-G, Section 6.6). Byte 3 is set to 0. Unused entries are set to 0.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_4", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_4", + "type": 4 + }, + { + "configId": "0x10e00a7", + "defaultValue": "0", + "description": "Defines tested audio mode. Bytes 2...0 of the word match data bytes 3...1 of a corresponding Audio InfoFrame (See CTA-861-G, Section 6.6). Byte 3 is set to 0. Unused entries are set to 0.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_5", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_5", + "type": 4 + }, + { + "configId": "0x10e00a8", + "defaultValue": "0", + "description": "Defines tested audio mode. Bytes 2...0 of the word match data bytes 3...1 of a corresponding Audio InfoFrame (See CTA-861-G, Section 6.6). Byte 3 is set to 0. Unused entries are set to 0.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_6", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_6", + "type": 4 + }, + { + "configId": "0x10e00a9", + "defaultValue": "0", + "description": "Defines tested audio mode. Bytes 2...0 of the word match data bytes 3...1 of a corresponding Audio InfoFrame (See CTA-861-G, Section 6.6). Byte 3 is set to 0. Unused entries are set to 0.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_7", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_7", + "type": 4 + }, + { + "configId": "0x10e00aa", + "defaultValue": "0x00000103", + "description": "DMT timings (Progressive Scan) supported by the DUT without DSC.", + "bitList": + [ + {"mask": "0x00000001", "description": "800x600 @ 60Hz (DMT 09h)"}, + {"mask": "0x00000002", "description": "1024x768 @ 60Hz (DMT 10h)"}, + {"mask": "0x00000004", "description": "1280x768 @ 60Hz (DMT 17h)"}, + {"mask": "0x00000008", "description": "1280x800 @ 60Hz (DMT 1Ch)"}, + {"mask": "0x00000010", "description": "1280x960 @ 60Hz (DMT 20h)"}, + {"mask": "0x00000020", "description": "1280x1024 @ 60Hz (DMT 23h)"}, + {"mask": "0x00000040", "description": "1360x768 @ 60Hz (DMT 27h)"}, + {"mask": "0x00000080", "description": "1400x1050 @ 60Hz (DMT 2Ah)"}, + {"mask": "0x00000100", "description": "1600x1200 @ 60Hz (DMT 33h)"}, + {"mask": "0x00000200", "description": "1680x1050 @ 60Hz (DMT 3Ah)"}, + {"mask": "0x00000400", "description": "1856x1392 @ 60Hz (DMT 41h)"}, + {"mask": "0x00000800", "description": "1920x1080 @ 60Hz (DMT 52h)"}, + {"mask": "0x00001000", "description": "1920x1200 @ 60Hz (DMT 45h)"}, + {"mask": "0x00002000", "description": "1920x1440 @ 60Hz (DMT 49h)"} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_DMT_TIMING", + "name": "DMT timings.", + "type": 12 + }, + { + "configId": "0x10e00ab", + "defaultValue": "0x00000106", + "description": "CTA timings (Progressive Scan) supported by the DUT without DSC.", + "bitList": + [ + {"mask": "0x00000001", "description": "720x480 @ 60Hz (VIC 2)"}, + {"mask": "0x00000002", "description": "1280x720 @ 60Hz (VIC 4)"}, + {"mask": "0x00000004", "description": "1920x1080 @ 60Hz (VIC 16)"}, + {"mask": "0x00000008", "description": "1280x720 @ 120Hz (VIC 47)"}, + {"mask": "0x00000010", "description": "720x480 @ 120Hz (VIC 48)"}, + {"mask": "0x00000020", "description": "1920x1080 @ 120Hz (VIC 63)"}, + {"mask": "0x00000040", "description": "1680x720 @ 60Hz (VIC 83)"}, + {"mask": "0x00000080", "description": "1680x720 @ 120Hz (VIC 85)"}, + {"mask": "0x00000100", "description": "2560x1080 @ 60Hz (VIC 90)"}, + {"mask": "0x00000200", "description": "2560x1080 @ 120Hz (VIC 92)"}, + {"mask": "0x00000400", "description": "3840x2160 @ 60Hz (VIC 97)"}, + {"mask": "0x00000800", "description": "4096x2160 @ 60Hz (VIC 102)"}, + {"mask": "0x00001000", "description": "3840x2160 @ 120Hz (VIC 118)"}, + {"mask": "0x00002000", "description": "5120x2160 @ 60Hz (VIC 126)"}, + {"mask": "0x00004000", "description": "7680x4320 @ 24Hz (VIC 194)"}, + {"mask": "0x00008000", "description": "7680x4320 @ 30Hz (VIC 196)"} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_CTA_TIMING", + "name": "CTA timings.", + "type": 12 + }, + { + "configId": "0x10e00ac", + "defaultValue": "0x00000492", + "description": "CVT timings (Progressive Scan) supported by the DUT without DSC.", + "bitList": + [ + {"mask": "0x00000001", "description": "1920x1080 @ 60Hz RBv1"}, + {"mask": "0x00000002", "description": "2048x1536 @ 60Hz RBv1"}, + {"mask": "0x00000004", "description": "2560x1080 @ 60Hz RBv1"}, + {"mask": "0x00000008", "description": "2560x1440 @ 60Hz RBv1"}, + {"mask": "0x00000010", "description": "2560x1440 @ 60Hz RBv2"}, + {"mask": "0x00000020", "description": "3840x2160 @ 60Hz RBv1"}, + {"mask": "0x00000040", "description": "3840x2160 @ 60Hz RBv2"}, + {"mask": "0x00000080", "description": "3840x2160 @ 60Hz RBv3"}, + {"mask": "0x00000100", "description": "4096x2160 @ 60Hz RBv1"}, + {"mask": "0x00000200", "description": "4096x2160 @ 60Hz RBv2"}, + {"mask": "0x00000400", "description": "4096x2160 @ 60Hz RBv3"}, + {"mask": "0x00000800", "description": "5120x2880 @ 60Hz RBv1"}, + {"mask": "0x00001000", "description": "5120x2880 @ 60Hz RBv2"}, + {"mask": "0x00002000", "description": "5120x2880 @ 60Hz RBv3"} + ], + "flag": 3, + "id": "TSI_DP14_SRCCTS_CVT_TIMING", + "name": "CVT timings.", + "type": 12 + }, + { + "configId": "0x10e00ad", + "defaultValue": "0x00000007", + "description": "DUT AdaptiveSync capabilitie.", + "bitList": + [ + {"mask": "0x00000001", "description": "DUT supports AdaptiveSync"}, + {"mask": "0x00000002", "description": "Device supports Fixed Average VTotal mode"}, + {"mask": "0x00000004", "description": "Device supports Duration Increase and Decrease constraints"}, + {"mask": "0x00000008", "description": "Adaptive-Sync must be enabled manually"} + ], + "flag": 1, + "id": "TSI_DP14_SRCCTS_AS_DUT_CAPS", + "name": "DUT AdaptiveSync capabilitie.", + "type": 12 + }, + { + "configId": "0x10e00ae", + "defaultValue": "3", + "description": "Adaptive-Sync range minimum refresh rate supported by the Source. Default value is 3.", + "enumerationVariants": "0 # 59.940 Hz\n1 # 47.952 Hz\n2 # 29.970 Hz\n3 # 23.976 Hz", + "flag": 3, + "id": "TSI_DP14_SRCCTS_AS_RANGE_MIN_RATE", + "maxValue": 3, + "minValue": 0, + "name": "Adaptive-Sync range minimum refresh rate supported by the Source", + "type": 4 + }, + { + "configId": "0x10e00af", + "defaultValue": "120", + "description": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz. A zero value means the timing is not supported.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_0", + "maxValue": 360, + "minValue": 0, + "name": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz.", + "type": 4 + }, + { + "configId": "0x10e00b0", + "defaultValue": "120", + "description": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz. A zero value means the timing is not supported.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_1", + "maxValue": 360, + "minValue": 0, + "name": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz.", + "type": 4 + }, + { + "configId": "0x10e00b1", + "defaultValue": "120", + "description": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz. A zero value means the timing is not supported.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_2", + "maxValue": 360, + "minValue": 0, + "name": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz.", + "type": 4 + }, + { + "configId": "0x10e00b2", + "defaultValue": "120", + "description": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz. A zero value means the timing is not supported.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_3", + "maxValue": 360, + "minValue": 0, + "name": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz.", + "type": 4 + }, + { + "configId": "0x10e00b3", + "defaultValue": "0", + "description": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz. A zero value means the timing is not supported.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_4", + "maxValue": 360, + "minValue": 0, + "name": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz.", + "type": 4 + }, + { + "configId": "0x10e00b4", + "defaultValue": "0", + "description": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz. A zero value means the timing is not supported.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_5", + "maxValue": 360, + "minValue": 0, + "name": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz.", + "type": 4 + }, + { + "configId": "0x10e00b5", + "defaultValue": "0", + "description": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz. A zero value means the timing is not supported.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_6", + "maxValue": 360, + "minValue": 0, + "name": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz.", + "type": 4 + }, + { + "configId": "0x10e00b6", + "defaultValue": "0", + "description": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz. A zero value means the timing is not supported.", + "flag": 3, + "id": "TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_7", + "maxValue": 360, + "minValue": 0, + "name": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz.", + "type": 4 + }, + { + "configId": "0x10e000d", + "defaultValue": "0", + "description": "Reserved for extension.", + "bitList": + [ + {"description": "0x10e000d", "defaultValue": "0"}, + {"description": "0x10e000f", "defaultValue": "0"}, + {"description": "0x10e001c", "defaultValue": "0"}, + {"description": "0x10e001d", "defaultValue": "0"}, + {"description": "0x10e001e", "defaultValue": "0"}, + {"description": "0x10e001f", "defaultValue": "0"}, + {"description": "0x10e002f", "defaultValue": "0"}, + {"description": "0x10e0030", "defaultValue": "0"}, + {"description": "0x10e0031", "defaultValue": "0"}, + {"description": "0x10e0032", "defaultValue": "0"}, + {"description": "0x10e0033", "defaultValue": "0"}, + {"description": "0x10e0034", "defaultValue": "0"}, + {"description": "0x10e0035", "defaultValue": "0"}, + {"description": "0x10e0036", "defaultValue": "0"}, + {"description": "0x10e0037", "defaultValue": "0"}, + {"description": "0x10e00b7", "defaultValue": "0"}, + {"description": "0x10e00b8", "defaultValue": "0"}, + {"description": "0x10e00b9", "defaultValue": "0"}, + {"description": "0x10e00ba", "defaultValue": "0"}, + {"description": "0x10e00bb", "defaultValue": "0"}, + {"description": "0x10e00bc", "defaultValue": "0"}, + {"description": "0x10e00bd", "defaultValue": "0"}, + {"description": "0x10e00be", "defaultValue": "0"} + ], + "flag": 3, + "id": "RESERVED", + "type": 4 + } + ], + "id": "0x03", + "name": "DP 1.4 LL CTS", + "old_names": [ + "DP 1.4 LL CTS", + "DP 1.4 Link Layer Source DUT CTS" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x04]src_dut_link_config.json b/UniTAP/dev/modules/dut_tests/cfg/[0x04]src_dut_link_config.json new file mode 100644 index 0000000..6c5e90c --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x04]src_dut_link_config.json @@ -0,0 +1,91 @@ +{ + "descriptions": [ + { + "configId": "0x10700", + "defaultValue": "5000", + "description": "Defines timeout for each test iteration, in milliseconds. The test iterates through a number of iterations depending on other tests. Each iteration must complete within this timeout in order for the test succeed. Default setting is 5000ms.", + "flag": 3, + "id": "TSI_DP_LTT_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x10701", + "defaultValue": "4", + "description": "Defines the maximum number of lanes to be tested. Valid settings are 1, 2 and 4. Default setting is 4.\n", + "enumerationVariants": "1\n2\n4", + "flag": 3, + "id": "TSI_DP_LTT_MAX_LANE_COUNT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Max lanes count supported by DUT", + "type": 4 + }, + { + "configId": "0x10702", + "defaultValue": "20", + "description": "Defines the maximum link rate to be tested. The setting is in multiplier of 0.27Gbps. Valid settings are 6, 10, 20 and 30. Default setting is 20.", + "enumerationVariants": "6 # 1.62 Gbps\n10 # 2.7 Gbps\n20 # 5.4 Gbps\n30 # 8.1Gbps", + "flag": 3, + "id": "TSI_DP_LTT_MAX_RATE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Max lane rate supported by DUT", + "type": 4 + }, + { + "configId": "0x10705", + "defaultValue": "1000", + "description": "Defines the length of the HPD pulse used to start each test iteration, in milliseconds. Default setting is 1000ms.", + "flag": 3, + "id": "TSI_DP_LTT_HPD_PULSE_DURATION", + "maxValue": 2147483647, + "minValue": 0, + "name": "Long HPD pulse duration, in milliseconds", + "type": 4 + }, + { + "configId": "0x10706", + "defaultValue": "5000", + "description": "Defines how long the test waits for LT start after issuing HPD pulse, in milliseconds. Default setting is 5000ms", + "flag": 3, + "id": "TSI_DP_LTT_LT_START_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Link training start timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x10707", + "defaultValue": "3000", + "description": "Defines the additional delay inserted in between test iterations, in milliseconds. Default setting is 3000ms.", + "flag": 3, + "id": "TSI_DP_LTT_TEST_LOOP_DELAY", + "maxValue": 2147483647, + "minValue": 0, + "name": "Delay between test cycles, in milliseconds", + "type": 4 + }, + { + "configId": "0x10703", + "defaultValue": "0", + "description": "Reserved for DUT Capabilities flags and DUT Test automation capabilities flags.", + "bitList": + [ + {"description": "0x10703", "defaultValue": "0"}, + {"description": "0x10704", "defaultValue": "0"} + ], + "flag": 3, + "id": "RESERVED", + "name": "Reserved", + "type": 4 + } + ], + "id": "0x04", + "name": "Link Config Tests", + "old_names": [ + "Link Test Set" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x05]src_dut_dp_electrical.json b/UniTAP/dev/modules/dut_tests/cfg/[0x05]src_dut_dp_electrical.json new file mode 100644 index 0000000..b1c4a9e --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x05]src_dut_dp_electrical.json @@ -0,0 +1,222 @@ +{ + "descriptions": [ + { + "configId": "0x10100", + "defaultValue": "5000", + "description": "Timeout period used for all DP RX electrical tests, in milliseconds. Default timeout is 5000ms.\n", + "flag": 3, + "id": "TSI_DP_RX_TEST_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x10101", + "defaultValue": "2600", + "description": "These two CI’s define the acceptable voltage range DP link lines. The measured voltage must be higher than TSI_DP_RX_LINKS_LOW_VOLTAGE setting, and lower than TSI_DP_RX_LINKS_HI_VOLTAGE setting in order to pass test. Default setting for low voltage limit is 2600mV, and for high voltage limit 4000mV.\n", + "flag": 3, + "id": "TSI_DP_RX_LINKS_LOW_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "Main link low voltage limit, mV", + "type": 0 + }, + { + "configId": "0x10102", + "defaultValue": "4000", + "description": "These two CI’s define the acceptable voltage range DP link lines. The measured voltage must be higher than TSI_DP_RX_LINKS_LOW_VOLTAGE setting, and lower than TSI_DP_RX_LINKS_HI_VOLTAGE setting in order to pass test. Default setting for low voltage limit is 2600mV, and for high voltage limit 4000mV.\n", + "flag": 3, + "id": "TSI_DP_RX_LINKS_HI_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "Main link high voltage limit, mV", + "type": 0 + }, + { + "configId": "0x10103", + "defaultValue": "-100", + "description": "These to CI’s define the acceptable voltage range for HDP line when it is in logical zero state. The measured voltage must be higher than TSI_DP_RX_HDP_ZERO_LOW_VOLTAGE setting, and lower than TSI_DP_RX_HPD_ZERO_HI_VOLTAGE setting in order to pass test. Default setting for low voltage limit is -100mV, and for high voltage limit 800mV.\n", + "flag": 3, + "id": "TSI_DP_RX_HPD_ZERO_LOW_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "HPD line logical zero low voltage level limit, mV", + "type": 0 + }, + { + "configId": "0x10104", + "defaultValue": "800", + "description": "These to CI’s define the acceptable voltage range for HDP line when it is in logical zero state. The measured voltage must be higher than TSI_DP_RX_HDP_ZERO_LOW_VOLTAGE setting, and lower than TSI_DP_RX_HPD_ZERO_HI_VOLTAGE setting in order to pass test. Default setting for low voltage limit is -100mV, and for high voltage limit 800mV.\n", + "flag": 3, + "id": "TSI_DP_RX_HPD_ZERO_HI_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "HPD line logical zero high voltage level limit, mV", + "type": 0 + }, + { + "configId": "0x10105", + "defaultValue": "800", + "description": "These two CI’s define the acceptable voltage range for HPD line when it is in logical one state. The measured voltage must be higher than TSI_DP_RX_HDP_ONE_LOW_VOLTAGE setting, and lower than TSI_DP_RX_HDP_ONE_HI_VOLTAGE setting in order to pass test. Default setting for low voltage limit is 2000mV, and for high voltage limit 5500mV.\n", + "flag": 3, + "id": "TSI_DP_RX_HPD_ONE_LOW_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "HPD line logical one low voltage level limit, mV", + "type": 0 + }, + { + "configId": "0x10106", + "defaultValue": "5500", + "description": "These two CI’s define the acceptable voltage range for HPD line when it is in logical one state. The measured voltage must be higher than TSI_DP_RX_HDP_ONE_LOW_VOLTAGE setting, and lower than TSI_DP_RX_HDP_ONE_HI_VOLTAGE setting in order to pass test. Default setting for low voltage limit is 800mV, and for high voltage limit 5500mV.", + "flag": 3, + "id": "TSI_DP_RX_HPD_ONE_HI_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "HPD line logical one high voltage level limit, mV", + "type": 0 + }, + { + "configId": "0x10107", + "defaultValue": "20", + "description": "These two CI’s define the acceptable AUX+ line idle voltage range when the AUX is idle. The measured voltage must be higher than TSI_DP_RX_AUX_P_IDLE_LOW_VOLTAGE setting, and lower than TSI_DP_RX_AUX_P_IDLE_HI_VOLTAGE setting in order to pass test. Default setting for low voltage limit is 2400mV, and for high voltage limit 2600mV.", + "flag": 3, + "id": "TSI_DP_RX_AUX_P_IDLE_LOW_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "AUX + line idle low voltage level limit, mV", + "type": 0 + }, + { + "configId": "0x10108", + "defaultValue": "500", + "description": "These two CI’s define the acceptable AUX+ line idle voltage range when the AUX is idle. The measured voltage must be higher than TSI_DP_RX_AUX_P_IDLE_LOW_VOLTAGE setting, and lower than TSI_DP_RX_AUX_P_IDLE_HI_VOLTAGE setting in order to pass test. Default setting for low voltage limit is 3600mV, and for high voltage limit 2600mV.\n", + "flag": 3, + "id": "TSI_DP_RX_AUX_P_IDLE_HI_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "AUX + line idle high voltage level limit, mV", + "type": 0 + }, + { + "configId": "0x10109", + "defaultValue": "2600", + "description": "These two CI’s defined the acceptable AUX- line idle voltage range when the AUX is idle. The measured voltage must be higher than TSI_DP_RX_AUX_N_IDLE_LOW_VOLTAGE setting, and lower than TSI_DP_RX_AUX_N_IDLE_HI_VOLTAGE setting in order to pass test. Default setting for low voltage limit is 2400mV, and for high voltage limit 3600mV.", + "flag": 3, + "id": "TSI_DP_RX_AUX_N_IDLE_LOW_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "AUX - line idle low voltage level limit, mV", + "type": 0 + }, + { + "configId": "0x1010a", + "defaultValue": "3600", + "description": "These two CI’s defined the acceptable AUX- line idle voltage range when the AUX is idle. The measured voltage must be higher than TSI_DP_RX_AUX_N_IDLE_LOW_VOLTAGE setting, and lower than TSI_DP_RX_AUX_N_IDLE_HI_VOLTAGE setting in order to pass test. Default setting for low voltage limit is 3600mV, and for high voltage limit 3600mV.", + "flag": 3, + "id": "TSI_DP_RX_AUX_N_IDLE_HI_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "AUX - line idle high voltage level limit, mV", + "type": 0 + }, + { + "configId": "0x1010b", + "defaultValue": "150", + "description": "These two CI’s define the AUX+ (TSI_DP_RX_AUX_P_TRIG_VOLTAGE) and AUX(TSI_DP_RX_AUX_N_TRIG_VOLTAGE) line state change trigger levels. Default settings are for AUX+ 150mV and for AUX- 200mV.", + "flag": 3, + "id": "TSI_DP_RX_AUX_P_TRIG_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "AUX + line signal trigger level, mV", + "type": 0 + }, + { + "configId": "0x1010c", + "defaultValue": "200", + "description": "These two CI’s define the AUX+ (TSI_DP_RX_AUX_P_TRIG_VOLTAGE) and AUX(TSI_DP_RX_AUX_N_TRIG_VOLTAGE) line state change trigger levels. Default settings are for AUX+ 150mV and for AUX- 200mV.", + "flag": 3, + "id": "TSI_DP_RX_AUX_N_TRIG_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "AUX - line signal trigger level, mV", + "type": 0 + }, + { + "configId": "0x1010d", + "defaultValue": "200", + "description": "Timeout for AUX signal capture, in milliseconds. When the TE generates a HPD pulse during test, it waits for this amount of time (max.) for DUT to read DPCD locations 0x200 to 0x205. If this transaction is not seen, the test will fail. Default setting is 200ms.\n", + "flag": 3, + "id": "TSI_DP_RX_AUX_SIGNAL_CAPT_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "AUX signal capture timeout, milliseconds", + "type": 4 + }, + { + "configId": "0x1010e", + "defaultValue": "5", + "description": "Retry count for AUX signal capture. If the AUX signal capture after TE generated a HPD pulse fails, the TE will re-try this many times. Default setting is 5.\n", + "flag": 3, + "id": "TSI_DP_RX_AUX_SIGNAL_CAPT_TRIES", + "maxValue": 2147483647, + "minValue": 0, + "name": "AUX signal capture attempts, times", + "type": 4 + }, + { + "configId": "0x1010f", + "defaultValue": "4", + "description": "Maximum number of lanes supported by the connected DUT. Typical values are 1, 2 or 4. Default setting is 4.", + "flag": 3, + "id": "TSI_DP_RX_MAX_DUT_MAX_LANES", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum lanes count supported by DUT", + "type": 4 + }, + { + "configId": "0x10110", + "defaultValue": "20", + "description": "Maximum link rate supported by the connected DUT, as multiplier of 0.27Gbps. Typical values are 6 (RBR), 10 (HBR), 20 (HBR-2) or 30 (HBR-3). Please note that HBR3 speed is not supported on all TE devices.\n", + "flag": 3, + "id": "TSI_DP_RX_MAX_DUT_LANE_RATE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum data rate supported by DUT in 0.27Gbps", + "type": 4 + }, + { + "configId": "0x10111", + "defaultValue": "0", + "description": "DUT Capabilities flags", + "flag": 3, + "id": "TSI_DP_RX_DUT_CAPS", + "maxValue": 2147483647, + "minValue": 0, + "name": "DUT Capabilities flags", + "type": 4 + }, + { + "configId": "0x10112", + "defaultValue": "0x00000000", + "description": "DUT Test automation capabilities flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "DUT is capable for test link training"}, + {"mask": "0x00000002", "description": "DUT is capable for test video pattern"}, + {"mask": "0x00000004", "description": "DUT is capable for test EDID read"} + ], + "flag": 3, + "id": "TSI_DP_RX_DUT_TA_CAPS", + "name": "DUT Test automation capabilities flags.", + "type": 12 + } + ], + "id": "0x05", + "name": "Electrical Tests", + "old_names": [ + "DP Electrical Test Set" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x06]src_dut_hdmi_electrical.json b/UniTAP/dev/modules/dut_tests/cfg/[0x06]src_dut_hdmi_electrical.json new file mode 100644 index 0000000..32a3fa4 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x06]src_dut_hdmi_electrical.json @@ -0,0 +1,174 @@ +{ + "descriptions": [ + { + "configId": "0x10200", + "defaultValue": "5000", + "description": "Timeout period used for all HDMI RX electrical tests, in milliseconds. Default timeout is 5000ms.\n", + "flag": 3, + "id": "TSI_HDMI_RX_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x10201", + "defaultValue": "4700", + "description": "HDMI power line voltage low limit, in millivolts. The voltage detected from HDMI power line must be higher than this value in order to pass tests. Default setting is 4700mV.\n", + "flag": 3, + "id": "TSI_HDMI_RX_POWER_LOW_LIMIT", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "Power line low voltage limit, mV", + "type": 0 + }, + { + "configId": "0x10202", + "defaultValue": "5300", + "description": "HDMI power line voltage high limit, in millivolts. The voltage detected from HDMI power line must be less than this value in order to pass tests. Default setting is 5300mV.\n", + "flag": 3, + "id": "TSI_HDMI_RX_POWER_HIGH_LIMIT", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "Power line high voltage limit, mV", + "type": 0 + }, + { + "configId": "0x10203", + "defaultValue": "2900", + "description": "HDMI link line voltage low limit, in millivolts. The voltage detected from HDMI link line(s) during test must be higher than this value in order to pass test. Default setting is 2900mV. Important: The acceptable setting for this value can be different for different types of DUT’s. Proper calibration of this value will require testing multiple DUT’s of same type in order to find typical value for the DUT in question.\n", + "flag": 3, + "id": "TSI_HDMI_RX_LINK_LOW_LIMIT", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "Main link low voltage limit, mV", + "type": 0 + }, + { + "configId": "0x10204", + "defaultValue": "3100", + "description": "HDMI link line voltage high limit, in millivolts. The voltage detected from HDMI link line(s) during test must be less than this value in order to pass test. Default setting is 3100mV. Important: The acceptable setting for this value can be different for different types of DUT’s. Proper calibration of this value will require testing multiple DUT’s of same type in order to find typical value for the DUT in question.\n", + "flag": 3, + "id": "TSI_HDMI_RX_LINK_HIGH_LIMIT", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "Main link high voltage limit, mV", + "type": 0 + }, + { + "configId": "0x10205", + "defaultValue": "-50", + "description": "HDMI HPD logical zero voltage level, lower limit, in millivolts. When HPD line is expected to be in logical zero state, the measured voltage must be higher than this value in order to pass test. Default setting is 0mV.\n", + "flag": 3, + "id": "TSI_HDMI_RX_HPD_ZERO_LOW_LIMIT", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "HPD line logical zero low voltage limit, mV", + "type": 0 + }, + { + "configId": "0x10206", + "defaultValue": "400", + "description": "HDMI HPD logical zero voltage level, higher limit, in millivolts. When HDP line is expected to be in logical zero state, the measured voltage must be lower than this value in order to pass test. Default setting is 400mV.", + "flag": 3, + "id": "TSI_HDMI_RX_HPD_ZERO_HIGH_LIMIT", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "HPD line logical zero high voltage limit, mV", + "type": 0 + }, + { + "configId": "0x10207", + "defaultValue": "2400", + "description": "HDMI HPD logical one voltage level, lower limit, in millivolts. When HPD line is expected to be in logical one state, the measured voltage must be less than this value in order to pass test. Default setting is 2400mV.", + "flag": 3, + "id": "TSI_HDMI_RX_HPD_ONE_LOW_LIMIT", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "HPD line logical one low voltage limit, mV", + "type": 0 + }, + { + "configId": "0x10208", + "defaultValue": "5300", + "description": "HDMI HPD logical one voltage level, higher limit, in millivolts. When HPD line is expected to be in logical one state, the measured voltage must be less than this value in order to pass test. Default setting is 5300mV.", + "flag": 3, + "id": "TSI_HDMI_RX_HPD_ONE_HIGHT_LIMIT", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "HPD line logical one high voltage limit, mV", + "type": 0 + }, + { + "configId": "0x10209", + "defaultValue": "4500", + "description": "DDC Line voltage low limit, in millivolts. Test will measure DDC line voltage when the line is not being driven low. The measured value must be higher than this value in order to pass test. Default setting is 4500mV.", + "flag": 3, + "id": "TSI_HDMI_RX_DDC_LOW_LIMIT", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "DDC lines low voltage limit, mV", + "type": 0 + }, + { + "configId": "0x1020a", + "defaultValue": "5500", + "description": "DDC Line voltage high limit, in millivolts. Test will measure DDC line voltage when the line is not being driven low. The measured value must be lower than this value in order to pass test. Default setting is 5500mV.", + "flag": 3, + "id": "TSI_HDMI_RX_DDC_HIGH_LIMIT", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "DDC lines high voltage limit, mV", + "type": 0 + }, + { + "configId": "0x1020b", + "defaultValue": "-50", + "description": "CCE Line logical zero voltage level, lower limit, in millivolts. The CCE line voltage is measured when CCE line state is logical zero. The measured value must be higher than this value in order to pass test. Default setting is 0mV.", + "flag": 3, + "id": "TSI_HDMI_RX_CEC_ZERO_LOW_LIMIT", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "CEC line logical zero low voltage limit, mV", + "type": 0 + }, + { + "configId": "0x1020c", + "defaultValue": "600", + "description": "CCE Line logical zero voltage level, higher limit, in millivolts. The CCE line voltage is measured when CCE line state is logical zero. The measured value must be lower than this value in order to pass test. Default setting is 600mV.", + "flag": 3, + "id": "TSI_HDMI_RX_CEC_ZERO_HIGH_LIMIT", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "CEC line logical zero high voltage limit, mV", + "type": 0 + }, + { + "configId": "0x1020d", + "defaultValue": "2500", + "description": "CCE Line logical one voltage level, lower limit, in millivolts. The CCE line voltage is measured when CCE line state is logical one. The measured value must be higher than this setting in order to pass test. Default setting is 2500mV.", + "flag": 3, + "id": "TSI_HDMI_RX_CEC_ONE_LOW_LIMIT", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "CEC line logical one low voltage limit, mV", + "type": 0 + }, + { + "configId": "0x1020e", + "defaultValue": "3600", + "description": "CCE Line logical one voltage level, higher limit, in millivolts. The CCE line voltage is measured when CCE line state is logical one. The measured value must be lower that this setting in order to pass test. Default setting is 3600mV.", + "flag": 3, + "id": "TSI_HDMI_RX_CEC_ONE_HIGH_LIMIT", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "CEC line logical one high voltage limit, mV", + "type": 0 + } + ], + "id": "0x06", + "name": "Electrical Tests", + "old_names": [ + "HDMI Electrical Test Set" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x07]any_dut_usbc_electrical.json b/UniTAP/dev/modules/dut_tests/cfg/[0x07]any_dut_usbc_electrical.json new file mode 100644 index 0000000..1ec19a2 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x07]any_dut_usbc_electrical.json @@ -0,0 +1,300 @@ +{ + "descriptions": [ + { + "configId": "0x10500", + "defaultValue": "5000", + "description": "Defines the test activity maximum run-time, in milliseconds. Default setting is 5000ms. Important: When the test is waiting for DUT with a max. delay this timeout is not advancing during the wait.", + "flag": 3, + "id": "TSI_USBC_EL_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x10501", + "defaultValue": "0x0000000F", + "description": "Defines DUT capabilities.", + "bitList": + [ + {"mask": "0x00000001", "description": "DUT Support DisplayPort Alternate mode"}, + {"mask": "0x00000002", "description": "DUT can act as a power source"}, + {"mask": "0x00000004", "description": "DUT can act as a power sink"}, + {"mask": "0x00000008", "description": "DUT does not support PD Contract"} + ], + "flag": 3, + "id": "TSI_USBC_EL_DUT_CAPS", + "name": "Defines DUT USBC capabilities.", + "type": 12 + }, + { + "configId": "0x10502", + "defaultValue": "1500", + "description": "Defines the time period for USB Type-C re-plug simulation “disconnected” state. The period is defined in milliseconds. Default value is 1500ms.", + "flag": 3, + "id": "TSI_USBC_EL_REPLUG_TIME", + "maxValue": 2147483647, + "minValue": 0, + "name": "Re-plug duration, in milliseconds", + "type": 4 + }, + { + "configId": "0x10503", + "defaultValue": "10000", + "description": "Defines the time period that the TE will wait for DUT to complete power contract negotiation. Time is defined in milliseconds. Default value is 5000ms.\n", + "flag": 3, + "id": "TSI_USBC_EL_DUT_ATTACH_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "DUT attach timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x10504", + "defaultValue": "5000", + "description": "Defines power contract timeout, in milliseconds", + "flag": 3, + "id": "TSI_USBC_EL_PWR_CONTRACT_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Power Contract timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x10505", + "defaultValue": "261", + "description": "Defines the low limit for the voltage window when power sink current is 0.5A or 0.9A. The limit is defined in millivolts (mV). Default setting is 261mV.\n", + "flag": 3, + "id": "TSI_USBC_EL_CC_LOW_VOLTAGE_1", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "CC low voltage limit for default current (0.5A/0.9A), in mV", + "type": 0 + }, + { + "configId": "0x10506", + "defaultValue": "588", + "description": "Defines the high limit for the voltage window when power sink current is 0.5A or 0.9A. The limit is defined in millivolts (mV). Default settingf is 588mV.\n", + "flag": 3, + "id": "TSI_USBC_EL_CC_HI_VOLTAGE_1", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "CC high voltage limit for default current (0.5A/0.9A), in mV", + "type": 0 + }, + { + "configId": "0x10507", + "defaultValue": "675", + "description": "Defines the low limit for the voltage window when power sink current is 1.5A. The limit is defined in millivolts (mV). Default setting is 675mV.", + "flag": 3, + "id": "TSI_USBC_EL_CC_LOW_VOLTAGE_2", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "CC low voltage limit for 1.5A current, in mV", + "type": 0 + }, + { + "configId": "0x10508", + "defaultValue": "1189", + "description": "Defines the high limit for the voltage window when power sink current is 1.5A. The limit is defined in millivolts (mV). Default setting is 1189mV.", + "flag": 3, + "id": "TSI_USBC_EL_CC_HI_VOLTAGE_2", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "CC high voltage limit for 1.5A current, in mV", + "type": 0 + }, + { + "configId": "0x10509", + "defaultValue": "1238", + "description": "Defines the low limit for the voltage window when power sink current is 3.0A. The limit is defined in millivolts (mV). Default setting is 1238mV.\n", + "flag": 3, + "id": "TSI_USBC_EL_CC_LOW_VOLTAGE_3", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "CC low voltage limit for 3.0A, in mV", + "type": 0 + }, + { + "configId": "0x1050a", + "defaultValue": "2181", + "description": "Defines the high limit for the voltage window when power sink current is 3.0A. The limit is defined in millivolts (mV). Default setting is 2181mV.\n", + "flag": 3, + "id": "TSI_USBC_EL_CC_HI_VOLTAGE_3", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "CC high voltage limit for 3.0A, in mV", + "type": 0 + }, + { + "configId": "0x1050b", + "defaultValue": "4750", + "description": "Defines the low limit for the Vcon voltage window. The limit is defined in millivolts (mV). Default setting is 4750mV.\n", + "flag": 3, + "id": "TSI_USBC_EL_VCON_LOW_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "Vconn low voltage limit, in mV", + "type": 0 + }, + { + "configId": "0x1050c", + "defaultValue": "5500", + "description": "Defines the high limit for the Vcon voltage window. The limit is defined in millivolts (mV). Default setting is 5500mV.", + "flag": 3, + "id": "TSI_USBC_EL_VCON_HI_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "Vconn high voltage limit, in mV", + "type": 0 + }, + { + "configId": "0x1050d", + "defaultValue": "5000", + "description": "Defines the timeout the TE will wait for the DUT to enter into DisplayPort alternate mode. The timeout is defined in milliseconds. Default setting is 5000ms.\n ", + "flag": 3, + "id": "TSI_USBC_EL_DP_ALT_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "DP Alt timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x1050e", + "defaultValue": "100", + "description": "Defines the low voltage limit for the positive DP AUX line when idle. The limit is defined in millivolts (mV). Default setting 100mV.\n", + "flag": 3, + "id": "TSI_USBC_EL_AUX_P_IDLE_LOW_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "DP Alt Mode AUX + line idle low voltage limit, signed, mV", + "type": 0 + }, + { + "configId": "0x1050f", + "defaultValue": "600", + "description": "Defines the high voltage limit for the positive DP AUX line when idle. The limit is defined in millivolts (mV). Default setting is 600mV.", + "flag": 3, + "id": "TSI_USBC_EL_AUX_P_IDLE_HI_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "DP Alt Mode AUX + line idle high voltage limit, signed, mV", + "type": 0 + }, + { + "configId": "0x10510", + "defaultValue": "2500", + "description": "Defines the low voltage limit for the negative DP AUX line when idle. The limit is defined in millivolts (mV). Default setting is 2500mV.", + "flag": 3, + "id": "TSI_USBC_EL_AUX_N_IDLE_LOW_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "DP Alt Mode AUX - line idle low voltage limit, signed, mV", + "type": 0 + }, + { + "configId": "0x10511", + "defaultValue": "3000", + "description": "Defines the high voltage limit for the negative DP AUX line when idle. The limit is defined in millivolts (mV). Default setting is 3000mV", + "flag": 3, + "id": "TSI_USBC_EL_AUX_N_IDLE_HI_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "DP Alt Mode AUX - line idle high voltage limit, signed, mV", + "type": 0 + }, + { + "configId": "0x10512", + "defaultValue": "4750", + "description": "Defines the low limit for Vbus voltage window. The limit is defined in millivolts (mV). Default setting is 4750mV.", + "flag": 3, + "id": "TSI_USBC_EL_VBUS_LOW_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "Vbus voltage low limit, mV", + "type": 0 + }, + { + "configId": "0x10513", + "defaultValue": "5500", + "description": "Defines the high limit for Vbus voltage window. The limit is defined in millivolts (mV). Default setting is 5500mV.", + "flag": 3, + "id": "TSI_USBC_EL_VBUS_HI_VOLTAGE", + "maxValue": 2147483647, + "minValue": -2147483647, + "name": "Vbus voltage high limit, mV", + "type": 0 + }, + { + "configId": "0x10514", + "defaultValue": "100", + "description": "Defines the highest allowed deviation between maximum and minimum currents measured from the individual Vbus pins as per-mill (‰) of total measured current. This means that if the total measured current is 3000mA, and the setting 100, the maximum difference that is allowed between maximum and minimum currents is 300mA. Default setting is 100‰.", + "flag": 3, + "id": "TSI_USBC_EL_VBUS_CURRENT_MAX_DEV", + "maxValue": 2147483647, + "minValue": 0, + "name": "Vbus current deviation (between wires), mA", + "type": 4 + }, + { + "configId": "0x10515", + "defaultValue": "100", + "description": "Defines the highest allowed deviation between maximum and minimum currents measured from the individual GND pins as per-mill (‰) of total measured current. This means that if the total measured current is 3000mA, and the setting 100, the maximum difference that is allowed between maximum and minimum currents is 300mA. Default setting is 100‰.", + "flag": 3, + "id": "TSI_USBC_EL_GND_CURRENT_MAX_DEV", + "maxValue": 2147483647, + "minValue": 0, + "name": "Return (GND) current deviation (between wires), mA", + "type": 4 + }, + { + "configId": "0x10516", + "defaultValue": "2000", + "description": "Defines delay from end of power contract negotiation to voltage / current measurements. The delay is defined in milliseconds, and the default setting is 2000ms\n", + "flag": 3, + "id": "TSI_USBC_EL_PWR_MEASURE_DELAY", + "maxValue": 2147483647, + "minValue": 0, + "name": "Measurement delay, msec", + "type": 4 + }, + { + "configId": "0x10517", + "defaultValue": "5", + "description": "Defines the minimum current, in mA, that a Power Sink DUT must use in order to pass the test. Set this value to zero (0) to disable minimum current check. Default value is 5.", + "flag": 3, + "id": "TSI_USBC_EL_MIN_DUT_CURRENT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Minimum current, mA", + "type": 4 + }, + { + "configId": "0x10518", + "defaultValue": "0", + "description": "Delay after load resistor on, milliseconds. This helps to skip transient processes.\n", + "flag": 3, + "id": "TSI_USBC_EL_RES_ON_DELAY", + "maxValue": 2147483647, + "minValue": 0, + "name": "Delay after connection load resistors, ms", + "type": 4 + }, + { + "configId": "0x10519", + "defaultValue": "0", + "description": "Delay for measure CC lines voltage after DUT plug detection, milliseconds. For Pegatron factory default value is 1500 ms.\n", + "flag": 3, + "id": "TSI_USBC_EL_CC_MEASURE_DELAY", + "maxValue": 2147483647, + "minValue": 0, + "name": "Delay for measure CC lines voltage after DUT plug detection, milliseconds", + "type": 4 + } + ], + "id": "0x07", + "name": "USB-C Electrical Tests", + "old_names": [ + "USBC Electrical Tests" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x08]src_dut_dp_128b132b_ll_cts.json b/UniTAP/dev/modules/dut_tests/cfg/[0x08]src_dut_dp_128b132b_ll_cts.json new file mode 100644 index 0000000..9368997 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x08]src_dut_dp_128b132b_ll_cts.json @@ -0,0 +1,2917 @@ +{ + "descriptions": [ + { + "configId": "0x1190000", + "defaultValue": "5000", + "description": "Defines the timeout for DP LL CTS Source DUT tests. The timeout is defined as milliseconds, and has a default setting of 5000ms. Please note that if the test contains internal iterations, this timeout is re-used for each iteration. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP20_SRCCTS_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout in milliseconds.", + "type": 4 + }, + { + "configId": "0x1190001", + "defaultValue": "4", + "description": "Defines the maximum number of lanes supported by the DUT. Typical settings are 1, 2 and 4. Default value is 4. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.\n", + "enumerationVariants": "1\n2\n4", + "flag": 3, + "id": "TSI_DP20_SRCCTS_MAX_LANES", + "maxValue": 2147480000, + "minValue": 0, + "name": "Maximum number of lanes supported by the DUT.", + "type": 4 + }, + { + "configId": "0x1190002", + "defaultValue": "30", + "description": "Defines the maximum link rate as multiplier for 0.27Gbps. Typical settings are 6 (RBR), 10 (HBR), 20 (HBR2) and 30 (HBR3). Notice that HBR3 link rate is usable only with UCD-400. The default setting is 30 (HBR3). Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "6 # RBR (1.62 Gbps)\n10 # HBR (2.7 Gbps)\n20 # HBR2 (5.4 Gbps)\n30 # HBR3 (8.10 Gbps)", + "flag": 3, + "id": "TSI_DP20_SRCCTS_MAX_LINK_RATE", + "maxValue": 30, + "minValue": 6, + "name": "Maximum link rate supported by the DUT as multiplier for 0.27Gbps.", + "type": 4 + }, + { + "configId": "0x1190003", + "defaultValue": "0x00F03A53", + "description": "Defines the DUT capabilities as flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Voltage swing level 3 (1.2V) supported"}, + {"mask": "0x00000002", "description": "Pre-emphasis level 3 (9.5dB) supported"}, + {"mask": "0x00000004", "description": "Fixed timing DUT supported"}, + {"mask": "0x00000008", "description": "Spread Spectrum support"}, + {"mask": "0x00000010", "description": "Video format change without LT supported"}, + {"mask": "0x00000040", "description": "E-DDC protocol supported"}, + {"mask": "0x00000080", "description": "Audio Info Frame for 2 channel audio transmission supported"}, + {"mask": "0x00000100", "description": "DUT is Type-C device"}, + {"mask": "0x00000200", "description": "FEC supported"}, + {"mask": "0x00000400", "description": "FEC disable sequence supported"}, + {"mask": "0x00000800", "description": "Audio without Video supported"}, + {"mask": "0x00001000", "description": "DSC supported"}, + {"mask": "0x00002000", "description": "DSC block prediction supported"}, + {"mask": "0x00004000", "description": "Max Link Bandwidth Policy supported"}, + {"mask": "0x00008000", "description": "Use YCbCr 444->422/420 3-tap filter"}, + {"mask": "0x00020000", "description": "USB4 tunnel presented"}, + {"mask": "0x00080000", "description": "DUT supports native Display ID read"}, + {"mask": "0x00100000", "description": "DisplayID Type VII Detailed Timing Descriptor supported"}, + {"mask": "0x00200000", "description": "DisplayID Type VIII Detailed Timing Descriptor supported"}, + {"mask": "0x00400000", "description": "DisplayID Type IX Detailed Timing Descriptor supported"}, + {"mask": "0x00800000", "description": "DisplayID Type X Detailed Timing Descriptor supported"}, + {"mask": "0x01000000", "description": "2x1 tiled display and DisplayID Tiled Display Topology data block supported"}, + {"mask": "0x02000000", "description": "Field sequential stereo and DisplayID Tiled Stereo Display Interface data block supported"}, + {"mask": "0x04000000", "description": "Stacked frame stereo and DisplayID Tiled Stereo Display Interface data block supported"}, + {"mask": "0x08000000", "description": "Dynamic Refresh Rate with VBlank stretch with MSA_TIMING_PAR_IGNORED supported"} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_DUT_CAPS", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x1190004", + "defaultValue": "0x0000001A", + "description": "Defines the DUT Test automation capabilities as flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "TEST_LINK_TRAINING"}, + {"mask": "0x00000002", "description": "TEST_EDID_READ"}, + {"mask": "0x00000004", "description": "TEST_VIDEO_PATTERN"}, + {"mask": "0x00000020", "description": "TEST_AUDIO_PATTERN"}, + {"mask": "0x00000018", "description": "Event indicating DUT ready", "enumerationVariants": "0 # Always ready\n1 # EDID read\n2 # Link Training end\n3 # Active Video"}, + {"mask": "0x00000040", "description": "VIDEO_OPERATOR_INPUT"}, + {"mask": "0x00000080", "description": "DSC_VIS_VAL"} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_DUT_TA", + "name": "DUT Test automation flags.", + "type": 12 + }, + { + "configId": "0x1190005", + "defaultValue": "1000", + "description": "Defines the duration of long HPD pulses generated by the tests. The duration is defined in millieconds. Default setting is 1000ms. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.\n", + "flag": 3, + "id": "TSI_DP20_SRCCTS_LONG_HPD_PULSE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Long HPD pulse duration, in milliseconds.", + "type": 4 + }, + { + "configId": "0x1190006", + "defaultValue": "5000", + "description": "Defines a timeout for Link Training start, in milliseconds. Default setting is 5000ms. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP20_SRCCTS_LT_START_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Link Training start timeout, in milliseconds.", + "type": 4 + }, + { + "configId": "0x1190007", + "defaultValue": "5000", + "description": "Defines an internal cycle delay used within tests that repeat a number of test steps. The delay is defined in milliseconds. Default setting is 3000ms. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_TEST_CYCLE_DELAY", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test cycle delay, in milliseconds", + "type": 4 + }, + { + "configId": "0x1190008", + "defaultValue": "2", + "description": "Defines number of color formats programmed into the CI’s TSI_DP20_SRCCTS_COLOR_MODE_0 to TSI_DP20_SRCCTS_COLOR_MODE_31. The default setting is 2. Important: The CTS Specification defines two mandatory color formats: 0x0 and 0x20. These color-format definitions must be present in the color mode table. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_FORMATS", + "maxValue": 2147483647, + "minValue": 0, + "name": "Number of valid color format definitions.", + "type": 4 + }, + { + "configId": "0x1190009", + "defaultValue": "1", + "description": "Defines the fail-safe mode ID. The default setting is 1 ( = 640 x 480 @ 60 Hz, 6 BPC). Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This setting should not be modified. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "enumerationVariants": "1 # 640 x 480 @ 60Hz 6 BPC", + "flag": 3, + "id": "TSI_DP20_SRCCTS_FAILSAFE_MODE", + "maxValue": 2147480000, + "minValue": 0, + "name": "Fail-safe video mode ID.", + "type": 4 + }, + { + "configId": "0x119000a", + "defaultValue": "22", + "description": "Defines the maximum resolution supported by the DUT. Default setting is 22 ( = 1920 x 1080 @ 60Hz, 8 BPC). Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC\n5 # 1920 x 1080 @ 60Hz 8 BPC\n6 # 1920 x 1440 @ 60Hz 8 BPC\n7 # 1920 x 1080 @ 120Hz 8 BPC\n8 # 1280 x 800 @ 60Hz RB1 6 BPC\n9 # 1280 x 768 @ 60Hz RB1 6 BPC\n10 # 800 x 600 @ 60Hz 10 BPC\n11 # 1024 x 768 @ 60Hz 10 BPC\n12 # 1280 x 1024 @ 60Hz 8 BPC\n13 # 1360 x 768 @ 60Hz 10 BPC\n14 # 1280 x 800 @ 60Hz RB1 10 BPC\n15 # 1400 x 1050 @ 60Hz RB1 8 BPC\n16 # 1280 x 768 @ 60Hz RB1 10 BPC\n17 # 1600 x 1200 @ 60Hz RB1 6 BPC\n18 # 2048 x 1536 @ 60Hz RB1 8 BPC\n19 # 1792 x 1344 @ 60Hz 8 BPC\n20 # 1600 x 1200 @ 60Hz RB1 10 BPC\n21 # 3840 x 2160 @ 30Hz 8 BPC\n22 # 3840 x 2160 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP20_SRCCTS_MAX_RESOLUTION", + "maxValue": 2147480000, + "minValue": 0, + "name": "Maximum resolution supported by the DUT.", + "type": 4 + }, + { + "configId": "0x119000b", + "defaultValue": "0x00000001", + "description": "Defines link rates supported by DUT as it defined in the DP 2.1 specification.\n ", + "flag": 3, + "bitList": + [ + {"mask": "0x00000001", "description": "10 Gbps per lane supported"}, + {"mask": "0x00000002", "description": "20 Gbps per lane supported"}, + {"mask": "0x00000004", "description": "13.5 Gbps per lane supported"} + ], + "id": "TSI_DP20_SRCCTS_DUT_LINK_RATES", + "name": "Link rates supported by DUT", + "type": 12 + }, + { + "configId": "0x119000c", + "defaultValue": "0", + "description": "Defines number of LTTPR devices present between Source DUT and Reference Sink. Up to the eight devices. Default setting is 0.\n ", + "flag": 3, + "id": "TSI_DP20_SRCCTS_LTTPR_DEVICE_COUNT", + "maxValue": 8, + "minValue": 0, + "name": "Number of LTTPR devices present between Source DUT and Reference Sink", + "type": 4 + }, + { + "configId": "0x119000d", + "defaultValue": "262", + "description": "Defines number of minimum link bandwidth supported by Source DUT. Default setting is 0x106 (1LRBR - 1 lane and 1.62 Gbpc).\n ", + "enumerationVariants": "262 # 1LRBR\n266 # 1LHBR\n518 # 2LRBR\n522 # 2LHBR/1LHBR2", + "flag": 3, + "id": "TSI_DP20_SRCCTS_MAX_LINK_BW_POLICY", + "maxValue": 2147480000, + "minValue": 0, + "name": "Number of minimum link bandwidth supported by Source DUT", + "type": 4 + }, + { + "configId": "0x119000e", + "defaultValue": "0x00000000", + "description": "Defines the Debug mode configuration.", + "bitList": + [ + {"mask": "0x00000001", "description": "Continue on fail enabled"}, + {"mask": "0x00000002", "description": "Force video check enabled"} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_DUT_DEBUG_CONF", + "name": "Debug mode configuration.", + "type": 12 + }, + { + "configId": "0x1190010", + "defaultValue": "32000", + "description": "Defines the minimum sample rate supported by the DUT in Hz. Allowed values are 32000, 44100, 48000, 88200, 96000, 176400 or 192000 Hz. Default setting is 32000 Hz.\n ", + "enumerationVariants": "32000\n44100\n48000\n88200\n96000\n176400\n192000", + "flag": 3, + "id": "TSI_DP20_SRCCTS_MIN_SAMPLE_RATE", + "maxValue": 2147480000, + "minValue": 0, + "name": "Minimum supported sample rate", + "type": 4 + }, + { + "configId": "0x1190011", + "defaultValue": "192000", + "description": "Defines the maximum sample rate supported by the DUT in Hz. Allowed values are 32000, 44100, 48000, 88200, 96000, 176400 or 192000 Hz. Default setting is 192000 Hz.\n ", + "enumerationVariants": "32000\n44100\n48000\n88200\n96000\n176400\n192000", + "flag": 3, + "id": "TSI_DP20_SRCCTS_MAX_SAMPLE_RATE", + "maxValue": 2147480000, + "minValue": 0, + "name": "Maximum supported sample rate", + "type": 4 + }, + { + "configId": "0x1190012", + "defaultValue": "2", + "description": "Defines the minimum number of channels for minimum sample rate. Default is 2 channels (Stereo). Highest valid channel count is 8. If the number is different from 2 channels, then the appropriate channel allocation must be provided through the CI 7.9.19 TSI_DP20_SRCCTS_CH_ALLOC1.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_CHANNELS1", + "maxValue": 8, + "minValue": 0, + "name": "Minimum number of channels for minimum sample rate", + "type": 4 + }, + { + "configId": "0x1190013", + "defaultValue": "1", + "description": "The audio parameters contain four pairs of audio channel count and channel allocation values. Please notice that maximum channel count allowed is 8. For two channels, the channel allocation of zero is considered valid; In this case, stereo is assumed (FL/FR).\nAllocation value\nChannel count/bit\nChannel assignments\n0x001 2 FL/FR. (Front Left, Front Right)\n0x002 1 LFE. (Low Frequency Effects)\n0x004 1 FC. (Front Center) \n0x008 2 RL/RR. (Rear Left, Rear Right).\n0x010 1 RC. (Rear Center). \n0x020 2 FLC/FRC. (Front Left off Center, Front Right off Center).\n0x040 2 RLC/RRC (Rear Left off Center, Rear Right off Center). \n0x080 2 FLW/FRW (Front Left Wide, Front Right Wide) \n0x100 2 FLH/FRH (Front Left High, Front Right High)\n0x200 1 TC (Top Center) \n0x400 1 FCH (Front Center High)\n", + "flag": 3, + "id": "TSI_DP20_SRCCTS_CH_ALLOC1", + "maxValue": 2147480000, + "minValue": 0, + "name": "Channel allocation bits", + "type": 4 + }, + { + "configId": "0x1190014", + "defaultValue": "6", + "description": "Defines the maximum number of channels for minimum sample rate. Default is 2 channels (Stereo). Highest valid channel count is 8. If the number is different from 2 channels, then the appropriate channel allocation must be provided through the CI 7.9.21 TSI_DP20_SRCCTS_CH_ALLOC2.\n ", + "flag": 3, + "id": "TSI_DP20_SRCCTS_CHANNELS2", + "maxValue": 8, + "minValue": 0, + "name": "Maximum number of channels for minimum sample rate", + "type": 4 + }, + { + "configId": "0x1190015", + "defaultValue": "15", + "description": "The audio parameters contain four pairs of audio channel count and channel allocation values. Please notice that maximum channel count allowed is 8. For two channels, the channel allocation of zero is considered valid; In this case, stereo is assumed (FL/FR).\nAllocation value\nChannel count/bit\nChannel assignments\n0x001 2 FL/FR. (Front Left, Front Right)\n0x002 1 LFE. (Low Frequency Effects)\n0x004 1 FC. (Front Center) \n0x008 2 RL/RR. (Rear Left, Rear Right).\n0x010 1 RC. (Rear Center). \n0x020 2 FLC/FRC. (Front Left off Center, Front Right off Center).\n0x040 2 RLC/RRC (Rear Left off Center, Rear Right off Center). \n0x080 2 FLW/FRW (Front Left Wide, Front Right Wide) \n0x100 2 FLH/FRH (Front Left High, Front Right High)\n0x200 1 TC (Top Center) \n0x400 1 FCH (Front Center High)\n", + "flag": 3, + "id": "TSI_DP20_SRCCTS_CH_ALLOC2", + "maxValue": 2147480000, + "minValue": 0, + "name": "Channel allocation bits 2", + "type": 4 + }, + { + "configId": "0x1190016", + "defaultValue": "2", + "description": "Defines the minimum number of channels for maximum sample rate. Default is 2 channels (Stereo). Highest valid channel count is 8. If the number is different from 2 channels, then the appropriate channel allocation must be provided through the CI 7.9.23 TSI_DP20_SRCCTS_CH_ALLOC3.\n ", + "flag": 3, + "id": "TSI_DP20_SRCCTS_CHANNELS3", + "maxValue": 8, + "minValue": 0, + "name": "Minimum number of channels for maximum sample rate", + "type": 4 + }, + { + "configId": "0x1190017", + "defaultValue": "1", + "description": "The audio parameters contain four pairs of audio channel count and channel allocation values. Please notice that maximum channel count allowed is 8. For two channels, the channel allocation of zero is considered valid; In this case, stereo is assumed (FL/FR).\nAllocation value\nChannel count/bit\nChannel assignments\n0x001 2 FL/FR. (Front Left, Front Right)\n0x002 1 LFE. (Low Frequency Effects)\n0x004 1 FC. (Front Center) \n0x008 2 RL/RR. (Rear Left, Rear Right).\n0x010 1 RC. (Rear Center). \n0x020 2 FLC/FRC. (Front Left off Center, Front Right off Center).\n0x040 2 RLC/RRC (Rear Left off Center, Rear Right off Center). \n0x080 2 FLW/FRW (Front Left Wide, Front Right Wide) \n0x100 2 FLH/FRH (Front Left High, Front Right High)\n0x200 1 TC (Top Center) \n0x400 1 FCH (Front Center High)\n", + "flag": 3, + "id": "TSI_DP20_SRCCTS_CH_ALLOC3", + "maxValue": 2147480000, + "minValue": 0, + "name": "Channel allocations bits 3", + "type": 4 + }, + { + "configId": "0x1190018", + "defaultValue": "6", + "description": "Defines the maximum number of channels for maximum sample rate. Default is 2 channels (Stereo). Highest valid channel count is 8. If the number is different from 2 channels, then the appropriate channel allocation must be provided through the CI 7.9.25 TSI_DP20_SRCCTS_CH_ALLOC4.\n ", + "flag": 3, + "id": "TSI_DP20_SRCCTS_CHANNELS4", + "maxValue": 8, + "minValue": 0, + "name": "Maximum number of channels for maximum sample rate", + "type": 4 + }, + { + "configId": "0x1190019", + "defaultValue": "15", + "description": "The audio parameters contain four pairs of audio channel count and channel allocation values. Please notice that maximum channel count allowed is 8. For two channels, the channel allocation of zero is considered valid; In this case, stereo is assumed (FL/FR).\nAllocation value\nChannel count/bit\nChannel assignments\n0x001 2 FL/FR. (Front Left, Front Right)\n0x002 1 LFE. (Low Frequency Effects)\n0x004 1 FC. (Front Center) \n0x008 2 RL/RR. (Rear Left, Rear Right).\n0x010 1 RC. (Rear Center). \n0x020 2 FLC/FRC. (Front Left off Center, Front Right off Center).\n0x040 2 RLC/RRC (Rear Left off Center, Rear Right off Center). \n0x080 2 FLW/FRW (Front Left Wide, Front Right Wide) \n0x100 2 FLH/FRH (Front Left High, Front Right High)\n0x200 1 TC (Top Center) \n0x400 1 FCH (Front Center High)\n", + "flag": 3, + "id": "TSI_DP20_SRCCTS_CH_ALLOC4", + "maxValue": 2147480000, + "minValue": 0, + "name": "Channel allocation bits 4", + "type": 4 + }, + { + "configId": "0x119001a", + "defaultValue": "0", + "description": "Defines the audio test pattern. Default setting is zero. Please see table below for allowed settings:\nValue Description \n0 Operator specific waveform \n1 Sawtooth waveform", + "enumerationVariants": "0 # Operator specific waveform\n1 # Sawtooth waveform", + "flag": 3, + "id": "TSI_DP20_SRCCTS_AUDIO_TEST_PATTERN", + "maxValue": 2147480000, + "minValue": 0, + "name": "Audio test pattern", + "type": 4 + }, + { + "configId": "0x119001b", + "defaultValue": "16", + "description": "Defines sample size used for EDID configuration while testing. The size is indicated as number of bits per sample. Default setting is 16. Valid settings are 16, 20 or 24.\n ", + "enumerationVariants": "16 # 16 bits / sample\n20 # 20 bits / sample\n24 # 24 bits / sample", + "flag": 3, + "id": "TSI_DP20_SRCCTS_EDID_SAMPLE_SIZE", + "maxValue": 2147480000, + "minValue": 0, + "name": "Sample size for EDID while testing", + "type": 4 + }, + { + "configId": "0x1190020", + "defaultValue": "9", + "description": "These three CI’s define the most packed timings for 1, 2 and 4 lane modes. Default for 1 lane is 8 ( = 1280 x 800 @ 60Hz RB1, 18BPP). Default 2 lanes is 12 ( = 1280 x 1024 @ 60Hz, 24BPP). Default for 4 lanes is 18 ( = 2048 x 1536 @ 60Hz RB1, 24BPP). Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n10 # DMT 800 x 600p @ 60.317Hz, 10 bpc\n11 # DMT 1024 x 768p @ 60Hz, 6 bpc\n8 # CVT 1280 x 800p @ 60Hz, RB1, 6 bpc\n9 # DMT 1280 x 768p @ 60Hz, RB1, 6 bpc\n23 # CTA 1440 x 480p @ 59.94Hz, 8 bpc\n24 # CTA 1440 x 576p @ 50Hz, 8 bpc", + "flag": 3, + "id": "TSI_DP20_SRCCTS_1L_MOST_PACKED", + "maxValue": 2147480000, + "minValue": 0, + "name": "Most packed video timing definitions for 1 lanes links", + "type": 4 + }, + { + "configId": "0x1190021", + "defaultValue": "15", + "description": "These three CI’s define the most packed timings for 1, 2 and 4 lane modes. Default for 1 lane is 8 ( = 1280 x 800 @ 60Hz RB1, 18BPP). Default 2 lanes is 12 ( = 1280 x 1024 @ 60Hz, 24BPP). Default for 4 lanes is 18 ( = 2048 x 1536 @ 60Hz RB1, 24BPP). Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n16 # DMT 1280 x 768p @ 60Hz, 10 bpc\n14 # CVT 1280 x 800p @ 60Hz, 10 bpc\n4 # DMT 1280 x 960p @ 60Hz, 8 bpc\n12 # DMT 1280 x 1024p @ 60Hz, 8 bpc\n13 # DMT 1360 x 768p @ 60Hz, 10 bpc\n15 # DMT 1400 x 1050p @ 60Hz, RB1, 8 bpc\n17 # CVT 1600 x 1200p @ 60Hz, RB1, 6 bpc\n13 # DMT 1360 x 768p @ 60Hz, 8 bpc\n26 # CVT 1280 x 800p @ 60Hz, 8 bpc\n25 # DMT 1280 x 768p @ 60Hz, 8 bpc", + "flag": 3, + "id": "TSI_DP20_SRCCTS_2L_MOST_PACKED", + "maxValue": 2147480000, + "minValue": 0, + "name": "Most packed video timing definitions for 2 lanes links", + "type": 4 + }, + { + "configId": "0x1190022", + "defaultValue": "29", + "description": "These three CI’s define the most packed timings for 1, 2 and 4 lane modes. Default for 1 lane is 8 ( = 1280 x 800 @ 60Hz RB1, 18BPP). Default 2 lanes is 12 ( = 1280 x 1024 @ 60Hz, 24BPP). Default for 4 lanes is 18 ( = 2048 x 1536 @ 60Hz RB1, 24BPP). Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n20 # DMT 1600 x 1200p @ 60Hz, 10 bpc\n28 # DMT 1600 x 1200p @ 60Hz, 8 bpc\n16 # DMT 1792 x 1344p @ 60Hz, 8 bpc\n29 # CTA 1920 x 1080p @ 60Hz, 10 bpc\n5 # CTA 1920 x 1080p @ 60Hz, 8 bpc\n18 # CVT 2048 x 1536p @ 60Hz, RB1, 8 bpc", + "flag": 3, + "id": "TSI_DP20_SRCCTS_4L_MOST_PACKED", + "maxValue": 2147480000, + "minValue": 0, + "name": "Most packed video timing definitions for 4 lanes links", + "type": 4 + }, + { + "configId": "0x1190023", + "defaultValue": "2", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP20_SRCCTS_1L_RBB", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for Reduced Bitrate (RBR, 1.62Gbps) testing for 1 lanes.", + "type": 4 + }, + { + "configId": "0x1190024", + "defaultValue": "3", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP20_SRCCTS_2L_RBB", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for Reduced Bitrate (RBR, 1.62Gbps) testing for 2 lanes.", + "type": 4 + }, + { + "configId": "0x1190025", + "defaultValue": "5", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC\n5 # 1920 x 1080 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP20_SRCCTS_4L_RBB", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for Reduced Bitrate (RBR, 1.62Gbps) testing for 4 lanes.", + "type": 4 + }, + { + "configId": "0x1190026", + "defaultValue": "3", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP20_SRCCTS_1L_HBR", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate (HBR, 2.7Gbps) testing for 1 lanes", + "type": 4 + }, + { + "configId": "0x1190027", + "defaultValue": "4", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP20_SRCCTS_2L_HBR", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate (HBR, 2.7Gbps) testing for 2 lanes", + "type": 4 + }, + { + "configId": "0x1190028", + "defaultValue": "5", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC\n5 # 1920 x 1080 @ 60Hz 8 BPC\n6 # 1920 x 1440 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP20_SRCCTS_4L_HBR", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate (HBR, 2.7Gbps) testing for 4 lanes", + "type": 4 + }, + { + "configId": "0x1190029", + "defaultValue": "4", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP20_SRCCTS_1L_HBR2", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate-2 (HBR2, 5.4Gbps) testing for 1 lanes.", + "type": 4 + }, + { + "configId": "0x119002a", + "defaultValue": "5", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC\n5 # 1920 x 1080 @ 60Hz 8 BPC\n6 # 1920 x 1440 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP20_SRCCTS_2L_HBR2", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate-2 (HBR2, 5.4Gbps) testing for 2 lanes.", + "type": 4 + }, + { + "configId": "0x119002b", + "defaultValue": "7", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC\n5 # 1920 x 1080 @ 60Hz 8 BPC\n6 # 1920 x 1440 @ 60Hz 8 BPC\n7 # 1920 x 1080 @ 120Hz 8 BPC", + "flag": 3, + "id": "TSI_DP20_SRCCTS_4L_HBR2", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate-2 (HBR2, 5.4Gbps) testing for 4 lanes.", + "type": 4 + }, + { + "configId": "0x119002c", + "defaultValue": "6", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC\n5 # 1920 x 1080 @ 60Hz 8 BPC\n6 # 1920 x 1440 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP20_SRCCTS_1L_HBR3", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate-3 (HBR3, 8.1Gbps) testing for 1 lanes.", + "type": 4 + }, + { + "configId": "0x119002d", + "defaultValue": "21", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC\n5 # 1920 x 1080 @ 60Hz 8 BPC\n6 # 1920 x 1440 @ 60Hz 8 BPC\n7 # 1920 x 1080 @ 120Hz 8 BPC\n8 # 1280 x 800 @ 60Hz RB1 6 BPC\n9 # 1280 x 768 @ 60Hz RB1 6 BPC\n10 # 800 x 600 @ 60Hz 10 BPC\n11 # 1024 x 768 @ 60Hz 10 BPC\n12 # 1280 x 1024 @ 60Hz 8 BPC\n13 # 1360 x 768 @ 60Hz 10 BPC\n14 # 1280 x 800 @ 60Hz RB1 10 BPC\n15 # 1400 x 1050 @ 60Hz RB1 8 BPC\n16 # 1280 x 768 @ 60Hz RB1 10 BPC\n17 # 1600 x 1200 @ 60Hz RB1 6 BPC\n18 # 2048 x 1536 @ 60Hz RB1 8 BPC\n19 # 1792 x 1344 @ 60Hz 8 BPC\n20 # 1600 x 1200 @ 60Hz RB1 10 BPC\n21 # 3840 x 2160 @ 30Hz 8 BPC", + "flag": 3, + "id": "TSI_DP20_SRCCTS_2L_HBR3", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate-3 (HBR3, 8.1Gbps) testing for 2 lanes.", + "type": 4 + }, + { + "configId": "0x119002e", + "defaultValue": "22", + "description": "These 12 CI’s define the video modes used for timestamp generations for RBR, HBR, HBR2 and HBR3 link rates with 1, 2 and 4 lanes configurations. Please see 6.9.1 Test ID definitions, configuration items for defaults. Please see 6.9.17 LL CTS Test resolution ID values for the mode definitions. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "0 # None\n1 # 640 x 480 @ 60Hz 6 BPC\n2 # 848 x 480 @ 60Hz 8 BPC\n3 # 1280 x 720 @ 60Hz 8 BPC\n4 # 1280 x 960 @ 60Hz 8 BPC\n5 # 1920 x 1080 @ 60Hz 8 BPC\n6 # 1920 x 1440 @ 60Hz 8 BPC\n7 # 1920 x 1080 @ 120Hz 8 BPC\n8 # 1280 x 800 @ 60Hz RB1 6 BPC\n9 # 1280 x 768 @ 60Hz RB1 6 BPC\n10 # 800 x 600 @ 60Hz 10 BPC\n11 # 1024 x 768 @ 60Hz 10 BPC\n12 # 1280 x 1024 @ 60Hz 8 BPC\n13 # 1360 x 768 @ 60Hz 10 BPC\n14 # 1280 x 800 @ 60Hz RB1 10 BPC\n15 # 1400 x 1050 @ 60Hz RB1 8 BPC\n16 # 1280 x 768 @ 60Hz RB1 10 BPC\n17 # 1600 x 1200 @ 60Hz RB1 6 BPC\n18 # 2048 x 1536 @ 60Hz RB1 8 BPC\n19 # 1792 x 1344 @ 60Hz 8 BPC\n20 # 1600 x 1200 @ 60Hz RB1 10 BPC\n21 # 3840 x 2160 @ 30Hz 8 BPC\n22 # 3840 x 2160 @ 60Hz 8 BPC", + "flag": 3, + "id": "TSI_DP20_SRCCTS_4L_HBR3", + "maxValue": 2147480000, + "minValue": 0, + "name": "Video timings used for High Bitrate-3 (HBR3, 8.1Gbps) testing for 4 lanes.", + "type": 4 + }, + { + "configId": "0x1190038", + "defaultValue": "0", + "description": "These parameters are considered mandatory and should not be changed.", + "flag": 1, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_0", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_0", + "type": 4 + }, + { + "configId": "0x1190039", + "defaultValue": "32", + "description": "These parameters are considered mandatory and should not be changed.", + "flag": 1, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_1", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_1", + "type": 4 + }, + { + "configId": "0x119003a", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_2", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_2", + "type": 4 + }, + { + "configId": "0x119003b", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_3", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_3", + "type": 4 + }, + { + "configId": "0x119003c", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_4", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_4", + "type": 4 + }, + { + "configId": "0x119003d", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_5", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_5", + "type": 4 + }, + { + "configId": "0x119003e", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_6", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_6", + "type": 4 + }, + { + "configId": "0x119003f", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_7", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_7", + "type": 4 + }, + { + "configId": "0x1190040", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_8", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_8", + "type": 4 + }, + { + "configId": "0x1190041", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_9", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_9", + "type": 4 + }, + { + "configId": "0x1190042", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_10", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_10", + "type": 4 + }, + { + "configId": "0x1190043", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_11", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_11", + "type": 4 + }, + { + "configId": "0x1190044", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_12", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_12", + "type": 4 + }, + { + "configId": "0x1190045", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_13", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_13", + "type": 4 + }, + { + "configId": "0x1190046", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_14", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_14", + "type": 4 + }, + { + "configId": "0x1190047", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_15", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_15", + "type": 4 + }, + { + "configId": "0x1190048", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_16", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_16", + "type": 4 + }, + { + "configId": "0x1190049", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_17", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_17", + "type": 4 + }, + { + "configId": "0x119004a", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_18", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_18", + "type": 4 + }, + { + "configId": "0x119004b", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_19", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_19", + "type": 4 + }, + { + "configId": "0x119004c", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_20", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_20", + "type": 4 + }, + { + "configId": "0x119004d", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_21", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_21", + "type": 4 + }, + { + "configId": "0x119004e", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_22", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_22", + "type": 4 + }, + { + "configId": "0x119004f", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_23", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_23", + "type": 4 + }, + { + "configId": "0x1190050", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_24", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_24", + "type": 4 + }, + { + "configId": "0x1190051", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_25", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_25", + "type": 4 + }, + { + "configId": "0x1190052", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_26", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_26", + "type": 4 + }, + { + "configId": "0x1190053", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_27", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_27", + "type": 4 + }, + { + "configId": "0x1190054", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_28", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_28", + "type": 4 + }, + { + "configId": "0x1190055", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_29", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_29", + "type": 4 + }, + { + "configId": "0x1190056", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_30", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_30", + "type": 4 + }, + { + "configId": "0x1190057", + "defaultValue": "0", + "description": "Optional and additional color modes to be used with DP CTS tests.\nThe CI with ID value 0x01190008 (DP20_SRCCTS_COLOR_FORMATS) should indicate the number of color modes defined in this table. Un-used color modes should be set to zero.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_COLOR_MODE_31", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_COLOR_MODE_31", + "type": 4 + }, + { + "configId": "0x1190058", + "defaultValue": "1", + "description": "Defines DUT DSC maximum supported slice. Default setting is 1. Valid settings are 1, 2, 4, 8, 10, 12, 16, 20, 24.\n ", + "enumerationVariants": "1\n2\n4\n8\n10\n12\n16\n20\n24", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_DUT_MAX_SLICE", + "maxValue": 24, + "minValue": 1, + "name": "DUT DSC maximum supported slice", + "type": 4 + }, + { + "configId": "0x1190059", + "defaultValue": "65538", + "description": "Defines DSC version. Default setting is 1.2. Valid settings are 1.1 and 1.2.\n ", + "enumerationVariants": "65537 # 1.1\n65538 # 1.2", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VERSION", + "maxValue": 65538, + "minValue": 65537, + "name": "DSC version", + "type": 4 + }, + { + "configId": "0x119005a", + "defaultValue": "0", + "description": "1920x1080 @ 30Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_0_ID", + "maxValue": 0, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_VMT_0_ID", + "type": 4 + }, + { + "configId": "0x119005b", + "defaultValue": "1", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_0_LC_LR", + "mask": 1, + "name": "Lane count and Link rate flags", + "type": 4 + }, + { + "configId": "0x119005c", + "defaultValue": "0x00000001", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_0_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x119005d", + "defaultValue": "1", + "description": "1920x1080 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_1_ID", + "maxValue": 1, + "minValue": 1, + "name": "TSI_DP20_SRCCTS_DSC_VMT_1_ID", + "type": 4 + }, + { + "configId": "0x119005e", + "defaultValue": "1", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_1_LC_LR", + "mask": 1, + "name": "Lane count and Link rate flags", + "type": 4 + }, + { + "configId": "0x119005f", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_1_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x1190060", + "defaultValue": "2", + "description": "1920x1080 @ 120Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_2_ID", + "maxValue": 2, + "minValue": 2, + "name": "TSI_DP20_SRCCTS_DSC_VMT_2_ID", + "type": 4 + }, + { + "configId": "0x1190061", + "defaultValue": "292", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_2_LC_LR", + "mask": 1911, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x1190062", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_2_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x1190063", + "defaultValue": "3", + "description": "3840x2160 @ 30Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_3_ID", + "maxValue": 3, + "minValue": 3, + "name": "TSI_DP20_SRCCTS_DSC_VMT_3_ID", + "type": 4 + }, + { + "configId": "0x1190064", + "defaultValue": "292", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_3_LC_LR", + "mask": 1911, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x1190065", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_3_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x1190066", + "defaultValue": "4", + "description": "3840x2160 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_4_ID", + "maxValue": 4, + "minValue": 4, + "name": "TSI_DP20_SRCCTS_DSC_VMT_4_ID", + "type": 4 + }, + { + "configId": "0x1190067", + "defaultValue": "328", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_4_LC_LR", + "mask": 3549, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x1190068", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_4_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x1190069", + "defaultValue": "5", + "description": "3840x2160 @ 120Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_5_ID", + "maxValue": 5, + "minValue": 5, + "name": "TSI_DP20_SRCCTS_DSC_VMT_5_ID", + "type": 4 + }, + { + "configId": "0x119006a", + "defaultValue": "1152", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_5_LC_LR", + "mask": 3264, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x119006b", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_5_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x119006c", + "defaultValue": "6", + "description": "5120x2160 @ 30Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_6_ID", + "maxValue": 6, + "minValue": 6, + "name": "TSI_DP20_SRCCTS_DSC_VMT_6_ID", + "type": 4 + }, + { + "configId": "0x119006d", + "defaultValue": "292", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_6_LC_LR", + "mask": 1911, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x119006e", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_6_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x119006f", + "defaultValue": "7", + "description": "5120x2160 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_7_ID", + "maxValue": 7, + "minValue": 7, + "name": "TSI_DP20_SRCCTS_DSC_VMT_6_ID", + "type": 4 + }, + { + "configId": "0x1190070", + "defaultValue": "584", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_7_LC_LR", + "mask": 3822, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x1190071", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_7_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x1190072", + "defaultValue": "8", + "description": "5120x2160 @ 120Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_8_ID", + "maxValue": 8, + "minValue": 8, + "name": "TSI_DP20_SRCCTS_DSC_VMT_8_ID", + "type": 4 + }, + { + "configId": "0x1190073", + "defaultValue": "1152", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_8_LC_LR", + "mask": 3264, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x1190074", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_8_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x1190075", + "defaultValue": "9", + "description": "7680x4320 @ 30Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_9_ID", + "maxValue": 9, + "minValue": 9, + "name": "TSI_DP20_SRCCTS_DSC_VMT_9_ID", + "type": 4 + }, + { + "configId": "0x1190076", + "defaultValue": "576", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_9_LC_LR", + "mask": 1632, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x1190077", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_9_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x1190078", + "defaultValue": "10", + "description": "7680x4320 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_10_ID", + "maxValue": 10, + "minValue": 10, + "name": "TSI_DP20_SRCCTS_DSC_VMT_10_ID", + "type": 4 + }, + { + "configId": "0x1190079", + "defaultValue": "2048", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_10_LC_LR", + "mask": 1024, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x119007a", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_10_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x119007b", + "defaultValue": "11", + "description": "7680x4320 @ 100Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_11_ID", + "maxValue": 11, + "minValue": 11, + "name": "TSI_DP20_SRCCTS_DSC_VMT_11_ID", + "type": 4 + }, + { + "configId": "0x119007c", + "defaultValue": "2048", + "description": "Defines the bitmap with Lane count and Link rate. Default setting is zero. Please see table below for defined capability flags:\nBits Description \n0 ONE_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n1 ONE_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n2 ONE_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n 3 ONE_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n4 TWO_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported. \n5 TWO_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported. \n6 TWO_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n7 TWO_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported. \n8 FOUR_LANE_LINK_RATE_1_62GBS. 0 = Not supported, 1 = Supported.\n9 FOUR_LANE_LINK_RATE_2_7GBS. 0 = Not supported, 1 = Supported.\n10 FOUR_LANE_LINK_RATE_5_4GBS. 0 = Not supported, 1 = Supported.\n11 FOUR_LANE_LINK_RATE_8_1GBS. 0 = Not supported, 1 = Supported.\n31:12 RESERVED, SET TO ZERO\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_11_LC_LR", + "mask": 2048, + "name": "Lane count and Link rate flags.", + "type": 4 + }, + { + "configId": "0x119007d", + "defaultValue": "0x00000000", + "description": "Defines the video mode timing.\nImportant: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "CTA support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000002", "description": "RB1 support. 0 = Not supported, 1 = Supported."}, + {"mask": "0x00000004", "description": "RB2 support. 0 = Not supported, 1 = Supported."} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_11_TIMINGS", + "name": "Lane count and Link rate flags.", + "type": 12 + }, + { + "configId": "0x119007e", + "defaultValue": "1", + "description": "Defined the number of items in DSC color format table.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_FORMATS", + "maxValue": 2147483647, + "minValue": 0, + "name": "Number of items in DSC color format table", + "type": 4 + }, + { + "configId": "0x119007f", + "defaultValue": "32", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 1, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_0", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_0", + "type": 4 + }, + { + "configId": "0x1190080", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_1", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_1", + "type": 4 + }, + { + "configId": "0x1190081", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_2", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_2", + "type": 4 + }, + { + "configId": "0x1190082", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_3", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_3", + "type": 4 + }, + { + "configId": "0x1190083", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_4", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_4", + "type": 4 + }, + { + "configId": "0x1190084", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_5", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_5", + "type": 4 + }, + { + "configId": "0x1190085", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_6", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_6", + "type": 4 + }, + { + "configId": "0x1190086", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_7", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_7", + "type": 4 + }, + { + "configId": "0x1190087", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_8", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_8", + "type": 4 + }, + { + "configId": "0x1190088", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_9", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_9", + "type": 4 + }, + { + "configId": "0x1190089", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_10", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_10", + "type": 4 + }, + { + "configId": "0x119008a", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_11", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_11", + "type": 4 + }, + { + "configId": "0x119008b", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_12", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_12", + "type": 4 + }, + { + "configId": "0x119008c", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_13", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_13", + "type": 4 + }, + { + "configId": "0x119008d", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_14", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_14", + "type": 4 + }, + { + "configId": "0x119008e", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_15", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_15", + "type": 4 + }, + { + "configId": "0x119008f", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_16", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_16", + "type": 4 + }, + { + "configId": "0x1190090", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_17", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_17", + "type": 4 + }, + { + "configId": "0x1190091", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_18", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_18", + "type": 4 + }, + { + "configId": "0x1190092", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_19", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_19", + "type": 4 + }, + { + "configId": "0x1190093", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_20", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_20", + "type": 4 + }, + { + "configId": "0x1190094", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_21", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_21", + "type": 4 + }, + { + "configId": "0x1190095", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_22", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_22", + "type": 4 + }, + { + "configId": "0x1190096", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_23", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_23", + "type": 4 + }, + { + "configId": "0x1190097", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_24", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_24", + "type": 4 + }, + { + "configId": "0x1190098", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_25", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_25", + "type": 4 + }, + { + "configId": "0x1190099", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_26", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_26", + "type": 4 + }, + { + "configId": "0x119009a", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_27", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_27", + "type": 4 + }, + { + "configId": "0x119009b", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_28", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_28", + "type": 4 + }, + { + "configId": "0x119009c", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_29", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_29", + "type": 4 + }, + { + "configId": "0x119009d", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_30", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_30", + "type": 4 + }, + { + "configId": "0x119009e", + "defaultValue": "0", + "description": "Defines used DSC color mode. These parameters are considered mandatory and should not be changed.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_31", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_DSC_COLOR_MODE_31", + "type": 4 + }, + { + "configId": "0x119009f", + "defaultValue": "5120", + "description": "Defines maximum HActive per stream.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_MAX_STREAM_HACTIVE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum HActive per stream", + "type": 4 + }, + { + "configId": "0x11900a0", + "defaultValue": "2160", + "description": "Defines maximum VActive per stream.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_MAX_STREAM_VACTIVE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum VActive per stream", + "type": 4 + }, + { + "configId": "0x11900a1", + "defaultValue": "742500", + "description": "Defines maximum pixel clock per stream, kHz.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_MAX_STREAM_PIXEL_CLOCK", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum pixel clock per stream, in kHz", + "type": 4 + }, + { + "configId": "0x11900a2", + "defaultValue": "1297", + "description": "Defines tested audio mode. Bytes 2...0 of the word match data bytes 3...1 of a corresponding Audio InfoFrame (See CTA-861-G, Section 6.6). Byte 3 is set to 0. Unused entries are set to 0.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_0", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_0", + "type": 4 + }, + { + "configId": "0x11900a3", + "defaultValue": "2321", + "description": "Defines tested audio mode. Bytes 2...0 of the word match data bytes 3...1 of a corresponding Audio InfoFrame (See CTA-861-G, Section 6.6). Byte 3 is set to 0. Unused entries are set to 0.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_1", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_1", + "type": 4 + }, + { + "configId": "0x11900a4", + "defaultValue": "3345", + "description": "Defines tested audio mode. Bytes 2...0 of the word match data bytes 3...1 of a corresponding Audio InfoFrame (See CTA-861-G, Section 6.6). Byte 3 is set to 0. Unused entries are set to 0.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_2", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_2", + "type": 4 + }, + { + "configId": "0x11900a5", + "defaultValue": "2833", + "description": "Defines tested audio mode. Bytes 2...0 of the word match data bytes 3...1 of a corresponding Audio InfoFrame (See CTA-861-G, Section 6.6). Byte 3 is set to 0. Unused entries are set to 0.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_3", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_3", + "type": 4 + }, + { + "configId": "0x11900a6", + "defaultValue": "3857", + "description": "Defines tested audio mode. Bytes 2...0 of the word match data bytes 3...1 of a corresponding Audio InfoFrame (See CTA-861-G, Section 6.6). Byte 3 is set to 0. Unused entries are set to 0.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_4", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_4", + "type": 4 + }, + { + "configId": "0x11900a7", + "defaultValue": "0", + "description": "Defines tested audio mode. Bytes 2...0 of the word match data bytes 3...1 of a corresponding Audio InfoFrame (See CTA-861-G, Section 6.6). Byte 3 is set to 0. Unused entries are set to 0.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_5", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_5", + "type": 4 + }, + { + "configId": "0x11900a8", + "defaultValue": "0", + "description": "Defines tested audio mode. Bytes 2...0 of the word match data bytes 3...1 of a corresponding Audio InfoFrame (See CTA-861-G, Section 6.6). Byte 3 is set to 0. Unused entries are set to 0.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_6", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_6", + "type": 4 + }, + { + "configId": "0x11900a9", + "defaultValue": "0", + "description": "Defines tested audio mode. Bytes 2...0 of the word match data bytes 3...1 of a corresponding Audio InfoFrame (See CTA-861-G, Section 6.6). Byte 3 is set to 0. Unused entries are set to 0.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_7", + "maxValue": 2147483647, + "minValue": 0, + "name": "TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_7", + "type": 4 + }, + { + "configId": "0x11900aa", + "defaultValue": "0x00000103", + "description": "DMT timings (Progressive Scan) supported by the DUT without DSC.", + "bitList": + [ + {"mask": "0x00000001", "description": "800x600 @ 60Hz (DMT 09h)"}, + {"mask": "0x00000002", "description": "1024x768 @ 60Hz (DMT 10h)"}, + {"mask": "0x00000004", "description": "1280x768 @ 60Hz (DMT 17h)"}, + {"mask": "0x00000008", "description": "1280x800 @ 60Hz (DMT 1Ch)"}, + {"mask": "0x00000010", "description": "1280x960 @ 60Hz (DMT 20h)"}, + {"mask": "0x00000020", "description": "1280x1024 @ 60Hz (DMT 23h)"}, + {"mask": "0x00000040", "description": "1360x768 @ 60Hz (DMT 27h)"}, + {"mask": "0x00000080", "description": "1400x1050 @ 60Hz (DMT 2Ah)"}, + {"mask": "0x00000100", "description": "1600x1200 @ 60Hz (DMT 33h)"}, + {"mask": "0x00000200", "description": "1680x1050 @ 60Hz (DMT 3Ah)"}, + {"mask": "0x00000400", "description": "1856x1392 @ 60Hz (DMT 41h)"}, + {"mask": "0x00000800", "description": "1920x1080 @ 60Hz (DMT 52h)"}, + {"mask": "0x00001000", "description": "1920x1200 @ 60Hz (DMT 45h)"}, + {"mask": "0x00002000", "description": "1920x1440 @ 60Hz (DMT 49h)"} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_DMT_TIMING", + "name": "DMT timings.", + "type": 12 + }, + { + "configId": "0x11900ab", + "defaultValue": "0x00000106", + "description": "CTA timings (Progressive Scan) supported by the DUT without DSC.", + "bitList": + [ + {"mask": "0x00000001", "description": "720x480 @ 60Hz (VIC 2)"}, + {"mask": "0x00000002", "description": "1280x720 @ 60Hz (VIC 4)"}, + {"mask": "0x00000004", "description": "1920x1080 @ 60Hz (VIC 16)"}, + {"mask": "0x00000008", "description": "1280x720 @ 120Hz (VIC 47)"}, + {"mask": "0x00000010", "description": "720x480 @ 120Hz (VIC 48)"}, + {"mask": "0x00000020", "description": "1920x1080 @ 120Hz (VIC 63)"}, + {"mask": "0x00000040", "description": "1680x720 @ 60Hz (VIC 83)"}, + {"mask": "0x00000080", "description": "1680x720 @ 120Hz (VIC 85)"}, + {"mask": "0x00000100", "description": "2560x1080 @ 60Hz (VIC 90)"}, + {"mask": "0x00000200", "description": "2560x1080 @ 120Hz (VIC 92)"}, + {"mask": "0x00000400", "description": "3840x2160 @ 60Hz (VIC 97)"}, + {"mask": "0x00000800", "description": "4096x2160 @ 60Hz (VIC 102)"}, + {"mask": "0x00001000", "description": "3840x2160 @ 120Hz (VIC 118)"}, + {"mask": "0x00002000", "description": "5120x2160 @ 60Hz (VIC 126)"}, + {"mask": "0x00004000", "description": "7680x4320 @ 24Hz (VIC 194)"}, + {"mask": "0x00008000", "description": "7680x4320 @ 30Hz (VIC 196)"} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_CTA_TIMING", + "name": "CTA timings.", + "type": 12 + }, + { + "configId": "0x11900ac", + "defaultValue": "0x00000492", + "description": "CVT timings (Progressive Scan) supported by the DUT without DSC.", + "bitList": + [ + {"mask": "0x00000001", "description": "1920x1080 @ 60Hz RBv1"}, + {"mask": "0x00000002", "description": "2048x1536 @ 60Hz RBv1"}, + {"mask": "0x00000004", "description": "2560x1080 @ 60Hz RBv1"}, + {"mask": "0x00000008", "description": "2560x1440 @ 60Hz RBv1"}, + {"mask": "0x00000010", "description": "2560x1440 @ 60Hz RBv2"}, + {"mask": "0x00000020", "description": "3840x2160 @ 60Hz RBv1"}, + {"mask": "0x00000040", "description": "3840x2160 @ 60Hz RBv2"}, + {"mask": "0x00000080", "description": "3840x2160 @ 60Hz RBv3"}, + {"mask": "0x00000100", "description": "4096x2160 @ 60Hz RBv1"}, + {"mask": "0x00000200", "description": "4096x2160 @ 60Hz RBv2"}, + {"mask": "0x00000400", "description": "4096x2160 @ 60Hz RBv3"}, + {"mask": "0x00000800", "description": "5120x2880 @ 60Hz RBv1"}, + {"mask": "0x00001000", "description": "5120x2880 @ 60Hz RBv2"}, + {"mask": "0x00002000", "description": "5120x2880 @ 60Hz RBv3"} + ], + "flag": 3, + "id": "TSI_DP20_SRCCTS_CVT_TIMING", + "name": "CVT timings.", + "type": 12 + }, + { + "configId": "0x11900ad", + "defaultValue": "0x00000007", + "description": "DUT AdaptiveSync capabilitie.", + "bitList": + [ + {"mask": "0x00000001", "description": "DUT supports AdaptiveSync"}, + {"mask": "0x00000002", "description": "Device supports Fixed Average VTotal mode"}, + {"mask": "0x00000004", "description": "Device supports Duration Increase and Decrease constraints"}, + {"mask": "0x00000008", "description": "Adaptive-Sync must be enabled manually"} + ], + "flag": 1, + "id": "TSI_DP20_SRCCTS_AS_DUT_CAPS", + "name": "DUT AdaptiveSync capabilitie.", + "type": 12 + }, + { + "configId": "0x11900ae", + "defaultValue": "3", + "description": "Adaptive-Sync range minimum refresh rate supported by the Source. Default value is 3.", + "enumerationVariants": "0 # 59.940 Hz\n1 # 47.952 Hz\n2 # 29.970 Hz\n3 # 23.976 Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_AS_RANGE_MIN_RATE", + "maxValue": 3, + "minValue": 0, + "name": "Adaptive-Sync range minimum refresh rate supported by the Source", + "type": 4 + }, + { + "configId": "0x11900af", + "defaultValue": "120", + "description": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz. A zero value means the timing is not supported.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_0", + "maxValue": 360, + "minValue": 0, + "name": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz.", + "type": 4 + }, + { + "configId": "0x11900b0", + "defaultValue": "120", + "description": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz. A zero value means the timing is not supported.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_1", + "maxValue": 360, + "minValue": 0, + "name": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz.", + "type": 4 + }, + { + "configId": "0x11900b1", + "defaultValue": "120", + "description": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz. A zero value means the timing is not supported.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_2", + "maxValue": 360, + "minValue": 0, + "name": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz.", + "type": 4 + }, + { + "configId": "0x11900b2", + "defaultValue": "120", + "description": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz. A zero value means the timing is not supported.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_3", + "maxValue": 360, + "minValue": 0, + "name": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz.", + "type": 4 + }, + { + "configId": "0x11900b3", + "defaultValue": "0", + "description": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz. A zero value means the timing is not supported.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_4", + "maxValue": 360, + "minValue": 0, + "name": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz.", + "type": 4 + }, + { + "configId": "0x11900b4", + "defaultValue": "0", + "description": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz. A zero value means the timing is not supported.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_5", + "maxValue": 360, + "minValue": 0, + "name": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz.", + "type": 4 + }, + { + "configId": "0x11900b5", + "defaultValue": "0", + "description": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz. A zero value means the timing is not supported.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_6", + "maxValue": 360, + "minValue": 0, + "name": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz.", + "type": 4 + }, + { + "configId": "0x11900b6", + "defaultValue": "0", + "description": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz. A zero value means the timing is not supported.", + "flag": 3, + "id": "TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_7", + "maxValue": 360, + "minValue": 0, + "name": "Maximum refresh rate for supported Adaptive-Sync video timings, in Hz.", + "type": 4 + }, + { + "configId": "0x11900bf", + "defaultValue": "61440", + "description": "Timing 768x480 @ 85Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_768_480_85", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900c0", + "defaultValue": "61440", + "description": "Timing 1024x640 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1024_640_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900c1", + "defaultValue": "61440", + "description": "Timing 1152x720 @ 75Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1152_720_75", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900c2", + "defaultValue": "61440", + "description": "Timing 1280x720 @ 24Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1280_720_24", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900c3", + "defaultValue": "61440", + "description": "Timing 1280x720 @ 120Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1280_720_120", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900c4", + "defaultValue": "61440", + "description": "Timing 1280x768 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1280_768_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900c5", + "defaultValue": "61440", + "description": "Timing 1280x960 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1280_960_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900c6", + "defaultValue": "61440", + "description": "Timing 1440x240 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1440_240_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900c7", + "defaultValue": "61440", + "description": "Timing 1440x480 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1440_480_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900c8", + "defaultValue": "61440", + "description": "Timing 1440x900 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1440_900_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900c9", + "defaultValue": "4096", + "description": "Timing 1536x960 @ 85Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1536_960_85", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900ca", + "defaultValue": "61465", + "description": "Timing 1920x1080p @ 30Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1920_1080_30", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900cb", + "defaultValue": "61465", + "description": "Timing 1920x1080p @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1920_1080_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900cc", + "defaultValue": "61440", + "description": "Timing 1920x1080 @ 85Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1920_1080_85", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900cd", + "defaultValue": "61465", + "description": "Timing 1920x1080p @ 100Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1920_1080_100", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900ce", + "defaultValue": "61465", + "description": "Timing 1920x1080p @ 120Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1920_1080_120", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900cf", + "defaultValue": "61465", + "description": "Timing 1920x1080p @ 144Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1920_1080_144", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900d0", + "defaultValue": "61465", + "description": "Timing 1920x1080p @ 240Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1920_1080_240", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900d1", + "defaultValue": "61440", + "description": "Timing 1920x1440 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_1920_1440_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900d2", + "defaultValue": "61440", + "description": "Timing 2048x1280 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_2048_1280_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900d3", + "defaultValue": "61440", + "description": "Timing 2048x1536 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_2048_1536_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900d4", + "defaultValue": "61440", + "description": "Timing 2128x1200 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_2128_1200_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900d5", + "defaultValue": "61440", + "description": "Timing 2456x1536 @ 50Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_2456_1536_50", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900d6", + "defaultValue": "61440", + "description": "Timing 2456x1536 @ 75Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_2456_1536_75", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900d7", + "defaultValue": "61440", + "description": "Timing 2560x1080 @ 30Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_2560_1080_30", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900d8", + "defaultValue": "61441", + "description": "Timing 2560x1080 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_2560_1080_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900d9", + "defaultValue": "61441", + "description": "Timing 2560x1080 @ 120Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_2560_1080_120", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900da", + "defaultValue": "61440", + "description": "Timing 2560x1600 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_2560_1600_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900db", + "defaultValue": "61440", + "description": "Timing 2560x1920 @ 75Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_2560_1920_75", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900dc", + "defaultValue": "61440", + "description": "Timing 2728x1536 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_2728_1536_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900dd", + "defaultValue": "61469", + "description": "Timing 3840x2160p @ 30Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_3840_2160_30", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900de", + "defaultValue": "61469", + "description": "Timing 3840x2160p @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_3840_2160_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900df", + "defaultValue": "61469", + "description": "Timing 3840x2160p @ 120Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_3840_2160_120", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900e0", + "defaultValue": "61496", + "description": "Timing 3840x2160p @ 144Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_3840_2160_144", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900e1", + "defaultValue": "61496", + "description": "Timing 3840x2160p @ 240Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_3840_2160_240", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900e2", + "defaultValue": "61440", + "description": "Timing 3840x2400 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_3840_2400_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900e3", + "defaultValue": "61440", + "description": "Timing 4096x2160 @ 30Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_4096_2160_30", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900e4", + "defaultValue": "61465", + "description": "Timing 5120x2160p @ 30Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_5120_2160_30", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900e5", + "defaultValue": "61465", + "description": "Timing 5120x2160p @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_5120_2160_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900e6", + "defaultValue": "61465", + "description": "Timing 5120x2160p @ 120Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_5120_2160_120", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900e7", + "defaultValue": "61496", + "description": "Timing 5120x2160p @ 144Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_5120_2160_144", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900e8", + "defaultValue": "61496", + "description": "Timing 5120x2160p @ 240Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_5120_2160_240", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900e9", + "defaultValue": "61440", + "description": "Timing 5120x2880 @ 30Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_5120_2880_30", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900ea", + "defaultValue": "61440", + "description": "Timing 5120x2880 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_5120_2880_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900eb", + "defaultValue": "61440", + "description": "Timing 5120x2880 @ 120Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_5120_2880_120", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900ec", + "defaultValue": "61456", + "description": "Timing 7680x4320 @ 24Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_7680_4320_24", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900ed", + "defaultValue": "61469", + "description": "Timing 7680x4320 @ 30Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_7680_4320_30", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900ee", + "defaultValue": "61440", + "description": "Timing 7680x4320 @ 50Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_7680_4320_50", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900ef", + "defaultValue": "61469", + "description": "Timing 7680x4320 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_7680_4320_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900f0", + "defaultValue": "61469", + "description": "Timing 7680x4320 @ 120Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_7680_4320_120", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900f1", + "defaultValue": "61440", + "description": "Timing 10240x4320 @ 24Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_10240_4320_24", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900f2", + "defaultValue": "61465", + "description": "Timing 10240x4320 @ 30Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_10240_4320_30", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x11900f3", + "defaultValue": "61465", + "description": "Timing 10240x4320 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_VMT_10240_4320_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x119000f", + "defaultValue": "0", + "description": "Reserved for extension.", + "bitList": + [ + {"description": "0x119000f", "defaultValue": "0"}, + {"description": "0x119001c", "defaultValue": "0"}, + {"description": "0x119001d", "defaultValue": "0"}, + {"description": "0x119001e", "defaultValue": "0"}, + {"description": "0x119001f", "defaultValue": "0"}, + {"description": "0x119002f", "defaultValue": "0"}, + {"description": "0x1190030", "defaultValue": "0"}, + {"description": "0x1190031", "defaultValue": "0"}, + {"description": "0x1190032", "defaultValue": "0"}, + {"description": "0x1190033", "defaultValue": "0"}, + {"description": "0x1190034", "defaultValue": "0"}, + {"description": "0x1190035", "defaultValue": "0"}, + {"description": "0x1190036", "defaultValue": "0"}, + {"description": "0x1190037", "defaultValue": "0"}, + {"description": "0x11900b7", "defaultValue": "0"}, + {"description": "0x11900b8", "defaultValue": "0"}, + {"description": "0x11900b9", "defaultValue": "0"}, + {"description": "0x11900ba", "defaultValue": "0"}, + {"description": "0x11900bb", "defaultValue": "0"}, + {"description": "0x11900bc", "defaultValue": "0"}, + {"description": "0x11900bd", "defaultValue": "0"}, + {"description": "0x11900be", "defaultValue": "0"} + ], + "flag": 3, + "id": "RESERVED", + "type": 4 + }, + { + "configId": "0x11900f4", + "defaultValue": "1920", + "description": "HActive in pixels", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DRR_HACTIVE", + "maxValue": 2147483647, + "minValue": 0, + "name": "HActive", + "type": 4 + }, + { + "configId": "0x11900f5", + "defaultValue": "1080", + "description": "VActive in lines", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DRR_VACTIVE", + "maxValue": 2147483647, + "minValue": 0, + "name": "VActive", + "type": 4 + }, + { + "configId": "0x11900f6", + "defaultValue": "297000", + "description": "Pixel Clock, kHz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DRR_PIXEL_CLOCK", + "maxValue": 2147483647, + "minValue": 0, + "name": "Pixel Clock", + "type": 4 + }, + { + "configId": "0x11900f7", + "defaultValue": "50", + "description": "Minimum Vertical Refresh Rate, Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DRR_MIN_VRR", + "maxValue": 2147483647, + "minValue": 0, + "name": "Minimum Vertical Refresh Rate", + "type": 4 + }, + { + "configId": "0x11900f8", + "defaultValue": "100", + "description": "Maximum Vertical Refresh Rate, Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DRR_MAX_VRR", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum Vertical Refresh Rate", + "type": 4 + }, + { + "configId": "0x01190100", + "defaultValue": "61465", + "description": "Timing 1920x1080p @ 30Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_1920_1080_30", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x01190101", + "defaultValue": "61465", + "description": "Timing 1920x1080p @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_1920_1080_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x01190102", + "defaultValue": "61465", + "description": "Timing 1920x1080p @ 120Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_1920_1080_120", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x01190103", + "defaultValue": "61465", + "description": "Timing 1920x1080p @ 144Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_1920_1080_144", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x01190104", + "defaultValue": "61465", + "description": "Timing 1920x1080p @ 240Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_1920_1080_240", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x01190105", + "defaultValue": "61469", + "description": "Timing 3840x2160p @ 30Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_3840_2160_30", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x01190106", + "defaultValue": "61469", + "description": "Timing 3840x2160p @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_3840_2160_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x01190107", + "defaultValue": "61469", + "description": "Timing 3840x2160p @ 120Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_3840_2160_120", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x01190108", + "defaultValue": "61496", + "description": "Timing 3840x2160p @ 144Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_3840_2160_144", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x01190109", + "defaultValue": "61496", + "description": "Timing 3840x2160p @ 240Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_3840_2160_240", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x0119010a", + "defaultValue": "61465", + "description": "Timing 5120x2160p @ 30Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_5120_2160_30", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x0119010b", + "defaultValue": "61465", + "description": "Timing 5120x2160p @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_5120_2160_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x0119010c", + "defaultValue": "61465", + "description": "Timing 5120x2160p @ 120Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_5120_2160_120", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x0119010d", + "defaultValue": "61496", + "description": "Timing 5120x2160p @ 144Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_5120_2160_144", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x0119010e", + "defaultValue": "61496", + "description": "Timing 5120x2160p @ 240Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_5120_2160_240", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x0119010f", + "defaultValue": "61469", + "description": "Timing 7680x4320 @ 30Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_7680_4320_30", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x01190110", + "defaultValue": "61469", + "description": "Timing 7680x4320 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_7680_4320_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x01190111", + "defaultValue": "61469", + "description": "Timing 7680x4320 @ 120Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_7680_4320_120", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x01190112", + "defaultValue": "61465", + "description": "Timing 10240x4320 @ 30Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_10240_4320_30", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + }, + { + "configId": "0x01190113", + "defaultValue": "61465", + "description": "Timing 10240x4320 @ 60Hz", + "flag": 3, + "id": "TSI_DP20_SRCCTS_DSC_VMT_10240_4320_60", + "maxValue": 2147483647, + "minValue": 0, + "name": "Video mode supported by Source DUT (DP2.1 video & DSC tests)", + "type": 4 + } + ], + "id": "0x08", + "name": "DP 2.1 LL CTS", + "old_names": [ + "DP 2.1 LL CTS", + "DP 2.0 Link Layer Source DUT CTS", + "DP 2.1 Link Layer Source DUT CTS" + ] +} diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x09]src_dut_hdcp_23_1a.json b/UniTAP/dev/modules/dut_tests/cfg/[0x09]src_dut_hdcp_23_1a.json new file mode 100644 index 0000000..157256a --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x09]src_dut_hdcp_23_1a.json @@ -0,0 +1,41 @@ +{ + "descriptions": [ + { + "configId": "0x10F0000", + "defaultValue": "200000", + "description": "Defines test timeout in milliseconds, default value is 200000ms.", + "flag": 3, + "id": "TSI_TEST_CFG_HDCP2_1A_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x10F0001", + "defaultValue": [48, 187, 226, 110, 98], + "description": "Defines Revoke ID.", + "flag": 3, + "id": "TSI_TEST_CFG_HDCP2_1A_REVOKEID", + "name": "Revoke ID", + "type": 7 + }, + { + "configId": "0x10F0002", + "defaultValue": "1", + "description": "Defines source DUT capabilities flags.", + "enumerationVariants": "0 # False\n1 # True", + "flag": 3, + "id": "TSI_TEST_CFG_HDCP2_1A_SRC_DUT_CAP", + "maxValue": 1, + "minValue": 0, + "name": "Source_EncDisableBootstrapping", + "type": 4 + } + ], + "id": "0x09", + "name": "HDCP 2.3 CTS 1A", + "old_names": [ + "HDCP 2.3 CTS 1A Test Set" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x0A]src_dut_hdcp_23_1b.json b/UniTAP/dev/modules/dut_tests/cfg/[0x0A]src_dut_hdcp_23_1b.json new file mode 100644 index 0000000..bfe2c30 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x0A]src_dut_hdcp_23_1b.json @@ -0,0 +1,20 @@ +{ + "descriptions": [ + { + "configId": "0x1110000", + "defaultValue": "200000", + "description": "Defines test timeout in milliseconds, default value is 200000ms.", + "flag": 3, + "id": "TSI_TEST_CFG_HDCP2_1B_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + } + ], + "id": "0x0A", + "name": "HDCP 2.3 CTS 1B", + "old_names": [ + "HDCP 2.3 CTS 1B Test Set" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x0B]src_dut_hdcp_23_3a.json b/UniTAP/dev/modules/dut_tests/cfg/[0x0B]src_dut_hdcp_23_3a.json new file mode 100644 index 0000000..c74b388 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x0B]src_dut_hdcp_23_3a.json @@ -0,0 +1,20 @@ +{ + "descriptions": [ + { + "configId": "0x1130000", + "defaultValue": "200000", + "description": "Defines test timeout in milliseconds, default value is 200000ms.", + "flag": 3, + "id": "TSI_TEST_CFG_HDCP2_3A_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + } + ], + "id": "0x0B", + "name": "HDCP 2.3 CTS 3A", + "old_names": [ + "HDCP 2.3 CTS 3A Test Set" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x0C]src_dut_hdcp_23_3b.json b/UniTAP/dev/modules/dut_tests/cfg/[0x0C]src_dut_hdcp_23_3b.json new file mode 100644 index 0000000..321b6ab --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x0C]src_dut_hdcp_23_3b.json @@ -0,0 +1,20 @@ +{ + "descriptions": [ + { + "configId": "0x1140000", + "defaultValue": "200000", + "description": "Defines test timeout in milliseconds, default value is 200000ms.", + "flag": 3, + "id": "TSI_TEST_CFG_HDCP2_3B_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + } + ], + "id": "0x0C", + "name": "HDCP 2.3 CTS 3B", + "old_names": [ + "HDCP 2.3 CTS 3B Test Set" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x0D]snk_dut_hdcp_23_2c.json b/UniTAP/dev/modules/dut_tests/cfg/[0x0D]snk_dut_hdcp_23_2c.json new file mode 100644 index 0000000..4db5ee2 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x0D]snk_dut_hdcp_23_2c.json @@ -0,0 +1,20 @@ +{ + "descriptions": [ + { + "configId": "0x1120000", + "defaultValue": "200000", + "description": "Defines test timeout in milliseconds, default value is 200000ms.", + "flag": 3, + "id": "TSI_TEST_CFG_HDCP2_2C_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + } + ], + "id": "0x0D", + "name": "HDCP 2.3 CTS 2C", + "old_names": [ + "HDCP 2.3 CTS 2C Test Set" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x0E]snk_dut_hdcp_23_3c.json b/UniTAP/dev/modules/dut_tests/cfg/[0x0E]snk_dut_hdcp_23_3c.json new file mode 100644 index 0000000..949ff8c --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x0E]snk_dut_hdcp_23_3c.json @@ -0,0 +1,32 @@ +{ + "descriptions": [ + { + "configId": "0x1150000", + "defaultValue": "200000", + "description": "Defines test timeout in milliseconds, default value is 200000ms.", + "flag": 3, + "id": "TSI_TEST_CFG_HDCP2_3C_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x1150001", + "defaultValue": "1", + "description": "Defines Repeater_MultipleOutputs.", + "enumerationVariants": "0 # False\n1 # True", + "flag": 3, + "id": "TSI_TEST_CFG_HDCP2_3C_REPEATER_MULTIPLE_OUTPUTS", + "maxValue": 1, + "minValue": 0, + "name": "Repeater_MultipleOutputs", + "type": 4 + } + ], + "id": "0x0E", + "name": "HDCP 2.3 CTS 3C", + "old_names": [ + "HDCP 2.3 CTS 3C Test Set" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x0F]snk_dut_vrr.json b/UniTAP/dev/modules/dut_tests/cfg/[0x0F]snk_dut_vrr.json new file mode 100644 index 0000000..159692c --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x0F]snk_dut_vrr.json @@ -0,0 +1,111 @@ +{ + "descriptions": [ + { + "configId": "0x1180000", + "defaultValue": "10000", + "description": "Timeout period used for all VRR tests, in milliseconds. Default timeout is 10000ms.", + "flag": 3, + "id": "TSI_VRR_SINK_DUT_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x1180001", + "defaultValue": "60", + "description": "VRR Max value. Default timeout is 60.", + "flag": 3, + "id": "TSI_VRR_SINK_DUT_VRR_MAX", + "maxValue": 2147483647, + "minValue": 0, + "name": "VRR Max value", + "type": 4 + }, + { + "configId": "0x1180002", + "defaultValue": "30", + "description": "VRR Min value. Default timeout is 30.", + "flag": 3, + "id": "TSI_VRR_SINK_DUT_VRR_MIN", + "maxValue": 2147483647, + "minValue": 0, + "name": "VRR Min value", + "type": 4 + }, + { + "configId": "0x1180003", + "defaultValue": "45", + "description": "VRR Static value. Default timeout is 45.", + "flag": 3, + "id": "TSI_VRR_SINK_DUT_VRR_STATIC", + "maxValue": 2147483647, + "minValue": 0, + "name": "VRR Static value", + "type": 4 + }, + { + "configId": "0x1180004", + "defaultValue": "1", + "description": "Determines step of changing frame rate. Default timeout is 1.", + "flag": 3, + "id": "TSI_VRR_SINK_DUT_VRR_STEP", + "maxValue": 2147483647, + "minValue": 0, + "name": "VRR Step value", + "type": 4 + }, + { + "configId": "0x1180005", + "defaultValue": "1000", + "description": "Determines timer to change frame rate. Default timeout is 1000.", + "flag": 3, + "id": "TSI_VRR_SINK_DUT_VRR_TIME_STEP", + "maxValue": 2147483647, + "minValue": 0, + "name": "VRR Time step value", + "type": 4 + }, + { + "configId": "0x1180006", + "defaultValue": "0x00000003", + "description": "VRR Enable and M_CONST", + "bitList": + [ + {"mask": "0x00000001", "description": "VRR Enable value"}, + {"mask": "0x00000002", "description": "M_CONST"} + ], + "flag": 3, + "id": "TSI_VRR_SINK_DUT_VRR_ENABLE", + "name": "VRR Enable and M_CONST", + "type": 12 + }, + { + "configId": "0x1180007", + "defaultValue": "3", + "description": "VFront and RB.", + "bitList": + [ + {"mask": "0x00000003", "description": "Base VFront"}, + {"mask": "0x00000004", "description": "RB"} + ], + "flag": 3, + "id": "TSI_VRR_SINK_DUT_BASE_VFRONT", + "name": "VFront and RB", + "type": 12 + }, + { + "configId": "0x1180008", + "defaultValue": "50", + "description": "Base Refresh Rate. Default timeout is 50.", + "flag": 3, + "id": "TSI_VRR_SINK_DUT_BASE_RATE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Base Refresh Rate", + "type": 4 + } + ], + "id": "0x0F", + "name": "VRR Sink DUT Tests" +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x10]src_dut_vrr.json b/UniTAP/dev/modules/dut_tests/cfg/[0x10]src_dut_vrr.json new file mode 100644 index 0000000..64b9c58 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x10]src_dut_vrr.json @@ -0,0 +1,111 @@ +{ + "descriptions": [ + { + "configId": "0x1170000", + "defaultValue": "10000", + "description": "Timeout period used for all VRR tests, in milliseconds. Default timeout is 10000ms.", + "flag": 3, + "id": "TSI_VRR_SRC_DUT_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x1170001", + "defaultValue": "60", + "description": "VRR Max value. Default timeout is 60.", + "flag": 3, + "id": "TSI_VRR_SRC_DUT_VRR_MAX", + "maxValue": 2147483647, + "minValue": 0, + "name": "VRR Max value", + "type": 4 + }, + { + "configId": "0x1170002", + "defaultValue": "30", + "description": "VRR Min value. Default timeout is 30.", + "flag": 3, + "id": "TSI_VRR_SRC_DUT_VRR_MIN", + "maxValue": 2147483647, + "minValue": 0, + "name": "VRR Min value", + "type": 4 + }, + { + "configId": "0x1170003", + "defaultValue": "45", + "description": "VRR Static value. Default timeout is 45.", + "flag": 3, + "id": "TSI_VRR_SRC_DUT_VRR_STATIC", + "maxValue": 2147483647, + "minValue": 0, + "name": "VRR Static value", + "type": 4 + }, + { + "configId": "0x1170004", + "defaultValue": "1", + "description": "Determines step of changing frame rate. Default timeout is 1.", + "flag": 3, + "id": "TSI_VRR_SRC_DUT_VRR_STEP", + "maxValue": 2147483647, + "minValue": 0, + "name": "VRR Step value", + "type": 4 + }, + { + "configId": "0x1170005", + "defaultValue": "1000", + "description": "Determines timer to change frame rate. Default timeout is 1000.", + "flag": 3, + "id": "TSI_VRR_SRC_DUT_VRR_TIME_STEP", + "maxValue": 2147483647, + "minValue": 0, + "name": "VRR Time step value", + "type": 4 + }, + { + "configId": "0x1170006", + "defaultValue": "0x00000003", + "description": "VRR Enable and M_CONST", + "bitList": + [ + {"mask": "0x00000001", "description": "VRR Enable value"}, + {"mask": "0x00000002", "description": "M_CONST"} + ], + "flag": 3, + "id": "TSI_VRR_SRC_DUT_VRR_ENABLE", + "name": "VRR Enable and M_CONST", + "type": 12 + }, + { + "configId": "0x1170007", + "defaultValue": "3", + "description": "VFront and RB.", + "bitList": + [ + {"mask": "0x00000003", "description": "Base VFront"}, + {"mask": "0x00000004", "description": "RB"} + ], + "flag": 3, + "id": "TSI_VRR_SRC_DUT_BASE_VFRONT", + "name": "VFront value", + "type": 12 + }, + { + "configId": "0x1170008", + "defaultValue": "50", + "description": "Base Refresh Rate. Default timeout is 50.", + "flag": 3, + "id": "TSI_VRR_SRC_DUT_BASE_RATE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Base Refresh Rate", + "type": 4 + } + ], + "id": "0x10", + "name": "VRR Source DUT Tests" +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x11]src_dut_cec.json b/UniTAP/dev/modules/dut_tests/cfg/[0x11]src_dut_cec.json new file mode 100644 index 0000000..b0d2ee6 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x11]src_dut_cec.json @@ -0,0 +1,26 @@ +{ + "descriptions": [ + { + "configId": "0x10400", + "defaultValue": "5000", + "description": "Defines the CEC functional test timeout, in milliseconds. The test must complete within this time-period in order to succeed. Default setting is 5000ms.", + "flag": 3, + "id": "TSI_HDMI_RX_CEC_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x10401", + "defaultValue": "16384", + "description": "Defines the CEC local PHY address. The address is stored in lowest 16-bits. Default setting is 0x4000 (“4.0.0.0”).\nTypically these addresses are given as “A.B.C.D”, similar to IP addresses. Each number in the address can be a value between 0 and 15. Therefore, address “8.9.10.11” would become HEX value 0x000089AB.", + "flag": 3, + "id": "TSI_HDMI_RX_CEC_LOCAL_PHY_ADDR", + "name": "Local CEC physical address", + "type": 8 + } + ], + "id": "0x11", + "name": "CEC functional Test Set" +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x12]src_dut_pixel_level_video.json b/UniTAP/dev/modules/dut_tests/cfg/[0x12]src_dut_pixel_level_video.json new file mode 100644 index 0000000..9912ea7 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x12]src_dut_pixel_level_video.json @@ -0,0 +1,223 @@ +{ + "descriptions": [ + { + "configId": "0x10", + "defaultValue": "640", + "description": "Defines frame width as number of elements. Actual width in pixels is therefore this value multiplied by the element width.", + "flag": 3, + "id": "TSI_REF1_WIDTH", + "maxValue": 2147483647, + "minValue": 0, + "name": "Reference width as count of elements", + "type": 4 + }, + { + "configId": "0x11", + "defaultValue": "480", + "description": "Defines frame height as number of elements. Actual height in pixels is therefore this value multiplied by the element height.", + "flag": 3, + "id": "TSI_REF1_HEIGHT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Reference height as count of elements", + "type": 4 + }, + { + "configId": "0x12", + "defaultValue": "3", + "description": "Defines the size of the element body, in as bytes of storage required. Certain formats allow this container to be of different size: For example RGB 8:8:8 can have size of 3 and/or 4. Often, the 4 byte version is referred to as ARGB, but TSI does not process the Alpha (“A”) channel, so the presence of that is ignored.", + "flag": 3, + "id": "TSI_REF1_ELEMENT_SIZE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Size of a single element as bytes", + "type": 4 + }, + { + "configId": "0x13", + "defaultValue": "1", + "description": "Defines the width of a single element as number of pixels. Important: TSI_REF1_PIXELS_PER_ELEMENT name define is considered obsolete, however it continues to be defined for backwards compatibility. The new name was incorporated as it is more descriptive.", + "flag": 3, + "id": "TSI_REF1_ELEMENT_WIDTH", + "maxValue": 2147483647, + "minValue": 0, + "name": "Element width as pixels", + "type": 4 + }, + { + "configId": "0x14", + "defaultValue": "1", + "description": "Defines the height of a single element as number of pixels. Important: TSI_REF1_LINES_PER_ELEMENT name define is considered obsolete, however it continues to be defined for backwards compatibility. The new name was incorporated as it is more descriptive.", + "flag": 3, + "id": "TSI_REF1_ELEMENT_HEIGHT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Element height as pixels", + "type": 4 + }, + { + "configId": "0x15", + "defaultValue": "8", + "description": "Defines the color depth of the image as number of bits per color channel regardless of the color format.", + "enumerationVariants": "6\n8\n10\n12\n16", + "flag": 3, + "id": "TSI_REF1_COLOR_DEPTH", + "maxValue": 2147483647, + "minValue": 0, + "name": "Active color bits per channel", + "type": 4 + }, + { + "configId": "0x16", + "defaultValue": "17", + "description": "Defines the element format used to encode the pixel data of the bitmap. Please see table below for currently defined format ID values:\nDefine -- ID -- Description\n\n TSI_ELF_RGB_080808 -- 0 -- RGB color, max color depth 8 bits per channel. Encoded as 3 unsigned bytes or 4 unsigned bytes per element. \n\nTSI_ELF_RGB_161616 -- 1 -- RGB color, max color depth 16 bits per channel. Encoded as 3 unsigned shorts or 4 unsigned shorts per element. \n\nTSI_ELF_YCbCr_080808 -- 0x100 -- YCbCr color, max color depth 8 bits per channel. Encoded as 3 unsigned bytes or 4 unsigned bytes per element. \n\nTSI_ELF_YCbCr_161616 -- 0x101 -- YCbCr color, max color depth 16 bits per channel. Encoded as 3 unsigned shorts or 4 unsigned shorts per element.\n\nImportant: TSI_REF1_PIXEL_FORMAT name define is considered obsolete, however it continues to be defined for backwards compatibility. The new name was incorporated as it is more descriptive.", + "enumerationVariants": "16 # RGB 6bpc\n17 # RGB 8bpc\n18 # RGB 10bpc\n19 # RGB 12bpc\n20 # RGB 16bpc\n256 # YCbCr 444 080808\n257 # YCbCr 444 161616\n800 # YCbCr 420 080808\n801 # YCbCr 420 10bpc\n802 # YCbCr 420 12bpc 161616\n803 # YCbCr 420 161616", + "flag": 3, + "id": "TSI_REF1_ELEMENT_FORMAT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Element data layout ID", + "type": 4 + }, + { + "configId": "0x17", + "defaultValue": "", + "description": "Contains bitmap data encoded as defined in other TSI_REF1_* CI’s.", + "flag": 3, + "id": "TSI_REF1_FRAME_DATA", + "maxLength": 10000, + "name": "Array of bytes that form the bitmap", + "type": 10 + }, + { + "configId": "0x119", + "defaultValue": "RGB", + "description": "Ref Image colorspace", + "flag": 3, + "enumerationVariants": "RGB\nYCbCr 444\nYCbCr 422\nYCbCr 420", + "maxValue": 2147483647, + "minValue": 0, + "id": "UI_VIDEO_PARAMS_COLORSPACE", + "maxLength": 10000, + "name": "Reference image colorspace", + "type": 4 + }, + { + "configId": "0x18", + "defaultValue": "1", + "description": "LSB-MSB data alignment", + "flag": 3, + "enumerationVariants": "1 # LSB\n0 # MSB", + "maxValue": 2147483647, + "minValue": 0, + "id": "TSI_REF1_LSB_MSB", + "maxLength": 10000, + "name": "LSB-MSB data alignment", + "type": 4 + }, + { + "configId": "0x1000", + "defaultValue": "60", + "description": "Defines the length of the video test as number of frames. Default setting is 60 frames. Important: 32-bit version of TSI with very high resolution inputs may require the test length to be reduced.", + "flag": 3, + "id": "TSI_TEST_LENGTH", + "maxValue": 2147483647, + "minValue": 0, + "name": "Length of video test in frames", + "type": 4 + }, + { + "configId": "0x1001", + "defaultValue": "0", + "description": "Defines number of frame that are allowed to be considered as “failed” before the entire test is considered as “failed”. Default setting is 0.", + "flag": 3, + "id": "TSI_LIM_FRAME_MISMATCHES", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum number of failed frames allowed per test", + "type": 4 + }, + { + "configId": "0x1002", + "defaultValue": "0", + "description": "Defines the number of pixels that allowed to be considered as “failed” before the frame is considered as “failed”. Default setting is 0.", + "flag": 3, + "id": "TSI_LIM_PIXEL_MISMATCHES", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum number of failed pixels allowed per frame", + "type": 4 + }, + { + "configId": "0x1003", + "defaultValue": "0", + "description": "Defines maximum difference allowed between reference image and captured image. If the difference is larger than the value of this CI on any color channel, the pixel is considered “failed”. Default setting is 0.", + "flag": 3, + "id": "TSI_PIXEL_TOLERANCE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum difference between color values allowed", + "type": 4 + }, + { + "configId": "0x1080", + "defaultValue": "0", + "description": "Maximum number of frames failed frames saved per test run. Default setting is 0. If the setting is “0”, no frames are saved.", + "flag": 3, + "id": "TSI_MAX_AUTO_SAVE_FAILED", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum number of failed frames to auto-save", + "type": 4 + }, + { + "configId": "0x1081", + "defaultValue": "", + "description": "Contains the full path to the folder where failed frames are to be saved without trailing backslash (‘\\’). No default. Failed frame file-name will be “Failed_<#>.ppm”, where <#> is replaced with an auto-incremented number.", + "flag": 3, + "id": "TSI_FAILED_FRAME_TARGET_FOLDER", + "maxLength": 10000, + "name": "Location where the failed frames are to be saved", + "type": 11 + }, + { + "configId": "0x1082", + "defaultValue": "0", + "description": "Defines the number of failed frames to be exported from the video test. Default setting is 0. If the setting is 0, no frames are exported.", + "flag": 3, + "id": "TSI_MAX_EXPORT_FAILED", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum number of exported frames", + "type": 4 + }, + { + "configId": "0x1084", + "defaultValue": "0", + "description": "Export format", + "flag": 3, + "enumerationVariants": "0 # Binary file\n1 # PPM image\n2 # BMP image", + "maxValue": 2147483647, + "minValue": 0, + "id": "TSI_EXPORT_FORMAT", + "maxLength": 10000, + "name": "Export format", + "type": 4 + }, + { + "configId": "0x1085", + "defaultValue": "0", + "description": "Align 12", + "flag": 3, + "id": "TSI_EXPORT_ALIGN_12", + "mask": 1, + "name": "Align 12", + "type": 3 + } + ], + "id": "0x12", + "name": "Pixel Level Video Tests", + "old_names": [ + "Compare video frame sequence with a single reference" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x13]snk_dut_dp_8b10b_ll_cts.json b/UniTAP/dev/modules/dut_tests/cfg/[0x13]snk_dut_dp_8b10b_ll_cts.json new file mode 100644 index 0000000..a03a7e2 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x13]snk_dut_dp_8b10b_ll_cts.json @@ -0,0 +1,551 @@ +{ + "descriptions": [ + { + "configId": "0x01160000", + "defaultValue": "10000", + "description": "Defines a test timeout, in milliseconds. Default setting is 10000ms. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP14_SINKCTS_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x01160001", + "defaultValue": "2", + "description": "Defines a DSC video mode: 1920x1080 @ 30Hz. Default and only ID: 2.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_DSC_VM_0", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 1920x1080 @ 30Hz.", + "type": 4 + }, + { + "configId": "0x01160002", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP14_SINKCTS_DSC_VM_T_0", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x01160003", + "defaultValue": "3", + "description": "Defines a DSC video mode: 1920x1080 @ 60Hz. Default and only ID: 3.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_DSC_VM_1", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 1920x1080 @ 60Hz.", + "type": 4 + }, + { + "configId": "0x01160004", + "defaultValue": "0x00000001", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP14_SINKCTS_DSC_VM_T_1", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x01160005", + "defaultValue": "4", + "description": "Defines a DSC video mode: 1920x1080 @ 120Hz. Default and only ID: 4.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_DSC_VM_2", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 1920x1080 @ 120Hz.", + "type": 4 + }, + { + "configId": "0x01160006", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP14_SINKCTS_DSC_VM_T_2", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x01160007", + "defaultValue": "5", + "description": "Defines a DSC video mode: 3840x2160 @ 30Hz. Default and only ID: 5.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_DSC_VM_3", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 3840x2160 @ 30Hz.", + "type": 4 + }, + { + "configId": "0x01160008", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP14_SINKCTS_DSC_VM_T_3", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x01160009", + "defaultValue": "6", + "description": "Defines a DSC video mode: 3840x2160 @ 60Hz. Default and only ID: 6.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_DSC_VM_4", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 3840x2160 @ 60Hz.", + "type": 4 + }, + { + "configId": "0x0116000A", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP14_SINKCTS_DSC_VM_T_4", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x0116000B", + "defaultValue": "7", + "description": "Defines a DSC video mode: 3840x2160 @ 120Hz. Default and only ID: 7.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_DSC_VM_5", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 3840x2160 @ 120Hz.", + "type": 4 + }, + { + "configId": "0x0116000C", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP14_SINKCTS_DSC_VM_T_5", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x0116000D", + "defaultValue": "8", + "description": "Defines a DSC video mode: 5120x2160 @ 30Hz. Default and only ID: 8.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_DSC_VM_6", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 5120x2160 @ 30Hz.", + "type": 4 + }, + { + "configId": "0x0116000E", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP14_SINKCTS_DSC_VM_T_6", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x0116000F", + "defaultValue": "9", + "description": "Defines a DSC video mode: 5120x2160 @ 60Hz. Default and only ID: 9.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_DSC_VM_7", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 5120x2160 @ 60Hz.", + "type": 4 + }, + { + "configId": "0x01160010", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP14_SINKCTS_DSC_VM_T_7", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x01160011", + "defaultValue": "10", + "description": "Defines a DSC video mode: 5120x2160 @ 120Hz. Default and only ID: 10.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_DSC_VM_8", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 5120x2160 @ 120Hz.", + "type": 4 + }, + { + "configId": "0x01160012", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP14_SINKCTS_DSC_VM_T_8", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x01160013", + "defaultValue": "11", + "description": "Defines a DSC video mode: 7680x4320 @ 30Hz. Default and only ID: 11.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_DSC_VM_9", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 7680x4320 @ 30Hz.", + "type": 4 + }, + { + "configId": "0x01160014", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP14_SINKCTS_DSC_VM_T_9", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x01160015", + "defaultValue": "12", + "description": "Defines a DSC video mode: 7680x4320 @ 60Hz. Default and only ID: 12.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_DSC_VM_10", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 7680x4320 @ 60Hz.", + "type": 4 + }, + { + "configId": "0x01160016", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP14_SINKCTS_DSC_VM_T_10", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x01160017", + "defaultValue": "13", + "description": "Defines a DSC video mode: 7680x4320 @ 100Hz. Default and only ID: 13.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_DSC_VM_11", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 7680x4320 @ 100Hz.", + "type": 4 + }, + { + "configId": "0x01160018", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP14_SINKCTS_DSC_VM_T_11", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x01160019", + "defaultValue": "1", + "description": "Defines the DSC video mode source. Default value is 0. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.\n", + "enumerationVariants": "0 # Use test configuration (below)\n1 # Use sink DUT EDID", + "flag": 3, + "id": "TSI_DP14_SINKCTS_DSC_SOURCE", + "maxValue": 2147480000, + "minValue": 0, + "name": "Source of the most packet video modes table", + "type": 4 + }, + { + "configId": "0x0116001A", + "defaultValue": "1", + "description": "Defines the Sink DUT support 444 CRC for Simple 422 bitstream. Default value is 1. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.\n", + "enumerationVariants": "0 # YCbCr 422 bitstream\n1 # YCbCr 444 bitstream", + "flag": 3, + "id": "TSI_DP14_SINKCTS_SUPPORT_444CRC", + "maxValue": 2147480000, + "minValue": 0, + "name": "Sink DUT support 444 CRC for Simple 422 bitstream.", + "type": 4 + }, + { + "configId": "0x0116001B", + "defaultValue": "1", + "description": "Defines the Source of the most packet video modes table. Default value is 1. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.\n", + "enumerationVariants": "0 # Use test configuration (below)\n1 # Use sink DUT EDID", + "flag": 3, + "id": "TSI_DP14_SINKCTS_PACKET_SOURCE", + "maxValue": 2147480000, + "minValue": 0, + "name": "Source of the most packet video modes table.", + "type": 4 + }, + { + "configId": "0x0116001C", + "defaultValue": "0", + "description": "Defines support for video mode: CVT 1280 x 800 @ 60p [RB1] 18bpp 99%.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VIDEO_MODE_0", + "mask": 1, + "name": "Support for video mode: CVT 1280 x 800 @ 60p [RB1] 18bpp 99%.", + "type": 3 + }, + { + "configId": "0x0116001D", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1280 x 768 @ 60p [RB1] 18bpp 95%.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VIDEO_MODE_1", + "mask": 1, + "name": "Support for video mode: DMT 1280 x 768 @ 60p [RB1] 18bpp 95%.", + "type": 3 + }, + { + "configId": "0x0116001E", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 800 x 600 @ 60.317p 30bpp 93%.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VIDEO_MODE_2", + "mask": 1, + "name": "Support for video mode: DMT 800 x 600 @ 60.317p 30bpp 93%.", + "type": 3 + }, + { + "configId": "0x0116001F", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1024 x 768 @ 60p 18bpp 90%.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VIDEO_MODE_3", + "mask": 1, + "name": "Support for video mode: DMT 1024 x 768 @ 60p 18bpp 90%.", + "type": 3 + }, + { + "configId": "0x01160020", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1280 x 1024 @ 60p 24bpp 100%.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VIDEO_MODE_4", + "mask": 1, + "name": "Support for video mode: DMT 1280 x 1024 @ 60p 24bpp 100%.", + "type": 3 + }, + { + "configId": "0x01160021", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1280 x 960 @ 60p 24bpp 100%.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VIDEO_MODE_5", + "mask": 1, + "name": "Support for video mode: DMT 1280 x 960 @ 60p 24bpp 100%.", + "type": 3 + }, + { + "configId": "0x01160022", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1360 x 768 @ 60p 30bpp 99%.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VIDEO_MODE_6", + "mask": 1, + "name": "Support for video mode: DMT 1360 x 768 @ 60p 30bpp 99%.", + "type": 3 + }, + { + "configId": "0x01160023", + "defaultValue": "0", + "description": "Defines support for video mode: CVT 1280 x 800 @ 60p 30bpp 97%.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VIDEO_MODE_7", + "mask": 1, + "name": "Support for video mode: CVT 1280 x 800 @ 60p 30bpp 97%.", + "type": 3 + }, + { + "configId": "0x01160024", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1400 x 1050 @ 60p [RB1] 24bpp 94%.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VIDEO_MODE_8", + "mask": 1, + "name": "Support for video mode: DMT 1400 x 1050 @ 60p [RB1] 24bpp 94%.", + "type": 3 + }, + { + "configId": "0x01160025", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1280 x 768 @ 60p 30bpp 92%.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VIDEO_MODE_9", + "mask": 1, + "name": "Support for video mode: DMT 1280 x 768 @ 60p 30bpp 92%.", + "type": 3 + }, + { + "configId": "0x01160026", + "defaultValue": "0", + "description": "Defines support for video mode: CVT 1600 x 1200 @ 60p [RB1] 18bpp 90%.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VIDEO_MODE_10", + "mask": 1, + "name": "Support for video mode: CVT 1600 x 1200 @ 60p [RB1] 18bpp 90%.", + "type": 3 + }, + { + "configId": "0x01160027", + "defaultValue": "0", + "description": "Defines support for video mode: CVT 2048 x 1536 @ 60p [RB1] 24bpp 97%.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VIDEO_MODE_11", + "mask": 1, + "name": "Support for video mode: CVT 2048 x 1536 @ 60p [RB1] 24bpp 97%.", + "type": 3 + }, + { + "configId": "0x01160028", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1792 x 1344 @ 60p 24bpp 95%.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VIDEO_MODE_12", + "mask": 1, + "name": "Support for video mode: DMT 1792 x 1344 @ 60p 24bpp 95%.", + "type": 3 + }, + { + "configId": "0x01160029", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1600 x 1200 @ 60p 30bpp 94%.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VIDEO_MODE_13", + "mask": 1, + "name": "Support for video mode: DMT 1600 x 1200 @ 60p 30bpp 94%.", + "type": 3 + }, + { + "configId": "0x0116002A", + "defaultValue": "0", + "description": "Defines support for video mode: CTA 1440 x 480 @ 59.94p 24bpp 100%.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VIDEO_MODE_14", + "mask": 1, + "name": "Support for video mode: CTA 1440 x 480 @ 59.94p 24bpp 100%.", + "type": 3 + }, + { + "configId": "0x0116002B", + "defaultValue": "0", + "description": "Defines support for video mode: CTA 1440 x 576 @ 50p 24bpp 100%.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VIDEO_MODE_15", + "mask": 1, + "name": "Support for video mode: CTA 1440 x 576 @ 50p 24bpp 100%.", + "type": 3 + }, + { + "configId": "0x0116002C", + "defaultValue": "0", + "description": "Defines support for video mode: CTA 1920 x 1080 @ 60p 30bpp 86%.", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VIDEO_MODE_16", + "mask": 1, + "name": "Support for video mode: CTA 1920 x 1080 @ 60p 30bpp 86%.", + "type": 3 + }, + { + "configId": "0x0116002D", + "defaultValue": "1", + "description": "Defines the Skip visual video check during DisplayID CTS tests. Default value is 0. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.\n", + "enumerationVariants": "0 # Never skip\n1 # Skip if CRC matches", + "flag": 3, + "id": "TSI_DP14_SINKCTS_VISUAL_TEST_CHECK", + "maxValue": 2147480000, + "minValue": 0, + "name": "Skip visual video check during DisplayID CTS tests.", + "type": 4 + } + ], + "id": "0x13", + "name": "DP 1.4 LL CTS", + "old_names": [ + "DP 1.4 LL CTS", + "DP 1.4 Link Layer Sink DUT CTS" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x14]src_dut_hdr10+.json b/UniTAP/dev/modules/dut_tests/cfg/[0x14]src_dut_hdr10+.json new file mode 100644 index 0000000..e0878a3 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x14]src_dut_hdr10+.json @@ -0,0 +1,29 @@ +{ + "descriptions": [ + { + "configId": "0x202A", + "defaultValue": "0", + "description": "Metadata at the same frame of associated video (0) or at one frame advance (1). Default 0", + "flag": 3, + "id": "TSI_HDR10_CTS_DISTR_SAME_FRAME", + "maxValue": 1, + "minValue": 0, + "name": "Video / Metadata frame offset", + "type": 4, + "enumerationVariants": "0 # Metadata at the same frame of associated Video data\n1 # Metadata at one frame advance from associated Video data" + }, + { + "configId": "0x202B", + "defaultValue": "10", + "description": "Defines the time period the TE will wait for metadata change; defined in seconds; default value is 10sec.\n", + "flag": 3, + "id": "TSI_HDR10_CTS_DISTR_TIMEOUT", + "maxValue": 1000, + "minValue": 1, + "name": "Metadata change timeout", + "type": 4 + } + ], + "id": "0x14", + "name": "HDR10+ Distribution Device Tests" +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x15]lttpr_dut_dp_128b132b_ll_cts.json b/UniTAP/dev/modules/dut_tests/cfg/[0x15]lttpr_dut_dp_128b132b_ll_cts.json new file mode 100644 index 0000000..d0a217d --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x15]lttpr_dut_dp_128b132b_ll_cts.json @@ -0,0 +1,116 @@ +{ + "descriptions": [ + { + "configId": "0x11B0000", + "defaultValue": "5000", + "description": "Defines test timeout in milliseconds, default value is 5000ms.", + "flag": 3, + "id": "TSI_DP20_LTTPR_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout in milliseconds.", + "reportFormat": "Test timeout = %2ms
", + "type": 4 + }, + { + "configId": "0x11B0001", + "defaultValue": "4", + "description": "Defines the maximum number of lanes supported by the DUT. Typical settings are 1, 2 and 4. Default value is 4. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.\n", + "enumerationVariants": "1\n2\n4", + "flag": 3, + "id": "TSI_DP20_LTTPR_MAX_LANES", + "maxValue": 2147480000, + "minValue": 0, + "name": "Maximum number of lanes supported by the DUT.", + "visible": false, + "reportFormat": "DUT capability settings and flags:
- Max lanes = %1
", + "type": 4 + }, + { + "configId": "0x11B0002", + "defaultValue": "30", + "description": "Defines the maximum link rate as multiplier for 0.27Gbps. Typical settings are 6 (RBR), 10 (HBR), 20 (HBR2) and 30 (HBR3). Notice that HBR3 link rate is usable only with UCD-400. The default setting is 30 (HBR3). Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.", + "enumerationVariants": "6 # RBR (1.62 Gbps)\n10 # HBR (2.7 Gbps)\n20 # HBR2 (5.4 Gbps)\n30 # HBR3 (8.10 Gbps)", + "flag": 3, + "id": "TSI_DP20_LTTPR_MAX_LINK_RATE", + "maxValue": 30, + "minValue": 6, + "name": "Maximum link rate supported by the DUT as multiplier for 0.27Gbps.", + "visible": false, + "reportFormat": "- Max link rate = %1
", + "type": 4 + }, + { + "configId": "0x11B0003", + "defaultValue": "0x00000000", + "description": "Defines the DUT capabilities as flags.", + "bitList": + [ + {"mask": "0x00000100", "description": "DUT is Type-C device", "reportFormat": "- DUT is Type-C device
"} + ], + "flag": 3, + "id": "TSI_DP20_LTTPR_DUT_CAPS", + "name": "DUT Capability flags.", + "reportFormat": "Test DUT capabilities flags:
", + "type": 12 + }, + { + "configId": "0x11B0004", + "defaultValue": "0x0000001A", + "description": "Defines the DUT Test automation capabilities as flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "TEST_LINK_TRAINING", "reportFormat": "- TEST_LINK_TRAINING
"}, + {"mask": "0x00000002", "description": "TEST_EDID_READ", "reportFormat": "- TEST_EDID_READ
"}, + {"mask": "0x00000004", "description": "TEST_VIDEO_PATTERN", "reportFormat": "- TEST_VIDEO_PATTERN
"}, + {"mask": "0x00000020", "description": "TEST_AUDIO_PATTERN", "reportFormat": "- TEST_AUDIO_PATTERN
"}, + {"mask": "0x00000018", "description": "Event indicating DUT ready", "enumerationVariants": "0 # Always ready\n1 # EDID read\n2 # Link Training end\n3 # Active Video", "reportFormat": "- Event indicating DUT ready: %1
"} + ], + "flag": 3, + "id": "TSI_DP20_LTTPR_DUT_TA", + "name": "DUT Test automation flags.", + "visible": false, + "reportFormat": "
Test automation flags and settings:
", + "type": 12 + }, + { + "configId": "0x11B0005", + "defaultValue": "1000", + "description": "Defines the duration of long HPD pulses generated by the tests. The duration is defined in millieconds. Default setting is 1000ms. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.\n", + "flag": 3, + "id": "TSI_DP20_LTTPR_LONG_HPD_PULSE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Long HPD pulse duration, in milliseconds.", + "reportFormat": "Long HPD Duration = %1ms
", + "type": 4 + }, + { + "configId": "0x11B0006", + "defaultValue": "100", + "description": "Defines the delay of CR iteration. The duration is defined in microseconds. Default setting is 100us. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.\n", + "flag": 3, + "id": "TSI_DP20_LTTPR_DEBUG_CR_ITERATION_DELAY", + "maxValue": 2147483647, + "minValue": 0, + "name": "CR iteration delay, in microseconds.", + "reportFormat": "CR Iteration Delay = %1us

", + "type": 4 + }, + { + "configId": "0x11B0007", + "defaultValue": "0x00000000", + "description": " Defines the Debug mode configuration.\n", + "bitList": + [ + {"mask": "0x00000001", "description": "Force video check enabled"} + ], + "flag": 3, + "id": "TSI_DP20_LTTPR_SINKCTS_DEBUG_CONF", + "name": "Debug mode configuration.", + "type": 12 + } + ], + "id": "0x15", + "name": "DP 2.1 LTTPR CTS" +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x16]src_dut_hdr10+sstm.json b/UniTAP/dev/modules/dut_tests/cfg/[0x16]src_dut_hdr10+sstm.json new file mode 100644 index 0000000..7b49b26 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x16]src_dut_hdr10+sstm.json @@ -0,0 +1,6 @@ +{ + "descriptions": [ + ], + "id": "0x16", + "name": "HDR10+ SSTM Tests for Source" +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x17]src_epr_power_sink.json b/UniTAP/dev/modules/dut_tests/cfg/[0x17]src_epr_power_sink.json new file mode 100644 index 0000000..3cdbb7d --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x17]src_epr_power_sink.json @@ -0,0 +1,18 @@ +{ + "descriptions": [ + { + "configId": "0x11C0000", + "defaultValue": "5000", + "description": "Defines test timeout in milliseconds, default value is 5000ms.", + "flag": 3, + "id": "TSI_USBC_EPR_POWER_SINK_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout in milliseconds.", + "reportFormat": "Test timeout = %2ms
", + "type": 4 + } + ], + "id": "0x17", + "name": "EPR Power Sink Tests" +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x18]snk_dut_dp_128b132b_ll_cts.json b/UniTAP/dev/modules/dut_tests/cfg/[0x18]snk_dut_dp_128b132b_ll_cts.json new file mode 100644 index 0000000..038cba7 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x18]snk_dut_dp_128b132b_ll_cts.json @@ -0,0 +1,727 @@ +{ + "descriptions": [ + { + "configId": "0x011A0000", + "defaultValue": "10000", + "description": "Defines a test timeout, in milliseconds. Default setting is 10000ms. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_DP20_SINKCTS_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x011A0001", + "defaultValue": "2", + "description": "Defines a DSC video mode: 1920x1080 @ 30Hz. Default and only ID: 2.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_DSC_VM_0", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 1920x1080 @ 30Hz.", + "type": 4 + }, + { + "configId": "0x011A0002", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_DSC_VM_T_0", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x011A0003", + "defaultValue": "3", + "description": "Defines a DSC video mode: 1920x1080 @ 60Hz. Default and only ID: 3.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_DSC_VM_1", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 1920x1080 @ 60Hz.", + "type": 4 + }, + { + "configId": "0x011A0004", + "defaultValue": "0x00000001", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_DSC_VM_T_1", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x011A0005", + "defaultValue": "4", + "description": "Defines a DSC video mode: 1920x1080 @ 120Hz. Default and only ID: 4.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_DSC_VM_2", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 1920x1080 @ 120Hz.", + "type": 4 + }, + { + "configId": "0x011A0006", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_DSC_VM_T_2", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x011A0007", + "defaultValue": "5", + "description": "Defines a DSC video mode: 3840x2160 @ 30Hz. Default and only ID: 5.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_DSC_VM_3", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 3840x2160 @ 30Hz.", + "type": 4 + }, + { + "configId": "0x011A0008", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_DSC_VM_T_3", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x011A0009", + "defaultValue": "6", + "description": "Defines a DSC video mode: 3840x2160 @ 60Hz. Default and only ID: 6.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_DSC_VM_4", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 3840x2160 @ 60Hz.", + "type": 4 + }, + { + "configId": "0x011A000A", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_DSC_VM_T_4", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x011A000B", + "defaultValue": "7", + "description": "Defines a DSC video mode: 3840x2160 @ 120Hz. Default and only ID: 7.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_DSC_VM_5", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 3840x2160 @ 120Hz.", + "type": 4 + }, + { + "configId": "0x011A000C", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_DSC_VM_T_5", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x011A000D", + "defaultValue": "8", + "description": "Defines a DSC video mode: 5120x2160 @ 30Hz. Default and only ID: 8.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_DSC_VM_6", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 5120x2160 @ 30Hz.", + "type": 4 + }, + { + "configId": "0x011A000E", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_DSC_VM_T_6", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x011A000F", + "defaultValue": "9", + "description": "Defines a DSC video mode: 5120x2160 @ 60Hz. Default and only ID: 9.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_DSC_VM_7", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 5120x2160 @ 60Hz.", + "type": 4 + }, + { + "configId": "0x011A0010", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_DSC_VM_T_7", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x011A0011", + "defaultValue": "10", + "description": "Defines a DSC video mode: 5120x2160 @ 120Hz. Default and only ID: 10.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_DSC_VM_8", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 5120x2160 @ 120Hz.", + "type": 4 + }, + { + "configId": "0x011A0012", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_DSC_VM_T_8", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x011A0013", + "defaultValue": "11", + "description": "Defines a DSC video mode: 7680x4320 @ 30Hz. Default and only ID: 11.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_DSC_VM_9", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 7680x4320 @ 30Hz.", + "type": 4 + }, + { + "configId": "0x011A0014", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_DSC_VM_T_9", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x011A0015", + "defaultValue": "12", + "description": "Defines a DSC video mode: 7680x4320 @ 60Hz. Default and only ID: 12.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_DSC_VM_10", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 7680x4320 @ 60Hz.", + "type": 4 + }, + { + "configId": "0x011A0016", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_DSC_VM_T_10", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x011A0017", + "defaultValue": "13", + "description": "Defines a DSC video mode: 7680x4320 @ 100Hz. Default and only ID: 13.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_DSC_VM_11", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC video mode: 7680x4320 @ 100Hz.", + "type": 4 + }, + { + "configId": "0x011A0018", + "defaultValue": "0x00000000", + "description": "DUT Capability flags.", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines the CTA resolution support. Default value: 0."}, + {"mask": "0x00000002", "description": "Defines the RB1 resolution support. Default value: 0."}, + {"mask": "0x00000004", "description": "Defines the RB2 resolution support. Default value: 0."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_DSC_VM_T_11", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x011A0019", + "defaultValue": "1", + "description": "Defines the DSC video mode source. Default value is 0. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.\n", + "enumerationVariants": "0 # Use test configuration (below)\n1 # Use sink DUT EDID", + "flag": 3, + "id": "TSI_DP20_SINKCTS_DSC_SOURCE", + "maxValue": 2147480000, + "minValue": 0, + "name": "Source of the most packet video modes table", + "type": 4 + }, + { + "configId": "0x011A001A", + "defaultValue": "1", + "description": "Defines the Sink DUT support 444 CRC for Simple 422 bitstream. Default value is 1. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.\n", + "enumerationVariants": "0 # YCbCr 422 bitstream\n1 # YCbCr 444 bitstream", + "flag": 3, + "id": "TSI_DP20_SINKCTS_SUPPORT_444CRC", + "maxValue": 2147480000, + "minValue": 0, + "name": "Sink DUT support 444 CRC for Simple 422 bitstream.", + "type": 4 + }, + { + "configId": "0x011A001B", + "defaultValue": "1", + "description": "Defines the Source of the most packet video modes table. Default value is 1. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.\n", + "enumerationVariants": "0 # Use test configuration (below)\n1 # Use sink DUT EDID", + "flag": 3, + "id": "TSI_DP20_SINKCTS_PACKET_SOURCE", + "maxValue": 2147480000, + "minValue": 0, + "name": "Source of the most packet video modes table.", + "type": 4 + }, + { + "configId": "0x011A001C", + "defaultValue": "0", + "description": "Defines support for video mode: CVT 1280 x 800 @ 60p [RB1] 18bpp 99%.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VIDEO_MODE_0", + "mask": 1, + "name": "Support for video mode: CVT 1280 x 800 @ 60p [RB1] 18bpp 99%.", + "type": 3 + }, + { + "configId": "0x011A001D", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1280 x 768 @ 60p [RB1] 18bpp 95%.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VIDEO_MODE_1", + "mask": 1, + "name": "Support for video mode: DMT 1280 x 768 @ 60p [RB1] 18bpp 95%.", + "type": 3 + }, + { + "configId": "0x011A001E", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 800 x 600 @ 60.317p 30bpp 93%.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VIDEO_MODE_2", + "mask": 1, + "name": "Support for video mode: DMT 800 x 600 @ 60.317p 30bpp 93%.", + "type": 3 + }, + { + "configId": "0x011A001F", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1024 x 768 @ 60p 18bpp 90%.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VIDEO_MODE_3", + "mask": 1, + "name": "Support for video mode: DMT 1024 x 768 @ 60p 18bpp 90%.", + "type": 3 + }, + { + "configId": "0x011A0020", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1280 x 1024 @ 60p 24bpp 100%.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VIDEO_MODE_4", + "mask": 1, + "name": "Support for video mode: DMT 1280 x 1024 @ 60p 24bpp 100%.", + "type": 3 + }, + { + "configId": "0x011A0021", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1280 x 960 @ 60p 24bpp 100%.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VIDEO_MODE_5", + "mask": 1, + "name": "Support for video mode: DMT 1280 x 960 @ 60p 24bpp 100%.", + "type": 3 + }, + { + "configId": "0x011A0022", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1360 x 768 @ 60p 30bpp 99%.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VIDEO_MODE_6", + "mask": 1, + "name": "Support for video mode: DMT 1360 x 768 @ 60p 30bpp 99%.", + "type": 3 + }, + { + "configId": "0x011A0023", + "defaultValue": "0", + "description": "Defines support for video mode: CVT 1280 x 800 @ 60p 30bpp 97%.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VIDEO_MODE_7", + "mask": 1, + "name": "Support for video mode: CVT 1280 x 800 @ 60p 30bpp 97%.", + "type": 3 + }, + { + "configId": "0x011A0024", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1400 x 1050 @ 60p [RB1] 24bpp 94%.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VIDEO_MODE_8", + "mask": 1, + "name": "Support for video mode: DMT 1400 x 1050 @ 60p [RB1] 24bpp 94%.", + "type": 3 + }, + { + "configId": "0x011A0025", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1280 x 768 @ 60p 30bpp 92%.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VIDEO_MODE_9", + "mask": 1, + "name": "Support for video mode: DMT 1280 x 768 @ 60p 30bpp 92%.", + "type": 3 + }, + { + "configId": "0x011A0026", + "defaultValue": "0", + "description": "Defines support for video mode: CVT 1600 x 1200 @ 60p [RB1] 18bpp 90%.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VIDEO_MODE_10", + "mask": 1, + "name": "Support for video mode: CVT 1600 x 1200 @ 60p [RB1] 18bpp 90%.", + "type": 3 + }, + { + "configId": "0x011A0027", + "defaultValue": "0", + "description": "Defines support for video mode: CVT 2048 x 1536 @ 60p [RB1] 24bpp 97%.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VIDEO_MODE_11", + "mask": 1, + "name": "Support for video mode: CVT 2048 x 1536 @ 60p [RB1] 24bpp 97%.", + "type": 3 + }, + { + "configId": "0x011A0028", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1792 x 1344 @ 60p 24bpp 95%.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VIDEO_MODE_12", + "mask": 1, + "name": "Support for video mode: DMT 1792 x 1344 @ 60p 24bpp 95%.", + "type": 3 + }, + { + "configId": "0x011A0029", + "defaultValue": "0", + "description": "Defines support for video mode: DMT 1600 x 1200 @ 60p 30bpp 94%.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VIDEO_MODE_13", + "mask": 1, + "name": "Support for video mode: DMT 1600 x 1200 @ 60p 30bpp 94%.", + "type": 3 + }, + { + "configId": "0x011A002A", + "defaultValue": "0", + "description": "Defines support for video mode: CTA 1440 x 480 @ 59.94p 24bpp 100%.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VIDEO_MODE_14", + "mask": 1, + "name": "Support for video mode: CTA 1440 x 480 @ 59.94p 24bpp 100%.", + "type": 3 + }, + { + "configId": "0x011A002B", + "defaultValue": "0", + "description": "Defines support for video mode: CTA 1440 x 576 @ 50p 24bpp 100%.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VIDEO_MODE_15", + "mask": 1, + "name": "Support for video mode: CTA 1440 x 576 @ 50p 24bpp 100%.", + "type": 3 + }, + { + "configId": "0x011A002C", + "defaultValue": "0", + "description": "Defines support for video mode: CTA 1920 x 1080 @ 60p 30bpp 86%.", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VIDEO_MODE_16", + "mask": 1, + "name": "Support for video mode: CTA 1920 x 1080 @ 60p 30bpp 86%.", + "type": 3 + }, + { + "configId": "0x011A002D", + "defaultValue": "1", + "description": "Defines the Skip visual video check during DisplayID CTS tests. Default value is 0. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready.\n", + "enumerationVariants": "0 # Never skip\n1 # Skip if CRC matches", + "flag": 3, + "id": "TSI_DP20_SINKCTS_VISUAL_TEST_CHECK", + "maxValue": 2147480000, + "minValue": 0, + "name": "Skip visual video check during DisplayID CTS tests.", + "type": 4 + }, + { + "configId": "0x011A002E", + "defaultValue": "0x00000010", + "description": "Resolution 1920x1080", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines 1920x1080 @ 30Hz CTA timing support."}, + {"mask": "0x00000002", "description": "Defines 1920x1080 @ 30Hz RBv1 timing support."}, + {"mask": "0x00000004", "description": "Defines 1920x1080 @ 30Hz RBv2 timing support."}, + {"mask": "0x00000010", "description": "Defines 1920x1080 @ 60Hz CTA timing support."}, + {"mask": "0x00000020", "description": "Defines 1920x1080 @ 60Hz RBv1 timing support."}, + {"mask": "0x00000040", "description": "Defines 1920x1080 @ 60Hz RBv2 timing support."}, + {"mask": "0x00000100", "description": "Defines 1920x1080 @ 120Hz CTA timing support."}, + {"mask": "0x00000200", "description": "Defines 1920x1080 @ 120Hz RBv1 timing support."}, + {"mask": "0x00000400", "description": "Defines 1920x1080 @ 120Hz RBv2 timing support."}, + {"mask": "0x00002000", "description": "Defines 1920x1080 @ 144Hz RBv1 timing support.."}, + {"mask": "0x00004000", "description": "Defines 1920x1080 @ 144Hz RBv2 timing support."}, + {"mask": "0x00008000", "description": "Defines 1920x1080 @ 144Hz OVT timing support."}, + {"mask": "0x00020000", "description": "Defines 1920x1080 @ 240Hz RBv1 timing support."}, + {"mask": "0x00040000", "description": "Defines 1920x1080 @ 240Hz RBv2 timing support."}, + {"mask": "0x00080000", "description": "Defines 1920x1080 @ 240Hz OVT timing support."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_VMT_1920_1080", + "name": "Resolution supported by Sink DUT", + "type": 12 + }, + { + "configId": "0x011A002F", + "defaultValue": "0x00000000", + "description": "Resolution 3840x2160", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines 3840x2160 @ 30Hz CTA timing support."}, + {"mask": "0x00000002", "description": "Defines 3840x2160 @ 30Hz RBv1 timing support."}, + {"mask": "0x00000004", "description": "Defines 3840x2160 @ 30Hz RBv2 timing support."}, + {"mask": "0x00000010", "description": "Defines 3840x2160 @ 60Hz CTA timing support."}, + {"mask": "0x00000020", "description": "Defines 3840x2160 @ 60Hz RBv1 timing support."}, + {"mask": "0x00000040", "description": "Defines 3840x2160 @ 60Hz RBv2 timing support."}, + {"mask": "0x00000100", "description": "Defines 3840x2160 @ 120Hz CTA timing support."}, + {"mask": "0x00000200", "description": "Defines 3840x2160 @ 120Hz RBv1 timing support."}, + {"mask": "0x00000400", "description": "Defines 3840x2160 @ 120Hz RBv2 timing support."}, + {"mask": "0x00002000", "description": "Defines 3840x2160 @ 144Hz RBv1 timing support.."}, + {"mask": "0x00004000", "description": "Defines 3840x2160 @ 144Hz RBv2 timing support."}, + {"mask": "0x00008000", "description": "Defines 3840x2160 @ 144Hz OVT timing support."}, + {"mask": "0x00020000", "description": "Defines 3840x2160 @ 240Hz RBv1 timing support."}, + {"mask": "0x00040000", "description": "Defines 3840x2160 @ 240Hz RBv2 timing support."}, + {"mask": "0x00080000", "description": "Defines 3840x2160 @ 240Hz OVT timing support."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_VMT_3840_2160", + "name": "Resolution supported by Sink DUT", + "type": 12 + }, + { + "configId": "0x011A0030", + "defaultValue": "0x00000000", + "description": "Resolution 5120x2160", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines 5120x2160 @ 30Hz CTA timing support."}, + {"mask": "0x00000002", "description": "Defines 5120x2160 @ 30Hz RBv1 timing support."}, + {"mask": "0x00000004", "description": "Defines 5120x2160 @ 30Hz RBv2 timing support."}, + {"mask": "0x00000010", "description": "Defines 5120x2160 @ 60Hz CTA timing support."}, + {"mask": "0x00000020", "description": "Defines 5120x2160 @ 60Hz RBv1 timing support."}, + {"mask": "0x00000040", "description": "Defines 5120x2160 @ 60Hz RBv2 timing support."}, + {"mask": "0x00000100", "description": "Defines 5120x2160 @ 120Hz CTA timing support."}, + {"mask": "0x00000200", "description": "Defines 5120x2160 @ 120Hz RBv1 timing support."}, + {"mask": "0x00000400", "description": "Defines 5120x2160 @ 120Hz RBv2 timing support."}, + {"mask": "0x00002000", "description": "Defines 5120x2160 @ 144Hz RBv1 timing support.."}, + {"mask": "0x00004000", "description": "Defines 5120x2160 @ 144Hz RBv2 timing support."}, + {"mask": "0x00008000", "description": "Defines 5120x2160 @ 144Hz OVT timing support."}, + {"mask": "0x00020000", "description": "Defines 5120x2160 @ 240Hz RBv1 timing support."}, + {"mask": "0x00040000", "description": "Defines 5120x2160 @ 240Hz RBv2 timing support."}, + {"mask": "0x00080000", "description": "Defines 5120x2160 @ 240Hz OVT timing support."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_VMT_5120_2160", + "name": "Resolution supported by Sink DUT", + "type": 12 + }, + { + "configId": "0x011A0031", + "defaultValue": "0x00000000", + "description": "Resolution 7680x4320", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines 7680x4320 @ 30Hz CTA timing support."}, + {"mask": "0x00000002", "description": "Defines 7680x4320 @ 30Hz RBv1 timing support."}, + {"mask": "0x00000004", "description": "Defines 7680x4320 @ 30Hz RBv2 timing support."}, + {"mask": "0x00000010", "description": "Defines 7680x4320 @ 60Hz CTA timing support."}, + {"mask": "0x00000020", "description": "Defines 7680x4320 @ 60Hz RBv1 timing support."}, + {"mask": "0x00000040", "description": "Defines 7680x4320 @ 60Hz RBv2 timing support."}, + {"mask": "0x00000100", "description": "Defines 7680x4320 @ 120Hz CTA timing support."}, + {"mask": "0x00000200", "description": "Defines 7680x4320 @ 120Hz RBv1 timing support."}, + {"mask": "0x00000400", "description": "Defines 7680x4320 @ 120Hz RBv2 timing support."}, + {"mask": "0x00002000", "description": "Defines 7680x4320 @ 144Hz RBv1 timing support.."}, + {"mask": "0x00004000", "description": "Defines 7680x4320 @ 144Hz RBv2 timing support."}, + {"mask": "0x00008000", "description": "Defines 7680x4320 @ 144Hz OVT timing support."}, + {"mask": "0x00020000", "description": "Defines 7680x4320 @ 240Hz RBv1 timing support."}, + {"mask": "0x00040000", "description": "Defines 7680x4320 @ 240Hz RBv2 timing support."}, + {"mask": "0x00080000", "description": "Defines 7680x4320 @ 240Hz OVT timing support."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_VMT_7680_4320", + "name": "Resolution supported by Sink DUT", + "type": 12 + }, + { + "configId": "0x011A0032", + "defaultValue": "0x00000000", + "description": "Resolution 10240x4320", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines 10240x4320 @ 30Hz CTA timing support."}, + {"mask": "0x00000002", "description": "Defines 10240x4320 @ 30Hz RBv1 timing support."}, + {"mask": "0x00000004", "description": "Defines 10240x4320 @ 30Hz RBv2 timing support."}, + {"mask": "0x00000010", "description": "Defines 10240x4320 @ 60Hz CTA timing support."}, + {"mask": "0x00000020", "description": "Defines 10240x4320 @ 60Hz RBv1 timing support."}, + {"mask": "0x00000040", "description": "Defines 10240x4320 @ 60Hz RBv2 timing support."}, + {"mask": "0x00000100", "description": "Defines 10240x4320 @ 120Hz CTA timing support."}, + {"mask": "0x00000200", "description": "Defines 10240x4320 @ 120Hz RBv1 timing support."}, + {"mask": "0x00000400", "description": "Defines 10240x4320 @ 120Hz RBv2 timing support."}, + {"mask": "0x00002000", "description": "Defines 10240x4320 @ 144Hz RBv1 timing support.."}, + {"mask": "0x00004000", "description": "Defines 10240x4320 @ 144Hz RBv2 timing support."}, + {"mask": "0x00008000", "description": "Defines 10240x4320 @ 144Hz OVT timing support."}, + {"mask": "0x00020000", "description": "Defines 10240x4320 @ 240Hz RBv1 timing support."}, + {"mask": "0x00040000", "description": "Defines 10240x4320 @ 240Hz RBv2 timing support."}, + {"mask": "0x00080000", "description": "Defines 10240x4320 @ 240Hz OVT timing support."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_VMT_10240_4320", + "name": "Resolution supported by Sink DUT", + "type": 12 + }, + { + "configId": "0x011A0033", + "defaultValue": "0x00000000", + "description": "Resolution 15360x8640", + "bitList": + [ + {"mask": "0x00000001", "description": "Defines 15360x8640 @ 30Hz CTA timing support."}, + {"mask": "0x00000002", "description": "Defines 15360x8640 @ 30Hz RBv1 timing support."}, + {"mask": "0x00000004", "description": "Defines 15360x8640 @ 30Hz RBv2 timing support."}, + {"mask": "0x00000010", "description": "Defines 15360x8640 @ 60Hz CTA timing support."}, + {"mask": "0x00000020", "description": "Defines 15360x8640 @ 60Hz RBv1 timing support."}, + {"mask": "0x00000040", "description": "Defines 15360x8640 @ 60Hz RBv2 timing support."}, + {"mask": "0x00000100", "description": "Defines 15360x8640 @ 120Hz CTA timing support."}, + {"mask": "0x00000200", "description": "Defines 15360x8640 @ 120Hz RBv1 timing support."}, + {"mask": "0x00000400", "description": "Defines 15360x8640 @ 120Hz RBv2 timing support."}, + {"mask": "0x00002000", "description": "Defines 15360x8640 @ 144Hz RBv1 timing support.."}, + {"mask": "0x00004000", "description": "Defines 15360x8640 @ 144Hz RBv2 timing support."}, + {"mask": "0x00008000", "description": "Defines 15360x8640 @ 144Hz OVT timing support."}, + {"mask": "0x00020000", "description": "Defines 15360x8640 @ 240Hz RBv1 timing support."}, + {"mask": "0x00040000", "description": "Defines 15360x8640 @ 240Hz RBv2 timing support."}, + {"mask": "0x00080000", "description": "Defines 15360x8640 @ 240Hz OVT timing support."} + ], + "flag": 1, + "id": "TSI_DP20_SINKCTS_VMT_15360_8640", + "name": "Resolution supported by Sink DUT", + "type": 12 + }, + { + "configId": "0x011A0034", + "defaultValue": "0x00000000", + "description": "Defines the Debug mode configuration.", + "bitList": + [ + {"mask": "0x00000001", "description": "Force video check enabled"} + ], + "flag": 3, + "id": "TSI_DP20_SINKCTS_DEBUG_CONF", + "name": "Debug mode configuration.", + "type": 12 + } + ], + "id": "0x18", + "name": "DP 2.1 LL CTS", + "old_names": [ + "DP 2.1 LL CTS", + "DP 2.0 Link Layer Sink DUT CTS", + "DP 2.1 Link Layer Sink DUT CTS" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x19]snk_epr_power_source.json b/UniTAP/dev/modules/dut_tests/cfg/[0x19]snk_epr_power_source.json new file mode 100644 index 0000000..5f7dd8f --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x19]snk_epr_power_source.json @@ -0,0 +1,18 @@ +{ + "descriptions": [ + { + "configId": "0x11D0000", + "defaultValue": "5000", + "description": "Defines test timeout in milliseconds, default value is 5000ms.", + "flag": 3, + "id": "TSI_USBC_EPR_POWER_SOURCE_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout in milliseconds.", + "reportFormat": "Test timeout = %2ms
", + "type": 4 + } + ], + "id": "0x19", + "name": "EPR Power Source Tests" +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x20]src_dut_hdmi_dsc_cts.json b/UniTAP/dev/modules/dut_tests/cfg/[0x20]src_dut_hdmi_dsc_cts.json new file mode 100644 index 0000000..d42b507 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x20]src_dut_hdmi_dsc_cts.json @@ -0,0 +1,21 @@ +{ + "descriptions": [ + { + "configId": "0x11C0000", + "defaultValue": "10000", + "description": "Defines a test timeout, in milliseconds. Default setting is 10000ms. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_HDMI_SRCCTS_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + } + ], + "id": "0x20", + "name": "HDMI RX DSC LL CTS", + "old_names": [ + "HDMI LL CTS", + "HDMI 2.1 Source DUT DSC Tests" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x21]snk_dut_hdmi_dsc_cts.json b/UniTAP/dev/modules/dut_tests/cfg/[0x21]snk_dut_hdmi_dsc_cts.json new file mode 100644 index 0000000..acf173c --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x21]snk_dut_hdmi_dsc_cts.json @@ -0,0 +1,210 @@ +{ + "descriptions": [ + { + "configId": "0x11D0000", + "defaultValue": "1", + "description": "Defines the Test mode. Default value is 0. Available variants: 0 - Test with CDF and EDID, 1 - Test only with EDID, 2 - Test with VIC selection", + "enumerationVariants": "0 # Force VIC output\n1 # CTS test", + "flag": 3, + "id": "TSI_HDMI_SNKCTS_TEST_MODE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test Mode", + "type": 4 + }, + { + "configId": "0x11D0001", + "defaultValue": "10000", + "description": "Defines a test timeout, in milliseconds. Default setting is 10000ms. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_HDMI_SNKCTS_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x11D0002", + "defaultValue": "6", + "description": "Defines a MAX FRL Rate. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "enumerationVariants": "0 # None\n1 # 3G 3 Lane\n2 # 6G 3 Lane\n3 # 6G 4 Lane\n4 # 8G 4 Lane\n5 # 10G 4 Lane\n6 # 12G 4 Lane", + "flag": 3, + "id": "TSI_HDMI_SNKCTS_MAX_FRL_RATE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum FRL rate", + "type": 4 + }, + { + "configId": "0x11D0003", + "defaultValue": "600", + "description": "Defines a Sink Max TMDS Clock in MHz. Default setting is 600. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_HDMI_SNKCTS_MAX_TMDS_CLOCK", + "maxValue": 2147483647, + "minValue": 0, + "name": "Max TMDS Clock in MHz", + "type": 4 + }, + { + "configId": "0x11D0004", + "defaultValue": "0x70", + "description": "Defines a DUT capabilies. Default setting is 0x70. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "Is Sink DC 30bit. Default value: 0."}, + {"mask": "0x00000002", "description": "Is Sink DC 36bit. Default value: 0."}, + {"mask": "0x00000004", "description": "Is Sink DC 48bit. Default value: 0."}, + {"mask": "0x00000008", "description": "Is Sink DC Y444. Default value: 0."}, + {"mask": "0x00000010", "description": "Is Sink Supports DSC. Default value: 1."}, + {"mask": "0x00000020", "description": "Is Sink DSC 10bpc. Default value: 1."}, + {"mask": "0x00000040", "description": "Is Sink DSC 12bpc. Default value: 1."}, + {"mask": "0x00000080", "description": "Is Sink DSC Native 420. Default value: 0."}, + {"mask": "0x00000100", "description": "Is Sink DSC 16bpc. Default value: 0."}, + {"mask": "0x00000200", "description": "Is Sink Support DSC all bpp. Default value: 0."} + ], + "flag": 1, + "id": "TSI_HDMI_SNKCTS_DUT_CAPS", + "name": "DUT Capability flags.", + "type": 12 + }, + { + "configId": "0x11D0005", + "defaultValue": "6", + "description": "Defines a DSC MAX FRL Rate. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "enumerationVariants": "0 # None\n1 # 3G 3 Lane\n2 # 6G 3 Lane\n3 # 6G 4 Lane\n4 # 8G 4 Lane\n5 # 10G 4 Lane\n6 # 12G 4 Lane", + "flag": 3, + "id": "TSI_HDMI_SNKCTS_DSC_MAX_FRL_RATE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum DSC FRL rate", + "type": 4 + }, + { + "configId": "0x11D0006", + "defaultValue": "0", + "description": "Defines a Sink HF EEODB Support. Default setting is 0. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_HDMI_SNKCTS_HF_EEODB_SUPPORT", + "maxValue": 2147483647, + "minValue": 0, + "name": "HF EEODB Support", + "type": 4 + }, + { + "configId": "0x11D0007", + "defaultValue": "0x8FF8C600", + "description": "Defines a support of DSC Video formats. Default setting is 0x8FF8C600. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000004", "description": "VIC 63 1920x1080 120Hz (16:9)"}, + {"mask": "0x00000008", "description": "VIC 64 1920x1080 100Hz (16:9)"}, + {"mask": "0x00000010", "description": "VIC 77 1920x1080 100Hz (64:27)"}, + {"mask": "0x00000020", "description": "VIC 78 1920x1080 120Hz (64:27)"}, + {"mask": "0x00000001", "description": "VIC 91 2560x1080 100Hz (64:27)"}, + {"mask": "0x00000002", "description": "VIC 92 2560x1080 120Hz (64:27)"}, + {"mask": "0x00000040", "description": "VIC 93 3840x2160 24Hz (16:9)"}, + {"mask": "0x00000080", "description": "VIC 94 3840x2160 25Hz (16:9)"}, + {"mask": "0x00000100", "description": "VIC 95 3840x2160 30Hz (16:9)"}, + {"mask": "0x00000200", "description": "VIC 96 3840x2160 50Hz (16:9)"}, + {"mask": "0x00000400", "description": "VIC 97 3840x2160 60Hz (16:9)"}, + {"mask": "0x00000800", "description": "VIC 98 4096x2160 24Hz (256:135)"}, + {"mask": "0x00001000", "description": "VIC 99 4096x2160 25Hz (256:135)"}, + {"mask": "0x00002000", "description": "VIC 100 4096x2160 30Hz (256:135)"}, + {"mask": "0x00004000", "description": "VIC 101 4096x2160 50Hz (256:135)"}, + {"mask": "0x00008000", "description": "VIC 102 4096x2160 60Hz (256:135)"}, + {"mask": "0x00010000", "description": "VIC 103 3840x2160 24Hz (64:27)"}, + {"mask": "0x00020000", "description": "VIC 104 3840x2160 25Hz (64:27)"}, + {"mask": "0x00040000", "description": "VIC 105 3840x2160 30Hz (64:27)"}, + {"mask": "0x00080000", "description": "VIC 106 3840x2160 50Hz (64:27)"}, + {"mask": "0x00100000", "description": "VIC 107 3840x2160 60Hz (64:27)"}, + {"mask": "0x00200000", "description": "VIC 114 3840x2160 48Hz (16:9)"}, + {"mask": "0x00400000", "description": "VIC 115 4096x2160 48Hz (256:135)"}, + {"mask": "0x00800000", "description": "VIC 116 3840x2160 48Hz (64:27)"}, + {"mask": "0x01000000", "description": "VIC 117 3840x2160 100Hz (64:27)"}, + {"mask": "0x02000000", "description": "VIC 118 3840x2160 120Hz (64:27)"}, + {"mask": "0x04000000", "description": "VIC 119 3840x2160 100Hz (64:27)"}, + {"mask": "0x08000000", "description": "VIC 120 3840x2160 120Hz (64:27)"}, + {"mask": "0x10000000", "description": "VIC 121 5120x2160 24Hz (64:27)"}, + {"mask": "0x20000000", "description": "VIC 122 5120x2160 25Hz (64:27)"}, + {"mask": "0x40000000", "description": "VIC 123 5120x2160 30Hz (64:27)"}, + {"mask": "0x80000000", "description": "VIC 124 5120x2160 48Hz (64:27)"} + ], + "flag": 1, + "id": "TSI_HDMI_SNKCTS_DSC_VIDEO_FORMAT", + "name": "DSC Video formats 1.", + "type": 12 + }, + { + "configId": "0x11D0008", + "defaultValue": "0x3000000F", + "description": "Defines a support of DSC Video formats 2. Default setting is 0x3000000F. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "VIC 125 5120x2160 50Hz (64:27)"}, + {"mask": "0x00000002", "description": "VIC 126 5120x2160 60Hz (64:27)"}, + {"mask": "0x00000004", "description": "VIC 127 5120x2160 100Hz (64:27)"}, + {"mask": "0x00000008", "description": "VIC 193 5120x2160 120Hz (64:27)"}, + {"mask": "0x00000010", "description": "VIC 194 7680x4320 24Hz (16:9)"}, + {"mask": "0x00000020", "description": "VIC 195 7680x4320 25Hz (16:9)"}, + {"mask": "0x00000040", "description": "VIC 196 7680x4320 30Hz (16:9)"}, + {"mask": "0x00000080", "description": "VIC 197 7680x4320 48Hz (16:9)"}, + {"mask": "0x00000100", "description": "VIC 198 7680x4320 50Hz (16:9)"}, + {"mask": "0x00000200", "description": "VIC 199 7680x4320 60Hz (16:9)"}, + {"mask": "0x00000400", "description": "VIC 200 7680x4320 100Hz (16:9)"}, + {"mask": "0x00000800", "description": "VIC 201 7680x4320 120Hz (16:9)"}, + {"mask": "0x00001000", "description": "VIC 202 7680x4320 24Hz (64:27)"}, + {"mask": "0x00002000", "description": "VIC 203 7680x4320 25Hz (64:27)"}, + {"mask": "0x00004000", "description": "VIC 204 7680x4320 30Hz (64:27)"}, + {"mask": "0x00008000", "description": "VIC 205 7680x4320 48Hz (64:27)"}, + {"mask": "0x00010000", "description": "VIC 206 7680x4320 50Hz (64:27)"}, + {"mask": "0x00020000", "description": "VIC 207 7680x4320 60Hz (64:27)"}, + {"mask": "0x00040000", "description": "VIC 208 7680x4320 100Hz (64:27)"}, + {"mask": "0x00080000", "description": "VIC 209 7680x4320 120Hz (64:27)"}, + {"mask": "0x00100000", "description": "VIC 210 10240x4320 24Hz (64:27)"}, + {"mask": "0x00200000", "description": "VIC 211 10240x4320 25Hz (64:27)"}, + {"mask": "0x00400000", "description": "VIC 212 10240x4320 30Hz (64:27)"}, + {"mask": "0x00800000", "description": "VIC 213 10240x4320 48Hz (64:27)"}, + {"mask": "0x01000000", "description": "VIC 214 10240x4320 50Hz (64:27)"}, + {"mask": "0x02000000", "description": "VIC 215 10240x4320 60Hz (64:27)"}, + {"mask": "0x04000000", "description": "VIC 216 10240x4320 100Hz (64:27)"}, + {"mask": "0x08000000", "description": "VIC 217 10240x4320 120Hz (64:27)"}, + {"mask": "0x10000000", "description": "VIC 218 4096x2160 100Hz (256:135)"}, + {"mask": "0x20000000", "description": "VIC 219 4096x2160 120Hz (256:135)"} + ], + "flag": 1, + "id": "TSI_HDMI_SNKCTS_DSC_VIDEO_FORMAT_2", + "name": "DSC Video formats 2.", + "type": 12 + }, + { + "configId": "0x11D0009", + "defaultValue": "4", + "description": "Defines a maxumim DSC slice number. Default setting is 4. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "enumerationVariants": "0 # 0 (DSC 1.2a is not supported)\n1 # 1 (up to 1 slice and 340MHz per slice)\n2 # 2 (up to 2 slices and 340MHz per slice)\n3 # 3 (up to 4 slices and 340MHz per slice)\n4 # 4 (up to 8 slices and 340MHz per slice)\n5 # 5 (up to 8 slices and 400MHz per slice)\n6 # 6 (up to 12 slices and 400MHz per slice)\n7 # 7 (up to 16 slices and 600MHz per slice)", + "flag": 3, + "id": "TSI_HDMI_SNKCTS_DSC_MAX_SLICES", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC Max Slices", + "type": 4 + }, + { + "configId": "0x11D000A", + "defaultValue": "11", + "description": "Defines a total DSC chunk size in KBytes. Default setting is 11. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_HDMI_SNKCTS_DSC_TOTAL_CHUNK_BYTES", + "maxValue": 2147483647, + "minValue": 0, + "name": "DSC Max Slices", + "type": 4 + } + ], + "id": "0x21", + "name": "HDMI TX DSC LL CTS", + "old_names": [ + "HDMI LL CTS", + "HDMI 2.1 Sink DUT DSC Tests" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x22]snk_dut_hdmi_continuity.json b/UniTAP/dev/modules/dut_tests/cfg/[0x22]snk_dut_hdmi_continuity.json new file mode 100644 index 0000000..8af9c63 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x22]snk_dut_hdmi_continuity.json @@ -0,0 +1,79 @@ +{ + "descriptions": [ + { + "configId": "0x011E0000", + "defaultValue": "60", + "description": "Total test time in seconds. Default value is 60s.", + "flag": 3, + "id": "TSI_HDMI_SNKCTS_TEST_TIME", + "maxValue": 2147483647, + "minValue": 0, + "name": "Total test time, in seconds", + "type": 4 + }, + { + "configId": "0x011E0001", + "defaultValue": "1", + "description": "Check status period in seconds. Default value is 1s.", + "flag": 3, + "id": "TSI_HDMI_SNKCTS_STATUS_PERIOD", + "maxValue": 2147483647, + "minValue": 0, + "name": "Period, in seconds", + "type": 4 + }, + { + "configId": "0x011E0002", + "defaultValue": "0", + "description": "Stop testing when status fail", + "flag": 3, + "enumerationVariants": "0 # False\n1 # True", + "id": "TSI_HDMI_SNKCTS_STOP_TESTING", + "name": "Stop testing when status fail", + "type": 4 + }, + { + "configId": "0x011E0003", + "defaultValue": "1", + "description": "Enable to check SCDC version", + "flag": 3, + "enumerationVariants": "0 # False\n1 # True", + "id": "TSI_HDMI_SNKCTS_SCDC_VERSION", + "name": "Enable to check SCDC version", + "type": 4 + }, + { + "configId": "0x011E0004", + "defaultValue": "1", + "description": "Enable to check SCDC status flag", + "flag": 3, + "enumerationVariants": "0 # False\n1 # True", + "id": "TSI_HDMI_SNKCTS_SCDC_STATUS", + "name": "Enable to check SCDC status flag ", + "type": 4 + }, + { + "configId": "0x011E0005", + "defaultValue": "1", + "description": "Enable to check SCDC error counters", + "flag": 3, + "enumerationVariants": "0 # False\n1 # True", + "id": "TSI_HDMI_SNKCTS_SCDC_ERROR", + "name": "Enable to check SCDC error counters", + "type": 4 + }, + { + "configId": "0x011E0006", + "defaultValue": "10", + "description": "SDCD error counter fail threshold. Default value is 10.", + "flag": 3, + "id": "TSI_HDMI_SNKCTS_SCDC_ERROR_COUNTER", + "maxValue": 2147483647, + "minValue": 0, + "name": "SDCD error counter fail threshold", + "type": 4 + } + ], + "id": "0x22", + "name": "Connection Test" +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x23]snk_dut_hdmi_gaming.json b/UniTAP/dev/modules/dut_tests/cfg/[0x23]snk_dut_hdmi_gaming.json new file mode 100644 index 0000000..e7ce346 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x23]snk_dut_hdmi_gaming.json @@ -0,0 +1,172 @@ +{ + "descriptions": [ + { + "configId": "0x2100", + "defaultValue": "0", + "description": "Does the Sink support Fast Vactive (FVA), also known as Quick Frame Transport (QFT)?", + "flag": 3, + "id": "TSI_HDMI_GAMING_FVA", + "maxValue": 1, + "minValue": 0, + "name": "Sink_Supports_FVA", + "type": 3, + "enumerationVariants": "0 # NO\n1 # YES" + }, + { + "configId": "0x2101", + "defaultValue": "1", + "description": "Does the Sink support Variable Refresh Rate (VRR)", + "flag": 3, + "id": "TSI_HDMI_GAMING_VRR", + "maxValue": 1, + "minValue": 0, + "name": "Sink_Supports_VRR", + "type": 3, + "enumerationVariants": "0 # NO\n1 # YES" + }, + { + "configId": "0x2102", + "defaultValue": "0", + "description": "Does the Sink support Negative Mvrr values?", + "flag": 3, + "id": "TSI_HDMI_GAMING_NMVRR", + "maxValue": 1, + "minValue": 0, + "name": "Sink_Supports_Negative_MVRR", + "type": 3, + "enumerationVariants": "0 # NO\n1 # YES" + }, + { + "configId": "0x2103", + "defaultValue": "40", + "description": "What is the Sink’s supported VRR Range? Minimum value.\n", + "flag": 3, + "id": "TSI_HDMI_GAMING_RANGE_MIN", + "maxValue": 480, + "minValue": 20, + "name": "Sink_Supported_VRR_Range VRRMIN", + "type": 4 + }, + { + "configId": "0x2104", + "defaultValue": "0", + "description": "What is the Sink’s supported VRR Range? Maximum value or BRR\n", + "flag": 3, + "id": "TSI_HDMI_GAMING_RANGE_MAX", + "maxValue": 480, + "minValue": 20, + "name": "Sink_Supported_VRR_Range VRRMAX or 0 for BRR", + "type": 4 + }, + { + "configId": "0x2105", + "defaultValue": "0", + "description": "Does the Sink implement Auto Low-Latency Mode (ALLM)?", + "flag": 3, + "id": "TSI_HDMI_GAMING_ALLM", + "maxValue": 1, + "minValue": 0, + "name": "Sink_Supports_ALLM", + "type": 3, + "enumerationVariants": "0 # NO\n1 # YES" + }, + { + "configId": "0x2106", + "defaultValue": "0", + "description": "Which display processing mode is the preferred mode with normal latency?", + "flag": 3, + "id": "TSI_HDMI_GAMING_NLM", + "maxValue": 2, + "minValue": 0, + "name": "Sink_Preferred_NLM", + "type": 4, + "enumerationVariants": "0 # Cinema\n1 # Vivid\n2 # Normal" + }, + { + "configId": "0x2107", + "defaultValue": "0", + "description": "Which display processing mode is the preferred mode to minimize latency?", + "flag": 3, + "id": "TSI_HDMI_GAMING_LLM", + "maxValue": 1, + "minValue": 0, + "name": "Sink_Preferred_LLM", + "type": 4, + "enumerationVariants": "0 # Game\n1 # PC" + }, + { + "configId": "0x2108", + "defaultValue": "1", + "description": "Does the Sink DUT support QMS-VRR?", + "flag": 3, + "id": "TSI_HDMI_GAMING_QMS", + "maxValue": 1, + "minValue": 0, + "name": "Sink_QMS_VRR_Supported", + "type": 3, + "enumerationVariants": "0 # NO\n1 # YES" + }, + { + "configId": "0x2109", + "defaultValue": "97", + "description": "What is the highest resolution (i.e., width x height) and base refresh rate (BRR) for which all TFRs up to Sink_QMS_Max_TFR_TMDS are supported by the Sink using TMDS?", + "flag": 3, + "id": "TSI_HDMI_GAMING_VIDEO_TMDS", + "maxValue": 255, + "minValue": 16, + "name": "Sink_QMS_Video_Format_TMDS", + "type": 4, + "enumerationVariants": "78 # VIC=78; 1920x1080@120\n97 # VIC=97; 3840x2160@60" + }, + { + "configId": "0x210A", + "defaultValue": "118", + "description": "What is the highest resolution (i.e., width x height) and base refresh rate (BRR) for which all TFRs up to Sink_QMS_Max_TFR_FRL are supported by the Sink using FRL?", + "flag": 3, + "id": "TSI_HDMI_GAMING_VIDEO_FRL", + "maxValue": 255, + "minValue": 16, + "name": "Sink_QMS_Video_Format_FRL", + "type": 4, + "enumerationVariants": "0 # N/A\n78 # VIC=78; 1920x1080@120\n97 # VIC=97; 3840x2160@60\n118 # VIC=118; 3840x2160@120" + }, + { + "configId": "0x210B", + "defaultValue": "1", + "description": "If the Sink DUT supports QMS-VRR, what is the minimum Next_TFR value supported? (Refer to the Next_TFR field definition in HDMI 2.1a Table 10-37.)", + "flag": 3, + "id": "TSI_HDMI_GAMING_MIN_TFR", + "maxValue": 13, + "minValue": 0, + "name": "Sink_QMS_Min_TFR", + "type": 4, + "enumerationVariants": "0 # N/A\n1 # TFR=1; 24/1.001\n2 # TFR=2; 24\n3 # TFR=3; 25\n4 # TFR=4; 30/1.001\n5 # TFR=5; 30\n6 # TFR=6; 48/1.001\n7 # TFR=7; 48\n8 # TFR=8; 50\n9 # TFR=9; 60/1.001\n10 # TFR=10; 60\n11 # TFR=11; 100\n12 # TFR=12; 120/1.001\n13 # TFR=13; 120" + }, + { + "configId": "0x210C", + "defaultValue": "10", + "description": "If the Sink DUT supports QMS-VRR, what is the maximum Next_TFR value supported using TMDS? (Refer to the Next_TFR field definition in HDMI 2.1a Table 10-37.)", + "flag": 3, + "id": "TSI_HDMI_GAMING_MAX_TFR_TMDS", + "maxValue": 13, + "minValue": 0, + "name": "Sink_QMS_Max_TFR_TMDS", + "type": 4, + "enumerationVariants": "0 # N/A\n1 # TFR=1; 24/1.001\n2 # TFR=2; 24\n3 # TFR=3; 25\n4 # TFR=4; 30/1.001\n5 # TFR=5; 30\n6 # TFR=6; 48/1.001\n7 # TFR=7; 48\n8 # TFR=8; 50\n9 # TFR=9; 60/1.001\n10 # TFR=10; 60\n11 # TFR=11; 100\n12 # TFR=12; 120/1.001\n13 # TFR=13; 120" + }, + { + "configId": "0x210D", + "defaultValue": "13", + "description": "If the Sink DUT supports QMS-VRR, what is the maximum Next_TFR value supported using FRL? (Refer to the Next_TFR field definition in HDMI 2.1a Table 10-37.)", + "flag": 3, + "id": "TSI_HDMI_GAMING_MAX_TFR_FRL", + "maxValue": 13, + "minValue": 0, + "name": "Sink_QMS_Max_TFR_FRL", + "type": 4, + "enumerationVariants": "0 # N/A\n1 # TFR=1; 24/1.001\n2 # TFR=2; 24\n3 # TFR=3; 25\n4 # TFR=4; 30/1.001\n5 # TFR=5; 30\n6 # TFR=6; 48/1.001\n7 # TFR=7; 48\n8 # TFR=8; 50\n9 # TFR=9; 60/1.001\n10 # TFR=10; 60\n11 # TFR=11; 100\n12 # TFR=12; 120/1.001\n13 # TFR=13; 120" + } + ], + "id": "0x23", + "name": "HDMI CTS MOI (VRR-QMS)" +} diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x24]snk_dut_hdmi_cable_check.json b/UniTAP/dev/modules/dut_tests/cfg/[0x24]snk_dut_hdmi_cable_check.json new file mode 100644 index 0000000..2d94a2c --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x24]snk_dut_hdmi_cable_check.json @@ -0,0 +1,96 @@ +{ + "descriptions": [ + { + "configId": "0x11F0000", + "defaultValue": "120", + "description": "Defines a test timeout, in seconds. Default setting is 120s. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_HDMI_SNKCTS_CABLE_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in seconds", + "type": 4 + }, + { + "configId": "0x11F0001", + "defaultValue": "2", + "description": "Defines a Cable test mode. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "enumerationVariants": "0 # High speed\n1 # Low speed\n2 # All", + "flag": 3, + "id": "TSI_HDMI_SNKCTS_CABLE_TEST_MODE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Cable test mode", + "type": 4 + }, + { + "configId": "0x11F0002", + "defaultValue": "1", + "description": "Defines a MIN FRL Rate. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "enumerationVariants": "0 # None\n1 # 3G 3 Lane\n2 # 6G 3 Lane\n3 # 6G 4 Lane\n4 # 8G 4 Lane\n5 # 10G 4 Lane\n6 # 12G 4 Lane", + "flag": 3, + "id": "TSI_HDMI_SNKCTS_CABLE_MIN_FRL_RATE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Minimum FRL rate", + "type": 4 + }, + { + "configId": "0x11F0003", + "defaultValue": "6", + "description": "Defines a MAX FRL Rate. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "enumerationVariants": "0 # None\n1 # 3G 3 Lane\n2 # 6G 3 Lane\n3 # 6G 4 Lane\n4 # 8G 4 Lane\n5 # 10G 4 Lane\n6 # 12G 4 Lane", + "flag": 3, + "id": "TSI_HDMI_SNKCTS_CABLE_MAX_FRL_RATE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Maximum FRL rate", + "type": 4 + }, + { + "configId": "0x11F0004", + "defaultValue": "10", + "description": "Threshold of allowed errors per lane. Default setting is 10. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_HDMI_SNKCTS_CABLE_ERROR_PER_LANE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Threshold of allowed errors per lane", + "type": 4 + }, + { + "configId": "0x11F0005", + "defaultValue": "5", + "description": "Defines a timeout for capturing errors, in seconds. Default setting is 5s. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "flag": 3, + "id": "TSI_HDMI_SNKCTS_CABLE_CAPTURE_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Timeout for capturing errors, in seconds", + "type": 4 + }, + { + "configId": "0x11F0006", + "defaultValue": "0x1F", + "description": "Defines a Low Speed Line selection. Default setting is 0x1F. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "bitList": + [ + {"mask": "0x00000001", "description": "Enable HPD line test."}, + {"mask": "0x00000002", "description": "Enable I2C line test."}, + {"mask": "0x00000004", "description": "Enable CEC line test."}, + {"mask": "0x00000008", "description": "Enable 5V line test."}, + {"mask": "0x00000010", "description": "Enable utility line test."} + ], + "flag": 3, + "id": "TSI_HDMI_SNKCTS_CABLE_LOW_SPEED_LINE", + "name": "Low Speed Line Select", + "type": 12 + } + ], + "id": "0x24", + "name": "Cable Test", + "old_names": [ + "HDMI LL CTS", + "HDMI Sink Cable Tests" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x25]src_dut_hdmi_hdcp_23_1a.json b/UniTAP/dev/modules/dut_tests/cfg/[0x25]src_dut_hdmi_hdcp_23_1a.json new file mode 100644 index 0000000..6549ad5 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x25]src_dut_hdmi_hdcp_23_1a.json @@ -0,0 +1,20 @@ +{ + "descriptions": [ + { + "configId": "0x1200000", + "defaultValue": "30000", + "description": "Defines test timeout in milliseconds, default value is 30000ms.", + "flag": 3, + "id": "TSI_HDMI_HDCP_1A_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + } + ], + "id": "0x25", + "name": "HDMI HDCP 2.3 CTS 1A", + "old_names": [ + "HDMI HDCP 2.3 CTS 1A Test Set" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x26]src_dut_hdmi_hdcp_23_1b.json b/UniTAP/dev/modules/dut_tests/cfg/[0x26]src_dut_hdmi_hdcp_23_1b.json new file mode 100644 index 0000000..8b84646 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x26]src_dut_hdmi_hdcp_23_1b.json @@ -0,0 +1,20 @@ +{ + "descriptions": [ + { + "configId": "0x1210000", + "defaultValue": "30000", + "description": "Defines test timeout in milliseconds, default value is 30000ms.", + "flag": 3, + "id": "TSI_HDMI_HDCP_1B_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + } + ], + "id": "0x26", + "name": "HDMI HDCP 2.3 CTS 1B", + "old_names": [ + "HDMI HDCP 2.3 CTS 1B Test Set" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x27]snk_dut_hdmi_hdcp_23_2c.json b/UniTAP/dev/modules/dut_tests/cfg/[0x27]snk_dut_hdmi_hdcp_23_2c.json new file mode 100644 index 0000000..8a2acce --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x27]snk_dut_hdmi_hdcp_23_2c.json @@ -0,0 +1,42 @@ +{ + "descriptions": [ + { + "configId": "0x1220000", + "defaultValue": "30000", + "description": "Defines test timeout in milliseconds, default value is 30000ms.", + "flag": 3, + "id": "TSI_HDMI_HDCP_2C_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + }, + { + "configId": "0x1220001", + "defaultValue": "0", + "description": "Defines a flag to do or not link training before starting test, default value is 0.", + "enumerationVariants": "0 # NO\n1 # YES", + "flag": 3, + "id": "TSI_HDMI_HDCP_2C_LT_BEFORE_TEST", + "name": "Link training before starting test", + "type": 4 + }, + { + "configId": "0x1220002", + "defaultValue": "0", + "description": "Defines a Link rate for link training. Important: This CI is still potential subject for changes due to the CTS specification not being officially ready", + "enumerationVariants": "0 # TMDS mode\n1 # 3G 3 Lane\n2 # 6G 3 Lane\n3 # 6G 4 Lane\n4 # 8G 4 Lane\n5 # 10G 4 Lane\n6 # 12G 4 Lane", + "flag": 3, + "id": "TSI_HDMI_HDCP_2C_LINK_RATE", + "maxValue": 2147483647, + "minValue": 0, + "name": "Link rate for link training", + "type": 4 + } + ], + "id": "0x27", + "name": "HDMI HDCP 2.3 CTS 2C", + "old_names": [ + "HDMI HDCP 2.3 CTS 2C Test Set" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x28]src_dut_hdmi_hdcp_23_3a.json b/UniTAP/dev/modules/dut_tests/cfg/[0x28]src_dut_hdmi_hdcp_23_3a.json new file mode 100644 index 0000000..e95a6a3 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x28]src_dut_hdmi_hdcp_23_3a.json @@ -0,0 +1,20 @@ +{ + "descriptions": [ + { + "configId": "0x1230000", + "defaultValue": "30000", + "description": "Defines test timeout in milliseconds, default value is 30000ms.", + "flag": 3, + "id": "TSI_HDMI_HDCP_3A_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + } + ], + "id": "0x28", + "name": "HDMI HDCP 2.3 CTS 3A", + "old_names": [ + "HDMI HDCP 2.3 CTS 3A Test Set" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x29]src_dut_hdmi_hdcp_23_3b.json b/UniTAP/dev/modules/dut_tests/cfg/[0x29]src_dut_hdmi_hdcp_23_3b.json new file mode 100644 index 0000000..2af90e4 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x29]src_dut_hdmi_hdcp_23_3b.json @@ -0,0 +1,20 @@ +{ + "descriptions": [ + { + "configId": "0x1240000", + "defaultValue": "30000", + "description": "Defines test timeout in milliseconds, default value is 30000ms.", + "flag": 3, + "id": "TSI_HDMI_HDCP_3B_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + } + ], + "id": "0x29", + "name": "HDMI HDCP 2.3 CTS 3B", + "old_names": [ + "HDMI HDCP 2.3 CTS 3B Test Set" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/[0x2A]snk_dut_hdmi_hdcp_23_3c.json b/UniTAP/dev/modules/dut_tests/cfg/[0x2A]snk_dut_hdmi_hdcp_23_3c.json new file mode 100644 index 0000000..2906d43 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/cfg/[0x2A]snk_dut_hdmi_hdcp_23_3c.json @@ -0,0 +1,20 @@ +{ + "descriptions": [ + { + "configId": "0x1250000", + "defaultValue": "30000", + "description": "Defines test timeout in milliseconds, default value is 30000ms.", + "flag": 3, + "id": "TSI_HDMI_HDCP_3C_TIMEOUT", + "maxValue": 2147483647, + "minValue": 0, + "name": "Test timeout, in milliseconds", + "type": 4 + } + ], + "id": "0x2A", + "name": "HDMI HDCP 2.3 CTS 3C", + "old_names": [ + "HDMI HDCP 2.3 CTS 3C Test Set" + ] +} \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/cfg/hdr10/hdr10_edid.bin b/UniTAP/dev/modules/dut_tests/cfg/hdr10/hdr10_edid.bin new file mode 100644 index 0000000..e064243 Binary files /dev/null and b/UniTAP/dev/modules/dut_tests/cfg/hdr10/hdr10_edid.bin differ diff --git a/UniTAP/dev/modules/dut_tests/cfg/hdr10/sstm_edid.bin b/UniTAP/dev/modules/dut_tests/cfg/hdr10/sstm_edid.bin new file mode 100644 index 0000000..8b73c12 Binary files /dev/null and b/UniTAP/dev/modules/dut_tests/cfg/hdr10/sstm_edid.bin differ diff --git a/UniTAP/dev/modules/dut_tests/cfg/hdr10/sstm_edid1_0.bin b/UniTAP/dev/modules/dut_tests/cfg/hdr10/sstm_edid1_0.bin new file mode 100644 index 0000000..e725c90 Binary files /dev/null and b/UniTAP/dev/modules/dut_tests/cfg/hdr10/sstm_edid1_0.bin differ diff --git a/UniTAP/dev/modules/dut_tests/cfg/hdr10/sstm_edid5_1.bin b/UniTAP/dev/modules/dut_tests/cfg/hdr10/sstm_edid5_1.bin new file mode 100644 index 0000000..a990865 Binary files /dev/null and b/UniTAP/dev/modules/dut_tests/cfg/hdr10/sstm_edid5_1.bin differ diff --git a/UniTAP/dev/modules/dut_tests/cfg/hdr10/sstm_edid7_2.bin b/UniTAP/dev/modules/dut_tests/cfg/hdr10/sstm_edid7_2.bin new file mode 100644 index 0000000..27ed427 Binary files /dev/null and b/UniTAP/dev/modules/dut_tests/cfg/hdr10/sstm_edid7_2.bin differ diff --git a/UniTAP/dev/modules/dut_tests/cfg/hdr10/sstm_edid9_3.bin b/UniTAP/dev/modules/dut_tests/cfg/hdr10/sstm_edid9_3.bin new file mode 100644 index 0000000..ce3871d Binary files /dev/null and b/UniTAP/dev/modules/dut_tests/cfg/hdr10/sstm_edid9_3.bin differ diff --git a/UniTAP/dev/modules/dut_tests/cfg/hdr10/vsif1.bin b/UniTAP/dev/modules/dut_tests/cfg/hdr10/vsif1.bin new file mode 100644 index 0000000..3cff59c Binary files /dev/null and b/UniTAP/dev/modules/dut_tests/cfg/hdr10/vsif1.bin differ diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/__init__.py b/UniTAP/dev/modules/dut_tests/dut_default_params/__init__.py new file mode 100644 index 0000000..6e3507e --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/__init__.py @@ -0,0 +1,56 @@ +from typing import TypeVar + +from .audio_test import AudioTestParam +from .crc_video_tests import CrcVideoTestParam, BrokenFrameExportFormat, CrcVideoTestBpp +from .cec_tests import CecFunctionalTestParam +from .dp1_4_sink_tests import Dp14SinkTestParam +from .dp2_1_sink_tests import Dp21SinkTestParam +from .dp1_4_source_tests import Dp14SourceDUTTestParam, PackedTimings1Lane, PackedTimings2Lane, \ + PackedTimings4Lane, EventIndication +from .dp2_1_source_tests import Dp21SourceDUTTestParam +from .dp_electrical_tests import DpElectricalTestParam +from .hdmi_electrical_tests import HdmiElectricalTestParam +from .usbc_electrical_tests import UsbcElectricalTestParam +from .hdcp_1a_tests import Hdcp1ATestParam +from .hdcp_1b_tests import Hdcp1BTestParam +from .hdcp_2c_tests import Hdcp2CTestParam +from .hdcp_3a_tests import Hdcp3ATestParam +from .hdcp_3b_tests import Hdcp3BTestParam +from .hdcp_3c_tests import Hdcp3CTestParam +from .hdr10_tests import Hdr10TestParam +from .pixel_video_test import VideoPixelTestParam +from .link_config_test import LinkConfigTestParam +from .vrr_tests import VrrSinkDUTTestParam, VrrSourceDUTTestParam +from .lttpr_tests import DpLttprTestParam +from .hdmi_sink_tests import HdmiSinkDUTTestParam, HdmiTestMode, HdmiFrlRate +from .hdmi_source_tests import HdmiSourceDUTTestParam +from .hdmi_sink_continuity_tests import HdmiSinkContinuityDUTTestParam +from .hdmi_sink_cable_check_tests import HdmiSinkCableCheckTestParam +from ..test_group_params_types import Param, get_param_list + +DUTTestParameters = TypeVar("DUTTestParameters", + AudioTestParam, + DpElectricalTestParam, + HdmiElectricalTestParam, + CecFunctionalTestParam, + CrcVideoTestParam, + LinkConfigTestParam, + UsbcElectricalTestParam, + Hdcp1ATestParam, + Hdcp1BTestParam, + Hdcp2CTestParam, + Hdcp3ATestParam, + Hdcp3BTestParam, + Hdcp3CTestParam, + VrrSinkDUTTestParam, + VrrSourceDUTTestParam, + Dp14SourceDUTTestParam, + Dp21SourceDUTTestParam, + Dp14SinkTestParam, + Dp21SinkTestParam, + VideoPixelTestParam, + DpLttprTestParam, + Hdr10TestParam, + HdmiSinkDUTTestParam, + HdmiSinkContinuityDUTTestParam, + HdmiSinkCableCheckTestParam) diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/audio_test.py b/UniTAP/dev/modules/dut_tests/dut_default_params/audio_test.py new file mode 100644 index 0000000..f46e972 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/audio_test.py @@ -0,0 +1,146 @@ +import os.path +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param + + +class AudioTestParam: + """ + Class `AudioTestParam` describes requirement parameters for audio test: + - Set and get `sample_rate`. Describes expected sampling rate of audio signal. + - Set and get `audio_frequency`. Describes expected audible (sine) frequency as Hz. + - Set and get `frequency_tolerance`. Describes allowed deviation from expected frequency as Hz. + - Set and get `audio_glitches_allowed`. Describes number of audio glitches allowed per test. + - Set and get `save_conditions`. Describes tested audio save conditions. + - Set and get `storage_folder`. Describes location where the captured audio is to be saved. + """ + __SAVE_CONDITION = {"None": 0, + 'Failed': 1, + 'All': 2} + + def __init__(self, json_obj): + self.__sample_rate = Param(json_obj["TSI_EXPECTED_SAMPLE_RATE"]) + self.__audio_frequency = Param(json_obj["TSI_EXPECTED_AUDIO_FREQUENCY"]) + self.__frequency_tolerance = Param(json_obj["TSI_AUDIO_FREQUENCY_TOLERANCE"]) + self.__glitch_detect_threshold = Param(json_obj["TSI_AUDIO_GLITCH_DETECT_TRESHOLD"]) + self.__audio_glitches_allowed = Param(json_obj["TSI_AUDIO_GLITCHES_ALLOWED"]) + self.__save_conditions = Param(json_obj["TSI_AUDIO_TEST_SAVE_CONDITIONS"]) + self.__storage_folder = Param(json_obj["TSI_AUDIO_TEST_STORAGE_FOLDER"]) + + @property + def sample_rate(self) -> int: + """ + Set and get sampling rate of audio signal. + + Returns: + object of int type + """ + return self.__sample_rate.default_value + + @sample_rate.setter + def sample_rate(self, sample_rate: int): + if not(self.__sample_rate.min_value < sample_rate < self.__sample_rate.max_value): + raise ValueError(f"Sample rate cannot be less than {self.__sample_rate.min_value} and more than " + f"{self.__sample_rate.max_value}.") + self.__sample_rate.default_value = sample_rate + + @property + def audio_frequency(self) -> int: + """ + Set and get audible (sine) frequency as Hz. + + Returns: + object of int type + """ + return self.__audio_frequency.default_value + + @audio_frequency.setter + def audio_frequency(self, audio_frequency: int): + if not(self.__audio_frequency.min_value < audio_frequency < self.__audio_frequency.max_value): + raise ValueError(f"Audio frequency cannot be less than {self.__audio_frequency.min_value} and more than " + f"{self.__audio_frequency.max_value}.") + self.__audio_frequency.default_value = audio_frequency + + @property + def frequency_tolerance(self) -> int: + """ + Set and get allowed deviation from expected frequency as Hz. + + Returns: + object of int type + """ + return self.__frequency_tolerance.default_value + + @frequency_tolerance.setter + def frequency_tolerance(self, frequency_tolerance: int): + if not(self.__frequency_tolerance.min_value < frequency_tolerance < self.__frequency_tolerance.max_value): + raise ValueError(f"Frequency tolerance cannot be less than {self.__frequency_tolerance.min_value} " + f"and more than {self.__frequency_tolerance.max_value}.") + self.__frequency_tolerance.default_value = frequency_tolerance + + @property + def audio_glitches_allowed(self) -> int: + """ + Set and get number of audio glitches allowed per test. + + Returns: + object of int type + """ + return self.__audio_glitches_allowed.default_value + + @audio_glitches_allowed.setter + def audio_glitches_allowed(self, audio_glitches_allowed: int): + if not(self.__audio_glitches_allowed.min_value < audio_glitches_allowed < self.__audio_glitches_allowed.max_value): + raise ValueError(f"Audio glitches allowed cannot be less than {self.__audio_glitches_allowed.min_value} " + f"and more than {self.__audio_glitches_allowed.max_value}.") + self.__audio_glitches_allowed.default_value = audio_glitches_allowed + + @property + def glitch_detect_threshold(self) -> int: + """ + Set and get number of audio glitches allowed per test. + + Returns: + object of int type + """ + return self.__glitch_detect_threshold.default_value + + @glitch_detect_threshold.setter + def glitch_detect_threshold(self, glitch_detect_threshold: int): + if not ( + self.__glitch_detect_threshold.min_value < glitch_detect_threshold < self.__glitch_detect_threshold.max_value): + raise ValueError(f"Glitch detect threshold cannot be less than {self.__glitch_detect_threshold.min_value} " + f"and more than {self.__glitch_detect_threshold.max_value}.") + self.__glitch_detect_threshold.default_value = glitch_detect_threshold + + @property + def save_conditions(self) -> str: + """ + Set and get tested audio save conditions. + + Returns: + object of str type + """ + return self.__save_conditions.default_value + + @save_conditions.setter + def save_conditions(self, save_conditions: str = 'None'): + if self.__SAVE_CONDITION.get(save_conditions) is None: + available_variants = '\n'.join(self.__SAVE_CONDITION.keys()) + raise ValueError(f"Incorrect input parameter {save_conditions}.\n" + f"Available variants: {available_variants}") + self.__save_conditions.default_value = self.__SAVE_CONDITION.get(save_conditions) + + @property + def storage_folder(self) -> str: + """ + Set and get location where the captured audio is to be saved. + + Returns: + object of str type + """ + return self.__storage_folder.default_value + + @storage_folder.setter + def storage_folder(self, storage_folder: str): + if self.__save_conditions.default_value != 0 and not os.path.exists(storage_folder): + raise ValueError('Incorrect input path. Path is not exist.') + self.__storage_folder.default_value = storage_folder diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/cec_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/cec_tests.py new file mode 100644 index 0000000..71c32fa --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/cec_tests.py @@ -0,0 +1,43 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param + + +class CecFunctionalTestParam: + """ + Class `CecFunctionalTestParam` describes requirement parameters for CEC tests: + - Set and get `timeout`. Describes test timeout, in milliseconds. + - Set and get `physical_address`. Describes Local CEC physical address. + """ + def __init__(self, json_obj): + self.__timeout = Param(json_obj["TSI_HDMI_RX_CEC_TIMEOUT"]) + self.__physical_address = Param(json_obj["TSI_HDMI_RX_CEC_LOCAL_PHY_ADDR"]) + + @property + def timeout(self) -> int: + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not (self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout + + @property + def physical_address(self) -> int: + """ + Set and get Local CEC physical address. + + Returns: + object of int type + """ + return self.__physical_address.default_value + + @physical_address.setter + def physical_address(self, physical_address: int): + self.__physical_address.default_value = physical_address diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/crc_video_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/crc_video_tests.py new file mode 100644 index 0000000..efda7a9 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/crc_video_tests.py @@ -0,0 +1,311 @@ +from enum import IntEnum +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param + + +class CrcVideoTestBpp(IntEnum): + BPP12 = 12 + BPP15 = 15 + BPP16 = 16 + BPP18 = 18 + BPP20 = 20 + BPP21 = 21 + BPP24 = 24 + BPP30 = 30 + BPP32 = 32 + BPP36 = 36 + BPP48 = 48 + + +class BrokenFrameExportFormat(IntEnum): + BIN = 0 + PPM = 1 + BMP = 2 + + +class CrcVideoTestParam: + """ + Class `CrcVideoTestParam` describes requirement parameters for CRC tests: + - Set and get `timeout`. Describes test timeout, in milliseconds. + - Set and get `number_frames_to_test`. Describes total number of frames to be tested. + - Set and get `number_reference_frames`. Describes number of reference frames. + - Set and get `number_frames_mismatch`. Describes number of bad frames allowed in single CRC tests. + - Set and get `reference_width`. Describes expected video width, in pixels. + - Set and get `reference_height`. Describes expected video weight, in pixels. + - Set and get `reference_color_depth`. Describes expected color depth, as bits per pixel `CrcVideoTestBpp`. + - Set and get `required_frame_rate`. Describes expected frame rate, in millihertz (mHz). + - Set and get `frame_rate_tolerance`. Describes frame rate tolerance, in millihertz (mHz). + - Set and get `reference_crc_values`. Describes CRC reference values. Each CRC set consists of 3 16-bit words. + - Set and get `motion_test_iteration`. Describes the number of iterations the defined CRC sequence must + be found in order to pass the test. + - Set and get `data_transfer_timeout`. Describes data transfer timeout in milliseconds. + - Set and get `failed_frames_folder`. Describes location where the failed frames are to be saved. + - Set and get `max_export_failed`. Describes the number of failed frames to be exported from the video test. + """ + def __init__(self, json_obj): + self.__timeout = Param(json_obj["TSI_CRC_TIMEOUT"]) + self.__number_frames_to_test = Param(json_obj["TSI_CRC_FRAMES_TO_TEST"]) + self.__number_reference_frames = Param(json_obj["TSI_CRC_REF_FRAME_COUNT"]) + self.__number_frames_mismatch = Param(json_obj["TSI_CRC_LIM_FRAME_MISMATCHES"]) + self.__reference_width = Param(json_obj["TSI_CRC_REF_WIDTH"]) + self.__reference_height = Param(json_obj["TSI_CRC_REF_HEIGHT"]) + self.__reference_color_depth = Param(json_obj["TSI_CRC_REF_COLORDEPTH"]) # CrcVideoTestBpp + self.__required_frame_rate = Param(json_obj["TSI_CRC_REQUIRED_FRAME_RATE"]) + self.__frame_rate_tolerance = Param(json_obj["TSI_CRC_FRAME_RATE_TOLERANCE"]) + self.__reference_crc_values = Param(json_obj["TSI_CRC_REFERENCE_CRC_VALUES"]) + self.__motion_test_iteration = Param(json_obj["TSI_CRC_MOTION_TEST_ITERATIONS"]) + self.__color_format = Param(json_obj["TSI_CRC_COLOR_FORMAT"]) # Do not provide. Must be 0 + self.__data_transfer_timeout = Param(json_obj["TSI_CRC_DATA_TRANSFER_TIMEOUT"]) + self.__failed_frames_folder = Param(json_obj["TSI_CRC_FAILED_FRAME_TARGET_FOLDER"]) + self.__max_export_failed = Param(json_obj["TSI_CRC_MAX_EXPORT_FAILED"]) + self.__export_format = Param(json_obj["TSI_CRC_EXPORT_FORMAT"]) + + @property + def timeout(self) -> int: + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout + + @property + def number_frames_to_test(self) -> int: + """ + Set and get total number of frames to be tested. + + Returns: + object of int type + """ + return self.__number_frames_to_test.default_value + + @number_frames_to_test.setter + def number_frames_to_test(self, number_frames_to_test: int): + if not(self.__number_frames_to_test.min_value < number_frames_to_test < self.__number_frames_to_test.max_value): + raise ValueError(f"Number frames to test cannot be less than {self.__number_frames_to_test.min_value} " + f"and more than {self.__number_frames_to_test.max_value}.") + self.__number_frames_to_test.default_value = number_frames_to_test + + @property + def number_reference_frames(self) -> int: + """ + Set and get number of reference frames. + + Returns: + object of int type + """ + return self.__number_reference_frames.default_value + + @number_reference_frames.setter + def number_reference_frames(self, number_reference_frames: int): + if not(self.__number_reference_frames.min_value < number_reference_frames < + self.__number_reference_frames.max_value): + raise ValueError(f"Number reference frames cannot be less than {self.__number_reference_frames.min_value} " + f"and more than {self.__number_reference_frames.max_value}.") + self.__number_reference_frames.default_value = number_reference_frames + + @property + def number_frames_mismatch(self) -> int: + """ + Set and get number of bad frames allowed in single CRC tests. + + Returns: + object of int type + """ + return self.__number_frames_mismatch.default_value + + @number_frames_mismatch.setter + def number_frames_mismatch(self, number_frames_mismatch: int): + if not(self.__number_frames_mismatch.min_value <= number_frames_mismatch < + self.__number_frames_mismatch.max_value): + raise ValueError(f"Number frames mismatch cannot be less than {self.__number_frames_mismatch.min_value} " + f"and more than {self.__number_frames_mismatch.max_value}.") + self.__number_frames_mismatch.default_value = number_frames_mismatch + + @property + def reference_width(self) -> int: + """ + Set and get expected video width, in pixels. + + Returns: + object of int type + """ + return self.__reference_width.default_value + + @reference_width.setter + def reference_width(self, reference_width: int): + if not(self.__reference_width.min_value < reference_width < self.__reference_width.max_value): + raise ValueError(f"Reference width cannot be less than {self.__reference_width.min_value} and more than " + f"{self.__reference_width.max_value}.") + self.__reference_width.default_value = reference_width + + @property + def reference_height(self) -> int: + """ + Set and get expected video weight, in pixels. + + Returns: + object of int type + """ + return self.__reference_height.default_value + + @reference_height.setter + def reference_height(self, reference_height: int): + if not(self.__reference_height.min_value < reference_height < self.__reference_height.max_value): + raise ValueError(f"Reference height cannot be less than {self.__reference_height.min_value} and more than " + f"{self.__reference_height.max_value}.") + self.__reference_height.default_value = reference_height + + @property + def reference_color_depth(self) -> int: + """ + Set and get expected color depth, as bits per pixel `CrcVideoTestBpp`. + + Returns: + object of int type + """ + return self.__reference_color_depth.default_value + + @reference_color_depth.setter + def reference_color_depth(self, reference_color_depth: CrcVideoTestBpp): + self.__reference_color_depth.default_value = reference_color_depth.value + + @property + def required_frame_rate(self) -> int: + """ + Set and get expected frame rate, in millihertz (mHz). + + Returns: + object of int type + """ + return self.__required_frame_rate.default_value + + @required_frame_rate.setter + def required_frame_rate(self, required_frame_rate: int): + if not(self.__required_frame_rate.min_value <= required_frame_rate < self.__required_frame_rate.max_value): + raise ValueError(f"Required frame rate cannot be less than {self.__required_frame_rate.min_value} " + f"and more than {self.__required_frame_rate.max_value}.") + self.__required_frame_rate.default_value = required_frame_rate + + @property + def frame_rate_tolerance(self) -> int: + """ + Set and get frame rate tolerance, in millihertz (mHz). + + Returns: + object of int type + """ + return self.__frame_rate_tolerance.default_value + + @frame_rate_tolerance.setter + def frame_rate_tolerance(self, frame_rate_tolerance: int): + if not(self.__frame_rate_tolerance.min_value <= frame_rate_tolerance < self.__frame_rate_tolerance.max_value): + raise ValueError(f"Frame rate tolerance cannot be less than {self.__frame_rate_tolerance.min_value} " + f"and more than {self.__frame_rate_tolerance.max_value}.") + self.__frame_rate_tolerance.default_value = frame_rate_tolerance + + @property + def reference_crc_values(self) -> list: + """ + Set and get CRC reference values. Each CRC set consists of 3 16-bit words. + + Returns: + object of list type + """ + return self.__reference_crc_values.default_value + + @reference_crc_values.setter + def reference_crc_values(self, reference_crc_values: list): + if len(reference_crc_values) <= 0: + raise ValueError(f"Length of reference crc list must be more that 0") + self.__reference_crc_values.default_value = reference_crc_values + + @property + def motion_test_iteration(self) -> int: + """ + Set and get number of iterations the defined CRC sequence must be found in order to pass the test. + + Returns: + object of int type + """ + return self.__motion_test_iteration.default_value + + @motion_test_iteration.setter + def motion_test_iteration(self, motion_test_iteration: int): + if not(self.__motion_test_iteration.min_value <= motion_test_iteration < self.__motion_test_iteration.max_value): + raise ValueError(f"Motion test iteration cannot be less motion_test_iteration " + f"{self.__motion_test_iteration.min_value} and more than " + f"{self.__motion_test_iteration.max_value}.") + self.__motion_test_iteration.default_value = motion_test_iteration + + @property + def data_transfer_timeout(self) -> int: + """ + Set and get data transfer timeout in milliseconds. + + Returns: + object of int type + """ + return self.__data_transfer_timeout.default_value + + @data_transfer_timeout.setter + def data_transfer_timeout(self, data_transfer_timeout: int): + if not(self.__data_transfer_timeout.min_value < data_transfer_timeout < self.__data_transfer_timeout.max_value): + raise ValueError(f"Data transfer timeout cannot be less than {self.__data_transfer_timeout.min_value} " + f"and more than {self.__data_transfer_timeout.max_value}.") + self.__data_transfer_timeout.default_value = data_transfer_timeout + + @property + def failed_frames_folder(self) -> str: + """ + Set and get location where the failed frames are to be saved. + + Returns: + object of int type + """ + return self.__failed_frames_folder.default_value + + @failed_frames_folder.setter + def failed_frames_folder(self, failed_frames_folder: str): + if len(failed_frames_folder) <= 0: + raise ValueError(f"Path length of folder list must be more that 0") + self.__failed_frames_folder.default_value = failed_frames_folder + + @property + def max_export_failed(self) -> int: + """ + Set and get number of failed frames to be exported from the video test. + + Returns: + object of int type + """ + return self.__max_export_failed.default_value + + @max_export_failed.setter + def max_export_failed(self, max_export_failed: int): + if not(self.__max_export_failed.min_value <= max_export_failed < self.__max_export_failed.max_value): + raise ValueError(f"Number of max export failed frames cannot be less than " + f"{self.__max_export_failed.min_value} and more than " + f"{self.__max_export_failed.max_value}.") + self.__max_export_failed.default_value = max_export_failed + + @property + def export_format(self) -> BrokenFrameExportFormat: + """ + Set and get crc failed frame file format. + + Returns: + object of int type + """ + return BrokenFrameExportFormat(self.__export_format.default_value) + + @export_format.setter + def export_format(self, export_format: BrokenFrameExportFormat): + self.__export_format.default_value = export_format.value diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/dp1_4_sink_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/dp1_4_sink_tests.py new file mode 100644 index 0000000..3657a33 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/dp1_4_sink_tests.py @@ -0,0 +1,773 @@ +from enum import IntEnum +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param +from UniTAP.dev.modules.dut_tests.test_utils import update_default_value + + +class BitStream(IntEnum): + """ + Describes available values for bit stream. + Sink DUT support 444 CRC for Simple 422 bitstream + """ + YCbCr422 = 0 + YCbCr444 = 1 + + +class PackedSource(IntEnum): + """ + Describes available values for packer source. + Source of the most packet video modes table. + """ + UseTestConfig = 0 + UseSinkDutEdid = 1 + + +class DisplayIdVisualCheck(IntEnum): + """ + Describes available values for visual check during DisplayID CTS tests. + """ + NeverSkip = 0 + SkipIfCrcMatches = 1 + + +class Timing: + """ + Class `Timing` describes available supported timings standard. + - CTA `cta` (enable/disable). + - RB1 `rb1` (enable/disable). + - RB2 `rb2` (enable/disable). + """ + def __init__(self, timing_data): + self.__data = Param(timing_data) + self.__cta = False + self.__rb1 = False + self.__rb2 = False + + @property + def cta(self): + """ + Set and get CTA flag support. + + Returns: + object of bool type + """ + return self.__cta + + @cta.setter + def cta(self, cta: bool): + self.__cta = cta + self.__data.default_value = update_default_value(value=self.__cta, + default_value=self.__data.default_value, + mask=self.__data.bit_field_list[0].mask) + + @property + def rb1(self): + """ + Set and get RB1 flag support. + + Returns: + object of bool type + """ + return self.__rb1 + + @rb1.setter + def rb1(self, rb1: bool): + self.__rb1 = rb1 + self.__data.default_value = update_default_value(value=self.__rb1, + default_value=self.__data.default_value, + mask=self.__data.bit_field_list[1].mask) + + @property + def rb2(self): + """ + Set and get RB2 flag support. + + Returns: + object of bool type + """ + return self.__rb2 + + @rb2.setter + def rb2(self, rb2: bool): + self.__rb2 = rb2 + self.__data.default_value = update_default_value(value=self.__rb2, + default_value=self.__data.default_value, + mask=self.__data.bit_field_list[2].mask) + + def set_all(self): + self.cta = True + self.rb1 = True + self.rb2 = True + + def clear(self): + self.cta = False + self.rb1 = False + self.rb2 = False + + +class Dp14SinkTimings: + """ + Class `Dp14SinkTimings` defines DSC video modes adn allows settings values. + - 1920x1080 30Hz `T_1920_x_1080_30`. + - 1920x1080 60Hz `T_1920_x_1080_60`. + - 1920x1080 120Hz `T_1920_x_1080_120`. + - 3840x2160 30Hz `T_3840_x_2160_30`. + - 3840x2160 60Hz `T_3840_x_2160_60`. + - 3840x2160 120Hz `T_3840_x_2160_120`. + - 5120x2160 30Hz `T_5120_x_2160_30`. + - 5120x2160 60Hz `T_5120_x_2160_60`. + - 5120x2160 120Hz `T_5120_x_2160_120`. + - 7680x4320 30Hz `T_7680_x_4320_30`. + - 7680x4320 60Hz `T_7680_x_4320_60`. + - 7680x4320 100Hz `T_7680_x_4320_100`. + """ + def __init__(self, json_obj): + self.__1920_x_1080_30_id = Param(json_obj["TSI_DP14_SINKCTS_DSC_VM_0"]) + self.__1920_x_1080_30 = Timing(timing_data=json_obj["TSI_DP14_SINKCTS_DSC_VM_T_0"]) + + self.__1920_x_1080_60_id = Param(json_obj["TSI_DP14_SINKCTS_DSC_VM_1"]) + self.__1920_x_1080_60 = Timing(timing_data=json_obj["TSI_DP14_SINKCTS_DSC_VM_T_1"]) + + self.__1920_x_1080_120_id = Param(json_obj["TSI_DP14_SINKCTS_DSC_VM_2"]) + self.__1920_x_1080_120 = Timing(timing_data=json_obj["TSI_DP14_SINKCTS_DSC_VM_T_2"]) + + self.__3840_x_2160_30_id = Param(json_obj["TSI_DP14_SINKCTS_DSC_VM_3"]) + self.__3840_x_2160_30 = Timing(timing_data=json_obj["TSI_DP14_SINKCTS_DSC_VM_T_3"]) + + self.__3840_x_2160_60_id = Param(json_obj["TSI_DP14_SINKCTS_DSC_VM_4"]) + self.__3840_x_2160_60 = Timing(timing_data=json_obj["TSI_DP14_SINKCTS_DSC_VM_T_4"]) + + self.__3840_x_2160_120_id = Param(json_obj["TSI_DP14_SINKCTS_DSC_VM_5"]) + self.__3840_x_2160_120 = Timing(timing_data=json_obj["TSI_DP14_SINKCTS_DSC_VM_T_5"]) + + self.__5120_x_2160_30_id = Param(json_obj["TSI_DP14_SINKCTS_DSC_VM_6"]) + self.__5120_x_2160_30 = Timing(timing_data=json_obj["TSI_DP14_SINKCTS_DSC_VM_T_6"]) + + self.__5120_x_2160_60_id = Param(json_obj["TSI_DP14_SINKCTS_DSC_VM_7"]) + self.__5120_x_2160_60 = Timing(timing_data=json_obj["TSI_DP14_SINKCTS_DSC_VM_T_7"]) + + self.__5120_x_2160_120_id = Param(json_obj["TSI_DP14_SINKCTS_DSC_VM_8"]) + self.__5120_x_2160_120 = Timing(timing_data=json_obj["TSI_DP14_SINKCTS_DSC_VM_T_8"]) + + self.__7680_x_4320_30_id = Param(json_obj["TSI_DP14_SINKCTS_DSC_VM_9"]) + self.__7680_x_4320_30 = Timing(timing_data=json_obj["TSI_DP14_SINKCTS_DSC_VM_T_9"]) + + self.__7680_x_4320_60_id = Param(json_obj["TSI_DP14_SINKCTS_DSC_VM_10"]) + self.__7680_x_4320_60 = Timing(timing_data=json_obj["TSI_DP14_SINKCTS_DSC_VM_T_10"]) + + self.__7680_x_4320_100_id = Param(json_obj["TSI_DP14_SINKCTS_DSC_VM_11"]) + self.__7680_x_4320_100 = Timing(timing_data=json_obj["TSI_DP14_SINKCTS_DSC_VM_T_11"]) + + @property + def T_1920_x_1080_30(self) -> Timing: + """ + Set and get 1920x1080 30Hz timing. + + Returns: + object of `Timing` type + """ + return self.__1920_x_1080_30 + + @T_1920_x_1080_30.setter + def T_1920_x_1080_30(self, _1920_x_1080_30: Timing): + self.__1920_x_1080_30 = _1920_x_1080_30 + + @property + def T_1920_x_1080_60(self) -> Timing: + """ + Set and get 1920x1080 60Hz timing. + + Returns: + object of `Timing` type + """ + return self.__1920_x_1080_60 + + @T_1920_x_1080_60.setter + def T_1920_x_1080_60(self, _1920_x_1080_60: Timing): + self.__1920_x_1080_60 = _1920_x_1080_60 + + @property + def T_1920_x_1080_120(self) -> Timing: + """ + Set and get 1920x1080 120Hz timing. + + Returns: + object of `Timing` type + """ + return self.__1920_x_1080_120 + + @T_1920_x_1080_120.setter + def T_1920_x_1080_120(self, _1920_x_1080_120: Timing): + self.__1920_x_1080_120 = _1920_x_1080_120 + + @property + def T_3840_x_2160_30(self) -> Timing: + """ + Set and get 3840x2160 30Hz timing. + + Returns: + object of `Timing` type + """ + return self.__3840_x_2160_30 + + @T_3840_x_2160_30.setter + def T_3840_x_2160_30(self, _3840_x_2160_30: Timing): + self.__3840_x_2160_30 = _3840_x_2160_30 + + @property + def T_3840_x_2160_60(self) -> Timing: + """ + Set and get 3840x2160 60Hz timing. + + Returns: + object of `Timing` type + """ + return self.__3840_x_2160_60 + + @T_3840_x_2160_60.setter + def T_3840_x_2160_60(self, _3840_x_2160_60: Timing): + self.__3840_x_2160_60 = _3840_x_2160_60 + + @property + def T_3840_x_2160_120(self) -> Timing: + """ + Set and get 3840x2160 120Hz timing. + + Returns: + object of `Timing` type + """ + return self.__3840_x_2160_120 + + @T_3840_x_2160_120.setter + def T_3840_x_2160_120(self, _3840_x_2160_120: Timing): + self.__3840_x_2160_120 = _3840_x_2160_120 + + @property + def T_5120_x_2160_30(self) -> Timing: + """ + Set and get 5120x2160 30Hz timing. + + Returns: + object of `Timing` type + """ + return self.__5120_x_2160_30 + + @T_5120_x_2160_30.setter + def T_5120_x_2160_30(self, _5120_x_2160_30: Timing): + self.__15120_x_2160_30 = _5120_x_2160_30 + + @property + def T_5120_x_2160_60(self) -> Timing: + """ + Set and get 5120x2160 60Hz timing. + + Returns: + object of `Timing` type + """ + return self.__5120_x_2160_60 + + @T_5120_x_2160_60.setter + def T_5120_x_2160_60(self, _5120_x_2160_60: Timing): + self.__5120_x_2160_60 = _5120_x_2160_60 + + @property + def T_5120_x_2160_120(self) -> Timing: + """ + Set and get 5120x2160 120Hz timing. + + Returns: + object of `Timing` type + """ + return self.__5120_x_2160_120 + + @T_5120_x_2160_120.setter + def T_5120_x_2160_120(self, _5120_x_2160_120: Timing): + self.__5120_x_2160_120 = _5120_x_2160_120 + + @property + def T_7680_x_4320_30(self) -> Timing: + """ + Set and get 7680x4320 30Hz timing. + + Returns: + object of `Timing` type + """ + return self.__7680_x_4320_30 + + @T_7680_x_4320_30.setter + def T_7680_x_4320_30(self, _7680_x_4320_30: Timing): + self.__7680_x_4320_30 = _7680_x_4320_30 + + @property + def T_7680_x_4320_60(self) -> Timing: + """ + Set and get 7680x4320 60Hz timing. + + Returns: + object of `Timing` type + """ + return self.__7680_x_4320_60 + + @T_7680_x_4320_60.setter + def T_7680_x_4320_60(self, _7680_x_4320_60: Timing): + self.__7680_x_4320_60 = _7680_x_4320_60 + + @property + def T_7680_x_4320_100(self) -> Timing: + """ + Set and get 7680x4320 100Hz timing. + + Returns: + object of `Timing` type + """ + return self.__7680_x_4320_100 + + @T_7680_x_4320_100.setter + def T_7680_x_4320_100(self, _7680_x_4320_100: Timing): + self.__7680_x_4320_100 = _7680_x_4320_100 + + +class ConfigVideoMode1LaneParam: + """ + Class `ConfigVideoMode1LaneParam` defines support for video mode for 1 lane and allows settings values. + - CVT 1280x800 `cvt_1280x800`. + - DMT 1280x768 `dmt_1280x768`. + - DMT 800x600 `dmt_800x600`. + - DMT 1024x768 `dmt_1024x768`. + - CTA 1440x480 `cta_1440x480`. + - CTA 1440x576 `cta_1440x576`. + """ + def __init__(self, json_obj): + self.__cvt_1280x800 = Param(json_obj["TSI_DP14_SINKCTS_VIDEO_MODE_0"]) + self.__dmt_1280x768 = Param(json_obj["TSI_DP14_SINKCTS_VIDEO_MODE_1"]) + self.__dmt_800x600 = Param(json_obj["TSI_DP14_SINKCTS_VIDEO_MODE_2"]) + self.__dmt_1024x768 = Param(json_obj["TSI_DP14_SINKCTS_VIDEO_MODE_3"]) + self.__cta_1440x480 = Param(json_obj["TSI_DP14_SINKCTS_VIDEO_MODE_14"]) + self.__cta_1440x576 = Param(json_obj["TSI_DP14_SINKCTS_VIDEO_MODE_15"]) + + @property + def cvt_1280x800(self) -> bool: + """ + Set and get CVT 1280x800 timing flag support. + + Returns: + object of bool type + """ + return self.__cvt_1280x800.default_value + + @cvt_1280x800.setter + def cvt_1280x800(self, cvt_1280x800: bool): + self.__cvt_1280x800.default_value = cvt_1280x800 + + @property + def dmt_1280x768(self) -> bool: + """ + Set and get DMT 1280x768 timing flag support. + + Returns: + object of bool type + """ + return self.__dmt_1280x768.default_value + + @dmt_1280x768.setter + def dmt_1280x768(self, dmt_1280x768: bool): + self.__dmt_1280x768.default_value = dmt_1280x768 + + @property + def dmt_800x600(self) -> bool: + """ + Set and get DMT 800x600 timing flag support. + + Returns: + object of bool type + """ + return self.__dmt_800x600.default_value + + @dmt_800x600.setter + def dmt_800x600(self, dmt_800x600: bool): + self.__dmt_800x600.default_value = dmt_800x600 + + @property + def dmt_1024x768(self) -> bool: + """ + Set and get DMT 1024x768 timing flag support. + + Returns: + object of bool type + """ + return self.__dmt_1024x768.default_value + + @dmt_1024x768.setter + def dmt_1024x768(self, dmt_1024x768: bool): + self.__dmt_1024x768.default_value = dmt_1024x768 + + @property + def cta_1440x480(self) -> bool: + """ + Set and get CTA 1440x480 timing flag support. + + Returns: + object of bool type + """ + return self.__cta_1440x480.default_value + + @cta_1440x480.setter + def cta_1440x480(self, cta_1440x480: bool): + self.__cta_1440x480.default_value = cta_1440x480 + + @property + def cta_1440x576(self) -> bool: + """ + Set and get CTA 1440x576 timing flag support. + + Returns: + object of bool type + """ + return self.__cta_1440x576.default_value + + @cta_1440x576.setter + def cta_1440x576(self, cta_1440x576: bool): + self.__cta_1440x576.default_value = cta_1440x576 + + +class ConfigVideoMode2LaneParam: + """ + Class `ConfigVideoMode2LaneParam` defines support for video mode for 2 lane and allows settings values. + - DMT 1280x1024 `dmt_1280x1024`. + - DMT 1280x960 `dmt_1280x960`. + - DMT 1360x768 `dmt_1360x768`. + - CVT 1280x800 `cvt_1280x800`. + - DMT 1400x1050 `dmt_1400x1050`. + - DMT 1280x768 `dmt_1280x768`. + - CVT 1600x1200 `cvt_1600x1200` + """ + def __init__(self, json_obj): + self.__dmt_1280x1024 = Param(json_obj["TSI_DP14_SINKCTS_VIDEO_MODE_4"]) + self.__dmt_1280x960 = Param(json_obj["TSI_DP14_SINKCTS_VIDEO_MODE_5"]) + self.__dmt_1360x768 = Param(json_obj["TSI_DP14_SINKCTS_VIDEO_MODE_6"]) + self.__cvt_1280x800 = Param(json_obj["TSI_DP14_SINKCTS_VIDEO_MODE_7"]) + self.__dmt_1400x1050 = Param(json_obj["TSI_DP14_SINKCTS_VIDEO_MODE_8"]) + self.__dmt_1280x768 = Param(json_obj["TSI_DP14_SINKCTS_VIDEO_MODE_9"]) + self.__cvt_1600x1200 = Param(json_obj["TSI_DP14_SINKCTS_VIDEO_MODE_10"]) + + @property + def dmt_1280x1024(self) -> bool: + """ + Set and get DMT 1280x1024 timing flag support. + + Returns: + object of bool type + """ + return self.__dmt_1280x1024.default_value + + @dmt_1280x1024.setter + def dmt_1280x1024(self, dmt_1280x1024: bool): + self.__dmt_1280x1024.default_value = dmt_1280x1024 + + @property + def dmt_1280x960(self) -> bool: + """ + Set and get DMT 1280x960 timing flag support. + + Returns: + object of bool type + """ + return self.__dmt_1280x960.default_value + + @dmt_1280x960.setter + def dmt_1280x960(self, dmt_1280x960: bool): + self.__dmt_1280x960.default_value = dmt_1280x960 + + @property + def dmt_1360x768(self) -> bool: + """ + Set and get DMT 1360x768 timing flag support. + + Returns: + object of bool type + """ + return self.__dmt_1360x768.default_value + + @dmt_1360x768.setter + def dmt_1360x768(self, dmt_1360x768: bool): + self.__dmt_1360x768.default_value = dmt_1360x768 + + @property + def cvt_1280x800(self) -> bool: + """ + Set and get CVT 1280x800 timing flag support. + + Returns: + object of bool type + """ + return self.__cvt_1280x800.default_value + + @cvt_1280x800.setter + def cvt_1280x800(self, cvt_1280x800: bool): + self.__cvt_1280x800.default_value = cvt_1280x800 + + @property + def dmt_1400x1050(self) -> bool: + """ + Set and get DMT 1400x1050 timing flag support. + + Returns: + object of bool type + """ + return self.__dmt_1400x1050.default_value + + @dmt_1400x1050.setter + def dmt_1400x1050(self, dmt_1400x1050: bool): + self.__dmt_1400x1050.default_value = dmt_1400x1050 + + @property + def dmt_1280x768(self) -> bool: + """ + Set and get DMT 1280x768 timing flag support. + + Returns: + object of bool type + """ + return self.__dmt_1280x768.default_value + + @dmt_1280x768.setter + def dmt_1280x768(self, dmt_1280x768: bool): + self.__dmt_1280x768.default_value = dmt_1280x768 + + @property + def cvt_1600x1200(self) -> bool: + """ + Set and get CVT 1600x1200 timing flag support. + + Returns: + object of bool type + """ + return self.__cvt_1600x1200.default_value + + @cvt_1600x1200.setter + def cvt_1600x1200(self, cvt_1600x1200: bool): + self.__cvt_1600x1200.default_value = cvt_1600x1200 + + +class ConfigVideoMode4LaneParam: + """ + Class `ConfigVideoMode4LaneParam` defines support for video mode for 4 lane and allows settings values. + - CVT 2048x1536 `cvt_2048x1536`. + - DMT 1792x1344 `dmt_1792x1344`. + - DMT 1600x1200 `dmt_1600x1200`. + - CTA 1920x1080 `cta_1920x1080`. + """ + def __init__(self, json_obj): + self.__cvt_2048x1536 = Param(json_obj["TSI_DP14_SINKCTS_VIDEO_MODE_11"]) + self.__dmt_1792x1344 = Param(json_obj["TSI_DP14_SINKCTS_VIDEO_MODE_12"]) + self.__dmt_1600x1200 = Param(json_obj["TSI_DP14_SINKCTS_VIDEO_MODE_13"]) + self.__cta_1920x1080 = Param(json_obj["TSI_DP14_SINKCTS_VIDEO_MODE_16"]) + + @property + def cvt_2048x1536(self) -> bool: + """ + Set and get CVT 2048x1536 timing flag support. + + Returns: + object of bool type + """ + return self.__cvt_2048x1536.default_value + + @cvt_2048x1536.setter + def cvt_2048x1536(self, cvt_2048x1536: bool): + self.__cvt_2048x1536.default_value = cvt_2048x1536 + + @property + def dmt_1792x1344(self) -> bool: + """ + Set and get DMT 1792x1344 timing flag support. + + Returns: + object of bool type + """ + return self.__dmt_1792x1344.default_value + + @dmt_1792x1344.setter + def dmt_1792x1344(self, dmt_1792x1344: bool): + self.__dmt_1792x1344.default_value = dmt_1792x1344 + + @property + def dmt_1600x1200(self) -> bool: + """ + Set and get DMT 1600x1200 timing flag support. + + Returns: + object of bool type + """ + return self.__dmt_1600x1200.default_value + + @dmt_1600x1200.setter + def dmt_1600x1200(self, dmt_1600x1200: bool): + self.__dmt_1600x1200.default_value = dmt_1600x1200 + + @property + def cta_1920x1080(self) -> bool: + """ + Set and get CTA 1920x1080 timing flag support. + + Returns: + object of bool type + """ + return self.__cta_1920x1080.default_value + + @cta_1920x1080.setter + def cta_1920x1080(self, cta_1920x1080: bool): + self.__cta_1920x1080.default_value = cta_1920x1080 + + +class Dp14SinkTestParam: + """ + Class `Dp14SinkTestParam` allows working with parameters for Sink DP 1.4 LLCTS tests. + - Set and get test timeout, in milliseconds `timeout`. + - Set and get DSC timings `timings` type `Dp14SinkTimings`. + - Set and get capabilities of DSC video mode `dsc_video_mode`. + - Set and get bitstream type `bitstream` - type `BitStream`. + - Set and get packed source `packed_source` - type `PackedSource`. + - Set and get configuration of video modes for 1 lane `config_video_mode_1_lane` type `ConfigVideoMode1LaneParam`. + - Set and get configuration of video modes for 2 lane `config_video_mode_2_lane` type `ConfigVideoMode2LaneParam`. + - Set and get configuration of video modes for 4 lane `config_video_mode_4_lane` type `ConfigVideoMode4LaneParam`. + - Set and get flag of display id visual `display_id_visual` type `DisplayIdVisualCheck`. + """ + def __init__(self, json_obj): + self.__timeout = Param(json_obj["TSI_DP14_SINKCTS_TIMEOUT"]) + self.__timings = Dp14SinkTimings(json_obj) + self.__dsc_video_mode = Param(json_obj["TSI_DP14_SINKCTS_DSC_SOURCE"]) # PackedSource + self.__bitstream = Param(json_obj["TSI_DP14_SINKCTS_SUPPORT_444CRC"]) # BitStream + self.__packed_source = Param(json_obj["TSI_DP14_SINKCTS_PACKET_SOURCE"]) # PackedSource + self.__config_video_mode_1_lane = ConfigVideoMode1LaneParam(json_obj) + self.__config_video_mode_2_lane = ConfigVideoMode2LaneParam(json_obj) + self.__config_video_mode_4_lane = ConfigVideoMode4LaneParam(json_obj) + self.__display_id_visual = Param(json_obj["TSI_DP14_SINKCTS_VISUAL_TEST_CHECK"]) + + @property + def timeout(self) -> int: + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout + + @property + def timings(self): + """ + Set and get timings. + + Returns: + object `Dp14SinkTimings` + """ + return self.__timings + + @timings.setter + def timings(self, timings: Dp14SinkTimings): + self.__timings = timings + + @property + def dsc_video_mode(self) -> PackedSource: + """ + Set and get dsc video packed source. + + Returns: + object `PackedSource` + """ + return PackedSource(self.__dsc_video_mode.default_value) + + @dsc_video_mode.setter + def dsc_video_mode(self, dsc_video_mode: PackedSource): + self.__dsc_video_mode.default_value = dsc_video_mode.value + + @property + def bitstream(self) -> BitStream: + """ + Set and get bitstream. + + Returns: + object `BitStream` + """ + return BitStream(self.__bitstream.default_value) + + @bitstream.setter + def bitstream(self, bitstream: BitStream): + self.__bitstream.default_value = bitstream.value + + @property + def packed_source(self) -> PackedSource: + """ + Set and get packed source. + + Returns: + object `PackedSource` + """ + return PackedSource(self.__packed_source.default_value) + + @packed_source.setter + def packed_source(self, packed_source: PackedSource): + self.__packed_source.default_value = packed_source.value + + @property + def config_video_mode_1_lane(self): + """ + Set and get configuration of video modes for 1 lane. + + Returns: + object `ConfigVideoMode1LaneParam` + """ + return self.__config_video_mode_1_lane + + @config_video_mode_1_lane.setter + def config_video_mode_1_lane(self, config_video_mode_1_lane: ConfigVideoMode1LaneParam): + self.__config_video_mode_1_lane = config_video_mode_1_lane + + @property + def config_video_mode_2_lane(self): + """ + Set and get configuration of video modes for 2 lane. + + Returns: + object `ConfigVideoMode2LaneParam` + """ + return self.__config_video_mode_2_lane + + @config_video_mode_2_lane.setter + def config_video_mode_2_lane(self, config_video_mode_2_lane: ConfigVideoMode2LaneParam): + self.__config_video_mode_2_lane = config_video_mode_2_lane + + @property + def config_video_mode_4_lane(self): + """ + Set and get configuration of video modes for 4 lane. + + Returns: + object `ConfigVideoMode4LaneParam` + """ + return self.__config_video_mode_4_lane + + @config_video_mode_4_lane.setter + def config_video_mode_4_lane(self, config_video_mode_4_lane: ConfigVideoMode4LaneParam): + self.__config_video_mode_4_lane = config_video_mode_4_lane + + @property + def display_id_visual(self) -> DisplayIdVisualCheck: + """ + Set and get flag of visual check in Display ID. + + Returns: + object `DisplayIdVisualCheck` + """ + return DisplayIdVisualCheck(self.__display_id_visual.default_value) + + @display_id_visual.setter + def display_id_visual(self, display_id_visual: DisplayIdVisualCheck): + self.__display_id_visual.default_value = display_id_visual diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/dp1_4_source_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/dp1_4_source_tests.py new file mode 100644 index 0000000..666bca1 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/dp1_4_source_tests.py @@ -0,0 +1,73 @@ +from UniTAP.dev.modules.dut_tests.dut_default_params.dp_1_4_source_general_tab import GeneralSourceDUTDp14SettingTab, \ + PackedTimings1Lane, PackedTimings2Lane, PackedTimings4Lane, EventIndication +from UniTAP.dev.modules.dut_tests.dut_default_params.dp_source_audio_tab import AudioSourceDp14SettingTab +from UniTAP.dev.modules.dut_tests.dut_default_params.dp_source_dsc_tab import DscConfigDp14Tab +from UniTAP.dev.modules.dut_tests.dut_default_params.dp_source_display_id_tab import DisplayIdDp14ConfigTab +from UniTAP.dev.modules.dut_tests.dut_default_params.dp_source_adaptive_sync_tab import AdaptiveSyncDp14ConfigTab + + +class Dp14SourceDUTTestParam: + """ + Class `Dp14SourceDUTTestParam` allows working with default group of parameters for DP 1.4 LLCTS tests: + - Set and get `GeneralSourceDUTDp14SettingTab`. Allows working with parameters from General source part `general`. + - Set and get `AudioSourceDp14SettingTab`. Allows working with parameters from Audio source part `audio`. + - Set and get `DscConfigDp14Tab`. Allows working with parameters from DSC part `dsc`. + - Set and get `DisplayIdDp14ConfigTab`. Allows working with parameters from Display ID part `display_id`. + - Set and get `AdaptiveSyncDp14ConfigTab`. Allows working with parameters from Adaptive-Sync part `adaptive_sync`. + """ + def __init__(self, json_obj): + self.__general_tab = GeneralSourceDUTDp14SettingTab(json_obj) + self.__audio_tab = AudioSourceDp14SettingTab(json_obj) + self.__dsc_tab = DscConfigDp14Tab(json_obj) + self.__display_id_tab = DisplayIdDp14ConfigTab(json_obj) + self.__adaptive_sync_tab = AdaptiveSyncDp14ConfigTab(json_obj) + + @property + def general(self) -> GeneralSourceDUTDp14SettingTab: + """ + Get object of parameters from General source part. + + Returns: + object of `GeneralSourceDUTDp14SettingTab` type + """ + return self.__general_tab + + @property + def audio(self) -> AudioSourceDp14SettingTab: + """ + Get object of parameters from Audio source part. + + Returns: + object of `AudioSourceDp14SettingTab` type + """ + return self.__audio_tab + + @property + def dsc(self) -> DscConfigDp14Tab: + """ + Get object of parameters from DSC source part. + + Returns: + object of `DscConfigDp14Tab` type + """ + return self.__dsc_tab + + @property + def display_id(self) -> DisplayIdDp14ConfigTab: + """ + Get object of parameters from Display ID source part. + + Returns: + object of `DisplayIdDp14ConfigTab` type + """ + return self.__display_id_tab + + @property + def adaptive_sync(self) -> AdaptiveSyncDp14ConfigTab: + """ + Get object of parameters from Adaptive-Sync source part. + + Returns: + object of `AdaptiveSyncDp14ConfigTab` type + """ + return self.__adaptive_sync_tab diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/dp2_1_sink_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/dp2_1_sink_tests.py new file mode 100644 index 0000000..fee4eed --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/dp2_1_sink_tests.py @@ -0,0 +1,355 @@ +from enum import IntEnum +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param +from UniTAP.dev.modules.dut_tests.test_utils import update_default_value + + +class BitStream(IntEnum): + """ + Describes available values for bit stream. + Sink DUT support 444 CRC for Simple 422 bitstream + """ + YCbCr422 = 0 + YCbCr444 = 1 + + +class PackedSource(IntEnum): + """ + Describes available values for packer source. + Source of the most packet video modes table. + """ + UseTestConfig = 0 + UseSinkDutEdid = 1 + + +class VisualCheck(IntEnum): + """ + Describes available values for visual check during DisplayID CTS tests. + """ + NeverSkip = 0 + SkipIfCrcMatches = 1 + + +class StandardBase: + + __rate_shift_rb1 = {30: 1, 60: 4, 120: 7, 144: 9, 240: 12} + __rate_shift_rb2 = {30: 2, 60: 5, 120: 8, 144: 10, 240: 13} + + def __init__(self, value: Param, rate: int): + self._value = value + self._rate = rate + + @property + def rb1(self) -> bool: + """ + Set and get RB1 flag support. + + Returns: + object of bool type + """ + return self._value.default_value & self._value.bit_field_list[self.__rate_shift_rb1.get(self._rate)].mask != 0 + + @rb1.setter + def rb1(self, value: bool): + self._value._set_by_bit_number(value, 1, self.__rate_shift_rb1.get(self._rate)) + + @property + def rb2(self) -> bool: + """ + Set and get RB2 flag support. + + Returns: + object of bool type + """ + return self._value.default_value & self._value.bit_field_list[self.__rate_shift_rb2.get(self._rate)].mask != 0 + + @rb2.setter + def rb2(self, value: bool): + self._value._set_by_bit_number(value, 1, self.__rate_shift_rb2.get(self._rate)) + + +class Rate30Hz(StandardBase): + + __rate_shift_cta = {30: 0, 60: 3, 120: 6} + + def __init__(self, value: Param, rate: int): + super().__init__(value, rate) + + @property + def cta(self) -> bool: + """ + Set and get CTA flag support. + + Returns: + object of bool type + """ + return self._value.default_value & self._value.bit_field_list[self.__rate_shift_cta.get(self._rate)].mask != 0 + + @cta.setter + def cta(self, value: bool): + self._value._set_by_bit_number(value, 1, self.__rate_shift_cta.get(self._rate)) + + def set_all(self): + self.cta = True + self.rb1 = True + self.rb2 = True + + def clear_all(self): + self.cta = False + self.rb1 = False + self.rb2 = False + + +class Rate144Hz(StandardBase): + + __rate_shift_ovt = {144: 11, 240: 14} + + def __init__(self, value: Param, rate: int): + super().__init__(value, rate) + + @property + def ovt(self) -> bool: + """ + Set and get OVT flag support. + + Returns: + object of bool type + """ + return self._value.default_value & self._value.bit_field_list[self.__rate_shift_ovt.get(self._rate)].mask != 0 + + @ovt.setter + def ovt(self, value: bool): + self._value._set_by_bit_number(value, 1, self.__rate_shift_ovt.get(self._rate)) + + def set_all(self): + self.ovt = True + self.rb1 = True + self.rb2 = True + + def clear_all(self): + self.ovt = False + self.rb1 = False + self.rb2 = False + + +class VideoModeInfo: + + def __init__(self, json_obj): + self.__value = Param(json_obj) + self.__30hz = Rate30Hz(self.__value, 30) + self.__60hz = Rate30Hz(self.__value, 60) + self.__120hz = Rate30Hz(self.__value, 120) + self.__144hz = Rate144Hz(self.__value, 144) + self.__240hz = Rate144Hz(self.__value, 240) + + @property + def rate_30hz(self) -> Rate30Hz: + return self.__30hz + + @property + def rate_60hz(self) -> Rate30Hz: + return self.__60hz + + @property + def rate_120hz(self) -> Rate30Hz: + return self.__120hz + + @property + def rate_144hz(self) -> Rate144Hz: + return self.__144hz + + @property + def rate_240hz(self) -> Rate144Hz: + return self.__240hz + + def set_all(self): + self.rate_30hz.set_all() + self.rate_60hz.set_all() + self.rate_120hz.set_all() + self.rate_144hz.set_all() + self.rate_240hz.set_all() + + def clear_all(self): + self.rate_30hz.clear_all() + self.rate_60hz.clear_all() + self.rate_120hz.clear_all() + self.rate_144hz.clear_all() + self.rate_240hz.clear_all() + + +class Dp21SinkVideoModes: + + def __init__(self, json_obj): + self.__1920x1080 = VideoModeInfo(json_obj["TSI_DP20_SINKCTS_VMT_1920_1080"]) + self.__3840x2160 = VideoModeInfo(json_obj["TSI_DP20_SINKCTS_VMT_3840_2160"]) + self.__5120x2160 = VideoModeInfo(json_obj["TSI_DP20_SINKCTS_VMT_5120_2160"]) + self.__7680x4320 = VideoModeInfo(json_obj["TSI_DP20_SINKCTS_VMT_7680_4320"]) + self.__10240x4320 = VideoModeInfo(json_obj["TSI_DP20_SINKCTS_VMT_10240_4320"]) + self.__15360x8640 = VideoModeInfo(json_obj["TSI_DP20_SINKCTS_VMT_15360_8640"]) + + @property + def vm_1920x1080(self) -> VideoModeInfo: + return self.__1920x1080 + + @property + def vm_3840x2160(self) -> VideoModeInfo: + return self.__3840x2160 + + @property + def vm_5120x2160(self) -> VideoModeInfo: + return self.__5120x2160 + + @property + def vm_7680x4320(self) -> VideoModeInfo: + return self.__7680x4320 + + @property + def vm_10240x4320(self) -> VideoModeInfo: + return self.__10240x4320 + + @property + def vm_15360x8640(self) -> VideoModeInfo: + return self.__15360x8640 + + def set_all(self): + self.vm_1920x1080.set_all() + self.vm_3840x2160.set_all() + self.vm_5120x2160.set_all() + self.vm_7680x4320.set_all() + self.vm_10240x4320.set_all() + self.vm_15360x8640.set_all() + + def clear_all(self): + self.vm_1920x1080.clear_all() + self.vm_3840x2160.clear_all() + self.vm_5120x2160.clear_all() + self.vm_7680x4320.clear_all() + self.vm_10240x4320.clear_all() + self.vm_15360x8640.clear_all() + + +class DebugOptions: + + def __init__(self, json_obj): + self.__param = Param(json_obj["TSI_DP20_SINKCTS_DEBUG_CONF"]) + + @property + def force_visual_check(self) -> bool: + """ + Set and get Force manual visual check flag. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(0, bool) + + @force_visual_check.setter + def force_visual_check(self, force_visual_check: bool): + self.__param.default_value = update_default_value(value=force_visual_check, + default_value=self.__param.default_value, + mask=self.__param.bit_field_list[0].mask) + + +class Dp21SinkTestParam: + """ + Class `Dp21SinkTestParam` allows working with parameters for Sink DP 2.1 LLCTS tests. + - Set and get test timeout, in milliseconds `timeout`. + - Set and get DSC VideoModeInfo `video_modes ` type `Dp21SinkVideoModes`. + - Set and get capabilities of DSC video mode `dsc_video_mode`. + - Set and get bitstream type `bitstream` - type `BitStream`. + - Set and get flag of display id visual `visual_check` type `VisualCheck`. + - Set and get debug options `debug_options` type `DebugOptions` + """ + def __init__(self, json_obj): + self.__timeout = Param(json_obj["TSI_DP20_SINKCTS_TIMEOUT"]) + self.__dsc_video_mode = Param(json_obj["TSI_DP20_SINKCTS_DSC_SOURCE"]) # PackedSource + self.__bitstream = Param(json_obj["TSI_DP20_SINKCTS_SUPPORT_444CRC"]) # BitStream + self.__display_id_visual = Param(json_obj["TSI_DP20_SINKCTS_VISUAL_TEST_CHECK"]) + self.__video_modes = Dp21SinkVideoModes(json_obj) + self.__debug_options = DebugOptions(json_obj) + + @property + def timeout(self) -> int: + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout + + @property + def dsc_video_mode(self) -> PackedSource: + """ + Set and get dsc video packed source. + + Returns: + object `PackedSource` + """ + return PackedSource(self.__dsc_video_mode.default_value) + + @dsc_video_mode.setter + def dsc_video_mode(self, dsc_video_mode: PackedSource): + self.__dsc_video_mode.default_value = dsc_video_mode.value + + @property + def bitstream(self) -> BitStream: + """ + Set and get bitstream. + + Returns: + object `BitStream` + """ + return BitStream(self.__bitstream.default_value) + + @bitstream.setter + def bitstream(self, bitstream: BitStream): + self.__bitstream.default_value = bitstream.value + + @property + def visual_check(self) -> VisualCheck: + """ + Set and get flag of visual check. + + Returns: + object `DisplayIdVisualCheck` + """ + return VisualCheck(self.__display_id_visual.default_value) + + @visual_check.setter + def visual_check(self, display_id_visual: VisualCheck): + self.__display_id_visual.default_value = display_id_visual + + @property + def video_modes(self) -> Dp21SinkVideoModes: + """ + Set and get DP 2.1 Sink video modes. + + Returns: + object `Dp21SinkVideoModes` + """ + return self.__video_modes + + @video_modes.setter + def video_modes(self, video_modes: Dp21SinkVideoModes): + self.__video_modes = video_modes + + @property + def debug_options(self) -> DebugOptions: + """ + Set and get debug options. + + Returns: + object of 'DebugOptions' type + """ + return self.__debug_options + + @debug_options.setter + def debug_options(self, debug_config: DebugOptions): + self.__debug_options = debug_config diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/dp2_1_source_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/dp2_1_source_tests.py new file mode 100644 index 0000000..e4e0e06 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/dp2_1_source_tests.py @@ -0,0 +1,83 @@ +from UniTAP.dev.modules.dut_tests.dut_default_params.dp_2_1_source_general_tab import GeneralSourceDUTDp21SettingTab +from UniTAP.dev.modules.dut_tests.dut_default_params.dp_source_display_id_tab import DisplayIdDp21ConfigTab +from UniTAP.dev.modules.dut_tests.dut_default_params.dp_source_adaptive_sync_tab import AdaptiveSyncDp21ConfigTab +from UniTAP.dev.modules.dut_tests.dut_default_params.dp_2_1_video_modes import Dp21AvailableVideoModes +from UniTAP.dev.modules.dut_tests.dut_default_params.dp_2_1_dsc_video_modes import Dp21AvailableDscVideoModes +from UniTAP.dev.modules.dut_tests.dut_default_params.dp_source_audio_tab import AudioSourceDp21SettingTab + + +class Dp21SourceDUTTestParam: + """ + Class `Dp21SourceDUTTestParam` allows working with default group of parameters for DP 2.1 LLCTS tests: + - Set and get `GeneralSourceDUTDp21SettingTab`. Allows working with parameters from General source part `general`. + - Set and get `DisplayIdDp21ConfigTab`. Allows working with parameters from Display ID part `display_id`. + - Set and get `AdaptiveSyncDp21ConfigTab`. Allows working with parameters from Adaptive-Sync part `adaptive_sync`. + - Set and get `Dp21AvailableVideoModes`. Allows working with parameters from Video modes part `video_modes`. + """ + def __init__(self, json_obj): + self.__general_tab = GeneralSourceDUTDp21SettingTab(json_obj) + self.__audio_tab = AudioSourceDp21SettingTab(json_obj) + self.__display_id_tab = DisplayIdDp21ConfigTab(json_obj) + self.__adaptive_sync_tab = AdaptiveSyncDp21ConfigTab(json_obj) + self.__video_modes = Dp21AvailableVideoModes(json_obj) + self.__dsc_video_modes = Dp21AvailableDscVideoModes(json_obj) + + @property + def general(self) -> GeneralSourceDUTDp21SettingTab: + """ + Get object of parameters from General source part. + + Returns: + object of `GeneralSourceDUTDp21SettingTab` type + """ + return self.__general_tab + + @property + def audio(self) -> AudioSourceDp21SettingTab: + """ + Get object of parameters from Audio source part. + + Returns: + object of `AudioSourceDp21SettingTab` type + """ + return self.__audio_tab + + @property + def display_id(self) -> DisplayIdDp21ConfigTab: + """ + Get object of parameters from Display ID source part. + + Returns: + object of `DisplayIdDp21ConfigTab` type + """ + return self.__display_id_tab + + @property + def adaptive_sync(self) -> AdaptiveSyncDp21ConfigTab: + """ + Get object of parameters from Adaptive-Sync source part. + + Returns: + object of `AdaptiveSyncDp21ConfigTab` type + """ + return self.__adaptive_sync_tab + + @property + def video_modes(self) -> Dp21AvailableVideoModes: + """ + Get object of parameters from Video modes source part. + + Returns: + object of `Dp21AvailableVideoModes` type + """ + return self.__video_modes + + @property + def dsc_video_modes(self) -> Dp21AvailableDscVideoModes: + """ + Get object of parameters from Video modes source part. + + Returns: + object of `Dp21AvailableVideoModes` type + """ + return self.__dsc_video_modes diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/dp_1_4_source_general_tab.py b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_1_4_source_general_tab.py new file mode 100644 index 0000000..c8151e2 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_1_4_source_general_tab.py @@ -0,0 +1,1526 @@ +from enum import IntEnum +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param +from UniTAP.dev.modules.dut_tests.test_utils import CMTFormat, CMTVSCFormat, CMTBitDepth, CMTVSCBitDepth,\ + make_cf, make_vsc_cf, update_default_value + + +class EventIndication(IntEnum): + """ + Describes available events indications for test automation. + """ + Unknown = -1 + AlwaysReady = 0 + EdidRead = 1 + LinkTrainingEnd = 2 + ActiveVideo = 3 + + +class MaxSupportedVideoMode(IntEnum): + """ + Describes available video modes for field `max_supported_video_mode`. + """ + T_640x480_60Hz_6BPC = 1 + T_848x480_60Hz_8BPC = 2 + T_1280x720_60Hz_8BPC = 3 + T_1280x960_60Hz_8BPC = 4 + T_1920x1080_60Hz_8BPC = 5 + T_1920x1440_60Hz_8BPC = 6 + T_1920x1080_120Hz_8BPC = 7 + T_1280x800_60Hz_RB1_6BPC = 8 + T_1280x768_60Hz_RB1_6BPC = 9 + T_800x600_60Hz_10BPC = 10 + T_1024x768_60Hz_10BPC = 11 + T_1280x1024_60Hz_8BPC = 12 + T_1360x768_60Hz_10BPC = 13 + T_1280x800_60Hz_RB1_10BPC = 14 + T_1400x1050_60Hz_RB1_8BPC = 15 + T_1280x768_60Hz_RB1_10BPC = 16 + T_1600x1200_60Hz_RB1_6BPC = 17 + T_2048x1536_60Hz_RB1_8BPC = 18 + T_1792x1344_60Hz_8BPC = 19 + T_1600x1200_60Hz_RB1_10BPC = 20 + T_3840x2160_30Hz_8BPC = 21 + T_3840x2160_60Hz_8BPC = 22 + + +class PackedTimings1Lane(IntEnum): + """ + Describes available timings for field `packed_timings_1_lane`. + """ + NoneTiming = 0 + DMT_800x600_60Hz_10BPC = 10 + DMT_1024x768_60Hz_6BPC = 11 + CVT_1280x800_60Hz_RB1_6BPC = 8 + DMT_1280x768_60Hz_RB1_6BPC = 9 + CTA_1440x480_60Hz_8BPC = 23 + CTA_1440x576_50Hz_8BPC = 24 + + +class PackedTimings2Lane(IntEnum): + """ + Describes available timings for field `packed_timings_2_lane`. + """ + NoneTiming = 0 + DMT_1280x768_60Hz_10BPC = 16 + CVT_1280x800_60Hz_10BPC = 14 + DMT_1280x960_60Hz_8BPC = 4 + DMT_1280x1024_60Hz_8BPC = 12 + DMT_1360x768_60Hz_10BPC = 13 + DMT_1400x1050_60Hz_RB1_8BPC = 15 + CVT_1600x1200_60Hz_RB1_6BPC = 17 + DMT_1360x768_60Hz_8BPC = 13 + CVT_1280x800_60Hz_8BPC = 26 + DMT_1280x768_60Hz_8BPC = 25 + + +class PackedTimings4Lane(IntEnum): + """ + Describes available timings for field `packed_timings_4_lane`. + """ + NoneTiming = 0 + DMT_1600x1200_60Hz_10BPC = 20 + DMT_1600x1200_60Hz_8BPC = 28 + DMT_1792x1344_60Hz_8BPC = 19 + CTA_1920x1080_60Hz_10BPC = 29 + CTA_1920x1080_60Hz_8BPC = 5 + CVT_2048x1536_60Hz_RB1_8BPC = 18 + + +class Rbb1Lane(IntEnum): + """ + Describes available timings for field `rbb_1l`. + """ + NoneTiming = 0 + T_640x480_60Hz_6BPC = 1 + T_848x480_60Hz_8BPC = 2 + + +class Rbb2Lane(IntEnum): + """ + Describes available timings for field `rbb_2l`. + """ + NoneTiming = 0 + T_640x480_60Hz_6BPC = 1 + T_848x480_60Hz_8BPC = 2 + T_1280x720_60Hz_8BPC = 3 + + +class Rbb4Lane(IntEnum): + """ + Describes available timings for field `rbb_4l`. + """ + NoneTiming = 0 + T_640x480_60Hz_6BPC = 1 + T_848x480_60Hz_8BPC = 2 + T_1280x720_60Hz_8BPC = 3 + T_1280x960_60Hz_8BPC = 4 + T_1920_1080_60Hz_8BPC = 5 + + +class Hbr1Lane(IntEnum): + """ + Describes available timings for field `hbr_1l`. + """ + NoneTiming = 0 + T_640x480_60Hz_6BPC = 1 + T_848x480_60Hz_8BPC = 2 + T_1280x720_60Hz_8BPC = 3 + + +class Hbr2Lane(IntEnum): + """ + Describes available timings for field `hbr_2l`. + """ + NoneTiming = 0 + T_640x480_60Hz_6BPC = 1 + T_848x480_60Hz_8BPC = 2 + T_1280x720_60Hz_8BPC = 3 + T_1280x960_60Hz_8BPC = 4 + + +class Hbr4Lane(IntEnum): + """ + Describes available timings for field `hbr_4l`. + """ + NoneTiming = 0 + T_640x480_60Hz_6BPC = 1 + T_848x480_60Hz_8BPC = 2 + T_1280x720_60Hz_8BPC = 3 + T_1280x960_60Hz_8BPC = 4 + T_1920_1080_60Hz_8BPC = 5 + T_1920_1440_60Hz_8BPC = 6 + + +class Hbr2_1Lane(IntEnum): + """ + Describes available timings for field `hbr2_1l`. + """ + NoneTiming = 0 + T_640x480_60Hz_6BPC = 1 + T_848x480_60Hz_8BPC = 2 + T_1280x720_60Hz_8BPC = 3 + T_1280x960_60Hz_8BPC = 4 + + +class Hbr2_2Lane(IntEnum): + """ + Describes available timings for field `hbr2_2l`. + """ + NoneTiming = 0 + T_640x480_60Hz_6BPC = 1 + T_848x480_60Hz_8BPC = 2 + T_1280x720_60Hz_8BPC = 3 + T_1280x960_60Hz_8BPC = 4 + T_1920_1080_60Hz_8BPC = 5 + T_1920_1440_60Hz_8BPC = 6 + + +class Hbr2_4Lane(IntEnum): + """ + Describes available timings for field `hbr2_4l`. + """ + NoneTiming = 0 + T_640x480_60Hz_6BPC = 1 + T_848x480_60Hz_8BPC = 2 + T_1280x720_60Hz_8BPC = 3 + T_1280x960_60Hz_8BPC = 4 + T_1920_1080_60Hz_8BPC = 5 + T_1920_1440_60Hz_8BPC = 6 + T_1920x1080_120Hz_8BPC = 7 + + +class Hbr3_1Lane(IntEnum): + """ + Describes available timings for field `hbr3_1l`. + """ + NoneTiming = 0 + T_640x480_60Hz_6BPC = 1 + T_848x480_60Hz_8BPC = 2 + T_1280x720_60Hz_8BPC = 3 + T_1280x960_60Hz_8BPC = 4 + T_1920_1080_60Hz_8BPC = 5 + T_1920_1440_60Hz_8BPC = 6 + + +class Hbr3_2Lane(IntEnum): + """ + Describes available timings for field `hbr3_2l`. + """ + NoneTiming = 0 + T_640x480_60Hz_6BPC = 1 + T_848x480_60Hz_8BPC = 2 + T_1280x720_60Hz_8BPC = 3 + T_1280x960_60Hz_8BPC = 4 + T_1920_1080_60Hz_8BPC = 5 + T_1920_1440_60Hz_8BPC = 6 + T_1920x1080_120Hz_8BPC = 7 + T_1280x800_60Hz_RB1_6BPC = 8 + T_1280x768_60Hz_RB1_6BPC = 9 + T_800x600_60Hz_10BPC = 10 + T_1024x768_60Hz_10BPC = 11 + T_1280x1024_60Hz_8BPC = 12 + T_1360x768_60Hz_10BPC = 13 + T_1280x800_60Hz_RB1_10BPC = 14 + T_1400x1050_60Hz_RB1_8BPC = 15 + T_1280x768_60Hz_RB1_10BPC = 16 + T_1600x1200_60Hz_RB1_6BPC = 17 + T_2048x1536_60Hz_RB1_8BPC = 18 + T_1792x1344_60Hz_8BPC = 19 + T_1600x1200_60Hz_RB1_10BPC = 20 + T_3840x2160_30Hz_8BPC = 21 + + +class Hbr3_4Lane(IntEnum): + """ + Describes available timings for field `hbr3_4l`. + """ + NoneTiming = 0 + T_640x480_60Hz_6BPC = 1 + T_848x480_60Hz_8BPC = 2 + T_1280x720_60Hz_8BPC = 3 + T_1280x960_60Hz_8BPC = 4 + T_1920_1080_60Hz_8BPC = 5 + T_1920_1440_60Hz_8BPC = 6 + T_1920x1080_120Hz_8BPC = 7 + T_1280x800_60Hz_RB1_6BPC = 8 + T_1280x768_60Hz_RB1_6BPC = 9 + T_800x600_60Hz_10BPC = 10 + T_1024x768_60Hz_10BPC = 11 + T_1280x1024_60Hz_8BPC = 12 + T_1360x768_60Hz_10BPC = 13 + T_1280x800_60Hz_RB1_10BPC = 14 + T_1400x1050_60Hz_RB1_8BPC = 15 + T_1280x768_60Hz_RB1_10BPC = 16 + T_1600x1200_60Hz_RB1_6BPC = 17 + T_2048x1536_60Hz_RB1_8BPC = 18 + T_1792x1344_60Hz_8BPC = 19 + T_1600x1200_60Hz_RB1_10BPC = 20 + T_3840x2160_30Hz_8BPC = 21 + T_3840x2160_60Hz_8BPC = 22 + + +class TestAutomationFlags: + """ + Class `TestAutomationFlags` allows configuring test automation flags: + - Set and get Test link training flag `test_link_training`. + - Set and get Test EDID read flag `test_edid_read`. + - Set and get Test video pattern flag `test_video_pattern`. + - Set and get Test audio pattern flag `test_audio_pattern`. + - Set and get Test Event indication `event_indication` type `EventIndication`. + """ + def __init__(self, json_obj): + self._param = Param(json_obj) + + @property + def test_link_training(self) -> bool: + """ + Set and get link training flag. + + Returns: + object of bool type + """ + return self._param._get_by_bitmask(0, bool) + + @test_link_training.setter + def test_link_training(self, test_link_training: bool): + self._param._set_by_bitmask(int(test_link_training), 0) + + @property + def test_edid_read(self) -> bool: + """ + Set and get EDID read flag. + + Returns: + object of bool type + """ + return self._param._get_by_bitmask(1, bool) + + @test_edid_read.setter + def test_edid_read(self, test_edid_read: bool): + self._param._set_by_bitmask(int(test_edid_read), 1) + + @property + def test_video_pattern(self) -> bool: + """ + Set and get video pattern flag. + + Returns: + object of bool type + """ + return self._param._get_by_bitmask(2, bool) + + @test_video_pattern.setter + def test_video_pattern(self, test_video_pattern: bool): + self._param._set_by_bitmask(int(test_video_pattern), 2) + + @property + def test_audio_pattern(self) -> bool: + """ + Set and get audio pattern flag. + + Returns: + object of bool type + """ + return self._param._get_by_bitmask(3, bool) + + @test_audio_pattern.setter + def test_audio_pattern(self, test_audio_pattern: bool): + self._param._set_by_bitmask(int(test_audio_pattern), 3) + + @property + def event_indication(self) -> EventIndication: + """ + Set and get link training flag. + + Returns: + object of `EventIndication` type + """ + return self._param._get_by_bitmask(4, EventIndication) + + @event_indication.setter + def event_indication(self, event_indication: EventIndication): + self._param._set_by_bitmask(event_indication.value, 4) + + +class DutCapsFlags(Param): + """ + Class `DutCapsFlags` defines the DUT capabilities as flags and allows setting: + - Voltage swing level 3 (1.2V) supported `voltage_swing_supported`. + - Pre-emphasis level 3 (9.5dB) supported `pre_emphasis_supported`. + - Fixed timing DUT supported `fixed_timing_dut_supported`. + - Spread Spectrum supported `spread_spectrum_supported`. + - Video format change without LT supported `change_vf_without_lt_supported`. + - Lane count reduction without LT supported `lane_count_reduction_without_lt_supported`. + - E-DDC protocol supported `e_ddc_protocol_supported`. + - Audio Info Frame for 2 channel audio transmission supported `audio_transmission_supported`. + - Define that DUT is Type-C device `dut_is_type_c_device`. + - FEC supported `fec_supported`. + - FEC disable sequence supported `fec_disable_sequence_supported`. + - Audio without Video supported `audio_without_video_supported`. + - DSC supported `dsc_supported`. + - DSC block prediction supported `dsc_block_prediction_supported`. + - DisplayID Type VII Detailed Timing Descriptor supported `display_id_vii_supported`. + - DisplayID Type VIII Detailed Timing Descriptor supported `display_id_viii_supported`. + - DisplayID Type IX Detailed Timing Descriptor supported `display_id_ix_supported`. + - DisplayID Type X Detailed Timing Descriptor supported `display_id_x_supported`. + - 2x1 tiled display and DisplayID Tiled Display Topology data block supported `display_id_tiled_display_topology`. + - Field sequential stereo and DisplayID Tiled Stereo Display Interface data block supported + `display_id_tiled_stereo_display` + - Stacked frame stereo and DisplayID Tiled Stereo Display Interface data block supported + `stacked_frame_stereo_supported`. + """ + def __init__(self, json_obj): + super().__init__(json_obj) + + @property + def voltage_swing_supported(self) -> bool: + """ + Set and get Voltage swing level flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(0, bool) + + @voltage_swing_supported.setter + def voltage_swing_supported(self, voltage_swing_supported: bool): + self._set_by_bitmask(voltage_swing_supported, 0) + + + @property + def pre_emphasis_supported(self) -> bool: + """ + Set and get Pre-emphasis level flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(1, bool) + + @pre_emphasis_supported.setter + def pre_emphasis_supported(self, pre_emphasis_supported: bool): + self._set_by_bitmask(pre_emphasis_supported, 1) + + @property + def fixed_timing_dut_supported(self) -> bool: + """ + Set and get Fixed timing DUT flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(2, bool) + + @fixed_timing_dut_supported.setter + def fixed_timing_dut_supported(self, fixed_timing_dut_supported: bool): + self._set_by_bitmask(fixed_timing_dut_supported, 2) + + @property + def spread_spectrum_supported(self) -> bool: + """ + Set and get Spread Spectrum flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(3, bool) + + @spread_spectrum_supported.setter + def spread_spectrum_supported(self, spread_spectrum_supported: bool): + self._set_by_bitmask(spread_spectrum_supported, 3) + + @property + def change_vf_without_lt_supported(self) -> bool: + """ + Set and get Video format change without LT flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(4, bool) + + @change_vf_without_lt_supported.setter + def change_vf_without_lt_supported(self, change_vf_without_lt_supported: bool): + self._set_by_bitmask(change_vf_without_lt_supported, 4) + + @property + def lane_count_reduction_without_lt_supported(self) -> bool: + """ + Set and get Lane count reduction without LT flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(5, bool) + + @lane_count_reduction_without_lt_supported.setter + def lane_count_reduction_without_lt_supported(self, lane_count_reduction_without_lt_supported: bool): + self._set_by_bitmask(lane_count_reduction_without_lt_supported, 5) + + @property + def e_ddc_protocol_supported(self) -> bool: + """ + Set and get E-DDC protocol flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(6, bool) + + @e_ddc_protocol_supported.setter + def e_ddc_protocol_supported(self, e_ddc_protocol_supported: bool): + self._set_by_bitmask(e_ddc_protocol_supported, 6) + + @property + def audio_transmission_supported(self) -> bool: + """ + Set and get Audio Info Frame for 2 channel audio transmission flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(7, bool) + + @audio_transmission_supported.setter + def audio_transmission_supported(self, audio_transmission_supported: bool): + self._set_by_bitmask(audio_transmission_supported, 7) + + @property + def dut_is_type_c_device(self) -> bool: + """ + Set and get Define that DUT is Type-C device flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(8, bool) + + @dut_is_type_c_device.setter + def dut_is_type_c_device(self, dut_is_type_c_device: bool): + self._set_by_bitmask(dut_is_type_c_device, 8) + + @property + def fec_supported(self) -> bool: + """ + Set and get FEC flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(9, bool) + + @fec_supported.setter + def fec_supported(self, fec_supported: bool): + self._set_by_bitmask(fec_supported, 9) + + @property + def fec_disable_sequence_supported(self) -> bool: + """ + Set and get FEC disable sequence flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(10, bool) + + @fec_disable_sequence_supported.setter + def fec_disable_sequence_supported(self, fec_disable_sequence_supported: bool): + self._set_by_bitmask(fec_disable_sequence_supported, 10) + + @property + def audio_without_video_supported(self) -> bool: + """ + Set and get Audio without Video flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(11, bool) + + @audio_without_video_supported.setter + def audio_without_video_supported(self, audio_without_video_supported: bool): + self._set_by_bitmask(audio_without_video_supported, 11) + + @property + def dsc_supported(self) -> bool: + """ + Set and get DSC flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(12, bool) + + @dsc_supported.setter + def dsc_supported(self, dsc_supported: bool): + self._set_by_bitmask(dsc_supported, 12) + + @property + def dsc_block_prediction_supported(self) -> bool: + """ + Set and get DSC block prediction flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(13, bool) + + @dsc_block_prediction_supported.setter + def dsc_block_prediction_supported(self, dsc_block_prediction_supported: bool): + self._set_by_bitmask(dsc_block_prediction_supported, 13) + + @property + def native_display_id_read(self) -> bool: + """ + Set and get DUT supports native Display ID read flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(14, bool) + + @native_display_id_read.setter + def native_display_id_read(self, native_display_id_read: bool): + self._set_by_bitmask(native_display_id_read, 14) + + @property + def display_id_vii_supported(self) -> bool: + """ + Set and get DisplayID Type VII Detailed Timing Descriptor flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(15, bool) + + @display_id_vii_supported.setter + def display_id_vii_supported(self, display_id_vii_supported: bool): + self._set_by_bitmask(display_id_vii_supported, 15) + + @property + def display_id_viii_supported(self) -> bool: + """ + Set and get DisplayID Type VIII Detailed Timing Descriptor flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(16, bool) + + @display_id_viii_supported.setter + def display_id_viii_supported(self, display_id_viii_supported: bool): + self._set_by_bitmask(display_id_viii_supported, 16) + + @property + def display_id_ix_supported(self) -> bool: + """ + Set and get DisplayID Type IX Detailed Timing Descriptor flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(17, bool) + + @display_id_ix_supported.setter + def display_id_ix_supported(self, display_id_ix_supported: bool): + self._set_by_bitmask(display_id_ix_supported, 17) + + @property + def display_id_x_supported(self) -> bool: + """ + Set and get DisplayID Type X Detailed Timing Descriptor flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(18, bool) + + @display_id_x_supported.setter + def display_id_x_supported(self, display_id_x_supported: bool): + self._set_by_bitmask(display_id_x_supported, 18) + + @property + def display_id_tiled_display_topology(self) -> bool: + """ + Set and get 2x1 tiled display and DisplayID Tiled Display Topology data block flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(19, bool) + + @display_id_tiled_display_topology.setter + def display_id_tiled_display_topology(self, display_id_tiled_display_topology: bool): + self._set_by_bitmask(display_id_tiled_display_topology, 19) + + @property + def display_id_tiled_stereo_display(self) -> bool: + """ + Set and get Field sequential stereo and DisplayID Tiled Stereo Display Interface data block flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(20, bool) + + @display_id_tiled_stereo_display.setter + def display_id_tiled_stereo_display(self, display_id_tiled_stereo_display: bool): + self._set_by_bitmask(display_id_tiled_stereo_display, 20) + + @property + def stacked_frame_stereo_supported(self) -> bool: + """ + Set and get Stacked frame stereo and DisplayID Tiled Stereo Display Interface data flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(21, bool) + + @stacked_frame_stereo_supported.setter + def stacked_frame_stereo_supported(self, stacked_frame_stereo_supported: bool): + self._set_by_bitmask(stacked_frame_stereo_supported, 21) + + def clear_all_caps(self): + """ + Clear all settings. + """ + self.voltage_swing_supported = False + self.pre_emphasis_supported = False + self.fixed_timing_dut_supported = False + self.spread_spectrum_supported = False + self.change_vf_without_lt_supported = False + self.lane_count_reduction_without_lt_supported = False + self.e_ddc_protocol_supported = False + self.audio_transmission_supported = False + self.dut_is_type_c_device = False + self.fec_supported = False + self.fec_disable_sequence_supported = False + self.audio_without_video_supported = False + self.dsc_supported = False + self.dsc_block_prediction_supported = False + self.native_display_id_read = False + self.display_id_vii_supported = False + self.display_id_viii_supported = False + self.display_id_ix_supported = False + self.display_id_x_supported = False + self.display_id_tiled_display_topology = False + self.display_id_tiled_stereo_display = False + self.stacked_frame_stereo_supported = False + + +class DutCapsDp14: + """ + Class `DutCapsDp14` defines the DUT capabilities and allows setting: + - Defines the maximum number of lanes supported by the DUT `max_lanes`. + - Maximum link rate supported by the DUT `max_link_rate`. + - Dut capabilities flags `dut_caps_flags` type `DutCapsFlags`. + """ + def __init__(self, json_obj): + self.__max_lanes = Param(json_obj["TSI_DP14_SRCCTS_MAX_LANES"]) + self.__max_link_rate = Param(json_obj["TSI_DP14_SRCCTS_MAX_LINK_RATE"]) + self.__dut_caps_flags = DutCapsFlags(json_obj["TSI_DP14_SRCCTS_DUT_CAPS"]) + + @property + def max_lanes(self) -> int: + """ + Set and get number of maximum lanes. + + Returns: + object of int type + """ + return self.__max_lanes.default_value + + @max_lanes.setter + def max_lanes(self, max_lanes: int): + if max_lanes not in [1, 2, 4]: + raise ValueError(f"Max lane count must be from list: 1, 2, 4. Current value: {max_lanes}") + self.__max_lanes.default_value = max_lanes + + @property + def max_link_rate(self): + """ + Set and get number of maximum link rate. + + Returns: + object of int type + """ + return self.__max_link_rate + + @max_link_rate.setter + def max_link_rate(self, max_link_rate: float): + if max_link_rate not in [1.62, 2.7, 5.4, 8.1]: + raise ValueError(f"Max link rate must be from list: 1.62, 2.7, 5.4, 8.1. Current value: {max_link_rate}") + self.__max_link_rate.default_value = round(max_link_rate / 0.27) + + @property + def dut_caps_flags(self) -> DutCapsFlags: + """ + Set and get DUT capabilities flags. + + Returns: + object of `DutCapsFlags` type + """ + return self.__dut_caps_flags + + @dut_caps_flags.setter + def dut_caps_flags(self, dut_caps_flags: DutCapsFlags): + self.__dut_caps_flags = dut_caps_flags + + +class TimeStampGenerationDp14: + """ + Class `TimeStampGenerationDp14` defines timings which used for different rates and allows setting: + - Video timings used for Reduced Bitrate (RBR, 1.62Gbps) testing for 1 lanes `rbb_1l`. + - Video timings used for Reduced Bitrate (RBR, 1.62Gbps) testing for 2 lanes `rbb_2l`. + - Video timings used for Reduced Bitrate (RBR, 1.62Gbps) testing for 4 lanes `rbb_4l`. + - Video timings used for High Bitrate (HBR, 2.7Gbps) testing for 1 lanes `hbr_1l`. + - Video timings used for High Bitrate (HBR, 2.7Gbps) testing for 2 lanes `hbr_2l`. + - Video timings used for High Bitrate (HBR, 2.7Gbps) testing for 4 lanes `hbr_4l`. + - Video timings used for High Bitrate-2 (HBR2, 5.4Gbps) testing for 1 lane `hbr2_1l`. + - Video timings used for High Bitrate-2 (HBR2, 5.4Gbps) testing for 2 lane `hbr2_2l`. + - Video timings used for High Bitrate-2 (HBR2, 5.4Gbps) testing for 4 lane `hbr2_4l`. + - Video timings used for High Bitrate-3 (HBR3, 8.1Gbps) testing for 1 lanes `hbr3_1l`. + - Video timings used for High Bitrate-3 (HBR3, 8.1Gbps) testing for 2 lanes `hbr3_2l`. + - Video timings used for High Bitrate-3 (HBR3, 8.1Gbps) testing for 4 lanes `hbr3_4l`. + """ + def __init__(self, json_obj): + self.__rbb_1l = Param(json_obj["TSI_DP14_SRCCTS_1L_RBB"]) + self.__rbb_2l = Param(json_obj["TSI_DP14_SRCCTS_2L_RBB"]) + self.__rbb_4l = Param(json_obj["TSI_DP14_SRCCTS_4L_RBB"]) + + self.__hbr_1l = Param(json_obj["TSI_DP14_SRCCTS_1L_HBR"]) + self.__hbr_2l = Param(json_obj["TSI_DP14_SRCCTS_2L_HBR"]) + self.__hbr_4l = Param(json_obj["TSI_DP14_SRCCTS_4L_HBR"]) + + self.__hbr2_1l = Param(json_obj["TSI_DP14_SRCCTS_1L_HBR2"]) + self.__hbr2_2l = Param(json_obj["TSI_DP14_SRCCTS_2L_HBR2"]) + self.__hbr2_4l = Param(json_obj["TSI_DP14_SRCCTS_4L_HBR2"]) + + self.__hbr3_1l = Param(json_obj["TSI_DP14_SRCCTS_1L_HBR3"]) + self.__hbr3_2l = Param(json_obj["TSI_DP14_SRCCTS_2L_HBR3"]) + self.__hbr3_4l = Param(json_obj["TSI_DP14_SRCCTS_4L_HBR3"]) + + @property + def rbb_1l(self): + """ + Set and get Video timings used for Reduced Bitrate (RBR, 1.62Gbps) testing for 1 lanes. + + Returns: + object of `Rbb1Lane` type + """ + return self.__rbb_1l + + @rbb_1l.setter + def rbb_1l(self, rbb_1l: Rbb1Lane): + self.__rbb_1l.default_value = rbb_1l.value + + @property + def rbb_2l(self): + """ + Set and get Video timings used for Reduced Bitrate (RBR, 1.62Gbps) testing for 2 lanes. + + Returns: + object of `Rbb2Lane` type + """ + return self.__rbb_2l + + @rbb_2l.setter + def rbb_2l(self, rbb_2l: Rbb2Lane): + self.__rbb_2l.default_value = rbb_2l.value + + @property + def rbb_4l(self): + """ + Set and get Video timings used for Reduced Bitrate (RBR, 1.62Gbps) testing for 4 lanes. + + Returns: + object of `Rbb4Lane` type + """ + return self.__rbb_4l + + @rbb_4l.setter + def rbb_4l(self, rbb_4l: Rbb4Lane): + self.__rbb_4l.default_value = rbb_4l.value + + @property + def hbr_1l(self): + """ + Set and get Video timings used for High Bitrate (HBR, 2.7Gbps) testing for 1 lanes. + + Returns: + object of `Hbr1Lane` type + """ + return self.__hbr_1l + + @hbr_1l.setter + def hbr_1l(self, hbr_1l: Hbr1Lane): + self.__hbr_1l.default_value = hbr_1l.value + + @property + def hbr_2l(self): + """ + Set and get Video timings used for High Bitrate (HBR, 2.7Gbps) testing for 2 lanes. + + Returns: + object of `Hbr2Lane` type + """ + return self.__hbr_2l + + @hbr_2l.setter + def hbr_2l(self, hbr_2l: Hbr2Lane): + self.__hbr_2l.default_value = hbr_2l.value + + @property + def hbr_4l(self): + """ + Set and get Video timings used for High Bitrate (HBR, 2.7Gbps) testing for 4 lanes. + + Returns: + object of `Hbr4Lane` type + """ + return self.__hbr_4l + + @hbr_4l.setter + def hbr_4l(self, hbr_4l: Hbr4Lane): + self.__hbr_4l.default_value = hbr_4l.value + + @property + def hbr2_1l(self): + """ + Set and get Video timings used for High Bitrate-2 (HBR2, 5.4Gbps) testing for 1 lane. + + Returns: + object of `Hbr2_1Lane` type + """ + return self.__hbr2_1l + + @hbr2_1l.setter + def hbr2_1l(self, hbr2_1l: Hbr2_1Lane): + self.__hbr2_1l.default_value = hbr2_1l.value + + @property + def hbr2_2l(self): + """ + Set and get Video timings used for High Bitrate-2 (HBR2, 5.4Gbps) testing for 2 lane. + + Returns: + object of `Hbr2_2Lane` type + """ + return self.__hbr2_2l + + @hbr2_2l.setter + def hbr2_2l(self, hbr2_2l: Hbr2_2Lane): + self.__hbr2_2l.default_value = hbr2_2l.value + + @property + def hbr2_4l(self): + """ + Set and get Video timings used for High Bitrate-2 (HBR2, 5.4Gbps) testing for 4 lane. + + Returns: + object of `Hbr2_4Lane` type + """ + return self.__hbr2_4l + + @hbr2_4l.setter + def hbr2_4l(self, hbr2_4l: Hbr2_4Lane): + self.__hbr2_4l.default_value = hbr2_4l.value + + @property + def hbr3_1l(self): + """ + Set and get Video timings used for High Bitrate-3 (HBR3, 8.1Gbps) testing for 1 lanes. + + Returns: + object of `Hbr3_1Lane` type + """ + return self.__hbr3_1l + + @hbr3_1l.setter + def hbr3_1l(self, hbr3_1l: Hbr3_1Lane): + self.__hbr3_1l.default_value = hbr3_1l.value + + @property + def hbr3_2l(self): + """ + Set and get Video timings used for High Bitrate-3 (HBR3, 8.1Gbps) testing for 2 lanes. + + Returns: + object of `Hbr3_2Lane` type + """ + return self.__hbr3_2l + + @hbr3_2l.setter + def hbr3_2l(self, hbr3_2l: Hbr3_2Lane): + self.__hbr3_2l.default_value = hbr3_2l.value + + @property + def hbr3_4l(self): + """ + Set and get Video timings used for High Bitrate-3 (HBR3, 8.1Gbps) testing for 4 lanes. + + Returns: + object of `Hbr3_4Lane` type + """ + return self.__hbr3_4l + + @hbr3_4l.setter + def hbr3_4l(self, hbr3_4l: Hbr3_4Lane): + self.__hbr3_4l.default_value = hbr3_4l.value + + +class ColorimetryDp14: + """ + Class `ColorimetryDp14` describes optional and additional color modes to be used with DP CTS tests. + - RGB 6 bpc VESA and RGB 8 bpc VESA - Mandatory. + - RGB 10 bpc VESA `rgb_10bpc_vesa`. + - RGB 8 bpc CTA `rgb_8bpc_cta`. + - RGB 10 bpc CTA `rgb_10bpc_cta`. + - YCbCr-422 8 bpc CTA ITU-601 `ycbcr422_8bpc_cta_itu601`. + - YCbCr-422 10 bpc CTA ITU-601 `ycbcr422_10bpc_cta_itu601`. + - YCbCr-422 8 bpc CTA ITU-709 `ycbcr422_8bpc_cta_itu709`. + - YCbCr-422 10 bpc CTA ITU-709 `ycbcr422_10bpc_cta_itu709`. + - YCbCr-420 8 bpc CTA ITU-601 `ycbcr420_8bpc_cta_itu601`. + - YCbCr-420 10 bpc CTA ITU-601 `ycbcr420_10bpc_cta_itu601`. + - YCbCr-420 8 bpc CTA ITU-709 `ycbcr420_8bpc_cta_itu709`. + - YCbCr-420 10 bpc CTA ITU-709 `ycbcr420_10bpc_cta_itu709`. + - YCbCr-444 8 bpc CTA ITU-601 `ycbcr444_8bpc_cta_itu601`. + - YCbCr-444 10 bpc CTA ITU-601 `ycbcr444_10bpc_cta_itu601`. + - YCbCr-444 8 bpc CTA ITU-709 `ycbcr444_8bpc_cta_itu709`. + - YCbCr-444 10 bpc CTA ITU-709 `ycbcr444_10bpc_cta_itu709`. + """ + class __ColorMode: + + def __init__(self, value: int): + self.value = value + self.state = False + + def __init__(self, json_obj): + self.__color_mode_count = Param(json_obj["TSI_DP14_SRCCTS_COLOR_FORMATS"]) + self.__rgb_6bpc_vesa = Param(json_obj["TSI_DP14_SRCCTS_COLOR_MODE_0"]) + self.__rgb_8bpc_vesa = Param(json_obj["TSI_DP14_SRCCTS_COLOR_MODE_1"]) + self.__rgb_10bpc_vesa = self.__ColorMode(make_cf(CMTFormat.RGB_LEGACY, CMTBitDepth.BPC_10)) + self.__rgb_8bpc_cta = self.__ColorMode(make_cf(CMTFormat.RGB_CEA, CMTBitDepth.BPC_8)) + self.__rgb_10bpc_cta = self.__ColorMode(make_cf(CMTFormat.RGB_CEA, CMTBitDepth.BPC_10)) + self.__ycbcr422_8bpc_cta_itu601 = self.__ColorMode(make_cf(CMTFormat.YCBCR_422_BT601, CMTBitDepth.BPC_8)) + self.__ycbcr422_10bpc_cta_itu601 = self.__ColorMode(make_cf(CMTFormat.YCBCR_422_BT601, CMTBitDepth.BPC_10)) + self.__ycbcr422_8bpc_cta_itu709 = self.__ColorMode(make_cf(CMTFormat.YCBCR_422_BT709, CMTBitDepth.BPC_8)) + self.__ycbcr422_10bpc_cta_itu709 = self.__ColorMode(make_cf(CMTFormat.YCBCR_422_BT709, CMTBitDepth.BPC_10)) + self.__ycbcr444_8bpc_cta_itu601 = self.__ColorMode(make_cf(CMTFormat.YCBCR_444_BT601, CMTBitDepth.BPC_8)) + self.__ycbcr444_10bpc_cta_itu601 = self.__ColorMode(make_cf(CMTFormat.YCBCR_444_BT601, CMTBitDepth.BPC_10)) + self.__ycbcr444_8bpc_cta_itu709 = self.__ColorMode(make_cf(CMTFormat.YCBCR_444_BT709, CMTBitDepth.BPC_8)) + self.__ycbcr444_10bpc_cta_itu709 = self.__ColorMode(make_cf(CMTFormat.YCBCR_444_BT709, CMTBitDepth.BPC_10)) + + self.__color_mode_count.default_value = 2 + self.__queue = [ + Param(json_obj["TSI_DP14_SRCCTS_COLOR_MODE_2"]), + Param(json_obj["TSI_DP14_SRCCTS_COLOR_MODE_3"]), + Param(json_obj["TSI_DP14_SRCCTS_COLOR_MODE_4"]), + Param(json_obj["TSI_DP14_SRCCTS_COLOR_MODE_5"]), + Param(json_obj["TSI_DP14_SRCCTS_COLOR_MODE_6"]), + Param(json_obj["TSI_DP14_SRCCTS_COLOR_MODE_7"]), + Param(json_obj["TSI_DP14_SRCCTS_COLOR_MODE_8"]), + Param(json_obj["TSI_DP14_SRCCTS_COLOR_MODE_9"]), + Param(json_obj["TSI_DP14_SRCCTS_COLOR_MODE_10"]), + Param(json_obj["TSI_DP14_SRCCTS_COLOR_MODE_11"]), + Param(json_obj["TSI_DP14_SRCCTS_COLOR_MODE_12"]) + ] + + def __find_index_by_value(self, value: int) -> int: + for index, item in enumerate(self.__queue): + if item.default_value == value: + return index + return -1 + + def __last_free_space_in_queue(self) -> int: + return self.__find_index_by_value(0) + + def __shift_values_in_queue(self, index: int): + for i in range(index, len(self.__queue) - 1): + self.__queue[i].default_value = self.__queue[i + 1].default_value + + def __check_color_mode(self, color_mode): + if color_mode.state: + if self.__find_index_by_value(color_mode.value) == -1: + self.__queue[self.__last_free_space_in_queue()].default_value = color_mode.value + self.__color_mode_count.default_value += 1 + else: + if self.__find_index_by_value(color_mode.value) != -1: + index = self.__find_index_by_value(color_mode.value) + self.__queue[index].default_value = 0 + self.__shift_values_in_queue(index) + if self.__color_mode_count.default_value >= 3: + self.__color_mode_count.default_value -= 1 + + @property + def rgb_10bpc_vesa(self) -> bool: + """ + Set and get RGB 10 bpc VESA flag support. + + Returns: + object of bool type + """ + return self.__rgb_10bpc_vesa.state + + @rgb_10bpc_vesa.setter + def rgb_10bpc_vesa(self, rgb_10bpc_vesa: bool): + if self.__rgb_10bpc_vesa.state != rgb_10bpc_vesa: + self.__rgb_10bpc_vesa.state = rgb_10bpc_vesa + self.__check_color_mode(self.__rgb_10bpc_vesa) + + @property + def rgb_8bpc_cta(self) -> bool: + """ + Set and get RGB 8 bpc CTA flag support. + + Returns: + object of bool type + """ + return self.__rgb_8bpc_cta.state + + @rgb_8bpc_cta.setter + def rgb_8bpc_cta(self, rgb_8bpc_cta: bool): + if self.__rgb_8bpc_cta.state != rgb_8bpc_cta: + self.__rgb_8bpc_cta.state = rgb_8bpc_cta + self.__check_color_mode(self.__rgb_8bpc_cta) + + @property + def rgb_10bpc_cta(self) -> bool: + """ + Set and get RGB 10 bpc CTA flag support. + + Returns: + object of bool type + """ + return self.__rgb_10bpc_cta.state + + @rgb_10bpc_cta.setter + def rgb_10bpc_cta(self, rgb_10bpc_cta: bool): + if self.__rgb_10bpc_cta.state != rgb_10bpc_cta: + self.__rgb_10bpc_cta.state = rgb_10bpc_cta + self.__check_color_mode(self.__rgb_10bpc_cta) + + @property + def ycbcr422_8bpc_cta_itu601(self) -> bool: + """ + Set and get YCbCr-422 8 bpc CTA ITU-601 flag support. + + Returns: + object of bool type + """ + return self.__ycbcr422_8bpc_cta_itu601.state + + @ycbcr422_8bpc_cta_itu601.setter + def ycbcr422_8bpc_cta_itu601(self, ycbcr422_8bpc_cta_itu601: bool): + if self.__ycbcr422_8bpc_cta_itu601.state != ycbcr422_8bpc_cta_itu601: + self.__ycbcr422_8bpc_cta_itu601.state = ycbcr422_8bpc_cta_itu601 + self.__check_color_mode(self.__ycbcr422_8bpc_cta_itu601) + + @property + def ycbcr422_10bpc_cta_itu601(self) -> bool: + """ + Set and get YCbCr-422 10 bpc CTA ITU-601 flag support. + + Returns: + object of bool type + """ + return self.__ycbcr422_10bpc_cta_itu601.state + + @ycbcr422_10bpc_cta_itu601.setter + def ycbcr422_10bpc_cta_itu601(self, ycbcr422_10bpc_cta_itu601: bool): + if self.__ycbcr422_10bpc_cta_itu601.state != ycbcr422_10bpc_cta_itu601: + self.__ycbcr422_10bpc_cta_itu601.state = ycbcr422_10bpc_cta_itu601 + self.__check_color_mode(self.__ycbcr422_10bpc_cta_itu601) + + @property + def ycbcr422_8bpc_cta_itu709(self) -> bool: + """ + Set and get YCbCr-422 8 bpc CTA ITU-709 flag support. + + Returns: + object of bool type + """ + return self.__ycbcr422_8bpc_cta_itu709.state + + @ycbcr422_8bpc_cta_itu709.setter + def ycbcr422_8bpc_cta_itu709(self, ycbcr422_8bpc_cta_itu709: bool): + if self.__ycbcr422_8bpc_cta_itu709.state != ycbcr422_8bpc_cta_itu709: + self.__ycbcr422_8bpc_cta_itu709.state = ycbcr422_8bpc_cta_itu709 + self.__check_color_mode(self.__ycbcr422_8bpc_cta_itu709) + + @property + def ycbcr422_10bpc_cta_itu709(self) -> bool: + """ + Set and get YCbCr-422 10 bpc CTA ITU-709 flag support. + + Returns: + object of bool type + """ + return self.__ycbcr422_10bpc_cta_itu709.state + + @ycbcr422_10bpc_cta_itu709.setter + def ycbcr422_10bpc_cta_itu709(self, ycbcr422_10bpc_cta_itu709: bool): + if self.__ycbcr422_10bpc_cta_itu709.state != ycbcr422_10bpc_cta_itu709: + self.__ycbcr422_10bpc_cta_itu709.state = ycbcr422_10bpc_cta_itu709 + self.__check_color_mode(self.__ycbcr422_10bpc_cta_itu709) + + @property + def ycbcr444_8bpc_cta_itu601(self) -> bool: + """ + Set and get YCbCr-444 8 bpc CTA ITU-601 flag support. + + Returns: + object of bool type + """ + return self.__ycbcr444_8bpc_cta_itu601.state + + @ycbcr444_8bpc_cta_itu601.setter + def ycbcr444_8bpc_cta_itu601(self, ycbcr444_8bpc_cta_itu601: bool): + if self.__ycbcr444_8bpc_cta_itu601.state != ycbcr444_8bpc_cta_itu601: + self.__ycbcr444_8bpc_cta_itu601.state = ycbcr444_8bpc_cta_itu601 + self.__check_color_mode(self.__ycbcr444_8bpc_cta_itu601) + + @property + def ycbcr444_10bpc_cta_itu601(self) -> bool: + """ + Set and get YCbCr-444 10 bpc CTA ITU-601 flag support. + + Returns: + object of bool type + """ + return self.__ycbcr444_10bpc_cta_itu601.state + + @ycbcr444_10bpc_cta_itu601.setter + def ycbcr444_10bpc_cta_itu601(self, ycbcr444_10bpc_cta_itu601: bool): + if self.__ycbcr444_10bpc_cta_itu601.state != ycbcr444_10bpc_cta_itu601: + self.__ycbcr444_10bpc_cta_itu601.state = ycbcr444_10bpc_cta_itu601 + self.__check_color_mode(self.__ycbcr444_10bpc_cta_itu601) + + @property + def ycbcr444_8bpc_cta_itu709(self) -> bool: + """ + Set and get YCbCr-444 8 bpc CTA ITU-709 flag support. + + Returns: + object of bool type + """ + return self.__ycbcr444_8bpc_cta_itu709.state + + @ycbcr444_8bpc_cta_itu709.setter + def ycbcr444_8bpc_cta_itu709(self, ycbcr444_8bpc_cta_itu709: bool): + if self.__ycbcr444_8bpc_cta_itu709.state != ycbcr444_8bpc_cta_itu709: + self.__ycbcr444_8bpc_cta_itu709.state = ycbcr444_8bpc_cta_itu709 + self.__check_color_mode(self.__ycbcr444_8bpc_cta_itu709) + + @property + def ycbcr444_10bpc_cta_itu709(self) -> bool: + """ + Set and get YCbCr-444 10 bpc CTA ITU-709 flag support. + + Returns: + object of bool type + """ + return self.__ycbcr444_10bpc_cta_itu709.state + + @ycbcr444_10bpc_cta_itu709.setter + def ycbcr444_10bpc_cta_itu709(self, ycbcr444_10bpc_cta_itu709: bool): + if self.__ycbcr444_10bpc_cta_itu709.state != ycbcr444_10bpc_cta_itu709: + self.__ycbcr444_10bpc_cta_itu709.state = ycbcr444_10bpc_cta_itu709 + self.__check_color_mode(self.__ycbcr444_10bpc_cta_itu709) + + def clear_all(self): + """ + Clear all settings. + """ + self.rgb_10bpc_vesa = False + self.rgb_8bpc_cta = False + self.rgb_10bpc_cta = False + self.ycbcr422_8bpc_cta_itu601 = False + self.ycbcr422_10bpc_cta_itu601 = False + self.ycbcr422_8bpc_cta_itu709 = False + self.ycbcr422_10bpc_cta_itu709 = False + self.ycbcr444_8bpc_cta_itu601 = False + self.ycbcr444_10bpc_cta_itu601 = False + self.ycbcr444_8bpc_cta_itu709 = False + self.ycbcr444_10bpc_cta_itu709 = False + + for item in self.__queue: + item.default_value = 0 + + def select_all(self): + """ + Select all modes. + """ + self.rgb_10bpc_vesa = True + self.rgb_8bpc_cta = True + self.rgb_10bpc_cta = True + self.ycbcr422_8bpc_cta_itu601 = True + self.ycbcr422_10bpc_cta_itu601 = True + self.ycbcr422_8bpc_cta_itu709 = True + self.ycbcr422_10bpc_cta_itu709 = True + self.ycbcr444_8bpc_cta_itu601 = True + self.ycbcr444_10bpc_cta_itu601 = True + self.ycbcr444_8bpc_cta_itu709 = True + self.ycbcr444_10bpc_cta_itu709 = True + + +class DebugOptions: + + def __init__(self, json_obj): + self.__param = Param(json_obj["TSI_DP14_SRCCTS_DUT_DEBUG_CONF"]) + + @property + def continue_on_fail(self) -> bool: + """ + Set and get continue on fail flag. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(0, bool) + + @continue_on_fail.setter + def continue_on_fail(self, continue_on_fail: bool): + self.__param.default_value = update_default_value(value=continue_on_fail, + default_value=self.__param.default_value, + mask=self.__param.bit_field_list[0].mask) + + +class GeneralSourceDUTDp14SettingTab: + """ + Class `GeneralSourceDUTDp14SettingTab` allows working with parameters from General source part. + - Set and get timeout `timeout`. + - Set and get DUT capabilities `dut_caps` type `DutCapsDp14`. + - Set and get test automation flags `test_automation` type `TestAutomationFlags`. + - Set and get HPD pulse duration `hpd_pulse_duration`. + - Set and get link training start timeout `lt_start_timeout`. + - Set and get test cycle delay `test_cycle_delay`. + - Get fail safe mode `fail_safe_mode`. + - Set and get maximum supported video modes `max_supported_video_mode` type `MaxSupportedVideoMode`. + - Set and get packed timings for 1 lane `packed_timings_1_lane` type `PackedTimings1Lane`. + - Set and get packed timings for 2 lane `packed_timings_2_lane` type `PackedTimings2Lane`. + - Set and get packed timings for 4 lane `packed_timings_4_lane` type `PackedTimings4Lane`. + - Set and get time stamp generation `time_stamp_generation` type `TimeStampGenerationDp14`. + - Set and get colorimetry modes `colorimetry` type `ColorimetryDp14`. + - Set and get debug options `debug_options` type `DebugOptions` + """ + def __init__(self, json_obj): + self.__timeout = Param(json_obj["TSI_DP14_SRCCTS_TIMEOUT"]) + self.__dut_caps = DutCapsDp14(json_obj) + self.__test_automation = TestAutomationFlags(json_obj["TSI_DP14_SRCCTS_DUT_TA"]) + self.__hpd_pulse_duration = Param(json_obj["TSI_DP14_SRCCTS_LONG_HPD_PULSE"]) + self.__lt_start_timeout = Param(json_obj["TSI_DP14_SRCCTS_LT_START_TIMEOUT"]) + self.__test_cycle_delay = Param(json_obj["TSI_DP14_SRCCTS_TEST_CYCLE_DELAY"]) + self.__fail_safe_mode = Param(json_obj["TSI_DP14_SRCCTS_FAILSAFE_MODE"]) + self.__max_supported_video_mode = Param(json_obj["TSI_DP14_SRCCTS_MAX_RESOLUTION"]) # MaxSupportedVideoMode Enum + self.__packed_timings_1_lane = Param(json_obj["TSI_DP14_SRCCTS_1L_MOST_PACKED"]) # PackedTimings1Lane + self.__packed_timings_2_lane = Param(json_obj["TSI_DP14_SRCCTS_2L_MOST_PACKED"]) # PackedTimings2Lane + self.__packed_timings_4_lane = Param(json_obj["TSI_DP14_SRCCTS_4L_MOST_PACKED"]) # PackedTimings4Lane + self.__time_stamp_generation = TimeStampGenerationDp14(json_obj) + self.__colorimetry = ColorimetryDp14(json_obj) + self.__debug_options = DebugOptions(json_obj) + + @property + def timeout(self) -> int: + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout + + @property + def dut_caps(self): + """ + Set and get DUT caps. + + Returns: + object DutCapsDp14 + """ + return self.__dut_caps + + @dut_caps.setter + def dut_caps(self, dut_caps: DutCapsDp14): + self.__dut_caps = dut_caps + + @property + def test_automation(self) -> TestAutomationFlags: + """ + Set and get test automation flags. + + Returns: + object TestAutomationFlags + """ + return self.__test_automation + + @test_automation.setter + def test_automation(self, test_automation: TestAutomationFlags): + self.__test_automation = test_automation + + @property + def hpd_pulse_duration(self) -> int: + """ + Set and get HPD pulse duration. + + Returns: + object of int type + """ + return self.__hpd_pulse_duration.default_value + + @hpd_pulse_duration.setter + def hpd_pulse_duration(self, hpd_pulse_duration: int): + if not(self.__hpd_pulse_duration.min_value < hpd_pulse_duration < self.__hpd_pulse_duration.max_value): + raise ValueError(f"HPD pulse duration cannot be less than {self.__timeout.min_value} and more than " + f"{self.__hpd_pulse_duration.max_value}.") + self.__hpd_pulse_duration.default_value = hpd_pulse_duration + + @property + def lt_start_timeout(self) -> int: + """ + Set and get link training start timeout. + + Returns: + object of int type + """ + return self.__lt_start_timeout.default_value + + @lt_start_timeout.setter + def lt_start_timeout(self, lt_start_timeout: int): + if not(self.__lt_start_timeout.min_value < lt_start_timeout < self.__lt_start_timeout.max_value): + raise ValueError(f"LT start timeout cannot be less than {self.__lt_start_timeout.min_value} and more than " + f"{self.__lt_start_timeout.max_value}.") + self.__lt_start_timeout.default_value = lt_start_timeout + + @property + def test_cycle_delay(self) -> int: + """ + Set and get test cycle delay. + + Returns: + object of int type + """ + return self.__test_cycle_delay.default_value + + @test_cycle_delay.setter + def test_cycle_delay(self, test_cycle_delay: int): + if not(self.__test_cycle_delay.min_value < test_cycle_delay < self.__test_cycle_delay.max_value): + raise ValueError(f"Test cycle delay cannot be less than {self.__test_cycle_delay.min_value} and more than " + f"{self.__test_cycle_delay.max_value}.") + self.__test_cycle_delay.default_value = test_cycle_delay + + @property + def fail_safe_mode(self): + """ + Get fail safe mode. + + Returns: + object of int type + """ + return self.__fail_safe_mode.default_value + + @property + def max_supported_video_mode(self): + """ + Set and get maximum supported video modes. + + Returns: + object MaxSupportedVideoMode + """ + return self.__max_supported_video_mode + + @max_supported_video_mode.setter + def max_supported_video_mode(self, max_supported_video_mode: MaxSupportedVideoMode): + self.__max_supported_video_mode.default_value = max_supported_video_mode.value + + @property + def packed_timings_1_lane(self): + """ + Set and get packed timings for 1 lane. + + Returns: + object PackedTimings1Lane + """ + return self.__packed_timings_1_lane + + @packed_timings_1_lane.setter + def packed_timings_1_lane(self, packed_timings_1_lane: PackedTimings1Lane): + self.__packed_timings_1_lane.default_value = packed_timings_1_lane.value + + @property + def packed_timings_2_lane(self): + """ + Set and get packed timings for 2 lane. + + Returns: + object PackedTimings2Lane + """ + return self.__packed_timings_2_lane + + @packed_timings_2_lane.setter + def packed_timings_2_lane(self, packed_timings_2_lane: PackedTimings2Lane): + self.__packed_timings_2_lane.default_value = packed_timings_2_lane.value + + @property + def packed_timings_4_lane(self): + """ + Set and get packed timings for 4 lane. + + Returns: + object PackedTimings4Lane + """ + return self.__packed_timings_4_lane + + @packed_timings_4_lane.setter + def packed_timings_4_lane(self, packed_timings_4_lane: PackedTimings4Lane): + self.__packed_timings_4_lane.default_value = packed_timings_4_lane.value + + @property + def time_stamp_generation(self): + """ + Set and get time stamp generation. + + Returns: + object TimeStampGenerationDp14 + """ + return self.__time_stamp_generation + + @time_stamp_generation.setter + def time_stamp_generation(self, time_stamp_generation: TimeStampGenerationDp14): + self.__time_stamp_generation = time_stamp_generation + + @property + def colorimetry(self) -> ColorimetryDp14: + """ + Set and get colorimetry modes. + + Returns: + object ColorimetryDp14 + """ + return self.__colorimetry + + @colorimetry.setter + def colorimetry(self, colorimetry: ColorimetryDp14): + self.__colorimetry = colorimetry + + @property + def debug_options(self) -> DebugOptions: + """ + Set and get debug options. + + Returns: + object of 'DebugOptions' type + """ + return self.__debug_options + + @debug_options.setter + def debug_options(self, debug_config: DebugOptions): + self.__debug_options = debug_config diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/dp_2_1_common_video_modes.py b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_2_1_common_video_modes.py new file mode 100644 index 0000000..d6e75fa --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_2_1_common_video_modes.py @@ -0,0 +1,288 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param + + +class VideoModeStandard: + """ + Class `VideoModeStandard` describes available supported timings standard. + - CTA `cta` (enable/disable). + - DMT `dmt` (enable/disable). + - CVT `cvt` (enable/disable). + - CVT RB1 `cvt_rb1` (enable/disable). + - CVT RB2 `cvt_rb2` (enable/disable). + - OVT `ovt` (enable/disable). + - Set all standards `set_all`. + """ + def __init__(self, value: Param): + self.__value = value + + @property + def cta(self) -> bool: + """ + Set and get CTA flag support. + + Returns: + object of bool type + """ + return self.__value._get_by_bit_number(0) + + @property + def dmt(self) -> bool: + """ + Set and get DMT flag support. + + Returns: + object of bool type + """ + return self.__value._get_by_bit_number(1) + + @property + def cvt(self) -> bool: + """ + Set and get CVT flag support. + + Returns: + object of bool type + """ + return self.__value._get_by_bit_number(2) + + @property + def cvt_rb1(self) -> bool: + """ + Set and get RB1 flag support. + + Returns: + object of bool type + """ + return self.__value._get_by_bit_number(3) + + @property + def cvt_rb2(self) -> bool: + """ + Set and get RB2 flag support. + + Returns: + object of bool type + """ + return self.__value._get_by_bit_number(4) + + @property + def ovt(self) -> bool: + """ + Set and get OVT flag support. + + Returns: + object of bool type + """ + return self.__value._get_by_bit_number(5) + + @cta.setter + def cta(self, value: bool): + self.__value._set_by_bit_number(value, 1, 0) + + @dmt.setter + def dmt(self, value: bool): + self.__value._set_by_bit_number(value, 1, 1) + + @cvt.setter + def cvt(self, value: bool): + self.__value._set_by_bit_number(value, 1, 2) + + @cvt_rb1.setter + def cvt_rb1(self, value: bool): + self.__value._set_by_bit_number(value, 1, 3) + + @cvt_rb2.setter + def cvt_rb2(self, value: bool): + self.__value._set_by_bit_number(value, 1, 4) + + @ovt.setter + def ovt(self, value: bool): + self.__value._set_by_bit_number(value, 1, 5) + + def set_all(self): + self.cta = True + self.dmt = True + self.cvt = True + self.cvt_rb1 = True + self.cvt_rb2 = True + self.ovt = True + + def clear_all(self): + self.cta = False + self.dmt = False + self.cvt = False + self.cvt_rb1 = False + self.cvt_rb2 = False + self.ovt = False + + +class ColorimetryModes: + """ + Class `ColorimetryModes` describes available supported colorimetry. + - RGB `rgb` (enable/disable, set bcp). + - YCbCr 444 `ycbcr444` (enable/disable, set bcp). + - YCbCr 422 `ycbcr422` (enable/disable, set bcp). + - YCbCr Simple 422 `ycbcr_simple422` (enable/disable, set bcp). + - YCbCr 420 `ycbcr420` (enable/disable, set bcp). + """ + + __COLORIMETRY_VALUES = {8: 1, 10: 3, 12: 7, 16: 15} + __COLORIMETRY_VALUES_MAP = {0: 0, 1: 8, 3: 10, 7: 12, 15: 16} + __AVAILABLE_BPC_VALUES = [0, 8, 10, 12, 16] + __STR_AVAILABLE_BPC_VALUES = [f" {_str}" for _str in __AVAILABLE_BPC_VALUES] + + def __init__(self, value: Param): + self.__value = value + + @property + def rgb(self) -> int: + """ + Set and get RGB bpc value. + + Returns: + object of int type + """ + return self.__COLORIMETRY_VALUES_MAP.get((self.__value.default_value >> 12) & 0xF) + + @property + def ycbcr444(self) -> int: + """ + Set and get YCbCr 444 bpc value. + + Returns: + object of int type + """ + return self.__COLORIMETRY_VALUES_MAP.get((self.__value.default_value >> 16) & 0xF) + + @property + def ycbcr422(self) -> int: + """ + Set and get YCbCr 422 bpc value. + + Returns: + object of int type + """ + return self.__COLORIMETRY_VALUES_MAP.get((self.__value.default_value >> 20) & 0xF) + + @property + def ycbcr_simple422(self) -> int: + """ + Set and get YCbCr Simple 422 bpc value. + + Returns: + object of bool type + """ + return self.__COLORIMETRY_VALUES_MAP.get((self.__value.default_value >> 24) & 0xF) + + @property + def ycbcr420(self) -> int: + """ + Set and get YCbCr 420 bpc value. + + Returns: + object of bool type + """ + return self.__COLORIMETRY_VALUES_MAP.get((self.__value.default_value >> 28) & 0xF) + + @rgb.setter + def rgb(self, value: int): + if value not in self.__AVAILABLE_BPC_VALUES: + raise ValueError(f'Incorrect input bpc value {value}. ' + f'Value must be from list:{self.__STR_AVAILABLE_BPC_VALUES}') + + new_value = self.__value.default_value + new_value &= ~(0xF << 12) + if value == 0: + self.__value.default_value = new_value + else: + new_value |= (self.__COLORIMETRY_VALUES.get(value) << 12) + self.__value.default_value = new_value + + @ycbcr444.setter + def ycbcr444(self, value: int): + if value not in self.__AVAILABLE_BPC_VALUES: + raise ValueError(f'Incorrect input bpc value {value}. ' + f'Value must be from list:{self.__STR_AVAILABLE_BPC_VALUES}') + + new_value = self.__value.default_value + new_value &= ~(0xF << 16) + if value == 0: + self.__value.default_value = new_value + else: + new_value |= (self.__COLORIMETRY_VALUES.get(value) << 16) + self.__value.default_value = new_value + + @ycbcr422.setter + def ycbcr422(self, value: int): + if value not in self.__AVAILABLE_BPC_VALUES: + raise ValueError(f'Incorrect input bpc value {value}. ' + f'Value must be from list:{self.__STR_AVAILABLE_BPC_VALUES}') + + new_value = self.__value.default_value + new_value &= ~(0xF << 20) + if value == 0: + self.__value.default_value = new_value + else: + new_value |= (self.__COLORIMETRY_VALUES.get(value) << 20) + self.__value.default_value = new_value + + @ycbcr_simple422.setter + def ycbcr_simple422(self, value: int): + if value not in self.__AVAILABLE_BPC_VALUES: + raise ValueError(f'Incorrect input bpc value {value}. ' + f'Value must be from list:{self.__STR_AVAILABLE_BPC_VALUES}') + + new_value = self.__value.default_value + new_value &= ~(0xF << 24) + if value == 0: + self.__value.default_value = new_value + else: + new_value |= (self.__COLORIMETRY_VALUES.get(value) << 24) + self.__value.default_value = new_value + + @ycbcr420.setter + def ycbcr420(self, value: int): + if value not in self.__AVAILABLE_BPC_VALUES: + raise ValueError(f'Incorrect input bpc value {value}. ' + f'Value must be from list:{self.__STR_AVAILABLE_BPC_VALUES}') + + new_value = self.__value.default_value + new_value &= ~(0xF << 28) + if value == 0: + self.__value.default_value = new_value + else: + new_value |= (self.__COLORIMETRY_VALUES.get(value) << 28) + self.__value.default_value = new_value + + def set_all(self, bpc: int): + if bpc not in self.__AVAILABLE_BPC_VALUES: + raise ValueError(f'Incorrect input bpc value {bpc}. ' + f'Value must be from list:{self.__STR_AVAILABLE_BPC_VALUES}') + self.rgb = bpc + self.ycbcr444 = bpc + self.ycbcr422 = bpc + self.ycbcr_simple422 = bpc + self.ycbcr420 = bpc + + def clear_all(self): + self.rgb = 0 + self.ycbcr444 = 0 + self.ycbcr422 = 0 + self.ycbcr_simple422 = 0 + self.ycbcr420 = 0 + + +class VideoModeInfo: + + def __init__(self, json_obj): + self.__value = Param(json_obj) + self.__standard = VideoModeStandard(self.__value) + self.__colorimetry = ColorimetryModes(self.__value) + + @property + def standard(self) -> VideoModeStandard: + return self.__standard + + @property + def colorimetry(self) -> ColorimetryModes: + return self.__colorimetry diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/dp_2_1_dsc_video_modes.py b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_2_1_dsc_video_modes.py new file mode 100644 index 0000000..85c4452 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_2_1_dsc_video_modes.py @@ -0,0 +1,202 @@ +from UniTAP.dev.modules.dut_tests.dut_default_params.dp_2_1_common_video_modes import VideoModeInfo + + +class Dp21AvailableDscVideoModes: + """ + Class `Dp21AvailableDscVideoModes` allows working with video modes. + - Set and get... + """ + def __init__(self, json_obj): + self.__is_config_changed = False + + self.__1920x1080_30hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_1920_1080_30"]) + self.__1920x1080_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_1920_1080_60"]) + self.__1920x1080_120hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_1920_1080_120"]) + self.__1920x1080_144hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_1920_1080_144"]) + self.__1920x1080_240hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_1920_1080_240"]) + self.__3840x2160_30hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_3840_2160_30"]) + self.__3840x2160_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_3840_2160_60"]) + self.__3840x2160_120hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_3840_2160_120"]) + self.__3840x2160_144hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_3840_2160_144"]) + self.__3840x2160_240hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_3840_2160_240"]) + self.__5120x2160_30hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_5120_2160_30"]) + self.__5120x2160_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_5120_2160_60"]) + self.__5120x2160_120hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_5120_2160_120"]) + self.__5120x2160_144hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_5120_2160_144"]) + self.__5120x2160_240hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_5120_2160_240"]) + self.__7680x4320_30hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_7680_4320_30"]) + self.__7680x4320_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_7680_4320_60"]) + self.__7680x4320_120hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_7680_4320_120"]) + self.__10240x4320_30hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_10240_4320_30"]) + self.__10240x4320_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_DSC_VMT_10240_4320_60"]) + + def __getattribute__(self, name): + attr = super().__getattribute__(name) + if "vm_" in name or "_all" in name: + self.__is_config_changed = True + return attr + + @property + def vm_1920x1080_30hz(self) -> VideoModeInfo: + return self.__1920x1080_30hz + + @property + def vm_1920x1080_60hz(self) -> VideoModeInfo: + return self.__1920x1080_60hz + + @property + def vm_1920x1080_120hz(self) -> VideoModeInfo: + return self.__1920x1080_120hz + + @property + def vm_1920x1080_144hz(self) -> VideoModeInfo: + return self.__1920x1080_144hz + + @property + def vm_1920x1080_240hz(self) -> VideoModeInfo: + return self.__1920x1080_240hz + + @property + def vm_3840x2160_30hz(self) -> VideoModeInfo: + return self.__3840x2160_30hz + + @property + def vm_3840x2160_60hz(self) -> VideoModeInfo: + return self.__3840x2160_60hz + + @property + def vm_3840x2160_120hz(self) -> VideoModeInfo: + return self.__3840x2160_120hz + + @property + def vm_3840x2160_144hz(self) -> VideoModeInfo: + return self.__3840x2160_144hz + + @property + def vm_3840x2160_240hz(self) -> VideoModeInfo: + return self.__3840x2160_240hz + + @property + def vm_5120x2160_30hz(self) -> VideoModeInfo: + return self.__5120x2160_30hz + + @property + def vm_5120x2160_60hz(self) -> VideoModeInfo: + return self.__5120x2160_60hz + + @property + def vm_5120x2160_120hz(self) -> VideoModeInfo: + return self.__5120x2160_120hz + + @property + def vm_5120x2160_144hz(self) -> VideoModeInfo: + return self.__5120x2160_144hz + + @property + def vm_5120x2160_240hz(self) -> VideoModeInfo: + return self.__5120x2160_240hz + + @property + def vm_7680x4320_30hz(self) -> VideoModeInfo: + return self.__7680x4320_30hz + + @property + def vm_7680x4320_60hz(self) -> VideoModeInfo: + return self.__7680x4320_60hz + + @property + def vm_7680x4320_120hz(self) -> VideoModeInfo: + return self.__7680x4320_120hz + + @property + def vm_10240x4320_30hz(self) -> VideoModeInfo: + return self.__10240x4320_30hz + + @property + def vm_10240x4320_60hz(self) -> VideoModeInfo: + return self.__10240x4320_60hz + + def select_all_standards(self): + var_list = self.__dict__ + for item in var_list: + if isinstance(self.__getattribute__(item), VideoModeInfo): + self.__getattribute__(item).standard.set_all() + + def select_all_cta_standard(self): + var_list = self.__dict__ + for item in var_list: + if isinstance(self.__getattribute__(item), VideoModeInfo): + self.__getattribute__(item).standard.cta = True + + def select_all_dmt_standard(self): + var_list = self.__dict__ + for item in var_list: + if isinstance(self.__getattribute__(item), VideoModeInfo): + self.__getattribute__(item).standard.dmt = True + + def select_all_cvt_standard(self): + var_list = self.__dict__ + for item in var_list: + if isinstance(self.__getattribute__(item), VideoModeInfo): + self.__getattribute__(item).standard.cvt = True + + def select_all_cvt_rb1_standard(self): + var_list = self.__dict__ + for item in var_list: + if isinstance(self.__getattribute__(item), VideoModeInfo): + self.__getattribute__(item).standard.cvt_rb1 = True + + def select_all_cvt_rb2_standard(self): + var_list = self.__dict__ + for item in var_list: + if isinstance(self.__getattribute__(item), VideoModeInfo): + self.__getattribute__(item).standard.cvt_rb2 = True + + def select_all_ovt_standard(self): + var_list = self.__dict__ + for item in var_list: + if isinstance(self.__getattribute__(item), VideoModeInfo): + self.__getattribute__(item).standard.ovt = True + + def select_all_colorimetries(self, bpc: int = 8): + var_list = self.__dict__ + for item in var_list: + if isinstance(self.__getattribute__(item), VideoModeInfo): + self.__getattribute__(item).colorimetry.set_all(bpc) + + def select_all_rgb_colorimetry(self, bpc: int = 8): + var_list = self.__dict__ + for item in var_list: + if isinstance(self.__getattribute__(item), VideoModeInfo): + self.__getattribute__(item).colorimetry.rgb = bpc + + def select_all_ycbcr444_colorimetry(self, bpc: int = 8): + var_list = self.__dict__ + for item in var_list: + if isinstance(self.__getattribute__(item), VideoModeInfo): + self.__getattribute__(item).colorimetry.ycbcr444 = bpc + + def select_all_ycbcr422_colorimetry(self, bpc: int = 8): + var_list = self.__dict__ + for item in var_list: + if isinstance(self.__getattribute__(item), VideoModeInfo): + self.__getattribute__(item).colorimetry.ycbcr422 = bpc + + def select_all_ycbcr_simple422_colorimetry(self, bpc: int = 8): + var_list = self.__dict__ + for item in var_list: + if isinstance(self.__getattribute__(item), VideoModeInfo): + self.__getattribute__(item).colorimetry.ycbcr_simple422 = bpc + + def select_all_ycbcr420_colorimetry(self, bpc: int = 8): + var_list = self.__dict__ + for item in var_list: + if isinstance(self.__getattribute__(item), VideoModeInfo): + self.__getattribute__(item).colorimetry.ycbcr420 = bpc + + def clear_all(self): + var_list = self.__dict__ + for item in var_list: + if isinstance(self.__getattribute__(item), VideoModeInfo): + self.__getattribute__(item).colorimetry.clear_all() + self.__getattribute__(item).standard.clear_all() diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/dp_2_1_source_general_tab.py b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_2_1_source_general_tab.py new file mode 100644 index 0000000..9ef398f --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_2_1_source_general_tab.py @@ -0,0 +1,758 @@ +from UniTAP.dev.modules.dut_tests.dut_default_params.dp_1_4_source_general_tab import DutCapsFlags,\ + update_default_value, Param, IntEnum, TestAutomationFlags + + +class MaxLinkBwPolicy(IntEnum): + """ + Describes available values for maximum link bandwidth policy. + """ + Link1LRBR = 262 + Link1LHBR = 266 + Link2LRBR = 518 + Link2LHBR = 522 + + +class TestAutomationFlagsDP21(TestAutomationFlags): + """ + Class `TestAutomationFlagsDP21` allows configuring test automation flags: + - Set and get video operator input flag `video_operator_input`. + - Set and get DSC VIC flag `dsc_vis_val`. + + Also has all the `TestAutomationFlags` functionality. + """ + + @property + def video_operator_input(self) -> bool: + """ + Set and get video operator input flag. + If flag == true -> If Source DUT does not generate the requested format then GUI request will be made to + operator to generate the requested format from the EDID (request and waits for test operator input). + If flag == false -> If Source DUT does not generate the requested Video Format, then no GUI request will be + made, and test will fail if parameters does not match (request and waits for test operator input). + + Usually use for manual testing. + + Returns: + object of bool type + """ + return self._param._get_by_bitmask(5, bool) + + @video_operator_input.setter + def video_operator_input(self, video_operator_input: bool): + self._param._set_by_bitmask(int(video_operator_input), 5) + + @property + def dsc_vis_val(self) -> bool: + """ + Set and get DSC visual validation flag. + If flag == true -> will check timing, color depth, color space and video CRC. + If flag == false -> will check timing, color depth, color space without checking video CRC. + + Usually use for manual checking. + + Returns: + object of bool type + """ + return self._param._get_by_bitmask(6, bool) + + @dsc_vis_val.setter + def dsc_vis_val(self, dsc_vis_val: bool): + self._param._set_by_bitmask(int(dsc_vis_val), 6) + + +class LinkRateDp21(Param): + """ + Class `LinkRateDp21` describes support DP 2.1 rates. Allows getting and setting values. + - Support 10 Gbps `support_10Gbps`. + - Support 13.5 Gbps `support_13_5Gbps`. + - Support 20 Gbps `support_20Gbps`. + """ + def __init__(self, json_obj): + super().__init__(json_obj) + + @property + def support_10Gbps(self) -> bool: + """ + Set and get 10 Gbps flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(0, bool) + + + @support_10Gbps.setter + def support_10Gbps(self, support_10Gbps: bool): + self._set_by_bitmask(support_10Gbps, 0) + + @property + def support_20Gbps(self) -> bool: + """ + Set and get 20 Gbps flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(1, bool) + + @support_20Gbps.setter + def support_20Gbps(self, support_20Gbps: bool): + self._set_by_bitmask(support_20Gbps, 1) + + @property + def support_13_5Gbps(self) -> bool: + """ + Set and get 13.5 Gbps flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(2, bool) + + @support_13_5Gbps.setter + def support_13_5Gbps(self, support_13_5Gbps: bool): + self._set_by_bitmask(support_13_5Gbps, 2) + + +class DutCapsDp21Flags(Param): + """ + Class `DutCapsDp21Flags` inherited of class`DutCapsFlags` which defines the DUT capabilities as flags and allows + setting: + - Maximum link bandwidth policy supported flag `max_link_bandwidth_policy_supported`. + + Also has all the `DutCapsFlags` functionality. + """ + def __init__(self, json_obj): + super().__init__(json_obj) + + @property + def voltage_swing_supported(self) -> bool: + """ + Set and get Voltage swing level flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(0, bool) + + @voltage_swing_supported.setter + def voltage_swing_supported(self, voltage_swing_supported: bool): + self._set_by_bitmask(voltage_swing_supported, 0) + + @property + def pre_emphasis_supported(self) -> bool: + """ + Set and get Pre-emphasis level flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(1, bool) + + @pre_emphasis_supported.setter + def pre_emphasis_supported(self, pre_emphasis_supported: bool): + self._set_by_bitmask(pre_emphasis_supported, 1) + + @property + def fixed_timing_dut_supported(self) -> bool: + """ + Set and get Fixed timing DUT flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(2, bool) + + @fixed_timing_dut_supported.setter + def fixed_timing_dut_supported(self, fixed_timing_dut_supported: bool): + self._set_by_bitmask(fixed_timing_dut_supported, 2) + + @property + def spread_spectrum_supported(self) -> bool: + """ + Set and get Spread Spectrum flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(3, bool) + + @spread_spectrum_supported.setter + def spread_spectrum_supported(self, spread_spectrum_supported: bool): + self._set_by_bitmask(spread_spectrum_supported, 3) + + @property + def change_vf_without_lt_supported(self) -> bool: + """ + Set and get Video format change without LT flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(4, bool) + + @change_vf_without_lt_supported.setter + def change_vf_without_lt_supported(self, change_vf_without_lt_supported: bool): + self._set_by_bitmask(change_vf_without_lt_supported, 4) + + @property + def e_ddc_protocol_supported(self) -> bool: + """ + Set and get E-DDC protocol flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(5, bool) + + @e_ddc_protocol_supported.setter + def e_ddc_protocol_supported(self, e_ddc_protocol_supported: bool): + self._set_by_bitmask(e_ddc_protocol_supported, 5) + + @property + def audio_transmission_supported(self) -> bool: + """ + Set and get Audio Info Frame for 2 channel audio transmission flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(6, bool) + + @audio_transmission_supported.setter + def audio_transmission_supported(self, audio_transmission_supported: bool): + self._set_by_bitmask(audio_transmission_supported, 6) + + @property + def dut_is_type_c_device(self) -> bool: + """ + Set and get Define that DUT is Type-C device flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(7, bool) + + @dut_is_type_c_device.setter + def dut_is_type_c_device(self, dut_is_type_c_device: bool): + self._set_by_bitmask(dut_is_type_c_device, 7) + + @property + def fec_supported(self) -> bool: + """ + Set and get FEC flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(8, bool) + + @fec_supported.setter + def fec_supported(self, fec_supported: bool): + self._set_by_bitmask(fec_supported, 8) + + @property + def fec_disable_sequence_supported(self) -> bool: + """ + Set and get FEC disable sequence flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(9, bool) + + @fec_disable_sequence_supported.setter + def fec_disable_sequence_supported(self, fec_disable_sequence_supported: bool): + self._set_by_bitmask(fec_disable_sequence_supported, 9) + + @property + def audio_without_video_supported(self) -> bool: + """ + Set and get Audio without Video flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(10, bool) + + @audio_without_video_supported.setter + def audio_without_video_supported(self, audio_without_video_supported: bool): + self._set_by_bitmask(audio_without_video_supported, 10) + + @property + def dsc_supported(self) -> bool: + """ + Set and get DSC flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(11, bool) + + @dsc_supported.setter + def dsc_supported(self, dsc_supported: bool): + self._set_by_bitmask(dsc_supported, 11) + + @property + def dsc_block_prediction_supported(self) -> bool: + """ + Set and get DSC block prediction flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(12, bool) + + @dsc_block_prediction_supported.setter + def dsc_block_prediction_supported(self, dsc_block_prediction_supported: bool): + self._set_by_bitmask(dsc_block_prediction_supported, 12) + + @property + def max_link_bandwidth_policy_supported(self): + """ + Set and get Maximum link bandwidth policy flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(13, bool) + + @max_link_bandwidth_policy_supported.setter + def max_link_bandwidth_policy_supported(self, max_link_bandwidth_policy_supported: bool): + self._set_by_bitmask(max_link_bandwidth_policy_supported, 13) + + @property + def use_3tap_conversion(self): + """ + Set and get 3TAP conversion flag using. + + Returns: + object of bool type + """ + return self._get_by_bitmask(14, bool) + + @use_3tap_conversion.setter + def use_3tap_conversion(self, use_3tap_filter: bool): + self._set_by_bitmask(use_3tap_filter, 14) + + @property + def usb4_tunnel_presented(self): + """ + Set and get USB4 tunnel presented flag. + + Returns: + object of bool type + """ + return self._get_by_bitmask(15, bool) + + @usb4_tunnel_presented.setter + def usb4_tunnel_presented(self, usb4_tunnel_presented: bool): + self._set_by_bitmask(usb4_tunnel_presented, 15) + + @property + def native_display_id_read(self) -> bool: + """ + Set and get DUT supports native Display ID read flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(16, bool) + + @native_display_id_read.setter + def native_display_id_read(self, native_display_id_read: bool): + self._set_by_bitmask(native_display_id_read, 16) + + @property + def display_id_vii_supported(self) -> bool: + """ + Set and get DisplayID Type VII Detailed Timing Descriptor flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(17, bool) + + @display_id_vii_supported.setter + def display_id_vii_supported(self, display_id_vii_supported: bool): + self._set_by_bitmask(display_id_vii_supported, 17) + + @property + def display_id_viii_supported(self) -> bool: + """ + Set and get DisplayID Type VIII Detailed Timing Descriptor flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(18, bool) + + @display_id_viii_supported.setter + def display_id_viii_supported(self, display_id_viii_supported: bool): + self._set_by_bitmask(display_id_viii_supported, 18) + + + @property + def display_id_ix_supported(self) -> bool: + """ + Set and get DisplayID Type IX Detailed Timing Descriptor flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(19, bool) + + @display_id_ix_supported.setter + def display_id_ix_supported(self, display_id_ix_supported: bool): + self._set_by_bitmask(display_id_ix_supported, 19) + + @property + def display_id_x_supported(self) -> bool: + """ + Set and get DisplayID Type X Detailed Timing Descriptor flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(20, bool) + + @display_id_x_supported.setter + def display_id_x_supported(self, display_id_x_supported: bool): + self._set_by_bitmask(display_id_x_supported, 20) + + @property + def display_id_tiled_display_topology(self) -> bool: + """ + Set and get 2x1 tiled display and DisplayID Tiled Display Topology data block flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(21, bool) + + @display_id_tiled_display_topology.setter + def display_id_tiled_display_topology(self, display_id_tiled_display_topology: bool): + self._set_by_bitmask(display_id_tiled_display_topology, 21) + + @property + def display_id_tiled_stereo_display(self) -> bool: + """ + Set and get Field sequential stereo and DisplayID Tiled Stereo Display Interface data block flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(22, bool) + + @display_id_tiled_stereo_display.setter + def display_id_tiled_stereo_display(self, display_id_tiled_stereo_display: bool): + self._set_by_bitmask(display_id_tiled_stereo_display, 22) + + @property + def stacked_frame_stereo_supported(self) -> bool: + """ + Set and get Stacked frame stereo and DisplayID Tiled Stereo Display Interface data flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(23, bool) + + @stacked_frame_stereo_supported.setter + def stacked_frame_stereo_supported(self, stacked_frame_stereo_supported: bool): + self._set_by_bitmask(stacked_frame_stereo_supported, 23) + + @property + def dynamic_refresh_rate_support(self) -> bool: + """ + Set and get Dynamic Refresh Rate with VBlank stretch with MSA_TIMING_PAR_IGNORED flag supported. + + Returns: + object of bool type + """ + return self._get_by_bitmask(24, bool) + + @dynamic_refresh_rate_support.setter + def dynamic_refresh_rate_support(self, dynamic_refresh_rate_support: bool): + self._set_by_bitmask(dynamic_refresh_rate_support, 24) + + +class DutCapsDp21: + """ + Class `DutCapsDp21` defines the DUT capabilities and allows setting: + - Defines the maximum number of lanes supported by the DUT `max_lanes`. + - Maximum link rate supported by the DUT `max_link_rate`. + - Dut capabilities flags `dut_caps_flags` type `DutCapsFlags`. + """ + def __init__(self, json_obj): + self.__max_lanes = Param(json_obj["TSI_DP20_SRCCTS_MAX_LANES"]) + self.__max_link_rate = Param(json_obj["TSI_DP20_SRCCTS_MAX_LINK_RATE"]) + self.__dut_caps_flags = DutCapsDp21Flags(json_obj["TSI_DP20_SRCCTS_DUT_CAPS"]) + + @property + def max_lanes(self) -> int: + """ + Set and get number of maximum lanes. + + Returns: + object of int type + """ + return self.__max_lanes.default_value + + @max_lanes.setter + def max_lanes(self, max_lanes: int): + if max_lanes not in [1, 2, 4]: + raise ValueError(f"Max lane count must be from list: 1, 2, 4. Current value: {max_lanes}") + self.__max_lanes.default_value = max_lanes + + @property + def max_link_rate(self): + """ + Set and get number of maximum link rate. + + Returns: + object of int type + """ + return self.__max_link_rate + + @max_link_rate.setter + def max_link_rate(self, max_link_rate: float): + if max_link_rate not in [1.62, 2.7, 5.4, 8.1]: + raise ValueError(f"Max link rate must be from list: 1.62, 2.7, 5.4, 8.1. Current value: {max_link_rate}") + self.__max_link_rate.default_value = round(max_link_rate / 0.27) + + @property + def dut_caps_flags(self) -> DutCapsDp21Flags: + """ + Set and get DUT capabilities flags. + + Returns: + object of `DutCapsFlags` type + """ + return self.__dut_caps_flags + + @dut_caps_flags.setter + def dut_caps_flags(self, dut_caps_flags: DutCapsDp21Flags): + self.__dut_caps_flags = dut_caps_flags + + +class DebugOptions: + + def __init__(self, json_obj): + self.__param = Param(json_obj["TSI_DP20_SRCCTS_DUT_DEBUG_CONF"]) + + @property + def continue_on_fail(self) -> bool: + """ + Set and get continue on fail flag. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(0, bool) + + @continue_on_fail.setter + def continue_on_fail(self, continue_on_fail: bool): + self.__param.default_value = update_default_value(value=continue_on_fail, + default_value=self.__param.default_value, + mask=self.__param.bit_field_list[0].mask) + + @property + def force_visual_check(self) -> bool: + """ + Set and get Force manual visual check flag. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(1, bool) + + @force_visual_check.setter + def force_visual_check(self, force_visual_check: bool): + self.__param.default_value = update_default_value(value=force_visual_check, + default_value=self.__param.default_value, + mask=self.__param.bit_field_list[1].mask) + + +class GeneralSourceDUTDp21SettingTab: + """ + Class `GeneralSourceDUTDp21SettingTab` inherited of class`GeneralSourceDUTDp14SettingTab` allows working with + parameters from General source part. + - Set and get timeout `timeout`. + - Set and get `hpd_pulse_duration`. Describes duration of long HPD pulses generated, in milliseconds. + - Set and get DUT capabilities `dut_caps` type `DutCapsDp21`. + - Set and get DUT link rates `dut_link_rates` type `LinkRateDp21`. + - Set and get LTTPR device count `lttpr_device_count`. + - Set and get maximum link bandwidth policy `max_link_bw_policy` type `MaxLinkBwPolicy`. + - Set and get DSC maximum slice number `dsc_max_slice`. + - Set and get DSC version `dsc_version`. + - Set and get debug options `debug_options` type `DebugOptions` + """ + def __init__(self, json_obj): + self.__timeout = Param(json_obj["TSI_DP20_SRCCTS_TIMEOUT"]) + self.__dut_caps = DutCapsDp21(json_obj) + self.__test_automation = TestAutomationFlagsDP21(json_obj["TSI_DP20_SRCCTS_DUT_TA"]) + self.__hpd_pulse_duration = Param(json_obj["TSI_DP20_SRCCTS_LONG_HPD_PULSE"]) + self.__dut_link_rates = LinkRateDp21(json_obj["TSI_DP20_SRCCTS_DUT_LINK_RATES"]) + self.__lttpr_device_count = Param(json_obj["TSI_DP20_SRCCTS_LTTPR_DEVICE_COUNT"]) + self.__max_link_bw_policy = Param(json_obj["TSI_DP20_SRCCTS_MAX_LINK_BW_POLICY"]) # MaxLinkBwPolicy + self.__dsc_max_slice = Param(json_obj["TSI_DP20_SRCCTS_DSC_DUT_MAX_SLICE"]) + self.__dsc_version = Param(json_obj["TSI_DP20_SRCCTS_DSC_VERSION"]) + self.__debug_options = DebugOptions(json_obj) + + @property + def timeout(self) -> int: + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout + + @property + def dut_caps(self) -> DutCapsDp21: + """ + Set and get DUT caps. + + Returns: + object DutCapsDp21 + """ + return self.__dut_caps + + @dut_caps.setter + def dut_caps(self, dut_caps: DutCapsDp21): + self.__dut_caps = dut_caps + + @property + def hpd_pulse_duration(self) -> int: + """ + Set and get HPD pulse duration. + + Returns: + object of int type + """ + return self.__hpd_pulse_duration.default_value + + @hpd_pulse_duration.setter + def hpd_pulse_duration(self, hpd_pulse_duration: int): + if not(self.__hpd_pulse_duration.min_value < hpd_pulse_duration < self.__hpd_pulse_duration.max_value): + raise ValueError(f"HPD pulse duration cannot be less than {self.__timeout.min_value} and more than " + f"{self.__hpd_pulse_duration.max_value}.") + self.__hpd_pulse_duration.default_value = hpd_pulse_duration + + @property + def test_automation(self) -> TestAutomationFlagsDP21: + """ + Set and get test automation flags. + + Returns: + object TestAutomationFlags + """ + return self.__test_automation + + @test_automation.setter + def test_automation(self, test_automation: TestAutomationFlagsDP21): + self.__test_automation = test_automation + + @property + def dut_link_rates(self) -> LinkRateDp21: + """ + Set and get DUT link rates. + + Returns: + object LinkRateDp21 + """ + return self.__dut_link_rates + + @dut_link_rates.setter + def dut_link_rates(self, dut_link_rates: LinkRateDp21): + self.__dut_link_rates = dut_link_rates + + @property + def max_link_bw_policy(self) -> int: + """ + Set and get maximum link bandwidth policy. + + Returns: + object int + """ + return self.__max_link_bw_policy.default_value + + @max_link_bw_policy.setter + def max_link_bw_policy(self, max_link_bw_policy: MaxLinkBwPolicy): + self.__max_link_bw_policy = max_link_bw_policy.value + + @property + def lttpr_device_count(self) -> int: + """ + Set and get LTTPR device count. + + Returns: + object int + """ + return self.__lttpr_device_count.default_value + + @lttpr_device_count.setter + def lttpr_device_count(self, lttpr_device_count: int): + if not(self.__lttpr_device_count.min_value < lttpr_device_count < self.__lttpr_device_count.max_value): + raise ValueError(f"LTTPR device count cannot be less than " + f"{self.__lttpr_device_count.min_value} and more than " + f"{self.__lttpr_device_count.max_value}.") + self.__lttpr_device_count.default_value = lttpr_device_count + + @property + def dsc_max_slice(self) -> int: + """ + Set and get DSC maximum slice number. + + Returns: + object of int type + """ + return self.__dsc_max_slice.default_value + + @dsc_max_slice.setter + def dsc_max_slice(self, dsc_max_slice: int): + if dsc_max_slice not in [1, 2, 4, 8, 10, 12, 16, 20, 24]: + raise ValueError(f"DSC Max slice count available values {[1, 2, 4, 8, 10, 12, 16, 20, 24]}") + self.__dsc_max_slice.default_value = dsc_max_slice + + @property + def dsc_version(self) -> list: + """ + Set and get DSC version. + Setter: .dsc_version((1, 2)) + + Returns: + object of list type + """ + return [self.__dsc_version.default_value >> 16 & 0xF, self.__dsc_version.default_value & 0xF] + + @dsc_version.setter + def dsc_version(self, dsc_version: tuple): + version = (dsc_version[0] << 16) | dsc_version[1] + if version not in [65537, 65538]: + raise ValueError("DSC Version must be 1.1 or 1.2") + self.__dsc_version.default_value = version + + @property + def debug_options(self) -> DebugOptions: + """ + Set and get debug options. + + Returns: + object of 'DebugOptions' type + """ + return self.__debug_options + + @debug_options.setter + def debug_options(self, debug_config: DebugOptions): + self.__debug_options = debug_config + diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/dp_2_1_video_modes.py b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_2_1_video_modes.py new file mode 100644 index 0000000..bbf2a5c --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_2_1_video_modes.py @@ -0,0 +1,365 @@ +from warnings import warn + +from UniTAP.dev.modules.dut_tests.dut_default_params.dp_2_1_common_video_modes import VideoModeInfo + + +class Dp21AvailableVideoModes: + """ + Class `Dp21AvailableVideoModes` allows working with video modes. + - Set and get... + """ + def __init__(self, json_obj): + self.__768x480_85hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_768_480_85"]) + self.__1024x640_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1024_640_60"]) + self.__1152x720_75hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1152_720_75"]) + self.__1280x720_24hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1280_720_24"]) + self.__1280x720_120hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1280_720_120"]) + self.__1280x768_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1280_768_60"]) + self.__1280x960_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1280_960_60"]) + self.__1440x240_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1440_240_60"]) + self.__1440x480_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1440_480_60"]) + self.__1440x900_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1440_900_60"]) + self.__1536x960_85hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1536_960_85"]) + self.__1920x1080_30hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1920_1080_30"]) + self.__1920x1080_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1920_1080_60"]) + self.__1920x1080_85hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1920_1080_85"]) + self.__1920x1080_100hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1920_1080_100"]) + self.__1920x1080_120hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1920_1080_120"]) + self.__1920x1080_144hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1920_1080_144"]) + self.__1920x1080_240hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1920_1080_240"]) + self.__1920x1440_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_1920_1440_60"]) + self.__2048x1280_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_2048_1280_60"]) + self.__2048x1536_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_2048_1536_60"]) + self.__2128x1200_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_2128_1200_60"]) + self.__2456x1536_50hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_2456_1536_50"]) + self.__2456x1536_75hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_2456_1536_75"]) + self.__2560x1080_30hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_2560_1080_30"]) + self.__2560x1080_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_2560_1080_60"]) + self.__2560x1080_120hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_2560_1080_120"]) + self.__2560x1600_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_2560_1600_60"]) + self.__2560x1920_75hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_2560_1920_75"]) + self.__2728x1536_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_2728_1536_60"]) + self.__3840x2160_30hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_3840_2160_30"]) + self.__3840x2160_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_3840_2160_60"]) + self.__3840x2160_120hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_3840_2160_120"]) + self.__3840x2160_144hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_3840_2160_144"]) + self.__3840x2160_240hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_3840_2160_240"]) + self.__3840x2400_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_3840_2400_60"]) + self.__4096x2160_30hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_4096_2160_30"]) + self.__5120x2160_30hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_5120_2160_30"]) + self.__5120x2160_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_5120_2160_60"]) + self.__5120x2160_120hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_5120_2160_120"]) + self.__5120x2160_144hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_5120_2160_144"]) + self.__5120x2160_240hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_5120_2160_240"]) + self.__5120x2880_30hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_5120_2880_30"]) + self.__5120x2880_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_5120_2880_60"]) + self.__5120x2880_120hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_5120_2880_120"]) + self.__7680x4320_24hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_7680_4320_24"]) + self.__7680x4320_30hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_7680_4320_30"]) + self.__7680x4320_50hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_7680_4320_50"]) + self.__7680x4320_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_7680_4320_60"]) + self.__7680x4320_120hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_7680_4320_120"]) + self.__10240x4320_24hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_10240_4320_24"]) + self.__10240x4320_30hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_10240_4320_30"]) + self.__10240x4320_60hz = VideoModeInfo(json_obj["TSI_DP20_SRCCTS_VMT_10240_4320_60"]) + + @property + def vm_768x480_85hz(self) -> VideoModeInfo: + return self.__768x480_85hz + + @property + def vm_1024x640_60hz(self) -> VideoModeInfo: + return self.__1024x640_60hz + + @property + def vm_1152x720_75hz(self) -> VideoModeInfo: + return self.__1152x720_75hz + + @property + def vm_1280x720_24hz(self) -> VideoModeInfo: + return self.__1280x720_24hz + + @property + def vm_1280x720_120hz(self) -> VideoModeInfo: + return self.__1280x720_120hz + + @property + def vm_1280x768_60hz(self) -> VideoModeInfo: + return self.__1280x768_60hz + + @property + def vm_1280x960_60hz(self) -> VideoModeInfo: + return self.__1280x960_60hz + + @property + def vm_1440x240_60hz(self) -> VideoModeInfo: + return self.__1440x240_60hz + + @property + def vm_1440x480_60hz(self) -> VideoModeInfo: + return self.__1440x480_60hz + + @property + def vm_1440x900_60hz(self) -> VideoModeInfo: + return self.__1440x900_60hz + + @property + def vm_1536x960_85hz(self) -> VideoModeInfo: + return self.__1536x960_85hz + + @property + def vm_1920x1080_30hz(self) -> VideoModeInfo: + return self.__1920x1080_30hz + + @property + def vm_1920x1080_60hz(self) -> VideoModeInfo: + return self.__1920x1080_60hz + + @property + def vm_1920x1080_85hz(self) -> VideoModeInfo: + return self.__1920x1080_85hz + + @property + def vm_1920x1080_100hz(self) -> VideoModeInfo: + return self.__1920x1080_100hz + + @property + def vm_1920x1080_120hz(self) -> VideoModeInfo: + return self.__1920x1080_120hz + + @property + def vm_1920x1080_144hz(self) -> VideoModeInfo: + warn("This video mode is DSC only, please configure this video mode using dsc_video_modes.", DeprecationWarning, + 2) + return self.__1920x1080_144hz + + @property + def vm_1920x1080_240hz(self) -> VideoModeInfo: + warn("This video mode is DSC only, please configure this video mode using dsc_video_modes.", DeprecationWarning, + 2) + return self.__1920x1080_240hz + + @property + def vm_1920x1440_60hz(self) -> VideoModeInfo: + return self.__1920x1440_60hz + + @property + def vm_2048x1280_60hz(self) -> VideoModeInfo: + return self.__2048x1280_60hz + + @property + def vm_2048x1536_60hz(self) -> VideoModeInfo: + return self.__2048x1536_60hz + + @property + def vm_2128x1200_60hz(self) -> VideoModeInfo: + return self.__2128x1200_60hz + + @property + def vm_2456x1536_50hz(self) -> VideoModeInfo: + return self.__2456x1536_50hz + + @property + def vm_2456x1536_75hz(self) -> VideoModeInfo: + return self.__2456x1536_75hz + + @property + def vm_2560x1080_30hz(self) -> VideoModeInfo: + return self.__2560x1080_30hz + + @property + def vm_2560x1080_60hz(self) -> VideoModeInfo: + return self.__2560x1080_60hz + + @property + def vm_2560x1080_120hz(self) -> VideoModeInfo: + return self.__2560x1080_120hz + + @property + def vm_2560x1600_60hz(self) -> VideoModeInfo: + return self.__2560x1600_60hz + + @property + def vm_2560x1920_75hz(self) -> VideoModeInfo: + return self.__2560x1920_75hz + + @property + def vm_2728x1536_60hz(self) -> VideoModeInfo: + return self.__2728x1536_60hz + + @property + def vm_3840x2160_30hz(self) -> VideoModeInfo: + warn("This video mode is DSC only, please configure this video mode using dsc_video_modes.", DeprecationWarning, + 2) + return self.__3840x2160_30hz + + @property + def vm_3840x2160_60hz(self) -> VideoModeInfo: + return self.__3840x2160_60hz + + @property + def vm_3840x2160_120hz(self) -> VideoModeInfo: + return self.__3840x2160_120hz + + @property + def vm_3840x2160_144hz(self) -> VideoModeInfo: + return self.__3840x2160_144hz + + @property + def vm_3840x2160_240hz(self) -> VideoModeInfo: + warn("This video mode is DSC only, please configure this video mode using dsc_video_modes.", DeprecationWarning, + 2) + return self.__3840x2160_240hz + + @property + def vm_3840x2400_60hz(self) -> VideoModeInfo: + return self.__3840x2400_60hz + + @property + def vm_4096x2160_30hz(self) -> VideoModeInfo: + return self.__4096x2160_30hz + + @property + def vm_5120x2160_30hz(self) -> VideoModeInfo: + return self.__5120x2160_30hz + + @property + def vm_5120x2160_60hz(self) -> VideoModeInfo: + return self.__5120x2160_60hz + + @property + def vm_5120x2160_120hz(self) -> VideoModeInfo: + warn("This video mode is DSC only, please configure this video mode using dsc_video_modes.", DeprecationWarning, + 2) + return self.__5120x2160_120hz + + @property + def vm_5120x2160_144hz(self) -> VideoModeInfo: + return self.__5120x2160_144hz + + @property + def vm_5120x2160_240hz(self) -> VideoModeInfo: + warn("This video mode is DSC only, please configure this video mode using dsc_video_modes.", DeprecationWarning, + 2) + return self.__5120x2160_240hz + + @property + def vm_5120x2880_30hz(self) -> VideoModeInfo: + return self.__5120x2880_30hz + + @property + def vm_5120x2880_60hz(self) -> VideoModeInfo: + return self.__5120x2880_60hz + + @property + def vm_5120x2880_120hz(self) -> VideoModeInfo: + return self.__5120x2880_120hz + + @property + def vm_7680x4320_24hz(self) -> VideoModeInfo: + return self.__7680x4320_24hz + + @property + def vm_7680x4320_30hz(self) -> VideoModeInfo: + return self.__7680x4320_30hz + + @property + def vm_7680x4320_50hz(self) -> VideoModeInfo: + return self.__7680x4320_50hz + + @property + def vm_7680x4320_60hz(self) -> VideoModeInfo: + return self.__7680x4320_60hz + + @property + def vm_7680x4320_120hz(self) -> VideoModeInfo: + warn("This video mode is DSC only, please configure this video mode using dsc_video_modes.", DeprecationWarning, + 2) + return self.__7680x4320_120hz + + @property + def vm_10240x4320_24hz(self) -> VideoModeInfo: + return self.__10240x4320_24hz + + @property + def vm_10240x4320_30hz(self) -> VideoModeInfo: + warn("This video mode is DSC only, please configure this video mode using dsc_video_modes.", DeprecationWarning, + 2) + return self.__10240x4320_30hz + + @property + def vm_10240x4320_60hz(self) -> VideoModeInfo: + warn("This video mode is DSC only, please configure this video mode using dsc_video_modes.", DeprecationWarning, + 2) + return self.__10240x4320_60hz + + def select_all_standards(self): + var_list = self.__dict__ + for item in var_list: + self.__getattribute__(item).standard.set_all() + + def select_all_cta_standard(self): + var_list = self.__dict__ + for item in var_list: + self.__getattribute__(item).standard.cta = True + + def select_all_dmt_standard(self): + var_list = self.__dict__ + for item in var_list: + self.__getattribute__(item).standard.dmt = True + + def select_all_cvt_standard(self): + var_list = self.__dict__ + for item in var_list: + self.__getattribute__(item).standard.cvt = True + + def select_all_cvt_rb1_standard(self): + var_list = self.__dict__ + for item in var_list: + self.__getattribute__(item).standard.cvt_rb1 = True + + def select_all_cvt_rb2_standard(self): + var_list = self.__dict__ + for item in var_list: + self.__getattribute__(item).standard.cvt_rb2 = True + + def select_all_ovt_standard(self): + var_list = self.__dict__ + for item in var_list: + self.__getattribute__(item).standard.ovt = True + + def select_all_colorimetries(self, bpc: int = 8): + var_list = self.__dict__ + for item in var_list: + self.__getattribute__(item).colorimetry.set_all(bpc) + + def select_all_rgb_colorimetry(self, bpc: int = 8): + var_list = self.__dict__ + for item in var_list: + self.__getattribute__(item).colorimetry.rgb = bpc + + def select_all_ycbcr444_colorimetry(self, bpc: int = 8): + var_list = self.__dict__ + for item in var_list: + self.__getattribute__(item).colorimetry.ycbcr444 = bpc + + def select_all_ycbcr422_colorimetry(self, bpc: int = 8): + var_list = self.__dict__ + for item in var_list: + self.__getattribute__(item).colorimetry.ycbcr422 = bpc + + def select_all_ycbcr_simple422_colorimetry(self, bpc: int = 8): + var_list = self.__dict__ + for item in var_list: + self.__getattribute__(item).colorimetry.ycbcr_simple422 = bpc + + def select_all_ycbcr420_colorimetry(self, bpc: int = 8): + var_list = self.__dict__ + for item in var_list: + self.__getattribute__(item).colorimetry.ycbcr420 = bpc + + def clear_all(self): + var_list = self.__dict__ + for item in var_list: + self.__getattribute__(item).colorimetry.clear_all() + self.__getattribute__(item).standard.clear_all() diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/dp_electrical_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_electrical_tests.py new file mode 100644 index 0000000..6212f3a --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_electrical_tests.py @@ -0,0 +1,418 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param +from UniTAP.dev.modules.dut_tests.test_utils import update_default_value + + +class DpDutTaCaps(Param): + """ + Class `DpDutTaCaps` describes DUT Test automation capabilities flags and allows settings values. + - DUT is capable for test link training `dut_capable_link_training`. + - DUT is capable for test video pattern `dut_capable_video_pattern` + - DUT is capable for test EDID read `dut_capable_edid_read`. + """ + def __init__(self, json_obj): + super().__init__(json_obj) + + @property + def dut_capable_link_training(self) -> bool: + """ + Set and get DUT is capable for test link training flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(0, bool) + + @dut_capable_link_training.setter + def dut_capable_link_training(self, dut_capable_link_training: bool): + self._set_by_bitmask(dut_capable_link_training, 0) + + @property + def dut_capable_video_pattern(self) -> bool: + """ + Set and get DUT is capable for test video pattern flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(1, bool) + + @dut_capable_video_pattern.setter + def dut_capable_video_pattern(self, dut_capable_video_pattern: bool): + self._set_by_bitmask(dut_capable_video_pattern, 1) + + @property + def dut_capable_edid_read(self) -> bool: + """ + Set and get DUT is capable for test EDID read flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(2, bool) + + @dut_capable_edid_read.setter + def dut_capable_edid_read(self, dut_capable_edid_read: bool): + self._set_by_bitmask(dut_capable_edid_read, 2) + + +class DpElectricalTestParam: + """ + Class `DpElectricalTestParam` describes parameters for DP electrical tests. + - Test timeout, in milliseconds `timeout`. + - Main link low voltage limit, mV `links_low_voltage`. + - Main link high voltage limit, mV `links_high_voltage`. + - HPD line logical zero low voltage level limit, mV `hpd_zero_low_voltage`. + - HPD line logical zero high voltage level limit, mV `hpd_zero_high_voltage`. + - HPD line logical one low voltage level limit, mV `hpd_one_low_voltage`. + - HPD line logical one high voltage level limit, mV `hpd_one_high_voltage`. + - AUX + line idle low voltage level limit, mV `aux_positive_idle_low_voltage`. + - AUX + line idle high voltage level limit, mV `aux_positive_idle_high_voltage`. + - AUX - line idle low voltage level limit, mV `aux_negative_idle_low_voltage`. + - AUX - line idle high voltage level limit, mV `aux_negative_idle_high_voltage`. + - AUX + line signal trigger level, mV `aux_positive_trig_voltage`. + - AUX - line signal trigger level, mV `aux_negative_trig_voltage`. + - AUX signal capture timeout, milliseconds `aux_signal_capture_timeout`. + - AUX signal capture attempts, times `aux_signal_capture_count`. + - Maximum lanes count supported by DUT `dut_max_lanes`. + - Maximum data rate supported by DUT in 0.27Gbps `dut_max_link_rate`. + - DUT Test automation capabilities flags `dut_ta_caps` type `DpDutTaCaps`. + """ + def __init__(self, json_obj): + self.__timeout = Param(json_obj["TSI_DP_RX_TEST_TIMEOUT"]) + self.__links_low_voltage = Param(json_obj["TSI_DP_RX_LINKS_LOW_VOLTAGE"]) + self.__links_high_voltage = Param(json_obj["TSI_DP_RX_LINKS_HI_VOLTAGE"]) + self.__hpd_zero_low_voltage = Param(json_obj["TSI_DP_RX_HPD_ZERO_LOW_VOLTAGE"]) + self.__hpd_zero_high_voltage = Param(json_obj["TSI_DP_RX_HPD_ZERO_HI_VOLTAGE"]) + self.__hpd_one_low_voltage = Param(json_obj["TSI_DP_RX_HPD_ONE_LOW_VOLTAGE"]) + self.__hpd_one_high_voltage = Param(json_obj["TSI_DP_RX_HPD_ONE_HI_VOLTAGE"]) + self.__aux_positive_idle_low_voltage = Param(json_obj["TSI_DP_RX_AUX_P_IDLE_LOW_VOLTAGE"]) + self.__aux_positive_idle_high_voltage = Param(json_obj["TSI_DP_RX_AUX_P_IDLE_HI_VOLTAGE"]) + self.__aux_negative_idle_low_voltage = Param(json_obj["TSI_DP_RX_AUX_N_IDLE_LOW_VOLTAGE"]) + self.__aux_negative_idle_high_voltage = Param(json_obj["TSI_DP_RX_AUX_N_IDLE_HI_VOLTAGE"]) + self.__aux_positive_trig_voltage = Param(json_obj["TSI_DP_RX_AUX_P_TRIG_VOLTAGE"]) + self.__aux_negative_trig_voltage = Param(json_obj["TSI_DP_RX_AUX_N_TRIG_VOLTAGE"]) + self.__aux_signal_capture_timeout = Param(json_obj["TSI_DP_RX_AUX_SIGNAL_CAPT_TIMEOUT"]) + self.__aux_signal_capture_count = Param(json_obj["TSI_DP_RX_AUX_SIGNAL_CAPT_TRIES"]) + self.__dut_max_lanes = Param(json_obj["TSI_DP_RX_MAX_DUT_MAX_LANES"]) + self.__dut_max_link_rate = Param(json_obj["TSI_DP_RX_MAX_DUT_LANE_RATE"]) + self.__unused_dut_caps_flag = Param(json_obj["TSI_DP_RX_DUT_CAPS"]) + self.__dut_ta_caps = DpDutTaCaps(json_obj["TSI_DP_RX_DUT_TA_CAPS"]) + + @property + def timeout(self) -> int: + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout + + @property + def links_low_voltage(self) -> int: + """ + Set and get Main link low voltage limit. + + Returns: + object of int type + """ + return self.__links_low_voltage.default_value + + @links_low_voltage.setter + def links_low_voltage(self, links_low_voltage: int): + if not(self.__links_low_voltage.min_value < links_low_voltage < self.__links_low_voltage.max_value): + raise ValueError(f"Links low voltage cannot be less than {self.__links_low_voltage.min_value} and more than" + f" {self.__links_low_voltage.max_value}.") + self.__links_low_voltage.default_value = links_low_voltage + + @property + def links_high_voltage(self) -> int: + """ + Set and get Main link high voltage limit. + + Returns: + object of int type + """ + return self.__links_high_voltage.default_value + + @links_high_voltage.setter + def links_high_voltage(self, links_high_voltage: int): + if not(self.__links_high_voltage.min_value < links_high_voltage < self.__links_high_voltage.max_value): + raise ValueError(f"Links high voltage cannot be less than {self.__links_high_voltage.min_value} " + f"and more than {self.__links_high_voltage.max_value}.") + self.__links_high_voltage.default_value = links_high_voltage + + @property + def hpd_zero_low_voltage(self) -> int: + """ + Set and get HPD line logical zero low voltage level limit. + + Returns: + object of int type + """ + return self.__hpd_zero_low_voltage.default_value + + @hpd_zero_low_voltage.setter + def hpd_zero_low_voltage(self, hpd_zero_low_voltage: int): + if not(self.__hpd_zero_low_voltage.min_value < hpd_zero_low_voltage < self.__hpd_zero_low_voltage.max_value): + raise ValueError(f"HDP zero low voltage cannot be less than {self.__hpd_zero_low_voltage.min_value} " + f"and more than {self.__hpd_zero_low_voltage.max_value}.") + self.__hpd_zero_low_voltage.default_value = hpd_zero_low_voltage + + @property + def hpd_zero_high_voltage(self) -> int: + """ + Set and get HPD line logical zero high voltage level limit. + + Returns: + object of int type + """ + return self.__hpd_zero_high_voltage.default_value + + @hpd_zero_high_voltage.setter + def hpd_zero_high_voltage(self, hpd_zero_high_voltage: int): + if not(self.__hpd_zero_high_voltage.min_value < hpd_zero_high_voltage < self.__hpd_zero_high_voltage.max_value): + raise ValueError(f"HDP zero high voltage cannot be less than {self.__hpd_zero_high_voltage.min_value} " + f"and more than {self.__hpd_zero_high_voltage.max_value}.") + self.__hpd_zero_high_voltage.default_value = hpd_zero_high_voltage + + @property + def hpd_one_low_voltage(self) -> int: + """ + Set and get HPD line logical one low voltage level limit. + + Returns: + object of int type + """ + return self.__hpd_one_low_voltage.default_value + + @hpd_one_low_voltage.setter + def hpd_one_low_voltage(self, hpd_one_low_voltage: int): + if not(self.__hpd_one_low_voltage.min_value < hpd_one_low_voltage < self.__hpd_one_low_voltage.max_value): + raise ValueError(f"HDP one low voltage cannot be less than {self.__hpd_one_low_voltage.min_value} " + f"and more than {self.__timeout.max_value}.") + self.__hpd_one_low_voltage.default_value = hpd_one_low_voltage + + @property + def hpd_one_high_voltage(self) -> int: + """ + Set and get HPD line logical one high voltage level limit. + + Returns: + object of int type + """ + return self.__hpd_one_high_voltage.default_value + + @hpd_one_high_voltage.setter + def hpd_one_high_voltage(self, hpd_one_high_voltage: int): + if not(self.__hpd_one_high_voltage.min_value < hpd_one_high_voltage < self.__hpd_one_high_voltage.max_value): + raise ValueError(f"HDP one high voltage cannot be less than {self.__hpd_one_high_voltage.min_value} " + f"and more than {self.__hpd_one_high_voltage.max_value}.") + self.__hpd_one_high_voltage.default_value = hpd_one_high_voltage + + @property + def aux_positive_idle_low_voltage(self) -> int: + """ + Set and get AUX + line idle low voltage level limit. + + Returns: + object of int type + """ + return self.__aux_positive_idle_low_voltage.default_value + + @aux_positive_idle_low_voltage.setter + def aux_positive_idle_low_voltage(self, aux_positive_idle_low_voltage: int): + if not(self.__aux_positive_idle_low_voltage.min_value < aux_positive_idle_low_voltage < + self.__aux_positive_idle_low_voltage.max_value): + raise ValueError(f"AUX positive IDLE low voltage cannot be less than " + f"{self.__aux_positive_idle_low_voltage.min_value} and more than " + f"{self.__aux_positive_idle_low_voltage.max_value}.") + self.__aux_positive_idle_low_voltage.default_value = aux_positive_idle_low_voltage + + @property + def aux_positive_idle_high_voltage(self) -> int: + """ + Set and get AUX + line idle high voltage level limit. + + Returns: + object of int type + """ + return self.__aux_positive_idle_high_voltage.default_value + + @aux_positive_idle_high_voltage.setter + def aux_positive_idle_high_voltage(self, aux_positive_idle_high_voltage: int): + if not(self.__aux_positive_idle_high_voltage.min_value < aux_positive_idle_high_voltage < + self.__aux_positive_idle_high_voltage.max_value): + raise ValueError(f"AUX positive IDLE high voltage cannot be less than " + f"{self.__aux_positive_idle_high_voltage.min_value} and more than " + f"{self.__aux_positive_idle_high_voltage.max_value}.") + self.__aux_positive_idle_high_voltage.default_value = aux_positive_idle_high_voltage + + @property + def aux_negative_idle_low_voltage(self) -> int: + """ + Set and get AUX - line idle low voltage level limit. + + Returns: + object of int type + """ + return self.__aux_negative_idle_low_voltage.default_value + + @aux_negative_idle_low_voltage.setter + def aux_negative_idle_low_voltage(self, aux_negative_idle_low_voltage: int): + if not(self.__aux_negative_idle_low_voltage.min_value < aux_negative_idle_low_voltage < + self.__aux_negative_idle_low_voltage.max_value): + raise ValueError(f"AUX negative IDLE low voltage cannot be less than " + f"{self.__aux_negative_idle_low_voltage.min_value} and more than " + f"{self.__aux_negative_idle_low_voltage.max_value}.") + self.__aux_negative_idle_low_voltage.default_value = aux_negative_idle_low_voltage + + @property + def aux_negative_idle_high_voltage(self) -> int: + """ + Set and get AUX - line idle high voltage level limit. + + Returns: + object of int type + """ + return self.__aux_negative_idle_high_voltage.default_value + + @aux_negative_idle_high_voltage.setter + def aux_negative_idle_high_voltage(self, aux_negative_idle_high_voltage: int): + if not(self.__aux_negative_idle_high_voltage.min_value < aux_negative_idle_high_voltage < + self.__aux_negative_idle_high_voltage.max_value): + raise ValueError(f"AUX negative IDLE high voltage cannot be less than " + f"{self.__aux_negative_idle_high_voltage.min_value} and more than " + f"{self.__aux_negative_idle_high_voltage.max_value}.") + self.__aux_negative_idle_high_voltage.default_value = aux_negative_idle_high_voltage + + @property + def aux_positive_trig_voltage(self) -> int: + """ + Set and get AUX + line signal trigger level. + + Returns: + object of int type + """ + return self.__aux_positive_trig_voltage.default_value + + @aux_positive_trig_voltage.setter + def aux_positive_trig_voltage(self, aux_positive_trig_voltage: int): + if not(self.__aux_positive_trig_voltage.min_value < aux_positive_trig_voltage < + self.__aux_positive_trig_voltage.max_value): + raise ValueError(f"AUX positive trig voltage cannot be less than " + f"{self.__aux_positive_trig_voltage.min_value} and more than " + f"{self.__aux_positive_trig_voltage.max_value}.") + self.__aux_positive_trig_voltage.default_value = aux_positive_trig_voltage + + @property + def aux_negative_trig_voltage(self) -> int: + """ + Set and get AUX - line signal trigger level. + + Returns: + object of int type + """ + return self.__aux_negative_trig_voltage.default_value + + @aux_negative_trig_voltage.setter + def aux_negative_trig_voltage(self, aux_negative_trig_voltage: int): + if not(self.__aux_negative_trig_voltage.min_value < aux_negative_trig_voltage < + self.__aux_negative_trig_voltage.max_value): + raise ValueError(f"AUX negative trig voltage cannot be less than " + f"{self.__aux_negative_trig_voltage.min_value} and more than " + f"{self.__aux_negative_trig_voltage.max_value}.") + self.__aux_negative_trig_voltage.default_value = aux_negative_trig_voltage + + @property + def aux_signal_capture_timeout(self) -> int: + """ + Set and get AUX signal capture timeout, milliseconds. + + Returns: + object of int type + """ + return self.__aux_signal_capture_timeout.default_value + + @aux_signal_capture_timeout.setter + def aux_signal_capture_timeout(self, aux_signal_capture_timeout: int): + if not(self.__aux_signal_capture_timeout.min_value < aux_signal_capture_timeout < + self.__aux_signal_capture_timeout.max_value): + raise ValueError(f"AUX signal capture timeout cannot be less than " + f"{self.__aux_signal_capture_timeout.min_value} and more than " + f"{self.__aux_signal_capture_timeout.max_value}.") + self.__aux_signal_capture_timeout.default_value = aux_signal_capture_timeout + + @property + def aux_signal_capture_count(self) -> int: + """ + Set and get AUX signal capture attempts. + + Returns: + object of int type + """ + return self.__aux_signal_capture_count.default_value + + @aux_signal_capture_count.setter + def aux_signal_capture_count(self, aux_signal_capture_count: int): + if not(self.__aux_signal_capture_count.min_value < aux_signal_capture_count < + self.__aux_signal_capture_count.max_value): + raise ValueError(f"AUX signal capture count cannot be less than " + f"{self.__aux_signal_capture_count.min_value} and more than " + f"{self.__aux_signal_capture_count.max_value}.") + self.__aux_signal_capture_count.default_value = aux_signal_capture_count + + @property + def dut_max_lanes(self) -> int: + """ + Set and get Maximum lanes count supported by DUT. + + Returns: + object of int type + """ + return self.__dut_max_lanes.default_value + + @dut_max_lanes.setter + def dut_max_lanes(self, dut_max_lanes: int): + if not(self.__dut_max_lanes.min_value < dut_max_lanes < self.__dut_max_lanes.max_value): + raise ValueError(f"DUT max lanes cannot be less than {self.__dut_max_lanes.min_value} and more than " + f"{self.__dut_max_lanes.max_value}.") + self.__dut_max_lanes.default_value = dut_max_lanes + + @property + def dut_max_link_rate(self) -> int: + """ + Set and get Maximum data rate supported by DUT in 0.27Gbps. + + Returns: + object of int type + """ + return self.__dut_max_link_rate.default_value + + @dut_max_link_rate.setter + def dut_max_link_rate(self, dut_max_link_rate: int): + if not(self.__dut_max_link_rate.min_value < dut_max_link_rate < self.__dut_max_link_rate.max_value): + raise ValueError(f"DUT max link rate cannot be less than {self.__dut_max_link_rate.min_value} and more than " + f"{self.__dut_max_link_rate.max_value}.") + self.__dut_max_link_rate.default_value = dut_max_link_rate + + @property + def dut_ta_caps(self): + """ + Set and get DUT Test automation capabilities flags. + + Returns: + object DpDutTaCaps + """ + return self.__dut_ta_caps + + @dut_ta_caps.setter + def dut_ta_caps(self, dut_ta_caps: DpDutTaCaps): + self.__dut_ta_caps = dut_ta_caps diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/dp_source_adaptive_sync_tab.py b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_source_adaptive_sync_tab.py new file mode 100644 index 0000000..15c9656 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_source_adaptive_sync_tab.py @@ -0,0 +1,343 @@ +from enum import IntEnum +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param +from UniTAP.dev.modules.dut_tests.test_utils import update_default_value + + +class RangeMinRate(IntEnum): + """ + Describes available values for rate in Adaptive-Sync tests. + """ + Rate_59_940Hz = 0 + Rate_47_952Hz = 1 + Rate_29_970Hz = 2 + Rate_23_976Hz = 3 + + +class AdaptiveSyncDpCaps(Param): + """ + Class `AdaptiveSyncDp14Caps` defines adaptive-sync capabilities and allows setting: + - Support Adaptive-Sync `support_adaptive_sync`. + - Support fixed average `support_fixed_average`. + - Support duration increase and decrease `support_duration_increase_and_decrease`. + """ + def __init__(self, json_obj): + super().__init__(json_obj) + + @property + def support_adaptive_sync(self): + """ + Set and get adaptive-sync flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(0, bool) + + @support_adaptive_sync.setter + def support_adaptive_sync(self, support: bool): + self._set_by_bitmask(support, 0) + + @property + def support_fixed_average(self): + """ + Set and get fixed average flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(1, bool) + + @support_fixed_average.setter + def support_fixed_average(self, support: bool): + self._set_by_bitmask(support, 1) + + @property + def support_duration_increase_and_decrease(self): + """ + Set and get duration increase and decrease flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(2, bool) + + @support_duration_increase_and_decrease.setter + def support_duration_increase_and_decrease(self, support: bool): + self._set_by_bitmask(support, 2) + + @property + def enable_manually(self): + """ + Set and get enable manually flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(3, bool) + + @enable_manually.setter + def enable_manually(self, enable: bool): + self._set_by_bitmask(enable, 3) + + +class AdaptiveSyncDpConfigTabBase: + def __init__(self, caps: AdaptiveSyncDpCaps, as_range_min_rate, + max_rate_1920x1080, + max_rate_2560x1080, + max_rate_2560x1440, + max_rate_3840x2160, + max_rate_4096x2160, + max_rate_5120x2160, + max_rate_7680x4320, + max_rate_10240x4320): + self._caps = caps + self._as_range_min_rate = as_range_min_rate + self._max_rate_1920x1080 = max_rate_1920x1080 + self._max_rate_2560x1080 = max_rate_2560x1080 + self._max_rate_2560x1440 = max_rate_2560x1440 + self._max_rate_3840x2160 = max_rate_3840x2160 + self._max_rate_4096x2160 = max_rate_4096x2160 + self._max_rate_5120x2160 = max_rate_5120x2160 + self._max_rate_7680x4320 = max_rate_7680x4320 + self._max_rate_10240x4320 = max_rate_10240x4320 + + @property + def as_caps(self) -> AdaptiveSyncDpCaps: + """ + Set and get adaptive-sync capabilities. + + Returns: + object `AdaptiveSyncDp14Caps` + """ + return self._caps + + @as_caps.setter + def as_caps(self, caps: AdaptiveSyncDpCaps): + self._caps = caps + + @property + def as_range_min_rate(self) -> int: + """ + Set and get adaptive-sync minimum rate. + + Returns: + object `RangeMinRate` + """ + return self._as_range_min_rate + + @as_range_min_rate.setter + def as_range_min_rate(self, as_range_min_rate: RangeMinRate): + self._as_range_min_rate.default_value = as_range_min_rate.value + + @property + def max_rate_1920x1080(self) -> int: + """ + Set and get maximum rate for resolution 1920x1080 + + Returns: + object of int type + """ + return self._max_rate_1920x1080.default_value + + @max_rate_1920x1080.setter + def max_rate_1920x1080(self, max_rate_1920x1080: int): + if not ( + self._max_rate_1920x1080.min_value <= max_rate_1920x1080 < self._max_rate_1920x1080.max_value): + raise ValueError(f"Max rate for 1920x1080 cannot be less than " + f"{self._max_rate_1920x1080.min_value} and more than " + f"{self._max_rate_1920x1080.max_value}.") + self._max_rate_1920x1080.default_value = max_rate_1920x1080 + + @property + def max_rate_2560x1080(self) -> int: + """ + Set and get maximum rate for resolution 2560x1080 + + Returns: + object of int type + """ + return self._max_rate_2560x1080.default_value + + @max_rate_2560x1080.setter + def max_rate_2560x1080(self, max_rate_2560x1080: int): + if not ( + self._max_rate_2560x1080.min_value <= max_rate_2560x1080 < self._max_rate_2560x1080.max_value): + raise ValueError(f"Max rate for 2560x1080 cannot be less than " + f"{self._max_rate_2560x1080.min_value} and more than " + f"{self._max_rate_2560x1080.max_value}.") + self._max_rate_2560x1080.default_value = max_rate_2560x1080 + + @property + def max_rate_2560x1440(self) -> int: + """ + Set and get maximum rate for resolution 2560x1440 + + Returns: + object of int type + """ + return self._max_rate_2560x1440.default_value + + @max_rate_2560x1440.setter + def max_rate_2560x1440(self, max_rate_2560x1440: int): + if not ( + self._max_rate_2560x1440.min_value <= max_rate_2560x1440 < self._max_rate_2560x1440.max_value): + raise ValueError(f"Max rate for 2560x1080 cannot be less than " + f"{self._max_rate_2560x1440.min_value} and more than " + f"{self._max_rate_2560x1440.max_value}.") + self._max_rate_2560x1440.default_value = max_rate_2560x1440 + + @property + def max_rate_3840x2160(self) -> int: + """ + Set and get maximum rate for resolution 3840x2160 + + Returns: + object of int type + """ + return self._max_rate_3840x2160.default_value + + @max_rate_3840x2160.setter + def max_rate_3840x2160(self, max_rate_3840x2160: int): + if not ( + self._max_rate_3840x2160.min_value <= max_rate_3840x2160 < self._max_rate_3840x2160.max_value): + raise ValueError(f"Max rate for 3840x2160 cannot be less than " + f"{self._max_rate_3840x2160.min_value} and more than " + f"{self._max_rate_3840x2160.max_value}.") + self._max_rate_3840x2160.default_value = max_rate_3840x2160 + + @property + def max_rate_4096x2160(self) -> int: + """ + Set and get maximum rate for resolution 4096x2160 + + Returns: + object of int type + """ + return self._max_rate_4096x2160.default_value + + @max_rate_4096x2160.setter + def max_rate_4096x2160(self, max_rate_4096x2160: int): + if not ( + self._max_rate_4096x2160.min_value <= max_rate_4096x2160 < self._max_rate_4096x2160.max_value): + raise ValueError(f"Max rate for 4096x2160 cannot be less than " + f"{self._max_rate_4096x2160.min_value} and more than " + f"{self._max_rate_4096x2160.max_value}.") + self._max_rate_4096x2160.default_value = max_rate_4096x2160 + + @property + def max_rate_5120x2160(self) -> int: + """ + Set and get maximum rate for resolution 5120x2160 + + Returns: + object of int type + """ + return self._max_rate_5120x2160.default_value + + @max_rate_5120x2160.setter + def max_rate_5120x2160(self, max_rate_5120x2160: int): + if not ( + self._max_rate_5120x2160.min_value <= max_rate_5120x2160 < self._max_rate_5120x2160.max_value): + raise ValueError(f"Max rate for 5120x2160 cannot be less than " + f"{self._max_rate_5120x2160.min_value} and more than " + f"{self._max_rate_5120x2160.max_value}.") + self._max_rate_5120x2160.default_value = max_rate_5120x2160 + + @property + def max_rate_7680x4320(self) -> int: + """ + Set and get maximum rate for resolution 7680x4320 + + Returns: + object of int type + """ + return self._max_rate_7680x4320.default_value + + @max_rate_7680x4320.setter + def max_rate_7680x4320(self, max_rate_7680x4320: int): + if not ( + self._max_rate_7680x4320.min_value <= max_rate_7680x4320 < self._max_rate_7680x4320.max_value): + raise ValueError(f"Max rate for 7680x4320 cannot be less than " + f"{self._max_rate_7680x4320.min_value} and more than " + f"{self._max_rate_7680x4320.max_value}.") + self._max_rate_7680x4320.default_value = max_rate_7680x4320 + + @property + def max_rate_10240x4320(self) -> int: + """ + Set and get maximum rate for resolution 10240x4320 + + Returns: + object of int type + """ + return self._max_rate_10240x4320.default_value + + @max_rate_10240x4320.setter + def max_rate_10240x4320(self, max_rate_10240x4320: int): + if not ( + self._max_rate_10240x4320.min_value <= max_rate_10240x4320 < self._max_rate_10240x4320.max_value): + raise ValueError(f"Max rate for 10240x4320 cannot be less than " + f"{self._max_rate_10240x4320.min_value} and more than " + f"{self._max_rate_10240x4320.max_value}.") + self._max_rate_10240x4320.default_value = max_rate_10240x4320 + + def clear_max_rates(self): + """ + Clear rates settings. + """ + self.max_rate_1920x1080 = 0 + self.max_rate_2560x1080 = 0 + self.max_rate_2560x1440 = 0 + self.max_rate_3840x2160 = 0 + self.max_rate_4096x2160 = 0 + self.max_rate_5120x2160 = 0 + self.max_rate_7680x4320 = 0 + self.max_rate_10240x4320 = 0 + + +class AdaptiveSyncDp14ConfigTab(AdaptiveSyncDpConfigTabBase): + """ + Class `AdaptiveSyncDp14ConfigTab` allows working with parameters for DP 1.4 Adaptive-Sync tests. + - Set and get capabilities `as_caps` type `AdaptiveSyncDp14Caps`. + - Set and get adaptive-sync range minimum rate `as_range_min_rate` type `RangeMinRate`. + - Set and get maximum rate for resolution 1920x1080 `max_rate_1920x1080`. + - Set and get maximum rate for resolution 2560x1080 `max_rate_2560x1080`. + - Set and get maximum rate for resolution 2560x1440 `max_rate_2560x1440`. + - Set and get maximum rate for resolution 3840x2160 `max_rate_3840x2160`. + - Set and get maximum rate for resolution 4096x2160 `max_rate_4096x2160`. + - Set and get maximum rate for resolution 5120x2160 `max_rate_5120x2160`. + - Set and get maximum rate for resolution 7680x4320 `max_rate_7680x4320`. + - Set and get maximum rate for resolution 10240x4320 `max_rate_10240x4320`. + """ + def __init__(self, json_obj): + super().__init__(AdaptiveSyncDpCaps(json_obj["TSI_DP14_SRCCTS_AS_DUT_CAPS"]), + Param(json_obj["TSI_DP14_SRCCTS_AS_RANGE_MIN_RATE"]), + Param(json_obj["TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_0"]), + Param(json_obj["TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_1"]), + Param(json_obj["TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_2"]), + Param(json_obj["TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_3"]), + Param(json_obj["TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_4"]), + Param(json_obj["TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_5"]), + Param(json_obj["TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_6"]), + Param(json_obj["TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_7"]) + ) + + +class AdaptiveSyncDp21ConfigTab(AdaptiveSyncDpConfigTabBase): + """ + Class `AdaptiveSyncDp21ConfigTab` inherited of class`AdaptiveSyncDp14ConfigTab` allows working with + parameters for DP 2.1 Adaptive-Sync tests + Class `AdaptiveSyncDp21ConfigTab` has all the `AdaptiveSyncDp14ConfigTab` functionality. + """ + def __init__(self, json_obj): + super().__init__(AdaptiveSyncDpCaps(json_obj["TSI_DP20_SRCCTS_AS_DUT_CAPS"]), + Param(json_obj["TSI_DP20_SRCCTS_AS_RANGE_MIN_RATE"]), + Param(json_obj["TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_0"]), + Param(json_obj["TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_1"]), + Param(json_obj["TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_2"]), + Param(json_obj["TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_3"]), + Param(json_obj["TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_4"]), + Param(json_obj["TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_5"]), + Param(json_obj["TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_6"]), + Param(json_obj["TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_7"]) + ) diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/dp_source_audio_tab.py b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_source_audio_tab.py new file mode 100644 index 0000000..8cd5aa5 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_source_audio_tab.py @@ -0,0 +1,305 @@ +from typing import List +from enum import IntEnum + +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param + + +class AudioTestPattern(IntEnum): + """ + Describes available values for audio test pattern. + """ + OperatorSpecificWaveform = 0 + SawtoothWaveform = 1 + + +class AudioDpChannelsConfig: + """ + Class `AudioDp14ChannelsConfig` allows working with audio channel configurations. + - Get Channel count `channels_count`. + - Select type of audio channel `select_channels`. + + List of audio channel types: + - FL+FR + - RL+RR + - FLH+FRH + - TC + - LFE + - RLC+RRC + - FLW+FRW + - FHC + - FC + - RC + - FLC+FRC + """ + __AUDIO_CHANNELS_INFO = {"FL+FR": 0x001, "RL+RR": 0x008, "FLH+FRH": 0x100, "TC": 0x200, 'LFE': 0x002, + "RLC+RRC": 0x040, "FLW+FRW": 0x080, "FHC": 0x400, 'FC': 0x004, 'RC': 0x010, + "FLC+FRC": 0x020} + + __AUDIO_CHANNELS_COUNT = {0x001: 2, 0x008: 2, 0x100: 2, 0x200: 1, 0x002: 1, 0x040: 2, 0x080: 2, 0x400: 1, 0x004: 1, + 0x010: 1, 0x020: 2} + + __AUDIO_CHANNELS_VARIANTS = '\n'.join(__AUDIO_CHANNELS_INFO.keys()) + + def __init__(self, parameters): + self.__channels_count = parameters[0] + self.__channels_config = parameters[1] + + @property + def channels_count(self) -> int: + """ + Get channels count. + + Returns: + object of int type + """ + return self.__channels_count.default_value + + def __check_audio(self, channel_type: str) -> bool: + old_value = new_value = self.__channels_config.default_value + new_value &= ~ self.__AUDIO_CHANNELS_INFO.get(channel_type) + return old_value == new_value + + def __already_selected(self) -> List[str]: + selected = [] + for item in self.__AUDIO_CHANNELS_INFO.keys(): + if self.__channels_config.default_value & self.__AUDIO_CHANNELS_INFO.get(item): + selected.append(item) + return selected + + def select_channels(self, channels: List[str], enable: bool): + """ + Set channels types. + + Args: + channels (list[str]) + enable (bool) + """ + if all(item in list(self.__AUDIO_CHANNELS_INFO.keys()) for item in channels) is False: + channels_type = '\n'.join(channels) + raise ValueError(f"Incorrect channels type{'s' if len(channels) > 1 else ''} {channels_type}. " + f"Available audio channels variants:\n{self.__AUDIO_CHANNELS_VARIANTS}") + if enable: + count = int(self.channels_count) + new_value = self.__channels_config.default_value + for channel_type in channels: + if self.__check_audio(channel_type): + count += self.__AUDIO_CHANNELS_COUNT.get(self.__AUDIO_CHANNELS_INFO.get(channel_type)) + new_value |= self.__AUDIO_CHANNELS_INFO.get(channel_type) + if count >= 9: + already_selected_list = self.__already_selected() + already_selected_list.extend(channels) + already_selected = ', '.join(already_selected_list) + raise ValueError(f"Max supported audio channels = 8. Current channels count = {count}, " + f"selected - {already_selected}") + else: + self.__channels_config.default_value |= new_value + self.__channels_count.default_value = count + else: + count = 0 + old_value = new_value = self.__channels_config.default_value + for channel_type in channels: + if not self.__check_audio(channel_type): + count += self.__AUDIO_CHANNELS_COUNT.get(self.__AUDIO_CHANNELS_INFO.get(channel_type)) + new_value &= ~self.__AUDIO_CHANNELS_INFO.get(channel_type) + if old_value != new_value: + self.__channels_count.default_value -= count + self.__channels_config.default_value = new_value + + def clear_all(self): + """ + Clear channel count and configs. + """ + self.__channels_count.default_value = 0 + self.__channels_config.default_value = 0 + + +class AudioSourceDpSettingTabBase: + __SAMPLE_RATE = [32000, 44100, 48000, 88200, 96000, 176400, 192000] + __available_variants = '\n'.join(map(str, __SAMPLE_RATE)) + + def __init__(self, + min_sample_rate, + max_sample_rate, + min_ch_config_min_rate, + max_ch_config_min_rate, + min_ch_config_max_rate, + max_ch_config_max_rate, + audio_pattern, + sample_size): + self.__min_sample_rate = min_sample_rate + self.__max_sample_rate = max_sample_rate + self.__min_ch_config_min_rate = AudioDpChannelsConfig(min_ch_config_min_rate) + self.__max_ch_config_min_rate = AudioDpChannelsConfig(max_ch_config_min_rate) + self.__min_ch_config_max_rate = AudioDpChannelsConfig(min_ch_config_max_rate) + self.__max_ch_config_max_rate = AudioDpChannelsConfig(max_ch_config_max_rate) + self.__audio_pattern = audio_pattern # AudioTestPattern + self.__sample_size = sample_size + + @property + def min_sample_rate(self) -> int: + """ + Set and get minimum sample rate. + + Returns: + object of int type + """ + return int(self.__min_sample_rate.default_value) + + @min_sample_rate.setter + def min_sample_rate(self, min_sample_rate: int): + if min_sample_rate not in self.__SAMPLE_RATE: + raise ValueError( + f"Value {min_sample_rate} not in available variants:\n{self.__available_variants}") + self.__min_sample_rate.default_value = min_sample_rate + + @property + def max_sample_rate(self) -> int: + """ + Set and get maximum sample rate. + + Returns: + object of int type + """ + return int(self.__max_sample_rate.default_value) + + @max_sample_rate.setter + def max_sample_rate(self, max_sample_rate: int): + if max_sample_rate not in self.__SAMPLE_RATE: + raise ValueError( + f"Value {max_sample_rate} not in available variants:\n{self.__available_variants}") + self.__max_sample_rate.default_value = max_sample_rate + + @property + def min_ch_config_min_rate(self) -> AudioDpChannelsConfig: + """ + Get object of config minimum channels and minimum sample rate. + + Returns: + object of `AudioDp14ChannelsConfig` type + """ + return self.__min_ch_config_min_rate + + @property + def max_ch_config_min_rate(self) -> AudioDpChannelsConfig: + """ + Get object of config maximum channels and minimum sample rate. + + Returns: + object of `AudioDp14ChannelsConfig` type + """ + return self.__max_ch_config_min_rate + + @property + def min_ch_config_max_rate(self) -> AudioDpChannelsConfig: + """ + Get object of config minimum channels and maximum sample rate. + + Returns: + object of `AudioDp14ChannelsConfig` type + """ + return self.__min_ch_config_max_rate + + @property + def max_ch_config_max_rate(self) -> AudioDpChannelsConfig: + """ + Get object of config maximum channels and maximum sample rate. + + Returns: + object of `AudioDp14ChannelsConfig` type + """ + return self.__max_ch_config_max_rate + + @property + def audio_pattern(self): + """ + Set and get audio pattern. + + Returns: + object of `AudioTestPattern` type + """ + return self.__audio_pattern + + @audio_pattern.setter + def audio_pattern(self, audio_pattern: AudioTestPattern): + self.__audio_pattern.default_value = audio_pattern.value + + @property + def sample_size(self) -> int: + """ + Set and get sample size. + + Returns: + object of int type + """ + return int(self.__sample_size.default_value) + + @sample_size.setter + def sample_size(self, sample_size: int): + if sample_size not in [16, 20, 24]: + raise ValueError(f"Value {sample_size} not in available variants:\n16\n20\n24\n") + self.__sample_size.default_value = sample_size + + def clear_all(self): + """ + Clear all settings. + """ + self.__min_sample_rate.default_value = 32000 + self.__max_sample_rate.default_value = 32000 + self.min_ch_config_min_rate.clear_all() + self.max_ch_config_min_rate.clear_all() + self.min_ch_config_max_rate.clear_all() + self.max_ch_config_max_rate.clear_all() + self.__sample_size.default_value = 16 + + +class AudioSourceDp14SettingTab(AudioSourceDpSettingTabBase): + """ + Class `AudioSourceDp14SettingTab` allows working with audio parameters for DP 1.4 DP LLCTS tests. + - Set and get minimum sample rate `min_sample_rate`. + - Set and get maximum sample rate `max_sample_rate`. + - Set and get minimum channels and minimum sample rate `min_ch_config_min_rate` type `AudioDp14ChannelsConfig`. + - Set and get maximum channels and minimum sample rate `max_ch_config_min_rate` type `AudioDp14ChannelsConfig`. + - Set and get minimum channels and maximum sample rate `min_ch_config_max_rate` type `AudioDp14ChannelsConfig`. + - Set and get maximum channels and maximum sample rate `max_ch_config_max_rate` type `AudioDp14ChannelsConfig`. + - Set and get audio pattern `audio_pattern` type `AudioTestPattern`. + - Set and get sample `sample_size``. + """ + + def __init__(self, json_obj): + super().__init__( + Param(json_obj["TSI_DP14_SRCCTS_MIN_SAMPLE_RATE"]), + Param(json_obj["TSI_DP14_SRCCTS_MAX_SAMPLE_RATE"]), + (Param(json_obj["TSI_DP14_SRCCTS_CHANNELS1"]), + Param(json_obj["TSI_DP14_SRCCTS_CH_ALLOC1"])), + (Param(json_obj["TSI_DP14_SRCCTS_CHANNELS2"]), + Param(json_obj["TSI_DP14_SRCCTS_CH_ALLOC2"])), + (Param(json_obj["TSI_DP14_SRCCTS_CHANNELS3"]), + Param(json_obj["TSI_DP14_SRCCTS_CH_ALLOC3"])), + (Param(json_obj["TSI_DP14_SRCCTS_CHANNELS4"]), + Param(json_obj["TSI_DP14_SRCCTS_CH_ALLOC4"])), + Param(json_obj["TSI_DP14_SRCCTS_AUDIO_TEST_PATTERN"]), + Param(json_obj["TSI_DP14_SRCCTS_EDID_SAMPLE_SIZE"]) + ) + + +class AudioSourceDp21SettingTab(AudioSourceDpSettingTabBase): + """ + Class `AudioSourceDp21SettingTab` inherited of class`AudioSourceDp14SettingTab` allows working with + audio parameters for DP 1.4 DP LLCTS tests. + Class `AudioSourceDp21SettingTab` has all the `AudioSourceDp14SettingTab` functionality. + """ + def __init__(self, json_obj): + super().__init__( + Param(json_obj["TSI_DP20_SRCCTS_MIN_SAMPLE_RATE"]), + Param(json_obj["TSI_DP20_SRCCTS_MAX_SAMPLE_RATE"]), + (Param(json_obj["TSI_DP20_SRCCTS_CHANNELS1"]), + Param(json_obj["TSI_DP20_SRCCTS_CH_ALLOC1"])), + (Param(json_obj["TSI_DP20_SRCCTS_CHANNELS2"]), + Param(json_obj["TSI_DP20_SRCCTS_CH_ALLOC2"])), + (Param(json_obj["TSI_DP20_SRCCTS_CHANNELS3"]), + Param(json_obj["TSI_DP20_SRCCTS_CH_ALLOC3"])), + (Param(json_obj["TSI_DP20_SRCCTS_CHANNELS4"]), + Param(json_obj["TSI_DP20_SRCCTS_CH_ALLOC4"])), + Param(json_obj["TSI_DP20_SRCCTS_AUDIO_TEST_PATTERN"]), + Param(json_obj["TSI_DP20_SRCCTS_EDID_SAMPLE_SIZE"]) + ) diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/dp_source_display_id_tab.py b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_source_display_id_tab.py new file mode 100644 index 0000000..d65435b --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_source_display_id_tab.py @@ -0,0 +1,1411 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param +from UniTAP.dev.modules.dut_tests.test_utils import update_default_value +from UniTAP.common import AudioFormat +from typing import TypeVar + +_dict_sample_rate = { + 1: 32000, + 2: 44100, + 3: 48000, + 4: 88200, + 5: 96000, + 6: 176400, + 7: 192000 +} + +_rev_dict_sample_rate = { + 32000: 1, + 44100: 2, + 48000: 3, + 88200: 4, + 96000: 5, + 176400: 6, + 192000: 7 +} + +_dict_sample_size = { + 1: 16, + 2: 20, + 3: 24 +} + +_rev_dict_sample_size = { + 16: 1, + 20: 2, + 24: 3 +} + +_dict_audio_type = { + 1: AudioFormat.L_PCM +} + +_rev_dict_audio_type = { + AudioFormat.L_PCM: 1 +} + + +class AudioMode: + """ + Class `AudioMode` describes audio mode for Display ID tests. Allows working with audio mode parameters. + - Set and get channel count `channel_count`. + - Set and get sample size `sample_size`. + - Set and get sample rate `sample_rate`. + - Set and get audio type `audio_type`. + """ + + def __init__(self, json_obj): + self.__value = Param(json_obj) + + def clear(self): + """ + Disable audio mode. + """ + self.__value.clear() + + @property + def channel_count(self) -> int: + """ + Set and get channels count. + + Returns: + object of int type + """ + return (self.__value.default_value & 0x7) + 1 + + @channel_count.setter + def channel_count(self, channel_count: int): + if channel_count >= 9 or channel_count <= 1: + raise ValueError(f"Channel count must be between 2-8") + self.__value.default_value &= ~0x7 + self.__value.default_value |= ((channel_count - 1) & 0x7) + + @property + def sample_size(self) -> int: + """ + Set and get sample size. + + Returns: + object of int type + """ + return _dict_sample_size.get((self.__value.default_value >> 8) & 0x3, 0) + + @sample_size.setter + def sample_size(self, sample_size: int): + if sample_size not in [16, 20, 24]: + raise ValueError(f"Value {sample_size} not in available variants:\n16\n20\n24\n") + self.__value.default_value &= ~(0x3 << 8) + self.__value.default_value |= _rev_dict_sample_size.get(sample_size, 0) << 8 + + @property + def sample_rate(self) -> int: + """ + Set and get sample rate. + + Returns: + object of int type + """ + return _dict_sample_rate.get(self.__value.default_value >> 10 & 0x7, 0) + + @sample_rate.setter + def sample_rate(self, sample_rate: int): + if _rev_dict_sample_rate.get(sample_rate) is None: + available_variants = '\n'.join(map(str, _rev_dict_sample_rate.keys())) + raise ValueError(f"Value {sample_rate} not in available variants:\n{available_variants}") + self.__value.default_value &= ~(0x7 << 10) + self.__value.default_value |= _rev_dict_sample_rate.get(sample_rate, 0) << 10 + + @property + def audio_type(self) -> AudioFormat: + """ + Get audio type. + + Returns: + object of audio type + """ + return _dict_audio_type.get(self.__value.default_value >> 4, AudioFormat.Unknown) + + @audio_type.setter + def audio_type(self, value: AudioFormat): + """ + Get audio type. + + Returns: + object of audio type + """ + if isinstance(value, AudioFormat): + self.__value.default_value &= ~(0xF << 4) + self.__value.default_value |= _rev_dict_audio_type.get(value, 0) << 4 + else: + raise TypeError(f"Incorrect value type {type(value)}. " + f"It must be UniTAP.AudioFormat.") + + +class DisplayIdDpAudioBase: + def __init__(self, audio_mode_list): + self._audio_mode_1 = AudioMode(audio_mode_list[0]) + self._audio_mode_2 = AudioMode(audio_mode_list[1]) + self._audio_mode_3 = AudioMode(audio_mode_list[2]) + self._audio_mode_4 = AudioMode(audio_mode_list[3]) + self._audio_mode_5 = AudioMode(audio_mode_list[4]) + self._audio_mode_6 = AudioMode(audio_mode_list[5]) + self._audio_mode_7 = AudioMode(audio_mode_list[6]) + self._audio_mode_8 = AudioMode(audio_mode_list[7]) + + @property + def audio_mode_1(self) -> AudioMode: + """ + Set and get audio mode 1. + + Returns: + object of `AudioMode` type + """ + return self._audio_mode_1 + + @audio_mode_1.setter + def audio_mode_1(self, audio_mode: AudioMode): + self._audio_mode_1 = audio_mode + + @property + def audio_mode_2(self) -> AudioMode: + """ + Set and get audio mode 2. + + Returns: + object of `AudioMode` type + """ + return self._audio_mode_2 + + @audio_mode_2.setter + def audio_mode_2(self, audio_mode: AudioMode): + self._audio_mode_2 = audio_mode + + @property + def audio_mode_3(self) -> AudioMode: + """ + Set and get audio mode 3. + + Returns: + object of `AudioMode` type + """ + return self._audio_mode_3 + + @audio_mode_3.setter + def audio_mode_3(self, audio_mode: AudioMode): + self._audio_mode_3 = audio_mode + + @property + def audio_mode_4(self) -> AudioMode: + """ + Set and get audio mode 4. + + Returns: + object of `AudioMode` type + """ + return self._audio_mode_4 + + @audio_mode_4.setter + def audio_mode_4(self, audio_mode: AudioMode): + self._audio_mode_4 = audio_mode + + @property + def audio_mode_5(self) -> AudioMode: + """ + Set and get audio mode 5. + + Returns: + object of `AudioMode` type + """ + return self._audio_mode_5 + + @audio_mode_5.setter + def audio_mode_5(self, audio_mode: AudioMode): + self._audio_mode_5 = audio_mode + + @property + def audio_mode_6(self) -> AudioMode: + """ + Set and get audio mode 6. + + Returns: + object of `AudioMode` type + """ + return self._audio_mode_6 + + @audio_mode_6.setter + def audio_mode_6(self, audio_mode: AudioMode): + self._audio_mode_6 = audio_mode + + @property + def audio_mode_7(self) -> AudioMode: + """ + Set and get audio mode 7. + + Returns: + object of `AudioMode` type + """ + return self._audio_mode_7 + + @audio_mode_7.setter + def audio_mode_7(self, audio_mode: AudioMode): + self._audio_mode_7 = audio_mode + + @property + def audio_mode_8(self) -> AudioMode: + """ + Set and get audio mode 8. + + Returns: + object of `AudioMode` type + """ + return self._audio_mode_8 + + @audio_mode_8.setter + def audio_mode_8(self, audio_mode: AudioMode): + self._audio_mode_8 = audio_mode + + def clear_all(self): + """ + Clear all settings. + """ + self._audio_mode_1.clear() + self._audio_mode_2.clear() + self._audio_mode_3.clear() + self._audio_mode_4.clear() + self._audio_mode_5.clear() + self._audio_mode_6.clear() + self._audio_mode_7.clear() + self._audio_mode_8.clear() + + +class DisplayIdDp14Audio(DisplayIdDpAudioBase): + """ + Class `DisplayIdDp14Audio` contains 8 available audio modes for Display ID tests. Allows settings each mode. + - Audio mode 1 `audio_mode_1`. + - Audio mode 2 `audio_mode_2`. + - Audio mode 3 `audio_mode_3`. + - Audio mode 4 `audio_mode_4`. + - Audio mode 5 `audio_mode_5`. + - Audio mode 6 `audio_mode_6`. + - Audio mode 7 `audio_mode_7`. + - Audio mode 8 `audio_mode_8`. + """ + + def __init__(self, json_obj): + super().__init__([ + json_obj["TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_0"], + json_obj["TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_1"], + json_obj["TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_2"], + json_obj["TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_3"], + json_obj["TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_4"], + json_obj["TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_5"], + json_obj["TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_6"], + json_obj["TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_7"] + ]) + + +class DisplayIdDp21Audio(DisplayIdDpAudioBase): + """ + Class `DisplayIdDp21Audio` inherited of class`DisplayIdDp14Audio` allows working with parameters for Display ID + LLCTS tests. + Class `DisplayIdDp21Audio` has all the `DisplayIdDp14Audio` functionality. + """ + + def __init__(self, json_obj): + super().__init__([ + json_obj["TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_0"], + json_obj["TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_1"], + json_obj["TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_2"], + json_obj["TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_3"], + json_obj["TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_4"], + json_obj["TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_5"], + json_obj["TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_6"], + json_obj["TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_7"] + ]) + + +class DmtTiming: + """ + Class `DmtTiming` describes all available DMT timings for Display ID LLCTS tests. Allows enabling and disable + timings. + - 800x600 60Hz `T_800x600_60Hz`. + - 1024x768 60Hz `T_1024x768_60Hz`. + - 1280x768 60Hz `T_1280x768_60Hz`. + - 1280x800 60Hz `T_1280x800_60Hz`. + - 1280x960 60Hz `T_1280x960_60Hz`. + - 1280x1024 60Hz `T_1280x1024_60Hz`. + - 1360x768 60Hz `T_1360x768_60Hz`. + - 1400x1050 60Hz `T_1400x1050_60Hz`. + - 1600x1200 60Hz `T_1600x1200_60Hz`. + - 1680x1050 60Hz `T_1680x1050_60Hz`. + - 1856x1392 60Hz `T_1856x1392_60Hz`. + - 1920x1080 60Hz `T_1920x1080_60Hz`. + - 1920x1200 60Hz `T_1920x1200_60Hz`. + - 1920x1440 60Hz `T_1920x1440_60Hz`. + - Disable all timings `clear_all`. + """ + + def __init__(self, json_obj): + self.__param = Param(json_obj) + + @property + def T_800x600_60Hz(self) -> bool: + """ + Set and get 800x600 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(0, bool) + + @T_800x600_60Hz.setter + def T_800x600_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 0) + + @property + def T_1024x768_60Hz(self) -> bool: + """ + Set and get 1024x768 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(1, bool) + + @T_1024x768_60Hz.setter + def T_1024x768_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 1) + + @property + def T_1280x768_60Hz(self) -> bool: + """ + Set and get 1280x768 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(2, bool) + + @T_1280x768_60Hz.setter + def T_1280x768_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 2) + + @property + def T_1280x800_60Hz(self) -> bool: + """ + Set and get 1280x800 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(3, bool) + + @T_1280x800_60Hz.setter + def T_1280x800_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 3) + + @property + def T_1280x960_60Hz(self) -> bool: + """ + Set and get 1280x960 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(4, bool) + + @T_1280x960_60Hz.setter + def T_1280x960_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 4) + + @property + def T_1280x1024_60Hz(self) -> bool: + """ + Set and get 1280x1024 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(5, bool) + + @T_1280x1024_60Hz.setter + def T_1280x1024_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 5) + + @property + def T_1360x768_60Hz(self) -> bool: + """ + Set and get 1360x768 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(6, bool) + + @T_1360x768_60Hz.setter + def T_1360x768_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 6) + + @property + def T_1400x1050_60Hz(self) -> bool: + """ + Set and get 1400x1050 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(7, bool) + + @T_1400x1050_60Hz.setter + def T_1400x1050_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 7) + + @property + def T_1600x1200_60Hz(self) -> bool: + """ + Set and get 1600x1200 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(8, bool) + + @T_1600x1200_60Hz.setter + def T_1600x1200_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 8) + + @property + def T_1680x1050_60Hz(self) -> bool: + """ + Set and get 1680x1050 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(9, bool) + + @T_1680x1050_60Hz.setter + def T_1680x1050_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 9) + + @property + def T_1856x1392_60Hz(self) -> bool: + """ + Set and get 1856x1392 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(10, bool) + + @T_1856x1392_60Hz.setter + def T_1856x1392_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 10) + + @property + def T_1920x1080_60Hz(self) -> bool: + """ + Set and get 1920x1080 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(11, bool) + + @T_1920x1080_60Hz.setter + def T_1920x1080_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 11) + + @property + def T_1920x1200_60Hz(self) -> bool: + """ + Set and get 1920x1200 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(12, bool) + + @T_1920x1200_60Hz.setter + def T_1920x1200_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 12) + + @property + def T_1920x1440_60Hz(self) -> bool: + """ + Set and get 1920x1440 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(13, bool) + + @T_1920x1440_60Hz.setter + def T_1920x1440_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 13) + + def clear_all(self): + """ + Disable all timings. + """ + self.T_800x600_60Hz = False + self.T_1024x768_60Hz = False + self.T_1280x768_60Hz = False + self.T_1280x800_60Hz = False + self.T_1280x960_60Hz = False + self.T_1280x1024_60Hz = False + self.T_1360x768_60Hz = False + self.T_1400x1050_60Hz = False + self.T_1600x1200_60Hz = False + self.T_1680x1050_60Hz = False + self.T_1856x1392_60Hz = False + self.T_1920x1080_60Hz = False + self.T_1920x1200_60Hz = False + self.T_1920x1440_60Hz = False + + +class CtaTiming: + """ + Class `CtaTiming` describes all available DMT timings for Display ID LLCTS tests. Allows enabling and disable + timings. + - 720x480 60Hz `T_720x480_60Hz` + - 1280x720 60Hz `T_1280x720_60Hz` + - 1920x1080 60Hz `T_1920x1080_60Hz` + - 1280x720 120Hz `T_1280x720_120Hz` + - 720x480 120Hz `T_720x480_120Hz` + - 1920x1080 120Hz `T_1920x1080_120Hz` + - 1680x720 60Hz `T_1680x720_60Hz` + - 1680x720 120Hz `T_1680x720_120Hz` + - 2560x1080 60Hz `T_2560x1080_60Hz` + - 2560x1080 120Hz `T_2560x1080_120Hz` + - 3840x2160 60Hz `T_3840x2160_60Hz` + - 4096x2160 60H `T_4096x2160_60Hz` + - 3840x2160 120Hz `T_3840x2160_120Hz` + - 5120x2160 60Hz `T_5120x2160_60Hz` + - 7680x4320 24Hz `T_7680x4320_24Hz` + - 7680x4320 30Hz `T_7680x4320_30Hz` + - Disable all timings `clear_all`. + """ + + def __init__(self, json_obj): + self.__param = Param(json_obj) + + @property + def T_720x480_60Hz(self) -> bool: + """ + Set and get 720x480 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(0, bool) + + @T_720x480_60Hz.setter + def T_720x480_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 0) + + @property + def T_1280x720_60Hz(self) -> bool: + """ + Set and get 1280x720 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(1, bool) + + @T_1280x720_60Hz.setter + def T_1280x720_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 1) + + @property + def T_1920x1080_60Hz(self) -> bool: + """ + Set and get 1920x1080 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(2, bool) + + @T_1920x1080_60Hz.setter + def T_1920x1080_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 2) + + @property + def T_1280x720_120Hz(self) -> bool: + """ + Set and get 1280x720 120Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(3, bool) + + @T_1280x720_120Hz.setter + def T_1280x720_120Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 3) + + @property + def T_720x480_120Hz(self) -> bool: + """ + Set and get 720x480 120Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(4, bool) + + @T_720x480_120Hz.setter + def T_720x480_120Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 4) + + @property + def T_1920x1080_120Hz(self) -> bool: + """ + Set and get 1920x1080 120Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(5, bool) + + @T_1920x1080_120Hz.setter + def T_1920x1080_120Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 5) + + @property + def T_1680x720_60Hz(self) -> bool: + """ + Set and get 1680x720 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(6, bool) + + @T_1680x720_60Hz.setter + def T_1680x720_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 6) + + @property + def T_1680x720_120Hz(self) -> bool: + """ + Set and get 1680x720 120Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(7, bool) + + @T_1680x720_120Hz.setter + def T_1680x720_120Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 7) + + @property + def T_2560x1080_60Hz(self) -> bool: + """ + Set and get 2560x1080 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(8, bool) + + @T_2560x1080_60Hz.setter + def T_2560x1080_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 8) + + @property + def T_2560x1080_120Hz(self) -> bool: + """ + Set and get 2560x1080 120Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(9, bool) + + @T_2560x1080_120Hz.setter + def T_2560x1080_120Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 9) + + @property + def T_3840x2160_60Hz(self) -> bool: + """ + Set and get 3840x2160 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(10, bool) + + @T_3840x2160_60Hz.setter + def T_3840x2160_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 10) + + @property + def T_4096x2160_60Hz(self) -> bool: + """ + Set and get 4096x2160 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(11, bool) + + @T_4096x2160_60Hz.setter + def T_4096x2160_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 11) + + @property + def T_3840x2160_120Hz(self) -> bool: + """ + Set and get 3840x2160 120Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(12, bool) + + @T_3840x2160_120Hz.setter + def T_3840x2160_120Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 12) + + @property + def T_5120x2160_60Hz(self) -> bool: + """ + Set and get 5120x2160 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(13, bool) + + @T_5120x2160_60Hz.setter + def T_5120x2160_60Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 13) + + @property + def T_7680x4320_24Hz(self) -> bool: + """ + Set and get 7680x4320 24Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(14, bool) + + @T_7680x4320_24Hz.setter + def T_7680x4320_24Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 14) + + @property + def T_7680x4320_30Hz(self) -> bool: + """ + Set and get 7680x4320 30Hz timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(15, bool) + + @T_7680x4320_30Hz.setter + def T_7680x4320_30Hz(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 15) + + def clear_all(self): + """ + Disable all timings. + """ + self.T_720x480_60Hz = False + self.T_1280x720_60Hz = False + self.T_1920x1080_60Hz = False + self.T_1280x720_120Hz = False + self.T_720x480_120Hz = False + self.T_1920x1080_120Hz = False + self.T_1680x720_60Hz = False + self.T_1680x720_120Hz = False + self.T_2560x1080_60Hz = False + self.T_2560x1080_120Hz = False + self.T_3840x2160_60Hz = False + self.T_4096x2160_60Hz = False + self.T_3840x2160_120Hz = False + self.T_5120x2160_60Hz = False + self.T_7680x4320_24Hz = False + self.T_7680x4320_30Hz = False + + +class CvtTiming: + """ + Class `CvtTiming` describes all available DMT timings for Display ID LLCTS tests. Allows enabling and disable + timings. + - 1920x1080 60Hz RB1 `T_1920x1080_60Hz_RB1`. + - 2048x1536 60Hz RB1 `T_2048x1536_60Hz_RB1`. + - 2560x1080 60Hz RB1 `T_2560x1080_60Hz_RB1`. + - 2560x1440 60Hz RB1 `T_2560x1440_60Hz_RB1`. + - 2560x1440 60Hz RB2 `T_2560x1440_60Hz_RB2`. + - 3840x2160 60Hz RB1 `T_3840x2160_60Hz_RB1`. + - 3840x2160 60Hz RB2 `T_3840x2160_60Hz_RB2`. + - 3840x2160 60Hz RB3 `T_3840x2160_60Hz_RB3`. + - 4096x2160 60Hz RB1 `T_4096x2160_60Hz_RB1`. + - 4096x2160 60Hz RB2 `T_4096x2160_60Hz_RB2`. + - 4096x2160 60Hz RB3 `T_4096x2160_60Hz_RB3`. + - 5120x2880 60Hz RB1 `T_5120x2880_60Hz_RB1`. + - 5120x2880 60Hz RB2 `T_5120x2880_60Hz_RB2`. + - 5120x2880 60Hz RB3 `T_5120x2880_60Hz_RB3`. + - Disable all timings `clear_all`. + """ + + def __init__(self, json_obj): + self.__param = Param(json_obj) + + @property + def T_1920x1080_60Hz_RB1(self) -> bool: + """ + Set and get 1920x1080 60Hz RB1 timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(0, bool) + + @T_1920x1080_60Hz_RB1.setter + def T_1920x1080_60Hz_RB1(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 0) + + @property + def T_2048x1536_60Hz_RB1(self) -> bool: + """ + Set and get 2048x1536 60Hz RB1 timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(1, bool) + + @T_2048x1536_60Hz_RB1.setter + def T_2048x1536_60Hz_RB1(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 1) + + @property + def T_2560x1080_60Hz_RB1(self) -> bool: + """ + Set and get 2560x1080 60Hz RB1 timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(2, bool) + + @T_2560x1080_60Hz_RB1.setter + def T_2560x1080_60Hz_RB1(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 2) + + @property + def T_2560x1440_60Hz_RB1(self) -> bool: + """ + Set and get 2560x1440 60Hz RB1 timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(3, bool) + + @T_2560x1440_60Hz_RB1.setter + def T_2560x1440_60Hz_RB1(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 3) + + @property + def T_2560x1440_60Hz_RB2(self) -> bool: + """ + Set and get 2560x1440 60Hz RB2 timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(4, bool) + + @T_2560x1440_60Hz_RB2.setter + def T_2560x1440_60Hz_RB2(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 4) + + @property + def T_3840x2160_60Hz_RB1(self) -> bool: + """ + Set and get 3840x2160 60Hz RB1 timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(5, bool) + + @T_3840x2160_60Hz_RB1.setter + def T_3840x2160_60Hz_RB1(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 5) + + @property + def T_3840x2160_60Hz_RB2(self) -> bool: + """ + Set and get 3840x2160 60Hz RB2 timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(6, bool) + + @T_3840x2160_60Hz_RB2.setter + def T_3840x2160_60Hz_RB2(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 6) + + @property + def T_3840x2160_60Hz_RB3(self) -> bool: + """ + Set and get 3840x2160 60Hz RB3 timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(7, bool) + + @T_3840x2160_60Hz_RB3.setter + def T_3840x2160_60Hz_RB3(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 7) + + @property + def T_4096x2160_60Hz_RB1(self) -> bool: + """ + Set and get 4096x2160 60Hz RB1 timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(8, bool) + + @T_4096x2160_60Hz_RB1.setter + def T_4096x2160_60Hz_RB1(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 8) + + @property + def T_4096x2160_60Hz_RB2(self) -> bool: + """ + Set and get 4096x2160 60Hz RB2 timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(9, bool) + + @T_4096x2160_60Hz_RB2.setter + def T_4096x2160_60Hz_RB2(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 9) + + @property + def T_4096x2160_60Hz_RB3(self) -> bool: + """ + Set and get 4096x2160 60Hz RB3 timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(10, bool) + + @T_4096x2160_60Hz_RB3.setter + def T_4096x2160_60Hz_RB3(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 10) + + @property + def T_5120x2880_60Hz_RB1(self) -> bool: + """ + Set and get 5120x2880 60Hz RB1 timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(11, bool) + + @T_5120x2880_60Hz_RB1.setter + def T_5120x2880_60Hz_RB1(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 11) + + @property + def T_5120x2880_60Hz_RB2(self) -> bool: + """ + Set and get 5120x2880 60Hz RB2 timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(12, bool) + + @T_5120x2880_60Hz_RB2.setter + def T_5120x2880_60Hz_RB2(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 12) + + @property + def T_5120x2880_60Hz_RB3(self) -> bool: + """ + Set and get 5120x2880 60Hz RB3 timing flag support. + + Returns: + object of bool type + """ + return self.__param._get_by_bitmask(13, bool) + + @T_5120x2880_60Hz_RB3.setter + def T_5120x2880_60Hz_RB3(self, enable: bool): + self.__param._set_by_bitmask(int(enable), 13) + + def clear_all(self): + """ + Disable all timings. + """ + self.T_1920x1080_60Hz_RB1 = False + self.T_2048x1536_60Hz_RB1 = False + self.T_2560x1080_60Hz_RB1 = False + self.T_2560x1440_60Hz_RB1 = False + self.T_2560x1440_60Hz_RB2 = False + self.T_3840x2160_60Hz_RB1 = False + self.T_3840x2160_60Hz_RB2 = False + self.T_3840x2160_60Hz_RB3 = False + self.T_4096x2160_60Hz_RB1 = False + self.T_4096x2160_60Hz_RB2 = False + self.T_4096x2160_60Hz_RB3 = False + self.T_5120x2880_60Hz_RB1 = False + self.T_5120x2880_60Hz_RB2 = False + self.T_5120x2880_60Hz_RB3 = False + + +class DisplayIdDpVideoTimingsBase: + def __init__(self, dmt_timings, cta_timings, cvt_timings): + self._dmt_timings = dmt_timings + self._cta_timings = cta_timings + self._cvt_timings = cvt_timings + + @property + def dmt_timings(self) -> DmtTiming: + """ + Set and get DMT timings. + + Returns: + object of `DmtTiming` type + """ + return self._dmt_timings + + @dmt_timings.setter + def dmt_timings(self, timings: DmtTiming): + self._dmt_timings = timings + + @property + def cta_timings(self) -> CtaTiming: + """ + Set and get CTA timings. + + Returns: + object of `CtaTiming` type + """ + return self._cta_timings + + @cta_timings.setter + def cta_timings(self, timings: CtaTiming): + self._cta_timings = timings + + @property + def cvt_timings(self) -> CvtTiming: + """ + Set and get CVT timings. + + Returns: + object of `CvtTiming` type + """ + return self._cvt_timings + + @cvt_timings.setter + def cvt_timings(self, timings: CvtTiming): + self._cvt_timings = timings + + def clear_all(self): + """ + Disable all timings. + """ + self.cta_timings.clear_all() + self.cvt_timings.clear_all() + self.dmt_timings.clear_all() + + +class DisplayIdDp14VideoTimings(DisplayIdDpVideoTimingsBase): + """ + Class `DisplayIdDp14VideoTimings` allows working with all timings for Display ID LLCTS tests. + - Configure DMT timings `dmt_timings`. + - Configure CTA timings `cta_timings`. + - Configure CVT timings `cvt_timings`. + - Disable all timings `clear_all`. + """ + + def __init__(self, json_obj): + super().__init__( + DmtTiming(json_obj["TSI_DP14_SRCCTS_DMT_TIMING"]), + CtaTiming(json_obj["TSI_DP14_SRCCTS_CTA_TIMING"]), + CvtTiming(json_obj["TSI_DP14_SRCCTS_CVT_TIMING"]) + ) + + +class DisplayIdDp21VideoTimings(DisplayIdDpVideoTimingsBase): + """ + Class `DisplayIdDp21VideoTimings` inherited of class`DisplayIdDp14VideoTimings` allows working with timings for + Display ID LLCTS tests. + Class `DisplayIdDp21VideoTimings` has all the `DisplayIdDp14VideoTimings` functionality. + """ + + def __init__(self, json_obj): + super().__init__( + DmtTiming(json_obj["TSI_DP20_SRCCTS_DMT_TIMING"]), + CtaTiming(json_obj["TSI_DP20_SRCCTS_CTA_TIMING"]), + CvtTiming(json_obj["TSI_DP20_SRCCTS_CVT_TIMING"]) + ) + + +GeneralDisplayIdParameters = TypeVar("GeneralDisplayIdParameters", + DisplayIdDp14VideoTimings, + DisplayIdDp21VideoTimings) + + +AudioDisplayIdParameters = TypeVar("AudioDisplayIdParameters", + DisplayIdDp14Audio, + DisplayIdDp21Audio) + + +class DisplayIdDpConfigTabBase: + def __init__(self, max_stream_h_active, max_stream_v_active, max_stream_pixel_clock, + display_id_audio, display_id_timings): + self.__max_stream_h_active = max_stream_h_active + self.__max_stream_v_active = max_stream_v_active + self.__max_stream_pixel_clock = max_stream_pixel_clock + self.__display_id_audio = display_id_audio + self.__display_id_timings = display_id_timings + + @property + def max_stream_h_active(self) -> int: + """ + Set and get maximum stream resolution H Active. + + Returns: + object of int type + """ + return self.__max_stream_h_active.default_value + + @max_stream_h_active.setter + def max_stream_h_active(self, max_stream_h_active: int): + if not ( + self.__max_stream_h_active.min_value < max_stream_h_active < self.__max_stream_h_active.max_value): + raise ValueError(f"Max stream H Active cannot be less than " + f"{self.__max_stream_h_active.min_value} and more than " + f"{self.__max_stream_h_active.max_value}.") + self.__max_stream_h_active.default_value = max_stream_h_active + + @property + def max_stream_v_active(self) -> int: + """ + Set and get maximum stream resolution V Active. + + Returns: + object of int type + """ + return self.__max_stream_v_active.default_value + + @max_stream_v_active.setter + def max_stream_v_active(self, max_stream_v_active: int): + if not ( + self.__max_stream_v_active.min_value < max_stream_v_active < self.__max_stream_v_active.max_value): + raise ValueError(f"Max stream V Active cannot be less than " + f"{self.__max_stream_v_active.min_value} and more than " + f"{self.__max_stream_v_active.max_value}.") + self.__max_stream_v_active.default_value = max_stream_v_active + + @property + def max_stream_pixel_clock(self) -> int: + """ + Set and get maximum stream pixel clock. + + Returns: + object of int type + """ + return self.__max_stream_pixel_clock.default_value + + @max_stream_pixel_clock.setter + def max_stream_pixel_clock(self, max_stream_pixel_clock: int): + if not (self.__max_stream_pixel_clock.min_value < max_stream_pixel_clock < + self.__max_stream_pixel_clock.max_value): + raise ValueError(f"Max stream pixel clock cannot be less than " + f"{self.__max_stream_pixel_clock.min_value} and more than " + f"{self.__max_stream_pixel_clock.max_value}.") + self.__max_stream_pixel_clock.default_value = max_stream_pixel_clock + + @property + def audio(self) -> AudioDisplayIdParameters: + """ + Set and get audio settings. + + Returns: + object of `DisplayIdDp14Audio` type + """ + return self.__display_id_audio + + @audio.setter + def audio(self, display_id_audio: DisplayIdDp14Audio): + self.__display_id_audio = display_id_audio + + @property + def general(self) -> GeneralDisplayIdParameters: + """ + Set and get timings settings. + + Returns: + object of `DisplayIdDp14VideoTimings` type + """ + return self.__display_id_timings + + @general.setter + def general(self, display_id_timings: DisplayIdDp14VideoTimings): + self.__display_id_timings = display_id_timings + + def clear_all(self): + self.__max_stream_h_active.default_value = 0 + self.__max_stream_v_active.default_value = 0 + self.__max_stream_pixel_clock.default_value = 0 + self.__display_id_timings.clear_all() + self.__display_id_audio.clear_all() + + +class DisplayIdDp14ConfigTab(DisplayIdDpConfigTabBase): + """ + Class `DisplayIdDp14ConfigTab` allows setting parameters for Display ID tests. + - Maximum stream resolution H Active `max_stream_h_active`. + - Maximum stream resolution V Active `max_stream_v_active`. + - Maximum stream pixel clock `max_stream_pixel_clock`. + - Audio modes `display_id_audio` type `DisplayIdDp14Audio`. + - Timings `display_id_timings` type `DisplayIdDp14VideoTimings`. + - Clear all settings `clear_all`. + """ + + def __init__(self, json_obj): + super().__init__(Param(json_obj["TSI_DP14_SRCCTS_MAX_STREAM_HACTIVE"]), + Param(json_obj["TSI_DP14_SRCCTS_MAX_STREAM_VACTIVE"]), + Param(json_obj["TSI_DP14_SRCCTS_MAX_STREAM_PIXEL_CLOCK"]), + DisplayIdDp14Audio(json_obj), + DisplayIdDp14VideoTimings(json_obj)) + + +class DisplayIdDp21ConfigTab(DisplayIdDpConfigTabBase): + """ + Class `DisplayIdDp21ConfigTab` inherited of class`DisplayIdDp14ConfigTab` allows working with parameters for + Display ID LLCTS tests. + Class `DisplayIdDp21ConfigTab` has all the `DisplayIdDp14ConfigTab` functionality. + """ + + def __init__(self, json_obj): + super().__init__(Param(json_obj["TSI_DP20_SRCCTS_MAX_STREAM_HACTIVE"]), + Param(json_obj["TSI_DP20_SRCCTS_MAX_STREAM_VACTIVE"]), + Param(json_obj["TSI_DP20_SRCCTS_MAX_STREAM_PIXEL_CLOCK"]), + DisplayIdDp21Audio(json_obj), + DisplayIdDp21VideoTimings(json_obj)) + + self.__dynamic_refresh_rate_h_active = Param(json_obj["TSI_DP20_SRCCTS_DRR_HACTIVE"]) + self.__dynamic_refresh_rate_v_active = Param(json_obj["TSI_DP20_SRCCTS_DRR_VACTIVE"]) + self.__dynamic_refresh_rate_pixel_clock = Param(json_obj["TSI_DP20_SRCCTS_DRR_PIXEL_CLOCK"]) + self.__dynamic_refresh_rate_min_vertical = Param(json_obj["TSI_DP20_SRCCTS_DRR_MIN_VRR"]) + self.__dynamic_refresh_rate_max_vertical = Param(json_obj["TSI_DP20_SRCCTS_DRR_MAX_VRR"]) + + @property + def dynamic_refresh_rate_h_active(self) -> int: + """ + Set and get Dynamic Refresh Rate h active. + + Returns: + object of int type + """ + return self.__dynamic_refresh_rate_h_active.default_value + + @dynamic_refresh_rate_h_active.setter + def dynamic_refresh_rate_h_active(self, dynamic_refresh_rate_h_active: int): + if not ( + self.__dynamic_refresh_rate_h_active.min_value < dynamic_refresh_rate_h_active < self.__dynamic_refresh_rate_h_active.max_value): + raise ValueError(f"Dynamic refresh rate h active be less than " + f"{self.__dynamic_refresh_rate_h_active.min_value} and more than " + f"{self.__dynamic_refresh_rate_h_active.max_value}.") + self.__dynamic_refresh_rate_h_active.default_value = dynamic_refresh_rate_h_active + + @property + def dynamic_refresh_rate_v_active(self) -> int: + """ + Set and get Dynamic Refresh Rate v active. + + Returns: + object of int type + """ + return self.__dynamic_refresh_rate_v_active.default_value + + @dynamic_refresh_rate_v_active.setter + def dynamic_refresh_rate_v_active(self, dynamic_refresh_rate_v_active: int): + if not ( + self.__dynamic_refresh_rate_v_active.min_value < dynamic_refresh_rate_v_active < self.__dynamic_refresh_rate_v_active.max_value): + raise ValueError(f"Dynamic refresh rate v active be less than " + f"{self.__dynamic_refresh_rate_v_active.min_value} and more than " + f"{self.__dynamic_refresh_rate_v_active.max_value}.") + self.__dynamic_refresh_rate_v_active.default_value = dynamic_refresh_rate_v_active + + @property + def dynamic_refresh_rate_pixel_clock(self) -> int: + """ + Set and get Dynamic Refresh Rate Pixel Clock. + + Returns: + object of int type + """ + return self.__dynamic_refresh_rate_pixel_clock.default_value + + @dynamic_refresh_rate_pixel_clock.setter + def dynamic_refresh_rate_pixel_clock(self, dynamic_refresh_rate_pixel_clock: int): + if not ( + self.__dynamic_refresh_rate_pixel_clock.min_value < dynamic_refresh_rate_pixel_clock < self.__dynamic_refresh_rate_pixel_clock.max_value): + raise ValueError(f"Dynamic refresh rate h active be less than " + f"{self.__dynamic_refresh_rate_pixel_clock.min_value} and more than " + f"{self.__dynamic_refresh_rate_pixel_clock.max_value}.") + self.__dynamic_refresh_rate_pixel_clock.default_value = dynamic_refresh_rate_pixel_clock + + @property + def dynamic_refresh_rate_min_vertical(self) -> int: + """ + Set and get Dynamic Refresh Rate minimum vertical. + + Returns: + object of int type + """ + return self.__dynamic_refresh_rate_min_vertical.default_value + + @dynamic_refresh_rate_min_vertical.setter + def dynamic_refresh_rate_min_vertical(self, dynamic_refresh_rate_min_vertical: int): + if not ( + self.__dynamic_refresh_rate_min_vertical.min_value < dynamic_refresh_rate_min_vertical < self.__dynamic_refresh_rate_min_vertical.max_value): + raise ValueError(f"Dynamic refresh rate h active be less than " + f"{self.__dynamic_refresh_rate_min_vertical.min_value} and more than " + f"{self.__dynamic_refresh_rate_min_vertical.max_value}.") + self.__dynamic_refresh_rate_min_vertical.default_value = dynamic_refresh_rate_min_vertical + + @property + def dynamic_refresh_rate_max_vertical(self) -> int: + """ + Set and get Dynamic Refresh Rate maximum vertical. + + Returns: + object of int type + """ + return self.__dynamic_refresh_rate_max_vertical.default_value + + @dynamic_refresh_rate_max_vertical.setter + def dynamic_refresh_rate_max_vertical(self, dynamic_refresh_rate_max_vertical: int): + if not ( + self.__dynamic_refresh_rate_max_vertical.min_value < dynamic_refresh_rate_max_vertical < self.__dynamic_refresh_rate_max_vertical.max_value): + raise ValueError(f"Dynamic refresh rate h active be less than " + f"{self.__dynamic_refresh_rate_max_vertical.min_value} and more than " + f"{self.__dynamic_refresh_rate_max_vertical.max_value}.") + self.__dynamic_refresh_rate_max_vertical.default_value = dynamic_refresh_rate_max_vertical \ No newline at end of file diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/dp_source_dsc_tab.py b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_source_dsc_tab.py new file mode 100644 index 0000000..2856825 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/dp_source_dsc_tab.py @@ -0,0 +1,703 @@ +from enum import IntEnum +from typing import Tuple +from UniTAP.dev.modules.dut_tests.test_utils import CMTFormat, CMTVSCFormat, CMTBitDepth, CMTVSCBitDepth,\ + make_cf, make_vsc_cf +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param + + +class Timing: + """ + Class `Timing` describes available supported timings standard. + - CTA `cta` (enable/disable). + - RB1 `rb1` (enable/disable). + - RB2 `rb2` (enable/disable). + - Set all standards `set_all`. + """ + def __init__(self, timing_value): + self.__value = Param(timing_value) + + @property + def cta(self) -> bool: + """ + Set and get CTA flag support. + + Returns: + object of bool type + """ + return bool(self.__value._get_by_bitmask(0)) + + @property + def rb1(self) -> bool: + """ + Set and get RB1 flag support. + + Returns: + object of bool type + """ + return bool(self.__value._get_by_bitmask(1)) + + @property + def rb2(self) -> bool: + """ + Set and get RB2 flag support. + + Returns: + object of bool type + """ + return bool(self.__value._get_by_bitmask(2)) + + @cta.setter + def cta(self, value: bool): + self.__value._set_by_bitmask(value, 0) + + @rb1.setter + def rb1(self, value: bool): + self.__value._set_by_bitmask(value, 1) + + @rb2.setter + def rb2(self, value: bool): + self.__value._set_by_bitmask(value, 2) + + def set_all(self): + self.cta = True + self.rb1 = True + self.rb2 = True + + def clear_all(self): + self.cta = False + self.rb1 = False + self.rb2 = False + + +class ColorimetryDp14: + """ + Class `ColorimetryDp14` describes optional and additional color modes to be used with DP CTS tests. + - RGB 8 bpc VESA `rgb_8bpc_vesa`. + - RGB 10 bpc VESA `rgb_10bpc_vesa`. + - RGB 12 bpc VESA `rgb_12bpc_vesa`. + - YCbCr-444 8 bpc CTA ITU-709 `ycbcr444_8bpc_cta_itu709`. + - YCbCr-444 10 bpc CTA ITU-709 `ycbcr444_10bpc_cta_itu709`. + - YCbCr-444 12 bpc CTA ITU-709 `ycbcr444_12bpc_cta_itu709`. + - YCbCr-422 8 bpc CTA ITU-709 `ycbcr422_8bpc_cta_itu709`. + - YCbCr-422 10 bpc CTA ITU-709 `ycbcr422_10bpc_cta_itu709`. + - YCbCr-422 12 bpc CTA ITU-709 `ycbcr422_10bpc_cta_itu709`. + - YCbCr-422 simple 8 bpc CTA ITU-709 `ycbcr422_simple_8bpc_cta_itu709`. + - YCbCr-422 simple 10 bpc CTA ITU-709 `ycbcr422_simple_10bpc_cta_itu709`. + - YCbCr-422 simple 12 bpc CTA ITU-709 `ycbcr422_simple_12bpc_cta_itu709`. + - YCbCr-420 8 bpc CTA ITU-709 `ycbcr420_8bpc_cta_itu709`. + - YCbCr-420 10 bpc CTA ITU-709 `ycbcr420_10bpc_cta_itu709`. + - YCbCr-420 12 bpc CTA ITU-709 `ycbcr420_12bpc_cta_itu709`. + - Clear all settings `clear_all`. + - Select all settings `select_all`. + """ + def __init__(self, json_obj): + self.__rgb_8bpc_vesa = Param(json_obj["TSI_DP14_SRCCTS_DSC_COLOR_MODE_0"]) + self.__rgb_10bpc_vesa = Param(json_obj["TSI_DP14_SRCCTS_DSC_COLOR_MODE_1"]) + self.__rgb_12bpc_vesa = Param(json_obj["TSI_DP14_SRCCTS_DSC_COLOR_MODE_2"]) + + self.__ycbcr444_8bpc_cta_itu709 = Param(json_obj["TSI_DP14_SRCCTS_DSC_COLOR_MODE_3"]) + self.__ycbcr444_10bpc_cta_itu709 = Param(json_obj["TSI_DP14_SRCCTS_DSC_COLOR_MODE_4"]) + self.__ycbcr444_12bpc_cta_itu709 = Param(json_obj["TSI_DP14_SRCCTS_DSC_COLOR_MODE_5"]) + + self.__ycbcr422_8bpc_cta_itu709 = Param(json_obj["TSI_DP14_SRCCTS_DSC_COLOR_MODE_6"]) + self.__ycbcr422_10bpc_cta_itu709 = Param(json_obj["TSI_DP14_SRCCTS_DSC_COLOR_MODE_7"]) + self.__ycbcr422_12bpc_cta_itu709 = Param(json_obj["TSI_DP14_SRCCTS_DSC_COLOR_MODE_8"]) + + self.__ycbcr422_simple_8bpc_cta_itu709 = Param(json_obj["TSI_DP14_SRCCTS_DSC_COLOR_MODE_9"]) + self.__ycbcr422_simple_10bpc_cta_itu709 = Param(json_obj["TSI_DP14_SRCCTS_DSC_COLOR_MODE_10"]) + self.__ycbcr422_simple_12bpc_cta_itu709 = Param(json_obj["TSI_DP14_SRCCTS_DSC_COLOR_MODE_11"]) + + self.__ycbcr420_8bpc_cta_itu709 = Param(json_obj["TSI_DP14_SRCCTS_DSC_COLOR_MODE_12"]) + self.__ycbcr420_10bpc_cta_itu709 = Param(json_obj["TSI_DP14_SRCCTS_DSC_COLOR_MODE_13"]) + self.__ycbcr420_12bpc_cta_itu709 = Param(json_obj["TSI_DP14_SRCCTS_DSC_COLOR_MODE_14"]) + + # Pass 126-142 + + self.__color_formats = Param(json_obj["TSI_DP14_SRCCTS_DSC_COLOR_FORMATS"]) + self.__color_formats.default_value = 32 + + def clear_all(self): + """ + Clear all modes (disable). + """ + self.rgb_8bpc_vesa = False + self.rgb_10bpc_vesa = False + self.rgb_12bpc_vesa = False + + self.ycbcr444_8bpc_cta_itu709 = False + self.ycbcr444_10bpc_cta_itu709 = False + self.ycbcr444_12bpc_cta_itu709 = False + + self.ycbcr422_8bpc_cta_itu709 = False + self.ycbcr422_10bpc_cta_itu709 = False + self.ycbcr422_12bpc_cta_itu709 = False + + self.ycbcr422_simple_8bpc_cta_itu709 = False + self.ycbcr422_simple_10bpc_cta_itu709 = False + self.ycbcr422_simple_12bpc_cta_itu709 = False + + self.ycbcr420_8bpc_cta_itu709 = False + self.ycbcr420_10bpc_cta_itu709 = False + self.ycbcr420_12bpc_cta_itu709 = False + + def select_all(self): + """ + Select all modes (enable). + """ + self.rgb_8bpc_vesa = True + self.rgb_10bpc_vesa = True + self.rgb_12bpc_vesa = True + + self.ycbcr444_8bpc_cta_itu709 = True + self.ycbcr444_10bpc_cta_itu709 = True + self.ycbcr444_12bpc_cta_itu709 = True + + self.ycbcr422_8bpc_cta_itu709 = True + self.ycbcr422_10bpc_cta_itu709 = True + self.ycbcr422_12bpc_cta_itu709 = True + + self.ycbcr422_simple_8bpc_cta_itu709 = True + self.ycbcr422_simple_10bpc_cta_itu709 = True + self.ycbcr422_simple_12bpc_cta_itu709 = True + + self.ycbcr420_8bpc_cta_itu709 = True + self.ycbcr420_10bpc_cta_itu709 = True + self.ycbcr420_12bpc_cta_itu709 = True + + @property + def rgb_8bpc_vesa(self): + """ + Set and get RGB 8 bpc VESA flag support. + + Returns: + object + """ + return self.__rgb_8bpc_vesa + + @rgb_8bpc_vesa.setter + def rgb_8bpc_vesa(self, value: bool): + cf_value = make_cf(CMTFormat.RGB_LEGACY, CMTBitDepth.BPC_8) if value else 0 + if self.__rgb_8bpc_vesa.default_value != cf_value: + self.__rgb_8bpc_vesa.default_value = cf_value + + @property + def rgb_10bpc_vesa(self): + """ + Set and get RGB 10 bpc VESA flag support. + + Returns: + object + """ + return self.__rgb_10bpc_vesa + + @rgb_10bpc_vesa.setter + def rgb_10bpc_vesa(self, value: bool): + cf_value = make_cf(CMTFormat.RGB_LEGACY, CMTBitDepth.BPC_10) if value else 0 + if self.__rgb_10bpc_vesa.default_value != cf_value: + self.__rgb_10bpc_vesa.default_value = cf_value + + @property + def rgb_12bpc_vesa(self): + """ + Set and get RGB 12 bpc VESA flag support. + + Returns: + object + """ + return self.__rgb_12bpc_vesa + + @rgb_12bpc_vesa.setter + def rgb_12bpc_vesa(self, value: bool): + cf_value = make_cf(CMTFormat.RGB_LEGACY, CMTBitDepth.BPC_12) if value else 0 + if self.__rgb_12bpc_vesa.default_value != cf_value: + self.__rgb_12bpc_vesa.default_value = cf_value + + @property + def ycbcr444_8bpc_cta_itu709(self): + """ + Set and get YCbCr-444 8 bpc CTA ITU-709 flag support. + + Returns: + object + """ + return self.__ycbcr444_8bpc_cta_itu709 + + @ycbcr444_8bpc_cta_itu709.setter + def ycbcr444_8bpc_cta_itu709(self, value: bool): + cf_value = make_cf(CMTFormat.YCBCR_444_BT709, CMTBitDepth.BPC_8) if value else 0 + if self.__ycbcr444_8bpc_cta_itu709.default_value != cf_value: + self.__ycbcr444_8bpc_cta_itu709.default_value = cf_value + + @property + def ycbcr444_10bpc_cta_itu709(self): + """ + Set and get YCbCr-444 10 bpc CTA ITU-709 flag support. + + Returns: + object + """ + return self.__ycbcr444_10bpc_cta_itu709 + + @ycbcr444_10bpc_cta_itu709.setter + def ycbcr444_10bpc_cta_itu709(self, value: bool): + cf_value = make_cf(CMTFormat.YCBCR_444_BT709, CMTBitDepth.BPC_10) if value else 0 + if self.__ycbcr444_10bpc_cta_itu709.default_value != cf_value: + self.__ycbcr444_10bpc_cta_itu709.default_value = cf_value + + @property + def ycbcr444_12bpc_cta_itu709(self): + """ + Set and get YCbCr-444 12 bpc CTA ITU-709 flag support. + + Returns: + object + """ + return self.__ycbcr444_12bpc_cta_itu709 + + @ycbcr444_12bpc_cta_itu709.setter + def ycbcr444_12bpc_cta_itu709(self, value: bool): + cf_value = make_cf(CMTFormat.YCBCR_444_BT709, CMTBitDepth.BPC_12) if value else 0 + if self.__ycbcr444_12bpc_cta_itu709.default_value != cf_value: + self.__ycbcr444_12bpc_cta_itu709.default_value = cf_value + + @property + def ycbcr422_8bpc_cta_itu709(self): + """ + Set and get YCbCr-422 8 bpc CTA ITU-709 flag support. + + Returns: + object + """ + return self.__ycbcr422_8bpc_cta_itu709 + + @ycbcr422_8bpc_cta_itu709.setter + def ycbcr422_8bpc_cta_itu709(self, value: bool): + cf_value = make_cf(CMTFormat.YCBCR_422_BT709, CMTBitDepth.BPC_8) if value else 0 + if self.__ycbcr444_12bpc_cta_itu709.default_value != cf_value: + self.__ycbcr444_12bpc_cta_itu709.default_value = cf_value + + @property + def ycbcr422_10bpc_cta_itu709(self): + """ + Set and get YCbCr-422 10 bpc CTA ITU-709 flag support. + + Returns: + object + """ + return self.__ycbcr422_10bpc_cta_itu709 + + @ycbcr422_10bpc_cta_itu709.setter + def ycbcr422_10bpc_cta_itu709(self, value: bool): + cf_value = make_cf(CMTFormat.YCBCR_422_BT709, CMTBitDepth.BPC_10) if value else 0 + if self.__ycbcr422_10bpc_cta_itu709.default_value != cf_value: + self.__ycbcr422_10bpc_cta_itu709.default_value = cf_value + + @property + def ycbcr422_12bpc_cta_itu709(self): + """ + Set and get YCbCr-422 12 bpc CTA ITU-709 flag support. + + Returns: + object + """ + return self.__ycbcr422_12bpc_cta_itu709 + + @ycbcr422_12bpc_cta_itu709.setter + def ycbcr422_12bpc_cta_itu709(self, value: bool): + cf_value = make_cf(CMTFormat.YCBCR_422_BT709, CMTBitDepth.BPC_12) if value else 0 + if self.__ycbcr422_12bpc_cta_itu709.default_value != cf_value: + self.__ycbcr422_12bpc_cta_itu709.default_value = cf_value + + @property + def ycbcr422_simple_8bpc_cta_itu709(self): + """ + Set and get YCbCr-422 Simple 8 bpc CTA ITU-709 flag support. + + Returns: + object + """ + return self.__ycbcr422_simple_8bpc_cta_itu709 + + @ycbcr422_simple_8bpc_cta_itu709.setter + def ycbcr422_simple_8bpc_cta_itu709(self, value: bool): + cf_value = make_vsc_cf(CMTVSCFormat.YCBCR_SIMPLE_422_BT709, CMTVSCBitDepth.BPC_YCBCR_8) if value else 0 + if self.__ycbcr422_simple_8bpc_cta_itu709.default_value != cf_value: + self.__ycbcr422_simple_8bpc_cta_itu709.default_value = cf_value + + @property + def ycbcr422_simple_10bpc_cta_itu709(self): + """ + Set and get YCbCr-422 Simple 10 bpc CTA ITU-709 flag support. + + Returns: + object + """ + return self.__ycbcr422_simple_10bpc_cta_itu709 + + @ycbcr422_simple_10bpc_cta_itu709.setter + def ycbcr422_simple_10bpc_cta_itu709(self, value: bool): + cf_value = make_vsc_cf(CMTVSCFormat.YCBCR_SIMPLE_422_BT709, CMTVSCBitDepth.BPC_YCBCR_10) if value else 0 + if self.__ycbcr422_simple_10bpc_cta_itu709.default_value != cf_value: + self.__ycbcr422_simple_10bpc_cta_itu709.default_value = cf_value + + @property + def ycbcr422_simple_12bpc_cta_itu709(self): + """ + Set and get YCbCr-422 Simple 12 bpc CTA ITU-709 flag support. + + Returns: + object + """ + return self.__ycbcr422_simple_12bpc_cta_itu709 + + @ycbcr422_simple_12bpc_cta_itu709.setter + def ycbcr422_simple_12bpc_cta_itu709(self, value: bool): + cf_value = make_vsc_cf(CMTVSCFormat.YCBCR_SIMPLE_422_BT709, CMTVSCBitDepth.BPC_YCBCR_12) if value else 0 + if self.__ycbcr422_simple_12bpc_cta_itu709.default_value != cf_value: + self.__ycbcr422_simple_12bpc_cta_itu709.default_value = cf_value + + @property + def ycbcr420_8bpc_cta_itu709(self): + """ + Set and get YCbCr-420 8 bpc CTA ITU-709 flag support. + + Returns: + object + """ + return self.__ycbcr420_8bpc_cta_itu709 + + @ycbcr420_8bpc_cta_itu709.setter + def ycbcr420_8bpc_cta_itu709(self, value: bool): + cf_value = make_vsc_cf(CMTVSCFormat.YCBCR_420_BT709, CMTVSCBitDepth.BPC_YCBCR_8) if value else 0 + if self.__ycbcr420_8bpc_cta_itu709.default_value != cf_value: + self.__ycbcr420_8bpc_cta_itu709.default_value = cf_value + + @property + def ycbcr420_10bpc_cta_itu709(self): + """ + Set and get YCbCr-420 10 bpc CTA ITU-709 flag support. + + Returns: + object + """ + return self.__ycbcr420_10bpc_cta_itu709 + + @ycbcr420_10bpc_cta_itu709.setter + def ycbcr420_10bpc_cta_itu709(self, value: bool): + cf_value = make_vsc_cf(CMTVSCFormat.YCBCR_420_BT709, CMTVSCBitDepth.BPC_YCBCR_10) if value else 0 + if self.__ycbcr420_10bpc_cta_itu709.default_value != cf_value: + self.__ycbcr420_10bpc_cta_itu709.default_value = cf_value + + @property + def ycbcr420_12bpc_cta_itu709(self): + """ + Set and get YCbCr-420 12 bpc CTA ITU-709 flag support. + + Returns: + object + """ + return self.__ycbcr420_12bpc_cta_itu709 + + @ycbcr420_12bpc_cta_itu709.setter + def ycbcr420_12bpc_cta_itu709(self, value: bool): + cf_value = make_vsc_cf(CMTVSCFormat.YCBCR_420_BT709, CMTVSCBitDepth.BPC_YCBCR_12) if value else 0 + if self.__ycbcr420_12bpc_cta_itu709.default_value != cf_value: + self.__ycbcr420_12bpc_cta_itu709.default_value = cf_value + + +class DscVideoModesDp14: + """ + Class `DscVideoModesDp14` describes available timings for DSC tests. + - 1920x1080 30Hz `vm_1920x1080_30hz`. + - 1920x1080 60Hz `vm_1920x1080_60hz`. + - 1920x1080 120Hz `vm_1920x1080_120hz`. + - 3840x2160 30Hz `vm_3840x2160_30hz`. + - 3840x2160 60Hz `vm_3840x2160_60hz`. + - 3840x2160 120Hz `vm_3840x2160_120hz`. + - 5120x2160 30Hz `vm_5120x2160_30hz`. + - 5120x2160 60Hz `vm_5120x2160_60hz`. + - 5120x2160 120Hz `vm_5120x2160_120hz`. + - 7680x4320 30Hz `vm_7680x4320_30hz`. + - 7680x4320 60Hz `vm_7680x4320_60hz`. + - 7680x4320 100Hz `vm_7680x4320_120hz`. + - Disable all timings `clear_all`. + - Select all timings `select_all`. + """ + def __init__(self, json_obj): + self.__id_1920x1080_30Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_0_ID"]) + self.__lc_lr_1920x1080_30Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_0_LC_LR"]) + self.__1920x1080_30Hz = Timing(json_obj["TSI_DP14_SRCCTS_DSC_VMT_0_TIMINGS"]) + + self.__id_1920x1080_60Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_1_ID"]) + self.__lc_lr_1920x1080_60Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_1_LC_LR"]) + self.__1920x1080_60Hz = Timing(json_obj["TSI_DP14_SRCCTS_DSC_VMT_1_TIMINGS"]) + + self.__id_1920x1080_120Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_2_ID"]) + self.__lc_lr_1920x1080_120Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_2_LC_LR"]) + self.__1920x1080_120Hz = Timing(json_obj["TSI_DP14_SRCCTS_DSC_VMT_2_TIMINGS"]) + + self.__id_3840x2160_30Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_3_ID"]) + self.__lc_lr_3840x2160_30Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_3_LC_LR"]) + self.__3840x2160_30Hz = Timing(json_obj["TSI_DP14_SRCCTS_DSC_VMT_3_TIMINGS"]) + + self.__id_3840x2160_60Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_4_ID"]) + self.__lc_lr_3840x2160_60Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_4_LC_LR"]) + self.__3840x2160_60Hz = Timing(json_obj["TSI_DP14_SRCCTS_DSC_VMT_4_TIMINGS"]) + + self.__id_3840x2160_120Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_5_ID"]) + self.__lc_lr_3840x2160_120Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_5_LC_LR"]) + self.__3840x2160_120Hz = Timing(json_obj["TSI_DP14_SRCCTS_DSC_VMT_5_TIMINGS"]) + + self.__id_5120x2160_30Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_6_ID"]) + self.__lc_lr_5120x2160_30Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_6_LC_LR"]) + self.__5120x2160_30Hz = Timing(json_obj["TSI_DP14_SRCCTS_DSC_VMT_6_TIMINGS"]) + + self.__id_5120x2160_60Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_7_ID"]) + self.__lc_lr_5120x2160_60Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_7_LC_LR"]) + self.__5120x2160_60Hz = Timing(json_obj["TSI_DP14_SRCCTS_DSC_VMT_7_TIMINGS"]) + + self.__id_5120x2160_120Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_8_ID"]) + self.__lc_lr_5120x2160_120Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_8_LC_LR"]) + self.__5120x2160_120Hz = Timing(json_obj["TSI_DP14_SRCCTS_DSC_VMT_8_TIMINGS"]) + + self.__id_7680x4320_30Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_9_ID"]) + self.__lc_lr_7680x4320_30Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_9_LC_LR"]) + self.__7680x4320_30Hz = Timing(json_obj["TSI_DP14_SRCCTS_DSC_VMT_9_TIMINGS"]) + + self.__id_7680x4320_60Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_10_ID"]) + self.__lc_lr_7680x4320_60Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_10_LC_LR"]) + self.__7680x4320_60Hz = Timing(json_obj["TSI_DP14_SRCCTS_DSC_VMT_10_TIMINGS"]) + + self.__id_7680x4320_100Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_11_ID"]) + self.__lc_lr_7680x4320_100Hz = Param(json_obj["TSI_DP14_SRCCTS_DSC_VMT_11_LC_LR"]) + self.__7680x4320_100Hz = Timing(json_obj["TSI_DP14_SRCCTS_DSC_VMT_11_TIMINGS"]) + + @property + def vm_1920x1080_30hz(self) -> Timing: + """ + Set and get 1920x1080 30Hz timing flag support. + + Returns: + object of bool type + """ + return self.__1920x1080_30Hz + + @property + def vm_1920x1080_60hz(self) -> Timing: + """ + Set and get 1920x1080 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__1920x1080_60Hz + + @property + def vm_1920x1080_120hz(self) -> Timing: + """ + Set and get 1920x1080 120Hz timing flag support. + + Returns: + object of bool type + """ + return self.__1920x1080_120Hz + + @property + def vm_3840x2160_30hz(self) -> Timing: + """ + Set and get 3840x2160 30Hz timing flag support. + + Returns: + object of bool type + """ + return self.__3840x2160_30Hz + + @property + def vm_3840x2160_60hz(self) -> Timing: + """ + Set and get 3840x2160 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__3840x2160_60Hz + + @property + def vm_3840x2160_120hz(self) -> Timing: + """ + Set and get 3840x2160 120Hz timing flag support. + + Returns: + object of bool type + """ + return self.__3840x2160_120Hz + + @property + def vm_5120x2160_30hz(self) -> Timing: + """ + Set and get 5120x2160 30Hz timing flag support. + + Returns: + object of bool type + """ + return self.__5120x2160_30Hz + + @property + def vm_5120x2160_60hz(self) -> Timing: + """ + Set and get 5120x2160 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__5120x2160_60Hz + + @property + def vm_5120x2160_120hz(self) -> Timing: + """ + Set and get 5120x2160 120Hz timing flag support. + + Returns: + object of bool type + """ + return self.__5120x2160_120Hz + + @property + def vm_7680x4320_30hz(self) -> Timing: + """ + Set and get 7680x4320 30Hz timing flag support. + + Returns: + object of bool type + """ + return self.__7680x4320_30Hz + + @property + def vm_7680x4320_60hz(self) -> Timing: + """ + Set and get 7680x4320 60Hz timing flag support. + + Returns: + object of bool type + """ + return self.__7680x4320_60Hz + + @property + def vm_7680x4320_100hz(self) -> Timing: + """ + Set and get 7680x4320 100Hz timing flag support. + + Returns: + object of bool type + """ + return self.__7680x4320_100Hz + + def clear_all(self): + """ + Clear (disable) all timings. + """ + self.vm_1920x1080_30hz.clear_all() + self.vm_1920x1080_60hz.clear_all() + self.vm_1920x1080_120hz.clear_all() + self.vm_3840x2160_30hz.clear_all() + self.vm_3840x2160_60hz.clear_all() + self.vm_3840x2160_120hz.clear_all() + self.vm_5120x2160_30hz.clear_all() + self.vm_5120x2160_60hz.clear_all() + self.vm_5120x2160_120hz.clear_all() + self.vm_7680x4320_30hz.clear_all() + self.vm_7680x4320_60hz.clear_all() + self.vm_7680x4320_100hz.clear_all() + + def select_all(self): + """ + Select (enable) all timings. + """ + self.vm_1920x1080_30hz.set_all() + self.vm_1920x1080_60hz.set_all() + self.vm_1920x1080_120hz.set_all() + self.vm_3840x2160_30hz.set_all() + self.vm_3840x2160_60hz.set_all() + self.vm_3840x2160_120hz.set_all() + self.vm_5120x2160_30hz.set_all() + self.vm_5120x2160_60hz.set_all() + self.vm_5120x2160_120hz.set_all() + self.vm_7680x4320_30hz.set_all() + self.vm_7680x4320_60hz.set_all() + self.vm_7680x4320_100hz.set_all() + + +class DscConfigDp14Tab: + """ + Class `DscConfigDp14Tab` allows working with settings for DP DSC LLCTS tests. + - Set and get DSC maximum slice number `dsc_max_slice`. + - Set and get DSC version `dsc_version`. + - Set and get DSC video modes `dsc_video_modes` type `DscVideoModesDp14`. + - Set and get colorimetry `ColorimetryDp14`. + """ + def __init__(self, json_obj): + self.__dsc_max_slice = Param(json_obj["TSI_DP14_SRCCTS_DSC_DUT_MAX_SLICE"]) # DscMaxSliceNumber + self.__dsc_version = Param(json_obj["TSI_DP14_SRCCTS_DSC_VERSION"]) # DscVersion + self.__dsc_video_modes = DscVideoModesDp14(json_obj) + self.__colorimetry = ColorimetryDp14(json_obj) + + @property + def dsc_max_slice(self) -> int: + """ + Set and get DSC maximum slice number. + + Returns: + object of int type + """ + return self.__dsc_max_slice.default_value + + @dsc_max_slice.setter + def dsc_max_slice(self, dsc_max_slice: int): + if dsc_max_slice not in [1, 2, 4, 8, 10, 12, 16, 20, 24]: + raise ValueError(f"DSC Max slice count available values {[1, 2, 4, 8, 10, 12, 16, 20, 24]}") + self.__dsc_max_slice.default_value = dsc_max_slice + + @property + def dsc_version(self) -> list: + """ + Set and get DSC version. + + Returns: + object of list type + """ + return [self.__dsc_version.default_value >> 16 & 0xF, self.__dsc_version.default_value & 0xF] + + @dsc_version.setter + def dsc_version(self, dsc_version: Tuple[int, int]): + version = (dsc_version[0] << 16) | dsc_version[1] + if version not in [65537, 65538]: + raise ValueError("DSC Version must be 1.1 or 1.2") + self.__dsc_version.default_value = version + + @property + def dsc_video_modes(self) -> DscVideoModesDp14: + """ + Set and get DSC video modes. + + Returns: + object of `DscVideoModesDp14` type + """ + return self.__dsc_video_modes + + @dsc_video_modes.setter + def dsc_video_modes(self, dsc_video_modes: DscVideoModesDp14): + self.__dsc_video_modes = dsc_video_modes + + @property + def colorimetry(self) -> ColorimetryDp14: + """ + Set and get colorimetry. + + Returns: + object of `ColorimetryDp14` type + """ + return self.__colorimetry + + @colorimetry.setter + def colorimetry(self, colorimetry: ColorimetryDp14): + self.__colorimetry = colorimetry diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_1a_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_1a_tests.py new file mode 100644 index 0000000..fdda225 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_1a_tests.py @@ -0,0 +1,61 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param + + +class Hdcp1ATestParam: + """ + Class `Hdcp1ATestParam` describes requirement parameters for HDCP 1A tests: + - Set and get `timeout`. Describes test timeout, in milliseconds. + - Set and get `revoke_id`. Describes Revoke ID. + - Set and get `dut_caps_flag`. Describes source DUT capabilities flags. + """ + def __init__(self, json_obj): + self.__timeout = Param(json_obj["TSI_TEST_CFG_HDCP2_1A_TIMEOUT"]) + self.__revoke_id = Param(json_obj["TSI_TEST_CFG_HDCP2_1A_REVOKEID"]) + self.__dut_caps_flag = Param(json_obj["TSI_TEST_CFG_HDCP2_1A_SRC_DUT_CAP"]) + + @property + def timeout(self) -> int: + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout + + @property + def revoke_id(self) -> int: + """ + Set and get Revoke ID. + + Returns: + object of int type + """ + return self.__revoke_id.default_value + + @revoke_id.setter + def revoke_id(self, revoke_id: list): + if len(revoke_id) == 0: + raise ValueError(f"Revoke ID list length must more than 0") + self.__revoke_id.default_value = revoke_id + + @property + def dut_caps_flag(self) -> bool: + """ + Set and get DUT caps flag support. + + Returns: + object of bool type + """ + return self.__dut_caps_flag.default_value + + @dut_caps_flag.setter + def dut_caps_flag(self, dut_caps_flag: bool): + self.__dut_caps_flag.default_value = dut_caps_flag diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_1b_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_1b_tests.py new file mode 100644 index 0000000..f8a620d --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_1b_tests.py @@ -0,0 +1,27 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param + + +class Hdcp1BTestParam: + """ + Class `Hdcp1BTestParam` describes requirement parameters for HDCP 1B tests: + - Set and get `timeout`. Describes test timeout, in milliseconds. + """ + def __init__(self, json_obj): + self.__timeout = Param(json_obj["TSI_TEST_CFG_HDCP2_1B_TIMEOUT"]) + + @property + def timeout(self): + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_2c_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_2c_tests.py new file mode 100644 index 0000000..7322ca6 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_2c_tests.py @@ -0,0 +1,27 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param + + +class Hdcp2CTestParam: + """ + Class `Hdcp2CTestParam` describes requirement parameters for HDCP 2C tests: + - Set and get `timeout`. Describes test timeout, in milliseconds. + """ + def __init__(self, json_obj): + self.__timeout = Param(json_obj["TSI_TEST_CFG_HDCP2_2C_TIMEOUT"]) + + @property + def timeout(self): + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_3a_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_3a_tests.py new file mode 100644 index 0000000..f467316 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_3a_tests.py @@ -0,0 +1,27 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param + + +class Hdcp3ATestParam: + """ + Class `Hdcp3ATestParam` describes requirement parameters for HDCP 3A tests: + - Set and get `timeout`. Describes test timeout, in milliseconds. + """ + def __init__(self, json_obj): + self.__timeout = Param(json_obj["TSI_TEST_CFG_HDCP2_3A_TIMEOUT"]) + + @property + def timeout(self): + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_3b_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_3b_tests.py new file mode 100644 index 0000000..50eb37e --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_3b_tests.py @@ -0,0 +1,27 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param + + +class Hdcp3BTestParam: + """ + Class `Hdcp3BTestParam` describes requirement parameters for HDCP 3B tests: + - Set and get `timeout`. Describes test timeout, in milliseconds. + """ + def __init__(self, json_obj): + self.__timeout = Param(json_obj["TSI_TEST_CFG_HDCP2_3B_TIMEOUT"]) + + @property + def timeout(self): + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_3c_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_3c_tests.py new file mode 100644 index 0000000..15848cf --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/hdcp_3c_tests.py @@ -0,0 +1,43 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param + + +class Hdcp3CTestParam: + """ + Class `Hdcp3CTestParam` describes requirement parameters for HDCP 3C tests: + - Set and get `timeout`. Describes test timeout, in milliseconds. + - Set and get `repeater_multiple_outputs`. Describes repeater multiple outputs (enable/disable). + """ + def __init__(self, json_obj): + self.__timeout = Param(json_obj["TSI_TEST_CFG_HDCP2_3C_TIMEOUT"]) + self.__repeater_multiple_outputs = Param(json_obj["TSI_TEST_CFG_HDCP2_3C_REPEATER_MULTIPLE_OUTPUTS"]) + + @property + def timeout(self): + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout + + @property + def repeater_multiple_outputs(self) -> bool: + """ + Set and get repeater multiple outputs (enable/disable). + + Returns: + object of bool type + """ + return self.__repeater_multiple_outputs.default_value + + @repeater_multiple_outputs.setter + def repeater_multiple_outputs(self, repeater_multiple_outputs: bool): + self.__repeater_multiple_outputs.default_value = repeater_multiple_outputs diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/hdmi_electrical_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/hdmi_electrical_tests.py new file mode 100644 index 0000000..d398cdd --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/hdmi_electrical_tests.py @@ -0,0 +1,293 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param + + +class HdmiElectricalTestParam: + """ + Class `DpElectricalTestParam` describes parameters for DP electrical tests. + - Test timeout, in milliseconds `timeout`. + - HDMI power line voltage low limit, in millivolts `power_low_limit`. + - HDMI power line voltage high limit, in millivolts `power_high_limit`. + - HDMI link line voltage low limit, in millivolts `link_low_limit`. + - HDMI link line voltage high limit, in millivolts `link_high_limit`. + - HDMI HPD logical zero voltage level, lower limit, in millivolts `hpd_zero_low_limit`. + - HDMI HPD logical zero voltage level, higher limit, in millivolts `hpd_zero_high_limit`. + - HDMI HPD logical one voltage level, lower limit, in millivolts `hpd_one_low_limit`. + - HDMI HPD logical one voltage level, higher limit, in millivolts `hpd_one_high_limit`. + - DDC Line voltage low limit, in millivolts `ddc_low_limit`. + - DDC Line voltage high limit, in millivolts `_ddc_high_limit`. + - CCE Line logical zero voltage level, lower limit, in millivolts `cec_zero_low_limit`. + - CCE Line logical zero voltage level, higher limit, in millivolts `cec_zero_high_limit`. + - CCE Line logical one voltage level, lower limit, in millivolts `cec_one_low_limit`. + - CCE Line logical one voltage level, higher limit, in millivolts `cec_one_high_limit`. + """ + def __init__(self, json_obj): + self.__timeout = Param(json_obj["TSI_HDMI_RX_TIMEOUT"]) + self.__power_low_limit = Param(json_obj["TSI_HDMI_RX_POWER_LOW_LIMIT"]) + self.__power_high_limit = Param(json_obj["TSI_HDMI_RX_POWER_HIGH_LIMIT"]) + self.__link_low_limit = Param(json_obj["TSI_HDMI_RX_LINK_LOW_LIMIT"]) + self.__link_high_limit = Param(json_obj["TSI_HDMI_RX_LINK_HIGH_LIMIT"]) + self.__hpd_zero_low_limit = Param(json_obj["TSI_HDMI_RX_HPD_ZERO_LOW_LIMIT"]) + self.__hpd_zero_high_limit = Param(json_obj["TSI_HDMI_RX_HPD_ZERO_HIGH_LIMIT"]) + self.__hpd_one_low_limit = Param(json_obj["TSI_HDMI_RX_HPD_ONE_LOW_LIMIT"]) + self.__hpd_one_high_limit = Param(json_obj["TSI_HDMI_RX_HPD_ONE_HIGHT_LIMIT"]) + self.__ddc_low_limit = Param(json_obj["TSI_HDMI_RX_DDC_LOW_LIMIT"]) + self.__ddc_high_limit = Param(json_obj["TSI_HDMI_RX_DDC_HIGH_LIMIT"]) + self.__cec_zero_low_limit = Param(json_obj["TSI_HDMI_RX_CEC_ZERO_LOW_LIMIT"]) + self.__cec_zero_high_limit = Param(json_obj["TSI_HDMI_RX_CEC_ZERO_HIGH_LIMIT"]) + self.__cec_one_low_limit = Param(json_obj["TSI_HDMI_RX_CEC_ONE_LOW_LIMIT"]) + self.__cec_one_high_limit = Param(json_obj["TSI_HDMI_RX_CEC_ONE_HIGH_LIMIT"]) + + @property + def timeout(self) -> int: + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout + + @property + def power_low_limit(self) -> int: + """ + Set and get HDMI power line voltage low limit. + + Returns: + object of int type + """ + return self.__power_low_limit.default_value + + @power_low_limit.setter + def power_low_limit(self, power_low_limit: int): + if not(self.__power_low_limit.min_value < power_low_limit < self.__power_low_limit.max_value): + raise ValueError(f"Power low limit cannot be less than {self.__power_low_limit.min_value} and more than " + f"{self.__power_low_limit.max_value}.") + self.__power_low_limit.default_value = power_low_limit + + @property + def power_high_limit(self) -> int: + """ + Set and get HDMI power line voltage high limit. + + Returns: + object of int type + """ + return self.__power_high_limit.default_value + + @power_high_limit.setter + def power_high_limit(self, power_high_limit: int): + if not(self.__power_high_limit.min_value < power_high_limit < self.__power_high_limit.max_value): + raise ValueError(f"Power high limit cannot be less than {self.__power_high_limit.min_value} and more than " + f"{self.__power_high_limit.max_value}.") + self.__power_high_limit.default_value = power_high_limit + + @property + def link_low_limit(self) -> int: + """ + Set and get HDMI link line voltage low limit. + + Returns: + object of int type + """ + return self.__link_low_limit.default_value + + @link_low_limit.setter + def link_low_limit(self, link_low_limit: int): + if not(self.__link_low_limit.min_value < link_low_limit < self.__link_low_limit.max_value): + raise ValueError(f"Link low limit cannot be less than {self.__link_low_limit.min_value} and more than " + f"{self.__link_low_limit.max_value}.") + self.__link_low_limit.default_value = link_low_limit + + @property + def link_high_limit(self) -> int: + """ + Set and get HDMI link line voltage high limit. + + Returns: + object of int type + """ + return self.__link_high_limit.default_value + + @link_high_limit.setter + def link_high_limit(self, link_high_limit: int): + if not(self.__link_high_limit.min_value < link_high_limit < self.__link_high_limit.max_value): + raise ValueError(f"Link high limit cannot be less than {self.__link_high_limit.min_value} and more than " + f"{self.__link_high_limit.max_value}.") + self.__link_high_limit.default_value = link_high_limit + + @property + def hpd_zero_low_limit(self) -> int: + """ + Set and get HDMI HPD logical zero voltage level, lower limit. + + Returns: + object of int type + """ + return self.__hpd_zero_low_limit.default_value + + @hpd_zero_low_limit.setter + def hpd_zero_low_limit(self, hpd_zero_low_limit: int): + if not(self.__hpd_zero_low_limit.min_value < hpd_zero_low_limit < self.__hpd_zero_low_limit.max_value): + raise ValueError(f"HPD zero low limit cannot be less than {self.__hpd_zero_low_limit.min_value} " + f"and more than {self.__hpd_zero_low_limit.max_value}.") + self.__hpd_zero_low_limit.default_value = hpd_zero_low_limit + + @property + def hpd_zero_high_limit(self) -> int: + """ + Set and get HDMI HPD logical zero voltage level, higher limit. + + Returns: + object of int type + """ + return self.__hpd_zero_high_limit.default_value + + @hpd_zero_high_limit.setter + def hpd_zero_high_limit(self, hpd_zero_high_limit: int): + if not(self.__hpd_zero_high_limit.min_value < hpd_zero_high_limit < self.__hpd_zero_high_limit.max_value): + raise ValueError(f"HPD zero high limit cannot be less than {self.__hpd_zero_high_limit.min_value} " + f"and more than {self.__hpd_zero_high_limit.max_value}.") + self.__hpd_zero_high_limit.default_value = hpd_zero_high_limit + + @property + def hpd_one_low_limit(self) -> int: + """ + Set and get HDMI HPD logical one voltage level, lower limit. + + Returns: + object of int type + """ + return self.__hpd_one_low_limit.default_value + + @hpd_one_low_limit.setter + def hpd_one_low_limit(self, hpd_one_low_limit: int): + if not(self.__hpd_one_low_limit.min_value < hpd_one_low_limit < self.__hpd_one_low_limit.max_value): + raise ValueError(f"HPD one low limit cannot be less than {self.__hpd_one_low_limit.min_value} " + f"and more than {self.__hpd_one_low_limit.max_value}.") + self.__hpd_one_low_limit.default_value = hpd_one_low_limit + + @property + def hpd_one_high_limit(self) -> int: + """ + Set and get HDMI HPD logical one voltage level, higher limit. + + Returns: + object of int type + """ + return self.__hpd_one_high_limit.default_value + + @hpd_one_high_limit.setter + def hpd_one_high_limit(self, hpd_one_high_limit: int): + if not(self.__hpd_one_high_limit.min_value < hpd_one_high_limit < self.__hpd_one_high_limit.max_value): + raise ValueError(f"HPD one high limit cannot be less than {self.__hpd_one_high_limit.min_value} " + f"and more than {self.__hpd_one_high_limit.max_value}.") + self.__hpd_one_high_limit.default_value = hpd_one_high_limit + + @property + def ddc_low_limit(self) -> int: + """ + Set and get DDC Line voltage low limit. + + Returns: + object of int type + """ + return self.__ddc_low_limit.default_value + + @ddc_low_limit.setter + def ddc_low_limit(self, ddc_low_limit: int): + if not(self.__ddc_low_limit.min_value < ddc_low_limit < self.__ddc_low_limit.max_value): + raise ValueError(f"DDC low limit cannot be less than {self.__ddc_low_limit.min_value} and more than " + f"{self.__ddc_low_limit.max_value}.") + self.__ddc_low_limit.default_value = ddc_low_limit + + @property + def ddc_high_limit(self) -> int: + """ + Set and get DDC Line voltage high limit. + + Returns: + object of int type + """ + return self.__ddc_high_limit.default_value + + @ddc_high_limit.setter + def ddc_high_limit(self, ddc_high_limit: int): + if not(self.__ddc_high_limit.min_value < ddc_high_limit < self.__ddc_high_limit.max_value): + raise ValueError(f"DDC high limit cannot be less than {self.__ddc_high_limit.min_value} and more than " + f"{self.__ddc_high_limit.max_value}.") + self.__ddc_high_limit.default_value = ddc_high_limit + + @property + def cec_zero_low_limit(self) -> int: + """ + Set and get CCE Line logical zero voltage level, lower limit. + + Returns: + object of int type + """ + return self.__cec_zero_low_limit.default_value + + @cec_zero_low_limit.setter + def cec_zero_low_limit(self, cec_zero_low_limit: int): + if not(self.__cec_zero_low_limit.min_value < cec_zero_low_limit < self.__cec_zero_low_limit.max_value): + raise ValueError(f"CEC zero low limit cannot be less than {self.__cec_zero_low_limit.min_value} " + f"and more than {self.__cec_zero_low_limit.max_value}.") + self.__cec_zero_low_limit.default_value = cec_zero_low_limit + + @property + def cec_zero_high_limit(self) -> int: + """ + Set and get CCE Line logical zero voltage level, higher limit. + + Returns: + object of int type + """ + return self.__cec_zero_high_limit.default_value + + @cec_zero_high_limit.setter + def cec_zero_high_limit(self, cec_zero_high_limit: int): + if not(self.__cec_zero_high_limit.min_value < cec_zero_high_limit < self.__cec_zero_high_limit.max_value): + raise ValueError(f"CEC zero high limit cannot be less than {self.__cec_zero_high_limit.min_value} " + f"and more than {self.__cec_zero_high_limit.max_value}.") + self.__cec_zero_high_limit.default_value = cec_zero_high_limit + + @property + def cec_one_low_limit(self) -> int: + """ + Set and get CCE Line logical one voltage level, lower limit. + + Returns: + object of int type + """ + return self.__cec_one_low_limit.default_value + + @cec_one_low_limit.setter + def cec_one_low_limit(self, cec_one_low_limit: int): + if not(self.__cec_one_low_limit.min_value < cec_one_low_limit < self.__cec_one_low_limit.max_value): + raise ValueError(f"CEC one low limit cannot be less than {self.__cec_one_low_limit.min_value} " + f"and more than {self.__cec_one_low_limit.max_value}.") + self.__cec_one_low_limit.default_value = cec_one_low_limit + + @property + def cec_one_high_limit(self) -> int: + """ + Set and get CCE Line logical one voltage level, higher limit. + + Returns: + object of int type + """ + return self.__cec_one_high_limit.default_value + + @cec_one_high_limit.setter + def cec_one_high_limit(self, cec_one_high_limit: int): + if not(self.__cec_one_high_limit.min_value < cec_one_high_limit < self.__cec_one_high_limit.max_value): + raise ValueError(f"CEC one high limit cannot be less than {self.__cec_one_high_limit.min_value} " + f"and more than {self.__cec_one_high_limit.max_value}.") + self.__cec_one_high_limit.default_value = cec_one_high_limit diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/hdmi_sink_cable_check_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/hdmi_sink_cable_check_tests.py new file mode 100644 index 0000000..55e8b13 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/hdmi_sink_cable_check_tests.py @@ -0,0 +1,217 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param +from .hdmi_sink_tests import HdmiFrlRate +from enum import IntEnum + + +class CableTestMode(IntEnum): + HighSpeed = 0 + LowSpeed = 1 + All = 2 + + +class LowSpeedLineSelection(Param): + """ + Class `LowSpeedLineSelection` defines variants of tests: + - Voltage swing level 3 (1.2V) supported `voltage_swing_supported`. + - Pre-emphasis level 3 (9.5dB) supported `pre_emphasis_supported`. + + """ + def __init__(self, json_obj): + super().__init__(json_obj) + + @property + def hpd(self) -> bool: + """ + Configure if HPD line will be tested. + + Returns: + object of bool type + """ + return self._get_by_bitmask(0, bool) + + @hpd.setter + def hpd(self, hpd_test: bool): + self._set_by_bitmask(hpd_test, 0) + + @property + def i2c(self) -> bool: + """ + Configure if I2C line will be tested. + + Returns: + object of bool type + """ + return self._get_by_bitmask(1, bool) + + @i2c.setter + def i2c(self, i2c_test: bool): + self._set_by_bitmask(i2c_test, 1) + + @property + def cec(self) -> bool: + """ + Configure if CEC line will be tested. + + Returns: + object of bool type + """ + return self._get_by_bitmask(2, bool) + + @cec.setter + def cec(self, cec_test: bool): + self._set_by_bitmask(cec_test, 2) + + @property + def voltage_5v(self) -> bool: + """ + Configure if 5V line will be tested. + + Returns: + object of bool type + """ + return self._get_by_bitmask(3, bool) + + @voltage_5v.setter + def voltage_5v(self, voltage_5v_test: bool): + self._set_by_bitmask(voltage_5v_test, 3) + + @property + def utility(self) -> bool: + """ + Configure if utility line will be tested. + + Returns: + object of bool type + """ + return self._get_by_bitmask(4, bool) + + @utility.setter + def utility(self, utility_test: bool): + self._set_by_bitmask(utility_test, 4) + + +class HdmiSinkCableCheckTestParam: + """ + Class `HdmiSinkDUTTestParam` allows working with parameters from HDMI DSC source part. + - Set and get test timeout `test_timeout`. + - Seg and get test mode `test_mode` type `CableTestMode`. + - Set and get MIN FRL rate `min_frl_rate` type `HdmiFrlRate`. + - Set and get MAX FRL rate `max_frl_rate` type `HdmiFrlRate`. + - Set and get amount of allowed errors per lane `allowed_errors_per_lane`. + - Set and get errors capture timeout `errors_capture_timeout `. + """ + def __init__(self, json_obj): + self.__test_timeout = Param(json_obj["TSI_HDMI_SNKCTS_CABLE_TIMEOUT"]) + self.__test_mode = Param(json_obj["TSI_HDMI_SNKCTS_CABLE_TEST_MODE"]) + self.__min_frl_rate = Param(json_obj["TSI_HDMI_SNKCTS_CABLE_MIN_FRL_RATE"]) + self.__max_frl_rate = Param(json_obj["TSI_HDMI_SNKCTS_CABLE_MAX_FRL_RATE"]) + self.__allowed_errors_per_lane = Param(json_obj["TSI_HDMI_SNKCTS_CABLE_ERROR_PER_LANE"]) + self.__errors_capture_timeout = Param(json_obj["TSI_HDMI_SNKCTS_CABLE_CAPTURE_TIMEOUT"]) + self.__low_speed_line_selection = LowSpeedLineSelection(json_obj["TSI_HDMI_SNKCTS_CABLE_LOW_SPEED_LINE"]) + + @property + def test_timeout(self) -> int: + """ + Set and get test timeout, in seconds. + + Returns: + object of int type + """ + return self.__test_timeout.default_value + + @test_timeout.setter + def test_timeout(self, test_timeout: int): + if not(self.__test_timeout.min_value < test_timeout < self.__test_timeout.max_value): + raise ValueError(f"Test timeout cannot be less than {self.__test_timeout.min_value} and more than " + f"{self.__test_timeout.max_value}.") + self.__test_timeout.default_value = test_timeout + + @property + def test_mode(self) -> CableTestMode: + """ + Set and get test mode. + + Returns: + object of `CableTestMode` type + """ + return CableTestMode(self.__test_mode.default_value) + + @test_mode.setter + def test_mode(self, test_mode: CableTestMode): + self.__test_mode.default_value = test_mode.value + + @property + def min_frl_rate(self) -> HdmiFrlRate: + """ + Set and get the minimum FRL rate supported by the cable. + + Returns: + object of `HdmiFrlRate` type + """ + return HdmiFrlRate(self.__min_frl_rate.default_value) + + @min_frl_rate.setter + def min_frl_rate(self, min_frl_rate: HdmiFrlRate): + self.__min_frl_rate.default_value = min_frl_rate.value + + @property + def max_frl_rate(self) -> HdmiFrlRate: + """ + Set and get the maximum FRL rate supported by the cable. + + Returns: + object of `HdmiFrlRate` type + """ + return HdmiFrlRate(self.__max_frl_rate.default_value) + + @max_frl_rate.setter + def max_frl_rate(self, max_frl_rate: HdmiFrlRate): + self.__max_frl_rate.default_value = max_frl_rate.value + + @property + def allowed_errors_per_lane(self) -> int: + """ + Set and get allowed errors per lane. + + Returns: + object of int type + """ + return self.__allowed_errors_per_lane.default_value + + @allowed_errors_per_lane.setter + def allowed_errors_per_lane(self, allowed_errors_per_lane: int): + if not(self.__allowed_errors_per_lane.min_value < allowed_errors_per_lane < self.__allowed_errors_per_lane.max_value): + raise ValueError(f"Amount of allowed errors per lane cannot be less than {self.__allowed_errors_per_lane.min_value} and more than " + f"{self.__allowed_errors_per_lane.max_value}.") + self.__allowed_errors_per_lane.default_value = allowed_errors_per_lane + + @property + def errors_capture_timeout(self) -> int: + """ + Set and get errors capture timeout, in seconds. + + Returns: + object of int type + """ + return self.__errors_capture_timeout.default_value + + @errors_capture_timeout.setter + def errors_capture_timeout(self, errors_capture_timeout: int): + if not(self.__errors_capture_timeout.min_value < errors_capture_timeout < self.__errors_capture_timeout.max_value): + raise ValueError(f"Errors capture timeout cannot be less than {self.__errors_capture_timeout.min_value} and more than " + f"{self.__errors_capture_timeout.max_value}.") + self.__errors_capture_timeout.default_value = errors_capture_timeout + + @property + def low_speed_line_selection(self) -> LowSpeedLineSelection: + """ + Set and get low speed lines for testing. + + Returns: + object `LowSpeedLineSelection` + """ + return self.__low_speed_line_selection + + @low_speed_line_selection.setter + def low_speed_line_selection(self, low_speed_line_selection: LowSpeedLineSelection): + self.__low_speed_line_selection = low_speed_line_selection diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/hdmi_sink_continuity_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/hdmi_sink_continuity_tests.py new file mode 100644 index 0000000..c120e92 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/hdmi_sink_continuity_tests.py @@ -0,0 +1,120 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param + + +class HdmiSinkContinuityDUTTestParam: + """ + Class `HdmiSinkContinuityDUTTestParam` allows working with parameters from HDMI Continuity part. + - Set and get total test time `test_time`, in seconds. Default value is 60s. + - Set and get status period `status_period`, in seconds. + - Set and get stop flag `stop_flag`. Stop testing when status fail. + - Set and get flag of enabling scdc version `enable_scdc_version`. Enable to check SCDC version. + - Set and get flag of enabling scdc status `enable_scdc_status`. Enable to check SCDC status flag. + - Set and get flag of enabling scdc error count `enable_scdc_error_count`. Enable to check SCDC error counters. + - Set and get SDCD error counter fail threshold `scdc_error_count`. Default value is 10. + """ + def __init__(self, json_obj): + self.__test_time = Param(json_obj["TSI_HDMI_SNKCTS_TEST_TIME"]) + self.__status_period = Param(json_obj["TSI_HDMI_SNKCTS_STATUS_PERIOD"]) + self.__stop_flag = Param(json_obj["TSI_HDMI_SNKCTS_STOP_TESTING"]) + self.__enable_scdc_version = Param(json_obj["TSI_HDMI_SNKCTS_SCDC_VERSION"]) + self.__enable_scdc_status = Param(json_obj["TSI_HDMI_SNKCTS_SCDC_STATUS"]) + self.__enable_scdc_error_count = Param(json_obj["TSI_HDMI_SNKCTS_SCDC_ERROR"]) + self.__scdc_error_count = Param(json_obj["TSI_HDMI_SNKCTS_SCDC_ERROR_COUNTER"]) + + @property + def test_time(self) -> int: + """ + Set and get test time. + + Returns: + object of `int` type + """ + return self.__test_time.default_value + + @test_time.setter + def test_time(self, test_time: int): + self.__test_time.default_value = test_time + + @property + def status_period(self) -> int: + """ + Set and get status period. + + Returns: + object of `int` type + """ + return self.__status_period.default_value + + @status_period.setter + def status_period(self, status_period: int): + self.__status_period.default_value = status_period + + @property + def stop_flag(self) -> bool: + """ + Set and get flag of stop testing when status fail. + + Returns: + object of `bool` type + """ + return self.__stop_flag.default_value + + @stop_flag.setter + def stop_flag(self, stop_flag: bool): + self.__stop_flag.default_value = int(stop_flag) + + @property + def enable_scdc_version(self) -> int: + """ + Set and get flag of enabling scdc version. + + Returns: + object of `bool` type + """ + return self.__enable_scdc_version.default_value + + @enable_scdc_version.setter + def enable_scdc_version(self, enable_scdc_version: bool): + self.__enable_scdc_version.default_value = int(enable_scdc_version) + + @property + def enable_scdc_status(self) -> bool: + """ + Set and get flag of enabling scdc status. + + Returns: + object of `bool` type + """ + return self.__test_time.default_value + + @enable_scdc_status.setter + def enable_scdc_status(self, enable_scdc_status: bool): + self.__enable_scdc_status.default_value = int(enable_scdc_status) + + @property + def enable_scdc_error_count(self) -> bool: + """ + Set and get flag of enabling scdc error count. + + Returns: + object of `int` type + """ + return self.__enable_scdc_error_count.default_value + + @enable_scdc_error_count.setter + def enable_scdc_error_count(self, enable_scdc_error_count: bool): + self.__enable_scdc_error_count.default_value = int(enable_scdc_error_count) + + @property + def scdc_error_count(self) -> int: + """ + Set and get scdc error count. + + Returns: + object of `int` type + """ + return self.__scdc_error_count.default_value + + @scdc_error_count.setter + def scdc_error_count(self, error_count: int): + self.__scdc_error_count.default_value = error_count diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/hdmi_sink_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/hdmi_sink_tests.py new file mode 100644 index 0000000..f074229 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/hdmi_sink_tests.py @@ -0,0 +1,1172 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param, param_by_ci_name +from enum import IntEnum + + +class HdmiTestMode(IntEnum): + ForceVICoutput = 0 + CTStest = 1 + + +class HdmiFrlRate(IntEnum): + Mode_Disable = 0 + Mode_3lanes_3gbps = 1 + Mode_3lanes_6gbps = 2 + Mode_4lanes_6gbps = 3 + Mode_4lanes_8gbps = 4 + Mode_4lanes_10gbps = 5 + Mode_4lanes_12gbps = 6 + + +class HdmiAvailableVideoModes: + + def __init__(self, param_1: Param, param_2: Param): + self.__param_1 = param_1 + self.__param_2 = param_2 + + @property + def T_2560x1080_100hz_vic_91(self) -> bool: + """ + Set and get state of support timing VIC 91 2560x1080 100Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(0) + + @T_2560x1080_100hz_vic_91.setter + def T_2560x1080_100hz_vic_91(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=0) + + @property + def T_2560x1080_120hz_vic_92(self) -> bool: + """ + Set and get state of support timing VIC 92 2560x1080 120Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(1) + + @T_2560x1080_120hz_vic_92.setter + def T_2560x1080_120hz_vic_92(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=1) + + @property + def T_1920x1080_120hz_vic_63(self) -> bool: + """ + Set and get state of support timing VIC 63 1920x1080 120Hz (16:9). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(2) + + @T_1920x1080_120hz_vic_63.setter + def T_1920x1080_120hz_vic_63(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=2) + + @property + def T_1920x1080_100hz_vic_64(self) -> bool: + """ + Set and get state of support timing VIC 64 1920x1080 100Hz (16:9). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(3) + + @T_1920x1080_100hz_vic_64.setter + def T_1920x1080_100hz_vic_64(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=3) + + @property + def T_1920x1080_100hz_vic_77(self) -> bool: + """ + Set and get state of support timing VIC 77 1920x1080 100Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(4) + + @T_1920x1080_100hz_vic_77.setter + def T_1920x1080_100hz_vic_77(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=4) + + @property + def T_1920x1080_120hz_vic_78(self) -> bool: + """ + Set and get state of support timing VIC 78 1920x1080 120Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(5) + + @T_1920x1080_120hz_vic_78.setter + def T_1920x1080_120hz_vic_78(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=5) + + @property + def T_3840x2160_24hz_vic_93(self) -> bool: + """ + Set and get state of support timing VIC 93 3840x2160 24Hz (16:9). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(6) + + @T_3840x2160_24hz_vic_93.setter + def T_3840x2160_24hz_vic_93(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=6) + + @property + def T_3840x2160_25hz_vic_94(self) -> bool: + """ + Set and get state of support timing VIC 94 3840x2160 25Hz (16:9). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(7) + + @T_3840x2160_25hz_vic_94.setter + def T_3840x2160_25hz_vic_94(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=7) + + @property + def T_3840x2160_30hz_vic_95(self) -> bool: + """ + Set and get state of support timing VIC 95 3840x2160 30Hz (16:9). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(8) + + @T_3840x2160_30hz_vic_95.setter + def T_3840x2160_30hz_vic_95(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=8) + + @property + def T_3840x2160_50hz_vic_96(self) -> bool: + """ + Set and get state of support timing VIC 96 3840x2160 50Hz (16:9). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(9) + + @T_3840x2160_50hz_vic_96.setter + def T_3840x2160_50hz_vic_96(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=9) + + @property + def T_3840x2160_60hz_vic_97(self) -> bool: + """ + Set and get state of support timing VIC 97 3840x2160 60Hz (16:9). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(10) + + @T_3840x2160_60hz_vic_97.setter + def T_3840x2160_60hz_vic_97(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=10) + + @property + def T_4096x2160_24hz_vic_98(self) -> bool: + """ + Set and get state of support timing VIC 98 4096x2160 24Hz (256:135). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(11) + + @T_4096x2160_24hz_vic_98.setter + def T_4096x2160_24hz_vic_98(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=11) + + @property + def T_4096x2160_25hz_vic_99(self) -> bool: + """ + Set and get state of support timing VIC 99 4096x2160 25Hz (256:135). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(12) + + @T_4096x2160_25hz_vic_99.setter + def T_4096x2160_25hz_vic_99(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=12) + + @property + def T_4096x2160_30hz_vic_100(self) -> bool: + """ + Set and get state of support timing VIC 100 4096x2160 30Hz (256:135). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(13) + + @T_4096x2160_30hz_vic_100.setter + def T_4096x2160_30hz_vic_100(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=13) + + @property + def T_4096x2160_50hz_vic_101(self) -> bool: + """ + Set and get state of support timing VIC 101 4096x2160 50Hz (256:135). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(14) + + @T_4096x2160_50hz_vic_101.setter + def T_4096x2160_50hz_vic_101(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=14) + + @property + def T_4096x2160_60hz_vic_102(self) -> bool: + """ + Set and get state of support timing VIC 102 4096x2160 60Hz (256:135). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(15) + + @T_4096x2160_60hz_vic_102.setter + def T_4096x2160_60hz_vic_102(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=15) + + @property + def T_3840x2160_24hz_vic_103(self) -> bool: + """ + Set and get state of support timing VIC 103 3840x2160 24Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(16) + + @T_3840x2160_24hz_vic_103.setter + def T_3840x2160_24hz_vic_103(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=16) + + @property + def T_3840x2160_25hz_vic_104(self) -> bool: + """ + Set and get state of support timing VIC 104 3840x2160 25Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(17) + + @T_3840x2160_25hz_vic_104.setter + def T_3840x2160_25hz_vic_104(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=17) + + @property + def T_3840x2160_30hz_vic_105(self) -> bool: + """ + Set and get state of support timing VIC 105 3840x2160 30Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(18) + + @T_3840x2160_30hz_vic_105.setter + def T_3840x2160_30hz_vic_105(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=18) + + @property + def T_3840x2160_50hz_vic_106(self) -> bool: + """ + Set and get state of support timing VIC 106 3840x2160 50Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(19) + + @T_3840x2160_50hz_vic_106.setter + def T_3840x2160_50hz_vic_106(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=19) + + @property + def T_3840x2160_60hz_vic_107(self) -> bool: + """ + Set and get state of support timing VIC 107 3840x2160 60Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(20) + + @T_3840x2160_60hz_vic_107.setter + def T_3840x2160_60hz_vic_107(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=20) + + @property + def T_3840x2160_48hz_vic_114(self) -> bool: + """ + Set and get state of support timing VIC 114 3840x2160 48Hz (16:9). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(21) + + @T_3840x2160_48hz_vic_114.setter + def T_3840x2160_48hz_vic_114(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=21) + + @property + def T_4096x2160_48hz_vic_115(self) -> bool: + """ + Set and get state of support timing VIC 115 4096x2160 48Hz (256:135). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(22) + + @T_4096x2160_48hz_vic_115.setter + def T_4096x2160_48hz_vic_115(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=22) + + @property + def T_3840x2160_48hz_vic_116(self) -> bool: + """ + Set and get state of support timing VIC 116 3840x2160 48Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(23) + + @T_3840x2160_48hz_vic_116.setter + def T_3840x2160_48hz_vic_116(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=23) + + @property + def T_3840x2160_100hz_vic_117(self) -> bool: + """ + Set and get state of support timing VIC 117 3840x2160 100Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(24) + + @T_3840x2160_100hz_vic_117.setter + def T_3840x2160_100hz_vic_117(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=24) + + @property + def T_3840x2160_120hz_vic_118(self) -> bool: + """ + Set and get state of support timing VIC 118 3840x2160 120Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(25) + + @T_3840x2160_120hz_vic_118.setter + def T_3840x2160_120hz_vic_118(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=25) + + @property + def T_3840x2160_100hz_vic_119(self) -> bool: + """ + Set and get state of support timing VIC 119 3840x2160 100Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(26) + + @T_3840x2160_100hz_vic_119.setter + def T_3840x2160_100hz_vic_119(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=26) + + @property + def T_3840x2160_120hz_vic_120(self) -> bool: + """ + Set and get state of support timing VIC 120 3840x2160 120Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(27) + + @T_3840x2160_120hz_vic_120.setter + def T_3840x2160_120hz_vic_120(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=27) + + @property + def T_5120x2160_24hz_vic_121(self) -> bool: + """ + Set and get state of support timing VIC 121 5120x2160 24Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(28) + + @T_5120x2160_24hz_vic_121.setter + def T_5120x2160_24hz_vic_121(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=28) + + @property + def T_5120x2160_25hz_vic_122(self) -> bool: + """ + Set and get state of support timing VIC 122 5120x2160 25Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(29) + + @T_5120x2160_25hz_vic_122.setter + def T_5120x2160_25hz_vic_122(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=29) + + @property + def T_5120x2160_30hz_vic_123(self) -> bool: + """ + Set and get state of support timing VIC 123 5120x2160 30Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(30) + + @T_5120x2160_30hz_vic_123.setter + def T_5120x2160_30hz_vic_123(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=30) + + @property + def T_5120x2160_48hz_vic_124(self) -> bool: + """ + Set and get state of support timing VIC 124 5120x2160 48Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_1._get_by_bit_number(31) + + @T_5120x2160_48hz_vic_124.setter + def T_5120x2160_48hz_vic_124(self, state: bool): + self.__param_1._set_by_bit_number(state=state, value=1, bit_number=31) + + @property + def T_5120x2160_50hz_vic_125(self) -> bool: + """ + Set and get state of support timing VIC 125 5120x2160 50Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(0) + + @T_5120x2160_50hz_vic_125.setter + def T_5120x2160_50hz_vic_125(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=0) + + @property + def T_5120x2160_60hz_vic_126(self) -> bool: + """ + Set and get state of support timing VIC 126 5120x2160 60Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(1) + + @T_5120x2160_60hz_vic_126.setter + def T_5120x2160_60hz_vic_126(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=1) + + @property + def T_5120x2160_100hz_vic_127(self) -> bool: + """ + Set and get state of support timing VIC 127 5120x2160 100Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(2) + + @T_5120x2160_100hz_vic_127.setter + def T_5120x2160_100hz_vic_127(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=2) + + @property + def T_5120x2160_120hz_vic_193(self) -> bool: + """ + Set and get state of support timing VIC 193 5120x2160 120Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(3) + + @T_5120x2160_120hz_vic_193.setter + def T_5120x2160_120hz_vic_193(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=3) + + @property + def T_7680x4320_24hz_vic_194(self) -> bool: + """ + Set and get state of support timing VIC 194 7680x4320 24Hz (16:9). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(4) + + @T_7680x4320_24hz_vic_194.setter + def T_7680x4320_24hz_vic_194(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=4) + + @property + def T_7680x4320_25hz_vic_195(self) -> bool: + """ + Set and get state of support timing VIC 195 7680x4320 25Hz (16:9). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(5) + + @T_7680x4320_25hz_vic_195.setter + def T_7680x4320_25hz_vic_195(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=5) + + @property + def T_7680x4320_30hz_vic_196(self) -> bool: + """ + Set and get state of support timing VIC 196 7680x4320 30Hz (16:9). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(6) + + @T_7680x4320_30hz_vic_196.setter + def T_7680x4320_30hz_vic_196(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=6) + + @property + def T_7680x4320_48hz_vic_197(self) -> bool: + """ + Set and get state of support timing VIC 197 7680x4320 48Hz (16:9). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(7) + + @T_7680x4320_48hz_vic_197.setter + def T_7680x4320_48hz_vic_197(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=7) + + @property + def T_7680x4320_50hz_vic_198(self) -> bool: + """ + Set and get state of support timing VIC 198 7680x4320 50Hz (16:9). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(8) + + @T_7680x4320_50hz_vic_198.setter + def T_7680x4320_50hz_vic_198(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=8) + + @property + def T_7680x4320_60hz_vic_199(self) -> bool: + """ + Set and get state of support timing VIC 199 7680x4320 60Hz (16:9). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(9) + + @T_7680x4320_60hz_vic_199.setter + def T_7680x4320_60hz_vic_199(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=9) + + @property + def T_7680x4320_100hz_vic_200(self) -> bool: + """ + Set and get state of support timing VIC 200 7680x4320 100Hz (16:9). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(10) + + @T_7680x4320_100hz_vic_200.setter + def T_7680x4320_100hz_vic_200(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=10) + + @property + def T_7680x4320_120hz_vic_201(self) -> bool: + """ + Set and get state of support timing VIC 201 7680x4320 120Hz (16:9). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(11) + + @T_7680x4320_120hz_vic_201.setter + def T_7680x4320_120hz_vic_201(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=11) + + @property + def T_7680x4320_24hz_vic_202(self) -> bool: + """ + Set and get state of support timing VIC 202 7680x4320 24Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(12) + + @T_7680x4320_24hz_vic_202.setter + def T_7680x4320_24hz_vic_202(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=12) + + @property + def T_7680x4320_25hz_vic_203(self) -> bool: + """ + Set and get state of support timing VIC 203 7680x4320 25Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(13) + + @T_7680x4320_25hz_vic_203.setter + def T_7680x4320_25hz_vic_203(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=13) + + @property + def T_7680x4320_30hz_vic_204(self) -> bool: + """ + Set and get state of support timing VIC 204 7680x4320 30Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(14) + + @T_7680x4320_30hz_vic_204.setter + def T_7680x4320_30hz_vic_204(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=14) + + @property + def T_7680x4320_48hz_vic_205(self) -> bool: + """ + Set and get state of support timing VIC 205 7680x4320 48Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(15) + + @T_7680x4320_48hz_vic_205.setter + def T_7680x4320_48hz_vic_205(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=15) + + @property + def T_7680x4320_50hz_vic_206(self) -> bool: + """ + Set and get state of support timing VIC 206 7680x4320 50Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(16) + + @T_7680x4320_50hz_vic_206.setter + def T_7680x4320_50hz_vic_206(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=16) + + @property + def T_7680x4320_60hz_vic_207(self) -> bool: + """ + Set and get state of support timing VIC 207 7680x4320 60Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(17) + + @T_7680x4320_60hz_vic_207.setter + def T_7680x4320_60hz_vic_207(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=17) + + @property + def T_7680x4320_100hz_vic_208(self) -> bool: + """ + Set and get state of support timing VIC 208 7680x4320 100Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(18) + + @T_7680x4320_100hz_vic_208.setter + def T_7680x4320_100hz_vic_208(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=18) + + @property + def T_7680x4320_120hz_vic_209(self) -> bool: + """ + Set and get state of support timing VIC 209 7680x4320 120Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(19) + + @T_7680x4320_120hz_vic_209.setter + def T_7680x4320_120hz_vic_209(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=19) + + @property + def T_10240x4320_24hz_vic_210(self) -> bool: + """ + Set and get state of support timing VIC 210 10240x4320 24Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(20) + + @T_10240x4320_24hz_vic_210.setter + def T_10240x4320_24hz_vic_210(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=20) + + @property + def T_10240x4320_25hz_vic_211(self) -> bool: + """ + Set and get state of support timing VIC 211 10240x4320 25Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(21) + + @T_10240x4320_25hz_vic_211.setter + def T_10240x4320_25hz_vic_211(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=21) + + @property + def T_10240x4320_30hz_vic_212(self) -> bool: + """ + Set and get state of support timing VIC 212 10240x4320 30Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(22) + + @T_10240x4320_30hz_vic_212.setter + def T_10240x4320_30hz_vic_212(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=22) + + @property + def T_10240x4320_48hz_vic_213(self) -> bool: + """ + Set and get state of support timing VIC 213 10240x4320 48Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(23) + + @T_10240x4320_48hz_vic_213.setter + def T_10240x4320_48hz_vic_213(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=23) + + @property + def T_10240x4320_50hz_vic_214(self) -> bool: + """ + Set and get state of support timing VIC 214 10240x4320 50Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(24) + + @T_10240x4320_50hz_vic_214.setter + def T_10240x4320_50hz_vic_214(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=24) + + @property + def T_10240x4320_60hz_vic_215(self) -> bool: + """ + Set and get state of support timing VIC 215 10240x4320 60Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(25) + + @T_10240x4320_60hz_vic_215.setter + def T_10240x4320_60hz_vic_215(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=25) + + @property + def T_10240x4320_100hz_vic_216(self) -> bool: + """ + Set and get state of support timing VIC 216 10240x4320 10Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(26) + + @T_10240x4320_100hz_vic_216.setter + def T_10240x4320_100hz_vic_216(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=26) + + @property + def T_10240x4320_120hz_vic_217(self) -> bool: + """ + Set and get state of support timing VIC 217 10240x4320 120Hz (64:27). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(27) + + @T_10240x4320_120hz_vic_217.setter + def T_10240x4320_120hz_vic_217(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=27) + + @property + def T_4096x2160_100hz_vic_218(self) -> bool: + """ + Set and get state of support timing VIC 218 4096x2160 100Hz (256:135). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(28) + + @T_4096x2160_100hz_vic_218.setter + def T_4096x2160_100hz_vic_218(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=28) + + @property + def T_4096x2160_120hz_vic_219(self) -> bool: + """ + Set and get state of support timing VIC 219 4096x2160 120Hz (256:135). + + Returns: + object of bool type + """ + return self.__param_2._get_by_bit_number(29) + + @T_4096x2160_120hz_vic_219.setter + def T_4096x2160_120hz_vic_219(self, state: bool): + self.__param_2._set_by_bit_number(state=state, value=1, bit_number=29) + + def clear_all(self): + self.__param_1.default_value = 0 + self.__param_2.default_value = 0 + + +class HdmiDutCaps: + """ + Class `DutCapsDp14` defines the DUT capabilities and allows setting: + - Set and get state of sink DSC support `is_sink_dsc_support`. + - Set and get state of sink DSC 10 BPC support `is_sink_dsc_10bpc`. + - Set and get state of sink DSC 12 BPC support `is_sink_dsc_12bpc`. + - Set and get state of sink Native 420 support `is_sink_dsc_native_420`. + - Set and get state of sink Native 420 support `is_sink_dsc_16bpc`. + - Set and get state of sink Native 420 support `is_sink_dsc_all_bpp`. + """ + def __init__(self, param: Param): + self.__value = param + + @property + def is_sink_dsc_support(self) -> bool: + """ + Set and get state of SINK DSC support. + + Returns: + object of bool type + """ + return self.__value._get_by_bit_number(4) + + @is_sink_dsc_support.setter + def is_sink_dsc_support(self, state: bool): + self.__value._set_by_bit_number(state=state, value=1, bit_number=4) + + @property + def is_sink_dsc_10bpc(self) -> bool: + """ + Set and get state of SINK DSC 10 bpc support. + + Returns: + object of bool type + """ + return self.__value._get_by_bit_number(5) + + @is_sink_dsc_10bpc.setter + def is_sink_dsc_10bpc(self, state: bool): + self.__value._set_by_bit_number(state=state, value=1, bit_number=5) + + @property + def is_sink_dsc_12bpc(self) -> bool: + """ + Set and get state of SINK DSC 12 bpc support. + + Returns: + object of bool type + """ + return self.__value._get_by_bit_number(6) + + @is_sink_dsc_12bpc.setter + def is_sink_dsc_12bpc(self, state: bool): + self.__value._set_by_bit_number(state=state, value=1, bit_number=6) + + @property + def is_sink_dsc_native_420(self) -> bool: + """ + Set and get state of SINK DSC Native 420 support. + + Returns: + object of bool type + """ + return self.__value._get_by_bit_number(7) + + @is_sink_dsc_native_420.setter + def is_sink_dsc_native_420(self, state: bool): + self.__value._set_by_bit_number(state=state, value=1, bit_number=7) + + @property + def is_sink_dsc_16bpc(self) -> bool: + """ + Set and get state of SINK DSC 16 bpc support. + + Returns: + object of bool type + """ + return self.__value._get_by_bit_number(8) + + @is_sink_dsc_16bpc.setter + def is_sink_dsc_16bpc(self, state: bool): + self.__value._set_by_bit_number(state=state, value=1, bit_number=8) + + @property + def is_sink_dsc_all_bpp(self) -> bool: + """ + Set and get state of SINK DSC all BPP support. + + Returns: + object of bool type + """ + return self.__value._get_by_bit_number(9) + + @is_sink_dsc_all_bpp.setter + def is_sink_dsc_all_bpp(self, state: bool): + self.__value._set_by_bit_number(state=state, value=1, bit_number=9) + + @property + def is_sink_ycbcr_support(self) -> bool: + """ + Set and get state of SINK YCbCr support. + + Returns: + object of bool type + """ + return self.__value._get_by_bit_number(10) + + @is_sink_ycbcr_support.setter + def is_sink_ycbcr_support(self, state: bool): + self.__value._set_by_bit_number(state=state, value=1, bit_number=10) + + +class HdmiSinkDUTTestParam: + """ + Class `HdmiSinkDUTTestParam` allows working with parameters from HDMI DSC source part. + - Seg and get test mode `test_mode`. + - Set and get timeout `timeout`. + - Set and get MAX FRL rate `max_frl_rate` type `HdmiFrlRate`. + - Set and get MAX TMDS Clock 'max_tmds_clock'. + - Set and get HDMI DUT Caps 'dut_caps' type 'HdmiDutCaps'. + - Set and get DSC MAX FRL rate `dsc_max_frl_rate` type `HdmiFrlRate`. + - Set and get available video modes `video_modes`. + - Set and get DSC max slice (from 0 to 7) 'dsc_max_slice'. + - Set and get Total chunk bytes `total_chunk_bytes`. + """ + def __init__(self, json_obj): + self.__test_mode = Param(json_obj["TSI_HDMI_SNKCTS_TEST_MODE"]) + self.__timeout = Param(json_obj["TSI_HDMI_SNKCTS_TIMEOUT"]) + self.__max_frl_rate = Param(json_obj["TSI_HDMI_SNKCTS_MAX_FRL_RATE"]) + self.__max_tmds_clock = Param(json_obj["TSI_HDMI_SNKCTS_MAX_TMDS_CLOCK"]) + self.__dut_caps = HdmiDutCaps(Param(json_obj["TSI_HDMI_SNKCTS_DUT_CAPS"])) + self.__dsc_max_frl_rate = Param(json_obj["TSI_HDMI_SNKCTS_DSC_MAX_FRL_RATE"]) + self.__video_modes = HdmiAvailableVideoModes(Param(json_obj["TSI_HDMI_SNKCTS_DSC_VIDEO_FORMAT"]), + Param(json_obj["TSI_HDMI_SNKCTS_DSC_VIDEO_FORMAT_2"])) + self.__dsc_max_slice = Param(json_obj["TSI_HDMI_SNKCTS_DSC_MAX_SLICES"]) + self.__total_chunk_bytes = Param(json_obj["TSI_HDMI_SNKCTS_DSC_TOTAL_CHUNK_BYTES"]) + + @property + def test_mode(self) -> HdmiTestMode: + """ + Set and get test mode. + + Returns: + object of `HdmiTestMode` type + """ + return HdmiTestMode(self.__test_mode.default_value) + + @test_mode.setter + def test_mode(self, test_mode: HdmiTestMode): + self.__test_mode.default_value = test_mode.value + + @property + def timeout(self) -> int: + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout + + @property + def max_frl_rate(self) -> HdmiFrlRate: + """ + Set and get Max DSC FRL mode. + + Returns: + object of `HdmiFrlRate` type + """ + return HdmiFrlRate(self.__max_frl_rate.default_value) + + @max_frl_rate.setter + def max_frl_rate(self, max_frl_rate: HdmiFrlRate): + self.__max_frl_rate.default_value = max_frl_rate.value + + @property + def max_tmds_clock(self) -> int: + """ + Set and get test MAX TMDS clock, in MHz. + + Returns: + object of int type + """ + return self.__max_tmds_clock.default_value + + @max_tmds_clock.setter + def max_tmds_clock(self, max_tmds_clock: int): + if not (self.__max_tmds_clock.min_value < max_tmds_clock < self.__max_tmds_clock.max_value): + raise ValueError(f"MAX TMDS clock cannot be less than {self.__max_tmds_clock.min_value} and more than " + f"{self.__max_tmds_clock.max_value}.") + self.__max_tmds_clock.default_value = max_tmds_clock + + @property + def dut_caps(self) -> HdmiDutCaps: + """ + Set and get DUT caps. + + Returns: + object HdmiDutCaps + """ + return self.__dut_caps + + @dut_caps.setter + def dut_caps(self, dut_caps: HdmiDutCaps): + self.__dut_caps = dut_caps + + @property + def dsc_max_frl_rate(self) -> HdmiFrlRate: + """ + Set and get Max DSC FRL mode. + + Returns: + object of `HdmiFrlRate` type + """ + return HdmiFrlRate(self.__dsc_max_frl_rate.default_value) + + @dsc_max_frl_rate.setter + def dsc_max_frl_rate(self, max_dsc_frl_rate: HdmiFrlRate): + self.__dsc_max_frl_rate.default_value = max_dsc_frl_rate.value + + @property + def video_modes(self) -> HdmiAvailableVideoModes: + """ + Set and get supported video modes. + + Returns: + object `HdmiAvailableVideoModes` + """ + return self.__video_modes + + @video_modes.setter + def video_modes(self, video_modes: HdmiAvailableVideoModes): + self.__video_modes = video_modes + + @property + def dsc_max_slice(self) -> int: + """ + Set and get test DSC max slice, in milliseconds. + + Returns: + object of int type + """ + return self.__dsc_max_slice.default_value + + @dsc_max_slice.setter + def dsc_max_slice(self, dsc_max_slice: int): + if not(self.__dsc_max_slice.min_value < dsc_max_slice < self.__dsc_max_slice.max_value): + raise ValueError(f"DSC max slice cannot be less than {self.__dsc_max_slice.min_value} and more than " + f"{self.__dsc_max_slice.max_value}.") + self.__dsc_max_slice.default_value = dsc_max_slice + + @property + def total_chunk_bytes(self) -> int: + """ + Set and get test Total chunk bytes, in milliseconds. + + Returns: + object of int type + """ + return self.__total_chunk_bytes.default_value + + @total_chunk_bytes.setter + def total_chunk_bytes(self, total_chunk_bytes: int): + if not(self.__total_chunk_bytes.min_value < total_chunk_bytes < self.__total_chunk_bytes.max_value): + raise ValueError(f"Total chunk bytes cannot be less than {self.__total_chunk_bytes.min_value} and more than" + f" {self.__total_chunk_bytes.max_value}.") + self.__total_chunk_bytes.default_value = total_chunk_bytes diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/hdmi_source_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/hdmi_source_tests.py new file mode 100644 index 0000000..cf9ca53 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/hdmi_source_tests.py @@ -0,0 +1,27 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import param_by_ci_name + + +class HdmiSourceDUTTestParam: + """ + Class `HdmiSourceDUTTestParam` allows working with parameters from HDMI DSC source part. + - Set and get timeout `timeout`. + """ + def __init__(self, json_obj): + self.__timeout = json_obj["TSI_HDMI_SRCCTS_TIMEOUT"] + + @property + def timeout(self) -> int: + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/hdr10_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/hdr10_tests.py new file mode 100644 index 0000000..798c358 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/hdr10_tests.py @@ -0,0 +1,27 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param + + +class Hdr10TestParam: + + def __init__(self, json_obj): + self.__same_frame = Param(json_obj["TSI_HDR10_CTS_DISTR_SAME_FRAME"]) + self.__timeout = Param(json_obj["TSI_HDR10_CTS_DISTR_TIMEOUT"]) + + @property + def timeout(self): + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout + + @property + def same_frame(self): + return self.__same_frame.default_value + + @same_frame.setter + def same_frame(self, same_frame: bool): + self.__same_frame.default_value = same_frame diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/link_config_test.py b/UniTAP/dev/modules/dut_tests/dut_default_params/link_config_test.py new file mode 100644 index 0000000..a00e2ff --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/link_config_test.py @@ -0,0 +1,129 @@ +from enum import IntEnum +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param + + +class LinkRate(IntEnum): + """ + Class `LinkRate` describes available supported link rates. + """ + Rate_1_62_Gbps = 6 + Rate_2_7_Gbps = 10 + Rate_5_4_Gbps = 20 + Rate_8_1_Gbpc = 30 + + +class LinkConfigTestParam: + """ + Class `LinkConfigTestParam` describes parameters for Link configuration test. + - Test timeout, in milliseconds `timeout`. + - Defines the maximum number of lanes to be tested `max_lane_count`. Possible variants: 1, 2, 4. + - Defines the maximum link rate to be tested `max_rate` type `LinkRate`. + - Defines the length of the HPD pulse used to start each test iteration, in milliseconds `hpd_pulse_duration`. + - Defines how long the test waits for LT start after issuing HPD pulse, in milliseconds `lt_start_timeout`. + - Defines the additional delay inserted in between test iterations, in milliseconds `test_loop_delay`. + """ + def __init__(self, json_obj): + self.__timeout = Param(json_obj["TSI_DP_LTT_TIMEOUT"]) + self.__max_lane_count = Param(json_obj["TSI_DP_LTT_MAX_LANE_COUNT"]) + self.__max_rate = Param(json_obj["TSI_DP_LTT_MAX_RATE"]) + self.__hpd_pulse_duration = Param(json_obj["TSI_DP_LTT_HPD_PULSE_DURATION"]) + self.__lt_start_timeout = Param(json_obj["TSI_DP_LTT_LT_START_TIMEOUT"]) + self.__test_loop_delay = Param(json_obj["TSI_DP_LTT_TEST_LOOP_DELAY"]) + + @property + def timeout(self): + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout + + @property + def max_lane_count(self) -> int: + """ + Set and get maximum number of lanes. + + Returns: + object of int type + """ + return self.__max_lane_count.default_value + + @max_lane_count.setter + def max_lane_count(self, max_lane_count: int): + if not max_lane_count in [1, 2, 4]: + raise ValueError("Max lane count must be in range: 1, 2, or 4.") + self.__max_lane_count.default_value = max_lane_count + + @property + def max_rate(self): + """ + Set and get maximum link rate. + + Returns: + object `LinkRate` + """ + return self.__max_rate.default_value + + @max_rate.setter + def max_rate(self, max_rate: LinkRate): + self.__max_rate.default_value = max_rate.value + + @property + def hpd_pulse_duration(self) -> int: + """ + Set and get length of the HPD pulse. + + Returns: + object of int type + """ + return self.__hpd_pulse_duration.default_value + + @hpd_pulse_duration.setter + def hpd_pulse_duration(self, hpd_pulse_duration: int): + if not(self.__hpd_pulse_duration.min_value < hpd_pulse_duration < self.__hpd_pulse_duration.max_value): + raise ValueError(f"HPD pulse duration must be less than {self.__hpd_pulse_duration.min_value} and more than" + f" {self.__hpd_pulse_duration.max_value}.") + self.__hpd_pulse_duration.default_value = hpd_pulse_duration + + @property + def lt_start_timeout(self) -> int: + """ + Set and get link training start timeout. + + Returns: + object of int type + """ + return self.__lt_start_timeout.default_value + + @lt_start_timeout.setter + def lt_start_timeout(self, lt_start_timeout: int): + if not(self.__lt_start_timeout.min_value < lt_start_timeout < self.__lt_start_timeout.max_value): + raise ValueError(f"Link training start timeout cannot be less than {self.__lt_start_timeout.min_value} " + f"and more than {self.__lt_start_timeout.max_value}.") + self.__lt_start_timeout.default_value = lt_start_timeout + + @property + def test_loop_delay(self) -> int: + """ + Set and get test loop delay. + + Returns: + object of int type + """ + return self.__test_loop_delay.default_value + + @test_loop_delay.setter + def test_loop_delay(self, test_loop_delay: int): + if not(self.__test_loop_delay.min_value < test_loop_delay < self.__test_loop_delay.max_value): + raise ValueError(f"Test loop delay cannot be less than {self.__test_loop_delay.min_value} " + f"and more than {self.__test_loop_delay.max_value}.") + self.__test_loop_delay.default_value = test_loop_delay diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/lttpr_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/lttpr_tests.py new file mode 100644 index 0000000..d4e139b --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/lttpr_tests.py @@ -0,0 +1,125 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param +from .dp_1_4_source_general_tab import TestAutomationFlags, update_default_value + + +class LttprDutCapsFlags(Param): + """ + Class `LttprDutCapsFlags` defines the DUT capabilities as flags and allows setting: + - Define that DUT is Type-C device `dut_is_type_c_device`. + """ + + def __init__(self, json_obj): + super().__init__(json_obj) + + @property + def dut_is_type_c_device(self) -> bool: + """ + Set and get Define that DUT is Type-C device flag support. + + Returns: + object of bool type + """ + return self._get_by_bitmask(0, bool) + + @dut_is_type_c_device.setter + def dut_is_type_c_device(self, dut_is_type_c_device: bool): + self._set_by_bitmask(dut_is_type_c_device, 0) + + def set_all_caps(self): + """ + Set all setting. + """ + self.dut_is_type_c_device = True + + def clear_all_caps(self): + """ + Clear all settings. + """ + self.dut_is_type_c_device = False + + +class DebugOptions: + def __init__(self, json_obj): + self.__cr_iteration_delay = Param(json_obj["TSI_DP20_LTTPR_DEBUG_CR_ITERATION_DELAY"]) + + @property + def cr_iteration_delay(self) -> bool: + """ + Set and get Force manual visual check flag. + + Returns: + object of bool type + """ + return self.__cr_iteration_delay.default_value + + @cr_iteration_delay.setter + def cr_iteration_delay(self, delay: int): + if not(self.__cr_iteration_delay.min_value < delay < self.__cr_iteration_delay.max_value): + raise ValueError(f"Timeout cannot be less than {self.__cr_iteration_delay.min_value} and more than " + f"{self.__cr_iteration_delay.max_value}.") + self.__cr_iteration_delay.default_value = delay + + +class DpLttprTestParam: + """ + Class `DpLttprTestParam` describes requirement parameters for LTTPR DP 1228b/132b tests: + - Set and get `timeout`. Describes test timeout, in milliseconds. + - Set and get `hpd_duration`. Describes duration of long HPD pulses generated, in milliseconds. + - Set and get `dut_caps_flag`. Describes source DUT capabilities flags. + """ + def __init__(self, json_obj): + self.__timeout = Param(json_obj["TSI_DP20_LTTPR_TIMEOUT"]) + self.__max_lanes = Param(json_obj["TSI_DP20_LTTPR_MAX_LANES"]) # Not access + self.__max_link_rate = Param(json_obj["TSI_DP20_LTTPR_MAX_LINK_RATE"]) # Not access + self.__dut_caps = LttprDutCapsFlags(json_obj["TSI_DP20_LTTPR_DUT_CAPS"]) + self.__test_automation = TestAutomationFlags(json_obj["TSI_DP20_LTTPR_DUT_TA"]) # Not access + self.__hpd_duration = Param(json_obj["TSI_DP20_LTTPR_LONG_HPD_PULSE"]) + self.__debug_options = DebugOptions(json_obj) + + @property + def timeout(self) -> int: + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout + + @property + def hpd_duration(self) -> int: + """ + Set and get duration of long HPD pulses generated, in milliseconds. + + Returns: + object of int type + """ + return self.__hpd_duration.default_value + + @hpd_duration.setter + def hpd_duration(self, hpd_duration: int): + if not(self.__hpd_duration.min_value < hpd_duration < self.__hpd_duration.max_value): + raise ValueError(f"Timeout cannot be less than {self.__hpd_duration.min_value} and more than " + f"{self.__hpd_duration.max_value}.") + self.__hpd_duration.default_value = hpd_duration + + @property + def dut_caps_flags(self) -> LttprDutCapsFlags: + """ + Set and get LTTPR DUT capabilities flags. + + Returns: + object of `DutCapsFlags` type + """ + return self.__dut_caps + + @dut_caps_flags.setter + def dut_caps_flags(self, dut_caps_flags: LttprDutCapsFlags): + self.__dut_caps = dut_caps_flags diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/pixel_video_test.py b/UniTAP/dev/modules/dut_tests/dut_default_params/pixel_video_test.py new file mode 100644 index 0000000..0201aa8 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/pixel_video_test.py @@ -0,0 +1,295 @@ +from typing import List, Union, Optional +from enum import IntEnum +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param + + +class VideoPixelTestColorDepth(IntEnum): + """ + Class `VideoPixelTestColorDepth` describes available supported color depth. + """ + ColorDepth6 = 6 + ColorDepth8 = 8 + ColorDepth10 = 10 + ColorDepth12 = 12 + ColorDepth16 = 16 + + +class VideoPixelTestElementFormat(IntEnum): + """ + Class `VideoPixelTestElementFormat` describes available supported color formats. + """ + RGB_6BPC = 16 + RGB_8BPC = 17 + RGB_10BPC = 18 + RGB_12BPC = 19 + RGB_16BPC = 20 + YCbCr444_080808 = 256 + YCbCr444_161616 = 257 + YCbCr420_8BPC_080808 = 800 + YCbCr420_10BPC_080808 = 801 + YCbCr420_12BPC_161616 = 802 + YCbCr420_16BPC_161616 = 803 + + +class VideoPixelTestAlignment(IntEnum): + """ + Class `VideoPixelTestColorDepth` describes available supported alignment. + """ + MSB = 0 + LSB = 1 + + +class VideoPixelTestExportFormat(IntEnum): + """ + Class `VideoPixelTestColorDepth` describes available supported files format. + """ + BIN = 0 + PPM = 1 + BMP = 2 + + +class VideoPixelTestParam: + """ + Class `VideoPixelTestParam` describes parameters for video pixel test. + - Defines frame width as number of elements `image_width`. + - Defines frame height as number of elements `image_height`. + - Defines the color depth of the image as number of bits per color channel regardless of the color format + `color_depth` type `VideoPixelTestColorDepth`. + - Defines the element format used to encode the pixel data of the bitmap `frame_data`. + - Defines type of alignment (LSB-MSB data alignment) `alignment` type `VideoPixelTestAlignment`. + - Defines the length of the video test as number of frames `frames_number`. + - Defines number of frame that are allowed to be considered as 'failed' before the entire test is considered as + 'failed' `frames_number_mismatch`. + - Defines the number of pixels that allowed to be considered as 'failed' before the frame is considered as 'failed' + `pixel_tolerance`. + - Maximum number of frames failed frames saved per test run `max_auto_save_failed`. + - Contains the full path to the folder where failed frames are to be saved without trailing backslash + `failed_frame_folder`. + - Defines the number of failed frames to be exported from the video test `max_export_failed_frames`. + - Defines export file format `export_data_format` type `VideoPixelTestExportFormat`. + """ + def __init__(self, json_obj): + self.__image_width = Param(json_obj["TSI_REF1_WIDTH"]) + self.__image_height = Param(json_obj["TSI_REF1_HEIGHT"]) + self.__element_size = Param(json_obj["TSI_REF1_ELEMENT_SIZE"]) # Do not provide + self.__element_width = Param(json_obj["TSI_REF1_ELEMENT_WIDTH"]) # Do not provide + self.__element_height = Param(json_obj["TSI_REF1_ELEMENT_HEIGHT"]) # Do not provide + self.__color_depth = Param(json_obj["TSI_REF1_COLOR_DEPTH"]) # VideoPixelTestColorDepth + self.__element_format = Param(json_obj["TSI_REF1_ELEMENT_FORMAT"]) # VideoPixelTestElementFormat + self.__frame_data = Param(json_obj["TSI_REF1_FRAME_DATA"]) + self.__alignment = Param(json_obj["TSI_REF1_LSB_MSB"]) # VideoPixelTestAlignment + self.__frames_number = Param(json_obj["TSI_TEST_LENGTH"]) + self.__frames_number_mismatch = Param(json_obj["TSI_LIM_FRAME_MISMATCHES"]) + self.__pixel_tolerance = Param(json_obj["TSI_PIXEL_TOLERANCE"]) + self.__max_auto_save_failed = Param(json_obj["TSI_MAX_AUTO_SAVE_FAILED"]) + self.__failed_frame_folder = Param(json_obj["TSI_FAILED_FRAME_TARGET_FOLDER"]) + self.__max_export_failed_frames = Param(json_obj["TSI_MAX_EXPORT_FAILED"]) + self.__export_data_format = Param(json_obj["TSI_EXPORT_FORMAT"]) # Export format VideoPixelTestExportFormat + + @property + def image_width(self) -> int: + """ + Set and get frame width as number of elements. + + Returns: + object of int type + """ + return self.__image_width.default_value + + @image_width.setter + def image_width(self, image_width: int): + if not(self.__image_width.min_value < image_width < self.__image_width.max_value): + raise ValueError(f"Image width cannot be less than {self.__image_width.min_value} and more than " + f"{self.__image_width.max_value}.") + self.__image_width.default_value = image_width + + @property + def image_height(self) -> int: + """ + Set and get frame height as number of elements. + + Returns: + object of int type + """ + return self.__image_height.default_value + + @image_height.setter + def image_height(self, image_height: int): + if not(self.__image_height.min_value < image_height < self.__image_height.max_value): + raise ValueError(f"Image height cannot be less than {self.__image_height.min_value} and more than " + f"{self.__image_height.max_value}.") + self.__image_height.default_value = image_height + + @property + def color_depth(self): + """ + Set and get color depth of the image. + + Returns: + object `VideoPixelTestColorDepth` + """ + return self.__color_depth.default_value + + @color_depth.setter + def color_depth(self, color_depth: VideoPixelTestColorDepth): + self.__color_depth.default_value = color_depth.value + + @property + def element_format(self): + """ + Set and get element format used to encode the pixel data. + + Returns: + object `VideoPixelTestElementFormat` + """ + return self.__element_format.default_value + + @element_format.setter + def element_format(self, element_format: VideoPixelTestElementFormat): + self.__element_format.direct_set_def_value(element_format.value) + + @property + def frame_data(self) -> Optional[Union[bytearray, List[int], str]]: + """ + Set and get pixel data of image. + + Returns: + object bytearray | list[int] | None | str + """ + return self.__image_height.default_value + + @frame_data.setter + def frame_data(self, frame_data: Optional[Union[bytearray, List[int], str]]): + self.__frame_data.default_value = frame_data + + @property + def alignment(self): + """ + Set and get type of alignment. + + Returns: + object `VideoPixelTestAlignment` + """ + return self.__alignment.default_value + + @alignment.setter + def alignment(self, alignment: VideoPixelTestAlignment): + self.__alignment.default_value = alignment.value + + @property + def frames_number(self) -> int: + """ + Set and get length of the video test as number of frames. + + Returns: + object `VideoPixelTestAlignment` + """ + return self.__frames_number.default_value + + @frames_number.setter + def frames_number(self, frames_number: int): + if not(self.__frames_number.min_value < frames_number < self.__frames_number.max_value): + raise ValueError(f"Frames number cannot be less than {self.__frames_number.min_value} and more than " + f"{self.__frames_number.max_value}.") + self.__frames_number.default_value = frames_number + + @property + def frames_number_mismatch(self) -> int: + """ + Set and get number of frame that are allowed to be considered as 'failed' before the entire test is considered + as 'failed'. + + Returns: + object `VideoPixelTestAlignment` + """ + return self.__frames_number_mismatch.default_value + + @frames_number_mismatch.setter + def frames_number_mismatch(self, frames_number_mismatch: int): + if not(self.__frames_number_mismatch.min_value < frames_number_mismatch < + self.__frames_number_mismatch.max_value): + raise ValueError(f"Image height cannot be less than {self.__frames_number_mismatch.min_value} and more than" + f" {self.__frames_number_mismatch.max_value}.") + self.__frames_number_mismatch.default_value = frames_number_mismatch + + @property + def pixel_tolerance(self) -> int: + """ + Set and get number of pixels that allowed to be considered as 'failed' before the frame is considered as + 'failed'. + + Returns: + object `VideoPixelTestAlignment` + """ + return self.__pixel_tolerance.default_value + + @pixel_tolerance.setter + def pixel_tolerance(self, pixel_tolerance: int): + if not(self.__pixel_tolerance.min_value < pixel_tolerance < self.__pixel_tolerance.max_value): + raise ValueError(f"Pixel tolerance cannot be less than {self.__pixel_tolerance.min_value} and more than " + f"{self.__image_height.max_value}.") + self.__pixel_tolerance.default_value = pixel_tolerance + + @property + def max_auto_save_failed(self) -> int: + """ + Set and get maximum number of frames failed frames saved per test run. + + Returns: + object `VideoPixelTestAlignment` + """ + return self.__max_auto_save_failed.default_value + + @max_auto_save_failed.setter + def max_auto_save_failed(self, max_auto_save_failed: int): + if not(self.__max_auto_save_failed.min_value < max_auto_save_failed < self.__max_auto_save_failed.max_value): + raise ValueError(f"Max auto save failed frames cannot be less than {self.__max_auto_save_failed.min_value}" + f" and more than {self.__max_auto_save_failed.max_value}.") + self.__max_auto_save_failed.default_value = max_auto_save_failed + + @property + def failed_frame_folder(self): + """ + Set and get full path to the folder where failed frames are to be saved without trailing backslash. + + Returns: + object str + """ + return self.failed_frame_folder.default_value + + @failed_frame_folder.setter + def failed_frame_folder(self, failed_frame_folder: str): + self.failed_frame_folder.default_value = failed_frame_folder + + @property + def max_export_failed_frames(self) -> int: + """ + Set and get number of failed frames to be exported from the video test. + + Returns: + object int + """ + return self.__max_export_failed_frames.default_value + + @max_export_failed_frames.setter + def max_export_failed_frames(self, max_export_failed_frames: int): + if not(self.__max_export_failed_frames.min_value < max_export_failed_frames < + self.__max_export_failed_frames.max_value): + raise ValueError(f"Max export failed frames number cannot be less than " + f"{self.__max_export_failed_frames.min_value} and more than " + f"{self.__image_height.max_value}.") + self.__max_export_failed_frames.default_value = max_export_failed_frames + + @property + def export_data_format(self): + """ + Set and get file format. + + Returns: + object `VideoPixelTestExportFormat` + """ + return self.__export_data_format.default_value + + @export_data_format.setter + def export_data_format(self, export_data_format: VideoPixelTestExportFormat): + self.__export_data_format.default_value = export_data_format.value diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/usbc_electrical_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/usbc_electrical_tests.py new file mode 100644 index 0000000..5d092fb --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/usbc_electrical_tests.py @@ -0,0 +1,546 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param +from UniTAP.dev.modules.dut_tests.test_utils import update_default_value + + +class UsbcElTestDutCaps(Param): + """ + Class `UsbcElTestDutCaps` describes DUT capabilities flags and allows settings values. + - DUT Support DisplayPort Alternate mode `support_dp_alt_mode`. + - DUT can act as a power source `can_act_power_source` + - DUT can act as a power sink `cab_act_power_sink`. + - DUT does not support PD Contract `not_support_pd_contract`. + """ + def __init__(self, json_obj): + super().__init__(json_obj) + + @property + def support_dp_alt_mode(self): + """ + Set and get DUT Support DisplayPort Alternate mode flag support. + + Returns: + object bool + """ + return self._get_by_bitmask(0, bool) + + @support_dp_alt_mode.setter + def support_dp_alt_mode(self, support_dp_alt_mode: bool): + self._set_by_bitmask(support_dp_alt_mode, 0) + + @property + def can_act_power_source(self): + """ + Set and get DUT can act as a power source flag support. + + Returns: + object bool + """ + return self._get_by_bitmask(1, bool) + + @can_act_power_source.setter + def can_act_power_source(self, can_act_power_source: bool): + self._set_by_bitmask(can_act_power_source, 1) + + @property + def cab_act_power_sink(self): + """ + Set and get DUT can act as a power sink flag support. + + Returns: + object bool + """ + return self._get_by_bitmask(2, bool) + + @cab_act_power_sink.setter + def cab_act_power_sink(self, cab_act_power_sink: bool): + self._set_by_bitmask(cab_act_power_sink, 2) + + @property + def not_support_pd_contract(self): + """ + Set and get DUT does not support PD Contract flag support. + + Returns: + object bool + """ + return self._get_by_bitmask(3, bool) + + @not_support_pd_contract.setter + def not_support_pd_contract(self, not_support_pd_contract: bool): + self._set_by_bitmask(not_support_pd_contract, 3) + + +class UsbcElectricalTestParam: + """ + Class `DpElectricalTestParam` describes parameters for DP electrical tests. + - Test timeout, in milliseconds `timeout`. + - Defines DUT capabilities `dut_caps` type `UsbcElTestDutCaps`. + - Defines the time period for USB Type-C re-plug simulation 'disconnected' state `re_plug_time`. + - Defines the time period that the TE will wait for DUT to complete power contract negotiation `attach_timeout`. + - Defines Power Contract timeout, in milliseconds `pwr_contract_timeout`. + - Defines the low limit for the voltage window when power sink current is 0.5A or 0.9A `cc_low_voltage_1 `. + - Defines the high limit for the voltage window when power sink current is 0.5A or 0.9A `cc_high_voltage_1`. + - Defines the low limit for the voltage window when power sink current is 1.5A `cc_low_voltage_2`. + - Defines the high limit for the voltage window when power sink current is 1.5A `cc_high_voltage_2`. + - Defines the low limit for the voltage window when power sink current is 3.0A `cc_low_voltage_3`. + - Defines the high limit for the voltage window when power sink current is 3.0A `cc_high_voltage_3`. + - Defines the low limit for the Vcon voltage window `vconn_low_voltage`. + - Defines the high limit for the Vcon voltage window `vconn_high_voltage`. + - Defines the timeout the TE will wait for the DUT to enter into DisplayPort alternate mode `dp_alt_mode_timeout`. + - Defines the low voltage limit for the positive DP AUX line when idle `aux_positive_idle_low_voltage`. + - Defines the high voltage limit for the positive DP AUX line when idle `aux_positive_idle_high_voltage`. + - Defines the low voltage limit for the negative DP AUX line when idle `aux_negative_idle_low_voltage`. + - Defines the high voltage limit for the negative DP AUX line when idle `aux_negative_idle_high_voltage`. + - Defines the low limit for Vbus voltage window. The limit is defined in millivolts (mV) `vbus_low_voltage`. + - Defines the high limit for Vbus voltage window. The limit is defined in millivolts (mV) `vbus_high_voltage`. + - Defines the highest allowed deviation between maximum and minimum currents measured from the individual Vbus + pins as per-mill (‰) of total measured current. This means that if the total measured current is 3000mA, and the + setting 100, the maximum difference that is allowed between maximum and minimum currents is 300mA + `vbus_current_max`. + - Defines the highest allowed deviation between maximum and minimum currents measured from the individual GND pins + as per-mill (‰) of total measured current `gnd_current_max`. + - Defines delay from end of power contract negotiation to voltage / current measurements `power_measure_delay`. + - Defines the minimum current, in mA, that a Power Sink DUT must use in order to pass the test `min_dut_current`. + - Delay after load resistor on, milliseconds. This helps to skip transient processes `resistor_on_delay`. + - Delay for measure CC lines voltage after DUT plug detection, milliseconds `cc_measure_delay`. + """ + def __init__(self, json_obj): + self.__timeout = Param(json_obj["TSI_USBC_EL_TIMEOUT"]) + self.__dut_caps = UsbcElTestDutCaps(json_obj["TSI_USBC_EL_DUT_CAPS"]) + self.__re_plug_time = Param(json_obj["TSI_USBC_EL_REPLUG_TIME"]) + self.__attach_timeout = Param(json_obj["TSI_USBC_EL_DUT_ATTACH_TIMEOUT"]) + self.__pwr_contract_timeout = Param(json_obj["TSI_USBC_EL_PWR_CONTRACT_TIMEOUT"]) + self.__cc_low_voltage_1 = Param(json_obj["TSI_USBC_EL_CC_LOW_VOLTAGE_1"]) + self.__cc_high_voltage_1 = Param(json_obj["TSI_USBC_EL_CC_HI_VOLTAGE_1"]) + self.__cc_low_voltage_2 = Param(json_obj["TSI_USBC_EL_CC_LOW_VOLTAGE_2"]) + self.__cc_high_voltage_2 = Param(json_obj["TSI_USBC_EL_CC_HI_VOLTAGE_2"]) + self.__cc_low_voltage_3 = Param(json_obj["TSI_USBC_EL_CC_LOW_VOLTAGE_3"]) + self.__cc_high_voltage_3 = Param(json_obj["TSI_USBC_EL_CC_HI_VOLTAGE_3"]) + self.__vconn_low_voltage = Param(json_obj["TSI_USBC_EL_VCON_LOW_VOLTAGE"]) + self.__vconn_high_voltage = Param(json_obj["TSI_USBC_EL_VCON_HI_VOLTAGE"]) + self.__dp_alt_mode_timeout = Param(json_obj["TSI_USBC_EL_DP_ALT_TIMEOUT"]) + self.__aux_positive_idle_low_voltage = Param(json_obj["TSI_USBC_EL_AUX_P_IDLE_LOW_VOLTAGE"]) + self.__aux_positive_idle_high_voltage = Param(json_obj["TSI_USBC_EL_AUX_P_IDLE_HI_VOLTAGE"]) + self.__aux_negative_idle_low_voltage = Param(json_obj["TSI_USBC_EL_AUX_N_IDLE_LOW_VOLTAGE"]) + self.__aux_negative_idle_high_voltage = Param(json_obj["TSI_USBC_EL_AUX_N_IDLE_HI_VOLTAGE"]) + self.__vbus_low_voltage = Param(json_obj["TSI_USBC_EL_VBUS_LOW_VOLTAGE"]) + self.__vbus_high_voltage = Param(json_obj["TSI_USBC_EL_VBUS_HI_VOLTAGE"]) + self.__vbus_current_max = Param(json_obj["TSI_USBC_EL_VBUS_CURRENT_MAX_DEV"]) + self.__gnd_current_max = Param(json_obj["TSI_USBC_EL_GND_CURRENT_MAX_DEV"]) + self.__power_measure_delay = Param(json_obj["TSI_USBC_EL_PWR_MEASURE_DELAY"]) + self.__min_dut_current = Param(json_obj["TSI_USBC_EL_MIN_DUT_CURRENT"]) + self.__resistor_on_delay = Param(json_obj["TSI_USBC_EL_RES_ON_DELAY"]) + self.__cc_measure_delay = Param(json_obj["TSI_USBC_EL_CC_MEASURE_DELAY"]) + + @property + def timeout(self) -> int: + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout + + @property + def dut_caps(self) -> UsbcElTestDutCaps: + """ + Set and get USB-C DUT caps. + + Returns: + object of `UsbcElTestDutCaps` type + """ + return self.__dut_caps + + @dut_caps.setter + def dut_caps(self, dut_caps: UsbcElTestDutCaps): + self.__dut_caps = dut_caps + + @property + def pwr_contract_timeout(self) -> int: + """ + Set and get power contract timeout. + + Returns: + object of int type + """ + return self.__pwr_contract_timeout.default_value + + @pwr_contract_timeout.setter + def pwr_contract_timeout(self, pwr_contract_timeout: int): + if not(self.__pwr_contract_timeout.min_value < pwr_contract_timeout < self.__pwr_contract_timeout.max_value): + raise ValueError(f"Power contract timeout cannot be less than {self.__pwr_contract_timeout.min_value} " + f"and more than {self.__pwr_contract_timeout.max_value}") + self.__pwr_contract_timeout.default_value = pwr_contract_timeout + + @property + def cc_low_voltage_1(self) -> int: + """ + Set and get low limit for the voltage window when power sink current is 0.5A or 0.9A. + + Returns: + object of int type + """ + return self.__cc_low_voltage_1.default_value + + @cc_low_voltage_1.setter + def cc_low_voltage_1(self, cc_low_voltage_1: int): + if not(self.__cc_low_voltage_1.min_value < cc_low_voltage_1 < self.__cc_low_voltage_1.max_value): + raise ValueError(f"First CC low voltage cannot be less than {self.__cc_low_voltage_1.min_value} " + f"and more than {self.__cc_low_voltage_1.max_value}") + self.__cc_low_voltage_1.default_value = cc_low_voltage_1 + + @property + def cc_high_voltage_1(self) -> int: + """ + Set and get high limit for the voltage window when power sink current is 0.5A or 0.9A. + + Returns: + object of int type + """ + return self.__cc_high_voltage_1.default_value + + @cc_high_voltage_1.setter + def cc_high_voltage_1(self, cc_high_voltage_1: int): + if not(self.__cc_high_voltage_1.min_value < cc_high_voltage_1 < self.__cc_high_voltage_1.max_value): + raise ValueError(f"First CC high voltage cannot be less than {self.__cc_high_voltage_1.min_value} " + f"and more than {self.__cc_high_voltage_1.max_value}") + self.__cc_high_voltage_1.default_value = cc_high_voltage_1 + + @property + def cc_low_voltage_2(self) -> int: + """ + Set and get low limit for the voltage window when power sink current is 1.5A. + + Returns: + object of int type + """ + return self.__cc_low_voltage_2.default_value + + @cc_low_voltage_2.setter + def cc_low_voltage_2(self, cc_low_voltage_2: int): + if not(self.__cc_low_voltage_2.min_value < cc_low_voltage_2 < self.__cc_low_voltage_2.max_value): + raise ValueError(f"Second CC low voltage cannot be less than {self.__cc_low_voltage_2.min_value} " + f"and more than {self.__cc_low_voltage_2.max_value}") + self.__cc_low_voltage_2.default_value = cc_low_voltage_2 + + @property + def cc_high_voltage_2(self) -> int: + """ + Set and get high limit for the voltage window when power sink current is 1.5A. + + Returns: + object of int type + """ + return self.__cc_high_voltage_2.default_value + + @cc_high_voltage_2.setter + def cc_high_voltage_2(self, cc_high_voltage_2: int): + if not(self.__cc_high_voltage_2.min_value < cc_high_voltage_2 < self.__cc_high_voltage_2.max_value): + raise ValueError(f"Second CC high voltage cannot be less than {self.__cc_high_voltage_2.min_value} " + f"and more than {self.__cc_high_voltage_2.max_value}") + self.__cc_high_voltage_2.default_value = cc_high_voltage_2 + + @property + def cc_low_voltage_3(self) -> int: + """ + Set and get low limit for the voltage window when power sink current is 3.0A. + + Returns: + object of int type + """ + return self.__cc_low_voltage_3.default_value + + @cc_low_voltage_3.setter + def cc_low_voltage_3(self, cc_low_voltage_3: int): + if not(self.__cc_low_voltage_3.min_value < cc_low_voltage_3 < self.__cc_low_voltage_3.max_value): + raise ValueError(f"Third CC low voltage cannot be less than {self.__cc_low_voltage_3.min_value} " + f"and more than {self.__cc_low_voltage_3.max_value}") + self.__cc_low_voltage_3.default_value = cc_low_voltage_3 + + @property + def cc_high_voltage_3(self): + """ + Set and get high limit for the voltage window when power sink current is 3.0A. + + Returns: + object of int type + """ + return self.__cc_high_voltage_3.default_value + + @cc_high_voltage_3.setter + def cc_high_voltage_3(self, cc_high_voltage_3: int): + if not(self.__cc_high_voltage_3.min_value < cc_high_voltage_3 < self.__cc_high_voltage_3.max_value): + raise ValueError(f"Third CC high voltage cannot be less than {self.__cc_high_voltage_3.min_value} " + f"and more than {self.__cc_high_voltage_3.max_value}") + self.__cc_high_voltage_3.default_value = cc_high_voltage_3 + + @property + def vconn_low_voltage(self) -> int: + """ + Set and get low limit for the Vcon voltage window. + + Returns: + object of int type + """ + return self.__vconn_low_voltage.default_value + + @vconn_low_voltage.setter + def vconn_low_voltage(self, vconn_low_voltage: int): + if not(self.__vconn_low_voltage.min_value < vconn_low_voltage < self.__vconn_low_voltage.max_value): + raise ValueError(f"VCONN low voltage cannot be less than {self.__vconn_low_voltage.min_value} " + f"and more than {self.__vconn_low_voltage.max_value}") + self.__vconn_low_voltage.default_value = vconn_low_voltage + + @property + def vconn_high_voltage(self) -> int: + """ + Set and get high limit for the Vcon voltage window. + + Returns: + object of int type + """ + return self.__vconn_high_voltage.default_value + + @vconn_high_voltage.setter + def vconn_high_voltage(self, vconn_high_voltage: int): + if not(self.__vconn_high_voltage.min_value < vconn_high_voltage < self.__vconn_high_voltage.max_value): + raise ValueError(f"VCONN high voltage cannot be less than {self.__vconn_high_voltage.min_value} " + f"and more than {self.__vconn_high_voltage.max_value}") + self.__vconn_high_voltage.default_value = vconn_high_voltage + + @property + def dp_alt_mode_timeout(self) -> int: + """ + Set and get DP alt mode timeout. + + Returns: + object of int type + """ + return self.__dp_alt_mode_timeout.default_value + + @dp_alt_mode_timeout.setter + def dp_alt_mode_timeout(self, dp_alt_mode_timeout: int): + if not(self.__dp_alt_mode_timeout.min_value < dp_alt_mode_timeout < self.__dp_alt_mode_timeout.max_value): + raise ValueError(f"DP Alt mode timeout cannot be less than {self.__dp_alt_mode_timeout.min_value} " + f"and more than {self.__dp_alt_mode_timeout.max_value}") + self.__dp_alt_mode_timeout.default_value = dp_alt_mode_timeout + + @property + def aux_positive_idle_low_voltage(self) -> int: + """ + Set and get low voltage limit for the positive DP AUX. + + Returns: + object of int type + """ + return self.__aux_positive_idle_low_voltage.default_value + + @aux_positive_idle_low_voltage.setter + def aux_positive_idle_low_voltage(self, aux_positive_idle_low_voltage: int): + if not(self.__aux_positive_idle_low_voltage.min_value < aux_positive_idle_low_voltage < + self.__aux_positive_idle_low_voltage.max_value): + raise ValueError(f"AUX positive IDLE low voltage cannot be less than " + f"{self.__aux_positive_idle_low_voltage.min_value} and more than " + f"{self.__aux_positive_idle_low_voltage.max_value}") + self.__aux_positive_idle_low_voltage.default_value = aux_positive_idle_low_voltage + + @property + def aux_positive_idle_high_voltage(self) -> int: + """ + Set and get high voltage limit for the positive DP AUX. + + Returns: + object of int type + """ + return self.__aux_positive_idle_high_voltage.default_value + + @aux_positive_idle_high_voltage.setter + def aux_positive_idle_high_voltage(self, aux_positive_idle_high_voltage: int): + if not(self.__aux_positive_idle_high_voltage.min_value < aux_positive_idle_high_voltage < + self.__aux_positive_idle_high_voltage.max_value): + raise ValueError(f"AUX positive IDLE high voltage cannot be less than " + f"{self.__aux_positive_idle_high_voltage.min_value} and more than " + f"{self.__aux_positive_idle_high_voltage.max_value}") + self.__aux_positive_idle_high_voltage.default_value = aux_positive_idle_high_voltage + + @property + def aux_negative_idle_low_voltage(self) -> int: + """ + Set and get low voltage limit for the negative DP AUX. + + Returns: + object of int type + """ + return self.__aux_negative_idle_low_voltage.default_value + + @aux_negative_idle_low_voltage.setter + def aux_negative_idle_low_voltage(self, aux_negative_idle_low_voltage: int): + if not(self.__aux_negative_idle_low_voltage.min_value < aux_negative_idle_low_voltage < + self.__aux_negative_idle_low_voltage.max_value): + raise ValueError(f"AUX negative IDLE low voltage cannot be less than " + f"{self.__aux_negative_idle_low_voltage.min_value} and more than " + f"{self.__aux_negative_idle_low_voltage.max_value}") + self.__aux_negative_idle_low_voltage.default_value = aux_negative_idle_low_voltage + + @property + def aux_negative_idle_high_voltage(self) -> int: + """ + Set and get high voltage limit for the negative DP AUX. + + Returns: + object of int type + """ + return self.__aux_negative_idle_high_voltage.default_value + + @aux_negative_idle_high_voltage.setter + def aux_negative_idle_high_voltage(self, aux_negative_idle_high_voltage: int): + if not(self.__aux_negative_idle_high_voltage.min_value < aux_negative_idle_high_voltage < + self.__aux_negative_idle_high_voltage.max_value): + raise ValueError(f"AUX negative IDLE high voltage cannot be less than " + f"{self.__aux_negative_idle_high_voltage.min_value} and more than " + f"{self.__aux_negative_idle_high_voltage.max_value}") + self.__aux_negative_idle_high_voltage.default_value = aux_negative_idle_high_voltage + + @property + def vbus_low_voltage(self) -> int: + """ + Set and get low limit for Vbus voltage window. + + Returns: + object of int type + """ + return self.__vbus_low_voltage.default_value + + @vbus_low_voltage.setter + def vbus_low_voltage(self, vbus_low_voltage: int): + if not(self.__vbus_low_voltage.min_value < vbus_low_voltage < self.__vbus_low_voltage.max_value): + raise ValueError(f"VBUS low voltage cannot be less than {self.__vbus_low_voltage.min_value} " + f"and more than {self.__vbus_low_voltage.max_value}") + self.__vbus_low_voltage.default_value = vbus_low_voltage + + @property + def vbus_high_voltage(self) -> int: + """ + Set and get high limit for Vbus voltage window. + + Returns: + object of int type + """ + return self.__vbus_high_voltage.default_value + + @vbus_high_voltage.setter + def vbus_high_voltage(self, vbus_high_voltage: int): + if not(self.__vbus_high_voltage.min_value < vbus_high_voltage < self.__vbus_high_voltage.max_value): + raise ValueError(f"VBUS high voltage cannot be less than {self.__vbus_high_voltage.min_value} " + f"and more than {self.__vbus_high_voltage.max_value}") + self.__vbus_high_voltage.default_value = vbus_high_voltage + + @property + def vbus_current_max(self) -> int: + """ + Set and get the highest allowed deviation of current VBUS. + + Returns: + object of int type + """ + return self.__vbus_current_max.default_value + + @vbus_current_max.setter + def vbus_current_max(self, vbus_current_max: int): + if not(self.__vbus_current_max.min_value < vbus_current_max < self.__vbus_current_max.max_value): + raise ValueError(f"VBUS current max cannot be less than {self.__vbus_current_max.min_value} " + f"and more than {self.__vbus_current_max.max_value}") + self.__vbus_current_max.default_value = vbus_current_max + + @property + def gnd_current_max(self) -> int: + """ + Set and get the highest allowed deviation of current GND. + + Returns: + object of int type + """ + return self.__gnd_current_max.default_value + + @gnd_current_max.setter + def gnd_current_max(self, gnd_current_max: int): + if not(self.__gnd_current_max.min_value < gnd_current_max < self.__gnd_current_max.max_value): + raise ValueError(f"GND current max cannot be less than {self.__gnd_current_max.min_value} " + f"and more than {self.__gnd_current_max.max_value}") + self.__gnd_current_max.default_value = gnd_current_max + + @property + def power_measure_delay(self) -> int: + """ + Set and get delay from end of power contract negotiation to voltage / current measurements. + + Returns: + object of int type + """ + return self.__power_measure_delay.default_value + + @power_measure_delay.setter + def power_measure_delay(self, power_measure_delay: int): + if not(self.__power_measure_delay.min_value < power_measure_delay < self.__power_measure_delay.max_value): + raise ValueError(f"Power measure delay cannot be less than {self.__power_measure_delay.min_value} " + f"and more than {self.__power_measure_delay.max_value}") + self.__power_measure_delay.default_value = power_measure_delay + + @property + def min_dut_current(self) -> int: + """ + Set and get the minimum current. + + Returns: + object of int type + """ + return self.__min_dut_current.default_value + + @min_dut_current.setter + def min_dut_current(self, min_dut_current: int): + if not(self.__min_dut_current.min_value < min_dut_current < self.__min_dut_current.max_value): + raise ValueError(f"Minimum current on DUT cannot be less than {self.__min_dut_current.min_value} " + f"and more than {self.__min_dut_current.max_value}") + self.__min_dut_current.default_value = min_dut_current + + @property + def resistor_on_delay(self) -> int: + """ + Set and get delay after load resistor. + + Returns: + object of int type + """ + return self.__resistor_on_delay.default_value + + @resistor_on_delay.setter + def resistor_on_delay(self, resistor_on_delay: int): + if not(self.__resistor_on_delay.min_value < resistor_on_delay < self.__resistor_on_delay.max_value): + raise ValueError(f"Resistor delay cannot be less than {self.__resistor_on_delay.min_value} " + f"and more than {self.__resistor_on_delay.max_value}") + self.__resistor_on_delay.default_value = resistor_on_delay + + @property + def cc_measure_delay(self) -> int: + """ + Set and get delay for measure CC lines voltage after DUT plug detection. + + Returns: + object of int type + """ + return self.__cc_measure_delay.default_value + + @cc_measure_delay.setter + def cc_measure_delay(self, cc_measure_delay: int): + if not(self.__cc_measure_delay.min_value < cc_measure_delay < self.__cc_measure_delay.max_value): + raise ValueError(f"CC measure delay cannot be less than {self.__cc_measure_delay.min_value} " + f"and more than {self.__cc_measure_delay.max_value}") + self.__cc_measure_delay.default_value = cc_measure_delay diff --git a/UniTAP/dev/modules/dut_tests/dut_default_params/vrr_tests.py b/UniTAP/dev/modules/dut_tests/dut_default_params/vrr_tests.py new file mode 100644 index 0000000..0923bb0 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_default_params/vrr_tests.py @@ -0,0 +1,303 @@ +from UniTAP.dev.modules.dut_tests.test_group_params_types import Param +from UniTAP.dev.modules.dut_tests.test_utils import update_default_value + + +class VrrCaps(Param): + """ + Class `VrrCaps` describes DUT VRR capabilities flags and allows settings values. + - VRR enable/disable `vrr_enable`. + - Defines M_CONST `m_const`. + """ + def __init__(self, json_obj): + super().__init__(json_obj) + + @property + def vrr_enable(self): + """ + Set and get VRR enable/disable flag. + + Returns: + object bool + """ + return self._get_by_bitmask(0, bool) + + @vrr_enable.setter + def vrr_enable(self, vrr_enable: bool): + self._set_by_bitmask(vrr_enable, 0) + + @property + def m_const(self): + """ + Set and get m const enable/disable flag. + + Returns: + object bool + """ + return self._get_by_bitmask(1, bool) + + @m_const.setter + def m_const(self, m_const: bool): + self._set_by_bitmask(m_const, 1) + + +class BaseFront(Param): + """ + Class `BaseFront` describes DUT BaseFront capabilities flags and allows settings values. + - Base front value `vrr_enable`. + - Reduced blanking enable/disable `m_const`. + """ + def __init__(self, json_obj): + super().__init__(json_obj) + + @property + def base_front(self): + """ + Set and get va;ue of base front. + + Returns: + object int + """ + return self._get_by_bitmask(0, int) + + @base_front.setter + def base_front(self, base_front: int): + if not (0 <= base_front <= 3): + raise ValueError(f"Base front value cannot be less than 0 and more than 3") + self._set_by_bitmask(base_front, 0) + + @property + def reduced_blanking(self): + """ + Set and get reduced blanking flag support. + + Returns: + object bool + """ + return self._get_by_bitmask(1, bool) + + @reduced_blanking.setter + def reduced_blanking(self, reduced_blanking: bool): + self._set_by_bitmask(reduced_blanking, 1) + + +class VrrTestParamBase: + """ + Class `VrrTestParam` describes parameters for VRR Sink and Source tests. + - Test timeout, in milliseconds `timeout`. + - Defines VRR Max value `vrr_max`. + - Defines VRR Min value `vrr_min`. + - Defines VRR Static value `vrr_static`. + - Defines step of changing frame rate `vrr_step`. + - Defines timer to change frame rate `vrr_time_step`. + - Defines VRR capabilities `vrr_enable_cups` type `VrrCaps`. + - Defines VRR Base front `base_vfront` type `BaseFront`. + - Defines Base Refresh Rate `base_rate`. + """ + def __init__(self, timeout, vrr_max, vrr_min, vrr_static, vrr_step, vrr_time_step, + vrr_enable_cups, base_vfront, base_rate): + self.__timeout = timeout + self.__vrr_max = vrr_max + self.__vrr_min = vrr_min + self.__vrr_static = vrr_static + self.__vrr_step = vrr_step + self.__vrr_time_step = vrr_time_step + self.__vrr_enable_cups = vrr_enable_cups + self.__base_vfront = base_vfront + self.__base_rate = base_rate + + @property + def timeout(self) -> int: + """ + Set and get test timeout, in milliseconds. + + Returns: + object of int type + """ + return self.__timeout.default_value + + @timeout.setter + def timeout(self, timeout: int): + if not(self.__timeout.min_value < timeout < self.__timeout.max_value): + raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than " + f"{self.__timeout.max_value}.") + self.__timeout.default_value = timeout + + @property + def vrr_max(self) -> int: + """ + Set and get VRR max. + + Returns: + object of int type + """ + return self.__vrr_max.default_value + + @vrr_max.setter + def vrr_max(self, vrr_max: int): + if not(self.__vrr_max.min_value < vrr_max < self.__vrr_max.max_value): + raise ValueError(f"VRR max cannot be less than {self.__vrr_max.min_value} and more than " + f"{self.__vrr_max.max_value}.") + self.__vrr_max.default_value = vrr_max + + @property + def vrr_min(self) -> int: + """ + Set and get VRR min. + + Returns: + object of int type + """ + return self.__vrr_min.default_value + + @vrr_min.setter + def vrr_min(self, vrr_min: int): + if not(self.__vrr_min.min_value < vrr_min < self.__vrr_min.max_value): + raise ValueError(f"VRR min cannot be less than {self.__vrr_min.min_value} and more than " + f"{self.__vrr_min.max_value}.") + self.__vrr_min.default_value = vrr_min + + @property + def vrr_static(self) -> int: + """ + Set and get VRR static. + + Returns: + object of int type + """ + return self.__vrr_static.default_value + + @vrr_static.setter + def vrr_static(self, vrr_static: int): + if not(self.__vrr_static.min_value < vrr_static < self.__vrr_static.max_value): + raise ValueError(f"VRR stasic cannot be less than {self.__vrr_static.min_value} and more than " + f"{self.__vrr_static.max_value}.") + self.__vrr_static.default_value = vrr_static + + @property + def vrr_step(self) -> int: + """ + Set and get VRR step. + + Returns: + object of int type + """ + return self.__vrr_step.default_value + + @vrr_step.setter + def vrr_step(self, vrr_step: int): + if not(self.__vrr_step.min_value < vrr_step < self.__vrr_step.max_value): + raise ValueError(f"VRR step cannot be less than {self.__vrr_step.min_value} and more than " + f"{self.__vrr_step.max_value}.") + self.__vrr_step.default_value = vrr_step + + @property + def vrr_time_step(self) -> int: + """ + Set and get VRR time step. + + Returns: + object of int type + """ + return self.__vrr_time_step.default_value + + @vrr_time_step.setter + def vrr_time_step(self, vrr_time_step: int): + if not(self.__vrr_time_step.min_value < vrr_time_step < self.__vrr_time_step.max_value): + raise ValueError(f"VRR time step cannot be less than {self.__vrr_time_step.min_value} and more than " + f"{self.__vrr_time_step.max_value}.") + self.__vrr_time_step.default_value = vrr_time_step + + @property + def vrr_enable_cups(self): + """ + Set and get VRR caps. + + Returns: + object of `VrrCaps` type + """ + return self.__vrr_enable_cups + + @vrr_enable_cups.setter + def vrr_enable_cups(self, vrr_enable_cups: VrrCaps): + self.__vrr_enable_cups = vrr_enable_cups + + @property + def base_vfront(self): + """ + Set and get base vfront value. + + Returns: + object of BaseFront type + """ + return self.__base_vfront + + @base_vfront.setter + def base_vfront(self, base_vfront: BaseFront): + self.__base_vfront = base_vfront + + @property + def base_rate(self) -> int: + """ + Set and get base rate. + + Returns: + object of int type + """ + return self.__base_rate.default_value + + @base_rate.setter + def base_rate(self, base_rate: int): + if not(self.__base_rate.min_value < base_rate < self.__base_rate.max_value): + raise ValueError(f"VRR time step cannot be less than {self.__vrr_time_step.min_value} and more than " + f"{self.__base_rate.max_value}.") + self.__base_rate.default_value = base_rate + + +class VrrSinkDUTTestParam(VrrTestParamBase): + """ + Class `VrrSinkDUTTestParam` describes parameters for VRR Sink and Source tests. + - Test timeout, in milliseconds `timeout`. + - Defines VRR Max value `vrr_max`. + - Defines VRR Min value `vrr_min`. + - Defines VRR Static value `vrr_static`. + - Defines step of changing frame rate `vrr_step`. + - Defines timer to change frame rate `vrr_time_step`. + - Defines VRR capabilities `vrr_enable_cups` type `VrrCaps`. + - Defines VRR Base front `base_vfront` type `BaseFront`. + - Defines Base Refresh Rate `base_rate`. + """ + def __init__(self, json_obj): + super().__init__(Param(json_obj["TSI_VRR_SINK_DUT_TIMEOUT"]), + Param(json_obj["TSI_VRR_SINK_DUT_VRR_MAX"]), + Param(json_obj["TSI_VRR_SINK_DUT_VRR_MIN"]), + Param(json_obj["TSI_VRR_SINK_DUT_VRR_STATIC"]), + Param(json_obj["TSI_VRR_SINK_DUT_VRR_STEP"]), + Param(json_obj["TSI_VRR_SINK_DUT_VRR_TIME_STEP"]), + VrrCaps(json_obj["TSI_VRR_SINK_DUT_VRR_ENABLE"]), + BaseFront(json_obj["TSI_VRR_SINK_DUT_BASE_VFRONT"]), + Param(json_obj["TSI_VRR_SINK_DUT_BASE_RATE"])) + + +class VrrSourceDUTTestParam(VrrTestParamBase): + """ + Class `VrrSourceDUTTestParam` describes parameters for VRR Sink and Source tests. + - Test timeout, in milliseconds `timeout`. + - Defines VRR Max value `vrr_max`. + - Defines VRR Min value `vrr_min`. + - Defines VRR Static value `vrr_static`. + - Defines step of changing frame rate `vrr_step`. + - Defines timer to change frame rate `vrr_time_step`. + - Defines VRR capabilities `vrr_enable_cups` type `VrrCaps`. + - Defines VRR Base front `base_vfront` type `BaseFront`. + - Defines Base Refresh Rate `base_rate`. + """ + def __init__(self, json_obj): + super().__init__(Param(json_obj["TSI_VRR_SRC_DUT_TIMEOUT"]), + Param(json_obj["TSI_VRR_SRC_DUT_VRR_MAX"]), + Param(json_obj["TSI_VRR_SRC_DUT_VRR_MIN"]), + Param(json_obj["TSI_VRR_SRC_DUT_VRR_STATIC"]), + Param(json_obj["TSI_VRR_SRC_DUT_VRR_STEP"]), + Param(json_obj["TSI_VRR_SRC_DUT_VRR_TIME_STEP"]), + VrrCaps(json_obj["TSI_VRR_SRC_DUT_VRR_ENABLE"]), + BaseFront(json_obj["TSI_VRR_SRC_DUT_BASE_VFRONT"]), + Param(json_obj["TSI_VRR_SRC_DUT_BASE_RATE"])) diff --git a/UniTAP/dev/modules/dut_tests/dut_tests.py b/UniTAP/dev/modules/dut_tests/dut_tests.py new file mode 100644 index 0000000..5ddd551 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/dut_tests.py @@ -0,0 +1,572 @@ +import concurrent.futures +import copy +import os +import json +import re +import time + +from typing import TypeVar, Generic, Optional, Type, Tuple, List + +from UniTAP.utils import tsi_logging as logging + +from UniTAP.dev.modules.dut_tests.test_info import TestResultObject, TestGroupId +from UniTAP.dev.modules.dut_tests.private_info import MergedTestGroups, group_params_dict, SubTestResultObject, \ + MERGED_GROUP_PARAMS_TYPE, test_group_to_merged_group +from .private_info import TestResultObject, TestGroupId, MergedTestGroups, group_params_dict, SubTestResultObject, \ + MERGED_GROUP_PARAMS_TYPE, test_group_to_merged_group +from UniTAP.dev.modules.dut_tests.test_group_params_types import dict_types, read_image_file, uicl_image_preparation +from UniTAP.libs.lib_tsi.tsi_io import DeviceIO, TestDialogConfig +from UniTAP.version import __version__ +from .dut_default_params import * +from .test_utils import is_need_to_update_test_configuration, TestReportConfigType, convert_to_cdf_json_base64 +from .report import update_configuration, dp21_source_timing_table, detect_warning_message +from .report import generate_html_report, TestAdditionalInfo, TestResult +from UniTAP.utils import tsi_logging as logging +from .test_info import TestStatusEnum +from .parser import Parser + + +class Test: + """ + Class `TestGroup` describes template of usual test. + Contains info of: + - Test name `name`. + - Test ID `id` + """ + + def __init__(self, name: str, test_id: int): + self.__name = name + self.__id = test_id + + @property + def name(self): + return self.__name + + @property + def id(self): + return self.__id + + def __str__(self): + return f"Test ID: {self.id} - {self.name}\n" + + +class TestGroup(Generic[DUTTestParameters]): + """ + Class `TestGroup` describes template of usual test group. + Contains info of: + - Test count `test_count`. + - Tests `tests`. + - Name `name`. + - ID `id`. + """ + + def __init__(self, _id: TestGroupId, name: str): + self.__name = name + self.__id = _id + self.__tests = list() + + @property + def test_count(self) -> int: + """ + Test count of test group. + + Returns: + object of int type + """ + return len(self.__tests) + + @property + def tests(self): + """ + Returns tests. + + Returns: + object of `` type + """ + return self.__tests + + @property + def name(self) -> str: + """ + Returns test group name. + + Returns: + object of str type + """ + return self.__name + + @property + def id(self) -> TestGroupId: + """ + Returns ID of test group. + + Returns: + object of id type + """ + return self.__id + + def _add_new_test(self, new_test: Test): + if not self.__test_presence_check(new_test): + self.__tests.append(new_test) + + def __test_presence_check(self, test_to_check: Test) -> bool: + for test in self.__tests: + if test.id == test_to_check.id: + return True + return False + + def __search_test_by_id(self, test_id: int) -> Optional[Test]: + for test in self.__tests: + if test.id == test_id: + return test + return None + + def __str__(self): + tests_info = "" + for test in self.tests: + tests_info += test.__str__() + return f"Test group name: {self.name}. ID Group: {self.id}\n" \ + f"Number of tests in a group: {self.test_count}\n" \ + f"{tests_info}" + + +class DUTTests: + """ + Class `DUTTests` allows working with available tests on the device. + - Run `run`. + - Get default parameters for selected test group `get_default_parameters`. + - Get available test count in selected group `number_tests_in_group`. + - Get all test results `get_all_tests_results`. + - Clear all test results `clear_results`. + - Get information of available test groups `info_of_available_test_groups`. + - Make report after testing `make_report`. + - Run test from file `run_from_file` - Not implemented. Will be added later. + """ + + def __init__(self, dev_io: DeviceIO): + self.__io = dev_io + self.__opf_device = None + self.__results = [] + self.__default_parameters = {} + self.__test_groups = {} + self.__parser = Parser() + + self.__io.set_test_config(0) + cfp_path = os.path.join(os.path.dirname(__file__), 'cfg') + files = os.listdir(cfp_path) + for file in files: + try: + if not os.path.isfile(os.path.join(cfp_path, file)): + continue + + with open(os.path.join(cfp_path, file), encoding='UTF-8') as cfg_json: + json_obj = json.load(cfg_json) + item_group = MergedTestGroups(int(json_obj.get('id'), 16)) + group_type = MERGED_GROUP_PARAMS_TYPE.get(item_group, None) + + if group_type is None: + continue + + group_parameters_dict = {} + for parameter in json_obj.get('descriptions'): + group_parameters_dict[parameter["id"]] = parameter + + self.__default_parameters[group_type] = group_type(group_parameters_dict) + except ValueError: + logging.info( + f"[UniTAP] Failed to load {os.path.join(cfp_path, file)}.\n Value {int(json_obj.get('id'), 16)} is missing in MergedTestGroups.") + + test_list = self.__io.get_test_list() + + for test in test_list: + test_id = test[0] & 0xFFFF + group_id = TestGroupId(test[0] >> 16) + name = test[2] + + if test[2].find(' / ') != -1: + name = test[2].split(" / ") + else: + name = [name] + if test[2].find("Validate audio") != -1: + name.insert(0, "Audio Test") + else: + name.insert(0, "Pixel Level Video Test") + group_id = TestGroupId.PIXEL_VIDEO_TEST + + group_name = name[0] + test_name = name[1] + + if self.__test_groups.get(group_id, None) is None: + self.__test_groups[group_id] = TestGroup(_id=group_id, name=group_name) + + self.__test_groups[group_id]._add_new_test(Test(name=test_name, test_id=test_id)) + + def run(self, group_id: TestGroupId, test_id: int, params: Optional[DUTTestParameters] = None, + print_fw_logs: bool = True, test_delay: int = 0) -> SubTestResultObject: + """ + Run selected test of test group. + + Args: + group_id (TestGroupId) - test group id + test_id (int) + params (DUTTestParameters) - one of the variants of params + print_fw_logs (bool) - print FW logs (enable/disable) + test_delay (int) - delay between tests (in seconds) + """ + if self.__test_groups.get(group_id, None) is None: + raise ValueError(f"Group {group_id} is not available.") + + if group_id in [TestGroupId.AUDIO_TEST, TestGroupId.PIXEL_VIDEO_TEST]: + if group_id == TestGroupId.AUDIO_TEST and test_id != 3 or \ + group_id == TestGroupId.PIXEL_VIDEO_TEST and test_id != 2: + raise ValueError(f"Test with id {test_id} is not available in {self.__test_groups.get(group_id).name}.") + group = self.__test_groups.get(group_id, None) + test_name = self.__test_groups.get(group_id).tests[0].name + group_id = TestGroupId.AUDIO_TEST + else: + if test_id > self.__test_groups.get(group_id).test_count: + raise ValueError(f"Test with id {test_id} is not available in {self.__test_groups.get(group_id).name}.") + + group = self.__test_groups.get(group_id, None) + test_name = self.__test_groups.get(group_id).tests[test_id].name + + merged_group_id = test_group_to_merged_group(group_id, test_id) + + if params is not None: + params_list = get_param_list(params) + if isinstance(params, Dp21SourceDUTTestParam) and not params.dsc_video_modes._Dp21AvailableDscVideoModes__is_config_changed: + logging.warning("Detected that used didn't change new DSC config. Copy setting from video config.") + dsc_dp2_ci_remapper = { + "TSI_DP20_SRCCTS_DSC_VMT_1920_1080_30": "TSI_DP20_SRCCTS_VMT_1920_1080_30", + "TSI_DP20_SRCCTS_DSC_VMT_1920_1080_60": "TSI_DP20_SRCCTS_VMT_1920_1080_60", + "TSI_DP20_SRCCTS_DSC_VMT_1920_1080_120": "TSI_DP20_SRCCTS_VMT_1920_1080_120", + "TSI_DP20_SRCCTS_DSC_VMT_1920_1080_144": "TSI_DP20_SRCCTS_VMT_1920_1080_144", + "TSI_DP20_SRCCTS_DSC_VMT_1920_1080_240": "TSI_DP20_SRCCTS_VMT_1920_1080_240", + "TSI_DP20_SRCCTS_DSC_VMT_3840_2160_30": "TSI_DP20_SRCCTS_VMT_3840_2160_30", + "TSI_DP20_SRCCTS_DSC_VMT_3840_2160_60": "TSI_DP20_SRCCTS_VMT_3840_2160_60", + "TSI_DP20_SRCCTS_DSC_VMT_3840_2160_120": "TSI_DP20_SRCCTS_VMT_3840_2160_120", + "TSI_DP20_SRCCTS_DSC_VMT_3840_2160_144": "TSI_DP20_SRCCTS_VMT_3840_2160_144", + "TSI_DP20_SRCCTS_DSC_VMT_3840_2160_240": "TSI_DP20_SRCCTS_VMT_3840_2160_240", + "TSI_DP20_SRCCTS_DSC_VMT_5120_2160_30": "TSI_DP20_SRCCTS_VMT_5120_2160_30", + "TSI_DP20_SRCCTS_DSC_VMT_5120_2160_60": "TSI_DP20_SRCCTS_VMT_5120_2160_60", + "TSI_DP20_SRCCTS_DSC_VMT_5120_2160_120": "TSI_DP20_SRCCTS_VMT_5120_2160_120", + "TSI_DP20_SRCCTS_DSC_VMT_5120_2160_144": "TSI_DP20_SRCCTS_VMT_5120_2160_144", + "TSI_DP20_SRCCTS_DSC_VMT_5120_2160_240": "TSI_DP20_SRCCTS_VMT_5120_2160_240", + "TSI_DP20_SRCCTS_DSC_VMT_7680_4320_30": "TSI_DP20_SRCCTS_VMT_7680_4320_30", + "TSI_DP20_SRCCTS_DSC_VMT_7680_4320_60": "TSI_DP20_SRCCTS_VMT_7680_4320_60", + "TSI_DP20_SRCCTS_DSC_VMT_7680_4320_120": "TSI_DP20_SRCCTS_VMT_7680_4320_120", + "TSI_DP20_SRCCTS_DSC_VMT_10240_4320_30": "TSI_DP20_SRCCTS_VMT_10240_4320_30", + "TSI_DP20_SRCCTS_DSC_VMT_10240_4320_60": "TSI_DP20_SRCCTS_VMT_10240_4320_60" + } + + for key in dsc_dp2_ci_remapper.keys(): + dst = next((obj for obj in params_list if obj.config_id_name == key), None) + src = next((obj for obj in params_list if obj.config_id_name == dsc_dp2_ci_remapper[key]), None) + + if dst.default_value != src.default_value: + logging.warn(f"DSC Config missmatch detected. Apply workaround: {key}:{hex(dst.default_value)}; {dsc_dp2_ci_remapper[key]}:{hex(src.default_value)}") + dst.default_value = src.default_value + else: + logging.warn(f"DSC Config modification detected. Skip workaround.") + else: + params_list = self.__default_parameters[merged_group_id] + + for item in params_list: + if group_id == TestGroupId.AUDIO_TEST and item.config_id_name == "TSI_REF1_FRAME_DATA": + if isinstance(item.default_value, str) and os.path.exists(item.default_value): + image_data = read_image_file(item.default_value) + if len(image_data) > 0: + item.default_value = uicl_image_preparation(image_data, params) + elif isinstance(item, bytearray): + # TODO: add 'bytearray' support + item.default_value = uicl_image_preparation(item.default_value, params) + + self.__io.set(item.config_id, item.default_value, dict_types.get(item.value_type), + item.data_length) + + report_type = is_need_to_update_test_configuration(params_list) + result_obj = SubTestResultObject(group.name, detect_warning_message(report_type, params_list)) + result_obj.test_name = test_name + result_obj.json_config_info = convert_to_cdf_json_base64(params_list, merged_group_id) + + if report_type != TestReportConfigType.Another: + result_obj.config_info = update_configuration(report_type, params_list) + else: + for item in params_list: + result_obj.config_info += "{} = {}
".format(item.name, item.__str__()) + + run_id = (group_id << 16) | test_id + + self.__io.set_opf_config(TestDialogConfig(disable_auto_precessing=True)) + + with concurrent.futures.ThreadPoolExecutor() as executor: + future = executor.submit(self.__io.run_test, run_id) + + try: + state = True + while future.running() or state: + state = self.__read_logs(result_obj, print_fw_logs) + except KeyboardInterrupt as e: + self.__io.abort_test() + raise + + result_obj.test_result = TestResult(future.result()) + result_obj.error_code = self.__io.get_test_error_code() + result_obj.test_delay = test_delay + + self.__results.append(result_obj) + + time.sleep(test_delay) + + return copy.deepcopy(self.__results[-1]) + + def get_params_from_file(self, path: str) -> Tuple[TestGroupId, int, DUTTestParameters]: + """ + Get test parameters from transferred file: td or json (not cdf). + + Args: + path ('str') - full path to config file + """ + self.__parser.set_file_name(path) + group_id = self.__parser.detect_group() + test_id = self.__parser.detect_test() + group_type = group_params_dict.get(group_id) + params = self.__parser.parse(self.get_default_parameters(group_type)) + return group_id, test_id, params + + # TODO - add support parsing '.td' (other test groups) file extension + + def get_params_from_cdf_file(self, path: str) -> DUTTestParameters: + """ + Get test parameters from transferred file: json (cdf). + + Args: + path ('str') - full path to config file + """ + self.__parser.set_file_name(path) + group_id = self.__parser.detect_merged_group() + group_type = MERGED_GROUP_PARAMS_TYPE.get(group_id) + params = self.__parser.parse(self.get_default_parameters(group_type)) + return params + + def get_default_parameters(self, group_type: Type[DUTTestParameters]) -> DUTTestParameters: + """ + Get predefined (default) parameters of test group. + + Args: + group_type (`DUTTestParameters`) - test group id + + Returns: + object of `DUTTestParameters` type + """ + for group_id in self.__default_parameters.keys(): + if isinstance(self.__default_parameters.get(group_id), group_type): + return copy.deepcopy(self.__default_parameters.get(group_id)) + raise ValueError(f"Group {group_type} is not available.") + + def number_tests_in_group(self, group_id: TestGroupId) -> int: + """ + Returns all count of available tests of selected test group. + + Args: + group_id (`TestGroupId`) - test group id + + Returns: + object of int type + """ + return self.__search_group_by_id(group_id).test_count + + def get_test_name(self, group_id: TestGroupId, test_id: int) -> str: + """ + Returns test name by test id in selected test group. + + Args: + group_id (`TestGroupId`) - test group id + test_id (`int`) - test id + + Returns: + object of str type + """ + if group_id in [TestGroupId.AUDIO_TEST, TestGroupId.PIXEL_VIDEO_TEST]: + if group_id == TestGroupId.AUDIO_TEST and test_id != 3 or \ + group_id == TestGroupId.PIXEL_VIDEO_TEST and test_id != 2: + raise ValueError(f"Test with id {test_id} is not available in {self.__test_groups.get(group_id).name}.") + group_id = TestGroupId.AUDIO_TEST + return self.__test_groups.get(group_id).tests[0 if test_id == 3 else 1].name + else: + if test_id > self.__test_groups.get(group_id).test_count: + raise ValueError(f"Test with id {test_id} is not available in {self.__test_groups.get(group_id).name}.") + return self.__test_groups.get(group_id).tests[test_id].name + + def get_test_id_by_name(self, group_id: TestGroupId, name: str) -> int: + """ + Returns test ID by test name id in selected test group. + + Args: + group_id (`TestGroupId`) - test group id + name (`str`) - test name or part name of the test + + Returns: + object of int type + """ + + if group_id == TestGroupId.AUDIO_TEST: + return 3 + elif group_id == TestGroupId.PIXEL_VIDEO_TEST: + return 2 + else: + for test in self.__test_groups.get(group_id).tests: + if test.name.find(name) != -1: + return test.id + + raise ValueError(f"Incorrect test name: {name}") + + def get_test_id_list_by_name(self, group_id: TestGroupId, name: str) -> List[int]: + """ + Returns list of test ID by test name id in selected test group. + + Args: + group_id (`TestGroupId`) - test group id + name (`str`) - test name or part name of the test + + Returns: + object of list[int] type + """ + if group_id == TestGroupId.AUDIO_TEST: + return [3] + elif group_id == TestGroupId.PIXEL_VIDEO_TEST: + return [2] + else: + id_list = [] + for test in self.__test_groups.get(group_id).tests: + if test.name.find(name) != -1: + id_list.append(test.id) + + return id_list + + @staticmethod + def print_test_params(params: DUTTestParameters): + """ + Print all test parameters in readable format. + + Args: + params (`DUTTestParameters`) - type of params + """ + params_list = get_param_list(params) + report_type = is_need_to_update_test_configuration(params_list) + if report_type != TestReportConfigType.Another: + info = update_configuration(report_type, params_list).replace("
", '\n') + if report_type == TestReportConfigType.Dp2_1_Source: + info = info[: info.find(" TestResultObject: + """ + Returns all test result. Combined in one `TestResultObject` object. + + Returns: + object of TestResultObject type + """ + return TestResultObject(self.__results) + + def clear_results(self): + """ + Clear all results. + """ + self.__results.clear() + + @property + def info_of_available_test_groups(self) -> str: + """ + Returns all info in string format of test groups. + + Returns: + object of str type + """ + test_groups_info = "" + for group_key in self.__test_groups.keys(): + test_groups_info += self.__test_groups.get(group_key).__str__() + '\n' + return test_groups_info + + def __search_group_by_id(self, group_id: TestGroupId) -> TestGroup: + for group in self.__test_groups: + if group == group_id: + return self.__test_groups[group] + return TestGroup(TestGroupId.UNKNOWN, 'Unknown') + + def make_report(self, path: str = "Report.html", tested_by="", dut_driver_version="", dut_firmware_version="", + dut_model_name="", dut_revision="", dut_serial_number="", remarks="", results=None): + """ + Make report after testing. + + Args: + path (str) - path to save report + tested_by (str) - who tested + dut_driver_version (str) - DUT driver version + dut_firmware_version (str) - DUT FW version + dut_model_name (str) - DUT model name + dut_revision (str) - DUT revision + dut_serial_number (str) - DUT serial number + remarks (int) - additional remarks of testing + results (list|None) - results of testing (if list is empty or None, usually use internal list of test + results, which was filled during testing) + """ + test_additional_info = TestAdditionalInfo() + + device_name, serial_number, fw_version, front_end, memory_size = self.__io.get_prepared_fw_info().split("|") + + test_additional_info.device_name = f"{device_name} [{serial_number.replace(' ', '')}]: " \ + f"{self.__io.get_role().name}" + test_additional_info.device_detailed_data = fw_version + test_additional_info.device_frontend_version = front_end + test_additional_info.memory_size = memory_size + test_additional_info.python_version = __version__ + test_additional_info.bundle_version = self.__io.get_bundle_version() + + test_additional_info.tested_by = tested_by + test_additional_info.dut_driver_version = dut_driver_version + test_additional_info.dut_firmware_version = dut_firmware_version + test_additional_info.dut_model_name = dut_model_name + test_additional_info.dut_revision = dut_revision + test_additional_info.dut_serial_number = dut_serial_number + test_additional_info.remarks = remarks + + if results is None: + generate_html_report(path, test_additional_info, self.__results) + else: + generate_html_report(path, test_additional_info, results) + + def __read_logs(self, test_result: SubTestResultObject, print_logs: bool) -> bool: + try: + msg_count = self.__io.is_log_message_available(250) + except ReferenceError as e: + return False + + if msg_count <= 0: + return False + + for _ in range(msg_count): + result, message, length = self.__io.get_test_log_message() + if result < 0: + continue + if print_logs: + print(message) + + try: + test_result.fw_logs += message + "\n" + if str(message).find("Error:") != -1: + try: + new_str = message.split("Error:")[1] + error_code = int(re.findall(r'\d+', new_str)[0]) + test_result.error_logs = new_str.replace(f"{error_code}:", "") + except BaseException as e: + pass + except BaseException as e: + pass + + return True diff --git a/UniTAP/dev/modules/dut_tests/parser/__init__.py b/UniTAP/dev/modules/dut_tests/parser/__init__.py new file mode 100644 index 0000000..2a3855a --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/parser/__init__.py @@ -0,0 +1 @@ +from .parser import Parser diff --git a/UniTAP/dev/modules/dut_tests/parser/parser.py b/UniTAP/dev/modules/dut_tests/parser/parser.py new file mode 100644 index 0000000..9ed096e --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/parser/parser.py @@ -0,0 +1,315 @@ +import os +import json +import re +import warnings + +from UniTAP.utils import tsi_logging as logging +from ..private_info import MergedTestGroups, test_group_to_merged_group +from ..test_group_params_types import DataType +from ..test_info import TestGroupId +from ..dut_default_params import * +from ..dut_default_params.crc_video_tests import CrcVideoTestBpp, BrokenFrameExportFormat +from enum import IntEnum + + +class ParserFileBase: + + def __init__(self): + self.__file_data = "" + self.__group_id = TestGroupId.UNKNOWN + self.__test_id = 0 + + +class ParserTd(ParserFileBase): + + def __init__(self): + super().__init__() + + @staticmethod + def __open_file(file_path: str) -> str: + with open(file_path, "r") as file: + data = file.read() + return data + + @property + def group_id(self) -> TestGroupId: + return self.__group_id + + @property + def test_id(self) -> int: + return self.__test_id + + def __detect_group(self) -> TestGroupId: + group_str = self.__file_data.split("\n")[0] + group_int = int(group_str[:4], 16) + if not TestGroupId.has_value(group_int): + raise ValueError(f"Incorrect test group with ID {group_int}.") + return TestGroupId(group_int) + + def __detect_test(self) -> int: + group_str = self.__file_data.split("\n")[0] + return int(group_str[4:], 16) + + def set_file_name(self, file_path: str): + self.__file_data = self.__open_file(file_path) + self.__group_id = self.__detect_group() + self.__test_id = self.__detect_test() + + def parse(self, params: DUTTestParameters) -> DUTTestParameters: + if isinstance(params, CrcVideoTestParam): + data = self.__file_data.split("\n") + if data[1][len(data[1]) - 1] == " ": + data[1] = data[1][: -2] + + params_count, timeout, frames_to_test, ref_frame_count, frame_mismatches, width, height, bpp, frame_rate, \ + rate_tolerance, crc_r, crc_g, crc_b, motion_iteration, *_ = data[1].split(" ") + + crc_values = [] + if len(data) > 2: + crc_values_str = data[2].split(" ") + crc_count = int(crc_values_str[0]) + if crc_count > 0: + for i in range(0, crc_count, 2): + crc_values.append(int(crc_values_str[i + 1]) & 0xFFFF) + crc_values.append((int(crc_values_str[i + 1]) >> 16) & 0xFFFF) + crc_values.append(int(crc_values_str[i + 2]) & 0xFFFF) + else: + crc_values.extend([int(crc_r), int(crc_g), int(crc_b)]) + else: + crc_values.extend([int(crc_r), int(crc_g), int(crc_b)]) + + params.timeout = int(timeout) + params.number_frames_to_test = int(frames_to_test) + params.number_reference_frames = int(ref_frame_count) + params.number_frames_mismatch = int(frame_mismatches) + params.reference_width = int(width) + params.reference_height = int(height) + params.reference_color_depth = CrcVideoTestBpp(int(bpp)) + params.required_frame_rate = int(frame_rate) + params.frame_rate_tolerance = int(rate_tolerance) + params.motion_test_iteration = int(motion_iteration) + params.reference_crc_values = crc_values + + return params + elif isinstance(params, HdmiSinkContinuityDUTTestParam): + data = self.__file_data.split("\n") + if data[1][len(data[1]) - 1] == " ": + data[1] = data[1][: -2] + + params_count, test_time, status_period, stop_flag, enable_scdc_version, enable_scdc_status, \ + enable_scdc_error_count, scdc_error_count, *_ = data[1].split(" ") + + params.test_time = int(test_time) + params.status_period = int(status_period) + params.stop_flag = bool(stop_flag) + params.enable_scdc_version = bool(enable_scdc_version) + params.enable_scdc_status = bool(enable_scdc_status) + params.enable_scdc_error_count = bool(enable_scdc_error_count) + params.scdc_error_count = int(scdc_error_count) + + return params + else: + raise NotImplementedError(f"Group {params.__name__} is not currently supported.") + + +class ParserJson(ParserFileBase): + + class JsonFormat(IntEnum): + UNKNOWN = -1 + EXPORT = 0 + CDF = 1 + + __DSC_VMT_CI_LIST = [ "TSI_DP20_SRCCTS_DSC_VMT_1920_1080_30", + "TSI_DP20_SRCCTS_DSC_VMT_1920_1080_60", + "TSI_DP20_SRCCTS_DSC_VMT_1920_1080_120", + "TSI_DP20_SRCCTS_DSC_VMT_1920_1080_144", + "TSI_DP20_SRCCTS_DSC_VMT_1920_1080_240", + "TSI_DP20_SRCCTS_DSC_VMT_3840_2160_30", + "TSI_DP20_SRCCTS_DSC_VMT_3840_2160_60", + "TSI_DP20_SRCCTS_DSC_VMT_3840_2160_120", + "TSI_DP20_SRCCTS_DSC_VMT_3840_2160_144", + "TSI_DP20_SRCCTS_DSC_VMT_3840_2160_240", + "TSI_DP20_SRCCTS_DSC_VMT_5120_2160_30", + "TSI_DP20_SRCCTS_DSC_VMT_5120_2160_60", + "TSI_DP20_SRCCTS_DSC_VMT_5120_2160_120", + "TSI_DP20_SRCCTS_DSC_VMT_5120_2160_144", + "TSI_DP20_SRCCTS_DSC_VMT_5120_2160_240", + "TSI_DP20_SRCCTS_DSC_VMT_7680_4320_30", + "TSI_DP20_SRCCTS_DSC_VMT_7680_4320_60", + "TSI_DP20_SRCCTS_DSC_VMT_7680_4320_120", + "TSI_DP20_SRCCTS_DSC_VMT_10240_4320_30", + "TSI_DP20_SRCCTS_DSC_VMT_10240_4320_60" ] + + def __init__(self): + super().__init__() + self.__type = self.JsonFormat.UNKNOWN + self.__merged_group_id = MergedTestGroups.Unknown + self.__test_id = -1 + self.__group_id = TestGroupId.UNKNOWN + + def __open_file(self, file_path: str) -> dict: + with open(file_path, encoding='UTF-8') as cfg_json: + data = json.load(cfg_json) + + if data.get('TestId') is not None: + self.__type = self.JsonFormat.EXPORT + elif data.get('Header_Type') is not None: + self.__type = self.JsonFormat.CDF + + return data + + @property + def group_id(self) -> TestGroupId: + return self.__group_id + + @property + def merged_group_id(self) -> MergedTestGroups: + return self.__merged_group_id + + @property + def test_id(self) -> int: + return self.__test_id + + @property + def json_type(self) -> JsonFormat: + return self.__type + + def __detect_group(self) -> TestGroupId: + group_int = int(self.__file_data.get('TestId')[:4], 16) + if group_int == 0x0 and self.__file_data.get('Data').get("TSI_REF1_FRAME_DATA") is not None: + group_int = 0x3E8 + if not TestGroupId.has_value(group_int): + raise ValueError(f"Incorrect test group with ID {group_int}.") + return TestGroupId(group_int) + + def __detect_merged_group(self) -> MergedTestGroups: + return MergedTestGroups(int(re.findall(r"0x\d+\w+_", self.__file_data.get("Header_Type"))[0][:-1], 16)) + + def __detect_test(self) -> int: + return int(self.__file_data.get('TestId')[4:], 16) + + def set_file_name(self, file_path: str): + self.__file_data = self.__open_file(file_path) + if self.json_type == self.JsonFormat.EXPORT: + self.__group_id = self.__detect_group() + self.__test_id = self.__detect_test() + elif self.json_type == self.JsonFormat.CDF: + self.__merged_group_id = self.__detect_merged_group() + else: + raise ValueError(f"Incorrect json file type {self.__type.name}.") + + def parse(self, params: DUTTestParameters) -> DUTTestParameters: + try: + param_list = get_param_list(params) + + if self.json_type == self.JsonFormat.EXPORT: + json_data = self.__file_data.get('Data') + else: + json_data = self.__file_data + + for param in param_list: + if param.config_id_name in json_data.keys(): + value = self.__json_to_param_value(param, json_data.get(param.config_id_name)) + if param.config_id_name == "TSI_REF1_ELEMENT_FORMAT": + param.direct_set_def_value(value) + else: + param.default_value = value + else: + if (self.__merged_group_id == MergedTestGroups.DPRX_128b132b_LL_CTS + or self.__group_id == TestGroupId.DP_2_1_RX_DSC_CTS): + if param.config_id_name in self.__DSC_VMT_CI_LIST: + param.default_value = self.__json_to_param_value(param, json_data.get(param.config_id_name.replace("DSC_", ""))) + + logging.warn(f"Required CDF key {param.config_id_name} is missing in provided CDF file. It will be skipped.") + + return params + except BaseException as e: + raise ValueError(f"Group {str(params)} cannot be parsed. Error {e}") + + @staticmethod + def __json_to_param_value(param, json_value): + if param.value_type in [ DataType.ArrayU8, DataType.ArrayU16, DataType.ArrayU32 ]: + str_vals = json_value.split(" ")[ :-1 ] + return [ int(i) for i in str_vals ] + elif param.value_type in [ DataType.Int, DataType.Uint32, DataType.BitList ]: + return int(json_value) + elif param.value_type == DataType.Bool: + return bool(json_value) + elif param.value_type == DataType.Double: + return float(json_value) + elif param.value_type == DataType.ArrayFromFilePath: + return str(json_value) + else: + return json_value + + +class Parser: + class ConfigFileFormat(IntEnum): + UNKNOWN = -1 + TD = 0 + JSON = 1 + + def __init__(self): + self.__extension = "" + self.__parser_td = ParserTd() + self.__parser_json = ParserJson() + + @staticmethod + def __detect_file_format(file_path: str) -> ConfigFileFormat: + filename, file_extension = os.path.splitext(file_path) + if file_extension.lower() == '.json': + return Parser.ConfigFileFormat.JSON + elif file_extension.lower() == '.td': + return Parser.ConfigFileFormat.TD + else: + return Parser.ConfigFileFormat.UNKNOWN + + def set_file_name(self, file_path: str): + self.__extension = self.__detect_file_format(file_path) + if self.__extension == Parser.ConfigFileFormat.JSON: + self.__parser_json.set_file_name(file_path) + elif self.__extension == Parser.ConfigFileFormat.TD: + self.__parser_td.set_file_name(file_path) + else: + raise ValueError("Incorrect file format. Must be: TD or JSON.") + + def detect_group(self) -> TestGroupId: + if self.__extension == Parser.ConfigFileFormat.JSON: + if self.__parser_json.json_type == ParserJson.JsonFormat.EXPORT: + return self.__parser_json.group_id + else: + raise ValueError("Cannot get Test Group ID from JSON (CDF type) file") + elif self.__extension == Parser.ConfigFileFormat.TD: + return self.__parser_td.group_id + else: + raise ValueError("Incorrect file format. Must be: TD or JSON.") + + def detect_merged_group(self) -> MergedTestGroups: + if self.__extension == Parser.ConfigFileFormat.JSON: + if self.__parser_json.json_type == ParserJson.JsonFormat.CDF: + return self.__parser_json.merged_group_id + else: + return test_group_to_merged_group(self.__parser_json.group_id, self.__parser_json.test_id) + elif self.__extension == Parser.ConfigFileFormat.TD: + return test_group_to_merged_group(self.__parser_json.group_id, self.__parser_json.test_id) + else: + raise ValueError("Incorrect file format. Must be: TD or JSON.") + + def detect_test(self) -> int: + if self.__extension == Parser.ConfigFileFormat.JSON: + if self.__parser_json.json_type == ParserJson.JsonFormat.EXPORT: + return self.__parser_json.test_id + else: + raise ValueError("Cannot get Test ID from JSON (CDF type) file") + elif self.__extension == Parser.ConfigFileFormat.TD: + return self.__parser_td.test_id + else: + raise ValueError("Incorrect file format. Must be: TD or JSON.") + + def parse(self, params: DUTTestParameters) -> DUTTestParameters: + if self.__extension == Parser.ConfigFileFormat.JSON: + return self.__parser_json.parse(params) + elif self.__extension == Parser.ConfigFileFormat.TD: + return self.__parser_td.parse(params) + else: + raise ValueError("Incorrect file format. Must be: TD or JSON.") diff --git a/UniTAP/dev/modules/dut_tests/private_info.py b/UniTAP/dev/modules/dut_tests/private_info.py new file mode 100644 index 0000000..b85a673 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/private_info.py @@ -0,0 +1,174 @@ +from .test_info import * + + +class MergedTestGroups(IntEnum): + Unknown = 0x00 + CRCVideoTest = 0x01 + AudioTest = 0x02 + DPRX_8b10b_LL_CTS = 0x03 + LinkConfigTest = 0x04 + DPRX_ELECTRICAL = 0x05 + HDRX_ELECTRICAL = 0x06 + USBC_ELECTRICAL = 0x07 + DPRX_128b132b_LL_CTS = 0x08 + DP_HDCP_CTS_1A = 0x09 + DP_HDCP_CTS_1B = 0x0A + DP_HDCP_CTS_3A = 0x0B + DP_HDCP_CTS_3B = 0x0C + DP_HDCP_CTS_2C = 0x0D + DP_HDCP_CTS_3C = 0x0E + HD_TX_VRR = 0x0F + HD_RX_VRR = 0x10 + HD_RX_CEC = 0x11 + PixelVideoTest = 0x12 + DPTX_8b10b_LL_CTS = 0x13 + HDR10PlusSourceTest = 0x14, + LTTPR_128b132b_LL_CTS = 0x15, + HDR10PlusSSTMTest = 0x16, + EPRPowerSink = 0x17, + DPTX_128b132b_LL_CTS = 0x18, + EPRPowerSource = 0x19, + HDRX_DSC_CTS = 0x20, + HDTX_DSC_CTS = 0x21, + HDTX_CONTINUITY = 0x22, + HDTX_CABLE_CHECK = 0x24 + + +MERGED_GROUP_PARAMS_TYPE = { + MergedTestGroups.CRCVideoTest: CrcVideoTestParam, + MergedTestGroups.AudioTest: AudioTestParam, + MergedTestGroups.DPRX_8b10b_LL_CTS: Dp14SourceDUTTestParam, + MergedTestGroups.LinkConfigTest: LinkConfigTestParam, + MergedTestGroups.DPRX_ELECTRICAL: DpElectricalTestParam, + MergedTestGroups.HDRX_ELECTRICAL: HdmiElectricalTestParam, + MergedTestGroups.USBC_ELECTRICAL: UsbcElectricalTestParam, + MergedTestGroups.DPRX_128b132b_LL_CTS: Dp21SourceDUTTestParam, + MergedTestGroups.DP_HDCP_CTS_1A: Hdcp1ATestParam, + MergedTestGroups.DP_HDCP_CTS_1B: Hdcp1BTestParam, + MergedTestGroups.DP_HDCP_CTS_3A: Hdcp3ATestParam, + MergedTestGroups.DP_HDCP_CTS_3B: Hdcp3BTestParam, + MergedTestGroups.DP_HDCP_CTS_2C: Hdcp2CTestParam, + MergedTestGroups.DP_HDCP_CTS_3C: Hdcp3CTestParam, + MergedTestGroups.HD_TX_VRR: VrrSinkDUTTestParam, + MergedTestGroups.HD_RX_VRR: VrrSourceDUTTestParam, + MergedTestGroups.HD_RX_CEC: CecFunctionalTestParam, + MergedTestGroups.PixelVideoTest: VideoPixelTestParam, + MergedTestGroups.DPTX_8b10b_LL_CTS: Dp14SinkTestParam, + MergedTestGroups.HDR10PlusSourceTest: Hdr10TestParam, + MergedTestGroups.DPTX_128b132b_LL_CTS: Dp21SinkTestParam, + MergedTestGroups.LTTPR_128b132b_LL_CTS: DpLttprTestParam, + MergedTestGroups.HDRX_DSC_CTS: HdmiSourceDUTTestParam, + MergedTestGroups.HDTX_DSC_CTS: HdmiSinkDUTTestParam, + MergedTestGroups.HDTX_CONTINUITY: HdmiSinkContinuityDUTTestParam, + MergedTestGroups.HDTX_CABLE_CHECK: HdmiSinkCableCheckTestParam +} + +group_params_dict = { + TestGroupId.AUDIO_TEST: AudioTestParam, + TestGroupId.DP_RX_ELECTRICAL: DpElectricalTestParam, + TestGroupId.HDMI_RX_ELECTRICAL: HdmiElectricalTestParam, + TestGroupId.CEC_FUNCTIONAL: CecFunctionalTestParam, + TestGroupId.DP_RX_CRC: CrcVideoTestParam, + TestGroupId.DP_RX_SIMPLE_LT: LinkConfigTestParam, + TestGroupId.HDMI_RX_CRC: CrcVideoTestParam, + TestGroupId.USB_TYPE_C_ELECTRICAL: UsbcElectricalTestParam, + TestGroupId.DP_RX_LL_CTS: Dp14SourceDUTTestParam, + TestGroupId.DP_TX_LL_CTS: Dp14SinkTestParam, + TestGroupId.DP_TX_LL_CTS_DSC: Dp14SinkTestParam, + TestGroupId.DP_RX_LL_CTS_DSC: Dp14SourceDUTTestParam, + TestGroupId.DP_2_1_RX_LL_CTS: Dp21SourceDUTTestParam, + TestGroupId.DP_2_1_TX_LL_CTS: Dp21SinkTestParam, + TestGroupId.DP_HDCP_CTS_1A: Hdcp1ATestParam, + TestGroupId.DP_HDCP_CTS_1B: Hdcp1BTestParam, + TestGroupId.DP_HDCP_CTS_2C: Hdcp2CTestParam, + TestGroupId.DP_HDCP_CTS_3A: Hdcp3ATestParam, + TestGroupId.DP_HDCP_CTS_3B: Hdcp3BTestParam, + TestGroupId.DP_HDCP_CTS_3C: Hdcp3CTestParam, + TestGroupId.DP_TX_DISPLAYID: Dp14SinkTestParam, + TestGroupId.DP_RX_DISPLAYID: Dp14SourceDUTTestParam, + TestGroupId.HDMI_RX_VRR: VrrSourceDUTTestParam, + TestGroupId.HDMI_TX_VRR: VrrSinkDUTTestParam, + TestGroupId.DP_TX_ADAPTIVESYNC: Dp14SinkTestParam, + TestGroupId.DP_RX_ADAPTIVESYNC: Dp14SourceDUTTestParam, + TestGroupId.DP_2_1_RX_LTTPR_CTS: Dp21SourceDUTTestParam, + TestGroupId.DP_2_1_TX_LTTPR_CTS: Dp21SinkTestParam, + TestGroupId.DP_2_1_RX_DSC_CTS: Dp21SourceDUTTestParam, + TestGroupId.DP_2_1_TX_DSC_CTS: Dp21SinkTestParam, + TestGroupId.DP_2_1_RX_DISPAYID: Dp21SourceDUTTestParam, + TestGroupId.DP_2_1_TX_DISPAYID: Dp21SinkTestParam, + TestGroupId.DP_2_1_RX_ADAPTIVESYNC: Dp21SourceDUTTestParam, + TestGroupId.DP_2_1_TX_ADAPTIVESYNC: Dp21SinkTestParam, + TestGroupId.PIXEL_VIDEO_TEST: VideoPixelTestParam, + TestGroupId.HDR10_TEST: Hdr10TestParam, + TestGroupId.DP_2_1_LTTPR_CTS: DpLttprTestParam, + TestGroupId.HD_RX_DSC_CTS: HdmiSourceDUTTestParam, + TestGroupId.HD_TX_DSC_CTS: HdmiSinkDUTTestParam, + TestGroupId.HD_TX_CONTINUITY: HdmiSinkContinuityDUTTestParam, + TestGroupId.HD_TX_CABLE_CHECK: HdmiSinkCableCheckTestParam +} + + +def test_group_to_merged_group(fw_group: TestGroupId, test_id: int) -> MergedTestGroups: + merged_group_id = MergedTestGroups.Unknown + if fw_group == TestGroupId.AUDIO_TEST or fw_group == TestGroupId.PIXEL_VIDEO_TEST: + if test_id == 0x02: + merged_group_id = MergedTestGroups.PixelVideoTest + elif test_id == 0x12: + merged_group_id = MergedTestGroups.AudioTest + elif fw_group in [TestGroupId.DP_RX_LL_CTS, TestGroupId.DP_RX_LL_CTS_DSC, TestGroupId.DP_RX_ADAPTIVESYNC, + TestGroupId.DP_RX_DISPLAYID]: + merged_group_id = MergedTestGroups.DPRX_8b10b_LL_CTS + elif fw_group in [TestGroupId.DP_TX_LL_CTS, TestGroupId.DP_TX_LL_CTS_DSC, TestGroupId.DP_TX_ADAPTIVESYNC, + TestGroupId.DP_TX_DISPLAYID]: + merged_group_id = MergedTestGroups.DPTX_8b10b_LL_CTS + elif fw_group == TestGroupId.DP_RX_CRC: + merged_group_id = MergedTestGroups.CRCVideoTest + elif fw_group == TestGroupId.HDMI_RX_CRC: + merged_group_id = MergedTestGroups.CRCVideoTest + elif fw_group in [TestGroupId.DP_2_1_RX_LL_CTS, TestGroupId.DP_2_1_RX_DSC_CTS, + TestGroupId.DP_2_1_RX_LTTPR_CTS, TestGroupId.DP_2_1_RX_DISPAYID, + TestGroupId.DP_2_1_RX_ADAPTIVESYNC]: + merged_group_id = MergedTestGroups.DPRX_128b132b_LL_CTS + elif fw_group in [TestGroupId.DP_2_1_TX_LL_CTS, TestGroupId.DP_2_1_TX_DSC_CTS, + TestGroupId.DP_2_1_TX_LTTPR_CTS, TestGroupId.DP_2_1_TX_DISPAYID, + TestGroupId.DP_2_1_TX_ADAPTIVESYNC]: + merged_group_id = MergedTestGroups.DPTX_128b132b_LL_CTS + elif fw_group == TestGroupId.DP_2_1_LTTPR_CTS: + merged_group_id = MergedTestGroups.LTTPR_128b132b_LL_CTS + elif fw_group == TestGroupId.DP_HDCP_CTS_1A: + merged_group_id = MergedTestGroups.DP_HDCP_CTS_1A + elif fw_group == TestGroupId.DP_HDCP_CTS_1B: + merged_group_id = MergedTestGroups.DP_HDCP_CTS_1B + elif fw_group == TestGroupId.DP_HDCP_CTS_2C: + merged_group_id = MergedTestGroups.DP_HDCP_CTS_2C + elif fw_group == TestGroupId.DP_HDCP_CTS_3A: + merged_group_id = MergedTestGroups.DP_HDCP_CTS_3A + elif fw_group == TestGroupId.DP_HDCP_CTS_3B: + merged_group_id = MergedTestGroups.DP_HDCP_CTS_3B + elif fw_group == TestGroupId.DP_HDCP_CTS_3C: + merged_group_id = MergedTestGroups.DP_HDCP_CTS_3C + elif fw_group == TestGroupId.DP_RX_ELECTRICAL: + merged_group_id = MergedTestGroups.DPRX_ELECTRICAL + elif fw_group == TestGroupId.HDMI_RX_ELECTRICAL: + merged_group_id = MergedTestGroups.HDRX_ELECTRICAL + elif fw_group == TestGroupId.USB_TYPE_C_ELECTRICAL: + merged_group_id = MergedTestGroups.USBC_ELECTRICAL + elif fw_group == TestGroupId.DP_RX_SIMPLE_LT: + merged_group_id = MergedTestGroups.LinkConfigTest + elif fw_group == TestGroupId.CEC_FUNCTIONAL: + merged_group_id = MergedTestGroups.HD_RX_CEC + elif fw_group == TestGroupId.HDMI_RX_VRR: + merged_group_id = MergedTestGroups.HD_RX_VRR + elif fw_group == TestGroupId.HDMI_TX_VRR: + merged_group_id = MergedTestGroups.HD_TX_VRR + elif fw_group == TestGroupId.HD_TX_DSC_CTS: + merged_group_id = MergedTestGroups.HDTX_DSC_CTS + elif fw_group == TestGroupId.HD_RX_DSC_CTS: + merged_group_id = MergedTestGroups.HDRX_DSC_CTS + elif fw_group == TestGroupId.HD_TX_CONTINUITY: + merged_group_id = MergedTestGroups.HDTX_CONTINUITY + elif fw_group == TestGroupId.HD_TX_CABLE_CHECK: + merged_group_id = MergedTestGroups.HDTX_CABLE_CHECK + else: + merged_group_id = MergedTestGroups.Unknown + return merged_group_id diff --git a/UniTAP/dev/modules/dut_tests/report/__init__.py b/UniTAP/dev/modules/dut_tests/report/__init__.py new file mode 100644 index 0000000..aa54cbd --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/report/__init__.py @@ -0,0 +1,2 @@ +from .report_maker import generate_html_report, TestAdditionalInfo, TestResult +from .report_utils import update_configuration, dp21_source_timing_table, detect_warning_message diff --git a/UniTAP/dev/modules/dut_tests/report/report_maker.py b/UniTAP/dev/modules/dut_tests/report/report_maker.py new file mode 100644 index 0000000..7c818af --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/report/report_maker.py @@ -0,0 +1,179 @@ +import platform +from datetime import datetime +from enum import IntEnum + +from .report_template import * + + +class TestResult(IntEnum): + Unknown = -1 + Passed = 0 + Failed = 1 + Skipped = 2 + Aborted = 3 + + +class TestAdditionalInfo: + + def __init__(self, dut_model_name: str = "", dut_revision: str = "", dut_serial_number: str = "", + dut_firmware_version: str = "", dut_driver_version: str = "", tested_by: str = "", remarks: str = ""): + self.device_name = str() + self.device_serial_number = str() + self.device_detailed_data = str() + self.device_frontend_version = str() + self.memory_size = str() + self.python_version = platform.python_version() + self.bundle_version = str() + self.dut_model_name = dut_model_name + self.dut_revision = dut_revision + self.dut_serial_number = dut_serial_number + self.dut_firmware_version = dut_firmware_version + self.dut_driver_version = dut_driver_version + self.tested_by = tested_by + self.remarks = remarks + + +def write_to_file(file_name, text: str): + with open(file_name, 'w', encoding='UTF-8') as file: + file.write(text) + + +def replace_additional_info(test_additional_info: TestAdditionalInfo): + result = DeviceDescription + + result = result.replace("#device_name#", test_additional_info.device_name) + result = result.replace("#detailed_vdata#", test_additional_info.device_detailed_data) + result = result.replace("#frontend_version#", test_additional_info.device_frontend_version) + result = result.replace("#memory_size#", test_additional_info.memory_size) + result = result.replace("#console_app_version#", test_additional_info.python_version) + result = result.replace("#console_bundle_version#", test_additional_info.bundle_version) + result = result.replace("#date_time#", datetime.now().strftime("%d-%m-%Y %H:%M")) + result = result.replace("#model_name#", test_additional_info.dut_model_name) + result = result.replace("#revision#", test_additional_info.dut_revision) + result = result.replace("#serial_number#", test_additional_info.dut_serial_number) + result = result.replace("#firmware_version#", test_additional_info.dut_firmware_version) + result = result.replace("#driver_version#", test_additional_info.dut_driver_version) + result = result.replace("#conducted_by#", test_additional_info.tested_by) + result = result.replace("#remarks#", test_additional_info.remarks) + + return result + + +def generate_html_report(path: str, test_additional_info: TestAdditionalInfo, reports: list): + result = str() + + head = ReportHead + head = head.replace("#count#", str((len(reports) + 2))) + + body = ReportBody + body = body.replace("#device_description#", replace_additional_info(test_additional_info)) + + runs = 0 + failed = 0 + skipped = 0 + aborted = 0 + + dict_result_2 = {TestResult.Failed: "FAILED", + TestResult.Skipped: "SKIPPED", + TestResult.Aborted: "ABORTED", + TestResult.Passed: "PASSED"} + + json_config_info = '[' + ",".join(f'"{report.json_config_info}"' for report in reports) + ']' + + for item in reports: + runs += 1 + if item.test_result == TestResult.Failed: + failed += 1 + elif item.test_result == TestResult.Skipped: + skipped += 1 + elif item.test_result == TestResult.Aborted: + aborted += 1 + + head = head.replace("#configInfo#", json_config_info) + result += head + + passed = runs - failed - skipped - aborted + + body = body.replace("#runs#", str(runs)) + body = body.replace("#passed#", str(passed)) + body = body.replace("#failed#", str(failed)) + body = body.replace("#skipped#", str(skipped)) + body = body.replace("#aborted#", str(aborted)) + + options_list = "" + for i in range(len(reports)): + line = OptionListLine + line = line.replace("#number#", str(i + 2)) + line = line.replace("#test_name#", f"{reports[i].group_name} / {reports[i].test_name}") + line = line.replace("#test_result#", dict_result_2.get(reports[i].test_result)) + options_list += line + "\n" + + body = body.replace("#optionList#", options_list) + + tables = Tables + table_lines = "" + for i in range(len(reports)): + line = TestDescriptionLine + line = line.replace("#handler_number#", str(i + 2)) + line = line.replace("#test_name#", reports[i].test_name) + line = line.replace("#test_group#", reports[i].group_name) + line = line.replace("#test_result#", dict_result_2.get(reports[i].test_result)) + line = line.replace("#error#", f"{reports[i].error_code}" if reports[i].error_code != 0 else "-") + line = line.replace("#ending#", "2" if i % 2 else "") + test_result = dict_result_2.get(reports[i].test_result) + line = line.replace("#SUCCESS#", test_result if test_result is not None else "PASSED") + table_lines += line + "\n" + + tables = tables.replace("#test_description_lines#", table_lines) + + def escape_html(log: str) -> str: + res = [] + + for c in log: + if c == '&': + res.append("&") + elif c == '<': + res.append("<") + elif c == '>': + res.append(">") + elif c == '"': + res.append(""") + elif c == '\'': + res.append("'") + else: + res.append(c) + + return ''.join(res) + + tables_list = "" + warnings = "" + for i in range(len(reports)): + line = TestTable + line = line.replace("#number#", f"{i + 2}") + line = line.replace("#test_number#", f"{i + 1}") + line = line.replace("#test_group#", reports[i].group_name) + line = line.replace("#test_name#", reports[i].test_name) + line = line.replace("#result#", dict_result_2.get(reports[i].test_result)) + line = line.replace("#test_delay#", f"{reports[i].test_delay * 1000}") + line = line.replace("#test_parameters#", reports[i].config_info) + line = line.replace("#warning#", "Debug options enabled!

" if reports[i].debug else "") + + if reports[i].debug: + warnings = "Debug options enabled!

" + + line = line.replace("#test_log#", escape_html(reports[i].fw_logs)) + tables_list += line + "\n" + + body = body.replace("#warning#", warnings) + tables = tables.replace("#test_tables_list#", tables_list) + tables = tables.replace("#warning#", warnings) + + result += body + result += tables + + footer = ReportFooter + footer = footer.replace("#result#", "PASSED" if failed == 0 else "FAILED") + result += footer + + full_name = path if path.lower().find(".html") != -1 else (path + ".html") + write_to_file(full_name, result) diff --git a/UniTAP/dev/modules/dut_tests/report/report_template.py b/UniTAP/dev/modules/dut_tests/report/report_template.py new file mode 100644 index 0000000..866b351 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/report/report_template.py @@ -0,0 +1,153 @@ +ReportHead = "\n" \ + "Unigraf Test Report\n" \ + "\n" \ + "\n" \ + "\n" \ + "\n" + +OptionListLine = " \n" + +TestDescriptionLine = ("\n") + +Tables = "
\n" \ + "

\n" \ + "

   Summary of individual test runs

\n" \ + "

\n#warning#

\n" \ + "

" + "#test_group# / #test_name#

" + " #test_result# 

 #error# 

\n" \ + "\n" \ + "#test_description_lines#\n" \ + "

Test name

Result

Error

\n" \ + "#test_tables_list#\n" + +DeviceDescription = "

\n" \ + "Device: #device_name#\n" \ + "
Firmware Package: #detailed_vdata#\n" \ + "
UniTAP: #console_app_version#\n" \ + "
Software Bundle: #console_bundle_version#\n" \ + "
Frontend Info: #frontend_version#\n" \ + "
Device total memory size: #memory_size#\n" \ + "

Report generated: #date_time#
\n" \ + "

   DUT

\n" \ + "Device/Model name: #model_name#\n" \ + "
HW Revision: #revision#\n" \ + "
Serial number: #serial_number#\n" \ + "
Firmware: #firmware_version#\n" \ + "
Driver: #driver_version#\n" \ + "

Testing conducted by: #conducted_by#\n" \ + "

Remarks: #remarks#\n" \ + "

\n" + +TestTable = "

\n" \ + "

   Test Details, Test #test_number#

\n" \ + "

   #test_name#

\n" \ + "

\n" \ + "#warning#

\n" \ + "

\n" \ + "Test Set: #test_group#
\n" \ + "Test Result: #result#\n" \ + "

Test Settings:

\n" \ + "

\n" \ + "Delay between tests = #test_delay#ms\n
" \ + "#test_parameters#\n" \ + "

   Test Log

" \
+            "#test_log#" \
+            "\n"
+
+ReportBody = "\n" \
+             "    \n" \
+             "    

  Unigraf Test Report

\n" \ + "
\n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + "

Select item to display

\n" \ + "
\n" \ + "
\n" \ + "\n" \ + "\n" \ + "\n" \ + "\n" \ + "
\n" \ + "
\n" \ + "
\n" \ + "
\n" \ + "

   Report summary

\n" \ + "

   Unigraf device\n" \ + "#device_description#\n" \ + "

   Test results summary

\n#warning#

\n

\n" \ + "Total number of test runs: #runs#
Passed test runs: #passed#
Failed test runs: #failed#
Skipped test runs: #skipped#
Aborted test runs: #aborted#\n" \ + "

\n" + +ReportFooter = "

\n" \ + "
\n" \ + "\n" \ + "\n" \ + "\n" \ + "\n" \ + "
\n" \ + "\n" \ + "
\n" \ + "

\n" \ + " Unigraf Oy | Piispantilankuja 4 | 02240 Espoo | Finland | +358-9-859 550
\n" \ + " E-mail: info@unigraf.fi " \ + "| Web site: www.unigraf.fi\n" \ + "

\n" \ + "\n" \ + "\n" diff --git a/UniTAP/dev/modules/dut_tests/report/report_utils.py b/UniTAP/dev/modules/dut_tests/report/report_utils.py new file mode 100644 index 0000000..0848872 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/report/report_utils.py @@ -0,0 +1,780 @@ +import re +from UniTAP.dev.modules.dut_tests.test_utils import TestReportConfigType, make_cf, CMTFormat, CMTBitDepth + + +def value_from_enumeration_var(_data, _value): + + if len(_data.str_presentation) > 0: + return _data.str_presentation[_data.available_values.index(str(_value))] + else: + return _value + + +def copy_data(data): + _data = {} + for item in data: + if item.config_id_name in ["TSI_DP14_SRCCTS_DUT_CAPS", "TSI_DP14_SRCCTS_DUT_TA", + "TSI_DP14_SRCCTS_DMT_TIMING", "TSI_DP14_SRCCTS_CTA_TIMING", + "TSI_DP14_SRCCTS_CVT_TIMING", "TSI_DP14_SRCCTS_AS_DUT_CAPS", + "TSI_DP20_SRCCTS_DUT_CAPS", "TSI_DP20_SRCCTS_DUT_TA", + "TSI_DP20_SRCCTS_DMT_TIMING", "TSI_DP20_SRCCTS_CTA_TIMING", + "TSI_DP20_SRCCTS_CVT_TIMING", "TSI_DP20_SRCCTS_AS_DUT_CAPS", + "TSI_DP20_SRCCTS_DUT_LINK_RATES", "TSI_DP14_SRCCTS_DUT_DEBUG_CONF", + "TSI_DP20_SRCCTS_DUT_DEBUG_CONF", "TSI_DP20_SINKCTS_DEBUG_CONF"]: + _data.update({item.config_id_name: [item.default_value, item.bit_field_list]}) + elif item.enumeration_variants is not None: + _data.update({item.config_id_name: [item.default_value, item.enumeration_variants]}) + else: + _data.update({item.config_id_name: item.default_value}) + return _data + + +def detect_warning_message(type_of_test: TestReportConfigType, data): + _data = copy_data(data) + _dict = {TestReportConfigType.Dp1_4_Source: "TSI_DP14_SRCCTS_DUT_DEBUG_CONF", + TestReportConfigType.Dp2_1_Source: "TSI_DP20_SRCCTS_DUT_DEBUG_CONF", + TestReportConfigType.Dp2_1_Sink: "TSI_DP20_SINKCTS_DEBUG_CONF", + TestReportConfigType.Dp2_1_Lttpr: "TSI_DP20_LTTPR_DEBUG_CR_ITERATION_DELAY"} + key = _dict.get(type_of_test) + val = _data.get(key) + if val is not None: + if key in ["TSI_DP14_SRCCTS_DUT_DEBUG_CONF", + "TSI_DP20_SRCCTS_DUT_DEBUG_CONF", + "TSI_DP20_SINKCTS_DEBUG_CONF"] and val[0] != 0: + return True + elif key == "TSI_DP20_LTTPR_DEBUG_CR_ITERATION_DELAY" and int(val[0] != 100): + return True + else: + return False + else: + return False + + +def update_configuration(type_of_test: TestReportConfigType, data): + configuration = "" + _data = copy_data(data) + if type_of_test == TestReportConfigType.Dp1_4_Source: + val = _data.get("TSI_DP14_SRCCTS_DUT_DEBUG_CONF") + if val is not None and val[0] != 0: + configuration += "
Debug options:
" + for _bit in val[1]: + if val[0] & _bit.mask: + configuration += f" - {_bit.description}
" + configuration += "
" + + configuration += "Test timeout = {}ms
".format(_data.get("TSI_DP14_SRCCTS_TIMEOUT")) + configuration += "Long HPD Duration = {}ms

".format(_data.get("TSI_DP14_SRCCTS_LONG_HPD_PULSE")) + configuration += "DUT capability settings and flags:
" + configuration += "- Max lanes = {}
".format(_data.get("TSI_DP14_SRCCTS_MAX_LANES")[0]) + configuration += "- Max link rate = {}
".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_MAX_LINK_RATE")[1], + _data.get("TSI_DP14_SRCCTS_MAX_LINK_RATE")[0])) + value = _data.get("TSI_DP14_SRCCTS_DUT_CAPS") + dut_caps_value = int(value[0]) + for _bit in _data.get("TSI_DP14_SRCCTS_DUT_CAPS")[1]: + if dut_caps_value & _bit.mask: + configuration += "- {}
".format(_bit.description) + + configuration += "
Test automation flags and settings:
" + + ta_flags = ["Always ready", "EDID read", "Link Training end", "Active Video"] + ta_flags_value = _data.get("TSI_DP14_SRCCTS_DUT_TA")[0] + for bit in _data.get("TSI_DP14_SRCCTS_DUT_TA")[1]: + if bit.mask & ta_flags_value or bit.enumeration_variants is not None: + if bit.enumeration_variants is not None: + configuration += "- Event indicating DUT ready: {}
".format( + ta_flags[(ta_flags_value >> 3) & 0x3]) + else: + configuration += "- {}
".format(bit.description) + + fail_mode = "640 x 480 @ 60Hz 6 BPC" + configuration += "
Failsafe timing: {}
".format( + fail_mode if int(_data.get("TSI_DP14_SRCCTS_FAILSAFE_MODE")[0]) else "") + configuration += "Max supported timing: {}
".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_MAX_RESOLUTION")[1], + _data.get("TSI_DP14_SRCCTS_MAX_RESOLUTION")[0])) + + configuration += "
Most packed timings, 1 lane: {}
".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_1L_MOST_PACKED")[1], + _data.get("TSI_DP14_SRCCTS_1L_MOST_PACKED")[0])) + configuration += "Most packed timings, 2 lane: {}
".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_2L_MOST_PACKED")[1], + _data.get("TSI_DP14_SRCCTS_2L_MOST_PACKED")[0])) + configuration += "Most packed timings, 4 lane: {}
".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_4L_MOST_PACKED")[1], + _data.get("TSI_DP14_SRCCTS_4L_MOST_PACKED")[0])) + + configuration += "
Time stamp generation timigns:
" + configuration += "RBR, 1 Lane: {}
".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_1L_RBB")[1], + _data.get("TSI_DP14_SRCCTS_1L_RBB")[0])) + configuration += "RBR, 2 Lane: {}
".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_2L_RBB")[1], + _data.get("TSI_DP14_SRCCTS_2L_RBB")[0])) + configuration += "RBR, 4 Lane: {}

".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_4L_RBB")[1], + _data.get("TSI_DP14_SRCCTS_4L_RBB")[0])) + + configuration += "HBR, 1 Lane: {}
".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_1L_HBR")[1], + _data.get("TSI_DP14_SRCCTS_1L_HBR")[0])) + configuration += "HBR, 2 Lane: {}
".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_2L_HBR")[1], + _data.get("TSI_DP14_SRCCTS_2L_HBR")[0])) + configuration += "HBR, 4 Lane: {}

".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_4L_HBR")[1], + _data.get("TSI_DP14_SRCCTS_4L_HBR")[0])) + + configuration += "HBR2, 1 Lane: {}
".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_1L_HBR2")[1], + _data.get("TSI_DP14_SRCCTS_1L_HBR2")[0])) + configuration += "HBR2, 2 Lane: {}
".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_2L_HBR2")[1], + _data.get("TSI_DP14_SRCCTS_2L_HBR2")[0])) + configuration += "HBR2, 4 Lane: {}

".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_4L_HBR2")[1], + _data.get("TSI_DP14_SRCCTS_4L_HBR2")[0])) + + configuration += "HBR3, 1 Lane: {}
".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_1L_HBR3")[1], + _data.get("TSI_DP14_SRCCTS_1L_HBR3")[0])) + configuration += "HBR3, 2 Lane: {}
".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_2L_HBR3")[1], + _data.get("TSI_DP14_SRCCTS_2L_HBR3")[0])) + configuration += "HBR3, 4 Lane: {}
".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_4L_HBR3")[1], + _data.get("TSI_DP14_SRCCTS_4L_HBR3")[0])) + + configuration += "
Colorimetry:
" + configuration += "- RGB 6 bpc VESA
" + + for i in range(1, 13): + value = int(_data.get(f"TSI_DP14_SRCCTS_COLOR_MODE_{i}")) + if value == make_cf(CMTFormat.RGB_LEGACY, CMTBitDepth.BPC_8): + configuration += "- RGB 8 bpc VESA
" + elif value == make_cf(CMTFormat.RGB_LEGACY, CMTBitDepth.BPC_10): + configuration += "- RGB 10 bpc VESA
" + elif value == make_cf(CMTFormat.RGB_CEA, CMTBitDepth.BPC_8): + configuration += "- RGB 8 bpc CTA
" + elif value == make_cf(CMTFormat.RGB_CEA, CMTBitDepth.BPC_10): + configuration += "- RGB 10 bpc CTA
" + elif value == make_cf(CMTFormat.YCBCR_422_BT601, CMTBitDepth.BPC_8): + configuration += "- YCbCr 4:2:2 8 bpc CTA (ITU.601)
" + elif value == make_cf(CMTFormat.YCBCR_422_BT601, CMTBitDepth.BPC_10): + configuration += "- YCbCr 4:2:2 10 bpc CTA (ITU.601)
" + elif value == make_cf(CMTFormat.YCBCR_422_BT709, CMTBitDepth.BPC_8): + configuration += "- YCbCr 4:2:2 8 bpc CTA (ITU.709)
" + elif value == make_cf(CMTFormat.YCBCR_422_BT709, CMTBitDepth.BPC_10): + configuration += "- YCbCr 4:2:2 10 bpc CTA (ITU.709)
" + elif value == make_cf(CMTFormat.YCBCR_444_BT601, CMTBitDepth.BPC_8): + configuration += "- YCbCr 4:4:4 8 bpc CTA (ITU.601)
" + elif value == make_cf(CMTFormat.YCBCR_444_BT601, CMTBitDepth.BPC_10): + configuration += "- YCbCr 4:4:4 10 bpc CTA (ITU.601)
" + elif value == make_cf(CMTFormat.YCBCR_444_BT709, CMTBitDepth.BPC_8): + configuration += "- YCbCr 4:4:4 8 bpc CTA (ITU.709)
" + elif value == make_cf(CMTFormat.YCBCR_444_BT709, CMTBitDepth.BPC_10): + configuration += "- YCbCr 4:4:4 10 bpc CTA (ITU.709)
" + + configuration += "
Audio format:
" + configuration += "- Minimum supported sample frequency: {}Hz
".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_MIN_SAMPLE_RATE")[1], + _data.get("TSI_DP14_SRCCTS_MIN_SAMPLE_RATE")[0])) + configuration += "- Maximum supported sample frequency: {}Hz
".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_MAX_SAMPLE_RATE")[1], + _data.get("TSI_DP14_SRCCTS_MAX_SAMPLE_RATE")[0])) + configuration += "- Audio test pattern: {}
". \ + format(value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_AUDIO_TEST_PATTERN")[1], + _data.get("TSI_DP14_SRCCTS_AUDIO_TEST_PATTERN")[0])) + configuration += "- Audio sample size: {}
". \ + format(value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_EDID_SAMPLE_SIZE")[1], + _data.get("TSI_DP14_SRCCTS_EDID_SAMPLE_SIZE")[0])) + + ch_alloc_title = ["Minimum channel configuration / Min. sample rate", + "Maximum channel configuration / Min. sample rate", + "Minimum channel configuration / Max. sample rate", + "Maximum channel configuration / Max. sample rate"] + + ch_alloc_str = [ + "- Front Left + Front Right (2 Channels)", + "- Low Frequency Effect (1 Channel)", + "- Front Center (1 Channel)", + "- Rear Left + Rear Right (2 Channels)", + "- Rear Center (1 Channel)", + "- Front Left Center + Front Right Center (2 Channels)", + "- Rear Left Center + Rear Right Center (2 Channels)", + "- Front Left Wide + Front Right Wide (2 Channels)", + "- Front Left High + Front Right High (2 Channels)", + "- Top Center (1 Channel)", + "- Front Center High (1 Channel)"] + + for i in range(len(ch_alloc_title)): + CH_ALLOC_CI = f"TSI_DP14_SRCCTS_CH_ALLOC{i + 1}" + CH_ALLOC_VAL = int(_data.get(CH_ALLOC_CI)) + + CH_COUNT_CI = f"TSI_DP14_SRCCTS_CHANNELS{i + 1}" + CH_COUNT_VAL = int(_data.get(CH_COUNT_CI)) + + configuration += "
{}:
".format(ch_alloc_title[i]) + configuration += "- Channel count: {}
".format(CH_COUNT_VAL) + + for j in range(len(ch_alloc_str)): + if CH_ALLOC_VAL >> j & 1: + configuration += "{}
".format(ch_alloc_str[j]) + + configuration += "
DSC Info:
" + dsc_version = int(_data.get("TSI_DP14_SRCCTS_DSC_VERSION")[0]) + configuration += "- DSC Version: {}.{}
".format((dsc_version >> 16) & 0xff, dsc_version & 0xff) + configuration += "- DSC Max. Slices: {}
".format( + value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_DSC_DUT_MAX_SLICE")[1], + _data.get("TSI_DP14_SRCCTS_DSC_DUT_MAX_SLICE")[0])) + + configuration += "
DSC Colorimetry:
" + configuration += "- RGB 8 bpc VESA
" + if int(_data.get("TSI_DP14_SRCCTS_DSC_COLOR_MODE_1")): + configuration += "- RGB 10 bpc VESA
" + if int(_data.get("TSI_DP14_SRCCTS_DSC_COLOR_MODE_2")): + configuration += "- RGB 12 bpc VESA
" + if int(_data.get("TSI_DP14_SRCCTS_DSC_COLOR_MODE_3")): + configuration += "- YCbCr 4:4:4 8 bpc CTA (ITU.709)
" + if int(_data.get("TSI_DP14_SRCCTS_DSC_COLOR_MODE_4")): + configuration += "- YCbCr 4:4:4 10 bpc CTA (ITU.709)
" + if int(_data.get("TSI_DP14_SRCCTS_DSC_COLOR_MODE_5")): + configuration += "- YCbCr 4:4:4 12 bpc CTA (ITU.709)
" + if int(_data.get("TSI_DP14_SRCCTS_DSC_COLOR_MODE_6")): + configuration += "- YCbCr 4:2:2 8 bpc CTA (ITU.709)
" + if int(_data.get("TSI_DP14_SRCCTS_DSC_COLOR_MODE_7")): + configuration += "- YCbCr 4:2:2 10 bpc CTA (ITU.709)
" + if int(_data.get("TSI_DP14_SRCCTS_DSC_COLOR_MODE_8")): + configuration += "- YCbCr 4:2:2 12 bpc CTA (ITU.709)
" + if int(_data.get("TSI_DP14_SRCCTS_DSC_COLOR_MODE_9")): + configuration += "- YCbCr 4:2:2 Simple 8 bpc CTA (ITU.709)
" + if int(_data.get("TSI_DP14_SRCCTS_DSC_COLOR_MODE_10")): + configuration += "- YCbCr 4:2:2 Simple 10 bpc CTA (ITU.709)
" + if int(_data.get("TSI_DP14_SRCCTS_DSC_COLOR_MODE_11")): + configuration += "- YCbCr 4:2:2 Simple 12 bpc CTA (ITU.709)
" + if int(_data.get("TSI_DP14_SRCCTS_DSC_COLOR_MODE_12")): + configuration += "- YCbCr 4:2:0 Simple 8 bpc CTA (ITU.709)
" + if int(_data.get("TSI_DP14_SRCCTS_DSC_COLOR_MODE_13")): + configuration += "- YCbCr 4:2:0 Simple 10 bpc CTA (ITU.709)
" + if int(_data.get("TSI_DP14_SRCCTS_DSC_COLOR_MODE_14")): + configuration += "- YCbCr 4:2:0 Simple 12 bpc CTA (ITU.709)
" + configuration += "
DSC resolutions to be tested:
" + + dsc_timings = ["1920x1080 @ 30Hz", "1920x1080 @ 60Hz", "1920x1080 @ 120Hz", "3840x2160 @ 30Hz", + "3840x2160 @ 60Hz", "3840x2160 @ 120Hz", "5120x2160 @ 30Hz", "5120x2160 @ 60Hz", + "5120x2160 @ 120Hz", "7680x4320 @ 30Hz", "7680x4320 @ 60Hz", "7680x4320 @ 100Hz"] + + for item in enumerate(dsc_timings): + if int(_data.get(f"TSI_DP14_SRCCTS_DSC_VMT_{item[0]}_TIMINGS")): + lane_count_link_rate = _data.get(f"TSI_DP14_SRCCTS_DSC_VMT_{item[0]}_LC_LR") + if lane_count_link_rate in [1 << 0, 1 << 1, 1 << 2, 1 << 3]: + n_lanes = "1" + elif lane_count_link_rate in [1 << 4, 1 << 5, 1 << 6, 1 << 7]: + n_lanes = "2" + else: + n_lanes = "4" + if lane_count_link_rate in [1 << 0, 1 << 4, 1 << 8]: + bit_rate = "1.62" + elif lane_count_link_rate in [1 << 1, 1 << 5, 1 << 9]: + bit_rate = "2.7" + elif lane_count_link_rate in [1 << 2, 1 << 6, 1 << 10]: + bit_rate = '5.4' + else: + bit_rate = "8.1" + configuration += "- {} with {} lanes(s) @ {} Gbps
".format(item[1], n_lanes, bit_rate) + + configuration += "
DisplayID test configuration:
" + configuration += "- Max HActive = {}
".format(_data.get("TSI_DP14_SRCCTS_MAX_STREAM_HACTIVE")) + configuration += "- Max VActive = {}
".format(_data.get("TSI_DP14_SRCCTS_MAX_STREAM_VACTIVE")) + configuration += "- Max Pixel Clock (kHz) = {}
".format( + _data.get("TSI_DP14_SRCCTS_MAX_STREAM_PIXEL_CLOCK")) + + if int(_data.get("TSI_DP14_SRCCTS_DUT_CAPS")[1][15].mask) & int( + _data.get("TSI_DP14_SRCCTS_DUT_CAPS")[0]): + configuration += "
DMT Timings:
" + dmt_timing_value = int(_data.get("TSI_DP14_SRCCTS_DMT_TIMING")[0]) + for _bit in _data.get("TSI_DP14_SRCCTS_DMT_TIMING")[1]: + if dmt_timing_value & int(_bit.mask): + configuration += "- {}
".format(_bit.description) + + if int(_data.get("TSI_DP14_SRCCTS_DUT_CAPS")[1][16].mask) & int( + _data.get("TSI_DP14_SRCCTS_DUT_CAPS")[0]): + configuration += "
CTA Timings:
" + cta_timing_value = int(_data.get("TSI_DP14_SRCCTS_CTA_TIMING")[0]) + for _bit in _data.get("TSI_DP14_SRCCTS_CTA_TIMING")[1]: + if cta_timing_value & int(_bit.mask): + configuration += "- {}
".format(_bit.description) + + if (int(_data.get("TSI_DP14_SRCCTS_DUT_CAPS")[1][17].mask) & int( + _data.get("TSI_DP14_SRCCTS_DUT_CAPS")[0])) or \ + (int(_data.get("TSI_DP14_SRCCTS_DUT_CAPS")[1][18].mask) & + _data.get("TSI_DP14_SRCCTS_DUT_CAPS")[0]): + configuration += "
CVT Timings:
" + cvt_timing_value = int(_data.get("TSI_DP14_SRCCTS_CVT_TIMING")[0]) + for _bit in _data.get("TSI_DP14_SRCCTS_CVT_TIMING")[1]: + if cvt_timing_value & int(_bit.mask): + configuration += "- {}
".format(_bit.description) + + configuration += "
Audio modes:
" + + for i in range(8): + audio_mode = int(_data.get(f"TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_{i}")) + audio_type = ["Refer to Stream Header", "L-PCM", "AC-3", "MPEG-1", "MP3", "MPEG2", "AAC LC", "DTS", + "ATRAC", "One Bit Audio", "Enhanced AC-3", "DTS-HD", "MAT", "DST", "WMA Pro", + "Refer to Audio Coding Extension Type (CXT)"] + audio_sampling = ["Refer to Stream Header", "32 kHz", "44.1 kHz (CD)", "48 kHz", "88.2 kHz", "96 kHz", + "176.4 kHz", "192 kHz"] + audio_ch_count = ["Refer to Stream Header", "2 channels", "3 channels", "4 channels", "5 channels", + "6 channels", "7 channels", "8 channels"] + audio_sample_size = ["Refer to Stream Header", "16 bit", "20 bit", "24 bit"] + + if audio_mode: + configuration += "- Audio Mode {}: {}, {}, {} @ {}
".format(i, audio_type[ + audio_mode >> 4 & 0xF], + audio_ch_count[ + audio_mode & 0x7], + audio_sample_size[ + audio_mode >> 8 & 0x3], + audio_sampling[ + audio_mode >> 10 & 0x7]) + else: + configuration += f"- Audio Mode {i}: Disabled
" + + configuration += "
AdaptiveSync configuration:
" + as_value = _data.get("TSI_DP14_SRCCTS_AS_DUT_CAPS")[0] + for _bit in _data.get("TSI_DP14_SRCCTS_AS_DUT_CAPS")[1]: + if as_value & _bit.mask: + configuration += "- {}
".format(_bit.description) + + configuration += "- Adaptive-Sync range minimum refresh rate supported by the Source: {}
" \ + .format(value_from_enumeration_var(_data.get("TSI_DP14_SRCCTS_AS_RANGE_MIN_RATE")[1], + _data.get("TSI_DP14_SRCCTS_AS_RANGE_MIN_RATE")[0])) + + as_timings = ["1920x1080p", "2560x1080p", "2560x1440p", "3840x2160p", "4096x2160p", "5120x2160p", + "7680x4320p", "10240x4320p"] + + for item in enumerate(as_timings): + as_value = int(_data.get(f"TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_{item[0]}")) + if as_value: + configuration += "- {} maximum refresh rate: {}Hz
".format(item[1], as_value) + else: + configuration += "- {} is not supported
".format(item[1]) + + elif type_of_test == TestReportConfigType.Dp2_1_Source: + + val = _data.get("TSI_DP20_SRCCTS_DUT_DEBUG_CONF") + if val is not None and val[0] != 0: + configuration += "
Debug options:
" + for _bit in val[1]: + if val[0] & _bit.mask: + configuration += f" - {_bit.description}
" + configuration += "
" + + configuration += "Test timeout = {}ms
".format(_data.get("TSI_DP20_SRCCTS_TIMEOUT")) + configuration += "Long HPD Duration = {}ms

".format(_data.get("TSI_DP20_SRCCTS_LONG_HPD_PULSE")) + configuration += "DUT capability settings and flags:
" + configuration += "- Max lanes = {}
".format(_data.get("TSI_DP20_SRCCTS_MAX_LANES")[0]) + configuration += "- Max link rate = {}
".format( + value_from_enumeration_var(_data.get("TSI_DP20_SRCCTS_MAX_LINK_RATE")[1], + _data.get("TSI_DP20_SRCCTS_MAX_LINK_RATE")[0])) + + dut_caps_value = _data.get("TSI_DP20_SRCCTS_DUT_CAPS")[0] + for _bit in _data.get("TSI_DP20_SRCCTS_DUT_CAPS")[1]: + if dut_caps_value & _bit.mask: + configuration += "- {}
".format(_bit.description) + + dut_link_rates_value = _data.get("TSI_DP20_SRCCTS_DUT_LINK_RATES")[0] + for _bit in _data.get("TSI_DP20_SRCCTS_DUT_LINK_RATES")[1]: + if dut_link_rates_value & _bit.mask: + configuration += "- {}
".format(_bit.description) + + configuration += f"- Number of LTTPR devices: {_data.get('TSI_DP20_SRCCTS_LTTPR_DEVICE_COUNT')}
" + configuration += "- Number of minimum link bandwidth: {}
".format( + value_from_enumeration_var(_data.get("TSI_DP20_SRCCTS_MAX_LINK_BW_POLICY")[1], + _data.get("TSI_DP20_SRCCTS_MAX_LINK_BW_POLICY")[0])) + + configuration += "
Settings for Dynamic Refresh Rate with VBlank stretch with MSA_TIMING_PAR_IGNORED:
" + configuration += f"- HActive: {_data.get('TSI_DP20_SRCCTS_DRR_HACTIVE')} pixels
" + configuration += f"- VActive: {_data.get('TSI_DP20_SRCCTS_DRR_VACTIVE')} lines
" + configuration += f"- Pixel Clock: {_data.get('TSI_DP20_SRCCTS_DRR_PIXEL_CLOCK')} kHz
" + configuration += f"- Minimum Vertical Refresh Rate: {_data.get('TSI_DP20_SRCCTS_DRR_MIN_VRR')} Hz
" + configuration += f"- Maximum Vertical Refresh Rate: {_data.get('TSI_DP20_SRCCTS_DRR_MAX_VRR')} Hz
" + + configuration += "
Test automation flags and settings:
" + + ta_flags = ["Always ready", "EDID read", "Link Training end", "Active Video"] + ta_flags_value = _data.get("TSI_DP20_SRCCTS_DUT_TA")[0] + for bit in _data.get("TSI_DP20_SRCCTS_DUT_TA")[1]: + if bit.mask & ta_flags_value or bit.enumeration_variants is not None: + if bit.enumeration_variants is not None: + configuration += "- Event indicating DUT ready: {}
".format(ta_flags[(ta_flags_value >> 3) & 0x3]) + else: + configuration += "- {}
".format(bit.description) + + configuration += "
Audio format:
" + configuration += "- Minimum supported sample frequency: {}Hz
".format( + value_from_enumeration_var(_data.get("TSI_DP20_SRCCTS_MIN_SAMPLE_RATE")[1], + _data.get("TSI_DP20_SRCCTS_MIN_SAMPLE_RATE")[0])) + configuration += "- Maximum supported sample frequency: {}Hz
".format( + value_from_enumeration_var(_data.get("TSI_DP20_SRCCTS_MAX_SAMPLE_RATE")[1], + _data.get("TSI_DP20_SRCCTS_MAX_SAMPLE_RATE")[0])) + configuration += "- Audio test pattern: {}
". \ + format(value_from_enumeration_var(_data.get("TSI_DP20_SRCCTS_AUDIO_TEST_PATTERN")[1], + _data.get("TSI_DP20_SRCCTS_AUDIO_TEST_PATTERN")[0])) + configuration += "- Audio sample size: {}
". \ + format(value_from_enumeration_var(_data.get("TSI_DP20_SRCCTS_EDID_SAMPLE_SIZE")[1], + _data.get("TSI_DP20_SRCCTS_EDID_SAMPLE_SIZE")[0])) + + ch_alloc_title = ["Minimum channel configuration / Min. sample rate", + "Maximum channel configuration / Min. sample rate", + "Minimum channel configuration / Max. sample rate", + "Maximum channel configuration / Max. sample rate"] + + ch_alloc_str = [ + "- Front Left + Front Right (2 Channels)", + "- Low Frequency Effect (1 Channel)", + "- Front Center (1 Channel)", + "- Rear Left + Rear Right (2 Channels)", + "- Rear Center (1 Channel)", + "- Front Left Center + Front Right Center (2 Channels)", + "- Rear Left Center + Rear Right Center (2 Channels)", + "- Front Left Wide + Front Right Wide (2 Channels)", + "- Front Left High + Front Right High (2 Channels)", + "- Top Center (1 Channel)", + "- Front Center High (1 Channel)"] + + for i in range(len(ch_alloc_title)): + CH_ALLOC_CI = f"TSI_DP20_SRCCTS_CH_ALLOC{i + 1}" + CH_ALLOC_VAL = int(_data.get(CH_ALLOC_CI)) + + CH_COUNT_CI = f"TSI_DP20_SRCCTS_CHANNELS{i + 1}" + CH_COUNT_VAL = int(_data.get(CH_COUNT_CI)) + + configuration += "
{}:
".format(ch_alloc_title[i]) + configuration += "- Channel count: {}
".format(CH_COUNT_VAL) + + for j in range(len(ch_alloc_str)): + if CH_ALLOC_VAL >> j & 1: + configuration += "{}
".format(ch_alloc_str[j]) + + configuration += "
DSC Info:
" + dsc_version = int(_data.get("TSI_DP20_SRCCTS_DSC_VERSION")[0]) + configuration += "- DSC Version: {}.{}
".format((dsc_version >> 16) & 0xff, dsc_version & 0xff) + configuration += "- DSC Max. Slices: {}
".format( + value_from_enumeration_var(_data.get("TSI_DP20_SRCCTS_DSC_DUT_MAX_SLICE")[1], + _data.get("TSI_DP20_SRCCTS_DSC_DUT_MAX_SLICE")[0])) + + configuration += "
DisplayID test configuration:
" + configuration += "- Max HActive = {}
".format(_data.get("TSI_DP20_SRCCTS_MAX_STREAM_HACTIVE")) + configuration += "- Max VActive = {}
".format(_data.get("TSI_DP20_SRCCTS_MAX_STREAM_VACTIVE")) + configuration += "- Max Pixel Clock (kHz) = {}
".format( + _data.get("TSI_DP20_SRCCTS_MAX_STREAM_PIXEL_CLOCK")) + + if _data.get("TSI_DP20_SRCCTS_DUT_CAPS")[1][17].mask & _data.get("TSI_DP20_SRCCTS_DUT_CAPS")[0]: + configuration += "
DMT Timings:
" + dmt_timing_value = _data.get("TSI_DP20_SRCCTS_DMT_TIMING")[0] + for _bit in _data.get("TSI_DP20_SRCCTS_DMT_TIMING")[1]: + if dmt_timing_value & _bit.mask: + configuration += "- {}
".format(_bit.description) + + if _data.get("TSI_DP20_SRCCTS_DUT_CAPS")[1][18].mask & _data.get("TSI_DP20_SRCCTS_DUT_CAPS")[0]: + configuration += "
CTA Timings:
" + cta_timing_value = _data.get("TSI_DP20_SRCCTS_CTA_TIMING")[0] + for _bit in _data.get("TSI_DP20_SRCCTS_CTA_TIMING")[1]: + if cta_timing_value & _bit.mask: + configuration += "- {}
".format(_bit.description) + + if (_data.get("TSI_DP20_SRCCTS_DUT_CAPS")[1][19].mask & _data.get("TSI_DP20_SRCCTS_DUT_CAPS")[0]) or \ + (_data.get("TSI_DP20_SRCCTS_DUT_CAPS")[1][20].mask & _data.get("TSI_DP20_SRCCTS_DUT_CAPS")[0]): + configuration += "
CVT Timings:
" + cvt_timing_value = _data.get("TSI_DP20_SRCCTS_CVT_TIMING")[0] + for _bit in _data.get("TSI_DP20_SRCCTS_CVT_TIMING")[1]: + if cvt_timing_value & _bit.mask: + configuration += "- {}
".format(_bit.description) + + configuration += "
Audio modes:
" + + for i in range(8): + audio_mode = int(_data.get(f"TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_{i}")) + audio_type = ["Refer to Stream Header", "L-PCM", "AC-3", "MPEG-1", "MP3", "MPEG2", "AAC LC", "DTS", + "ATRAC", "One Bit Audio", "Enhanced AC-3", "DTS-HD", "MAT", "DST", "WMA Pro", + "Refer to Audio Coding Extension Type (CXT)"] + audio_sampling = ["Refer to Stream Header", "32 kHz", "44.1 kHz (CD)", "48 kHz", "88.2 kHz", "96 kHz", + "176.4 kHz", "192 kHz"] + audio_ch_count = ["Refer to Stream Header", "2 channels", "3 channels", "4 channels", "5 channels", + "6 channels", "7 channels", "8 channels"] + audio_sample_size = ["Refer to Stream Header", "16 bit", "20 bit", "24 bit"] + + if audio_mode: + configuration += "- Audio Mode {}: {}, {}, {} @ {}
".format(i, audio_type[ + audio_mode >> 4 & 0xF], + audio_ch_count[ + audio_mode & 0x7], + audio_sample_size[ + audio_mode >> 8 & 0x3], + audio_sampling[ + audio_mode >> 10 & 0x7]) + else: + configuration += f"- Audio Mode {i}: Disabled
" + + configuration += "
AdaptiveSync configuration:
" + as_value = _data.get("TSI_DP20_SRCCTS_AS_DUT_CAPS")[0] + for _bit in _data.get("TSI_DP20_SRCCTS_AS_DUT_CAPS")[1]: + if as_value & _bit.mask: + configuration += "- {}
".format(_bit.description) + + configuration += "- Adaptive-Sync range minimum refresh rate supported by the Source: {}
" \ + .format(value_from_enumeration_var(_data.get("TSI_DP20_SRCCTS_AS_RANGE_MIN_RATE")[1], + _data.get("TSI_DP20_SRCCTS_AS_RANGE_MIN_RATE")[0])) + + as_timings = ["1920x1080p", "2560x1080p", "2560x1440p", "3840x2160p", "4096x2160p", "5120x2160p", + "7680x4320p", "10240x4320p"] + + for item in enumerate(as_timings): + as_value = int(_data.get(f"TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_{item[0]}")) + if as_value: + configuration += "- {} maximum refresh rate: {}Hz
".format(item[1], as_value) + else: + configuration += "- {} is not supported
".format(item[1]) + + colorimetry_map = {0: '-', 1: 8, 3: 10, 7: 12, 15: 16} + + def add_column_title(include_dsc : bool): + title = "" + style = 'style="padding: 10px; border: 1px solid black;"' + title_list = [f'Timing\n', + f'Timing vendors\n', + f'Colorimetry\n'] + timing_vendors = ['CTA', 'DMT', 'CVT', 'CVT RB1', 'CVT RB2', 'OVT'] + colorimetry = ["RGB", "YCbCr444", "YCbCr422", "YCbCr422Simple", "YCbCr420"] if include_dsc else [ "RGB", "YCbCr444", "YCbCr422", "YCbCr420" ] + + for title_item in title_list: + title += title_item + title += "\n" + + for timing_vendors_item in timing_vendors: + title += f'{timing_vendors_item}' + + for colorimetry_item in colorimetry: + title += f'{colorimetry_item}' + + title += "\n" + + return title + + def add_video_row(timing_info, tim_value): + style = 'align="center";' + row = f'{timing_info}' \ + f'{"+" if tim_value & 0x1 else "-"}' \ + f'{"+" if (tim_value >> 1) & 0x1 else "-"}' \ + f'{"+" if (tim_value >> 2) & 0x1 else "-"}' \ + f'{"+" if (tim_value >> 3) & 0x1 else "-"}' \ + f'{"+" if (tim_value >> 4) & 0x1 else "-"}' \ + f'{"+" if (tim_value >> 5) & 0x1 else "-"}' + row += f'{colorimetry_map.get((tim_value >> 12) & 0xF)}' \ + f'{colorimetry_map.get((tim_value >> 16) & 0xF)}' \ + f'{colorimetry_map.get((tim_value >> 20) & 0xF)}' \ + f'{colorimetry_map.get((tim_value >> 28) & 0xF)}' + return row + + def add_dsc_row(timing_info, tim_value): + style = 'align="center";' + row = f'{timing_info}' \ + f'{"+" if tim_value & 0x1 else "-"}' \ + f'{"+" if (tim_value >> 1) & 0x1 else "-"}' \ + f'{"+" if (tim_value >> 2) & 0x1 else "-"}' \ + f'{"+" if (tim_value >> 3) & 0x1 else "-"}' \ + f'{"+" if (tim_value >> 4) & 0x1 else "-"}' \ + f'{"+" if (tim_value >> 5) & 0x1 else "-"}' + row += f'{colorimetry_map.get((tim_value >> 12) & 0xF)}' \ + f'{colorimetry_map.get((tim_value >> 16) & 0xF)}' \ + f'{colorimetry_map.get((tim_value >> 20) & 0xF)}' \ + f'{colorimetry_map.get((tim_value >> 24) & 0xF)}' \ + f'{colorimetry_map.get((tim_value >> 28) & 0xF)}' + return row + + configuration += '
' + add_column_title(False) + + deprecated_video_modes = [ "TSI_DP20_SRCCTS_VMT_1920_1080_144", "TSI_DP20_SRCCTS_VMT_1920_1080_240", + "TSI_DP20_SRCCTS_VMT_3840_2160_30", "TSI_DP20_SRCCTS_VMT_3840_2160_240", + "TSI_DP20_SRCCTS_VMT_5120_2160_120", "TSI_DP20_SRCCTS_VMT_5120_2160_240", + "TSI_DP20_SRCCTS_VMT_7680_4320_120", "TSI_DP20_SRCCTS_VMT_10240_4320_30", + "TSI_DP20_SRCCTS_VMT_10240_4320_60"] + + for key in _data.keys(): + if key in deprecated_video_modes: + continue + if key.find("TSI_DP20_SRCCTS_VMT_") != -1: + value = _data.get(key) + _, h_active, w_active, rate = re.findall(r'\d+', key) + configuration += add_video_row(f"{h_active}x{w_active} @ {rate}Hz", value) + configuration += "

" + + configuration += '
' + add_column_title(True) + + for key in _data.keys(): + if key.find("TSI_DP20_SRCCTS_DSC_VMT_") != -1: + value = _data.get(key) + _, h_active, w_active, rate = re.findall(r'\d+', key) + configuration += add_dsc_row(f"{h_active}x{w_active} @ {rate}Hz", value) + configuration += "

" + + + elif type_of_test == TestReportConfigType.Dp1_4_Sink: + configuration = "Test timeout = {}ms
".format(_data.get("TSI_DP14_SINKCTS_TIMEOUT")) + if not int(_data.get("TSI_DP14_SINKCTS_DSC_SOURCE")[0]): + configuration += "
DSC resolutions to be tested:
" + dsc_timings = ["1920x1080 @ 30Hz", "1920x1080 @ 60Hz", "1920x1080 @ 120Hz", "3840x2160 @ 30Hz", + "3840x2160 @ 60Hz", "3840x2160 @ 120Hz", "5120x2160 @ 30Hz", "5120x2160 @ 60Hz", + "5120x2160 @ 120Hz", "7680x4320 @ 30Hz", "7680x4320 @ 60Hz", "7680x4320 @ 100Hz"] + + for item in enumerate(dsc_timings): + timing_flags = int(str(_data.get(f"TSI_DP14_SINKCTS_DSC_VM_T_{item[0]}")), 16) + if timing_flags: + configuration += "- {}{}{}{}
".format(item[1], " CTA" if timing_flags & 0x1 else "", + " RB1" if timing_flags & 0x2 else "", + " RB2" if timing_flags & 0x4 else "") + else: + configuration += "
DSC resolutions to be tested are defined by DUT EDID
" + + configuration += "
Sink DUT supports DSC Simple 422 CRC for {}
". \ + format(value_from_enumeration_var(_data.get("TSI_DP14_SINKCTS_SUPPORT_444CRC")[1], + _data.get("TSI_DP14_SINKCTS_SUPPORT_444CRC")[0])) + + if int(_data.get("TSI_DP14_SINKCTS_VISUAL_TEST_CHECK")[0]): + configuration += "Never skip Display ID test's visual video checks
" + else: + configuration += "Skip Display ID test's visual video checks if CRC available and matching
" + + if not int(_data.get("TSI_DP14_SINKCTS_PACKET_SOURCE")[0]): + configuration += "
Most packed resolutions to be tested:
" + video_timings = ["CVT 1280 x 800 @ 60p [RB1] 18bpp 99", "DMT 1280 x 768 @ 60p [RB1] 18bpp 95", + "DMT 800 x 600 @ 60.317p 30bpp 93", "DMT 1024 x 768 @ 60p 18bpp 90", + "DMT 1280 x 1024 @ 60p 24bpp 100", "DMT 1280 x 960 @ 60p 24bpp 100", + "DMT 1360 x 768 @ 60p 30bpp 99", "CVT 1280 x 800 @ 60p 30bpp 97", + "DMT 1400 x 1050 @ 60p [RB1] 24bpp 94", "DMT 1280 x 768 @ 60p 30bpp 92", + "CVT 1600 x 1200 @ 60p [RB1] 18bpp 90", "CVT 2048 x 1536 @ 60p [RB1] 24bpp 97", + "DMT 1792 x 1344 @ 60p 24bpp 95", "DMT 1600 x 1200 @ 60p 30bpp 94", + "CTA 1440 x 480 @ 59.94p 24bpp 100", "CTA 1440 x 576 @ 50p 24bpp 100", + "CTA 1920 x 1080 @ 60p 30bpp 86"] + + for item in video_timings: + configuration += "- {}
".format(item) + else: + configuration += "
Most packed resolutions to be tested are defined by DUT EDID
" + elif type_of_test == TestReportConfigType.Dp2_1_Sink: + configuration = "" + + val = _data.get("TSI_DP20_SINKCTS_DEBUG_CONF") + if val is not None and val[0] != 0: + configuration += "
Debug options:
" + for _bit in val[1]: + if val[0] & _bit.mask: + configuration += f" - {_bit.description}
" + configuration += "
" + + configuration += "Test timeout = {}ms
".format(_data.get("TSI_DP20_SINKCTS_TIMEOUT")) + if not int(_data.get("TSI_DP20_SINKCTS_DSC_SOURCE")[0]): + configuration += "
DSC resolutions to be tested:
" + video_modes_list = [[1920, 1080, 30], [1920, 1080, 60], [1920, 1080, 120], [1920, 1080, 144], + [1920, 1080, 240], + [3840, 2160, 30], [3840, 2160, 60], [3840, 2160, 120], [3840, 2160, 144], + [3840, 2160, 240], + [5120, 2160, 30], [5120, 2160, 60], [5120, 2160, 120], [5120, 2160, 144], + [5120, 2160, 240], + [7680, 4320, 30], [7680, 4320, 60], [7680, 4320, 120], [7680, 4320, 144], + [7680, 4320, 240], + [10240, 4320, 30], [10240, 4320, 60], [10240, 4320, 120], [10240, 4320, 144], + [10240, 4320, 240], + [15360, 8640, 30], [15360, 8640, 60], [15360, 8640, 120], [15360, 8640, 144], + [15360, 8640, 240]] + for vm in video_modes_list: + timing_flag = _data.get(f"TSI_DP20_SINKCTS_VMT_{vm[0]}_{vm[1]}") + bit_n = 0 + if vm[2] == 60: + bit_n = 4 + elif vm[2] == 120: + bit_n = 8 + elif vm[2] == 144: + bit_n = 13 + elif vm[2] == 240: + bit_n = 17 + + if vm[2] in [144, 240]: + rb1_value = (timing_flag >> bit_n) & 0x1 + rb2_value = (timing_flag >> (bit_n + 1)) & 0x1 + ovt_value = (timing_flag >> (bit_n + 2)) & 0x1 + if rb1_value or rb2_value or ovt_value: + configuration += f"- {vm[0]}x{vm[1]} @ {vm[2]}Hz" \ + f"{' RB1' if rb1_value else ''}" \ + f"{' RB2' if rb2_value else ''}" \ + f"{' OVT' if ovt_value else ''}
" + else: + cta_value = (timing_flag >> bit_n) & 0x1 + rb1_value = (timing_flag >> (bit_n + 1)) & 0x1 + rb2_value = (timing_flag >> (bit_n + 2)) & 0x1 + if rb1_value or rb2_value or cta_value: + configuration += f"- {vm[0]}x{vm[1]} @ {vm[2]}Hz" \ + f"{' CTA' if cta_value else ''}" \ + f"{' RB1' if rb1_value else ''}" \ + f"{' RB2' if rb2_value else ''}
" + else: + configuration += "
DSC resolutions to be tested are defined by DUT EDID
" + + if _data.get("TSI_DP20_SINKCTS_SUPPORT_444CRC")[0] == 0: + configuration += "
Sink DUT supports YCbCR 422 CRC for Simple 422 bitstream
" + elif _data.get("TSI_DP20_SINKCTS_SUPPORT_444CRC")[0] == 1: + configuration += "
Sink DUT supports YCbCR 444 CRC for Simple 422 bitstream
" + else: + configuration += "

" + + if int(_data.get("TSI_DP20_SINKCTS_VISUAL_TEST_CHECK")[0]): + configuration += "
Skip Display ID test's visual video checks if CRC available and matching
" + else: + configuration += "
Never skip Display ID test's visual video checks
" + + return configuration + + +def dp21_source_timing_table(data) -> str: + _data = copy_data(data) + colorimetry_map = {0: '-', 1: 8, 3: 10, 7: 12, 15: 16} + info = f"Timing {'Timing vendors':>35} {'Colorimetry':>40}\n" + info += f"{'CTA DMT CVT CVT-RB1 CVT-RB2 OVT':>50}" + info += f"{'RGB YCbCr444 YCbCr422 YCbCr422Simple YCbCr420':>50}\n" + + def shift(str_length) -> int: + if str_length == 14: + return 7 + elif str_length == 15: + return 6 + elif str_length == 16: + return 5 + elif str_length == 17: + return 4 + + def support(flag) -> str: + return "+" if flag else "-" + + def add_row(timing_info, tim_value): + row = f'{timing_info}' \ + f'{support(tim_value & 0x1):>{shift(len(timing_info))}}' \ + f'{support((tim_value >> 1) & 0x1):>4}' \ + f'{support((tim_value >> 2) & 0x1):>4}' \ + f'{support((tim_value >> 3) & 0x1):>6}' \ + f'{support((tim_value >> 4) & 0x1):>8}' \ + f'{support((tim_value >> 5) & 0x1):>6}' + row += f'{colorimetry_map.get((tim_value >> 12) & 0xF):>8}' \ + f'{colorimetry_map.get((tim_value >> 16) & 0xF):>7}' \ + f'{colorimetry_map.get((tim_value >> 20) & 0xF):>9}' \ + f'{colorimetry_map.get((tim_value >> 24) & 0xF):>12}' \ + f'{colorimetry_map.get((tim_value >> 28) & 0xF):>12}\n' + return row + + for key in _data.keys(): + if key.find("TSI_DP20_SRCCTS_VMT_") != -1: + value = _data.get(key) + _, h_active, w_active, rate = re.findall(r'\d+', key) + info += add_row(f"{h_active}x{w_active} @ {rate}Hz", value) + + return info diff --git a/UniTAP/dev/modules/dut_tests/test_group_params_types.py b/UniTAP/dev/modules/dut_tests/test_group_params_types.py new file mode 100644 index 0000000..51a77e8 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/test_group_params_types.py @@ -0,0 +1,346 @@ +import math +import sys +import os +from enum import IntEnum +from PIL import Image + +from ctypes import c_int, c_uint8, c_uint16, c_uint32, c_float, c_char_p, c_bool + +from UniTAP.libs.lib_uicl.uicl import UICL_GetRequiredBufferSize, UICL_Convert +from UniTAP.libs.lib_uicl.uicl_types import UICL_Image, UICL_ImageParameters, UICL_Colorspace, UICL_Packing, \ + UICL_Sampling, UICL_Colorimetry, UICL_ComponentOrder, UICL_Alignment, UICL_SUCCESS +from .test_utils import search_bit + + +class DataType(IntEnum): + Int = 0 + Double = 1 + String = 2 + Bool = 3 + Uint32 = 4 + ArrayU8 = 5 + ArrayU16 = 6 + ArrayU32 = 7 + PHYAddress = 8 + BitFlag = 9 + ArrayFromFilePath = 10 + FolderPath = 11 + BitList = 12 + + +dict_types = { + DataType.Int: c_int, + DataType.Double: c_float, + DataType.String: c_char_p, + DataType.Bool: c_bool, + DataType.Uint32: c_uint32, + DataType.ArrayU8: c_uint8, + DataType.ArrayU16: c_uint16, + DataType.ArrayU32: c_uint32, + DataType.PHYAddress: c_uint32, + DataType.BitFlag: c_bool, + DataType.ArrayFromFilePath: c_uint8, + DataType.FolderPath: c_char_p, + DataType.BitList: c_uint32 +} + + +class EnumVar: + + def __init__(self, enumeration_variants: str): + self.__available_values = [] + self.__str_present = [] + if enumeration_variants.find("#") != -1: + available_values = enumeration_variants.split("\n") + for item in available_values: + self.__available_values.append(item.split(" # ")[0]) + self.__str_present.append(item.split(" # ")[1]) + else: + self.__available_values = enumeration_variants.split("\n") + + @property + def available_values(self) -> list: + return self.__available_values + + @property + def str_presentation(self) -> list: + return self.__str_present + + +class BitField: + + def __init__(self, json_obj): + self.__description = json_obj.get('description') + self.__mask = int(json_obj.get('mask'), 16) + if 'enumerationVariants' in json_obj: + self.__enumeration_variants = EnumVar(json_obj.get('enumerationVariants')) + else: + self.__enumeration_variants = None + if 'defaultValue' in json_obj: + self.__default_value = json_obj.get('defaultValue') + else: + self.__default_value = None + + @property + def description(self): + return self.__description + + @property + def mask(self): + return self.__mask + + @property + def enumeration_variants(self): + return self.__enumeration_variants + + @description.setter + def description(self, value: str): + self.__description = value + + @mask.setter + def mask(self, value: int): + self.__mask = value + + @enumeration_variants.setter + def enumeration_variants(self, value: list): + self.__enumeration_variants = value + + +class Param: + + def __init__(self, json_obj): + self.__min_value = sys.float_info.min + self.__max_value = sys.float_info.max + self.__type = DataType.Int + self.__config_id = 0 + self.__config_id_name = "" + self.__name = "" + self.__default_value = None + self.__enumeration_variants = None + self.__bit_field_list = [] + + self.__process_value(json_obj) + + def _set_by_bitmask(self, value, bitmask_index): + if len(self.__bit_field_list) > bitmask_index: + mask = self.__bit_field_list[bitmask_index].mask + self.__default_value &= ~mask + self.__default_value |= mask & (value << search_bit(mask)) + + def _get_by_bitmask(self, bitmask_index: int, value_type=int): + if len(self.__bit_field_list) > bitmask_index: + mask = self.__bit_field_list[bitmask_index].mask + masked_value = self.__default_value & mask + return value_type(masked_value >> search_bit(mask)) + return 0 + + def _set_by_bit_number(self, state, value, bit_number): + if state: + self.__default_value |= (value << bit_number) + else: + self.__default_value &= ~(value << bit_number) + + def _get_by_bit_number(self, bit_number) -> bool: + return (self.__default_value >> bit_number) & 0x1 + + def clear(self): + self.__default_value = 0 + + def direct_set_def_value(self, default_value): + self.__default_value = default_value + + @property + def default_value(self): + return self.__default_value + + @default_value.setter + def default_value(self, default_value): + if self.__enumeration_variants is not None: + if str(default_value) in self.__enumeration_variants.available_values: + self.__default_value = default_value + else: + available_variants = '\n'.join(self.__enumeration_variants.available_values) + raise ValueError(f"Value {default_value} is not in available variants:\n {available_variants}.") + else: + self.__default_value = default_value + + @property + def min_value(self): + return self.__min_value + + @property + def max_value(self): + return self.__max_value + + @property + def config_id(self): + return self.__config_id + + @property + def config_id_name(self): + return self.__config_id_name + + @property + def name(self): + return self.__name + + @property + def value_type(self): + return self.__type + + @property + def enumeration_variants(self): + return self.__enumeration_variants + + @property + def bit_field_list(self): + return self.__bit_field_list + + @property + def data_length(self): + if isinstance(self.__default_value, (list, tuple, bytes, bytearray, str)): + return len(self.__default_value) + else: + return 1 + + def __str__(self): + return f"{self.default_value if self.data_length <= 50 else self.default_value[:50]}" + + def __process_value(self, json_obj): + if 'minValue' in json_obj: + self.__min_value = int(json_obj.get('minValue')) + if 'maxValue' in json_obj: + self.__max_value = int(json_obj.get('maxValue')) + if 'enumerationVariants' in json_obj: + self.__enumeration_variants = EnumVar(json_obj.get('enumerationVariants')) + if 'bitList' in json_obj: + bit_list = json_obj.get('bitList') + for item in bit_list: + self.__bit_field_list.append(BitField(item)) + + self.__config_id = int(json_obj.get('configId'), 16) + self.__type = DataType(json_obj.get('type')) + self.__name = json_obj.get('name') + self.__config_id_name = json_obj.get('id') + if self.__type in [DataType.Int, DataType.Uint32]: + self.__default_value = int(json_obj.get('defaultValue'), 16) \ + if json_obj.get('defaultValue').find("0x") != -1 \ + else int(json_obj.get('defaultValue')) + elif self.__type == DataType.BitList: + self.__default_value = int(json_obj.get('defaultValue'), 16) + elif self.__type == DataType.Bool: + self.__default_value = bool(json_obj.get('defaultValue')) + elif self.__type == DataType.Double: + self.__default_value = float(json_obj.get('defaultValue')) + else: + self.__default_value = json_obj.get('defaultValue') + + def __eq__(self, other): + return self.default_value == other.default_value + + +def check_have_dict_attribute(obj) -> bool: + try: + vars(obj) + return True + except TypeError: + return False + + +def get_param_list(param_obj) -> list: + param_list = [] + if check_have_dict_attribute(param_obj): + var_list = vars(param_obj) + else: + return param_list + + for item in var_list.keys(): + if isinstance(var_list.get(item), Param) or issubclass(type(var_list.get(item)), Param): + param_list.append(var_list.get(item)) + elif isinstance(var_list.get(item), list): + for elem in var_list.get(item): + if isinstance(elem, Param): + param_list.append(elem) + else: + param_list.extend(get_param_list(var_list.get(item))) + + return param_list + + +def param_by_ci_name(json_obj, name: str) -> Param: + for obj in json_obj: + if obj.get('id') == name: + return Param(obj) + return Param({}) + + +def read_image_file(full_filename: str) -> bytearray: + file_name, file_extension = os.path.splitext(full_filename) + if file_extension.lower() == '.bin': + with open(full_filename, 'rb') as file: + image_data = bytearray(file.read()) + return image_data + elif file_extension.lower() in [".png", '.jpg', ".ppm", '.bmp', '.tif', '.tiff']: + image = Image.open(full_filename) + image = image.convert('RGB') + image_data = bytearray(image.tobytes()) + return image_data + else: + return bytearray() + + +def uicl_image_preparation(data: bytearray, params): + ref_image = UICL_Image() + ref_param = UICL_ImageParameters() + + ref_param.Width = params.image_width + ref_param.Height = params.image_height + # VideoPixelTestElementFormat: RGB_16BPC = 20 + ref_param.Colorspace = UICL_Colorspace.Colorspace_RGB + ref_param.BitsPerColor = 8 + ref_param.ComponentOrder = UICL_ComponentOrder.Order_RGB + ref_param.Packing = UICL_Packing.Packing_Packed + ref_param.Alignment = UICL_Alignment.Alignment_MSB + # VideoPixelTestElementFormat: YCbCr444_161616 = 257 + ref_param.Sampling = UICL_Sampling.Sampling_444 + ref_param.IsFullRange = False + ref_param.Colorimetry = UICL_Colorimetry.Colorimetry_Unknown + ref_param.IsMonochrome = False + ref_image.Parameters = ref_param + ref_image.DataSize = UICL_GetRequiredBufferSize(ref_image) + ref_image.DataPtr = (c_uint8 * ref_image.DataSize)(*data) + + _dest_params = UICL_ImageParameters() + _dest_params.Width = params.image_width + _dest_params.Height = params.image_height + _dest_params.BitsPerColor = params.color_depth + _dest_params.Colorspace = UICL_Colorspace.Colorspace_RGB if params.element_format <= 20 else UICL_Colorspace.Colorspace_YCbCr + _dest_params.ComponentOrder = UICL_ComponentOrder.Order_RGB if params.element_format <= 20 else UICL_ComponentOrder.Order_YCbCr + _dest_params.Sampling = UICL_Sampling.Sampling_444 if params.element_format <= 257 else UICL_Sampling.Sampling_420 + _dest_params.Packing = UICL_Packing.Packing_Packed if params.element_format <= 257 else UICL_Packing.Packing_Planar + _dest_params.Alignment = UICL_Alignment.Alignment_LSB + _dest_params.IsFullRange = False + _dest_params.Colorimetry = UICL_Colorimetry.Colorimetry_Unknown if params.element_format <= 20 else UICL_Colorimetry.Colorimetry_ITU_R_BT601 + _dest_params.IsMonochrome = False + + _dest_image = UICL_Image() + _dest_image.Parameters = _dest_params + _dest_image.DataSize = UICL_GetRequiredBufferSize(_dest_image) + _dest_image.DataPtr = (c_uint8 * _dest_image.DataSize)() + + result = UICL_Convert(ref_image, _dest_image) + + del ref_image + del data + + if result < UICL_SUCCESS: + print(f"UICL Error {result}") + + _data = [] + + for i in range(_dest_image.DataSize): + _data.append(_dest_image.DataPtr[i]) + + del _dest_image + + return _data diff --git a/UniTAP/dev/modules/dut_tests/test_info.py b/UniTAP/dev/modules/dut_tests/test_info.py new file mode 100644 index 0000000..a47e6e9 --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/test_info.py @@ -0,0 +1,315 @@ +import warnings +from enum import IntEnum +from typing import Optional, Union, List + +from .dut_default_params import * +from .report import generate_html_report, TestAdditionalInfo, TestResult + + +class TestStatusEnum(IntEnum): + Idle = 0 + Completed = 1 + InProgress = 2 + + +class TestGroupId(IntEnum): + """ + Class `TestGroupId` contains all possible variants of Test groups ID. + """ + UNKNOWN = -1 + AUDIO_TEST = 0x00 + DP_RX_ELECTRICAL = 0x01 + HDMI_RX_ELECTRICAL = 0x02 + DVI_RX_ELECTRICAL = 0x03 + CEC_FUNCTIONAL = 0x05 + DP_RX_CRC = 0x06 + DP_RX_SIMPLE_LT = 0x07 + DP_HDCP_CTS_AUTHENTICATION = 0x0A + HDMI_RX_CRC = 0x0B + USB_TYPE_C_ELECTRICAL = 0x0C + DP_RX_LL_CTS = 0x0E + DP_TX_LL_CTS = 0x0F + DP_TX_LL_CTS_DSC = 0x10 + DP_RX_LL_CTS_DSC = 0x11 + DP_2_1_RX_LL_CTS = 0x12 + DP_2_1_TX_LL_CTS = 0x13 + DP_2_1_LTTPR_CTS = 0x14 + DP_HDCP_CTS_1A = 0x26 + DP_HDCP_CTS_1B = 0x27 + DP_HDCP_CTS_2C = 0x28 + DP_HDCP_CTS_3A = 0x29 + DP_HDCP_CTS_3B = 0x2A + DP_HDCP_CTS_3C = 0x2B + DP_TX_DISPLAYID = 0x32 + DP_RX_DISPLAYID = 0x33 + HDMI_RX_VRR = 0x34 + HDMI_TX_VRR = 0x35 + DP_TX_ADAPTIVESYNC = 0x36 + DP_RX_ADAPTIVESYNC = 0x37 + DP_2_1_RX_LTTPR_CTS = 0x38 + DP_2_1_TX_LTTPR_CTS = 0x39 + DP_2_1_RX_DSC_CTS = 0x3A + DP_2_1_TX_DSC_CTS = 0x3B + DP_2_1_TX_DISPAYID = 0x3C + DP_2_1_RX_DISPAYID = 0x3D + DP_2_1_TX_ADAPTIVESYNC = 0x3E + DP_2_1_RX_ADAPTIVESYNC = 0x3F + HD_RX_DSC_CTS = 0x40 + HD_TX_DSC_CTS = 0x41 + HD_TX_CONTINUITY = 0x42 + HD_TX_CABLE_CHECK = 0x43 + PIXEL_VIDEO_TEST = 0x3E8 + HDR10_TEST = -2 + + @classmethod + def has_value(cls, value) -> bool: + return value in iter(cls) + + +class SubTestResultObject: + """ + Class `SubTestResultObject` contains information about testing of one test. + - Set and get Test result `test_result`. + - Set and get FW logs after testing `fw_logs`. + - Set and get Configuration information `config_info`. + - Set and get Error code `error_code`. + - Set and get Error logs `error_logs`. + - Set and get Test name `test_name`. + - Set and get Group name `group_name`. + """ + def __init__(self, group_name: str, debug: bool): + self.__test_result = TestResult.Unknown + self.__fw_logs = "" + self.__config_info = "" + self.__error_code = 0 + self.__error_logs = "" + self.__test_name = "" + self.__group_name = group_name + self.__test_delay = 0 + self.__debug = debug + self.__json_config_info = "" + + @property + def test_result(self) -> TestResult: + """ + Returns result after testing. + + Returns: + object of `TestResult` type + """ + return self.__test_result + + @test_result.setter + def test_result(self, test_result: TestResult): + """ + Set new test result. + + Args: + test_result (TestResult) + """ + self.__test_result = test_result + + @property + def fw_logs(self) -> str: + """ + Returns FW logs after testing. + + Returns: + object of str type + """ + return self.__fw_logs + + @fw_logs.setter + def fw_logs(self, fw_logs: str): + """ + Set new test result. + + Args: + fw_logs (str) + """ + self.__fw_logs = fw_logs + + @property + def config_info(self) -> str: + """ + Returns config info after testing. + + Returns: + object of str type + """ + return self.__config_info + + @config_info.setter + def config_info(self, config_info: str): + """ + Set new config info. + + Args: + config_info (str) + """ + self.__config_info = config_info + + @property + def error_code(self) -> int: + """ + Returns error code after testing. + + Returns: + object of int type + """ + return self.__error_code + + @error_code.setter + def error_code(self, error_code: int): + """ + Set new error code. + + Args: + error_code (int) + """ + self.__error_code = error_code + + @property + def error_logs(self) -> str: + """ + Returns error logs after testing. + + Returns: + object of str type + """ + return self.__error_logs + + @error_logs.setter + def error_logs(self, error_logs: str): + """ + Set new error logs. + + Args: + error_logs (str) + """ + self.__error_logs = error_logs + + @property + def test_name(self) -> str: + """ + Returns test name. + + Returns: + object of str type + """ + return self.__test_name + + @test_name.setter + def test_name(self, test_name: str): + """ + Set new test name. + + Args: + test_name (str) + """ + self.__test_name = test_name + + @property + def group_name(self) -> str: + """ + Returns test group name. + + Returns: + object of str type + """ + return self.__group_name + + @group_name.setter + def group_name(self, group_name: str): + """ + Set new test group name. + + Args: + group_name (str) + """ + self.__group_name = group_name + + @property + def test_delay(self) -> int: + """ + Returns test delay. + + Returns: + object of int type + """ + return self.__test_delay + + @test_delay.setter + def test_delay(self, test_delay: int): + """ + Set new test delay. + + Args: + test_delay (int) + """ + self.__test_delay = test_delay + + @property + def debug(self) -> bool: + """ + Returns debug test state (if you want to use special debug flags, your report will not valid). + + Returns: + object of bool type + """ + return self.__debug + + @property + def json_config_info(self): + """ + Returns json test config info. + + Returns: + object of base64 str type + """ + return self.__json_config_info + + @json_config_info.setter + def json_config_info(self, json_config_info): + """ + Set new json config info. + + Args: + json_config_info: base64 str type + """ + self.__json_config_info = json_config_info + + +class TestResultObject: + """ + Class `TestResultObject` allows get test results. + - Test result of selected test `test_result`. + - All test results `all_test_results`. + """ + def __init__(self, results: Union[SubTestResultObject, List[SubTestResultObject]]): + if isinstance(results, SubTestResultObject): + self.__sub_tests = [results] + else: + self.__sub_tests = results + + def test_result(self, index: int) -> Optional[SubTestResultObject]: + """ + Test result of selected test. + + Args: + index (int) + + Returns: + object of `SubTestResultObject`|None type + """ + if index < 0 or index > len(self.__sub_tests) - 1: + warnings.warn("Incorrect test result object index. Please, select index again.") + return None + return self.__sub_tests[index] + + def all_test_results(self) -> list: + """ + Returns: + object of list type + """ + return self.__sub_tests diff --git a/UniTAP/dev/modules/dut_tests/test_utils.py b/UniTAP/dev/modules/dut_tests/test_utils.py new file mode 100644 index 0000000..efbe1db --- /dev/null +++ b/UniTAP/dev/modules/dut_tests/test_utils.py @@ -0,0 +1,190 @@ +import json +import base64 +from enum import IntEnum +from typing import Union + + +class CMTFormat(IntEnum): + RGB_LEGACY = 0 + XVYCC_422_BT601 = 1 + XVYCC_444_BT601 = 2 + RGB_WIDE_FIXED = 3 + RGB_CEA = 4 + YCBCR_422_BT601 = 5 + YCBCR_444_BT601 = 6 + DCI_P3 = 7 + XVYCC_422_BT709 = 9 + XVYCC_444_BT709 = 10 + RGB_WIDE_FLOAT = 11 + RGB_ADOBE = 12 + YCBCR_422_BT709 = 13 + YCBCR_444_BT709 = 14 + COLOR_PROFILE = 15 + Y_ONLY = 16 + RAW = 17 + + +class CMTVSCFormat(IntEnum): + SRGB = 0x00 + RGB_WIDE_FIXED_VCS = 0x01 + RGB_WIDE_FLOAT_VCS = 0x02 + RGB_ADOBE_V = 0x03 + RGB_DCI_P3 = 0x04 + RGB_CUSTOM_PROFILE = 0x05 + RGB_BT2020 = 0x06 + + YCBCR_444_BT601_VCS = 0x10 + YCBCR_444_BT709_VCS = 0x11 + YCBCR_444_XVYCC601 = 0x12 + YCBCR_444_XVYCC709 = 0x13 + YCBCR_444_SYCC601 = 0x14 + YCBCR_444_ADOBE_YCC601 = 0x15 + YCBCR_444_BT2020_YCCBCCRC = 0x16 + YCBCR_444_BT2020_YCBCR = 0x17 + + YCBCR_422_BT601_VCS = 0x20 + YCBCR_422_BT709_VCS = 0x21 + YCBCR_422_XVYCC601 = 0x22 + YCBCR_422_XVYCC709 = 0x23 + YCBCR_422_SYCC601 = 0x24 + YCBCR_422_ADOBE_YCC601 = 0x25 + YCBCR_422_BT2020_YCCBCCRC = 0x26 + YCBCR_422_BT2020_YCBCR = 0x27 + + YCBCR_420_BT601 = 0x30 + YCBCR_420_BT709 = 0x31 + YCBCR_420_XVYCC601 = 0x32 + YCBCR_420_XVYCC709 = 0x33 + YCBCR_420_SYCC601 = 0x34 + YCBCR_420_ADOBE_YCC601 = 0x35 + YCBCR_420_BT2020_YCCBCCRC = 0x36 + YCBCR_420_BT2020_YCBCR = 0x37 + + Y_ONLY_DICOM = 0x40 + RAW_CUSTOM_PROFILE = 0x50 + + YCBCR_SIMPLE_422_BT601 = 0x60 + YCBCR_SIMPLE_422_BT709 = 0x61 + YCBCR_SIMPLE_422_XVYCC601 = 0x62 + YCBCR_SIMPLE_422_XVYCC709 = 0x63 + YCBCR_SIMPLE_422_SYCC601 = 0x64 + YCBCR_SIMPLE_422_ADOBE_YCC601 = 0x65 + YCBCR_SIMPLE_422_BT2020_YCCBCCRC = 0x66 + YCBCR_SIMPLE_422_BT2020_YCBCR = 0x67 + + +class CMTBitDepth(IntEnum): + BPC_6 = 0 + BPC_8 = 1 + BPC_10 = 2 + BPC_12 = 3 + BPC_16 = 4 + + # BPP_RAW_6 = 1 + # BPP_RAW_7 = 2 + # BPP_RAW_8 = 3 + # BPP_RAW_10 = 4 + # BPP_RAW_12 = 5 + # BPP_RAW_14 = 6 + # BPP_RAW_16 = 7 + + +class CMTVSCBitDepth(IntEnum): + BPC_RGB_6 = 0 + BPC_RGB_8 = 1 + BPC_RGB_10 = 2 + BPC_RGB_12 = 3 + BPC_RGB_16 = 4 + + BPC_YCBCR_8 = 1 + BPC_YCBCR_10 = 2 + BPC_YCBCR_12 = 3 + BPC_YCBCR_16 = 4 + + # BPP_RAW_6 = 1 + # BPP_RAW_7 = 2 + # BPP_RAW_8 = 3 + # BPP_RAW_10 = 4 + # BPP_RAW_12 = 5 + # BPP_RAW_14 = 6 + # BPP_RAW_16 = 7 + + +def make_cf(color_format: CMTFormat, bpc: CMTBitDepth): + return (((color_format & 15) << 1) | ((bpc & 7) << 5)) + + +def make_vsc_cf(color_format: CMTVSCFormat, bpc: CMTVSCBitDepth): + return ((1 << 14) | ((color_format << 16) | ((bpc & 7) << 24))) + + +class TestReportConfigType(IntEnum): + Another = -1 + Dp1_4_Source = 0 + Dp2_1_Source = 1 + Dp1_4_Sink = 2 + Dp2_1_Sink = 3 + Dp2_1_Lttpr = 4 + + +class TestResult(IntEnum): + Unknown = -1 + Passed = 0 + Failed = 1 + Skipped = 2 + Aborted = 3 + + +def contains(params, config_id): + for item in params: + if item.config_id_name == config_id: + return True + return False + + +def is_need_to_update_test_configuration(params): + if contains(params, "TSI_DP14_SRCCTS_TIMEOUT"): + return TestReportConfigType.Dp1_4_Source + elif contains(params, "TSI_DP20_SRCCTS_TIMEOUT"): + return TestReportConfigType.Dp2_1_Source + elif contains(params, "TSI_DP14_SINKCTS_TIMEOUT"): + return TestReportConfigType.Dp1_4_Sink + elif contains(params, "TSI_DP20_SINKCTS_TIMEOUT"): + return TestReportConfigType.Dp2_1_Sink + else: + return TestReportConfigType.Another + + +def search_bit(mask): + count = 0 + if mask: + while not (mask & 1): + mask >>= 1 + count += 1 + return count + + +def update_default_value(value, default_value, mask): + old_value = default_value + offset = search_bit(mask) + value = (value << offset) & mask + old_value &= ~mask + old_value |= value + return old_value + + +def convert_to_cdf_json_base64(params, test_group): + group_info = str(hex(test_group)).replace("0x", "").zfill(8) + result_json = {"Header_Type": f"Group_0x{group_info}_Config"} + + for item in params: + result_json.update({item.config_id_name: + f"{int(item.default_value) if isinstance(item.default_value, bool) else item.default_value}"}) + + temp = json.dumps(result_json, indent=4) + + sample_string_bytes = temp.encode() + base64_bytes = base64.b64encode(sample_string_bytes) + base64_string = base64_bytes.decode() + + return base64_string diff --git a/UniTAP/dev/modules/memory_manager/__init__.py b/UniTAP/dev/modules/memory_manager/__init__.py new file mode 100644 index 0000000..5cd9046 --- /dev/null +++ b/UniTAP/dev/modules/memory_manager/__init__.py @@ -0,0 +1 @@ +from .memory_manager import MemoryManager diff --git a/UniTAP/dev/modules/memory_manager/memory_manager.py b/UniTAP/dev/modules/memory_manager/memory_manager.py new file mode 100644 index 0000000..7f6a196 --- /dev/null +++ b/UniTAP/dev/modules/memory_manager/memory_manager.py @@ -0,0 +1,46 @@ +from typing import List +from ctypes import c_uint64, c_uint32, c_uint8 +from enum import IntEnum + +from UniTAP.libs.lib_tsi.tsi_types import TSI_R_MEMORY_SIZE, TSI_MEMORY_LAYOUT,\ + TSI_MEMORY_BLOCK_INDEX, TSI_MEMORY_RESET_W, TSI_MEMORY_WRITE_W +from UniTAP.libs.lib_tsi.tsi import TSIX_TS_GetConfigItem, TSIX_TS_SetConfigItem +from UniTAP.libs.lib_tsi.tsi_io import DeviceIO + + +class MemoryManager: + class MemoryOwner(IntEnum): + MO_PatternGenerator = 0 + MO_AudioGenerator = 1 + MO_Events = 2 + + MAX_BLOCK_COUNT = 4 + + DEFAULT_MEMOTY_ALLOCATION = [534600704, 534600704, 534600704] + RESERVED_MEMORY_BYTES = 8192 * (5120 + 1) * 4 + 0x80000 + + def __init__(self, io: DeviceIO): + self.__io = io + + def get_total_memory(self) -> int: + result = self.__io.get(TSI_R_MEMORY_SIZE, c_uint64)[1] + return result + + def set_memory_layout(self, layout: List[int]): + self.__io.set(TSI_MEMORY_LAYOUT, layout, c_uint64, data_count=len(layout)) + + def get_memory_layout(self) -> List[int]: + return self.__io.get(TSI_MEMORY_LAYOUT, c_uint64, self.MAX_BLOCK_COUNT) + + def set_memory_block_index(self, index: MemoryOwner): + self.__io.set(TSI_MEMORY_BLOCK_INDEX, index.value) + + def make_default(self): + self.__io.set(TSI_MEMORY_LAYOUT, self.DEFAULT_MEMOTY_ALLOCATION, c_uint64, + data_count=len(self.DEFAULT_MEMOTY_ALLOCATION)) + + def reset(self): + self.__io.set(TSI_MEMORY_RESET_W, 0) + + def memory_write(self, data, size): + self.__io.set(TSI_MEMORY_WRITE_W, bytearray(data), data_type=c_uint8, data_count=size) diff --git a/UniTAP/dev/modules/opf/__init__.py b/UniTAP/dev/modules/opf/__init__.py new file mode 100644 index 0000000..06dc6d1 --- /dev/null +++ b/UniTAP/dev/modules/opf/__init__.py @@ -0,0 +1,2 @@ +from UniTAP.dev.modules.opf.handlers import OpfHandlerInternal, OpfHandlerDefault, OPFDialogAnswer +from UniTAP.dev.modules.opf.handler import OperatorFeedbackHandler diff --git a/UniTAP/dev/modules/opf/handler.py b/UniTAP/dev/modules/opf/handler.py new file mode 100644 index 0000000..ac658bd --- /dev/null +++ b/UniTAP/dev/modules/opf/handler.py @@ -0,0 +1,66 @@ +import platform +import warnings + +from UniTAP.libs.lib_tsi.tsi_types import TSI_OPF_CALLBACK_STRUCT, TSI_OPF_RETURN_CODE_ABORT +from UniTAP.libs.lib_tsi.tsi_io import DeviceIO +from UniTAP.utils import tsi_logging as logging +from .handlers import OpfHandlerBase, OpfHandlerDefault + +if platform.system() == 'Windows': + from ctypes import c_int, POINTER, c_void_p, WINFUNCTYPE + + OPF_CALLBACK_TYPE = WINFUNCTYPE(c_int, POINTER(TSI_OPF_CALLBACK_STRUCT), c_void_p) +else: + from ctypes import c_int, POINTER, c_void_p, CFUNCTYPE + + OPF_CALLBACK_TYPE = CFUNCTYPE(c_int, POINTER(TSI_OPF_CALLBACK_STRUCT), c_void_p) + + +class OperatorFeedbackHandler: + """ + Class `OperatorFeedbackHandler` helps to do required actions during DUT tests. Contains object of `OpfHandlerBase`, + that can be overridden. + - `handler` - set and get OPF Handler. `OpfHandlerBase` does OPF number 19 and 103, + `OpfHandlerInternal` does all OPF, `OpfHandlerDefault` does overridden OPF by user. + """ + + def __init__(self, device_io: DeviceIO): + global OPF_CALLBACK_TYPE + self.__io = device_io + self.__tsi_callback = OPF_CALLBACK_TYPE(self.__get_callback()) + self.__io.set_opf_callback(self.__tsi_callback) + + self.__handler = OpfHandlerDefault() + + def __get_callback(self): + def ofp_impl(_struct: POINTER(TSI_OPF_CALLBACK_STRUCT), context_ptr: c_void_p): + opf_struct: TSI_OPF_CALLBACK_STRUCT = _struct.contents + logging.debug(f"Received OPF dialog with ID: {opf_struct.id()}, Session: {opf_struct.session_id()}.") + + try: + return self.handler.handle(opf_struct.id(), + opf_struct.write_opf_result, + opf_struct.title(), + opf_struct.request1(), + opf_struct.request2(), + opf_struct.parameters()) + except BaseException as e: + warnings.warn(f"OPF Dialog {opf_struct.id()} was aborted due an exception. Exception: {e}") + return opf_struct.write_opf_result(TSI_OPF_RETURN_CODE_ABORT) + + return ofp_impl + + @property + def handler(self) -> OpfHandlerBase: + """ + + Return current OPF handler. Can be overriden in `set` method. + + Returns: + object of `OpfHandlerBase` type + """ + return self.__handler + + @handler.setter + def handler(self, value): + self.__handler = value diff --git a/UniTAP/dev/modules/opf/handlers/__init__.py b/UniTAP/dev/modules/opf/handlers/__init__.py new file mode 100644 index 0000000..4ed016a --- /dev/null +++ b/UniTAP/dev/modules/opf/handlers/__init__.py @@ -0,0 +1,3 @@ +from .base import OPFDialogAnswer, OpfHandlerBase +from .default import OpfHandlerDefault +from .internal import OpfHandlerInternal diff --git a/UniTAP/dev/modules/opf/handlers/base.py b/UniTAP/dev/modules/opf/handlers/base.py new file mode 100644 index 0000000..54527de --- /dev/null +++ b/UniTAP/dev/modules/opf/handlers/base.py @@ -0,0 +1,51 @@ +import abc +from typing import Callable +from enum import IntEnum + +from .opf_functions import OPFFunctions, logging, OPFDialogAnswer +from UniTAP.dev.modules.opf.parsers import OPFParametersParser + + +class WrongOPFFunctionSignature(Exception): + pass + + +class OpfHandlerBase: + """ + Class `OpfHandlerBase` allows working with functions that helps to do required actions during DUT tests. + - Use DSC content library `set_dsc_content_library`. If DSC image does not exist, it will be generated. + """ + __FUNCTIONS_MAP = { + 19: OPFFunctions.opf_19_handler, + 103: OPFFunctions.opf_103_handler, + } + + def __init__(self): + self.__parser = OPFParametersParser() + self.__tx = None + self.__rx = None + self.__callback_before_handling = {} + self._dsc_content_library_path = "" + + def _set_roles(self, role_tx, role_rx): + self.__tx = role_tx + self.__rx = role_rx + + def set_dsc_content_library(self, dsc_content_library_path: str = ""): + """ + Set new path to DSC content library folder. + If path is empty, content library will not use (all images will be generated into OPF functions without saving). + + Arguments: + dsc_content_library_path (`str`) - full path to DSC content library folder. + """ + self._dsc_content_library_path = dsc_content_library_path + + def handle(self, opf_id, reply_answer, *args) -> OPFDialogAnswer: + opf_params = self.__parser.parse(opf_id, *args) + + OPFFunctions.dsc_content_library_path = self._dsc_content_library_path + return reply_answer(self.__FUNCTIONS_MAP.get(opf_id)(self.__tx, self.__rx, *opf_params)) + + def _is_opf_processed(self, opf_id: int): + return self.__FUNCTIONS_MAP.get(opf_id) is not None diff --git a/UniTAP/dev/modules/opf/handlers/default.py b/UniTAP/dev/modules/opf/handlers/default.py new file mode 100644 index 0000000..cc53dd0 --- /dev/null +++ b/UniTAP/dev/modules/opf/handlers/default.py @@ -0,0 +1,46 @@ +import inspect +import warnings +from typing import Callable + +from UniTAP.dev.modules.opf.handlers.base import OpfHandlerBase, OPFDialogAnswer,\ + WrongOPFFunctionSignature +from UniTAP.dev.modules.opf.parsers import OPFParametersParser + + +class OpfHandlerDefault(OpfHandlerBase): + """ + Class `OpfHandlerDefault` inherit functionality from `OpfHandlerBase` and allows overriding opf functions. + Function `assign_function_for_opf_id` allows doing it. + """ + def __init__(self): + super().__init__() + self.__user_callback_dict = {} + self.__parser = OPFParametersParser() + + def handle(self, opf_id, reply_answer, *args) -> OPFDialogAnswer: + base_opf_handler = super()._is_opf_processed(opf_id) + user_opf_handler = self.__user_callback_dict.get(opf_id, None) is not None + if not user_opf_handler and not base_opf_handler: + warnings.warn(f"Test requested operator feedback dialog with id: {opf_id}, but user " + f"function was not provided. Test will be aborted.") + return reply_answer(OPFDialogAnswer.ABORT) + + if user_opf_handler: + opf_params_for_user = self.__parser.parse(opf_id, *args) + return reply_answer(self.__user_callback_dict.get(opf_id, None)(*opf_params_for_user)) + else: + return super().handle(opf_id, reply_answer, *args) + + def assign_function_for_opf_id(self, opf_id: int, func: Callable): + """ + Assign new function for doing required actions from OPF. + + Arguments: + opf_id (`int`) - id of OPF. + func (`Callable`) - object of function + """ + signature = inspect.getfullargspec(func) + if signature.annotations['return'] != OPFDialogAnswer: + raise WrongOPFFunctionSignature(f"Function MUST declare {OPFDialogAnswer} " + f"as return value") + self.__user_callback_dict[opf_id] = func diff --git a/UniTAP/dev/modules/opf/handlers/internal.py b/UniTAP/dev/modules/opf/handlers/internal.py new file mode 100644 index 0000000..aae2804 --- /dev/null +++ b/UniTAP/dev/modules/opf/handlers/internal.py @@ -0,0 +1,110 @@ +import inspect +from typing import Callable + +from UniTAP.dev.modules.opf.handlers.base import OpfHandlerBase, WrongOPFFunctionSignature +from UniTAP.dev.modules.opf.parsers import OPFParametersParser +from .opf_functions import * + + +class OpfHandlerInternal(OpfHandlerBase): + + __FUNCTIONS_MAP = { + 1: OPFFunctions.opf_1_handler, + 2: OPFFunctions.opf_2_handler, + 3: OPFFunctions.opf_3_handler, + 4: OPFFunctions.opf_4_handler, + 5: OPFFunctions.opf_5_handler, + 6: OPFFunctions.opf_6_handler, + 7: OPFFunctions.opf_7_handler, + 8: OPFFunctions.opf_8_handler, + 9: OPFFunctions.opf_9_handler, + 10: OPFFunctions.opf_10_handler, + 11: OPFFunctions.opf_11_handler, + 12: OPFFunctions.opf_12_handler, + 13: OPFFunctions.opf_13_handler, + 14: OPFFunctions.opf_14_handler, + 15: OPFFunctions.opf_15_handler, + 16: OPFFunctions.opf_16_handler, + 17: OPFFunctions.opf_17_handler, + 18: OPFFunctions.opf_18_handler, + 19: OPFFunctions.opf_19_handler, + 20: OPFFunctions.opf_20_handler, + 21: OPFFunctions.opf_21_handler, + 101: OPFFunctions.opf_101_handler, + 102: OPFFunctions.opf_102_handler, + 103: OPFFunctions.opf_103_handler, + 104: OPFFunctions.opf_104_handler, + 105: OPFFunctions.opf_105_handler, + 106: OPFFunctions.opf_106_handler, + 107: OPFFunctions.opf_107_handler, + 120: OPFFunctions.opf_120_handler, + 121: OPFFunctions.opf_121_handler, + 122: OPFFunctions.opf_122_handler, + 123: OPFFunctions.opf_123_handler, + 140: OPFFunctions.opf_140_handler, + 141: OPFFunctions.opf_141_handler, + 142: OPFFunctions.opf_142_handler, + 143: OPFFunctions.opf_143_handler, + 144: OPFFunctions.opf_144_handler, + 145: OPFFunctions.opf_145_handler, + 150: OPFFunctions.opf_150_handler, + 161: OPFFunctions.opf_161_handler + } + + __FUNCTIONS_MAP_AFTER = { + 1: OPFFunctions.opf_1_after_handler + } + + def __init__(self, port_tx, port_rx): + super().__init__() + self.__parser = OPFParametersParser() + self.__tx = port_tx + self.__rx = port_rx + self.__callback_before_handling = {} + self.__callback_after_handling = {} + + def handle(self, opf_id, reply_answer, *args) -> OPFDialogAnswer: + opf_params = self.__parser.parse(opf_id, *args) + user_opf_before_callback = self.__callback_before_handling.get(opf_id, None) + user_opf_after_callback = self.__callback_after_handling.get(opf_id, None) + internal_opf_after_callback = self.__FUNCTIONS_MAP_AFTER.get(opf_id, None) + + # + # Callback before main callback + # + if user_opf_before_callback is not None: + opf_before_result = user_opf_before_callback(*opf_params) + # Interrupt OPF in case of fail "before" OPF + if opf_before_result not in [OPFDialogAnswer.PASS, OPFDialogAnswer.PROCEED]: + return reply_answer(opf_before_result) + + # + # Main callback + # + OPFFunctions.dsc_content_library_path = self._dsc_content_library_path + opf_internal_result = self.__FUNCTIONS_MAP.get(opf_id)(self.__tx, self.__rx, *opf_params) + reply_answer(opf_internal_result) + + # + # Callback after main callback + # + if user_opf_after_callback is not None: + user_opf_after_callback(*opf_params) + elif internal_opf_after_callback is not None: + internal_opf_after_callback(self.__tx, self.__rx, *opf_params) + + return opf_internal_result + + def assign_function_before_handling_opf_id(self, opf_id: int, func: Callable): + signature = inspect.getfullargspec(func) + if signature.annotations['return'] != OPFDialogAnswer: + raise WrongOPFFunctionSignature(f"Function MUST declare {OPFDialogAnswer} " + f"as return value") + self.__callback_before_handling[opf_id] = func + + def assign_function_after_handling_opf_id(self, opf_id: int, func: Callable): + signature = inspect.getfullargspec(func) + if signature.annotations['return'] != OPFDialogAnswer: + raise WrongOPFFunctionSignature(f"Function MUST declare {OPFDialogAnswer} " + f"as return value") + self.__callback_after_handling[opf_id] = func diff --git a/UniTAP/dev/modules/opf/handlers/opf_functions.py b/UniTAP/dev/modules/opf/handlers/opf_functions.py new file mode 100644 index 0000000..c713319 --- /dev/null +++ b/UniTAP/dev/modules/opf/handlers/opf_functions.py @@ -0,0 +1,1390 @@ +import copy +import re +import threading +import time +import warnings +import UniTAP +from UniTAP.dev.modules.dut_tests.test_info import TestGroupId +from UniTAP.libs.lib_uicl.uicl import UICL_GeneratePattern, UICL_GeneratePattern_3Tap +from UniTAP.utils import tsi_logging as logging +from UniTAP.dev.ports.modules.link.dp.link_tx_types import LinkConfig +from UniTAP.common.audio_mode import AudioMode +from UniTAP.dev.ports.modules.vtg import * +from UniTAP.utils.uicl_api import * +from UniTAP.dev.ports.modules.edid import MainBlockType, AdditionalBlockType +from enum import IntEnum +from UniTAP.utils.function_wrapper import function_scheduler +from UniTAP.libs.lib_dscl.dscl import DSC_TOOLS_FOLDER, DSCL_ExtractPPSFromData, DSCL_Encode, DSCL_Decode +from UniTAP.libs.lib_dscl.dscl_utils import calculate_slice_size, dscl_image_to_dsc_vf +from UniTAP.libs.lib_pdl.pdl import generate_pattern_as_vf, PatternType +from UniTAP.libs.lib_tsi.tsi_types import TSI_MEMORY_WRITE_W, TSI_MEMORY_BLOCK_INDEX, TSI_MEMORY_LAYOUT, \ + TSI_DP20_SINKCTS_SUPPORT_444CRC, TSI_DP14_SINKCTS_SUPPORT_444CRC +from UniTAP.libs.lib_tsi.tsi_private_types import TSI_DSC_MEMORY_BLOCK, TSI_DSC_DATA_SIZE, \ + TSI_DSC_TX_CRC, TSI_DPRX_DSC_TEST_CRC, TSI_SELECT_SUITE +from UniTAP.libs.lib_tsi.tsi import TSIX_TS_SetConfigItem, TSIX_TS_GetConfigItem +from ctypes import c_uint64, c_uint32 +from UniTAP.libs.lib_tsi import TSI_OPF_RETURN_CODE_ABORT, TSI_OPF_RETURN_CODE_PASS, \ + TSI_OPF_RETURN_CODE_PROCEED, TSI_OPF_RETURN_CODE_FAIL, TSI_OPF_RETURN_CODE_AUTO_CLOSED + + +class OPFDialogAnswer(IntEnum): + ABORT = TSI_OPF_RETURN_CODE_ABORT + PASS = TSI_OPF_RETURN_CODE_PASS + PROCEED = TSI_OPF_RETURN_CODE_PROCEED + FAIL = TSI_OPF_RETURN_CODE_FAIL + NOTHING = TSI_OPF_RETURN_CODE_AUTO_CLOSED + + +class OPFFunctions: + dict_color_formats = {"RGB": ColorInfo.ColorFormat.CF_RGB, + "YCbCr 4:2:2": ColorInfo.ColorFormat.CF_YCbCr_422, + "YCbCr 4:4:4": ColorInfo.ColorFormat.CF_YCbCr_444, + "YCbCr 4:2:0": ColorInfo.ColorFormat.CF_YCbCr_420, + "Simple 4:2:2": ColorInfo.ColorFormat.CF_YCbCr_422, + "YCbCr422": ColorInfo.ColorFormat.CF_YCbCr_422, + "YCbCr444": ColorInfo.ColorFormat.CF_YCbCr_444, + "YCbCr420": ColorInfo.ColorFormat.CF_YCbCr_420, + "Simple422": ColorInfo.ColorFormat.CF_YCbCr_422 + } + + dict_dsc_color_formats = {"RGB": CompressionInfo.DscColorFormat.CF_RGB, + "YCbCr 4:2:2": CompressionInfo.DscColorFormat.CF_YCbCr_422, + "YCbCr 4:4:4": CompressionInfo.DscColorFormat.CF_YCbCr_444, + "YCbCr 4:2:0": CompressionInfo.DscColorFormat.CF_YCbCr_420, + "Simple 4:2:2": CompressionInfo.DscColorFormat.CF_Simple_422, + "YCbCr422": CompressionInfo.DscColorFormat.CF_YCbCr_422, + "YCbCr444": CompressionInfo.DscColorFormat.CF_YCbCr_444, + "YCbCr420": CompressionInfo.DscColorFormat.CF_YCbCr_420, + "Simple422": CompressionInfo.DscColorFormat.CF_Simple_422 + } + + dict_colorimetry = {None: ColorInfo.Colorimetry.CM_sRGB, + "ITU-601": ColorInfo.Colorimetry.CM_ITUR_BT601, + "ITU-709": ColorInfo.Colorimetry.CM_ITUR_BT709} + + dict_reduce_blanking = {None: Timing.ReduceBlanking.RB_NONE, + 'RB1': Timing.ReduceBlanking.RB1, + 'RB2': Timing.ReduceBlanking.RB2, + 'RB3': Timing.ReduceBlanking.RB3} + + dict_patterns = { + "Color Ramp": VideoPattern.ColorRamp, + "Color Square": VideoPattern.ColorSquares + } + + dict_dsc_color_names = { + CompressionInfo.DscColorFormat.CF_RGB: "RGB444", + CompressionInfo.DscColorFormat.CF_YCbCr_444: "YUV444", + CompressionInfo.DscColorFormat.CF_YCbCr_422: "YUV422", + CompressionInfo.DscColorFormat.CF_YCbCr_420: "YUV420", + CompressionInfo.DscColorFormat.CF_Simple_422: "SIMPL422" + } + + path_custom_image = os.path.join(DSC_TOOLS_FOLDER, "Default_16K.png") + dsc_content_library_path = "" + + g_vm = None + g_vf = None + g_h_slice_size = 0 + g_v_slice_size = 0 + + @staticmethod + def opf_pass_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + logging.info(message) + return OPFDialogAnswer.PASS + + @staticmethod + def check_dptx(dptx) -> bool: + return isinstance(dptx, (UniTAP.DPTX, UniTAP.DPTX4xx, UniTAP.DPTX5xx)) + + @staticmethod + def check_dprx(dprx) -> bool: + return isinstance(dprx, (UniTAP.DPRX, UniTAP.DPRX4xx, UniTAP.DPRX5xx)) + + @staticmethod + def check_hdtx(hdtx) -> bool: + return isinstance(hdtx, (UniTAP.HDTX, UniTAP.HDTX4xx)) + + @staticmethod + def check_hdrx(hdrx) -> bool: + return isinstance(hdrx, (UniTAP.HDRX, UniTAP.DPRX4xx)) + + @staticmethod + def check_video(dev_rx) -> bool: + def is_video_available(_dev_rx): + return _dev_rx.link.status.stream(0).crc != [0, 0, 0] and \ + _dev_rx.link.status.stream(0).video_mode.timing.hactive != 0 + + return function_scheduler(is_video_available, dev_rx, interval=2, timeout=10) + + @staticmethod + def opf_1_after_handler(dptx, dprx, dp_lanes, dp_link_rate, encoding): + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info((dp_lanes, dp_link_rate, encoding)) + if encoding is None: + config = LinkConfig.DP8b10b() + else: + if encoding == '8b/10b': + config = LinkConfig.DP8b10b() + else: + config = LinkConfig.DP128b132b() + config.lane_count = dp_lanes + config.bit_rate = dp_link_rate + dptx.link.config.set(config) + dptx.link.start_link_training() + + @staticmethod + def opf_1_handler(dptx, dprx, dp_lanes, dp_link_rate, encoding): + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_2_handler(dptx, dprx, res_x, res_y, res_frate, res_bpc, tim_std, tim_rb): + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info((res_x, res_y, res_frate, tim_std)) + timing_manager = dptx.pg.timing_manager + if tim_std.find("VIC") != -1: + tim_id = int(re.findall(r'\d+', tim_std)[0]) + timing = timing_manager.get_cta(tim_id) + elif tim_std.find("DMT") != -1: + tim_id = int(tim_std.replace("h", "").replace("DMT ", ""), 16) + timing = timing_manager.get_dmt(tim_id) + elif tim_std.find("CVT") != -1: + tim_id = int(tim_std.replace("h", "").replace("CVT ", ""), 16) + timing = timing_manager.get_cvt(tim_id) + else: + timing = timing_manager.search(res_x, res_y, res_frate, OPFFunctions.dict_reduce_blanking.get(tim_rb)) + + if timing is None: + timing = timing_manager.search(res_x, res_y, res_frate, OPFFunctions.dict_reduce_blanking.get(tim_rb)) + + color_mode = ColorInfo() + color_mode.color_format = ColorInfo.ColorFormat.CF_RGB + color_mode.bpc = res_bpc + color_mode.colorimetry = ColorInfo.Colorimetry.CM_sRGB + + video_mode = VideoMode(timing=timing, color_info=color_mode) + + dptx.pg.set_vm(video_mode) + dptx.pg.set_pattern(pattern=VideoPattern.ColorBars) + res_pg = dptx.pg.apply() + + res_app = dptx.pg.status().error + + if not res_pg: + logging.info(f"[UniTAP] Stream {0} - Apply {res_app.__str__()}") + return OPFDialogAnswer.ABORT + + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_3_handler(dptx, dprx, message: str): + logging.info(message) + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_4_handler(dptx, dprx, message: str): + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info(message) + + val = dptx.dpcd.read(0x00101, 1).data[0] & 0xFF + val &= ~ 0x7 + val |= 1 + dptx.dpcd.write(0x00101, val) + + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_5_handler(dptx, dprx, message: str): + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + val = dptx.dpcd.read(0x00101, 1).data[0] & 0xFF + val &= ~ 0x7 + val |= 4 + dptx.dpcd.write(0x00101, val) + + logging.info(message) + + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_6_handler(dptx, dprx, res_x, res_y, res_frate, res_bpc, timing_standard, col_format, + col_range, col_yc, + pattern_name) -> OPFDialogAnswer: + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info((res_x, res_y, res_frate, res_bpc, timing_standard, col_format, col_range, col_yc, pattern_name)) + + if dptx.link.config.get(UniTAP.LinkConfig.DP8b10b).force_edid_timings_after_lt: + timing = dptx.pg.get_stream_video_mode().timing + else: + timing = dptx.pg.timing_manager.search(res_x, res_y, res_frate, + rb=OPFFunctions.dict_reduce_blanking.get(timing_standard)) + + color_mode = ColorInfo() + color_mode.color_format = OPFFunctions.dict_color_formats.get(col_format) + color_mode.bpc = res_bpc + color_mode.colorimetry = OPFFunctions.dict_colorimetry.get(col_yc) + color_mode.dynamic_range = ColorInfo.DynamicRange.DR_VESA if col_range == 'VESA' else ColorInfo.DynamicRange.DR_CTA + + video_mode = VideoMode(timing=timing, color_info=color_mode) + + dptx.pg.set_vm(video_mode) + dptx.pg.set_pattern(pattern=OPFFunctions.dict_patterns.get(pattern_name)) + res_pg = dptx.pg.apply() + + if not res_pg: + return OPFDialogAnswer.ABORT + + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_7_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info(message) + + dptx.dpcd.write(0x600, 2) + dptx.link.start_link_training() + + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_8_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info(message) + + dptx.dpcd.write(0x600, 1) + dptx.link.start_link_training() + + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_9_handler(dptx, dprx, res_x, res_y, res_frate, res_bpc, tim_std, + pattern_name) -> OPFDialogAnswer: + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info((res_x, res_y, res_frate, res_bpc, tim_std)) + if not dptx.link.config.get(UniTAP.LinkConfig.DP8b10b).force_edid_timings_after_lt: + timing_manager = dptx.pg.timing_manager + rb = OPFFunctions.dict_reduce_blanking.get(tim_std) + timing = timing_manager.search(res_x, res_y, res_frate, rb=rb) + else: + timing = dptx.pg.get_stream_video_mode().timing + color_mode = ColorInfo() + color_mode.color_format = ColorInfo.ColorFormat.CF_RGB + color_mode.bpc = res_bpc + color_mode.colorimetry = ColorInfo.Colorimetry.CM_sRGB + + video_mode = VideoMode(timing=timing, color_info=color_mode) + + dptx.pg.set_vm(video_mode) + dptx.pg.set_pattern(pattern=OPFFunctions.dict_patterns.get(pattern_name)) + res_pg = dptx.pg.apply() + + if not res_pg: + return OPFDialogAnswer.ABORT + + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_10_handler(dptx, dprx, res_x, res_y, res_frate, res_bpc, timing_standard, audio_pattern, + ch_count, sample_freq, + sample_size, ch_alloc, ch_info) -> OPFDialogAnswer: + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info((audio_pattern, ch_count, sample_freq, sample_size)) + + audio_mode = AudioMode() + audio_mode.channel_count = ch_count + audio_mode.bits = sample_size + audio_mode.sample_rate = sample_freq + + dptx.ag.setup(audio_mode=audio_mode, + audio_pattern=UniTAP.AudioPattern.SignalSine, + signal_frequency=2000, + amplitude=60) + dptx.ag.apply() + + if res_x != 0: + timing_manager = dptx.pg.timing_manager + rb = OPFFunctions.dict_reduce_blanking.get(timing_standard) + + for standard in [UniTAP.Timing.Standard.SD_CTA, UniTAP.Timing.Standard.SD_DMT, + UniTAP.Timing.Standard.SD_CVT]: + timing = timing_manager.search(res_x, res_y, res_frate, standard=standard, rb=rb) + if timing is not None: + break + + color_mode = ColorInfo() + color_mode.color_format = ColorInfo.ColorFormat.CF_RGB + color_mode.bpc = res_bpc + color_mode.colorimetry = ColorInfo.Colorimetry.CM_sRGB + + video_mode = VideoMode(timing=timing, color_info=color_mode) + + dptx.pg.set_vm(video_mode) + dptx.pg.set_pattern(pattern=VideoPattern.ColorRamp) + res_pg = dptx.pg.apply() + + if dptx.pg.status().error != PGStatus.PGError.OK and not res_pg: + return OPFDialogAnswer.ABORT + else: + for i in range(dptx.pg.max_stream_count): + dptx.pg[i].set_pattern(UniTAP.VideoPattern.Disabled) + res_pg = dptx.pg.apply() + + if dptx.pg.status().error != PGStatus.PGError.OK and not res_pg: + return OPFDialogAnswer.ABORT + + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_11_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + return OPFFunctions.opf_pass_handler(dptx, dprx, message) + + @staticmethod + def opf_12_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + + if not OPFFunctions.check_dprx(dprx): + return OPFDialogAnswer.ABORT + + logging.info(message) + + def capture_frame(_dprx): + try: + dprx.video_capturer.start() + _vf = dprx.video_capturer.pop_element() + dprx.video_capturer.stop() + return _vf + except BaseException: + return None + + iterations = 5 + + for i in range(iterations): + + vf = capture_frame(dprx) + vf = video_frame_to_ci(vf, vf.color_info, OPFFunctions.g_vf.data_info) + + if vf is not None and len(vf.data) > 0: + if OPFFunctions.g_vf is not None: + if vf.color_info.color_format == UniTAP.ColorInfo.ColorFormat.CF_DSC: + if OPFFunctions.g_vf.data[4:] in vf.data: + return OPFDialogAnswer.PASS + else: + if OPFFunctions.g_vf.data in vf.data: + return OPFDialogAnswer.PASS + return OPFDialogAnswer.FAIL + + @staticmethod + def opf_13_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + return OPFFunctions.opf_pass_handler(dptx, dprx, message) + + @staticmethod + def opf_14_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info(message) + + data = dptx.dpcd.read(0x120, 1).data + data.append(1) + dptx.dpcd.write(0x120, data) + + if OPFFunctions.check_dprx(dprx): + data = dptx.dpcd.read(0x120, 1).data + if len(data) > 0 and data[0] == 1: + return OPFDialogAnswer.PROCEED + else: + return OPFDialogAnswer.ABORT + + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_15_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info(message) + + dptx.fec.enable(True) + + def is_fec_enabled(_dptx): + return _dptx.fec.is_enabled() + + if not function_scheduler(is_fec_enabled, dptx, interval=2, timeout=10): + return OPFDialogAnswer.ABORT + + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_16_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info(message) + + dptx.fec.enable(False) + + def is_fec_disable(_dptx): + return not _dptx.fec.is_enabled() + + if not function_scheduler(is_fec_disable, dptx, interval=2, timeout=10): + return OPFDialogAnswer.ABORT + + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_17_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + logging.info(message) + res_x = 1920 + res_y = 1080 + res_frate = 60000 + res_bpc = 8 + col_yc = None + col_format = "RGB" + timing_manager = dptx.pg.timing_manager + standard = Timing.Standard.SD_CTA + timing = timing_manager.search(res_x, res_y, res_frate, standard=standard) + + return OPFFunctions.opf_18_20_support(dptx, dprx, col_format, res_x, res_y, res_bpc, col_yc, timing, + pattern_name=None, enable_dsc=True) + + @staticmethod + def opf_18_20_support(dptx, dprx, col_format, res_x, res_y, res_bpc, col_yc, timing, pattern_name=None, + enable_dsc=True): + color_mode = ColorInfo() + color_mode.color_format = OPFFunctions.dict_color_formats.get(col_format) + color_mode.bpc = res_bpc + color_mode.colorimetry = ColorInfo.Colorimetry.CM_sRGB if col_yc is None else \ + ColorInfo.Colorimetry.CM_ITUR_BT601 + color_mode.dynamic_range = ColorInfo.DynamicRange.DR_VESA if col_yc is None else ColorInfo.DynamicRange.DR_CTA + data_info = DataInfo() + data_info.packing = DataInfo.Packing.P_PACKED if col_yc is None else DataInfo.Packing.P_PLANAR + data_info.component_order = DataInfo.ComponentOrder.CO_RGB if col_yc is None else DataInfo.ComponentOrder.CO_YCbCr + data_info.alignment = DataInfo.Alignment.A_LSB + + video_mode = VideoMode(timing=timing, color_info=color_mode) + + if enable_dsc: + sink_dsc_caps = dptx.dpcd.read(0x60, 16) + + block_prediction = bool(sink_dsc_caps.data[6] & 0x1) + dsc_minor = sink_dsc_caps.data[1] >> 4 & 0xf + dsc_major = sink_dsc_caps.data[1] & 0xf + + h_slice_size = int(calculate_slice_size(res_x, 10, color_mode.color_format)) + v_slice_size = int(calculate_slice_size(res_y, 10, color_mode.color_format)) + + dptx.pg.set_vm(video_mode) + + pixel_rate = timing.htotal * timing.vtotal * (timing.frame_rate / 1000) + link_bandwidth = dptx.link.status.available_link_rate + + bpp = (link_bandwidth * 0.9 / pixel_rate) * 16 + bpp = res_bpc * 16 if res_bpc * 16 < bpp else round(bpp) + + compression_info = CompressionInfo() + compression_info.v_slice_size = v_slice_size + compression_info.h_slice_size = h_slice_size + compression_info.bpp = bpp + compression_info.color_format = OPFFunctions.dict_dsc_color_formats.get(col_format) + compression_info.buffer_bit_depth = res_bpc + 1 + compression_info.is_block_prediction_enabled = block_prediction + compression_info.version = (dsc_major, dsc_minor) + + if OPFFunctions.dsc_content_library_path != "": + if not os.path.exists(OPFFunctions.dsc_content_library_path): + os.makedirs(OPFFunctions.dsc_content_library_path) + dsc_image_name = f"{res_x}x{res_y}_{OPFFunctions.dict_dsc_color_names.get(compression_info.color_format)}_" \ + f"{'BPY' if block_prediction else 'NBP'}_bpc{res_bpc}_" \ + f"bpp{compression_info.bpp}_{10}slicew_{10}sliceh_" \ + f"{compression_info.buffer_bit_depth}lb_v{dsc_major}{dsc_minor}.dsc" + full_path = os.path.join(OPFFunctions.dsc_content_library_path, dsc_image_name) + if os.path.exists(full_path): + # + # Temporary message "Update DSC folder" + # + warnings.warn("Since version 3.5.X/3.6.X the usual file for generating DSC images has been updated." + "Please update/regenerate your DSC folder/library with using new file.") + + with open(full_path, 'rb') as file: + data = bytearray(file.read()) + encoded_video_frame = dsc_video_frame_from_data(data) + else: + custom_vf = generate_pattern_as_vf(PatternType.Unigraf, res_x, res_y, color_mode, data_info) + encoded_video_frame = encode_video_frame(custom_vf, compression_info) + with open(full_path, 'wb') as file: + file.write(encoded_video_frame.data) + else: + custom_vf = generate_pattern_as_vf(PatternType.Unigraf, res_x, res_y, color_mode, data_info) + encoded_video_frame = encode_video_frame(custom_vf, compression_info) + + dptx.pg.set_pattern(pattern=encoded_video_frame) + res = dptx.pg.apply() + + if not res and dptx.pg.status().error != PGStatus.PGError.OK: + return OPFDialogAnswer.ABORT + + OPFFunctions.g_vf = encoded_video_frame + elif pattern_name is not None: + dptx.pg.set_vm(video_mode) + dptx.pg.set_pattern(pattern=pattern_name) + res = dptx.pg.apply() + + if not res and dptx.pg.status().error != PGStatus.PGError.OK: + return OPFDialogAnswer.ABORT + + try: + if not OPFFunctions.check_video(dprx): + print('Video is not available.') + return OPFDialogAnswer.ABORT + except BaseException as e: + print(e) + return OPFDialogAnswer.ABORT + + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_18_handler(dptx, dprx, res_x, res_y, res_frate, res_bpc, col_format, col_range, col_yc, + tim_std) \ + -> OPFDialogAnswer: + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info((res_x, res_y, res_frate, res_bpc, col_format, col_range, col_yc, tim_std)) + + timing_manager = dptx.pg.timing_manager + standard = Timing.Standard.SD_CTA if OPFFunctions.dict_reduce_blanking.get( + tim_std) is None else Timing.Standard.SD_CVT + timing = timing_manager.search(res_x, res_y, res_frate, standard=standard, + rb=OPFFunctions.dict_reduce_blanking.get(tim_std)) + + return OPFFunctions.opf_18_20_support(dptx, dprx, col_format, res_x, res_y, res_bpc, col_yc, timing) + + @staticmethod + def opf_19_handler(dptx, dprx, use_3tap) -> OPFDialogAnswer: + + if not OPFFunctions.check_dprx(dprx): + return OPFDialogAnswer.ABORT + + # check DSC state + if not dprx.link.capabilities.link_caps_status().dsc: + return OPFDialogAnswer.ABORT + + incorrect_crc = [0xFFFF, 0xFFFF, 0xFFFF, 0x1111, 0x1111, 0x1111] + + def load_dsc(handle: int, values: list) -> OPFDialogAnswer: + TSIX_TS_SetConfigItem(handle, TSI_DPRX_DSC_TEST_CRC, values, data_count=6) + return OPFDialogAnswer.FAIL + + # TODO: Need to change on public interface. Temporary solution. + device_handle = dprx.bulk_capturer.__getattribute__("_BulkCapturer__memory_manager"). \ + __getattribute__('_MemoryManager__io'). \ + __getattribute__('device_handle') + + iterations = 5 + + for i in range(iterations): + # capture frame + try: + dprx.video_capturer.start(frames_count=1) + vf = dprx.video_capturer.capture_result.buffer[-1] + dprx.video_capturer.stop() + except BaseException: + dprx.video_capturer.stop() + return load_dsc(device_handle, incorrect_crc) + + def create_reference_image(video_frame, crc_values: list): + try: + pps_info = DSCL_ExtractPPSFromData(video_frame.data) + except BaseException: + return + + sampling = UICL_Sampling.Sampling_444 + if pps_info.is_yuv(): + if pps_info.is_420(): + sampling = UICL_Sampling.Sampling_420 + elif pps_info.is_422(): + sampling = UICL_Sampling.Sampling_422 + elif pps_info.is_simple_422(): + sampling = UICL_Sampling.Sampling_422 + + image = UICL_Image() + parameters = UICL_ImageParameters() + parameters.Width = pps_info.width() + parameters.Height = pps_info.height() + parameters.BitsPerColor = pps_info.bpc() + parameters.Alignment = UICL_Alignment.Alignment_LSB + parameters.IsFullRange = False + + if pps_info.is_yuv(): + parameters.Colorspace = UICL_Colorspace.Colorspace_YCbCr + parameters.Colorimetry = UICL_Colorimetry.Colorimetry_ITU_R_BT709 + parameters.Sampling = sampling + parameters.ComponentOrder = UICL_ComponentOrder.Order_YCbCr + parameters.Packing = UICL_Packing.Packing_Planar + image.Parameters = parameters + + try: + image.DataSize = UICL_GetRequiredBufferSize(image) + image.DataPtr = (c_uint8 * image.DataSize)() + if use_3tap: + res = UICL_GeneratePattern_3Tap(image, 1) + else: + res = UICL_GeneratePattern(image, 1) + + if res < UICL_SUCCESS: + return + + except BaseException: + return + else: + parameters.Colorspace = UICL_Colorspace.Colorspace_RGB + parameters.Colorimetry = UICL_Colorimetry.Colorimetry_Unknown + parameters.Sampling = sampling + parameters.ComponentOrder = UICL_ComponentOrder.Order_RGB + parameters.Packing = UICL_Packing.Packing_Packed + image.Parameters = parameters + + try: + image.DataSize = UICL_GetRequiredBufferSize(image) + image.DataPtr = (c_uint8 * image.DataSize)() + if use_3tap: + res = UICL_GeneratePattern_3Tap(image, 0) + else: + res = UICL_GeneratePattern(image, 0) + + if res < UICL_SUCCESS: + return + + except BaseException: + return + + dsc_encoded_image = DSCL_Encode(image, pps_info) + OPFFunctions.g_vf = dscl_image_to_dsc_vf(dsc_encoded_image) + dsc_decoded_uicl_image = DSCL_Decode(dsc_encoded_image) + + reference_image_crc = UICL_CRC16() + try: + res = UICL_CalculateCRC16(dsc_decoded_uicl_image, reference_image_crc) + if res < UICL_SUCCESS: + crc_values.extend([0xFFFF, 0xFFFF, 0xFFFF]) + else: + crc_values.extend([reference_image_crc.R, reference_image_crc.G, reference_image_crc.B]) + except BaseException: + return + + def decompress_dsc(video_frame, crc_values: list): + decoded_captured_vf = decode_video_frame(video_frame) + uicl_decoded_image_2 = image_from_vf(decoded_captured_vf) + decompressed_image_crc = UICL_CRC16() + try: + res = UICL_CalculateCRC16(uicl_decoded_image_2, decompressed_image_crc) + if res < UICL_SUCCESS: + crc_values.extend([0x1111, 0x1111, 0x1111]) + else: + crc_values.extend([decompressed_image_crc.R, decompressed_image_crc.G, + decompressed_image_crc.B]) + except BaseException: + return + + reference_image_crc_values = [] + decompressed_image_crc_values = [] + + thread_create_reference_image = threading.Thread(target=create_reference_image, + args=(vf, reference_image_crc_values)) + thread_decompressed_image = threading.Thread(target=decompress_dsc, + args=(vf, decompressed_image_crc_values)) + + thread_create_reference_image.start() + thread_decompressed_image.start() + + thread_create_reference_image.join() + thread_decompressed_image.join() + + if len(reference_image_crc_values) > 0 and len(decompressed_image_crc_values) > 0 and \ + ((reference_image_crc_values == decompressed_image_crc_values) or + (reference_image_crc_values != decompressed_image_crc_values and (i == iterations - 1))): + # Write crc to register + load_dsc(device_handle, [decompressed_image_crc_values[0], + decompressed_image_crc_values[1], + decompressed_image_crc_values[2], + reference_image_crc_values[0], + reference_image_crc_values[1], + reference_image_crc_values[2]]) + + return OPFDialogAnswer.PROCEED + + return load_dsc(device_handle, incorrect_crc) + + @staticmethod + def opf_20_handler(dptx, dprx, res_x, res_y, res_frate, res_bpc, col_format, col_range, col_yc, tim_std, + pattern_name, enable_dsc) -> OPFDialogAnswer: + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info((res_x, res_y, res_frate, res_bpc, col_format, col_range, col_yc, tim_std, pattern_name)) + + if pattern_name is not None: + if pattern_name == 'No Video': + pattern_name = VideoPattern.Disabled + elif pattern_name == 'Color Ramp': + pattern_name = VideoPattern.ColorRamp + elif pattern_name == 'Color Square': + pattern_name = VideoPattern.ColorSquares + elif pattern_name == 'Black and Vertical lines': + pattern_name = VideoPattern.WhiteVStrips + elif pattern_name == "Any": + if col_format == "RGB": + pattern_name = UniTAP.VideoPattern.ColorRamp + else: + pattern_name = UniTAP.VideoPattern.ColorSquares + else: + pattern_name = VideoPattern.ColorBars + + timing_manager = dptx.pg.timing_manager + + for item in tim_std: + if item.find("VIC") != -1: + tim_id = int(re.findall(r'\d+', item)[0]) + timing = timing_manager.get_cta(tim_id) + if timing is None: + continue + break + elif item.find("DMT") != -1: + res = re.findall(r'\w+', item)[1][:-1] + tim_id = int(res, 16) + timing = timing_manager.get_dmt(tim_id) + if timing is None: + continue + break + elif item.find("CVT") != -1: + res = re.findall(r'RB1|RB2|RB3', item) + tim_id = res[0] if len(res) > 0 else None + standard = Timing.Standard.SD_CVT + timing = timing_manager.search(res_x, res_y, res_frate, + standard=standard, + rb=OPFFunctions.dict_reduce_blanking.get(tim_id)) + if timing is None: + continue + break + elif item.find("UGF") != -1: + pass + elif item.find("OVT") != -1: + standard = Timing.Standard.SD_OVT + timing = timing_manager.search(res_x, res_y, res_frate, + standard=standard) + if timing is None: + continue + break + else: + timing = timing_manager.search(res_x, res_y, res_frate) + + return OPFFunctions.opf_18_20_support(dptx, dprx, col_format, res_x, res_y, res_bpc, col_yc, timing, + pattern_name, enable_dsc) + + @staticmethod + def opf_21_handler(dptx, dprx, audio_pattern, ch_count, sample_freq, sample_size, ch_alloc, ch_info): + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info((audio_pattern, ch_count, sample_freq, sample_size)) + + audio_mode = AudioMode() + audio_mode.channel_count = ch_count + audio_mode.bits = sample_size + audio_mode.sample_rate = sample_freq + audio_pattern_value = UniTAP.AudioPattern.SignalSawtooth if audio_pattern.find("Sawtooth") != -1 else ( + UniTAP.AudioPattern.SignalSine) + + dptx.ag.setup(audio_mode=audio_mode, + audio_pattern=audio_pattern_value, + signal_frequency=2000, + amplitude=60) + res = dptx.ag.apply() + if not res: + return OPFDialogAnswer.ABORT + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_101_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + if not OPFFunctions.check_dprx(dprx): + return OPFDialogAnswer.FAIL + + try: + dprx.audio_capturer.start(10) + dprx.audio_capturer.stop() + return UniTAP.OPFDialogAnswer.PASS if len( + dprx.audio_capturer.capture_result.buffer) > 0 else UniTAP.OPFDialogAnswer.FAIL + except BaseException as e: + print(f"Cannot capture audio. Error: {e}") + return UniTAP.OPFDialogAnswer.FAIL + + @staticmethod + def opf_102_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + + if not OPFFunctions.check_dprx(dprx): + return OPFDialogAnswer.FAIL + + def is_crc_valid(rx, dsc_flag): + return rx.link.status.stream(0).dsc_crc != [0, 0, 0] if dsc_flag else \ + rx.link.status.stream(0).crc != [0, 0, 0] + + logging.info(message) + if OPFFunctions.g_vm is not None: + try: + if UniTAP.utils.function_scheduler(is_crc_valid, dprx, True, interval=2, timeout=10): + + try: + dprx.video_capturer.start(frames_count=5) + vf_list = dprx.video_capturer.capture_result.buffer + dprx.video_capturer.stop() + except BaseException as e: + logging.info(f'{e}\nCannot capture frames.') + dprx.video_capturer.stop() + return OPFDialogAnswer.FAIL + + if isinstance(vf_list, list) and len(vf_list) == 5: + for vf in vf_list: + delta = len(vf.data) - len(OPFFunctions.g_vm.data) + 4 + if OPFFunctions.g_vm.data[4:] in vf.data[:-delta]: + return OPFDialogAnswer.PASS + + logging.info("Incorrect sequence of frames.") + return OPFDialogAnswer.FAIL + else: + logging.info("CRC is not valid.") + return OPFDialogAnswer.FAIL + except BaseException as e: + logging.info(f"{e}\nCannot check CRC.") + return OPFDialogAnswer.FAIL + else: + if UniTAP.utils.function_scheduler(is_crc_valid, dprx, False, interval=2, timeout=10): + + def capture_frame(_dprx): + try: + dprx.video_capturer.start() + _vf = dprx.video_capturer.pop_element() + dprx.video_capturer.stop() + return _vf + except BaseException: + return None + + iterations = 5 + + for i in range(iterations): + vf = capture_frame(dprx) + if len(vf.data) > 0: + return OPFDialogAnswer.PASS + + return OPFDialogAnswer.FAIL + + @staticmethod + def opf_103_handler(dptx, dprx, width, height, rate, color_format, is_block_prediction_enabled, bpc, + bpp, + h_slice_number, buffer_bit_depth, v_slice_number, dsc_v_minor, dsc_v_major): + # TODO: Need to resolve problem with writing TSI_VR_DSC_MEMORY_BLOCK and TSI_VR_DSC_DATA_SIZE + + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info((width, height, rate, color_format, is_block_prediction_enabled, bpc, bpp, + h_slice_number, + buffer_bit_depth, v_slice_number, dsc_v_major, dsc_v_minor)) + + # TODO: Need to change on public interface. Temporary solution. + device_handle = dptx.pg.__getattribute__('_DpMstPatternGenerator__pg_list')[0]. \ + __getattribute__("_PatternGenerator__memory_manager"). \ + __getattribute__('_MemoryManager__io'). \ + __getattribute__('device_handle') + + color_mode = ColorInfo() + color_mode.color_format = OPFFunctions.dict_color_formats.get(color_format) + col_rgb = color_mode.color_format == ColorInfo.ColorFormat.CF_RGB + color_mode.bpc = bpc + color_mode.colorimetry = ColorInfo.Colorimetry.CM_sRGB if col_rgb else ColorInfo.Colorimetry.CM_ITUR_BT709 + + h_slice_size = int(calculate_slice_size(width, h_slice_number, color_mode.color_format)) + v_slice_size = int(calculate_slice_size(height, v_slice_number, color_mode.color_format)) + OPFFunctions.g_h_slice_size = h_slice_size + OPFFunctions.g_v_slice_size = v_slice_size + + res, suite_id = TSIX_TS_GetConfigItem(device_handle, TSI_SELECT_SUITE, c_uint32) + if res > 0: + suite_id = TestGroupId(suite_id) + else: + suite_id = TestGroupId.UNKNOWN + + res, is_simple_as_444 = 0, False + try: + if suite_id in [TestGroupId.DP_TX_LL_CTS, TestGroupId.DP_TX_LL_CTS_DSC, + TestGroupId.DP_TX_DISPLAYID, TestGroupId.DP_TX_ADAPTIVESYNC]: + res, is_simple_as_444 = TSIX_TS_GetConfigItem(device_handle, TSI_DP14_SINKCTS_SUPPORT_444CRC, c_uint32) + elif suite_id in [TestGroupId.DP_2_1_TX_LL_CTS, TestGroupId.DP_2_1_TX_DSC_CTS, + TestGroupId.DP_2_1_TX_DISPAYID, TestGroupId.DP_2_1_TX_ADAPTIVESYNC]: + res, is_simple_as_444 = TSIX_TS_GetConfigItem(device_handle, TSI_DP20_SINKCTS_SUPPORT_444CRC, c_uint32) + except BaseException as e: + pass + + compression_info = OPFFunctions.get_compression_info(v_slice_size, h_slice_size, bpp, color_format, + buffer_bit_depth, is_block_prediction_enabled, + dsc_v_major, dsc_v_minor, is_simple_as_444) + + data_info = DataInfo() + data_info.packing = DataInfo.Packing.P_PACKED if col_rgb else DataInfo.Packing.P_PLANAR + data_info.component_order = DataInfo.ComponentOrder.CO_RGB if col_rgb else DataInfo.ComponentOrder.CO_YCbCr + data_info.alignment = DataInfo.Alignment.A_LSB + + try: + encoded_video_frame = OPFFunctions.check_dsc_content_library(width, height, color_mode, + is_block_prediction_enabled, bpc, bpp, + h_slice_number, v_slice_number, + compression_info, dsc_v_major, + dsc_v_minor, data_info) + except BaseException as e: + logging.info(e) + return OPFDialogAnswer.ABORT + + OPFFunctions.g_vm = copy.deepcopy(encoded_video_frame) + + TSIX_TS_SetConfigItem(device_handle, TSI_MEMORY_LAYOUT, len(encoded_video_frame.data), c_uint64) + TSIX_TS_SetConfigItem(device_handle, TSI_MEMORY_BLOCK_INDEX, 0, data_size=4) + TSIX_TS_SetConfigItem(device_handle, TSI_MEMORY_WRITE_W, bytearray(encoded_video_frame.data), + data_type=c_uint8, + data_count=len(encoded_video_frame.data)) + TSIX_TS_SetConfigItem(device_handle, TSI_DSC_MEMORY_BLOCK, 0) + TSIX_TS_SetConfigItem(device_handle, TSI_DSC_DATA_SIZE, len(encoded_video_frame.data)) + + decoded_vf = decode_video_frame(encoded_video_frame) + + crc0, crc1, crc2 = calculate_crc(decoded_vf) + TSIX_TS_SetConfigItem(device_handle, TSI_DSC_TX_CRC, [crc0, crc1, crc2], data_count=3) + + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_104_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + + if not OPFFunctions.check_dprx(dprx): + return OPFDialogAnswer.FAIL + + logging.info(message) + + def capture_frame(_dprx): + try: + dprx.video_capturer.start() + _vf = dprx.video_capturer.pop_element() + dprx.video_capturer.stop() + return _vf + except BaseException: + return None + + iterations = 5 + + for i in range(iterations): + + vf = capture_frame(dprx) + + if vf is not None and isinstance(vf, VideoFrameDSC): + slice_size = OPFFunctions.g_v_slice_size * OPFFunctions.g_h_slice_size + pps_size = 128 + delta = len(vf.data) - len(OPFFunctions.g_vm.data) + 4 + + # Slice #1 + if OPFFunctions.g_vm.data[4 + pps_size:slice_size] in vf.data[pps_size:slice_size]: + # Slice #3 + if OPFFunctions.g_vm.data[4 + pps_size + slice_size * 2:slice_size * 3] in \ + vf.data[pps_size + slice_size * 2:slice_size * 3]: + # Slice #4 + if OPFFunctions.g_vm.data[4 + pps_size + slice_size * 3:slice_size * 4] in \ + vf.data[pps_size + slice_size * 3:]: + # Slice #2 + if OPFFunctions.g_vm.data[4 + pps_size + slice_size:slice_size * 2] not in \ + vf.data[pps_size + slice_size:slice_size * 2]: + return OPFDialogAnswer.PASS + else: + print(f"Frame {i + 1}: Image looks good and distortion free for slice #2. It is wrong.") + else: + print( + f"Frame {i + 1}: Image does not look good and distortion free for slice #4. It is wrong.") + else: + print(f"Frame {i + 1}: Image does not look good and distortion free for slice #3. It is wrong.") + else: + print(f"Frame {i + 1}: Image does not look good and distortion free for slice #1. It is wrong.") + + return OPFDialogAnswer.FAIL + + @staticmethod + def opf_105_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + return OPFFunctions.opf_pass_handler(dptx, dprx, message) + + @staticmethod + def opf_106_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + return OPFFunctions.opf_pass_handler(dptx, dprx, message) + + @staticmethod + def opf_107_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + return OPFFunctions.opf_pass_handler(dptx, dprx, message) + + @staticmethod + def opf_120_handler(dptx, dprx, res_x, res_y, res_frate, res_bpc, color_format) -> OPFDialogAnswer: + if not OPFFunctions.check_dprx(dprx): + return OPFDialogAnswer.ABORT + + logging.info((res_x, res_y, res_frate, res_bpc, color_format)) + stream_status = dprx.link.status.stream(0) + + if res_x == stream_status.video_mode.timing.hactive and res_y == stream_status.video_mode.timing.vactive and \ + abs(res_frate - stream_status.video_mode.timing.frame_rate) <= 500 and \ + res_bpc == stream_status.video_mode.color_info.bpc and \ + OPFFunctions.dict_color_formats.get( + color_format) == stream_status.video_mode.color_info.color_format: + return OPFDialogAnswer.PASS + else: + return OPFDialogAnswer.FAIL + + @staticmethod + def opf_121_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + return OPFFunctions.opf_pass_handler(dptx, dprx, message) + + @staticmethod + def opf_122_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + return OPFFunctions.opf_pass_handler(dptx, dprx, message) + + @staticmethod + def opf_123_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + return OPFFunctions.opf_pass_handler(dptx, dprx, message) + + @staticmethod + def opf_140_handler(dptx, dprx, res_x, res_y, res_frate, tim_std, tim_std_num) -> OPFDialogAnswer: + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info((res_x, res_y, res_frate, tim_std, tim_std_num)) + + if tim_std_num is not None and tim_std_num.find('h') != -1: + tim_std_num = tim_std_num.replace('h', '') + + timing_manager = dptx.pg.timing_manager + if tim_std == 'DMT': + timing = timing_manager.get_dmt(int(tim_std_num, 16)) + elif tim_std == 'VIC': + timing = timing_manager.get_cta(int(tim_std_num)) + else: + timing = timing_manager.search(res_x, res_y, res_frate, + rb=OPFFunctions.dict_reduce_blanking.get(tim_std)) + + color_mode = ColorInfo() + color_mode.color_format = ColorInfo.ColorFormat.CF_RGB + color_mode.bpc = 8 + color_mode.colorimetry = ColorInfo.Colorimetry.CM_sRGB + + video_mode = VideoMode(timing=timing, color_info=color_mode) + + dptx.pg.set_vm(video_mode) + dptx.pg.set_pattern(pattern=VideoPattern.ColorBars) + res_pg = dptx.pg.apply() + + if not res_pg: + return OPFDialogAnswer.ABORT + + time.sleep(5) + rx_vm = dprx.link.status.stream(0).video_mode + logging.info((rx_vm.timing.hactive, video_mode.timing.hactive, " | ", rx_vm.timing.vactive, + video_mode.timing.vactive)) + if rx_vm.timing.hactive == video_mode.timing.hactive and rx_vm.timing.vactive == video_mode.timing.vactive: + return OPFDialogAnswer.PROCEED + else: + return OPFDialogAnswer.ABORT + + @staticmethod + def opf_141_handler(dptx, dprx, audio_format, channels, size, rate) -> OPFDialogAnswer: + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info((audio_format, channels, size, rate)) + + audio_mode = AudioMode() + audio_mode.channel_count = channels + audio_mode.bits = size + audio_mode.sample_rate = rate + + dptx.ag.setup(audio_mode=audio_mode, + audio_pattern=UniTAP.AudioPattern.SignalSine, + signal_frequency=2000, + amplitude=60) + dptx.ag.apply() + time.sleep(3) + + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_142_handler(dptx, dprx, message: str): + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info(message) + + if dptx.link.status.link_encoding == UniTAP.DpLinkEncoding.LE_8b10b: + config = UniTAP.LinkConfig.DP8b10b() + else: + config = UniTAP.LinkConfig.DP128b132b() + config.adaptive_sync_auto_enable = True + dptx.link.config.set(config) + dptx.link.start_link_training() + time.sleep(1) + + res_app = dptx.pg.status().error + + def is_adaptive_sync_enabled(_dptx): + status = _dptx.pg[0].adaptive_sync_status() + return status + + if not function_scheduler(is_adaptive_sync_enabled, dptx, interval=3, timeout=30): + return OPFDialogAnswer.ABORT + + if res_app == PGStatus.PGError.OK: + return OPFDialogAnswer.PROCEED + else: + return OPFDialogAnswer.ABORT + + @staticmethod + def opf_143_handler(dptx, dprx, rate: int) -> OPFDialogAnswer: + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info(rate) + + dptx.pg.set_as_config(as_config=FixedASParams(refresh_rate=rate, + divide_by_1_001=False, + increase_lines=100, + decrease_lines=100)) + dptx.pg.apply() + + res_app = dptx.pg.status().error + + def is_adaptive_sync_enabled(_dptx): + status = _dptx.pg[0].adaptive_sync_status() + return status + + if not function_scheduler(is_adaptive_sync_enabled, dptx, interval=3, timeout=30): + return OPFDialogAnswer.ABORT + + if res_app == PGStatus.PGError.OK: + return OPFDialogAnswer.PROCEED + else: + return OPFDialogAnswer.ABORT + + @staticmethod + def opf_144_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + logging.info(message) + if message.find("Zigzag Sweep") != -1 or message.find("Square") != -1: + + increase_lines, decrease_lines, max_lanes, min_lanes = OPFFunctions.as_parse_edid(dptx) + + if message.find("Zigzag Sweep") != -1: + dptx.pg.set_as_config(as_config=ZigzagASParams(min_lanes=min_lanes, + max_lanes=max_lanes, + increase_lines=increase_lines, + decrease_lines=decrease_lines)) + else: + dptx.pg.set_as_config(as_config=SquareASParams(min_lanes=min_lanes, + max_lanes=max_lanes, + period_frames=20)) + else: + dptx.pg.set_as_config(as_config=ConstantASParams(lines=20)) + dptx.pg.apply() + res_app = dptx.pg.status().error + + def is_adaptive_sync_enabled(_dptx): + status = _dptx.pg[0].adaptive_sync_status() + return status + + if not function_scheduler(is_adaptive_sync_enabled, dptx, interval=3, timeout=30): + return OPFDialogAnswer.ABORT + + if res_app == PGStatus.PGError.OK: + return OPFDialogAnswer.PROCEED + else: + return OPFDialogAnswer.ABORT + + @staticmethod + def opf_145_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + if not OPFFunctions.check_dptx(dptx): + return OPFDialogAnswer.ABORT + + increase_lines, decrease_lines, max_lanes, min_lanes = OPFFunctions.as_parse_edid(dptx) + + dptx.pg.set_as_config(as_config=ZigzagASParams(min_lanes=min_lanes, + max_lanes=max_lanes, + increase_lines=increase_lines, + decrease_lines=decrease_lines)) + + dptx.pg.apply() + res_app = dptx.pg.status().error + + def is_adaptive_sync_enabled(_dptx): + time.sleep(0.5) + status = _dptx.pg[0].adaptive_sync_status() + return status + + if not function_scheduler(is_adaptive_sync_enabled, dptx, interval=3, timeout=30): + return OPFDialogAnswer.ABORT + + if res_app == PGStatus.PGError.OK: + return OPFDialogAnswer.PROCEED + else: + return OPFDialogAnswer.ABORT + + @staticmethod + def opf_150_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + return OPFFunctions.opf_pass_handler(dptx, dprx, message) + + @staticmethod + def opf_151_handler(hdtx, hdrx, width, height, rate, color_format, is_block_prediction_enabled, bpc, + bpp, h_slice_number, buffer_bit_depth, v_slice_number, dsc_v_minor, dsc_v_major): + + if not OPFFunctions.check_hdtx(hdtx): + return OPFDialogAnswer.ABORT + + logging.info(width, height, rate, color_format, is_block_prediction_enabled, bpc, bpp, + h_slice_number, + buffer_bit_depth, v_slice_number, dsc_v_major, dsc_v_minor) + + color_mode = ColorInfo() + color_mode.color_format = OPFFunctions.dict_color_formats.get(color_format) + col_rgb = color_mode.color_format == ColorInfo.ColorFormat.CF_RGB + color_mode.bpc = bpc + color_mode.colorimetry = ColorInfo.Colorimetry.CM_sRGB if col_rgb else ColorInfo.Colorimetry.CM_ITUR_BT709 + + h_slice_size = int(calculate_slice_size(width, h_slice_number)) + v_slice_size = int(calculate_slice_size(height, v_slice_number)) + OPFFunctions.g_h_slice_size = h_slice_size + OPFFunctions.g_v_slice_size = v_slice_size + + compression_info = OPFFunctions.get_compression_info(v_slice_size, h_slice_size, bpp, color_format, + buffer_bit_depth, is_block_prediction_enabled, + dsc_v_major, dsc_v_minor, False) + + data_info = DataInfo() + data_info.packing = DataInfo.Packing.P_PACKED if col_rgb else DataInfo.Packing.P_PLANAR + data_info.component_order = DataInfo.ComponentOrder.CO_RGB if col_rgb else DataInfo.ComponentOrder.CO_YCbCr + data_info.alignment = DataInfo.Alignment.A_LSB + + try: + encoded_video_frame = OPFFunctions.check_dsc_content_library(width, height, color_mode, + is_block_prediction_enabled, bpc, bpp, + h_slice_number, v_slice_number, + compression_info, dsc_v_major, + dsc_v_minor, data_info) + + except BaseException as e: + logging.info(e) + return OPFDialogAnswer.ABORT + + OPFFunctions.g_vm = copy.deepcopy(encoded_video_frame) + + hdtx.pg.set_pattern(pattern=encoded_video_frame) + res = hdtx.pg.apply() + + if not res and hdtx.pg.status().error != PGStatus.PGError.OK: + return OPFDialogAnswer.ABORT + + return OPFDialogAnswer.PROCEED + + @staticmethod + def opf_161_handler(dptx, dprx, message: str) -> OPFDialogAnswer: + return OPFDialogAnswer.PROCEED + + @staticmethod + def as_parse_edid(dptx): + edid_data = dptx.edid.read_i2c() + display_id_data = dptx.edid._parser.find_main_block(MainBlockType.DisplayID, edid_data) + if len(display_id_data) == 0: + return OPFDialogAnswer.ABORT + + as_data = dptx.edid._parser.find_additional_block(AdditionalBlockType.AdaptiveSync, display_id_data) + if len(as_data) == 0: + return OPFDialogAnswer.ABORT + + increase_lines = as_data[4] + decrease_lines = as_data[8] + min_ref_rate = round(as_data[5] / 1.001, 3) + frame_rate = dptx.link.status.stream(0).video_mode.timing.frame_rate / 1000 + v_total = dptx.link.status.stream(0).video_mode.timing.vtotal + max_lanes = round(v_total * (frame_rate - min_ref_rate) / min_ref_rate) + + max_ref_rate = round((int.from_bytes(as_data[6: 8], "little") + 1) * 1.00035) + min_lanes = round(v_total * (frame_rate - max_ref_rate) / max_ref_rate) + + return increase_lines, decrease_lines, max_lanes, min_lanes + + @staticmethod + def check_dsc_content_library(*args): + width, height, color_mode, is_block_prediction_enabled, bpc, bpp, h_slice_number, v_slice_number, \ + compression_info, dsc_v_major, dsc_v_minor, data_info = args + if OPFFunctions.dsc_content_library_path != "": + if not os.path.exists(OPFFunctions.dsc_content_library_path): + os.makedirs(OPFFunctions.dsc_content_library_path) + dsc_image_name = f"{width}x{height}_{OPFFunctions.dict_dsc_color_names.get(compression_info.color_format)}_" \ + f"{'BPY' if is_block_prediction_enabled else 'NBP'}_bpc{bpc}_" \ + f"bpp{bpp}_{h_slice_number}slicew_{v_slice_number}sliceh_" \ + f"{compression_info.buffer_bit_depth}lb_v{dsc_v_major}{dsc_v_minor}.dsc" + full_path = os.path.join(OPFFunctions.dsc_content_library_path, dsc_image_name) + if os.path.exists(full_path): + # + # Temporary message "Update DSC folder" + # + warnings.warn("Since version 3.5.X/3.6.X the usual file for generating DSC images has been updated." + "Please update/regenerate your DSC folder/library with using new file.") + + with open(full_path, 'rb') as file: + data = bytearray(file.read()) + encoded_video_frame = dsc_video_frame_from_data(data) + else: + vf = generate_pattern_as_vf(PatternType.Unigraf, width, height, color_mode, data_info) + encoded_video_frame = encode_video_frame(vf, compression_info) + with open(full_path, 'wb') as file: + file.write(encoded_video_frame.data) + else: + vf = generate_pattern_as_vf(PatternType.Unigraf, width, height, color_mode, data_info) + encoded_video_frame = encode_video_frame(vf, compression_info) + + return encoded_video_frame + + @staticmethod + def get_compression_info(*args): + v_slice_size, h_slice_size, bpp, color_mode, buffer_bit_depth, is_block_prediction_enabled, dsc_v_major, \ + dsc_v_minor, is_simple_as_444 = args + + compression_info = CompressionInfo() + compression_info.v_slice_size = v_slice_size + compression_info.h_slice_size = h_slice_size + compression_info.bpp = bpp + compression_info.color_format = OPFFunctions.dict_dsc_color_formats.get(color_mode) + compression_info.buffer_bit_depth = buffer_bit_depth + compression_info.is_block_prediction_enabled = is_block_prediction_enabled + compression_info.version = (dsc_v_major, dsc_v_minor) + compression_info.is_simple_as_444 = is_simple_as_444 + + return compression_info diff --git a/UniTAP/dev/modules/opf/parsers/__init__.py b/UniTAP/dev/modules/opf/parsers/__init__.py new file mode 100644 index 0000000..10069b9 --- /dev/null +++ b/UniTAP/dev/modules/opf/parsers/__init__.py @@ -0,0 +1 @@ +from .main_handle import OPFParametersParser diff --git a/UniTAP/dev/modules/opf/parsers/main_handle.py b/UniTAP/dev/modules/opf/parsers/main_handle.py new file mode 100644 index 0000000..5d4f6f4 --- /dev/null +++ b/UniTAP/dev/modules/opf/parsers/main_handle.py @@ -0,0 +1,44 @@ +from .opf_utils import * + + +class OPFParametersParser: + @staticmethod + def parse(opf_id: int, *args) -> list: + if opf_id in [1]: + return parse_link_training(args[3]) + if opf_id in [2]: + return parse_video_mode_2(args[3]) + if opf_id in [3, 4, 5, 7, 8, 11, 12, 13, 14, 15, 16, 17, 101, 102, 104, 105, 106, 121, 122, 123, 145, 150, 152, 161]: + return parse_message(args[1]) + if opf_id in [6]: + return parse_video_mode_6(args[1], args[3]) + if opf_id in [9]: + return parse_video_mode_9(args[3], args[1]) + if opf_id in [10]: + return parse_opf_10(args[3]) + if opf_id in [18]: + return parse_video_mode_18(args[3]) + if opf_id in [19]: + return parse_video_mode_19(args[3]) + if opf_id in [20]: + return parse_video_mode_20(args[3], args[1]) + if opf_id in [21]: + return parse_opf_21(args[3]) + if opf_id in [103]: + return parse_video_mode_103(args[3]) + if opf_id in [120]: + return parse_video_mode_120(args[3]) + if opf_id in [140]: + return parse_video_mode_140(args[3]) + if opf_id in [141]: + return parse_video_mode_141(args[3]) + if opf_id in [142]: + return [args[1]] + if opf_id in [143]: + return parse_video_mode_143(args[3]) + if opf_id in [144]: + return parse_video_mode_144(args[3]) + if opf_id in [151]: + return parse_video_mode_103(args[3]) + else: + raise ValueError(f"Received OPF request with unknown code: {opf_id}") diff --git a/UniTAP/dev/modules/opf/parsers/opf_utils.py b/UniTAP/dev/modules/opf/parsers/opf_utils.py new file mode 100644 index 0000000..7989b17 --- /dev/null +++ b/UniTAP/dev/modules/opf/parsers/opf_utils.py @@ -0,0 +1,327 @@ +import re +from typing import List, Optional + +from UniTAP.libs.lib_tsi.tsi_types import TSI_OPF_PARAMETER + + +def parse_link_training(parameters_list: List[TSI_OPF_PARAMETER]): + dp_lanes = 0 + dp_link_rate = 0 + encoding = None + + for param in parameters_list: + if param.name == 'Lane Count': + dp_lanes, = re.findall(r'\d+', param.description) + if param.name == 'Link Rate': + dp_link_rate = re.findall(r'\d+', param.description) + if len(dp_link_rate) == 0: + dp_link_rate = 0 + elif len(dp_link_rate) == 2: + dp_link_rate = f"{dp_link_rate[0]}.{dp_link_rate[1]}" + if len(parameters_list) >= 3: + if param.name == 'Encoding': + encoding = re.findall(r'\d+\w+/\d+\w+', param.description) + if len(encoding) == 0: + encoding = "8b/10b" + else: + encoding = encoding[0] + + return int(dp_lanes), float(dp_link_rate), encoding + + +def parse_video_mode_2(parameters_list: List[TSI_OPF_PARAMETER]): + res_x = 0 + res_y = 0 + res_frate = 0 + res_bpc = 0 + tim_std = None + tim_rb = None + + for param in parameters_list: + if param.name == 'Video Mode': + result = re.findall(r'\d+|RB1|RB2|VIC \d+|DMT \w+|CVT \w+', param.description) + if len(result) == 5: + res_x, res_y, res_frate, res_bpc, tim_std = result + else: + res_x, res_y, res_frate, res_bpc, tim_rb, tim_std = result + + return int(res_x), int(res_y), int(float(res_frate) * 1000), int(res_bpc), tim_std, tim_rb + + +def parse_video_mode_6(pattern_request: str, parameters_list: List[TSI_OPF_PARAMETER]): + res_x = 0 + res_y = 0 + res_frate = 0 + res_bpc = 0 + col_format = 0 + col_range = 0 + col_yc = None + timing_standard = None + + pattern_name = re.findall(r'Color Ramp|Color Square', pattern_request)[0] + + for param in parameters_list: + if param.name == 'Video Mode': + result = re.findall(r'\d+|RB1|RB2', param.description) + if len(result) == 3: + res_x, res_y, res_frate = result + else: + res_x, res_y, res_frate, timing_standard = result + elif param.name == 'Color Format': + result = re.findall(r'RGB|YCbCr 4:4:4|YCbCr 4:2:2|YCbCr 4:2:0|Simple 4:2:2|ITU-601|ITU-709|\d+|VESA|CTA', + param.description) + if len(result) == 3: + col_format, res_bpc, col_range = result + elif len(result) == 4: + col_format, col_yc, res_bpc, col_range = result + + return int(res_x), int(res_y), int(float(res_frate) * 1000), int( + res_bpc), timing_standard, col_format, col_range, col_yc, pattern_name + + +def parse_video_mode_9(parameters_list: List[TSI_OPF_PARAMETER], pattern_request: Optional[str] = None): + res_x = 0 + res_y = 0 + res_frate = 0 + res_bpc = 0 + tim_std = None + pattern_name = None + + if pattern_request is not None: + pattern_name = re.findall(r'Color Ramp|Color Square', pattern_request)[0] + + for param in parameters_list: + if param.name == 'Video Mode': + result = re.findall(r'\d+|RB1|RB2', param.description) + if len(result) == 4: + res_x, res_y, res_frate, res_bpc = result + elif len(result) == 5: + res_x, res_y, res_frate, tim_std, res_bpc = result + + return int(res_x), int(res_y), int(float(res_frate) * 1000), int(res_bpc), tim_std, pattern_name + + +def parse_opf_10(parameters_list: List[TSI_OPF_PARAMETER]): + res_x, res_y, res_frate, res_bpc, timing_standard = [0] * 5 + + audio_pattern = '' + ch_count = 0 + sample_freq = 0 + sample_size = 0 + ch_alloc = 0 + ch_info = [] + + for ind, param in enumerate(parameters_list): + if param.name == 'Video Mode' and param.description != "Video is not required": + res_x, res_y, res_frate, res_bpc, timing_standard, _ = parse_video_mode_9(parameters_list=parameters_list) + elif param.name == 'Audio Pattern': + audio_pattern = param.description + elif param.name == 'Channel Count': + ch_count, = re.findall(r'\d+', param.description) + elif param.name == 'Sample Frequency': + sample_freq, = re.findall(r'\d+', param.description) + elif param.name == 'Sample Size': + sample_size, = re.findall(r'\d+', param.description) + elif param.name == 'Channel Allocation' and ind == 5: + ch_alloc = re.findall(r'FL/FR|LFE1|FC|BL/BR|BC|FLC/FRC|RLC/RRC|FLW/FRW|TpFL/TpFR|TpC|TpFC|LS/RS|LFE2|TpBC' + r'|SiL/SiR|TpSiL/TpSiR|TpBL/TpBR|BtFC|BtFL/BtFR|TpLS/TpRS', param.description) + elif param.name == 'Channel Allocation' and ind > 5: + ch_info.append(re.findall(r'\d+', param.description)[0]) + + return int(res_x), int(res_y), int(res_frate), int(res_bpc), timing_standard, audio_pattern, \ + int(ch_count), int(sample_freq), int(sample_size), ch_alloc, ch_info if len(ch_info) > 0 else None + + +def parse_message(message: str): + return [str(message)] + + +def parse_video_mode_19(parameters_list: List[TSI_OPF_PARAMETER]): + use_3tap = False + for param in parameters_list: + if param.name == 'Conversion type filter': + use_3tap = param.description.find('3-Tap Filter') != -1 + return [use_3tap] + + +def parse_video_mode_18(parameters_list: List[TSI_OPF_PARAMETER]): + res_x = 0 + res_y = 0 + res_frate = 0 + res_bpc = 0 + col_format = 0 + col_range = 0 + col_yc = 0 + tim_std = 0 + + for param in parameters_list: + if param.name == 'Video Mode': + res_x, res_y, res_frate, tim_std = re.findall(r'\d+|CTA|RB1|RB2', param.description) + elif param.name == 'Color Format': + color_format_str = param.description.replace("ITU-709", '') + col_format, res_bpc, col_range = re.findall( + r'RGB|YCbCr 4:2:2|YCbCr 4:4:4|YCbCr 4:2:0|Simple 4:2:2|\d+|VESA|CTA', color_format_str) + col_yc = 'ITU-709' if col_format != 'RGB' else None + + return int(res_x), int(res_y), int(float(res_frate) * 1000), int(res_bpc), col_format, col_range, col_yc, tim_std + + +def parse_video_mode_20(parameters_list: List[TSI_OPF_PARAMETER], pattern_request: Optional[str] = None): + pattern_name = None + res_x = 0 + res_y = 0 + res_frate = 0 + res_bpc = 0 + col_format = 0 + col_range = 0 + col_yc = None + tim_std = "" + enable_dsc = False + + if pattern_request is not None: + if pattern_request.find('DSC compressed') != -1: + enable_dsc = True + else: + pattern_name = re.findall(r'No Video|Color Ramp|Color Square|Black and Vertical lines|Any', pattern_request)[0] + enable_dsc = False + + for param in parameters_list: + if param.name == 'Video Mode': + line = param.description.split("Hz") + res_x, res_y, res_frate = re.findall(r'\d+', line[0]) + tim_std = re.findall(r'VIC \d+|DMT \w+|UFG \d+|CVT RB\d+|CVT|OVT', line[1]) + elif param.name == 'Color Format': + color_format_str = param.description + result = re.findall(r'RGB|YCbCr 4:2:2|YCbCr 4:4:4|YCbCr 4:2:0|Simple 4:2:2|ITU-601|ITU-709|\d+|VESA|CTA', + color_format_str) + if len(result) == 3: + col_format, res_bpc, col_range = result + elif len(result) == 4: + col_format, col_yc, res_bpc, col_range = result + + return int(res_x), int(res_y), int(float(res_frate) * 1000), int(res_bpc), col_format, col_range, col_yc, tim_std,\ + pattern_name, enable_dsc + + +def parse_opf_21(parameters_list: List[TSI_OPF_PARAMETER]): + audio_pattern = '' + ch_count = 0 + sample_freq = 0 + sample_size = 0 + ch_alloc = 0 + ch_info = [] + + for ind, param in enumerate(parameters_list): + if param.name == 'Audio Pattern': + audio_pattern = param.description + elif param.name == 'Channel Count': + ch_count, = re.findall(r'\d+', param.description) + elif param.name == 'Sample Frequency': + sample_freq, = re.findall(r'\d+', param.description) + elif param.name == 'Sample Size': + sample_size, = re.findall(r'\d+', param.description) + elif param.name == 'Channel Allocation' and ind == 5: + ch_alloc = re.findall(r'FL/FR|LFE1|FC|BL/BR|BC|FLC/FRC|RLC/RRC|FLW/FRW|TpFL/TpFR|TpC|TpFC|LS/RS|LFE2|TpBC' + r'|SiL/SiR|TpSiL/TpSiR|TpBL/TpBR|BtFC|BtFL/BtFR|TpLS/TpRS', param.description) + elif param.name == 'Channel Allocation' and ind > 5: + ch_info.append(re.findall(r'\d+', param.description)[0]) + + return (audio_pattern, int(ch_count), int(sample_freq), int(sample_size), ch_alloc, + ch_info if len(ch_info) > 0 else None) + + +def parse_video_mode_103(parameters_list: List[TSI_OPF_PARAMETER]): + width = 0 + height = 0 + res_frate = 60000 + color_format = "" + block_prediction = False + bpc = 8 + bpp = 8 + h_slice_number = 0 + buffer_bit_depth = 8 + v_slice_number = 0 + dsc_v_minor = 1 + dsc_v_major = 1 + + for param in parameters_list: + if param.name == "Video mode": + width, height, res_frate = re.findall(r'\d+', param.description) + elif param.name == "Color format": + color_format = re.findall(r'RGB|YCbCr 4:2:2|YCbCr 4:4:4|YCbCr 4:2:0|Simple 4:2:2', param.description)[0] + elif param.name == 'Block prediction': + block_prediction = False if param.description.find("disabled") != -1 else True + elif param.name == 'Bits per component': + bpc = re.findall(r'\d+', param.description)[0] + elif param.name == 'Bits per pixel (1/16 units)': + bpp = re.findall(r'\d+', param.description)[2] + elif param.name == 'Horizontal slice number': + h_slice_number = re.findall(r'\d+', param.description)[0] + elif param.name == 'Buffer bit depth': + buffer_bit_depth = re.findall(r'\d+', param.description)[0] + elif param.name == 'Vertical slice number': + v_slice_number = re.findall(r'\d+', param.description)[0] + elif param.name == 'DSC Algorithm revision': + dsc_v_minor, dsc_v_major = re.findall(r'\d+', param.description) + + return int(width), int(height), int(float(res_frate) * 1000), color_format, block_prediction, int(bpc), int(bpp),\ + int(h_slice_number), int(buffer_bit_depth), int(v_slice_number), int(dsc_v_major), int(dsc_v_minor) + + +def parse_video_mode_120(parameters_list: List[TSI_OPF_PARAMETER]): + res_x = 0 + res_y = 0 + res_frate = 0 + res_bpc = 0 + color_format = "" + + for param in parameters_list: + if param.name == "Video pattern": + res_x, res_y, res_frate, res_bpc, color_format = re.findall(r'\d+|RGB|YCbCr 4:2:2|YCbCr 4:4:4|YCbCr 4:2:0|Simple 4:2:2|YCbCr422|YCbCr444|YCbCr420|Simple422', param.description) + + return int(res_x), int(res_y), int(float(res_frate) * 1000), int(res_bpc), color_format + + +def parse_video_mode_140(parameters_list: List[TSI_OPF_PARAMETER]): + res_x = 0 + res_y = 0 + res_frate = 0 + tim_std = 0 + tim_std_num = None + + for param in parameters_list: + if param.name == 'Timing': + result = re.findall(r'\d[a-fA-F]h|\d+|VIC|DMT|RB1|RB2|RB3', param.description) + if len(result) == 5: + res_x, res_y, res_frate, tim_std, tim_std_num = result + elif len(result) == 4: + res_x, res_y, res_frate, tim_std = result + + return int(res_x), int(res_y), int(float(res_frate) * 1000), tim_std, tim_std_num + + +def parse_video_mode_141(parameters_list: List[TSI_OPF_PARAMETER]): + audio_format = '' + channels = 0 + size = 0 + rate = 0 + + for param in parameters_list: + if param.name == 'Audio': + audio_format, channels, size, rate = re.findall(r'LPCM|\d+.\d+|\d+', param.description) + + return audio_format, int(channels), int(size), int(float(rate) * 1000) + + +def parse_video_mode_143(parameters_list: List[TSI_OPF_PARAMETER]): + res_frate = 0 + + for param in parameters_list: + if param.name == 'Refresh Rate': + res_frate = re.findall(r'\d+', param.description)[0] + + return [int(float(res_frate))] + + +def parse_video_mode_144(parameters_list: List[TSI_OPF_PARAMETER]): + return [parameters_list[0].description] diff --git a/UniTAP/dev/modules/terminal/__init__.py b/UniTAP/dev/modules/terminal/__init__.py new file mode 100644 index 0000000..d2a3fab --- /dev/null +++ b/UniTAP/dev/modules/terminal/__init__.py @@ -0,0 +1 @@ +from .dev_terminal import Terminal diff --git a/UniTAP/dev/modules/terminal/dev_terminal.py b/UniTAP/dev/modules/terminal/dev_terminal.py new file mode 100644 index 0000000..ae414ea --- /dev/null +++ b/UniTAP/dev/modules/terminal/dev_terminal.py @@ -0,0 +1,58 @@ +import time +from UniTAP.libs.lib_tsi.tsi_io import DeviceIO +from UniTAP.libs.lib_tsi.tsi import TSI_TERMINAL_RW +from ctypes import c_char + + +class Terminal: + + def __init__(self, device_io: DeviceIO): + self.__io = device_io + + def write_vr(self, register: int, value: int): + self.__execute(f"vw {hex(register)} {value}") + + def read_vr(self, register: int) -> str: + self.__execute(f"vr {hex(register)}") + return self.__get_logs() + + def write_fifo_vr(self, register: int, value: int): + self.__execute(f"vfw {hex(register)} {value}") + + def read_fifo_vr(self, register: int) -> str: + self.__execute(f"vfr {hex(register)}") + return self.__get_logs() + + def run_command(self, command: str) -> str: + self.__execute(command) + return self.__get_logs() + + def __execute(self, command): + command += '\r' + str_len = (len(command) / 4 + (1 if len(command) % 4 else 0)) * 4 + self.__io.set(config_id=TSI_TERMINAL_RW, + data=command, + data_type=c_char, + data_count=len(command), + data_size=int(str_len)) + + def __get_logs(self, timeout: int = 5) -> str: + try: + start = time.time() + result = [] + while time.time() - start < timeout: + result = self.__io.get(TSI_TERMINAL_RW, c_char, 1024)[1] + if result[0] != b'\x00': + break + + output = "" + for i in result: + if i != b'\x00': + output += i.decode() + + if output == "": + print("No output message") + return output + except BaseException: + print("No output message") + return "" diff --git a/UniTAP/dev/ports/__init__.py b/UniTAP/dev/ports/__init__.py new file mode 100644 index 0000000..9b16249 --- /dev/null +++ b/UniTAP/dev/ports/__init__.py @@ -0,0 +1,7 @@ +from .dptx5xx import DPTX, DPTX4xx, DPTX5xx +from .dprx5xx import DPRX, DPRX4xx, DPRX5xx +from .hdrx4xx import HDRX, HDRX4xx +from .hdtx4xx import HDTX, HDTX4xx +from .pdc_port import PDC340, PDC424, PDC500 +from .modules import * +import UniTAP.dev.ports.modules.capturer.bulk as bulk diff --git a/UniTAP/dev/ports/dprx.py b/UniTAP/dev/ports/dprx.py new file mode 100644 index 0000000..f2fb187 --- /dev/null +++ b/UniTAP/dev/ports/dprx.py @@ -0,0 +1,117 @@ +from .rx import * +from .modules.dpcd.dpcd import DPCDRegisters +from .modules.link.dp.link_rx import LinkDisplayPortRx +from .modules.edid.edid import EdidSink +from .modules.hdcp import HdcpSink +from .modules.capturer.event.event_capturer import EventCapturer, EventFilterDpRx, EventFilterUsbc +from .modules.link.dp.private_link_rx_types import DPRXHWCaps +from UniTAP.libs.lib_tsi.tsi_types import TSI_DPRX_HDCP_CAPS_R, TSI_DPRX_HDCP_STATUS_R +from UniTAP.libs.lib_tsi.tsi_types import TSI_DPRX_DPCD_BASE_W, TSI_DPRX_DPCD_DATA +from UniTAP.libs.lib_tsi.tsi_types import TSI_PDC_LOG_CONTROL, TSI_DPRX_HW_CAPS_R +from UniTAP.libs.lib_tsi.tsi_io import PortProtocol + + +class DPRX(RX): + + """ + + Main class of `DPRX` object. + Inherited from class `RX`. + Class describes capabilities of 300th (3XX) series of DP and USB-C devices in Sink (RX - receiver) role. + + Attributes: + __link (LinkDisplayPortRx): object of `LinkDisplayPortRx`. + __dpcd (DPCDRegisters): object of `DPCDRegisters`. + __edid (EdidSink): object of `EdidSink`. + __hdcp (HdcpSink): object of `HdcpSink`. + __event_capturer (EventCapturer): object of `EventCapturer`. + __video_capturer (VideoCapturer): object of `VideoCapturerDP`. + """ + + __CHECK_EVENT_FILTER = {PortProtocol.DisplayPort: [EventFilterDpRx], + PortProtocol.DisplayPortThrowUSBC: [EventFilterDpRx, EventFilterUsbc]} + + def __init__(self, port_io: PortIO, memory_manager: MemoryManager, capturer: Capturer): + super().__init__(port_io, memory_manager, capturer) + + hw_caps = port_io.get(TSI_DPRX_HW_CAPS_R, DPRXHWCaps)[1] + self.__dpcd = DPCDRegisters(port_io, TSI_DPRX_DPCD_BASE_W, TSI_DPRX_DPCD_DATA) + self.__link = LinkDisplayPortRx(port_io, hw_caps, self.__dpcd) + self.__edid = EdidSink(port_io, self.__link.status.mst_stream_count) + self.__hdcp = HdcpSink(port_io, TSI_DPRX_HDCP_CAPS_R, TSI_DPRX_HDCP_STATUS_R) + self.__video_capturer = VideoCapturerDP(capturer, hw_caps.mst_stream_count) + + event_filters = [] + for item in self.__CHECK_EVENT_FILTER.get(port_io.protocol()): + if item == EventFilterDpRx: + event_filters.append(item(hw_caps)) + else: + event_filters.append(item(None)) + + self.__event_capturer = EventCapturer(capturer, port_io.index(), event_filters) + + @property + def link(self) -> LinkDisplayPortRx: + """ + + Should be used to control link capabilities on Sink (RX - receiver) role. + + Returns: + object of `LinkDisplayPortRx` type. + """ + return self.__link + + @property + def dpcd(self) -> DPCDRegisters: + """ + + Should be used to work with DPCD registers on Sink (RX - receiver) role. + + Returns: + object of `DPCDRegisters` type. + """ + return self.__dpcd + + @property + def edid(self) -> EdidSink: + """ + + Should be used to work with EDID on Sink (RX - receiver) role. + + Returns: + object of `EdidSink` type. + """ + return self.__edid + + @property + def hdcp(self) -> HdcpSink: + """ + + Should be used to work with HDCP on Sink (RX - receiver) role. + + Returns: + object of `HdcpSink`. + """ + return self.__hdcp + + @property + def event_capturer(self) -> EventCapturer: + """ + + Should be used to control `EventCapturer` on Sink (RX - receiver) role. + + Returns: + object of `EventCapturer` type. + """ + return self.__event_capturer + + @property + def video_capturer(self) -> VideoCapturerDP: + """ + + Should be used to control `VideoCapturerDP` on Sink (RX - receiver) role. + + Returns: + object of `VideoCapturerDP` type. + """ + return self.__video_capturer diff --git a/UniTAP/dev/ports/dprx4xx.py b/UniTAP/dev/ports/dprx4xx.py new file mode 100644 index 0000000..251850b --- /dev/null +++ b/UniTAP/dev/ports/dprx4xx.py @@ -0,0 +1,89 @@ +from .dprx import * +from .modules.fec import FecRx +from .modules.capturer.bulk.bulk_capturer import BulkCapturer +from .modules.edid.edid import DisplayIdSink +from .modules.panel_replay import SinkPanelReplay, SinkPanelSelfRefresh +from typing import Optional + + +class DPRX4xx(DPRX): + + """ + + Main class of `DPRX4xx` object. + Inherited from class `DPRX`. + Class describes capabilities of 400th (4XX) series of DP and USB-C devices in Sink (RX - receiver) role. + + Attributes: + __fec (FecTx): object of `FecTx`. + __bulk_capturer (BulkCapturer): object of `BulkCapturer` + __display_id (DisplayIdSink): object of `DisplayIdSink` + __panel_replay (SinkPanelReplay): object of `SinkPanelReplay` + __psr (SinkPanelSelfRefresh): object of `SinkPanelSelfRefresh` + + """ + + def __init__(self, port_io: PortIO, memory_manager: MemoryManager, capturer: Capturer): + super().__init__(port_io, memory_manager, capturer) + + hw_caps = port_io.get(TSI_DPRX_HW_CAPS_R, DPRXHWCaps)[1] + self.__fec = FecRx(port_io, self.dpcd) + self.__bulk_capturer = BulkCapturer(capturer, memory_manager) + self.__display_id = DisplayIdSink(port_io, hw_caps.mst_stream_count) + self.__panel_replay = SinkPanelReplay(port_io) if hw_caps.pr != 0 else None + self.__psr = SinkPanelSelfRefresh(port_io) if hw_caps.psr != 0 else None + + @property + def fec(self) -> FecRx: + """ + + Should be used to control FEC functionality on Sink (RX - receiver) side. + + Returns: + object of `FecRx` type. + """ + return self.__fec + + @property + def bulk_capturer(self) -> BulkCapturer: + """ + + Should be used to control Bulk capturer functionality on Sink (RX - receiver) side. + + Returns: + object of `BulkCapturer` type. + """ + return self.__bulk_capturer + + @property + def display_id(self) -> DisplayIdSink: + """ + + Should be used to control DisplayID functionality on Sink (RX - receiver) side. + + Returns: + object of `DisplayIdSink` type. + """ + return self.__display_id + + @property + def panel_replay(self) -> Optional[SinkPanelReplay]: + """ + + Should be used to control Panel Replay on Sink (RX - receiver) side. + + Returns: + object of `SinkPanelReplay` type. + """ + return self.__panel_replay + + @property + def panel_self_refresh(self) -> Optional[SinkPanelSelfRefresh]: + """ + + Should be used to control Panel Self Refresh on Sink (RX - receiver) side. + + Returns: + object of `SinkPanelSelfRefresh` type. + """ + return self.__psr diff --git a/UniTAP/dev/ports/dprx5xx.py b/UniTAP/dev/ports/dprx5xx.py new file mode 100644 index 0000000..82ef1c5 --- /dev/null +++ b/UniTAP/dev/ports/dprx5xx.py @@ -0,0 +1,18 @@ +from .dprx4xx import * + + +class DPRX5xx(DPRX4xx): + + """ + + Main class of `DPRX5xx` object. + Inherited from class `DPRX4xx`. + Class describes capabilities of 500th (5XX) series of DP and USB-C devices in Sink (RX - receiver) role. + + Attributes: + __panel_replay (SinkPanelReplay): object of `SinkPanelReplay`. + __psr (SinkPanelSelfRefresh): object of `SinkPanelSelfRefresh`. + """ + + def __init__(self, port_io: PortIO, memory_manager: MemoryManager, capturer: Capturer): + super().__init__(port_io, memory_manager, capturer) diff --git a/UniTAP/dev/ports/dptx.py b/UniTAP/dev/ports/dptx.py new file mode 100644 index 0000000..7436f59 --- /dev/null +++ b/UniTAP/dev/ports/dptx.py @@ -0,0 +1,118 @@ +from .tx import * +from .modules import DPCDRegisters, LinkDisplayPortTx +from .modules.edid.edid import EdidSource +from .modules.vtg.pg import DpPatternGenerator +from .modules.hdcp import HdcpSource +from .modules.capturer.event.event_capturer import EventCapturer, EventFilterDpTx, EventFilterUsbc +from .modules.link.dp.private_link_tx_types import DPTXHWCaps +from UniTAP.libs.lib_tsi.tsi_types import TSI_DPTX_DPCD_BASE_W, TSI_DPTX_DPCD_DATA +from UniTAP.libs.lib_tsi.tsi_types import TSI_DPTX_HDCP_CAPS_R, TSI_DPTX_HDCP_STATUS_R +from UniTAP.libs.lib_tsi.tsi_types import TSI_DPTX_HW_CAPS_R +from UniTAP.libs.lib_tsi.tsi_io import PortProtocol + + +class DPTX(TX): + + """ + + Main class of `DPTX` object. + Inherited from class `TX`. + Class describes capabilities of 300th (3XX) series of DP and USB-C devices in Source (TX - transmitter) role. + + Attributes: + __link (LinkDisplayPortTx): object of `LinkDisplayPortTx`. + __dpcd (DPCDRegisters): object of `DPCDRegisters`. + __edid (EdidSource): object of `EdidSource`. + __hdcp (HdcpSource): object of `HdcpSource`. + __pg (DpPatternGenerator): object of `DpPatternGenerator`. + __event_capturer (EventCapturer): object of `EventCapturer`. + """ + + __CHECK_EVENT_FILTER = {PortProtocol.DisplayPort: [EventFilterDpTx], + PortProtocol.DisplayPortThrowUSBC: [EventFilterDpTx, EventFilterUsbc]} + + def __init__(self, port_io: PortIO, memory_manager: MemoryManager, capturer: Capturer): + super().__init__(port_io, memory_manager, capturer) + + hw_caps = port_io.get(TSI_DPTX_HW_CAPS_R, DPTXHWCaps)[1] + self.__dpcd = DPCDRegisters(port_io, TSI_DPTX_DPCD_BASE_W, TSI_DPTX_DPCD_DATA) + self.__link = LinkDisplayPortTx(port_io, self.__dpcd, hw_caps) + self.__pg = DpPatternGenerator(port_io, memory_manager, 0) + self.__edid = EdidSource(port_io, self.__link.max_stream_count) + self.__hdcp = HdcpSource(port_io, TSI_DPTX_HDCP_CAPS_R, TSI_DPTX_HDCP_STATUS_R) + # event_filters = [e(hw_caps) for e in self.__CHECK_EVENT_FILTER.get(port_io.protocol())] + + event_filters = [] + for item in self.__CHECK_EVENT_FILTER.get(port_io.protocol()): + if item == EventFilterDpTx: + event_filters.append(item(hw_caps)) + else: + event_filters.append(item(None)) + + self.__event_capturer = EventCapturer(capturer, port_io.index(), event_filters) + + @property + def dpcd(self) -> DPCDRegisters: + """ + + Should be used to work with DPCD registers on Source (TX - transmitter) side. + + Returns: + object of `DPCDRegisters` type. + """ + return self.__dpcd + + @property + def pg(self) -> DpPatternGenerator: + """ + + Should be used to control Pattern generator functionality on Source (TX - transmitter) side. + + Returns: + object of `DpPatternGenerator` type. + """ + return self.__pg + + @property + def link(self) -> LinkDisplayPortTx: + """ + + Should be used to control link settings on Source (TX - transmitter) side. + + Returns: + object of `LinkDisplayPortTx` type. + """ + return self.__link + + @property + def edid(self) -> EdidSource: + """ + + Should be used to work with EDID on Source (TX - transmitter) side. + + Returns: + object of `EdidSource` type. + """ + return self.__edid + + @property + def hdcp(self) -> HdcpSource: + """ + + Should be used to work with HDCP on Source (TX - transmitter) side. + + Returns: + object of `HdcpSource`. + """ + return self.__hdcp + + @property + def event_capturer(self) -> EventCapturer: + """ + + Should be used to control `EventCapturer` on Source (TX - transmitter) role. + + Returns: + object of `EventCapturer` type. + """ + return self.__event_capturer diff --git a/UniTAP/dev/ports/dptx4xx.py b/UniTAP/dev/ports/dptx4xx.py new file mode 100644 index 0000000..fba65ba --- /dev/null +++ b/UniTAP/dev/ports/dptx4xx.py @@ -0,0 +1,62 @@ +from .dptx import * +from .modules.fec import FecTx, FECCounters, FECErrorType8b10b +from .modules.vtg.pg import DpMstPatternGenerator +from .modules.edid.edid import DisplayIdSource + + +class DPTX4xx(DPTX): + + """ + + Main class of `DPTX4xx` object. + Inherited from class `DPTX`. + Class describes capabilities of 400th (4XX) series of DP and USB-C devices in Source (TX - transmitter) role. + + Attributes: + __fec (FecTx): object of `FecTx`. + __pg (DpMstPatternGenerator): object of `DpMstPatternGenerator` + + """ + + def __init__(self, port_io: PortIO, memory_manager: MemoryManager, capturer: Capturer): + super().__init__(port_io, memory_manager, capturer) + + hw_caps = port_io.get(TSI_DPTX_HW_CAPS_R, DPTXHWCaps)[1] + self.__fec = FecTx(port_io, self.dpcd) + self.__pg = DpMstPatternGenerator(port_io, memory_manager, hw_caps.mst_stream_count) + self.__display_id = DisplayIdSource(port_io, hw_caps.mst_stream_count) + + @property + def fec(self) -> FecTx: + """ + + Should be used to control FEC functionality on Source (TX - transmitter) side. + + Returns: + object of `FecTx` type. + """ + return self.__fec + + @property + def pg(self) -> DpMstPatternGenerator: + """ + + Should be used to control Pattern generator functionality on Source (TX - transmitter) side. + `DpMstPatternGenerator` contain list of `DpPatternGenerator` objects. For access to element in list, + use expression `pg.[index]`. + + Returns: + object of `DpMstPatternGenerator` type. + """ + return self.__pg + + @property + def display_id(self) -> DisplayIdSource: + """ + + Should be used to control DisplayID functionality on Source (TX - transmitter) side. + + Returns: + object of `DisplayIdSource` type. + """ + return self.__display_id \ No newline at end of file diff --git a/UniTAP/dev/ports/dptx5xx.py b/UniTAP/dev/ports/dptx5xx.py new file mode 100644 index 0000000..954bb8a --- /dev/null +++ b/UniTAP/dev/ports/dptx5xx.py @@ -0,0 +1,15 @@ +from .dptx4xx import * + + +class DPTX5xx(DPTX4xx): + + """ + + Main class of `DPTX5xx` object. + Inherited from class `DPTX4xx`. + Class describes capabilities of 500th (5XX) series of DP and USB-C devices in Source (TX - transmitter) role. + + """ + + def __init__(self, port_io: PortIO, memory_manager: MemoryManager, capturer: Capturer): + super().__init__(port_io, memory_manager, capturer) diff --git a/UniTAP/dev/ports/hdrx.py b/UniTAP/dev/ports/hdrx.py new file mode 100644 index 0000000..72add26 --- /dev/null +++ b/UniTAP/dev/ports/hdrx.py @@ -0,0 +1,102 @@ +from .rx import * +from .modules.edid.edid import EdidSink +from .modules.link.hdmi.link import HdmiLinkRx +from .modules.hdcp import HdcpSink +from .modules.cec.cec_rx import CecRx +from .modules.capturer.event.event_capturer import EventCapturer, EventFilterHdRx +from UniTAP.libs.lib_tsi.tsi_types import TSI_HDRX_HDCP_CAPS_R, TSI_HDRX_HDCP_STATUS_R +from UniTAP.libs.lib_tsi.tsi_types import TSI_HDRX_LOG_CONTROL + + +class HDRX(RX): + + """ + + Main class of `HDRX` object. + Inherited from class `RX`. + Class describes capabilities of 300th (3XX) series of HDMI devices in Sink (RX - receiver) role. + + Attributes: + __link (HdmiLinkRx): object of `HdmiLinkRx`. + __edid (EdidSink): object of `EdidSink`. + __hdcp (HdcpSink): object of `HdcpSink`. + __event_capturer (EventCapturer): object of `EventCapturer`. + __video_capturer (VideoCapturer): object of `VideoCapturerHDMI`. + __cec (CecRx): object of `CecRx`. + """ + + def __init__(self, port_io: PortIO, memory_manager: MemoryManager, capturer: Capturer): + super().__init__(port_io, memory_manager, capturer) + + self.__link = HdmiLinkRx(port_io) + self.__edid = EdidSink(port_io, 1) + self.__hdcp = HdcpSink(port_io, TSI_HDRX_HDCP_CAPS_R, TSI_HDRX_HDCP_STATUS_R) + self.__event_capturer = EventCapturer(capturer, port_io.index(), [EventFilterHdRx(0)]) + self.__video_capturer = VideoCapturerHDMI(capturer, 1) + self.__cec = CecRx(port_io) + + @property + def link(self) -> HdmiLinkRx: + """ + + Should be used to control link capabilities on Sink (RX - receiver) role. + + Returns: + object of `HdmiLinkRx` type. + """ + return self.__link + + @property + def edid(self) -> EdidSink: + """ + + Should be used to work with EDID on Sink (RX - receiver) role. + + Returns: + object of `EdidSink` type. + """ + return self.__edid + + @property + def hdcp(self) -> HdcpSink: + """ + + Should be used to work with HDCP on Sink (RX - receiver) role. + + Returns: + object of `HdcpSink`. + """ + return self.__hdcp + + @property + def event_capturer(self) -> EventCapturer: + """ + + Should be used to control `EventCapturer` on Sink (RX - receiver) role. + + Returns: + object of `EventCapturer` type. + """ + return self.__event_capturer + + @property + def video_capturer(self) -> VideoCapturerHDMI: + """ + + Should be used to control `VideoCapturerHDMI` on Sink (RX - receiver) role. + + Returns: + object of `VideoCapturerHDMI` type. + """ + return self.__video_capturer + + @property + def cec(self) -> CecRx: + """ + + Should be used to control `CecRx` on Sink (RX - receiver) role. + + Returns: + object of `CecRx` type. + """ + return self.__cec diff --git a/UniTAP/dev/ports/hdrx4xx.py b/UniTAP/dev/ports/hdrx4xx.py new file mode 100644 index 0000000..9f43ae8 --- /dev/null +++ b/UniTAP/dev/ports/hdrx4xx.py @@ -0,0 +1,15 @@ +from .hdrx import * + + +class HDRX4xx(HDRX): + + """ + + Main class of `HDRX4xx` object. + Inherited from class `HDRX`. + Class describes capabilities of 400th (4XX) series of HDMI devices in Sink (RX - receiver) rolee. + + """ + + def __init__(self, port_io: PortIO, memory_manager: MemoryManager, capturer: Capturer): + super().__init__(port_io, memory_manager, capturer) diff --git a/UniTAP/dev/ports/hdtx.py b/UniTAP/dev/ports/hdtx.py new file mode 100644 index 0000000..461b846 --- /dev/null +++ b/UniTAP/dev/ports/hdtx.py @@ -0,0 +1,103 @@ +from .tx import * +from .modules.edid.edid import EdidSource +from .modules.link.hdmi.link import HdmiLinkTx +from .modules.vtg.pg import HdmiPatternGenerator +from .modules.hdcp import HdcpSource +from .modules.capturer.event.event_capturer import EventCapturer, EventFilterHdTx +from .modules.cec.cec_tx import CecTx +from UniTAP.libs.lib_tsi.tsi_types import TSI_HDTX_HDCP_CAPS_R, TSI_HDTX_HDCP_STATUS_R +from UniTAP.libs.lib_tsi.tsi_types import TSI_HDTX_LOG_CONTROL + + +class HDTX(TX): + + """ + + Main class of `HDTX` object. + Inherited from class `TX`. + Class describes capabilities of 300th (3XX) series of HDMI devices in Source (TX - transmitter) role. + + Attributes: + __link (HdmiLinkTx): object of `HdmiLinkTx`. + __edid (EdidSource): object of `EdidSource`. + __hdcp (HdcpSource): object of `HdcpSource`. + __pg (HdmiPatternGenerator): object of `HdmiPatternGenerator`. + __event_capturer (EventCapturer): object of `EventCapturer`. + __cec (CecTx): object of `CecTx`. + """ + + def __init__(self, port_io: PortIO, memory_manager: MemoryManager, capturer: Capturer): + super().__init__(port_io, memory_manager, capturer) + + self.__link = HdmiLinkTx(port_io) + self.__edid = EdidSource(port_io, 1) + self.__pg = HdmiPatternGenerator(port_io, memory_manager) + self.__hdcp = HdcpSource(port_io, TSI_HDTX_HDCP_CAPS_R, TSI_HDTX_HDCP_STATUS_R) + self.__event_capturer = EventCapturer(capturer, port_io.index(), [EventFilterHdTx(0)]) + self.__cec = CecTx(port_io) + + @property + def link(self) -> HdmiLinkTx: + """ + + Should be used to control link settings on Source (TX - transmitter) side. + + Returns: + object of `HdmiLinkTx` type. + """ + return self.__link + + @property + def pg(self) -> HdmiPatternGenerator: + """ + + Should be used to control Pattern generator functionality on Source (TX - transmitter) side. + + Returns: + object of `HdmiPatternGenerator` type. + """ + return self.__pg + + @property + def edid(self): + """ + + Should be used to work with EDID on Source (TX - transmitter) side. + + Returns: + object of `EdidSource` type. + """ + return self.__edid + + @property + def hdcp(self) -> HdcpSource: + """ + + Should be used to work with HDCP on Source (TX - transmitter) side. + + Returns: + object of `HdcpSource`. + """ + return self.__hdcp + + @property + def event_capturer(self) -> EventCapturer: + """ + + Should be used to control `EventCapturer` on Source (TX - transmitter) role. + + Returns: + object of `EventCapturer` type. + """ + return self.__event_capturer + + @property + def cec(self) -> CecTx: + """ + + Should be used to control `CecTx` on Source (TX - transmitter) role. + + Returns: + object of `CecTx` type. + """ + return self.__cec diff --git a/UniTAP/dev/ports/hdtx4xx.py b/UniTAP/dev/ports/hdtx4xx.py new file mode 100644 index 0000000..6096adc --- /dev/null +++ b/UniTAP/dev/ports/hdtx4xx.py @@ -0,0 +1,16 @@ +from .hdtx import * + + +class HDTX4xx(HDTX): + + """ + + Main class of `HDTX4xx` object. + Inherited from class `HDTX`. + Class describes capabilities of 400th (4XX) series of HDMI devices in Source (TX - transmitter) role. + + """ + + def __init__(self, port_io: PortIO, memory_manager: MemoryManager, capturer: Capturer): + super().__init__(port_io, memory_manager, capturer) + diff --git a/UniTAP/dev/ports/modules/__init__.py b/UniTAP/dev/ports/modules/__init__.py new file mode 100644 index 0000000..f218638 --- /dev/null +++ b/UniTAP/dev/ports/modules/__init__.py @@ -0,0 +1,11 @@ +from .dpcd import * +from .link import * +from .fec import * +from .vtg import * +from .ag import * +from .hdcp import * +from .capturer import * +from .edid import * +from .panel_replay import * +import UniTAP.dev.ports.modules.cec as cec +import UniTAP.dev.ports.modules.pdc as pdc diff --git a/UniTAP/dev/ports/modules/ag/__init__.py b/UniTAP/dev/ports/modules/ag/__init__.py new file mode 100644 index 0000000..23e43d5 --- /dev/null +++ b/UniTAP/dev/ports/modules/ag/__init__.py @@ -0,0 +1 @@ +from .ag import AudioPattern diff --git a/UniTAP/dev/ports/modules/ag/ag.py b/UniTAP/dev/ports/modules/ag/ag.py new file mode 100644 index 0000000..e3ed288 --- /dev/null +++ b/UniTAP/dev/ports/modules/ag/ag.py @@ -0,0 +1,156 @@ +from UniTAP.libs.lib_tsi.tsi_types import * +from UniTAP.utils import function_scheduler +from .ag_utils import * +from UniTAP.libs.lib_tsi.tsi_io import PortIO, PortProtocol +from UniTAP.dev.modules import MemoryManager +from .types import * +from .private_types import * +from typing import Union + + +class AudioGenerator: + """ + Class `AudioGenerator` allows working with generating audio from Source (TX - transmitter). You can configure + audio generator `setup`, apply settings and start generate audio `apply`, stop generate audio `stop_generate`, + read audio generator `status` and get current `audio_mode`. + """ + def __init__(self, port_io: PortIO, memory_manager: MemoryManager): + self.__io = port_io + self.__memory_manager = memory_manager + self.__status = AGStatus.Unknown + self.__audio_mode = AudioMode() + + def setup(self, audio_mode: AudioMode = AudioMode(), + audio_pattern: Union[AudioPattern, str] = AudioPattern.SignalSine, + signal_frequency: int = 1000, amplitude: int = 60): + """ + Configure audio generator. Possible two variants of configuration: + - From 'wav' or 'bin' file. + - From `AudioPattern` parameters. + + Args: + audio_mode (AudioMode) - object of `AudioMode` + audio_pattern (Union[AudioPattern, str]) - object of `AudioPattern` or path to audio file ('bin' or 'wave') + signal_frequency (int) + amplitude (int) + """ + self.stop_generate() + if isinstance(audio_pattern, str) and check_file_format(audio_pattern) != AudioFileFormat.UNKNOWN: + if check_file_format(audio_pattern) == AudioFileFormat.BIN: + data, _, size = load_from_bin_file(path=audio_pattern) + else: + data, audio_mode, size = load_from_wave_file(path=audio_pattern) + + self.__memory_manager.make_default() + self.__memory_manager.set_memory_block_index(MemoryManager.MemoryOwner.MO_AudioGenerator) + self.__io.set(TSI_AUDGEN_SIGNAL_BLOCK, MemoryManager.MemoryOwner.MO_AudioGenerator.value) + self.__memory_manager.memory_write(data, size) + self.__io.set(TSI_AUDGEN_AUDIO_SIZE, size) + + ag_struct = self.__io.get(TSI_AUDGEN_CONFIG, AudioConfigStructure)[1] + ag_struct.non_lpcm = 0 + ag_struct.loop = 1 + + audio_sts = create_audio_sts(audio_mode=audio_mode) + self.__io.set(TSI_AUDGEN_CHANNELS_STS, audio_sts, data_type=c_uint8, data_count=len(audio_sts)) + + if self.__io.protocol() == PortProtocol.HDMI: + tp = 0 + + if audio_mode.channel_count == 8 and audio_mode.sample_rate >= 64000: + tp = 3 + + ag_struct.n_selector = 1 + ag_struct.cts_selector = 1 + + self.__io.set(TSI_AUDGEN_CONFIG, ag_struct.value()) + + self.__io.set(TSI_AUDGEN_PACKET_TYPE, tp) + else: + self.__io.set(TSI_AUDGEN_CONFIG, ag_struct.value()) + + self.__io.set(TSI_AUDGEN_SIGNAL_TYPE, AudioPattern.CustomAudio.value) + self.__io.set(TSI_AUDGEN_CHANNEL_COUNT, audio_mode.channel_count) + self.__io.set(TSI_AUDGEN_SAMPLE_RATE, audio_mode.sample_rate) + self.__io.set(TSI_AUDGEN_SAMPLE_SIZE, audio_mode.bits) + elif isinstance(audio_pattern, AudioPattern): + list_sample_rate = [22050, 44100, 88200, 176400, 24000, 48000, 96000, 192000, 32000, 768000] + list_bits = [16, 20, 24] + audio_pattern = AudioPattern(audio_pattern.value) + + if not (1 <= audio_mode.channel_count <= 8): + raise ValueError(f"'Channel count' must be in range: 1 - 8") + + if audio_mode.sample_rate not in list_sample_rate: + raise ValueError(f"'Sample rate' must be in list {list_sample_rate}") + + if audio_mode.bits not in list_bits: + raise ValueError(f"'Bits' must be in list {list_bits}") + + if signal_frequency <= 0: + raise ValueError(f"'Signal frequency' must be more than 0") + + if amplitude not in [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]: + raise ValueError(f"'Amplitude' must be more than 0") + + memory_block_no = 2 + + self.__memory_manager.make_default() + self.__io.set(TSI_AUDGEN_SIGNAL_BLOCK, memory_block_no) + self.__io.set(TSI_AUDGEN_SIGNAL_TYPE, audio_pattern.value) + self.__io.set(TSI_AUDGEN_CHANNEL_COUNT, audio_mode.channel_count) + self.__io.set(TSI_AUDGEN_SAMPLE_RATE, audio_mode.sample_rate) + self.__io.set(TSI_AUDGEN_SAMPLE_SIZE, audio_mode.bits) + self.__io.set(TSI_AUDGEN_SIGNAL_FREQ, signal_frequency) + self.__io.set(TSI_AUDGEN_SIGNAL_VOLUME, amplitude) + else: + available_variants = '\n'.join([e.name for e in AudioPattern]) + raise ValueError(f"Incorrect value of audio pattern - {audio_pattern}\n" + f"Available audio patterns: {available_variants}") + + def apply(self) -> bool: + """ + Apply settings and start generate audio. + + Returns: + object of `bool` type - generation was enabled successfully or not. + """ + self.__io.set(TSI_W_AUDGEN_CONTROL, 1) + + def is_apply_ag_success(ag: AudioGenerator): + return ag.status == AGStatus.Running + + return function_scheduler(is_apply_ag_success, self, interval=1, timeout=10) + + def stop_generate(self) -> bool: + """ + Stop generate audio. + + Returns: + object of `bool` type - generation was disabled successfully or not. + """ + return True if self.__io.set(TSI_W_AUDGEN_CONTROL, 0) >= TSI_SUCCESS else False + + @property + def status(self) -> AGStatus: + """ + Return audio generator status. + + Returns: + object of `AGStatus` type + """ + self.__status = AGStatus(self.__io.get(TSI_AUDGEN_STATUS_R, c_int)[1] & 0x1) + return self.__status + + @property + def audio_mode(self) -> AudioMode: + """ + Return current audio mode. + + Returns: + object of `AudioMode` type + """ + self.__audio_mode.sample_rate = self.__io.get(TSI_AUDGEN_SAMPLE_RATE, c_int32)[1] + self.__audio_mode.bits = self.__io.get(TSI_AUDGEN_SAMPLE_SIZE, c_int32)[1] + self.__audio_mode.channel_count = self.__io.get(TSI_AUDGEN_CHANNEL_COUNT, c_int32)[1] + return self.__audio_mode diff --git a/UniTAP/dev/ports/modules/ag/ag_utils.py b/UniTAP/dev/ports/modules/ag/ag_utils.py new file mode 100644 index 0000000..453bc9f --- /dev/null +++ b/UniTAP/dev/ports/modules/ag/ag_utils.py @@ -0,0 +1,109 @@ +import os.path +import warnings +import wave +import pickle +from collections import deque +from UniTAP.common.audio_mode import AudioMode, AudioFileFormat +from UniTAP.libs.lib_tsi.tsi import sizeof, c_uint32 + + +def save_to_wave_file(path: str, audio_mode: AudioMode, data: bytearray): + if len(data) <= 0: + raise ValueError(f"Audio data must not be empty! Current size {len(data)}") + + file = wave.Wave_write(path + ".wav") + file.setframerate(audio_mode.sample_rate) + file.setnchannels(audio_mode.channel_count) + file.setsampwidth(int(audio_mode.bits / 8)) + file.writeframesraw(data) + file.close() + + +def save_to_bin_file(path: str, data: bytearray): + if len(data) <= 0: + raise ValueError(f"Audio data must not be empty! Current size {len(data)}") + + bin_file = open(path + '.bin', 'wb') + bin_file.write(data) + bin_file.close() + + +def load_from_bin_file(path: str): + file = open(path, "rb") + data = pickle.load(file) + file.close() + + return data, AudioMode(), len(data) + + +def load_from_wave_file(path: str): + try: + data = wave.open(path, 'rb') + except wave.Error: + raise NotImplementedError("Python Wave package doesn't support not PCM formats. Please, use PCM format WAV " + "file.") + + channels = 2 if data.getnchannels() <= 2 else 8 + size = data.getnframes() * channels * sizeof(c_uint32) + sizeof(c_uint32) + channel_count = data.getnchannels() + sample_rate = data.getframerate() + bits = data.getsampwidth() * 8 + bytes_block = data.readframes(size) + data.close() + ret_list = [0] * size + + list_of_data = deque(list(bytes_block)) + for i in range(0, len(ret_list), (channels * sizeof(c_uint32))): + for j in range(data.getsampwidth() * channel_count): + if len(list_of_data) == 0: + break + idx = i // (channels * sizeof(c_uint32)) * channels * sizeof(c_uint32) + j + chunk_num = j // data.getsampwidth() + addition = chunk_num * data.getsampwidth() + if data.getsampwidth() == 2: + ret_list[idx + addition + 1] = list_of_data.popleft() + else: + ret_list[idx + addition] = list_of_data.popleft() + + audio_mode = AudioMode() + audio_mode.channel_count = channel_count + audio_mode.sample_rate = sample_rate + audio_mode.bits = bits + + return bytes(ret_list), audio_mode, len(ret_list) + + +def check_file_format(path: str): + if not os.path.exists(path): + return AudioFileFormat.UNKNOWN + + filename, file_extension = os.path.splitext(path) + if file_extension.lower() == 'bin': + return AudioFileFormat.BIN + elif file_extension.lower() not in ["wav", "wave"]: + return AudioFileFormat.WAV + else: + return AudioFileFormat.UNKNOWN + + +def create_audio_sts(audio_mode: AudioMode): + sts_b = [0] * 48 + + sts_b[0] = 0 + sts_b[0] |= 4 + sts_b[24] = sts_b[0] + rate = audio_mode.sample_rate + bits = audio_mode.bits + + dict_sample_rate = {22050: 4, 44100: 0, 88200: 8, 176400: 12, 24000: 6, 48000: 2, 96000: 10, 192000: 14, + 32000: 3, + 768000: 9} + dict_bits = {16: 2, 20: 10, 24: 11} + smpl = dict_sample_rate.get(rate) + sts_b[3] = smpl + sts_b[27] = sts_b[3] + smlen = dict_bits.get(bits) + sts_b[4] = smlen + sts_b[28] = sts_b[4] + + return sts_b diff --git a/UniTAP/dev/ports/modules/ag/private_types.py b/UniTAP/dev/ports/modules/ag/private_types.py new file mode 100644 index 0000000..3dac6ba --- /dev/null +++ b/UniTAP/dev/ports/modules/ag/private_types.py @@ -0,0 +1,19 @@ +from ctypes import Structure, c_uint32 + + +class AudioConfigStructure(Structure): + _fields_ = [ + ('auto_ch_sts', c_uint32, 1), + ('use_raw_data', c_uint32, 1), + ('non_lpcm', c_uint32, 1), + ('loop', c_uint32, 1), + ('auto_info_frame', c_uint32, 1), + ('', c_uint32, 3), + ('n_selector', c_uint32, 4), + ('cts_selector', c_uint32, 4), + ('', c_uint32, 16), + ] + + def value(self) -> int: + return self.auto_ch_sts | self.use_raw_data << 1 | self.non_lpcm << 2 | self.loop << 3 | \ + self.auto_info_frame << 4 | self.n_selector << 8 | self.cts_selector << 12 diff --git a/UniTAP/dev/ports/modules/ag/types.py b/UniTAP/dev/ports/modules/ag/types.py new file mode 100644 index 0000000..252e348 --- /dev/null +++ b/UniTAP/dev/ports/modules/ag/types.py @@ -0,0 +1,22 @@ +from enum import IntEnum + + +class AudioPattern(IntEnum): + """ + Class `AudioPattern` contains all possible variants of audio templates. + """ + SignalSine = 0 + SignalSawtooth = 1 + SignalSquare = 2 + CustomAudio = 3 + SignalIncremental = 4 + Unknown = 5 + + +class AGStatus(IntEnum): + """ + Class `AGStatus` contains all possible variants of Audio generator states. + """ + Unknown = -1 + Stop = 0 + Running = 1 diff --git a/UniTAP/dev/ports/modules/capturer/__init__.py b/UniTAP/dev/ports/modules/capturer/__init__.py new file mode 100644 index 0000000..048b6d3 --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/__init__.py @@ -0,0 +1,2 @@ +from .event import * +from .video import * diff --git a/UniTAP/dev/ports/modules/capturer/audio/__init__.py b/UniTAP/dev/ports/modules/capturer/audio/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/UniTAP/dev/ports/modules/capturer/audio/audio_capturer.py b/UniTAP/dev/ports/modules/capturer/audio/audio_capturer.py new file mode 100644 index 0000000..67e576b --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/audio/audio_capturer.py @@ -0,0 +1,117 @@ +import time +import copy +import warnings + +from typing import List + +from UniTAP.common.audio_mode import AudioFrameData +from UniTAP.dev.modules.capturer.capture import Capturer, CaptureConfig +from UniTAP.dev.modules.capturer.statuses import AudioCaptureStatus +from .result_audio import ResultAudioObject + + +class AudioCapturer: + """ + Class `AudioCapturer` allows working with capturing audio frames on Sink (RX - receiver) side. + You can `start` capturing in several modes, `stop` capturing, getting current `status` and result of capturing + `capture_result`. + """ + def __init__(self, capturer: Capturer): + self.__capturer = capturer + self.__result = ResultAudioObject() + + @property + def status(self) -> AudioCaptureStatus: + """ + Returns current audio capturer status. + + Returns: + object of `AudioCaptureStatus` type + """ + return self.__capturer.audio_capturer_status + + @property + def capture_result(self) -> ResultAudioObject: + """ + Returns result of audio capturing. + + Returns: + object of `ResultAudioObject` type + """ + return self.__result + + def start(self, frames_count=0, m_sec=0, timeout=None): + """ + Start capturing. Possible some variants of capturing: + - Capture with fixed frames count (will be captured fixed frames count and capturing will be stopped). + - Capture with fixed audio duration (captures audio for the specified duration in milliseconds). + - Capture without parameters - Live capturing (for getting frames you need to use functions `pop_element`) + - Capture with timeout (maximum duration for capturing operations before stopping, in seconds). + + All results can be obtained using the function `capture_result`. + + Args: + frames_count (int) + m_sec (int) + """ + config = CaptureConfig() + config.audio = True + config.type = CaptureConfig.Type.LIVE + config.video = True + + self.__capturer.start_capture(config) + + self.__result.clear() + self.__result.start_capture_time = time.time() + + if frames_count > 0: + self.__result.buffer.extend(self.__capturer.capture_audio_by_n_frames(frames_count, timeout)) + self.__fill_audio_mode() + elif m_sec > 0: + self.__result.buffer.extend(self.__capturer.capture_audio_by_m_sec(m_sec)) + self.__fill_audio_mode() + + def __fill_audio_mode(self): + if len(self.__result.buffer) > 0: + self.__result.audio_mode.channel_count = self.__result.buffer[0].channel_count + self.__result.audio_mode.bits = self.__result.buffer[0].sample_size + self.__result.audio_mode.sample_rate = self.__result.buffer[0].sample_rate + + def stop(self): + """ + Stop capture audio. + """ + config = CaptureConfig() + config.audio = True + config.type = CaptureConfig.Type.LIVE + config.video = True + + self.__capturer.stop_capture(config) + self.__result.end_capture_time = time.time() + + def pop_element(self) -> List[AudioFrameData]: + """ + Return first object of `AudioFrameData`. + + Returns: + object of `AudioFrameData` type + """ + if self.status == AudioCaptureStatus.Stop: + warnings.warn("Audio capture is not working now. Please, turn it on.") + captured_audio_frames = self.__capturer.capture_audio_by_n_frames(1) + self.__result.buffer.extend(copy.deepcopy(captured_audio_frames)) + self.__fill_audio_mode() + return captured_audio_frames[0] + + def pop_element_as_result_object(self) -> ResultAudioObject: + """ + Return captured audio frame(objects of `AudioFrameData`) as `ResultAudioObject`. + + Returns: + object of `ResultAudioObject` type + """ + captured_audio_frames = self.__capturer.capture_audio_by_n_frames(1) + + res = ResultAudioObject() + res.buffer.extend(captured_audio_frames) + return res diff --git a/UniTAP/dev/ports/modules/capturer/audio/result_audio.py b/UniTAP/dev/ports/modules/capturer/audio/result_audio.py new file mode 100644 index 0000000..277cc7f --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/audio/result_audio.py @@ -0,0 +1,60 @@ +from UniTAP.dev.modules.capturer.result_object import ResultObject + +from UniTAP.common.audio_mode import AudioMode, AudioFileFormat + +from UniTAP.dev.ports.modules.ag.ag_utils import save_to_bin_file, save_to_wave_file +import warnings + + +class ResultAudioObject(ResultObject): + """ + Class `ResultAudioObject` inherited from class `ResultObject`. + Class `ResultAudioObject` allows saving captured frames to image `save_image_to_file`. + Also has all the `ResultObject` functionality. + """ + def __init__(self): + super().__init__() + self.__audio_mode = AudioMode() + + @property + def audio_mode(self) -> AudioMode: + """ + Returns current audio mode for captured audio frames. + + Returns: + object of `AudioMode` type + """ + return self.__audio_mode + + def save_to_file(self, file_format: AudioFileFormat, path: str): + """ + Saving audio frames to file. Supported file formats describe in `AudioFileFormat`. + + Args: + file_format (`AudioFileFormat`) - file format + path (str) - path to save + """ + if len(self.buffer) > 0: + if file_format == AudioFileFormat.BIN: + save_to_bin_file(path=path, data=self.__convert_buffer()) + elif file_format == AudioFileFormat.WAV: + save_to_wave_file(path=path, audio_mode=self.audio_mode, data=self.__convert_buffer()) + else: + raise ValueError(f"Incorrect audio format. Available formats: " + f"{AudioFileFormat.BIN.name}, {AudioFileFormat.WAV.name}.\n" + f"Transferred audio format: {file_format.name}") + else: + warnings.warn("Buffer size is equal 0.") + + def __convert_buffer(self): + data = [] + for audio_frame in self.buffer: + data.append(audio_frame.data) + return bytearray().join(data) + + def __str__(self): + return f"Start capture time: {self.start_capture_time}\n" \ + f"End capture time: {self.end_capture_time}\n" \ + f"Timestamp: {self.timestamp.__str__()}\n" \ + f"Audio mode:\n{self.audio_mode.__str__()}\n" + diff --git a/UniTAP/dev/ports/modules/capturer/bulk/__init__.py b/UniTAP/dev/ports/modules/capturer/bulk/__init__.py new file mode 100644 index 0000000..d7d9be7 --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/bulk/__init__.py @@ -0,0 +1 @@ +from .bulk_types import TriggerType, TriggerPosition, TriggerTypeEnum diff --git a/UniTAP/dev/ports/modules/capturer/bulk/bulk_capturer.py b/UniTAP/dev/ports/modules/capturer/bulk/bulk_capturer.py new file mode 100644 index 0000000..3a89bd8 --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/bulk/bulk_capturer.py @@ -0,0 +1,132 @@ +import warnings +import time + +from typing import Optional + +from UniTAP.dev.modules.capturer.capture import Capturer, CaptureConfig +from UniTAP.dev.modules.memory_manager import MemoryManager +from UniTAP.dev.ports.modules.capturer.bulk.bulk_types import (TriggerPosition, TriggerVarType, _get_trigger_value, + EncodingTypeEnum, LaneCountEnum) +from UniTAP.dev.ports.modules.capturer.bulk.result_bulk import ResultBulkObject + +megabyte = 1024 * 1024 + + +class BulkCapturer: + """ + Class `BulkCapturer` allows working with capturing Bulk data on Sink (RX - receiver) side. + You can `start` capturing in several modes, `stop` capturing, getting current `status` and result of + capturing `capture_result`. + """ + def __init__(self, capturer: Capturer, memory_manager: MemoryManager): + self.__capturer = capturer + self.__memory_manager = memory_manager + self.__trigger_position = TriggerPosition.TP_Start + + self.__result = ResultBulkObject() + + @property + def status(self): + """ + Returns current bulk capturer status. + + Returns: + object of `VideoCaptureStatus` type + """ + return self.__capturer.bulk_capturer_status + + @property + def capture_result(self) -> ResultBulkObject: + """ + Returns result of bulk capturing. + + Returns: + object of `ResultBulkObject` type + """ + return self.__result + + @property + def encoding_type(self) -> EncodingTypeEnum: + """ + Returns current encoding type of capturing. + + Returns: + object of `EncodingTypeEnum` type + """ + return self.__capturer.read_encoding_type() + + @property + def lane_count(self) -> LaneCountEnum: + """ + Returns current lane count for capturing. + + Returns: + object of `LaneCountEnum` type + """ + return self.__capturer.read_lane_count() + + def start(self, bulk_size: int = 1, trigger_position: TriggerPosition = TriggerPosition.TP_Start, + trigger_config: Optional[TriggerVarType] = None, assume_scrambler: bool = False, gpio: bool = False, + encoding_type: Optional[EncodingTypeEnum] = None, lane_count: Optional[LaneCountEnum] = None): + """ + Start capturing. All results can be obtained using the function `capture_result`. + + Args: + bulk_size (int) - bulk data size in megabytes + trigger_position (`TriggerPosition`) + trigger_config (`TriggerVarType`|None) + assume_scrambler (bool) + gpio (bool) + encoding_type (`EncodingTypeEnum`|None) + lane_count (`LaneCountEnum`|None) + """ + self.__result.clear() + + if trigger_config is None: + trigger_position = TriggerPosition.TP_Start + + capture_config = CaptureConfig() + capture_config.video = True + capture_config.event = True + capture_config.audio = True + capture_config.type = CaptureConfig.Type.LIVE + self.__capturer.stop_capture(capture_config) + self.__capturer.stop_bulk_capture() + self.__capturer.clear_bulk_buffer() + + max_bulk_block_bytes = self.__memory_manager.get_total_memory() - self.__memory_manager.RESERVED_MEMORY_BYTES + bulk_block_bytes = max_bulk_block_bytes if bulk_size * megabyte > max_bulk_block_bytes else bulk_size * megabyte + self.__memory_manager.set_memory_layout([bulk_block_bytes]) + self.__capturer.write_bulk_size(bulk_block_bytes) + + if encoding_type is None: + encoding_type = EncodingTypeEnum.Encoding_Auto + self.__capturer.write_encoding_type(encoding_type) + + if lane_count is None: + lane_count = LaneCountEnum.Auto + self.__capturer.write_lane_count(lane_count) + + if self.__capturer.read_bulk_capture_caps().bitGrabWidth: + self.__capturer.write_bulk_gpio(gpio) + + self.__capturer.write_bulk_trigger_position(trigger_position.value) + + if self.__capturer.read_bulk_capture_caps().triggers: + self.__capturer.write_bulk_trigger_settings(*_get_trigger_value(trigger_config, + self.__capturer.read_bulk_trigger_caps())) + else: + warnings.warn("Triggers are not supported.") + + self.__result.assume_scrambler_disabled = assume_scrambler + self.__result.start_capture_time = time.time() + + self.__capturer.start_bulk_capture() + self.__result.buffer.extend(self.__capturer.bulk_capture(bulk_block_bytes, trigger_config)) + + def stop(self): + """ + Stop capture video. + """ + self.__capturer.stop_bulk_capture() + self.__result.end_capture_time = time.time() diff --git a/UniTAP/dev/ports/modules/capturer/bulk/bulk_types.py b/UniTAP/dev/ports/modules/capturer/bulk/bulk_types.py new file mode 100644 index 0000000..9b82d87 --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/bulk/bulk_types.py @@ -0,0 +1,979 @@ +from enum import IntEnum +from typing import TypeVar, Optional + + +class EncodingTypeEnum(IntEnum): + Encoding_Auto = 0, + Encoding_10Bit = 1, + Encoding_32Bit = 2 + + +class LaneCountEnum(IntEnum): + Auto = 0, + Lane_1 = 1, + Lane_2 = 2, + Lane_4 = 4 + + +class TriggerPosition(IntEnum): + """ + Trigger position relative to the start of capture, in percent of total capture size. + - TP_Start – 0% + - TP_25 – 25% + - TP_50 – 50% + - TP_75 – 75% + - TP_End - 100% + """ + TP_Start = 0 + TP_25 = 1 + TP_50 = 2 + TP_75 = 3 + TP_End = 4 + + +class TriggerTypeEnum: + """ + Class `TriggerTypeEnum` contains all necessary enum types for describing values. + """ + + class SourceType(IntEnum): + TPS1 = 0 + TPS2 = 1 + TPS3 = 2 + TPS4 = 3 + + class SourceTypePosition(IntEnum): + InitialLT = 0 + AfterALPM = 1 + InitialLTORAfterALPM = 2 + + class SourceMLPHY(IntEnum): + Standby = 0 + Sleep = 1 + + class SourceVBIDWithMask(IntEnum): + AnyVB_IDChange = 0 + VB_IDMatchWithMask = 1 + ChangeAnyBitSetInMask = 2 + + class SourceVBID(IntEnum): + BS = 0 + SR = 1 + CPBS = 2 + CPSR = 3 + + class SDPTypeReceived(IntEnum): + MatchHB0 = 1 + MatchHB1 = 2 + MatchHB0AndHB1 = 3 + + class MSA(IntEnum): + AnyMSAChange = 0 + ChangeMSAAttribute = 1 + MatchMSAAttribute = 2 + + class Error8b_10b(IntEnum): + CodeError = 0 + DisparityError = 0 + Both = 0 + + class TypeAUX(IntEnum): + NativeWrite = 0x8 + NativeRead = 0x9 + + +class TriggerType: + """ + Main class `TriggerType` defines possible variants of trigger types. + - `U1` - Start of TPS1/TPS2/TPS3/TPS4 - initial LT, after ALPM exit, both. + - `U2` - Exit of TPS1/TPS2/TPS3/TPS4 - initial LT, after ALPM exit, both. + - `U3` - Start of ML_PHY_STANDBY or ML_PHY_SLEEP. + - `U4` - Exit of ML_PHY_STANDBY or ML_PHY_SLEEP. + - `U5` - Start of EIEOS - initial LT, after ALPM exit, both. + - `U6` - Exit of EIEOS - initial LT, after ALPM exit, both. + - `U7` - VB-ID with the MASK - any change, match, selected bit transition. + - `U8` - VB-ID on TYPE - BS/SR/CPBS/CPSR. + - `U9` - Up to 8 control or data symbols, 8b/10b encoded. + - `U10` - SDP Type received – HB0 and/or HB1 match. + - `U11` - MSA – any change, change by mask, match by mask. + - `U12` - 8b/10b error – code error, disparity error, both. + - `U13` - Any AUX transaction - initial LT, after ALPM exit, both. + - `U17` - AUX read or write of specific address. + """ + + class U1: + """ + Class `U1` describes one of the possible trigger type. Allows: + - Get trigger mask `trigger_mask`. + - Set and get source type `source_type` - `TriggerTypeEnum.SourceType`. + - Set and get position `position` - `TriggerTypeEnum.SourceTypePosition. + """ + + def __init__(self): + self.__trigger_mask = 1 + self.__source_type = TriggerTypeEnum.SourceType.TPS1 + self.__position = TriggerTypeEnum.SourceTypePosition.InitialLT + + @property + def trigger_mask(self) -> int: + """ + Returns trigger mask. + + Returns: + object of int value + """ + return self.__trigger_mask + + @property + def source_type(self) -> TriggerTypeEnum.SourceType: + """ + Returns source type. + + Returns: + object of `TriggerTypeEnum.SourceType` value + """ + return self.__source_type + + @property + def position(self) -> TriggerTypeEnum.SourceTypePosition: + """ + Returns position. + + Returns: + object of `TriggerTypeEnum.SourceTypePosition` value + """ + return self.__position + + @source_type.setter + def source_type(self, value: TriggerTypeEnum.SourceType): + self.__source_type = value + + @position.setter + def position(self, value: TriggerTypeEnum.SourceTypePosition): + self.__position = value + + class U2: + """ + Class `U2` describes one of the possible trigger type. Allows: + - Get trigger mask `trigger_mask`. + - Set and get source type `source_type` - `TriggerTypeEnum.SourceType`. + - Set and get position `position` - `TriggerTypeEnum.SourceTypePosition. + """ + + def __init__(self): + self.__trigger_mask = 1 << 1 + self.__source_type = TriggerTypeEnum.SourceType.TPS1 + self.__position = TriggerTypeEnum.SourceTypePosition.InitialLT + + @property + def trigger_mask(self) -> int: + """ + Returns trigger mask. + + Returns: + object of int value + """ + return self.__trigger_mask + + @property + def source_type(self) -> TriggerTypeEnum.SourceType: + """ + Returns source type. + + Returns: + object of `TriggerTypeEnum.SourceType` value + """ + return self.__source_type + + @property + def position(self) -> TriggerTypeEnum.SourceTypePosition: + """ + Returns position. + + Returns: + object of `TriggerTypeEnum.SourceTypePosition` value + """ + return self.__position + + @source_type.setter + def source_type(self, value: TriggerTypeEnum.SourceType): + self.__source_type = value + + @position.setter + def position(self, value: TriggerTypeEnum.SourceTypePosition): + self.__position = value + + class U3: + """ + Class `U3` describes one of the possible trigger type. Allows: + - Get trigger mask `trigger_mask`. + - Set and get source `source` - `TriggerTypeEnum.SourceMLPHY`. + """ + + def __init__(self): + self.__trigger_mask = 1 << 2 + self.__source = TriggerTypeEnum.SourceMLPHY.Standby + + @property + def trigger_mask(self) -> int: + """ + Returns trigger mask. + + Returns: + object of int value + """ + return self.__trigger_mask + + @property + def source(self) -> TriggerTypeEnum.SourceMLPHY: + """ + Returns source. + + Returns: + object of `TriggerTypeEnum.SourceMLPHY` value + """ + return self.__source + + @source.setter + def source(self, value: TriggerTypeEnum.SourceMLPHY): + self.__source = value + + class U4: + """ + Class `U4` describes one of the possible trigger type. Allows: + - Get trigger mask `trigger_mask`. + - Set and get source `source` - `TriggerTypeEnum.SourceMLPHY`. + """ + + def __init__(self): + self.__trigger_mask = 1 << 3 + self.__source = TriggerTypeEnum.SourceMLPHY.Standby + + @property + def trigger_mask(self) -> int: + """ + Returns trigger mask. + + Returns: + object of int value + """ + return self.__trigger_mask + + @property + def source(self) -> TriggerTypeEnum.SourceMLPHY: + """ + Returns source. + + Returns: + object of `TriggerTypeEnum.SourceMLPHY` value + """ + return self.__source + + @source.setter + def source(self, value: TriggerTypeEnum.SourceMLPHY): + self.__source = value + + class U5: + """ + Class `U5` describes one of the possible trigger type. Allows: + - Get trigger mask `trigger_mask`. + - Set and get position `position` - `TriggerTypeEnum.SourceTypePosition`. + """ + + def __init__(self): + self.__trigger_mask = 1 << 4 + self.__position = TriggerTypeEnum.SourceTypePosition.InitialLT + + @property + def trigger_mask(self) -> int: + """ + Returns trigger mask. + + Returns: + object of int value + """ + return self.__trigger_mask + + @property + def position(self) -> TriggerTypeEnum.SourceTypePosition: + """ + Returns position. + + Returns: + object of `TriggerTypeEnum.SourceTypePosition` value + """ + return self.__position + + @position.setter + def position(self, value: TriggerTypeEnum.SourceTypePosition): + self.__position = value + + class U6: + """ + Class `U6` describes one of the possible trigger type. Allows: + - Get trigger mask `trigger_mask`. + - Set and get position `position` - `TriggerTypeEnum.SourceTypePosition`. + """ + + def __init__(self): + self.__trigger_mask = 1 << 5 + self.__position = TriggerTypeEnum.SourceTypePosition.InitialLT + + @property + def trigger_mask(self) -> int: + """ + Returns trigger mask. + + Returns: + object of int value + """ + return self.__trigger_mask + + @property + def position(self) -> TriggerTypeEnum.SourceTypePosition: + """ + Returns position. + + Returns: + object of `TriggerTypeEnum.SourceTypePosition` value + """ + return self.__position + + @position.setter + def position(self, value: TriggerTypeEnum.SourceTypePosition): + self.__position = value + + class U7: + """ + Class `U7` describes one of the possible trigger type. Allows: + - Get trigger mask `trigger_mask`. + - Set and get mask `mask`. + - Set and get source `source` - `TriggerTypeEnum.SourceVBIDWithMask`. + """ + + def __init__(self): + self.__trigger_mask = 1 << 6 + self.__source = TriggerTypeEnum.SourceVBIDWithMask.AnyVB_IDChange + self.__mask = 0 + + @property + def trigger_mask(self) -> int: + """ + Returns trigger mask. + + Returns: + object of int value + """ + return self.__trigger_mask + + @property + def source(self) -> TriggerTypeEnum.SourceVBIDWithMask: + """ + Returns source. + + Returns: + object of `TriggerTypeEnum.SourceVBIDWithMask` value + """ + return self.__source + + @property + def mask(self) -> int: + """ + Returns mask. + + Returns: + object of int value + """ + return self.__mask + + @source.setter + def source(self, value: TriggerTypeEnum.SourceVBIDWithMask): + self.__source = value + + @mask.setter + def mask(self, value: int): + if value < 0: + raise ValueError("Mask must be more than 0.") + self.__mask = value + + class U8: + """ + Class `U8` describes one of the possible trigger type. Allows: + - Get trigger mask `trigger_mask`. + - Set and get source `source` - `TriggerTypeEnum.SourceVBID`. + """ + + def __init__(self): + self.__trigger_mask = 1 << 7 + self.__source = TriggerTypeEnum.SourceVBID.BS + + @property + def trigger_mask(self) -> int: + """ + Returns trigger mask. + + Returns: + object of int value + """ + return self.__trigger_mask + + @property + def source(self) -> TriggerTypeEnum.SourceVBID: + """ + Returns source. + + Returns: + object of `TriggerTypeEnum.SourceVBID` value + """ + return self.__source + + @source.setter + def source(self, value: TriggerTypeEnum.SourceVBID): + self.__source = value + + class U9: + """ + Class `U9` describes one of the possible trigger type. Allows: + - Get trigger mask `trigger_mask`. + - Set and get value for count of symbols `count`. + - Set and get value for symbol 0 `symbol_0`. + - Set and get value for symbol 1 `symbol_1`. + - Set and get value for symbol 2 `symbol_2`. + - Set and get value for symbol 3 `symbol_3`. + - Set and get value for symbol 4 `symbol_4`. + - Set and get value for symbol 5 `symbol_5`. + - Set and get value for symbol 6 `symbol_6`. + - Set and get value for symbol 7 `symbol_7`. + """ + + def __init__(self): + self.__trigger_mask = 1 << 8 + self.__symbol_0 = 0 + self.__symbol_1 = 0 + self.__symbol_2 = 0 + self.__symbol_3 = 0 + self.__symbol_4 = 0 + self.__symbol_5 = 0 + self.__symbol_6 = 0 + self.__symbol_7 = 0 + self.__count = 0 + + @property + def trigger_mask(self) -> int: + """ + Returns trigger mask. + + Returns: + object of int value + """ + return self.__trigger_mask + + @property + def count(self) -> int: + """ + Returns count of symbols. + + Returns: + object of int value + """ + return self.__count + + @count.setter + def count(self, value: int): + if value < 0: + raise ValueError("Mask must be more than 0 and length must be less than 5") + self.__count = value + + @property + def symbol_0(self) -> int: + """ + Returns value of symbol 0. + + Returns: + object of int value + """ + return self.__symbol_0 + + @symbol_0.setter + def symbol_0(self, value: int): + if value < 0 or len(str(value)) >= 5: + raise ValueError("Mask must be more than 0 and length must be less than 5") + self.__symbol_0 = value + + @property + def symbol_1(self) -> int: + """ + Returns value of symbol 1. + + Returns: + object of int value + """ + return self.__symbol_1 + + @symbol_1.setter + def symbol_1(self, value: int): + if value < 0 or len(str(value)) >= 5: + raise ValueError("Mask must be more than 0 and length must be less than 5") + self.__symbol_1 = value + + @property + def symbol_2(self) -> int: + """ + Returns value of symbol 2. + + Returns: + object of int value + """ + return self.__symbol_2 + + @symbol_2.setter + def symbol_2(self, value: int): + if value < 0 or len(str(value)) >= 5: + raise ValueError("Mask must be more than 0 and length must be less than 5") + self.__symbol_2 = value + + @property + def symbol_3(self) -> int: + """ + Returns value of symbol 3. + + Returns: + object of int value + """ + return self.__symbol_3 + + @symbol_3.setter + def symbol_3(self, value: int): + if value < 0 or len(str(value)) >= 5: + raise ValueError("Mask must be more than 0 and length must be less than 5") + self.__symbol_3 = value + + @property + def symbol_4(self) -> int: + """ + Returns value of symbol 4. + + Returns: + object of int value + """ + return self.__symbol_4 + + @symbol_4.setter + def symbol_4(self, value: int): + if value < 0 or len(str(value)) >= 5: + raise ValueError("Mask must be more than 0 and length must be less than 5") + self.__symbol_4 = value + + @property + def symbol_5(self) -> int: + """ + Returns value of symbol 5. + + Returns: + object of int value + """ + return self.__symbol_5 + + @symbol_5.setter + def symbol_5(self, value: int): + if value < 0 or len(str(value)) >= 5: + raise ValueError("Mask must be more than 0 and length must be less than 5") + self.__symbol_5 = value + + @property + def symbol_6(self) -> int: + """ + Returns value of symbol 6. + + Returns: + object of int value + """ + return self.__symbol_6 + + @symbol_6.setter + def symbol_6(self, value: int): + if value < 0 or len(str(value)) >= 5: + raise ValueError("Mask must be more than 0 and length must be less than 5") + self.__symbol_6 = value + + @property + def symbol_7(self) -> int: + """ + Returns value of symbol 7. + + Returns: + object of int value + """ + return self.__symbol_7 + + @symbol_7.setter + def symbol_7(self, value: int): + if value < 0 or len(str(value)) >= 5: + raise ValueError("Mask must be more than 0 and length must be less than 5") + self.__symbol_7 = value + + class U10: + """ + Class `U10` describes one of the possible trigger type. Allows: + - Get trigger mask `trigger_mask`. + - Set and get SDP type `sdp_type` - `TriggerTypeEnum.SDPTypeReceived`. + - Set and get value for HB 0 `hb0`. + - Set and get value for HB 1 `hb1`. + """ + + def __init__(self): + self.__trigger_mask = 1 << 9 + self.__sdp_type = TriggerTypeEnum.SDPTypeReceived.MatchHB0 + self.__hb0 = 0 + self.__hb1 = 0 + + @property + def trigger_mask(self) -> int: + """ + Returns trigger mask. + + Returns: + object of int value + """ + return self.__trigger_mask + + @property + def sdp_type(self) -> TriggerTypeEnum.SDPTypeReceived: + """ + Returns SDP type. + + Returns: + object of `TriggerTypeEnum.SDPTypeReceived` value + """ + return self.__sdp_type + + @sdp_type.setter + def sdp_type(self, value: TriggerTypeEnum.SDPTypeReceived): + self.__sdp_type = value + + @property + def hb0(self) -> int: + """ + Returns value of HB0. + + Returns: + object of int value + """ + return self.__hb0 + + @hb0.setter + def hb0(self, value: int): + if value < 0: + raise ValueError("Mask must be more than 0.") + self.__hb0 = value + + @property + def hb1(self) -> int: + """ + Returns value of HB1. + + Returns: + object of int value + """ + return self.__hb1 + + @hb1.setter + def hb1(self, value: int): + if value < 0: + raise ValueError("Mask must be more than 0.") + self.__hb1 = value + + class U11: + """ + Class `U11` describes one of the possible trigger type. Allows: + - Get trigger mask `trigger_mask`. + - Set and get source `source` - `TriggerTypeEnum.MSA`. + - Set and get MSa attributes (mvid, nvid, hactive, vactive and so on.) + """ + + def __init__(self): + self.__trigger_mask = 1 << 10 + self.__source = TriggerTypeEnum.MSA.AnyMSAChange + self.mvid_flag = False + self.nvid_flag = False + self.hactive_flag = False + self.vactive_flag = False + self.htotal_flag = False + self.vtotal_flag = False + self.hsyncw_flag = False + self.vsyncw_flag = False + self.hsyncp_flag = False + self.vsyncp_flag = False + self.hsyncs_flag = False + self.vsyncs_flag = False + self.misc0_flag = False + self.misc1_flag = False + + self.mvid = 0 + self.nvid = 0 + self.hactive = 0 + self.vactive = 0 + self.htotal = 0 + self.vtotal = 0 + self.hsyncw = 0 + self.vsyncw = 0 + self.hsyncs = 0 + self.vsyncs = 0 + self.misc0 = 0 + self.misc1 = 0 + + @property + def trigger_mask(self) -> int: + """ + Returns trigger mask. + + Returns: + object of int value + """ + return self.__trigger_mask + + @property + def source(self) -> TriggerTypeEnum.MSA: + """ + Returns source. + + Returns: + object of `TriggerTypeEnum.MSA` value + """ + return self.__source + + @source.setter + def source(self, value: TriggerTypeEnum.MSA): + self.__source = value + + class U12: + """ + Class `U12` describes one of the possible trigger type. Allows: + - Get trigger mask `trigger_mask`. + - Set and get source `source` - `TriggerTypeEnum.Error8b_10b`. + """ + + def __init__(self): + self.__trigger_mask = 1 << 11 + self.__source = TriggerTypeEnum.Error8b_10b.CodeError + + @property + def trigger_mask(self) -> int: + """ + Returns trigger mask. + + Returns: + object of int value + """ + return self.__trigger_mask + + @property + def source(self) -> TriggerTypeEnum.Error8b_10b: + """ + Returns source. + + Returns: + object of `TriggerTypeEnum.Error8b_10b` value + """ + return self.__source + + @source.setter + def source(self, value: TriggerTypeEnum.Error8b_10b): + self.__source = value + + class U13: + """ + Class `U13` describes one of the possible trigger type. Allows: + - Get trigger mask `trigger_mask`. + - Set and get position `position` - `TriggerTypeEnum.SourceTypePosition`. + """ + + def __init__(self): + self.__trigger_mask = 1 << 12 + self.__position = TriggerTypeEnum.SourceTypePosition.InitialLT + + @property + def trigger_mask(self) -> int: + """ + Returns trigger mask. + + Returns: + object of int value + """ + return self.__trigger_mask + + @property + def position(self) -> TriggerTypeEnum.SourceTypePosition: + """ + Returns position. + + Returns: + object of `TriggerTypeEnum.SourceTypePosition` value + """ + return self.__position + + @position.setter + def position(self, value: TriggerTypeEnum.SourceTypePosition): + self.__position = value + + class U17: + """ + Class `U17` describes one of the possible trigger type. Allows: + - Get trigger mask `trigger_mask`. + - Set and get address `address`. + - Set and get type `type` - `TriggerTypeEnum.TypeAUX`. + """ + + def __init__(self): + self.__trigger_mask = 1 << 16 + self.__address = 0 + self.__type = TriggerTypeEnum.TypeAUX.NativeWrite + + @property + def trigger_mask(self) -> int: + """ + Returns trigger mask. + + Returns: + object of int value + """ + return self.__trigger_mask + + @property + def address(self) -> int: + """ + Returns address. + + Returns: + object of int value + """ + return self.__address + + @address.setter + def address(self, value: int): + if value < 0: + raise ValueError("Mask must be more than 0.") + self.__address = value + + @property + def type(self) -> TriggerTypeEnum.TypeAUX: + """ + Returns type. + + Returns: + object of `TriggerTypeEnum.TypeAUX` value + """ + return self.__type + + @type.setter + def type(self, value: TriggerTypeEnum.TypeAUX): + self.__type = value + + +TriggerVarType = TypeVar("TriggerVarType", TriggerType.U1, TriggerType.U2, TriggerType.U3, TriggerType.U4, + TriggerType.U5, TriggerType.U6, TriggerType.U7, TriggerType.U8, TriggerType.U9, + TriggerType.U10, TriggerType.U11, TriggerType.U12, TriggerType.U13, TriggerType.U17) + + +def _get_trigger_value(value: Optional[TriggerVarType], caps: int): + trigger_config = [0] * 39 + trigger_config_ext = [0] * 35 + + if value is None: + return 0, trigger_config, trigger_config_ext + + if isinstance(value, TriggerType.U1) and caps & 0x1: + trigger_config[0] |= value.source_type.value + trigger_config[0] |= (value.position.value << 4) + if isinstance(value, TriggerType.U2) and (caps >> 1) & 0x1: + trigger_config[1] |= value.source_type.value + trigger_config[1] |= (value.position.value << 4) + if isinstance(value, TriggerType.U3) and (caps >> 2) & 0x1: + trigger_config[2] |= value.source.value + if isinstance(value, TriggerType.U4) and (caps >> 3) & 0x1: + trigger_config[3] |= value.source.value + if isinstance(value, TriggerType.U5) and (caps >> 4) & 0x1: + trigger_config[4] |= value.position.value << 4 + if isinstance(value, TriggerType.U6) and (caps >> 5) & 0x1: + trigger_config[5] |= value.position.value << 4 + if isinstance(value, TriggerType.U7) and (caps >> 6) & 0x1: + trigger_config[6] |= value.source.value + trigger_config[6] |= value.mask.value << 8 + if isinstance(value, TriggerType.U8) and (caps >> 7) & 0x1: + trigger_config[7] |= value.source.value + if isinstance(value, TriggerType.U9) and (caps >> 8) & 0x1: + trigger_config[8] |= value.count + if value.symbol_0: + trigger_config_ext[0] |= value.symbol_0 + if value.symbol_1: + trigger_config_ext[0] |= (value.symbol_1 << 16) + if value.symbol_0: + trigger_config_ext[1] |= value.symbol_2 + if value.symbol_0: + trigger_config_ext[1] |= (value.symbol_3 << 16) + if value.symbol_0: + trigger_config_ext[2] |= value.symbol_4 + if value.symbol_0: + trigger_config_ext[2] |= (value.symbol_5 << 16) + if value.symbol_0: + trigger_config_ext[3] |= value.symbol_6 + if value.symbol_0: + trigger_config_ext[3] |= (value.symbol_7 << 16) + if isinstance(value, TriggerType.U10) and (caps >> 10) & 0x1: + trigger_config[9] |= value.sdp_type.value + trigger_config[9] |= (value.hb0 << 8) + trigger_config[9] |= (value.hb1 << 16) + if isinstance(value, TriggerType.U11) and (caps >> 11) & 0x1: + trigger_config[10] |= value.source.value + trigger_config[10] |= value.source + trigger_config[10] |= (1 << 8) if value.mvid_flag else 0 + trigger_config[10] |= (1 << 9) if value.nvid_flag else 0 + trigger_config[10] |= (1 << 10) if value.htotal_flag else 0 + trigger_config[10] |= (1 << 11) if value.vtotal_flag else 0 + trigger_config[10] |= (1 << 12) if value.hactive_flag else 0 + trigger_config[10] |= (1 << 13) if value.vactive_flag else 0 + trigger_config[10] |= (1 << 14) if value.hsyncw_flag else 0 + trigger_config[10] |= (1 << 15) if value.vsyncw_flag else 0 + trigger_config[10] |= (1 << 16) if value.hsyncp_flag else 0 + trigger_config[10] |= (1 << 17) if value.vsyncp_flag else 0 + trigger_config[10] |= (1 << 18) if value.hsyncs_flag else 0 + trigger_config[10] |= (1 << 19) if value.vsyncs_flag else 0 + trigger_config[10] |= (1 << 20) if value.misc0_flag else 0 + trigger_config[10] |= (1 << 21) if value.misc1_flag else 0 + + if value.mvid_flag: + trigger_config_ext[4] |= value.mvid & 0xFFFFFF + if value.nvid_flag: + trigger_config_ext[5] |= value.nvid & 0xFFFFFF + if value.htotal_flag: + trigger_config_ext[6] |= value.htotal & 0xFFFF + if value.vtotal_flag: + trigger_config_ext[6] |= ((value.vtotal & 0xFFFF) << 16) + if value.hactive_flag: + trigger_config_ext[7] |= value.hactive & 0xFFFF + if value.vactive_flag: + trigger_config_ext[7] |= ((value.vactive & 0xFFFF) << 16) + if value.hsyncw_flag: + trigger_config_ext[8] |= value.hsyncw & 0x7FFF + if value.hsyncp_flag: + trigger_config_ext[8] |= (value.hsyncp_flag << 15) + if value.vsyncw_flag: + trigger_config_ext[8] |= ((value.vsyncw & 0x7FFF) << 16) + if value.vsyncp_flag: + trigger_config_ext[8] |= (value.vsyncp_flag << 31) + if value.hsyncs_flag: + trigger_config_ext[9] |= value.hsyncs & 0xFFFF + if value.vsyncs_flag: + trigger_config_ext[9] |= ((value.vsyncs & 0xFFFF) << 16) + if value.misc0_flag: + trigger_config_ext[10] |= value.misc0 & 0xFF + if value.misc1_flag: + trigger_config_ext[10] |= ((value.misc1 & 0xFF) << 8) + if isinstance(value, TriggerType.U12) and (caps >> 11) & 0x1: + trigger_config[11] |= value.position + if isinstance(value, TriggerType.U13) and (caps >> 12) & 0x1: + trigger_config[12] |= (value.position << 4) + if isinstance(value, TriggerType.U17) and (caps >> 16) & 0x1: + trigger_config[16] |= (value.type.value << 24) | value.address + + return value.trigger_mask, trigger_config, trigger_config_ext diff --git a/UniTAP/dev/ports/modules/capturer/bulk/private_bulk_types.py b/UniTAP/dev/ports/modules/capturer/bulk/private_bulk_types.py new file mode 100644 index 0000000..61ee919 --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/bulk/private_bulk_types.py @@ -0,0 +1,45 @@ +from ctypes import Structure, c_uint8, c_uint16, c_uint32, c_uint64 + + +class BulkHeader(Structure): + class Attributes(Structure): + _fields_ = [ + ('LINK_BW', c_uint8), + ('LANE_COUNT', c_uint8, 4), + ('Packing1', c_uint8, 1), + ('Packing', c_uint8, 2), + ('MST', c_uint8, 1), + ('n_val', c_uint16), + ] + + _fields_ = [ + ('Sync', c_uint32), + ('Tns', c_uint32), + ('SourcId', c_uint32), + ('Length', c_uint32), + ('TimeStamp', c_uint64), + ('Sync', c_uint32), + ('Attribute', Attributes) + ] + + +class CaptureCaps(Structure): + _fields_ = [ + ('triggers', c_uint32, 1), + ('multipleTriggersSelection', c_uint32, 1), + ('multipleTriggersAndingSelection', c_uint32, 1), + ('alpmTriggers', c_uint32, 1), + ('', c_uint32, 4), + ('grab128_132', c_uint32, 1), + ('', c_uint32, 3), + ("bitGrabWidth", c_uint32, 4), + ("", c_uint32, 16) + ] + + +class SBlock(Structure): + _fields_ = [ + ('BLOCK', c_uint32), + ('OFFSET', c_uint64), + ('SIZE', c_uint64), + ] diff --git a/UniTAP/dev/ports/modules/capturer/bulk/result_bulk.py b/UniTAP/dev/ports/modules/capturer/bulk/result_bulk.py new file mode 100644 index 0000000..ff23782 --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/bulk/result_bulk.py @@ -0,0 +1,60 @@ +import os +import warnings +import shutil +from ctypes import sizeof +from datetime import datetime +from UniTAP.dev.modules.capturer.result_object import ResultObject +from UniTAP.dev.modules.capturer.types import CapturedDataType +from UniTAP.dev.ports.modules.capturer.bulk.private_bulk_types import BulkHeader + + +class ResultBulkObject(ResultObject): + """ + Class `ResultBulkObject` inherited from class `ResultObject`. + Class `ResultBulkObject` allows saving captured data to file `save_to_bin_file`. + Also has all the `ResultObject` functionality. + """ + def __init__(self, assume_scrambler_disabled: bool = False): + super().__init__() + self.assume_scrambler_disabled = assume_scrambler_disabled + + def save_to_bin_file(self, directory_name: str): + """ + Saving captured bulk data to file. + + Args: + directory_name (str) - path to save + """ + if len(self.buffer) > 0: + if not os.path.exists(directory_name): + os.makedirs(directory_name) + else: + shutil.rmtree(directory_name) + os.makedirs(directory_name) + + main_link_file = f'capture_{datetime.now().strftime("%Y%m%d_%H%M%S")}.mainlink.bin' + events_file = f'capture_{datetime.now().strftime("%Y%m%d_%H%M%S")}.events.bin' + for data in self.buffer: + if data.type == CapturedDataType.Event: + e_file = open(os.path.join(directory_name, events_file), 'a+b') + sync = 0x0B41550B + e_file.write(sync.to_bytes(length=4, byteorder='little')) + e_file.write(0x0.to_bytes(length=4, byteorder='little')) + e_file.write(int(data.data[13] & 0xF).to_bytes(length=4, byteorder='little')) + e_file.write(int((len(data.data) - 12) / 4).to_bytes(length=4, byteorder='little')) + e_file.write(data.data[12:]) + elif data.type == CapturedDataType.Bulk: + b_file = open(os.path.join(directory_name, main_link_file), 'a+b') + if len(data.data) > sizeof(BulkHeader): + header = BulkHeader.from_buffer(data.data) + if self.assume_scrambler_disabled: + header.Attribute.Packing |= 0x2 + b_file.write(bytearray(header)) + b_file.write(data.data[sizeof(BulkHeader):]) + else: + warnings.warn("Buffer size is equal 0, without bulk data.") + + def __str__(self): + return f"Start capture time: {self.start_capture_time}\n" \ + f"End capture time: {self.end_capture_time}\n" \ + f"Timestamp: {self.timestamp.__str__()}\n" diff --git a/UniTAP/dev/ports/modules/capturer/event/__init__.py b/UniTAP/dev/ports/modules/capturer/event/__init__.py new file mode 100644 index 0000000..a8e3415 --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/event/__init__.py @@ -0,0 +1,2 @@ +from .event_types import EventSDP, EventLinkPattern, EventVBID, EventMSA, EventInfoFrame, EventLCE, EventFileFormat, \ + EventFilterDpRx, EventFilterDpTx, EventFilterHdRx, EventFilterHdTx, EventFilterUsbc diff --git a/UniTAP/dev/ports/modules/capturer/event/event_capturer.py b/UniTAP/dev/ports/modules/capturer/event/event_capturer.py new file mode 100644 index 0000000..dfbf2a7 --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/event/event_capturer.py @@ -0,0 +1,205 @@ +import time +import warnings +import copy + +from typing import Union, TypeVar, Type, List + +from UniTAP.libs.lib_tsi.tsi import TSIX_TS_SetPortConfigItem +from UniTAP.dev.modules.capturer.capture import Capturer, CaptureConfig +from UniTAP.dev.modules.capturer.statuses import EventCaptureStatus +from .result_event import ResultEventObject +from .event_types import EventFilterDpRx, EventFilterDpTx, EventFilterHdRx, EventFilterHdTx, EventFilterUsbc, EventData +from UniTAP.version import __version__ + +EventFilterType = TypeVar("EventFilterType", + EventFilterDpRx, + EventFilterDpTx, + EventFilterHdRx, + EventFilterHdTx, + EventFilterUsbc + ) + + +class EventCapturer: + """ + Class `EventCapturer` allows working with capturing events on Sink (RX - receiver) side. + You can `start` capturing in several modes, `stop` capturing, getting current `status` and result of capturing + `capture_result`. + """ + + def __init__(self, capturer: Capturer, port_id: int, event_filter: list): + self.__device = capturer.device + self.__capturer = capturer + self.__status = EventCaptureStatus.Unknown + + device_name, serial_number, fw_version, front_end, memory_size = \ + capturer.device.get_prepared_fw_info().split("|") + + self.__fw_info = {"bundle_version": capturer.device.get_bundle_version(), + "app_version": __version__, + "frontend_version": front_end, + "memory_size": memory_size, + "ucd_device": f"{device_name} [{serial_number.replace(' ','')}]", + "ucd_fw_version": fw_version} + self.__result = ResultEventObject(self.__fw_info) + self.__event_filter = event_filter + self.__port_id = port_id + + @property + def status(self) -> EventCaptureStatus: + """ + Returns current event capturer status. + + Returns: + object of `VideoCaptureStatus` type + """ + return self.__capturer.event_capturer_status + + @property + def capture_result(self) -> ResultEventObject: + """ + Returns result of event capturing. + + Returns: + object of `ResultEventObject` type + """ + return self.__result + + def event_filter(self, event_filter_type: Type[EventFilterType]) -> EventFilterType: + """ + Returns event filter for current `EventCapturer`. + + Returns: + object of one of available [EventFilterDpRx, EventFilterDpTx, EventFilterHdRx, EventFilterHdTx, + EventFilterUsbc] type + """ + for item in self.__event_filter: + if isinstance(item, event_filter_type): + return item + available_variants = '\n'.join([str(type(e)) for e in self.__event_filter]) + raise TypeError(f"Unsupported type of EventFilter: {event_filter_type}. Available variants:\n" + f"{available_variants}") + + def configure_capturer(self, event_filter: Union[EventFilterDpRx, EventFilterDpTx, EventFilterHdRx, EventFilterHdTx, + EventFilterUsbc]): + """ + Configure + + Args: + event_filter (Union[EventFilterDpRx, EventFilterDpTx, EventFilterHdRx, EventFilterHdTx, EventFilterUsbc]) + """ + if not self.__check_event_filter_type(event_filter): + available_variants = '\n'.join([str(type(e)) for e in self.__event_filter]) + raise TypeError(f"Unsupported type of EventFilter: {type(event_filter)}. " + f"Available variants:\n{available_variants}") + + for i, item in enumerate(self.__event_filter): + if isinstance(item, type(event_filter)): + self.__event_filter[i] = event_filter + + for item in event_filter.additional_filter: + TSIX_TS_SetPortConfigItem(self.__device.device_handle, self.__port_id, item.ci_control, item.value, + data_count=len(item.value) if isinstance(item.value, list) else 1) + + TSIX_TS_SetPortConfigItem(self.__device.device_handle, self.__port_id, event_filter._control_ci, + event_filter.config) + + def clear_capturer_config(self): + """ + Clear event captuter configuration (filter). + """ + for item in self.__event_filter: + item.clear() + TSIX_TS_SetPortConfigItem(self.__device.device_handle, self.__port_id, item._control_ci, 0) + + def start(self, sec=0, n_elements=0): + """ + Start capturing. Possible some variants of capturing: \n + - Capture with fixed event count (will be captured fixed event count and capturing will be stopped) + - Capture with fixed time (capturing will be continued fixed seconds and capturing will be stopped). + - Capture without parameters Live capturing (for getting events you need to use functions `pop_element` and + `pop_all_elements`). Here you need to manually call the `stop` after capture. + + All results can be obtained using the function `capture_result`. + + Args: + n_elements (int) + sec (int) + """ + config = CaptureConfig() + config.event = True + config.type = CaptureConfig.Type.LIVE + + self.__capturer.start_event_capture() + + self.__capturer.start_capture(config) + + self.__result.clear() + self.__result.start_capture_time = time.time() + + if n_elements > 0: + self.__result.buffer.extend(self.__capturer.capture_n_events(n_elements)) + self.stop() + elif sec > 0: + time.sleep(sec) + self.stop() + self.__result.buffer.extend(self.__capturer.read_all_events()) + + def stop(self): + """ + Stop capture events. + """ + config = CaptureConfig() + config.event = True + config.type = CaptureConfig.Type.LIVE + + self.__capturer.stop_event_capture() + self.__capturer.stop_capture(config) + self.__result.end_capture_time = time.time() + + def pop_element(self) -> EventData: + """ + Return first captured object of `EventData`. + + Returns: + object of `EventData` type or `ResultEventObject` + """ + if self.status == EventCaptureStatus.Stop: + warnings.warn("Event capture is not working now. Please, turn it on.") + captured_events = self.__capturer.capture_n_events(1) + self.__result.buffer.extend(copy.deepcopy(captured_events)) + + return captured_events[0] + + def __get_events_count(self) -> int: + return self.__capturer.get_available_events_count() + + def __check_event_filter_type(self, event_filter: Union[EventFilterDpRx, EventFilterDpTx, EventFilterHdRx, + EventFilterHdTx, EventFilterUsbc]) -> bool: + for item in self.__event_filter: + if isinstance(item, type(event_filter)): + return True + return False + + def pop_all_elements(self) -> List[EventData]: + """ + Return all captured event frames(objects of `EventData`). + + Returns: + object of list[`EventData`] type + """ + + return self.__capturer.read_all_events() + + def pop_all_elements_as_result_object(self) -> ResultEventObject: + """ + Return all captured event frames(objects of `EventData`) as `ResultEventObject`. + + Returns: + object of `ResultEventObject` type + """ + captured_events = self.__capturer.read_all_events() + + res = ResultEventObject(self.__fw_info) + res.buffer.extend(captured_events) + return res diff --git a/UniTAP/dev/ports/modules/capturer/event/event_report_template.py b/UniTAP/dev/ports/modules/capturer/event/event_report_template.py new file mode 100644 index 0000000..afbd3d7 --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/event/event_report_template.py @@ -0,0 +1,235 @@ +event_entry_template = """ +var TxtEntry{$num} = [ + 'Event Details', + '{$num} {$event_name} ', + '{$time}
Entry type {$entry_type}
Sent from {$send_from}
', + '{$event_data}

',] +var ShortTxtEntry{$num} = [ + '', + '

'] +var Entry{$num} = [ + EventProto, + TxtEntry{$num}, + ShortEventProto, + ShortTxtEntry{$num} ] +""" + +event_table_row = '' + + +events_template = """ +UCD Console event monitoring report + + + + +
+

  {$report_name}

+
+
+ + + + + + + +
+

+    Transactions and events +

+

+ Click entry to see details

+

+  # TimestampSource Type Info + + +

+ +

+ +
+
+   +
+
+
+ + + +""" + +packet_entry_template = """ +var TxtEntry{$num} = [ + 'Event Details', + '{$num} {$event_name} ', + '{$time}
Entry type {$entry_type}
Sent from {$send_from}
', + '{$hex}', + '{$event_data}

',] +var ShortTxtEntry{$num} = [ + '
{$num_just} {$time}   {$send_from_just} {$type_just} {$event_name}
', + '{$hex_just}

'] +var Entry{$num} = [ + PacketProto, + TxtEntry{$num}, + ShortPacketProto, + ShortTxtEntry{$num} ] + +""" \ No newline at end of file diff --git a/UniTAP/dev/ports/modules/capturer/event/event_types.py b/UniTAP/dev/ports/modules/capturer/event/event_types.py new file mode 100644 index 0000000..6ac7024 --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/event/event_types.py @@ -0,0 +1,774 @@ +from .event_utils import set_bit_in_vector, search_in_list +from .private_event_types import AdditionalSDPEventFilter, AdditionalLinkPatternEventFilter, \ + AdditionalInfoFrameEventFilter, AdditionalVBIDEventFilter, AdditionalMSAEventFilter, AdditionalLSEEventFilter +from UniTAP.libs.lib_tsi.tsi_types import * +import warnings + + +class EventFileFormat(IntEnum): + """ + Describe all supported file formats for saving events: + - BIN. + - TXT (Support will be added later). + """ + UNKNOWN = -1 + BIN = 0 + TXT = 1 + HTML = 2 + CSV = 3 + + +class EventSDP(IntEnum): + """ + Describe all supported SDP packets types: + """ + AudioTimeStamp = 0x1 + AudioStream = 0x2 + Extension = 0x4 + AudioCopyManagement = 0x5 + ISRC = 0x6 + VSC = 0x7 + CG0 = 0x8 + CG1 = 0x9 + CG2 = 0xA + CG3 = 0xB + CG4 = 0xC + CG5 = 0xD + CG6 = 0xE + CG7 = 0xF + PictureParamSet = 0x10 + VSC_EXT_VESA = 0x20 + VSC_EXT_CTA = 0x21 + Adaptive_Sync = 0x22 + VS = 0x81 + AVI = 0x82 + SPD = 0x83 + Audio = 0x84 + MPEG = 0x85 + NTSC_VBI = 0x86 + DRM = 0x87 + + +class EventLinkPattern(IntEnum): + """ + Describe all supported Link Pattern packets types: + """ + TPS1Begin = 0 + TPS1End = 1 + TPS2Begin = 2 + TPS2End = 3 + TPS3Begin = 4 + TPS3End = 5 + TPS4Begin = 6 + TPS4End = 7 + IdleBegin = 10 + IdleEnd = 11 + ActiveBegin = 12 + ActiveEnd = 13 + SleepBegin = 14 + SleepEnd = 15 + StandbyBegin = 16 + StandbyEnd = 17 + EIEOSBegin = 18 + EIEOSEnd = 19 + CustomBegin = 20 + CustomEnd = 21 + Begin25201 = 22 + End25201 = 23 + Begin25202 = 24 + End25202 = 25 + PRBS7Begin = 26 + PRBS7End = 27 + PRBS31Begin = 28 + PRBS31End = 29 + + +class EventVBID(IntEnum): + """ + Describe all supported VBID packets types: + """ + SetVBlank = 0x1 + ClearVBlank = 0x2 + AnyVBlank = 0x3 + SetFieldID = 0x4 + CleatFieldID = 0x8 + AnyFieldID = 0xC + SetInterface = 0x10 + CleatInterface = 0x20 + AnyInterface = 0x30 + SetNoVideo = 0x40 + CleatNoVideo = 0x80 + AnyNoVideo = 0xC0 + SetNoAudio = 0x100 + CleatNoAudio = 0x200 + AnyNoAudio = 0x300 + SetHDCPSYNC = 0x400 + CleatHDCPSYNC = 0x800 + AnyHDCPSYNC = 0xC00 + SetCompressed = 0x1000 + CleatCompressed = 0x2000 + AnyCompressed = 0x3000 + SetReserved = 0x4000 + CleatReserved = 0x8000 + AnyReserved = 0xC000 + MVID = 0x10000 + MAUD = 0x20000 + + +class EventMSA(IntEnum): + """ + Describe all supported MSA packets types: + """ + MVID = 0x1 + NVID = 0x2 + HTOTAL = 0x4 + VTOTAL = 0x8 + HSTART = 0x10 + VSTART = 0x20 + HSP = 0x40 + HSW = 0x80 + VSP = 0x100 + VSW = 0x200 + HWIDTH = 0x400 + VHEIGHT = 0x800 + MISC0 = 0x1000 + MISC1 = 0x2000 + + +class EventInfoFrame(IntEnum): + """ + Describe all supported Info Frame packets types: + """ + VS = 0x81 + AVI = 0x82 + SPD = 0x83 + Audio = 0x84 + MPEG = 0x85 + NTSC_VBI = 0x86 + DRM = 0x87 + + +class EventData: + """ + Class `EventData` describe one event. Contains following information: + - `source` + - `type` + - `brief` info + - `info` + - `time` + - `duration` + - `data` + """ + + def __init__(self): + self.__source = "" + self.__type = "" + self.__brief = "" + self.__info = "" + self.__time = 0 + self.__duration = 0 + self.__data = bytearray() + + @property + def source(self): + return self.__source + + @property + def type(self): + return self.__type + + @property + def brief(self): + return self.__brief + + @property + def info(self): + return self.__info + + @property + def time(self): + return self.__time + + @property + def duration(self): + return self.__duration + + @property + def data(self): + return self.__data + + @source.setter + def source(self, source: str): + self.__source = source + + @type.setter + def type(self, _type: str): + self.__type = _type + + @brief.setter + def brief(self, brief: str): + self.__brief = brief + + @info.setter + def info(self, info: str): + self.__info = info + + @time.setter + def time(self, time: int): + if time <= 0: + raise ValueError(f"Time must be more than 0.") + self.__time = time + + @duration.setter + def duration(self, duration: int): + if duration <= 0: + raise ValueError(f"Duration must be more than 0.") + self.__duration = duration + + @data.setter + def data(self, data: bytearray): + if len(data) <= 0: + raise ValueError(f"Data length must be more than 0.") + self.__data = data + + +class EventLCE: + """ + Describe settings for LCE USB-C events: + - `v_bus` + - `iv_bus` + - `vcc` + - `vsbu` + - `i_vconn` + """ + + def __init__(self, v_bus: int, iv_bus: int, vcc: int, vsbu: int, i_vconn: int): + self.__v_bus = v_bus + self.__iv_bus = iv_bus + self.__vcc = vcc + self.__vsbu = vsbu + self.__i_vconn = i_vconn + + @property + def v_bus(self): + return self.__v_bus + + @property + def iv_bus(self): + return self.__iv_bus + + @property + def vcc(self): + return self.__vcc + + @property + def vsbu(self): + return self.__vsbu + + @property + def i_vconn(self): + return self.__i_vconn + + @v_bus.setter + def v_bus(self, v_bus: int): + if v_bus <= 0: + raise ValueError(f"V bus must be more than 0.") + self.__v_bus = v_bus + + @iv_bus.setter + def iv_bus(self, iv_bus: int): + if iv_bus <= 0: + raise ValueError(f"I V bus must be more than 0.") + self.__iv_bus = iv_bus + + @vcc.setter + def vcc(self, vcc: int): + if vcc <= 0: + raise ValueError(f"VCC must be more than 0.") + self.__vcc = vcc + + @vsbu.setter + def vsbu(self, vsbu: int): + if vsbu <= 0: + raise ValueError(f"V sbu must be more than 0.") + self.__vsbu = vsbu + + @i_vconn.setter + def i_vconn(self, i_vconn: int): + if i_vconn <= 0: + raise ValueError(f"I vconn must be more than 0.") + self.i_vconn = i_vconn + + def values(self) -> list: + """ + Returns all values how list. + + Returns: + object of list type + """ + return [self.v_bus, self.iv_bus, self.vcc, self.vsbu, self.i_vconn] + + +def _set_bit_in_link_pattern(enable, data, bit_no: EventLinkPattern): + _list = list(EventLinkPattern) + _ind = _list.index(bit_no) + if 0 <= _ind <= 7: + if enable: + data[0] |= (1 << bit_no.value) + else: + data[0] &= ~(1 << bit_no.value) + elif 8 <= _ind <= 17: + if enable: + data[1] |= (1 << (bit_no.value - 10)) + else: + data[1] &= ~(1 << (bit_no.value - 10)) + else: + if enable: + data[2] |= (1 << (bit_no.value - 20)) + else: + data[2] &= ~(1 << (bit_no.value - 20)) + + +class EventFilter: + """ + Base class of all filters. + """ + + def __init__(self, hw_caps=None): + self.__config = 0 + self._control_ci = 0 + self._hw_caps = hw_caps + self.__additional_filter = [] + + @property + def config(self) -> int: + """ + Returns current configuration value of filter. + + Returns: + object of int type + """ + return self.__config + + @property + def additional_filter(self) -> list: + """ + Returns current additional filters for main filter. + + Returns: + object of list type + """ + return self.__additional_filter + + @config.setter + def config(self, config: int): + """ + Set configuration value of filter. + + Args: + config (int) + """ + if config < 0: + raise ValueError(f"Config value must be more than 0.") + self.__config = config + + @additional_filter.setter + def additional_filter(self, additional_filter: list): + """ + Set additional filters of main filter. + + Args: + additional_filter (list) + """ + self.__additional_filter = additional_filter + + def clear(self): + """ + Clear all config. + """ + self.config = 0 + + for item in self.additional_filter: + if isinstance(item.value, int): + item.value = 0 + elif isinstance(item.value, list): + item.value = [0] * len(item.value) + + +class EventFilterDpRx(EventFilter): + """ + Class `EventFilterDpRx` allows setting filter for DPRX available events: `config_hpd_events`, `config_aux_events`, + `config_sdp_events`, `config_link_pattern_events`, `config_vb_id_events`, `config_msa_events`, + `config_aux_bw_events`. + Inherited from class `EventFilter`. + """ + + def __init__(self, hw_caps): + super().__init__(hw_caps) + self._control_ci = TSI_DPRX_LOG_CONTROL + self.additional_filter = [AdditionalSDPEventFilter()] + if hw_caps.link_pat_log: + self.additional_filter.append(AdditionalLinkPatternEventFilter()) + if hw_caps.vbid_hw_log: + self.additional_filter.append(AdditionalVBIDEventFilter()) + if self._hw_caps.msa_hw_log: + self.additional_filter.append(AdditionalMSAEventFilter()) + + def config_hpd_events(self, enable: bool): + """ + Configure HDP events. + + Args: + enable (bool) - enable/disable HDP events + """ + if enable: + self.config |= TSI_DPRX_LOG_CTRL_VALUE_HPD + else: + self.config &= ~TSI_DPRX_LOG_CTRL_VALUE_HPD + + def config_aux_events(self, enable: bool): + """ + Configure AUX events. + + Args: + enable (bool) - enable/disable AUX events + """ + if enable: + self.config |= TSI_DPRX_LOG_CTRL_VALUE_AUX + else: + self.config &= ~TSI_DPRX_LOG_CTRL_VALUE_AUX + + def config_sdp_events(self, enable: bool, *args: EventSDP): + """ + Configure SDP events. + + Args: + enable (bool) - enable/disable SDP events + *args (`EventSDP`) - SDP packet types + """ + if enable: + self.config |= TSI_DPRX_LOG_CTRL_VALUE_SDP + else: + self.config &= ~TSI_DPRX_LOG_CTRL_VALUE_SDP + + if len(args) > 0: + for item in args: + if isinstance(item, EventSDP): + set_bit_in_vector(enable, self.additional_filter[0].value, item.value) + + def config_link_pattern_events(self, enable: bool, *args: EventLinkPattern): + """ + Configure Link Pattern events. + + Args: + enable (bool) - enable/disable Link Pattern events + *args (`EventLinkPattern`) - Link Pattern packet types + """ + if self._hw_caps is not None and self._hw_caps.link_pat_log: + if enable: + self.config |= TSI_DPRX_LOG_CTRL_VALUE_LINK_PAT + else: + self.config &= ~TSI_DPRX_LOG_CTRL_VALUE_LINK_PAT + + if len(args) > 0: + for item in args: + if isinstance(item, EventLinkPattern): + _set_bit_in_link_pattern(enable, + self.additional_filter[search_in_list(AdditionalLinkPatternEventFilter, + self.additional_filter)].value, + item) + else: + warnings.warn("HW does not support Link Pattern Logger") + + def config_vb_id_events(self, enable: bool, *args: EventVBID): + """ + Configure VB ID events. + + Args: + enable (bool) - enable/disable VB ID events + *args (`EventVBID`) - VB ID packet types + """ + if self._hw_caps is not None and self._hw_caps.vbid_hw_log: + if enable: + self.config |= TSI_DPRX_LOG_CTRL_VALUE_VBID_HW + else: + self.config &= ~TSI_DPRX_LOG_CTRL_VALUE_VBID_HW + + if len(args) > 0: + for item in args: + if isinstance(item, EventVBID): + if enable: + self.additional_filter[search_in_list(AdditionalVBIDEventFilter, + self.additional_filter)].value |= item.value + else: + self.additional_filter[search_in_list(AdditionalVBIDEventFilter, + self.additional_filter)].value &= ~item.value + else: + warnings.warn("HW does not support Vbid Hw Logger") + + def config_msa_events(self, enable: bool, *args: EventMSA): + """ + Configure MSA events. + + Args: + enable (bool) - enable/disable MSA events + *args (`EventMSA`) - MSA packet types + """ + if self._hw_caps is not None and self._hw_caps.msa_hw_log: + if enable: + self.config |= TSI_DPRX_LOG_CTRL_VALUE_MSA_HW + else: + self.config &= ~TSI_DPRX_LOG_CTRL_VALUE_MSA_HW + + if len(args) > 0: + for item in args: + if isinstance(item, EventMSA): + if enable: + self.additional_filter[search_in_list(AdditionalMSAEventFilter, + self.additional_filter)].value |= item.value + else: + self.additional_filter[search_in_list(AdditionalMSAEventFilter, + self.additional_filter)].value &= ~item.value + else: + warnings.warn("HW does not support Msa Hw Logger") + + def config_aux_bw_events(self, enable: bool): + """ + Configure AUX BW events. + + Args: + enable (bool) - enable/disable AUX BW events + """ + if self._hw_caps is not None and self._hw_caps.aux_bw_log: + if enable: + self.config |= TSI_DPRX_LOG_CTRL_VALUE_AUX_BW + else: + self.config &= ~TSI_DPRX_LOG_CTRL_VALUE_AUX_BW + else: + warnings.warn("HW does not support Aux Bw Logger") + + +class EventFilterDpTx(EventFilter): + """ + Class `EventFilterDpTx` allows setting filter for DPTX available events: `config_hpd_events`, `config_aux_events`. + Inherited from class `EventFilter`. + """ + + def __init__(self, hw_caps): + super().__init__(hw_caps) + self._control_ci = TSI_DPTX_LOG_CONTROL + + def config_hpd_events(self, enable: bool): + """ + Configure HDP events. + + Args: + enable (bool) - enable/disable HDP events + """ + if enable: + self.config |= TSI_DPTX_LOG_CONTROL_VALUE_HPD + else: + self.config &= ~TSI_DPTX_LOG_CONTROL_VALUE_HPD + + def config_aux_events(self, enable: bool): + """ + Configure AUX events. + + Args: + enable (bool) - enable/disable AUX events + """ + if enable: + self.config |= TSI_DPTX_LOG_CONTROL_VALUE_AUX + else: + self.config &= ~TSI_DPTX_LOG_CONTROL_VALUE_AUX + + +class EventFilterHdRx(EventFilter): + """ + Class `EventFilterHdRx` allows setting filter for HDRX available events: `config_hpd_events`, + `config_packets_events`, `config_i2c_events`, `config_cec_events`. + Inherited from class `EventFilter`. + """ + + def __init__(self, hw_caps): + super().__init__(hw_caps) + self.additional_filter = [AdditionalInfoFrameEventFilter()] + self._control_ci = TSI_HDRX_LOG_CONTROL + + def config_hpd_events(self, enable: bool): + """ + Configure HPD events. + + Args: + enable (bool) - enable/disable HPD events + """ + if enable: + self.config |= TSI_HDRX_LOG_CTRL_VALUE_HPD + else: + self.config &= ~TSI_HDRX_LOG_CTRL_VALUE_HPD + + def config_packets_events(self, enable: bool, *args: EventInfoFrame): + """ + Configure InfoFrame events. + + Args: + enable (bool) - enable/disable InfoFrame events + *args (`EventInfoFrame`) - InfoFrame packet types + """ + if enable: + self.config |= TSI_HDRX_LOG_CTRL_VALUE_INFO + else: + self.config &= ~TSI_HDRX_LOG_CTRL_VALUE_INFO + + if len(args) > 0: + for item in args: + if isinstance(item, EventInfoFrame): + set_bit_in_vector(enable, self.additional_filter[0].value, item.value) + + def config_i2c_events(self, enable: bool): + """ + Configure I2C events. + + Args: + enable (bool) - enable/disable I2C events + """ + if enable: + self.config |= TSI_HDRX_LOG_CTRL_VALUE_I2C + else: + self.config &= ~TSI_HDRX_LOG_CTRL_VALUE_I2C + + def config_cec_events(self, enable: bool): + """ + Configure CEC events. + + Args: + enable (bool) - enable/disable CEC events + """ + if enable: + self.config |= TSI_HDRX_LOG_CTRL_VALUE_CEC + else: + self.config &= ~TSI_HDRX_LOG_CTRL_VALUE_CEC + + +class EventFilterHdTx(EventFilter): + """ + Class `EventFilterHdTx` allows setting filter for HDTX available events: `config_hpd_events`, `config_i2c_events`, + `config_cec_events`. + Inherited from class `EventFilter`. + """ + + def __init__(self, hw_caps): + super().__init__(hw_caps) + self._control_ci = TSI_HDTX_LOG_CONTROL + + def config_hpd_events(self, enable: bool): + """ + Configure HPD events. + + Args: + enable (bool) - enable/disable HPD events + """ + if enable: + self.config |= TSI_HDTX_LOG_CTRL_VALUE_HPD + else: + self.config &= ~TSI_HDTX_LOG_CTRL_VALUE_HPD + + def config_i2c_events(self, enable: bool): + """ + Configure I2C events. + + Args: + enable (bool) - enable/disable I2C events + """ + if enable: + self.config |= TSI_HDTX_LOG_CTRL_VALUE_I2C + else: + self.config &= ~TSI_HDTX_LOG_CTRL_VALUE_I2C + + def config_cec_events(self, enable: bool): + """ + Configure CEC events. + + Args: + enable (bool) - enable/disable CEC events + """ + if enable: + self.config |= TSI_HDTX_LOG_CTRL_VALUE_CEC + else: + self.config &= ~TSI_HDTX_LOG_CTRL_VALUE_CEC + + +class EventFilterUsbc(EventFilter): + """ + Class `EventFilterUsbc` allows setting filter for USB-C available events: `config_pd_events`, + `config_voltage_events`, `config_usbc_events`, `config_port_state_events`. + Inherited from class `EventFilter`. + """ + + def __init__(self, hw_caps): + super().__init__(hw_caps) + self._control_ci = TSI_PDC_LOG_CONTROL + if hw_caps is not None and hw_caps.voltage: + self.additional_filter.append(AdditionalLSEEventFilter()) + + def config_pd_events(self, enable: bool): + """ + Configure PD events. + + Args: + enable (bool) - enable/disable PD events + """ + if enable: + self.config |= TSI_PDC_LOG_CTRL_VALUE_PD + else: + self.config &= ~TSI_PDC_LOG_CTRL_VALUE_PD + + def config_voltage_events(self, enable: bool, value: EventLCE): + """ + Configure LCE (voltage) events. + + Args: + enable (bool) - enable/disable LCE events + value (`EventLCE`) - LCE packet types + """ + if self._hw_caps is not None and self._hw_caps.voltage: + if enable: + self.config |= TSI_PDC_LOG_CTRL_VALUE_USBC_VOLTAGE + else: + self.config &= ~TSI_PDC_LOG_CTRL_VALUE_USBC_VOLTAGE + + for i in enumerate(value.values()): + self.additional_filter[search_in_list(AdditionalLSEEventFilter, + self.additional_filter)].value[i[0]] = i[1] + else: + warnings.warn("HW does not support 'LCE voltage' events") + + def config_usbc_events(self, enable: bool): + """ + Configure USB-C events. + + Args: + enable (bool) - enable/disable USB-C events + """ + if self._hw_caps is not None and self._hw_caps.events: + if enable: + self.config |= TSI_PDC_LOG_CTRL_VALUE_USBC_EVENT + else: + self.config &= ~TSI_PDC_LOG_CTRL_VALUE_USBC_EVENT + else: + warnings.warn("HW does not support 'USB-C' events") + + def config_port_state_events(self, enable: bool): + """ + Configure USB-C state events. + + Args: + enable (bool) - enable/disable USB-C state events + """ + if self._hw_caps is not None and self._hw_caps.states: + if enable: + self.config |= TSI_PDC_LOG_CTRL_VALUE_USBC_STATE + else: + self.config &= ~TSI_PDC_LOG_CTRL_VALUE_USBC_STATE + else: + warnings.warn("HW does not support 'USB-C Port State' events") diff --git a/UniTAP/dev/ports/modules/capturer/event/event_utils.py b/UniTAP/dev/ports/modules/capturer/event/event_utils.py new file mode 100644 index 0000000..b7ef859 --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/event/event_utils.py @@ -0,0 +1,193 @@ +import csv +import warnings +from .event_report_template import * + + +def set_bit_in_vector(enable, data, bit_no): + + bits_in_word = 32 + total_words = len(data) + selected_word = int(bit_no / bits_in_word) + bit_offset = bits_in_word - (((selected_word + 1) * bits_in_word) - bit_no) + + if selected_word >= total_words: + warnings.warn(f"Bit {bit_no} is out of provided vector's range {total_words} words") + return + + if enable: + data[selected_word] |= (1 << bit_offset) + else: + data[selected_word] &= ~(1 << bit_offset) + + +def search_in_list(_type, _list): + for item in enumerate(_list): + if isinstance(item[1], _type): + return item[0] + + return None + + +def byterarray_to_hex_str(data: bytearray) -> str: + return ' '.join(format(x, '02x') for x in data) + + +def content_to_str(data: str) -> str: + data = data.replace("\r\n", "
") + data = data.replace("\n\r", "
") + data = data.replace("\r", "
") + data = data.replace("\n", "
") + data = data.replace("'", "\\'") + data = data.replace(" ", "  ") + return data + + +def timestamp_to_str(value) -> str: + nsec = value % 1000 + value -= nsec + value /= 1000 + + mcsec = value % 1000 + value -= mcsec + value /= 1000 + + msec = value % 1000 + value -= msec + value /= 1000 + + sec = value % 60 + value -= sec + value /= 60 + + min_v = value % 60 + value -= min_v + + hour = value / 60 + + return "{:02d}:{:02d}:{:02d}.{:03d}.{:03d}.{:03d}".format(int(hour), int(min_v), int(sec), int(msec), int(mcsec), + int(nsec)) + + +def save_to_txt_file(path: str, events: list, index=None): + file = open(path, 'w') + if index is not None: + if len(events[index]) == 6: + file.write(f"{events[index].get('content')}\n{events[index].get('brief')}\n" + f"{byterarray_to_hex_str(events[index].get('data'))}\n" + f"{events[index].get('source')}\n") + else: + warnings.warn("Empty event") + else: + for data in events: + if len(data) == 6: + file.write(f"{data.get('content')}\n{data.get('brief')}\n" + f"{byterarray_to_hex_str(data.get('data'))}\n" + f"{data.get('source')}\n") + else: + warnings.warn("Empty event") + + file.close() + + +def save_to_bin_file(path: str, events: list, index=None): + file = open(path, 'wb') + if index is not None: + data = events[index].data + if len(data) > 14: + sync = 0x0B41550B + file.write(sync.to_bytes(length=4, byteorder='little')) + file.write(0x0.to_bytes(length=4, byteorder='little')) + file.write(int(data[13] & 0xF).to_bytes(length=4, byteorder='little')) + file.write(int((len(data) - 12) / 4).to_bytes(length=4, byteorder='little')) + file.write(data[12:]) + else: + for data in events: + if len(data.data) > 14: + sync = 0x0B41550B + file.write(sync.to_bytes(length=4, byteorder='little')) + file.write(0x0.to_bytes(length=4, byteorder='little')) + file.write(int(data.data[13] & 0xF).to_bytes(length=4, byteorder='little')) + file.write(int((len(data.data) - 12) / 4).to_bytes(length=4, byteorder='little')) + file.write(data.data[12:]) + file.close() + + +def save_to_csv_file(path: str, events: list): + with (open(path, 'w', newline='') as file): + writer = csv.writer(file, delimiter=';') + writer.writerow(["Source", "Type", "Start", "Brief info", "Full info"]) + for data in events: + if len(data) == 6: + processed_info = data.get('content')[:-1] + '.' + processed_info = processed_info.replace("\n", "; ") + + writer.writerow([ + data.get('source'), + data.get('type'), + timestamp_to_str(data.get('timestamp')), + data.get('brief'), + f'"{processed_info}"' + ]) + else: + warnings.warn("Empty event") + + + +def save_to_html_file(path: str, events: list, fw_info: dict, long_type_string: bool): + file = open(path, 'w') + + new_events_template = events_template.replace("{$report_name}", "Unigraf UCD Console event monitoring report").\ + replace("{$bundle_version}", fw_info.get('bundle_version')).\ + replace("{$app_version}", fw_info.get('app_version')).\ + replace("{$frontend_version}", fw_info.get('frontend_version')).\ + replace("{$memory_size}", fw_info.get('memory_size')).\ + replace("{$ucd_device}", fw_info.get('ucd_device')).\ + replace("{$ucd_fw_version}", fw_info.get('ucd_fw_version')).\ + replace("{$remarks}", '').\ + replace("{$tested}", "") + + events_data = [] + entries_list = [] + entries_rows = [] + + for index, item in enumerate(events): + + new_entry_value = event_entry_template.replace("{$event_name}", item.get('brief')).\ + replace("{$time}", f'{timestamp_to_str(item.get("timestamp"))}').\ + replace("{$entry_type}", "").\ + replace("{$send_from}", item.get('type')).\ + replace("{$num}", f'{index}').\ + replace("{$hex}", byterarray_to_hex_str(item.get('data'))).\ + replace("{$hex_just}", byterarray_to_hex_str(item.get('data'))).\ + replace("{$event_data}", content_to_str(item.get('content'))).\ + replace("{$num_just}", f"{index}".ljust(6, " ").replace(" ", " ")).\ + replace("{$send_from_just}", f"{item.get('source')}".ljust(10, " ").replace(" ", " ")).\ + replace("{$type_just}", f"{item.get('type')}".ljust(16 if long_type_string else 8, " ").replace(" ", " ")).\ + replace("{$text_color}", "#000000").\ + replace("{$back_color}", '#ffffff').\ + replace("{$font_bold}", "").\ + replace("{$font_italic}", "").\ + replace("{$font_family}", "") + + new_event_table_row = event_table_row.replace("{$text_color}", "#000000").\ + replace("{$back_color}", '#ffffff').\ + replace("{$font_family}", "").\ + replace("{$font_italic}", "").\ + replace("{$font_bold}", "").\ + replace("{$num}", f'{index}').\ + replace("{$num_just}", f"{index}".ljust(6, " ").replace(" ", " ")).\ + replace("{$time}", f'{timestamp_to_str(item.get("timestamp"))}').\ + replace("{$send_from_just}", f"{item.get('source')}".ljust(10, " ").replace(" ", " ")).\ + replace("{$type_just}", f"{item.get('type')}".ljust(16 if long_type_string else 8, " ").replace(" ", " ")).\ + replace("{$event_name}", item.get('brief')) + + events_data.append(new_entry_value) + entries_rows.append(new_event_table_row) + entries_list.append(f"Entry{index},") + + new_events_template = new_events_template.replace("{$events_data}", "\n".join(e for e in events_data)).\ + replace("{$events_list}", "\n".join(e for e in entries_list)).\ + replace("{$events_table}", "\n".join(e for e in entries_rows)) + + file.write(new_events_template) + file.close() diff --git a/UniTAP/dev/ports/modules/capturer/event/private_event_types.py b/UniTAP/dev/ports/modules/capturer/event/private_event_types.py new file mode 100644 index 0000000..a308d5c --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/event/private_event_types.py @@ -0,0 +1,75 @@ +from UniTAP.libs.lib_tsi.tsi_types import TSI_DPRX_LOG_IF_SEL, TSI_HDRX_LOG_IF_SEL, TSI_DPRX_LOG_CTRL_VALUE_LINK_PAT, \ + TSI_DPRX_VBID_LOG_SEL, TSI_DPRX_MSA_LOG_SEL, TSI_PDC_USBC_LOG_THRESH + + +class AdditionalEventFilter: + + def __init__(self): + self.__ci_control = 0 + self.__value = None + + @property + def ci_control(self): + return self.__ci_control + + @property + def value(self): + return self.__value + + @ci_control.setter + def ci_control(self, ci_control: int): + if ci_control <= 0: + raise ValueError(f"CI control must be more than 0.") + self.__ci_control = ci_control + + @value.setter + def value(self, value): + self.__value = value + + +class AdditionalSDPEventFilter(AdditionalEventFilter): + + def __init__(self): + super().__init__() + self.ci_control = TSI_DPRX_LOG_IF_SEL + self.value = [0] * 8 + + +class AdditionalInfoFrameEventFilter(AdditionalEventFilter): + + def __init__(self): + super().__init__() + self.ci_control = TSI_HDRX_LOG_IF_SEL + self.value = [0] * 8 + + +class AdditionalLinkPatternEventFilter(AdditionalEventFilter): + + def __init__(self): + super().__init__() + self.ci_control = TSI_DPRX_LOG_CTRL_VALUE_LINK_PAT + self.value = [0] * 3 + + +class AdditionalVBIDEventFilter(AdditionalEventFilter): + + def __init__(self): + super().__init__() + self.ci_control = TSI_DPRX_VBID_LOG_SEL + self.value = 0 + + +class AdditionalMSAEventFilter(AdditionalEventFilter): + + def __init__(self): + super().__init__() + self.ci_control = TSI_DPRX_MSA_LOG_SEL + self.value = 0 + + +class AdditionalLSEEventFilter(AdditionalEventFilter): + + def __init__(self): + super().__init__() + self.ci_control = TSI_PDC_USBC_LOG_THRESH + self.value = [0] * 5 diff --git a/UniTAP/dev/ports/modules/capturer/event/result_event.py b/UniTAP/dev/ports/modules/capturer/event/result_event.py new file mode 100644 index 0000000..4ebecf1 --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/event/result_event.py @@ -0,0 +1,108 @@ +import warnings + +from UniTAP.dev.modules.capturer.result_object import ResultObject +from UniTAP.libs.lib_tsi.tsi import TSIEventParser +from .event_utils import save_to_bin_file, save_to_csv_file, save_to_txt_file, save_to_html_file +from .event_types import EventFileFormat + + +class ResultEventObject(ResultObject): + """ + Class `ResultEventObject` inherited from class `ResultObject`. + Class `ResultEventObject` allows saving captured events to file `save_to_file_selected_event` or + `save_to_file_all_events`. + Also has all the `ResultObject` functionality. + """ + + def __init__(self, fw_info: dict): + super().__init__() + self.__parsed_buffer = [] + self.__fw_info = fw_info + self.__long_type_string = False + + def __str__(self): + return f"Start capture time: {self.start_capture_time}\n" \ + f"End capture time: {self.end_capture_time}\n" \ + f"Timestamp: {self.timestamp.__str__()}\n" + + def save_to_file_selected_event(self, file_format: EventFileFormat, path: str, index: int): + """ + + Saving selected event to file. Supported file formats describe in `EventFileFormat`. + + Args: + file_format (`PictureFileFormat`) - file format + path (str) - path to save + index (int) - number of event in list + """ + if len(self.buffer) > 0: + if file_format == EventFileFormat.BIN: + save_to_bin_file(path=path, events=self.buffer, index=index) + elif file_format == EventFileFormat.TXT: + if len(self.__parsed_buffer) == 0: + self.__parse_buffer() + save_to_txt_file(path=path, events=self.__parsed_buffer, index=index) + elif file_format == EventFileFormat.HTML: + raise NotImplementedError('Temporary not supported to txt files.') + else: + raise ValueError(f"Incorrect file format. Available formats: " + f"{EventFileFormat.BIN.name}, {EventFileFormat.TXT.name}, " + f"{EventFileFormat.HTML.name}.\n" + f"Transferred audio format: {file_format.name}") + self.__parsed_buffer.clear() + else: + warnings.warn("Buffer size is equal 0.") + + def save_to_file_all_events(self, file_format: EventFileFormat, path: str): + """ + + Saving all events to file. Supported file formats describe in `EventFileFormat`. + + Args: + file_format (`EventFileFormat`) - file format + path (str) - path to save + """ + if len(self.buffer) > 0: + if file_format == EventFileFormat.BIN: + if path.find(".bin") == -1: + path += ".bin" + save_to_bin_file(path=path, events=self.buffer) + elif file_format == EventFileFormat.CSV: + if path.find(".csv") == -1: + path += ".csv" + if len(self.__parsed_buffer) == 0: + self.__parse_buffer() + save_to_csv_file(path=path, events=self.__parsed_buffer) + elif file_format == EventFileFormat.TXT: + if path.find(".txt") == -1: + path += ".txt" + if len(self.__parsed_buffer) == 0: + self.__parse_buffer() + save_to_txt_file(path=path, events=self.__parsed_buffer) + elif file_format == EventFileFormat.HTML: + if path.find(".html") == -1: + path += ".html" + if len(self.__parsed_buffer) == 0: + self.__parse_buffer() + save_to_html_file(path=path, events=self.__parsed_buffer, fw_info=self.__fw_info, + long_type_string=self.__long_type_string) + else: + raise ValueError(f"Incorrect file format. Available formats: " + f"{EventFileFormat.BIN.name}, {EventFileFormat.TXT.name}, " + f"{EventFileFormat.HTML.name}.\n" + f"Transferred audio format: {file_format.name}") + self.__parsed_buffer.clear() + else: + warnings.warn("Buffer size is equal 0.") + + def __parse_buffer(self): + with TSIEventParser() as parser: + for index, item in enumerate(self.buffer): + if len(item.data) > 12: + res = parser.parse(item.data, to_dict=True) + if res[0] < 0: + warnings.warn(f"Cannot parse element {index}. Error {res[0]}") + else: + self.__parsed_buffer.append(res[1]) + if len(res[1].get('type')) > 8 and self.__long_type_string is False: + self.__long_type_string = True diff --git a/UniTAP/dev/ports/modules/capturer/video/__init__.py b/UniTAP/dev/ports/modules/capturer/video/__init__.py new file mode 100644 index 0000000..81331b6 --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/video/__init__.py @@ -0,0 +1 @@ +from .result_video import PictureFileFormat diff --git a/UniTAP/dev/ports/modules/capturer/video/result_video.py b/UniTAP/dev/ports/modules/capturer/video/result_video.py new file mode 100644 index 0000000..b98e4df --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/video/result_video.py @@ -0,0 +1,63 @@ +import warnings +from UniTAP.dev.modules.capturer.result_object import ResultObject +from UniTAP.dev.ports.modules.internal_utils.image_formats import PictureFileFormat, VideoFileFormat +from UniTAP.dev.ports.modules.internal_utils.image_utils import save_video_to_bin, save_video_to_mp4 +from UniTAP.utils.uicl_api import video_frame_save_to_file, ImageFileFormat + + +class ResultVideoObject(ResultObject): + """ + Class `ResultVideoObject` inherited from class `ResultObject`. + Class `ResultVideoObject` allows saving captured frames to image `save_image_to_file`. + Also has all the `ResultObject` functionality. + """ + def __init__(self): + super().__init__() + + def save_image_to_file(self, file_format: PictureFileFormat, path: str, index: int): + """ + + Saving selected video frame to file. Supported file formats describe in `PictureFileFormat`. + + Args: + file_format (`PictureFileFormat`) - file format + path (str) - path to save + index (int) - number of video frame in list + """ + if len(self.buffer) > 0: + if file_format == PictureFileFormat.BIN: + video_frame_save_to_file(video_frame=self.buffer[index], path=path, file_type=ImageFileFormat.IFF_BIN) + elif file_format == PictureFileFormat.BMP: + video_frame_save_to_file(video_frame=self.buffer[index], path=path, file_type=ImageFileFormat.IFF_BMP) + elif file_format == PictureFileFormat.PPM: + video_frame_save_to_file(video_frame=self.buffer[index], path=path, file_type=ImageFileFormat.IFF_PPM) + elif file_format == PictureFileFormat.DSC: + video_frame_save_to_file(video_frame=self.buffer[index], path=path, file_type=ImageFileFormat.IFF_DSC) + else: + raise ValueError(f"Incorrect image format. Available formats: " + f"{PictureFileFormat.BIN.name}, {PictureFileFormat.BMP.name}, " + f"{PictureFileFormat.PPM.name}.\n" + f"Transferred image format: {file_format.name}") + else: + warnings.warn("Buffer size is equal 0.") + + def __save_to_video_file(self, file_format: VideoFileFormat, path: str): + """ + Will be implemented later. + """ + if len(self.buffer) > 0: + if file_format == VideoFileFormat.BIN: + save_video_to_bin(path=path, data=self.buffer) + elif file_format == VideoFileFormat.MP4: + save_video_to_mp4(path=path, data=self.buffer) + else: + raise ValueError(f"Incorrect video format. Available formats: " + f"{VideoFileFormat.BIN.name}, {VideoFileFormat.MP4.name}.\n" + f"Transferred video format: {file_format.name}") + else: + warnings.warn("Buffer size is equal 0.") + + def __str__(self): + return f"Start capture time: {self.start_capture_time}\n" \ + f"End capture time: {self.end_capture_time}\n" \ + f"Timestamp: {self.timestamp.__str__()}\n" diff --git a/UniTAP/dev/ports/modules/capturer/video/video_capturer.py b/UniTAP/dev/ports/modules/capturer/video/video_capturer.py new file mode 100644 index 0000000..b4d3071 --- /dev/null +++ b/UniTAP/dev/ports/modules/capturer/video/video_capturer.py @@ -0,0 +1,224 @@ +import time +import warnings +import copy + +from typing import Union, List + +from UniTAP.dev.modules.capturer.capture import Capturer, CaptureConfig +from UniTAP.dev.modules.capturer.statuses import VideoCaptureStatus +from UniTAP.common import VideoFrame, VideoFrameDSC +from .result_video import ResultVideoObject + + +class VideoCapturer: + """ + Class `VideoCapturer` allows working with capturing video frames on Sink (RX - receiver) side. + You can `start` capturing in several modes, `stop` capturing, getting current `status` and result of + capturing `capture_result`. + """ + + def __init__(self, capturer: Capturer, max_stream_number: int): + self.__capturer = capturer + self.__result = ResultVideoObject() + self.__max_stream_number = max_stream_number + + @property + def status(self) -> VideoCaptureStatus: + """ + Returns current video capturer status. + + Returns: + object of `VideoCaptureStatus` type + """ + return self.__capturer.video_capturer_status + + @property + def capture_result(self) -> ResultVideoObject: + """ + Returns result of video capturing. + + Returns: + object of `ResultVideoObject` type + """ + return self.__result + + @property + def max_stream_number(self) -> int: + """ + Returns max stream number supported for capturing. + + Returns: + object of `int` type + """ + return self.__max_stream_number + + def stop(self): + """ + Stop capture video. + """ + config = CaptureConfig() + config.video = True + config.type = CaptureConfig.Type.LIVE + + self.__capturer.stop_capture(config) + self.__result.end_capture_time = time.time() + + def pop_element(self) -> Union[VideoFrame, VideoFrameDSC]: + """ + Return first object of `VideoFrame` or `VideoFrameDSC`. + + Returns: + object of `VideoFrame` or `VideoFrameDSC` type + """ + if self.status == VideoCaptureStatus.Idle: + warnings.warn("Video capture is not working now. Please, turn it on.") + captured_video_frames = self.__capturer.capture_video_by_n_frames(1) + self.__result.buffer.extend(copy.deepcopy(captured_video_frames)) + return captured_video_frames[0] + + def pop_element_as_result_object(self) -> ResultVideoObject: + """ + Return captured video frame(objects of `VideoFrame` or VideoFrameDSC`) as `ResultVideoObject`. + + Returns: + object of `ResultVideoObject` type + """ + available_frame_count = self.__capturer.get_available_video_frame_count() + captured_video_frames = self.__capturer.capture_video_by_n_frames(available_frame_count) + + res = ResultVideoObject() + res.buffer.extend(captured_video_frames) + return res + + def pop_all_elements(self) -> Union[List[VideoFrame], List[VideoFrameDSC]]: + """ + Return all captured video frames(objects of `VideoFrame` or `VideoFrameDSC`). + + Returns: + object of list[`VideoFrame` or `VideoFrameDSC`] type + """ + if self.status == VideoCaptureStatus.Idle: + warnings.warn("Video capture is not working now. Please, turn it on.") + available_frame_count = self.__capturer.get_available_video_frame_count() + print(f"Available frames count {available_frame_count}") + captured_video_frames = self.__capturer.capture_video_by_n_frames(available_frame_count) + self.__result.buffer.extend(copy.deepcopy(captured_video_frames)) + return captured_video_frames + + def get_crc(self, crc_frame_count: int = 1) -> List[tuple[int, int, int]]: + """ + Returns captured crc values. + + Returns: + list[tuple[int, int, int]] + """ + return self.__capturer.capture_crc(crc_frame_count) + + +class VideoCapturerDP(VideoCapturer): + """ + Class `VideoCapturerDP` inherited from class `VideoCapturer` and also allows working with capturing video frames + on DP Sink (RX - receiver) side. + You can `start` capturing in several modes, `stop` capturing, getting current `status` and result of + capturing `capture_result`. + """ + + def __init__(self, capturer: Capturer, max_stream_number: int): + super().__init__(capturer, max_stream_number) + self.__capturer = capturer + + def start(self, frames_count: int = 0, sec: int = 0, stream_number: int = 0, + capture_type: CaptureConfig.Type = CaptureConfig.Type.LIVE): + """ + Start capturing. Possible some variants of capturing: + - Capture with fixed frames count (will be captured fixed frames count and capturing will be stopped). + - Capture with fixed time (capturing will be continued fixed seconds and capturing will be stopped). + - Capture without parameters - Live capturing (for getting frames you need to use functions `pop_element` and + `pop_all_elements`) + + All results can be obtained using the function `capture_result`. + + Args: + frames_count (int) + sec (int) + stream_number (int) + capture_type (CaptureConfig.Type) + """ + + if stream_number >= self.max_stream_number: + assert ValueError(f"Incorrect index of stream. Value must be from available options: " + f"{', '.join([str(i) for i in range(self.max_stream_number)])}") + + self.__capturer.set_video_stream_number(stream_number) + + config = CaptureConfig() + config.video = True + config.type = capture_type + + self.__capturer.start_capture(config) + + self.capture_result.clear() + self.capture_result.start_capture_time = time.time() + + if frames_count > 0: + self.capture_result.buffer.extend(self.__capturer.capture_video_by_n_frames(frames_count, capture_type)) + elif sec > 0: + self.capture_result.buffer.extend(self.__capturer.capture_video_by_n_sec(sec)) + + + def get_buffer_capacity(self, stream_number: int = 0): + return self.__capturer.get_buffer_capacity(stream_number) + + +class VideoCapturerHDMI(VideoCapturer): + """ + Class `VideoCapturerHDMI` inherited from class `VideoCapturer` and also allows working with capturing video frames + on HDMI Sink (RX - receiver) side. + You can `start` capturing in several modes, `stop` capturing, getting current `status` and result of + capturing `capture_result`. + """ + + def __init__(self, capturer: Capturer, max_stream_number: int): + super().__init__(capturer, max_stream_number) + self.__capturer = capturer + + def start(self, frames_count: int = 0, sec: int = 0, stream_number: int = 0): + """ + Start capturing. Possible some variants of capturing: + - Capture with fixed frames count (will be captured fixed frames count and capturing will be stopped). + - Capture with fixed time (capturing will be continued fixed seconds and capturing will be stopped). + - Capture without parameters - Live capturing (for getting frames you need to use functions `pop_element` and + `pop_all_elements`) + + All results can be obtained using the function `capture_result`. + + Args: + frames_count (int) + sec (int) + stream_number (int) + """ + + if stream_number >= self.max_stream_number: + assert ValueError(f"Incorrect index of stream. Value must be from available options: " + f"{', '.join([str(i) for i in range(self.max_stream_number)])}") + + config = CaptureConfig() + config.video = True + config.type = CaptureConfig.Type.LIVE + + self.__capturer.start_capture(config) + + self.capture_result.clear() + self.capture_result.start_capture_time = time.time() + + if frames_count > 0: + if frames_count > 1: + self.capture_result.buffer.extend(self.__capturer.capture_video_by_n_frames(frames_count)) + else: + self.capture_result.buffer.extend(self.__capturer.capture_video_by_n_frames(frames_count)) + elif sec > 0: + self.capture_result.buffer.extend(self.__capturer.capture_video_by_n_sec(sec)) + + + def get_buffer_capacity(self): + return self.__capturer.get_buffer_capacity() diff --git a/UniTAP/dev/ports/modules/cec/__init__.py b/UniTAP/dev/ports/modules/cec/__init__.py new file mode 100644 index 0000000..5b7be48 --- /dev/null +++ b/UniTAP/dev/ports/modules/cec/__init__.py @@ -0,0 +1,2 @@ +from .cec_types import (CECStatus, DeviceType, CecResetDataCommand, LogicalAddressEnum, CECCommand, CECCommand, + DeviceTypeEnum) diff --git a/UniTAP/dev/ports/modules/cec/cec_private_types.py b/UniTAP/dev/ports/modules/cec/cec_private_types.py new file mode 100644 index 0000000..5604ad5 --- /dev/null +++ b/UniTAP/dev/ports/modules/cec/cec_private_types.py @@ -0,0 +1,41 @@ +from ctypes import Structure, c_uint32, c_uint16 +from .cec_types import DeviceType + + +class DeviceTypePrivate(Structure): + _fields_ = [ + ("reserved", c_uint32, 2), + ("cecSwitch", c_uint32, 1), + ('audioSystem', c_uint32, 1), + ('pbDevice', c_uint32, 1), + ('tuner', c_uint32, 1), + ('recDev', c_uint32, 1), + ('tv', c_uint32, 1), + ] + + @property + def value(self) -> int: + return (self.reserved | self.cecSwitch << 2 | self.audioSystem << 3 | self.pbDevice << 4 | self.tuner << 5 + | self.recDev << 6 | self.tv << 7) + + @value.setter + def value(self, dev_type: DeviceType): + self.cecSwitch = dev_type.cec_switch + self.audioSystem = dev_type.audio_system + self.pbDevice = dev_type.pb_device + self.tuner = dev_type.tuner + self.recDev = dev_type.rec_dev + self.tv = dev_type.tv + + +class CECStatusPrivate(Structure): + _fields_ = [ + ("bufferOverflowed", c_uint32, 1) + ] + + +class CECVersion(Structure): + _fields_ = [ + ("minor", c_uint16), + ("major", c_uint16) + ] diff --git a/UniTAP/dev/ports/modules/cec/cec_rx.py b/UniTAP/dev/ports/modules/cec/cec_rx.py new file mode 100644 index 0000000..0ef45e4 --- /dev/null +++ b/UniTAP/dev/ports/modules/cec/cec_rx.py @@ -0,0 +1,269 @@ +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from UniTAP.libs.lib_tsi.tsi import * +from .cec_types import * +from .cec_private_types import * +from typing import Union + + +class CecRx: + + def __init__(self, port_io: PortIO): + self.__io = port_io + self.__version = self.__read_version() + self.__commands = [] + + @property + def version(self) -> (int, int): + """ + Read CEC version from the RX side. + + Returns: + object of tuple of `int` type + """ + return self.__version + + def enable(self, state: bool): + """ + Enable/Disable CEC. + + Args: + state (`bool`) + """ + self.__io.set(TSI_HDRX_CEC_CONTROL, state, c_uint32) + + def is_enabled(self) -> bool: + """ + Return state of CEC (enabled or not). + + Returns: + object of `bool` type + """ + return self.__io.get(TSI_HDRX_CEC_CONTROL, c_uint32)[1] & 0x1 == 1 + + @property + def logical_address(self) -> LogicalAddressEnum: + """ + Set/Get CEC logical address. + + Returns: + object of `LogicalAddressEnum` type + """ + return LogicalAddressEnum(self.__io.get(TSI_HDRX_CEC_LOGICAL_ADDRESS, c_uint32)[1]) + + @logical_address.setter + def logical_address(self, address: LogicalAddressEnum): + self.__io.set(TSI_HDRX_CEC_LOGICAL_ADDRESS, address.value, c_uint32) + + @property + def destination(self) -> LogicalAddressEnum: + """ + Set/Get CEC destination address. + + Returns: + object of `LogicalAddressEnum` type + """ + return LogicalAddressEnum(self.__io.get(TSI_HDRX_CEC_DESTINATION, c_uint32)[1]) + + @destination.setter + def destination(self, address: LogicalAddressEnum): + self.__io.set(TSI_HDRX_CEC_DESTINATION, address.value, c_uint32) + + @property + def phy_address(self) -> int: + """ + Set/Get CEC physical address. + + Returns: + object of `int` type + """ + return self.__io.get(TSI_HDRX_CEC_PHYSICAL_ADDRESS, c_uint32)[1] + + @phy_address.setter + def phy_address(self, address: int): + self.__io.set(TSI_HDRX_CEC_PHYSICAL_ADDRESS, address, c_uint32) + + @property + def op_code(self) -> int: + """ + Set/Get CEC operation code. + + Returns: + object of `int` type + """ + return self.__io.get(TSI_HDRX_CEC_OP_CODE, c_uint32)[1] + + @op_code.setter + def op_code(self, code: int): + self.__io.set(TSI_HDRX_CEC_OP_CODE, code, c_uint32) + + @property + def op_code_param(self) -> Union[int, list]: + """ + Set/Get CEC additional parameter for operation code. + + Returns: + object of `int` type + """ + return self.__io.get(TSI_HDRX_CEC_OP_CODE_PARAM, c_uint32)[1] + + @op_code_param.setter + def op_code_param(self, code_param: Union[int, list]): + data_count = len(code_param) if isinstance(code_param, list) else 1 + self.__io.set(TSI_HDRX_CEC_OP_CODE_PARAM, code_param, c_uint32, data_count=data_count) + + @property + def device_type(self) -> DeviceType: + """ + Set/Get CEC Device type. + + Returns: + object of `DeviceType` type + """ + data = self.__io.get(TSI_HDRX_CEC_DEVICE_TYPE, DeviceTypePrivate)[1] + return DeviceType(data.cecSwitch, data.audioSystem, data.pbDevice, data.tuner, data.recDev, data.tv) + + @device_type.setter + def device_type(self, dev_type: Union[DeviceTypeEnum, DeviceType, int]): + if isinstance(dev_type, DeviceType): + dev_type_value = DeviceTypePrivate() + dev_type_value.value = dev_type + value = dev_type_value.value + elif isinstance(dev_type, DeviceTypeEnum): + value = dev_type.value + elif isinstance(dev_type, int): + value = dev_type & 0xFF + else: + raise TypeError(f"Unsupported type: {type(dev_type)}") + self.__io.set(TSI_HDRX_CEC_DEVICE_TYPE, value, c_uint32) + + @property + def status(self) -> CECStatus: + """ + Get CEC status. + + Returns: + object of `CECStatus` type + """ + data = self.__io.get(TSI_HDRX_CEC_STATUS_R, CECStatusPrivate)[1] + return CECStatus(data.bufferOverflowed) + + @property + def command_number(self) -> int: + """ + Get CEC command number. + + Returns: + object of `int` type + """ + return self.__io.get(TSI_HDRX_CEC_RECEIVED_CNT_R, c_uint32)[1] + + @property + def data(self) -> list: + """ + Get CEC raw data. + + Returns: + object of `list` type + """ + data_size = self.__io.get(TSI_HDRX_CEC_DATA_SIZE_R, c_uint32)[1] + return self.__io.get(TSI_HDRX_CEC_DATA_RECIEVED, c_uint32, data_size)[1] + + def reset_data(self, command: CecResetDataCommand): + """ + Get CEC reset data. + + Returns: + object of `CecResetDataCommand` type + """ + self.__io.set(TSI_HDRX_CEC_DATA_RESET_W, command.value, c_uint32) + + def __read_version(self) -> (int, int): + version = self.__io.get(TSI_HDRX_CEC_VERSION_R, CECVersion)[1] + return version.minor, version.major + + def add_command(self, command: CECCommand = CECCommand()): + """ + Add CEC command in list. + + Args: + command (`CECCommand`) + """ + self.__commands.append(command) + + def delete_command(self, index: int = 0): + """ + Delete CEC command in list by index. + + Args: + index (`int`) + """ + self.__commands.pop(index) + + def send_command(self, logical_address: LogicalAddressEnum = LogicalAddressEnum.Tv, + destination: LogicalAddressEnum = LogicalAddressEnum.Broadcast, + phy_address: int = 0x0, op_code: int = 0x0, op_code_param: Union[int, list] = 0x0, + device_type: Union[DeviceTypeEnum, DeviceType, int] = DeviceTypeEnum.TV): + """ + Send CEC command with the transferred parameters. + + Args: + logical_address (`LogicalAddressEnum`) + destination (`LogicalAddressEnum`) + phy_address (`int`) + op_code (`int`) + op_code_param (`int`) + device_type (`DeviceTypeEnum` | `DeviceType`) + """ + self.enable(True) + self.logical_address = logical_address + self.phy_address = phy_address + self.op_code = op_code + self.op_code_param = op_code_param + self.device_type = device_type + self.destination = destination + self.__send_cec_command() + + def send_command_by_index(self, index: int): + """ + Send CEC command from the list by index. + + Args: + index (`int`) + """ + if len(self.__commands) > 0 and 0 <= index < len(self.__commands): + self.__send_command(self.__commands[index]) + + def send_commands(self): + """ + Send all saved commands. + """ + for command in self.__commands: + self.__send_command(command) + + def __send_command(self, command: CECCommand = CECCommand()): + self.logical_address = command.logical_address + self.destination = command.destination + self.phy_address = command.phy_address + self.op_code = command.op_code + self.op_code_param = command.op_code_param + self.device_type = command.device_type + self.__send_cec_command() + + def __send_cec_command(self): + self.__io.set(TSI_HDRX_CEC_COMMAND, 1, c_uint32) + + @property + def commands(self) -> list: + """ + Get CEC command list. + + Returns: + object of `list` type + """ + return self.__commands + + def clear_commands(self): + """ + Clear saved commands. + """ + self.__commands.clear() diff --git a/UniTAP/dev/ports/modules/cec/cec_tx.py b/UniTAP/dev/ports/modules/cec/cec_tx.py new file mode 100644 index 0000000..cdfefa3 --- /dev/null +++ b/UniTAP/dev/ports/modules/cec/cec_tx.py @@ -0,0 +1,269 @@ +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from UniTAP.libs.lib_tsi.tsi import * +from .cec_types import * +from .cec_private_types import * +from typing import Union + + +class CecTx: + + def __init__(self, port_io: PortIO): + self.__io = port_io + self.__version = self.__read_version() + self.__commands = [] + + @property + def version(self) -> (int, int): + """ + Read CEC version from the TX side. + + Returns: + object of tuple of `int` type + """ + return self.__version + + def enable(self, state: bool): + """ + Enable/Disable CEC. + + Args: + state (`bool`) + """ + self.__io.set(TSI_HDTX_CEC_CONTROL, state, c_uint32) + + def is_enabled(self) -> bool: + """ + Return state of CEC (enabled or not). + + Returns: + object of `bool` type + """ + return self.__io.get(TSI_HDTX_CEC_CONTROL, c_uint32)[1] & 0x1 == 1 + + @property + def logical_address(self) -> LogicalAddressEnum: + """ + Set/Get CEC logical address. + + Returns: + object of `LogicalAddressEnum` type + """ + return LogicalAddressEnum(self.__io.get(TSI_HDTX_CEC_LOGICAL_ADDRESS, c_uint32)[1]) + + @logical_address.setter + def logical_address(self, address: LogicalAddressEnum): + self.__io.set(TSI_HDTX_CEC_LOGICAL_ADDRESS, address.value, c_uint32) + + @property + def destination(self) -> LogicalAddressEnum: + """ + Set/Get CEC destination address. + + Returns: + object of `LogicalAddressEnum` type + """ + return LogicalAddressEnum(self.__io.get(TSI_HDTX_CEC_DESTINATION, c_uint32)[1]) + + @destination.setter + def destination(self, address: LogicalAddressEnum): + self.__io.set(TSI_HDTX_CEC_DESTINATION, address.value, c_uint32) + + @property + def phy_address(self) -> int: + """ + Set/Get CEC physical address. + + Returns: + object of `int` type + """ + return self.__io.get(TSI_HDTX_CEC_PHYSICAL_ADDRESS, c_uint32)[1] + + @phy_address.setter + def phy_address(self, address: int): + self.__io.set(TSI_HDTX_CEC_PHYSICAL_ADDRESS, address, c_uint32) + + @property + def op_code(self) -> int: + """ + Set/Get CEC operation code. + + Returns: + object of `int` type + """ + return self.__io.get(TSI_HDTX_CEC_OP_CODE, c_uint32)[1] + + @op_code.setter + def op_code(self, code: int): + self.__io.set(TSI_HDTX_CEC_OP_CODE, code, c_uint32) + + @property + def op_code_param(self) -> Union[int, list]: + """ + Set/Get CEC additional parameter for operation code. + + Returns: + object of `int` type + """ + return self.__io.get(TSI_HDTX_CEC_OP_CODE_PARAM, c_uint32)[1] + + @op_code_param.setter + def op_code_param(self, code_param: Union[int, list]): + data_count = len(code_param) if isinstance(code_param, list) else 1 + self.__io.set(TSI_HDTX_CEC_OP_CODE_PARAM, code_param, c_uint32, data_count=data_count) + + @property + def device_type(self) -> DeviceType: + """ + Set/Get CEC Device type. + + Returns: + object of `DeviceType` type + """ + data = self.__io.get(TSI_HDTX_CEC_DEVICE_TYPE, DeviceTypePrivate)[1] + return DeviceType(data.cecSwitch, data.audioSystem, data.pbDevice, data.tuner, data.recDev, data.tv) + + @device_type.setter + def device_type(self, dev_type: Union[DeviceTypeEnum, DeviceType, int]): + if isinstance(dev_type, DeviceType): + dev_type_value = DeviceTypePrivate() + dev_type_value.value = dev_type + value = dev_type_value.value + elif isinstance(dev_type, DeviceTypeEnum): + value = dev_type.value + elif isinstance(dev_type, int): + value = dev_type & 0xFF + else: + raise TypeError(f"Unsupported type: {type(dev_type)}") + self.__io.set(TSI_HDTX_CEC_DEVICE_TYPE, value, c_uint32) + + @property + def status(self) -> CECStatus: + """ + Get CEC status. + + Returns: + object of `CECStatus` type + """ + data = self.__io.get(TSI_HDTX_CEC_STATUS_R, CECStatusPrivate)[1] + return CECStatus(data.bufferOverflowed) + + @property + def command_number(self) -> int: + """ + Get CEC command number. + + Returns: + object of `int` type + """ + return self.__io.get(TSI_HDTX_CEC_RECEIVED_CNT_R, c_uint32)[1] + + @property + def data(self) -> list: + """ + Get CEC raw data. + + Returns: + object of `list` type + """ + data_size = self.__io.get(TSI_HDTX_CEC_DATA_SIZE_R, c_uint32)[1] + return self.__io.get(TSI_HDTX_CEC_DATA_RECEIVED_R, c_uint32, data_size)[1] + + def reset_data(self, command: CecResetDataCommand): + """ + Get CEC reset data. + + Returns: + object of `CecResetDataCommand` type + """ + self.__io.set(TSI_HDTX_CEC_DATA_RESET_W, command.value, c_uint32) + + def __read_version(self) -> (int, int): + version = self.__io.get(TSI_HDTX_CEC_VERSION_R, CECVersion)[1] + return version.minor, version.major + + def add_command(self, command: CECCommand = CECCommand()): + """ + Add CEC command in list. + + Args: + command (`CECCommand`) + """ + self.__commands.append(command) + + def delete_command(self, index: int = 0): + """ + Delete CEC command in list by index. + + Args: + index (`int`) + """ + self.__commands.pop(index) + + def send_command(self, logical_address: LogicalAddressEnum = LogicalAddressEnum.Tv, + destination: LogicalAddressEnum = LogicalAddressEnum.Broadcast, + phy_address: int = 0x0, op_code: int = 0x0, op_code_param: Union[int, list] = 0x0, + device_type: Union[DeviceTypeEnum, DeviceType, int] = DeviceTypeEnum.TV): + """ + Send CEC command with the transferred parameters. + + Args: + logical_address (`LogicalAddressEnum`) + destination (`LogicalAddressEnum`) + phy_address (`int`) + op_code (`int`) + op_code_param (`int`) + device_type (`DeviceTypeEnum` | `DeviceType`) + """ + self.enable(True) + self.logical_address = logical_address + self.phy_address = phy_address + self.op_code = op_code + self.op_code_param = op_code_param + self.device_type = device_type + self.destination = destination + self.__send_cec_command() + + def send_command_by_index(self, index: int): + """ + Send CEC command from the list by index. + + Args: + index (`int`) + """ + if len(self.__commands) > 0 and 0 <= index < len(self.__commands): + self.__send_command(self.__commands[index]) + + def send_commands(self): + """ + Send all saved commands. + """ + for command in self.__commands: + self.__send_command(command) + + def __send_command(self, command: CECCommand = CECCommand()): + self.logical_address = command.logical_address + self.destination = command.destination + self.phy_address = command.phy_address + self.op_code = command.op_code + self.op_code_param = command.op_code_param + self.device_type = command.device_type + self.__send_cec_command() + + def __send_cec_command(self): + self.__io.set(TSI_HDTX_CEC_COMMAND, 1, c_uint32) + + @property + def commands(self) -> list: + """ + Get CEC command list. + + Returns: + object of `list` type + """ + return self.__commands + + def clear_commands(self): + """ + Clear saved commands. + """ + self.__commands.clear() diff --git a/UniTAP/dev/ports/modules/cec/cec_types.py b/UniTAP/dev/ports/modules/cec/cec_types.py new file mode 100644 index 0000000..916d2d3 --- /dev/null +++ b/UniTAP/dev/ports/modules/cec/cec_types.py @@ -0,0 +1,134 @@ +from enum import IntEnum +from typing import Union + + +class LogicalAddressEnum(IntEnum): + """ + Class `PRCommand` contains all possible variants of Panel Replay Command. + """ + + Tv = 0x0, + RecDevice1 = 0x1, + RecDevice2 = 0x2, + Tuner1 = 0x3, + PlayDevice1 = 0x4, + AudioSystem = 0x5, + Tuner2 = 0x6, + Tuner3 = 0x7, + PlayDevice2 = 0x8, + RecDevice3 = 0x9, + Tuner4 = 0xA, + PlayDevice3 = 0xB, + Reserved1 = 0xC, + Reserved2 = 0xD, + SpecificUse = 0xE, + Broadcast = 0xF, + Unregistered = Broadcast + + +class DeviceTypeEnum(IntEnum): + CECSwitch = 0x1 << 2, + AudioSystem = 0x1 << 3, + PlayBack = 0x1 << 4, + Tuner = 0x1 << 5, + Rec = 0x1 << 6, + TV = 0x1 << 7 + + +class CecResetDataCommand(IntEnum): + """ + Class `PRCommand` contains all possible variants of Panel Replay Command. + """ + OneFrame = 1, + AllFrames = 2 + + +class DeviceType: + + def __init__(self, cec_switch: bool = False, audio_system: bool = False, pb_device: bool = False, + tuner: bool = False, rec_dev: bool = False, tv: bool = False): + self.cec_switch = cec_switch + self.audio_system = audio_system + self.pb_device = pb_device + self.tuner = tuner + self.rec_dev = rec_dev + self.tv = tv + + def __str__(self) -> str: + return f"CEC switch - {self.cec_switch}\n" \ + f"Audio System - {self.audio_system}\n" \ + f"PD Device - {self.pb_device}\n" \ + f"Tuner - {self.tuner}\n" \ + f"Rec dev - {self.rec_dev}\n" \ + f"TV - {self.tv}\n" + + +class CECStatus: + + def __init__(self, buffer_overflowed: bool = False): + self.buffer_overflowed = buffer_overflowed + + def __str__(self) -> str: + return f"Buffer Overflowed - {self.buffer_overflowed}\n" + + +class CECCommand: + + def __init__(self, logical_address: LogicalAddressEnum = LogicalAddressEnum.Tv, + phy_address: int = 0x0, op_code: int = 0x0, op_code_param: Union[int, list] = 0x0, + device_type: DeviceType = DeviceType(), + destination: LogicalAddressEnum = LogicalAddressEnum.Broadcast): + self.__logical_address = logical_address + self.__phy_address = phy_address + self.__op_code = op_code + self.__op_code_param = op_code_param + self.__device_type = device_type + self.__destination = destination + + @property + def logical_address(self) -> LogicalAddressEnum: + return self.__logical_address + + @logical_address.setter + def logical_address(self, address: LogicalAddressEnum): + self.__logical_address = address + + @property + def phy_address(self) -> int: + return self.__phy_address + + @phy_address.setter + def phy_address(self, address: int): + self.__phy_address = address + + @property + def op_code(self) -> int: + return self.__op_code + + @op_code.setter + def op_code(self, op_code: int): + self.__op_code = op_code + + @property + def op_code_param(self) -> Union[int, list]: + return self.__op_code_param + + @op_code_param.setter + def op_code_param(self, param: Union[int, list]): + self.__op_code_param = param + + @property + def device_type(self) -> DeviceType: + return self.__device_type + + @device_type.setter + def device_type(self, dev_type: DeviceType): + self.__device_type = dev_type + + @property + def destination(self) -> LogicalAddressEnum: + return self.__destination + + @destination.setter + def destination(self, address: LogicalAddressEnum): + self.__destination = address diff --git a/UniTAP/dev/ports/modules/device_constants.py b/UniTAP/dev/ports/modules/device_constants.py new file mode 100644 index 0000000..07074b8 --- /dev/null +++ b/UniTAP/dev/ports/modules/device_constants.py @@ -0,0 +1,304 @@ +import time +import warnings + +case_values_pg = [[800, 525, 640, 480, 144, 35, 96, 2, 60000, 0, 1, False, False, 0, "Color Bars"], + [858, 525, 720, 480, 122, 36, 62, 6, 60000, 0, 1, False, False, 0, "Color Bars"], + [858, 525, 720, 480, 122, 36, 62, 6, 120000, 0, 1, False, False, 0, "Color Bars"], + [1056, 628, 800, 600, 216, 27, 128, 4, 60000, 0, 1, True, True, 0, "Color Bars"], + [1088, 517, 848, 480, 224, 31, 112, 8, 60000, 0, 1, True, True, 0, "Color Bars"], + [1344, 806, 1024, 768, 296, 35, 136, 6, 60000, 0, 1, False, False, 0, "Color Bars"], + [1650, 750, 1280, 720, 260, 25, 40, 5, 60000, 0, 1, True, True, 0, "Color Bars"], + [1650, 750, 1280, 720, 260, 25, 40, 5, 120000, 0, 1, True, True, 0, "Color Bars"], + [1440, 790, 1280, 768, 112, 19, 32, 7, 60000, 0, 1, True, False, 0, "Color Bars"], + [1664, 798, 1280, 768, 320, 27, 128, 7, 60000, 0, 1, False, True, 0, "Color Bars"], + [1440, 823, 1280, 800, 112, 20, 32, 6, 60000, 0, 1, True, False, 1, "Color Bars"], + [1680, 831, 1280, 800, 328, 28, 128, 6, 60000, 0, 1, False, True, 0, "Color Bars"], + [1800, 1000, 1280, 960, 424, 39, 112, 3, 60000, 0, 1, True, True, 0, "Color Bars"], + [1688, 1066, 1280, 1024, 360, 41, 112, 3, 60000, 0, 1, True, True, 0, "Color Bars"], + [1792, 795, 1360, 768, 368, 24, 112, 6, 60000, 0, 1, True, True, 0, "Color Bars"], + [1560, 1080, 1400, 1050, 112, 27, 32, 4, 60000, 0, 1, False, False, 1, "Color Bars"], + [1864, 1089, 1400, 1050, 376, 36, 144, 4, 60000, 0, 1, False, False, 1, "Color Bars"], + # [1716, 525, 1440, 480, 244, 36, 124, 6, 59940, 0, 1, False, False, 0, "Color Bars"], + [1728, 625, 1440, 576, 264, 44, 128, 5, 50000, 0, 1, False, False, 0, "Color Bars"], + [1760, 1235, 1600, 1200, 112, 32, 32, 4, 60000, 0, 1, False, False, 1, "Color Bars"], + [2160, 1250, 1600, 1200, 496, 49, 192, 3, 60000, 0, 1, True, True, 0, "Color Bars"], + [2200, 750, 1680, 720, 260, 25, 40, 5, 60000, 0, 1, True, True, 0, "Color Bars"], + [2000, 825, 1680, 720, 260, 100, 40, 5, 120000, 0, 1, True, True, 0, "Color Bars"], + [1840, 1080, 1680, 1050, 112, 27, 32, 6, 60000, 0, 1, True, False, 1, "Color Bars"], + [2240, 1089, 1680, 1050, 456, 36, 176, 6, 60000, 0, 1, False, True, 0, "Color Bars"], + [2448, 1394, 1792, 1344, 528, 49, 200, 3, 60000, 0, 1, False, True, 0, "Color Bars"], + [2528, 1439, 1856, 1392, 576, 46, 224, 3, 60000, 0, 1, False, True, 0, "Color Bars"], + [2080, 1096, 1920, 1080, 112, 13, 32, 5, 30000, 0, 1, True, False, 1, "Color Bars"], + [2000, 1096, 1920, 1080, 72, 14, 32, 8, 30000, 0, 1, True, False, 2, "Color Bars"], + [2200, 1125, 1920, 1080, 192, 41, 44, 5, 30000, 0, 1, True, True, 0, "Color Bars"], + # [2080, 1157, 1920, 1080, 152, 14, 32, 8, 144050, 0, 1, False, False, 3, "Color Bars"], + # [2080, 1190, 1920, 1080, 152, 14, 32, 8, 200070, 0, 1, False, False, 3, "Color Bars"], + [2080, 1111, 1920, 1080, 112, 28, 32, 5, 60000, 0, 1, False, False, 1, "Color Bars"], + [2080, 1111, 1920, 1080, 72, 14, 32, 8, 60000, 0, 1, True, True, 2, "Color Bars"], + [2200, 1125, 1920, 1080, 192, 41, 44, 5, 60000, 0, 1, True, True, 0, "Color Bars"], + [2080, 1144, 1920, 1080, 112, 61, 32, 5, 120000, 0, 1, True, False, 1, "Color Bars"], + [2000, 1144, 1920, 1080, 72, 14, 32, 8, 120000, 0, 1, True, False, 2, "Color Bars"], + [2200, 1125, 1920, 1080, 192, 41, 44, 5, 120000, 0, 1, True, True, 0, "Color Bars"], + [2592, 1245, 1920, 1200, 536, 42, 200, 6, 60000, 0, 1, False, True, 0, "Color Bars"], + [2600, 1500, 1920, 1440, 552, 59, 208, 3, 60000, 0, 1, False, True, 0, "Color Bars"], + [2208, 1580, 2048, 1536, 112, 41, 32, 4, 60000, 0, 1, True, False, 1, "Color Bars"], + [2640, 1481, 2560, 1440, 72, 14, 32, 8, 60000, 0, 1, True, False, 2, "Color Bars"], + [2720, 1481, 2560, 1440, 112, 38, 32, 5, 60000, 0, 1, True, False, 1, "Color Bars"], + # [2720, 1543, 2560, 1440, 152, 14, 32, 8, 144050, 0, 1, True, False, 3, "Color Bars"], + # [2720, 1586, 2560, 1440, 152, 14, 32, 8, 200070, 0, 1, True, False, 3, "Color Bars"], + [3424, 1120, 2560, 1080, 704, 37, 272, 10, 60000, 0, 1, False, True, 0, "Color Bars"], + [2720, 1111, 2560, 1080, 112, 28, 32, 10, 60000, 0, 1, True, False, 1, "Color Bars"], + # [2720, 1157, 2560, 1080, 152, 14, 32, 8, 144051, 0, 1, True, False, 3, "Color Bars"], + # [2720, 1190, 2560, 1080, 152, 14, 32, 8, 200070, 0, 1, True, False, 3, "Color Bars"], + [3000, 1100, 2560, 1080, 192, 16, 44, 5, 60000, 0, 1, True, True, 0, "Color Bars"], + [3300, 1250, 2560, 1080, 192, 16, 44, 5, 120000, 0, 1, True, True, 0, "Color Bars"], + [3504, 1658, 2560, 1600, 752, 55, 280, 6, 60000, 0, 1, False, True, 0, "Color Bars"], + [2720, 1646, 2560, 1600, 112, 43, 32, 6, 60000, 0, 1, True, False, 1, "Color Bars"], + [2976, 1456, 2880, 1440, 48, 8, 8, 1, 60000, 0, 1, True, True, 0, "Color Bars"], + [4176, 2222, 4096, 2160, 72, 14, 32, 8, 60000, 0, 1, True, False, 2, "Color Bars"], + [4000, 2191, 3840, 2160, 112, 28, 32, 5, 30000, 0, 1, True, False, 1, "Color Bars"], + [3920, 2191, 3840, 2160, 72, 14, 32, 8, 30000, 0, 1, True, False, 2, "Color Bars"], + # [4000, 2314, 3840, 2160, 152, 14, 32, 8, 144050, 0, 1, True, False, 3, "Color Bars"], + [4400, 2250, 3840, 2160, 384, 82, 88, 10, 30000, 0, 1, True, True, 0, "Color Bars"], + [5280, 2250, 3840, 2160, 384, 82, 88, 10, 50000, 0, 1, True, True, 0, "Color Bars"], + [5280, 2250, 4096, 2160, 216, 82, 88, 10, 50000, 0, 1, True, True, 0, "Color Bars"], + [3840, 2160, 4000, 2222, 112, 59, 32, 5, 60000, 0, 1, True, False, 1, "Color Bars"], + [3920, 2222, 3840, 2160, 72, 14, 32, 8, 60000, 0, 1, True, False, 2, "Color Bars"], + # [4000, 2222, 3840, 2160, 152, 14, 32, 8, 60021, 0, 1, True, False, 3, "Color Bars"], + [4400, 2250, 3840, 2160, 384, 82, 88, 10, 60000, 0, 1, True, True, 0, "Color Bars"], + [4256, 2222, 4096, 2160, 112, 59, 32, 10, 60000, 0, 1, True, False, 1, "Color Bars"], + # [4256, 2222, 4096, 2160, 152, 14, 32, 8, 60021, 0, 1, True, False, 3, "Color Bars"], + # [4256, 2314, 4096, 2160, 152, 14, 32, 8, 144050, 0, 1, True, False, 3, "Color Bars"], + [4400, 2250, 4096, 2160, 216, 82, 88, 10, 60000, 0, 1, True, True, 0, "Color Bars"], + [4000, 2287, 3840, 2160, 112, 124, 32, 5, 120000, 0, 1, True, False, 1, "Color Bars"], + [3920, 2287, 3840, 2160, 72, 14, 32, 8, 120000, 0, 1, True, False, 2, "Color Bars"], + [4400, 2250, 3840, 2160, 384, 82, 88, 10, 120000, 0, 1, True, True, 0, "Color Bars"], + [5280, 2191, 5120, 2160, 112, 28, 32, 10, 30000, 0, 1, True, False, 1, "Color Bars"], + [5200, 2191, 5120, 2160, 72, 14, 32, 8, 30000, 0, 1, True, False, 2, "Color Bars"], + [6000, 2200, 5120, 2160, 216, 32, 88, 10, 30000, 0, 1, True, True, 0, "Color Bars"], + [5280, 2222, 5120, 2160, 112, 59, 32, 10, 60000, 0, 1, True, False, 1, "Color Bars"], + [5200, 2222, 5120, 2160, 72, 14, 32, 6, 60000, 0, 1, True, False, 2, "Color Bars"], + [5500, 2250, 5120, 2160, 216, 82, 88, 10, 60000, 0, 1, True, True, 0, "Color Bars"], + [5280, 2287, 5120, 2160, 112, 124, 32, 10, 120000, 0, 1, True, False, 1, "Color Bars"], + # [5280, 2287, 5120, 2160, 152, 14, 32, 8, 120042, 0, 1, True, False, 3, "Color Bars"], + [5200, 2287, 5120, 2160, 72, 14, 32, 8, 120000, 0, 1, True, False, 2, "Color Bars"], + [5500, 2250, 5120, 2160, 216, 82, 88, 10, 120000, 0, 1, True, True, 0, "Color Bars"], + [5280, 2962, 5120, 2880, 112, 79, 32, 5, 60000, 0, 1, True, False, 1, "Color Bars"], + [5200, 2962, 5120, 2880, 72, 14, 32, 8, 60000, 0, 1, True, False, 2, "Color Bars"], + # [5280, 2962, 5120, 2880, 152, 14, 32, 8, 60021, 0, 1, True, False, 3, "Color Bars"], + [11000, 4500, 7680, 4320, 768, 164, 176, 20, 24000, 0, 1, True, True, 0, "Color Bars"], + [7840, 4381, 7680, 4320, 112, 58, 32, 5, 30000, 0, 1, True, False, 1, "Color Bars"], + [7760, 4381, 7680, 4320, 72, 14, 32, 8, 30000, 0, 1, True, False, 2, "Color Bars"], + [9000, 4400, 7680, 4320, 768, 64, 176, 20, 30000, 0, 1, True, True, 0, "Color Bars"], + [7840, 4443, 7680, 4320, 112, 120, 32, 5, 60000, 0, 1, True, True, 1, "Color Bars"], + [7760, 4443, 7680, 4320, 72, 14, 32, 8, 60000, 0, 1, True, False, 2, "Color Bars"], + [9000, 4400, 7680, 4320, 768, 64, 176, 20, 60000, 0, 1, True, True, 0, "Color Bars"], + # [7840, 4529, 7680, 4320, 112, 206, 32, 5, 100000, 0, 1, True, False, 1, "Color Bars"], + # [7760, 4529, 7680, 4320, 72, 14, 32, 8, 100000, 0, 1, True, False, 2, "Color Bars"], + # [10560, 4500, 7680, 4320, 768, 164, 176, 20, 100000, 0, 1, True, True, 0, "Color Bars"], + [11000, 4500, 10240, 4320, 112, 58, 32, 5, 30000, 0, 1, True, False, 1, "Color Bars"], + [11000, 4500, 10240, 4320, 72, 14, 32, 8, 30000, 0, 1, True, False, 2, "Color Bars"], + [12500, 4950, 10240, 4320, 768, 614, 176, 20, 24000, 0, 1, True, True, 0, "Color Bars"], + [13500, 4400, 10240, 4320, 768, 64, 176, 20, 25000, 0, 1, True, True, 0, "Color Bars"], + [11000, 4500, 10240, 4320, 472, 164, 176, 20, 30000, 0, 1, True, True, 0, "Color Bars"] + # [12500, 4950, 10240, 4320, 768, 614, 176, 20, 48000, 0, 1, True, True, 0, "Color Bars"], + # [13500, 4400, 10240, 4320, 768, 64, 176, 20, 50000, 0, 1, True, True, 0, "Color Bars"], + # [11000, 4500, 10240, 4320, 472, 164, 176, 20, 60000, 0, 1, True, True, 0, "Color Bars"], + # [13200, 4500, 10240, 4320, 768, 164, 176, 20, 100000, 0, 1, True, True, 0, "Color Bars"], + # [11000, 4500, 10240, 4320, 472, 164, 176, 20, 120000, 0, 1, True, True, 0, "Color Bars"] + ] + +dict_rate = {1.62: 6, 2.70: 10, 5.40: 20, 6.75: 25, 8.10: 30} + +dict_color_formate = {"RGB": 0, "YCbCr 4:4:4 ITU-601": 1, "YCbCr 4:2:2 ITU-601": 2, "YCbCr 4:2:0 ITU-601": 3, + "YCbCr 4:4:4 ITU-709": 4, "YCbCr 4:2:2 ITU-709": 5, "YCbCr 4:2:0 ITU-709": 6} + +dict_color_formate_rev = {0: "RGB", 1: "YCbCr 4:4:4 ITU-601", 2: "YCbCr 4:2:2 ITU-601", 3: "YCbCr 4:2:0 ITU-601", + 4: "YCbCr 4:4:4 ITU-709", 5: "YCbCr 4:2:2 ITU-709", 6: "YCbCr 4:2:0 ITU-709"} + +dict_color_formate_2 = {"RGB": 0, "YCbCr 4:4:4": 1, "YCbCr 4:2:2": 2, "YCbCr 4:2:0": 3, "Simple 4:2:2": 4} + +dict_colorimetry_rev = {"ITU-601": 0, "ITU-709": 1} + +dict_color_mode_rev = {0: 0, 1: 2, 2: 1, 3: 3, 4: 4} + +dict_color_mode_dp = {0: "No Data", 1: "Unknown", 2: "RGB", 3: "YCbCr 4:2:2", 4: "YCbCr 4:4:4", 5: "YCbCr 4:2:0", + 6: "IDO", 7: "Y only", 8: "Raw", 9: "DSC"} + +dict_colorimetry_dp = {0: "No Data", 1: "Unknown", 2: "RGB", 3: "SMPTE 170M", 4: "ITU-601", 5: "ITU-709", + 6: "xvYCC.601", 7: "xvYCC.709", 8: "sYCC.601", 9: "Adobe YCC.601", 10: "Adobe RGB", + 11: "ITU-R BT2020 (YcCbcCrc)", 12: "ITU-R BT2020 (YCbCr)", 13: "ITU-R BT2020 (RGB)", + 14: "Wide gamut RGB fixed point", 15: "Wide gamut RGB floating point", + 16: "DCI-3 (SMPTE RP 431-1)", 17: "DICOM 1.4 gray scale", 18: "Custom color profile"} + +dict_build_color_format = {"PGImageFormatRGB_080808": 0x000, + "PGImageFormatRGB_161616": 0x001, + "PGImageFormatRGBA_080808A": 0x002, + "PGImageFormatRGBA_161616A": 0x003, + "PGImageFormatRGB_121212": 0x004, + "PGImageFormatYCbCr_080808": 0x100, + "PGImageFormatYCbCr_161616": 0x101, + "PGImageFormatYCbYCr_08": 0x200, + "PGImageFormatYCbYCr_16": 0x201, + "PGImageFormatYYCbYYCr_08": 0x300, + "PGImageFormatYYCbYYCr_16": 0x301, + "PGImageFormatI420_08": 0x310, + "PGImageFormatI420_10": 0x311, + "PGImageFormatI420_12": 0x312, + "PGImageFormatI420_16": 0x313, + "PGImageFormatUnknown": 2147483647} + +dict_bpc = {6: 0, 8: 1, 10: 2, 12: 3, 16: 4, 18: 0, 24: 1, 32: 2, 7: 5, 14: 6} + +dict_bpc_rev = {0: 6, 1: 8, 2: 10, 3: 12, 4: 16, 5: 7, 6: 14} + +dict_usbc_bit_rate_values = {10: 1, 20: 2, 13: 4} +dict_usbc_bit_rate_rev_values = {1: 10, 2: 20, 4: 13} + +dict_color_mode_hdmi = { + 0: "No Data", 1: "Unknown", 2: "RGB", 3: "YCbCr4:2:2", 4: "YCbCr4:4:4", 5: "YCbCr4:2:0", 6: "IDO", 7: "Y-Only", + 8: "Raw" +} + +dict_colorimetry_hdmi = { + 0: "No Data", 1: "Unknown", 2: "RGB", 3: "SMPTE 170M", 4: "ITU-R BT.601", 5: "ITU-R BT.709", 6: "xvYCC.601", + 7: "xvYCC.709", 8: "sYCC.601", 9: "Adobe YCC.601", 10: "Adobe RGB", 11: "ITU-R BT2020 (YcCbcCrc)", + 12: "ITU-R BT2020 (YCbCr)", 13: "ITU-R BT2020 (RGB)", 14: "Wide gamut RGB fixed point", + 15: "Wide gamut RGB floating point", 16: "DCI-3 (SMPTE RP 431-1)", 17: "DICOM 1.4 gray scale", + 18: "Custom color profile" +} + +dict_link_mode_frl = { + 0: "FRL_Disable", + 1: "FRL_3L_03G", + 2: "FRL_3L_06G", + 3: "FRL_4L_06G", + 4: "FRL_4L_08G", + 5: "FRL_4L_10G", + 6: "FRL_4L_12G" +} + +dict_hdmi_behavior = {0: "HDMI 1.4", 1: "HDMI 2.0", 2: "HDMI 2.1"} + +device_roles = {"sink": "RX", "source": "TX"} + +gbit = 1000000000 +available_dp20_link_rates = {10: 10 * gbit, 13.5: 135 * gbit / 10, 20: 20 * gbit} + + +def info_hex(idx: int, data: str, sz: int, gap: int) -> str: + list_r_dp_sdp = ["ACR", "ASP", "AIF"] + tmp = list_r_dp_sdp[idx] + for i in range(sz): + tmp += " " + hex(int(data[i])) + (" " if i == gap else "") + tmp += "\n" + return tmp + + +def info_header(num: int, hbsz: int) -> str: + s = " " + n = 0 + for i in range(num): + if i != hbsz: + s += " " + str(n) + n += 1 + else: + n = 0 + s += " " + return s + + +PDC_STATE_PULLUP_POS = 0 +PDC_STATE_PULLUP_09A = 0 << PDC_STATE_PULLUP_POS +PDC_STATE_PULLUP_15A = 1 << PDC_STATE_PULLUP_POS +PDC_STATE_PULLUP_30A = 2 << PDC_STATE_PULLUP_POS +PDC_STATE_PULLUP_UNKNOWN = 3 << PDC_STATE_PULLUP_POS + +PDC_STATE_DEV_ROLE_POS = 2 +PDC_STATE_DEV_ROLE_UFP = 0 << PDC_STATE_DEV_ROLE_POS +PDC_STATE_DEV_ROLE_DFP = 1 << PDC_STATE_DEV_ROLE_POS +PDC_STATE_DEV_ROLE_DRP = 2 << PDC_STATE_DEV_ROLE_POS +PDC_STATE_DEV_ROLE_UNKNOWN = 3 << PDC_STATE_DEV_ROLE_POS + +PDC_STATE_PD_MODE_POS = 4 +PDC_STATE_PD_MODE_NORMAL = 0 << PDC_STATE_PD_MODE_POS +PDC_STATE_PD_MODE_LSOURCE = 1 << PDC_STATE_PD_MODE_POS +PDC_STATE_PD_MODE_LSINK = 2 << PDC_STATE_PD_MODE_POS +PDC_STATE_PD_MODE_UNKNOWN = 3 << PDC_STATE_PD_MODE_POS + +PDC_STATE_USB3_MODE_POS = 6 +PDC_STATE_USB3_MODE_DISABLED = 0 << PDC_STATE_USB3_MODE_POS +PDC_STATE_USB3_MODE_GEN_1 = 1 << PDC_STATE_USB3_MODE_POS +PDC_STATE_USB3_MODE_GEN_2 = 2 << PDC_STATE_USB3_MODE_POS +PDC_STATE_USB3_MODE_UNKNOWN = 3 << PDC_STATE_USB3_MODE_POS + +PDC_STATE_USB2_ENABLED = 1 << 8 +PDC_STATE_VCONN_SWAP_EN = 1 << 9 +PDC_STATE_PR_SWAP_EN = 1 << 10 +PDC_STATE_DR_SWAP_EN = 1 << 11 +PDC_STATE_DEBUG_ACCESSORY = 1 << 12 +PDC_STATE_AUDIO_ACCESSORY = 1 << 13 +PDC_STATE_FR_SWAP_EN = 1 << 14 + +PDC_STATE_DRP_TRY_MODE_POS = 16 +PDC_STATE_DRP_TRY_MODE_PURE_DRP = 0 << PDC_STATE_DRP_TRY_MODE_POS +PDC_STATE_DRP_TRY_MODE_DRP_TRYSNK = 1 << PDC_STATE_DRP_TRY_MODE_POS +PDC_STATE_DRP_TRY_MODE_DRP_TRYSRC = 2 << PDC_STATE_DRP_TRY_MODE_POS +PDC_STATE_DRP_TRY_MODE_DRP_UNKNOWN = 3 << PDC_STATE_DRP_TRY_MODE_POS + +PG_TIMF_META_REDUCED_BLANK = (1 << 24) +PG_TIMF_META_REDUCED_BLANK_2 = (2 << 24) +PG_TIMF_META_REDUCED_BLANK_3 = (3 << 24) + +CAP_OPTION_AUDIO = 1 +CAP_OPTION_VIDEO = 1 << 1 +CAP_OPTION_EVENTS = 1 << 2 +CAP_OPTION_MODE_LIVE = 1 << 3 +CRC_MAX_READ_COUNT = 125 + +PG_TIMF_COLOR_RGB = (0 << 11) +PG_TIMF_COLOR_YUV = (1 << 11) +PG_TIMF_COLOR_YUV422 = (2 << 11) +PG_TIMF_COLOR_YUV420 = (3 << 11) +PG_TIMF_COLOR_Y = (4 << 11) +PG_TIMF_COLOR_RAW = (5 << 11) + +dict_color_formate_tx_rev = {PG_TIMF_COLOR_RGB: "RGB", PG_TIMF_COLOR_YUV: "YCbCr 4:4:4", + PG_TIMF_COLOR_YUV422: "YCbCr 4:2:2", PG_TIMF_COLOR_YUV420: "YCbCr 4:2:0", + PG_TIMF_COLOR_Y: "Y only", PG_TIMF_COLOR_RAW: "Raw"} + +PG_TIMF_COLORIMETRY_MASK = (0x01 << 16) +PG_TIMF_ITU_601 = (0 << 16) +PG_TIMF_ITU_709 = (1 << 16) + +DP2_LINK_BW_LOSS_M = 128 * 383 +DP2_LINK_BW_LOSS_N = 132 * 384 + +MAX_EDID_SIZE = 4096 + +dict_colorimetry_tx = {PG_TIMF_ITU_601: "ITU-601", PG_TIMF_ITU_709: "ITU-709"} + + +class DeviceOpenError(Exception): + + def __init__(self, message: str): + self.__message = message + super().__init__(self.__message) + + +def check_frame_size(h_active, v_active, input_color_mode, count, memory): + color_mode = 4 if input_color_mode in [2, 3, 4, "RGB", "YCbCr4:2:2", "YCbCr4:4:4"] else 3 + frame_size = h_active * v_active * color_mode + + max_device_memory = memory + try: + count_frames_available = int(max_device_memory // frame_size) + except BaseException: + count_frames_available = 0 + + if count > count_frames_available: + warnings.warn("Exceeded the maximum number of frames available to capture {}. Will be captured {} frames" + .format(count_frames_available, count_frames_available)) + count = count_frames_available + + return count diff --git a/UniTAP/dev/ports/modules/dpcd/__init__.py b/UniTAP/dev/ports/modules/dpcd/__init__.py new file mode 100644 index 0000000..d59b403 --- /dev/null +++ b/UniTAP/dev/ports/modules/dpcd/__init__.py @@ -0,0 +1 @@ +from .dpcd import DPCDRegisters, DPCDRegion diff --git a/UniTAP/dev/ports/modules/dpcd/dpcd.py b/UniTAP/dev/ports/modules/dpcd/dpcd.py new file mode 100644 index 0000000..03aac7b --- /dev/null +++ b/UniTAP/dev/ports/modules/dpcd/dpcd.py @@ -0,0 +1,187 @@ +import os +from typing import Union, Tuple + +from UniTAP.libs.lib_tsi.tsi import * +from UniTAP.libs.lib_tsi.tsi_io import PortIO + + +class DPCDRegion: + + """ + Class `DPCDRegion` describe a byte range of DPCD registers. + Allows saving DPCD data to dpd file format `save_to_dpd`, hex files format `save_to_hex`, + dsc file format `save_to_csv`. + + Arguments: + base - start DPCD address of byte range. + data - DPCD data of byte range. + """ + + def __init__(self, base: int, data: Union[bytearray, int]): + + if not isinstance(data, bytearray): + self.data = bytearray() + self.data.append(data) + else: + self.data = data + self.base = base + + @property + def size(self) -> int: + """ + + Returns length fo data. + + Returns: + result of int object + """ + return len(self.data) + + def save_to_dpd(self, path: str): + """ + + Save DPCD data to 'dpd' file format. + + Args: + path (str) - full path to file + + """ + if not path.lower().endswith('.dpd'): + path += '.DPD' + with open(path, 'bw') as file: + dpd_mark = 1 + file.write(dpd_mark.to_bytes(4, 'little')) + file.write(self.base.to_bytes(4, 'little')) + file.write(len(self.data).to_bytes(4, 'little')) + file.write(self.data) + + def save_to_hex(self, path: str): + """ + + Save DPCD data to 'hex' file format. + + Args: + path (str) - full path to file + + """ + if not path.lower().endswith('.hex'): + path += '.HEX' + with open(path, 'w') as file: + file.write(f"\nRange 1. Start {self.base:#0{8}x}, Length {len(self.data):#0{8}x}\n") + file.write( + '\n'.join( + ' '.join( + f'{value:#0{2}x}' for value in self.data[i:i + 16]) + for i in range(0, len(self.data), 16) + ) + ) + + def save_to_csv(self, path: str): + """ + + Save DPCD data to 'csv' file format. + + Args: + path (str) - full path to file + + """ + if not path.lower().endswith('.csv'): + path += '.csv' + with open(path, 'w') as file: + file.write("Address (A), Data A+0, Data A+1, Data A+2, Data A+3, Data A+4, Data A+5," + " Data A+6, Data A+7\n") + file.write( + '\n'.join( + f'{self.base + 8 * i:#0{8}x},' + ','.join(f'{value:#0{2}x}' + for value in self.data[i:i + 8]) + for i in range(0, len(self.data), 8) + ) + ) + + +class DPCDRegisters: + + """ + Class `DPCDRegisters` allows working with DPCD registers: writing `write` DPCD data to device, + reading `read` DPCD data from device, loading `load_from_file` DPCD data from file. + """ + + def __init__(self, port_io: PortIO, base_ci: int, data_ci: int): + self.__io = port_io + self.__base_ci = base_ci + self.__data_ci = data_ci + + def write(self, base: int, data: Union[bytearray, int, list]) -> int: + """ + + Write transferred DPCD data to device from base address. + + Args: + base (int) - start (base) address. + data (Union[bytearray, int]) - DPCD data. + + Returns: + result of operation + """ + if isinstance(data, int): + data = bytearray(data.to_bytes(1, "big")) + elif isinstance(data, list): + data = bytearray(data) + + result = self.__io.set(self.__base_ci, base) + if result < TSI_SUCCESS: + return result + result = self.__io.set(self.__data_ci, data, c_ubyte, len(data)) + return result + + def read(self, base: int, count: int) -> DPCDRegion: + """ + + Read DPCD data from base address in a certain quantity. + + Args: + base (int) - start (base) address. + count (int) - quantity of DPCD bytes. + + Returns: + object of `DPCDRegion` + """ + self.__io.set(self.__base_ci, base) + + result = self.__io.get(self.__data_ci, c_ubyte, count) + + return DPCDRegion(base, result[1]) + + @staticmethod + def load_from_file(path: str) -> Tuple[DPCDRegion, DPCDRegion]: + """ + + Read DPCD data from file. + Supported formats: + - DPD. + + Args: + path (str) - full path to file. + + Returns: + object of tuple with two `DPCDRegion` objects + """ + if not os.path.exists(path): + raise FileNotFoundError(f"DPCD load_from_file function can't find file: {path}") + + if '.DPD' not in os.path.splitext(path): + raise TypeError("Wrong file extension. It should be .DPD") + + with open(path, 'rb') as file: + file.read(4) + base1 = int.from_bytes(file.read(4)[::-1], 'little') + size1 = int.from_bytes(file.read(4)[::-1], 'little') + data1 = bytearray(file.read(size1)) + region1 = DPCDRegion(base1, data1) + + base2 = int.from_bytes(file.read(4)[::-1], 'little') + size2 = int.from_bytes(file.read(4)[::-1], 'little') + data2 = bytearray(file.read(size2)) + region2 = DPCDRegion(base2, data2) + + return region1, region2 diff --git a/UniTAP/dev/ports/modules/edid/__init__.py b/UniTAP/dev/ports/modules/edid/__init__.py new file mode 100644 index 0000000..c1b0dcb --- /dev/null +++ b/UniTAP/dev/ports/modules/edid/__init__.py @@ -0,0 +1,2 @@ +from .edid_parser import EdidParser, MainBlockType, AdditionalBlockType +from .edid import EdidFileType, DisplayIDReadMode diff --git a/UniTAP/dev/ports/modules/edid/edid.py b/UniTAP/dev/ports/modules/edid/edid.py new file mode 100644 index 0000000..7d86d8d --- /dev/null +++ b/UniTAP/dev/ports/modules/edid/edid.py @@ -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() \ No newline at end of file diff --git a/UniTAP/dev/ports/modules/edid/edid_parser.py b/UniTAP/dev/ports/modules/edid/edid_parser.py new file mode 100644 index 0000000..3156989 --- /dev/null +++ b/UniTAP/dev/ports/modules/edid/edid_parser.py @@ -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() diff --git a/UniTAP/dev/ports/modules/edid/edid_private_types.py b/UniTAP/dev/ports/modules/edid/edid_private_types.py new file mode 100644 index 0000000..03b5342 --- /dev/null +++ b/UniTAP/dev/ports/modules/edid/edid_private_types.py @@ -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 diff --git a/UniTAP/dev/ports/modules/edid/edid_types.py b/UniTAP/dev/ports/modules/edid/edid_types.py new file mode 100644 index 0000000..bc81283 --- /dev/null +++ b/UniTAP/dev/ports/modules/edid/edid_types.py @@ -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 diff --git a/UniTAP/dev/ports/modules/edid/edid_utils.py b/UniTAP/dev/ports/modules/edid/edid_utils.py new file mode 100644 index 0000000..38a7cca --- /dev/null +++ b/UniTAP/dev/ports/modules/edid/edid_utils.py @@ -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) diff --git a/UniTAP/dev/ports/modules/fec/__init__.py b/UniTAP/dev/ports/modules/fec/__init__.py new file mode 100644 index 0000000..877be94 --- /dev/null +++ b/UniTAP/dev/ports/modules/fec/__init__.py @@ -0,0 +1,3 @@ +from .fec_tx import FecTx +from .fec_rx import FecRx +from .fec_shared import FECCounters, FECErrorType128b132b, FECErrorType8b10b diff --git a/UniTAP/dev/ports/modules/fec/fec_rx.py b/UniTAP/dev/ports/modules/fec/fec_rx.py new file mode 100644 index 0000000..28cb318 --- /dev/null +++ b/UniTAP/dev/ports/modules/fec/fec_rx.py @@ -0,0 +1,131 @@ +from UniTAP.libs.lib_tsi.tsi import * +from UniTAP.libs.lib_tsi.tsi_io import PortIO + +from .fec_shared import FECCounters +from UniTAP.dev.ports.modules.dpcd.dpcd import DPCDRegisters + + +class FecRx: + """ + Class `FecRx` allows working with FEC functionality from Sink (RX - receiver) side. You can: + - Check capable FEC or not `is_capable`. + - Check enabled FEC or not `is_enabled`. + - Enable/Disable FEC `enable`. + - Enable/Disable calculating sum of errors `aggregate_errors`. + - Get error counters `get_error_counters`. + - Clear all errors `clear`. + """ + def __init__(self, port_io: PortIO, dpcd: DPCDRegisters): + self.__io = port_io + self.__dpcd = dpcd + self.__aggregate_error = 0 + + def is_enabled(self) -> bool: + """ + Returns status of FEC, is enabled or not. + + Returns: + object of `bool` type. + """ + result = self.__io.get(TSI_DPRX_FEC_STATUS_R, c_int) + status_fec = ((result[1] & 0x1) != 0) + return bool(status_fec) + + def is_capable(self) -> bool: + """ + Returns status of FEC, is capable or not. + + Returns: + object of `bool` type. + """ + result = self.__io.get(TSI_DPRX_FEC_CTRL, c_int) + enabled_fec = (result[1] & 0x1) != 0 + return enabled_fec + + def enable(self, enable: bool): + """ + Enable/Disable FEC. + + Args: + enable (bool) - enable (True) or disable (False) + """ + val = 0x1 if enable else 0x0 + self.__io.set(TSI_DPRX_FEC_CTRL, val) + + def aggregate_errors(self, enable: bool): + """ + Enable/Disable calculating sum of errors. + + Args: + enable (bool) - enable (True) or disable (False) + """ + result = self.__io.get(TSI_DPRX_FEC_CONTROL, c_int) + val = result[1] + if enable: + val |= 0x2 + self.__aggregate_error = 1 + else: + val &= ~0x2 + self.__aggregate_error = 0 + self.__io.set(TSI_DPRX_FEC_CONTROL, val) + + def get_error_counters(self) -> FECCounters: + """ + + Get current error counters. + + Returns: + object of `FECCounters` type + """ + result = FECCounters() + lane_count = self.__io.get(TSI_R_DPRX_LINK_LANE_COUNT, c_int)[1] + if lane_count == 4: + lane_count += 1 if self.__aggregate_error else 0 + + dpcd = self.__dpcd.read(0x120, 1).data[0] + dpcdaggr = bool(dpcd & (1 << 6)) + + for i in range(lane_count): + if i == 0 and dpcdaggr: + dpcd &= ~(1 << 6) + + if i == 4 and dpcdaggr: + dpcd |= 1 << 6 + elif i == 4: + continue + + if i < 4: + dpcd &= ~(0x3 << 4) + dpcd |= (i << 4) + + for j in range(1, 6): + dpcd &= ~(0x7 << 1) + v = dpcd | (j << 1) + self.__dpcd.write(0x120, v) + val = int.from_bytes(self.__dpcd.read(0x281, 2).data, 'little') + + if j == 1: + result.uncorrectedBlockErrors[i] = val & 0x7FFF if val & 0x8000 else None + elif j == 2: + result.correctedBlockErrors[i] = val & 0x7FFF if val & 0x8000 else None + elif j == 3: + result.bitErrors[i] = val & 0x7FFF if val & 0x8000 else None + elif j == 4: + result.parityBlockErrors[i] = val & 0x7FFF if val & 0x8000 else None + elif j == 5: + result.parityBitErrors[i] = val & 0x7FFF if val & 0x8000 else None + + return result + + def clear(self): + """ + Clear all errors. + """ + address = 0x120 + dpcd = self.__dpcd.read(address, 1).data[0] + old_fec = dpcd + fec_ready = dpcd & ~0xE + dpcd = fec_ready + self.__dpcd.write(address, dpcd) + dpcd = old_fec + self.__dpcd.write(address, dpcd) diff --git a/UniTAP/dev/ports/modules/fec/fec_shared.py b/UniTAP/dev/ports/modules/fec/fec_shared.py new file mode 100644 index 0000000..449d45c --- /dev/null +++ b/UniTAP/dev/ports/modules/fec/fec_shared.py @@ -0,0 +1,38 @@ +from enum import IntEnum + + +class FECCounters: + """ + Class `FECCounters` possible errors: + - Uncorrected block errors. + - Corrected block errors. + - Bit errors. + - Parity block errors. + - Parity bit errors. + """ + def __init__(self): + self.uncorrectedBlockErrors = [None, None, None, None, None] + self.correctedBlockErrors = [None, None, None, None, None] + self.bitErrors = [None, None, None, None, None] + self.parityBlockErrors = [None, None, None, None, None] + self.parityBitErrors = [None, None, None, None, None] + + +class FECErrorType8b10b(IntEnum): + """ + Describes possible FEC 8b/10b errors. + """ + UNCORRECTED_BLOCK = 0 + CORRECTED_BLOCK = 1 + CORRECTED_PARITY = 2 + CORRECTED_BLOCK_1 = 3 + CORRECTED_PARITY_1 = 4 + + +class FECErrorType128b132b(IntEnum): + """ + Describes possible FEC 128b/132b errors. + """ + UNCORRECTED_BLOCK = 0 + CORRECTED_BLOCK_4 = 1 + CORRECTED_BLOCK_2 = 3 diff --git a/UniTAP/dev/ports/modules/fec/fec_tx.py b/UniTAP/dev/ports/modules/fec/fec_tx.py new file mode 100644 index 0000000..5ef27d4 --- /dev/null +++ b/UniTAP/dev/ports/modules/fec/fec_tx.py @@ -0,0 +1,204 @@ +from UniTAP.libs.lib_tsi.tsi import * +from UniTAP.libs.lib_tsi.tsi_io import PortIO + +from .fec_shared import FECCounters, FECErrorType8b10b, FECErrorType128b132b +from UniTAP.dev.ports.modules.dpcd.dpcd import DPCDRegisters +from typing import Union + + +class FecTx: + """ + Class `FecTx` allows working with FEC functionality from Source (TX - transmitter) side. You can: + - Check enabled FEC or not `is_enabled`. + - Check state that FEC is prefers after link training `is_prefer_after_lt`. + - Enable/Disable FEC `enable`. + - Enable/Disable intent FEC `enable_intent`. + - Enable/Disable calculating sum of errors `aggregate_errors`. + - Get error counters `get_error_counters`. + - Clear all errors `clear`. + """ + def __init__(self, port_io: PortIO, dpcd: DPCDRegisters): + self.__io = port_io + self.__dpcd = dpcd + self.__aggregate_error = 0 + + def is_enabled(self) -> bool: + """ + Returns status of FEC, is enabled or not. + + Returns: + object of `bool` type. + """ + result = self.__io.get(TSI_DPTX_FEC_STATUS_R, c_int) + status_fec = ((result[1] & 0x1) != 0) + return bool(status_fec) + + def is_prefer_after_lt(self) -> bool: + """ + Check state that FEC is prefers after link training. + + Returns: + object of `bool` type. + """ + result = self.__io.get(TSI_DPTX_FEC_CTRL, c_int) + enabled_fec = (result[1] & 0x2) != 0 + return enabled_fec + + def enable(self, enable: bool): + """ + Enable/Disable FEC. + + Args: + enable (bool) - enable (True) or disable (False) + """ + result = self.__io.get(TSI_DPTX_FEC_CTRL, c_int) + val = result[1] + val |= 0x8 if enable else 0x10 + self.__io.set(TSI_DPTX_FEC_CTRL, val) + + def enable_intent(self, enable: bool): + """ + Enable/Disable intent FEC. + + Args: + enable (bool) - enable (True) or disable (False) + """ + result = self.__io.get(TSI_DPTX_FEC_CTRL, c_int) + val = result[1] + val |= 1 if enable else 4 + self.__io.set(TSI_DPTX_FEC_CTRL, val) + + def aggregate_errors(self, enable: bool): + """ + Enable/Disable calculating sum of errors. + + Args: + enable (bool) - enable (True) or disable (False) + """ + result = self.__io.get(TSI_DPTX_FEC_CONTROL, c_int) + val = result[1] + if enable: + val |= 0x40 + self.__aggregate_error = 1 + else: + val &= ~0x40 + self.__aggregate_error = 0 + self.__io.set(TSI_DPTX_FEC_CONTROL, val) + + def generate_errors(self, error_type: Union[FECErrorType8b10b, FECErrorType128b132b], lane: list, ms: int = 100): + """ + Generate FEC errors. + + Args: + error_type (Union[`FECErrorType8b10b`, `FECErrorType128b132b`]) + lane (list) + ms (int) - time in m seconds + """ + result, status = self.__io.get(TSI_DPTX_LINK_MODE_R, c_int) + resut, hw_caps, size = self.__io.get(TSI_DPTX_HW_CAPS_R, c_uint32, 4) + if status == 0: + if isinstance(error_type, FECErrorType128b132b): + assert False, "This device doesn't support 128b/132b" if (hw_caps[1] & 0x7) == 0 else "Change link mode!" + if status == 1: + if isinstance(error_type, FECErrorType8b10b): + assert False, "This device doesn't support 8b/10b" if (hw_caps[1] & 0x7) == 0 else "Change link mode!" + + dpcd = self.__dpcd.read(0x120, 1).data[0] + dpcd |= 2 + self.__dpcd.write(0x120, dpcd) + + delay = ms * 100 + + self.__io.set(TSI_MLEG_CONTROL, 0) + nl = self.__io.get(TSI_R_DPTX_LINK_STATUS_LANE_COUNT, c_uint32)[1] + + self.__io.set(TSI_MLEG_SYMBOL_REPLACE_A, 0x00010000) + self.__io.set(TSI_MLEG_SYMBOL_REPLACE_MASK_A, 0x00010001) + self.__io.set(TSI_MLEG_SYMBOL_REPLACE_B, 0x00000001) + self.__io.set(TSI_MLEG_SYMBOL_REPLACE_MASK_B, 0x00010001) + + self.__io.set(TSI_MLEG_DELAY_COUNTER, delay) + + n = lane[0] << 16 + self.__io.set(TSI_MLEG_LANE0_REPLACE_COUNTERS, n) + n = lane[1] << 16 + self.__io.set(TSI_MLEG_LANE1_REPLACE_COUNTERS, n) + n = lane[2] << 16 + self.__io.set(TSI_MLEG_LANE2_REPLACE_COUNTERS, n) + n = lane[3] << 16 + self.__io.set(TSI_MLEG_LANE3_REPLACE_COUNTERS, n) + + ctrl = 0 + ctrl |= (error_type.value << 16) + if nl == 1: + ctrl |= (1 << 13) + ctrl |= (1 << 12) + ctrl |= (0xF << 4) + + if lane[0]: + ctrl |= (1 << 0) + if lane[1]: + ctrl |= (1 << 1) + if lane[2]: + ctrl |= (1 << 2) + if lane[3]: + ctrl |= (1 << 3) + + self.__io.set(TSI_MLEG_CONTROL, ctrl) + + def get_error_counters(self) -> FECCounters: + """ + Get current error counters. + + Returns: + object of `FECCounters` type + """ + result = FECCounters() + lane_count = self.__io.get(TSI_R_DPTX_LINK_STATUS_LANE_COUNT, c_uint32)[1] + if lane_count == 4: + lane_count += 1 if self.__aggregate_error else 0 + + dpcd = self.__dpcd.read(0x120, 1).data[0] + dpcdaggr = bool(dpcd & (1 << 6)) + + for i in range(lane_count): + if i == 0 and dpcdaggr: + dpcd &= ~(1 << 6) + + if i == 4 and dpcdaggr: + dpcd |= 1 << 6 + elif i == 4: + continue + + if i < 4: + dpcd &= ~(0x3 << 4) + dpcd |= (i << 4) + + for j in range(1, 6): + dpcd &= ~(0x7 << 1) + v = dpcd | (j << 1) + self.__dpcd.write(0x120, v) + val = int.from_bytes(self.__dpcd.read(0x281, 2).data, 'little') + + if j == 1: + result.uncorrectedBlockErrors[i] = val & 0x7FFF if val & 0x8000 else None + elif j == 2: + result.correctedBlockErrors[i] = val & 0x7FFF if val & 0x8000 else None + elif j == 3: + result.bitErrors[i] = val & 0x7FFF if val & 0x8000 else None + elif j == 4: + result.parityBlockErrors[i] = val & 0x7FFF if val & 0x8000 else None + elif j == 5: + result.parityBitErrors[i] = val & 0x7FFF if val & 0x8000 else None + + return result + + def clear(self): + """ + Clear all errors. + """ + address = 0x120 + dpcd = self.__dpcd.read(address, 1).data[0] + fec_ready = dpcd & ~0xE + dpcd = fec_ready + self.__dpcd.write(address, dpcd) diff --git a/UniTAP/dev/ports/modules/hdcp/__init__.py b/UniTAP/dev/ports/modules/hdcp/__init__.py new file mode 100644 index 0000000..bb93044 --- /dev/null +++ b/UniTAP/dev/ports/modules/hdcp/__init__.py @@ -0,0 +1,4 @@ +from .hdcp_tx import HdcpSource, HdcpSourceStatus, HdcpSourceConfig +from .hdcp_rx import HdcpSink, HdcpSinkStatus, HdcpSinkConfig +from .types import HdcpMode, HdcpRxConfig, HdcpTxConfig, HdcpStatus, HdcpSink1XKeys, HdcpSink2XKeys, HdcpSource1XKeys, \ + HdcpSource2XKeys diff --git a/UniTAP/dev/ports/modules/hdcp/hdcp_rx.py b/UniTAP/dev/ports/modules/hdcp/hdcp_rx.py new file mode 100644 index 0000000..dabea42 --- /dev/null +++ b/UniTAP/dev/ports/modules/hdcp/hdcp_rx.py @@ -0,0 +1,246 @@ +import warnings +from typing import Optional, Union + +from UniTAP.libs.lib_tsi.tsi_io import PortIO, PortProtocol +from .types import * +from UniTAP.libs.lib_tsi.tsi import c_int + + +class HdcpSinkStatus: + + """ + + Class `HdcpSinkStatus` contains information about HDCP 1.4 and 2.3 statuses. + If you want to get object of one the status, use function `get`. + + """ + + def __init__(self, port_io: PortIO, ci_status_control: int, caps_1x: HdcpHwSinkCaps, caps_2x: HdcpHwSinkCaps): + super().__init__() + self.__io = port_io + self.__ci_status_control = ci_status_control + self.__caps_1x = caps_1x + self.__caps_2x = caps_2x + + def get(self, hdcp_mode: Type[HdcpStatusType]) -> HdcpStatusType: + """ + + Returns one of possible HDCP Status Type: + - Status1x (HDCP 1.4). + - StatusRx2x (HDCP 2.3). + + Object contains info about: + - HDCP keys (HdcpSink1XKeys if HDCP type 1.4; HdcpSink2XKeys if HDCP type 2.3). + - Active state (True or False). + - Authenticated state (True or False). + - Capable state (True or False). + + Returns: + object of `HdcpStatusType` (`Status1x` or `StatusRx2x`) + """ + if hdcp_mode not in [HdcpStatus.Status1x, HdcpStatus.StatusRx2x]: + raise TypeError(f"Wrong type HDCP status, provided type {hdcp_mode}") + + if hdcp_mode == HdcpStatus.Status1x: + if self.__caps_1x.hw_supported: + status_value = self.__read_status(HdcpMode.Mode1_4) + status = HdcpStatus.Status1x() + status.active = status_value & 1 + status.keys = HdcpSink1XKeys((status_value >> 1) & 3) + status.capable = ((status_value >> 3) & 1) + status.authenticated = ((status_value >> 4) & 1) + return status + else: + warnings.warn(f"Not supported 'HDCP 1.4'") + elif hdcp_mode == HdcpStatus.StatusRx2x: + if self.__caps_2x.hw_supported: + status_value = self.__read_status(HdcpMode.Mode2_3) + status = HdcpStatus.StatusRx2x() + if self.__io.protocol() == PortProtocol.HDMI: + status.keys = HdcpSink2XKeys.Production if (status_value >> 9) & 1 else HdcpSink2XKeys.Unload + else: + if (status_value >> 11) & 1: + status.keys = HdcpSink2XKeys.TestR1 + elif (status_value >> 13) & 1: + status.keys = HdcpSink2XKeys.TestR2 + elif (status_value >> 9) & 1: + status.keys = HdcpSink2XKeys.Production + else: + status.keys = HdcpSink2XKeys.Unload + + status.active = (status_value >> 8) & 1 + status.capable = (status_value >> 10) & 1 + status.authenticated = (status_value >> 12) & 1 + return status + else: + warnings.warn(f"Not supported 'HDCP 2.3'") + + def __read_status(self, mode: HdcpMode) -> int: + if mode == HdcpMode.Mode1_4: + return self.__io.get(TSI_HDCP_1X_STATUS_R, c_int)[1] + else: + return self.__io.get(self.__ci_status_control, c_int)[1] + + +class HdcpSinkConfig: + + """ + Class `HdcpSinkConfig` contains information about HDCP 1.4 and 2.3 configurations. + If you want to set configuration, use function `set`. + If you want to get current config, use function `get`. + """ + + def __init__(self, port_io: PortIO, caps_1x: HdcpHwSinkCaps, caps_2x: HdcpHwSinkCaps, status: HdcpSinkStatus): + self.__io = port_io + self.__caps_1x = caps_1x + self.__caps_2x = caps_2x + self.__status = status + + def set(self, config: HdcpRxConfigType): + """ + + This function is used to set the HDCP on Sink (RX - receiver) side. + Possible to load HDCP keys and enable/disable HDCP. + + Args: + config (HdcpRxConfigType) - one of the available HDCP config type: Config1x (HDCP 1.4), Config2x (HDCP 2.3) + """ + if isinstance(config, HdcpRxConfig.Config1x): + if self.__caps_1x.hw_supported: + self.__load_keys(config.keys, HdcpMode.Mode1_4) + self.__set_capable(config.capable, HdcpMode.Mode1_4) + else: + warnings.warn(f"Not supported 'HDCP 1.4'") + elif isinstance(config, HdcpRxConfig.Config2x): + if self.__caps_2x.hw_supported: + self.__load_keys(config.keys, HdcpMode.Mode2_3) + self.__set_capable(config.capable, HdcpMode.Mode2_3) + else: + warnings.warn(f"Not supported 'HDCP 2.3'") + + def get(self, hdcp_mode: Type[HdcpRxConfigType]) -> HdcpRxConfigType: + """ + + This function is used to get current HDCP configuration on Sink (RX - receiver) side. + + Returns: + object of HdcpRxConfigType - one of the available HDCP config type: Config1x (HDCP 1.4), Config2x (HDCP 2.3) + """ + if hdcp_mode == HdcpRxConfig.Config1x: + if self.__caps_1x.hw_supported: + config = HdcpRxConfig.Config1x() + status = self.__status.get(HdcpStatus.Status1x) + config.keys = status.keys + config.capable = status.capable + return config + else: + warnings.warn(f"Not supported 'HDCP 1.4'") + elif hdcp_mode == HdcpRxConfig.Config2x: + if self.__caps_2x.hw_supported: + config = HdcpRxConfig.Config2x() + status = self.__status.get(HdcpStatus.StatusRx2x) + config.keys = status.keys + config.capable = status.capable + return config + else: + warnings.warn(f"Not supported 'HDCP 2.3'") + + def __load_keys(self, keys: Optional[Union[HdcpSink1XKeys, HdcpSink2XKeys]], hdcp_mode: HdcpMode): + if keys is None: + return + if not self.__full_check(hdcp_mode): + return + + if hdcp_mode == HdcpMode.Mode1_4 and not isinstance(keys, HdcpSink1XKeys): + raise TypeError("Was selected HDCP 1.4 mode. Need to use 'HdcpSink1XKeys' type.") + elif hdcp_mode == HdcpMode.Mode1_4 and isinstance(keys, HdcpSink1XKeys): + if keys == HdcpSink1XKeys.Production and not self.__caps_1x.production_keys_available: + warnings.warn("Production keys is not hw supported. Please, select another keys.") + elif keys == HdcpSink1XKeys.Test and not self.__caps_1x.test_keys_available: + warnings.warn("Test keys is not hw supported. Please, select another keys.") + elif hdcp_mode == HdcpMode.Mode2_3 and not isinstance(keys, HdcpSink2XKeys): + raise TypeError("Was selected HDCP 2.3 mode. Need to use 'HdcpSink2XKeys' type.") + elif hdcp_mode == HdcpMode.Mode2_3 and isinstance(keys, HdcpSink2XKeys): + if keys == HdcpSink2XKeys.Production and not self.__caps_2x.production_keys_available: + warnings.warn("Production keys is not hw supported. Please, select another keys.") + + self.__io.set(TSI_HDCP_1X_COMMAND_W if hdcp_mode == HdcpMode.Mode1_4 else TSI_HDCP_2X_COMMAND_W, keys.value) + + def __set_capable(self, value: Optional[bool], hdcp_mode: HdcpMode): + if value is None: + return + if not self.__full_check(hdcp_mode): + return + + self.__io.set(TSI_HDCP_1X_COMMAND_W if hdcp_mode == HdcpMode.Mode1_4 else TSI_HDCP_2X_COMMAND_W, + (H1_SINK_SET_CAPABLE if value else H1_SINK_CLEAR_CAPABLE) + if hdcp_mode == HdcpMode.Mode1_4 else (H2_SINK_SET_CAPABLE if value else H2_SINK_CLEAR_CAPABLE)) + + def __full_check(self, hdcp_mode: HdcpMode) -> bool: + if hdcp_mode == HdcpMode.Unknown: + warnings.warn(f"Did not select HDCP mode. Please, select it from available variants: " + f"{'HDCP 1.4 ' if self.__caps_1x.hw_supported else ''}" + f"{'HDCP 2.3 ' if self.__caps_2x.hw_supported else ''}") + return False + + if not self.__availability_mode_check(hdcp_mode): + warnings.warn(f"Selected HDCP mode is not HW supported. Please, select it from available variants: " + f"{'HDCP 1.4 ' if self.__caps_1x.hw_supported else ''}" + f"{'HDCP 2.3 ' if self.__caps_2x.hw_supported else ''}") + return False + + return True + + def __availability_mode_check(self, hdcp_mode: HdcpMode) -> bool: + if hdcp_mode == HdcpMode.Mode1_4 and self.__caps_1x.hw_supported: + return True + elif hdcp_mode == HdcpMode.Mode2_3 and self.__caps_2x.hw_supported: + return True + else: + return False + + +class HdcpSink: + + """ + Main class contains info of HDCP on Sink (RX - receiver) side. + If you need to configurate HDCP, use `config` for getting object responsible for the configuration. + If you need to read HDCP status, use `status` for getting object responsible for the reading current status. + """ + + def __init__(self, port_io: PortIO, ci_caps_control: int, ci_status_control: int): + self.__io = port_io + self.__hw_caps_1x = HdcpHwSinkCaps(self.__read_hw_caps(HdcpMode.Mode1_4), HdcpMode.Mode1_4) + self.__hw_caps_2x = HdcpHwSinkCaps(self.__read_hw_caps(HdcpMode.Mode2_3, ci_caps_control), HdcpMode.Mode2_3) + self.__status = HdcpSinkStatus(port_io, ci_status_control, self.__hw_caps_1x, self.__hw_caps_2x) + self.__config = HdcpSinkConfig(port_io, self.__hw_caps_1x, self.__hw_caps_2x, self.__status) + + @property + def config(self) -> HdcpSinkConfig: + """ + + Should be used to configure HDCP on Sink (RX - receiver) role. + + Returns: + object of `HdcpSinkConfig`. + """ + return self.__config + + @property + def status(self) -> HdcpSinkStatus: + """ + + Should be used to read HDCP current status on Sink (RX - receiver) role. + + Returns: + object of `HdcpSinkStatus`. + """ + return self.__status + + def __read_hw_caps(self, mode: HdcpMode, ci_caps_control: int = 0) -> int: + if mode == HdcpMode.Mode1_4: + return self.__io.get(TSI_HDCP_1X_STATUS_R, c_int)[1] + elif mode == HdcpMode.Mode2_3: + return self.__io.get(ci_caps_control, c_int)[1] + else: + return 0 diff --git a/UniTAP/dev/ports/modules/hdcp/hdcp_tx.py b/UniTAP/dev/ports/modules/hdcp/hdcp_tx.py new file mode 100644 index 0000000..f252c0e --- /dev/null +++ b/UniTAP/dev/ports/modules/hdcp/hdcp_tx.py @@ -0,0 +1,288 @@ +import warnings +from typing import Optional, Union + +from UniTAP.libs.lib_tsi.tsi_io import PortIO, PortProtocol +from .types import * +from UniTAP.libs.lib_tsi.tsi_private_types import * +from ctypes import c_int + + +class HdcpSourceStatus: + + """ + + Class `HdcpSourceStatus` contains information about HDCP 1.4 and 2.3 statuses. + If you want to get object of one the status, use function `get`. + + """ + + def __init__(self, port_io: PortIO, ci_status_control: int, caps_1x: HdcpHwSourceCaps, caps_2x: HdcpHwSourceCaps): + super().__init__() + self.__io = port_io + self.__ci_status_control = ci_status_control + self.__caps_1x = caps_1x + self.__caps_2x = caps_2x + + def get(self, hdcp_mode: Type[HdcpStatusType]) -> HdcpStatusType: + """ + + Returns one of possible HDCP Status Type: + - Status1x (HDCP 1.4). + - StatusTx2x (HDCP 2.3). + + Object contains info about: + - HDCP keys (HdcpSource1XKeys if HDCP type 1.4; HdcpSource2XKeys if HDCP type 2.3). + - Active state (True or False). + - Authenticated state (True or False). + - Capable state (True or False). + + If HDCP type is 2.3, then it contains more information: + - KM stored state (True or False). + - Try to authenticate state (True or False). + - Try to encrypt state (True or False). + - Content level. + + Returns: + object of `HdcpStatusType` (`Status1x` or `StatusTx2x`) + """ + if hdcp_mode not in [HdcpStatus.Status1x, HdcpStatus.StatusTx2x]: + raise TypeError(f"Wrong type HDCP status, provided type {hdcp_mode}") + + if hdcp_mode == HdcpStatus.Status1x: + if self.__caps_1x.hw_supported: + status_value = self.__read_status(HdcpMode.Mode1_4) + status = HdcpStatus.Status1x() + status.active = (status_value >> 8) & 1 + status.keys = HdcpSource1XKeys(((status_value >> 9) & 3) + 0x100) + status.capable = ((status_value >> 11) & 1) + status.authenticated = ((status_value >> 12) & 1) + return status + else: + warnings.warn(f"Not supported 'HDCP 1.4'") + elif hdcp_mode == HdcpStatus.StatusTx2x: + if self.__caps_2x.hw_supported: + status_value = self.__read_status(HdcpMode.Mode2_3) + status = HdcpStatus.StatusTx2x() + if self.__io.protocol() == PortProtocol.HDMI: + status.keys = HdcpSource2XKeys.Production if (status_value >> 9) & 1 else HdcpSource2XKeys.Unload + else: + if (status_value >> 14) & 1: + status.keys = HdcpSource2XKeys.TestR1 + elif (status_value >> 15) & 1: + status.keys = HdcpSource2XKeys.TestR2 + elif (status_value >> 9) & 1: + status.keys = HdcpSource2XKeys.Production + else: + status.keys = HdcpSource2XKeys.Unload + + status.km_is_stored = (status_value >> 13) & 1 + status.try_authenticate = (status_value >> 18) & 1 + status.try_encrypt = (status_value >> 19) & 1 + status.content_level = self.__io.get(TSI_HDCP_2X_CFG, c_uint32)[1] + + status.active = (status_value >> 8) & 1 + status.capable = (status_value >> 10) & 1 + status.authenticated = (status_value >> 12) & 1 + + return status + else: + warnings.warn(f"Not supported 'HDCP 2.3'") + + def __read_status(self, mode: HdcpMode): + if mode == HdcpMode.Mode1_4: + return self.__io.get(TSI_HDCP_1X_STATUS_R, c_int)[1] + elif mode == HdcpMode.Mode2_3: + return self.__io.get(self.__ci_status_control, c_int)[1] + else: + return 0 + + +class HdcpSourceConfig: + + """ + Class `HdcpSourceConfig` contains information about HDCP 1.4 and 2.3 configurations. + If you want to set configuration, use function `set`. + If you want to get current config, use function `get`. + """ + + def __init__(self, port_io: PortIO, caps_1x: HdcpHwSourceCaps, caps_2x: HdcpHwSourceCaps, status: HdcpSourceStatus): + self.__io = port_io + self.__caps_1x = caps_1x + self.__caps_2x = caps_2x + self.__status = status + + def set(self, config: HdcpTxConfigType): + """ + + This function is used to set the HDCP on Source (TX - transmitter) side. + Possible to load HDCP keys and enable/disable HDCP. + + Args: + config (HdcpTxConfigType) - one of the available HDCP config type: Config1x (HDCP 1.4), Config2x (HDCP 2.3) + """ + if isinstance(config, HdcpTxConfig.Config1x): + if self.__caps_1x.hw_supported: + self.__load_keys(config.keys, HdcpMode.Mode1_4) + self.__set_authenticate(config.authenticate, HdcpMode.Mode1_4) + self.__set_encryption(config.encryption, HdcpMode.Mode1_4) + else: + warnings.warn(f"Not supported 'HDCP 1.4'") + elif isinstance(config, HdcpTxConfig.Config2x): + if self.__caps_2x.hw_supported: + self.__load_keys(config.keys, HdcpMode.Mode2_3) + self.__set_content_level(config.content_level) + self.__set_authenticate(config.authenticate, HdcpMode.Mode2_3) + self.__set_store_km(config.store_km) + self.__set_encryption(config.encryption, HdcpMode.Mode2_3) + else: + warnings.warn(f"Not supported 'HDCP 2.3'") + + def get(self, hdcp_mode: Type[HdcpTxConfigType]) -> HdcpTxConfigType: + """ + + This function is used to get current HDCP configuration on Source (TX - transmitter) side. + + Returns: + object of HdcpTxConfigType - one of the available HDCP config type: Config1x (HDCP 1.4), Config2x (HDCP 2.3) + """ + if hdcp_mode == HdcpTxConfig.Config1x: + if self.__caps_1x.hw_supported: + config = HdcpTxConfig.Config1x() + status = self.__status.get(HdcpStatus.Status1x) + config.keys = status.keys + config.encryption = status.active + config.authenticate = status.authenticated + return config + else: + warnings.warn(f"Not supported 'HDCP 1.4'") + elif hdcp_mode == HdcpTxConfig.Config2x: + if self.__caps_2x.hw_supported: + config = HdcpTxConfig.Config2x() + status = self.__status.get(HdcpStatus.StatusTx2x) + config.keys = status.keys + config.encryption = status.try_encrypt + config.authenticate = status.try_authenticate + config.store_km = status.km_is_stored + config.content_level = status.content_level + return config + else: + warnings.warn(f"Not supported 'HDCP 2.3'") + + def __set_encryption(self, value: Optional[bool], hdcp_mode: HdcpMode): + if value is None: + return + if hdcp_mode == HdcpMode.Mode1_4: + self.__io.set(TSI_HDCP_1X_COMMAND_W, H1_SOURCE_ENABLE_ENCRYPT if value else H1_SOURCE_DISABLE_ENCRYPT) + elif hdcp_mode == HdcpMode.Mode2_3: + self.__io.set(TSI_HDCP_2X_COMMAND_W, H2_SOURCE_ENABLE_ENCRYPT if value else H2_SOURCE_DISABLE_ENCRYPT) + + def __set_authenticate(self, value: Optional[bool], hdcp_mode: HdcpMode): + if value is None: + return + if hdcp_mode == HdcpMode.Mode1_4: + self.__io.set(TSI_HDCP_1X_COMMAND_W, H1_SOURCE_AUTHENTICATE if value else H1_SOURCE_DE_AUTHENTICATE) + elif hdcp_mode == HdcpMode.Mode2_3: + self.__io.set(TSI_HDCP_2X_COMMAND_W, H2_SOURCE_AUTHENTICATE if value else H2_SOURCE_DE_AUTHENTICATE) + + def __load_keys(self, keys: Optional[Union[HdcpSource1XKeys, HdcpSource2XKeys]], hdcp_mode: HdcpMode): + if keys is None: + return + if not self.__full_check(hdcp_mode): + return + + if hdcp_mode == HdcpMode.Mode1_4 and not isinstance(keys, HdcpSource1XKeys): + raise TypeError("Was selected HDCP 1.4 mode. Need to use 'HdcpSource1XKeys' type.") + elif hdcp_mode == HdcpMode.Mode1_4 and isinstance(keys, HdcpSource1XKeys): + if keys == HdcpSource1XKeys.Production and not self.__caps_1x.production_keys_available: + warnings.warn("Production keys is not hw supported. Please, select another keys.") + elif keys == HdcpSource1XKeys.Test and not self.__caps_1x.test_keys_available: + warnings.warn("Test keys is not hw supported. Please, select another keys.") + elif hdcp_mode == HdcpMode.Mode2_3 and not isinstance(keys, HdcpSource2XKeys): + raise TypeError("Was selected HDCP 2.3 mode. Need to use 'HdcpSource2XKeys' type.") + elif hdcp_mode == HdcpMode.Mode2_3 and isinstance(keys, HdcpSource2XKeys): + if keys == HdcpSource2XKeys.Production and not self.__caps_2x.production_keys_available: + warnings.warn("Production keys is not hw supported. Please, select another keys.") + + self.__io.set(TSI_HDCP_1X_COMMAND_W if hdcp_mode == HdcpMode.Mode1_4 else TSI_HDCP_2X_COMMAND_W, keys.value) + + def __full_check(self, hdcp_mode: HdcpMode) -> bool: + if hdcp_mode == HdcpMode.Unknown: + warnings.warn(f"Did not select HDCP mode. Please, select it from available variants: " + f"{'HDCP 1.4 ' if self.__caps_1x.hw_supported else ''}" + f"{'HDCP 2.3 ' if self.__caps_2x.hw_supported else ''}") + return False + + if not self.__availability_mode_check(hdcp_mode): + warnings.warn(f"Selected HDCP mode is not HW supported. Please, select it from available variants: " + f"{'HDCP 1.4 ' if self.__caps_1x.hw_supported else ''}" + f"{'HDCP 2.3 ' if self.__caps_2x.hw_supported else ''}") + return False + + return True + + def __availability_mode_check(self, hdcp_mode: HdcpMode) -> bool: + if hdcp_mode == HdcpMode.Mode1_4 and self.__caps_1x.hw_supported: + return True + elif hdcp_mode == HdcpMode.Mode2_3 and self.__caps_2x.hw_supported: + return True + else: + return False + + def __set_store_km(self, value: Optional[bool]): + if value is None: + return + self.__io.set(TSI_HDCP_2X_COMMAND_W, H2_SOURCE_AUTHENTICATE_STORE_KM if value else H2_SOURCE_CLEAR_STORE_KM) + + def __set_content_level(self, value: Optional[int]): + if value is None: + return + if value not in [0, 1]: + raise ValueError(f"Incorrect input value of 'Content level' - {value}. Must be from range: 0-1") + self.__io.set(TSI_HDCP_2X_CFG, value) + + +class HdcpSource: + + """ + Main class contains info of HDCP on Source (TX - transmitter) side. + If you need to configurate HDCP, use `config` for getting object responsible for the configuration. + If you need to read HDCP status, use `status` for getting object responsible for the reading current status. + """ + + def __init__(self, port_io: PortIO, ci_caps_control: int, ci_status_control: int): + self.__io = port_io + self.__hw_caps_1x = HdcpHwSourceCaps(self.__read_hw_caps(HdcpMode.Mode1_4), HdcpMode.Mode1_4) + self.__hw_caps_2x = HdcpHwSourceCaps(self.__read_hw_caps(HdcpMode.Mode2_3, ci_caps_control), HdcpMode.Mode2_3) + self.__status = HdcpSourceStatus(port_io, ci_status_control, self.__hw_caps_1x, self.__hw_caps_2x) + self.__config = HdcpSourceConfig(port_io, self.__hw_caps_1x, self.__hw_caps_2x, self.__status) + + @property + def config(self) -> HdcpSourceConfig: + """ + + Should be used to configure HDCP on Source (TX - transmitter) role. + + Returns: + object of `HdcpSourceConfig`. + """ + return self.__config + + @property + def status(self) -> HdcpSourceStatus: + """ + + Should be used to read HDCP current status onSource (TX - transmitter) role. + + Returns: + object of `HdcpSourceStatus`. + """ + return self.__status + + def __read_hw_caps(self, mode: HdcpMode, ci_caps_control: int = 0) -> int: + if mode == HdcpMode.Mode1_4: + return self.__io.get(TSI_HDCP_1X_STATUS_R, c_int)[1] + elif mode == HdcpMode.Mode2_3: + return self.__io.get(ci_caps_control, c_int)[1] + else: + return 0 + diff --git a/UniTAP/dev/ports/modules/hdcp/types.py b/UniTAP/dev/ports/modules/hdcp/types.py new file mode 100644 index 0000000..ea9d643 --- /dev/null +++ b/UniTAP/dev/ports/modules/hdcp/types.py @@ -0,0 +1,221 @@ +from typing import TypeVar + +from UniTAP.libs.lib_tsi.tsi_types import * +from enum import IntEnum +from typing import Type + + +class HdcpSink1XKeys(IntEnum): + Unknown = -1 + Unload = H1_SINK_UNLOAD_KEYS + Test = H1_SINK_LOAD_TEST_KEYS + Production = H1_SINK_LOAD_PROD_KEYS + + +class HdcpSink2XKeys(IntEnum): + Unknown = -1 + Unload = H2_SINK_UNLOAD_KEYS + Production = H2_SINK_LOAD_PROD_KEYS + TestR1 = H2_SINK_LOAD_TEST_KEYS_R1 + TestR2 = H2_SINK_LOAD_TEST_KEYS_R2 + + +class HdcpSource1XKeys(IntEnum): + Unknown = -1 + Unload = H1_SOURCE_UNLOAD_KEYS + Test = H1_SOURCE_LOAD_TEST_KEYS + Production = H1_SOURCE_LOAD_PROD_KEYS + + +class HdcpSource2XKeys(IntEnum): + Unknown = -1 + Unload = H2_SOURCE_UNLOAD_KEYS + Production = H2_SOURCE_LOAD_PROD_KEYS + TestR1 = H2_SOURCE_LOAD_TEST_KEYS_R1 + TestR2 = H2_SOURCE_LOAD_TEST_KEYS_R2 + + +class HdcpMode(IntEnum): + Unknown = -1 + Mode1_4 = 0 + Mode2_3 = 1 + + +class HdcpHwCaps: + + def __init__(self): + self.hw_supported = False + self.production_keys_available = False + self.test_keys_available = False + + def __str__(self): + return f"HW Supported: {self.hw_supported}\n" \ + f"Production keys available: {self.production_keys_available}\n" \ + f"Test keys available: {self.test_keys_available}\n" + + +class HdcpHwSinkCaps(HdcpHwCaps): + + def __init__(self, caps: int, hdcp_mode: HdcpMode): + super().__init__() + if hdcp_mode == HdcpMode.Mode1_4: + self.hw_supported = ((caps >> 16) & 1) != 0 + self.production_keys_available = ((caps >> 17) & 1) != 0 + self.test_keys_available = ((caps >> 18) & 1) != 0 + else: + self.hw_supported = ((caps >> 8) & 1) != 0 + self.production_keys_available = ((caps >> 9) & 1) != 0 + self.test_keys_available = ((caps >> 10) & 1) != 0 + + +class HdcpHwSourceCaps(HdcpHwCaps): + + def __init__(self, caps: int, hdcp_mode: HdcpMode): + super().__init__() + if hdcp_mode == HdcpMode.Mode1_4: + self.hw_supported = ((caps >> 24) & 1) != 0 + self.production_keys_available = ((caps >> 25) & 1) != 0 + self.test_keys_available = ((caps >> 26) & 1) != 0 + elif hdcp_mode == HdcpMode.Mode2_3: + self.hw_supported = ((caps >> 8) & 1) != 0 + self.production_keys_available = ((caps >> 9) & 1) != 0 + self.test_keys_available = ((caps >> 10) & 1) != 0 + + +class HdcpStatus: + + class Status1x: + + def __init__(self): + self.active = False + self.keys = None + self.capable = False + self.authenticated = False + + def __str__(self): + return f"Active: {bool(self.active)}\n" \ + f"Keys: {self.keys.name}\n" \ + f"Capable: {bool(self.capable)}\n" \ + f"Authenticated: {bool(self.authenticated)}\n" + + def __eq__(self, other): + return self.active == other.active and \ + self.keys == other.keys and \ + self.capable == other.capable and \ + self.authenticated == other.authenticated + + class StatusRx2x: + + def __init__(self): + self.active = False + self.keys = None + self.capable = False + self.authenticated = False + self.km_is_stored = False + self.content_level = 0 + + def __str__(self): + return f"Active: {bool(self.active)}\n" \ + f"Keys: {self.keys.name}\n" \ + f"Capable: {bool(self.capable)}\n" \ + f"Authenticated: {bool(self.authenticated)}\n" \ + f"Km is stored: {bool(self.km_is_stored)}\n" \ + f"Content level: {self.content_level}\n" + + def __eq__(self, other): + return self.active == other.active and \ + self.keys == other.keys and \ + self.capable == other.capable and \ + self.authenticated == other.authenticated and \ + self.km_is_stored == other.km_is_stored and \ + self.content_level == other.content_level + + class StatusTx2x: + + def __init__(self): + self.active = False + self.keys = None + self.capable = False + self.authenticated = False + self.km_is_stored = False + self.content_level = 0 + self.try_authenticate = False + self.try_encrypt = False + + def __str__(self): + return f"Active: {bool(self.active)}\n" \ + f"Keys: {self.keys.name}\n" \ + f"Capable: {bool(self.capable)}\n" \ + f"Authenticated: {bool(self.authenticated)}\n" \ + f"Km is stored: {bool(self.km_is_stored)}\n" \ + f"Content level: {self.content_level}\n" + + def __eq__(self, other): + return self.active == other.active and \ + self.keys == other.keys and \ + self.capable == other.capable and \ + self.authenticated == other.authenticated and \ + self.km_is_stored == other.km_is_stored and \ + self.content_level == other.content_level and \ + self.try_authenticate == other.try_authenticate and \ + self.try_encrypt == other.try_encrypt + + +class HdcpRxConfig: + + class Config1x: + + def __init__(self): + self.keys = None + self.capable = None + + class Config2x: + + def __init__(self): + self.keys = None + self.capable = None + + +class HdcpTxConfig: + + class Config1x: + + def __init__(self): + self.encryption = None + self.authenticate = None + self.keys = None + + def __eq__(self, other): + return self.encryption == other.encryption and \ + self.authenticate == other.authenticate and \ + self.keys == other.keys + + class Config2x: + + def __init__(self): + self.encryption = None + self.authenticate = None + self.keys = None + self.store_km = None + self.content_level = None + + def __eq__(self, other): + return self.encryption == other.encryption and \ + self.authenticate == other.authenticate and \ + self.keys == other.keys and \ + self.store_km == other.store_km and \ + self.content_level == other.content_level + + +HdcpRxConfigType = TypeVar("HdcpRxConfigType", + HdcpRxConfig.Config1x, + HdcpRxConfig.Config2x) + +HdcpTxConfigType = TypeVar("HdcpTxConfigType", + HdcpTxConfig.Config1x, + HdcpTxConfig.Config2x) + +HdcpStatusType = TypeVar("HdcpStatusType", + HdcpStatus.Status1x, + HdcpStatus.StatusRx2x, + HdcpStatus.StatusTx2x) diff --git a/UniTAP/dev/ports/modules/internal_utils/__init__.py b/UniTAP/dev/ports/modules/internal_utils/__init__.py new file mode 100644 index 0000000..cf114fa --- /dev/null +++ b/UniTAP/dev/ports/modules/internal_utils/__init__.py @@ -0,0 +1,6 @@ +from .math import aligned +from .image_formats import uicl_cf_from_vm, uicl_sampling_from_vm, uicl_colorimetry_from_vm,\ + uicl_parameters_from_vm, uicl_image_from_vm_and_data, uicl_image_convert_to_vm + + + diff --git a/UniTAP/dev/ports/modules/internal_utils/image_formats.py b/UniTAP/dev/ports/modules/internal_utils/image_formats.py new file mode 100644 index 0000000..2c84da2 --- /dev/null +++ b/UniTAP/dev/ports/modules/internal_utils/image_formats.py @@ -0,0 +1,122 @@ +from UniTAP.libs.lib_uicl.uicl import * +from UniTAP.common import VideoMode, ColorInfo + + +class VideoFileFormat(IntEnum): + UNKNOWN = -1 + BIN = 0 + MP4 = 1 + + +class PictureFileFormat(IntEnum): + UNKNOWN = -1 + BIN = 0 + BMP = 1 + PPM = 2 + DSC = 3 + + +def uicl_cf_from_vm(color_format: ColorInfo.ColorFormat) -> UICL_Colorspace: + if color_format == ColorInfo.ColorFormat.CF_RGB: + return UICL_Colorspace.Colorspace_RGB + elif color_format in [ColorInfo.ColorFormat.CF_YCbCr_444, ColorInfo.ColorFormat.CF_YCbCr_422, + ColorInfo.ColorFormat.CF_YCbCr_420]: + return UICL_Colorspace.Colorspace_YCbCr + else: + return UICL_Colorspace.Colorspace_Unknown + + +def uicl_sampling_from_vm(color_format: ColorInfo.ColorFormat) -> UICL_Sampling: + if color_format in [ColorInfo.ColorFormat.CF_RGB, ColorInfo.ColorFormat.CF_YCbCr_444]: + return UICL_Sampling.Sampling_444 + elif color_format == ColorInfo.ColorFormat.CF_YCbCr_422: + return UICL_Sampling.Sampling_422 + elif color_format == ColorInfo.ColorFormat.CF_YCbCr_420: + return UICL_Sampling.Sampling_420 + else: + return UICL_Sampling.Sampling_Unknown + + +def uicl_colorimetry_from_vm(colorimetry: ColorInfo.Colorimetry) -> UICL_Colorimetry: + if colorimetry == ColorInfo.Colorimetry.CM_ITUR_BT601: + return UICL_Colorimetry.Colorimetry_ITU_R_BT601 + elif colorimetry == ColorInfo.Colorimetry.CM_ITUR_BT709: + return UICL_Colorimetry.Colorimetry_ITU_R_BT709 + elif colorimetry == ColorInfo.Colorimetry.CM_ITUR_BT2020_YCbCr: + return UICL_Colorimetry.Colorimetry_ITU_R_BT2020 + else: + return UICL_Colorimetry.Colorimetry_Unknown + + +def uicl_parameters_from_vm(vm: VideoMode): + parameters = UICL_ImageParameters() + + parameters.Width = vm.timing.hactive + parameters.Height = vm.timing.vactive + parameters.BitsPerColor = vm.color_info.bpc + parameters.Colorimetry = uicl_colorimetry_from_vm(vm.color_info.colorimetry) + + if vm.color_info.color_format == ColorInfo.ColorFormat.CF_RGB: + parameters.Colorspace = UICL_Colorspace.Colorspace_RGB + parameters.ComponentOrder = UICL_ComponentOrder.Order_RGB + parameters.Sampling = UICL_Sampling.Sampling_444 + parameters.Packing = UICL_Packing.Packing_Packed + parameters.Alignment = UICL_Alignment.Alignment_LSB + parameters.IsFullRange = vm.color_info.DynamicRange == ColorInfo.DynamicRange.DR_VESA + + elif vm.color_info.color_format == ColorInfo.ColorFormat.CF_YCbCr_444: + parameters.Colorspace = UICL_Colorspace.Colorspace_YCbCr + parameters.ComponentOrder = UICL_ComponentOrder.Order_YCbCr + parameters.Sampling = UICL_Sampling.Sampling_444 + parameters.Packing = UICL_Packing.Packing_Packed + parameters.Alignment = UICL_Alignment.Alignment_LSB + + parameters.IsFullRange = False + + elif vm.color_info.color_format == ColorInfo.ColorFormat.CF_YCbCr_422: + parameters.Colorspace = UICL_Colorspace.Colorspace_YCbCr + parameters.ComponentOrder = UICL_ComponentOrder.Order_CbY0CrY1 + parameters.Sampling = UICL_Sampling.Sampling_422 + parameters.Packing = UICL_Packing.Packing_Packed + parameters.Alignment = UICL_Alignment.Alignment_LSB + + parameters.IsFullRange = False + + elif vm.color_info.color_format == ColorInfo.ColorFormat.CF_YCbCr_420: + parameters.Colorspace = UICL_Colorspace.Colorspace_YCbCr + parameters.ComponentOrder = UICL_ComponentOrder.Order_YCbCr + parameters.Sampling = UICL_Sampling.Sampling_420 + parameters.Packing = UICL_Packing.Packing_Planar + parameters.Alignment = UICL_Alignment.Alignment_LSB + + parameters.IsFullRange = False + + return parameters + + +def uicl_image_from_vm_and_data(video_mode: VideoMode, data: bytearray) -> UICL_Image: + image = UICL_Image() + + image.DataPtr = (c_uint8 * len(data))(*data) + image.DataSize = len(data) + image.Parameters = uicl_parameters_from_vm(video_mode) + + return image + + +def uicl_image_convert_to_vm(src_image: UICL_Image, video_mode: VideoMode) -> bytearray: + dest_image = UICL_Image() + + dest_image.Parameters = uicl_parameters_from_vm(video_mode) + result = UICL_GetRequiredBufferSize(dest_image) + + assert result >= UICL_SUCCESS, f"Calculation required buffer size failed with error {result}" + + dest_image.DataPtr = (c_uint8 * result)() + dest_image.DataSize = result + + result = UICL_Convert(src_image, dest_image) + + assert result == UICL_SUCCESS, f"Image conversion failed with error {result}" + + return bytearray(dest_image.DataPtr[:dest_image.DataSize]) diff --git a/UniTAP/dev/ports/modules/internal_utils/image_utils.py b/UniTAP/dev/ports/modules/internal_utils/image_utils.py new file mode 100644 index 0000000..d42994f --- /dev/null +++ b/UniTAP/dev/ports/modules/internal_utils/image_utils.py @@ -0,0 +1,27 @@ +from UniTAP.libs.lib_uicl.uicl import * + + +def save_video_to_bin(path: str, data: list): + raise NotImplementedError(f"Temporary not supported saving to bin file format.") + + +def save_video_to_mp4(path: str, data: list): + raise NotImplementedError(f"Temporary not supported saving to mp4 file format.") + + +def save_image_to_bin(path: str, image_object: UICL_Image): + res = UICL_SaveToFile(image_object, path, ImageFileFormat.FILE_FORMAT_BIN) + if res != 0: + raise SystemError(f"Error saving image to BIN format. Error code {res}. Description {uicl_errors.get(res)}") + + +def save_image_to_bmp(path: str, image_object: UICL_Image): + res = UICL_SaveToFile(image_object, path, ImageFileFormat.FILE_FORMAT_BMP) + if res != 0: + raise SystemError(f"Error saving image to BMP format. Error code {res}. Description {uicl_errors.get(res)}") + + +def save_image_to_ppm(path: str, image_object: UICL_Image): + res = UICL_SaveToFile(image_object, path, ImageFileFormat.FILE_FORMAT_PPM) + if res != 0: + raise SystemError(f"Error saving image to PPM format. Error code {res}. Description {uicl_errors.get(res)}") diff --git a/UniTAP/dev/ports/modules/internal_utils/math.py b/UniTAP/dev/ports/modules/internal_utils/math.py new file mode 100644 index 0000000..c31bf39 --- /dev/null +++ b/UniTAP/dev/ports/modules/internal_utils/math.py @@ -0,0 +1,2 @@ +def aligned(v, by): + return (v + (by - 1)) / by * by diff --git a/UniTAP/dev/ports/modules/link/__init__.py b/UniTAP/dev/ports/modules/link/__init__.py new file mode 100644 index 0000000..c49c1e7 --- /dev/null +++ b/UniTAP/dev/ports/modules/link/__init__.py @@ -0,0 +1,2 @@ +from .dp import * +from .hdmi import * diff --git a/UniTAP/dev/ports/modules/link/dp/__init__.py b/UniTAP/dev/ports/modules/link/dp/__init__.py new file mode 100644 index 0000000..060e9ba --- /dev/null +++ b/UniTAP/dev/ports/modules/link/dp/__init__.py @@ -0,0 +1,8 @@ +from .link_rx import LinkDisplayPortRx +from .link_rx_types import LinkCapabilities, LinkEDPCapabilities +from .link_tx import LinkDisplayPortTx, DpLinkTrainingResult, DPLinkPattern +from .link_tx_config import LinkConfig, SSCConfig +from .link_status_common import DpLinkEncoding +from .link_tx_types import DPLinkPattern, DP128b132bLinkPattern, DPOutLinkMode +from .dp_cable_info import CableCapabilitiesEnum +from .link_rx_aux_controller import RoutedLTConfig diff --git a/UniTAP/dev/ports/modules/link/dp/dp_cable_info.py b/UniTAP/dev/ports/modules/link/dp/dp_cable_info.py new file mode 100644 index 0000000..02e6c68 --- /dev/null +++ b/UniTAP/dev/ports/modules/link/dp/dp_cable_info.py @@ -0,0 +1,19 @@ +from UniTAP.libs.lib_tsi import PortIO +from .private_link_rx_types import CableAttributesStruct, PortCableAttributesStruct +from .link_rx_types import CableCapabilitiesEnum + + +class DpCableInfo: + + def __init__(self, port_io: PortIO, ci_name: int): + self.__io = port_io + self.__ci = ci_name + + def __read_cable_attributes(self) -> CableAttributesStruct: + return self.__io.get(self.__ci, CableAttributesStruct)[1] + + def get_current_port_cable_type(self) -> CableCapabilitiesEnum: + return PortCableAttributesStruct(self.__read_cable_attributes().currentPort).get_cable_caps() + + def get_another_port_cable_type(self) -> CableCapabilitiesEnum: + return PortCableAttributesStruct(self.__read_cable_attributes().anotherPort).get_cable_caps() diff --git a/UniTAP/dev/ports/modules/link/dp/dp_utils.py b/UniTAP/dev/ports/modules/link/dp/dp_utils.py new file mode 100644 index 0000000..0b9cf1e --- /dev/null +++ b/UniTAP/dev/ports/modules/link/dp/dp_utils.py @@ -0,0 +1,137 @@ +from UniTAP.common import ColorInfo + + +def get_vm_color_format(vsc_sdp_db16: int) -> ColorInfo.ColorFormat: + color_format_value = vsc_sdp_db16 >> 4 & 0xf + + if color_format_value == 0: + return ColorInfo.ColorFormat.CF_RGB + elif color_format_value == 1: + return ColorInfo.ColorFormat.CF_YCbCr_444 + elif color_format_value == 2: + return ColorInfo.ColorFormat.CF_YCbCr_422 + elif color_format_value == 3: + return ColorInfo.ColorFormat.CF_YCbCr_420 + elif color_format_value == 4: + return ColorInfo.ColorFormat.CF_Y_ONLY + elif color_format_value == 5: + return ColorInfo.ColorFormat.CF_RAW + else: + return ColorInfo.ColorFormat.CF_UNKNOWN + + +def get_vm_colorimetry(vsc_sdp_db16: int) -> ColorInfo.Colorimetry: + color_format_value = vsc_sdp_db16 >> 4 & 0xf + colorimetry_value = vsc_sdp_db16 & 0xf + + if color_format_value == 0: + if colorimetry_value == 0: + return ColorInfo.Colorimetry.CM_sRGB + elif colorimetry_value == 1: + return ColorInfo.Colorimetry.CM_RGB_WIDE_GAMUT_FIX + elif colorimetry_value == 2: + return ColorInfo.Colorimetry.CM_RGB_WIDE_GAMUT_FLT + elif colorimetry_value == 3: + return ColorInfo.Colorimetry.CM_AdobeRGB + elif colorimetry_value == 4: + return ColorInfo.Colorimetry.CM_DCI_P3 + elif colorimetry_value == 5: + return ColorInfo.Colorimetry.CM_CUSTOM_COLOR_PROFILE + elif colorimetry_value == 6: + return ColorInfo.Colorimetry.CM_ITUR_BT2020_RGB + else: + return ColorInfo.Colorimetry.CM_RESERVED + + elif color_format_value in [1, 2, 3]: + if colorimetry_value == 0: + return ColorInfo.Colorimetry.CM_ITUR_BT601 + elif colorimetry_value == 1: + return ColorInfo.Colorimetry.CM_ITUR_BT709 + elif colorimetry_value == 2: + return ColorInfo.Colorimetry.CM_xvYCC601 + elif colorimetry_value == 3: + return ColorInfo.Colorimetry.CM_xvYCC709 + elif colorimetry_value == 4: + return ColorInfo.Colorimetry.CM_sYCC601 + elif colorimetry_value == 5: + return ColorInfo.Colorimetry.CM_opYCC601 + elif colorimetry_value == 6: + return ColorInfo.Colorimetry.CM_ITUR_BT2020_YcCbcCrc + elif colorimetry_value == 7: + return ColorInfo.Colorimetry.CM_ITUR_BT2020_YCbCr + else: + return ColorInfo.Colorimetry.CM_RESERVED + + elif color_format_value == 4: + if colorimetry_value == 0: + return ColorInfo.Colorimetry.CM_DCI_P3 + else: + return ColorInfo.Colorimetry.CM_RESERVED + + elif color_format_value == 5: + if colorimetry_value == 0: + return ColorInfo.Colorimetry.CM_CUSTOM_COLOR_PROFILE + else: + return ColorInfo.Colorimetry.CM_RESERVED + + else: + return ColorInfo.Colorimetry.CM_RESERVED + + +def get_vm_bpc(vsc_sdp_db16: int, vsc_sdp_db17: int) -> int: + color_format_value = vsc_sdp_db16 >> 4 & 0xf + bpc_value = vsc_sdp_db17 & 0x7 + + if color_format_value == 0: + if bpc_value == 0: + return 6 + elif bpc_value == 1: + return 8 + elif bpc_value == 2: + return 10 + elif bpc_value == 3: + return 12 + elif bpc_value == 4: + return 16 + else: + return 0 + + elif color_format_value in [1, 2, 3, 4]: + if bpc_value == 1: + return 8 + elif bpc_value == 2: + return 10 + elif bpc_value == 3: + return 12 + elif bpc_value == 4: + return 16 + else: + return 0 + + elif color_format_value == 5: + if bpc_value == 1: + return 6 + elif bpc_value == 2: + return 7 + elif bpc_value == 3: + return 8 + elif bpc_value == 4: + return 10 + elif bpc_value == 5: + return 12 + elif bpc_value == 6: + return 14 + elif bpc_value == 7: + return 16 + else: + return 0 + + else: + return 0 + + +def get_vm_dynamic_range(vsc_sdp_db17: int) -> ColorInfo.DynamicRange: + if vsc_sdp_db17 >> 7 & 1: + return ColorInfo.DynamicRange.DR_CTA + else: + return ColorInfo.DynamicRange.DR_VESA diff --git a/UniTAP/dev/ports/modules/link/dp/link_rx.py b/UniTAP/dev/ports/modules/link/dp/link_rx.py new file mode 100644 index 0000000..e684f8c --- /dev/null +++ b/UniTAP/dev/ports/modules/link/dp/link_rx.py @@ -0,0 +1,121 @@ +from .link_rx_caps import LinkDisplayPortCaps +from .link_rx_status import * +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from .dp_cable_info import DpCableInfo, CableCapabilitiesEnum +from .link_rx_aux_controller import DisplayPortAUXController +from UniTAP.dev.ports.modules.dpcd import DPCDRegisters + + +class LinkDisplayPortRx: + """ + Class `LinkDisplayPortRx` contains information about DP link. + - Read link status `status`. + - Configure and read link capabilities `capabilities`. + - Make `hpd_pulse`. + - Assert/Deassert HPD state `set_assert_state`. + - Read and write scrambler seed value `scrambler_seed`. + """ + def __init__(self, port_io: PortIO, hw_caps: DPRXHWCaps, dpcd: DPCDRegisters): + self.__io = port_io + self.__HW_CAPS = hw_caps + self.__status = LinkDisplayPortStatusSink(port_io, self.__HW_CAPS) + self.__capabilities = LinkDisplayPortCaps(port_io, self.__HW_CAPS) + self.__cable_info = DpCableInfo(port_io, TSI_DPRX_CABLE_ATTRIBUTES_R) + self.__aux_controller = DisplayPortAUXController(port_io, dpcd) + + @property + def status(self) -> LinkDisplayPortStatusSink: + """ + Returns object of class `LinkDisplayPortStatusSink` for working with link status. + + Returns: + object of `LinkDisplayPortStatusSink` type + """ + return self.__status + + @property + def capabilities(self) -> LinkDisplayPortCaps: + """ + Returns object of class `LinkDisplayPortCaps` for working with link capabilities. + + Returns: + object of `LinkDisplayPortStatusSink` type + """ + return self.__capabilities + + @property + def aux_controller(self) -> DisplayPortAUXController: + """ + Returns object of class `DisplayPortAUXController` for working with DP AUX Controller. + + Returns: + object of `DisplayPortAUXController` type + """ + return self.__aux_controller + + def hpd_pulse(self, duration_us: int = 500000): + """ + Start HPD pulse. + + Args: + duration_us (int) + """ + self.__io.set(TSI_DPRX_HPD_PULSE_W, duration_us, c_int) + + def set_assert_state(self, state: bool): + """ + Assert/Deassert HPD state. + + Args: + state (bool) + """ + val = 0x1 if state else 0x0 + self.__io.set(TSI_FORCE_HOT_PLUG_STATE_W, val) + + @property + def scrambler_seed(self) -> int: + """ + Returns scrambler seed value. + + Returns: + object of `int` type + """ + if not self.__HW_CAPS.scrambler_seed: + warnings.warn("Scrambler Seed is not supported.") + return 0 + + return self.__io.get(TSI_DPRX_SCR_SEED, c_uint32)[1] + + @scrambler_seed.setter + def scrambler_seed(self, value: int = 0): + """ + Write new value to scrambler seed + + Args: + value (int) - new scrambler seed value + """ + if value < 0 or value > 65535: + raise ValueError(f"Scrambler seed {value} is not available.") + + if self.__HW_CAPS.scrambler_seed: + self.__io.set(TSI_DPRX_SCR_SEED, value) + else: + warnings.warn("Scrambler Seed is not supported.") + + def cable_rx_type(self) -> CableCapabilitiesEnum: + """ + Get cable type from the RX side. + + Returns: + object of `CableCapabilitiesEnum` type + """ + return self.__cable_info.get_current_port_cable_type() + + def cable_tx_type(self) -> CableCapabilitiesEnum: + """ + Get cable type from the TX side. + + Returns: + object of `CableCapabilitiesEnum` type + """ + return self.__cable_info.get_another_port_cable_type() diff --git a/UniTAP/dev/ports/modules/link/dp/link_rx_aux_controller.py b/UniTAP/dev/ports/modules/link/dp/link_rx_aux_controller.py new file mode 100644 index 0000000..6344181 --- /dev/null +++ b/UniTAP/dev/ports/modules/link/dp/link_rx_aux_controller.py @@ -0,0 +1,218 @@ +from typing import Optional +from UniTAP.libs.lib_tsi import PortIO +from UniTAP.libs.lib_tsi.tsi_private_types import * +from UniTAP.libs.lib_tsi.tsi_types import * +from .private_link_rx_types import RoutedLTStatusPrivate +from .link_rx_types import RoutedLTStatus, RoutedLTConfig +from UniTAP.dev.ports.modules.dpcd import DPCDRegisters + + +class DisplayPortAUXController: + """ + Class `DisplayPortAUXController` describes information about DP AUX Controller. Contains following info: + - configure routed LT `exec_routed_lt` + - read routed T status `status` + - request PHY test pattern `request_phy_test_pattern` + """ + def __init__(self, port_io: PortIO, dpcd: DPCDRegisters): + self.__io = port_io + self.__dpcd = dpcd + + def enable(self, enable: bool): + """ + Enable or disable Routed LT. + + Args: + enable ('bool') + + """ + status = self.status() + + ctrl = status.dp20_old_lt << 1 + if enable: + ctrl |= 0x01 + else: + ctrl &= ~0x01 + + res = self.__io.set(TSI_DPRX_LT_ROUTE_CONTROL_W, ctrl, c_uint32) + if not res: + raise Exception("The feature requires a license key and the required license key is not installed.") + + def exec_routed_lt(self, config: RoutedLTConfig, use_ta_request: bool = False): + """ + + Execute routed link training with transferred configuration. + + Args: + config ('RoutedLTConfig') + use_ta_request (`bool`) + """ + max_rate_dp14 = 0 + max_rate_dp20 = 0 + + if config.is128b132b: + if config.link_bw == 1: + max_rate_dp20 = 1 + elif config.link_bw == 2: + max_rate_dp20 = 3 + elif config.link_bw == 4: + max_rate_dp20 = 5 + + max_rate_dp14 = 0x1e + else: + max_rate_dp14 = config.link_bw + + res = self.__set_routed_lt_config(config.int_value()) + if not res: + raise Exception("The feature requires a license key and the required license key is not installed.") + self.__set_force_cable_status_to_plugged(True) + + dpcd_rev = 0x14 + dpcd_01_val = max_rate_dp14 + dpcd_02_val = config.lane_count | 0xc0 + dpcd_03_val = 0x81 + dpcd_06_val = 0x03 if config.is128b132b else 0x01 + + self.__dpcd.write(0x00, dpcd_rev) + self.__dpcd.write(0x01, dpcd_01_val) + self.__dpcd.write(0x02, dpcd_02_val) + self.__dpcd.write(0x03, dpcd_03_val) + self.__dpcd.write(0x06, dpcd_06_val) + + self.__dpcd.write(0x0e, 0x80) + self.__dpcd.write(0x2200, dpcd_rev) + self.__dpcd.write(0x2201, dpcd_01_val) + self.__dpcd.write(0x2202, dpcd_02_val) + self.__dpcd.write(0x2203, dpcd_03_val) + self.__dpcd.write(0x2206, dpcd_06_val) + + self.__dpcd.write(0x2215, max_rate_dp20) + + if config.is128b132b: + self.__dpcd.write(0x21, 0x01) + self.__dpcd.write(0x90, 0xff) + dsc_data = [0x0f, 0x21, 0x03, 0x03, 0xeb, 0x07, 0x01, 0x00, 0x00, 0x1f, 0x0e, 0x11, 0x08, 0x07, 0x00, 0x00] + self.__dpcd.write(0x60, dsc_data) + self.__dpcd.write(0x2217, 0x06) + else: + self.__dpcd.write(0x21, 0x00) + self.__dpcd.write(0x90, 0x00) + dsc_data = [0x00] * 16 + self.__dpcd.write(0x60, dsc_data) + self.__dpcd.write(0x2217, 0x00) + + self.__dpcd.write(0x100, 0) + self.__dpcd.write(0x101, 0) + self.__dpcd.write(0x103, 0) + self.__dpcd.write(0x104, 0) + self.__dpcd.write(0x105, 0) + self.__dpcd.write(0x106, 0) + self.__dpcd.write(0x107, 0) + self.__dpcd.write(0x108, 1) + self.__dpcd.write(0x206, 0) + self.__dpcd.write(0x207, 0) + + if use_ta_request: + self.__dpcd.write(0x219, config.link_bw) + self.__dpcd.write(0x220, config.lane_count) + + dpcd_218 = self.__dpcd.read(0x218, 1) + dpcd_218_data = int.from_bytes(dpcd_218.data, byteorder='big', signed=True) + dpcd_218_data |= 0x01 + dpcd_218_data &= ~0x30 + if config.is128b132b: + dpcd_218_data |= 0x10 + self.__dpcd.write(dpcd_218.base, dpcd_218_data) + + dpcd_201 = self.__dpcd.read(0x201, 1) + dpcd_201_data = int.from_bytes(dpcd_201.data, byteorder='big', signed=True) + dpcd_201_data |= 0x02 + self.__dpcd.write(dpcd_201.base, dpcd_201_data) + + self.__generate_short_hpd_pulse(750) + else: + dpcd_201 = self.__dpcd.read(0x201, 1) + dpcd_201_data = int.from_bytes(dpcd_201.data, byteorder='big', signed=True) + dpcd_201_data &= ~2 + self.__dpcd.write(dpcd_201.base, dpcd_201_data) + + dpcd_218 = self.__dpcd.read(0x218, 1) + dpcd_218_data = int.from_bytes(dpcd_218.data, byteorder='big', signed=True) + dpcd_218_data &= ~0xf + self.__dpcd.write(dpcd_218.base, dpcd_218_data) + + self.__generate_short_hpd_pulse(500 * 1000) + + def request_phy_test_pattern(self, pattern: int, sq_num: int, config: RoutedLTConfig): + """ + + Request PHY test pattern. + + Args: + config ('RoutedLTConfig') + pattern (`int`) + sq_num (`int`) + """ + + self.__set_force_cable_status_to_plugged(True) + + self.__dpcd.write(0x00, 0x14) + self.__dpcd.write(0x218, 0x18 if config.is128b132b else 0x08) + self.__dpcd.write(0x219, config.link_bw) + self.__dpcd.write(0x220, config.lane_count) + + self.__dpcd.write(0x248, pattern) + + if config.is128b132b and 0x48 <= pattern <= 0x4b: + self.__dpcd.write(0x249, sq_num) + + if config.is128b132b: + value = config.ffe | (config.ffe << 4) + else: + value = (config.vs & 3) | ((config.pe & 3) << 2) + value |= value << 4 + + self.__dpcd.write(0x206, value) + self.__dpcd.write(0x207, value) + self.__dpcd.write(0x260, 0x0) + + dpcd_201 = self.__dpcd.read(0x201, 1) + dpcd_201_data = int.from_bytes(dpcd_201.data, byteorder='big', signed=True) + dpcd_201_data |= 0x02 + self.__dpcd.write(dpcd_201.base, dpcd_201_data) + + self.__generate_short_hpd_pulse(750) + + def status(self) -> RoutedLTStatus: + """ + Get Routed link training status. + + Returns: + object of `RoutedLTStatus` + """ + routed_lt_status = self.__read_status() + if routed_lt_status is None: + raise Exception("The feature requires a license key and the required license key is not installed.") + return RoutedLTStatus(enabled=routed_lt_status.enabled, + dp20_old_lt=routed_lt_status.dp2OldLt, + state=routed_lt_status.state, + success=routed_lt_status.success, + step=routed_lt_status.step) + + def __set_routed_lt_config(self, value: int) -> bool: + res = self.__io.set(TSI_DPRX_LT_ROUTE_CREATE, value, c_uint32) + return res >= 0 + + def __set_force_cable_status_to_plugged(self, enable: bool): + val = 0x3C if enable else 0 + self.__io.set(TSI_DPRX_HPD_FORCE, val, c_int) + + def __read_status(self) -> Optional[RoutedLTStatusPrivate]: + res, status = self.__io.get(TSI_DPRX_LT_ROUTE_STATUS_R, RoutedLTStatusPrivate) + + if res >= 0: + return status + return None + + def __generate_short_hpd_pulse(self, value: int): + self.__io.set(TSI_DPRX_HPD_PULSE_W, value, c_int) diff --git a/UniTAP/dev/ports/modules/link/dp/link_rx_caps.py b/UniTAP/dev/ports/modules/link/dp/link_rx_caps.py new file mode 100644 index 0000000..e3769c6 --- /dev/null +++ b/UniTAP/dev/ports/modules/link/dp/link_rx_caps.py @@ -0,0 +1,332 @@ +import warnings +from typing import List, Optional, Union, Type + +from .link_rx_types import LinkCapabilities, LinkEDPCapabilities, DisplayPortLinkCaps +from .private_link_rx_types import DPRXHWCaps, DPRXLinkControl +from UniTAP.libs.lib_tsi import PortIO +from UniTAP.libs.lib_tsi.tsi_types import * + + +class LinkDisplayPortCaps: + """ + Class `LinkDisplayPortCaps` allows settings link capabilities on Sink (RX - receiver) side. + - Set configuration `set`. + - Get current configuration on link `link_caps_status`. + """ + def __init__(self, port_io: PortIO, caps: DPRXHWCaps): + self.__io = port_io + self.__caps = caps + + def set(self, capabilities: Union[LinkCapabilities, LinkEDPCapabilities]): + """ + Set new settings on link. Only those values will be written that were specified. + + Args: + capabilities (`LinkCapabilities` or `LinkEDPCapabilities`) + """ + if isinstance(capabilities, LinkCapabilities): + self.__set_max_lanes(capabilities.max_lane) + self.__set_max_bitrate(capabilities.bit_rate) + self.__set_force_cable_status_to_plugged(capabilities.force_cable_status_to_plugged) + self.__set_dp_link_control(old_dp2_lt=capabilities.old_dp_2_0_lt) + self.__set_dp_128_132_bitrates(capabilities.dp_128_132_bitrates) + self.__set_bitrate_override(10.0, capabilities.override_10g) + self.__set_dsc(capabilities.dsc) + self.__set_ss_sbm(capabilities.ss_sbm) + self.__set_fec(capabilities.fec) + self.__set_tps3(capabilities.tps3) + self.__set_tps4(capabilities.tps4) + self.__set_mst(capabilities.mst) + self.__set_edp_support(False) + self.__set_mst_sink_count(capabilities.mst_sink_count) + elif isinstance(capabilities, LinkEDPCapabilities): + self.__set_max_lanes(capabilities.max_lane) + self.__set_edp_sel_rates(capabilities.eDp_cur_rate) + self.__set_dp_link_control(edp_aux_preamble=capabilities.eDp_aux_preamble) + self.__set_edp_support(capabilities.eDp_support) + else: + raise TypeError("Incorrect LinkCapabilities format!") + + def link_caps_status(self, config_type: Union[Type[DisplayPortLinkCaps], None] = None) -> DisplayPortLinkCaps: + """ + Returns current configuration on link `LinkCapabilities`. + + Returns: + object of `LinkCapabilities` type + """ + if config_type is None or issubclass(config_type, LinkCapabilities): + link_status = LinkCapabilities() + link_status.max_lane = self.__get_max_lanes() + link_status.bit_rate = self.__get_max_bitrate() + link_status.force_cable_status_to_plugged = self.__get_force_cable_status_to_plugged() + link_status.old_dp_2_0_lt = self.__get_dp_link_control().old_dp2_lt + link_status.dp_128_132_bitrates = self.__get_dp_128_132_bitrates() + link_status.override_10g = self.__get_bitrate_override(10.0) + link_status.dsc = self.__get_dsc() + link_status.ss_sbm = self.__get_ss_sbm() + link_status.fec = self.__get_fec() + link_status.tps3 = self.__get_tps3() + link_status.tps4 = self.__get_tps4() + link_status.mst = self.__get_mst() + link_status.mst_sink_count = self.__get_mst_sink_count() + return link_status + elif issubclass(config_type, LinkEDPCapabilities): + link_status = LinkEDPCapabilities() + link_status.max_lane = self.__get_max_lanes() + link_status.eDp_cur_rate = self.__get_edp_sel_rates() + link_status.eDp_supported_rates = self.__get_edp_caps_rates() + link_status.eDp_aux_preamble = self.__get_dp_link_control().edp_aux_preamble + link_status.eDp_support = self.__get_edp_support() + return link_status + else: + raise TypeError("Incorrect LinkCapabilities format!") + + def __set_max_lanes(self, lane_count: Optional[int]): + if lane_count is None: + return + if lane_count not in [1, 2, 4]: + raise ValueError(f"Incorrect lane count number {lane_count}. Must be from list: 1, 2, 4") + self.__io.set(TSI_DPRX_MAX_LANES, lane_count, c_int) + + def __get_max_lanes(self) -> int: + return self.__io.get(TSI_DPRX_MAX_LANES)[1] + + def __set_max_bitrate(self, bitrate: Optional[float]): + if bitrate is None: + return + bitrate = round(bitrate / 0.27) + if bitrate not in [6, 10, 20, 25, 30]: + raise ValueError(f"Incorrect bit rate number {bitrate}. Must be from list: 1.62, 2.7, 5.4, 6.75, 8.1") + self.__io.set(TSI_DPRX_MAX_LINK_RATE, bitrate, c_int) + + def __get_max_bitrate(self) -> int: + return round(self.__io.get(TSI_DPRX_MAX_LINK_RATE)[1] * 0.27, 2) + + def __set_bitrate_override(self, to_override: float, with_override: Optional[float]): + if with_override is None: + return + + if not isinstance(to_override, float) or not isinstance(with_override, float): + raise TypeError("DP 128b/132b bitrate override settings must float type!") + + list_value = self.__io.get(TSI_DP2RX_CUSTOM_RATE_MAP, c_uint, 3)[1] + + if with_override not in [2.5, 2.7, 5.0, 5.4]: + raise ValueError(f"Incorrect bit rate number {with_override}. " + f"Must be from list: 2.5, 2.7, 5.0, 5.4") + list_value[0] = int(with_override * 1000000000 / 200000) + self.__io.set(TSI_DP2RX_CUSTOM_RATE_MAP, list_value, c_uint, 3) + + def __get_bitrate_override(self, to_override: float) -> Optional[float]: + if not isinstance(to_override, float): + raise TypeError("DP 128b/132b bitrate override settings must float type!") + + override_list = self.__io.get(TSI_DP2RX_CUSTOM_RATE_MAP, c_uint, 3)[1] + if override_list.count(0) == len(override_list): + return None + + if to_override == 10.0 and override_list[0] > 0: + return round((override_list[0] * 200000 / 1000000000), 2) + + return None + + def __set_dp_link_control(self, old_dp2_lt: Optional[bool] = None, edp_aux_preamble: Optional[bool] = None): + ctrl = self.__get_dp_link_control() + + if old_dp2_lt is not None: + ctrl.old_dp2_lt = old_dp2_lt + if edp_aux_preamble is not None: + ctrl.edp_aux_preamble = edp_aux_preamble + + self.__io.set(TSI_DPRX_LINK_CONTROL, ctrl.value(), c_uint32) + + def __get_dp_link_control(self) -> DPRXLinkControl: + return self.__io.get(TSI_DPRX_LINK_CONTROL, DPRXLinkControl)[1] + + def __set_dp_128_132_bitrates(self, rates: Optional[List[float]]): + flags = self.__io.get(TSI_DPRX_LINK_FLAGS)[1] & ~0xc0 + if rates is None: + return + elif isinstance(rates, list) and len(rates) == 0: + flags &= ~(1 << 4) + self.__io.set(TSI_DPRX_LINK_FLAGS, flags, c_int) + else: + flags |= (1 << 4) + self.__io.set(TSI_DPRX_LINK_FLAGS, flags, c_int) + val = 0 + if 10.0 in rates: + val |= 0x01 + if 13.5 in rates: + val |= 0x04 + if 20.0 in rates: + val |= 0x02 + self.__io.set(TSI_DP2RX_LINK_RATE_CAPS, val, c_int) + + def __get_dp_128_132_bitrates(self) -> Optional[List[float]]: + val = (self.__io.get(TSI_DPRX_LINK_FLAGS)[1] >> 4) & 0x1 + if val: + rates = self.__io.get(TSI_DP2RX_LINK_RATE_CAPS)[1] + list_rates = [] + if rates & 0x01: + list_rates.append(10.0) + if rates & 0x04: + list_rates.append(13.5) + if rates & 0x02: + list_rates.append(20.0) + return list_rates + else: + return None + + def __set_force_cable_status_to_plugged(self, enable: Optional[bool]): + if enable is None: + return + val = 0x3C if enable else 0 + self.__io.set(TSI_DPRX_HPD_FORCE, val, c_int) + + def __get_force_cable_status_to_plugged(self) -> bool: + return bool(self.__io.get(TSI_DPRX_HPD_FORCE)[1]) + + def __set_dsc(self, enable: Optional[bool]): + if enable is None: + return + val = 0x1 if enable else 0x0 + if self.__caps.dsc: + self.__io.set(TSI_DPRX_DSC_CONTROL, val, c_int) + + def __get_dsc(self) -> Optional[bool]: + if self.__io.get(TSI_DPRX_DSC_STATUS_R)[1] & 0x1: + return bool(self.__io.get(TSI_DPRX_DSC_CONTROL)[1] & 0x1) + + def __set_fec(self, enable: Optional[bool]): + if enable is None: + return + val = 0x1 if enable else 0x0 + if self.__caps.fec: + self.__io.set(TSI_DPRX_FEC_CONTROL, val, c_int) + + def __get_fec(self) -> Optional[bool]: + if self.__caps.fec: + return bool(self.__io.get(TSI_DPRX_FEC_CONTROL)[1] & 0x1) + + def __set_mst(self, enable: Optional[bool]): + if enable is None: + return + flags = self.__io.get(TSI_DPRX_LINK_FLAGS)[1] & ~0xc0 + if enable: + flags |= 1 + else: + flags &= ~1 + if self.__caps.mst: + self.__io.set(TSI_DPRX_LINK_FLAGS, flags, c_int) + + def __get_mst(self) -> Optional[bool]: + if self.__caps.mst: + return bool(self.__io.get(TSI_DPRX_LINK_FLAGS)[1] & 0x1) + + def __set_mst_sink_count(self, count: int): + if count is None: + return + if self.__caps.sink_cnt_config and self.__caps.mst: + if count > self.__caps.mst_stream_count: + warnings.warn(f"Maximum sink count: {self.__caps.mst_stream_count}") + else: + self.__io.set(TSI_DPRX_MST_SINK_COUNT, count, c_uint32) + else: + warnings.warn("MST or Custom Sink Count are not supported") + + def __get_mst_sink_count(self) -> int: + if self.__caps.sink_cnt_config and self.__caps.mst: + return int(self.__io.get(TSI_DPRX_MST_SINK_COUNT)[1]) + else: + return 0 + + def __set_tps3(self, enable: Optional[bool]): + if enable is None: + return + flags = self.__io.get(TSI_DPRX_LINK_FLAGS)[1] & ~0xc0 + if enable: + flags |= (1 << 1) + else: + flags &= ~(1 << 1) + if self.__caps.fec: + self.__io.set(TSI_DPRX_LINK_FLAGS, flags, c_int) + + def __get_tps3(self) -> Optional[bool]: + if self.__caps.fec: + return bool((self.__io.get(TSI_DPRX_LINK_FLAGS)[1] >> 1) & 0x1) + + def __set_tps4(self, enable: Optional[bool]): + if enable is None: + return + flags = self.__io.get(TSI_DPRX_LINK_FLAGS)[1] & ~0xc0 + if enable: + flags |= (1 << 2) + else: + flags &= ~(1 << 2) + if self.__caps.fec: + self.__io.set(TSI_DPRX_LINK_FLAGS, flags, c_int) + + def __get_tps4(self) -> Optional[bool]: + if self.__caps.fec: + return bool((self.__io.get(TSI_DPRX_LINK_FLAGS)[1] >> 2) & 0x1) + + def __set_ss_sbm(self, enable: bool): + if enable is None: + return + if self.__caps.dp2_support_rates: + flags = self.__io.get(TSI_DPRX_LINK_FLAGS)[1] & ~0xc0 + if enable: + flags |= (1 << 5) + else: + flags &= ~(1 << 5) + self.__io.set(TSI_DPRX_LINK_FLAGS, flags, c_int) + + def __get_ss_sbm(self) -> Optional[bool]: + if self.__caps.dp2_support_rates: + return bool((self.__io.get(TSI_DPRX_LINK_FLAGS)[1] >> 5) & 0x1) + + def __set_edp_support(self, enable: bool): + if self.__caps.edp: + flags = self.__io.get(TSI_DPRX_LINK_FLAGS)[1] & ~0xc0 + if enable: + flags |= (1 << 3) + else: + flags &= ~(1 << 3) + self.__io.set(TSI_DPRX_LINK_FLAGS, flags, c_int) + + def __get_edp_support(self) -> Optional[bool]: + if self.__caps.edp: + return bool((self.__io.get(TSI_DPRX_LINK_FLAGS)[1] >> 3) & 0x1) + + def __set_edp_sel_rates(self, rates: List[float]): + if self.__caps.edp: + rates.sort() + rates = rates[:8] + to_write_list = [0 for i in range(8)] + j = 0 + for i in range(len(rates)): + if rates[i] in self.__get_edp_caps_rates(): + to_write_list[j] = int(rates[i] / 0.0002) + j += 1 + + rates.sort() + self.__io.set(TSI_DPRX_EDP_SEL_RATES, to_write_list, data_type=c_uint32, data_count=8) + + def __get_edp_sel_rates(self) -> List[float]: + if self.__caps.edp: + rates = self.__io.get(TSI_DPRX_EDP_SEL_RATES, data_type=c_uint32, data_count=8)[1] + res_rates = [] + for rate in rates: + if rate == 0: + return res_rates + res_rates.append(rate * 0.0002) + return res_rates + + def __get_edp_caps_rates(self) -> List[float]: + if self.__caps.edp: + rates = self.__io.get(TSI_DPRX_EDP_CAPS_RATES_R, data_type=c_uint32, data_count=32)[1] + new_rates = [] + for rate in rates: + if rate > 0: + new_rates.append(rate * 0.0002) + return new_rates diff --git a/UniTAP/dev/ports/modules/link/dp/link_rx_status.py b/UniTAP/dev/ports/modules/link/dp/link_rx_status.py new file mode 100644 index 0000000..e26b99e --- /dev/null +++ b/UniTAP/dev/ports/modules/link/dp/link_rx_status.py @@ -0,0 +1,407 @@ +import time +import warnings + +from typing import Optional + +from UniTAP.libs.lib_tsi import PortIO +from UniTAP.libs.lib_tsi.tsi_private_types import * +from UniTAP.libs.lib_tsi.tsi_types import * +from UniTAP.utils.function_wrapper import function_scheduler +from .private_link_rx_types import DPRXHWCaps +from .link_status_common import * +from .private_link_status_common import VCPTable, DpMsa, MSAInfo, DpCrc, msa_info_to_video_mode +from typing import Union + + +class LinkDisplayPortStatusSink: + """ + Class `LinkDisplayPortStatusSink` describes information about DP link status. Contains following info: + - MST stream count `mst_stream_count`. + - Lane count `lane_count`. + - Link encoding `link_encoding`. + - Link rate `link_rate`. + - State of HPD `hpd_asserted`. + - Cable state `cable_state`. + - State of framing `enhanced_framing`. + - State of scrambling `scrambling_enabled`. + - State of DSC `dsc_enabled` + - State of FEC `fec_enabled`. + - State of MST `mst_enabled`. + - State of SSC `ssc_enabled`. + - State of ILA `ila`. + - State of EQ ILA `eq_ila`. + - State of CDS ILA `cds_ila`. + - State of LT fail `lt_fail`. + - State of selected lane `lane`. + - State of VCP `vcp`. + - State of selected stream `stream`. + """ + def __init__(self, port_io: PortIO, caps: DPRXHWCaps): + self.__io = port_io + self.__caps = caps + + @property + def mst_stream_count(self) -> int: + """ + Returns current MST count. + + Returns: + object of int type + """ + res, value = self.__io.get(TSI_DPRX_MST_STATUS_R, c_uint) + return value >> 8 + + @property + def lane_count(self) -> int: + """ + Returns current lane count. + + Returns: + object of int type + """ + return self.__io.get(TSI_DPRX_LINK_LANE_COUNT_R, c_int)[1] + + @property + def link_encoding(self) -> DpLinkEncoding: + """ + Returns current link encoding `DpLinkEncoding`. + + Returns: + object of `DpLinkEncoding` type + """ + result, value = self.__io.get(TSI_DPRX_LINK_STATUS_FLAGS_R, c_uint) + + if value >> 20 & 1: + return DpLinkEncoding.LE_128b132b + else: + return DpLinkEncoding.LE_8b10b + + @property + def link_rate(self) -> float: + """ + Returns current link rate. + + Returns: + object of float type + """ + current_link_rate = self.__io.get(TSI_DPRX_LINK_BR_R, c_int)[1] + if self.link_encoding == DpLinkEncoding.LE_8b10b: + return round(current_link_rate * 0.27, 2) + else: + if current_link_rate == 1: + return 10.0 + elif current_link_rate == 2: + return 20.0 + elif current_link_rate == 4: + return 13.5 + else: + return 0.0 + + @property + def hpd_asserted(self) -> bool: + """ + Returns current state of HDP asserted. + + Returns: + object of bool type + """ + return bool(self.__io.get(TSI_DPRX_HPD_STATUS_R, c_int)[1] & 1) + + @property + def cable_state(self) -> bool: + """ + Returns current cable state. + + Returns: + object of bool type + """ + return bool(self.__io.get(TSI_DPRX_HPD_STATUS_R, c_int)[1] & 4) + + @property + def enhanced_framing(self) -> bool: + """ + Returns current enhanced framing state. + + Returns: + object of bool type + """ + return bool((self.__io.get(TSI_DPRX_LINK_STATUS_FLAGS_R, c_uint32)[1] >> 17) & 1) + + @property + def scrambling_enabled(self) -> bool: + """ + Returns current scrambling enabled state. + + Returns: + object of bool type + """ + return bool((self.__io.get(TSI_DPRX_LINK_STATUS_FLAGS_R, c_uint32)[1] >> 18) & 1) + + @property + def dsc_enabled(self) -> Union[bool, None]: + """ + Returns current DSC state. + + Returns: + object of bool|None type + """ + if self.__caps.dsc or self.__caps.dsc2: + return bool((self.__io.get(TSI_DPRX_DSC_STATUS_R)[1] >> 1) & 1) + else: + return None + + @property + def fec_enabled(self) -> Union[bool, None]: + """ + Returns current FEC state. + + Returns: + object of bool|None type + """ + if self.__caps.fec or self.__caps.fec2: + return bool(self.__io.get(TSI_DPRX_FEC_STATUS_R)[1] & 1) + else: + return None + + @property + def mst_enabled(self) -> Union[bool, None]: + """ + Returns current MST state. + + Returns: + object of bool|None type + """ + if self.__caps.mst: + return bool((self.__io.get(TSI_DPRX_LINK_STATUS_FLAGS_R, c_uint32)[1] >> 19) & 1) + else: + return None + + @property + def ssc_enabled(self) -> Union[bool, None]: + """ + Returns current SSC state. + + Returns: + object of bool|None type + """ + if (self.__io.get(TSI_DPRX_SSC_STATUS_R, c_int)[1] >> 1) & 1: + return bool(self.__io.get(TSI_DPRX_SSC_STATUS_R, c_int)[1] & 1) + else: + return None + + @property + def ila(self) -> bool: + """ + Returns current ILA state. + + Returns: + object of bool type + """ + return bool((self.__io.get(TSI_DPRX_LINK_STATUS_FLAGS_R, c_uint32)[1] >> 16) & 1) + + @property + def eq_ila(self) -> bool: + """ + Returns current EQ ILA state. + + Returns: + object of bool type + """ + return bool((self.__io.get(TSI_DPRX_LINK_STATUS_FLAGS_R, c_uint32)[1] >> 30) & 1) + + @property + def cds_ila(self) -> bool: + """ + Returns current CDS ILA state. + + Returns: + object of bool type + """ + return bool((self.__io.get(TSI_DPRX_LINK_STATUS_FLAGS_R, c_uint32)[1] >> 29) & 1) + + @property + def lt_fail(self) -> bool: + """ + Returns current LT fail state. + + Returns: + object of bool type + """ + return bool((self.__io.get(TSI_DPRX_LINK_STATUS_FLAGS_R, c_uint32)[1] >> 28) & 1) + + def lane(self, lane_number: int) -> LaneStatus: + """ + Returns status of lane `LaneStatus`. + + Args: + lane_number (int) - number of selected number + + Returns: + object of `LaneStatus` type + """ + if not (0 <= lane_number <= 3): + raise ValueError(f"Incorrect lane number {lane_number}. Available range: 0-{self.lane_count}") + + status = self.__io.get(TSI_DPRX_LINK_STATUS_FLAGS_R, c_uint32)[1] + voltage_swing = self.__io.get(TSI_DPRX_LINK_VS_R, c_uint32)[1] + pre_emphasis = self.__io.get(TSI_DPRX_LINK_PE_R, c_uint32)[1] + _, error_counters = self.__io.get(TSI_DPRX_GET_ERROR_COUNTS_R, c_uint32 * 4) + + lane_status = LaneStatus() + + if lane_number == 0: + lane_status.cr = bool(status & 0x1) + lane_status.sl = bool((status >> 1) & 0x1) + lane_status.eq = bool((status >> 2) & 0x1) + lane_status.error_count = error_counters[0] + elif lane_number == 1: + lane_status.cr = bool((status >> 4) & 0x1) + lane_status.sl = bool((status >> 5) & 0x1) + lane_status.eq = bool((status >> 6) & 0x1) + lane_status.error_count = error_counters[1] + elif lane_number == 2: + lane_status.cr = bool((status >> 8) & 0x1) + lane_status.sl = bool((status >> 9) & 0x1) + lane_status.eq = bool((status >> 10) & 0x1) + lane_status.error_count = error_counters[2] + elif lane_number == 3: + lane_status.cr = bool((status >> 12) & 0x1) + lane_status.sl = bool((status >> 13) & 0x1) + lane_status.eq = bool((status >> 14) & 0x1) + lane_status.error_count = error_counters[3] + + if self.__caps.dp2_support_rates and self.link_encoding == DpLinkEncoding.LE_128b132b: + lane_status.voltage_swing = 0 + lane_status.pre_emphasis = 0 + _, ffe_preset = self.__io.get(TSI_DP2RX_LINK_FFE_PRESET_R, c_uint32) + lane_status.ffe_preset = (ffe_preset >> (8 * lane_number)) & 0xF + else: + lane_status.voltage_swing = (voltage_swing >> (8 * lane_number)) & 0x3 + lane_status.pre_emphasis = (pre_emphasis >> (8 * lane_number)) & 0x3 + lane_status.ffe_preset = 0 + + return lane_status + + def vcp(self, stream_index: int = 0) -> Optional[VCPStatus]: + """ + Returns VCP status of selected stream `VCPStatus`. + + Args: + stream_index (int) - number of selected number + + Returns: + object of `VCPStatus` | None type + """ + if self.__caps.mst: + if not stream_index < self.mst_stream_count: + raise ValueError(f"Selected stream {stream_index} is not available. " + f"Available stream number: {self.mst_stream_count}") + + res = self.__io.get(TSI_DPRX_VC_TABLE_R, VCPTable, 4)[1] + + if self.mst_enabled: + status = VCPStatus() + + status.port_number = res[stream_index].port_number + status.stream_id = res[stream_index].stream_id + status.req_pbn = res[stream_index].req_pbn + status.alloc_pbn = res[stream_index].alloc_pbn + status.first_slot = res[stream_index].first_slot + status.slot_num = res[stream_index].slot_num + + return status + else: + warnings.warn("MST does not work. Cannot show VCP status.") + return VCPStatus() + + def __check_video(self) -> bool: + def is_msa_available(io): + result, msa_info, size = io.get(TSI_DPRX_MSA_INFO_R, DpMsa, 4) + return size > 0 + + return function_scheduler(is_msa_available, self.__io, interval=5, timeout=10) + + def stream(self, stream_index: int) -> StreamStatusDP: + """ + Returns status of selected stream `StreamStatusDP`. + + Args: + stream_index (int) - number of selected number + + Returns: + object of `StreamStatusDP` type + """ + stream_status = StreamStatusDP() + + if not self.__check_video(): + warnings.warn("Video is not available.") + return stream_status + + time.sleep(1) + + result, msa_info, size = self.__io.get(TSI_DPRX_MSA_INFO_R, DpMsa, 4) + + if stream_index < size: + msa_info_private = MSAInfo(msa_info[stream_index]) + stream_status.video_mode = msa_info_to_video_mode(msa_info_private, self.link_rate * 100000000, + self.link_encoding) + stream_status.mvid = msa_info_private.m_video + stream_status.nvid = msa_info_private.n_video + + result = self.__io.get(TSI_DPRX_CRC_LOG_R, DpCrc, self.mst_stream_count) + if result[0] >= TSI_SUCCESS and self.mst_stream_count > 1: + stream_status.crc = [result[1][stream_index].r, result[1][stream_index].g, result[1][stream_index].b] + elif result[0] >= TSI_SUCCESS and size == 1: + stream_status.crc = [result[1].r, result[1].g, result[1].b] + + result = self.__io.get(TSI_DPRX_DSC_CRC_R, DpCrc, self.mst_stream_count) + if result[0] >= TSI_SUCCESS and self.mst_stream_count > 1: + stream_status.dsc_crc = [result[1][stream_index].r, result[1][stream_index].g, result[1][stream_index].b] + elif result[0] >= TSI_SUCCESS and size == 1: + stream_status.dsc_crc = [result[1].r, result[1].g, result[1].b] + + if self.__caps.dp2_support_rates: + res = self.__io.get(TSI_DPRX_SDP_CRC16_CTRL, c_uint32) + stream_status.sdp_crc16.state = False if res[0] < TSI_SUCCESS else (res[1] & 0x1 == 1) + stream_status.sdp_crc16.errors = self.__io.get(TSI_DPRX_SDP_CRC16_COUNTERS, c_uint8, + self.__caps.mst_stream_count)[1][stream_index] + else: + raise ValueError(f"Selected stream {stream_index} is not available. " + f"Available stream number: {size}") + + return stream_status + + def reset_sdp_crc16_errors(self): + """ + Reset SDP CRC16 errors. + """ + if not self.__caps.dp2_support_rates: + warnings.warn("SDP CRC16 is not supported. Cannot reset errors.") + return + self.__io.set(TSI_DPRX_SDP_CRC16_COUNTERS, 0) + + def __str__(self) -> str: + lane_status_str = "" + stream_info_str = "" + vcp_table_str = "" + for i in range(self.mst_stream_count): + lane_status_str += f"Lane {i}\n{self.lane(i)}\n" + stream_info_str += f"Stream {i}\n{self.stream(i)}\n" + vcp_table_str += f"#{i}\n{self.vcp(i)}" + + return f"Lane count: {self.lane_count}\n" \ + f"Bit rate: {self.link_rate} Gbps\n" \ + f"Enhanced framing mode: {self.enhanced_framing}\n" \ + f"MST: {self.mst_enabled}\n" \ + f"DSC: {self.dsc_enabled}\n" \ + f"Link Encoding: {self.link_encoding.name}\n" \ + f"Scrambling: {self.scrambling_enabled}\n" \ + f"SSC: {self.ssc_enabled}\n" \ + f"FEC: {self.fec_enabled}\n" \ + f"ILA: {self.ila}\n" \ + f"EQ_ILA: {self.eq_ila}\n" \ + f"CDS_ILA: {self.cds_ila}\n" \ + f"LT_FAIL: {self.lt_fail}\n\n" \ + f"Lane status:\n{lane_status_str}\n" \ + f"Stream info:\n{stream_info_str}\n" \ + f"VCP status:\n{vcp_table_str}\n" diff --git a/UniTAP/dev/ports/modules/link/dp/link_rx_types.py b/UniTAP/dev/ports/modules/link/dp/link_rx_types.py new file mode 100644 index 0000000..91d465e --- /dev/null +++ b/UniTAP/dev/ports/modules/link/dp/link_rx_types.py @@ -0,0 +1,177 @@ +from typing import TypeVar +from enum import IntEnum + + +class LinkCapabilities: + """ + Class `LinkCapabilities` describes capabilities of DP link. + """ + def __init__(self): + self.max_lane = None + self.bit_rate = None + self.dp_128_132_bitrates = None + self.override_10g = None + self.old_dp_2_0_lt = None + self.force_cable_status_to_plugged = None + self.mst = None + self.ss_sbm = None + self.fec = None + self.tps4 = None + self.tps3 = None + self.dsc = None + self.mst_sink_count = None + + def __str__(self) -> str: + return f"Max Lanes - {self.max_lane}\n" \ + f"Max Bitrate - {self.bit_rate}\n" \ + f"DP (128b/132b) Supported Bitrates - {self.dp_128_132_bitrates}\n" \ + f"Instead of 10 Gbps, use - {self.override_10g}\n" \ + f"Old DP 2.0 LT - {self.old_dp_2_0_lt}\n" \ + f"Force Cable Status to Plugged - {self.force_cable_status_to_plugged}\n" \ + f"MST - {self.mst} (Sink Count - {self.mst_sink_count})\n" \ + f"SS SBM - {self.ss_sbm}\n" \ + f"FEC - {self.fec}\n" \ + f"TPS4 - {self.tps4}\n" \ + f"TPS3 - {self.tps3}\n" \ + f"DSC - {self.dsc}\n" + + +class LinkEDPCapabilities: + """ + Class `LinkEDPCapabilities` describes capabilities of eDP link. + """ + + def __init__(self): + self.max_lane = None + self.eDp_cur_rate = None + self.eDp_supported_rates = None + self.eDp_aux_preamble = None + self.eDp_support = None + + def __str__(self) -> str: + return f"Max Lanes - {self.max_lane}\n" \ + f"eDP rate - {self.eDp_cur_rate}\n" \ + f"eDP supported rates - {self.eDp_supported_rates}\n" \ + f"eDP AUX preamble - {self.eDp_aux_preamble}\n" \ + f"eDp Support - {self.eDp_support}" + + +DisplayPortLinkCaps = TypeVar("DisplayPortLinkCaps", + LinkEDPCapabilities, + LinkCapabilities) + + +class CableCapabilitiesEnum(IntEnum): + Unknown = 0, + DP40 = 1 + DP54 = 2 + DP80 = 3 + + def __str__(self) -> str: + return self.name + + +class RoutedLTConfig: + """ + Class `RoutedLTConfig` describes configuration fields for Routed LinkTraining. + """ + def __init__(self): + self.__is128b132b = False + self.__is_old_dp20_lt = False + self.__vs = 0 + self.__pe = 0 + self.__ffe = 0 + self.__link_bw = 0 + self.__lane_count = 0 + + @property + def is128b132b(self) -> bool: + return self.__is128b132b + + @is128b132b.setter + def is128b132b(self, value: bool = False): + self.__is128b132b = value + + @property + def is_old_dp20_lt(self) -> bool: + return self.__is_old_dp20_lt + + @is_old_dp20_lt.setter + def is_old_dp20_lt(self, value: bool = False): + self.__is_old_dp20_lt = value + + @property + def vs(self) -> int: + return self.__vs + + @vs.setter + def vs(self, value: int): + if not (0 <= value < 3): + raise ValueError(f"VS cannot be less than 0 and more than 3") + self.__vs = value + + @property + def pe(self) -> int: + return self.__pe + + @pe.setter + def pe(self, value: int): + if not (0 <= value <= 0x3): + raise ValueError(f"PE cannot be less than 0 and more than 0x3") + self.__pe = value + + @property + def ffe(self) -> int: + return self.__ffe + + @ffe.setter + def ffe(self, value: int): + if not (0 <= value <= 0xF): + raise ValueError(f"FFE cannot be less than 0 and more than 0xF") + self.__ffe = value + + @property + def link_bw(self) -> int: + return self.__link_bw + + @link_bw.setter + def link_bw(self, value: int): + if not (0 <= value <= 0xFF): + raise ValueError(f"link_bw cannot be less than 0 and more than 0xFF") + self.__link_bw = value + + @property + def lane_count(self) -> int: + return self.__lane_count + + @lane_count.setter + def lane_count(self, value: int): + if not (0 <= value <= 0xFF): + raise ValueError(f"lane_count cannot be less than 0 and more than 0xFF") + self.__lane_count = value + + def int_value(self) -> int: + val = (self.ffe << 8) if self.is128b132b else ((self.vs << 8) | (self.pe << 10)) + return self.is128b132b | (self.__is_old_dp20_lt << 1) | val | (self.link_bw << 16) | (self.lane_count << 24) + + +class RoutedLTStatus: + + __StateToStr = {0: "Not started", 1: "In progress", 2: "Finished", 3: "Incorrect"} + + def __init__(self, enabled: bool, dp20_old_lt: bool, state: int, success: bool, step: int): + self.enabled = enabled + self.dp20_old_lt = dp20_old_lt + self.state = state + self.success = success + self.step = step + + def __str__(self) -> str: + + return (f"Is Enabled: {'yes' if self.enabled else 'no'}\n" + f"Is DP 20 old LT: {'yes' if self.dp20_old_lt else 'no'}\n" + f"State: {self.__StateToStr.get(self.state)}\n" + f"Result: {'Successful' if self.success else 'Unsuccessful'}\n" + f"Steps: {self.step}") + + diff --git a/UniTAP/dev/ports/modules/link/dp/link_status_common.py b/UniTAP/dev/ports/modules/link/dp/link_status_common.py new file mode 100644 index 0000000..50d6827 --- /dev/null +++ b/UniTAP/dev/ports/modules/link/dp/link_status_common.py @@ -0,0 +1,173 @@ +from enum import IntEnum +from UniTAP.common.video_mode import VideoMode + + +class VCPStatus: + """ + Class `VCPStatus` describes МСЗ status. Contains following information: + - Port number. + - Stream ID. + - Requested PBN. + - Allocated PBN. + - Number of slots allocated for VC. + - Number of first time slot allocated for VC. + """ + def __init__(self): + self.port_number = 0 + self.stream_id = 0 + self.req_pbn = 0 + self.alloc_pbn = 0 + self.first_slot = 0 + self.slot_num = 0 + + def __eq__(self, other): + return self.port_number == other.port_number and \ + self.stream_id == other.stream_id and \ + self.req_pbn == other.req_pbn and \ + self.alloc_pbn == other.alloc_pbn and \ + self.first_slot == other.first_slot and \ + self.slot_num == other.slot_num + + def __str__(self) -> str: + return f"Port # - {self.port_number}\n" \ + f"SID - {self.stream_id}\n" \ + f"REQ PBN - {self.req_pbn}\n" \ + f"Alloc PBN - {self.alloc_pbn}\n" \ + f"First Slot - {self.first_slot}\n" \ + f"Slot num - {self.slot_num}\n" + + +class DpLinkEncoding(IntEnum): + """ + Class `DpLinkEncoding` contains all possible variants of DP link encoding. + """ + LE_NONE = 0 + LE_8b10b = 1 + LE_128b132b = 2 + + def __str__(self): + if self.value == DpLinkEncoding.LE_NONE: + return "None" + elif self.value == DpLinkEncoding.LE_8b10b: + return "8b/10b" + elif self.value == DpLinkEncoding.LE_128b132b: + return "128b/132b" + else: + return "Invalid" + + +class DpLinkTrainingResult(IntEnum): + """ + Class `DpLinkTrainingResult` contains all possible variants of Link training results. + """ + LTR_NOT_STARTED = 0 + LTR_IN_PROGRESS = 1 + LTR_FAIL = 2 + LTR_SUCCESS = 3 + + +class LaneStatus: + """ + Class `LaneStatus` describes lane status. Contains following information: + - CR state. + - SL state. + - EQ state. + - Voltage swing value. + - Pre Emphasis value. + - FFE preset value. + - Error count. + """ + def __init__(self): + self.cr = False + self.sl = False + self.eq = False + self.voltage_swing = 0 + self.pre_emphasis = 0 + self.ffe_preset = 0 + self.error_count = 0 + + def __eq__(self, other) -> bool: + return self.cr == other.cr and \ + self.sl == other.sl and \ + self.eq == other.eq and \ + self.voltage_swing == other.voltage_swing and \ + self.pre_emphasis == other.pre_emphasis and \ + self.ffe_preset == other.ffe_preset and \ + self.error_count == other.error_count + + def __str__(self) -> str: + return f"CR - {bool(self.cr)}\nSL - {bool(self.sl)}\nEQ - {bool(self.eq)}\n" \ + f"Voltage Swing - {self.voltage_swing}\n" \ + f"Pre Emphasis - {self.pre_emphasis}\nFFE Preset - {self.ffe_preset}\nError count - {self.error_count}\n" + + +class SdpCrc16: + """ + Class `SdpCrc16` describes SDP CRC16 errors on the stream. It contains 'State' - enabled or disabled, and errors - + count of the errors on the stream. + """ + def __init__(self): + self.state = False + self.errors = 0 + + def __eq__(self, other) -> bool: + return self.state == other.state and self.errors == other.errors + + def __str__(self) -> str: + return f"State: {'Enabled' if self.state else 'Disabled'}\n" \ + f"Errors: {hex(self.errors)}" + + +class StreamStatus: + """ + Class `StreamStatus` describes stream status. Contains following information: + - Video mode `VideoMode`. + - CRC value of stream. + - DSC CRC value of stream. + - SDP CRC16 error of stream + """ + def __init__(self): + self.video_mode = VideoMode() + self.crc = [0, 0, 0] + self.dsc_crc = [0, 0, 0] + self.sdp_crc16 = SdpCrc16() + + def __str__(self) -> str: + return f"Video mode:\n{self.video_mode.__str__()}\n" \ + f"CRC: r - {self.crc[0]}, g - {self.crc[1]}, b - {self.crc[2]}\n" \ + f"DSC CRC: r - {self.dsc_crc[0]}, g - {self.dsc_crc[1]}, b - {self.dsc_crc[2]}\n" + + +class StreamStatusDP(StreamStatus): + """ + The `StreamStatusDP` class inherited from the `StreamStatus` class and contains all the functionality. + - MVID + - NVID + - VFREQ `vfreq` + """ + def __init__(self): + super().__init__() + self.mvid = 0 + self.nvid = 0 + + @property + def vfreq(self) -> int: + """ + Return value of VFREQ. + + Returns: + object of `int` type + + """ + return ((self.mvid & 0xFFFFFF) << 24) | (self.nvid & 0xFFFFFF) + + def __str__(self) -> str: + return f"Video mode:\n{self.video_mode.__str__()}\n" \ + f"CRC: r - {self.crc[0]}, g - {self.crc[1]}, b - {self.crc[2]}\n" \ + f"DSC CRC: r - {self.dsc_crc[0]}, g - {self.dsc_crc[1]}, b - {self.dsc_crc[2]}\n" \ + f"MVID / NVID - {self.mvid:06X} / {self.nvid:06X}\n" \ + f"VFREQ - {self.vfreq}\n" + + +DP21_LinkRate = {1: 10.0, 2: 20.0, 4: 13.5} +DP21_LinkRateRev = {10.0: 1, 20.0: 2, 13.5: 4} diff --git a/UniTAP/dev/ports/modules/link/dp/link_tx.py b/UniTAP/dev/ports/modules/link/dp/link_tx.py new file mode 100644 index 0000000..206df95 --- /dev/null +++ b/UniTAP/dev/ports/modules/link/dp/link_tx.py @@ -0,0 +1,279 @@ +from warnings import warn + +from UniTAP.libs.lib_tsi import * +from UniTAP.dev.ports.modules.link.dp.link_status_common import DpLinkTrainingResult +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from UniTAP.utils.function_wrapper import * +from .link_tx_status import LinkDisplayPortStatusSource +from .link_tx_config import LinkDisplayPortConfig +from .link_tx_force_config import LinkDisplayPortForceConfig +from .private_link_tx_types import * +from UniTAP.dev.ports.modules.dpcd import DPCDRegisters +from .link_tx_types import DPLinkPattern, DPOutLinkMode +from .dp_cable_info import DpCableInfo, CableCapabilitiesEnum + + +class LinkDisplayPortTx: + """ + Class `LinkDisplayPortTx` contains information about DP link. + - Read link status `status`. + - Configure and read link configuration `config`. + - Get maximum stream count `max_stream_count`. + - Do link training `link_training`. + - Get last result of link training `last_lt_result`. + - Read and write scrambler seed value `scrambler_seed`. + - Set `set_override_voltage` and get `get_override_voltage` override voltage. + - Set `set_override_pre_emp` and get `get_override_pre_emp` override pre-emphasis. + - Set and get override FFE presets `override_ffe_presets`. + - Set and get link pattern `link_pattern` 'set_link_pattern'. + - Set and get force link config `LinkDisplayPortForceConfig`. + """ + def __init__(self, port_io: PortIO, dpcd: DPCDRegisters, hw_caps: DPTXHWCaps): + self.__io = port_io + self.__HW_CAPS = hw_caps + self.__status = LinkDisplayPortStatusSource(self.__io, self.__HW_CAPS, dpcd) + self.__config = LinkDisplayPortConfig(self.__io, self.__HW_CAPS) + self.__force_config = LinkDisplayPortForceConfig(self.__io, self.__HW_CAPS) + self.__cable_info = DpCableInfo(port_io, TSI_DPTX_CABLE_ATTRIBUTES_R) + + @property + def status(self) -> LinkDisplayPortStatusSource: + """ + Returns object of class `LinkDisplayPortStatusSource` for working with link status. + + Returns: + object of `LinkDisplayPortStatusSource` type + """ + return self.__status + + @property + def config(self) -> LinkDisplayPortConfig: + """ + Returns object of class `LinkDisplayPortConfig` for working with link configuration. + + Returns: + object of `LinkDisplayPortConfig` type + """ + return self.__config + + @property + def force_config(self) -> LinkDisplayPortForceConfig: + """ + Returns object of class `LinkDisplayPortForceConfig` for working with link configuration. + + Returns: + object of `LinkDisplayPortForceConfig` type + """ + return self.__force_config + + @property + def max_stream_count(self): + """ + Returns maximum supported stream count. + + Returns: + object of int type + """ + return self.__HW_CAPS.mst_stream_count + + def start_link_training(self) -> bool: + """ + Make link training. Returns 'True' state if link training was success, 'False' - if not. + + Returns: + object of bool type + """ + def is_link_training_success(link: LinkDisplayPortTx): + return link.last_lt_result() == DpLinkTrainingResult.LTR_SUCCESS + + self.__io.set(TSI_W_DPTX_COMMAND, 1, c_uint32) + + return function_scheduler(is_link_training_success, self, interval=0.1, timeout=5) + + def last_lt_result(self) -> DpLinkTrainingResult: + """ + Returns last result of link training. + + Returns: + object of `DpLinkTrainingResult` type + """ + _, value = self.__io.get(TSI_DPTX_LT_RESULT_R, c_uint) + return DpLinkTrainingResult(value) + + @property + def scrambler_seed(self) -> int: + """ + Returns scrambler seed value. + + Returns: + object of `int` type + """ + if not self.__HW_CAPS.scrambler_seed: + warnings.warn("Scrambler Seed is not supported.") + return 0 + + return self.__io.get(TSI_DPTX_SCR_SEED, c_uint32)[1] + + @scrambler_seed.setter + def scrambler_seed(self, value: int = 0): + """ + Write new value to scrambler seed + + Args: + value (int) - new scrambler seed value + """ + if not self.__HW_CAPS.scrambler_seed: + warnings.warn("Scrambler Seed is not supported.") + return + if self.config._read_lt_features().auto_seed is not None and not self.config._read_lt_features().auto_seed: + self.__io.set(TSI_DPTX_SCR_SEED, value, c_uint32) + else: + warnings.warn("AutoSeed has already enabled. Transferred value will not be written") + + def get_override_voltage(self, stream_index: int) -> int: + """ + Returns override voltage of selected stream. + + Args: + stream_index (int) - number of selected stream + + Returns: + object of `int` type + """ + return (self.__get_override_voltage() >> stream_index) & 0x3 + + def set_override_voltage(self, stream_index: int, value: int): + """ + Returns override voltage of selected stream. + + Args: + stream_index (int) - number of selected stream + value (int) - new override voltage value + """ + if stream_index < self.status.mst_stream_count: + raise ValueError(f"Selected stream {stream_index} is not available. " + f"Available stream number: {self.status.mst_stream_count}") + + if not (0 <= value <= 3): + raise ValueError(f"Incorrect input value {value}. Available range: 0-{self.status.lane_count}") + + old_value = self.__get_override_voltage() + old_value &= ~(3 << (stream_index * 8)) + old_value |= (value << (stream_index * 8)) + self.__io.set(TSI_DPTX_OVERRIDE_VOLTAGE_SWING, old_value, c_uint32) + + def get_override_pre_emp(self, stream_index: int) -> int: + """ + Returns override pre-emphasis of selected stream. + + Args: + stream_index (int) - number of selected stream + + Returns: + object of `int` type + """ + return (self.__get_override_pre_emp() >> stream_index) & 0x3 + + def set_override_pre_emp(self, stream_index: int, value: int): + """ + Returns override pre-emphasis of selected stream. + + Args: + stream_index (int) - number of selected stream + value (int) - new override pre-emphasis value + """ + if stream_index < self.status.mst_stream_count: + raise ValueError(f"Selected stream {stream_index} is not available. " + f"Available stream number: {self.status.mst_stream_count}") + + if not (0 <= value <= 3): + raise ValueError(f"Incorrect input value {value}. Available range: 0-{self.status.lane_count}") + + old_value = self.__get_override_pre_emp() + old_value &= ~(3 << (stream_index * 8)) + old_value |= (value << (stream_index * 8)) + self.__io.set(TSI_DPTX_OVERRIDE_PRE_EMPHASIS, old_value, c_uint32) + + @property + def override_ffe_presets(self) -> list: + """ + Returns override FFE presets values. + + Returns: + object of `list` type + """ + if not self.__HW_CAPS.dp2_custom_rates: + warnings.warn("FFE presets does not support.") + return [] + return self.__io.get(TSI_DP2TX_OUT_FFE, c_uint8, 4)[1] + + @override_ffe_presets.setter + def override_ffe_presets(self, value: List[int]): + """ + Returns override FFE presets. + + Args: + value list[int] - new values for override FFE presets. + """ + if not self.__HW_CAPS.dp2_custom_rates: + warnings.warn("FFE presets does not support.") + return + new_value = value[0] | (value[0] << 8) | (value[0] << 16) | (value[0] << 32) + self.__io.set(TSI_DP2TX_OUT_FFE, new_value) + + def __get_override_voltage(self) -> int: + return self.__io.get(TSI_DPTX_OVERRIDE_VOLTAGE_SWING, c_uint32)[1] + + def __get_override_pre_emp(self) -> int: + return self.__io.get(TSI_DPTX_OVERRIDE_PRE_EMPHASIS, c_uint32)[1] + + @property + def link_pattern(self) -> DPLinkPattern: + """ + Returns current DP link pattern `DPLinkPattern`. + + Returns: + object of `DPLinkPattern` type + """ + return DPLinkPattern(self.__io.get(TSI_DPTX_OUTPUT_PATTERN, c_uint32)[1]) + + def set_link_pattern(self, pattern: DPLinkPattern, additional_param: int = 1): + """ + Write DP link pattern value `DPLinkPattern`. + + Args: + pattern (DPLinkPattern) - new pattern value. + additional_param (int) + """ + if not self.__HW_CAPS.dp2_custom_rates and pattern not in [e.value for e in DPLinkPattern]: + raise ValueError(f"Current pattern {pattern.name} is not supported") + self.__io.set(TSI_DPTX_OUTPUT_PATTERN, pattern.value, c_uint32) + + if pattern == DPLinkPattern.LinkSquarePattern: + self.__io.set(TSI_DPTX_SQUARE_PATTERN_NUMBER, additional_param, c_uint32) + + def set_force_link_mode(self, link_mode: DPOutLinkMode): + self.__io.set(TSI_DPTX_OUT_LINK_MODE, link_mode.value, c_uint32) + + def get_force_link_mode(self) -> DPOutLinkMode: + return DPOutLinkMode(self.__io.get(TSI_DPTX_OUT_LINK_MODE, c_uint32)[1]) + + def cable_rx_type(self) -> CableCapabilitiesEnum: + """ + Get cable type from the RX side. + + Returns: + object of `CableCapabilitiesEnum` type + """ + return self.__cable_info.get_another_port_cable_type() + + def cable_tx_type(self) -> CableCapabilitiesEnum: + """ + Get cable type from the TX side. + + Returns: + object of `CableCapabilitiesEnum` type + """ + warn("TX cable information is no longer available on the TX port.", DeprecationWarning,2) + + return CableCapabilitiesEnum.Unknown diff --git a/UniTAP/dev/ports/modules/link/dp/link_tx_config.py b/UniTAP/dev/ports/modules/link/dp/link_tx_config.py new file mode 100644 index 0000000..e5009e9 --- /dev/null +++ b/UniTAP/dev/ports/modules/link/dp/link_tx_config.py @@ -0,0 +1,360 @@ +from typing import Optional, Union, Type +from UniTAP.libs.lib_tsi import PortIO +from UniTAP.libs.lib_tsi.tsi_private_types import * +from .link_tx_types import * +from .private_link_tx_types import * +from .link_status_common import * + + +class LinkDisplayPortConfig: + """ + Class `LinkDisplayPortConfig` allows settings link configuration on Source (TX - transmitter) side. + - Set configuration `set`. + - Get current configuration on link `get`. + """ + def __init__(self, port_io: PortIO, caps: DPTXHWCaps): + self.__io = port_io + self.__caps = caps + self.__dp2_custom_rate = self.__get_dp2_custom_rates() + + def set(self, config: DisplayPortLinkConfig): + """ + Write new configuration on DP link. + + Args: + config (`DisplayPortLinkConfig`) - `LinkConfig.DP8b10b`, `LinkConfig.DP128b132b` or `LinkConfig.eDP`. + """ + if isinstance(config, LinkConfig.DP8b10b): + self.__set_lane_count_dp14(config.lane_count) + self.__set_bit_rate_14(config.bit_rate) + self.__set_lt_features(config) + self.__set_config_ssc(config.ssc) + self.__enable_mst(config.mst) + self.__set_mst_channel_count(config.mst_stream_count) + self.__try_fec_after_lt(config.fec) + self.__set_post_lt_features(config) + self.__sdp_control(config) + elif isinstance(config, LinkConfig.DP128b132b): + self.__set_lane_count_dp21(config.lane_count) + self.__set_bit_rate_21(config.bit_rate) + self.__set_lt_features(config) + self.__set_lttpr(config.lttpr) + self.__set_config_ssc(config.ssc) + self.__set_post_lt_features(config) + self.__enable_mst(config.mst) + self.__set_mst_channel_count(config.mst_stream_count) + self.__sdp_control(config) + elif isinstance(config, LinkConfig.eDP): + self.__set_lane_count_dp14(config.lane_count) + self.__set_lt_features(config) + self.__set_edp_sel_rate(config.eDp_cur_rate) + else: + raise TypeError("Incorrect DisplayPortLinkConfig format!") + + def get(self, config_type: Union[Type[DisplayPortLinkConfig], None] = None) -> DisplayPortLinkConfig: + """ + Returns current DP source link configuration. + + Returns: + object of `DisplayPortLinkConfig` type + """ + if config_type is not None and issubclass(config_type, LinkConfig.eDP): + lt_features = self._read_lt_features() + link_status = LinkConfig.eDP() + link_status.lane_count = self.__get_lane_count_dp14() + link_status.force_edp = lt_features.force_edp + link_status.eDp_cur_rate = self.__get_edp_sel_rate() + link_status.eDp_supported_rates = self.__get_edp_caps_rates() + link_status.eDp_aux_preamble = lt_features.edp_aux_preamble + return link_status + elif config_type is not None and issubclass(config_type, LinkConfig.DP8b10b): + return self.__fill_dp14_config() + elif config_type is not None and issubclass(config_type, LinkConfig.DP128b132b) or \ + self.__link_encoding() == DpLinkEncoding.LE_128b132b: + return self.__fill_dp21_config() + elif config_type is None and self.__link_encoding() == DpLinkEncoding.LE_8b10b: + return self.__fill_dp14_config() + elif config_type is None and \ + (self.__caps.support_10gbps or self.__caps.support_20gbps or self.__caps.support_13_5gbps) and \ + self.__link_encoding() == DpLinkEncoding.LE_128b132b: + return self.__fill_dp21_config() + else: + raise ValueError('Incorrect link encoding type') + + def __fill_dp14_config(self) -> LinkConfig.DP8b10b: + lt_features = self._read_lt_features() + post_lt_features = self.__get_post_lt_features() + link_status = LinkConfig.DP8b10b() + link_status.lane_count = self.__get_lane_count_dp14() + link_status.bit_rate = self.__get_bit_rate_14() + link_status.mst = self.__mst_enabled() + link_status.mst_stream_count = self.__mst_stream_count() + link_status.enhanced_framing_mode = lt_features.framing + link_status.auto_seed = lt_features.auto_seed + link_status.ssc = self.__get_config_ssc() + link_status.fec = self.__fec_after_lt_state() + link_status.force_edid_timings_after_lt = post_lt_features.force_edid_timings + link_status.adaptive_sync_auto_enable = post_lt_features.as_auto_enable + link_status.split_sdp = self.__read_sdp_control().dp_split_sdp != 0 if self.__caps.sdp_split else False + return link_status + + def __fill_dp21_config(self) -> LinkConfig.DP128b132b: + link_status = LinkConfig.DP128b132b() + link_status.lane_count = self.__get_lane_count_dp21() + if self.__check_custom_bit_rate() != 0: + link_status.bit_rate = self.__check_custom_bit_rate() + else: + link_status.bit_rate = self.__get_bit_rate_21() + lt_features = self._read_lt_features() + post_lt_features = self.__get_post_lt_features() + link_status.force_dp_128_132 = lt_features.force_dp2 + link_status.enhanced_framing_mode = lt_features.framing + link_status.max_link_bandwidth_supported = lt_features.max_link_bw_policy + link_status.old_dp2_lt = lt_features.old_dp2_lt + link_status.lttpr = self.__lttpr_active() + link_status.try_dp_128_132 = lt_features.try_dp2 + link_status.auto_seed = lt_features.auto_seed + link_status.ssc = self.__get_config_ssc() + link_status.force_edid_timings_after_lt = post_lt_features.force_edid_timings + link_status.adaptive_sync_auto_enable = post_lt_features.as_auto_enable + link_status.mst = self.__mst_enabled() + link_status.mst_stream_count = self.__mst_stream_count() + link_status.crc_16 = self.__read_sdp_control().dp2_crc_sdp != 0 if self.__caps.dp2_sdp_crc else False + return link_status + + def _read_lt_features(self) -> LTFeatures: + return self.__io.get(TSI_DPTX_LT_FEATURES, LTFeatures)[1] + + def __get_post_lt_features(self) -> PostLtFeatures: + return self.__io.get(TSI_DPTX_POST_LT_FEATURES, PostLtFeatures)[1] + + def __set_post_lt_features(self, config: DisplayPortLinkConfig): + post_lt_features = self.__get_post_lt_features() + if config.force_edid_timings_after_lt is not None and self.__caps.edid_parser: + post_lt_features.force_edid_timings = config.force_edid_timings_after_lt + if config.adaptive_sync_auto_enable is not None and self.__caps.adaptive_sync: + post_lt_features.as_auto_enable = config.adaptive_sync_auto_enable + self.__io.set(TSI_DPTX_POST_LT_FEATURES, post_lt_features.value(), c_uint32) + + def __get_dp2_custom_rates(self) -> list: + rates = self.__io.get(TSI_DP2TX_CUSTOM_RATE_CAPS_R, c_uint32, 32)[1] + custom_rates = [] + for i in range(len(rates)): + custom_rates.append(float(rates[i]) * 200000 / 1000000000) + + return custom_rates + + def __set_lane_count_dp14(self, lane_count: Optional[int]): + if lane_count is None: + return + if lane_count not in [1, 2, 4]: + raise ValueError(f"Incorrect lane count number {lane_count}. Must be from list: 1, 2, 4") + self.__io.set(TSI_DPTX_LINK_CFG_LANES, lane_count, c_uint32) + + def __get_lane_count_dp14(self) -> int: + return self.__io.get(TSI_DPTX_LINK_CFG_LANES)[1] + + def __set_lane_count_dp21(self, lane_count: Optional[int]): + if lane_count is None: + return + if lane_count not in [1, 2, 4]: + raise ValueError(f"Incorrect lane count number {lane_count}. Must be from list: 1, 2, 4") + self.__io.set(TSI_DP2TX_LT_SLC, lane_count, c_uint32) + + def __get_lane_count_dp21(self) -> int: + return self.__io.get(TSI_DP2TX_LT_SLC)[1] + + def __set_bit_rate_14(self, bit_rate: Optional[float]): + if bit_rate is None: + return + if int(round(bit_rate / 0.27)) > self.__caps.max_link_rate: + raise ValueError(f"Following 8b/10b bit rate {bit_rate} is not supported on device. " + f"Max supported link rate {round(self.__caps.max_link_rate * 0.27, 2)}") + self.__io.set(TSI_DPTX_LINK_CFG_BIT_RATE, int(round(bit_rate / 0.27)), c_uint32) + + def __get_bit_rate_14(self) -> float: + return round(self.__io.get(TSI_DPTX_LINK_CFG_BIT_RATE)[1] * 0.27, 2) + + def __set_bit_rate_21(self, bit_rate: Optional[Union[float, int]]): + if bit_rate is None: + return + if bit_rate in self.__dp2_custom_rate: + if not self.__caps.support_10gbps: + raise ValueError(f"Following bit rate {bit_rate} is not supported on device.") + else: + res = self.__io.get(TSI_DP2TX_CUSTOM_RATE_MAP, c_uint32, 3)[1] + self.__io.set(TSI_DP2TX_CUSTOM_RATE_MAP, (int(bit_rate * 1000000000 / 200000), res[1], res[2]), + c_uint32, 3) + elif (bit_rate == 10.0 and not self.__caps.support_10gbps) or \ + (bit_rate == 13.5 and not self.__caps.support_13_5gbps) or \ + (bit_rate == 20.0 and not self.__caps.support_20gbps): + raise ValueError(f"Following bit rate {bit_rate} is not supported on device.") + self.__io.set(TSI_DP2TX_LT_SBR, DP21_LinkRateRev.get(bit_rate), c_uint32) + + def __get_bit_rate_21(self) -> float: + return DP21_LinkRate.get(self.__io.get(TSI_DP2TX_LT_SBR)[1]) + + def __support_ssc(self) -> bool: + return (self.__io.get(TSI_DPTX_DOWNSPREAD_STATUS_R, c_uint32)[1] >> 1) & 1 != 0 + + def __set_config_ssc(self, ssc_conf: Optional[SSCConfig]): + if ssc_conf is None: + return + if not self.__support_ssc(): + raise ValueError(f"SSC is not supported on the device.") + self.__io.set(TSI_DPTX_DOWNSPREAD_CONTROL, int(ssc_conf.enabled), c_uint32) + if ssc_conf.enabled: + if not (1 <= ssc_conf.amplitude <= 50): + raise ValueError(f"Incorrect amplitude value {ssc_conf.amplitude}. Must be from range: 0.1 - 0.5") + self.__io.set(TSI_DPTX_DOWNSPREAD_AMP, ssc_conf.amplitude, c_uint32) + if not (30000 <= ssc_conf.frequency <= 63000): + raise ValueError(f"Incorrect frequency value {ssc_conf.frequency}. Must be from range: 30000 - 63000") + self.__io.set(TSI_DPTX_DOWNSPREAD_FREQ, ssc_conf.frequency, c_uint32) + + def __get_config_ssc(self) -> SSCConfig: + if not self.__support_ssc(): + return None + ssc_conf = SSCConfig() + ssc_conf.enabled = (self.__io.get(TSI_DPTX_DOWNSPREAD_STATUS_R)[1] & 1) != 0 + ssc_conf.frequency = self.__io.get(TSI_DPTX_DOWNSPREAD_FREQ)[1] + ssc_conf.amplitude = self.__io.get(TSI_DPTX_DOWNSPREAD_AMP)[1] + return ssc_conf + + def __dp2_support_rates(self) -> bool: + return self.__caps.support_10gbps or self.__caps.support_13_5gbps or self.__caps.support_20gbps + + def __set_lttpr(self, enable: Optional[bool]): + if enable is None: + return + if not self.__dp2_support_rates(): + raise ValueError(f"LTTPR is not supported on the device.") + self.__io.set(TSI_DPTX_LTTPR_CONTROL, int(enable)) + + def __set_lt_features(self, config: Optional[DisplayPortLinkConfig]): + if config is None: + return + res = self._read_lt_features() + if isinstance(config, LinkConfig.DP8b10b): + if config.enhanced_framing_mode is not None: + res.framing = int(config.enhanced_framing_mode) + if config.auto_seed is not None: + res.auto_seed = int(config.auto_seed) + if self.__caps.support_10gbps or self.__caps.support_20gbps or self.__caps.support_13_5gbps: + res.try_dp2 = 0 + res.try_edp = 0 + elif isinstance(config, LinkConfig.DP128b132b): + if not self.__dp2_support_rates(): + raise ValueError(f"DP 2.1 is not supported on the device.") + if config.try_dp_128_132 is not None: + res.try_dp2 = int(config.try_dp_128_132) + if config.force_dp_128_132 is not None: + res.force_dp2 = int(config.force_dp_128_132) + if config.old_dp2_lt is not None: + res.old_dp2_lt = int(config.old_dp2_lt) + if config.enhanced_framing_mode is not None: + res.framing = int(config.enhanced_framing_mode) + if config.auto_seed is not None: + res.auto_seed = int(config.auto_seed) + if config.max_link_bandwidth_supported is not None: + res.max_link_bw_policy = int(config.max_link_bandwidth_supported) + res.try_edp = 0 + elif isinstance(config, LinkConfig.eDP): + if self.__caps.edp: + if config.force_edp is not None: + res.force_edp = config.force_edp + if config.eDp_aux_preamble is not None: + res.edp_aux_preamble = config.eDp_aux_preamble + res.try_edp = 1 + elif not self.__caps.edp: + raise ValueError("EDP is not supported.") + else: + raise TypeError("Incorrect DisplayPortLinkConfig format!") + + self.__io.set(TSI_DPTX_LT_FEATURES, res.value(), c_uint32) + + def __enable_mst(self, enable: Optional[bool]): + if enable is None: + return + self.__io.set(TSI_DPTX_COMMAND_W, 3 if enable else 4, c_uint32) + + def __set_mst_channel_count(self, count: Optional[int]): + if count is None: + return + if not (0 <= count <= self.__caps.mst_stream_count): + raise ValueError(f"Incorrect stream count {count}. Available stream count {self.__caps.mst_stream_count}") + self.__io.set(TSI_PG_ENABLED_STREAM_COUNT, count, c_uint32) + + def __link_encoding(self) -> DpLinkEncoding: + result, value = self.__io.get(TSI_DPTX_LINK_MODE_R, c_uint) + + if value == 0: + return DpLinkEncoding.LE_8b10b + else: + return DpLinkEncoding.LE_128b132b + + def __mst_enabled(self) -> bool: + return (self.__io.get(TSI_DPTX_LINK_STATUS_BITS_R)[1] & (1 << 30)) != 0 + + def __mst_stream_count(self) -> int: + result, mst_status = self.__io.get(TSI_DPTX_MST_STATUS_R, c_uint) + return (mst_status >> 8) & 0xFF + + def __lttpr_active(self) -> Optional[bool]: + if self.__link_encoding == DpLinkEncoding.LE_128b132b: + return (self.__io.get(TSI_DPTX_LTTPR_CONTROL, c_uint32)[1] & 1) != 0 + else: + return None + + def __check_custom_bit_rate(self) -> float: + res = self.__io.get(TSI_DP2TX_CUSTOM_RATE_MAP, c_uint32, 3)[1] + for i in range(3): + if res[i] != 0: + return res[i] * 200000 / 1000000000 + return 0 + + def __try_fec_after_lt(self, enable: bool): + result = self.__io.get(TSI_DPTX_FEC_CTRL, c_int) + val = result[1] + if enable: + val |= 0x2 + else: + val &= ~0x2 + self.__io.set(TSI_DPTX_FEC_CTRL, val) + + def __fec_after_lt_state(self) -> bool: + return (self.__io.get(TSI_DPTX_FEC_CTRL, c_int)[1] & 0x2) != 0 + + def __sdp_control(self, config: Optional[DisplayPortLinkConfig]): + if config is None: + return + sdp_control = self.__read_sdp_control() + if self.__caps.dp2_sdp_crc and isinstance(config, LinkConfig.DP128b132b) and config.crc_16 is not None: + sdp_control.dp2_crc_sdp = config.crc_16 + if self.__caps.sdp_split and isinstance(config, LinkConfig.DP8b10b) and config.split_sdp is not None: + sdp_control.dp_split_sdp = config.split_sdp + self.__write_sdp_control(sdp_control) + + def __write_sdp_control(self, sdp_control: SdpControl): + self.__io.set(TSI_DPTX_SDP_CTRL, sdp_control.value()) + + def __read_sdp_control(self) -> SdpControl: + return self.__io.get(TSI_DPTX_SDP_CTRL, SdpControl)[1] + + def __set_edp_sel_rate(self, rate: float): + if self.__caps.edp and rate in self.__get_edp_caps_rates(): + self.__io.set(TSI_DPTX_EDP_LT_SBR, int(rate / 0.0002), c_uint32) + elif not self.__caps.edp: + raise ValueError("EDP is not supported.") + + def __get_edp_sel_rate(self) -> int: + if self.__caps.edp: + return self.__io.get(TSI_DPTX_EDP_LT_SBR)[1] * 0.0002 + + def __get_edp_caps_rates(self) -> List[float]: + if self.__caps.edp: + rates = self.__io.get(TSI_DPTX_EXTRA_DP14_RATES, data_type=c_uint32, data_count=8)[1] + new_rates = [] + for rate in rates: + if rate > 0: + new_rates.append(rate * 0.27) + return new_rates diff --git a/UniTAP/dev/ports/modules/link/dp/link_tx_force_config.py b/UniTAP/dev/ports/modules/link/dp/link_tx_force_config.py new file mode 100644 index 0000000..c26b069 --- /dev/null +++ b/UniTAP/dev/ports/modules/link/dp/link_tx_force_config.py @@ -0,0 +1,86 @@ +from typing import Optional, Union, Type +from UniTAP.libs.lib_tsi import PortIO +from UniTAP.libs.lib_tsi.tsi_private_types import * +from .link_tx_types import * +from .private_link_tx_types import * +from .link_status_common import * + + +class LinkDisplayPortForceConfig: + """ + Class `LinkDisplayPortForceConfig` allows settings link configuration on Source (TX - transmitter) side. + - Set configuration `set`. + - Get current configuration on link `get`. + """ + def __init__(self, port_io: PortIO, caps: DPTXHWCaps): + self.__io = port_io + self.__caps = caps + + def set(self, config: DisplayPortLinkConfig): + """ + Write new configuration on DP link. + + Args: + config (`DisplayPortLinkConfig`). + """ + if isinstance(config, LinkConfig.Force8b10b): + # TODO + pass + elif isinstance(config, LinkConfig.Force128b132b): + self.__force_set_bit_rate(config.bit_rate) + self.__force_set_lane_count(config.lane_count) + self.__force_set_pattern(config.pattern) + else: + raise TypeError("Incorrect LinkConfig Force format!") + + def get(self, config_type: Type[DisplayPortLinkConfig]) -> DisplayPortLinkConfig: + """ + Returns current DP source link configuration. + + Args: + config_type Type[DisplayPortLinkConfig]. + + Returns: + object of `DisplayPortLinkConfig` type + """ + if issubclass(config_type, LinkConfig.Force8b10b): + # TODO + return LinkConfig.Force8b10b() + elif issubclass(config_type, LinkConfig.Force128b132b): + link_status = LinkConfig.Force128b132b() + link_status.lane_count = self.__force_get_lane_count() + link_status.bit_rate = self.__force_get_bit_rate() + link_status.pattern = self.__force_get_pattern() + return link_status + else: + raise TypeError("Incorrect type!") + + def __force_set_bit_rate(self, bit_rate: Optional[Union[float, int]]): + if bit_rate is None: + return + elif (bit_rate == 10.0 and not self.__caps.support_10gbps) or \ + (bit_rate == 13.5 and not self.__caps.support_13_5gbps) or \ + (bit_rate == 20.0 and not self.__caps.support_20gbps): + raise ValueError(f"Following bit rate {bit_rate} is not supported on device.") + self.__io.set(TSI_DP2TX_OUT_BR, DP21_LinkRateRev.get(bit_rate), c_uint32) + + def __force_get_bit_rate(self) -> float: + return DP21_LinkRate.get(self.__io.get(TSI_DP2TX_OUT_BR)[1]) + + def __force_set_lane_count(self, lane_count: Optional[int]): + if lane_count is None: + return + if lane_count not in [1, 2, 4]: + raise ValueError(f"Incorrect lane count number {lane_count}. Must be from list: 1, 2, 4") + self.__io.set(TSI_DP2TX_OUT_LC, lane_count, c_uint32) + + def __force_get_lane_count(self) -> int: + return self.__io.get(TSI_DP2TX_OUT_LC)[1] + + def __force_set_pattern(self, pattern: Optional[DP128b132bLinkPattern]): + if pattern is None: + return + self.__io.set(TSI_DP2TX_OUT_TEST_PATTERN, pattern.value, c_uint32) + + def __force_get_pattern(self) -> DP128b132bLinkPattern: + return DP128b132bLinkPattern(self.__io.get(TSI_DP2TX_OUT_TEST_PATTERN, c_uint32)[1]) diff --git a/UniTAP/dev/ports/modules/link/dp/link_tx_status.py b/UniTAP/dev/ports/modules/link/dp/link_tx_status.py new file mode 100644 index 0000000..db2123d --- /dev/null +++ b/UniTAP/dev/ports/modules/link/dp/link_tx_status.py @@ -0,0 +1,432 @@ +import warnings +from typing import Optional + +from UniTAP.libs.lib_tsi import PortIO +from UniTAP.libs.lib_tsi.tsi_types import * +from .link_tx_types import * +from .private_link_tx_types import * +from .link_status_common import * +from .private_link_status_common import * +from typing import Union +from UniTAP.dev.ports.modules.dpcd import DPCDRegisters + + +class LinkDisplayPortStatusSource: + """ + Class `LinkDisplayPortStatusSource` describes information about DP link status. Contains following info: + - MST stream count `mst_stream_count`. + - Link encoding `link_encoding`. + - Link rate `link_rate`. + - Lane count `lane_count`. + - State of HPD `hpd_asserted`. + - State of framing `enhanced_framing`. + - State of scrambling `scrambling_enabled`. + - State of DSC `dsc_enabled` + - State of FEC `fec_enabled`. + - State of MST `mst_enabled`. + - State of SSC `ssc_enabled`. + - State of ILA `ila`. + - State of EQ ILA `eq_ila`. + - State of CDS ILA `cds_ila`. + - State of LT fail `lt_fail`. + - State of selected lane `lane`. + - State of VCP `vcp`. + - State of selected stream `stream`. + - Send ACT command `send_act`. + """ + def __init__(self, port_io: PortIO, caps: DPTXHWCaps, dpcd: DPCDRegisters): + self.__io = port_io + self.__caps = caps + self.__dpcd = dpcd + + @property + def mst_stream_count(self) -> int: + """ + Returns current mst stream count. + + Returns: + object of int type + """ + result, mst_status = self.__io.get(TSI_DPTX_MST_STATUS_R, c_uint) + return (mst_status >> 8) & 0xFF + + @property + def link_encoding(self) -> DpLinkEncoding: + """ + Returns current link encoding `DpLinkEncoding`. + + Returns: + object of DpLinkEncoding type + """ + result, value = self.__io.get(TSI_DPTX_LINK_MODE_R, c_uint) + + if value == 0: + return DpLinkEncoding.LE_8b10b + else: + return DpLinkEncoding.LE_128b132b + + @property + def link_rate(self) -> float: + """ + Returns current link rate. + + Returns: + object of float type + """ + if self.link_encoding == DpLinkEncoding.LE_128b132b: + if self.__check_custom_bit_rate() != 0: + return self.__check_custom_bit_rate() + else: + return DP21_LinkRate.get(self.__io.get(TSI_DP2TX_LT_RATE_R, c_uint)[1]) + else: + return round(self.__io.get(TSI_DPTX_LINK_RATE_R, c_uint)[1] * 0.27, 2) + + @property + def lane_count(self) -> int: + """ + Returns current lane count. + + Returns: + object of int type + """ + return self.__io.get(TSI_DPTX_LINK_LANE_COUNT_R, c_int)[1] + + @property + def hpd_asserted(self) -> bool: + """ + Returns current state of HPD asserted. + + Returns: + object of bool type + """ + return (self.__io.get(TSI_DPTX_HPD_STATUS_R, c_int)[1] & 1) != 0 + + @property + def available_link_rate(self) -> float: + """ + + Returns available link rate. + + Returns: + object of float type + """ + + if self.link_encoding == DpLinkEncoding.LE_128b132b: + link_rate = self.link_rate * 1000000000 * self.lane_count * ((128 * 383) / (132 * 384)) + else: + link_rate = self.link_rate * 1000000000 * self.lane_count + if self.mst_enabled: + if self.fec_enabled: + link_rate = link_rate * 7690 / 10000 + else: + link_rate = link_rate * 7875 / 10000 + else: + if self.fec_enabled: + link_rate = link_rate * 7813 / 10000 + else: + link_rate = link_rate * 8000 / 10000 + + return link_rate + + def lane(self, lane_number: int) -> LaneStatus: + """ + Returns current status of selected lane `LaneStatus`. + + Args: + lane_number (int) - number of selected lane + + Returns: + object of LaneStatus type + """ + if not (0 <= lane_number <= 3): + raise ValueError(f"Incorrect lane number {lane_number}. Available range: 0-{self.lane_count}") + + status = self.__read_link_status() + voltage_swing = self.__io.get(TSI_DPTX_LINK_VOLTAGE_SWING_R, LinkVoltageSwing)[1] + pre_emphasis = self.__io.get(TSI_DPTX_LINK_PRE_EMPHASIS_R, LinkPreEmphasis)[1] + error_counters = self.__dpcd.read(0x210, 8).data + + lane_status = LaneStatus() + + if lane_number == 0: + lane_status.cr = status.l0_cr + lane_status.sl = status.l0_sl + lane_status.eq = status.l0_eq + lane_status.voltage_swing = voltage_swing.vs_l0 + lane_status.pre_emphasis = pre_emphasis.pe_l0 + lane_status.error_count = error_counters[0] | ((error_counters[1] & 0x7F) << 8) + elif lane_number == 1: + lane_status.cr = status.l1_cr + lane_status.sl = status.l1_sl + lane_status.eq = status.l1_eq + lane_status.voltage_swing = voltage_swing.vs_l1 + lane_status.pre_emphasis = pre_emphasis.pe_l1 + lane_status.error_count = error_counters[2] | ((error_counters[3] & 0x7F) << 8) + elif lane_number == 2: + lane_status.cr = status.l2_cr + lane_status.sl = status.l2_sl + lane_status.eq = status.l2_eq + lane_status.voltage_swing = voltage_swing.vs_l2 + lane_status.pre_emphasis = pre_emphasis.pe_l2 + lane_status.error_count = error_counters[4] | ((error_counters[5] & 0x7F) << 8) + elif lane_number == 3: + lane_status.cr = status.l3_cr + lane_status.sl = status.l3_sl + lane_status.eq = status.l3_eq + lane_status.voltage_swing = voltage_swing.vs_l3 + lane_status.pre_emphasis = pre_emphasis.pe_l3 + lane_status.error_count = error_counters[6] | ((error_counters[7] & 0x7F) << 8) + + if self.link_encoding == DpLinkEncoding.LE_128b132b: + ffe_preset = self.__io.get(TSI_DP2TX_LT_FFE_PRESET_R, c_uint32)[1] + lane_status.ffe_preset = (ffe_preset >> (8 * lane_number)) & 0xFF + else: + lane_status.ffe_preset = 0 + + return lane_status + + @property + def dsc_enabled(self) -> Union[bool, None]: + """ + Returns current state of DSC (enabled or disabled). + None if DSC does not support. + + Returns: + object of bool or None type + """ + if self.__caps.dsc or self.__caps.dsc2: + return (self.__io.get(TSI_DPTX_DSC_STATUS_R)[1] & 1) != 0 + else: + return None + + @property + def mst_enabled(self) -> Union[bool, None]: + """ + Returns current state of MST (enabled or disabled). + None if MST does not support. + + Returns: + object of bool or None type + """ + if self.__caps.mst: + return (self.__io.get(TSI_DPTX_MST_STATUS_R, c_uint32)[1] & 1) != 0 + else: + return None + + @property + def ssc_enabled(self) -> Union[bool, None]: + """ + Returns current state of SSC (enabled or disabled). + None if SSC does not support. + + Returns: + object of bool or None type + """ + res = self.__io.get(TSI_DPTX_DOWNSPREAD_STATUS_R)[1] + if ((res >> 1) & 1) != 0: + return (res & 1) != 0 + else: + return None + + @property + def fec_enabled(self) -> Union[bool, None]: + """ + Returns current state of FEC (enabled or disabled). + None if FEC does not support. + + Returns: + object of bool or None type + """ + if self.__caps.fec or self.__caps.fec2: + return (self.__io.get(TSI_DPTX_FEC_STATUS_R)[1] & 1) != 0 + else: + return None + + @property + def enhanced_framing(self) -> bool: + """ + Returns current state of enhanced framing (enabled or disabled). + + Returns: + object of bool type + """ + return self.__read_lt_features().framing + + @property + def scrambling_enabled(self) -> bool: + """ + Returns current state of scrambling (enabled or disabled). + + Returns: + object of bool type + """ + return bool(self.__caps.scrambler_seed) + + @property + def lttpr_active(self) -> Union[bool, None]: + """ + Returns current state of LTTPR (enabled or disabled). + None if LTTPR does not support. + + Returns: + object of bool or None type + """ + if self.link_encoding == DpLinkEncoding.LE_128b132b: + return (self.__io.get(TSI_DPTX_LTTPR_STATUS_R, c_uint32)[1] & 1) != 0 + else: + return None + + @property + def ila(self) -> bool: + """ + Returns current ILA state. + + Returns: + object of bool type + """ + return bool(self.__read_link_status().ila) + + @property + def eq_ila(self) -> bool: + """ + Returns current EQ ILA state. + + Returns: + object of bool type + """ + return bool(self.__read_link_status().eq_ila) + + @property + def cds_ila(self) -> bool: + """ + Returns current CDS ILA state. + + Returns: + object of bool type + """ + return bool(self.__read_link_status().cds_ila) + + @property + def lt_fail(self) -> bool: + """ + Returns current LT fail state. + + Returns: + object of bool type + """ + return bool(self.__read_link_status().cds_ila) + + def stream(self, stream_index: int) -> StreamStatusDP: + """ + Returns status of selected stream `StreamStatusDP`. + + Args: + stream_index (int) - number of selected number + + Returns: + object of `StreamStatusDP` type + """ + stream_status = StreamStatusDP() + result, msa_info, size = self.__io.get(TSI_DPTX_MSA_INFO_R, DpMsa, 4) + + if stream_index < size: + msa_info_private = MSAInfo(msa_info[stream_index]) + stream_status.video_mode = msa_info_to_video_mode(msa_info_private, self.link_rate * 100000000, + self.link_encoding) + stream_status.mvid = msa_info_private.m_video + stream_status.nvid = msa_info_private.n_video + + result = self.__io.get(TSI_DPTX_CRC_R, DpCrc, self.mst_stream_count) + if result[0] >= TSI_SUCCESS and self.mst_stream_count > 1: + stream_status.crc = [result[1][stream_index].r, result[1][stream_index].g, result[1][stream_index].b] + elif result[0] >= TSI_SUCCESS and self.mst_stream_count == 1: + stream_status.crc = [result[1].r, result[1].g, result[1].b] + + result = self.__io.get(TSI_DPTX_DSC_CRC_R, DpCrc, self.mst_stream_count) + if result[0] >= TSI_SUCCESS and self.mst_stream_count > 1: + stream_status.dsc_crc = [result[1][stream_index].r, result[1][stream_index].g, result[1][stream_index].b] + elif result[0] >= TSI_SUCCESS and self.mst_stream_count == 1: + stream_status.dsc_crc = [result[1].r, result[1].g, result[1].b] + else: + raise ValueError(f"Selected stream {stream_index} is not available. " + f"Available stream number: {size}") + + return stream_status + + def vcp(self, stream_index: int = 0) -> Optional[VCPStatus]: + """ + Returns VCP status of selected stream `VCPStatus`. + + Args: + stream_index (int) - number of selected number + + Returns: + object of `VCPStatus` | None type + """ + if self.__caps.mst: + if not stream_index < self.mst_stream_count: + raise ValueError(f"Selected stream {stream_index} is not available. " + f"Available stream number: {self.mst_stream_count}") + + res = self.__io.get(TSI_DPTX_VCP_TABLE_R, VCPTable, 4)[1] + + if self.mst_enabled: + status = VCPStatus() + + status.port_number = res[stream_index].port_number + status.stream_id = res[stream_index].stream_id + status.req_pbn = res[stream_index].req_pbn + status.alloc_pbn = res[stream_index].alloc_pbn + status.first_slot = res[stream_index].first_slot + status.slot_num = res[stream_index].slot_num + + return status + else: + warnings.warn("MST not enabled. Cannot show VCP status.") + return VCPStatus() + + def send_act(self): + """ + Send ACT command. + """ + self.__io.set(TSI_DPTX_MST_COMMAND_W, 1, c_uint32) + + def __read_lt_features(self) -> LTFeatures: + return self.__io.get(TSI_DPTX_LT_FEATURES, LTFeatures)[1] + + def __read_link_status(self) -> LinkTxStatus: + return self.__io.get(TSI_DPTX_LINK_STATUS_R, LinkTxStatus)[1] + + def __check_custom_bit_rate(self) -> float: + res = self.__io.get(TSI_DP2TX_CUSTOM_RATE_MAP, c_uint32, 3)[1] + for i in range(3): + if res[i] != 0: + return res[i] * 200000 / 1000000000 + return 0 + + def __str__(self) -> str: + lane_status_str = "" + stream_info_str = "" + vcp_table_str = "" + for i in range(self.mst_stream_count): + lane_status_str += f"Lane {i}\n{self.lane(i)}\n" + stream_info_str += f"Stream {i}\n{self.stream(i)}\n" + vcp_table_str += f"#{i}\n{self.vcp(i)}" + + return f"Lane count: {self.lane_count}" \ + f"Bit rate: {self.link_rate:.3} Gbps\n" \ + f"Enhanced framing mode: {self.enhanced_framing}\n" \ + f"MST: {self.mst_enabled}\n" \ + f"DSC: {self.dsc_enabled}\n" \ + f"LTTPR: {self.lttpr_active}\n" \ + f"Link Encoding: {self.link_encoding.name}\n" \ + f"Scrambling: {self.scrambling_enabled}\n" \ + f"SSC: {self.ssc_enabled}\n" \ + f"FEC: {self.fec_enabled}\n" \ + f"ILA: {self.ila}\n" \ + f"EQ_ILA: {self.eq_ila}\n" \ + f"CDS_ILA: {self.cds_ila}\n" \ + f"LT_FAIL: {self.lt_fail}\n" \ + f"Lane status\n{lane_status_str}\n" \ + f"Stream info:\n{stream_info_str}\n" \ + f"VCP status:\n{vcp_table_str}\n" diff --git a/UniTAP/dev/ports/modules/link/dp/link_tx_types.py b/UniTAP/dev/ports/modules/link/dp/link_tx_types.py new file mode 100644 index 0000000..3261867 --- /dev/null +++ b/UniTAP/dev/ports/modules/link/dp/link_tx_types.py @@ -0,0 +1,329 @@ +from typing import TypeVar +from enum import IntEnum +from .link_status_common import DpLinkEncoding + + +class DPLinkPattern(IntEnum): + """ + Class `DPLinkPattern` contains all possible variants of DP link patterns. + Names TrainingPattern1, TrainingPattern2, TrainingPattern3, TrainingPattern4 will be deleted in RC 3.7 + """ + ActiveVideo = 0 + IdlePattern = 1 + TrainingPattern1 = 2 + TrainingPattern2 = 3 + TrainingPattern3 = 4 + TrainingPattern4 = 5 + PRBS7 = 6 + HBR2 = 7 + SER = 8 + ForceVideo = 10 + ForceIdle = 11 + PRBS9 = 12 + PRBS11 = 13 + PRBS15 = 14 + PRBS23 = 15 + PRBS31 = 16 + LinkSquarePattern = 17 + Undefined = 18 + TPS1 = TrainingPattern1 + TSP2 = TrainingPattern2 + TSP3 = TrainingPattern3 + TSP4 = TrainingPattern4 + + +class DP128b132bLinkPattern(IntEnum): + """ + Class `DP128b132bLinkPattern` contains all possible output patterns in DP2.0 force link mode. + """ + ActiveVideo = 0 + IdlePattern = 1 + TPS1 = 2 + TPS2 = 3 + PRBS9 = 4 + PRBS11 = 5 + PRBS15 = 6 + PRBS23 = 7 + PRBS31 = 8 + Custom80bit = 9 + LinkSquarePattern = 10 + PRBS7 = 11 + + +class DPOutLinkMode(IntEnum): + """ + Class `DPOutLinkMode` contains all possible out link modes. + """ + Normal = 0 + Force8b10b = 1 + Force128b132b = 2 + + +class SSCConfig: + """ + Class `SSCConfig` contains information about SSC configuration parameters. + - Set and get amplitude `amplitude`. + - Set and get frequency `frequency`. + - Enable flag. + """ + def __init__(self): + self.enabled = False + self.amplitude = 0.5 + self.frequency = 30000 + + def __eq__(self, other) -> bool: + return self.enabled == other.enabled and \ + self.amplitude == other.amplitude and \ + self.frequency == other.frequency + + def __str__(self) -> str: + return f"Enabled - {self.enabled}\n" \ + f"Amplitude - {self.amplitude}\n" \ + f"Frequency - {self.frequency}\n" + + +class LinkConfig: + """ + Main class contains variants of link configuration: + - DP 1.4 config `DP8b10b`. + - DP 2.1 config `DP128b132b`. + """ + class DP8b10b: + """ + Class `DP8b10b` contains information of possible DP 1.4 configuration. Contains following field: + - lane count + - link bit rate + - MST + - MST stream count + - set enhanced framing mode + - Auto seed + - SSC + - FEC + - Force EDID timings after link training + - Adaptive-Sync auto enable + - Split SDP (if supported) + """ + def __init__(self): + self.lane_count = None + self.bit_rate = None + self.mst = None + self.mst_stream_count = None + self.enhanced_framing_mode = None + self.auto_seed = None + self.ssc = None + self.fec = None + self.force_edid_timings_after_lt = None + self.adaptive_sync_auto_enable = None + self.split_sdp = None + + def __eq__(self, other) -> bool: + return self.lane_count == other.lane_count and \ + self.bit_rate == other.bit_rate and \ + self.mst == other.mst and \ + self.mst_stream_count == other.mst_stream_count and \ + self.enhanced_framing_mode == other.enhanced_framing_mode and \ + self.auto_seed == other.auto_seed and \ + self.ssc == other.ssc and \ + self.fec == other.fec and \ + self.force_edid_timings_after_lt == other.force_edid_timings_after_lt and \ + self.adaptive_sync_auto_enable == other.adaptive_sync_auto_enable and \ + self.split_sdp == other.split_sdp + + def __str__(self) -> str: + return f"Lane Count - {self.lane_count}\n" \ + f"Bit rate - {self.bit_rate:.3}\nMST enabled - {self.mst}\n" \ + f"MST stream count - {self.mst_stream_count}\n" \ + f"Enhanced framing mode - {bool(self.enhanced_framing_mode)}\n" \ + f"Auto Seed - {bool(self.auto_seed)}\nSSC info:\n{self.ssc.__str__()}\nFEC: {self.fec}\n" \ + f"Force EDID Timings after LT: {self.force_edid_timings_after_lt}\n" \ + f"Adaptive Sync auto enable: {self.adaptive_sync_auto_enable}\n" \ + f"Split SDP: {self.split_sdp}\n" + + class DP128b132b: + """ + Class `DP128b132b` contains information of possible DP 2.1 configuration. Contains following field: + - lane count + - link bit rate + - Force DP 128/132 + - Maximum link bandwidth supported + - Set enhanced framing mode + - Auto seed + - SSC + - LLTPR + - Try (enable/disable) DP 128/132 + - Old DP2 link training + - Force EDID timings after link training + - Adaptive-Sync auto enable + - Split SDP (if supported) + - Force eDP rates + """ + def __init__(self): + self.lane_count = None + self.bit_rate = None + self.force_dp_128_132 = None + self.enhanced_framing_mode = None + self.max_link_bandwidth_supported = None + self.old_dp2_lt = None + self.lttpr = None + self.try_dp_128_132 = None + self.auto_seed = None + self.ssc = None + self.force_edid_timings_after_lt = None + self.adaptive_sync_auto_enable = None + self.mst = None + self.mst_stream_count = None + self.crc_16 = None + + def __eq__(self, other) -> bool: + return self.lane_count == other.lane_count and \ + self.bit_rate == other.bit_rate and \ + self.force_dp_128_132 == other.force_dp_128_132 and \ + self.max_link_bandwidth_supported == other.max_link_bandwidth_supported and \ + self.old_dp2_lt == other.old_dp2_lt and \ + self.lttpr == other.lttpr and \ + self.try_dp_128_132 == other.try_dp_128_132 and \ + self.enhanced_framing_mode == other.enhanced_framing_mode and \ + self.auto_seed == other.auto_seed and \ + self.ssc == other.ssc and \ + self.force_edid_timings_after_lt == other.force_edid_timings_after_lt and \ + self.adaptive_sync_auto_enable == other.adaptive_sync_auto_enable and \ + self.mst == other.mst and \ + self.mst_stream_count == other.mst_stream_count and \ + self.crc_16 == other.crc_16 + + def __str__(self) -> str: + return f"Lane Count - {self.lane_count}\n" \ + f"Bit rate - {self.bit_rate:.3}\nMST enabled - {self.mst}\n" \ + f"MST stream count - {self.mst_stream_count}\n" \ + f"Force DP 128b/132b enabled - {self.force_dp_128_132}\n" \ + f"Max link bandwidth supported - {self.max_link_bandwidth_supported}\n" \ + f"Enhanced framing mode - {self.enhanced_framing_mode}\n" \ + f"Try DP 128b/132b enabled - {self.try_dp_128_132}\n"\ + f"Old DP 2.1 Link training enabled - {self.old_dp2_lt}\n" \ + f"LTTPR enabled - {self.lttpr}\n" \ + f"Auto Seed - {self.auto_seed}\nSSC info - {self.ssc.__str__()}\n" \ + f"Force EDID Timings after LT: {self.force_edid_timings_after_lt}\n" \ + f"Adaptive Sync auto enable: {self.adaptive_sync_auto_enable}\n" \ + f"Split SDP: {self.crc_16}\n" + + class Force8b10b: + """ + + """ + def __init__(self): + self.lane_count = None + self.bit_rate = None + self.pattern = None + + def __str__(self) -> str: + return f"Lane Count - {self.lane_count}\n" \ + f"Bit rate - {self.bit_rate:.3}\n" \ + f"Link Pattern - {self.pattern.name}" + + class Force128b132b: + """ + + """ + def __init__(self): + self.lane_count = None + self.bit_rate = None + self.pattern = None + + def __str__(self) -> str: + return f"Lane Count - {self.lane_count}\n" \ + f"Bit rate - {self.bit_rate:.3}\n" \ + f"Link Pattern - {self.pattern.name}" + + class eDP: + """ + + """ + def __init__(self): + self.lane_count = None + self.force_edp = None + self.eDp_cur_rate = None + self.eDp_supported_rates = None + self.eDp_aux_preamble = None + + def __str__(self) -> str: + return f"Lane Count - {self.lane_count}\n" \ + f"Force eDP: {bool(self.force_edp)}\n" \ + f"eDP rate - {self.eDp_cur_rate}\n" \ + f"eDP supported rates - {self.eDp_supported_rates}\n" \ + f"eDP AUX preamble - {self.eDp_aux_preamble}" + + def __eq__(self, other) -> bool: + return self.lane_count == other.lane_count and \ + self.force_edp == other.force_edp and \ + self.eDp_cur_rate == other.eDp_cur_rate and \ + self.eDp_aux_preamble == other.eDp_aux_preamble + + +class LinkStatus: + """ + Main class describes current link status. Contains following field: + - Lane count + - BIt rate + - State of enhanced framing + - State of MST mode + - State of DSC mode + - LTTPR + - Link encoding `DpLinkEncoding` + - State of scrambling + - State of SSC + - State of FEC + - State of eDP + """ + def __init__(self): + self.lane_count = 0 + self.bit_rate = 0 + self.enhanced_framing = False + self.mst_enabled = False + self.dsc_enabled = False + self.lttpr = False + self.link_encoding = DpLinkEncoding.LE_NONE + self.scrambling_enabled = False + self.ssc_enabled = False + self.fec_enabled = False + self.force_edp_enabled = False + + def __eq__(self, other) -> bool: + return self.lane_count == other.lane_count and \ + self.bit_rate == other.bit_rate and \ + self.enhanced_framing == other.enhanced_framing and \ + self.mst_enabled == other.mst_enabled and \ + self.dsc_enabled == other.dsc_enabled and \ + self.lttpr == other.lttpr and \ + self.link_encoding == other.link_encoding and \ + self.scrambling_enabled == other.scrambling_enabled and \ + self.ssc_enabled == other.ssc_enabled and \ + self.fec_enabled == other.fec_enabled and \ + self.force_edp_enabled == other.force_edp_enabled + + def __str__(self) -> str: + return f"Lane Count - {self.lane_count}\n" \ + f"Bit rate - {self.bit_rate:.3}\n" \ + f"Enhanced framing - {self.enhanced_framing}\n" \ + f"MST enabled - {self.mst_enabled}\n" \ + f"DSC enabled - {self.dsc_enabled}\n" \ + f"LTTPR - {self.lttpr}\n" \ + f"Link Encoding - {self.link_encoding.name}\n" \ + f"Scrambling enabled - {self.link_encoding.name}\n" \ + f"SSC enabled - {self.ssc_enabled}\n" \ + f"FEC enabled - {self.fec_enabled}\n" \ + f"Force eDP enabled: {self.force_edp_enabled}" + + # class LinkOverrides: + # + # def __init__(self): + # self.override_voltage = 0 + # self.override_pre_emp = 0 + # self.override_ffe_presets = [0] * 4 + + +DisplayPortLinkConfig = TypeVar("DisplayPortLinkConfig", + LinkConfig.DP8b10b, + LinkConfig.DP128b132b, + LinkConfig.eDP, + LinkConfig.Force8b10b, + LinkConfig.Force128b132b) diff --git a/UniTAP/dev/ports/modules/link/dp/private_link_rx_types.py b/UniTAP/dev/ports/modules/link/dp/private_link_rx_types.py new file mode 100644 index 0000000..e39fbad --- /dev/null +++ b/UniTAP/dev/ports/modules/link/dp/private_link_rx_types.py @@ -0,0 +1,122 @@ +from ctypes import Structure, c_uint32, c_uint8, c_uint16 +from enum import IntEnum +from .link_rx_types import CableCapabilitiesEnum + + +class DPRXHWCaps(Structure): + _fields_ = [ + ('mst', c_uint32, 1), + ('hdcp_1_x', c_uint32, 1), + ('hdcp_2_x', c_uint32, 1), + ('fec', c_uint32, 1), + ('dsc', c_uint32, 1), + ('alpm', c_uint32, 1), + ('lanecount3', c_uint32, 1), + ('edp', c_uint32, 1), + ('mst_stream_count', c_uint32, 4), + ('pr', c_uint32, 1), + ('psr', c_uint32, 1), + ('', c_uint32, 1), + ('display_id_capability', c_uint32, 1), + ('max_link_rate', c_uint32, 8), + ('force_link_congiguration', c_uint32, 1), + ('cirdan', c_uint32, 1), + ('dp_power', c_uint32, 1), + ('aux_swing', c_uint32, 1), + ('dp2_custom_rates', c_uint32, 1), + ('custom_rate_ex', c_uint32, 1), + ('fec2', c_uint32, 1), + ('dsc2', c_uint32, 1), + ('dp2_support_rates', c_uint32, 8), + ('', c_uint32, 24), + ('link_pat_log', c_uint32, 1), + ('vbid_hw_log', c_uint32, 1), + ('msa_hw_log', c_uint32, 1), + ('aux_bw_log', c_uint32, 1), + ('', c_uint32, 28), + ('scrambler_seed', c_uint32, 1), + ('sdp_err_counters', c_uint32, 1), + ('sink_cnt_config', c_uint32, 1), + ('', c_uint32, 29) + ] + + +class DPRXLinkControl(Structure): + _fields_ = [ + ('tps4_type', c_uint32, 1), + ('alpm_aux_wake', c_uint32, 1), + ('old_dp2_lt', c_uint32, 1), + ('edp_aux_preamble', c_uint32, 1) + ] + + def value(self) -> int: + return self.tps4_type << 0 | self.alpm_aux_wake << 1 | self.old_dp2_lt << 2 | self.edp_aux_preamble << 3 + + +class CableTypeEnum(IntEnum): + Unknown = 0 + Passive = 1 + LRD = 2 + Retimer = 3 + + +class PortCableAttributesEnum(IntEnum): + DP_CABLE_ATTR_UHBR_10_20_CAPABILITY_POS = 0 + DP_CABLE_ATTR_UHBR_10_20_CAPABILITY_MASK = 0x03 << DP_CABLE_ATTR_UHBR_10_20_CAPABILITY_POS + DP_CABLE_ATTR_UHBR_10_20_NOT_CAPABLE = 0x00 << DP_CABLE_ATTR_UHBR_10_20_CAPABILITY_POS + DP_CABLE_ATTR_UHBR_10_CAPABLE = 0x01 << DP_CABLE_ATTR_UHBR_10_20_CAPABILITY_POS + DP_CABLE_ATTR_UHBR_10_20_CAPABLE = 0x02 << DP_CABLE_ATTR_UHBR_10_20_CAPABILITY_POS + + DP_CABLE_ATTR_UHBR_13_5_CAPABILITY_POS = 2 + DP_CABLE_ATTR_UHBR_13_5_CAPABLE = 0x01 << DP_CABLE_ATTR_UHBR_13_5_CAPABILITY_POS + + DP_CABLE_ATTR_CABLE_TYPE_POS = 3 + DP_CABLE_ATTR_CABLE_TYPE_MASK = 0x7 << DP_CABLE_ATTR_CABLE_TYPE_POS + + +class PortCableAttributesStruct(Structure): + _fields_ = [ + ('data', c_uint8) + ] + + def isUHBR20Supported(self) -> bool: + return (self.data & PortCableAttributesEnum.DP_CABLE_ATTR_UHBR_10_20_CAPABILITY_MASK) == PortCableAttributesEnum.DP_CABLE_ATTR_UHBR_10_20_CAPABLE + + def isUHBR13_5Supported(self) -> bool: + return (self.data & PortCableAttributesEnum.DP_CABLE_ATTR_UHBR_13_5_CAPABLE) != 0 + + def isUHBR10Supported(self) -> bool: + return ((self.data & PortCableAttributesEnum.DP_CABLE_ATTR_UHBR_10_20_CAPABILITY_MASK) == PortCableAttributesEnum.DP_CABLE_ATTR_UHBR_10_CAPABLE) or self.isUHBR20Supported() + + def get_cable_caps(self) -> CableCapabilitiesEnum: + if self.isUHBR20Supported(): + return CableCapabilitiesEnum.DP80 + elif self.isUHBR13_5Supported(): + return CableCapabilitiesEnum.DP54 + elif self.isUHBR10Supported(): + return CableCapabilitiesEnum.DP40 + else: + return CableCapabilitiesEnum.Unknown + + def get_cable_type(self) -> CableTypeEnum: + return CableTypeEnum(self.data & PortCableAttributesEnum.DP_CABLE_ATTR_CABLE_TYPE_MASK >> PortCableAttributesEnum.DP_CABLE_ATTR_CABLE_TYPE_POS) + + +class CableAttributesStruct(Structure): + _fields_ = [ + ('currentPort', c_uint8), + ('anotherPort', c_uint8), + ('reserved', c_uint16) + ] + + +class RoutedLTStatusPrivate(Structure): + _fields_ = [ + ('enabled', c_uint32, 1), + ('dp2OldLt', c_uint32, 1), + ('state', c_uint32, 2), + ('success', c_uint32, 1), + ('', c_uint32, 11), + ('step', c_uint32, 9), + ('', c_uint32, 7) + ] diff --git a/UniTAP/dev/ports/modules/link/dp/private_link_status_common.py b/UniTAP/dev/ports/modules/link/dp/private_link_status_common.py new file mode 100644 index 0000000..21c42c5 --- /dev/null +++ b/UniTAP/dev/ports/modules/link/dp/private_link_status_common.py @@ -0,0 +1,157 @@ +from ctypes import * +from .dp_utils import * +from .link_status_common import DpLinkEncoding +from UniTAP.common.video_mode import VideoMode + + +class DpMsa(Structure): + class PixelAttributes(Structure): + _fields_ = [ + ("n_video_or_freq_low", c_uint32, 24), + ("", c_uint32, 8), + ("m_video_or_freq_high", c_uint32, 24), + ("", c_uint32, 8), + ] + + _fields_ = [ + ('attributes', PixelAttributes), + ('h_total', c_uint16), + ('v_total', c_uint16), + ('h_active', c_uint16), + ('v_active', c_uint16), + ('h_sync', c_int16), + ('v_sync', c_int16), + ('h_sync_start', c_uint16), + ('v_sync_start', c_uint16), + ('misc0', c_uint8), + ('misc1', c_uint8), + ('vbid', c_uint8), + ('port_number', c_uint8), + + ('vsc_sdp_db16', c_uint8), + ('vsc_sdp_db17', c_uint8), + ('vsc_sdp_db18', c_uint8), + ('', c_uint8), + + ('', c_uint32 * 8) + ] + + +class DpCrc(Structure): + _fields_ = [ + ('r', c_uint16), + ('g', c_uint16), + ('b', c_uint16), + ('', c_uint16) + ] + + +class VCPTable(Structure): + _fields_ = [ + ('port_number', c_int), + ('stream_id', c_int), + ('req_pbn', c_int), + ('alloc_pbn', c_int), + ('first_slot', c_int), + ('slot_num', c_int), + ('reserved1', c_int), + ('reserved2', c_int), + ] + + +class LinkVoltageSwing(Structure): + _fields_ = [ + ('vs_l0', c_uint32, 2), + ('', c_uint32, 6), + ('vs_l1', c_uint32, 2), + ('', c_uint32, 6), + ('vs_l2', c_uint32, 2), + ('', c_uint32, 1), + ('vs_l3', c_uint32, 2), + ('', c_uint32, 6), + ] + + +class LinkPreEmphasis(Structure): + _fields_ = [ + ('pe_l0', c_uint32, 2), + ('', c_uint32, 6), + ('pe_l1', c_uint32, 2), + ('', c_uint32, 6), + ('pe_l2', c_uint32, 2), + ('', c_uint32, 1), + ('pe_l3', c_uint32, 2), + ('', c_uint32, 6), + ] + + +class MSAInfo: + + def __init__(self, dp_msa: DpMsa): + self.n_video = dp_msa.attributes.n_video_or_freq_low + self.m_video = dp_msa.attributes.m_video_or_freq_high + self.video_frequency = (dp_msa.attributes.m_video_or_freq_high << 24) | dp_msa.attributes.n_video_or_freq_low + self.h_total = dp_msa.h_total + self.v_total = dp_msa.v_total + self.h_active = dp_msa.h_active + self.v_active = dp_msa.v_active + self.h_sync = dp_msa.h_sync + self.v_sync = dp_msa.v_sync + self.h_sync_start = dp_msa.h_sync_start + self.v_sync_start = dp_msa.v_sync_start + self.misc0 = dp_msa.misc0 + self.misc1 = dp_msa.misc1 + self.vbid = dp_msa.vbid + self.port_number = dp_msa.port_number + + self.vsc_sdp_db16 = dp_msa.vsc_sdp_db16 + self.vsc_sdp_db17 = dp_msa.vsc_sdp_db17 + self.vsc_sdp_db18 = dp_msa.vsc_sdp_db18 + + def is_eq(self, other, enc128b132b: bool) -> bool: + if enc128b132b: + if self.video_frequency != other.video_frequency: + return False + else: + # m_video changes frequently + if self.n_video != other.n_video: + return False + + return self.h_total == other.h_total and self.v_total == other.v_total and self.h_active == other.h_active and \ + self.v_active == other.v_active and self.h_sync == other.h_sync and self.v_sync == other.v_sync and \ + self.h_sync_start == other.h_sync_start and self.v_sync_start == other.v_sync_start and \ + self.misc0 == other.misc0 and self.misc1 == other.misc1 and self.vbid == other.vbid and \ + self.vsc_sdp_db16 == self.vsc_sdp_db16 and self.vsc_sdp_db17 == self.vsc_sdp_db17 and \ + self.vsc_sdp_db18 == self.vsc_sdp_db18 + + +def msa_info_to_video_mode(msa_info: MSAInfo, f_ls_clock: float, link_encoding: DpLinkEncoding): + video_mode = VideoMode() + + video_mode.timing.hactive = msa_info.h_active + video_mode.timing.vactive = msa_info.v_active + video_mode.timing.htotal = msa_info.h_total + video_mode.timing.vtotal = msa_info.v_total + video_mode.timing.hstart = msa_info.h_sync_start + video_mode.timing.vstart = msa_info.v_sync_start + video_mode.timing.hswidth = msa_info.h_sync + video_mode.timing.vswidth = msa_info.v_sync + + video_mode.color_info.colorimetry = get_vm_colorimetry(msa_info.vsc_sdp_db16) + video_mode.color_info.color_format = get_vm_color_format(msa_info.vsc_sdp_db16) + video_mode.color_info.bpc = get_vm_bpc(msa_info.vsc_sdp_db16, msa_info.vsc_sdp_db17) + video_mode.color_info.dynamic_range = get_vm_dynamic_range(msa_info.vsc_sdp_db17) + + frame_rate = 0 + if (msa_info.v_total * msa_info.h_total) > 0: + if link_encoding == DpLinkEncoding.LE_8b10b and msa_info.n_video > 0: + f_strm_clock = f_ls_clock * (msa_info.m_video / msa_info.n_video) + frame_rate = 1000 * f_strm_clock / (msa_info.v_total * msa_info.h_total) + elif link_encoding == DpLinkEncoding.LE_128b132b: + frame_rate = 1000 * msa_info.video_frequency / (msa_info.v_total * msa_info.h_total) + else: + pass + + video_mode.timing.frame_rate = round(frame_rate) + + return video_mode diff --git a/UniTAP/dev/ports/modules/link/dp/private_link_tx_types.py b/UniTAP/dev/ports/modules/link/dp/private_link_tx_types.py new file mode 100644 index 0000000..65b81e4 --- /dev/null +++ b/UniTAP/dev/ports/modules/link/dp/private_link_tx_types.py @@ -0,0 +1,107 @@ +from ctypes import Structure, c_uint32 + + +class DPTXHWCaps(Structure): + _fields_ = [ + ('mst', c_uint32, 1), + ('hdcp_1_x', c_uint32, 1), + ('hdcp_2_x', c_uint32, 1), + ('fec', c_uint32, 1), + ('dsc', c_uint32, 1), + ('alpm', c_uint32, 1), + ('lane_3', c_uint32, 1), + ('edp', c_uint32, 1), + ('mst_stream_count', c_uint32, 4), + ('', c_uint32, 4), + ('max_link_rate', c_uint32, 8), + ('', c_uint32, 1), + ('dp2_custom_rates', c_uint32, 1), + ('custom_rates_ext', c_uint32, 1), + ('adaptive_sync', c_uint32, 1), + ('edid_parser', c_uint32, 1), + ('', c_uint32, 1), + ('fec2', c_uint32, 1), + ('dsc2', c_uint32, 1), + ('support_10gbps', c_uint32, 1), + ('support_20gbps', c_uint32, 1), + ('support_13_5gbps', c_uint32, 1), + ('', c_uint32, 29), + ('link_pat_log', c_uint32, 1), + ('vbid_hw_log', c_uint32, 1), + ('msa_hw_log', c_uint32, 1), + ('aux_bw_log', c_uint32, 1), + ('', c_uint32, 28), + ('scrambler_seed', c_uint32, 1), + ('dp2_sdp_crc', c_uint32, 1), + ('sdp_split', c_uint32, 1), + ('dp2_sdp_split', c_uint32, 1), + ('', c_uint32, 28), + ('supported_dp2_custom_rates', c_uint32 * 32), + ] + + +class LTFeatures(Structure): + _fields_ = [ + ('skew', c_uint32, 1), + ('', c_uint32, 1), + ('framing', c_uint32, 1), + ('try_3_lane', c_uint32, 1), + ('try_edp', c_uint32, 1), + ('force_edp', c_uint32, 1), + ('try_dp2', c_uint32, 1), + ('force_dp2', c_uint32, 1), + ('old_dp2_lt', c_uint32, 1), + ('auto_seed', c_uint32, 1), + ('max_link_bw_policy', c_uint32, 1), + ('edp_aux_preamble', c_uint32, 1) + ] + + def value(self) -> int: + return self.framing << 2 | self.try_edp << 4 | self.force_edp << 5 | self.try_dp2 << 6 | self.force_dp2 << 7 |\ + self.old_dp2_lt << 8 | self.auto_seed << 9 | self.max_link_bw_policy << 10 | self.edp_aux_preamble << 11 + + +class LinkTxStatus(Structure): + _fields_ = [ + ('l0_cr', c_uint32, 1), + ('l0_eq', c_uint32, 1), + ('l0_sl', c_uint32, 1), + ('', c_uint32, 1), + ('l1_cr', c_uint32, 1), + ('l1_eq', c_uint32, 1), + ('l1_sl', c_uint32, 1), + ('', c_uint32, 1), + ('l2_cr', c_uint32, 1), + ('l2_eq', c_uint32, 1), + ('l2_sl', c_uint32, 1), + ('', c_uint32, 1), + ('l3_cr', c_uint32, 1), + ('l3_eq', c_uint32, 1), + ('l3_sl', c_uint32, 1), + ('', c_uint32, 13), + ('lt_fail', c_uint32, 1), + ('cds_ila', c_uint32, 1), + ('eq_ila', c_uint32, 1), + ('ila', c_uint32, 1) + ] + + +class PostLtFeatures(Structure): + _fields_ = [ + ('force_edid_timings', c_uint32, 1), + ('as_auto_enable', c_uint32, 1) + ] + + def value(self) -> int: + return self.force_edid_timings | self.as_auto_enable << 1 + + +class SdpControl(Structure): + _fields_ = [ + ('dp2_crc_sdp', c_uint32, 1), + ('dp_split_sdp', c_uint32, 1), + ('dp2_split_sdp', c_uint32, 1) + ] + + def value(self) -> int: + return self.dp2_crc_sdp | self.dp_split_sdp << 1 diff --git a/UniTAP/dev/ports/modules/link/hdmi/__init__.py b/UniTAP/dev/ports/modules/link/hdmi/__init__.py new file mode 100644 index 0000000..e873a98 --- /dev/null +++ b/UniTAP/dev/ports/modules/link/hdmi/__init__.py @@ -0,0 +1 @@ +from .types import FrlMode, FrlCaps, ClockRate, HdmiModeTx, HdmiModeRx, LinkMode diff --git a/UniTAP/dev/ports/modules/link/hdmi/arc_rx.py b/UniTAP/dev/ports/modules/link/hdmi/arc_rx.py new file mode 100644 index 0000000..855a025 --- /dev/null +++ b/UniTAP/dev/ports/modules/link/hdmi/arc_rx.py @@ -0,0 +1,134 @@ +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from enum import IntEnum +from UniTAP.libs.lib_tsi.tsi_types import TSI_ARC_CONTROL_W, TSI_HDRX_ARC_STATUS_R +from ctypes import c_uint32 + + +class ArcLoopbackAudioSource(IntEnum): + """ + Class `ArcLoopbackAudioSource` contains all possible variants of ARC loopback audio source type. + """ + Unknown = -1 + TPG = 0 + HDMI = 1 + DVI = 2 + DP = 3 + SPDIF = 4 + + +class ArcRx: + """ + Class `ArcRx` contains information about caps and states of Audio return channel. + - Support ARC `supported`. + - Loopback support TGP `loopback_supported_tpg`. + - Loopback support HDMI `loopback_supported_hdmi`. + - Loopback support DVI `loopback_supported_dvi`. + - Loopback support DP `loopback_supported_dp`. + - Loopback support SPDIF `loopback_supported_spdif`. + - Current state (enabled/disabled) - `enabled`. + - Control of ARC Source `arc_source`. + - Control of single mode `single_mode`. + + """ + def __init__(self, port_io: PortIO): + self.__io = port_io + self.__supported = False + self.__loopback_supported_tpg = False + self.__loopback_supported_hdmi = False + self.__loopback_supported_dvi = False + self.__loopback_supported_dp = False + self.__loopback_supported_spdif = False + self.__enabled = False + self.__arc_source = ArcLoopbackAudioSource.HDMI + self.__arc_source_value = 2 + self.__single_mode = True + + def __read_arc_status(self) -> int: + return self.__io.get(TSI_HDRX_ARC_STATUS_R, c_uint32)[1] + + def __write_arc(self, value: int): + self.__io.set(TSI_ARC_CONTROL_W, value, c_uint32) + + @property + def supported(self) -> bool: + self.__supported = (self.__read_arc_status() & 0x1) != 0 + return self.__supported + + @property + def loopback_supported_tpg(self) -> bool: + self.__loopback_supported_tpg = ((self.__read_arc_status() >> 1) & 0x1) != 0 + return self.__loopback_supported_tpg + + @property + def loopback_supported_hdmi(self) -> bool: + self.__loopback_supported_hdmi = ((self.__read_arc_status() >> 2) & 0x1) != 0 + return self.__loopback_supported_hdmi + + @property + def loopback_supported_dvi(self) -> bool: + self.__loopback_supported_dvi = ((self.__read_arc_status() >> 8) & 0x1) != 0 + return self.__loopback_supported_dvi + + @property + def loopback_supported_dp(self) -> bool: + self.__loopback_supported_dp = ((self.__read_arc_status() >> 9) & 0x1) != 0 + return self.__loopback_supported_dp + + @property + def loopback_supported_spdif(self) -> bool: + self.__loopback_supported_spdif = ((self.__read_arc_status() >> 10) & 0x1) != 0 + return self.__loopback_supported_spdif + + @property + def enabled(self) -> bool: + self.__enabled = ((self.__read_arc_status() >> 31) & 0x1) != 0 + return self.__enabled + + @property + def arc_source(self) -> ArcLoopbackAudioSource: + return self.__arc_source + + @property + def single_mode(self) -> bool: + return self.__single_mode + + @single_mode.setter + def single_mode(self, single_mode: bool): + value = self.__arc_source_value | ((1 << 16) if single_mode else 0) + self.__write_arc(value) + self.__single_mode = single_mode + + @arc_source.setter + def arc_source(self, arc_source: ArcLoopbackAudioSource): + + self.__arc_source_value = 0 + + if arc_source == ArcLoopbackAudioSource.HDMI: + self.__arc_source_value |= 2 + self.__arc_source_value |= (0 << 8) + elif arc_source == ArcLoopbackAudioSource.TPG: + self.__arc_source_value |= 1 + elif arc_source == ArcLoopbackAudioSource.DVI: + self.__arc_source_value |= 2 + self.__arc_source_value |= (1 << 8) + elif arc_source == ArcLoopbackAudioSource.DP: + self.__arc_source_value |= 2 + self.__arc_source_value |= (2 << 8) + elif arc_source == ArcLoopbackAudioSource.SPDIF: + self.__arc_source_value |= 2 + self.__arc_source_value |= (3 << 8) + + value = self.__arc_source_value | ((1 << 16) if self.__single_mode else 0) + self.__write_arc(value) + self.__arc_source = arc_source + + def __str__(self): + return f"Audio return channel supported: {self.supported}\n" \ + f"Loopback supported TPG: {self.loopback_supported_tpg}\n" \ + f"Loopback supported HDMI: {self.loopback_supported_hdmi}\n" \ + f"Loopback supported DVI: {self.loopback_supported_dvi}\n" \ + f"Loopback supported DP: {self.loopback_supported_dp}\n" \ + f"Loopback supported SPDIF: {self.loopback_supported_spdif}\n" \ + f"Enabled: {self.enabled}\n" \ + f"ARC Source: {self.arc_source.name}\n" \ + f"Single mode: {self.single_mode}\n" diff --git a/UniTAP/dev/ports/modules/link/hdmi/capabilities.py b/UniTAP/dev/ports/modules/link/hdmi/capabilities.py new file mode 100644 index 0000000..4ab88fe --- /dev/null +++ b/UniTAP/dev/ports/modules/link/hdmi/capabilities.py @@ -0,0 +1,45 @@ +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from ctypes import c_uint32 + + +class HdmiCapabilities: + + def __init__(self, ci_control: int, port_io: PortIO): + self.__io = port_io + + caps = self.__io.get(ci_control, c_uint32)[1] + self.__cap_tmds = caps & 0x1 + self.__cap_frl = (caps >> 1) & 0x1 + self.__cap_arc = (caps >> 2) & 0x1 + self.__cap_hdcp = (caps >> 3) & 0x1 + self.__cap_behavior_14 = (caps >> 4) & 0x1 + self.__cap_behavior_20 = (caps >> 5) & 0x1 + self.__cap_behavior_21 = (caps >> 6) & 0x1 + + @property + def support_tmds(self) -> bool: + return self.__cap_tmds + + @property + def support_frl(self) -> bool: + return self.__cap_tmds + + @property + def support_arc(self) -> bool: + return self.__cap_tmds + + @property + def support_hdcp(self) -> bool: + return self.__cap_tmds + + @property + def support_hdmi_mode_14(self) -> bool: + return self.__cap_tmds + + @property + def support_hdmi_mode_20(self) -> bool: + return self.__cap_tmds + + @property + def support_hdmi_mode_21(self) -> bool: + return self.__cap_tmds diff --git a/UniTAP/dev/ports/modules/link/hdmi/frl_caps_rx.py b/UniTAP/dev/ports/modules/link/hdmi/frl_caps_rx.py new file mode 100644 index 0000000..c2c549a --- /dev/null +++ b/UniTAP/dev/ports/modules/link/hdmi/frl_caps_rx.py @@ -0,0 +1,139 @@ +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from .types import FrlMode, LtpLanesPattern, FrlCaps, LtpPattern, _update_frl_values, _update_ltp_pattern_values +from UniTAP.libs.lib_tsi.tsi_types import TSI_HDRX_FRL_CAPABILITY, TSI_HDRX_FRL_PATTERN, TSI_HDRX_LINK_STATUS_R +from ctypes import c_uint32 + + +class FrlControlRx: + """ + Class `FrlControlRx` contains information about FRL on Sink (RX - receiver) side. + allows working with: + - Set and get FRL mode `frl_mode`. + - Set and get FRL capabilities `frl_caps`. + - Set and get LTP requested `ltp_request` and additional `ltp_additional`. + """ + def __init__(self, port_io: PortIO): + self.__io = port_io + self.__frl_caps = FrlCaps() + self.__ltp_request = LtpLanesPattern() + self.__ltp_additional = LtpLanesPattern() + + def __read_caps(self) -> int: + return self.__io.get(TSI_HDRX_FRL_CAPABILITY, c_uint32)[1] + + def __write_caps(self, value: int): + self.__io.set(TSI_HDRX_FRL_CAPABILITY, value, c_uint32) + + def __read_frl_pattern(self) -> int: + return self.__io.get(TSI_HDRX_FRL_PATTERN, c_uint32)[1] + + def __write_frl_pattern(self, value: int): + self.__io.set(TSI_HDRX_FRL_PATTERN, value, c_uint32) + + @property + def frl_mode(self) -> FrlMode: + """ + Returns current FRL mode. + + Returns: + object of `FrlMode` type + """ + return FrlMode(self.__read_caps() & 0xF) + + @frl_mode.setter + def frl_mode(self, frl_mode: FrlMode): + """ + Set new FRL mode. + + Args: + frl_mode (`FrlMode`) + """ + new_value = frl_mode.value + new_value |= self.__read_caps() & ~0xF + self.__write_caps(new_value) + + @property + def frl_caps(self) -> FrlCaps: + """ + Returns current FRL capabilities. + + Returns: + object of `FrlCaps` type + """ + _update_frl_values(self.__frl_caps, self.__read_caps()) + return self.__frl_caps + + @frl_caps.setter + def frl_caps(self, frl_caps: FrlCaps): + """ + Set new FRL capabilities. + + Args: + frl_caps (`FrlCaps`) + """ + new_value = frl_caps.value() + new_value |= self.__read_caps() & ~0xF + self.__write_caps(new_value) + self.__frl_caps = frl_caps + + @property + def ltp_request(self) -> LtpLanesPattern: + """ + Returns current LTP lanes pattern. Current Pattern that is being checking during Link Training. + Each lane can request different pattern. + + Returns: + object of `LtpLanesPattern` type + """ + _update_ltp_pattern_values(self.__ltp_request, self.__read_frl_pattern()) + return self.__ltp_request + + @ltp_request.setter + def ltp_request(self, ltp_request: LtpLanesPattern): + """ + Set new LTP lanes pattern. + + Args: + ltp_request (`LtpLanesPattern`) + """ + new_value = ltp_request.value() + new_value |= self.__read_frl_pattern() + self.__write_frl_pattern(new_value) + self.__ltp_request = ltp_request + + @property + def ltp_additional(self) -> LtpLanesPattern: + """ + Returns current additional LTP lanes pattern. Current Pattern that is being checking during Link Training. + Each lane can request different pattern. + + Returns: + object of `LtpLanesPattern` type + """ + _update_ltp_pattern_values(self.__ltp_additional, self.__read_frl_pattern() >> 0xFF) + return self.__ltp_additional + + @ltp_additional.setter + def ltp_additional(self, ltp_request: LtpLanesPattern): + """ + Set new additional LTP lanes pattern. + + Args: + ltp_request (`LtpLanesPattern`) + """ + new_value = ltp_request.value() << 0xFF + new_value |= self.__read_frl_pattern() + self.__write_frl_pattern(new_value) + self.__ltp_additional = ltp_request + + def re_train(self): + """ + DO re train. + """ + self.__io.set(TSI_HDRX_LINK_STATUS_R, 0x1c0000, c_uint32) + + def __str__(self): + return f"FRL Mode: {self.frl_mode.name}\n" \ + f"FRL Caps:\n{self.frl_caps.__str__()}" \ + f"LTP Lanes Pattern:\n{self.ltp_request.__str__()}" \ + f"LTP Lanes Pattern Additional:\n{self.ltp_additional.__str__()}\n" diff --git a/UniTAP/dev/ports/modules/link/hdmi/frl_control_tx.py b/UniTAP/dev/ports/modules/link/hdmi/frl_control_tx.py new file mode 100644 index 0000000..1c1cb3c --- /dev/null +++ b/UniTAP/dev/ports/modules/link/hdmi/frl_control_tx.py @@ -0,0 +1,366 @@ +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from .types import FrlMode, LtpLanesPattern, FrlCaps, _update_frl_values, _update_ltp_pattern_values +from UniTAP.libs.lib_tsi.tsi_types import TSI_HDTX_FRL_CAPABILITY, TSI_HDTX_FRL_PATTERN_R, TSI_HDTX_FRL_TIMERS, \ + TSI_HDTX_SINK_FEATURE_W, TSI_HDTX_FRL_STATUS_R +from ctypes import c_uint32 + + +class FfeMax: + """ + Class `FfeMax` allows working with FFE on HDMI. + Possible to configure: + - Set and get Value for mode 3 lanes and 3 Gbps `mode_3lanes_3gbps`. + - Set and get Value for mode 3 lanes and 6 Gbps `mode_3lanes_6gbps`. + - Set and get Value for mode 4 lanes and 6 Gbps `mode_4lanes_6gbps`. + - Set and get Value for mode 4 lanes and 8 Gbps `mode_4lanes_8gbps`. + - Set and get Value for mode 4 lanes and 10 Gbps `mode_4lanes_10gbps`. + - Set and get Value for mode 4 lanes and 12 Gbps `mode_4lanes_12gbps`. + """ + def __init__(self, mode_3lanes_3gbps: int = 0, mode_3lanes_6gbps: int = 0, mode_4lanes_6gbps: int = 0, + mode_4lanes_8gbps: int = 0, mode_4lanes_10gbps: int = 0, mode_4lanes_12gbps: int = 0): + self.__mode_3lanes_3gbps = mode_3lanes_3gbps + self.__mode_3lanes_6gbps = mode_3lanes_6gbps + self.__mode_4lanes_6gbps = mode_4lanes_6gbps + self.__mode_4lanes_8gbps = mode_4lanes_8gbps + self.__mode_4lanes_10gbps = mode_4lanes_10gbps + self.__mode_4lanes_12gbps = mode_4lanes_12gbps + + @property + def mode_3lanes_3gbps(self) -> int: + """ + Returns current value for mode 3 lanes and 3 Gbps. + + Returns: + object of int type + """ + return self.__mode_3lanes_3gbps + + @mode_3lanes_3gbps.setter + def mode_3lanes_3gbps(self, ffe_value: int): + """ + Set new FFE value for mode 3 lanes and 3 Gbps. + + Args: + ffe_value (int) + """ + if not(0 <= ffe_value <= 3): + raise ValueError(f"FFE value must be in range 0-3. Current value = {ffe_value}") + self.__mode_3lanes_3gbps = ffe_value + + @property + def mode_3lanes_6gbps(self) -> int: + """ + Returns current value for mode 3 lanes and 6 Gbps. + + Returns: + object of int type + """ + return self.__mode_3lanes_6gbps + + @mode_3lanes_6gbps.setter + def mode_3lanes_6gbps(self, ffe_value: int): + """ + Set new FFE value for mode 3 lanes and 6 Gbps. + + Args: + ffe_value (int) + """ + if not(0 <= ffe_value <= 3): + raise ValueError(f"FFE value must be in range 0-3. Current value = {ffe_value}") + self.__mode_3lanes_6gbps = ffe_value + + @property + def mode_4lanes_6gbps(self) -> int: + """ + Returns current value for mode 6 lanes and 6 Gbps. + + Returns: + object of int type + """ + return self.__mode_4lanes_6gbps + + @mode_4lanes_6gbps.setter + def mode_4lanes_6gbps(self, ffe_value: int): + """ + Set new FFE value for mode 4 lanes and 6 Gbps. + + Args: + ffe_value (int) + """ + if not (0 <= ffe_value <= 3): + raise ValueError(f"FFE value must be in range 0-3. Current value = {ffe_value}") + self.__mode_4lanes_6gbps = ffe_value + + @property + def mode_4lanes_8gbps(self) -> int: + """ + Returns current value for mode 6 lanes and 8 Gbps. + + Returns: + object of int type + """ + return self.__mode_4lanes_8gbps + + @mode_4lanes_8gbps.setter + def mode_4lanes_8gbps(self, ffe_value: int): + """ + Set new FFE value for mode 4 lanes and 8 Gbps. + + Args: + ffe_value (int) + """ + if not (0 <= ffe_value <= 3): + raise ValueError(f"FFE value must be in range 0-3. Current value = {ffe_value}") + self.__mode_4lanes_8gbps = ffe_value + + @property + def mode_4lanes_10gbps(self) -> int: + """ + Returns current value for mode 6 lanes and 10 Gbps. + + Returns: + object of int type + """ + return self.__mode_4lanes_10gbps + + @mode_4lanes_10gbps.setter + def mode_4lanes_10gbps(self, ffe_value: int): + """ + Set new FFE value for mode 4 lanes and 10 Gbps. + + Args: + ffe_value (int) + """ + if not (0 <= ffe_value <= 3): + raise ValueError(f"FFE value must be in range 0-3. Current value = {ffe_value}") + self.__mode_4lanes_10gbps = ffe_value + + @property + def mode_4lanes_12gbps(self) -> int: + """ + Returns current value for mode 6 lanes and 12 Gbps. + + Returns: + object of int type + """ + return self.__mode_4lanes_12gbps + + @mode_4lanes_12gbps.setter + def mode_4lanes_12gbps(self, ffe_value: int): + """ + Set new FFE value for mode 4 lanes and 12 Gbps. + + Args: + ffe_value (int) + """ + if not (0 <= ffe_value <= 3): + raise ValueError(f"FFE value must be in range 0-3. Current value = {ffe_value}") + self.__mode_4lanes_12gbps = ffe_value + + def value(self) -> int: + """ + Returns current combined value from all modes. + + Returns: + object of int type + """ + return ((self.mode_3lanes_3gbps & 0x3) << 5) | ((self.mode_3lanes_6gbps & 0x3) << 7) |\ + ((self.mode_4lanes_6gbps & 0x3) << 9) | ((self.mode_4lanes_8gbps & 0x3) << 11) | \ + ((self.mode_4lanes_10gbps & 0x3) << 13) | ((self.mode_4lanes_12gbps & 0x3) << 15) + + def __str__(self): + return f"3 lanes 3 Gbps - {self.mode_3lanes_3gbps}\n" \ + f"3 lanes 6 Gbps - {self.mode_3lanes_6gbps}\n" \ + f"4 lanes 6 Gbps - {self.mode_4lanes_6gbps}\n" \ + f"4 lanes 8 Gbps - {self.mode_4lanes_8gbps}\n" \ + f"4 lanes 10 Gbps - {self.mode_4lanes_10gbps}\n" \ + f"4 lanes 12 Gbps - {self.mode_4lanes_12gbps}\n" + + +class FrlControlTx: + """ + Class `FrlControlTx` contains information about FRL on Source (TX - transmitter) side. + allows working with: + - Set and get FRL mode `frl_mode`. + - Set and get FRL capabilities `frl_caps`. + - Set and get FFE max `ffe_max`. + - Set and get LTP requested `ltp_pattern` and additional `ltp_additional_pattern`. + - Set link training timeout `lt_timeout` and link training poll timeout `lt_poll_timeout`. + """ + def __init__(self, port_io: PortIO): + self.__io = port_io + self.__ffe_max = FfeMax(0) + self.__frl_caps = FrlCaps() + self.__lt_timeout = 0 + self.__lt_poll_timeout = 0 + self.__ltp_request = LtpLanesPattern() + self.__ltp_additional = LtpLanesPattern() + + def __read_caps(self) -> int: + return self.__io.get(TSI_HDTX_FRL_CAPABILITY, c_uint32)[1] + + def __write_caps(self, value: int): + self.__io.set(TSI_HDTX_FRL_CAPABILITY, value, c_uint32) + + def __read_frl_timers(self) -> int: + return self.__io.get(TSI_HDTX_FRL_TIMERS, c_uint32)[1] + + def __write_frl_timers(self, value: int): + self.__io.set(TSI_HDTX_FRL_TIMERS, value, c_uint32) + + def __read_frl_patterns(self) -> int: + return self.__io.get(TSI_HDTX_FRL_PATTERN_R, c_uint32)[1] + + @property + def ltp_pattern(self) -> LtpLanesPattern: + """ + Returns current LTP lanes pattern. Current Pattern that is being checking during Link Training. + Each lane can request different pattern. + + Returns: + object of `LtpLanesPattern` type + """ + _update_ltp_pattern_values(self.__ltp_request, self.__read_frl_patterns()) + return self.__ltp_request + + @property + def ltp_additional_pattern(self) -> LtpLanesPattern: + """ + Returns current additional LTP lanes pattern. Current Pattern that is being checking during Link Training. + Each lane can request different pattern. + + Returns: + object of `LtpLanesPattern` type + """ + _update_ltp_pattern_values(self.__ltp_additional, self.__read_frl_patterns() >> 16) + return self.__ltp_additional + + @property + def frl_caps(self) -> FrlCaps: + """ + Returns current FRL capabilities. + + Returns: + object of `FrlCaps` type + """ + _update_frl_values(self.__frl_caps, self.__io.get(TSI_HDTX_FRL_STATUS_R, c_uint32)[1]) + return self.__frl_caps + + @property + def frl_mode(self) -> FrlMode: + """ + Returns current FRL mode. + + Returns: + object of `FrlMode` type + """ + return FrlMode(self.__read_caps() & 0x7) + + @frl_mode.setter + def frl_mode(self, frl_mode: FrlMode): + """ + Set new FRL mode. + + Args: + frl_mode (FrlMode) + """ + new_value = frl_mode.value + new_value |= self.__read_caps() & ~0x7 + self.__write_caps(new_value) + + @property + def ffe_max(self) -> FfeMax: + """ + Returns current FFE MAX values. + + Returns: + object of `FfeMax` type + """ + ffe_values = self.__read_caps() + self.__update_values(self.__ffe_max, ffe_values) + return self.__ffe_max + + @ffe_max.setter + def ffe_max(self, ffe_max: FfeMax): + """ + Set new FFE max values. + + Args: + ffe_max (FfeMax) + """ + new_value = ffe_max.value() + new_value |= (self.__read_caps() >> 5) & ~0xFFF + self.__write_caps(new_value) + self.__ffe_max = ffe_max + + @property + def lt_timeout(self) -> int: + """ + Returns current value of link training timeout. + + Returns: + object of int type + """ + self.__lt_timeout = self.__read_frl_timers() & 0xFFFF + return self.__lt_timeout + + @lt_timeout.setter + def lt_timeout(self, lt_timeout: int): + """ + Set new link training timeout. + + Args: + lt_timeout (int) + """ + new_value = lt_timeout + new_value |= self.__read_frl_timers() & ~0xFFFF + self.__write_frl_timers(new_value) + self.__lt_timeout = lt_timeout + + @property + def lt_poll_timeout(self) -> int: + """ + Returns current value of link training poll timeout. + + Returns: + object of int type + """ + self.__lt_timeout = self.__read_frl_timers() & 0xFFFF + return self.__lt_timeout + + @lt_poll_timeout.setter + def lt_poll_timeout(self, lt_poll_timeout: int): + """ + Set new link training poll timeout. + + Args: + lt_poll_timeout (int) + """ + new_value = lt_poll_timeout + new_value |= self.__read_frl_timers() & ~(0xFFFF << 16) + self.__write_frl_timers(new_value) + self.__lt_poll_timeout = lt_poll_timeout + + def link_training(self): + """ + Do link training. + """ + self.__io.set(TSI_HDTX_SINK_FEATURE_W, 5, c_uint32) + + @staticmethod + def __update_values(ffe_max: FfeMax, value: int): + ffe_max.mode_3lanes_3gbps = (value >> 5) & 0x3 + ffe_max.mode_3lanes_6gbps = (value >> 7) & 0x3 + ffe_max.mode_4lanes_6gbps = (value >> 9) & 0x3 + ffe_max.mode_4lanes_8gbps = (value >> 11) & 0x3 + ffe_max.mode_4lanes_10gbps = (value >> 13) & 0x3 + ffe_max.mode_4lanes_12gbps = (value >> 15) & 0x3 + + def __str__(self): + return f"FRL Mode: {self.frl_mode.name}\n" \ + f"FRL Caps:\n{self.frl_caps.__str__()}" \ + f"LTP Lanes Pattern:\n{self.ltp_pattern.__str__()}" \ + f"LTP Lanes Pattern Additional:\n{self.ltp_additional_pattern.__str__()}" \ + f"LT timeout: {self.lt_timeout}\n" \ + f"LT poll timeout: {self.lt_poll_timeout}\n" \ + f"FFE Max:\n{self.ffe_max.__str__()}\n" diff --git a/UniTAP/dev/ports/modules/link/hdmi/hdmi_utils.py b/UniTAP/dev/ports/modules/link/hdmi/hdmi_utils.py new file mode 100644 index 0000000..d067bf7 --- /dev/null +++ b/UniTAP/dev/ports/modules/link/hdmi/hdmi_utils.py @@ -0,0 +1,53 @@ +from UniTAP.common import VideoMode, ColorInfo, Timing + + +def get_vm_color_format(color_mode: int) -> ColorInfo.ColorFormat: + + if color_mode == 0: + return ColorInfo.ColorFormat.CF_NONE + elif color_mode == 1: + return ColorInfo.ColorFormat.CF_UNKNOWN + elif color_mode == 2: + return ColorInfo.ColorFormat.CF_RGB + elif color_mode == 3: + return ColorInfo.ColorFormat.CF_YCbCr_422 + elif color_mode == 4: + return ColorInfo.ColorFormat.CF_YCbCr_444 + elif color_mode == 5: + return ColorInfo.ColorFormat.CF_YCbCr_420 + else: + return ColorInfo.ColorFormat.CF_IDO_DEFINED + + +def get_vm_colorimetry(colorimetry: int) -> ColorInfo.Colorimetry: + + if colorimetry == 0: + return ColorInfo.Colorimetry.CM_RESERVED + elif colorimetry == 1: + return ColorInfo.Colorimetry.CM_NONE + elif colorimetry == 2: + return ColorInfo.Colorimetry.CM_sRGB + elif colorimetry == 3: + return ColorInfo.Colorimetry.CM_SMPTE_170M + elif colorimetry == 4: + return ColorInfo.Colorimetry.CM_ITUR_BT601 + elif colorimetry == 5: + return ColorInfo.Colorimetry.CM_ITUR_BT709 + elif colorimetry == 6: + return ColorInfo.Colorimetry.CM_xvYCC601 + elif colorimetry == 7: + return ColorInfo.Colorimetry.CM_xvYCC709 + elif colorimetry == 8: + return ColorInfo.Colorimetry.CM_sYCC601 + elif colorimetry == 9: + return ColorInfo.Colorimetry.CM_AdobeYCC601 + elif colorimetry == 10: + return ColorInfo.Colorimetry.CM_AdobeRGB + elif colorimetry == 11: + return ColorInfo.Colorimetry.CM_ITUR_BT2020_YcCbcCrc + elif colorimetry == 12: + return ColorInfo.Colorimetry.CM_ITUR_BT2020_YCbCr + elif colorimetry == 13: + return ColorInfo.Colorimetry.CM_ITUR_BT2020_RGB + else: + return ColorInfo.Colorimetry.CM_RESERVED diff --git a/UniTAP/dev/ports/modules/link/hdmi/link.py b/UniTAP/dev/ports/modules/link/hdmi/link.py new file mode 100644 index 0000000..ca158de --- /dev/null +++ b/UniTAP/dev/ports/modules/link/hdmi/link.py @@ -0,0 +1,171 @@ +import warnings + +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from .arc_rx import ArcRx +from .tmds_rx import TmdsRx +from .tmds_tx import TmdsTx +from .status_rx import StatusRx, HdmiModeRx +from .status_tx import StatusTx, HdmiModeTx +from .capabilities import HdmiCapabilities +from .frl_control_tx import FrlControlTx +from .frl_caps_rx import FrlControlRx +from UniTAP.libs.lib_tsi.tsi_types import TSI_HDTX_CAPABILITY_R, TSI_HDRX_CAPABILITY_R +from typing import Optional + + +class HdmiLinkTx: + """ + Main class describes HDMI link on Source (TX - transmitter) side. Contains following objects for working with link: + - TMDS `tmds`. + - FRL `frl`. + - Status `status`. + """ + def __init__(self, port_io: PortIO): + self.__io = port_io + self.__tmds = TmdsTx(self.__io) + self.__status = StatusTx(self.__io) + self.__frl = FrlControlTx(self.__io) + self.__caps = HdmiCapabilities(TSI_HDTX_CAPABILITY_R, self.__io) + + @property + def tmds(self) -> Optional[TmdsTx]: + """ + Returns object of class `TmdsTx` for working with TMDS. + None if TMDS does not support on the device. + + Returns: + object of `TmdsTx`|None type + """ + if self.__status.hdmi_mode == HdmiModeTx.HDMI_2_1: + warnings.warn("Current device mode is HDMI 2.1, please, use FRL controls or change HDMI mode.") + return None + if not self.__caps.support_tmds: + warnings.warn("Current device does not support TMDS.") + return None + + return self.__tmds + + @property + def frl(self) -> Optional[FrlControlTx]: + """ + Returns object of class `FrlControlTx` for working with FRL. + None if FRL does not support on the device. + + Returns: + object of `FrlControlTx`|None type + """ + if self.__status.hdmi_mode != HdmiModeTx.HDMI_2_1: + warnings.warn("Current device mode is not HDMI 2.1, please, use TMDS controls or change HDMI mode.") + return None + if not self.__caps.support_frl: + warnings.warn("Current device does not support FRl.") + return None + + return self.__frl + + @property + def status(self) -> StatusTx: + """ + Returns object of class `StatusTx` for working with link status. + + Returns: + object of `StatusTx` type + """ + return self.__status + + def __str__(self): + __str = f"Status:\n{self.status.__str__()}\n" + if self.__status.hdmi_mode == HdmiModeTx.HDMI_2_1 and self.__caps.support_frl: + __str += f"FRL:\n{self.frl.__str__()}\n" + if self.__status.hdmi_mode != HdmiModeTx.HDMI_2_1 and self.__caps.support_tmds: + __str += f"TMDS:\n{self.tmds.__str__()}\n" + return __str + + +class HdmiLinkRx: + """ + Main class describes HDMI link on Sink (RX - receiver) side. Contains following objects for working with link: + - TMDS `tmds`. + - FRL `frl`. + - ARC `arc`. + - Status `status`. + """ + def __init__(self, port_io: PortIO): + self.__io = port_io + self.__tmds = TmdsRx(self.__io) + self.__status = StatusRx(self.__io) + self.__frl = FrlControlRx(self.__io) + self.__arc = ArcRx(self.__io) + self.__caps = HdmiCapabilities(TSI_HDRX_CAPABILITY_R, self.__io) + + @property + def tmds(self) -> Optional[TmdsRx]: + """ + Returns object of class `TmdsRx` for working with TMDS. + None if TMDS does not support on the device. + + Returns: + object of `TmdsRx`|None type + """ + if self.__status.hdmi_mode == HdmiModeTx.HDMI_2_1: + warnings.warn("Current device mode is HDMI 2.1, please, use FRL controls or change HDMI mode.") + return None + if not self.__caps.support_tmds: + warnings.warn("Current device does not support TMDS.") + return None + + return self.__tmds + + @property + def frl(self) -> Optional[FrlControlRx]: + """ + Returns object of class `FrlControlRx` for working with FRL. + None if FRL does not support on the device. + + Returns: + object of `FrlControlRx`|None type + """ + if self.__status.hdmi_mode != HdmiModeRx.HDMI_2_1: + warnings.warn("Current device mode is not HDMI 2.1, please, use TMDS controls or change HDMI mode.") + return None + if not self.__caps.support_frl: + warnings.warn("Current device does not support FRl.") + return None + + return self.__frl + + @property + def arc(self) -> Optional[ArcRx]: + """ + Returns object of class `ArcRx` for working with ARC. + None if ARC does not support on the device. + + Returns: + object of `ArcRx`|None type + """ + if not self.__caps.support_arc: + warnings.warn("Current device does not support ARC.") + return None + + return self.__arc + + @property + def status(self) -> StatusRx: + """ + Returns object of class `StatusRx` for working with link status. + + Returns: + object of `StatusRx` type + """ + return self.__status + + def __str__(self): + __str = f"Status:\n{self.status.__str__()}\n" + if self.__status.hdmi_mode == HdmiModeTx.HDMI_2_1 and self.__caps.support_frl: + __str += f"FRL:\n{self.frl.__str__()}\n" + if self.__status.hdmi_mode != HdmiModeTx.HDMI_2_1 and self.__caps.support_tmds: + __str += f"TMDS:\n{self.tmds.__str__()}\n" + if self.__caps.support_arc: + __str += f"ARC:\n{self.arc.__str__()}\n" + __str += f"Stream info:\n{self.status.stream(0).__str__()}\n" + return __str diff --git a/UniTAP/dev/ports/modules/link/hdmi/private_types.py b/UniTAP/dev/ports/modules/link/hdmi/private_types.py new file mode 100644 index 0000000..ed6badb --- /dev/null +++ b/UniTAP/dev/ports/modules/link/hdmi/private_types.py @@ -0,0 +1,52 @@ +from ctypes import Structure, c_uint16, c_uint32 + + +class HdmiCrc(Structure): + _fields_ = [ + ('r', c_uint16), + ('g', c_uint16), + ('b', c_uint16) + ] + + +class HdmiVideoMode(Structure): + _fields_ = [ + ('h_total', c_uint32), + ('v_total', c_uint32), + ('h_active', c_uint32), + ('v_active', c_uint32), + ('f_rate', c_uint32), + ('color_mode', c_uint32), + ('colorimetry', c_uint32), + ('bpp', c_uint32), + ('h_start', c_uint32), + ('v_start', c_uint32), + ('h_sync_width', c_uint32), + ('v_sync_width', c_uint32), + ('bpc', c_uint32) + ] + + +class HdmiVideoModeInfo: + + def __init__(self, hdmi_vm: HdmiVideoMode): + self.h_total = hdmi_vm.h_total + self.v_total = hdmi_vm.v_total + self.h_active = hdmi_vm.h_active + self.v_active = hdmi_vm.v_active + self.h_start = hdmi_vm.h_start + self.v_start = hdmi_vm.v_start + self.h_sync_width = hdmi_vm.h_sync_width + self.v_sync_width = hdmi_vm.v_sync_width + self.frame_rate = hdmi_vm.f_rate + self.bpp = hdmi_vm.bpp + self.color_mode = hdmi_vm.color_mode + self.colorimetry = hdmi_vm.colorimetry + self.bpc = hdmi_vm.bpc + + def is_eq(self, other) -> bool: + return self.h_total == other.h_total and self.v_total == other.v_total and self.h_active == other.h_active and \ + self.v_active == other.v_active and self.h_start == other.h_start and self.v_start == other.v_start and \ + self.h_sync_width == other.h_sync_start and self.v_sync_width == other.v_sync_start and \ + self.frame_rate == other.frame_rate and self.bpp == other.bpp and self.color_mode == other.color_mode \ + and self.colorimetry == self.colorimetry and self.bpc == self.bpc diff --git a/UniTAP/dev/ports/modules/link/hdmi/status_rx.py b/UniTAP/dev/ports/modules/link/hdmi/status_rx.py new file mode 100644 index 0000000..74dedcf --- /dev/null +++ b/UniTAP/dev/ports/modules/link/hdmi/status_rx.py @@ -0,0 +1,151 @@ +import time + +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from .types import HdmiModeRx, FrlMode, _update_error_counters_rx +from UniTAP.libs.lib_tsi.tsi_types import TSI_HDRX_BEHAVIOR, TSI_HDRX_LANES_ERR_COUNTERS_R, TSI_HDRX_HPD_STATUS_R,\ + TSI_HDRX_FRL_CAPABILITY, TSI_HDRX_LINK_STATUS_R, TSI_HDRX_HPD_CONTROL_W, TSI_HDRX_VIDEO_MODE_R, \ + TSI_VIDCAP_SIGNAL_CRC_R, TSI_SUCCESS +from ctypes import c_uint8, c_uint32, c_uint64 +from UniTAP.utils.function_wrapper import function_scheduler +from .types import _hdmi_vm_info_to_video_mode, HdmiVideoModeInfo +from .private_types import HdmiCrc, HdmiVideoMode +from ..dp.link_status_common import StreamStatus + + +class StatusRx: + """ + Class `StatusRx` describes information about HDMI link status on Sink (RX - receiver) side. Contains following info: + - HDMI mode `hdmi_mode`. + - Link Error counters `error_counters`. + - Channel lock `channel_lock`. + - HPD status `hpd_status` and HPD assert `set_assert_state`. + - Stream info `stream`. + """ + def __init__(self, port_io: PortIO): + self.__io = port_io + + def __frl_status(self) -> FrlMode: + return FrlMode(self.__io.get(TSI_HDRX_FRL_CAPABILITY, c_uint32)[1] & 0xF) + + @property + def hdmi_mode(self) -> HdmiModeRx: + """ + Returns current HDMI mode. + + Returns: + object of `HdmiModeRx` type + """ + return HdmiModeRx(self.__io.get(TSI_HDRX_BEHAVIOR, c_uint32)[1]) + + @hdmi_mode.setter + def hdmi_mode(self, hdmi_mode: HdmiModeRx): + """ + Set new HDMI mode. + + Args: + hdmi_mode (HdmiModeRx) + """ + self.__io.set(TSI_HDRX_BEHAVIOR, hdmi_mode.value, c_uint32) + + @property + def error_counters(self) -> list: + """ + Returns values of current errors on link. + + Returns: + object of list type + """ + return _update_error_counters_rx(self.__io.get(TSI_HDRX_LANES_ERR_COUNTERS_R, c_uint64)[1]) + + def set_assert_state(self, asserted: bool = True): + """ + Assert/Deassert HPD state. + + Args: + asserted (bool) + """ + self.__io.set(TSI_HDRX_HPD_CONTROL_W, int(asserted), c_uint32) + + @property + def channel_lock(self) -> list: + """ + Returns channel lock states of current link. + + Returns: + object of list type + """ + return self.__update_channel_lock(self.__io.get(TSI_HDRX_LINK_STATUS_R, c_uint32)[1], self.hdmi_mode) + + @staticmethod + def __update_channel_lock(value: int, mode: HdmiModeRx) -> list: + if mode != HdmiModeRx.HDMI_2_1: + lane_0 = (value & (1 << 4)) != 0 + lane_1 = (value & (1 << 5)) != 0 + lane_2 = (value & (1 << 6)) != 0 + lane_3 = False + + else: + lane_0 = (value & (1 << 13)) != 0 + lane_1 = (value & (1 << 14)) != 0 + lane_2 = (value & (1 << 15)) != 0 + lane_3 = (value & (1 << 16)) != 0 + return [lane_0, lane_1, lane_2, lane_3] + + @property + def hpd_status(self) -> bool: + """ + Returns True if HDP is enabled, False - if not. + + Returns: + object of bool type + """ + return (self.__io.get(TSI_HDRX_HPD_STATUS_R, c_uint32)[1] & 0x1) != 0 + + def __check_video(self) -> bool: + def is_msa_available(io): + result, msa_info, size = io.get(TSI_HDRX_VIDEO_MODE_R, HdmiVideoMode, 4) + return result > 0 + + return function_scheduler(is_msa_available, self.__io, interval=5, timeout=10) + + def stream(self, stream_index: int = 0) -> StreamStatus: + """ + Returns status of selected stream `StreamStatus`. + + Args: + stream_index (int) - number of selected number + + Returns: + object of `StreamStatus` type + """ + stream_status = StreamStatus() + + if not self.__check_video(): + raise ValueError("Video is not available.") + + time.sleep(1) + + result, hdmi_video_mode_info, size = self.__io.get(TSI_HDRX_VIDEO_MODE_R, HdmiVideoMode, 4) + + # TODO - Size temporary will be equal 1 + size = 1 + + if 0 <= stream_index < size: + stream_status.video_mode = _hdmi_vm_info_to_video_mode(HdmiVideoModeInfo(hdmi_video_mode_info + [stream_index])) + result = self.__io.get(TSI_VIDCAP_SIGNAL_CRC_R, data_type=c_uint8, data_count=6) + if result[0] >= TSI_SUCCESS: + hdmi_crc = HdmiCrc.from_buffer(bytearray(result[1])) + stream_status.crc = [hdmi_crc.r, hdmi_crc.g, hdmi_crc.b] + + stream_status.dsc_crc = [0, 0, 0] + else: + raise ValueError(f"Selected stream {stream_index} is not available. " + f"Available stream number: {size}") + + return stream_status + + def __str__(self): + return f"HDMI Mode: {self.hdmi_mode.name}\n" \ + f"Channel Lock:\n{self.channel_lock}\n" \ + f"Error Counters:\n{self.error_counters.__str__()}\n" diff --git a/UniTAP/dev/ports/modules/link/hdmi/status_tx.py b/UniTAP/dev/ports/modules/link/hdmi/status_tx.py new file mode 100644 index 0000000..3b230a0 --- /dev/null +++ b/UniTAP/dev/ports/modules/link/hdmi/status_tx.py @@ -0,0 +1,140 @@ +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from .types import HdmiModeTx, FrlMode, _update_error_counters_tx +from UniTAP.libs.lib_tsi.tsi_types import TSI_HDTX_FRL_STATUS_R, TSI_HDTX_CONTROL_W, TSI_HDTX_STATUS_R, \ + TSI_HDTX_SINK_STATUS_R, TSI_HDTX_LANES_ERR_COUNTERS_R, TSI_HDTX_HPD_STATUS_R +from ctypes import c_uint32, c_uint64 + + +class StatusTx: + """ + Class `StatusTx` describes information about HDMI link status on Source (TX - transmitter) side. + Contains following info: + - Set and get HDMI mode `hdmi_mode`. + - Link Error counters `error_counters`. + - Video status `video_status`. + - Channel lock `channel_lock`. + - HPD status `hpd_status`. + """ + def __init__(self, port_io: PortIO): + self.__io = port_io + + def __read_status(self) -> int: + return self.__io.get(TSI_HDTX_STATUS_R, c_uint32)[1] + + def __frl_status(self) -> FrlMode: + return FrlMode(self.__io.get(TSI_HDTX_FRL_STATUS_R, c_uint32)[1] & 0xF) + + @property + def video_status(self) -> bool: + """ + Returns current video status (video enable or not). + + Returns: + object of bool type + """ + return (self.__read_status() & 0x2) != 0 + + @property + def error_counters(self) -> list: + """ + Returns values of current errors on link. + + Returns: + object of list type + """ + return _update_error_counters_tx(self.__io.get(TSI_HDTX_LANES_ERR_COUNTERS_R, c_uint64)[1]) + + @property + def channel_lock(self) -> list: + """ + Returns channel lock states of current link. + + Returns: + object of list type + """ + if self.__frl_status().Mode_Disable or self.hdmi_mode in [HdmiModeTx.HDMI_1_4, HdmiModeTx.HDMI_2_0]: + return self.__update_channel_lock(self.__io.get(TSI_HDTX_SINK_STATUS_R, c_uint32)[1], self.hdmi_mode) + else: + return self.__update_channel_lock(self.__io.get(TSI_HDTX_FRL_STATUS_R, c_uint32)[1], self.hdmi_mode) + + @property + def hpd_status(self) -> bool: + """ + Returns True if HDP is enabled, False - if not. + + Returns: + object of bool type + """ + return (self.__io.get(TSI_HDTX_HPD_STATUS_R, c_uint32)[1] & 0x1) != 0 + + @property + def hdmi_mode(self) -> HdmiModeTx: + """ + Returns current HDMI mode. + + Returns: + object of `HdmiModeTx` type + """ + return HdmiModeTx((self.__read_status() >> 2) & 0x3) + + @hdmi_mode.setter + def hdmi_mode(self, hdmi_mode: HdmiModeTx): + """ + Set new HDMI mode. + + Args: + hdmi_mode (HdmiModeTx) + """ + self.__io.set(TSI_HDTX_CONTROL_W, hdmi_mode.value << 2, c_uint32) + + @property + def available_link_rate(self) -> float: + """ + + Returns available link rate. + + Returns: + object of float type + """ + + gbps = 1000000000 + frl_status = self.__frl_status() + if frl_status == FrlMode.Mode_Disable: + link_rate = 3 * 6 * gbps + elif frl_status == FrlMode.Mode_3lanes_3gbps: + link_rate = 3 * 3 * gbps + elif frl_status == FrlMode.Mode_3lanes_6gbps: + link_rate = 3 * 6 * gbps + elif frl_status == FrlMode.Mode_4lanes_6gbps: + link_rate = 4 * 6 * gbps + elif frl_status == FrlMode.Mode_4lanes_8gbps: + link_rate = 4 * 8 * gbps + elif frl_status == FrlMode.Mode_4lanes_10gbps: + link_rate = 4 * 10 * gbps + elif frl_status == FrlMode.Mode_4lanes_12gbps: + link_rate = 4 * 12 * gbps + else: + link_rate = 0 + + return link_rate + + @staticmethod + def __update_channel_lock(value, mode: HdmiModeTx) -> list: + if mode != HdmiModeTx.HDMI_2_1: + lane_0 = (value & (1 << 6)) != 0 + lane_1 = (value & (1 << 7)) != 0 + lane_2 = (value & (1 << 8)) != 0 + lane_3 = False + + else: + lane_0 = (((value >> 25) & 0xF) >> 0) & 0x1 != 0 + lane_1 = (((value >> 25) & 0xF) >> 1) & 0x1 != 0 + lane_2 = (((value >> 25) & 0xF) >> 2) & 0x1 != 0 + lane_3 = (((value >> 25) & 0xF) >> 3) & 0x1 != 0 + + return [lane_0, lane_1, lane_2, lane_3] + + def __str__(self): + return f"HDMI Mode: {self.hdmi_mode.name}\n" \ + f"Channel Lock:\n{self.channel_lock}\n" \ + f"Error Counters:\n{self.error_counters.__str__()}\n" diff --git a/UniTAP/dev/ports/modules/link/hdmi/tmds_rx.py b/UniTAP/dev/ports/modules/link/hdmi/tmds_rx.py new file mode 100644 index 0000000..5681ee5 --- /dev/null +++ b/UniTAP/dev/ports/modules/link/hdmi/tmds_rx.py @@ -0,0 +1,57 @@ +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from .types import LinkMode +from UniTAP.libs.lib_tsi.tsi_types import TSI_HDRX_LINK_STATUS_R +from ctypes import c_uint32 + + +class TmdsRx: + """ + CLass `TmdsRx` allows working with TMDS on Sink (RX - receiver) side. + - Get link mode `link_mode`. + - Get clock rate `clock_rate`. + - Get input stream lock state `input_stream_lock`. + """ + def __init__(self, port_io: PortIO): + self.__io = port_io + self.__link_mode = LinkMode.Unknown + self.__input_stream_lock = False + + def __read_link_status(self) -> int: + return self.__io.get(TSI_HDRX_LINK_STATUS_R, c_uint32)[1] + + @property + def clock_rate(self) -> int: + """ + Returns current clock rate. + + Returns: + object of int type + """ + return 6 if self.__read_link_status() & 0x2 else 3 + + @property + def link_mode(self) -> LinkMode: + """ + Returns current link mode. + + Returns: + object of `LinkMode` type + """ + self.__link_mode = LinkMode(self.__read_link_status() & 0x8) + return self.__link_mode + + @property + def input_stream_lock(self) -> bool: + """ + Returns current state of input stream lock. + + Returns: + object of bool type + """ + self.__input_stream_lock = (self.__read_link_status() & 0x4) != 0 + return self.__input_stream_lock + + def __str__(self): + return f"Clock rate: {self.clock_rate}G\n" \ + f"Link mode: {self.link_mode.name}\n" \ + f"Input stream lock: {'Enable' if self.input_stream_lock else 'Disable'}" diff --git a/UniTAP/dev/ports/modules/link/hdmi/tmds_tx.py b/UniTAP/dev/ports/modules/link/hdmi/tmds_tx.py new file mode 100644 index 0000000..1af216a --- /dev/null +++ b/UniTAP/dev/ports/modules/link/hdmi/tmds_tx.py @@ -0,0 +1,99 @@ +import warnings + +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from .types import ClockRate, LinkMode, ScramblerState +from UniTAP.libs.lib_tsi.tsi_types import TSI_HDTX_SINK_FEATURE_W, TSI_HDTX_STATUS_R, TSI_HDTX_SINK_STATUS_R, \ + TSI_HDTX_CONTROL_W +from ctypes import c_uint32 + + +class TmdsTx: + """ + CLass `TmdsRx` allows working with TMDS on Source (TX - transmitter) side. + - Get link mode `link_mode`. + - Set and get clock rate `clock_rate`. + - Set and get scrambler `scrambler`. + """ + def __init__(self, port_io: PortIO): + self.__io = port_io + self.__clock_rate = ClockRate.Unknown + self.__link_mode = LinkMode.Unknown + self.__scrambler = ScramblerState.Unknown + + def __read_sink_status(self) -> int: + return self.__io.get(TSI_HDTX_SINK_STATUS_R, c_uint32)[1] + + def __read_hdtx_status(self) -> int: + return self.__io.get(TSI_HDTX_STATUS_R, c_uint32)[1] + + def __write_sink_feature(self, value: int): + self.__io.set(TSI_HDTX_SINK_FEATURE_W, value, c_uint32) + + @property + def clock_rate(self) -> int: + """ + Returns current clock rate. + + Returns: + object of int type + """ + return 6 if self.__read_sink_status() & 0x2 else 3 + + @clock_rate.setter + def clock_rate(self, clock_rate: int): + """ + Set new clock rate value. Available variants: 3, 6. + + Args: + clock_rate (int) + """ + if clock_rate not in [3, 6]: + warnings.warn("Incorrect value. Must be from available values: 3, 6") + return + self.__write_sink_feature(3 if clock_rate == 3 else 4) + + @property + def link_mode(self) -> LinkMode: + """ + Returns current link mode. + + Returns: + object of `LinkMode` type + """ + self.__link_mode = LinkMode(self.__read_hdtx_status() & 0x1) + return self.__link_mode + + @link_mode.setter + def link_mode(self, link_mode: LinkMode): + """ + Set new link mode. + + Args: + link_mode (LinkMode) + """ + self.__io.set(TSI_HDTX_CONTROL_W, link_mode.value, c_uint32) + + @property + def scrambler(self) -> bool: + """ + Returns current scrambler state. + + Returns: + object of bool type + """ + return self.__read_sink_status() & 0x1 != 0 + + @scrambler.setter + def scrambler(self, scrambler: bool): + """ + Enable/disable scrambler. + + Args: + scrambler (bool) + """ + self.__write_sink_feature(1 if scrambler else 2) + + def __str__(self): + return f"Clock rate: {self.clock_rate}G\n" \ + f"Link mode: {self.link_mode.name}\n" \ + f"Scrambler: {'Enable' if self.scrambler else 'Disable'}\n" diff --git a/UniTAP/dev/ports/modules/link/hdmi/types.py b/UniTAP/dev/ports/modules/link/hdmi/types.py new file mode 100644 index 0000000..2d1af37 --- /dev/null +++ b/UniTAP/dev/ports/modules/link/hdmi/types.py @@ -0,0 +1,372 @@ +from enum import IntEnum +from .hdmi_utils import * +from .private_types import HdmiVideoModeInfo + + +class FrlMode(IntEnum): + """ + Class `FrlMode` contains all possible variants of FRL modes. + """ + Mode_Unknown = -1 + Mode_Disable = 0 + Mode_3lanes_3gbps = 1 + Mode_3lanes_6gbps = 2 + Mode_4lanes_6gbps = 3 + Mode_4lanes_8gbps = 4 + Mode_4lanes_10gbps = 5 + Mode_4lanes_12gbps = 6 + + +class LtpPattern(IntEnum): + """ + Class `LtpPattern` contains all possible variants of LTP pattern types. + """ + Unknown = -1 + NoLinkPattern = 0 + All_1s = 1 + All_0s = 2 + NyquistClockPattern = 3 + SourceTxFFECompliance = 4 + LFSR0 = 5 + LFSR1 = 6 + LFSR2 = 7 + LFSR3 = 8 + + +class ScramblerState(IntEnum): + """ + Class `ScramblerState` contains all possible variants of scrambler states. + """ + Unknown = -1 + Disable = 0 + Enable = 1 + + +class LinkMode(IntEnum): + """ + Class `LinkMode` contains all possible variants of Link modes. + """ + Unknown = -1 + HDMI = 0 + DVI = 1 + + +class ClockRate(IntEnum): + """ + Class `ClockRate` contains all possible variants of clock rate. + """ + Unknown = -1 + Rate3G = 0 + Rate6G = 1 + + +class HdmiModeTx(IntEnum): + """ + Class `HdmiModeTx` contains all possible variants of HDMI modes on TX side. + """ + Unknown = -1 + HDMI_2_0 = 0 + HDMI_1_4 = 1 + HDMI_2_1 = 2 + + +class HdmiModeRx(IntEnum): + """ + Class `HdmiModeRx` contains all possible variants of HDMI modes on RX side. + """ + Unknown = -1 + HDMI_1_4 = 0 + HDMI_2_0 = 1 + HDMI_2_1 = 2 + + +class LtpLanesPattern: + """ + Class `LtpLanesPattern` describes LTP pattern values on all lanes. + - Lane 0 `lane0`. + - Lane 1 `lane1`. + - Lane 2 `lane2`. + - Lane 3 `lane3`. + """ + def __init__(self): + self.__lane0 = LtpPattern.Unknown + self.__lane1 = LtpPattern.Unknown + self.__lane2 = LtpPattern.Unknown + self.__lane3 = LtpPattern.Unknown + + @property + def lane0(self) -> LtpPattern: + """ + Returns `LtpPattern` value of line 0. + + Returns: + object of `LtpPattern` type + """ + return self.__lane0 + + @lane0.setter + def lane0(self, lane0: LtpPattern): + """ + Set new value `LtpPattern` to lane 0. + + Args: + lane0 (LtpPattern) + """ + self.__lane0 = lane0 + + @property + def lane1(self) -> LtpPattern: + """ + Returns `LtpPattern` value of line 1. + + Returns: + object of `LtpPattern` type + """ + return self.__lane1 + + @lane1.setter + def lane1(self, lane1: LtpPattern): + """ + Set new value `LtpPattern` to lane 1. + + Args: + lane1 (LtpPattern) + """ + self.__lane1 = lane1 + + @property + def lane2(self) -> LtpPattern: + """ + Returns `LtpPattern` value of line 2. + + Returns: + object of `LtpPattern` type + """ + return self.__lane2 + + @lane2.setter + def lane2(self, lane2: LtpPattern): + """ + Set new value `LtpPattern` to lane 1. + + Args: + lane2 (LtpPattern) + """ + self.__lane2 = lane2 + + @property + def lane3(self) -> LtpPattern: + """ + Returns `LtpPattern` value of line 3. + + Returns: + object of `LtpPattern` type + """ + return self.__lane3 + + @lane3.setter + def lane3(self, lane3: LtpPattern): + """ + Set new value `LtpPattern` to lane 3. + + Args: + lane3 (LtpPattern) + """ + self.__lane3 = lane3 + + def value(self) -> int: + """ + Returns combines value of all lines. + + Returns: + object of int type + """ + return self.lane0.value | (self.lane1.value << 4) | (self.lane2.value << 8) | (self.lane3.value << 12) + + def __str__(self): + return f"Lane 0: {self.lane0}\n" \ + f"Lane 1: {self.lane1}\n" \ + f"Lane 2: {self.lane2}\n" \ + f"Lane 3: {self.lane3}\n" + + +class FrlCaps: + """ + Class `FrlCaps` describes FRL capabilities. + - FRL start `frl_start`. + - FLT no timeout `flt_no_timeout`. + - FLT ready `flt_ready` + - FRL max `frl_max`. + - Check patterns state `check_patterns`. + """ + def __init__(self): + self.__frl_start = False + self.__flt_no_timeout = False + self.__flt_ready = False + self.__frl_max = False + self.__check_patterns = False + + @property + def frl_start(self) -> bool: + """ + Returns state of FRL start. + + Returns: + object of bool type + """ + return self.__frl_start + + @frl_start.setter + def frl_start(self, frl_start: bool): + """ + Set new state to FRL start. + + Args: + frl_start (bool) + """ + self.__frl_start = frl_start + + @property + def flt_no_timeout(self) -> bool: + """ + Returns state of FLT no timeout. + + Returns: + object of bool type + """ + return self.__flt_no_timeout + + @flt_no_timeout.setter + def flt_no_timeout(self, flt_no_timeout: bool): + """ + Set new state to FLT no timeout. + + Args: + flt_no_timeout (bool) + """ + self.__flt_no_timeout = flt_no_timeout + + @property + def flt_ready(self) -> bool: + """ + Returns state of FLT ready. + + Returns: + object of bool type + """ + return self.__flt_ready + + @flt_ready.setter + def flt_ready(self, flt_ready: bool): + """ + Set new state to FLT ready. + + Args: + flt_ready (bool) + """ + self.__flt_ready = flt_ready + + @property + def frl_max(self) -> bool: + """ + Returns state of FLT max. + + Returns: + object of bool type + """ + return self.__frl_max + + @frl_max.setter + def frl_max(self, frl_max: bool): + """ + Set new state to FLT max. + + Args: + frl_max (bool) + """ + self.__frl_max = frl_max + + @property + def check_patterns(self) -> bool: + """ + Returns state of check patterns. + + Returns: + object of bool type + """ + return self.__check_patterns + + @check_patterns.setter + def check_patterns(self, check_patterns: bool): + """ + Set new state to check patterns. + + Args: + check_patterns (bool) + """ + self.__check_patterns = check_patterns + + def value(self) -> int: + """ + Returns combined value of all flags. + + Returns: + object of int type + """ + return (self.frl_start << 4) | (self.flt_ready << 5) | (self.flt_no_timeout << 6) | (self.frl_max << 7) | \ + (self.check_patterns << 8) + + def __str__(self): + return f"FRL Start: {self.__frl_start}\n" \ + f"FRL No Timeout: {self.__flt_no_timeout}\n" \ + f"FRL Max: {self.__frl_max}\n" \ + f"Check patterns: {self.__check_patterns}\n" + + +def _hdmi_vm_info_to_video_mode(hdmi_vm_info: HdmiVideoModeInfo): + video_mode = VideoMode() + + video_mode.timing.hactive = hdmi_vm_info.h_active + video_mode.timing.vactive = hdmi_vm_info.v_active + video_mode.timing.htotal = hdmi_vm_info.h_total + video_mode.timing.vtotal = hdmi_vm_info.v_total + video_mode.timing.hstart = hdmi_vm_info.h_start + video_mode.timing.vstart = hdmi_vm_info.v_start + video_mode.timing.hswidth = hdmi_vm_info.h_sync_width + video_mode.timing.vswidth = hdmi_vm_info.v_sync_width + + video_mode.color_info.colorimetry = get_vm_colorimetry(hdmi_vm_info.colorimetry) + video_mode.color_info.color_format = get_vm_color_format(hdmi_vm_info.color_mode) + video_mode.color_info.bpc = hdmi_vm_info.bpc + video_mode.color_info.dynamic_range = ColorInfo.DynamicRange.DR_VESA + + video_mode.timing.frame_rate = hdmi_vm_info.frame_rate + + return video_mode + + +def _update_frl_values(frl_caps: FrlCaps, value: int): + frl_caps.frl_start = bool(value >> 4 & 0x1) + frl_caps.flt_ready = bool(value >> 5 & 0x1) + frl_caps.flt_no_timeout = bool(value >> 6 & 0x1) + frl_caps.frl_max = bool(value >> 7 & 0x1) + frl_caps.check_patterns = bool(value >> 8 & 0x1) + + +def _update_ltp_pattern_values(pattern: LtpLanesPattern, value: int): + pattern.lane0 = LtpPattern(value & 0xF) + pattern.lane1 = LtpPattern((value >> 4) & 0xF) + pattern.lane2 = LtpPattern((value >> 8) & 0xF) + pattern.lane3 = LtpPattern((value >> 12) & 0xF) + + +def _update_error_counters_rx(value: int) -> list: + lane0 = value & 0x7FFF if (value >> 15) & 0x1 else 0 + lane1 = ((value >> 16) & 0xFFFF) if (value >> 31) & 0x1 else 0 + lane2 = ((value >> 32) & 0xFFFF) if (value >> 47) & 0x1 else 0 + lane3 = ((value >> 48) & 0xFFFF) if (value >> 63) & 0x1 else 0 + return [lane0, lane1, lane2, lane3] + + +def _update_error_counters_tx(value: int) -> list: + return [value & 0xFFFF, (value >> 16) & 0xFFFF, (value >> 32) & 0xFFFF, (value >> 48) & 0xFFFF] diff --git a/UniTAP/dev/ports/modules/panel_replay/__init__.py b/UniTAP/dev/ports/modules/panel_replay/__init__.py new file mode 100644 index 0000000..94c80fa --- /dev/null +++ b/UniTAP/dev/ports/modules/panel_replay/__init__.py @@ -0,0 +1,4 @@ +from .pr import PanelReplay, PanelReplayConfig, PanelReplayStatus +from .pr_types import * +from .pr_sink import SinkPanelReplay, SinkPanelSelfRefresh +from .pr_sink_types import PRCapsFlags, PrGranularityCaps, SuYGranularity, PSRCapsFlags, PSRCaps, PSRSetupTime diff --git a/UniTAP/dev/ports/modules/panel_replay/pr.py b/UniTAP/dev/ports/modules/panel_replay/pr.py new file mode 100644 index 0000000..5ccae64 --- /dev/null +++ b/UniTAP/dev/ports/modules/panel_replay/pr.py @@ -0,0 +1,73 @@ +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from UniTAP.libs.lib_tsi.tsi import * +from .pr_private_types import * +from .pr_status import PanelReplayStatus +from .pr_config import PanelReplayConfig + + +class PanelReplay: + """ + Class `PanelReplay` contains information about Panel Replay feature. + - Read Panel Replay `status`. + - Configure Panel Replay `config`. + - Disable Panel Replay `disable`. + - Enable Active mode `active_mode`. + - Enable Inactive mode `inactive_mode`. + - Enable selective update `selective_update`. + """ + + def __init__(self, port_io: PortIO, pg_caps): + self.__io = port_io + self.__status = PanelReplayStatus(self.__io) + self.__config = PanelReplayConfig(self.__io, pg_caps) + self.__caps = self.__config.pr_caps + + @property + def status(self) -> PanelReplayStatus: + """ + Returns object of class `PanelReplayStatus` for working with Panel Replay Status. + + Returns: + object of `PanelReplayStatus` type + """ + return self.__status + + @property + def config(self) -> PanelReplayConfig: + """ + Returns object of class `PanelReplayConfig` for configuration PR. + + Returns: + object of `PanelReplayStatus` type + """ + return self.__config + + def disable(self): + """ + Disable Panel Replay + """ + self.__write_pr_control(PrControl.Disable) + + def active_mode(self): + """ + Enable active mode + """ + self.__write_pr_control(PrControl.EnableActiveMode) + + def inactive_mode(self): + """ + Enable inactive mode + """ + self.__write_pr_control(PrControl.EnableInactiveMode) + + def selective_update(self): + """ + Enable selective update mode (if device supports this feature) + """ + if self.__caps.pr_flags().selective_update: + self.__write_pr_control(PrControl.EnableSelectiveUpdate) + + def __write_pr_control(self, command: PrControl): + self.__io.set(TSI_PG_STREAM_SELECT, 0) + self.__io.set(TSI_PG_PR_CTRL_W, command.value) + diff --git a/UniTAP/dev/ports/modules/panel_replay/pr_config.py b/UniTAP/dev/ports/modules/panel_replay/pr_config.py new file mode 100644 index 0000000..c7eeca6 --- /dev/null +++ b/UniTAP/dev/ports/modules/panel_replay/pr_config.py @@ -0,0 +1,132 @@ +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from UniTAP.libs.lib_tsi.tsi import * +from .pr_types import * +from .pr_private_types import * + + +class PanelReplayConfig: + """ + Class `PanelReplayConfig` contains information about Panel Replay Configuration. + - Set configuration object `PrSettings`, function `set`. + - Get configuration object `PrSettings`, function `get`. + """ + + def __init__(self, port_io: PortIO, pg_caps): + self.__io = port_io + self.__y_granularity_list = [e for e in YGranularity] + + self.__mode_list = [] + if pg_caps.flags.panel_replay: + self.__mode_list.append(PRMode.PR) + if pg_caps.flags.psr: + self.__mode_list.append(PRMode.PSR1) + self.__mode_list.append(PRMode.PSR2) + + @property + def pr_caps(self): + return self.__read_caps() + + def set(self, config: PrSettings): + """ + Write new configuration. + + Args: + config (PrSettings). + """ + caps = self.pr_caps + + self.__io.set(TSI_PG_STREAM_SELECT, 0) + pr_config, _ = self.__read_config() + + pr_config.early_transport = config.flags.early_transport if caps.pr_flags().early_transport else 0 + pr_config.crc_in_vsc_sdp = config.flags.crc_in_vsc_sdp if caps.pr_flags().crc_sel_update else 0 + pr_config.hpd_irq_as_sdp = config.flags.hpd_irq + pr_config.hpd_irq_vsc_sdp = config.flags.hpd_irq_vsc_sdp + pr_config.hpd_irq_rfb = config.flags.hpd_irq_rfb + pr_config.hpd_irq_crc = config.flags.hpd_irq_crc + pr_config.self_refresh = config.flags.refresh_rate_unlock + pr_config.ext_y_granularity = config.flags.ext_y_gran + pr_config.main_link_on = config.flags.main_link_remain_on + + if config.flags.y_granularity in self.get_available_y_granularity_values(): + y_gran_value = (caps.pr_flags().y_gran_value & (1 << config.flags.y_granularity.value)) > 0 + pr_config.y_gran_value = 1 << config.flags.y_granularity.value if y_gran_value else 0 + + if config.flags.mode in self.__mode_list: + pr_config.mode = config.flags.mode.value + + data = [pr_config.value] + for item in config.regions: + region = RegionConfig(0) + region.value = item + data.extend(region.value) + self.__io.set(TSI_PG_PR_CFG, data, data_count=len(data)) + + def get(self) -> PrSettings: + """ + Returns current Panel Replay Configuration. + + Returns: + object of `PrSettings` type + """ + pr_config, pr_regions = self.__read_config() + pr_settings = PrSettings() + pr_settings.flags = Flags(early_transport=pr_config.early_transport == 1, + crc_in_vsc_sdp=pr_config.crc_in_vsc_sdp == 1, + pr_mode=PRMode(pr_config.mode), + hpd_irq=pr_config.hpd_irq_as_sdp == 1, + hpd_irq_vsc_sdp=pr_config.hpd_irq_vsc_sdp == 1, + hpd_irq_rfb=pr_config.hpd_irq_rfb == 1, + hpd_irq_crc=pr_config.hpd_irq_crc == 1, + refresh_rate_unlock=pr_config.self_refresh == 1, + ext_y_gran=pr_config.ext_y_granularity == 1, + main_link_remain_on=pr_config.main_link_on == 1) + + value = pr_config.y_gran_value + bit_number = 0 + + while value: + if value & 0x1: + pr_settings.y_granularity = YGranularity(bit_number) + break + bit_number += 1 + value >>= 1 + + pr_settings.regions = pr_regions + return pr_settings + + def get_available_y_granularity_values(self) -> List[YGranularity]: + """ + Get available values for Y Granularity. + + Returns: + list of `YGranularity` type + """ + caps = self.pr_caps + gran_list = [] + + for i, enum_value in enumerate(YGranularity): + if caps.pr_flags().y_gran_value >> i & 0x1: + gran_list.append(enum_value) + + return gran_list + + def __read_config(self): + caps = self.pr_caps + + self.__io.set(TSI_PG_STREAM_SELECT, 0) + result = self.__io.get(TSI_PG_PR_CFG, c_uint32, caps.calculate_size())[1] + if not isinstance(result, list): + result = [0] + pr_config = PrConfig(result[0]) + pr_regions = [] + for i in range(1, len(result) - 1, 2): + pr_regions.append(RegionConfig(result[i] & 0xFFFF, + result[i] >> 16 & 0xFFFF, + result[i + 1] & 0xFFFF, + result[i + 1] >> 16 & 0xFFFF)) + return pr_config, pr_regions + + def __read_caps(self) -> PRCaps: + self.__io.set(TSI_PG_STREAM_SELECT, 0) + return self.__io.get(TSI_PG_PR_CAPS_R, PRCaps)[1] diff --git a/UniTAP/dev/ports/modules/panel_replay/pr_private_types.py b/UniTAP/dev/ports/modules/panel_replay/pr_private_types.py new file mode 100644 index 0000000..b797385 --- /dev/null +++ b/UniTAP/dev/ports/modules/panel_replay/pr_private_types.py @@ -0,0 +1,161 @@ +from ctypes import Structure, c_uint32, c_uint16 +from enum import IntEnum +from .pr_types import Flags, Region + + +class PrControl(IntEnum): + Disable = 0 + EnableInactiveMode = 1 + EnableActiveMode = 2 + EnableSelectiveUpdate = 3 + FullScreenLiveFrameUpdate = 4 + + +class PrConfig(Structure): + _fields_ = [ + ("early_transport", c_uint32, 1), + ("crc_in_vsc_sdp", c_uint32, 1), + ('mode', c_uint32, 2), + ('hpd_irq_as_sdp', c_uint32, 1), + ('hpd_irq_vsc_sdp', c_uint32, 1), + ('hpd_irq_rfb', c_uint32, 1), + ('hpd_irq_crc', c_uint32, 1), + ('self_refresh', c_uint32, 1), + ('ext_y_granularity', c_uint32, 1), + ('main_link_on', c_uint32, 1), + ("y_gran_value", c_uint32, 4), + ('reserved', c_uint32, 17) + ] + + @property + def value(self) -> int: + return (self.early_transport | self.crc_in_vsc_sdp << 1 | self.mode << 3 | self.hpd_irq_as_sdp << 4 | + self.hpd_irq_vsc_sdp << 5 | self.hpd_irq_rfb << 6 | self.hpd_irq_crc << 7 | self.self_refresh << 8 | + self.ext_y_granularity << 9 | self.main_link_on << 10 | self.y_gran_value << 13) + + +class RegionConfig(Structure): + _fields_ = [ + ("x", c_uint32, 16), + ("y", c_uint32, 16), + ('width', c_uint32, 16), + ('height', c_uint32, 16), + ] + + @property + def value(self) -> list: + return [self.x | self.y << 16, self.width | self.height << 16] + + @value.setter + def value(self, config: Region): + self.x = config.x + self.y = config.y + self.width = config.width + self.height = config.height + + +class PatternCaps(IntEnum): + PrNotSupported = 0 + PrOnly = 1 + PrSelectiveUpdate = 2 + PrSelectiveUpdateEarlyTransport = 3 + + +class PRCaps(Structure): + class PRFlags(Structure): + _fields_ = [ + ("selective_update", c_uint32, 1), + ("early_transport", c_uint32, 1), + ("crc_sel_update", c_uint32, 1), + ("y_gran_value", c_uint16, 16), + ('reserved', c_uint32, 13) + ] + + class SuRegions(Structure): + _fields_ = [ + ("number_regions", c_uint32, 8), + ("buffer_capacity", c_uint32, 8), + ('reserved', c_uint32, 16) + ] + + _fields_ = [ + ('flags', c_uint32), + ('su_regions', c_uint32), + ('reserved1', c_uint32, 4), + ('color_bars', c_uint32, 4), + ('chessboard', c_uint32, 4), + ('solid_color', c_uint32, 4), + ('solid_white', c_uint32, 4), + ('solid_red', c_uint32, 4), + ('solid_green', c_uint32, 4), + ('solid_blue', c_uint32, 4), + ('white_v_strips', c_uint32, 4), + ('gradient_h_strips', c_uint32, 4), + ('color_ramp', c_uint32, 4), + ('color_square', c_uint32, 4), + ('motion_pattern', c_uint32, 4), + ('custom', c_uint32, 4), + ('reserved2', c_uint32, 4), + ('square_window', c_uint32, 4), + ('dsc', c_uint32, 4) + ] + + def color_bars_caps(self) -> PatternCaps: + return PatternCaps(self.color_bars & 0x3) + + def chessboard_caps(self) -> PatternCaps: + return PatternCaps(self.chessboard & 0x3) + + def solid_color_caps(self) -> PatternCaps: + return PatternCaps(self.solid_color & 0x3) + + def solid_white_caps(self) -> PatternCaps: + return PatternCaps(self.solid_white & 0x3) + + def solid_red_caps(self) -> PatternCaps: + return PatternCaps(self.solid_red & 0x3) + + def solid_green_caps(self) -> PatternCaps: + return PatternCaps(self.solid_green & 0x3) + + def white_v_strips_caps(self) -> PatternCaps: + return PatternCaps(self.white_v_strips & 0x3) + + def gradient_h_strips_caps(self) -> PatternCaps: + return PatternCaps(self.gradient_h_strips & 0x3) + + def color_ramp_caps(self) -> PatternCaps: + return PatternCaps(self.color_ramp & 0x3) + + def color_square_caps(self) -> PatternCaps: + return PatternCaps(self.color_square & 0x3) + + def motion_pattern_caps(self) -> PatternCaps: + return PatternCaps(self.motion_pattern & 0x3) + + def custom_caps(self) -> PatternCaps: + return PatternCaps(self.custom & 0x3) + + def square_window_caps(self) -> PatternCaps: + return PatternCaps(self.square_window & 0x3) + + def dsc_caps(self) -> PatternCaps: + return PatternCaps(self.dsc & 0x3) + + def calculate_size(self) -> int: + return self.pr_su_regions().number_regions * self.pr_su_regions().buffer_capacity * 2 + 1 + + def pr_flags(self) -> PRFlags: + return self.PRFlags(self.flags & 0x1, (self.flags >> 1) & 0x1, (self.flags >> 2) & 0x1) + + def pr_su_regions(self) -> SuRegions: + return self.SuRegions(self.su_regions & 0xFF, (self.su_regions >> 8) & 0xFF) + + +class PrStatus(Structure): + _fields_ = [ + ("command_status", c_uint32, 8), + ("status", c_uint32, 8), + ('reserved', c_uint32, 8), + ('error', c_uint32, 8) + ] diff --git a/UniTAP/dev/ports/modules/panel_replay/pr_sink.py b/UniTAP/dev/ports/modules/panel_replay/pr_sink.py new file mode 100644 index 0000000..41ef4de --- /dev/null +++ b/UniTAP/dev/ports/modules/panel_replay/pr_sink.py @@ -0,0 +1,59 @@ +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from .pr_sink_status import SinkPanelReplayStatus, SinkPanelSelfRefreshStatus +from .pr_sink_caps import SinkPanelReplayCaps, SinkPanelSelfRefreshCaps + + +class SinkPanelReplay: + + def __init__(self, port_io: PortIO): + self.__io = port_io + self.__status = SinkPanelReplayStatus(self.__io) + self.__caps = SinkPanelReplayCaps(self.__io) + + @property + def status(self) -> SinkPanelReplayStatus: + """ + Returns object of class `SinkPanelReplayStatus` for working with Sink Panel Replay Status. + + Returns: + object of `SinkPanelReplayStatus` type + """ + return self.__status + + @property + def caps(self) -> SinkPanelReplayCaps: + """ + Returns object of class `SinkPanelReplayCaps` for configuration Sink Panel Replay capabilities. + + Returns: + object of `SinkPanelReplayCaps` type + """ + return self.__caps + + +class SinkPanelSelfRefresh: + + def __init__(self, port_io: PortIO): + self.__io = port_io + self.__status = SinkPanelSelfRefreshStatus(self.__io) + self.__caps = SinkPanelSelfRefreshCaps(self.__io) + + @property + def status(self) -> SinkPanelSelfRefreshStatus: + """ + Returns object of class `SinkPanelSelfRefreshStatus` for working with Sink Panel Self Refresh Status. + + Returns: + object of `SinkPanelSelfRefreshStatus` type + """ + return self.__status + + @property + def caps(self) -> SinkPanelSelfRefreshCaps: + """ + Returns object of class `SinkPanelSelfRefreshCaps` for configuration Sink Panel Self Refresh capabilities. + + Returns: + object of `SinkPanelSelfRefreshCaps` type + """ + return self.__caps diff --git a/UniTAP/dev/ports/modules/panel_replay/pr_sink_caps.py b/UniTAP/dev/ports/modules/panel_replay/pr_sink_caps.py new file mode 100644 index 0000000..53a2a0b --- /dev/null +++ b/UniTAP/dev/ports/modules/panel_replay/pr_sink_caps.py @@ -0,0 +1,111 @@ +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from .pr_sink_private_types import SinkPanelReplayCapsStruct, SinkPanelSelfRefreshCapsStruct +from .pr_sink_types import PRCapsFlags, PrGranularityCaps, SuYGranularity, PSRCapsFlags, PSRCaps, PSRSetupTime +from UniTAP.libs.lib_tsi.tsi import * +from typing import Tuple + + +class SinkPanelReplayCaps: + """ + Class `SinkPanelReplayCaps` contains information about Sink Panel Replay capabilities. + - Set configuration with parameters: `PRCapsFlags`, `PrGranularityCaps`, PrGranularityCaps`, `SuYGranularity`. + Function `set`. + - Get configuration: Tuple[`PRCapsFlags`, `PrGranularityCaps`, `PrGranularityCaps`, `SuYGranularity``]. + Function `get`. + """ + + def __init__(self, port_io: PortIO): + self.__io = port_io + + def set(self, flags: PRCapsFlags = PRCapsFlags(), + x_granularity: PrGranularityCaps = PrGranularityCaps.Line_1, + y_granularity: PrGranularityCaps = PrGranularityCaps.Line_1, + granularity: SuYGranularity = SuYGranularity()): + flags_value = (flags.pr_support | (flags.selective_update_support << 1) | (flags.early_transport_support << 2) | + (flags.dsc_decode_func_support << 10) | (flags.asynch_video_timing_support << 11) | + (flags.dsc_src_support << 12) | (flags.granularity_needed << 13) | + (flags.y_gran_extended_supported << 14) | (flags.link_off_supported << 15)) + + self.__write([flags_value, x_granularity.value, y_granularity.value, granularity.combined_value()]) + self.__io.set(TSI_DPRX_HPD_PULSE_W, 500000, c_int) + + def get(self) -> Tuple[PRCapsFlags, PrGranularityCaps, PrGranularityCaps, SuYGranularity]: + caps = self.__read() + + flags = PRCapsFlags(pr_support=caps.pr_support, + selective_update_support=caps.selective_update_support, + early_transport_support=caps.early_transport_support, + dsc_decode_func_support=caps.dsc_decode_func_support, + asynch_video_timing_support=caps.asynch_video_timing_support, + dsc_src_support=caps.dsc_src_support, + granularity_needed=caps.granularity_needed, + y_gran_extended_supported=caps.y_gran_extended_supported, + link_off_supported=caps.link_off_supported) + + x_granularity = PrGranularityCaps(caps.pr_x_granularity if caps.pr_x_granularity != 0 else 1) + y_granularity = PrGranularityCaps(caps.pr_y_granularity if caps.pr_y_granularity != 0 else 1) + + granularity = SuYGranularity(value_of_8=caps.su_y_granularity.value_of_8, + value_of_10=caps.su_y_granularity.value_of_10, + value_of_12=caps.su_y_granularity.value_of_12, + value_of_14=caps.su_y_granularity.value_of_14, + value_of_15=caps.su_y_granularity.value_of_15, + value_of_16=caps.su_y_granularity.value_of_16, + value_of_18=caps.su_y_granularity.value_of_18, + value_of_20=caps.su_y_granularity.value_of_20, + value_of_24=caps.su_y_granularity.value_of_24, + value_of_30=caps.su_y_granularity.value_of_30, + value_of_32=caps.su_y_granularity.value_of_32, + value_of_36=caps.su_y_granularity.value_of_36, + value_of_40=caps.su_y_granularity.value_of_40, + value_of_48=caps.su_y_granularity.value_of_48, + value_of_54=caps.su_y_granularity.value_of_54, + value_of_64=caps.su_y_granularity.value_of_64) + + return flags, x_granularity, y_granularity, granularity + + def __read(self) -> SinkPanelReplayCapsStruct: + return self.__io.get(TSI_DPRX_PR_CAPS, SinkPanelReplayCapsStruct)[1] + + def __write(self, data: list): + self.__io.set(TSI_DPRX_PR_CAPS, data, data_type=c_uint32, data_count=len(data)) + + +class SinkPanelSelfRefreshCaps: + """ + Class `SinkPanelSelfRefreshCaps` contains information about Sink Panel Self Refresh capabilities. + - Set configuration with parameters: `PSRCapsFlags`, `PrGranularityCaps`, PrGranularityCaps`. Function `set`. + - Get configuration: Tuple[`PSRCapsFlags`, `PrGranularityCaps`, `PrGranularityCaps`]. Function `get`. + """ + + def __init__(self, port_io: PortIO): + self.__io = port_io + + def set(self, flags: PSRCapsFlags = PSRCapsFlags(), + x_granularity: PrGranularityCaps = PrGranularityCaps.Line_1, + y_granularity: PrGranularityCaps = PrGranularityCaps.Line_1): + flags_value = (flags.psr_caps.value | (flags.link_training_req_psr1 << 8) | (flags.psr_setup_time.value << 9) | + (flags.y_coor_psr2_su << 12) | (flags.su_coordinates_shall_adhere << 13) | + (flags.no_update_aux_frame_sync << 14)) + + self.__write([flags_value, x_granularity.value, y_granularity.value]) + + def get(self) -> Tuple[PSRCapsFlags, PrGranularityCaps, PrGranularityCaps]: + caps = self.__read() + + flags = PSRCapsFlags(psr_caps=PSRCaps(caps.psr_caps), + link_training_req_psr1=caps.link_training_req_psr1, + psr_setup_time=PSRSetupTime(caps.psr_setup_time), y_coor_psr2_su=caps.y_coor_psr2_su, + su_coordinates_shall_adhere=caps.su_coordinates_shall_adhere, + no_update_aux_frame_sync=caps.no_update_aux_frame_sync) + + x_granularity = PrGranularityCaps(caps.pr_x_granularity if caps.pr_x_granularity != 0 else 1) + y_granularity = PrGranularityCaps(caps.pr_y_granularity if caps.pr_y_granularity != 0 else 1) + + return flags, x_granularity, y_granularity + + def __read(self) -> SinkPanelSelfRefreshCapsStruct: + return self.__io.get(TSI_DPRX_PSR_CAPS, SinkPanelSelfRefreshCapsStruct)[1] + + def __write(self, data: list): + self.__io.set(TSI_DPRX_PSR_CAPS, data, data_type=c_uint32, data_count=len(data)) \ No newline at end of file diff --git a/UniTAP/dev/ports/modules/panel_replay/pr_sink_private_types.py b/UniTAP/dev/ports/modules/panel_replay/pr_sink_private_types.py new file mode 100644 index 0000000..1b7b75b --- /dev/null +++ b/UniTAP/dev/ports/modules/panel_replay/pr_sink_private_types.py @@ -0,0 +1,111 @@ +from ctypes import Structure, c_uint32 + + +class SinkPanelReplayCapsStruct(Structure): + class SuYGranularityStruct(Structure): + _fields_ = [ + ("value_of_8", c_uint32, 1), + ("value_of_10", c_uint32, 1), + ("value_of_12", c_uint32, 1), + ("value_of_14", c_uint32, 1), + ("value_of_15", c_uint32, 1), + ("value_of_16", c_uint32, 1), + ("value_of_18", c_uint32, 1), + ("value_of_20", c_uint32, 1), + ("value_of_24", c_uint32, 1), + ("value_of_30", c_uint32, 1), + ("value_of_32", c_uint32, 1), + ("value_of_36", c_uint32, 1), + ("value_of_40", c_uint32, 1), + ("value_of_48", c_uint32, 1), + ("value_of_54", c_uint32, 1), + ("value_of_64", c_uint32, 1), + ("res", c_uint32, 16), + ] + + _fields_ = [ + ("pr_support", c_uint32, 1), + ("selective_update_support", c_uint32, 1), + ("early_transport_support", c_uint32, 1), + ("res", c_uint32, 7), + ("dsc_decode_func_support", c_uint32, 1), + ("asynch_video_timing_support", c_uint32, 1), + ("dsc_src_support", c_uint32, 1), + ("granularity_needed", c_uint32, 1), + ("y_gran_extended_supported", c_uint32, 1), + ("link_off_supported", c_uint32, 1), + ("res1", c_uint32, 16), + ("pr_x_granularity", c_uint32), + ("pr_y_granularity", c_uint32), + ("su_y_granularity", SuYGranularityStruct) + ] + + +class SinkPanelReplayStatusStruct(Structure): + + class PRStatusDebugStruct(Structure): + _fields_ = [ + ("res", c_uint32, 8), + ("psr_state", c_uint32, 1), + ("res1", c_uint32, 1), + ("crc_valid", c_uint32, 1), + ("su_coordinate_valid", c_uint32, 1), + ("res2", c_uint32, 20), + ] + + _fields_ = [ + ("active_frame_crc_err", c_uint32, 1), + ("rfb_storage_err", c_uint32, 1), + ("vsc_sdp_err", c_uint32, 1), + ("ass_dp_missing", c_uint32, 1), + ("res", c_uint32, 12), + ("self_status", c_uint32, 3), + ("frame_locked", c_uint32, 2), + ("frame_locked_status_valid", c_uint32, 1), + ("res1", c_uint32, 10), + ("debug", PRStatusDebugStruct) + ] + + +class SinkPanelSelfRefreshCapsStruct(Structure): + + _fields_ = [ + ("psr_caps", c_uint32, 8), + ("link_training_req_psr1", c_uint32, 1), + ("psr_setup_time", c_uint32, 3), + ("y_coor_psr2_su", c_uint32, 1), + ("su_coordinates_shall_adhere", c_uint32, 1), + ("no_update_aux_frame_sync", c_uint32, 1), + ("res", c_uint32, 17), + ("pr_x_granularity", c_uint32), + ("pr_y_granularity", c_uint32), + ] + + +class SinkPanelSelfRefreshStatusStruct(Structure): + + class PSRStatusDebugStruct(Structure): + _fields_ = [ + ("min_frame_count_reentry", c_uint32, 1), + ("last_actual_syn_latency", c_uint32, 1), + ("psr_state", c_uint32, 1), + ("update_rfb", c_uint32, 1), + ("crc_valid", c_uint32, 1), + ("su_valid", c_uint32, 1), + ("first_scan_line_su", c_uint32, 1), + ("last_scan_line_su", c_uint32, 1), + ("y_coordinate_valid", c_uint32, 1), + ("res", c_uint32, 17) + ] + + _fields_ = [ + ("link_crc_err", c_uint32, 1), + ("rfb_storage_err", c_uint32, 1), + ("vsc_sdp_err", c_uint32, 1), + ("res", c_uint32, 5), + ("su_psr_caps_change", c_uint32, 1), + ("res1", c_uint32, 1), + ("self_refresh", c_uint32, 3), + ("res2", c_uint32, 13), + ("debugStatus", PSRStatusDebugStruct) + ] diff --git a/UniTAP/dev/ports/modules/panel_replay/pr_sink_status.py b/UniTAP/dev/ports/modules/panel_replay/pr_sink_status.py new file mode 100644 index 0000000..889fccc --- /dev/null +++ b/UniTAP/dev/ports/modules/panel_replay/pr_sink_status.py @@ -0,0 +1,62 @@ +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from .pr_sink_private_types import SinkPanelSelfRefreshStatusStruct, SinkPanelReplayStatusStruct +from .pr_sink_types import (SinkPRStatusFlags, SelfStatus, FrameStatus, SinkPSRStatusFlags, PRSSelfRefreshStatus) +from UniTAP.libs.lib_tsi.tsi import * + + +class SinkPanelReplayStatus: + """ + Class `SinkPanelReplayStatus` contains information about Sink Panel Replay Status. + - Get Flags status `flags`. + - Get Debug status `debug`. + """ + + def __init__(self, port_io: PortIO): + self.__io = port_io + + def flags(self) -> SinkPRStatusFlags: + """ + Returns current flags status. + + Returns: + object of `SinkPRStatusFlags` type + """ + pr_flags = self.__read() + return SinkPRStatusFlags(active_frame_crc_err=pr_flags.active_frame_crc_err, + rfb_storage_err=pr_flags.rfb_storage_err, + vsc_sdp_err=pr_flags.vsc_sdp_err, ass_dp_missing=pr_flags.ass_dp_missing, + self_status=SelfStatus(pr_flags.self_status), + frame_locked=FrameStatus(pr_flags.frame_locked), + frame_locked_status_valid=pr_flags.frame_locked_status_valid) + + def __read(self) -> SinkPanelReplayStatusStruct: + + return self.__io.get(TSI_DPRX_PR_STATUS, SinkPanelReplayStatusStruct)[1] + + +class SinkPanelSelfRefreshStatus: + """ + Class `SinkPanelReplayStatus` contains information about Sink Panel Self Refresh Status. + - Get Flags status `flags`. + - Get Debug status `debug`. + """ + + def __init__(self, port_io: PortIO): + self.__io = port_io + + def flags(self) -> SinkPSRStatusFlags: + """ + Returns current flags status. + + Returns: + object of `SinkPSRStatusFlags` type + """ + pr_flags = self.__read() + return SinkPSRStatusFlags(link_crc_err=pr_flags.link_crc_err, + rfb_storage_err=pr_flags.rfb_storage_err, + vsc_sdp_err=pr_flags.vsc_sdp_err, + su_psr_caps_change=pr_flags.su_psr_caps_change, + self_refresh=PRSSelfRefreshStatus(pr_flags.self_refresh)) + + def __read(self) -> SinkPanelSelfRefreshStatusStruct: + return self.__io.get(TSI_DPRX_PSR_STATUS, SinkPanelSelfRefreshStatusStruct)[1] diff --git a/UniTAP/dev/ports/modules/panel_replay/pr_sink_types.py b/UniTAP/dev/ports/modules/panel_replay/pr_sink_types.py new file mode 100644 index 0000000..d2785b3 --- /dev/null +++ b/UniTAP/dev/ports/modules/panel_replay/pr_sink_types.py @@ -0,0 +1,844 @@ +from enum import IntEnum + + +class SelfStatus(IntEnum): + """ + Class `SelfStatus` contains all possible variants of Sink Panel Replay Self Status. + """ + + Disable = 0 + Inactive = 1 + Entry = 2 + Active = 3 + InternalError = 7 + + def __str__(self): + if self.value == self.Disable: + return "Disable" + elif self.value == self.Inactive: + return "Inactive" + elif self.value == self.Entry: + return "Entry" + elif self.value == self.Active: + return "Active" + elif self.value == self.InternalError: + return "Internal error" + else: + return "Incorrect value" + + +class FrameStatus(IntEnum): + """ + Class `FrameStatus` contains all possible variants of Sink Panel Replay frame locked. + """ + Locked = 0 + Coasting = 1 + Governing = 2 + ReLocking = 3 + + def __str__(self): + if self.value == self.Locked: + return "Locked" + elif self.value == self.Coasting: + return "Coasting" + elif self.value == self.Governing: + return "Governing" + elif self.value == self.ReLocking: + return "Re Locking" + else: + return "Incorrect value" + + +class SinkPRStatusFlags: + + def __init__(self, active_frame_crc_err: bool = False, rfb_storage_err: bool = False, + vsc_sdp_err: bool = False, ass_dp_missing: bool = False, self_status: SelfStatus = SelfStatus.Disable, + frame_locked: FrameStatus = FrameStatus.Locked, frame_locked_status_valid: bool = False): + self.__active_frame_crc_err = active_frame_crc_err + self.__rfb_storage_err = rfb_storage_err + self.__vsc_sdp_err = vsc_sdp_err + self.__ass_dp_missing = ass_dp_missing + self.__self_status = self_status + self.__frame_locked = frame_locked + self.__frame_locked_status_valid = frame_locked_status_valid + + @property + def active_frame_crc_err(self) -> bool: + """ + Returns state of flag active frame crc error . + + Returns: + object of `bool` type + """ + return self.__active_frame_crc_err + + @property + def rfb_storage_err(self) -> bool: + """ + Returns state of flag rfb storage error. + + Returns: + object of `bool` type + """ + return self.__rfb_storage_err + + @property + def vsc_sdp_err(self) -> bool: + """ + Returns state of flag VSC SDP error. + + Returns: + object of `bool` type + """ + return self.__vsc_sdp_err + + @property + def ass_dp_missing(self) -> bool: + """ + Returns state of flag ASSDP missing. + + Returns: + object of `bool` type + """ + return self.__ass_dp_missing + + @property + def self_status(self) -> SelfStatus: + """ + Returns self status. + + Returns: + object of `SelfStatus` type + """ + return self.__self_status + + @property + def frame_locked(self) -> FrameStatus: + """ + Returns frame locked status. + + Returns: + object of `FrameStatus` type + """ + return self.__frame_locked + + @property + def frame_locked_status_valid(self) -> bool: + """ + Returns state of valid frame locked status. + + Returns: + object of `bool` type + """ + return self.__frame_locked_status_valid + + def __str__(self) -> str: + return (f"Active frame CRC error: {self.active_frame_crc_err}\n" + f"RFB Storage error: {self.rfb_storage_err}\n" + f"PR VSC SDP uncorrectable error: {self.vsc_sdp_err}\n" + f"Adaptive sync SDP missing and not disabled: {self.ass_dp_missing}\n" + f"Sink device self refresh status: {self.self_status.__str__()}\n" + f"Sink frame locked: {self.frame_locked.__str__()}\n" + f"Sink frame locked status valid: {self.frame_locked_status_valid}\n") + + +class PRSSelfRefreshStatus(IntEnum): + InActive = 0 + CaptureAndDisplayEntry = 1 + DisplayFromRFBEntry = 2 + CaptureAndDisplayActive = 3 + CaptureAndDisplayExit = 4 + InternalError = 7 + + def __str__(self): + if self.value == self.InActive: + return "Inactive" + elif self.value == self.CaptureAndDisplayEntry: + return "Capture and display (Entry)" + elif self.value == self.DisplayFromRFBEntry: + return "Display from RFB (Entry)" + elif self.value == self.CaptureAndDisplayActive: + return "Capture and display (Active)" + elif self.value == self.CaptureAndDisplayExit: + return "Capture and display (Exit_" + elif self.value == self.InternalError: + return "Internal Error" + else: + return "Incorrect version" + + +class SinkPSRStatusFlags: + + def __init__(self, link_crc_err: bool = False, rfb_storage_err: bool = False, vsc_sdp_err: bool = False, + su_psr_caps_change: bool = False, self_refresh: PRSSelfRefreshStatus = PRSSelfRefreshStatus.InActive): + self.__link_crc_err = link_crc_err + self.__rfb_storage_err = rfb_storage_err + self.__vsc_sdp_err = vsc_sdp_err + self.__su_psr_caps_change = su_psr_caps_change + self.__self_refresh = self_refresh + + @property + def link_crc_err(self) -> bool: + """ + Returns state of link crc error. + + Returns: + object of `bool` type + """ + return self.__link_crc_err + + @property + def rfb_storage_err(self) -> bool: + """ + Returns state of RFB storage error. + + Returns: + object of `bool` type + """ + return self.__rfb_storage_err + + @property + def vsc_sdp_err(self) -> bool: + """ + Returns state of vsc sdp error. + + Returns: + object of `bool` type + """ + return self.__vsc_sdp_err + + @property + def su_psr_caps_change(self) -> bool: + """ + Returns state of su PSR caps change. + + Returns: + object of `bool` type + """ + return self.__su_psr_caps_change + + @property + def self_refresh(self) -> PRSSelfRefreshStatus: + """ + Returns state of self refresh. + + Returns: + object of `bool` type + """ + return self.__self_refresh + + def __str__(self) -> str: + return (f"Link CRC error: {self.link_crc_err}\n" + f"FRB storage error: {self.rfb_storage_err}\n" + f"PSR2 VSC SDP uncorrectable error: {self.vsc_sdp_err}\n" + f"Sink device SU_PSR capability change: {self.su_psr_caps_change}\n" + f"Sink device self-refresh status: {self.self_refresh.__str__()}\n") + + +class PrGranularityCaps(IntEnum): + Line_1 = 1 + Line_2 = 2 + Line_4 = 4 + Line_8 = 8 + Line_16 = 16 + + def __str__(self) -> str: + if self.value == self.Line_1: + return "Line 1" + elif self.value == self.Line_2: + return "Line 2" + elif self.value == self.Line_4: + return "Line 4" + elif self.value == self.Line_8: + return "Line 8" + elif self.value == self.Line_16: + return "Line 16" + else: + return "Incorrect version" + + +class PRCapsFlags: + + def __init__(self, pr_support: bool = False, selective_update_support: bool = False, + early_transport_support: bool = False, dsc_decode_func_support: bool = False, + asynch_video_timing_support: bool = False, dsc_src_support: bool = False, + granularity_needed: bool = False, y_gran_extended_supported: bool = False, + link_off_supported: bool = False): + self.__pr_support = pr_support + self.__selective_update_support = selective_update_support + self.__early_transport_support = early_transport_support + self.__dsc_decode_func_support = dsc_decode_func_support + self.__asynch_video_timing_support = asynch_video_timing_support + self.__dsc_src_support = dsc_src_support + self.__granularity_needed = granularity_needed + self.__y_gran_extended_supported = y_gran_extended_supported + self.__link_off_supported = link_off_supported + + @property + def pr_support(self) -> bool: + """ + Returns PR support state. + + Returns: + object of `bool` type + """ + return self.__pr_support + + @pr_support.setter + def pr_support(self, value: bool): + self.__pr_support = value + + @property + def selective_update_support(self) -> bool: + """ + Returns state of selective update support. + + Returns: + object of `bool` type + """ + return self.__selective_update_support + + @selective_update_support.setter + def selective_update_support(self, value: bool): + self.__selective_update_support = value + + @property + def early_transport_support(self) -> bool: + """ + Returns state of early transport support. + + Returns: + object of `bool` type + """ + return self.__early_transport_support + + @early_transport_support.setter + def early_transport_support(self, value: bool): + self.__early_transport_support = value + + @property + def dsc_decode_func_support(self) -> bool: + """ + Returns state of DSC decode function support. + + Returns: + object of `bool` type + """ + return self.__dsc_decode_func_support + + @dsc_decode_func_support.setter + def dsc_decode_func_support(self, value: bool): + self.__dsc_decode_func_support = value + + @property + def asynch_video_timing_support(self) -> bool: + """ + Returns state of supports Asynchronous Video Timing. + + Returns: + object of `bool` type + """ + return self.__asynch_video_timing_support + + @asynch_video_timing_support.setter + def asynch_video_timing_support(self, value: bool): + self.__asynch_video_timing_support = value + + @property + def dsc_src_support(self) -> bool: + """ + Returns state of DSC CRC of multiple SU regions support. + + Returns: + object of `bool` type + """ + return self.__dsc_src_support + + @dsc_src_support.setter + def dsc_src_support(self, value: bool): + self.__dsc_src_support = value + + @property + def granularity_needed(self) -> bool: + """ + Returns state of Panel Replay Selective Update Granularity Needed. + + Returns: + object of `bool` type + """ + return self.__granularity_needed + + @granularity_needed.setter + def granularity_needed(self, value: bool): + self.__granularity_needed = value + + @property + def y_gran_extended_supported(self) -> bool: + """ + Returns state of SU Y Granularity Extended Capabilities supported. + + Returns: + object of `bool` type + """ + return self.__y_gran_extended_supported + + @y_gran_extended_supported.setter + def y_gran_extended_supported(self, value: bool): + self.__y_gran_extended_supported = value + + @property + def link_off_supported(self) -> bool: + """ + Returns state of Link OFF Supported in PR after Adaptive Sync SDP. + + Returns: + object of `bool` type + """ + return self.__link_off_supported + + @link_off_supported.setter + def link_off_supported(self, value: bool): + self.__link_off_supported = value + + def __str__(self) -> str: + return (f"Panel Replay support: {self.pr_support}\n" + f"Selective Update support: {self.selective_update_support}\n" + f"Early Transport support: {self.early_transport_support}\n" + f"DSC decode functionality support: {self.dsc_decode_func_support}\n" + f"Asynchronous Video Timing support: {self.asynch_video_timing_support}\n" + f"DSC CRC of multiple SU regions support: {self.dsc_src_support}\n" + f"Panel Replay Selective Update granularity needed: {self.granularity_needed}\n" + f"SU Y Granularity Extended Capabilities supported: {self.y_gran_extended_supported}\n" + f"Link OFF Supported in PR after Adaptive Sync SDP: {self.link_off_supported}\n") + + +class SuYGranularity: + + def __init__(self, value_of_8: bool = False, value_of_10: bool = False, value_of_12: bool = False, + value_of_14: bool = False, value_of_15: bool = False, value_of_16: bool = False, + value_of_18: bool = False, value_of_20: bool = False, value_of_24: bool = False, + value_of_30: bool = False, value_of_32: bool = False, value_of_36: bool = False, + value_of_40: bool = False, value_of_48: bool = False, value_of_54: bool = False, + value_of_64: bool = False): + self.__value_of_8 = value_of_8 + self.__value_of_10 = value_of_10 + self.__value_of_12 = value_of_12 + self.__value_of_14 = value_of_14 + self.__value_of_15 = value_of_15 + self.__value_of_16 = value_of_16 + self.__value_of_18 = value_of_18 + self.__value_of_20 = value_of_20 + self.__value_of_24 = value_of_24 + self.__value_of_30 = value_of_30 + self.__value_of_32 = value_of_32 + self.__value_of_36 = value_of_36 + self.__value_of_40 = value_of_40 + self.__value_of_48 = value_of_48 + self.__value_of_54 = value_of_54 + self.__value_of_64 = value_of_64 + + @property + def value_of_8(self) -> bool: + """ + Supported Y granularity value of 8. + + Returns: + object of `bool` type + """ + return self.__value_of_8 + + @value_of_8.setter + def value_of_8(self, value: bool): + self.__value_of_8 = value + + @property + def value_of_10(self) -> bool: + """ + Supported Y granularity value of 10. + + Returns: + object of `bool` type + """ + return self.__value_of_10 + + @value_of_10.setter + def value_of_10(self, value: bool): + self.__value_of_10 = value + + @property + def value_of_12(self) -> bool: + """ + Supported Y granularity value of 12. + + Returns: + object of `bool` type + """ + return self.__value_of_12 + + @value_of_12.setter + def value_of_12(self, value: bool): + self.__value_of_12 = value + + @property + def value_of_14(self) -> bool: + """ + Supported Y granularity value of 14. + + Returns: + object of `bool` type + """ + return self.__value_of_14 + + @value_of_14.setter + def value_of_14(self, value: bool): + self.__value_of_14 = value + + @property + def value_of_15(self) -> bool: + """ + Supported Y granularity value of 15. + + Returns: + object of `bool` type + """ + return self.__value_of_15 + + @value_of_15.setter + def value_of_15(self, value: bool): + self.__value_of_15 = value + + @property + def value_of_16(self) -> bool: + """ + Supported Y granularity value of 16. + + Returns: + object of `bool` type + """ + return self.__value_of_16 + + @value_of_16.setter + def value_of_16(self, value: bool): + self.__value_of_16 = value + + @property + def value_of_18(self) -> bool: + """ + Supported Y granularity value of 18. + + Returns: + object of `bool` type + """ + return self.__value_of_18 + + @value_of_18.setter + def value_of_18(self, value: bool): + self.__value_of_18 = value + + @property + def value_of_20(self) -> bool: + """ + Supported Y granularity value of 20. + + Returns: + object of `bool` type + """ + return self.__value_of_20 + + @value_of_20.setter + def value_of_20(self, value: bool): + self.__value_of_20 = value + + @property + def value_of_24(self) -> bool: + """ + Supported Y granularity value of 24. + + Returns: + object of `bool` type + """ + return self.__value_of_24 + + @value_of_24.setter + def value_of_24(self, value: bool): + self.__value_of_24 = value + + @property + def value_of_30(self) -> bool: + """ + Supported Y granularity value of 30. + + Returns: + object of `bool` type + """ + return self.__value_of_30 + + @value_of_30.setter + def value_of_30(self, value: bool): + self.__value_of_30 = value + + @property + def value_of_32(self) -> bool: + """ + Supported Y granularity value of 32. + + Returns: + object of `bool` type + """ + return self.__value_of_32 + + @value_of_32.setter + def value_of_32(self, value: bool): + self.__value_of_32 = value + + @property + def value_of_36(self) -> bool: + """ + Supported Y granularity value of 36. + + Returns: + object of `bool` type + """ + return self.__value_of_36 + + @value_of_36.setter + def value_of_36(self, value: bool): + self.__value_of_36 = value + + @property + def value_of_40(self) -> bool: + """ + Supported Y granularity value of 40. + + Returns: + object of `bool` type + """ + return self.__value_of_40 + + @value_of_40.setter + def value_of_40(self, value: bool): + self.__value_of_40 = value + + @property + def value_of_48(self) -> bool: + """ + Supported Y granularity value of 48. + + Returns: + object of `bool` type + """ + return self.__value_of_48 + + @value_of_48.setter + def value_of_48(self, value: bool): + self.__value_of_48 = value + + @property + def value_of_54(self) -> bool: + """ + Supported Y granularity value of 54. + + Returns: + object of `bool` type + """ + return self.__value_of_54 + + @value_of_54.setter + def value_of_54(self, value: bool): + self.__value_of_54 = value + + @property + def value_of_64(self) -> bool: + """ + Supported Y granularity value of 64. + + Returns: + object of `bool` type + """ + return self.__value_of_64 + + @value_of_64.setter + def value_of_64(self, value: bool): + self.__value_of_64 = value + + def combined_value(self) -> int: + return (self.value_of_8 | (self.value_of_10 << 1) | (self.value_of_12 << 2) | (self.value_of_14 << 3) | + (self.value_of_15 << 4) | (self.value_of_16 << 5) | (self.value_of_18 << 6) | (self.value_of_20 << 7) | + (self.value_of_24 << 8) | (self.value_of_30 << 9) | (self.value_of_32 << 10) | + (self.value_of_36 << 11) | (self.value_of_40 << 12) | (self.value_of_48 << 13) | + (self.value_of_54 << 14) | (self.value_of_64 << 15)) + + def __str__(self) -> str: + return (f"Value of 8: {self.value_of_8}\n" + f"Value of 10: {self.value_of_10}\n" + f"Value of 12: {self.value_of_12}\n" + f"Value of 14: {self.value_of_14}\n" + f"Value of 15: {self.value_of_15}\n" + f"Value of 16: {self.value_of_16}\n" + f"Value of 18: {self.value_of_18}\n" + f"Value of 20: {self.value_of_20}\n" + f"Value of 24: {self.value_of_24}\n" + f"Value of 30: {self.value_of_30}\n" + f"Value of 32: {self.value_of_32}\n" + f"Value of 36: {self.value_of_36}\n" + f"Value of 40: {self.value_of_40}\n" + f"Value of 48: {self.value_of_48}\n" + f"Value of 54: {self.value_of_54}\n" + f"Value of 64: {self.value_of_64}\n" + ) + + +class PSRCaps(IntEnum): + NotSupported = 0, + PSR1Supported = 1, + PSR2SUSupported = 2, + PSR2YcoorSupported = 3, + PSR2EarlyTransportSupported = 4 + + def __str__(self) -> str: + if self.value == self.NotSupported: + return "Not supported" + elif self.value == self.PSR1Supported: + return "PSR 1 supported" + elif self.value == self.PSR2SUSupported: + return "PSR 2 SU supported" + elif self.value == self.PSR2YcoorSupported: + return "PSR 2 SU and Y coordinates supported" + elif self.value == self.PSR2EarlyTransportSupported: + return "PSR 2 SU, Y coordinates and Early Transport supported" + else: + return "Incorrect version" + + +class PSRSetupTime(IntEnum): + T_330us = 0, + T_275us = 1, + T_220us = 2, + T_165us = 3, + T_110us = 4, + T_55us = 5, + T_0us = 6, + T_Reserved = 7 + + def __str__(self) -> str: + if self.value == self.T_330us: + return "330 us" + elif self.value == self.T_275us: + return "275 us" + elif self.value == self.T_220us: + return "220 us" + elif self.value == self.T_165us: + return "165 us" + elif self.value == self.T_110us: + return "110 us" + elif self.value == self.T_55us: + return "55 us" + elif self.value == self.T_0us: + return "0 us" + else: + return "Reserved" + + +class PSRCapsFlags: + + def __init__(self, psr_caps: PSRCaps = PSRCaps.NotSupported, link_training_req_psr1: bool = False, + psr_setup_time: PSRSetupTime = PSRSetupTime.T_330us, y_coor_psr2_su: bool = False, + su_coordinates_shall_adhere: bool = False, no_update_aux_frame_sync: bool = False): + self.__psr_caps = psr_caps + self.__link_training_req_psr1 = link_training_req_psr1 + self.__psr_setup_time = psr_setup_time + self.__y_coor_psr2_su = y_coor_psr2_su + self.__su_coordinates_shall_adhere = su_coordinates_shall_adhere + self.__no_update_aux_frame_sync = no_update_aux_frame_sync + + @property + def psr_caps(self) -> PSRCaps: + """ + Returns PSR caps value from enum. + + Returns: + object of `PSRCaps` type + """ + return self.__psr_caps + + @psr_caps.setter + def psr_caps(self, value: PSRCaps): + self.__psr_caps = value + + @property + def link_training_req_psr1(self) -> bool: + """ + Returns link training request PSR1 flag. + + Returns: + object of `bool` type + """ + return self.__link_training_req_psr1 + + @link_training_req_psr1.setter + def link_training_req_psr1(self, value: bool): + self.__link_training_req_psr1 = value + + @property + def psr_setup_time(self) -> PSRSetupTime: + """ + Returns PSR setup time value. + + Returns: + object of `PSRSetupTime` type + """ + return self.__psr_setup_time + + @psr_setup_time.setter + def psr_setup_time(self, value: PSRSetupTime): + self.__psr_setup_time = value + + @property + def y_coor_psr2_su(self) -> bool: + """ + Returns flag for "Y-coordinate for PSR2 SU". + + Returns: + object of `bool` type + """ + return self.__y_coor_psr2_su + + @y_coor_psr2_su.setter + def y_coor_psr2_su(self, value: bool): + self.__y_coor_psr2_su = value + + @property + def su_coordinates_shall_adhere(self) -> bool: + """ + Returns flag for "SU coordinates shall adhere" + + Returns: + object of `bool` type + """ + return self.__su_coordinates_shall_adhere + + @su_coordinates_shall_adhere.setter + def su_coordinates_shall_adhere(self, value: bool): + self.__su_coordinates_shall_adhere = value + + @property + def no_update_aux_frame_sync(self) -> bool: + """ + Returns value for flag "Do not update AUX FRAME SYNC". + + Returns: + object of `bool` type + """ + return self.__no_update_aux_frame_sync + + @no_update_aux_frame_sync.setter + def no_update_aux_frame_sync(self, value: bool): + self.__no_update_aux_frame_sync = value + + def __str__(self) -> str: + return (f"Panel Self Refresh supported: {self.psr_caps.__str__()}\n" + f"Link Training Requirement of Sink: {self.link_training_req_psr1}\n" + f"PSR Setup time: {self.psr_setup_time.__str__()}\n" + f"Y-coordinate Requirement of Sink Device: {self.y_coor_psr2_su}\n" + f"Panel Self Refresh 2 Selective Update Granularity Required: {self.su_coordinates_shall_adhere}\n" + f"Frame Sync Function Is Not Needed for Selective Update: {self.no_update_aux_frame_sync}\n") diff --git a/UniTAP/dev/ports/modules/panel_replay/pr_status.py b/UniTAP/dev/ports/modules/panel_replay/pr_status.py new file mode 100644 index 0000000..4ee3170 --- /dev/null +++ b/UniTAP/dev/ports/modules/panel_replay/pr_status.py @@ -0,0 +1,48 @@ +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from .pr_private_types import * +from UniTAP.libs.lib_tsi.tsi import * +from .pr_types import * + + +class PanelReplayStatus: + """ + Class `PanelReplayStatus` contains information about Panel Replay Status. + - Get PR command `command`. + - Get current state (enabled/disabled) `status`. + - Get PR Error `error`. + """ + + def __init__(self, port_io: PortIO): + self.__io = port_io + + def command(self) -> PRCommand: + """ + Returns current command of PR. + + Returns: + object of `PRCommand` type + """ + return PRCommand(self.__read().command_status) + + def status(self) -> PRStatus: + """ + Returns current status of PR (enabled/disabled). + + Returns: + object of `PRStatus` type + """ + return PRStatus(self.__read().status) + + def error(self) -> PRError: + """ + Returns current error of PR. + + Returns: + object of `PRError` type + """ + return PRError(self.__read().error) + + def __read(self) -> PrStatus: + self.__io.set(TSI_PG_STREAM_SELECT, 0) + return self.__io.get(TSI_PG_PR_STATUS_R, PrStatus)[1] + diff --git a/UniTAP/dev/ports/modules/panel_replay/pr_types.py b/UniTAP/dev/ports/modules/panel_replay/pr_types.py new file mode 100644 index 0000000..f1e2610 --- /dev/null +++ b/UniTAP/dev/ports/modules/panel_replay/pr_types.py @@ -0,0 +1,399 @@ +from enum import IntEnum +from typing import List + + +class PRCommand(IntEnum): + """ + Class `PRCommand` contains all possible variants of Panel Replay Command. + """ + + No = 0 + EnablingInactive = 1 + EnablingActive = 2 + RFB = 3 + + def __str__(self): + if self.value == self.No: + return "No command under execution." + if self.value == self.EnablingInactive: + return "Enabling inactive mode is in progress." + if self.value == self.EnablingActive: + return "Enabling active mode is in progress." + if self.value == self.RFB: + return "RFB selective update is in progress." + + +class PRStatus(IntEnum): + """ + Class `PRStatus` contains all possible variants of Panel Replay Status. + """ + + Disabled = 0 + EnabledInactive = 1 + EnabledActive = 2 + + def __str__(self): + if self.value == self.Disabled: + return "Disabled." + if self.value == self.EnabledInactive: + return "Enabled in an inactive mode." + if self.value == self.EnabledActive: + return "Enabled in an active mode." + + +class PRError(IntEnum): + """ + Class `PRError` contains all possible variants of Panel Replay Error type. + """ + + No = 0 + Internal = 1 + IncorrectCommandReceived = 2 + FailedAccess = 3 + LTFailed = 4 + SelUpdIncorrect = 5 + + def __str__(self): + if self.value == self.No: + return "No errors." + if self.value == self.Internal: + return "Internal error." + if self.value == self.IncorrectCommandReceived: + return "Incorrect command received." + if self.value == self.FailedAccess: + return "Failed to access DPCD." + if self.value == self.LTFailed: + return "LinkTraining failed." + if self.value == self.SelUpdIncorrect: + return "Selective update config is incorrect." + + +class PRMode(IntEnum): + """ + Class `PRMode` contains all possible variants of Panel Replay mode. + """ + PR = 0 + PSR1 = 1 + PSR2 = 2 + Res = 3 + + +class YGranularity(IntEnum): + """ + Class `YGranularity` contains all possible variants of SU Y granularity. + """ + Value_8 = 0 + Value_10 = 1 + Value_12 = 2 + Value_14 = 3 + Value_15 = 4 + Value_16 = 5 + Value_18 = 6 + Value_20 = 7 + Value_24 = 8 + Value_30 = 9 + Value_32 = 10 + Value_36 = 11 + Value_40 = 12 + Value_48 = 13 + Value_54 = 14 + Value_64 = 15 + + +class Region: + """ + Class `Region` contains information about each PR Region (Frame). + - Set and get X coordinate 'x'. + - Set and get Y coordinate 'y'. + - Set and get Width 'width'. + - Set and get Height `height` + """ + + def __init__(self, x: int = 0, y: int = 0, width: int = 0, height: int = 0): + self.__x = x + self.__y = y + self.__width = width + self.__height = height + + @property + def x(self) -> int: + """ + Returns current X coordinate value. + + Returns: + object of `int` type + """ + return self.__x + + @x.setter + def x(self, value: int): + self.__x = value + + @property + def y(self) -> int: + """ + Returns current Y coordinate value. + + Returns: + object of `int` type + """ + return self.__y + + @y.setter + def y(self, value: int): + self.__y = value + + @property + def width(self) -> int: + """ + Returns current Width value. + + Returns: + object of `int` type + """ + return self.__width + + @width.setter + def width(self, value: int): + self.__width = value + + @property + def height(self) -> int: + """ + Returns current Height value. + + Returns: + object of `int` type + """ + return self.__height + + @height.setter + def height(self, value: int): + self.__height = value + + +class Flags: + """ + Class `Flags` contains information about Panel Replay flags capabilities. + - Set and get `early_transport` Early transport during selective update is supported. + - Set and get `crc_in_vsc_sdp`. CRC for selective update region/full active frame in VSC SDP is supported. + - Set and get `mode`. PanelReplay mode. + - Set and get `hpd_irq`. Generate HPD_IRQ with Adaptive-Sync SDP missing (PR). + - Set and get `hpd_irq_vsc_sdp`. IRQ_HPD with VSC SDP uncorrectable error (PR). + - Set and get `hpd_irq_rfb`. IRQ_HPD with RFB storage error (PR). + - Set and get `hpd_irq_crc`. IRQ_HPD with active frame CRC error. + - Set and get `refresh_rate_unlock`. Sink refresh rate unlock granted. + - Set and get `ext_y_gran`. Use SU extended Y granularity (PSR2 and PR). + - Set and get `main_link_remain_on`. Main-link remains ON during PSR1/PSR2 active states. + - Set and get `y_granularity`. SU Y granularity extended value selection. + """ + + def __init__(self, early_transport: bool = False, crc_in_vsc_sdp: bool = False, pr_mode: PRMode = PRMode.PR, + hpd_irq: bool = False, hpd_irq_vsc_sdp: bool = False, hpd_irq_rfb: bool = False, + hpd_irq_crc: bool = False, refresh_rate_unlock: bool = False, ext_y_gran: bool = False, + main_link_remain_on: bool = False, y_granularity: YGranularity = YGranularity.Value_8): + self.__early_transport = early_transport + self.__crc_in_vsc_sdp = crc_in_vsc_sdp + self.__mode = pr_mode + self.__hpd_irq = hpd_irq + self.__hpd_irq_vsc_sdp = hpd_irq_vsc_sdp + self.__hpd_irq_rfb = hpd_irq_rfb + self.__hpd_irq_crc = hpd_irq_crc + self.__refresh_rate_unlock = refresh_rate_unlock + self.__ext_y_gran = ext_y_gran + self.__main_link_remain_on = main_link_remain_on + self.__y_granularity = y_granularity + + @property + def early_transport(self) -> bool: + """ + Returns current early transport value. + + Returns: + object of `bool` type + """ + return self.__early_transport + + @early_transport.setter + def early_transport(self, value: bool): + self.__early_transport = value + + @property + def crc_in_vsc_sdp(self) -> bool: + """ + Returns current crc in vsc sdp value. + + Returns: + object of `bool` type + """ + return self.__crc_in_vsc_sdp + + @crc_in_vsc_sdp.setter + def crc_in_vsc_sdp(self, value: bool): + self.__crc_in_vsc_sdp = value + + @property + def mode(self) -> PRMode: + """ + Returns current PR mode. + + Returns: + object of `PRMode` type + """ + return self.__mode + + @mode.setter + def mode(self, value: PRMode): + self.__mode = value + + @property + def hpd_irq(self) -> bool: + """ + Returns current state of HPD_IRQ with Adaptive-Sync SDP missing (PR). + + Returns: + object of `bool` type + """ + return self.__hpd_irq + + @hpd_irq.setter + def hpd_irq(self, value: bool): + self.__hpd_irq = value + + @property + def hpd_irq_vsc_sdp(self) -> bool: + """ + Returns current state of IRQ_HPD with VSC SDP uncorrectable error (PR). + + Returns: + object of `bool` type + """ + return self.__hpd_irq_vsc_sdp + + @hpd_irq_vsc_sdp.setter + def hpd_irq_vsc_sdp(self, value: bool): + self.__hpd_irq_vsc_sdp = value + + @property + def hpd_irq_rfb(self) -> bool: + """ + Returns current state of IRQ_HPD with RFB storage error (PR). + + Returns: + object of `bool` type + """ + return self.__hpd_irq_rfb + + @hpd_irq_rfb.setter + def hpd_irq_rfb(self, value: bool): + self.__hpd_irq_rfb = value + + @property + def hpd_irq_crc(self) -> bool: + """ + Returns current state of IRQ_HPD with active frame CRC error. + + Returns: + object of `bool` type + """ + return self.__hpd_irq_crc + + @hpd_irq_crc.setter + def hpd_irq_crc(self, value: bool): + self.__hpd_irq_crc = value + + @property + def refresh_rate_unlock(self) -> bool: + """ + Returns current state of Sink refresh rate unlock granted. + + Returns: + object of `bool` type + """ + return self.__refresh_rate_unlock + + @refresh_rate_unlock.setter + def refresh_rate_unlock(self, value: bool): + self.__refresh_rate_unlock = value + + @property + def ext_y_gran(self) -> bool: + """ + Returns current state of SU extended Y granularity (PSR2 and PR). + + Returns: + object of `bool` type + """ + return self.__ext_y_gran + + @ext_y_gran.setter + def ext_y_gran(self, value: bool): + self.__ext_y_gran = value + + @property + def main_link_remain_on(self) -> bool: + """ + Returns current state of Main-link remains ON during PSR1/PSR2 active states. + + Returns: + object of `bool` type + """ + return self.__main_link_remain_on + + @main_link_remain_on.setter + def main_link_remain_on(self, value: bool): + self.__main_link_remain_on = value + + @property + def y_granularity(self) -> YGranularity: + """ + Returns current value of SU Y granularity. + + Returns: + object of `YGranularity` type + """ + return self.__y_granularity + + @y_granularity.setter + def y_granularity(self, y_granularity: YGranularity): + self.__y_granularity = y_granularity + + +class PrSettings: + """ + Class `PrSettings` contains information about Panel Replay capabilities. + - Set and get `flags` Flags capabilities. + - Set and get `regions`. Regions capabilities. + """ + + def __init__(self): + self.__regions: [Region] = [] + self.__flags: Flags = Flags() + + @property + def flags(self) -> Flags: + """ + Returns current Flags caps. + + Returns: + object of `Flags` type + """ + return self.__flags + + @flags.setter + def flags(self, value: Flags): + self.__flags = value + + @property + def regions(self) -> List[Region]: + """ + Returns current Regions caps. + + Returns: + object of `Region` list type + """ + return self.__regions + + @regions.setter + def regions(self, value: List[Region]): + self.__regions = value diff --git a/UniTAP/dev/ports/modules/pdc/__init__.py b/UniTAP/dev/ports/modules/pdc/__init__.py new file mode 100644 index 0000000..65693a9 --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/__init__.py @@ -0,0 +1,6 @@ +from .pdo import Pdo +from .pdc_types import ExternalResistance, InternalResistance, ContractTypePriority, DifferentialPair, \ + CableControlOrientation, DRPTryMode, USB3Mode, PDMode, PdcDeviceRole, FrSwapCurrent, PowerRole, CCPullUp +from .pdo_types import PdoSide, PeakCurrent, PdoTypeEnum, FixedPdoSink, FixedPdoSource, BatteryPdo, VariablePdo +from .pdc_dp_alt_mode import PinAssignment +from .pdc_dpam_types import PinAssignmentModes, DPAMVersion diff --git a/UniTAP/dev/ports/modules/pdc/pdc_bus_status.py b/UniTAP/dev/ports/modules/pdc/pdc_bus_status.py new file mode 100644 index 0000000..ed933f8 --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/pdc_bus_status.py @@ -0,0 +1,111 @@ +from .pdc_io import PDCPortIO + + +class BusElectricalStatus: + + """ + Class `BusElectricalStatus` contains information about current BUU Electrical status. + - Get VBus voltage `vbus_voltage`, type `int`. + - Get VBus current `vbus_current`, type `int`. + - Get CC1 voltage `cc1_voltage`, type `int`. + - Get CC2 voltage `cc2_voltage`, type `int`. + - Get VCONN voltage `vconn_voltage`, type `int`. + - Get VCONN current `vconn_current`, type `int`. + - Get SBU-1 voltage `sbu_1_voltage`, type `int`. + - Get SBU-2 voltage `sbu_2_voltage`, type `int`. + """ + + def __init__(self, pdc_io: PDCPortIO): + self.__io = pdc_io + + @property + def vbus_voltage(self) -> int: + """ + Returns current VBus voltage. + + Returns: + object of `int` type + """ + return self.__io.read_adc_vbus_status()[0] + + @property + def vbus_current(self) -> int: + """ + Returns current VBus current. + + Returns: + object of `int` type + """ + return self.__io.read_adc_vbus_status()[1] + + @property + def cc1_voltage(self) -> int: + """ + Returns current CC1 voltage. + + Returns: + object of `int` type + """ + return self.__io.read_adc_vbus_status()[2] + + @property + def cc2_voltage(self) -> int: + """ + Returns current CC2 voltage. + + Returns: + object of `int` type + """ + return self.__io.read_adc_vbus_status()[3] + + @property + def vconn_voltage(self) -> int: + """ + Returns current VCONN voltage. + + Returns: + object of `int` type + """ + return self.__io.read_adc_vbus_status()[4] + + @property + def vconn_current(self) -> int: + """ + Returns current VCONN current. + + Returns: + object of `int` type + """ + return self.__io.read_adc_vbus_status()[5] + + @property + def sbu_1_voltage(self) -> int: + """ + Returns current SBU-1 voltage. + + Returns: + object of `int` type + """ + return self.__io.read_adc_vbus_status()[6] + + @property + def sbu_2_voltage(self) -> int: + """ + Returns current SBU-2 voltage. + + Returns: + object of `int` type + """ + return self.__io.read_adc_vbus_status()[7] + + def __str__(self) -> str: + electrical_status = self.__io.read_adc_vbus_status() + return f"Bus Electrical Status\n" \ + f"VBus current: {electrical_status[0]}\n" \ + f"VBus voltage: {electrical_status[1]}\n" \ + f"CC1 voltage: {electrical_status[2]}\n" \ + f"CC2 voltage: {electrical_status[3]}\n" \ + f"VCONN voltage: {electrical_status[4]}\n" \ + f"VCONN current: {electrical_status[5]}\n" \ + f"SBU-1 voltage: {electrical_status[6]}\n" \ + f"SBU-2 voltage: {electrical_status[7]}\n" diff --git a/UniTAP/dev/ports/modules/pdc/pdc_capabilities.py b/UniTAP/dev/ports/modules/pdc/pdc_capabilities.py new file mode 100644 index 0000000..64813bd --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/pdc_capabilities.py @@ -0,0 +1,340 @@ +from .pdc_io import PDCPortIO, PDCControlCommand, HWControlCommand +from .pdc_types import PdcDeviceRole, CCPullUp, DRPTryMode, USB3Mode, PowerRole +from .pdc_utils import support_or_not + + +class PcdCapsStatus: + + """ + Class `PcdCapsStatus` contains information about PDC capabilities. + - Get initial device role `initial_role`, type `PdcDeviceRole`. + - Get data device role `data_role`, type `PdcDeviceRole`. + - Get power device role `power_role`, type `PowerRole`. + - Get Cable Control Pull Up `cc_pull_up`, type `CCPullUp`. + - Get current behavior, DRP try mode `behavior`, type `DRPTryMode`. + - Get state of PR Swap `pr_swap`, type `bool`. + - Get state of DR Swap `dr_swap`, type `bool`. + - Get state of FR Swap `fr_swap`, type `bool`. + - Get state of VCONN Swap `vconn_swap`, type `bool`. + - Get state of Debug accessory `debug_accessory`, type `bool`. + - Get state of Audio accessory `audio_accessory`, type `bool`. + """ + + def __init__(self, pdc_io: PDCPortIO): + self.__io = pdc_io + + def initial_role(self) -> PdcDeviceRole: + """ + Returns current initial role. + + Returns: + object of `PdcDeviceRole` type + """ + return PdcDeviceRole(self.__io.read_pdc_state().dev_role) + + def data_role(self) -> PdcDeviceRole: + """ + Returns current data role. + + Returns: + object of `PdcDeviceRole` type + """ + return PdcDeviceRole(self.__io.read_pdc_status().data_role) + + def power_role(self) -> PowerRole: + """ + Returns current power role. + + Returns: + object of `PowerRole` type + """ + return PowerRole(self.__io.read_pdc_status().power_role) + + def cc_pull_up(self) -> CCPullUp: + """ + Returns current cable control pull up. + + Returns: + object of `CCPullUp` type + """ + return CCPullUp(self.__io.read_pdc_state().cc_pull_up) + + def behavior(self) -> DRPTryMode: + """ + Returns current behavior of DPR mode. + + Returns: + object of `DRPTryMode` type + """ + return DRPTryMode(self.__io.read_pdc_state().drp_try_mode) + + def pr_swap(self) -> bool: + """ + Returns current state of PR Swap. + + Returns: + object of `bool` type + """ + return bool(self.__io.read_pdc_state().pr_swap_en) + + def dr_swap(self) -> bool: + """ + Returns current state of DR Swap. + + Returns: + object of `bool` type + """ + return bool(self.__io.read_pdc_state().dr_swap_en) + + def fr_swap(self) -> bool: + """ + Returns current state of FR Swap. + + Returns: + object of `bool` type + """ + return bool(self.__io.read_pdc_state().fr_swap_en) + + def vconn_swap(self) -> bool: + """ + Returns current state of VCONN Swap. + + Returns: + object of `bool` type + """ + return bool(self.__io.read_pdc_state().vconn_swap_en) + + def debug_accessory(self) -> bool: + """ + Returns current state of Debug accessory. + + Returns: + object of `bool` type + """ + return bool(self.__io.read_pdc_state().dbg_accessory) + + def audio_accessory(self) -> bool: + """ + Returns current state of Audio accessory. + + Returns: + object of `bool` type + """ + return bool(self.__io.read_pdc_state().aud_accessory) + + def __str__(self): + status = self.__io.read_pdc_state() + return f"PDC Capabilities:\n" \ + f"Initial role: {PdcDeviceRole(status.dev_role).name}\n" \ + f"Power role: {self.power_role().name}\n" \ + f"Data role: {self.data_role().name}\n" \ + f"CC Pull-Up: {CCPullUp(status.cc_pull_up).name}\n" \ + f"Try behavior: {DRPTryMode(status.drp_try_mode).name}\n" \ + f"PR Swap enabled: {support_or_not(status.pr_swap_en)}\n" \ + f"DR Swap enabled: {support_or_not(status.dr_swap_en)}\n" \ + f"FR Swap enabled: {support_or_not(status.fr_swap_en)}\n" \ + f"VCONN Swap enabled: {support_or_not(status.vconn_swap_en)}\n" \ + f"Debug accessory enabled: {support_or_not(status.dbg_accessory)}\n" \ + f"Audio accessory enabled: {support_or_not(status.aud_accessory)}\n" + + +class PcdCapsStatus340(PcdCapsStatus): + + """ + Class `PcdCapsStatus340` inherited of class`PcdCapsStatus` allows working with PDC. + Class `PcdCapsStatus340` has all the `PcdCapsStatus` functionality. + - Get state of USB 2.0 mode `usb20_mode`, type `bool`. + - Get state of UCd 3.0 mode `usb30_mode`, type `USB3Mode`. + """ + + def __init__(self, pdc_io: PDCPortIO): + super().__init__(pdc_io) + + def usb20_mode(self) -> bool: + """ + Returns current state of USB 2.0 mode. + + Returns: + object of `bool` type + """ + return bool(self.__io.read_pdc_state().usb2_enabled) + + def usb30_mode(self) -> USB3Mode: + """ + Returns current state of USB 3.0 mode. + + Returns: + object of `USB3Mode` type + """ + return USB3Mode(self.__io.read_pdc_state().usb30_mode) + + def __str__(self): + status = self.__io.read_pdc_state() + return f"PDC Capabilities:" \ + f"Initial role: {PdcDeviceRole(status.dev_role).name}\n" \ + f"CC Pull-Up: {CCPullUp(status.cc_pull_up).name}\n" \ + f"Try behavior: {DRPTryMode(status.drp_try_mode).name}\n" \ + f"USB 2 enabled: {support_or_not(status.usb2_enabled)}\n" \ + f"USB 3: {USB3Mode(status.usb30_mode).name}\n" \ + f"PR Swap enabled: {support_or_not(status.pr_swap_en)}\n" \ + f"DR Swap enabled: {support_or_not(status.dr_swap_en)}\n" \ + f"FR Swap enabled: {support_or_not(status.fr_swap_en)}\n" \ + f"VCONN Swap enabled: {support_or_not(status.vconn_swap_en)}\n" \ + f"Debug accessory enabled: {support_or_not(status.dbg_accessory)}\n" \ + f"Audio accessory enabled: {support_or_not(status.aud_accessory)}\n" + + +class PdcCapabilities: + + """ + Class `PdcCapabilities` allows controlling PDC capabilities. + - Set device initial role `set_initial_role`. + - Enable/Disable PR Swap `enable_pr_swap`. + - Enable/Disable DR Swap `enable_dr_swap`. + - Enable/Disable FR Swap `enable_fr_swap`. + - Enable/Disable VCONN Swap `enable_vconn_swap`. + - Set CC Pull Up `cc_pull_up`. + - Set DPR try mode behavior `try_behavior`. + - Enable/Disable Debug accessory `enable_debug_accessory`. + - Enable/Disable Audio accessory `enable_audio_accessory`. + """ + + __device_role = {PdcDeviceRole.DFP: PDCControlCommand.SetDataRoleDFP, + PdcDeviceRole.UFP: PDCControlCommand.SetDataRoleUFP, + PdcDeviceRole.DRP: PDCControlCommand.SetDataRoleDRP} + + __cc_pull_up = {CCPullUp.Current_05_09A: PDCControlCommand.PullCCLineToDefault, + CCPullUp.Current_1_5A: PDCControlCommand.PullCCLineTo1_5A, + CCPullUp.Current_3A: PDCControlCommand.PullCCLineTo3_0A} + + __behavior = {DRPTryMode.PureDRP: PDCControlCommand.SetDataRoleDRP, + DRPTryMode.DRP_try_SNK: PDCControlCommand.SetDataRoleDRP_SNK_Support, + DRPTryMode.DRP_try_SRC: PDCControlCommand.SetDataRoleDRP_SRC_Support} + + def __init__(self, pdc_io: PDCPortIO): + self.__io = pdc_io + self.__status = PcdCapsStatus(self.__io) + + @property + def status(self) -> PcdCapsStatus: + """ + Returns current PDC Capabilities status. + + Returns: + object of `PcdCapsStatus` type + """ + return self.__status + + def set_initial_role(self, role: PdcDeviceRole): + """ + Set device initial role. + + Args: + role (`PdcDeviceRole`) + """ + self.__io.write_pdc_command(self.__device_role.get(role)) + + def enable_pr_swap(self, enable: bool): + """ + Enable/Disable PR Swap. + + Args: + enable (`bool`) + """ + self.__io.write_pdc_command(PDCControlCommand.EnablePRSwap if enable else PDCControlCommand.DisablePRSwap) + + def enable_dr_swap(self, enable: bool): + """ + Enable/Disable DR Swap. + + Args: + enable (`bool`) + """ + self.__io.write_pdc_command(PDCControlCommand.EnableDRSwap if enable else PDCControlCommand.DisableDRSwap) + + def enable_fr_swap(self, enable: bool): + """ + Enable/Disable FR Swap. + + Args: + enable (`bool`) + """ + self.__io.write_pdc_command(PDCControlCommand.EnableFRSwap if enable else PDCControlCommand.DisableFRSwap) + + def enable_vconn_swap(self, enable: bool): + """ + Enable/Disable VCONN Swap. + + Args: + enable (`bool`) + """ + self.__io.write_pdc_command(PDCControlCommand.EnableVconnSwap if enable else PDCControlCommand.DisableVconnSwap) + + def cc_pull_up(self, cc_pull_up: CCPullUp): + """ + Set Cable Control Pull Up. + + Args: + cc_pull_up (`CCPullUp`) + """ + self.__io.write_pdc_command(self.__cc_pull_up.get(cc_pull_up)) + + def try_behavior(self, behavior: DRPTryMode): + """ + Set DRP try mode behavior. + + Args: + behavior (`DRPTryMode`) + """ + self.__io.write_pdc_command(self.__behavior.get(behavior)) + + def enable_debug_accessory(self, enable: bool): + """ + Enable/Disable Debug Accessory. + + Args: + enable (`bool`) + """ + self.__io.write_pdc_command(PDCControlCommand.EnableDebugAccessorySupport if enable else + PDCControlCommand.EnableDebugAccessorySupport) + + def enable_audio_accessory(self, enable: bool): + """ + Enable/Disable Audio Accessory. + + Args: + enable (`bool`) + """ + self.__io.write_pdc_command(PDCControlCommand.EnableAudioAccessorySupport if enable else + PDCControlCommand.DisableAudioAccessorySupport) + + +class PdcCapabilities340(PdcCapabilities): + + """ + Class `PdcCapabilities340` inherited of class`PdcCapabilities` allows working with PDC. + Class `PdcCapabilities340` has all the `PdcCapabilities` functionality. + - Enable/Disable USB 2.0 bypass `usb_2_bypass_function`. + - Enable/Disable USB 3.0 bypass `usb_3_bypass_function`. + """ + + def __init__(self, pdc_io: PDCPortIO): + super().__init__(pdc_io) + + def usb_2_bypass_function(self, enable: bool): + """ + Enable/Disable USB 2.0 bypass. + + Args: + enable (`bool`) + """ + self.__io.write_hw_command(HWControlCommand.Enable20PHY if enable else HWControlCommand.Disable20PHY) + + def usb_3_bypass_function(self, enable: bool): + """ + Enable/Disable USB 3.0 bypass. + + Args: + enable (`bool`) + """ + self.__io.write_hw_command(HWControlCommand.Enable30PHY if enable else HWControlCommand.Disable30PHY) \ No newline at end of file diff --git a/UniTAP/dev/ports/modules/pdc/pdc_contract_control.py b/UniTAP/dev/ports/modules/pdc/pdc_contract_control.py new file mode 100644 index 0000000..dce1bc7 --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/pdc_contract_control.py @@ -0,0 +1,253 @@ +import math +import warnings +from .pdc_io import PDCPortIO, PDCControlCommand +from .pdc_utils import support_or_not +from .pdc_types import FrSwapCurrent, ContractTypePriority, InternalResistance, ExternalResistance +from typing import Union + + +class PowerContractControlBase: + + """ + Class `PowerContractControlBase` allows setting and getting field related with power contract. + - Set and get Give Back flag `give_back_flag`, type `bool`. + - Set and get No USB suspend `no_usb_suspend`, type `bool`. + - Set and get PDO type priority `pdo_type_priority`, type `ContractTypePriority`. + - Set and get Maximum operation current `max_operation_current`, type `int`. + - Set FW Swap required current `fw_swap_required_current`, type `FrSwapCurrent`. + """ + + __fr_swap_current = {FrSwapCurrent.Disable: PDCControlCommand.SetFRSwapNoSupported, + FrSwapCurrent.EnableDefaultUSBPower: PDCControlCommand.SetFRSwapUSBPower, + FrSwapCurrent.Enable_1_5A_5V: PDCControlCommand.SetFRSwap1_5A_5V, + FrSwapCurrent.Enable_3A_5V: PDCControlCommand.SetFRSwap3A_5V} + + def __init__(self, pdc_io: PDCPortIO): + self._io = pdc_io + self.__caps = self._io.read_pdc_caps() + self.__pdo_count = self._io.read_hw_caps().pdo_count + + @property + def give_back_flag(self) -> bool: + """ + Returns state of Give back flag. + + Returns: + object of `bool` type + """ + return self._io.read_pdc_contract_control().give_back_flag != 0 + + @give_back_flag.setter + def give_back_flag(self, enable: bool): + data = self._io.read_pdc_contract_control() + data.give_back_flag = int(enable) + self._io.write_pdc_contract_control(data) + + @property + def no_usb_suspend(self) -> bool: + """ + Returns state of No USB suspend flag. + + Returns: + object of `bool` type + """ + return self._io.read_pdc_contract_control().no_usb_suspend != 0 + + @no_usb_suspend.setter + def no_usb_suspend(self, enable: bool): + data = self._io.read_pdc_contract_control() + data.no_usb_suspend = int(enable) + self._io.write_pdc_contract_control(data) + + def fw_swap_required_current(self, fr_swap_current: FrSwapCurrent): + """ + Set FW Swap required current. + + Args: + fr_swap_current (`FrSwapCurrent`) + """ + if self.__caps.fr_swap: + self._io.write_pdc_command(self.__fr_swap_current.get(fr_swap_current)) + else: + warnings.warn("Do not support Fast Role Swap.") + + @property + def pdo_type_priority(self) -> ContractTypePriority: + """ + Returns current PDO Type priority. + + Returns: + object of `ContractTypePriority` type + """ + return ContractTypePriority(self._io.read_pdc_contract_control().type_priority) + + @pdo_type_priority.setter + def pdo_type_priority(self, type_priority: ContractTypePriority): + data = self._io.read_pdc_contract_control() + data.type_priority = type_priority.value + self._io.write_pdc_contract_control(data) + + @property + def max_operation_current(self) -> int: + """ + Returns current value of maximum operation current. + + Returns: + object of `int` type + """ + data = self._io.read_contract_data() + return data[3] & 0x03FF + + @max_operation_current.setter + def max_operation_current(self, current: int): + if current < 0: + raise ValueError(f"Incorrect current {current}. Must be more than 0.") + data = self._io.read_contract_data() + data[3] = (data[3] & 0xFFFFFC00) | (current & 0x03FF) + self._io.write_contract_data(data) + + def established(self) -> bool: + """ + Returns state of power contract (established or not). + + Returns: + object of `bool` type + """ + return self._io.read_pdc_status().pwr_contract == 1 + + def __str__(self) -> str: + return f"Power Contract Control\n" \ + f"Give Back flag active: {support_or_not(self.give_back_flag)}\n" \ + f"No USB suspend flag active: {support_or_not(self.no_usb_suspend)}\n" \ + f"Contract type priority: {self.pdo_type_priority.name}\n" \ + f"Max operation current: {self.max_operation_current} mA" + + +class PowerContractControl340(PowerContractControlBase): + + """ + Class `PowerContractControl340` inherited from class `PowerContractControlBase`. + Class `PowerContractControl340` allows setting and getting internal and external resistance, + index of power contract. + Also has all the `PowerContractControlBase` functionality. + - Set and get flag Selecting power contract by index `selecting_by_index`, type `bool`. + - Set and get index of power contract `index_of_power_contract`, type `int`. + - Set and get internal resistance `internal_resistance`, type `int` or `InternalResistance`. + - Set and get external resistance `external_resistance`, type `int` or `ExternalResistance`. + """ + + __internal_resistance = {10000: InternalResistance.Resistance_10_Ohm, + 5500: InternalResistance.Resistance_5_5_Ohm, + 3550: InternalResistance.Resistance_3_55_Ohm, + 3500: InternalResistance.Resistance_3_5_Ohm, + 2600: InternalResistance.Resistance_2_6_Ohm, + 2140: InternalResistance.Resistance_2_14_Ohm, + 1760: InternalResistance.Resistance_1_76_Ohm} + + __external_resistance = {13900: ExternalResistance.Resistance_13_9_Ohm, + 10600: ExternalResistance.Resistance_10_6_Ohm, + 9100: ExternalResistance.Resistance_9_1_Ohm, + 7600: ExternalResistance.Resistance_7_6_Ohm, + 6600: ExternalResistance.Resistance_6_6_Ohm, + 5600: ExternalResistance.Resistance_5_6_Ohm, + 4600: ExternalResistance.Resistance_4_6_Ohm, + 3600: ExternalResistance.Resistance_3_6_Ohm, + 1800: ExternalResistance.Resistance_1_8_Ohm} + + def __init__(self, pdc_io: PDCPortIO): + super().__init__(pdc_io) + + @property + def selecting_by_index(self) -> bool: + """ + Returns state of Selecting by index flag. + + Returns: + object of `bool` type + """ + return self._io.read_pdc_contract_control().manual != 0 + + @selecting_by_index.setter + def selecting_by_index(self, enable: bool): + data = self._io.read_pdc_contract_control() + data.manual = enable + self._io.write_pdc_contract_control(data) + + @property + def index_of_power_contract(self) -> int: + """ + Returns index of current power contract. + + Returns: + object of `int` type + """ + if not self._io.read_pdc_contract_control().manual: + return self._io.read_power_contract_select() + else: + warnings.warn("Negotiate power contract automatically disabled. " + "Please, enable (use function 'enable_selecting_by_index'.)") + return -1 + + @index_of_power_contract.setter + def index_of_power_contract(self, index: int): + if not self._io.read_pdc_contract_control().manual: + if index < 0: + raise ValueError(f"Incorrect index {index} of power contract. " + f"Must be between 0 and {self.__pdo_count}.") + self._io.write_power_contract_select(index) + else: + warnings.warn("Negotiate power contract automatically disabled. " + "Please, enable (use function 'enable_selecting_by_index'.)") + + @property + def internal_resistance(self) -> Union[InternalResistance, int]: + """ + Returns internal resistance. + + Returns: + object of `int` or `InternalResistance` type + """ + res = self._io.read_internal_resistance() + for key in self.__internal_resistance.keys(): + if math.fabs(res - key) < 50: + return self.__internal_resistance.get(key) + return res + + @internal_resistance.setter + def internal_resistance(self, resistance: InternalResistance): + self._io.write_resistance(resistance.value) + + @property + def external_resistance(self) -> Union[ExternalResistance, int]: + """ + Returns external resistance. + + Returns: + object of `int` or `ExternalResistance` type + """ + if self._io.read_hw_status().ext_power: + res = self._io.read_external_resistance() + for key in self.__external_resistance.keys(): + if math.fabs(res - key) < 50: + return self.__external_resistance.get(key) + return res + else: + return ExternalResistance.Disable + + @external_resistance.setter + def external_resistance(self, resistance: ExternalResistance): + if self._io.read_hw_status().ext_power: + self._io.write_resistance(resistance.value) + else: + warnings.warn("Does not enabled external power. Please, attach external power device.") + + def __str__(self) -> str: + return f"Power Contract Control\n" \ + f"Give Back flag active: {support_or_not(self.give_back_flag)}\n" \ + f"No USB suspend flag active: {support_or_not(self.no_usb_suspend)}\n" \ + f"Contract type priority: {self.pdo_type_priority.name}\n" \ + f"Max operation current: {self.max_operation_current} mA\n" \ + f"Internal resistance: {self.internal_resistance} Om\n" \ + f"External resistance: {self.external_resistance} Om\n" \ + f"Selecting power contract by index flag active: {support_or_not(self.selecting_by_index)}\n" \ + f"Power contract index: {self.index_of_power_contract if self.selecting_by_index else '0'}" diff --git a/UniTAP/dev/ports/modules/pdc/pdc_controls.py b/UniTAP/dev/ports/modules/pdc/pdc_controls.py new file mode 100644 index 0000000..cce2d74 --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/pdc_controls.py @@ -0,0 +1,268 @@ +import warnings +from typing import Optional + +from UniTAP.utils import function_scheduler +from .pdc_io import PDCPortIO, PDCControlCommand, HWControlCommand +from .pdc_types import CableControlOrientation, DifferentialPair + + +class PdcControlsBase: + + """ + Class `PdcControlsBase` allows controlling some PDC commands: + - Send PR Swap `send_pr_swap`. + - Send DR Swap `send_dr_swap`. + - Send FR Swap `send_fr_swap`. + - Send VCONN Swap `send_vconn_swap`. + - Change cable control orientation `orientation`. + - Attach/DeAttach `attach`. + - Enable/Disable auto negotiate power contract `enable_auto_negotiate_power_contract`. + - Enable/Disable communication capable as PD Sink `communication_capable_as_pd_sink`. + - Enable/Disable communication capable as PD Source `communication_capable_as_pd_source`. + """ + + def __init__(self, pdc_io: PDCPortIO): + self.__io = pdc_io + self.__caps = self.__io.read_pdc_caps() + + def send_pr_swap(self): + """ + Send Power Role swap. It will be necessary to manually check that the changes have been made. + """ + self.__io.write_pdc_command(PDCControlCommand.TogglePowerRole) + + def send_dr_swap(self): + """ + Send Dual Role swap. It will be necessary to manually check that the changes have been made. + """ + self.__io.write_pdc_command(PDCControlCommand.ToggleDataRole) + + def send_fr_swap(self): + """ + Send Fast Role swap. It will be necessary to manually check that the changes have been made. + """ + if self.__caps.fr_swap: + self.__io.write_pdc_command(PDCControlCommand.FastRoleSwap) + else: + warnings.warn("Do not support Fast Role Swap.") + + def send_vconn_swap(self): + """ + Send VCONN Role swap. It will be necessary to manually check that the changes have been made. + """ + self.__io.write_pdc_command(PDCControlCommand.SwapVconn) + + @property + def orientation(self) -> Optional[CableControlOrientation]: + if self.__io.read_hw_status().et_cable: + if self.__io.read_hw_status().cc1_closed: + return CableControlOrientation.CC1 + elif self.__io.read_hw_status().cc2_closed: + return CableControlOrientation.CC2 + else: + return None + else: + return None + + @orientation.setter + def orientation(self, orientation: CableControlOrientation): + """ + Change cable control orientation. + + Args: + orientation (`CableControlOrientation`) + """ + if self.__io.read_hw_status().et_cable: + self.__io.write_hw_command(HWControlCommand.SetCableFlipToOn + if orientation == CableControlOrientation.CC2 else + HWControlCommand.SetCableFlipToOff) + else: + warnings.warn("Unigraf ET cable is not connected. Cannot switch cable control orientation.") + + def enable_auto_negotiate_power_contract(self, enable: bool): + """ + Enable/Disable auto negotiate power contract. + + Args: + enable (`bool`) + """ + caps = self.__io.read_pdc_contract_control() + caps.auto_negotate = enable + self.__io.write_pdc_contract_control(caps) + + def communication_capable_as_pd_sink(self, capable: bool): + """ + Enable/Disable communication capable as PD Sink. + + Args: + capable (`bool`) + """ + self.enable_auto_negotiate_power_contract(capable) + pdo = self.__io.read_local_spr_source_pdo() + vdo = self.__io.read_tx_id_vdo() + if isinstance(vdo, list): + if capable: + vdo[0] |= 1 << 30 + else: + vdo[0] &= ~(1 << 30) + self.__io.write_tx_id_vdo(vdo) + if isinstance(pdo, list): + if capable: + pdo[0].data |= 1 << 26 + else: + pdo[0].data &= ~(1 << 26) + self.__io.write_local_spr_source_pdo([v.data for v in pdo]) + + def communication_capable_as_pd_source(self, capable: bool): + """ + Enable/Disable communication capable as PD Source. + + Args: + capable (`bool`) + """ + pdo = self.__io.read_local_spr_source_pdo() + vdo = self.__io.read_tx_id_vdo() + if isinstance(vdo, list): + if capable: + vdo[0] |= 1 << 31 + else: + vdo[0] &= ~(1 << 31) + self.__io.write_tx_id_vdo(vdo) + if isinstance(pdo, list): + if capable: + pdo[0].data |= 1 << 26 + else: + pdo[0].data &= ~(1 << 26) + self.__io.write_local_spr_source_pdo([v.data for v in pdo]) + + +class PdcControls340(PdcControlsBase): + + """ + Class `PdcControls340` inherited from class `PdcControlsBase`. + Class `PdcControls340` allows controlling additional commands. + Also has all the `PdcControlsBase` functionality. + - Reconnect device `reconnect`. + - Reset device `reset`. + - Set ET cable different pairs `et_cable_diff_pairs`. + """ + + def __init__(self, pdc_io: PDCPortIO): + super().__init__(pdc_io) + self.__io = pdc_io + + def reset(self): + """ + Reset device. + """ + self.__io.write_hw_command(HWControlCommand.ResetPDController) + + def attach(self, attach: bool): + """ + Attach/DeAttach device. + """ + self.__io.write_hw_command(HWControlCommand.ConnectCCLines if attach else HWControlCommand.DisconnectCCLines) + + def reconnect(self): + """ + Reconnect device. + + Returns: + result of reconnection, type `bool`. + """ + self.__io.write_hw_command(HWControlCommand.RePlugCable) + + def is_reconnect_success(__io): + return self.__io.read_pdc_status().cable_plug != 0 + + return function_scheduler(is_reconnect_success, self.__io, interval=1, timeout=10) + + def et_cable_diff_pairs(self, differential_pair: DifferentialPair): + """ + Set ET cable different pairs. + + Args: + differential_pair (`DifferentialPair`) + """ + self.__io.write_hw_command(HWControlCommand.SetNewETCableMode if differential_pair.OnePair else + HWControlCommand.SetOldETCableMode) + + +class PdcControls424(PdcControlsBase): + + """ + Class `PdcControls424` inherited from class `PdcControlsBase`. + Class `PdcControls424` allows controlling additional commands. + Also has all the `PdcControlsBase` functionality. + - Reconnect device `reconnect`. + - Reset device `reset`. + """ + + def __init__(self, pdc_io: PDCPortIO): + super().__init__(pdc_io) + self.__io = pdc_io + + def reset(self): + """ + Reset device. + """ + self.__io.write_hw_command(HWControlCommand.ResetPDController) + + def reconnect(self) -> bool: + """ + Reconnect device. + + Returns: + result of reconnection, type `bool`. + """ + self.__io.write_pdc_command(PDCControlCommand.SimulateReConnect) + + def is_reconnect_success(__io): + return self.__io.read_pdc_status().cable_plug != 0 + + return function_scheduler(is_reconnect_success, self.__io, interval=1, timeout=10) + + +class PdcControls500(PdcControlsBase): + + """ + Class `PdcControls500` inherited from class `PdcControlsBase`. + Class `PdcControls500` allows controlling additional commands. + Also has all the `PdcControlsBase` functionality. + - Reconnect device `reconnect`. + - Enable/Disable internal resistance 10 Ohm `enable_internal_load_10_ohm`. + """ + + def __init__(self, pdc_io: PDCPortIO): + super().__init__(pdc_io) + self.__io = pdc_io + + def reconnect(self) -> bool: + """ + Reconnect device. + + Returns: + result of reconnection, type `bool`. + """ + self.__io.write_hw_command(HWControlCommand.RePlugCable) + + def is_reconnect_success(__io): + return self.__io.read_pdc_status().cable_plug != 0 + + return function_scheduler(is_reconnect_success, self.__io, interval=1, timeout=10) + + def attach(self, attach: bool): + """ + Attach/DeAttach device. + """ + self.__io.write_hw_command(HWControlCommand.ConnectCCLines if attach else HWControlCommand.DisconnectCCLines) + + def enable_internal_load_10_ohm(self, enable: bool): + """ + Enable/Disable internal resistance 10 Ohm. + + Args: + enable (`bool`) + """ + self.__io.write_hw_command(HWControlCommand.EnableIntLoad_10_ohm if enable else + HWControlCommand.DisableIntLoad_10_ohm) diff --git a/UniTAP/dev/ports/modules/pdc/pdc_dp_alt_mode.py b/UniTAP/dev/ports/modules/pdc/pdc_dp_alt_mode.py new file mode 100644 index 0000000..bd7a893 --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/pdc_dp_alt_mode.py @@ -0,0 +1,491 @@ +import warnings + +from UniTAP.dev.ports.modules.pdc.pdc_dpam_types import DPAMVersion +from .pdc_io import PDCPortIO, DPAMCommand +from .pdc_private_dpam_types import PinAssignmentModes, DISCDpSignaling, DISCCapability +from .pdc_utils import support_or_not + + +class PinAssignment: + + """ + Class `PinAssignment` allows enabling and disabling assignment. + - Enable/Disable mode C 4 lanes `c_4_lanes`, type `bool`. + - Enable/Disable mode D 2 lanes `d_2_lanes`, type `bool`. + - Enable/Disable mode E 4 lanes `e_4_lanes`, type `bool`. + """ + + def __init__(self): + self.__c_4_lanes = False + self.__d_2_lanes = False + self.__e_4_lanes = False + + @property + def c_4_lanes(self) -> bool: + """ + Returns state of C 4 lanes mode. + + Returns: + object of `bool` type. + """ + return self.__c_4_lanes + + @c_4_lanes.setter + def c_4_lanes(self, enable: bool): + self.__c_4_lanes = enable + + @property + def d_2_lanes(self) -> bool: + """ + Returns state of D 2 lanes mode. + + Returns: + object of `bool` type. + """ + return self.__d_2_lanes + + @d_2_lanes.setter + def d_2_lanes(self, enable: bool): + self.__d_2_lanes = enable + + @property + def e_4_lanes(self) -> bool: + """ + Returns state of E 4 lanes mode. + + Returns: + object of `bool` type. + """ + return self.__e_4_lanes + + @e_4_lanes.setter + def e_4_lanes(self, enable: bool): + self.__e_4_lanes = enable + + def __str__(self) -> str: + return f"C 4 lanes: {self.c_4_lanes}\n" \ + f"D 2 lanes: {self.d_2_lanes}\n" \ + f"E 4 lanes: {self.e_4_lanes}\n" + + +class DutDpAltModeStatus: + + """ + Class `DutDpAltModeStatus` describes DUT DP Alt mode status. + - Get DUT connection state `dut_connection`, type `str`. + - Get DUT multi-function state `dut_multi_function`, type `bool`. + - Get DUT power low state `dut_power_low`, type `bool`. + """ + + __dut_connection_status = {DISCCapability.Reserved: "No connection", + DISCCapability.DPF_D_capable: "DFP_D is connected", + DISCCapability.UFP_D_capable: "UFP_D is connected", + DISCCapability.UFP_D_and_DPF_D: "Both DFP_D and UFP_D are connected"} + + def __init__(self, pdc_io: PDCPortIO): + self.__io = pdc_io + + @property + def dut_connection(self) -> str: + """ + Returns DUT connection state. + + Returns: + object of `str` type. + """ + return self.__dut_connection_status.get(DISCCapability(self.__io.read_dpam_recv().connection)) + + @property + def dut_multi_function(self) -> bool: + """ + Returns DUT multi-function state. + + Returns: + object of `bool` type. + """ + return bool(self.__io.read_dpam_recv().multi_func) + + @property + def dut_power_low(self) -> bool: + """ + Returns DUT power low state. + + Returns: + object of `bool` type. + """ + return bool(self.__io.read_dpam_recv().power_low) + + def __str__(self): + return f"DUT Alt mode: {self.dut_connection}\n" \ + f"DUT Multi-function preferred: {support_or_not(self.dut_multi_function)}\n" \ + f"DUT Power low: {'Low power or DP support disabled' if self.dut_power_low else 'Normal operation'}\n" + + +class TeDpAltModeStatus: + + """ + Class `DutDpAltModeStatus` describes TE DP Alt mode status. + - Get active state `te_active`, type `bool`. + - Get DISC signaling DP v1.3 state, type `bool`. + - Get DISC signaling USB Gen 2 state, type `bool`. + - Get PIN assignment, type `PinAssignmentModes`. + """ + + def __init__(self, pdc_io: PDCPortIO): + self.__io = pdc_io + + @property + def te_active(self) -> bool: + """ + Returns active state. + + Returns: + object of `bool` type. + """ + return bool(self.__io.read_pdc_status().cable_plug) + + @property + def te_select_dp_1_3(self) -> bool: + """ + Returns DISC signaling DP v1.3 state. + + Returns: + object of `bool` type. + """ + return self.__io.read_dpam_config().signaling == DISCDpSignaling.Dp_v13 + + @property + def te_select_usb_gen2(self) -> bool: + """ + Returns DISC signaling USB Gen 2 state. + + Returns: + object of `bool` type. + """ + return self.__io.read_dpam_config().signaling == DISCDpSignaling.Gen2 + + @property + def te_pin_assignment(self) -> PinAssignmentModes: + """ + Returns PIN assignment. + + Returns: + object of `PinAssignmentModes` type. + """ + + return PinAssignmentModes(self.__io.read_dpam_config().pin_assign) + + def __str__(self): + return f"TE Status Active: {support_or_not(self.te_active)}\n" \ + f"TE Select DP v1.3: {self.te_select_dp_1_3}\n" \ + f"TE Select USB gen2: {self.te_select_usb_gen2}\n" \ + f"TE Pin Assignment: {self.te_pin_assignment.name}\n" + + +class DpAltModeStatus: + """ + Class `DpAltModeStatus` describes DP Alt mode status. + - Get DUT DP Alt mode status `dut_dp_alt_mode`, type `DutDpAltModeStatus`. + - Get TE DP Alt mode status, type `TeDpAltModeStatus`. + - Get support state of DISC Signaling DP v13 `support_dp_1_3`, type `bool`. + - Get support state of DISC Signaling USB Gen2 `support_usb_gen2`, type `bool`. + - Get support state of DFP D `support_dfp_d`, type `bool`. + - Get support state of UFP D `support_ufp_d`, type `bool`. + """ + + def __init__(self, pdc_io: PDCPortIO): + self.__io = pdc_io + self.__dut_dp_alt_mode = DutDpAltModeStatus(self.__io) + self.__te_dp_alt_mode = TeDpAltModeStatus(self.__io) + + @property + def support_dp_1_3(self) -> bool: + """ + Returns support state of DISC Signaling DP v13. + + Returns: + object of `bool` type. + """ + return self.__io.read_dpam_disc_modes().signaling == DISCDpSignaling.Dp_v13 + + @property + def support_usb_gen2(self) -> bool: + """ + Returns support state of DISC Signaling USB Gen2. + + Returns: + object of `bool` type. + """ + return self.__io.read_dpam_disc_modes().signaling == DISCDpSignaling.Gen2 + + @property + def support_dfp_d(self) -> bool: + """ + Returns support state of DFP D. + + Returns: + object of `bool` type. + """ + return DISCCapability(self.__io.read_dpam_disc_modes().capability) in [DISCCapability.DPF_D_capable, + DISCCapability.UFP_D_and_DPF_D] + + @property + def support_ufp_d(self) -> bool: + """ + Returns support state of UFP D. + + Returns: + object of `bool` type. + """ + return DISCCapability(self.__io.read_dpam_disc_modes().capability) in [DISCCapability.UFP_D_capable, + DISCCapability.UFP_D_and_DPF_D] + + def multifunction_preferred(self) -> bool: + """ + Returns state of multifunction preferred. + + Returns: + object of `bool` type. + """ + return self.__io.read_dpam_caps2() + + def auto_enter(self) -> bool: + """ + Returns state of auto enter. + + Returns: + object of `bool` type. + """ + return self.__io.read_dpam_state().auto_dpam != 0 + + @property + def dut_dp_alt_mode(self) -> DutDpAltModeStatus: + """ + Returns DUT DP Alt mode status. + + Returns: + object of `DutDpAltModeStatus` type. + """ + return self.__dut_dp_alt_mode + + @property + def te_dp_alt_mode(self) -> TeDpAltModeStatus: + """ + Returns TE DP Alt mode status. + + Returns: + object of `TeDpAltModeStatus` type. + """ + return self.__te_dp_alt_mode + + def __str__(self): + return f"Supports DP v1.3: {support_or_not(self.support_dp_1_3)}\n" \ + f"Supports USB gen2: {support_or_not(self.support_usb_gen2)}\n" \ + f"Multifunction preferred: {support_or_not(self.multifunction_preferred())}\n" \ + f"Auto enter: {support_or_not(self.auto_enter())}\n" \ + f"Pin Assignment supported:\n" \ + f" - Support DFP_D: {support_or_not(self.support_dfp_d)}\n" \ + f" - Support UFP_D: {support_or_not(self.support_ufp_d)}\n" \ + f"{self.__dut_dp_alt_mode.__str__()}" \ + f"{self.__te_dp_alt_mode.__str__()}" + + +class DpAltModeBase: + + """ + Class `DpAltModeBase` describes basic DP Alt mode functionality. + - Get DP Alt mode status `status`, type `DpAltModeStatus`. + - Enter to 2 lane mode `enter_2_lane`. + - Enter to 4 lane mode `enter_4_lane`. + - Exit from DP Alt mode `exit`. + - Disable DP Alt mode `disable`. + - Enable/Disable auto enter to DP Alt mode `auto_enter`. + - Enable/Disable multifunction preferred `multifunction_preferred`. + - Enable/Disable align Dp and USB Data role `align_dp_and_usb_data_role`. + - Set and get UFP caps `ufp_caps`, type `PinAssignment`. + - Set and get DFP caps `dfp_caps`, type `PinAssignment`. + """ + + def __init__(self, pdc_io: PDCPortIO): + self._io = pdc_io + self.__caps = self._io.read_pdc_caps() + self.__status = DpAltModeStatus(pdc_io) + + @property + def status(self) -> DpAltModeStatus: + """ + Returns DP Alt mode status. + + Returns: + object of `DpAltModeStatus` type. + """ + return self.__status + + def enter_2_lane(self): + """ + Enter to 2 lane mode. + """ + self._io.write_dpam_control(DPAMCommand.EnterDPAM2lanes) + + def enter_4_lane(self): + """ + Enter to 4 lane mode. + """ + self._io.write_dpam_control(DPAMCommand.EnterDPAM4lanes) + + def exit(self): + """ + Exit from DP Alt mode. + """ + self._io.write_dpam_control(DPAMCommand.ExitDPAM) + + def disable(self): + """ + Disable DP Alt mode. + """ + self._io.write_dpam_control(DPAMCommand.DisableDPAM) + + def auto_enter(self, enable: bool): + """ + Enable/Disable auto enter to DP Alt mode. + + Args: + enable (`bool`) + """ + self._io.write_dpam_control(DPAMCommand.AutoEnterDPAM if enable else DPAMCommand.ManualDPAM) + + def multifunction_preferred(self, enable: bool): + """ + Enable/Disable multifunction preferred. + + Args: + enable (`bool`) + """ + self._io.write_dpam_control(DPAMCommand.SetMFPreferredFlag if enable else DPAMCommand.ClearMFPreferredFlag) + + def align_dp_and_usb_data_role(self, enable: bool): + """ + Enable/Disable align Dp and USB Data role. + + Args: + enable (`bool`) + """ + if self.__caps.align_dp_usb: + self._io.write_dpam_control(DPAMCommand.SetAlignDpCUsb if enable else DPAMCommand.ClearAlignDpCUsb) + self._io.write_dpam_control(DPAMCommand.SetAlignDpDUsb if enable else DPAMCommand.ClearAlignDpDUsb) + self._io.write_dpam_control(DPAMCommand.SetAlignDpEUsb if enable else DPAMCommand.ClearAlignDpEUsb) + else: + warnings.warn("Align DP and USB data role is not supported.") + + @property + def ufp_caps(self) -> PinAssignment: + """ + Returns current UFP caps. + + Returns: + object of `PinAssignment` + """ + caps = self._io.read_dpam_caps() + pin_assignment = PinAssignment() + pin_assignment.c_4_lanes = ((caps.ufp_assign >> 2 & 1) != 0) + pin_assignment.d_2_lanes = ((caps.ufp_assign >> 3 & 1) != 0) + pin_assignment.e_4_lanes = ((caps.ufp_assign >> 4 & 1) != 0) + return pin_assignment + + @ufp_caps.setter + def ufp_caps(self, ufp_caps: PinAssignment): + caps = self._io.read_dpam_caps() + if ufp_caps.c_4_lanes: + caps.ufp_assign |= PinAssignmentModes.C_4lanes.value + else: + caps.ufp_assign &= ~PinAssignmentModes.C_4lanes.value + if ufp_caps.d_2_lanes: + caps.ufp_assign |= PinAssignmentModes.D_2lanes.value + else: + caps.ufp_assign &= ~PinAssignmentModes.D_2lanes.value + if ufp_caps.e_4_lanes: + caps.ufp_assign |= PinAssignmentModes.E_4lanes.value + else: + caps.ufp_assign &= ~PinAssignmentModes.E_4lanes.value + self._io.write_dpam_caps(caps) + + @property + def dfp_caps(self) -> PinAssignment: + """ + Returns current DFP caps. + + Returns: + object of `PinAssignment` + """ + caps = self._io.read_dpam_caps() + pin_assignment = PinAssignment() + pin_assignment.c_4_lanes = ((caps.ufp_assign >> 2 & 1) != 0) + pin_assignment.d_2_lanes = ((caps.ufp_assign >> 3 & 1) != 0) + pin_assignment.e_4_lanes = ((caps.ufp_assign >> 4 & 1) != 0) + return pin_assignment + + @dfp_caps.setter + def dfp_caps(self, dfp_caps: PinAssignment): + caps = self._io.read_dpam_caps() + if dfp_caps.c_4_lanes: + caps.dfp_assign |= PinAssignmentModes.C_4lanes.value + else: + caps.dfp_assign &= ~PinAssignmentModes.C_4lanes.value + if dfp_caps.d_2_lanes: + caps.dfp_assign |= PinAssignmentModes.D_2lanes.value + else: + caps.dfp_assign &= ~PinAssignmentModes.D_2lanes.value + if dfp_caps.e_4_lanes: + caps.dfp_assign |= PinAssignmentModes.E_4lanes.value + else: + caps.dfp_assign &= ~PinAssignmentModes.E_4lanes.value + self._io.write_dpam_caps(caps) + + @property + def version(self) -> DPAMVersion: + return DPAMVersion(self._io.read_dpam_caps().dpam_version) + + +class DpAltMode500(DpAltModeBase): + + """ + Class `DpAltMode500` inherited from class `DpAltModeBase`. + Class `DpAltMode500` allows controlling additional commands. + Also has all the `DpAltModeBase` functionality. + - Enable/Disable DP 2.1 Alt mode `enable_dp21`. + """ + + def __init__(self, pdc_io: PDCPortIO): + super().__init__(pdc_io) + + def enable_dp21(self, enable: bool): + """ + Enable/Disable DP 2.1 Alt mode. + + Args: + enable (`bool`) + """ + self._io.write_dpam_control(DPAMCommand.EnableDPAM2_1 if enable else DPAMCommand.DisableDPAM2_1) + + +class DpAltMode340(DpAltModeBase): + + """ + Class `DpAltMode340` inherited from class `DpAltModeBase`. + Class `DpAltMode340` allows controlling additional commands. + Also has all the `DpAltModeBase` functionality. + - Enable/Disable DP to Type-C cable adapter mode `dp_to_type_c_cable_adapter_mode`. + """ + + def __init__(self, pdc_io: PDCPortIO): + super().__init__(pdc_io) + + def dp_to_type_c_cable_adapter_mode(self, enable: bool): + """ + Enable/Disable DP to Type-C cable adapter mode. + + Args: + enable (`bool`) + """ + self._io.write_dpam_control(DPAMCommand.EnableDPtoTypeCAdapter if enable else + DPAMCommand.DisableDPtoTypeCAdapter) diff --git a/UniTAP/dev/ports/modules/pdc/pdc_dpam_types.py b/UniTAP/dev/ports/modules/pdc/pdc_dpam_types.py new file mode 100644 index 0000000..d5486cd --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/pdc_dpam_types.py @@ -0,0 +1,43 @@ +from enum import IntEnum + + +class DISCCapability(IntEnum): + """ + Class `DISCCapability` contains all possible variants of DISC capability. + """ + Reserved = 0 + UFP_D_capable = 1 + DPF_D_capable = 2 + UFP_D_and_DPF_D = 3 + + +class DISCDpSignaling(IntEnum): + """ + Class `DISCDpSignaling` contains all possible variants of signaling. + """ + Unspecified = 0 + Dp_v13 = 1 + Gen2 = 2 + + +class PinAssignmentModes(IntEnum): + """ + Class `PinAssignmentModes` contains all possible variants of pin assignment. + """ + NotSupported = 0x00 + A_4_2lanes = 0x01 + B_2_1lanes = 0x02 + C_4lanes = 0x04 + D_2lanes = 0x08 + E_4lanes = 0x10 + F_2lanes_USB_GEN1 = 0x20 + + +class DPAMVersion(IntEnum): + """ + Class `PinAssignmentModes` contains all possible variants of DP Alt mode version. + """ + V_20_or_Earlier = 0 + V_21_or_Higher = 1 + Reserved = 2 + Reserved2 = 3 diff --git a/UniTAP/dev/ports/modules/pdc/pdc_io.py b/UniTAP/dev/ports/modules/pdc/pdc_io.py new file mode 100644 index 0000000..359670d --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/pdc_io.py @@ -0,0 +1,115 @@ +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from UniTAP.libs.lib_tsi.tsi_private_types import * +from .pdc_private_dpam_types import DPAMCaps, DPAMState, DPAMCommand, DPAMConfig, DPAMDiscModes, DPAMRecv +from .pdc_private_types import HWControlCommand, HWStatus, HWCaps, PDCStatus, PDCControlCommand, PDCState, PDCCaps, \ + PDCContractInfo +from .pdo_private_types import PdoUnion + + +class PDCPortIO: + + def __init__(self, port_io: PortIO): + self.__io = port_io + self.write_adc_control() + + def write_dpam_caps(self, caps: DPAMCaps): + self.__io.set(TSI_PDC_DPAM_CAPS, caps.value(), c_uint32) + + def read_dpam_caps(self) -> DPAMCaps: + return self.__io.get(TSI_PDC_DPAM_CAPS, DPAMCaps)[1] + + def read_dpam_caps2(self) -> bool: + return (self.__io.get(TSI_PDC_DPAM_CAPS2, c_uint32)[1] & 0x1) != 0 + + def read_dpam_state(self) -> DPAMState: + return self.__io.get(TSI_PDC_DPAM_STATE_R, DPAMState)[1] + + def write_dpam_control(self, command: DPAMCommand): + self.__io.set(TSI_PDC_DPAM_CONTROL_W, command.value, c_uint32) + + def read_dpam_config(self) -> DPAMConfig: + return self.__io.get(TSI_PDC_DPAM_CONFIG_R, DPAMConfig)[1] + + def read_dpam_disc_modes(self) -> DPAMDiscModes: + return self.__io.get(TSI_PDC_DPAM_DISC_MODES_R, DPAMDiscModes)[1] + + def read_dpam_recv(self) -> DPAMRecv: + return self.__io.get(TSI_PDC_DPAM_RECV_STS_R, DPAMRecv)[1] + + def write_hw_command(self, command: HWControlCommand): + self.__io.set(TSI_PDC_HW_CONTROL_W, command.value, c_uint32) + + def read_hw_status(self) -> HWStatus: + return self.__io.get(TSI_PDC_HW_STATUS_R, HWStatus)[1] + + def read_hw_caps(self) -> HWCaps: + return self.__io.get(TSI_PDC_HW_CAPS_R, HWCaps)[1] + + def read_pdc_status(self) -> PDCStatus: + return self.__io.get(TSI_PDC_STATUS_R, PDCStatus)[1] + + def write_pdc_command(self, command: PDCControlCommand): + self.__io.set(TSI_PDC_CONTROL, command.value, c_uint32) + + def read_pdc_state(self) -> PDCState: + return self.__io.get(TSI_PDC_STATE, PDCState)[1] + + def read_pdc_caps(self) -> PDCCaps: + return self.__io.get(TSI_PDC_CAPS, PDCCaps)[1] + + def write_pdc_contract_control(self, caps: PDCContractInfo): + self.__io.set(TSI_USBC_PWR_CONTRACT_CONTROL, caps.value(), c_uint32) + + def read_pdc_contract_control(self) -> PDCContractInfo: + return self.__io.get(TSI_USBC_PWR_CONTRACT_CONTROL, PDCContractInfo)[1] + + def read_local_spr_source_pdo(self) -> list: + return self.__io.get(TSI_USBC_PWR_LOCAL_SOURCE_PDO, PdoUnion, self.read_hw_caps().pdo_count)[1] + + def write_local_spr_source_pdo(self, data: list): + self.__io.set(TSI_USBC_PWR_LOCAL_SOURCE_PDO, data, c_uint32, len(data)) + + def read_local_spr_sink_pdo(self) -> list: + return self.__io.get(TSI_USBC_PWR_LOCAL_SINK_PDO, PdoUnion, self.read_hw_caps().pdo_count)[1] + + def write_local_spr_sink_pdo(self, data: list): + self.__io.set(TSI_USBC_PWR_LOCAL_SINK_PDO, data, c_uint32, len(data)) + + def read_tx_id_vdo_count(self) -> int: + return self.__io.get(TSI_PDC_TX_ID_VDO_CNT, c_uint32)[1] + + def read_tx_id_vdo(self) -> list: + return self.__io.get(TSI_PDC_TX_ID_VDO, c_uint32, self.read_tx_id_vdo_count())[1] + + def write_tx_id_vdo(self, data: list): + self.__io.set(TSI_PDC_TX_ID_VDO, data, c_uint32, len(data)) + + def read_contract_data(self) -> list: + return self.__io.get(TSI_PDC_CONTRACT_DATA, c_uint32, 4)[1] + + def write_contract_data(self, data: list): + self.__io.set(TSI_PDC_CONTRACT_DATA, data, c_uint32, len(data)) + + def read_power_contract_select(self) -> int: + return self.__io.get(TSI_USBC_PWR_CONTRACT_SELECT, c_uint32)[1] + + def write_power_contract_select(self, index: int): + self.__io.set(TSI_USBC_PWR_CONTRACT_SELECT, index, c_uint32) + + def read_resistance(self) -> int: + return self.__io.get(TSI_USBC_RESISTANCE_CONTROL, c_uint32)[1] + + def write_resistance(self, resistance: int): + self.__io.set(TSI_USBC_RESISTANCE_CONTROL, resistance, c_uint32) + + def read_adc_vbus_status(self) -> list: + return self.__io.get(TSI_ADC_VBUS_VOLTAGE_R, c_uint32, TSI_ADC_BLOCK_SIZE_WORDS)[1] + + def read_internal_resistance(self) -> int: + return self.__io.get(TSI_USBC_INT_RESISTANCE_STATUS_R, c_uint32)[1] + + def read_external_resistance(self) -> int: + return self.__io.get(TSI_USBC_EXT_RESISTANCE_STATUS_R, c_uint32)[1] + + def write_adc_control(self): + self.__io.set(TSI_W_USBC_ADC_CTRL, 1, c_uint32) diff --git a/UniTAP/dev/ports/modules/pdc/pdc_power_sink.py b/UniTAP/dev/ports/modules/pdc/pdc_power_sink.py new file mode 100644 index 0000000..e40e02f --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/pdc_power_sink.py @@ -0,0 +1,144 @@ +import copy + +from .pdc_io import PDCPortIO +from .pdc_utils import load_pdo, save_pdo +from .pdo_types import FixedPdoSink, BatteryPdo, VariablePdo, PdoTypeEnum, PdoSide +from .pdo import Pdo +from .pdo_utils import get_pdo_value +from typing import List + + +class PowerSink: + + """ + Class `PowerSink` contains information about PDO's on Sink side. + - Get PDO count `pdo_count`, type `int`. + - Set and get PDO list, `set_pdo_list`, `get_pdo_list`, type `list` with `Pdo`. + - Set and get PDO by index, `set_pdo_by_index`, `get_pdo_by_index`, type `Pdo`. + - Save information about PDO's to file, `save_pdo`. + - Load information about PDO's from file, `load_pdo`. + """ + + def __init__(self, pdc_io: PDCPortIO): + self.__io = pdc_io + self.__pdo_count = self.__io.read_hw_caps().pdo_count + self.__pdo_list = self.__read_pdo() + + def __read_pdo(self) -> List[Pdo]: + pdo_list = [] + values = self.__io.read_local_spr_sink_pdo() + for i in range(self.__pdo_count): + if PdoTypeEnum(values[i].brief_info.pdo_type) == PdoTypeEnum.Fixed and values[i].data > 0: + pdo_list.append(Pdo(pdo=FixedPdoSink(values[i].fixed_pdo_sink, disable=False), side=PdoSide.Sink)) + elif PdoTypeEnum(values[i].brief_info.pdo_type) == PdoTypeEnum.Fixed and values[i].data == 0: + pdo_list.append(Pdo(pdo=FixedPdoSink(values[i].fixed_pdo_sink, disable=True), side=PdoSide.Sink)) + elif PdoTypeEnum(values[i].brief_info.pdo_type) == PdoTypeEnum.Battery: + pdo_list.append(Pdo(pdo=BatteryPdo(values[i].battery_pdo), side=PdoSide.Sink)) + elif PdoTypeEnum(values[i].brief_info.pdo_type) == PdoTypeEnum.Variable: + pdo_list.append(Pdo(pdo=VariablePdo(values[i].variable_pdo), side=PdoSide.Sink)) + return pdo_list + + def __write_pdo(self) -> List[int]: + pdo_list = [] + for index, item in enumerate(self.__pdo_list): + if index == 0: + pdo_list.append(get_pdo_value(item.get_pdo_as_selected_type(FixedPdoSink))) + elif item.pdo_type != PdoTypeEnum.Disabled: + pdo_list.append(get_pdo_value(item.pdo_object)) + else: + pdo_list.append(0) + return pdo_list + + @property + def pdo_count(self) -> int: + """ + Returns current pdo count. + + Returns: + object of `int` type + """ + return self.__pdo_count + + def set_pdo_list(self, pdo_list: List[Pdo]): + """ + Set new Pdo list. + + Args: + pdo_list (`list` with 'Pdo') + """ + self.__pdo_list = copy.deepcopy(pdo_list) + self.__io.write_local_spr_sink_pdo(self.__write_pdo()) + + def set_pdo_by_index(self, pdo_object, index: int): + """ + Set new Pdo by index. + + Args: + pdo_object ('Pdo') + index (`int`) + """ + if not 0 <= index < self.__pdo_count: + raise ValueError(f"Incorrect PDO index. Supported PDO amount: {self.__pdo_count}") + self.__pdo_list[index] = copy.deepcopy(pdo_object) + self.__io.write_local_spr_sink_pdo(self.__write_pdo()) + + def get_pdo_list(self, read_from_device: bool = False) -> List[Pdo]: + """ + Returns current pdo list. + + Args: + read_from_device (`bool`) + + Returns: + object of `list` type with `Pdo`. + """ + if read_from_device: + self.__pdo_list = self.__read_pdo() + return copy.deepcopy(self.__pdo_list) + + def get_pdo_by_index(self, index: int, read_from_device: bool = False) -> Pdo: + """ + Returns current pdo by index. + + Args: + index (`int`) + read_from_device (`bool`) + + Returns: + object of `Pdo` type. + """ + if read_from_device: + self.__pdo_list = self.__read_pdo() + if not 0 <= index < self.__pdo_count: + raise ValueError(f"Incorrect PDO index. Supported PDO amount: {self.__pdo_count}") + return copy.deepcopy(self.__pdo_list[index]) + + def save_pdo(self, path: str): + """ + Save information about PDO's to file. + Supported formats: + - txt + - json + + Args: + path (`str`) + """ + save_pdo(path, self.__pdo_list, False) + + def load_pdo(self, path: str): + """ + Load information about PDO's from file. + Supported formats: + - txt + - json + + Args: + path (`str`) + """ + self.__pdo_list = load_pdo(path, False) + + def __str__(self) -> str: + pdo_status = "" + for index, item in enumerate(self.__pdo_list): + pdo_status += f"PDO {index}\n{item.pdo_object.__str__()}\n" + return f"Power Sink\n{pdo_status}\n" diff --git a/UniTAP/dev/ports/modules/pdc/pdc_power_source.py b/UniTAP/dev/ports/modules/pdc/pdc_power_source.py new file mode 100644 index 0000000..47bb77f --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/pdc_power_source.py @@ -0,0 +1,152 @@ +import copy + +from .pdc_io import PDCPortIO +from .pdc_private_types import PDCControlCommand +from .pdc_utils import load_pdo, save_pdo +from .pdo_types import FixedPdoSource, BatteryPdo, VariablePdo, PdoTypeEnum, PdoSide +from .pdo import Pdo +from .pdo_utils import get_pdo_value +from typing import List + + +class PowerSource: + + """ + Class `PowerSource` contains information about PDO's on Source side. + - Get PDO count `pdo_count`, type `int`. + - Set and get PDO list, `set_pdo_list`, `get_pdo_list`, type `list` with `Pdo`. + - Set and get PDO by index, `set_pdo_by_index`, `get_pdo_by_index`, type `Pdo`. + - Send Source PDO's, `send_pdo`. + - Save information about PDO's to file, `save_pdo`. + - Load information about PDO's from file, `load_pdo`. + """ + + def __init__(self, pdc_io: PDCPortIO): + self.__io = pdc_io + self.__pdo_count = self.__io.read_hw_caps().pdo_count + self.__pdo_list = self.__read_pdo() + + def __read_pdo(self) -> List[Pdo]: + pdo_list = [] + values = self.__io.read_local_spr_source_pdo() + for i in range(self.__pdo_count): + if PdoTypeEnum(values[i].brief_info.pdo_type) == PdoTypeEnum.Fixed and values[i].data > 0: + pdo_list.append(Pdo(pdo=FixedPdoSource(values[i].fixed_pdo_source, disable=False), side=PdoSide.Source)) + elif PdoTypeEnum(values[i].brief_info.pdo_type) == PdoTypeEnum.Fixed and values[i].data == 0: + pdo_list.append(Pdo(pdo=FixedPdoSource(values[i].fixed_pdo_source, disable=True), side=PdoSide.Source)) + elif PdoTypeEnum(values[i].brief_info.pdo_type) == PdoTypeEnum.Battery: + pdo_list.append(Pdo(pdo=BatteryPdo(values[i].battery_pdo), side=PdoSide.Source)) + elif PdoTypeEnum(values[i].brief_info.pdo_type) == PdoTypeEnum.Variable: + pdo_list.append(Pdo(pdo=VariablePdo(values[i].variable_pdo), side=PdoSide.Source)) + return pdo_list + + def __write_pdo(self) -> List[int]: + pdo_list = [] + for index, item in enumerate(self.__pdo_list): + if index == 0: + pdo_list.append(get_pdo_value(item.get_pdo_as_selected_type(FixedPdoSource))) + elif item.pdo_type != PdoTypeEnum.Disabled: + pdo_list.append(get_pdo_value(item.pdo_object)) + else: + pdo_list.append(0) + return pdo_list + + @property + def pdo_count(self) -> int: + """ + Returns current pdo count. + + Returns: + object of `int` type + """ + return self.__pdo_count + + def send_pdo(self): + """ + Send Source PDO's. + """ + self.__io.write_pdc_command(PDCControlCommand.SendSourceCaps) + + def get_pdo_list(self, read_from_device: bool = False) -> List[Pdo]: + """ + Returns current pdo list. + + Args: + read_from_device (`bool`) + + Returns: + object of `list` type with `Pdo`. + """ + if read_from_device: + self.__pdo_list = self.__read_pdo() + return copy.deepcopy(self.__pdo_list) + + def get_pdo_by_index(self, index: int, read_from_device: bool = False): + """ + Returns current pdo by index. + + Args: + index (`int`) + read_from_device (`bool`) + + Returns: + object of `Pdo` type. + """ + if read_from_device: + self.__pdo_list = self.__read_pdo() + if not 0 <= index < self.__pdo_count: + raise ValueError(f"Incorrect PDO index. Supported PDO amount: {self.__pdo_count}") + return copy.deepcopy(self.__pdo_list[index]) + + def set_pdo_list(self, pdo_list: List[Pdo]): + """ + Set new Pdo list. + + Args: + pdo_list (`list` with 'Pdo') + """ + self.__pdo_list = copy.deepcopy(pdo_list) + self.__io.write_local_spr_source_pdo(self.__write_pdo()) + + def set_pdo_by_index(self, pdo_object, index: int): + """ + Set new Pdo by index. + + Args: + pdo_object ('Pdo') + index (`int`) + """ + if not 0 <= index < self.__pdo_count: + raise ValueError(f"Incorrect PDO index. Supported PDO amount: {self.__pdo_count}") + self.__pdo_list[index] = copy.deepcopy(pdo_object) + self.__io.write_local_spr_source_pdo(self.__write_pdo()) + + def save_pdo(self, path: str): + """ + Save information about PDO's to file. + Supported formats: + - txt + - json + + Args: + path (`str`) + """ + save_pdo(path, self.__pdo_list, True) + + def load_pdo(self, path: str): + """ + Load information about PDO's from file. + Supported formats: + - txt + - json + + Args: + path (`str`) + """ + self.__pdo_list = load_pdo(path, True) + + def __str__(self) -> str: + pdo_status = "" + for index, item in enumerate(self.__pdo_list): + pdo_status += f"PDO {index}\n{item.pdo_object.__str__()}\n" + return f"Power Source\n{pdo_status}\n" diff --git a/UniTAP/dev/ports/modules/pdc/pdc_private_dpam_types.py b/UniTAP/dev/ports/modules/pdc/pdc_private_dpam_types.py new file mode 100644 index 0000000..996cd43 --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/pdc_private_dpam_types.py @@ -0,0 +1,104 @@ +from ctypes import Structure, c_uint32 +from enum import IntEnum +from .pdc_dpam_types import DISCCapability, DISCDpSignaling, PinAssignmentModes + + +class DPAMCommand(IntEnum): + DoesNothing = 0x00 + AutoEnterDPAM = 0x01 + EnterDPAM2lanes = 0x02 + EnterDPAM4lanes = 0x03 + ExitDPAM = 0x04 + DisableDPAM = 0x05 + ManualDPAM = 0x06 + DisableDPtoTypeCAdapter = 0x08 + EnableDPtoTypeCAdapter = 0x09 + ClearMFPreferredFlag = 0x0E + SetMFPreferredFlag = 0x0F + ClearAlignDpCUsb = 0x30 + SetAlignDpCUsb = 0x31 + ClearAlignDpDUsb = 0x32 + SetAlignDpDUsb = 0x33 + ClearAlignDpEUsb = 0x34 + SetAlignDpEUsb = 0x35 + EnableDPAM2_1 = 0x38 + DisableDPAM2_1 = 0x39 + + +class DPAMCaps(Structure): + _fields_ = [ + ('', c_uint32, 8), + ('dfp_assign', c_uint32, 8), + ('ufp_assign', c_uint32, 8), + ('', c_uint32, 6), + ('dpam_version', c_uint32, 2) + ] + + def value(self) -> int: + return self.dfp_assign << 8 | self.dfp_assign << 16 | self.dpam_version << 30 + + +class DPAMState(Structure): + _fields_ = [ + ('auto_dpam', c_uint32, 1), + ('dpam_enable', c_uint32, 1), + ('adapter_mode', c_uint32, 1), + ('', c_uint32, 1), + ('align_dp_c_usb', c_uint32, 1), + ('align_dp_d_usb', c_uint32, 1), + ('align_dp_e_usb', c_uint32, 1), + ('', c_uint32, 25) + ] + + +class DPAMConfig(Structure): + _fields_ = [ + ('config', c_uint32, 2), + ('signaling', c_uint32, 4), + ('', c_uint32, 2), + ('pin_assign', c_uint32, 8), + ('', c_uint32, 10), + ('uhbr_13_5_support', c_uint32, 1), + ('', c_uint32, 1), + ('cable_active_component', c_uint32, 2), + ('dpam_version', c_uint32, 2) + ] + + +class DPAMDiscModes(Structure): + _fields_ = [ + ('capability', c_uint32, 2), + ('signaling', c_uint32, 4), + ('dp_placement', c_uint32, 1), + ('usb_signaling', c_uint32, 1), + ('dfp_assign', c_uint32, 8), + ('ufp_assign', c_uint32, 8), + ('', c_uint32, 6), + ('dpam_version', c_uint32, 2) + ] + + @property + def capability(self) -> DISCCapability: + return DISCCapability(self.capability) + + @property + def signaling(self) -> DISCDpSignaling: + return DISCDpSignaling(self.signaling) + + +class DPAMRecv(Structure): + _fields_ = [ + ('connection', c_uint32, 2), + ('power_low', c_uint32, 1), + ('enable', c_uint32, 1), + ('multi_func', c_uint32, 1), + ('usb_cfg_rq', c_uint32, 1), + ('exit_rq', c_uint32, 1), + ('hpd_state', c_uint32, 1), + ('hpd_irq', c_uint32, 1), + ('no_dpam_suspend', c_uint32, 1), + ] + + @property + def connection(self) -> DISCCapability: + return DISCCapability(self.capability) diff --git a/UniTAP/dev/ports/modules/pdc/pdc_private_types.py b/UniTAP/dev/ports/modules/pdc/pdc_private_types.py new file mode 100644 index 0000000..8813683 --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/pdc_private_types.py @@ -0,0 +1,227 @@ +from ctypes import Structure, c_uint32, c_uint64 +from enum import IntEnum +from .pdc_types import CCPullUp, ContractTypePriority, DRPTryMode, USB3Mode, PdcDeviceRole, PDMode + + +class HWControlCommand(IntEnum): + DoNothing = 0x00 + ToggleCableFlip = 0x01 + SetCableFlipToOff = 0x02 + SetCableFlipToOn = 0x03 + EnableUSBDeviceCharger = 0x04 + DisableUSBDeviceCharger = 0x05 + SwitchToHostMode = 0x06 + SwitchToDeviceMode = 0x07 + Enable20PHY = 0x08 + Disable20PHY = 0x09 + Enable20PHYDeviceModeEnableCharger = 0x0A + Enable20PHYHostModeDisableCharger = 0x0B + Disable20PHYDisableCharger = 0x0C + DisableIntLoad_5_5_ohm = 0x0E + EnableIntLoad_5_5_ohm = 0x0F + DisableIntLoad_3_5_ohm = 0x10 + EnableIntLoad_3_5_ohm = 0x11 + DisableIntLoad_10_ohm = 0x12 + EnableIntLoad_10_ohm = 0x13 + DisconnectCCLines = 0x14 + ConnectCCLines = 0x15 + RePlugCable = 0x16 + SetNewETCableMode = 0x18 + SetOldETCableMode = 0x19 + Enable30PHY = 0x1A + Disable30PHY = 0x1B + ResetPDController = 0x1C + + +class PDCControlCommand(IntEnum): + DoNothing = 0x00 + ToggleDataRole = 0x01 + SetDataRoleUFP = 0x02 + SetDataRoleDFP = 0x03 + SwapVconn = 0x04 + TogglePowerRole = 0x05 + FastRoleSwap = 0x06 + HardReset = 0x07 + SimulateReConnect = 0x0A + PullCCLineToDefault = 0x0C + PullCCLineTo1_5A = 0x0D + PullCCLineTo3_0A = 0x0E + SetDataRoleDRP = 0x14 + SetDataRoleDRP_SNK_Support = 0x15 + SetDataRoleDRP_SRC_Support = 0x16 + DisableUSB3 = 0x18 + EnableUSB3_gen1 = 0x19 + EnableUSB3_gen2 = 0x1A + DisableUSB2 = 0x1C + EnableUSB2 = 0x1D + DisableVconnSwap = 0x1E + EnableVconnSwap = 0x1F + DisablePRSwap = 0x20 + EnablePRSwap = 0x21 + DisableDRSwap = 0x22 + EnableDRSwap = 0x23 + DisableDebugAccessorySupport = 0x24 + EnableDebugAccessorySupport = 0x25 + DisableAudioAccessorySupport = 0x26 + EnableAudioAccessorySupport = 0x27 + DisableFRSwap = 0x28 + EnableFRSwap = 0x29 + SetFRSwapNoSupported = 0x2A + SetFRSwapUSBPower = 0x2B + SetFRSwap1_5A_5V = 0x2C + SetFRSwap3A_5V = 0x2C + EnableEPRasPowerSink = 0x2E + DisableEPRasPowerSink = 0x2F + EnableEPRAutoEnter = 0x30 + DisableEPRAutoEnter = 0x31 + InitiateEPRModeEnter = 0x32 + InitiateEPRModeExit = 0x33 + EnableRejectEPRModeEnter = 0x34 + DisableRejectEPRModeEnter = 0x35 + SendSourceCaps = 0x41 + + +class HWCaps(Structure): + _fields_ = [ + ('', c_uint32, 1), + ('et_board', c_uint32, 1), + ('', c_uint32, 2), + ('pdo_count', c_uint32, 4), + ('', c_uint32, 24) + ] + + +class HWStatus(Structure): + _fields_ = [ + ('cable_flip', c_uint32, 1), + ('ext_power', c_uint32, 1), + ('charger', c_uint32, 1), + ('usb_mode', c_uint32, 1), + ('usb_20_phy', c_uint32, 1), + ('et_cable', c_uint32, 1), + ('int_3_5_ohm', c_uint32, 1), + ('int_10_ohm', c_uint32, 1), + ('cc1_closed', c_uint32, 1), + ('cc2_closed', c_uint32, 1), + ('int_5_5_ohm', c_uint32, 1), + ('et_cable_mode', c_uint32, 1), + ('usb_30_phy', c_uint32, 1), + ('int_3_5_ovp', c_uint32, 1), + ('int_10_ovp', c_uint32, 1), + ('int_5_5_ovp', c_uint32, 1), + ('', c_uint32, 13), + ('pdc_fault', c_uint32, 1), + ('', c_uint32, 1), + ('pdc_reset', c_uint32, 1) + ] + + +class PDCStatus(Structure): + _fields_ = [ + ('cable_plug', c_uint64, 1), + ('cable_orientation', c_uint64, 1), + ('power_role', c_uint64, 1), + ('data_role', c_uint64, 1), + ('rmt_snk_pdo', c_uint64, 1), + ('rmt_src_pdo', c_uint64, 1), + ('pwr_contract', c_uint64, 1), + ('rmt_svid', c_uint64, 1), + ('vconn_enabled', c_uint64, 1), + ('emarked_cable', c_uint64, 1), + ('dp_alt_mode', c_uint64, 1), + ('usb_device', c_uint64, 1), + ('usb_host', c_uint64, 1), + ('usb_2_conn', c_uint64, 1), + ('usb_3_conn', c_uint64, 1), + ('sop_identity', c_uint64, 1), + ('current', c_uint64, 2), + ('adc_data', c_uint64, 1), + ('am_sw_inprog', c_uint64, 1), + ('pullup_sence', c_uint64, 2), + ('rx_identity', c_uint64, 1), + ('', c_uint64, 6), + ('pdc_fault', c_uint64, 1), + ('', c_uint64, 2), + ('rx_sink_pdp', c_uint64, 1), + ('rx_source_pdp', c_uint64, 1), + ('rx_snk_epr_pdo', c_uint64, 1), + ('rx_src_epr_pdo', c_uint64, 1), + ('', c_uint64, 28) + ] + + +class PDCState(Structure): + _fields_ = [ + ('cc_pull_up', c_uint32, 2), + ('dev_role', c_uint32, 2), + ('pd_mode', c_uint32, 2), + ('usb30_mode', c_uint32, 2), + ('usb2_enabled', c_uint32, 1), + ('vconn_swap_en', c_uint32, 1), + ('pr_swap_en', c_uint32, 1), + ('dr_swap_en', c_uint32, 1), + ('dbg_accessory', c_uint32, 1), + ('aud_accessory', c_uint32, 1), + ('fr_swap_en', c_uint32, 1), + ('', c_uint32, 1), + ('drp_try_mode', c_uint32, 2), + ('', c_uint32, 2), + ('epr_snk_en', c_uint32, 1), + ('epr_auto_enter_en', c_uint32, 1), + ('epr_mode', c_uint32, 1), + ('epr_enter_en', c_uint32, 1), + ] + + @property + def cc_pull_up(self) -> CCPullUp: + return CCPullUp(self.cc_pull_up) + + @property + def dev_role(self) -> PdcDeviceRole: + return PdcDeviceRole(self.dev_role) + + @property + def pd_mode(self) -> PDMode: + return PDMode(self.pd_mode) + + @property + def usb30_mode(self) -> USB3Mode: + return USB3Mode(self.usb30_mode) + + @property + def drp_try_mode(self) -> DRPTryMode: + return DRPTryMode(self.drp_try_mode) + + +class PDCCaps(Structure): + _fields_ = [ + ('try_snk', c_uint32, 1), + ('try_src', c_uint32, 1), + ('fr_swap', c_uint32, 1), + ('cable_sim', c_uint32, 1), + ('ext_sink_pc', c_uint32, 1), + ('align_dp_usb', c_uint32, 1), + ('', c_uint32, 16) + ] + + +class PDCContractInfo(Structure): + _fields_ = [ + ('auto_negotate', c_uint32, 1), + ('use_battery_pdo', c_uint32, 1), + ('use_variable_pdo', c_uint32, 1), + ('comm_capable', c_uint32, 1), + ('type_priority', c_uint32, 2), + ('no_usb_suspend', c_uint32, 1), + ('give_back_flag', c_uint32, 1), + ('auto_min_power', c_uint32, 1), + ('', c_uint32, 6), + ('min_power', c_uint32, 10), + ('', c_uint32, 5), + ('manual', c_uint32, 1), + ] + + def value(self) -> int: + return self.auto_negotate | self.use_battery_pdo << 1 | self.use_variable_pdo << 2 | self.comm_capable << 3 | \ + self.type_priority << 4 | self.no_usb_suspend << 6 | self.give_back_flag << 7 | \ + self.auto_min_power << 8 | self.min_power << 15 | self.manual << 31 diff --git a/UniTAP/dev/ports/modules/pdc/pdc_types.py b/UniTAP/dev/ports/modules/pdc/pdc_types.py new file mode 100644 index 0000000..6ab7bff --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/pdc_types.py @@ -0,0 +1,125 @@ +from enum import IntEnum + + +class CCPullUp(IntEnum): + """ + Class `CCPullUp` contains all possible variants of CC Pull Up. + """ + Current_05_09A = 0 + Current_1_5A = 1 + Current_3A = 2 + Unknown = 3 + + +class PowerRole(IntEnum): + """ + Class `PowerRole` contains all possible variants of device power role. + """ + Source = 0 + Sink = 1 + + +class FrSwapCurrent(IntEnum): + """ + Class `FrSwapCurrent` contains all possible variants of FR Swap current. + """ + Disable = 0 + EnableDefaultUSBPower = 1 + Enable_1_5A_5V = 2 + Enable_3A_5V = 3 + + +class PdcDeviceRole(IntEnum): + """ + Class `PdcDeviceRole` contains all possible variants of PDC device role. + """ + UFP = 0 + DFP = 1 + DRP = 2 + Unknown = 3 + + +class PDMode(IntEnum): + """ + Class `PDMode` contains all possible variants of PD modes. + """ + NormalPD = 0 + LegacyUSBSource = 1 + LegacyUSBSink = 2 + Unknown = 3 + + +class USB3Mode(IntEnum): + """ + Class `USB3Mode` contains all possible variants of USB-3 modes. + """ + Disabled = 0 + EnabledGen1 = 1 + EnabledGen2 = 2 + Unknown = 3 + + +class DRPTryMode(IntEnum): + """ + Class `DRPTryMode` contains all possible variants of DRP behavior modes. + """ + PureDRP = 0 + DRP_try_SNK = 1 + DRP_try_SRC = 2 + Unknown = 3 + + +class CableControlOrientation(IntEnum): + """ + Class `CableControlOrientation` contains all possible variants of orientation. + """ + CC1 = 0 + CC2 = 1 + + +class DifferentialPair(IntEnum): + """ + Class `DifferentialPair` contains all possible variants of differential pairs. + """ + OnePair = 0 + TwoPair = 1 + + +class ContractTypePriority(IntEnum): + """ + Class `ContractTypePriority` contains all possible variants of contract type priority. + """ + HigherCurrent = 0 + HigherVoltage = 1 + HigherPower = 2 + Unknown = 3 + + +class InternalResistance(IntEnum): + """ + Class `InternalResistance` contains all possible variants of internal resistance. + """ + Disable = 1 + Resistance_10_Ohm = 40001 + Resistance_5_5_Ohm = 22001 + Resistance_3_55_Ohm = 14161 + Resistance_3_5_Ohm = 14001 + Resistance_2_6_Ohm = 10361 + Resistance_2_14_Ohm = 8521 + Resistance_1_76_Ohm = 7001 + + +class ExternalResistance(IntEnum): + """ + Class `ExternalResistance` contains all possible variants of external resistance. + """ + Disable = 3 + Resistance_13_9_Ohm = 55603 + Resistance_10_6_Ohm = 42403 + Resistance_9_1_Ohm = 36403 + Resistance_7_6_Ohm = 30403 + Resistance_6_6_Ohm = 26403 + Resistance_5_6_Ohm = 22403 + Resistance_4_6_Ohm = 18403 + Resistance_3_6_Ohm = 14403 + Resistance_1_8_Ohm = 7203 diff --git a/UniTAP/dev/ports/modules/pdc/pdc_utils.py b/UniTAP/dev/ports/modules/pdc/pdc_utils.py new file mode 100644 index 0000000..b6eacf1 --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/pdc_utils.py @@ -0,0 +1,72 @@ +import warnings +import os +import json + +from UniTAP.dev.ports.modules.pdc.pdo import Pdo +from UniTAP.dev.ports.modules.pdc.pdo_utils import get_pdo_value +from UniTAP.dev.ports.modules.pdc.pdo_types import * +from UniTAP.dev.ports.modules.pdc.pdo_private_types import PdoUnion + + +def support_or_not(value) -> str: + return "Yes" if bool(value) else "No" + + +def save_pdo(path: str, pdo_list: list, source: bool): + filename, file_extension = os.path.splitext(path) + if file_extension.find('.txt') != -1: + file = open(path, 'w') + file.write("TSI_USBC_PWR_LOCAL_SOURCE_PDO\n" if source else "TSI_USBC_PWR_LOCAL_SINK_PDO\n") + file.write("1\n") + file.write(str(len(pdo_list) * 4)) + for pdo in pdo_list: + file.write(str(get_pdo_value(pdo.get_pdo_object()))) + file.close() + elif file_extension.find('.json') != -1: + values = {} + for index, pdo in enumerate(pdo_list): + values.update({f"EPR{index}": get_pdo_value(pdo.get_pdo_object())}) + with open(path, "w") as outfile: + json.dump(values, outfile) + else: + warnings.warn(f"Unsupported file format: {file_extension}. " + f"Please, select file format from available variants: 'txt', 'json'") + + +def load_pdo(path: str, source: bool) -> list: + if os.path.exists(path): + raise ValueError(f"Incorrect path {path} to file. Path does not exist.") + filename, file_extension = os.path.splitext(path) + pdo_list = [] + if file_extension.find('.txt') != -1: + file = open(path, 'r') + data = file.read().split("\n") + for index, line in enumerate(data): + if index >= 3: + fill_list(int(line), pdo_list, source) + file.close() + elif file_extension.find('.json') != -1: + with open(path, "r", encoding='utf-8') as read_file: + data = json.load(read_file) + keys = list(data.keys()) + for index, key in enumerate(keys): + fill_list(int(data.get(key)), pdo_list, source) + else: + raise ValueError(f"Unsupported file format: {file_extension}. " + f"Please, select file format from available variants: 'txt', 'json'") + return pdo_list + + +def fill_list(data, pdo_list, source): + pdo_data = PdoUnion(data) + pdo_type = PdoTypeEnum(pdo_data.brief_info.pdo_type) + if pdo_type == PdoTypeEnum.Fixed: + pdo_list.append(Pdo(pdo=FixedPdoSource(pdo_data.fixed_pdo_source) if source else + FixedPdoSink(pdo_data.fixed_pdo_sink), + side=PdoSide.Source if source else PdoSide.Sink)) + elif pdo_type == PdoTypeEnum.Battery: + pdo_list.append(Pdo(pdo=BatteryPdo(pdo_data.battery_pdo), + side=PdoSide.Source if source else PdoSide.Sink)) + elif pdo_type == PdoTypeEnum.Variable: + pdo_list.append(Pdo(pdo=VariablePdo(pdo_data.variable_pdo), + side=PdoSide.Source if source else PdoSide.Sink)) diff --git a/UniTAP/dev/ports/modules/pdc/pdo.py b/UniTAP/dev/ports/modules/pdc/pdo.py new file mode 100644 index 0000000..b079eab --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/pdo.py @@ -0,0 +1,99 @@ +from typing import Type + +from UniTAP.dev.ports.modules.pdc.pdo_utils import interpret_pdo_value +from UniTAP.dev.ports.modules.pdc.pdo_types import PdoSide, PdoType, FixedPdoSink, FixedPdoSource, BatteryPdo, \ + VariablePdo, PdoTypeEnum + + +class Pdo: + + """ + Class `Pdo` describes power delivery object in PDC module. Contains information about side of PDO `PdoSide` and + main PDO data. May have one of the available type `PdoType`: `FixedPdoSink`, `FixedPdoSource`, `BatteryPdo`, + `VariablePdo`. + - Get PDO type `pdo_type`, type of `PdoTypeEnum`. + - Get PDO side `pdo_side`, type of `PdoSide`. + - Set and get main PDO object `pdo_object`, type of `PdoType`. + - Get pdo object as selected PDO type `PdoType` `get_pdo_as_selected_type`, type of `PdoType`. + - Convert (interpret) from one PDO type to another `interpret_pdo_as_selected_type`. + """ + + def __init__(self, pdo: PdoType, side: PdoSide): + self.__pdo = pdo + self.__side = side + + @property + def pdo_type(self) -> PdoTypeEnum: + """ + Returns current PDO type. + + Returns: + object of `PdoTypeEnum` type + """ + return self.__pdo.pdo_type + + @property + def pdo_side(self) -> PdoSide: + """ + Returns current PDO side. + + Returns: + object of `PdoSide` type + """ + return self.__side + + @property + def pdo_object(self) -> PdoType: + """ + Returns current PDO object. + + Returns: + object of `PdoType` type + """ + return self.__pdo + + @pdo_object.setter + def pdo_object(self, pdo: PdoType): + self.__pdo = pdo + + def disable_pdo(self): + """ + Disable PDO. Will be filled with zeros. + """ + self.__pdo = FixedPdoSink(disable=True) if self.__side == PdoSide.Sink else FixedPdoSource(disable=True) + + def get_pdo_as_selected_type(self, pdo: Type[PdoType]) -> PdoType: + """ + Returns PDO object as selected new PDO type. + + Args: + pdo (`PdoType`) - type of PDO + + Returns: + object of `PdoType` type + """ + self.interpret_pdo_as_selected_type(pdo) + return self.__pdo + + def interpret_pdo_as_selected_type(self, pdo: Type[PdoType]): + """ + Convert (interpret) from one PDO type to another. + + Args: + pdo (`PdoType`) - type of PDO + """ + if pdo == FixedPdoSink and self.__side == PdoSide.Sink: + self.__pdo = FixedPdoSink(interpret_pdo_value(self.__pdo, pdo)) + elif pdo == FixedPdoSource and self.__side == PdoSide.Source: + self.__pdo = FixedPdoSource(interpret_pdo_value(self.__pdo, pdo)) + elif pdo == BatteryPdo: + self.__pdo = BatteryPdo(interpret_pdo_value(self.__pdo, pdo)) + elif pdo == VariablePdo: + self.__pdo = VariablePdo(interpret_pdo_value(self.__pdo, pdo)) + else: + raise ValueError(f"Incorrect PDO type: {pdo}. Available types: BatteryPdo, VariablePdo, " + f"{'FixedPdoSink' if self.__side == PdoSide.Sink else 'FixedPdoSource'}") + + def __str__(self): + return f'PDO type: {self.pdo_type.name}\n' \ + f'{self.__pdo.__str__()}' diff --git a/UniTAP/dev/ports/modules/pdc/pdo_private_types.py b/UniTAP/dev/ports/modules/pdc/pdo_private_types.py new file mode 100644 index 0000000..2c1e67a --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/pdo_private_types.py @@ -0,0 +1,74 @@ +from typing import TypeVar +from ctypes import Structure, Union, c_uint32 + + +class FixedPdoSourceStruct(Structure): + _fields_ = [ + ('max_current', c_uint32, 10), + ('voltage', c_uint32, 10), + ('peak_current', c_uint32, 2), + ('', c_uint32, 3), + ('dual_data_role', c_uint32, 1), + ('usb_communication_capable', c_uint32, 1), + ('externally_powered', c_uint32, 1), + ('usb_suspend_supported', c_uint32, 1), + ('dual_power_role_capable', c_uint32, 1), + ('pdo_type', c_uint32, 2), + ] + + +class FixedPdoSinkStruct(Structure): + _fields_ = [ + ('oper_current', c_uint32, 10), + ('voltage', c_uint32, 10), + ('', c_uint32, 5), + ('dual_data_role', c_uint32, 1), + ('usb_communication_capable', c_uint32, 1), + ('externally_powered', c_uint32, 1), + ('usb_suspend_supported', c_uint32, 1), + ('dual_power_role_capable', c_uint32, 1), + ('pdo_type', c_uint32, 2), + ] + + +class VariablePdoStruct(Structure): + _fields_ = [ + ('max_current', c_uint32, 10), + ('min_voltage', c_uint32, 10), + ('max_voltage', c_uint32, 10), + ('pdo_type', c_uint32, 2), + ] + + +class BatteryPdoStruct(Structure): + _fields_ = [ + ('max_power', c_uint32, 10), + ('min_voltage', c_uint32, 10), + ('max_voltage', c_uint32, 10), + ('pdo_type', c_uint32, 2), + ] + + +class PdoBriefInfo(Structure): + _fields_ = [ + ('data', c_uint32, 30), + ('pdo_type', c_uint32, 2) + ] + + +class PdoUnion(Union): + _fields_ = [ + ('data', c_uint32), + ('brief_info', PdoBriefInfo), + ('fixed_pdo_source', FixedPdoSourceStruct), + ('fixed_pdo_sink', FixedPdoSinkStruct), + ('variable_pdo', VariablePdoStruct), + ('battery_pdo', BatteryPdoStruct) + ] + + +PdoTypeStruct = TypeVar("PdoTypeStruct", + FixedPdoSinkStruct, + FixedPdoSourceStruct, + BatteryPdoStruct, + VariablePdoStruct) diff --git a/UniTAP/dev/ports/modules/pdc/pdo_types.py b/UniTAP/dev/ports/modules/pdc/pdo_types.py new file mode 100644 index 0000000..c3c4354 --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/pdo_types.py @@ -0,0 +1,488 @@ +from enum import IntEnum +from typing import TypeVar +from .pdo_private_types import FixedPdoSourceStruct, FixedPdoSinkStruct, BatteryPdoStruct, VariablePdoStruct + + +class PdoTypeEnum(IntEnum): + """ + Class `PdoTypeEnum` contains all possible variants of PDO types. + """ + Fixed = 0 + Battery = 1 + Variable = 2 + Disabled = 3 + Mandatory = 4 + + +class PdoSide(IntEnum): + """ + Class `PdoTypeEnum` contains all possible variants of PDO side. + """ + Sink = 0 + Source = 1 + + +class PeakCurrent(IntEnum): + """ + Class `PdoTypeEnum` contains all possible variants of peak current for PDO. + """ + Percent_100 = 0 + Percent_110 = 1 + Percent_125 = 2 + Percent_150 = 3 + + +class FixedPdoSource: + + """ + Class `FixedPdoSource` contains information about Fixed PDO on Source side. + - Set and get maximum current `max_current`, type `int`. + - Set and get voltage `voltage`, type `int`. + - Set and get peak_current `peak_current`, type `PeakCurrent`. + - Set and get dual data role flag `dual_data_role`, type `bool`. + - Set and get usb communication flag `usb_communication`, type `bool`. + - Set and get unconstrained power flag `unconstrained_power`, type `bool`. + - Set and get higher capability flag `higher_capability`, type `bool`. + - Set and get dual power role flag `dual_power_role`, type `bool`. + - Get PDO type `pdo_type`, type `PdoTypeEnum`. + """ + + def __init__(self, pdo: FixedPdoSourceStruct = FixedPdoSourceStruct(0), disable=False): + self.__max_current = pdo.max_current * 10 + self.__voltage = pdo.voltage * 50 + self.__peak_current = PeakCurrent(pdo.peak_current) + self.__dual_data_role = pdo.dual_data_role + self.__usb_communication = pdo.usb_communication_capable + self.__unconstrained_power = pdo.externally_powered + self.__higher_capability = pdo.usb_suspend_supported + self.__dual_power_role = pdo.dual_power_role_capable + self.__type = PdoTypeEnum.Disabled if disable else PdoTypeEnum.Fixed + + @property + def max_current(self) -> int: + """ + Returns maximum current. + + Returns: + object of `int` type + """ + return self.__max_current + + @max_current.setter + def max_current(self, max_current: int): + self.__max_current = max_current + + @property + def voltage(self) -> int: + """ + Returns voltage. + + Returns: + object of `int` type + """ + return self.__voltage + + @voltage.setter + def voltage(self, voltage: int): + self.__voltage = voltage + + @property + def peak_current(self) -> PeakCurrent: + """ + Returns peak current. + + Returns: + object of `PeakCurrent` type + """ + return self.__peak_current + + @peak_current.setter + def peak_current(self, peak_current: PeakCurrent): + self.__peak_current = peak_current + + @property + def dual_data_role(self) -> bool: + """ + Returns flag of dual data role. + + Returns: + object of `bool` type + """ + return bool(self.__dual_data_role) + + @dual_data_role.setter + def dual_data_role(self, dual_data_role: bool): + self.__dual_data_role = dual_data_role + + @property + def usb_communication(self) -> bool: + """ + Returns flag of usb communication. + + Returns: + object of `bool` type + """ + return bool(self.__usb_communication) + + @usb_communication.setter + def usb_communication(self, usb_communication: bool): + self.__usb_communication = usb_communication + + @property + def unconstrained_power(self) -> bool: + """ + Returns flag of unconstrained power. + + Returns: + object of `bool` type + """ + return bool(self.__unconstrained_power) + + @unconstrained_power.setter + def unconstrained_power(self, unconstrained_power: bool): + self.__unconstrained_power = unconstrained_power + + @property + def higher_capability(self) -> bool: + """ + Returns flag of higher capability. + + Returns: + object of `bool` type + """ + return bool(self.__higher_capability) + + @higher_capability.setter + def higher_capability(self, higher_capability: bool): + self.__higher_capability = higher_capability + + @property + def dual_power_role(self) -> bool: + """ + Returns flag of dual power role. + + Returns: + object of `bool` type + """ + return bool(self.__dual_power_role) + + @dual_power_role.setter + def dual_power_role(self, dual_power_role: bool): + self.__dual_power_role = dual_power_role + + @property + def pdo_type(self) -> PdoTypeEnum: + """ + Returns flag of PDO type. + + Returns: + object of `PdoTypeEnum` type + """ + return self.__type + + def __str__(self): + return f"Max Current: {self.__max_current}\n" \ + f"Voltage: {self.__voltage}\n" \ + f"Peak Current: {self.__peak_current.name}\n" \ + f"Dual Role: {'Yes' if self.__dual_data_role else 'No'}\n" \ + f"USB Communication: {'Yes' if self.__usb_communication else 'No'}\n" \ + f"Unconstrained Power: {'Yes' if self.__unconstrained_power else 'No'}\n" \ + f"Higher capability: {'Yes' if self.__higher_capability else 'No'}\n" \ + f"Dual Power role: {'Yes' if self.__dual_power_role else 'No'}\n" + + +class VariablePdo: + + """ + Class `VariablePdo` contains information about Fixed PDO on Sink and Source side. + - Set and get maximum current `max_current`, type `int`. + - Set and get minimum voltage `min_voltage`, type `int`. + - Set and get maximum voltage `max_voltage`, type `bool`. + - Get PDO type `pdo_type`, type `PdoTypeEnum`. + """ + + def __init__(self, pdo: VariablePdoStruct = VariablePdoStruct(0)): + self.__max_current = pdo.max_current * 10 + self.__min_voltage = pdo.min_voltage * 50 + self.__max_voltage = pdo.max_voltage * 50 + self.__type = PdoTypeEnum.Variable + + @property + def max_current(self) -> int: + """ + Returns maximum current. + + Returns: + object of `int` type + """ + return self.__max_current + + @max_current.setter + def max_current(self, max_current: int): + self.__max_current = max_current + + @property + def min_voltage(self) -> int: + """ + Returns minimum voltage. + + Returns: + object of `int` type + """ + return self.__min_voltage + + @min_voltage.setter + def min_voltage(self, min_voltage: int): + self.__min_voltage = min_voltage + + @property + def max_voltage(self) -> int: + """ + Returns maximum voltage. + + Returns: + object of `int` type + """ + return self.__max_voltage + + @max_voltage.setter + def max_voltage(self, max_voltage: int): + self.__max_voltage = max_voltage + + @property + def pdo_type(self) -> PdoTypeEnum: + """ + Returns flag of PDO type. + + Returns: + object of `PdoTypeEnum` type + """ + return self.__type + + def __str__(self): + return f"Max Current: {self.__max_current}\n" \ + f"Min Voltage: {self.__min_voltage}\n" \ + f"Max Voltage: {self.__max_voltage}" + + +class BatteryPdo: + + """ + Class `BatteryPdo` contains information about Fixed PDO on Sink and Source side. + - Set and get maximum power `max_power`, type `int`. + - Set and get minimum voltage `min_voltage`, type `int`. + - Set and get maximum voltage `max_voltage`, type `bool`. + - Get PDO type `pdo_type`, type `PdoTypeEnum`. + """ + + def __init__(self, pdo: BatteryPdoStruct = BatteryPdoStruct(0)): + self.__max_power = pdo.max_power * 250 + self.__min_voltage = pdo.min_voltage * 50 + self.__max_voltage = pdo.max_voltage * 50 + self.__type = PdoTypeEnum.Battery + + @property + def max_power(self) -> int: + """ + Returns maximum power. + + Returns: + object of `int` type + """ + return self.__max_power + + @max_power.setter + def max_power(self, max_power: int): + self.__max_power = max_power + + @property + def min_voltage(self) -> int: + """ + Returns minimum voltage. + + Returns: + object of `int` type + """ + return self.__min_voltage + + @min_voltage.setter + def min_voltage(self, min_voltage: int): + self.__min_voltage = min_voltage + + @property + def max_voltage(self) -> int: + """ + Returns maximum voltage. + + Returns: + object of `int` type + """ + return self.__max_voltage + + @max_voltage.setter + def max_voltage(self, max_voltage: int): + self.__max_voltage = max_voltage + + @property + def pdo_type(self) -> PdoTypeEnum: + """ + Returns flag of PDO type. + + Returns: + object of `PdoTypeEnum` type + """ + return self.__type + + def __str__(self): + return f"Max power: {self.__max_power}\n" \ + f"Min Voltage: {self.__min_voltage}\n" \ + f"Max Voltage: {self.__max_voltage}" + + +class FixedPdoSink: + + """ + Class `FixedPdoSink` contains information about Fixed PDO on Sink side. + - Set and get maximum current `max_current`, type `int`. + - Set and get voltage `voltage`, type `int`. + - Set and get dual data role flag `dual_data_role`, type `bool`. + - Set and get usb communication flag `usb_communication`, type `bool`. + - Set and get unconstrained power flag `unconstrained_power`, type `bool`. + - Set and get higher capability flag `higher_capability`, type `bool`. + - Set and get dual power role flag `dual_power_role`, type `bool`. + - Get PDO type `pdo_type`, type `PdoTypeEnum`. + """ + + def __init__(self, pdo: FixedPdoSinkStruct = FixedPdoSinkStruct(0), disable=False): + self.__oper_current = pdo.oper_current * 10 + self.__voltage = pdo.voltage * 50 + self.__dual_data_role = bool(pdo.dual_data_role) + self.__usb_communication = bool(pdo.usb_communication_capable) + self.__unconstrained_power = bool(pdo.externally_powered) + self.__higher_capability = bool(pdo.usb_suspend_supported) + self.__dual_power_role = bool(pdo.dual_power_role_capable) + self.__type = PdoTypeEnum.Disabled if disable else PdoTypeEnum.Fixed + + @property + def oper_current(self) -> int: + """ + Returns operation current. + + Returns: + object of `int` type + """ + return self.__oper_current + + @oper_current.setter + def oper_current(self, oper_current: int): + self.__oper_current = oper_current + + @property + def voltage(self) -> int: + """ + Returns voltage. + + Returns: + object of `int` type + """ + return self.__voltage + + @voltage.setter + def voltage(self, voltage: int): + self.__voltage = voltage + + @property + def dual_data_role(self) -> bool: + """ + Returns flag of dual data role. + + Returns: + object of `bool` type + """ + return bool(self.__dual_data_role) + + @dual_data_role.setter + def dual_data_role(self, dual_data_role: bool): + self.__dual_data_role = dual_data_role + + @property + def usb_communication(self) -> bool: + """ + Returns flag of usb communication. + + Returns: + object of `bool` type + """ + return bool(self.__usb_communication) + + @usb_communication.setter + def usb_communication(self, usb_communication: bool): + self.__usb_communication = usb_communication + + @property + def unconstrained_power(self) -> bool: + """ + Returns flag of unconstrained power. + + Returns: + object of `bool` type + """ + return bool(self.__unconstrained_power) + + @unconstrained_power.setter + def unconstrained_power(self, unconstrained_power: bool): + self.__unconstrained_power = unconstrained_power + + @property + def higher_capability(self) -> bool: + """ + Returns flag of higher capability. + + Returns: + object of `bool` type + """ + return bool(self.__higher_capability) + + @higher_capability.setter + def higher_capability(self, higher_capability: bool): + self.__higher_capability = higher_capability + + @property + def dual_power_role(self) -> bool: + """ + Returns flag of dual power role. + + Returns: + object of `bool` type + """ + return bool(self.__dual_power_role) + + @dual_power_role.setter + def dual_power_role(self, dual_power_role: bool): + self.__dual_power_role = dual_power_role + + @property + def pdo_type(self) -> PdoTypeEnum: + """ + Returns flag of PDO type. + + Returns: + object of `PdoTypeEnum` type + """ + return self.__type + + def __str__(self): + return f"Oper Current: {self.__oper_current}\n" \ + f"Voltage: {self.__voltage}\n" \ + f"Dual Role: {'Yes' if self.__dual_data_role else 'No'}\n" \ + f"USB Communication: {'Yes' if self.__usb_communication else 'No'}\n" \ + f"Unconstrained Power: {'Yes' if self.__unconstrained_power else 'No'}\n" \ + f"Higher capability: {'Yes' if self.__higher_capability else 'No'}\n" \ + f"Dual Power role: {'Yes' if self.__dual_power_role else 'No'}\n" + + +PdoType = TypeVar("PdoType", + FixedPdoSink, + FixedPdoSource, + BatteryPdo, + VariablePdo) diff --git a/UniTAP/dev/ports/modules/pdc/pdo_utils.py b/UniTAP/dev/ports/modules/pdc/pdo_utils.py new file mode 100644 index 0000000..9cc5fe9 --- /dev/null +++ b/UniTAP/dev/ports/modules/pdc/pdo_utils.py @@ -0,0 +1,31 @@ +from UniTAP.dev.ports.modules.pdc.pdo_private_types import * +from UniTAP.dev.ports.modules.pdc.pdo_types import * + + +def get_pdo_value(pdo: PdoType) -> int: + if isinstance(pdo, FixedPdoSink): + value = int(pdo.oper_current / 10) | (int(pdo.voltage / 50) << 10) | (pdo.dual_data_role << 25) | (pdo.usb_communication << 26) | \ + (pdo.unconstrained_power << 27) | (pdo.higher_capability << 28) | (pdo.dual_power_role << 29) + return value + elif isinstance(pdo, FixedPdoSource): + value = int(pdo.max_current / 10) | (int(pdo.voltage / 50) << 10) | (pdo.peak_current.value << 20) | (pdo.dual_data_role << 25) | \ + (pdo.usb_communication << 26) | (pdo.unconstrained_power << 27) | (pdo.higher_capability << 28) | \ + pdo.dual_power_role << 29 + return value + elif isinstance(pdo, BatteryPdo): + value = int(pdo.max_power / 250) | (int(pdo.min_voltage / 50) << 10) | (int(pdo.max_voltage / 50) << 20) | (1 << 30) + return value + elif isinstance(pdo, VariablePdo): + value = int(pdo.max_current / 10) | (int(pdo.min_voltage / 50) << 10) | (int(pdo.max_voltage / 50) << 20) | (2 << 30) + return value + + +def interpret_pdo_value(old_type: PdoType, new_type: PdoType) -> PdoTypeStruct: + if new_type == FixedPdoSink: + return PdoUnion(get_pdo_value(old_type)).fixed_pdo_sink + elif new_type == FixedPdoSource: + return PdoUnion(get_pdo_value(old_type)).fixed_pdo_source + elif new_type == BatteryPdo: + return PdoUnion(get_pdo_value(old_type)).battery_pdo + elif new_type == VariablePdo: + return PdoUnion(get_pdo_value(old_type)).variable_pdo diff --git a/UniTAP/dev/ports/modules/vtg/__init__.py b/UniTAP/dev/ports/modules/vtg/__init__.py new file mode 100644 index 0000000..46d3751 --- /dev/null +++ b/UniTAP/dev/ports/modules/vtg/__init__.py @@ -0,0 +1,3 @@ +from .pg import PGPatternParams, SolidColorParams, WhiteVStripsParams, GradientStripsParams, MotionParams, \ + SquareWindowParams, StepsScrollingParams, ASParams, ConstantASParams, SquareASParams, ZigzagASParams, FixedASParams, \ + VideoPattern, PGStatus diff --git a/UniTAP/dev/ports/modules/vtg/pg.py b/UniTAP/dev/ports/modules/vtg/pg.py new file mode 100644 index 0000000..7615f03 --- /dev/null +++ b/UniTAP/dev/ports/modules/vtg/pg.py @@ -0,0 +1,898 @@ +import copy +import os.path +from typing import Union, List, Optional + +from PIL import Image +from UniTAP.utils import function_scheduler +from UniTAP.libs.lib_tsi.tsi import * +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from UniTAP.dev.modules import MemoryManager +from UniTAP.dev.ports.modules.internal_utils.math import aligned +from UniTAP.common import VideoMode, Timing, ColorInfo, VideoFrame, VideoFrameDSC +from UniTAP.dev.ports.modules.internal_utils import uicl_image_from_vm_and_data, uicl_image_convert_to_vm +from .types import VideoPattern, PGVideoMode, PGAdaptiveSyncPatternType, PGColorimetry, \ + PGColorInfo, PGColorDepth, PGStandard, PGDynamicRange, PGAspectRatio, ConstantASParams, SquareASParams, \ + ZigzagASParams, FixedASParams, ASParams +from .private_types import get_as_params_value, PgCaps, CustomTimingFlags, PGPatternID, get_pattern_params_value, \ + PGPatternType +from .pg_utils import pg_timingflags_from_vm, pg_pixel_format_from_vm, pg_colorformat_to_ci_colorformat, \ + pg_dynamicrange_to_ci_dynamicrange, pg_colorimetry_to_ci_colorimetry, pg_bpc_to_ci_bpc +from .timing_manager import TimingManager +from .pg_pattern_params import * +from ..panel_replay import * + +# Override MAX_IMAGE_PIXELS +Image.MAX_IMAGE_PIXELS = None + + +def _read_predefined_timings(io: PortIO) -> List[Timing]: + count = io.get(TSI_R_PG_PREDEF_TIMING_COUNT)[1] + + predefined_list = [] + + for i in range(count): + io.set(TSI_W_PG_PREDEF_TIMING_SELECT, i) + + timing = Timing() + timing.htotal = io.get(TSI_PG_CUSTOM_TIMING_HTOTAL)[1] + timing.vtotal = io.get(TSI_PG_CUSTOM_TIMING_VTOTAL)[1] + timing.hactive = io.get(TSI_PG_CUSTOM_TIMING_HACTIVE)[1] + timing.vactive = io.get(TSI_PG_CUSTOM_TIMING_VACTIVE)[1] + timing.hstart = io.get(TSI_PG_CUSTOM_TIMING_HSTART)[1] + timing.vstart = io.get(TSI_PG_CUSTOM_TIMING_VSTART)[1] + timing.hswidth = io.get(TSI_PG_CUSTOM_TIMING_HSYNCW)[1] + timing.vswidth = io.get(TSI_PG_CUSTOM_TIMING_VSYNCW)[1] + timing.id = io.get(TSI_PG_CUSTOM_TIMING_ID)[1] + # TODO: add conversion from pg AR to Timing AR + timing.aspect_ratio = io.get(TSI_PG_CUSTOM_TIMING_ASPECT_RATIO)[1] + timing.frame_rate = io.get(TSI_PG_CUSTOM_TIMING_FIELD_RATE)[1] + timing.standard = Timing.Standard(io.get(TSI_PG_CUSTOM_TIMING_STANDARD)[1]) + + timing_flags = io.get(TSI_PG_CUSTOM_TIMING_FLAGS)[1] + timing.hswidth *= (-1 if timing_flags >> 9 & 1 else 1) + timing.vswidth *= (-1 if timing_flags >> 10 & 1 else 1) + timing.reduce_blanking = Timing.ReduceBlanking((timing_flags >> 24) & 0x3) + + predefined_list.append(timing) + + return predefined_list + + +class PGStatus: + """ + + Class `PGStatus` describes possible states of `PatternGenerator`. + + """ + + class PGError(IntEnum): + """ + + Class `PGError` contains codes of errors with the possibility of string representation. + + """ + NotReady = -1 + OK = 0 + HWFault = 1 + PixelClock = 2 + MemoryError = 3 + DscFileZero = 4 + DscPixelClockExceeds = 5 + DscSourceNotSupport = 6 + DscSinkNotSupport = 7 + DscFailReadDpcd = 8 + DscFailWriteDpcd = 9 + WrongColorFormat = 10 + WrongColorimetry = 11 + WrongBitsPerComponent = 12 + WrongDynamicRange = 13 + NotEnoughMemoryForPattern = 14 + + def __str__(self): + if self.value == self.OK: + return "OK" + if self.value == self.HWFault: + return "HW Fault." + if self.value == self.PixelClock: + return "Pixel clock exceed HW Caps." + if self.value == self.MemoryError: + return "Not enough memory for custom pattern." + if self.value == self.DscFileZero: + return "DSC isn’t enabled. DSC file has zero size." + if self.value == self.DscPixelClockExceeds: + return "DSC isn’t enabled. Video pixel clock exceeds hardware capabilities." + if self.value == self.DscSourceNotSupport: + return "DSC isn’t enabled. Source does not support DSC." + if self.value == self.DscSinkNotSupport: + return "DSC isn’t enabled. Sink does not support DSC." + if self.value == self.DscFailReadDpcd: + return "DSC isn’t enabled. Fail to read DSC DPCD 0x160." + if self.value == self.DscFailWriteDpcd: + return "DSC isn’t enabled. Fail to write DSC DPCD 0x160." + if self.value == self.WrongColorFormat: + return "Selected Color Format does not match the pattern." + if self.value == self.WrongColorimetry: + return "Selected Colorimetry does not match the pattern." + if self.value == self.WrongBitsPerComponent: + return "Selected BPC does not match the port capabilities." + if self.value == self.WrongDynamicRange: + return "Selected Dynamic Range does not match the pattern." + else: + return f"Unknown error: {self.value}" + + def __init__(self, value: int): + self._error = self.PGError(value & 0xFF) + self._is_video_produced = (value >> 30) & 1 != 0 + self._non_applied_changes = (value >> 31) & 1 != 0 + + @property + def error(self) -> PGError: + """ + + Returns pg error. + + Returns: + object of `PGError` + """ + return self._error + + @property + def is_video_produced(self) -> bool: + """ + + Returns state of video produced. + + Returns: + object of bool - is video produced or not + """ + return self._is_video_produced + + @property + def non_applied_changes(self) -> bool: + """ + + Returns state of applied changes. + + Returns: + object of bool - were there any changes or not + """ + return self._non_applied_changes + + +class PatternGenerator: + """ + + Main class `PatternGenerator` allows working with PG functionality on the device: set different types of pattern + `set_pattern`, set different video modes `set_vm`, set additional parameters for some patterns `set_pattern_params`, + get information about current video mode on stream `get_stream_video_mode`, `apply` all transferred setting, + `reset` settings and read pattern generator `status`. + + """ + + __available_patterns = '\n'.join([e.name for e in VideoPattern]) + __hardware_generated_patterns = ''.join([(e.name + '\n') if i <= 7 else '' for i, e in enumerate(VideoPattern)]) + + _MAP_PATTERN_PARAMETER = {PGPatternID.SolidColor: SolidColorParams, + PGPatternID.WhiteVStrips: WhiteVStripsParams, + PGPatternID.GradientRGBStripes: GradientStripsParams, + PGPatternID.Motion_Pattern: MotionParams, + PGPatternID.SquareWindow: SquareWindowParams} + + def __init__(self, port_io: PortIO, memory_manager: MemoryManager, stream: int): + self.__io = port_io + self.__memory_manager = memory_manager + self.__stream = stream + self.__caps = self.__read_caps() + self._pattern = None + self._pattern_id = PGPatternID.Disabled + self._pattern_params = None + self._vm = None + self.__panel_replay = PanelReplay(port_io, self.__caps) + + def __read_caps(self): + self._become_active() + return self.__io.get(TSI_PG_CAPS_R, PgCaps)[1] + + def _caps(self) -> PgCaps: + return self.__caps + + def __data_from_image_file(self, target_video_mode: VideoMode, path: str) -> bytearray: + if target_video_mode is None: + target_video_mode = self.get_stream_video_mode() + current_video_mode = copy.deepcopy(target_video_mode) + target_size = (target_video_mode.timing.hactive, target_video_mode.timing.vactive) + + image = Image.open(path) + image = image.resize(target_size) + image = image.convert('RGB') + data = bytearray([x for sets in list(image.getdata()) for x in sets]) + + current_video_mode.color_info.color_format = ColorInfo.ColorFormat.CF_RGB + current_video_mode.color_info.bpc = 8 + current_video_mode.color_info.colorimetry = ColorInfo.Colorimetry.CM_sRGB + + if current_video_mode != target_video_mode: + image = uicl_image_from_vm_and_data(current_video_mode, data) + data = uicl_image_convert_to_vm(image, target_video_mode) + + return data + + @staticmethod + def __data_from_dsc_file(target_video_mode: VideoMode, path: str) -> bytearray: + with open(path, "rb") as dsc_file: + data = bytearray(dsc_file.read()) + assert data[:4] == b"DSCF", 'DSC File missing \'DSCF\' header.' + + return data + + def __load_custom_image(self, video_mode: VideoMode, content_data: Union[str, bytearray, VideoFrame]): + if video_mode is None: + video_mode = self.get_stream_video_mode() + + if isinstance(content_data, str): + data = self.__data_from_image_file(video_mode, str(content_data)) + elif isinstance(content_data, VideoFrame): + data = content_data.data + else: + image = uicl_image_from_vm_and_data(video_mode, content_data) + data = uicl_image_convert_to_vm(image, video_mode) + + block_sizes = PatternGenerator.__calculate_frame_size(video_mode.timing.hactive, + video_mode.timing.vactive) + self.__memory_manager.set_memory_layout([block_sizes]) + self.__memory_manager.set_memory_block_index(MemoryManager.MemoryOwner.MO_PatternGenerator) + + self.__io.set(TSI_PG_CUSTOM_PATTERN_MEMORY_BLOCK_INDEX, 0) + self.__io.set(TSI_PG_CUSTOM_PATTERN_WIDTH, video_mode.timing.hactive) + self.__io.set(TSI_PG_CUSTOM_PATTERN_HEIGHT, video_mode.timing.vactive) + self.__io.set(TSI_PG_CUSTOM_PATTERN_PIXEL_FORMAT, pg_pixel_format_from_vm(video_mode)) + self.__io.set(TSI_PG_CUSTOM_PATTERN_DATA, data, c_uint8, data_count=len(data)) + + def __load_dsc_image(self, video_mode: VideoMode, content_data: Union[str, bytearray, VideoFrameDSC]): + if isinstance(content_data, str): + data = self.__data_from_dsc_file(video_mode, str(content_data)) + elif isinstance(content_data, VideoFrameDSC): + data = content_data.data + else: + data = content_data + + block_sizes = len(data) + self.__memory_manager.set_memory_layout([block_sizes]) + self.__memory_manager.set_memory_block_index(MemoryManager.MemoryOwner.MO_PatternGenerator) + self.__io.set(TSI_PG_CUSTOM_PATTERN_MEMORY_BLOCK_INDEX, 0) + self.__io.set(TSI_PG_CUSTOM_PATTERN_DATA, data, c_uint8, data_count=len(data)) + + @staticmethod + def __calculate_frame_size(width: int, height: int) -> int: + line_stride_bytes = width * 4 + line_stride_aligned_bytes = aligned(line_stride_bytes, 1024) + + line_stride_bytes2 = width * 2 + line_stride_aligned_bytes2 = aligned(line_stride_bytes2, 1024) + + return int((line_stride_aligned_bytes + line_stride_aligned_bytes2) * height) + + def set_pattern(self, pattern: Union[VideoPattern, str, bytearray, VideoFrame, VideoFrameDSC]): + """ + + Allows setting video pattern on current stream. + Possible variants: + - type `VideoPattern` - value from enum `VideoPattern` (one of th e possible predefined patterns). + - type str - path to image (bmp, png, jpeg, dsc and so on). + - type bytearray - raw image data, which will be loaded to device memory. + - type `VideoFrame` - object of class that contains the image data. + - type `VideoFrameDSC` - object of class that contains dsc image data. + + Args: + pattern (Union[`VideoPattern`, str, bytearray, `VideoFrame`, `VideoFrameDSC`]) + + """ + self._pattern = pattern + + def _setup_pattern(self): + if self._pattern is not None: + if isinstance(self._pattern, VideoPattern): + self._pattern_id = PGPatternID(self._pattern.value) + if not self.__caps.flags.custom_vp and self._pattern_id in [PGPatternID.WhiteVStrips, + PGPatternID.GradientRGBStripes, + PGPatternID.ColorRamp, + PGPatternID.ColorSquares, + PGPatternID.Motion_Pattern]: + raise ValueError(f'Current PG (stream {self.__stream}) does not support pattern ' + f'{self._pattern.name}. Please select another supported pattern from ' + f'"VideoPattern":\n{self.__hardware_generated_patterns}') + elif isinstance(self._pattern, str) and os.path.exists(self._pattern): + filename, file_extension = os.path.splitext(self._pattern) + if file_extension.lower() == '.dsc' and self.__support_dsc(): + self.__load_dsc_image(self._vm, self._pattern) + self._pattern_id = PGPatternID.DscImage + else: + self.__load_custom_image(self._vm, self._pattern) + self._pattern_id = PGPatternID.ImageFile + elif isinstance(self._pattern, VideoFrame): + if self._pattern.color_info.color_format == ColorInfo.ColorFormat.CF_DSC and self.__support_dsc(): + self.__load_dsc_image(self._vm, self._pattern) + self._pattern_id = PGPatternID.DscImage + else: + self.__load_custom_image(self._vm, self._pattern) + self._pattern_id = PGPatternID.ImageFile + elif isinstance(self._pattern, VideoFrameDSC): + if self._pattern.data[:4] == b'DSCF' and self.__support_dsc(): + self.__load_dsc_image(self._vm, self._pattern) + self._pattern_id = PGPatternID.DscImage + elif isinstance(self._pattern, bytearray): + if self._pattern[:4] == b'DSCF' and self.__support_dsc(): + self.__load_dsc_image(self._vm, self._pattern) + self._pattern_id = PGPatternID.DscImage + else: + self.__load_custom_image(self._vm, self._pattern) + self._pattern_id = PGPatternID.ImageFile + else: + raise TypeError(f"Incorrect input value: {self._pattern}.\n" + f"If you want to select pattern, see following list of available patterns:\n" + f"{self.__available_patterns}") + self.__io.set(TSI_PG_PREDEF_PATTERN_SELECT, self._pattern_id.value) + return True + return False + + def __support_dsc(self) -> bool: + if not self.__caps.flags.dsc_supported: + warnings.warn("Display Stream Compression is not supported.") + return False + return True + + def set_vm(self, vm: VideoMode): + """ + + Allows setting `VideoMode` on current stream. + + Args: + vm (VideoMode) + """ + self._vm = vm + + def _setup_vm(self): + if self._vm is None: + self.__read_pg_settings() + return False + if not self._vm.is_valid(): + raise ValueError(f"Incorrect Video Mode") + if self.__caps.max_h_active >= self._vm.timing.hactive and \ + self.__caps.max_v_active >= self._vm.timing.vactive and \ + self.__caps.max_h_total >= self._vm.timing.htotal and \ + self.__caps.max_v_total >= self._vm.timing.vtotal and \ + self.__caps.max_frame >= int(self._vm.timing.frame_rate / 1000): + self.__io.set(TSI_PG_CUSTOM_TIMING_FLAGS, pg_timingflags_from_vm(self._vm)) + self.__io.set(TSI_PG_CUSTOM_TIMING_HTOTAL, self._vm.timing.htotal, c_uint) + self.__io.set(TSI_PG_CUSTOM_TIMING_VTOTAL, self._vm.timing.vtotal, c_uint) + self.__io.set(TSI_PG_CUSTOM_TIMING_HACTIVE, self._vm.timing.hactive, c_uint) + self.__io.set(TSI_PG_CUSTOM_TIMING_VACTIVE, self._vm.timing.vactive, c_uint) + self.__io.set(TSI_PG_CUSTOM_TIMING_HSTART, self._vm.timing.hstart, c_uint) + self.__io.set(TSI_PG_CUSTOM_TIMING_VSTART, self._vm.timing.vstart, c_uint) + self.__io.set(TSI_PG_CUSTOM_TIMING_HSYNCW, abs(self._vm.timing.hswidth), c_uint) + self.__io.set(TSI_PG_CUSTOM_TIMING_VSYNCW, abs(self._vm.timing.vswidth), c_uint) + self.__io.set(TSI_PG_CUSTOM_TIMING_FIELD_RATE, self._vm.timing.frame_rate, c_uint) + self.__io.set(TSI_PG_CUSTOM_TIMING_STANDARD, self._vm.timing.standard.value, c_uint) + return True + else: + raise ValueError(f"Incorrect Video Mode (incorrect resolution and(or) frame rate") + + def set_pattern_params(self, pattern_params: PGPatternParams): + """ + + Allows setting additional parameters for some patters on current stream. + See available `PGPatternParams` types: `SolidColorParams`, `WhiteVStripsParams`, `GradientStripsParams`, + `MotionParams`,`SquareWindowParams` (see in pg pattern params). + + Args: + pattern_params (PGPatternParams) + """ + self._pattern_params = pattern_params + + def _setup_pattern_params(self): + if self._pattern_params is not None and isinstance(self._pattern, VideoPattern): + if self._MAP_PATTERN_PARAMETER.get(PGPatternID(self._pattern_id.value)) is not None: + if isinstance(self._pattern_params, + self._MAP_PATTERN_PARAMETER.get(PGPatternID(self._pattern_id.value))): + self.__io.set(TSI_PG_PREDEF_PATTERN_PARAMS, get_pattern_params_value(self._pattern_params, + self._vm.color_info.bpc), + data_count=len(get_pattern_params_value(self._pattern_params))) + return True + else: + warnings.warn(f"Incorrect pattern name {self._pattern} and " + f"pattern params {type(self._pattern_params)}.\n" + f"For {self._pattern} you need to use " + f"{self._MAP_PATTERN_PARAMETER.get(self._pattern_id)}") + return False + + def __get_custom_timing_flags_to_video_mode(self) -> (ColorInfo, bool, bool): + timing_flags = self.__io.get(TSI_PG_CUSTOM_TIMING_FLAGS, CustomTimingFlags)[1] + + vm_color_format = pg_colorformat_to_ci_colorformat(PGColorInfo(timing_flags.color_space)) + + vm_colorimetry = pg_colorimetry_to_ci_colorimetry(PGColorInfo(timing_flags.color_space), + PGColorimetry(timing_flags.colorimetry)) + + vm_dynamic_range = pg_dynamicrange_to_ci_dynamicrange(PGDynamicRange(timing_flags.dynamic_range)) + + vm_h_sync_polarity = bool(timing_flags.h_sync_polarity) + vm_v_sync_polarity = bool(timing_flags.v_sync_polarity) + + vm_bpc = pg_bpc_to_ci_bpc(timing_flags.color_depth) + + color_info = ColorInfo() + color_info.color_format = vm_color_format + color_info.colorimetry = vm_colorimetry + color_info.dynamic_range = vm_dynamic_range + color_info.bpc = vm_bpc + + return color_info, vm_h_sync_polarity, vm_v_sync_polarity + + def get_stream_video_mode(self) -> VideoMode: + """ + + Returns `VideoMode` information about current stream. + + Returns: + object of VideoMode type + """ + self.__read_pg_settings() + + video_mode = VideoMode() + + video_mode.timing.hactive = self.__io.get(TSI_PG_CUSTOM_TIMING_HACTIVE, c_uint32)[1] + video_mode.timing.vactive = self.__io.get(TSI_PG_CUSTOM_TIMING_VACTIVE, c_uint32)[1] + video_mode.timing.htotal = self.__io.get(TSI_PG_CUSTOM_TIMING_HTOTAL, c_uint32)[1] + video_mode.timing.vtotal = self.__io.get(TSI_PG_CUSTOM_TIMING_VTOTAL, c_uint32)[1] + video_mode.timing.hstart = self.__io.get(TSI_PG_CUSTOM_TIMING_HSTART, c_uint32)[1] + video_mode.timing.vstart = self.__io.get(TSI_PG_CUSTOM_TIMING_VSTART, c_uint32)[1] + + video_mode.color_info, h_sync_polarity, v_sync_polarity = self.__get_custom_timing_flags_to_video_mode() + + video_mode.timing.hswidth = self.__io.get(TSI_PG_CUSTOM_TIMING_HSYNCW, c_uint32)[1] * ( + -1 if h_sync_polarity else 1) + video_mode.timing.vswidth = self.__io.get(TSI_PG_CUSTOM_TIMING_VSYNCW, c_uint32)[1] * ( + -1 if v_sync_polarity else 1) + video_mode.timing.frame_rate = self.__io.get(TSI_PG_CUSTOM_TIMING_FIELD_RATE, c_uint32)[1] + + return video_mode + + def _become_active(self): + self.__io.set(TSI_PG_STREAM_SELECT, self.__stream) + + def __read_pg_settings(self): + self.__io.set(TSI_PG_COMMAND_W, 2) + + def apply(self) -> bool: + """ + + Apply all settings on current stream. + + Returns: + object of bool type - settings were set successfully or not + """ + self.__io.set(TSI_PG_COMMAND_W, 3) + + def is_apply_pg_success(pg: PatternGenerator): + return pg.status().error == PGStatus.PGError.OK + + return function_scheduler(is_apply_pg_success, self, interval=1, timeout=10) + + def status(self) -> PGStatus: + """ + + Returns `PGStatus` of current stream. + + Returns: + object of PGStatus type. + """ + self._become_active() + return PGStatus(self.__io.get(TSI_PG_STATUS_R, c_uint32)[1]) + + def reset(self): + """ + + Reset all setting on current stream. + + """ + self._vm = VideoMode() + self._pattern = None + self._pattern_id = PGPatternID.Disabled + self._pattern_params = None + + def get_pixel_rate(self) -> int: + """ + + Returns current pixel rate. + + Returns: + object of int type + """ + vm = self.get_stream_video_mode() + return vm.timing.htotal * vm.timing.vtotal * int(vm.timing.frame_rate / 1000) + + def panel_replay(self) -> Optional[PanelReplay]: + """ + + Returns object of PanelReplay if device supports this feature. + + Returns: + object of 'PanelReplay' type or None + """ + if self.__caps.flags.panel_replay != 0 or self.__caps.flags.psr != 0: + return self.__panel_replay + return None + + +class HdmiPatternGenerator(PatternGenerator): + """ + Class `HdmiPatternGenerator` inherited from class `PatternGenerator`. + Allows getting `timing_manager`, `max_stream_count`, `apply` PG settings. + Also has all the `PatternGenerator` functionality. + """ + + def __init__(self, port_io: PortIO, memory_manager: MemoryManager): + super().__init__(port_io, memory_manager, 0) + self.__timing_manager = TimingManager(_read_predefined_timings(port_io)) + + @property + def timing_manager(self) -> TimingManager: + """ + + Should be used for working with available timings on device. + + Returns: + object of `TimingManager` type. + """ + return self.__timing_manager + + @property + def max_stream_count(self) -> int: + """ + + Returns maximum count of available streams. + + Returns: + object of int type. + """ + return 1 + + def apply(self) -> bool: + """ + + Apply all settings. + + Returns: + object of bool type - settings were set successfully or not + """ + self._become_active() + res = self._setup_vm() + res += self._setup_pattern() + res += self._setup_pattern_params() + if res != 0: + return super().apply() + else: + return False + + +class DpPatternGenerator(PatternGenerator): + """ + Class `DpPatternGenerator` inherited from class `PatternGenerator`. + Allows getting `timing_manager`, `adaptive_sync_status`, `apply` and `reset` PG settings and set additional settings: + `set_as_config`, `set_scrolling_params`. + Also has all the `PatternGenerator` functionality. + """ + + def __init__(self, port_io: PortIO, memory_manager: MemoryManager, stream: int): + super().__init__(port_io, memory_manager, stream) + self.__scrolling_params = None + self.__as_config = None + self.__io = port_io + self.__timing_manager = TimingManager(_read_predefined_timings(port_io)) + + @property + def timing_manager(self) -> TimingManager: + """ + + Should be used for working with available timings on device. + + Returns: + object of `TimingManager` type. + """ + return self.__timing_manager + + def set_as_config(self, as_config: ASParams): + """ + + Allows setting adaptive sync configuration. + See available `ASParams` types: `ConstantASParams`, `SquareASParams`, `ZigzagASParams`, `FixedASParams` + (see in types). + + Args: + as_config (ASParams) + """ + self.__as_config = as_config + + def set_scrolling_params(self, scrolling_params: PGScrollingParams): + """ + + Allows setting additional configuration for "Scrolling pattern". See available `PGScrollingParams` types: + `StepsScrollingParams` (see in pg pattern params). + + Args: + scrolling_params (PGScrollingParams) + """ + self.__scrolling_params = scrolling_params + + def _setup_as_config(self): + if self.__as_config is not None: + if not self._caps().flags.adaptive_sync: + warnings.warn("AdaptiveSync is not supported") + self.__as_config = None + return False + + self.__io.set(TSI_PG_ADAPTIVE_SYNC_CTRL, + get_as_params_value(self.__as_config), + data_count=len(get_as_params_value(self.__as_config))) + return True + return False + + def _setup_scrolling_params(self): + if self.__scrolling_params is not None: + if not self._caps().flags.scrolling_pattern: + warnings.warn("Scrolling params is not supported") + self.__scrolling_params = None + return False + if self._vm.color_info.color_format == ColorInfo.ColorFormat.CF_YCbCr_422 and \ + self.__scrolling_params.horizontally % 2: + warnings.warn(f"Wrong horizontally param. Number must be even. Will be used param - 1: " + f"{self.__scrolling_params.horizontally - 1}") + self.__scrolling_params.horizontally -= 1 + elif self._vm.color_info.color_format == ColorInfo.ColorFormat.CF_YCbCr_420 and \ + self.__scrolling_params.vertically % 2: + warnings.warn(f"Wrong vertically param. Number must be even. Will be used param - 1: " + f"{self.__scrolling_params.vertically - 1}") + self.__scrolling_params.vertically -= 1 + + if not (self._caps().patterns & (1 << self._pattern_id.value)): + warnings.warn(f"Patterns scrolling feature is not supported on pattern {self._pattern_id.name}") + return False + value = 1 + value |= (1 << 31) + self.__io.set(TSI_PG_CUSTOM_PATTERN_SCROLLING_CONTROL, value, c_uint32) + self.__io.set(TSI_PG_CUSTOM_PATTERN_SCROLLING_CONFIG, get_pattern_params_value(self.__scrolling_params), + data_count=len(get_pattern_params_value(self.__scrolling_params))) + return True + return False + + def adaptive_sync_status(self) -> bool: + """ + + Returns work status of adaptive sync. + + Returns: + object of bool type - adaptive sync enabled or not + """ + self._become_active() + + res = self.__io.get(TSI_PG_ADAPTIVE_SYNC_STS, c_uint32) + + if res[0] < TSI_SUCCESS: + return False + + return res[1] & 1 != 0 + + def apply(self) -> bool: + """ + + Apply all settings. + + Returns: + object of bool type - settings were set successfully or not + """ + self._become_active() + res = self._setup_vm() + res += self._setup_pattern() + res += self._setup_pattern_params() + res += self._setup_scrolling_params() + if res != 0: + res = super().apply() + else: + res = False + self._setup_as_config() + return res + + def reset(self): + """ + + Reset all setting. + + """ + self.__scrolling_params = None + self.__as_config = None + super().reset() + + +class DpMstPatternGenerator: + """ + Class `DpMstPatternGenerator` allows working with one of the supported streams on the device (contains list of the + `DpPatternGenerator` objects). To access the selected stream, use an override of `[ ]`. + Also, allows working with stream number 0 directly and applying all settings of all streams together. + """ + + def __init__(self, port_io: PortIO, memory_manager: MemoryManager, max_stream_count: int): + self.__pg_list = [] + self.__io = port_io + self.__MAX_STREAM_COUNT = max_stream_count + for i in range(self.max_stream_count): + self.__pg_list.append(DpPatternGenerator(port_io, memory_manager, i)) + self.__timing_manager = self.__pg_list[0].timing_manager + + def __getitem__(self, index: int): + """ + + Allows selecting one of the supported streams. + Example: + `'object of device role'.pg[index]` + + """ + if not (0 <= index < self.max_stream_count): + raise ValueError(f"Index of PG must be in range [0..{self.max_stream_count}].") + + return self.__pg_list[index] + + @property + def timing_manager(self) -> TimingManager: + """ + + Should be used for working with available timings on device. + + Returns: + object of `TimingManager` type. + """ + return self.__timing_manager + + @property + def max_stream_count(self) -> int: + """ + + Returns maximum count of available streams. + + Returns: + object of int type. + """ + return self.__MAX_STREAM_COUNT + + def set_pattern(self, pattern: Union[VideoPattern, str, bytearray, VideoFrame, VideoFrameDSC]): + """ + + Allows setting video pattern on stream number 0 of pattern generator. + Possible variants: + - type `VideoPattern` - value from enum `VideoPattern` (one of th e possible predefined patterns). + - type str - path to image (bmp, png, jpeg, dsc and so on). + - type bytearray - raw image data, which will be loaded to device memory. + - type `VideoFrame` - object of class that contains the image data. + - type `VideoFrameDSC` - object of class that contains the dsc image data. + + Args: + pattern (Union[`VideoPattern`, str, bytearray, `VideoFrame`, `VideoFrameDSC`]) + + """ + self.__pg_list[0].set_pattern(pattern) + + def set_vm(self, vm: VideoMode): + """ + + Allows setting `VideoMode` on stream number 0 of pattern generator. + + Args: + vm (VideoMode) + """ + self.__pg_list[0].set_vm(vm) + + def set_pattern_params(self, pattern_params: PGPatternParams): + """ + + Allows setting additional parameters for some patters on stream number 0 of pattern generator. + See available `PGPatternParams` types: `SolidColorParams`, `WhiteVStripsParams`, `GradientStripsParams`, + `MotionParams`,`SquareWindowParams` (see in pg pattern params). + + Args: + pattern_params (PGPatternParams) + """ + self.__pg_list[0].set_pattern_params(pattern_params) + + def set_as_config(self, as_config: ASParams): + """ + + Allows setting adaptive sync configuration on stream number 0 of pattern generator. + See available `ASParams` types: `ConstantASParams`, `SquareASParams`, `ZigzagASParams`, `FixedASParams`. + (see in types). + + Args: + as_config (ASParams) + """ + self.__pg_list[0].set_as_config(as_config) + + def set_scrolling_params(self, scrolling_params: PGScrollingParams): + """ + + Allows setting additional configuration for "Scrolling pattern" on stream number 0 of pattern generator. + See available `PGScrollingParams` types: `StepsScrollingParams` (see in pg pattern params). + + Args: + scrolling_params (PGScrollingParams) + """ + self.__pg_list[0].set_scrolling_params(scrolling_params) + + def get_stream_video_mode(self) -> VideoMode: + """ + + Returns `VideoMode` information about current configuration of PG on stream 0. + + Returns: + object of VideoMode type + """ + return self.__pg_list[0].get_stream_video_mode() + + def apply(self) -> bool: + """ + + Apply all setting on stream number 0 of pattern generator. + + Returns: + object of bool type - settings were set successfully or not + """ + return self.__pg_list[0].apply() + + def status(self) -> PGStatus: + """ + + Returns `PGStatus` on stream number 0 of pattern generator. + + Returns: + object of PGStatus type. + """ + return self.__pg_list[0].status() + + def reset(self): + """ + + Reset all setting on stream number 0 of pattern generator. + + """ + self.__pg_list[0].reset() + + def apply_all(self): + """ + + Apply all setting on all supported streams of pattern generator. + + """ + for i in range(self.max_stream_count): + self.__pg_list[i].apply() + + def get_pixel_rate(self, stream: int = 0) -> int: + """ + + Returns current pixel rate for selected stream. + + Returns: + object of int type + """ + return self.__pg_list[stream].get_pixel_rate() + + @property + def panel_replay(self) -> PanelReplay: + """ + + Returns object of PanelReplay if device supports this feature (first stream). + + Returns: + object of 'PanelReplay' type or None + """ + return self.__pg_list[0].panel_replay() + + def __read_max_stream_count(self) -> int: + result = self.__io.get(TSI_PG_MAX_STREAM_COUNT_R, c_uint32) + return result[1] diff --git a/UniTAP/dev/ports/modules/vtg/pg_pattern_params.py b/UniTAP/dev/ports/modules/vtg/pg_pattern_params.py new file mode 100644 index 0000000..42abf12 --- /dev/null +++ b/UniTAP/dev/ports/modules/vtg/pg_pattern_params.py @@ -0,0 +1,133 @@ +import warnings +from typing import TypeVar, Union + + +class SolidColorParams: + + """ + Special configuration class for configure Solid color pattern. Contains information about R (Y) - first, + G (Cb) - second, B(Cr) - third components. + """ + + def __init__(self, first: int = 0, second: int = 0, third: int = 0): + if not (0 <= first <= 255): + warnings.warn("Incorrect value. Must use value from range: 0 - 255. Will be used default value = 0") + first = 0 + self.first = first + + if not (0 <= second <= 255): + warnings.warn("Incorrect value. Must use value from range: 0 - 255. Will be used default value = 0") + second = 0 + self.second = second + + if not (0 <= third <= 255): + warnings.warn("Incorrect value. Must use value from range: 0 - 255. Will be used default value = 0") + third = 0 + self.third = third + + +class WhiteVStripsParams: + + """ + Special configuration class for configure White V Strips pattern. Contains information about white stripes width + and black stripes width. + """ + + def __init__(self, white_stripes_width: int = 1, black_stripes_width: int = 1): + if not (1 <= white_stripes_width <= 1000): + warnings.warn("Incorrect value. Must use value from range: 1 - 1000. Will be used default value = 1") + white_stripes_width = 1 + self.white_stripes_width = white_stripes_width + + if not (0 <= black_stripes_width <= 1000): + warnings.warn("Incorrect value. Must use value from range: 1 - 1000. Will be used default value = 1") + black_stripes_width = 1 + self.black_stripes_width = black_stripes_width + + +class GradientStripsParams: + + """ + Special configuration class for configure Gradient Strips pattern. Contains information about color steps conut. + """ + + def __init__(self, color_step: int = 10000): + if not (0 <= color_step <= 10000): + warnings.warn("Incorrect value. Must use value from range: 1 - 10000. Will be used default value = 1") + color_step = 1 + self.color_step = color_step + + +class MotionParams: + + """ + Special configuration class for configure Motion pattern. Contains information about frames conut. + """ + + def __init__(self, frames_count: int = 10000): + if not (0 <= frames_count <= 10000): + warnings.warn("Incorrect value. Must use value from range: 1 - 10000. Will be used default value = 1") + frames_count = 1 + self.frames_count = frames_count + + +class SquareWindowParams: + + """ + Special configuration class for configure Square Window pattern. Contains information about white square size. + """ + + def __init__(self, white_square: int = 30): + if not (0 <= white_square <= 100): + warnings.warn("Incorrect value. Must use value from range: 0 - 100. Will be used default value = 30") + white_square = 30 + self.white_square = white_square + + +class StepsScrollingParams: + + """ + Special configuration class for configure Scrolling (Steps type) pattern. Contains information about horizontally + delta, vertically delta and frames count. + """ + + def __init__(self, horizontally: int = 0, vertically: int = 0, frames: int = 0): + if not (-128 <= horizontally <= 127): + warnings.warn("Incorrect value (horizontally). Must use value from range: (-)128 - 127. " + "For YCbCr 422 range: (-)128 - 126 \n" + "Will be used default value = 0") + horizontally = 0 + if not (-128 <= vertically <= 127): + warnings.warn("Incorrect value (vertically). Must use value from range: (-)128 - 127. " + "For YCbCr 420 range: (-)128 - 126 \n" + "Will be used default value = 0") + vertically = 0 + if not (0 <= frames <= 255): + warnings.warn("Incorrect value (frames). Must use value from range: 0 - 255. " + "Will be used default value = 0") + frames = 0 + self.horizontally = (-1) * horizontally + self.vertically = vertically + self.frames = frames + + +class DistanceScrollingParams: + """ + Support of DistanceScrolling will be added later. + """ + def __init__(self): + pass + + +PGPatternParams = TypeVar("PGPatternParams", + SolidColorParams, + WhiteVStripsParams, + GradientStripsParams, + MotionParams, + SquareWindowParams + ) + + +PGScrollingParams = TypeVar("PGScrollingParams", + StepsScrollingParams, + DistanceScrollingParams) diff --git a/UniTAP/dev/ports/modules/vtg/pg_utils.py b/UniTAP/dev/ports/modules/vtg/pg_utils.py new file mode 100644 index 0000000..53bdec5 --- /dev/null +++ b/UniTAP/dev/ports/modules/vtg/pg_utils.py @@ -0,0 +1,163 @@ +from UniTAP.common import ColorInfo, VideoMode +from UniTAP.libs.lib_tsi.tsi_types import TSI_IMF_PK444_RGB_W8_C8, TSI_IMF_PK444_RGB_W16_C12, \ + TSI_IMF_PK444_RGB_W16_C16, TSI_IMF_PK444_YCbCr_W8_C8, TSI_IMF_PK444_YCbCr_W16_C16, \ + TSI_IMF_PK422_CbYCrY_W16_C8, TSI_IMF_PK422_CbYCrY_W16_C16, TSI_IMF_PK420_CxYY0_W8_C8, \ + TSI_IMF_PK420_CxYY0_W8_C10, TSI_IMF_PK420_CxYY_W16_C12, TSI_IMF_PK420_CxYY_W16_C16 +from .types import PGColorInfo, PGColorDepth, PGColorimetry, PGDynamicRange + + +def pg_colorformat_to_ci_colorformat(color_mode: PGColorInfo) -> ColorInfo.ColorFormat: + if color_mode == PGColorInfo.RGB: + return ColorInfo.ColorFormat.CF_RGB + elif color_mode == PGColorInfo.YCbCr444: + return ColorInfo.ColorFormat.CF_YCbCr_444 + elif color_mode == PGColorInfo.YCbCr422: + return ColorInfo.ColorFormat.CF_YCbCr_422 + elif color_mode == PGColorInfo.YCbCr420: + return ColorInfo.ColorFormat.CF_YCbCr_420 + elif color_mode == PGColorInfo.Y_only: + return ColorInfo.ColorFormat.CF_Y_ONLY + elif color_mode == PGColorInfo.RAW: + return ColorInfo.ColorFormat.CF_RAW + else: + return ColorInfo.ColorFormat.CF_UNKNOWN + + +def pg_colorformat_from_ci_colorformat(color_mode: ColorInfo.ColorFormat) -> PGColorInfo: + if color_mode == ColorInfo.ColorFormat.CF_RGB: + return PGColorInfo.RGB + elif color_mode == ColorInfo.ColorFormat.CF_YCbCr_444: + return PGColorInfo.YCbCr444 + elif color_mode == ColorInfo.ColorFormat.CF_YCbCr_422: + return PGColorInfo.YCbCr422 + elif color_mode == ColorInfo.ColorFormat.CF_YCbCr_420: + return PGColorInfo.YCbCr420 + elif color_mode == ColorInfo.ColorFormat.CF_Y_ONLY: + return PGColorInfo.Y_only + elif color_mode == ColorInfo.ColorFormat.CF_RAW: + return PGColorInfo.RAW + else: + return PGColorInfo.Unknown + + +def pg_colorimetry_to_ci_colorimetry(color_mode: PGColorInfo, colorimetry: PGColorimetry)\ + -> ColorInfo.Colorimetry: + if color_mode == PGColorInfo.RGB: + return ColorInfo.Colorimetry.CM_sRGB + elif color_mode in [PGColorInfo.YCbCr444, PGColorInfo.YCbCr422, PGColorInfo.YCbCr420]: + if colorimetry == PGColorimetry.ITU601: + return ColorInfo.Colorimetry.CM_ITUR_BT601 + elif colorimetry == PGColorimetry.ITU709: + return ColorInfo.Colorimetry.CM_ITUR_BT709 + else: + return ColorInfo.Colorimetry.CM_RESERVED + else: + return ColorInfo.Colorimetry.CM_RESERVED + + +def pg_colorimetry_from_ci_colorimetry(colorimetry: ColorInfo.Colorimetry) -> PGColorimetry: + if colorimetry == ColorInfo.Colorimetry.CM_ITUR_BT601: + return PGColorimetry.ITU601 + elif colorimetry == ColorInfo.Colorimetry.CM_ITUR_BT709: + return PGColorimetry.ITU709 + else: + return PGColorimetry.ITU601 + + +def pg_bpc_to_ci_bpc(bpc: PGColorDepth) -> int: + if bpc == PGColorDepth.BPC6: + return 6 + elif bpc == PGColorDepth.BPC8: + return 8 + elif bpc == PGColorDepth.BPC10: + return 10 + elif bpc == PGColorDepth.BPC12: + return 12 + elif bpc == PGColorDepth.BPC16: + return 16 + elif bpc == PGColorDepth.BPC7: + return 7 + elif bpc == PGColorDepth.BPC14: + return 14 + return 0 + + +def pg_bpc_from_ci_bpc(bpc: int) -> PGColorDepth: + if bpc == 6: + return PGColorDepth.BPC6 + elif bpc == 8: + return PGColorDepth.BPC8 + elif bpc == 10: + return PGColorDepth.BPC10 + elif bpc == 12: + return PGColorDepth.BPC12 + elif bpc == 16: + return PGColorDepth.BPC16 + elif bpc == 7: + return PGColorDepth.BPC7 + elif bpc == 14: + return PGColorDepth.BPC14 + return PGColorDepth.Unknown + + +def pg_pixel_format_from_vm(vm: VideoMode) -> int: + bpc = vm.color_info.bpc + color_format = vm.color_info.color_format + + if color_format == ColorInfo.ColorFormat.CF_RGB: + if bpc == 8: + return TSI_IMF_PK444_RGB_W8_C8 + elif bpc in [10, 12]: + return TSI_IMF_PK444_RGB_W16_C12 + elif bpc == 16: + return TSI_IMF_PK444_RGB_W16_C16 + elif color_format == ColorInfo.ColorFormat.CF_YCbCr_444: + if bpc == 8: + return TSI_IMF_PK444_YCbCr_W8_C8 + elif bpc in [10, 12, 16]: + return TSI_IMF_PK444_YCbCr_W16_C16 + elif color_format == ColorInfo.ColorFormat.CF_YCbCr_422: + if bpc == 8: + return TSI_IMF_PK422_CbYCrY_W16_C8 + elif bpc in [10, 12, 16]: + return TSI_IMF_PK422_CbYCrY_W16_C16 + elif color_format == ColorInfo.ColorFormat.CF_YCbCr_420: + if bpc == 8: + return TSI_IMF_PK420_CxYY0_W8_C8 + elif bpc == 10: + return TSI_IMF_PK420_CxYY0_W8_C10 + elif bpc == 12: + return TSI_IMF_PK420_CxYY_W16_C12 + elif bpc == 16: + return TSI_IMF_PK420_CxYY_W16_C16 + + return 0xffffffff + + +def pg_dynamicrange_to_ci_dynamicrange(dynamic_range: PGDynamicRange) -> ColorInfo.DynamicRange: + if dynamic_range == PGDynamicRange.CTA: + return ColorInfo.DynamicRange.DR_CTA + elif dynamic_range == PGDynamicRange.VESA: + return ColorInfo.DynamicRange.DR_VESA + else: + return ColorInfo.DynamicRange.DR_UNKNOWN + + +def pg_dynamicrange_from_ci_dynamicrange(dynamic_range: ColorInfo.DynamicRange) -> PGDynamicRange: + if dynamic_range == ColorInfo.DynamicRange.DR_CTA: + return PGDynamicRange.CTA + else: + return PGDynamicRange.VESA + + +def pg_timingflags_from_vm(vm: VideoMode) -> int: + timing_flags = 0 + + timing_flags |= pg_bpc_from_ci_bpc(vm.color_info.bpc) + timing_flags |= (1 << 9) if vm.timing.hswidth < 0 else 0 + timing_flags |= (1 << 10) if vm.timing.vswidth < 0 else 0 + timing_flags |= pg_colorformat_from_ci_colorformat(vm.color_info.color_format) << 11 + timing_flags |= pg_colorimetry_from_ci_colorimetry(vm.color_info.colorimetry) << 16 + timing_flags |= pg_dynamicrange_from_ci_dynamicrange(vm.color_info.dynamic_range) << 31 + + return timing_flags diff --git a/UniTAP/dev/ports/modules/vtg/private_types.py b/UniTAP/dev/ports/modules/vtg/private_types.py new file mode 100644 index 0000000..7724c36 --- /dev/null +++ b/UniTAP/dev/ports/modules/vtg/private_types.py @@ -0,0 +1,109 @@ +from ctypes import Structure, c_uint32, c_uint64 +from enum import IntEnum +from typing import Union +from .types import ASParams, ConstantASParams, PGAdaptiveSyncPatternType, SquareASParams, ZigzagASParams, FixedASParams +from .pg_pattern_params import PGPatternParams, PGScrollingParams, SolidColorParams, WhiteVStripsParams, \ + GradientStripsParams, MotionParams, SquareWindowParams, StepsScrollingParams + + +class PGPatternID(IntEnum): + Disabled = 0 + ColorBars = 1 + Chessboard = 2 + SolidColor = 3 + SolidWhite = 4 + SolidRed = 5 + SolidGreen = 6 + SolidBlue = 7 + WhiteVStrips = 8 + GradientRGBStripes = 9 + ColorRamp = 10 + ColorSquares = 11 + Motion_Pattern = 12 + ImageFile = 13 + Playback = 14 + SquareWindow = 15 + DscImage = 16 + + +class PGPatternType(IntEnum): + FPGA = 0 + NIOS = 1 + Image = 2 + Scalable = 3 + VTG = 4 + Software = 5 + + +class PgCaps(Structure): + class PgCapsFlags(Structure): + _fields_ = [ + ("custom_vp", c_uint32, 1), + ("playback_vp", c_uint32, 1), + ("vrr_supported", c_uint32, 1), + ("color_modes_for_non_fw_pattern", c_uint32, 1), + ("cea_vesa_dynamic_range", c_uint32, 1), + ("dsc_supported", c_uint32, 1), + ("high_dynamic_range", c_uint32, 1), + ("adaptive_sync", c_uint32, 1), + ("scrolling_pattern", c_uint32, 1), + ("panel_replay", c_uint32, 1), + ("alpm", c_uint32, 1), + ("raw_y_only", c_uint32, 1), + ("psr", c_uint32, 1), + ('reserved', c_uint32, 19) + ] + + _fields_ = [ + ('max_h_active', c_uint32), + ('max_v_active', c_uint32), + ('max_frame', c_uint32), + ('flags', PgCapsFlags), + ('max_h_total', c_uint32), + ('max_v_total', c_uint32), + ('max_pix_rate', c_uint64), + ('patterns', c_uint32) + ] + + +class CustomTimingFlags(Structure): + _fields_ = [ + ('color_depth', c_uint32, 8), + ('interlace', c_uint32, 1), + ('h_sync_polarity', c_uint32, 1), + ('v_sync_polarity', c_uint32, 1), + ('color_space', c_uint32, 4), + ('', c_uint32, 1), + ('colorimetry', c_uint32, 4), + ('', c_uint32, 10), + ('dynamic_range', c_uint32, 1) + ] + + +def get_as_params_value(params: ASParams) -> list: + if isinstance(params, ConstantASParams): + return [PGAdaptiveSyncPatternType.AS_Constant.value, params.lines] + elif isinstance(params, SquareASParams): + return [PGAdaptiveSyncPatternType.AS_Square.value, params.min_lanes, params.max_lanes, params.period_frames] + elif isinstance(params, ZigzagASParams): + return [PGAdaptiveSyncPatternType.AS_Zigzag.value, params.min_lanes, params.max_lanes, params.increase_lines, + params.decrease_lines] + elif isinstance(params, FixedASParams): + return [PGAdaptiveSyncPatternType.AS_Fixed.value, params.refresh_rate, params.divide_by_1_001, + params.increase_lines, params.decrease_lines] + + +def get_pattern_params_value(params: Union[PGPatternParams, PGScrollingParams], bpc: int = 8) -> list: + if isinstance(params, SolidColorParams): + up_shift = 16 - bpc + return [(((params.first << up_shift) << 16) | (params.second << up_shift)), (params.third << up_shift)] + elif isinstance(params, WhiteVStripsParams): + return [params.white_stripes_width, params.black_stripes_width] + elif isinstance(params, GradientStripsParams): + return [params.color_step] + elif isinstance(params, MotionParams): + return [params.frames_count] + elif isinstance(params, SquareWindowParams): + return [params.white_square] + elif isinstance(params, StepsScrollingParams): + return [params.frames, params.horizontally, params.frames, params.vertically] diff --git a/UniTAP/dev/ports/modules/vtg/timing_manager.py b/UniTAP/dev/ports/modules/vtg/timing_manager.py new file mode 100644 index 0000000..779855f --- /dev/null +++ b/UniTAP/dev/ports/modules/vtg/timing_manager.py @@ -0,0 +1,135 @@ +import copy +from UniTAP.common import Timing +from typing import Union, List, Optional + + +class TimingManager: + """ + + Class `TimingManager` allows working with all available predefined timings from device. + You can get cvt timing by index `get_cvt`, dmt timing by index `get_dmt`, cta timing by index `get_cta`, + get list of all timing `get_all` ot search timing by parameters `search`. + + """ + def __init__(self, available_list: List[Timing]): + self.__timings = available_list + + def get_cvt(self, index: int) -> Optional[Timing]: + """ + + Returns cvt `Timing` by index. + + Args: + index (int): CVT timing index + + Returns: + timing (Timing | None) - type `Timing` if search was success, `None` if not. + """ + return copy.deepcopy(self.__search_by_standard_and_index(Timing.Standard.SD_CVT, index)) + + def get_dmt(self, index: int) -> Optional[Timing]: + """ + + Returns cvt `Timing` by index. + + Args: + index (int): DMT timing index + + Returns: + timing (Timing | None) - type `Timing` if search was success, `None` if not. + """ + return copy.deepcopy(self.__search_by_standard_and_index(Timing.Standard.SD_DMT, index)) + + def get_cta(self, index: int) -> Optional[Timing]: + """ + + Returns cvt `Timing` by index. + + Args: + index (int): CTA timing index + + Returns: + timing (Timing | None) - type `Timing` if search was success, `None` if not. + """ + return copy.deepcopy(self.__search_by_standard_and_index(Timing.Standard.SD_CTA, index)) + + def get_all(self) -> List[Timing]: + """ + + Returns list of `Timing` objects. + + Returns: + timing (list[Timing]) + """ + return copy.deepcopy(self.__timings) + + def get_by_list_index(self, index: int) -> Optional[Timing]: + """ + + Returns `Timing` objects by index in timings list. + + Args: + index (int) index of timing in list + + Returns: + timing (Timing) + """ + if index < 0 or index >= len(self.__timings): + raise ValueError(f"Incorrect index {index} of timings. Must be between 0 and {len(self.__timings)}") + return self.__timings[index] + + def print_all(self) -> str: + """ + + Print list of `Timing` objects. + + Returns: + str + """ + return " \n".join(f'# {index} - ' + f'{item.standard.name.replace("SD_", "") if item.standard != Timing.Standard.SD_NONE else ""} ' + f'{item.hactive}x{item.vactive} @ {item.frame_rate / 1000}Hz' + f'{self.__print_timing_id(item)}' + for index, item in enumerate(self.__timings)) + + def search(self, h_active: Optional[int] = None, v_active: Optional[int] = None, f_rate: Optional[int] = None, + standard: Optional[Timing.Standard] = None, + rb: Optional[Timing.ReduceBlanking] = None) -> Optional[Timing]: + """ + + Search timing by transferred parameters. + + Args: + h_active (int | None): h active resolution of timing + v_active (int | None): v active resolution of timing + f_rate (int | None): frame rate of timing + standard (Standard | None): timing `Standard` + rb (ReduceBlanking | None): timing `ReduceBlanking` + + Returns: + timing (Timing | None) - type `Timing` if search was success, `None` if not. + """ + for timing in self.__timings: + if (timing.hactive == h_active or h_active is None) and (timing.vactive == v_active or v_active is None)\ + and (f_rate is None or abs(timing.frame_rate - f_rate) <= 500) \ + and (timing.standard == standard or standard is None) \ + and (timing.reduce_blanking == rb or rb is None): + return copy.deepcopy(timing) + + def __search_by_standard_and_index(self, sd: Timing.Standard, index: int) -> Union[Timing, None]: + search_result = [ + t for t in self.__timings if t.id == index and t.standard == sd + ] + return None if len(search_result) == 0 else search_result[0] + + @staticmethod + def __print_timing_id(timing: Timing) -> str: + if timing.standard == Timing.Standard.SD_CTA: + return f" (VIC {timing.id})" + elif timing.standard == Timing.Standard.SD_DMT: + return f" (ID {hex(timing.id).replace('0x', '').upper()}h)" \ + f"{f' [{timing.reduce_blanking.name}]' if timing.reduce_blanking != Timing.ReduceBlanking.RB_NONE else ''}" + elif timing.standard == Timing.Standard.SD_CVT: + return f" {f'[{timing.reduce_blanking.name}]' if timing.reduce_blanking != Timing.ReduceBlanking.RB_NONE else ''}" + else: + return "" diff --git a/UniTAP/dev/ports/modules/vtg/types.py b/UniTAP/dev/ports/modules/vtg/types.py new file mode 100644 index 0000000..f3bc920 --- /dev/null +++ b/UniTAP/dev/ports/modules/vtg/types.py @@ -0,0 +1,208 @@ +import warnings +from enum import IntEnum +from typing import TypeVar + + +class VideoPattern(IntEnum): + """ + Class `VideoPattern` contains all possible variants of patterns which can be set in the function `set_pattern`. + """ + Disabled = 0 + ColorBars = 1 + Chessboard = 2 + SolidColor = 3 + SolidWhite = 4 + SolidRed = 5 + SolidGreen = 6 + SolidBlue = 7 + WhiteVStrips = 8 + GradientRGBStripes = 9 + ColorRamp = 10 + ColorSquares = 11 + MotionPattern = 12 + SquareWindow = 15 + + +class PGDynamicRange(IntEnum): + """ + Class `PGDynamicRange` contains all possible variants of Dynamic Range. + """ + VESA = 0x00 + CTA = 0x01 + + +class PGStandard(IntEnum): + """ + Class `PGStandard` contains all possible variants of Standard. + """ + CVT = 0x01 + DMT = 0x02 + CTA = 0x03 + + +class PGVideoMode(IntEnum): + """ + Class `PGVideoMode` contains all possible variants of Video mode. + """ + CTA = 0x00 + RB1 = 0x01 + RB2 = 0x02 + RB3 = 0x03 + + +class PGAspectRatio(IntEnum): + """ + Class `PGVideoMode` contains all possible variants of Aspect ratio. + """ + NoData = 0x00 + Ratio4x3 = 0x01 + Ratio16x9 = 0x02 + + +class PGColorInfo(IntEnum): + """ + Class `PGColorInfo` contains all possible variants of Color info. + """ + Unknown = -1 + RGB = 0 + YCbCr444 = 1 + YCbCr422 = 2 + YCbCr420 = 3 + Y_only = 4 + RAW = 5 + + +class PGColorimetry(IntEnum): + """ + Class `PGColorimetry` contains all possible variants of Colorimetry. + """ + Unknown = 0 + ITU601 = 0 + ITU709 = 1 + + +class PGColorDepth(IntEnum): + """ + Class `PGColorDepth` contains all possible variants of Color Depth. + """ + Unknown = -1 + BPC6 = 0 + BPC8 = 1 + BPC10 = 2 + BPC12 = 3 + BPC16 = 4 + BPC7 = 5 + BPC14 = 6 + + +class PGAdaptiveSyncPatternType(IntEnum): + """ + Class `PGAdaptiveSyncPatternType` contains all possible variants of Adaptive Sync Pattern. + """ + AS_None = 0 + AS_Constant = 1 + AS_Square = 2 + AS_Zigzag = 3 + AS_Fixed = 4 + + +class ConstantASParams: + """ + Special configuration class for configure Adaptive-Sync. Contains information about blank lines count. + """ + def __init__(self, lines: int = 0): + if lines < 0: + warnings.warn("Incorrect blank lines count. Must use more then 0. " + "Will be used default value = 0") + lines = 0 + self.lines = lines + + +class SquareASParams: + + """ + Special configuration class for configure Adaptive-Sync. Contains information about blank lines minimum and maximum + count and period frames count. + """ + + def __init__(self, min_lanes: int = 0, max_lanes: int = 1000, period_frames: int = 10): + if min_lanes < 0: + warnings.warn("Incorrect blank lines min count. Must use more then 0. " + "Will be used default value = 0") + min_lanes = 0 + if max_lanes < 0: + warnings.warn("Incorrect blank lines max count. Must use more then 0. " + "Will be used default value = 1000") + max_lanes = 1000 + if period_frames < 0: + warnings.warn("Incorrect period frames count. Must use more then 0. " + "Will be used default value = 10") + period_frames = 10 + self.min_lanes = min_lanes + self.max_lanes = max_lanes + self.period_frames = period_frames + + +class ZigzagASParams: + + """ + Special configuration class for configure Adaptive-Sync. Contains information about blank lines minimum and maximum + count, increase and decrease lanes count. + """ + + def __init__(self, min_lanes: int = 0, max_lanes: int = 1000, increase_lines: int = 100, decrease_lines: int = 100): + if min_lanes < 0: + warnings.warn("Incorrect blank lines min count. Must use more then 0. " + "Will be used default value = 0") + min_lanes = 0 + if max_lanes < 0: + warnings.warn("Incorrect blank lines max count. Must use more then 0. " + "Will be used default value = 1000") + max_lanes = 1000 + if increase_lines < 0: + warnings.warn("Incorrect increase lines count. Must use more then 0. " + "Will be used default value = 100") + increase_lines = 100 + if decrease_lines < 0: + warnings.warn("Incorrect decrease lines count. Must use more then 0. " + "Will be used default value = 100") + decrease_lines = 100 + self.min_lanes = min_lanes + self.max_lanes = max_lanes + self.increase_lines = increase_lines + self.decrease_lines = decrease_lines + + +class FixedASParams: + + """ + Special configuration class for configure Adaptive-Sync. Contains information about refresh rate count, increase + and decrease lanes count. + """ + + def __init__(self, refresh_rate: int = 60, divide_by_1_001: bool = False, increase_lines: int = 100, + decrease_lines: int = 100): + if refresh_rate < 0: + warnings.warn("Incorrect refresh rate count. Must use more then 0. " + "Will be used default value = 60") + refresh_rate = 60 + if increase_lines < 0: + warnings.warn("Incorrect increase lines count. Must use more then 0. " + "Will be used default value = 100") + increase_lines = 100 + if decrease_lines < 0: + warnings.warn("Incorrect decrease lines count. Must use more then 0. " + "Will be used default value = 100") + decrease_lines = 100 + self.refresh_rate = refresh_rate + self.divide_by_1_001 = int(divide_by_1_001) + self.increase_lines = increase_lines + self.decrease_lines = decrease_lines + + +ASParams = TypeVar("ASParams", + ConstantASParams, + SquareASParams, + ZigzagASParams, + FixedASParams, + ) diff --git a/UniTAP/dev/ports/pdc_port.py b/UniTAP/dev/ports/pdc_port.py new file mode 100644 index 0000000..de17d45 --- /dev/null +++ b/UniTAP/dev/ports/pdc_port.py @@ -0,0 +1,215 @@ +from UniTAP.libs.lib_tsi.tsi_io import PortIO +from UniTAP.dev.ports.modules.pdc.pdc_io import PDCPortIO +from UniTAP.dev.ports.modules.pdc.pdc_power_sink import PowerSink +from UniTAP.dev.ports.modules.pdc.pdc_contract_control import PowerContractControl340, PowerContractControlBase +from UniTAP.dev.ports.modules.pdc.pdc_power_source import PowerSource +from UniTAP.dev.ports.modules.pdc.pdc_controls import PdcControls340, PdcControls424, PdcControls500 +from UniTAP.dev.ports.modules.pdc.pdc_dp_alt_mode import DpAltMode500, DpAltMode340 +from UniTAP.dev.ports.modules.pdc.pdc_capabilities import PdcCapabilities, PdcCapabilities340 +from UniTAP.dev.ports.modules.pdc.pdc_bus_status import BusElectricalStatus + + +class PDC: + + """ + Class `PDC` describes capabilities of power delivery controller. + Contains following field: + - Power contract control `power_contract_control`, type `PowerContractControlBase`. + - Power Sink `power_sink`, type `PowerSink`. + - Power Source `power_source`, type `PowerSource`. + - DP Alt mode `dp_alt_mode`, type `DpAltMode340`. + - PDC Capabilities `capabilities`, type `PdcCapabilities`. + - BUS electrical status `bus_electrical_status`, type `BusElectricalStatus`. + """ + + def __init__(self, port_io: PortIO): + self._pdc_io = PDCPortIO(port_io) + self.__power_contract_control = PowerContractControlBase(self._pdc_io) + self.__power_sink = PowerSink(self._pdc_io) + self.__power_source = PowerSource(self._pdc_io) + self.__dpam = DpAltMode340(self._pdc_io) + self.__caps = PdcCapabilities(self._pdc_io) + self.__bus_electrical_status = BusElectricalStatus(self._pdc_io) + + @property + def power_contract_control(self) -> PowerContractControlBase: + """ + Returns Power contract control. + + Returns: + object of `PowerContractControlBase` type + """ + return self.__power_contract_control + + @property + def power_sink(self) -> PowerSink: + """ + Returns Power Sink. + + Returns: + object of `PowerSink` type + """ + return self.__power_sink + + @property + def power_source(self) -> PowerSource: + """ + Returns Power Source. + + Returns: + object of `PowerSource` type + """ + return self.__power_source + + @property + def dp_alt_mode(self) -> DpAltMode340: + """ + Returns DP Alt mode. + + Returns: + object of `DpAltMode340` type + """ + return self.__dpam + + @property + def capabilities(self) -> PdcCapabilities: + """ + Returns PDC capabilities. + + Returns: + object of `PdcCapabilities` type + """ + return self.__caps + + @property + def bus_electrical_status(self) -> BusElectricalStatus: + """ + Returns BUS electrical status. + + Returns: + object of `BusElectricalStatus` type + """ + return self.__bus_electrical_status + + def __str__(self) -> str: + return (f"DP Alt Mode\n{self.dp_alt_mode}\n" + f"{self.power_sink}\n" + f"{self.power_source}\n" + f"{self.power_contract_control}\n" + f"{self.capabilities.status}\n" + f"{self.bus_electrical_status}\n") + + +class PDC340(PDC): + + """ + Class `PDC340` inherited from class `PDC`. + Class `PDC340` allows working with overriding PDc controls and power contract control. + Also has all the `PDC` functionality. + - Power Contract control `power_contract_control`, type `PowerContractControl340`. + - PDC Controls `controls`, type `PdcControls340`. + """ + + def __init__(self, port_io: PortIO): + super().__init__(port_io) + self.__power_contract_control = PowerContractControl340(self._pdc_io) + self.__controls = PdcControls340(self._pdc_io) + self.__caps = PdcCapabilities340(self._pdc_io) + + @property + def capabilities(self) -> PdcCapabilities340: + """ + Returns PDC capabilities. + + Returns: + object of `PdcCapabilities340` type + """ + return self.__caps + + @property + def power_contract_control(self) -> PowerContractControl340: + """ + Returns Power contract control. + + Returns: + object of `PowerContractControl340` type + """ + return self.__power_contract_control + + @property + def controls(self) -> PdcControls340: + """ + Returns PDC Controls. + + Returns: + object of `PdcControls340` type + """ + return self.__controls + + def __str__(self) -> str: + return (f"DP Alt Mode\n{self.dp_alt_mode}\n" + f"{self.power_sink}\n" + f"{self.power_source}\n" + f"{self.power_contract_control}\n" + f"{self.capabilities.status}" + f"{self.bus_electrical_status}\n") + + +class PDC424(PDC): + + """ + Class `PDC424` inherited from class `PDC`. + Class `PDC424` allows working with overriding controls. + Also has all the `PDC` functionality. + - PDC Controls `controls`, type `PdcControls340`. + """ + + def __init__(self, port_io: PortIO): + super().__init__(port_io) + self.__controls = PdcControls424(self._pdc_io) + + @property + def controls(self) -> PdcControls424: + """ + Returns PDC Controls. + + Returns: + object of `PdcControls424` type + """ + return self.__controls + + +class PDC500(PDC): + + """ + Class `PDC500` inherited from class `PDC`. + Class `PDC500` allows working with overriding controls and DP Alt mode. + Also has all the `PDC` functionality. + - PDC Controls `controls`, type `PdcControls500`. + - DP Alt mode `dp_alt_mode`, type `DpAltMode500`. + """ + + def __init__(self, port_io: PortIO): + super().__init__(port_io) + self.__controls = PdcControls500(self._pdc_io) + self.__dpam = DpAltMode500(self._pdc_io) + + @property + def controls(self) -> PdcControls500: + """ + Returns PDC Controls. + + Returns: + object of `PdcControls500` type + """ + return self.__controls + + @property + def dp_alt_mode(self) -> DpAltMode500: + """ + Returns DP Alt mode. + + Returns: + object of `DpAltMode500` type + """ + return self.__dpam diff --git a/UniTAP/dev/ports/port.py b/UniTAP/dev/ports/port.py new file mode 100644 index 0000000..d58d216 --- /dev/null +++ b/UniTAP/dev/ports/port.py @@ -0,0 +1,9 @@ +from UniTAP.dev.modules import MemoryManager, Capturer +from UniTAP.libs.lib_tsi.tsi_io import PortIO + + +class TSIPort: + def __init__(self, port_io: PortIO, memory_manager: MemoryManager, capturer: Capturer): + self.__io = port_io + self.__memory_manager = memory_manager + self.__capturer = capturer diff --git a/UniTAP/dev/ports/rx.py b/UniTAP/dev/ports/rx.py new file mode 100644 index 0000000..8647e02 --- /dev/null +++ b/UniTAP/dev/ports/rx.py @@ -0,0 +1,25 @@ +from .port import TSIPort, MemoryManager, Capturer, PortIO +from .modules.capturer.video.video_capturer import VideoCapturerDP, VideoCapturerHDMI +from .modules.capturer.audio.audio_capturer import AudioCapturer + + +class RX(TSIPort): + """ + Class describe base capabilities of Sink (RX - receiver). + This functionality is used by child classes `DPRX` and `HDRX`. + You cannot use a class `RX` object directly. + """ + def __init__(self, port_io: PortIO, memory_manager: MemoryManager, capturer: Capturer): + super().__init__(port_io, memory_manager, capturer) + self.__audio_capturer = AudioCapturer(capturer) + + @property + def audio_capturer(self) -> AudioCapturer: + """ + + Should be used to control `AudioCapturer` on Sink (RX - receiver) role. + + Returns: + object of `AudioCapturer` type. + """ + return self.__audio_capturer diff --git a/UniTAP/dev/ports/tx.py b/UniTAP/dev/ports/tx.py new file mode 100644 index 0000000..8794acf --- /dev/null +++ b/UniTAP/dev/ports/tx.py @@ -0,0 +1,25 @@ +from .port import TSIPort, MemoryManager, PortIO, Capturer +from .modules.ag.ag import AudioGenerator + + +class TX(TSIPort): + """ + Class describe base capabilities of Source (TX - transmitter). + This functionality is used by child classes `DPTX` and `HDTX`. + You cannot use a class `TX` object directly. + """ + def __init__(self, port_io: PortIO, memory_manager: MemoryManager, capturer: Capturer): + super().__init__(port_io, memory_manager, capturer) + + self.__ag = AudioGenerator(port_io, memory_manager) + + @property + def ag(self) -> AudioGenerator: + """ + + Should be used to control `AudioGenerator` on Source (TX - transmitter) role. + + Returns: + object of `AudioGenerator` type. + """ + return self.__ag diff --git a/UniTAP/libs/__init__.py b/UniTAP/libs/__init__.py new file mode 100644 index 0000000..b0e9711 --- /dev/null +++ b/UniTAP/libs/__init__.py @@ -0,0 +1,3 @@ +from UniTAP.libs.lib_helper.lib_os_helpers import OS_Requirements +from UniTAP.libs.lib_dscl import * +from UniTAP.libs.lib_uicl import * diff --git a/UniTAP/libs/lib_dscl/DSCL.dll b/UniTAP/libs/lib_dscl/DSCL.dll new file mode 100644 index 0000000..fd352fe Binary files /dev/null and b/UniTAP/libs/lib_dscl/DSCL.dll differ diff --git a/UniTAP/libs/lib_dscl/Dsc.exe b/UniTAP/libs/lib_dscl/Dsc.exe new file mode 100644 index 0000000..e6740b4 Binary files /dev/null and b/UniTAP/libs/lib_dscl/Dsc.exe differ diff --git a/UniTAP/libs/lib_dscl/__init__.py b/UniTAP/libs/lib_dscl/__init__.py new file mode 100644 index 0000000..e1ef127 --- /dev/null +++ b/UniTAP/libs/lib_dscl/__init__.py @@ -0,0 +1,3 @@ +from .dsc_info import DscParameters +from .dscl_utils import (dscl_cf_from_dsc_cf, dscl_cf_to_dsc_cf, image_from_dsc_vf, + dscl_image_calculate_crc, calculate_slice_size) diff --git a/UniTAP/libs/lib_dscl/dsc_info.py b/UniTAP/libs/lib_dscl/dsc_info.py new file mode 100644 index 0000000..68c73c1 --- /dev/null +++ b/UniTAP/libs/lib_dscl/dsc_info.py @@ -0,0 +1,156 @@ +from UniTAP.common.color_info import ColorInfo + + +class DscParameters: + """ + Class `DscParameters` describes all parameters necessary for formatting input image to DSC image. + - Width of image `width`. + - Height of image `height`. + - Color format of image `color_format` type `ColorInfo.ColorFormat`. + - BPC `bpc`. + - BPP `bpp`. + - Flag of is block prediction enabled `is_block_prediction_enabled`. + - Horizontal slice number `horizontal_slice_number`. + - Number of buffer bit depth `buffer_bit_depth`. + - Vertical slice number `vertical_slice_number`. + - Vertical slice size `vertical_slice_size`. + - DSC major version `version_major`. + - DSC minor version `version_minor`. + """ + def __init__(self, width: int = 1920, height: int = 1080, + color_format: ColorInfo.ColorFormat = ColorInfo.ColorFormat.CF_RGB, bpc: int = 8, bpp: int = 128, + is_block_prediction_enabled: bool = False, horizontal_slice_number: int = 8, buffer_bit_depth: int = 8, + vertical_slice_number: int = 8, vertical_slice_size: int = 1, version_major: int = 1, + version_minor: int = 2): + self.__width = width + self.__height = height + self.__color_format = color_format + self.__bpc = bpc + self.__bpp = bpp + self.__is_block_prediction_enabled = is_block_prediction_enabled + self.__horizontal_slice_number = horizontal_slice_number + self.__buffer_bit_depth = buffer_bit_depth + self.__vertical_slice_number = vertical_slice_number + self.__vertical_slice_size = vertical_slice_size + self.__version_major = version_major + self.__version_minor = version_minor + + @property + def width(self) -> int: + return self.__width + + @width.setter + def width(self, width: int = 1920): + if width <= 0: + raise ValueError(f"Incorrect width of image. Must be more, than 0. Current value: {width}") + self.__width = width + + @property + def height(self) -> int: + return self.__height + + @height.setter + def height(self, height: int = 1080): + if height <= 0: + raise ValueError(f"Incorrect height of image. Must be more, than 0. Current value: {height}") + self.__height = height + + @property + def color_format(self) -> ColorInfo.ColorFormat: + return self.__color_format + + @color_format.setter + def color_format(self, color_format: ColorInfo.ColorFormat.CF_RGB): + self.__color_format = color_format + + @property + def bpc(self) -> int: + return self.__bpc + + @bpc.setter + def bpc(self, bpc: int = 8): + if bpc <= 5: + raise ValueError(f"Incorrect bpc of image. Must be more, than 5. Current value: {bpc}") + self.__bpc = bpc + + @property + def bpp(self) -> int: + return self.__bpp + + @bpp.setter + def bpp(self, bpp: int = 10): + if bpp <= 0: + raise ValueError(f"Incorrect bpp of image. Must be more, than 0. Current value: {bpp}") + self.__bpp = bpp + + @property + def is_block_prediction_enabled(self) -> bool: + return self.__is_block_prediction_enabled + + @width.setter + def width(self, is_block_prediction_enabled: bool = False): + self.__is_block_prediction_enabled = is_block_prediction_enabled + + @property + def horizontal_slice_number(self) -> int: + return self.__horizontal_slice_number + + @horizontal_slice_number.setter + def horizontal_slice_number(self, horizontal_slice_number: int = 1920): + if horizontal_slice_number <= 0: + raise ValueError(f"Incorrect horizontal_slice_number of image. Must be more, than 0. " + f"Current value: {horizontal_slice_number}") + self.__horizontal_slice_number = horizontal_slice_number + + @property + def buffer_bit_depth(self) -> int: + return self.__buffer_bit_depth + + @buffer_bit_depth.setter + def buffer_bit_depth(self, buffer_bit_depth: int = 8): + if buffer_bit_depth <= 0: + raise ValueError(f"Incorrect buffer_bit_depth of image. Must be more, than 0. " + f"Current value: {buffer_bit_depth}") + self.__buffer_bit_depth = buffer_bit_depth + + @property + def vertical_slice_number(self) -> int: + return self.__vertical_slice_number + + @vertical_slice_number.setter + def vertical_slice_number(self, vertical_slice_number: int = 8): + if vertical_slice_number <= 0: + raise ValueError(f"Incorrect vertical_slice_number of image. Must be more, than 0. " + f"Current value: {vertical_slice_number}") + self.__vertical_slice_number = vertical_slice_number + + @property + def vertical_slice_size(self) -> int: + return self.__vertical_slice_size + + @vertical_slice_size.setter + def vertical_slice_size(self, vertical_slice_size: int = 1): + if vertical_slice_size <= 0: + raise ValueError(f"Incorrect vertical_slice_size of image. Must be more, than 0. " + f"Current value: {vertical_slice_size}") + self.__vertical_slice_size = vertical_slice_size + + @property + def version_major(self) -> int: + return self.__version_major + + @version_major.setter + def version_major(self, version_major: int = 1): + if version_major <= 0: + raise ValueError(f"Incorrect version_major of image. Must be more, than 0. Current value: {version_major}") + self.__version_major = version_major + + @property + def version_minor(self) -> int: + return self.__version_minor + + @version_minor.setter + def version_minor(self, version_minor: int = 2): + if version_minor <= 0: + raise ValueError(f"Incorrect version_minor of image. Must be more, than 0. Current value: {version_minor}") + self.__version_minor = version_minor diff --git a/UniTAP/libs/lib_dscl/dscl.py b/UniTAP/libs/lib_dscl/dscl.py new file mode 100644 index 0000000..a57d428 --- /dev/null +++ b/UniTAP/libs/lib_dscl/dscl.py @@ -0,0 +1,194 @@ +import os +from typing import Optional + +from .dscl_types import * +from UniTAP.libs.lib_uicl.uicl_types import UICL_Image, UICL_ImageParameters +from UniTAP.libs.lib_helper import OS_Requirements, lib_method_wrapper + +DSCL_CURRENT_VERSION = 1 +DSCL = OS_Requirements("DSCL").get_lib() +DSCL_PATH = OS_Requirements("DSCL").get_path_to_lib() +DSC_TOOLS_FOLDER = os.path.dirname(DSCL_PATH) + + +def from_cstr(src_str): + try: + return src_str.value.decode('cp1252') + except BaseException: + return src_str.value.decode('utf-8') + + +class DSCLError(Exception): + def __init__(self, message, errors=None): + super().__init__(message) + self.errors = errors + + +def DSCL_GetRequiredBufferSize(image_to_encode: DSCL_Image): + _DSCL_GetRequiredBufferSize = lib_method_wrapper(DSCL.DSCL_GetRequiredBufferSize, + [POINTER(DSCL_Image)], + c_uint64) + + return _DSCL_GetRequiredBufferSize(byref(image_to_encode)) + + +def DSCL_GetRequiredBufferSizeFromPPS(pps: DSCL_PPS): + _DSCL_GetRequiredBufferSizeFromPPS = lib_method_wrapper(DSCL.DSCL_GetRequiredBufferSizeFromPPS, + [POINTER(DSCL_PPS)], + c_uint64) + + return _DSCL_GetRequiredBufferSizeFromPPS(byref(pps)) + + +def DSCL_Encode(image: UICL_Image, pps: DSCL_PPS) -> DSCL_Image: + _DSCL_Encode = lib_method_wrapper(DSCL.DSCL_Encode, + [POINTER(UICL_Image), POINTER(DSCL_PPS), POINTER(DSCL_Image), + POINTER(DSCL_EncoderConfig), c_char_p, c_int], + DSCL_RESULT) + + max_enc_image_size = DSCL_GetRequiredBufferSizeFromPPS(pps) + + if max_enc_image_size <= 132: + raise DSCLError("Failed to calculate buffer size for DSC encoding!") + + encoded_image = DSCL_Image() + encoded_image.DataPtr = (c_uint8 * max_enc_image_size)() + encoded_image.DataSize = max_enc_image_size + + _log_struct = DSCL_EncoderConfig() + _log_string_size = 65536 + _log_string = create_string_buffer(_log_string_size) + + result = _DSCL_Encode(byref(image), byref(pps), byref(encoded_image), byref(_log_struct), + _log_string, _log_string_size) + + log = str(from_cstr(_log_string)) + + if result < DSCL_SUCCESS: + raise DSCLError(f"Encoding finished with error: {result}. Error log: {log}", result) + + return encoded_image + + +def DSCL_Decode(encoded_image: DSCL_Image, decode_simple422_to_444: Optional[bool] = False) -> UICL_Image: + _DSCL_Decode = lib_method_wrapper(DSCL.DSCL_Decode, + [POINTER(DSCL_Image), POINTER(UICL_Image), + POINTER(DSCL_DecoderConfig)], + DSCL_RESULT) + + decoded_image = UICL_Image() + decoded_image.Parameters = UICL_ImageParameters() + decoded_image.DataPtr = POINTER(c_uint8)() + decoded_image.DataSize = 0 + + decoder_config = DSCL_DecoderConfig() + decoder_config.decodeSimple422to444 = False if decode_simple422_to_444 is None else decode_simple422_to_444 + + result = _DSCL_Decode(byref(encoded_image), byref(decoded_image), byref(decoder_config)) + + if result < DSCL_SUCCESS: + raise DSCLError(f"Decoding failed with error code: {result}", result) + + return decoded_image + + +def DSCL_ResultToString(result): + _DSCL_ResultToString = lib_method_wrapper(DSCL.DSCL_ResultToString, + [DSCL_RESULT, c_char_p, c_uint64], + c_uint64) + + _error_string = create_string_buffer(1024) + _DSCL_ResultToString(result, _error_string, 1024) + return from_cstr(_error_string) + + +def DSCL_CalculateDSCCRC(image: DSCL_Image): + _DSCL_CalculateDSCCRC = lib_method_wrapper(DSCL.DSCL_CalculateDSCCRC, + [POINTER(DSCL_Image), POINTER(DSCL_CRC16)], + c_uint64) + crc_value = DSCL_CRC16() + result = _DSCL_CalculateDSCCRC(byref(image), byref(crc_value)) + + if result < DSCL_SUCCESS: + raise DSCLError(f"Calculate DSC CRC: Failed to calculate CRC", result) + + return crc_value + + +def DSCL_ExtractPPSFromData(data): + _DSCL_ExtractPPSFromData = lib_method_wrapper(DSCL.DSCL_ExtractPPSFromData, + [POINTER(c_uint8), c_uint64, POINTER(DSCL_PPS)], + c_uint64) + _pps = DSCL_PPS() + _size = len(data) + _data = (c_uint8 * _size)(*data) + result = _DSCL_ExtractPPSFromData(_data, _size, byref(_pps)) + if result < DSCL_SUCCESS: + raise DSCLError(f"Cannot extract PPS from data. Code: {result}") + + return _pps + + +def DSCL_ExtractPPS(image: DSCL_Image): + _DSCL_ExtractPPS = lib_method_wrapper(DSCL.DSCL_ExtractPPS, + [POINTER(DSCL_Image), POINTER(DSCL_PPS)], + c_uint64) + _pps = DSCL_PPS() + result = _DSCL_ExtractPPS(byref(image), byref(_pps)) + if result < DSCL_SUCCESS: + raise DSCLError(f"Cannot extract PPS from DSCL Image. Code: {result}") + + return _pps + + +def DSCL_VerifyPPS(image: DSCL_Image): + _DSCL_VerifyPPS = lib_method_wrapper(DSCL.DSCL_VerifyPPS, + [POINTER(DSCL_Image), c_char_p, c_uint64], + c_bool) + + _error_string = create_string_buffer(1024) + return _DSCL_VerifyPPS(byref(image), _error_string, 1024) + + +def DSCL_GeneratePPS(decoded_image_parameters: UICL_ImageParameters, + is_simple_422: bool, is_block_prediction_enabled: bool, bits_per_pixel: int, + horizontal_slice_count: int, vertical_slice_count: int, buffer_bit_depth: int, + dsc_version: DSCL_DscVersion) -> DSCL_PPS: + _DSCL_GeneratePPS = lib_method_wrapper(DSCL.DSCL_GeneratePPS, + [POINTER(DSCL_PPS), POINTER(UICL_ImageParameters), c_bool, c_bool, + c_uint16, c_uint16, c_uint16, c_uint8, DSCL_DscVersion], + c_bool) + _pps = DSCL_PPS() + result = _DSCL_GeneratePPS(byref(_pps), byref(decoded_image_parameters), is_simple_422, + is_block_prediction_enabled, bits_per_pixel, horizontal_slice_count, + vertical_slice_count, buffer_bit_depth, dsc_version) + if result < DSCL_SUCCESS: + raise DSCLError(f"Cannot generate PPS. Error code {result}") + + return _pps + + +def DSCL_LoadFromFile(file_name: str) -> DSCL_RESULT: + _DSCL_LoadFromFile = lib_method_wrapper(DSCL.DSCL_LoadFromFile, + [c_char_p, POINTER(DSCL_Image)], + DSCL_Image) + image = DSCL_Image() + image.DataPtr = POINTER(c_uint8)() + image.DataSize = 0 + + result = _DSCL_LoadFromFile(c_char_p(file_name.encode('utf-8')), byref(image)) + + if result < DSCL_SUCCESS: + raise DSCLError(f"Cannot load image from file. Error code {result}.") + + return result + + +def DSCL_FreeImage(image: DSCL_Image) -> DSCL_RESULT: + _DSCL_FreeImage = lib_method_wrapper(DSCL.DSCL_FreeImage, + [POINTER(POINTER(DSCL_Image))], + c_uint64) + + result = _DSCL_FreeImage(byref(byref(image))) + + return result diff --git a/UniTAP/libs/lib_dscl/dscl_types.py b/UniTAP/libs/lib_dscl/dscl_types.py new file mode 100644 index 0000000..593054c --- /dev/null +++ b/UniTAP/libs/lib_dscl/dscl_types.py @@ -0,0 +1,206 @@ +from ctypes import * +from enum import IntEnum + +DSCL_RESULT = c_int32 + +DSCL_SUCCESS = 0 +DSCL_INTERNAL_ERROR = -1 +DSCL_ERROR_NULL_IMAGE = -2 +DSCL_NOT_ENOUGH_SPACE_FOR_ENCODED_IMAGE = -3 +DSCL_COMPRESSION_FAILED = -4 +DSCL_DECOMPRESSION_FAILED = -5 +DSCL_DSC_EXE_NOT_FOUND = -6 +DSCL_DP_CRC_EXE_NOT_FOUND = -7 +DSCL_PB_EXE_NOT_FOUND = -8 +DSCL_CFG_NOT_CREATED = -9 +DSCL_CFG_NOT_FOUND = -10 +DSCL_FAIL_TO_CREATE_TMP_FOLDER = -11 +DSCL_FAIL_TO_CREATE_TMP_FILE = -12 + +RC_BUF_THRESH_SIZE = 14 +RC_RANGE_PARAMETERS_SIZE = 30 +RC_RANGE_PARAMETERS_COUNT = int(RC_RANGE_PARAMETERS_SIZE / 2) + + +class CtypesEnum(IntEnum): + @classmethod + def from_param(cls, obj): + return int(obj) + + +class DSCL_Colorformat(CtypesEnum): + Colorformat_RGB = 0, + Colorformat_YCbCr422 = 1, + Colorformat_YCbCr444 = 2, + Colorformat_YCbCr420 = 3, + Colorformat_Simple422 = 4, + Colorformat_MaxValue = 5 + + +class DSCL_DscVersion(Structure): + _fields_ = [ + ('major', c_uint8), + ('minor', c_uint8), + ] + + +DSCL_ColorformatStr = ["RGB", + "YCbCr 4:2:2", + "YCbCr 4:4:4", + "YCbCr 4:2:0", + "Simple 4:2:2"] + +class DSCL_Image(Structure): + _fields_ = [ + ('DataPtr', POINTER(c_uint8)), + ('DataSize', c_uint64), + ] + + +class DSCL_Compression(Structure): + _fields_ = [ + ('bitsPerPixel', c_uint32), + ('bitsPerComponent', c_uint32), + ('is422', c_bool), + ('is420', c_bool), + ] + + +class DSCL_CRC16(Structure): + _fields_ = [ + ('eng0', c_uint16), + ('eng1', c_uint16), + ('eng2', c_uint16) + ] + + +class DSCL_PPS(Structure): + _fields_ = [ + ('dsc_version_minor', c_uint8, 4), + ('dsc_version_major', c_uint8, 4), + ('pps_identifier', c_uint8, 8), + ('', c_uint8, 8), + ('linebuf_depth', c_uint8, 4), + ('bits_per_component', c_uint8, 4), + ('bits_per_pixel_high', c_uint8, 2), + ('vbr_enable', c_uint8, 1), + ('simple_422', c_uint8, 1), + ('convert_rgb', c_uint8, 1), + ('block_pred_enable', c_uint8, 1), + ('', c_uint8, 2), + ('bits_per_pixel_low', c_uint8, 8), + ('pic_height_high', c_uint8, 8), + ('pic_height_low', c_uint8, 8), + ('pic_width_high', c_uint8, 8), + ('pic_width_low', c_uint8, 8), + ('slice_height_high', c_uint8, 8), + ('slice_height_low', c_uint8, 8), + ('slice_width_high', c_uint8, 8), + ('slice_width_low', c_uint8, 8), + ('chunk_size_high', c_uint8, 8), + ('chunk_size_low', c_uint8, 8), + ('initial_xmit_delay_high', c_uint8, 2), + ('', c_uint8, 6), + ('initial_xmit_delay_low', c_uint8, 8), + ('initial_dec_delay_high', c_uint8, 8), + ('initial_dec_delay_low', c_uint8, 8), + ('', c_uint8, 8), + ('initial_scale_value', c_uint8, 6), + ('', c_uint8, 2), + ('scale_increment_interval_high', c_uint8, 8), + ('scale_increment_interval_low', c_uint8, 8), + ('scale_decrement_interval_high', c_uint8, 4), + ('', c_uint8, 4), + ('scale_decrement_interval_low', c_uint8, 8), + ('', c_uint8, 8), + ('first_line_bpg_offset', c_uint8, 5), + ('', c_uint8, 3), + ('nfl_bpg_offset_high', c_uint8, 8), + ('nfl_bpg_offset_low', c_uint8, 8), + ('slice_bpg_offset_high', c_uint8, 8), + ('slice_bpg_offset_low', c_uint8, 8), + ('initial_offset_high', c_uint8, 8), + ('initial_offset_low', c_uint8, 8), + ('final_offset_high', c_uint8, 8), + ('final_offset_low', c_uint8, 8), + ('flatness_min_qp', c_uint8, 5), + ('', c_uint8, 3), + ('flatness_min_qp', c_uint8, 5), + ('', c_uint8, 3), + ('rc_model_size_high', c_uint8, 8), + ('rc_model_size_low', c_uint8, 8), + ('rc_edge_factor', c_uint8, 4), + ('', c_uint8, 4), + ('rc_quant_incr_limit0', c_uint8, 5), + ('', c_uint8, 3), + ('rc_quant_incr_limit1', c_uint8, 5), + ('', c_uint8, 3), + ('rc_tgt_offset_lo', c_uint8, 4), + ('rc_tgt_offset_hi', c_uint8, 4), + ('rc_buf_thresh', c_uint8 * RC_BUF_THRESH_SIZE), + ('rc_range_parameters', c_uint8 * RC_RANGE_PARAMETERS_SIZE), + ('native_422', c_uint8, 1), + ('native_420', c_uint8, 1), + ('', c_uint8, 6), + ('second_line_bpg_offset', c_uint8, 5), + ('', c_uint8, 3), + ('nsl_bpg_offset_high', c_uint8, 8), + ('nsl_bpg_offset_low', c_uint8, 8), + ('second_line_offset_adj_high', c_uint8, 8), + ('second_line_offset_adj_low', c_uint8, 8), + ('', c_uint8 * 34) + ] + + def width(self) -> int: + return (self.pic_width_high << 8) | self.pic_width_low + + def height(self) -> int: + return (self.pic_height_high << 8) | self.pic_height_low + + def is_yuv(self) -> bool: + return self.convert_rgb == 0 + + def bpp(self) -> int: + if self.native_422 == 1 or self.native_420 == 1: + return int((((self.bits_per_pixel_high & 0x3) << 8) | self.bits_per_pixel_low) / 2) + else: + return ((self.bits_per_pixel_high & 0x3) << 8) | self.bits_per_pixel_low + + def bpc(self) -> int: + return self.bits_per_component & 0x0F + + def is_422(self) -> bool: + return self.native_422 != 0 + + def is_420(self) -> bool: + return self.native_420 != 0 + + def is_simple_422(self) -> bool: + return self.simple_422 != 0 + + def v_slice(self) -> int: + return (self.slice_height_high << 8) | self.slice_height_low + + def h_slice(self) -> int: + return (self.slice_width_high << 8) | self.slice_width_low + + def buffer_bit_depth(self) -> int: + return self.linebuf_depth + + def is_block_prediction_enabled(self) -> bool: + return self.block_pred_enable != 0 + + def dsc_version(self) -> tuple: + return self.dsc_version_major, self.dsc_version_minor + + +class DSCL_EncoderConfig(Structure): + _fields_ = [ + # ('log_buffer', c_char * 2048), + ] + + +class DSCL_DecoderConfig(Structure): + _fields_ = [ + ('decodeSimple422to444', c_bool), + ] diff --git a/UniTAP/libs/lib_dscl/dscl_utils.py b/UniTAP/libs/lib_dscl/dscl_utils.py new file mode 100644 index 0000000..448c077 --- /dev/null +++ b/UniTAP/libs/lib_dscl/dscl_utils.py @@ -0,0 +1,219 @@ +from UniTAP.libs.lib_dscl.dscl_types import * +from UniTAP.libs.lib_dscl.dscl import (DSCL_Encode, DSCL_Decode, DSCL_CalculateDSCCRC, DSCL_ExtractPPSFromData, + DSCL_GeneratePPS) +from UniTAP.common import VideoFrameDSC, CompressionInfo +from UniTAP.libs.lib_uicl.uicl_utils import * + + +def dscl_cf_from_dsc_cf(color_format: CompressionInfo.DscColorFormat) -> DSCL_Colorformat: + assert isinstance(color_format, CompressionInfo.DscColorFormat) + if color_format == CompressionInfo.DscColorFormat.CF_RGB: + return DSCL_Colorformat.Colorformat_RGB + elif color_format == CompressionInfo.DscColorFormat.CF_YCbCr_444: + return DSCL_Colorformat.Colorformat_YCbCr444 + elif color_format == CompressionInfo.DscColorFormat.CF_YCbCr_422: + return DSCL_Colorformat.Colorformat_YCbCr422 + elif color_format == CompressionInfo.DscColorFormat.CF_YCbCr_420: + return DSCL_Colorformat.Colorformat_YCbCr420 + elif color_format == CompressionInfo.DscColorFormat.CF_Simple_422: + return DSCL_Colorformat.Colorformat_Simple422 + else: + return DSCL_Colorformat.Colorformat_MaxValue + + +def dscl_cf_to_dsc_cf(color_format: DSCL_Colorformat) -> CompressionInfo.DscColorFormat: + if color_format == DSCL_Colorformat.Colorformat_RGB: + return CompressionInfo.DscColorFormat.CF_RGB + elif color_format == DSCL_Colorformat.Colorformat_YCbCr444: + return CompressionInfo.DscColorFormat.CF_YCbCr_444 + elif color_format == DSCL_Colorformat.Colorformat_YCbCr422: + return CompressionInfo.DscColorFormat.CF_YCbCr_422 + elif color_format == DSCL_Colorformat.Colorformat_YCbCr420: + return CompressionInfo.DscColorFormat.CF_YCbCr_420 + elif color_format == DSCL_Colorformat.Colorformat_Simple422: + return CompressionInfo.DscColorFormat.CF_Simple_422 + else: + return CompressionInfo.DscColorFormat.CF_NONE + + +def vm_cf_to_dscl_cf(color_format: ColorInfo.ColorFormat) -> DSCL_Colorformat: + if color_format == ColorInfo.ColorFormat.CF_RGB: + return DSCL_Colorformat.Colorformat_RGB + elif color_format == ColorInfo.ColorFormat.CF_YCbCr_444: + return DSCL_Colorformat.Colorformat_YCbCr444 + elif color_format == ColorInfo.ColorFormat.CF_YCbCr_422: + return DSCL_Colorformat.Colorformat_YCbCr422 + elif color_format == ColorInfo.ColorFormat.CF_YCbCr_420: + return DSCL_Colorformat.Colorformat_YCbCr420 + else: + return DSCL_Colorformat.Colorformat_MaxValue + + +def vm_cf_to_dsc_cf(color_format: ColorInfo.ColorFormat) -> CompressionInfo.DscColorFormat: + if color_format == ColorInfo.ColorFormat.CF_RGB: + return CompressionInfo.DscColorFormat.CF_RGB + elif color_format == ColorInfo.ColorFormat.CF_YCbCr_444: + return CompressionInfo.DscColorFormat.CF_YCbCr_444 + elif color_format == ColorInfo.ColorFormat.CF_YCbCr_422: + return CompressionInfo.DscColorFormat.CF_YCbCr_422 + elif color_format == ColorInfo.ColorFormat.CF_YCbCr_420: + return CompressionInfo.DscColorFormat.CF_YCbCr_420 + else: + return CompressionInfo.DscColorFormat.CF_NONE + + +def dscl_pps_cf_to_compression_info_cf(dscl_pps: DSCL_PPS) -> CompressionInfo.DscColorFormat: + if dscl_pps.convert_rgb: + return CompressionInfo.DscColorFormat.CF_RGB + elif dscl_pps.native_422: + return CompressionInfo.DscColorFormat.CF_YCbCr_422 + elif dscl_pps.native_420: + return CompressionInfo.DscColorFormat.CF_YCbCr_420 + elif dscl_pps.simple_422: + return CompressionInfo.DscColorFormat.CF_Simple_422 + else: + return CompressionInfo.DscColorFormat.CF_YCbCr_444 + + +def is_simple_as_444(dscl_pps: DSCL_PPS) -> bool: + return dscl_pps_cf_to_compression_info_cf(dscl_pps) == CompressionInfo.DscColorFormat.CF_YCbCr_444 + + +def dscl_pps_to_compression_info(dscl_pps: DSCL_PPS) -> CompressionInfo: + compression_info = CompressionInfo() + + compression_info.color_format = dscl_pps_cf_to_compression_info_cf(dscl_pps) + compression_info.bpp = ((dscl_pps.bits_per_pixel_high << 8) | dscl_pps.bits_per_pixel_low) + compression_info.is_block_prediction_enabled = dscl_pps.block_pred_enable + compression_info.h_slice_size = ((dscl_pps.slice_height_high << 8) | dscl_pps.slice_height_low) + compression_info.v_slice_size = ((dscl_pps.slice_width_high << 8) | dscl_pps.slice_width_low) + compression_info.buffer_bit_depth = dscl_pps.linebuf_depth + compression_info.version = (dscl_pps.dsc_version_major, dscl_pps.dsc_version_minor) + compression_info.is_simple_as_444 = is_simple_as_444(dscl_pps) + + return compression_info + + +def image_from_dsc_vf(video_frame: VideoFrameDSC) -> DSCL_Image: + image = DSCL_Image() + image.DataSize = len(video_frame.data) + + if isinstance(video_frame.data, bytearray): + image.DataPtr = (c_uint8 * image.DataSize)(*video_frame.data) + elif isinstance(video_frame.data, bytes): + image.DataPtr = (c_uint8 * image.DataSize)(*bytearray(video_frame.data)) + else: + image.DataPtr = video_frame.data + + return image + + +def dscl_image_from_vf(video_frame: VideoFrame, parameters: CompressionInfo) -> DSCL_Image: + image = DSCL_Image() + image.DataSize = len(video_frame.data) + + if isinstance(video_frame.data, bytearray): + image.DataPtr = (c_uint8 * image.DataSize).from_buffer(video_frame.data) + elif isinstance(video_frame.data, bytes): + image.DataPtr = (c_uint8 * image.DataSize).from_buffer(bytearray(video_frame.data)) + else: + image.DataPtr = video_frame.data + + return image + + +def dscl_image_to_dsc_vf(image: DSCL_Image, width: int = 0, height: int = 0, bpc: int = 0) -> VideoFrameDSC: + video_frame = VideoFrameDSC() + + video_frame.data = bytearray(image.DataPtr[:image.DataSize]) + video_frame.width = width + video_frame.height = height + video_frame.color_info.bpc = bpc + video_frame.color_info.color_format = ColorInfo.ColorFormat.CF_DSC + + return video_frame + + +def encode_vf(video_frame: VideoFrame, parameters: CompressionInfo) -> VideoFrameDSC: + if video_frame.color_info.color_format == ColorInfo.ColorFormat.CF_DSC: + raise AssertionError(f"Image must not have color format DSC. Current format: " + f"{video_frame.color_info.color_format.name}") + + uicl_image = image_from_vf(video_frame) + + width, height, color_info, data_info = image_params_to_size_and_ci(uicl_image.Parameters) + horizontal_slice_count = calculate_slice_number(width, parameters.h_slice_size, color_info.color_format) + vertical_slice_count = calculate_slice_number(height, parameters.v_slice_size, color_info.color_format) + dsc_version = DSCL_DscVersion() + dsc_version.major = parameters.version[0] + dsc_version.minor = parameters.version[1] + target_pps = DSCL_GeneratePPS(decoded_image_parameters=uicl_image.Parameters, + is_simple_422=parameters.color_format == CompressionInfo.DscColorFormat.CF_Simple_422, + is_block_prediction_enabled=parameters.is_block_prediction_enabled, + bits_per_pixel=parameters.bpp, + horizontal_slice_count=horizontal_slice_count, + vertical_slice_count=vertical_slice_count, + buffer_bit_depth=parameters.buffer_bit_depth, + dsc_version=dsc_version) + encoded_image = DSCL_Encode(uicl_image, target_pps) + encoded_vf = dscl_image_to_dsc_vf(encoded_image, width, height, color_info.bpc) + encoded_vf.compression_info = parameters + + return encoded_vf + + +def decode_vf(video_frame: VideoFrameDSC) -> VideoFrame: + if bytearray("DSCF".encode()) not in video_frame.data: + video_frame.data = bytearray("DSCF".encode()) + video_frame.data + encoded_image = image_from_dsc_vf(video_frame) + decoded_image = DSCL_Decode(encoded_image, decode_simple422_to_444=video_frame.compression_info.is_simple_as_444) + decoded_vf = image_to_vf(decoded_image) + + return decoded_vf + + +def dscl_image_calculate_crc(video_frame: VideoFrameDSC): + image = DSCL_Image() + image.DataSize = len(video_frame.data) + + if isinstance(video_frame.data, bytearray): + image.DataPtr = (c_uint8 * image.DataSize)(*video_frame.data) + elif isinstance(video_frame.data, bytes): + image.DataPtr = (c_uint8 * image.DataSize)(*bytearray(video_frame.data)) + else: + image.DataPtr = video_frame.data + crc = DSCL_CalculateDSCCRC(image) + return crc.eng0, crc.eng1, crc.eng2 + + +def dscl_data_processing(data: bytearray) -> VideoFrameDSC: + vf = VideoFrameDSC() + pps_data = DSCL_ExtractPPSFromData(data) + vf.compression_info = dscl_pps_to_compression_info(pps_data) + vf.height = ((pps_data.pic_height_high << 8) | pps_data.pic_height_low) + vf.width = ((pps_data.pic_width_high << 8) | pps_data.pic_width_low) + vf.color_info.bpc = pps_data.bits_per_component + vf.color_info.color_format = ColorInfo.ColorFormat.CF_DSC + vf.data = data + return vf + + +def calculate_slice_size(value: int, slice_number: int, + color_format: ColorInfo.ColorFormat = ColorInfo.ColorFormat.CF_RGB): + slice_size = value / slice_number + (1 if value % slice_number else 0) + + if color_format in [ColorInfo.ColorFormat.CF_YCbCr_422, ColorInfo.ColorFormat.CF_YCbCr_420, + ColorInfo.ColorFormat.CF_Y_ONLY]: + slice_size += (1 if slice_size % 2 else 0) + + return slice_size + + +def calculate_slice_number(value: int, slice_size: int, + color_format: ColorInfo.ColorFormat = ColorInfo.ColorFormat.CF_RGB): + if color_format in [ColorInfo.ColorFormat.CF_YCbCr_422, ColorInfo.ColorFormat.CF_YCbCr_420, + ColorInfo.ColorFormat.CF_Y_ONLY]: + slice_size -= (1 if slice_size % 2 else 0) + + slice_number = (value + slice_size - 1) // slice_size + return slice_number diff --git a/UniTAP/libs/lib_helper/__init__.py b/UniTAP/libs/lib_helper/__init__.py new file mode 100644 index 0000000..b329354 --- /dev/null +++ b/UniTAP/libs/lib_helper/__init__.py @@ -0,0 +1 @@ +from .lib_os_helpers import OS_Requirements, lib_method_wrapper \ No newline at end of file diff --git a/UniTAP/libs/lib_helper/lib_os_helpers.py b/UniTAP/libs/lib_helper/lib_os_helpers.py new file mode 100644 index 0000000..27cc2bc --- /dev/null +++ b/UniTAP/libs/lib_helper/lib_os_helpers.py @@ -0,0 +1,131 @@ +import warnings +from ctypes import * +import os +import platform +import glob + +LIBS_PATH = os.path.dirname(os.path.dirname(__file__)) + + +def lib_method_wrapper(named_func_pointer, argtypes, restype): + wrapper = named_func_pointer + wrapper.argtypes = argtypes + wrapper.restype = restype + + return wrapper + + +class OS_Requirements(): + + def __init__(self, lib_type: str, lib_path=""): + self.__os_name = platform.system() + self.__lib_type = lib_type + self.__lib_specific_path = os.path.join(LIBS_PATH, lib_path if lib_path != "" else f"lib_{self.__lib_type.lower()}") + + def __gen_dll_path_for_win(self): + dll_path = os.path.join(self.__lib_specific_path, f'{self.__lib_type}.dll') + if not os.path.isfile(dll_path): + dll_path = f'C:\\Program Files\\Unigraf\\Unigraf UCD Tools\\{self.__lib_type}.dll' + + return dll_path + + def __gen_lib_path_for_osx(self): + lib_path = os.path.join(self.__lib_specific_path, f'lib{self.__lib_type}.dylib') + return lib_path + + def __get_exe_path_for_win(self): + dll_path = os.path.join(self.__lib_specific_path, f'{self.__lib_type}.exe') + if not os.path.isfile(dll_path): + dll_path = f'C:\\Program Files\\Unigraf\\Unigraf UCD Tools\\{self.__lib_type}.exe' + + return dll_path + + def __gen_lib_path_for_lin(self): + full_dll_name = f'lib{self.__lib_type}.so' + + dll_path = os.path.join(self.__lib_specific_path, full_dll_name) + if not os.path.isfile(dll_path): + dll_path = glob.glob(f'/home/user/Downloads/*/sdk/python/common/tsi/tsi_devices/libs/lib_{self.__lib_type.lower()}/{full_dll_name}') + if len(dll_path) > 0: + dll_path = dll_path[0] + else: + dll_path = "" + + return dll_path + + def __gen_exe_path_for_lin_and_mac(self, is_linux): + full_dll_name = f'lib{self.__lib_type}.so' if is_linux else f'lib{self.__lib_type}.' + + dll_path = os.path.join(self.__lib_specific_path, full_dll_name) + if not os.path.isfile(dll_path): + dll_path = glob.glob(f'/home/user/Downloads/*/sdk/python/common/tsi/tsi_devices/libs/lib_{self.__lib_type.lower()}/{full_dll_name}') + if len(dll_path) > 0: + dll_path = dll_path[0] + else: + dll_path = "" + + return dll_path + + def __get_lib_for_win(self): + if os.path.exists(self.__gen_dll_path_for_win()): + return windll.LoadLibrary(self.__gen_dll_path_for_win()) + else: + warnings.warn(f"Failed to find library at {self.__gen_dll_path_for_win()}") + + def __get_lib_for_lin(self): + if os.path.exists(self.__gen_lib_path_for_lin()): + return cdll.LoadLibrary(self.__gen_lib_path_for_lin()) + else: + warnings.warn(f"Failed to find library at {self.__gen_lib_path_for_lin()}") + return None + + def __get_lib_for_osx(self): + if os.path.exists(self.__gen_lib_path_for_osx()): + return cdll.LoadLibrary(self.__gen_lib_path_for_osx()) + else: + warnings.warn(f"Failed to find library at {self.__gen_lib_path_for_osx()}") + return None + + def get_lib(self): + if self.__os_name == 'Windows': + return self.__get_lib_for_win() + elif self.__os_name == 'Linux': + return self.__get_lib_for_lin() + elif self.__os_name == 'Darwin': + return self.__get_lib_for_osx() + else: + raise Exception('Un-supported OS!') + + def get_additional_file(self): + try: + if self.__os_name == 'Windows': + return self.__get_exe_path_for_win() + elif self.__os_name == 'Linux': + return self.__gen_exe_path_for_lin_and_mac(True) + elif self.__os_name == 'Darwin': + return self.__gen_exe_path_for_lin_and_mac(False) + else: + raise Exception('Un-supported OS!') + except OSError: + error = get_errno() + print(f'Get error = {error} while loading {self.__lib_type} lib for {self.__os_name}!') + if error == 193: + print('Perhaps you are using a 64-bit python interpreter along with a 32-bit library, or vice versa.' + ' Need the same.') + + def get_path_to_lib(self): + try: + if self.__os_name == 'Windows': + return self.__gen_dll_path_for_win() + elif self.__os_name == 'Linux': + return self.__gen_lib_path_for_lin() + elif self.__os_name == 'Darwin': + return self.__gen_lib_path_for_osx() + else: + raise Exception('Un-supported OS!') + except OSError: + error = GetLastError() + print(f'Get error = {error} while loading {self.__lib_type} lib for {self.__os_name}!') + if error == 193: + print('Perhaps you are using a 64-bit python interpreter along with a 32-bit library, or vice versa.' + ' Need the same.') diff --git a/UniTAP/libs/lib_pdl/Default_16K.png b/UniTAP/libs/lib_pdl/Default_16K.png new file mode 100644 index 0000000..d4faaa1 Binary files /dev/null and b/UniTAP/libs/lib_pdl/Default_16K.png differ diff --git a/UniTAP/libs/lib_pdl/__init__.py b/UniTAP/libs/lib_pdl/__init__.py new file mode 100644 index 0000000..0ab4947 --- /dev/null +++ b/UniTAP/libs/lib_pdl/__init__.py @@ -0,0 +1 @@ +from .pdl import generate_pattern_as_vf, PatternType diff --git a/UniTAP/libs/lib_pdl/pdl.py b/UniTAP/libs/lib_pdl/pdl.py new file mode 100644 index 0000000..f4244ea --- /dev/null +++ b/UniTAP/libs/lib_pdl/pdl.py @@ -0,0 +1,21 @@ +import os + +from UniTAP.common import VideoFrame, ColorInfo, DataInfo, get_vf_from_image +from UniTAP.utils import video_frame_to_ci +from .pdl_types import * + +UNIGRAF_IMAGE_PATH = os.path.join(os.path.dirname(__file__), "Default_16K.png") + + +def generate_pattern_as_vf(pattern_type: PatternType, width: int, height: int, + color_info: ColorInfo, data_info: DataInfo) -> VideoFrame: + if pattern_type != PatternType.Unigraf: + raise NotImplementedError("Generating ColorRamp and ColorSquares not supported yet.") + + if pattern_type == PatternType.Unigraf: + if not os.path.exists(UNIGRAF_IMAGE_PATH): + raise FileNotFoundError("Unigraf default image not found. Not possible to generate " + "Unigraf pattern.") + vf = get_vf_from_image(UNIGRAF_IMAGE_PATH, width, height) + + return video_frame_to_ci(vf, color_info, data_info) diff --git a/UniTAP/libs/lib_pdl/pdl_types.py b/UniTAP/libs/lib_pdl/pdl_types.py new file mode 100644 index 0000000..50dea0a --- /dev/null +++ b/UniTAP/libs/lib_pdl/pdl_types.py @@ -0,0 +1,7 @@ +from enum import IntEnum + + +class PatternType(IntEnum): + Unigraf = 0, + ColorSquares = 1, + ColorRamp = 2 diff --git a/UniTAP/libs/lib_tsi/TSI.dll b/UniTAP/libs/lib_tsi/TSI.dll new file mode 100644 index 0000000..017e92c Binary files /dev/null and b/UniTAP/libs/lib_tsi/TSI.dll differ diff --git a/UniTAP/libs/lib_tsi/__init__.py b/UniTAP/libs/lib_tsi/__init__.py new file mode 100644 index 0000000..042efdf --- /dev/null +++ b/UniTAP/libs/lib_tsi/__init__.py @@ -0,0 +1,3 @@ +from UniTAP.libs.lib_tsi.tsi_types import * +from UniTAP.libs.lib_tsi.tsi import * +from UniTAP.libs.lib_tsi.tsi_io import BaseIO, DeviceIO, PortIO diff --git a/UniTAP/libs/lib_tsi/tsi.py b/UniTAP/libs/lib_tsi/tsi.py new file mode 100644 index 0000000..293dbf3 --- /dev/null +++ b/UniTAP/libs/lib_tsi/tsi.py @@ -0,0 +1,624 @@ +import warnings + +from UniTAP.libs.lib_tsi.tsi_private_types import * +from UniTAP.libs.lib_helper import OS_Requirements, lib_method_wrapper +from ctypes import c_char_p, c_uint32, c_void_p, c_int32, POINTER, create_string_buffer, byref, \ + sizeof, c_uint64, c_ubyte, c_bool +from UniTAP.utils import tsi_logging as logging + +CTYPES_TYPE_LIST = [c_char_p, c_uint32, c_void_p, c_int32, c_uint64, c_ubyte, c_bool] + + +callback = None + + +class Singleton(type): + _instances = {} + + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) + return cls._instances[cls] + + +class TSIWrapper(metaclass=Singleton): + _TSI_CURRENT_VERSION = 12 + + def __init__(self): + self._TSI_DEV_SetSearchMask = None + self._TSI_GetParsedEventData = None + self._TSI_RemoveEventParser = None + self._TSI_CreateEventParser = None + self._TSIX_STLOG_GetMessageData = None + self._TSIX_STLOG_WaitMessage = None + self._TSIX_PORT_GetTestInfo = None + self._TSIX_PORT_GetTestCount = None + self._TSIX_TS_RunTest = None + self._TSIX_TS_AbortTests = None + self._TSIX_PORT_Deselect = None + self._TSIX_PORT_Select = None + self._TSI_MISC_GetErrorDescription = None + self._TSIX_TS_GetConfigItem = None + self._TSI2_TS_GetConfigItem = None + self._TSIX_TS_SetConfigItem = None + self._TSI2_TS_SetConfigItem = None + self._TSIX_DEV_CloseDevice = None + self._TSIX_DEV_GetDeviceRoleCount = None + self._TSIX_DEV_GetDeviceRoleName = None + self._TSIX_DEV_RescanDevices = None + self._TSIX_DEV_SelectRole = None + self._TSIX_DEV_OpenDevice = None + self._TSIX_DEV_GetDeviceName = None + self._TSI_DEV_GetDeviceName = None + self._TSI_DEV_GetDeviceCount = None + self._TSI_Clean = None + self._TSIX_Init = None + self._TSI_Init = None + self.lib = OS_Requirements("TSI").get_lib() + self.__setup_callback() + + def __del__(self): + self.deinitialize() + + def initialize(self): + + self._TSIX_Init = lib_method_wrapper(self.lib.TSIX_Init, [POINTER(TSI_INIT_CONFIGURATION)], + TSI_RESULT) + self._TSI_Init = lib_method_wrapper(self.lib.TSI_Init, [c_uint32], TSI_RESULT) + self._TSI_Clean = lib_method_wrapper(self.lib.TSI_Clean, [], TSI_RESULT) + self._TSI_DEV_GetDeviceCount = lib_method_wrapper(self.lib.TSIX_DEV_GetDeviceCount, [], + TSI_RESULT) + self._TSIX_DEV_GetDeviceName = lib_method_wrapper(self.lib.TSIX_DEV_GetDeviceName, + [TSI_DEVICE_ID, c_char_p, c_uint32], + TSI_RESULT) + self._TSIX_DEV_OpenDevice = lib_method_wrapper(self.lib.TSIX_DEV_OpenDevice, + [TSI_DEVICE_ID, POINTER(TSI_RESULT)], + TSI_HANDLE) + self._TSIX_DEV_SelectRole = lib_method_wrapper(self.lib.TSIX_DEV_SelectRole, + [TSI_HANDLE, c_int32], + TSI_RESULT) + self._TSIX_DEV_RescanDevices = lib_method_wrapper(self.lib.TSIX_DEV_RescanDevices, + [c_int32, c_int32, c_int32], + TSI_RESULT) + self._TSIX_DEV_GetDeviceRoleName = lib_method_wrapper(self.lib.TSIX_DEV_GetDeviceRoleName, + [TSI_HANDLE, c_int32, c_char_p, + c_uint32], + TSI_RESULT) + self._TSIX_DEV_GetDeviceRoleCount = lib_method_wrapper(self.lib.TSIX_DEV_GetDeviceRoleCount, + [TSI_HANDLE, ], + TSI_RESULT) + self._TSIX_DEV_CloseDevice = lib_method_wrapper(self.lib.TSIX_DEV_CloseDevice, + [TSI_HANDLE, ], + TSI_RESULT) + self._TSI2_TS_SetConfigItem = lib_method_wrapper(self.lib.TSI2_TS_SetConfigItem, + [TSI_HANDLE, TSI_LOGICAL_PORT, + TSI_CONFIG_ID, + c_void_p, c_uint32], + TSI_RESULT) + self._TSIX_TS_SetConfigItem = lib_method_wrapper(self.lib.TSIX_TS_SetConfigItem, + [TSI_HANDLE, TSI_CONFIG_ID, c_void_p, + c_uint32], + TSI_RESULT) + self._TSI2_TS_GetConfigItem = lib_method_wrapper(self.lib.TSI2_TS_GetConfigItem, + [TSI_HANDLE, TSI_LOGICAL_PORT, + TSI_CONFIG_ID, + c_void_p, c_uint32], + TSI_RESULT) + self._TSIX_TS_GetConfigItem = lib_method_wrapper(self.lib.TSIX_TS_GetConfigItem, + [TSI_HANDLE, TSI_CONFIG_ID, c_void_p, + c_uint32], + TSI_RESULT) + self._TSI_MISC_GetErrorDescription = lib_method_wrapper( + self.lib.TSI_MISC_GetErrorDescription, + [TSI_RESULT, c_char_p, c_uint32], + TSI_RESULT) + self._TSIX_PORT_Select = lib_method_wrapper(self.lib.TSIX_PORT_Select, + [TSI_HANDLE, TSI_LOGICAL_PORT], + TSI_RESULT) + self._TSIX_PORT_Deselect = lib_method_wrapper(self.lib.TSIX_PORT_Deselect, + [TSI_HANDLE, TSI_LOGICAL_PORT], + TSI_RESULT) + self._TSIX_TS_RunTest = lib_method_wrapper(self.lib.TSIX_TS_RunTest, + [TSI_HANDLE, TSI_TEST_ID], + TSI_RESULT) + self._TSIX_TS_AbortTests = lib_method_wrapper(self.lib.TSIX_TS_AbortTests, + [TSI_HANDLE], + TSI_RESULT) + self._TSIX_PORT_GetTestCount = lib_method_wrapper(self.lib.TSIX_PORT_GetTestCount, + [TSI_HANDLE, TSI_LOGICAL_PORT], + TSI_RESULT) + self._TSIX_PORT_GetTestInfo = lib_method_wrapper(self.lib.TSIX_PORT_GetTestInfo, + [TSI_HANDLE, TSI_LOGICAL_PORT, c_int32, + POINTER(TSI_TEST_ID), + POINTER(c_int32), c_char_p, c_uint32], + TSI_RESULT) + self._TSIX_STLOG_WaitMessage = lib_method_wrapper(self.lib.TSIX_STLOG_WaitMessage, + [TSI_HANDLE, c_int32], + TSI_RESULT) + self._TSIX_STLOG_GetMessageData = lib_method_wrapper(self.lib.TSIX_STLOG_GetMessageData, + [TSI_HANDLE, c_char_p, c_uint32, + POINTER(c_uint32)], + TSI_RESULT) + self._TSI_CreateEventParser = lib_method_wrapper(self.lib.TSI_CreateEventParser, + [POINTER(TSI_RESULT), c_uint32], + TSI_PARSER) + self._TSI_RemoveEventParser = lib_method_wrapper(self.lib.TSI_RemoveEventParser, + [TSI_PARSER, ], + TSI_RESULT) + self._TSI_GetParsedEventData = lib_method_wrapper(self.lib.TSI_GetParsedEventData, + [TSI_PARSER, POINTER(c_ubyte), c_int32, + POINTER(c_uint64), c_char_p, c_int32, + c_char_p, + c_int32, c_char_p, c_int32, c_char_p, + c_int32], + TSI_RESULT) + self._TSI_DEV_SetSearchMask = lib_method_wrapper(self.lib.TSI_DEV_SetSearchMask, + [TSI_DEVICE_CAPS, TSI_DEVICE_CAPS], + TSI_RESULT) + + return self.__TSIX_Init() if logging.is_enabled() else self.__TSI_Init() + + def deinitialize(self): + return self.__TSI_Clean() + + @staticmethod + def __setup_callback(): + global callback + + def ofp_impl(level, message): + try: + logging.log((level + 1) * 10, message.decode()) + except Exception as exc: + warnings.warn(f"[UniTAP] Logger function receive exception: {exc}") + + callback = TSI_USER_LOG_FUNCTION(ofp_impl) + + def __TSIX_Init(self): + global callback + + config = TSI_INIT_CONFIGURATION() + config.size = sizeof(config) + config.version = self._TSI_CURRENT_VERSION + config.function = callback + + return self._TSIX_Init(byref(config)) + + def __TSI_Init(self): + return self._TSI_Init(self._TSI_CURRENT_VERSION) + + def __TSI_Clean(self): + return self._TSI_Clean() + + def TSIX_DEV_GetDeviceCount(self): + return self._TSI_DEV_GetDeviceCount() + + def TSIX_DEV_GetDeviceName(self, device_id: int): + _device_name = create_string_buffer(TSI_NAME_SIZE) + result = self._TSIX_DEV_GetDeviceName(device_id, _device_name, TSI_NAME_SIZE) + return result, from_cstr(_device_name) + + def TSIX_DEV_OpenDevice(self, device_id: int): + result = c_int32(TSI_SUCCESS) + device = self._TSIX_DEV_OpenDevice(c_uint32(device_id), byref(result)) + return device + + def TSIX_DEV_SelectRole(self, device, role_id: int): + return self._TSIX_DEV_SelectRole(device, role_id) + + def TSIX_DEV_RescanDevices(self, search_options=1, required_caps=0, unallowed_caps=0): + return self._TSIX_DEV_RescanDevices(search_options, required_caps, unallowed_caps) + + def TSIX_DEV_GetDeviceRoleName(self, device, role_id: int): + _log_string_size = c_uint32(65536) + _log_string = create_string_buffer(_log_string_size.value) + result = self._TSIX_DEV_GetDeviceRoleName(device, role_id, _log_string, _log_string_size) + return result, from_cstr(_log_string) + + def TSIX_DEV_GetDeviceRoleCount(self, device): + return self._TSIX_DEV_GetDeviceRoleCount(device) + + def TSIX_DEV_CloseDevice(self, device): + return self._TSIX_DEV_CloseDevice(device) + + def TSIX_TS_SetPortConfigItem(self, device_handle, port_id, config_id, data, data_type=c_uint32, + data_count=1, data_size=0): + result = 0 + if data_size > 0: + if type(data) == int or type(data) == bool: + _data = data_type(data) + result = self._TSI2_TS_SetConfigItem(device_handle, port_id, config_id, + byref(_data), + data_size) + elif type(data) == str: + _data = create_string_buffer(data_count) + _data.value = str.encode(data) + result = self._TSI2_TS_SetConfigItem(device_handle, port_id, config_id, + byref(_data), + data_size) + else: + if type(data) == int or type(data) == bool or type(data) == float: + _data = data_type(data) + result = self._TSI2_TS_SetConfigItem(device_handle, port_id, config_id, + byref(_data), + sizeof(_data)) + elif type(data) == list or type(data) == bytearray or type(data) == tuple: + _data = (data_type * data_count)(*data) + result = self._TSI2_TS_SetConfigItem(device_handle, port_id, config_id, + byref(_data), + sizeof(_data)) + elif type(data) == str: + _data = create_string_buffer(data_count) + _data.value = str.encode(data) + result = self._TSI2_TS_SetConfigItem(device_handle, port_id, config_id, + byref(_data), + sizeof(_data)) + elif str(type(data)).find('WINFUNCTYPE') != -1 or str(type(data)).find( + 'CFUNCTYPE') != -1: + result = self._TSI2_TS_SetConfigItem(device_handle, port_id, config_id, data, + sizeof(data)) + elif issubclass(type(data), IntEnum): + _data = data_type(data.value) + result = self._TSI2_TS_SetConfigItem(device_handle, config_id, byref(_data), + sizeof(_data)) + else: + result = TSI_ERROR_NOT_IMPLEMENTED + + return result + + def TSIX_TS_SetConfigItem(self, device_handle, config_id, data, data_type=c_uint32, + data_count=1, + data_size=0): + result = 0 + if data_size > 0: + if type(data) == int or type(data) == bool: + _data = data_type(data) + result = self._TSIX_TS_SetConfigItem(device_handle, config_id, byref(_data), + data_size) + elif type(data) == str: + _data = create_string_buffer(data_count) + _data.value = str.encode(data) + result = self._TSIX_TS_SetConfigItem(device_handle, config_id, byref(_data), + data_size) + else: + if type(data) == int or type(data) == bool or type(data) == float: + _data = data_type(data) + result = self._TSIX_TS_SetConfigItem(device_handle, config_id, byref(_data), + sizeof(_data)) + elif type(data) == list or type(data) == bytearray: + _data = (data_type * data_count)(*data) + result = self._TSIX_TS_SetConfigItem(device_handle, config_id, byref(_data), + sizeof(_data)) + elif type(data) == str: + _data = create_string_buffer(data_count) + _data.value = str.encode(data) + result = self._TSIX_TS_SetConfigItem(device_handle, config_id, byref(_data), + sizeof(_data)) + elif str(type(data)).find('WINFUNCTYPE') != -1 or str(type(data)).find( + 'CFUNCTYPE') != -1: + result = self._TSIX_TS_SetConfigItem(device_handle, config_id, data, sizeof(data)) + elif issubclass(type(data), IntEnum): + _data = data_type(data.value) + result = self._TSIX_TS_SetConfigItem(device_handle, config_id, byref(_data), + sizeof(_data)) + elif issubclass(type(data), Structure): + result = self._TSIX_TS_SetConfigItem(device_handle, config_id, byref(data), + sizeof(data)) + else: + result = TSI_ERROR_NOT_IMPLEMENTED + + return result + + def TSIX_TS_GetPortConfigItem(self, device_handle, port_id, config_id, data_type, data_count=1): + if data_type is None: + rv = self._TSI2_TS_GetConfigItem(device_handle, port_id, config_id, data_type, + data_count) + result = rv, + elif data_count > 1: + _data = (data_type * data_count)() + rv = self._TSI2_TS_GetConfigItem(device_handle, port_id, config_id, _data, + sizeof(data_type) * data_count) + if data_type == c_ubyte: + result = rv, bytearray(_data), int(rv / sizeof(data_type)) + else: + result = rv, list(_data), int(rv / sizeof(data_type)) + + else: + _data = data_type(0) + rv = self._TSI2_TS_GetConfigItem(device_handle, port_id, config_id, byref(_data), + sizeof(_data)) + + if data_type in CTYPES_TYPE_LIST: + result = rv, _data.value + else: + result = rv, _data + + return result + + def TSIX_TS_GetConfigItem(self, device_handle, config_id, data_type, data_count=1): + if data_type is None: + rv = self._TSIX_TS_GetConfigItem(device_handle, config_id, data_type, data_count) + result = rv, + elif data_count > 1: + _data = (data_type * data_count)() + rv = self._TSIX_TS_GetConfigItem(device_handle, config_id, _data, + sizeof(data_type) * data_count) + result = rv, _data, data_count + else: + _data = data_type(0) + rv = self._TSIX_TS_GetConfigItem(device_handle, config_id, byref(_data), sizeof(_data)) + + if data_type in CTYPES_TYPE_LIST: + result = rv, _data.value + else: + result = rv, _data + + return result + + def TSI_MISC_GetErrorDescription(self, error): + err_msg_size = 256 + err_msg = create_string_buffer(err_msg_size) + result = self._TSI_MISC_GetErrorDescription(error, err_msg, err_msg_size) + + return result, from_cstr(err_msg) + + def TSIX_PORT_Select(self, device: TSI_HANDLE, port): + return self._TSIX_PORT_Select(device, port) + + def TSIX_PORT_Deselect(self, device: TSI_HANDLE, port): + return self._TSIX_PORT_Deselect(device, port) + + def TSIX_TS_RunTest(self, device: TSI_HANDLE, test_id: int): + return self._TSIX_TS_RunTest(device, c_uint32(test_id)) + + def TSIX_TS_AbortTests(self, device: TSI_HANDLE): + return self._TSIX_TS_AbortTests(device) + + def TSIX_PORT_GetTestCount(self, device: TSI_HANDLE, port: TSI_LOGICAL_PORT): + return self._TSIX_PORT_GetTestCount(device, port) + + def TSIX_PORT_GetTestInfo(self, device: TSI_HANDLE, port: TSI_LOGICAL_PORT, test_index: int): + msg_size = c_uint32(1024) + test_id = c_uint32(0) + msg_buffer = create_string_buffer(msg_size.value) + test_flags = c_int32(0) + + self._TSIX_PORT_GetTestInfo(device, port, test_index, byref(test_id), byref(test_flags), + msg_buffer, msg_size) + return test_id.value, test_flags.value, from_cstr(msg_buffer) + + def TSIX_STLOG_WaitMessage(self, device: TSI_HANDLE, max_wait: int): + return self._TSIX_STLOG_WaitMessage(device, c_int32(max_wait)) + + def TSIX_STLOG_GetMessageData(self, device: TSI_HANDLE): + msg_size = c_uint32(1024) + out_size = c_uint32(0) + msg_buffer = create_string_buffer(msg_size.value) + + result = self._TSIX_STLOG_GetMessageData(device, msg_buffer, msg_size, byref(out_size)) + + return result, from_cstr(msg_buffer), out_size.value + + def TSI_CreateEventParser(self, timestamp_res: int = 100): + result = c_int32(TSI_SUCCESS) + return self._TSI_CreateEventParser(byref(result), c_uint32(timestamp_res)) + + def TSI_RemoveEventParser(self, parser): + return self._TSI_RemoveEventParser(parser) + + def TSI_GetParsedEventData(self, parser, data, to_dict=False): + event_data = (c_ubyte * (len(data) - 12))(*data[12:]) + type_str_max_size = 128 + brief_str_max_size = 256 + content_str_max_size = 4096 + event_source_str_max_size = 64 + + typeStr = create_string_buffer(type_str_max_size) + briefStr = create_string_buffer(brief_str_max_size) + contentStr = create_string_buffer(content_str_max_size) + eventSourceStr = create_string_buffer(event_source_str_max_size) + timestamp = c_uint64(0) + + result = self._TSI_GetParsedEventData(parser, event_data, c_int32(len(data) - 12), + byref(timestamp), + typeStr, + type_str_max_size, briefStr, + brief_str_max_size, + contentStr, content_str_max_size, eventSourceStr, + event_source_str_max_size) + + if to_dict: + return result, \ + { + 'timestamp': timestamp.value, + 'type': from_cstr(typeStr), + 'brief': from_cstr(briefStr), + 'content': from_cstr(contentStr), + 'source': from_cstr(eventSourceStr), + 'data': data[12:] + } + else: + return result, timestamp.value, from_cstr(typeStr), from_cstr(briefStr), from_cstr( + contentStr), from_cstr( + eventSourceStr), data[12:] + + def TSI_DEV_SetSearchMask(self, required_caps, unallowed_caps): + return self._TSI_DEV_SetSearchMask(required_caps, unallowed_caps) + + +class TSIEventParser: + def __init__(self): + self.__parser = None + self.__parse_function = None + + def __enter__(self): + self.__parser = TSIWrapper().TSI_CreateEventParser() + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + TSIWrapper().TSI_RemoveEventParser(self.__parser) + + def parse(self, data, to_dict=False): + return TSIWrapper().TSI_GetParsedEventData(self.__parser, data, to_dict) + + +class TSI_INIT_CONFIGURATION(Structure): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.size = 0 + self.version = 0 + self.function = TSI_USER_LOG_FUNCTION() + + _fields_ = [ + ("size", c_uint), + ("version", c_uint), + ("function", TSI_USER_LOG_FUNCTION), + ] + + +def get_config_item_name(config_item) -> str: + items = globals().items() + variable_name = [k for k, v in items if v == config_item][0] + return variable_name + + +def from_cstr(src_str): + try: + return src_str.value.decode('cp1252') + except BaseException: + return src_str.value.decode('utf-8') + + +def to_cstr(src_str): + return c_char_p(src_str.encode('utf-8')) + + +def show_error(error_code): + if error_code >= TSI_SUCCESS: + return False + result = TSI_MISC_GetErrorDescription(error_code) + if result[0] < TSI_SUCCESS: + val = f"Error {str(error_code)}: (No description available)\n" + else: + val = f"Error {str(error_code)}: {str(result[1])}\n" + return val + + +def create_c_array(data, _type, _size): + buftype = _type * _size + buf = buftype() + buf.value = data + return buf + + +def TSIX_Init(): + return TSIWrapper().initialize() + + +def TSI_Clean(): + return TSIWrapper().deinitialize() + + +def TSIX_DEV_GetDeviceCount(): + return TSIWrapper().TSIX_DEV_GetDeviceCount() + + +def TSIX_DEV_GetDeviceName(device_id: int): + return TSIWrapper().TSIX_DEV_GetDeviceName(device_id) + + +def TSIX_DEV_OpenDevice(device_id: int): + return TSIWrapper().TSIX_DEV_OpenDevice(device_id) + + +def TSIX_DEV_SelectRole(device, role_id: int): + return TSIWrapper().TSIX_DEV_SelectRole(device, role_id) + + +def TSIX_DEV_RescanDevices(search_options=1, required_caps=0, unallowed_caps=0): + return TSIWrapper().TSIX_DEV_RescanDevices(search_options, required_caps, unallowed_caps) + + +def TSIX_DEV_GetDeviceRoleName(device, role_id: int): + return TSIWrapper().TSIX_DEV_GetDeviceRoleName(device, role_id) + + +def TSIX_DEV_GetDeviceRoleCount(device): + return TSIWrapper().TSIX_DEV_GetDeviceRoleCount(device) + + +def TSIX_DEV_CloseDevice(device): + return TSIWrapper().TSIX_DEV_CloseDevice(device) + + +def TSIX_TS_SetPortConfigItem(device_handle, port_id, config_id, data, data_type=c_uint32, + data_count=1, data_size=0): + return TSIWrapper().TSIX_TS_SetPortConfigItem(device_handle, port_id, config_id, data, + data_type, data_count, data_size) + + +def TSIX_TS_SetConfigItem(device_handle, config_id, data, data_type=c_uint32, data_count=1, + data_size=0): + return TSIWrapper().TSIX_TS_SetConfigItem(device_handle, config_id, data, + data_type, data_count, data_size) + + +def TSIX_TS_GetPortConfigItem(device_handle, port_id, config_id, data_type, data_count=1): + return TSIWrapper().TSIX_TS_GetPortConfigItem(device_handle, port_id, config_id, data_type, + data_count) + + +def TSIX_TS_GetConfigItem(device_handle, config_id, data_type, data_count=1): + return TSIWrapper().TSIX_TS_GetConfigItem(device_handle, config_id, data_type, data_count) + + +def TSI_MISC_GetErrorDescription(error): + return TSIWrapper().TSI_MISC_GetErrorDescription(error) + + +def TSIX_PORT_Select(device: TSI_HANDLE, port): + return TSIWrapper().TSIX_PORT_Select(device, port) + + +def TSIX_PORT_Deselect(device, port): + return TSIWrapper().TSIX_PORT_Deselect(device, port) + + +def TSIX_TS_RunTest(device: TSI_HANDLE, test_id: int): + return TSIWrapper().TSIX_TS_RunTest(device, test_id) + + +def TSIX_TS_AbortTests(device: TSI_HANDLE): + return TSIWrapper().TSIX_TS_AbortTests(device) + + +def TSIX_PORT_GetTestCount(device: TSI_HANDLE, port: TSI_LOGICAL_PORT): + return TSIWrapper().TSIX_PORT_GetTestCount(device, port) + + +def TSIX_PORT_GetTestInfo(device: TSI_HANDLE, port: TSI_LOGICAL_PORT, test_index: int): + return TSIWrapper().TSIX_PORT_GetTestInfo(device, port, test_index) + + +def TSIX_STLOG_WaitMessage(device: TSI_HANDLE, max_wait: int): + return TSIWrapper().TSIX_STLOG_WaitMessage(device, max_wait) + + +def TSIX_STLOG_GetMessageData(device: TSI_HANDLE): + return TSIWrapper().TSIX_STLOG_GetMessageData(device) + + +def TSI_CreateEventParser(timestamp_res: int = 100): + return TSIWrapper().TSI_CreateEventParser(timestamp_res) + + +def TSI_RemoveEventParser(parser): + return TSIWrapper().TSI_RemoveEventParser(parser) + + +def TSI_GetParsedEventData(parser, data, to_dict=False): + return TSIWrapper().TSI_GetParsedEventData(parser, data, to_dict) + + +def TSI_DEV_SetSearchMask(required_caps, unallowed_caps): + return TSIWrapper().TSI_DEV_SetSearchMask(required_caps, unallowed_caps) diff --git a/UniTAP/libs/lib_tsi/tsi_io.py b/UniTAP/libs/lib_tsi/tsi_io.py new file mode 100644 index 0000000..e19f0af --- /dev/null +++ b/UniTAP/libs/lib_tsi/tsi_io.py @@ -0,0 +1,253 @@ +import weakref +from ctypes import c_uint32, c_char +from typing import Callable, Tuple, List +from enum import IntEnum + +from .tsi import (TSI_HANDLE, TSI_LOGICAL_PORT, TSI_SUCCESS, \ + TSIX_TS_SetConfigItem, TSIX_TS_GetConfigItem, TSIX_TS_SetPortConfigItem, \ + TSIX_TS_GetPortConfigItem, TSIX_Init, TSI_Clean, TSIX_DEV_RescanDevices, \ + TSIX_DEV_OpenDevice, TSIX_DEV_GetDeviceName, TSIX_DEV_GetDeviceCount, + TSIX_DEV_CloseDevice, \ + TSIX_DEV_GetDeviceRoleCount, TSIX_DEV_SelectRole, TSIX_DEV_GetDeviceRoleName, + TSIX_PORT_Select, \ + get_config_item_name, TSIX_TS_RunTest, TSIX_STLOG_WaitMessage, + TSIX_STLOG_GetMessageData, \ + TSIX_PORT_GetTestCount, TSIX_PORT_GetTestInfo, TSI_TS_OF_MODE, \ + TSI_OFMODE_RUN_CALL_STRUCT_PROCEDURE, TSI_TS_OF_REQ_ID, TSI_TS_OF_CALLBACK, + TSI_FW_VERSION_TEXT, + Structure, TSI_TEST_STATUS, TSI_DEVICE_INFO_R, TSI_BUNDLE_VERSION_TEXT, \ + TSI_OFMODE_RUN_CALL_STRUCT_PROCEDURE_AND_RESULT, TSI_DEV_SetSearchMask, + DeviceMaskInternal, \ + TSI_TEST_LOG_CONFIGURATION, TSI_TEST_ERROR_CODE_R, TSI_TEST_PASSED, TSI_TEST_FAILED, + TSI_TEST_NOT_STARTED, TSI_TEST_ABORTED, TSIX_TS_AbortTests, TSI_TEST_DLG_CONFIG) + +from UniTAP.utils import tsi_logging as logging + + +class PortProtocol(IntEnum): + DisplayPort = 0 + HDMI = 1 + DisplayPortThrowUSBC = 2 + + +class TestStatus(Structure): + _fields_ = [ + ("status", c_uint32, 8), + ("result", c_uint32, 7), + ("oper_rq", c_uint32, 1), + ("error", c_uint32, 7), + ("log_ovf", c_uint32, 1), + ("progress", c_uint32, 8), + ] + + +class TestDialogConfig(Structure): + _fields_ = [ + ("disable_auto_precessing", c_uint32, 1), + ("reserved", c_uint32, 31) + ] + + def __init__(self, disable_auto_precessing: bool): + super().__init__() + self.disable_auto_precessing = disable_auto_precessing + + +class PortIO: + def __init__(self, device: TSI_HANDLE, index: TSI_LOGICAL_PORT, protocol: PortProtocol): + logging.info(f"[UniTAP] PortIO.init: {self}") + self.__device = device + self.__index = index + self.__protocol = protocol + self.active() + + def __del__(self): + logging.info(f"[UniTAP] PortIO.del: {self}") + + def set(self, config_id, data, data_type=c_uint32, data_count=1, data_size=0): + result = TSIX_TS_SetPortConfigItem(self.__device, self.__index, config_id, data, data_type, + data_count, + data_size) + if isinstance(data, bytearray): + logging.debug(f"[UniTAP] PortIO.set {self} {get_config_item_name(config_id)} " + f"with data: {data[:(10 if len(data) > 10 else len(data))]}[{len(data)}] " + f"({type(data)}); result: {result}") + else: + logging.debug(f"[UniTAP] PortIO.set {self} {get_config_item_name(config_id)} " + f"with data: {data} ({type(data)}); result: {result}") + return result + + def get(self, config_id, data_type=c_uint32, data_count=1): + result = TSIX_TS_GetPortConfigItem(self.__device, self.__index, config_id, data_type, + data_count) + logging.debug( + f"PortIO.get {self} {get_config_item_name(config_id)} " + f"with type: {data_type}[{data_count}]; result: {result}") + + return result + + def active(self): + TSIX_PORT_Select(self.__device, self.__index) + + def get_test_list(self) -> List[Tuple[int, int, str]]: + test_list = [] + for test_id in range(self.__get_test_count()): + test_list.append(self.__get_test_info(test_id)) + + return test_list + + def protocol(self) -> PortProtocol: + return self.__protocol + + def index(self): + return self.__index + + def __get_test_count(self) -> int: + return TSIX_PORT_GetTestCount(self.__device, self.__index) + + def __get_test_info(self, test_id: int): + return TSIX_PORT_GetTestInfo(self.__device, self.__index, test_id) + + +class DeviceIO: + MAX_FW_VERSION_STR_SIZE = 65535 + + def __init__(self, index: int): + logging.info(f"[UniTAP] DeviceIO.init: {self}") + self.__device = TSIX_DEV_OpenDevice(index) + self.__port_io_list = [] # type: list[PortIO] + self.__current_role = None + + def __del__(self): + self.__port_io_list.clear() + TSIX_DEV_CloseDevice(self.__device) + logging.info(f"[UniTAP] DeviceIO.del: {self}") + + @property + def device_handle(self): + return self.__device + + def set(self, config_id, data, data_type=c_uint32, data_count=1, data_size=0): + return TSIX_TS_SetConfigItem(self.__device, config_id, data, data_type, data_count, + data_size) + + def get(self, config_id, data_type=c_uint32, data_count=1): + return TSIX_TS_GetConfigItem(self.__device, config_id, data_type, data_count) + + def get_device_role_count(self) -> int: + return TSIX_DEV_GetDeviceRoleCount(self.__device) + + def get_device_role_name(self, index) -> str: + return TSIX_DEV_GetDeviceRoleName(self.__device, index)[1] + + def select_role(self, index): + logging.info( + f"[UniTAP] DeviceIO.select_role: {index}") + return TSIX_DEV_SelectRole(self.__device, index) + + def create_port_io(self, index, protocol: PortProtocol) -> PortIO: + self.__port_io_list.append(PortIO(self.__device, index, protocol)) + return weakref.proxy(self.__port_io_list[-1]) + + def set_opf_callback(self, callback: Callable): + TSIX_TS_SetConfigItem(self.__device, TSI_TS_OF_MODE, + TSI_OFMODE_RUN_CALL_STRUCT_PROCEDURE_AND_RESULT) + TSIX_TS_SetConfigItem(self.__device, TSI_TS_OF_REQ_ID, -1) + TSIX_TS_SetConfigItem(self.__device, TSI_TS_OF_CALLBACK, callback) + + def set_opf_config(self, config: TestDialogConfig): + self.set(TSI_TEST_DLG_CONFIG, config) + + def get_test_list(self) -> List[Tuple[int, int, str]]: + test_list = [] + for port in self.__port_io_list: + test_list.extend(port.get_test_list()) + return test_list + + def run_test(self, test_id: int): + logging.info( + f"[UniTAP] DeviceIO.run_test with id: {hex(test_id)} " + f"(group={test_id >> 16 & 0xffff}; test={test_id & 0xffff})") + + result = TSIX_TS_RunTest(self.__device, test_id) + + if result not in [TSI_TEST_PASSED, TSI_TEST_FAILED, TSI_TEST_NOT_STARTED, TSI_TEST_ABORTED]: + result = TSI_TEST_FAILED + + return result + + def abort_test(self): + logging.info(f"[UniTAP] DeviceIO.abort_test") + + return TSIX_TS_AbortTests(self.__device) + + def set_test_config(self, config: int): + if config > 0xffffffff or config < 0: + raise ValueError("Test config value must be more than 0 and less than 0xffffffff") + TSIX_TS_SetConfigItem(self.__device, TSI_TEST_LOG_CONFIGURATION, config) + + def is_log_message_available(self, max_wait_timeout=5000) -> int: + return int(TSIX_STLOG_WaitMessage(self.__device, max_wait_timeout)) + + def get_test_log_message(self) -> str: + return TSIX_STLOG_GetMessageData(self.__device) + + def get_test_status(self) -> TestStatus: + return TSIX_TS_GetConfigItem(self.__device, TSI_TEST_STATUS, TestStatus, 1)[1] + + def get_test_error_code(self) -> int: + return TSIX_TS_GetConfigItem(self.__device, TSI_TEST_ERROR_CODE_R, c_uint32, 1)[1] + + def get_fw_version_string(self): + return TSIX_TS_GetConfigItem(self.__device, TSI_FW_VERSION_TEXT, c_char, + self.MAX_FW_VERSION_STR_SIZE)[1].value.decode('utf-8') + + def get_prepared_fw_info(self): + return TSIX_TS_GetConfigItem(self.__device, TSI_DEVICE_INFO_R, c_char, + self.MAX_FW_VERSION_STR_SIZE)[1].value.decode('utf-8') + + def get_bundle_version(self): + ver = TSIX_TS_GetConfigItem(self.__device, TSI_BUNDLE_VERSION_TEXT, c_char, + self.MAX_FW_VERSION_STR_SIZE)[1].value.decode('utf-8') + if ver == '': + ver = 'not specified (NOT FOR SHARING)' + return ver + + def set_role(self, role): + self.__current_role = role + + def get_role(self): + if self.__current_role is not None: + return weakref.proxy(self.__current_role) + return None + + +class BaseIO: + def __init__(self): + self.__ref_count = TSIX_Init() + logging.info(f"[UniTAP] BaseIO.init: {self}; Ref count: {self.__ref_count}") + TSIX_DEV_RescanDevices() + + def cleanup(self): + self.__ref_count = TSI_Clean() + logging.info(f"[UniTAP] BaseIO.del: {self}; Ref count: {self.__ref_count}") + + def get_device_count(self) -> int: + if self.__ref_count > 0: + return TSIX_DEV_GetDeviceCount() + logging.error("[UniTAP] BaseIO.get_device_count call without TSI_Init") + + def get_device_name(self, index) -> str: + if self.__ref_count > 0: + return TSIX_DEV_GetDeviceName(index)[1] + logging.error("[UniTAP] BaseIO.get_device_name call without TSI_Init") + + def get_devices_by_mask(self, require_caps: DeviceMaskInternal, + unallowed_caps: DeviceMaskInternal): + if self.__ref_count > 0: + return TSI_DEV_SetSearchMask(require_caps.value, unallowed_caps.value) + logging.error("[UniTAP] BaseIO.get_devices_by_mask call without TSI_Init") + + def create_device_io(self, index: int) -> DeviceIO: + if self.__ref_count > 0: + return DeviceIO(index) + logging.error("[UniTAP] BaseIO.create_device_io call without TSI_Init") diff --git a/UniTAP/libs/lib_tsi/tsi_private_types.py b/UniTAP/libs/lib_tsi/tsi_private_types.py new file mode 100644 index 0000000..8440b44 --- /dev/null +++ b/UniTAP/libs/lib_tsi/tsi_private_types.py @@ -0,0 +1,140 @@ +from UniTAP.libs.lib_tsi.tsi_types import * + + +TSI_CAP_OWNER_UNKNOWN = 0x0 +TSI_CAP_OWNER_NONE = 0x1 +TSI_CAP_OWNER_TEST_SYSTEM = 0x2 +TSI_CAP_OWNER_PREVIEW = 0x3 +TSI_CAP_OWNER = TSI_BASE_LEGACY_CAP(0x1f) + +TSI_SEARCHOPTIONS_FIX_500_IN_UCDCONFIG = 0x80000000 + +TSI_VIDCAP_AVAILABLE_FRAME_COUNT_R = TSI_BASE_LEGACY_VIDCAP(0x1d) +TSI_VIDCAP_CAPTURE_STATUS_R = TSI_BASE_LEGACY_VIDCAP(0x1e) +TSI_VIDCAP_AVAILABLE_FRAME_COUNT = TSI_VIDCAP_AVAILABLE_FRAME_COUNT_R +TSI_CRC_MEASUREMENT_DATA_R = TSI_BASE_R_CRC_VIDEO(0x3) + +TSI_AUDCAP_PACKET_HEADER_R = TSI_BASE_LEGACY_AUDCAP(0xe) + +TSI_PG_ADAPTIVE_SYNC_CTRL = 0x733 +TSI_PG_ADAPTIVE_SYNC_CAPS = 0x734 +TSI_PG_ADAPTIVE_SYNC_STS = 0x735 +TSI_PG_CAPS_R = 0x736 +TSI_PG_PR_CAPS_R = 0x737 +TSI_PG_PR_CTRL_W = 0x738 +TSI_PG_PR_STATUS_R = 0x738 +TSI_PG_PR_CFG = 0x739 +TSI_DPTX_EVENTS = TSI_BASE_DPTX(0x0e) +TSI_DPTX_POST_LT_FEATURES = TSI_BASE_DPTX(0x68) +TSI_DPTX_SDP_CTRL = TSI_BASE_DPTX(0x74) +TSI_TEST_STATUS = 0x80000019 # Used for reading test status. +TSI_DEVICE_INFO_R = 0x80000020 # Used for reading FW version +TSI_TEST_ERROR_CODE_R = 0x80000021 # Used for reading test error code +TSI_PDC_DPAM_CONFIG_R = TSI_BASE_PDC(0x33) +TSI_PDC_HW_CAPS_R = TSI_BASE_PDC(0x00) +TSI_PDC_CONTRACT_DATA = TSI_BASE_PDC(0x09) +TSI_PDC_TX_ID_VDO_CNT = TSI_BASE_PDC(0x14) +TSI_PDC_TX_ID_VDO = TSI_BASE_PDC(0x15) + +TSI_DPTX_OUT_LC = TSI_BASE_DPTX(0x52) +TSI_DPTX_OUT_BR = TSI_BASE_DPTX(0x53) +TSI_DPTX_OUT_LINK_MODE = TSI_BASE_DPTX(0x54) +TSI_DP2TX_OUT_BR = TSI_BASE_DPTX(0x55) +TSI_DP2TX_OUT_LC = TSI_BASE_DPTX(0x5A) +TSI_DP2TX_OUT_TEST_PATTERN = TSI_BASE_DPTX(0x5B) + +TSI_MLEG_CONTROL = TSI_BASE_MLEG(0x00) +TSI_MLEG_SYMBOL_REPLACE_A = TSI_BASE_MLEG(0x01) +TSI_MLEG_SYMBOL_REPLACE_MASK_A = TSI_BASE_MLEG(0x02) +TSI_MLEG_SYMBOL_REPLACE_B = TSI_BASE_MLEG(0x03) +TSI_MLEG_SYMBOL_REPLACE_MASK_B = TSI_BASE_MLEG(0x04) +TSI_MLEG_DELAY_COUNTER = TSI_BASE_MLEG(0x0A) +TSI_MLEG_LANE0_REPLACE_COUNTERS = TSI_BASE_MLEG(0x05) +TSI_MLEG_LANE1_REPLACE_COUNTERS = TSI_BASE_MLEG(0x06) +TSI_MLEG_LANE2_REPLACE_COUNTERS = TSI_BASE_MLEG(0x07) +TSI_MLEG_LANE3_REPLACE_COUNTERS = TSI_BASE_MLEG(0x08) + +TSI_HDCP_2X_CFG = TSI_BASE_LEGACY_HDCP2(0x2) + +TSI_W_PDC_EPR_MODE_CONTROL = TSI_BASE_PDC(0x04) + +TSI_EVCAP_TIMESTAMP = TSI_BASE_LEGACY_EVCAP(0x8) + +TSI_DPRX_INF_VALID_EX = TSI_BASE_DPRX(0x29) +TSI_DPRX_SDP_CRC16_CTRL = TSI_BASE_DPRX(0x98) +TSI_DPRX_SDP_CRC16_COUNTERS = TSI_BASE_DPRX(0x99) + +TSI_HDRX_INF_STATUS = TSI_BASE_HDRX(0x0C) +TSI_HDRX_INF_VALID = TSI_BASE_HDRX(0x0D) +TSI_HDRX_INF_SELECT = TSI_BASE_HDRX(0x0E) +TSI_HDRX_INF_DATA = TSI_BASE_HDRX(0x0F) + +MAX_EDID_SIZE = 4096 + +TSI_DPRX_DSC_TEST_CRC = TSI_BASE_DPRX(0x43) +TSI_DPRX_CRD_FEATURES = TSI_BASE_DPRX(0x63) +TSI_DPRX_ALPM_LT_INFO_R = TSI_BASE_DPRX(0x38) + +TSI_DPRX_MSA_INFO_R = TSI_BASE_DPRX(0x48) +TSI_DPRX_MST_STATUS_R = TSI_BASE_DPRX(0x49) + +TSI_GENERIC_STATUS = 0x210 # TSI State flags. NOTE: Access mode changes to R+W + +TSI_VIDCAP_HDCP_PROTECTED_R = 0x8000f000 +TSI_VIDCAP_HDCP_DECRYPTED_R = 0x8000f001 +TSI_HDCP_COMPLIANT_R = 0x8000f002 + +TSI_DSC_BLOCK_NO = 0x80000009 # Used for stating start & end address to DSC PG. Changed 20191202 because not properly reserved. + +TSI_CRC_DEFINITIONS = TSI_BASE_CRC_VIDEO(14) # CRC definitions. Default setting is 0 +TSI_CRC_WINDOW_0_0 = TSI_BASE_CRC_VIDEO(15) # Left border/window pixel for first window 15..0. Top border/window line for first window 31..16. Default setting is 0 +TSI_CRC_WINDOW_0_1 = TSI_BASE_CRC_VIDEO(16) # Right border pixel/ window width for first window. 15..0. Bottom border line / window height for first window 31..16. Default setting is 0 +TSI_CRC_WINDOW_1_0 = TSI_BASE_CRC_VIDEO(17) # Left border/window pixel for second window 15..0. Top border/window line for second window 31..16. Default setting is 0 +TSI_CRC_WINDOW_1_1 = TSI_BASE_CRC_VIDEO(18) # Right border pixel/ window width for second window. 15..0. Bottom border line / window height for second window 31..16. Default setting is 0 +TSI_CRC_WINDOW_2_0 = TSI_BASE_CRC_VIDEO(19) # Left border/window pixel for third window 15..0. Top border/window line for third window 31..16. Default setting is 0 +TSI_CRC_WINDOW_2_1 = TSI_BASE_CRC_VIDEO(20) # Right border pixel/ window width for third window. 15..0. Bottom border line / window height for third window 31..16. Default setting is 0 +TSI_CRC_WINDOW_3_0 = TSI_BASE_CRC_VIDEO(21) # Left border/window pixel for fourth window 15..0. Top border/window line for fourth window 31..16. Default setting is 0 +TSI_CRC_WINDOW_3_1 = TSI_BASE_CRC_VIDEO(22) # Right border pixel/ window width for fourth window. 15..0. Bottom border line / window height for fourth window 31..16. Default setting is 0 + +TSI_R_LC_COUNT = 0x80000010 # Number of licenses available. +TSI_W_LC_SELECT = 0x80000011 # Index from 0 to LC_Count-1: Select which license to access through the CI's below +TSI_R_LC_CODE = 0x80000012 # License ID (To identify type of license: "Basic" / "Advanced" / etc...) +TSI_R_LC_NAME = 0x80000013 # Human readable name for license. +TSI_R_LC_GET_KEY = 0x80000014 # Key string (40 characters, including the terminating NULL) +TSI_W_LC_ADD_KEY = 0x80000015 # -> Write license key string. Matching key is validated and added. +TSI_W_LC_REMOVE_KEY = 0x80000016 # -> Write license key string. Matching key is deleted. +TSI_W_LC_HAS_CODE = 0x80000017 # Check if specified code is available on device. +TSI_R_LC_SHA_TYPE = 0x80000018 # Check SHA type used in device. + +TSI_DEVICE_DESCRIPTOR = 0x81000000 +TSI_DEBUG_INFORMATION = 0x82000000 + +TSI_SELECT_SUITE = TSI_BASE_TEST(0x04) +TSI_TEST_DLG_CONFIG = TSI_BASE_TEST(0x22) + +TSI_LOGICAL_PORT = c_uint +TSI_PORT_FLAGS = c_uint + +TSI_DPRX_ALPM_STATS_CONTROL_W = TSI_BASE_DPRX(0x60) +TSI_DPRX_ALPM_STATS_STATUS_R = TSI_BASE_DPRX(0x60) +TSI_DPRX_ALPM_STATS_VB_R = TSI_BASE_DPRX(0x61) +TSI_DPRX_ALPM_STATS_IFP_R = TSI_BASE_DPRX(0x62) +TSI_DPRX_ALPM_STATS_CTRL_W = TSI_DPRX_ALPM_STATS_CONTROL_W +TSI_DSC_TX_CRC = TSI_BASE_DSC(0x01) +TSI_DSC_MEMORY_BLOCK = TSI_BASE_DSC(0x05) +TSI_DSC_DATA_SIZE = TSI_BASE_DSC(0x06) + +TSI_TERMINAL_RW = 0xFFFFFFFE + +TSI_DPTX_CABLE_ATTRIBUTES_R = TSI_BASE_DPTX(0x6E) +TSI_DPRX_CABLE_ATTRIBUTES_R = TSI_BASE_DPRX(0x6E) + +TSI_DPRX_LT_INTERNAL_USE = 0x50000000 + 0xFFFFF +TSI_DPRX_LT_ROUTE_CREATE = TSI_BASE_LEGACY_DPRX(0x1A + TSI_DPRX_LT_INTERNAL_USE) + + +class DeviceMaskInternal(IntEnum): + Sink = TSI_DEVCAP_SINK + Source = TSI_DEVCAP_SOURCE + All = TSI_DEVCAP_ALL_CAPS + Nothing = 0 diff --git a/UniTAP/libs/lib_tsi/tsi_types.py b/UniTAP/libs/lib_tsi/tsi_types.py new file mode 100644 index 0000000..f1ac0a9 --- /dev/null +++ b/UniTAP/libs/lib_tsi/tsi_types.py @@ -0,0 +1,2414 @@ +import platform +from typing import List + +from ctypes import c_void_p, c_int, c_uint, Structure, c_char_p, POINTER, c_bool, c_uint32, \ + c_uint64, c_int8, c_uint8, c_char, c_int16, c_int32, c_int64 +from enum import IntEnum +from ctypes import wintypes + +if platform.system() == 'Windows': + from ctypes import WINFUNCTYPE + TSI_USER_LOG_FUNCTION = WINFUNCTYPE(None, c_int32, c_char_p) +else: + from ctypes import CFUNCTYPE + TSI_USER_LOG_FUNCTION = CFUNCTYPE(None, c_int32, c_char_p) + + +class CtypeEnum(IntEnum): + @classmethod + def from_param(cls, obj): + return int(obj) + + +OS_WINDOW_ID = wintypes.HWND +OS_SEMAPHORE = c_void_p + +# !CI_PARSE_DISABLE +TSI_BASE_LEGACY_REFFRAME = lambda offset: (0x10 + offset) # Reserved range from 0x10 to 0x1ff. +TSI_BASE_LEGACY_INPUT = lambda offset: (0x200 + offset) # Reserved range from 0x200 to 0x20f. +TSI_BASE_LEGACY_GENERIC = lambda offset: (0x210 + offset) # Reserved range from 0x210 to 0x21f. +TSI_BASE_LEGACY_AUDIO = lambda offset: (0x220 + offset) # Reserved range from 0x220 to 0x22f. +TSI_BASE_LEGACY_VX1 = lambda offset: (0x240 + offset) # Reserved range from 0x240 to 0x24f. +TSI_BASE_LEGACY_DPRX_MSA = lambda offset: (0x260 + offset) # Reserved range from 0x260 to 0x27f. +TSI_BASE_LEGACY_HDCP1 = lambda offset: (0x280 + offset) # Reserved range from 0x280 to 0x28f. +TSI_BASE_LEGACY_HDCP2 = lambda offset: (0x290 + offset) # Reserved range from 0x290 to 0x29f. +TSI_BASE_LEGACY_LVDS = lambda offset: (0x2a0 + offset) # Reserved range from 0x2a0 to 0x2af. +TSI_BASE_LEGACY_DPRX = lambda offset: (0x2b0 + offset) # Reserved range from 0x2b0 to 0x2ef. +TSI_BASE_LEGACY_AUDGEN = lambda offset: (0x2f0 + offset) # Reserved range from 0x2f0 to 0x2ff. +TSI_BASE_LEGACY_CAP = lambda offset: (0x300 + offset) # Reserved range from 0x300 to 0x31f. +TSI_BASE_LEGACY_VIDCAP = lambda offset: (0x320 + offset) # Reserved range from 0x320 to 0x33f. +TSI_BASE_LEGACY_AUDCAP = lambda offset: (0x340 + offset) # Reserved range from 0x340 to 0x35f. +TSI_BASE_LEGACY_EVCAP = lambda offset: (0x360 + offset) # Reserved range from 0x360 to 0x37f. +TSI_BASE_LEGACY_USBC = lambda offset: (0x600 + offset) # Reserved range from 0x600 to 0x6ff. +TSI_BASE_LEGACY_PATGEN = lambda offset: (0x700 + offset) # Reserved range from 0x700 to 0x77f. +TSI_BASE_LEGACY_DPTX = lambda offset: (0x780 + offset) # Reserved range from 0x780 to 0x7ff. +TSI_BASE_LEGACY_HDTX = lambda offset: (0x800 + offset) # Reserved range from 0x800 to 0x80f. +TSI_BASE_LEGACY_HDRX = lambda offset: (0x810 + offset) # Reserved range from 0x810 to 0x81f. +TSI_BASE_LEGACY_HDRX_TIM = lambda offset: (0x820 + offset) # Reserved range from 0x820 to 0x82f. +TSI_BASE_LEGACY_MEMORY = lambda offset: (0x880 + offset) # Reserved range from 0x880 to 0x89f. +TSI_BASE_LEGACY_PDO = lambda offset: (0xfe0 + offset) # Reserved range from 0xfe0 to 0xfff. +TSI_BASE_LEGACY_VIDTEST = lambda offset: (0x1000 + offset) # Reserved range from 0x1000 to 0x109f. +TSI_BASE_LEGACY_EDID = lambda offset: (0x1100 + offset) # Reserved range from 0x1100 to 0x110f. +TSI_BASE_LEGACY_HPDARC = lambda offset: (0x1200 + offset) # Reserved range from 0x1200 to 0x121f. +TSI_BASE_LEGACY_AUDTEST = lambda offset: (0x2020 + offset) # Reserved range from 0x2020 to 0x202f. +TSI_BASE_LEGACY_SCRIPT = lambda offset: (0x2030 + offset) # Reserved range from 0x2030 to 0x203f. +TSI_BASE_LEGACY_ADC = lambda offset: (0x12000 + offset) # Reserved range from 0x12000 to 0x120ff. + +TSI_BASE_DVI_RX = lambda offset: (0x00010000 + offset) +TSI_BASE_DP_RX = lambda offset: (0x00010100 + offset) +TSI_BASE_HDMI_RX = lambda offset: (0x00010200 + offset) +TSI_BASE_CRC_VIDEO = lambda offset: (0x00010300 + offset) +TSI_BASE_R_CRC_VIDEO = lambda offset: (0x00010380 + offset) +TSI_BASE_HDMI_RX_CEC = lambda offset: (0x00010400 + offset) +TSI_BASE_USBC_EL = lambda offset: (0x00010500 + offset) +TSI_BASE_DP_LTT = lambda offset: (0x00010700 + offset) +TSI_BASE_R_HDMI_INFOFRAME = lambda offset: (0x00011000 + offset) # Reserved range from 0x11000 to 0x11200. +TSI_BASE_USBC_DP_SRC_DUT = lambda offset: (0x00011300 + offset) +TSI_BASE_USBC_DP_SINK_DUT = lambda offset: (0x00011400 + offset) +TSI_BASE_R_DP_SDP = lambda offset: (0x00011500 + offset) # Reserved range from 0x11500 to 0x116FF (0x115ff?). +TSI_BASE_VD = lambda offset: (0x00100000 + offset) +TSI_BASE_DP14_SRCCTS = lambda offset: (0x010e0000 + offset) +TSI_BASE_TEST_HDCP2_1A = lambda offset: (0x010F0000 + offset) +TSI_BASE_TEST_HDCP2_1B = lambda offset: (0x01110000 + offset) +TSI_BASE_TEST_HDCP2_2C = lambda offset: (0x01120000 + offset) +TSI_BASE_TEST_HDCP2_3A = lambda offset: (0x01130000 + offset) +TSI_BASE_TEST_HDCP2_3B = lambda offset: (0x01140000 + offset) +TSI_BASE_TEST_HDCP2_3C = lambda offset: (0x01150000 + offset) +TSI_BASE_DP14_SINKCTS = lambda offset: (0x01160000 + offset) +TSI_BASE_VRR_SINK_DUT = lambda offset: (0x01170000 + offset) +TSI_BASE_VRR_SRC_DUT = lambda offset: (0x01180000 + offset) +TSI_BASE_DP20_SRCCTS = lambda offset: (0x01190000 + offset) +TSI_BASE_DP20_SINKCTS = lambda offset: (0x011A0000 + offset) +TSI_BASE_DP20_LTTPRCTS = lambda offset: (0x011B0000 + offset) +TSI_BASE_HDMI_SRCCTS = lambda offset: (0x011C0000 + offset) +TSI_BASE_HDMI_SINKCTS = lambda offset: (0x011D0000 + offset) +TSI_BASE_HDMI_CALBE_CHECK_SINK = lambda offset: (0x011F0000 + offset) + +TSI_BASE_TPG = lambda offset: (0x50000000 + 0x5000 + offset) +TSI_BASE_TEST = lambda offset: (0x50000000 + 0xC000 + offset) +TSI_BASE_DPRX = lambda offset: (0x50000000 + 0x21000 + offset) +TSI_BASE_DPTX = lambda offset: (0x50000000 + 0x22000 + offset) +TSI_BASE_HDRX = lambda offset: (0x50000000 + 0x23000 + offset) +TSI_BASE_HDTX = lambda offset: (0x50000000 + 0x24000 + offset) +TSI_BASE_SPDIF = lambda offset: (0x50000000 + 0x25000 + offset) +TSI_BASE_PDC = lambda offset: (0x50000000 + 0x26000 + offset) +TSI_BASE_PLAYBACK = lambda offset: (0x50000000 + 0x2B000 + offset) +TSI_BASE_BULK_CAPTURE = lambda offset: (0x50000000 + 0x2C000 + offset) +TSI_BASE_MLEG = lambda offset: (0x50000000 + 0x2D000 + offset) +TSI_BASE_DSC = lambda offset: (0x50000000 + 0x32000 + offset) +TSI_BASE_INPUT = lambda offset: (0x50000000 + 0x99000 + offset) +TSI_BASE_DID = lambda offset: (0x50000000 + 0xF0000 + offset) +# !CI_PARSE_ENABLE + + +# !CI_PARSE_DISABLE +TSI_SUCCESS = 0 # No error -- Generic indication of success. +TSI_ERROR_NOT_INITIALIZED = -1 # TSI_Init() has not been called, and/or TSI_Clean() has already been called. +TSI_ERROR_COMPATIBILITY_MISMATCH = -2 # Compatibility mode given to TSI_Init() does not match a previously given one. +TSI_ERROR_NOT_COMPATIBLE = -3 # The requested compatibility mode is not available in this TSI version. +TSI_ERROR_DLL_NOT_FOUND = -4 # A dynamic link library file was not found. +TSI_ERROR_DLL_VERSION_READ = -5 # Failed to determine dynamic link library file's version. +TSI_ERROR_OUT_OF_MEMORY = -6 # Failed to allocate system resources (Memory and/or other resources) +TSI_ERROR_FUNCTION_NOT_FOUND = -7 # A dynamic link library is missing a required function. +TSI_ERROR_ACCESS_DENIED = -8 # The feature requires a license key and the required license key is not installed. +TSI_ERROR_NO_REFERENCES = -9 # A reference counted item is already at zero references, or the item is already destroyed. +TSI_ERROR_DEVICE_INDEX_OUT_OF_RANGE = -10 # A device index is out of range. +TSI_ERROR_INVALID_PARAMETER = -11 # One or more of the parameters passed to a function are invalid. +TSI_ERROR_INPUT_ENABLED = -12 # The requested operation is not available while input is enabled. +TSI_ERROR_INPUT_DISABLED = -13 # The requested operation is not available while input is disabled. +TSI_ERROR_INPUT_ENABLE_FAILED = -14 # Failed to enable input due to resources allocation problems. +TSI_ERROR_OPEN_DEVICE = -15 # Failed to open requested device. (Device in use? Device removed?) +TSI_ERROR_INPUT_SELECT = -16 # Failed to select input. +TSI_ERROR_NOT_IMPLEMENTED = -17 # The requested function is not implemented. +TSI_ERROR_UNEXPECTED_ITEM_SIZE = -18 # Set/Get config item: Size of the item being set or get was unexpected. +TSI_ERROR_UNSUPPORTED_CONFIG_ID = -19 # The used configuration item ID is unknown and/or not supported on the device. +TSI_ERROR_CONFIGURATION_ITEM_NOT_SET = -20 # Get config item: The configuration item has no value/data stored. +TSI_ERROR_FILE_CREATE = -21 # Failed to create save file. +TSI_ERROR_FILE_WRITE = -22 # Failed to write into file. +TSI_ERROR_FILE_OPEN = -23 # Failed to open existing file. +TSI_ERROR_FILE_READ = -24 # Failed to read file. +TSI_ERROR_INVALID_FILE = -25 # Unsupported file format. +TSI_ERROR_DATA_CORRUPTED = -26 # The load file's format is incorrect, partial or corrupted. +TSI_ERROR_FORMAT_MISMATCH = -27 # The (reference) frame does not match input format. +TSI_ERROR_UNSUPPORTED_TEST = -28 # Given test ID is not available. +TSI_ERROR_COMPARE_FAILED = -29 # Compare function failed. +TSI_ERROR_NO_REFERENCE_FRAME = -30 # Reference frame not set up.. +TSI_ERROR_TIMEOUT = -31 # Requested operation did not complete within allowed time. +TSI_ERROR_NO_DATA_AVAILABLE = -32 # Requested/needed data is not available. (No signal Feature disabled etc...) +TSI_ERROR_CONFIG_ITEM_ACCESS = -33 # Configuration item read or write failed. +TSI_ERROR_PRESENT_GRAPHICS = -34 # Failed to show graphics preview frame/modify preview area properties. +TSI_ERROR_UNSUPPORTED_FORMAT = -35 # The capture format or file format is not supported. +TSI_ERROR_DEVICE_SPECIFIC = -36 # A problem occurred on the device. +TSI_ERROR_DISK_FILE_IO = -37 # A problem occurred while reading or writing to a file. +TSI_ERROR_INTERNAL = -38 # Internal state is invalid. +TSI_ERROR_CONFIGURATION_ITEM_VALUE = -39 # Attempted to set a configuration item to a value that is not allowed. +TSI_ERROR_CAPTURE_BROKEN = -40 # Capture (video/audio/other) was re-started while testing. +TSI_ERROR_OS_ERROR = -41 # An Operating system call has failed. +TSI_ERROR_DATA_PROTECTION_ENABLED = -42 # Video/Audio data is HDCP protected and can't be used for testing or saving. +TSI_ERROR_TEST_REQUIREMENTS_NOT_MET = -43 # Test requirements were not met by capture device. +TSI_ERROR_UNSUPPORTED_COLORSPACE = -44 # The frame color space is not supported by the function +TSI_ERROR_NO_DEVICE_SELECTED = -45 # No device is selected +TSI_ERROR_INVALID_ACCESS_MODE = -46 # Attempt to read a write-only CI, or write a read-only CI. +TSI_ERROR_OUTPUT_ENABLED = -47 # The attempted operation is not available if source function is enabled +TSI_ERROR_OPERATION_DATA_LOSS = -48 # The attempted operation would have caused unwanted loss of information. +TSI_ERROR_BAD_FW_VERSION = -49 # Firmware of the selected device needs to be updated +TSI_ERROR_INVALID_HANDLE = -50 # TSI X only: Invalid handle used. +TSI_ERROR_NOT_SUPPORTED = -51 # The function is not supported by the device. +TSI_ERROR_ALREADY_STARTED = -52 # The requested functionality is already started. +TSI_ERROR_ALREADY_STOPPED = -53 # The requested functionality is already stopped. +TSI_ERROR_IN_SCRIPT = -54 # Error in provided script. +TSI_ERROR_NOTHING_TO_UPDATE = -55 # Nothing to update: when try to apply something but no changes pending +TSI_ERROR_NOT_COMPLETED = -56 # The attempted operation and/or function is not completed. +TSI_ERROR_DEVICE_RESET = -57 # The attempted device reset operation failed. +TSI_ERROR_TEST_START = -58 # Test failed during startup phase. +TSI_ERROR_OUT_OF_RANGE = -59 # Provided value is out of existing range. +TSI_ERROR_DEVICE_CONNECTION = -60 # Device connection error. +# !CI_PARSE_ENABLE + + +# !CI_PARSE_DISABLE +TSI_IMF_PK444_RGB_W8_C8 = 0x000 +TSI_IMF_PK444_RGB_W16_C12 = 0x004 +TSI_IMF_PK444_RGB_W16_C16 = 0x001 +TSI_IMF_PK444_RGB0_W8_C8 = 0x002 +TSI_IMF_PK444_RGB0_W16_C16 = 0x003 +TSI_IMF_PK444_BGR0_W8_C6 = 0x010 +TSI_IMF_PK444_BGR0_W8_C8 = 0x011 +TSI_IMF_PK444_BGR0_W8_C10 = 0x012 +TSI_IMF_PK444_BGR_W16_C12 = 0x013 +TSI_IMF_PK444_BGR_W16_C16 = 0x014 +TSI_IMF_PK444_RGB_ALIGN_LSB_W8_C6 = 0x00A +TSI_IMF_PK444_RGB_ALIGN_LSB_W8_C8 = TSI_IMF_PK444_RGB_W8_C8 +TSI_IMF_PK444_RGB_ALIGN_LSB_W16_C10 = 0x00B +TSI_IMF_PK444_RGB_ALIGN_LSB_W16_C12 = 0x00C +TSI_IMF_PK444_RGB_ALIGN_LSB_W16_C16 = TSI_IMF_PK444_RGB_W16_C16 + +TSI_IMF_PK444_YCbCr_W8_C8 = 0x100 +TSI_IMF_PK444_YCbCr_W16_C16 = 0x101 +TSI_IMF_PK444_YCbCr_ALIGN_LSB_W8_C8 = TSI_IMF_PK444_YCbCr_W8_C8 +TSI_IMF_PK444_YCbCr_ALIGN_LSB_W16_C10 = 0x10B +TSI_IMF_PK444_YCbCr_ALIGN_LSB_W16_C12 = 0x10C +TSI_IMF_PK444_YCbCr_ALIGN_LSB_W16_C16 = TSI_IMF_PK444_YCbCr_W16_C16 +TSI_IMF_PK444_CbYCr0_W8_C8 = 0x110 +TSI_IMF_PK444_CbYCr0_W8_C10 = 0x111 +TSI_IMF_PK444_CbYCr_W16_C12 = 0x112 +TSI_IMF_PK444_CbYCr_W16_C16 = 0x113 + +TSI_IMF_PK422_CbYCrY_W16_C8 = 0x200 +TSI_IMF_PK422_CbYCrY_W16_C10 = 0x210 +TSI_IMF_PK422_CbYCrY_W16_C12 = 0x211 +TSI_IMF_PK422_CbYCrY_W16_C16 = 0x201 +TSI_IMF_PK422_CbYCrY_ALIGN_LSB_W8_C8 = 0x20A +TSI_IMF_PK422_CbYCrY_ALIGN_LSB_W16_C10 = 0x21B +TSI_IMF_PK422_CbYCrY_ALIGN_LSB_W16_C12 = 0x21C +TSI_IMF_PK422_CbYCrY_ALIGN_LSB_W16_C16 = TSI_IMF_PK422_CbYCrY_W16_C16 + +TSI_IMF_PL420_YCbCr_W8_C8 = 0x310 +TSI_IMF_PL420_YCbCr_W16_C10 = 0x311 +TSI_IMF_PL420_YCbCr_W16_C12 = 0x312 +TSI_IMF_PL420_YCbCr_W16_C16 = 0x313 +TSI_IMF_PL420_ALIGN_LSB_W8_C8 = TSI_IMF_PL420_YCbCr_W8_C8 +TSI_IMF_PL420_ALIGN_LSB_W16_C10 = 0x31B +TSI_IMF_PL420_ALIGN_LSB_W16_C12 = 0x31C +TSI_IMF_PL420_ALIGN_LSB_W16_C16 = TSI_IMF_PL420_YCbCr_W16_C16 + +TSI_IMF_PK420_CxYY0_W8_C8 = 0x320 +TSI_IMF_PK420_CxYY0_W8_C10 = 0x321 +TSI_IMF_PK420_CxYY_W16_C12 = 0x322 +TSI_IMF_PK420_CxYY_W16_C16 = 0x323 + +TSI_IMF_PK444_YYY0_W8_C6 = 0x400 +TSI_IMF_PK444_YYY0_W8_C8 = 0x401 +TSI_IMF_PK444_YYY0_W8_C10 = 0x402 +TSI_IMF_PK444_YYY_W16_C12 = 0x403 +TSI_IMF_PK444_YYY_W16_C16 = 0x404 + +TSI_IMF_PKN_DSC_W8_CN = 0x600 + +TSI_IMF_PK444_RAW0_W8_C6 = 0x500 +TSI_IMF_PK444_RAW0_W8_C8 = 0x501 +TSI_IMF_PK444_RAW0_W8_C10 = 0x502 +TSI_IMF_PK444_RAW_W16_C12 = 0x503 +TSI_IMF_PK444_RAW_W16_C16 = 0x504 + +# Frame format ID definitions +TSI_FRAME_FORMAT_BMP = 0x00000001 # 24 bits per pixel (true-color) BMP file, LOSSY CONVERSION ALLOWED! +TSI_FRAME_FORMAT_PPM = 0x00000002 # 24 or 48 bits per pixel PPM file. Always lossless! +TSI_FRAME_FORMAT_BMP_LOSSLESS = 0x00000003 # 24 bits per pixel (true-color) BMP file, NO LOSSY CONVERSION ALLOWED! +# !CI_PARSE_ENABLE + + +# !CI_PARSE_DISABLE +TSI_ELF_RGB_080808 = TSI_IMF_PK444_RGB_W8_C8 +TSI_ELF_RGB_161616 = TSI_IMF_PK444_RGB_W16_C16 +TSI_IMF_PK444_RGBA_W8_C8 = TSI_IMF_PK444_RGB0_W8_C8 +TSI_ELF_RGBA_080808A = TSI_IMF_PK444_RGBA_W8_C8 +TSI_IMF_PK444_RGBA_W16_C16 = TSI_IMF_PK444_RGB0_W16_C16 +TSI_ELF_RGBA_161616A = TSI_IMF_PK444_RGBA_W16_C16 +TSI_ELF_RGB_121212 = TSI_IMF_PK444_RGB_W16_C12 +TSI_ELF_YCbCr_080808 = TSI_IMF_PK444_YCbCr_W8_C8 +TSI_ELF_YCbCr_161616 = TSI_IMF_PK444_YCbCr_W16_C16 +TSI_ELF_YCbYCr_08 = TSI_IMF_PK422_CbYCrY_W16_C8 +TSI_ELF_YCbYCr_16 = TSI_IMF_PK422_CbYCrY_W16_C16 +TSI_ELF_I420_08 = TSI_IMF_PL420_YCbCr_W8_C8 +TSI_ELF_I420_10 = TSI_IMF_PL420_YCbCr_W16_C10 +TSI_ELF_I420_12 = TSI_IMF_PL420_YCbCr_W16_C12 +TSI_ELF_I420_16 = TSI_IMF_PL420_YCbCr_W16_C16 +TSI_IMF_PK444_RGBA_W8_C8 = TSI_IMF_PK444_RGB0_W8_C8 +# !CI_PARSE_ENABLE + + +# !CI_PARSE_DISABLE +TSI_CI_RO = 0x01 +TSI_CI_WO = 0x02 +TSI_CI_RW = (TSI_CI_RO | TSI_CI_WO) +TSI_CI_CRO = 0x04 # A CRO (Clear On Read) should also have CI_RO set. +TSI_CI_STROBE = 0x08 # A strobe should also have CI_WO set. +TSI_CI_NOLOAD = 0x10 +TSI_CI_NOSAVE = 0x20 + +TSI_DEVCAP_VIDEO_CAPTURE = 0x00000001 # Can capture and preview video +TSI_DEVCAP_AUDIO_CAPTURE = 0x00000002 # Can capture and preview audio +TSI_DEVCAP_CEC_IO = 0x00000004 # Can capture and send CEC messages +TSI_DEVCAP_CTS = 0x00000008 # Supports interface specific CTS +TSI_DEVCAP_SOURCE = 0x00000010 # Can generate output signal. +TSI_DEVCAP_SINK = 0x00000020 # General for all sinks. +TSI_DEVCAP_ALL_CAPS = 0x0000003f # Sum of all caps above. + +TSI_SEARCHOPTIONS_SHOW_DEVICES_IN_USE = 0x00000001 + +TSI_NAME_SIZE = 128 +TSI_SN_SIZE = 16 + +TSI_HANDLE = c_void_p +TSI_PARSER = c_void_p + +TSI_VERSION_ID = c_uint +TSI_RESULT = c_int +TSI_DEVICE_ID = c_uint +TSI_INPUT_ID = c_uint +TSI_OUTPUT_ID = c_uint +TSI_CONFIG_ID = c_uint +TSI_TEST_ID = c_uint +TSI_DEVICE_CAPS = c_uint +TSI_SEARCH_OPTIONS = c_uint +TSI_FRAME_FORMAT_ID = c_uint + + +class TSI_TEST_LOG_CONFIGURATION_STRUCT(Structure): + _fields_ = [ + ('datePrint ', c_uint), + ] + + +# !CI_PARSE_ENABLE + +TSI_VERSION_TEXT = 0x80000001 # Return version string identifying TSI version, used API versions and device info. !CI_PARSE_SIZE 16384 +TSI_LOG_FILE = 0x80000002 # TSI status log file path and name. Default = EMPTY, log file not used. +TSI_GLOBAL_LEGACY_DEVICE_HANDLE = 0x80000004 # "Classic TSI" device handle. CAUTION: Use with care! +TSI_DEVICE_FIRMWARE_INFO = 0x80000005 # Return FW version data for the give device, if available.. !CI_PARSE_SIZE 1024 +TSI_DEVICE_RELOAD_FW = 0x80000006 # STROBE, WRITE-ONLY. Re-loads FW. Supported on UCD-300 devices. Data written is ignored. +TSI_DEVICE_HW_RESET = 0x80000007 # STORBE, WRITE-ONLY. Soft-reset. Write device specific bit-mask. -1 to reset everything. Supported on UCD-300 devices. +TSI_DEVICE_MINIMUM_TEST_TIMEOUT = 0x80000008 # No test timeout will be shorter than this timeout. In milliseconds. +TSI_BUNDLE_VERSION_TEXT = 0x80000009 # Return version string identifying bundle version. !CI_PARSE_SIZE 32 +TSI_TEST_LOG_CONFIGURATION = 0x80000018 # Used for configuring test logging. Please refer to TSI_TEST_LOG_CONFIGURATION_STRUCT for details. +TSI_INVALID_CONFIG_ITEM = 0xffffffff + +# !CI_PARSE_DISABLE +TSI_FLAGS = c_int +TSI_FLAG_AUTOMATIC = 0x00000000 # Input: Capture formats are auto-detected. +TSI_FLAG_VIDEO = 0x00000001 # Status Video data from device. +TSI_FLAG_AUDIO = 0x00000002 # Status Audio data from device. +TSI_FLAG_CEC = 0x00000004 # Status CEC data from device. +TSI_FLAG_ERROR = 0x80000000 # Signal Error has occurred. +TSI_FLAG_QUEUED = 0x00000010 # Signal There are queued frames present on device. (NOTE: This information may not be available for all devices). +TSI_FLAG_CAPTURE_STOP = 0x00000020 # Signal Capture was stopped during last GetFrame call. +TSI_FLAG_CAPTURE_START = 0x00000040 # Signal Capture was (re)started during last CaptureState call. +TSI_FLAG_HDCP_PROTECTED = 0x00000080 # Signal HDCP protected content. +TSI_FW_VERSION_TEXT = TSI_DEVICE_FIRMWARE_INFO +TSI_PARAGRP_REFERENCE_1 = 0x7f000001 # Refers to ID's 0x10 - 0x17 +# !CI_PARSE_ENABLE + + +TSI_FRAME_BLANK_TOP = TSI_BASE_LEGACY_SCRIPT(0x0) +TSI_FRAME_BLANK_BOT = TSI_BASE_LEGACY_SCRIPT(0xF) +TSI_SCRI_DELAY_W = 0x70000001 + +TSI_W_SCRI_DELAY = TSI_SCRI_DELAY_W + +TSI_REF1_WIDTH = TSI_BASE_LEGACY_REFFRAME(0x0) +TSI_REF1_HEIGHT = TSI_BASE_LEGACY_REFFRAME(0x1) +TSI_REF1_ELEMENT_SIZE = TSI_BASE_LEGACY_REFFRAME(0x2) +TSI_REF1_ELEMENT_WIDTH = TSI_BASE_LEGACY_REFFRAME(0x3) +TSI_REF1_ELEMENT_HEIGHT = TSI_BASE_LEGACY_REFFRAME(0x4) +TSI_REF1_COLOR_DEPTH = TSI_BASE_LEGACY_REFFRAME(0x5) +TSI_REF1_ELEMENT_FORMAT = TSI_BASE_LEGACY_REFFRAME(0x6) +TSI_REF1_FRAME_DATA = TSI_BASE_LEGACY_REFFRAME(0x7) +TSI_REF1_LSB_MSB = TSI_BASE_LEGACY_REFFRAME(0x8) + +TSI_REF1_PIXELS_PER_ELEMENT = TSI_REF1_ELEMENT_WIDTH +TSI_REF1_LINES_PER_ELEMENT = TSI_REF1_ELEMENT_HEIGHT +TSI_REF1_PIXEL_FORMAT = TSI_REF1_ELEMENT_FORMAT + +TSI_UNITS_PRESENT_R = TSI_BASE_LEGACY_GENERIC(0x1) # Number of units present on current device chain. +TSI_CABLE_STATUS = TSI_BASE_LEGACY_GENERIC(0x3) +TSI_SRC_TYPE = TSI_BASE_LEGACY_GENERIC(0x4) +TSI_TS_OF_MODE = TSI_BASE_LEGACY_GENERIC(0x5) # Operator feedback system mode reservation (Currently not implemented) +TSI_TS_OF_REQ_ID = TSI_BASE_LEGACY_GENERIC( + 0x6) # Operator feedback index for get/set app. setting (signed integer index) +TSI_TS_OF_EXT_APP = TSI_BASE_LEGACY_GENERIC(0x7) # Operator feedback app setting. +TSI_TS_OF_CALLBACK = TSI_BASE_LEGACY_GENERIC( + 0x8) # Operator feedback function pointer setting: int(__stdcall *pCB)(int ArgCount, char *Args[]) +TSI_TS_OF_CALLBACK_PARAM = TSI_BASE_LEGACY_GENERIC(0xa) +TSI_TS_EXTENDED_RESULT_CODE_R = TSI_BASE_LEGACY_GENERIC(0xe) # Extended test result (Device specific error code) +TSI_TS_EXTENDED_RESULTS_R = TSI_BASE_LEGACY_GENERIC(0xf) # Extended test result (Text string) + +TSI_R_GENERIC_STATUS = TSI_BASE_LEGACY_GENERIC(0x0) +TSI_R_UNITS_PRESENT = TSI_UNITS_PRESENT_R +TSI_R_TS_EXTENDED_RESULT_CODE = TSI_TS_EXTENDED_RESULT_CODE_R +TSI_R_TS_EXTENDED_RESULTS = TSI_TS_EXTENDED_RESULTS_R + +TSI_DID_TE_INPUT = TSI_BASE_DID(0x0) # DID from currently Selected TE Input. !CI_PARSE_SIZE 512 +TSI_DID_TE_OUTPUT = TSI_BASE_DID(0x1) # DID from DUT connected to currently selected TE Output. !CI_PARSE_SIZE 512 +TSI_DID_SELECT_STREAM = TSI_BASE_DID(0x2) # DID select stream for writting/reading TE Input/Output.S +TSI_DID_TE_OUTPUT_REMOTE_R = TSI_BASE_DID(0x3) # Read remote DID's + +TSI_EDID_TE_INPUT = TSI_BASE_LEGACY_EDID(0x0) # EDID from currently Selected TE Input. !CI_PARSE_SIZE 512 +TSI_EDID_TE_OUTPUT = TSI_BASE_LEGACY_EDID( + 0x1) # EDID from DUT connected to currently selected TE Output. !CI_PARSE_SIZE 512 +TSI_EDID_SELECT_STREAM = TSI_BASE_LEGACY_EDID(0x2) # EDID select stream for writting/reading TE Input/Output.S +TSI_EDID_TE_OUTPUT_REMOTE_R = TSI_BASE_LEGACY_EDID(0x3) # Read remote EDID's + +TSI_EDID_CACHE_UPDATE = TSI_BASE_LEGACY_EDID(0x4) +TSI_EDID_DUT_TIMINGS_STREAM = TSI_BASE_LEGACY_EDID(0x5) +TSI_EDID_DUT_TIMINGS_COUNT = TSI_BASE_LEGACY_EDID(0x6) # Read EDID's cached on TX +TSI_EDID_DUT_TIMINGS_DATA = TSI_BASE_LEGACY_EDID(0x7) + +TSI_CURRENT_SINK_EDID = TSI_EDID_TE_INPUT + +TSI_FORCE_HOT_PLUG_STATE_W = TSI_BASE_LEGACY_GENERIC( + 0x2) # HPD state setting: 0 = Set low, 1 = Set high, Other values = INVALID. +TSI_HDCP_CAPABILITY = TSI_BASE_LEGACY_HPDARC( + 0x0) # Input HDCP capability state. -1 = Require NO HDCP, 0 = Don't care (Default), 1 = Require HDCP 1.x +TSI_HPD_LENGTH = TSI_BASE_LEGACY_HPDARC( + 0x1) # HPD pulse length, in milliseconds. Default is 1000. Use 0 to disable HPD pulses. +TSI_ARC_CONTROL_W = TSI_BASE_LEGACY_HPDARC( + 0x10) # Write only, ARC control. 0 = Disable (default), 1 = Generate audio, 2 = Loopback + +TSI_W_FORCE_HOT_PLUG_STATE = TSI_FORCE_HOT_PLUG_STATE_W +TSI_W_ARC_CONTROL = TSI_ARC_CONTROL_W + +# !CI_PARSE_DISABLE +H1_SINK_LOAD_TEST_KEYS = 0x001 +H1_SINK_LOAD_PROD_KEYS = 0x002 +H1_SINK_UNLOAD_KEYS = 0x003 +H1_SINK_SET_CAPABLE = 0x004 +H1_SINK_CLEAR_CAPABLE = 0x005 + +H1_SOURCE_LOAD_TEST_KEYS = 0x101 +H1_SOURCE_LOAD_PROD_KEYS = 0x102 +H1_SOURCE_UNLOAD_KEYS = 0x103 +H1_SOURCE_AUTHENTICATE = 0x104 +H1_SOURCE_DE_AUTHENTICATE = 0x105 +H1_SOURCE_ENABLE_ENCRYPT = 0x106 +H1_SOURCE_DISABLE_ENCRYPT = 0x107 +# !CI_PARSE_ENABLE + +TSI_HDCP_1X_STATUS_R = TSI_BASE_LEGACY_HDCP1(0x0) +TSI_HDCP_1X_COMMAND_W = TSI_BASE_LEGACY_HDCP1(0x1) + +# !CI_PARSE_DISABLE +H1_LOAD_TEST_KEYS = H1_SINK_LOAD_TEST_KEYS +H1_LOAD_PROD_KEYS = H1_SINK_LOAD_PROD_KEYS +H1_UNLOAD_KEYS = H1_SINK_UNLOAD_KEYS +H1_SET_CAPABLE = H1_SINK_SET_CAPABLE +H1_CLEAR_CAPABLE = H1_SINK_CLEAR_CAPABLE +# !CI_PARSE_ENABLE +TSI_R_HDCP_1X_STATUS = TSI_HDCP_1X_STATUS_R +TSI_W_HDCP_1X_COMMAND = TSI_HDCP_1X_COMMAND_W + +# !CI_PARSE_DISABLE +H2_SINK_LOAD_TEST_KEYS_R1 = 0x000 +H2_SINK_LOAD_TEST_KEYS_R2 = 0x001 +H2_SINK_LOAD_PROD_KEYS = 0x002 +H2_SINK_UNLOAD_KEYS = 0x003 +H2_SINK_SET_CAPABLE = 0x004 +H2_SINK_CLEAR_CAPABLE = 0x005 + +H2_SOURCE_LOAD_TEST_KEYS_R1 = 0x100 +H2_SOURCE_LOAD_TEST_KEYS_R2 = 0x101 +H2_SOURCE_LOAD_PROD_KEYS = 0x102 +H2_SOURCE_UNLOAD_KEYS = 0x103 +H2_SOURCE_AUTHENTICATE = 0x104 +H2_SOURCE_DE_AUTHENTICATE = 0x105 +H2_SOURCE_ENABLE_ENCRYPT = 0x106 +H2_SOURCE_DISABLE_ENCRYPT = 0x107 +H2_SOURCE_AUTHENTICATE_STORE_KM = 0x108 +H2_SOURCE_CLEAR_STORE_KM = 0x109 + +# !CI_PARSE_ENABLE + +TSI_HDCP_2X_STATUS_R = TSI_BASE_LEGACY_HDCP2(0x0) +TSI_HDCP_2X_COMMAND_W = TSI_BASE_LEGACY_HDCP2(0x1) + +# !CI_PARSE_DISABLE +H2_LOAD_PROD_KEYS = H2_SINK_LOAD_PROD_KEYS +H2_UNLOAD_KEYS = H2_SINK_UNLOAD_KEYS +H2_SET_CAPABLE = H2_SINK_SET_CAPABLE +H2_CLEAR_CAPABLE = H2_SINK_CLEAR_CAPABLE +# !CI_PARSE_ENABLE +TSI_R_HDCP_2X_STATUS = TSI_HDCP_2X_STATUS_R +TSI_W_HDCP_2X_COMMAND = TSI_HDCP_2X_COMMAND_W + +TSI_MEMORY_SIZE_R = TSI_BASE_LEGACY_MEMORY(0x0) +TSI_MEMORY_MAX_BLOCK_AMOUNT_R = TSI_BASE_LEGACY_MEMORY(0x1) +TSI_MEMORY_RESET_W = TSI_BASE_LEGACY_MEMORY(0x2) +TSI_MEMORY_LAYOUT = TSI_BASE_LEGACY_MEMORY(0x3) +TSI_MEMORY_BLOCK_INDEX = TSI_BASE_LEGACY_MEMORY(0x4) +TSI_MEMORY_WRITE_W = TSI_BASE_LEGACY_MEMORY(0x5) + +TSI_R_MEMORY_SIZE = TSI_MEMORY_SIZE_R +TSI_R_MEMORY_MAX_BLOCK_AMOUNT = TSI_MEMORY_MAX_BLOCK_AMOUNT_R +TSI_W_MEMORY_RESET = TSI_MEMORY_RESET_W +TSI_W_MEMORY_WRITE = TSI_MEMORY_WRITE_W + +TSI_PG_ENABLED_STREAM_COUNT = TSI_BASE_LEGACY_PATGEN(0x0) +TSI_PG_MAX_STREAM_COUNT_R = TSI_BASE_LEGACY_PATGEN(0x1) +TSI_PG_STREAM_SELECT = TSI_BASE_LEGACY_PATGEN(0x2) +TSI_PG_COMMAND_W = TSI_BASE_LEGACY_PATGEN(0x3) +TSI_PG_CUSTOM_TIMING_HTOTAL = TSI_BASE_LEGACY_PATGEN(0x4) +TSI_PG_CUSTOM_TIMING_HSTART = TSI_BASE_LEGACY_PATGEN(0x5) +TSI_PG_CUSTOM_TIMING_HACTIVE = TSI_BASE_LEGACY_PATGEN(0x6) +TSI_PG_CUSTOM_TIMING_HSYNCW = TSI_BASE_LEGACY_PATGEN(0x7) +TSI_PG_CUSTOM_TIMING_VTOTAL = TSI_BASE_LEGACY_PATGEN(0x8) +TSI_PG_CUSTOM_TIMING_VSTART = TSI_BASE_LEGACY_PATGEN(0x9) +TSI_PG_CUSTOM_TIMING_VACTIVE = TSI_BASE_LEGACY_PATGEN(0xa) +TSI_PG_CUSTOM_TIMING_VSYNCW = TSI_BASE_LEGACY_PATGEN(0xb) +TSI_PG_CUSTOM_TIMING_FLAGS = TSI_BASE_LEGACY_PATGEN(0xc) +TSI_PG_CUSTOM_TIMING_FIELD_RATE = TSI_BASE_LEGACY_PATGEN(0xd) +TSI_PG_PREDEF_TIMING_COUNT_R = TSI_BASE_LEGACY_PATGEN(0xe) +TSI_PG_PREDEF_TIMING_SELECT_W = TSI_BASE_LEGACY_PATGEN(0xf) + +TSI_PG_PREDEF_PATTERN_COUNT_R = TSI_BASE_LEGACY_PATGEN(0x10) +TSI_PG_PREDEF_PATTERN_SELECT = TSI_BASE_LEGACY_PATGEN(0x11) +TSI_PG_PREDEF_PATTERN_NAME_R = TSI_BASE_LEGACY_PATGEN(0x12) +TSI_PG_PREDEF_PATTERN_PARAMS = TSI_BASE_LEGACY_PATGEN(0x14) +TSI_PG_CUSTOM_PATTERN_WIDTH = TSI_BASE_LEGACY_PATGEN(0x15) +TSI_PG_CUSTOM_PATTERN_HEIGHT = TSI_BASE_LEGACY_PATGEN(0x16) +TSI_PG_CUSTOM_PATTERN_PIXEL_FORMAT = TSI_BASE_LEGACY_PATGEN(0x17) +TSI_PG_CUSTOM_PATTERN_DATA_W = TSI_BASE_LEGACY_PATGEN(0x18) +TSI_PG_CUSTOM_PATTERN_MEMORY_BLOCK_INDEX = TSI_BASE_LEGACY_PATGEN(0x19) + +TSI_PG_CUSTOM_PATTERN_CHAINED_DATA_W = TSI_BASE_LEGACY_PATGEN(0x20) +TSI_PG_CUSTOM_PATTERN_FRAME_TYPE = TSI_BASE_LEGACY_PATGEN(0x21) +TSI_PG_CUSTOM_TIMING_STANDARD = TSI_BASE_LEGACY_PATGEN(0x22) +TSI_PG_CUSTOM_TIMING_ID = TSI_BASE_LEGACY_PATGEN(0x23) +TSI_PG_STATUS_R = TSI_BASE_LEGACY_PATGEN(0x24) +TSI_PG_CUSTOM_TIMING_DSC_HCACTIVE = TSI_BASE_LEGACY_PATGEN(0x25) +TSI_PG_CUSTOM_TIMING_DSC_HCBLANK = TSI_BASE_LEGACY_PATGEN(0x26) +TSI_PG_CUSTOM_TIMING_DSC_FRAME_RATE = TSI_BASE_LEGACY_PATGEN(0x27) +TSI_PG_CUSTOM_TIMING_DSC_VTOTAL = TSI_BASE_LEGACY_PATGEN(0x28) +TSI_PG_CUSTOM_TIMING_DSC_VSTART = TSI_BASE_LEGACY_PATGEN(0x29) + +TSI_PG_CUSTOM_TIMING_DSC_VACTIVE = TSI_BASE_LEGACY_PATGEN(0x30) +TSI_PG_CUSTOM_TIMING_DSC_VSYNC = TSI_BASE_LEGACY_PATGEN(0x31) +TSI_PG_CUSTOM_TIMING_ASPECT_RATIO = TSI_BASE_LEGACY_PATGEN(0x32) +TSI_PG_CUSTOM_PATTERN_SCROLLING_CONTROL = TSI_BASE_TPG(0x0) +TSI_PG_CUSTOM_PATTERN_SCROLLING_CONFIG = TSI_BASE_TPG(0x1) + +TSI_PG_PREDEF_PATTERN_ID_R = TSI_BASE_LEGACY_PATGEN(0x13) +TSI_W_PG_PREDEF_PATTERN_SELECT = TSI_PG_PREDEF_PATTERN_SELECT +TSI_R_PG_MAX_STREAM_COUNT = TSI_PG_MAX_STREAM_COUNT_R +TSI_W_PG_COMMAND = TSI_PG_COMMAND_W +TSI_R_PG_PREDEF_TIMING_COUNT = TSI_PG_PREDEF_TIMING_COUNT_R +TSI_W_PG_PREDEF_TIMING_SELECT = TSI_PG_PREDEF_TIMING_SELECT_W +TSI_R_PG_PREDEF_PATTERN_COUNT = TSI_PG_PREDEF_PATTERN_COUNT_R +TSI_R_PG_PREDEF_PATTERN_NAME = TSI_PG_PREDEF_PATTERN_NAME_R +TSI_R_PG_PREDEF_PATTERN_ID = TSI_PG_PREDEF_PATTERN_ID_R +TSI_R_PG_STS_EX = TSI_PG_STATUS_R +TSI_PG_CUSTOM_PATTERN_INDEX = TSI_PG_CUSTOM_PATTERN_MEMORY_BLOCK_INDEX +TSI_PG_CUSTOM_PATTERN_DATA = TSI_PG_CUSTOM_PATTERN_DATA_W +TSI_PG_CUSTOM_PATTERN_CHAINED_DATA = TSI_PG_CUSTOM_PATTERN_CHAINED_DATA_W + +TSI_AUDGEN_CONTROL_W = TSI_BASE_LEGACY_AUDGEN(0x0) +TSI_AUDGEN_STATUS_R = TSI_BASE_LEGACY_AUDGEN(0x1) +TSI_AUDGEN_SIGNAL_TYPE = TSI_BASE_LEGACY_AUDGEN(0x2) +TSI_AUDGEN_SIGNAL_FREQ = TSI_BASE_LEGACY_AUDGEN(0x3) +TSI_AUDGEN_SIGNAL_BLOCK = TSI_BASE_LEGACY_AUDGEN(0x4) +TSI_AUDGEN_SIGNAL_VOLUME = TSI_BASE_LEGACY_AUDGEN(0x5) +TSI_AUDGEN_CHANNEL_COUNT = TSI_BASE_LEGACY_AUDGEN(0x6) +TSI_AUDGEN_SAMPLE_RATE = TSI_BASE_LEGACY_AUDGEN(0x7) +TSI_AUDGEN_SAMPLE_SIZE = TSI_BASE_LEGACY_AUDGEN(0x8) +TSI_AUDGEN_AUDIO_SIZE = TSI_BASE_LEGACY_AUDGEN(0x9) +TSI_AUDGEN_CONFIG = TSI_BASE_LEGACY_AUDGEN(0xA) +TSI_AUDGEN_PACKET_TYPE = TSI_BASE_LEGACY_AUDGEN(0xB) +TSI_AUDGEN_CHANNELS_STS = TSI_BASE_LEGACY_AUDGEN(0xC) + +TSI_W_AUDGEN_CONTROL = TSI_AUDGEN_CONTROL_W +TSI_R_AUDGEN_STATUS = TSI_AUDGEN_STATUS_R + +TSI_INPUT_WIDTH_R = TSI_BASE_LEGACY_INPUT(0x0) +TSI_INPUT_HEIGHT_R = TSI_BASE_LEGACY_INPUT(0x1) +TSI_INPUT_FREQ_R = TSI_BASE_LEGACY_INPUT(0x2) +TSI_INPUT_ELEMENT_SIZE_R = TSI_BASE_LEGACY_INPUT(0x3) +TSI_INPUT_ELEMENT_WIDTH_R = TSI_BASE_LEGACY_INPUT(0x4) +TSI_INPUT_ELEMENT_HEIGHT_R = TSI_BASE_LEGACY_INPUT(0x5) +TSI_INPUT_COLOR_DEPTH_R = TSI_BASE_LEGACY_INPUT(0x6) +TSI_INPUT_ELEMENT_FORMAT_R = TSI_BASE_LEGACY_INPUT(0x7) +TSI_INPUT_INTERLACE_R = TSI_BASE_LEGACY_INPUT(0x8) +TSI_INPUT_HDCP_PROTECTION_R = TSI_BASE_LEGACY_INPUT(0xa) +TSI_INPUT_COLOR_DEPTH_BPP_R = TSI_BASE_LEGACY_INPUT(0xb) +TSI_INPUT_COLORIMETRY_R = TSI_BASE_INPUT(0x0) +TSI_INPUT_COLOR_MODE_R = TSI_BASE_INPUT(0x1) + +TSI_R_INPUT_PIXELS_PER_ELEMENT = TSI_INPUT_ELEMENT_WIDTH_R +TSI_R_INPUT_LINES_PER_ELEMENT = TSI_INPUT_ELEMENT_HEIGHT_R +TSI_R_INPUT_PIXEL_FORMAT = TSI_INPUT_ELEMENT_FORMAT_R +TSI_R_INPUT_WIDTH = TSI_INPUT_WIDTH_R +TSI_R_INPUT_HEIGHT = TSI_INPUT_HEIGHT_R +TSI_R_INPUT_FREQ = TSI_INPUT_FREQ_R +TSI_R_INPUT_ELEMENT_SIZE = TSI_INPUT_ELEMENT_SIZE_R +TSI_R_INPUT_ELEMENT_WIDTH = TSI_INPUT_ELEMENT_WIDTH_R +TSI_R_INPUT_ELEMENT_HEIGHT = TSI_INPUT_ELEMENT_HEIGHT_R +TSI_R_INPUT_COLOR_DEPTH = TSI_INPUT_COLOR_DEPTH_R +TSI_R_INPUT_ELEMENT_FORMAT = TSI_INPUT_ELEMENT_FORMAT_R +TSI_R_INPUT_INTERLACE = TSI_INPUT_INTERLACE_R +TSI_R_INPUT_HDCP_PROTECTION = TSI_INPUT_HDCP_PROTECTION_R +TSI_R_INPUT_COLORIMETRY = TSI_INPUT_COLORIMETRY_R +TSI_R_INPUT_COLOR_MODE = TSI_INPUT_COLOR_MODE_R + +TSI_AUDIO_CHANNELS_R = TSI_BASE_LEGACY_AUDIO(0x0) +TSI_AUDIO_SAMPLE_RATE_R = TSI_BASE_LEGACY_AUDIO(0x1) +TSI_AUDIO_SAMPLE_SIZE_R = TSI_BASE_LEGACY_AUDIO(0x2) +TSI_CAPTURE_AUDIO_MASK = 0x238 + +TSI_R_AUDIO_CHANNELS = TSI_AUDIO_CHANNELS_R +TSI_R_AUDIO_SAMPLE_RATE = TSI_AUDIO_SAMPLE_RATE_R +TSI_R_AUDIO_SAMPLE_SIZE = TSI_AUDIO_SAMPLE_SIZE_R + +TSI_CAP_STATUS_R = TSI_BASE_LEGACY_CAP(0x0) +TSI_CAP_COMMAND_W = TSI_BASE_LEGACY_CAP(0x1) +TSI_CAP_CONFIG = TSI_BASE_LEGACY_CAP(0x2) + +TSI_R_CAP_STATUS = TSI_CAP_STATUS_R +TSI_W_CAP_COMMAND = TSI_CAP_COMMAND_W + +TSI_VIDCAP_WIDTH_R = TSI_BASE_LEGACY_VIDCAP(0x0) +TSI_VIDCAP_HEIGHT_R = TSI_BASE_LEGACY_VIDCAP(0x1) +TSI_VIDCAP_DATA_FORMAT_R = TSI_BASE_LEGACY_VIDCAP(0x3) +TSI_VIDCAP_FRAME_DATA_R = TSI_BASE_LEGACY_VIDCAP(0x4) +TSI_VIDCAP_MIN_BUFFER_SIZE_R = TSI_BASE_LEGACY_VIDCAP(0x5) +TSI_VIDCAP_TIMESTAMP_R = TSI_BASE_LEGACY_VIDCAP(0x6) +TSI_VIDCAP_COLOR_MODE_R = TSI_BASE_LEGACY_VIDCAP(0x7) +TSI_VIDCAP_COLORIMETRY_R = TSI_BASE_LEGACY_VIDCAP(0x8) +TSI_VIDCAP_FRAME_COUNTER_R = TSI_BASE_LEGACY_VIDCAP(0x9) +TSI_VIDCAP_FRAME_HEADER_R = TSI_BASE_LEGACY_VIDCAP(0xa) +TSI_VIDCAP_PROCESSED_FRAME_DATA_R = TSI_BASE_LEGACY_VIDCAP(0xb) +TSI_VIDCAP_PROCESSED_FRAME_SIZE_R = TSI_BASE_LEGACY_VIDCAP(0xc) +TSI_VIDCAP_FRAME_PROCESSING_METHOD = TSI_BASE_LEGACY_VIDCAP(0xd) +TSI_VIDCAP_CUSTOM_CONVERSION = TSI_BASE_LEGACY_VIDCAP(0xe) +TSI_VIDCAP_CUSTOM_CLAMPS = TSI_BASE_LEGACY_VIDCAP(0xf) +TSI_VIDCAP_CAPTURE_NEXT_W = TSI_BASE_LEGACY_VIDCAP(0x19) +TSI_VIDCAP_COLOR_DEPTH_BPC_R = TSI_BASE_LEGACY_VIDCAP(0x1a) +TSI_VIDCAP_IMF_FORMAT_R = TSI_BASE_LEGACY_VIDCAP(0x1b) +TSI_VIDCAP_COMPONENT_SIZE_R = TSI_BASE_LEGACY_VIDCAP(0x1c) +TSI_VIDCAP_SIGNAL_CRC_R = TSI_BASE_R_CRC_VIDEO(0x0) + +TSI_VIDCAP_SUBFRAME_COUNT_R = TSI_BASE_LEGACY_VIDCAP(0x10) +TSI_VIDCAP_SUBFRAME_INDEX = TSI_BASE_LEGACY_VIDCAP(0x11) +TSI_VIDCAP_SUBFRAME_OFFSET_R = TSI_BASE_LEGACY_VIDCAP(0x12) +TSI_VIDCAP_SUBFRAME_WIDTH_R = TSI_BASE_LEGACY_VIDCAP(0x13) +TSI_VIDCAP_SUBFRAME_HEIGHT_R = TSI_BASE_LEGACY_VIDCAP(0x14) +TSI_VIDCAP_SUBFRAME_SIZE_R = TSI_BASE_LEGACY_VIDCAP(0x15) +TSI_VIDCAP_SUBFRAME_DATA_R = TSI_BASE_LEGACY_VIDCAP(0x16) +TSI_VIDCAP_SUBFRAME_PROCESSED_SIZE_R = TSI_BASE_LEGACY_VIDCAP(0x17) +TSI_VIDCAP_SUBFRAME_PROCESSED_DATA_R = TSI_BASE_LEGACY_VIDCAP(0x18) + +TSI_R_VIDCAP_WIDTH = TSI_VIDCAP_WIDTH_R +TSI_R_VIDCAP_HEIGHT = TSI_VIDCAP_HEIGHT_R +TSI_R_VIDCAP_DATA_FORMAT = TSI_VIDCAP_DATA_FORMAT_R +TSI_R_VIDCAP_FRAME_DATA = TSI_VIDCAP_FRAME_DATA_R +TSI_R_VIDCAP_MIN_BUFFER_SIZE = TSI_VIDCAP_MIN_BUFFER_SIZE_R +TSI_R_VIDCAP_TIMESTAMP = TSI_VIDCAP_TIMESTAMP_R +TSI_R_VIDCAP_COLOR_MODE = TSI_VIDCAP_COLOR_MODE_R +TSI_R_VIDCAP_COLORIMETRY = TSI_VIDCAP_COLORIMETRY_R +TSI_R_VIDCAP_FRAME_COUNTER = TSI_VIDCAP_FRAME_COUNTER_R +TSI_R_VIDCAP_HEADER = TSI_VIDCAP_FRAME_HEADER_R +TSI_R_VIDCAP_PROCESSED_FRAME_DATA = TSI_VIDCAP_PROCESSED_FRAME_DATA_R +TSI_R_VIDCAP_PROCESSED_FRAME_SIZE = TSI_VIDCAP_PROCESSED_FRAME_SIZE_R +TSI_R_VIDCAP_CAPTURE_NEXT = TSI_VIDCAP_CAPTURE_NEXT_W +TSI_R_VIDCAP_BPC = TSI_VIDCAP_COLOR_DEPTH_BPC_R +TSI_R_CRC_CAPTURE = TSI_VIDCAP_SIGNAL_CRC_R + +TSI_AUDCAP_CHANNEL_COUNT_R = TSI_BASE_LEGACY_AUDCAP(0x0) +TSI_AUDCAP_SAMPLE_COUNT_R = TSI_BASE_LEGACY_AUDCAP(0x1) +TSI_AUDCAP_SAMPLE_SIZE_R = TSI_BASE_LEGACY_AUDCAP(0x3) +TSI_AUDCAP_SAMPLE_FORMAT_R = TSI_BASE_LEGACY_AUDCAP(0x4) +TSI_AUDCAP_SAMPLE_RATE_R = TSI_BASE_LEGACY_AUDCAP(0x6) +TSI_AUDCAP_SAMPLE_DATA_R = TSI_BASE_LEGACY_AUDCAP(0x7) +TSI_AUDCAP_TIMESTAMP_R = TSI_BASE_LEGACY_AUDCAP(0x8) +TSI_AUDCAP_FRAME_COUNTER_R = TSI_BASE_LEGACY_AUDCAP(0x9) +TSI_AUDCAP_CHANNEL_STATUS_R = TSI_BASE_LEGACY_AUDCAP(0xa) +TSI_AUDCAP_MIN_BUFFER_SIZE_R = TSI_BASE_LEGACY_AUDCAP(0xb) +TSI_AUDCAP_SAMPLE_SERVICE_INFO_R = TSI_BASE_LEGACY_AUDCAP(0xc) +TSI_AUDCAP_STATUS_R = TSI_BASE_LEGACY_AUDCAP(0xd) + +TSI_R_AUDCAP_CHANNEL_COUNT = TSI_AUDCAP_CHANNEL_COUNT_R +TSI_R_AUDCAP_SAMPLE_COUNT = TSI_AUDCAP_SAMPLE_COUNT_R +TSI_R_AUDCAP_SAMPLE_SIZE = TSI_AUDCAP_SAMPLE_SIZE_R +TSI_R_AUDCAP_SAMPLE_FORMAT = TSI_AUDCAP_SAMPLE_FORMAT_R +TSI_R_AUDCAP_SAMPLE_RATE = TSI_AUDCAP_SAMPLE_RATE_R +TSI_R_AUDCAP_SAMPLE_DATA = TSI_AUDCAP_SAMPLE_DATA_R +TSI_R_AUDCAP_TIMESTAMP = TSI_AUDCAP_TIMESTAMP_R +TSI_R_AUDCAP_FRAME_COUNTER = TSI_AUDCAP_FRAME_COUNTER_R +TSI_R_AUDCAP_CHANNEL_STS = TSI_AUDCAP_CHANNEL_STATUS_R +TSI_R_AUDCAP_MIN_BUFFER_SIZE = TSI_AUDCAP_MIN_BUFFER_SIZE_R +TSI_R_AUDCAP_SAMPLE_SERVICE_INFO = TSI_AUDCAP_SAMPLE_SERVICE_INFO_R +TSI_R_AUDCAP_STATUS = TSI_AUDCAP_STATUS_R + +# !CI_PARSE_DISABLE +# The following bits are used to set wanted event sources into TSI_EVCAP_EVENT_SRC_EN +UCD_ALL_EVENTS = 0x7f1371f3 +UCD_FW_EVENTS = 0x7f000000 +UCD_NO_EVENTS = 0x00000000 +UCD_DPOUT_HPD = 0x00000001 +UCD_DPOUT_AUX = 0x00000002 +UCD_DPIN_EV_HPD = 0x00000010 +UCD_DPIN_EV_AUX = 0x00000020 +UCD_DPIN_EV_SDP = 0x00000040 +UCD_DPIN_EV_MSA_CHANGE = 0x00000080 +UCD_DPIN_EV_VBID_CHANGE = 0x00000100 +UCD_HDIN_EV_HPD = 0x00001000 +UCD_HDIN_EV_HDMI = 0x00002000 +UCD_HDIN_EV_I2C = 0x00004000 +UCD_HDOUT_EV_HPD = 0x00010000 +UCD_HDOUT_EV_I2C = 0x00020000 +UCD_USBCOUT_EV_PD = 0x00100000 +UCD_FWLOG_CRIT = 0x01000000 +UCD_FWLOG_ERROR = 0x02000000 +UCD_FWLOG_WARNING = 0x04000000 +UCD_FWLOG_NOMINAL = 0x08000000 +UCD_FWLOG_INFO = 0x10000000 +UCD_FWLOG_DEBUG = 0x20000000 +UCD_FWLOG_VERBOSE = 0x40000000 +# !CI_PARSE_ENABLE + +TSI_EVENT_SIZE_R = TSI_BASE_LEGACY_EVCAP(0x0) +TSI_EVENT_DATA_R = TSI_BASE_LEGACY_EVCAP(0x1) +TSI_EVCAP_HDMI_PACKETS_EN = TSI_BASE_LEGACY_EVCAP(0x2) # !CI_PARSE_SIZE 32 +TSI_EVCAP_SDP_PACKETS_EN = TSI_BASE_LEGACY_EVCAP(0x3) # !CI_PARSE_SIZE 32 +TSI_EVCAP_EVENT_SRC_EN = TSI_BASE_LEGACY_EVCAP(0x4) +TSI_EVCAP_DATA_R = TSI_BASE_LEGACY_EVCAP(0x5) +TSI_EVCAP_COUNT_R = TSI_BASE_LEGACY_EVCAP(0x6) +TSI_EVCAP_CTRL = TSI_BASE_LEGACY_EVCAP(0x7) + +TSI_R_EVENT_SIZE = TSI_EVENT_SIZE_R +TSI_R_EVENT_DATA = TSI_EVENT_DATA_R +TSI_R_EVCAP_DATA = TSI_EVCAP_DATA_R +TSI_R_EVCAP_COUNT = TSI_EVCAP_COUNT_R + +TSI_HDMI_INFOFRAME_RANGE_START_R = TSI_BASE_R_HDMI_INFOFRAME(0x00) +TSI_HDMI_INFOFRAME_RANGE_END_R = TSI_BASE_R_HDMI_INFOFRAME(0xff) +TSI_HDMI_INFOFRAME_NULL_R = TSI_BASE_R_HDMI_INFOFRAME(0x00) +TSI_HDMI_INFOFRAME_ACR_R = TSI_BASE_R_HDMI_INFOFRAME(0x01) +TSI_HDMI_INFOFRAME_ASP_R = TSI_BASE_R_HDMI_INFOFRAME(0x02) +TSI_HDMI_INFOFRAME_GCP_R = TSI_BASE_R_HDMI_INFOFRAME(0x03) +TSI_HDMI_INFOFRAME_ACP_R = TSI_BASE_R_HDMI_INFOFRAME(0x04) +TSI_HDMI_INFOFRAME_ISRC1_R = TSI_BASE_R_HDMI_INFOFRAME(0x05) +TSI_HDMI_INFOFRAME_ISRC2_R = TSI_BASE_R_HDMI_INFOFRAME(0x06) +TSI_HDMI_INFOFRAME_OBA_R = TSI_BASE_R_HDMI_INFOFRAME(0x07) +TSI_HDMI_INFOFRAME_DTS_R = TSI_BASE_R_HDMI_INFOFRAME(0x08) +TSI_HDMI_INFOFRAME_HBR_R = TSI_BASE_R_HDMI_INFOFRAME(0x09) +TSI_HDMI_INFOFRAME_GMP_R = TSI_BASE_R_HDMI_INFOFRAME(0x0a) +TSI_HDMI_INFOFRAME_3D_ASP_R = TSI_BASE_R_HDMI_INFOFRAME(0x0b) +TSI_HDMI_INFOFRAME_3D_OBA_R = TSI_BASE_R_HDMI_INFOFRAME(0x0c) +TSI_HDMI_INFOFRAME_AMP_R = TSI_BASE_R_HDMI_INFOFRAME(0x0d) +TSI_HDMI_INFOFRAME_MST_ASP_R = TSI_BASE_R_HDMI_INFOFRAME(0x0e) +TSI_HDMI_INFOFRAME_MST_OBA_R = TSI_BASE_R_HDMI_INFOFRAME(0x0f) +TSI_HDMI_INFOFRAME_VSI_R = TSI_BASE_R_HDMI_INFOFRAME(0x81) +TSI_HDMI_INFOFRAME_AVI_R = TSI_BASE_R_HDMI_INFOFRAME(0x82) +TSI_HDMI_INFOFRAME_SPD_R = TSI_BASE_R_HDMI_INFOFRAME(0x83) +TSI_HDMI_INFOFRAME_AIF_R = TSI_BASE_R_HDMI_INFOFRAME(0x84) +TSI_HDMI_INFOFRAME_MPEG_R = TSI_BASE_R_HDMI_INFOFRAME(0x85) +TSI_HDMI_INFOFRAME_DRM_R = TSI_BASE_R_HDMI_INFOFRAME(0x87) +TSI_HDMI_INFOFRAME_UPDATE_FLAGS_R = TSI_BASE_R_HDMI_INFOFRAME(0x100) + +TSI_R_HDMI_INFOFRAME_RANGE_START = TSI_HDMI_INFOFRAME_RANGE_START_R +TSI_R_HDMI_INFOFRAME_RANGE_END = TSI_HDMI_INFOFRAME_RANGE_END_R +TSI_R_HDMI_INFOFRAME_NULL = TSI_HDMI_INFOFRAME_NULL_R +TSI_R_HDMI_INFOFRAME_ACR = TSI_HDMI_INFOFRAME_ACR_R +TSI_R_HDMI_INFOFRAME_ASP = TSI_HDMI_INFOFRAME_ASP_R +TSI_R_HDMI_INFOFRAME_GCP = TSI_HDMI_INFOFRAME_GCP_R +TSI_R_HDMI_INFOFRAME_ACP = TSI_HDMI_INFOFRAME_ACP_R +TSI_R_HDMI_INFOFRAME_ISRC1 = TSI_HDMI_INFOFRAME_ISRC1_R +TSI_R_HDMI_INFOFRAME_ISRC2 = TSI_HDMI_INFOFRAME_ISRC2_R +TSI_R_HDMI_INFOFRAME_OBA = TSI_HDMI_INFOFRAME_OBA_R +TSI_R_HDMI_INFOFRAME_DTS = TSI_HDMI_INFOFRAME_DTS_R +TSI_R_HDMI_INFOFRAME_HBR = TSI_HDMI_INFOFRAME_HBR_R +TSI_R_HDMI_INFOFRAME_GMP = TSI_HDMI_INFOFRAME_GMP_R +TSI_R_HDMI_INFOFRAME_3D_ASP = TSI_HDMI_INFOFRAME_3D_ASP_R +TSI_R_HDMI_INFOFRAME_3D_OBA = TSI_HDMI_INFOFRAME_3D_OBA_R +TSI_R_HDMI_INFOFRAME_AMP = TSI_HDMI_INFOFRAME_AMP_R +TSI_R_HDMI_INFOFRAME_MST_ASP = TSI_HDMI_INFOFRAME_MST_ASP_R +TSI_R_HDMI_INFOFRAME_MST_OBA = TSI_HDMI_INFOFRAME_MST_OBA_R +TSI_R_HDMI_INFOFRAME_VSI = TSI_HDMI_INFOFRAME_VSI_R +TSI_R_HDMI_INFOFRAME_AVI = TSI_HDMI_INFOFRAME_AVI_R +TSI_R_HDMI_INFOFRAME_SPD = TSI_HDMI_INFOFRAME_SPD_R +TSI_R_HDMI_INFOFRAME_AIF = TSI_HDMI_INFOFRAME_AIF_R +TSI_R_HDMI_INFOFRAME_MPEG = TSI_HDMI_INFOFRAME_MPEG_R +TSI_R_HDMI_INFOFRAME_DRM = TSI_HDMI_INFOFRAME_DRM_R +TSI_R_HDMI_INFOFRAME_UPDATE_FLAGS = TSI_HDMI_INFOFRAME_UPDATE_FLAGS_R + +TSI_R_DP_SDP_RANGE_START = TSI_BASE_R_DP_SDP(0x00) +TSI_R_DP_SDP_RANGE_END = TSI_BASE_R_DP_SDP(0xff) +TSI_R_DP_SDP_UPDATE_FLAGS = TSI_BASE_R_DP_SDP(0x100) +TSI_BASE_R_DP_SDP_SELECT_EX = TSI_BASE_R_DP_SDP(0x00) +TSI_BASE_R_DP_SDP_DATA_EX = TSI_BASE_R_DP_SDP(0x01) + +# !CI_PARSE_DISABLE +TSI_BULK_STATUS_IDLE = 0 +TSI_BULK_STATUS_WAITING = 1 +TSI_BULK_STATUS_CAPTURING = 2 +TSI_BULK_STATUS_TRANSFERRING = 3 + +TSI_BULK_CAPTURE_STOP = 0 +TSI_BULK_CAPTURE_START = 1 + +TSI_BULK_CAPTURE_TYPE_8BIT = 0 +TSI_BULK_CAPTURE_TYPE_10BIT = 1 + +TSI_BULK_CAPTURE_TRG_POS_BEGIN = 0 +TSI_BULK_CAPTURE_TRG_POS_25 = 1 +TSI_BULK_CAPTURE_TRG_POS_50 = 2 +TSI_BULK_CAPTURE_TRG_POS_75 = 3 +TSI_BULK_CAPTURE_TRG_POS_END = 4 + +TSI_BULK_CAPTURE_GPIO_OFF = 0 +TSI_BULK_CAPTURE_GPIO_5BIT = 1 +# !CI_PARSE_ENABLE + +TSI_BULK_CAPTURE_CONTROL_W = TSI_BASE_BULK_CAPTURE(0x0) +TSI_BULK_CAPTURE_STATUS_R = TSI_BASE_BULK_CAPTURE(0x1) +TSI_BULK_CAPTURE_MEMORY_BLOCK_INDEX = TSI_BASE_BULK_CAPTURE(0x2) +TSI_BULK_CAPTURE_DATA_R = TSI_BASE_BULK_CAPTURE(0x3) +TSI_BULK_CAPTURE_CLEAR_W = TSI_BASE_BULK_CAPTURE(0x4) +TSI_BULK_CAPTURE_TYPE = TSI_BASE_BULK_CAPTURE(0x5) +TSI_BULK_TRIGGER_CONFIGURATION_W = TSI_BASE_BULK_CAPTURE(0x6) +TSI_BULK_TRIGGER_MASK_W = TSI_BASE_BULK_CAPTURE(0x7) +TSI_BULK_CAPTURE_CAPS_R = TSI_BASE_BULK_CAPTURE(0x8) +TSI_BULK_TRIGGER_CAPS_R = TSI_BASE_BULK_CAPTURE(0x9) +TSI_BULK_TRIGGER_CONFIGURATION_EXT_W = TSI_BASE_BULK_CAPTURE(0xA) + +TSI_BULK_TRIGGER_POS = TSI_BASE_BULK_CAPTURE(0xB) +TSI_BULK_CAPTURE_GPIO_W = TSI_BASE_BULK_CAPTURE(0xC) +TSI_BULK_CAPTURE_LANE_COUNT = TSI_BASE_BULK_CAPTURE(0xD) + +TSI_BULK_CAPTURE_BLOCK = TSI_BULK_CAPTURE_MEMORY_BLOCK_INDEX +TSI_BULK_CAPTURE_TYPE_W = TSI_BULK_CAPTURE_TYPE + +# !CI_PARSE_DISABLE +TSI_DPRX_LOG_CTRL_VALUE_HPD = (1 << 0) +TSI_DPRX_LOG_CTRL_VALUE_SDP = (1 << 1) +TSI_DPRX_LOG_CTRL_VALUE_AUX = (1 << 2) +TSI_DPRX_LOG_CTRL_VALUE_MSA_CHANGE = (1 << 3) +TSI_DPRX_LOG_CTRL_VALUE_VBID_CHANGE = (1 << 4) +TSI_DPRX_LOG_CTRL_VALUE_LINK_PAT = (1 << 5) +TSI_DPRX_LOG_CTRL_VALUE_VBID_HW = (1 << 6) +TSI_DPRX_LOG_CTRL_VALUE_MSA_HW = (1 << 7) +TSI_DPRX_LOG_CTRL_VALUE_AUX_BW = (1 << 8) +TSI_DPRX_LOG_CTRL_VALUE_VFRAME_INFO = (1 << 9) +# !CI_PARSE_ENABLE + +TSI_DPRX_LINK_STATUS_FLAGS_R = TSI_BASE_LEGACY_DPRX(0x0) +TSI_DPRX_LT_STATUS_FLAGS_R = TSI_BASE_LEGACY_DPRX(0x1) +TSI_DPRX_LINK_VOLTAGE_SWING_R = TSI_BASE_LEGACY_DPRX(0x2) +TSI_DPRX_LINK_PRE_EMPHASIS_R = TSI_BASE_LEGACY_DPRX(0x3) +TSI_DPRX_LINK_LANE_COUNT_R = TSI_BASE_LEGACY_DPRX(0x4) +TSI_DPRX_LINK_RATE_R = TSI_BASE_LEGACY_DPRX(0x5) +TSI_DPRX_ERROR_COUNTS_R = TSI_BASE_LEGACY_DPRX(0x8) # !CI_PARSE_SIZE 16 +TSI_DPRX_DPCD_BASE_W = TSI_BASE_LEGACY_DPRX(0x9) +TSI_DPRX_DPCD_DATA = TSI_BASE_LEGACY_DPRX(0xA) +TSI_DPRX_DSC_CONTROL = TSI_BASE_LEGACY_DPRX(0xD) +TSI_DPRX_MSA_SIZE_R = TSI_BASE_LEGACY_DPRX(0xE) +TSI_DPRX_MSA_LOG_R = TSI_BASE_LEGACY_DPRX(0xF) +TSI_DPRX_MAX_LANES = TSI_BASE_LEGACY_DPRX(0x10) +TSI_DPRX_MAX_LINK_RATE = TSI_BASE_LEGACY_DPRX(0x11) +TSI_DPRX_LINK_FLAGS = TSI_BASE_LEGACY_DPRX(0x12) +TSI_DPRX_STREAM_SELECT = TSI_BASE_LEGACY_DPRX(0x13) +TSI_DPRX_CRC_R_R = TSI_BASE_LEGACY_DPRX(0x14) +TSI_DPRX_CRC_G_R = TSI_BASE_LEGACY_DPRX(0x15) +TSI_DPRX_CRC_B_R = TSI_BASE_LEGACY_DPRX(0x16) +TSI_DPRX_LT_ROUTE_CONTROL_W = TSI_BASE_LEGACY_DPRX(0x18) +TSI_DPRX_LT_ROUTE_STATUS_R = TSI_BASE_LEGACY_DPRX(0x19) +TSI_DPRX_LT_ROUTE_DATA_W = TSI_BASE_LEGACY_DPRX(0x1A) +TSI_DPRX_HPD_PULSE_W = TSI_BASE_LEGACY_DPRX(0x1B) +TSI_DPRX_EDP_LINK_RATES = TSI_BASE_LEGACY_DPRX(0x1C) +TSI_DPRX_DSC_STATUS_R = TSI_BASE_LEGACY_DPRX(0x1D) +TSI_DPRX_GET_ERROR_COUNTS_R = TSI_BASE_LEGACY_DPRX(0x1E) # !CI_PARSE_SIZE 16 +TSI_DPRX_CLEAR_ERROR_COUNTS_W = TSI_BASE_LEGACY_DPRX(0x1F) +TSI_DPRX_AUX_SWING_CAPS_R = TSI_BASE_LEGACY_DPRX(0x20) +TSI_DPRX_AUX_SWING_CONTROL = TSI_BASE_LEGACY_DPRX(0x21) +TSI_DP2RX_CUSTOM_RATE_CAPS_R = TSI_BASE_LEGACY_DPRX(0x22) +TSI_DP2RX_CUSTOM_RATE_MAP = TSI_BASE_LEGACY_DPRX(0x23) + +TSI_DPRX_LINK_CONTROL = TSI_BASE_DPRX(0x3) +TSI_DPRX_HW_CAPS_R = TSI_BASE_DPRX(0x4) +TSI_DPRX_LT_VOLTAGE_SWING_R = TSI_BASE_DPRX(0x9) +TSI_DPRX_LT_PRE_EMPHASIS_R = TSI_BASE_DPRX(0xA) +TSI_DPRX_LT_LANE_COUNT_R = TSI_BASE_DPRX(0xB) +TSI_DPRX_LT_RATE_R = TSI_BASE_DPRX(0xC) +TSI_DPRX_HPD_STATUS_R = TSI_BASE_DPRX(0x10) +TSI_DPRX_CRC_SIZE_R = TSI_BASE_DPRX(0x1C) +TSI_DPRX_CRC_LOG_R = TSI_BASE_DPRX(0x1D) +TSI_DPRX_HPD_FORCE = TSI_BASE_DPRX(0x12) +TSI_DPRX_HDCP_CAPS_R = TSI_BASE_DPRX(0x20) +TSI_DPRX_HDCP_STATUS_R = TSI_BASE_DPRX(0x21) +TSI_DPRX_LOG_CONTROL = TSI_BASE_DPRX(0x22) +TSI_DPRX_FEC_CONTROL = TSI_BASE_DPRX(0x23) +TSI_DPRX_FEC_STATUS_R = TSI_BASE_DPRX(0x24) +TSI_DPRX_LOG_IF_SEL = TSI_BASE_DPRX(0x27) +TSI_DPRX_LT_INFO_R = TSI_BASE_DPRX(0x30) +TSI_DPRX_LINK_LOG_SEL = TSI_BASE_DPRX(0x34) +TSI_DPRX_VBID_LOG_SEL = TSI_BASE_DPRX(0x35) +TSI_DPRX_MSA_LOG_SEL = TSI_BASE_DPRX(0x36) +TSI_DPRX_PHY_CONTROL = TSI_BASE_DPRX(0x3A) +TSI_DPRX_FEC_ERROR_COUNTERS_R = TSI_BASE_DPRX(0x44) +TSI_DPRX_DSC_CRC_R = TSI_BASE_DPRX(0x45) +TSI_DPRX_HDCP_STREAM_STS_R = TSI_BASE_DPRX(0x47) +TSI_DPRX_PSR_CAPS = TSI_BASE_DPRX(0x4A) +TSI_DPRX_PR_CAPS = TSI_BASE_DPRX(0x4B) +TSI_DPRX_PSR_STATUS = TSI_BASE_DPRX(0x4C) +TSI_DPRX_PR_STATUS = TSI_BASE_DPRX(0x4D) +TSI_DPRX_DSC_DPCD_CONTROL = TSI_BASE_DPRX(0x50) +TSI_DPRX_DSC_DPCD_PROPERTIES = TSI_BASE_DPRX(0x51) +TSI_DPRX_EDP_LT_RATE_R = TSI_BASE_DPRX(0x52) +TSI_DPRX_SSC_STATUS_R = TSI_BASE_DPRX(0x53) +TSI_DPRX_EDP_CAPS_RATES_R = TSI_BASE_DPRX(0x54) +TSI_DPRX_EDP_SEL_RATES = TSI_BASE_DPRX(0x55) +TSI_DPRX_FORCE_LINK_CONF_W = TSI_BASE_DPRX(0x5C) +TSI_DPRX_EDP_LINK_RATE_R = TSI_BASE_DPRX(0x5D) +TSI_DPRX_VC_TABLE_R = TSI_BASE_DPRX(0x64) +TSI_DPRX_CUSTOM_PATTERN = TSI_BASE_DPRX(0x65) +TSI_DPRX_IOP_ERROR_COUNTERS = TSI_BASE_DPRX(0x66) +TSI_DP2RX_LINK_RATE_CAPS = TSI_BASE_DPRX(0x88) +TSI_DP2RX_LT_FFE_PRESET_R = TSI_BASE_DPRX(0x89) +TSI_DP2RX_LINK_FFE_PRESET_R = TSI_BASE_DPRX(0x8A) +TSI_DPRX_SCR_SEED = TSI_BASE_DPRX(0x8f) +TSI_DPRX_DISPLAYID_CTRL = TSI_BASE_DPRX(0x9A) +TSI_DPRX_MST_SINK_COUNT = TSI_BASE_DPRX(0x9D) + +TSI_DPRX_MSA_COMMAND_W = TSI_BASE_LEGACY_DPRX_MSA(0x0) +TSI_DPRX_MSA_STREAM_COUNT_R = TSI_BASE_LEGACY_DPRX_MSA(0x1) +TSI_DPRX_MSA_DATA_R = TSI_BASE_LEGACY_DPRX_MSA(0x2) +TSI_DPRX_MSA_STREAM_SELECT = TSI_BASE_LEGACY_DPRX_MSA(0x3) +TSI_DPRX_MSA_N_VIDEO_R = TSI_BASE_LEGACY_DPRX_MSA(0x4) +TSI_DPRX_MSA_M_VIDEO_R = TSI_BASE_LEGACY_DPRX_MSA(0x5) +TSI_DPRX_MSA_HTOTAL_R = TSI_BASE_LEGACY_DPRX_MSA(0x6) +TSI_DPRX_MSA_VTOTAL_R = TSI_BASE_LEGACY_DPRX_MSA(0x7) +TSI_DPRX_MSA_HACTIVE_R = TSI_BASE_LEGACY_DPRX_MSA(0x8) +TSI_DPRX_MSA_VACTIVE_R = TSI_BASE_LEGACY_DPRX_MSA(0x9) +TSI_DPRX_MSA_HSYNC_WIDTH_R = TSI_BASE_LEGACY_DPRX_MSA(0xa) +TSI_DPRX_MSA_VSYNC_WIDTH_R = TSI_BASE_LEGACY_DPRX_MSA(0xb) +TSI_DPRX_MSA_HSTART_R = TSI_BASE_LEGACY_DPRX_MSA(0xc) +TSI_DPRX_MSA_VSTART_R = TSI_BASE_LEGACY_DPRX_MSA(0xd) +TSI_DPRX_MSA_MISC_R = TSI_BASE_LEGACY_DPRX_MSA(0xe) +TSI_DPRX_MSA_VBID_R = TSI_BASE_LEGACY_DPRX_MSA(0xf) +TSI_DPRX_MSA_PORT_NUMBER_R = TSI_BASE_LEGACY_DPRX_MSA(0x10) +TSI_DPRX_MSA_FRAME_RATE_R = TSI_BASE_LEGACY_DPRX_MSA(0x200 + 31) +TSI_DPRX_MSA_COLOR_DEPTH_BPC_R = TSI_BASE_LEGACY_DPRX_MSA(0x200 + 32) +TSI_DPRX_MSA_COLOR_MODE_R = TSI_BASE_LEGACY_DPRX_MSA(0x200 + 33) +TSI_DPRX_MSA_COLORIMETRY_R = TSI_BASE_LEGACY_DPRX_MSA(0x200 + 34) +TSI_DPRX_MSA_COLOR_DEPTH_BPP_R = TSI_BASE_LEGACY_DPRX_MSA(0x200 + 35) + +TSI_DP2RX_LT_FFE_PRESET = TSI_DP2RX_LT_FFE_PRESET_R +TSI_DPRX_LINK_VS_R = TSI_DPRX_LINK_VOLTAGE_SWING_R +TSI_DPRX_LINK_PE_R = TSI_DPRX_LINK_PRE_EMPHASIS_R +TSI_DPRX_LINK_LC_R = TSI_DPRX_LINK_LANE_COUNT_R +TSI_DPRX_LINK_BR_R = TSI_DPRX_LINK_RATE_R +TSI_DPRX_EDP_LINK_BR_R = TSI_DPRX_EDP_LINK_RATE_R +TSI_DPRX_EDP_LT_BR_R = TSI_DPRX_EDP_LT_RATE_R +TSI_DPRX_LT_VS_R = TSI_DPRX_LT_VOLTAGE_SWING_R +TSI_DPRX_LT_PE_R = TSI_DPRX_LT_PRE_EMPHASIS_R +TSI_DPRX_LT_LC_R = TSI_DPRX_LT_LANE_COUNT_R +TSI_DPRX_LT_BR_R = TSI_DPRX_LT_RATE_R +TSI_DPRX_LINK_CONTROL_R = TSI_DPRX_LINK_CONTROL +TSI_DPRX_MSA_SIZE = TSI_DPRX_MSA_SIZE_R +TSI_DPRX_MSA_LOG = TSI_DPRX_MSA_LOG_R +TSI_DPRX_LT_ROUTE_CTRL_W = TSI_DPRX_LT_ROUTE_CONTROL_W +TSI_DPRX_FEC_CTRL = TSI_DPRX_FEC_CONTROL +TSI_DPRX_DSC_CTRL = TSI_DPRX_DSC_CONTROL +TSI_DPRX_LINK_CTRL_R = TSI_DPRX_LINK_CONTROL_R +TSI_R_DPRX_HW_CAPS = TSI_DPRX_HW_CAPS_R +TSI_DPRX_HW_CAPS = TSI_DPRX_HW_CAPS_R +TSI_R_DPRX_LINK_STATUS_FLAGS = TSI_DPRX_LINK_STATUS_FLAGS_R +TSI_R_DPRX_LT_STATUS_FLAGS = TSI_DPRX_LT_STATUS_FLAGS_R +TSI_R_DPRX_LINK_VOLTAGE_SWING = TSI_DPRX_LINK_VOLTAGE_SWING_R +TSI_R_DPRX_LINK_PRE_EMPHASIS = TSI_DPRX_LINK_PRE_EMPHASIS_R +TSI_R_DPRX_LINK_LANE_COUNT = TSI_DPRX_LINK_LANE_COUNT_R +TSI_R_DPRX_LINK_RATE = TSI_DPRX_LINK_RATE_R +TSI_R_DPRX_ERROR_COUNTS = TSI_DPRX_ERROR_COUNTS_R +TSI_W_DPRX_DPCD_BASE = TSI_DPRX_DPCD_BASE_W +TSI_R_DPRX_CRC_R = TSI_DPRX_CRC_R_R +TSI_R_DPRX_CRC_G = TSI_DPRX_CRC_G_R +TSI_R_DPRX_CRC_B = TSI_DPRX_CRC_B_R +TSI_DPRX_AUX_SWING_CTRL_RW = TSI_DPRX_AUX_SWING_CONTROL +TSI_DPRX_LOG_CTRL_RW = TSI_DPRX_LOG_CONTROL +TSI_DPRX_LOG_IF_SEL_RW = TSI_DPRX_LOG_IF_SEL +TSI_DPRX_LINK_LOG_SEL_RW = TSI_DPRX_LINK_LOG_SEL +TSI_DPRX_VBID_LOG_SEL_RW = TSI_DPRX_VBID_LOG_SEL +TSI_DPRX_MSA_LOG_SEL_RW = TSI_DPRX_MSA_LOG_SEL +TSI_DPRX_PHY_CONTROL_RW = TSI_DPRX_PHY_CONTROL +TSI_DPRX_CUSTOM_PATTERN_RW = TSI_DPRX_CUSTOM_PATTERN +TSI_DPRX_IOP_ERROR_COUNTERS_RW = TSI_DPRX_IOP_ERROR_COUNTERS +TSI_DP2RX_CUSTOM_RATE_MAP_RW = TSI_DP2RX_CUSTOM_RATE_MAP +TSI_DPRX_HPD_STATUS = TSI_DPRX_HPD_STATUS_R +TSI_W_DPRX_MSA_COMMAND = TSI_DPRX_MSA_COMMAND_W +TSI_R_DPRX_MSA_STREAM_COUNT = TSI_DPRX_MSA_STREAM_COUNT_R +TSI_R_DPRX_MSA_DATA = TSI_DPRX_MSA_DATA_R +TSI_R_DPRX_MSA_N_VIDEO = TSI_DPRX_MSA_N_VIDEO_R +TSI_R_DPRX_MSA_M_VIDEO = TSI_DPRX_MSA_M_VIDEO_R +TSI_R_DPRX_MSA_HTOTAL = TSI_DPRX_MSA_HTOTAL_R +TSI_R_DPRX_MSA_VTOTAL = TSI_DPRX_MSA_VTOTAL_R +TSI_R_DPRX_MSA_HACTIVE = TSI_DPRX_MSA_HACTIVE_R +TSI_R_DPRX_MSA_VACTIVE = TSI_DPRX_MSA_VACTIVE_R +TSI_R_DPRX_MSA_HSYNC_WIDTH = TSI_DPRX_MSA_HSYNC_WIDTH_R +TSI_R_DPRX_MSA_VSYNC_WIDTH = TSI_DPRX_MSA_VSYNC_WIDTH_R +TSI_R_DPRX_MSA_HSTART = TSI_DPRX_MSA_HSTART_R +TSI_R_DPRX_MSA_VSTART = TSI_DPRX_MSA_VSTART_R +TSI_R_DPRX_MSA_MISC = TSI_DPRX_MSA_MISC_R +TSI_R_DPRX_MSA_VBID = TSI_DPRX_MSA_VBID_R +TSI_R_DPRX_MSA_PORT_NUMBER = TSI_DPRX_MSA_PORT_NUMBER_R +TSI_R_DPRX_MSA_FRATE = TSI_DPRX_MSA_FRAME_RATE_R +TSI_R_DPRX_MSA_COLOR_DEPTH_BPC = TSI_DPRX_MSA_COLOR_DEPTH_BPC_R +TSI_R_DPRX_MSA_COLOR_MODE = TSI_DPRX_MSA_COLOR_MODE_R +TSI_R_DPRX_MSA_COLORIMETRY = TSI_DPRX_MSA_COLORIMETRY_R + +# !CI_PARSE_DISABLE +TSI_DPTX_LOG_CONTROL_VALUE_HPD = (1 << 0) +TSI_DPTX_LOG_CONTROL_VALUE_AUX = (1 << 2) +# !CI_PARSE_ENABLE + +TSI_DPTX_LINK_CFG_LANES = TSI_BASE_LEGACY_DPTX(0x0) +TSI_DPTX_LINK_CFG_BIT_RATE = TSI_BASE_LEGACY_DPTX(0x1) +TSI_DPTX_LINK_CFG_FLAGS = TSI_BASE_LEGACY_DPTX(0x2) +TSI_DPTX_OVERRIDE_VOLTAGE_SWING = TSI_BASE_LEGACY_DPTX(0x3) +TSI_DPTX_OVERRIDE_PRE_EMPHASIS = TSI_BASE_LEGACY_DPTX(0x4) +TSI_DPTX_LINK_PATTERN = TSI_BASE_LEGACY_DPTX(0x5) +TSI_DPTX_COMMAND_W = TSI_BASE_LEGACY_DPTX(0x6) +TSI_DPTX_HPD_STATUS_R = TSI_BASE_LEGACY_DPTX(0x7) +TSI_DPTX_LT_RESULT_R = TSI_BASE_LEGACY_DPTX(0x8) +TSI_DPTX_LINK_STATUS_BITS_R = TSI_BASE_LEGACY_DPTX(0x9) +TSI_DPTX_LINK_VOLTAGE_SWING_R = TSI_BASE_LEGACY_DPTX(0xa) +TSI_DPTX_LINK_LANE_COUNT_R = TSI_BASE_LEGACY_DPTX(0xb) +TSI_DPTX_LINK_RATE_R = TSI_BASE_LEGACY_DPTX(0xc) +TSI_DPTX_LINK_PRE_EMPHASIS_R = TSI_BASE_LEGACY_DPTX(0xd) +TSI_DPTX_CRC_R_R = TSI_BASE_LEGACY_DPTX(0x10) +TSI_DPTX_CRC_G_R = TSI_BASE_LEGACY_DPTX(0x11) +TSI_DPTX_CRC_B_R = TSI_BASE_LEGACY_DPTX(0x12) +TSI_DPTX_DPCD_BASE_W = TSI_BASE_LEGACY_DPTX(0x13) +TSI_DPTX_DPCD_DATA = TSI_BASE_LEGACY_DPTX(0x14) +TSI_DPTX_HW_CAPS_R = TSI_BASE_LEGACY_DPTX(0x15) +TSI_DPTX_AUX_SWING_CAPS_R = TSI_BASE_LEGACY_DPTX(0x16) +TSI_DPTX_AUX_SWING_CONTROL = TSI_BASE_LEGACY_DPTX(0x17) +TSI_DP2TX_CUSTOM_RATE_CAPS_R = TSI_BASE_LEGACY_DPTX(0x18) +TSI_DP2TX_CUSTOM_RATE_MAP = TSI_BASE_LEGACY_DPTX(0x19) +TSI_DPTX_HDCP_CAPS_R = TSI_BASE_LEGACY_DPTX(0x20) +TSI_DPTX_HDCP_STATUS_R = TSI_BASE_LEGACY_DPTX(0x21) + +TSI_DPTX_LT_FEATURES = TSI_BASE_DPTX(0x02) +TSI_DPTX_OUTPUT_PATTERN = TSI_BASE_DPTX(0x04) +TSI_DPTX_LT_STATUS_R = TSI_BASE_DPTX(0x08) +TSI_DPTX_LINK_STATUS_R = TSI_BASE_DPTX(0x0F) +TSI_DPTX_MST_STATUS_R = TSI_BASE_DPTX(0x1B) +TSI_DPTX_VCP_TABLE_R = TSI_BASE_DPTX(0x1D) +TSI_DPTX_LOG_CONTROL = TSI_BASE_DPTX(0x22) +TSI_DPTX_FEC_CONTROL = TSI_BASE_DPTX(0x26) +TSI_DPTX_FEC_STATUS_R = TSI_BASE_DPTX(0x27) +TSI_DPTX_DSC_STATUS_R = TSI_BASE_DPTX(0x2A) +TSI_DPTX_LTTPR_CONTROL = TSI_BASE_DPTX(0x30) +TSI_DPTX_LTTPR_STATUS_R = TSI_BASE_DPTX(0x31) +TSI_DPTX_CRC_R = TSI_BASE_DPTX(0x38) +TSI_DPTX_DSC_CRC_R = TSI_BASE_DPTX(0x39) +TSI_DPTX_LINK_MODE_R = TSI_BASE_DPTX(0x3A) +TSI_DPTX_DOWNSPREAD_CONTROL = TSI_BASE_DPTX(0x41) +TSI_DPTX_EDP_LT_SBR = TSI_BASE_DPTX(0x42) +TSI_DPTX_DOWNSPREAD_AMP = TSI_BASE_DPTX(0x43) +TSI_DPTX_DOWNSPREAD_FREQ = TSI_BASE_DPTX(0x44) +TSI_DPTX_EDP_LT_RATE_R = TSI_BASE_DPTX(0x45) +TSI_DPTX_DOWNSPREAD_STATUS_R = TSI_BASE_DPTX(0x46) +TSI_DPTX_SQUARE_PATTERN_NUMBER = TSI_BASE_DPTX(0x4f) +TSI_DPTX_GENERIC_CONTROL_W = TSI_BASE_DPTX(0x51) +TSI_DPTX_MSA_INFO_R = TSI_BASE_DPTX(0x58) +TSI_DPTX_SCR_SEED = TSI_BASE_DPTX(0x5f) +TSI_DPTX_MST_COMMAND_W = TSI_BASE_DPTX(0x70) +TSI_DP2TX_LT_FFE_PRESET_R = TSI_BASE_DPTX(0x3B) +TSI_DP2TX_LT_SLC = TSI_BASE_DPTX(0x3C) +TSI_DP2TX_LT_SBR = TSI_BASE_DPTX(0x3D) +TSI_DP2TX_LT_RATE_R = TSI_BASE_DPTX(0x3F) +TSI_DP2TX_OUT_FFE = TSI_BASE_DPTX(0x50) +TSI_DPTX_EXTRA_DP14_RATES = TSI_BASE_DPTX(0x69) +TSI_DPTX_DISPLAYID_CTRL = TSI_BASE_DPTX(0x78) + +TSI_SRC_DP_LINK_CFG_LANES = TSI_DPTX_LINK_CFG_LANES +TSI_SRC_DP_LINK_CFG_BIT_RATE = TSI_DPTX_LINK_CFG_BIT_RATE +TSI_SRC_DP_CFG_FLAGS = TSI_DPTX_LINK_CFG_FLAGS +TSI_SRC_DP_OVERRIDE_VOLTAGE_SWING = TSI_DPTX_OVERRIDE_VOLTAGE_SWING +TSI_SRC_DP_OVERRIDE_PRE_EMPHASIS = TSI_DPTX_OVERRIDE_PRE_EMPHASIS +TSI_SRC_DP_LINK_PATTERN = TSI_DPTX_LINK_PATTERN +TSI_W_SRC_DP_COMMAND = TSI_DPTX_COMMAND_W +TSI_R_SRC_DP_HPD_STATUS = TSI_DPTX_HPD_STATUS_R +TSI_R_SRC_DP_LT_RESULT = TSI_DPTX_LT_RESULT_R +TSI_R_SRC_DP_LINK_STATUS_BITS = TSI_DPTX_LINK_STATUS_BITS_R +TSI_R_SRC_DP_LINK_STATUS_VOLT_SWING = TSI_DPTX_LINK_VOLTAGE_SWING_R +TSI_R_SRC_DP_LINK_STATUS_LANE_COUNT = TSI_DPTX_LINK_LANE_COUNT_R +TSI_R_SRC_DP_LINK_STATUS_BIT_RATE = TSI_DPTX_LINK_RATE_R +TSI_R_SRC_DP_LINK_STATUS_PRE_EMP = TSI_DPTX_LINK_PRE_EMPHASIS_R +TSI_R_SRC_DP_CRC_R = TSI_DPTX_CRC_R_R +TSI_R_SRC_DP_CRC_G = TSI_DPTX_CRC_G_R +TSI_R_SRC_DP_CRC_B = TSI_DPTX_CRC_B_R +TSI_DP2TX_LT_BR_R = TSI_DP2TX_LT_RATE_R +TSI_DPTX_EDP_LT_BR_R = TSI_DPTX_EDP_LT_RATE_R +TSI_W_DPTX_COMMAND = TSI_DPTX_COMMAND_W +TSI_R_DPTX_HPD_STATUS = TSI_DPTX_HPD_STATUS_R +TSI_R_DPTX_LT_RESULT = TSI_DPTX_LT_RESULT_R +TSI_R_DPTX_LINK_STATUS_BITS = TSI_DPTX_LINK_STATUS_BITS_R +TSI_R_DPTX_LINK_STATUS_VOLT_SWING = TSI_DPTX_LINK_VOLTAGE_SWING_R +TSI_R_DPTX_LINK_STATUS_LANE_COUNT = TSI_DPTX_LINK_LANE_COUNT_R +TSI_R_DPTX_LINK_STATUS_BIT_RATE = TSI_DPTX_LINK_RATE_R +TSI_R_DPTX_LINK_STATUS_PRE_EMP = TSI_DPTX_LINK_PRE_EMPHASIS_R +TSI_R_DPTX_CRC_R = TSI_DPTX_CRC_R_R +TSI_R_DPTX_CRC_G = TSI_DPTX_CRC_G_R +TSI_R_DPTX_CRC_B = TSI_DPTX_CRC_B_R +TSI_W_DPTX_DPCD_BASE = TSI_DPTX_DPCD_BASE_W +TSI_R_DPTX_HW_CAPS = TSI_DPTX_HW_CAPS_R +TSI_DPTX_LOG_CTRL_RW = TSI_DPTX_LOG_CONTROL +TSI_DPTX_FEC_CTRL = TSI_DPTX_FEC_CONTROL +TSI_DPTX_FEC_STATUS = TSI_DPTX_FEC_STATUS_R +TSI_DPTX_DOWNSPREAD_CTRL_RW = TSI_DPTX_DOWNSPREAD_CONTROL +TSI_DPTX_DOWNSPREAD_AMP_RW = TSI_DPTX_DOWNSPREAD_AMP +TSI_DPTX_DOWNSPREAD_FREQ_RW = TSI_DPTX_DOWNSPREAD_FREQ +TSI_DPTX_AUX_SWING_CTRL_RW = TSI_DPTX_AUX_SWING_CONTROL +TSI_DP2TX_CUSTOM_RATE_MAP_RW = TSI_DP2TX_CUSTOM_RATE_MAP +TSI_DPTX_OUT_PATTERN = TSI_DPTX_OUTPUT_PATTERN +TSI_DPTX_SYS_CTRL_W = TSI_DPTX_GENERIC_CONTROL_W +TSI_DPTX_SQUARE_PAT_NUM = TSI_DPTX_SQUARE_PATTERN_NUMBER +TSI_DPTX_DP_LINK_PATTERN = TSI_DPTX_LINK_PATTERN +TSI_DPTX_CFG_FLAGS = TSI_DPTX_LINK_CFG_FLAGS + +# !CI_PARSE_DISABLE +TSI_HDRX_LOG_CTRL_VALUE_HPD = (1 << 0) +TSI_HDRX_LOG_CTRL_VALUE_INFO = (1 << 1) +TSI_HDRX_LOG_CTRL_VALUE_I2C = (1 << 2) +TSI_HDRX_LOG_CTRL_VALUE_CEC = (1 << 3) +TSI_HDRX_LOG_CTRL_VALUE_FRAME_INFO = (1 << 4) +# !CI_PARSE_ENABLE + +TSI_HDRX_LINK_STATUS_R = TSI_BASE_LEGACY_HDRX(0x0) +TSI_HDRX_ARC_STATUS_R = TSI_BASE_LEGACY_HDRX(0x2) +TSI_HDRX_CRC_R_R = TSI_BASE_LEGACY_HDRX(0x3) +TSI_HDRX_CRC_G_R = TSI_BASE_LEGACY_HDRX(0x4) +TSI_HDRX_CRC_B_R = TSI_BASE_LEGACY_HDRX(0x5) +TSI_HDRX_VIDEO_MODE_R = TSI_BASE_LEGACY_HDRX(0x7) +TSI_HDRX_EVENTS_R = TSI_BASE_HDRX(0x00) +TSI_HDRX_HPD_CONTROL_W = TSI_BASE_HDRX(0x02) +TSI_HDRX_HPD_STATUS_R = TSI_BASE_HDRX(0x02) + +# RM9124 BELOW +TSI_HDRX_ARC_SOURCE = TSI_BASE_HDRX(0x18) +TSI_HDRX_ARC_MODE = TSI_BASE_HDRX(0x19) +TSI_HDRX_ARC_CONTROL_W = TSI_BASE_HDRX(0x1A) +TSI_HDRX_ARC_SUPPORTED_R = TSI_BASE_HDRX(0x1B) + +TSI_HDRX_EARC_CTRL = TSI_BASE_HDRX(0x50) +TSI_HDRX_EARC_STS_R = TSI_BASE_HDRX(0x51) +TSI_HDRX_EARC_EDID_OFFSET = TSI_BASE_HDRX(0x52) +TSI_HDRX_EARC_EDID_DATA = TSI_BASE_HDRX(0x53) +TSI_HDRX_EARC_LATENCY = TSI_BASE_HDRX(0x54) +TSI_HDRX_EARC_LATENCY_REQ = TSI_BASE_HDRX(0x55) +# RM9124 ABOVE +TSI_HDRX_HDCP_CAPS_R = TSI_BASE_HDRX(0x10) +TSI_HDRX_HDCP_STATUS_R = TSI_BASE_HDRX(0x11) +TSI_HDRX_LOG_IF_SEL = TSI_BASE_HDRX(0x021) +TSI_HDRX_LOG_CONTROL = TSI_BASE_HDRX(0x023) +TSI_HDRX_FRL_CAPABILITY = TSI_BASE_HDRX(0x024) +TSI_HDRX_FRL_PATTERN = TSI_BASE_HDRX(0x025) +TSI_HDRX_FRL_FFE_R = TSI_BASE_HDRX(0x026) +TSI_HDRX_LANES_ERR_COUNTERS_R = TSI_BASE_HDRX(0x027) +TSI_HDRX_CAPABILITY_R = TSI_BASE_HDRX(0x029) +TSI_HDRX_BEHAVIOR = TSI_BASE_HDRX(0x02A) + +TSI_HDRX_CEC_CONTROL = TSI_BASE_HDRX(0x030) +TSI_HDRX_CEC_COMMAND = TSI_BASE_HDRX(0x031) +TSI_HDRX_CEC_VERSION_R = TSI_BASE_HDRX(0x032) +TSI_HDRX_CEC_LOGICAL_ADDRESS = TSI_BASE_HDRX(0x033) +TSI_HDRX_CEC_PHYSICAL_ADDRESS = TSI_BASE_HDRX(0x034) +TSI_HDRX_CEC_OP_CODE = TSI_BASE_HDRX(0x035) +TSI_HDRX_CEC_OP_CODE_PARAM = TSI_BASE_HDRX(0x036) +TSI_HDRX_CEC_DEVICE_TYPE = TSI_BASE_HDRX(0x037) +TSI_HDRX_CEC_RECEIVED_CNT_R = TSI_BASE_HDRX(0x038) +TSI_HDRX_CEC_STATUS_R = TSI_BASE_HDRX(0x039) +TSI_HDRX_CEC_DATA_RECEIVED_R = TSI_BASE_HDRX(0x03A) +TSI_HDRX_CEC_DATA_RESET_W = TSI_BASE_HDRX(0x03A) +TSI_HDRX_CEC_DATA_SIZE_R = TSI_BASE_HDRX(0x03B) +TSI_HDRX_CEC_DESTINATION = TSI_BASE_HDRX(0x03F) + +TSI_HDRX_TIM_COMMAND_W = TSI_BASE_LEGACY_HDRX_TIM(0x0) +TSI_HDRX_TIM_HTOTAL_R = TSI_BASE_LEGACY_HDRX_TIM(0x1) +TSI_HDRX_TIM_VTOTAL_R = TSI_BASE_LEGACY_HDRX_TIM(0x2) +TSI_HDRX_TIM_HACTIVE_R = TSI_BASE_LEGACY_HDRX_TIM(0x3) +TSI_HDRX_TIM_VACTIVE_R = TSI_BASE_LEGACY_HDRX_TIM(0x4) +TSI_HDRX_TIM_HSTART_R = TSI_BASE_LEGACY_HDRX_TIM(0x5) +TSI_HDRX_TIM_VSTART_R = TSI_BASE_LEGACY_HDRX_TIM(0x6) +TSI_HDRX_TIM_HSYNC_WIDTH_R = TSI_BASE_LEGACY_HDRX_TIM(0x7) +TSI_HDRX_TIM_VSYNC_WIDTH_R = TSI_BASE_LEGACY_HDRX_TIM(0x8) +TSI_HDRX_TIM_COLOR_DEPTH_R = TSI_BASE_LEGACY_HDRX_TIM(0x9) +TSI_HDRX_TIM_FRAME_RATE_R = TSI_BASE_LEGACY_HDRX_TIM(0xa) +TSI_HDRX_TIM_COLOR_MODE_R = TSI_BASE_LEGACY_HDRX_TIM(0xb) +TSI_HDRX_TIM_COLORIMETRY_R = TSI_BASE_LEGACY_HDRX_TIM(0xc) +TSI_HDRX_TIM_COLOR_DEPTH_BPC_R = TSI_BASE_LEGACY_HDRX_TIM(0xd) + +TSI_HDRX_PIX_CAP_CONTROL = TSI_BASE_HDRX(0x040) +TSI_HDRX_PIX_CAP_STATUS_R = TSI_BASE_HDRX(0x041) +TSI_HDRX_PIX_CAP_TIMESTAMP_0_R = TSI_BASE_HDRX(0x042) +TSI_HDRX_PIX_CAP_TIMESTAMP_1_R = TSI_BASE_HDRX(0x043) +TSI_HDRX_PIX_CAP_COORDINATES_W = TSI_BASE_HDRX(0x044) +TSI_HDRX_PIX_CAP_DATA_R = TSI_BASE_HDRX(0x045) + +TSI_R_SINK_HDMI_FPERIOD = TSI_BASE_LEGACY_HDRX(0x6) +TSI_R_SINK_HDMI_VIDEO_MODE = TSI_HDRX_VIDEO_MODE_R +TSI_R_HDRX_LINK_STATUS = TSI_HDRX_LINK_STATUS_R +TSI_HDRX_CEC_CTRL_RW = TSI_HDRX_CEC_CONTROL +TSI_HDRX_CEC_CMD_RW = TSI_HDRX_CEC_COMMAND +TSI_HDRX_CEC_LOGICAL_ADDRESS_RW = TSI_HDRX_CEC_LOGICAL_ADDRESS +TSI_HDRX_CEC_PHYSICAL_ADDRESS_RW = TSI_HDRX_CEC_PHYSICAL_ADDRESS +TSI_HDRX_CEC_OP_CODE_RW = TSI_HDRX_CEC_OP_CODE +TSI_HDRX_CEC_OP_CODE_PARAM_RW = TSI_HDRX_CEC_OP_CODE_PARAM +TSI_HDRX_CEC_DEVICE_TYPE_RW = TSI_HDRX_CEC_DEVICE_TYPE +TSI_HDRX_CEC_RECIVED_CNT = TSI_HDRX_CEC_RECEIVED_CNT_R +TSI_HDRX_CEC_OP_CODE_RECIEVED = TSI_HDRX_CEC_STATUS_R +TSI_HDRX_CEC_DATA_RECIEVED = TSI_HDRX_CEC_DATA_RECEIVED_R +TSI_W_HDRX_TIM_COMMAND = TSI_HDRX_TIM_COMMAND_W +TSI_R_HDRX_TIM_HTOTAL = TSI_HDRX_TIM_HTOTAL_R +TSI_R_HDRX_TIM_VTOTAL = TSI_HDRX_TIM_VTOTAL_R +TSI_R_HDRX_TIM_HACTIVE = TSI_HDRX_TIM_HACTIVE_R +TSI_R_HDRX_TIM_VACTIVE = TSI_HDRX_TIM_VACTIVE_R +TSI_R_HDRX_TIM_HSTART = TSI_HDRX_TIM_HSTART_R +TSI_R_HDRX_TIM_VSTART = TSI_HDRX_TIM_VSTART_R +TSI_R_HDRX_TIM_HSYNC_WIDTH = TSI_HDRX_TIM_HSYNC_WIDTH_R +TSI_R_HDRX_TIM_VSYNC_WIDTH = TSI_HDRX_TIM_VSYNC_WIDTH_R +TSI_R_HDRX_TIM_COLOR_DEPTH = TSI_HDRX_TIM_COLOR_DEPTH_R +TSI_R_HDRX_TIM_FRATE = TSI_HDRX_TIM_FRAME_RATE_R +TSI_R_HDRX_TIM_COLOR_MODE = TSI_HDRX_TIM_COLOR_MODE_R +TSI_R_HDRX_TIM_COLORIMETRY = TSI_HDRX_TIM_COLORIMETRY_R +TSI_HDRX_LOG_IF_SEL_RW = TSI_HDRX_LOG_IF_SEL +TSI_HDRX_LOG_CTRL_RW = TSI_HDRX_LOG_CONTROL +TSI_HDRX_FRL_CAPABILITY_RW = TSI_HDRX_FRL_CAPABILITY +TSI_HDRX_FRL_PATTERN_RW = TSI_HDRX_FRL_PATTERN +TSI_HDRX_HPD_CTRL_W = TSI_HDRX_HPD_CONTROL_W +TSI_HDRX_BEHAVIOR_RW = TSI_HDRX_BEHAVIOR +TSI_R_HDRX_CRC_R = TSI_HDRX_CRC_R_R +TSI_R_HDRX_CRC_G = TSI_HDRX_CRC_G_R +TSI_R_HDRX_CRC_B = TSI_HDRX_CRC_B_R +TSI_W_HDRX_LINK_CONTROL = TSI_HDRX_HPD_CONTROL_W +TSI_R_HDRX_ARC_STATUS = TSI_HDRX_ARC_STATUS_R +TSI_HDRX_PIX_CAP_CTRL_RW = TSI_HDRX_PIX_CAP_CONTROL +TSI_HDRX_PIX_CAP_STS_R = TSI_HDRX_PIX_CAP_STATUS_R + +# !CI_PARSE_DISABLE +TSI_HDTX_LOG_CTRL_VALUE_HPD = (1 << 0) +TSI_HDTX_LOG_CTRL_VALUE_I2C = (1 << 1) +TSI_HDTX_LOG_CTRL_VALUE_CEC = (1 << 2) +# !CI_PARSE_ENABLE + +TSI_HDTX_CONTROL_W = TSI_BASE_HDTX(0x00) +TSI_HDTX_STATUS_R = TSI_BASE_HDTX(0x00) +TSI_HDTX_SINK_CAPS_R = TSI_BASE_HDTX(0x01) +TSI_HDTX_SINK_FEATURE_W = TSI_BASE_HDTX(0x02) +TSI_HDTX_SINK_STATUS_R = TSI_BASE_HDTX(0x02) +TSI_HDTX_HPD_STATUS_R = TSI_BASE_HDTX(0x0d) +TSI_HDTX_EVENTS_R = TSI_BASE_HDTX(0x0e) +TSI_HDTX_SCDC_OFFSET = TSI_BASE_HDTX(0x12) +TSI_HDTX_SCDC_DATA = TSI_BASE_HDTX(0x13) +TSI_HDTX_HDCP_CAPS_R = TSI_BASE_HDTX(0x020) +TSI_HDTX_HDCP_STATUS_R = TSI_BASE_HDTX(0x021) +TSI_HDTX_LOG_CONTROL = TSI_BASE_HDTX(0x022) +TSI_HDTX_PKT_INFO_R = TSI_BASE_HDTX(0x023) +TSI_HDTX_FRL_CAPABILITY = TSI_BASE_HDTX(0x025) +TSI_HDTX_FRL_STATUS_R = TSI_BASE_HDTX(0x026) +TSI_HDTX_FRL_PATTERN_R = TSI_BASE_HDTX(0x027) +TSI_HDTX_FRL_TIMERS = TSI_BASE_HDTX(0x028) +TSI_HDTX_LANES_ERR_COUNTERS_R = TSI_BASE_HDTX(0x029) +TSI_HDTX_CAPABILITY_R = TSI_BASE_HDTX(0x02B) +TSI_HDTX_CEC_CONTROL = TSI_BASE_HDTX(0x031) +TSI_HDTX_CEC_COMMAND = TSI_BASE_HDTX(0x032) +TSI_HDTX_CEC_VERSION_R = TSI_BASE_HDTX(0x033) +TSI_HDTX_CEC_LOGICAL_ADDRESS = TSI_BASE_HDTX(0x034) +TSI_HDTX_CEC_PHYSICAL_ADDRESS = TSI_BASE_HDTX(0x035) +TSI_HDTX_CEC_OP_CODE = TSI_BASE_HDTX(0x036) +TSI_HDTX_CEC_OP_CODE_PARAM = TSI_BASE_HDTX(0x037) +TSI_HDTX_CEC_DEVICE_TYPE = TSI_BASE_HDTX(0x038) +TSI_HDTX_CEC_RECEIVED_CNT_R = TSI_BASE_HDTX(0x039) +TSI_HDTX_CEC_STATUS_R = TSI_BASE_HDTX(0x03A) +TSI_HDTX_CEC_DATA_RECEIVED_R = TSI_BASE_HDTX(0x03B) +TSI_HDTX_CEC_DATA_RESET_W = TSI_BASE_HDTX(0x03B) +TSI_HDTX_CEC_DATA_SIZE_R = TSI_BASE_HDTX(0x03C) +TSI_HDTX_CEC_DESTINATION = TSI_BASE_HDTX(0x03F) +TSI_HDTX_LEGACY_CONTROL_W = TSI_BASE_LEGACY_HDTX(0x0) +TSI_HDTX_LEGACY_STATUS_R = TSI_BASE_LEGACY_HDTX(0x1) + +TSI_SRC_HDMI_SCDC_ADDRESS = TSI_HDTX_SCDC_OFFSET +TSI_SRC_HDMI_SCDC_DATA = TSI_HDTX_SCDC_DATA +TSI_W_SRC_HDMI_CONTROL = TSI_HDTX_LEGACY_CONTROL_W +TSI_R_SRC_HDMI_STATUS = TSI_HDTX_LEGACY_STATUS_R +TSI_R_SRC_HDMI_DUT_CAPS = TSI_HDTX_SINK_CAPS_R +TSI_HDTX_SCDC_OFFSET_RW = TSI_HDTX_SCDC_OFFSET +TSI_HDTX_SCDC_DATA_RW = TSI_HDTX_SCDC_DATA +TSI_HDTX_LOG_CTRL_RW = TSI_HDTX_LOG_CONTROL +TSI_HDTX_FRL_CAPABILITY_RW = TSI_HDTX_FRL_CAPABILITY +TSI_HDTX_FRL_TIMERS_RW = TSI_HDTX_FRL_TIMERS +TSI_HDTX_CEC_CTRL_RW = TSI_HDTX_CEC_CONTROL +TSI_HDTX_CEC_CMD_RW = TSI_HDTX_CEC_COMMAND +TSI_HDTX_CEC_LOGICAL_ADDRESS_RW = TSI_HDTX_CEC_LOGICAL_ADDRESS +TSI_HDTX_CEC_PHYSICAL_ADDRESS_RW = TSI_HDTX_CEC_PHYSICAL_ADDRESS +TSI_HDTX_CEC_OP_CODE_RW = TSI_HDTX_CEC_OP_CODE +TSI_HDTX_CEC_OP_CODE_PARAM_RW = TSI_HDTX_CEC_OP_CODE_PARAM +TSI_HDTX_CEC_DEVICE_TYPE_RW = TSI_HDTX_CEC_DEVICE_TYPE + +TSI_USBC_CABLE_CONTROL_W = TSI_BASE_LEGACY_USBC(0x0) +TSI_USBC_INITIAL_ROLE_W = TSI_BASE_LEGACY_USBC(0x1) +TSI_USBC_DP_ALT_MODE_SETUP = TSI_BASE_LEGACY_USBC(0x2) +TSI_USBC_ROLE_CONTROL_W = TSI_BASE_LEGACY_USBC(0x3) +TSI_USBC_DP_ALT_MODE_COMMAND_W = TSI_BASE_LEGACY_USBC(0x4) +TSI_USBC_TE_HW_CONFIGURATION_R = TSI_BASE_LEGACY_USBC(0x5) +TSI_USBC_CABLE_STATUS_R = TSI_BASE_LEGACY_USBC(0x6) +TSI_USBC_ROLE_STATUS_R = TSI_BASE_LEGACY_USBC(0x7) +TSI_USBC_DP_ALT_MODE_STATUS_R = TSI_BASE_LEGACY_USBC(0x8) +TSI_USBC_POWER_STATUS_R = TSI_BASE_LEGACY_USBC(0x9) +TSI_USBC_POWER_SOURCE_PDO_R = TSI_BASE_LEGACY_USBC(0xa) +TSI_USBC_POWER_SINK_RDO_R = TSI_BASE_LEGACY_USBC(0xb) +TSI_USBC_IDO_TABLE_R = TSI_BASE_LEGACY_USBC(0xc) # !CI_PARSE_SIZE 24 +TSI_USBC_EPU_LOAD_CONTROL = TSI_BASE_LEGACY_USBC(0xd) + +TSI_USBC_PWR_CONTRACT_CONTROL = TSI_BASE_LEGACY_USBC(0xe) +TSI_USBC_PWR_CONTRACT_SELECT = TSI_BASE_LEGACY_USBC(0xf) +TSI_USBC_PWR_LOCAL_SINK_PDO = TSI_BASE_LEGACY_USBC(0x10) # !CI_PARSE_SIZE 28 +TSI_USBC_PWR_LOCAL_SOURCE_PDO = TSI_BASE_LEGACY_USBC(0x11) # !CI_PARSE_SIZE 28 +TSI_USBC_PWR_REMOTE_SINK_PDO_R = TSI_BASE_LEGACY_USBC(0x12) # !CI_PARSE_SIZE 28 +TSI_USBC_PWR_REMOTE_SOURCE_PDO_R = TSI_BASE_LEGACY_USBC(0x13) # !CI_PARSE_SIZE 28 + +TSI_USBC_PD_STATUS_R = TSI_BASE_LEGACY_USBC(0x14) +TSI_USBC_PD_COMMAND_W = TSI_BASE_LEGACY_USBC(0x15) + +TSI_USBC_RESISTANCE_CONTROL = TSI_BASE_LEGACY_USBC(0x16) +TSI_USBC_INT_RESISTANCE_STATUS_R = TSI_BASE_LEGACY_USBC(0x17) +TSI_USBC_EXT_RESISTANCE_STATUS_R = TSI_BASE_LEGACY_USBC(0x18) + +TSI_USBC_ROLE_CONTROL_SWAP_W = TSI_BASE_LEGACY_USBC(0x27) # Warning - TSIX is 0x619! +TSI_USBC_ROLE_CONTROL_SWAP_R = TSI_BASE_LEGACY_USBC(0x28) # Warning - TSIX is 0x620! + +TSI_USBC_PWR_COMMAND = TSI_BASE_LEGACY_PDO(0x0) +TSI_USBC_PWR_LOCAL_SOURCE_PDO_SELECT = TSI_BASE_LEGACY_PDO(0x1) +TSI_USBC_PWR_LOCAL_SOURCE_PDO_TYPE = TSI_BASE_LEGACY_PDO(0x2) +TSI_USBC_PWR_LOCAL_SOURCE_PDO_MAX_CURRENT = TSI_BASE_LEGACY_PDO(0x3) +TSI_USBC_PWR_LOCAL_SOURCE_PDO_VOLTAGE = TSI_BASE_LEGACY_PDO(0x4) +TSI_USBC_PWR_LOCAL_SOURCE_PDO_PEAK_CURRENT = TSI_BASE_LEGACY_PDO(0x5) +TSI_USBC_PWR_LOCAL_SOURCE_PDO_MAX_POWER = TSI_BASE_LEGACY_PDO(0x6) +TSI_USBC_PWR_LOCAL_SOURCE_PDO_MAX_VOLTAGE = TSI_BASE_LEGACY_PDO(0x7) +TSI_USBC_PWR_LOCAL_SOURCE_PDO_MIN_VOLTAGE = TSI_BASE_LEGACY_PDO(0x8) + +TSI_USBC_PWR_LOCAL_SINK_PDO_SELECT = TSI_BASE_LEGACY_PDO(0x9) +TSI_USBC_PWR_LOCAL_SINK_PDO_TYPE = TSI_BASE_LEGACY_PDO(0xa) +TSI_USBC_PWR_LOCAL_SINK_PDO_MAX_CURRENT = TSI_BASE_LEGACY_PDO(0xb) +TSI_USBC_PWR_LOCAL_SINK_PDO_VOLTAGE = TSI_BASE_LEGACY_PDO(0xc) +TSI_USBC_PWR_LOCAL_SINK_PDO_MAX_POWER = TSI_BASE_LEGACY_PDO(0xd) +TSI_USBC_PWR_LOCAL_SINK_PDO_MAX_VOLTAGE = TSI_BASE_LEGACY_PDO(0xe) +TSI_USBC_PWR_LOCAL_SINK_PDO_MIN_VOLTAGE = TSI_BASE_LEGACY_PDO(0xf) + +TSI_USBC_PWR_LOCAL_SOURCE_PDO_FIXED_SUPPLY_BITS_25_TO_29 = TSI_BASE_LEGACY_PDO(0x10) +TSI_USBC_PWR_LOCAL_SINK_PDO_FIXED_SUPPLY_BITS_25_TO_29 = TSI_BASE_LEGACY_PDO(0x11) + +TSI_W_USBC_CABLE_CONTROL = TSI_USBC_CABLE_CONTROL_W +TSI_W_USBC_INITIAL_ROLE = TSI_USBC_INITIAL_ROLE_W +TSI_W_USBC_ROLE_CONTROL = TSI_USBC_ROLE_CONTROL_W +TSI_W_USBC_DP_ALT_MODE_COMMAND = TSI_USBC_DP_ALT_MODE_COMMAND_W +TSI_R_USBC_TE_HW_CONFIGURATION = TSI_USBC_TE_HW_CONFIGURATION_R +TSI_R_USBC_CABLE_STATUS = TSI_USBC_CABLE_STATUS_R +TSI_R_USBC_ROLE_STATUS = TSI_USBC_ROLE_STATUS_R +TSI_R_USBC_DP_ALT_MODE_STATUS = TSI_USBC_DP_ALT_MODE_STATUS_R +TSI_R_USBC_POWER_STATUS = TSI_USBC_POWER_STATUS_R +TSI_R_USBC_POWER_SOURCE_PDO = TSI_USBC_POWER_SOURCE_PDO_R +TSI_R_USBC_POWER_SINK_RDO = TSI_USBC_POWER_SINK_RDO_R +TSI_R_USBC_IDO_TABLE = TSI_USBC_IDO_TABLE_R # !CI_PARSE_SIZE 24 +TSI_R_USBC_PWR_REMOTE_SINK_PDO = TSI_USBC_PWR_REMOTE_SINK_PDO_R +TSI_R_USBC_PWR_REMOTE_SOURCE_PDO = TSI_USBC_PWR_REMOTE_SOURCE_PDO_R +TSI_R_USBC_PD_STATUS = TSI_USBC_PD_STATUS_R +TSI_W_USBC_PD_COMMAND = TSI_USBC_PD_COMMAND_W +TSI_R_USBC_INT_RESISTANCE_STATUS = TSI_USBC_INT_RESISTANCE_STATUS_R +TSI_R_USBC_EXT_RESISTANCE_STATUS = TSI_USBC_EXT_RESISTANCE_STATUS_R +TSI_W_USBC_ROLE_CONTROL_SWAP = TSI_USBC_ROLE_CONTROL_SWAP_W +TSI_R_USBC_ROLE_CONTROL_SWAP = TSI_USBC_ROLE_CONTROL_SWAP_R +TSI_USBC_RESISTANCE_CTRL = TSI_USBC_RESISTANCE_CONTROL + +# !CI_PARSE_DISABLE +TSI_PDC_LOG_CTRL_VALUE_PD = (1 << 0) +TSI_PDC_LOG_CTRL_VALUE_USBC_VOLTAGE = (1 << 1) +TSI_PDC_LOG_CTRL_VALUE_USBC_EVENT = (1 << 2) +TSI_PDC_LOG_CTRL_VALUE_USBC_STATE = (1 << 3) +# !CI_PARSE_ENABLE + +TSI_PDC_HW_CONTROL_W = TSI_BASE_PDC(0x01) +TSI_PDC_HW_STATUS_R = TSI_BASE_PDC(0x01) +TSI_PDC_STATUS_R = TSI_BASE_PDC(0x03) +TSI_PDC_CONTROL_W = TSI_BASE_PDC(0x04) +TSI_PDC_STATE_R = TSI_BASE_PDC(0x04) +TSI_PDC_CAPS_R = TSI_BASE_PDC(0x06) +TSI_PDC_LOG_CAPS_R = TSI_BASE_PDC(0x07) +TSI_PDC_DPAM_CONTROL_W = TSI_BASE_PDC(0x34) +TSI_PDC_DPAM_STATE_R = TSI_BASE_PDC(0x34) +TSI_PDC_DPAM_CAPS = TSI_BASE_PDC(0x35) +TSI_PDC_DPAM_CAPS2 = TSI_BASE_PDC(0x36) +TSI_PDC_LOG_CONTROL = TSI_BASE_PDC(0x44) +TSI_PDC_USBC_LOG_CFG = TSI_BASE_PDC(0x48) +TSI_PDC_USBC_LOG_THRESH = TSI_BASE_PDC(0x49) # !CI_PARSE_SIZE 20 + +TSI_PDC_RX_ID_VDO_COUNT_R = TSI_BASE_LEGACY_USBC(0x19) +TSI_PDC_RX_ID_VDO = TSI_BASE_LEGACY_USBC(0x20) # !CI_PARSE_SIZE 48 +TSI_PDC_RX_SVID_COUNT_R = TSI_BASE_LEGACY_USBC(0x21) +TSI_PDC_RX_SVID = TSI_BASE_LEGACY_USBC(0x22) # !CI_PARSE_SIZE 48 +TSI_PDC_DPAM_RECV_STATUS_R = TSI_BASE_LEGACY_USBC(0x23) +TSI_PDC_DPAM_SENT_STATUS_R = TSI_BASE_LEGACY_USBC(0x24) +TSI_PDC_DPAM_DISC_MODES_R = TSI_BASE_LEGACY_USBC(0x25) +TSI_PDC_TYPE_R = TSI_BASE_LEGACY_USBC(0x26) +TSI_PDO_COUNT_R = TSI_BASE_LEGACY_USBC(0x29) # Warning - value has been changed due to overlapping! + +TSI_PDC_HW_CTRL = TSI_PDC_HW_CONTROL_W +TSI_PDC_HW_STATUS = TSI_PDC_HW_STATUS_R +TSI_PDC_CONTROL = TSI_PDC_CONTROL_W +TSI_PDC_STATE = TSI_PDC_STATE_R +TSI_PDC_CAPS = TSI_PDC_CAPS_R +TSI_PDC_LOG_CTRL_RW = TSI_PDC_LOG_CONTROL +TSI_PDC_RX_ID_VDO_RW = TSI_PDC_RX_ID_VDO # !CI_PARSE_SIZE 48 +TSI_PDC_RX_SVID_RW = TSI_PDC_RX_SVID # !CI_PARSE_SIZE 48 +TSI_PDC_DPAM_RECV_STS_R = TSI_PDC_DPAM_RECV_STATUS_R +TSI_PDC_DPAM_SENT_STS_R = TSI_PDC_DPAM_SENT_STATUS_R +TSI_PDC_RX_ID_VDO_CNT_R = TSI_PDC_RX_ID_VDO_COUNT_R +TSI_PDC_RX_SVID_CNT_R = TSI_PDC_RX_SVID_COUNT_R + +TSI_ADC_BLOCK_SIZE_WORDS = 0x08 +TSI_ADC_FIRST = TSI_BASE_LEGACY_ADC(0x0) +TSI_ADC_LAST = TSI_BASE_LEGACY_ADC(0xff) +# !CI_PARSE_DISABLE +TSI_R_ADC_FIRST = TSI_ADC_FIRST +TSI_R_ADC_LAST = TSI_ADC_LAST +TSI_ADC_WORD_COUNT = (1 + TSI_R_ADC_LAST - TSI_R_ADC_FIRST) +# !CI_PARSE_ENABLE + +TSI_ADC_VBUS_VOLTAGE_R = TSI_BASE_LEGACY_ADC(0x0) +TSI_ADC_VBUS_CURRENT_R = TSI_BASE_LEGACY_ADC(0x1) +TSI_ADC_VCC1_VOLTAGE_R = TSI_BASE_LEGACY_ADC(0x2) +TSI_ADC_VCC2_VOLTAGE_R = TSI_BASE_LEGACY_ADC(0x3) +TSI_ADC_VCONN_VOLTAGE_R = TSI_BASE_LEGACY_ADC(0x4) +TSI_ADC_VCONN_CURRENT_R = TSI_BASE_LEGACY_ADC(0x5) +TSI_ADC_SBU1_VOLTAGE_R = TSI_BASE_LEGACY_ADC(0x6) +TSI_ADC_SBU2_VOLTAGE_R = TSI_BASE_LEGACY_ADC(0x7) + +TSI_USBC_ADC_DATA_R = TSI_BASE_LEGACY_ADC(0x1) +TSI_USBC_ADC_CONTROL_W = TSI_BASE_LEGACY_ADC(0x100) +TSI_USBC_ADC_TIMEOUT_W = TSI_BASE_LEGACY_ADC(0x101) +TSI_USBC_ADC_STATUS_R = TSI_BASE_LEGACY_ADC(0x102) + +TSI_R_ADC_WORD_COUNT = TSI_ADC_WORD_COUNT +TSI_USBC_ADC_BLOCK_SIZE_R = TSI_ADC_BLOCK_SIZE_WORDS +TSI_R_ADC_FIRST = TSI_ADC_FIRST +TSI_R_ADC_LAST = TSI_ADC_LAST +TSI_W_USBC_ADC_CTRL = TSI_USBC_ADC_CONTROL_W +TSI_W_USBC_ADC_TIMEOUT = TSI_USBC_ADC_TIMEOUT_W +TSI_R_USBC_ADC_DATA = TSI_USBC_ADC_DATA_R +TSI_R_USBC_ADC_BLOCK_SIZE = TSI_USBC_ADC_BLOCK_SIZE_R +TSI_R_ADC_VBUS_VOLTAGE = TSI_ADC_VBUS_VOLTAGE_R +TSI_R_ADC_VBUS_CURRENT = TSI_ADC_VBUS_CURRENT_R +TSI_R_ADC_VCC1_VOLTAGE = TSI_ADC_VCC1_VOLTAGE_R +TSI_R_ADC_VCC2_VOLTAGE = TSI_ADC_VCC2_VOLTAGE_R +TSI_R_ADC_VCONN_VOLTAGE = TSI_ADC_VCONN_VOLTAGE_R +TSI_R_ADC_VCONN_CURRENT = TSI_ADC_VCONN_CURRENT_R +TSI_R_ADC_SBU1_VOLTAGE = TSI_ADC_SBU1_VOLTAGE_R +TSI_R_ADC_SBU2_VOLTAGE = TSI_ADC_SBU2_VOLTAGE_R + +# TODO PI:! unpublished API to test Playback, dont send to customers until finalized + +# !CI_PARSE_DISABLE +TSI_PLAYBACK_STOP = 0 +TSI_PLAYBACK_START = 1 + +TSI_PLAYBACK_ENABLE_VIDEO = 1 +TSI_PLAYBACK_ENABLE_PACKETS = 2 +TSI_PLAYBACK_ENABLE_AUDIO = 4 +TSI_PLAYBACK_ENABLE_DSC = 8 +TSI_PLAYBACK_ENABLE_PCIE = 16 +# !CI_PARSE_ENABLE + +TSI_PLAYBACK_CONTROL = TSI_BASE_PLAYBACK(0x00) +TSI_PLAYBACK_CONFIG = TSI_BASE_PLAYBACK(0x01) +TSI_PLAYBACK_PACKET_BLOCK = TSI_BASE_PLAYBACK(0x02) +TSI_PLAYBACK_STEP_R = TSI_BASE_PLAYBACK(0x03) + +TSI_SCENARIO_STEP_AMOUNT = TSI_BASE_PLAYBACK(0x04) +TSI_SCENARIO_CURRENT_STEP = TSI_BASE_PLAYBACK(0x05) +TSI_SCENARIO_STEP_IMAGE_INDEX = TSI_BASE_PLAYBACK(0x06) +TSI_SCENARIO_STEP_REPETITION = TSI_BASE_PLAYBACK(0x07) +TSI_SCENARIO_STEP_COLOR_FORMAT = TSI_BASE_PLAYBACK(0x08) +TSI_SCENARIO_STEP_COLOR_DEPTH = TSI_BASE_PLAYBACK(0x09) +TSI_SCENARIO_STEP_VRR = TSI_BASE_PLAYBACK(0x0a) +TSI_SCENARIO_STEP_FLAGS = TSI_BASE_PLAYBACK(0x0b) +TSI_SCENARIO_STEP_PACKETS_ADD = TSI_BASE_PLAYBACK(0x0c) +TSI_SCENARIO_STEP_PACKETS_CLEAR = TSI_BASE_PLAYBACK(0x0d) +TSI_SCENARIO_STRING = TSI_BASE_PLAYBACK(0x0e) +TSI_SCENARIO_STEP_SIZE_R = TSI_BASE_PLAYBACK(0x0f) +TSI_SCENARIO_VSC_FLAGS = TSI_BASE_PLAYBACK(0x10) + +# TODO PI:! end unpublished API to test Playback + + +# !CI_PARSE_DISABLE +TSI_TEST_NONE = 0x00000000 # NO Test (Used for configuration through TSI_TST_RunTest, for example). +TSI_TEST_VIDEO_NO_PXL_TOLERANCE = 0x00000001 # TEST ID (Generic) +TSI_TEST_VIDEO_PXL_TOLERANCE = 0x00000002 # TEST ID (Generic) +TSI_TEST_AUDIO_KILOHERTZ = 0x00000003 # TEST ID (Generic, with audio capability) +TSI_TEST_CEC_DISCOVERY = 0x00000004 # TEST ID (UFG-06 Specific) +TSI_TEST_HDR10_SSTM_SRC1 = 0x00000100 # TEST ID (Generic) +TSI_TEST_HDR10_SSTM_SRC2 = 0x00000101 # TEST ID (Generic) +TSI_TEST_HDR10_SSTM_SRC3 = 0x00000102 # TEST ID (Generic) +TSI_TEST_HDR10_SSTM_SRC4 = 0x00000103 # TEST ID (Generic) +TSI_TEST_HDR10_SSTM_SRC5 = 0x00000104 # TEST ID (Generic) +TSI_TEST_HDR10_SSTM_SRC6 = 0x00000105 # TEST ID (Generic) +TSI_TEST_HDR10_SRC1 = 0x00000200 # TEST ID (Generic) +TSI_TEST_HDR10_SRC2 = 0x00000201 # TEST ID (Generic) +TSI_TEST_HDR10_SRC3 = 0x00000202 # TEST ID (Generic) +TSI_TEST_HDR10_SRC4 = 0x00000203 # TEST ID (Generic) +TSI_TEST_HDR10_SRC5 = 0x00000204 # TEST ID (Generic) +TSI_TEST_HDR10_SRC6 = 0x00000205 # TEST ID (Generic) +TSI_TEST_HDR10_SRC7 = 0x00000206 # TEST ID (Generic) +TSI_TEST_HDR10_SRC8 = 0x00000207 # TEST ID (Generic) +TSI_TEST_HDR10_SRC9 = 0x00000208 # TEST ID (Generic) +TSI_TEST_HDR10_SRC10 = 0x00000209 # TEST ID (Generic) +TSI_TEST_HDR10_SRC11 = 0x0000020A # TEST ID (Generic) +TSI_TEST_HDR10_SRC12 = 0x0000020B # TEST ID (Generic) +TSI_TEST_HDR10_SRC13 = 0x0000020C # TEST ID (Generic) +TSI_TEST_HDR10_SRC14 = 0x0000020D # TEST ID (Generic) +TSI_TEST_HDR10_SRC15 = 0x0000020E # TEST ID (Generic) +TSI_TEST_HDR10_SRC16 = 0x0000020F # TEST ID (Generic) +TSI_TEST_HDR10_SRC17 = 0x00000210 # TEST ID (Generic) +TSI_TEST_HDR10_SRC18 = 0x00000211 # TEST ID (Generic) +TSI_TEST_HDR10_SRC19 = 0x00000212 # TEST ID (Generic) +TSI_TEST_HDR10_SRC20 = 0x00000213 # TEST ID (Generic) +TSI_TEST_HDR10_SRC21 = 0x00000214 # TEST ID (Generic) +TSI_TEST_HDR10_SRC22 = 0x00000215 # TEST ID (Generic) +TSI_TEST_HDR10_SRC23 = 0x00000216 # TEST ID (Generic) +TSI_TEST_HDR10_SRC24 = 0x00000217 # TEST ID (Generic) +TSI_TEST_HDR10_SRC25 = 0x00000218 # TEST ID (Generic) +TSI_TEST_HDR10_SRC26 = 0x00000219 # TEST ID (Generic) +TSI_TEST_HDR10_SRC27 = 0x0000021A # TEST ID (Generic) +TSI_TEST_HDR10_SRC28 = 0x0000021B # TEST ID (Generic) +TSI_TEST_HDR10_SRC29 = 0x0000021C # TEST ID (Generic) +TSI_TEST_HDR10_SRC30 = 0x0000021D # TEST ID (Generic) +TSI_TEST_HDR10_SRC31 = 0x0000021E # TEST ID (Generic) +TSI_TEST_HDR10_SRC32 = 0x0000021F # TEST ID (Generic) +TSI_TEST_HDR10_SRC33 = 0x00000220 # TEST ID (Generic) +TSI_TEST_HDR10_SRC34 = 0x00000221 # TEST ID (Generic) +TSI_TEST_HDR10_SRC35 = 0x00000222 # TEST ID (Generic) + +TSI_TEST_HDMI_EL_POWER_LINE = 0x00020000 # TEST ID (UCD-301 HDMI specific) +TSI_TEST_HDMI_EL_HPD_LINE = 0x00020002 # TEST ID (UCD-301 HDMI specific) +TSI_TEST_HDMI_EL_DDC_CEC_LINES = 0x00020003 # TEST ID (UCD-301 HDMI specific) +TSI_TEST_HDMI_EL_TMDS_LINES = 0x00020001 # TEST ID (UCD-301 HDMI specific) +TSI_TEST_HDMI_CEC = 0x00050000 # TEST ID (UCD-301 HDMI specific) +TSI_TEST_DP_EL_MAIN_LINK = 0x00010001 # TEST ID (UCD-301 DP specific) +TSI_TEST_DP_EL_AUX_LINE = 0x00010002 # TEST ID (UCD-301 DP specific) +TSI_TEST_DP_EL_HPD_LINE = 0x00010000 # TEST ID (UCD-301 DP specific) +TSI_TEST_DP_SIMPLE_LINK = 0x00070000 # TEST ID (UCD-300 DP specific) +TSI_TEST_USBC_CC_VCON = 0x000c0000 # TEST ID (UCD-340 specific) +TSI_TEST_USBC_SBU_DP_AUX = 0x000c0001 # TEST ID (UCD-340 specific) +TSI_TEST_USBC_DUT_PWR_SINK = 0x000c0002 # TEST ID (UCD-340 specific) +TSI_TEST_USBC_DUT_PWR_SOURCE = 0x000c0003 # TEST ID (UCD-340 specific) +TSI_TEST_DP_VIDEO_CRC_SINGLE_REF = 0x00060000 # TEST ID (UCD-300 DP specific) +TSI_TEST_DP_CRC_VIDEO_STABILITY = 0x00060001 # TEST ID (UCD-300 DP specific) +TSI_TEST_DP_CRC_VIDEO_SEQUENCE = 0x00060002 # TEST ID (UCD-300 DP specific) +TSI_TEST_DP_CRC_CONT_VIDEO_SEQUENCE = 0x00060003 # TEST ID (UCD-300 DP specific) +TSI_TEST_HD_VIDEO_CRC_SINGLE_REF = 0x000b0000 # TEST ID (UCD-300 HDMI specific) +TSI_TEST_HD_CRC_VIDEO_STABILITY = 0x000b0001 # TEST ID (UCD-300 HDMI specific) +TSI_TEST_HD_CRC_VIDEO_SEQUENCE = 0x000b0002 # TEST ID (UCD-300 HDMI specific) +TSI_TEST_HD_CRC_CONT_VIDEO_SEQUENCE = 0x000b0003 # TEST ID (UCD-300 HDMI specific) +TSI_DP14_LL_CTS_SRC_DUT_4_2_1_1 = 0x000e0000 # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_4_2_1_2 = 0x000e0001 # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_4_2_2_1 = 0x000e0002 # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_4_2_2_2 = 0x000e0003 # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_4_2_2_3 = 0x000e0004 # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_4_2_2_4 = 0x000e0005 # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_4_2_2_5 = 0x000e0006 # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_4_2_2_6 = 0x000e0007 # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_4_2_2_7 = 0x000e0008 # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_4_2_2_8 = 0x000e0009 # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_4_2_2_9 = 0x000e000a # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_4_2_2_10 = 0x000e000b # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_4_3_1_1 = 0x000e000c # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_4_3_1_2 = 0x000e000d # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_4_3_1_3 = 0x000e000e # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_4_3_1_4 = 0x000e000f # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_4_3_1_5 = 0x000e0010 # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_4_3_1_6 = 0x000e0011 # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_4_3_1_7 = 0x000e0012 # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_4_3_1_8 = 0x000e0013 # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_4_3_1_9 = 0x000e0014 # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_4_3_1_10 = 0x000e0015 # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_4_3_1_11 = 0x000e0016 # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_4_3_2_1 = 0x000e0017 # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_4_3_2_2 = 0x000e0018 # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_4_3_2_3 = 0x000e0019 # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_4_3_2_4 = 0x000e001a # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_4_3_2_5 = 0x000e001b # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_4_3_3_1 = 0x000e001c # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_4_4_1_1 = 0x000e001d # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_4_4_1_2 = 0x000e001e # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_4_4_1_3 = 0x000e001f # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_4_4_2 = 0x000e0020 # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_4_4_3 = 0x000e0021 # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_400_1_1 = 0x000e0022 # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_400_1_2 = 0x000e0023 # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_400_1_3 = 0x000e0024 # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_400_2_1 = 0x000e0025 # TEST ID (UCD-400 DP sink specific) +TSI_DP14_LL_CTS_SRC_DUT_400_2_2 = 0x000e0026 # TEST ID (UCD-400 DP sink specific) +# !CI_PARSE_ENABLE + + +# !CI_PARSE_DISABLE +TSI_TEST_VIDEO_CRC_SINGLE_REF = TSI_TEST_DP_VIDEO_CRC_SINGLE_REF +TSI_TEST_CRC_VIDEO_STABILITY = TSI_TEST_DP_CRC_VIDEO_STABILITY +TSI_TEST_CRC_VIDEO_SEQUENCE = TSI_TEST_DP_CRC_VIDEO_SEQUENCE +TSI_TEST_CRC_CONT_VIDEO_SEQUENCE = TSI_TEST_DP_CRC_CONT_VIDEO_SEQUENCE +# !CI_PARSE_ENABLE + + +# !CI_PARSE_DISABLE +TSI_OFMODE_RUN_EXTERNAL = 1 +TSI_OFMODE_RUN_INTERNAL = 2 +TSI_OFMODE_RUN_CALL_PROCEEDURE = 3 +TSI_OFMODE_RUN_CALL_STRUCT_PROCEDURE = 4 +TSI_OFMODE_RUN_CALL_STRUCT_PROCEDURE_AND_RESULT = 5 + +TSI_OPF_RETURN_CODE_ABORT = 0 +TSI_OPF_RETURN_CODE_FAIL = 1 +TSI_OPF_RETURN_CODE_PASS = 2 +TSI_OPF_RETURN_CODE_PROCEED = 3 +TSI_OPF_RETURN_CODE_AUTO_CLOSED = 4 + +TSI_OPF_LED_NONE = 0 +TSI_OPF_LED_GRAY = 1 +TSI_OPF_LED_RED = 2 +TSI_OPF_LED_GREEN = 3 + +TSI_OPF_CONTROL_ABORT = 1 +TSI_OPF_CONTROL_FAIL = 2 +TSI_OPF_CONTROL_PASS = 4 +TSI_OPF_CONTROL_PROCEED = 8 + + +class TSI_OPF_PARAMETER(Structure): + _fields_ = [ + ('StatusLED', c_int), + ('Name', c_char_p), + ('Description', c_char_p), + ] + + @property + def name(self): + return self.Name.decode('UTF-8') + + @property + def description(self): + return self.Description.decode('UTF-8') + + +class TSI_OPF_DATA_BLOCK(Structure): + _fields_ = [ + ('Sync', c_uint), + ('Length', c_uint), + ('Data', c_void_p) + ] + + +class TSI_OPF_CALLBACK_STRUCT(Structure): + _fields_ = [ + + ('ID', c_uint), + ('Title', c_char_p), + ('Request', c_char_p), + ('Request2', c_char_p), + + ('Parameters', POINTER(TSI_OPF_PARAMETER)), + ('ParameterCount', c_uint), + + ('DialogControls', c_int), + + ('OwnerObject', c_void_p), + ('UpdateParameters', WINFUNCTYPE(c_bool, c_void_p, POINTER(TSI_OPF_PARAMETER), c_uint) if platform.system() == 'Windows' else CFUNCTYPE(c_bool, c_void_p, POINTER(TSI_OPF_PARAMETER), c_uint)), + ('IsDialogStillShown', WINFUNCTYPE(c_bool, c_void_p) if platform.system() == 'Windows' else CFUNCTYPE(c_bool, c_void_p)), + ('WriteOpfDialogReply', WINFUNCTYPE(c_bool, c_void_p, c_uint32, c_uint32, c_int) if platform.system() == 'Windows' else CFUNCTYPE(c_bool, c_void_p, c_uint32, c_uint32, c_int)), + + ('DataBlocks', POINTER(TSI_OPF_DATA_BLOCK)), + ('DataBlocksCount', c_uint), + + ('SessionId', c_uint) + ] + + def id(self) -> int: + return self.ID + + def parameters(self) -> List[TSI_OPF_PARAMETER]: + return list(self.Parameters[:self.ParameterCount]) + + def title(self) -> str: + return self.Title.decode("utf-8") + + def request1(self) -> str: + return self.Request.decode("utf-8") + + def request2(self) -> str: + return self.Request2.decode("utf-8") + + def write_opf_result(self, opf_reply: int) -> int: + return TSI_OPF_CONTROL_PASS if self.WriteOpfDialogReply(self.OwnerObject, self.ID, self.SessionId, c_int(opf_reply)) else TSI_OPF_CONTROL_ABORT + + def session_id(self) -> int: + return self.SessionId + + +# !CI_PARSE_ENABLE + + +# !CI_PARSE_DISABLE +TSI_TEST_PASSED = TSI_SUCCESS # Test completed with success. +TSI_TEST_FAILED = 1 # Test failed: (Too many) errors found. +TSI_TEST_NOT_STARTED = 2 # Test skipped: non-error situation preventing test to run. +TSI_TEST_ABORTED = 3 # Test aborted: by user command or automatically by device. +TSI_TEST_NOT_PASSED = TSI_TEST_FAILED # Provided for compatibility. + +TSI_GT_CFG_FIRST = 0x00000000 # Normal config parameters: 3200 DWORDs available. +TSI_GT_CFG_LAST = 0x00000c7f # Last valid normal parameter CI. +TSI_GT_CFG_EXT_FIRST = 0x00001000 # Extended parameters per test: 61440 DWORDS available. +TSI_GT_CFG_EXT_LAST = 0x0000ffff # Last valid EXTENDED parameter CI +# !CI_PARSE_ENABLE + +TSI_GT_CONFIG_START = 0x01000000 # Start of Reserved CI range for generic tests +TSI_GT_CONFIG_END = 0x01ffffff # End of Reserved CI range for generic tests +TSI_GT_SET_MASK = 0x00ff0000 # AND with a test ID -> test set ID at bits 23:16 +TSI_GT_NORMAL_CONFIG_CI_COUNT_R = 0x01000ff0 # OR with test set ID to access. +TSI_GT_EXTENDED_CONFIG_CI_COUNT = 0x01000ff1 # OR with test set ID to access. +# NOTE TSI_GT_EXTENDED_CONFIG_CI_COUNT returns maximum number of extended parameters. +# Write sets actual number of extended configuration sent to the device. + +TSI_R_GT_NORMAL_CONFIG_CI_COUNT = TSI_GT_NORMAL_CONFIG_CI_COUNT_R + +TSI_TEST_LENGTH = TSI_BASE_LEGACY_VIDTEST(0x0) # As number of consequent frames +TSI_LIM_FRAME_MISMATCHES = TSI_BASE_LEGACY_VIDTEST(0x1) # As number of frames. +TSI_LIM_PIXEL_MISMATCHES = TSI_BASE_LEGACY_VIDTEST(0x2) # As number of pixels. +TSI_PIXEL_TOLERANCE = TSI_BASE_LEGACY_VIDTEST(0x3) # As absolute difference vs. reference. +TSI_VIDEO_TEST_RAW_RESULTS_DATA_R = TSI_BASE_LEGACY_VIDTEST( + 0x8) # Raw results data. Please refer to TSI reference manual for data format info. +TSI_HDCP_REQUIREMENT = TSI_BASE_LEGACY_VIDTEST( + 0x9) # Input HDCP capability state. -1 = Require NO HDCP, 0 = Don't care (Default), 1 = Require HDCP 1.x + +TSI_MAX_AUTO_SAVE_FAILED = TSI_BASE_LEGACY_VIDTEST( + 0x80) # Max. number of failed frames to save into target folder per test. +TSI_FAILED_FRAME_TARGET_FOLDER = TSI_BASE_LEGACY_VIDTEST(0x81) # String: Target folder for save frames. +TSI_MAX_EXPORT_FAILED = TSI_BASE_LEGACY_VIDTEST(0x82) # Max. number of failed frames to export per test. +TSI_EXPORT_TO_PPM = 0x1083 # Export frames to ppm format. Obsolete use TSI_EXPORT_FORMAT instead +TSI_EXPORT_FORMAT = 0x1084 +TSI_EXPORT_ALIGN_12 = 0x1085 + +TSI_EXPORTED_FRAME_COUNT_R = TSI_BASE_LEGACY_VIDTEST(0x8e) # Number of exported frames available. +TSI_EXPORT_ACCESS_INDEX_W = TSI_BASE_LEGACY_VIDTEST( + 0x8f) # Which exported frame to access with config ID values 0x1090 to 0x1097 +TSI_EXPORT_WIDTH_R = TSI_BASE_LEGACY_VIDTEST(0x90) # As number of elements. +TSI_EXPORT_HEIGHT_R = TSI_BASE_LEGACY_VIDTEST(0x91) # As number of elements. +TSI_EXPORT_ELEMENT_SIZE_R = TSI_BASE_LEGACY_VIDTEST(0x92) # As number of bytes. +TSI_EXPORT_ELEMENT_WIDTH_R = TSI_BASE_LEGACY_VIDTEST(0x93) +TSI_EXPORT_ELEMENT_HEIGHT_R = TSI_BASE_LEGACY_VIDTEST(0x94) +TSI_EXPORT_COLOR_DEPTH_R = TSI_BASE_LEGACY_VIDTEST(0x95) # As bits per color-channel +TSI_EXPORT_ELEMENT_FORMAT_R = TSI_BASE_LEGACY_VIDTEST(0x96) +TSI_EXPORT_FRAME_DATA_R = TSI_BASE_LEGACY_VIDTEST(0x97) # Pointer to image data. + +TSI_EXPORT_PIXELS_PER_ELEMENT = TSI_EXPORT_ELEMENT_WIDTH_R +TSI_EXPORT_LINES_PER_ELEMENT = TSI_EXPORT_ELEMENT_HEIGHT_R +TSI_EXPORT_PIXEL_FORMAT = TSI_EXPORT_ELEMENT_FORMAT_R +TSI_R_VIDEO_TEST_RAW_RESULTS_DATA = TSI_VIDEO_TEST_RAW_RESULTS_DATA_R +TSI_EXPORTED = TSI_EXPORTED_FRAME_COUNT_R +TSI_EXPORT_ACCESS_INDEX = TSI_EXPORT_ACCESS_INDEX_W +TSI_EXPORT_WIDTH = TSI_EXPORT_WIDTH_R +TSI_EXPORT_HEIGHT = TSI_EXPORT_HEIGHT_R +TSI_EXPORT_ELEMENT_SIZE = TSI_EXPORT_ELEMENT_SIZE_R +TSI_EXPORT_ELEMENT_WIDTH = TSI_EXPORT_ELEMENT_WIDTH_R +TSI_EXPORT_ELEMENT_HEIGHT = TSI_EXPORT_ELEMENT_HEIGHT_R +TSI_EXPORT_COLOR_DEPTH = TSI_EXPORT_COLOR_DEPTH_R +TSI_EXPORT_ELEMENT_FORMAT = TSI_EXPORT_ELEMENT_FORMAT_R +TSI_EXPORT_FRAME_DATA = TSI_EXPORT_FRAME_DATA_R + +TSI_AUDIO_TEST_SAVE_NONE = 0 +TSI_AUDIO_TEST_SAVE_FAILED = 1 +TSI_AUDIO_TEST_SAVE_ALL = 2 + +TSI_EXPECTED_SAMPLE_RATE = TSI_BASE_LEGACY_AUDTEST(0x0) +TSI_EXPECTED_AUDIO_FREQUENCY = TSI_BASE_LEGACY_AUDTEST(0x1) +TSI_AUDIO_FREQUENCY_TOLERANCE = TSI_BASE_LEGACY_AUDTEST(0x2) +TSI_AUDIO_GLITCH_DETECT_TRESHOLD = TSI_BASE_LEGACY_AUDTEST( + 0x3) # Treshold value used to detect audible clicks caused by dropped or duplicated samples. +# FIXED POINT: Default 5.0 (Encoded with 16 bit scaling Actual value is 327680) +TSI_AUDIO_GLITCHES_ALLOWED = TSI_BASE_LEGACY_AUDTEST(0x4) +TSI_AUDIO_TEST_SAVE_CONDITIONS = TSI_BASE_LEGACY_AUDTEST(0x5) +TSI_AUDIO_TEST_STORAGE_FOLDER = TSI_BASE_LEGACY_AUDTEST(0x6) + +TSI_TDATA_BLOCK_FIRST_R = 0x0000f800 +TSI_TDATA_BLOCK_LAST_R = 0x0000ffd0 +# !CI_PARSE_DISABLE +TSI_TDATA_BLOCK_WORD_COUNT_R = (1 + TSI_TDATA_BLOCK_LAST_R - TSI_TDATA_BLOCK_FIRST_R) +# !CI_PARSE_ENABLE +# HDR10+ CTS parameters +TSI_HDR10_CTS_DISTR_SAME_FRAME = 0x202A # Metadata at the same frame of associated video (0) or at one frame advance (1). Default 0 +TSI_HDR10_CTS_DISTR_TIMEOUT = 0x202B # Test timeout, sec +TSI_HDR10_TEMP_FOLDER = 0x202C # Folder to save captured events and video of test #13 + +TSI_TDATA_BLOCK_SIZE_R = 0x0000ffff # Number of bytes available from TDATA CI 0xf800. + +# Low-level test specific defines. +TSI_R_TDATA_BLOCK_FIRST = TSI_TDATA_BLOCK_FIRST_R +TSI_TDATA_GENERIC_STRUCT_VERSION_R = TSI_R_TDATA_BLOCK_FIRST + 0 + +# Applicable to test 0x000c0000 "Up Face Port CC and VCONN test" +TSI_TDATA_USBC_EL_VCC1_R = TSI_R_TDATA_BLOCK_FIRST + 1 # CC1 line voltage, in mV +TSI_TDATA_USBC_EL_VCONN1_R = TSI_R_TDATA_BLOCK_FIRST + 2 # CC2 line voltage when in Vconn role, in mV +TSI_TDATA_USBC_EL_VCC2_R = TSI_R_TDATA_BLOCK_FIRST + 3 # CC2 line voltage, in mV +TSI_TDATA_USBC_EL_VCONN2_R = TSI_R_TDATA_BLOCK_FIRST + 4 # CC1 line voltage when in Vconn role, in mV + +# Applicable to test 0x000c0001 "AUX (SBU) lines test" +TSI_TDATA_USBC_VAUX1_P_R = TSI_R_TDATA_BLOCK_FIRST + 1 # Voltage level on positive AUX line (direct cable mode), in mV +TSI_TDATA_USBC_VAUX1_N_R = TSI_R_TDATA_BLOCK_FIRST + 2 # Voltage level on negative AUX line (direct cable mode), in mV +TSI_TDATA_USBC_VAUX2_P_R = TSI_R_TDATA_BLOCK_FIRST + 3 # Voltage level on positive AUX line (crossed cable mode), in mV +TSI_TDATA_USBC_VAUX2_N_R = TSI_R_TDATA_BLOCK_FIRST + 4 # Voltage level on negative AUX line (crossed cable mode), in mV + +# Applicable to tests 0x000c0002 "DUT as Power Sink" +# 0x000c0003 "DUT as Power Source" +TSI_TDATA_USBC_EL_VBUS_V_R = TSI_R_TDATA_BLOCK_FIRST + 1 # Vbus voltage, in mV. +TSI_TDATA_USBC_EL_VBUS_I1_R = TSI_R_TDATA_BLOCK_FIRST + 2 # Vbus current, line 1, in mA. +TSI_TDATA_USBC_EL_VBUS_I2_R = TSI_R_TDATA_BLOCK_FIRST + 3 # Vbus current, line 2, in mA. +TSI_TDATA_USBC_EL_VBUS_I3_R = TSI_R_TDATA_BLOCK_FIRST + 4 # Vbus current, line 3, in mA. +TSI_TDATA_USBC_EL_VBUS_I4_R = TSI_R_TDATA_BLOCK_FIRST + 5 # Vbus current, line 4, in mA. +TSI_TDATA_USBC_EL_GND_I1_R = TSI_R_TDATA_BLOCK_FIRST + 6 # Gnd current, line 1, in mA. +TSI_TDATA_USBC_EL_GND_I2_R = TSI_R_TDATA_BLOCK_FIRST + 7 # Gnd current, line 2, in mA. +TSI_TDATA_USBC_EL_GND_I3_R = TSI_R_TDATA_BLOCK_FIRST + 8 # Gnd current, line 3, in mA. +TSI_TDATA_USBC_EL_GND_I4_R = TSI_R_TDATA_BLOCK_FIRST + 9 # Gnd current, line 4, in mA. + +TSI_R_TDATA_BLOCK_LAST = TSI_TDATA_BLOCK_LAST_R +TSI_R_TDATA_BLOCK_WORD_COUNT = TSI_TDATA_BLOCK_WORD_COUNT_R +TSI_R_TDATA_BLOCK_SIZE = TSI_TDATA_BLOCK_SIZE_R +TSI_R_TDATA_GENERIC_STRUCT_VERSION = TSI_TDATA_GENERIC_STRUCT_VERSION_R +TSI_R_TDATA_USBC_EL_VCC1 = TSI_TDATA_USBC_EL_VCC1_R +TSI_R_TDATA_USBC_EL_VCONN1 = TSI_TDATA_USBC_EL_VCONN1_R +TSI_R_TDATA_USBC_EL_VCC2 = TSI_TDATA_USBC_EL_VCC2_R +TSI_R_TDATA_USBC_EL_VCONN2 = TSI_TDATA_USBC_EL_VCONN2_R +TSI_R_TDATA_USBC_VAUX1_P = TSI_TDATA_USBC_VAUX1_P_R +TSI_R_TDATA_USBC_VAUX1_N = TSI_TDATA_USBC_VAUX1_N_R +TSI_R_TDATA_USBC_VAUX2_P = TSI_TDATA_USBC_VAUX2_P_R +TSI_R_TDATA_USBC_VAUX2_N = TSI_TDATA_USBC_VAUX2_N_R +TSI_R_TDATA_USBC_EL_VBUS_V = TSI_TDATA_USBC_EL_VBUS_V_R +TSI_R_TDATA_USBC_EL_VBUS_I1 = TSI_TDATA_USBC_EL_VBUS_I1_R +TSI_R_TDATA_USBC_EL_VBUS_I2 = TSI_TDATA_USBC_EL_VBUS_I2_R +TSI_R_TDATA_USBC_EL_VBUS_I3 = TSI_TDATA_USBC_EL_VBUS_I3_R +TSI_R_TDATA_USBC_EL_VBUS_I4 = TSI_TDATA_USBC_EL_VBUS_I4_R +TSI_R_TDATA_USBC_EL_GND_I1 = TSI_TDATA_USBC_EL_GND_I1_R +TSI_R_TDATA_USBC_EL_GND_I2 = TSI_TDATA_USBC_EL_GND_I2_R +TSI_R_TDATA_USBC_EL_GND_I3 = TSI_TDATA_USBC_EL_GND_I3_R +TSI_R_TDATA_USBC_EL_GND_I4 = TSI_TDATA_USBC_EL_GND_I4_R + +TSI_DP_RX_TEST_TIMEOUT = TSI_BASE_DP_RX(0x0) +TSI_DP_RX_LINKS_LOW_VOLTAGE = TSI_BASE_DP_RX(0x1) +TSI_DP_RX_LINKS_HI_VOLTAGE = TSI_BASE_DP_RX(0x2) +TSI_DP_RX_HPD_ZERO_LOW_VOLTAGE = TSI_BASE_DP_RX(0x3) +TSI_DP_RX_HPD_ZERO_HI_VOLTAGE = TSI_BASE_DP_RX(0x4) +TSI_DP_RX_HPD_ONE_LOW_VOLTAGE = TSI_BASE_DP_RX(0x5) +TSI_DP_RX_HPD_ONE_HI_VOLTAGE = TSI_BASE_DP_RX(0x6) +TSI_DP_RX_AUX_P_IDLE_LOW_VOLTAGE = TSI_BASE_DP_RX(0x7) +TSI_DP_RX_AUX_P_IDLE_HI_VOLTAGE = TSI_BASE_DP_RX(0x8) +TSI_DP_RX_AUX_N_IDLE_LOW_VOLTAGE = TSI_BASE_DP_RX(0x9) +TSI_DP_RX_AUX_N_IDLE_HI_VOLTAGE = TSI_BASE_DP_RX(0xa) +TSI_DP_RX_AUX_P_TRIG_VOLTAGE = TSI_BASE_DP_RX(0xb) +TSI_DP_RX_AUX_N_TRIG_VOLTAGE = TSI_BASE_DP_RX(0xc) +TSI_DP_RX_AUX_SIGNAL_CAPT_TIMEOUT = TSI_BASE_DP_RX(0xd) +TSI_DP_RX_AUX_SIGNAL_CAPT_TRIES = TSI_BASE_DP_RX(0xe) +TSI_DP_RX_DUT_MAX_LANE_COUNT = TSI_BASE_DP_RX(0xf) +TSI_DP_RX_DUT_MAX_LINK_RATE = TSI_BASE_DP_RX(0x10) +TSI_DP_RX_DUT_TA_CAPS = TSI_BASE_DP_RX(0x12) + +TSI_DP_RX_DUT_CAPS = TSI_BASE_DP_RX(0x11) +TSI_DP_RX_MAX_DUT_MAX_LANES = TSI_DP_RX_DUT_MAX_LANE_COUNT +TSI_DP_RX_MAX_DUT_LANE_RATE = TSI_DP_RX_DUT_MAX_LINK_RATE + +TSI_HDMI_RX_TIMEOUT = TSI_BASE_HDMI_RX(0x0) +TSI_HDMI_RX_POWER_LOW_LIMIT = TSI_BASE_HDMI_RX(0x1) +TSI_HDMI_RX_POWER_HIGH_LIMIT = TSI_BASE_HDMI_RX(0x2) +TSI_HDMI_RX_LINK_LOW_LIMIT = TSI_BASE_HDMI_RX(0x3) +TSI_HDMI_RX_LINK_HIGH_LIMIT = TSI_BASE_HDMI_RX(0x4) +TSI_HDMI_RX_HPD_ZERO_LOW_LIMIT = TSI_BASE_HDMI_RX(0x5) +TSI_HDMI_RX_HPD_ZERO_HIGH_LIMIT = TSI_BASE_HDMI_RX(0x6) +TSI_HDMI_RX_HPD_ONE_LOW_LIMIT = TSI_BASE_HDMI_RX(0x7) +TSI_HDMI_RX_HPD_ONE_HIGHT_LIMIT = TSI_BASE_HDMI_RX(0x8) +TSI_HDMI_RX_DDC_LOW_LIMIT = TSI_BASE_HDMI_RX(0x9) +TSI_HDMI_RX_DDC_HIGH_LIMIT = TSI_BASE_HDMI_RX(0xa) +TSI_HDMI_RX_CEC_ZERO_LOW_LIMIT = TSI_BASE_HDMI_RX(0xb) +TSI_HDMI_RX_CEC_ZERO_HIGH_LIMIT = TSI_BASE_HDMI_RX(0xc) +TSI_HDMI_RX_CEC_ONE_LOW_LIMIT = TSI_BASE_HDMI_RX(0xd) +TSI_HDMI_RX_CEC_ONE_HIGH_LIMIT = TSI_BASE_HDMI_RX(0xe) + +TSI_CRC_TIMEOUT = TSI_BASE_CRC_VIDEO(0x0) +TSI_CRC_FRAMES_TO_TEST = TSI_BASE_CRC_VIDEO(0x1) +TSI_CRC_REF_FRAME_COUNT = TSI_BASE_CRC_VIDEO(0x2) +TSI_CRC_LIM_FRAME_MISMATCHES = TSI_BASE_CRC_VIDEO(0x3) +TSI_CRC_REF_WIDTH = TSI_BASE_CRC_VIDEO(0x4) +TSI_CRC_REF_HEIGHT = TSI_BASE_CRC_VIDEO(0x5) +TSI_CRC_REF_COLORDEPTH = TSI_BASE_CRC_VIDEO(0x6) +TSI_CRC_REQUIRED_FRAME_RATE = TSI_BASE_CRC_VIDEO(0x7) +TSI_CRC_FRAME_RATE_TOLERANCE = TSI_BASE_CRC_VIDEO(0x8) +TSI_CRC_REFERENCE_CRC_VALUES = TSI_BASE_CRC_VIDEO(0x9) +TSI_CRC_MOTION_TEST_ITERATIONS = TSI_BASE_CRC_VIDEO(0xc) +TSI_CRC_COLOR_FORMAT = TSI_BASE_CRC_VIDEO(0xd) +TSI_CRC_DATA_TRANSFER_TIMEOUT = TSI_BASE_CRC_VIDEO(0x17) +TSI_CRC_FAILED_FRAME_TARGET_FOLDER = TSI_BASE_CRC_VIDEO(0x20) # Directory to save failed video frames to +TSI_CRC_MAX_EXPORT_FAILED = TSI_BASE_CRC_VIDEO(0x22) # Max number of failed frames to save +TSI_CRC_EXPORT_FORMAT = TSI_BASE_CRC_VIDEO(0x23) # Export format (0 - bin, 1 - ppm, 2 - bmp) +TSI_CRC_ALIGN_12 = TSI_BASE_CRC_VIDEO(0x21) # Align exported bin images to msb + +TSI_HDMI_RX_CEC_TIMEOUT = TSI_BASE_HDMI_RX_CEC(0x0) +TSI_HDMI_RX_CEC_LOCAL_PHY_ADDR = TSI_BASE_HDMI_RX_CEC(0x1) + +TSI_USBC_EL_TIMEOUT = TSI_BASE_USBC_EL(0x0) +TSI_USBC_EL_DUT_CAPS = TSI_BASE_USBC_EL(0x1) +TSI_USBC_EL_REPLUG_TIME = TSI_BASE_USBC_EL(0x2) +TSI_USBC_EL_DUT_ATTACH_TIMEOUT = TSI_BASE_USBC_EL(0x3) +TSI_USBC_EL_PWR_CONTRACT_TIMEOUT = TSI_BASE_USBC_EL(0x4) +TSI_USBC_EL_CC_LOW_VOLTAGE_1 = TSI_BASE_USBC_EL(0x5) +TSI_USBC_EL_CC_HI_VOLTAGE_1 = TSI_BASE_USBC_EL(0x6) +TSI_USBC_EL_CC_LOW_VOLTAGE_2 = TSI_BASE_USBC_EL(0x7) +TSI_USBC_EL_CC_HI_VOLTAGE_2 = TSI_BASE_USBC_EL(0x8) +TSI_USBC_EL_CC_LOW_VOLTAGE_3 = TSI_BASE_USBC_EL(0x9) +TSI_USBC_EL_CC_HI_VOLTAGE_3 = TSI_BASE_USBC_EL(0xa) +TSI_USBC_EL_VCON_LOW_VOLTAGE = TSI_BASE_USBC_EL(0xb) +TSI_USBC_EL_VCON_HI_VOLTAGE = TSI_BASE_USBC_EL(0xc) +TSI_USBC_EL_DP_ALT_TIMEOUT = TSI_BASE_USBC_EL(0xd) +TSI_USBC_EL_AUX_P_IDLE_LOW_VOLTAGE = TSI_BASE_USBC_EL(0xe) +TSI_USBC_EL_AUX_P_IDLE_HI_VOLTAGE = TSI_BASE_USBC_EL(0xf) +TSI_USBC_EL_AUX_N_IDLE_LOW_VOLTAGE = TSI_BASE_USBC_EL(0x10) +TSI_USBC_EL_AUX_N_IDLE_HI_VOLTAGE = TSI_BASE_USBC_EL(0x11) +TSI_USBC_EL_VBUS_LOW_VOLTAGE = TSI_BASE_USBC_EL(0x12) +TSI_USBC_EL_VBUS_HI_VOLTAGE = TSI_BASE_USBC_EL(0x13) +TSI_USBC_EL_VBUS_CURRENT_MAX_DEV = TSI_BASE_USBC_EL(0x14) +TSI_USBC_EL_GND_CURRENT_MAX_DEV = TSI_BASE_USBC_EL(0x15) +TSI_USBC_EL_PWR_MEASURE_DELAY = TSI_BASE_USBC_EL(0x16) +TSI_USBC_EL_MIN_DUT_CURRENT = TSI_BASE_USBC_EL(0x17) +TSI_USBC_EL_RES_ON_DELAY = TSI_BASE_USBC_EL(0x18) +TSI_USBC_EL_CC_MEASURE_DELAY = TSI_BASE_USBC_EL(0x19) + +TSI_DP_LTT_TIMEOUT = TSI_BASE_DP_LTT(0x00) +TSI_DP_LTT_MAX_LANE_COUNT = TSI_BASE_DP_LTT(0x01) +TSI_DP_LTT_MAX_RATE = TSI_BASE_DP_LTT(0x02) +TSI_DP_LTT_HPD_PULSE_DURATION = TSI_BASE_DP_LTT(0x05) +TSI_DP_LTT_LT_START_TIMEOUT = TSI_BASE_DP_LTT(0x06) +TSI_DP_LTT_TEST_LOOP_DELAY = TSI_BASE_DP_LTT(0x07) + +TSI_DP14_SRCCTS_TIMEOUT = TSI_BASE_DP14_SRCCTS(0) +TSI_DP14_SRCCTS_MAX_LANES = TSI_BASE_DP14_SRCCTS(1) +TSI_DP14_SRCCTS_MAX_LINK_RATE = TSI_BASE_DP14_SRCCTS(2) +TSI_DP14_SRCCTS_DUT_CAPS = TSI_BASE_DP14_SRCCTS(3) +TSI_DP14_SRCCTS_DUT_TA = TSI_BASE_DP14_SRCCTS(4) +TSI_DP14_SRCCTS_LONG_HPD_PULSE = TSI_BASE_DP14_SRCCTS(5) +TSI_DP14_SRCCTS_LT_START_TIMEOUT = TSI_BASE_DP14_SRCCTS(6) +TSI_DP14_SRCCTS_TEST_CYCLE_DELAY = TSI_BASE_DP14_SRCCTS(7) +TSI_DP14_SRCCTS_COLOR_FORMATS = TSI_BASE_DP14_SRCCTS(8) +TSI_DP14_SRCCTS_FAILSAFE_MODE = TSI_BASE_DP14_SRCCTS(9) +TSI_DP14_SRCCTS_MAX_RESOLUTION = TSI_BASE_DP14_SRCCTS(10) +TSI_DP14_SRCCTS_DUT_LINK_RATES = TSI_BASE_DP14_SRCCTS(11) +TSI_DP14_SRCCTS_LTTPR_DEVICE_COUNT = TSI_BASE_DP14_SRCCTS(12) +TSI_DP14_SRCCTS_MIN_SAMPLE_RATE = TSI_BASE_DP14_SRCCTS(16) +TSI_DP14_SRCCTS_MAX_SAMPLE_RATE = TSI_BASE_DP14_SRCCTS(17) +TSI_DP14_SRCCTS_CHANNELS1 = TSI_BASE_DP14_SRCCTS(18) +TSI_DP14_SRCCTS_CH_ALLOC1 = TSI_BASE_DP14_SRCCTS(19) +TSI_DP14_SRCCTS_CHANNELS2 = TSI_BASE_DP14_SRCCTS(20) +TSI_DP14_SRCCTS_CH_ALLOC2 = TSI_BASE_DP14_SRCCTS(21) +TSI_DP14_SRCCTS_CHANNELS3 = TSI_BASE_DP14_SRCCTS(22) +TSI_DP14_SRCCTS_CH_ALLOC3 = TSI_BASE_DP14_SRCCTS(23) +TSI_DP14_SRCCTS_CHANNELS4 = TSI_BASE_DP14_SRCCTS(24) +TSI_DP14_SRCCTS_CH_ALLOC4 = TSI_BASE_DP14_SRCCTS(25) +TSI_DP14_SRCCTS_AUDIO_TEST_PATTERN = TSI_BASE_DP14_SRCCTS(26) +TSI_DP14_SRCCTS_EDID_SAMPLE_SIZE = TSI_BASE_DP14_SRCCTS(27) +TSI_DP14_SRCCTS_1L_MOST_PACKED = TSI_BASE_DP14_SRCCTS(32) +TSI_DP14_SRCCTS_2L_MOST_PACKED = TSI_BASE_DP14_SRCCTS(33) +TSI_DP14_SRCCTS_4L_MOST_PACKED = TSI_BASE_DP14_SRCCTS(34) +TSI_DP14_SRCCTS_1L_RBB = TSI_BASE_DP14_SRCCTS(35) +TSI_DP14_SRCCTS_2L_RBB = TSI_BASE_DP14_SRCCTS(36) +TSI_DP14_SRCCTS_4L_RBB = TSI_BASE_DP14_SRCCTS(37) +TSI_DP14_SRCCTS_1L_HBR = TSI_BASE_DP14_SRCCTS(38) +TSI_DP14_SRCCTS_2L_HBR = TSI_BASE_DP14_SRCCTS(39) +TSI_DP14_SRCCTS_4L_HBR = TSI_BASE_DP14_SRCCTS(40) +TSI_DP14_SRCCTS_1L_HBR2 = TSI_BASE_DP14_SRCCTS(41) +TSI_DP14_SRCCTS_2L_HBR2 = TSI_BASE_DP14_SRCCTS(42) +TSI_DP14_SRCCTS_4L_HBR2 = TSI_BASE_DP14_SRCCTS(43) +TSI_DP14_SRCCTS_1L_HBR3 = TSI_BASE_DP14_SRCCTS(44) +TSI_DP14_SRCCTS_2L_HBR3 = TSI_BASE_DP14_SRCCTS(45) +TSI_DP14_SRCCTS_4L_HBR3 = TSI_BASE_DP14_SRCCTS(46) +TSI_DP14_SRCCTS_COLOR_MODE_0 = TSI_BASE_DP14_SRCCTS(56) +TSI_DP14_SRCCTS_COLOR_MODE_1 = TSI_BASE_DP14_SRCCTS(57) +TSI_DP14_SRCCTS_COLOR_MODE_2 = TSI_BASE_DP14_SRCCTS(58) +TSI_DP14_SRCCTS_COLOR_MODE_3 = TSI_BASE_DP14_SRCCTS(59) +TSI_DP14_SRCCTS_COLOR_MODE_4 = TSI_BASE_DP14_SRCCTS(60) +TSI_DP14_SRCCTS_COLOR_MODE_5 = TSI_BASE_DP14_SRCCTS(61) +TSI_DP14_SRCCTS_COLOR_MODE_6 = TSI_BASE_DP14_SRCCTS(62) +TSI_DP14_SRCCTS_COLOR_MODE_7 = TSI_BASE_DP14_SRCCTS(63) +TSI_DP14_SRCCTS_COLOR_MODE_8 = TSI_BASE_DP14_SRCCTS(64) +TSI_DP14_SRCCTS_COLOR_MODE_9 = TSI_BASE_DP14_SRCCTS(65) +TSI_DP14_SRCCTS_COLOR_MODE_10 = TSI_BASE_DP14_SRCCTS(66) +TSI_DP14_SRCCTS_COLOR_MODE_11 = TSI_BASE_DP14_SRCCTS(67) +TSI_DP14_SRCCTS_COLOR_MODE_12 = TSI_BASE_DP14_SRCCTS(68) +TSI_DP14_SRCCTS_COLOR_MODE_13 = TSI_BASE_DP14_SRCCTS(69) +TSI_DP14_SRCCTS_COLOR_MODE_14 = TSI_BASE_DP14_SRCCTS(70) +TSI_DP14_SRCCTS_COLOR_MODE_15 = TSI_BASE_DP14_SRCCTS(71) +TSI_DP14_SRCCTS_COLOR_MODE_16 = TSI_BASE_DP14_SRCCTS(72) +TSI_DP14_SRCCTS_COLOR_MODE_17 = TSI_BASE_DP14_SRCCTS(73) +TSI_DP14_SRCCTS_COLOR_MODE_18 = TSI_BASE_DP14_SRCCTS(74) +TSI_DP14_SRCCTS_COLOR_MODE_19 = TSI_BASE_DP14_SRCCTS(75) +TSI_DP14_SRCCTS_COLOR_MODE_20 = TSI_BASE_DP14_SRCCTS(76) +TSI_DP14_SRCCTS_COLOR_MODE_21 = TSI_BASE_DP14_SRCCTS(77) +TSI_DP14_SRCCTS_COLOR_MODE_22 = TSI_BASE_DP14_SRCCTS(78) +TSI_DP14_SRCCTS_COLOR_MODE_23 = TSI_BASE_DP14_SRCCTS(79) +TSI_DP14_SRCCTS_COLOR_MODE_24 = TSI_BASE_DP14_SRCCTS(80) +TSI_DP14_SRCCTS_COLOR_MODE_25 = TSI_BASE_DP14_SRCCTS(81) +TSI_DP14_SRCCTS_COLOR_MODE_26 = TSI_BASE_DP14_SRCCTS(82) +TSI_DP14_SRCCTS_COLOR_MODE_27 = TSI_BASE_DP14_SRCCTS(83) +TSI_DP14_SRCCTS_COLOR_MODE_28 = TSI_BASE_DP14_SRCCTS(84) +TSI_DP14_SRCCTS_COLOR_MODE_29 = TSI_BASE_DP14_SRCCTS(85) +TSI_DP14_SRCCTS_COLOR_MODE_30 = TSI_BASE_DP14_SRCCTS(86) +TSI_DP14_SRCCTS_COLOR_MODE_31 = TSI_BASE_DP14_SRCCTS(87) +TSI_DP14_SRCCTS_DSC_DUT_MAX_SLICE = TSI_BASE_DP14_SRCCTS(88) +TSI_DP14_SRCCTS_DSC_VERSION = TSI_BASE_DP14_SRCCTS(89) +TSI_DP14_SRCCTS_DSC_VMT_0_ID = TSI_BASE_DP14_SRCCTS(90) +TSI_DP14_SRCCTS_DSC_VMT_0_LC_LR = TSI_BASE_DP14_SRCCTS(91) +TSI_DP14_SRCCTS_DSC_VMT_0_TIMINGS = TSI_BASE_DP14_SRCCTS(92) +TSI_DP14_SRCCTS_DSC_VMT_1_ID = TSI_BASE_DP14_SRCCTS(93) +TSI_DP14_SRCCTS_DSC_VMT_1_LC_LR = TSI_BASE_DP14_SRCCTS(94) +TSI_DP14_SRCCTS_DSC_VMT_1_TIMINGS = TSI_BASE_DP14_SRCCTS(95) +TSI_DP14_SRCCTS_DSC_VMT_2_ID = TSI_BASE_DP14_SRCCTS(96) +TSI_DP14_SRCCTS_DSC_VMT_2_LC_LR = TSI_BASE_DP14_SRCCTS(97) +TSI_DP14_SRCCTS_DSC_VMT_2_TIMINGS = TSI_BASE_DP14_SRCCTS(98) +TSI_DP14_SRCCTS_DSC_VMT_3_ID = TSI_BASE_DP14_SRCCTS(99) +TSI_DP14_SRCCTS_DSC_VMT_3_LC_LR = TSI_BASE_DP14_SRCCTS(100) +TSI_DP14_SRCCTS_DSC_VMT_3_TIMINGS = TSI_BASE_DP14_SRCCTS(101) +TSI_DP14_SRCCTS_DSC_VMT_4_ID = TSI_BASE_DP14_SRCCTS(102) +TSI_DP14_SRCCTS_DSC_VMT_4_LC_LR = TSI_BASE_DP14_SRCCTS(103) +TSI_DP14_SRCCTS_DSC_VMT_4_TIMINGS = TSI_BASE_DP14_SRCCTS(104) +TSI_DP14_SRCCTS_DSC_VMT_5_ID = TSI_BASE_DP14_SRCCTS(105) +TSI_DP14_SRCCTS_DSC_VMT_5_LC_LR = TSI_BASE_DP14_SRCCTS(106) +TSI_DP14_SRCCTS_DSC_VMT_5_TIMINGS = TSI_BASE_DP14_SRCCTS(107) +TSI_DP14_SRCCTS_DSC_VMT_6_ID = TSI_BASE_DP14_SRCCTS(108) +TSI_DP14_SRCCTS_DSC_VMT_6_LC_LR = TSI_BASE_DP14_SRCCTS(109) +TSI_DP14_SRCCTS_DSC_VMT_6_TIMINGS = TSI_BASE_DP14_SRCCTS(110) +TSI_DP14_SRCCTS_DSC_VMT_7_ID = TSI_BASE_DP14_SRCCTS(111) +TSI_DP14_SRCCTS_DSC_VMT_7_LC_LR = TSI_BASE_DP14_SRCCTS(112) +TSI_DP14_SRCCTS_DSC_VMT_7_TIMINGS = TSI_BASE_DP14_SRCCTS(113) +TSI_DP14_SRCCTS_DSC_VMT_8_ID = TSI_BASE_DP14_SRCCTS(114) +TSI_DP14_SRCCTS_DSC_VMT_8_LC_LR = TSI_BASE_DP14_SRCCTS(115) +TSI_DP14_SRCCTS_DSC_VMT_8_TIMINGS = TSI_BASE_DP14_SRCCTS(116) +TSI_DP14_SRCCTS_DSC_VMT_9_ID = TSI_BASE_DP14_SRCCTS(117) +TSI_DP14_SRCCTS_DSC_VMT_9_LC_LR = TSI_BASE_DP14_SRCCTS(118) +TSI_DP14_SRCCTS_DSC_VMT_9_TIMINGS = TSI_BASE_DP14_SRCCTS(119) +TSI_DP14_SRCCTS_DSC_VMT_10_ID = TSI_BASE_DP14_SRCCTS(120) +TSI_DP14_SRCCTS_DSC_VMT_10_LC_LR = TSI_BASE_DP14_SRCCTS(121) +TSI_DP14_SRCCTS_DSC_VMT_10_TIMINGS = TSI_BASE_DP14_SRCCTS(122) +TSI_DP14_SRCCTS_DSC_VMT_11_ID = TSI_BASE_DP14_SRCCTS(123) +TSI_DP14_SRCCTS_DSC_VMT_11_LC_LR = TSI_BASE_DP14_SRCCTS(124) +TSI_DP14_SRCCTS_DSC_VMT_11_TIMINGS = TSI_BASE_DP14_SRCCTS(125) +TSI_DP14_SRCCTS_DSC_COLOR_FORMATS = TSI_BASE_DP14_SRCCTS(126) +TSI_DP14_SRCCTS_DSC_COLOR_MODE_0 = TSI_BASE_DP14_SRCCTS(127) +TSI_DP14_SRCCTS_DSC_COLOR_MODE_1 = TSI_BASE_DP14_SRCCTS(128) +TSI_DP14_SRCCTS_DSC_COLOR_MODE_2 = TSI_BASE_DP14_SRCCTS(129) +TSI_DP14_SRCCTS_DSC_COLOR_MODE_3 = TSI_BASE_DP14_SRCCTS(130) +TSI_DP14_SRCCTS_DSC_COLOR_MODE_4 = TSI_BASE_DP14_SRCCTS(131) +TSI_DP14_SRCCTS_DSC_COLOR_MODE_5 = TSI_BASE_DP14_SRCCTS(132) +TSI_DP14_SRCCTS_DSC_COLOR_MODE_6 = TSI_BASE_DP14_SRCCTS(133) +TSI_DP14_SRCCTS_DSC_COLOR_MODE_7 = TSI_BASE_DP14_SRCCTS(134) +TSI_DP14_SRCCTS_DSC_COLOR_MODE_8 = TSI_BASE_DP14_SRCCTS(135) +TSI_DP14_SRCCTS_DSC_COLOR_MODE_9 = TSI_BASE_DP14_SRCCTS(136) +TSI_DP14_SRCCTS_DSC_COLOR_MODE_10 = TSI_BASE_DP14_SRCCTS(137) +TSI_DP14_SRCCTS_DSC_COLOR_MODE_11 = TSI_BASE_DP14_SRCCTS(138) +TSI_DP14_SRCCTS_DSC_COLOR_MODE_12 = TSI_BASE_DP14_SRCCTS(139) +TSI_DP14_SRCCTS_DSC_COLOR_MODE_13 = TSI_BASE_DP14_SRCCTS(140) +TSI_DP14_SRCCTS_DSC_COLOR_MODE_14 = TSI_BASE_DP14_SRCCTS(141) +TSI_DP14_SRCCTS_DSC_COLOR_MODE_15 = TSI_BASE_DP14_SRCCTS(142) +TSI_DP14_SRCCTS_DSC_COLOR_MODE_16 = TSI_BASE_DP14_SRCCTS(143) +TSI_DP14_SRCCTS_DSC_COLOR_MODE_17 = TSI_BASE_DP14_SRCCTS(144) +TSI_DP14_SRCCTS_DSC_COLOR_MODE_18 = TSI_BASE_DP14_SRCCTS(145) +TSI_DP14_SRCCTS_DSC_COLOR_MODE_19 = TSI_BASE_DP14_SRCCTS(146) +TSI_DP14_SRCCTS_DSC_COLOR_MODE_20 = TSI_BASE_DP14_SRCCTS(147) +TSI_DP14_SRCCTS_DSC_COLOR_MODE_21 = TSI_BASE_DP14_SRCCTS(148) +TSI_DP14_SRCCTS_DSC_COLOR_MODE_22 = TSI_BASE_DP14_SRCCTS(149) +TSI_DP14_SRCCTS_DSC_COLOR_MODE_23 = TSI_BASE_DP14_SRCCTS(150) +TSI_DP14_SRCCTS_DSC_COLOR_MODE_24 = TSI_BASE_DP14_SRCCTS(151) +TSI_DP14_SRCCTS_DSC_COLOR_MODE_25 = TSI_BASE_DP14_SRCCTS(152) +TSI_DP14_SRCCTS_DSC_COLOR_MODE_26 = TSI_BASE_DP14_SRCCTS(153) +TSI_DP14_SRCCTS_DSC_COLOR_MODE_27 = TSI_BASE_DP14_SRCCTS(154) +TSI_DP14_SRCCTS_DSC_COLOR_MODE_28 = TSI_BASE_DP14_SRCCTS(155) +TSI_DP14_SRCCTS_DSC_COLOR_MODE_29 = TSI_BASE_DP14_SRCCTS(156) +TSI_DP14_SRCCTS_DSC_COLOR_MODE_30 = TSI_BASE_DP14_SRCCTS(157) +TSI_DP14_SRCCTS_DSC_COLOR_MODE_31 = TSI_BASE_DP14_SRCCTS(158) +TSI_DP14_SRCCTS_MAX_STREAM_HACTIVE = TSI_BASE_DP14_SRCCTS(159) +TSI_DP14_SRCCTS_MAX_STREAM_VACTIVE = TSI_BASE_DP14_SRCCTS(160) +TSI_DP14_SRCCTS_MAX_STREAM_PIXEL_CLOCK = TSI_BASE_DP14_SRCCTS(161) +TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_0 = TSI_BASE_DP14_SRCCTS(162) +TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_1 = TSI_BASE_DP14_SRCCTS(163) +TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_2 = TSI_BASE_DP14_SRCCTS(164) +TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_3 = TSI_BASE_DP14_SRCCTS(165) +TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_4 = TSI_BASE_DP14_SRCCTS(166) +TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_5 = TSI_BASE_DP14_SRCCTS(167) +TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_6 = TSI_BASE_DP14_SRCCTS(168) +TSI_DP14_SRCCTS_TESTED_AUDIO_MODE_7 = TSI_BASE_DP14_SRCCTS(169) +TSI_DP14_SRCCTS_DMT_TIMING = TSI_BASE_DP14_SRCCTS(170) +TSI_DP14_SRCCTS_CTA_TIMING = TSI_BASE_DP14_SRCCTS(171) +TSI_DP14_SRCCTS_CVT_TIMING = TSI_BASE_DP14_SRCCTS(172) +TSI_DP14_SRCCTS_AS_DUT_CAPS = TSI_BASE_DP14_SRCCTS(173) +TSI_DP14_SRCCTS_AS_RANGE_MIN_RATE = TSI_BASE_DP14_SRCCTS(174) +TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_0 = TSI_BASE_DP14_SRCCTS(175) +TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_1 = TSI_BASE_DP14_SRCCTS(176) +TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_2 = TSI_BASE_DP14_SRCCTS(177) +TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_3 = TSI_BASE_DP14_SRCCTS(178) +TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_4 = TSI_BASE_DP14_SRCCTS(179) +TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_5 = TSI_BASE_DP14_SRCCTS(180) +TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_6 = TSI_BASE_DP14_SRCCTS(181) +TSI_DP14_SRCCTS_AS_RANGE_MAX_RATE_7 = TSI_BASE_DP14_SRCCTS(182) + +TSI_DP14_SINKCTS_TIMEOUT = TSI_BASE_DP14_SINKCTS(0) +TSI_DP14_SINKCTS_DSC_VM_0 = TSI_BASE_DP14_SINKCTS(1) +TSI_DP14_SINKCTS_DSC_VM_T_0 = TSI_BASE_DP14_SINKCTS(2) +TSI_DP14_SINKCTS_DSC_VM_1 = TSI_BASE_DP14_SINKCTS(3) +TSI_DP14_SINKCTS_DSC_VM_T_1 = TSI_BASE_DP14_SINKCTS(4) +TSI_DP14_SINKCTS_DSC_VM_2 = TSI_BASE_DP14_SINKCTS(5) +TSI_DP14_SINKCTS_DSC_VM_T_2 = TSI_BASE_DP14_SINKCTS(6) +TSI_DP14_SINKCTS_DSC_VM_3 = TSI_BASE_DP14_SINKCTS(7) +TSI_DP14_SINKCTS_DSC_VM_T_3 = TSI_BASE_DP14_SINKCTS(8) +TSI_DP14_SINKCTS_DSC_VM_4 = TSI_BASE_DP14_SINKCTS(9) +TSI_DP14_SINKCTS_DSC_VM_T_4 = TSI_BASE_DP14_SINKCTS(10) +TSI_DP14_SINKCTS_DSC_VM_5 = TSI_BASE_DP14_SINKCTS(11) +TSI_DP14_SINKCTS_DSC_VM_T_5 = TSI_BASE_DP14_SINKCTS(12) +TSI_DP14_SINKCTS_DSC_VM_6 = TSI_BASE_DP14_SINKCTS(13) +TSI_DP14_SINKCTS_DSC_VM_T_6 = TSI_BASE_DP14_SINKCTS(14) +TSI_DP14_SINKCTS_DSC_VM_7 = TSI_BASE_DP14_SINKCTS(15) +TSI_DP14_SINKCTS_DSC_VM_T_7 = TSI_BASE_DP14_SINKCTS(16) +TSI_DP14_SINKCTS_DSC_VM_8 = TSI_BASE_DP14_SINKCTS(17) +TSI_DP14_SINKCTS_DSC_VM_T_8 = TSI_BASE_DP14_SINKCTS(18) +TSI_DP14_SINKCTS_DSC_VM_9 = TSI_BASE_DP14_SINKCTS(19) +TSI_DP14_SINKCTS_DSC_VM_T_9 = TSI_BASE_DP14_SINKCTS(20) +TSI_DP14_SINKCTS_DSC_VM_10 = TSI_BASE_DP14_SINKCTS(21) +TSI_DP14_SINKCTS_DSC_VM_T_10 = TSI_BASE_DP14_SINKCTS(22) +TSI_DP14_SINKCTS_DSC_VM_11 = TSI_BASE_DP14_SINKCTS(23) +TSI_DP14_SINKCTS_DSC_VM_T_11 = TSI_BASE_DP14_SINKCTS(24) +TSI_DP14_SINKCTS_DSC_SOURCE = TSI_BASE_DP14_SINKCTS(25) +TSI_DP14_SINKCTS_SUPPORT_444CRC = TSI_BASE_DP14_SINKCTS(26) +TSI_DP14_SINKCTS_PACKET_SOURCE = TSI_BASE_DP14_SINKCTS(27) +TSI_DP14_SINKCTS_VIDEO_MODE_0 = TSI_BASE_DP14_SINKCTS(28) +TSI_DP14_SINKCTS_VIDEO_MODE_1 = TSI_BASE_DP14_SINKCTS(29) +TSI_DP14_SINKCTS_VIDEO_MODE_2 = TSI_BASE_DP14_SINKCTS(30) +TSI_DP14_SINKCTS_VIDEO_MODE_3 = TSI_BASE_DP14_SINKCTS(31) +TSI_DP14_SINKCTS_VIDEO_MODE_4 = TSI_BASE_DP14_SINKCTS(32) +TSI_DP14_SINKCTS_VIDEO_MODE_5 = TSI_BASE_DP14_SINKCTS(33) +TSI_DP14_SINKCTS_VIDEO_MODE_6 = TSI_BASE_DP14_SINKCTS(34) +TSI_DP14_SINKCTS_VIDEO_MODE_7 = TSI_BASE_DP14_SINKCTS(35) +TSI_DP14_SINKCTS_VIDEO_MODE_8 = TSI_BASE_DP14_SINKCTS(36) +TSI_DP14_SINKCTS_VIDEO_MODE_9 = TSI_BASE_DP14_SINKCTS(37) +TSI_DP14_SINKCTS_VIDEO_MODE_10 = TSI_BASE_DP14_SINKCTS(38) +TSI_DP14_SINKCTS_VIDEO_MODE_11 = TSI_BASE_DP14_SINKCTS(39) +TSI_DP14_SINKCTS_VIDEO_MODE_12 = TSI_BASE_DP14_SINKCTS(40) +TSI_DP14_SINKCTS_VIDEO_MODE_13 = TSI_BASE_DP14_SINKCTS(41) +TSI_DP14_SINKCTS_VIDEO_MODE_14 = TSI_BASE_DP14_SINKCTS(42) +TSI_DP14_SINKCTS_VIDEO_MODE_15 = TSI_BASE_DP14_SINKCTS(43) +TSI_DP14_SINKCTS_VIDEO_MODE_16 = TSI_BASE_DP14_SINKCTS(44) +TSI_DP14_SINKCTS_VISUAL_TEST_CHECK = TSI_BASE_DP14_SINKCTS(45) + +TSI_DP20_SRCCTS_TIMEOUT = TSI_BASE_DP20_SRCCTS(0) +TSI_DP20_SRCCTS_MAX_LANES = TSI_BASE_DP20_SRCCTS(1) +TSI_DP20_SRCCTS_MAX_LINK_RATE = TSI_BASE_DP20_SRCCTS(2) +TSI_DP20_SRCCTS_DUT_CAPS = TSI_BASE_DP20_SRCCTS(3) +TSI_DP20_SRCCTS_DUT_TA = TSI_BASE_DP20_SRCCTS(4) +TSI_DP20_SRCCTS_LONG_HPD_PULSE = TSI_BASE_DP20_SRCCTS(5) +TSI_DP20_SRCCTS_LT_START_TIMEOUT = TSI_BASE_DP20_SRCCTS(6) +TSI_DP20_SRCCTS_TEST_CYCLE_DELAY = TSI_BASE_DP20_SRCCTS(7) +TSI_DP20_SRCCTS_COLOR_FORMATS = TSI_BASE_DP20_SRCCTS(8) +TSI_DP20_SRCCTS_FAILSAFE_MODE = TSI_BASE_DP20_SRCCTS(9) +TSI_DP20_SRCCTS_MAX_RESOLUTION = TSI_BASE_DP20_SRCCTS(10) +TSI_DP20_SRCCTS_DUT_LINK_RATES = TSI_BASE_DP20_SRCCTS(11) +TSI_DP20_SRCCTS_LTTPR_DEVICE_COUNT = TSI_BASE_DP20_SRCCTS(12) +TSI_DP20_SRCCTS_MAX_LINK_BW_POLICY = TSI_BASE_DP20_SRCCTS(13) +TSI_DP20_SRCCTS_MIN_SAMPLE_RATE = TSI_BASE_DP20_SRCCTS(16) +TSI_DP20_SRCCTS_MAX_SAMPLE_RATE = TSI_BASE_DP20_SRCCTS(17) +TSI_DP20_SRCCTS_CHANNELS1 = TSI_BASE_DP20_SRCCTS(18) +TSI_DP20_SRCCTS_CH_ALLOC1 = TSI_BASE_DP20_SRCCTS(19) +TSI_DP20_SRCCTS_CHANNELS2 = TSI_BASE_DP20_SRCCTS(20) +TSI_DP20_SRCCTS_CH_ALLOC2 = TSI_BASE_DP20_SRCCTS(21) +TSI_DP20_SRCCTS_CHANNELS3 = TSI_BASE_DP20_SRCCTS(22) +TSI_DP20_SRCCTS_CH_ALLOC3 = TSI_BASE_DP20_SRCCTS(23) +TSI_DP20_SRCCTS_CHANNELS4 = TSI_BASE_DP20_SRCCTS(24) +TSI_DP20_SRCCTS_CH_ALLOC4 = TSI_BASE_DP20_SRCCTS(25) +TSI_DP20_SRCCTS_AUDIO_TEST_PATTERN = TSI_BASE_DP20_SRCCTS(26) +TSI_DP20_SRCCTS_EDID_SAMPLE_SIZE = TSI_BASE_DP20_SRCCTS(27) +TSI_DP20_SRCCTS_1L_MOST_PACKED = TSI_BASE_DP20_SRCCTS(32) +TSI_DP20_SRCCTS_2L_MOST_PACKED = TSI_BASE_DP20_SRCCTS(33) +TSI_DP20_SRCCTS_4L_MOST_PACKED = TSI_BASE_DP20_SRCCTS(34) +TSI_DP20_SRCCTS_1L_RBB = TSI_BASE_DP20_SRCCTS(35) +TSI_DP20_SRCCTS_2L_RBB = TSI_BASE_DP20_SRCCTS(36) +TSI_DP20_SRCCTS_4L_RBB = TSI_BASE_DP20_SRCCTS(37) +TSI_DP20_SRCCTS_1L_HBR = TSI_BASE_DP20_SRCCTS(38) +TSI_DP20_SRCCTS_2L_HBR = TSI_BASE_DP20_SRCCTS(39) +TSI_DP20_SRCCTS_4L_HBR = TSI_BASE_DP20_SRCCTS(40) +TSI_DP20_SRCCTS_1L_HBR2 = TSI_BASE_DP20_SRCCTS(41) +TSI_DP20_SRCCTS_2L_HBR2 = TSI_BASE_DP20_SRCCTS(42) +TSI_DP20_SRCCTS_4L_HBR2 = TSI_BASE_DP20_SRCCTS(43) +TSI_DP20_SRCCTS_1L_HBR3 = TSI_BASE_DP20_SRCCTS(44) +TSI_DP20_SRCCTS_2L_HBR3 = TSI_BASE_DP20_SRCCTS(45) +TSI_DP20_SRCCTS_4L_HBR3 = TSI_BASE_DP20_SRCCTS(46) +TSI_DP20_SRCCTS_COLOR_MODE_0 = TSI_BASE_DP20_SRCCTS(56) +TSI_DP20_SRCCTS_COLOR_MODE_1 = TSI_BASE_DP20_SRCCTS(57) +TSI_DP20_SRCCTS_COLOR_MODE_2 = TSI_BASE_DP20_SRCCTS(58) +TSI_DP20_SRCCTS_COLOR_MODE_3 = TSI_BASE_DP20_SRCCTS(59) +TSI_DP20_SRCCTS_COLOR_MODE_4 = TSI_BASE_DP20_SRCCTS(60) +TSI_DP20_SRCCTS_COLOR_MODE_5 = TSI_BASE_DP20_SRCCTS(61) +TSI_DP20_SRCCTS_COLOR_MODE_6 = TSI_BASE_DP20_SRCCTS(62) +TSI_DP20_SRCCTS_COLOR_MODE_7 = TSI_BASE_DP20_SRCCTS(63) +TSI_DP20_SRCCTS_COLOR_MODE_8 = TSI_BASE_DP20_SRCCTS(64) +TSI_DP20_SRCCTS_COLOR_MODE_9 = TSI_BASE_DP20_SRCCTS(65) +TSI_DP20_SRCCTS_COLOR_MODE_10 = TSI_BASE_DP20_SRCCTS(66) +TSI_DP20_SRCCTS_COLOR_MODE_11 = TSI_BASE_DP20_SRCCTS(67) +TSI_DP20_SRCCTS_COLOR_MODE_12 = TSI_BASE_DP20_SRCCTS(68) +TSI_DP20_SRCCTS_COLOR_MODE_13 = TSI_BASE_DP20_SRCCTS(69) +TSI_DP20_SRCCTS_COLOR_MODE_14 = TSI_BASE_DP20_SRCCTS(70) +TSI_DP20_SRCCTS_COLOR_MODE_15 = TSI_BASE_DP20_SRCCTS(71) +TSI_DP20_SRCCTS_COLOR_MODE_16 = TSI_BASE_DP20_SRCCTS(72) +TSI_DP20_SRCCTS_COLOR_MODE_17 = TSI_BASE_DP20_SRCCTS(73) +TSI_DP20_SRCCTS_COLOR_MODE_18 = TSI_BASE_DP20_SRCCTS(74) +TSI_DP20_SRCCTS_COLOR_MODE_19 = TSI_BASE_DP20_SRCCTS(75) +TSI_DP20_SRCCTS_COLOR_MODE_20 = TSI_BASE_DP20_SRCCTS(76) +TSI_DP20_SRCCTS_COLOR_MODE_21 = TSI_BASE_DP20_SRCCTS(77) +TSI_DP20_SRCCTS_COLOR_MODE_22 = TSI_BASE_DP20_SRCCTS(78) +TSI_DP20_SRCCTS_COLOR_MODE_23 = TSI_BASE_DP20_SRCCTS(79) +TSI_DP20_SRCCTS_COLOR_MODE_24 = TSI_BASE_DP20_SRCCTS(80) +TSI_DP20_SRCCTS_COLOR_MODE_25 = TSI_BASE_DP20_SRCCTS(81) +TSI_DP20_SRCCTS_COLOR_MODE_26 = TSI_BASE_DP20_SRCCTS(82) +TSI_DP20_SRCCTS_COLOR_MODE_27 = TSI_BASE_DP20_SRCCTS(83) +TSI_DP20_SRCCTS_COLOR_MODE_28 = TSI_BASE_DP20_SRCCTS(84) +TSI_DP20_SRCCTS_COLOR_MODE_29 = TSI_BASE_DP20_SRCCTS(85) +TSI_DP20_SRCCTS_COLOR_MODE_30 = TSI_BASE_DP20_SRCCTS(86) +TSI_DP20_SRCCTS_COLOR_MODE_31 = TSI_BASE_DP20_SRCCTS(87) +TSI_DP20_SRCCTS_DSC_DUT_MAX_SLICE = TSI_BASE_DP20_SRCCTS(88) +TSI_DP20_SRCCTS_DSC_VERSION = TSI_BASE_DP20_SRCCTS(89) +TSI_DP20_SRCCTS_DSC_VMT_0_ID = TSI_BASE_DP20_SRCCTS(90) +TSI_DP20_SRCCTS_DSC_VMT_0_LC_LR = TSI_BASE_DP20_SRCCTS(91) +TSI_DP20_SRCCTS_DSC_VMT_0_TIMINGS = TSI_BASE_DP20_SRCCTS(92) +TSI_DP20_SRCCTS_DSC_VMT_1_ID = TSI_BASE_DP20_SRCCTS(93) +TSI_DP20_SRCCTS_DSC_VMT_1_LC_LR = TSI_BASE_DP20_SRCCTS(94) +TSI_DP20_SRCCTS_DSC_VMT_1_TIMINGS = TSI_BASE_DP20_SRCCTS(95) +TSI_DP20_SRCCTS_DSC_VMT_2_ID = TSI_BASE_DP20_SRCCTS(96) +TSI_DP20_SRCCTS_DSC_VMT_2_LC_LR = TSI_BASE_DP20_SRCCTS(97) +TSI_DP20_SRCCTS_DSC_VMT_2_TIMINGS = TSI_BASE_DP20_SRCCTS(98) +TSI_DP20_SRCCTS_DSC_VMT_3_ID = TSI_BASE_DP20_SRCCTS(99) +TSI_DP20_SRCCTS_DSC_VMT_3_LC_LR = TSI_BASE_DP20_SRCCTS(100) +TSI_DP20_SRCCTS_DSC_VMT_3_TIMINGS = TSI_BASE_DP20_SRCCTS(101) +TSI_DP20_SRCCTS_DSC_VMT_4_ID = TSI_BASE_DP20_SRCCTS(102) +TSI_DP20_SRCCTS_DSC_VMT_4_LC_LR = TSI_BASE_DP20_SRCCTS(103) +TSI_DP20_SRCCTS_DSC_VMT_4_TIMINGS = TSI_BASE_DP20_SRCCTS(104) +TSI_DP20_SRCCTS_DSC_VMT_5_ID = TSI_BASE_DP20_SRCCTS(105) +TSI_DP20_SRCCTS_DSC_VMT_5_LC_LR = TSI_BASE_DP20_SRCCTS(106) +TSI_DP20_SRCCTS_DSC_VMT_5_TIMINGS = TSI_BASE_DP20_SRCCTS(107) +TSI_DP20_SRCCTS_DSC_VMT_6_ID = TSI_BASE_DP20_SRCCTS(108) +TSI_DP20_SRCCTS_DSC_VMT_6_LC_LR = TSI_BASE_DP20_SRCCTS(109) +TSI_DP20_SRCCTS_DSC_VMT_6_TIMINGS = TSI_BASE_DP20_SRCCTS(110) +TSI_DP20_SRCCTS_DSC_VMT_7_ID = TSI_BASE_DP20_SRCCTS(111) +TSI_DP20_SRCCTS_DSC_VMT_7_LC_LR = TSI_BASE_DP20_SRCCTS(112) +TSI_DP20_SRCCTS_DSC_VMT_7_TIMINGS = TSI_BASE_DP20_SRCCTS(113) +TSI_DP20_SRCCTS_DSC_VMT_8_ID = TSI_BASE_DP20_SRCCTS(114) +TSI_DP20_SRCCTS_DSC_VMT_8_LC_LR = TSI_BASE_DP20_SRCCTS(115) +TSI_DP20_SRCCTS_DSC_VMT_8_TIMINGS = TSI_BASE_DP20_SRCCTS(116) +TSI_DP20_SRCCTS_DSC_VMT_9_ID = TSI_BASE_DP20_SRCCTS(117) +TSI_DP20_SRCCTS_DSC_VMT_9_LC_LR = TSI_BASE_DP20_SRCCTS(118) +TSI_DP20_SRCCTS_DSC_VMT_9_TIMINGS = TSI_BASE_DP20_SRCCTS(119) +TSI_DP20_SRCCTS_DSC_VMT_10_ID = TSI_BASE_DP20_SRCCTS(120) +TSI_DP20_SRCCTS_DSC_VMT_10_LC_LR = TSI_BASE_DP20_SRCCTS(121) +TSI_DP20_SRCCTS_DSC_VMT_10_TIMINGS = TSI_BASE_DP20_SRCCTS(122) +TSI_DP20_SRCCTS_DSC_VMT_11_ID = TSI_BASE_DP20_SRCCTS(123) +TSI_DP20_SRCCTS_DSC_VMT_11_LC_LR = TSI_BASE_DP20_SRCCTS(124) +TSI_DP20_SRCCTS_DSC_VMT_11_TIMINGS = TSI_BASE_DP20_SRCCTS(125) +TSI_DP20_SRCCTS_DSC_COLOR_FORMATS = TSI_BASE_DP20_SRCCTS(126) +TSI_DP20_SRCCTS_DSC_COLOR_MODE_0 = TSI_BASE_DP20_SRCCTS(127) +TSI_DP20_SRCCTS_DSC_COLOR_MODE_1 = TSI_BASE_DP20_SRCCTS(128) +TSI_DP20_SRCCTS_DSC_COLOR_MODE_2 = TSI_BASE_DP20_SRCCTS(129) +TSI_DP20_SRCCTS_DSC_COLOR_MODE_3 = TSI_BASE_DP20_SRCCTS(130) +TSI_DP20_SRCCTS_DSC_COLOR_MODE_4 = TSI_BASE_DP20_SRCCTS(131) +TSI_DP20_SRCCTS_DSC_COLOR_MODE_5 = TSI_BASE_DP20_SRCCTS(132) +TSI_DP20_SRCCTS_DSC_COLOR_MODE_6 = TSI_BASE_DP20_SRCCTS(133) +TSI_DP20_SRCCTS_DSC_COLOR_MODE_7 = TSI_BASE_DP20_SRCCTS(134) +TSI_DP20_SRCCTS_DSC_COLOR_MODE_8 = TSI_BASE_DP20_SRCCTS(135) +TSI_DP20_SRCCTS_DSC_COLOR_MODE_9 = TSI_BASE_DP20_SRCCTS(136) +TSI_DP20_SRCCTS_DSC_COLOR_MODE_10 = TSI_BASE_DP20_SRCCTS(137) +TSI_DP20_SRCCTS_DSC_COLOR_MODE_11 = TSI_BASE_DP20_SRCCTS(138) +TSI_DP20_SRCCTS_DSC_COLOR_MODE_12 = TSI_BASE_DP20_SRCCTS(139) +TSI_DP20_SRCCTS_DSC_COLOR_MODE_13 = TSI_BASE_DP20_SRCCTS(140) +TSI_DP20_SRCCTS_DSC_COLOR_MODE_14 = TSI_BASE_DP20_SRCCTS(141) +TSI_DP20_SRCCTS_DSC_COLOR_MODE_15 = TSI_BASE_DP20_SRCCTS(142) +TSI_DP20_SRCCTS_DSC_COLOR_MODE_16 = TSI_BASE_DP20_SRCCTS(143) +TSI_DP20_SRCCTS_DSC_COLOR_MODE_17 = TSI_BASE_DP20_SRCCTS(144) +TSI_DP20_SRCCTS_DSC_COLOR_MODE_18 = TSI_BASE_DP20_SRCCTS(145) +TSI_DP20_SRCCTS_DSC_COLOR_MODE_19 = TSI_BASE_DP20_SRCCTS(146) +TSI_DP20_SRCCTS_DSC_COLOR_MODE_20 = TSI_BASE_DP20_SRCCTS(147) +TSI_DP20_SRCCTS_DSC_COLOR_MODE_21 = TSI_BASE_DP20_SRCCTS(148) +TSI_DP20_SRCCTS_DSC_COLOR_MODE_22 = TSI_BASE_DP20_SRCCTS(149) +TSI_DP20_SRCCTS_DSC_COLOR_MODE_23 = TSI_BASE_DP20_SRCCTS(150) +TSI_DP20_SRCCTS_DSC_COLOR_MODE_24 = TSI_BASE_DP20_SRCCTS(151) +TSI_DP20_SRCCTS_DSC_COLOR_MODE_25 = TSI_BASE_DP20_SRCCTS(152) +TSI_DP20_SRCCTS_DSC_COLOR_MODE_26 = TSI_BASE_DP20_SRCCTS(153) +TSI_DP20_SRCCTS_DSC_COLOR_MODE_27 = TSI_BASE_DP20_SRCCTS(154) +TSI_DP20_SRCCTS_DSC_COLOR_MODE_28 = TSI_BASE_DP20_SRCCTS(155) +TSI_DP20_SRCCTS_DSC_COLOR_MODE_29 = TSI_BASE_DP20_SRCCTS(156) +TSI_DP20_SRCCTS_DSC_COLOR_MODE_30 = TSI_BASE_DP20_SRCCTS(157) +TSI_DP20_SRCCTS_DSC_COLOR_MODE_31 = TSI_BASE_DP20_SRCCTS(158) +TSI_DP20_SRCCTS_MAX_STREAM_HACTIVE = TSI_BASE_DP20_SRCCTS(159) +TSI_DP20_SRCCTS_MAX_STREAM_VACTIVE = TSI_BASE_DP20_SRCCTS(160) +TSI_DP20_SRCCTS_MAX_STREAM_PIXEL_CLOCK = TSI_BASE_DP20_SRCCTS(161) +TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_0 = TSI_BASE_DP20_SRCCTS(162) +TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_1 = TSI_BASE_DP20_SRCCTS(163) +TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_2 = TSI_BASE_DP20_SRCCTS(164) +TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_3 = TSI_BASE_DP20_SRCCTS(165) +TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_4 = TSI_BASE_DP20_SRCCTS(166) +TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_5 = TSI_BASE_DP20_SRCCTS(167) +TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_6 = TSI_BASE_DP20_SRCCTS(168) +TSI_DP20_SRCCTS_TESTED_AUDIO_MODE_7 = TSI_BASE_DP20_SRCCTS(169) +TSI_DP20_SRCCTS_DMT_TIMING = TSI_BASE_DP20_SRCCTS(170) +TSI_DP20_SRCCTS_CTA_TIMING = TSI_BASE_DP20_SRCCTS(171) +TSI_DP20_SRCCTS_CVT_TIMING = TSI_BASE_DP20_SRCCTS(172) +TSI_DP20_SRCCTS_AS_DUT_CAPS = TSI_BASE_DP20_SRCCTS(173) +TSI_DP20_SRCCTS_AS_RANGE_MIN_RATE = TSI_BASE_DP20_SRCCTS(174) +TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_0 = TSI_BASE_DP20_SRCCTS(175) +TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_1 = TSI_BASE_DP20_SRCCTS(176) +TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_2 = TSI_BASE_DP20_SRCCTS(177) +TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_3 = TSI_BASE_DP20_SRCCTS(178) +TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_4 = TSI_BASE_DP20_SRCCTS(179) +TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_5 = TSI_BASE_DP20_SRCCTS(180) +TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_6 = TSI_BASE_DP20_SRCCTS(181) +TSI_DP20_SRCCTS_AS_RANGE_MAX_RATE_7 = TSI_BASE_DP20_SRCCTS(182) + +TSI_DP20_SINKCTS_TIMEOUT = TSI_BASE_DP20_SINKCTS(0) +TSI_DP20_SINKCTS_DSC_VM_0 = TSI_BASE_DP20_SINKCTS(1) +TSI_DP20_SINKCTS_DSC_VM_T_0 = TSI_BASE_DP20_SINKCTS(2) +TSI_DP20_SINKCTS_DSC_VM_1 = TSI_BASE_DP20_SINKCTS(3) +TSI_DP20_SINKCTS_DSC_VM_T_1 = TSI_BASE_DP20_SINKCTS(4) +TSI_DP20_SINKCTS_DSC_VM_2 = TSI_BASE_DP20_SINKCTS(5) +TSI_DP20_SINKCTS_DSC_VM_T_2 = TSI_BASE_DP20_SINKCTS(6) +TSI_DP20_SINKCTS_DSC_VM_3 = TSI_BASE_DP20_SINKCTS(7) +TSI_DP20_SINKCTS_DSC_VM_T_3 = TSI_BASE_DP20_SINKCTS(8) +TSI_DP20_SINKCTS_DSC_VM_4 = TSI_BASE_DP20_SINKCTS(9) +TSI_DP20_SINKCTS_DSC_VM_T_4 = TSI_BASE_DP20_SINKCTS(10) +TSI_DP20_SINKCTS_DSC_VM_5 = TSI_BASE_DP20_SINKCTS(11) +TSI_DP20_SINKCTS_DSC_VM_T_5 = TSI_BASE_DP20_SINKCTS(12) +TSI_DP20_SINKCTS_DSC_VM_6 = TSI_BASE_DP20_SINKCTS(13) +TSI_DP20_SINKCTS_DSC_VM_T_6 = TSI_BASE_DP20_SINKCTS(14) +TSI_DP20_SINKCTS_DSC_VM_7 = TSI_BASE_DP20_SINKCTS(15) +TSI_DP20_SINKCTS_DSC_VM_T_7 = TSI_BASE_DP20_SINKCTS(16) +TSI_DP20_SINKCTS_DSC_VM_8 = TSI_BASE_DP20_SINKCTS(17) +TSI_DP20_SINKCTS_DSC_VM_T_8 = TSI_BASE_DP20_SINKCTS(18) +TSI_DP20_SINKCTS_DSC_VM_9 = TSI_BASE_DP20_SINKCTS(19) +TSI_DP20_SINKCTS_DSC_VM_T_9 = TSI_BASE_DP20_SINKCTS(20) +TSI_DP20_SINKCTS_DSC_VM_10 = TSI_BASE_DP20_SINKCTS(21) +TSI_DP20_SINKCTS_DSC_VM_T_10 = TSI_BASE_DP20_SINKCTS(22) +TSI_DP20_SINKCTS_DSC_VM_11 = TSI_BASE_DP20_SINKCTS(23) +TSI_DP20_SINKCTS_DSC_VM_T_11 = TSI_BASE_DP20_SINKCTS(24) +TSI_DP20_SINKCTS_DSC_SOURCE = TSI_BASE_DP20_SINKCTS(25) +TSI_DP20_SINKCTS_SUPPORT_444CRC = TSI_BASE_DP20_SINKCTS(26) +TSI_DP20_SINKCTS_PACKET_SOURCE = TSI_BASE_DP20_SINKCTS(27) +TSI_DP20_SINKCTS_VIDEO_MODE_0 = TSI_BASE_DP20_SINKCTS(28) +TSI_DP20_SINKCTS_VIDEO_MODE_1 = TSI_BASE_DP20_SINKCTS(29) +TSI_DP20_SINKCTS_VIDEO_MODE_2 = TSI_BASE_DP20_SINKCTS(30) +TSI_DP20_SINKCTS_VIDEO_MODE_3 = TSI_BASE_DP20_SINKCTS(31) +TSI_DP20_SINKCTS_VIDEO_MODE_4 = TSI_BASE_DP20_SINKCTS(32) +TSI_DP20_SINKCTS_VIDEO_MODE_5 = TSI_BASE_DP20_SINKCTS(33) +TSI_DP20_SINKCTS_VIDEO_MODE_6 = TSI_BASE_DP20_SINKCTS(34) +TSI_DP20_SINKCTS_VIDEO_MODE_7 = TSI_BASE_DP20_SINKCTS(35) +TSI_DP20_SINKCTS_VIDEO_MODE_8 = TSI_BASE_DP20_SINKCTS(36) +TSI_DP20_SINKCTS_VIDEO_MODE_9 = TSI_BASE_DP20_SINKCTS(37) +TSI_DP20_SINKCTS_VIDEO_MODE_10 = TSI_BASE_DP20_SINKCTS(38) +TSI_DP20_SINKCTS_VIDEO_MODE_11 = TSI_BASE_DP20_SINKCTS(39) +TSI_DP20_SINKCTS_VIDEO_MODE_12 = TSI_BASE_DP20_SINKCTS(40) +TSI_DP20_SINKCTS_VIDEO_MODE_13 = TSI_BASE_DP20_SINKCTS(41) +TSI_DP20_SINKCTS_VIDEO_MODE_14 = TSI_BASE_DP20_SINKCTS(42) +TSI_DP20_SINKCTS_VIDEO_MODE_15 = TSI_BASE_DP20_SINKCTS(43) +TSI_DP20_SINKCTS_VIDEO_MODE_16 = TSI_BASE_DP20_SINKCTS(44) +TSI_DP20_SINKCTS_VISUAL_TEST_CHECK = TSI_BASE_DP20_SINKCTS(45) + +TSI_DP20_LTTPR_TIMEOUT = TSI_BASE_DP20_LTTPRCTS(0x0) + +TSI_TEST_CFG_HDCP2_1A_TIMEOUT = TSI_BASE_TEST_HDCP2_1A(0x0) +TSI_TEST_CFG_HDCP2_1A_REVOKEID = TSI_BASE_TEST_HDCP2_1A(0x1) +TSI_TEST_CFG_HDCP2_1A_SRC_DUT_CAP = TSI_BASE_TEST_HDCP2_1A(0x2) + +TSI_TEST_CFG_HDCP2_1B_TIMEOUT = TSI_BASE_TEST_HDCP2_1B(0x0) + +TSI_TEST_CFG_HDCP2_2C_TIMEOUT = TSI_BASE_TEST_HDCP2_2C(0x0) + +TSI_TEST_CFG_HDCP2_3A_TIMEOUT = TSI_BASE_TEST_HDCP2_3A(0x0) + +TSI_TEST_CFG_HDCP2_3B_TIMEOUT = TSI_BASE_TEST_HDCP2_3B(0x0) + +TSI_TEST_CFG_HDCP2_3C_TIMEOUT = TSI_BASE_TEST_HDCP2_3C(0x0) +TSI_TEST_CFG_HDCP2_3C_REPEATER_MULTIPLE_OUTPUTS = TSI_BASE_TEST_HDCP2_3C(0x1) + +TSI_VRR_SINK_DUT_TIMEOUT = TSI_BASE_VRR_SINK_DUT(0x0) +TSI_VRR_SINK_DUT_VRR_MAX = TSI_BASE_VRR_SINK_DUT(0x1) +TSI_VRR_SINK_DUT_VRR_MIN = TSI_BASE_VRR_SINK_DUT(0x2) +TSI_VRR_SINK_DUT_VRR_STATIC = TSI_BASE_VRR_SINK_DUT(0x3) +TSI_VRR_SINK_DUT_VRR_STEP = TSI_BASE_VRR_SINK_DUT(0x4) +TSI_VRR_SINK_DUT_VRR_TIME_STEP = TSI_BASE_VRR_SINK_DUT(0x5) +TSI_VRR_SINK_DUT_VRR_ENABLE = TSI_BASE_VRR_SINK_DUT(0x6) +TSI_VRR_SINK_DUT_BASE_VFRONT = TSI_BASE_VRR_SINK_DUT(0x7) +TSI_VRR_SINK_DUT_BASE_RATE = TSI_BASE_VRR_SINK_DUT(0x8) + +TSI_VRR_SRC_DUT_TIMEOUT = TSI_BASE_VRR_SRC_DUT(0x0) +TSI_VRR_SRC_DUT_VRR_MAX = TSI_BASE_VRR_SRC_DUT(0x1) +TSI_VRR_SRC_DUT_VRR_MIN = TSI_BASE_VRR_SRC_DUT(0x2) +TSI_VRR_SRC_DUT_VRR_STATIC = TSI_BASE_VRR_SRC_DUT(0x3) +TSI_VRR_SRC_DUT_VRR_STEP = TSI_BASE_VRR_SRC_DUT(0x4) +TSI_VRR_SRC_DUT_VRR_TIME_STEP = TSI_BASE_VRR_SRC_DUT(0x5) +TSI_VRR_SRC_DUT_VRR_ENABLE = TSI_BASE_VRR_SRC_DUT(0x6) +TSI_VRR_SRC_DUT_BASE_VFRONT = TSI_BASE_VRR_SRC_DUT(0x7) +TSI_VRR_SRC_DUT_BASE_RATE = TSI_BASE_VRR_SRC_DUT(0x8) + +TSI_HDMI_SRCCTS_TIMEOUT = TSI_BASE_HDMI_SRCCTS(0x0) + +TSI_HDMI_SNKCTS_TEST_MODE = TSI_BASE_HDMI_SINKCTS(0x0) +TSI_HDMI_SNKCTS_TIMEOUT = TSI_BASE_HDMI_SINKCTS(0x1) +TSI_HDMI_SNKCTS_MAX_FRL_RATE = TSI_BASE_HDMI_SINKCTS(0x2) +TSI_HDMI_SNKCTS_MAX_TMDS_CLOCK = TSI_BASE_HDMI_SINKCTS(0x3) +TSI_HDMI_SNKCTS_DUT_CAPS = TSI_BASE_HDMI_SINKCTS(0x4) +TSI_HDMI_SNKCTS_DSC_MAX_FRL_RATE = TSI_BASE_HDMI_SINKCTS(0x5) +TSI_HDMI_SNKCTS_HF_EEODB_SUPPORT = TSI_BASE_HDMI_SINKCTS(0x6) +TSI_HDMI_SNKCTS_DSC_VIDEO_FORMAT = TSI_BASE_HDMI_SINKCTS(0x7) +TSI_HDMI_SNKCTS_DSC_VIDEO_FORMAT_2 = TSI_BASE_HDMI_SINKCTS(0x8) +TSI_HDMI_SNKCTS_DSC_MAX_SLICES = TSI_BASE_HDMI_SINKCTS(0x9) +TSI_HDMI_SNKCTS_DSC_TOTAL_CHUNK_BYTES = TSI_BASE_HDMI_SINKCTS(0xA) + +TSI_HDMI_SNKCTS_CABLE_TIMEOUT = TSI_BASE_HDMI_CALBE_CHECK_SINK(0x0) +TSI_HDMI_SNKCTS_CABLE_TEST_MODE = TSI_BASE_HDMI_CALBE_CHECK_SINK(0x1) +TSI_HDMI_SNKCTS_CABLE_MIN_FRL_RATE = TSI_BASE_HDMI_CALBE_CHECK_SINK(0x2) +TSI_HDMI_SNKCTS_CABLE_MAX_FRL_RATE = TSI_BASE_HDMI_CALBE_CHECK_SINK(0x3) +TSI_HDMI_SNKCTS_CABLE_ERROR_PER_LANE = TSI_BASE_HDMI_CALBE_CHECK_SINK(0x4) +TSI_HDMI_SNKCTS_CABLE_CAPTURE_TIMEOUT = TSI_BASE_HDMI_CALBE_CHECK_SINK(0x5) +TSI_HDMI_SNKCTS_CABLE_LOW_SPEED_LINE = TSI_BASE_HDMI_CALBE_CHECK_SINK(0x6) +# !CI_PARSE_COMPLETE_OK diff --git a/UniTAP/libs/lib_uicl/UICL.dll b/UniTAP/libs/lib_uicl/UICL.dll new file mode 100644 index 0000000..e569c55 Binary files /dev/null and b/UniTAP/libs/lib_uicl/UICL.dll differ diff --git a/UniTAP/libs/lib_uicl/__init__.py b/UniTAP/libs/lib_uicl/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/UniTAP/libs/lib_uicl/uicl.py b/UniTAP/libs/lib_uicl/uicl.py new file mode 100644 index 0000000..41a1b8b --- /dev/null +++ b/UniTAP/libs/lib_uicl/uicl.py @@ -0,0 +1,73 @@ +from .uicl_types import * +from ..lib_helper import OS_Requirements, lib_method_wrapper + + +UICL_CURRENT_VERSION = 1 +UICL = OS_Requirements("UICL").get_lib() + + +class UICLError(Exception): + def __init__(self, message, errors=None): + super().__init__(message) + self.errors = errors + + +def UICL_GetRequiredBufferSize(dest_image: UICL_Image): + _UICL_GetRequiredBufferSize = lib_method_wrapper(UICL.UICL_GetRequiredBufferSize, [POINTER(UICL_Image)], c_int64) + + return _UICL_GetRequiredBufferSize(byref(dest_image)) + + +def UICL_Convert(src_image: UICL_Image, dest_image: UICL_Image): + _UICL_Convert = lib_method_wrapper(UICL.UICL_Convert, [POINTER(UICL_Image), POINTER(UICL_Image)], UICL_RESULT) + + return _UICL_Convert(byref(src_image), byref(dest_image)) + + +def UICL_SaveToFile(src_image: UICL_Image, file_name, image_file_format): + _UICL_SaveToFile = lib_method_wrapper(UICL.UICL_SaveToFile, [c_char_p, POINTER(UICL_Image), c_int], UICL_RESULT) + + return _UICL_SaveToFile(c_char_p(file_name.encode('utf-8')), byref(src_image), image_file_format) + + +def UICL_CalculateCRC16(src_image: UICL_Image, crc: UICL_CRC16): + _UICL_CalculateCRC16 = lib_method_wrapper(UICL.UICL_CalculateCRC16, [POINTER(UICL_Image), POINTER(UICL_CRC16)], + UICL_RESULT) + + return _UICL_CalculateCRC16(byref(src_image), byref(crc)) + + +def UICL_GeneratePattern(src_image: UICL_Image, pattern_type: int): + _UICL_GeneratePattern = lib_method_wrapper(UICL.UICL_GeneratePattern, [POINTER(UICL_Image), c_int], + UICL_RESULT) + + return _UICL_GeneratePattern(byref(src_image), pattern_type) + + +def UICL_GeneratePattern_3Tap(src_image: UICL_Image, pattern_type: int): + _UICL_GeneratePattern_3Tap = lib_method_wrapper(UICL.UICL_GeneratePattern_3Tap, [POINTER(UICL_Image), c_int], + UICL_RESULT) + + return _UICL_GeneratePattern_3Tap(byref(src_image), pattern_type) + + +def UICL_FreeImage(src_image: UICL_Image): + _UICL_DeleteImage = lib_method_wrapper(UICL.UICL_FreeImage, [ POINTER(UICL_Image) ], + UICL_RESULT) + + return _UICL_DeleteImage(byref(src_image)) + + +def UICL_AllocImage(image_params: UICL_ImageParameters) -> UICL_Image: + _UICL_AllocateImage = lib_method_wrapper(UICL.UICL_AllocImage, + [POINTER(UICL_ImageParameters), POINTER(UICL_Image)], + UICL_RESULT) + + image = UICL_Image() + + result = _UICL_AllocateImage(byref(image_params), byref(image)) + + if result < UICL_SUCCESS: + raise UICLError(f"Cannot allocate image. Error code: {result}") + + return image diff --git a/UniTAP/libs/lib_uicl/uicl_types.py b/UniTAP/libs/lib_uicl/uicl_types.py new file mode 100644 index 0000000..5d25d14 --- /dev/null +++ b/UniTAP/libs/lib_uicl/uicl_types.py @@ -0,0 +1,169 @@ +from ctypes import * +from enum import IntEnum + +UICL_RESULT = c_int32 + +UICL_SUCCESS = 0 +UICL_ERROR_UNKNOWN = -1 +UICL_ERROR_NULL_IMAGE = -2 +UICL_ERROR_INVALID_COLORSPACE = -3 +UICL_ERROR_CONVERSION_NOT_SUPPORTED = -4 + + +class CtypesEnum(IntEnum): + @classmethod + def from_param(cls, obj): + return int(obj) + + +class UICL_ImageFileFormat(CtypesEnum): + FILE_FORMAT_BMP = 0, + FILE_FORMAT_BIN = 1, + FILE_FORMAT_PPM = 2, + + +class UICL_Colorspace(CtypesEnum): + Colorspace_Unknown = 0, + Colorspace_RGB = 1, + Colorspace_YCbCr = 2, + Colorspace_Raw = 3, + Colorspace_Y = 4, + Colorspace_MaxValue = 5 + + +class UICL_Sampling(CtypesEnum): + Sampling_Unknown = 0, + Sampling_444 = 1, + Sampling_422 = 2, + Sampling_420 = 3, + Sampling_MaxValue = 4 + + +class UICL_Colorimetry(CtypesEnum): + Colorimetry_Unknown = 0, + Colorimetry_ITU_R_BT601 = 1, + Colorimetry_ITU_R_BT709 = 2, + Colorimetry_ITU_R_BT2020 = 3, + Colorimetry_MaxValue = 4 + + +class UICL_Packing(CtypesEnum): + Packing_Unknown = 0, + Packing_Planar = 1, + Packing_Packed = 2, + Packing_DP_1_lane = 3 + Packing_DP_2_lane = 4 + Packing_DP_4_lane = 5 + Packing_MaxValue = 6 + + +class UICL_ComponentOrder(CtypesEnum): + Order_Unknown = 0, + Order_UCDTX_HI = 1, + Order_UCDTX_LO = 2, + Order_UCDRX = 3, + Order_RGB = 4, + Order_RGBA = 5, + Order_BGR = 6, + Order_BGRA = 7, + Order_YCbCr = 8, + Order_CbYCr = 9, + Order_CbY0CrY1 = 10, + Order_PlainRaw = 11, + Order_Y = 12, + Order_CirdanRaw = 13, + Order_MaxValue = 14 + + +class UICL_Alignment(CtypesEnum): + Alignment_Unknown = 0, + Alignment_MSB = 1, + Alignment_LSB = 2, + Alignment_MaxValue = 3 + + +class UICL_ImageParameters(Structure): + _fields_ = [ + ('Width', c_uint32), + ('Height', c_uint32), + ('BitsPerColor', c_uint8), + ('Colorspace', c_int), + ('Colorimetry', c_int), + ('Sampling', c_int), + ('Packing', c_int), + ('ComponentOrder', c_int), + ('Alignment', c_int), + ('IsFullRange', c_bool), + ] + + +class ImageFileFormat(CtypesEnum): + FILE_FORMAT_BMP = 0, + FILE_FORMAT_BIN = 1 + FILE_FORMAT_PPM = 2 + + +class UICL_Image(Structure): + _fields_ = [ + ('Parameters', UICL_ImageParameters), + ('DataPtr', POINTER(c_uint8)), + ('DataSize', c_uint64) + ] + + +class UICL_CRC16(Structure): + _fields_ = [ + ('R', c_uint16), + ('G', c_uint16), + ('B', c_uint16) + ] + + +parse_attributes_bpc = [6, 8, 10, 12, 16, 7, 14, 0] + +parse_attributes_packing = [UICL_Packing.Packing_Packed, + UICL_Packing.Packing_Packed, + UICL_Packing.Packing_Unknown, + UICL_Packing.Packing_Unknown] + +parse_attributes_sampling = [UICL_Sampling.Sampling_444, + UICL_Sampling.Sampling_422, + UICL_Sampling.Sampling_444, + UICL_Sampling.Sampling_420, + UICL_Sampling.Sampling_444, + UICL_Sampling.Sampling_Unknown, + UICL_Sampling.Sampling_Unknown, + UICL_Sampling.Sampling_Unknown] + +parse_attributes_colorspace = [UICL_Colorspace.Colorspace_RGB, + UICL_Colorspace.Colorspace_YCbCr, + UICL_Colorspace.Colorspace_YCbCr, + UICL_Colorspace.Colorspace_YCbCr, + UICL_Colorspace.Colorspace_Y, + UICL_Colorspace.Colorspace_Raw, + UICL_Colorspace.Colorspace_Unknown, + UICL_Colorspace.Colorspace_Unknown] + +parse_attributes_colorimetry = [UICL_Colorimetry.Colorimetry_Unknown, + UICL_Colorimetry.Colorimetry_ITU_R_BT601, + UICL_Colorimetry.Colorimetry_ITU_R_BT709, + UICL_Colorimetry.Colorimetry_Unknown] + +parse_attributes_ecolorimetry = [UICL_Colorimetry.Colorimetry_Unknown, + UICL_Colorimetry.Colorimetry_Unknown, + UICL_Colorimetry.Colorimetry_Unknown, + UICL_Colorimetry.Colorimetry_Unknown, + UICL_Colorimetry.Colorimetry_Unknown, + UICL_Colorimetry.Colorimetry_ITU_R_BT2020, + UICL_Colorimetry.Colorimetry_ITU_R_BT2020, + UICL_Colorimetry.Colorimetry_ITU_R_BT601] + +dict_colorspace = {0: "Unknown", 1: "RGB", 2: "YCbCr", 3: "Raw", 4: "MaxValue"} +dict_sampling = {0: "Unknown", 1: "4:4:4", 2: "4:2:2", 3: "4:2:0"} +dict_colorimetry = {0: "Unknown", 1: "RGB", 2: "ITU_R_BT601", 3: "ITU_R_BT709", 4: "ITU_R_BT2020"} +dict_packing = {0: "Unknown", 1: "Planar", 2: "SemiPlanar", 3: "Packed", 4: "32bit", 5: "48bit"} +dict_component_oreder = {0: "Unknown", 1: "UCDTX_HI", 2: "UCDTX_LO", 3: "UCDRX", 4: "RGB", 5: "RGBA", 6: "BGR", + 7: "BGRA", 8: "YCbCr", 9: "CbYCr", 10: "CrYCb", 11: "CbY0CrY1", 12: "PlainRaw"} +dict_alignment = {0: "Unknown", 1: "MSB", 2: "LSB"} +uicl_errors = {-1: "Error unknown", -2: "Error null image", -3: "Invalid colorspace", -4: "Conversion not supported", + -5: "Error in conversion", -6: "Unsupported format", -7: "Invalid parameters"} diff --git a/UniTAP/libs/lib_uicl/uicl_utils.py b/UniTAP/libs/lib_uicl/uicl_utils.py new file mode 100644 index 0000000..23d310d --- /dev/null +++ b/UniTAP/libs/lib_uicl/uicl_utils.py @@ -0,0 +1,331 @@ +from UniTAP.common import VideoMode, VideoFrame, ColorInfo, Timing, ImageFileFormat, DataInfo +from .uicl_types import UICL_Image, UICL_ImageParameters, UICL_Colorimetry, UICL_Sampling,\ + UICL_Colorspace, UICL_Packing, UICL_Alignment, UICL_ComponentOrder, UICL_SUCCESS,\ + UICL_ImageFileFormat +from .uicl import UICL_Convert, UICL_GetRequiredBufferSize, UICL_SaveToFile, UICL_CRC16, UICL_CalculateCRC16 + +from typing import Tuple +from ctypes import c_uint8 + + +def uicl_cf_from_vm(color_format: ColorInfo.ColorFormat) -> UICL_Colorspace: + if color_format == ColorInfo.ColorFormat.CF_RGB: + return UICL_Colorspace.Colorspace_RGB + elif color_format in [ColorInfo.ColorFormat.CF_YCbCr_444, ColorInfo.ColorFormat.CF_YCbCr_422, + ColorInfo.ColorFormat.CF_YCbCr_420]: + return UICL_Colorspace.Colorspace_YCbCr + else: + return UICL_Colorspace.Colorspace_Unknown + + +def uicl_sampling_from_vm(color_format: ColorInfo.ColorFormat) -> UICL_Sampling: + if color_format in [ColorInfo.ColorFormat.CF_RGB, ColorInfo.ColorFormat.CF_YCbCr_444]: + return UICL_Sampling.Sampling_444 + elif color_format == ColorInfo.ColorFormat.CF_YCbCr_422: + return UICL_Sampling.Sampling_422 + elif color_format == ColorInfo.ColorFormat.CF_YCbCr_420: + return UICL_Sampling.Sampling_420 + else: + return UICL_Sampling.Sampling_Unknown + + +def uicl_colorimetry_from_ci_colorimetry(colorimetry: ColorInfo.Colorimetry) -> UICL_Colorimetry: + if colorimetry == ColorInfo.Colorimetry.CM_ITUR_BT601: + return UICL_Colorimetry.Colorimetry_ITU_R_BT601 + elif colorimetry == ColorInfo.Colorimetry.CM_ITUR_BT709: + return UICL_Colorimetry.Colorimetry_ITU_R_BT709 + elif colorimetry == ColorInfo.Colorimetry.CM_ITUR_BT2020_YCbCr: + return UICL_Colorimetry.Colorimetry_ITU_R_BT2020 + else: + return UICL_Colorimetry.Colorimetry_Unknown + + +def uicl_colorimetry_to_ci_colorimetry(colorimetry: UICL_Colorimetry) -> ColorInfo.Colorimetry: + if colorimetry == UICL_Colorimetry.Colorimetry_ITU_R_BT601: + return ColorInfo.Colorimetry.CM_ITUR_BT601 + elif colorimetry == UICL_Colorimetry.Colorimetry_ITU_R_BT709: + return ColorInfo.Colorimetry.CM_ITUR_BT709 + elif colorimetry == UICL_Colorimetry.Colorimetry_ITU_R_BT2020: + return ColorInfo.Colorimetry.CM_ITUR_BT2020_YCbCr + else: + return ColorInfo.Colorimetry.CM_NONE + + +def uicl_parameters_from_vm(vm: VideoMode) -> UICL_ImageParameters: + parameters = UICL_ImageParameters() + + parameters.Width = vm.timing.hactive + parameters.Height = vm.timing.vactive + parameters.BitsPerColor = vm.color_info.bpc + parameters.Colorimetry = uicl_colorimetry_from_ci_colorimetry(vm.color_info.colorimetry) + + if vm.color_info.color_format == ColorInfo.ColorFormat.CF_RGB: + parameters.Colorspace = UICL_Colorspace.Colorspace_RGB + parameters.ComponentOrder = UICL_ComponentOrder.Order_RGB + parameters.Sampling = UICL_Sampling.Sampling_444 + parameters.Packing = UICL_Packing.Packing_Packed + parameters.Alignment = UICL_Alignment.Alignment_LSB + parameters.IsFullRange = vm.color_info.DynamicRange == ColorInfo.DynamicRange.DR_VESA + + elif vm.color_info.color_format == ColorInfo.ColorFormat.CF_YCbCr_444: + parameters.Colorspace = UICL_Colorspace.Colorspace_YCbCr + parameters.ComponentOrder = UICL_ComponentOrder.Order_YCbCr + parameters.Sampling = UICL_Sampling.Sampling_444 + parameters.Packing = UICL_Packing.Packing_Packed + parameters.Alignment = UICL_Alignment.Alignment_LSB + parameters.IsFullRange = False + + elif vm.color_info.color_format == ColorInfo.ColorFormat.CF_YCbCr_422: + parameters.Colorspace = UICL_Colorspace.Colorspace_YCbCr + parameters.ComponentOrder = UICL_ComponentOrder.Order_CbY0CrY1 + parameters.Sampling = UICL_Sampling.Sampling_422 + parameters.Packing = UICL_Packing.Packing_Packed + parameters.Alignment = UICL_Alignment.Alignment_LSB + parameters.IsFullRange = False + + elif vm.color_info.color_format == ColorInfo.ColorFormat.CF_YCbCr_420: + parameters.Colorspace = UICL_Colorspace.Colorspace_YCbCr + parameters.ComponentOrder = UICL_ComponentOrder.Order_YCbCr + parameters.Sampling = UICL_Sampling.Sampling_420 + parameters.Packing = UICL_Packing.Packing_Planar + parameters.Alignment = UICL_Alignment.Alignment_LSB + parameters.IsFullRange = False + + return parameters + + +def uicl_image_from_vm_and_data(video_mode: VideoMode, data: bytearray) -> UICL_Image: + image = UICL_Image() + + image.DataPtr = (c_uint8 * len(data))(*data) + image.DataSize = len(data) + image.Parameters = uicl_parameters_from_vm(video_mode) + + return image + + +def uicl_image_convert_to_vm(src_image: UICL_Image, video_mode: VideoMode) -> bytearray: + dest_image = UICL_Image() + + dest_image.Parameters = uicl_parameters_from_vm(video_mode) + result = UICL_GetRequiredBufferSize(dest_image) + + assert result >= UICL_SUCCESS, f"Calculation required buffer size failed with error {result}" + + dest_image.DataPtr = (c_uint8 * result)() + dest_image.DataSize = result + + result = UICL_Convert(src_image, dest_image) + + assert result == UICL_SUCCESS, f"Image conversion failed with error {result}" + + return bytearray(dest_image.DataPtr[:dest_image.DataSize]) + + +def image_from_vf(video_frame: VideoFrame) -> UICL_Image: + image = UICL_Image() + + image.Parameters = image_params_from_size_and_ci(video_frame.width, video_frame.height, video_frame.color_info, + video_frame.data_info) + image.DataSize = len(video_frame.data) + image.DataPtr = (c_uint8 * len(video_frame.data)).from_buffer(video_frame.data) + + return image + + +def image_to_vf(image: UICL_Image) -> VideoFrame: + video_frame = VideoFrame() + + video_frame.width, video_frame.height, video_frame.color_info, video_frame.data_info = image_params_to_size_and_ci(image.Parameters) + video_frame.data = bytearray(image.DataPtr[:image.DataSize]) + + return video_frame + + +def uicl_image_calculate_crc(src_vf: VideoFrame) -> tuple: + crc = UICL_CRC16() + + src_image = image_from_vf(src_vf) + + result = UICL_CalculateCRC16(src_image, crc) + + assert result >= UICL_SUCCESS, f"Calculation CRC failed with error {result}" + + return crc.R, crc.G, crc.B + + +def image_params_to_vm(image_params: UICL_ImageParameters) -> VideoMode: + pass + + +def image_params_to_size_and_ci(image_params: UICL_ImageParameters) -> Tuple[int, int, ColorInfo, DataInfo]: + width = image_params.Width + height = image_params.Height + color_info = ColorInfo() + + color_info.bpc = image_params.BitsPerColor + color_info.colorimetry = uicl_colorimetry_to_ci_colorimetry(image_params.Colorimetry) + color_info.dynamic_range = ColorInfo.DynamicRange.DR_VESA if image_params.IsFullRange else ColorInfo.DynamicRange.DR_CTA + + if image_params.Colorspace == UICL_Colorspace.Colorspace_RGB: + color_info.color_format = ColorInfo.ColorFormat.CF_RGB + elif image_params.Colorspace == UICL_Colorspace.Colorspace_YCbCr: + if image_params.Sampling == UICL_Sampling.Sampling_444: + color_info.color_format = ColorInfo.ColorFormat.CF_YCbCr_444 + elif image_params.Sampling == UICL_Sampling.Sampling_422: + color_info.color_format = ColorInfo.ColorFormat.CF_YCbCr_422 + elif image_params.Sampling == UICL_Sampling.Sampling_420: + color_info.color_format = ColorInfo.ColorFormat.CF_YCbCr_420 + else: + color_info.color_format = ColorInfo.ColorFormat.CF_UNKNOWN + elif image_params.Colorspace == UICL_Colorspace.Colorspace_Y: + color_info.color_format = ColorInfo.ColorFormat.CF_Y_ONLY + elif image_params.Colorspace == UICL_Colorspace.Colorspace_Raw: + color_info.color_format = ColorInfo.ColorFormat.CF_RAW + + data_info = DataInfo() + data_info.alignment = uicl_alignment_to_vf_alignment(image_params.Alignment) + data_info.component_order = uicl_com_order_to_vf_com_order(image_params.ComponentOrder) + data_info.packing = uicl_packing_to_vf_packing(image_params.Packing) + + return width, height, color_info, data_info + + +def vf_com_order_to_uicl_com_order(component_order: DataInfo.ComponentOrder) -> UICL_ComponentOrder: + if component_order == DataInfo.ComponentOrder.CO_UCDRX: + return UICL_ComponentOrder.Order_UCDRX + elif component_order == DataInfo.ComponentOrder.CO_RGB: + return UICL_ComponentOrder.Order_RGB + elif component_order == DataInfo.ComponentOrder.CO_RGBA: + return UICL_ComponentOrder.Order_RGBA + elif component_order == DataInfo.ComponentOrder.CO_BGR: + return UICL_ComponentOrder.Order_BGR + elif component_order == DataInfo.ComponentOrder.CO_BGRA: + return UICL_ComponentOrder.Order_BGRA + elif component_order == DataInfo.ComponentOrder.CO_YCbCr: + return UICL_ComponentOrder.Order_YCbCr + elif component_order == DataInfo.ComponentOrder.CO_CbY0CrY1: + return UICL_ComponentOrder.Order_CbY0CrY1 + else: + return UICL_ComponentOrder.Order_Unknown + + +def uicl_com_order_to_vf_com_order(component_order: UICL_ComponentOrder) -> DataInfo.ComponentOrder: + if component_order == UICL_ComponentOrder.Order_UCDRX: + return DataInfo.ComponentOrder.CO_UCDRX + elif component_order == UICL_ComponentOrder.Order_RGB: + return DataInfo.ComponentOrder.CO_RGB + elif component_order == UICL_ComponentOrder.Order_RGBA: + return DataInfo.ComponentOrder.CO_RGBA + elif component_order == UICL_ComponentOrder.Order_BGR: + return DataInfo.ComponentOrder.CO_BGR + elif component_order == UICL_ComponentOrder.Order_BGRA: + return DataInfo.ComponentOrder.CO_BGRA + elif component_order == UICL_ComponentOrder.Order_YCbCr: + return DataInfo.ComponentOrder.CO_YCbCr + elif component_order == UICL_ComponentOrder.Order_CbY0CrY1: + return DataInfo.ComponentOrder.CO_CbY0CrY1 + else: + return DataInfo.ComponentOrder.CO_UNKNOWN + + +def vf_packing_to_uicl_packing(packing: DataInfo.Packing) -> UICL_Packing: + if packing == DataInfo.Packing.P_PLANAR: + return UICL_Packing.Packing_Planar + elif packing == DataInfo.Packing.P_PACKED: + return UICL_Packing.Packing_Packed + else: + return UICL_Packing.Packing_Unknown + + +def uicl_packing_to_vf_packing(packing: UICL_Packing) -> DataInfo.Packing: + if packing == UICL_Packing.Packing_Planar: + return DataInfo.Packing.P_PLANAR + elif packing == UICL_Packing.Packing_Packed: + return DataInfo.Packing.P_PACKED + else: + return DataInfo.Packing.P_UNKNOWN + + +def vf_alignment_to_uicl_alignment(alignment: DataInfo.Alignment) -> UICL_Alignment: + if alignment == DataInfo.Alignment.A_LSB: + return UICL_Alignment.Alignment_LSB + elif alignment == DataInfo.Alignment.A_MSB: + return UICL_Alignment.Alignment_MSB + else: + return UICL_Alignment.Alignment_Unknown + + +def uicl_alignment_to_vf_alignment(alignment: UICL_Alignment) -> DataInfo.Alignment: + if alignment == UICL_Alignment.Alignment_LSB: + return DataInfo.Alignment.A_LSB + elif alignment == UICL_Alignment.Alignment_MSB: + return DataInfo.Alignment.A_MSB + else: + return DataInfo.Alignment.A_UNKNOWN + + +def image_params_from_vm(video_mode: VideoMode) -> UICL_ImageParameters: + pass + + +def image_params_from_size_and_ci(width: int, height: int, color_info: ColorInfo, + data_info: DataInfo) -> UICL_ImageParameters: + parameters = UICL_ImageParameters() + + parameters.Width = width + parameters.Height = height + parameters.BitsPerColor = color_info.bpc + parameters.Colorimetry = uicl_colorimetry_from_ci_colorimetry(color_info.colorimetry) + parameters.ComponentOrder = vf_com_order_to_uicl_com_order(data_info.component_order) + parameters.Packing = vf_packing_to_uicl_packing(data_info.packing) + parameters.Alignment = vf_alignment_to_uicl_alignment(data_info.alignment) + + if color_info.color_format == ColorInfo.ColorFormat.CF_RGB: + parameters.Colorspace = UICL_Colorspace.Colorspace_RGB + parameters.Sampling = UICL_Sampling.Sampling_444 + parameters.IsFullRange = color_info.DynamicRange == ColorInfo.DynamicRange.DR_VESA + + elif color_info.color_format == ColorInfo.ColorFormat.CF_YCbCr_444: + parameters.Colorspace = UICL_Colorspace.Colorspace_YCbCr + parameters.Sampling = UICL_Sampling.Sampling_444 + parameters.IsFullRange = True if data_info.component_order == DataInfo.ComponentOrder.CO_UCDRX else False + + elif color_info.color_format == ColorInfo.ColorFormat.CF_YCbCr_422: + parameters.Colorspace = UICL_Colorspace.Colorspace_YCbCr + parameters.Sampling = UICL_Sampling.Sampling_422 + parameters.IsFullRange = True if data_info.component_order == DataInfo.ComponentOrder.CO_UCDRX else False + + elif color_info.color_format == ColorInfo.ColorFormat.CF_YCbCr_420: + parameters.Colorspace = UICL_Colorspace.Colorspace_YCbCr + parameters.Sampling = UICL_Sampling.Sampling_420 + parameters.IsFullRange = True if data_info.component_order == DataInfo.ComponentOrder.CO_UCDRX else False + + else: + parameters.Colorspace = UICL_Colorspace.Colorspace_Unknown + parameters.Sampling = UICL_Sampling.Sampling_Unknown + parameters.IsFullRange = True if data_info.component_order == DataInfo.ComponentOrder.CO_UCDRX else False + + return parameters + + +def image_convert_to_parameters(src_image: UICL_Image, parameters: UICL_ImageParameters) -> UICL_Image: + dst_image = UICL_Image() + + dst_image.Parameters = parameters + result = UICL_GetRequiredBufferSize(dst_image) + + assert result >= UICL_SUCCESS, f"Calculation required buffer size failed with error {result}" + + dst_image.DataSize = result + dst_image.DataPtr = (c_uint8 * result)() + + result = UICL_Convert(src_image, dst_image) + + assert result >= UICL_SUCCESS, f"Image conversion failed with error {result}" + + return dst_image + + +def image_save_to_file(image: UICL_Image, path: str, image_format: UICL_ImageFileFormat) -> bool: + return UICL_SaveToFile(image, path, image_format) diff --git a/UniTAP/tsi_lib.py b/UniTAP/tsi_lib.py new file mode 100644 index 0000000..5fa7131 --- /dev/null +++ b/UniTAP/tsi_lib.py @@ -0,0 +1,233 @@ +import re +from typing import Union, Optional +from enum import IntEnum + +from UniTAP.dev import * +from UniTAP.libs.lib_tsi import BaseIO, DeviceMaskInternal +from UniTAP.utils import tsi_logging as logging + + +class DeviceMask(IntEnum): + Sink = 0 + Source = 1 + All = 2 + Nothing = 3 + + +class DeviceAlreadyInUse(Exception): + """ + Redefinition of base exception. + Define error of 'device already in use'. + """ + pass + + +class FailedToOpenDevice(Exception): + """ + Redefinition of base exception. + Define error of 'device already in use'. + """ + pass + + +class DeviceNotFound(Exception): + """ + Redefinition of base exception. + Define error of 'device not found'. + """ + pass + + +class DeviceNotSupported(Exception): + """ + Redefinition of base exception. + Define error of 'device not supported'. + """ + pass + + +class TsiLib: + """ + Class `TsiLib` allows working with TSI Devices. + - Open selected device `open`. + """ + + __device_mask_convert = {DeviceMask.Sink: DeviceMaskInternal.Sink, + DeviceMask.Source: DeviceMaskInternal.Source, + DeviceMask.All: DeviceMaskInternal.All, + DeviceMask.Nothing: DeviceMaskInternal.Nothing} + + def __init__(self): + logging.info(f"[UniTAP] TsiLib.init {self}") + self.__io = BaseIO() + self.__device_list = [] + + def cleanup(self): + """ + Clear list of devices and call function `TSI_Clean` for cleaning TSI library. + """ + self.__device_list.clear() + self.__io.cleanup() + logging.info(f"[UniTAP] TsiLib.cleanup {self}") + + def __del__(self): + """ + Automatically call the destructor after the script completes. + Call function `cleanup`. + """ + self.cleanup() + logging.info(f"[UniTAP] TsiLib.del {self}") + + def open(self, info: Union[str, int]) -> TSIDevice: + """ + Open selected TSI device. + + Args: + info (str|int) - serial number of device or device index. + + Returns: + object of `TSIDevice` type + """ + dev_handle = None + dev_full_name = "" + if isinstance(info, str) and len(info) == 8: + for dev_idx in range(self.__io.get_device_count()): + dev_full_name = self.__io.get_device_name(dev_idx) + if info.lower() in dev_full_name.lower(): + dev_io = self.__io.create_device_io(dev_idx) + break + else: + available_device_list = [] + for dev_idx in range(self.__io.get_device_count()): + available_device_list.append(f"{dev_idx}: {self.__io.get_device_name(dev_idx)}") + available_device_list_str = '\n'.join(available_device_list) + raise DeviceNotFound(f"Device [{info}] was not found, available device list:\n" + f"{available_device_list_str}") + elif isinstance(info, int): + if info < self.__io.get_device_count(): + dev_full_name = self.__io.get_device_name(info) + dev_io = self.__io.create_device_io(info) + else: + available_device_list = [] + for dev_idx in range(self.__io.get_device_count()): + available_device_list.append(f"{dev_idx}: {self.__io.get_device_name(dev_idx)}") + available_device_list_str = '\n'.join(available_device_list) + raise DeviceNotFound(f"Device with index {info} was not found, available device list:\n" + f"{available_device_list_str}") + else: + raise ValueError("'info' must be serial number (8 symbol str) or device index.") + + if "in use" in dev_full_name: + raise DeviceAlreadyInUse(f"Device [{info}] is already in use by other application.") + + if "not supported" in dev_full_name: + raise DeviceNotSupported(f"Device [{info}] is not supported.") + + if dev_io is None: + raise FailedToOpenDevice(f"Failed to open device [{info}].") + + dev_series, dev_model, dev_serial = self.__extract_dev_info(dev_full_name) + dev_series_model = f'{dev_series}-{dev_model}' + role_list = [] + for role_idx in range(dev_io.get_device_role_count()): + role_full_name = dev_io.get_device_role_name(role_idx) + role_list.append(MODEL_TO_CLASS[dev_series_model].ROLE_DICT[role_full_name]) + + self.__device_list.append(TSIDevice(dev_io, dev_series_model, dev_serial, role_list)) + + return weakref.proxy(self.__device_list[-1]) + + def close(self, device: TSIDevice): + """ + Close selected device `TSIDevice` (removing from list of devices) + + Args: + device (`TSIDevice`) - object of `TSIDevice` type + """ + self.__device_list.remove(device) + + def get_list_of_available_devices(self, require_caps: Optional[DeviceMask] = None, + unallowed_caps: Optional[DeviceMask] = None) -> list: + """ + Returns list of available devices for using by selected masks. + + Args: + require_caps (`DeviceMask`) - object of `DeviceMask` type + unallowed_caps (`DeviceMask`) - object of `DeviceMask` type + + Returns: + object of `list` type + """ + available_device_list = [] + if require_caps is not None and unallowed_caps is not None: + self.__set_search_mask(require_caps, unallowed_caps) + for dev_idx in range(self.__io.get_device_count()): + available_device_list.append(f"{dev_idx}: {self.__io.get_device_name(dev_idx)}") + return available_device_list + + def __set_search_mask(self, require_caps: DeviceMask, unallowed_caps: DeviceMask): + return self.__io.get_devices_by_mask(self.__device_mask_convert.get(require_caps), + self.__device_mask_convert.get(unallowed_caps)) + + def get_list_of_available_roles(self, dev_series_model: Union[str, TSIDevice]) -> list: + """ + Returns list of available roles for the selected device. + + Args: + dev_series_model (`str`|`TSIDevice`) - serial number of device + + Returns: + object of `list` type with `MODEL_TO_CLASS` roles + """ + if isinstance(dev_series_model, str) and dev_series_model not in MODEL_TO_CLASS.keys(): + _list_model_to_class = '\n'.join(f'{i}: {e}' for i, e in enumerate(MODEL_TO_CLASS.keys())) + raise ValueError(f"Incorrect Device series model {dev_series_model}. Must be from list:\n" + f"{_list_model_to_class}") + available_role_list = [] + if isinstance(dev_series_model, str): + devices = self.get_list_of_available_devices() + for dev in devices: + series_model = re.findall(r'UCD-\d+', dev)[0] + if dev_series_model == series_model: + available_role_list = list(MODEL_TO_CLASS[dev_series_model].ROLE_DICT.values()) + elif isinstance(dev_series_model, TSIDevice): + available_role_list = dev_series_model.available_roles + return available_role_list + + def get_str_list_of_available_roles(self, dev_series_model: Union[str, TSIDevice]) -> list: + """ + Returns list of available roles for the selected device. + + Args: + dev_series_model (`str`|`TSIDevice`) - serial number of device + + Returns: + object of `list` type with `str` + """ + if isinstance(dev_series_model, str) and dev_series_model not in MODEL_TO_CLASS.keys(): + _list_model_to_class = '\n'.join(f'{i}: {e}' for i, e in enumerate(MODEL_TO_CLASS.keys())) + raise ValueError(f"Incorrect Device series model {dev_series_model}. Must be from list:\n" + f"{_list_model_to_class}") + available_role_list = [] + if isinstance(dev_series_model, str): + devices = self.get_list_of_available_devices() + for dev in devices: + series_model = re.findall(r'UCD-\d+', dev)[0] + if dev_series_model == series_model: + available_role_list = [e for i, e in enumerate(MODEL_TO_CLASS[dev_series_model].ROLE_DICT.keys())] + elif isinstance(dev_series_model, TSIDevice): + dev_series_model = re.findall(r'UCD\d+', str(dev_series_model.available_roles[0]))[0]. \ + replace("UCD", "UCD-") + available_role_list = [f"{i}: {e}" for i, e in enumerate(MODEL_TO_CLASS[dev_series_model].ROLE_DICT.keys())] + return available_role_list + + @staticmethod + def __extract_dev_info(dev_full_name: str): + if dev_full_name.count(" ") == 1: + dev_name, dev_serial = dev_full_name.split(' ') + else: + dev_name, dev_serial = dev_full_name.split(":")[0].split(" ") + dev_series, dev_model = dev_name.split('-') + dev_serial = dev_serial[1:9] + + return dev_series, int(dev_model), dev_serial diff --git a/UniTAP/utils/__init__.py b/UniTAP/utils/__init__.py new file mode 100644 index 0000000..b602270 --- /dev/null +++ b/UniTAP/utils/__init__.py @@ -0,0 +1,4 @@ +from .function_wrapper import function_scheduler +from .uicl_api import video_frame_to_rgb8, video_frame_to_ci, video_frame_save_to_file, calculate_crc, ImageFileFormat,\ + calculate_dsc_crc +from .dscl_api import encode_video_frame, decode_video_frame, calculate_dsc_slice_size diff --git a/UniTAP/utils/dscl_api.py b/UniTAP/utils/dscl_api.py new file mode 100644 index 0000000..d722c9e --- /dev/null +++ b/UniTAP/utils/dscl_api.py @@ -0,0 +1,56 @@ +from UniTAP.libs.lib_dscl.dscl_utils import encode_vf, decode_vf, dscl_image_calculate_crc, dscl_data_processing, calculate_slice_size +from UniTAP.common import VideoFrame, VideoFrameDSC, CompressionInfo + + +def encode_video_frame(src_video_frame: VideoFrame, info: CompressionInfo) -> VideoFrameDSC: + """ + Encode custom video frame `VideoFrame` with transferred DSC parameters to video frame with DSC data. + + Args: + src_video_frame (`VideoFrame`) + info (`CompressionInfo`) + """ + return encode_vf(src_video_frame, info) + + +def decode_video_frame(src_video_frame: VideoFrameDSC) -> VideoFrame: + """ + Decode custom video frame `VideoFrameDSC` with DSC data. + + Args: + src_video_frame (`VideoFrame`) + """ + return decode_vf(src_video_frame) + + +def dsc_video_frame_from_data(data: bytearray) -> VideoFrameDSC: + """ + Read PPS from DSC video frame data `VideoFrameDSC`. + + Args: + data (`bytearray`) + """ + return dscl_data_processing(data) + + +def calculate_dsc_crc(video_frame: VideoFrameDSC) -> tuple: + """ + Calculate DSC CRC by DSC Video Frame. + + Args: + video_frame ('VideoFrameDSC') + """ + if not isinstance(video_frame, VideoFrameDSC): + raise TypeError("Calculate DSC crc accept only VideoFrameDSC object!") + return dscl_image_calculate_crc(video_frame) + + +def calculate_dsc_slice_size(value: int, slice_number: int): + """ + Calculate DSC slice size by value and slice number. + + Args: + value ('int') + slice_number ('int') + """ + return int(calculate_slice_size(value, slice_number)) diff --git a/UniTAP/utils/function_wrapper.py b/UniTAP/utils/function_wrapper.py new file mode 100644 index 0000000..1a070b6 --- /dev/null +++ b/UniTAP/utils/function_wrapper.py @@ -0,0 +1,43 @@ +import sched +import time + + +def function_scheduler(exec_func, *args, interval: float = 1, timeout: float = 1) -> bool: + """ + Execute function or lambda with timeout and interval. + + Args: + exec_func (function) - Function for execution. + *args - Arguments for executing function. + interval (float) - Function call interval. + timeout (float) - Timeout until the end of calls function. + + Returns: + function_successful (bool) - function result + + """ + __sched = sched.scheduler() + + def __function_wrapper(): + nonlocal function_successful + nonlocal time_start + if time.time() - time_start > timeout: + function_successful = False + for job in __sched.queue: + __sched.cancel(job) + return + if function_successful or exec_func(*args): + function_successful = True + for job in __sched.queue: + __sched.cancel(job) + + function_successful = False + + call_count = round(timeout / interval) + + for call in range(0, call_count): + __sched.enter(call * interval, 1, __function_wrapper) + + time_start = time.time() + __sched.run() + return function_successful diff --git a/UniTAP/utils/tsi_logging.py b/UniTAP/utils/tsi_logging.py new file mode 100644 index 0000000..cb6529d --- /dev/null +++ b/UniTAP/utils/tsi_logging.py @@ -0,0 +1,302 @@ +import logging as _logging +import os as _os +import sys as _sys +import _thread +import time as _time +import traceback as _traceback +import warnings +from logging import DEBUG +from logging import ERROR +from logging import CRITICAL +from logging import INFO +from logging import WARN +import threading + +# Don't use this directly. Use get_logger() instead. +_logger = None +_logger_lock = threading.Lock() + +_file_handler = None + + +def set_enabled(value: bool): + get_logger().disabled = not value + + +def is_enabled(): + return not get_logger().disabled + + +def error_log(error_msg, level=ERROR): + """Empty helper method.""" + del error_msg, level + + +def _get_formatter(): + return _logging.Formatter( + fmt='%(asctime)s.%(msecs)03d %(name)-12s %(levelname)-8s %(thread)-6d %(message)s', + datefmt='%H:%M:%S') + + +def _get_caller(offset=3): + """Returns a code and frame object for the lowest non-logging stack frame.""" + # Use sys._getframe(). This avoids creating a traceback object. + f = _sys._getframe(offset) + our_file = f.f_code.co_filename + f = f.f_back + while f: + code = f.f_code + if code.co_filename != our_file: + return code, f + f = f.f_back + return None, None + + +def _logger_find_caller(stack_info=False, stacklevel=1): + code, frame = _get_caller(4) + sinfo = None + if stack_info: + sinfo = '\n'.join(_traceback.format_stack()) + if code: + return (code.co_filename, frame.f_lineno, code.co_name, sinfo) + else: + return '(unknown file)', 0, '(unknown function)', sinfo + + +def get_logger(): + global _logger + + # Use double-checked locking to avoid taking lock unnecessarily. + if _logger: + return _logger + + _logger_lock.acquire() + + try: + if _logger: + return _logger + + logger = _logging.getLogger('unitap') + + # Override findCaller on the logger to skip internal helper functions + logger.findCaller = _logger_find_caller + + if not _logging.getLogger().handlers: + # Determine whether we are in an interactive environment + _interactive = False + try: + # This is only defined in interactive shells. + if _sys.ps1: + _interactive = True + except AttributeError: + # Even now, we may be in an interactive shell with `python -i`. + _interactive = _sys.flags.interactive + + # If we are in an interactive environment (like Jupyter), set loglevel + # to INFO and pipe the output to stdout. + if _interactive: + logger.setLevel(INFO) + _logging_target = _sys.stdout + else: + _logging_target = _sys.stderr + + # Add the output handler. + _handler = _logging.StreamHandler(_logging_target) + _handler.setFormatter(_get_formatter()) + logger.addHandler(_handler) + logger.propagate = False + + _logger = logger + return _logger + + finally: + _logger_lock.release() + + +def log(level, msg, *args, **kwargs): + get_logger().log(level, msg, *args, **kwargs) + + +def debug(msg, *args, **kwargs): + get_logger().debug(msg, *args, **kwargs) + + +def error(msg, *args, **kwargs): + get_logger().error(msg, *args, **kwargs) + + +def fatal(msg, *args, **kwargs): + get_logger().fatal(msg, *args, **kwargs) + + +def info(msg, *args, **kwargs): + get_logger().info(msg, *args, **kwargs) + + +def warn(msg, *args, **kwargs): + get_logger().warning(msg, *args, **kwargs) + + +def warning(msg, *args, **kwargs): + get_logger().warning(msg, *args, **kwargs) + + +_level_names = { + CRITICAL: 'CRITICAL', + ERROR: 'ERROR', + WARN: 'WARN', + INFO: 'INFO', + DEBUG: 'DEBUG', +} + +# Mask to convert integer thread ids to unsigned quantities for logging +# purposes +_THREAD_ID_MASK = 2 * _sys.maxsize + 1 + +_log_prefix = None # later set to google2_log_prefix + +# Counter to keep track of number of log entries per token. +_log_counter_per_token = {} + + +def TaskLevelStatusMessage(msg): + error(msg) + + +def flush(): + raise NotImplementedError() + + +# Code below is taken from pyglib/logging +def vlog(level, msg, *args, **kwargs): + get_logger().log(level, msg, *args, **kwargs) + + +def _GetNextLogCountPerToken(token): + global _log_counter_per_token + _log_counter_per_token[token] = 1 + _log_counter_per_token.get(token, -1) + return _log_counter_per_token[token] + + +def log_every_n(level, msg, n, *args): + """Log 'msg % args' at level 'level' once per 'n' times. + Logs the 1st call, (N+1)st call, (2N+1)st call, etc. + Not threadsafe. + Args: + level: The level at which to log. + msg: The message to be logged. + n: The number of times this should be called before it is logged. + *args: The args to be substituted into the msg. + """ + count = _GetNextLogCountPerToken(_GetFileAndLine()) + log_if(level, msg, not (count % n), *args) + + +def log_first_n(level, msg, n, *args): + """Log 'msg % args' at level 'level' only first 'n' times. + Not threadsafe. + Args: + level: The level at which to log. + msg: The message to be logged. + n: The number of times this should be called before it is logged. + *args: The args to be substituted into the msg. + """ + count = _GetNextLogCountPerToken(_GetFileAndLine()) + log_if(level, msg, count < n, *args) + + +def log_if(level, msg, condition, *args): + """Log 'msg % args' at level 'level' only if condition is fulfilled.""" + if condition: + vlog(level, msg, *args) + + +def _GetFileAndLine(): + """Returns (filename, linenumber) for the stack frame.""" + code, f = _get_caller() + if not code: + return '', 0 + return code.co_filename, f.f_lineno + + +def google2_log_prefix(level, timestamp=None, file_and_line=None): + """Assemble a logline prefix using the google2 format.""" + global _level_names + + # Record current time + now = timestamp or _time.time() + now_tuple = _time.localtime(now) + now_microsecond = int(1e6 * (now % 1.0)) + + (filename, line) = file_and_line or _GetFileAndLine() + basename = _os.path.basename(filename) + + # Severity string + severity = 'I' + if level in _level_names: + severity = _level_names[level][0] + + s = '%c%02d%02d %02d:%02d:%02d.%06d %5d %s:%d] ' % ( + severity, + now_tuple[1], # month + now_tuple[2], # day + now_tuple[3], # hour + now_tuple[4], # min + now_tuple[5], # sec + now_microsecond, + _get_thread_id(), + basename, + line) + + return s + + +def get_verbosity(): + """Return how much logging output will be produced.""" + return get_logger().getEffectiveLevel() + + +def set_verbosity(v): + """Sets the threshold for what messages will be logged.""" + get_logger().setLevel(v) + + +def set_file_output(filename, file_mode="w"): + """Set file handler""" + warnings.warn("This function is deprecated. Use `enable_file_logging` and `disable_file_logging` instead.", + DeprecationWarning) + enable_file_logging(filename, file_mode) + + +def enable_file_logging(filename, file_mode="w"): + global _file_handler + + if _file_handler is None: + _file_handler = _logging.FileHandler(filename, mode=file_mode) + _file_handler.setFormatter(_get_formatter()) + get_logger().addHandler(_file_handler) + else: + _file_handler.stream.close() + filename = _os.fspath(filename) + _file_handler.baseFilename = _os.path.abspath(filename) + _file_handler.stream = open(_file_handler.baseFilename, file_mode) + + +def disable_file_logging(): + global _file_handler + + if _file_handler is not None: + _file_handler.stream.close() + get_logger().removeHandler(_file_handler) + _file_handler = None + else: + warnings.warn("Cannot disable file logging because file logger is not defined") + + +def _get_thread_id(): + """Get id of current thread, suitable for logging as an unsigned quantity.""" + thread_id = _thread.get_ident() + return thread_id & _THREAD_ID_MASK + + +_log_prefix = google2_log_prefix diff --git a/UniTAP/utils/uicl_api.py b/UniTAP/utils/uicl_api.py new file mode 100644 index 0000000..4ff06e4 --- /dev/null +++ b/UniTAP/utils/uicl_api.py @@ -0,0 +1,85 @@ +import os.path +import subprocess +from typing import Union +from UniTAP.libs.lib_uicl.uicl_utils import * +from .dscl_api import * + + +def image_file_format_to_uicl_format(file_format): + if file_format == ImageFileFormat.IFF_PPM: + return UICL_ImageFileFormat.FILE_FORMAT_PPM + elif file_format == ImageFileFormat.IFF_BIN: + return UICL_ImageFileFormat.FILE_FORMAT_BIN + elif file_format == ImageFileFormat.IFF_BMP: + return UICL_ImageFileFormat.FILE_FORMAT_BMP + else: + raise TypeError(f"Incorrect file format: {file_format}") + + +def video_frame_to_rgb8(src_video_frame: VideoFrame, data_info: DataInfo) -> VideoFrame: + rgb8_color_info = ColorInfo() + rgb8_color_info.color_format = ColorInfo.ColorFormat.CF_RGB + rgb8_color_info.bpc = 8 + rgb8_color_info.dynamic_range = ColorInfo.DynamicRange.DR_VESA + + return video_frame_to_ci(src_video_frame, rgb8_color_info, data_info) + + +def video_frame_to_ci(src_video_frame: VideoFrame, color_info: ColorInfo, data_info: DataInfo) -> VideoFrame: + src_image = image_from_vf(src_video_frame) + dst_image_params = image_params_from_size_and_ci(src_video_frame.width, src_video_frame.height, + color_info, data_info) + + if dst_image_params.Alignment != UICL_Alignment.Alignment_LSB: + src_image = image_convert_to_parameters(src_image, dst_image_params) + dst_image_params.Alignment = UICL_Alignment.Alignment_LSB + + return image_to_vf(image_convert_to_parameters(src_image, dst_image_params)) + + +def video_frame_uicl_image(src_video_frame: VideoFrame, color_info: ColorInfo, data_info: DataInfo) -> UICL_Image: + src_image = image_from_vf(src_video_frame) + dst_image_params = image_params_from_size_and_ci(src_video_frame.width, src_video_frame.height, + color_info, data_info) + + if dst_image_params.Alignment != UICL_Alignment.Alignment_LSB: + src_image = image_convert_to_parameters(src_image, dst_image_params) + dst_image_params.Alignment = UICL_Alignment.Alignment_LSB + return image_convert_to_parameters(src_image, dst_image_params) + + +def video_frame_save_to_file(video_frame: Union[VideoFrame, VideoFrameDSC], path: str, file_type: ImageFileFormat) -> bool: + if file_type == ImageFileFormat.IFF_DSC: + with open(path, "wb") as dsc_file: + if b'DSCF' not in video_frame.data: + dsc_file.write(b'DSCF') + dsc_file.write(video_frame.data) + return os.path.exists(path) + else: + if b'DSCF' not in video_frame.data: + if video_frame.data_info.component_order == DataInfo.ComponentOrder.CO_UCDRX: + data_info = DataInfo() + color_info = ColorInfo() + + data_info.component_order = DataInfo.ComponentOrder.CO_RGB + data_info.alignment = DataInfo.Alignment.A_LSB + data_info.packing = DataInfo.Packing.P_PACKED + color_info.color_format = ColorInfo.ColorFormat.CF_RGB + color_info.bpc = 8 + color_info.dynamic_range = ColorInfo.DynamicRange.DR_VESA + + frame = video_frame_uicl_image(video_frame, color_info, data_info) + else: + frame = image_from_vf(video_frame) + + return image_save_to_file(frame, path, image_file_format_to_uicl_format(file_type)) + else: + vf = decode_video_frame(video_frame) + frame = image_from_vf(vf) + return image_save_to_file(frame, path, image_file_format_to_uicl_format(file_type)) + + +def calculate_crc(video_frame: VideoFrame) -> tuple: + if not isinstance(video_frame, VideoFrame): + raise TypeError("Calculate crc accept only VideoFrame object!") + return uicl_image_calculate_crc(video_frame) diff --git a/UniTAP/version.py b/UniTAP/version.py new file mode 100644 index 0000000..fe6abd7 --- /dev/null +++ b/UniTAP/version.py @@ -0,0 +1 @@ +__version__ = "3.6.274.0.18845.4" diff --git a/algorithm/pq_algorithm.py b/algorithm/pq_algorithm.py new file mode 100644 index 0000000..e391ca7 --- /dev/null +++ b/algorithm/pq_algorithm.py @@ -0,0 +1,559 @@ +import math +import numpy as np +from shapely.geometry import Polygon + + +# ================== +# 色域覆盖率计算公式 +# ================== + + +def calculate_gamut_coverage(points1, points2): + """ + 计算色域覆盖率(使用 shapely 库) + + Args: + points1: 测量三角形 [[x1, y1], [x2, y2], [x3, y3]] + points2: 参考三角形 [[x1, y1], [x2, y2], [x3, y3]] + + Returns: + area: 交集面积 + coverage: 覆盖率(%)≤ 100% + """ + try: + # 创建多边形对象 + poly1 = Polygon(points1) + poly2 = Polygon(points2) + + # 计算交集 + intersection = poly1.intersection(poly2) + + # 计算面积 + intersection_area = intersection.area + reference_area = poly2.area + + # 计算覆盖率 + coverage = ( + (intersection_area / reference_area) * 100 if reference_area > 0 else 0.0 + ) + + return intersection_area, coverage + + except Exception as e: + print(f"❌ 计算色域覆盖率失败: {e}") + return 0.0, 0.0 + + +def calculate_gamut_coverage_DCIP3(points): + """ + 计算 CIE 1931 xy 色度空间下的 DCI-P3 覆盖率 + + Args: + points: [[x1, y1], [x2, y2], [x3, y3]] 测量的 xy 坐标 + + Returns: + area: 交集面积 + coverage: 覆盖率(%) + """ + # DCI-P3 标准色域坐标(CIE 1931 xy) + dcip3_xy = [ + [0.680, 0.320], # Red + [0.265, 0.690], # Green + [0.150, 0.060], # Blue + ] + + area, coverage = calculate_gamut_coverage(points, dcip3_xy) + return area, coverage + + +def calculate_gamut_coverage_BT709(results): + """ + 计算相对于 BT.709 的色域覆盖率 + + Args: + results: 测量结果列表 + [[x1, y1, lv1, X1, Y1, Z1], ...] + 或 [[x1, y1], [x2, y2], [x3, y3]] + + Returns: + tuple: (intersection_area, coverage_percentage) + """ + # BT.709 标准色域三角形(CIE 1931 xy) + bt709_triangle = [ + [0.6400, 0.3300], # Red + [0.3000, 0.6000], # Green + [0.1500, 0.0600], # Blue + ] + + # 提取测量的 RGB 三个点的 xy 坐标 + if len(results[0]) > 2: + measured_points = [[r[0], r[1]] for r in results[:3]] + else: + measured_points = results[:3] + + area, coverage = calculate_gamut_coverage(measured_points, bt709_triangle) + return area, coverage + + +def calculate_gamut_coverage_BT601(results): + """ + 计算相对于 BT.601 的色域覆盖率 + + Args: + results: 测量结果列表 + + Returns: + tuple: (intersection_area, coverage_percentage) + """ + # BT.601 标准色域三角形(NTSC 版本) + bt601_triangle = [ + [0.6300, 0.3400], # Red + [0.3100, 0.5950], # Green + [0.1550, 0.0700], # Blue + ] + + # 提取测量点 + if len(results[0]) > 2: + measured_points = [[r[0], r[1]] for r in results[:3]] + else: + measured_points = results[:3] + + area, coverage = calculate_gamut_coverage(measured_points, bt601_triangle) + return area, coverage + + +def calculate_gamut_coverage_BT2020(results): + """ + 计算相对于 BT.2020 的色域覆盖率 + + Args: + results: 测量结果列表 + + Returns: + tuple: (intersection_area, coverage_percentage) + """ + # BT.2020 标准色域三角形 + bt2020_triangle = [ + [0.7080, 0.2920], # Red + [0.1700, 0.7970], # Green + [0.1310, 0.0460], # Blue + ] + + # 提取测量点 + if len(results[0]) > 2: + measured_points = [[r[0], r[1]] for r in results[:3]] + else: + measured_points = results[:3] + + area, coverage = calculate_gamut_coverage(measured_points, bt2020_triangle) + return area, coverage + + +def xy_to_uv_1976(x, y): + """ + CIE 1931 xy → CIE 1976 u'v' 转换(现代标准) + + Args: + x: CIE 1931 x 坐标 + y: CIE 1931 y 坐标 + + Returns: + (u', v'): CIE 1976 色度坐标 + """ + denom = -2 * x + 12 * y + 3 + if abs(denom) < 1e-10: + return (0, 0) + + u_prime = (4 * x) / denom + v_prime = (9 * y) / denom + + return (u_prime, v_prime) + + +def calculate_uv_gamut_coverage(uv_coords, reference="DCI-P3"): + """ + 计算 CIE 1976 u'v' 色度空间下的色域覆盖率 + + Args: + uv_coords: [[u1, v1], [u2, v2], [u3, v3]] 测量的 u'v' 坐标 + reference: 参考标准 ("DCI-P3", "BT.709", "BT.2020", "BT.601") + + Returns: + float: 覆盖率百分比 + """ + try: + # ========== 根据参考标准选择 xy 坐标 ========== + if reference == "BT.2020": + ref_xy = [ + (0.7080, 0.2920), # Red + (0.1700, 0.7970), # Green + (0.1310, 0.0460), # Blue + ] + elif reference == "BT.709": + ref_xy = [ + (0.6400, 0.3300), # Red + (0.3000, 0.6000), # Green + (0.1500, 0.0600), # Blue + ] + elif reference == "DCI-P3": + ref_xy = [ + (0.680, 0.320), # Red + (0.265, 0.690), # Green + (0.150, 0.060), # Blue + ] + elif reference == "BT.601": + ref_xy = [ + (0.6300, 0.3400), # Red + (0.3100, 0.5950), # Green + (0.1550, 0.0700), # Blue + ] + else: + # 默认使用 DCI-P3 + ref_xy = [ + (0.680, 0.320), + (0.265, 0.690), + (0.150, 0.060), + ] + + # 转换参考标准为 u'v' + ref_uv = [list(xy_to_uv_1976(x, y)) for x, y in ref_xy] + + # 确保测量坐标格式正确 + measured_uv = [[float(u), float(v)] for u, v in uv_coords] + + # 计算覆盖率 + area, coverage = calculate_gamut_coverage(measured_uv, ref_uv) + + return coverage + + except Exception as e: + print(f"❌ 计算 u'v' 色域覆盖率失败: {str(e)}") + import traceback + + traceback.print_exc() + return 0.0 + + +def calculate_gamut_coverage_DCIP3_uv(uv_points): + """ + 计算 CIE 1976 u'v' 色度空间下的 DCI-P3 覆盖率 + + Args: + uv_points: [[u1, v1], [u2, v2], [u3, v3]] 测量的 u'v' 坐标 + + Returns: + area: 交集面积 + coverage: 覆盖率(%) + """ + dcip3_xy = [ + (0.680, 0.320), # Red + (0.265, 0.690), # Green + (0.150, 0.060), # Blue + ] + + dcip3_uv = [list(xy_to_uv_1976(x, y)) for x, y in dcip3_xy] + area, coverage = calculate_gamut_coverage(uv_points, dcip3_uv) + return area, coverage + + +def calculate_gamut_coverage_BT709_uv(uv_points): + """ + 计算 CIE 1976 u'v' 色度空间下的 BT.709 覆盖率 + + Args: + uv_points: [[u1, v1], [u2, v2], [u3, v3]] 测量的 u'v' 坐标 + + Returns: + area: 交集面积 + coverage: 覆盖率(%) + """ + bt709_xy = [ + (0.6400, 0.3300), # Red + (0.3000, 0.6000), # Green + (0.1500, 0.0600), # Blue + ] + + bt709_uv = [list(xy_to_uv_1976(x, y)) for x, y in bt709_xy] + area, coverage = calculate_gamut_coverage(uv_points, bt709_uv) + return area, coverage + + +def calculate_gamut_coverage_BT2020_uv(uv_points): + """ + 计算 CIE 1976 u'v' 色度空间下的 BT.2020 覆盖率 + + Args: + uv_points: [[u1, v1], [u2, v2], [u3, v3]] 测量的 u'v' 坐标 + + Returns: + area: 交集面积 + coverage: 覆盖率(%) + """ + bt2020_xy = [ + (0.7080, 0.2920), # Red + (0.1700, 0.7970), # Green + (0.1310, 0.0460), # Blue + ] + + bt2020_uv = [list(xy_to_uv_1976(x, y)) for x, y in bt2020_xy] + area, coverage = calculate_gamut_coverage(uv_points, bt2020_uv) + return area, coverage + + +def calculate_gamut_coverage_BT601_uv(uv_points): + """ + 计算 CIE 1976 u'v' 色度空间下的 BT.601 覆盖率 + + Args: + uv_points: [[u1, v1], [u2, v2], [u3, v3]] 测量的 u'v' 坐标 + + Returns: + area: 交集面积 + coverage: 覆盖率(%) + """ + bt601_xy = [ + (0.6300, 0.3400), # Red + (0.3100, 0.5950), # Green + (0.1550, 0.0700), # Blue + ] + + bt601_uv = [list(xy_to_uv_1976(x, y)) for x, y in bt601_xy] + area, coverage = calculate_gamut_coverage(uv_points, bt601_uv) + return area, coverage + + +# =============== +# Gamma 计算公式 +# =============== + + +def calculate_gamma(results, max_index_fix, pattern_params=None): + """ + 计算 Gamma 值 + + Args: + results: [[x, y, lv, X, Y, Z], ...] 测量结果 + max_index_fix: 最大灰阶索引 + pattern_params: [[R, G, B], ...] 8bit pattern参数,用于计算input_level + 如果提供,则使用 pattern_value/255 作为底数 + 如果不提供,则使用传统的 灰阶百分比 作为底数 + + Returns: + results_with_gamma: [[x, y, lv, gamma], ...] + """ + if not results: + raise ValueError("测量结果为空") + + # 提取亮度值 + luminance_values = [result[2] for result in results] + + if len(luminance_values) < 2: + raise ValueError(f"数据点数量不足: {len(luminance_values)}") + + # 使用实际的最大亮度值 + max_luminance = max(luminance_values) + min_luminance = min(luminance_values) + + if max_luminance <= 0: + raise ValueError(f"最大亮度无效: {max_luminance}") + + # 判断数据排列顺序 + is_descending = luminance_values[0] > luminance_values[-1] + + results_with_gamma = [] + valid_gamma_sum = 0.0 + valid_gamma_count = 0 + + for i, result in enumerate(results): + x, y, lv = result[0], result[1], result[2] + + # 归一化亮度 + L_bar = lv / max_luminance + + # 计算输入灰阶值 input_level + if pattern_params is not None and i < len(pattern_params): + # 使用 8bit pattern 数据 / 255 作为底数(22293 Gamma 数据对齐) + # 取 R 通道值(灰阶图案 R=G=B) + pattern_value = pattern_params[i][0] + input_level = pattern_value / 255.0 + else: + # 传统计算方式:根据数据顺序计算输入灰阶值 + if is_descending: + # 从亮到暗: i=0 (100%) → i=max (0%) + input_level = 1.0 - (i / max_index_fix) if max_index_fix > 0 else 0 + else: + # 从暗到亮: i=0 (0%) → i=max (100%) + input_level = (i / max_index_fix) if max_index_fix > 0 else 0 + + # 计算 Gamma + gamma = 0.0 + + # 跳过极端值(接近 0% 和 100%) + if input_level <= 0.01 or input_level >= 0.99 or L_bar <= 0.001: + gamma = 0.0 + else: + try: + log_L = math.log(L_bar) + log_I = math.log(input_level) + gamma = log_L / log_I + + # 合理性检查 + if 0.5 <= gamma <= 5.0: + valid_gamma_sum += gamma + valid_gamma_count += 1 + else: + gamma = 0.0 + + except (ValueError, ZeroDivisionError): + gamma = 0.0 + + results_with_gamma.append( + [round(x, 7), round(y, 7), round(lv, 7), round(gamma, 4)] + ) + + return results_with_gamma + + +def calculate_L_bar(results): + """ + 计算归一化亮度列表(用于绘制 Gamma 曲线) + + Args: + results: 测量结果列表 [[x, y, lv, X, Y, Z], ...] + + Returns: + L_bar: 归一化亮度列表 [0.0, 0.1, 0.2, ..., 1.0] + """ + if not results: + return [] + + # 提取亮度值 + luminance_values = [result[2] for result in results] + + # 使用实际的最大亮度值 + max_luminance = max(luminance_values) + + if max_luminance <= 0: + return [0.0] * len(luminance_values) + + # 归一化 + L_bar = [lv / max_luminance for lv in luminance_values] + + return L_bar + + +# ============ +# 色度坐标提取 +# ============ + + +def calculate_cct_from_results(results): + """ + 从测量结果提取色度坐标(xy 坐标) + + ⚠️ 注意:方法名保持不变(历史原因),实际功能是提取色度坐标 + + Args: + results: [[x, y, lv, ...], ...] 测量结果列表 + + Returns: + dict: { + 'x': [x1, x2, x3, ...], + 'y': [y1, y2, y3, ...], + 'lv': [lv1, lv2, lv3, ...] + } + """ + x_values = [] + y_values = [] + lv_values = [] + + for result in results: + try: + if isinstance(result, (list, tuple)) and len(result) >= 2: + x = float(result[0]) + y = float(result[1]) + x_values.append(x) + y_values.append(y) + + # 如果有亮度值也保存(可选) + if len(result) >= 3: + lv = float(result[2]) + lv_values.append(lv) + else: + # 数据格式错误时的默认值(D65 白点) + x_values.append(0.3127) + y_values.append(0.3290) + lv_values.append(0.0) + + except Exception as e: + print(f"⚠️ 提取色度坐标失败: {str(e)}") + x_values.append(0.3127) + y_values.append(0.3290) + lv_values.append(0.0) + + return {"x": x_values, "y": y_values, "lv": lv_values if lv_values else None} + + +# ======== +# 测试代码 +# ======== +if __name__ == "__main__": + print("=" * 60) + print("PQ 算法库测试(使用 shapely 库)") + print("=" * 60) + + # 测试 1: DCI-P3 覆盖率 + print("\n1. 测试 DCI-P3 覆盖率计算") + test_points = [[0.680, 0.320], [0.265, 0.690], [0.150, 0.060]] + area, coverage = calculate_gamut_coverage_DCIP3(test_points) + print(f" DCI-P3 覆盖率: {coverage:.2f}% (期望: 100.00%)") + + # 测试 2: BT.709 覆盖率 + print("\n2. 测试 BT.709 覆盖率计算(测量色域超出标准)") + test_results_bt709 = [ + [0.6637, 0.3236, 41.90], + [0.3087, 0.6250, 160.29], + [0.1435, 0.0447, 12.69], + ] + area, coverage = calculate_gamut_coverage_BT709(test_results_bt709) + print(f" BT.709 覆盖率: {coverage:.2f}% (期望: ~98%)") + + # 测试 3: BT.2020 覆盖率 + print("\n3. 测试 BT.2020 覆盖率计算(测量色域小于标准)") + test_results_bt2020 = [ + [0.6662, 0.3216, 39.75], + [0.3096, 0.6282, 155.24], + [0.1440, 0.0452, 11.96], + ] + area, coverage = calculate_gamut_coverage_BT2020(test_results_bt2020) + print(f" BT.2020 覆盖率: {coverage:.2f}% (期望: ~60%)") + + # 测试 4: 色度坐标提取 + print("\n4. 测试色度坐标提取") + test_results = [ + [0.3127, 0.3290, 190.5], + [0.3125, 0.3288, 150.2], + [0.3129, 0.3292, 100.8], + ] + chromaticity_data = calculate_cct_from_results(test_results) + print(f" x 坐标: {chromaticity_data['x']}") + print(f" y 坐标: {chromaticity_data['y']}") + print(f" 亮度值: {chromaticity_data['lv']}") + + # 测试 5: Gamma 计算 + print("\n5. 测试 Gamma 计算") + test_gamma_results = [ + [0.31, 0.33, 450.0, 0, 0, 0], + [0.31, 0.33, 350.0, 0, 0, 0], + [0.31, 0.33, 250.0, 0, 0, 0], + [0.31, 0.33, 150.0, 0, 0, 0], + [0.31, 0.33, 50.0, 0, 0, 0], + [0.31, 0.33, 0.1, 0, 0, 0], + ] + gamma_results = calculate_gamma(test_gamma_results, 5) + for i, result in enumerate(gamma_results): + print(f" 点{i+1}: Gamma={result[3]:.3f}") + + print("\n" + "=" * 60) + print("✅ 测试完成!") + print("=" * 60) diff --git a/app_version.py b/app_version.py new file mode 100644 index 0000000..6db5dc5 --- /dev/null +++ b/app_version.py @@ -0,0 +1,6 @@ +APP_NAME = "PQ 自动化测试工具" +APP_VERSION = "1.1.0" + + +def get_app_title(): + return f"{APP_NAME} v{APP_VERSION}" \ No newline at end of file diff --git a/assets/IMG_9473.PNG b/assets/IMG_9473.PNG new file mode 100644 index 0000000..6b04412 Binary files /dev/null and b/assets/IMG_9473.PNG differ diff --git a/assets/cie.png b/assets/cie.png new file mode 100644 index 0000000..7bcb748 Binary files /dev/null and b/assets/cie.png differ diff --git a/assets/cie_uv.png b/assets/cie_uv.png new file mode 100644 index 0000000..8a9e081 Binary files /dev/null and b/assets/cie_uv.png differ diff --git a/assets/connect-svgrepo-com.png b/assets/connect-svgrepo-com.png new file mode 100644 index 0000000..43095ef Binary files /dev/null and b/assets/connect-svgrepo-com.png differ diff --git a/assets/connect-svgrepo-com.svg b/assets/connect-svgrepo-com.svg new file mode 100644 index 0000000..f5a764e --- /dev/null +++ b/assets/connect-svgrepo-com.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/assets/disconnect-svgrepo-com.png b/assets/disconnect-svgrepo-com.png new file mode 100644 index 0000000..09a2a88 Binary files /dev/null and b/assets/disconnect-svgrepo-com.png differ diff --git a/assets/disconnect-svgrepo-com.svg b/assets/disconnect-svgrepo-com.svg new file mode 100644 index 0000000..98688d7 --- /dev/null +++ b/assets/disconnect-svgrepo-com.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/assets/entry_1.png b/assets/entry_1.png new file mode 100644 index 0000000..840b729 Binary files /dev/null and b/assets/entry_1.png differ diff --git a/assets/entry_2.png b/assets/entry_2.png new file mode 100644 index 0000000..9aa9893 Binary files /dev/null and b/assets/entry_2.png differ diff --git a/assets/entry_3.png b/assets/entry_3.png new file mode 100644 index 0000000..73b3a01 Binary files /dev/null and b/assets/entry_3.png differ diff --git a/assets/entry_4.png b/assets/entry_4.png new file mode 100644 index 0000000..73b3a01 Binary files /dev/null and b/assets/entry_4.png differ diff --git a/assets/gamma.ico b/assets/gamma.ico new file mode 100644 index 0000000..1728864 Binary files /dev/null and b/assets/gamma.ico differ diff --git a/assets/icon_check.bat b/assets/icon_check.bat new file mode 100644 index 0000000..d27657c --- /dev/null +++ b/assets/icon_check.bat @@ -0,0 +1,93 @@ +@echo off +setlocal ENABLEDELAYEDEXPANSION + +:: ------------------------------------------------------------ +:: Select ico file via GUI +:: ------------------------------------------------------------ +for /f "delims=" %%A in ('powershell -command ^ + "Add-Type -AssemblyName System.Windows.Forms | Out-Null; $f=New-Object Windows.Forms.OpenFileDialog; $f.Filter='Icon (*.ico)|*.ico'; if($f.ShowDialog() -eq 'OK'){Write-Output $f.FileName}"') do ( + set ICO=%%A +) + +if "%ICO%"=="" ( + echo No file selected. + pause + exit /b +) + +echo Checking icon: %ICO% +echo ----------------------------------------- + +:: ------------------------------------------------------------ +:: Extract bytes via PowerShell (safe for PNG-ICON) +:: ------------------------------------------------------------ +for /f "tokens=* delims=" %%B in ('powershell -command ^ + "[System.IO.File]::ReadAllBytes('%ICO%') -join ' '"') do ( + set RAW=%%B +) + +:: Convert byte list into array +set i=0 +for %%b in (%RAW%) do ( + set BYTE[!i!]=%%b + set /a i+=1 +) +set /a LEN=i + +:: ICONDIR.Count = bytes 4-5 +set /a COUNT=BYTE[4] + BYTE[5]*256 +echo Total frames: %COUNT% +echo. + +if %COUNT% LEQ 0 ( + echo ERROR: Invalid icon or no frames. + pause + exit /b +) + +set HAS256=0 +set FIRST256=0 + +echo Frame list: +echo ----------------------------------------- + +:: Each ICONDIRENTRY = 16 bytes starting at offset 6 +for /l %%I in (0,1,%COUNT%-1) do ( + set /a OFFSET=6 + 16 * %%I + + set W=!BYTE[%OFFSET%]! + set H=!BYTE[%OFFSET%+1]! + + if "!W!"=="0" set W=256 + if "!H!"=="0" set H=256 + + echo Frame %%I = !W! x !H! + + if "!W!"=="256" if "!H!"=="256" ( + set HAS256=1 + if %%I==0 set FIRST256=1 + ) +) + +echo. +echo ----------------------------------------- + +if %HAS256%==0 ( + echo ERROR: No 256x256 frame found. + echo This is why NSIS and Windows show a small icon. + pause + exit /b +) + +if %FIRST256%==0 ( + echo WARNING: 256x256 exists but is NOT frame 0. + echo This causes small icons in NSIS installers. + echo You should reorder frames so 256x256 is the first frame. + pause + exit /b +) + +echo SUCCESS: 256x256 frame exists AND is the first frame. +echo Icon is well-structured. +pause +exit /b \ No newline at end of file diff --git a/assets/icons8_double_right_24px.png b/assets/icons8_double_right_24px.png new file mode 100644 index 0000000..4386f81 Binary files /dev/null and b/assets/icons8_double_right_24px.png differ diff --git a/assets/icons8_double_up_24px.png b/assets/icons8_double_up_24px.png new file mode 100644 index 0000000..47e55ff Binary files /dev/null and b/assets/icons8_double_up_24px.png differ diff --git a/assets/pq.ico b/assets/pq.ico new file mode 100644 index 0000000..b6dbd18 Binary files /dev/null and b/assets/pq.ico differ diff --git a/assets/refresh-svgrepo-com.png b/assets/refresh-svgrepo-com.png new file mode 100644 index 0000000..3eafc0b Binary files /dev/null and b/assets/refresh-svgrepo-com.png differ diff --git a/assets/refresh-svgrepo-com.svg b/assets/refresh-svgrepo-com.svg new file mode 100644 index 0000000..a53a5b0 --- /dev/null +++ b/assets/refresh-svgrepo-com.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/assets/zx_LOGO.png b/assets/zx_LOGO.png new file mode 100644 index 0000000..2b423c7 Binary files /dev/null and b/assets/zx_LOGO.png differ diff --git a/installer.nsi b/installer.nsi new file mode 100644 index 0000000..014c09f --- /dev/null +++ b/installer.nsi @@ -0,0 +1,131 @@ +Unicode True +SetCompressor /SOLID lzma +RequestExecutionLevel user + +!include "MUI2.nsh" +!include "LogicLib.nsh" + +!define PROJECT_ROOT "." +!define DIST_ROOT "${PROJECT_ROOT}\\dist\\pqAutomationApp" +!define APP_EXE "pqAutomationApp.exe" +!define APP_ID "PQAutomationApp" + +; ------------------------------------------------------------ +; Detect Python from PATH +; ------------------------------------------------------------ +!define PYTHON_CMD "python" + +!system '"${PYTHON_CMD}" -V > python_check.txt 2>&1' +!searchparse /file python_check.txt "Python " PY_VER "" +!if "${PY_VER}" == "" + !error "Python not found. Ensure python.exe is available in PATH." +!endif +!delfile python_check.txt + +; ------------------------------------------------------------ +; Extract APP_NAME and APP_VERSION from Python code +; (App version file may contain Unicode, so we use Python to output ASCII) +; ------------------------------------------------------------ +!system '"${PYTHON_CMD}" -c "import app_version; print(app_version.APP_NAME)" > app_name.txt' +!system '"${PYTHON_CMD}" -c "import app_version; print(app_version.APP_VERSION)" > app_version.txt' + +!searchparse /file "app_name.txt" "" APP_NAME "" +!searchparse /file "app_version.txt" "" APP_VERSION "" + +!delfile "app_name.txt" +!delfile "app_version.txt" + +; ------------------------------------------------------------ +; Registry Keys +; ------------------------------------------------------------ +!define UNINSTALL_REG_KEY "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${APP_ID}" +!define APP_REG_KEY "Software\\${APP_ID}" + +Name "${APP_NAME} ${APP_VERSION}" +OutFile "dist\\PQAutomationApp_Setup_${APP_VERSION}.exe" +InstallDir "$LOCALAPPDATA\\Programs\\${APP_ID}" +InstallDirRegKey HKCU "${APP_REG_KEY}" "InstallDir" + +!define MUI_ABORTWARNING +!define MUI_ICON "assets\\pq.ico" +!define MUI_UNICON "assets\\pq.ico" + +!insertmacro MUI_PAGE_WELCOME +!insertmacro MUI_PAGE_DIRECTORY +!insertmacro MUI_PAGE_INSTFILES +!insertmacro MUI_PAGE_FINISH + +!insertmacro MUI_UNPAGE_CONFIRM +!insertmacro MUI_UNPAGE_INSTFILES +!insertmacro MUI_UNPAGE_FINISH + +!insertmacro MUI_LANGUAGE "English" + + +; ------------------------------------------------------------ +; Init +; ------------------------------------------------------------ +Function .onInit + IfFileExists "${DIST_ROOT}\\${APP_EXE}" +2 0 + MessageBox MB_ICONSTOP|MB_OK "Executable not found: ${DIST_ROOT}\\${APP_EXE}$\r$\nPlease build using PyInstaller first." + IfFileExists "${DIST_ROOT}\\${APP_EXE}" +2 0 + Abort +FunctionEnd + + +; ------------------------------------------------------------ +; Installation Section +; ------------------------------------------------------------ +Section "Main Installation" SEC_MAIN + SetOutPath "$INSTDIR" + CreateDirectory "$INSTDIR" + CreateDirectory "$INSTDIR\\internal" + CreateDirectory "$INSTDIR\\settings" + + File "${DIST_ROOT}\\${APP_EXE}" + + SetOutPath "$INSTDIR\\internal" + File /r "${DIST_ROOT}\\internal\\*.*" + + IfFileExists "${PROJECT_ROOT}\\settings\\pq_config.json" 0 +3 + SetOutPath "$INSTDIR\\settings" + File /oname=pq_config.json "${PROJECT_ROOT}\\settings\\pq_config.json" + + WriteUninstaller "$INSTDIR\\Uninstall.exe" + + WriteRegStr HKCU "${APP_REG_KEY}" "InstallDir" "$INSTDIR" + WriteRegStr HKCU "${UNINSTALL_REG_KEY}" "DisplayName" "${APP_NAME}" + WriteRegStr HKCU "${UNINSTALL_REG_KEY}" "DisplayVersion" "${APP_VERSION}" + WriteRegStr HKCU "${UNINSTALL_REG_KEY}" "InstallLocation" "$INSTDIR" + WriteRegStr HKCU "${UNINSTALL_REG_KEY}" "DisplayIcon" "$INSTDIR\\${APP_EXE}" + WriteRegStr HKCU "${UNINSTALL_REG_KEY}" "Publisher" "Moka" + WriteRegStr HKCU "${UNINSTALL_REG_KEY}" "UninstallString" "$\"$INSTDIR\\Uninstall.exe$\"" + WriteRegDWORD HKCU "${UNINSTALL_REG_KEY}" "NoModify" 1 + WriteRegDWORD HKCU "${UNINSTALL_REG_KEY}" "NoRepair" 1 + + CreateDirectory "$SMPROGRAMS\\${APP_NAME}" + CreateShortcut "$SMPROGRAMS\\${APP_NAME}\\${APP_NAME}.lnk" "$INSTDIR\\${APP_EXE}" + CreateShortcut "$SMPROGRAMS\\${APP_NAME}\\Uninstall ${APP_NAME}.lnk" "$INSTDIR\\Uninstall.exe" + CreateShortcut "$DESKTOP\\${APP_NAME}.lnk" "$INSTDIR\\${APP_EXE}" +SectionEnd + + +; ------------------------------------------------------------ +; Uninstall Section +; ------------------------------------------------------------ +Section "Uninstall" + Delete "$DESKTOP\\${APP_NAME}.lnk" + Delete "$SMPROGRAMS\\${APP_NAME}\\${APP_NAME}.lnk" + Delete "$SMPROGRAMS\\${APP_NAME}\\Uninstall ${APP_NAME}.lnk" + RMDir "$SMPROGRAMS\\${APP_NAME}" + + Delete "$INSTDIR\\${APP_EXE}" + Delete "$INSTDIR\\Uninstall.exe" + Delete "$INSTDIR\\settings\\pq_config.json" + RMDir /r "$INSTDIR\\internal" + RMDir /r "$INSTDIR\\settings" + RMDir "$INSTDIR" + + DeleteRegKey HKCU "${UNINSTALL_REG_KEY}" + DeleteRegKey HKCU "${APP_REG_KEY}" +SectionEnd \ No newline at end of file diff --git a/pqAutomationApp.py b/pqAutomationApp.py new file mode 100644 index 0000000..2135651 --- /dev/null +++ b/pqAutomationApp.py @@ -0,0 +1,9751 @@ +import ttkbootstrap as ttk +import tkinter as tk +from tkinter import messagebox, filedialog +import sys +import threading +import time +import os +import datetime +import colour +import json +import traceback +import numpy as np +import matplotlib.pyplot as plt +import matplotlib.image as mpimg +import algorithm.pq_algorithm as pq_algorithm +from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg +from app_version import APP_NAME, APP_VERSION, get_app_title +from utils.caSerail import CASerail +from utils.tvSerail import tvSerial +from utils.UCD323_Function import UCDController +from utils.UCD323_Enum import UCDEnum +from utils.pq.pq_config import PQConfig +from utils.pq.pq_result import PQResult +from utils.data_range_converter import convert_pattern_params +from PIL import Image, ImageTk +from views.collapsing_frame import CollapsingFrame + +# from views.pq_history_gui import PQHistoryGUI +from views.pq_log_gui import PQLogGUI +from colormath.color_objects import xyYColor, LabColor +from colormath.color_conversions import convert_color +from colormath.color_diff import delta_e_cie2000 +from views.pq_debug_panel import PQDebugPanel + +plt.rcParams["font.family"] = ["sans-serif"] +plt.rcParams["font.sans-serif"] = ["Microsoft YaHei"] + + +def get_resource_path(relative_path): + """ + 获取资源文件的绝对路径(兼容开发环境和打包后) + + Args: + relative_path: 相对路径,如 "assets/cie.png" + + Returns: + str: 资源文件的绝对路径 + """ + try: + # PyInstaller 打包后的临时文件夹路径 + base_path = sys._MEIPASS + except AttributeError: + # 开发环境:使用脚本所在目录 + base_path = os.path.dirname(os.path.abspath(__file__)) + + return os.path.join(base_path, relative_path) + + +def load_icon(png_path): + """加载并调整图标大小为64x64""" + img = Image.open(get_resource_path(png_path)) + img = img.resize((24, 24), Image.LANCZOS) + return ImageTk.PhotoImage(img) + + +def backgroud_style_set(): + style = ttk.Style() + # 移除背景色设置,使用默认背景色 + style.configure( + "SidebarSelected.TButton", + # anchor="w", + padding=10, + background="#005470", + ) + + +class PQAutomationApp: + def __init__(self, root): + self.root = root + self.root.title(get_app_title()) + self.root.geometry("900x650") + self.root.minsize(900, 650) + self.root.protocol("WM_DELETE_WINDOW", self.on_closing) + self.app_name = APP_NAME + self.app_version = APP_VERSION + + self.config_cleared = False + + # 初始化设备连接状态 + self.ca = None # CA410色度计 + self.ucd = UCDController() # 信号发生器 + + # 初始化测试状态 + self.testing = False + self.test_thread = None + # 采集节奏参数:默认在稳定性与速度之间取平衡,可按现场情况再微调。 + self.pattern_settle_time = 0.4 + self.pattern_progress_log_step = 5 + + # 创建主框架 + self.main_frame = ttk.Frame(root) + + self.main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) + + backgroud_style_set() + + # 创建配置对象 + self.config = PQConfig() + self.results = None + + # 加载上次保存的设置 + self.config_file = self.get_config_path() + self.load_pq_config() + + # 如果加载的配置不是屏模组,强制切换为屏模组 + if self.config.current_test_type != "screen_module": + self.config.set_current_test_type("screen_module") + + # 初始化侧边栏功能显示状态 - 使用统一的页面管理 + self.current_panel = None # 当前显示的面板名称 + self.panels = {} # 存储所有面板的信息 + self.log_visible = False + + # 创建左侧面板 + self.left_frame = ttk.Frame(self.main_frame, width=180) + self.left_frame.pack(side=tk.LEFT, fill=tk.Y, padx=0, pady=5) + self.left_frame.pack_propagate(False) + + # 创建左侧导航栏 + self.sidebar_frame = ttk.Frame(self.left_frame, bootstyle="primary") + self.sidebar_frame.pack(fill=tk.BOTH, expand=True, padx=0, pady=5) + # self.sidebar_frame.pack_propagate(False) + + # 创建右侧内容区域 + self.content_frame = ttk.Frame(self.main_frame) + self.content_frame.pack( + side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5, pady=5 + ) + + # 创建右侧内容区域的上中下三个分区 + self.control_frame_top = ttk.Frame(self.content_frame) + self.control_frame_top.pack( + side=tk.TOP, fill=tk.BOTH, expand=True, padx=0, pady=5 + ) + + self.control_frame_middle = ttk.Frame(self.content_frame) + self.control_frame_middle.pack( + side=tk.TOP, fill=tk.BOTH, expand=True, padx=0, pady=5 + ) + + self.control_frame_bottom = ttk.Frame(self.content_frame) + self.control_frame_bottom.pack( + side=tk.TOP, fill=tk.BOTH, expand=True, padx=0, pady=5 + ) + + # 创建右上角悬浮配置框 + self.create_floating_config_panel() + + # 创建右侧结果显示区域 + self.result_frame = ttk.LabelFrame(self.control_frame_middle, text="测试结果") + self.result_frame.pack( + side=tk.BOTTOM, fill=tk.BOTH, expand=True, padx=0, pady=5 + ) + + # 创建日志显示区域 + self.create_log_panel() + + # 创建 Local Dimming 面板 + self.create_local_dimming_panel() + + # 创建测试类型选择区域 + self.create_test_type_frame() + + # 创建操作按钮区域 + self.create_operation_frame() + + # 创建结果图表区域 + self.create_result_chart_frame() + + # 创建客户模板结果显示区域(黑底表格) + self.create_custom_template_result_panel() + + # 在所有控件创建完成后,统一初始化测试类型 + self.root.after(100, self.initialize_default_test_type) + + # 状态栏 + self.status_var = tk.StringVar(value="就绪") + self.status_bar = ttk.Label( + root, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W + ) + self.status_bar.pack(side=tk.BOTTOM, fill=tk.X) + + def get_config_path(self): + """获取配置文件的完整路径(兼容打包后的程序)""" + import os + import sys + + # 判断是否是打包后的程序 + if getattr(sys, "frozen", False): + # 打包后:使用可执行文件所在目录 + base_path = os.path.dirname(sys.executable) + else: + # 开发环境:使用脚本所在目录 + base_path = os.path.dirname(os.path.abspath(__file__)) + + # 构建配置文件路径 + config_dir = os.path.join(base_path, "settings") + config_file = os.path.join(config_dir, "pq_config.json") + + # 确保 settings 目录存在 + if not os.path.exists(config_dir): + os.makedirs(config_dir) + + return config_file + + def initialize_default_test_type(self): + """初始化默认测试类型(在所有控件创建完成后调用)""" + try: + # 强制切换到屏模组 + self.change_test_type("screen_module") + + if hasattr(self, "log_gui"): + self.log_gui.log("✓ 默认测试类型已设置为屏模组") + except Exception as e: + if hasattr(self, "log_gui"): + self.log_gui.log(f"初始化默认测试类型失败: {str(e)}") + + def init_gamut_chart(self): + """初始化色域图表 - 手动设置subplot位置,完全避免重叠""" + container = ttk.Frame(self.gamut_chart_frame) + container.pack(expand=True, fill=tk.BOTH) + + self.gamut_fig = plt.Figure(figsize=(14, 6), dpi=100) + self.gamut_canvas = FigureCanvasTkAgg(self.gamut_fig, master=container) + + canvas_widget = self.gamut_canvas.get_tk_widget() + canvas_widget.pack(expand=True, fill=tk.BOTH) + + # ✅ 恢复原来的大尺寸:0.84 高度 + self.gamut_ax_xy = self.gamut_fig.add_axes( + [0.02, 0.08, 0.46, 0.84] + ) # ← 改回 0.84 + self.gamut_ax_uv = self.gamut_fig.add_axes( + [0.52, 0.08, 0.46, 0.84] + ) # ← 改回 0.84 + + # 初始化XY图 + self.gamut_ax_xy.set_xlim(0, 600) + self.gamut_ax_xy.set_ylim(600, 0) + self.gamut_ax_xy.axis("off") + self.gamut_ax_xy.set_clip_on(False) + + # 初始化UV图 + self.gamut_ax_uv.set_xlim(0, 600) + self.gamut_ax_uv.set_ylim(600, 0) + self.gamut_ax_uv.axis("off") + self.gamut_ax_uv.set_clip_on(False) + + # 调整标题位置:y=0.98 + self.gamut_fig.suptitle("色域测试", fontsize=12, y=0.98) + + self.gamut_canvas.draw() + + def init_gamma_chart(self): + """初始化Gamma曲线图表 - 左侧曲线 + 右侧表格(✅ 4列 + 通用说明)""" + container = ttk.Frame(self.gamma_chart_frame) + container.pack(expand=True, fill=tk.BOTH) + + self.gamma_fig = plt.Figure(figsize=(12, 6), dpi=100, constrained_layout=False) + self.gamma_canvas = FigureCanvasTkAgg(self.gamma_fig, master=container) + + canvas_widget = self.gamma_canvas.get_tk_widget() + canvas_widget.pack(expand=True, fill=tk.BOTH) + + # 左侧:Gamma 曲线 + self.gamma_ax = self.gamma_fig.add_axes([0.08, 0.12, 0.50, 0.78]) + self.gamma_ax.set_xlabel("灰阶 (%)", fontsize=10) + self.gamma_ax.set_ylabel("L_bar", fontsize=10) + self.gamma_ax.set_xlim(0, 105) + self.gamma_ax.set_ylim(0, 1.1) + self.gamma_ax.grid(True, linestyle="--", alpha=0.3) + self.gamma_ax.tick_params(labelsize=9) + + # 左侧提示(通用说明,不显示具体 Gamma 值) + self.gamma_ax.text( + 0.5, + 0.5, + "等待测试数据...\n\n" + "将显示:\n" + "• 实测曲线 (蓝色)\n" + "• 理想 Gamma 曲线 (红色)\n\n" + "Gamma 值由测试配置决定", + ha="center", + va="center", + fontsize=10, + color="gray", + transform=self.gamma_ax.transAxes, + bbox=dict( + boxstyle="round,pad=1", facecolor="white", edgecolor="gray", alpha=0.8 + ), + ) + + # 右侧:数据表格 + self.gamma_table_ax = self.gamma_fig.add_axes([0.62, 0.12, 0.35, 0.78]) + self.gamma_table_ax.axis("off") + + # 4列表格数据 + table_data = [ + ["灰阶", "实测亮度\n(cd/m²)", "L_bar\n(计算)", "Gamma"], + ["0%", "--", "--", "--"], + ["10%", "--", "--", "--"], + ["20%", "--", "--", "--"], + ["30%", "--", "--", "--"], + ["40%", "--", "--", "--"], + ["50%", "--", "--", "--"], + ["60%", "--", "--", "--"], + ["70%", "--", "--", "--"], + ["80%", "--", "--", "--"], + ["90%", "--", "--", "--"], + ["100%", "--", "--", "--"], + ] + + table = self.gamma_table_ax.table( + cellText=table_data, + cellLoc="center", + loc="center", + colWidths=[0.18, 0.28, 0.27, 0.27], # ← 4列宽度 + ) + + table.auto_set_font_size(False) + table.set_fontsize(7.5) + table.scale(1, 1.5) + + # 表头样式 + for i in range(4): + cell = table[(0, i)] + cell.set_facecolor("#4472C4") + cell.set_text_props(weight="bold", color="white", fontsize=7) + + # 数据行交替颜色 + for i in range(1, len(table_data)): + for j in range(4): + cell = table[(i, j)] + if i % 2 == 0: + cell.set_facecolor("#E7E6E6") + else: + cell.set_facecolor("#FFFFFF") + + # 底部说明 + self.gamma_table_ax.text( + 0.5, + 0.02, + "表格说明:\n" + "• 实测亮度: 色度计测量值 (cd/m²)\n" + "• L_bar: 归一化亮度 (0-1)\n" + "• Gamma: 实际 Gamma 值", + ha="center", + va="bottom", + fontsize=7, + color="gray", + transform=self.gamma_table_ax.transAxes, + bbox=dict( + boxstyle="round,pad=0.5", + facecolor="lightyellow", + edgecolor="gray", + alpha=0.8, + ), + ) + + self.gamma_fig.suptitle("Gamma曲线 + 数据表格", fontsize=12, y=0.98) + self.gamma_canvas.draw() + + def init_eotf_chart(self): + """初始化 EOTF 曲线图表(HDR 专用)- 左侧曲线 + 右侧表格(✅ 4列)""" + container = ttk.Frame(self.eotf_chart_frame) + container.pack(expand=True, fill=tk.BOTH) + + self.eotf_fig = plt.Figure(figsize=(12, 6), dpi=100, constrained_layout=False) + self.eotf_canvas = FigureCanvasTkAgg(self.eotf_fig, master=container) + + canvas_widget = self.eotf_canvas.get_tk_widget() + canvas_widget.pack(expand=True, fill=tk.BOTH) + + # 左侧:EOTF 曲线 + self.eotf_ax = self.eotf_fig.add_axes([0.08, 0.12, 0.50, 0.78]) + self.eotf_ax.set_xlabel("灰阶 (%)", fontsize=10) + self.eotf_ax.set_ylabel("L_bar (归一化亮度)", fontsize=10) + self.eotf_ax.set_xlim(0, 105) + self.eotf_ax.set_ylim(0, 1.1) + self.eotf_ax.grid(True, linestyle="--", alpha=0.3) + self.eotf_ax.tick_params(labelsize=9) + + # 左侧提示 + self.eotf_ax.text( + 0.5, + 0.5, + "等待测试数据...\n\n将显示:\n• 实测 EOTF 曲线 (蓝色)\n• 理想 PQ 曲线 (红色)", + ha="center", + va="center", + fontsize=11, + color="gray", + transform=self.eotf_ax.transAxes, + bbox=dict( + boxstyle="round,pad=1", facecolor="white", edgecolor="gray", alpha=0.8 + ), + ) + + # 右侧:数据表格 + self.eotf_table_ax = self.eotf_fig.add_axes([0.62, 0.12, 0.35, 0.78]) + self.eotf_table_ax.axis("off") + + # 4列表格数据 + table_data = [ + ["灰阶", "实测亮度\n(cd/m²)", "L_bar\n(计算)", "EOTF γ"], + ["0%", "--", "--", "--"], + ["10%", "--", "--", "--"], + ["20%", "--", "--", "--"], + ["30%", "--", "--", "--"], + ["40%", "--", "--", "--"], + ["50%", "--", "--", "--"], + ["60%", "--", "--", "--"], + ["70%", "--", "--", "--"], + ["80%", "--", "--", "--"], + ["90%", "--", "--", "--"], + ["100%", "--", "--", "--"], + ] + + table = self.eotf_table_ax.table( + cellText=table_data, + cellLoc="center", + loc="center", + colWidths=[0.18, 0.28, 0.27, 0.27], + ) + + table.auto_set_font_size(False) + table.set_fontsize(7.5) + table.scale(1, 1.5) + + # 表头样式 + for i in range(4): + cell = table[(0, i)] + cell.set_facecolor("#4472C4") + cell.set_text_props(weight="bold", color="white", fontsize=7) + + # 数据行交替颜色 + for i in range(1, len(table_data)): + for j in range(4): + cell = table[(i, j)] + if i % 2 == 0: + cell.set_facecolor("#E7E6E6") + else: + cell.set_facecolor("#FFFFFF") + + # 底部说明 + self.eotf_table_ax.text( + 0.5, + 0.02, + "表格说明:\n" + "• 实测亮度: 色度计测量值 (cd/m²)\n" + "• L_bar: 归一化亮度 (0-1)\n" + "• EOTF γ: HDR 实际 Gamma 值", + ha="center", + va="bottom", + fontsize=7, + color="gray", + transform=self.eotf_table_ax.transAxes, + bbox=dict( + boxstyle="round,pad=0.5", + facecolor="lightyellow", + edgecolor="gray", + alpha=0.8, + ), + ) + + self.eotf_fig.suptitle("EOTF 曲线 + 数据表格", fontsize=12, y=0.98) + self.eotf_canvas.draw() + + def init_cct_chart(self): + """初始化色度坐标图表 - 正向横坐标,标题居中最上方""" + container = ttk.Frame(self.cct_chart_frame) + container.pack(expand=True) + + self.cct_fig = plt.Figure(figsize=(8, 6), dpi=100, tight_layout=False) + self.cct_canvas = FigureCanvasTkAgg(self.cct_fig, master=container) + + canvas_widget = self.cct_canvas.get_tk_widget() + canvas_widget.pack() + canvas_widget.config(width=800, height=600) + canvas_widget.pack_propagate(False) + + self.cct_ax1 = self.cct_fig.add_subplot(211) + self.cct_ax2 = self.cct_fig.add_subplot(212) + + # 上图:x coordinates + self.cct_ax1.set_xlabel("灰阶 (%)", fontsize=9) + self.cct_ax1.set_ylabel("CIE x", fontsize=9) + self.cct_ax1.set_xlim(0, 105) + self.cct_ax1.set_ylim(0.25, 0.35) + self.cct_ax1.grid(True, linestyle="--", alpha=0.3) + self.cct_ax1.tick_params(labelsize=8) + + # 下图:y coordinates + self.cct_ax2.set_xlabel("灰阶 (%)", fontsize=9) + self.cct_ax2.set_ylabel("CIE y", fontsize=9) + self.cct_ax2.set_xlim(0, 105) + self.cct_ax2.set_ylim(0.25, 0.35) + self.cct_ax2.grid(True, linestyle="--", alpha=0.3) + self.cct_ax2.tick_params(labelsize=8) + + # 调整标题位置:y=0.985(比色域/Gamma略高) + self.cct_fig.suptitle("色度一致性测试", fontsize=12, y=0.985) + + self.cct_fig.subplots_adjust( + left=0.12, + right=0.88, + top=0.90, + bottom=0.08, + hspace=0.25, + ) + + self.cct_canvas.draw() + + def init_contrast_chart(self): + """初始化对比度图表 - 固定大小,居中显示""" + container = ttk.Frame(self.contrast_chart_frame) + container.pack(expand=True) + + self.contrast_fig = plt.Figure( + figsize=(6, 6), + dpi=100, + tight_layout=False, + ) + self.contrast_canvas = FigureCanvasTkAgg(self.contrast_fig, master=container) + + canvas_widget = self.contrast_canvas.get_tk_widget() + canvas_widget.pack() + + canvas_widget.config(width=600, height=600) + canvas_widget.pack_propagate(False) + + self.contrast_ax = self.contrast_fig.add_subplot(111) + self.contrast_ax.set_xlim(0, 1) + self.contrast_ax.set_ylim(0, 1) + self.contrast_ax.axis("off") + + # 调整标题位置:y=0.985 + self.contrast_fig.suptitle("对比度测试", fontsize=12, y=0.985) + + self.contrast_fig.subplots_adjust( + left=0.02, + right=0.98, + top=0.90, + bottom=0.02, + ) + + self.contrast_canvas.draw() + + def init_accuracy_chart(self): + """初始化色准图表 - 固定大小,居中显示""" + container = ttk.Frame(self.accuracy_chart_frame) + container.pack(expand=True) + + self.accuracy_fig = plt.Figure( + figsize=(10, 6), + dpi=100, + tight_layout=False, + ) + self.accuracy_canvas = FigureCanvasTkAgg(self.accuracy_fig, master=container) + + canvas_widget = self.accuracy_canvas.get_tk_widget() + canvas_widget.pack() + + canvas_widget.config(width=1000, height=600) + canvas_widget.pack_propagate(False) + + self.accuracy_ax = self.accuracy_fig.add_subplot(111) + self.accuracy_ax.set_xlim(0, 1) + self.accuracy_ax.set_ylim(0, 1) + self.accuracy_ax.axis("off") + + # 调整标题位置 + self.accuracy_fig.suptitle("色准测试", fontsize=12, y=0.985) + + self.accuracy_fig.subplots_adjust( + left=0.05, + right=0.95, + top=0.90, + bottom=0.05, + ) + + self.accuracy_canvas.draw() + + def clear_chart(self): + """清空所有图表""" + + # ========== 1. 清空色域图表 ========== + if hasattr(self, "gamut_ax_xy") and hasattr(self, "gamut_ax_uv"): + # 清空XY图 + self.gamut_ax_xy.clear() + self.gamut_ax_xy.set_xlim(0, 600) + self.gamut_ax_xy.set_ylim(600, 0) + self.gamut_ax_xy.axis("off") + self.gamut_ax_xy.set_clip_on(False) + + # 清空UV图 + self.gamut_ax_uv.clear() + self.gamut_ax_uv.set_xlim(0, 600) + self.gamut_ax_uv.set_ylim(600, 0) + self.gamut_ax_uv.axis("off") + self.gamut_ax_uv.set_clip_on(False) + + self.gamut_fig.suptitle("色域测试", fontsize=12, y=0.98) + self.gamut_canvas.draw() + + # ========== 2. 清空Gamma图表(4列 + 通用说明)========== + if hasattr(self, "gamma_ax") and hasattr(self, "gamma_table_ax"): + # 清空左侧曲线 + self.gamma_ax.clear() + self.gamma_ax.set_xlim(0, 105) + self.gamma_ax.set_ylim(0, 1.1) + self.gamma_ax.set_xlabel("灰阶 (%)", fontsize=10) + self.gamma_ax.set_ylabel("L_bar", fontsize=10) + self.gamma_ax.grid(True, linestyle="--", alpha=0.3) + self.gamma_ax.tick_params(labelsize=9) + + # 左侧提示 + self.gamma_ax.text( + 0.5, + 0.5, + "等待测试数据...\n\n" + "将显示:\n" + "• 实测曲线 (蓝色)\n" + "• 理想 Gamma 曲线 (红色)\n\n" + "Gamma 值由测试配置决定", + ha="center", + va="center", + fontsize=10, + color="gray", + transform=self.gamma_ax.transAxes, + bbox=dict( + boxstyle="round,pad=1", + facecolor="white", + edgecolor="gray", + alpha=0.8, + ), + ) + + # 清空右侧表格 + self.gamma_table_ax.clear() + self.gamma_table_ax.axis("off") + + # 4列表格 + table_data = [ + ["灰阶", "实测亮度\n(cd/m²)", "L_bar\n(计算)", "Gamma"], + ["0%", "--", "--", "--"], + ["10%", "--", "--", "--"], + ["20%", "--", "--", "--"], + ["30%", "--", "--", "--"], + ["40%", "--", "--", "--"], + ["50%", "--", "--", "--"], + ["60%", "--", "--", "--"], + ["70%", "--", "--", "--"], + ["80%", "--", "--", "--"], + ["90%", "--", "--", "--"], + ["100%", "--", "--", "--"], + ] + + table = self.gamma_table_ax.table( + cellText=table_data, + cellLoc="center", + loc="center", + colWidths=[0.18, 0.28, 0.27, 0.27], + ) + + table.auto_set_font_size(False) + table.set_fontsize(7.5) + table.scale(1, 1.5) + + # 表头样式 + for i in range(4): + cell = table[(0, i)] + cell.set_facecolor("#4472C4") + cell.set_text_props(weight="bold", color="white", fontsize=7) + + # 数据行交替颜色 + for i in range(1, len(table_data)): + for j in range(4): + cell = table[(i, j)] + if i % 2 == 0: + cell.set_facecolor("#E7E6E6") + else: + cell.set_facecolor("#FFFFFF") + + # 底部说明 + self.gamma_table_ax.text( + 0.5, + 0.02, + "表格说明:\n" + "• 实测亮度: 色度计测量值 (cd/m²)\n" + "• L_bar: 归一化亮度 (0-1)\n" + "• Gamma: 实际 Gamma 值", + ha="center", + va="bottom", + fontsize=7, + color="gray", + transform=self.gamma_table_ax.transAxes, + bbox=dict( + boxstyle="round,pad=0.5", + facecolor="lightyellow", + edgecolor="gray", + alpha=0.8, + ), + ) + + self.gamma_fig.suptitle("Gamma曲线 + 数据表格", fontsize=12, y=0.98) + self.gamma_canvas.draw() + + # ========== 3. 清空EOTF图表(4列)========== + if hasattr(self, "eotf_ax") and hasattr(self, "eotf_table_ax"): + # 清空左侧曲线 + self.eotf_ax.clear() + self.eotf_ax.set_xlim(0, 105) + self.eotf_ax.set_ylim(0, 1.1) + self.eotf_ax.set_xlabel("灰阶 (%)", fontsize=10) + self.eotf_ax.set_ylabel("L_bar (归一化亮度)", fontsize=10) + self.eotf_ax.grid(True, linestyle="--", alpha=0.3) + self.eotf_ax.tick_params(labelsize=9) + + # 左侧提示 + self.eotf_ax.text( + 0.5, + 0.5, + "等待测试数据...\n\n将显示:\n• 实测 EOTF 曲线 (蓝色)\n• 理想 PQ 曲线 (红色)", + ha="center", + va="center", + fontsize=11, + color="gray", + transform=self.eotf_ax.transAxes, + bbox=dict( + boxstyle="round,pad=1", + facecolor="white", + edgecolor="gray", + alpha=0.8, + ), + ) + + # 清空右侧表格 + self.eotf_table_ax.clear() + self.eotf_table_ax.axis("off") + + # 4列表格 + table_data = [ + ["灰阶", "实测亮度\n(cd/m²)", "L_bar\n(计算)", "EOTF γ"], + ["0%", "--", "--", "--"], + ["10%", "--", "--", "--"], + ["20%", "--", "--", "--"], + ["30%", "--", "--", "--"], + ["40%", "--", "--", "--"], + ["50%", "--", "--", "--"], + ["60%", "--", "--", "--"], + ["70%", "--", "--", "--"], + ["80%", "--", "--", "--"], + ["90%", "--", "--", "--"], + ["100%", "--", "--", "--"], + ] + + table = self.eotf_table_ax.table( + cellText=table_data, + cellLoc="center", + loc="center", + colWidths=[0.18, 0.28, 0.27, 0.27], + ) + + table.auto_set_font_size(False) + table.set_fontsize(7.5) + table.scale(1, 1.5) + + # 表头样式 + for i in range(4): + cell = table[(0, i)] + cell.set_facecolor("#4472C4") + cell.set_text_props(weight="bold", color="white", fontsize=7) + + # 数据行交替颜色 + for i in range(1, len(table_data)): + for j in range(4): + cell = table[(i, j)] + if i % 2 == 0: + cell.set_facecolor("#E7E6E6") + else: + cell.set_facecolor("#FFFFFF") + + # 底部说明 + self.eotf_table_ax.text( + 0.5, + 0.02, + "表格说明:\n" + "• 实测亮度: 色度计测量值 (cd/m²)\n" + "• L_bar: 归一化亮度 (0-1)\n" + "• EOTF γ: HDR 实际 Gamma 值", + ha="center", + va="bottom", + fontsize=7, + color="gray", + transform=self.eotf_table_ax.transAxes, + bbox=dict( + boxstyle="round,pad=0.5", + facecolor="lightyellow", + edgecolor="gray", + alpha=0.8, + ), + ) + + self.eotf_fig.suptitle("EOTF 曲线 + 数据表格", fontsize=12, y=0.98) + self.eotf_canvas.draw() + + # ========== 4. 清空色度图表 ========== + if hasattr(self, "cct_ax1") and hasattr(self, "cct_ax2"): + # 上图:x coordinates + self.cct_ax1.clear() + self.cct_ax1.set_xlabel("灰阶 (%)", fontsize=9) + self.cct_ax1.set_ylabel("CIE x", fontsize=9) + self.cct_ax1.set_xlim(0, 105) + self.cct_ax1.set_ylim(0.25, 0.35) + self.cct_ax1.grid(True, linestyle="--", alpha=0.3) + self.cct_ax1.tick_params(labelsize=8) + + # 下图:y coordinates + self.cct_ax2.clear() + self.cct_ax2.set_xlabel("灰阶 (%)", fontsize=9) + self.cct_ax2.set_ylabel("CIE y", fontsize=9) + self.cct_ax2.set_xlim(0, 105) + self.cct_ax2.set_ylim(0.25, 0.35) + self.cct_ax2.grid(True, linestyle="--", alpha=0.3) + self.cct_ax2.tick_params(labelsize=8) + + self.cct_fig.suptitle("色度一致性测试", fontsize=12, y=0.985) + + # 重置布局 + self.cct_fig.subplots_adjust( + left=0.12, + right=0.88, + top=0.90, + bottom=0.08, + hspace=0.25, + ) + + self.cct_canvas.draw() + + # ========== 5. 清空对比度图表 ========== + if hasattr(self, "contrast_ax"): + self.contrast_ax.clear() + self.contrast_ax.set_xlim(0, 1) + self.contrast_ax.set_ylim(0, 1) + self.contrast_ax.axis("off") + + self.contrast_fig.suptitle("对比度测试", fontsize=12, y=0.985) + + # 重置布局 + self.contrast_fig.subplots_adjust( + left=0.02, + right=0.98, + top=0.90, + bottom=0.02, + ) + + self.contrast_canvas.draw() + + # ========== 6. 清空色准图表 ========== + if hasattr(self, "accuracy_ax"): + self.accuracy_ax.clear() + self.accuracy_ax.set_xlim(0, 1) + self.accuracy_ax.set_ylim(0, 1) + self.accuracy_ax.axis("off") + + # 标题 + self.accuracy_fig.suptitle("色准测试", fontsize=12, y=0.985) + + # 重置布局 + self.accuracy_fig.subplots_adjust( + left=0.05, + right=0.95, + top=0.90, + bottom=0.05, + ) + + self.accuracy_canvas.draw() + + def create_floating_config_panel(self): + """创建右上角悬浮配置框""" + cf = CollapsingFrame(self.control_frame_top) + cf.pack(fill="both") + # 创建悬浮框主容器 + self.config_panel_frame = ttk.Frame(cf) + cf.add(self.config_panel_frame, title="配置项") + + # 创建一个统一的frame来替代选项卡控件 + self.config_content_frame = ttk.Frame(self.config_panel_frame) + self.config_content_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) + + # 创建一个横向排列的Frame + config_row_frame = ttk.Frame(self.config_content_frame) + config_row_frame.pack(fill=tk.X, expand=False, padx=5, pady=5) + + # 创建连接内容区域 + self.connection_frame = ttk.LabelFrame(config_row_frame, text="设备连接") + self.connection_frame.pack( + side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5 + ) + + # 创建测试项目区域 + self.test_items_frame = ttk.LabelFrame(config_row_frame, text="测试项目") + self.test_items_frame.pack( + side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5 + ) + + # 创建信号格式区域 + self.signal_format_frame = ttk.LabelFrame(config_row_frame, text="信号格式") + self.signal_format_frame.pack( + side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5 + ) + + # 创建连接内容 + self.create_connection_content() + # 创建测试项目内容 + self.create_test_items_content() + # 创建信号格式内容 + self.create_signal_format_content() + + self.config_panel_frame.grid_remove() + self.config_panel_frame.btn.configure(image="closed") + + def create_test_items_content(self): + """创建测试项目选项卡内容""" + # 创建测试项目字典,用于管理不同测试类型的选项 + self.test_items = { + "screen_module": { + "frame": ttk.Frame(self.test_items_frame), + "items": [ + ("色域", "gamut"), + ("Gamma", "gamma"), + ("色度", "cct"), + ("对比度", "contrast"), + ], + }, + "sdr_movie": { + "frame": ttk.Frame(self.test_items_frame), + "items": [ + ("色域", "gamut"), + ("Gamma", "gamma"), + ("色度", "cct"), + ("对比度", "contrast"), + ("色准", "accuracy"), + ], + }, + "hdr_movie": { + "frame": ttk.Frame(self.test_items_frame), + "items": [ + ("色域", "gamut"), + ("EOTF", "eotf"), + ("色度", "cct"), + ("对比度", "contrast"), + ("色准", "accuracy"), + ], + }, + } + + # 根据当前测试类型创建复选框 + self.test_vars = {} + self.update_test_items() + + # 创建色度参数设置框架 + self.create_cct_params_frame() + + def create_cct_params_frame(self): + """创建色度参数设置区域 - 屏模组、SDR、HDR 独立(✅ 增加色域参考标准选择 + 单步调试按钮)""" + + # ==================== 屏模组色度参数 Frame ==================== + self.cct_params_frame = ttk.LabelFrame( + self.test_items_frame, text="色度参数设置(屏模组)" + ) + + # 默认值 + self.DEFAULT_CCT_PARAMS = { + "x_ideal": 0.3127, + "x_tolerance": 0.003, + "y_ideal": 0.3290, + "y_tolerance": 0.003, + } + + # 从配置读取屏模组参数 + saved_params = self.config.current_test_types.get("screen_module", {}).get( + "cct_params", self.DEFAULT_CCT_PARAMS.copy() + ) + + # 色域参考标准 + saved_gamut_ref = self.config.current_test_types.get("screen_module", {}).get( + "gamut_reference", "DCI-P3" + ) + + # 创建屏模组变量 + self.cct_x_ideal_var = tk.StringVar( + value=str(saved_params.get("x_ideal", 0.3127)) + ) + self.cct_x_tolerance_var = tk.StringVar( + value=str(saved_params.get("x_tolerance", 0.003)) + ) + self.cct_y_ideal_var = tk.StringVar( + value=str(saved_params.get("y_ideal", 0.3290)) + ) + self.cct_y_tolerance_var = tk.StringVar( + value=str(saved_params.get("y_tolerance", 0.003)) + ) + self.screen_gamut_ref_var = tk.StringVar(value=saved_gamut_ref) + + # 创建屏模组输入框(左侧:色度参数) + params = [ + ("x-ideal:", self.cct_x_ideal_var, "x_ideal"), + ("x-tolerance:", self.cct_x_tolerance_var, "x_tolerance"), + ("y-ideal:", self.cct_y_ideal_var, "y_ideal"), + ("y-tolerance:", self.cct_y_tolerance_var, "y_tolerance"), + ] + + for i, (label_text, var, key) in enumerate(params): + ttk.Label(self.cct_params_frame, text=label_text).grid( + row=i, column=0, sticky=tk.W, padx=5, pady=3 + ) + entry = ttk.Entry(self.cct_params_frame, textvariable=var, width=15) + entry.grid(row=i, column=1, sticky=tk.W, padx=5, pady=3) + + # 绑定失去焦点事件 + default_val = self.DEFAULT_CCT_PARAMS[key] + entry.bind( + "", + lambda e, v=var, d=default_val: self.on_cct_param_focus_out(v, d), + ) + + # 色域参考标准选择(右侧第一行) + ttk.Label(self.cct_params_frame, text="色域参考标准:").grid( + row=0, column=2, sticky=tk.W, padx=(20, 5), pady=3 + ) + screen_gamut_combo = ttk.Combobox( + self.cct_params_frame, + textvariable=self.screen_gamut_ref_var, + values=["BT.2020", "BT.709", "DCI-P3"], + state="disabled", + width=12, + ) + screen_gamut_combo.grid(row=0, column=3, sticky=tk.W, padx=5, pady=3) + screen_gamut_combo.bind( + "<>", self.on_screen_gamut_ref_changed + ) + self.screen_gamut_combo = screen_gamut_combo + + # ==================== ✅ 单步调试按钮(右侧第二行)==================== + ttk.Label(self.cct_params_frame, text="单步调试:").grid( + row=1, column=2, sticky=tk.W, padx=(20, 5), pady=3 + ) + + self.screen_debug_btn = ttk.Button( + self.cct_params_frame, + text="打开调试面板", + command=self.toggle_screen_debug_panel, + bootstyle="info-outline", + state=tk.DISABLED, # 初始禁用 + width=15, + ) + self.screen_debug_btn.grid(row=1, column=3, sticky=tk.W, padx=5, pady=3) + + # 重新计算按钮(屏模组) + self.recalc_cct_btn = ttk.Button( + self.cct_params_frame, + text="应用新参数并重绘", + command=self.recalculate_cct, + bootstyle="success", + ) + self.recalc_cct_btn.grid( + row=4, column=0, columnspan=2, pady=10, padx=5, sticky="ew" + ) + self.recalc_cct_btn.grid_remove() + + # 色域重新计算按钮 + self.recalc_gamut_btn = ttk.Button( + self.cct_params_frame, + text="应用色域参考并重绘", + command=self.recalculate_gamut, + bootstyle="warning", + ) + self.recalc_gamut_btn.grid( + row=4, column=2, columnspan=2, pady=10, padx=5, sticky="ew" + ) + self.recalc_gamut_btn.grid_remove() + + # 提示文字 + ttk.Label( + self.cct_params_frame, + text="提示: 清空输入框将恢复默认值", + font=("SimHei", 8), + foreground="gray", + ).grid(row=5, column=0, columnspan=4, sticky=tk.W, padx=5, pady=5) + + # ==================== SDR 色度参数 Frame ==================== + self.sdr_cct_params_frame = ttk.LabelFrame( + self.test_items_frame, text="色度参数设置(SDR)" + ) + + # SDR 默认值 + self.SDR_DEFAULT_CCT_PARAMS = { + "x_ideal": 0.3127, + "x_tolerance": 0.003, + "y_ideal": 0.3290, + "y_tolerance": 0.003, + } + + # 从配置读取 SDR 参数 + sdr_saved_params = self.config.current_test_types.get("sdr_movie", {}).get( + "cct_params", self.SDR_DEFAULT_CCT_PARAMS.copy() + ) + + # 色域参考标准 + sdr_saved_gamut_ref = self.config.current_test_types.get("sdr_movie", {}).get( + "gamut_reference", "BT.709" + ) + + # 创建 SDR 变量 + self.sdr_cct_x_ideal_var = tk.StringVar( + value=str(sdr_saved_params.get("x_ideal", 0.3127)) + ) + self.sdr_cct_x_tolerance_var = tk.StringVar( + value=str(sdr_saved_params.get("x_tolerance", 0.003)) + ) + self.sdr_cct_y_ideal_var = tk.StringVar( + value=str(sdr_saved_params.get("y_ideal", 0.3290)) + ) + self.sdr_cct_y_tolerance_var = tk.StringVar( + value=str(sdr_saved_params.get("y_tolerance", 0.003)) + ) + self.sdr_gamut_ref_var = tk.StringVar(value=sdr_saved_gamut_ref) + + # 创建 SDR 输入框 + sdr_params = [ + ("x-ideal:", self.sdr_cct_x_ideal_var, "x_ideal"), + ("x-tolerance:", self.sdr_cct_x_tolerance_var, "x_tolerance"), + ("y-ideal:", self.sdr_cct_y_ideal_var, "y_ideal"), + ("y-tolerance:", self.sdr_cct_y_tolerance_var, "y_tolerance"), + ] + + for i, (label_text, var, key) in enumerate(sdr_params): + ttk.Label(self.sdr_cct_params_frame, text=label_text).grid( + row=i, column=0, sticky=tk.W, padx=5, pady=3 + ) + entry = ttk.Entry(self.sdr_cct_params_frame, textvariable=var, width=15) + entry.grid(row=i, column=1, sticky=tk.W, padx=5, pady=3) + + # 绑定失去焦点事件 + default_val = self.SDR_DEFAULT_CCT_PARAMS[key] + entry.bind( + "", + lambda e, v=var, d=default_val: self.on_sdr_cct_param_focus_out(v, d), + ) + + # 色域参考标准选择(右侧第一行) + ttk.Label(self.sdr_cct_params_frame, text="色域参考标准:").grid( + row=0, column=2, sticky=tk.W, padx=(20, 5), pady=3 + ) + sdr_gamut_combo = ttk.Combobox( + self.sdr_cct_params_frame, + textvariable=self.sdr_gamut_ref_var, + values=["BT.2020", "BT.709", "DCI-P3"], + state="disabled", + width=12, + ) + sdr_gamut_combo.grid(row=0, column=3, sticky=tk.W, padx=5, pady=3) + sdr_gamut_combo.bind("<>", self.on_sdr_gamut_ref_changed) + self.sdr_gamut_combo = sdr_gamut_combo + + # ==================== ✅ SDR 单步调试按钮(右侧第二行)==================== + ttk.Label(self.sdr_cct_params_frame, text="单步调试:").grid( + row=1, column=2, sticky=tk.W, padx=(20, 5), pady=3 + ) + + self.sdr_debug_btn = ttk.Button( + self.sdr_cct_params_frame, + text="打开调试面板", + command=self.toggle_sdr_debug_panel, + bootstyle="info-outline", + state=tk.DISABLED, # 初始禁用 + width=15, + ) + self.sdr_debug_btn.grid(row=1, column=3, sticky=tk.W, padx=5, pady=3) + + # 重新计算按钮(SDR) + self.sdr_recalc_cct_btn = ttk.Button( + self.sdr_cct_params_frame, + text="应用新参数并重绘", + command=self.recalculate_cct, + bootstyle="success", + ) + self.sdr_recalc_cct_btn.grid( + row=4, column=0, columnspan=2, pady=10, padx=5, sticky="ew" + ) + self.sdr_recalc_cct_btn.grid_remove() + + # 色域重新计算按钮(SDR) + self.sdr_recalc_gamut_btn = ttk.Button( + self.sdr_cct_params_frame, + text="应用色域参考并重绘", + command=self.recalculate_gamut, + bootstyle="warning", + ) + self.sdr_recalc_gamut_btn.grid( + row=4, column=2, columnspan=2, pady=10, padx=5, sticky="ew" + ) + self.sdr_recalc_gamut_btn.grid_remove() + + # 提示文字 + ttk.Label( + self.sdr_cct_params_frame, + text="提示: 清空输入框将恢复默认值", + font=("SimHei", 8), + foreground="gray", + ).grid(row=5, column=0, columnspan=4, sticky=tk.W, padx=5, pady=5) + + # ==================== HDR 色度参数 Frame ==================== + self.hdr_cct_params_frame = ttk.LabelFrame( + self.test_items_frame, text="色度参数设置(HDR)" + ) + + # HDR 默认值 + self.HDR_DEFAULT_CCT_PARAMS = { + "x_ideal": 0.3127, + "x_tolerance": 0.003, + "y_ideal": 0.3290, + "y_tolerance": 0.003, + } + + # 从配置读取 HDR 参数 + hdr_saved_params = self.config.current_test_types.get("hdr_movie", {}).get( + "cct_params", self.HDR_DEFAULT_CCT_PARAMS.copy() + ) + + # 色域参考标准 + hdr_saved_gamut_ref = self.config.current_test_types.get("hdr_movie", {}).get( + "gamut_reference", "BT.2020" + ) + + # 创建 HDR 变量 + self.hdr_cct_x_ideal_var = tk.StringVar( + value=str(hdr_saved_params.get("x_ideal", 0.3127)) + ) + self.hdr_cct_x_tolerance_var = tk.StringVar( + value=str(hdr_saved_params.get("x_tolerance", 0.003)) + ) + self.hdr_cct_y_ideal_var = tk.StringVar( + value=str(hdr_saved_params.get("y_ideal", 0.3290)) + ) + self.hdr_cct_y_tolerance_var = tk.StringVar( + value=str(hdr_saved_params.get("y_tolerance", 0.003)) + ) + self.hdr_gamut_ref_var = tk.StringVar(value=hdr_saved_gamut_ref) + + # 创建 HDR 输入框 + hdr_params = [ + ("x-ideal:", self.hdr_cct_x_ideal_var, "x_ideal"), + ("x-tolerance:", self.hdr_cct_x_tolerance_var, "x_tolerance"), + ("y-ideal:", self.hdr_cct_y_ideal_var, "y_ideal"), + ("y-tolerance:", self.hdr_cct_y_tolerance_var, "y_tolerance"), + ] + + for i, (label_text, var, key) in enumerate(hdr_params): + ttk.Label(self.hdr_cct_params_frame, text=label_text).grid( + row=i, column=0, sticky=tk.W, padx=5, pady=3 + ) + entry = ttk.Entry(self.hdr_cct_params_frame, textvariable=var, width=15) + entry.grid(row=i, column=1, sticky=tk.W, padx=5, pady=3) + + # 绑定失去焦点事件 + default_val = self.HDR_DEFAULT_CCT_PARAMS[key] + entry.bind( + "", + lambda e, v=var, d=default_val: self.on_hdr_cct_param_focus_out(v, d), + ) + + # 色域参考标准选择(右侧第一行) + ttk.Label(self.hdr_cct_params_frame, text="色域参考标准:").grid( + row=0, column=2, sticky=tk.W, padx=(20, 5), pady=3 + ) + hdr_gamut_combo = ttk.Combobox( + self.hdr_cct_params_frame, + textvariable=self.hdr_gamut_ref_var, + values=["BT.2020", "BT.709", "DCI-P3"], + state="disabled", + width=12, + ) + hdr_gamut_combo.grid(row=0, column=3, sticky=tk.W, padx=5, pady=3) + hdr_gamut_combo.bind("<>", self.on_hdr_gamut_ref_changed) + self.hdr_gamut_combo = hdr_gamut_combo + + # ==================== ✅ HDR 单步调试按钮(右侧第二行)==================== + ttk.Label(self.hdr_cct_params_frame, text="单步调试:").grid( + row=1, column=2, sticky=tk.W, padx=(20, 5), pady=3 + ) + + self.hdr_debug_btn = ttk.Button( + self.hdr_cct_params_frame, + text="打开调试面板", + command=self.toggle_hdr_debug_panel, + bootstyle="info-outline", + state=tk.DISABLED, # 初始禁用 + width=15, + ) + self.hdr_debug_btn.grid(row=1, column=3, sticky=tk.W, padx=5, pady=3) + + # 重新计算按钮(HDR) + self.hdr_recalc_cct_btn = ttk.Button( + self.hdr_cct_params_frame, + text="应用新参数并重绘", + command=self.recalculate_cct, + bootstyle="success", + ) + self.hdr_recalc_cct_btn.grid( + row=4, column=0, columnspan=2, pady=10, padx=5, sticky="ew" + ) + self.hdr_recalc_cct_btn.grid_remove() + + # 色域重新计算按钮(HDR) + self.hdr_recalc_gamut_btn = ttk.Button( + self.hdr_cct_params_frame, + text="应用色域参考并重绘", + command=self.recalculate_gamut, + bootstyle="warning", + ) + self.hdr_recalc_gamut_btn.grid( + row=4, column=2, columnspan=2, pady=10, padx=5, sticky="ew" + ) + self.hdr_recalc_gamut_btn.grid_remove() + + # 提示文字 + ttk.Label( + self.hdr_cct_params_frame, + text="提示: 清空输入框将恢复默认值", + font=("SimHei", 8), + foreground="gray", + ).grid(row=5, column=0, columnspan=4, sticky=tk.W, padx=5, pady=5) + + def on_sdr_cct_param_focus_out(self, var, default_value): + """SDR 色度参数失去焦点时的处理""" + try: + value = var.get().strip() + + if value == "": + var.set(str(default_value)) + if hasattr(self, "log_gui"): + self.log_gui.log(f"✓ SDR 参数为空,恢复默认值: {default_value}") + else: + try: + float_val = float(value) + if float_val < 0 or float_val > 1: + var.set(str(default_value)) + if hasattr(self, "log_gui"): + self.log_gui.log( + f"⚠️ SDR 参数超出范围,恢复默认值: {default_value}" + ) + except ValueError: + var.set(str(default_value)) + if hasattr(self, "log_gui"): + self.log_gui.log(f"⚠️ SDR 参数无效,恢复默认值: {default_value}") + + self.save_sdr_cct_params() + except Exception as e: + if hasattr(self, "log_gui"): + self.log_gui.log(f"处理 SDR 参数失败: {str(e)}") + + def save_sdr_cct_params(self): + """保存 SDR 色度参数""" + try: + + def get_float(var, default): + try: + value = var.get().strip() + if value == "": + return default + return float(value) + except: + return default + + sdr_cct_params = { + "x_ideal": get_float( + self.sdr_cct_x_ideal_var, self.SDR_DEFAULT_CCT_PARAMS["x_ideal"] + ), + "x_tolerance": get_float( + self.sdr_cct_x_tolerance_var, + self.SDR_DEFAULT_CCT_PARAMS["x_tolerance"], + ), + "y_ideal": get_float( + self.sdr_cct_y_ideal_var, self.SDR_DEFAULT_CCT_PARAMS["y_ideal"] + ), + "y_tolerance": get_float( + self.sdr_cct_y_tolerance_var, + self.SDR_DEFAULT_CCT_PARAMS["y_tolerance"], + ), + } + + if "sdr_movie" not in self.config.current_test_types: + self.config.current_test_types["sdr_movie"] = {} + + self.config.current_test_types["sdr_movie"]["cct_params"] = sdr_cct_params + self.save_pq_config() + except: + pass + + def on_hdr_cct_param_focus_out(self, var, default_value): + """HDR 色度参数失去焦点时的处理""" + try: + value = var.get().strip() + + if value == "": + var.set(str(default_value)) + if hasattr(self, "log_gui"): + self.log_gui.log(f"✓ HDR 参数为空,恢复默认值: {default_value}") + else: + try: + float_val = float(value) + if float_val < 0 or float_val > 1: + var.set(str(default_value)) + if hasattr(self, "log_gui"): + self.log_gui.log( + f"⚠️ HDR 参数超出范围,恢复默认值: {default_value}" + ) + except ValueError: + var.set(str(default_value)) + if hasattr(self, "log_gui"): + self.log_gui.log(f"⚠️ HDR 参数无效,恢复默认值: {default_value}") + + self.save_hdr_cct_params() + except Exception as e: + if hasattr(self, "log_gui"): + self.log_gui.log(f"处理 HDR 参数失败: {str(e)}") + + def save_hdr_cct_params(self): + """保存 HDR 色度参数""" + try: + + def get_float(var, default): + try: + value = var.get().strip() + if value == "": + return default + return float(value) + except: + return default + + hdr_cct_params = { + "x_ideal": get_float( + self.hdr_cct_x_ideal_var, self.HDR_DEFAULT_CCT_PARAMS["x_ideal"] + ), + "x_tolerance": get_float( + self.hdr_cct_x_tolerance_var, + self.HDR_DEFAULT_CCT_PARAMS["x_tolerance"], + ), + "y_ideal": get_float( + self.hdr_cct_y_ideal_var, self.HDR_DEFAULT_CCT_PARAMS["y_ideal"] + ), + "y_tolerance": get_float( + self.hdr_cct_y_tolerance_var, + self.HDR_DEFAULT_CCT_PARAMS["y_tolerance"], + ), + } + + if "hdr_movie" not in self.config.current_test_types: + self.config.current_test_types["hdr_movie"] = {} + + self.config.current_test_types["hdr_movie"]["cct_params"] = hdr_cct_params + self.save_pq_config() + except: + pass + + def recalculate_cct(self): + """重新计算并绘制色度图""" + try: + # 1. 保存新参数 + self.save_cct_params() + self.log_gui.log("✓ 色度参数已更新") + + # 2. 收起配置项 + if hasattr(self, "config_panel_frame"): + try: + if self.config_panel_frame.winfo_viewable(): + self.config_panel_frame.btn.invoke() + self.root.update_idletasks() + time.sleep(0.1) + except: + pass + + # 3. 跳转到色度图Tab + self.chart_notebook.select(2) # ← 色度图是第3个Tab(索引2) + self.root.update_idletasks() + + # 4. 检查是否有数据 + if not hasattr(self, "results") or not self.results: + self.log_gui.log("⚠️ 没有测试数据,无法重新绘制") + messagebox.showwarning("警告", "请先完成测试后再重新计算") + return + + # 5. 获取保存的灰阶数据 + gray_data = self.results.get_intermediate_data("shared", "gray") + if not gray_data: + gray_data = self.results.get_intermediate_data("cct", "gray") + + if not gray_data or len(gray_data) < 2: + self.log_gui.log("⚠️ 没有可用的灰阶数据") + messagebox.showwarning("警告", "没有找到色度测试数据") + return + + # 6. 重新计算 CCT + self.log_gui.log("=" * 50) + self.log_gui.log("开始重新计算色度一致性...") + self.log_gui.log("=" * 50) + + import algorithm.pq_algorithm as pq_algorithm + + cct_values = pq_algorithm.calculate_cct_from_results(gray_data) + + # 7. 更新结果 + self.results.set_test_item_result("cct", {"cct_values": cct_values}) + + # 8. 重新绘制色度图 + test_type = self.config.current_test_type + self.plot_cct(test_type) + + self.log_gui.log("✓ 色度图已重新绘制") + self.log_gui.log("=" * 50) + + messagebox.showinfo("成功", "色度图已根据新参数重新绘制!") + + except Exception as e: + self.log_gui.log(f"❌ 重新计算失败: {str(e)}") + import traceback + + self.log_gui.log(traceback.format_exc()) + messagebox.showerror("错误", f"重新计算失败: {str(e)}") + + def recalculate_gamut(self): + """重新计算并绘制色域图(使用新的参考标准)""" + try: + # 1. 收起配置项 + if hasattr(self, "config_panel_frame"): + try: + if self.config_panel_frame.winfo_viewable(): + self.config_panel_frame.btn.invoke() + self.root.update_idletasks() + time.sleep(0.1) + except: + pass + + # 2. 跳转到色域图Tab + self.chart_notebook.select(0) # 色域图是第1个Tab + self.root.update_idletasks() + + # 3. 检查是否有数据 + if not hasattr(self, "results") or not self.results: + self.log_gui.log("⚠️ 没有测试数据,无法重新绘制") + messagebox.showwarning("警告", "请先完成测试后再重新计算") + return + + # 4. 获取保存的色域数据 + rgb_data = self.results.get_intermediate_data("gamut", "rgb") + + if not rgb_data or len(rgb_data) < 3: + self.log_gui.log("⚠️ 没有可用的色域数据") + messagebox.showwarning("警告", "没有找到色域测试数据") + return + + # 5. 获取当前测试类型 + test_type = self.config.current_test_type + + # 6. 获取用户选择的参考标准 + if test_type == "screen_module": + reference_standard = self.screen_gamut_ref_var.get() + elif test_type == "sdr_movie": + reference_standard = self.sdr_gamut_ref_var.get() + elif test_type == "hdr_movie": + reference_standard = self.hdr_gamut_ref_var.get() + else: + reference_standard = "DCI-P3" + + self.log_gui.log("=" * 50) + self.log_gui.log(f"开始重新计算色域(参考标准: {reference_standard})...") + self.log_gui.log("=" * 50) + + # 7. 重新计算 XY 色域覆盖率 + xy_points = [[result[0], result[1]] for result in rgb_data] + + # 根据参考标准计算 XY 覆盖率 + if reference_standard == "BT.2020": + area_xy, coverage_xy = pq_algorithm.calculate_gamut_coverage_BT2020( + xy_points + ) + elif reference_standard == "BT.709": + area_xy, coverage_xy = pq_algorithm.calculate_gamut_coverage_BT709( + xy_points + ) + elif reference_standard == "DCI-P3": + area_xy, coverage_xy = pq_algorithm.calculate_gamut_coverage_DCIP3( + xy_points + ) + else: + area_xy, coverage_xy = pq_algorithm.calculate_gamut_coverage_DCIP3( + xy_points + ) + reference_standard = "DCI-P3" + + self.log_gui.log(f"✓ 参考标准: {reference_standard}") + self.log_gui.log(f"✓ XY 色域覆盖率: {coverage_xy:.1f}%") + + # ========== ✅✅✅ 8. 重新计算 UV 色域覆盖率 ========== + # 将 XY 坐标转换为 UV 坐标 + uv_points = [] + for x, y in xy_points: + try: + # XY转UV公式 + denom = -2 * x + 12 * y + 3 + if abs(denom) < 1e-10: + u, v = 0, 0 + else: + u = 4 * x / denom + v = 9 * y / denom + uv_points.append([u, v]) + except ZeroDivisionError: + continue + + self.log_gui.log(f"✓ 转换后的 UV 点数量: {len(uv_points)}") + + # 根据参考标准计算 UV 覆盖率 + if reference_standard == "BT.2020": + area_uv, coverage_uv = pq_algorithm.calculate_gamut_coverage_BT2020_uv( + uv_points + ) + elif reference_standard == "BT.709": + area_uv, coverage_uv = pq_algorithm.calculate_gamut_coverage_BT709_uv( + uv_points + ) + elif reference_standard == "DCI-P3": + area_uv, coverage_uv = pq_algorithm.calculate_gamut_coverage_DCIP3_uv( + uv_points + ) + else: + area_uv, coverage_uv = pq_algorithm.calculate_gamut_coverage_DCIP3_uv( + uv_points + ) + + self.log_gui.log(f"✓ UV 色域覆盖率: {coverage_uv:.1f}%") + # ======================================================== + + # 9. ✅ 更新结果(同时保存 XY 和 UV 覆盖率) + self.results.set_test_item_result( + "gamut", + { + "area": area_xy, # ← 兼容旧字段 + "coverage": coverage_xy, # ← 兼容旧字段 + "area_xy": area_xy, # ← XY 面积 + "coverage_xy": coverage_xy, # ← XY 覆盖率 + "area_uv": area_uv, # ← UV 面积 + "coverage_uv": coverage_uv, # ← UV 覆盖率 + "uv_coverage": coverage_uv, # ← 兼容字段(Excel 导出用) + "reference": reference_standard, + }, + ) + + self.log_gui.log("✓ 测试结果已更新到 results 对象") + + # 10. 重新绘制色域图 + self.plot_gamut(rgb_data, coverage_xy, test_type) + + self.log_gui.log("✓ 色域图已重新绘制") + self.log_gui.log("=" * 50) + + messagebox.showinfo( + "成功", + f"色域图已根据新参考标准 {reference_standard} 重新绘制!\n\n" + f"XY 覆盖率: {coverage_xy:.1f}%\n" + f"UV 覆盖率: {coverage_uv:.1f}%", + ) + + except Exception as e: + self.log_gui.log(f"❌ 重新计算失败: {str(e)}") + import traceback + + self.log_gui.log(traceback.format_exc()) + messagebox.showerror("错误", f"重新计算失败: {str(e)}") + + def on_cct_param_change(self, var, default_value): + """色度参数改变时的处理 - 空值恢复默认""" + try: + value = var.get().strip() + + if value == "": + # 空值:恢复默认值 + var.set(str(default_value)) + if hasattr(self, "log_gui"): + self.log_gui.log(f"输入框为空,恢复默认值: {default_value}") + else: + # 验证是否为有效数字 + try: + float_val = float(value) + if float_val < 0 or float_val > 1: + var.set(str(default_value)) + if hasattr(self, "log_gui"): + self.log_gui.log( + f"参数超出范围 [0, 1],恢复默认值: {default_value}" + ) + except ValueError: + var.set(str(default_value)) + if hasattr(self, "log_gui"): + self.log_gui.log(f"无效的参数值,恢复默认值: {default_value}") + + # 保存配置 + self.save_cct_params() + + except Exception as e: + if hasattr(self, "log_gui"): + self.log_gui.log(f"处理参数变化失败: {str(e)}") + + def on_cct_param_focus_out(self, var, default_value): + """色度参数失去焦点时的处理 - 空值恢复默认""" + try: + value = var.get().strip() + + if value == "": + # 空值:恢复默认值 + var.set(str(default_value)) + if hasattr(self, "log_gui"): + self.log_gui.log(f"✓ 输入框为空,恢复默认值: {default_value}") + else: + # 验证是否为有效数字 + try: + float_val = float(value) + if float_val < 0 or float_val > 1: + var.set(str(default_value)) + if hasattr(self, "log_gui"): + self.log_gui.log( + f"⚠️ 参数超出范围 [0, 1],恢复默认值: {default_value}" + ) + except ValueError: + var.set(str(default_value)) + if hasattr(self, "log_gui"): + self.log_gui.log(f"⚠️ 无效的参数值,恢复默认值: {default_value}") + + # 保存配置 + self.save_cct_params() + + except Exception as e: + if hasattr(self, "log_gui"): + self.log_gui.log(f"处理参数变化失败: {str(e)}") + + def save_cct_params(self): + """保存色度参数 - 简化版""" + try: + current_type = self.config.current_test_type + + def get_float(var, default): + try: + value = var.get().strip() + if value == "": + return default + return float(value) + except: + return default + + cct_params = { + "x_ideal": get_float( + self.cct_x_ideal_var, self.DEFAULT_CCT_PARAMS["x_ideal"] + ), + "x_tolerance": get_float( + self.cct_x_tolerance_var, self.DEFAULT_CCT_PARAMS["x_tolerance"] + ), + "y_ideal": get_float( + self.cct_y_ideal_var, self.DEFAULT_CCT_PARAMS["y_ideal"] + ), + "y_tolerance": get_float( + self.cct_y_tolerance_var, self.DEFAULT_CCT_PARAMS["y_tolerance"] + ), + } + + if current_type not in self.config.current_test_types: + self.config.current_test_types[current_type] = {} + + self.config.current_test_types[current_type]["cct_params"] = cct_params + self.save_pq_config() + + except: + pass + + def reload_cct_params(self): + """切换测试类型时重新加载色度参数""" + try: + current_type = self.config.current_test_type + saved_params = self.config.current_test_types.get(current_type, {}).get( + "cct_params", None + ) + + if saved_params is None: + saved_params = self.DEFAULT_CCT_PARAMS.copy() + + # 更新输入框的值 + self.cct_x_ideal_var.set(str(saved_params["x_ideal"])) + self.cct_x_tolerance_var.set(str(saved_params["x_tolerance"])) + self.cct_y_ideal_var.set(str(saved_params["y_ideal"])) + self.cct_y_tolerance_var.set(str(saved_params["y_tolerance"])) + + except Exception as e: + if hasattr(self, "log_gui"): + self.log_gui.log(f"重新加载色度参数失败: {str(e)}") + + def update_test_items(self): + """根据当前测试类型更新测试项目复选框""" + # 先隐藏所有测试项目框架 + for config in self.test_items.values(): + config["frame"].pack_forget() + + current_test_type = self.config.current_test_type + self.test_vars = {} + + if current_test_type in self.test_items: + config = self.test_items[current_test_type] + frame = config["frame"] + frame.pack(fill=tk.X, padx=5, pady=5) + + # 添加测试类型标签 + type_label = ttk.Label( + frame, + text=self.get_test_type_display_name(current_test_type), + style="primary.TLabel", + ) + type_label.grid(row=0, column=0, columnspan=2, sticky=tk.W, padx=5, pady=3) + + # 从配置中读取保存的选择状态 + saved_test_items = self.config.current_test_types[current_test_type].get( + "test_items", [] + ) + + # 添加复选框 + for i, (text, var_name) in enumerate(config["items"]): + # 修改:根据配置决定是否勾选 + # 如果配置中有该测试项,则勾选;否则不勾选 + is_checked = var_name in saved_test_items + var = tk.BooleanVar(value=is_checked) + + self.test_vars[f"{current_test_type}_{var_name}"] = var + ttk.Checkbutton( + frame, + text=text, + variable=var, + bootstyle="round-toggle", + command=self.update_config_and_tabs, + ).grid(row=i // 2 + 1, column=i % 2, sticky=tk.W, padx=10, pady=5) + + # 只有在 chart_notebook 已创建后才更新状态 + if hasattr(self, "chart_notebook"): + self.update_chart_tabs_state() + + # 更新色度参数框的显示状态 + if hasattr(self, "cct_params_frame"): + self.toggle_cct_params_frame() + + # ========== 新增方法: 更新配置并同步Tab状态 ========== + def update_config_and_tabs(self): + """更新配置并同步图表Tab状态""" + self.update_config() + self.update_chart_tabs_state() + + def update_chart_tabs_state(self): + """根据测试项目复选框状态更新图表Tab的启用/禁用""" + if not hasattr(self, "chart_notebook"): + return + + selected_items = self.get_selected_test_items() + current_test_type = self.config.current_test_type + + # 检查5个独立图表的选中状态 + gamut_selected = "gamut" in selected_items + gamma_selected = "gamma" in selected_items or "eotf" in selected_items + cct_selected = "cct" in selected_items + contrast_selected = "contrast" in selected_items + accuracy_selected = "accuracy" in selected_items + + # 屏模组测试时,强制隐藏色准 Tab + if current_test_type == "screen_module": + accuracy_selected = False + + try: + # 获取当前所有 Tab + current_tabs = self.chart_notebook.tabs() + current_tab_set = set(current_tabs) + + gamut_tab_id = str(self.gamut_chart_frame) + gamma_tab_id = str(self.gamma_chart_frame) + eotf_tab_id = str(self.eotf_chart_frame) + cct_tab_id = str(self.cct_chart_frame) + contrast_tab_id = str(self.contrast_chart_frame) + accuracy_tab_id = str(self.accuracy_chart_frame) + + # ========== 控制前4个固定 Tab ========== + if gamut_tab_id in current_tab_set: + self.chart_notebook.tab( + gamut_tab_id, state="normal" if gamut_selected else "disabled" + ) + + if gamma_tab_id in current_tab_set: + self.chart_notebook.tab( + gamma_tab_id, state="normal" if gamma_selected else "disabled" + ) + elif eotf_tab_id in current_tab_set: + self.chart_notebook.tab( + eotf_tab_id, state="normal" if gamma_selected else "disabled" + ) + + if cct_tab_id in current_tab_set: + self.chart_notebook.tab( + cct_tab_id, state="normal" if cct_selected else "disabled" + ) + + if contrast_tab_id in current_tab_set: + self.chart_notebook.tab( + contrast_tab_id, + state="normal" if contrast_selected else "disabled", + ) + + # ========== 控制色准 Tab(动态添加/移除)========== + accuracy_tab_exists = accuracy_tab_id in current_tab_set + + if accuracy_selected and not accuracy_tab_exists: + # 需要显示色准,但当前没有 → 添加 + self.chart_notebook.add(self.accuracy_chart_frame, text="色准") + self.chart_notebook.tab(accuracy_tab_id, state="normal") + self.log_gui.log("✓ 色准 Tab 已显示") + + elif accuracy_selected and accuracy_tab_exists: + # 需要显示色准,且已存在 → 启用 + self.chart_notebook.tab(accuracy_tab_id, state="normal") + + elif not accuracy_selected and accuracy_tab_exists: + # 不需要显示色准,但存在 → 移除 + self.chart_notebook.forget(self.accuracy_chart_frame) + self.log_gui.log("✓ 色准 Tab 已隐藏") + + except Exception as e: + self.log_gui.log(f"更新Tab状态失败: {str(e)}") + + def get_test_type_display_name(self, test_type): + """获取测试类型的显示名称""" + display_names = { + "screen_module": "屏模组性能测试", + "sdr_movie": "SDR Movie测试", + "hdr_movie": "HDR Movie测试", + } + return display_names.get(test_type, test_type) + + def create_signal_format_content(self): + """创建信号格式选项卡内容""" + self.signal_tabs = ttk.Notebook(self.signal_format_frame) + self.signal_tabs.pack(fill=tk.BOTH, expand=True) + + # ==================== 屏模组格式设置 ==================== + self.screen_module_signal_frame = ttk.Frame(self.signal_tabs) + self.screen_module_signal_frame.grid_columnconfigure(0, weight=1) + self.signal_tabs.add(self.screen_module_signal_frame, text="屏模组测试") + + self.screen_module_timing_var = tk.StringVar( + value=self.config.current_test_types[self.config.current_test_type][ + "timing" + ] + ) + screen_module_timing_combo = ttk.Combobox( + self.screen_module_signal_frame, + textvariable=self.screen_module_timing_var, + values=UCDEnum.TimingInfo.get_formatted_resolution_list(), + state="readonly", + ) + screen_module_timing_combo.bind( + "<>", self.on_screen_module_timing_changed + ) + screen_module_timing_combo.grid(row=0, column=0, sticky="ew", padx=5, pady=5) + + # ==================== SDR信号格式设置 ==================== + self.sdr_signal_frame = ttk.Frame(self.signal_tabs) + # 配置列权重 + self.sdr_signal_frame.grid_columnconfigure(0, weight=0) + self.sdr_signal_frame.grid_columnconfigure(1, weight=1) + self.signal_tabs.add(self.sdr_signal_frame, text="SDR测试") + + # 色彩空间 + ttk.Label(self.sdr_signal_frame, text="色彩空间:").grid( + row=0, column=0, sticky=tk.W, padx=5, pady=2 + ) + self.sdr_color_space_var = tk.StringVar(value="BT.709") + sdr_color_space_combo = ttk.Combobox( + self.sdr_signal_frame, + textvariable=self.sdr_color_space_var, + values=["BT.709", "BT.601", "BT.2020"], + width=10, + state="readonly", + ) + sdr_color_space_combo.grid(row=0, column=1, sticky=tk.W, padx=5, pady=2) + + # Gamma + ttk.Label(self.sdr_signal_frame, text="Gamma:").grid( + row=1, column=0, sticky=tk.W, padx=5, pady=2 + ) + self.sdr_gamma_type_var = tk.StringVar(value="2.2") + sdr_gamma_combo = ttk.Combobox( + self.sdr_signal_frame, + textvariable=self.sdr_gamma_type_var, + values=["2.2", "2.4", "2.6"], + width=10, + state="readonly", + ) + sdr_gamma_combo.grid(row=1, column=1, sticky=tk.W, padx=5, pady=2) + + # 数据范围 + ttk.Label(self.sdr_signal_frame, text="数据范围:").grid( + row=2, column=0, sticky=tk.W, padx=5, pady=2 + ) + self.sdr_data_range_var = tk.StringVar(value="Full") + sdr_range_combo = ttk.Combobox( + self.sdr_signal_frame, + textvariable=self.sdr_data_range_var, + values=["Full", "Limited"], + width=10, + state="readonly", + ) + sdr_range_combo.grid(row=2, column=1, sticky=tk.W, padx=5, pady=2) + + # 编码位深 + ttk.Label(self.sdr_signal_frame, text="编码位深:").grid( + row=3, column=0, sticky=tk.W, padx=5, pady=2 + ) + self.sdr_bit_depth_var = tk.StringVar(value="8bit") + sdr_bit_depth_combo = ttk.Combobox( + self.sdr_signal_frame, + textvariable=self.sdr_bit_depth_var, + values=["8bit", "10bit", "12bit"], + width=10, + state="readonly", + ) + sdr_bit_depth_combo.grid(row=3, column=1, sticky=tk.W, padx=5, pady=2) + + # ==================== HDR信号格式设置 ==================== + self.hdr_signal_frame = ttk.Frame(self.signal_tabs) + # 配置列权重 + self.hdr_signal_frame.grid_columnconfigure(0, weight=0) + self.hdr_signal_frame.grid_columnconfigure(1, weight=1) + self.signal_tabs.add(self.hdr_signal_frame, text="HDR") + + # 色彩空间 + ttk.Label(self.hdr_signal_frame, text="色彩空间:").grid( + row=0, column=0, sticky=tk.W, padx=5, pady=2 + ) + self.hdr_color_space_var = tk.StringVar(value="BT.2020") + hdr_color_space_combo = ttk.Combobox( + self.hdr_signal_frame, + textvariable=self.hdr_color_space_var, + values=["BT.2020", "DCI-P3"], + width=10, + state="readonly", + ) + hdr_color_space_combo.grid(row=0, column=1, sticky=tk.W, padx=5, pady=2) + + # Metadata设置 + ttk.Label(self.hdr_signal_frame, text="Metadata:").grid( + row=1, column=0, sticky=tk.W, padx=5, pady=2 + ) + self.hdr_metadata_frame = ttk.Frame(self.hdr_signal_frame) + self.hdr_metadata_frame.grid( + row=1, column=1, rowspan=2, sticky=tk.W, padx=5, pady=2 + ) + + ttk.Label(self.hdr_metadata_frame, text="MaxCLL:").grid( + row=0, column=0, sticky=tk.W + ) + self.hdr_maxcll_var = tk.StringVar(value="1000") + ttk.Entry( + self.hdr_metadata_frame, textvariable=self.hdr_maxcll_var, width=6 + ).grid(row=0, column=1, padx=2) + + ttk.Label(self.hdr_metadata_frame, text="MaxFALL:").grid( + row=1, column=0, sticky=tk.W + ) + self.hdr_maxfall_var = tk.StringVar(value="400") + ttk.Entry( + self.hdr_metadata_frame, textvariable=self.hdr_maxfall_var, width=6 + ).grid(row=1, column=1, padx=2) + + # 数据范围 + ttk.Label(self.hdr_signal_frame, text="数据范围:").grid( + row=3, column=0, sticky=tk.W, padx=5, pady=2 + ) + self.hdr_data_range_var = tk.StringVar(value="Full") + hdr_range_combo = ttk.Combobox( + self.hdr_signal_frame, + textvariable=self.hdr_data_range_var, + values=["Full", "Limited"], + width=10, + state="readonly", + ) + hdr_range_combo.grid(row=3, column=1, sticky=tk.W, padx=5, pady=2) + + # 编码位深 + ttk.Label(self.hdr_signal_frame, text="编码位深:").grid( + row=4, column=0, sticky=tk.W, padx=5, pady=2 + ) + self.hdr_bit_depth_var = tk.StringVar(value="8bit") + hdr_bit_depth_combo = ttk.Combobox( + self.hdr_signal_frame, + textvariable=self.hdr_bit_depth_var, + values=["8bit", "10bit", "12bit"], + width=10, + state="readonly", + ) + hdr_bit_depth_combo.grid(row=4, column=1, sticky=tk.W, padx=5, pady=2) + + # ==================== 初始化:默认只启用屏模组 Tab ==================== + self.signal_tabs.select(0) # 选中屏模组 + self.signal_tabs.tab(1, state="disabled") # 禁用 SDR + self.signal_tabs.tab(2, state="disabled") # 禁用 HDR + + def create_connection_content(self): + """创建设备连接区域""" + # 创建设备连接区域的主框架 + com_frame = ttk.Frame(self.connection_frame) + com_frame.pack(fill=tk.X, pady=5) + + # 获取可用的COM端口列表 + available_ports = self.get_available_com_ports() + + # 使用网格布局,更整齐 + ttk.Label(com_frame, text="UCD列表:").grid( + row=0, column=0, sticky=ttk.W, padx=5, pady=3 + ) + self.ucd_list_var = tk.StringVar(value=self.config.device_config["ucd_list"]) + self.ucd_list_combo = ttk.Combobox( + com_frame, + textvariable=self.ucd_list_var, + values=available_ports, + width=10, + state="readonly", + ) + self.ucd_list_combo.grid(row=0, column=1, sticky=ttk.W, padx=5, pady=3) + self.ucd_list_combo.bind("<>", self.update_config) + + # 添加UCD连接状态指示器 + self.ucd_status_indicator = tk.Canvas( + com_frame, width=15, height=15, bg="gray", highlightthickness=0 + ) + self.ucd_status_indicator.grid(row=0, column=2, padx=(10, 20)) + self.ucd_status_indicator.config(bg="gray") + + # 添加按钮框架 + button_frame = ttk.Frame(com_frame) + button_frame.grid(row=3, column=0, columnspan=3, pady=3, sticky="w") + + connect_icon = load_icon("assets/connect-svgrepo-com.png") + self.check_button = ttk.Button( + button_frame, + image=connect_icon, + bootstyle="link", + takefocus=False, + command=self.check_com_connections, + ) + self.check_button.image = connect_icon + self.check_button.pack(side="left", padx=0, pady=3) + + disconnect_icon = load_icon("assets/disconnect-svgrepo-com.png") + # 断开连接按钮 + self.disconnect_button = ttk.Button( + button_frame, + image=disconnect_icon, + bootstyle="link", + takefocus=False, + command=self.disconnect_com_connections, + ) + self.disconnect_button.image = disconnect_icon # 防止图标被垃圾回收 + self.disconnect_button.pack(side="left", padx=0, pady=3) + + refresh_icon = load_icon("assets/refresh-svgrepo-com.png") + self.refresh_button = ttk.Button( + button_frame, + image=refresh_icon, + bootstyle="link", + takefocus=False, + command=self.refresh_com_ports, + ) + self.refresh_button.image = refresh_icon # 防止图标被垃圾回收 + self.refresh_button.pack(side="left", padx=0, pady=3) + + # CA端口 + ttk.Label(com_frame, text="CA端口:").grid( + row=1, column=0, sticky=ttk.W, padx=5, pady=3 + ) + self.ca_com_var = tk.StringVar(value=self.config.device_config["ca_com"]) + self.ca_com_combo = ttk.Combobox( + com_frame, + textvariable=self.ca_com_var, + values=available_ports, + width=10, + state="readonly", + ) + self.ca_com_combo.grid(row=1, column=1, sticky=ttk.W, padx=5, pady=3) + self.ca_com_combo.bind("<>", self.update_config) + + # 添加CA连接状态指示器 + self.ca_status_indicator = tk.Canvas( + com_frame, width=15, height=15, bg="gray", highlightthickness=0 + ) + self.ca_status_indicator.grid(row=1, column=2, padx=(10, 20)) + self.ca_status_indicator.config(bg="gray") + + # 添加CA通道设置 + ttk.Label(com_frame, text="CA通道:").grid( + row=2, column=0, sticky=tk.W, padx=5, pady=3 + ) + self.ca_channel_var = tk.StringVar( + value=self.config.device_config["ca_channel"] + ) + ca_channel_combo = ttk.Combobox( + com_frame, + textvariable=self.ca_channel_var, + values=[str(i) for i in range(11)], + width=10, + state="readonly", + ) + ca_channel_combo.grid(row=2, column=1, sticky=ttk.W, padx=5, pady=3) + ca_channel_combo.bind("<>", self.update_config) + + def get_available_ucd_ports(self): + """获取可用的UCD端口列表""" + return self.ucd.search_device() + + def get_available_com_ports(self): + """获取可用的COM端口列表""" + try: + import serial.tools.list_ports + + ports = serial.tools.list_ports.comports() + return [port.device for port in ports] + except Exception as e: + self.log_gui.log(f"获取COM端口列表出错: {e}") + return [] + + def refresh_com_ports(self): + """刷新COM端口列表""" + available_ports = self.get_available_com_ports() + available_list = self.get_available_ucd_ports() + + # 更新UCD列表的下拉框选项 + ucd_list_current = self.ucd_list_var.get() + if ucd_list_current not in available_list: + self.ucd_list_var.set(available_list[0] if available_list else "") + self.ucd_list_combo.config(values=available_list) + + # 更新CA端口的下拉框选项 + ca_com_current = self.ca_com_var.get() + if ca_com_current not in available_ports: + self.ca_com_var.set( + available_ports[1] + if len(available_ports) > 1 + else (available_ports[0] if available_ports else "") + ) + self.ca_com_combo.config(values=available_ports) + + # 重置连接状态指示器为灰色 + if hasattr(self, "ucd_status_indicator"): + self.ucd_status_indicator.config(bg="gray") + if hasattr(self, "ca_status_indicator"): + self.ca_status_indicator.config(bg="gray") + + self.update_config() + + def check_com_connections(self): + """检测COM端口连接状态""" + # 禁用连接按钮,防止重复点击 + self.check_button.configure(state="disabled") + self.refresh_button.configure(state="disabled") + + # 更新状态栏 + self.status_var.set("正在检测连接...") + self.root.update() + + # 使用线程进行连接检测 + def check_connections(): + try: + # 检测TV连接 + ucd_connected = self.check_port_connection(is_ucd=True) + self.root.after( + 0, + lambda: self.update_connection_indicator( + self.ucd_status_indicator, ucd_connected + ), + ) + + # 检测CA连接 + ca_connected = self.check_port_connection(is_ucd=False) + self.root.after( + 0, + lambda: self.update_connection_indicator( + self.ca_status_indicator, ca_connected + ), + ) + + # 更新状态栏 + self.root.after(0, lambda: self.status_var.set("连接检测完成")) + + # 重新启用所有控件 + self.root.after(0, self.enable_com_widgets) + except Exception as e: + self.root.after(0, lambda: self.log_gui.log(f"连接检测出错: {e}")) + self.root.after(0, self.enable_com_widgets) + + # 启动线程 + threading.Thread(target=check_connections, daemon=True).start() + + def update_connection_indicator(self, indicator, connected): + """更新连接状态指示器颜色""" + if connected: + indicator.config(bg="green") + else: + indicator.config(bg="red") + + def check_port_connection(self, is_ucd=True): + """检测指定端口是否可以连接""" + try: + if is_ucd: + if self.ucd.status: + try: + self.ucd.close() + except: + pass + if not self.ucd.open(self.ucd_list_var.get()): + self.log_gui.log( + f"设备 {self.ucd_list_var.get()} 异常,UCD323连接失败" + ) + return False + else: + return True + else: + # 如果CA对象已存在,先关闭 + if self.ca is not None: + try: + self.ca.close() + except: + pass + channel_value = self.ca_channel_var.get() + str_channel = f"{int(channel_value):02d}" + self.ca = CASerail() + self.ca.open(self.config.device_config["ca_com"], 19200, 7, "E", 2) + # data = self.ca.set_xyLv_Display() + data = self.ca.set_all_Display() + if data: + data = self.ca.setSynchMode(3) + data = self.ca.setMeasureSpeed(1) + if True: + time.sleep(0.5) + data = self.ca.setZeroCalibration() + channel_value = self.ca_channel_var.get() + str_channel = f"{int(channel_value):02d}" + data = self.ca.setChannel(str_channel) + return True + else: + self.log_gui.log( + f"端口 {self.config.device_config["ca_com"]} 异常,色温仪连接失败" + ) + self.ca.close() + self.ca = None + return False + except Exception as e: + self.log_gui.log(f"端口连接失败: {e}") + return False + + def enable_com_widgets(self): + """重新启用所有控件""" + self.check_button.configure(state="normal") + self.refresh_button.configure(state="normal") + + def disconnect_com_connections(self): + """断开所有串口连接""" + try: + # 断开TV连接 + if self.ucd.status: + try: + self.ucd.close() + except: + pass + finally: + self.ucd.status = False + self.log_gui.log("UCD连接已断开") + + # 断开CA连接 + if self.ca is not None: + try: + self.ca.close() + except: + pass + finally: + self.ca = None + self.log_gui.log("CA连接已断开") + + # 重新启用相关控件 + self.enable_com_widgets() + self.ucd_status_indicator.config(bg="gray") + self.ca_status_indicator.config(bg="gray") + self.status_var.set("串口连接已断开") + + except Exception as e: + self.log_gui.log(f"断开连接时发生错误: {str(e)}") + messagebox.showerror("错误", f"断开连接失败: {str(e)}") + + def create_test_type_frame(self): + """创建测试类型选择区域(侧边栏形式)""" + # 设置测试类型变量 + self.test_type_var = tk.StringVar(value="screen_module") + + # 创建测试类型按钮并放置在侧边栏 + test_types = [ + ("屏模组性能测试", "screen_module"), + ("SDR Movie测试", "sdr_movie"), + ("HDR Movie测试", "hdr_movie"), + ] + + for text, type_value in test_types: + btn = ttk.Button( + master=self.sidebar_frame, + text=text, + style="Sidebar.TButton", + padding=10, + command=lambda v=type_value: self.change_test_type(v), + takefocus=False, + ) + btn.pack(fill=tk.X, padx=0, pady=1) + + # 保存按钮引用以便后续更新样式 + setattr(self, f"{type_value}_btn", btn) + + # 添加分隔线 + ttk.Separator(self.sidebar_frame, orient="horizontal").pack( + fill=tk.X, padx=10, pady=10 + ) + + # ✅ 只保留日志按钮 + self.log_btn = ttk.Button( + self.sidebar_frame, + text="测试日志", + style="Sidebar.TButton", + command=self.toggle_log_panel, + takefocus=False, + ) + self.log_btn.pack(fill=tk.X, padx=0, pady=1) + + # Local Dimming 测试按钮 + self.local_dimming_btn = ttk.Button( + self.sidebar_frame, + text="Local Dimming", + style="Sidebar.TButton", + command=self.toggle_local_dimming_panel, + takefocus=False, + ) + self.local_dimming_btn.pack(fill=tk.X, padx=0, pady=1) + + # 注册面板按钮(只保留日志) + if hasattr(self, "panels"): + if "log" in self.panels: + self.panels["log"]["button"] = self.log_btn + if "local_dimming" in self.panels: + self.panels["local_dimming"]["button"] = self.local_dimming_btn + + def update_config_info_display(self): + """更新配置信息显示""" + if hasattr(self, "config") and hasattr(self.config, "get_current_config"): + current_config = self.config.get_current_config() + + info_text = f"测试类型: {current_config.get('name', '未知')}\n" + info_text += ( + f"测试项目: {', '.join(current_config.get('test_items', []))}\n" + ) + info_text += f"信号格式: {current_config.get('signal_format', 'none')}\n" + info_text += f"色彩空间: {current_config.get('color_space', 'unknown')}\n" + info_text += f"位深度: {current_config.get('bit_depth', 'unknown')}" + + # 高亮当前选中的测试类型 + self.update_sidebar_selection() + + def create_operation_frame(self): + """创建操作按钮区域""" + operation_frame = ttk.Frame(self.control_frame_top) + operation_frame.pack(fill=tk.X, padx=5, pady=10) + + self.start_btn = ttk.Button( + operation_frame, + text="开始测试", + command=self.start_test, + style="success.TButton", + ) + self.start_btn.pack(side=tk.LEFT, padx=5) + + self.stop_btn = ttk.Button( + operation_frame, + text="停止测试", + command=self.stop_test, + style="danger.TButton", + state=tk.DISABLED, + ) + self.stop_btn.pack(side=tk.LEFT, padx=5) + + self.save_btn = ttk.Button( + operation_frame, + text="保存结果", + command=self.save_results, + state=tk.DISABLED, + ) + self.save_btn.pack(side=tk.LEFT, padx=5) + + self.clear_config_btn = ttk.Button( + operation_frame, + text="清理配置", + command=self.clear_config_file, + ) + self.clear_config_btn.pack(side=tk.LEFT, padx=5) + + self.custom_btn = ttk.Button( + operation_frame, + text="客户模版", + command=self.start_custom_template_test, + style="info.TButton", + ) + self.custom_btn.pack(side=tk.LEFT, padx=5) + self.update_custom_button_visibility() + + def create_custom_template_result_panel(self): + """创建客户模板结果显示区域(黑底表格)""" + self.custom_result_frame = ttk.LabelFrame( + self.custom_template_tab_frame, text="客户模板结果显示" + ) + self.custom_result_frame.pack( + side=tk.TOP, fill=tk.BOTH, expand=True, padx=5, pady=5 + ) + + table_container = tk.Frame( + self.custom_result_frame, + bg="#000000", + highlightthickness=1, + highlightbackground="#5a5a5a", + ) + table_container.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) + + style = ttk.Style() + style.configure( + "CustomResult.Treeview", + background="#000000", + fieldbackground="#000000", + foreground="#ffffff", + rowheight=28, + borderwidth=0, + ) + style.configure( + "CustomResult.Treeview.Heading", + background="#2f2f2f", + foreground="#f5f5f5", + font=("Microsoft YaHei", 10, "bold"), + relief="flat", + ) + style.map( + "CustomResult.Treeview", + background=[("selected", "#1f4e79")], + foreground=[("selected", "#ffffff")], + ) + style.map( + "CustomResult.Treeview.Heading", + background=[("active", "#3b3b3b")], + ) + + columns = ( + "Pattern", + "No.", + "X", + "Y", + "Z", + "x", + "y", + "Lv", + "u'", + "v'", + "Tcp", + "duv", + "λd/λc", + "Pe" + ) + + self.custom_result_tree = ttk.Treeview( + table_container, + columns=columns, + show="headings", + height=4, + style="CustomResult.Treeview", + ) + + column_widths = { + "Pattern": 90, + "No.": 60, + "X": 80, + "Y": 80, + "Z": 80, + "x": 80, + "y": 80, + "Lv": 80, + "u'": 80, + "v'": 80, + "Tcp": 90, + "duv": 80, + "λd/λc": 95, + "Pe": 80, + } + + for col in columns: + self.custom_result_tree.heading(col, text=col) + self.custom_result_tree.column( + col, + width=column_widths.get(col, 80), + minwidth=60, + anchor=tk.CENTER, + stretch=False, + ) + + y_scroll = ttk.Scrollbar( + table_container, + orient=tk.VERTICAL, + command=self.custom_result_tree.yview, + ) + x_scroll = ttk.Scrollbar( + table_container, + orient=tk.HORIZONTAL, + command=self.custom_result_tree.xview, + ) + + self.custom_result_tree.configure( + yscrollcommand=y_scroll.set, + xscrollcommand=x_scroll.set, + ) + + self.custom_result_tree.grid(row=0, column=0, sticky="nsew") + y_scroll.grid(row=0, column=1, sticky="ns") + x_scroll.grid(row=1, column=0, sticky="ew") + + # 右键菜单:复制全部数据(Excel 可直接按行列粘贴) + self.custom_result_menu = tk.Menu(self.root, tearoff=0) + self.custom_result_menu.add_command( + label="复制全部数据", + command=self.copy_custom_result_table, + ) + self.custom_result_menu.add_command( + label="单步测试", + command=self.start_custom_row_single_step, + ) + + # self.custom_result_menu.add_separator() + # self.custom_result_menu.add_command( + # label="单步测试", + # command=self.fill_custom_result_test_data, + # ) + self.custom_result_tree.bind("", self.show_custom_result_context_menu) + + table_container.grid_rowconfigure(0, weight=1) + table_container.grid_columnconfigure(0, weight=1) + + def show_custom_result_context_menu(self, event): + """显示客户模板结果右键菜单""" + if not hasattr(self, "custom_result_tree") or not hasattr( + self, "custom_result_menu" + ): + return + + if self.testing: + # 测试进行中锁定客户模板结果表,禁止右键菜单。 + return + + row_id = self.custom_result_tree.identify_row(event.y) + if row_id: + self.custom_result_tree.selection_set(row_id) + self.custom_result_tree.focus(row_id) + + has_rows = len(self.custom_result_tree.get_children()) > 0 + has_selection = len(self.custom_result_tree.selection()) > 0 + can_single_step = ( + has_selection + and self.ca is not None + and self.ucd is not None + and not self.testing + ) + try: + self.custom_result_menu.entryconfigure( + 0, + state=("normal" if has_rows else "disabled"), + ) + self.custom_result_menu.entryconfigure( + 1, + state=("normal" if can_single_step else "disabled"), + ) + self.custom_result_menu.tk_popup(event.x_root, event.y_root) + finally: + self.custom_result_menu.grab_release() + + def set_custom_result_table_locked(self, locked): + """锁定/解锁客户模板结果表(测试期间禁选择、禁右键)""" + if not hasattr(self, "custom_result_tree"): + return + + try: + self.custom_result_tree.configure(selectmode=("none" if locked else "browse")) + except Exception: + pass + + def start_custom_row_single_step(self): + """单步测试当前选中行:发送该行 pattern 并覆盖该行测量结果""" + if not hasattr(self, "custom_result_tree"): + return + + if self.ca is None or self.ucd is None: + messagebox.showerror("错误", "请先连接CA410和信号发生器") + return + + if self.testing: + messagebox.showinfo("提示", "测试进行中,无法执行单步测试") + return + + selected = self.custom_result_tree.selection() + if not selected: + messagebox.showinfo("提示", "请先选中一行再执行单步测试") + return + + item_id = selected[0] + values = self.custom_result_tree.item(item_id, "values") + if not values: + messagebox.showinfo("提示", "选中行没有有效数据") + return + + row_no = None + if len(values) > 1: + try: + row_no = int(float(values[1])) + except Exception: + row_no = None + + if row_no is None or row_no <= 0: + children = list(self.custom_result_tree.get_children()) + row_no = children.index(item_id) + 1 if item_id in children else 1 + + self._clear_custom_result_row(item_id, row_no) + + threading.Thread( + target=self._run_custom_row_single_step, + args=(item_id, row_no), + daemon=True, + ).start() + + def _clear_custom_result_row(self, item_id, row_no): + """单步测试开始前清空指定行的测量数据""" + if not hasattr(self, "custom_result_tree"): + return + + old_values = list(self.custom_result_tree.item(item_id, "values")) + pattern_name = old_values[0] if len(old_values) > 0 else f"P {row_no}" + + cleared_values = ( + pattern_name, + row_no, + "---", + "---", + "---", + "---", + "---", + "---", + "---", + "---", + "---", + "---", + "---", + "---", + ) + + self.custom_result_tree.item(item_id, values=cleared_values) + self.custom_result_tree.see(item_id) + + def _run_custom_row_single_step(self, item_id, row_no): + """后台执行客户模板单步测试""" + try: + self.root.after(0, lambda: self.status_var.set(f"单步测试第 {row_no} 行...")) + self.log_gui.log(f"开始单步测试第 {row_no} 行") + + self.config.set_current_pattern("custom") + + # 与批量 custom 测试保持一致:根据当前 SDR 配置转换 pattern 数据。 + import copy + + data_range = self.sdr_data_range_var.get() + original_params = copy.deepcopy(self.config.default_pattern_temp["pattern_params"]) + converted_params = convert_pattern_params( + pattern_params=original_params, + data_range=data_range, + verbose=False, + ) + + temp_config = self.config.get_temp_config_with_converted_params( + mode="custom", + converted_params=converted_params, + ) + + if row_no > len(converted_params): + self.log_gui.log(f"❌ 行号超出 pattern 范围: {row_no}/{len(converted_params)}") + self.root.after(0, lambda: self.status_var.set("单步测试失败:行号超范围")) + return + + self.ucd.set_ucd_params(temp_config) + pattern_param = converted_params[row_no - 1] + self.ucd.set_pattern(self.ucd.current_pattern, pattern_param) + self.ucd.run() + + time.sleep(self.pattern_settle_time) + + # 测量:显示模式1读取 Tcp/duv/Lv,显示模式8读取 λd/Pe/Lv 与 XYZ。 + self.ca.set_Display(1) + tcp, duv, lv, _, _, _ = self.ca.readAllDisplay() + + self.ca.set_Display(8) + lambda_d, pe, lv, X, Y, Z = self.ca.readAllDisplay() + + xy = colour.XYZ_to_xy(np.array([X, Y, Z])) + u_prime, v_prime, _ = colour.XYZ_to_CIE1976UCS(np.array([X, Y, Z])) + + row_data = { + "X": X, + "Y": Y, + "Z": Z, + "x": xy[0], + "y": xy[1], + "Lv": lv, + "u_prime": u_prime, + "v_prime": v_prime, + "Tcp": tcp, + "duv": duv, + "lambda_d": lambda_d, + "Pe": pe, + } + + self.root.after( + 0, + lambda: self._update_custom_result_row(item_id, row_no, row_data), + ) + + self.log_gui.log(f"✓ 第 {row_no} 行单步测试完成并已覆盖") + self.root.after(0, lambda: self.status_var.set(f"第 {row_no} 行单步测试完成")) + + except Exception as e: + self.log_gui.log(f"❌ 单步测试失败: {str(e)}") + self.root.after(0, lambda: self.status_var.set("单步测试失败")) + + def _update_custom_result_row(self, item_id, row_no, result_data): + """覆盖更新客户模板结果表中指定行""" + + def fmt(value, digits=4): + if value is None: + return "--" + if isinstance(value, (int, float, np.floating)): + # CA 返回异常哨兵值(如 -99999999)时,显示为占位符。 + if (not np.isfinite(value)) or value <= -99999998: + return "---" + return f"{value:.{digits}f}" + try: + numeric_value = float(value) + if (not np.isfinite(numeric_value)) or numeric_value <= -99999998: + return "---" + except (TypeError, ValueError): + pass + return str(value) + + old_values = list(self.custom_result_tree.item(item_id, "values")) + pattern_name = old_values[0] if len(old_values) > 0 else f"P {row_no}" + + new_values = ( + pattern_name, + row_no, + fmt(result_data.get("X")), + fmt(result_data.get("Y")), + fmt(result_data.get("Z")), + fmt(result_data.get("x")), + fmt(result_data.get("y")), + fmt(result_data.get("Lv"), 3), + fmt(result_data.get("u_prime")), + fmt(result_data.get("v_prime")), + fmt(result_data.get("Tcp"), 1), + fmt(result_data.get("duv"), 5), + fmt(result_data.get("lambda_d"), 1), + fmt(result_data.get("Pe"), 1), + ) + + self.custom_result_tree.item(item_id, values=new_values) + + def copy_custom_result_table(self): + """复制客户模板结果表格到剪贴板(不含标题行/No./Pattern)""" + if not hasattr(self, "custom_result_tree"): + return + + items = self.custom_result_tree.get_children() + if not items: + messagebox.showinfo("提示", "当前没有可复制的数据") + return + + lines = [] + columns = tuple(self.custom_result_tree["columns"]) + excluded_col_indexes = { + idx + for idx, col_name in enumerate(columns) + if col_name in ("No.", "Pattern") + } + + for item in items: + values = self.custom_result_tree.item(item, "values") + # 跳过 No. 和 Pattern 两列,只保留测量数据列。 + data_values = [ + v for idx, v in enumerate(values) if idx not in excluded_col_indexes + ] + row = [ + str(v).replace("\t", " ").replace("\n", " ") + for v in data_values + ] + lines.append("\t".join(row)) + + clipboard_text = "\n".join(lines) + self.root.clipboard_clear() + self.root.clipboard_append(clipboard_text) + self.root.update_idletasks() + + if hasattr(self, "status_var"): + self.status_var.set(f"已复制 {len(items)} 行客户模板数据到剪贴板") + if hasattr(self, "log_gui"): + self.log_gui.log(f"✓ 已复制客户模板表格数据({len(items)} 行)") + + def fill_custom_result_test_data(self): + """填充 147 行客户模板测试数据(用于界面验证)""" + if not hasattr(self, "custom_result_tree"): + return + + self.clear_custom_template_results() + + pattern_names = [] + if hasattr(self, "config") and hasattr(self.config, "get_temp_pattern_names"): + pattern_names = self.config.get_temp_pattern_names() + + total_rows = 147 + for i in range(1, total_rows + 1): + ratio = (i - 1) / (total_rows - 1) if total_rows > 1 else 0 + row_data = { + "pattern_name": ( + pattern_names[i - 1] if i - 1 < len(pattern_names) else f"P {i}" + ), + "X": 0.8 + ratio * 120, + "Y": 0.9 + ratio * 135, + "Z": 1.1 + ratio * 145, + "x": 0.24 + ratio * 0.10, + "y": 0.26 + ratio * 0.10, + "Lv": 1.0 + ratio * 500, + "u_prime": 0.16 + ratio * 0.12, + "v_prime": 0.42 + ratio * 0.08, + "Tcp": 1800 + ratio * 12000, + "duv": -0.01 + ratio * 0.03, + "lambda_d": 430 + ratio * 200, + "Pe": 10 + ratio * 90, + } + self.append_custom_template_result(i, row_data) + + if hasattr(self, "chart_notebook") and hasattr(self, "custom_template_tab_frame"): + self.chart_notebook.select(self.custom_template_tab_frame) + + if hasattr(self, "status_var"): + self.status_var.set("已填充 147 行客户模板测试数据") + if hasattr(self, "log_gui"): + self.log_gui.log("✓ 已填充 147 行客户模板测试数据") + + def clear_custom_template_results(self): + """清空客户模板结果表格""" + if not hasattr(self, "custom_result_tree"): + return + for item in self.custom_result_tree.get_children(): + self.custom_result_tree.delete(item) + + def auto_expand_custom_result_view(self): + """当客户模板表格有数据时,自动扩展窗口以尽量完整显示所有列""" + if not hasattr(self, "custom_result_tree"): + return + + if len(self.custom_result_tree.get_children()) == 0: + return + + try: + self.root.update_idletasks() + + columns = tuple(self.custom_result_tree["columns"]) + columns_total_width = 0 + for col in columns: + columns_total_width += int(self.custom_result_tree.column(col, "width")) + + left_panel_width = self.left_frame.winfo_width() if hasattr(self, "left_frame") else 180 + if left_panel_width <= 1: + left_panel_width = 180 + + # 列宽 + 左侧导航 + 滚动条/边框/外边距。 + target_width = int(left_panel_width + columns_total_width + 120) + + screen_max_width = max(900, self.root.winfo_screenwidth() - 40) + target_width = min(target_width, screen_max_width) + + current_width = self.root.winfo_width() + current_height = self.root.winfo_height() + + # 只扩不缩,避免用户窗口被反复改变。 + if target_width > current_width: + self.root.geometry(f"{target_width}x{current_height}") + self.root.update_idletasks() + except Exception as e: + if hasattr(self, "log_gui"): + self.log_gui.log(f"⚠️ 自动扩展客户模板窗口失败: {str(e)}") + + def append_custom_template_result(self, row_no, result_data): + """追加一条客户模板结果到表格""" + + def fmt(value, digits=4): + if value is None: + return "--" + if isinstance(value, (int, float, np.floating)): + # CA 返回异常哨兵值(如 -99999999)时,显示为占位符。 + if (not np.isfinite(value)) or value <= -99999998: + return "---" + return f"{value:.{digits}f}" + try: + numeric_value = float(value) + if (not np.isfinite(numeric_value)) or numeric_value <= -99999998: + return "---" + except (TypeError, ValueError): + pass + return str(value) + + row_values = ( + result_data.get("pattern_name", f"P {row_no}"), + row_no, + fmt(result_data.get("X")), + fmt(result_data.get("Y")), + fmt(result_data.get("Z")), + fmt(result_data.get("x")), + fmt(result_data.get("y")), + fmt(result_data.get("Lv"), 3), + fmt(result_data.get("u_prime")), + fmt(result_data.get("v_prime")), + fmt(result_data.get("Tcp"), 1), + fmt(result_data.get("duv"), 5), + fmt(result_data.get("lambda_d"), 1), + fmt(result_data.get("Pe"), 1) + ) + + if hasattr(self, "custom_result_tree"): + item_id = self.custom_result_tree.insert("", tk.END, values=row_values) + # 新增数据后自动跳转到最新行。 + self.custom_result_tree.see(item_id) + self.auto_expand_custom_result_view() + + def start_custom_template_test(self): + """开始客户模板测试(SDR)""" + + if hasattr(self, "chart_notebook") and hasattr(self, "custom_template_tab_frame"): + self.chart_notebook.select(self.custom_template_tab_frame) + + if self.ca is None or self.ucd is None: + messagebox.showerror("错误", "请先连接CA410和信号发生器") + return + + if self.testing: + messagebox.showinfo("提示", "测试已在进行中") + return + + if hasattr(self, "debug_container"): + self.debug_container.pack_forget() + + self.testing = True + self.start_btn.config(state=tk.DISABLED) + self.stop_btn.config(state=tk.NORMAL) + self.save_btn.config(state=tk.DISABLED) + self.clear_config_btn.config(state=tk.DISABLED) + self.custom_btn.config(state=tk.DISABLED) + self.status_var.set("客户模板测试进行中...") + + self.log_gui.clear_log() + self.clear_custom_template_results() + + confirm = messagebox.askyesno( + "确认测试", "开始客户模板测试(SDR)?\n\n将采集并显示客户模板格式结果。" + ) + + if not confirm: + self.testing = False + self.start_btn.config(state=tk.NORMAL) + self.stop_btn.config(state=tk.DISABLED) + self.clear_config_btn.config(state=tk.NORMAL) + self.custom_btn.config(state=tk.NORMAL) + self.status_var.set("测试已取消") + self.set_custom_result_table_locked(False) + return + + self.set_custom_result_table_locked(True) + + self.test_thread = threading.Thread(target=self.run_custom_sdr_test, args=([],)) + self.test_thread.daemon = True + self.test_thread.start() + + + def register_panel(self, panel_name, frame, button, visible_attr): + """注册一个面板到管理系统""" + self.panels[panel_name] = { + "frame": frame, + "button": button, + "visible_attr": visible_attr, + } + + def show_panel(self, panel_name): + """显示指定面板,隐藏其他所有面板""" + if panel_name not in self.panels: + return + + # 如果当前面板就是要显示的面板,则隐藏它 + if self.current_panel == panel_name: + self.hide_all_panels() + return + + # 隐藏所有面板 + self.hide_all_panels() + + # 显示指定面板 + panel_info = self.panels[panel_name] + + # 隐藏主内容区域 + self.control_frame_top.pack_forget() + self.control_frame_middle.pack_forget() + self.control_frame_bottom.pack_forget() + + # 显示目标面板 + panel_info["frame"].pack(side=tk.TOP, fill=tk.BOTH, expand=True, padx=5, pady=5) + + # 更新按钮样式 + if panel_info["button"]: + panel_info["button"].configure(style="SidebarSelected.TButton") + + # 更新状态 + setattr(self, panel_info["visible_attr"], True) + self.current_panel = panel_name + + def hide_all_panels(self): + """隐藏所有面板,显示主内容区域""" + # 隐藏所有注册的面板 + for panel_name, panel_info in self.panels.items(): + panel_info["frame"].pack_forget() + if panel_info["button"]: + panel_info["button"].configure(style="Sidebar.TButton") + setattr(self, panel_info["visible_attr"], False) + + # 显示主内容区域 + self.control_frame_top.pack( + side=tk.TOP, fill=tk.BOTH, expand=True, padx=0, pady=5 + ) + self.control_frame_middle.pack( + side=tk.TOP, fill=tk.BOTH, expand=True, padx=0, pady=5 + ) + self.control_frame_bottom.pack( + side=tk.TOP, fill=tk.BOTH, expand=True, padx=0, pady=5 + ) + + self.current_panel = None + + def create_log_panel(self): + """创建日志面板""" + self.log_frame = ttk.Frame(self.content_frame) + self.log_gui = PQLogGUI(self.log_frame) + self.log_gui.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) + + # 默认隐藏日志面板 + self.log_visible = False + + # 注册到面板管理系统 + self.register_panel( + "log", self.log_frame, None, "log_visible" + ) # button会在后面设置 + + def create_local_dimming_panel(self): + """创建 Local Dimming 测试面板 - 手动控制版""" + self.local_dimming_frame = ttk.Frame(self.content_frame) + + # 主容器 + main_container = ttk.Frame(self.local_dimming_frame, padding=10) + main_container.pack(fill=tk.BOTH, expand=True) + + # ==================== 1. 标题 ==================== + title_frame = ttk.Frame(main_container) + title_frame.pack(fill=tk.X, pady=(0, 10)) + + ttk.Label( + title_frame, + text="🔆 Local Dimming 窗口测试", + font=("微软雅黑", 14, "bold"), + ).pack(side=tk.LEFT) + + # ==================== 2. 窗口百分比按钮 ==================== + window_frame = ttk.LabelFrame( + main_container, text="🔆 窗口百分比(点击发送)", padding=10 + ) + window_frame.pack(fill=tk.X, pady=(0, 10)) + + # 说明文字 + ttk.Label( + window_frame, + text="点击按钮发送对应百分比的白色窗口(黑色背景 + 居中白色矩形)", + font=("", 9), + foreground="#28a745", + ).pack(pady=(0, 8)) + + # 第一行:1%, 2%, 5%, 10%, 18% + row1 = ttk.Frame(window_frame) + row1.pack(fill=tk.X, pady=(0, 5)) + + percentages_row1 = [1, 2, 5, 10, 18] + for p in percentages_row1: + ttk.Button( + row1, + text=f"{p}%", + command=lambda p=p: self.send_ld_window(p), + bootstyle="success", + width=12, + ).pack(side=tk.LEFT, padx=3) + + # 第二行:25%, 50%, 75%, 100% + row2 = ttk.Frame(window_frame) + row2.pack(fill=tk.X) + + percentages_row2 = [25, 50, 75, 100] + for p in percentages_row2: + ttk.Button( + row2, + text=f"{p}%", + command=lambda p=p: self.send_ld_window(p), + bootstyle="success", + width=12, + ).pack(side=tk.LEFT, padx=3) + + # ==================== 4. CA410 采集按钮 ==================== + measure_frame = ttk.LabelFrame(main_container, text="📊 CA410 测量", padding=10) + measure_frame.pack(fill=tk.X, pady=(0, 10)) + + measure_btn_frame = ttk.Frame(measure_frame) + measure_btn_frame.pack(fill=tk.X) + + self.ld_measure_btn = ttk.Button( + measure_btn_frame, + text="📏 采集当前亮度", + command=self.measure_ld_luminance, + bootstyle="primary", + width=15, + ) + self.ld_measure_btn.pack(side=tk.LEFT, padx=(0, 5)) + + # 显示测量结果 + self.ld_result_label = ttk.Label( + measure_btn_frame, + text="亮度: -- cd/m² | x: -- | y: --", + font=("Consolas", 10), + foreground="#007bff", + ) + self.ld_result_label.pack(side=tk.LEFT, padx=(10, 0)) + + # ==================== 5. 测试结果表格 ==================== + result_frame = ttk.LabelFrame(main_container, text="📋 测试记录", padding=10) + result_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10)) + + # Treeview + columns = ("窗口百分比", "亮度 (cd/m²)", "x", "y", "时间") + self.ld_tree = ttk.Treeview( + result_frame, columns=columns, show="headings", height=10 + ) + + for col in columns: + self.ld_tree.heading(col, text=col) + if col == "窗口百分比": + self.ld_tree.column(col, width=100, anchor=tk.CENTER) + elif col == "时间": + self.ld_tree.column(col, width=120, anchor=tk.CENTER) + else: + self.ld_tree.column(col, width=100, anchor=tk.CENTER) + + self.ld_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + + # 滚动条 + scrollbar = ttk.Scrollbar( + result_frame, orient=tk.VERTICAL, command=self.ld_tree.yview + ) + scrollbar.pack(side=tk.RIGHT, fill=tk.Y) + self.ld_tree.configure(yscrollcommand=scrollbar.set) + + # ==================== 6. 底部操作按钮 ==================== + bottom_frame = ttk.Frame(main_container) + bottom_frame.pack(fill=tk.X) + + self.ld_clear_btn = ttk.Button( + bottom_frame, + text="🗑️ 清空记录", + command=self.clear_ld_records, + bootstyle="danger-outline", + width=12, + ) + self.ld_clear_btn.pack(side=tk.LEFT, padx=(0, 5)) + + self.ld_save_btn = ttk.Button( + bottom_frame, + text="💾 保存结果", + command=self.save_local_dimming_results, + bootstyle="info", + width=12, + ) + self.ld_save_btn.pack(side=tk.LEFT) + + # 默认隐藏 + self.local_dimming_visible = False + + # 注册到面板管理系统 + self.register_panel( + "local_dimming", + self.local_dimming_frame, + None, + "local_dimming_visible", + ) + + # 初始化当前窗口百分比(用于记录) + self.current_ld_percentage = None + + def toggle_local_dimming_panel(self): + """切换 Local Dimming 面板显示""" + self.show_panel("local_dimming") + + def toggle_log_panel(self): + """切换日志面板的显示状态""" + self.show_panel("log") + + def create_result_chart_frame(self): + """创建结果图表区域 - 6个独立Tab(Gamma 和 EOTF 分离)""" + # 创建Notebook用于图表切换 + self.chart_notebook = ttk.Notebook(self.result_frame) + self.chart_notebook.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) + + # ========== 创建6个独立的Tab页面 ========== + # 1. 色域图页面 + self.gamut_chart_frame = ttk.Frame(self.chart_notebook) + + # 2. Gamma图页面(SDR/屏模组使用) + self.gamma_chart_frame = ttk.Frame(self.chart_notebook) + + # 3. EOTF图页面(HDR专用) + self.eotf_chart_frame = ttk.Frame(self.chart_notebook) + + # 4. 色度一致性页面 + self.cct_chart_frame = ttk.Frame(self.chart_notebook) + + # 5. 对比度页面 + self.contrast_chart_frame = ttk.Frame(self.chart_notebook) + + # 6. 色准页面 + self.accuracy_chart_frame = ttk.Frame(self.chart_notebook) + + # 7. 客户模板结果页面 + self.custom_template_tab_frame = ttk.Frame(self.chart_notebook) + + # ========== 添加到Notebook(初始只添加前5个)========== + self.chart_notebook.add(self.gamut_chart_frame, text="色域图") + self.chart_notebook.add(self.gamma_chart_frame, text="Gamma曲线") + # ← EOTF 不添加,由 change_test_type() 动态控制 + self.chart_notebook.add(self.cct_chart_frame, text="色度一致性") + self.chart_notebook.add(self.contrast_chart_frame, text="对比度") + self.chart_notebook.add(self.accuracy_chart_frame, text="色准") + + # 初始化六个独立的图表 + self.init_gamut_chart() + self.init_gamma_chart() + self.init_eotf_chart() + self.init_cct_chart() + self.init_contrast_chart() + self.init_accuracy_chart() + + # 绑定Tab切换事件 + self.chart_notebook.bind("<>", self.on_chart_tab_changed) + + # ==================== ✅ 在图表下方创建单步调试面板 ==================== + self.debug_container = ttk.LabelFrame( + self.result_frame, # ← 放在 result_frame 内,图表正下方 + text="🔧 单步调试", + padding=10, + ) + # 默认不显示 + + # 创建单步调试面板实例 + self.debug_panel = PQDebugPanel(self.debug_container, self) + + self.log_gui.log("✓ 单步调试面板已创建(放在测试结果图表下方)") + + def on_chart_tab_changed(self, event): + """Tab切换时的事件处理""" + try: + self._last_tab_index = self.chart_notebook.index( + self.chart_notebook.select() + ) + except Exception as e: + self.log_gui.log(f"Tab切换事件处理失败: {str(e)}") + + def change_test_type(self, test_type): + """切换测试类型""" + # 切换测试类型时,自动隐藏日志面板和 Local Dimming 面板 + if self.current_panel in ("log", "local_dimming"): + self.hide_all_panels() + # 先保存当前测试类型的色度参数 + if hasattr(self, "cct_x_ideal_var"): + try: + current_type = self.config.current_test_type + if current_type == "screen_module": + self.save_cct_params() + elif current_type == "sdr_movie": + self.save_sdr_cct_params() + elif current_type == "hdr_movie": + if hasattr(self, "save_hdr_cct_params"): + self.save_hdr_cct_params() + except Exception as e: + if hasattr(self, "log_gui"): + self.log_gui.log(f"保存参数失败: {str(e)}") + + # 更新测试类型 + self.test_type_var.set(test_type) + if hasattr(self, "config") and hasattr(self.config, "set_current_test_type"): + success = self.config.set_current_test_type(test_type) + if not success and hasattr(self, "log_gui"): + self.log_gui.log(f"切换测试类型失败: {test_type}") + + # 更新测试项目和侧边栏 + self.update_test_items() + self.update_sidebar_selection() + self.on_test_type_change() + + # ========== ✅ 1. 切换信号格式 Tab ========== + if hasattr(self, "signal_tabs"): + try: + # 定义测试类型与信号格式 Tab 的映射 + tab_mapping = { + "screen_module": 0, # 屏模组测试 + "sdr_movie": 1, # SDR测试 + "hdr_movie": 2, # HDR + } + + target_tab = tab_mapping.get(test_type, 0) + + # 先启用所有 Tab + for i in range(3): + self.signal_tabs.tab(i, state="normal") + + # 切换到目标 Tab + self.signal_tabs.select(target_tab) + + # 强制刷新显示 + self.signal_tabs.update() + self.root.update_idletasks() + + # 强制显示对应的 Frame + if target_tab == 0: + self.screen_module_signal_frame.tkraise() + elif target_tab == 1: + self.sdr_signal_frame.tkraise() + elif target_tab == 2: + self.hdr_signal_frame.tkraise() + + # 禁用其他 Tab + for i in range(3): + if i != target_tab: + self.signal_tabs.tab(i, state="disabled") + + # 日志记录 + if hasattr(self, "log_gui"): + tab_names = ["屏模组测试", "SDR测试", "HDR"] + self.log_gui.log(f"✓ 已切换到 {tab_names[target_tab]} 信号格式") + + except Exception as e: + if hasattr(self, "log_gui"): + self.log_gui.log(f"切换信号格式失败: {str(e)}") + else: + if hasattr(self, "log_gui"): + self.log_gui.log("⚠️ signal_tabs 尚未创建") + + # ========== 2. 动态切换 Gamma/EOTF Tab ========== + if hasattr(self, "chart_notebook"): + try: + current_tabs = list(self.chart_notebook.tabs()) + + # 获取当前 Tab 的索引 + gamma_tab_id = str(self.gamma_chart_frame) + eotf_tab_id = str(self.eotf_chart_frame) + + if test_type == "hdr_movie": + # ========== HDR 测试:移除 Gamma,添加 EOTF ========== + + # 1. 如果 Gamma Tab 存在,移除它 + if gamma_tab_id in current_tabs: + gamma_index = current_tabs.index(gamma_tab_id) + self.chart_notebook.forget(gamma_index) + if hasattr(self, "log_gui"): + self.log_gui.log("✓ 已隐藏 Gamma 曲线 Tab") + + # 2. 如果 EOTF Tab 不存在,添加它(在色域图之后) + if eotf_tab_id not in current_tabs: + self.chart_notebook.insert( + 1, self.eotf_chart_frame, text="EOTF 曲线" + ) + if hasattr(self, "log_gui"): + self.log_gui.log("✓ 已显示 EOTF 曲线 Tab") + + else: + # ========== SDR/屏模组测试:移除 EOTF,添加 Gamma ========== + + # 1. 如果 EOTF Tab 存在,移除它 + if eotf_tab_id in current_tabs: + eotf_index = current_tabs.index(eotf_tab_id) + self.chart_notebook.forget(eotf_index) + if hasattr(self, "log_gui"): + self.log_gui.log("✓ 已隐藏 EOTF 曲线 Tab") + + # 2. 如果 Gamma Tab 不存在,添加它(在色域图之后) + if gamma_tab_id not in current_tabs: + self.chart_notebook.insert( + 1, self.gamma_chart_frame, text="Gamma 曲线" + ) + if hasattr(self, "log_gui"): + self.log_gui.log("✓ 已显示 Gamma 曲线 Tab") + + # ========== 3. 仅在 SDR 测试显示客户模板结果 Tab ========== + custom_tab_id = str(self.custom_template_tab_frame) + current_tabs = list(self.chart_notebook.tabs()) + + if test_type == "sdr_movie": + if custom_tab_id not in current_tabs: + self.chart_notebook.add( + self.custom_template_tab_frame, + text="客户模板结果显示", + ) + if hasattr(self, "log_gui"): + self.log_gui.log("✓ 已显示客户模板结果 Tab") + else: + if custom_tab_id in current_tabs: + self.chart_notebook.forget(self.custom_template_tab_frame) + if hasattr(self, "log_gui"): + self.log_gui.log("✓ 已隐藏客户模板结果 Tab") + + # 刷新显示 + self.chart_notebook.update_idletasks() + + except Exception as e: + if hasattr(self, "log_gui"): + self.log_gui.log(f"切换 Gamma/EOTF Tab 失败: {str(e)}") + + def update_sidebar_selection(self): + """更新侧边栏按钮的选中状态""" + # 重置所有按钮样式为默认 + self.screen_module_btn.configure(style="Sidebar.TButton") + self.sdr_movie_btn.configure(style="Sidebar.TButton") + self.hdr_movie_btn.configure(style="Sidebar.TButton") + + # 设置当前选中按钮的样式 + current_type = self.test_type_var.get() + if current_type == "screen_module": + self.screen_module_btn.configure(style="SidebarSelected.TButton") + elif current_type == "sdr_movie": + self.sdr_movie_btn.configure(style="SidebarSelected.TButton") + elif current_type == "hdr_movie": + self.hdr_movie_btn.configure(style="SidebarSelected.TButton") + + def on_test_type_change(self): + """根据测试类型更新内容区域""" + test_type = self.test_type_var.get() + + # 获取当前测试类型的配置 + if hasattr(self, "config") and hasattr(self.config, "get_current_config"): + current_config = self.config.get_current_config() + + # 更新配置信息显示 + self.update_config_info_display() + + # SDR 选中时显示客户模版按钮 + self.update_custom_button_visibility() + + def update_custom_button_visibility(self): + """只在 SDR 测试时显示客户模版按钮""" + if not hasattr(self, "custom_btn") or not hasattr(self, "test_type_var"): + return + if self.test_type_var.get() == "sdr_movie": + if not self.custom_btn.winfo_manager(): + self.custom_btn.pack(side=tk.LEFT, padx=5) + else: + if self.custom_btn.winfo_manager(): + self.custom_btn.pack_forget() + + def start_test(self): + """开始测试""" + # 检查设备连接状态 + if self.ca is None or self.ucd is None: + messagebox.showerror("错误", "请先连接CA410和信号发生器") + return + + # 检查是否已经在测试中 + if self.testing: + messagebox.showinfo("提示", "测试已在进行中") + return + + # ✅ 禁用并隐藏单步调试 + if hasattr(self, "debug_panel"): + self.debug_panel.disable_all_debug() + self.log_gui.log("✓ 单步调试已禁用") + + if hasattr(self, "debug_container"): + self.debug_container.pack_forget() + self.log_gui.log("✓ 单步调试面板已隐藏") + + # 获取测试类型和测试项目 + test_type = self.test_type_var.get() + test_items = self.get_selected_test_items() + + if not test_items: + messagebox.showinfo("提示", "请至少选择一个测试项目") + return + + # 自动收起配置项 + if hasattr(self, "config_panel_frame"): + try: + if self.config_panel_frame.winfo_viewable(): + self.config_panel_frame.btn.invoke() + self.root.update_idletasks() + time.sleep(0.2) + except: + pass + + # 禁用配置项按钮 + try: + self.config_panel_frame.btn.configure(state="disabled") + except: + pass + + # ✅ 新增:禁用色域参考标准下拉框 + try: + if hasattr(self, "screen_gamut_combo"): + self.screen_gamut_combo.configure(state="disabled") + if hasattr(self, "sdr_gamut_combo"): + self.sdr_gamut_combo.configure(state="disabled") + if hasattr(self, "hdr_gamut_combo"): + self.hdr_gamut_combo.configure(state="disabled") + except Exception as e: + self.log_gui.log(f"禁用色域参考标准失败: {str(e)}") + + # 隐藏所有重新计算按钮 + if hasattr(self, "recalc_cct_btn"): + try: + self.recalc_cct_btn.grid_remove() + except: + pass + + if hasattr(self, "sdr_recalc_cct_btn"): + try: + self.sdr_recalc_cct_btn.grid_remove() + except: + pass + + if hasattr(self, "hdr_recalc_cct_btn"): + try: + self.hdr_recalc_cct_btn.grid_remove() + except: + pass + + # 更新UI状态 + self.testing = True + self.start_btn.config(state=tk.DISABLED) + self.stop_btn.config(state=tk.NORMAL) + self.save_btn.config(state=tk.DISABLED) + self.clear_config_btn.config(state=tk.DISABLED) + self.status_var.set("测试进行中...") + + # 清空日志和图表 + self.log_gui.clear_log() + self.clear_chart() + + # 根据测试类型显示不同提示 + if test_type == "screen_module": + # 屏模组测试:提示 byPass All PQ + message = f"开始屏模组性能测试,请 byPass All PQ" + + elif test_type == "sdr_movie": + # SDR测试:提示设置正确图像模式 + message = f"开始 SDR Movie 测试,请设置正确的图像模式" + + elif test_type == "hdr_movie": + # HDR测试:提示设置正确图像模式 + message = f"开始 HDR Movie 测试,请设置正确的图像模式" + + else: + message = f"开始{self.get_test_type_name(test_type)}测试" + + confirm = messagebox.askyesno("确认测试", message) + + if not confirm: + self.testing = False + self.start_btn.config(state=tk.NORMAL) + self.stop_btn.config(state=tk.DISABLED) + self.clear_config_btn.config(state=tk.NORMAL) + self.status_var.set("测试已取消") + + # 恢复配置项按钮 + if hasattr(self, "config_panel_frame"): + try: + self.config_panel_frame.btn.configure(state="normal") + except: + pass + return + + # 在新线程中执行测试 + self.test_thread = threading.Thread( + target=self.run_test, args=(test_type, test_items) + ) + self.test_thread.daemon = True + self.test_thread.start() + + def stop_test(self): + """停止测试 - 放弃本次所有数据(完全集成版)""" + if not self.testing: + return + + # ========== 1. 添加确认对话框 ========== + confirm = messagebox.askyesno( + "确认停止测试", + "测试正在进行中,确定要停止吗?\n\n⚠️ 停止后将放弃本次测试的所有数据,无法保存。", + icon="warning", + ) + + if not confirm: + self.log_gui.log("用户取消停止操作") + return + + # ========== 2. 立即设置停止标志 ========== + self.testing = False # ← 关键:先设置标志,让测试线程停止 + + self.log_gui.log("=" * 50) + self.log_gui.log("⚠️ 正在停止测试...") + self.log_gui.log("=" * 50) + + # ========== 3. 立即更新UI状态(让用户感知到停止)========== + self.stop_btn.config(state=tk.DISABLED) + self.status_var.set("正在停止测试,请稍候...") + self.root.update() # 立即刷新界面 + + # ========== 4. 等待测试线程结束 ========== + if self.test_thread and self.test_thread.is_alive(): + self.log_gui.log("等待测试线程结束...") + + # 等待最多5秒 + for i in range(50): # 50 * 0.1秒 = 5秒 + if not self.test_thread.is_alive(): + break + time.sleep(0.1) + self.root.update() # 保持界面响应 + + if self.test_thread.is_alive(): + self.log_gui.log("⚠️ 测试线程未能正常结束,将在后台继续等待") + else: + self.log_gui.log("✓ 测试线程已结束") + + # ========== 5. 延迟1秒后执行清理(使用内部函数)========== + def cleanup_and_finish(): + """清理数据并完成停止操作""" + # ========== 5.1 清理测试数据 ========== + try: + self.log_gui.log("清理测试数据...") + + # 清空测试结果对象 + if hasattr(self, "results"): + self.results = None + self.log_gui.log(" ✓ 测试结果对象已清空") + + # 清空中间数据缓存 + for attr in [ + "gamut_results", + "gamma_results", + "cct_results", + "contrast_results", + "accuracy_results", + ]: + if hasattr(self, attr): + setattr(self, attr, None) + + self.log_gui.log(" ✓ 所有中间数据已清空") + + except Exception as e: + self.log_gui.log(f"⚠️ 清理数据时出错: {str(e)}") + + # ========== 5.2 清空图表显示 ========== + try: + self.clear_chart() + self.log_gui.log("✓ 图表已清空") + except Exception as e: + self.log_gui.log(f"⚠️ 清空图表时出错: {str(e)}") + + try: + self.clear_custom_template_results() + self.log_gui.log("✓ 客户模板结果表格已清空") + except Exception as e: + self.log_gui.log(f"⚠️ 清空客户模板结果表格失败: {str(e)}") + + # ========== 5.2.5 跳转到色域图Tab(第一个Tab)========== + try: + if hasattr(self, "chart_notebook"): + self.chart_notebook.select(0) # ← 选中第一个Tab(色域图) + self.root.update_idletasks() # ← 刷新界面 + self.log_gui.log("✓ 已跳转到色域图界面") + except Exception as e: + self.log_gui.log(f"⚠️ 跳转到色域图失败: {str(e)}") + + # ========== 5.3 更新UI状态 ========== + self.set_custom_result_table_locked(False) + self.start_btn.config(state=tk.NORMAL) + self.stop_btn.config(state=tk.DISABLED) + self.save_btn.config(state=tk.DISABLED) + self.clear_config_btn.config(state=tk.NORMAL) + if hasattr(self, "custom_btn"): + self.custom_btn.config(state=tk.NORMAL) + self.status_var.set("测试已停止 - 数据已清空") + self.log_gui.log("✓ UI状态已更新") + + # ========== 5.4 恢复配置项按钮 ========== + if hasattr(self, "config_panel_frame"): + try: + self.config_panel_frame.btn.configure(state="normal") + self.log_gui.log("✓ 配置项已恢复") + except: + pass + + # ========== 5.4.5 禁用色域参考标准下拉框 ========== + try: + if hasattr(self, "screen_gamut_combo"): + self.screen_gamut_combo.configure(state="disabled") + if hasattr(self, "sdr_gamut_combo"): + self.sdr_gamut_combo.configure(state="disabled") + if hasattr(self, "hdr_gamut_combo"): + self.hdr_gamut_combo.configure(state="disabled") + self.log_gui.log("✓ 色域参考标准已禁用") + except Exception as e: + self.log_gui.log(f"禁用色域参考标准失败: {str(e)}") + + # ========== 5.5 隐藏所有重新计算按钮 ========== + try: + button_hidden_count = 0 + for btn_attr in [ + "recalc_cct_btn", + "sdr_recalc_cct_btn", + "hdr_recalc_cct_btn", + "recalc_gamut_btn", # ✅ 新增 + "sdr_recalc_gamut_btn", # ✅ 新增 + "hdr_recalc_gamut_btn", # ✅ 新增 + ]: + if hasattr(self, btn_attr): + try: + getattr(self, btn_attr).grid_remove() + button_hidden_count += 1 + except: + pass + + if button_hidden_count > 0: + self.log_gui.log(f"✓ 已隐藏 {button_hidden_count} 个重新计算按钮") + + except Exception as e: + self.log_gui.log(f"⚠️ 隐藏按钮时出错: {str(e)}") + + # ========== 5.6 禁用并隐藏单步调试 ========== + if hasattr(self, "debug_panel"): + try: + self.debug_panel.disable_all_debug() + self.log_gui.log("✓ 单步调试已禁用") + except Exception as e: + self.log_gui.log(f"⚠️ 禁用单步调试失败: {str(e)}") + + # ✅ 隐藏调试面板 + if hasattr(self, "debug_container"): + try: + self.debug_container.pack_forget() + self.log_gui.log("✓ 单步调试面板已隐藏") + except Exception as e: + self.log_gui.log(f"⚠️ 隐藏调试面板失败: {str(e)}") + + # ========== 5.7 最终日志 ========== + self.log_gui.log("=" * 50) + self.log_gui.log("✓ 测试已停止,所有数据已清空") + self.log_gui.log("=" * 50) + + # ========== 5.8 显示提示信息 ========== + messagebox.showinfo( + "测试已停止", + "测试已停止,本次测试数据已清空。\n\n可以重新开始新的测试。", + ) + + # ========== 延迟1秒后执行清理 ========== + self.root.after(1000, cleanup_and_finish) + + def save_results(self): + """保存测试结果(图片 + Excel)""" + save_dir = filedialog.askdirectory(title="选择保存测试结果的目录") + if not save_dir: + return + + try: + timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") + test_type = self.get_test_type_name(self.test_type_var.get()) + result_dir = os.path.join(save_dir, f"{test_type}_{timestamp}") + os.makedirs(result_dir, exist_ok=True) + + # ========== ✅ 获取当前测试类型和已选测试项 ========== + current_test_type = self.test_type_var.get() + selected_items = self.get_selected_test_items() + + self.log_gui.log(f"保存测试类型: {current_test_type}") + self.log_gui.log(f"已选测试项: {selected_items}") + + # ========== 保存图片 ========== + if "gamut" in selected_items and hasattr(self, "gamut_fig"): + gamut_path = os.path.join(result_dir, "色域测试结果.png") + self.gamut_fig.savefig(gamut_path, dpi=300) + self.log_gui.log(f"✓ 已保存: 色域测试结果.png") + + if current_test_type in ["screen_module", "sdr_movie"]: + if "gamma" in selected_items and hasattr(self, "gamma_fig"): + gamma_path = os.path.join(result_dir, "Gamma曲线测试结果.png") + self.gamma_fig.savefig(gamma_path, dpi=300) + self.log_gui.log(f"✓ 已保存: Gamma曲线测试结果.png") + + if current_test_type == "hdr_movie": + if "eotf" in selected_items and hasattr(self, "eotf_fig"): + eotf_path = os.path.join(result_dir, "EOTF曲线测试结果.png") + self.eotf_fig.savefig(eotf_path, dpi=300) + self.log_gui.log(f"✓ 已保存: EOTF曲线测试结果.png") + + if "cct" in selected_items and hasattr(self, "cct_fig"): + cct_path = os.path.join(result_dir, "色度一致性测试结果.png") + self.cct_fig.savefig(cct_path, dpi=300) + self.log_gui.log(f"✓ 已保存: 色度一致性测试结果.png") + + if "contrast" in selected_items and hasattr(self, "contrast_fig"): + contrast_path = os.path.join(result_dir, "对比度测试结果.png") + self.contrast_fig.savefig(contrast_path, dpi=300, bbox_inches="tight") + self.log_gui.log(f"✓ 已保存: 对比度测试结果.png") + + if current_test_type in ["sdr_movie", "hdr_movie"]: + if "accuracy" in selected_items and hasattr(self, "accuracy_fig"): + accuracy_path = os.path.join(result_dir, "色准测试结果.png") + self.accuracy_fig.savefig(accuracy_path, dpi=300) + self.log_gui.log(f"✓ 已保存: 色准测试结果.png") + + # ========== ✅ 屏模组测试 Excel 导出 ========== + if ( + current_test_type == "screen_module" + and hasattr(self, "results") + and self.results + ): + try: + import openpyxl + from openpyxl.styles import ( + Font, + Alignment, + PatternFill, + Border, + Side, + ) + + self.log_gui.log("=" * 60) + self.log_gui.log("开始生成屏模组 Excel 数据报告...") + + wb = openpyxl.Workbook() + ws = wb.active + ws.title = "测试数据" + + # ========== 样式定义 ========== + title_font = Font( + name="微软雅黑", size=16, bold=True, color="FFFFFF" + ) + title_fill = PatternFill( + start_color="4472C4", end_color="4472C4", fill_type="solid" + ) + title_alignment = Alignment(horizontal="center", vertical="center") + + section_font = Font( + name="微软雅黑", size=13, bold=True, color="FFFFFF" + ) + section_fill = PatternFill( + start_color="5B9BD5", end_color="5B9BD5", fill_type="solid" + ) + section_alignment = Alignment( + horizontal="center", vertical="center" + ) + + header_font = Font( + name="微软雅黑", size=10, bold=True, color="FFFFFF" + ) + header_fill = PatternFill( + start_color="70AD47", end_color="70AD47", fill_type="solid" + ) + header_alignment = Alignment( + horizontal="center", vertical="center", wrap_text=True + ) + + data_font = Font(name="微软雅黑", size=10) + data_alignment = Alignment(horizontal="center", vertical="center") + + label_font = Font(name="微软雅黑", size=10, bold=True) + + thin_border = Border( + left=Side(style="thin"), + right=Side(style="thin"), + top=Side(style="thin"), + bottom=Side(style="thin"), + ) + + # ========== 总标题 ========== + ws.merge_cells("A1:G1") + ws["A1"] = "屏模组性能测试数据报告" + ws["A1"].font = title_font + ws["A1"].fill = title_fill + ws["A1"].alignment = title_alignment + ws.row_dimensions[1].height = 35 + + # ========== 测试基本信息 ========== + row = 3 + ws.merge_cells(f"A{row}:B{row}") + ws[f"A{row}"] = "📋 测试基本信息" + ws[f"A{row}"].font = section_font + ws[f"A{row}"].fill = section_fill + ws[f"A{row}"].alignment = section_alignment + ws.row_dimensions[row].height = 25 + + row += 1 + info_items = [ + ( + "测试时间", + datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + ), + ("测试类型", "屏模组"), + ] + + for label, value in info_items: + ws[f"A{row}"] = label + ws[f"B{row}"] = value + ws[f"A{row}"].font = label_font + ws[f"B{row}"].font = data_font + ws[f"A{row}"].border = thin_border + ws[f"B{row}"].border = thin_border + row += 1 + + row += 1 # 空行 + + # ========== 1. 色域数据 ========== + if "gamut" in selected_items: + rgb_data = self.results.get_intermediate_data("gamut", "rgb") + gamut_final_result = None + if "gamut" in self.results.test_items: + gamut_final_result = self.results.test_items[ + "gamut" + ].final_result + + if rgb_data and len(rgb_data) >= 3: + # 分区标题 + ws.merge_cells(f"A{row}:G{row}") + ws[f"A{row}"] = "🎨 色域测试数据" + ws[f"A{row}"].font = section_font + ws[f"A{row}"].fill = section_fill + ws[f"A{row}"].alignment = section_alignment + ws.row_dimensions[row].height = 25 + row += 1 + + if gamut_final_result: + # 第一行:参考标准 + ws[f"A{row}"] = "参考标准" + ws[f"B{row}"] = gamut_final_result.get( + "reference", "DCI-P3" + ) + ws[f"A{row}"].font = label_font + ws[f"B{row}"].font = data_font + ws[f"A{row}"].border = thin_border + ws[f"B{row}"].border = thin_border + row += 1 + + # 第二行:XY 覆盖率 | UV 覆盖率 + xy_coverage = gamut_final_result.get("coverage", 0) + uv_coverage = ( + gamut_final_result.get("uv_coverage", 0) + or gamut_final_result.get("uv_space_coverage", 0) + or gamut_final_result.get("coverage_uv", 0) + or 0 + ) + + ws[f"A{row}"] = "XY 色域覆盖率" + ws[f"B{row}"] = f"{xy_coverage:.2f}%" + ws[f"C{row}"] = "UV 色域覆盖率" + ws[f"D{row}"] = f"{uv_coverage:.2f}%" + + ws[f"A{row}"].font = label_font + ws[f"B{row}"].font = data_font + ws[f"C{row}"].font = label_font + ws[f"D{row}"].font = data_font + + for col in ["A", "B", "C", "D"]: + ws[f"{col}{row}"].border = thin_border + + row += 1 + + # RGB 数据表格 + headers = [ + "点位", + "x 坐标", + "y 坐标", + "亮度 (cd/m²)", + "", + "", + "", + ] + for col_idx, header in enumerate(headers, start=1): + cell = ws.cell(row=row, column=col_idx) + cell.value = header + cell.font = header_font + cell.fill = header_fill + cell.alignment = header_alignment + cell.border = thin_border + row += 1 + + rgb_labels = ["Red", "Green", "Blue"] + for i, result in enumerate(rgb_data[:3]): + x, y, lv = result[0], result[1], result[2] + + ws[f"A{row}"] = rgb_labels[i] + ws[f"B{row}"] = x + ws[f"C{row}"] = y + ws[f"D{row}"] = lv + + ws[f"A{row}"].font = data_font + ws[f"A{row}"].alignment = data_alignment + ws[f"A{row}"].border = thin_border + + ws[f"B{row}"].number_format = "0.0000" + ws[f"B{row}"].font = data_font + ws[f"B{row}"].alignment = data_alignment + ws[f"B{row}"].border = thin_border + + ws[f"C{row}"].number_format = "0.0000" + ws[f"C{row}"].font = data_font + ws[f"C{row}"].alignment = data_alignment + ws[f"C{row}"].border = thin_border + + ws[f"D{row}"].number_format = "0.00" + ws[f"D{row}"].font = data_font + ws[f"D{row}"].alignment = data_alignment + ws[f"D{row}"].border = thin_border + + row += 1 + + row += 1 # 空行 + self.log_gui.log(" ✓ 添加色域数据") + + # ========== 2. Gamma 数据 ========== + if "gamma" in selected_items: + gray_data = self.results.get_intermediate_data("shared", "gray") + if not gray_data: + gray_data = self.results.get_intermediate_data( + "gamma", "gray" + ) + + gamma_final_result = None + if "gamma" in self.results.test_items: + gamma_final_result = self.results.test_items[ + "gamma" + ].final_result + + if gray_data and len(gray_data) > 0 and gamma_final_result: + gamma_list = gamma_final_result.get("gamma", []) + L_bar_list = gamma_final_result.get("L_bar", []) + + # 分区标题 + ws.merge_cells(f"A{row}:G{row}") + ws[f"A{row}"] = "📊 Gamma 曲线数据" + ws[f"A{row}"].font = section_font + ws[f"A{row}"].fill = section_fill + ws[f"A{row}"].alignment = section_alignment + ws.row_dimensions[row].height = 25 + row += 1 + + # Gamma 统计信息 + valid_gamma = [] + if gamma_list: + for item in gamma_list: + if ( + isinstance(item, (list, tuple)) + and len(item) >= 4 + ): + gamma_val = item[3] + if 0.5 < gamma_val < 5.0: + valid_gamma.append(gamma_val) + + if valid_gamma: + avg_gamma = sum(valid_gamma) / len(valid_gamma) + max_gamma = max(valid_gamma) + min_gamma = min(valid_gamma) + + ws[f"A{row}"] = "平均 Gamma" + ws[f"B{row}"] = f"{avg_gamma:.3f}" + ws[f"C{row}"] = "最大 Gamma" + ws[f"D{row}"] = f"{max_gamma:.3f}" + ws[f"E{row}"] = "最小 Gamma" + ws[f"F{row}"] = f"{min_gamma:.3f}" + + for col in ["A", "B", "C", "D", "E", "F"]: + ws[f"{col}{row}"].font = ( + label_font + if col in ["A", "C", "E"] + else data_font + ) + ws[f"{col}{row}"].border = thin_border + row += 1 + + # Gamma 数据表格 + headers = [ + "灰阶 (%)", + "x 坐标", + "y 坐标", + "实测亮度\n(cd/m²)", + "归一化亮度\n(L_bar)", + "Gamma 值", + "", + ] + for col_idx, header in enumerate(headers, start=1): + cell = ws.cell(row=row, column=col_idx) + cell.value = header + cell.font = header_font + cell.fill = header_fill + cell.alignment = header_alignment + cell.border = thin_border + row += 1 + + total_points = len(gray_data) + for i in range(total_points - 1, -1, -1): + gray_level = ( + 100 - int(i * 100 / (total_points - 1)) + if total_points > 1 + else 0 + ) + + x, y, lv = ( + gray_data[i][0], + gray_data[i][1], + gray_data[i][2], + ) + + L_bar_val = L_bar_list[i] if i < len(L_bar_list) else 0 + + gamma_val = None + if ( + i < len(gamma_list) + and isinstance(gamma_list[i], (list, tuple)) + and len(gamma_list[i]) >= 4 + ): + gamma_val = gamma_list[i][3] + + ws[f"A{row}"] = gray_level + ws[f"B{row}"] = x + ws[f"C{row}"] = y + ws[f"D{row}"] = lv + ws[f"E{row}"] = L_bar_val + + if gamma_val is not None and 0.5 < gamma_val < 5.0: + ws[f"F{row}"] = gamma_val + ws[f"F{row}"].number_format = "0.000" + else: + ws[f"F{row}"] = "N/A" + + ws[f"A{row}"].number_format = "0" + ws[f"B{row}"].number_format = "0.0000" + ws[f"C{row}"].number_format = "0.0000" + ws[f"D{row}"].number_format = "0.00" + ws[f"E{row}"].number_format = "0.0000" + + for col in ["A", "B", "C", "D", "E", "F"]: + ws[f"{col}{row}"].font = data_font + ws[f"{col}{row}"].alignment = data_alignment + ws[f"{col}{row}"].border = thin_border + + row += 1 + + row += 1 + self.log_gui.log(" ✓ 添加 Gamma 数据") + + # ========== 3. 色度一致性数据 ========== + if "cct" in selected_items: + gray_data = self.results.get_intermediate_data("shared", "gray") + if not gray_data: + gray_data = self.results.get_intermediate_data( + "cct", "gray" + ) + + if gray_data and len(gray_data) > 1: + gray_data_no_black = gray_data[:-1] + + # 分区标题 + ws.merge_cells(f"A{row}:G{row}") + ws[f"A{row}"] = "🌈 色度一致性数据" + ws[f"A{row}"].font = section_font + ws[f"A{row}"].fill = section_fill + ws[f"A{row}"].alignment = section_alignment + ws.row_dimensions[row].height = 25 + row += 1 + + # 色度波动信息 + x_coords = [d[0] for d in gray_data_no_black] + y_coords = [d[1] for d in gray_data_no_black] + + ws[f"A{row}"] = "x 坐标范围" + ws[f"B{row}"] = f"{min(x_coords):.4f} ~ {max(x_coords):.4f}" + ws[f"C{row}"] = "y 坐标范围" + ws[f"D{row}"] = f"{min(y_coords):.4f} ~ {max(y_coords):.4f}" + + for col in ["A", "B", "C", "D"]: + ws[f"{col}{row}"].font = ( + label_font if col in ["A", "C"] else data_font + ) + ws[f"{col}{row}"].border = thin_border + row += 1 + + # 数据表格 + headers = [ + "灰阶 (%)", + "x 坐标", + "y 坐标", + "亮度 (cd/m²)", + "", + "", + "", + ] + for col_idx, header in enumerate(headers, start=1): + cell = ws.cell(row=row, column=col_idx) + cell.value = header + cell.font = header_font + cell.fill = header_fill + cell.alignment = header_alignment + cell.border = thin_border + row += 1 + + total_points = len(gray_data) + for i in range(len(gray_data_no_black) - 1, -1, -1): + x, y, lv = ( + gray_data_no_black[i][0], + gray_data_no_black[i][1], + gray_data_no_black[i][2], + ) + gray_level = ( + 100 - int(i * 100 / (total_points - 1)) + if total_points > 1 + else 0 + ) + + ws[f"A{row}"] = gray_level + ws[f"B{row}"] = x + ws[f"C{row}"] = y + ws[f"D{row}"] = lv + + ws[f"A{row}"].number_format = "0" + ws[f"B{row}"].number_format = "0.0000" + ws[f"C{row}"].number_format = "0.0000" + ws[f"D{row}"].number_format = "0.00" + + for col in ["A", "B", "C", "D"]: + ws[f"{col}{row}"].font = data_font + ws[f"{col}{row}"].alignment = data_alignment + ws[f"{col}{row}"].border = thin_border + + row += 1 + + row += 1 + self.log_gui.log(" ✓ 添加色度一致性数据") + + # ========== 4. 对比度数据 ========== + if "contrast" in selected_items: + contrast_final_result = None + if "contrast" in self.results.test_items: + contrast_final_result = self.results.test_items[ + "contrast" + ].final_result + + if contrast_final_result: + # 分区标题 + ws.merge_cells(f"A{row}:G{row}") + ws[f"A{row}"] = "⚫⚪ 对比度测试数据" + ws[f"A{row}"].font = section_font + ws[f"A{row}"].fill = section_fill + ws[f"A{row}"].alignment = section_alignment + ws.row_dimensions[row].height = 25 + row += 1 + + max_lv = contrast_final_result.get("max_luminance", 0) + min_lv = contrast_final_result.get("min_luminance", 0) + contrast_ratio = contrast_final_result.get( + "contrast_ratio", 0 + ) + + info_items = [ + ("最大亮度(白场)", f"{max_lv:.2f} cd/m²"), + ("最小亮度(黑场)", f"{min_lv:.4f} cd/m²"), + ("对比度", f"{contrast_ratio:.0f}:1"), + ] + + for label, value in info_items: + ws[f"A{row}"] = label + ws[f"B{row}"] = value + ws[f"A{row}"].font = label_font + ws[f"B{row}"].font = data_font + ws[f"A{row}"].border = thin_border + ws[f"B{row}"].border = thin_border + row += 1 + + self.log_gui.log(" ✓ 添加对比度数据") + + # ========== 调整列宽 ========== + ws.column_dimensions["A"].width = 18 + ws.column_dimensions["B"].width = 18 + ws.column_dimensions["C"].width = 18 + ws.column_dimensions["D"].width = 18 + ws.column_dimensions["E"].width = 18 + ws.column_dimensions["F"].width = 15 + ws.column_dimensions["G"].width = 15 + + # ========== 保存 Excel ========== + excel_path = os.path.join(result_dir, "测试数据.xlsx") + wb.save(excel_path) + + self.log_gui.log(f"✓ 已保存: 测试数据.xlsx") + self.log_gui.log("=" * 60) + + except ImportError: + self.log_gui.log("⚠️ 未安装 openpyxl 库,跳过 Excel 导出") + self.log_gui.log(" 安装方法: pip install openpyxl") + except Exception as e: + self.log_gui.log(f"⚠️ Excel 导出失败: {str(e)}") + import traceback + + self.log_gui.log(traceback.format_exc()) + + # ========== ✅ SDR Movie 测试 Excel 导出 ========== + elif ( + current_test_type == "sdr_movie" + and hasattr(self, "results") + and self.results + ): + try: + import openpyxl + from openpyxl.styles import ( + Font, + Alignment, + PatternFill, + Border, + Side, + ) + + self.log_gui.log("=" * 60) + self.log_gui.log("开始生成 SDR Movie Excel 数据报告...") + + wb = openpyxl.Workbook() + ws = wb.active + ws.title = "测试数据" + + # ========== 样式定义 ========== + title_font = Font( + name="微软雅黑", size=16, bold=True, color="FFFFFF" + ) + title_fill = PatternFill( + start_color="4472C4", end_color="4472C4", fill_type="solid" + ) + title_alignment = Alignment(horizontal="center", vertical="center") + + section_font = Font( + name="微软雅黑", size=13, bold=True, color="FFFFFF" + ) + section_fill = PatternFill( + start_color="5B9BD5", end_color="5B9BD5", fill_type="solid" + ) + section_alignment = Alignment( + horizontal="center", vertical="center" + ) + + header_font = Font( + name="微软雅黑", size=10, bold=True, color="FFFFFF" + ) + header_fill = PatternFill( + start_color="70AD47", end_color="70AD47", fill_type="solid" + ) + header_alignment = Alignment( + horizontal="center", vertical="center", wrap_text=True + ) + + data_font = Font(name="微软雅黑", size=10) + data_alignment = Alignment(horizontal="center", vertical="center") + label_font = Font(name="微软雅黑", size=10, bold=True) + + thin_border = Border( + left=Side(style="thin"), + right=Side(style="thin"), + top=Side(style="thin"), + bottom=Side(style="thin"), + ) + + # ========== 总标题 ========== + ws.merge_cells("A1:G1") + ws["A1"] = "SDR Movie 性能测试数据报告" + ws["A1"].font = title_font + ws["A1"].fill = title_fill + ws["A1"].alignment = title_alignment + ws.row_dimensions[1].height = 35 + + # ========== 测试基本信息 ========== + row = 3 + ws.merge_cells(f"A{row}:B{row}") + ws[f"A{row}"] = "📋 测试基本信息" + ws[f"A{row}"].font = section_font + ws[f"A{row}"].fill = section_fill + ws[f"A{row}"].alignment = section_alignment + ws.row_dimensions[row].height = 25 + + row += 1 + info_items = [ + ( + "测试时间", + datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + ), + ("测试类型", "SDR Movie"), + ] + + for label, value in info_items: + ws[f"A{row}"] = label + ws[f"B{row}"] = value + ws[f"A{row}"].font = label_font + ws[f"B{row}"].font = data_font + ws[f"A{row}"].border = thin_border + ws[f"B{row}"].border = thin_border + row += 1 + + row += 1 # 空行 + + # ========== 1. 色域数据 ========== + if "gamut" in selected_items: + rgb_data = self.results.get_intermediate_data("gamut", "rgb") + gamut_final_result = None + if "gamut" in self.results.test_items: + gamut_final_result = self.results.test_items[ + "gamut" + ].final_result + + if rgb_data and len(rgb_data) >= 3: + ws.merge_cells(f"A{row}:G{row}") + ws[f"A{row}"] = "🎨 色域测试数据" + ws[f"A{row}"].font = section_font + ws[f"A{row}"].fill = section_fill + ws[f"A{row}"].alignment = section_alignment + ws.row_dimensions[row].height = 25 + row += 1 + + if gamut_final_result: + xy_coverage = gamut_final_result.get("coverage", 0) + uv_coverage = ( + gamut_final_result.get("uv_coverage", 0) + or gamut_final_result.get("uv_space_coverage", 0) + or 0 + ) + + ws[f"A{row}"] = "参考标准" + ws[f"B{row}"] = gamut_final_result.get( + "reference", "DCI-P3" + ) + ws[f"A{row}"].font = label_font + ws[f"B{row}"].font = data_font + ws[f"A{row}"].border = thin_border + ws[f"B{row}"].border = thin_border + row += 1 + + ws[f"A{row}"] = "XY 色域覆盖率" + ws[f"B{row}"] = f"{xy_coverage:.2f}%" + ws[f"C{row}"] = "UV 色域覆盖率" + ws[f"D{row}"] = f"{uv_coverage:.2f}%" + + for col in ["A", "B", "C", "D"]: + ws[f"{col}{row}"].font = ( + label_font if col in ["A", "C"] else data_font + ) + ws[f"{col}{row}"].border = thin_border + row += 1 + + # RGB 数据表格 + headers = [ + "点位", + "x 坐标", + "y 坐标", + "亮度 (cd/m²)", + "", + "", + "", + ] + for col_idx, header in enumerate(headers, start=1): + cell = ws.cell(row=row, column=col_idx) + cell.value = header + cell.font = header_font + cell.fill = header_fill + cell.alignment = header_alignment + cell.border = thin_border + row += 1 + + rgb_labels = ["Red", "Green", "Blue"] + for i, result in enumerate(rgb_data[:3]): + x, y, lv = result[0], result[1], result[2] + ws[f"A{row}"] = rgb_labels[i] + ws[f"B{row}"] = x + ws[f"C{row}"] = y + ws[f"D{row}"] = lv + + ws[f"B{row}"].number_format = "0.0000" + ws[f"C{row}"].number_format = "0.0000" + ws[f"D{row}"].number_format = "0.00" + + for col in ["A", "B", "C", "D"]: + ws[f"{col}{row}"].font = data_font + ws[f"{col}{row}"].alignment = data_alignment + ws[f"{col}{row}"].border = thin_border + row += 1 + + row += 1 + self.log_gui.log(" ✓ 添加色域数据") + + # ========== 2. Gamma 数据 ========== + if "gamma" in selected_items: + gray_data = self.results.get_intermediate_data("shared", "gray") + if not gray_data: + gray_data = self.results.get_intermediate_data( + "gamma", "gray" + ) + + gamma_final_result = None + if "gamma" in self.results.test_items: + gamma_final_result = self.results.test_items[ + "gamma" + ].final_result + + if gray_data and gamma_final_result: + gamma_list = gamma_final_result.get("gamma", []) + L_bar_list = gamma_final_result.get("L_bar", []) + + ws.merge_cells(f"A{row}:G{row}") + ws[f"A{row}"] = "📊 Gamma 曲线数据" + ws[f"A{row}"].font = section_font + ws[f"A{row}"].fill = section_fill + ws[f"A{row}"].alignment = section_alignment + ws.row_dimensions[row].height = 25 + row += 1 + + # Gamma 统计 + valid_gamma = [ + item[3] + for item in gamma_list + if isinstance(item, (list, tuple)) + and len(item) >= 4 + and 0.5 < item[3] < 5.0 + ] + if valid_gamma: + avg_gamma = sum(valid_gamma) / len(valid_gamma) + ws[f"A{row}"] = "平均 Gamma" + ws[f"B{row}"] = f"{avg_gamma:.3f}" + ws[f"C{row}"] = "最大 Gamma" + ws[f"D{row}"] = f"{max(valid_gamma):.3f}" + ws[f"E{row}"] = "最小 Gamma" + ws[f"F{row}"] = f"{min(valid_gamma):.3f}" + + for col in ["A", "B", "C", "D", "E", "F"]: + ws[f"{col}{row}"].font = ( + label_font + if col in ["A", "C", "E"] + else data_font + ) + ws[f"{col}{row}"].border = thin_border + row += 1 + + # Gamma 数据表格 + headers = [ + "灰阶 (%)", + "x 坐标", + "y 坐标", + "实测亮度\n(cd/m²)", + "归一化亮度\n(L_bar)", + "Gamma 值", + "", + ] + for col_idx, header in enumerate(headers, start=1): + cell = ws.cell(row=row, column=col_idx) + cell.value = header + cell.font = header_font + cell.fill = header_fill + cell.alignment = header_alignment + cell.border = thin_border + row += 1 + + total_points = len(gray_data) + for i in range(total_points - 1, -1, -1): + gray_level = ( + 100 - int(i * 100 / (total_points - 1)) + if total_points > 1 + else 0 + ) + x, y, lv = ( + gray_data[i][0], + gray_data[i][1], + gray_data[i][2], + ) + L_bar_val = L_bar_list[i] if i < len(L_bar_list) else 0 + + gamma_val = None + if ( + i < len(gamma_list) + and isinstance(gamma_list[i], (list, tuple)) + and len(gamma_list[i]) >= 4 + ): + gamma_val = gamma_list[i][3] + + ws[f"A{row}"] = gray_level + ws[f"B{row}"] = x + ws[f"C{row}"] = y + ws[f"D{row}"] = lv + ws[f"E{row}"] = L_bar_val + + if gamma_val is not None and 0.5 < gamma_val < 5.0: + ws[f"F{row}"] = gamma_val + ws[f"F{row}"].number_format = "0.000" + else: + ws[f"F{row}"] = "N/A" + + ws[f"A{row}"].number_format = "0" + ws[f"B{row}"].number_format = "0.0000" + ws[f"C{row}"].number_format = "0.0000" + ws[f"D{row}"].number_format = "0.00" + ws[f"E{row}"].number_format = "0.0000" + + for col in ["A", "B", "C", "D", "E", "F"]: + ws[f"{col}{row}"].font = data_font + ws[f"{col}{row}"].alignment = data_alignment + ws[f"{col}{row}"].border = thin_border + row += 1 + + row += 1 + self.log_gui.log(" ✓ 添加 Gamma 数据") + + # ========== 3. 色度一致性数据 ========== + if "cct" in selected_items: + gray_data = self.results.get_intermediate_data("shared", "gray") + if not gray_data: + gray_data = self.results.get_intermediate_data( + "cct", "gray" + ) + + if gray_data and len(gray_data) > 1: + gray_data_no_black = gray_data[:-1] + + ws.merge_cells(f"A{row}:G{row}") + ws[f"A{row}"] = "🌈 色度一致性数据" + ws[f"A{row}"].font = section_font + ws[f"A{row}"].fill = section_fill + ws[f"A{row}"].alignment = section_alignment + ws.row_dimensions[row].height = 25 + row += 1 + + x_coords = [d[0] for d in gray_data_no_black] + y_coords = [d[1] for d in gray_data_no_black] + + ws[f"A{row}"] = "x 坐标范围" + ws[f"B{row}"] = f"{min(x_coords):.4f} ~ {max(x_coords):.4f}" + ws[f"C{row}"] = "y 坐标范围" + ws[f"D{row}"] = f"{min(y_coords):.4f} ~ {max(y_coords):.4f}" + + for col in ["A", "B", "C", "D"]: + ws[f"{col}{row}"].font = ( + label_font if col in ["A", "C"] else data_font + ) + ws[f"{col}{row}"].border = thin_border + row += 1 + + headers = [ + "灰阶 (%)", + "x 坐标", + "y 坐标", + "亮度 (cd/m²)", + "", + "", + "", + ] + for col_idx, header in enumerate(headers, start=1): + cell = ws.cell(row=row, column=col_idx) + cell.value = header + cell.font = header_font + cell.fill = header_fill + cell.alignment = header_alignment + cell.border = thin_border + row += 1 + + total_points = len(gray_data) + for i in range(len(gray_data_no_black) - 1, -1, -1): + x, y, lv = ( + gray_data_no_black[i][0], + gray_data_no_black[i][1], + gray_data_no_black[i][2], + ) + gray_level = ( + 100 - int(i * 100 / (total_points - 1)) + if total_points > 1 + else 0 + ) + + ws[f"A{row}"] = gray_level + ws[f"B{row}"] = x + ws[f"C{row}"] = y + ws[f"D{row}"] = lv + + ws[f"A{row}"].number_format = "0" + ws[f"B{row}"].number_format = "0.0000" + ws[f"C{row}"].number_format = "0.0000" + ws[f"D{row}"].number_format = "0.00" + + for col in ["A", "B", "C", "D"]: + ws[f"{col}{row}"].font = data_font + ws[f"{col}{row}"].alignment = data_alignment + ws[f"{col}{row}"].border = thin_border + row += 1 + + row += 1 + self.log_gui.log(" ✓ 添加色度一致性数据") + + # ========== 4. 对比度数据 ========== + if "contrast" in selected_items: + contrast_final_result = None + if "contrast" in self.results.test_items: + contrast_final_result = self.results.test_items[ + "contrast" + ].final_result + + if contrast_final_result: + ws.merge_cells(f"A{row}:G{row}") + ws[f"A{row}"] = "⚫⚪ 对比度测试数据" + ws[f"A{row}"].font = section_font + ws[f"A{row}"].fill = section_fill + ws[f"A{row}"].alignment = section_alignment + ws.row_dimensions[row].height = 25 + row += 1 + + max_lv = contrast_final_result.get("max_luminance", 0) + min_lv = contrast_final_result.get("min_luminance", 0) + contrast_ratio = contrast_final_result.get( + "contrast_ratio", 0 + ) + + info_items = [ + ("最大亮度(白场)", f"{max_lv:.2f} cd/m²"), + ("最小亮度(黑场)", f"{min_lv:.4f} cd/m²"), + ("对比度", f"{contrast_ratio:.0f}:1"), + ] + + for label, value in info_items: + ws[f"A{row}"] = label + ws[f"B{row}"] = value + ws[f"A{row}"].font = label_font + ws[f"B{row}"].font = data_font + ws[f"A{row}"].border = thin_border + ws[f"B{row}"].border = thin_border + row += 1 + + row += 1 + self.log_gui.log(" ✓ 添加对比度数据") + + # ========== 5. 色准数据(SDR 特有)========== + if "accuracy" in selected_items: + accuracy_final_result = None + if "accuracy" in self.results.test_items: + accuracy_final_result = self.results.test_items[ + "accuracy" + ].final_result + + if accuracy_final_result: + ws.merge_cells(f"A{row}:G{row}") + ws[f"A{row}"] = "🎯 色准测试数据" + ws[f"A{row}"].font = section_font + ws[f"A{row}"].fill = section_fill + ws[f"A{row}"].alignment = section_alignment + ws.row_dimensions[row].height = 25 + row += 1 + + # 色准统计信息 + avg_delta_e = accuracy_final_result.get("avg_delta_e", 0) + max_delta_e = accuracy_final_result.get("max_delta_e", 0) + min_delta_e = accuracy_final_result.get("min_delta_e", 0) + excellent_count = accuracy_final_result.get( + "excellent_count", 0 + ) + good_count = accuracy_final_result.get("good_count", 0) + poor_count = accuracy_final_result.get("poor_count", 0) + + ws[f"A{row}"] = "平均 ΔE" + ws[f"B{row}"] = f"{avg_delta_e:.2f}" + ws[f"C{row}"] = "最大 ΔE" + ws[f"D{row}"] = f"{max_delta_e:.2f}" + ws[f"E{row}"] = "最小 ΔE" + ws[f"F{row}"] = f"{min_delta_e:.2f}" + + for col in ["A", "B", "C", "D", "E", "F"]: + ws[f"{col}{row}"].font = ( + label_font if col in ["A", "C", "E"] else data_font + ) + ws[f"{col}{row}"].border = thin_border + row += 1 + + # 第二行统计 + ws[f"A{row}"] = "优秀 (ΔE<3)" + ws[f"B{row}"] = f"{excellent_count} 个" + ws[f"C{row}"] = "良好 (3≤ΔE<5)" + ws[f"D{row}"] = f"{good_count} 个" + ws[f"E{row}"] = "偏差 (ΔE≥5)" + ws[f"F{row}"] = f"{poor_count} 个" + + for col in ["A", "B", "C", "D", "E", "F"]: + ws[f"{col}{row}"].font = ( + label_font if col in ["A", "C", "E"] else data_font + ) + ws[f"{col}{row}"].border = thin_border + row += 1 + + # ========== 色准详细数据表格(带 xy 坐标和亮度)========== + color_patches = accuracy_final_result.get( + "color_patches", [] + ) + delta_e_values = accuracy_final_result.get( + "delta_e_values", [] + ) + + # ✅ 获取原始测量数据(包含 xy 和亮度) + color_measurements = accuracy_final_result.get( + "color_measurements", [] + ) + + if color_patches and delta_e_values: + # 表头 + headers = [ + "序号", + "颜色名称", + "x 坐标", + "y 坐标", + "亮度 (cd/m²)", + "ΔE 2000", + "等级", + ] + for col_idx, header in enumerate(headers, start=1): + cell = ws.cell(row=row, column=col_idx) + cell.value = header + cell.font = header_font + cell.fill = header_fill + cell.alignment = header_alignment + cell.border = thin_border + row += 1 + + # 数据行 + for idx, (color_name, delta_e) in enumerate( + zip(color_patches, delta_e_values), start=1 + ): + # 判断等级 + if delta_e < 3: + grade = "优秀" + elif delta_e < 5: + grade = "良好" + else: + grade = "偏差" + + # ✅ 获取测量数据(x, y, 亮度) + x_val = "N/A" + y_val = "N/A" + lv_val = "N/A" + + if color_measurements and idx - 1 < len( + color_measurements + ): + measurement = color_measurements[idx - 1] + if len(measurement) >= 3: + x_val = measurement[0] + y_val = measurement[1] + lv_val = measurement[2] + + ws[f"A{row}"] = idx + ws[f"B{row}"] = color_name + ws[f"C{row}"] = x_val + ws[f"D{row}"] = y_val + ws[f"E{row}"] = lv_val + ws[f"F{row}"] = delta_e + ws[f"G{row}"] = grade + + # 数字格式 + ws[f"A{row}"].number_format = "0" + if isinstance(x_val, (int, float)): + ws[f"C{row}"].number_format = "0.0000" + if isinstance(y_val, (int, float)): + ws[f"D{row}"].number_format = "0.0000" + if isinstance(lv_val, (int, float)): + ws[f"E{row}"].number_format = "0.00" + ws[f"F{row}"].number_format = "0.00" + + for col in ["A", "B", "C", "D", "E", "F", "G"]: + ws[f"{col}{row}"].font = data_font + ws[f"{col}{row}"].alignment = data_alignment + ws[f"{col}{row}"].border = thin_border + + row += 1 + + row += 1 + self.log_gui.log(" ✓ 添加色准数据(含 xy 坐标和亮度)") + + # ========== 调整列宽 ========== + for col in ["A", "B", "C", "D", "E", "F", "G"]: + ws.column_dimensions[col].width = 18 + + # ========== 保存 Excel ========== + excel_path = os.path.join(result_dir, "测试数据.xlsx") + wb.save(excel_path) + + self.log_gui.log(f"✓ 已保存: 测试数据.xlsx") + self.log_gui.log("=" * 60) + + except ImportError: + self.log_gui.log("⚠️ 未安装 openpyxl 库,跳过 Excel 导出") + except Exception as e: + self.log_gui.log(f"⚠️ Excel 导出失败: {str(e)}") + import traceback + + self.log_gui.log(traceback.format_exc()) + + # ========== ✅ HDR Movie 测试 Excel 导出 ========== + elif ( + current_test_type == "hdr_movie" + and hasattr(self, "results") + and self.results + ): + try: + import openpyxl + from openpyxl.styles import ( + Font, + Alignment, + PatternFill, + Border, + Side, + ) + + self.log_gui.log("=" * 60) + self.log_gui.log("开始生成 HDR Movie Excel 数据报告...") + + wb = openpyxl.Workbook() + ws = wb.active + ws.title = "测试数据" + + # ========== 样式定义 ========== + title_font = Font( + name="微软雅黑", size=16, bold=True, color="FFFFFF" + ) + title_fill = PatternFill( + start_color="4472C4", end_color="4472C4", fill_type="solid" + ) + title_alignment = Alignment(horizontal="center", vertical="center") + + section_font = Font( + name="微软雅黑", size=13, bold=True, color="FFFFFF" + ) + section_fill = PatternFill( + start_color="5B9BD5", end_color="5B9BD5", fill_type="solid" + ) + section_alignment = Alignment( + horizontal="center", vertical="center" + ) + + header_font = Font( + name="微软雅黑", size=10, bold=True, color="FFFFFF" + ) + header_fill = PatternFill( + start_color="70AD47", end_color="70AD47", fill_type="solid" + ) + header_alignment = Alignment( + horizontal="center", vertical="center", wrap_text=True + ) + + data_font = Font(name="微软雅黑", size=10) + data_alignment = Alignment(horizontal="center", vertical="center") + label_font = Font(name="微软雅黑", size=10, bold=True) + + thin_border = Border( + left=Side(style="thin"), + right=Side(style="thin"), + top=Side(style="thin"), + bottom=Side(style="thin"), + ) + + # ========== 总标题 ========== + ws.merge_cells("A1:G1") + ws["A1"] = "HDR Movie 性能测试数据报告" + ws["A1"].font = title_font + ws["A1"].fill = title_fill + ws["A1"].alignment = title_alignment + ws.row_dimensions[1].height = 35 + + # ========== 测试基本信息 ========== + row = 3 + ws.merge_cells(f"A{row}:B{row}") + ws[f"A{row}"] = "📋 测试基本信息" + ws[f"A{row}"].font = section_font + ws[f"A{row}"].fill = section_fill + ws[f"A{row}"].alignment = section_alignment + ws.row_dimensions[row].height = 25 + + row += 1 + info_items = [ + ( + "测试时间", + datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + ), + ("测试类型", "HDR Movie"), + ] + + for label, value in info_items: + ws[f"A{row}"] = label + ws[f"B{row}"] = value + ws[f"A{row}"].font = label_font + ws[f"B{row}"].font = data_font + ws[f"A{row}"].border = thin_border + ws[f"B{row}"].border = thin_border + row += 1 + + row += 1 + + # ========== 1. 色域数据 ========== + if "gamut" in selected_items: + rgb_data = self.results.get_intermediate_data("gamut", "rgb") + gamut_final_result = None + if "gamut" in self.results.test_items: + gamut_final_result = self.results.test_items[ + "gamut" + ].final_result + + if rgb_data and len(rgb_data) >= 3: + ws.merge_cells(f"A{row}:G{row}") + ws[f"A{row}"] = "🎨 色域测试数据" + ws[f"A{row}"].font = section_font + ws[f"A{row}"].fill = section_fill + ws[f"A{row}"].alignment = section_alignment + ws.row_dimensions[row].height = 25 + row += 1 + + if gamut_final_result: + xy_coverage = gamut_final_result.get("coverage", 0) + uv_coverage = ( + gamut_final_result.get("uv_coverage", 0) + or gamut_final_result.get("uv_space_coverage", 0) + or 0 + ) + + ws[f"A{row}"] = "参考标准" + ws[f"B{row}"] = gamut_final_result.get( + "reference", "DCI-P3" + ) + ws[f"A{row}"].font = label_font + ws[f"B{row}"].font = data_font + ws[f"A{row}"].border = thin_border + ws[f"B{row}"].border = thin_border + row += 1 + + ws[f"A{row}"] = "XY 色域覆盖率" + ws[f"B{row}"] = f"{xy_coverage:.2f}%" + ws[f"C{row}"] = "UV 色域覆盖率" + ws[f"D{row}"] = f"{uv_coverage:.2f}%" + + for col in ["A", "B", "C", "D"]: + ws[f"{col}{row}"].font = ( + label_font if col in ["A", "C"] else data_font + ) + ws[f"{col}{row}"].border = thin_border + row += 1 + + # RGB 数据表格 + headers = [ + "点位", + "x 坐标", + "y 坐标", + "亮度 (cd/m²)", + "", + "", + "", + ] + for col_idx, header in enumerate(headers, start=1): + cell = ws.cell(row=row, column=col_idx) + cell.value = header + cell.font = header_font + cell.fill = header_fill + cell.alignment = header_alignment + cell.border = thin_border + row += 1 + + rgb_labels = ["Red", "Green", "Blue"] + for i, result in enumerate(rgb_data[:3]): + x, y, lv = result[0], result[1], result[2] + ws[f"A{row}"] = rgb_labels[i] + ws[f"B{row}"] = x + ws[f"C{row}"] = y + ws[f"D{row}"] = lv + + ws[f"B{row}"].number_format = "0.0000" + ws[f"C{row}"].number_format = "0.0000" + ws[f"D{row}"].number_format = "0.00" + + for col in ["A", "B", "C", "D"]: + ws[f"{col}{row}"].font = data_font + ws[f"{col}{row}"].alignment = data_alignment + ws[f"{col}{row}"].border = thin_border + row += 1 + + row += 1 + self.log_gui.log(" ✓ 添加色域数据") + + # ========== 2. EOTF 数据(HDR 特有)========== + if "eotf" in selected_items: + gray_data = self.results.get_intermediate_data("shared", "gray") + if not gray_data: + gray_data = self.results.get_intermediate_data( + "eotf", "gray" + ) + + eotf_final_result = None + if "eotf" in self.results.test_items: + eotf_final_result = self.results.test_items[ + "eotf" + ].final_result + + if gray_data and len(gray_data) > 0 and eotf_final_result: + eotf_list = eotf_final_result.get("eotf", []) + L_bar_list = eotf_final_result.get("L_bar", []) + + ws.merge_cells(f"A{row}:G{row}") + ws[f"A{row}"] = "📊 EOTF 曲线数据" + ws[f"A{row}"].font = section_font + ws[f"A{row}"].fill = section_fill + ws[f"A{row}"].alignment = section_alignment + ws.row_dimensions[row].height = 25 + row += 1 + + # ✅ EOTF 统计信息(类似 Gamma 统计) + valid_eotf = [] + if eotf_list: + for item in eotf_list: + if ( + isinstance(item, (list, tuple)) + and len(item) >= 4 + ): + eotf_val = item[3] + if 0.5 < eotf_val < 5.0: + valid_eotf.append(eotf_val) + + if valid_eotf: + avg_eotf = sum(valid_eotf) / len(valid_eotf) + max_eotf = max(valid_eotf) + min_eotf = min(valid_eotf) + + ws[f"A{row}"] = "平均 EOTF" + ws[f"B{row}"] = f"{avg_eotf:.3f}" + ws[f"C{row}"] = "最大 EOTF" + ws[f"D{row}"] = f"{max_eotf:.3f}" + ws[f"E{row}"] = "最小 EOTF" + ws[f"F{row}"] = f"{min_eotf:.3f}" + + for col in ["A", "B", "C", "D", "E", "F"]: + ws[f"{col}{row}"].font = ( + label_font + if col in ["A", "C", "E"] + else data_font + ) + ws[f"{col}{row}"].border = thin_border + row += 1 + + # ✅ EOTF 数据表格(与 Gamma 表格完全一致) + headers = [ + "灰阶 (%)", + "x 坐标", + "y 坐标", + "实测亮度\n(cd/m²)", + "归一化亮度\n(L_bar)", + "EOTF 值", + "", + ] + for col_idx, header in enumerate(headers, start=1): + cell = ws.cell(row=row, column=col_idx) + cell.value = header + cell.font = header_font + cell.fill = header_fill + cell.alignment = header_alignment + cell.border = thin_border + row += 1 + + total_points = len(gray_data) + for i in range(total_points - 1, -1, -1): + gray_level = ( + 100 - int(i * 100 / (total_points - 1)) + if total_points > 1 + else 0 + ) + + x, y, lv = ( + gray_data[i][0], + gray_data[i][1], + gray_data[i][2], + ) + + L_bar_val = L_bar_list[i] if i < len(L_bar_list) else 0 + + eotf_val = None + if ( + i < len(eotf_list) + and isinstance(eotf_list[i], (list, tuple)) + and len(eotf_list[i]) >= 4 + ): + eotf_val = eotf_list[i][3] + + ws[f"A{row}"] = gray_level + ws[f"B{row}"] = x + ws[f"C{row}"] = y + ws[f"D{row}"] = lv + ws[f"E{row}"] = L_bar_val + + if eotf_val is not None and 0.5 < eotf_val < 5.0: + ws[f"F{row}"] = eotf_val + ws[f"F{row}"].number_format = "0.000" + else: + ws[f"F{row}"] = "N/A" + + ws[f"A{row}"].number_format = "0" + ws[f"B{row}"].number_format = "0.0000" + ws[f"C{row}"].number_format = "0.0000" + ws[f"D{row}"].number_format = "0.00" + ws[f"E{row}"].number_format = "0.0000" + + for col in ["A", "B", "C", "D", "E", "F"]: + ws[f"{col}{row}"].font = data_font + ws[f"{col}{row}"].alignment = data_alignment + ws[f"{col}{row}"].border = thin_border + + row += 1 + + row += 1 + self.log_gui.log(" ✓ 添加 EOTF 数据") + + # ========== 3. 色度一致性数据 ========== + if "cct" in selected_items: + gray_data = self.results.get_intermediate_data("shared", "gray") + if not gray_data: + gray_data = self.results.get_intermediate_data( + "cct", "gray" + ) + + if gray_data and len(gray_data) > 1: + gray_data_no_black = gray_data[:-1] + + ws.merge_cells(f"A{row}:G{row}") + ws[f"A{row}"] = "🌈 色度一致性数据" + ws[f"A{row}"].font = section_font + ws[f"A{row}"].fill = section_fill + ws[f"A{row}"].alignment = section_alignment + ws.row_dimensions[row].height = 25 + row += 1 + + x_coords = [d[0] for d in gray_data_no_black] + y_coords = [d[1] for d in gray_data_no_black] + + ws[f"A{row}"] = "x 坐标范围" + ws[f"B{row}"] = f"{min(x_coords):.4f} ~ {max(x_coords):.4f}" + ws[f"C{row}"] = "y 坐标范围" + ws[f"D{row}"] = f"{min(y_coords):.4f} ~ {max(y_coords):.4f}" + + for col in ["A", "B", "C", "D"]: + ws[f"{col}{row}"].font = ( + label_font if col in ["A", "C"] else data_font + ) + ws[f"{col}{row}"].border = thin_border + row += 1 + + headers = [ + "灰阶 (%)", + "x 坐标", + "y 坐标", + "亮度 (cd/m²)", + "", + "", + "", + ] + for col_idx, header in enumerate(headers, start=1): + cell = ws.cell(row=row, column=col_idx) + cell.value = header + cell.font = header_font + cell.fill = header_fill + cell.alignment = header_alignment + cell.border = thin_border + row += 1 + + total_points = len(gray_data) + for i in range(len(gray_data_no_black) - 1, -1, -1): + x, y, lv = ( + gray_data_no_black[i][0], + gray_data_no_black[i][1], + gray_data_no_black[i][2], + ) + gray_level = ( + 100 - int(i * 100 / (total_points - 1)) + if total_points > 1 + else 0 + ) + + ws[f"A{row}"] = gray_level + ws[f"B{row}"] = x + ws[f"C{row}"] = y + ws[f"D{row}"] = lv + + ws[f"A{row}"].number_format = "0" + ws[f"B{row}"].number_format = "0.0000" + ws[f"C{row}"].number_format = "0.0000" + ws[f"D{row}"].number_format = "0.00" + + for col in ["A", "B", "C", "D"]: + ws[f"{col}{row}"].font = data_font + ws[f"{col}{row}"].alignment = data_alignment + ws[f"{col}{row}"].border = thin_border + row += 1 + + row += 1 + self.log_gui.log(" ✓ 添加色度一致性数据") + + # ========== 4. 对比度数据 ========== + if "contrast" in selected_items: + contrast_final_result = None + if "contrast" in self.results.test_items: + contrast_final_result = self.results.test_items[ + "contrast" + ].final_result + + if contrast_final_result: + ws.merge_cells(f"A{row}:G{row}") + ws[f"A{row}"] = "⚫⚪ 对比度测试数据" + ws[f"A{row}"].font = section_font + ws[f"A{row}"].fill = section_fill + ws[f"A{row}"].alignment = section_alignment + ws.row_dimensions[row].height = 25 + row += 1 + + max_lv = contrast_final_result.get("max_luminance", 0) + min_lv = contrast_final_result.get("min_luminance", 0) + contrast_ratio = contrast_final_result.get( + "contrast_ratio", 0 + ) + + info_items = [ + ("最大亮度(白场)", f"{max_lv:.2f} cd/m²"), + ("最小亮度(黑场)", f"{min_lv:.4f} cd/m²"), + ("对比度", f"{contrast_ratio:.0f}:1"), + ] + + for label, value in info_items: + ws[f"A{row}"] = label + ws[f"B{row}"] = value + ws[f"A{row}"].font = label_font + ws[f"B{row}"].font = data_font + ws[f"A{row}"].border = thin_border + ws[f"B{row}"].border = thin_border + row += 1 + + row += 1 + self.log_gui.log(" ✓ 添加对比度数据") + + # ========== 5. 色准数据(HDR 特有)========== + if "accuracy" in selected_items: + accuracy_final_result = None + if "accuracy" in self.results.test_items: + accuracy_final_result = self.results.test_items[ + "accuracy" + ].final_result + + if accuracy_final_result: + ws.merge_cells(f"A{row}:G{row}") + ws[f"A{row}"] = "🎯 色准测试数据" + ws[f"A{row}"].font = section_font + ws[f"A{row}"].fill = section_fill + ws[f"A{row}"].alignment = section_alignment + ws.row_dimensions[row].height = 25 + row += 1 + + # 色准统计信息 + avg_delta_e = accuracy_final_result.get("avg_delta_e", 0) + max_delta_e = accuracy_final_result.get("max_delta_e", 0) + min_delta_e = accuracy_final_result.get("min_delta_e", 0) + excellent_count = accuracy_final_result.get( + "excellent_count", 0 + ) + good_count = accuracy_final_result.get("good_count", 0) + poor_count = accuracy_final_result.get("poor_count", 0) + + ws[f"A{row}"] = "平均 ΔE" + ws[f"B{row}"] = f"{avg_delta_e:.2f}" + ws[f"C{row}"] = "最大 ΔE" + ws[f"D{row}"] = f"{max_delta_e:.2f}" + ws[f"E{row}"] = "最小 ΔE" + ws[f"F{row}"] = f"{min_delta_e:.2f}" + + for col in ["A", "B", "C", "D", "E", "F"]: + ws[f"{col}{row}"].font = ( + label_font if col in ["A", "C", "E"] else data_font + ) + ws[f"{col}{row}"].border = thin_border + row += 1 + + # 第二行统计 + ws[f"A{row}"] = "优秀 (ΔE<3)" + ws[f"B{row}"] = f"{excellent_count} 个" + ws[f"C{row}"] = "良好 (3≤ΔE<5)" + ws[f"D{row}"] = f"{good_count} 个" + ws[f"E{row}"] = "偏差 (ΔE≥5)" + ws[f"F{row}"] = f"{poor_count} 个" + + for col in ["A", "B", "C", "D", "E", "F"]: + ws[f"{col}{row}"].font = ( + label_font if col in ["A", "C", "E"] else data_font + ) + ws[f"{col}{row}"].border = thin_border + row += 1 + + # ========== 色准详细数据表格(带 xy 坐标和亮度)========== + color_patches = accuracy_final_result.get( + "color_patches", [] + ) + delta_e_values = accuracy_final_result.get( + "delta_e_values", [] + ) + + # ✅ 获取原始测量数据(包含 xy 和亮度) + color_measurements = accuracy_final_result.get( + "color_measurements", [] + ) + + if color_patches and delta_e_values: + # 表头 + headers = [ + "序号", + "颜色名称", + "x 坐标", + "y 坐标", + "亮度 (cd/m²)", + "ΔE 2000", + "等级", + ] + for col_idx, header in enumerate(headers, start=1): + cell = ws.cell(row=row, column=col_idx) + cell.value = header + cell.font = header_font + cell.fill = header_fill + cell.alignment = header_alignment + cell.border = thin_border + row += 1 + + # 数据行 + for idx, (color_name, delta_e) in enumerate( + zip(color_patches, delta_e_values), start=1 + ): + # 判断等级 + if delta_e < 3: + grade = "优秀" + elif delta_e < 5: + grade = "良好" + else: + grade = "偏差" + + # ✅ 获取测量数据(x, y, 亮度) + x_val = "N/A" + y_val = "N/A" + lv_val = "N/A" + + if color_measurements and idx - 1 < len( + color_measurements + ): + measurement = color_measurements[idx - 1] + if len(measurement) >= 3: + x_val = measurement[0] + y_val = measurement[1] + lv_val = measurement[2] + + ws[f"A{row}"] = idx + ws[f"B{row}"] = color_name + ws[f"C{row}"] = x_val + ws[f"D{row}"] = y_val + ws[f"E{row}"] = lv_val + ws[f"F{row}"] = delta_e + ws[f"G{row}"] = grade + + # 数字格式 + ws[f"A{row}"].number_format = "0" + if isinstance(x_val, (int, float)): + ws[f"C{row}"].number_format = "0.0000" + if isinstance(y_val, (int, float)): + ws[f"D{row}"].number_format = "0.0000" + if isinstance(lv_val, (int, float)): + ws[f"E{row}"].number_format = "0.00" + ws[f"F{row}"].number_format = "0.00" + + for col in ["A", "B", "C", "D", "E", "F", "G"]: + ws[f"{col}{row}"].font = data_font + ws[f"{col}{row}"].alignment = data_alignment + ws[f"{col}{row}"].border = thin_border + + row += 1 + + row += 1 + self.log_gui.log(" ✓ 添加色准数据(含 xy 坐标和亮度)") + + # ========== 调整列宽 ========== + for col in ["A", "B", "C", "D", "E", "F", "G"]: + ws.column_dimensions[col].width = 18 + + # ========== 保存 Excel ========== + excel_path = os.path.join(result_dir, "测试数据.xlsx") + wb.save(excel_path) + + self.log_gui.log(f"✓ 已保存: 测试数据.xlsx") + self.log_gui.log("=" * 60) + + except ImportError: + self.log_gui.log("⚠️ 未安装 openpyxl 库,跳过 Excel 导出") + except Exception as e: + self.log_gui.log(f"⚠️ Excel 导出失败: {str(e)}") + import traceback + + self.log_gui.log(traceback.format_exc()) + + # ========== ✅ 统一的成功提示(在所有 Excel 代码之后)========== + self.log_gui.log(f"=" * 50) + self.log_gui.log(f"✅ 测试结果已保存到目录: {result_dir}") + self.log_gui.log(f"=" * 50) + messagebox.showinfo("成功", f"测试结果已保存到目录:\n{result_dir}") + + except Exception as e: + self.log_gui.log(f"❌ 保存测试结果失败: {str(e)}") + import traceback + + self.log_gui.log(traceback.format_exc()) + messagebox.showerror("错误", f"保存测试结果失败: {str(e)}") + + def new_pq_results(self, test_type, test_name): + self.results = PQResult(test_type, test_name) + # 设置配置 + config = { + "test_type": test_type, + "test_name": test_name, + "test_items": self.config.current_test_types[test_type]["test_items"], + "test_items_chinese": self.config.get_test_item_chinese_names( + self.config.current_test_types[test_type]["test_items"] + ), + } + self.results.set_test_config(config) + + # 添加测试项 + for item in config["test_items"]: + self.results.add_test_item( + item, config["test_items_chinese"][config["test_items"].index(item)] + ) + + def run_test(self, test_type, test_items): + """执行测试""" + try: + self.log_gui.log(f"开始执行{self.get_test_type_name(test_type)}测试") + self.log_gui.log( + f"测试项目: {', '.join(self.config.get_test_item_chinese_names(test_items))}" + ) + + # 根据测试类型执行不同的测试流程 + if test_type == "screen_module": + self.run_screen_module_test(test_items) + elif test_type == "sdr_movie": + self.run_sdr_movie_test(test_items) + elif test_type == "hdr_movie": + self.run_hdr_movie_test(test_items) + + # 测试完成后更新UI状态 + if self.testing: # 如果没有被中途停止 + self.root.after(0, self.on_test_completed) + except Exception as e: + self.log_gui.log(f"测试过程中发生错误: {str(e)}") + import traceback + + self.log_gui.log(traceback.format_exc()) + self.root.after(0, self.on_test_error) + + def run_screen_module_test(self, test_items): + """执行屏模组性能测试 - 优化版""" + self.log_gui.log("执行屏模组性能测试...") + + if test_items: + self.new_pq_results("screen_module", "屏模组性能测试") + else: + self.log_gui.log("未选择任何测试项目") + return + + # 判断是否需要灰阶数据 + needs_gray_data = any( + item in test_items for item in ["gamma", "cct", "contrast"] + ) + shared_gray_data = None # 共享的灰阶数据 + + # 计算总测试项数量 + total_items = len(test_items) + current_item = 0 + + for item in test_items: + if not self.testing: # 检查是否被停止 + return + + current_item += 1 + self.status_var.set(f"测试进行中... ({current_item}/{total_items})") + + # ==================== 色域测试 ==================== + if item == "gamut": + self.test_gamut("screen_module") + + # ==================== 灰阶数据采集 ==================== + # 如果是第一个需要灰阶数据的测试项,统一采集数据 + elif ( + item in ["gamma", "cct", "contrast"] + and shared_gray_data is None + and needs_gray_data + ): + self.log_gui.log("=" * 50) + self.log_gui.log("开始统一采集灰阶数据(用于 Gamma/CCT/对比度测试)") + self.log_gui.log("=" * 50) + + shared_gray_data = self.send_fix_pattern("gray") + + if not shared_gray_data or len(shared_gray_data) < 2: + self.log_gui.log("灰阶数据采集失败或数据不足,跳过相关测试") + return + + self.log_gui.log( + f"✓ 灰阶数据采集完成,共 {len(shared_gray_data)} 个数据点" + ) + + # 保存到 results 对象,供所有灰阶测试使用 + self.results.add_intermediate_data("shared", "gray", shared_gray_data) + + # 执行当前测试项 + if item == "gamma": + self.test_gamma("screen_module", shared_gray_data) + elif item == "cct": + self.test_cct("screen_module", shared_gray_data) + elif item == "contrast": + self.test_contrast("screen_module", shared_gray_data) + + # ==================== 后续灰阶测试(复用数据) ==================== + elif item in ["gamma", "cct", "contrast"] and shared_gray_data is not None: + self.log_gui.log(f"复用已采集的灰阶数据进行 {item} 测试") + + if item == "gamma": + self.test_gamma("screen_module", shared_gray_data) + elif item == "cct": + self.test_cct("screen_module", shared_gray_data) + elif item == "contrast": + self.test_contrast("screen_module", shared_gray_data) + + + + def run_custom_sdr_test(self, test_items): + """执行客户定制 SDR 测试 - 升级版""" + self.log_gui.log("执行客户定制 SDR 测试...") + # 获取信号格式设置 + color_space = self.sdr_color_space_var.get() # BT.709/BT.601/BT.2020 + gamma_type = self.sdr_gamma_type_var.get() # 2.2/2.4/2.6 + data_range = self.sdr_data_range_var.get() # Full/Limited + bit_depth = self.sdr_bit_depth_var.get() # 8bit/10bit/12bit + + self.log_gui.log(f"信号格式: 色彩空间={color_space}, Gamma={gamma_type}") + self.log_gui.log(f" 数据范围={data_range}, 编码位深={bit_depth}") + self.log_gui.log("开始统一采集灰阶数据(用于 Gamma/CCT/对比度测试)") + self.test_custom_sdr() + + if self.testing: + self.root.after(0, self.on_custom_template_test_completed) + + + + + def run_sdr_movie_test(self, test_items): + """执行SDR Movie测试""" + self.log_gui.log("执行SDR Movie测试...") + + if test_items: + self.new_pq_results("sdr_movie", "SDR Movie测试") + else: + self.log_gui.log("未选择任何测试项目") + return + + # 获取信号格式设置 + color_space = self.sdr_color_space_var.get() # BT.709/BT.601/BT.2020 + gamma_type = self.sdr_gamma_type_var.get() # 2.2/2.4/2.6 + data_range = self.sdr_data_range_var.get() # Full/Limited + bit_depth = self.sdr_bit_depth_var.get() # 8bit/10bit/12bit + + self.log_gui.log(f"信号格式: 色彩空间={color_space}, Gamma={gamma_type}") + self.log_gui.log(f" 数据范围={data_range}, 编码位深={bit_depth}") + + # 判断是否需要灰阶数据 + needs_gray_data = any( + item in test_items for item in ["gamma", "cct", "contrast"] + ) + shared_gray_data = None + + # 计算总测试项数量 + total_items = len(test_items) + current_item = 0 + + for item in test_items: + if not self.testing: + return + + current_item += 1 + self.status_var.set(f"测试进行中... ({current_item}/{total_items})") + + if item == "gamut": + self.test_gamut("sdr_movie") + + elif ( + item in ["gamma", "cct", "contrast"] + and shared_gray_data is None + and needs_gray_data + ): + self.log_gui.log("开始统一采集灰阶数据(用于 Gamma/CCT/对比度测试)") + shared_gray_data = self.send_fix_pattern("gray") + + if not shared_gray_data or len(shared_gray_data) < 2: + self.log_gui.log("灰阶数据采集失败或数据不足") + return + + self.results.add_intermediate_data("shared", "gray", shared_gray_data) + + if item == "gamma": + self.test_gamma("sdr_movie", shared_gray_data) + elif item == "cct": + self.test_cct("sdr_movie", shared_gray_data) + elif item == "contrast": + self.test_contrast("sdr_movie", shared_gray_data) + + elif item in ["gamma", "cct", "contrast"] and shared_gray_data is not None: + self.log_gui.log(f"复用已采集的灰阶数据进行 {item} 测试") + if item == "gamma": + self.test_gamma("sdr_movie", shared_gray_data) + elif item == "cct": + self.test_cct("sdr_movie", shared_gray_data) + elif item == "contrast": + self.test_contrast("sdr_movie", shared_gray_data) + + elif item == "accuracy": + self.test_color_accuracy("sdr_movie") + + def run_hdr_movie_test(self, test_items): + """执行HDR Movie测试""" + self.log_gui.log("执行HDR Movie测试...") + + if test_items: + self.new_pq_results("hdr_movie", "HDR Movie测试") + else: + self.log_gui.log("未选择任何测试项目") + return + + # 获取信号格式设置 + color_space = self.hdr_color_space_var.get() + max_cll = self.hdr_maxcll_var.get() + max_fall = self.hdr_maxfall_var.get() + data_range = self.hdr_data_range_var.get() + bit_depth = self.hdr_bit_depth_var.get() + + self.log_gui.log(f"信号格式: 色彩空间={color_space}") + self.log_gui.log(f" MaxCLL={max_cll}, MaxFALL={max_fall}") + self.log_gui.log(f" 数据范围={data_range}, 编码位深={bit_depth}") + + # 判断是否需要灰阶数据 + needs_gray_data = any( + item in test_items for item in ["eotf", "cct", "contrast"] + ) + shared_gray_data = None + + # 计算总测试项数量 + total_items = len(test_items) + current_item = 0 + + for item in test_items: + if not self.testing: + return + + current_item += 1 + self.status_var.set(f"测试进行中... ({current_item}/{total_items})") + + if item == "gamut": + self.test_gamut("hdr_movie") + + elif ( + item in ["eotf", "cct", "contrast"] + and shared_gray_data is None + and needs_gray_data + ): + self.log_gui.log("开始统一采集灰阶数据(用于 EOTF/CCT/对比度测试)") + shared_gray_data = self.send_fix_pattern("gray") + + if not shared_gray_data or len(shared_gray_data) < 2: + self.log_gui.log("灰阶数据采集失败或数据不足") + return + + self.results.add_intermediate_data("shared", "gray", shared_gray_data) + + if item == "eotf": + self.test_eotf("hdr_movie", shared_gray_data) + elif item == "cct": + self.test_cct("hdr_movie", shared_gray_data) + elif item == "contrast": + self.test_contrast("hdr_movie", shared_gray_data) + + elif item in ["eotf", "cct", "contrast"] and shared_gray_data is not None: + self.log_gui.log(f"复用已采集的灰阶数据进行 {item} 测试") + if item == "eotf": + self.test_eotf("hdr_movie", shared_gray_data) + elif item == "cct": + self.test_cct("hdr_movie", shared_gray_data) + elif item == "contrast": + self.test_contrast("hdr_movie", shared_gray_data) + + elif item == "accuracy": + self.test_color_accuracy("hdr_movie") + + def send_fix_pattern(self, mode): + """发送固定图案并采集数据 - 支持不同测试类型的信号格式""" + results = [] + + try: + # 1. 设置图案模式 + if mode == "rgb": + self.config.set_current_pattern("rgb") + elif mode == "gray": + self.config.set_current_pattern("gray") + elif mode == "accuracy": # 色准模式(SDR 和 HDR 通用 29色) + self.config.set_current_pattern("accuracy") + elif mode == "custom": + self.config.set_current_pattern("custom") + else: + self.log_gui.log(f"❌ 未知的图案模式: {mode}") + return None + + # 2. 获取当前测试类型 + test_type = self.config.current_test_type + + # 3. 根据测试类型设置信号格式和图案 + if test_type == "screen_module": + # 屏模组测试:使用 Timing + self.log_gui.log("=" * 50) + self.log_gui.log("设置屏模组信号格式:") + self.log_gui.log("=" * 50) + + timing_str = self.config.current_test_types[test_type]["timing"] + self.log_gui.log(f" Timing: {timing_str}") + + # ✅ 屏模组测试:直接使用原始配置 + self.ucd.set_ucd_params(self.config) + + elif test_type == "sdr_movie": + # SDR 测试:设置色彩空间、Gamma 等 + self.log_gui.log("=" * 50) + self.log_gui.log("设置 SDR 信号格式:") + self.log_gui.log("=" * 50) + + color_space = self.sdr_color_space_var.get() + gamma = self.sdr_gamma_type_var.get() + data_range = self.sdr_data_range_var.get() + bit_depth = self.sdr_bit_depth_var.get() + + self.log_gui.log(f" 色彩空间: {color_space}") + self.log_gui.log(f" Gamma: {gamma}") + self.log_gui.log(f" 数据范围: {data_range}") + self.log_gui.log(f" 编码位深: {bit_depth}") + + success = self.ucd.set_sdr_format( + color_space=color_space, + gamma=gamma, + data_range=data_range, + bit_depth=bit_depth, + ) + + if success: + self.log_gui.log("✓ SDR 信号格式设置成功") + else: + self.log_gui.log("✗ SDR 信号格式设置失败") + + # 设置图案参数 + if mode == "accuracy": + self.log_gui.log(f"设置 SDR 29色色准测试图案...") + else: + self.log_gui.log(f"设置 SDR 测试图案({mode} 模式)...") + + # ========== ✅✅✅ 修改:使用临时配置对象 ========== + import copy + + # 从原始配置获取参数(每次都是干净的) + if mode == "rgb": + original_params = copy.deepcopy( + self.config.default_pattern_rgb["pattern_params"] + ) + elif mode == "gray": + original_params = copy.deepcopy( + self.config.default_pattern_gray["pattern_params"] + ) + elif mode == "accuracy": + original_params = copy.deepcopy( + self.config.default_pattern_accuracy["pattern_params"] + ) + elif mode == "custom": + original_params = copy.deepcopy( + self.config.default_pattern_temp["pattern_params"] + ) + + self.log_gui.log(f"🔍 使用原始 RGB 参数(前 3 个):") + for i in range(min(3, len(original_params))): + self.log_gui.log(f" [{i+1}] {original_params[i]}") + + # 根据 data_range 转换 + converted_params = convert_pattern_params( + pattern_params=original_params, data_range=data_range, verbose=False + ) + + if data_range == "Limited": + self.log_gui.log("🔧 转换为 Limited Range (16-235):") + for i in range(min(3, len(converted_params))): + self.log_gui.log( + f" {original_params[i]} → {converted_params[i]}" + ) + else: + self.log_gui.log("✓ Full Range,RGB 保持不变") + + # ✅ 创建临时配置对象(不修改 self.config) + temp_config = self.config.get_temp_config_with_converted_params( + mode=mode, converted_params=converted_params + ) + + # ✅ 使用临时配置设置参数 + self.ucd.set_ucd_params(temp_config) + + self.log_gui.log(f"✓ 图案参数已设置,共 {len(converted_params)} 个图案") + # ========== 修改结束 ========== + + elif test_type == "hdr_movie": + # HDR 测试:设置色彩空间、Metadata 等 + self.log_gui.log("=" * 50) + self.log_gui.log("设置 HDR 信号格式:") + self.log_gui.log("=" * 50) + + color_space = self.hdr_color_space_var.get() + data_range = self.hdr_data_range_var.get() + bit_depth = self.hdr_bit_depth_var.get() + max_cll = self.hdr_maxcll_var.get() + max_fall = self.hdr_maxfall_var.get() + + self.log_gui.log(f" 色彩空间: {color_space}") + self.log_gui.log(f" 数据范围: {data_range}") + self.log_gui.log(f" 编码位深: {bit_depth}") + self.log_gui.log(f" MaxCLL: {max_cll}") + self.log_gui.log(f" MaxFALL: {max_fall}") + + success = self.ucd.set_hdr_format( + color_space=color_space, + data_range=data_range, + bit_depth=bit_depth, + max_cll=max_cll, + max_fall=max_fall, + ) + + if success: + self.log_gui.log("✓ HDR 信号格式设置成功") + else: + self.log_gui.log("✗ HDR 信号格式设置失败") + + # 设置图案参数 + if mode == "accuracy": + self.log_gui.log(f"设置 HDR 29色色准测试图案...") + else: + self.log_gui.log(f"设置 HDR 测试图案({mode} 模式)...") + + # ========== ✅✅✅ 修改:使用临时配置对象 ========== + import copy + + # 从原始配置获取参数 + if mode == "rgb": + original_params = copy.deepcopy( + self.config.default_pattern_rgb["pattern_params"] + ) + elif mode == "gray": + original_params = copy.deepcopy( + self.config.default_pattern_gray["pattern_params"] + ) + elif mode == "accuracy": + original_params = copy.deepcopy( + self.config.default_pattern_accuracy["pattern_params"] + ) + + self.log_gui.log(f"🔍 使用原始 RGB 参数(前 3 个):") + for i in range(min(3, len(original_params))): + self.log_gui.log(f" [{i+1}] {original_params[i]}") + + # 根据 data_range 转换 + converted_params = convert_pattern_params( + pattern_params=original_params, data_range=data_range, verbose=False + ) + + if data_range == "Limited": + self.log_gui.log("🔧 转换为 Limited Range (16-235):") + for i in range(min(3, len(converted_params))): + self.log_gui.log( + f" {original_params[i]} → {converted_params[i]}" + ) + else: + self.log_gui.log("✓ Full Range,RGB 保持不变") + + # ✅ 创建临时配置对象 + temp_config = self.config.get_temp_config_with_converted_params( + mode=mode, converted_params=converted_params + ) + + self.ucd.set_ucd_params(temp_config) + + self.log_gui.log(f"✓ 图案参数已设置,共 {len(converted_params)} 个图案") + # ========== 修改结束 ========== + + self.log_gui.log("=" * 50) + + # 4. 循环发送图案并采集数据(使用原始配置的数量) + total_patterns = len(self.config.current_pattern["pattern_params"]) + self.log_gui.log(f"开始采集数据,共 {total_patterns} 个图案") + settle_time = max(0.2, float(getattr(self, "pattern_settle_time", 1.0))) + progress_step = max( + 1, int(getattr(self, "pattern_progress_log_step", 5)) + ) + self.log_gui.log( + f"采集等待时间: {settle_time:.2f}s(可通过 pattern_settle_time 调整)" + ) + + # 获取颜色名称列表(用于日志显示) + color_names = None + if mode == "accuracy": + color_names = self.config.get_accuracy_color_names() + + custom_pattern_names = [] + if mode == "custom" and hasattr(self.config, "get_temp_pattern_names"): + custom_pattern_names = self.config.get_temp_pattern_names() + + for i in range(total_patterns): + if not self.testing: + self.log_gui.log("⚠️ 测试已停止") + return results + + should_log_detail = ( + i == 0 + or (i + 1) == total_patterns + or ((i + 1) % progress_step == 0) + ) + + # 设置下一个图案(显示颜色名称) + if should_log_detail: + if color_names and i < len(color_names): + self.log_gui.log( + f"发送第 {i+1}/{total_patterns} 个图案: {color_names[i]}..." + ) + else: + self.log_gui.log(f"发送第 {i+1}/{total_patterns} 个图案...") + + self.ucd.set_next_pattern() + self.ucd.run() + time.sleep(settle_time) + + # 测量数据 + if mode == "custom": + result = [] + self.ca.set_Display(1) + tcp, duv, lv, X, Y, Z = self.ca.readAllDisplay() + + if should_log_detail: + self.log_gui.log( + f" ✓ 测量完成: TCP={tcp:.4f}, DUV={duv:.4f}, lv={lv:.2f}, " + f"X={X:.4f}, Y={Y:.4f}, Z={Z:.4f}" + ) + + self.ca.set_Display(8) + lambda_, Pe, lv, X, Y, Z = self.ca.readAllDisplay() + + if should_log_detail: + self.log_gui.log( + f" ✓ 测量完成: λ={lambda_:.4f}, Pe={Pe:.4f}, lv={lv:.2f}, " + f"X={X:.4f}, Y={Y:.4f}, Z={Z:.4f}" + ) + result = [tcp, duv, lv, lambda_, Pe, lv, X, Y, Z] + results.append(result) + + # 每完成一个 pattern,实时写入客户模板结果表。 + try: + xy = colour.XYZ_to_xy(np.array([X, Y, Z])) + u_prime, v_prime, _ = colour.XYZ_to_CIE1976UCS( + np.array([X, Y, Z]) + ) + row_data = { + "pattern_name": ( + custom_pattern_names[i] + if i < len(custom_pattern_names) + else f"P {i + 1}" + ), + "X": X, + "Y": Y, + "Z": Z, + "x": xy[0], + "y": xy[1], + "Lv": lv, + "u_prime": u_prime, + "v_prime": v_prime, + "Tcp": tcp, + "duv": duv, + "lambda_d": lambda_, + "Pe": Pe, + } + self.root.after( + 0, + lambda row_no=i + 1, data=row_data: self.append_custom_template_result( + row_no, data + ), + ) + except Exception as e: + self.log_gui.log(f"⚠️ 第 {i+1} 行实时结果写入失败: {str(e)}") + + else: + self.ca.set_xyLv_Display() + + x, y, lv, X, Y, Z = self.ca.readAllDisplay() + results.append([x, y, lv, X, Y, Z]) + + if should_log_detail: + self.log_gui.log( + f" ✓ 测量完成: x={x:.4f}, y={y:.4f}, lv={lv:.2f}, " + f"X={X:.4f}, Y={Y:.4f}, Z={Z:.4f}" + ) + + self.log_gui.log(f"✓ 数据采集完成,共 {len(results)} 组数据") + return results + + except Exception as e: + self.log_gui.log(f"❌ 发送图案失败: {str(e)}") + import traceback + + self.log_gui.log(traceback.format_exc()) + return None + + def test_custom_sdr(self): + """执行客户定制 SDR 测试 - 升级版""" + self.log_gui.log("执行客户定制 SDR 测试...") + results = self.send_fix_pattern("custom") + if not results: + self.log_gui.log("客户模板SDR测试被中断") + return + + self.log_gui.log(f"客户模板采集完成,共 {len(results)} 组数据") + + + + + def test_gamut(self, test_type): + """测试色域""" + self.log_gui.log("开始测试色域...") + self.results.start_test_item("gamut") + + try: + # 存储测量结果 + results = self.send_fix_pattern("rgb") + + # 检查结果是否为空 + if not results: + self.log_gui.log("色域测试被中断") + return + + self.results.add_intermediate_data("gamut", "rgb", results) + + # 计算色域覆盖率 + self.log_gui.log("计算色域覆盖率...") + + # 提取 x, y 坐标用于计算色域 + xy_points = [[result[0], result[1]] for result in results] + + # ========== ✅ 测试时:使用色彩空间的值作为参考标准 ========== + reference_standard = None + area = None + coverage = None + + if test_type == "screen_module": + # 屏模组测试:固定使用 DCI-P3(因为没有色彩空间设置) + reference_standard = "DCI-P3" + + # ✅ 同步更新到色域参考标准变量(供后续重绘使用) + self.screen_gamut_ref_var.set(reference_standard) + + elif test_type == "sdr_movie": + # SDR 测试:使用色彩空间设置 + color_space = self.sdr_color_space_var.get() + + if color_space == "BT.709": + reference_standard = "BT.709" + elif color_space == "BT.601": + reference_standard = "BT.601" + elif color_space == "BT.2020": + reference_standard = "BT.2020" + else: + reference_standard = "BT.709" + self.log_gui.log( + f"⚠️ 未识别的色彩空间 '{color_space}',使用默认标准 BT.709" + ) + + # ✅ 同步更新到色域参考标准变量 + self.sdr_gamut_ref_var.set(reference_standard) + + elif test_type == "hdr_movie": + # HDR 测试:使用色彩空间设置 + color_space = self.hdr_color_space_var.get() + + if color_space == "BT.2020": + reference_standard = "BT.2020" + elif color_space == "DCI-P3": + reference_standard = "DCI-P3" + else: + reference_standard = "BT.2020" + self.log_gui.log( + f"⚠️ 未识别的色彩空间 '{color_space}',使用默认标准 BT.2020" + ) + + # ✅ 同步更新到色域参考标准变量 + self.hdr_gamut_ref_var.set(reference_standard) + else: + # 未知测试类型,使用 DCI-P3 作为后备 + reference_standard = "DCI-P3" + self.log_gui.log( + f"⚠️ 未识别的测试类型 '{test_type}',使用默认标准 DCI-P3" + ) + + # ========== 根据参考标准计算 XY 覆盖率 ========== + if reference_standard == "BT.2020": + area, coverage = pq_algorithm.calculate_gamut_coverage_BT2020(xy_points) + elif reference_standard == "BT.709": + area, coverage = pq_algorithm.calculate_gamut_coverage_BT709(xy_points) + elif reference_standard == "DCI-P3": + area, coverage = pq_algorithm.calculate_gamut_coverage_DCIP3(xy_points) + elif reference_standard == "BT.601": + area, coverage = pq_algorithm.calculate_gamut_coverage_BT601(xy_points) + else: + # 默认使用 DCI-P3 + area, coverage = pq_algorithm.calculate_gamut_coverage_DCIP3(xy_points) + reference_standard = "DCI-P3" + self.log_gui.log( + f"⚠️ 未识别的参考标准 '{reference_standard}',使用默认标准 DCI-P3" + ) + + # ========== ✅✅✅ 新增:计算 UV 覆盖率 ========== + uv_coverage = 0 + try: + # 将 XY 转换为 UV + uv_points = [] + for x, y in xy_points: + u, v = pq_algorithm.xy_to_uv_1976(x, y) + uv_points.append([u, v]) + + # 根据参考标准计算 UV 覆盖率 + if len(uv_points) >= 3: + if reference_standard == "BT.2020": + _, uv_coverage = ( + pq_algorithm.calculate_gamut_coverage_BT2020_uv(uv_points) + ) + elif reference_standard == "BT.709": + _, uv_coverage = pq_algorithm.calculate_gamut_coverage_BT709_uv( + uv_points + ) + elif reference_standard == "DCI-P3": + _, uv_coverage = pq_algorithm.calculate_gamut_coverage_DCIP3_uv( + uv_points + ) + elif reference_standard == "BT.601": + _, uv_coverage = pq_algorithm.calculate_gamut_coverage_BT601_uv( + uv_points + ) + else: + _, uv_coverage = pq_algorithm.calculate_gamut_coverage_DCIP3_uv( + uv_points + ) + + self.log_gui.log( + f"✓ XY 覆盖率: {coverage:.1f}% | UV 覆盖率: {uv_coverage:.1f}%" + ) + except: + uv_coverage = 0 + + # ========== 保存结果时包含 XY 和 UV 覆盖率 ========== + self.results.set_test_item_result( + "gamut", + { + "area": area, + "coverage": coverage, + "uv_coverage": uv_coverage, # ✅ 新增 UV 覆盖率 + "reference": reference_standard, + }, + ) + + # 传递完整的 results 用于绘图 + self.plot_gamut(results, coverage, test_type) + + self.log_gui.log("色域测试完成") + + except Exception as e: + self.log_gui.log(f"色域测试失败: {str(e)}") + import traceback + + self.log_gui.log(traceback.format_exc()) + raise + + def test_gamma(self, test_type, gray_data=None): + """测试Gamma曲线 + + Args: + test_type: 测阶数据,如果提供则使用,否则重新采集 + """ + self.log_gui.log("开始测试Gamma曲线...") + self.results.start_test_item("gamma") + + try: + # 使用传入的灰阶数据或独立采集 + if gray_data is not None: + self.log_gui.log("使用共享的灰阶数据") + results = gray_data + else: + self.log_gui.log("独立采集灰阶数据") + results = self.send_fix_pattern("gray") + + if not results or len(results) < 2: + self.log_gui.log("Gamma测试被中断或数据不足") + return + + self.results.add_intermediate_data("gamma", "gray", results) + + self.log_gui.log(f"使用 {len(results)} 个灰阶数据点进行计算") + self.log_gui.log("计算Gamma值...") + + # ========== ✅ 修复:正确获取 max_index_fix ========== + # 获取配置中的值 + config_max_value = self.config.current_pattern.get( + "measurement_max_value", 10 + ) + + # 强制转换为整数 + try: + max_index_fix = int(config_max_value) + except (ValueError, TypeError): + self.log_gui.log(f"警告: measurement_max_value 转换失败,使用默认值 10") + max_index_fix = 10 + + self.log_gui.log(f"配置中的 max_index_fix = {max_index_fix}") + + # 关键修复:验证并调整 max_index_fix + # max_index_fix 应该是数据点的最大索引(从0开始,所以是 len - 1) + actual_max_index = len(results) - 1 + + if max_index_fix > actual_max_index: + self.log_gui.log( + f"警告: 配置的 max_index_fix({max_index_fix}) > 实际最大索引({actual_max_index})" + ) + self.log_gui.log(f"自动调整为: {actual_max_index}") + max_index_fix = actual_max_index + + self.log_gui.log(f"最终使用的 max_index_fix = {max_index_fix}") + # ======================================================== + + # 获取灰阶 pattern 参数(用于22293 Gamma数据对齐) + pattern_params = self.config.default_pattern_gray.get( + "pattern_params", None + ) + + # 计算Gamma值(使用修正后的 max_index_fix 和 8bit pattern参数) + results_with_gamma_list, L_bar = self.calculate_gamma( + results, max_index_fix, pattern_params + ) + self.results.set_test_item_result( + "gamma", {"gamma": results_with_gamma_list, "L_bar": L_bar} + ) + + # 绘制Gamma曲线 + if test_type == "sdr_movie": + try: + target_gamma = float(self.sdr_gamma_type_var.get()) + except (ValueError, AttributeError): + target_gamma = 2.2 + else: + target_gamma = 2.2 + + self.plot_gamma(L_bar, results_with_gamma_list, target_gamma, test_type) + + self.log_gui.log("Gamma测试完成") + + except Exception as e: + self.log_gui.log(f"Gamma测试失败: {str(e)}") + import traceback + + self.log_gui.log(traceback.format_exc()) + raise + + def test_eotf(self, test_type, gray_data=None): + """测试 EOTF 曲线(HDR 专用) + + Args: + test_type: 测试类型阶数据,如果提供则使用,否则重新采集 + """ + self.log_gui.log("开始测试 EOTF 曲线(HDR)...") + self.results.start_test_item("eotf") + + try: + # 使用传入的灰阶数据或独立采集 + if gray_data is not None: + self.log_gui.log("使用共享的灰阶数据") + results = gray_data + else: + self.log_gui.log("独立采集灰阶数据") + results = self.send_fix_pattern("gray") + + if not results or len(results) < 2: + self.log_gui.log("EOTF 测试被中断或数据不足") + return + + self.results.add_intermediate_data("eotf", "gray", results) + + self.log_gui.log(f"使用 {len(results)} 个灰阶数据点进行计算") + self.log_gui.log("计算 EOTF 值...") + + # ========== 获取 max_index_fix ========== + config_max_value = self.config.current_pattern.get( + "measurement_max_value", 10 + ) + + try: + max_index_fix = int(config_max_value) + except (ValueError, TypeError): + self.log_gui.log(f"警告: measurement_max_value 转换失败,使用默认值 10") + max_index_fix = 10 + + self.log_gui.log(f"配置中的 max_index_fix = {max_index_fix}") + + # 验证并调整 max_index_fix + actual_max_index = len(results) - 1 + + if max_index_fix > actual_max_index: + self.log_gui.log( + f"警告: 配置的 max_index_fix({max_index_fix}) > 实际最大索引({actual_max_index})" + ) + self.log_gui.log(f"自动调整为: {actual_max_index}") + max_index_fix = actual_max_index + + self.log_gui.log(f"最终使用的 max_index_fix = {max_index_fix}") + + # 获取灰阶 pattern 参数(用于22293 Gamma数据对齐) + pattern_params = self.config.default_pattern_gray.get( + "pattern_params", None + ) + + # ========== 计算 EOTF(复用 Gamma 计算逻辑,使用8bit pattern参数)========== + results_with_eotf_list, L_bar = self.calculate_gamma( + results, max_index_fix, pattern_params + ) + + # 保存结果 + self.results.set_test_item_result( + "eotf", {"eotf": results_with_eotf_list, "L_bar": L_bar} + ) + + # ========== 绘制 EOTF 曲线 ========== + # HDR 使用 PQ 曲线,目标 gamma 设为 None(不使用传统 gamma) + self.plot_eotf(L_bar, results_with_eotf_list, test_type) + + self.log_gui.log("EOTF 测试完成") + + except Exception as e: + self.log_gui.log(f"EOTF 测试失败: {str(e)}") + import traceback + + self.log_gui.log(traceback.format_exc()) + raise + + def test_cct(self, test_type, gray_data=None): + """测试色度一致性""" + self.log_gui.log("开始测试色度一致性...") + self.results.start_test_item("cct") + + try: + if gray_data is not None: + self.log_gui.log("使用共享的灰阶数据") + results = gray_data + else: + self.log_gui.log("独立采集灰阶数据") + results = self.send_fix_pattern("gray") + + if not results: + self.log_gui.log("色度一致性测试被中断") + return + + self.results.add_intermediate_data("cct", "gray", results) + + self.log_gui.log(f"使用 {len(results)} 个灰阶数据点进行色度计算") + + # 提取色度坐标 + cct_values = pq_algorithm.calculate_cct_from_results(results) + + # 保存到结果 + self.results.set_test_item_result("cct", {"cct_values": cct_values}) + + # 绘制图表 + self.plot_cct(test_type) + + self.log_gui.log("色度一致性测试完成") + except Exception as e: + self.log_gui.log(f"色度一致性测试失败: {str(e)}") + import traceback + + self.log_gui.log(traceback.format_exc()) + raise + + def test_contrast(self, test_type, gray_data=None): + """测试对比度 + + Args: + test_type: 阶数据,如果提供则使用,否则重新采集 + """ + self.log_gui.log("开始测试对比度...") + self.results.start_test_item("contrast") + + try: + # ✅ 优先使用传入的灰阶数据 + if gray_data is not None: + self.log_gui.log("使用共享的灰阶数据") + results = gray_data + else: + self.log_gui.log("独立采集灰阶数据") + results = self.send_fix_pattern("gray") + + if not results: + self.log_gui.log("对比度测试被中断") + return + + self.results.add_intermediate_data("contrast", "gray", results) + + # 获取最亮和最暗的亮度值 + luminance_values = [result[2] for result in results] # 提取lv值 + + max_luminance = max(luminance_values) # 最大亮度(白) + min_luminance = min(luminance_values) # 最小亮度(黑) + + # 防止除以0 + if min_luminance < 0.001: + min_luminance = 0.001 + + # 计算对比度 + contrast_ratio = max_luminance / min_luminance + + # 保存结果 + contrast_data = { + "max_luminance": max_luminance, + "min_luminance": min_luminance, + "contrast_ratio": contrast_ratio, + "luminance_values": luminance_values, + } + self.results.set_test_item_result("contrast", contrast_data) + + # 显示对比度结果到日志 + self.log_gui.log(f"最大亮度 (白场): {max_luminance:.2f} cd/m²") + self.log_gui.log(f"最小亮度 (黑场): {min_luminance:.4f} cd/m²") + self.log_gui.log(f"对比度: {contrast_ratio:.0f}:1") + + # 绘制对比度图表 + self.plot_contrast(contrast_data, test_type) + + self.log_gui.log("对比度测试完成") + except Exception as e: + self.log_gui.log(f"对比度测试失败: {str(e)}") + import traceback + + self.log_gui.log(traceback.format_exc()) + raise + + def calculate_delta_e_2000( + self, measured_x, measured_y, measured_lv, standard_x, standard_y + ): + """ + 计算 ΔE 2000 色差(修正版) + + Args: + measured_x, measured_y: 测量的 xy 坐标 + measured_lv: 测量的亮度(cd/m²) # ← 新增 + standard_x, standard_y: 标准的 xy 坐标 + + Returns: + float: ΔE 2000 色差值 + """ + import math + + # ========== 1. xy → XYZ(使用实际亮度)========== + def xy_to_XYZ(x, y, Y): # ← 修改:接收 Y 参数 + if y == 0: + return 0, 0, 0 + X = x * Y / y + Z = (1 - x - y) * Y / y + return X, Y, Z + + # 修复:使用实际测量的亮度 + X1, Y1, Z1 = xy_to_XYZ(measured_x, measured_y, measured_lv) + + # 修复:标准值使用相同的参考亮度(只比较色度差异) + X2, Y2, Z2 = xy_to_XYZ(standard_x, standard_y, measured_lv) + + # ========== 2. XYZ → Lab(D65 白点)========== + def XYZ_to_Lab(X, Y, Z): + # D65 白点 + Xn, Yn, Zn = 95.047, 100.000, 108.883 + + # 归一化 + xr = X / Xn + yr = Y / Yn + zr = Z / Zn + + # f(t) 函数 + def f(t): + delta = 6.0 / 29.0 + if t > delta**3: + return t ** (1.0 / 3.0) + else: + return t / (3 * delta**2) + 4.0 / 29.0 + + fx = f(xr) + fy = f(yr) + fz = f(zr) + + L = 116 * fy - 16 + a = 500 * (fx - fy) + b = 200 * (fy - fz) + + return L, a, b + + L1, a1, b1 = XYZ_to_Lab(X1, Y1, Z1) + L2, a2, b2 = XYZ_to_Lab(X2, Y2, Z2) + + # ========== 3. ΔE 2000 公式(保持不变)========== + L_bar = (L1 + L2) / 2.0 + C1 = math.sqrt(a1**2 + b1**2) + C2 = math.sqrt(a2**2 + b2**2) + C_bar = (C1 + C2) / 2.0 + + G = 0.5 * (1 - math.sqrt(C_bar**7 / (C_bar**7 + 25**7))) + + a1_prime = a1 * (1 + G) + a2_prime = a2 * (1 + G) + + C1_prime = math.sqrt(a1_prime**2 + b1**2) + C2_prime = math.sqrt(a2_prime**2 + b2**2) + C_bar_prime = (C1_prime + C2_prime) / 2.0 + + def calc_hue(a_prime, b): + if a_prime == 0 and b == 0: + return 0 + h = math.atan2(b, a_prime) * 180 / math.pi + if h < 0: + h += 360 + return h + + h1_prime = calc_hue(a1_prime, b1) + h2_prime = calc_hue(a2_prime, b2) + + if C1_prime == 0 or C2_prime == 0: + delta_h_prime = 0 + else: + delta_h = h2_prime - h1_prime + if abs(delta_h) <= 180: + delta_h_prime = delta_h + elif delta_h > 180: + delta_h_prime = delta_h - 360 + else: + delta_h_prime = delta_h + 360 + + if C1_prime == 0 or C2_prime == 0: + H_bar_prime = h1_prime + h2_prime + else: + if abs(h1_prime - h2_prime) <= 180: + H_bar_prime = (h1_prime + h2_prime) / 2.0 + elif h1_prime + h2_prime < 360: + H_bar_prime = (h1_prime + h2_prime + 360) / 2.0 + else: + H_bar_prime = (h1_prime + h2_prime - 360) / 2.0 + + delta_L_prime = L2 - L1 + delta_C_prime = C2_prime - C1_prime + delta_H_prime = ( + 2 + * math.sqrt(C1_prime * C2_prime) + * math.sin(math.radians(delta_h_prime / 2.0)) + ) + + S_L = 1 + (0.015 * (L_bar - 50) ** 2) / math.sqrt(20 + (L_bar - 50) ** 2) + S_C = 1 + 0.045 * C_bar_prime + + T = ( + 1 + - 0.17 * math.cos(math.radians(H_bar_prime - 30)) + + 0.24 * math.cos(math.radians(2 * H_bar_prime)) + + 0.32 * math.cos(math.radians(3 * H_bar_prime + 6)) + - 0.20 * math.cos(math.radians(4 * H_bar_prime - 63)) + ) + + S_H = 1 + 0.015 * C_bar_prime * T + + delta_theta = 30 * math.exp(-(((H_bar_prime - 275) / 25) ** 2)) + R_C = 2 * math.sqrt(C_bar_prime**7 / (C_bar_prime**7 + 25**7)) + R_T = -R_C * math.sin(math.radians(2 * delta_theta)) + + kL = 1.0 + kC = 1.0 + kH = 1.0 + + delta_E = math.sqrt( + (delta_L_prime / (kL * S_L)) ** 2 + + (delta_C_prime / (kC * S_C)) ** 2 + + (delta_H_prime / (kH * S_H)) ** 2 + + R_T * (delta_C_prime / (kC * S_C)) * (delta_H_prime / (kH * S_H)) + ) + + return delta_E + + def test_color_accuracy(self, test_type): + """测试色准 - 使用手工实现的 ΔE 2000(应用 Gamma)""" + + # ========== 读取用户选择的 Gamma ========== + if test_type == "sdr_movie": + try: + target_gamma = float(self.sdr_gamma_type_var.get()) + except (ValueError, AttributeError): + target_gamma = 2.2 + + self.log_gui.log("=" * 50) + self.log_gui.log(f"开始测试色准(SDR Movie 标准 - 29色)") + self.log_gui.log(f"✓ 使用 Gamma: {target_gamma}") # ← 新增 + self.log_gui.log("=" * 50) + + elif test_type == "hdr_movie": + target_gamma = 2.4 # HDR 使用 PQ,但保留参考值 + + self.log_gui.log("=" * 50) + self.log_gui.log(f"开始测试色准(HDR Movie 标准 - 29色)") + self.log_gui.log(f"✓ 使用 Gamma: PQ (参考γ={target_gamma})") # ← 新增 + self.log_gui.log("=" * 50) + + else: # screen_module + target_gamma = 2.2 + + self.log_gui.log("=" * 50) + self.log_gui.log(f"开始测试色准(屏模组 标准 - 29色)") + self.log_gui.log(f"✓ 使用 Gamma: {target_gamma}") + self.log_gui.log("=" * 50) + + # 获取 29色名称 + color_names = self.config.get_accuracy_color_names() + + self.log_gui.log(f"✓ 将测试 {len(color_names)} 个色块") + self.log_gui.log(f" 色块分组:") + self.log_gui.log(f" 灰阶 (5个): {', '.join(color_names[:5])}") + self.log_gui.log(f" ColorChecker (18个): {', '.join(color_names[5:23])}") + self.log_gui.log(f" 饱和色 (6个): {', '.join(color_names[23:])}") + + self.log_gui.log("=" * 50) + self.log_gui.log("开始发送色准图案并采集数据...") + self.log_gui.log("=" * 50) + + # 发送 29色图案 + measured_data_list = self.send_fix_pattern("accuracy") + + if measured_data_list is None or len(measured_data_list) != 29: + self.log_gui.log(f"❌ 数据数量不匹配") + self.log_gui.log(f" 期望: 29 个") + self.log_gui.log( + f" 实际: {len(measured_data_list) if measured_data_list else 0} 个" + ) + return + + # 保存原始测量数据供单步调试使用 + self.results.add_intermediate_data("accuracy", "measured", measured_data_list) + + # ========== 计算 ΔE 2000(显示 Gamma)========== + self.log_gui.log("=" * 50) + self.log_gui.log(f"计算色准(ΔE 2000,Gamma {target_gamma})...") + self.log_gui.log("=" * 50) + + # 获取标准 xy 坐标 + standards = self.get_accuracy_color_standards(test_type) + + delta_e_values = [] + color_patches = [] + + for i, (name, measured_data) in enumerate(zip(color_names, measured_data_list)): + measured_x = measured_data[0] + measured_y = measured_data[1] + measured_lv = measured_data[2] + + standard_x, standard_y = standards.get(name, (0.3127, 0.3290)) + + delta_e = self.calculate_delta_e_2000( + measured_x, + measured_y, + measured_lv, + standard_x, + standard_y, + ) + + delta_e_values.append(delta_e) + color_patches.append(name) + + if delta_e < 3: + grade, icon = "优秀", "✓" + elif delta_e < 5: + grade, icon = "良好", "○" + else: + grade, icon = "偏差", "✗" + + self.log_gui.log( + f" [{i+1:2d}] {name:20s} ΔE={delta_e:5.2f} {icon} {grade}" + ) + + # ========== 统计 ========== + avg_delta_e_all = sum(delta_e_values) / len(delta_e_values) + max_delta_e_all = max(delta_e_values) + min_delta_e_all = min(delta_e_values) + + excellent_count_all = sum(1 for de in delta_e_values if de < 3) + good_count_all = sum(1 for de in delta_e_values if 3 <= de < 5) + poor_count_all = sum(1 for de in delta_e_values if de >= 5) + + delta_e_gray = delta_e_values[0:5] + avg_delta_e_gray = sum(delta_e_gray) / len(delta_e_gray) + + delta_e_colorchecker = delta_e_values[5:23] + avg_delta_e_colorchecker = sum(delta_e_colorchecker) / len(delta_e_colorchecker) + + delta_e_saturated = delta_e_values[23:29] + avg_delta_e_saturated = sum(delta_e_saturated) / len(delta_e_saturated) + + self.log_gui.log("=" * 50) + self.log_gui.log("色准统计(全 29色):") + self.log_gui.log("=" * 50) + self.log_gui.log(f" 平均 ΔE: {avg_delta_e_all:.2f}") + self.log_gui.log(f" 最大 ΔE: {max_delta_e_all:.2f}") + self.log_gui.log(f" 最小 ΔE: {min_delta_e_all:.2f}") + self.log_gui.log(f" 优秀 (ΔE<3): {excellent_count_all} 个") + self.log_gui.log(f" 良好 (3≤ΔE<5): {good_count_all} 个") + self.log_gui.log(f" 偏差 (ΔE≥5): {poor_count_all} 个") + + self.log_gui.log("") + self.log_gui.log("分组统计:") + self.log_gui.log(f" 灰阶 (5个): 平均 ΔE = {avg_delta_e_gray:.2f}") + self.log_gui.log( + f" ColorChecker (18个): 平均 ΔE = {avg_delta_e_colorchecker:.2f}" + ) + self.log_gui.log(f" 饱和色 (6个): 平均 ΔE = {avg_delta_e_saturated:.2f}") + + # ========== 保存测试结果 ========== + accuracy_data = { + "color_patches": color_patches, + "delta_e_values": delta_e_values, + "color_measurements": measured_data_list, + "avg_delta_e": avg_delta_e_all, + "max_delta_e": max_delta_e_all, + "min_delta_e": min_delta_e_all, + "excellent_count": excellent_count_all, + "good_count": good_count_all, + "poor_count": poor_count_all, + "avg_delta_e_gray": avg_delta_e_gray, + "avg_delta_e_colorchecker": avg_delta_e_colorchecker, + "avg_delta_e_saturated": avg_delta_e_saturated, + "target_gamma": target_gamma, + } + + self.results.set_test_item_result("accuracy", accuracy_data) + + # ========== 绘制图表 ========== + self.plot_accuracy(accuracy_data, test_type) + + self.log_gui.log("色准测试完成") + + def get_accuracy_color_standards(self, test_type): + """ + 获取色准测试的标准 xy 色度坐标(动态计算) + + Args: + test_type: 测试类型 ("sdr_movie" 或 "hdr_movie") + + Returns: + dict: {color_name: (x, y), ...} + """ + + # ========== RGB → xy 转换函数 ========== + def rgb_to_xy_srgb(r, g, b): + """sRGB (8bit) → CIE 1931 xy""" + # 1. 归一化到 0-1 + r, g, b = r / 255.0, g / 255.0, b / 255.0 + + # 2. sRGB Gamma 解码 + def gamma_decode(c): + if c <= 0.04045: + return c / 12.92 + else: + return ((c + 0.055) / 1.055) ** 2.4 + + r_linear = gamma_decode(r) + g_linear = gamma_decode(g) + b_linear = gamma_decode(b) + + # 3. sRGB → XYZ(D65 白点,IEC 61966-2-1 标准) + X = r_linear * 0.4124564 + g_linear * 0.3575761 + b_linear * 0.1804375 + Y = r_linear * 0.2126729 + g_linear * 0.7151522 + b_linear * 0.0721750 + Z = r_linear * 0.0193339 + g_linear * 0.1191920 + b_linear * 0.9503041 + + # 4. XYZ → xy + total = X + Y + Z + if total == 0: + return 0.3127, 0.3290 # D65 白点 + + x = X / total + y = Y / total + return x, y + + # ========== 你的 RGB 定义(29色)========== + SDR_COLOR_PATTERNS = [ + ("White", 255, 255, 255), + ("Gray 80", 230, 230, 230), + ("Gray 65", 209, 209, 209), + ("Gray 50", 186, 186, 186), + ("Gray 35", 158, 158, 158), + ("Dark Skin", 115, 82, 66), + ("Light Skin", 194, 150, 130), + ("Blue Sky", 94, 122, 156), + ("Foliage", 89, 107, 66), + ("Blue Flower", 130, 128, 176), + ("Bluish Green", 99, 189, 168), + ("Orange", 217, 120, 41), + ("Purplish Blue", 74, 92, 163), + ("Moderate Red", 194, 84, 97), + ("Purple", 92, 61, 107), + ("Yellow Green", 158, 186, 64), + ("Orange Yellow", 230, 161, 46), + ("Blue (Legacy)", 51, 61, 150), + ("Green (Legacy)", 71, 148, 71), + ("Red (Legacy)", 176, 48, 59), + ("Yellow (Legacy)", 237, 199, 33), + ("Magenta (Legacy)", 186, 84, 145), + ("Cyan (Legacy)", 0, 133, 163), + ("100% Red", 255, 0, 0), + ("100% Green", 0, 255, 0), + ("100% Blue", 0, 0, 255), + ("100% Cyan", 0, 255, 255), + ("100% Magenta", 255, 0, 255), + ("100% Yellow", 255, 255, 0), + ] + + # ========== 动态计算 xy 坐标 ========== + standards = {} + for name, r, g, b in SDR_COLOR_PATTERNS: + x, y = rgb_to_xy_srgb(r, g, b) + standards[name] = (x, y) + + return standards + + def calculate_gamut_coverage(self, results): + """计算色域覆盖率""" + area, coverage = pq_algorithm.calculate_gamut_coverage_DCIP3(results) + return area, coverage + + def calculate_gamma(self, results, max_index_fix, pattern_params=None): + """计算Gamma值,返回results + gamma + + Args: + results: 测量结果列表 + max_index_fix: 最大灰阶索引 + pattern_params: 8bit pattern参数,用于计算input_level(22293 Gamma数据对齐) + """ + results_with_gamma_list = pq_algorithm.calculate_gamma( + results, max_index_fix, pattern_params + ) + L_bar = pq_algorithm.calculate_L_bar(results) + return results_with_gamma_list, L_bar + + def calculate_color_accuracy(self, measured, standard): + """计算色差""" + # 使用简化的色差计算方法 + delta_E = {} + + for color in measured.keys(): + # 计算欧氏距离作为简化的色差 + dx = measured[color][0] - standard[color][0] + dy = measured[color][1] - standard[color][1] + delta_E[color] = np.sqrt(dx * dx + dy * dy) * 1000 # 放大1000倍便于显示 + + return delta_E + + def plot_gamut(self, results, coverage, test_type): + """绘制色域图 - 根据用户选择的参考标准动态计算覆盖率""" + + self.gamut_ax_xy.clear() + self.gamut_ax_uv.clear() + + # ==================== XY 图校准参数 ==================== + XY_ORIGIN_X = 20.55 + XY_ORIGIN_Y = 378.00 + XY_PIXELS_PER_X = 510.6818 + XY_PIXELS_PER_Y = 429.8844 + + # ==================== UV 图校准参数 ==================== + UV_ORIGIN_U = 26.91 + UV_ORIGIN_V = 377.16 + UV_PIXELS_PER_U = 615.7260 + UV_PIXELS_PER_V = 599.8432 + + # ========== ✅ 读取用户选择的参考标准 ========== + if test_type == "screen_module": + current_ref = self.screen_gamut_ref_var.get() + elif test_type == "sdr_movie": + current_ref = self.sdr_gamut_ref_var.get() + elif test_type == "hdr_movie": + current_ref = self.hdr_gamut_ref_var.get() + else: + current_ref = "DCI-P3" + + # ========== ✅✅✅ 根据参考标准重新计算覆盖率(XY 空间)========== + xy_coverage = coverage # 默认使用传入的值 + uv_coverage = 0.0 + + try: + # 提取前 3 个 RGB 点的 xy 坐标 + if len(results) >= 3: + xy_points = [[result[0], result[1]] for result in results[:3]] + + # 根据参考标准计算 XY 覆盖率 + if current_ref == "BT.2020": + _, xy_coverage = pq_algorithm.calculate_gamut_coverage_BT2020( + xy_points + ) + elif current_ref == "BT.709": + _, xy_coverage = pq_algorithm.calculate_gamut_coverage_BT709( + xy_points + ) + elif current_ref == "DCI-P3": + _, xy_coverage = pq_algorithm.calculate_gamut_coverage_DCIP3( + xy_points + ) + elif current_ref == "BT.601": + _, xy_coverage = pq_algorithm.calculate_gamut_coverage_BT601( + xy_points + ) + else: + self.log_gui.log(f"⚠️ 未知参考标准 '{current_ref}',使用 DCI-P3") + _, xy_coverage = pq_algorithm.calculate_gamut_coverage_DCIP3( + xy_points + ) + current_ref = "DCI-P3" + + self.log_gui.log( + f"✓ XY 空间覆盖率({current_ref}): {xy_coverage:.1f}%" + ) + + except Exception as e: + self.log_gui.log(f"⚠️ 重新计算 XY 覆盖率失败: {str(e)}") + xy_coverage = coverage # 回退到传入值 + # ================================================= + + # ========== 左图:CIE 1931 xy ========== + try: + img_xy = mpimg.imread(get_resource_path("assets/cie.png")) + h_xy, w_xy = img_xy.shape[:2] + + self.log_gui.log(f"加载 XY 色域图: {w_xy}x{h_xy}") + + self.gamut_ax_xy.imshow(img_xy, extent=[0, w_xy, h_xy, 0], aspect="equal") + self.gamut_ax_xy.set_xlim(0, w_xy) + self.gamut_ax_xy.set_ylim(h_xy, 0) + self.gamut_ax_xy.axis("off") + self.gamut_ax_xy.set_clip_on(False) + + def cie_xy_to_pixel(x, y): + """CIE xy → 像素坐标""" + px = XY_ORIGIN_X + x * XY_PIXELS_PER_X + py = XY_ORIGIN_Y - y * XY_PIXELS_PER_Y + return px, py + + if len(results) >= 3: + red_x, red_y = results[0][0], results[0][1] + green_x, green_y = results[1][0], results[1][1] + blue_x, blue_y = results[2][0], results[2][1] + + self.log_gui.log( + f"测量色域: R({red_x:.4f},{red_y:.4f}) " + f"G({green_x:.4f},{green_y:.4f}) B({blue_x:.4f},{blue_y:.4f})" + ) + + # ========== 绘制测量三角形 ========== + points = [ + cie_xy_to_pixel(red_x, red_y), + cie_xy_to_pixel(green_x, green_y), + cie_xy_to_pixel(blue_x, blue_y), + cie_xy_to_pixel(red_x, red_y), + ] + + xs = [p[0] for p in points] + ys = [p[1] for p in points] + + self.gamut_ax_xy.plot( + xs, + ys, + color="red", + linewidth=2.5, + marker="o", + markersize=10, + markerfacecolor="red", + markeredgecolor="white", + markeredgewidth=2, + label="测量色域", + zorder=10, + ) + + # ========== 标注 RGB 点 ========== + labels = ["R", "G", "B"] + coords = [(red_x, red_y), (green_x, green_y), (blue_x, blue_y)] + + for (x_cie, y_cie), label in zip(coords, labels): + px, py = cie_xy_to_pixel(x_cie, y_cie) + + # 自适应偏移 + if label == "R": + offset = (-60, -40) if x_cie > 0.6 else (0, -60) + elif label == "G": + offset = (0, -60) + else: # B + offset = (60, 40) + + self.gamut_ax_xy.annotate( + f"{label}\n({x_cie:.3f},{y_cie:.3f})", + xy=(px, py), + xytext=offset, + textcoords="offset points", + fontsize=9, + color="white", + fontweight="bold", + bbox=dict( + boxstyle="round,pad=0.5", + facecolor="red", + alpha=0.9, + edgecolor="white", + linewidth=2, + ), + arrowprops=dict(arrowstyle="->", color="red", lw=2), + zorder=11, + clip_on=False, + ) + + # ========== 绘制所有参考标准 ========== + # DCI-P3 + dcip3 = [ + (0.6800, 0.3200), + (0.2650, 0.6900), + (0.1500, 0.0600), + (0.6800, 0.3200), + ] + dcip3_px = [cie_xy_to_pixel(x, y) for x, y in dcip3] + self.gamut_ax_xy.plot( + [p[0] for p in dcip3_px], + [p[1] for p in dcip3_px], + color="blue", + linewidth=1.5, + linestyle="--", + marker="s", + markersize=6, + alpha=0.7, + label="DCI-P3", + zorder=5, + ) + + # BT.2020 + bt2020 = [ + (0.7080, 0.2920), + (0.1700, 0.7970), + (0.1310, 0.0460), + (0.7080, 0.2920), + ] + bt2020_px = [cie_xy_to_pixel(x, y) for x, y in bt2020] + self.gamut_ax_xy.plot( + [p[0] for p in bt2020_px], + [p[1] for p in bt2020_px], + color="green", + linewidth=1.5, + linestyle="-.", + marker="D", + markersize=5, + alpha=0.7, + label="BT.2020", + zorder=4, + ) + + # BT.709 + bt709 = [ + (0.6400, 0.3300), + (0.3000, 0.6000), + (0.1500, 0.0600), + (0.6400, 0.3300), + ] + bt709_px = [cie_xy_to_pixel(x, y) for x, y in bt709] + self.gamut_ax_xy.plot( + [p[0] for p in bt709_px], + [p[1] for p in bt709_px], + color="gray", + linewidth=1.2, + linestyle=":", + marker="^", + markersize=5, + alpha=0.6, + label="BT.709", + zorder=3, + ) + + # BT.601(仅 SDR 测试) + if test_type == "sdr_movie": + bt601 = [ + (0.6300, 0.3400), + (0.3100, 0.5950), + (0.1550, 0.0700), + (0.6300, 0.3400), + ] + bt601_px = [cie_xy_to_pixel(x, y) for x, y in bt601] + self.gamut_ax_xy.plot( + [p[0] for p in bt601_px], + [p[1] for p in bt601_px], + color="purple", + linewidth=1.2, + linestyle="-", + marker="o", + markersize=5, + alpha=0.6, + label="BT.601", + zorder=3, + ) + + # ========== ✅ XY 覆盖率标注(使用重新计算的值)========== + self.gamut_ax_xy.text( + w_xy * 0.85, + h_xy * 0.92, + f"参考: {current_ref}\n覆盖率: {xy_coverage:.1f}%", + ha="right", + va="bottom", + fontsize=11, + fontweight="bold", + color="red", + bbox=dict( + boxstyle="round,pad=0.5", + facecolor="white", + alpha=0.95, + edgecolor="red", + linewidth=2, + ), + zorder=12, + ) + + # 图例 + self.gamut_ax_xy.legend( + loc="upper right", + fontsize=7, + framealpha=0.95, + edgecolor="black", + fancybox=True, + ) + + except Exception as e: + self.log_gui.log(f"XY 图绘制失败: {str(e)}") + import traceback + + self.log_gui.log(traceback.format_exc()) + + # ========== 右图:CIE 1976 u'v' ========== + try: + img_uv = mpimg.imread(get_resource_path("assets/cie_uv.png")) + h_uv, w_uv = img_uv.shape[:2] + + self.log_gui.log(f"加载 UV 色域图: {w_uv}x{h_uv}") + + self.gamut_ax_uv.imshow(img_uv, extent=[0, w_uv, h_uv, 0], aspect="equal") + self.gamut_ax_uv.set_xlim(0, w_uv) + self.gamut_ax_uv.set_ylim(h_uv, 0) + self.gamut_ax_uv.axis("off") + self.gamut_ax_uv.set_clip_on(False) + + def cie_uv_to_pixel(u, v): + """CIE u'v' → 像素坐标""" + px = UV_ORIGIN_U + u * UV_PIXELS_PER_U + py = UV_ORIGIN_V - v * UV_PIXELS_PER_V + return px, py + + if len(results) >= 3: + # 只取前 3 个 RGB 点 + rgb_results = results[:3] + + # 转换为 u'v' 坐标 + def xy_to_uv(x, y): + """xy → u'v' 转换""" + denom = -2 * x + 12 * y + 3 + if abs(denom) < 1e-10: + return 0, 0 + u = (4 * x) / denom + v = (9 * y) / denom + return u, v + + uv_coords = [ + [u, v] for u, v in [xy_to_uv(r[0], r[1]) for r in rgb_results] + ] + + self.log_gui.log(f"UV 坐标: {uv_coords}") + + # ========== ✅✅✅ 计算 u'v' 覆盖率(使用参考标准)========== + try: + uv_coverage = pq_algorithm.calculate_uv_gamut_coverage( + uv_coords, reference=current_ref # ← 传入用户选择的参考标准 + ) + self.log_gui.log( + f"✓ UV 空间覆盖率({current_ref}): {uv_coverage:.1f}%" + ) + except Exception as e: + self.log_gui.log(f"⚠️ 计算 UV 覆盖率失败: {str(e)}") + uv_coverage = 0.0 + # ================================================= + + # ========== 绘制测量三角形 ========== + uv_coords_plot = uv_coords + [uv_coords[0]] + points_uv = [cie_uv_to_pixel(u, v) for u, v in uv_coords_plot] + xs_uv = [p[0] for p in points_uv] + ys_uv = [p[1] for p in points_uv] + + self.gamut_ax_uv.plot( + xs_uv, + ys_uv, + color="red", + linewidth=2.5, + marker="o", + markersize=10, + markerfacecolor="red", + markeredgecolor="white", + markeredgewidth=2, + label="测量色域", + zorder=10, + ) + + # ========== 标注 RGB 点 ========== + labels = ["R", "G", "B"] + for (u, v), label in zip(uv_coords, labels): + px, py = cie_uv_to_pixel(u, v) + + # 自适应偏移 + if label == "R": + if u > 0.42 and v > 0.50: + offset = (-70, 20) + elif u > 0.45: + offset = (30, 50) + else: + offset = (50, 45) + elif label == "G": + offset = (0, -60) + else: # B + offset = (60, 40) + + self.gamut_ax_uv.annotate( + f"{label}\n({u:.3f},{v:.3f})", + xy=(px, py), + xytext=offset, + textcoords="offset points", + fontsize=9, + color="white", + fontweight="bold", + bbox=dict( + boxstyle="round,pad=0.5", + facecolor="red", + alpha=0.9, + edgecolor="white", + linewidth=2, + ), + arrowprops=dict(arrowstyle="->", color="red", lw=2), + zorder=11, + clip_on=False, + ) + + # ========== DCI-P3 参考(蓝色)========== + dcip3_uv = [ + [0.4970, 0.5260], # Red + [0.0999, 0.5780], # Green + [0.1754, 0.1576], # Blue + [0.4970, 0.5260], # 闭合 + ] + dcip3_uv_px = [cie_uv_to_pixel(u, v) for u, v in dcip3_uv] + + self.gamut_ax_uv.plot( + [p[0] for p in dcip3_uv_px], + [p[1] for p in dcip3_uv_px], + color="blue", + linewidth=1.5, + linestyle="--", + marker="s", + markersize=6, + alpha=0.7, + label="DCI-P3", + zorder=5, + ) + + # ========== BT.2020 参考(绿色)========== + bt2020_uv = [ + [0.5566, 0.5165], # Red + [0.0556, 0.5868], # Green + [0.1593, 0.1258], # Blue + [0.5566, 0.5165], # 闭合 + ] + bt2020_uv_px = [cie_uv_to_pixel(u, v) for u, v in bt2020_uv] + + self.gamut_ax_uv.plot( + [p[0] for p in bt2020_uv_px], + [p[1] for p in bt2020_uv_px], + color="green", + linewidth=1.5, + linestyle="-.", + marker="D", + markersize=5, + alpha=0.7, + label="BT.2020", + zorder=4, + ) + + # ========== BT.709 参考(灰色)========== + bt709_uv = [ + [0.4507, 0.5229], # Red + [0.1250, 0.5625], # Green + [0.1754, 0.1576], # Blue + [0.4507, 0.5229], # 闭合 + ] + bt709_uv_px = [cie_uv_to_pixel(u, v) for u, v in bt709_uv] + + self.gamut_ax_uv.plot( + [p[0] for p in bt709_uv_px], + [p[1] for p in bt709_uv_px], + color="gray", + linewidth=1.2, + linestyle=":", + marker="^", + markersize=5, + alpha=0.6, + label="BT.709", + zorder=3, + ) + + # ========== BT.601 参考(紫色)- 仅 SDR 测试显示 ========== + if test_type == "sdr_movie": + bt601_uv = [ + [0.4510, 0.5236], # Red + [0.1291, 0.5606], # Green + [0.1787, 0.1610], # Blue + [0.4510, 0.5236], # 闭合 + ] + bt601_uv_px = [cie_uv_to_pixel(u, v) for u, v in bt601_uv] + + self.gamut_ax_uv.plot( + [p[0] for p in bt601_uv_px], + [p[1] for p in bt601_uv_px], + color="purple", + linewidth=1.2, + linestyle="-", + marker="o", + markersize=5, + alpha=0.6, + label="BT.601", + zorder=3, + ) + + # ========== ✅ UV 覆盖率标注(使用动态计算的值)========== + self.gamut_ax_uv.text( + w_uv * 0.85, + h_uv * 0.92, + f"参考: {current_ref}\n覆盖率: {uv_coverage:.1f}%", # ← 使用动态计算的值 + ha="right", + va="bottom", + fontsize=11, + fontweight="bold", + color="red", + bbox=dict( + boxstyle="round,pad=0.5", + facecolor="white", + alpha=0.95, + edgecolor="red", + linewidth=2, + ), + zorder=12, + ) + + # 图例 + self.gamut_ax_uv.legend( + loc="upper right", + fontsize=7, + framealpha=0.95, + edgecolor="black", + fancybox=True, + ) + + except Exception as e: + self.log_gui.log(f"UV 图绘制失败: {str(e)}") + import traceback + + self.log_gui.log(traceback.format_exc()) + + # ========== 总标题 ========== + test_type_name = self.get_test_type_name(test_type) + self.gamut_fig.suptitle( + f"{test_type_name} - 色域测试", fontsize=12, y=0.98, fontweight="bold" + ) + + self.gamut_canvas.draw() + self.chart_notebook.select(0) + + self.log_gui.log("色域图绘制完成") + + def plot_gamma(self, L_bar, results_with_gamma_list, target_gamma, test_type): + """绘制Gamma曲线 + 数据表格(包含实测亮度)""" + # ========== 1. 清空并重置左侧曲线 ========== + self.gamma_ax.clear() + self.gamma_ax.set_xlim(0, 105) + self.gamma_ax.set_ylim(0, 1.1) + self.gamma_ax.set_xlabel("灰阶 (%)", fontsize=10) + self.gamma_ax.set_ylabel("L_bar", fontsize=10) + self.gamma_ax.grid(True, linestyle="--", alpha=0.3) + self.gamma_ax.tick_params(labelsize=9) + + # 生成横坐标(灰阶百分比) + x_values = np.linspace(0, 100, len(L_bar)) + + # 反转 L_bar(确保从左到右是 0% → 100%) + if len(L_bar) > 1 and L_bar[0] > L_bar[-1]: + L_bar = L_bar[::-1] + results_with_gamma_list = results_with_gamma_list[::-1] + + # 计算平均Gamma + gamma_values = [] + for item in results_with_gamma_list: + if isinstance(item, (list, tuple)) and len(item) >= 4: + gamma = item[3] + if 0.5 < gamma < 5.0: + gamma_values.append(gamma) + + avg_gamma = np.mean(gamma_values) if gamma_values else target_gamma + + # 绘制实测曲线 + self.gamma_ax.plot( + x_values, + L_bar, + "b-o", + label=f"实测 (平均γ={avg_gamma:.2f})", + linewidth=2, + markersize=4, + zorder=5, + ) + + # 绘制理想曲线(使用 target_gamma) + ideal_L_bar = [(x / 100) ** target_gamma for x in x_values] + self.gamma_ax.plot( + x_values, + ideal_L_bar, + "r--", + label=f"理想 (γ={target_gamma})", # ← 显示实际的 target_gamma + linewidth=2, + alpha=0.7, + zorder=3, + ) + + # 图例 + self.gamma_ax.legend(fontsize=9, loc="upper left", framealpha=0.95) + + # ========== 2. 清空并绘制右侧表格 ========== + self.gamma_table_ax.clear() + self.gamma_table_ax.axis("off") + + # 构建表格数据(4列) + table_data = [["灰阶", "实测亮度\n(cd/m²)", "L_bar", "Gamma"]] + + for i, (x_val, L_val, result) in enumerate( + zip(x_values, L_bar, results_with_gamma_list) + ): + # 提取实测亮度 + if isinstance(result, (list, tuple)) and len(result) >= 3: + measured_lv = result[2] + measured_lv_str = f"{measured_lv:.2f}" + else: + measured_lv_str = "--" + + # 提取 Gamma + if isinstance(result, (list, tuple)) and len(result) >= 4: + gamma = result[3] + if gamma < 0.5 or gamma > 5.0: + gamma_str = "--" + else: + gamma_str = f"{gamma:.2f}" + else: + gamma_str = "--" + + table_data.append( + [ + f"{x_val:.0f}%", + measured_lv_str, # ← 实测亮度 + f"{L_val:.3f}", + gamma_str, + ] + ) + + # 绘制表格(4列) + table = self.gamma_table_ax.table( + cellText=table_data, + cellLoc="center", + loc="center", + colWidths=[0.18, 0.28, 0.27, 0.27], + ) + + # 美化表格 + table.auto_set_font_size(False) + table.set_fontsize(7.5) + table.scale(1, 1.5) + + # 表头样式 + for i in range(4): # ← 4列 + cell = table[(0, i)] + cell.set_facecolor("#4472C4") + cell.set_text_props(weight="bold", color="white") + + # 数据行交替颜色 + for i in range(1, len(table_data)): + for j in range(4): # ← 4列 + cell = table[(i, j)] + if i % 2 == 0: + cell.set_facecolor("#E7E6E6") + else: + cell.set_facecolor("#FFFFFF") + + # ========== 3. 总标题 ========== + test_type_name = self.get_test_type_name(test_type) + self.gamma_fig.suptitle( + f"{test_type_name} - Gamma曲线", + fontsize=12, + y=0.98, + fontweight="bold", + ) + + # ========== 4. 绘制到画布 ========== + self.gamma_canvas.draw() + self.chart_notebook.select(1) + + self.log_gui.log("Gamma曲线 + 数据表格绘制完成") + + def plot_eotf(self, L_bar, results_with_eotf_list, test_type): + """绘制 EOTF 曲线 + 数据表格(HDR 专用,包含实测亮度)""" + # ========== 1. 清空并重置左侧曲线 ========== + self.eotf_ax.clear() + self.eotf_ax.set_xlim(0, 105) + self.eotf_ax.set_ylim(0, 1.1) + self.eotf_ax.set_xlabel("灰阶 (%)", fontsize=10) + self.eotf_ax.set_ylabel("L_bar", fontsize=10) + self.eotf_ax.grid(True, linestyle="--", alpha=0.3) + self.eotf_ax.tick_params(labelsize=9) + + # 生成横坐标(灰阶百分比) + x_values = np.linspace(0, 100, len(L_bar)) + + # 反转 L_bar + if len(L_bar) > 1 and L_bar[0] > L_bar[-1]: + L_bar = L_bar[::-1] + results_with_eotf_list = results_with_eotf_list[::-1] + + # 计算平均 EOTF Gamma + eotf_values = [] + for item in results_with_eotf_list: + if isinstance(item, (list, tuple)) and len(item) >= 4: + eotf = item[3] + if 0.5 < eotf < 5.0: + eotf_values.append(eotf) + + avg_eotf = np.mean(eotf_values) if eotf_values else 2.2 + + # 绘制实测曲线 + self.eotf_ax.plot( + x_values, + L_bar, + "b-o", + label=f"实测 (平均γ={avg_eotf:.2f})", + linewidth=2, + markersize=4, + zorder=5, + ) + + # 绘制 PQ (ST.2084) 理想曲线 + pq_L_bar = self.calculate_pq_curve(x_values) + self.eotf_ax.plot( + x_values, + pq_L_bar, + "r--", + label="理想 PQ (ST.2084)", + linewidth=2, + alpha=0.7, + zorder=3, + ) + + # 图例 + self.eotf_ax.legend(fontsize=9, loc="upper left", framealpha=0.95) + + # ========== 2. 清空并绘制右侧表格 ========== + self.eotf_table_ax.clear() + self.eotf_table_ax.axis("off") + + # 构建表格数据(4列) + table_data = [["灰阶", "实测亮度\n(cd/m²)", "L_bar", "EOTF γ"]] + + for i, (x_val, L_val, result) in enumerate( + zip(x_values, L_bar, results_with_eotf_list) + ): + # 提取实测亮度 + if isinstance(result, (list, tuple)) and len(result) >= 3: + measured_lv = result[2] + measured_lv_str = f"{measured_lv:.2f}" + else: + measured_lv_str = "--" + + # 提取 EOTF + if isinstance(result, (list, tuple)) and len(result) >= 4: + eotf = result[3] + if eotf < 0.5 or eotf > 5.0: + eotf_str = "--" + else: + eotf_str = f"{eotf:.2f}" + else: + eotf_str = "--" + + table_data.append( + [ + f"{x_val:.0f}%", + measured_lv_str, # ← 实测亮度 + f"{L_val:.3f}", + eotf_str, + ] + ) + + # 绘制表格(4列) + table = self.eotf_table_ax.table( + cellText=table_data, + cellLoc="center", + loc="center", + colWidths=[0.18, 0.28, 0.27, 0.27], + ) + + # 美化表格 + table.auto_set_font_size(False) + table.set_fontsize(7.5) + table.scale(1, 1.5) + + # 表头样式 + for i in range(4): # ← 4列 + cell = table[(0, i)] + cell.set_facecolor("#4472C4") + cell.set_text_props(weight="bold", color="white") + + # 数据行交替颜色 + for i in range(1, len(table_data)): + for j in range(4): # ← 4列 + cell = table[(i, j)] + if i % 2 == 0: + cell.set_facecolor("#E7E6E6") + else: + cell.set_facecolor("#FFFFFF") + + # ========== 3. 总标题 ========== + test_type_name = self.get_test_type_name(test_type) + self.eotf_fig.suptitle( + f"{test_type_name} - EOTF 曲线(PQ ST.2084)", + fontsize=12, + y=0.98, + fontweight="bold", + ) + + # ========== 4. 绘制到画布 ========== + self.eotf_canvas.draw() + + # 选中 EOTF Tab + try: + eotf_tab_id = str(self.eotf_chart_frame) + current_tabs = list(self.chart_notebook.tabs()) + if eotf_tab_id in current_tabs: + eotf_index = current_tabs.index(eotf_tab_id) + self.chart_notebook.select(eotf_index) + except: + pass + + self.log_gui.log("EOTF 曲线 + 数据表格绘制完成") + + def calculate_pq_curve(self, gray_levels): + """计算 PQ (ST.2084) EOTF 理想曲线 + + Args: + gray_levels: 灰阶百分比数组 (0-100) + + Returns: + L_bar: 归一化亮度数组 (0-1) + """ + # PQ 曲线参数(ITU-R BT.2100 标准) + m1 = 0.1593017578125 # = 2610 / 16384 + m2 = 78.84375 # = 78.84375 + c1 = 0.8359375 # = 3424 / 4096 + c2 = 18.8515625 # = 2413 / 128 + c3 = 18.6875 # = 2392 / 128 + + L_bar = [] + for gray in gray_levels: + # 归一化灰阶(0-1) + V = gray / 100.0 + + if V <= 0: + L_bar.append(0) + else: + # PQ 反向 EOTF 计算 + V_pow = np.power(V, 1 / m2) + numerator = max(V_pow - c1, 0) + denominator = c2 - c3 * V_pow + + if denominator > 0: + L = np.power(numerator / denominator, 1 / m1) + else: + L = 0 + + L_bar.append(L) + + return np.array(L_bar) + + def plot_cct(self, test_type): + """绘制 x 和 y 坐标分离图 - 每个点标注纵坐标值""" + self.cct_fig.clear() + + gray_data = self.results.get_intermediate_data("shared", "gray") + if not gray_data: + gray_data = self.results.get_intermediate_data("cct", "gray") + + if not gray_data or len(gray_data) < 2: + self.log_gui.log("⚠️ 无 xy 数据可用") + ax = self.cct_fig.add_subplot(111) + ax.text( + 0.5, + 0.5, + "无可用数据", + ha="center", + va="center", + fontsize=14, + color="red", + ) + ax.axis("off") + self.cct_canvas.draw() + return + + x_measured = [data[0] for data in gray_data] + y_measured = [data[1] for data in gray_data] + + # 反转数据顺序(从暗到亮) + x_measured = x_measured[::-1] + y_measured = y_measured[::-1] + + # 去掉第一个点 + x_measured = x_measured[1:] + y_measured = y_measured[1:] + + # 重新生成灰阶坐标 + total_points = len(gray_data) + grayscale = np.linspace(100 / total_points, 100, len(x_measured)) + + self.log_gui.log(f"✓ 已移除第一个数据点,当前数据点数: {len(x_measured)}") + self.log_gui.log(f" x范围: {min(x_measured):.6f} - {max(x_measured):.6f}") + self.log_gui.log(f" y范围: {min(y_measured):.6f} - {max(y_measured):.6f}") + + # ========== 根据测试类型读取对应参数 ========== + if test_type == "sdr_movie": + try: + x_ideal = float(self.sdr_cct_x_ideal_var.get()) + x_tolerance = float(self.sdr_cct_x_tolerance_var.get()) + y_ideal = float(self.sdr_cct_y_ideal_var.get()) + y_tolerance = float(self.sdr_cct_y_tolerance_var.get()) + self.log_gui.log("✓ 使用 SDR 色度参数") + except: + x_ideal = 0.3127 + x_tolerance = 0.003 + y_ideal = 0.3290 + y_tolerance = 0.003 + self.log_gui.log("⚠️ SDR 参数读取失败,使用默认值") + elif test_type == "hdr_movie": + try: + x_ideal = float(self.hdr_cct_x_ideal_var.get()) + x_tolerance = float(self.hdr_cct_x_tolerance_var.get()) + y_ideal = float(self.hdr_cct_y_ideal_var.get()) + y_tolerance = float(self.hdr_cct_y_tolerance_var.get()) + self.log_gui.log("✓ 使用 HDR 色度参数") + except: + x_ideal = 0.3127 + x_tolerance = 0.003 + y_ideal = 0.3290 + y_tolerance = 0.003 + self.log_gui.log("⚠️ HDR 参数读取失败,使用默认值") + else: # screen_module + try: + x_ideal = float(self.cct_x_ideal_var.get()) + x_tolerance = float(self.cct_x_tolerance_var.get()) + y_ideal = float(self.cct_y_ideal_var.get()) + y_tolerance = float(self.cct_y_tolerance_var.get()) + self.log_gui.log("✓ 使用屏模组色度参数") + except: + x_ideal = 0.306 + x_tolerance = 0.003 + y_ideal = 0.318 + y_tolerance = 0.003 + self.log_gui.log("⚠️ 屏模组参数读取失败,使用默认值") + + x_low = x_ideal - x_tolerance + x_high = x_ideal + x_tolerance + y_low = y_ideal - y_tolerance + y_high = y_ideal + y_tolerance + + self.log_gui.log(f"✓ 用户设置参数:") + self.log_gui.log(f" x-ideal={x_ideal:.4f}, tolerance={x_tolerance:.4f}") + self.log_gui.log(f" x范围: [{x_low:.4f}, {x_high:.4f}]") + self.log_gui.log(f" y-ideal={y_ideal:.4f}, tolerance={y_tolerance:.4f}") + self.log_gui.log(f" y范围: [{y_low:.4f}, {y_high:.4f}]") + + # 为所有测试类型创建子图 + ax1 = self.cct_fig.add_subplot(211) + ax2 = self.cct_fig.add_subplot(212) + + # ========== 上图:x coordinates ========== + ax1.plot( + grayscale, + x_measured, + "b-o", + label="屏本体", + linewidth=2, + markersize=4, + zorder=5, + ) + + # 为每个点添加数值标注(x 坐标) + for i, (gs, x_val) in enumerate(zip(grayscale, x_measured)): + ax1.annotate( + f"{x_val:.5f}", # 显示 5 位小数 + xy=(gs, x_val), + xytext=(0, 8), # 向上偏移 8 点 + textcoords="offset points", + ha="center", + va="bottom", + fontsize=7, + color="blue", + bbox=dict( + boxstyle="round,pad=0.2", + facecolor="white", + edgecolor="blue", + alpha=0.8, + linewidth=0.5, + ), + ) + + # 绘制完整的参考线 + full_grayscale = np.linspace(0, 100, 100) + ax1.axhline( + y=x_ideal, + color="green", + linestyle="--", + linewidth=1.5, + label=f"x-ideal ({x_ideal:.4f})", + zorder=3, + ) + ax1.axhline( + y=x_low, + color="red", + linestyle=":", + linewidth=1, + alpha=0.7, + label=f"x-low ({x_low:.4f})", + zorder=2, + ) + ax1.axhline( + y=x_high, + color="red", + linestyle=":", + linewidth=1, + alpha=0.7, + label=f"x-high ({x_high:.4f})", + zorder=2, + ) + ax1.fill_between( + full_grayscale, x_low, x_high, alpha=0.15, color="blue", zorder=1 + ) + + ax1.set_xlabel("灰阶 (%)", fontsize=9) + ax1.set_ylabel("CIE x", fontsize=9) + ax1.grid(True, linestyle="--", alpha=0.3) + ax1.tick_params(labelsize=8) + ax1.set_xlim(0, 105) + + # 纵坐标范围由用户参数控制 + x_min_data = min(x_measured) + x_max_data = max(x_measured) + data_range_x = x_max_data - x_min_data + + self.log_gui.log(f" x数据波动: {data_range_x:.6f}") + + range_span = x_tolerance * 2 + margin_ratio = 0.20 # ← 增大边距以容纳标注 + extra_margin = range_span * margin_ratio + + final_y_min = min(x_min_data, x_low) - extra_margin + final_y_max = max(x_max_data, x_high) + extra_margin + + if x_min_data >= x_low and x_max_data <= x_high: + self.log_gui.log(f" x数据在tolerance范围内,使用tolerance范围显示") + final_y_min = x_low - extra_margin + final_y_max = x_high + extra_margin + else: + self.log_gui.log(f" x数据超出tolerance范围,扩展显示范围") + + ax1.set_ylim(final_y_min, final_y_max) + self.log_gui.log( + f" x轴显示范围: {final_y_min:.6f} - {final_y_max:.6f} (跨度: {final_y_max - final_y_min:.6f})" + ) + + # ========== 下图:y coordinates ========== + ax2.plot( + grayscale, + y_measured, + "r-o", + label="屏本体", + linewidth=2, + markersize=4, + zorder=5, + ) + + # 为每个点添加数值标注(y 坐标) + for i, (gs, y_val) in enumerate(zip(grayscale, y_measured)): + ax2.annotate( + f"{y_val:.5f}", # 显示 5 位小数 + xy=(gs, y_val), + xytext=(0, 8), # 向上偏移 8 点 + textcoords="offset points", + ha="center", + va="bottom", + fontsize=7, + color="red", + bbox=dict( + boxstyle="round,pad=0.2", + facecolor="white", + edgecolor="red", + alpha=0.8, + linewidth=0.5, + ), + ) + + ax2.axhline( + y=y_ideal, + color="green", + linestyle="--", + linewidth=1.5, + label=f"y-ideal ({y_ideal:.4f})", + zorder=3, + ) + ax2.axhline( + y=y_low, + color="orange", + linestyle=":", + linewidth=1, + alpha=0.7, + label=f"y-low ({y_low:.4f})", + zorder=2, + ) + ax2.axhline( + y=y_high, + color="orange", + linestyle=":", + linewidth=1, + alpha=0.7, + label=f"y-high ({y_high:.4f})", + zorder=2, + ) + ax2.fill_between( + full_grayscale, y_low, y_high, alpha=0.15, color="orange", zorder=1 + ) + + ax2.set_xlabel("灰阶 (%)", fontsize=9) + ax2.set_ylabel("CIE y", fontsize=9) + ax2.grid(True, linestyle="--", alpha=0.3) + ax2.tick_params(labelsize=8) + ax2.set_xlim(0, 105) + + # 纵坐标范围由用户参数控制 + y_min_data = min(y_measured) + y_max_data = max(y_measured) + data_range_y = y_max_data - y_min_data + + self.log_gui.log(f" y数据波动: {data_range_y:.6f}") + + range_span = y_tolerance * 2 + extra_margin = range_span * margin_ratio + + final_y_min = min(y_min_data, y_low) - extra_margin + final_y_max = max(y_max_data, y_high) + extra_margin + + if y_min_data >= y_low and y_max_data <= y_high: + self.log_gui.log(f" y数据在tolerance范围内,使用tolerance范围显示") + final_y_min = y_low - extra_margin + final_y_max = y_high + extra_margin + else: + self.log_gui.log(f" y数据超出tolerance范围,扩展显示范围") + + ax2.set_ylim(final_y_min, final_y_max) + self.log_gui.log( + f" y轴显示范围: {final_y_min:.6f} - {final_y_max:.6f} (跨度: {final_y_max - final_y_min:.6f})" + ) + + # ========== 总标题 - 统一格式(去掉统计信息)========== + test_type_name = self.get_test_type_name(test_type) + + self.cct_fig.suptitle( + f"{test_type_name} - 色度一致性测试", + fontsize=12, + y=0.98, + fontweight="bold", + ) + + self.cct_fig.subplots_adjust( + left=0.12, + right=0.82, + top=0.92, + bottom=0.08, + hspace=0.30, + ) + + ax1.legend( + fontsize=7, loc="center left", bbox_to_anchor=(1.05, 0.5), framealpha=1.0 + ) + ax2.legend( + fontsize=7, loc="center left", bbox_to_anchor=(1.05, 0.5), framealpha=1.0 + ) + + self.cct_canvas.draw() + self.chart_notebook.select(2) + + self.log_gui.log("✓ xy 色度坐标图绘制完成") + + def plot_contrast(self, contrast_data, test_type): + """绘制对比度测试结果 - 固定布局版本""" + # 清空并重置 + self.contrast_ax.clear() + self.contrast_ax.set_xlim(0, 1) + self.contrast_ax.set_ylim(0, 1) + self.contrast_ax.axis("off") + + # 强制重置布局 + self.contrast_fig.subplots_adjust( + left=0.02, + right=0.98, + top=0.90, + bottom=0.02, + ) + + max_lum = contrast_data["max_luminance"] + min_lum = contrast_data["min_luminance"] + contrast = contrast_data["contrast_ratio"] + + # 确定等级和颜色 + if contrast >= 5000: + grade, grade_color = "优秀", "#4CAF50" + elif contrast >= 3000: + grade, grade_color = "良好", "#8BC34A" + elif contrast >= 1000: + grade, grade_color = "合格", "#FFC107" + else: + grade, grade_color = "不合格", "#F44336" + + test_type_name = self.get_test_type_name(test_type) + + # ========== 顶部标题 - 统一格式 ========== + self.contrast_fig.suptitle( + f"{test_type_name} - 对比度测试", + fontsize=12, + y=0.98, + fontweight="bold", + ) + + # ========== 中央大对比度卡片 ========== + from matplotlib.patches import Rectangle + + center_card = Rectangle( + (0.15, 0.48), + 0.70, + 0.32, + transform=self.contrast_ax.transAxes, + facecolor=grade_color, + edgecolor="black", + linewidth=2.5, + alpha=0.15, + ) + self.contrast_ax.add_patch(center_card) + + # 对比度数值 + self.contrast_ax.text( + 0.5, + 0.65, + f"{contrast:.0f} : 1", + ha="center", + va="center", + fontsize=36, + fontweight="bold", + color=grade_color, + transform=self.contrast_ax.transAxes, + ) + + # 等级标签 + self.contrast_ax.text( + 0.5, + 0.51, + f"等级: {grade}", + ha="center", + va="center", + fontsize=12, + fontweight="bold", + color=grade_color, + transform=self.contrast_ax.transAxes, + ) + + # ========== 两个信息卡片(缩小)========== + card_width = 0.32 + card_height = 0.22 + card_y = 0.12 + + gap = 0.05 + total_width = card_width * 2 + gap + start_x = (1 - total_width) / 2 + + cards_data = [ + { + "x": start_x, + "title": "白场亮度", + "value": f"{max_lum:.2f}", + "unit": "cd/m²", + "color": "#E3F2FD", + "edge_color": "#2196F3", + }, + { + "x": start_x + card_width + gap, + "title": "黑场亮度", + "value": f"{min_lum:.4f}", + "unit": "cd/m²", + "color": "#F3E5F5", + "edge_color": "#9C27B0", + }, + ] + + for card in cards_data: + # 绘制卡片背景 + rect = Rectangle( + (card["x"], card_y), + card_width, + card_height, + transform=self.contrast_ax.transAxes, + facecolor=card["color"], + edgecolor=card["edge_color"], + linewidth=2, + ) + self.contrast_ax.add_patch(rect) + + # 标题 + self.contrast_ax.text( + card["x"] + card_width / 2, + card_y + card_height - 0.03, + card["title"], + ha="center", + va="top", + fontsize=10, + fontweight="bold", + transform=self.contrast_ax.transAxes, + ) + + # 数值 + self.contrast_ax.text( + card["x"] + card_width / 2, + card_y + card_height / 2, + card["value"], + ha="center", + va="center", + fontsize=16, + fontweight="bold", + transform=self.contrast_ax.transAxes, + ) + + # 单位 + self.contrast_ax.text( + card["x"] + card_width / 2, + card_y + 0.03, + card["unit"], + ha="center", + va="bottom", + fontsize=9, + color="gray", + transform=self.contrast_ax.transAxes, + ) + + self.contrast_canvas.draw() + self.chart_notebook.select(3) + + def plot_accuracy(self, accuracy_data, test_type): + """绘制色准测试结果 - 29色显示 - 简洁版布局(显示 Gamma)""" + self.accuracy_ax.clear() + self.accuracy_ax.set_xlim(0, 1) + self.accuracy_ax.set_ylim(0, 1) + self.accuracy_ax.axis("off") + + self.accuracy_fig.subplots_adjust( + left=0.05, + right=0.95, + top=0.95, + bottom=0.02, + ) + + # 获取色准数据 + color_patches = accuracy_data.get("color_patches", []) + delta_e_values = accuracy_data.get("delta_e_values", []) + avg_delta_e = accuracy_data.get("avg_delta_e", 0) + max_delta_e = accuracy_data.get("max_delta_e", 0) + min_delta_e = accuracy_data.get("min_delta_e", 0) + excellent_count = accuracy_data.get("excellent_count", 0) + good_count = accuracy_data.get("good_count", 0) + poor_count = accuracy_data.get("poor_count", 0) + + # 获取 Gamma 值 + target_gamma = accuracy_data.get("target_gamma", 2.2) + + test_type_name = self.get_test_type_name(test_type) + + # ========== 标题(动态显示 Gamma)========== + if test_type == "sdr_movie": + title = f"{test_type_name} - 色准测试(全 29色 | Gamma {target_gamma})" + elif test_type == "hdr_movie": + title = f"{test_type_name} - 色准测试(全 29色 | PQ EOTF)" + else: # screen_module + title = f"{test_type_name} - 色准测试(全 29色 | Gamma {target_gamma})" + + self.accuracy_fig.suptitle( + title, + fontsize=11, + y=0.98, + fontweight="bold", + ) + + # ========== 29色:6行5列布局 ========== + cols = 5 + rows = 6 + + patch_width = 0.135 + patch_height = 0.085 + x_start = 0.08 + y_start = 0.90 + x_gap = 0.035 + y_gap = 0.050 + + from matplotlib.patches import Rectangle + + # ========== 绘制色块 ========== + for i, (color_name, delta_e) in enumerate(zip(color_patches, delta_e_values)): + row = i // cols + col = i % cols + + x = x_start + col * (patch_width + x_gap) + y = y_start - row * (patch_height + y_gap) + + # 颜色映射 + color_map = { + # 灰阶 + "White": "#FFFFFF", + "Gray 80": "#E6E6E6", + "Gray 65": "#D1D1D1", + "Gray 50": "#BABABA", + "Gray 35": "#9E9E9E", + # 饱和色 + "100% Red": "#FF0000", + "100% Green": "#00FF00", + "100% Blue": "#0000FF", + "100% Cyan": "#00FFFF", + "100% Magenta": "#FF00FF", + "100% Yellow": "#FFFF00", + # ColorChecker 颜色 + "Dark Skin": "#735242", + "Light Skin": "#C29682", + "Blue Sky": "#5E7A9C", + "Foliage": "#596B42", + "Blue Flower": "#8280B0", + "Bluish Green": "#63BDA8", + "Orange": "#D97829", + "Purplish Blue": "#4A5CA3", + "Moderate Red": "#C25461", + "Purple": "#5C3D6B", + "Yellow Green": "#9EBA40", + "Orange Yellow": "#E6A12E", + "Blue (Legacy)": "#333D96", + "Green (Legacy)": "#479447", + "Red (Legacy)": "#B0303B", + "Yellow (Legacy)": "#EDC721", + "Magenta (Legacy)": "#BA5491", + "Cyan (Legacy)": "#0085A3", + } + + patch_color = color_map.get(color_name, "#808080") + + # ΔE 等级颜色 + if delta_e < 3: + edge_color = "green" + elif delta_e < 5: + edge_color = "orange" + else: + edge_color = "red" + + # 绘制色块 + rect = Rectangle( + (x, y), + patch_width, + patch_height, + transform=self.accuracy_ax.transAxes, + facecolor=patch_color, + edgecolor=edge_color, + linewidth=1.8, + ) + self.accuracy_ax.add_patch(rect) + + # ========== 标注色块名称(上方)========== + self.accuracy_ax.text( + x + patch_width / 2, + y + patch_height + 0.015, + color_name, + ha="center", + va="bottom", + fontsize=5.5, + fontweight="bold", + transform=self.accuracy_ax.transAxes, + clip_on=False, + ) + + # ========== 标注 ΔE 值(中心)========== + dark_colors = [ + "100% Red", + "100% Green", + "100% Blue", + "Gray 35", + "Dark Skin", + "Foliage", + "Purple", + "Purplish Blue", + "Blue (Legacy)", + "Green (Legacy)", + "Red (Legacy)", + "Magenta (Legacy)", + "Cyan (Legacy)", + ] + + text_color = "white" if color_name in dark_colors else "black" + + self.accuracy_ax.text( + x + patch_width / 2, + y + patch_height / 2, + f"ΔE\n{delta_e:.2f}", + ha="center", + va="center", + fontsize=5.2, + fontweight="bold", + color=text_color, + transform=self.accuracy_ax.transAxes, + bbox=dict( + boxstyle="round,pad=0.22", + facecolor="white" if text_color == "black" else "black", + alpha=0.75, + edgecolor=edge_color, + linewidth=1.0, + ), + ) + + # ========== 统计信息卡片(只保留外框)========== + card_width = 0.84 + card_height = 0.15 + card_x = 0.08 + card_y = 0.01 + + info_card = Rectangle( + (card_x, card_y), + card_width, + card_height, + transform=self.accuracy_ax.transAxes, + facecolor="#F0F0F0", + edgecolor="black", + linewidth=1.5, + ) + self.accuracy_ax.add_patch(info_card) + + # ========== 标题(带说明)========== + self.accuracy_ax.text( + card_x + card_width / 2, + card_y + card_height - 0.008, + "色准统计(5灰阶 + 18 ColorChecker + 6饱和色 | ΔE 2000 标准)", + ha="center", + va="top", + fontsize=7.5, + fontweight="bold", + transform=self.accuracy_ax.transAxes, + ) + + # ========== 统计内容(无内部框)========== + stats_y = card_y + card_height * 0.55 + + # 左侧:ΔE 统计 + left_x = card_x + 0.02 + stats_text = [ + f"平均 ΔE: {avg_delta_e:.2f}", + f"最大 ΔE: {max_delta_e:.2f}", + f"最小 ΔE: {min_delta_e:.2f}", + ] + + for i, text in enumerate(stats_text): + self.accuracy_ax.text( + left_x, + stats_y - i * 0.030, + text, + ha="left", + va="center", + fontsize=7, + fontweight="bold", + transform=self.accuracy_ax.transAxes, + ) + + # 中间:色块统计 + middle_x = card_x + card_width * 0.32 + + self.accuracy_ax.text( + middle_x, + stats_y, + f"优秀 (ΔE<3): {excellent_count} 个", + ha="left", + va="center", + fontsize=7, + color="green", + fontweight="bold", + transform=self.accuracy_ax.transAxes, + ) + + self.accuracy_ax.text( + middle_x, + stats_y - 0.030, + f"良好 (3≤ΔE<5): {good_count} 个", + ha="left", + va="center", + fontsize=7, + color="orange", + fontweight="bold", + transform=self.accuracy_ax.transAxes, + ) + + self.accuracy_ax.text( + middle_x, + stats_y - 0.060, + f"偏差 (ΔE≥5): {poor_count} 个", + ha="left", + va="center", + fontsize=7, + color="red", + fontweight="bold", + transform=self.accuracy_ax.transAxes, + ) + + # 右侧:总体评价 + right_x = card_x + card_width - 0.02 + + if avg_delta_e < 2: + grade = "专业级" + grade_icon = "★★★" + grade_color = "darkgreen" + elif avg_delta_e < 3: + grade = "优秀" + grade_icon = "✓✓" + grade_color = "green" + elif avg_delta_e < 5: + grade = "良好" + grade_icon = "✓" + grade_color = "orange" + else: + grade = "需要校准" + grade_icon = "✗" + grade_color = "red" + + self.accuracy_ax.text( + right_x, + stats_y + 0.020, + "总体评价:", + ha="right", + va="bottom", + fontsize=7, + fontweight="bold", + transform=self.accuracy_ax.transAxes, + ) + + self.accuracy_ax.text( + right_x, + stats_y - 0.025, + f"{grade} {grade_icon}", + ha="right", + va="top", + fontsize=11, + fontweight="bold", + color=grade_color, + transform=self.accuracy_ax.transAxes, + ) + + self.accuracy_canvas.draw() + self.chart_notebook.select(4) + + def on_test_completed(self): + """测试完成后的UI更新""" + self.testing = False + self.start_btn.config(state=tk.NORMAL) + self.stop_btn.config(state=tk.DISABLED) + self.save_btn.config(state=tk.NORMAL) + self.clear_config_btn.config(state=tk.NORMAL) + self.status_var.set("测试完成") + self.log_gui.log("测试完成") + + # 恢复配置项按钮 + if hasattr(self, "config_panel_frame"): + try: + self.config_panel_frame.btn.configure(state="normal") + except: + pass + + # 启用色域参考标准下拉框 + try: + test_type = self.config.current_test_type + + if test_type == "screen_module" and hasattr(self, "screen_gamut_combo"): + self.screen_gamut_combo.configure(state="readonly") + self.log_gui.log("✓ 屏模组色域参考标准已启用") + + elif test_type == "sdr_movie" and hasattr(self, "sdr_gamut_combo"): + self.sdr_gamut_combo.configure(state="readonly") + self.log_gui.log("✓ SDR 色域参考标准已启用") + + elif test_type == "hdr_movie" and hasattr(self, "hdr_gamut_combo"): + self.hdr_gamut_combo.configure(state="readonly") + self.log_gui.log("✓ HDR 色域参考标准已启用") + except Exception as e: + self.log_gui.log(f"启用色域参考标准失败: {str(e)}") + + # 获取当前测试类型和选中的测试项 + selected_items = self.get_selected_test_items() + test_type = self.config.current_test_type + + # ==================== ✅ 启用单步调试按钮 ==================== + if hasattr(self, "debug_panel"): + try: + # 屏模组:启用 Gamma 和 RGB 单步调试 + if test_type == "screen_module": + if "gamma" in selected_items: + gray_data = self.results.get_intermediate_data("shared", "gray") + if gray_data: + self.debug_panel.enable_debug( + "screen_module", "gamma", gray_data + ) + + # 启用 RGB 单步调试(色域测试完成后) + if "gamut" in selected_items: + rgb_data = self.results.get_intermediate_data("gamut", "rgb") + if rgb_data: + self.debug_panel.enable_debug( + "screen_module", "rgb", rgb_data + ) + + # ✅ 启用单步调试按钮 + if hasattr(self, "screen_debug_btn"): + self.screen_debug_btn.config(state=tk.NORMAL) + self.log_gui.log("✓ 屏模组单步调试按钮已启用") + + # SDR:启用 Gamma、色准和 RGB 单步调试 + elif test_type == "sdr_movie": + if "gamma" in selected_items: + gray_data = self.results.get_intermediate_data("shared", "gray") + if gray_data: + self.debug_panel.enable_debug( + "sdr_movie", "gamma", gray_data + ) + + if "accuracy" in selected_items: + accuracy_data = self.results.get_intermediate_data( + "accuracy", "measured" + ) + if accuracy_data: + self.debug_panel.enable_debug( + "sdr_movie", "accuracy", accuracy_data + ) + + # 启用 RGB 单步调试(色域测试完成后) + if "gamut" in selected_items: + rgb_data = self.results.get_intermediate_data("gamut", "rgb") + if rgb_data: + self.debug_panel.enable_debug("sdr_movie", "rgb", rgb_data) + + # ✅ 启用单步调试按钮 + if hasattr(self, "sdr_debug_btn"): + self.sdr_debug_btn.config(state=tk.NORMAL) + self.log_gui.log("✓ SDR 单步调试按钮已启用") + + # HDR:启用 EOTF、色准和 RGB 单步调试 + elif test_type == "hdr_movie": + if "eotf" in selected_items: + gray_data = self.results.get_intermediate_data("shared", "gray") + if gray_data: + self.debug_panel.enable_debug( + "hdr_movie", "eotf", gray_data + ) + + if "accuracy" in selected_items: + accuracy_data = self.results.get_intermediate_data( + "accuracy", "measured" + ) + if accuracy_data: + self.debug_panel.enable_debug( + "hdr_movie", "accuracy", accuracy_data + ) + + # 启用 RGB 单步调试(色域测试完成后) + if "gamut" in selected_items: + rgb_data = self.results.get_intermediate_data("gamut", "rgb") + if rgb_data: + self.debug_panel.enable_debug("hdr_movie", "rgb", rgb_data) + + # ✅ 启用单步调试按钮 + if hasattr(self, "hdr_debug_btn"): + self.hdr_debug_btn.config(state=tk.NORMAL) + self.log_gui.log("✓ HDR 单步调试按钮已启用") + + except Exception as e: + self.log_gui.log(f"启用单步调试失败: {str(e)}") + + # ==================== 显示色度/色域重新计算按钮 ==================== + if "cct" in selected_items: + try: + if test_type == "screen_module" and hasattr(self, "recalc_cct_btn"): + self.recalc_cct_btn.grid() + self.log_gui.log("✓ 屏模组色度参数调整按钮已启用") + elif test_type == "sdr_movie" and hasattr(self, "sdr_recalc_cct_btn"): + self.sdr_recalc_cct_btn.grid() + self.log_gui.log("✓ SDR 色度参数调整按钮已启用") + elif test_type == "hdr_movie" and hasattr(self, "hdr_recalc_cct_btn"): + self.hdr_recalc_cct_btn.grid() + self.log_gui.log("✓ HDR 色度参数调整按钮已启用") + except Exception as e: + self.log_gui.log(f"显示色度重新计算按钮失败: {str(e)}") + + if "gamut" in selected_items: + try: + if test_type == "screen_module" and hasattr(self, "recalc_gamut_btn"): + self.recalc_gamut_btn.grid() + self.log_gui.log("✓ 屏模组色域参考调整按钮已启用") + elif test_type == "sdr_movie" and hasattr(self, "sdr_recalc_gamut_btn"): + self.sdr_recalc_gamut_btn.grid() + self.log_gui.log("✓ SDR 色域参考调整按钮已启用") + elif test_type == "hdr_movie" and hasattr(self, "hdr_recalc_gamut_btn"): + self.hdr_recalc_gamut_btn.grid() + self.log_gui.log("✓ HDR 色域参考调整按钮已启用") + except Exception as e: + self.log_gui.log(f"显示色域重新计算按钮失败: {str(e)}") + + messagebox.showinfo("完成", "测试已完成!") + + def on_custom_template_test_completed(self): + """客户模板测试完成后的UI更新""" + self.testing = False + self.set_custom_result_table_locked(False) + self.start_btn.config(state=tk.NORMAL) + self.stop_btn.config(state=tk.DISABLED) + self.save_btn.config(state=tk.DISABLED) + self.clear_config_btn.config(state=tk.NORMAL) + self.custom_btn.config(state=tk.NORMAL) + self.status_var.set("客户模板测试完成") + + if hasattr(self, "config_panel_frame"): + try: + self.config_panel_frame.btn.configure(state="normal") + except: + pass + + self.log_gui.log("客户模板测试完成") + messagebox.showinfo("完成", "客户模板测试已完成!") + + def get_current_test_result(self): + """获取当前测试结果""" + test_type = self.test_type_var.get() + test_items = self.get_selected_test_items() + + # 构建测试结果字典 + result = { + "test_type": test_type, + "test_type_name": self.get_test_type_name(test_type), + "test_items": test_items, + "test_items_names": self.config.get_test_item_chinese_names(test_items), + "timestamp": datetime.datetime.now(), + "status": "完成", + "results": {}, + } + + # 根据测试项目收集结果数据 + for item in test_items: + if item == "gamut" and hasattr(self, "gamut_results"): + result["results"]["gamut"] = getattr(self, "gamut_results", {}) + elif item in ["gamma", "eotf"] and hasattr(self, "gamma_results"): + result["results"][item] = getattr(self, "gamma_results", {}) + elif item == "cct" and hasattr(self, "cct_results"): + result["results"]["cct"] = getattr(self, "cct_results", {}) + elif item == "contrast" and hasattr(self, "contrast_results"): + result["results"]["contrast"] = getattr(self, "contrast_results", {}) + elif item == "accuracy" and hasattr(self, "accuracy_results"): + result["results"]["accuracy"] = getattr(self, "accuracy_results", {}) + + return result + + def on_test_error(self): + """测试出错后的UI更新""" + self.testing = False + self.set_custom_result_table_locked(False) + self.start_btn.config(state=tk.NORMAL) + self.stop_btn.config(state=tk.DISABLED) + self.clear_config_btn.config(state=tk.NORMAL) + if hasattr(self, "custom_btn"): + self.custom_btn.config(state=tk.NORMAL) + self.status_var.set("测试出错") + + # 恢复配置项按钮 + if hasattr(self, "config_panel_frame"): + try: + self.config_panel_frame.btn.configure(state="normal") + except: + pass + + messagebox.showerror("错误", "测试过程中发生错误,请查看日志") + + def get_test_type_name(self, test_type): + """获取测试类型的显示名称""" + if test_type == "screen_module": + return "屏模组性能测试" + elif test_type == "sdr_movie": + return "SDR Movie测试" + elif test_type == "hdr_movie": + return "HDR Movie测试" + return test_type + + def get_selected_test_items(self): + """获取当前选中的测试项""" + selected_items = [] + for var_name, var in self.test_vars.items(): + if var.get(): + selected_items.append(var_name.split("_")[-1]) + return selected_items + + def update_config(self, event=None): + """更新配置""" + try: + self.config.set_device_config( + self.ca_com_var.get(), + self.ucd_list_var.get(), + self.ca_channel_var.get(), + ) + + # 保存当前选中的测试项到配置 + self.config.set_current_test_items(self.get_selected_test_items()) + + # 待修改为三种测试类型的timing值 + self.config.set_current_timing(self.screen_module_timing_var.get()) + + # 自动保存配置到文件 + self.save_pq_config() + + except Exception as e: + self.log_gui.log(f"更新配置失败: {str(e)}") + + def update_config_and_tabs(self): + """更新配置并同步Tab状态""" + self.update_config() + self.update_chart_tabs_state() + + # 根据当前测试类型保存对应参数 + current_test_type = self.config.current_test_type + selected_items = self.get_selected_test_items() + + if current_test_type == "screen_module": + if "cct" in selected_items: + self.save_cct_params() + + elif current_test_type == "sdr_movie": + if "cct" in selected_items: + self.save_sdr_cct_params() + + elif current_test_type == "hdr_movie": + if "cct" in selected_items: + if hasattr(self, "save_hdr_cct_params"): + self.save_hdr_cct_params() + + # 控制参数框的显示 + self.toggle_cct_params_frame() + + def toggle_cct_params_frame(self): + """根据测试类型和测试项的选中状态显示对应参数框""" + selected_items = self.get_selected_test_items() + current_test_type = self.config.current_test_type + + # ========== 默认隐藏所有参数框 ========== + self.cct_params_frame.pack_forget() + self.sdr_cct_params_frame.pack_forget() + + # HDR 色度参数框(如果存在的话) + if hasattr(self, "hdr_cct_params_frame"): + self.hdr_cct_params_frame.pack_forget() + + # ========== 根据测试类型和选中项显示对应参数框 ========== + if current_test_type == "screen_module": + # 屏模组:只有色度参数 + if "cct" in selected_items: + self.cct_params_frame.pack(fill=tk.X, padx=5, pady=5) + if hasattr(self, "log_gui"): + self.log_gui.log("✓ 显示屏模组色度参数设置") + + elif current_test_type == "sdr_movie": + # SDR:只有色度参数(色准不需要参数设置框) + if "cct" in selected_items: + self.sdr_cct_params_frame.pack(fill=tk.X, padx=5, pady=5) + if hasattr(self, "log_gui"): + self.log_gui.log("✓ 显示 SDR 色度参数设置") + + elif current_test_type == "hdr_movie": + # HDR:只有色度参数(色准不需要参数设置框) + if "cct" in selected_items: + if hasattr(self, "hdr_cct_params_frame"): + self.hdr_cct_params_frame.pack(fill=tk.X, padx=5, pady=5) + if hasattr(self, "log_gui"): + self.log_gui.log("✓ 显示 HDR 色度参数设置") + else: + if hasattr(self, "log_gui"): + self.log_gui.log("⚠️ HDR 色度参数框尚未创建") + + def on_screen_module_timing_changed(self, event=None): + """屏模组信号格式改变时的回调""" + try: + selected_timing = self.screen_module_timing_var.get() + + # 记录日志 + self.log_gui.log(f"屏模组信号格式已更改为: {selected_timing}") + + # 解析分辨率和刷新率 + import re + + match = re.search(r"(\d+)x(\d+)\s*@\s*(\d+)", selected_timing) + if match: + width = int(match.group(1)) + height = int(match.group(2)) + refresh_rate = int(match.group(3)) + + self.log_gui.log(f" ├─ 分辨率: {width}x{height}") + self.log_gui.log(f" └─ 刷新率: {refresh_rate}Hz") + + # 根据分辨率给出提示 + if width >= 3840: # 4K及以上 + self.log_gui.log(" ℹ️ 检测到4K分辨率") + + if refresh_rate >= 120: + self.log_gui.log(" ℹ️ 检测到高刷新率") + + # 更新配置 + self.config.set_current_timing(selected_timing) + + # 如果正在测试,提示用户 + if self.testing: + self.log_gui.log("⚠️ 警告: 测试进行中,信号格式更改将在下次测试时生效") + + # 保存配置 + self.save_pq_config() + + except Exception as e: + self.log_gui.log(f"❌ 屏模组信号格式更改失败: {str(e)}") + + def load_pq_config(self): + """加载PQ配置(兼容打包后的程序)""" + try: + # ✅ 使用 self.config_file(已经是动态路径) + if os.path.exists(self.config_file): + with open(self.config_file, "r", encoding="utf-8") as f: + config_dict = json.load(f) + self.config.from_dict(config_dict) + if hasattr(self, "log_gui"): + self.log_gui.log("✓ 配置文件加载成功") + else: + if hasattr(self, "log_gui"): + self.log_gui.log("⚠️ 配置文件不存在,使用默认配置") + except Exception as e: + if hasattr(self, "log_gui"): + self.log_gui.log(f"⚠️ 加载配置文件失败: {str(e)},使用默认配置") + + def save_pq_config(self): + """保存PQ配置(兼容打包后的程序)""" + try: + # 确保目录存在 + os.makedirs(os.path.dirname(self.config_file), exist_ok=True) + + # 保存配置 + self.config.save_to_file(self.config_file) + except Exception as e: + if hasattr(self, "log_gui"): + self.log_gui.log(f"保存配置文件失败: {str(e)}") + + def on_closing(self): + """窗口关闭时的处理""" + try: + # ✅ 检查是否清理了配置 + if not self.config_cleared: + # 保存配置 + self.save_pq_config() + else: + print("配置已清理,不再保存") + + # 断开设备连接 + if self.ucd.status: + self.ucd.close() + if self.ca is not None: + self.ca.close() + + # 关闭窗口 + self.root.destroy() + + except Exception as e: + print(f"关闭窗口时出错: {str(e)}") + self.root.destroy() + + def on_screen_gamut_ref_changed(self, event=None): + """屏模组色域参考标准改变时的回调""" + try: + new_ref = self.screen_gamut_ref_var.get() + self.log_gui.log(f"✓ 屏模组色域参考标准已更改为: {new_ref}") + + # 保存到配置 + if "screen_module" not in self.config.current_test_types: + self.config.current_test_types["screen_module"] = {} + + self.config.current_test_types["screen_module"]["gamut_reference"] = new_ref + self.save_pq_config() + + except Exception as e: + self.log_gui.log(f"保存屏模组色域参考标准失败: {str(e)}") + + def on_sdr_gamut_ref_changed(self, event=None): + """SDR 色域参考标准改变时的回调""" + try: + new_ref = self.sdr_gamut_ref_var.get() + self.log_gui.log(f"✓ SDR 色域参考标准已更改为: {new_ref}") + + # 保存到配置 + if "sdr_movie" not in self.config.current_test_types: + self.config.current_test_types["sdr_movie"] = {} + + self.config.current_test_types["sdr_movie"]["gamut_reference"] = new_ref + self.save_pq_config() + + except Exception as e: + self.log_gui.log(f"保存 SDR 色域参考标准失败: {str(e)}") + + def on_hdr_gamut_ref_changed(self, event=None): + """HDR 色域参考标准改变时的回调""" + try: + new_ref = self.hdr_gamut_ref_var.get() + self.log_gui.log(f"✓ HDR 色域参考标准已更改为: {new_ref}") + + # 保存到配置 + if "hdr_movie" not in self.config.current_test_types: + self.config.current_test_types["hdr_movie"] = {} + + self.config.current_test_types["hdr_movie"]["gamut_reference"] = new_ref + self.save_pq_config() + + except Exception as e: + self.log_gui.log(f"保存 HDR 色域参考标准失败: {str(e)}") + + def toggle_screen_debug_panel(self): + """打开/关闭屏模组单步调试面板(独立窗口)""" + # 如果窗口已存在且可见,关闭它 + if hasattr(self, "debug_window") and self.debug_window.winfo_exists(): + self.debug_window.destroy() + self.screen_debug_btn.config(text="打开调试面板") + self.log_gui.log("✓ 单步调试面板已关闭") + return + + # 创建新窗口 + self.debug_window = ttk.Toplevel(self.root) + self.debug_window.title("🔧 单步调试面板") + self.debug_window.geometry("900x400") + self.debug_window.transient(self.root) + + # 创建调试面板容器 + debug_container = ttk.Frame(self.debug_window, padding=10) + debug_container.pack(fill=tk.BOTH, expand=True) # ← 这个 pack 是对的 + + # 创建调试面板实例 + from views.pq_debug_panel import PQDebugPanel + + debug_panel_instance = PQDebugPanel(debug_container, self) + # ← 这里不应该有任何 pack 调用! + + self.log_gui.log("✓ 单步调试面板实例已创建") + + # 重新启用调试(如果有数据) + try: + test_type = self.config.current_test_type + selected_items = self.get_selected_test_items() + + if test_type == "screen_module": + if "gamma" in selected_items: + gray_data = self.results.get_intermediate_data("shared", "gray") + if gray_data: + self.log_gui.log(f" → 加载 {len(gray_data)} 个灰阶数据点") + debug_panel_instance.enable_debug( + "screen_module", "gamma", gray_data + ) + self.log_gui.log("✓ 屏模组 Gamma 单步调试已重新启用") + else: + self.log_gui.log(" ✗ 没有可用的灰阶数据") + + if "gamut" in selected_items: + rgb_data = self.results.get_intermediate_data("gamut", "rgb") + if rgb_data: + self.log_gui.log(f" → 加载 {len(rgb_data)} 个RGB数据点") + debug_panel_instance.enable_debug( + "screen_module", "rgb", rgb_data + ) + self.log_gui.log("✓ 屏模组 RGB 单步调试已重新启用") + + except Exception as e: + self.log_gui.log(f"⚠️ 加载调试数据失败: {str(e)}") + import traceback + + self.log_gui.log(traceback.format_exc()) + + # 更新按钮文字 + self.screen_debug_btn.config(text="关闭调试面板") + + # 窗口关闭时的回调 + def on_closing(): + self.screen_debug_btn.config(text="打开调试面板") + self.debug_window.destroy() + self.log_gui.log("✓ 单步调试窗口已关闭") + + self.debug_window.protocol("WM_DELETE_WINDOW", on_closing) + self.debug_window.update_idletasks() + + self.log_gui.log("✓ 单步调试面板已打开(独立窗口)") + + def toggle_sdr_debug_panel(self): + """打开/关闭 SDR 单步调试面板(独立窗口)""" + # 如果窗口已存在且可见,关闭它 + if hasattr(self, "sdr_debug_window") and self.sdr_debug_window.winfo_exists(): + self.sdr_debug_window.destroy() + self.sdr_debug_btn.config(text="打开调试面板") + self.log_gui.log("✓ SDR 单步调试面板已关闭") + return + + # 创建新窗口 + self.sdr_debug_window = ttk.Toplevel(self.root) + self.sdr_debug_window.title("🔧 SDR 单步调试面板") + self.sdr_debug_window.geometry("900x400") + self.sdr_debug_window.transient(self.root) + + # 创建调试面板容器 + debug_container = ttk.Frame(self.sdr_debug_window, padding=10) + debug_container.pack(fill=tk.BOTH, expand=True) + + # ✅ 创建调试面板实例(不要对它调用 pack) + from views.pq_debug_panel import PQDebugPanel + + debug_panel_instance = PQDebugPanel(debug_container, self) + # ← 删除:debug_panel_instance.pack(...) + + self.log_gui.log("✓ SDR 单步调试面板实例已创建") + + # ✅ 重新启用调试(如果有数据) + try: + selected_items = self.get_selected_test_items() + + if "gamma" in selected_items: + gray_data = self.results.get_intermediate_data("shared", "gray") + if gray_data: + self.log_gui.log(f" → 加载 {len(gray_data)} 个灰阶数据点") + debug_panel_instance.enable_debug("sdr_movie", "gamma", gray_data) + self.log_gui.log("✓ SDR Gamma 单步调试已重新启用") + + if "accuracy" in selected_items: + accuracy_data = self.results.get_intermediate_data( + "accuracy", "measured" + ) + if accuracy_data: + self.log_gui.log(f" → 加载 {len(accuracy_data)} 个色准数据点") + debug_panel_instance.enable_debug( + "sdr_movie", "accuracy", accuracy_data + ) + self.log_gui.log("✓ SDR 色准单步调试已重新启用") + + if "gamut" in selected_items: + rgb_data = self.results.get_intermediate_data("gamut", "rgb") + if rgb_data: + self.log_gui.log(f" → 加载 {len(rgb_data)} 个RGB数据点") + debug_panel_instance.enable_debug("sdr_movie", "rgb", rgb_data) + self.log_gui.log("✓ SDR RGB 单步调试已重新启用") + + except Exception as e: + self.log_gui.log(f"⚠️ 加载 SDR 调试数据失败: {str(e)}") + import traceback + + self.log_gui.log(traceback.format_exc()) + + # 更新按钮文字 + self.sdr_debug_btn.config(text="关闭调试面板") + + # 窗口关闭时的回调 + def on_closing(): + self.sdr_debug_btn.config(text="打开调试面板") + self.sdr_debug_window.destroy() + self.log_gui.log("✓ SDR 单步调试窗口已关闭") + + self.sdr_debug_window.protocol("WM_DELETE_WINDOW", on_closing) + self.sdr_debug_window.update_idletasks() + + self.log_gui.log("✓ SDR 单步调试面板已打开(独立窗口)") + + def toggle_hdr_debug_panel(self): + """打开/关闭 HDR 单步调试面板(独立窗口)""" + # 如果窗口已存在且可见,关闭它 + if hasattr(self, "hdr_debug_window") and self.hdr_debug_window.winfo_exists(): + self.hdr_debug_window.destroy() + self.hdr_debug_btn.config(text="打开调试面板") + self.log_gui.log("✓ HDR 单步调试面板已关闭") + return + + # 创建新窗口 + self.hdr_debug_window = ttk.Toplevel(self.root) + self.hdr_debug_window.title("🔧 HDR 单步调试面板") + self.hdr_debug_window.geometry("900x400") + self.hdr_debug_window.transient(self.root) + + # 创建调试面板容器 + debug_container = ttk.Frame(self.hdr_debug_window, padding=10) + debug_container.pack(fill=tk.BOTH, expand=True) + + # ✅ 创建调试面板实例(不要对它调用 pack) + from views.pq_debug_panel import PQDebugPanel + + debug_panel_instance = PQDebugPanel(debug_container, self) + # ← 删除:debug_panel_instance.pack(...) + + self.log_gui.log("✓ HDR 单步调试面板实例已创建") + + # ✅ 重新启用调试(如果有数据) + try: + selected_items = self.get_selected_test_items() + + if "eotf" in selected_items: + gray_data = self.results.get_intermediate_data("shared", "gray") + if gray_data: + self.log_gui.log(f" → 加载 {len(gray_data)} 个灰阶数据点") + debug_panel_instance.enable_debug("hdr_movie", "eotf", gray_data) + self.log_gui.log("✓ HDR EOTF 单步调试已重新启用") + + if "accuracy" in selected_items: + accuracy_data = self.results.get_intermediate_data( + "accuracy", "measured" + ) + if accuracy_data: + self.log_gui.log(f" → 加载 {len(accuracy_data)} 个色准数据点") + debug_panel_instance.enable_debug( + "hdr_movie", "accuracy", accuracy_data + ) + self.log_gui.log("✓ HDR 色准单步调试已重新启用") + + if "gamut" in selected_items: + rgb_data = self.results.get_intermediate_data("gamut", "rgb") + if rgb_data: + self.log_gui.log(f" → 加载 {len(rgb_data)} 个RGB数据点") + debug_panel_instance.enable_debug("hdr_movie", "rgb", rgb_data) + self.log_gui.log("✓ HDR RGB 单步调试已重新启用") + + except Exception as e: + self.log_gui.log(f"⚠️ 加载 HDR 调试数据失败: {str(e)}") + import traceback + + self.log_gui.log(traceback.format_exc()) + + # 更新按钮文字 + self.hdr_debug_btn.config(text="关闭调试面板") + + # 窗口关闭时的回调 + def on_closing(): + self.hdr_debug_btn.config(text="打开调试面板") + self.hdr_debug_window.destroy() + self.log_gui.log("✓ HDR 单步调试窗口已关闭") + + self.hdr_debug_window.protocol("WM_DELETE_WINDOW", on_closing) + self.hdr_debug_window.update_idletasks() + + self.log_gui.log("✓ HDR 单步调试面板已打开(独立窗口)") + + def clear_config_file(self): + """清理配置文件(兼容打包后的程序)""" + from tkinter import messagebox + + config_file = self.get_config_path() + + try: + if os.path.exists(config_file): + os.remove(config_file) + self.config_cleared = True + messagebox.showinfo("提示", "✓ 清理成功") + self.log_gui.log("✓ 配置文件清理成功") + else: + messagebox.showinfo("提示", "配置文件不存在") + self.log_gui.log("⚠️ 配置文件不存在") + + except Exception as e: + messagebox.showerror("错误", "❌ 清理失败") + self.log_gui.log(f"❌ 配置文件清理失败: {str(e)}") + + def start_local_dimming_test(self): + """开始 Local Dimming 测试""" + # 检查设备连接 + if not self.ca or not self.ucd.status: + messagebox.showerror("错误", "请先连接 CA410 和 UCD323") + return + + # 禁用按钮 + self.ld_start_btn.config(state=tk.DISABLED) + self.ld_stop_btn.config(state=tk.NORMAL) + self.ld_save_btn.config(state=tk.DISABLED) + + # 清空结果 + for item in self.ld_tree.get_children(): + self.ld_tree.delete(item) + + # 获取配置 + wait_time = float(self.ld_wait_time_var.get()) + + # 在新线程中执行测试 + def run_test(): + from utils.local_dimming_test import LocalDimmingTest, LocalDimmingController + + # 从设备当前 timing 获取分辨率 + ld_ctrl = LocalDimmingController(self.ucd) + cur_w, cur_h = ld_ctrl.get_current_resolution() + resolution = f"{cur_w}x{cur_h}" + + ld_test = LocalDimmingTest( + self.ucd, + self.ca, + log_callback=self.log_gui.log, + ) + + ld_test.wait_time = wait_time + + results = ld_test.run_test(resolution=resolution) + + # 保存到实例变量 + self.ld_test_instance = ld_test + self.ld_test_results = results + + # 更新结果显示 + self.root.after(0, lambda: self.update_ld_results(results)) + + # 清理临时文件 + ld_test.cleanup() + + # 恢复按钮状态 + self.root.after(0, lambda: self.ld_start_btn.config(state=tk.NORMAL)) + self.root.after(0, lambda: self.ld_stop_btn.config(state=tk.DISABLED)) + self.root.after(0, lambda: self.ld_save_btn.config(state=tk.NORMAL)) + + threading.Thread(target=run_test, daemon=True).start() + + def update_ld_results(self, results): + """更新 Local Dimming 结果显示""" + for percentage, x, y, lv, X, Y, Z in results: + self.ld_tree.insert( + "", + tk.END, + values=(f"{percentage}%", f"{lv:.2f}", f"{x:.4f}", f"{y:.4f}"), + ) + + def stop_local_dimming_test(self): + """停止 Local Dimming 测试""" + if hasattr(self, "ld_test_instance"): + self.ld_test_instance.stop() + + def save_local_dimming_results(self): + """保存 Local Dimming 结果""" + from tkinter import filedialog + import csv + import datetime + + default_name = f"LocalDimming_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" + + save_path = filedialog.asksaveasfilename( + title="保存测试结果", + initialfile=default_name, + defaultextension=".csv", + filetypes=[("CSV 文件", "*.csv"), ("所有文件", "*.*")], + ) + + if not save_path: + return + + try: + with open(save_path, "w", newline="", encoding="utf-8-sig") as f: + writer = csv.writer(f) + writer.writerow(["窗口百分比", "x", "y", "亮度 (cd/m²)", "X", "Y", "Z"]) + + for item in self.ld_tree.get_children(): + values = self.ld_tree.item(item, "values") + # 从 self.ld_test_results 获取完整数据 + percentage_str = values[0] + percentage = int(percentage_str.replace("%", "")) + + # 找到对应的完整数据 + for p, x, y, lv, X, Y, Z in self.ld_test_results: + if p == percentage: + writer.writerow([f"{p}%", x, y, lv, X, Y, Z]) + break + + self.log_gui.log(f"✓ 测试结果已保存: {save_path}") + messagebox.showinfo("成功", f"测试结果已保存到:\n{save_path}") + + except Exception as e: + self.log_gui.log(f"❌ 保存失败: {str(e)}") + messagebox.showerror("错误", f"保存失败: {str(e)}") + + def send_ld_window(self, percentage): + """发送指定百分比的窗口""" + if not self.ucd.status: + messagebox.showwarning("警告", "请先连接 UCD323 设备") + return + + self.log_gui.log(f"🔆 发送 {percentage}% 窗口...") + + # 记录当前百分比(用于测量) + self.current_ld_percentage = percentage + + def send(): + from utils.local_dimming_test import LocalDimmingController + + ld_controller = LocalDimmingController(self.ucd) + + # 从设备当前 timing 获取分辨率 + width, height = ld_controller.get_current_resolution() + + # 生成并发送图片 + success = ld_controller.send_window_pattern_with_resolution( + percentage, width, height + ) + + if success: + self.root.after( + 0, lambda: self.log_gui.log(f"✅ {percentage}% 窗口已发送") + ) + else: + self.root.after( + 0, lambda: self.log_gui.log(f"❌ {percentage}% 窗口发送失败") + ) + + threading.Thread(target=send, daemon=True).start() + + def measure_ld_luminance(self): + """测量当前亮度""" + if not self.ca: + messagebox.showwarning("警告", "请先连接 CA410 色度计") + return + + if self.current_ld_percentage is None: + messagebox.showinfo("提示", "请先发送一个窗口图案") + return + + self.log_gui.log("📏 正在采集亮度...") + + def measure(): + try: + x, y, lv, X, Y, Z = self.ca.readAllDisplay() + + if lv is not None: + import datetime + + timestamp = datetime.datetime.now().strftime("%H:%M:%S") + + # 更新显示 + self.root.after( + 0, + lambda: self.ld_result_label.config( + text=f"亮度: {lv:.2f} cd/m² | x: {x:.4f} | y: {y:.4f}" + ), + ) + + # 添加到表格 + self.root.after( + 0, + lambda: self.ld_tree.insert( + "", + tk.END, + values=( + f"{self.current_ld_percentage}%", + f"{lv:.2f}", + f"{x:.4f}", + f"{y:.4f}", + timestamp, + ), + ), + ) + + self.root.after( + 0, lambda: self.log_gui.log(f"✅ 采集完成: {lv:.2f} cd/m²") + ) + else: + self.root.after(0, lambda: self.log_gui.log("❌ 采集失败")) + + except Exception as e: + self.root.after(0, lambda: self.log_gui.log(f"❌ 采集异常: {str(e)}")) + + threading.Thread(target=measure, daemon=True).start() + + def clear_ld_records(self): + """清空测试记录""" + for item in self.ld_tree.get_children(): + self.ld_tree.delete(item) + self.ld_result_label.config(text="亮度: -- cd/m² | x: -- | y: --") + self.current_ld_percentage = None + self.log_gui.log("🗑️ 测试记录已清空") + + def save_local_dimming_results(self): + """保存 Local Dimming 结果""" + from tkinter import filedialog + import csv + import datetime + + if len(self.ld_tree.get_children()) == 0: + messagebox.showinfo("提示", "没有可保存的数据") + return + + default_name = f"LocalDimming_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" + + save_path = filedialog.asksaveasfilename( + title="保存测试结果", + initialfile=default_name, + defaultextension=".csv", + filetypes=[("CSV 文件", "*.csv"), ("所有文件", "*.*")], + ) + + if not save_path: + return + + try: + with open(save_path, "w", newline="", encoding="utf-8-sig") as f: + writer = csv.writer(f) + writer.writerow(["窗口百分比", "亮度 (cd/m²)", "x", "y", "时间"]) + + for item in self.ld_tree.get_children(): + values = self.ld_tree.item(item, "values") + writer.writerow(values) + + self.log_gui.log(f"✓ 测试结果已保存: {save_path}") + messagebox.showinfo("成功", f"测试结果已保存到:\n{save_path}") + + except Exception as e: + self.log_gui.log(f"❌ 保存失败: {str(e)}") + messagebox.showerror("错误", f"保存失败: {str(e)}") + + +def main(): + try: + # root = tk.Tk() + root = ttk.Window(themename="yeti") + app = PQAutomationApp(root) + root.mainloop() + except Exception as e: + print("程序发生错误:", e) + traceback.print_exc() + finally: + pass + +if __name__ == "__main__": + main() diff --git a/pqAutomationApp.spec b/pqAutomationApp.spec new file mode 100644 index 0000000..9bacb06 --- /dev/null +++ b/pqAutomationApp.spec @@ -0,0 +1,111 @@ +# -*- mode: python ; coding: utf-8 -*- + +import os +import sys + +SPEC_DIR = ( + os.path.abspath(os.path.dirname(__file__)) + if "__file__" in globals() + else os.getcwd() +) +if SPEC_DIR not in sys.path: + sys.path.insert(0, SPEC_DIR) + +from app_version import APP_NAME, APP_VERSION +from PyInstaller.utils.win32.versioninfo import ( + VSVersionInfo, + FixedFileInfo, + StringFileInfo, + StringTable, + StringStruct, + VarFileInfo, + VarStruct, +) + + +def build_windows_version(version_text): + parts = [int(part) for part in version_text.split('.') if part.strip()] + parts = (parts + [0, 0, 0, 0])[:4] + return tuple(parts) + + +windows_version = build_windows_version(APP_VERSION) +windows_version_text = '.'.join(str(part) for part in windows_version) + +version_info = VSVersionInfo( + ffi=FixedFileInfo( + filevers=windows_version, + prodvers=windows_version, + mask=0x3F, + flags=0x0, + OS=0x40004, + fileType=0x1, + subtype=0x0, + date=(0, 0), + ), + kids=[ + StringFileInfo( + [ + StringTable( + '080404B0', + [ + StringStruct('CompanyName', 'Moka'), + StringStruct('FileDescription', APP_NAME), + StringStruct('FileVersion', windows_version_text), + StringStruct('InternalName', 'pqAutomationApp'), + StringStruct('OriginalFilename', 'pqAutomationApp.exe'), + StringStruct('ProductName', APP_NAME), + StringStruct('ProductVersion', windows_version_text), + ], + ) + ] + ), + VarFileInfo([VarStruct('Translation', [2052, 1200])]), + ], +) + + +a = Analysis( + ['pqAutomationApp.py'], + pathex=[], + binaries=[], + datas=[('assets', 'assets'), ('UniTAP', 'UniTAP')], + hiddenimports=[], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=['PyQt5'], + noarchive=False, + optimize=0, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + [], + exclude_binaries=True, + name='pqAutomationApp', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + console=False, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, + contents_directory='internal', + version=version_info, + icon=os.path.join(SPEC_DIR, 'assets', 'pq.ico'), +) +coll = COLLECT( + exe, + a.binaries, + a.datas, + strip=False, + upx=True, + upx_exclude=[], + name='pqAutomationApp', +) diff --git a/settings/pq_config.json b/settings/pq_config.json new file mode 100644 index 0000000..d7b733a --- /dev/null +++ b/settings/pq_config.json @@ -0,0 +1,311 @@ +{ + "current_test_type": "screen_module", + "test_types": { + "screen_module": { + "name": "屏模组性能测试", + "test_items": [ + "gamut", + "gamma", + "cct", + "contrast" + ], + "timing": "DMT 1920x 1080 @ 60Hz", + "color_format": "RGB", + "bpc": 8, + "colorimetry": "sRGB", + "cct_params": { + "x_ideal": 0.3127, + "x_tolerance": 0.003, + "y_ideal": 0.329, + "y_tolerance": 0.003 + } + }, + "sdr_movie": { + "name": "SDR Movie测试", + "test_items": [ + "gamut", + "gamma", + "cct", + "contrast", + "accuracy" + ], + "timing": "DMT 1920x 1080 @ 60Hz", + "color_format": "RGB", + "bpc": 8, + "colorimetry": "sRGB", + "cct_params": { + "x_ideal": 0.3127, + "x_tolerance": 0.003, + "y_ideal": 0.329, + "y_tolerance": 0.003 + } + }, + "hdr_movie": { + "name": "HDR Movie测试", + "test_items": [ + "gamut", + "eotf", + "cct", + "contrast", + "accuracy" + ], + "timing": "DMT 1920x 1080 @ 60Hz", + "color_format": "RGB", + "bpc": 8, + "colorimetry": "sRGB", + "cct_params": { + "x_ideal": 0.3127, + "x_tolerance": 0.003, + "y_ideal": 0.329, + "y_tolerance": 0.003 + } + } + }, + "device_config": { + "ca_com": "COM3", + "ucd_list": "0: UCD-323 [2128C209]", + "ca_channel": "0" + }, + "default_pattern_rgb": { + "pattern_mode": "SolidColor", + "measurement_bit_depth": 8, + "measurement_max_value": 2, + "pattern_params": [ + [ + 255, + 0, + 0 + ], + [ + 0, + 255, + 0 + ], + [ + 0, + 0, + 255 + ] + ] + }, + "default_pattern_gray": { + "pattern_mode": "SolidColor", + "measurement_bit_depth": 8, + "measurement_max_value": 10, + "pattern_params": [ + [ + 255, + 255, + 255 + ], + [ + 230, + 230, + 230 + ], + [ + 205, + 205, + 205 + ], + [ + 179, + 179, + 179 + ], + [ + 154, + 154, + 154 + ], + [ + 128, + 128, + 128 + ], + [ + 102, + 102, + 102 + ], + [ + 78, + 78, + 78 + ], + [ + 52, + 52, + 52 + ], + [ + 26, + 26, + 26 + ], + [ + 0, + 0, + 0 + ] + ] + }, + "default_pattern_accuracy": { + "pattern_mode": "SolidColor", + "measurement_bit_depth": 8, + "measurement_max_value": 28, + "pattern_params": [ + [ + 255, + 255, + 255 + ], + [ + 230, + 230, + 230 + ], + [ + 209, + 209, + 209 + ], + [ + 186, + 186, + 186 + ], + [ + 158, + 158, + 158 + ], + [ + 115, + 82, + 66 + ], + [ + 194, + 150, + 130 + ], + [ + 94, + 122, + 156 + ], + [ + 89, + 107, + 66 + ], + [ + 130, + 128, + 176 + ], + [ + 99, + 189, + 168 + ], + [ + 217, + 120, + 41 + ], + [ + 74, + 92, + 163 + ], + [ + 194, + 84, + 97 + ], + [ + 92, + 61, + 107 + ], + [ + 158, + 186, + 64 + ], + [ + 230, + 161, + 46 + ], + [ + 51, + 61, + 150 + ], + [ + 71, + 148, + 71 + ], + [ + 176, + 48, + 59 + ], + [ + 237, + 199, + 33 + ], + [ + 186, + 84, + 145 + ], + [ + 0, + 133, + 163 + ], + [ + 255, + 0, + 0 + ], + [ + 0, + 255, + 0 + ], + [ + 0, + 0, + 255 + ], + [ + 0, + 255, + 255 + ], + [ + 255, + 0, + 255 + ], + [ + 255, + 255, + 0 + ] + ] + }, + "custom_pattern": { + "pattern_mode": "SolidColor", + "measurement_bit_depth": 8, + "measurement_max_value": 0, + "pattern_params": [] + } +} \ No newline at end of file diff --git a/utils/UCD323_Enum.py b/utils/UCD323_Enum.py new file mode 100644 index 0000000..57c3ca9 --- /dev/null +++ b/utils/UCD323_Enum.py @@ -0,0 +1,590 @@ +from enum import IntEnum +import UniTAP + + +class UCDEnum: + class ColorInfo: + """ + + Class contains information of frame `ColorFormat`, `DynamicRange`, `Colorimetry`. + + """ + + class ColorFormat(IntEnum): + """ + Contains values of possible color format. + """ + + CF_NONE = 0 + CF_UNKNOWN = 1 + CF_RGB = 2 + CF_YCbCr_422 = 3 + CF_YCbCr_444 = 4 + CF_YCbCr_420 = 5 + CF_IDO_DEFINED = 6 + CF_Y_ONLY = 7 + CF_RAW = 8 + CF_DSC = 9 + + class DynamicRange(IntEnum): + """ + Contains values of possible dynamic range. + """ + + DR_UNKNOWN = -1 + DR_VESA = 0 + DR_CTA = 1 + + class Colorimetry(IntEnum): + """ + Contains values of possible colorimetry. + """ + + CM_NONE = 0 + CM_RESERVED = 1 + CM_sRGB = 2 + CM_SMPTE_170M = 3 + CM_ITUR_BT601 = 4 + CM_ITUR_BT709 = 5 + CM_xvYCC601 = 6 + CM_xvYCC709 = 7 + CM_sYCC601 = 8 + CM_AdobeYCC601 = 9 + CM_AdobeRGB = 10 + CM_ITUR_BT2020_YcCbcCrc = 11 + CM_ITUR_BT2020_YCbCr = 12 + CM_ITUR_BT2020_RGB = 13 + CM_RGB_WIDE_GAMUT_FIX = 14 + CM_RGB_WIDE_GAMUT_FLT = 15 + CM_DCI_P3 = 16 + CM_DICOM_1_4_GRAY_SCALE = 17 + CM_CUSTOM_COLOR_PROFILE = 18 + + CM_opYCC601 = CM_AdobeYCC601 + CM_opRGB = CM_AdobeRGB + + # 颜色格式映射 - 支持不区分大小写的字符串匹配 + @staticmethod + def get_color_format(format_str): + format_map = { + "none": UniTAP.ColorInfo.ColorFormat.CF_NONE, + "unknown": UniTAP.ColorInfo.ColorFormat.CF_UNKNOWN, + "rgb": UniTAP.ColorInfo.ColorFormat.CF_RGB, + "ycbcr422": UniTAP.ColorInfo.ColorFormat.CF_YCbCr_422, + "ycbcr444": UniTAP.ColorInfo.ColorFormat.CF_YCbCr_444, + "ycbcr420": UniTAP.ColorInfo.ColorFormat.CF_YCbCr_420, + "ido_defined": UniTAP.ColorInfo.ColorFormat.CF_IDO_DEFINED, + "yonly": UniTAP.ColorInfo.ColorFormat.CF_Y_ONLY, + "raw": UniTAP.ColorInfo.ColorFormat.CF_RAW, + "dsc": UniTAP.ColorInfo.ColorFormat.CF_DSC, + } + if not format_str: + return None + return format_map.get(format_str.lower(), None) + + # 色度映射 - 支持不区分大小写的字符串匹配 + @staticmethod + def get_colorimetry(colorimetry_str): + colorimetry_map = { + "none": UniTAP.ColorInfo.Colorimetry.CM_NONE, + "reserved": UniTAP.ColorInfo.Colorimetry.CM_RESERVED, + "srgb": UniTAP.ColorInfo.Colorimetry.CM_sRGB, + "smpte170m": UniTAP.ColorInfo.Colorimetry.CM_SMPTE_170M, + "bt601": UniTAP.ColorInfo.Colorimetry.CM_ITUR_BT601, + "bt709": UniTAP.ColorInfo.Colorimetry.CM_ITUR_BT709, + "xvycc601": UniTAP.ColorInfo.Colorimetry.CM_xvYCC601, + "xvycc709": UniTAP.ColorInfo.Colorimetry.CM_xvYCC709, + "sycc601": UniTAP.ColorInfo.Colorimetry.CM_sYCC601, + "adobeycc601": UniTAP.ColorInfo.Colorimetry.CM_AdobeYCC601, + "adobergb": UniTAP.ColorInfo.Colorimetry.CM_AdobeRGB, + "bt2020yccbccrc": UniTAP.ColorInfo.Colorimetry.CM_ITUR_BT2020_YcCbcCrc, + "bt2020ycbcr": UniTAP.ColorInfo.Colorimetry.CM_ITUR_BT2020_YCbCr, + "bt2020rgb": UniTAP.ColorInfo.Colorimetry.CM_ITUR_BT2020_RGB, + "rgbwidegamutfix": UniTAP.ColorInfo.Colorimetry.CM_RGB_WIDE_GAMUT_FIX, + "rgbwidegamutflt": UniTAP.ColorInfo.Colorimetry.CM_RGB_WIDE_GAMUT_FLT, + "dcip3": UniTAP.ColorInfo.Colorimetry.CM_DCI_P3, + "dicom14grayscale": UniTAP.ColorInfo.Colorimetry.CM_DICOM_1_4_GRAY_SCALE, + "customcolorprofile": UniTAP.ColorInfo.Colorimetry.CM_CUSTOM_COLOR_PROFILE, + "opycc601": UniTAP.ColorInfo.Colorimetry.CM_opYCC601, + "oprgb": UniTAP.ColorInfo.Colorimetry.CM_opRGB, + } + if not colorimetry_str: + return None + return colorimetry_map.get(colorimetry_str.lower(), None) + + class VideoPatternInfo: + class VideoPattern(IntEnum): + """ + Class `VideoPattern` contains all possible variants of patterns which can be set in the function `set_pattern`. + """ + + Disabled = 0 + ColorBars = 1 + Chessboard = 2 + SolidColor = 3 + SolidWhite = 4 + SolidRed = 5 + SolidGreen = 6 + SolidBlue = 7 + WhiteVStrips = 8 + GradientRGBStripes = 9 + ColorRamp = 10 + ColorSquares = 11 + MotionPattern = 12 + SquareWindow = 15 + + class VideoPatternParams(IntEnum): + """ + Class `VideoPatternParams` contains all possible variants of parameters which can be set in the function `set_pattern_params`. + """ + + SolidColor = 3 + WhiteVStrips = 8 + GradientRGBStripes = 9 + MotionPattern = 12 + SquareWindow = 15 + + @staticmethod + def get_video_pattern(pattern_str): + pattern_map = { + "disabled": UniTAP.VideoPattern.Disabled, + "colorbars": UniTAP.VideoPattern.ColorBars, + "chessboard": UniTAP.VideoPattern.Chessboard, + "solidcolor": UniTAP.VideoPattern.SolidColor, + "solidwhite": UniTAP.VideoPattern.SolidWhite, + "solidred": UniTAP.VideoPattern.SolidRed, + "solidgreen": UniTAP.VideoPattern.SolidGreen, + "solidblue": UniTAP.VideoPattern.SolidBlue, + "whitevstrips": UniTAP.VideoPattern.WhiteVStrips, + "gradientrgbstripes": UniTAP.VideoPattern.GradientRGBStripes, + "colorramp": UniTAP.VideoPattern.ColorRamp, + "coloursquares": UniTAP.VideoPattern.ColorSquares, + "motionpattern": UniTAP.VideoPattern.MotionPattern, + "squarewindow": UniTAP.VideoPattern.SquareWindow, + } + if not pattern_str: + return None + return pattern_map.get(pattern_str.lower(), None) + + class TimingInfo: + class ResolutionType(IntEnum): + """ + 分辨率类型枚举,包含DMT、CTA、CVT和OVT四种类型 + """ + + DMT = 0 # VESA Display Monitor Timing + CTA = 1 # Consumer Technology Association + CVT = 2 # Coordinated Video Timing + OVT = 3 # Other Video Timing + + # 分辨率类型映射 + resolution_type_map = { + "dmt": ResolutionType.DMT, + "cta": ResolutionType.CTA, + "cvt": ResolutionType.CVT, + "ovt": ResolutionType.OVT, + } + + # DMT分辨率ID映射 + dmt_resolution_map = { + 9: {"width": 800, "height": 600, "refresh_rate": 60.317, "id_hex": "9h"}, + 14: {"width": 848, "height": 480, "refresh_rate": 60.0, "id_hex": "Eh"}, + 16: {"width": 1024, "height": 768, "refresh_rate": 60.0, "id_hex": "10h"}, + 23: {"width": 1280, "height": 768, "refresh_rate": 60.0, "id_hex": "17h"}, + 27: { + "width": 1280, + "height": 800, + "refresh_rate": 60.0, + "id_hex": "1Bh", + "note": "RB1", + }, + 28: {"width": 1280, "height": 800, "refresh_rate": 60.0, "id_hex": "1Ch"}, + 32: {"width": 1280, "height": 960, "refresh_rate": 60.0, "id_hex": "20h"}, + 35: {"width": 1280, "height": 1024, "refresh_rate": 60.0, "id_hex": "23h"}, + 39: {"width": 1360, "height": 768, "refresh_rate": 60.0, "id_hex": "27h"}, + 41: { + "width": 1400, + "height": 1050, + "refresh_rate": 60.0, + "id_hex": "29h", + "note": "RB1", + }, + 42: {"width": 1400, "height": 1050, "refresh_rate": 60.0, "id_hex": "2Ah"}, + 47: {"width": 1440, "height": 900, "refresh_rate": 59.887, "id_hex": "2Fh"}, + 51: {"width": 1600, "height": 1200, "refresh_rate": 60.0, "id_hex": "33h"}, + 57: { + "width": 1680, + "height": 1050, + "refresh_rate": 60.0, + "id_hex": "39h", + "note": "RB1", + }, + 58: {"width": 1680, "height": 1050, "refresh_rate": 60.0, "id_hex": "3Ah"}, + 62: {"width": 1792, "height": 1344, "refresh_rate": 60.0, "id_hex": "3Eh"}, + 65: {"width": 1856, "height": 1392, "refresh_rate": 60.0, "id_hex": "41h"}, + 69: {"width": 1920, "height": 1200, "refresh_rate": 60.0, "id_hex": "45h"}, + 73: {"width": 1920, "height": 1440, "refresh_rate": 60.0, "id_hex": "49h"}, + 76: { + "width": 2560, + "height": 1600, + "refresh_rate": 60.0, + "id_hex": "4Ch", + "note": "RB1", + }, + 77: {"width": 2560, "height": 1600, "refresh_rate": 60.0, "id_hex": "4Dh"}, + 82: {"width": 1920, "height": 1080, "refresh_rate": 60.0, "id_hex": "52h"}, + } + + # CTA分辨率ID映射 + cta_resolution_map = { + 1: {"width": 640, "height": 480, "refresh_rate": 59.94, "vic": 1}, + 2: {"width": 720, "height": 480, "refresh_rate": 59.94, "vic": 2}, + 3: {"width": 720, "height": 480, "refresh_rate": 59.94, "vic": 3}, + 4: {"width": 1280, "height": 720, "refresh_rate": 60.0, "vic": 4}, + 8: {"width": 1440, "height": 240, "refresh_rate": 59.826, "vic": 8}, + 9: {"width": 1440, "height": 240, "refresh_rate": 60.054, "vic": 9}, + 12: {"width": 2880, "height": 240, "refresh_rate": 59.826, "vic": 12}, + 13: {"width": 2880, "height": 240, "refresh_rate": 59.826, "vic": 13}, + 14: {"width": 1440, "height": 480, "refresh_rate": 59.94, "vic": 14}, + 15: {"width": 1440, "height": 480, "refresh_rate": 59.94, "vic": 15}, + 16: {"width": 1920, "height": 1080, "refresh_rate": 60.0, "vic": 16}, + 17: {"width": 720, "height": 576, "refresh_rate": 50.0, "vic": 17}, + 18: {"width": 720, "height": 576, "refresh_rate": 50.0, "vic": 18}, + 19: {"width": 1280, "height": 720, "refresh_rate": 50.0, "vic": 19}, + 23: {"width": 1440, "height": 288, "refresh_rate": 49.761, "vic": 23}, + 24: {"width": 1440, "height": 288, "refresh_rate": 49.761, "vic": 24}, + 27: {"width": 2880, "height": 288, "refresh_rate": 49.761, "vic": 27}, + 28: {"width": 2880, "height": 288, "refresh_rate": 49.761, "vic": 28}, + 29: {"width": 1440, "height": 576, "refresh_rate": 50.0, "vic": 29}, + 30: {"width": 1440, "height": 576, "refresh_rate": 50.0, "vic": 30}, + 31: {"width": 1920, "height": 1080, "refresh_rate": 50.0, "vic": 31}, + 32: {"width": 1920, "height": 1080, "refresh_rate": 24.0, "vic": 32}, + 33: {"width": 1920, "height": 1080, "refresh_rate": 25.0, "vic": 33}, + 34: {"width": 1920, "height": 1080, "refresh_rate": 30.0, "vic": 34}, + 35: {"width": 2880, "height": 480, "refresh_rate": 59.94, "vic": 35}, + 36: {"width": 2880, "height": 480, "refresh_rate": 59.94, "vic": 36}, + 37: {"width": 2880, "height": 576, "refresh_rate": 50.0, "vic": 37}, + 38: {"width": 2880, "height": 576, "refresh_rate": 50.0, "vic": 38}, + 41: {"width": 1280, "height": 720, "refresh_rate": 100.0, "vic": 41}, + 42: {"width": 720, "height": 576, "refresh_rate": 100.0, "vic": 42}, + 43: {"width": 720, "height": 576, "refresh_rate": 100.0, "vic": 43}, + 47: {"width": 1280, "height": 720, "refresh_rate": 120.0, "vic": 47}, + 48: {"width": 720, "height": 480, "refresh_rate": 120.0, "vic": 48}, + 49: {"width": 720, "height": 480, "refresh_rate": 119.88, "vic": 49}, + 52: {"width": 720, "height": 576, "refresh_rate": 200.0, "vic": 52}, + 53: {"width": 720, "height": 576, "refresh_rate": 200.0, "vic": 53}, + 56: {"width": 720, "height": 480, "refresh_rate": 239.76, "vic": 56}, + 57: {"width": 720, "height": 480, "refresh_rate": 239.76, "vic": 57}, + 60: {"width": 1280, "height": 720, "refresh_rate": 24.0, "vic": 60}, + 61: {"width": 1280, "height": 720, "refresh_rate": 25.0, "vic": 61}, + 62: {"width": 1280, "height": 720, "refresh_rate": 30.0, "vic": 62}, + 63: {"width": 1920, "height": 1080, "refresh_rate": 120.0, "vic": 63}, + 64: {"width": 1920, "height": 1080, "refresh_rate": 100.0, "vic": 64}, + 65: {"width": 1280, "height": 720, "refresh_rate": 24.0, "vic": 65}, + 66: {"width": 1280, "height": 720, "refresh_rate": 25.0, "vic": 66}, + 67: {"width": 1280, "height": 720, "refresh_rate": 30.0, "vic": 67}, + 68: {"width": 1280, "height": 720, "refresh_rate": 50.0, "vic": 68}, + 69: {"width": 1280, "height": 720, "refresh_rate": 60.0, "vic": 69}, + 70: {"width": 1280, "height": 720, "refresh_rate": 100.0, "vic": 70}, + 71: {"width": 1280, "height": 720, "refresh_rate": 120.0, "vic": 71}, + 72: {"width": 1920, "height": 1080, "refresh_rate": 24.0, "vic": 72}, + 73: {"width": 1920, "height": 1080, "refresh_rate": 25.0, "vic": 73}, + 74: {"width": 1920, "height": 1080, "refresh_rate": 30.0, "vic": 74}, + 75: {"width": 1920, "height": 1080, "refresh_rate": 50.0, "vic": 75}, + 76: {"width": 1920, "height": 1080, "refresh_rate": 60.0, "vic": 76}, + 77: {"width": 1920, "height": 1080, "refresh_rate": 100.0, "vic": 77}, + 78: {"width": 1920, "height": 1080, "refresh_rate": 120.0, "vic": 78}, + 79: {"width": 1680, "height": 720, "refresh_rate": 24.0, "vic": 79}, + 80: {"width": 1680, "height": 720, "refresh_rate": 25.0, "vic": 80}, + 81: {"width": 1680, "height": 720, "refresh_rate": 30.0, "vic": 81}, + 82: {"width": 1680, "height": 720, "refresh_rate": 50.0, "vic": 82}, + 83: {"width": 1680, "height": 720, "refresh_rate": 60.0, "vic": 83}, + 84: {"width": 1680, "height": 720, "refresh_rate": 100.0, "vic": 84}, + 85: {"width": 1680, "height": 720, "refresh_rate": 120.0, "vic": 85}, + 86: {"width": 2560, "height": 1080, "refresh_rate": 24.0, "vic": 86}, + 87: {"width": 2560, "height": 1080, "refresh_rate": 25.0, "vic": 87}, + 88: {"width": 2560, "height": 1080, "refresh_rate": 30.0, "vic": 88}, + 89: {"width": 2560, "height": 1080, "refresh_rate": 50.0, "vic": 89}, + 90: {"width": 2560, "height": 1080, "refresh_rate": 60.0, "vic": 90}, + 91: {"width": 2560, "height": 1080, "refresh_rate": 100.0, "vic": 91}, + 92: {"width": 2560, "height": 1080, "refresh_rate": 120.0, "vic": 92}, + 93: {"width": 3840, "height": 2160, "refresh_rate": 24.0, "vic": 93}, + 94: {"width": 3840, "height": 2160, "refresh_rate": 25.0, "vic": 94}, + 95: {"width": 3840, "height": 2160, "refresh_rate": 30.0, "vic": 95}, + 96: {"width": 3840, "height": 2160, "refresh_rate": 50.0, "vic": 96}, + 97: {"width": 3840, "height": 2160, "refresh_rate": 60.0, "vic": 97}, + 98: {"width": 4096, "height": 2160, "refresh_rate": 24.0, "vic": 98}, + 99: {"width": 4096, "height": 2160, "refresh_rate": 25.0, "vic": 99}, + 100: {"width": 4096, "height": 2160, "refresh_rate": 30.0, "vic": 100}, + 101: {"width": 4096, "height": 2160, "refresh_rate": 50.0, "vic": 101}, + 102: {"width": 4096, "height": 2160, "refresh_rate": 60.0, "vic": 102}, + 103: {"width": 3840, "height": 2160, "refresh_rate": 24.0, "vic": 103}, + 104: {"width": 3840, "height": 2160, "refresh_rate": 25.0, "vic": 104}, + 105: {"width": 3840, "height": 2160, "refresh_rate": 30.0, "vic": 105}, + 106: {"width": 3840, "height": 2160, "refresh_rate": 50.0, "vic": 106}, + 107: {"width": 3840, "height": 2160, "refresh_rate": 60.0, "vic": 107}, + 108: {"width": 1280, "height": 720, "refresh_rate": 48.0, "vic": 108}, + 109: {"width": 1280, "height": 720, "refresh_rate": 48.0, "vic": 109}, + 110: {"width": 1680, "height": 720, "refresh_rate": 48.0, "vic": 110}, + 111: {"width": 1920, "height": 1080, "refresh_rate": 48.0, "vic": 111}, + 112: {"width": 1920, "height": 1080, "refresh_rate": 48.0, "vic": 112}, + 113: {"width": 2560, "height": 1080, "refresh_rate": 48.0, "vic": 113}, + 114: {"width": 3840, "height": 2160, "refresh_rate": 48.0, "vic": 114}, + 115: {"width": 4096, "height": 2160, "refresh_rate": 48.0, "vic": 115}, + 116: {"width": 3840, "height": 2160, "refresh_rate": 48.0, "vic": 116}, + 117: {"width": 3840, "height": 2160, "refresh_rate": 100.0, "vic": 117}, + 118: {"width": 3840, "height": 2160, "refresh_rate": 120.0, "vic": 118}, + 119: {"width": 3840, "height": 2160, "refresh_rate": 100.0, "vic": 119}, + 120: {"width": 3840, "height": 2160, "refresh_rate": 120.0, "vic": 120}, + 218: {"width": 4096, "height": 2160, "refresh_rate": 100.0, "vic": 218}, + 219: {"width": 4096, "height": 2160, "refresh_rate": 120.0, "vic": 219}, + } + + # CVT分辨率ID映射 + cvt_resolution_map = { + 0: [ + {"width": 640, "height": 480, "refresh_rate": 60.0}, + {"width": 768, "height": 480, "refresh_rate": 84.502}, + {"width": 1024, "height": 640, "refresh_rate": 59.887}, + {"width": 1152, "height": 720, "refresh_rate": 74.721}, + {"width": 1280, "height": 768, "refresh_rate": 60.0, "note": "RB1"}, + {"width": 1280, "height": 960, "refresh_rate": 59.939}, + {"width": 1536, "height": 960, "refresh_rate": 84.884}, + {"width": 1600, "height": 1200, "refresh_rate": 60.0, "note": "RB1"}, + {"width": 1920, "height": 1080, "refresh_rate": 30.0, "note": "RB1"}, + {"width": 1920, "height": 1080, "refresh_rate": 30.0, "note": "RB2"}, + {"width": 1920, "height": 1080, "refresh_rate": 60.0, "note": "RB1"}, + {"width": 1920, "height": 1080, "refresh_rate": 60.0, "note": "RB2"}, + {"width": 1920, "height": 1080, "refresh_rate": 84.884}, + {"width": 1920, "height": 1080, "refresh_rate": 120.0, "note": "RB1"}, + {"width": 1920, "height": 1080, "refresh_rate": 120.0, "note": "RB2"}, + {"width": 1920, "height": 1080, "refresh_rate": 144.05, "note": "RB1"}, + {"width": 1920, "height": 1080, "refresh_rate": 144.05, "note": "RB2"}, + {"width": 1920, "height": 1080, "refresh_rate": 144.05, "note": "RB3"}, + {"width": 1920, "height": 1080, "refresh_rate": 200.07, "note": "RB3"}, + {"width": 1920, "height": 1080, "refresh_rate": 239.898, "note": "RB1"}, + {"width": 1920, "height": 1080, "refresh_rate": 239.898, "note": "RB2"}, + {"width": 1920, "height": 1080, "refresh_rate": 239.898, "note": "RB3"}, + {"width": 1920, "height": 1440, "refresh_rate": 59.974, "note": "RB1"}, + {"width": 2048, "height": 1280, "refresh_rate": 59.922, "note": "RB1"}, + {"width": 2048, "height": 1536, "refresh_rate": 60.0, "note": "RB1"}, + {"width": 2128, "height": 1200, "refresh_rate": 59.946}, + {"width": 2456, "height": 1536, "refresh_rate": 50.0}, + {"width": 2456, "height": 1536, "refresh_rate": 74.935}, + {"width": 2560, "height": 1080, "refresh_rate": 60.0}, + {"width": 2560, "height": 1080, "refresh_rate": 60.0, "note": "RB1"}, + {"width": 2560, "height": 1080, "refresh_rate": 144.051, "note": "RB3"}, + {"width": 2560, "height": 1080, "refresh_rate": 200.07, "note": "RB3"}, + {"width": 2560, "height": 1440, "refresh_rate": 60.0, "note": "RB1"}, + {"width": 2560, "height": 1440, "refresh_rate": 60.0, "note": "RB2"}, + {"width": 2560, "height": 1440, "refresh_rate": 144.05, "note": "RB3"}, + {"width": 2560, "height": 1440, "refresh_rate": 200.07, "note": "RB3"}, + {"width": 2560, "height": 1920, "refresh_rate": 74.979}, + {"width": 2728, "height": 1536, "refresh_rate": 59.944}, + {"width": 3440, "height": 1440, "refresh_rate": 60.0}, + {"width": 3440, "height": 1440, "refresh_rate": 60.0, "note": "RB1"}, + {"width": 3440, "height": 1440, "refresh_rate": 60.0, "note": "RB2"}, + {"width": 3440, "height": 1440, "refresh_rate": 120.0}, + {"width": 3440, "height": 1440, "refresh_rate": 120.0, "note": "RB1"}, + {"width": 3440, "height": 1440, "refresh_rate": 120.0, "note": "RB2"}, + {"width": 3440, "height": 1440, "refresh_rate": 165.0, "note": "RB1"}, + {"width": 3440, "height": 1440, "refresh_rate": 165.0, "note": "RB2"}, + {"width": 3440, "height": 1440, "refresh_rate": 200.0, "note": "RB1"}, + {"width": 3440, "height": 1440, "refresh_rate": 200.0, "note": "RB2"}, + {"width": 3840, "height": 2160, "refresh_rate": 30.0, "note": "RB1"}, + {"width": 3840, "height": 2160, "refresh_rate": 30.0, "note": "RB2"}, + {"width": 3840, "height": 2160, "refresh_rate": 60.0, "note": "RB1"}, + {"width": 3840, "height": 2160, "refresh_rate": 60.0, "note": "RB2"}, + {"width": 3840, "height": 2160, "refresh_rate": 60.021, "note": "RB3"}, + {"width": 3840, "height": 2160, "refresh_rate": 120.0, "note": "RB1"}, + {"width": 3840, "height": 2160, "refresh_rate": 120.0, "note": "RB2"}, + {"width": 4096, "height": 2160, "refresh_rate": 60.0, "note": "RB1"}, + {"width": 4096, "height": 2160, "refresh_rate": 60.0, "note": "RB2"}, + {"width": 4096, "height": 2160, "refresh_rate": 60.021, "note": "RB3"}, + ] + } + + # OVT分辨率ID映射 + ovt_resolution_map = { + 0: [ + {"width": 768, "height": 480, "refresh_rate": 85.0}, + {"width": 1024, "height": 640, "refresh_rate": 60.0}, + {"width": 1152, "height": 720, "refresh_rate": 75.0}, + {"width": 1280, "height": 768, "refresh_rate": 60.0}, + {"width": 1280, "height": 960, "refresh_rate": 60.0}, + {"width": 1440, "height": 240, "refresh_rate": 60.0}, + {"width": 1440, "height": 480, "refresh_rate": 60.0}, + {"width": 1440, "height": 900, "refresh_rate": 60.0}, + {"width": 1536, "height": 960, "refresh_rate": 85.0}, + {"width": 1920, "height": 1080, "refresh_rate": 30.0}, + {"width": 1920, "height": 1080, "refresh_rate": 60.0}, + {"width": 1920, "height": 1080, "refresh_rate": 85.0}, + {"width": 1920, "height": 1080, "refresh_rate": 100.0}, + {"width": 1920, "height": 1080, "refresh_rate": 120.0}, + {"width": 1920, "height": 1440, "refresh_rate": 60.0}, + {"width": 2048, "height": 1280, "refresh_rate": 60.0}, + {"width": 2048, "height": 1536, "refresh_rate": 60.0}, + {"width": 2128, "height": 1200, "refresh_rate": 60.0}, + {"width": 2456, "height": 1536, "refresh_rate": 50.0}, + {"width": 2456, "height": 1536, "refresh_rate": 75.0}, + {"width": 2560, "height": 1080, "refresh_rate": 30.0}, + {"width": 2560, "height": 1080, "refresh_rate": 60.0}, + {"width": 2560, "height": 1080, "refresh_rate": 120.0}, + {"width": 2560, "height": 1600, "refresh_rate": 60.0}, + {"width": 2560, "height": 1920, "refresh_rate": 75.0}, + {"width": 2728, "height": 1536, "refresh_rate": 60.0}, + {"width": 3840, "height": 2160, "refresh_rate": 30.0}, + {"width": 3840, "height": 2160, "refresh_rate": 60.0}, + {"width": 3840, "height": 2160, "refresh_rate": 120.0}, + {"width": 4096, "height": 2160, "refresh_rate": 30.0}, + ], + 1: [ + {"width": 1280, "height": 720, "refresh_rate": 24.0}, + {"width": 1280, "height": 720, "refresh_rate": 120.0}, + ], + 4: [ + {"width": 1920, "height": 1080, "refresh_rate": 144.0}, + {"width": 1920, "height": 1080, "refresh_rate": 240.0}, + ], + } + + # 根据分辨率类型和ID获取分辨率信息的函数 + @staticmethod + def get_resolution_info(resolution_type, resolution_id): + """ + 根据分辨率类型和ID获取分辨率信息 + + Args: + resolution_type: 分辨率类型,可以是DMT、CTA、CVT或OVT + resolution_id: 分辨率ID + + Returns: + 包含分辨率信息的字典,如果未找到则返回None + """ + if resolution_type == UCDEnum.ResolutionType.DMT: + return UCDEnum.dmt_resolution_map.get(resolution_id) + elif resolution_type == UCDEnum.ResolutionType.CTA: + return UCDEnum.cta_resolution_map.get(resolution_id) + elif resolution_type == UCDEnum.ResolutionType.CVT: + resolutions = UCDEnum.cvt_resolution_map.get(resolution_id, []) + return resolutions[0] if resolutions else None + elif resolution_type == UCDEnum.ResolutionType.OVT: + resolutions = UCDEnum.ovt_resolution_map.get(resolution_id, []) + return resolutions[0] if resolutions else None + return None + + @staticmethod + def get_formatted_resolution_list(): + """ + 从分辨率映射中生成格式化的分辨率字符串列表,用于UI显示 + 格式为: "类型 宽度x 高度 @ 刷新率Hz" + + Returns: + 包含格式化分辨率字符串的列表 + """ + formatted_list = [] + + # 添加DMT分辨率 + for res_id, res_info in UCDEnum.TimingInfo.dmt_resolution_map.items(): + formatted_str = f"DMT {res_info['width']}x {res_info['height']} @ {int(res_info['refresh_rate'])}Hz" + formatted_list.append(formatted_str) + + # 添加CTA分辨率 + for res_id, res_info in UCDEnum.TimingInfo.cta_resolution_map.items(): + formatted_str = f"CTA {res_info['width']}x {res_info['height']} @ {int(res_info['refresh_rate'])}Hz" + formatted_list.append(formatted_str) + + # 添加CVT分辨率 (只取每个ID的第一个) + for res_id, res_list in UCDEnum.TimingInfo.cvt_resolution_map.items(): + for res_info in res_list: + note = f" {res_info.get('note', '')}" if "note" in res_info else "" + formatted_str = f"CVT {res_info['width']}x {res_info['height']} @ {int(res_info['refresh_rate'])}Hz{note}" + formatted_list.append(formatted_str) + + # 添加OVT分辨率 (只取每个ID的第一个) + for res_id, res_list in UCDEnum.TimingInfo.ovt_resolution_map.items(): + for res_info in res_list: + formatted_str = f"OVT {res_info['width']}x {res_info['height']} @ {int(res_info['refresh_rate'])}Hz" + formatted_list.append(formatted_str) + + # 排序并去重 + return sorted(list(set(formatted_list))) + + class SignalFormat: + """信号格式相关枚举""" + + class GammaType: + """Gamma 类型枚举""" + + GAMMA_22 = "2.2" + GAMMA_24 = "2.4" + GAMMA_26 = "2.6" + + @staticmethod + def get_list(): + return [ + SignalFormat.GammaType.GAMMA_22, + SignalFormat.GammaType.GAMMA_24, + SignalFormat.GammaType.GAMMA_26, + ] + + @staticmethod + def get_gamma_value(gamma_str): + """将 Gamma 字符串转换为数值""" + gamma_map = {"2.2": 2.2, "2.4": 2.4, "2.6": 2.6} + return gamma_map.get(gamma_str, 2.2) + + class DataRange: + """数据范围枚举""" + + FULL = "Full" + LIMITED = "Limited" + + @staticmethod + def get_list(): + return [SignalFormat.DataRange.FULL, SignalFormat.DataRange.LIMITED] + + @staticmethod + def is_full_range(range_str): + """判断是否为 Full Range""" + return range_str == SignalFormat.DataRange.FULL + + class BitDepth: + """编码位深枚举""" + + BIT_8 = "8bit" + BIT_10 = "10bit" + BIT_12 = "12bit" + + @staticmethod + def get_list(): + return [ + SignalFormat.BitDepth.BIT_8, + SignalFormat.BitDepth.BIT_10, + SignalFormat.BitDepth.BIT_12, + ] + + @staticmethod + def get_bit_value(bit_str): + """将位深字符串转换为数值""" + bit_map = {"8bit": 8, "10bit": 10, "12bit": 12} + return bit_map.get(bit_str, 8) + + class HDRMetadata: + """HDR Metadata 参数""" + + # MaxCLL (Maximum Content Light Level) - 最大内容亮度级别 + MAX_CLL_DEFAULT = 1000 + MAX_CLL_OPTIONS = [400, 600, 800, 1000, 1200, 1500, 2000, 4000] + + # MaxFALL (Maximum Frame Average Light Level) - 最大帧平均亮度级别 + MAX_FALL_DEFAULT = 400 + MAX_FALL_OPTIONS = [200, 300, 400, 500, 600, 800, 1000] + + @staticmethod + def get_maxcll_list(): + return SignalFormat.HDRMetadata.MAX_CLL_OPTIONS + + @staticmethod + def get_maxfall_list(): + return SignalFormat.HDRMetadata.MAX_FALL_OPTIONS diff --git a/utils/UCD323_Function.py b/utils/UCD323_Function.py new file mode 100644 index 0000000..50026ef --- /dev/null +++ b/utils/UCD323_Function.py @@ -0,0 +1,589 @@ +# -*- coding: UTF-8 -*- +import UniTAP +import time +import gc +from utils.UCD323_Enum import UCDEnum + + +class UCDController: + """UCD323信号发生器控制类""" + + def __init__(self): + self.lUniTAP = UniTAP.TsiLib() + self.dev = None + self.role = None + self.timing_manager = None + self.config = None + self.color_mode = None + self.status = False + + self.current_timing = None + self.current_pattern = None + self.current_pattern_param = None + self.current_pattern_params = None + self.current_pattern_index = 0 + + def search_device(self): + """搜索可用设备""" + available_devices = self.lUniTAP.get_list_of_available_devices() + return available_devices if available_devices else [] + + def open(self, device_name): + """打开设备""" + temp_dev = None + + try: + if self.dev is not None or self.status: + self._force_cleanup() + + device_id = int(device_name.split(":")[0]) + temp_dev = self.lUniTAP.open(device_id) + + try: + self.role = temp_dev.select_role(UniTAP.dev.UCD323.HDMISource) + self.dev = temp_dev + + except Exception as role_error: + self._close_device_object(temp_dev) + raise role_error + + self.timing_manager = self.role.hdtx.pg.timing_manager + self.color_mode = UniTAP.ColorInfo() + self.status = True + + return True + + except Exception as e: + self._force_cleanup() + return False + + def close(self): + """关闭设备""" + try: + if self.dev: + self._close_device_object(self.dev) + + self.dev = None + self.role = None + self.status = False + self.timing_manager = None + self.current_timing = None + self.current_pattern = None + self.current_pattern_param = None + self.current_pattern_params = None + self.current_pattern_index = 0 + + self.lUniTAP = None + + for i in range(3): + gc.collect() + + time.sleep(2.0) + + self.lUniTAP = UniTAP.TsiLib() + + return True + + except Exception as e: + self.dev = None + self.role = None + self.status = False + self.timing_manager = None + self.current_timing = None + self.current_pattern = None + self.current_pattern_param = None + self.current_pattern_params = None + self.current_pattern_index = 0 + + try: + self.lUniTAP = None + gc.collect() + time.sleep(2.0) + self.lUniTAP = UniTAP.TsiLib() + except Exception as init_error: + pass + + return False + + def _close_device_object(self, dev_obj): + """显式关闭设备对象""" + try: + if dev_obj is None: + return + + if self.lUniTAP and hasattr(self.lUniTAP, "close"): + try: + self.lUniTAP.close(dev_obj) + except Exception as e: + pass + + dev_obj = None + gc.collect() + time.sleep(1.0) + + except Exception as e: + pass + + def _force_cleanup(self): + """强制清理所有状态""" + try: + if self.dev: + self._close_device_object(self.dev) + + self.dev = None + self.role = None + self.status = False + self.timing_manager = None + self.current_timing = None + self.current_pattern = None + self.current_pattern_param = None + self.current_pattern_params = None + self.current_pattern_index = 0 + + except Exception as e: + pass + + def _cleanup(self): + """清理设备资源""" + try: + if self.dev: + self._close_device_object(self.dev) + self.dev = None + + if hasattr(self.lUniTAP, "cleanup"): + self.lUniTAP.cleanup() + + except Exception as e: + pass + + def set_ucd_params(self, config): + """设置UCD323参数""" + self.config = config + test_type = self.config.current_test_type + + if test_type == "hdr_movie": + color_format_str = self.config.current_test_types[test_type]["color_format"] + color_format = UCDEnum.ColorInfo.get_color_format(color_format_str) + + if color_format: + self.color_mode.color_format = color_format + else: + return False + + else: + color_format = self.config.current_test_types[test_type]["color_format"] + bpc = self.config.current_test_types[test_type]["bpc"] + colorimetry = self.config.current_test_types[test_type]["colorimetry"] + + if not self.set_color_mode(color_format, bpc, colorimetry): + return False + + timing_str = self.config.current_test_types[test_type]["timing"] + self.set_timing_from_string(timing_str) + + self.current_pattern_index = 0 + pattern_mode = self.config.current_pattern["pattern_mode"] + pattern = UCDEnum.VideoPatternInfo.get_video_pattern(pattern_mode) + + if pattern is None: + return False + + self.current_pattern = pattern + self.current_pattern_params = self.config.current_pattern["pattern_params"] + + return True + + def run(self): + """运行设备""" + self.apply_video_mode() + self.apply_pattern() + self.role.hdtx.pg.apply() + return True + + def set_color_mode(self, cf, bpc, cm): + """设置颜色模式""" + current_dynamic_range = None + current_transfer_characteristic = None + + if hasattr(self.color_mode, "dynamic_range"): + current_dynamic_range = self.color_mode.dynamic_range + + if hasattr(self.color_mode, "transfer_characteristic"): + current_transfer_characteristic = self.color_mode.transfer_characteristic + + color_format = UCDEnum.ColorInfo.get_color_format(cf) + if color_format is None: + return False + + if not isinstance(bpc, int) or bpc <= 0: + return False + + colorimetry = UCDEnum.ColorInfo.get_colorimetry(cm) + if colorimetry is None: + return False + + self.color_mode.color_format = color_format + self.color_mode.bpc = bpc + self.color_mode.colorimetry = colorimetry + + if current_dynamic_range is not None: + self.color_mode.dynamic_range = current_dynamic_range + + if current_transfer_characteristic is not None: + self.color_mode.transfer_characteristic = current_transfer_characteristic + + return True + + def apply_video_mode(self): + """应用当前colormode和timing""" + if self.current_timing: + self.set_video_mode() + return True + return False + + def set_video_mode(self): + """设置视频模式""" + video_mode = UniTAP.VideoMode( + timing=self.current_timing, color_info=self.color_mode + ) + self.role.hdtx.pg.set_vm(vm=video_mode) + return True + + def set_pattern(self, pattern, pattern_params=None): + """设置pattern""" + if self.current_timing: + if ( + pattern == UCDEnum.VideoPatternInfo.VideoPatternParams.SolidColor + or pattern == UCDEnum.VideoPatternInfo.VideoPatternParams.WhiteVStrips + or pattern + == UCDEnum.VideoPatternInfo.VideoPatternParams.GradientRGBStripes + or pattern == UCDEnum.VideoPatternInfo.VideoPatternParams.MotionPattern + or pattern == UCDEnum.VideoPatternInfo.VideoPatternParams.SquareWindow + and pattern_params + ): + self.set_pattern_params(pattern, pattern_params) + return True + return False + + def set_next_pattern(self): + """设置下一个pattern""" + if self.current_pattern_index < len(self.current_pattern_params): + p = self.current_pattern_params[self.current_pattern_index] + self.set_pattern(self.current_pattern, p) + self.current_pattern_index += 1 + else: + error_msg = ( + f"No more patterns to set. (已设置 {self.current_pattern_index} 个图案)" + ) + raise IndexError(error_msg) + + def set_pattern_params(self, pattern, pattern_params): + """设置pattern参数""" + if pattern: + if pattern == UCDEnum.VideoPatternInfo.VideoPatternParams.SolidColor: + self.current_pattern_param = UniTAP.SolidColorParams( + first=pattern_params[0], + second=pattern_params[1], + third=pattern_params[2], + ) + return True + return False + + def apply_pattern(self): + """应用当前pattern""" + if self.current_pattern: + self.role.hdtx.pg.set_pattern(self.current_pattern) + + if self.current_pattern_param: + self.role.hdtx.pg.set_pattern_params(self.current_pattern_param) + return True + return False + + def search_timing(self, width, height, refresh_rate, resolution_type=None): + """根据分辨率参数搜索合适的timing""" + if resolution_type: + resolution_type = resolution_type.lower() + standard = None + if resolution_type == "dmt": + standard = UniTAP.common.timing.Timing.Standard.SD_DMT + elif resolution_type == "cta": + standard = UniTAP.common.timing.Timing.Standard.SD_CTA + elif resolution_type == "cvt": + standard = UniTAP.common.timing.Timing.Standard.SD_CVT + + timing = self.timing_manager.search( + h_active=width, + v_active=height, + f_rate=int(refresh_rate) * 1000, + standard=standard, + ) + + if timing: + return timing + else: + for res_type in ["dmt", "cta", "cvt", "ovt"]: + result = self.search_timing(width, height, refresh_rate, res_type) + if result: + return result + + return None + + def parse_formatted_timing(self, timing_str): + """解析格式化的timing字符串""" + if not isinstance(timing_str, str): + raise ValueError("timing_str 必须是字符串") + + s = " ".join(timing_str.strip().split()) + s = s.replace(" x", "x").replace("x ", "x") + + parts = s.split(" ", 1) + if len(parts) < 2: + raise ValueError(f"无法解析timing: {timing_str}") + type_str = parts[0].strip().upper() + rest = parts[1].strip() + + if "@" not in rest: + raise ValueError(f"无法解析timing(缺少 @): {timing_str}") + left, right = [p.strip() for p in rest.split("@", 1)] + + if "x" not in left: + raise ValueError(f"无法解析分辨率(缺少 x): {timing_str}") + wh = left.split("x") + if len(wh) != 2: + raise ValueError(f"无法解析分辨率: {timing_str}") + try: + width = int(wh[0]) + height = int(wh[1]) + except Exception: + raise ValueError(f"分辨率数字解析失败: {timing_str}") + + hz_str = right.replace("Hz", "").replace("HZ", "").strip() + try: + refresh_rate = float(hz_str) + except Exception: + raise ValueError(f"刷新率解析失败: {timing_str}") + + rtype_map = { + "DMT": "dmt", + "CTA": "cta", + "CVT": "cvt", + "OVT": "ovt", + } + if type_str not in rtype_map: + raise ValueError(f"未知的分辨率类型: {type_str}") + resolution_type = rtype_map[type_str] + + def find_best_id_in_dict(res_map): + best_id, best_diff = None, float("inf") + for rid, info in res_map.items(): + if info["width"] == width and info["height"] == height: + diff = abs(float(info["refresh_rate"]) - refresh_rate) + if diff < best_diff: + best_diff = diff + best_id = rid + return best_id if best_diff <= 1.0 else None + + def find_best_id_in_list_map(res_map): + best_id, best_diff = None, float("inf") + for rid, infos in res_map.items(): + for info in infos: + if info["width"] == width and info["height"] == height: + diff = abs(float(info["refresh_rate"]) - refresh_rate) + if diff < best_diff: + best_diff = diff + best_id = rid + return best_id if best_diff <= 1.0 else None + + resolution_id = None + if resolution_type == "dmt": + resolution_id = find_best_id_in_dict(UCDEnum.TimingInfo.dmt_resolution_map) + elif resolution_type == "cta": + resolution_id = find_best_id_in_dict(UCDEnum.TimingInfo.cta_resolution_map) + elif resolution_type == "cvt": + resolution_id = find_best_id_in_list_map( + UCDEnum.TimingInfo.cvt_resolution_map + ) + elif resolution_type == "ovt": + resolution_id = find_best_id_in_list_map( + UCDEnum.TimingInfo.ovt_resolution_map + ) + + result = { + "resolution_type": resolution_type, + "width": width, + "height": height, + "refresh_rate": refresh_rate, + "resolution_id": resolution_id, + } + + return result + + def set_timing_from_string(self, timing_str): + """根据格式化timing字符串设置设备timing""" + spec = self.parse_formatted_timing(timing_str) + rtype = spec["resolution_type"] + width = spec["width"] + height = spec["height"] + fr = spec["refresh_rate"] + + if rtype != "ovt": + timing = self.search_timing(width, height, fr, rtype) + if timing: + self.current_timing = timing + return True + + return False + + def set_timing_from_id(self, rtype, rid): + """根据(type, id)设置设备timing""" + timing = None + if rtype.lower() == "dmt": + timing = self.timing_manager.get_dmt(rid) + elif rtype.lower() == "cta": + timing = self.timing_manager.get_cta(rid) + elif rtype.lower() == "cvt": + timing = self.timing_manager.get_cvt(rid) + else: + raise ValueError(f"不支持的分辨率类型: {rtype}") + + if timing: + self.current_timing = timing + return True + else: + return False + + def set_sdr_format( + self, color_space=None, gamma=None, data_range=None, bit_depth=None + ): + """设置SDR信号格式""" + + def _get_colorimetry_from_color_space(color_space_name): + """将色彩空间名称转换为UniTAP colorimetry枚举值""" + try: + colorimetry_map = { + "BT.709": UniTAP.ColorInfo.Colorimetry.CM_ITUR_BT709, + "BT.601": UniTAP.ColorInfo.Colorimetry.CM_ITUR_BT601, + "BT.2020": UniTAP.ColorInfo.Colorimetry.CM_ITUR_BT2020_RGB, + } + result = colorimetry_map.get(color_space_name) + return result if result else UniTAP.ColorInfo.Colorimetry.CM_ITUR_BT709 + except Exception as e: + return UniTAP.ColorInfo.Colorimetry.CM_ITUR_BT709 + + def _set_gamma_transfer_characteristic(gamma_value_str): + """设置Gamma传输特性""" + try: + gamma_value = float(gamma_value_str) + + if abs(gamma_value - 2.2) < 0.1: + self.color_mode.transfer_characteristic = ( + UniTAP.ColorInfo.TransferCharacteristic.TRANSFER_BT709 + ) + return True + + elif abs(gamma_value - 2.4) < 0.1: + if hasattr( + UniTAP.ColorInfo.TransferCharacteristic, "TRANSFER_GAMMA24" + ): + self.color_mode.transfer_characteristic = ( + UniTAP.ColorInfo.TransferCharacteristic.TRANSFER_GAMMA24 + ) + return True + else: + self.color_mode.transfer_characteristic = ( + UniTAP.ColorInfo.TransferCharacteristic.TRANSFER_BT709 + ) + return False + + elif abs(gamma_value - 2.6) < 0.1: + if hasattr( + UniTAP.ColorInfo.TransferCharacteristic, "TRANSFER_GAMMA26" + ): + self.color_mode.transfer_characteristic = ( + UniTAP.ColorInfo.TransferCharacteristic.TRANSFER_GAMMA26 + ) + return True + else: + self.color_mode.transfer_characteristic = ( + UniTAP.ColorInfo.TransferCharacteristic.TRANSFER_BT709 + ) + return False + else: + return False + + except Exception as e: + return False + + try: + if color_space: + colorimetry = _get_colorimetry_from_color_space(color_space) + if colorimetry: + self.color_mode.colorimetry = colorimetry + + if gamma: + _set_gamma_transfer_characteristic(gamma) + + if data_range: + if data_range == "Full": + self.color_mode.dynamic_range = ( + UniTAP.ColorInfo.DynamicRange.DR_VESA + ) + elif data_range == "Limited": + self.color_mode.dynamic_range = UniTAP.ColorInfo.DynamicRange.DR_CTA + + if bit_depth: + bpc = UCDEnum.SignalFormat.BitDepth.get_bit_value(bit_depth) + self.color_mode.bpc = bpc + + if self.current_timing: + self.set_video_mode() + + return True + + except Exception as e: + return False + + def set_hdr_format( + self, + color_space=None, + data_range=None, + bit_depth=None, + max_cll=None, + max_fall=None, + ): + """设置HDR信号格式""" + try: + if color_space: + colorimetry = self._get_colorimetry_from_color_space(color_space) + if colorimetry: + self.color_mode.colorimetry = colorimetry + + if data_range: + if data_range == "Full": + self.color_mode.dynamic_range = ( + UniTAP.ColorInfo.DynamicRange.DR_VESA + ) + elif data_range == "Limited": + self.color_mode.dynamic_range = UniTAP.ColorInfo.DynamicRange.DR_CTA + + if bit_depth: + bpc = UCDEnum.SignalFormat.BitDepth.get_bit_value(bit_depth) + self.color_mode.bpc = bpc + + if self.current_timing: + self.set_video_mode() + + return True + + except Exception as e: + return False + + def _get_colorimetry_from_color_space(self, color_space): + """将色彩空间字符串转换为UniTAP.ColorInfo.Colorimetry""" + colorimetry_map = { + "BT.709": UniTAP.ColorInfo.Colorimetry.CM_ITUR_BT709, + "BT.601": UniTAP.ColorInfo.Colorimetry.CM_ITUR_BT601, + "BT.2020": UniTAP.ColorInfo.Colorimetry.CM_ITUR_BT2020_RGB, + "DCI-P3": UniTAP.ColorInfo.Colorimetry.CM_DCI_P3, + } + return colorimetry_map.get(color_space) diff --git a/utils/baseSerail.py b/utils/baseSerail.py new file mode 100644 index 0000000..d9eef57 --- /dev/null +++ b/utils/baseSerail.py @@ -0,0 +1,117 @@ +# -*- coding: UTF-8 -*- +import serial +import time +import binascii + +# CRC16矩阵表; +CRC_TABLE = [ + 0x0000, 0x1021, 0x2042, 0x3063, + 0x4084, 0x50A5, 0x60C6, 0x70E7, + 0x8108, 0x9129, 0xA14A, 0xB16B, + 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF ] + + +# 计算CRC16值; +def crc16(bys, bysLen): + uwCRC, ucTemp = 0xFFFF, 0x00 + index = 0 + while index < bysLen: + ucTemp = (uwCRC >> 0x0C) & 0xFF + uwCRC <<= 4 + uwCRC ^= CRC_TABLE[ucTemp ^ ((bys[index]) >> 0x04)] + uwCRC &= 0xFFFF + ucTemp = (uwCRC >> 0x0C) & 0xFF + uwCRC <<= 4 + uwCRC ^= CRC_TABLE[ucTemp ^ (bys[index] & 0x0F)] + uwCRC &= 0xFFFF + index += 1 + return uwCRC + +def convert_rgb16(rgb16_values): + data = bytearray() + for value in rgb16_values: + high_byte = (value & 0xFF00) >> 8 + low_byte = value & 0x00FF + + data.append(high_byte) + data.append(low_byte) + + return data + +class BaseSerial(): + def __init__(self): + self.exception = None + self.ser = serial.Serial() + + def open(self, port, baudrate=115200, bytesize=8, parity=serial.PARITY_NONE, stopbits=1, timeout=3, + writeTimeout=3): + if type(port) == type(0): + self.ser.port = "COM" + str(port) + else: + self.ser.port = port + # 波特率; + self.ser.baudrate = baudrate + # 数据位; + self.ser.bytesize = bytesize + # 校验码; + self.ser.parity = parity # PARITY_NONE=0 + # 停止位; + self.ser.stopbits = stopbits + # 串口读超时值 + self.ser.timeout = timeout + # 串口写超时值 + self.ser.write_timeout = writeTimeout + try: + self.ser.open() + return self.ser.is_open + except Exception as e: + self.exception = e + print('打开串口异常', str(e)) + return False + + def reOpen(self): + self.exception = None + self.ser.close() + try: + self.ser.open() + return self.ser.is_open + except Exception as e: + self.exception = e + return False + + def close(self): + self.ser.close() + + def write(self, cmd: bytearray): + try: + self.exception = None + writelen = self.ser.write(cmd) + self.ser.flush() + # print("writehex:" + str(binascii.b2a_hex(cmd)) + " writelen=" + str(writelen)) + return True if writelen == len(cmd) else False + except Exception as e: + self.exception = e + print('写串口异常', str(e)) + return False + + def read(self, size=0): + try: + self.exception = None + bys = self.ser.read(1) + for i in range(0, 3): + while self.ser.in_waiting > 0: + d = self.ser.read(1) + bys = bys + d + time.sleep(0.01) + + if bys.__len__() < size: + time.sleep(0.1) + if self.ser.in_waiting > 0: + d = self.ser.read(self.ser.in_waiting) + bys = bys + d + # print("readhex:" + str(binascii.hexlify(bys)) + " readlen=" + str(bys.__len__())) + return bys + except Exception as e: + self.exception = e + print('读串口异常', str(e)) + return None \ No newline at end of file diff --git a/utils/caSerail.py b/utils/caSerail.py new file mode 100644 index 0000000..630a22a --- /dev/null +++ b/utils/caSerail.py @@ -0,0 +1,181 @@ +# -*- coding: UTF-8 -*- +import re +import time +from utils.baseSerail import BaseSerial +# from baseSerail import BaseSerial +import colour + +class CASerail(BaseSerial): + def __init__(self): + BaseSerial.__init__(self) + + def __del__(self): + self.close() + + def checkport(self): + if not self.ser.is_open: + self.reOpen() + + return self.ser.is_open + + def sendcmd(self, cmd:str): + self.write(cmd.encode('utf-8')) + return self.read() + + '''开启通讯''' + def startCommunication(self): + self.sendcmd('COM,1\r') + + '''结束通讯''' + def endCommunication(self): + self.sendcmd('COM,0\r') + + '''设置用于测量的校准通道''' + def setChannel(self, channel:str): + self.sendcmd('MCH,%s\r'%(channel)) + + ''' + 设置显示模式 + ''' + def setDisplayMode(self, mode:int): + return self.sendcmd('MDS,%d\r'%(mode)) + + '''设置显示模式为XYZ''' + def set_XYZ_Display(self): + return self.sendcmd('MDS,%d\r'%(7)) + + '''设置显示模式为xyLv''' + def set_xyLv_Display(self): + return self.sendcmd('MDS,%d\r'%(0)) + + '''设置显示模式为xyLv''' + def set_all_Display(self): + return self.sendcmd('MDS,%d\r'%(0)) + + def set_Display(self, mode:int): + ''' + 0: x,y,LV + 1: Tcp,duv,LV + 5: u’,v’,LV + 7: X,Y,Z + 8: λd,Pe,LV + ''' + return self.sendcmd('MDS,%d\r'%(mode)) + + + ''' + 设置同步模式和同步频率 + ''' + def setSynchMode(self, mode: int, freq: int = None): + if freq == None: + return self.sendcmd('SCS,%d\r'%(mode)) + else: + return self.sendcmd('SCS,%d,%d\r'%(mode,freq)) + + ''' + 设置测量速度 + ''' + def setMeasureSpeed(self, speed: int): + return self.sendcmd('FSC,%d\r'%(speed)) + + '''执行零点校准''' + def setZeroCalibration(self): + return self.sendcmd('ZRC\r') + + '''读取当前显示模式下的三值''' + def readDisplay(self): + data = self.sendcmd('MES\r') + x,y,z = self.__get_display_data(data.decode('utf-8')) + return float(x), float(y), float(z) + + '''获取当前显示模式值''' + def __get_display_data(self, data): + p = re.compile(r"(\D*)(\d+),P1 (.*);(.*);(.*)", re.DOTALL) + mo = p.search(data) + if mo is None: + print("无匹配正则") + pw = re.compile(r"(\D*)(\d+)", re.DOTALL) + mo = pw.search(data) + if mo is None: + # print("短匹配失败") + return None, None, None + else: + return None, None, None + else: + # print(mo.group(1), mo.group(2), mo.group(3), mo.group(4), mo.group(5)) + return mo.group(3), mo.group(4), mo.group(5) + + '''读取所有数据''' + def readAllDisplay(self): + data = self.sendcmd('MES,2\r') + x,y,lv,X,Y,Z = self.__get_all_display_data(data.decode('utf-8')) + return float(x), float(y), float(lv), float(X), float(Y), float(Z) + + '''获取所有显示模式值''' + def __get_all_display_data(self, data): + p = re.compile(r"(\D*)(\d+),P1,(.*),(.*),(.*),(.*),(.*),(.*),(.*),(.*),(.*)", re.DOTALL) + mo = p.search(data) + if mo is None: + return None, None, None, None, None, None + else: + return mo.group(4), mo.group(5), mo.group(6), mo.group(9), mo.group(10), mo.group(11) + +def xyY_to_XYZ( x, y, Y): + X = x * Y / y + Z = (1 - x - y) * Y / y + return X, Y, Z + + +def XYZ_to_lambda_pe(XYZ, illuminant='D65'): + xy = colour.XYZ_to_xy(XYZ) + white = colour.CCS_ILLUMINANTS['CIE 1931 2 Degree Standard Observer'][illuminant] + λd, comp, xy_intersection = colour.dominant_wavelength(xy, white) + Pe = colour.excitation_purity(xy, white) + return xy, λd, comp, Pe + + +def test_ca410(): + print('\r\rtest ca410\r\r') + ca = CASerail() + ca.open("COM3", 19200, 7, 'E', 2) + ca.startCommunication() + ca.setSynchMode(3) + ca.setMeasureSpeed(2) + ca.setZeroCalibration() + ca.setChannel('01') + + ca.set_Display(0) + xyYXYZ = ca.readAllDisplay() + print(f"xyYXYZ: {xyYXYZ}") + XYZ = xyYXYZ[3], xyYXYZ[4], xyYXYZ[5] + xy = colour.XYZ_to_xy(XYZ) + print(f"xy: {xy}") + + ca.set_Display(1) + tcpduvlvXYZ = ca.readAllDisplay() + print(f"tcpduvlvXYZ: {tcpduvlvXYZ}") + XYZ = tcpduvlvXYZ[3], tcpduvlvXYZ[4], tcpduvlvXYZ[5] + xy = colour.XYZ_to_xy(XYZ) + TCP = colour.temperature.xy_to_CCT(xy, 'Hernandez 1999') + print(f"TCP: {TCP}K") + + ca.set_Display(5) + uvlvXYZ = ca.readAllDisplay() + print(f"uvlvXYZ: {uvlvXYZ}") + XYZ = uvlvXYZ[3], uvlvXYZ[4], uvlvXYZ[5] + xy = colour.XYZ_to_xy(XYZ) + u_prime, v_prime, _ = colour.XYZ_to_CIE1976UCS(XYZ) + print(f"u': {u_prime}, v': {v_prime}") + + + ca.set_Display(8) + lambdaPeXYZ = ca.readAllDisplay() + print(f"lambdaPeXYZ: {lambdaPeXYZ}") + XYZ = lambdaPeXYZ[3], lambdaPeXYZ[4], lambdaPeXYZ[5] + xy, λd, isComplementary, Pe = XYZ_to_lambda_pe(XYZ) + print(f"xy: {xy}, λd: {λd}, isComplementary: {isComplementary}, Pe: {Pe}") + + + ca.endCommunication() + +# test_ca410() \ No newline at end of file diff --git a/utils/data_range_converter.py b/utils/data_range_converter.py new file mode 100644 index 0000000..1bd16d1 --- /dev/null +++ b/utils/data_range_converter.py @@ -0,0 +1,288 @@ +# -*- coding: UTF-8 -*- +""" +数据范围转换器 +将 Full Range (0-255) 转换为 Limited Range (16-235) + +使用方法: + from utils.data_range_converter import DataRangeConverter + + converter = DataRangeConverter() + converted_params = converter.convert(pattern_params, "Limited") +""" + + +class DataRangeConverter: + """数据范围转换器""" + + def __init__(self, verbose=True): + """ + 初始化转换器 + + Args: + verbose: 是否打印详细日志 + """ + self.verbose = verbose + + def convert_value(self, value): + """ + 将单个 Full Range 值转换为 Limited Range + + 转换公式: + limited = 16 + (value / 255) × (235 - 16) + + Args: + value: Full Range 值 (0-255) + + Returns: + int: Limited Range 值 (16-235) + """ + # 边界值直接映射 + if value == 0: + return 16 + elif value == 255: + return 235 + else: + # 线性映射 + limited = 16 + round((value / 255.0) * (235 - 16)) + # 限制范围 + return max(16, min(235, limited)) + + def convert_rgb(self, r, g, b): + """ + 转换单个 RGB 值 + + Args: + r, g, b: Full Range RGB 值 (0-255) + + Returns: + tuple: Limited Range RGB 值 (16-235) + """ + return (self.convert_value(r), self.convert_value(g), self.convert_value(b)) + + def convert(self, pattern_params, data_range="Full"): + """ + 转换图案参数列表 + + Args: + pattern_params: 图案参数列表 [[r,g,b], [r,g,b], ...] + data_range: "Full" 或 "Limited" + + Returns: + list: 转换后的图案参数列表 + """ + # Full Range 不需要转换 + if data_range == "Full": + if self.verbose: + print("✓ 使用 Full Range (0-255),无需转换") + return pattern_params + + # Limited Range 需要转换 + if data_range == "Limited": + if self.verbose: + self._print_header() + + converted = [] + for i, rgb in enumerate(pattern_params): + # 获取原始值 + r_orig, g_orig, b_orig = rgb[0], rgb[1], rgb[2] + + # 转换 + r_new, g_new, b_new = self.convert_rgb(r_orig, g_orig, b_orig) + + # 保存 + converted.append([r_new, g_new, b_new]) + + # 打印日志(关键值) + if self.verbose: + self._print_conversion( + i, r_orig, g_orig, b_orig, r_new, g_new, b_new + ) + + if self.verbose: + self._print_footer(len(pattern_params)) + + return converted + + # 未知范围,返回原始值 + else: + if self.verbose: + print(f"⚠ 未知的数据范围: {data_range},使用原始值") + return pattern_params + + def _print_header(self): + """打印转换头部信息""" + print("=" * 80) + print("【数据范围转换】Limited Range (16-235)") + print(" 转换公式: 16 + (value / 255) × (235 - 16)") + print("=" * 80) + + def _print_conversion(self, index, r_orig, g_orig, b_orig, r_new, g_new, b_new): + """ + 打印转换日志 + + 策略:只打印关键值,避免刷屏 + - 第一个和最后一个图案 + - 0, 128, 255 等关键值 + """ + # 判断是否需要打印 + should_print = False + + # 第一个和最后一个 + if index == 0: + should_print = True + label = "黑色" + elif index == len([]) - 1: # 需要在外部判断 + should_print = True + label = "白色" + # 关键 RGB 值 + elif ( + r_orig in [0, 128, 255] + or g_orig in [0, 128, 255] + or b_orig in [0, 128, 255] + ): + should_print = True + if r_orig == 128: + label = "50% 灰" + else: + label = "" + + if should_print: + diff = abs(r_new - r_orig) + print( + f" 图案 {index+1:2d} {label:8s}: " + f"RGB({r_orig:3d},{g_orig:3d},{b_orig:3d}) → " + f"RGB({r_new:3d},{g_new:3d},{b_new:3d}) " + f"(差值: {diff:+3d})" + ) + + def _print_footer(self, total_count): + """打印转换尾部信息""" + print(f"✓ 转换完成,共 {total_count} 个图案") + print("=" * 80) + + def get_info(self): + """获取转换器信息""" + return { + "name": "Data Range Converter", + "version": "1.0.0", + "full_range": "0-255", + "limited_range": "16-235", + "formula": "16 + (value / 255) × 219", + } + + +# ========== 便捷函数 ========== + + +def convert_pattern_params(pattern_params, data_range="Full", verbose=True): + """ + 便捷函数:转换图案参数 + + Args: + pattern_params: 图案参数列表 [[r,g,b], [r,g,b], ...] + data_range: "Full" 或 "Limited" + verbose: 是否打印日志 + + Returns: + list: 转换后的图案参数列表 + + 示例: + >>> from utils.data_range_converter import convert_pattern_params + >>> params = [[0,0,0], [255,255,255]] + >>> converted = convert_pattern_params(params, "Limited") + [[16,16,16], [235,235,235]] + """ + converter = DataRangeConverter(verbose=verbose) + return converter.convert(pattern_params, data_range) + + +def convert_single_rgb(r, g, b, data_range="Full"): + """ + 便捷函数:转换单个 RGB 值 + + Args: + r, g, b: RGB 值 (0-255) + data_range: "Full" 或 "Limited" + + Returns: + tuple: 转换后的 RGB 值 + + 示例: + >>> from utils.data_range_converter import convert_single_rgb + >>> r, g, b = convert_single_rgb(0, 0, 0, "Limited") + (16, 16, 16) + """ + if data_range == "Full": + return (r, g, b) + + converter = DataRangeConverter(verbose=False) + return converter.convert_rgb(r, g, b) + + +# ========== 测试代码 ========== + +if __name__ == "__main__": + """测试转换器""" + + print("=" * 80) + print("数据范围转换器 - 测试") + print("=" * 80) + + # 测试 1: 基本转换 + print("\n[测试 1] 基本转换...") + converter = DataRangeConverter(verbose=False) + + test_values = [0, 16, 64, 128, 192, 235, 255] + print(" Full Range → Limited Range:") + for v in test_values: + limited = converter.convert_value(v) + diff = limited - v + print(f" {v:3d} → {limited:3d} (差值: {diff:+3d})") + + # 测试 2: RGB 转换 + print("\n[测试 2] RGB 转换...") + test_rgb = [ + (0, 0, 0), + (128, 128, 128), + (255, 255, 255), + ] + + for r, g, b in test_rgb: + r_new, g_new, b_new = converter.convert_rgb(r, g, b) + print(f" RGB({r},{g},{b}) → RGB({r_new},{g_new},{b_new})") + + # 测试 3: 完整转换流程 + print("\n[测试 3] 完整转换流程...") + pattern_params = [ + [255, 255, 255], # 100% 白 + [230, 230, 230], # 90% + [204, 204, 204], # 80% + [128, 128, 128], # 50% + [0, 0, 0], # 0% 黑 + ] + + converted = converter.convert(pattern_params, "Limited") + + print("\n 对比:") + for i, (orig, conv) in enumerate(zip(pattern_params, converted)): + print(f" [{i+1}] {orig} → {conv}") + + # 测试 4: 便捷函数 + print("\n[测试 4] 便捷函数...") + result = convert_pattern_params( + [[0, 0, 0], [255, 255, 255]], "Limited", verbose=False + ) + print(f" 结果: {result}") + + r, g, b = convert_single_rgb(128, 128, 128, "Limited") + print(f" RGB(128,128,128) → RGB({r},{g},{b})") + + # 测试 5: 获取信息 + print("\n[测试 5] 转换器信息...") + info = converter.get_info() + for key, value in info.items(): + print(f" {key}: {value}") + + print("\n" + "=" * 80) + print("测试完成") + print("=" * 80) diff --git a/utils/local_dimming_test.py b/utils/local_dimming_test.py new file mode 100644 index 0000000..9b59a38 --- /dev/null +++ b/utils/local_dimming_test.py @@ -0,0 +1,585 @@ +""" +Local Dimming 测试模块 +功能: +- 生成不同百分比的白色窗口图片 +- 通过 UCD 发送图片到显示器 +- 自动采集 CA410 亮度数据 +- 记录并导出测试结果 +""" + +import os +import sys +import time +import atexit +import shutil +import numpy as np +from PIL import Image, ImageDraw +import UniTAP + + +class LocalDimmingController: + """Local Dimming 控制器 - 用于发送不同百分比窗口 Pattern""" + + def __init__(self, ucd_controller): + """ + 初始化 Local Dimming 控制器 + + Args: + ucd_controller: UCD323 控制器实例 + """ + self.ucd = ucd_controller + + # 兼容打包后的路径 + if getattr(sys, "frozen", False): + base_dir = os.path.dirname(sys.executable) + else: + base_dir = os.getcwd() + + self.temp_dir = os.path.join(base_dir, "temp_local_dimming") + + # 创建临时目录 + if not os.path.exists(self.temp_dir): + os.makedirs(self.temp_dir) + print(f"[LD] 创建临时目录: {self.temp_dir}") + + self.cached_images = {} # 缓存已生成的图片 {(分辨率, 百分比): 文件路径} + + # 注册退出时自动清理 + atexit.register(self.cleanup) + + print("[LD] Local Dimming 控制器已初始化") + + def send_window_pattern_with_resolution(self, percentage, width, height): + """ + 发送指定百分比和分辨率的白色窗口 Pattern + + Args: + percentage: 窗口面积百分比 (1-100) + width: 图像宽度 + height: 图像高度 + + Returns: + bool: 是否成功 + """ + try: + # 检查设备连接状态 + if not self.ucd.status: + print("[LD 错误] 设备未连接") + return False + + print(f"\n[LD] 开始发送 {percentage}% 窗口 Pattern") + print(f"[LD] 使用分辨率: {width}x{height}") + + # 获取 Pattern Generator 和 Audio Generator + # 兼容 UCDController(仅 HDMI)和 UCD323Controller(多接口) + if hasattr(self.ucd, 'current_interface'): + # UCD323Controller(多接口支持) + interface = self.ucd.current_interface + if interface == "HDMI": + pg = self.ucd.role.hdtx.pg + ag = self.ucd.role.hdtx.ag + elif interface == "Type-C" or interface == "DP": + pg = self.ucd.role.dptx.pg + ag = self.ucd.role.dptx.ag + else: + print(f"[LD 错误] 不支持的接口类型: {interface}") + return False + else: + # UCDController(仅 HDMI) + pg = self.ucd.role.hdtx.pg + ag = self.ucd.role.hdtx.ag + + # 先停止音频,避免蜂鸣声 + try: + ag.stop_generate() + print("[LD] 已停止音频生成") + except Exception as e: + print(f"[LD 警告] 停止音频失败: {e}") + + # 检查缓存 + cache_key = (f"{width}x{height}", percentage) + + if cache_key in self.cached_images: + image_path = self.cached_images[cache_key] + if os.path.exists(image_path): + print(f"[LD] 使用缓存图片: {image_path}") + else: + print(f"[LD] 缓存图片不存在,重新生成...") + image_path = self._generate_and_save_image( + width, height, percentage, cache_key + ) + else: + print(f"[LD] 正在生成 {percentage}% 窗口图像...") + image_path = self._generate_and_save_image( + width, height, percentage, cache_key + ) + + # 发送图像到设备 + print(f"[LD] 正在发送图像到设备...") + + # 设置 ColorInfo + color_mode = UniTAP.ColorInfo() + color_mode.color_format = UniTAP.ColorInfo.ColorFormat.CF_RGB + color_mode.bpc = 8 + color_mode.colorimetry = UniTAP.ColorInfo.Colorimetry.CM_sRGB + + # 获取当前 timing + try: + current_vm = pg.get_vm() + timing = ( + current_vm.timing + if current_vm and hasattr(current_vm, "timing") + else None + ) + except: + timing = None + + # 如果有 timing,设置 VideoMode + if timing: + video_mode = UniTAP.VideoMode(timing=timing, color_info=color_mode) + pg.set_vm(vm=video_mode) + + # 设置图片 Pattern + pg.set_pattern(pattern=image_path) + + # 应用 + pg.apply() + + print(f"[LD] {percentage}% 窗口 Pattern 已发送到设备") + return True + + except Exception as e: + print(f"[LD 异常] 发送 {percentage}% 窗口失败: {e}") + import traceback + + traceback.print_exc() + return False + + def send_window_pattern(self, percentage): + """ + 发送指定百分比的白色窗口 Pattern(从 GUI 获取分辨率) + + Args: + percentage: 窗口面积百分比 (1-100) + + Returns: + bool: 是否成功 + """ + # 从设备当前 timing 获取分辨率 + width, height = self.get_current_resolution() + return self.send_window_pattern_with_resolution(percentage, width, height) + + def get_current_resolution(self): + """ + 从设备当前 timing 获取显示器分辨率 + + Returns: + tuple: (width, height) + """ + try: + # 方式1:从 Pattern Generator 的当前 VideoMode 获取 + if hasattr(self.ucd, 'current_interface'): + interface = self.ucd.current_interface + if interface == "HDMI": + pg = self.ucd.role.hdtx.pg + elif interface == "Type-C" or interface == "DP": + pg = self.ucd.role.dptx.pg + else: + pg = None + else: + pg = self.ucd.role.hdtx.pg + + if pg: + current_vm = pg.get_vm() + if current_vm and hasattr(current_vm, "timing") and current_vm.timing: + timing = current_vm.timing + if hasattr(timing, "h_active") and hasattr(timing, "v_active"): + width = timing.h_active + height = timing.v_active + print(f"[LD] 从当前 timing 获取分辨率: {width}x{height}") + return width, height + + # 方式2:从 current_timing 属性获取 + if hasattr(self.ucd, "current_timing") and self.ucd.current_timing: + timing = self.ucd.current_timing + if hasattr(timing, "h_active") and hasattr(timing, "v_active"): + width = timing.h_active + height = timing.v_active + print(f"[LD] 从 current_timing 获取分辨率: {width}x{height}") + return width, height + + except Exception as e: + print(f"[LD 警告] 获取分辨率失败: {e}") + + print("[LD 警告] 使用默认分辨率 3840x2160") + return 3840, 2160 + + def _generate_and_save_image(self, width, height, percentage, cache_key): + """ + 生成并保存窗口图像 + + Args: + width: 图像宽度 + height: 图像高度 + percentage: 窗口面积百分比 + cache_key: 缓存键 + + Returns: + str: 图像文件路径 + """ + # 生成图像 + image_array = self._create_window_image(width, height, percentage) + + # 保存到项目目录 + filename = f"window_{width}x{height}_{percentage:03d}percent.png" + image_path = os.path.join(self.temp_dir, filename) + + image = Image.fromarray(image_array, mode="RGB") + image.save(image_path, format="PNG") + + # 缓存 + self.cached_images[cache_key] = image_path + + print(f"[LD] 图像已保存: {image_path}") + return image_path + + def _create_window_image(self, width, height, percentage): + """ + 创建窗口图像 + 黑色背景 + 居中白色矩形窗口(保持屏幕比例) + + Args: + width: 图像宽度 + height: 图像高度 + percentage: 窗口面积百分比 (1-100) + + Returns: + numpy.ndarray: RGB 图像数组 (height, width, 3) + """ + # 创建黑色背景 + image = np.zeros((height, width, 3), dtype=np.uint8) + + # 计算窗口尺寸(保持屏幕比例) + scale_factor = (percentage / 100.0) ** 0.5 + window_width = int(width * scale_factor) + window_height = int(height * scale_factor) + + # 100% 时强制全屏 + if percentage == 100: + window_width = width + window_height = height + + # 计算居中位置 + x1 = (width - window_width) // 2 + y1 = (height - window_height) // 2 + x2 = x1 + window_width + y2 = y1 + window_height + + # 绘制白色窗口 + image[y1:y2, x1:x2] = [255, 255, 255] + + print( + f"[LD] 图像生成完成: {width}x{height}, 窗口 {window_width}x{window_height}" + ) + return image + + def cleanup(self): + """清理临时文件夹""" + if os.path.exists(self.temp_dir): + try: + shutil.rmtree(self.temp_dir) + print(f"[LD] 临时文件夹已删除: {self.temp_dir}") + except Exception as e: + print(f"[LD 警告] 删除临时文件夹失败: {e}") + + def __del__(self): + """析构函数:清理临时文件(备用机制)""" + try: + self.cleanup() + except: + pass + + +class LocalDimmingTest: + def __init__(self, ucd_controller, ca_serial, log_callback=None): + """ + 初始化 Local Dimming 测试 + + Args: + ucd_controller: UCD323 控制器实例 + ca_serial: CA410 串口实例 + log_callback: 日志回调函数 + """ + self.ucd = ucd_controller + self.ca = ca_serial + self.log = log_callback if log_callback else print + + # 临时图片目录 + self.temp_dir = self._init_temp_dir() + + # 测试结果 + self.test_results = [] + + # 测试配置 + self.window_percentages = [1, 2, 5, 10, 18, 25, 50, 75, 100] + self.wait_time = 2.0 # 每次切换后等待时间(秒) + + # 停止标志 + self.stop_flag = False + + self.log("✓ Local Dimming 测试模块已初始化") + + def _init_temp_dir(self): + """初始化临时目录""" + if getattr(sys, "frozen", False): + base_dir = os.path.dirname(sys.executable) + else: + base_dir = os.getcwd() + + temp_dir = os.path.join(base_dir, "temp_local_dimming") + os.makedirs(temp_dir, exist_ok=True) + return temp_dir + + def generate_window_image(self, width, height, percentage): + """ + 生成窗口图片(黑色背景 + 居中白色矩形窗口) + + Args: + width: 图像宽度 + height: 图像高度 + percentage: 窗口面积百分比 (1-100) + + Returns: + str: 图片文件路径 + """ + # 计算窗口尺寸(保持屏幕比例) + scale_factor = (percentage / 100.0) ** 0.5 + window_width = int(width * scale_factor) + window_height = int(height * scale_factor) + + # 100% 时强制全屏 + if percentage == 100: + window_width = width + window_height = height + + # 创建黑色背景 + image = np.zeros((height, width, 3), dtype=np.uint8) + + # 计算居中位置 + x1 = (width - window_width) // 2 + y1 = (height - window_height) // 2 + x2 = x1 + window_width + y2 = y1 + window_height + + # 绘制白色窗口 + image[y1:y2, x1:x2] = [255, 255, 255] + + # 保存图片 + filename = f"window_{width}x{height}_{percentage:03d}percent.png" + image_path = os.path.join(self.temp_dir, filename) + + pil_image = Image.fromarray(image, mode="RGB") + pil_image.save(image_path, format="PNG") + + self.log(f" ✓ 图片已生成: {window_width}×{window_height} px") + return image_path + + def send_image_to_ucd(self, image_path): + """ + 通过 UCD 发送图片到显示器 + + Args: + image_path: 图片文件路径 + + Returns: + bool: 是否成功 + """ + try: + # 获取 Pattern Generator 和 Audio Generator + # 兼容 UCDController(仅 HDMI)和 UCD323Controller(多接口) + if hasattr(self.ucd, 'current_interface'): + interface = self.ucd.current_interface + if interface == "HDMI": + pg = self.ucd.role.hdtx.pg + ag = self.ucd.role.hdtx.ag + elif interface == "Type-C" or interface == "DP": + pg = self.ucd.role.dptx.pg + ag = self.ucd.role.dptx.ag + else: + self.log(f" ❌ 不支持的接口类型: {interface}") + return False + else: + # UCDController(仅 HDMI) + pg = self.ucd.role.hdtx.pg + ag = self.ucd.role.hdtx.ag + + # 停止音频 + try: + ag.stop_generate() + except: + pass + + # 设置 ColorInfo + color_mode = UniTAP.ColorInfo() + color_mode.color_format = UniTAP.ColorInfo.ColorFormat.CF_RGB + color_mode.bpc = 8 + color_mode.colorimetry = UniTAP.ColorInfo.Colorimetry.CM_sRGB + + # 获取当前 timing + try: + current_vm = pg.get_vm() + timing = ( + current_vm.timing + if current_vm and hasattr(current_vm, "timing") + else None + ) + except: + timing = None + + # 设置 VideoMode + if timing: + video_mode = UniTAP.VideoMode(timing=timing, color_info=color_mode) + pg.set_vm(vm=video_mode) + + # 设置图片 Pattern + pg.set_pattern(pattern=image_path) + + # 应用 + pg.apply() + + return True + + except Exception as e: + self.log(f" ❌ 发送图片失败: {str(e)}") + import traceback + + traceback.print_exc() + return False + + def measure_luminance(self): + """ + 使用 CA410 采集亮度 + + Returns: + tuple: (x, y, lv, X, Y, Z) 或 None + """ + try: + if not self.ca: + self.log(" ❌ CA410 未连接") + return None + + # 采集数据 + x, y, lv, X, Y, Z = self.ca.readAllDisplay() + + if x is not None and y is not None and lv is not None: + self.log(f" ✓ 采集亮度: {lv:.2f} cd/m²") + return (x, y, lv, X, Y, Z) + else: + self.log(" ❌ 采集数据失败") + return None + + except Exception as e: + self.log(f" ❌ 采集亮度异常: {str(e)}") + return None + + def run_test(self, resolution="3840x2160"): + """ + 执行完整的 Local Dimming 测试 + + Args: + resolution: 分辨率字符串,如 "3840x2160" + + Returns: + list: 测试结果 [(百分比, x, y, lv, X, Y, Z), ...] + """ + self.log("=" * 60) + self.log("开始 Local Dimming 测试") + self.log("=" * 60) + + # 重置停止标志 + self.stop_flag = False + + # 解析分辨率 + try: + width, height = map(int, resolution.split("x")) + except: + width, height = 3840, 2160 + self.log(f" ⚠️ 分辨率解析失败,使用默认值: {width}x{height}") + + self.log(f" 分辨率: {width}x{height}") + self.log(f" 测试窗口: {self.window_percentages}") + self.log(f" 等待时间: {self.wait_time} 秒") + self.log("") + + self.test_results = [] + + for i, percentage in enumerate(self.window_percentages, start=1): + # 检查停止标志 + if self.stop_flag: + self.log("⚠️ 测试已停止") + break + + self.log(f"[{i}/{len(self.window_percentages)}] 测试 {percentage}% 窗口...") + + # 1. 生成图片 + image_path = self.generate_window_image(width, height, percentage) + + # 2. 发送到 UCD + if not self.send_image_to_ucd(image_path): + self.log(f" ❌ {percentage}% 窗口发送失败,跳过") + continue + + # 3. 等待稳定 + self.log(f" ⏳ 等待 {self.wait_time} 秒...") + time.sleep(self.wait_time) + + # 4. 采集亮度 + result = self.measure_luminance() + + if result: + x, y, lv, X, Y, Z = result + self.test_results.append((percentage, x, y, lv, X, Y, Z)) + self.log(f" ✅ {percentage}% 窗口测试完成") + else: + self.log(f" ❌ {percentage}% 窗口采集失败") + + self.log("") + + self.log("=" * 60) + self.log("✅ Local Dimming 测试完成") + self.log( + f" 成功测试: {len(self.test_results)}/{len(self.window_percentages)} 个窗口" + ) + self.log("=" * 60) + + return self.test_results + + def stop(self): + """停止测试""" + self.stop_flag = True + self.log("⚠️ 正在停止测试...") + + def get_results_summary(self): + """获取测试结果摘要""" + if not self.test_results: + return None + + luminances = [lv for _, _, _, lv, _, _, _ in self.test_results] + + return { + "data_points": self.test_results, + "max_luminance": max(luminances), + "min_luminance": min(luminances), + "avg_luminance": sum(luminances) / len(luminances), + } + + def cleanup(self): + """清理临时文件""" + try: + import shutil + + if os.path.exists(self.temp_dir): + shutil.rmtree(self.temp_dir) + self.log(f"✓ 临时文件已清理: {self.temp_dir}") + except Exception as e: + self.log(f"⚠️ 清理临时文件失败: {e}") diff --git a/utils/pq/pq_config.py b/utils/pq/pq_config.py new file mode 100644 index 0000000..af07a9a --- /dev/null +++ b/utils/pq/pq_config.py @@ -0,0 +1,601 @@ +# PQ自动化测试配置模块 +import json +import copy + + +class PQConfig: + def __init__(self, current_test_type="screen_module", device_config={}, pattern={}): + self.default_test_types = { + "screen_module": { + "name": "屏模组性能测试", + "test_items": ["gamut", "gamma", "cct", "contrast"], + "timing": "DMT 1920x 1080 @ 60Hz", + "color_format": "RGB", + "bpc": 8, + "colorimetry": "sRGB", + }, + "sdr_movie": { + "name": "SDR Movie测试", + "test_items": ["gamut", "gamma", "cct", "contrast", "accuracy"], + "timing": "DMT 1920x 1080 @ 60Hz", + "color_format": "RGB", + "bpc": 8, + "colorimetry": "sRGB", + }, + "hdr_movie": { + "name": "HDR Movie测试", + "test_items": ["gamut", "eotf", "cct", "contrast", "accuracy"], + "timing": "DMT 1920x 1080 @ 60Hz", + "color_format": "RGB", + "bpc": 8, + "colorimetry": "sRGB", + }, + } + + # 设备连接配置 + self.device_config = { + "ca_com": "COM1", + "ucd_list": "0: UCD-323 [2128C209]", + "ca_channel": "0", + } + + # ========== RGB Pattern 配置 ========== + self.default_pattern_rgb = { + "pattern_mode": "SolidColor", + "measurement_bit_depth": 8, + "measurement_max_value": 2, + "pattern_params": [ + [255, 0, 0], # 红色 + [0, 255, 0], # 绿色 + [0, 0, 255], # 蓝色 + ], + } + + # ========== 灰阶 Pattern 配置 ========== + self.default_pattern_gray = { + "pattern_mode": "SolidColor", + "measurement_bit_depth": 8, + "measurement_max_value": 10, + "pattern_params": [ + [255, 255, 255], # 100% 白色 + [230, 230, 230], # 90% + [205, 205, 205], # 80% + [179, 179, 179], # 70% + [154, 154, 154], # 60% + [128, 128, 128], # 50% + [102, 102, 102], # 40% + [78, 78, 78], # 30% + [52, 52, 52], # 20% + [26, 26, 26], # 10% + [0, 0, 0], # 0% 黑色 + ], + } + + # ========== 色准 Pattern 配置(29色 - SDR 和 HDR 通用)========== + self.default_pattern_accuracy = { + "pattern_mode": "SolidColor", + "measurement_bit_depth": 8, + "measurement_max_value": 28, # 29个颜色,最大索引是28 + "pattern_params": [ + # ========== 灰阶 (5个) ========== + [255, 255, 255], # 0: White + [230, 230, 230], # 1: Gray 80 + [209, 209, 209], # 2: Gray 65 + [186, 186, 186], # 3: Gray 50 + [158, 158, 158], # 4: Gray 35 + # ========== ColorChecker 24色 (18个) ========== + [115, 82, 66], # 5: Dark Skin + [194, 150, 130], # 6: Light Skin + [94, 122, 156], # 7: Blue Sky + [89, 107, 66], # 8: Foliage + [130, 128, 176], # 9: Blue Flower + [99, 189, 168], # 10: Bluish Green + [217, 120, 41], # 11: Orange + [74, 92, 163], # 12: Purplish Blue + [194, 84, 97], # 13: Moderate Red + [92, 61, 107], # 14: Purple + [158, 186, 64], # 15: Yellow Green + [230, 161, 46], # 16: Orange Yellow + [51, 61, 150], # 17: Blue (Legacy) + [71, 148, 71], # 18: Green (Legacy) + [176, 48, 59], # 19: Red (Legacy) + [237, 199, 33], # 20: Yellow (Legacy) + [186, 84, 145], # 21: Magenta (Legacy) + [0, 133, 163], # 22: Cyan (Legacy) + # ========== 100% 饱和色 (6个) ========== + [255, 0, 0], # 23: 100% Red + [0, 255, 0], # 24: 100% Green + [0, 0, 255], # 25: 100% Blue + [0, 255, 255], # 26: 100% Cyan + [255, 0, 255], # 27: 100% Magenta + [255, 255, 0], # 28: 100% Yellow + ], + } + + self.default_pattern_temp = { + "pattern_mode": "SolidColor", + "measurement_bit_depth": 8, + "measurement_max_value": 146, + "pattern_params": [ + [255, 255, 255], + [242, 242, 242], + [230, 230, 230], + [217, 217, 217], + [204, 204, 204], + [191, 191, 191], + [179, 179, 179], + [166, 166, 166], + [153, 153, 153], + [140, 140, 140], + [128, 128, 128], + [115, 115, 115], + [102, 102, 102], + [89, 89, 89], + [77, 77, 77], + [64, 64, 64], + [51, 51, 51], + [38, 38, 38], + [26, 26, 26], + [13, 13, 13], + [0, 0, 0], + + [255, 0, 0], + [242, 0, 0], + [230, 0, 0], + [217, 0, 0], + [204, 0, 0], + [191, 0, 0], + [179, 0, 0], + [166, 0, 0], + [153, 0, 0], + [140, 0, 0], + [128, 0, 0], + [115, 0, 0], + [102, 0, 0], + [89, 0, 0], + [77, 0, 0], + [64, 0, 0], + [51, 0, 0], + [38, 0, 0], + [26, 0, 0], + [13, 0, 0], + [0, 0, 0], + + [0, 255, 0], + [0, 242, 0], + [0, 230, 0], + [0, 217, 0], + [0, 204, 0], + [0, 191, 0], + [0, 179, 0], + [0, 166, 0], + [0, 153, 0], + [0, 140, 0], + [0, 128, 0], + [0, 115, 0], + [0, 102, 0], + [0, 89, 0], + [0, 77, 0], + [0, 64, 0], + [0, 51, 0], + [0, 38, 0], + [0, 26, 0], + [0, 13, 0], + [0, 0, 0], + + [0, 0, 255], + [0, 0, 242], + [0, 0, 230], + [0, 0, 217], + [0, 0, 204], + [0, 0, 191], + [0, 0, 179], + [0, 0, 166], + [0, 0, 153], + [0, 0, 140], + [0, 0, 128], + [0, 0, 115], + [0, 0, 102], + [0, 0, 89], + [0, 0, 77], + [0, 0, 64], + [0, 0, 51], + [0, 0, 38], + [0, 0, 26], + [0, 0, 13], + [0, 0, 0], + + [255, 255, 0], + [242, 242, 0], + [230, 230, 0], + [217, 217, 0], + [204, 204, 0], + [191, 191, 0], + [179, 179, 0], + [166, 166, 0], + [153, 153, 0], + [140, 140, 0], + [128, 128, 0], + [115, 115, 0], + [102, 102, 0], + [89, 89, 0], + [77, 77, 0], + [64, 64, 0], + [51, 51, 0], + [38, 38, 0], + [26, 26, 0], + [13, 13, 0], + [0, 0, 0], + + [0, 255, 255], + [0, 242, 242], + [0, 230, 230], + [0, 217, 217], + [0, 204, 204], + [0, 191, 191], + [0, 179, 179], + [0, 166, 166], + [0, 153, 153], + [0, 140, 140], + [0, 128, 128], + [0, 115, 115], + [0, 102, 102], + [0, 89, 89], + [0, 77, 77], + [0, 64, 64], + [0, 51, 51], + [0, 38, 38], + [0, 26, 26], + [0, 13, 13], + [0, 0, 0], + + [255, 0, 255], + [242, 0, 242], + [230, 0, 230], + [217, 0, 217], + [204, 0, 204], + [191, 0, 191], + [179, 0, 179], + [166, 0, 166], + [153, 0, 153], + [140, 0, 140], + [128, 0, 128], + [115, 0, 115], + [102, 0, 102], + [89, 0, 89], + [77, 0, 77], + [64, 0, 64], + [51, 0, 51], + [38, 0, 38], + [26, 0, 26], + [13, 0, 13], + [0, 0, 0], + ] + } + + # 自定义图案 + self.custom_pattern = { + "pattern_mode": "SolidColor", + "measurement_bit_depth": 8, + "measurement_max_value": 0, + "pattern_params": [], + } + + self.current_test_types = self.default_test_types + self.current_test_type = current_test_type + self.current_pattern = self.default_pattern_rgb + + # ========== 获取临时配置(用于 Full/Limited 转换)========== + def get_temp_config_with_converted_params(self, mode, converted_params): + """ + 创建一个临时配置对象,包含转换后的 pattern 参数 + + Args: + mode: "rgb" | "gray" | "accuracy" + converted_params: 转换后的参数列表(Full 或 Limited Range) + + Returns: + PQConfig: 临时配置对象(深拷贝,不影响原始配置) + """ + # 1. 深拷贝整个配置对象 + temp_config = copy.deepcopy(self) + + # 2. 设置正确的 pattern 模式 + if mode == "rgb": + temp_config.current_pattern = copy.deepcopy(self.default_pattern_rgb) + elif mode == "gray": + temp_config.current_pattern = copy.deepcopy(self.default_pattern_gray) + elif mode == "accuracy": + temp_config.current_pattern = copy.deepcopy(self.default_pattern_accuracy) + + # 3. 替换为转换后的参数 + temp_config.current_pattern["pattern_params"] = converted_params + + return temp_config + + def to_dict(self): + """将配置转换为字典格式""" + return { + "current_test_type": self.current_test_type, + "test_types": self.current_test_types, + "device_config": self.device_config, + "default_pattern_rgb": self.default_pattern_rgb, + "default_pattern_gray": self.default_pattern_gray, + "default_pattern_accuracy": self.default_pattern_accuracy, + "custom_pattern": self.custom_pattern, + } + + def from_dict(self, config_dict): + """从字典加载配置""" + self.current_test_type = config_dict.get("current_test_type", "screen_module") + self.current_test_types = config_dict.get("test_types", self.current_test_types) + self.device_config = config_dict.get("device_config", self.device_config) + + self.default_pattern_rgb = config_dict.get( + "default_pattern_rgb", self.default_pattern_rgb + ) + self.default_pattern_gray = config_dict.get( + "default_pattern_gray", self.default_pattern_gray + ) + + # ========== ✅ 强制使用新的 29色配置 ========== + loaded_accuracy = config_dict.get("default_pattern_accuracy", None) + + # 检查加载的配置是否是旧的 10色 + if loaded_accuracy and len(loaded_accuracy.get("pattern_params", [])) != 29: + print( + f"⚠️ 检测到旧的配置({len(loaded_accuracy.get('pattern_params', []))}色),强制使用新的 29色配置" + ) + # 使用 __init__ 中定义的新配置 + self.default_pattern_accuracy = self.default_pattern_accuracy + else: + self.default_pattern_accuracy = config_dict.get( + "default_pattern_accuracy", self.default_pattern_accuracy + ) + # ========================================== + + self.custom_pattern = config_dict.get("custom_pattern", self.custom_pattern) + + def save_to_file(self, filename): + """将配置保存到文件""" + with open(filename, "w", encoding="utf-8") as f: + json.dump(self.to_dict(), f, indent=4, ensure_ascii=False) + + def set_current_test_type(self, test_type): + """设置当前测试类型""" + if test_type in self.current_test_types: + self.current_test_type = test_type + return True + return False + + def set_current_test_items(self, test_items): + """设置当前测试类型的测试项""" + if self.current_test_type in self.current_test_types: + self.current_test_types[self.current_test_type]["test_items"] = test_items + return True + return False + + def set_current_timing(self, timing): + if self.current_test_type in self.current_test_types: + self.current_test_types[self.current_test_type]["timing"] = timing + return True + return False + + def set_device_config(self, ca_com, ucd_list, ca_channel): + """设置设备连接配置""" + self.device_config["ca_com"] = ca_com + self.device_config["ucd_list"] = ucd_list + self.device_config["ca_channel"] = ca_channel + return True + + def set_current_pattern(self, mode): + """设置当前模式的测试图案""" + if mode == "rgb": + self.current_pattern = self.default_pattern_rgb + elif mode == "gray": + self.current_pattern = self.default_pattern_gray + elif mode == "accuracy": # ✅ 色准模式(SDR 和 HDR 通用 29色) + self.current_pattern = self.default_pattern_accuracy + elif mode == "custom": + # self.current_pattern = self.custom_pattern + self.current_pattern = self.default_pattern_temp + else: + return False + + # 确保 measurement_max_value 是整数 + if "measurement_max_value" in self.current_pattern: + value = self.current_pattern["measurement_max_value"] + if isinstance(value, str): + try: + self.current_pattern["measurement_max_value"] = int(value) + except ValueError: + self.current_pattern["measurement_max_value"] = ( + len(self.current_pattern["pattern_params"]) - 1 + ) + + return True + + def set_custom_pattern(self, pattern_mode, pattern_params): + """设置自定义模式的测试项""" + self.custom_pattern["pattern_mode"] = pattern_mode + self.custom_pattern["pattern_params"] = pattern_params + self.custom_pattern["measurement_max_value"] = len(pattern_params) - 1 + return True + + # ========== ✅ 获取 29色名称列表 ========== + def get_accuracy_color_names(self): + """ + 获取色准测试的 29个颜色名称(SDR 和 HDR 通用) + + Returns: + list: 29个颜色名称 + """ + return [ + # 灰阶 (5个) + "White", + "Gray 80", + "Gray 65", + "Gray 50", + "Gray 35", + # ColorChecker 24色 (18个) + "Dark Skin", + "Light Skin", + "Blue Sky", + "Foliage", + "Blue Flower", + "Bluish Green", + "Orange", + "Purplish Blue", + "Moderate Red", + "Purple", + "Yellow Green", + "Orange Yellow", + "Blue (Legacy)", + "Green (Legacy)", + "Red (Legacy)", + "Yellow (Legacy)", + "Magenta (Legacy)", + "Cyan (Legacy)", + # 100% 饱和色 (6个) + "100% Red", + "100% Green", + "100% Blue", + "100% Cyan", + "100% Magenta", + "100% Yellow", + ] + + # ========== ✅ 获取 29色的 RGB 值 ========== + def get_accuracy_color_rgb(self): + """ + 获取色准测试的 RGB 值(用于标准值计算) + + Returns: + list: [(name, r, g, b), ...] + """ + names = self.get_accuracy_color_names() + rgb_values = self.default_pattern_accuracy["pattern_params"] + + return [(name, r, g, b) for name, (r, g, b) in zip(names, rgb_values)] + + def get_temp_pattern_names(self): + """获取客户模板测试(default_pattern_temp)的固定 pattern 名称列表""" + percentages = list(range(100, -1, -5)) + color_prefixes = ["W", "R", "G", "B", "Y", "C", "M"] + + names = [] + for prefix in color_prefixes: + for value in percentages: + names.append(f"{prefix} {value}%") + + pattern_count = len(self.default_pattern_temp.get("pattern_params", [])) + + if pattern_count <= len(names): + return names[:pattern_count] + + # 兜底:如果后续扩展了 pattern 数量,补充通用名称,避免索引越界。 + for i in range(len(names), pattern_count): + names.append(f"P {i + 1}") + + return names + + def get_test_item_chinese_names(self, test_items): + """获取测试项目的显示名称""" + item_names = [] + for item in test_items: + if item == "gamut": + item_names.append("色域") + elif item == "gamma": + item_names.append("Gamma") + elif item == "eotf": + item_names.append("EOTF") + elif item == "cct": + item_names.append("色度一致性") + elif item == "contrast": + item_names.append("对比度") + elif item == "accuracy": + item_names.append("色准") + else: + item_names.append(item) + return item_names + + def get_current_config(self): + """返回当前测试类型相关的所有配置信息""" + if self.current_test_type not in self.current_test_types: + return {} + + current_test = self.current_test_types[self.current_test_type] + + config_info = { + "test_type": self.current_test_type, + "test_name": current_test.get("name", "未知测试"), + "test_items": current_test.get("test_items", []), + "test_items_chinese": self.get_test_item_chinese_names( + current_test.get("test_items", []) + ), + "timing": current_test.get("timing", "DMT 1920x 1080 @ 60Hz"), + "color_format": current_test.get("color_format", "RGB"), + "bpc": current_test.get("bpc", 8), + "colorimetry": current_test.get("colorimetry", "sRGB"), + } + + return config_info + + +# ========== ✅ 验证代码(测试完成后可删除)========== +if __name__ == "__main__": + print("=" * 60) + print("验证 pq_config.py 配置") + print("=" * 60) + + config = PQConfig() + + # 检查 default_pattern_accuracy + pattern_count = len(config.default_pattern_accuracy["pattern_params"]) + max_value = config.default_pattern_accuracy["measurement_max_value"] + + print(f"\ndefault_pattern_accuracy:") + print(f" 图案数量: {pattern_count}") + print(f" measurement_max_value: {max_value}") + + if pattern_count == 29 and max_value == 28: + print("\n✅ 配置正确(29色)") + + # 显示前 5 个图案 + print("\n前5个图案:") + for i in range(5): + rgb = config.default_pattern_accuracy["pattern_params"][i] + names = config.get_accuracy_color_names() + print(f" [{i}] {names[i]:15s} RGB{rgb}") + + # 显示后 5 个图案 + print("\n后5个图案:") + for i in range(24, 29): + rgb = config.default_pattern_accuracy["pattern_params"][i] + names = config.get_accuracy_color_names() + print(f" [{i}] {names[i]:15s} RGB{rgb}") + + # 测试 set_current_pattern + print("\n测试 set_current_pattern('accuracy'):") + config.set_current_pattern("accuracy") + current_count = len(config.current_pattern["pattern_params"]) + print(f" current_pattern 图案数量: {current_count}") + + if current_count == 29: + print(" ✅ set_current_pattern 工作正常") + else: + print(f" ❌ set_current_pattern 失败!只有 {current_count} 个图案") + + else: + print(f"\n❌ 配置错误!") + print(f" 期望: 29 个图案, measurement_max_value=28") + print(f" 实际: {pattern_count} 个图案, measurement_max_value={max_value}") + + print("\n❌ 请检查 default_pattern_accuracy 定义!") + print(" 应该包含:") + print(" - 5个灰阶") + print(" - 18个 ColorChecker 色块") + print(" - 6个 100% 饱和色") + print(" - 总计 29 个 RGB 数组") + + print("=" * 60) diff --git a/utils/pq/pq_result.py b/utils/pq/pq_result.py new file mode 100644 index 0000000..5301795 --- /dev/null +++ b/utils/pq/pq_result.py @@ -0,0 +1,469 @@ +import json +import os +import datetime +from typing import Dict, List, Any, Optional +from dataclasses import dataclass, asdict + + +@dataclass +class TestItemResult: + """单个测试项的结果数据""" + + item_name: str # 测试项名称 (如 "gamut", "gamma", "cct" 等) + item_display_name: str # 测试项显示名称 (如 "色域", "Gamma", "色温一致性" 等) + status: str # 测试状态: "completed", "failed", "skipped" + start_time: Optional[datetime.datetime] = None + end_time: Optional[datetime.datetime] = None + intermediate_data: Dict[str, Any] = None # 中间过程数据 + final_result: Dict[str, Any] = None # 最终测试结果 + error_message: Optional[str] = None # 错误信息 + + def __post_init__(self): + if self.intermediate_data is None: + self.intermediate_data = {} + if self.final_result is None: + self.final_result = {} + + def to_dict(self): + """转换为字典格式""" + data = asdict(self) + if self.start_time: + data["start_time"] = self.start_time.isoformat() + if self.end_time: + data["end_time"] = self.end_time.isoformat() + return data + + @classmethod + def from_dict(cls, data): + """从字典创建对象""" + if "start_time" in data and data["start_time"]: + data["start_time"] = datetime.datetime.fromisoformat(data["start_time"]) + if "end_time" in data and data["end_time"]: + data["end_time"] = datetime.datetime.fromisoformat(data["end_time"]) + return cls(**data) + + +class PQResult: + """PQ测试结果管理类""" + + def __init__( + self, test_type: str = "", test_name: str = "", output_dir: str = "results" + ): + """ + 初始化PQ测试结果管理器 + + Args: + test_type: 测试类型 ("screen_module", "sdr_movie", "hdr_movie") + test_name: 测试名称显示 + output_dir: 结果输出目录 + """ + self.test_id = self._generate_test_id() + self.test_type = test_type + self.test_name = test_name + self.output_dir = output_dir + + # 测试基本信息 + self.start_time = datetime.datetime.now() + self.end_time = None + self.status = "running" # "running", "completed", "failed", "stopped" + + # 测试配置信息 + self.test_config = {} + + # 测试项结果 + self.test_items: Dict[str, TestItemResult] = {} + + # 全局测试数据 + self.global_data = { + "device_info": {}, + "environment_info": {}, + "measurement_settings": {}, + } + + # 确保输出目录存在 + self._ensure_output_dir() + + # ============================================================================= + # 存放当次测试的中间数据,方便调用 + # ============================================================================= + self.fix_pattern_rgb = None + self.fix_pattern_gray = None + + def _generate_test_id(self) -> str: + """生成唯一的测试ID""" + return datetime.datetime.now().strftime("PQ_%Y%m%d_%H%M%S_%f") + + def _ensure_output_dir(self): + pass + + def set_test_config(self, config: Dict[str, Any]): + """设置测试配置信息""" + self.test_config = config.copy() + + def set_global_data( + self, + device_info: Dict = None, + environment_info: Dict = None, + measurement_settings: Dict = None, + ): + """设置全局测试数据""" + if device_info: + self.global_data["device_info"].update(device_info) + if environment_info: + self.global_data["environment_info"].update(environment_info) + if measurement_settings: + self.global_data["measurement_settings"].update(measurement_settings) + + def add_test_item(self, item_name: str, item_display_name: str) -> TestItemResult: + """添加测试项""" + test_item = TestItemResult( + item_name=item_name, item_display_name=item_display_name, status="pending" + ) + self.test_items[item_name] = test_item + return test_item + + def start_test_item(self, item_name: str): + """开始测试项""" + if item_name in self.test_items: + self.test_items[item_name].status = "running" + self.test_items[item_name].start_time = datetime.datetime.now() + + def add_intermediate_data(self, item_name: str, data_key: str, data_value: Any): + """添加测试项的中间过程数据""" + if item_name in self.test_items: + self.test_items[item_name].intermediate_data[data_key] = data_value + if data_key == "rgb": + self.fix_pattern_rgb = data_value + if data_key == "gray": + self.fix_pattern_gray = data_value + + def get_intermediate_data(self, item_name: str, data_key: str) -> Any: + """ + 获取测试项的中间过程数据 + + Args: + item_name: 测试项名称 (如 "gamut", "gamma", "cct", "shared") + data_key: 数据键名 (如 "rgb", "gray", "measurement_points") + + Returns: + 对应的数据,如果不存在则返回 None + + Examples: + >>> pq_result.get_intermediate_data("gamut", "rgb") + [[0.64, 0.33, 100.5, ...], ...] + + >>> pq_result.get_intermediate_data("shared", "gray") + [[0.31, 0.33, 50.2, ...], ...] + """ + # 方式1: 从 test_items 中获取 + if item_name in self.test_items: + intermediate_data = self.test_items[item_name].intermediate_data + if data_key in intermediate_data: + return intermediate_data[data_key] + + # 方式2: 从快捷属性中获取(用于 "shared" 数据) + if item_name == "shared": + if data_key == "rgb" and self.fix_pattern_rgb is not None: + return self.fix_pattern_rgb + if data_key == "gray" and self.fix_pattern_gray is not None: + return self.fix_pattern_gray + + # 未找到数据 + return None + + def has_intermediate_data(self, item_name: str, data_key: str) -> bool: + """ + 检查是否存在指定的中间数据 + + Args: + item_name: 测试项名称 + data_key: 数据键名 + + Returns: + bool: 数据是否存在 + """ + return self.get_intermediate_data(item_name, data_key) is not None + + def get_all_intermediate_data(self, item_name: str) -> Dict[str, Any]: + """ + 获取测试项的所有中间数据 + + Args: + item_name: 测试项名称 + + Returns: + 包含所有中间数据的字典,如果测试项不存在则返回空字典 + """ + if item_name in self.test_items: + return self.test_items[item_name].intermediate_data.copy() + + if item_name == "shared": + return { + "rgb": self.fix_pattern_rgb, + "gray": self.fix_pattern_gray, + } + + return {} + + def clear_intermediate_data(self, item_name: str = None): + """ + 清除中间数据 + + Args: + item_name: 测试项名称,如果为 None 则清除所有中间数据 + """ + if item_name is None: + # 清除所有测试项的中间数据 + for item in self.test_items.values(): + item.intermediate_data.clear() + # 清除快捷属性 + self.fix_pattern_rgb = None + self.fix_pattern_gray = None + elif item_name in self.test_items: + # 清除指定测试项的中间数据 + self.test_items[item_name].intermediate_data.clear() + + def set_test_item_result( + self, + item_name: str, + result_data: Dict[str, Any], + status: str = "completed", + error_message: str = None, + ): + """设置测试项的最终结果""" + if item_name in self.test_items: + self.test_items[item_name].final_result = result_data + self.test_items[item_name].status = status + self.test_items[item_name].end_time = datetime.datetime.now() + if error_message: + self.test_items[item_name].error_message = error_message + + def complete_test(self, status: str = "completed"): + """完成整个测试""" + self.end_time = datetime.datetime.now() + self.status = status + + def get_test_summary(self) -> Dict[str, Any]: + """获取测试摘要信息""" + completed_items = len( + [item for item in self.test_items.values() if item.status == "completed"] + ) + failed_items = len( + [item for item in self.test_items.values() if item.status == "failed"] + ) + total_items = len(self.test_items) + + duration = None + if self.start_time and self.end_time: + duration = (self.end_time - self.start_time).total_seconds() + + return { + "test_id": self.test_id, + "test_type": self.test_type, + "test_name": self.test_name, + "status": self.status, + "start_time": self.start_time.isoformat() if self.start_time else None, + "end_time": self.end_time.isoformat() if self.end_time else None, + "duration_seconds": duration, + "total_items": total_items, + "completed_items": completed_items, + "failed_items": failed_items, + } + + def to_dict(self) -> Dict[str, Any]: + """转换为完整的字典格式""" + return { + "test_summary": self.get_test_summary(), + "test_config": self.test_config, + "global_data": self.global_data, + "test_items": { + name: item.to_dict() for name, item in self.test_items.items() + }, + "export_timestamp": datetime.datetime.now().isoformat(), + "format_version": "1.0", + } + + def save_to_file(self, file_path: str) -> bool: + return False + + def save_to_json(self, filename: str = None) -> str: + return "" + + @classmethod + def load_from_json(cls, file_path: str) -> "PQResult": + """从JSON文件加载测试结果""" + with open(file_path, "r", encoding="utf-8") as f: + data = json.load(f) + + # 创建PQResult实例 + test_summary = data.get("test_summary", {}) + pq_result = cls( + test_type=test_summary.get("test_type", ""), + test_name=test_summary.get("test_name", ""), + ) + + # 恢复基本信息 + pq_result.test_id = test_summary.get("test_id", pq_result.test_id) + pq_result.status = test_summary.get("status", "unknown") + + if test_summary.get("start_time"): + pq_result.start_time = datetime.datetime.fromisoformat( + test_summary["start_time"] + ) + if test_summary.get("end_time"): + pq_result.end_time = datetime.datetime.fromisoformat( + test_summary["end_time"] + ) + + # 恢复配置和全局数据 + pq_result.test_config = data.get("test_config", {}) + pq_result.global_data = data.get("global_data", {}) + + # 恢复测试项 + test_items_data = data.get("test_items", {}) + for item_name, item_data in test_items_data.items(): + pq_result.test_items[item_name] = TestItemResult.from_dict(item_data) + + return pq_result + + def export_item_data(self, item_name: str, export_format: str = "json") -> str: + """ + 导出单个测试项的数据 + + Args: + item_name: 测试项名称 + export_format: 导出格式 ("json", "csv") + + Returns: + 导出文件路径 + """ + if item_name not in self.test_items: + raise ValueError(f"测试项 {item_name} 不存在") + + item = self.test_items[item_name] + timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") + + if export_format == "json": + filename = f"{self.test_id}_{item_name}_{timestamp}.json" + if self.test_type: + file_path = os.path.join(self.output_dir, self.test_type, filename) + else: + file_path = os.path.join(self.output_dir, filename) + + with open(file_path, "w", encoding="utf-8") as f: + json.dump(item.to_dict(), f, ensure_ascii=False, indent=2) + + elif export_format == "csv": + import csv + + filename = f"{self.test_id}_{item_name}_{timestamp}.csv" + if self.test_type: + file_path = os.path.join(self.output_dir, self.test_type, filename) + else: + file_path = os.path.join(self.output_dir, filename) + + with open(file_path, "w", newline="", encoding="utf-8") as f: + writer = csv.writer(f) + + # 写入基本信息 + writer.writerow(["测试项", item.item_display_name]) + writer.writerow(["状态", item.status]) + writer.writerow( + ["开始时间", item.start_time.isoformat() if item.start_time else ""] + ) + writer.writerow( + ["结束时间", item.end_time.isoformat() if item.end_time else ""] + ) + writer.writerow([]) + + # 写入最终结果数据 + writer.writerow(["最终结果"]) + for key, value in item.final_result.items(): + writer.writerow([key, str(value)]) + + else: + raise ValueError(f"不支持的导出格式: {export_format}") + + return file_path + + def get_progress_info(self) -> Dict[str, Any]: + """获取测试进度信息""" + total_items = len(self.test_items) + completed_items = len( + [ + item + for item in self.test_items.values() + if item.status in ["completed", "failed"] + ] + ) + running_items = len( + [item for item in self.test_items.values() if item.status == "running"] + ) + + progress_percentage = ( + (completed_items / total_items * 100) if total_items > 0 else 0 + ) + + return { + "total_items": total_items, + "completed_items": completed_items, + "running_items": running_items, + "pending_items": total_items - completed_items - running_items, + "progress_percentage": progress_percentage, + "current_status": self.status, + } + + +# 使用示例和工具函数 +def create_pq_result_from_config(config: Dict[str, Any]) -> PQResult: + """根据配置创建PQResult实例""" + test_type = config.get("test_type", "") + test_name = config.get("test_name", "") + + pq_result = PQResult(test_type=test_type, test_name=test_name) + pq_result.set_test_config(config) + + # 添加测试项 + test_items = config.get("test_items", []) + test_items_names = config.get("test_items_chinese", []) + + for i, item in enumerate(test_items): + display_name = test_items_names[i] if i < len(test_items_names) else item + pq_result.add_test_item(item, display_name) + + return pq_result + + +if __name__ == "__main__": + # 测试代码 + print("PQResult类测试") + + # 创建测试实例 + pq_result = PQResult("screen_module", "屏模组性能测试") + + # 设置配置 + config = { + "test_type": "screen_module", + "test_name": "屏模组性能测试", + "test_items": ["gamut", "gamma", "cct"], + "test_items_chinese": ["色域", "Gamma", "色温一致性"], + } + pq_result.set_test_config(config) + + # 添加测试项 + pq_result.add_test_item("gamut", "色域") + pq_result.add_test_item("gamma", "Gamma") + pq_result.add_test_item("cct", "色温一致性") + + # 模拟测试过程 + pq_result.start_test_item("gamut") + pq_result.add_intermediate_data( + "gamut", "measurement_points", [[0.64, 0.33], [0.30, 0.60]] + ) + pq_result.set_test_item_result("gamut", {"coverage": 95.2, "accuracy": 98.5}) + + # 完成测试 + pq_result.complete_test() + + print(f"测试结果已保存到: {pq_result.save_to_json()}") + print("测试摘要:", pq_result.get_test_summary()) diff --git a/utils/tvSerail.py b/utils/tvSerail.py new file mode 100644 index 0000000..2adf618 --- /dev/null +++ b/utils/tvSerail.py @@ -0,0 +1,423 @@ +# -*- coding: UTF-8 -*- +import zlib +from xmlrpc.client import Boolean +from utils.baseSerail import BaseSerial +import binascii +import utils.baseSerail as baseSerail + +# 包头码(包引导码) +PHeader = { + "TV_Debug": 0xAA, + "TV_Return": 0xAB, + "TV_Panel_Debug": 0xAC, + "TV_Panel_Return": 0xAD, + "TV_Debug_Other": 0xAE, + "TV_Other_Return": 0xAF, +} + +# 命令结果码; +RCode = { + "RC_OK": 0x0A, # 命令执行通过; + "RC_ERR": 0x0E, # 命令错误或无法执行; + "RC_LOSE": 0x0F, # 命令丢失(链路层出错); +} + + +# 命令的封装与解析; +class TvParse(): + def __init__(self): + # 头引导码(默认1字节,0x86命令二字节); + self.Header = [PHeader['TV_Debug']] + # 包长(默认1字节,0x86长度二字节); + self.Length = [0x00] + # 命令码,1字节(int类型); + self.Command = [0x00] + # 子命令参数; + self.SubCommand = [] + # 数据,bytearray格式; + self.Data = [] + # crch,1字节; + self.CRCH = 0xFF + # crcl,1字节; + self.CRCL = 0xFF + # 是否是特殊命令; + self.FEFlag = False + # 是否是多参数命令;(同一个Command,有多个含义的参数) + self.isMultipleParams = False + # 正确执行后的结果值,bytearray格式的二维数组; + self.successData = [] + + def parseCommand(self, head: int, command: list[int], subCommand: list[int] = [], data: bytearray = bytearray(), isMultipleParams=False, FEFlag=False): + self.Header[0] = head + self.Command = command + self.SubCommand = subCommand + self.Data = data + self.FEFlag = FEFlag + self.isMultipleParams = isMultipleParams + if FEFlag: + self.Header.append(0xFE) + # 注意:4 = crch + crcl + lenh + lenl + length = 4 + self.Header.__len__() + self.Data.__len__() + self.Command.__len__() + self.SubCommand.__len__() + # 高字节; + self.Length[0] = length >> 8 + # 低字节; + self.Length.append(length & 0xFF) + else: + # 注意:3 = crch + crcl + len + self.Length[0] = 3 + self.Header.__len__() + self.Data.__len__() + self.Command.__len__() + self.SubCommand.__len__() + + # 生成package; + package = bytearray(self.Header + self.Length + self.Command + self.SubCommand) + self.Data + + self.calculateCRC(package) + + # 形成最终的命令; + package = bytearray(self.Header + self.Length + self.Command + self.SubCommand) + self.Data + bytearray([self.CRCH, self.CRCL]) + + # 打印最终命令的十六进制形式,每两个字符之间加一个空格 + # hex_string = ' '.join(f'{byte:02X}' for byte in package) + # print("最终命令的十六进制形式:", hex_string) + return package + + def calculateCRC(self, data: bytearray): + crc = baseSerail.crc16(data, data.__len__()) + # 高字节; + self.CRCH = crc >> 8 + # 低字节; + self.CRCL = crc & 0xFF + + def parseCommandFc(self, head: int, command: list[int], subCommand: list[int] = [], data: bytearray = bytearray(), isMultipleParams=False): + self.Header[0] = head + self.Command = command + self.SubCommand = subCommand + self.Data = data + self.isMultipleParams = isMultipleParams + self.Length[0] = 3 + self.Header.__len__() + self.Data.__len__() + self.Command.__len__() + self.SubCommand.__len__() + + package = bytearray(self.Header + self.Length + self.Command + self.SubCommand) + self.Data + self.calculateCRC(package) + package = bytearray(self.Header + self.Length + self.Command + self.SubCommand) + self.Data + bytearray([self.CRCH]) + bytearray([self.CRCL]) + return package + +# 成功返回:(AB 08 FC 08 EC 80 CR1 CR2) +# 失败返回:(AB 08 FC 08 EC 00 CR1 CR2) + def parseResultFc(self,data): + data = bytearray(data) + + return False + + def parseResult(self, data): + data = bytearray(data) + if data.__len__() < 5: + return False + + retCode = 0 + if self.Header[0] == PHeader['TV_Debug']: + retCode = PHeader['TV_Return'] + elif self.Header[0] == PHeader['TV_Panel_Debug']: + retCode = PHeader['TV_Panel_Return'] + elif self.Header[0] == PHeader['TV_Debug_Other']: + retCode = PHeader['TV_Other_Return'] + + if retCode != data[0]: + return False + + package = [] + tooken_len = 0 + return True + + def parseString(self, data): + data = bytearray(data) + if data.__len__() < 5: + return False + + retCode = 0 + if self.Header[0] == PHeader['TV_Debug']: + retCode = PHeader['TV_Return'] + elif self.Header[0] == PHeader['TV_Panel_Debug']: + retCode = PHeader['TV_Panel_Return'] + elif self.Header[0] == PHeader['TV_Debug_Other']: + retCode = PHeader['TV_Other_Return'] + + if retCode != data[0]: + return False + + package = [] + tooken_len = 0 + while True: + if tooken_len >= data.__len__(): + break + if self.FEFlag: + package_len = data[tooken_len + 1] << 8 + data[tooken_len + 2] + else: + package_len = data[tooken_len + 1] + package = data[tooken_len:tooken_len + package_len] + + if package[0] != retCode: + print('Incorrect package head!\n') + return False + + crc = crc16(package, package_len - 2) + CRCH = crc >> 8 + CRCL = crc & 0xFF + if CRCH != package[-2] and CRCL != package[-1]: + return False + + if tooken_len == 0: + if package[2] != RCode['RC_OK']: + return False + else: + if self.Command[0] == 0xFC: + pass + else: + if package[2] - 1 != self.Command[0]: + return False + if package[2] == 0xFE: + self.successData.append(package[5:-2]) + else: + self.successData.append(package[3:-2]) + + tooken_len += package_len + + # print('successData:', binascii.b2a_hex(bytearray(self.Command)), self.successData) + return self.successData + + +class tvSerial(BaseSerial): + def __init__(self): + BaseSerial.__init__(self) + + def __del__(self): + self.close() + + def checkport(self): + if not self.ser.is_open: + self.reOpen() + + return self.ser.is_open + + def sendcmd(self, cmd: list[int]): + self.write(bytearray(cmd)) + return self.read() + + '''协议模式发送命令''' + def sendcmdEx(self, head: int, command: list[int], subCommand: list[int] = [], data: bytearray = bytearray(), FEFlag: Boolean = False, returnParam: Boolean = False): + cmd = TvParse() + package = cmd.parseCommand(head, command, subCommand, data, returnParam, FEFlag) + if self.write(package): + package = self.read() + return cmd.parseResult(package) + + return False + + def sendcmdEx_string_return(self, head: int, command: list[int], subCommand: list[int] = [], data: bytearray = bytearray(), FEFlag: Boolean = False, returnParam: Boolean = False): + cmd = TvParse() + package = cmd.parseCommand(head, command, subCommand, data, returnParam, FEFlag) + if self.write(package): + package = self.read() + return cmd.parseString(package) + + return "" + + + def sendcmdFc(self, head: int, command: list[int], subCommand: list[int] = [], data: bytearray = bytearray(), FEFlag: Boolean = False, returnParam: Boolean = False): + cmd = TvParse() + package = cmd.parseCommand(head, command, subCommand, data, returnParam, FEFlag) + if self.write(package): + package = self.read() + return cmd.parseResult(package) + + return False + + def gen_zzip(selfe,gm_file,save_file): + with open(gm_file, 'rb') as f: + data = f.read() + + compressed_data = zlib.compress(data) + + with open(save_file, 'wb') as f: + f.write(compressed_data) + + '''发送pattern''' + def send_parttern(self, rgb: list[int]): + cmd = TvParse() + package = cmd.parseCommand(0xAA, [0x28], [], bytearray(rgb)) + if self.write(package): + package = self.read() + return cmd.parseResult(package) + + return False + + # '''发送10bit-pattern''' + # def send_10_bit_parttern(self, rgb: list[int]): + # cmd = MokaParse() + # rgb16 = convert_rgb16(rgb) + # package = cmd.parseCommandFc(0xAA, [0xFC,0x07,0x06], [0xEC,0x01,0x86,0x00], rgb16) + # if self.write(package): + # package = self.read() + # return cmd.parseResult(package) + + # return False + + '''发送12bit-pattern''' + def send_12_bit_parttern(self, rgb: list[int]): + cmd = TvParse() + rgb16 = baseSerail.convert_rgb16(rgb) + package = cmd.parseCommand(0xAA, [0x28], [], rgb16) + if self.write(package): + package = self.read() + return cmd.parseResult(package) + + return False + + '''发送gamma文件''' + def send_gamma(self, file_path: str): + fp = open(file_path, 'rb') + cmd = TvParse() + data = fp.read() + # 生成命令包 + package = cmd.parseCommand(0xAA, [0xE9], [0x02], data, False, True) + # 将命令包写入临时文件 + if self.write(package): + package = self.read() + print(cmd.parseResult(package)) + return cmd.parseResult(package) + + '''激活gamma文件''' + def send_gamma_active(self, ini_file: str): + ini_fp = open(ini_file, 'rb') + ind_data = ini_fp.read() + ini_fp.close() + + # 计算crc + crc = crc16(ind_data, ind_data.__len__()) + CRC_GM = [crc >> 8, crc & 0xFF] + + cmd = TvParse() + package = cmd.parseCommand(0xAA, [0x99], [0x06], bytearray(CRC_GM)) + if self.write(package): + package = self.read() + print("---------------------") + print(cmd.parseResult(package)) + return cmd.parseResult(package) + + '''激活gamma文件-12bit版本''' + def send_gamma_active_12bit(self, ini_file: str): + ini_fp = open(ini_file, 'rb') + ind_data = ini_fp.read() + ini_fp.close() + + # 计算crc + crc = baseSerail.crc16(ind_data, ind_data.__len__()) + CRC_GM = [crc >> 8, crc & 0xFF] + + cmd = TvParse() + package = cmd.parseCommandFc(0xAA, [0xFC,0x07], [0x0E], bytearray(CRC_GM)) + if self.write(package): + package = self.read() + print("---------------------") + print(cmd.parseResult(package)) + return cmd.parseResult(package) + + + '''进工厂模式''' + def enterFactory(self): + return self.sendcmdEx(0xAA, [0x10], [0x01]) + + '''白平衡初始化''' + def initWhiteBalance(self): + return self.sendcmdEx(0xAA, [0x16], [0x01]) + + '''关闭localdimming''' + def closeLocaldimming(self): + return self.sendcmdEx(0xAA, [0x9F,0x07], [0x00]) + + '''打开内置pattern''' + def openBuiltInPattern(self): + return self.sendcmdEx(0xAA, [0x27], [0x01]) + + '''关闭内置pattern''' + def closeBuiltInPattern(self): + return self.sendcmdEx(0xAA, [0x27], [0x00]) + + '''切换标准色温''' + def switchStdColorTemperature(self): + return self.sendcmdEx(0xAA, [0x31], [0x01]) + + '''切换冷色温''' + def switchColdColorTemperature(self): + return self.sendcmdEx(0xAA, [0x31], [0x02]) + + '''切换暖色温''' + def switchWarmColorTemperature(self): + return self.sendcmdEx(0xAA, [0x31], [0x03]) + + '''切换暖2色温''' + def switchWarm2ColorTemperature(self): + return self.sendcmdEx(0xAA, [0x31], [0x04]) + + '''初始化gamma''' + def initGamma(self): + return self.sendcmdEx(0xAA, [0x9F,0x09], [0x01]) + + '''进老化模式''' + def enterAgingMode(self): + return self.sendcmdEx(0xAA, [0x13], [0x01]) + + '''退出老化模式''' + def exitAgingMode(self): + return self.sendcmdEx(0xAA, [0x13], [0x00]) + + '''软件版本查询''' + def sendSoftwareVersionQuery(self): + return self.sendcmdEx_string_return(0xAA, [0x57], [0x00]) + + '''PID查询''' + def sendPIDQuery(self): + return self.sendcmdEx_string_return(0xAA, [0x84], [0x00]) + + def switchHDMI1(self): + """切换到HDMI1信源""" + return self.sendcmdEx(0xAA, [0x25], [0x01]) + + def switchHDMI2(self): + """切换到HDMI2信源""" + return self.sendcmdEx(0xAA, [0x25], [0x02]) + + def switchHDMI3(self): + """切换到HDMI3信源""" + return self.sendcmdEx(0xAA, [0x25], [0x03]) + + def switchVGA(self): + """切换到VGA信源""" + return self.sendcmdEx(0xAA, [0x24], [0x01]) + + def switchAV1(self): + """切换到AV1信源""" + return self.sendcmdEx(0xAA, [0x22], [0x01]) + + def switchAV2(self): + """切换到AV2信源""" + return self.sendcmdEx(0xAA, [0x22], [0x02]) + + def switchAV3(self): + """切换到AV3信源""" + return self.sendcmdEx(0xAA, [0x22], [0x03]) + + def switchDisplayMode(self, mode="natural"): + """切换图像预设模式 """ + if mode == "natural": + return self.sendcmdEx(0xAA, [0x30], [0x01]) + elif mode == "soft": + return self.sendcmdEx(0xAA, [0x30], [0x02]) + elif mode == "bright": + return self.sendcmdEx(0xAA, [0x30], [0x03]) + elif mode == "personal": + return self.sendcmdEx(0xAA, [0x30], [0x04]) + elif mode == "cinematic": + return self.sendcmdEx(0xAA, [0x30], [0x05]) + else: + raise ValueError("Invalid display mode. Use 'natural', 'soft', 'bright', 'personal', or 'cinematic'.") + +if __name__ == "__main__": + pass \ No newline at end of file diff --git a/views/collapsing_frame.py b/views/collapsing_frame.py new file mode 100644 index 0000000..87ce11e --- /dev/null +++ b/views/collapsing_frame.py @@ -0,0 +1,126 @@ +import ttkbootstrap as ttk +import tkinter +from tkinter import ttk +from pathlib import Path +from ttkbootstrap import Style +import sys +import os + + +def get_resource_path(relative_path): + """ + 获取资源文件的绝对路径(兼容开发环境和打包后) + + Args: + relative_path: 相对路径,如 "assets/icons8_double_up_24px.png" + + Returns: + str: 资源文件的绝对路径 + """ + try: + # PyInstaller 打包后的临时文件夹路径 + base_path = sys._MEIPASS + except AttributeError: + # 开发环境:使用项目根目录 + # 当前文件: views/collapsing_frame.py + # 项目根目录: views 的父目录 + current_file = os.path.abspath(__file__) + views_dir = os.path.dirname(current_file) + base_path = os.path.dirname(views_dir) + + return os.path.join(base_path, relative_path) + + +class CollapsingFrame(ttk.Frame): + """ + A collapsible frame widget that opens and closes with a button click. + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.columnconfigure(0, weight=1) + self.cumulative_rows = 0 + p = Path(__file__).parent + self.images = [ + tkinter.PhotoImage( + name="open", file=get_resource_path("assets/icons8_double_up_24px.png") + ), + tkinter.PhotoImage( + name="closed", + file=get_resource_path("assets/icons8_double_right_24px.png"), + ), + ] + + def add(self, child, title="", style="primary.TButton", **kwargs): + """Add a child to the collapsible frame + + :param ttk.Frame child: the child frame to add to the widget + :param str title: the title appearing on the collapsible section header + :param str style: the ttk style to apply to the collapsible section header + """ + if child.winfo_class() != "TFrame": # must be a frame + return + style_color = style.split(".")[0] + frm = ttk.Frame(self, style=f"{style_color}.TFrame") + frm.grid(row=self.cumulative_rows, column=0, sticky="ew") + + # header title + lbl = ttk.Label(frm, text=title, style=f"{style_color}.Inverse.TLabel") + if kwargs.get("textvariable"): + lbl.configure(textvariable=kwargs.get("textvariable")) + lbl.pack(side="left", fill="both", padx=10) + + # header toggle button + btn = ttk.Button( + frm, + image="open", + style=style, + command=lambda c=child: self._toggle_open_close(child), + ) + btn.pack(side="right") + + # assign toggle button to child so that it's accesible when toggling (need to change image) + child.btn = btn + child.grid(row=self.cumulative_rows + 1, column=0, sticky="news") + + # increment the row assignment + self.cumulative_rows += 2 + + def _toggle_open_close(self, child): + """ + Open or close the section and change the toggle button image accordingly + + :param ttk.Frame child: the child element to add or remove from grid manager + """ + if child.winfo_viewable(): + child.grid_remove() + child.btn.configure(image="closed") + else: + child.grid() + child.btn.configure(image="open") + + +# class Application(tkinter.Tk): + +# def __init__(self): +# super().__init__() +# self.title('Collapsing Frame') +# # self.style = Style() + +# cf = CollapsingFrame(self) +# cf.pack(fill='both') + +# # option group 1 +# group1 = ttk.Frame(cf, padding=10) +# for x in range(5): +# ttk.Checkbutton(group1, text=f'Option {x + 1}').pack(fill='x') +# cf.add(group1, title='Option Group 1', style='primary.TButton') + +# # option group 2 +# group2 = ttk.Frame(cf, padding=10) +# for x in range(5): +# ttk.Checkbutton(group2, text=f'Option {x + 1}').pack(fill='x') +# cf.add(group2, title='Option Group 2', style='danger.TButton') + +# if __name__ == '__main__': +# Application().mainloop() diff --git a/views/pq_debug_panel.py b/views/pq_debug_panel.py new file mode 100644 index 0000000..059c8d9 --- /dev/null +++ b/views/pq_debug_panel.py @@ -0,0 +1,1230 @@ +""" +PQ 单步调试面板 +支持屏模组、SDR、HDR 三种测试类型的单步调试功能 +""" + +import ttkbootstrap as ttk +import tkinter as tk +from tkinter import messagebox +import threading +import time + + +class PQDebugPanel: + """PQ 单步调试面板 - 支持 Gamma/EOTF/色准单步测试""" + + def __init__(self, parent, app): + """ + 初始化单步调试面板 + + Args: + parent: 父容器 + app: 主应用程序实例(用于访问设备和配置) + """ + self.parent = parent + self.app = app + + # 当前测试类型和项目 + self.current_test_type = None # screen_module / sdr_movie / hdr_movie + self.current_test_item = None # gamma / eotf / accuracy + + # 测试完成标志 + self.test_completed = False + + # 原始测试数据(用于对比) + self.original_data = {} + + # ==================== ✅ 创建主容器并自动 pack ==================== + self.main_container = ttk.Frame(parent) + self.main_container.pack(fill=tk.BOTH, expand=True) + + # 创建 LabelFrame 放在 main_container 中 + self.main_frame = ttk.LabelFrame( + self.main_container, text="单步调试", padding=10 + ) + self.main_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) + + # 创建三个独立的调试面板 + self.create_screen_module_panel() + self.create_sdr_panel() + self.create_hdr_panel() + + # 默认隐藏所有面板 + self.hide_all_panels() + + # ==================== 创建三个独立面板 ==================== + + def create_screen_module_panel(self): + """创建屏模组单步调试面板""" + self.screen_frame = ttk.LabelFrame( + self.main_frame, text="屏模组 - 单步调试", padding=10 + ) + + # Notebook 用于切换 Gamma 和 RGB 调试 + self.screen_notebook = ttk.Notebook(self.screen_frame) + self.screen_notebook.pack(fill=tk.BOTH, expand=True) + + # ========== Gamma 调试页 ========== + self.screen_gamma_frame = ttk.Frame(self.screen_notebook, padding=10) + self.screen_notebook.add(self.screen_gamma_frame, text="Gamma 调试") + + ttk.Label( + self.screen_gamma_frame, + text="测试完成后可用,选择灰阶进行单步调试", + font=("SimHei", 9), + foreground="gray", + ).grid(row=0, column=0, columnspan=3, pady=(0, 10)) + + # 灰阶选择 + ttk.Label(self.screen_gamma_frame, text="选择灰阶:").grid( + row=1, column=0, sticky=tk.W, padx=5, pady=5 + ) + + self.screen_gray_var = tk.StringVar(value="0%") + self.screen_gray_combo = ttk.Combobox( + self.screen_gamma_frame, + textvariable=self.screen_gray_var, + values=[f"{i}%" for i in [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]], + state="disabled", + width=12, + ) + self.screen_gray_combo.grid(row=1, column=1, sticky=tk.W, padx=5, pady=5) + + # 测试按钮 + self.screen_test_btn = ttk.Button( + self.screen_gamma_frame, + text="单步测试", + command=lambda: self.run_single_step("screen_module", "gamma"), + state=tk.DISABLED, + bootstyle="success", + ) + self.screen_test_btn.grid(row=1, column=2, sticky=tk.W, padx=5, pady=5) + + # 对比结果显示区域 + self.screen_result_frame = ttk.Frame(self.screen_gamma_frame) + self.screen_result_frame.grid( + row=2, column=0, columnspan=3, sticky="nsew", pady=10 + ) + + # 创建对比表格 + columns = ("项目", "原始测试", "单步测试", "差异") + self.screen_result_tree = ttk.Treeview( + self.screen_result_frame, + columns=columns, + show="headings", + height=6, + ) + + for col in columns: + self.screen_result_tree.heading(col, text=col) + self.screen_result_tree.column(col, width=120, anchor=tk.CENTER) + + self.screen_result_tree.pack(fill=tk.BOTH, expand=True) + + scrollbar = ttk.Scrollbar( + self.screen_result_frame, + orient=tk.VERTICAL, + command=self.screen_result_tree.yview, + ) + self.screen_result_tree.configure(yscrollcommand=scrollbar.set) + scrollbar.pack(side=tk.RIGHT, fill=tk.Y) + + # ========== RGB 调试页 ========== + self.screen_rgb_frame = ttk.Frame(self.screen_notebook, padding=10) + self.screen_notebook.add(self.screen_rgb_frame, text="RGB 调试") + + ttk.Label( + self.screen_rgb_frame, + text="测试完成后可用,选择颜色进行单步调试", + font=("SimHei", 9), + foreground="gray", + ).grid(row=0, column=0, columnspan=3, pady=(0, 10)) + + # RGB 颜色选择 + ttk.Label(self.screen_rgb_frame, text="选择颜色:").grid( + row=1, column=0, sticky=tk.W, padx=5, pady=5 + ) + + self.screen_rgb_var = tk.StringVar(value="Red") + self.screen_rgb_combo = ttk.Combobox( + self.screen_rgb_frame, + textvariable=self.screen_rgb_var, + values=["Red", "Green", "Blue"], + state="disabled", + width=12, + ) + self.screen_rgb_combo.grid(row=1, column=1, sticky=tk.W, padx=5, pady=5) + + self.screen_rgb_test_btn = ttk.Button( + self.screen_rgb_frame, + text="单步测试", + command=lambda: self.run_single_step("screen_module", "rgb"), + state=tk.DISABLED, + bootstyle="info", + ) + self.screen_rgb_test_btn.grid(row=1, column=2, sticky=tk.W, padx=5, pady=5) + + # RGB 对比结果 + self.screen_rgb_result_frame = ttk.Frame(self.screen_rgb_frame) + self.screen_rgb_result_frame.grid( + row=2, column=0, columnspan=3, sticky="nsew", pady=10 + ) + + columns = ("项目", "原始测试", "单步测试", "差异") + self.screen_rgb_result_tree = ttk.Treeview( + self.screen_rgb_result_frame, + columns=columns, + show="headings", + height=6, + ) + + for col in columns: + self.screen_rgb_result_tree.heading(col, text=col) + self.screen_rgb_result_tree.column(col, width=120, anchor=tk.CENTER) + + self.screen_rgb_result_tree.pack(fill=tk.BOTH, expand=True) + + scrollbar = ttk.Scrollbar( + self.screen_rgb_result_frame, + orient=tk.VERTICAL, + command=self.screen_rgb_result_tree.yview, + ) + self.screen_rgb_result_tree.configure(yscrollcommand=scrollbar.set) + scrollbar.pack(side=tk.RIGHT, fill=tk.Y) + + def create_sdr_panel(self): + """创建 SDR 单步调试面板""" + self.sdr_frame = ttk.LabelFrame( + self.main_frame, text="SDR Movie - 单步调试", padding=10 + ) + + # Notebook 用于切换 Gamma 和色准 + self.sdr_notebook = ttk.Notebook(self.sdr_frame) + self.sdr_notebook.pack(fill=tk.BOTH, expand=True) + + # ========== Gamma 调试页 ========== + self.sdr_gamma_frame = ttk.Frame(self.sdr_notebook, padding=10) + self.sdr_notebook.add(self.sdr_gamma_frame, text="Gamma 调试") + + ttk.Label( + self.sdr_gamma_frame, + text="测试完成后可用,选择灰阶进行单步调试", + font=("SimHei", 9), + foreground="gray", + ).grid(row=0, column=0, columnspan=3, pady=(0, 10)) + + ttk.Label(self.sdr_gamma_frame, text="选择灰阶:").grid( + row=1, column=0, sticky=tk.W, padx=5, pady=5 + ) + + self.sdr_gray_var = tk.StringVar(value="0%") + self.sdr_gray_combo = ttk.Combobox( + self.sdr_gamma_frame, + textvariable=self.sdr_gray_var, + values=[f"{i}%" for i in [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]], + state="disabled", + width=12, + ) + self.sdr_gray_combo.grid(row=1, column=1, sticky=tk.W, padx=5, pady=5) + + self.sdr_gamma_test_btn = ttk.Button( + self.sdr_gamma_frame, + text="单步测试", + command=lambda: self.run_single_step("sdr_movie", "gamma"), + state=tk.DISABLED, + bootstyle="success", + ) + self.sdr_gamma_test_btn.grid(row=1, column=2, sticky=tk.W, padx=5, pady=5) + + # 对比结果 + self.sdr_gamma_result_frame = ttk.Frame(self.sdr_gamma_frame) + self.sdr_gamma_result_frame.grid( + row=2, column=0, columnspan=3, sticky="nsew", pady=10 + ) + + columns = ("项目", "原始测试", "单步测试", "差异") + self.sdr_gamma_result_tree = ttk.Treeview( + self.sdr_gamma_result_frame, + columns=columns, + show="headings", + height=6, + ) + + for col in columns: + self.sdr_gamma_result_tree.heading(col, text=col) + self.sdr_gamma_result_tree.column(col, width=120, anchor=tk.CENTER) + + self.sdr_gamma_result_tree.pack(fill=tk.BOTH, expand=True) + + scrollbar = ttk.Scrollbar( + self.sdr_gamma_result_frame, + orient=tk.VERTICAL, + command=self.sdr_gamma_result_tree.yview, + ) + self.sdr_gamma_result_tree.configure(yscrollcommand=scrollbar.set) + scrollbar.pack(side=tk.RIGHT, fill=tk.Y) + + # ========== 色准调试页 ========== + self.sdr_accuracy_frame = ttk.Frame(self.sdr_notebook, padding=10) + self.sdr_notebook.add(self.sdr_accuracy_frame, text="色准调试") + + ttk.Label( + self.sdr_accuracy_frame, + text="测试完成后可用,选择色块进行单步调试", + font=("SimHei", 9), + foreground="gray", + ).grid(row=0, column=0, columnspan=3, pady=(0, 10)) + + ttk.Label(self.sdr_accuracy_frame, text="选择色块:").grid( + row=1, column=0, sticky=tk.W, padx=5, pady=5 + ) + + self.sdr_color_var = tk.StringVar(value="White") + self.sdr_color_combo = ttk.Combobox( + self.sdr_accuracy_frame, + textvariable=self.sdr_color_var, + values=self.get_29_color_names(), + state="disabled", + width=20, + ) + self.sdr_color_combo.grid(row=1, column=1, sticky=tk.W, padx=5, pady=5) + + self.sdr_accuracy_test_btn = ttk.Button( + self.sdr_accuracy_frame, + text="单步测试", + command=lambda: self.run_single_step("sdr_movie", "accuracy"), + state=tk.DISABLED, + bootstyle="warning", + ) + self.sdr_accuracy_test_btn.grid(row=1, column=2, sticky=tk.W, padx=5, pady=5) + + # 对比结果 + self.sdr_accuracy_result_frame = ttk.Frame(self.sdr_accuracy_frame) + self.sdr_accuracy_result_frame.grid( + row=2, column=0, columnspan=3, sticky="nsew", pady=10 + ) + + columns = ("项目", "原始测试", "单步测试", "差异") + self.sdr_accuracy_result_tree = ttk.Treeview( + self.sdr_accuracy_result_frame, + columns=columns, + show="headings", + height=6, + ) + + for col in columns: + self.sdr_accuracy_result_tree.heading(col, text=col) + self.sdr_accuracy_result_tree.column(col, width=120, anchor=tk.CENTER) + + self.sdr_accuracy_result_tree.pack(fill=tk.BOTH, expand=True) + + scrollbar = ttk.Scrollbar( + self.sdr_accuracy_result_frame, + orient=tk.VERTICAL, + command=self.sdr_accuracy_result_tree.yview, + ) + self.sdr_accuracy_result_tree.configure(yscrollcommand=scrollbar.set) + scrollbar.pack(side=tk.RIGHT, fill=tk.Y) + + # ========== RGB 调试页 ========== + self.sdr_rgb_frame = ttk.Frame(self.sdr_notebook, padding=10) + self.sdr_notebook.add(self.sdr_rgb_frame, text="RGB 调试") + + ttk.Label( + self.sdr_rgb_frame, + text="测试完成后可用,选择颜色进行单步调试", + font=("SimHei", 9), + foreground="gray", + ).grid(row=0, column=0, columnspan=3, pady=(0, 10)) + + # RGB 颜色选择 + ttk.Label(self.sdr_rgb_frame, text="选择颜色:").grid( + row=1, column=0, sticky=tk.W, padx=5, pady=5 + ) + + self.sdr_rgb_var = tk.StringVar(value="Red") + self.sdr_rgb_combo = ttk.Combobox( + self.sdr_rgb_frame, + textvariable=self.sdr_rgb_var, + values=["Red", "Green", "Blue"], + state="disabled", + width=12, + ) + self.sdr_rgb_combo.grid(row=1, column=1, sticky=tk.W, padx=5, pady=5) + + self.sdr_rgb_test_btn = ttk.Button( + self.sdr_rgb_frame, + text="单步测试", + command=lambda: self.run_single_step("sdr_movie", "rgb"), + state=tk.DISABLED, + bootstyle="info", + ) + self.sdr_rgb_test_btn.grid(row=1, column=2, sticky=tk.W, padx=5, pady=5) + + # RGB 对比结果 + self.sdr_rgb_result_frame = ttk.Frame(self.sdr_rgb_frame) + self.sdr_rgb_result_frame.grid( + row=2, column=0, columnspan=3, sticky="nsew", pady=10 + ) + + columns = ("项目", "原始测试", "单步测试", "差异") + self.sdr_rgb_result_tree = ttk.Treeview( + self.sdr_rgb_result_frame, + columns=columns, + show="headings", + height=6, + ) + + for col in columns: + self.sdr_rgb_result_tree.heading(col, text=col) + self.sdr_rgb_result_tree.column(col, width=120, anchor=tk.CENTER) + + self.sdr_rgb_result_tree.pack(fill=tk.BOTH, expand=True) + + scrollbar = ttk.Scrollbar( + self.sdr_rgb_result_frame, + orient=tk.VERTICAL, + command=self.sdr_rgb_result_tree.yview, + ) + self.sdr_rgb_result_tree.configure(yscrollcommand=scrollbar.set) + scrollbar.pack(side=tk.RIGHT, fill=tk.Y) + + def create_hdr_panel(self): + """创建 HDR 单步调试面板""" + self.hdr_frame = ttk.LabelFrame( + self.main_frame, text="HDR Movie - 单步调试", padding=10 + ) + + # Notebook 用于切换 EOTF 和色准 + self.hdr_notebook = ttk.Notebook(self.hdr_frame) + self.hdr_notebook.pack(fill=tk.BOTH, expand=True) + + # ========== EOTF 调试页 ========== + self.hdr_eotf_frame = ttk.Frame(self.hdr_notebook, padding=10) + self.hdr_notebook.add(self.hdr_eotf_frame, text="EOTF 调试") + + ttk.Label( + self.hdr_eotf_frame, + text="测试完成后可用,选择灰阶进行单步调试", + font=("SimHei", 9), + foreground="gray", + ).grid(row=0, column=0, columnspan=3, pady=(0, 10)) + + ttk.Label(self.hdr_eotf_frame, text="选择灰阶:").grid( + row=1, column=0, sticky=tk.W, padx=5, pady=5 + ) + + self.hdr_gray_var = tk.StringVar(value="0%") + self.hdr_gray_combo = ttk.Combobox( + self.hdr_eotf_frame, + textvariable=self.hdr_gray_var, + values=[f"{i}%" for i in [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]], + state="disabled", + width=12, + ) + self.hdr_gray_combo.grid(row=1, column=1, sticky=tk.W, padx=5, pady=5) + + self.hdr_eotf_test_btn = ttk.Button( + self.hdr_eotf_frame, + text="单步测试", + command=lambda: self.run_single_step("hdr_movie", "eotf"), + state=tk.DISABLED, + bootstyle="success", + ) + self.hdr_eotf_test_btn.grid(row=1, column=2, sticky=tk.W, padx=5, pady=5) + + # 对比结果 + self.hdr_eotf_result_frame = ttk.Frame(self.hdr_eotf_frame) + self.hdr_eotf_result_frame.grid( + row=2, column=0, columnspan=3, sticky="nsew", pady=10 + ) + + columns = ("项目", "原始测试", "单步测试", "差异") + self.hdr_eotf_result_tree = ttk.Treeview( + self.hdr_eotf_result_frame, + columns=columns, + show="headings", + height=6, + ) + + for col in columns: + self.hdr_eotf_result_tree.heading(col, text=col) + self.hdr_eotf_result_tree.column(col, width=120, anchor=tk.CENTER) + + self.hdr_eotf_result_tree.pack(fill=tk.BOTH, expand=True) + + scrollbar = ttk.Scrollbar( + self.hdr_eotf_result_frame, + orient=tk.VERTICAL, + command=self.hdr_eotf_result_tree.yview, + ) + self.hdr_eotf_result_tree.configure(yscrollcommand=scrollbar.set) + scrollbar.pack(side=tk.RIGHT, fill=tk.Y) + + # ========== 色准调试页 ========== + self.hdr_accuracy_frame = ttk.Frame(self.hdr_notebook, padding=10) + self.hdr_notebook.add(self.hdr_accuracy_frame, text="色准调试") + + ttk.Label( + self.hdr_accuracy_frame, + text="测试完成后可用,选择色块进行单步调试", + font=("SimHei", 9), + foreground="gray", + ).grid(row=0, column=0, columnspan=3, pady=(0, 10)) + + ttk.Label(self.hdr_accuracy_frame, text="选择色块:").grid( + row=1, column=0, sticky=tk.W, padx=5, pady=5 + ) + + self.hdr_color_var = tk.StringVar(value="White") + self.hdr_color_combo = ttk.Combobox( + self.hdr_accuracy_frame, + textvariable=self.hdr_color_var, + values=self.get_29_color_names(), + state="disabled", + width=20, + ) + self.hdr_color_combo.grid(row=1, column=1, sticky=tk.W, padx=5, pady=5) + + self.hdr_accuracy_test_btn = ttk.Button( + self.hdr_accuracy_frame, + text="单步测试", + command=lambda: self.run_single_step("hdr_movie", "accuracy"), + state=tk.DISABLED, + bootstyle="warning", + ) + self.hdr_accuracy_test_btn.grid(row=1, column=2, sticky=tk.W, padx=5, pady=5) + + # 对比结果 + self.hdr_accuracy_result_frame = ttk.Frame(self.hdr_accuracy_frame) + self.hdr_accuracy_result_frame.grid( + row=2, column=0, columnspan=3, sticky="nsew", pady=10 + ) + + columns = ("项目", "原始测试", "单步测试", "差异") + self.hdr_accuracy_result_tree = ttk.Treeview( + self.hdr_accuracy_result_frame, + columns=columns, + show="headings", + height=6, + ) + + for col in columns: + self.hdr_accuracy_result_tree.heading(col, text=col) + self.hdr_accuracy_result_tree.column(col, width=120, anchor=tk.CENTER) + + self.hdr_accuracy_result_tree.pack(fill=tk.BOTH, expand=True) + + scrollbar = ttk.Scrollbar( + self.hdr_accuracy_result_frame, + orient=tk.VERTICAL, + command=self.hdr_accuracy_result_tree.yview, + ) + self.hdr_accuracy_result_tree.configure(yscrollcommand=scrollbar.set) + scrollbar.pack(side=tk.RIGHT, fill=tk.Y) + + # ========== RGB 调试页 ========== + self.hdr_rgb_frame = ttk.Frame(self.hdr_notebook, padding=10) + self.hdr_notebook.add(self.hdr_rgb_frame, text="RGB 调试") + + ttk.Label( + self.hdr_rgb_frame, + text="测试完成后可用,选择颜色进行单步调试", + font=("SimHei", 9), + foreground="gray", + ).grid(row=0, column=0, columnspan=3, pady=(0, 10)) + + # RGB 颜色选择 + ttk.Label(self.hdr_rgb_frame, text="选择颜色:").grid( + row=1, column=0, sticky=tk.W, padx=5, pady=5 + ) + + self.hdr_rgb_var = tk.StringVar(value="Red") + self.hdr_rgb_combo = ttk.Combobox( + self.hdr_rgb_frame, + textvariable=self.hdr_rgb_var, + values=["Red", "Green", "Blue"], + state="disabled", + width=12, + ) + self.hdr_rgb_combo.grid(row=1, column=1, sticky=tk.W, padx=5, pady=5) + + self.hdr_rgb_test_btn = ttk.Button( + self.hdr_rgb_frame, + text="单步测试", + command=lambda: self.run_single_step("hdr_movie", "rgb"), + state=tk.DISABLED, + bootstyle="info", + ) + self.hdr_rgb_test_btn.grid(row=1, column=2, sticky=tk.W, padx=5, pady=5) + + # RGB 对比结果 + self.hdr_rgb_result_frame = ttk.Frame(self.hdr_rgb_frame) + self.hdr_rgb_result_frame.grid( + row=2, column=0, columnspan=3, sticky="nsew", pady=10 + ) + + columns = ("项目", "原始测试", "单步测试", "差异") + self.hdr_rgb_result_tree = ttk.Treeview( + self.hdr_rgb_result_frame, + columns=columns, + show="headings", + height=6, + ) + + for col in columns: + self.hdr_rgb_result_tree.heading(col, text=col) + self.hdr_rgb_result_tree.column(col, width=120, anchor=tk.CENTER) + + self.hdr_rgb_result_tree.pack(fill=tk.BOTH, expand=True) + + scrollbar = ttk.Scrollbar( + self.hdr_rgb_result_frame, + orient=tk.VERTICAL, + command=self.hdr_rgb_result_tree.yview, + ) + self.hdr_rgb_result_tree.configure(yscrollcommand=scrollbar.set) + scrollbar.pack(side=tk.RIGHT, fill=tk.Y) + + # ==================== 面板显示控制 ==================== + + def hide_all_panels(self): + """隐藏所有面板""" + self.screen_frame.pack_forget() + self.sdr_frame.pack_forget() + self.hdr_frame.pack_forget() + + def show_panel(self, test_type): + """根据测试类型显示对应面板""" + self.hide_all_panels() + + if test_type == "screen_module": + self.screen_frame.pack(fill=tk.BOTH, expand=True) + self.app.log_gui.log("✓ 显示屏模组调试面板") + elif test_type == "sdr_movie": + self.sdr_frame.pack(fill=tk.BOTH, expand=True) + self.app.log_gui.log("✓ 显示 SDR 调试面板") + elif test_type == "hdr_movie": + self.hdr_frame.pack(fill=tk.BOTH, expand=True) + self.app.log_gui.log("✓ 显示 HDR 调试面板") + + # ==================== 启用/禁用控制 ==================== + + def enable_debug(self, test_type, test_item, original_data): + """ + 测试完成后启用单步调试 + + Args: + test_type: screen_module / sdr_movie / hdr_movie + test_item: gamma / eotf / accuracy / rgb + """ + self.current_test_type = test_type + self.current_test_item = test_item + self.test_completed = True + self.original_data[f"{test_type}_{test_item}"] = original_data + + # 显示对应面板 + self.show_panel(test_type) + + # 启用对应控件 + if test_type == "screen_module": + if test_item == "gamma": + self.screen_gray_combo.config(state="readonly") + self.screen_test_btn.config(state=tk.NORMAL) + self.app.log_gui.log("✓ 屏模组 Gamma 单步调试已启用") + elif test_item == "rgb": + self.screen_rgb_combo.config(state="readonly") + self.screen_rgb_test_btn.config(state=tk.NORMAL) + self.app.log_gui.log("✓ 屏模组 RGB 单步调试已启用") + + elif test_type == "sdr_movie": + if test_item == "gamma": + self.sdr_gray_combo.config(state="readonly") + self.sdr_gamma_test_btn.config(state=tk.NORMAL) + self.app.log_gui.log("✓ SDR Gamma 单步调试已启用") + elif test_item == "accuracy": + self.sdr_color_combo.config(state="readonly") + self.sdr_accuracy_test_btn.config(state=tk.NORMAL) + self.app.log_gui.log("✓ SDR 色准单步调试已启用") + elif test_item == "rgb": + self.sdr_rgb_combo.config(state="readonly") + self.sdr_rgb_test_btn.config(state=tk.NORMAL) + self.app.log_gui.log("✓ SDR RGB 单步调试已启用") + + elif test_type == "hdr_movie": + if test_item == "eotf": + self.hdr_gray_combo.config(state="readonly") + self.hdr_eotf_test_btn.config(state=tk.NORMAL) + self.app.log_gui.log("✓ HDR EOTF 单步调试已启用") + elif test_item == "accuracy": + self.hdr_color_combo.config(state="readonly") + self.hdr_accuracy_test_btn.config(state=tk.NORMAL) + self.app.log_gui.log("✓ HDR 色准单步调试已启用") + elif test_item == "rgb": + self.hdr_rgb_combo.config(state="readonly") + self.hdr_rgb_test_btn.config(state=tk.NORMAL) + self.app.log_gui.log("✓ HDR RGB 单步调试已启用") + + def disable_all_debug(self): + """禁用所有单步调试(新测试开始时调用)""" + self.test_completed = False + self.original_data.clear() + + # 禁用屏模组 + self.screen_gray_combo.config(state="disabled") + self.screen_test_btn.config(state=tk.DISABLED) + self.screen_result_tree.delete(*self.screen_result_tree.get_children()) + self.screen_rgb_combo.config(state="disabled") + self.screen_rgb_test_btn.config(state=tk.DISABLED) + self.screen_rgb_result_tree.delete(*self.screen_rgb_result_tree.get_children()) + + # 禁用 SDR + self.sdr_gray_combo.config(state="disabled") + self.sdr_gamma_test_btn.config(state=tk.DISABLED) + self.sdr_gamma_result_tree.delete(*self.sdr_gamma_result_tree.get_children()) + + self.sdr_color_combo.config(state="disabled") + self.sdr_accuracy_test_btn.config(state=tk.DISABLED) + self.sdr_accuracy_result_tree.delete( + *self.sdr_accuracy_result_tree.get_children() + ) + + self.sdr_rgb_combo.config(state="disabled") + self.sdr_rgb_test_btn.config(state=tk.DISABLED) + self.sdr_rgb_result_tree.delete(*self.sdr_rgb_result_tree.get_children()) + + # 禁用 HDR + self.hdr_gray_combo.config(state="disabled") + self.hdr_eotf_test_btn.config(state=tk.DISABLED) + self.hdr_eotf_result_tree.delete(*self.hdr_eotf_result_tree.get_children()) + + self.hdr_color_combo.config(state="disabled") + self.hdr_accuracy_test_btn.config(state=tk.DISABLED) + self.hdr_accuracy_result_tree.delete( + *self.hdr_accuracy_result_tree.get_children() + ) + + self.hdr_rgb_combo.config(state="disabled") + self.hdr_rgb_test_btn.config(state=tk.DISABLED) + self.hdr_rgb_result_tree.delete(*self.hdr_rgb_result_tree.get_children()) + + self.hide_all_panels() + + # ==================== 单步测试执行 ==================== + + def run_single_step(self, test_type, test_item): + """ + 执行单步测试 + + Args: + test_type: screen_module / sdr_movie / hdr_movie + test_item: gamma / eotf / accuracy / rgb + """ + # 检查设备连接 + if not self.app.ca or not self.app.ucd: + messagebox.showerror("错误", "请先连接设备") + return + + # 获取选择的项目 + if test_item in ["gamma", "eotf"]: + if test_type == "screen_module": + selected = self.screen_gray_var.get() + elif test_type == "sdr_movie": + selected = self.sdr_gray_var.get() + elif test_type == "hdr_movie": + selected = self.hdr_gray_var.get() + + elif test_item == "accuracy": + if test_type == "sdr_movie": + selected = self.sdr_color_var.get() + elif test_type == "hdr_movie": + selected = self.hdr_color_var.get() + + elif test_item == "rgb": + if test_type == "screen_module": + selected = self.screen_rgb_var.get() + elif test_type == "sdr_movie": + selected = self.sdr_rgb_var.get() + elif test_type == "hdr_movie": + selected = self.hdr_rgb_var.get() + + # 在新线程中执行测试 + thread = threading.Thread( + target=self._run_single_step_thread, + args=(test_type, test_item, selected), + daemon=True, + ) + thread.start() + + def _run_single_step_thread(self, test_type, test_item, selected): + """单步测试线程""" + try: + self.app.log_gui.log("=" * 50) + self.app.log_gui.log( + f"开始单步调试: {test_type} - {test_item} - {selected}" + ) + self.app.log_gui.log("=" * 50) + + # 禁用按钮 + self._disable_test_button(test_type, test_item) + + # 根据测试类型设置信号格式 + self._setup_signal_format(test_type) + + # 获取图案索引并发送 + if test_item in ["gamma", "eotf"]: + pattern_index = self.get_gray_index(selected) + self.app.config.set_current_pattern("gray") + elif test_item == "accuracy": + pattern_index = self.get_color_index(selected) + self.app.config.set_current_pattern("accuracy") + elif test_item == "rgb": + pattern_index = self.get_rgb_index(selected) + self.app.config.set_current_pattern("rgb") + + # 设置图案 + self.app.ucd.set_ucd_params(self.app.config) + + # 跳转到目标图案 + for i in range(pattern_index + 1): + self.app.ucd.set_next_pattern() + + self.app.ucd.run() + time.sleep(1.5) + + # 测量数据 + x, y, lv, X, Y, Z = self.app.ca.readAllDisplay() + + self.app.log_gui.log( + f"✓ 测量完成: x={x:.4f}, y={y:.4f}, lv={lv:.2f}, " + f"X={X:.4f}, Y={Y:.4f}, Z={Z:.4f}" + ) + + # 对比数据 + self._compare_and_display( + test_type, test_item, selected, [x, y, lv, X, Y, Z] + ) + + # 重新启用按钮 + self._enable_test_button(test_type, test_item) + + except Exception as e: + self.app.log_gui.log(f"❌ 单步测试失败: {str(e)}") + import traceback + + self.app.log_gui.log(traceback.format_exc()) + self._enable_test_button(test_type, test_item) + + def _setup_signal_format(self, test_type): + """设置信号格式""" + if test_type == "screen_module": + self.app.ucd.set_ucd_params(self.app.config) + + elif test_type == "sdr_movie": + self.app.ucd.set_sdr_format( + color_space=self.app.sdr_color_space_var.get(), + gamma=self.app.sdr_gamma_type_var.get(), + data_range=self.app.sdr_data_range_var.get(), + bit_depth=self.app.sdr_bit_depth_var.get(), + ) + + elif test_type == "hdr_movie": + self.app.ucd.set_hdr_format( + color_space=self.app.hdr_color_space_var.get(), + data_range=self.app.hdr_data_range_var.get(), + bit_depth=self.app.hdr_bit_depth_var.get(), + max_cll=self.app.hdr_maxcll_var.get(), + max_fall=self.app.hdr_maxfall_var.get(), + ) + + def _compare_and_display(self, test_type, test_item, selected, new_data): + """对比数据并显示""" + # 获取原始数据 + key = f"{test_type}_{test_item}" + if key not in self.original_data: + self.app.log_gui.log("⚠️ 未找到原始测试数据") + return + + original_data_list = self.original_data[key] + + # 找到对应的原始数据 + if test_item in ["gamma", "eotf"]: + index = self.get_gray_index(selected) + elif test_item == "accuracy": + index = self.get_color_index(selected) + elif test_item == "rgb": + index = self.get_rgb_index(selected) + + if index >= len(original_data_list): + self.app.log_gui.log(f"⚠️ 索引超出范围: {index}") + return + + original_data = original_data_list[index] + + # ==================== ✅ 构建对比数据 ==================== + comparison = {} + + if test_item == "gamma": + # ========== Gamma 测试:只对比 x, y, lv ========== + comparison["x"] = ( + original_data[0], + new_data[0], + new_data[0] - original_data[0], + ) + comparison["y"] = ( + original_data[1], + new_data[1], + new_data[1] - original_data[1], + ) + comparison["lv"] = ( + original_data[2], + new_data[2], + new_data[2] - original_data[2], + ) + + elif test_item == "eotf": + # ========== EOTF 测试:只对比 x, y, lv ========== + comparison["x"] = ( + original_data[0], + new_data[0], + new_data[0] - original_data[0], + ) + comparison["y"] = ( + original_data[1], + new_data[1], + new_data[1] - original_data[1], + ) + comparison["lv"] = ( + original_data[2], + new_data[2], + new_data[2] - original_data[2], + ) + + elif test_item == "accuracy": + # ========== 色准测试:添加 ΔE 2000 值 ========== + + # 从原始测试结果中读取 ΔE 值 + original_delta_e = self._get_delta_e_from_results(test_type, selected) + + # 重新计算单步测试的 ΔE 值 + new_delta_e = self._calculate_delta_e_for_color( + selected, new_data[0], new_data[1], new_data[2] + ) + + # ΔE 值放在最上面 + comparison["ΔE 2000"] = ( + original_delta_e, + new_delta_e, + new_delta_e - original_delta_e, + ) + + # 只保留 x, y, lv + comparison["x"] = ( + original_data[0], + new_data[0], + new_data[0] - original_data[0], + ) + comparison["y"] = ( + original_data[1], + new_data[1], + new_data[1] - original_data[1], + ) + comparison["lv"] = ( + original_data[2], + new_data[2], + new_data[2] - original_data[2], + ) + + elif test_item == "rgb": + # ========== RGB 测试:对比 x, y, lv ========== + comparison["x"] = ( + original_data[0], + new_data[0], + new_data[0] - original_data[0], + ) + comparison["y"] = ( + original_data[1], + new_data[1], + new_data[1] - original_data[1], + ) + comparison["lv"] = ( + original_data[2], + new_data[2], + new_data[2] - original_data[2], + ) + + # 显示到对应的 Treeview + self._display_comparison(test_type, test_item, selected, comparison) + + def _display_comparison(self, test_type, test_item, selected, comparison): + """显示对比结果到 Treeview""" + # 获取对应的 Treeview + if test_type == "screen_module": + if test_item == "gamma": + tree = self.screen_result_tree + elif test_item == "rgb": + tree = self.screen_rgb_result_tree + elif test_type == "sdr_movie": + if test_item == "gamma": + tree = self.sdr_gamma_result_tree + elif test_item == "accuracy": + tree = self.sdr_accuracy_result_tree + elif test_item == "rgb": + tree = self.sdr_rgb_result_tree + elif test_type == "hdr_movie": + if test_item == "eotf": + tree = self.hdr_eotf_result_tree + elif test_item == "accuracy": + tree = self.hdr_accuracy_result_tree + elif test_item == "rgb": + tree = self.hdr_rgb_result_tree + + # 清空旧数据 + tree.delete(*tree.get_children()) + + # 插入标题行 + tree.insert( + "", + "end", + values=(f"【{selected}】", "", "", ""), + tags=("header",), + ) + + # 插入对比数据 + for key, (original, new, diff) in comparison.items(): + # 根据参数类型设置格式和阈值 + if key == "ΔE 2000": + # ΔE 使用 2 位小数 + original_str = f"{original:.2f}" + new_str = f"{new:.2f}" + diff_str = f"{diff:+.2f}" + threshold = 0.5 + + elif key == "lv": + # 亮度使用 2 位小数 + original_str = f"{original:.2f}" + new_str = f"{new:.2f}" + diff_str = f"{diff:+.2f}" + threshold = 0.5 + + else: # x, y + # x, y 使用 4 位小数 + original_str = f"{original:.4f}" + new_str = f"{new:.4f}" + diff_str = f"{diff:+.4f}" + threshold = 0.001 + + # 判断差异 + if abs(diff) > threshold: + tag = "warning" + else: + tag = "normal" + + # ΔE 使用特殊标签 + if key == "ΔE 2000": + tag = "highlight" if tag == "normal" else "highlight_warning" + + tree.insert( + "", + "end", + values=(key, original_str, new_str, diff_str), + tags=(tag,), + ) + + # 设置标签样式 + tree.tag_configure("header", background="#E3F2FD", font=("SimHei", 9, "bold")) + tree.tag_configure("normal", foreground="black") + tree.tag_configure("warning", foreground="red") + tree.tag_configure("highlight", foreground="blue", font=("SimHei", 9, "bold")) + tree.tag_configure( + "highlight_warning", foreground="red", font=("SimHei", 9, "bold") + ) + + def _disable_test_button(self, test_type, test_item): + """禁用测试按钮""" + if test_type == "screen_module": + if test_item == "gamma": + self.screen_test_btn.config(state=tk.DISABLED) + elif test_item == "rgb": + self.screen_rgb_test_btn.config(state=tk.DISABLED) + elif test_type == "sdr_movie": + if test_item == "gamma": + self.sdr_gamma_test_btn.config(state=tk.DISABLED) + elif test_item == "accuracy": + self.sdr_accuracy_test_btn.config(state=tk.DISABLED) + elif test_item == "rgb": + self.sdr_rgb_test_btn.config(state=tk.DISABLED) + elif test_type == "hdr_movie": + if test_item == "eotf": + self.hdr_eotf_test_btn.config(state=tk.DISABLED) + elif test_item == "accuracy": + self.hdr_accuracy_test_btn.config(state=tk.DISABLED) + elif test_item == "rgb": + self.hdr_rgb_test_btn.config(state=tk.DISABLED) + + def _enable_test_button(self, test_type, test_item): + """启用测试按钮""" + if test_type == "screen_module": + if test_item == "gamma": + self.app.root.after(0, lambda: self.screen_test_btn.config(state=tk.NORMAL)) + elif test_item == "rgb": + self.app.root.after(0, lambda: self.screen_rgb_test_btn.config(state=tk.NORMAL)) + elif test_type == "sdr_movie": + if test_item == "gamma": + self.app.root.after( + 0, lambda: self.sdr_gamma_test_btn.config(state=tk.NORMAL) + ) + elif test_item == "accuracy": + self.app.root.after( + 0, lambda: self.sdr_accuracy_test_btn.config(state=tk.NORMAL) + ) + elif test_item == "rgb": + self.app.root.after( + 0, lambda: self.sdr_rgb_test_btn.config(state=tk.NORMAL) + ) + elif test_type == "hdr_movie": + if test_item == "eotf": + self.app.root.after( + 0, lambda: self.hdr_eotf_test_btn.config(state=tk.NORMAL) + ) + elif test_item == "accuracy": + self.app.root.after( + 0, lambda: self.hdr_accuracy_test_btn.config(state=tk.NORMAL) + ) + elif test_item == "rgb": + self.app.root.after( + 0, lambda: self.hdr_rgb_test_btn.config(state=tk.NORMAL) + ) + + # ==================== 辅助方法 ==================== + + def get_gray_index(self, gray_str): + """获取灰阶索引""" + gray_levels = [100, 90, 80, 70, 60, 50, 40, 30, 20, 10, 0] + gray_value = int(gray_str.replace("%", "")) + return gray_levels.index(gray_value) + + def get_color_index(self, color_name): + """获取色块索引""" + color_names = self.get_29_color_names() + return color_names.index(color_name) + + def get_rgb_index(self, color_name): + """获取RGB颜色索引""" + rgb_colors = ["Red", "Green", "Blue"] + return rgb_colors.index(color_name) + + def get_29_color_names(self): + """获取29色名称列表""" + return [ + "White", + "Gray 80", + "Gray 65", + "Gray 50", + "Gray 35", + "Dark Skin", + "Light Skin", + "Blue Sky", + "Foliage", + "Blue Flower", + "Bluish Green", + "Orange", + "Purplish Blue", + "Moderate Red", + "Purple", + "Yellow Green", + "Orange Yellow", + "Blue (Legacy)", + "Green (Legacy)", + "Red (Legacy)", + "Yellow (Legacy)", + "Magenta (Legacy)", + "Cyan (Legacy)", + "100% Red", + "100% Green", + "100% Blue", + "100% Cyan", + "100% Magenta", + "100% Yellow", + ] + + def _get_delta_e_from_results(self, test_type, color_name): + """从原始测试结果中读取 ΔE 值""" + try: + self.app.log_gui.log(f"[读取 ΔE] 开始:") + self.app.log_gui.log(f" test_type = {test_type}") + self.app.log_gui.log(f" color_name = {color_name}") + + # ✅ 正确的访问方式:通过 test_items + if "accuracy" not in self.app.results.test_items: + self.app.log_gui.log(" ✗ 未找到 accuracy 测试项") + return 0.0 + + test_item = self.app.results.test_items["accuracy"] + accuracy_result = test_item.final_result + + self.app.log_gui.log(f" accuracy_result = {accuracy_result is not None}") + + if not accuracy_result: + self.app.log_gui.log(" ✗ accuracy_result 为空") + return 0.0 + + # 获取色块名称列表和 ΔE 值列表 + color_patches = accuracy_result.get("color_patches", []) + delta_e_values = accuracy_result.get("delta_e_values", []) + + self.app.log_gui.log(f" color_patches 数量: {len(color_patches)}") + self.app.log_gui.log(f" delta_e_values 数量: {len(delta_e_values)}") + + if color_patches: + self.app.log_gui.log(f" 前3个色块: {color_patches[:3]}") + if delta_e_values: + self.app.log_gui.log(f" 前3个ΔE: {delta_e_values[:3]}") + + # 查找对应色块的索引 + try: + index = color_patches.index(color_name) + delta_e = delta_e_values[index] + self.app.log_gui.log( + f" ✓ 找到 {color_name}: index={index}, ΔE={delta_e:.2f}" + ) + return delta_e + except ValueError: + self.app.log_gui.log(f" ✗ 未找到色块 '{color_name}'") + self.app.log_gui.log(f" 可用色块: {color_patches}") + return 0.0 + except IndexError: + self.app.log_gui.log(f" ✗ 索引超出范围: {index}/{len(delta_e_values)}") + return 0.0 + + except Exception as e: + self.app.log_gui.log(f"⚠️ 读取 ΔE 失败: {str(e)}") + import traceback + + self.app.log_gui.log(traceback.format_exc()) + return 0.0 + + def _calculate_delta_e_for_color( + self, color_name, measured_x, measured_y, measured_lv + ): + """计算单个色块的 ΔE 2000""" + try: + # 获取标准 xy 坐标 + test_type = self.current_test_type + standards = self.app.get_accuracy_color_standards(test_type) + + if color_name not in standards: + return 0.0 + + standard_x, standard_y = standards[color_name] + + # 调用主程序的 ΔE 计算方法 + delta_e = self.app.calculate_delta_e_2000( + measured_x, measured_y, measured_lv, standard_x, standard_y + ) + + return delta_e + + except Exception as e: + self.app.log_gui.log(f"⚠️ 计算 ΔE 失败: {str(e)}") + return 0.0 diff --git a/views/pq_log_gui.py b/views/pq_log_gui.py new file mode 100644 index 0000000..4f2c1fd --- /dev/null +++ b/views/pq_log_gui.py @@ -0,0 +1,31 @@ +import tkinter as tk +import ttkbootstrap as ttk + +class PQLogGUI(ttk.Frame): + def __init__(self, parent): + super().__init__(parent) + self.create_widgets() + + def create_widgets(self): + log_frame = ttk.LabelFrame(self, text="测试日志") + log_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) + + self.log_text = ttk.Text(log_frame, height=8, width=50) + self.log_text.pack(fill=tk.BOTH, expand=True, side=tk.LEFT) + + log_scrollbar = ttk.Scrollbar(log_frame, command=self.log_text.yview) + log_scrollbar.pack(fill=tk.Y, side=tk.RIGHT) + self.log_text.config(yscrollcommand=log_scrollbar.set) + + self.log_text.config(state=tk.DISABLED) + + def log(self, message): + self.log_text.config(state=tk.NORMAL) + self.log_text.insert(tk.END, message + "\n") + self.log_text.see(tk.END) + self.log_text.config(state=tk.DISABLED) + + def clear_log(self): + self.log_text.config(state=tk.NORMAL) + self.log_text.delete(1.0, tk.END) + self.log_text.config(state=tk.DISABLED) \ No newline at end of file