Поддержка счетчиков воды Элехант

Приветствую!

Добавил возможность считывать показания счетчиков воды Элехант через Bluetooth (BLE). В движке правил не спец, так что на идеальность кода не претендую.
Счетчик посылает по BLE свои показания каждые 15 сек.

Скрипт elehant-scan.sh работает следующим образом.

  1. Запускается сканирование всех устройств BLE.
  2. Дампом RAW-данных определяются пакеты только от счетчиков Элехант.
  3. Все обнаруженные пакеты счетчиков Элехант выводятся построчно с значениями: “серийник, показание в М3, показание в литрах, RSSI, штамп времени”.
  4. Таймаут сканирования установлен 120 сек. Если сигнал до счетчков хороший, можно уменьшить до 60 сек, но у меня -80…-85, не всегда за 1 минуту ловятся пакеты.

elehant-scan.sh:

#!/bin/bash

# timeout for scan
timeout=$(($(date +%s) + 120))

halt_hcitool_lescan() {
  pkill --signal SIGINT hcitool
}

trap halt_hcitool_lescan INT

process_complete_packet() {
  ## Elehant packet example
  ##packet="> 04 3E 21 02 01 03 00 82 4B 00 02 01 B0 15 14 FF FF FF 80 C8 0A 01 02 01 82 4B 00 67 44 00 00 69 1E 0A 00 AC"

  local packet=${1//[\ |>]/}

  # process only Elehant packets
  if [[ ! $packet =~ ^043E210201.{10}0201B0 ]]; then
    return
  fi

  serial=$((0x${packet:52:2}${packet:50:2}${packet:48:2}))
  vol_cl=$((0x${packet:60:2}${packet:58:2}${packet:56:2}${packet:54:2}))
    let "vol_m3 = $vol_cl / 10000"
    let "vol_m3_cl = $vol_cl % 10000"
  vol_m3=$vol_m3.$vol_m3_cl
    let "vol_liters = $vol_cl / 10"
    let "vol_liters_cl = $vol_cl % 10"
  vol_liters=$vol_liters.$vol_liters_cl
  rssi=$[$((0x${packet:70:2})) - 256]

  echo $serial"|"$vol_m3"|"$vol_liters"|"$rssi"|"$(date "+%d-%m-%Y %H:%M:%S")
}

read_blescan_packet_dump() {
  # packets span multiple lines and need to be built up
  packet=""
  while read line; do
    # packets start with ">"
    if [[ $line =~ ^\> ]]; then
      # process the completed packet (unless this is the first time through)
      if [ "$packet" ]; then
         if [[ $(date +%s) > $timeout ]]; then
           exit 0
         fi
        process_complete_packet "$packet"
      fi
      # start the new packet
      packet=$line
    else
      # continue building the packet
      packet="$packet $line"
    fi
    seconds=$(date +%s)
  done
}

# begin BLE scanning
killall hcitool
hciconfig hci0 down
sleep 1
hciconfig hci0 up
hcitool lescan --duplicates > /dev/null &
sleep 1
# make sure the scan started
if [ "$(pidof hcitool)" ]; then
  # start the scan packet dump and process the stream
  hcidump --raw | read_blescan_packet_dump
else
  echo "ERROR: it looks like hcitool lescan isn't starting up correctly" >&2
  exit 1
fi

Соответствие серийника счетчика и его назначения задается в движке правил. Запускается раз в 5 минут.

elehant.js:

// Enter your meters here
var meter1_serial = 19330;
var meter1_device = "water_meter_ro";
var meter1_title = "Water meter (RO)";
var meter2_serial = 22222;
var meter2_device = "water_meter_cold";
var meter2_title = "Water meter (cold)";
var meter3_serial = 33333;
var meter3_device = "water_meter_hot";
var meter3_title = "Water meter (hot)";

defineVirtualDevice(meter1_device, {
    title: meter1_title,
    cells: {
        "Serial number": {
            type: "text",
            value: ""
        },
        "Value in m3": {
            type: "text",
            value: ""
        },
        "Value in liters": {
            type: "text",
            value: ""
        },
        "RSSI": {
            type: "text",
            value: ""
        },
        "Timestamp": {
            type: "text",
            value: ""
        }
    }
});

//defineVirtualDevice(meter2_device, {
//    title: meter2_title,
//    cells: {
//        "Serial number": {
//            type: "text",
//            value: ""
//        },
//        "Value in m3": {
//            type: "text",
//            value: ""
//        },
//        "Value in liters": {
//            type: "text",
//            value: ""
//        },
//        "RSSI": {
//            type: "text",
//            value: ""
//        },
//        "Timestamp": {
//            type: "text",
//            value: ""
//        }
//    }
//});

//defineVirtualDevice(meter3_device, {
//    title: meter3_title,
//    cells: {
//        "Serial number": {
//            type: "text",
//            value: ""
//        },
//        "Value in m3": {
//            type: "text",
//            value: ""
//        },
//        "Value in liters": {
//            type: "text",
//            value: ""
//        },
//        "RSSI": {
//            type: "text",
//            value: ""
//        },
//        "Timestamp": {
//            type: "text",
//            value: ""
//        }
//    }
//});

defineRule("elehant_dynamic_refresh", {
  when: cron("0 */5 * * *"),
  then: function () {
    runShellCommand("bash /mnt/data/root/elehant-scan.sh", {
      captureOutput: true,
      exitCallback: function (exitCode, capturedOutput) {
        if (exitCode != 0) return;
        var meterList=capturedOutput.split("\n")
          for (var i=0; i<meterList.length; ++i) {
            var meterParts=meterList[i].split("|")
            if (meterParts[0] == meter1_serial) {
               dev[meter1_device]["Serial number"] = meterParts[0];
               dev[meter1_device]["Value in m3"] = meterParts[1];
               dev[meter1_device]["Value in liters"] = meterParts[2];
               dev[meter1_device]["RSSI"] = meterParts[3];
               dev[meter1_device]["Timestamp"] = meterParts[4];
            }
            if (meterParts[0] == meter2_serial) {
               dev[meter2_device]["Serial number"] = meterParts[0];
               dev[meter2_device]["Value in m3"] = meterParts[1];
               dev[meter2_device]["Value in liters"] = meterParts[2];
               dev[meter2_device]["RSSI"] = meterParts[3];
               dev[meter2_device]["Timestamp"] = meterParts[4];               
            }
            if (meterParts[0] == meter3_serial) {
               dev[meter3_device]["Serial number"] = meterParts[0];
               dev[meter3_device]["Value in m3"] = meterParts[1];
               dev[meter3_device]["Value in liters"] = meterParts[2];
               dev[meter3_device]["RSSI"] = meterParts[3];
               dev[meter3_device]["Timestamp"] = meterParts[4];               
            }          
          }
      }
    });    
  }
});

elehant2

3 лайка

Добрый день!

Очень здорово!
Спасибо, что поделились.

А подскажите, пожалуйста:

  1. С какой моделью счётчика вы работали?
  2. С какими моделями, по вашему мнению, это будет работать?

Я зашёл на https://elehant.ru/, и на первый взгляд кажется, что между их счётчиками воды не должно быть отличий. Но, возможно, какие-то различия всё-таки есть.

У меня СВД-15. СВД-20 отличается только посадочным диаметром. Есть еще модели СВТ-x (счетчик тарифный), по ним точно не могу сказать, возможно, там передаются еще какие-то параметры.

1 лайк

Прописал скрипт и правило в WB6. Не отображает в интерфейсе:
Логи rules:

31-03-2022 12:07:51.788	INFO: network/Wi-Fi 2 IP: failed to convert value '', passing raw
31-03-2022 12:07:51.668	INFO: network/GPRS IP: failed to convert value '', passing raw
31-03-2022 12:07:51.647	INFO: network/Ethernet 2 IP: failed to convert value '', passing raw
31-03-2022 12:07:51.598	Device "ppp0" does not exist.

Реже проскакивает такая строка, только сейчас увидел:

31-03-2022 12:20:00.230	/mnt/data/root/elehant-scan.sh: строка 73: 29031 Завершено      hcitool lescan --duplicates > /dev/null

Если прямо запросить из cli, то устройство счетчик элехант видит:

/mnt/data/root# hcitool lescan --duplicates
LE Scan ...
B0:02:02:00:41:50 (unknown)
B0:02:02:00:41:50 (unknown)

Но иногда и не видит:

/mnt/data/root# hcitool lescan --duplicates
Set scan parameters failed: Input/output error

На время спасает:

hciconfig hci0 down
hciconfig hci0 up

Потом опять ловит пакеты от счетчика из cli

Куда покопать?
Используется встроенный модуль.

P.S. серийник счетчика прописал правильно, с адресом в hex совпадает, который видится из cli.
Рядом стоит модуль на ESP32, прошитый esphome, он пакеты норм ловит и отдаёт, т.е. пакеты есть и настройки счетчика в скрипте, надеюсь, прописаны правильно, проверил несколько раз.

var meter1_serial = 16ххх;

В новой версии скрипта это уже учитывается (дергается hci интерфейс перед сканированием). Но вот с зависанием bluetooth сделать ничего не получается - только перезагружать контроллер. У себя сделал доп. проверку в правилах - если данные по счетчикам не обновлялись более суток - значит bluetooth завис.
Но надо сказать, что в релизе wb-2201 зависания bluetooth случаются существенно реже, раньше было каждые 2-3 недели.

Но в wb-2201 всплыла другая проблема - существенное снижение быстродействия системы. Особенно заметно по датчикам движения - свет включается с задержкой в несколько секунд во время выполнения данного сканирования счетчиков. Пока не знаю что с этим делать, видимо, создам отдельную тему, но пока пришлось снизить частоту сканирования дл 1 раз в час.

#!/bin/bash

# timeout for scan
timeout=$(($(date +%s) + 120))

halt_hcitool_lescan() {
  pkill --signal SIGINT hcitool
}

trap halt_hcitool_lescan INT

process_complete_packet() {
  ## Elehant packet example
  #packet="> 04 3E 21 02 01 03 00 82 4B 00 02 01 B0 15 14 FF FF FF 80 C8 0A 01 02 01 82 4B 00 67 44 00 00 69 1E 0A 00 AC"

  local packet=${1//[\ |>]/}

  # process only Elehant packets
  if [[ ! $packet =~ ^043E210201.{10}0201B0 ]]; then
    return
  fi

  serial=$((0x${packet:52:2}${packet:50:2}${packet:48:2}))
  vol_cl=$((0x${packet:60:2}${packet:58:2}${packet:56:2}${packet:54:2}))
    let "vol_m3 = $vol_cl / 10000"
    let "vol_m3_cl = $vol_cl % 10000"
  if (( $vol_m3_cl < 10 )); then
    vol_m3=$vol_m3."000"$vol_m3_cl
  fi
  if (( $vol_m3_cl >= 10 )) && (( $vol_m3_cl < 100 ))  ; then
    vol_m3=$vol_m3."00"$vol_m3_cl
  fi
  if (( $vol_m3_cl >= 100 )) && (( $vol_m3_cl < 1000 ))  ; then
    vol_m3=$vol_m3."0"$vol_m3_cl
  fi
  if (( $vol_m3_cl > 1000 )); then
    vol_m3=$vol_m3.$vol_m3_cl
  fi
    let "vol_liters = $vol_cl / 10"
    let "vol_liters_cl = $vol_cl % 10"
  vol_liters=$vol_liters.$vol_liters_cl
  rssi=$[$((0x${packet:70:2})) - 256]

  echo $serial"|"$vol_m3"|"$vol_liters"|"$rssi"|"$(date "+%Y-%m-%d %H:%M:%S")
}

read_blescan_packet_dump() {
  # packets span multiple lines and need to be built up
  packet=""
  while read line; do
    # packets start with ">"
    if [[ $line =~ ^\> ]]; then
      # process the completed packet (unless this is the first time through)
      if [ "$packet" ]; then
         if [[ $(date +%s) > $timeout ]]; then
           exit 0
         fi
        process_complete_packet "$packet"
      fi
      # start the new packet
      packet=$line
    else
      # continue building the packet
      packet="$packet $line"
    fi
    seconds=$(date +%s)
  done
}

# begin BLE scanning
killall hcitool
hciconfig hci0 down
sleep 1
hciconfig hci0 up
nice -n 20 hcitool -i hci0 lescan --duplicates > /dev/null &
sleep 1
# make sure the scan started
if [ "$(pidof hcitool)" ]; then
  # start the scan packet dump and process the stream
  nice -n 20 hcidump -i hci0 --raw | read_blescan_packet_dump
else
  echo "ERROR: it looks like hcitool lescan isn't starting up correctly" >&2
  exit 1
fi

Спасибо! Скрипт поменял, но пока не заработало) Судя по активности, немногим требуется синезуб в контроллере))
Кратко, заменил скрипт на новый, увидел, что поправлено. Правило не менял в контроллере. Проверил еще раз серийник. Сел ждать показаний в Устройствах контроллера, параллельно мониторя лог wb-rules. В устройствах появляется блок с наименованиями (Как и ранее), но без данных, в log wb.rules показывает только это:

