Měření a ukládání dat v cloudu (3.)

  1. Měříme a získáváme měřené údaje
  2. Odesíláme údaje a sbíráme v cloudu
  3. Sebraná data ukládáme do databáze
  4. Data z databáze zpracujeme, zobrazíme a analyzujeme

3. Ukládání měření do databáze

Pro sběr měřených dat je nejvhodnější databáze časových řad (TSDB – Time Series Database), protože je přizpůsobena pro optimální uložení velkého množství dat v časové řadě a pro další zpracování disponuje optimalizovanými agregačními funkcemi. Velmi často používanou databází je InfluxDB, která je zdarma i s užitečnými nástroji ke zpracování dat a díky připraveným knihovnám se s ní snadno pracuje v různých programovacích jazycích.

3.1 Instalace vlastní InfluxDB

Databázový stroj InfluxDB můžeme v oblíbeném operačním systému Debian GNU/Linux či Ubuntu/Mint Linux jednoduše nainstalovat ze standardního instalačního balíku influxdb. Případně na jiných platformách podle návodu k instalaci na webu influxdata.com.

Předpokladem je, že máme k dispozici hodně úložného prostoru, protože data mohou časem narůst do obřích rozměrů.

$ apt install influxdb

Nepotřebujeme-li zaznamenávat dotazy a přístupy přes HTTP, tak můžeme vypnout logování v konfiguračním souboru /etc/influxdb/influxdb.conf.

[data]
query-log-enabled = false

[http]
log-enabled = false

3.2 InfluxDB v cloudu

InfluxDB lze používat také v cloudu a zde se budeme věnovat platformě InfluxDB Cloud 2.0, která nabízí provoz připraveného databázového stroje v cloudu AWS (v Evropě Frankfurt n.M.), v Azure (v Evropě Amsterdam) nebo v Google Cloud (v Evropě Belgie).

Varianta Free je k dispozici všem zdarma s několika omezeními: zápis 5 MB za 5 vteřin, dotazy 300 MB za 5 minut, 10000 řad dat a maximálně 2 databáze (Buckets) se zachováním dat 30 dní. Pro nenáročné domácí použití naprosto dostačující.

Pro další práci s databází jsou klíčové následující kroky s poznamenáním údajů potřebných k přístupu k databázi:

  • Podle vybrané lokality poznamenat URL InfluxDB Cluster Node
  • Při instalaci nebo později vytvořit databázi (Bucket) na stránce Data/Buckets
    – poznamenat Bucket ID
  • Vytvořit přístupový Token a nastavit oprávnění na stránce Data/Tokens
    – poznamenat Token
  • Pojmenovat organizaci – Organization/About
    – poznamenat User ID a Organization ID

3.3 Ukládání zpráv z MQTT brokeru do InfluxDB

Cesta měřených údajů z MQTT klienta na MQTT broker a přes MQTT - TSDB bridge do databáze časových řad.

Je několik možností, jak přijímat zprávy z MQTT brokeru a data ukládat do databáze InfluxDB.

  • Vlastní skript/program – v Pythonu lze pro připojení k MQTT brokeru a InfluxDB použít moduly Paho MQTT Client a InfluxDB-Python. Jako například ve skriptech MQTT 2 InfluxDB Bridge nebo MQTTInfluxDBBridge.py.
  • Program Telegraf obsahuje modul MQTT Consumer, který pracuje jako MQTT klient zapisuje odebírané zprávy do InfluxDB. V konfiguračním souboru /etc/telegraf/telegraf.conf stačí nastavit jen pár údajů nebo si vygenerovat potřebnou konfiguraci v administraci InfluxDB Cloud.
[[inputs.mqtt_consumer]]
   servers = ["tcp://42616269c5a14a655a6c6f64c49b6a.s2.eu.hivemq.cloud:8883"]
   topics = [
     "test/#",
   ]
   username = "kralicek-itacek"
   password = "***heslo***"

[[outputs.influxdb_v2]]
   urls = ["https://eu-central-1-1.aws.cloud2.influxdata.com"]
   token = "$INFLUX_TOKEN"
   organization = "Kralickovo"
   bucket = "Kralickuv Bucket"

Do souboru /etc/default/telegraf vložíme token pro přístup k databázi InfluxDB v podobě nastavení proměnné prostředí:

