Skip to content

Commit f36a620

Browse files
author
Patrick Bechon
committed
Add support for COBS-encoding, no backward compatible with previous versions
1 parent df2d7d5 commit f36a620

2 files changed

Lines changed: 130 additions & 79 deletions

File tree

src/KerbalSimpit.cpp

Lines changed: 99 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
KerbalSimpit::KerbalSimpit(Stream &serial)
77
{
88
_serial = &serial;
9+
packetDroppedNbr = 0;
910
}
1011

1112
bool KerbalSimpit::init()
@@ -25,7 +26,6 @@ bool KerbalSimpit::init()
2526
_outboundBuffer[i+1] = KERBALSIMPIT_VERSION[i];
2627
}
2728
i = i + 1;
28-
_receiveState = WaitingFirstByte;
2929
_send(0x00, _outboundBuffer, i); // Send SYN
3030

3131
// Wait for an answer. If no answer in 1 sec, return false
@@ -38,26 +38,28 @@ bool KerbalSimpit::init()
3838
}
3939
}
4040

41-
// Read all the bytes available and look for the SYNACK
41+
_receivedIndex = 0;
4242
while(_serial->available()){
43-
if (_serial->read() == 0xAA) { // First byte of header
44-
while (!_serial->available()); // Wait for a next byte
45-
if (_serial->read() == 0x50) { // Second byte of header
46-
while (!_serial->available()); // Wait for a next byte
47-
_serial->read(); // size
48-
while (!_serial->available()); // Wait for a next byte
49-
if (_serial->read() == 0x00) { // type
50-
while (!_serial->available()); // Wait for a next byte
51-
if (_serial->read() == 0x01) { // first byte of payload, we got a SYNACK
52-
// TODO: Do we care about tracking handshake state?
53-
_outboundBuffer[0] = 0x02;
54-
_send(0x00, _outboundBuffer, i); // Send ACK
55-
return true;
56-
}
57-
}
58-
}
43+
_inboundBuffer[_receivedIndex] = _serial->read();
44+
if(!_inboundBuffer[_receivedIndex]){
45+
cobsDecode(_inboundBuffer, _receivedIndex + 1, _inboundDecodedBuffer);
46+
_receivedIndex = 0;
47+
48+
// Test is the message received is a SYNACK
49+
if(_inboundDecodedBuffer[0] == SYNC_MESSAGE && _inboundDecodedBuffer[1] == 0x01){
50+
_outboundBuffer[0] = 0x02;
51+
_send(0x00, _outboundBuffer, i); // Send ACK
52+
53+
// Log the serial buffer size
54+
printToKSP("Buffer receive size : " + String(SERIAL_RX_BUFFER_SIZE));
55+
return true;
56+
} else {
57+
return false;
58+
}
5959
}
60+
_receivedIndex ++;
6061
}
62+
6163
return false;
6264
}
6365