01-04-2022 16:11:51.966	INFO: network/Wi-Fi 2 IP: failed to convert value '', passing raw
01-04-2022 16:11:51.856	INFO: network/Ethernet 2 IP: failed to convert value '', passing raw
01-04-2022 16:11:51.822	INFO: network/GPRS IP: failed to convert value '', passing raw
01-04-2022 16:11:51.762	Device "ppp0" does not exist.
01-04-2022 16:11:27.174	INFO: reloading file: /etc/wb-rules/elehant.js

и далее идёт повтор только этих строк:

01-04-2022 16:11:51.966	INFO: network/Wi-Fi 2 IP: failed to convert value '', passing raw
01-04-2022 16:11:51.856	INFO: network/Ethernet 2 IP: failed to convert value '', passing raw
01-04-2022 16:11:51.822	INFO: network/GPRS IP: failed to convert value '', passing raw
01-04-2022 16:11:51.762	Device "ppp0" does not exist.

Где теряются данные?
image

Дополню:
рядом лежит esp32 (esphome) и тоже мониторит эфир, удачно ловит пакеты от счетчика и закидывает их в home assistant. Т.е. данные в эфире есть.

Я бы на вашем месте сначала запустил скрипт в консоли, посмотрел что он выдает. Возможно, он не ловит счетчики. У меня сигнал от счетчиков не очень хороший и далеко не каждый раз и не все счетчики ловит.
Должен вывод типа такого:

