signal-rgb-test/Logitech_Powerplay_Dongle.js.dis

770 lines
246 KiB
Plaintext
Raw Normal View History

export function Name() { return "Logitech Powerplay Dongle"; }
export function VendorId() { return 0x046d; }
export function ProductId() { return 0xC53A; }
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":"25600","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 PollingRateID;
var DpiID;
var BattID;
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",
}
const Powerplay_Mat = {
mapping : [
0
],
positioning : [
[0,0]
],
displayName: "PowerPlay MousePad",
ledCount : 1,
width: 3,
height: 3,
image: Image()
}
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();
//Create Mouse Pad Sub Device
device.createSubdevice("PowerPlayMat");
device.setSubdeviceName("PowerPlayMat",`${Powerplay_Mat.displayName}`);
device.setSubdeviceImage("PowerPlayMat", Powerplay_Mat.image);
device.setSubdeviceSize("PowerPlayMat",Powerplay_Mat.width,Powerplay_Mat.height);
}
export function ondpi1Changed()
{
setDpi(dpi1);
}
var savedPollTimer = Date.now();
var PollModeInternal = 15000;
function PollBattery()
{
if (Date.now() - savedPollTimer < PollModeInternal)
{
return
}
savedPollTimer = Date.now();
GetBatteryCharge();
}
export function Render()
{
CheckForSleep();
CheckForHandoff();
if(Sleep == false)
{
if(Hero == true)
{
sendHeroZone(0);
sendHeroZone(1);
}
else
{
sendZone(0);
sendZone(1);
}
}
sendMousePad();
PollBattery();
}
export function Shutdown()
{
if(Hero == true)
{
sendHeroZone(0, true);
sendHeroZone(1, true);
}
else
{
sendZone(0, true);
sendZone(1, true);
}
sendMousePad(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]
BattID = Logitech_FeatureID_Get(BatteryPage)
if(BattID != 0)
{
device.log("Battery ID: " + BattID);
}
const UnifiedBatteryPage = [0x10,0x04]
var UnifiedBattID = Logitech_FeatureID_Get(UnifiedBatteryPage)
if(UnifiedBattID != 0)
{
device.log("Unified Battery ID: " + UnifiedBattID);
}
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]
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(BattID)
return GetApproximateBatteryPercentage(voltage)
}
export function GetBatteryStatus(){
let [voltage,state] = LogitechGetBatteryVoltage()
return StatusDict[state]
}
function LogitechGetBatteryVoltage()
{
let data = [BattID, 0x00, 0x1A]
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] = 0x20;
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 sendMousePad(shutdown = false){
device.set_endpoint(2, 0x0002, 0xff00); // Lighting IF
var packet = [];
packet[0x00] = 0x11;
packet[0x01] = 0x07;
packet[0x02] = 0x0b;
packet[0x03] = 0x3E;
packet[0x04] = 0x00;
packet[0x05] = 0x01;
var iX = Powerplay_Mat.positioning[0][0];
var iY = Powerplay_Mat.positioning[0][1];
var color;
if(shutdown){
color = hexToRgb(shutdownColor)
}else if (LightingMode == "Forced") {
color = hexToRgb(forcedColor)
}else{
color = device.subdeviceColor("PowerPlayMat",iX, iY);
}
packet[0x06] = color[0];
packet[0x07] = color[1];
packet[0x08] = color[2];
device.write(packet, 20);
device.pause(1);
}
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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