@@ -109,59 +111,93 @@ void KerbalSimpit::printToKSP(String msg, byte options){
109111

110112
void KerbalSimpit::_send(byte messageType, byte msg[], byte msgSize)
111113
{
112-
_serial->write(0xAA);
113-
_serial->write(0x50);
114-
_serial->write(msgSize);
115-
_serial->write(messageType);
116-
for (int x=0; x<msgSize; x++) {
117-
_serial->write(*(msg+x));
114+
_msgBuffer[0] = messageType;
115+
byte checksum = messageType;
116+
for(byte i = 0; i < msgSize; i++){
117+
_msgBuffer[i+1] = msg[i];
118+
checksum ^= msg[i];
118119
}
120+
_msgBuffer[msgSize+1] = checksum;
121+
122+
byte encodedMsgSize = cobsEncode(_msgBuffer, msgSize + 2, _encodedBuffer);
123+
for (byte x=0; x<encodedMsgSize; x++) {
124+
_serial->write(_encodedBuffer[x]);
125+
}
126+
_serial->write((byte) 0); // Encoded buffer does not have the null terminating byte.
119127
}
120128

121129
void KerbalSimpit::update()
122130
{
123-
while (_serial->available()) {
124-
_readBuffer = _serial->read();
125-
switch (_receiveState) {
126-
case WaitingFirstByte:
127-
if (_readBuffer == 0xAA) {
128-
_receiveState = WaitingSecondByte;
129-
} else {
130-
_receiveState = WaitingFirstByte;
131-
}
132-
break;
133-
case WaitingSecondByte:
134-
if (_readBuffer == 0x50) {
135-
_receiveState = WaitingSize;
136-
} else {
137-
_receiveState = WaitingFirstByte;
138-
}
139-
break;
140-
case WaitingSize:
141-
if (_readBuffer > MAX_PAYLOAD_SIZE) {
142-
_receiveState = WaitingFirstByte;
131+
while(_serial->available()){
132+
_inboundBuffer[_receivedIndex] = _serial->read();
133+
_receivedIndex ++;
134+
135+
if(!_inboundBuffer[_receivedIndex - 1]){
136+
byte decodedSize = cobsDecode(_inboundBuffer, _receivedIndex + 1, _inboundDecodedBuffer);
137+
138+
// Account for the overhead of 1 byte of COBS
139+
if(decodedSize != _receivedIndex - 1){
140+
// Ill-formed packet
141+
packetDroppedNbr ++;
143142
} else {
144-
_inboundSize = _readBuffer;
145-
_receiveState = WaitingType;
143+
// Check checksum
144+
byte checksum = 0;
145+
for(byte x = 0; x < decodedSize - 2; x++){
146+
checksum ^= _inboundDecodedBuffer[x];
147+
}
148+
if(checksum != _inboundDecodedBuffer[decodedSize-2]){
149+
// Discard message, bad checksum
150+
packetDroppedNbr ++;
151+
} else {
152+
_messageHandler(_inboundDecodedBuffer[0], _inboundDecodedBuffer + 1, decodedSize - 3);
153+
}
146154
}
147-
break;
148-
case WaitingType:
149-
_inboundType = _readBuffer;
150155
_receivedIndex = 0;
151-
_receiveState = WaitingData;
152-
break;
153-
case WaitingData:
154-
_inboundBuffer[_receivedIndex] = _readBuffer;
155-
_receivedIndex++;
156-
if (_receivedIndex == _inboundSize) {
157-
_receiveState = WaitingFirstByte;
158-
if (_messageHandler != NULL) {
159-
_messageHandler(_inboundType, _inboundBuffer, _inboundSize);
160-
}
161-
}
162-
break;
163-
default:
164-
_receiveState = WaitingFirstByte;
165156
}
166157
}
167158
}
159+
160+
size_t KerbalSimpit::cobsEncode(const void *data, size_t length, uint8_t *buffer)
161+
{
162+
uint8_t *encode = buffer; // Encoded byte pointer
163+
uint8_t *codep = encode++; // Output code pointer
164+
uint8_t code = 1; // Code value
165+
166+
for (const uint8_t *byte = (const uint8_t *)data; length--; ++byte)
167+
{
168+
if (*byte) // Byte not zero, write it
169+
*encode++ = *byte, ++code;
170+
171+
if (!*byte || code == 0xff) // Input is zero or block completed, restart
172+
{
173+
*codep = code, code = 1, codep = encode;
174+
if (!*byte || length)
175+
++encode;
176+
}
177+
}
178+
*codep = code; // Write final code value
179+
180+
return (size_t)(encode - buffer);
181+
}
182+
183+
size_t KerbalSimpit::cobsDecode(const uint8_t *buffer, size_t length, void *data)
184+
{
185+
const uint8_t *byte = buffer; // Encoded input byte pointer
186+
uint8_t *decode = (uint8_t *)data; // Decoded output byte pointer
187+
188+
for (uint8_t code = 0xff, block = 0; byte < buffer + length; --block)
189+
{
190+
if (block) // Decode block byte
191+
*decode++ = *byte++;
192+
else
193+
{
194+
if (code != 0xff) // Encoded zero, write it
195+
*decode++ = 0;
196+
block = code = *byte++; // Next block length
197+
if (!code) // Delimiter code found
198+
break;
199+
}
200+
}
201+
202+
return (size_t)(decode - (uint8_t *)data);
203+
}

src/KerbalSimpit.h

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
#include "PayloadStructs.h"
1111

1212
const char KERBALSIMPIT_VERSION[] = "1.3.0"; /**< Library version sent to the plugin for compatibility checking. */
13-
const byte MAX_PAYLOAD_SIZE = 32; /**< Maximum payload size does not include header. */
13+
const byte MAX_PAYLOAD_SIZE = 32; /**< Maximum payload size does not include header. Header is 4 chars. */
1414

1515
/** The KerbalSimpit class manages a serial connection to KSP.
1616
It automates the handshaking process, and provides utility
@@ -138,24 +138,20 @@ class KerbalSimpit
138138
void printToKSP(String msg);
139139
void printToKSP(String msg, byte options);
140140

141+
/**
142+
* Number of message dropped due to corrupted packet (missing data or bad checksum).
143+
* This may not be accurate in all cases, some packets can be lost without being noticed here in case of extreme congestion.
144+
*/
145+
unsigned int packetDroppedNbr;
146+
141147
private:
142-
byte _readBuffer;
143-
byte _inboundType;
144-
byte _inboundSize;
145-
byte _inboundBuffer[MAX_PAYLOAD_SIZE];
146-
byte _outboundBuffer[MAX_PAYLOAD_SIZE];
147-
byte _outboundSize;
148+
byte _inboundBuffer[MAX_PAYLOAD_SIZE + 4]; // used to store incoming data
149+
byte _inboundDecodedBuffer[MAX_PAYLOAD_SIZE + 4]; // used to store decoded incoming data.
150+
byte _outboundBuffer[MAX_PAYLOAD_SIZE]; // used to store the outbound message for initital handshake
151+
byte _msgBuffer[MAX_PAYLOAD_SIZE + 2]; // used to build the message before sending it (adding type, checksum)
152+
byte _encodedBuffer[MAX_PAYLOAD_SIZE + 4]; // used to encode message before sending it
148153
Stream *_serial;
149154

150-
enum ReceiveState_t
151-
{
152-
WaitingFirstByte,
153-
WaitingSecondByte,
154-
WaitingSize,
155-
WaitingType,
156-
WaitingData,
157-
};
158-
ReceiveState_t _receiveState;
159155
byte _receivedIndex;
160156

161157
/** Callback function to handle messages from the plugin.
@@ -176,6 +172,25 @@ class KerbalSimpit
176172
@param msgSize The size of msg.
177173
*/
178174
void _send(byte messageType, byte msg[], byte msgSize);
175+
176+
/** COBS decode data from buffer. Taken from Wikipedia : https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing
177+
@param buffer Pointer to encoded input bytes
178+
@param length Number of bytes to decode
179+
@param data Pointer to decoded output data
180+
@return Number of bytes successfully decoded
181+
@note Stops decoding if delimiter byte is found
182+
*/
183+
size_t cobsDecode(const uint8_t *buffer, size_t length, void *data);
184+
185+
/** COBS encode data to buffer. Taken from Wikipedia : https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing
186+
@param data Pointer to input data to encode
187+
@param length Number of bytes to encode
188+
@param buffer Pointer to encoded output buffer
189+
@return Encoded buffer length in bytes
190+
@note Does not output delimiter byte
191+
*/
192+
size_t cobsEncode(const void *data, size_t length, uint8_t *buffer);
179193
};
180194

195+
181196
#endif

0 commit comments

Comments
 (0)