aliexpressで安く買える温湿度計はカスタムファームを入れることでBLEビーコンになる
- カスタムファームだと.01単位で温度湿度測定できて良い
- 電池が1年くらい持つとのこと
- OTA対応製品がまだ出回っている(2024.07現在)
OTA対応のファーム(2024.07購入:2023.08生産でOTA対応)であればスマホ上でファームウウェア入れ替えと設定が可能。
ファームウェア入れ替えと設定手順
上記に書いてある通り。flasherでMijiaを対象選択(LYWSD03...)してファームがOTA対象であることを確認したら、その時点での安定板(2024.08時点では4.7)を選んでフラッシュする。なお、他のホストとbluetooth接続していると(液晶にロゴが出ます)選択対象として出てこない。
フラッシュ後にデフォルト通りであればadvertising formatはcustom formatになっているが、念の為flasherでもう一度接続(ATC...)して設定する。
format
uint8_t size; // = 18
uint8_t uid; // = 0x16, 16-bit UUID
uint16_t UUID; // = 0x181A, GATT Service 0x181A Environmental Sensing
uint8_t MAC[6]; // [0] - lo, .. [6] - hi digits
int16_t temperature; // x 0.01 degree
uint16_t humidity; // x 0.01 %
uint16_t battery_mv; // mV
uint8_t battery_level; // 0..100 %
uint8_t counter; // measurement count
uint8_t flags; // GPIO_TRG pin (marking "reset" on circuit board) flags:
// bit0: Reed Switch, input
// bit1: GPIO_TRG pin output value (pull Up/Down)
// bit2: Output GPIO_TRG pin is controlled according to the set parameters
// bit3: Temperature trigger event
// bit4: Humidity trigger event
node-redでBLEを受信する
何を使っても良いかと思われるが、ファームウェアのフォーラム
にあったnode-red-contrib-ble-scanを利用。
nobleは自動では入らなかったのでコマンドプロンプトから。
sudo apt-get install bluetooth bluez libbluetooth-dev libudev-dev
sudo setcap cap_net_raw+eip $(eval readlink -f `which node`)
npm install noble
BLE受信とparse
受信ができるとあちこちのビーコンを拾うので、フォーラムにあったfunctionノードで対象のものだけをフィルター。
ファームウェアのフォーマットに従ってnode-red-contrib-buffer-parser(ラズパイのnode-redでは標準)でパースする。
[
{
"id": "2638cecad749fa1e",
"type": "tab",
"label": "sensors",
"disabled": false,
"info": "",
"env": []
},
{
"id": "282e1cbf3dc18174",
"type": "debug",
"z": "2638cecad749fa1e",
"name": "debug 1",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 560,
"y": 320,
"wires": []
},
{
"id": "c08cddf3cd1437ba",
"type": "BLE Scan",
"z": "2638cecad749fa1e",
"name": "",
"x": 80,
"y": 40,
"wires": [
[
"a6375ab2fe258940"
]
]
},
{
"id": "a6375ab2fe258940",
"type": "function",
"z": "2638cecad749fa1e",
"name": "filter xiaomi",
"func": "var payload = msg.payload;\nif (payload.address.startsWith(\"a4:c1:38\") &&\n payload.advertisement.localName.startsWith(\"ATC_\")) {\n var buffer = payload.advertisement.serviceData[0].data;\n var mac = payload.address\n msg.topic = mac;\n\n msg.payload = {\n \"buffer\": buffer\n };\n return msg;\n}\nreturn null;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 250,
"y": 40,
"wires": [
[
"1d7bbc567f124044"
]
]
},
{
"id": "1d7bbc567f124044",
"type": "buffer-parser",
"z": "2638cecad749fa1e",
"name": "",
"data": "payload.buffer",
"dataType": "msg",
"specification": "spec",
"specificationType": "ui",
"items": [
{
"type": "int16le",
"name": "temp",
"offset": 6,
"length": 1,
"offsetbit": 0,
"scale": "0.01",
"mask": ""
},
{
"type": "uint16le",
"name": "humidity",
"offset": 8,
"length": 1,
"offsetbit": 0,
"scale": ".01",
"mask": ""
},
{
"type": "uint16le",
"name": "mV",
"offset": 10,
"length": 1,
"offsetbit": 0,
"scale": "1",
"mask": ""
},
{
"type": "uint8",
"name": "battery",
"offset": 12,
"length": 1,
"offsetbit": 0,
"scale": "1",
"mask": ""
}
],
"swap1": "",
"swap2": "",
"swap3": "",
"swap1Type": "swap",
"swap2Type": "swap",
"swap3Type": "swap",
"msgProperty": "payload",
"msgPropertyType": "str",
"resultType": "value",
"resultTypeType": "return",
"multipleResult": true,
"fanOutMultipleResult": true,
"setTopic": true,
"outputs": 4,
"x": 430,
"y": 40,
"wires": [
[
"282e1cbf3dc18174",
"339d05cfc0501b4b"
],
[
"282e1cbf3dc18174",
"acd3a6735d4046ab"
],
[
"282e1cbf3dc18174"
],
[
"282e1cbf3dc18174"
]
]
},
{
"id": "6a6ad82253b970e2",
"type": "ui_chart",
"z": "2638cecad749fa1e",
"name": "temp chart",
"group": "89dfaf28c135d6ea",
"order": 1,
"width": 0,
"height": 0,
"label": "",
"chartType": "line",
"legend": "false",
"xformat": "HH:mm",
"interpolate": "linear",
"nodata": "",
"dot": false,
"ymin": "",
"ymax": "",
"removeOlder": 1,
"removeOlderPoints": "",
"removeOlderUnit": "86400",
"cutout": 0,
"useOneColor": false,
"useUTC": false,
"colors": [
"#1f77b4",
"#aec7e8",
"#ff7f0e",
"#2ca02c",
"#98df8a",
"#d62728",
"#ff9896",
"#9467bd",
"#c5b0d5"
],
"outputs": 1,
"useDifferentColor": false,
"className": "",
"x": 110,
"y": 140,
"wires": [
[]
]
},
{
"id": "ed92b03f0ac04d53",
"type": "ui_text",
"z": "2638cecad749fa1e",
"group": "89dfaf28c135d6ea",
"order": 2,
"width": "2",
"height": "1",
"name": "temp",
"label": "<font> <i class=\"fa fa-thermometer-empty \"></i> </font>",
"format": "{{msg.payload}}°C",
"layout": "row-center",
"className": "",
"style": false,
"font": "",
"fontSize": 16,
"color": "#000000",
"x": 90,
"y": 180,
"wires": []
},
{
"id": "7e964d382647eec1",
"type": "ui_text",
"z": "2638cecad749fa1e",
"group": "89dfaf28c135d6ea",
"order": 2,
"width": "1",
"height": "1",
"name": "humidity",
"label": "",
"format": "{{msg.payload}}%",
"layout": "row-center",
"className": "",
"style": false,
"font": "",
"fontSize": 16,
"color": "#000000",
"x": 100,
"y": 240,
"wires": []
},
{
"id": "339d05cfc0501b4b",
"type": "function",
"z": "2638cecad749fa1e",
"name": "two decimal",
"func": "msg.payload = (msg.payload.toFixed(2));\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 610,
"y": 20,
"wires": [
[
"6a6ad82253b970e2",
"ed92b03f0ac04d53"
]
]
},
{
"id": "acd3a6735d4046ab",
"type": "function",
"z": "2638cecad749fa1e",
"name": "two decimal",
"func": "msg.payload = (msg.payload.toFixed(2));\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 610,
"y": 60,
"wires": [
[
"7e964d382647eec1"
]
]
},
{
"id": "89dfaf28c135d6ea",
"type": "ui_group",
"name": "graph",
"tab": "859daaa640503ab3",
"order": 6,
"disp": false,
"width": 4,
"collapse": false,
"className": ""
},
{
"id": "859daaa640503ab3",
"type": "ui_tab",
"name": "home",
"icon": "dashboard",
"order": 1,
"disabled": false,
"hidden": false
}
]