Initial commit: Security Mesh Node

This commit is contained in:
UA1ZBE
2026-07-22 07:56:45 +03:00
commit 965c3e9e30
19 changed files with 1565 additions and 0 deletions

236
INSTRUCTION.md Normal file
View File

@@ -0,0 +1,236 @@
# 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` |
| `status` | report state | `status:armed/off temp:±X.XC hall:trig/ok motion:trig/ok` |
| `ver` | build date | `build:Mon DD YYYY HH:MM:SS` |
| `scan` | noise floor | `noise:-XXXdBm` |
| `security_conf <name>` | change channel | `channel:<name>` |
### 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%, Flash 36.5%.
---
## 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()` after line 168.
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.