22#include <LiquidCrystal_AIP31068_I2C.h>
105 while (Serial.available() > 0) {
139 Serial.write(
reinterpret_cast<const uint8_t*
>(&hello),
sizeof(
HelloPacket));
142void sendPacket(uint8_t noteIndex, uint16_t noteDurationMs, uint16_t seqNumber) {
148 packetNonce[10] ^=
static_cast<uint8_t
>((seqNumber >> 8u) & 0xFFu);
149 packetNonce[11] ^=
static_cast<uint8_t
>(seqNumber & 0xFFu);
160 const uint8_t aad[3] = {
162 static_cast<uint8_t
>(seqNumber & 0xFFu),
163 static_cast<uint8_t
>((seqNumber >> 8u) & 0xFFu)
169 const uint16_t plaintext[2] = {
170 static_cast<uint16_t
>(noteIndex),
178 s_cipher.addAuthData(aad,
sizeof(aad));
180 reinterpret_cast<uint8_t*
>(pkt.
payload),
181 reinterpret_cast<const uint8_t*
>(plaintext),
189 uint8_t full_mac[16];
199 Serial.write(
reinterpret_cast<const uint8_t*
>(&pkt),
sizeof(
DataPacket));
235 lcd.print(seqNumber);
270 packetNonce[10] ^=
static_cast<uint8_t
>((
seqNum >> 8u) & 0xFFu);
271 packetNonce[11] ^=
static_cast<uint8_t
>(
seqNum & 0xFFu);
283 const uint8_t aad[3] = {
285 static_cast<uint8_t
>(
seqNum & 0xFFu),
286 static_cast<uint8_t
>((
seqNum >> 8u) & 0xFFu)
290 const uint16_t plaintext[2] = {0u, 0u};
294 s_cipher.addAuthData(aad,
sizeof(aad));
296 reinterpret_cast<uint8_t*
>(finPkt.
payload),
297 reinterpret_cast<const uint8_t*
>(plaintext),
302 uint8_t full_mac[16];
310 Serial.write(
reinterpret_cast<const uint8_t*
>(&finPkt),
sizeof(
DataPacket));
329static inline uint32_t
mixEntropy(uint32_t pool, uint32_t bits) {
330 return ((pool << 1u) | (pool >> 31u)) ^ bits;
348 const uint8_t* sramBase =
reinterpret_cast<const uint8_t*
>(0x0100);
350 for (uint8_t wordIndex = 0; wordIndex < 8u; ++wordIndex) {
362 for (uint8_t i = 0; i < 8u; ++i) {
363 pool =
mixEntropy(pool, sramBase[wordIndex * 8u + i]);
368 const uint8_t savedAdmux = ADMUX;
369 ADMUX = _BV(REFS1) | _BV(REFS0) | _BV(MUX3);
372 ADCSRA |= _BV(ADSC);
while (ADCSRA & _BV(ADSC)) {}
373 uint8_t adcEntropy = 0;
374 for (uint8_t i = 0; i < 8u; ++i) {
375 ADCSRA |= _BV(ADSC);
while (ADCSRA & _BV(ADSC)) {}
376 adcEntropy =
static_cast<uint8_t
>((adcEntropy << 1u) | (ADCL & 0x01u));
383 pool =
mixEntropy(pool,
static_cast<uint32_t
>(TCNT1));
387 uint8_t a0Entropy = 0;
388 for (uint8_t i = 0; i < 8u; ++i) {
389 a0Entropy =
static_cast<uint8_t
>(
390 (a0Entropy << 1u) | (static_cast<uint8_t>(analogRead(A0)) & 0x01u)
400 pool =
mixEntropy(pool,
static_cast<uint32_t
>(random()));
403 outputSeed[wordIndex * 4u + 0u] =
static_cast<uint8_t
>(pool);
404 outputSeed[wordIndex * 4u + 1u] =
static_cast<uint8_t
>(pool >> 8u);
405 outputSeed[wordIndex * 4u + 2u] =
static_cast<uint8_t
>(pool >> 16u);
406 outputSeed[wordIndex * 4u + 3u] =
static_cast<uint8_t
>(pool >> 24u);
429 lcd.print(
"LINK LOST!");
431 lcd.print(
"RECONNECTING...");
469 uint8_t entropySeed[32];
472 memset(entropySeed, 0,
sizeof(entropySeed));
515 if (Serial.available() > 0) {
516 const uint8_t response =
static_cast<uint8_t
>(Serial.read());
572 if (Serial.available() > 0) {
573 const uint8_t response =
static_cast<uint8_t
>(Serial.read());
645 if (Serial.available() > 0) {
646 const uint8_t response =
static_cast<uint8_t
>(Serial.read());
uint32_t getSecureRandom32()
Return 32 bits of CSPRNG output (non-blocking).
void initCSPRNG(const uint8_t *hardwareSeed)
Seed the ChaCha20 CSPRNG with 256 bits of hardware entropy.
static uint8_t pendingNoteIndex
Snapshot of the last transmitted note index — allows retransmission without re-reading PROGMEM.
static volatile uint32_t s_ringOscPulses
Ring oscillator pulse counter — incremented by the INT0 ISR on pin 2.
static uint16_t seqNum
Packet sequence number (0–65535, wraps).
static void onRingOscPulse()
INT0 interrupt service routine — counts ring oscillator rising edges.
static uint16_t pendingNoteDuration
Snapshot of the last transmitted note duration (ms) — allows retransmission without re-reading PROGME...
void suspendSession()
Perform an unclean session teardown and enter the Self-Healing reconnect loop.
void updateTxDisplay(TxState state, uint16_t seqNumber, uint8_t retries)
Refresh the TX LCD with the current ARQ status and FSM state label.
static void drainRxFifo()
Discard all bytes currently waiting in the hardware UART RX FIFO.
static uint32_t noteWaitStart
millis() timestamp marking the start of the current WAIT_BETWEEN_NOTES pause.
static uint32_t mixEntropy(uint32_t pool, uint32_t bits)
One mixing step of the entropy accumulation sponge.
void setup()
Arduino sketch entry point — delegates to tx_setup().
static uint16_t melodyIndex
Current position in melody[][] (preserved across RECONNECTING).
void sendHelloPacket()
Generate a fresh 12-byte CSPRNG nonce and broadcast it as a FLAG_SYN HelloPacket.
void sendFinPacket()
Construct and transmit a FLAG_FIN session-teardown packet.
static uint32_t lastReconnectAttempt
Auto-reconnect countdown; a new HelloPacket is broadcast when (millis() - lastReconnectAttempt) >= RE...
static bool btnLastRawState
Raw digitalRead() result from the previous call.
static uint8_t retryCount
Consecutive retransmission counter (displayed on LCD).
static uint32_t ackWaitStart
millis() timestamp when the current ACK-wait window opened.
static uint8_t s_sessionNonce[HELLO_NONCE_SIZE]
96-bit session nonce generated once per button press by sendHelloPacket().
void generateEntropyPool(uint8_t *outputSeed)
Harvest 256 bits of hardware entropy and write them to outputSeed (TX variant).
static uint32_t btnLastChangeMs
millis() timestamp of the last raw state transition.
void tx_loop()
Execute one non-blocking C2P-ARQ FSM tick. Called repeatedly from loop().
static bool btnStableState
Debounce-confirmed stable button state.
static LiquidCrystal_AIP31068_I2C lcd(TX_LCD_ADDR, TX_LCD_COLS, TX_LCD_ROWS)
I2C LCD — 16 columns x 2 rows, Aip31068-compatible controller.
void tx_setup()
Initialise TX hardware and seed the CSPRNG. Called once from setup().
static ChaChaPoly s_cipher
ChaCha20-Poly1305 cipher instance — re-initialised per packet via clear().
bool readButtonPress()
Read and debounce the start button (millis-based falling-edge detector).
static TxState currentState
Active FSM state.
void formAndSendPacket(uint8_t note_idx, uint16_t duration_ms)
Snapshot the note payload and delegate to sendPacket() for encryption and transmission.
void loop()
Arduino sketch main loop — delegates to tx_loop() on every iteration.
void sendPacket(uint8_t noteIndex, uint16_t noteDurationMs, uint16_t seqNumber)
const uint32_t ACK_TIMEOUT_MS
Maximum milliseconds TX waits for an ACK before retransmitting.
const uint8_t MAX_RETRIES
Retransmission limit per packet; exhausting this triggers suspendSession().
const uint8_t FLAG_FIN
Session close — instructs RX to teardown the current session.
const uint8_t FLAG_DAT
Data frame — DataPacket carrying an encrypted note (index + duration).
const uint8_t FLAG_SYN
Session open — HelloPacket carrying the 12-byte CSPRNG nonce.
const uint8_t HELLO_NONCE_SIZE
ChaCha20 IV length in bytes (IETF 96-bit nonce format).
const uint8_t DATA_PAYLOAD_SIZE
Byte size of the encrypted payload in a DataPacket.
const uint8_t TRUNCATED_MAC_SIZE
Bytes of Poly1305 MAC actually placed on the wire (first 8 of 16).
const uint8_t MASTER_PSK[32]
256-bit (32-byte) Pre-Shared Master Key for ChaCha20-Poly1305 AEAD.
const uint8_t NACK_BYTE
Negative acknowledgement — MAC mismatch; TX must retransmit.
const uint8_t ACK_BYTE
Positive acknowledgement — MAC verified, note played (or FIN accepted).
const uint8_t SYNC_BYTE_2
Second synchronisation byte (preamble marker).
const uint8_t SYNC_BYTE_1
First synchronisation byte (preamble marker).
const uint8_t TX_BUTTON_PIN
Tactile start button (INPUT_PULLUP, active LOW).
const uint8_t TX_LCD_COLS
Number of columns on the TX LCD.
const uint8_t ENTROPY_RING_OSC_PIN
Hardware ring oscillator entropy source (INT0).
const uint8_t TX_LCD_ROWS
Number of rows on the TX LCD.
const uint8_t TX_LCD_ADDR
I2C address of the Aip31068 16x2 LCD display.
const uint16_t DEBOUNCE_DELAY_MS
const uint32_t RECONNECT_INTERVAL_MS
Auto-reconnect HelloPacket broadcast interval (ms) while in RECONNECTING state.
const uint16_t BAUD_RATE
UART baud rate in bps (8N1). Chosen for clear bit-width visibility on SimulIDE oscilloscope.
PROGMEM melody data for SH2SC-EDT — Transmitter Node A.
static const uint16_t MELODY_LENGTH
Total number of note/rest entries in the PROGMEM melody table.
static const uint16_t melody[MELODY_LENGTH][2]
PROGMEM melody table — note indices and durations for the Imperial March.
Authenticated data packet (FLAG_DAT) and session-close packet (FLAG_FIN).
uint16_t payload[2]
ChaCha20-encrypted payload: [0] = note_index, [1] = duration_ms.
uint8_t flags
Frame type — FLAG_DAT (0x02) for data; FLAG_FIN (0x04) for teardown. Part of plaintext AAD.
uint16_t seq_num
16-bit packet sequence number (little-endian). Part of plaintext AAD.
uint8_t mac[TRUNCATED_MAC_SIZE]
First 8 bytes of the Poly1305 authentication tag.
Session-open handshake packet (FLAG_SYN).
uint8_t nonce[HELLO_NONCE_SIZE]
96-bit session nonce for ChaChaPoly per-packet IV derivation.
uint8_t flags
Frame type — must equal FLAG_SYN (0x01).
SH2SC-EDT — Transmitter Node A ("The Conductor") public interface.
TxState
Finite State Machine states for the C2P-ARQ Transmitter (Node A).
@ SENDING_FIN
All notes delivered; transmitting the FLAG_FIN teardown packet.
@ WAITING_ACK
DataPacket sent; listening on the feedback line for ACK or NACK.
@ SENDING_HELLO
Generating a CSPRNG nonce and transmitting the SYN handshake packet.
@ IDLE
Waiting for a button press to begin melody playback.
@ SENDING
Constructing, encrypting, and transmitting the current DataPacket.
@ WAITING_HELLO_ACK
HelloPacket sent; awaiting RX nonce-acceptance ACK.
@ WAIT_BETWEEN_NOTES
ACK received; holding the inter-note gap before advancing melodyIndex.
@ RECONNECTING
Link lost mid-melody; broadcasting HelloPackets every RECONNECT_INTERVAL_MS.
@ WAITING_FIN_ACK
FIN packet sent; awaiting RX acknowledgement before erasing the session nonce.