#include <Arduino.h>
// =====================================================
// Лабораторна 4: Комунікація / телеметрія
// Формат кадру, timestamp, CRC, ACK/NACK, retry, статистика каналу
// Базовий режим: усе моделюється в одному скетчі, diagram.json не потрібен
// =====================================================
const uint8_t VARIANT = 4; // 1..12 — змінювати номер варіанту
const float Tobs = 6.0f; // модельований час сеансу, с
const uint16_t CSV_EVERY_NTH = 1; // для компактнішого CSV можна збільшити
const uint32_t RNG_SEED = 42UL;
// override-параметри для експериментів
const int32_t PERIOD_US_OVERRIDE = -1; // <0 = взяти з варіанту
const int8_t PAYLOAD_MODE_OVERRIDE = -1; // <0 = взяти з варіанту,0=compact,1=extended
const float DATA_DROP_SCALE = 1.0f; // 1.0 = без змін
const float DATA_CORRUPT_SCALE = 1.0f; // 1.0 = без змін
const float ACK_DROP_SCALE = 1.0f; // 1.0 = без змін
const float ACK_CORRUPT_SCALE = 1.0f; // 1.0 = без змін
const int8_t MAX_RETRIES_OVERRIDE = -1; // <0 = взяти з варіанту
const int32_t TIMEOUT_US_OVERRIDE = 3500; // <0 = взяти з варіанту
const float PI_F = 3.1415926535f;
const uint8_t SOF1 = 0xAA;
const uint8_t SOF2 = 0x55;
const uint8_t FRAME_TYPE_TELEMETRY = 0x10;
const uint8_t FRAME_TYPE_ACK = 0xA1;
const uint8_t FRAME_TYPE_NACK = 0xA2;
const uint16_t MAX_FRAMES = 420;
const uint8_t MAX_FRAME_BYTES = 24;
const uint16_t LINK_BYTE_TIME_US = 90;
enum ParseStatus : uint8_t {
PARSE_OK = 0,
PARSE_BAD_SOF = 1,
PARSE_BAD_LEN = 2,
PARSE_BAD_CRC = 3,
PARSE_BAD_TYPE = 4
};
enum ResolveStatus : uint8_t {
RESOLVE_ACKED = 0,
RESOLVE_FAILED = 1,
RESOLVE_UNCONFIRMED_RX = 2
};
struct VariantConfig {
const char* id;
uint32_t period_us;
uint8_t payload_mode; // 0 = compact, 1 = extended
float data_drop_p;
float data_corrupt_p;
float ack_drop_p;
float ack_corrupt_p;
uint8_t max_retries;
uint32_t timeout_us;
uint32_t data_delay_us;
uint32_t ack_delay_us;
uint16_t jitter_us;
float sensor_noise;
};
struct RuntimeConfig {
const char* id;
uint32_t period_us;
uint8_t payload_mode;
float data_drop_p;
float data_corrupt_p;
float ack_drop_p;
float ack_corrupt_p;
uint8_t max_retries;
uint32_t timeout_us;
uint32_t data_delay_us;
uint32_t ack_delay_us;
uint16_t jitter_us;
float sensor_noise;
};
struct FrameInfo {
uint8_t type;
uint16_t seq;
uint32_t t_gen_us;
uint16_t value;
uint8_t status;
};
struct ReceiverState {
bool seen[MAX_FRAMES];
uint16_t expected_seq;
uint32_t unique_ok;
uint32_t duplicates;
uint32_t gap_count;
uint32_t late_count;
uint32_t crc_fail;
uint32_t sof_fail;
uint32_t len_fail;
uint32_t type_fail;
uint32_t nack_sent;
uint32_t ack_sent;
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 src_unconfirmed_rx;
uint32_t data_tx_attempts;
uint32_t retransmissions;
uint32_t timeouts;
uint32_t nack_rx;
uint32_t ack_rx;
uint32_t data_drop;
uint32_t data_corrupt;
uint32_t ack_drop;
uint32_t ack_corrupt;
uint64_t data_tx_bytes;
uint64_t ack_tx_bytes;
};
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 final_status;
uint32_t first_rx_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);
}
float randn01_approx() {
float s = 0.0f;
for (int i = 0; i < 12; i++) s += randu01();
return s - 6.0f;
}
uint32_t randRangeU32(uint32_t max_inclusive) {
if (max_inclusive == 0) return 0;
return (uint32_t)(randu01() * (max_inclusive + 1.0f));
}
uint8_t crc8_07(const uint8_t* data, uint8_t len) {
uint8_t crc = 0x00;
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) & 0xFF);
}
void put_u32(uint8_t* dst, uint32_t v) {
dst[0] = (uint8_t)(v & 0xFF);
dst[1] = (uint8_t)((v >> 8) & 0xFF);
dst[2] = (uint8_t)((v >> 16) & 0xFF);
dst[3] = (uint8_t)((v >> 24) & 0xFF);
}
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);
}
uint8_t buildTelemetryFrame(uint16_t seq, uint32_t t_gen_us, uint16_t value, uint8_t payload_mode, uint8_t* out) {
out[0] = SOF1;
out[1] = SOF2;
out[2] = FRAME_TYPE_TELEMETRY;
uint8_t payload_len = (payload_mode == 0) ? 7 : 10;
out[3] = payload_len;
put_u16(out + 4, seq);
put_u32(out + 6, t_gen_us);
put_u16(out + 10, value);
out[12] = payload_mode;
uint8_t idx = 13;
if (payload_mode == 1) {
uint16_t avg = (uint16_t)((value + 3U * (seq % 31U) + 17U) % 1024U);
out[idx++] = (uint8_t)(avg & 0xFF);
out[idx++] = (uint8_t)((avg >> 8) & 0x03);
out[idx++] = (uint8_t)(50 + (seq % 50));
}
uint8_t crc = crc8_07(out + 2, (uint8_t)(idx - 2));
out[idx++] = crc;
return idx;
}
uint8_t buildAckFrame(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);
uint8_t crc = crc8_07(out + 2, 4);
out[6] = crc;
return 7;
}
void maybeCorruptFrame(uint8_t* data, uint8_t len) {
if (len == 0) return;
uint8_t byte_idx = (uint8_t)randRangeU32((uint32_t)len - 1U);
uint8_t bit_idx = (uint8_t)randRangeU32(7);
data[byte_idx] ^= (uint8_t)(1U << bit_idx);
}
ParseStatus parseFrame(const uint8_t* data, uint8_t len, FrameInfo& out) {
if (len < 7) return PARSE_BAD_LEN;
if (data[0] != SOF1 || data[1] != SOF2) return PARSE_BAD_SOF;
uint8_t type = data[2];
uint8_t payload_len = data[3];
uint8_t expected_len = (uint8_t)(2 + 1 + 1 + 2 + payload_len + 1);
if (len != expected_len) return PARSE_BAD_LEN;
uint8_t crc = crc8_07(data + 2, (uint8_t)(len - 3));
if (crc != data[len - 1]) return PARSE_BAD_CRC;
if (type == FRAME_TYPE_TELEMETRY) {
if (!(payload_len == 7 || payload_len == 10)) return PARSE_BAD_LEN;
out.type = type;
out.seq = get_u16(data + 4);
out.t_gen_us = get_u32(data + 6);
out.value = get_u16(data + 10);
out.status = data[12];
return PARSE_OK;
}
if (type == FRAME_TYPE_ACK || type == FRAME_TYPE_NACK) {
if (payload_len != 0) return PARSE_BAD_LEN;
out.type = type;
out.seq = get_u16(data + 4);
out.t_gen_us = 0;
out.value = 0;
out.status = 0;
return PARSE_OK;
}
return PARSE_BAD_TYPE;
}
void initReceiver(ReceiverState& rx) {
memset(&rx, 0, sizeof(rx));
}
void loadVariant(uint8_t v, VariantConfig& cfg) {
switch (v) {
case 1: cfg = {"V1", 25000, 0, 0.01f, 0.002f, 0.01f, 0.001f, 2, 6000, 1800, 1100, 700, 4.0f}; break;
case 2: cfg = {"V2", 22000, 0, 0.02f, 0.003f, 0.01f, 0.001f, 2, 6500, 1800, 1200, 900, 5.0f}; break;
case 3: cfg = {"V3", 25000, 1, 0.01f, 0.006f, 0.01f, 0.001f, 2, 6500, 2000, 1200, 900, 5.5f}; break;
case 4: cfg = {"V4", 20000, 0, 0.04f, 0.004f, 0.02f, 0.002f, 3, 7200, 2100, 1300, 1200, 6.0f}; break;
case 5: cfg = {"V5", 30000, 1, 0.03f, 0.002f, 0.03f, 0.002f, 2, 7200, 2200, 1300, 1100, 4.5f}; break;
case 6: cfg = {"V6", 18000, 0, 0.06f, 0.006f, 0.02f, 0.002f, 3, 8000, 2200, 1400, 1400, 7.0f}; break;
case 7: cfg = {"V7", 25000, 1, 0.02f, 0.010f, 0.03f, 0.003f, 3, 8200, 2300, 1400, 1500, 6.5f}; break;
case 8: cfg = {"V8", 16000, 0, 0.08f, 0.004f, 0.05f, 0.002f, 4, 9000, 2400, 1500, 1600, 8.0f}; break;
case 9: cfg = {"V9", 22000, 1, 0.05f, 0.012f, 0.04f, 0.003f, 4, 9200, 2400, 1500, 1700, 7.5f}; break;
case 10: cfg = {"V10",14000, 0, 0.10f, 0.006f, 0.05f, 0.003f, 4, 10000, 2500, 1600, 1800, 9.0f}; break;
case 11: cfg = {"V11",25000, 1, 0.12f, 0.008f, 0.08f, 0.004f, 4, 10500, 2600, 1700, 2000, 8.5f}; break;
default: cfg = {"V12",18000, 0, 0.08f, 0.014f, 0.10f, 0.004f, 5, 11000, 2700, 1800, 2200, 10.0f}; break;
}
}
RuntimeConfig resolveConfig(const VariantConfig& base) {
RuntimeConfig cfg;
cfg.id = base.id;
cfg.period_us = (PERIOD_US_OVERRIDE > 0) ? (uint32_t)PERIOD_US_OVERRIDE : base.period_us;
cfg.payload_mode = (PAYLOAD_MODE_OVERRIDE >= 0) ? (uint8_t)PAYLOAD_MODE_OVERRIDE : base.payload_mode;
cfg.data_drop_p = constrain(base.data_drop_p * DATA_DROP_SCALE, 0.0f, 0.95f);
cfg.data_corrupt_p = constrain(base.data_corrupt_p * DATA_CORRUPT_SCALE, 0.0f, 0.95f);
cfg.ack_drop_p = constrain(base.ack_drop_p * ACK_DROP_SCALE, 0.0f, 0.95f);
cfg.ack_corrupt_p = constrain(base.ack_corrupt_p * ACK_CORRUPT_SCALE, 0.0f, 0.95f);
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.data_delay_us = base.data_delay_us;
cfg.ack_delay_us = base.ack_delay_us;
cfg.jitter_us = base.jitter_us;
cfg.sensor_noise = base.sensor_noise;
return cfg;
}
float sensorValueAt(uint32_t t_us, float noise_sigma) {
float t = t_us * 1e-6f;
float x = 512.0f;
x += 180.0f * sinf(2.0f * PI_F * 0.45f * t);
x += 65.0f * sinf(2.0f * PI_F * 2.80f * t + 0.7f);
x += 18.0f * sinf(2.0f * PI_F * 8.00f * t);
x += 12.0f * sinf(2.0f * PI_F * 0.05f * t);
x += noise_sigma * randn01_approx();
return constrain(x, 0.0f, 1023.0f);
}
bool receiverAcceptTelemetry(const FrameInfo& info, uint8_t payload_mode, ReceiverState& rx, uint32_t arrival_us, bool& is_duplicate) {
is_duplicate = false;
if (info.seq < MAX_FRAMES && rx.seen[info.seq]) {
rx.duplicates++;
if (info.seq + 1U < rx.expected_seq) rx.late_count++;
is_duplicate = true;
return false;
}
if (info.seq > rx.expected_seq) {
rx.gap_count += (uint32_t)(info.seq - rx.expected_seq);
}
rx.expected_seq = (uint16_t)(info.seq + 1U);
if (info.seq < MAX_FRAMES) rx.seen[info.seq] = true;
rx.unique_ok++;
uint8_t payload_len = (payload_mode == 0) ? 7 : 10;
rx.useful_payload_bytes += payload_len;
uint32_t latency = arrival_us - info.t_gen_us;
rx.sum_latency_us += latency;
if (latency > rx.max_latency_us) rx.max_latency_us = latency;
return true;
}
void printSummary(const RuntimeConfig& cfg, const SessionStats& st, const ReceiverState& rx, uint32_t total_time_us) {
float total_s = total_time_us * 1e-6f;
float loss_rate = (st.generated > 0) ? (100.0f * st.src_failed / st.generated) : 0.0f;
float confirmed_rate = (st.generated > 0) ? (100.0f * st.src_confirmed_ok / st.generated) : 0.0f;
float unconfirmed_rate = (st.generated > 0) ? (100.0f * st.src_unconfirmed_rx / st.generated) : 0.0f;
float retransmission_rate = (st.generated > 0) ? (100.0f * st.retransmissions / st.generated) : 0.0f;
float effective_throughput = (total_s > 0.0f) ? (rx.useful_payload_bytes / total_s) : 0.0f;
float efficiency = (st.data_tx_bytes > 0) ? (100.0f * ((float)rx.useful_payload_bytes / (float)st.data_tx_bytes)) : 0.0f;
float mean_latency = (rx.unique_ok > 0) ? ((float)rx.sum_latency_us / (float)rx.unique_ok) : 0.0f;
Serial.print("#SUMMARY,variant="); Serial.println(cfg.id);
Serial.print("#SUMMARY,period_us="); Serial.println((unsigned long)cfg.period_us);
Serial.print("#SUMMARY,payload_mode="); Serial.println((int)cfg.payload_mode);
Serial.print("#SUMMARY,data_drop_p="); Serial.println(cfg.data_drop_p, 4);
Serial.print("#SUMMARY,data_corrupt_p="); Serial.println(cfg.data_corrupt_p, 4);
Serial.print("#SUMMARY,ack_drop_p="); Serial.println(cfg.ack_drop_p, 4);
Serial.print("#SUMMARY,ack_corrupt_p="); Serial.println(cfg.ack_corrupt_p, 4);
Serial.print("#SUMMARY,max_retries="); Serial.println((int)cfg.max_retries);
Serial.print("#SUMMARY,timeout_us="); Serial.println((unsigned long)cfg.timeout_us);
Serial.print("#SUMMARY,generated="); Serial.println((unsigned long)st.generated);
Serial.print("#SUMMARY,src_confirmed_ok="); Serial.println((unsigned long)st.src_confirmed_ok);
Serial.print("#SUMMARY,src_failed="); Serial.println((unsigned long)st.src_failed);
Serial.print("#SUMMARY,src_unconfirmed_rx="); Serial.println((unsigned long)st.src_unconfirmed_rx);
Serial.print("#SUMMARY,data_tx_attempts="); Serial.println((unsigned long)st.data_tx_attempts);
Serial.print("#SUMMARY,retransmissions="); Serial.println((unsigned long)st.retransmissions);
Serial.print("#SUMMARY,timeouts="); Serial.println((unsigned long)st.timeouts);
Serial.print("#SUMMARY,nack_rx="); Serial.println((unsigned long)st.nack_rx);
Serial.print("#SUMMARY,data_drop="); Serial.println((unsigned long)st.data_drop);
Serial.print("#SUMMARY,data_corrupt="); Serial.println((unsigned long)st.data_corrupt);
Serial.print("#SUMMARY,ack_drop="); Serial.println((unsigned long)st.ack_drop);
Serial.print("#SUMMARY,ack_corrupt="); Serial.println((unsigned long)st.ack_corrupt);
Serial.print("#SUMMARY,rx_unique_ok="); Serial.println((unsigned long)rx.unique_ok);
Serial.print("#SUMMARY,duplicate_count="); Serial.println((unsigned long)rx.duplicates);
Serial.print("#SUMMARY,gap_count="); Serial.println((unsigned long)rx.gap_count);
Serial.print("#SUMMARY,late_count="); Serial.println((unsigned long)rx.late_count);
Serial.print("#SUMMARY,crc_fail="); Serial.println((unsigned long)rx.crc_fail);
Serial.print("#SUMMARY,sof_fail="); Serial.println((unsigned long)rx.sof_fail);
Serial.print("#SUMMARY,len_fail="); Serial.println((unsigned long)rx.len_fail);
Serial.print("#SUMMARY,mean_latency_us="); Serial.println(mean_latency, 2);
Serial.print("#SUMMARY,max_latency_us="); Serial.println((unsigned long)rx.max_latency_us);
Serial.print("#SUMMARY,effective_throughput_Bps="); Serial.println(effective_throughput, 2);
Serial.print("#SUMMARY,payload_efficiency_pct="); Serial.println(efficiency, 2);
Serial.print("#SUMMARY,loss_rate_pct="); Serial.println(loss_rate, 2);
Serial.print("#SUMMARY,confirmed_delivery_pct="); Serial.println(confirmed_rate, 2);
Serial.print("#SUMMARY,unconfirmed_delivery_pct="); Serial.println(unconfirmed_rate, 2);
Serial.print("#SUMMARY,retransmission_rate_pct="); Serial.println(retransmission_rate, 2);
Serial.print("#SUMMARY,total_time_s="); Serial.println(total_s, 3);
}
void runSimulation() {
VariantConfig base;
loadVariant(VARIANT, base);
RuntimeConfig cfg = resolveConfig(base);
rng_state = RNG_SEED + (uint32_t)VARIANT * 7919UL;
ReceiverState rx;
initReceiver(rx);
SessionStats st{};
FrameLog lg;
uint16_t total_frames = (uint16_t)(Tobs * 1000000.0f / (float)cfg.period_us);
if (total_frames > MAX_FRAMES) total_frames = MAX_FRAMES;
uint32_t sim_now_us = 0;
Serial.print("#VARIANT,"); Serial.println(cfg.id);
Serial.println("#CSV_BEGIN");
Serial.println("seq,t_gen_us,value,attempts,retries_used,acked,unique_rx,duplicate_rx,final_status,first_rx_latency_us,done_us");
for (uint16_t seq = 0; seq < total_frames; seq++) {
uint32_t t_gen_us = (uint32_t)seq * cfg.period_us;
if (sim_now_us < t_gen_us) sim_now_us = t_gen_us;
FrameLog lg;
lg.seq = seq;
lg.t_gen_us = t_gen_us;
lg.value = (uint16_t)(sensorValueAt(t_gen_us, cfg.sensor_noise) + 0.5f);
lg.attempts = 0;
lg.retries_used = 0;
lg.acked = 0;
lg.unique_rx = 0;
lg.duplicate_rx = 0;
lg.final_status = RESOLVE_FAILED;
lg.first_rx_latency_us = 0;
lg.done_us = sim_now_us;
st.generated++;
bool sender_done = false;
bool receiver_accepted_once = false;
for (uint8_t attempt = 0; attempt <= cfg.max_retries && !sender_done; attempt++) {
uint8_t tx[MAX_FRAME_BYTES];
uint8_t tx_len = buildTelemetryFrame(seq, t_gen_us, lg.value, cfg.payload_mode, tx);
lg.attempts++;
if (attempt > 0) {
st.retransmissions++;
lg.retries_used = attempt;
}
st.data_tx_attempts++;
st.data_tx_bytes += tx_len;
uint32_t send_time_us = sim_now_us;
uint32_t rx_arrival_us = send_time_us + cfg.data_delay_us + (uint32_t)tx_len * LINK_BYTE_TIME_US + randRangeU32(cfg.jitter_us);
bool data_dropped = randu01() < cfg.data_drop_p;
bool data_corrupted = false;
if (data_dropped) {
st.data_drop++;
st.timeouts++;
sim_now_us = send_time_us + cfg.timeout_us;
continue;
}
if (randu01() < cfg.data_corrupt_p) {
data_corrupted = true;
st.data_corrupt++;
maybeCorruptFrame(tx, tx_len);
}
FrameInfo info{};
ParseStatus p = parseFrame(tx, tx_len, info);
bool response_generated = false;
uint8_t rsp[MAX_FRAME_BYTES];
uint8_t rsp_len = 0;
bool duplicate_this_attempt = false;
if (p == PARSE_OK && info.type == FRAME_TYPE_TELEMETRY) {
bool is_duplicate = false;
bool unique_ok = receiverAcceptTelemetry(info, cfg.payload_mode, rx, rx_arrival_us, is_duplicate);
if (unique_ok) {
receiver_accepted_once = true;
if (lg.first_rx_latency_us == 0) {
lg.first_rx_latency_us = rx_arrival_us - t_gen_us;
}
lg.unique_rx = 1;
}
if (is_duplicate) {
lg.duplicate_rx = 1;
duplicate_this_attempt = true;
}
rsp_len = buildAckFrame(FRAME_TYPE_ACK, seq, rsp);
rx.ack_sent++;
response_generated = true;
} else if (p == PARSE_BAD_CRC) {
rx.crc_fail++;
rsp_len = buildAckFrame(FRAME_TYPE_NACK, seq, rsp);
rx.nack_sent++;
response_generated = true;
} else if (p == PARSE_BAD_SOF) {
rx.sof_fail++;
} else if (p == PARSE_BAD_LEN) {
rx.len_fail++;
rsp_len = buildAckFrame(FRAME_TYPE_NACK, seq, rsp);
rx.nack_sent++;
response_generated = true;
} else {
rx.type_fail++;
}
if (!response_generated) {
st.timeouts++;
sim_now_us = send_time_us + cfg.timeout_us;
continue;
}
st.ack_tx_bytes += rsp_len;
uint32_t rsp_arrival_us = rx_arrival_us + cfg.ack_delay_us + (uint32_t)rsp_len *
LINK_BYTE_TIME_US + randRangeU32(cfg.jitter_us);
if (rsp_arrival_us > send_time_us + cfg.timeout_us) {
st.timeouts++;
sim_now_us = send_time_us + cfg.timeout_us;
continue;
}
bool ack_dropped = randu01() < cfg.ack_drop_p;
if (ack_dropped) {
st.ack_drop++;
st.timeouts++;
sim_now_us = send_time_us + cfg.timeout_us;
continue;
}
if (randu01() < cfg.ack_corrupt_p) {
st.ack_corrupt++;
maybeCorruptFrame(rsp, rsp_len);
}
FrameInfo ack_info{};
ParseStatus ack_parse = parseFrame(rsp, rsp_len, ack_info);
if (ack_parse != PARSE_OK) {
st.timeouts++;
sim_now_us = send_time_us + cfg.timeout_us;
continue;
}
sim_now_us = rsp_arrival_us;
if (ack_info.type == FRAME_TYPE_ACK) {
st.ack_rx++;
lg.acked = 1;
lg.done_us = sim_now_us;
lg.final_status = RESOLVE_ACKED;
st.src_confirmed_ok++;
sender_done = true;
} else if (ack_info.type == FRAME_TYPE_NACK) {
st.nack_rx++;
if (attempt == cfg.max_retries) {
lg.done_us = sim_now_us;
lg.final_status = receiver_accepted_once ? RESOLVE_UNCONFIRMED_RX :
RESOLVE_FAILED;
sender_done = true;
}
} else {
st.timeouts++;
sim_now_us = send_time_us + cfg.timeout_us;
}
(void)data_corrupted;
(void)duplicate_this_attempt;
}
if (!lg.acked) {
if (receiver_accepted_once) {
lg.final_status = RESOLVE_UNCONFIRMED_RX;
st.src_unconfirmed_rx++;
} else {
lg.final_status = RESOLVE_FAILED;
st.src_failed++;
}
lg.done_us = sim_now_us;
}
if ((seq % CSV_EVERY_NTH) == 0) {
Serial.print((unsigned long)lg.seq); Serial.print(",");
Serial.print((unsigned long)lg.t_gen_us); Serial.print(",");
Serial.print((unsigned long)lg.value); Serial.print(",");
Serial.print((int)lg.attempts); Serial.print(",");
Serial.print((int)lg.retries_used); Serial.print(",");
Serial.print((int)lg.acked); Serial.print(",");
Serial.print((int)lg.unique_rx); Serial.print(",");
Serial.print((int)lg.duplicate_rx); Serial.print(",");
Serial.print((int)lg.final_status); Serial.print(",");
Serial.print((unsigned long)lg.first_rx_latency_us); Serial.print(",");
Serial.println((unsigned long)lg.done_us);
}
}
Serial.println("#CSV_END");
printSummary(cfg, st, rx, sim_now_us);
}
void setup() {
Serial.begin(115200);
runSimulation();
}
void loop() {
delay(1000);
}