我在通过软件读取显示器状态时碰见了问题。
我是通过ddc/ci 指令,通过VCP code获取显示器的状态。
我可以通过一些测试软件或者通过自己写代码获取显示器的一些状态,但是当这个VCP code值代表多个功能时(有多个sub-code),读取会读不到内容。
如链接方式:可以获取到显示器功能集合的capability string,然后getVCPFeature获取某个VCP code代表功能的当前值
https://learn.microsoft.com/zh-cn/windows/win32/monitor/using-the-low-level-monitor-configuration-functions
如下是我测试的显示器的capability string:
Capabilities String:
(prot(monitor)type(lcd)model(HP Z24m G3)cmds(01 02 03 07 0C E3 F3)vcp(02 04 05 08 0B 0C 10 12 16 18 1A 2E(00 01 02 03) 52 60(0F 11 13) 62 6C 6E 70 86(01 02 05) 87(01 02 03 04 05 06 07) 8D(01 02) AC AE B2 B6 C0 C6 C8 C9 CA(01 02) CC(01 02 03 04 05 06 09 0A 0D 0E 14) D6(01 02 03 04 05) DC(00( 00 01 02 05 13 14 15 1D 2B)02(00 01 02 03 04) ) DF E0(02(00 01 03))** E1(04(00 01 03 04 06 08 09 0A) 0A(00 01) 0C(20) 0F(00 01) 10(00 01) 27(02 03 04 05 07 09 0A FF) 28(00 01) 2C(00 01) 2D(00 01) 30(00 01) 31(00 01) 32(00 01) 35(00 01 02) 36(00 01) 39(02 03) 3B 3C 3E 3F(02 03 04 05) 40(00 01 02 03) 42(00 01) 43(00 01) 45(00 01) 49(00 01) 4A(00 01) 4C( 00 01 ) 4E(00 01 02 03 04 05 FF) 56 57(00 01) 6E(01) 6F(00) 72(00 01) 73(00 01))** E6(00 01) E7(00 01) E8(18(00 01) 1B( 00 01 02) 1C(00 01) 1E( 00 01 02)) E9(0D(00 01)) EA(00 01) EB(00( 01 02 03 04)) EE(01 02 03) EF(01 02 03 04 05) F0 F5(00 01 02 03 05 06 0A 0B 0C 0D 0E 0F 11 12 13 14 15 18 19 1B 1C 1D 1E) F8(00 01 02(00 01 02) 0A) FA(00 01 02) FB FC FD FE(00 01 02 03 04))mswhql(1)asset_eep(40)mccs_ver(2.2))
如图,结合上述Capabilities String,我可以通过读取E6,E7的VCP来直接获取这两个代表功能的当前值--E6(00 01) E7(00 01)
但是由于E1用于多个功能,如上述中加粗部分,直接读取E1只会返回00,并不能直接获取到想要那个功能的值。
我需要的是获取displayport mode 的当前值,也就是Capabilities String中相当于括号嵌套的 E1(35(00 01 02)),请问这能怎么实现?
我这边也用一些测试的软件,直接下DDC/CI指令的,如果能通过修改指令格式或者通过编程解决问题,将不胜感激!!
以下答案由GPT-3.5大模型与博主波罗歌共同编写:
在处理这个问题之前,需要先了解一些基础知识。DDC/CI(Display Data Channel/Command Interface)是一串用于控制液晶显示器的信号。使用这个信号,可以做很多东西,如调节亮度、对比度,以及分辨率等等。为了避免混乱,VESA(Video Electronics Standards Association)建立了一套规范,即VESA VCP(VESA Command Set for Video Electronics Standards Association (VESA) Monitor Control Command Set,即VESA监视器控制命令集),该规范定义了各种VCP code及其所代表的功能。每个VCP code都有一个值代表该功能的当前状态。
根据你提供的信息,可以通过读取E1的值,确定该功能有多个子功能(sub-code)。在这种情况下,需要使用SetVCPFeature命令通过指定sub-code值,选择要控制的子功能。下一步是在使用getVCPFeature命令时,将选择对应的VCP code和对应的sub-code值,以读取正确的显示器状态。
以下是一个示例代码,它通过DDC/CI命令读取displayport mode(E1(35(00 01 02)))的当前值:
import win32api, win32con, time
# 定义所有的VCP code和对应的sub-code列表,可以根据实际情况修改
vcp_dict = {
0x10: [0x00], # 亮度
0x12: [0x00], # 对比度
0xe1: [0x35, 0x00, 0x01, 0x02], # DisplayPort mode
0x60: [0x0f, 0x11, 0x13], # 色温
}
# 定义读取VCP值的函数
def read_vcp_value(vcp_code, sub_codes):
ddc_cmd = "\x6e\x01" + chr(vcp_code) # 6e是附加DDC/CI的指令,01是查询命令,vcp_code是要查询的VCP code
data_block = [len(sub_codes) + 1] + sub_codes # 数据块的内容,第一个字节是sub-code的数量,后面的字节是sub-code的值
crc = 0 # 校验码,使用纵向冗余校验(CRC)算法生成
for byte in data_block:
ddc_cmd += chr(byte)
crc ^= byte
for i in range(8):
if crc & 0x80:
crc = (crc << 1) ^ 0x07
else:
crc <<= 1
crc &= 0xff
# 组合DDC command和CRC码
ddc_cmd += chr(crc)
# 使用win32api发送DDC命令,并读取显示器响应
monitor_handle = win32api.EnumDisplayMonitors()[0][0]
_, res, _ = win32api.MonitorFromHandle(monitor_handle, win32con.MONITOR_DEFAULTTONULL)
if res:
return win32api.SendMessage(res, win32con.WM_GETDCCI_REPLY, 0x02, ddc_cmd).encode('hex')[:-4]
else:
return None
# 读取DisplayPort mode的当前值
mode_value = int(read_vcp_value(0xe1, [0x35, 0x02])[6:8], 16)
print("DisplayPort mode值为:%s" % mode_value)
在代码中,我们首先定义了一个字典(vcp_dict)将VCP code和对应的sub-code列表一一映射起来。这样,我们可以根据需要扩展该字典,随时添加新的VCP code和sub-code。接下来,我们定义了一个read_vcp_value函数,该函数接受VCP code和sub-code的列表作为参数,使用DDC/CI命令读取显示器响应,并返回VCP值。最后,我们通过调用read_vcp_value函数读取DisplayPort mode的当前值,并打印结果。
注意,在使用win32api发送DDC命令之前,必须先通过win32api.EnumDisplayMonitors函数获取现有的显示器句柄,并使用win32api.MonitorFromHandle函数从句柄获取显示器的RECT结构体。如果我们在发送DDC命令之前没有这些必要的准备工作,那么函数read_vcp_value将返回None值。
以上代码仅供参考,具体情况需要根据实际情况进行调节。
如果我的回答解决了您的问题,请采纳!
该回答引用GPTᴼᴾᴱᴺᴬᴵ
从您提供的信息中,我可以看到您想要读取显示器的某个VCP code的当前值,但是该VCP code代表多个功能(即有多个sub-code),您无法确定具体要获取哪个sub-code的值。
根据VESA DDC/CI协议,您可以通过发送VCP Feature Set命令来获取显示器的VCP code列表及其对应的当前值。该命令返回的数据中包含了每个VCP code的名称和值。
以下是通过VCP Feature Set命令获取VCP code列表的示例代码:
#include <windows.h>
#include <highlevelmonitorconfigurationapi.h>
int main()
{
// 获取默认显示器的句柄
HMONITOR hMonitor = MonitorFromWindow(NULL, MONITOR_DEFAULTTOPRIMARY);
// 获取VCP code列表
DWORD dwVCPSize = 256;
MC_VCP_CODE_TYPE pVCPCode[dwVCPSize];
DWORD dwNumOfPages;
BOOL bSuccess = GetVCPFeatureAndVCPFeatureReply(hMonitor, 0x00, NULL, &pVCPCode[0], &dwVCPSize, &dwNumOfPages);
if (bSuccess)
{
for (DWORD i = 0; i < dwVCPSize; i++)
{
MC_VCP_CODE_TYPE VCPCode = pVCPCode[i];
if (VCPCode.ucCode != 0x00)
{
// 输出VCP code的名称和当前值
printf("VCP code: 0x%02x, Name: %s, Current value: %d\n", VCPCode.ucCode, VCPCode.szDesc, VCPCode.dwCurVal);
}
}
}
return 0;
}
通过上述代码,您可以获取到显示器支持的所有VCP code及其对应的当前值。接下来,您可以根据Capabilities String中相应的sub-code,从列表中找到该VCP code的名称,然后获取其当前值即可。
需要注意的是,Capabilities String中的VCP code列表可能不是完整的,有些显示器可能不会返回所有VCP code,因此在获取VCP code列表时,您需要检查dwVCPSize的值以确保您已经获取到了所有的VCP code。
参考GPT和自己的思路:根据你提供的Capabilities String,显示器支持的VCP Code包括了多个子功能,而且有些子功能还有参数(如E1(35(00 01 02))),这就需要在获取VCP Code的值时,同时指定要读取的子功能和参数。
对于获取显示器的DisplayPort mode的当前值,你需要使用VCP Code为E1的子功能35,参数为00的指令,具体指令格式为“B2 01 35 00”。其中,B2是DDC/CI的显示器地址,01表示读取VCP Code,35表示E1的子功能35,00表示参数。
以下是一个简单的Python代码示例,用于获取显示器的DisplayPort mode的当前值:
import win32api, win32con, win32gui
def get_vcp_feature(vcp_code, sub_function, parameter):
# 获取显示器句柄
monitor_enum_handle = win32api.EnumDisplayMonitors(None, None)
monitor = win32api.GetMonitorInfo(monitor_enum_handle[0][0])['Device']
# 发送DDC/CI命令
ddc_command = [0x6E] + [vcp_code] + [sub_function] + [parameter]
win32gui.SetVCPFeature(monitor, 0x60, ddc_command)
# 获取VCP值
vcp_value = win32gui.GetVCPFeature(monitor, vcp_code)
return vcp_value
# 获取DisplayPort mode的当前值
displayport_mode = get_vcp_feature(0xE1, 0x35, 0x00)
print(f"DisplayPort mode: {displayport_mode}")
在上述代码中,我们首先使用EnumDisplayMonitors函数获取显示器句柄,然后使用SetVCPFeature函数发送DDC/CI命令,最后使用GetVCPFeature函数获取VCP值。其中,SetVCPFeature函数的第二个参数为0x60,表示发送DDC/CI命令,第三个参数为DDC/CI命令,即一个由地址、VCP Code、子功能和参数组成的列表。GetVCPFeature函数的第二个参数为VCP Code,表示要获取的VCP Code的值。
注意,在使用SetVCPFeature函数发送DDC/CI命令时,需要根据你的显示器和要读取的VCP Code进行修改。同时,需要确保你的电脑和显示器支持DDC/CI,并且DDC/CI功能已经启用。
从您提供的信息中,我可以看到您正在使用VESA DDC/CI协议来读取显示器状态。从您的描述中可以看出,您可以成功读取某些VCP代码的值,但对于那些具有多个子代码的代码,您无法确定要读取哪个子代码的值。
根据VESA DDC/CI规范,如果一个VCP代码有多个子代码,您需要使用VCP子代码选择命令来指定要读取的子代码的值。例如,如果要读取显示器的DisplayPort模式值,您需要按照以下步骤进行操作:
1.首先,您需要使用VCP代码查询命令(即命令0x0F)来查询支持的VCP代码。
2.根据Capabilities String中提供的信息,DisplayPort模式的VCP代码是E1(35(00 01 02))。请注意,VCP代码的值不是35,而是E1,而子代码是35,其值可以是00、01或02。
3.现在,您需要使用VCP子代码选择命令(即命令0x52)来选择要读取的子代码的值。您需要将参数设置为35,表示您希望读取子代码35的值。
4.最后,您可以使用getVCPFeature命令(即命令0x0E)来读取所选子代码的值。
以下是一个示例代码,它使用DDC/CI协议来查询DisplayPort模式的值:
// 查询支持的VCP代码
unsigned char vcp_codes[128] = {0};
unsigned char vcp_max = 0;
if (DDC2B_ReadMonitorVCPs(hMonitor, vcp_codes, &vcp_max)) {
// 遍历支持的VCP代码,查找E1(35)子代码
for (int i = 0; i < vcp_max; i++) {
unsigned char vcp_code = vcp_codes[i];
if (vcp_code == 0xE1) {
// 选择子代码35
unsigned char sub_code = 0x35;
DDC2B_WriteVCP(hMonitor, 0x52, sub_code);
// 读取子代码35的值
unsigned short value = 0;
if (DDC2B_GetVCPFeature(hMonitor, 0xE1, &value)) {
// 显示子代码35的值
printf("DisplayPort mode: %d\n", value);
}
}
}
}
请注意,上述示例代码仅用于演示如何使用DDC/CI协议读取DisplayPort模式的值。您需要根据自己的需求进行修改和调整。
已解决,通过SetVCPFeature 设置E1的值为B500,如图,相当于做一个select sub-vcp 动作,如然后再getVCPFeature即可获得当前值。