Advanced Settings Panel
[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       QMutexLocker cifLocker(&commandIFMutex);
70       commandInFlight = false;
71       return;
72 //      std::string s = "Tried to send a non-existent command.\n";
73 //      throw PIRException(s);
74     }
75
76     // construct the device:
77     PIRRX51Hardware rx51device(carrierFrequency, dutyCycle);
78
79     int repeatCount = 0;
80     int commandDuration = 0;
81     while (repeatCount < MAX_REPEAT_COUNT)
82     {
83       if (repeatCount)
84       {
85         commandDuration = generateRepeatCommand(i->second, rx51device);
86       }
87       else
88       {
89         commandDuration = generateStandardCommand(i->second, rx51device);
90       }
91
92       // Now, tell the device to send the whole command:
93       rx51device.sendCommandToDevice();
94
95       // sleep until the next repetition of command:
96       sleepUntilRepeat(commandDuration);
97
98       // Check whether we've reached the minimum required number of repetitons:
99       if (repeatCount >= minimumRepetitions)
100       {
101         // Check whether we've been asked to stop:
102         if (checkRepeatFlag())
103         {
104           // Do we need to send out a final frame?
105           if (hasFinalFrame)
106           {
107             commandDuration = generateFinalCommand(i->second, rx51device);
108             rx51device.sendCommandToDevice();
109             sleepUntilRepeat(commandDuration);
110           }
111
112           break;
113 /*
114           QMutexLocker cifLocker(&commandIFMutex);
115           commandInFlight = false;
116           return;
117 */
118         }
119       }
120
121       ++repeatCount;
122     }
123
124     QMutexLocker cifLocker(&commandIFMutex);
125     commandInFlight = false;
126   }
127   catch (PIRException e)
128   {
129     // inform the gui:
130     emit commandFailed(e.getError().c_str());
131   }
132 }
133
134
135 int XMPProtocol::generateStandardCommand(
136   const PIRKeyBits &pkb,
137   PIRRX51Hardware &rx51device)
138 {
139   int duration = 0;
140
141   // XMP frames have the following structure:
142   // 1) The first 4 bits of the "sub-device" code
143   // 2) A four-bit checksum value
144   // 3) The second 4 bits of the "sub-device" code
145   // 4) The four-bit value 0xF
146   // 5) An eight-bit OEM code (normally 0x44)
147   // 6) An eight-bit device code
148   // 7) a 210 usec pulse, 13800 usec space divider
149   // 8) The first 4 bits of the "sub-device" code (again)
150   // 9) Another four-bit checksum value
151   // 10) The four-bit toggle value
152   // 11) The second 4 bits of the "sub-device" code (again)
153   // 12) A pair of 8-bit command codes (often one of them will be 0)
154   // All of this is sent in MSB order.
155   // The checksums are constructed by adding up all the half-bytes in
156   // their side of the frame to 15, taking the complement, and modding the
157   // result with 16.
158
159   duration += pushHalfByte(subDeviceOne, rx51device);
160   duration += pushHalfByte(calculateChecksumOne(), rx51device);
161   duration += pushHalfByte(subDeviceTwo, rx51device);
162   duration += pushHalfByte(0xF, rx51device);
163   duration += pushFullByte(oemCode, rx51device);
164   duration += pushFullByte(deviceCode, rx51device);
165
166   rx51device.addPair(210, 13800);
167   duration += 14010;
168
169   duration += pushHalfByte(subDeviceOne, rx51device);
170   duration += pushHalfByte(
171     calculateChecksumTwo(0x0, pkb.firstCode, pkb.secondCode),
172     rx51device);
173   duration += pushHalfByte(0x0, rx51device);
174   duration += pushHalfByte(subDeviceTwo, rx51device);
175   duration += pushBits(pkb.firstCode, rx51device);
176   duration += pushBits(pkb.secondCode, rx51device);
177
178   // Finally add the "trail":
179   rx51device.addSingle(210);
180   duration += 210;
181
182   return duration;
183 }
184
185
186 int XMPProtocol::generateRepeatCommand(
187   const PIRKeyBits &pkb,
188   PIRRX51Hardware &rx51device)
189 {
190   int duration = 0;
191
192   // an XMP repeat frame is identical to the start frame, except that
193   // the "toggle" value is now 8.
194
195   duration += pushHalfByte(subDeviceOne, rx51device);
196   duration += pushHalfByte(calculateChecksumOne(), rx51device);
197   duration += pushHalfByte(subDeviceTwo, rx51device);
198   duration += pushHalfByte(0xF, rx51device);
199   duration += pushFullByte(oemCode, rx51device);
200   duration += pushFullByte(deviceCode, rx51device);
201
202   rx51device.addPair(210, 13800);
203   duration += 14010;
204
205   duration += pushHalfByte(subDeviceOne, rx51device);
206   duration += pushHalfByte(
207     calculateChecksumTwo(0x8, pkb.firstCode, pkb.secondCode),
208     rx51device);
209   duration += pushHalfByte(0x8, rx51device);
210   duration += pushHalfByte(subDeviceTwo, rx51device);
211   duration += pushBits(pkb.firstCode, rx51device);
212   duration += pushBits(pkb.secondCode, rx51device);
213
214   // Finally add the "trail":
215   rx51device.addSingle(210);
216   duration += 210;
217
218   return duration;
219 }
220
221
222 int XMPProtocol::generateFinalCommand(
223   const PIRKeyBits &pkb,
224   PIRRX51Hardware &rx51device)
225 {
226   int duration = 0;
227
228   // an XMP final frame is basically a pair of repeat frames, but the
229   // gap between them is only 13800 usec, and the "toggle" value of the
230   // second frame is 9.
231
232   duration += pushHalfByte(subDeviceOne, rx51device);
233   duration += pushHalfByte(calculateChecksumOne(), rx51device);
234   duration += pushHalfByte(subDeviceTwo, rx51device);
235   duration += pushHalfByte(0xF, rx51device);
236   duration += pushFullByte(oemCode, rx51device);
237   duration += pushFullByte(deviceCode, rx51device);
238
239   rx51device.addPair(210, 13800);
240   duration += 14010;
241
242   duration += pushHalfByte(subDeviceOne, rx51device);
243   duration += pushHalfByte(
244     calculateChecksumTwo(0x8, pkb.firstCode, pkb.secondCode),
245     rx51device);
246   duration += pushHalfByte(0x8, rx51device);
247   duration += pushHalfByte(subDeviceTwo, rx51device);
248   duration += pushBits(pkb.firstCode, rx51device);
249   duration += pushBits(pkb.secondCode, rx51device);
250
251   rx51device.addPair(210, 13800);
252   duration += 14010;
253
254   duration += pushHalfByte(subDeviceOne, rx51device);
255   duration += pushHalfByte(calculateChecksumOne(), rx51device);
256   duration += pushHalfByte(subDeviceTwo, rx51device);
257   duration += pushHalfByte(0xF, rx51device);
258   duration += pushFullByte(oemCode, rx51device);
259   duration += pushFullByte(deviceCode, rx51device);
260
261   rx51device.addPair(210, 13800);
262   duration += 14010;
263
264   duration += pushHalfByte(subDeviceOne, rx51device);
265   duration += pushHalfByte(
266     calculateChecksumTwo(0x9, pkb.firstCode, pkb.secondCode),
267     rx51device);
268   duration += pushHalfByte(0x9, rx51device);
269   duration += pushHalfByte(subDeviceTwo, rx51device);
270   duration += pushBits(pkb.firstCode, rx51device);
271   duration += pushBits(pkb.secondCode, rx51device);
272
273   // Finally add the "trail":
274   rx51device.addSingle(210);
275   duration += 210;
276
277   return duration;
278 }
279
280
281 unsigned int XMPProtocol::calculateChecksumOne()
282 {
283   // Start with the value 0xF:
284   unsigned int total = 0xF;
285
286   // Add the other half-bytes in the first part of the frame:
287   total += subDeviceOne;
288   total += subDeviceTwo;
289   total += 0xF;
290   total += oemCode >> 4;
291   total += oemCode & 0x0F;
292   total += deviceCode >> 4;
293   total += deviceCode & 0x0F;
294
295   // Next, invert:
296   total = -total;
297
298   // Finally, mod 0x10:
299   total = total % 0x10;
300
301   return total;
302 }
303
304
305 unsigned int XMPProtocol::calculateChecksumTwo(
306   unsigned int toggle,
307   const CommandSequence &firstCode,
308   const CommandSequence &secondCode)
309 {
310   // Start with the value 0xF:
311   unsigned int total = 0xF;
312
313   // Add the other half-bytes in the second part of the frame:
314   total += subDeviceOne;
315   total += toggle;
316   total += subDeviceTwo;
317
318   unsigned int codeValue = 0;
319   CommandSequence::const_iterator i = firstCode.begin();
320
321   while (i != firstCode.end())
322   {
323     // Shift codeValue over and add the bit:
324     codeValue = codeValue << 1;
325     codeValue += *i;
326     ++i;
327   }
328
329   total += codeValue >> 4;
330   total += codeValue & 0xF;
331
332   codeValue = 0;
333   i = secondCode.begin();
334
335   while (i != secondCode.end())
336   {
337     codeValue = codeValue << 1;
338     codeValue += *i;
339     ++i;
340   }
341
342   total += codeValue >> 4;
343   total += codeValue & 0xF;
344
345   // Next, invert:
346   total = -total;
347
348   // Finally, mod 0x10:
349   total = total % 0x10;
350
351   return total;
352 }
353
354
355 int XMPProtocol::pushHalfByte(
356   unsigned int halfByte,
357   PIRRX51Hardware &rx51device)
358 {
359   unsigned int space = 760 + (136 * halfByte);
360   rx51device.addPair(210, space);
361
362   return (210 + space);
363 }
364
365
366 int XMPProtocol::pushFullByte(
367   unsigned int fullByte,
368   PIRRX51Hardware &rx51device)
369 {
370   unsigned int firstSpace = 760 + (136 * (fullByte >> 4));
371   unsigned int secondSpace = 760 + (136 * (fullByte & 0xF));
372
373   rx51device.addPair(210, firstSpace);
374   rx51device.addPair(210, secondSpace);
375
376   return (420 + firstSpace + secondSpace);
377 }
378
379
380 int XMPProtocol::pushBits(
381   const CommandSequence &bits,
382   PIRRX51Hardware &rx51device)
383 {
384   unsigned int duration = 0;
385
386   // We can only sent 4-bit values in XMP, so need to collect bits up into
387   // bundles of 4:
388
389   unsigned int bitsValue = 0;
390   int count = 0;
391   CommandSequence::const_iterator i = bits.begin();
392
393   while (i != bits.end())
394   {
395     if (count < 4)
396     {
397       bitsValue = bitsValue << 1;
398       bitsValue += *i;
399       ++count;
400     }
401     else
402     {
403       rx51device.addPair(210, 760 + (136 * bitsValue));
404       duration += 970 + (136 * bitsValue);
405
406       count = 1;
407       bitsValue = *i;
408     }
409
410     ++i;
411   }
412
413   if (count == 4)
414   {
415     rx51device.addPair(210, 760 + (136 * bitsValue));
416     duration += 970 + (136 * bitsValue);
417   }
418
419   return duration;
420 }