Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c754bfb7f8 | ||
|
|
99e684dd4f | ||
|
|
6e77419828 | ||
|
|
166fffa2ce | ||
|
|
4a05d81585 | ||
|
|
d215c12038 | ||
|
|
966ef26c5e | ||
|
|
7886291759 | ||
|
|
aea9860bbf | ||
|
|
92427da26a | ||
|
|
b7857a029b | ||
|
|
e94dbcfb55 | ||
|
|
0cbb75853d | ||
|
|
287da12a83 | ||
|
|
db8a358573 | ||
|
|
c576c571e5 | ||
|
|
307e27de60 | ||
|
|
2909d069d0 | ||
|
|
6978394307 | ||
|
|
ade6f3d433 |
@@ -112,10 +112,28 @@ All commands are processed in `onGroupDataRecv()`:
|
|||||||
| `relay1 off` | relay1 OFF (direct) | `relay1 off ok` |
|
| `relay1 off` | relay1 OFF (direct) | `relay1 off ok` |
|
||||||
| `relay2 on` | relay2 ON | `relay2 on ok` |
|
| `relay2 on` | relay2 ON | `relay2 on ok` |
|
||||||
| `relay2 off` | relay2 OFF | `relay2 off ok` |
|
| `relay2 off` | relay2 OFF | `relay2 off ok` |
|
||||||
| `status` | report state | `status:armed/off temp:±X.XC hall:trig/ok motion:trig/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` |
|
| `ver` | build date | `build:Mon DD YYYY HH:MM:SS` |
|
||||||
| `scan` | noise floor | `noise:-XXXdBm` |
|
| `scan` | noise floor | `noise:-XXXdBm` |
|
||||||
| `security_conf <name>` | change channel | `channel:<name>` |
|
| `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 | `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
|
||||||
|
|
||||||
|
- **`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: 1–1440 minutes.
|
||||||
|
|
||||||
### Auto-Sent Messages
|
### Auto-Sent Messages
|
||||||
- **Startup:** `poweron Mon DD YYYY HH:MM:SS` — sent 10s after boot.
|
- **Startup:** `poweron Mon DD YYYY HH:MM:SS` — sent 10s after boot.
|
||||||
|
|||||||
51
README.md
Normal file
51
README.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Security Mesh Node
|
||||||
|
|
||||||
|
LoRa mesh-устройство охраны на базе nRF52840 (ProMicro) и SX1262.
|
||||||
|
Работает по радиоканалу через MeshCore — без интернета, Bluetooth и проводов.
|
||||||
|
|
||||||
|
## Возможности
|
||||||
|
|
||||||
|
- Управление двумя реле (охрана + независимое)
|
||||||
|
- Датчик Холла (геркон) — оповещение об открытии двери/окна
|
||||||
|
- PIR датчик движения
|
||||||
|
- DS18B20 температура
|
||||||
|
- Светодиодная индикация состояния
|
||||||
|
- Сохранение конфигурации в энергонезависимую память
|
||||||
|
- Смена канала через mesh-команду
|
||||||
|
|
||||||
|
## Аппарат
|
||||||
|
|
||||||
|
- **SoC:** nRF52840 (Adafruit ProMicro nRF52840)
|
||||||
|
- **Радио:** SX1262 (868 МГц, SF7, 62.5 кГц)
|
||||||
|
- **Память:** 256 КБ ОЗУ, 712 КБ Flash (занято 36%)
|
||||||
|
|
||||||
|
## Команды
|
||||||
|
|
||||||
|
| Команда | Действие |
|
||||||
|
|---------|---------|
|
||||||
|
| `ohrana on` | Включить охрану |
|
||||||
|
| `ohrana off` | Выключить охрану |
|
||||||
|
| `relay1 on/off` | Управление реле 1 |
|
||||||
|
| `relay2 on/off` | Управление реле 2 |
|
||||||
|
| `relay` | Состояние обоих реле |
|
||||||
|
| `status` | Состояние устройства (включая bat) |
|
||||||
|
| `ver` | Версия прошивки |
|
||||||
|
| `scan` | Уровень шума в эфире |
|
||||||
|
| `uptime` | Время работы |
|
||||||
|
| `kontrol <min>` | Автоотправка статуса каждые N мин |
|
||||||
|
| `kontrol off` | Отключить автоотправку |
|
||||||
|
| `reboot` | Перезагрузить устройство |
|
||||||
|
| `time` | Показать дату/время |
|
||||||
|
| `time s DD.MM.YYYY HH.MM` | Установить дату/время |
|
||||||
|
| `help` | Список команд |
|
||||||
|
|
||||||
|
## Релизы
|
||||||
|
|
||||||
|
Готовые прошивки (.hex, .zip, .uf2) — в [Releases](https://git2.ua1zbe.ru/ua1zbe/security-new/releases).
|
||||||
|
|
||||||
|
## Сборка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pio run -e promicro_security
|
||||||
|
pio run -e promicro_security -t upload
|
||||||
|
```
|
||||||
@@ -30,16 +30,22 @@ Security Node — LoRa mesh-устройство, которое следит з
|
|||||||
| `relay1 off` | Выключить реле 1 | `relay1 off ok` |
|
| `relay1 off` | Выключить реле 1 | `relay1 off ok` |
|
||||||
| `relay2 on` | Включить реле 2 | `relay2 on ok` |
|
| `relay2 on` | Включить реле 2 | `relay2 on ok` |
|
||||||
| `relay2 off` | Выключить реле 2 | `relay2 off ok` |
|
| `relay2 off` | Выключить реле 2 | `relay2 off ok` |
|
||||||
| `status` | Показать состояние устройства | `status:armed temp:+23.5C hall:ok motion:trig` |
|
| `relay` | Состояние обоих реле | `relay:1on 2off` |
|
||||||
|
| `status` | Показать состояние устройства | `status:armed temp:+23.5C hall:ok motion:trig bat:4.12V` |
|
||||||
| `ver` | Версия прошивки (дата сборки) | `build:Jul 21 2026 15:30:00` |
|
| `ver` | Версия прошивки (дата сборки) | `build:Jul 21 2026 15:30:00` |
|
||||||
| `scan` | Измерить уровень шума в эфире | `noise:-112dBm` |
|
| `scan` | Измерить уровень шума в эфире | `noise:-112dBm` |
|
||||||
| `security_conf <имя>` | Сменить канал (пароль из `<имя>`) | `channel:mysecret` |
|
| `uptime` | Время работы устройства | `uptime:1d 3h 15m` |
|
||||||
|
| `kontrol <мин>` | Автоотправка статуса каждые N мин | `kontrol 60 min ok` |
|
||||||
|
| `kontrol off` | Отключить автоотправку статуса | `kontrol off` |
|
||||||
|
| `reboot` | Перезагрузить устройство | `rebooting` |
|
||||||
|
| `time` | Показать дату и время | `time:22.07.2026 11:00` |
|
||||||
|
| `time s DD.MM.YYYY HH.MM` | Установить дату и время | `time set 22.07.2026 11.00` |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Поля статуса
|
## Поля статуса
|
||||||
|
|
||||||
Ответ: `status:armed/off temp:+XX.XC/N/A hall:trig/ok motion:trig/ok`
|
Ответ: `status:armed/off temp:+XX.XC/N/A hall:trig/ok motion:trig/ok bat:X.XXV/USB`
|
||||||
|
|
||||||
| Поле | Значение |
|
| Поле | Значение |
|
||||||
|------|----------|
|
|------|----------|
|
||||||
@@ -51,6 +57,8 @@ Security Node — LoRa mesh-устройство, которое следит з
|
|||||||
| `hall:trig` | Геркон разомкнулся (дверь/окно открыли) |
|
| `hall:trig` | Геркон разомкнулся (дверь/окно открыли) |
|
||||||
| `motion:ok` | Движения нет |
|
| `motion:ok` | Движения нет |
|
||||||
| `motion:trig` | Движение обнаружено |
|
| `motion:trig` | Движение обнаружено |
|
||||||
|
| `bat:4.12V` | Напряжение батареи |
|
||||||
|
| `bat:USB` | Питание от USB (нет батареи) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -99,21 +107,46 @@ Security Node — LoRa mesh-устройство, которое следит з
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Смена канала (`security_conf`)
|
## Автоотправка статуса (`kontrol`)
|
||||||
|
|
||||||
|
`kontrol 60` — устройство будет само отправлять `status` каждые 60 минут.
|
||||||
|
`kontrol off` — отключить. Интервал от 1 до 1440 минут (1 минута — 24 часа).
|
||||||
|
|
||||||
|
## Синхронизация времени (`time`)
|
||||||
|
|
||||||
|
`time` — показать текущее время устройства.
|
||||||
|
`time s 22.07.2026 11.00` — установить дату и время. Формат: `time s DD.MM.YYYY HH.MM`
|
||||||
|
Время используется для отметок в сообщениях. После перезагрузки сбрасывается (если нет внешнего RTC).
|
||||||
|
|
||||||
|
## Перезагрузка (`reboot`)
|
||||||
|
|
||||||
|
`reboot` — устройство отправит `rebooting` и перезагрузится.
|
||||||
|
|
||||||
|
## Время работы (`uptime`)
|
||||||
|
|
||||||
|
`uptime` — показывает, сколько времени устройство работает без перезагрузки.
|
||||||
|
Пример: `uptime:1d 3h 15m` (1 день, 3 часа, 15 минут).
|
||||||
|
|
||||||
|
## Смена канала (`canal`)
|
||||||
|
|
||||||
По умолчанию канал формируется из пароля `"1234"`.
|
По умолчанию канал формируется из пароля `"1234"`.
|
||||||
Чтобы сменить:
|
Чтобы сменить:
|
||||||
|
|
||||||
```
|
```
|
||||||
security_conf мойпароль
|
canal домашний 5674
|
||||||
```
|
```
|
||||||
|
|
||||||
Ответ: `channel:мойпароль`
|
Ответ: `canal домашний 5674 ok`
|
||||||
|
Через ~5 секунд устройство перезагрузится и начнёт работать на новом канале.
|
||||||
|
|
||||||
**Важно:** Все устройства должны быть на одном канале. После смены канала связь будет только с теми узлами, у которых такой же пароль.
|
**Важно:** Все устройства должны быть на одном канале. После смены канала связь будет только с теми узлами, у которых такой же пароль. Канал сохраняется в энергонезависимой памяти и не сбрасывается после перезагрузки. Чтобы вернуть канал по умолчанию — используйте `canal default 1234`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Задержка ответа
|
||||||
|
|
||||||
|
Ответ на любую команду приходит **через 5 секунд**. Само действие (включение реле, смена охраны) выполняется мгновенно, но ответ отправляется с задержкой. Оповещения и `poweron` отправляются без задержки.
|
||||||
|
|
||||||
## Советы
|
## Советы
|
||||||
|
|
||||||
- Если команда не получила ответа: проверьте канал, питание, дальность связи. LED должен мигнуть 3 раза при получении команды — если не мигнул, сообщение не дошло.
|
- Если команда не получила ответа: проверьте канал, питание, дальность связи. LED должен мигнуть 3 раза при получении команды — если не мигнул, сообщение не дошло.
|
||||||
|
|||||||
160
WIRING.md
Normal file
160
WIRING.md
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
# Схема подключения — Security Mesh Node (ProMicro nRF52840)
|
||||||
|
|
||||||
|
Все пины ниже указаны по **маркировке на плате** (nRF52840 GPiO).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Сопоставление пинов
|
||||||
|
|
||||||
|
| В коде (Arduino pin) | На плате (nRF) | Куда подключено |
|
||||||
|
|----------------------|----------------|-----------------|
|
||||||
|
| 0 | **P0.08** | Геркон (reed switch) |
|
||||||
|
| 1 | **P0.06** | PIR датчик движения |
|
||||||
|
| 2 | **P0.17** | DS18B20 температура |
|
||||||
|
| 3 | **P0.20** | Реле 1 (охрана) |
|
||||||
|
| 4 | **P0.22** | Реле 2 |
|
||||||
|
| 10 | **P0.09** | SX1262 RST |
|
||||||
|
| 11 | **P0.10** | SX1262 DIO1 |
|
||||||
|
| 12 | **P1.11** | SX1262 SCK |
|
||||||
|
| 13 | **P1.13** | SX1262 NSS |
|
||||||
|
| 14 | **P1.15** | SX1262 MOSI |
|
||||||
|
| 15 | **P0.02** | SX1262 MISO |
|
||||||
|
| 16 | **P0.29** | SX1262 BUSY |
|
||||||
|
| 21 | **P0.13** | SX1262 POWER_EN |
|
||||||
|
| 22 | **P0.15** | LED статуса (встроенный) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SX1262 LoRa модуль
|
||||||
|
|
||||||
|
```
|
||||||
|
Плата (nRF) ───── SX1262
|
||||||
|
────────────────────────────────
|
||||||
|
P1.13 ────────── NSS
|
||||||
|
P1.11 ────────── SCK
|
||||||
|
P1.15 ────────── MOSI
|
||||||
|
P0.02 ────────── MISO
|
||||||
|
P0.10 ────────── DIO1
|
||||||
|
P0.09 ────────── RST
|
||||||
|
P0.29 ────────── BUSY
|
||||||
|
P0.13 ────────── POWER_EN (если есть, иначе VCC)
|
||||||
|
3.3V ────────── VCC
|
||||||
|
GND ────────── GND
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Важно:** SX1262 питается от 3.3В. Не подключайте к 5В!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Датчики
|
||||||
|
|
||||||
|
### Геркон (Reed) — P0.08
|
||||||
|
|
||||||
|
```
|
||||||
|
P0.08 ──┬──────────── GND
|
||||||
|
│
|
||||||
|
╱
|
||||||
|
╲ 10kΩ (подтяжка к 3.3В, либо INPUT_PULLUP)
|
||||||
|
╱
|
||||||
|
│
|
||||||
|
3.3V
|
||||||
|
|
||||||
|
Геркон: один конец на P0.08, второй на GND.
|
||||||
|
При замыкании — P0.08 соединяется с GND.
|
||||||
|
```
|
||||||
|
|
||||||
|
### PIR датчик движения — P0.06
|
||||||
|
|
||||||
|
```
|
||||||
|
PIR датчик:
|
||||||
|
VCC ── 3.3V или 5V (по питанию датчика)
|
||||||
|
GND ── GND
|
||||||
|
OUT ── P0.06
|
||||||
|
```
|
||||||
|
|
||||||
|
### DS18B20 — P0.17
|
||||||
|
|
||||||
|
```
|
||||||
|
DS18B20:
|
||||||
|
VCC ── 3.3В
|
||||||
|
GND ── GND
|
||||||
|
DQ ── P0.17
|
||||||
|
|
||||||
|
┌─ 4.7kΩ
|
||||||
|
P0.17 ────┤
|
||||||
|
└── 3.3В
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Реле
|
||||||
|
|
||||||
|
### Реле 1 (охрана) — P0.20
|
||||||
|
|
||||||
|
```
|
||||||
|
P0.20 ── IN1 релейного модуля
|
||||||
|
|
||||||
|
Релейный модуль:
|
||||||
|
VCC ── 5V (внешний источник)
|
||||||
|
GND ── GND
|
||||||
|
IN1 ── P0.20
|
||||||
|
```
|
||||||
|
|
||||||
|
> Реле 1 управляется `ohrana on/off` и `relay1 on/off`.
|
||||||
|
|
||||||
|
### Реле 2 — P0.22
|
||||||
|
|
||||||
|
```
|
||||||
|
P0.22 ── IN2 релейного модуля
|
||||||
|
|
||||||
|
Релейный модуль:
|
||||||
|
VCC ── 5V
|
||||||
|
GND ── GND
|
||||||
|
IN2 ── P0.22
|
||||||
|
```
|
||||||
|
|
||||||
|
> Реле 2 управляется только `relay2 on/off`. Состояние сохраняется.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## LED статуса — P0.15
|
||||||
|
|
||||||
|
Встроенный красный светодиод на плате (P0.15).
|
||||||
|
Активный уровень — HIGH.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Общая схема
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────┐
|
||||||
|
│ ProMicro nRF52840 │
|
||||||
|
│ │
|
||||||
|
SX1262 ◄──SPI──┤ P1.13 NSS │
|
||||||
|
│ │ P1.11 SCK │
|
||||||
|
│ │ P1.15 MOSI │
|
||||||
|
│ │ P0.02 MISO │
|
||||||
|
├──────┤ P0.10 DIO1 │
|
||||||
|
├──────┤ P0.09 RST │
|
||||||
|
├──────┤ P0.29 BUSY │
|
||||||
|
├──────┤ P0.13 POWER_EN │
|
||||||
|
│ │ │
|
||||||
|
Геркон ──┤ P0.08│ P0.20 ─── Реле 1 │
|
||||||
|
│ │ P0.22 ─── Реле 2 │
|
||||||
|
PIR ────┤ P0.06│ │
|
||||||
|
│ │ P0.15 ─── LED статуса│
|
||||||
|
DS18B20 ───┤ P0.17│ │
|
||||||
|
└──────┴───────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Питание
|
||||||
|
|
||||||
|
- **USB** (5В) — через встроенный регулятор
|
||||||
|
- **Релейные модули** обычно требуют 5В — используйте внешний источник
|
||||||
|
- SX1262 питается от 3.3В (pin VCC на плате)
|
||||||
|
|
||||||
|
**Потребление:**
|
||||||
|
- TX (~22 dBm): ~120-140 мА
|
||||||
|
- RX: ~15-20 мА
|
||||||
154
src/main.cpp
154
src/main.cpp
@@ -46,8 +46,22 @@ static float g_last_temperature = -127.0f;
|
|||||||
static unsigned long g_last_hall_alert = 0;
|
static unsigned long g_last_hall_alert = 0;
|
||||||
static unsigned long g_last_motion_alert = 0;
|
static unsigned long g_last_motion_alert = 0;
|
||||||
static const unsigned long ALERT_COOLDOWN_MS = 600000;
|
static const unsigned long ALERT_COOLDOWN_MS = 600000;
|
||||||
|
static uint32_t date_to_epoch(int y, int m, int d, int h, int mi) {
|
||||||
|
const uint8_t mdays[] = {31,28,31,30,31,30,31,31,30,31,30,31};
|
||||||
|
uint32_t days = (y - 1970) * 365;
|
||||||
|
for (int i = 1970; i < y; i++) {
|
||||||
|
if ((i % 4 == 0 && i % 100 != 0) || i % 400 == 0) days++;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < m - 1; i++) days += mdays[i];
|
||||||
|
if (m > 2 && ((y % 4 == 0 && y % 100 != 0) || y % 400 == 0)) days++;
|
||||||
|
days += d - 1;
|
||||||
|
return days * 86400 + h * 3600 + mi * 60;
|
||||||
|
}
|
||||||
|
|
||||||
static unsigned long g_boot_time = 0;
|
static unsigned long g_boot_time = 0;
|
||||||
static bool g_startup_msg_sent = false;
|
static bool g_startup_msg_sent = false;
|
||||||
|
static unsigned long g_auto_status_interval = 0;
|
||||||
|
static unsigned long g_last_auto_status = 0;
|
||||||
|
|
||||||
struct SecurityConfig { uint32_t magic; uint8_t armed; uint8_t relay2; };
|
struct SecurityConfig { uint32_t magic; uint8_t armed; uint8_t relay2; };
|
||||||
static const uint32_t CFG_MAGIC = 0x53454355;
|
static const uint32_t CFG_MAGIC = 0x53454355;
|
||||||
@@ -96,6 +110,24 @@ public:
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char pending_response[256];
|
||||||
|
unsigned long pending_at = 0;
|
||||||
|
bool pending = false;
|
||||||
|
|
||||||
|
void flush_response() {
|
||||||
|
if (pending && millis() >= pending_at) {
|
||||||
|
send_text(pending_response);
|
||||||
|
pending = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void defer_response(const char* text) {
|
||||||
|
strncpy(pending_response, text, sizeof(pending_response) - 1);
|
||||||
|
pending_response[sizeof(pending_response) - 1] = 0;
|
||||||
|
pending_at = millis() + 5000;
|
||||||
|
pending = true;
|
||||||
|
}
|
||||||
|
|
||||||
void send_text(const char* text) {
|
void send_text(const char* text) {
|
||||||
if (!channel_ready) return;
|
if (!channel_ready) return;
|
||||||
uint32_t ts = rtc_clock.getCurrentTime();
|
uint32_t ts = rtc_clock.getCurrentTime();
|
||||||
@@ -130,41 +162,105 @@ public:
|
|||||||
while (*cmd == ' ') cmd++;
|
while (*cmd == ' ') cmd++;
|
||||||
|
|
||||||
if (strcmp(cmd, "help") == 0) {
|
if (strcmp(cmd, "help") == 0) {
|
||||||
send_text("help:ohrana on,ohrana off,relay1 on,relay1 off,relay2 on,relay2 off,status,ver,scan,security_conf");
|
defer_response("help:ohrana on,ohrana off,relay1 on,relay1 off,relay2 on,relay2 off,relay,status,ver,scan,uptime,kontrol,time,reboot");
|
||||||
} else if (strcmp(cmd, "ohrana on") == 0) {
|
} else if (strcmp(cmd, "ohrana on") == 0) {
|
||||||
g_armed = true; digitalWrite(PIN_RELAY_1, LOW); saveSecurityConfig(); send_text("ohrana on ok");
|
g_armed = true; digitalWrite(PIN_RELAY_1, LOW); saveSecurityConfig(); defer_response("ohrana on ok");
|
||||||
} else if (strcmp(cmd, "ohrana off") == 0) {
|
} else if (strcmp(cmd, "ohrana off") == 0) {
|
||||||
g_armed = false; digitalWrite(PIN_RELAY_1, HIGH); saveSecurityConfig(); send_text("ohrana off ok");
|
g_armed = false; digitalWrite(PIN_RELAY_1, HIGH); saveSecurityConfig(); defer_response("ohrana off ok");
|
||||||
} else if (strcmp(cmd, "relay1 on") == 0) {
|
} else if (strcmp(cmd, "relay1 on") == 0) {
|
||||||
digitalWrite(PIN_RELAY_1, LOW); send_text("relay1 on ok");
|
digitalWrite(PIN_RELAY_1, LOW); defer_response("relay1 on ok");
|
||||||
} else if (strcmp(cmd, "relay1 off") == 0) {
|
} else if (strcmp(cmd, "relay1 off") == 0) {
|
||||||
digitalWrite(PIN_RELAY_1, HIGH); send_text("relay1 off ok");
|
digitalWrite(PIN_RELAY_1, HIGH); defer_response("relay1 off ok");
|
||||||
} else if (strcmp(cmd, "relay2 on") == 0) {
|
} else if (strcmp(cmd, "relay2 on") == 0) {
|
||||||
g_relay2_state = true; digitalWrite(PIN_RELAY_2, LOW); send_text("relay2 on ok");
|
g_relay2_state = true; digitalWrite(PIN_RELAY_2, LOW); defer_response("relay2 on ok");
|
||||||
} else if (strcmp(cmd, "relay2 off") == 0) {
|
} else if (strcmp(cmd, "relay2 off") == 0) {
|
||||||
g_relay2_state = false; digitalWrite(PIN_RELAY_2, HIGH); send_text("relay2 off ok");
|
g_relay2_state = false; digitalWrite(PIN_RELAY_2, HIGH); defer_response("relay2 off ok");
|
||||||
|
} else if (strcmp(cmd, "relay") == 0) {
|
||||||
|
char r[32]; snprintf(r, sizeof(r), "relay:1%s 2%s",
|
||||||
|
digitalRead(PIN_RELAY_1) == LOW ? "on" : "off",
|
||||||
|
g_relay2_state ? "on" : "off");
|
||||||
|
defer_response(r);
|
||||||
} else if (strcmp(cmd, "status") == 0) {
|
} else if (strcmp(cmd, "status") == 0) {
|
||||||
char r[128]; char tb[16]; float t = g_last_temperature;
|
char r[128]; char tb[16]; char vb[16]; float t = g_last_temperature;
|
||||||
if (t == -127.0f) strcpy(tb, "N/A"); else snprintf(tb, sizeof(tb), "%+.1fC", t);
|
if (t == -127.0f) strcpy(tb, "N/A"); else snprintf(tb, sizeof(tb), "%+.1fC", t);
|
||||||
snprintf(r, sizeof(r), "status:%s temp:%s hall:%s motion:%s",
|
uint16_t mv = board.getBattMilliVolts();
|
||||||
g_armed ? "armed" : "off", tb, g_hall_alert ? "trig" : "ok", g_motion_alert ? "trig" : "ok");
|
if (mv < 100) strcpy(vb, "USB"); else snprintf(vb, sizeof(vb), "%.2fV", mv / 1000.0f);
|
||||||
send_text(r);
|
snprintf(r, sizeof(r), "status:%s temp:%s hall:%s motion:%s bat:%s",
|
||||||
|
g_armed ? "armed" : "off", tb, g_hall_alert ? "trig" : "ok", g_motion_alert ? "trig" : "ok", vb);
|
||||||
|
defer_response(r);
|
||||||
} else if (strcmp(cmd, "ver") == 0) {
|
} else if (strcmp(cmd, "ver") == 0) {
|
||||||
char r[64]; snprintf(r, sizeof(r), "build:%s %s", __DATE__, __TIME__); send_text(r);
|
char r[64]; snprintf(r, sizeof(r), "build:%s %s", __DATE__, __TIME__); defer_response(r);
|
||||||
} else if (strcmp(cmd, "scan") == 0) {
|
} else if (strcmp(cmd, "scan") == 0) {
|
||||||
float sum = 0;
|
float sum = 0;
|
||||||
for (int j = 0; j < 20; j++) { sum += radio_driver.getCurrentRSSI(); delay(50); }
|
for (int j = 0; j < 20; j++) { sum += radio_driver.getCurrentRSSI(); delay(50); }
|
||||||
char r[32]; snprintf(r, sizeof(r), "noise:%.0fdBm", sum / 20); send_text(r);
|
char r[32]; snprintf(r, sizeof(r), "noise:%.0fdBm", sum / 20); defer_response(r);
|
||||||
} else if (strncmp(cmd, "security_conf", 13) == 0) {
|
} else if (strcmp(cmd, "uptime") == 0) {
|
||||||
const char* nc = cmd + 13; while (*nc == ' ') nc++;
|
unsigned long sec = (millis() - g_boot_time) / 1000;
|
||||||
if (strlen(nc) > 0 && strlen(nc) < 32) {
|
int d = sec / 86400; sec %= 86400;
|
||||||
uint8_t key[32];
|
int h = sec / 3600; sec %= 3600;
|
||||||
SHA256 sha; sha.reset(); sha.update((const uint8_t*)nc, strlen(nc)); sha.finalize(key, 32);
|
int m = sec / 60;
|
||||||
memset(mesh_channel.secret, 0, 32);
|
char r[64];
|
||||||
memcpy(mesh_channel.secret, key, 16);
|
if (d > 0) snprintf(r, sizeof(r), "uptime:%dd %dh %dm", d, h, m);
|
||||||
mesh::Utils::sha256(mesh_channel.hash, sizeof(mesh_channel.hash), mesh_channel.secret, 16);
|
else if (h > 0) snprintf(r, sizeof(r), "uptime:%dh %dm", h, m);
|
||||||
char r[64]; snprintf(r, sizeof(r), "channel:%s", nc); send_text(r);
|
else snprintf(r, sizeof(r), "uptime:%dm", m);
|
||||||
} else send_text("bad name");
|
defer_response(r);
|
||||||
|
} else if (strcmp(cmd, "kontrol") == 0 || strncmp(cmd, "kontrol ", 8) == 0) {
|
||||||
|
const char* arg = cmd + 7; while (*arg == ' ') arg++;
|
||||||
|
if (strcmp(arg, "off") == 0) {
|
||||||
|
g_auto_status_interval = 0;
|
||||||
|
defer_response("kontrol off");
|
||||||
|
} else {
|
||||||
|
int val = atoi(arg);
|
||||||
|
if (val >= 1 && val <= 1440) {
|
||||||
|
g_auto_status_interval = (unsigned long)val * 60000;
|
||||||
|
g_last_auto_status = millis();
|
||||||
|
char r[48]; snprintf(r, sizeof(r), "kontrol %d min ok", val);
|
||||||
|
defer_response(r);
|
||||||
|
} else defer_response("kontrol 1-1440 or off");
|
||||||
|
}
|
||||||
|
} else if (strncmp(cmd, "time", 4) == 0) {
|
||||||
|
const char* rest = cmd + 4; while (*rest == ' ') rest++;
|
||||||
|
if (*rest == 0) {
|
||||||
|
uint32_t ts = rtc_clock.getCurrentTime();
|
||||||
|
if (ts < 100000) { defer_response("time not set"); return; }
|
||||||
|
uint32_t days = ts / 86400;
|
||||||
|
int h = (ts % 86400) / 3600;
|
||||||
|
int mi = (ts % 3600) / 60;
|
||||||
|
int y = 1970;
|
||||||
|
while (true) {
|
||||||
|
int leap = ((y % 4 == 0 && y % 100 != 0) || y % 400 == 0) ? 366 : 365;
|
||||||
|
if (days < (uint32_t)leap) break;
|
||||||
|
days -= leap; y++;
|
||||||
|
}
|
||||||
|
const uint8_t mdays[] = {31,28,31,30,31,30,31,31,30,31,30,31};
|
||||||
|
int m;
|
||||||
|
for (m = 1; m <= 12; m++) {
|
||||||
|
int dim = mdays[m-1];
|
||||||
|
if (m == 2 && ((y % 4 == 0 && y % 100 != 0) || y % 400 == 0)) dim = 29;
|
||||||
|
if (days < (uint32_t)dim) break;
|
||||||
|
days -= dim;
|
||||||
|
}
|
||||||
|
char r[64];
|
||||||
|
snprintf(r, sizeof(r), "time:%02d.%02d.%04d %02d:%02d", (int)(days + 1), m, y, h, mi);
|
||||||
|
defer_response(r);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (strncmp(rest, "s ", 2) != 0) { defer_response("time s DD.MM.YYYY HH.MM"); return; }
|
||||||
|
rest += 2;
|
||||||
|
int d, m, y, h, mi;
|
||||||
|
if (sscanf(rest, "%d.%d.%d %d.%d", &d, &m, &y, &h, &mi) == 5) {
|
||||||
|
if (y < 2020 || y > 2099 || m < 1 || m > 12 || d < 1 || d > 31 || h > 23 || mi > 59) {
|
||||||
|
defer_response("bad date"); return;
|
||||||
|
}
|
||||||
|
rtc_clock.setCurrentTime(date_to_epoch(y, m, d, h, mi));
|
||||||
|
char r[64]; snprintf(r, sizeof(r), "time set %02d.%02d.%04d %02d.%02d", d, m, y, h, mi);
|
||||||
|
defer_response(r);
|
||||||
|
} else defer_response("time s DD.MM.YYYY HH.MM");
|
||||||
|
} else if (strcmp(cmd, "reboot") == 0) {
|
||||||
|
send_text("rebooting");
|
||||||
|
delay(100);
|
||||||
|
for (int i = 0; i < 5; i++) { digitalWrite(PIN_LED_STATUS, HIGH); delay(80); digitalWrite(PIN_LED_STATUS, LOW); delay(80); }
|
||||||
|
NVIC_SystemReset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -221,6 +317,7 @@ void setup() {
|
|||||||
void loop() {
|
void loop() {
|
||||||
NRF_WDT->RR[0] = WDT_RR_RR_Reload;
|
NRF_WDT->RR[0] = WDT_RR_RR_Reload;
|
||||||
the_mesh.loop();
|
the_mesh.loop();
|
||||||
|
the_mesh.flush_response();
|
||||||
rtc_clock.tick();
|
rtc_clock.tick();
|
||||||
|
|
||||||
if (!g_startup_msg_sent && millis() - g_boot_time >= 10000) {
|
if (!g_startup_msg_sent && millis() - g_boot_time >= 10000) {
|
||||||
@@ -244,6 +341,17 @@ void loop() {
|
|||||||
if (!g_temp_requested) { ds18b20.requestTemperatures(); g_temp_requested = true; g_temp_last_request = now; }
|
if (!g_temp_requested) { ds18b20.requestTemperatures(); g_temp_requested = true; g_temp_last_request = now; }
|
||||||
else if (now - g_temp_last_request > 750) { g_last_temperature = ds18b20.getTempCByIndex(0); g_temp_requested = false; }
|
else if (now - g_temp_last_request > 750) { g_last_temperature = ds18b20.getTempCByIndex(0); g_temp_requested = false; }
|
||||||
|
|
||||||
|
if (g_auto_status_interval > 0 && now - g_last_auto_status >= g_auto_status_interval) {
|
||||||
|
g_last_auto_status = now;
|
||||||
|
char r[128]; char tb[16]; char vb[16]; float t = g_last_temperature;
|
||||||
|
if (t == -127.0f) strcpy(tb, "N/A"); else snprintf(tb, sizeof(tb), "%+.1fC", t);
|
||||||
|
uint16_t mv = board.getBattMilliVolts();
|
||||||
|
if (mv < 100) strcpy(vb, "USB"); else snprintf(vb, sizeof(vb), "%.2fV", mv / 1000.0f);
|
||||||
|
snprintf(r, sizeof(r), "status:%s temp:%s hall:%s motion:%s bat:%s",
|
||||||
|
g_armed ? "armed" : "off", tb, g_hall_alert ? "trig" : "ok", g_motion_alert ? "trig" : "ok", vb);
|
||||||
|
the_mesh.send_text(r);
|
||||||
|
}
|
||||||
|
|
||||||
if (g_armed) {
|
if (g_armed) {
|
||||||
static unsigned long lt = 0; static bool ls = false;
|
static unsigned long lt = 0; static bool ls = false;
|
||||||
if (now - lt > 1000) { lt = now; ls = !ls; digitalWrite(PIN_LED_STATUS, ls); }
|
if (now - lt > 1000) { lt = now; ls = !ls; digitalWrite(PIN_LED_STATUS, ls); }
|
||||||
|
|||||||
Reference in New Issue
Block a user