Last active
February 12, 2018 01:23
-
-
Save scott2b/67bdb6b0e7da8f154c979520adb98169 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Proof-of-concept for establishing design bassis for an extremely compact | |
* data transfer protocol for wireless data transmission | |
* | |
* The idea is to have a protocol that is flexible in that arbitrary data types | |
* can be passed without the wasted space that would be caused by a struct-based | |
* approach that would need to reserve more space than required for a given message | |
* | |
* This example uses a byte for each data point to determine its type. This seems | |
* wasteful to use up a full extra byte for each data type, however, as seen with | |
* Node 5 in the example below, the end goal is to have abstract types associated | |
* with sensor data. Thus we have a type-space of 255 possible sensor types which | |
* will carry semantic weight as well as deeper indication of how to parse out the | |
* subsequent bytes | |
* | |
* The general messaging format will look like: | |
* | |
* NODE_ID MSG_ID MSG_COUNT DATA_TYPE DATA DATA_TYPE DATA ... etc | |
* | |
* e.g. Air quality sample data point #100 from node 3 might look like: | |
* | |
* 3 100 1 AIR_QUALITY 20 1234567 | |
* | |
* where AIR_QAULITY is a constant in the type space, 20 is some meaningful value | |
* for the specified type and 1234567 is a bogus timestamp. The protocol would | |
* know to parse out the IDs, and the number of records, and then parse the | |
* remainder of the message according to the type and data of each record | |
* | |
* Or another example which does not require a timestamp: | |
* | |
* 3 101 BATTERY_LEVEL 39 | |
* | |
* Where the BATTERY_LEVEL data type is protocol-determined to be divided by 10 | |
* | |
* In reality, there will be multiple nodes each with multiple data points. So, | |
* for example: | |
* | |
* 2 1 3 BAT 39 GPS 3 42.04507 -87.68770 DUST 20 12345 3 1 1 BAT 42 | |
* | |
* Is a messages containing Node 2 data message #1, with battery, GPS, and | |
* air quality records (with the latter having a timestamp) and Node 3 having | |
* only a battery value to deliver | |
* | |
* The following example does some sanity checking on standard numerica data | |
* types, then includes a sensor node data set for Node 5 | |
* | |
**/ | |
#include <stdio.h> | |
#include <stdlib.h> | |
#define CHAR 1 | |
#define INT8 2 | |
#define UINT8 3 | |
#define INT16 4 | |
#define UINT16 5 | |
#define BATTERY 6 | |
#define GPS 7 | |
#define AIR_PARTICULATE 8 | |
/** Utilities for traversing the byte message */ | |
uint8_t next_8_bit(uint8_t* data, int len, int* index, uint8_t* val) | |
{ | |
*val = data[*index]; | |
*index += 1; | |
if (*index > len) { | |
printf("\nBAD BYTE SEQUENCE\n"); | |
return 0; | |
} | |
return 1; | |
} | |
uint8_t next_16_bit(uint8_t* data, int len, int* index, uint16_t* val) | |
{ | |
if (*index > len-2) { | |
printf("\nBAD BYTE SEQUENCE: Not enough data for 16-bit value\n"); | |
return 0; | |
} | |
*val = (data[*index] << 8) | (data[*index+1] & 0xff); | |
*index += 2; | |
return 1; | |
} | |
uint8_t next_32_bit(uint8_t* data, int len, int* index, uint32_t* val) | |
{ | |
if (*index > len-4) { | |
printf("\nBAD BYTE SEQUENCE: Not enough data for 32-bit value\n"); | |
return 0; | |
} | |
*val = (data[*index] << 24) | |
| (data[*index+1] << 16) | |
| (data[*index+2] << 8) | |
| (data[*index+3] & 0xff); | |
*index += 4; | |
return 1; | |
} | |
void printval8(uint8_t val, uint8_t data_type) | |
{ | |
if (data_type == CHAR) { | |
printf("%c ", (char)val); | |
} else if (data_type == INT8) { | |
printf("%d ", (int8_t)val); | |
} else if (data_type == UINT8) { | |
printf("%d ", val); | |
} | |
} | |
void printval16(uint16_t val, uint8_t data_type) | |
{ | |
if (data_type == INT16) { | |
printf("%d ", (int16_t)val); | |
} else if (data_type == UINT16) { | |
printf("%d ", val); | |
} | |
} | |
/** | |
* Floating points are not supported. Instead, types will be protocol defined | |
* to divide by a power of 10 according to required precision for that specific | |
* data type | |
*/ | |
/** | |
* This example byte sequence will result in human-readable print of: | |
* | |
* NODE ID: 3; MSG ID: 1; MSG COUNT: 8; MESSAGES: | |
* ! ~ A Z a z 0 255 | |
* | |
* NODE ID: 4; MSG ID: 1; MSG COUNT: 7; MESSAGES: | |
* -128 127 65535 0 32767 -32768 0 | |
* | |
* NODE ID: 5; MSG ID: 1; MSG COUNT: 3; MESSAGES: | |
* BAT: 3.9; SATS: 3; LAT: 42.04507; LON: -87.68770; DUST: 20; TIMESTAMP: 12345; | |
* | |
**/ | |
int main(int argc, char** argv) | |
{ | |
int32_t latitude = 4204507; | |
int32_t longitude = -8768770; | |
uint8_t example[] = { | |
// Node 3, some 8-bit testing | |
3, 1, 8, | |
CHAR, '!', | |
CHAR, '~', | |
CHAR, 'A', | |
CHAR, 'Z', | |
CHAR, 'a', | |
CHAR, 'z', | |
UINT8, 0, | |
UINT8, 255, | |
// Node 4, 8 and 16-bit testing | |
4, 1, 7, | |
INT8, -128, | |
INT8, 127, | |
UINT16, 65535 >> 8, 65535 & 0xff, | |
UINT16, 0 >> 8, 0 & 0xff, | |
INT16, 32767 >> 8, 32767 & 0xff, | |
INT16, -32768 >> 8, -32768 & 0xff, | |
INT16, 0 >> 8, 0 & 0xff, | |
// Node 5 starts to look like real sensor data | |
5, 1, 3, | |
BATTERY, 39, | |
GPS, 3, | |
latitude >> 24, latitude >> 16, | |
latitude >> 8, latitude & 0xff, | |
longitude >> 24, longitude >> 16, | |
longitude >> 8, longitude & 0xff, | |
AIR_PARTICULATE, 20, 12345 >> 8, 12345 & 0xff | |
}; | |
int len = sizeof(example); | |
uint8_t node_id; | |
uint8_t msg_id; | |
uint8_t record_count; | |
uint8_t data_type; | |
uint8_t val8; | |
uint16_t val16; | |
uint32_t val32; | |
for (int i=0; i+4<sizeof(example);) { // min 5 bytes for a node message | |
node_id = example[i++]; | |
msg_id = example[i++]; | |
record_count = example[i++]; | |
printf("NODE ID: %d; MSG ID: %d; MSG COUNT: %d; MESSAGES:\n", | |
node_id, msg_id, record_count); | |
for (int j=0; j<record_count; j++) { | |
data_type = example[i++]; | |
switch(data_type) { | |
/* 8-bit type testing */ | |
case CHAR : | |
case INT8 : | |
case UINT8 : | |
if (!next_8_bit(example, len, &i, &val8)) break; | |
printval8(val8, data_type); | |
break; | |
/* 16-bit type testing */ | |
case INT16 : | |
case UINT16 : | |
if (!next_16_bit(example, len, &i, &val16)) break; | |
printval16(val16, data_type); | |
break; | |
/* abstract types */ | |
case BATTERY : | |
if (!next_8_bit(example, len, &i, &val8)) break; | |
printf("BAT: %2.1f; ", val8 / 10.0); | |
break; | |
case GPS : | |
if (!next_8_bit(example, len, &i, &val8)) break; | |
printf("SATS: %d; ", val8); | |
if (!next_32_bit(example, len, &i, &val32)) break; | |
printf("LAT: %8.5f; ", (int32_t)val32 / 100000.0); | |
if (!next_32_bit(example, len, &i, &val32)) break; | |
printf("LON: %8.5f; ", (int32_t)val32 / 100000.0); | |
break; | |
case AIR_PARTICULATE : | |
if (!next_8_bit(example, len, &i, &val8)) break; | |
printf("DUST: %d; ", val8); | |
if (!next_16_bit(example, len, &i, &val16)) break; | |
printf("TIMESTAMP: %d; ", val16); | |
break; | |
} | |
} | |
printf("\n\n"); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment