Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
231db4c652 | ||
|
|
b349dd38ec | ||
|
|
7e978fcc9c | ||
|
|
9ca5530c73 | ||
|
|
1430bba3f4 | ||
|
|
e68a762e88 | ||
|
|
9b1c8f3497 | ||
|
|
0b01526a39 | ||
|
|
fb30065edd | ||
|
|
87ac515063 | ||
|
|
9deb046ae3 | ||
|
|
d49443f66c | ||
|
|
8b21bccac2 | ||
|
|
d487b84926 | ||
|
|
62cf8fc3f4 | ||
|
|
acc36cf3ad | ||
|
|
d3990516b5 | ||
|
|
dfdd6ab343 | ||
|
|
cb2a33c97e |
@@ -106,21 +106,26 @@ All commands are processed in `onGroupDataRecv()`:
|
|||||||
| Command | Action | Response |
|
| Command | Action | Response |
|
||||||
|---------|--------|----------|
|
|---------|--------|----------|
|
||||||
| `help` | list commands | `help:...` |
|
| `help` | list commands | `help:...` |
|
||||||
| `ohrana on` | arm, relay1 ON | `ohrana on ok` |
|
| `ohrana on` | arm | `ohrana on ok` |
|
||||||
| `ohrana off` | disarm, relay1 OFF | `ohrana off ok` |
|
| `ohrana off` | disarm | `ohrana off ok` |
|
||||||
| `relay1 on` | relay1 ON (direct) | `relay1 on ok` |
|
| `relay1 on` | relay1 ON (HIGH) | `relay1 on ok` |
|
||||||
| `relay1 off` | relay1 OFF (direct) | `relay1 off ok` |
|
| `relay1 off` | relay1 OFF (LOW) | `relay1 off ok` |
|
||||||
| `relay2 on` | relay2 ON | `relay2 on ok` |
|
| `relay2 on` | relay2 ON (HIGH) | `relay2 on ok` |
|
||||||
| `relay2 off` | relay2 OFF | `relay2 off ok` |
|
| `relay2 off` | relay2 OFF (LOW) | `relay2 off ok` |
|
||||||
| `relay` | state of both relays | `relay:1on 2off` |
|
| `relay3 on` | relay3 ON (HIGH) | `relay3 on ok` |
|
||||||
|
| `relay3 off` | relay3 OFF (LOW) | `relay3 off ok` |
|
||||||
|
| `relay4 on` | relay4 ON (HIGH) | `relay4 on ok` |
|
||||||
|
| `relay4 off` | relay4 OFF (LOW) | `relay4 off ok` |
|
||||||
|
| `relay` | state of all relays | `relay:1on 2off 3on 4off` |
|
||||||
| `status` | report state | `status:armed/off temp:±X.XC hall:trig/ok motion:trig/ok bat:X.XXV/USB` |
|
| `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` |
|
||||||
|
| `snr` | RSSI и SNR последнего пакета | `rssi:-78dBm snr:+6.5dB` |
|
||||||
| `uptime` | device uptime | `uptime:1d 3h 15m` |
|
| `uptime` | device uptime | `uptime:1d 3h 15m` |
|
||||||
| `kontrol <min>` | auto-status every N min | `kontrol 60 min ok` |
|
| `kontrol <min>` | auto-status every N min | `kontrol 60 min ok` |
|
||||||
| `kontrol off` | disable auto-status | `kontrol off` |
|
| `kontrol off` | disable auto-status | `kontrol off` |
|
||||||
| `reboot` | reboot device | `rebooting` |
|
| `reboot` | reboot device | `rebooting` |
|
||||||
| `time` | show RTC date/time | `time:22.07.2026 11:00` |
|
| `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` |
|
| `time s DD.MM.YYYY HH.MM` | set RTC date/time | `time set 22.07.2026 11.00` |
|
||||||
|
|
||||||
### Response Delay
|
### Response Delay
|
||||||
@@ -131,6 +136,8 @@ Alerts (`alert:hall`, `alert:motion`) and startup (`poweron`) are sent immediate
|
|||||||
|
|
||||||
### Special Commands
|
### 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()`.
|
- **`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).
|
- **`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.
|
- **`kontrol <min>`** — sets `g_auto_status_interval = min * 60000`. In `loop()`, when `millis() - g_last_auto_status >= interval`, sends status. Range: 1–1440 minutes.
|
||||||
@@ -198,7 +205,7 @@ pio run -e promicro_security -t upload
|
|||||||
pio device monitor -b 115200
|
pio device monitor -b 115200
|
||||||
```
|
```
|
||||||
|
|
||||||
Current resource usage: RAM 8%, Flash 36.5%.
|
Current resource usage: RAM 8.1%, Flash 36.8%.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -220,14 +227,14 @@ The LED blink (3 fast flashes) is the first thing in `onGroupDataRecv`. If LED d
|
|||||||
- `rtc_clock.getCurrentTime()` returning bogus data (VolatileRTCClock starts from 0, fine).
|
- `rtc_clock.getCurrentTime()` returning bogus data (VolatileRTCClock starts from 0, fine).
|
||||||
|
|
||||||
### Adding a new command
|
### Adding a new command
|
||||||
1. Add `else if` block in `onGroupDataRecv()` after line 168.
|
1. Add `else if` block in `onGroupDataRecv()`.
|
||||||
2. Use `strcmp(cmd, "command") == 0` for exact match.
|
2. Use `strcmp(cmd, "command") == 0` for exact match.
|
||||||
3. Call `send_text("response")` to reply.
|
3. Call `send_text("response")` to reply.
|
||||||
4. Update the `help` string.
|
4. Update the `help` string.
|
||||||
|
|
||||||
### Config persistent storage
|
### Config persistent storage
|
||||||
- `InternalFS` (LittleFS) on nRF52 internal flash.
|
- `InternalFS` (LittleFS) on nRF52 internal flash.
|
||||||
- `SecurityConfig`: armed + relay2 state saved as binary struct.
|
- `SecurityConfig`: armed + relay1/2/3/4 state saved as binary struct.
|
||||||
- File: `/security.cfg`, magic `0x53454355`.
|
- File: `/security.cfg`, magic `0x53454355`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
11
README.md
11
README.md
@@ -5,19 +5,19 @@ LoRa mesh-устройство охраны на базе nRF52840 (ProMicro) и
|
|||||||
|
|
||||||
## Возможности
|
## Возможности
|
||||||
|
|
||||||
- Управление двумя реле (охрана + независимое)
|
- Управление четырьмя реле
|
||||||
- Датчик Холла (геркон) — оповещение об открытии двери/окна
|
- Датчик Холла (геркон) — оповещение об открытии двери/окна
|
||||||
- PIR датчик движения
|
- PIR датчик движения
|
||||||
- DS18B20 температура
|
- DS18B20 температура
|
||||||
- Светодиодная индикация состояния
|
- Светодиодная индикация состояния
|
||||||
- Сохранение конфигурации в энергонезависимую память
|
- Сохранение конфигурации в энергонезависимую память
|
||||||
- Смена канала через mesh-команду
|
- SNR — RSSI и SNR последнего принятого пакета
|
||||||
|
|
||||||
## Аппарат
|
## Аппарат
|
||||||
|
|
||||||
- **SoC:** nRF52840 (Adafruit ProMicro nRF52840)
|
- **SoC:** nRF52840 (Adafruit ProMicro nRF52840)
|
||||||
- **Радио:** SX1262 (868 МГц, SF7, 62.5 кГц)
|
- **Радио:** SX1262 (868 МГц, SF7, 62.5 кГц)
|
||||||
- **Память:** 256 КБ ОЗУ, 712 КБ Flash (занято 36%)
|
- **Память:** 256 КБ ОЗУ, 712 КБ Flash (занято 37%)
|
||||||
|
|
||||||
## Команды
|
## Команды
|
||||||
|
|
||||||
@@ -27,10 +27,13 @@ LoRa mesh-устройство охраны на базе nRF52840 (ProMicro) и
|
|||||||
| `ohrana off` | Выключить охрану |
|
| `ohrana off` | Выключить охрану |
|
||||||
| `relay1 on/off` | Управление реле 1 |
|
| `relay1 on/off` | Управление реле 1 |
|
||||||
| `relay2 on/off` | Управление реле 2 |
|
| `relay2 on/off` | Управление реле 2 |
|
||||||
| `relay` | Состояние обоих реле |
|
| `relay3 on/off` | Управление реле 3 |
|
||||||
|
| `relay4 on/off` | Управление реле 4 |
|
||||||
|
| `relay` | Состояние всех реле |
|
||||||
| `status` | Состояние устройства (включая bat) |
|
| `status` | Состояние устройства (включая bat) |
|
||||||
| `ver` | Версия прошивки |
|
| `ver` | Версия прошивки |
|
||||||
| `scan` | Уровень шума в эфире |
|
| `scan` | Уровень шума в эфире |
|
||||||
|
| `snr` | RSSI и SNR последнего пакета |
|
||||||
| `uptime` | Время работы |
|
| `uptime` | Время работы |
|
||||||
| `kontrol <min>` | Автоотправка статуса каждые N мин |
|
| `kontrol <min>` | Автоотправка статуса каждые N мин |
|
||||||
| `kontrol off` | Отключить автоотправку |
|
| `kontrol off` | Отключить автоотправку |
|
||||||
|
|||||||
@@ -24,16 +24,21 @@ Security Node — LoRa mesh-устройство, которое следит з
|
|||||||
| Команда | Что делает | Пример ответа |
|
| Команда | Что делает | Пример ответа |
|
||||||
|---------|-----------|---------------|
|
|---------|-----------|---------------|
|
||||||
| `help` | Список всех команд | `help:ohrana on,ohrana off,...` |
|
| `help` | Список всех команд | `help:ohrana on,ohrana off,...` |
|
||||||
| `ohrana on` | **Включить охрану** — датчики активны, реле 1 замкнуто | `ohrana on ok` |
|
| `ohrana on` | **Включить охрану** — датчики активны | `ohrana on ok` |
|
||||||
| `ohrana off` | **Выключить охрану** — датчики не активны, реле 1 разомкнуто | `ohrana off ok` |
|
| `ohrana off` | **Выключить охрану** — датчики не активны | `ohrana off ok` |
|
||||||
| `relay1 on` | Включить реле 1 (независимо от охраны) | `relay1 on ok` |
|
| `relay1 on` | Включить реле 1 | `relay1 on ok` |
|
||||||
| `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` |
|
||||||
| `relay` | Состояние обоих реле | `relay:1on 2off` |
|
| `relay3 on` | Включить реле 3 | `relay3 on ok` |
|
||||||
|
| `relay3 off` | Выключить реле 3 | `relay3 off ok` |
|
||||||
|
| `relay4 on` | Включить реле 4 | `relay4 on ok` |
|
||||||
|
| `relay4 off` | Выключить реле 4 | `relay4 off ok` |
|
||||||
|
| `relay` | Состояние всех реле | `relay:1on 2off 3on 4off` |
|
||||||
| `status` | Показать состояние устройства | `status:armed temp:+23.5C hall:ok motion:trig bat:4.12V` |
|
| `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` |
|
||||||
|
| `snr` | RSSI и SNR последнего принятого пакета | `rssi:-78dBm snr:+6.5dB` |
|
||||||
| `uptime` | Время работы устройства | `uptime:1d 3h 15m` |
|
| `uptime` | Время работы устройства | `uptime:1d 3h 15m` |
|
||||||
| `kontrol <мин>` | Автоотправка статуса каждые N мин | `kontrol 60 min ok` |
|
| `kontrol <мин>` | Автоотправка статуса каждые N мин | `kontrol 60 min ok` |
|
||||||
| `kontrol off` | Отключить автоотправку статуса | `kontrol off` |
|
| `kontrol off` | Отключить автоотправку статуса | `kontrol off` |
|
||||||
@@ -98,12 +103,13 @@ Security Node — LoRa mesh-устройство, которое следит з
|
|||||||
|
|
||||||
## Реле
|
## Реле
|
||||||
|
|
||||||
- **Реле 1** — управляется `ohrana on/off` и `relay1 on/off`.
|
Все реле независимы, состояние каждого сохраняется при перезагрузке.
|
||||||
Охрана включена (`ohrana on`): **замкнуто** (NC разомкнуты, NO замкнуты).
|
Логика: ON = высокий уровень (HIGH), OFF = низкий (LOW).
|
||||||
Охрана выключена (`ohrana off`): **разомкнуто**.
|
|
||||||
|
|
||||||
- **Реле 2** — независимое, только `relay2 on/off`.
|
- **Реле 1** (P0.20) — `relay1 on/off`
|
||||||
Состояние сохраняется при перезагрузке.
|
- **Реле 2** (P0.22) — `relay2 on/off`
|
||||||
|
- **Реле 3** (P0.24) — `relay3 on/off`
|
||||||
|
- **Реле 4** (P1.00) — `relay4 on/off`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -112,9 +118,13 @@ Security Node — LoRa mesh-устройство, которое следит з
|
|||||||
`kontrol 60` — устройство будет само отправлять `status` каждые 60 минут.
|
`kontrol 60` — устройство будет само отправлять `status` каждые 60 минут.
|
||||||
`kontrol off` — отключить. Интервал от 1 до 1440 минут (1 минута — 24 часа).
|
`kontrol off` — отключить. Интервал от 1 до 1440 минут (1 минута — 24 часа).
|
||||||
|
|
||||||
|
## Качество сигнала (`snr`)
|
||||||
|
|
||||||
|
`snr` — показывает RSSI (уровень сигнала в dBm) и SNR (соотношение сигнал/шум в dB) последнего принятого пакета. Полезно для оценки качества связи и при настройке антенны. Чем ближе RSSI к 0 и чем выше SNR — тем лучше.
|
||||||
|
|
||||||
## Синхронизация времени (`time`)
|
## Синхронизация времени (`time`)
|
||||||
|
|
||||||
`time` — показать текущее время устройства.
|
`time` — показать текущее время устройства (МСК, UTC+3).
|
||||||
`time s 22.07.2026 11.00` — установить дату и время. Формат: `time s DD.MM.YYYY HH.MM`
|
`time s 22.07.2026 11.00` — установить дату и время. Формат: `time s DD.MM.YYYY HH.MM`
|
||||||
Время используется для отметок в сообщениях. После перезагрузки сбрасывается (если нет внешнего RTC).
|
Время используется для отметок в сообщениях. После перезагрузки сбрасывается (если нет внешнего RTC).
|
||||||
|
|
||||||
@@ -127,20 +137,6 @@ Security Node — LoRa mesh-устройство, которое следит з
|
|||||||
`uptime` — показывает, сколько времени устройство работает без перезагрузки.
|
`uptime` — показывает, сколько времени устройство работает без перезагрузки.
|
||||||
Пример: `uptime:1d 3h 15m` (1 день, 3 часа, 15 минут).
|
Пример: `uptime:1d 3h 15m` (1 день, 3 часа, 15 минут).
|
||||||
|
|
||||||
## Смена канала (`canal`)
|
|
||||||
|
|
||||||
По умолчанию канал формируется из пароля `"1234"`.
|
|
||||||
Чтобы сменить:
|
|
||||||
|
|
||||||
```
|
|
||||||
canal домашний 5674
|
|
||||||
```
|
|
||||||
|
|
||||||
Ответ: `canal домашний 5674 ok`
|
|
||||||
Через ~5 секунд устройство перезагрузится и начнёт работать на новом канале.
|
|
||||||
|
|
||||||
**Важно:** Все устройства должны быть на одном канале. После смены канала связь будет только с теми узлами, у которых такой же пароль. Канал сохраняется в энергонезависимой памяти и не сбрасывается после перезагрузки. Чтобы вернуть канал по умолчанию — используйте `canal default 1234`.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Задержка ответа
|
## Задержка ответа
|
||||||
|
|||||||
42
WIRING.md
42
WIRING.md
@@ -11,8 +11,10 @@
|
|||||||
| 0 | **P0.08** | Геркон (reed switch) |
|
| 0 | **P0.08** | Геркон (reed switch) |
|
||||||
| 1 | **P0.06** | PIR датчик движения |
|
| 1 | **P0.06** | PIR датчик движения |
|
||||||
| 2 | **P0.17** | DS18B20 температура |
|
| 2 | **P0.17** | DS18B20 температура |
|
||||||
| 3 | **P0.20** | Реле 1 (охрана) |
|
| 3 | **P0.20** | Реле 1 |
|
||||||
| 4 | **P0.22** | Реле 2 |
|
| 4 | **P0.22** | Реле 2 |
|
||||||
|
| 5 | **P0.24** | Реле 3 |
|
||||||
|
| 6 | **P1.00** | Реле 4 |
|
||||||
| 10 | **P0.09** | SX1262 RST |
|
| 10 | **P0.09** | SX1262 RST |
|
||||||
| 11 | **P0.10** | SX1262 DIO1 |
|
| 11 | **P0.10** | SX1262 DIO1 |
|
||||||
| 12 | **P1.11** | SX1262 SCK |
|
| 12 | **P1.11** | SX1262 SCK |
|
||||||
@@ -89,7 +91,9 @@ P0.17 ────┤
|
|||||||
|
|
||||||
## Реле
|
## Реле
|
||||||
|
|
||||||
### Реле 1 (охрана) — P0.20
|
Логика: ON = HIGH, OFF = LOW.
|
||||||
|
|
||||||
|
### Реле 1 — P0.20
|
||||||
|
|
||||||
```
|
```
|
||||||
P0.20 ── IN1 релейного модуля
|
P0.20 ── IN1 релейного модуля
|
||||||
@@ -100,7 +104,7 @@ GND ── GND
|
|||||||
IN1 ── P0.20
|
IN1 ── P0.20
|
||||||
```
|
```
|
||||||
|
|
||||||
> Реле 1 управляется `ohrana on/off` и `relay1 on/off`.
|
> Реле 1 управляется `relay1 on/off`. Состояние сохраняется.
|
||||||
|
|
||||||
### Реле 2 — P0.22
|
### Реле 2 — P0.22
|
||||||
|
|
||||||
@@ -113,7 +117,33 @@ GND ── GND
|
|||||||
IN2 ── P0.22
|
IN2 ── P0.22
|
||||||
```
|
```
|
||||||
|
|
||||||
> Реле 2 управляется только `relay2 on/off`. Состояние сохраняется.
|
> Реле 2 управляется `relay2 on/off`. Состояние сохраняется.
|
||||||
|
|
||||||
|
### Реле 3 — P0.24
|
||||||
|
|
||||||
|
```
|
||||||
|
P0.24 ── IN3 релейного модуля
|
||||||
|
|
||||||
|
Релейный модуль:
|
||||||
|
VCC ── 5V
|
||||||
|
GND ── GND
|
||||||
|
IN3 ── P0.24
|
||||||
|
```
|
||||||
|
|
||||||
|
> Реле 3 управляется `relay3 on/off`. Состояние сохраняется.
|
||||||
|
|
||||||
|
### Реле 4 — P1.00
|
||||||
|
|
||||||
|
```
|
||||||
|
P1.00 ── IN4 релейного модуля
|
||||||
|
|
||||||
|
Релейный модуль:
|
||||||
|
VCC ── 5V
|
||||||
|
GND ── GND
|
||||||
|
IN4 ── P1.00
|
||||||
|
```
|
||||||
|
|
||||||
|
> Реле 4 управляется `relay4 on/off`. Состояние сохраняется.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -140,7 +170,9 @@ IN2 ── P0.22
|
|||||||
├──────┤ P0.13 POWER_EN │
|
├──────┤ P0.13 POWER_EN │
|
||||||
│ │ │
|
│ │ │
|
||||||
Геркон ──┤ P0.08│ P0.20 ─── Реле 1 │
|
Геркон ──┤ P0.08│ P0.20 ─── Реле 1 │
|
||||||
│ │ P0.22 ─── Реле 2 │
|
│ │ P0.22 ─── Реле 2 │
|
||||||
|
│ │ P0.24 ─── Реле 3 │
|
||||||
|
│ │ P1.00 ─── Реле 4 │
|
||||||
PIR ────┤ P0.06│ │
|
PIR ────┤ P0.06│ │
|
||||||
│ │ P0.15 ─── LED статуса│
|
│ │ P0.15 ─── LED статуса│
|
||||||
DS18B20 ───┤ P0.17│ │
|
DS18B20 ───┤ P0.17│ │
|
||||||
|
|||||||
76
src/main.cpp
76
src/main.cpp
@@ -39,13 +39,18 @@ DallasTemperature ds18b20(&oneWire);
|
|||||||
static bool g_armed = false;
|
static bool g_armed = false;
|
||||||
static bool g_hall_alert = false;
|
static bool g_hall_alert = false;
|
||||||
static bool g_motion_alert = false;
|
static bool g_motion_alert = false;
|
||||||
|
static bool g_relay1_state = false;
|
||||||
static bool g_relay2_state = false;
|
static bool g_relay2_state = false;
|
||||||
|
static bool g_relay3_state = false;
|
||||||
|
static bool g_relay4_state = false;
|
||||||
static bool g_temp_requested = false;
|
static bool g_temp_requested = false;
|
||||||
static unsigned long g_temp_last_request = 0;
|
static unsigned long g_temp_last_request = 0;
|
||||||
static float g_last_temperature = -127.0f;
|
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 const uint32_t TZ_OFFSET = 10800; // +3h (MSK)
|
||||||
|
|
||||||
static uint32_t date_to_epoch(int y, int m, int d, int h, int mi) {
|
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};
|
const uint8_t mdays[] = {31,28,31,30,31,30,31,31,30,31,30,31};
|
||||||
uint32_t days = (y - 1970) * 365;
|
uint32_t days = (y - 1970) * 365;
|
||||||
@@ -63,11 +68,11 @@ static bool g_startup_msg_sent = false;
|
|||||||
static unsigned long g_auto_status_interval = 0;
|
static unsigned long g_auto_status_interval = 0;
|
||||||
static unsigned long g_last_auto_status = 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 relay1; uint8_t relay2; uint8_t relay3; uint8_t relay4; };
|
||||||
static const uint32_t CFG_MAGIC = 0x53454355;
|
static const uint32_t CFG_MAGIC = 0x53454357;
|
||||||
|
|
||||||
bool saveSecurityConfig() {
|
bool saveSecurityConfig() {
|
||||||
SecurityConfig cfg = { CFG_MAGIC, g_armed ? 1 : 0, g_relay2_state ? 1 : 0 };
|
SecurityConfig cfg = { CFG_MAGIC, g_armed ? 1 : 0, g_relay1_state ? 1 : 0, g_relay2_state ? 1 : 0, g_relay3_state ? 1 : 0, g_relay4_state ? 1 : 0 };
|
||||||
File f = InternalFS.open(CONFIG_FILE, FILE_O_WRITE);
|
File f = InternalFS.open(CONFIG_FILE, FILE_O_WRITE);
|
||||||
if (!f) return false;
|
if (!f) return false;
|
||||||
bool ok = f.write((const uint8_t*)&cfg, sizeof(cfg)) == sizeof(cfg);
|
bool ok = f.write((const uint8_t*)&cfg, sizeof(cfg)) == sizeof(cfg);
|
||||||
@@ -81,7 +86,7 @@ bool loadSecurityConfig() {
|
|||||||
if (f.read(&cfg, sizeof(cfg)) != sizeof(cfg)) { f.close(); return false; }
|
if (f.read(&cfg, sizeof(cfg)) != sizeof(cfg)) { f.close(); return false; }
|
||||||
f.close();
|
f.close();
|
||||||
if (cfg.magic != CFG_MAGIC) return false;
|
if (cfg.magic != CFG_MAGIC) return false;
|
||||||
g_armed = cfg.armed; g_relay2_state = cfg.relay2;
|
g_armed = cfg.armed; g_relay1_state = cfg.relay1; g_relay2_state = cfg.relay2; g_relay3_state = cfg.relay3; g_relay4_state = cfg.relay4;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,6 +115,9 @@ public:
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float last_rssi = -127.0f;
|
||||||
|
float last_snr = 0.0f;
|
||||||
|
|
||||||
char pending_response[256];
|
char pending_response[256];
|
||||||
unsigned long pending_at = 0;
|
unsigned long pending_at = 0;
|
||||||
bool pending = false;
|
bool pending = false;
|
||||||
@@ -145,6 +153,9 @@ public:
|
|||||||
if (type != PAYLOAD_TYPE_GRP_TXT) return;
|
if (type != PAYLOAD_TYPE_GRP_TXT) return;
|
||||||
if (len < 5 || data[4] != 0) return;
|
if (len < 5 || data[4] != 0) return;
|
||||||
|
|
||||||
|
last_rssi = radio_driver.getLastRSSI();
|
||||||
|
last_snr = pkt->getSNR();
|
||||||
|
|
||||||
for (int i = 0; i < 3; i++) { digitalWrite(PIN_LED_STATUS, HIGH); delay(50); digitalWrite(PIN_LED_STATUS, LOW); delay(50); }
|
for (int i = 0; i < 3; i++) { digitalWrite(PIN_LED_STATUS, HIGH); delay(50); digitalWrite(PIN_LED_STATUS, LOW); delay(50); }
|
||||||
|
|
||||||
int tlen = len - 5;
|
int tlen = len - 5;
|
||||||
@@ -162,23 +173,45 @@ public:
|
|||||||
while (*cmd == ' ') cmd++;
|
while (*cmd == ' ') cmd++;
|
||||||
|
|
||||||
if (strcmp(cmd, "help") == 0) {
|
if (strcmp(cmd, "help") == 0) {
|
||||||
defer_response("help:ohrana on,ohrana off,relay1 on,relay1 off,relay2 on,relay2 off,relay,status,ver,scan,uptime,kontrol,time,reboot");
|
defer_response("help:ohrana on,ohrana off,relay on,relay off,relay1 on,relay1 off,relay2 on,relay2 off,relay3 on,relay3 off,relay4 on,relay4 off,relay,status,ver,scan,snr,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(); defer_response("ohrana on ok");
|
g_armed = true; 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(); defer_response("ohrana off ok");
|
g_armed = false; 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); defer_response("relay1 on ok");
|
g_relay1_state = true; digitalWrite(PIN_RELAY_1, LOW); saveSecurityConfig(); defer_response("relay1 on ok");
|
||||||
} else if (strcmp(cmd, "relay1 off") == 0) {
|
} else if (strcmp(cmd, "relay1 off") == 0) {
|
||||||
digitalWrite(PIN_RELAY_1, HIGH); defer_response("relay1 off ok");
|
g_relay1_state = false; digitalWrite(PIN_RELAY_1, HIGH); saveSecurityConfig(); 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); defer_response("relay2 on ok");
|
g_relay2_state = true; digitalWrite(PIN_RELAY_2, LOW); saveSecurityConfig(); 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); defer_response("relay2 off ok");
|
g_relay2_state = false; digitalWrite(PIN_RELAY_2, HIGH); saveSecurityConfig(); defer_response("relay2 off ok");
|
||||||
|
} else if (strcmp(cmd, "relay3 on") == 0) {
|
||||||
|
g_relay3_state = true; digitalWrite(PIN_RELAY_3, LOW); saveSecurityConfig(); defer_response("relay3 on ok");
|
||||||
|
} else if (strcmp(cmd, "relay3 off") == 0) {
|
||||||
|
g_relay3_state = false; digitalWrite(PIN_RELAY_3, HIGH); saveSecurityConfig(); defer_response("relay3 off ok");
|
||||||
|
} else if (strcmp(cmd, "relay4 on") == 0) {
|
||||||
|
g_relay4_state = true; digitalWrite(PIN_RELAY_4, LOW); saveSecurityConfig(); defer_response("relay4 on ok");
|
||||||
|
} else if (strcmp(cmd, "relay4 off") == 0) {
|
||||||
|
g_relay4_state = false; digitalWrite(PIN_RELAY_4, HIGH); saveSecurityConfig(); defer_response("relay4 off ok");
|
||||||
|
} else if (strcmp(cmd, "relay on") == 0) {
|
||||||
|
g_relay1_state = true; digitalWrite(PIN_RELAY_1, LOW);
|
||||||
|
g_relay2_state = true; digitalWrite(PIN_RELAY_2, LOW);
|
||||||
|
g_relay3_state = true; digitalWrite(PIN_RELAY_3, LOW);
|
||||||
|
g_relay4_state = true; digitalWrite(PIN_RELAY_4, LOW);
|
||||||
|
saveSecurityConfig(); defer_response("relay all on ok");
|
||||||
|
} else if (strcmp(cmd, "relay off") == 0) {
|
||||||
|
g_relay1_state = false; digitalWrite(PIN_RELAY_1, HIGH);
|
||||||
|
g_relay2_state = false; digitalWrite(PIN_RELAY_2, HIGH);
|
||||||
|
g_relay3_state = false; digitalWrite(PIN_RELAY_3, HIGH);
|
||||||
|
g_relay4_state = false; digitalWrite(PIN_RELAY_4, HIGH);
|
||||||
|
saveSecurityConfig(); defer_response("relay all off ok");
|
||||||
} else if (strcmp(cmd, "relay") == 0) {
|
} else if (strcmp(cmd, "relay") == 0) {
|
||||||
char r[32]; snprintf(r, sizeof(r), "relay:1%s 2%s",
|
char r[48]; snprintf(r, sizeof(r), "relay:1%s 2%s 3%s 4%s",
|
||||||
digitalRead(PIN_RELAY_1) == LOW ? "on" : "off",
|
g_relay1_state ? "on" : "off",
|
||||||
g_relay2_state ? "on" : "off");
|
g_relay2_state ? "on" : "off",
|
||||||
|
g_relay3_state ? "on" : "off",
|
||||||
|
g_relay4_state ? "on" : "off");
|
||||||
defer_response(r);
|
defer_response(r);
|
||||||
} else if (strcmp(cmd, "status") == 0) {
|
} else if (strcmp(cmd, "status") == 0) {
|
||||||
char r[128]; char tb[16]; char vb[16]; float t = g_last_temperature;
|
char r[128]; char tb[16]; char vb[16]; float t = g_last_temperature;
|
||||||
@@ -221,7 +254,7 @@ public:
|
|||||||
} else if (strncmp(cmd, "time", 4) == 0) {
|
} else if (strncmp(cmd, "time", 4) == 0) {
|
||||||
const char* rest = cmd + 4; while (*rest == ' ') rest++;
|
const char* rest = cmd + 4; while (*rest == ' ') rest++;
|
||||||
if (*rest == 0) {
|
if (*rest == 0) {
|
||||||
uint32_t ts = rtc_clock.getCurrentTime();
|
uint32_t ts = rtc_clock.getCurrentTime() + TZ_OFFSET;
|
||||||
if (ts < 100000) { defer_response("time not set"); return; }
|
if (ts < 100000) { defer_response("time not set"); return; }
|
||||||
uint32_t days = ts / 86400;
|
uint32_t days = ts / 86400;
|
||||||
int h = (ts % 86400) / 3600;
|
int h = (ts % 86400) / 3600;
|
||||||
@@ -252,10 +285,13 @@ public:
|
|||||||
if (y < 2020 || y > 2099 || m < 1 || m > 12 || d < 1 || d > 31 || h > 23 || mi > 59) {
|
if (y < 2020 || y > 2099 || m < 1 || m > 12 || d < 1 || d > 31 || h > 23 || mi > 59) {
|
||||||
defer_response("bad date"); return;
|
defer_response("bad date"); return;
|
||||||
}
|
}
|
||||||
rtc_clock.setCurrentTime(date_to_epoch(y, m, d, h, mi));
|
rtc_clock.setCurrentTime(date_to_epoch(y, m, d, h, mi) - TZ_OFFSET);
|
||||||
char r[64]; snprintf(r, sizeof(r), "time set %02d.%02d.%04d %02d.%02d", d, m, y, h, mi);
|
char r[64]; snprintf(r, sizeof(r), "time set %02d.%02d.%04d %02d.%02d", d, m, y, h, mi);
|
||||||
defer_response(r);
|
defer_response(r);
|
||||||
} else defer_response("time s DD.MM.YYYY HH.MM");
|
} else defer_response("time s DD.MM.YYYY HH.MM");
|
||||||
|
} else if (strcmp(cmd, "snr") == 0) {
|
||||||
|
char r[48]; snprintf(r, sizeof(r), "rssi:%.0fdBm snr:%.1fdB", last_rssi, last_snr);
|
||||||
|
defer_response(r);
|
||||||
} else if (strcmp(cmd, "reboot") == 0) {
|
} else if (strcmp(cmd, "reboot") == 0) {
|
||||||
send_text("rebooting");
|
send_text("rebooting");
|
||||||
delay(100);
|
delay(100);
|
||||||
@@ -293,13 +329,15 @@ void setup() {
|
|||||||
NRF_WDT->TASKS_START = 1;
|
NRF_WDT->TASKS_START = 1;
|
||||||
|
|
||||||
pinMode(PIN_REED, INPUT_PULLUP); pinMode(PIN_PIR, INPUT); ds18b20.begin();
|
pinMode(PIN_REED, INPUT_PULLUP); pinMode(PIN_PIR, INPUT); ds18b20.begin();
|
||||||
pinMode(PIN_RELAY_1, OUTPUT); pinMode(PIN_RELAY_2, OUTPUT); pinMode(PIN_LED_STATUS, OUTPUT);
|
pinMode(PIN_RELAY_1, OUTPUT); pinMode(PIN_RELAY_2, OUTPUT); pinMode(PIN_RELAY_3, OUTPUT); pinMode(PIN_RELAY_4, OUTPUT); pinMode(PIN_LED_STATUS, OUTPUT);
|
||||||
digitalWrite(PIN_RELAY_1, HIGH); digitalWrite(PIN_RELAY_2, HIGH); digitalWrite(PIN_LED_STATUS, LOW);
|
digitalWrite(PIN_RELAY_1, LOW); digitalWrite(PIN_RELAY_2, LOW); digitalWrite(PIN_RELAY_3, LOW); digitalWrite(PIN_RELAY_4, LOW); digitalWrite(PIN_LED_STATUS, LOW);
|
||||||
|
|
||||||
InternalFS.begin();
|
InternalFS.begin();
|
||||||
loadSecurityConfig();
|
loadSecurityConfig();
|
||||||
if (g_armed) digitalWrite(PIN_RELAY_1, LOW); else digitalWrite(PIN_RELAY_1, HIGH);
|
digitalWrite(PIN_RELAY_1, g_relay1_state ? LOW : HIGH);
|
||||||
digitalWrite(PIN_RELAY_2, g_relay2_state ? LOW : HIGH);
|
digitalWrite(PIN_RELAY_2, g_relay2_state ? LOW : HIGH);
|
||||||
|
digitalWrite(PIN_RELAY_3, g_relay3_state ? LOW : HIGH);
|
||||||
|
digitalWrite(PIN_RELAY_4, g_relay4_state ? LOW : HIGH);
|
||||||
|
|
||||||
if (!radio_init()) {
|
if (!radio_init()) {
|
||||||
Serial.println("RADIO FAIL");
|
Serial.println("RADIO FAIL");
|
||||||
|
|||||||
12
src/target.h
12
src/target.h
@@ -98,6 +98,12 @@
|
|||||||
#ifndef PIN_RELAY_2
|
#ifndef PIN_RELAY_2
|
||||||
#define PIN_RELAY_2 4
|
#define PIN_RELAY_2 4
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef PIN_RELAY_3
|
||||||
|
#define PIN_RELAY_3 5
|
||||||
|
#endif
|
||||||
|
#ifndef PIN_RELAY_4
|
||||||
|
#define PIN_RELAY_4 6
|
||||||
|
#endif
|
||||||
#ifndef PIN_LED_STATUS
|
#ifndef PIN_LED_STATUS
|
||||||
#define PIN_LED_STATUS 22
|
#define PIN_LED_STATUS 22
|
||||||
#endif
|
#endif
|
||||||
@@ -108,6 +114,12 @@
|
|||||||
#ifndef PIN_RELAY_2
|
#ifndef PIN_RELAY_2
|
||||||
#define PIN_RELAY_2 5
|
#define PIN_RELAY_2 5
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef PIN_RELAY_3
|
||||||
|
#define PIN_RELAY_3 6
|
||||||
|
#endif
|
||||||
|
#ifndef PIN_RELAY_4
|
||||||
|
#define PIN_RELAY_4 7
|
||||||
|
#endif
|
||||||
#ifndef PIN_LED_STATUS
|
#ifndef PIN_LED_STATUS
|
||||||
#define PIN_LED_STATUS 13
|
#define PIN_LED_STATUS 13
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Reference in New Issue
Block a user