1 /*
2 * The contents of this file are subject to the Mozilla Public License
3 * Version 1.0 (the "License"); you may not use this file except in
4 * compliance with the License. You may obtain a copy of the License at
5 * http://www.mozilla.org/MPL/
6 *
7 * Software distributed under the License is distributed on an "AS IS"
8 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
9 * License for the specific language governing rights and limitations
10 * under the License.
11 *
12 * The Initial Developer of this code is David Baum.
13 * Portions created by David Baum are Copyright (C) 1998 David Baum.
14 * All Rights Reserved.
15 *
16 * Portions created by John Hansen are Copyright (C) 2005 John Hansen.
17 * All Rights Reserved.
18 *
19 */
20
21 #include <cstring>
22 #include <cstdio>
23 #include <cstdlib>
24
25 #include "RCX_PipeTransport.h"
26 #include "RCX_Link.h"
27 #include "RCX_SerialPipe.h"
28
29 using std::printf;
30
31 #ifdef DEBUG
32 #define DEBUG_TIMEOUT
33 #endif
34
35 #define kMaxTxData (2*RCX_Link::kMaxCmdLength + 6)
36 #define kMaxRxData (kMaxTxData + 2*RCX_Link::kMaxReplyLength + 5)
37
38 #define kDefaultRetryCount 4
39
40 static UByte cmSync[] = { 1, 0xff };
41 static UByte rcxSync[] = { 3, 0x55, 0xff, 0x00 };
42 static UByte rcxNo55Sync[] = { 2, 0xff, 0x00 };
43 static UByte spyboticsSync[] = {1, 0x98 };
44
45 static int FindSync(const UByte *data, int length, const UByte *sync, const UByte cmd);
46 static UByte ComputeChecksum(UByte dataSum, RCX_TargetType targetType);
47
48 // receive states
49 enum
50 {
51 kReplyState = 0, // state when rx buffer used for replies
52 kIdleState,
53 kSync1State,
54 kSync2State,
55 kDataState
56 };
57
58
RCX_PipeTransport(RCX_Pipe * pipe)59 RCX_PipeTransport::RCX_PipeTransport(RCX_Pipe *pipe) : fPipe(pipe)
60 {
61 fTxData = new UByte[kMaxTxData];
62 fRxData = new UByte[kMaxRxData];
63 fVerbose = false;
64 fTxLastCommand = 0;
65 fFastMode = false;
66 fOmitHeader = false;
67 }
68
69
~RCX_PipeTransport()70 RCX_PipeTransport::~RCX_PipeTransport()
71 {
72 Close();
73 delete [] fTxData;
74 delete [] fRxData;
75 delete fPipe;
76 }
77
78
Open(RCX_TargetType target,const char * deviceName,ULong options)79 RCX_Result RCX_PipeTransport::Open(RCX_TargetType target, const char *deviceName, ULong options)
80 {
81 RCX_Result err;
82
83 fTarget = target;
84 fComplementData = true; // default
85
86 int mode;
87 switch(fTarget)
88 {
89 case kRCX_CMTarget:
90 mode = RCX_Pipe::kCyberMasterMode;
91 fSync = cmSync;
92 break;
93 case kRCX_SpyboticsTarget:
94 mode = RCX_Pipe::kSpyboticsMode;
95 fSync = spyboticsSync;
96 fComplementData = false;
97 break;
98 default:
99 mode = RCX_Pipe::kNormalIrMode;
100 fSync = (fPipe->GetCapabilities() & RCX_Pipe::kAbsorb55Flag) ? rcxNo55Sync : rcxSync;
101 break;
102 }
103
104 err = fPipe->Open(deviceName, mode);
105 if (RCX_ERROR(err)) return err;
106
107 fVerbose = (options & RCX_Link::kVerboseMode);
108
109 fRxTimeout = (options & RCX_Link::kRxTimeoutMask);
110 if (fRxTimeout==0) fRxTimeout = kMaxTimeout;
111
112 fDynamicTimeout = false;
113
114 fRxState = kReplyState;
115 return kRCX_OK;
116 }
117
118
Close()119 void RCX_PipeTransport::Close()
120 {
121 fPipe->Close();
122 }
123
124
FastModeSupported() const125 bool RCX_PipeTransport::FastModeSupported() const
126 {
127 return (fPipe->GetCapabilities() & RCX_Pipe::kFastIrMode);
128 }
129
130
SetFastMode(bool fast)131 void RCX_PipeTransport::SetFastMode(bool fast)
132 {
133 if (fast == fFastMode) return;
134
135 if (fast)
136 {
137 fComplementData = false;
138 fPipe->SetMode(RCX_Pipe::kFastIrMode);
139 }
140 else
141 {
142 fComplementData = (fTarget != kRCX_SpyboticsTarget);
143 fPipe->SetMode(RCX_Pipe::kNormalIrMode);
144 }
145
146 fFastMode = fast;
147 }
148
149
150
Send(const UByte * txData,int txLength,UByte * rxData,int rxExpected,int rxMax,bool retry,int timeout)151 RCX_Result RCX_PipeTransport::Send(const UByte *txData, int txLength, UByte *rxData, int rxExpected, int rxMax, bool retry, int timeout)
152 {
153 RCX_Result result;
154
155 // format the command
156 BuildTxData(txData, txLength, retry);
157
158 // try sending
159 int tries = retry ? kDefaultRetryCount : 1;
160 int originalTimeout = fRxTimeout;
161
162 for(int i=0; i<tries; i++)
163 {
164 SendFromTxBuffer(fFastMode ? 100 : 0);
165
166 // if no reply is expected, we can just return now (no retries, no errors, etc)
167 if (!rxExpected) return kRCX_OK;
168
169 int tmpTimeout;
170 if (timeout > 0)
171 tmpTimeout = timeout;
172 else
173 tmpTimeout = fRxTimeout;
174
175
176 int replyOffset;
177 result = ReceiveReply(rxExpected, tmpTimeout, replyOffset);
178 if (fDynamicTimeout) AdjustTimeout(result, i);
179
180 if (!RCX_ERROR(result))
181 {
182 if (rxData)
183 {
184 int length = result+1;
185 if (length > rxMax) length = rxMax;
186 CopyReply(rxData, replyOffset, length);
187 }
188
189 return result;
190 }
191
192 // only the second kRCX_IREchoError is catastrophic
193 // this is somewhat of a hack - I really should keep track
194 // of the echo, but for now this makes sure that a serial
195 // level failure on a single packet doesn't kill the entire
196 // send
197 if (result == kRCX_IREchoError && i > 0) break;
198 if (fVerbose)
199 {
200 printf("Retrying...\n");
201 }
202 }
203
204 if (retry)
205 {
206 // retries exceeded, restore original timeout and lose the sync
207 if (fDynamicTimeout)
208 fRxTimeout = originalTimeout;
209 fSynced = false;
210 }
211
212 return result;
213 }
214
215
BuildTxData(const UByte * data,int length,bool duplicateReduction)216 void RCX_PipeTransport::BuildTxData(const UByte *data, int length, bool duplicateReduction)
217 {
218 int i;
219 UByte dataSum = 0;
220 UByte byte;
221 UByte *ptr = fTxData;
222
223 if (fTarget == kRCX_CMTarget)
224 {
225 // CM sync pattern
226 *ptr++ = 0xfe;
227 *ptr++ = 0x00;
228 *ptr++ = 0x00;
229 *ptr++ = 0xff;
230 }
231 else if (fTarget == kRCX_SpyboticsTarget)
232 {
233 *ptr++ = 0x98;
234 }
235 else if (fFastMode)
236 {
237 *ptr++ = 0xff;
238 }
239 else
240 {
241 if (!fOmitHeader)
242 {
243 // RCX sync pattern
244 *ptr++ = 0x55;
245 *ptr++ = 0xff;
246 *ptr++ = 0x00;
247 }
248 }
249
250 // interleaved data & inverse data
251 for(i=0; i<length; i++)
252 {
253 byte = *data++;
254
255 if (i==0)
256 {
257 if (duplicateReduction && byte==fTxLastCommand)
258 byte ^= 8;
259 fTxLastCommand = byte;
260 }
261
262
263 *ptr++ = byte;
264 if (fComplementData)
265 *ptr++ = (UByte)~byte;
266 dataSum += byte;
267 }
268
269 UByte checksum = ComputeChecksum(dataSum, fTarget);
270
271 // checksum
272 *ptr++ = checksum;
273 if (fComplementData)
274 *ptr++ = (UByte)~checksum;
275
276 fTxLength = ptr - fTxData;
277 }
278
279
SendFromTxBuffer(int delay)280 void RCX_PipeTransport::SendFromTxBuffer(int delay)
281 {
282 // drain serial rx buffer
283 fPipe->FlushRead(delay);
284
285 // send command
286 fPipe->Write(fTxData, fTxLength);
287 if (fVerbose)
288 {
289 printf("Tx: ");
290 DumpData(fTxData, fTxLength);
291 }
292 }
293
294
295
ReceiveReply(int rxExpected,int timeout,int & replyOffset)296 RCX_Result RCX_PipeTransport::ReceiveReply(int rxExpected, int timeout, int &replyOffset)
297 {
298 int receiveLen = ExpectedReceiveLen(rxExpected);
299 if (!((fTarget == kRCX_SpyboticsTarget) || fPipe->IsUSB()))
300 {
301 receiveLen += fTxLength; // serial tower echoes the sent bytes
302 }
303
304 // get the reply
305 fRxState = kReplyState;
306 fRxLength = 0;
307
308 int length = 0;
309 bool bFirstRead = true;
310 while(fRxLength < kMaxRxData)
311 {
312 if (bFirstRead)
313 {
314 bFirstRead = false;
315 if (fVerbose) printf("reading %ld bytes, timeout = %ld\n", receiveLen, timeout);
316 int bytesRead = fPipe->Read(fRxData+fRxLength, receiveLen, timeout);
317 if (bytesRead == 0) break;
318 fRxLength += bytesRead;
319 }
320 else
321 {
322 if (fVerbose) printf("reading 1 byte, timeout = %ld\n", timeout);
323 if (fPipe->Read(fRxData+fRxLength, 1, timeout) != 1) break;
324 fRxLength++;
325
326 }
327 /*
328 if (fPipe->Read(fRxData+fRxLength, 1, timeout) != 1) break;
329 fRxLength++;
330
331 if (fRxLength < receiveLen) continue;
332 */
333 // check for replies
334 length = FindReply(rxExpected, replyOffset);
335 if (length == rxExpected) break;
336 }
337
338 if (fVerbose)
339 {
340 printf("Rx: ");
341 DumpData(fRxData, fRxLength);
342 }
343
344 if (fRxLength == 0 &&
345 (fPipe->GetCapabilities() & RCX_Pipe::kTxEchoFlag) &&
346 (fTarget!=kRCX_CMTarget))
347 return kRCX_IREchoError;
348
349 if (length == 0)
350 return kRCX_ReplyError;
351
352 return length - 1;
353 }
354
355
356 /* this function is now obsolete
357
358 int RCX_PipeTransport::ValidateRxData()
359 {
360 // must be an even number of bytes
361 if (fRxLength & 1) return 0;
362
363 // validate complements
364 int count = fRxLength / 2;
365 for(int i=0; i<count; ++i)
366 {
367 UByte d = fRxData[2*i];
368
369 if (d + fRxData[2*i+1] != 0xff) return 0;
370
371 fRxData[i] = d;
372 }
373
374 // compute checksum
375 --count;
376 UByte checksum = 0;
377 for(int i=0; i<count; ++i)
378 checksum += fRxData[i];
379
380 if (checksum != fRxData[count]) return 0;
381
382 return count;
383 }
384 */
385
386
FindReply(const int rxExpected,int & offset)387 int RCX_PipeTransport::FindReply(const int rxExpected, int &offset)
388 {
389 int length;
390
391 offset = 0;
392 while(1)
393 {
394 int start = FindSync(fRxData + offset, fRxLength - offset, fSync, fTxLastCommand);
395
396 if (start == 0) return 0;
397
398 offset += start;
399
400 length = VerifyReply(rxExpected, fRxData + offset, fRxLength - offset, fTxLastCommand);
401
402 if (length > 0) return length;
403 }
404 }
405
406
CopyReply(UByte * dst,int offset,RCX_Result length)407 void RCX_PipeTransport::CopyReply(UByte *dst, int offset, RCX_Result length)
408 {
409 const UByte *src = fRxData + offset;
410
411 while(length>0)
412 {
413 *dst++ = *src++;
414 if (fComplementData) src++;
415 length--;
416 }
417 }
418
419
AdjustTimeout(RCX_Result result,int attempt)420 void RCX_PipeTransport::AdjustTimeout(RCX_Result result, int attempt)
421 {
422 int newTimeout = fRxTimeout;
423
424 if (!RCX_ERROR(result) && attempt==0)
425 {
426 // worked on first try, lets see if we can go faster next time
427 newTimeout = fRxTimeout - (fRxTimeout / 10);
428 if (newTimeout < kMinTimeout)
429 newTimeout = kMinTimeout;
430 }
431 else if (RCX_ERROR(result) && attempt > 0)
432 {
433 // failed on try other than first - slow down
434 newTimeout *= 2;
435 if (newTimeout > kMaxTimeout) newTimeout = kMaxTimeout;
436 }
437
438 if (newTimeout != fRxTimeout)
439 {
440 fRxTimeout = newTimeout;
441 #ifdef DEBUG_TIMEOUT
442 printf("Timeout = %d\n", fRxTimeout);
443 #endif
444 }
445 }
446
447
Receive(UByte * data,int maxLength,bool)448 RCX_Result RCX_PipeTransport::Receive(UByte *data, int maxLength, bool /* echo */)
449 {
450 if (fRxState == kReplyState) fRxState = kIdleState;
451
452 while(true)
453 {
454 UByte b;
455
456 if (fPipe->Read(&b, 1, 100) != 1) return 0;
457
458 // if (echo) fPipe->Write(&b, 1);
459 ProcessRxByte(b);
460
461 if (fRxState == kDataState && fRxLength>3 && (fRxLength & 1)==0) {
462 // compute checksum
463 int count = fRxLength/2 - 1;
464 UByte checksum = 0;
465
466 for(int i=0; i<count; ++i)
467 {
468 checksum += fRxData[i*2];
469 }
470
471 if (fRxData[count*2] != checksum) continue;
472
473 if (count > maxLength) count = maxLength;
474 for(int i=0; i<count; ++i) {
475 data[i] = fRxData[i*2];
476 }
477
478 fRxState = kIdleState;
479 return count;
480 }
481 }
482 }
483
484
485
ProcessRxByte(UByte b)486 void RCX_PipeTransport::ProcessRxByte(UByte b)
487 {
488 int newState = fRxState;
489 bool reset = false;
490
491 if (fVerbose) {
492 DumpData(&b, 1);
493 }
494
495 switch(fRxState)
496 {
497 case kIdleState:
498 if (b == 0x55)
499 newState = kSync1State;
500 break;
501 case kSync1State:
502 if (b == 0xff)
503 newState = kSync2State;
504 else
505 reset = true;
506 break;
507 case kSync2State:
508 if (b == 0x00)
509 {
510 fRxLength = 0;
511 newState = kDataState;
512 }
513 else
514 reset = true;
515 break;
516 case kDataState:
517 // verify that odd bytes are complements of previous bytes
518 if ((fRxLength & 1) && (b + fRxData[fRxLength-1] != 0xff))
519 {
520 // complement failed
521 reset = true;
522 }
523 else if (fRxLength < kMaxRxData)
524 {
525 fRxData[fRxLength++] = b;
526 }
527 else
528 {
529 reset = true;
530 }
531 break;
532 }
533
534 if (reset) {
535 newState = kIdleState;
536
537 switch(b) {
538 case 0x55:
539 newState = kSync1State;
540 break;
541 case 0xff:
542 if (fRxState==kDataState && fRxLength > 0 && fRxData[fRxLength-1]==0x55) {
543 newState = kSync2State;
544 }
545 break;
546 }
547
548 }
549
550 fRxState = newState;
551 }
552
553
ExpectedReceiveLen(const int rxExpected)554 int RCX_PipeTransport::ExpectedReceiveLen(const int rxExpected)
555 {
556 int result = rxExpected;
557 if (fComplementData)
558 result *= 2;
559 if ((fFastMode && ((fTxLastCommand & 0xf7) == 0xa5)) || fComplementData)
560 result += 2;
561 else
562 result += 1;
563 // allow at least one byte for the header
564 result += 3; // 3 byte header
565 return result;
566 }
567
VerifyReply(const int rxExpected,const UByte * data,int length,UByte cmd)568 int RCX_PipeTransport::VerifyReply(const int rxExpected, const UByte *data, int length, UByte cmd)
569 {
570 UByte dataSum = data[0];
571 const UByte *ptr = data;
572 const UByte *match = nil;
573 int width = (fComplementData ? 2 : 1);
574 const UByte *end = data + length + 1 - width;
575
576 // this is a hack to work around the problem that the reply for
577 // opcode a5 has a complemented command byte even in fast mode
578 bool complementCmd;
579 if (fFastMode && ((cmd & 0xf7) == 0xa5))
580 complementCmd = true;
581 else
582 complementCmd = fComplementData;
583
584 // always need a cmd and a checksum
585 if (length < ((complementCmd ? 2 : 1) + width)) return 0;
586
587 // check the cmd
588 if ((*ptr & 0xf7) != (~cmd & 0xf7)) return 0;
589 ptr++;
590
591 if (complementCmd)
592 {
593 if ((*ptr & 0xf7) != (cmd & 0xf7)) return 0;
594 ptr++;
595 }
596
597 while(ptr < end)
598 {
599 if (fComplementData && ((ptr[0] & 0xf7) != (~ptr[1] & 0xf7))) break;
600
601 if (ptr[0] == ComputeChecksum(dataSum, fTarget)) match = ptr;
602
603 dataSum += ptr[0];
604 ptr += width;
605 }
606
607 // certain spybot responses have screwed-up checksums when
608 // communicating via USB tower
609 if (!match && (fTarget == kRCX_SpyboticsTarget) && fPipe->IsUSB())
610 {
611 if (length == rxExpected+1)
612 match = end - 1;
613 }
614 if (!match) return 0;
615
616 return ((match - data) / width);
617 }
618
619
ComputeChecksum(UByte dataSum,RCX_TargetType targetType)620 UByte ComputeChecksum(UByte dataSum, RCX_TargetType targetType)
621 {
622 if (targetType == kRCX_SpyboticsTarget)
623 {
624 // preamble + dataSum + checksum = 0
625 return -0x98 - dataSum;
626 }
627 else
628 {
629 // checksum = dataSum
630 return dataSum;
631 }
632 }
633
FindSync(const UByte * data,int length,const UByte * sync,const UByte cmd)634 int FindSync(const UByte *data, int length, const UByte *sync, const UByte cmd)
635 {
636 int syncLen = *sync++;
637 while (syncLen > 0)
638 {
639 const UByte *end = data + length - syncLen + 1;
640 const UByte *ptr;
641
642 for(ptr=data; ptr<end; ptr++)
643 {
644 int i;
645 for(i=0; i<syncLen; i++)
646 {
647 if (ptr[i] != sync[i]) break;
648 }
649 // check the next byte to see if it matches the command.
650 // if it doesn't then we haven't really found the sync
651 if ((i==syncLen) && ((ptr[syncLen] & 0xf7) == (~cmd & 0xf7)))
652 {
653 return ptr-data+syncLen;
654 }
655 }
656 sync++;
657 syncLen--;
658 }
659
660 return 0;
661 }
662