INFLUX_TOKEN=CkJhYmnFoWUgZG8ga2/FoWUuCgo=
  • Definice toku dat ve vývojovém nástroji Node-RED – tuto variantu popíšu podrobněji níže.

3.4 Node-RED jako MQTT klient zapisující data do InfluxDB

Můžeme použít vlastní instalaci prostředí Node-RED na počítači nebo v cloudu. Nejzajímavější je spuštění v IBM Cloud v rámci free tier, kde lze celé používat zdarma (stačí úroveň Lite). Instalaci provedeme po počáteční registraci podle návodu. Během instalace se pro uložení dat programu vytvoří databáze Cloudant, spustí instance aplikace Node-RED a nakonfigurují nějaké další služby potřebné pro běh prostředí.

Po přihlášení do editoru Node-RED nastavíme:

  • Menu / Manage palette / Install – do vyhledávacího pole zadáme influxdb a nainstalujeme doplněk node-red-contrib-stackhero-influxdb-v2
  • Založíme a přejmenujeme nové Flow
  • Vytvoříme node typu „mqtt-in“ a nastavíme:
    • Server (Connection – hostname, port, Use TLS; Security – uživatelské jméno a heslo)
    • Topic
    • Output – a parsed JSON object
  • Vytvoříme node typu „function“, pojmenujeme „toFloat“ a na záložce onMessage zadáme kód:
msg.payload = parseFloat(msg.payload);
return msg;
  • Vytvoříme node typu „change“, pojmenujeme „prepare fields“ a nastavíme pravidlo:
    • Set msg.payload to expression
{
   'precision': 'ms',
   'data': [
        {
            'measurement': 'test',
            'tags': {
                'host': 'zajickuvprevadec',
                'topic': msg.topic
            },
            'fields': {   
                'value': msg.payload
            },
           'timestamp':$toMillis($now())
        }
    ]
}
  • Vytvoříme node typu „Stackhero-InfluxDB-v2-write“ a nastavíme přístupové údaje Database – Host, Port, TLS, Token, Organization, Default bucket
  • Všechny vytvořené nody postupně propojíme z výstupu prvního na vstup druhého atd. Případně můžeme přidat node typu „debug“ pro výpis informací.
  • Po vytvoření kompletního Flow použijeme vpravo nahoře tlačítko Deploy a mělo by začít fungovat.

Čtení teploměru přes Bluetooth v CoreELEC

Jak načíst hodnoty aktuální teploty a vlhkosti z bezdrátového teploměru Xiaomi Temperature and Humidity Monitor 2 nebo podobného přes Bluetooth v TV boxu se systémem CoreELEC.

Proč zrovna v TV boxu? Protože bych rád údaje o teplotě dále zpracovával do přehledných statistik a grafů, a k tomu je zapotřebí trvale běžící zařízení, které bude naměřené údaje pravidelně přijímat, a posílat k dalšímu zpracování na MQTT broker nebo do databáze. Mobilní telefon nebo obecně Android se na to moc nehodí a jiné vhodné zařízení kompatibilní s Bluetooth 4.2 Low Energy (BLE) nemám k dispozici.

Prerekvizity

Co všechno budeme potřebovat:

  • Zařízení s přijímačem Bluetooth minimálně 4.2,
  • nainstalovaný systém CoreELEC s volným místem na paměťovém úložišti, se zapnutým SSH přístupem a s funkčním připojením k Internetu (na podobném systému LibreELEC možná bude fungovat také),
  • SSH terminál,
  • bezdrátový teploměr Xiaomi LYWSD03MMC nebo podobný s komunikačním modulem Bluetooth.

Jak zjistit, jakou verzi Bluetooth podporuje zařízení, kde chceme teplotu načítat z teploměru:

