Remember from the numbers intro that we can define densely-packed data structures? Data structures will help you with sending data over the internet.
//Put this code outside of all functions, in the *global scope*
// Define a structure 'pkt_fmt' with a packed attribute to ensure there is no padding between its members.
// This is important when dealing with binary data formats or hardware communication, where exact size and layout matter.
struct __attribute__((__packed__)) pkt_fmt {
uint32_t totalPackets; // A 32-bit unsigned integer to store the total number of packets.
uint8_t myString[6]; // A fixed-size array of 6 bytes to store a short string or byte sequence.
float illumination; // A 32-bit floating-point value to store illumination data.
};
// Declare a global variable 'myPkt' of type 'pkt_fmt'.
// This variable can be accessed and modified by any function in the program.
pkt_fmt myPkt;
That code defines a densely-packed packet format and one instance called
myPkt
that you can send over LoRaWAN. In the lines reproduced below
myLoRaWAN.SendBuffer(messageBuffer, bufferLength, myStatusCallback, NULL, false, 1);
you now want to send bytes starting at (uint8_t *) &myPkt
instead of messageBuffer.
There are now sizeof(myPkt)
bytes to send instead of bufferLength
bytes to send.
Please don’t copy this exact data structure in your assignment.
The example functions for working with strings on TTN assume you pass them
a list of bytes. You can use input.bytes.slice(begin, end)
and that
will give you a list of bytes in [begin, end) (set open to the right: includes
begin, but does not include end). So, we can get the illumination from bytes
10, 11, 12, and 13 by using
var exampleFloat = floatFromBytes(input.bytes.slice(10,14));
We know that our float is at bytes 10-13 because totalPackets resides in bytes 0-3,
and our 6-uint8_t string resides in six bytes, bytes 4-9. We have to use
attribute((__packed))
to guarantee that, though.
Recall from the numbers intro that there are standard integer data types available in C++
on Arduino. They are uint8_t
, int8_t
, uint16_t
, int16_t
,
uint32_t
, and int32_t
. U stands for unsigned, and signed just means
that the most significant bit has a negative weight.
Simply add fixed-width integer types to your packet struct. You could also copy
the bytes directly into a messagebuffer with memcpy
.
You need to reassemble ints from bytes. You can start decoding both signed and unsigned ints in a similar way. However, for bytes that represent a signed integer, you must assign a negative weight to the most significant bit.
// Function to build an unsigned integer from an array of bytes
// This function treats the bytes as an unsigned integer in little-endian format
function build_uint_from_bytes(bytes) {
var built_up_uint = 0; // Initialize the result as 0
for (var i = 0; i < bytes.length; i++) {
// Add the value of each byte shifted to its appropriate position
// '<<' shifts the byte value by (i * 8) bits to its place in the integer
built_up_uint = built_up_uint + (bytes[i] << i * 8);
}
return built_up_uint; // Return the constructed unsigned integer
}
// Function to build a signed integer from an array of bytes
// This function interprets the bytes as a signed integer in little-endian format
function build_int_from_bytes(bytes) {
var built_up_int = 0; // Initialize the result as 0
var cap = bytes.length - 1; // Determine the index of the most significant byte
// Process all bytes except the most significant byte
for (var i = 0; i < cap; i++) {
// Add the value of each byte shifted to its appropriate position
built_up_int = built_up_int + (bytes[i] << i * 8);
}
// Handle the most significant byte separately to account for its signedness
var lastByte = bytes[cap]; // Get the most significant byte
if (lastByte & 0b10000000) {
// Check if the most significant bit (MSB) is set, indicating a negative value
lastByte = lastByte - 256;
// Convert the byte to a signed value (e.g., 0xFF becomes -1)
built_up_int = built_up_int + (lastByte << cap * 8);
// Add the signed value to the total, shifted to its proper position
}
return built_up_int; // Return the constructed signed integer
}
Unsigned Integer Function
Input: [0x01, 0x02, 0x03, 0x04]
Function: build_uint_from_bytes
Explanation: Each byte contributes to the final value based on its position.
built_up_uint = (0x01 « 0) + (0x02 « 8) + (0x03 « 16) + (0x04 « 24) = 0x04030201 = 67,305,985 (decimal)
Output: 67,305,985 (or 0x04030201)
Signed Integer Function with Positive Value
Input: [0xFF, 0xFF, 0xFF, 0x7F]
Function: build_int_from_bytes
Explanation: Most significant byte (MSB) is 0x7F (MSB bit 0, positive number).
built_up_int = (0xFF « 0) + (0xFF « 8) + (0xFF « 16) + (0x7F « 24) = 0x7FFFFFFF = 2,147,483,647 (decimal)
Output: 2,147,483,647 (or 0x7FFFFFFF).
Signed Integer Function with Negative Value
Input: [0xFF, 0xFF, 0xFF, 0xFF]
Function: build_int_from_bytes
Explanation: MSB is 0xFF (MSB bit 1, negative number). Adjust 0xFF to signed value: 0xFF - 256 = -1.
built_up_int = (0xFF « 0) + (0xFF « 8) + (0xFF « 16) + (-1 « 24) = -1
Output: -1
You may copy and paste these payload bytes to confirm your understanding of these data types.
Payload Bytes | Payload |
---|---|
72d3e901 | uint32_t 32101234 |
8e2c16fe | int32_t -32101234 |
A string is an ordered collection of characters (char
or uint8_t
),
such as “Hello”.
There is a String datatype, but we will use C strings. Define a C string
as an array of char
s or uint8_t
s. Use memcpy
to copy into
your buffer. Every string ends with a terminating zero (also called a
null terminator): using double quotes gives the null terminator
automatically.
The following are equivalent:
struct __attribute__((__packed__)) pkt_fmt{
uint32_t totalPackets;
uint8_t myString[6];
float illumination;
};
pkt_fmt myPkt;
String myString = "Hello";
uint8_t myCString[6] = "Hello";
uint8_t myCString2[6] = {'H', 'e', 'l', 'l', 'o', 0};
setup(){
memcpy(myPkt.myString, myString.c_str(), 6);
memcpy(myPkt.myString, myCString, 6);
memcpy(myPkt.myString, myCString2, 6);
};
They all copy a string into an example packet.
You can use the static method String.fromCharCode
with Javascript’s
spread operator ...
as in the following function:
function stringFromBytes(bytes){
return String.fromCharCode(...bytes);
}
You may copy and paste these payload bytes to confirm your understanding of these data types.
Payload Bytes | Payload |
---|---|
68656c6c6f00 | “hello” |
72d3e90168656c6c6f00 | uint32_t 32101234, then “hello” |
The null terminator is included and appears as “\u0000” on TTN.
float
is a four byte datatype for floating-point arithmetic specified in
the IEEE 754 standard. Wikipedia Article on Floats
Its most significant bit (bit 31, or bit 7 of byte 3) is its sign bit. There
are then 8 bits that are a biased exponent, and the remaining
23 bits are the fractional part. Check the article for more details. For lab 2,
you may simply cite the bytesToFloat
function given below and trust that
it works.
Simply add a float to your packet struct. You could also copy
the bytes directly into a messagebuffer with memcpy
.
You can pass in a list of four little-endian bytes that represent a float and this function will turn them in to a Javascript float.
function floatFromBytes(bytes) {
// JavaScript bitwise operators yield a 32 bit integer, not a float.
// Assume LSB (least significant byte first).
var bits = bytes[3]<<24 | bytes[2]<<16 | bytes[1]<<8 | bytes[0];
// >>> is unsigned right shift. Zeros are shifted in at all times from the left
var sign = (bits>>>31 === 0) ? 1.0 : -1.0;
var e = bits>>>23 & 0xff;
// Can code explicit leading 0 with 0 exponent. Otherwise, there is a
// leading 1 implied.
// Since we have constructed our significand/mantissa like we constructed
// our integers, it is a factor of 2^23 too large (decimal point all the way)
// right.
// So, we correct that when we build the final number.
var m = (e === 0) ? (bits & 0x7fffff)<<1 : (bits & 0x7fffff) | 0x800000;
var f = sign * m * Math.pow(2, e - 127-23);
return f;
}
You may copy and paste these payload bytes to confirm your understanding of these data types.
Payload Bytes | Payload |
---|---|
ae474940 | 3.145 |
72d3e90168656c6c6f00ae474940 | uint32_t 32101234, then “hello”, then 3.145 |