From 4aa9598e007429d6eed8b65e2798f62dab19e84f Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Thu, 28 Jul 2022 22:36:09 -0400 Subject: [PATCH] build a packet buffer and add a test suite --- flake.nix | 80 ++++++++++++-- packet-radio/src/packet.h | 28 +++++ packet-radio/src/packet_buffer.c | 62 +++++++++++ packet-radio/src/packet_buffer.h | 30 ++++++ packet-radio/{ => src}/radio.c | 70 ++++++++---- packet-radio/{ => src}/radio.h | 0 packet-radio/tests/packet-buffer.c | 167 +++++++++++++++++++++++++++++ 7 files changed, 407 insertions(+), 30 deletions(-) create mode 100644 packet-radio/src/packet.h create mode 100644 packet-radio/src/packet_buffer.c create mode 100644 packet-radio/src/packet_buffer.h rename packet-radio/{ => src}/radio.c (61%) rename packet-radio/{ => src}/radio.h (100%) create mode 100644 packet-radio/tests/packet-buffer.c diff --git a/flake.nix b/flake.nix index 3c6f32a..63c6886 100644 --- a/flake.nix +++ b/flake.nix @@ -34,7 +34,12 @@ ${gcc} ${cflags} ${include_dirs} -c $source fi done - cp ${src}/*.h . || true + for source in ${src}/src/*.c; do + if [ -e $source ]; then + ${gcc} ${cflags} ${include_dirs} -c $source + fi + done + cp ${src}/*.h ${src}/src/*.h . || true ''; installPhase = '' @@ -44,15 +49,16 @@ ''; }; - mkProgram = { pkgs, gcc, cflags, pname, psrc, pbuildInputs ? [] }: + mkProgram = { pkgs, gcc, cflags, pname, psrc, pbuildInputs ? [], pnativeBuildInputs ? [] }: pkgs.stdenv.mkDerivation rec { name = pname; src = psrc; buildInputs = pbuildInputs; + nativeBuildInputs = pnativeBuildInputs; - include_dirs = pkgs.lib.concatStringsSep " " (map (dir: "-I${dir}/include") buildInputs); - object_files = pkgs.lib.concatStringsSep " " (map (dir: "${dir}/lib/*.o") buildInputs); + include_dirs = pkgs.lib.concatStringsSep " " (map (dir: "-I${dir}/include") pbuildInputs); + object_files = pkgs.lib.concatStringsSep " " (map (dir: "${dir}/lib/*.o") pbuildInputs); dontUnpack = true; dontConfigure = true; @@ -73,6 +79,35 @@ ''; }; + mkTests = { pkgs, gcc, cflags, pname, psrc, pbuildInputs ? [], pnativeBuildInputs ? [] }: + pkgs.stdenv.mkDerivation rec { + name = pname; + src = psrc; + + buildInputs = pbuildInputs; + nativeBuildInputs = pnativeBuildInputs; + + include_dirs = pkgs.lib.concatStringsSep " " (map (dir: "-I${dir}/include") buildInputs); + object_files = pkgs.lib.concatStringsSep " " (map (dir: "${dir}/lib/*.o") buildInputs); + + dontUnpack = true; + dontConfigure = true; + + buildPhase = '' + for source in ${src}/tests/*.c; do + if [ -e $source ]; then + ${gcc} ${cflags} ${include_dirs} -c $source + fi + done + + ${gcc} -o ${name} ${cflags} ${object_files} *.o + ''; + + installPhase = '' + mkdir -p $out/bin + cp ${name} $out/bin + ''; + }; in rec { @@ -85,7 +120,8 @@ paths = [ (packages."x86_64-linux"."prime-tx" { gcc = "${avr.gcc}/bin/avr-gcc"; cflags = mcu_cflags atmega32u4; }) (packages."x86_64-linux"."radio-rx" { gcc = "${avr.gcc}/bin/avr-gcc"; cflags = mcu_cflags atmega32u4; }) - (packages."x86_64-linux"."packet-radio" { gcc = "${avr.gcc}/bin/avr-gcc"; cflags = mcu_cflags atmega32u4; }) + (packages."x86_64-linux"."packet-radio" { gcc = "${avr.gcc}/bin/avr-gcc"; cflags = "-Wall -Werror"; }) + (packages."x86_64-linux"."packet-radio-tests" { gcc = "${avr.gcc}/bin/avr-gcc"; cflags = "-Wall -Werror"; }) ]; }; @@ -202,6 +238,11 @@ ]; }; + packages."x86_64-linux"."packet-radio_" = + let + pkgs = import nixpkgs { system = "x86_64-linux"; }; + in packages."x86_64-linux"."packet-radio" { gcc = "${pkgs.gcc}/bin/gcc"; cflags = "-Wall -Werror"; }; + packages."x86_64-linux"."packet-radio" = { gcc, cflags }: let @@ -210,11 +251,31 @@ pkgs = pkgs; gcc = gcc; cflags = cflags; - pname = "display"; - psrc = ./display; + pname = "packet-radio"; + psrc = ./packet-radio; + pbuildInputs = [ ]; + }; + + packages."x86_64-linux"."packet-radio-tests_" = + let + pkgs = import nixpkgs { system = "x86_64-linux"; }; + in packages."x86_64-linux"."packet-radio-tests" { gcc = "${pkgs.gcc}/bin/gcc"; cflags = "-Wall -Werror -lcriterion"; }; + + packages."x86_64-linux"."packet-radio-tests" = + { gcc, cflags }: + let + pkgs = import nixpkgs { system = "x86_64-linux"; }; + in mkTests { + pkgs = pkgs; + gcc = gcc; + cflags = cflags; + pname = "packet-radio-tests"; + psrc = ./packet-radio; pbuildInputs = [ - (packages."x86_64-linux"."dio" { inherit gcc cflags; }) - (packages."x86_64-linux"."shift-register" { inherit gcc cflags; }) + (packages."x86_64-linux"."packet-radio" { inherit gcc cflags; }) + ]; + pnativeBuildInputs = [ + pkgs.criterion ]; }; @@ -266,6 +327,7 @@ avrdude simavr gtkwave + criterion ]; in pkgs.mkShell { diff --git a/packet-radio/src/packet.h b/packet-radio/src/packet.h new file mode 100644 index 0000000..7f1bfc6 --- /dev/null +++ b/packet-radio/src/packet.h @@ -0,0 +1,28 @@ + +#ifndef __PACKET_H__ +#define __PACKET_H__ + +#ifdef __AVR__ + +#else +#include +#include +#endif + +typedef enum { + packet_type_data, + packet_type_ack, + packet_type_retry, +} packet_type_e; + +typedef struct { + packet_type_e type; + uint16_t count; + uint8_t sender; + uint8_t receiver; + size_t length; + char message[60]; +} packet_t; + +#endif + diff --git a/packet-radio/src/packet_buffer.c b/packet-radio/src/packet_buffer.c new file mode 100644 index 0000000..1b66466 --- /dev/null +++ b/packet-radio/src/packet_buffer.c @@ -0,0 +1,62 @@ + +#include "radio.h" +#include "packet.h" +#include "packet_buffer.h" +#include + +size_t circular_next(size_t current, size_t max); + +packet_buffer_t *packet_buffer_new() { + return malloc(sizeof(packet_buffer_t)); +} + +packet_t *packet_buffer_enqueue(packet_buffer_t *self, packet_buffer_status_e *status) { + if (!IS_OK(status)) return NULL; + + if (packet_buffer_is_full(self)) { + *status = packet_buffer_status_full; + return NULL; + } + + packet_t *ptr = &self->buffer[self->top]; + self->top = circular_next(self->top, 5); + self->count++; + return ptr; +} + +packet_t *packet_buffer_head(packet_buffer_t *self, packet_buffer_status_e *status) { + if (!IS_OK(status)) return NULL; + + if (packet_buffer_is_empty(self)) { + *status = packet_buffer_status_empty; + return NULL; + } + + return &self->buffer[self->bottom]; +} + +void packet_buffer_dequeue(packet_buffer_t *self, packet_buffer_status_e *status) { + if (!IS_OK(status)) return; + + if (packet_buffer_is_empty(self)) { + *status = packet_buffer_status_empty; + return; + } + + self->bottom = circular_next(self->bottom, 5); + self->count--; +} + +bool packet_buffer_is_full(packet_buffer_t *self) { + return self->count == 5; +} + +bool packet_buffer_is_empty(packet_buffer_t *self) { + return self->count == 0; +} + +size_t circular_next(size_t current, size_t max) { + current++; + return (current < max) ? current : 0; +} + diff --git a/packet-radio/src/packet_buffer.h b/packet-radio/src/packet_buffer.h new file mode 100644 index 0000000..c7320de --- /dev/null +++ b/packet-radio/src/packet_buffer.h @@ -0,0 +1,30 @@ + +#ifndef __PACKET_BUFFER_H__ +#define __PACKET_BUFFER_H__ + +#include "packet.h" +#include + +typedef enum { + packet_buffer_status_ok, + packet_buffer_status_empty, + packet_buffer_status_full, +} packet_buffer_status_e; + +typedef struct { + size_t count; + size_t bottom; + size_t top; + packet_t buffer[5]; +} packet_buffer_t; + +packet_buffer_t *packet_buffer_new(); + +packet_t * packet_buffer_enqueue(packet_buffer_t *, packet_buffer_status_e *); +packet_t * packet_buffer_head(packet_buffer_t *, packet_buffer_status_e *); +void packet_buffer_dequeue(packet_buffer_t *, packet_buffer_status_e *); + +bool packet_buffer_is_full(packet_buffer_t *); +bool packet_buffer_is_empty(packet_buffer_t *); + +#endif diff --git a/packet-radio/radio.c b/packet-radio/src/radio.c similarity index 61% rename from packet-radio/radio.c rename to packet-radio/src/radio.c index 5f7561c..b5b3937 100644 --- a/packet-radio/radio.c +++ b/packet-radio/src/radio.c @@ -1,39 +1,28 @@ #include "radio.h" +#include "packet.h" #include #include -typedef enum { - data, - ack, - retry, -} packet_type_e; - typedef struct { bool packet_sent; bool packet_ready; } flags_t; -typedef struct { - packet_type_e type; - uint16_t count; - uint8_t sender; - uint8_t receiver; - size_t size; - char message[60]; -} packet_t; - struct conn_s { uint16_t packet_counter; uint8_t address; - packet_t outgoing_buffer[5]; - size_t outgoing_bottom; - size_t outgoing_top; + // packet_buffer_t outgoing_packets; + // packet_buffer_t incoming_packets; - packet_t incoming_buffer[5]; - size_t incoming_bottom; - size_t incoming_top; + // packet_t outgoing_buffer[5]; + // size_t outgoing_bottom; + // size_t outgoing_top; + + // packet_t incoming_buffer[5]; + // size_t incoming_bottom; + // size_t incoming_top; void (*set_mode)(void *, radio_mode_e, radio_status_e *); flags_t (*get_flags)(void *, radio_status_e *); @@ -45,16 +34,21 @@ struct conn_s { void conn_set_address(conn_t *self, uint8_t addr, radio_status_e *status) { + /* if (!IS_OK(status)) return; self->address = addr; + */ } void conn_set_mode(conn_t *self, radio_mode_e mode, radio_status_e *status) { + /* if (!IS_OK(status)) return; self->set_mode(self->radio, mode, status); + */ } void conn_send(conn_t *self, uint8_t dest, msg_t *message, radio_status_e *status) { + /* if (!IS_OK(status)) return; if (message->length > MAX_MESSAGE_LENGTH) { @@ -78,9 +72,11 @@ void conn_send(conn_t *self, uint8_t dest, msg_t *message, radio_status_e *statu if (IS_OK(status)) self->outgoing_top++; return; + */ } received_msg_t * conn_receive(conn_t *self, radio_status_e *status) { + /* if (!IS_OK(status)) return NULL; if (self->incoming_bottom == self->incoming_top) { @@ -96,18 +92,50 @@ received_msg_t * conn_receive(conn_t *self, radio_status_e *status) { self->incoming_bottom++; return msg; + */ + return NULL; } void conn_handle_interrupt(conn_t *self) { + /* radio_status_e *status = ok; flags_t flags = self->get_flags(self->radio, status); + // if this is failing, we should raise a flag that indicates that the radio itself is in trouble if (!IS_OK(status)) return; if (flags.packet_ready) { uint8_t data[66]; self->receive(self->radio, data, status); + // if this fails, increment an error counter if (!IS_OK(status)) return; + + packet_t *packet = (packet_t *)data; + if (packet->receiver != self->address) { + return; + } + + switch (packet->type) { + case packet_type_data: + memcpy(self->incoming_buffer[self->incoming_top], packet, packet->size); + self->incoming_top++; + break; + case packet_type_ack: + uint16_t confirmed = packet->data[0] << 8 + packet->data[1]; + while (self->outgoing_buffer[self->outgoing_bottom]->count < confirmed && !is_empty(self->outgoing_buffer) + break; + case packet_type_retry: + break; + } + // process the packet type + // break; } + + // timeout processing + // if (time > receiving_timeout) { } + // if we were receiving data, a receiving_timeout must have been set. At this point, we're not receiving any more data, so it's time to send a confirmation. + // if (time > confirmation_timeout) { } + // if we were sending data, a confirmation timeout must have been set. But I don't recall what this is good for? Do we want to retransmit everything? + */ } diff --git a/packet-radio/radio.h b/packet-radio/src/radio.h similarity index 100% rename from packet-radio/radio.h rename to packet-radio/src/radio.h diff --git a/packet-radio/tests/packet-buffer.c b/packet-radio/tests/packet-buffer.c new file mode 100644 index 0000000..1d15768 --- /dev/null +++ b/packet-radio/tests/packet-buffer.c @@ -0,0 +1,167 @@ +#include +#include +#include "packet_buffer.h" + +Test(packet_buffer, empty_buffer) { + packet_buffer_t *buffer = packet_buffer_new(); + cr_assert(packet_buffer_is_empty(buffer)); + cr_assert(!packet_buffer_is_full(buffer)); +} + +Test(packet_buffer, full_buffer) { + packet_buffer_status_e status = packet_buffer_status_ok; + packet_buffer_t *buffer = packet_buffer_new(); + packet_t *packet; + + packet = packet_buffer_enqueue(buffer, &status); + cr_assert(packet != NULL); + cr_assert(status == packet_buffer_status_ok); + cr_assert(!packet_buffer_is_empty(buffer)); + cr_assert(!packet_buffer_is_full(buffer)); + + packet = packet_buffer_enqueue(buffer, &status); + cr_assert(packet != NULL); + cr_assert(status == packet_buffer_status_ok); + + packet = packet_buffer_enqueue(buffer, &status); + cr_assert(packet != NULL); + cr_assert(status == packet_buffer_status_ok); + + packet = packet_buffer_enqueue(buffer, &status); + cr_assert(packet != NULL); + cr_assert(status == packet_buffer_status_ok); + + packet = packet_buffer_enqueue(buffer, &status); + cr_assert(packet != NULL); + cr_assert(status == packet_buffer_status_ok); + cr_assert(packet_buffer_is_full(buffer)); + + packet = packet_buffer_enqueue(buffer, &status); + cr_assert(packet == NULL); + cr_assert(status == packet_buffer_status_full); + cr_assert(packet_buffer_is_full(buffer)); +} + +Test(packet_buffer, basic_enqueue_and_dequeue) { + packet_buffer_status_e status = packet_buffer_status_ok; + packet_buffer_t *buffer = packet_buffer_new(); + packet_t *packet; + + packet = packet_buffer_enqueue(buffer, &status); + cr_assert(status == packet_buffer_status_ok); + + packet->type = packet_type_data; + packet->count = 0; + packet->sender = 15; + packet->receiver = 16; + packet->length = 2; + memcpy(packet->message, (char [2]){ 25, 24 }, 2); + + packet = packet_buffer_head(buffer, &status); + cr_assert(status == packet_buffer_status_ok); + cr_assert(packet->type == packet_type_data); + cr_assert(packet->count == 0); + cr_assert(packet->sender == 15); + cr_assert(packet->receiver == 16); + cr_assert(packet->length == 2); + cr_assert(packet->message[0] == 25); + cr_assert(packet->message[1] == 24); + + packet_buffer_dequeue(buffer, &status); + cr_assert(status == packet_buffer_status_ok); +} + +Test(packet_buffer, enqueue_to_full) { + packet_buffer_status_e status = packet_buffer_status_ok; + packet_buffer_t *buffer = packet_buffer_new(); + packet_t *packet; + + packet = packet_buffer_enqueue(buffer, &status); + cr_assert(status == packet_buffer_status_ok); + + packet->type = packet_type_data; + packet->count = 0; + packet->sender = 15; + packet->receiver = 16; + packet->length = 2; + memcpy(packet->message, (char [2]){ 25, 24 }, 2); + + packet = packet_buffer_enqueue(buffer, &status); + packet = packet_buffer_enqueue(buffer, &status); + packet = packet_buffer_enqueue(buffer, &status); + packet = packet_buffer_enqueue(buffer, &status); + cr_assert(status == packet_buffer_status_ok); + + packet = packet_buffer_head(buffer, &status); + cr_assert(status == packet_buffer_status_ok); + cr_assert(packet->type == packet_type_data); + cr_assert(packet->count == 0); + cr_assert(packet->sender == 15); + cr_assert(packet->receiver == 16); + cr_assert(packet->length == 2); + cr_assert(packet->message[0] == 25); + cr_assert(packet->message[1] == 24); + + packet_buffer_dequeue(buffer, &status); + cr_assert(status == packet_buffer_status_ok); +} + +Test(packet_buffer, circular_buffer) { + packet_buffer_status_e status = packet_buffer_status_ok; + packet_buffer_t *buffer = packet_buffer_new(); + packet_t *packet; + + packet = packet_buffer_enqueue(buffer, &status); + packet = packet_buffer_enqueue(buffer, &status); + packet = packet_buffer_enqueue(buffer, &status); + packet->type = packet_type_data; + packet->count = 0; + packet->sender = 15; + packet->receiver = 16; + packet->length = 2; + memcpy(packet->message, (char [2]){ 25, 24 }, 2); + + packet_buffer_dequeue(buffer, &status); + packet_buffer_dequeue(buffer, &status); + + packet = packet_buffer_enqueue(buffer, &status); + packet = packet_buffer_enqueue(buffer, &status); + packet = packet_buffer_enqueue(buffer, &status); + packet = packet_buffer_enqueue(buffer, &status); + cr_assert(status == packet_buffer_status_ok); + + packet->type = packet_type_data; + packet->count = 1; + packet->sender = 15; + packet->receiver = 16; + packet->length = 2; + memcpy(packet->message, (char [2]){ 25, 24 }, 2); + + packet = packet_buffer_head(buffer, &status); + cr_assert(status == packet_buffer_status_ok); + cr_assert(packet->type == packet_type_data); + cr_assert(packet->count == 0); + cr_assert(packet->sender == 15); + cr_assert(packet->receiver == 16); + cr_assert(packet->length == 2); + cr_assert(packet->message[0] == 25); + cr_assert(packet->message[1] == 24); + + packet_buffer_dequeue(buffer, &status); + packet_buffer_dequeue(buffer, &status); + packet_buffer_dequeue(buffer, &status); + packet_buffer_dequeue(buffer, &status); + packet = packet_buffer_head(buffer, &status); + cr_assert(status == packet_buffer_status_ok); + cr_assert(packet->type == packet_type_data); + cr_assert(packet->count == 1); + cr_assert(packet->sender == 15); + cr_assert(packet->receiver == 16); + cr_assert(packet->length == 2); + cr_assert(packet->message[0] == 25); + cr_assert(packet->message[1] == 24); + + packet_buffer_dequeue(buffer, &status); + cr_assert(status == packet_buffer_status_ok); + cr_assert(packet_buffer_is_empty(buffer)); +}