Сценарий с Entware

TPlung для Keenetic: Entware, USB-донгл, MQTT и отчеты

Это классический сценарий с Entware: роутер Keenetic работает как центральный узел, USB-донгл TPlung делает локальные измерения на собственных датчиках, собирает телеметрию по BLE от других TPlung устройств, публикует значения в MQTT брокер, отправляет ежедневные отчеты на e-mail и может принимать сервисные команды через почтовый ящик.

L

Локальные измерения

USB-донгл продолжает использовать собственные каналы DS18x20, HR202 и термистор как обычный TPlung датчик.

B

BLE-коллектор

Параллельно донгл принимает advertising от других TPlung устройств и передает их показания в систему роутера.

M

MQTT и отчеты

Роутер публикует локальные и BLE-данные в MQTT брокер, ведет архив и отправляет ежедневную сводку на e-mail.

C

Команды по e-mail

Почтовый обработчик принимает разрешенные письма, выполняет сервисные команды и отправляет результат обратно.

Пошагово

Запуск USB-донгла TPlung на Keenetic

Ниже опорная последовательность для стенда Keenetic + Entware + TPlung. Набор пакетов и скриптов может отличаться в вашей сборке, но логика этапов остается той же.

01 Подготовка Entware

Разверните Скачать образ 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.

02 Запуск сервисов TPlung

Подключите USB-донгл к роутеру - все готово к работе.

03 Настройка отчета и MQTT

Задайте параметры в конфигурационных файлах на разделенном SMB-шаре.

MQTT брокер

MQTT топики TPlung и проверка данных

Сервисные настройки

В конфигурационных топиках удобно хранить e-mail адрес, переключатель отчетов и параметры supervise.

Settings/Supervise/mailAddress
Settings/Supervise/reportToEmail
Settings/Supervise/reports/...

Поток телеметрии

Данные датчиков и энергометрии публикуются в прикладные топики и могут быть связаны с устройствами Home Assistant.

Thermo/...
Energy/...
Binding/...

Проверка из CLI

Для диагностики можно подписаться на дерево топиков и убедиться, что значения обновляются в реальном времени.

mosquitto_sub -v -t '#'

Ежедневные e-mail отчеты

E-mail отчеты TPlung по датчикам и счетчикам

Основной ежедневный сценарий формирует HTML-отчет устройств: датчики, энергометрия, свежесть данных, RSSI, состояние батареи и расчет потребления по тарифам, если они заданы в конфигурации.

S

HTML-отчет устройств

Карточки датчиков с именем/MAC, временем последнего значения, RSSI, состоянием батареи, температурой и влажностью.

B

Батарея и статусы

Параметр batt_status попадает в отчет как отдельный бейдж, а уведомления могут приходить с состоянием OK или FAILURE.

E

Энергия и тарифы

Показания энергометрии, суммы по парным каналам, прирост за день/месяц и стоимость в рублях при настроенном tarif.cfg.

Пример HTML в теле письма

Ниже упрощенный пример фрагмента отчета, который может отправляться прямо в body письма без вложений.

Пример ежедневного HTML-отчета TPlung в теле письма

E-mail управление

E-mail команды для роутера Keenetic

IMAP-слушатель

Роутер проверяет входящую почту, забирает свежие команды и не требует внешнего входящего подключения к объекту.

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

Эксплуатация

Эксплуатация TPlung, MQTT и e-mail отчетов

Резерв конфигурации

Храните копии `tarif.cfg`, `mac2name.cfg` и пользовательских скриптов, чтобы быстро восстановить систему после замены носителя.

Контроль доставки

Добавьте контрольный MQTT heartbeat, проверку отправки e-mail и правила OK/FAILURE для батареи, температуры и напряжения.

Разделение ролей

Оставьте MQTT для онлайн-автоматизации, а e-mail отчеты для ежедневной аналитики и журнала состояния объекта.

Keenetic как удаленный шлюз телеметрии для Home Assistant

Сбор телеметрии в режиме эмуляции USB-модема

Для этого сценария на Keenetic нужен только системный модемный компонент: EntWare не требуется. После задания AT-команды в профиле 3G/4G телеметрия появляется в системном журнале JSON-объектами, которые далее можно забирать по REST API в Home Assistant или другие интеграции.

Минимум действий на Keenetic

1) Установить компонент ОС для USB-модемов.

2) В настройках подключения 3G/4G включить дополнительную инициализацию и в поле AT1 указать ATSCAN или ATMEAS.

Что будет в журнале

Процесс modem/chat пишет сервисные строки (send/expect), а затем JSON с измерениями по устройствам.

Эти JSON-строки являются готовым источником для REST и дальнейшего импорта в Home Assistant.

Пошагово: только компонент ОС + AT-команда

Шаг 1. Компоненты операционной системы
  • Откройте список компонентов Keenetic.
  • Добавьте модемный компонент ОС (без EntWare).
Шаг 2. Подключение 3G/4G
  • Откройте профиль подключения модема.
  • Включите поле дополнительной инициализации.
  • AT1 = ATSCAN для BLE-сбора, либо ATMEAS для локальных измерений.
Шаг 3. Контроль результата
  • Проверьте системный журнал: должны идти JSON-объекты с mac, T*, H*, batt, E*.
  • Отдайте эти данные в REST API для внешнего чтения (Home Assistant, скрипты, Grafana).

Реальные экраны Keenetic

Ниже реальные скриншоты из веб-интерфейса: установка компонента ОС, задание AT-команды и JSON в системном журнале.

1) Компоненты ОС: добавить модемный компонент Keenetic.
2) Профиль 3G/4G: AT1 = ATSCAN или ATMEAS.
3) Системный журнал: строки chat/modem и JSON-телеметрия для REST API.
01 Задать AT-команду в модеме

Используйте ATSCAN для BLE-сканирования и сбора устройств или ATMEAS для локальных каналов.

AT1=ATSCAN
# либо
AT1=ATMEAS
02 Убедиться, что JSON идет в лог

В журнале должны появляться строки от chat[...], затем пакеты телеметрии в JSON.

send(atscan^M)
expect(OK)
{"mac":"E4:83:A9:68:8A:B8","t1":26.1,"H1":44.0,"batt":3}
03 Забирать данные по REST

Чтение системного лога можно делать напрямую через 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
    }
  }
}

Пример REST интеграции Home Assistant

Файл: /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 %}