$ hciconfig -a
hci0:	Type: Primary  Bus: UART
	BD Address: 12:34:56:78:9A:BC  ACL MTU: 1021:5  SCO MTU: 255:11
	UP RUNNING PSCAN 
	RX bytes:2504 acl:4 sco:0 events:95 errors:0
	TX bytes:4463 acl:6 sco:0 commands:65 errors:0
	Features: 0xff 0xff 0xff 0xfe 0xdb 0xfd 0x7b 0x87
	Packet type: DM1 DM3 DM5 DH1 DH3 DH5 HV1 HV2 HV3 
	Link policy: RSWITCH HOLD SNIFF PARK 
	Link mode: SLAVE ACCEPT 
	Name: 'tv-box-coreelec-9.2'
	Class: 0x0c0000
	Service Classes: Rendering, Capturing
	Device Class: Miscellaneous, 
	HCI Version:  (0xa)  Revision: 0x599
	LMP Version:  (0xa)  Subversion: 0xd54
	Manufacturer: Realtek Semiconductor Corporation (93)

Zde je ve výpisu vlastností zařízení HCI verze 0xa a LMP verze 0xa, což znamená dle specifikace v tabulce níže Bluetooth 5.1.

Host Controller
Interface (HCI) Version
Link Manager Protocol
(LMP) Version
Bluetooth Core
Specification
0 0 1.0b
1 1 1.1
2 2 1.2
3 3 2.0 + EDR
4 4 2.1 + EDR
5 5 3.0 + HS
6 6 4.0
7 7 4.1
8 8 4.2
9 9 5.0
10 (0xA) 10 (0xA) 5.1
11 (0xB) 11 (0xB) 5.2
12 (0xC) 12 (0xC) 5.3

Zjištění MAC adresy bezdrátového teploměru a jakou verzi Bluetooth zařízení podporuje:

$ hcitool lescan
LE Scan ...
A4:C1:38:98:76:54 (unknown)
A4:C1:38:98:76:54 LYWSD03MMC

$ hcitool leinfo A4:C1:38:98:76:54
Requesting information ...
	Handle: 16 (0x0010)
	LMP Version: 5.0 (0x9) LMP Subversion: 0x1c1c
	Manufacturer: Telink Semiconductor Co. Ltd (529)
	Features: 0x3d 0x00 0x00 0x00 0x00 0x00 0x00 0x00

Instalace Entware

Entware je repozitář balíčků z kterého lze nainstalovat mnoho programů na jakékoliv podporované zařízení s operačním systémem založeným na Linuxu – třeba na NAS nebo WiFI router s DD-WRT. Na zařízení s CoreELEC se základní instalace Entware spustí z příkazového řádku vestavěným skriptem installentware a po restartu máme k dispozici program opkg, kterým můžeme instalovat další balíčky s programy.

$ installentware 
Entware is already installed.

Příprava jazyka Python

Začneme přípravou virtuálního prostředí jazyka Python, abychom měli všechny moduly soustředěné na jednom místě a změnami neovlivnili zbytek systému.

$ python3 -m venv ~/.venv/mitemp

$ source ~/.venv/mitemp/bin/activate

(mitemp) $ python3 -m pip install --upgrade pip
Collecting pip
  Using cached https://files.pythonhosted.org/packages/8a/d7/f505e91e2cdea53cfcf51f4ac478a8cd64fb0bc1042629cedde20d9a6a9b/pip-21.2.2-py3-none-any.whl
Installing collected packages: pip
  Found existing installation: pip 19.0.3
    Uninstalling pip-19.0.3:
      Successfully uninstalled pip-19.0.3
Successfully installed pip-21.2.2

Alternativně je možno nainstalovat novější verzi Python 3.9 z Entware. S výchozím Pythonem 3.7 v CoreELEC jsem měl potíže při výpočtech s desetinnými čísly, kdy výsledkem dělení 2347/100.0 nebylo hezkých 23.47, ale výsledek byl ošklivých 23.469999999999999, což se nepříjemně projevuje v zobrazení naměřených teplot.

$ opkg install python3

$ python3 -m venv --without-pip mitemp

$ source ~/.venv/mitemp/bin/activate

(mitemp) $ curl https://bootstrap.pypa.io/get-pip.py | python
Collecting pip
  Using cached pip-21.2.2-py3-none-any.whl (1.6 MB)
Collecting setuptools
  Downloading setuptools-57.4.0-py3-none-any.whl (819 kB)
     |████████████████████████████████| 819 kB 5.8 MB/s 
Collecting wheel
  Downloading wheel-0.36.2-py2.py3-none-any.whl (35 kB)
