# Batch A — Integration Notes for T1 RID Sensor Firmware

Files shipped in this batch (new files only — no existing files modified):

```
src/sensor_location.h
src/sensor_location.cpp
src/watchdog.h
src/watchdog.cpp
src/jamming_detector.h
src/jamming_detector.cpp
test/test_batch_a/test_batch_a.cpp
```

---

## 1 — sensor_location: fixed lat/lon persistence

### config.h / command_handler.h

Add `SET_LOCATION` to the `SensorCmdType` enum in `command_handler.h`
immediately after the last existing entry:

```cpp
// in enum class SensorCmdType : uint8_t { ... }
SET_LOCATION = 19,  // params: int32_le lat (4 bytes) | int32_le lon (4 bytes)
```

### main.cpp — replace hard-coded zero coordinates

Find the detection event build block (search for `evt.lat_fixed = 0`):

```cpp
// BEFORE
evt.lat_fixed  = 0;
evt.lon_fixed  = 0;

// AFTER
#include "sensor_location.h"
location_get(&evt.lat_fixed, &evt.lon_fixed);
```

### setup() in main.cpp

Call `location_init()` after NVS and LittleFS initialisation:

```cpp
location_init();
```

### command_handler.cpp — add dispatch case

In `dispatchCommand()`, inside the `switch ((SensorCmdType)pkt.cmd_type)` block:

```cpp
case SensorCmdType::SET_LOCATION: {
    if (pkt.param_len < 8) {
        ack_out.status = (uint8_t)CmdAckStatus::INVALID;
        return false;
    }
    int32_t lat, lon;
    memcpy(&lat, &pkt.params[0], 4);
    memcpy(&lon, &pkt.params[4], 4);
    bool ok = location_set(lat, lon);
    ack_out.status      = ok ? (uint8_t)CmdAckStatus::OK
                             : (uint8_t)CmdAckStatus::FAILED;
    ack_out.result_code = ok ? 1u : 0u;
    return true;
}
```

Wire format note: params bytes are `int32_le lat | int32_le lon`, total 8 bytes.
T2's `sensor_commander.py` should pack with `struct.pack('<ii', lat, lon)`.

---

## 2 — watchdog: task WDT + crash coredump handler

### setup() in main.cpp

Call after all subsystem inits (LoRa, LittleFS, model manager, etc.) so the
WDT timeout starts from a clean state. Coredump handler must come after the
LittleFS mount:

```cpp
#include "watchdog.h"

// ... existing subsystem inits ...
watchdog_register_panic_dump();   // must be after LittleFS.begin()
watchdog_init(30);                // 30-second task watchdog
```

### loop() in main.cpp

Call once near the top of the main loop body (before any blocking sections):

```cpp
watchdog_feed();
```

### ESP-IDF version note

`esp_set_panic_handler()` is available in IDF 5.x (ESP32-S3 targets).
For IDF 4.x targets the `coredump_panic_handler` is compiled in but not
automatically registered; if the project targets an IDF 4.x board, add a
custom `esp_panic_handler_t` linker override or call from the abort hook.

### Coredump file rotation

Files written to LittleFS at `/coredump_0.bin`, `/coredump_1.bin`, `/coredump_2.bin`.
Format: 32-byte header (see watchdog.cpp for layout). T2's health check can
retrieve these via an OTA read command and report to the C2.

---

## 3 — jamming_detector: counter-RF alarm

### config.h — add message type

```cpp
#define MSG_TYPE_JAMMING_ALERT  0x1A  // 8-byte broadcast: see jamming_detector.h
```

### setup() in main.cpp

```cpp
#include "jamming_detector.h"
jamming_init();
```

### loop() / WiFi sniffer callback in main.cpp

Feed the WiFi channel RSSI from the existing `wifi_sniffer_cb`:

```cpp
// Inside wifi_sniffer_cb (or channel-scan aggregation loop):
jamming_observe(s_scan_channel, pkt->rx_ctrl.rssi, true);
```

Feed LoRa TX/RX outcomes from the LoRa RX handler and after each TX:

```cpp
// On successful LoRa receive:
jamming_observe(0, (int16_t)rssi, true);

// On LoRa RX timeout / CRC error:
jamming_observe(0, (int16_t)rssi, false);
```

### Alarm query (optional — for health report inclusion)

```cpp
uint8_t jam_reason = 0;
if (jamming_is_alarmed(&jam_reason)) {
    // Log / include in health_report
}
```

### Mesh alert packet wire format (MSG_TYPE_JAMMING_ALERT = 0x1A)

```
Byte  0     : 0x1A  (message type)
Bytes 1..4  : sensor_id  (uint32 little-endian)
Byte  5     : reason code  (0=ok, 1=high_loss, 2=spectrum_anomaly, 3=noise_floor_jump)
Bytes 6..7  : elapsed_sec since last alert (uint16 little-endian)
```

T2 `sensor_commander.py` should unpack with `struct.unpack_from('<BIBB H', pkt, 0)`.

### Rate-limiting

The module suppresses duplicate broadcasts for `JAMMING_ALERT_COOLDOWN_S` (60)
seconds after each broadcast. The alarm state (`jamming_is_alarmed()`) continues
to reflect the live evaluation between broadcasts.

---

## 4 — platformio.ini: add test_batch_a to test_filter

In the `[env:native]` section, add `test_batch_a` to the `test_filter` list:

```ini
test_filter =
    test_command_handler
    test_spoof_pec
    test_markov_scorer
    test_wfx_features
    test_batch_a
```

Run the new tests with:

```
pio test -e native -f test_batch_a
```
