signal-rgb-test/Corsair_Virtuoso_XT_Headset.js

377 lines
12 KiB
JavaScript

export function Name() { return "Corsair Virtuoso Device"; }
export function VendorId() { return 0x1B1C; }
export function ProductId() { return Object.keys(CORSAIRdeviceLibrary.PIDLibrary); }
export function Publisher() { return "WhirlwindFx"; }
export function Documentation(){ return "troubleshooting/corsair"; }
export function Size() { return [1, 1]; }
/* global
shutdownColor:readonly
LightingMode:readonly
forcedColor:readonly
micLedMode:readonly
micMuteColor:readonly
idleTimeout:readonly
SidetoneAmount:readonly
*/
export function ControllableParameters() {
return [
{"property":"shutdownColor", "group":"lighting", "label":"Shutdown Color", "min":"0", "max":"360", "type":"color", "default":"#009bde"},
{"property":"LightingMode", "group":"lighting", "label":"Lighting Mode", "type":"combobox", "values":["Canvas", "Forced"], "default":"Canvas"},
{"property":"forcedColor", "group":"lighting", "label":"Forced Color", "min":"0", "max":"360", "type":"color", "default":"#009bde"},
{"property":"micLedMode", "group":"lighting", "label":"Microphone LED Mode", "type":"combobox", "values":["Canvas", "MuteState"], "default":"Canvas"},
{"property":"micMuteColor", "group":"lighting", "label":"Microphone Mute Color", "min":"0", "max":"360", "type":"color", "default":"#ff0000"},
{"property":"SidetoneAmount", "group":"", "label":"Sidetone", "step":"1", "type":"number", "min":"0", "max":"100", "default":"0"},
];
}
export function Initialize() {
CORSAIR.Initialize();
}
export function Render() {
CORSAIR.sendColors();
if(CORSAIR.getWirelessSupport()){
CORSAIR.fetchBattery();
}
}
export function Shutdown(SystemSuspending) {
if(SystemSuspending){
// Go Dark on System Sleep/Shutdown
CORSAIR.sendColors("#000000");
}else{
const headsetMode = CORSAIR.getWirelessSupport() === true ? 0x09 : 0x08;
device.write([0x02, headsetMode, 0x01, 0x03, 0x00, 0x01], 64); // Hardware mode
}
}
export function onSidetoneAmountChanged() {
CORSAIR.setSidetone();
}
export function onidleTimeoutChanged() {
CORSAIR.setIdleTimeout();
}
export class CORSAIR_Device_Protocol {
constructor() {
this.Config = {
DeviceProductID: 0x0000,
DeviceName: "Corsair Virtuoso Device",
DeviceEndpoint: { "interface": 0, "usage": 0x0000, "usage_page": 0x0000, "collection": 0x0000 },
LedNames: [],
LedPositions: [],
Leds: [],
Wireless: false, // True equals to 0x09 and false to 0x08,
pollingInterval: 500,
lastMicStatePolling: 0,
lastMicState: 0,
lastBatteryPolling: 0,
pollingBatteryInterval: 300000, // 5 Minutes
};
this.chargingStates = Object.freeze({
1 : 2,
2 : 1,
3 : 4
});
}
getDeviceProperties(deviceID) { return CORSAIRdeviceLibrary.LEDLibrary[deviceID];};
getDeviceProductId() { return this.Config.DeviceProductID; }
setDeviceProductId(productID) { this.Config.DeviceProductID = productID; }
getDeviceName() { return this.Config.DeviceName; }
setDeviceName(deviceName) { this.Config.DeviceName = deviceName; }
getDeviceEndpoint() { return this.Config.DeviceEndpoint; }
setDeviceEndpoint(deviceEndpoint) { this.Config.DeviceEndpoint = deviceEndpoint; }
getLedNames() { return this.Config.LedNames; }
setLedNames(ledNames) { this.Config.LedNames = ledNames; }
getLedPositions() { return this.Config.LedPositions; }
setLedPositions(ledPositions) { this.Config.LedPositions = ledPositions; }
getLeds() { return this.Config.Leds; }
setLeds(leds) { this.Config.Leds = leds; }
getDeviceImage() { return this.Config.image; }
setDeviceImage(image) { this.Config.image = image; }
getWirelessSupport() { return this.Config.Wireless; }
setWirelessSupport(wireless) { this.Config.Wireless = wireless; }
Initialize() {
//Initializing vars
this.setDeviceProductId(device.productId());
const DeviceProperties = this.getDeviceProperties(this.getDeviceProductId());
this.setDeviceName(DeviceProperties.name);
this.setDeviceEndpoint(DeviceProperties.endpoint);
this.setLedNames(DeviceProperties.LedNames);
this.setLedPositions(DeviceProperties.LedPositions);
this.setLeds(DeviceProperties.Leds);
this.setDeviceImage(DeviceProperties.image);
if(DeviceProperties.wireless){
this.setWirelessSupport(DeviceProperties.wireless);
device.addFeature("battery");
device.addProperty({"property":"idleTimeout", "group":"", "label":"Device Sleep Timeout (Minutes)", "type":"combobox", "values":["Off", "1", "2", "3", "4", "5", "10", "15", "20", "25", "30"], "default":"10"});
}
device.log("Device model found: " + this.getDeviceName());
device.setName("Corsair " + this.getDeviceName());
device.setSize(DeviceProperties.size);
device.setControllableLeds(this.getLedNames(), this.getLedPositions());
device.setImageFromUrl(this.getDeviceImage());
device.set_endpoint(
DeviceProperties.endpoint[`interface`],
DeviceProperties.endpoint[`usage`],
DeviceProperties.endpoint[`usage_page`],
DeviceProperties.endpoint[`collection`]
);
this.modernDirectLightingMode();
this.fetchBattery();
}
modernDirectLightingMode() {
const headsetMode = this.getWirelessSupport() === true ? 0x09 : 0x08;
const endpoint = this.getDeviceEndpoint();
device.set_endpoint(endpoint[`interface`], endpoint[`usage`], endpoint[`usage_page`]);
console.log("Setting Software Mode!");
device.write([0x02, headsetMode, 0x01, 0x03, 0x00, 0x02], 64); // Enable Software Mode
device.write([0x02, headsetMode, 0x0D, 0x00, 0x01], 64); //Open lighting endpoint
device.pause(1000);
const HWBrightnessPacket = [0x02, headsetMode, 0x02, 0x02, 0x00];
device.clearReadBuffer();
device.write(HWBrightnessPacket, 64);
device.pause(100);
const HWBrightness = device.read(HWBrightnessPacket, 64);
if (HWBrightness[4] !== 0xe8 || HWBrightness[5] !== 0x03) {
device.write([0x02, headsetMode, 0x01, 0x02, 0x00, 0xe8, 0x03], 64); //Hardware Brightness 100%
}
}
sendColors(overrideColor) {
const deviceLedPositions = this.getLedPositions();
const deviceLeds = this.getLeds();
const RGBData = [];
for (let iIdx = 0; iIdx < deviceLeds.length; iIdx++) {
const iPxX = deviceLedPositions[iIdx][0];
const iPxY = deviceLedPositions[iIdx][1];
let color;
if(deviceLeds[iIdx] === 2 && micLedMode === "MuteState"){
const micState = this.fetchMicStatus();
if (micState === 1) {
color = hexToRgb(micMuteColor);
} else {
color = device.color(iPxX, iPxY);
}
}else if (overrideColor){
color = hexToRgb(overrideColor);
}else if (LightingMode === "Forced") {
color = hexToRgb(forcedColor);
}else {
color = device.color(iPxX, iPxY);
}
RGBData[(deviceLeds[iIdx])] = color[0];
RGBData[(deviceLeds[iIdx])+3] = color[1];
RGBData[(deviceLeds[iIdx])+6] = color[2];
}
this.writeRGB(RGBData);
}
writeRGB(RGBData) {
const headsetMode = this.getWirelessSupport() === true ? 0x09 : 0x08;
const endpoint = this.getDeviceEndpoint();
device.set_endpoint(endpoint[`interface`], endpoint[`usage`], endpoint[`usage_page`]);
device.write([0x02, headsetMode, 0x06, 0x00, 0x09, 0x00, 0x00, 0x00].concat(RGBData), 64);
}
fetchMicStatus(){
if(Date.now() - this.Config.lastMicStatePolling < this.Config.pollingInterval) {
return this.Config.lastMicState;
}
const headsetMode = this.getWirelessSupport() === true ? 0x09 : 0x08;
const endpoint = this.getDeviceEndpoint();
device.set_endpoint(endpoint[`interface`], endpoint[`usage`], endpoint[`usage_page`]);
const micStatusPacket = [0x02, headsetMode, 0x02, 0x46, 0x00];
device.pause(100);
device.clearReadBuffer();
device.write(micStatusPacket, 64);
device.pause(50);
const micStatus = device.read(micStatusPacket, 64); // Index 5 returns 0 to unmutted/enable and 1 to muted/disabled
this.Config.lastMicStatePolling = Date.now();
if (micStatus[3] === 0x46) {
console.log("Invalid read on mic status.");
return this.Config.lastMicState;
}
this.Config.lastMicState = micStatus[4];
return micStatus[4];
}
setSidetone() {
const headsetMode = this.getWirelessSupport() === true ? 0x09 : 0x08;
const endpoint = this.getDeviceEndpoint();
device.set_endpoint(endpoint[`interface`], endpoint[`usage`], endpoint[`usage_page`]);
const sidetoneValue = Math.round((SidetoneAmount / 100) * 1000);
console.log("Setting Sidetone!");
device.write([0x02, headsetMode, 0x01, 0x47, 0x00, sidetoneValue & 0xFF, (sidetoneValue >> 8) & 0xFF], 64);
}
setIdleTimeout() {
const headsetMode = this.getWirelessSupport() === true ? 0x09 : 0x08;
const endpoint = this.getDeviceEndpoint();
device.set_endpoint(endpoint[`interface`], endpoint[`usage`], endpoint[`usage_page`]);
if (idleTimeout === "Off") {
console.log ("Setting Idle Timeout to: disabled");
device.write([0x02, headsetMode, 0x01, 0x0D], 64);
} else {
device.write([0x02, headsetMode, 0x01, 0x0D, 0x01], 64);
device.pause(1);
device.write([0x02, headsetMode, 0x01, 0x0D, 0x00, 0x01], 64);
device.pause(1);
const timeoutValue = idleTimeout * 60000;
const hexValue = timeoutValue.toString(16).padStart(6, '0');
const littleEndianHex = hexValue.match(/../g).reverse();
const packet = [];
packet[0] = 0x02;
packet[1] = headsetMode;
packet[2] = 0x01;
packet[3] = 0x0e;
packet[5] = parseInt(littleEndianHex[0], 16);
packet[6] = parseInt(littleEndianHex[1], 16);
packet[7] = parseInt(littleEndianHex[2], 16);
console.log ("Setting Idle Timeout to: " + idleTimeout);
device.write(packet, 64);
}
}
fetchBattery(){
if(Date.now() - this.Config.lastBatteryPolling < this.Config.pollingBatteryInterval) {
return;
}
const headsetMode = this.getWirelessSupport() === true ? 0x09 : 0x08;
const endpoint = this.getDeviceEndpoint();
device.set_endpoint(endpoint[`interface`], endpoint[`usage`], endpoint[`usage_page`]);
const batteryLevelPacket = [0x02, headsetMode, 0x02, 0x0F, 0x00];
const batteryStatusPacket = [0x02, headsetMode, 0x02, 0x10, 0x00];
device.pause(1000);
device.clearReadBuffer();
device.write(batteryLevelPacket, 64);
device.pause(50);
device.clearReadBuffer();
device.write(batteryStatusPacket, 64);
const batteryLevelData = device.read(batteryLevelPacket, 64);
const batteryStatusData = device.read(batteryStatusPacket, 64);
const batteryLevel = this.ReadInt32LittleEndian(batteryLevelData.slice(4, 7));
const batteryStatus = batteryStatusData[4];
this.Config.lastBatteryPolling = Date.now();
device.log(`Battery Level is [${(batteryLevel ?? 0)/10}%]`);
device.log(`Battery Status is [${this.chargingStates[batteryStatus ?? 0]}]`);
battery.setBatteryLevel((batteryLevel ?? 0)/ 10);
battery.setBatteryState(this.chargingStates[batteryStatus ?? 0]);
}
ReadInt32LittleEndian(array){
return (array[0] & 0xFF) | ((array[1] << 8) & 0xFF00) | ((array[2] << 16) & 0xFF0000) | ((array[3] << 24) & 0xFF000000);
}
}
export class deviceLibrary {
constructor(){
this.PIDLibrary = {
0x0A62: "Virtuoso XT",
0x0A64: "Virtuoso XT", // Wireless
};
this.LEDLibrary = {
0x0A62: {
name: "Virtuoso XT",
size: [1, 2],
LedNames: ["Logo", "Mic"],
LedPositions: [[0, 0], [0, 1]],
Leds: [0, 2],
endpoint : { "interface": 3, "usage": 0x0001, "usage_page": 0xFF42, "collection": 0x0005 },
image: "https://assets.signalrgb.com/devices/brands/corsair/audio/virtuoso-xt.png"
},
0x0A64: {
name: "Virtuoso XT Wireless",
size: [1, 2],
LedNames: ["Logo", "Mic"],
LedPositions: [[0, 0], [0, 1]],
Leds: [0, 2],
wireless: true,
endpoint : { "interface": 3, "usage": 0x0001, "usage_page": 0xFF42, "collection": 0x0005 },
image: "https://assets.signalrgb.com/devices/brands/corsair/audio/virtuoso-xt.png"
},
};
}
}
const CORSAIRdeviceLibrary = new deviceLibrary();
const CORSAIR = new CORSAIR_Device_Protocol();
function hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
const colors = [];
colors[0] = parseInt(result[1], 16);
colors[1] = parseInt(result[2], 16);
colors[2] = parseInt(result[3], 16);
return colors;
}
export function Validate(endpoint) {
return endpoint.interface === 3;
}
export function ImageUrl() {
return "https://assets.signalrgb.com/devices/default/misc/usb-drive-render.png";
}