From 79bf864f4a606a7aa87ef6b7c91af09cf99dfe6a Mon Sep 17 00:00:00 2001 From: Be Date: Fri, 22 Jun 2018 09:43:16 -0500 Subject: send report IDs of HID output packets Before, HID controller mappings were improperly relying on an implementation detail of hidapi by putting the report ID as the first byte of the packet and not specifying a report ID. hidapi requires a report ID specified as the first byte sent to hid_write. For devices that only have a single output report type, hid_write expects a 0 as the first byte. So, Mixxx prepends a 0 when the script does not specify a report ID. Windows requires the leading 0 byte for devices that only have a single output report type, so hidapi sends the data from Mixxx to Windows. However, on Linux, libusb does not require prepending a report ID of 0 when the device only uses one output report type, so hidapi strips the leading 0 byte before sending the data to libusb. Thus, when Mixxx prepended a 0 byte because the scripts did not specify a report ID, it happened to work on Linux but did not work on Windows. --- res/controllers/EKS-Otus.js | 28 +++++++++--------- res/controllers/Nintendo-Wiimote.js | 30 +++++++++---------- res/controllers/Pioneer-CDJ-HID.js | 10 +++---- res/controllers/Traktor-Kontrol-F1-scripts.js | 4 +-- .../Traktor-Kontrol-S4-MK2-hid-scripts.js | 34 ++++++++++------------ res/controllers/common-hid-devices.js | 4 +-- res/controllers/common-hid-packet-parser.js | 34 +++++++++++++--------- 7 files changed, 74 insertions(+), 70 deletions(-) (limited to 'res') diff --git a/res/controllers/EKS-Otus.js b/res/controllers/EKS-Otus.js index f8a523c143..b3842415e0 100644 --- a/res/controllers/EKS-Otus.js +++ b/res/controllers/EKS-Otus.js @@ -27,7 +27,7 @@ function EKSOtusController() { var name = undefined; var offset = 0; - packet = new HIDPacket("control",[0x0,0x35],64); + packet = new HIDPacket("control", 0, undefined, [0x35]); packet.addControl("hid","wheel_position",2,"H"); packet.addControl("hid","wheel_speed",4,"h"); packet.addControl("hid","timestamp",6,"I"); @@ -87,12 +87,12 @@ function EKSOtusController() { packet.addControl("hid","deck_status",52,"B"); this.controller.registerInputPacket(packet); - packet = new HIDPacket("firmware_version",[0xa,0x4],64); + packet = new HIDPacket("firmware_version", 0xa, undefined, [0x4]); packet.addControl("hid","major",2,"B"); packet.addControl("hid","minor",3,"B"); this.controller.registerInputPacket(packet); - packet = new HIDPacket("trackpad_mode",[0x5,0x3],64); + packet = new HIDPacket("trackpad_mode", 0x5, undefined, [0x3]); packet.addControl("hid","status",2,"B"); this.controller.registerInputPacket(packet); @@ -103,8 +103,8 @@ function EKSOtusController() { var name = undefined; var offset = 0; - packet = new HIDPacket("button_leds",[0x16,0x18],32); - offset = 2; + packet = new HIDPacket("button_leds", 0x16, undefined, [0x18]); + offset = 1; packet.addOutput("hid","jog_nw",offset++,"B"); packet.addOutput("hid","jog_ne",offset++,"B"); packet.addOutput("hid","jog_se",offset++,"B"); @@ -129,8 +129,8 @@ function EKSOtusController() { packet.addOutput("hid","fastforward",offset++,"B"); this.controller.registerOutputPacket(packet); - packet = new HIDPacket("slider_leds",[0x17,0x16],32); - offset = 2; + packet = new HIDPacket("slider_leds", 0x17, undefined, [0x16]); + offset = 1; packet.addOutput("pitch","slider_1",offset++,"B"); packet.addOutput("pitch","slider_2",offset++,"B"); packet.addOutput("pitch","slider_3",offset++,"B"); @@ -153,26 +153,26 @@ function EKSOtusController() { packet.addOutput("pitch","slider_scale_3",offset++,"B"); this.controller.registerOutputPacket(packet); - packet = new HIDPacket("led_wheel_left",[0x14,0x20],32); - offset = 2; + packet = new HIDPacket("led_wheel_left", 0x14, undefined, [0x20]); + offset = 1; for (var led_index=1;led_index<=this.wheelLEDCount/2;led_index++) packet.addOutput("hid","wheel_" + led_index,offset++,"B"); this.controller.registerOutputPacket(packet); - packet = new HIDPacket("led_wheel_right",[0x15,0x20],32); - offset = 2; + packet = new HIDPacket("led_wheel_right", 0x15, undefined, [0x20]); + offset = 1; for (var led_index=this.wheelLEDCount/2+1;led_index<=this.wheelLEDCount;led_index++) packet.addOutput("hid","wheel_" + led_index,offset++,"B"); this.controller.registerOutputPacket(packet); - packet = new HIDPacket("request_firmware_version",[0xa,0x2],32); + packet = new HIDPacket("request_firmware_version", 0xa, undefined, [0x2]); this.controller.registerOutputPacket(packet); - packet = new HIDPacket("set_trackpad_mode",[0x5,0x3],32); + packet = new HIDPacket("set_trackpad_mode", 0x5, undefined, [0x3]); packet.addControl("hid","mode",2,"B"); this.controller.registerOutputPacket(packet); - packet = new HIDPacket("set_ledcontrol_mode",[0x1d,0x3],32); + packet = new HIDPacket("set_ledcontrol_mode", 0x1d, undefined, [0x3]); packet.addControl("hid","mode",2,"B"); this.controller.registerOutputPacket(packet); } diff --git a/res/controllers/Nintendo-Wiimote.js b/res/controllers/Nintendo-Wiimote.js index 19635a76f2..7613e650de 100644 --- a/res/controllers/Nintendo-Wiimote.js +++ b/res/controllers/Nintendo-Wiimote.js @@ -30,7 +30,7 @@ function WiimoteController() { this.controller.defaultPacket = "coreaccel"; // Core buttons input packet - packet = new HIDPacket("buttons",[0x30],3); + packet = new HIDPacket("buttons", 0x30); packet.addControl("buttons","arrow_left",1,"B",0x1); packet.addControl("buttons","arrow_right",1,"B",0x2); packet.addControl("buttons","arrow_down",1,"B",0x4); @@ -45,7 +45,7 @@ function WiimoteController() { this.controller.registerInputPacket(packet); // Core buttons and accelerometer data - packet = new HIDPacket("coreaccel",[0x31],6); + packet = new HIDPacket("coreaccel", 0x31); packet.addControl("coreaccel","arrow_left",1,"B",0x1); packet.addControl("coreaccel","arrow_right",1,"B",0x2); packet.addControl("coreaccel","arrow_down",1,"B",0x4); @@ -64,7 +64,7 @@ function WiimoteController() { // Core buttons and accelerometer data with 8 bytes // from extension module - packet = new HIDPacket("coreaccel_ext8",[0x32],14); + packet = new HIDPacket("coreaccel_ext8", 0x32); packet.addControl("coreaccel_ext8","arrow_left",1,"B",0x1); packet.addControl("coreaccel_ext8","arrow_right",1,"B",0x2); packet.addControl("coreaccel_ext8","arrow_down",1,"B",0x4); @@ -91,7 +91,7 @@ function WiimoteController() { // Core buttons and accelerometer data with 12 bytes // from IR camera - packet = new HIDPacket("coreaccel_ir12",[0x33],18); + packet = new HIDPacket("coreaccel_ir12", 0x33); packet.addControl("coreaccel_ir12","arrow_left",1,"B",0x1); packet.addControl("coreaccel_ir12","arrow_right",1,"B",0x2); packet.addControl("coreaccel_ir12","arrow_down",1,"B",0x4); @@ -122,7 +122,7 @@ function WiimoteController() { // Core buttons and 19 bytes from extension module, // no accelerometer data - packet = new HIDPacket("corebuttons_ext19",[0x34],22); + packet = new HIDPacket("corebuttons_ext19", 0x34); packet.addControl("corebuttons_ext19","arrow_left",1,"B",0x1); packet.addControl("corebuttons_ext19","arrow_right",1,"B",0x2); packet.addControl("corebuttons_ext19","arrow_down",1,"B",0x4); @@ -157,7 +157,7 @@ function WiimoteController() { // Core buttons, accelerometer and 16 bytes from // extension module - packet = new HIDPacket("coreaccel_ext16",[0x35],22); + packet = new HIDPacket("coreaccel_ext16", 0x35); packet.addControl("coreaccel_ext16","arrow_left",1,"B",0x1); packet.addControl("coreaccel_ext16","arrow_right",1,"B",0x2); packet.addControl("coreaccel_ext16","arrow_down",1,"B",0x4); @@ -192,7 +192,7 @@ function WiimoteController() { // Core buttons, no accelerometer and 10 IR bytes and // 9 bytes from extension module - packet = new HIDPacket("corebuttons_ir10_ext9",[0x36],22); + packet = new HIDPacket("corebuttons_ir10_ext9", 0x36); packet.addControl("corebuttons_ir10_ext9","arrow_left",1,"B",0x1); packet.addControl("corebuttons_ir10_ext9","arrow_right",1,"B",0x2); packet.addControl("corebuttons_ir10_ext9","arrow_down",1,"B",0x4); @@ -227,7 +227,7 @@ function WiimoteController() { // Core buttons, accelerometer and 10 IR bytes and // 6 bytes from extension module - packet = new HIDPacket("coreaccel_ir10_ext6",[0x37],22); + packet = new HIDPacket("coreaccel_ir10_ext6", 0x37); packet.addControl("coreaccel_ir10_ext6","arrow_left",1,"B",0x1); packet.addControl("coreaccel_ir10_ext6","arrow_right",1,"B",0x2); packet.addControl("coreaccel_ir10_ext6","arrow_down",1,"B",0x4); @@ -262,7 +262,7 @@ function WiimoteController() { // No core buttons, no accelerometer, 21 bytes from // extension module - packet = new HIDPacket("ext_21",[0x3d],22); + packet = new HIDPacket("ext_21", 0x3d); packet.addControl("ext_21","extension_1",1,"B"); packet.addControl("ext_21","extension_2",2,"B"); packet.addControl("ext_21","extension_3",3,"B"); @@ -288,7 +288,7 @@ function WiimoteController() { // Interleaved packet 1: core buttons, accelerometer, // first 16 bytes from IR camera - packet = new HIDPacket("coreaccel_interleaved_1",[0x3e],22); + packet = new HIDPacket("coreaccel_interleaved_1", 0x3e); packet.addControl("coreaccel_interleaved_1","arrow_left",1,"B",0x1); packet.addControl("coreaccel_interleaved_1","arrow_right",1,"B",0x2); packet.addControl("coreaccel_interleaved_1","arrow_down",1,"B",0x4); @@ -323,7 +323,7 @@ function WiimoteController() { // Interleaved packet 2: core buttons, accelerometer, // last 16 bytes from IR camera - packet = new HIDPacket("coreaccel_interleaved_2",[0x3f],22); + packet = new HIDPacket("coreaccel_interleaved_2", 0x3f); packet.addControl("coreaccel_interleaved_2","arrow_left",1,"B",0x1); packet.addControl("coreaccel_interleaved_2","arrow_right",1,"B",0x2); packet.addControl("coreaccel_interleaved_2","arrow_down",1,"B",0x4); @@ -358,7 +358,7 @@ function WiimoteController() { } this.registerOutputPackets = function() { - packet = new HIDPacket("feedback",[0x11],2); + packet = new HIDPacket("feedback", 0x11); packet.addControl("state","rumble",1,"B",0x1); packet.addControl("state","led_1",1,"B",0x10); packet.addControl("state","led_2",1,"B",0x20); @@ -366,16 +366,16 @@ function WiimoteController() { packet.addControl("state","led_4",1,"B",0x80); this.controller.registerOutputPacket(packet); - packet = new HIDPacket("setreportmode",[0x12],3); + packet = new HIDPacket("setreportmode", 0x12); packet.addControl("reportmode","continuous",1,"B",0x4); packet.addControl("reportmode","code",2,"B"); this.controller.registerOutputPacket(packet); - packet = new HIDPacket("ircamera",[0x13],3); + packet = new HIDPacket("ircamera", 0x13); packet.addControl("ircontrol","enabled",1,"B",0x4); this.controller.registerOutputPacket(packet); - packet = new HIDPacket("ircamerastate",[0x1a],3); + packet = new HIDPacket("ircamerastate", 0x1a); packet.addControl("irstate","enabled",1,"B",0x4); this.controller.registerOutputPacket(packet); } diff --git a/res/controllers/Pioneer-CDJ-HID.js b/res/controllers/Pioneer-CDJ-HID.js index 39f54079e8..602272aabe 100644 --- a/res/controllers/Pioneer-CDJ-HID.js +++ b/res/controllers/Pioneer-CDJ-HID.js @@ -21,7 +21,7 @@ function PioneerCDJController() { var name = undefined; var offset = 0; - packet = new HIDPacket("control",[],20); + packet = new HIDPacket("control", 0); packet.addControl("hid","eject",0,"B",0x1); packet.addControl("hid","previous_track",0,"B",0x4); packet.addControl("hid","next_track",0,"B",0x8); @@ -79,7 +79,7 @@ function PioneerCDJController() { // TODO - Sean: this is just example, fill in correct packet // size, header bytes control field offesets but bits to make // it work. - packet = new HIDPacket("lights", [0x1],36); + packet = new HIDPacket("lights", 0x1); packet.addOutput("hid","screen_acue",4,"B",0x1); packet.addOutput("hid","remain",4,"B",0x2); packet.addOutput("hid","screen_flag_1",4,"B",0x4); @@ -142,7 +142,7 @@ function PioneerCDJController() { // TODO - Sean: This is arbitrary example packet, fix the // bytes to get it working. Need to add a response packet // to input packets as well, if we receive acknowledgement - packet = new HIDPacket("request_hid_mode",[0x1],0x20); + packet = new HIDPacket("request_hid_mode", 0x1); packet.addControl("hid","mode",0,"B",1); this.controller.registerOutputPacket(packet); @@ -155,7 +155,7 @@ function PioneerCDJController() { var chars = 1; var offset = 2; // Register 2 bytes for each letter, I expect UTF-8 output - packet = new HIDPacket("display",[0x2,0x2],2+textlines*chars*2); + packet = new HIDPacket("display", 0x2, undefined, [0x2]); for (var i=0;ithis.length) { - HIDDebug("Invalid offset+pack range " + - offset + "-" + end_offset + - " for " + this.length + " byte packet" - ); - return undefined; - } var group = undefined; var field = undefined; for (var group_name in this.groups) { @@ -670,7 +670,7 @@ HIDPacket.prototype.send = function() { // packet_string += packet.data[d].toString(16) + " "; //} //HIDDebug("packet: " + packet_string); - controller.send(packet.data, packet.length, 0); + controller.send(packet.data, packet.data.length, packet.reportId); } // @@ -1023,10 +1023,16 @@ HIDController.prototype.parsePacket = function(data,length) { } for (var name in this.InputPackets) { packet = this.InputPackets[name]; - if (packet.length!=length) { + + // When the device uses multiple report types with report IDs, hidapi + // prepends the report ID to the data sent to Mixxx. If the device + // only has a single report type, the HIDPacket constructor sets the + // reportId as 0. In this case, hidapi only sends the data of the + // report to Mixxx without a report ID. + if (packet.reportId !== 0 && packet.reportId !== data[0]) { continue; } - // Check for packet header match against data + for (var header_byte=0;header_byte Date: Thu, 20 Dec 2018 19:20:37 +0000 Subject: Fix input when header is undefined --- res/controllers/Traktor-Kontrol-F1-scripts.js | 8 ++------ res/controllers/common-hid-packet-parser.js | 20 ++++++++++++-------- 2 files changed, 14 insertions(+), 14 deletions(-) (limited to 'res') diff --git a/res/controllers/Traktor-Kontrol-F1-scripts.js b/res/controllers/Traktor-Kontrol-F1-scripts.js index 885e017896..66660971bb 100644 --- a/res/controllers/Traktor-Kontrol-F1-scripts.js +++ b/res/controllers/Traktor-Kontrol-F1-scripts.js @@ -25,9 +25,7 @@ function KontrolF1Controller() { ]; this.registerInputPackets = function() { - var packet = undefined; - - packet = new HIDPacket("control", 0x1); + var packet = new HIDPacket("control", 0x1); packet.addControl("hid", "grid_8", 1,"I", 0x1); packet.addControl("hid", "grid_7", 1,"I", 0x2); packet.addControl("hid", "grid_6", 1,"I", 0x4); @@ -75,9 +73,7 @@ function KontrolF1Controller() { } this.registerOutputPackets = function() { - var packet = undefined; - - packet = new HIDPacket("lights", 0x80); + var packet = new HIDPacket("lights", 0x80); // Right 7-segment element - 0x0 off, 0x40 on packet.addControl("hid", "right_segment_dp", 1,"B"); packet.addControl("hid", "right_segment_1", 2,"B"); diff --git a/res/controllers/common-hid-packet-parser.js b/res/controllers/common-hid-packet-parser.js index 00bd1e9a3c..b2fc3f2351 100644 --- a/res/controllers/common-hid-packet-parser.js +++ b/res/controllers/common-hid-packet-parser.js @@ -645,8 +645,10 @@ HIDPacket.prototype.send = function() { var i; var packet = new Packet(this.length); - for (header_byte=0;header_byte Date: Thu, 20 Dec 2018 21:36:14 +0100 Subject: Fix HID output on Windows --- res/controllers/Traktor-Kontrol-F1-scripts.js | 45 ++++++++-------------- res/controllers/common-controller-scripts.js | 54 ++++++++++++++++++++------- res/controllers/common-hid-packet-parser.js | 2 +- 3 files changed, 58 insertions(+), 43 deletions(-) (limited to 'res') diff --git a/res/controllers/Traktor-Kontrol-F1-scripts.js b/res/controllers/Traktor-Kontrol-F1-scripts.js index 66660971bb..4bc32f1518 100644 --- a/res/controllers/Traktor-Kontrol-F1-scripts.js +++ b/res/controllers/Traktor-Kontrol-F1-scripts.js @@ -169,7 +169,7 @@ function KontrolF1Controller() { } this.initializeHIDController = function() { - this.scalers = new Object(); + this.scalers = {}; this.scalers["volume"] = function(value) { return script.absoluteLin(value, 0, 1, 0, 4096); } @@ -216,10 +216,6 @@ function KontrolF1Controller() { field.value = value; } - this.segments = function(number) { - - } - this.set7SegmentNumber = function(number) { var controller = this.controller; var packet = controller.getOutputPacket("lights"); @@ -326,27 +322,19 @@ KontrolF1.init = function (id) { KontrolF1.initializeHIDController(); var controller = KontrolF1.controller; - - KontrolF1.knobs = new Object(); - KontrolF1.faders = new Object(); - KontrolF1.grids = new Object(); - KontrolF1.playbuttons = new Object(); - KontrolF1.segments = new Object(); - controller.postProcessDelta = KontrolF1.ButtonLEDPressUpdate; + KontrolF1.knobs = {}; + KontrolF1.faders = {}; + KontrolF1.grids = {}; + KontrolF1.playbuttons = {}; + KontrolF1.segments = {}; + KontrolF1.registerCallbacks(); KontrolF1.resetLEDs(); KontrolF1.setControlMode(KontrolF1.defaultControlMode); - // Timers can't be defined in prototype with this. - if (KontrolF1.LEDUpdateInterval!=undefined) { - KontrolF1.LEDTimer = engine.beginTimer( - KontrolF1.LEDUpdateInterval, - "KontrolF1.controller.updateLEDs(true)" - ); - } KontrolF1.segments['empty'] = [0,0,0,0,0,0,0]; KontrolF1.segments[0] = [0x0,0x40,0x40,0x40,0x40,0x40,0x40]; KontrolF1.segments[1] = [0x0,0x40,0x40,0x0,0x0,0x0,0x0]; @@ -359,8 +347,8 @@ KontrolF1.init = function (id) { KontrolF1.segments[8] = [0x40,0x40,0x40,0x40,0x40,0x40,0x40]; KontrolF1.segments[9] = [0x40,0x40,0x40,0x40,0x40,0x0,0x40]; - KontrolF1.testUpdateInterval = 5; KontrolF1.testSegment = 0; + KontrolF1.testUpdateInterval = 20; KontrolF1.testTimer = engine.beginTimer( KontrolF1.testUpdateInterval, "KontrolF1.testSegments()" @@ -537,8 +525,8 @@ KontrolF1.setLED = function(value,group,key) { KontrolF1.linkKnob = function(mode,knob,group,name,scaler) { if (!(mode in KontrolF1.knobs)) - KontrolF1.knobs[mode] = new Object(); - var mapping = new Object(); + KontrolF1.knobs[mode] = {}; + var mapping = {}; mapping.mode = mode; mapping.knob = knob; mapping.group = group; @@ -564,8 +552,8 @@ KontrolF1.knob = function(field) { KontrolF1.linkFader = function(mode,fader,group,name,scaler,callback) { if (!(mode in KontrolF1.faders)) - KontrolF1.faders[mode] = new Object(); - var mapping = new Object(); + KontrolF1.faders[mode] = {}; + var mapping = {}; mapping.mode = mode; mapping.fader = fader; mapping.group = group; @@ -592,7 +580,7 @@ KontrolF1.fader = function(field) { KontrolF1.linkGrid = function(mode,button,group,name,toggle,callback,ledcolor,ledname) { if (!(mode in KontrolF1.grids)) - KontrolF1.grids[mode] = new Object(); + KontrolF1.grids[mode] = {}; if (ledname==undefined) { if (name.match(/hotcue_[0-9]/)) ledname = name + '_enabled'; @@ -602,7 +590,7 @@ KontrolF1.linkGrid = function(mode,button,group,name,toggle,callback,ledcolor,le if (ledcolor==undefined) { ledcolor = [0x7f,0x7f,0x7f]; } - var mapping = new Object(); + var mapping = {}; mapping.mode = mode; mapping.button = button; mapping.group = group; @@ -630,7 +618,7 @@ KontrolF1.grid = function(field) { KontrolF1.linkPlay = function(mode,button,group,name,toggle,callback,ledname) { if (!(mode in KontrolF1.playbuttons)) - KontrolF1.playbuttons[mode] = new Object(); + KontrolF1.playbuttons[mode] = {}; if (ledname==undefined) { if (name.match(/hotcue_[0-9]/)) @@ -638,7 +626,7 @@ KontrolF1.linkPlay = function(mode,button,group,name,toggle,callback,ledname) { else ledname = name; } - var mapping = new Object(); + var mapping = {}; mapping.mode = mode; mapping.button = button; mapping.group = group; @@ -696,7 +684,6 @@ KontrolF1.switchControlMode = function(field) { KontrolF1.setControlMode("decks"); } else { HIDDebug("Unconfigured mode selector button: " + field.name); - return; } } diff --git a/res/controllers/common-controller-scripts.js b/res/controllers/common-controller-scripts.js index 687f422b14..13f54b3ea0 100644 --- a/res/controllers/common-controller-scripts.js +++ b/res/controllers/common-controller-scripts.js @@ -1,7 +1,5 @@ // Functions common to all controllers go in this file -nop = function () {} // Only here so you don't get a syntax error on load if the file was otherwise empty - // ----------------- Prototype enhancements --------------------- // Returns an ASCII byte array for the string @@ -16,18 +14,50 @@ String.prototype.toInt = function() { // ----------------- Function overloads --------------------- // Causes script print() calls to appear in the log file as well -print = function(string) { +function print(string) { engine.log(string); } -var printObject = function (object) { - print(JSON.stringify(object, null, 2)); -}; +function printObject(obj, maxdepth) { + print(stringifyObject(obj, maxdepth)); +} + +function stringifyObject(obj, maxdepth, checked, prefix) { + if (!maxdepth) + maxdepth = 2; + try { + return JSON.stringify(obj, null, maxdepth); + } catch(e) { + if (!checked) + checked = []; + if (!prefix) + prefix = ""; + if (maxdepth > 0 && typeof obj === 'object' && obj !== null && Object.getPrototypeOf(obj) !== '' && !arrayContains(checked, obj)) { + checked.push(obj); + var output = '{\n'; + for (var property in obj) { + const value = obj[property]; + if(typeof value === 'function') + continue; + output += prefix + property + ': ' + stringifyObject(value, maxdepth - 1, checked, prefix + ' ') + '\n'; + } + return output + prefix.substr(2) + '}'; + } + } + return obj; +} + +function arrayContains(array, elem) { + for (var i = 0; i < array.length; i++) { + if (array[i] === elem) + return true; + } + return false; +} // ----------------- Generic functions --------------------- -function secondstominutes(secs) -{ +function secondstominutes(secs) { var m = (secs / 60) | 0; return (m < 10 ? "0" + m : m) @@ -35,16 +65,14 @@ function secondstominutes(secs) + ( ( secs %= 60 ) < 10 ? "0" + secs : secs); } -function msecondstominutes(msecs) -{ +function msecondstominutes(msecs) { var m = (msecs / 60000) | 0; msecs %= 60000; var secs = (msecs / 1000) | 0; msecs %= 1000; msecs = Math.round(msecs * 100 / 1000); - if (msecs==100) msecs=99; - -// print("secs="+secs+", msecs="+msecs); + if (msecs===100) + msecs=99; return (m < 10 ? "0" + m : m) + ":" diff --git a/res/controllers/common-hid-packet-parser.js b/res/controllers/common-hid-packet-parser.js index b2fc3f2351..65bd581cc6 100644 --- a/res/controllers/common-hid-packet-parser.js +++ b/res/controllers/common-hid-packet-parser.js @@ -672,7 +672,7 @@ HIDPacket.prototype.send = function() { // packet_string += packet.data[d].toString(16) + " "; //} //HIDDebug("packet: " + packet_string); - controller.send(packet.data, packet.data.length, packet.reportId); + controller.send(packet.data, packet.data.length, this.reportId); } // -- cgit v1.2.3 From 12106699111f04bd5995b05e1548088b9e671596 Mon Sep 17 00:00:00 2001 From: Xerus <27jf@web.de> Date: Thu, 20 Dec 2018 21:41:41 +0100 Subject: Remove Packet object --- res/controllers/common-controller-scripts.js | 13 ------------- res/controllers/common-hid-packet-parser.js | 24 ++++++++++++------------ 2 files changed, 12 insertions(+), 25 deletions(-) (limited to 'res') diff --git a/res/controllers/common-controller-scripts.js b/res/controllers/common-controller-scripts.js index 13f54b3ea0..799803ff9e 100644 --- a/res/controllers/common-controller-scripts.js +++ b/res/controllers/common-controller-scripts.js @@ -534,17 +534,4 @@ Deck = function (deckNumber, group) { Deck.prototype.setControlValue = Controller.prototype.setControlValue; Deck.prototype.addButton = Controller.prototype.addButton; -// Data packet -function Packet(length, initialValue) { - this.length = length; - this.data = new Array(length); // Size the array - - if (!initialValue) initialValue=0; - - // Initialize data values - for (i=0; i=0) { - packet.data[index] = (value>>(byte_index*8)) & 255; + data[index] = (value>>(byte_index*8)) & 255; } else { - packet.data[index] = 255 - ((-(value+1)>>(byte_index*8)) & 255); + data[index] = 255 - ((-(value+1)>>(byte_index*8)) & 255); } } else { - packet.data[index] = (value>>(byte_index*8)) & 255; + data[index] = (value>>(byte_index*8)) & 255; } } @@ -643,11 +643,11 @@ HIDPacket.prototype.parse = function(data) { HIDPacket.prototype.send = function() { var offset = 0; var i; - var packet = new Packet(this.length); + var data = []; if(this.header !== undefined) { for (header_byte = 0; header_byte < this.header.length; header_byte++) { - packet.data[header_byte] = this.header[header_byte]; + data[header_byte] = this.header[header_byte]; } } @@ -660,19 +660,19 @@ HIDPacket.prototype.send = function() { var bit = field.value.bits[bit_id]; } } - this.pack(packet,field); + this.pack(data,field); } } //var packet_string = ""; - //for (d in packet.data) { - // if (packet.data[d] < 0x10) { + //for (d in data) { + // if (data[d] < 0x10) { // packet_string += "0"; // } - // packet_string += packet.data[d].toString(16) + " "; + // packet_string += data[d].toString(16) + " "; //} //HIDDebug("packet: " + packet_string); - controller.send(packet.data, packet.data.length, this.reportId); + controller.send(data, data.length, this.reportId); } // -- cgit v1.2.3 From b45d05c3f79edc4d0cef034b4ecab837af8d6b29 Mon Sep 17 00:00:00 2001 From: Xerus <27jf@web.de> Date: Thu, 20 Dec 2018 22:09:01 +0100 Subject: Add debug printing for packet sending --- res/controllers/common-hid-packet-parser.js | 30 +++++++++++++---------------- 1 file changed, 13 insertions(+), 17 deletions(-) (limited to 'res') diff --git a/res/controllers/common-hid-packet-parser.js b/res/controllers/common-hid-packet-parser.js index 6bbf1a0f89..a2974c066d 100644 --- a/res/controllers/common-hid-packet-parser.js +++ b/res/controllers/common-hid-packet-parser.js @@ -640,7 +640,7 @@ HIDPacket.prototype.parse = function(data) { // First the header bytes are copied to beginning of packet, then // field object values are packed to the HID packet according to the // field type. -HIDPacket.prototype.send = function() { +HIDPacket.prototype.send = function(debug) { var offset = 0; var i; var data = []; @@ -652,26 +652,22 @@ HIDPacket.prototype.send = function() { } for (var group_name in this.groups) { - var group = this.groups[group_name]; + var group = this.groups[group_name] for (var field_name in group) { - var field = group[field_name]; - if (field.type=="bitvector") { - for (var bit_id in field.value.bits) { - var bit = field.value.bits[bit_id]; - } - } - this.pack(data,field); + this.pack(data, group[field_name]) } } - //var packet_string = ""; - //for (d in data) { - // if (data[d] < 0x10) { - // packet_string += "0"; - // } - // packet_string += data[d].toString(16) + " "; - //} - //HIDDebug("packet: " + packet_string); + if(debug) { + var packet_string = ""; + for (d in data) { + if (data[d] < 0x10) { + packet_string += "0"; + } + packet_string += data[d].toString(16) + " "; + } + HIDDebug("Sending packet: " + packet_string + " with Report ID " + this.reportId); + } controller.send(data, data.length, this.reportId); } -- cgit v1.2.3 From 622cd5303eacd09e7e552d43a31917b08312b705 Mon Sep 17 00:00:00 2001 From: Xerus <27jf@web.de> Date: Sat, 22 Dec 2018 13:33:43 +0100 Subject: Properly format documentation in common-hid-packet-parser --- res/controllers/common-hid-packet-parser-test.js | 29 ++ res/controllers/common-hid-packet-parser.js | 463 +++++++++++------------ 2 files changed, 246 insertions(+), 246 deletions(-) create mode 100644 res/controllers/common-hid-packet-parser-test.js (limited to 'res') diff --git a/res/controllers/common-hid-packet-parser-test.js b/res/controllers/common-hid-packet-parser-test.js new file mode 100644 index 0000000000..d25e7f4b96 --- /dev/null +++ b/res/controllers/common-hid-packet-parser-test.js @@ -0,0 +1,29 @@ +// Manual packing test functions + +var packet = new HIDPacket('test', [0x1, 0x2], 6) +packet.addOutput('test', 'ushort', 2, 'H') +packet.addOutput('test', 'short', 4, 'h') + +var field = packet.getField('test', 'ushort') +print('FIELD ' + field.id + ' MIN ' + field.min + ' MAX ' + field.max) + +field.value = 1024 +field = packet.getField('test', 'short') +field.value = -32767 +print('FIELD ' + field.id + ' MIN ' + field.min + ' MAX ' + field.max) + +var out = { 'length': packet.length, 'data': [] } +for (var i = 0; i < packet.header.length; i++) { + out.data[i] = i +} +for (var group_name in packet.groups) { + var group = packet.groups[group_name] + for (var field_name in group) { + var field = group[field_name] + print('PACKING ' + field.id) + packet.pack(out, field) + } +} +for (var i = 0; i < out.length; i++) { + print('BYTE ' + i + ' VALUE ' + out.data[i]) +} diff --git a/res/controllers/common-hid-packet-parser.js b/res/controllers/common-hid-packet-parser.js index a2974c066d..be43a80511 100644 --- a/res/controllers/common-hid-packet-parser.js +++ b/res/controllers/common-hid-packet-parser.js @@ -1,18 +1,18 @@ -// Common HID script debugging function. Just to get logging with 'HID' prefix. +/** Common HID script debugging function. Just to get logging with 'HID' prefix. */ HIDDebug = function (message) { print("HID " + message); } -// HID Bit Vector Class -// -// Collection of bits in one parsed packet field. These objects are -// created by HIDPacket addControl and addOutput and should not be -// created manually. +/** HID Bit Vector Class + * + * Collection of bits in one parsed packet field. These objects are + * created by HIDPacket addControl and addOutput and should not be + * created manually. */ function HIDBitVector () { this.size = 0; this.bits = new Object(); } -// Return bit offset based on bitmask +/** Return bit offset based on bitmask */ HIDBitVector.prototype.getOffset = function(bitmask) { for (var i=0;i<32;i++) if ( (1&bitmask>>i)!=0 ) @@ -20,7 +20,7 @@ HIDBitVector.prototype.getOffset = function(bitmask) { return 0; } -// Add a control bitmask to the HIDBitVector +/** Add a control bitmask to the HIDBitVector */ HIDBitVector.prototype.addBitMask = function(group,name,bitmask) { var bit = new Object(); bit.type = "button"; @@ -40,7 +40,7 @@ HIDBitVector.prototype.addBitMask = function(group,name,bitmask) { this.bits[bit.id] = bit; } -// Add a Output control bitmask to the HIDBitVector +/** Add a Output control bitmask to the HIDBitVector */ HIDBitVector.prototype.addOutputMask = function(group,name,bitmask) { var bit = new Object(); bit.type = "output"; @@ -58,16 +58,16 @@ HIDBitVector.prototype.addOutputMask = function(group,name,bitmask) { this.bits[bit.id] = bit; } -// HID Modifiers object -// -// Wraps all defined modifiers to one object with uniform API. -// Don't call directly, this is available as HIDController.modifiers +/** HID Modifiers object + * + * Wraps all defined modifiers to one object with uniform API. + * Don't call directly, this is available as HIDController.modifiers */ function HIDModifierList() { this.modifiers = Object(); this.callbacks = Object(); } -// Add a new modifier to controller. +/** Add a new modifier to controller. */ HIDModifierList.prototype.add = function(name) { if (name in this.modifiers) { HIDDebug("Modifier already defined: " + name); @@ -76,7 +76,7 @@ HIDModifierList.prototype.add = function(name) { this.modifiers[name] = undefined; } -// Set modifier value +/** Set modifier value */ HIDModifierList.prototype.set = function(name,value) { if ((!name in this.modifiers)) { HIDDebug("Unknown modifier: " + name); @@ -89,7 +89,7 @@ HIDModifierList.prototype.set = function(name,value) { } } -// Get modifier value +/** Get modifier value */ HIDModifierList.prototype.get = function(name) { if (!(name in this.modifiers)) { HIDDebug("Unknown modifier: " + name); @@ -98,7 +98,7 @@ HIDModifierList.prototype.get = function(name) { return this.modifiers[name]; } -// Set modifier callback (update function after modifier state changes) +/** Set modifier callback (update function after modifier state changes) */ HIDModifierList.prototype.setCallback = function(name,callback) { if ((!name in this.modifiers)) { HIDDebug("Unknonwn modifier: " + name); @@ -107,20 +107,20 @@ HIDModifierList.prototype.setCallback = function(name,callback) { this.callbacks[name] = callback; } -// -// HID Packet object -// -// One HID input/output packet to register to HIDController -// name name of packet -// reportId report ID of the packet. If the device only uses -// one report type, this must be 0. -// callback function to call when this packet type is input -// and is received. If packet callback is set, the -// packet is not parsed by delta functions. -// callback is not meaningful for output packets -// header (optional) list of bytes to match from beginning -// of packet. Do NOT put the report ID in this; use -// the reportId parameter instead. +/** + * HID Packet object + * + * One HID input/output packet to register to HIDController + * @param name name of packet + * @param reportId report ID of the packet. If the device only uses + * one report type, this must be 0. + * @param callback function to call when this packet type is input + * and is received. If packet callback is set, the + * packet is not parsed by delta functions. + * callback is not meaningful for output packets + * @param header (optional) list of bytes to match from beginning + * of packet. Do NOT put the report ID in this; use + * the reportId parameter instead. */ function HIDPacket(name, reportId, callback, header) { this.name = name; this.header = header; @@ -138,8 +138,8 @@ function HIDPacket(name, reportId, callback, header) { this.signedPackFormats = [ "b", "h", "i"]; } -// Pack a field value to the packet. -// Can only pack bits and byte values, patches welcome. +/** Pack a field value to the packet. + * Can only pack bits and byte values, patches welcome. */ HIDPacket.prototype.pack = function(data,field) { var value = 0; if (!(field.pack in this.packSizes)) { @@ -186,14 +186,14 @@ HIDPacket.prototype.pack = function(data,field) { } -// Parse and return field value matching the 'pack' field from field attributes. -// Valid values are: -// b signed byte -// B unsigned byte -// h signed short -// H unsigned short -// i signed integer -// I unsigned integer +/** Parse and return field value matching the 'pack' field from field attributes. + * Valid values are: + * b signed byte + * B unsigned byte + * h signed short + * H unsigned short + * i signed integer + * I unsigned integer */ HIDPacket.prototype.unpack = function(data,field) { var value = 0; @@ -221,8 +221,8 @@ HIDPacket.prototype.unpack = function(data,field) { return value; } -// Find HID packet group matching name. -// Create group if create is true +/** Find HID packet group matching name. + * Create group if create is true */ HIDPacket.prototype.getGroup = function(name,create) { if (this.groups==undefined) this.groups = new Object(); @@ -234,8 +234,8 @@ HIDPacket.prototype.getGroup = function(name,create) { return this.groups[name]; } -// Lookup HID packet field matching given offset and pack type -// Returns undefined if no patching field can be found. +/** Lookup HID packet field matching given offset and pack type + * Returns undefined if no patching field can be found. */ HIDPacket.prototype.getFieldByOffset = function(offset,pack) { if (!(pack in this.packSizes)) { HIDDebug("Unknown pack string " + pack); @@ -266,8 +266,8 @@ HIDPacket.prototype.getFieldByOffset = function(offset,pack) { return undefined; } -// Return a field by group and name from the packet, -// Returns undefined if field could not be found +/** Return a field by group and name from the packet, + * Returns undefined if field could not be found */ HIDPacket.prototype.getField = function(group,name) { var field_id = group+"."+name; if (!(group in this.groups)) { @@ -298,7 +298,7 @@ HIDPacket.prototype.getField = function(group,name) { return undefined; } -// Return reference to a bit in a bitvector field +/** Return reference to a bit in a bitvector field */ HIDPacket.prototype.lookupBit = function(group,name) { var field = this.getField(group,name); if (field==undefined) { @@ -315,7 +315,7 @@ HIDPacket.prototype.lookupBit = function(group,name) { return undefined; } -// Remove a control registered. Normally not needed +/** Remove a control registered. Normally not needed */ HIDPacket.prototype.removeControl = function(group,name) { var control_group = this.getGroup(group); if (!(name in control_group)) { @@ -325,17 +325,15 @@ HIDPacket.prototype.removeControl = function(group,name) { delete control_group[name]; } -// Register a numeric value to parse from input packet -// Parameters: -// group control group name -// name name of the field -// offset field offset inside packet (bytes) -// pack control packing format for unpack() -// bitmask bitmask size, undefined for byte(s) controls -// NOTE: Parsing bitmask with multiple bits is not supported yet. -// isEncoder indicates if this is an encoder which should be wrapped and delta reported -// callback callback function to apply to the field value, or undefined for no callback -// +/** Register a numeric value to parse from input packet + * + * @param group control group name + * @param name name of the field + * @param offset field offset inside packet (bytes) + * @param pack control packing format for unpack() + * @param bitmask bitmask size, undefined for byte(s) controls + * NOTE: Parsing bitmask with multiple bits is not supported yet. + * @param isEncoder indicates if this is an encoder which should be wrapped and delta reported */ HIDPacket.prototype.addControl = function(group,name,offset,pack,bitmask,isEncoder) { var control_group = this.getGroup(group,true); var bitvector = undefined; @@ -418,14 +416,14 @@ HIDPacket.prototype.addControl = function(group,name,offset,pack,bitmask,isEncod control_group[field.id] = field; } -// Register a Output control field or Output control bit to output packet -// Output control field: -// Output field with no bitmask, controls Output with multiple values -// Output control bit: -// Output with with bitmask, controls Output with a single bit -// -// It is recommended to define callbacks after packet creation with -// setCallback instead of adding it directly here. But you can do it. +/** Register a Output control field or Output control bit to output packet + * Output control field: + * Output field with no bitmask, controls Output with multiple values + * Output control bit: + * Output with with bitmask, controls Output with a single bit + * + * It is recommended to define callbacks after packet creation with + * setCallback instead of adding it directly here. But you can do it. */ HIDPacket.prototype.addOutput = function(group,name,offset,pack,bitmask,callback) { var control_group = this.getGroup(group,true); var field = undefined; @@ -499,8 +497,8 @@ HIDPacket.prototype.addOutput = function(group,name,offset,pack,bitmask,callback control_group[field.id] = field; } -// Register a callback to field or a bit vector bit. -// Does not make sense for Output fields but you can do that. +/** Register a callback to field or a bit vector bit. + * Does not make sense for Output fields but you can do that. */ HIDPacket.prototype.setCallback = function(group,name,callback) { var field = this.getField(group,name); var field_id = group+"."+name; @@ -527,8 +525,8 @@ HIDPacket.prototype.setCallback = function(group,name,callback) { } } -// Set 'ignored' flag for field to given value (true or false) -// If field is ignored, it is not reported in 'delta' objects. +/** Set 'ignored' flag for field to given value (true or false) + * If field is ignored, it is not reported in 'delta' objects. */ HIDPacket.prototype.setIgnored = function(group,name,ignored) { var field = this.getField(group,name); if (field==undefined) { @@ -538,8 +536,8 @@ HIDPacket.prototype.setIgnored = function(group,name,ignored) { field.ignored = ignored; } -// Adjust field's minimum delta value. -// Input value changes smaller than this are not reported in delta +/** Adjust field's minimum delta value. + * Input value changes smaller than this are not reported in delta */ HIDPacket.prototype.setMinDelta = function(group,name,mindelta) { field = this.getField(group,name); if (field==undefined) { @@ -553,9 +551,9 @@ HIDPacket.prototype.setMinDelta = function(group,name,mindelta) { field.mindelta = mindelta; } -// Parse bitvector field values, returning object with the named bits set. -// Value must be a valid unsigned byte to parse, with enough bits. -// Returns list of modified bits (delta) +/** Parse bitvector field values, returning object with the named bits set. + * Value must be a valid unsigned byte to parse, with enough bits. + * Returns list of modified bits (delta) */ HIDPacket.prototype.parseBitVector = function(field,value) { var bits = new Object(); var bit; @@ -570,19 +568,16 @@ HIDPacket.prototype.parseBitVector = function(field,value) { return bits; } -// Parse input packet fields from data. -// Data is expected to be a Packet() received from HID device. -// Returns list of changed fields with new value. -// BitVectors are returned as bits you can iterate separately. +/** Parse input packet fields from data. + * Data is expected to be a Packet() received from HID device. + * Returns list of changed fields with new value. + * BitVectors are returned as bits you can iterate separately. */ HIDPacket.prototype.parse = function(data) { var field_changes = new Object(); var group; var group_name; var field; - var field_name; var field_id; - var bit; - var bit_value; for (group_name in this.groups) { group = this.groups[group_name]; @@ -598,7 +593,7 @@ HIDPacket.prototype.parse = function(data) { } if (field.type=="bitvector") { - // Bitvector deltas are checked in parseBitVector + // Bitvector deltas are checked in parseBitVector var changed_bits = this.parseBitVector(field,value); for (bit_name in changed_bits) field_changes[bit_name] = changed_bits[bit_name]; @@ -636,10 +631,10 @@ HIDPacket.prototype.parse = function(data) { return field_changes; } -// Send this HID packet to device. -// First the header bytes are copied to beginning of packet, then -// field object values are packed to the HID packet according to the -// field type. +/** Send this HID packet to device. + * First the header bytes are copied to beginning of packet, then + * field object values are packed to the HID packet according to the + * field type. */ HIDPacket.prototype.send = function(debug) { var offset = 0; var i; @@ -671,37 +666,38 @@ HIDPacket.prototype.send = function(debug) { controller.send(data, data.length, this.reportId); } -// -// HID Controller Class -// -// HID Controller with packet parser -// Global attributes include: -// -// initialized by default false, you should set this to true when -// controller is found and everything is OK -// activeDeck by default undefined, used to map the virtual deck -// names 'deck','deck1' and 'deck2' to actual [ChannelX] -// isScratchEnabled set to true, when button 'jog_touch' is active -// buttonStates valid state values for buttons, should contain fields -// released (default 0) and pressed (default 1) -// LEDColors possible Output colors named, must contain 'off' value -// deckOutputColors Which colors to use for each deck. Default 'on' for first -// four decks. Values are like {1: 'red', 2: 'green' } -// and must reference valid OutputColors fields. -// OutputUpdateInterval By default undefined. If set, it's a value for timer -// executed every n ms to update Outputs with updateOutputs() -// modifiers Reference to HIDModifierList object -// toggleButtons List of button names you wish to act as 'toggle', i.e. -// pressing the button and releasing toggles state of the -// control and does not set it off again when released. -// -// Scratch variables (initialized with 'common' defaults, you can override): -// scratchintervalsPerRev Intervals value for scratch_enable -// scratchRPM RPM value for scratch_enable -// scratchAlpha Alpha value for scratch_enable -// scratchBeta Beta value for scratch_enable -// scratchRampOnEnable If 'ramp' is used when enabling scratch -// scratchRampOnDisable If 'ramp' is used when disabling scratch +/** + * HID Controller Class + * + * HID Controller with packet parser + * Global attributes include: + * + * initialized by default false, you should set this to true when + * controller is found and everything is OK + * activeDeck by default undefined, used to map the virtual deck + * names 'deck','deck1' and 'deck2' to actual [ChannelX] + * isScratchEnabled set to true, when button 'jog_touch' is active + * buttonStates valid state values for buttons, should contain fields + * released (default 0) and pressed (default 1) + * LEDColors possible Output colors named, must contain 'off' value + * deckOutputColors Which colors to use for each deck. Default 'on' for first + * four decks. Values are like {1: 'red', 2: 'green' } + * and must reference valid OutputColors fields. + * OutputUpdateInterval By default undefined. If set, it's a value for timer + * executed every n ms to update Outputs with updateOutputs() + * modifiers Reference to HIDModifierList object + * toggleButtons List of button names you wish to act as 'toggle', i.e. + * pressing the button and releasing toggles state of the + * control and does not set it off again when released. + * + * Scratch variables (initialized with 'common' defaults, you can override): + * scratchintervalsPerRev Intervals value for scratch_enable + * scratchRPM RPM value for scratch_enable + * scratchAlpha Alpha value for scratch_enable + * scratchBeta Beta value for scratch_enable + * scratchRampOnEnable If 'ramp' is used when enabling scratch + * scratchRampOnDisable If 'ramp' is used when disabling scratch + */ function HIDController () { this.initialized = false; this.activeDeck = undefined; @@ -763,7 +759,7 @@ function HIDController () { this.auto_repeat_interval = 100; } -// Function to close the controller object cleanly +/** Function to close the controller object cleanly */ HIDController.prototype.close = function() { for (var name in this.timers) { var timer = this.timers[name]; @@ -774,12 +770,12 @@ HIDController.prototype.close = function() { } } -// Initialize our packet data and callbacks. This does not seem to -// work when executed from here, but we keep a stub just in case. +/** Initialize our packet data and callbacks. This does not seem to + * work when executed from here, but we keep a stub just in case. */ HIDController.prototype.initializePacketData = function() { } -// Return deck number from deck name. Deck name can't be virtual deck name -// in this function call. +/** Return deck number from deck name. Deck name can't be virtual deck name + * in this function call. */ HIDController.prototype.resolveDeck = function(group) { if (group==undefined) return undefined; @@ -790,15 +786,15 @@ HIDController.prototype.resolveDeck = function(group) { return str.substring(0,str.length-1); } -// Return the group name from given deck number. +/** Return the group name from given deck number. */ HIDController.prototype.resolveDeckGroup = function(deck) { if (deck==undefined) return undefined; return "[Channel"+deck+"]"; } -// Map virtual deck names to real deck group. If group is already -// a real mixxx group value, just return it as it without mapping. +/** Map virtual deck names to real deck group. If group is already + * a real mixxx group value, just return it as it without mapping. */ HIDController.prototype.resolveGroup = function(group) { var channel_name = /\[Channel[0-9]+\]/; if (group!=undefined && group.match(channel_name) ) @@ -822,8 +818,8 @@ HIDController.prototype.resolveGroup = function(group) { return undefined; } -// Find Output control matching give group and name -// Returns undefined if output field can't be found. +/** Find Output control matching give group and name + * Returns undefined if output field can't be found. */ HIDController.prototype.getOutputField = function(m_gr