Installing collected packages: wheel, setuptools, pip
Successfully installed pip-21.2.2 setuptools-57.4.0 wheel-0.36.2

(mitemp) $ deactivate

$ source ~/.venv/mitemp/bin/activate

Tímto máme připravené virtuální prostředí Pythonu pro další pokračování instalace.

Instalace programu lywsd03mmc

(Účelem tohoto článku není popsat jak to funguje, ale naopak zdokumentovat, co přesně nefunguje a proč, takže se předem omlouvám všem netrpělivým, že na to jdu od lesa.)

K načtení hodnot z teploměru jsem zvolil skript lywsd03mmc, který splňuje všechny požadavky. Má možnost jednorázového načtení teploty a vlhkosti nebo přečtení a uložení celé historie teplot od zapnutí teploměru do souboru CSV.

Začneme dle dokumentace s instalací vyžadované knihovny glib2 pomocí programu opkg a program pip3 slouží obecně k instalaci skriptů a modulů Pythonu.

(mitemp) $ opkg install glib2
Installing glib2 (2.68.1-3) to root...
Downloading http://bin.entware.net/aarch64-k3.10/glib2_2.68.1-3_aarch64-3.10.ipk
Installing libiconv-full (1.16-1) to root...
Downloading http://bin.entware.net/aarch64-k3.10/libiconv-full_1.16-1_aarch64-3.10.ipk
Installing libintl-full (0.21-2) to root...
Downloading http://bin.entware.net/aarch64-k3.10/libintl-full_0.21-2_aarch64-3.10.ipk
Installing zlib (1.2.11-3) to root...
Downloading http://bin.entware.net/aarch64-k3.10/zlib_1.2.11-3_aarch64-3.10.ipk
Installing libffi (3.3-2) to root...
Downloading http://bin.entware.net/aarch64-k3.10/libffi_3.3-2_aarch64-3.10.ipk
Installing libattr (2.5.1-3) to root...
Downloading http://bin.entware.net/aarch64-k3.10/libattr_2.5.1-3_aarch64-3.10.ipk
Configuring libiconv-full.
Configuring libintl-full.
Configuring zlib.
Configuring libffi.
Configuring libattr.
Configuring glib2.

(mitemp) $ pip3 install lywsd03mmc
Collecting lywsd03mmc
  Using cached lywsd03mmc-0.1.0-py3-none-any.whl (6.4 kB)
Collecting lywsd02==0.0.9
  Using cached lywsd02-0.0.9-py3-none-any.whl (4.3 kB)
Collecting bluepy==1.3.0
  Using cached bluepy-1.3.0.tar.gz (217 kB)
Using legacy 'setup.py install' for bluepy, since package 'wheel' is not installed.
Installing collected packages: bluepy, lywsd02, lywsd03mmc
    Running setup.py install for bluepy ... error
    ERROR: Command errored out with exit status 1:
     command: /storage/.venv/mitemp/bin/python3 -u -c 'import io, os, sys, setuptools, tokenize; sys.argv[0] = '"'"'/storage/.opt/tmp/pip-install-09_n_amk/bluepy_f3934a7a656342c1a87ef752f6bc3e9b/setup.py'"'"'; __file__='"'"'/storage/.opt/tmp/pip-install-09_n_amk/bluepy_f3934a7a656342c1a87ef752f6bc3e9b/setup.py'"'"';f = getattr(tokenize, '"'"'open'"'"', open)(__file__) if os.path.exists(__file__) else io.StringIO('"'"'from setuptools import setup; setup()'"'"');code = f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' install --record /storage/.opt/tmp/pip-record-wbxlv4uq/install-record.txt --single-version-externally-managed --compile --install-headers /storage/.venv/mitemp/include/site/python3.7/bluepy
         cwd: /storage/.opt/tmp/pip-install-09_n_amk/bluepy_f3934a7a656342c1a87ef752f6bc3e9b/
    Complete output (6 lines):
    running install
    running build
    running build_py
    Working dir is /storage/.opt/tmp/pip-install-09_n_amk/bluepy_f3934a7a656342c1a87ef752f6bc3e9b
    execute make -C ./bluepy clean
    error: [Errno 2] No such file or directory: 'make': 'make'
    ----------------------------------------
