Add Zotac 30 40 series gpus
This commit is contained in:
parent
ddef980db4
commit
a60a273893
7 changed files with 864 additions and 0 deletions
469
Controllers/ZotacV2GPUController/RGBController_ZotacV2GPU.cpp
Normal file
469
Controllers/ZotacV2GPUController/RGBController_ZotacV2GPU.cpp
Normal file
|
|
@ -0,0 +1,469 @@
|
|||
/*-----------------------------------------*\
|
||||
| RGBController_ZotacV2GPU.cpp |
|
||||
| |
|
||||
| Generic RGB Interface for OpenRGB |
|
||||
| ZOTAC RTX 30/40 series GPU Driver |
|
||||
| |
|
||||
| Krzysztof Haładyn (krzys_h) 3/16/2023 |
|
||||
\*-----------------------------------------*/
|
||||
|
||||
#include "RGBController_ZotacV2GPU.h"
|
||||
#include "LogManager.h"
|
||||
#include <map>
|
||||
|
||||
std::map<std::string, ZotacV2GPUConfig> ZOTAC_V2_GPU_CONFIG =
|
||||
{
|
||||
{ "N653E-1013", { 2, false } }, // ZOTAC GAMING GeForce RTX 3070 Ti Trinity OC
|
||||
{ "N612A-1012", { 2, false } }, // ZOTAC GAMING GeForce RTX 3080 Ti AMP Holo
|
||||
{ "N618A-1015", { 4, true } }, // ZOTAC GAMING GeForce RTX 3090 AMP Extreme Holo
|
||||
{ "N675E-1019", { 1, true } }, // ZOTAC GAMING GeForce RTX 4090 Trinity OC
|
||||
{ "N675A-1019", { 5, true } }, // ZOTAC GAMING GeForce RTX 4090 AMP Extreme AIRO
|
||||
};
|
||||
|
||||
std::vector<std::pair<RGBColor, RGBColor>> ZOTAC_V2_GPU_DUET_PRESETS =
|
||||
{
|
||||
{ ToRGBColor(0x32, 0xCF, 0xA7), ToRGBColor(0x93, 0x34, 0xC2) },
|
||||
{ ToRGBColor(0x00, 0xC9, 0x14), ToRGBColor(0x00, 0x20, 0xF5) },
|
||||
{ ToRGBColor(0xD1, 0xFC, 0x00), ToRGBColor(0xF1, 0x0C, 0x00) },
|
||||
{ ToRGBColor(0xFF, 0x68, 0x7C), ToRGBColor(0xD4, 0x00, 0x4D) },
|
||||
};
|
||||
|
||||
/**------------------------------------------------------------------*\
|
||||
@name ZOTAC 30/40 series GPU
|
||||
@category GPU
|
||||
@type I2C
|
||||
@save :robot:
|
||||
@direct :x:
|
||||
@effects :tools:
|
||||
@detectors DetectZotacV2GPUControllers
|
||||
@comment
|
||||
OpenRGB does not support per-zone effect modes, so only
|
||||
the synchronized mode is supported for now. Sound based
|
||||
effects are not supported. Idle/active config is not
|
||||
supported.
|
||||
\*-------------------------------------------------------------------*/
|
||||
|
||||
RGBController_ZotacV2GPU::RGBController_ZotacV2GPU(ZotacV2GPUController* controller_ptr)
|
||||
{
|
||||
controller = controller_ptr;
|
||||
|
||||
name = "ZOTAC GPU";
|
||||
vendor = "ZOTAC";
|
||||
description = "ZOTAC 30/40 series RGB GPU Device (" + controller->GetVersion() + ")";
|
||||
location = controller->GetDeviceLocation();
|
||||
type = DEVICE_TYPE_GPU;
|
||||
|
||||
try
|
||||
{
|
||||
config = ZOTAC_V2_GPU_CONFIG.at(controller->GetVersion());
|
||||
}
|
||||
catch(std::out_of_range)
|
||||
{
|
||||
LOG_ERROR("No zone config found for %s", version.c_str());
|
||||
config = ZotacV2GPUConfig();
|
||||
}
|
||||
version += std::to_string(config.numberOfZones) + " zones, "
|
||||
+ (config.supportsExternalLEDStrip ? "with" : "without") + " external LED strip support";
|
||||
|
||||
mode STATIC;
|
||||
STATIC.name = "Static";
|
||||
STATIC.value = ZOTAC_V2_GPU_MODE_STATIC;
|
||||
STATIC.flags = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR;
|
||||
STATIC.brightness_min = 0;
|
||||
STATIC.brightness_max = 100;
|
||||
STATIC.brightness = 100;
|
||||
STATIC.color_mode = MODE_COLORS_MODE_SPECIFIC;
|
||||
STATIC.colors_min = 1;
|
||||
STATIC.colors_max = 1;
|
||||
STATIC.colors.resize(1);
|
||||
STATIC.colors[0] = ToRGBColor(0, 0, 255);
|
||||
modes.push_back(STATIC);
|
||||
|
||||
mode BREATH;
|
||||
BREATH.name = "Breath";
|
||||
BREATH.value = ZOTAC_V2_GPU_MODE_BREATH;
|
||||
BREATH.flags = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR;
|
||||
BREATH.brightness_min = 0;
|
||||
BREATH.brightness_max = 100;
|
||||
BREATH.brightness = 100;
|
||||
BREATH.speed_min = 0;
|
||||
BREATH.speed_max = 100;
|
||||
BREATH.speed = 20;
|
||||
BREATH.color_mode = MODE_COLORS_MODE_SPECIFIC;
|
||||
BREATH.colors_min = 1;
|
||||
BREATH.colors_max = 1;
|
||||
BREATH.colors.resize(1);
|
||||
BREATH.colors[0] = ToRGBColor(0, 0, 255);
|
||||
modes.push_back(BREATH);
|
||||
|
||||
mode FADE;
|
||||
FADE.name = "Fade";
|
||||
FADE.value = ZOTAC_V2_GPU_MODE_FADE;
|
||||
FADE.flags = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_SPEED;
|
||||
FADE.speed_min = 0;
|
||||
FADE.speed_max = 100;
|
||||
FADE.speed = 20;
|
||||
FADE.color_mode = MODE_COLORS_NONE;
|
||||
modes.push_back(FADE);
|
||||
|
||||
mode WINK;
|
||||
WINK.name = "Wink";
|
||||
WINK.value = ZOTAC_V2_GPU_MODE_WINK;
|
||||
WINK.flags = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR;
|
||||
WINK.brightness_min = 0;
|
||||
WINK.brightness_max = 100;
|
||||
WINK.brightness = 100;
|
||||
WINK.speed_min = 0;
|
||||
WINK.speed_max = 100;
|
||||
WINK.speed = 20;
|
||||
WINK.color_mode = MODE_COLORS_MODE_SPECIFIC;
|
||||
WINK.colors_min = 1;
|
||||
WINK.colors_max = 1;
|
||||
WINK.colors.resize(1);
|
||||
WINK.colors[0] = ToRGBColor(0, 0, 255);
|
||||
modes.push_back(WINK);
|
||||
|
||||
if(config.numberOfZones > 1)
|
||||
{
|
||||
// This mode is only supported on GPUs with more than one zone,
|
||||
// because it spans multiple zones.
|
||||
|
||||
// It's also supported in synchronized mode only (which is the only
|
||||
// thing this RGBController supports for now anyway)
|
||||
|
||||
mode FLASH;
|
||||
FLASH.name = "Flash";
|
||||
FLASH.value = ZOTAC_V2_GPU_MODE_FLASH;
|
||||
FLASH.flags = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_SPEED;
|
||||
FLASH.speed_min = 0;
|
||||
FLASH.speed_max = 100;
|
||||
FLASH.speed = 20;
|
||||
FLASH.color_mode = MODE_COLORS_NONE;
|
||||
modes.push_back(FLASH);
|
||||
}
|
||||
|
||||
// (Sound activated - not supported)
|
||||
//mode SHINE;
|
||||
//SHINE.name = "Shine";
|
||||
//SHINE.value = ZOTAC_V2_GPU_MODE_SHINE;
|
||||
//SHINE.flags = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR;
|
||||
//SHINE.brightness_min = 0;
|
||||
//SHINE.brightness_max = 100;
|
||||
//SHINE.brightness = 100;
|
||||
//SHINE.color_mode = MODE_COLORS_MODE_SPECIFIC;
|
||||
//SHINE.colors_min = 1;
|
||||
//SHINE.colors_max = 1;
|
||||
//SHINE.colors.resize(1);
|
||||
//SHINE.colors[0] = ToRGBColor(0, 0, 255);
|
||||
//modes.push_back(SHINE);
|
||||
|
||||
mode RANDOM;
|
||||
RANDOM.name = "Random";
|
||||
RANDOM.value = ZOTAC_V2_GPU_MODE_RANDOM;
|
||||
RANDOM.flags = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR;
|
||||
RANDOM.brightness_min = 0;
|
||||
RANDOM.brightness_max = 100;
|
||||
RANDOM.brightness = 100;
|
||||
RANDOM.speed_min = 0;
|
||||
RANDOM.speed_max = 100;
|
||||
RANDOM.speed = 20;
|
||||
RANDOM.color_mode = MODE_COLORS_MODE_SPECIFIC;
|
||||
RANDOM.colors_min = 1;
|
||||
RANDOM.colors_max = 1;
|
||||
RANDOM.colors.resize(1);
|
||||
RANDOM.colors[0] = ToRGBColor(0, 0, 255);
|
||||
modes.push_back(RANDOM);
|
||||
|
||||
mode SLIDE;
|
||||
SLIDE.name = "Slide";
|
||||
SLIDE.value = ZOTAC_V2_GPU_MODE_SLIDE;
|
||||
SLIDE.flags = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_DIRECTION_LR;
|
||||
SLIDE.brightness_min = 0;
|
||||
SLIDE.brightness_max = 100;
|
||||
SLIDE.brightness = 100;
|
||||
SLIDE.speed_min = 0;
|
||||
SLIDE.speed_max = 100;
|
||||
SLIDE.speed = 20;
|
||||
SLIDE.color_mode = MODE_COLORS_MODE_SPECIFIC;
|
||||
SLIDE.colors_min = 1;
|
||||
SLIDE.colors_max = 1;
|
||||
SLIDE.colors.resize(1);
|
||||
SLIDE.colors[0] = ToRGBColor(0, 0, 255);
|
||||
modes.push_back(SLIDE);
|
||||
|
||||
mode RAINBOW;
|
||||
RAINBOW.name = "Rainbow";
|
||||
RAINBOW.value = ZOTAC_V2_GPU_MODE_RAINBOW;
|
||||
RAINBOW.flags = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_DIRECTION_LR;
|
||||
RAINBOW.speed_min = 0;
|
||||
RAINBOW.speed_max = 100;
|
||||
RAINBOW.speed = 20;
|
||||
RAINBOW.color_mode = MODE_COLORS_NONE;
|
||||
modes.push_back(RAINBOW);
|
||||
mode RAINBOW_CIRCUIT = RAINBOW;
|
||||
RAINBOW_CIRCUIT.name = "Rainbow (circuit)";
|
||||
modes.push_back(RAINBOW_CIRCUIT);
|
||||
|
||||
mode MARQUEE;
|
||||
MARQUEE.name = "Marquee";
|
||||
MARQUEE.value = ZOTAC_V2_GPU_MODE_MARQUEE;
|
||||
MARQUEE.flags = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_DIRECTION_LR;
|
||||
MARQUEE.brightness_min = 0;
|
||||
MARQUEE.brightness_max = 100;
|
||||
MARQUEE.brightness = 100;
|
||||
MARQUEE.speed_min = 0;
|
||||
MARQUEE.speed_max = 100;
|
||||
MARQUEE.speed = 20;
|
||||
MARQUEE.color_mode = MODE_COLORS_MODE_SPECIFIC;
|
||||
MARQUEE.colors_min = 1;
|
||||
MARQUEE.colors_max = 1;
|
||||
MARQUEE.colors.resize(1);
|
||||
MARQUEE.colors[0] = ToRGBColor(0, 0, 255);
|
||||
modes.push_back(MARQUEE);
|
||||
mode MARQUEE_CIRCUIT = MARQUEE;
|
||||
MARQUEE_CIRCUIT.name = "Marquee (circuit)";
|
||||
modes.push_back(MARQUEE_CIRCUIT);
|
||||
|
||||
mode DRIP;
|
||||
DRIP.name = "Drip";
|
||||
DRIP.value = ZOTAC_V2_GPU_MODE_DRIP;
|
||||
DRIP.flags = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_DIRECTION_LR;
|
||||
DRIP.brightness_min = 0;
|
||||
DRIP.brightness_max = 100;
|
||||
DRIP.brightness = 100;
|
||||
DRIP.speed_min = 0;
|
||||
DRIP.speed_max = 100;
|
||||
DRIP.speed = 20;
|
||||
DRIP.color_mode = MODE_COLORS_MODE_SPECIFIC;
|
||||
DRIP.colors_min = 1;
|
||||
DRIP.colors_max = 1;
|
||||
DRIP.colors.resize(1);
|
||||
DRIP.colors[0] = ToRGBColor(0, 0, 255);
|
||||
modes.push_back(DRIP);
|
||||
mode DRIP_CIRCUIT = DRIP;
|
||||
DRIP_CIRCUIT.name = "Drip (circuit)";
|
||||
modes.push_back(DRIP_CIRCUIT);
|
||||
|
||||
// (Sound activated - not supported)
|
||||
//mode DANCE;
|
||||
//DANCE.name = "Dance (sound activated)";
|
||||
//DANCE.value = ZOTAC_V2_GPU_MODE_DANCE;
|
||||
//DANCE.flags = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_DIRECTION_LR;
|
||||
//DANCE.brightness_min = 0;
|
||||
//DANCE.brightness_max = 100;
|
||||
//DANCE.brightness = 100;
|
||||
//DANCE.color_mode = MODE_COLORS_MODE_SPECIFIC;
|
||||
//DANCE.colors_min = 1;
|
||||
//DANCE.colors_max = 1;
|
||||
//DANCE.colors.resize(1);
|
||||
//DANCE.colors[0] = ToRGBColor(0, 0, 255);
|
||||
//modes.push_back(DANCE);
|
||||
|
||||
mode DUET;
|
||||
DUET.name = "Duet";
|
||||
DUET.value = ZOTAC_V2_GPU_MODE_DUET;
|
||||
DUET.flags = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_DIRECTION_LR;
|
||||
DUET.speed_min = 0;
|
||||
DUET.speed_max = 100;
|
||||
DUET.speed = 20;
|
||||
DUET.color_mode = MODE_COLORS_MODE_SPECIFIC;
|
||||
DUET.colors_min = 2;
|
||||
DUET.colors_max = 2;
|
||||
DUET.colors.resize(2);
|
||||
DUET.colors[0] = ZOTAC_V2_GPU_DUET_PRESETS[1].first;
|
||||
DUET.colors[1] = ZOTAC_V2_GPU_DUET_PRESETS[1].second;
|
||||
modes.push_back(DUET);
|
||||
mode DUET_CIRCUIT = DUET;
|
||||
DUET_CIRCUIT.name = "Duet (circuit)";
|
||||
modes.push_back(DUET_CIRCUIT);
|
||||
|
||||
|
||||
mode PATH;
|
||||
PATH.name = "Path";
|
||||
PATH.value = ZOTAC_V2_GPU_MODE_PATH;
|
||||
PATH.flags = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_DIRECTION_LR;
|
||||
PATH.brightness_min = 0;
|
||||
PATH.brightness_max = 100;
|
||||
PATH.brightness = 100;
|
||||
PATH.speed_min = 0;
|
||||
PATH.speed_max = 100;
|
||||
PATH.speed = 20;
|
||||
PATH.color_mode = MODE_COLORS_NONE;
|
||||
modes.push_back(PATH);
|
||||
|
||||
SetupZones();
|
||||
}
|
||||
|
||||
RGBController_ZotacV2GPU::~RGBController_ZotacV2GPU()
|
||||
{
|
||||
delete controller;
|
||||
}
|
||||
|
||||
void RGBController_ZotacV2GPU::SetupZones()
|
||||
{
|
||||
led new_led;
|
||||
new_led.name = "GPU LED";
|
||||
leds.push_back(new_led);
|
||||
|
||||
zone new_zone;
|
||||
new_zone.name = "GPU Zone";
|
||||
new_zone.type = ZONE_TYPE_SINGLE;
|
||||
new_zone.leds_min = 1;
|
||||
new_zone.leds_max = 1;
|
||||
new_zone.leds_count = 1;
|
||||
new_zone.matrix_map = NULL;
|
||||
zones.push_back(new_zone);
|
||||
|
||||
SetupColors();
|
||||
SetupInitialValues();
|
||||
}
|
||||
|
||||
void RGBController_ZotacV2GPU::SetupInitialValues()
|
||||
{
|
||||
/*---------------------------------------------------------*\
|
||||
| Retrieve current values by reading the device |
|
||||
\*---------------------------------------------------------*/
|
||||
|
||||
bool on;
|
||||
int syncMode;
|
||||
ZotacV2GPUZone zoneConfig;
|
||||
|
||||
// We don't support anything other than synchronized mode, so read the last
|
||||
// config used in synchronized mode for idle settings.
|
||||
int zoneNum = FindSynchronizedZoneNum(ZOTAC_V2_GPU_SYNC_SYNCHRONIZED);
|
||||
if(!controller->GetMode(zoneNum, ZOTAC_V2_GPU_CONFIG_IDLE, syncMode, zoneConfig, on))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for(unsigned int i = 0; i < modes.size(); ++i)
|
||||
{
|
||||
if(zoneConfig.mode != modes[i].value)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(zoneConfig.mode == ZOTAC_V2_GPU_MODE_RAINBOW ||
|
||||
zoneConfig.mode == ZOTAC_V2_GPU_MODE_MARQUEE ||
|
||||
zoneConfig.mode == ZOTAC_V2_GPU_MODE_DRIP ||
|
||||
zoneConfig.mode == ZOTAC_V2_GPU_MODE_DUET)
|
||||
{
|
||||
if((zoneConfig.circuit == ZOTAC_V2_GPU_CIRCUIT_ON) != (modes[i].name.find("(circuit)") != std::string::npos))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
active_mode = i;
|
||||
}
|
||||
|
||||
colors[0] = zoneConfig.color1;
|
||||
if(modes[active_mode].colors.size() >= 1)
|
||||
{
|
||||
modes[active_mode].colors[0] = zoneConfig.color1;
|
||||
}
|
||||
if(modes[active_mode].colors.size() >= 2)
|
||||
{
|
||||
modes[active_mode].colors[1] = zoneConfig.color2;
|
||||
}
|
||||
modes[active_mode].speed = zoneConfig.speed;
|
||||
modes[active_mode].brightness = zoneConfig.brightness;
|
||||
modes[active_mode].direction = zoneConfig.direction;
|
||||
|
||||
SignalUpdate();
|
||||
}
|
||||
|
||||
void RGBController_ZotacV2GPU::ResizeZone(int /*zone*/, int /*new_size*/)
|
||||
{
|
||||
/*---------------------------------------------------------*\
|
||||
| This device does not support resizing zones |
|
||||
\*---------------------------------------------------------*/
|
||||
}
|
||||
|
||||
void RGBController_ZotacV2GPU::DeviceUpdateLEDs()
|
||||
{
|
||||
DeviceUpdateMode();
|
||||
}
|
||||
|
||||
void RGBController_ZotacV2GPU::UpdateZoneLEDs(int /*zone*/)
|
||||
{
|
||||
DeviceUpdateMode();
|
||||
}
|
||||
|
||||
void RGBController_ZotacV2GPU::UpdateSingleLED(int /*led*/)
|
||||
{
|
||||
DeviceUpdateMode();
|
||||
}
|
||||
|
||||
void RGBController_ZotacV2GPU::DeviceUpdateMode()
|
||||
{
|
||||
ZotacV2GPUZone zoneConfig;
|
||||
zoneConfig.mode = modes[active_mode].value;
|
||||
|
||||
zoneConfig.color1 = modes[active_mode].colors.size() >= 1 ? modes[active_mode].colors[0] : ToRGBColor(0, 0, 0);
|
||||
zoneConfig.color2 = modes[active_mode].colors.size() >= 2 ? modes[active_mode].colors[1] : ToRGBColor(0, 0, 0);
|
||||
|
||||
// This is probably not strictly neccessary
|
||||
zoneConfig.colorPreset = 0;
|
||||
if(zoneConfig.mode == ZOTAC_V2_GPU_MODE_DUET)
|
||||
{
|
||||
zoneConfig.colorPreset = ZOTAC_V2_GPU_DUET_PRESETS.size(); // custom
|
||||
for(int i = 0; i < ZOTAC_V2_GPU_DUET_PRESETS.size(); ++i)
|
||||
{
|
||||
if(zoneConfig.color1 == ZOTAC_V2_GPU_DUET_PRESETS[i].first &&
|
||||
zoneConfig.color2 == ZOTAC_V2_GPU_DUET_PRESETS[i].second)
|
||||
{
|
||||
zoneConfig.colorPreset = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
zoneConfig.speed = modes[active_mode].speed;
|
||||
zoneConfig.brightness = modes[active_mode].brightness;
|
||||
zoneConfig.direction = modes[active_mode].direction == MODE_DIRECTION_RIGHT ? ZOTAC_V2_GPU_DIR_RIGHT : ZOTAC_V2_GPU_DIR_LEFT;
|
||||
|
||||
if(zoneConfig.mode == ZOTAC_V2_GPU_MODE_RAINBOW ||
|
||||
zoneConfig.mode == ZOTAC_V2_GPU_MODE_MARQUEE ||
|
||||
zoneConfig.mode == ZOTAC_V2_GPU_MODE_DRIP ||
|
||||
zoneConfig.mode == ZOTAC_V2_GPU_MODE_DUET)
|
||||
{
|
||||
zoneConfig.circuit = modes[active_mode].name.find("(circuit)") != std::string::npos ? ZOTAC_V2_GPU_CIRCUIT_ON : ZOTAC_V2_GPU_CIRCUIT_OFF;
|
||||
}
|
||||
else
|
||||
{
|
||||
zoneConfig.circuit = 0;
|
||||
}
|
||||
|
||||
int zoneNum = FindSynchronizedZoneNum(ZOTAC_V2_GPU_SYNC_SYNCHRONIZED);
|
||||
controller->TurnOnOff(true);
|
||||
controller->SetMode(zoneNum, ZOTAC_V2_GPU_CONFIG_IDLE, ZOTAC_V2_GPU_SYNC_SYNCHRONIZED, zoneConfig);
|
||||
controller->SetMode(zoneNum, ZOTAC_V2_GPU_CONFIG_ACTIVE, ZOTAC_V2_GPU_SYNC_SYNCHRONIZED, zoneConfig);
|
||||
}
|
||||
|
||||
int RGBController_ZotacV2GPU::FindSynchronizedZoneNum(int syncMode)
|
||||
{
|
||||
// Figure out the index of the zone used for ZOTAC_V2_GPU_SYNC_SYNCHRONIZED
|
||||
// or ZOTAC_V2_GPU_SYNC_SYNCHRONIZED_WITH_EXTERNAL settings based on the GPU
|
||||
// zone config
|
||||
|
||||
int lastRealZone = config.numberOfZones - 1;
|
||||
if(config.supportsExternalLEDStrip)
|
||||
{
|
||||
lastRealZone += 1;
|
||||
}
|
||||
|
||||
if(syncMode == ZOTAC_V2_GPU_SYNC_SYNCHRONIZED)
|
||||
{
|
||||
return lastRealZone + 1;
|
||||
}
|
||||
else if(syncMode == ZOTAC_V2_GPU_SYNC_SYNCHRONIZED_WITH_EXTERNAL)
|
||||
{
|
||||
assert(config.supportsExternalLEDStrip);
|
||||
return lastRealZone + 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(false);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
38
Controllers/ZotacV2GPUController/RGBController_ZotacV2GPU.h
Normal file
38
Controllers/ZotacV2GPUController/RGBController_ZotacV2GPU.h
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/*-----------------------------------------*\
|
||||
| RGBController_ZotacV2GPU.h |
|
||||
| |
|
||||
| Generic RGB Interface for OpenRGB |
|
||||
| ZOTAC RTX 30/40 series GPU Driver |
|
||||
| |
|
||||
| Krzysztof Haładyn (krzys_h) 3/16/2023 |
|
||||
\*-----------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "RGBController.h"
|
||||
#include "ZotacV2GPUController.h"
|
||||
|
||||
class RGBController_ZotacV2GPU : public RGBController
|
||||
{
|
||||
public:
|
||||
RGBController_ZotacV2GPU(ZotacV2GPUController* controller_ptr);
|
||||
~RGBController_ZotacV2GPU();
|
||||
|
||||
void SetupInitialValues();
|
||||
void SetupZones();
|
||||
|
||||
void ResizeZone(int zone, int new_size);
|
||||
|
||||
void DeviceUpdateLEDs();
|
||||
void UpdateZoneLEDs(int zone);
|
||||
void UpdateSingleLED(int led);
|
||||
|
||||
void DeviceUpdateMode();
|
||||
|
||||
ZotacV2GPUConfig config;
|
||||
|
||||
private:
|
||||
ZotacV2GPUController* controller;
|
||||
|
||||
int FindSynchronizedZoneNum(int syncMode);
|
||||
};
|
||||
204
Controllers/ZotacV2GPUController/ZotacV2GPUController.cpp
Normal file
204
Controllers/ZotacV2GPUController/ZotacV2GPUController.cpp
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
/*------------------------------------------*\
|
||||
| ZotacV2GPUController.cpp |
|
||||
| |
|
||||
| Driver for ZOTAC GeForce RTX 30/40 series |
|
||||
| GPU lighting controller |
|
||||
| |
|
||||
| Krzysztof Haładyn (krzys_h) 3/16/2023 |
|
||||
\*------------------------------------------*/
|
||||
|
||||
#include "ZotacV2GPUController.h"
|
||||
#include "LogManager.h"
|
||||
|
||||
ZotacV2GPUController::ZotacV2GPUController(i2c_smbus_interface* bus, u8 dev)
|
||||
{
|
||||
this->bus = bus;
|
||||
this->dev = dev;
|
||||
|
||||
if(dev)
|
||||
{
|
||||
ReadVersion();
|
||||
}
|
||||
}
|
||||
|
||||
ZotacV2GPUController::~ZotacV2GPUController()
|
||||
{
|
||||
}
|
||||
|
||||
std::string ZotacV2GPUController::GetDeviceLocation()
|
||||
{
|
||||
std::string return_string(bus->device_name);
|
||||
char addr[5];
|
||||
snprintf(addr, 5, "0x%02X", dev);
|
||||
return_string.append(", address ");
|
||||
return_string.append(addr);
|
||||
return ("I2C: " + return_string);
|
||||
}
|
||||
|
||||
std::string ZotacV2GPUController::GetVersion()
|
||||
{
|
||||
return version;
|
||||
}
|
||||
|
||||
bool ZotacV2GPUController::ReadVersion()
|
||||
{
|
||||
u8 data_pkt[] = { ZOTAC_V2_GPU_REG_RGB, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
if(bus->i2c_write_block(dev, sizeof(data_pkt), data_pkt) < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
u8 rdata_pkt[I2C_SMBUS_BLOCK_MAX] = { 0x00 };
|
||||
int rdata_len = sizeof(rdata_pkt);
|
||||
if(bus->i2c_read_block(dev, &rdata_len, rdata_pkt) < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
version = std::string((char*)rdata_pkt);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ZotacV2GPUController::TurnOnOff(bool on)
|
||||
{
|
||||
return SendCommand(on, false, 0, 0, 0, ZotacV2GPUZone());
|
||||
}
|
||||
|
||||
bool ZotacV2GPUController::ResetToDefaults()
|
||||
{
|
||||
return SendCommand(true, true, 0, 0, 0, ZotacV2GPUZone());
|
||||
}
|
||||
|
||||
bool ZotacV2GPUController::SetMode(int zone, int idleActive, int syncMode, ZotacV2GPUZone zoneConfig)
|
||||
{
|
||||
// NOTE: This only works if the device is in the ON state. Otherwise, the SetMode command will behave
|
||||
// like TurnOnOff(true), and the change will be ignored.
|
||||
|
||||
// NOTE: syncMode is per idleActive, NOT per (zone, idleActive) pair like zoneConfig is - as in,
|
||||
// you can have ACTIVE in INDIVIDUAL mode and IDLE in SYNCHRONIZED mode, but you can't have
|
||||
// different syncModes in different zones. The last written value is always applied, so make
|
||||
// sure you don't change it accidentally between writes.
|
||||
// TODO: Verify what I said above - it doesn't match the GUI, but it seems to match the NvAPISpy traces
|
||||
// From the GUI it seems like syncMode should be global, period.
|
||||
|
||||
return SendCommand(true, false, zone, idleActive, syncMode, zoneConfig);
|
||||
}
|
||||
|
||||
bool ZotacV2GPUController::GetMode(int zone, int idleActive, int& syncMode, ZotacV2GPUZone& zoneConfig, bool& on)
|
||||
{
|
||||
u8 data_pkt[] =
|
||||
{
|
||||
ZOTAC_V2_GPU_REG_RGB,
|
||||
0xF0,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
(u8)idleActive,
|
||||
(u8)zone,
|
||||
0x00,
|
||||
};
|
||||
|
||||
if(bus->i2c_write_block(dev, sizeof(data_pkt), data_pkt) < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
u8 rdata_pkt[I2C_SMBUS_BLOCK_MAX] = { 0x00 };
|
||||
int rdata_len = sizeof(rdata_pkt);
|
||||
if(bus->i2c_read_block(dev, &rdata_len, rdata_pkt) < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool readReset;
|
||||
int readZone;
|
||||
int readIdleActive;
|
||||
if(!ParseCommand(on, readReset, readZone, readIdleActive, syncMode, zoneConfig))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(readReset != 0)
|
||||
{
|
||||
LOG_WARNING("Reset byte was not 0?!");
|
||||
}
|
||||
|
||||
if(readZone != zone || readIdleActive != idleActive)
|
||||
{
|
||||
LOG_WARNING("Got unexpected data - expected to recieve data for (%d, %d) but got for (%d, %d)", zone, idleActive, readZone, readIdleActive);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ZotacV2GPUController::SendCommand(bool on, bool reset, int zone, int idleActive, int syncMode, ZotacV2GPUZone zoneConfig)
|
||||
{
|
||||
u8 data_pkt[] =
|
||||
{
|
||||
ZOTAC_V2_GPU_REG_RGB,
|
||||
on ? (u8)0x01 : (u8)0x00,
|
||||
reset ? (u8)0x01 : (u8)0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
(u8)idleActive,
|
||||
(u8)zone,
|
||||
(u8)zoneConfig.mode,
|
||||
(u8)RGBGetRValue(zoneConfig.color1),
|
||||
(u8)RGBGetGValue(zoneConfig.color1),
|
||||
(u8)RGBGetBValue(zoneConfig.color1),
|
||||
(u8)zoneConfig.speed,
|
||||
(u8)zoneConfig.brightness,
|
||||
(u8)zoneConfig.direction,
|
||||
0x00,
|
||||
(u8)syncMode,
|
||||
(u8)zoneConfig.circuit,
|
||||
(u8)RGBGetRValue(zoneConfig.color2),
|
||||
(u8)RGBGetGValue(zoneConfig.color2),
|
||||
(u8)RGBGetBValue(zoneConfig.color2),
|
||||
(u8)zoneConfig.colorPreset,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
};
|
||||
|
||||
if(bus->i2c_write_block(dev, sizeof(data_pkt), data_pkt) < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ZotacV2GPUController::ParseCommand(bool& on, bool& reset, int& zone, int& idleActive, int& syncMode, ZotacV2GPUZone& zoneConfig)
|
||||
{
|
||||
u8 rdata_pkt[I2C_SMBUS_BLOCK_MAX] = { 0x00 };
|
||||
int rdata_len = sizeof(rdata_pkt);
|
||||
|
||||
if(bus->i2c_read_block(dev, &rdata_len, rdata_pkt) < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
on = rdata_pkt[0] != 0x00;
|
||||
reset = rdata_pkt[1] != 0x00;
|
||||
idleActive = rdata_pkt[4];
|
||||
zone = rdata_pkt[5];
|
||||
zoneConfig.mode = rdata_pkt[6];
|
||||
zoneConfig.color1 = ToRGBColor(rdata_pkt[7], rdata_pkt[8], rdata_pkt[9]);
|
||||
zoneConfig.speed = rdata_pkt[10];
|
||||
zoneConfig.brightness = rdata_pkt[11];
|
||||
zoneConfig.direction = rdata_pkt[12];
|
||||
syncMode = rdata_pkt[14];
|
||||
zoneConfig.circuit = rdata_pkt[15];
|
||||
zoneConfig.color2 = ToRGBColor(rdata_pkt[16], rdata_pkt[17], rdata_pkt[18]);
|
||||
zoneConfig.colorPreset = rdata_pkt[19];
|
||||
return true;
|
||||
}
|
||||
106
Controllers/ZotacV2GPUController/ZotacV2GPUController.h
Normal file
106
Controllers/ZotacV2GPUController/ZotacV2GPUController.h
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
/*-----------------------------------------*\
|
||||
| ZotacV2GPUController.cpp |
|
||||
| |
|
||||
| Definitions and types for ZOTAC GeForce |
|
||||
| RTX 30/40 series GPU lighting controller |
|
||||
| |
|
||||
| Krzysztof Haładyn (krzys_h) 3/16/2023 |
|
||||
\*-----------------------------------------*/
|
||||
|
||||
#include <string>
|
||||
#include "i2c_smbus.h"
|
||||
#include "RGBController.h"
|
||||
|
||||
#pragma once
|
||||
|
||||
enum
|
||||
{
|
||||
ZOTAC_V2_GPU_REG_RGB = 0xA0,
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
ZOTAC_V2_GPU_CONFIG_IDLE = 0x00, // Config for when there is no load
|
||||
ZOTAC_V2_GPU_CONFIG_ACTIVE = 0x01, // Config for when GPU is under load
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
ZOTAC_V2_GPU_SYNC_INDIVIDUAL = 0x00, // Everything separated
|
||||
ZOTAC_V2_GPU_SYNC_SYNCHRONIZED = 0x01, // All internal zones synchronized, external is separated
|
||||
ZOTAC_V2_GPU_SYNC_SYNCHRONIZED_WITH_EXTERNAL = 0x02, // Everything synchronized
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
ZOTAC_V2_GPU_MODE_STATIC = 0x00, // Basic static color
|
||||
ZOTAC_V2_GPU_MODE_BREATH = 0x01, // Single color fades on and off
|
||||
ZOTAC_V2_GPU_MODE_FADE = 0x02, // All colors fade through the spectrum
|
||||
ZOTAC_V2_GPU_MODE_WINK = 0x03, // Single color flashes on and off
|
||||
ZOTAC_V2_GPU_MODE_FLASH = 0x04, // Each zone flashes a different color (only supported in SYNCHRONIZED or SYNCHRONIZED_WITH_EXTERNAL mode)
|
||||
ZOTAC_V2_GPU_MODE_SHINE = 0x05, // (Sound activated) Single color, on and off
|
||||
ZOTAC_V2_GPU_MODE_RANDOM = 0x06, // Single color, random patern
|
||||
ZOTAC_V2_GPU_MODE_SLIDE = 0x07, // Single color, moves one side to the other
|
||||
ZOTAC_V2_GPU_MODE_RAINBOW = 0x08, // All colors move one side to the other
|
||||
ZOTAC_V2_GPU_MODE_MARQUEE = 0x09, // Very similar to SLIDE effect
|
||||
ZOTAC_V2_GPU_MODE_DRIP = 0x0A, // Similar to SLIDE as well, less color moves
|
||||
ZOTAC_V2_GPU_MODE_DANCE = 0x0B, // (Sound activated) Single color, equalizer effect
|
||||
ZOTAC_V2_GPU_MODE_DUET = 0x17, // Dual colors
|
||||
ZOTAC_V2_GPU_MODE_PATH = 0x18, // Very similar to RAINBOW effect
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
ZOTAC_V2_GPU_DIR_LEFT = 0x00,
|
||||
ZOTAC_V2_GPU_DIR_RIGHT = 0x01,
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
ZOTAC_V2_GPU_CIRCUIT_ON = 0x00,
|
||||
ZOTAC_V2_GPU_CIRCUIT_OFF = 0x01,
|
||||
};
|
||||
|
||||
struct ZotacV2GPUConfig
|
||||
{
|
||||
int numberOfZones = 0;
|
||||
bool supportsExternalLEDStrip = false;
|
||||
};
|
||||
|
||||
struct ZotacV2GPUZone
|
||||
{
|
||||
int mode = 0;
|
||||
RGBColor color1 = ToRGBColor(0, 0, 0);
|
||||
RGBColor color2 = ToRGBColor(0, 0, 0);
|
||||
unsigned int colorPreset = 0;
|
||||
unsigned int speed = 0;
|
||||
unsigned int brightness = 0;
|
||||
unsigned int direction = 0;
|
||||
unsigned int circuit = 0;
|
||||
};
|
||||
|
||||
|
||||
class ZotacV2GPUController
|
||||
{
|
||||
public:
|
||||
ZotacV2GPUController(i2c_smbus_interface* bus, u8 dev);
|
||||
~ZotacV2GPUController();
|
||||
|
||||
std::string GetDeviceLocation();
|
||||
std::string GetVersion();
|
||||
|
||||
bool TurnOnOff(bool on);
|
||||
bool ResetToDefaults();
|
||||
bool GetMode(int zone, int idleActive, int& syncMode, ZotacV2GPUZone& zoneConfig, bool& on);
|
||||
bool SetMode(int zone, int idleActive, int syncMode, ZotacV2GPUZone zoneConfig);
|
||||
|
||||
private:
|
||||
bool ReadVersion();
|
||||
bool SendCommand(bool on, bool reset, int zone, int idleActive, int syncMode, ZotacV2GPUZone zoneConfig);
|
||||
bool ParseCommand(bool& on, bool& reset, int& zone, int& idleActive, int& syncMode, ZotacV2GPUZone& zoneConfig);
|
||||
|
||||
i2c_smbus_interface* bus;
|
||||
u8 dev;
|
||||
std::string version;
|
||||
|
||||
};
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
#include "Detector.h"
|
||||
#include "ZotacV2GPUController.h"
|
||||
#include "RGBController.h"
|
||||
#include "RGBController_ZotacV2GPU.h"
|
||||
#include "i2c_smbus.h"
|
||||
#include "pci_ids.h"
|
||||
|
||||
/******************************************************************************************\
|
||||
* *
|
||||
* DetectZotacV2GPUControllers *
|
||||
* *
|
||||
* Detect ZOTAC 30/40 series RGB controllers on the enumerated I2C busses *
|
||||
* at address 0x49. *
|
||||
* *
|
||||
* bus - pointer to i2c_smbus_interface where RGB device is connected *
|
||||
* dev - I2C address of RGB device *
|
||||
* *
|
||||
\******************************************************************************************/
|
||||
|
||||
void DetectZotacV2GPUControllers(i2c_smbus_interface* bus, u8 i2c_addr, const std::string& name)
|
||||
{
|
||||
ZotacV2GPUController* controller = new ZotacV2GPUController(bus, i2c_addr);
|
||||
RGBController_ZotacV2GPU* rgb_controller = new RGBController_ZotacV2GPU(controller);
|
||||
rgb_controller->name = name;
|
||||
|
||||
if(rgb_controller->config.numberOfZones > 0)
|
||||
{
|
||||
ResourceManager::get()->RegisterRGBController(rgb_controller);
|
||||
}
|
||||
}
|
||||
|
||||
REGISTER_I2C_PCI_DETECTOR("ZOTAC GAMING GeForce RTX 3070 Ti Trinity OC", DetectZotacV2GPUControllers, NVIDIA_VEN, NVIDIA_RTX3070TI_DEV, ZOTAC_SUB_VEN, ZOTAC_RTX3070TI_TRINITY_SUB_DEV, 0x49);
|
||||
REGISTER_I2C_PCI_DETECTOR("ZOTAC GAMING GeForce RTX 3080 Ti AMP Holo", DetectZotacV2GPUControllers, NVIDIA_VEN, NVIDIA_RTX3080TI_DEV, ZOTAC_SUB_VEN, ZOTAC_RTX3080TI_AMP_SUB_DEV, 0x49);
|
||||
REGISTER_I2C_PCI_DETECTOR("ZOTAC GAMING GeForce RTX 3090 AMP Extreme Holo", DetectZotacV2GPUControllers, NVIDIA_VEN, NVIDIA_RTX3090_DEV, ZOTAC_SUB_VEN, ZOTAC_RTX3090_AMP_SUB_DEV, 0x49);
|
||||
REGISTER_I2C_PCI_DETECTOR("ZOTAC GAMING GeForce RTX 4090 Trinity OC", DetectZotacV2GPUControllers, NVIDIA_VEN, NVIDIA_RTX4090_DEV, ZOTAC_SUB_VEN, ZOTAC_RTX4090_TRINITY_SUB_DEV, 0x49);
|
||||
REGISTER_I2C_PCI_DETECTOR("ZOTAC GAMING GeForce RTX 4090 AMP Extreme AIRO", DetectZotacV2GPUControllers, NVIDIA_VEN, NVIDIA_RTX4090_DEV, ZOTAC_SUB_VEN, ZOTAC_RTX4090_AMP_SUB_DEV, 0x49);
|
||||
|
|
@ -189,6 +189,7 @@ INCLUDEPATH +=
|
|||
Controllers/YeelightController/ \
|
||||
Controllers/ZalmanZSyncController/ \
|
||||
Controllers/ZotacTuringGPUController/ \
|
||||
Controllers/ZotacV2GPUController/ \
|
||||
KeyboardLayoutManager/ \
|
||||
RGBController/ \
|
||||
qt/
|
||||
|
|
@ -734,6 +735,8 @@ HEADERS +=
|
|||
Controllers/ZETKeyboardController/RGBController_ZETBladeOptical.h \
|
||||
Controllers/ZotacTuringGPUController/ZotacTuringGPUController.h \
|
||||
Controllers/ZotacTuringGPUController/RGBController_ZotacTuringGPU.h \
|
||||
Controllers/ZotacV2GPUController/ZotacV2GPUController.h \
|
||||
Controllers/ZotacV2GPUController/RGBController_ZotacV2GPU.h \
|
||||
KeyboardLayoutManager/KeyboardLayoutManager.h \
|
||||
RGBController/RGBController.h \
|
||||
RGBController/RGBController_Dummy.h \
|
||||
|
|
@ -1431,6 +1434,9 @@ SOURCES +=
|
|||
Controllers/ZotacTuringGPUController/ZotacTuringGPUController.cpp \
|
||||
Controllers/ZotacTuringGPUController/ZotacTuringGPUControllerDetect.cpp \
|
||||
Controllers/ZotacTuringGPUController/RGBController_ZotacTuringGPU.cpp \
|
||||
Controllers/ZotacV2GPUController/ZotacV2GPUController.cpp \
|
||||
Controllers/ZotacV2GPUController/ZotacV2GPUControllerDetect.cpp \
|
||||
Controllers/ZotacV2GPUController/RGBController_ZotacV2GPU.cpp \
|
||||
KeyboardLayoutManager/KeyboardLayoutManager.cpp \
|
||||
RGBController/RGBController.cpp \
|
||||
RGBController/RGBController_Dummy.cpp \
|
||||
|
|
|
|||
|
|
@ -628,6 +628,11 @@
|
|||
#define ZOTAC_RTX2060S_AMP_SUB_DEV 0x5511
|
||||
#define ZOTAC_RTX2070S_GAMING_SUB_DEV 0x7500
|
||||
#define ZOTAC_RTX2080_AMP_SUB_DEV 0x3500
|
||||
#define ZOTAC_RTX3070TI_TRINITY_SUB_DEV 0x1653
|
||||
#define ZOTAC_RTX3080TI_AMP_SUB_DEV 0x2612
|
||||
#define ZOTAC_RTX3090_AMP_SUB_DEV 0x1619
|
||||
#define ZOTAC_RTX4090_TRINITY_SUB_DEV 0x3675
|
||||
#define ZOTAC_RTX4090_AMP_SUB_DEV 0x4675
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
| Manli Sub-Device IDs |
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue