Keyset update
[pierogi] / protocols / xmpprotocol.cpp
1 #include "xmpprotocol.h"
2
3 #include "pirrx51hardware.h"
4
5 #include "pirexception.h"
6
7 // Some global communications stuff:
8 #include <QMutex>
9 extern bool commandInFlight;
10 extern QMutex commandIFMutex;
11
12 // The XMP protocol is a real beast, packed full of checksums, toggle bits,
13 // large command codes and fancy repeat mechanisms.
14 // Each pulse/space pair represents four bits, as so:
15 // A "zero" is encoded with a 210 usec pulse, 760 usec space.
16 // Each value after that adds an additional 136 usec to the space, so
17 // a "one" has a 1*136 + 760 = 896 usec space,
18 // a "two" has a 2*136 + 760 = 1032 usec space,
19 // ...
20 // and a "fifteen" has a 15*136 + 760 = 2800 usec space.
21 // There is no header pulse.
22 // There is a 210 usec pulse, 13800 usec space in the middle...
23 // Commands end with a trailing 210 usec pulse.
24 // The first "frame" has a 4-bit "toggle" value of 0; repeat frames following
25 // this one are identical, except for the "toggle" value changed to 8.
26 // There is a gap of 80000 usec between each frame.
27 // An optional "final" frame can also exist, with a toggle value of 9, and
28 // separated from the previous frame by only 13800 usec.
29 // The carrier frequency should be 38 kHz.
30
31 XMPProtocol::XMPProtocol(
32   QObject *guiObject,
33   unsigned int index,
34   unsigned int sd1,
35   unsigned int sd2,
36   unsigned int oem,
37   unsigned int d,
38   bool hasFF)
39   : PIRProtocol(
40       guiObject, index,
41       80000, true),
42     subDeviceOne(sd1),
43     subDeviceTwo(sd2),
44     oemCode(oem),
45     deviceCode(d),
46     hasFinalFrame(hasFF)
47 {
48 }
49
50
51 void XMPProtocol::startSendingCommand(
52   unsigned int threadableID,
53   PIRKeyName command)
54 {
55   // Exceptions here are problematic; I'll try to weed them out by putting the
56   // whole thing in a try/catch block:
57   try
58   {
59     // First, check if we are meant to be the recipient of this command:
60     if (threadableID != id) return;
61
62     clearRepeatFlag();
63
64     KeycodeCollection::const_iterator i = keycodes.find(command);
65
66     // Do we even have this key defined?
67     if (i == keycodes.end())
68     {
69       std::string s = "Tried to send a non-existent command.\n";
70       throw PIRException(s);
71     }
72
73     // construct the device:
74     PIRRX51Hardware rx51device(carrierFrequency, dutyCycle);
75
76     int repeatCount = 0;
77     int commandDuration = 0;
78     while (repeatCount < MAX_REPEAT_COUNT)
79     {
80       if (repeatCount)
81       {
82         commandDuration = generateRepeatCommand(i->second, rx51device);
83       }
84       else
85       {
86         commandDuration = generateStandardCommand(i->second, rx51device);
87       }
88
89       // Now, tell the device to send the whole command:
90       rx51device.sendCommandToDevice();
91
92       // sleep until the next repetition of command:
93       sleepUntilRepeat(commandDuration);
94
95       // Check whether we've reached the minimum required number of repetitons:
96       if (repeatCount >= minimumRepetitions)
97       {
98         // Check whether we've been asked to stop:
99         if (checkRepeatFlag())
100         {
101           // Do we need to send out a final frame?
102           if (hasFinalFrame)
103           {
104             commandDuration = generateFinalCommand(i->second, rx51device);
105             rx51device.sendCommandToDevice();
106             sleepUntilRepeat(commandDuration);
107           }
108
109           QMutexLocker cifLocker(&commandIFMutex);
110           commandInFlight = false;
111           return;
112         }
113       }
114
115       ++repeatCount;
116     }
117   }
118   catch (PIRException e)
119   {
120     // inform the gui:
121     emit commandFailed(e.getError().c_str());
122   }
123
124   QMutexLocker cifLocker(&commandIFMutex);
125   commandInFlight = false;
126 }
127
128
129 int XMPProtocol::generateStandardCommand(
130   const PIRKeyBits &pkb,
131   PIRRX51Hardware &rx51device)
132 {
133   int duration = 0;
134
135   // XMP frames have the following structure:
136   // 1) The first 4 bits of the "sub-device" code
137   // 2) A four-bit checksum value
138   // 3) The second 4 bits of the "sub-device" code
139   // 4) The four-bit value 0xF
140   // 5) An eight-bit OEM code (normally 0x44)
141   // 6) An eight-bit device code
142   // 7) a 210 usec pulse, 13800 usec space divider
143   // 8) The first 4 bits of the "sub-device" code (again)
144   // 9) Another four-bit checksum value
145   // 10) The four-bit toggle value
146   // 11) The second 4 bits of the "sub-device" code (again)
147   // 12) A pair of 8-bit command codes (often one of them will be 0)
148   // All of this is sent in MSB order.
149   // The checksums are constructed by adding up all the half-bytes in
150   // their side of the frame to 15, taking the complement, and modding the
151   // result with 16.
152
153   duration += pushHalfByte(subDeviceOne, rx51device);
154   duration += pushHalfByte(calculateChecksumOne(), rx51device);
155   duration += pushHalfByte(subDeviceTwo, rx51device);
156   duration += pushHalfByte(0xF, rx51device);
157   duration += pushFullByte(oemCode, rx51device);
158   duration += pushFullByte(deviceCode, rx51device);
159
160   rx51device.addPair(210, 13800);
161   duration += 14010;
162
163   duration += pushHalfByte(subDeviceOne, rx51device);
164   duration += pushHalfByte(
165     calculateChecksumTwo(0x0, pkb.firstCode, pkb.secondCode),
166     rx51device);
167   duration += pushHalfByte(0x0, rx51device);
168   duration += pushHalfByte(subDeviceTwo, rx51device);
169   duration += pushBits(pkb.firstCode, rx51device);
170   duration += pushBits(pkb.secondCode, rx51device);
171
172   // Finally add the "trail":
173   rx51device.addSingle(210);
174   duration += 210;
175
176   return duration;
177 }
178
179
180 int XMPProtocol::generateRepeatCommand(
181   const PIRKeyBits &pkb,
182   PIRRX51Hardware &rx51device)
183 {
184   int duration = 0;
185
186   // an XMP repeat frame is identical to the start frame, except that
187   // the "toggle" value is now 8.
188
189   duration += pushHalfByte(subDeviceOne, rx51device);
190   duration += pushHalfByte(calculateChecksumOne(), rx51device);
191   duration += pushHalfByte(subDeviceTwo, rx51device);
192   duration += pushHalfByte(0xF, rx51device);
193   duration += pushFullByte(oemCode, rx51device);
194   duration += pushFullByte(deviceCode, rx51device);
195
196   rx51device.addPair(210, 13800);
197   duration += 14010;
198
199   duration += pushHalfByte(subDeviceOne, rx51device);
200   duration += pushHalfByte(
201     calculateChecksumTwo(0x8, pkb.firstCode, pkb.secondCode),
202     rx51device);
203   duration += pushHalfByte(0x8, rx51device);
204   duration += pushHalfByte(subDeviceTwo, rx51device);
205   duration += pushBits(pkb.firstCode, rx51device);
206   duration += pushBits(pkb.secondCode, rx51device);
207
208   // Finally add the "trail":
209   rx51device.addSingle(210);
210   duration += 210;
211
212   return duration;
213 }
214
215
216 int XMPProtocol::generateFinalCommand(
217   const PIRKeyBits &pkb,
218   PIRRX51Hardware &rx51device)
219 {
220   int duration = 0;
221
222   // an XMP final frame is basically a pair of repeat frames, but the
223   // gap between them is only 13800 usec, and the "toggle" value of the
224   // second frame is 9.
225
226   duration += pushHalfByte(subDeviceOne, rx51device);
227   duration += pushHalfByte(calculateChecksumOne(), rx51device);
228   duration += pushHalfByte(subDeviceTwo, rx51device);
229   duration += pushHalfByte(0xF, rx51device);
230   duration += pushFullByte(oemCode, rx51device);
231   duration += pushFullByte(deviceCode, rx51device);
232
233   rx51device.addPair(210, 13800);
234   duration += 14010;
235
236   duration += pushHalfByte(subDeviceOne, rx51device);
237   duration += pushHalfByte(
238     calculateChecksumTwo(0x8, pkb.firstCode, pkb.secondCode),
239     rx51device);
240   duration += pushHalfByte(0x8, rx51device);
241   duration += pushHalfByte(subDeviceTwo, rx51device);
242   duration += pushBits(pkb.firstCode, rx51device);
243   duration += pushBits(pkb.secondCode, rx51device);
244
245   rx51device.addPair(210, 13800);
246   duration += 14010;
247
248   duration += pushHalfByte(subDeviceOne, rx51device);
249   duration += pushHalfByte(calculateChecksumOne(), rx51device);
250   duration += pushHalfByte(subDeviceTwo, rx51device);
251   duration += pushHalfByte(0xF, rx51device);
252   duration += pushFullByte(oemCode, rx51device);
253   duration += pushFullByte(deviceCode, rx51device);
254
255   rx51device.addPair(210, 13800);
256   duration += 14010;
257
258   duration += pushHalfByte(subDeviceOne, rx51device);
259   duration += pushHalfByte(
260     calculateChecksumTwo(0x9, pkb.firstCode, pkb.secondCode),
261     rx51device);
262   duration += pushHalfByte(0x9, rx51device);
263   duration += pushHalfByte(subDeviceTwo, rx51device);
264   duration += pushBits(pkb.firstCode, rx51device);
265   duration += pushBits(pkb.secondCode, rx51device);
266
267   // Finally add the "trail":
268   rx51device.addSingle(210);
269   duration += 210;
270
271   return duration;
272 }
273
274
275 unsigned int XMPProtocol::calculateChecksumOne()
276 {
277   // Start with the value 0xF:
278   unsigned int total = 0xF;
279
280   // Add the other half-bytes in the first part of the frame:
281   total += subDeviceOne;
282   total += subDeviceTwo;
283   total += 0xF;
284   total += oemCode >> 4;
285   total += oemCode & 0x0F;
286   total += deviceCode >> 4;
287   total += deviceCode & 0x0F;
288
289   // Next, invert:
290   total = -total;
291
292   // Finally, mod 0x10:
293   total = total % 0x10;
294
295   return total;
296 }
297
298
299 unsigned int XMPProtocol::calculateChecksumTwo(
300   unsigned int toggle,
301   const CommandSequence &firstCode,
302   const CommandSequence &secondCode)
303 {
304   // Start with the value 0xF:
305   unsigned int total = 0xF;
306
307   // Add the other half-bytes in the second part of the frame:
308   total += subDeviceOne;
309   total += toggle;
310   total += subDeviceTwo;
311
312   unsigned int codeValue = 0;
313   CommandSequence::const_iterator i = firstCode.begin();
314
315   while (i != firstCode.end())
316   {
317     // Shift codeValue over and add the bit:
318     codeValue = codeValue << 1;
319     codeValue += *i;
320     ++i;
321   }
322
323   total += codeValue >> 4;
324   total += codeValue & 0xF;
325
326   codeValue = 0;
327   i = secondCode.begin();
328
329   while (i != secondCode.end())
330   {
331     codeValue = codeValue << 1;
332     codeValue += *i;
333     ++i;
334   }
335
336   total += codeValue >> 4;
337   total += codeValue & 0xF;
338
339   // Next, invert:
340   total = -total;
341
342   // Finally, mod 0x10:
343   total = total % 0x10;
344
345   return total;
346 }
347
348
349 int XMPProtocol::pushHalfByte(
350   unsigned int halfByte,
351   PIRRX51Hardware &rx51device)
352 {
353   unsigned int space = 760 + (136 * halfByte);
354   rx51device.addPair(210, space);
355
356   return (210 + space);
357 }
358
359
360 int XMPProtocol::pushFullByte(
361   unsigned int fullByte,
362   PIRRX51Hardware &rx51device)
363 {
364   unsigned int firstSpace = 760 + (136 * (fullByte >> 4));
365   unsigned int secondSpace = 760 + (136 * (fullByte & 0xF));
366
367   rx51device.addPair(210, firstSpace);
368   rx51device.addPair(210, secondSpace);
369
370   return (420 + firstSpace + secondSpace);
371 }
372
373
374 int XMPProtocol::pushBits(
375   const CommandSequence &bits,
376   PIRRX51Hardware &rx51device)
377 {
378   unsigned int duration = 0;
379
380   // We can only sent 4-bit values in XMP, so need to collect bits up into
381   // bundles of 4:
382
383   unsigned int bitsValue = 0;
384   int count = 0;
385   CommandSequence::const_iterator i = bits.begin();
386
387   while (i != bits.end())
388   {
389     if (count < 4)
390     {
391       bitsValue = bitsValue << 1;
392       bitsValue += *i;
393       ++count;
394     }
395     else
396     {
397       rx51device.addPair(210, 760 + (136 * bitsValue));
398       duration += 970 + (136 * bitsValue);
399
400       count = 1;
401       bitsValue = *i;
402     }
403
404     ++i;
405   }
406
407   if (count == 4)
408   {
409     rx51device.addPair(210, 760 + (136 * bitsValue));
410     duration += 970 + (136 * bitsValue);
411   }
412
413   return duration;
414 }