ERROR: Command errored out with exit status 1: /storage/.venv/mitemp/bin/python3 -u -c 'import io, os, sys, setuptools, tokenize; sys.argv[0] = '"'"'/storage/.opt/tmp/pip-install-09_n_amk/bluepy_f3934a7a656342c1a87ef752f6bc3e9b/setup.py'"'"'; __file__='"'"'/storage/.opt/tmp/pip-install-09_n_amk/bluepy_f3934a7a656342c1a87ef752f6bc3e9b/setup.py'"'"';f = getattr(tokenize, '"'"'open'"'"', open)(__file__) if os.path.exists(__file__) else io.StringIO('"'"'from setuptools import setup; setup()'"'"');code = f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' install --record /storage/.opt/tmp/pip-record-wbxlv4uq/install-record.txt --single-version-externally-managed --compile --install-headers /storage/.venv/mitemp/include/site/python3.7/bluepy Check the logs for full command output.

Instalace závislosti – pythonovského modulu bluepy – zhavarovala, protože není nainstalovaný program make, a proto ho nainstalujeme společně s překladačem a dalšími programy, které budou také potřeba pro překlad modulu z jazyka C.

(mitemp) $ opkg install make gcc
Installing make (4.3-1) to root...
Downloading http://bin.entware.net/aarch64-k3.10/make_4.3-1_aarch64-3.10.ipk
Installing gcc (7.4.0-6a) to root...
Downloading http://bin.entware.net/aarch64-k3.10/gcc_7.4.0-6a_aarch64-3.10.ipk
Installing libbfd (2.35.2-1) to root...
Downloading http://bin.entware.net/aarch64-k3.10/libbfd_2.35.2-1_aarch64-3.10.ipk
Installing libopcodes (2.35.2-1) to root...
Downloading http://bin.entware.net/aarch64-k3.10/libopcodes_2.35.2-1_aarch64-3.10.ipk
Installing libctf (2.35.2-1) to root...
Downloading http://bin.entware.net/aarch64-k3.10/libctf_2.35.2-1_aarch64-3.10.ipk
Installing objdump (2.35.2-1) to root...
Downloading http://bin.entware.net/aarch64-k3.10/objdump_2.35.2-1_aarch64-3.10.ipk
Installing ar (2.35.2-1) to root...
Downloading http://bin.entware.net/aarch64-k3.10/ar_2.35.2-1_aarch64-3.10.ipk
Installing binutils (2.35.2-1) to root...
Downloading http://bin.entware.net/aarch64-k3.10/binutils_2.35.2-1_aarch64-3.10.ipk
Configuring libbfd.
Configuring libopcodes.
Configuring libctf.
Configuring objdump.
Configuring ar.
Configuring binutils.
Configuring gcc.
There are no *-dev packages in Entware(with few exceptions)!
Please install headers as described in the wiki:
https://github.com/Entware/Entware/wiki
Configuring make.

Na konci jsme vyzváni k instalaci hlavičkových souborů podle popisu v dokumentaci. Ukázkový příkaz z popisu pro stažení a rozbalení hlavičkových souborů do adresáře /opt/include je pro potřeby CoreELEC zapotřebí upravit na magický příkaz:

(mitemp) $ wget -qO- \
  "$(sed -Ene 's|^src/gz[[:space:]]entware[[:space:]]([[:graph:]]+)|\1/include/include.tar.gz|p' /opt/etc/opkg.conf)" \
  | tar xvzC /opt/include
(mitemp) $ pip3 install lywsd03mmc
...
bluepy-helper.c:33:10: fatal error: glib.h: No such file or directory
 #include <glib.h>
          ^~~~~~~~
compilation terminated.
./bluez-5.47/attrib/att.c:33:10: fatal error: glib.h: No such file or directory
 #include <glib.h>
          ^~~~~~~~
compilation terminated.
./bluez-5.47/attrib/gatt.c:32:10: fatal error: glib.h: No such file or directory
 #include <glib.h>
          ^~~~~~~~
compilation terminated.
./bluez-5.47/attrib/gattrib.c:34:10: fatal error: glib.h: No such file or directory
 #include <glib.h>
          ^~~~~~~~
compilation terminated.
./bluez-5.47/attrib/utils.c:30:10: fatal error: glib.h: No such file or directory
 #include <glib.h>
          ^~~~~~~~