./elehant-scan.sh
19xxx|9.5623|9562.3|-84|2022-04-01 16:27:28

Если ничего не выводится, значит не ловит счетчики… Можно попробовать увеличить в скрипте время сканирования до 3-5 минут, но дамп BLE-пакетов создает высокую нагрузку на контроллер.

P.s.
Если вы используете встроенный Bluetooth-адаптер, то скорее всего он просто не работает. Это все неоднократно обсуждалось в этой ветке. Я давно пользуюсь внешним USB-адаптером - с ним нормально, а на встроенном у меня так ничего и не заработало.

Поэкспериментировал.
Если запускаю из командной строки:

hcitool -i hci0 lescan --duplicates

То всё ловится:

 hcitool -i hci0 lescan --duplicates
LE Scan ...
B0:02:02:00:41:50 (unknown)
7E:E6:30:D0:3D:57 (unknown)
7E:E6:30:D0:3D:57 (unknown)
7E:E6:30:D0:3D:57 (unknown)
5C:E6:D1:0C:1E:F9 (unknown)
7E:E6:30:D0:3D:57 (unknown)
5C:E6:D1:0C:1E:F9 (unknown)
5C:E6:D1:0C:1E:F9 (unknown)
7E:E6:30:D0:3D:57 (unknown)

Видно, что и счетчик есть в принятых пакетах.
Но если запустить скрипт, то он отрабатывает положенное время и завершается стандартно сообщая об этом

