Files
security-new/INSTRUCTION.md
2026-07-22 14:03:44 +03:00

258 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Security Mesh Node — Architecture & Development Guide
## Project Structure
```
security-new/
├── platformio.ini # PlatformIO config (targets, libs, flags)
├── src/
│ ├── main.cpp # Main firmware — SecurityMesh class + loop
│ ├── target.h # Pin mappings, radio params, platform defs
│ └── variants/
│ └── promicro/ # ProMicro-specific files
├── boards/ # Custom linker scripts
├── scripts/ # Build helper scripts
├── .pio/ # Build output (ignored)
```
All MeshCore library lives at `../MeshCore/`.
---
## How Radio Mesh Works
### Low-level Layer (RadioLib)
- `radio_module` (SX1262) — raw LoRa send/receive.
- `radio_driver` (CustomSX1262Wrapper) — RadioLib wrapper. Provides `getCurrentRSSI()`, `setParams()`, `setTxPower()`.
### Mesh Layer (MeshCore)
- `mesh::Mesh` — main class that manages flooding, routing, encryption, channel storage.
- `mesh::Dispatcher` — base of Mesh (packet scheduling, radio control).
- `mesh::GroupChannel` — channel with `hash[1]` and `secret[32]`.
- `mesh::Packet` — a mesh packet. Obtained from `PacketManager` (pool of 16 slots).
### Lifecycle
```
1. radio_module.begin() — init SX1262
2. radio_driver.setParams(...) — freq, BW, SF, CR
3. Mesh.begin() — starts listening
4. Mesh.loop() loop() — pumps radio ISR, processes incoming
5. onGroupDataRecv() callback — called when encrypted group msg arrives
```
---
## SecurityMesh Class
### Channel Derivation (`setup_channel()`)
```
SHA256("1234") → 32 bytes key
mesh_channel.secret = key[0..15] (first 16 bytes)
mesh_channel.hash = SHA256(secret)[0..0] (first 1 byte, PATH_HASH_SIZE=1)
```
### Critical Virtual Overrides
#### `searchChannelsByHash()`
**WHY IT'S NEEDED:** `Mesh::loop()` calls `searchChannelsByHash()` to match incoming packet hashes against stored channels. The base implementation returns 0 (no matches). Without this override, **every incoming group message is silently dropped** and `onGroupDataRecv` is never called.
```cpp
int searchChannelsByHash(const uint8_t* hash, mesh::GroupChannel channels[], int max_matches) override {
if (max_matches > 0 && channel_ready
&& memcmp(hash, mesh_channel.hash, sizeof(mesh_channel.hash)) == 0) {
channels[0] = mesh_channel;
return 1; // found 1 match
}
return 0;
}
```
`sizeof(mesh_channel.hash)` = `PATH_HASH_SIZE` = 1 byte.
#### `onGroupDataRecv()`
Called for each decrypted group message that matched a channel. Format: `[4 bytes ts][1 byte type][text]`. Type byte must be 0 for text.
### Sending (`send_text()`)
```cpp
void send_text(const char* text) {
uint8_t buf[256];
buf[0..3] = timestamp (4 bytes)
buf[4] = 0 (type: text)
buf[5..] = text
Packet* pkt = createGroupDatagram(PAYLOAD_TYPE_GRP_TXT, mesh_channel, buf, 5 + text_len);
sendFlood(pkt, 0, 1); // 1 hop
}
```
`sendFlood` with `path_hash_size=1` means 0-hop (immediate neighbors only). Increase to flood across the mesh.
---
## Command Processing
All commands are processed in `onGroupDataRecv()`:
### Preprocessing
1. **Trim trailing spaces**`"status "``"status"`
2. **Lowercase**`"Status"``"status"`
3. **Strip prefix**`"Security: status"``"status"` (anything before `": "`)
4. **Trim leading spaces**
### Command Table
| Command | Action | Response |
|---------|--------|----------|
| `help` | list commands | `help:...` |
| `ohrana on` | arm, relay1 ON | `ohrana on ok` |
| `ohrana off` | disarm, relay1 OFF | `ohrana off ok` |
| `relay1 on` | relay1 ON (direct) | `relay1 on ok` |
| `relay1 off` | relay1 OFF (direct) | `relay1 off ok` |
| `relay2 on` | relay2 ON | `relay2 on ok` |
| `relay2 off` | relay2 OFF | `relay2 off ok` |
| `relay` | state of both relays | `relay:1on 2off` |
| `status` | report state | `status:armed/off temp:±X.XC hall:trig/ok motion:trig/ok bat:X.XXV/USB` |
| `ver` | build date | `build:Mon DD YYYY HH:MM:SS` |
| `scan` | noise floor | `noise:-XXXdBm` |
| `snr` | RSSI и SNR последнего пакета | `rssi:-78dBm snr:+6.5dB` |
| `uptime` | device uptime | `uptime:1d 3h 15m` |
| `kontrol <min>` | auto-status every N min | `kontrol 60 min ok` |
| `kontrol off` | disable auto-status | `kontrol off` |
| `reboot` | reboot device | `rebooting` |
| `time` | show RTC date/time (MSK, UTC+3) | `time:22.07.2026 11:00` |
| `time s DD.MM.YYYY HH.MM` | set RTC date/time | `time set 22.07.2026 11.00` |
### Response Delay
All command responses are sent with a **5-second delay** (non-blocking, via `defer_response`).
The action (relay toggle, armed state change) happens immediately — only the reply text is deferred.
Alerts (`alert:hall`, `alert:motion`) and startup (`poweron`) are sent immediately without delay.
### Special Commands
- **`snr`** — returns `rssi:-78dBm snr:+6.5dB` based on the last received packet. Uses `radio_driver.getLastRSSI()` and `pkt->getSNR()`. Stored in `last_rssi` / `last_snr` fields, updated on every `onGroupDataRecv`.
- **`reboot`** — sends `rebooting` immediately (not deferred), blinks LED 5 times, then `NVIC_SystemReset()`.
- **`uptime`** — calculates uptime from `g_boot_time` (set at boot). Format: `uptime:1d 3h 15m` (days/hours/minutes).
- **`kontrol <min>`** — sets `g_auto_status_interval = min * 60000`. In `loop()`, when `millis() - g_last_auto_status >= interval`, sends status. Range: 11440 minutes.
### Auto-Sent Messages
- **Startup:** `poweron Mon DD YYYY HH:MM:SS` — sent 10s after boot.
- **Hall alert:** `alert:hall` — when armed and reed opens.
- **Motion alert:** `alert:motion` — when armed and PIR triggers.
Cooldown: 10 minutes between alerts (ALERT_COOLDOWN_MS).
---
## Hardware
| Pin (nRF52) | Pin (ProMicro) | Function |
|-------------|----------------|----------|
| 30 | 13 | NSS (LoRa SPI CS) |
| 29 | 12 | SCK |
| 31 | 14 | MOSI |
| 2 | 15 | MISO |
| 7 | 11 | DIO1 (LoRa IRQ) |
| 6 | 10 | RST |
| 10 | 16 | BUSY |
| — | 21 | POWER_EN (ProMicro only) |
| 12 | 0 | REED (hall sensor) |
| 11 | 1 | PIR (motion) |
| 3 | 2 | DS18B20 (temp) |
| 4 | 3 | RELAY_1 |
| 5 | 4 | RELAY_2 |
| 13 | 22 | LED_STATUS |
**Reference:** `target.h` lines 25114.
---
## Radio Config
| Param | Value |
|-------|-------|
| Frequency | 868.731018 MHz |
| Bandwidth | 62.5 kHz |
| Spreading Factor | 7 |
| Coding Rate | 7 (4/7) |
| TX Power | 22 dBm |
| TCXO voltage | 1.8V |
| Current limit | 140 mA |
| RX Boosted Gain | on |
| DIO2 as RF switch | on |
`PATH_HASH_MODE=0``PATH_HASH_SIZE=1` (minimal hash, maximum throughput).
---
## Build & Upload
```bash
# Build
pio run -e promicro_security
# Upload (via nrfutil)
pio run -e promicro_security -t upload
# Monitor
pio device monitor -b 115200
```
Current resource usage: RAM 8.1%, Flash 36.8%.
---
## Common Pitfalls & Debugging
### `onGroupDataRecv` not called despite LED blink
The LED blink (3 fast flashes) is the first thing in `onGroupDataRecv`. If LED doesn't blink:
- Check `searchChannelsByHash()` override — it's required.
- Check hash match: both sides must derive the same `secret` and `hash` from passphrase.
- Check radio config matches (freq, BW, SF, CR).
### No response despite LED blinking
- Command was received (LED blinked) but processing or send failed.
- **Test with simple response** — if `send_text("ok")` fails but `send_text("relay2 on ok")` works, check response length/SNPRINTF.
- `sendFlood` with `path_hash_size=1` sends only to neighbors. If response doesn't arrive at the remote app, check app's mesh routing or increase path_hash_size.
### `send_text()` doesn't actually send
- `createGroupDatagram` may return NULL if packet pool is full (16 slots).
- `rtc_clock.getCurrentTime()` returning bogus data (VolatileRTCClock starts from 0, fine).
### Adding a new command
1. Add `else if` block in `onGroupDataRecv()`.
2. Use `strcmp(cmd, "command") == 0` for exact match.
3. Call `send_text("response")` to reply.
4. Update the `help` string.
### Config persistent storage
- `InternalFS` (LittleFS) on nRF52 internal flash.
- `SecurityConfig`: armed + relay2 state saved as binary struct.
- File: `/security.cfg`, magic `0x53454355`.
---
## Key Files in MeshCore Library
| File | What it provides |
|------|-----------------|
| `MeshCore/src/Mesh.h` | `Mesh`, `GroupChannel`, virtual methods |
| `MeshCore/src/MeshCore.h` | Constants (`PUB_KEY_SIZE`, `PATH_HASH_SIZE`, etc.) |
| `MeshCore/src/Dispatcher.h` | `Dispatcher`, packet scheduling |
| `MeshCore/src/helpers/SimpleMeshTables.h` | Duplicate packet detection |
| `MeshCore/src/helpers/StaticPoolPacketManager.h` | Packet memory pool |
| `MeshCore/src/helpers/ChannelDetails.h` | Channel helper structs |
| `MeshCore/src/helpers/NRF52Board.h` | nRF52 board abstraction |
---
## Things That Won't Work
- **BLE** — not included. No `BLESerialInterface`, no `BaseChatMesh`.
- **`BaseChatMesh`** — not used. SecurityMesh inherits directly from `mesh::Mesh`.
- **Serial commands** — not processed. Serial is for debug output only.
- **`onPeerDataRecv`** — not used. Group messaging only, no direct peer messages.