compilation terminated.
./bluez-5.47/btio/btio.c:37:10: fatal error: glib.h: No such file or directory
 #include <glib.h>
          ^~~~~~~~
compilation terminated.
./bluez-5.47/src/log.c:38:10: fatal error: glib.h: No such file or directory
 #include <glib.h>
          ^~~~~~~~
compilation terminated.
./bluez-5.47/src/shared/io-glib.c:30:10: fatal error: glib.h: No such file or directory
 #include <glib.h>
          ^~~~~~~~
compilation terminated.
./bluez-5.47/src/shared/timeout-glib.c:22:10: fatal error: glib.h: No such file or directory
 #include <glib.h>
          ^~~~~~~~
compilation terminated.
...

Další pokus o instalaci modulu bluepy opět selhal, protože tentokrát nebyl nalezen hlavičkový soubor glib.h. Ten totiž není přímo v adresáři /opt/include, ale je v podadresáři:

(mitemp) $ find /opt/include -name 'glib.h'
/opt/include/glib-2.0/glib.h

Nyní se tedy vrátíme ke konfiguraci prostředí překladače a vytvoříme soubor gcc_env.sh s proměnnými definujícími, kde se má hledat hlavičkový soubor a knihovna glib-2.0. Následně vytvořený soubor použijeme před instalací nového skriptu. (Další možností by byla instalace balíku pkg-config z Entware, ale ten by vyžadoval ještě další konfiguraci, a proto jsem se touto cestou nevydal.)

(mitemp) $ cat > gcc_env.sh <<'END'
PREFIX=/opt
LIBDIR="${PREFIX}/lib"
INCLUDEDIR="${PREFIX}/include"

export LDFLAGS="-Wl,-rpath=${LIBDIR} -Wl,--dynamic-linker=${LIBDIR}/ld-linux.so.3 -L${LIBDIR} "
export CFLAGS="-O2 -pipe -march=native -fno-caller-saves "
export CPPFLAGS="${CFLAGS} -I${INCLUDEDIR} -I${INCLUDEDIR}/glib-2.0 -lglib-2.0 "
export CXXFLAGS="${CFLAGS} ${LDFLAGS} "
END

(mitemp) $ source ./gcc_env.sh && pip3 install lywsd03mmc
Collecting lywsd03mmc
  Using cached lywsd03mmc-0.1.0-py3-none-any.whl (6.4 kB)
Collecting lywsd02==0.0.9
  Using cached lywsd02-0.0.9-py3-none-any.whl (4.3 kB)
Collecting bluepy==1.3.0
  Using cached bluepy-1.3.0.tar.gz (217 kB)
Using legacy 'setup.py install' for bluepy, since package 'wheel' is not installed.
Installing collected packages: bluepy, lywsd02, lywsd03mmc
    Running setup.py install for bluepy ... done
Successfully installed bluepy-1.3.0 lywsd02-0.0.9 lywsd03mmc-0.1.0

Nyní je skript lywsd03mmc se všemi potřebnými moduly úspěšně nainstalovaný a můžeme ho vyzkoušet k načtení hodnot z teploměru s MAC adresou, kterou jsme zjistili na začátku tohoto článku.

(mitemp) $ lywsd03mmc A4:C1:38:98:76:54
Fetching data from A4:C1:38:98:76:54
Temperature: 24.36°C
Humidity: 61%
Battery: 89%

Nebo můžeme uložit historii teplot od zapnutí teploměru do CSV souboru k dalšímu zpracování.

(mitemp) $ lywsd03mmc2csv A4:C1:38:98:76:54 --output data.csv
Fetching data from A4:C1:38:98:76:54
Temperature: 24.36°C
Humidity: 61%
Battery: 89%
Device start time: 2021-07-22 18:39:52.948031

Fetching history from A4:C1:38:98:76:54
2021-07-22 19:39:52.948031: 24.1 to 26.8
2021-07-22 20:39:52.948031: 24.1 to 24.2
2021-07-22 21:39:52.948031: 23.7 to 24.1
2021-07-22 22:39:52.948031: 23.4 to 23.7
2021-07-22 23:39:52.948031: 23.3 to 23.8
...
Done