#ifdef ARDUINO
#include <Arduino.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#else
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstring>
#endif
#ifndef ARDUINO
struct FakeSerial {
void begin(unsigned long) {}
void println() { std::printf("\n"); }
void println(const char* s) { std::printf("%s\n", s); }
void println(unsigned long v) { std::printf("%lu\n", v); }
void println(long v) { std::printf("%ld\n", v); }
void println(int v) { std::printf("%d\n", v); }
void println(float v) { std::printf("%.6f\n", (double)v); }
void print(const char* s) { std::printf("%s", s); }
void print(unsigned long v) { std::printf("%lu", v); }
void print(long v) { std::printf("%ld", v); }
void print(int v) { std::printf("%d", v); }
void print(float v) { std::printf("%.6f", (double)v); }
};
FakeSerial Serial;
static void delay(unsigned long) {}
#endif
const uint8_t VARIANT = 4;
const float Tobs = 6.0f;
const uint32_t RNG_SEED = 42UL;
const uint16_t CSV_EVERY_NTH = 1;
const float DATA_DROP_SCALE = 1.0f;
const float DATA_BITFLIP_SCALE = 1.0f;
const float ACK_DROP_SCALE = 1.0f;
const float ACK_BITFLIP_SCALE = 1.0f;
const int32_t MAX_RETRIES_OVERRIDE = -1;
const int32_t TIMEOUT_US_OVERRIDE = 4300;
const int32_t PAYLOAD_BYTES_OVERRIDE = -1;
const int32_t PERIOD_US_OVERRIDE = -1;
const uint8_t SOF1 = 0xA5;
const uint8_t SOF2 = 0x5A;
const uint8_t TYPE_DATA = 0x31;
const uint8_t TYPE_ACK = 0xC1;
const uint8_t TYPE_NACK = 0xC2;
const uint8_t MAX_FRAME_BYTES = 64;
const uint16_t MAX_LOGS = 240;
const uint16_t BYTE_TIME_US = 85;
struct VariantConfig {
const char* id;
uint32_t period_us;
uint8_t payload_bytes;
float data_drop_p;
float data_bitflip_p;
float ack_drop_p;
float ack_bitflip_p;
uint8_t max_retries;
uint32_t timeout_us;
uint32_t one_way_delay_us;
uint16_t jitter_us;
};
struct RuntimeConfig {
const char* id;
uint32_t period_us;
uint8_t payload_bytes;
float data_drop_p;
float data_bitflip_p;
float ack_drop_p;
float ack_bitflip_p;
uint8_t max_retries;
uint32_t timeout_us;
uint32_t one_way_delay_us;
uint16_t jitter_us;
};
struct ReceiverState {
bool seen[MAX_LOGS];
uint16_t expected_seq;
uint32_t unique_ok;
uint32_t duplicates;
uint32_t gap_count;
uint32_t crc_fail;
uint32_t late_resp;
uint64_t useful_payload_bytes;
uint64_t sum_latency_us;
uint32_t max_latency_us;
};
struct SessionStats {
uint32_t generated;
uint32_t src_confirmed_ok;
uint32_t src_failed;
uint32_t data_tx_attempts;
uint32_t retransmissions;
uint32_t timeouts;
uint32_t ack_rx;
uint32_t nack_rx;
uint32_t data_drop;
uint32_t data_bitflip;
uint32_t ack_drop;
uint32_t ack_bitflip;
uint64_t data_tx_bytes;
uint64_t ack_tx_bytes;
uint32_t data_crc_fail_at_rx;
uint32_t ack_crc_fail_at_tx;
};
struct FrameLog {
uint16_t seq;
uint32_t t_gen_us;
uint16_t value;
uint8_t attempts;
uint8_t retries_used;
uint8_t acked;
uint8_t unique_rx;
uint8_t duplicate_rx;
uint8_t late_resp;
uint32_t latency_us;
uint32_t done_us;
};
static uint32_t rng_state = RNG_SEED;
float randu01() {
rng_state = 1664525UL * rng_state + 1013904223UL;
return (rng_state >> 8) * (1.0f / 16777216.0f);
}
uint32_t randRangeU32(uint32_t max_inclusive) {
if (max_inclusive == 0) return 0;
return (uint32_t)(randu01() * (max_inclusive + 1.0f));
}
float clampProb(float p) {
if (p < 0.0f) return 0.0f;
if (p > 0.95f) return 0.95f;
return p;
}
uint8_t crc8_07(const uint8_t* data, uint8_t len) {
uint8_t crc = 0;
for (uint8_t i = 0; i < len; ++i) {
crc ^= data[i];
for (uint8_t b = 0; b < 8; ++b) {
if (crc & 0x80) crc = (uint8_t)((crc << 1) ^ 0x07);
else crc <<= 1;
}
}
return crc;
}
void put_u16(uint8_t* dst, uint16_t v) {
dst[0] = (uint8_t)(v & 0xFF);
dst[1] = (uint8_t)(v >> 8);
}
void put_u32(uint8_t* dst, uint32_t v) {
dst[0]=(uint8_t)(v & 0xFF);
dst[1]=(uint8_t)(v >> 8);
dst[2]=(uint8_t)(v >> 16);
dst[3]=(uint8_t)(v >> 24);
}
uint16_t get_u16(const uint8_t* src) {
return (uint16_t)src[0] | ((uint16_t)src[1] << 8);
}
uint32_t get_u32(const uint8_t* src) {
return (uint32_t)src[0] | ((uint32_t)src[1] << 8) | ((uint32_t)src[2] << 16) | ((uint32_t)src[3] << 24);
}
uint16_t synthValue(uint16_t seq) {
float x = (float)seq;
float v = 520.0f + 110.0f * sinf(2.0f * 3.1415926535f * x / 19.0f) + 40.0f * cosf(2.0f * 3.1415926535f * x / 7.0f);
if ((seq % 17U) == 9U) v += 65.0f;
if (v < 0.0f) v = 0.0f;
if (v > 1023.0f) v = 1023.0f;
return (uint16_t)(v + 0.5f);
}
uint8_t buildDataFrame(uint16_t seq, uint32_t t_us, uint16_t value, uint8_t payload_bytes, uint8_t* out) {
out[0]=SOF1;
out[1]=SOF2;
out[2]=TYPE_DATA;
out[3]=payload_bytes;
put_u16(out+4, seq);
put_u32(out+6, t_us);
put_u16(out+10, value);
for (uint8_t i = 0; i < payload_bytes; ++i) out[12+i] = (uint8_t)((value + 13U * i + 7U * seq) & 0xFF);
uint8_t len_no_crc = (uint8_t)(12 + payload_bytes);
out[len_no_crc] = crc8_07(out+2, (uint8_t)(len_no_crc-2));
return (uint8_t)(len_no_crc + 1);
}
uint8_t buildRespFrame(uint8_t type, uint16_t seq, uint8_t* out) {
out[0]=SOF1; out[1]=SOF2; out[2]=type; out[3]=0; put_u16(out+4, seq);
out[6]=crc8_07(out+2,4); return 7;
}
void flipOneBitPreserveHeader(uint8_t* data, uint8_t len) {
if (len <= 6) return;
uint8_t start = 6;
uint8_t end = (uint8_t)(len - 1); // allow payload or crc flip
uint8_t idx = (uint8_t)(start + randRangeU32((uint32_t)(end - start)));
uint8_t bit = (uint8_t)randRangeU32(7);
data[idx] ^= (uint8_t)(1U << bit);
}
bool parseFrame(const uint8_t* data, uint8_t len, uint8_t expected_type, uint16_t& seq_out, bool& crc_ok) {
crc_ok = false;
if (len < 7) return false;
if (data[0] != SOF1 || data[1] != SOF2) return false;
if (data[2] != expected_type) return false;
uint8_t payload_len = data[3];
uint8_t expected_len = (uint8_t)(2 + 1 + 1 + 2 + (expected_type == TYPE_DATA ? 4 + 2 + payload_len : payload_len) + 1);
if (len != expected_len) return false;
seq_out = get_u16(data + 4);
uint8_t crc = crc8_07(data + 2, (uint8_t)(len - 3));
crc_ok = (crc == data[len - 1]);
return true;
}
const VariantConfig variants[] = {
{"V1", 70000, 6, 0.02f, 0.006f, 0.01f, 0.003f, 2, 7000, 1500, 250},
{"V2", 70000, 8, 0.05f, 0.010f, 0.02f, 0.004f, 2, 7600, 1600, 350},
{"V3", 65000, 10, 0.03f, 0.020f, 0.01f, 0.008f, 3, 8600, 1700, 450},
{"V4", 65000, 12, 0.08f, 0.012f, 0.03f, 0.006f, 3, 9200, 1800, 500},
{"V5", 60000, 8, 0.03f, 0.008f, 0.08f, 0.006f, 3, 7800, 1500, 350},
{"V6", 60000, 10, 0.05f, 0.014f, 0.05f, 0.010f, 1, 8400, 1700, 500},
{"V7", 55000, 6, 0.02f, 0.005f, 0.01f, 0.003f, 2, 6500, 1400, 500},
{"V8", 55000, 12, 0.06f, 0.020f, 0.02f, 0.008f, 2, 9000, 1800, 700},
{"V9", 80000, 14, 0.04f, 0.010f, 0.03f, 0.004f, 4, 9800, 2200, 600},
{"V10", 90000, 16, 0.10f, 0.025f, 0.05f, 0.010f, 4, 11000, 2500, 900},
{"V11", 75000, 9, 0.12f, 0.015f, 0.08f, 0.012f, 3, 9800, 2000, 700},
{"V12", 85000, 5, 0.01f, 0.030f, 0.01f, 0.015f, 2, 7600, 1500, 400}
};
RuntimeConfig makeRuntimeConfig() {
RuntimeConfig cfg{};
VariantConfig base = variants[(VARIANT < 1 ? 1 : (VARIANT > 12 ? 12 : VARIANT)) - 1];
cfg.id = base.id;
cfg.period_us = (PERIOD_US_OVERRIDE > 0) ? (uint32_t)PERIOD_US_OVERRIDE : base.period_us;
cfg.payload_bytes = (PAYLOAD_BYTES_OVERRIDE > 0) ? (uint8_t)PAYLOAD_BYTES_OVERRIDE : base.payload_bytes;
cfg.data_drop_p = clampProb(base.data_drop_p * DATA_DROP_SCALE);
cfg.data_bitflip_p = clampProb(base.data_bitflip_p * DATA_BITFLIP_SCALE);
cfg.ack_drop_p = clampProb(base.ack_drop_p * ACK_DROP_SCALE);
cfg.ack_bitflip_p = clampProb(base.ack_bitflip_p * ACK_BITFLIP_SCALE);
cfg.max_retries = (MAX_RETRIES_OVERRIDE >= 0) ? (uint8_t)MAX_RETRIES_OVERRIDE : base.max_retries;
cfg.timeout_us = (TIMEOUT_US_OVERRIDE > 0) ? (uint32_t)TIMEOUT_US_OVERRIDE : base.timeout_us;
cfg.one_way_delay_us = base.one_way_delay_us;
cfg.jitter_us = base.jitter_us;
return cfg;
}
void receiverAccept(ReceiverState& rx, uint16_t seq, uint32_t latency_us, uint8_t payload_bytes, bool& unique_rx, bool& duplicate_rx) {
unique_rx = false;
duplicate_rx = false;
if (seq < MAX_LOGS && rx.seen[seq]) {
rx.duplicates++;
duplicate_rx = true;
return;
}
unique_rx = true;
rx.unique_ok++;
if (seq > rx.expected_seq) rx.gap_count += (uint32_t)(seq - rx.expected_seq);
if (seq >= rx.expected_seq) rx.expected_seq = (uint16_t)(seq + 1);
if (seq < MAX_LOGS) rx.seen[seq] = true;
rx.useful_payload_bytes += payload_bytes;
rx.sum_latency_us += latency_us;
if (latency_us > rx.max_latency_us) rx.max_latency_us = latency_us;
}
void printSummary(const RuntimeConfig& cfg, const SessionStats& st, const ReceiverState& rx, uint32_t session_end_us) {
float secs = session_end_us / 1000000.0f;
float confirm_rate = st.generated ? (100.0f * st.src_confirmed_ok / st.generated) : 0.0f;
float final_loss = st.generated ? (100.0f * st.src_failed / st.generated) : 0.0f;
float retrans_rate = st.data_tx_attempts ? (100.0f * st.retransmissions / st.data_tx_attempts) : 0.0f;
float receiver_tp = secs > 0.0f ? (float)(rx.useful_payload_bytes / secs) : 0.0f;
float eff_tp = secs > 0.0f ? (float)((st.src_confirmed_ok * (uint32_t)cfg.payload_bytes) / secs) : 0.0f;
float mean_lat = rx.unique_ok ? (float)(rx.sum_latency_us / (double)rx.unique_ok) : 0.0f;
float payload_eff = (st.data_tx_bytes + st.ack_tx_bytes) ? (100.0f * (st.src_confirmed_ok * (uint32_t)cfg.payload_bytes) / (float)(st.data_tx_bytes + st.ack_tx_bytes)) : 0.0f;
uint32_t unconfirmed_rx = (rx.unique_ok > st.src_confirmed_ok) ? (rx.unique_ok - st.src_confirmed_ok) : 0U;
Serial.println("#SUMMARY");
char buf[160];
snprintf(buf, sizeof(buf), "variant=%s", cfg.id);
Serial.println(buf);
snprintf(buf, sizeof(buf), "period_us=%lu", (unsigned long)cfg.period_us);
Serial.println(buf);
snprintf(buf, sizeof(buf), "payload_bytes=%u", (unsigned)cfg.payload_bytes);
Serial.println(buf);
snprintf(buf, sizeof(buf), "max_retries=%u", (unsigned)cfg.max_retries);
Serial.println(buf);
snprintf(buf, sizeof(buf), "timeout_us=%lu", (unsigned long)cfg.timeout_us);
Serial.println(buf);
Serial.print("data_drop_p=");
Serial.println(cfg.data_drop_p, 4);
Serial.print("data_bitflip_p=");
Serial.println(cfg.data_bitflip_p, 4);
Serial.print("ack_drop_p=");
Serial.println(cfg.ack_drop_p, 4);
Serial.print("ack_bitflip_p=");
Serial.println(cfg.ack_bitflip_p, 4);
snprintf(buf, sizeof(buf), "generated=%lu", (unsigned long)st.generated);
Serial.println(buf);
snprintf(buf, sizeof(buf), "src_confirmed_ok=%lu", (unsigned long)st.src_confirmed_ok);
Serial.println(buf);
snprintf(buf, sizeof(buf), "src_failed=%lu", (unsigned long)st.src_failed);
Serial.println(buf);
snprintf(buf, sizeof(buf), "data_tx_attempts=%lu", (unsigned long)st.data_tx_attempts);
Serial.println(buf);
snprintf(buf, sizeof(buf), "retransmissions=%lu", (unsigned long)st.retransmissions);
Serial.println(buf);
snprintf(buf, sizeof(buf), "timeouts=%lu", (unsigned long)st.timeouts);
Serial.println(buf);
snprintf(buf, sizeof(buf), "ack_rx=%lu", (unsigned long)st.ack_rx);
Serial.println(buf);
snprintf(buf, sizeof(buf), "nack_rx=%lu", (unsigned long)st.nack_rx);
Serial.println(buf);
snprintf(buf, sizeof(buf), "data_drop=%lu", (unsigned long)st.data_drop);
Serial.println(buf);
snprintf(buf, sizeof(buf), "data_bitflip=%lu", (unsigned long)st.data_bitflip);
Serial.println(buf);
snprintf(buf, sizeof(buf), "ack_drop=%lu", (unsigned long)st.ack_drop);
Serial.println(buf);
snprintf(buf, sizeof(buf), "ack_bitflip=%lu", (unsigned long)st.ack_bitflip);
Serial.println(buf);
snprintf(buf, sizeof(buf), "data_crc_fail_at_rx=%lu", (unsigned long)st.data_crc_fail_at_rx);
Serial.println(buf);
snprintf(buf, sizeof(buf), "ack_crc_fail_at_tx=%lu", (unsigned long)st.ack_crc_fail_at_tx);
Serial.println(buf);
snprintf(buf, sizeof(buf), "rx_unique_ok=%lu", (unsigned long)rx.unique_ok);
Serial.println(buf);
snprintf(buf, sizeof(buf), "duplicate_count=%lu", (unsigned long)rx.duplicates);
Serial.println(buf);
snprintf(buf, sizeof(buf), "gap_count=%lu", (unsigned long)rx.gap_count);
Serial.println(buf);
snprintf(buf, sizeof(buf), "late_resp=%lu", (unsigned long)rx.late_resp);
Serial.println(buf);
snprintf(buf, sizeof(buf), "unconfirmed_received=%lu", (unsigned long)unconfirmed_rx);
Serial.println(buf);
Serial.print("effective_throughput_Bps=");
Serial.println(eff_tp, 2);
Serial.print("receiver_goodput_Bps=");
Serial.println(receiver_tp, 2);
Serial.print("payload_efficiency_pct=");
Serial.println(payload_eff, 2);
Serial.print("confirm_rate_pct=");
Serial.println(confirm_rate, 2);
Serial.print("final_loss_rate_pct=");
Serial.println(final_loss, 2);
Serial.print("retransmission_rate_pct=");
Serial.println(retrans_rate, 2);
Serial.print("mean_latency_us=");
Serial.println(mean_lat, 2);
snprintf(buf, sizeof(buf), "max_latency_us=%lu", (unsigned long)rx.max_latency_us);
Serial.println(buf);
snprintf(buf, sizeof(buf), "session_end_us=%lu", (unsigned long)session_end_us);
Serial.println(buf);
}
void runSession() {
RuntimeConfig cfg = makeRuntimeConfig();
ReceiverState rx{};
SessionStats st{};
uint32_t nFrames = (uint32_t)(Tobs * 1000000.0f / cfg.period_us);
if (nFrames > MAX_LOGS) nFrames = MAX_LOGS;
uint32_t current_time_us = 0;
Serial.println("#CSV_BEGIN");
Serial.println("seq,t_gen_us,value,attempts,retries_used,acked,unique_rx,duplicate_rx,late_resp,latency_us,done_us");
for (uint32_t i = 0; i < nFrames; ++i) {
uint16_t seq = (uint16_t)i;
uint32_t t_gen_us = i * cfg.period_us;
if (current_time_us < t_gen_us) current_time_us = t_gen_us;
uint16_t value = synthValue(seq);
FrameLog lg{};
lg.seq = seq; lg.t_gen_us = t_gen_us; lg.value = value;
st.generated++;
uint8_t frame[MAX_FRAME_BYTES];
uint8_t resp[MAX_FRAME_BYTES];
uint8_t data_len = buildDataFrame(seq, t_gen_us, value, cfg.payload_bytes, frame);
uint8_t resp_len = 0;
bool resolved = false;
for (uint8_t attempt = 0; attempt <= cfg.max_retries; ++attempt) {
lg.attempts++;
if (attempt > 0) { st.retransmissions++; lg.retries_used++; }
st.data_tx_attempts++;
st.data_tx_bytes += data_len;
uint32_t start_us = current_time_us;
uint32_t tx_data_done = start_us + data_len * BYTE_TIME_US;
uint32_t jitter_fwd = cfg.jitter_us ? randRangeU32(cfg.jitter_us) : 0;
uint32_t rx_arrival = tx_data_done + cfg.one_way_delay_us + jitter_fwd;
uint32_t timeout_deadline = start_us + cfg.timeout_us;
bool data_dropped = randu01() < cfg.data_drop_p;
bool data_flip = false;
bool header_ok = true;
bool crc_ok = false;
bool unique_rx = false;
bool duplicate_rx = false;
uint16_t parsed_seq = seq;
uint8_t resp_type = TYPE_ACK;
uint32_t resp_ready = 0;
bool receiver_has_response = false;
if (data_dropped) {
st.data_drop++;
} else {
uint8_t rxbuf[MAX_FRAME_BYTES];
memcpy(rxbuf, frame, data_len);
data_flip = (randu01() < cfg.data_bitflip_p);
if (data_flip) { flipOneBitPreserveHeader(rxbuf, data_len); st.data_bitflip++; }
header_ok = parseFrame(rxbuf, data_len, TYPE_DATA, parsed_seq, crc_ok);
if (header_ok && crc_ok) {
uint32_t latency_us = rx_arrival - t_gen_us;
receiverAccept(rx, parsed_seq, latency_us, cfg.payload_bytes, unique_rx, duplicate_rx);
resp_type = TYPE_ACK;
receiver_has_response = true;
lg.latency_us = latency_us;
} else if (header_ok && !crc_ok) {
st.data_crc_fail_at_rx++;
rx.crc_fail++;
resp_type = TYPE_NACK;
receiver_has_response = true;
}
if (receiver_has_response) {
resp_len = buildRespFrame(resp_type, parsed_seq, resp);
st.ack_tx_bytes += resp_len;
uint32_t jitter_back = cfg.jitter_us ? randRangeU32(cfg.jitter_us) : 0;
resp_ready = rx_arrival + resp_len * BYTE_TIME_US + cfg.one_way_delay_us +
jitter_back;
}
}
bool got_valid_response = false;
uint8_t got_type = 0;
if (receiver_has_response) {
bool resp_drop = (randu01() < cfg.ack_drop_p);
if (resp_drop) {
st.ack_drop++;
} else {
uint8_t txresp[MAX_FRAME_BYTES];
memcpy(txresp, resp, resp_len);
bool resp_flip = (randu01() < cfg.ack_bitflip_p);
if (resp_flip) { flipOneBitPreserveHeader(txresp, resp_len); st.ack_bitflip++; }
uint16_t ack_seq = parsed_seq;
bool ack_crc_ok = false;
bool ack_header_ok = parseFrame(txresp, resp_len, resp_type, ack_seq, ack_crc_ok);
if (resp_ready > timeout_deadline) {
rx.late_resp++;
lg.late_resp = 1;
} else if (ack_header_ok && ack_crc_ok && ack_seq == seq) {
got_valid_response = true;
got_type = resp_type;
} else if (ack_header_ok && !ack_crc_ok) {
st.ack_crc_fail_at_tx++;
}
}
}
if (got_valid_response && got_type == TYPE_ACK) {
st.ack_rx++;
st.src_confirmed_ok++;
lg.acked = 1;
lg.unique_rx = unique_rx ? 1 : 0;
lg.duplicate_rx = duplicate_rx ? 1 : 0;
lg.done_us = receiver_has_response ? resp_ready : timeout_deadline;
current_time_us = lg.done_us;
resolved = true;
break;
}
if (got_valid_response && got_type == TYPE_NACK) {
st.nack_rx++;
current_time_us = receiver_has_response ? resp_ready : timeout_deadline;
continue;
}
st.timeouts++;
current_time_us = timeout_deadline;
}
if (!resolved) {
st.src_failed++;
lg.done_us = current_time_us;
}
if ((i % CSV_EVERY_NTH) == 0) {
char row[200];
snprintf(row, sizeof(row), "%u,%lu,%u,%u,%u,%u,%u,%u,%u,%lu,%lu",
(unsigned)lg.seq,
(unsigned long)lg.t_gen_us,
(unsigned)lg.value,
(unsigned)lg.attempts,
(unsigned)lg.retries_used,
(unsigned)lg.acked,
(unsigned)lg.unique_rx,
(unsigned)lg.duplicate_rx,
(unsigned)lg.late_resp,
(unsigned long)lg.latency_us,
(unsigned long)lg.done_us);
Serial.println(row);
}
}
Serial.println("#CSV_END");
printSummary(cfg, st, rx, current_time_us);
}
void setup() {
Serial.begin(115200);
runSession();
}
void loop() {
delay(1000);
}
#ifndef ARDUINO
int main(){ setup(); return 0; }
#endif