/mnt/data/root# ./elehant-scan.sh
./elehant-scan.sh: строка 84: 15442 Завершено      nice -n 20 hcitool -i hci0 lescan --duplicates > /dev/null

Попробую посражаться за встроенный)), если поможете
Из cli то работает более менее.
Добавил в скрипт команды и по общему беспроводному интерфейсу, т.к. рекомендовали, что от него есть зависимость и, скорее всего так, т.к. в cli начал работать более менее стабильнее.

ifconfig wlan0 down && hciconfig hci0 down
sleep 1
ifconfig wlan0 up && hciconfig hci0 up

А серийный номер счетчика только в rules указывать?
В скрипте ничего не надо поменять?

Проверил скрипт. Оказалось, что у моего счетчика другой серийник…
Т.е. заменил его в этих строках

  # process only Elehant packets
  if [[ ! $packet =~ ^043E210201.{10}0202B0 ]]; then

и скрипт заработал. Т.е. было 0201B0, а у меня 0202B0. Видимо сменили мак адрес.

/mnt/data/root# ./elehant-scan.sh
16xxx|410.8949|410894.9|-84|2022-04-01 17:24:24
16xxx|410.8949|410894.9|-84|2022-04-01 17:25:34

Не знаю, зачем затирать серийник, но повторил))
Сейчас жду реакции от правила в контроллере.

image

Данные поступают, всё отлично! Помониторил 30 минут, пока сбоев не было. Отпишусь по прошествии большего времени.

Подведем итоги:
image

4 суток полет нормальный. Данные поступают без сбоев.
Спасибо за скрипт и помощь!

Добрый, спасибо за скрипт. Для team WB - у вас ошибка в Вики, там написано что счетчик подключается по rs485, поправьте. Я доставал их суппорт с тем что должен где то быть выход, пока не зашел сюда :)))

Здравствуйте! Да, вы правы. Спасибо за информацию, исправим!