signal-rgb-test/Logitech_Wireless_Mouse_Dongle_Auto_Detect.js.dis

690 lines
273 KiB
Plaintext
Raw Normal View History

export function Name() { return "Logitech Wireless Mouse Dongle"; }
export function VendorId() { return 0x046d; }
export function ProductId() { return 0xC539; }
export function Publisher() { return "WhirlwindFX"; }
export function Size() { return [3, 3]; }
export function DefaultPosition(){return [240,120]}
export function DefaultScale(){return 8.0}
export function ControllableParameters(){
return [
{"property":"shutdownColor", "label":"Shutdown Color","min":"0","max":"360","type":"color","default":"009bde"},
{"property":"LightingMode", "label":"Lighting Mode", "type":"combobox", "values":["Canvas","Forced"], "default":"Canvas"},
{"property":"forcedColor", "label":"Forced Color","min":"0","max":"360","type":"color","default":"009bde"},
{"property":"DpiControl", "label":"Enable Dpi Control","type":"boolean","default":"false"},
{"property":"dpi1", "label":"DPI","step":"50", "type":"number","min":"200", "max":"12000","default":"800"},
{"property":"DpiLight", "label":"DPI Light Always On","type":"boolean","default":1},
{"property":"pollingrate", "label":"Polling Rate","type":"combobox", "values":[ "1000","500", "250", "100" ], "default":"1000"},
];
}
const LogitechBatteryVoltageDict =
{
4186: 100,
4156: 99,
4143: 98,
4133: 97,
4122: 96,
4113: 95,
4103: 94,
4094: 93,
4086: 92,
4076: 91,
4067: 90,
4060: 89,
4051: 88,
4043: 87,
4036: 86,
4027: 85,
4019: 84,
4012: 83,
4004: 82,
3997: 81,
3989: 80,
3983: 79,
3976: 78,
3969: 77,
3961: 76,
3955: 75,
3949: 74,
3942: 73,
3935: 72,
3929: 71,
3922: 70,
3916: 69,
3909: 68,
3902: 67,
3896: 66,
3890: 65,
3883: 64,
3877: 63,
3870: 62,
3865: 61,
3859: 60,
3853: 59,
3848: 58,
3842: 57,
3837: 56,
3833: 55,
3828: 54,
3824: 53,
3819: 52,
3815: 51,
3811: 50,
3808: 49,
3804: 48,
3800: 47,
3797: 46,
3793: 45,
3790: 44,
3787: 43,
3784: 42,
3781: 41,
3778: 40,
3775: 39,
3772: 38,
3770: 37,
3767: 36,
3764: 35,
3762: 34,
3759: 33,
3757: 32,
3754: 31,
3751: 30,
3748: 29,
3744: 28,
3741: 27,
3737: 26,
3734: 25,
3730: 24,
3726: 23,
3724: 22,
3720: 21,
3717: 20,
3714: 19,
3710: 18,
3706: 17,
3702: 16,
3697: 15,
3693: 14,
3688: 13,
3683: 12,
3677: 11,
3671: 10,
3666: 9,
3662: 8,
3658: 7,
3654: 6,
3646: 5,
3633: 4,
3612: 3,
3579: 2,
3537: 1,
3500: 0
}
const WIRED = 0xFF;
const WIRELESS = 0x01;
var Hero = false;
var savedDpi1;
var DeviceId;
var TransactionId;
var deviceName;
var RGBFeatureID;
var PollingId;
var Sleep = false;
const deviceIdMap =
{
"405d" : "Logitech G403L",
"407f" : "Logitech G502L",
"4070" : "Logitech G703L",
"4086" : "Logitech G703 Hero",
"4053" : "Logitech G900L",
"4067" : "Logitech G903L",
"4087" : "Logitech G903 Hero",
"4079" : "Logitech GPro Wireless",
}
var vLedNames = ["Primary Zone", "Logo Zone"];
var vLedPositions = [ [0,1],[0,2] ];
export function LedNames()
{
return vLedNames;
}
export function LedPositions()
{
return vLedPositions;
}
export function Initialize()
{
device.flush()
GrabIds();
let data = [0x80, 0x02, 0x02, 0x00]
let value = Logitech_Short_Set(WIRED, data)
DeviceId = value[3].toString(16) + value[2].toString(16)
TransactionId = value[0];
deviceName = deviceIdMap[DeviceId] || "UNKNOWN"
device.log(`Device Id Found: ${DeviceId}`);
device.log(`Device Name: ${deviceName}`);
device.log("Transaction ID Found: " + TransactionId.toString(16));
if(DeviceId == "00")//make hero mice not angry. They won't run without that extra junk and always give 00.
{
let value = Logitech_Short_Set(WIRED)
let data = [0x80, 0x00, 0x00, 0x01]
Logitech_Short_Set(WIRED, data)
data = [0x80, 0x02, 0x00, 0x00]
value = Logitech_Short_Set(WIRED, data)
data = [0x80, 0x02, 0x02, 0x00]
value = Logitech_Short_Set(WIRED, data)
DeviceId = value[3].toString(16) + value[2].toString(16)
TransactionId = value[0];
deviceName = deviceIdMap[DeviceId] || "UNKNOWN"
device.log(`Device Id Found: ${DeviceId}`);
device.log(`Device Name: ${deviceName}`);
}
if(Hero == true)
{
SetHeroDirectMode();
SetHeroDpiLightAlwaysOn(DpiLight);
}
else
{
SetDirectMode();
SetDpiLightAlwaysOn(DpiLight)
}
if(savedDpi1 != dpi1 && DpiControl)
{
setDpi(dpi1);
}
GetBatteryCharge();
}
export function Render()
{
CheckForSleep();
CheckForHandoff();
if(Sleep == false)
{
if(Hero == true)
{
sendHeroZone(0);
sendHeroZone(1);
}
else
{
sendZone(0);
sendZone(1);
}
}
}
export function Shutdown()
{
if(Hero == true)
{
sendHeroZone(0, true);
sendHeroZone(1, true);
}
else
{
sendZone(0, true);
sendZone(1, true);
}
}
export function onDpiLightChanged()
{
if (Hero == true)
{
SetHeroDpiLightAlwaysOn(DpiLight)
}
else
{
SetDpiLightAlwaysOn(DpiLight)
}
}
export function onpollingrateChanged()
{
setPollingRate();
}
function GrabIds()
{
const InfoPage = [0x00,0x03]
var InfoID = Logitech_FeatureID_Get(InfoPage)
if(InfoID != 0)
{
device.log("Device Info ID: " + InfoID);
}
const NamePage = [0x00,0x05]
var NameID = Logitech_FeatureID_Get(NamePage)
if(NameID != 0)
{
device.log("Device Name ID: " + NameID);
}
const ResetPage = [0x00,0x20]
var ResetID = Logitech_FeatureID_Get(ResetPage)
if(ResetID != 0)
{
device.log("Device Reset ID: " + ResetID);
}
const BatteryPage = [0x10,0x01]
var BattID = Logitech_FeatureID_Get(BatteryPage)
if(BattID != 0)
{
device.log("Battery ID: " + BattID);
}
const LEDCtrlPage = [0x13,0x00]
var LEDCtrlID = Logitech_FeatureID_Get(LEDCtrlPage)
if(LEDCtrlID != 0)
{
device.log("Led Control ID: " + LEDCtrlID);
}
const WirelessStatusPage = [0x1D,0x4B]
var WirelessStatusID = Logitech_FeatureID_Get(WirelessStatusPage)
if(WirelessStatusID != 0)
{
device.log("Wireless Status ID: " + WirelessStatusID);
}
const DPIPage = [0x22,0x01]
var DpiID = Logitech_FeatureID_Get(DPIPage)
if(DpiID != 0)
{
device.log("DPI ID: " + DpiID);
}
const PollingRatePage = [0x80,0x60]
var PollingRateID = Logitech_FeatureID_Get(PollingRatePage)
if(PollingRateID != 0)
{
device.log("Polling Rate ID: " + PollingRateID);
}
const OnboardProfilePage = [0x00,0x00,0x81,0x00]
var OnboardID = Logitech_FeatureID_Get(OnboardProfilePage)
if(OnboardID != 0)
{
device.log("Onboard Profiles ID: " + OnboardID);
}
const ButtonSpyPage = [0x81,0x10]
var ButtonSpyID = Logitech_FeatureID_Get(ButtonSpyPage)
if(ButtonSpyID != 0)
{
device.log("Button Spy ID: " + ButtonSpyID);
}
const ReportRatePage = [0x80,0x60]
var ReportRateID = Logitech_FeatureID_Get(ReportRatePage)
if(ReportRateID != 0)
{
device.log("ReportRateID: " + ReportRateID);
}
const PerKeyLightingPage = [0x80,0x80]
var PerKeyLightingID = Logitech_FeatureID_Get(PerKeyLightingPage)
if(PerKeyLightingID != 0)
{
device.log("PerKeyLightingID: " + PerKeyLightingID);
}
const PerKeyLightingV2Page = [0x80,0x81]
var PerKeyLightingV2ID = Logitech_FeatureID_Get(PerKeyLightingV2Page)
if(PerKeyLightingV2ID != 0)
{
device.log("PerKeyLightingV2ID: " + PerKeyLightingID);
}
const RGB8070Page = [0x80,0x70]
RGBFeatureID = Logitech_FeatureID_Get(RGB8070Page)
if(RGBFeatureID == 0)
{
const RGB8071Page = [0x80,0x71]
RGBFeatureID = Logitech_FeatureID_Get(RGB8071Page)
if(RGBFeatureID != 0)
{
Hero = true;
device.log("Hero");
}
}
if(RGBFeatureID != 0)
{
device.log("RGB Control ID : " + RGBFeatureID);
}
}
function clearShortReadBuffer(){
device.set_endpoint(2, 0x0001, 0xff00); // System IF
device.read([0x10,0x01],7)
while(device.getLastReadSize() > 0){
device.read([0x10,0xFF],7)
}
}
function clearLongReadBuffer(){
device.set_endpoint(2, 0x0002, 0xff00); // Lighting IF
device.read([0x11,0x01],20)
while(device.getLastReadSize() > 0){
device.read([0x10,0x01],20)
}
}
function Logitech_Short_Set(Mode, data)
{
device.set_endpoint(2, 0x0001, 0xff00); // System IF
clearShortReadBuffer();
var packet = [0x10,Mode ]
data = data || [ 0x80, 0x00, 0x00, 0x00];
packet = packet.concat(data)
device.write(packet, 7)
packet = device.read(packet,7);
return packet.slice(3,7)
}
function Logitech_Long_Get(Mode, data)
{
device.set_endpoint(2, 0x0002, 0xff00); // Lighting IF
clearLongReadBuffer();
var packet = [0x11,Mode]
data = data || [0x00, 0x00, 0x00];
packet = packet.concat(data)
device.write(packet, 20)
packet = device.read(packet,20);
return packet.slice(4,7)
}
function Logitech_FeatureID_Get(page){
return Logitech_Long_Get(WIRELESS, [0x00,0x00].concat(page))[0]
}
export function GetBatteryCharge(){
let [voltage,state] = LogitechGetBatteryVoltage()
return GetApproximateBatteryPercentage(voltage)
}
export function GetBatteryStatus(){
let [voltage,state] = LogitechGetBatteryVoltage()
return StatusDict[state]
}
function LogitechGetBatteryVoltage()
{
let data = [0x06, 0x00, 0x10]
let BatteryArray = Logitech_Long_Get(0x01, data)
device.log(BatteryArray)
let BatteryVoltage = (BatteryArray[0] << 8) + BatteryArray[1]
let BatteryStatus = BatteryArray[2]
device.log("Battery Voltage: " + BatteryVoltage);
device.log("Battery Status: " + StatusDict[BatteryStatus]);
return [BatteryVoltage, BatteryStatus];
}
const StatusDict =
{
0 : "Discharging", //Discharging
128 : "Charging", //Charging
144 : "Wireless Charging" //Wireless Charging
}
const VoltageArray = [ 4186,4156,4143,4133,4122,4113,4103,4094,4086,4076,4067,4060,4051,4043,4036,4027,4019,4012,4004,3997,3989,3983,3976,3969,3961,3955,3949,3942,3935,3929,3922,3916,3909,3902,3896,3890,3883,3877,3870,3865,3859,3853,3848,3842,3837,3833,3828,3824,3819,3815,3811,3808,3804,3800,3797,3793,3790,3787,3784,3781,3778,3775,3772,3770,3767,3764,3762,3759,3757,3754,3751,3748,3744,3741,3737,3734,3730,3726,3724,3720,3717,3714,3710,3706,3702,3697,3693,3688,3683,3677,3671,3666,3662,3658,3654,3646,3633,3612,3579,3537,3500 ];
function GetApproximateBatteryPercentage(BatteryVoltage){
const nearestVoltageBand = VoltageArray.reduce((prev, curr) => {
return (Math.abs(curr - BatteryVoltage) < Math.abs(prev - BatteryVoltage) ? curr : prev);
});
device.log("Battery Percentage Remaining: " + LogitechBatteryVoltageDict[nearestVoltageBand]);
return LogitechBatteryVoltageDict[nearestVoltageBand]
}
function setDpi(dpi)
{
device.set_endpoint(2, 0x0001, 0xff00); // System IF
savedDpi1 = dpi1;
var packet = [];
packet[0] = 0x10;
packet[1] = WIRELESS;
packet[2] = DPIID;
packet[3] = 0x30;
packet[4] = 0x00;//My money is on this being zone
packet[5] = Math.floor(dpi/256);
packet[6] = dpi%256;
device.write(packet, 7);
device.pause(1); //This is here because I was having issues with the device applying the dpi consistenly
}
function setPollingRate()
{
device.set_endpoint(2, 0x0001, 0xff00); // System IF
var packet = [];
packet[0] = 0x10;
packet[1] = WIRELESS;
packet[2] = PollingRateID;
packet[3] = 0x2E;
packet[4] = 1000/pollingrate;
device.write(packet, 7);
device.pause(1);
}
function SetDpiLightAlwaysOn(DpiLight){//Fun fact: this is making use of the led control Feature ID
device.set_endpoint(2, 0x0001, 0xff00); // System IF
var packet = [0x10, 0x01, 0x08, 0x07E, 0x01, (DpiLight ? 0x02 : 0x04) ,0x00]//0x07 is unknown
device.write(packet, 7);
device.set_endpoint(2, 0x0002, 0xff00); // Lighting IF
packet = [0x11, 0x01, 0x08, 0x05E, 0x01,0x00,0x02, 0x00, 0x02]//0x50 is set led state
device.write(packet, 20);
device.set_endpoint(2, 0x0001, 0xff00); // System IF //0x60 should be grabbing config from nvram
packet = [0x10, 0x01, 0x08, 0x06E, 0x01]
device.write(packet, 7);
}
function SetHeroDpiLightAlwaysOn(DpiLight)
{
device.set_endpoint(2, 0x0002, 0xff00);
var packet = [0x11, 0x01, 0x07, 0x03B, 0x01, 0x00 ,0x08, (DpiLight ? 0x04 : 0x02), 0x07]
device.write(packet, 20)
device.set_endpoint(2, 0x0001, 0xff00);
packet = [0x10, 0x01, 0x07, 0x02B, 0x00, 0x03, 0x00 ]
device.write(packet, 7)
device.set_endpoint(2, 0x0001, 0xff00);
packet = [0x10, 0x01, 0x07, 0x03B, 0x00, 0x00, 0x08]
device.write(packet, 7)
}
function sendZone(zone, shutdown = false)
{
device.set_endpoint(2, 0x0002, 0xff00);
var packet = [];
packet[0x00] = 0x11;
packet[0x01] = WIRELESS;
packet[0x02] = RGBFeatureID;
packet[0x03] = 0x3B;
packet[0x04] = zone;
packet[0x05] = 0x01;
var iX = vLedPositions[zone][0];
var iY = vLedPositions[zone][1];
var color;
if(shutdown)
{
color = hexToRgb(shutdownColor);
}else if (LightingMode == "Forced")
{
color = hexToRgb(forcedColor);
}else
{
color = device.color(iX, iY);
}
packet[0x06] = color[0];
packet[0x07] = color[1];
packet[0x08] = color[2];
packet[0x09] = 0x02;
if(DeviceId == "4067" || DeviceId == "4070" || DeviceId == "4087")
{
packet[16] = 0x01;
}
device.write(packet, 20);
device.read(packet, 20)
if(DeviceId == "4079" || DeviceId == "4087" || DeviceId == "405d")
{
Apply();
}
}
function sendHeroZone(zone, shutdown = false)
{
device.set_endpoint(2, 0x0002, 0xff00); // Lighting IF
var packet = [];
packet[0x00] = 0x11;
packet[0x01] = WIRELESS;
packet[0x02] = RGBFeatureID;
packet[0x03] = 0x1A;
packet[0x04] = zone;
packet[0x05] = 0x01;
var iX = vLedPositions[zone][0];
var iY = vLedPositions[zone][1];
var color;
if(shutdown)
{
color = hexToRgb(shutdownColor);
}else if (LightingMode == "Forced")
{
color = hexToRgb(forcedColor);
}else
{
color = device.color(iX, iY);
}
packet[0x06] = color[0];
packet[0x07] = color[1];
packet[0x08] = color[2];
packet[0x10] = 0x02;
packet[16] = 0x01;
device.write(packet, 20);
device.read(packet, 20)
}
function Apply()
{
device.set_endpoint(2, 0x0001, 0xff00); // System IF
var packet = [];
packet[0] = 0x10;
packet[1] = WIRELESS;
packet[2] = 0x0B;
packet[3] = 0x2F;
packet[4] = 0x01;
device.write(packet, 7);
device.read(packet, 7)
}
function CheckForSleep()
{
device.set_endpoint(2, 0x0001, 0xff00);
var packet = [];
packet = device.readTimeout([0x00],64, 5);
if(packet[0] == 0x10 && packet[1] == 0x01 && packet[2] == 0x41 && packet[3] == 0x0C && packet[4] == 0x62 && packet[6] == 0x40)
{
device.log("Mouse Going to Sleep");
return Sleep = true;
}
}
function CheckForHandoff()
{
device.set_endpoint(2, 0x0002, 0xff00);
var packet = [];
packet = device.readTimeout([0x00],64, 5);
if(packet[0] == 0x11 && packet[1] == 0x01 && packet[2] == 0x06 && packet[3] == 0x00 && packet[6] == 0x00)
{
device.log("Waking From Sleep")
device.pause(5000); //Wait five seconds before Handoff. Allows device boot time.
Initialize();
return Sleep = false;
}
}
function SetDirectMode()
{
device.set_endpoint(2, 0x0001, 0xff00);
let packet = [0x10, 0x01, RGBFeatureID, 0x80, 0x01, 0x01]//0x80 in this case sets a register
device.write(packet, 7)
device.read(packet, 7)
}
function SetHeroDirectMode()
{
device.set_endpoint(2, 0x0001, 0xff00);
let packet = [0x10, 0x01, 0x07, 0x5A, 0x01, 0x03, 0x05]
device.write(packet, 7)
device.read(packet, 7)
}
export function Validate(endpoint)
{
return endpoint.interface === 2 && endpoint.usage === 0x0002 && endpoint.usage_page === 0xff00
|| endpoint.interface === 2 && endpoint.usage === 0x0001 && endpoint.usage_page === 0xff00;
}
function hexToRgb(hex)
{
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
var colors = [];
colors[0] = parseInt(result[1], 16);
colors[1] = parseInt(result[2], 16);
colors[2] = parseInt(result[3], 16);
return colors;
}
export function Image()
{
return "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