1 module rivald.device; 2 3 // 4 // Rivald 5 // 6 import rivald.cycle; 7 import rivald.error; 8 import rivald.values; 9 10 // 11 // Hidapi-d 12 // 13 import hidapi.device; 14 import hidapi.devicelist; 15 16 // 17 // Std 18 // 19 import std.exception : enforce; 20 import std.experimental.logger; 21 import std.format : format; 22 import std.math : log2; 23 import std.typecons : Tuple, tuple; 24 25 // 26 // Core 27 // 28 import core.thread; 29 30 class RivalDevice 31 { 32 private Device dev; 33 34 /** 35 * Destructor 36 */ 37 ~this() 38 { 39 destroy(dev); // make sure we destroy dev to properly close hidapi 40 } 41 42 /** 43 * Opens the first matching device 44 */ 45 this() 46 { 47 dev = new Device(0x1038, 0x1720); 48 } 49 50 /** 51 * Opens the first matching device 52 * 53 * Params: 54 * serial_nuber = Serial Number 55 */ 56 this(string serial_number) 57 { 58 dev = new Device(0x1038, 0x1720, serial_number); 59 } 60 61 /** 62 * Opens the first matching device 63 * 64 * Params: 65 * vendor_id = Vendor ID 66 * product_id = Product ID 67 */ 68 this(uint vendor_id, uint product_id) 69 { 70 warning("Unsafe function: You are directly opening a device.\nThis library (rivald) was meant " ~ 71 "to be used with the Steelseries Rival 310. Use this function at your own risk!"); 72 dev = new Device(vendor_id, product_id); 73 } 74 75 /** 76 * Opens the first matching device 77 * 78 * Params: 79 * vendor_id = Vendor ID 80 * product_id = Product ID 81 * serial_nuber = Serial Number 82 */ 83 this(uint vendor_id, uint product_id, string serial_number) 84 { 85 warning("Unsafe function: You are directly opening a device.\nThis library (rivald) was meant " ~ 86 "to be used with the Steelseries Rival 310. Use this function at your own risk!"); 87 dev = new Device(vendor_id, product_id, serial_number); 88 } 89 90 /** 91 * Opens a specific device 92 * 93 * Params: 94 * serial_nuber = Serial Number 95 */ 96 /* 97 this(string path) 98 { 99 warning("Unsafe function: You are directly opening a device.\nThis library (rivald) was meant " ~ 100 "to be used with the Steelseries Rival 310. Use this function at your own risk!"); 101 dev = new Device(path); 102 } 103 */ 104 105 /** 106 * Opens a specific device 107 * 108 * Params: 109 * serial_nuber = Serial Number 110 */ 111 this(Device dev) 112 { 113 warning("Unsafe function: You are directly opening a device.\nThis library (rivald) was meant " ~ 114 "to be used with the Steelseries Rival 310. Use this function at your own risk!"); 115 this.dev = dev; 116 } 117 118 /** 119 * Change DPI 120 * 121 * Params: 122 * id = DPI ID (1 or 2) 123 * value = DPI value 124 * 125 * https://github.com/FFY00/rival310-re/blob/master/53.md 126 * https://github.com/FFY00/rival310-re/blob/master/5A.md 127 */ 128 void setDpi(ubyte id, uint value) 129 { 130 enforce!RivalError(id == 1 || id == 2, "Invalid DPI ID."); 131 enforce!RivalError(value >= Dpi.STEP && value <= Dpi.MAX, 132 format!"Invalid DPI value (min. %d, max. %d)."(Dpi.STEP, Dpi.MAX)); 133 134 auto steps = (value - Dpi.STEP) / Dpi.STEP; 135 auto rem = (value - Dpi.STEP) % Dpi.STEP; 136 137 // Round .5 and up 138 if(rem >= Dpi.STEP / 2) 139 steps++; 140 141 warning(rem != 0, 142 format!"Invalid DPI value, asserting from %d to %d"(value, steps * Dpi.STEP + Dpi.STEP)); 143 144 // Write DPI values (53) 145 ubyte[] buf = new ubyte[Size.NORMAL]; 146 buf[0] = Command.DPI; 147 buf[2] = id; 148 buf[3] = cast(ubyte) steps; 149 150 log(buf); 151 152 assert(steps >= 0 && steps <= 119 ); 153 154 dev.write(buf, Size.NORMAL); 155 Thread.sleep(10.msecs); 156 157 // Save DPI values? (5A) 158 buf = new ubyte[Size.NORMAL]; 159 buf[0] = Command.DPI_UNKNOWN; 160 buf[2] = cast(ubyte) (id - 1); 161 162 log(buf); 163 164 dev.write(buf, Size.NORMAL); 165 Thread.sleep(10.msecs); 166 } 167 168 /** 169 * Reads DPI values 170 * 171 * Warning! Can return an empty array. 172 * https://github.com/FFY00/rival310-re/blob/master/92.md 173 */ 174 Tuple!(ushort, ushort) readDpi() 175 { 176 ubyte[] buf = new ubyte[Size.NORMAL]; 177 buf[0] = Command.READ_DPI; 178 179 auto res = dev.command(buf, Size.NORMAL); 180 Thread.sleep(10.msecs); 181 182 log(res); 183 184 return tuple( cast(ushort) (Dpi.STEP * res[2] + Dpi.STEP), 185 cast(ushort) (Dpi.STEP * res[4] + Dpi.STEP)); 186 } 187 188 /** 189 * Reads DPI value 190 * 191 * Params: 192 * id = DPI ID (1 or 2) 193 * 194 * Warning! Can return -1. 195 * https://github.com/FFY00/rival310-re/blob/master/92.md 196 */ 197 ushort readDpi(ubyte id) 198 { 199 ubyte[] buf = new ubyte[Size.NORMAL]; 200 buf[0] = Command.READ_DPI; 201 202 auto res = dev.command(buf, Size.NORMAL); 203 Thread.sleep(10.msecs); 204 205 if(id == 1) 206 return Dpi.STEP * res[2] + Dpi.STEP; 207 208 if(id == 2) 209 return Dpi.STEP * res[4] + Dpi.STEP; 210 211 return 0; 212 } 213 214 /** 215 * Change report rate 216 * 217 * Params: 218 * value = rate value 219 * 220 * https://github.com/FFY00/rival310-re/blob/master/54.md 221 */ 222 void setRate(uint value) 223 { 224 enforce!RivalError(value >= Rate.MIN && value <= Rate.MAX, 225 format!"Invalid Report Rate value (min. %d, max. %d)."(Rate.MIN, Rate.MAX)); 226 227 auto calc(T)(T steps) 228 { 229 return Rate.MAX / (2 ^^ (steps - 1)); 230 } 231 232 ubyte round(real steps) 233 { 234 ubyte steps_int = cast(ubyte) steps; 235 uint step_val = cast(uint) (calc(steps_int) - calc(steps + 1)); 236 237 if(calc(steps_int) - value > step_val / 2) 238 steps_int++; 239 240 return steps_int; 241 } 242 243 const ubyte steps = round(log2(2 * Rate.MAX / value)); 244 245 warning(value != calc(steps), 246 format!"Invalid Report Rate value, asserting from %d to %d"(value, calc(steps))); 247 248 ubyte[] buf = new ubyte[Size.NORMAL]; 249 buf[0] = Command.RATE; 250 buf[2] = steps; 251 252 assert(steps >= 1 && steps <= 4 ); 253 254 dev.write(buf, Size.NORMAL); 255 Thread.sleep(10.msecs); 256 } 257 258 /** 259 * Save settings in the mouse 260 * 261 * https://github.com/FFY00/rival310-re/blob/master/59.md 262 */ 263 void save() 264 { 265 ubyte[] buf = new ubyte[Size.NORMAL]; 266 buf[0] = Command.SAVE; 267 268 dev.write(buf, Size.NORMAL); 269 Thread.sleep(10.msecs); 270 } 271 272 /** 273 * Set Led cycle 274 * 275 * Params: 276 * led = Led ID 277 * cycle = Cycle to write 278 * 279 * https://github.com/FFY00/rival310-re/blob/master/5B.md 280 */ 281 void setLedCycle(ubyte led, Cycle cycle) 282 { 283 enforce!RivalError(led == Led.LOGO || led == Led.WHEEL, "Invalid Led ID."); 284 285 ubyte[Size.LONG] buf = cycle.getBuffer(); 286 287 dev.write(buf, Size.LONG); 288 Thread.sleep(10.msecs); 289 } 290 291 /** 292 * Set Led cycle 293 * 294 * Params: 295 * led = Led ID 296 * points = Points of the cycle 297 * 298 * https://github.com/FFY00/rival310-re/blob/master/5B.md 299 */ 300 void setLedCycle(ubyte led, Point[] points) 301 { 302 enforce!RivalError(led == Led.LOGO || led == Led.WHEEL, "Invalid Led ID."); 303 304 auto cycle = new Cycle(led); 305 cycle.setPoints(points); 306 307 ubyte[Size.LONG] buf = cycle.getBuffer(); 308 309 dev.write(buf, Size.LONG); 310 Thread.sleep(10.msecs); 311 } 312 313 /** 314 * Set Led cycle 315 * 316 * Params: 317 * led = Led ID 318 * points = Points of the cycle 319 * duration = Duration of the cycle 320 * 321 * https://github.com/FFY00/rival310-re/blob/master/5B.md 322 */ 323 void setLedCycle(ubyte led, Point[] points, ushort duration) 324 { 325 enforce!RivalError(led == Led.LOGO || led == Led.WHEEL, "Invalid Led ID."); 326 327 auto cycle = new Cycle(led); 328 cycle.setPoints(points); 329 cycle.setDuration(duration); 330 331 ubyte[Size.LONG] buf = cycle.getBuffer(); 332 333 dev.write(buf, Size.LONG); 334 Thread.sleep(10.msecs); 335 } 336 337 /** 338 * Reset LEDs 339 * 340 * https://github.com/FFY00/rival310-re 341 */ 342 void resetLed() 343 { 344 ubyte[] buf = new ubyte[Size.NORMAL]; 345 buf[0] = Command.RESET_LED; 346 347 dev.write(buf, Size.NORMAL); 348 Thread.sleep(10.msecs); 349 } 350 351 /** 352 * Reads firmware version 353 * 354 * https://github.com/FFY00/rival310-re/blob/master/90.md 355 */ 356 Tuple!(ubyte, ubyte) readFirmware() 357 { 358 ubyte[] buf = new ubyte[Size.NORMAL]; 359 buf[0] = Command.FIRMWARE; 360 361 auto res = dev.command(buf, Size.NORMAL); 362 Thread.sleep(10.msecs); 363 364 log(res); 365 366 assert(res[1] == 1); 367 368 return tuple(res[1], res[0]); 369 } 370 371 void test() 372 { 373 ubyte[] buf = new ubyte[Size.NORMAL]; 374 buf[0] = 0x91; 375 buf[1] = 0; 376 buf[2] = 2; 377 buf[3] = 0; 378 buf[4] = 0; 379 380 // 9A - 2 381 // 0 - 66 382 // 1 - 1 383 // 2 - 32 384 385 auto res = dev.command(buf, Size.NORMAL); 386 Thread.sleep(10.msecs); 387 388 log(res); 389 } 390 391 }