Локальные измерения
USB-донгл продолжает использовать собственные каналы DS18x20, HR202 и термистор как обычный TPlung датчик.
Сценарий с Entware
Это классический сценарий с Entware: роутер Keenetic работает как центральный узел, USB-донгл TPlung делает локальные измерения на собственных датчиках, собирает телеметрию по BLE от других TPlung устройств, публикует значения в MQTT брокер, отправляет ежедневные отчеты на e-mail и может принимать сервисные команды через почтовый ящик.
USB-донгл продолжает использовать собственные каналы DS18x20, HR202 и термистор как обычный TPlung датчик.
Параллельно донгл принимает advertising от других TPlung устройств и передает их показания в систему роутера.
Роутер публикует локальные и BLE-данные в MQTT брокер, ведет архив и отправляет ежедневную сводку на e-mail.
Почтовый обработчик принимает разрешенные письма, выполняет сервисные команды и отправляет результат обратно.
Пошагово
Ниже опорная последовательность для стенда Keenetic + Entware + TPlung. Набор пакетов и скриптов может отличаться в вашей сборке, но логика этапов остается той же.
Разверните Скачать образ Entware mipsel для Keenetic на USB-накопитель. Образ сделан минимальным; после записи на флешку при необходимости расширьте раздел до нужного размера в GParted. Это USB-модели Keenetic на little-endian MIPS/MediaTek MT7621 и MT7628: например Giga KN-1010, Ultra KN-1810, Viva KN-1910, Extra KN-1711 и Omni KN-1410.
Подключите USB-донгл к роутеру - все готово к работе.
Задайте параметры в конфигурационных файлах на разделенном SMB-шаре.
MQTT брокер
В конфигурационных топиках удобно хранить e-mail адрес, переключатель отчетов и параметры supervise.
Settings/Supervise/mailAddress Settings/Supervise/reportToEmail Settings/Supervise/reports/...
Данные датчиков и энергометрии публикуются в прикладные топики и могут быть связаны с устройствами Home Assistant.
Thermo/... Energy/... Binding/...
Для диагностики можно подписаться на дерево топиков и убедиться, что значения обновляются в реальном времени.
mosquitto_sub -v -t '#'
Ежедневные e-mail отчеты
Основной ежедневный сценарий формирует HTML-отчет устройств: датчики, энергометрия, свежесть данных, RSSI, состояние батареи и расчет потребления по тарифам, если они заданы в конфигурации.
Карточки датчиков с именем/MAC, временем последнего значения, RSSI, состоянием батареи, температурой и влажностью.
Параметр batt_status попадает в отчет как отдельный бейдж, а уведомления могут приходить с состоянием OK или FAILURE.
Показания энергометрии, суммы по парным каналам, прирост за день/месяц и стоимость в рублях при настроенном tarif.cfg.
Ниже упрощенный пример фрагмента отчета, который может отправляться прямо в body письма без вложений.
E-mail управление
Роутер проверяет входящую почту, забирает свежие команды и не требует внешнего входящего подключения к объекту.
imapfilter mailbox: hostname
Обрабатываются только свежие письма от адресов из allow-list; остальные отправители и темы игнорируются.
From: allowed@example.com Subject: command
В text/plain части письма передается Perl-хеш: список команд run и, при необходимости, список файлов get.
{
run => ['date', '/opt/usr/local/bin/report_total.pm'],
get => ['/opt/usr/local/data/settings.conf']
};
Результат выполнения приходит обратным письмом; запрошенные файлы упаковываются в 7z-архив и прикладываются к ответу.
Subject: results: command Attachment: 7z_data.7z
Эксплуатация
Храните копии `tarif.cfg`, `mac2name.cfg` и пользовательских скриптов, чтобы быстро восстановить систему после замены носителя.
Добавьте контрольный MQTT heartbeat, проверку отправки e-mail и правила OK/FAILURE для батареи, температуры и напряжения.
Оставьте MQTT для онлайн-автоматизации, а e-mail отчеты для ежедневной аналитики и журнала состояния объекта.
Keenetic как удаленный шлюз телеметрии для Home Assistant
Для этого сценария на Keenetic нужен только системный модемный компонент: EntWare не требуется. После задания AT-команды в профиле 3G/4G телеметрия появляется в системном журнале JSON-объектами, которые далее можно забирать по REST API в Home Assistant или другие интеграции.
1) Установить компонент ОС для USB-модемов.
2) В настройках подключения 3G/4G включить дополнительную инициализацию и в поле AT1 указать ATSCAN или ATMEAS.
Процесс modem/chat пишет сервисные строки (send/expect), а затем JSON с измерениями по устройствам.
Эти JSON-строки являются готовым источником для REST и дальнейшего импорта в Home Assistant.
ATSCAN для BLE-сбора, либо ATMEAS для локальных измерений.mac, T*, H*, batt, E*.Ниже реальные скриншоты из веб-интерфейса: установка компонента ОС, задание AT-команды и JSON в системном журнале.
Используйте ATSCAN для BLE-сканирования и сбора устройств или ATMEAS для локальных каналов.
AT1=ATSCAN # либо AT1=ATMEAS
В журнале должны появляться строки от chat[...], затем пакеты телеметрии в JSON.
send(atscan^M)
expect(OK)
{"mac":"E4:83:A9:68:8A:B8","t1":26.1,"H1":44.0,"batt":3}
Чтение системного лога можно делать напрямую через RCI REST API Keenetic с авторизацией.
curl -N "https://rest-api.netcraze.pro/rci/show/log" \
--user "tplung:tplung" \
--request POST \
--header "Content-Type: application/json" \
--data-ascii '{"max-lines":10,"once":"false"}'
{
"log": {
"163770": {
"message": {
"level": "Info",
"label": "I",
"message": "{\"mac\":\"E4:83:A9:68:8A:B8\",\"t1\":26.8,\"H1\":45.7,\"batt\":3}^M"
},
"timestamp": "Jul 4 17:39:28",
"ident": "chat[2575]",
"id": 163770
}
}
}
Файл: /homeassistant/configuration.yaml
# Loads default set of integrations. Do not remove.
default_config:
# Load frontend themes from the themes folder
frontend:
themes: !include_dir_merge_named themes
automation: !include automations.yaml
script: !include scripts.yaml
scene: !include scenes.yaml
rest:
- resource: "https://rest-api.netcraze.pro/rci/show/log"
method: POST
authentication: basic
username: "tplung"
password: "tplung"
headers:
Content-Type: "application/json"
payload: '{"max-lines":15,"once":"false"}'
scan_interval: 10
sensor:
- name: "Sensor Kitchen Temperature"
unique_id: "tplung_kitchen_temp_unique_01"
unit_of_measurement: "°C"
device_class: temperature
value_template: >
{% set raw = value_json.log.values() | map(attribute='message.message') | select('string') | list %}
{% set parsed = raw
| map('regex_replace', '\\^M$', '')
| select('match', '^\\{.*\\}$')
| map('from_json')
| list %}
{% set match = parsed | selectattr('mac', 'eq', 'E4:83:A9:68:8A:B8') | list %}
{{ match[-1].t1 | float }}
availability: >
{% if value_json is defined and value_json.log is defined %}
{% set raw = value_json.log.values() | map(attribute='message.message') | select('string') | list %}
{% set parsed = raw | map('regex_replace', '\\^M$', '') | select('match', '^\\{.*\\}$') | map('from_json') | list %}
{{ parsed | selectattr('mac', 'eq', 'E4:83:A9:68:8A:B8') | list | length > 0 }}
{% else %}
false
{% endif %}
- name: "Sensor Kitchen Humidity"
unique_id: "tplung_kitchen_hum_unique_01"
unit_of_measurement: "%"
device_class: humidity
value_template: >
{% set raw = value_json.log.values() | map(attribute='message.message') | select('string') | list %}
{% set parsed = raw | map('regex_replace', '\\^M$', '') | select('match', '^\\{.*\\}$') | map('from_json') | list %}
{% set match = parsed | selectattr('mac', 'eq', 'E4:83:A9:68:8A:B8') | list %}
{{ match[-1].H1 | float }}
availability: >
{% if value_json is defined and value_json.log is defined %}
{% set raw = value_json.log.values() | map(attribute='message.message') | select('string') | list %}
{% set parsed = raw | map('regex_replace', '\\^M$', '') | select('match', '^\\{.*\\}$') | map('from_json') | list %}
{{ parsed | selectattr('mac', 'eq', 'E4:83:A9:68:8A:B8') | list | length > 0 }}
{% else %}
false
{% endif %}
- name: "Sensor Outdoor Temperature T1"
unique_id: "tplung_outdoor_temp_unique_02"
unit_of_measurement: "°C"
device_class: temperature
value_template: >
{% set raw = value_json.log.values() | map(attribute='message.message') | select('string') | list %}
{% set parsed = raw | map('regex_replace', '\\^M$', '') | select('match', '^\\{.*\\}$') | map('from_json') | list %}
{% set match = parsed | selectattr('mac', 'eq', '5C:53:10:AB:C5:CB') | list %}
{{ match[-1].T1 | float }}
availability: >
{% if value_json is defined and value_json.log is defined %}
{% set raw = value_json.log.values() | map(attribute='message.message') | select('string') | list %}
{% set parsed = raw | map('regex_replace', '\\^M$', '') | select('match', '^\\{.*\\}$') | map('from_json') | list %}
{{ parsed | selectattr('mac', 'eq', '5C:53:10:AB:C5:CB') | list | length > 0 }}
{% else %}
false
{% endif %}