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 }