node-red-contrib-periph-environmental 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/examples/aht21/demo.json +121 -0
- package/examples/bme280/demo.json +161 -0
- package/examples/bme680/demo.json +175 -0
- package/nodes/aht21/aht21.html +75 -0
- package/nodes/aht21/aht21.js +49 -0
- package/nodes/bme280/bme280.html +170 -0
- package/nodes/bme280/bme280.js +71 -0
- package/nodes/bme680/bme680.html +160 -0
- package/nodes/bme680/bme680.js +70 -0
- package/package.json +7 -2
- package/examples/.gitkeep +0 -0
- package/nodes/.gitkeep +0 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "tab-aht21-demo",
|
|
4
|
+
"type": "tab",
|
|
5
|
+
"label": "AHT21 Weather Station",
|
|
6
|
+
"disabled": false,
|
|
7
|
+
"info": "Reads temperature and humidity from an AHT21 every 5 seconds and displays a formatted string in the debug panel. A second branch feeds two Gauge dashboard widgets for live visual monitoring of temperature and humidity."
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"id": "cfg-aht21",
|
|
11
|
+
"type": "aht21-device",
|
|
12
|
+
"name": "AHT21",
|
|
13
|
+
"bus": "1",
|
|
14
|
+
"address": "38"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"id": "inj-tick",
|
|
18
|
+
"type": "inject",
|
|
19
|
+
"name": "every 5 s",
|
|
20
|
+
"repeat": "5",
|
|
21
|
+
"crontab": "",
|
|
22
|
+
"once": true,
|
|
23
|
+
"onceDelay": 0.1,
|
|
24
|
+
"topic": "",
|
|
25
|
+
"payload": "",
|
|
26
|
+
"payloadType": "date",
|
|
27
|
+
"z": "tab-aht21-demo",
|
|
28
|
+
"x": 120,
|
|
29
|
+
"y": 160,
|
|
30
|
+
"wires": [["read-aht21"]]
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"id": "read-aht21",
|
|
34
|
+
"type": "aht21",
|
|
35
|
+
"name": "AHT21",
|
|
36
|
+
"device": "cfg-aht21",
|
|
37
|
+
"z": "tab-aht21-demo",
|
|
38
|
+
"x": 300,
|
|
39
|
+
"y": 160,
|
|
40
|
+
"wires": [["fmt-readings", "gauge-temp", "gauge-hum"]]
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"id": "fmt-readings",
|
|
44
|
+
"type": "function",
|
|
45
|
+
"name": "format",
|
|
46
|
+
"func": "const p = msg.payload;\nmsg.payload = p.temperature_c.toFixed(1) + ' °C / ' + p.humidity_pct.toFixed(1) + ' %RH';\nreturn msg;",
|
|
47
|
+
"outputs": 1,
|
|
48
|
+
"noerr": 0,
|
|
49
|
+
"initialize": "",
|
|
50
|
+
"finalize": "",
|
|
51
|
+
"libs": [],
|
|
52
|
+
"z": "tab-aht21-demo",
|
|
53
|
+
"x": 480,
|
|
54
|
+
"y": 160,
|
|
55
|
+
"wires": [["dbg-readings"]]
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"id": "dbg-readings",
|
|
59
|
+
"type": "debug",
|
|
60
|
+
"name": "",
|
|
61
|
+
"active": true,
|
|
62
|
+
"tosidebar": true,
|
|
63
|
+
"console": false,
|
|
64
|
+
"tostatus": true,
|
|
65
|
+
"complete": "payload",
|
|
66
|
+
"targetType": "msg",
|
|
67
|
+
"statusVal": "payload",
|
|
68
|
+
"statusType": "auto",
|
|
69
|
+
"z": "tab-aht21-demo",
|
|
70
|
+
"x": 650,
|
|
71
|
+
"y": 160,
|
|
72
|
+
"wires": []
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"id": "gauge-temp",
|
|
76
|
+
"type": "ui_gauge",
|
|
77
|
+
"name": "Temperature",
|
|
78
|
+
"group": "grp-aht21",
|
|
79
|
+
"order": 1,
|
|
80
|
+
"width": 6,
|
|
81
|
+
"height": 4,
|
|
82
|
+
"gtype": "gage",
|
|
83
|
+
"title": "Temperature",
|
|
84
|
+
"label": "°C",
|
|
85
|
+
"format": "{{value | number:1}}",
|
|
86
|
+
"min": -10,
|
|
87
|
+
"max": 50,
|
|
88
|
+
"z": "tab-aht21-demo",
|
|
89
|
+
"x": 490,
|
|
90
|
+
"y": 220,
|
|
91
|
+
"wires": []
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"id": "gauge-hum",
|
|
95
|
+
"type": "ui_gauge",
|
|
96
|
+
"name": "Humidity",
|
|
97
|
+
"group": "grp-aht21",
|
|
98
|
+
"order": 2,
|
|
99
|
+
"width": 6,
|
|
100
|
+
"height": 4,
|
|
101
|
+
"gtype": "gage",
|
|
102
|
+
"title": "Humidity",
|
|
103
|
+
"label": "%RH",
|
|
104
|
+
"format": "{{value | number:1}}",
|
|
105
|
+
"min": 0,
|
|
106
|
+
"max": 100,
|
|
107
|
+
"z": "tab-aht21-demo",
|
|
108
|
+
"x": 480,
|
|
109
|
+
"y": 280,
|
|
110
|
+
"wires": []
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"id": "grp-aht21",
|
|
114
|
+
"type": "ui_group",
|
|
115
|
+
"name": "AHT21",
|
|
116
|
+
"tab": "tab-aht21-demo",
|
|
117
|
+
"order": 1,
|
|
118
|
+
"disp": true,
|
|
119
|
+
"width": 12
|
|
120
|
+
}
|
|
121
|
+
]
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "bme280-device-1",
|
|
4
|
+
"type": "bme280-device",
|
|
5
|
+
"name": "BME280 @ i2c-1",
|
|
6
|
+
"bus": "1",
|
|
7
|
+
"addr": "0x76",
|
|
8
|
+
"osrsT": "1",
|
|
9
|
+
"osrsP": "1",
|
|
10
|
+
"osrsH": "1",
|
|
11
|
+
"mode": "0",
|
|
12
|
+
"filter": "0",
|
|
13
|
+
"tSb": "0"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": "bme280-read-1",
|
|
17
|
+
"type": "periph-bme280",
|
|
18
|
+
"name": "BME280 Indoor",
|
|
19
|
+
"device": "bme280-device-1",
|
|
20
|
+
"seaLevelHpa": "1013.25",
|
|
21
|
+
"wires": [["debug-1", "chart-temp-1", "chart-pres-1", "chart-hum-1", "chart-alt-1", "chart-dew-1", "cond-alert-1"]]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"id": "inject-timer-1",
|
|
25
|
+
"type": "inject",
|
|
26
|
+
"name": "Every 10 seconds",
|
|
27
|
+
"topic": "",
|
|
28
|
+
"payload": "",
|
|
29
|
+
"payloadType": "date",
|
|
30
|
+
"repeat": "10",
|
|
31
|
+
"crontab": "",
|
|
32
|
+
"onceDelay": "0.1",
|
|
33
|
+
"wires": [["bme280-read-1"]]
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"id": "debug-1",
|
|
37
|
+
"type": "debug",
|
|
38
|
+
"name": "Debug output",
|
|
39
|
+
"active": true,
|
|
40
|
+
"tosideuit": false,
|
|
41
|
+
"console": "false",
|
|
42
|
+
"tostatus": false,
|
|
43
|
+
"complete": "payload",
|
|
44
|
+
"targetType": "msg",
|
|
45
|
+
"x": 480,
|
|
46
|
+
"y": 200,
|
|
47
|
+
"wires": []
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"id": "chart-temp-1",
|
|
51
|
+
"type": "ui_chart",
|
|
52
|
+
"name": "Temperature",
|
|
53
|
+
"group": "bme280-group-1",
|
|
54
|
+
"order": 1,
|
|
55
|
+
"width": 6,
|
|
56
|
+
"height": 4,
|
|
57
|
+
"label": "Temperature (°C)",
|
|
58
|
+
"chartType": "line",
|
|
59
|
+
"x": 480,
|
|
60
|
+
"y": 260,
|
|
61
|
+
"wires": []
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"id": "chart-pres-1",
|
|
65
|
+
"type": "ui_chart",
|
|
66
|
+
"name": "Pressure",
|
|
67
|
+
"group": "bme280-group-1",
|
|
68
|
+
"order": 2,
|
|
69
|
+
"width": 6,
|
|
70
|
+
"height": 4,
|
|
71
|
+
"label": "Pressure (hPa)",
|
|
72
|
+
"chartType": "line",
|
|
73
|
+
"x": 480,
|
|
74
|
+
"y": 320,
|
|
75
|
+
"wires": []
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"id": "chart-hum-1",
|
|
79
|
+
"type": "ui_chart",
|
|
80
|
+
"name": "Humidity",
|
|
81
|
+
"group": "bme280-group-1",
|
|
82
|
+
"order": 3,
|
|
83
|
+
"width": 6,
|
|
84
|
+
"height": 4,
|
|
85
|
+
"label": "Humidity (%RH)",
|
|
86
|
+
"chartType": "line",
|
|
87
|
+
"x": 480,
|
|
88
|
+
"y": 380,
|
|
89
|
+
"wires": []
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"id": "chart-alt-1",
|
|
93
|
+
"type": "ui_chart",
|
|
94
|
+
"name": "Altitude",
|
|
95
|
+
"group": "bme280-group-1",
|
|
96
|
+
"order": 4,
|
|
97
|
+
"width": 6,
|
|
98
|
+
"height": 4,
|
|
99
|
+
"label": "Altitude (m)",
|
|
100
|
+
"chartType": "line",
|
|
101
|
+
"x": 480,
|
|
102
|
+
"y": 440,
|
|
103
|
+
"wires": []
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"id": "chart-dew-1",
|
|
107
|
+
"type": "ui_chart",
|
|
108
|
+
"name": "Dew Point",
|
|
109
|
+
"group": "bme280-group-1",
|
|
110
|
+
"order": 5,
|
|
111
|
+
"width": 6,
|
|
112
|
+
"height": 4,
|
|
113
|
+
"label": "Dew Point (°C)",
|
|
114
|
+
"chartType": "line",
|
|
115
|
+
"x": 480,
|
|
116
|
+
"y": 500,
|
|
117
|
+
"wires": []
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"id": "cond-alert-1",
|
|
121
|
+
"type": "function",
|
|
122
|
+
"name": "Condensation Alert",
|
|
123
|
+
"func": "if (msg.payload.dew_point !== null && msg.payload.temperature - msg.payload.dew_point < 2) {\n msg.payload = 'Condensation likely: T-dew=' + (msg.payload.temperature - msg.payload.dew_point).toFixed(1) + ' C';\n return msg;\n}\nreturn null;",
|
|
124
|
+
"outputs": 1,
|
|
125
|
+
"noerr": 0,
|
|
126
|
+
"x": 480,
|
|
127
|
+
"y": 560,
|
|
128
|
+
"wires": [["debug-cond-1"]]
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"id": "debug-cond-1",
|
|
132
|
+
"type": "debug",
|
|
133
|
+
"name": "Condensation Alert",
|
|
134
|
+
"active": true,
|
|
135
|
+
"tosideuit": false,
|
|
136
|
+
"console": "false",
|
|
137
|
+
"tostatus": false,
|
|
138
|
+
"complete": "payload",
|
|
139
|
+
"targetType": "msg",
|
|
140
|
+
"x": 660,
|
|
141
|
+
"y": 560,
|
|
142
|
+
"wires": []
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"id": "bme280-group-1",
|
|
146
|
+
"type": "ui_group",
|
|
147
|
+
"name": "BME280 Indoor Environment",
|
|
148
|
+
"tab": "bme280-tab-1",
|
|
149
|
+
"order": 1,
|
|
150
|
+
"disp": true,
|
|
151
|
+
"width": 12
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"id": "bme280-tab-1",
|
|
155
|
+
"type": "ui_tab",
|
|
156
|
+
"name": "BME280 Demo",
|
|
157
|
+
"icon": "dashboard",
|
|
158
|
+
"order": 1,
|
|
159
|
+
"info": "This flow demonstrates the BME280 combined humidity + pressure + temperature sensor.\n\nAn inject node fires every 10 seconds into the periph-bme280 node. The output goes to a debug node and five ui_chart nodes showing temperature, pressure, humidity, altitude, and dew point over time.\n\nA condensation-alert function node watches the dew-point trace and emits a 'Condensation likely' alert when temperature minus dew point falls below 2 °C — useful for HVAC, greenhouse, and crawl-space monitoring where surface condensation can damage equipment or promote mould.\n\nThe BME280 combines three sensors (BMP280 pressure/temperature plus a humidity front-end) in one package, making it ideal for indoor air quality, weather stations, and HVAC applications where a separate humidity sensor would otherwise be needed."
|
|
160
|
+
}
|
|
161
|
+
]
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "bme680-device-1",
|
|
4
|
+
"type": "bme680-device",
|
|
5
|
+
"name": "BME680 @ i2c-1",
|
|
6
|
+
"bus": "1",
|
|
7
|
+
"addr": "0x76",
|
|
8
|
+
"osrsT": "1",
|
|
9
|
+
"osrsP": "1",
|
|
10
|
+
"osrsH": "1",
|
|
11
|
+
"filter": "0",
|
|
12
|
+
"heaterTemp": "320",
|
|
13
|
+
"heaterDur": "150"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": "bme680-read-1",
|
|
17
|
+
"type": "periph-bme680",
|
|
18
|
+
"name": "BME680 Air Quality",
|
|
19
|
+
"device": "bme680-device-1",
|
|
20
|
+
"seaLevelHpa": "1013.25",
|
|
21
|
+
"wires": [["debug-1", "chart-temp-1", "chart-pres-1", "chart-hum-1", "chart-gas-1", "chart-alt-1", "chart-dew-1"]]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"id": "inject-timer-1",
|
|
25
|
+
"type": "inject",
|
|
26
|
+
"name": "Every 10 seconds",
|
|
27
|
+
"topic": "",
|
|
28
|
+
"payload": "",
|
|
29
|
+
"payloadType": "date",
|
|
30
|
+
"repeat": "10",
|
|
31
|
+
"crontab": "",
|
|
32
|
+
"onceDelay": "0.1",
|
|
33
|
+
"wires": [["bme680-read-1"]]
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"id": "debug-1",
|
|
37
|
+
"type": "debug",
|
|
38
|
+
"name": "Debug output",
|
|
39
|
+
"active": true,
|
|
40
|
+
"tosideuit": false,
|
|
41
|
+
"console": "false",
|
|
42
|
+
"tostatus": "false",
|
|
43
|
+
"complete": "payload",
|
|
44
|
+
"targetType": "msg",
|
|
45
|
+
"x": 480,
|
|
46
|
+
"y": 200,
|
|
47
|
+
"wires": []
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"id": "chart-temp-1",
|
|
51
|
+
"type": "ui_chart",
|
|
52
|
+
"name": "Temperature",
|
|
53
|
+
"group": "bme680-group-1",
|
|
54
|
+
"order": 1,
|
|
55
|
+
"width": 6,
|
|
56
|
+
"height": 4,
|
|
57
|
+
"label": "Temperature (°C)",
|
|
58
|
+
"chartType": "line",
|
|
59
|
+
"x": 480,
|
|
60
|
+
"y": 260,
|
|
61
|
+
"wires": []
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"id": "chart-pres-1",
|
|
65
|
+
"type": "ui_chart",
|
|
66
|
+
"name": "Pressure",
|
|
67
|
+
"group": "bme680-group-1",
|
|
68
|
+
"order": 2,
|
|
69
|
+
"width": 6,
|
|
70
|
+
"height": 4,
|
|
71
|
+
"label": "Pressure (hPa)",
|
|
72
|
+
"chartType": "line",
|
|
73
|
+
"x": 480,
|
|
74
|
+
"y": 320,
|
|
75
|
+
"wires": []
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"id": "chart-hum-1",
|
|
79
|
+
"type": "ui_chart",
|
|
80
|
+
"name": "Humidity",
|
|
81
|
+
"group": "bme680-group-1",
|
|
82
|
+
"order": 3,
|
|
83
|
+
"width": 6,
|
|
84
|
+
"height": 4,
|
|
85
|
+
"label": "Humidity (%RH)",
|
|
86
|
+
"chartType": "line",
|
|
87
|
+
"x": 480,
|
|
88
|
+
"y": 380,
|
|
89
|
+
"wires": []
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"id": "chart-gas-1",
|
|
93
|
+
"type": "ui_chart",
|
|
94
|
+
"name": "Gas Resistance",
|
|
95
|
+
"group": "bme680-group-1",
|
|
96
|
+
"order": 4,
|
|
97
|
+
"width": 6,
|
|
98
|
+
"height": 4,
|
|
99
|
+
"label": "Gas Resistance (Ohm)",
|
|
100
|
+
"chartType": "line",
|
|
101
|
+
"x": 480,
|
|
102
|
+
"y": 440,
|
|
103
|
+
"wires": []
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"id": "chart-alt-1",
|
|
107
|
+
"type": "ui_chart",
|
|
108
|
+
"name": "Altitude",
|
|
109
|
+
"group": "bme680-group-1",
|
|
110
|
+
"order": 5,
|
|
111
|
+
"width": 6,
|
|
112
|
+
"height": 4,
|
|
113
|
+
"label": "Altitude (m)",
|
|
114
|
+
"chartType": "line",
|
|
115
|
+
"x": 480,
|
|
116
|
+
"y": 500,
|
|
117
|
+
"wires": []
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"id": "chart-dew-1",
|
|
121
|
+
"type": "ui_chart",
|
|
122
|
+
"name": "Dew Point",
|
|
123
|
+
"group": "bme680-group-1",
|
|
124
|
+
"order": 6,
|
|
125
|
+
"width": 6,
|
|
126
|
+
"height": 4,
|
|
127
|
+
"label": "Dew Point (°C)",
|
|
128
|
+
"chartType": "line",
|
|
129
|
+
"x": 480,
|
|
130
|
+
"y": 560,
|
|
131
|
+
"wires": []
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"id": "voc-alert-1",
|
|
135
|
+
"type": "function",
|
|
136
|
+
"name": "VOC Alert",
|
|
137
|
+
"func": "var readings = context.get('readings') || [];\nreadings.push(msg.payload.gas_resistance);\nif (readings.length > 144) readings.shift();\ncontext.set('readings', readings);\nif (readings.length >= 10 && msg.payload.gas_resistance !== null) {\n var sorted = readings.slice().sort(function(a,b){return a-b;});\n var median = sorted[Math.floor(sorted.length/2)];\n if (msg.payload.gas_resistance < median * 0.8) {\n msg.payload = 'VOC concentration rising: ' + msg.payload.gas_resistance.toFixed(0) + ' Ohm (median: ' + median.toFixed(0) + ')';\n return msg;\n }\n}\nreturn null;",
|
|
138
|
+
"outputs": 1,
|
|
139
|
+
"noerr": 0,
|
|
140
|
+
"x": 480,
|
|
141
|
+
"y": 620,
|
|
142
|
+
"wires": [["debug-voc-1"]]
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"id": "debug-voc-1",
|
|
146
|
+
"type": "debug",
|
|
147
|
+
"name": "VOC Alert",
|
|
148
|
+
"active": true,
|
|
149
|
+
"tosideuit": false,
|
|
150
|
+
"console": "false",
|
|
151
|
+
"tostatus": "false",
|
|
152
|
+
"complete": "payload",
|
|
153
|
+
"targetType": "msg",
|
|
154
|
+
"x": 660,
|
|
155
|
+
"y": 620,
|
|
156
|
+
"wires": []
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"id": "bme680-group-1",
|
|
160
|
+
"type": "ui_group",
|
|
161
|
+
"name": "BME680 Air Quality",
|
|
162
|
+
"tab": "bme680-tab-1",
|
|
163
|
+
"order": 1,
|
|
164
|
+
"disp": true,
|
|
165
|
+
"width": 12
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"id": "bme680-tab-1",
|
|
169
|
+
"type": "ui_tab",
|
|
170
|
+
"name": "BME680 Demo",
|
|
171
|
+
"icon": "dashboard",
|
|
172
|
+
"order": 1,
|
|
173
|
+
"info": "This flow demonstrates the BME680 4-in-1 environmental sensor (temperature, pressure, humidity, gas resistance).\n\nAn inject node fires every 10 seconds into the periph-bme680 node. The output goes to a debug node and six ui_chart nodes showing temperature, pressure, humidity, gas resistance, altitude, and dew point.\n\nA VOC alert function node watches the gas-resistance trace — a sustained drop below 80% of the running 24-hour median triggers a \"VOC concentration rising\" alert. This demonstrates that even the raw uncalibrated gas reading is useful as a relative VOC indicator without the closed-source BSEC library.\n\nGas resistance drops when VOCs (isopropyl alcohol, marker pen fumes, cooking) are present near the sensor and recovers as the air clears."
|
|
174
|
+
}
|
|
175
|
+
]
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<!-- Config node: one instance per physical device -->
|
|
2
|
+
<script type="text/javascript">
|
|
3
|
+
RED.nodes.registerType('aht21-device', {
|
|
4
|
+
category: 'config',
|
|
5
|
+
defaults: {
|
|
6
|
+
name: { value: '' },
|
|
7
|
+
bus: { value: '1', required: true, validate: RED.validators.number() },
|
|
8
|
+
address: { value: '38', required: true }
|
|
9
|
+
},
|
|
10
|
+
label: function() {
|
|
11
|
+
return this.name || ('AHT21 @ 0x' + this.address + ' (bus ' + this.bus + ')');
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<script type="text/html" data-template-name="aht21-device">
|
|
17
|
+
<div class="form-row">
|
|
18
|
+
<label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
19
|
+
<input type="text" id="node-config-input-name" placeholder="AHT21">
|
|
20
|
+
</div>
|
|
21
|
+
<div class="form-row">
|
|
22
|
+
<label for="node-config-input-bus"><i class="fa fa-microchip"></i> I²C bus</label>
|
|
23
|
+
<input type="number" id="node-config-input-bus" min="0" style="width:80px"> (Linux /dev/i2c-N)
|
|
24
|
+
</div>
|
|
25
|
+
<div class="form-row">
|
|
26
|
+
<label for="node-config-input-address">Address (hex)</label>
|
|
27
|
+
<input type="text" id="node-config-input-address" placeholder="38" style="width:80px"> default: 38 (0x38)
|
|
28
|
+
</div>
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<!-- Read node: trigger → measurements -->
|
|
32
|
+
<script type="text/javascript">
|
|
33
|
+
RED.nodes.registerType('aht21', {
|
|
34
|
+
category: 'Peripheral',
|
|
35
|
+
color: '#a6bbcf',
|
|
36
|
+
defaults: {
|
|
37
|
+
name: { value: '' },
|
|
38
|
+
device: { value: '', type: 'aht21-device', required: true }
|
|
39
|
+
},
|
|
40
|
+
inputs: 1,
|
|
41
|
+
outputs: 1,
|
|
42
|
+
icon: 'font-awesome/fa-thermometer-half',
|
|
43
|
+
label: function() { return this.name || 'AHT21'; },
|
|
44
|
+
paletteLabel: 'AHT21'
|
|
45
|
+
});
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<script type="text/html" data-template-name="aht21">
|
|
49
|
+
<div class="form-row">
|
|
50
|
+
<label for="node-input-device"><i class="fa fa-microchip"></i> Device</label>
|
|
51
|
+
<input type="text" id="node-input-device">
|
|
52
|
+
</div>
|
|
53
|
+
<div class="form-row">
|
|
54
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
55
|
+
<input type="text" id="node-input-name" placeholder="AHT21">
|
|
56
|
+
</div>
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
<script type="text/html" data-help-name="aht21">
|
|
60
|
+
<p>Reads temperature and humidity from an AHT21 environmental sensor over I²C.</p>
|
|
61
|
+
<p>Any input message triggers a measurement. The device is configured via an <b>aht21-device</b> config node.</p>
|
|
62
|
+
|
|
63
|
+
<h3>Output</h3>
|
|
64
|
+
<dl class="message-properties">
|
|
65
|
+
<dt>payload.temperature_c <span class="property-type">number</span></dt>
|
|
66
|
+
<dd>Temperature in degrees Celsius.</dd>
|
|
67
|
+
<dt>payload.humidity_pct <span class="property-type">number</span></dt>
|
|
68
|
+
<dd>Relative humidity in percent.</dd>
|
|
69
|
+
</dl>
|
|
70
|
+
|
|
71
|
+
<h3>Details</h3>
|
|
72
|
+
<p>Configure the I²C bus number and the chip's fixed address (default <code>0x38</code>)
|
|
73
|
+
in the device config node. The AHT21 has no configurable address — only one AHT21
|
|
74
|
+
can be on a single I²C bus.</p>
|
|
75
|
+
</script>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = function(RED) {
|
|
4
|
+
const { I2CTransport } = require('periph/src/transport/i2c');
|
|
5
|
+
const { AHT21Full } = require('periph/src/chips/environmental/aht21');
|
|
6
|
+
|
|
7
|
+
function AHT21DeviceNode(config) {
|
|
8
|
+
RED.nodes.createNode(this, config);
|
|
9
|
+
const node = this;
|
|
10
|
+
try {
|
|
11
|
+
const transport = new I2CTransport(parseInt(config.bus), parseInt(config.address, 16));
|
|
12
|
+
node.driver = new AHT21Full(transport);
|
|
13
|
+
node.transport = transport;
|
|
14
|
+
} catch (e) {
|
|
15
|
+
node.error('AHT21 init failed: ' + e.message);
|
|
16
|
+
}
|
|
17
|
+
node.on('close', function() {
|
|
18
|
+
if (node.transport) node.transport.close();
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
RED.nodes.registerType('aht21-device', AHT21DeviceNode);
|
|
22
|
+
|
|
23
|
+
function AHT21ReadNode(config) {
|
|
24
|
+
RED.nodes.createNode(this, config);
|
|
25
|
+
const node = this;
|
|
26
|
+
node.device = RED.nodes.getNode(config.device);
|
|
27
|
+
|
|
28
|
+
node.on('input', function(msg, send, done) {
|
|
29
|
+
if (!node.device || !node.device.driver) {
|
|
30
|
+
node.error('No AHT21 device configured', msg);
|
|
31
|
+
done();
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const d = node.device.driver;
|
|
36
|
+
const r = d.read();
|
|
37
|
+
msg.payload = {
|
|
38
|
+
temperature_c: r.temperature_c,
|
|
39
|
+
humidity_pct: r.humidity_pct
|
|
40
|
+
};
|
|
41
|
+
send(msg);
|
|
42
|
+
done();
|
|
43
|
+
} catch (e) {
|
|
44
|
+
done(e);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
RED.nodes.registerType('aht21', AHT21ReadNode);
|
|
49
|
+
};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
<!-- Config node: one instance per physical device -->
|
|
2
|
+
<script type="text/javascript">
|
|
3
|
+
RED.nodes.registerType('bme280-device', {
|
|
4
|
+
category: 'config',
|
|
5
|
+
defaults: {
|
|
6
|
+
name: { value: '' },
|
|
7
|
+
bus: { value: '1', required: true, validate: RED.validators.number() },
|
|
8
|
+
addr: { value: '0x76', required: true },
|
|
9
|
+
osrsT: { value: '1', required: true, validate: RED.validators.number() },
|
|
10
|
+
osrsP: { value: '1', required: true, validate: RED.validators.number() },
|
|
11
|
+
osrsH: { value: '1', required: true, validate: RED.validators.number() },
|
|
12
|
+
mode: { value: '0', required: true, validate: RED.validators.number() },
|
|
13
|
+
filter: { value: '0', required: true, validate: RED.validators.number() },
|
|
14
|
+
tSb: { value: '0', required: true, validate: RED.validators.number() }
|
|
15
|
+
},
|
|
16
|
+
label: function() {
|
|
17
|
+
return this.name || ('BME280 @ ' + this.addr + ' (bus ' + this.bus + ')');
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<script type="text/html" data-template-name="bme280-device">
|
|
23
|
+
<div class="form-row">
|
|
24
|
+
<label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
25
|
+
<input type="text" id="node-config-input-name" placeholder="BME280">
|
|
26
|
+
</div>
|
|
27
|
+
<div class="form-row">
|
|
28
|
+
<label for="node-config-input-bus"><i class="fa fa-microchip"></i> I²C bus</label>
|
|
29
|
+
<input type="number" id="node-config-input-bus" min="0" style="width:80px"> (Linux /dev/i2c-N)
|
|
30
|
+
</div>
|
|
31
|
+
<div class="form-row">
|
|
32
|
+
<label for="node-config-input-addr"><i class="fa fa-microchip"></i> I²C address</label>
|
|
33
|
+
<select id="node-config-input-addr" style="width:100px">
|
|
34
|
+
<option value="0x76">0x76 (SDO=GND)</option>
|
|
35
|
+
<option value="0x77">0x77 (SDO=VDDIO)</option>
|
|
36
|
+
</select>
|
|
37
|
+
</div>
|
|
38
|
+
<div class="form-row">
|
|
39
|
+
<label for="node-config-input-osrsT">Temperature oversampling</label>
|
|
40
|
+
<select id="node-config-input-osrsT" style="width:180px">
|
|
41
|
+
<option value="0">Skip</option>
|
|
42
|
+
<option value="1" selected>×1</option>
|
|
43
|
+
<option value="2">×2</option>
|
|
44
|
+
<option value="3">×4</option>
|
|
45
|
+
<option value="4">×8</option>
|
|
46
|
+
<option value="5">×16</option>
|
|
47
|
+
</select>
|
|
48
|
+
</div>
|
|
49
|
+
<div class="form-row">
|
|
50
|
+
<label for="node-config-input-osrsP">Pressure oversampling</label>
|
|
51
|
+
<select id="node-config-input-osrsP" style="width:180px">
|
|
52
|
+
<option value="0">Skip</option>
|
|
53
|
+
<option value="1" selected>×1</option>
|
|
54
|
+
<option value="2">×2</option>
|
|
55
|
+
<option value="3">×4</option>
|
|
56
|
+
<option value="4">×8</option>
|
|
57
|
+
<option value="5">×16</option>
|
|
58
|
+
</select>
|
|
59
|
+
</div>
|
|
60
|
+
<div class="form-row">
|
|
61
|
+
<label for="node-config-input-osrsH">Humidity oversampling</label>
|
|
62
|
+
<select id="node-config-input-osrsH" style="width:180px">
|
|
63
|
+
<option value="0">Skip</option>
|
|
64
|
+
<option value="1" selected>×1</option>
|
|
65
|
+
<option value="2">×2</option>
|
|
66
|
+
<option value="3">×4</option>
|
|
67
|
+
<option value="4">×8</option>
|
|
68
|
+
<option value="5">×16</option>
|
|
69
|
+
</select>
|
|
70
|
+
</div>
|
|
71
|
+
<div class="form-row">
|
|
72
|
+
<label for="node-config-input-mode">Power mode</label>
|
|
73
|
+
<select id="node-config-input-mode" style="width:180px">
|
|
74
|
+
<option value="0" selected>Sleep</option>
|
|
75
|
+
<option value="1">Forced (one-shot)</option>
|
|
76
|
+
<option value="3">Normal (continuous)</option>
|
|
77
|
+
</select>
|
|
78
|
+
</div>
|
|
79
|
+
<div class="form-row">
|
|
80
|
+
<label for="node-config-input-filter">IIR filter coefficient</label>
|
|
81
|
+
<select id="node-config-input-filter" style="width:180px">
|
|
82
|
+
<option value="0" selected>Off</option>
|
|
83
|
+
<option value="1">2</option>
|
|
84
|
+
<option value="2">4</option>
|
|
85
|
+
<option value="3">8</option>
|
|
86
|
+
<option value="4">16</option>
|
|
87
|
+
</select>
|
|
88
|
+
</div>
|
|
89
|
+
<div class="form-row">
|
|
90
|
+
<label for="node-config-input-tSb">Standby time (normal mode)</label>
|
|
91
|
+
<select id="node-config-input-tSb" style="width:180px">
|
|
92
|
+
<option value="0" selected>0.5 ms</option>
|
|
93
|
+
<option value="1">62.5 ms</option>
|
|
94
|
+
<option value="2">125 ms</option>
|
|
95
|
+
<option value="3">250 ms</option>
|
|
96
|
+
<option value="4">500 ms</option>
|
|
97
|
+
<option value="5">1000 ms</option>
|
|
98
|
+
<option value="6">10 ms (BME280 only)</option>
|
|
99
|
+
<option value="7">20 ms (BME280 only)</option>
|
|
100
|
+
</select>
|
|
101
|
+
</div>
|
|
102
|
+
</script>
|
|
103
|
+
|
|
104
|
+
<!-- Read node: trigger → measurements -->
|
|
105
|
+
<script type="text/javascript">
|
|
106
|
+
RED.nodes.registerType('periph-bme280', {
|
|
107
|
+
category: 'Peripheral',
|
|
108
|
+
color: '#a6bbcf',
|
|
109
|
+
defaults: {
|
|
110
|
+
name: { value: '' },
|
|
111
|
+
device: { value: '', type: 'bme280-device', required: true },
|
|
112
|
+
seaLevelHpa: { value: '1013.25' }
|
|
113
|
+
},
|
|
114
|
+
inputs: 1,
|
|
115
|
+
outputs: 1,
|
|
116
|
+
icon: 'font-awesome/fa-tachometer',
|
|
117
|
+
label: function() { return this.name || 'BME280'; },
|
|
118
|
+
paletteLabel: 'BME280'
|
|
119
|
+
});
|
|
120
|
+
</script>
|
|
121
|
+
|
|
122
|
+
<script type="text/html" data-template-name="periph-bme280">
|
|
123
|
+
<div class="form-row">
|
|
124
|
+
<label for="node-input-device"><i class="fa fa-microchip"></i> Device</label>
|
|
125
|
+
<input type="text" id="node-input-device">
|
|
126
|
+
</div>
|
|
127
|
+
<div class="form-row">
|
|
128
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
129
|
+
<input type="text" id="node-input-name" placeholder="BME280">
|
|
130
|
+
</div>
|
|
131
|
+
<div class="form-row">
|
|
132
|
+
<label for="node-input-seaLevelHpa">Sea-level pressure (hPa)</label>
|
|
133
|
+
<input type="number" id="node-input-seaLevelHpa" min="800" max="1100" step="0.01" style="width:100px">
|
|
134
|
+
</div>
|
|
135
|
+
</script>
|
|
136
|
+
|
|
137
|
+
<script type="text/html" data-help-name="periph-bme280">
|
|
138
|
+
<p>Reads temperature, pressure, humidity, altitude, and dew point
|
|
139
|
+
from a BME280 combined humidity + pressure + temperature sensor.</p>
|
|
140
|
+
<p>Any input message triggers a measurement. The device is configured via a <b>bme280-device</b> config node.</p>
|
|
141
|
+
|
|
142
|
+
<h3>Inputs</h3>
|
|
143
|
+
<dl class="message-properties">
|
|
144
|
+
<dt>payload <span class="property-type">any</span></dt>
|
|
145
|
+
<dd>Any incoming message triggers a measurement.</dd>
|
|
146
|
+
</dl>
|
|
147
|
+
|
|
148
|
+
<h3>Outputs</h3>
|
|
149
|
+
<dl class="message-properties">
|
|
150
|
+
<dt>payload.temperature <span class="property-type">number</span></dt>
|
|
151
|
+
<dd>Temperature in degrees Celsius.</dd>
|
|
152
|
+
<dt>payload.pressure <span class="property-type">number</span></dt>
|
|
153
|
+
<dd>Pressure in hPa.</dd>
|
|
154
|
+
<dt>payload.humidity <span class="property-type">number</span></dt>
|
|
155
|
+
<dd>Relative humidity in %RH.</dd>
|
|
156
|
+
<dt>payload.altitude <span class="property-type">number</span></dt>
|
|
157
|
+
<dd>Altitude in metres computed from the configured sea-level pressure.</dd>
|
|
158
|
+
<dt>payload.dew_point <span class="property-type">number | null</span></dt>
|
|
159
|
+
<dd>Dew point in degrees Celsius (Magnus-Tetens approximation), or null if humidity is non-positive.</dd>
|
|
160
|
+
</dl>
|
|
161
|
+
|
|
162
|
+
<h3>Configuration</h3>
|
|
163
|
+
<p>Set the I²C bus number, I²C address (<code>0x76</code> or <code>0x77</code>),
|
|
164
|
+
temperature, pressure, and humidity oversampling, power mode (sleep / forced / normal),
|
|
165
|
+
IIR filter coefficient, and standby time in the device config node.
|
|
166
|
+
Note that the BME280's <code>t_sb</code> codes 6 and 7 mean <b>10 ms</b> and <b>20 ms</b>
|
|
167
|
+
(not 2000 ms / 4000 ms as on the BMP280).
|
|
168
|
+
The sea-level reference pressure defaults to <code>1013.25</code> hPa and can be
|
|
169
|
+
adjusted in the read node config panel.</p>
|
|
170
|
+
</script>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = function(RED) {
|
|
4
|
+
const { I2CTransport } = require('periph/src/transport/i2c');
|
|
5
|
+
const { BME280Full } = require('periph/src/chips/environmental/bme280');
|
|
6
|
+
|
|
7
|
+
function BME280DeviceNode(config) {
|
|
8
|
+
RED.nodes.createNode(this, config);
|
|
9
|
+
const node = this;
|
|
10
|
+
try {
|
|
11
|
+
const addr = parseInt(config.addr) || 0x76;
|
|
12
|
+
const transport = new I2CTransport(parseInt(config.bus), addr);
|
|
13
|
+
node.driver = new BME280Full(transport);
|
|
14
|
+
node.driver.configure(
|
|
15
|
+
parseInt(config.osrsT) || 1,
|
|
16
|
+
parseInt(config.osrsP) || 1,
|
|
17
|
+
parseInt(config.osrsH) || 1,
|
|
18
|
+
parseInt(config.mode) || 0,
|
|
19
|
+
parseInt(config.filter) || 0,
|
|
20
|
+
parseInt(config.tSb) || 0
|
|
21
|
+
);
|
|
22
|
+
node.transport = transport;
|
|
23
|
+
} catch (e) {
|
|
24
|
+
node.error('BME280 init failed: ' + e.message);
|
|
25
|
+
}
|
|
26
|
+
node.on('close', function() {
|
|
27
|
+
if (node.transport) node.transport.close();
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
RED.nodes.registerType('bme280-device', BME280DeviceNode);
|
|
31
|
+
|
|
32
|
+
function BME280ReadNode(config) {
|
|
33
|
+
RED.nodes.createNode(this, config);
|
|
34
|
+
const node = this;
|
|
35
|
+
node.device = RED.nodes.getNode(config.device);
|
|
36
|
+
|
|
37
|
+
node.on('input', function(msg, send, done) {
|
|
38
|
+
if (!node.device || !node.device.driver) {
|
|
39
|
+
node.error('No BME280 device configured', msg);
|
|
40
|
+
done();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const d = node.device.driver;
|
|
45
|
+
const t = d.temperature();
|
|
46
|
+
const p = d.pressure();
|
|
47
|
+
const h = d.humidity();
|
|
48
|
+
const seaLevelHpa = parseFloat(config.seaLevelHpa) || 1013.25;
|
|
49
|
+
const altitude = 44330 * (1 - Math.pow(p / seaLevelHpa, 1 / 5.255));
|
|
50
|
+
const a = 17.27, b = 237.7;
|
|
51
|
+
let dewPoint = null;
|
|
52
|
+
if (h > 0) {
|
|
53
|
+
const alpha = (a * t) / (b + t) + Math.log(h / 100);
|
|
54
|
+
dewPoint = (b * alpha) / (a - alpha);
|
|
55
|
+
}
|
|
56
|
+
msg.payload = {
|
|
57
|
+
temperature: t,
|
|
58
|
+
pressure: p,
|
|
59
|
+
humidity: h,
|
|
60
|
+
altitude: altitude,
|
|
61
|
+
dew_point: dewPoint
|
|
62
|
+
};
|
|
63
|
+
send(msg);
|
|
64
|
+
done();
|
|
65
|
+
} catch (e) {
|
|
66
|
+
done(e);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
RED.nodes.registerType('periph-bme280', BME280ReadNode);
|
|
71
|
+
};
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
<!-- Config node: one instance per physical device -->
|
|
2
|
+
<script type="text/javascript">
|
|
3
|
+
RED.nodes.registerType('bme680-device', {
|
|
4
|
+
category: 'config',
|
|
5
|
+
defaults: {
|
|
6
|
+
name: { value: '' },
|
|
7
|
+
bus: { value: '1', required: true, validate: RED.validators.number() },
|
|
8
|
+
addr: { value: '0x76', required: true },
|
|
9
|
+
osrsT: { value: '1', required: true, validate: RED.validators.number() },
|
|
10
|
+
osrsP: { value: '1', required: true, validate: RED.validators.number() },
|
|
11
|
+
osrsH: { value: '1', required: true, validate: RED.validators.number() },
|
|
12
|
+
filter: { value: '0', required: true, validate: RED.validators.number() },
|
|
13
|
+
heaterTemp: { value: '320', required: true, validate: RED.validators.number() },
|
|
14
|
+
heaterDur: { value: '150', required: true, validate: RED.validators.number() }
|
|
15
|
+
},
|
|
16
|
+
label: function() {
|
|
17
|
+
return this.name || ('BME680 @ ' + this.addr + ' (bus ' + this.bus + ')');
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<script type="text/html" data-template-name="bme680-device">
|
|
23
|
+
<div class="form-row">
|
|
24
|
+
<label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
25
|
+
<input type="text" id="node-config-input-name" placeholder="BME680">
|
|
26
|
+
</div>
|
|
27
|
+
<div class="form-row">
|
|
28
|
+
<label for="node-config-input-bus"><i class="fa fa-microchip"></i> I²C bus</label>
|
|
29
|
+
<input type="number" id="node-config-input-bus" min="0" style="width:80px"> (Linux /dev/i2c-N)
|
|
30
|
+
</div>
|
|
31
|
+
<div class="form-row">
|
|
32
|
+
<label for="node-config-input-addr"><i class="fa fa-microchip"></i> I²C address</label>
|
|
33
|
+
<select id="node-config-input-addr" style="width:100px">
|
|
34
|
+
<option value="0x76">0x76 (SDO=GND)</option>
|
|
35
|
+
<option value="0x77">0x77 (SDO=VDDIO)</option>
|
|
36
|
+
</select>
|
|
37
|
+
</div>
|
|
38
|
+
<div class="form-row">
|
|
39
|
+
<label for="node-config-input-osrsT">Temperature oversampling</label>
|
|
40
|
+
<select id="node-config-input-osrsT" style="width:180px">
|
|
41
|
+
<option value="0">Skip</option>
|
|
42
|
+
<option value="1" selected>×1</option>
|
|
43
|
+
<option value="2">×2</option>
|
|
44
|
+
<option value="3">×4</option>
|
|
45
|
+
<option value="4">×8</option>
|
|
46
|
+
<option value="5">×16</option>
|
|
47
|
+
</select>
|
|
48
|
+
</div>
|
|
49
|
+
<div class="form-row">
|
|
50
|
+
<label for="node-config-input-osrsP">Pressure oversampling</label>
|
|
51
|
+
<select id="node-config-input-osrsP" style="width:180px">
|
|
52
|
+
<option value="0">Skip</option>
|
|
53
|
+
<option value="1" selected>×1</option>
|
|
54
|
+
<option value="2">×2</option>
|
|
55
|
+
<option value="3">×4</option>
|
|
56
|
+
<option value="4">×8</option>
|
|
57
|
+
<option value="5">×16</option>
|
|
58
|
+
</select>
|
|
59
|
+
</div>
|
|
60
|
+
<div class="form-row">
|
|
61
|
+
<label for="node-config-input-osrsH">Humidity oversampling</label>
|
|
62
|
+
<select id="node-config-input-osrsH" style="width:180px">
|
|
63
|
+
<option value="0">Skip</option>
|
|
64
|
+
<option value="1" selected>×1</option>
|
|
65
|
+
<option value="2">×2</option>
|
|
66
|
+
<option value="3">×4</option>
|
|
67
|
+
<option value="4">×8</option>
|
|
68
|
+
<option value="5">×16</option>
|
|
69
|
+
</select>
|
|
70
|
+
</div>
|
|
71
|
+
<div class="form-row">
|
|
72
|
+
<label for="node-config-input-filter">IIR filter coefficient</label>
|
|
73
|
+
<select id="node-config-input-filter" style="width:180px">
|
|
74
|
+
<option value="0" selected>Off</option>
|
|
75
|
+
<option value="1">1</option>
|
|
76
|
+
<option value="2">3</option>
|
|
77
|
+
<option value="3">7</option>
|
|
78
|
+
<option value="4">15</option>
|
|
79
|
+
<option value="5">31</option>
|
|
80
|
+
<option value="6">63</option>
|
|
81
|
+
<option value="7">127</option>
|
|
82
|
+
</select>
|
|
83
|
+
</div>
|
|
84
|
+
<div class="form-row">
|
|
85
|
+
<label for="node-config-input-heaterTemp">Heater target (°C)</label>
|
|
86
|
+
<input type="number" id="node-config-input-heaterTemp" min="200" max="400" style="width:80px">
|
|
87
|
+
</div>
|
|
88
|
+
<div class="form-row">
|
|
89
|
+
<label for="node-config-input-heaterDur">Heater duration (ms)</label>
|
|
90
|
+
<input type="number" id="node-config-input-heaterDur" min="1" max="4032" style="width:80px">
|
|
91
|
+
</div>
|
|
92
|
+
</script>
|
|
93
|
+
|
|
94
|
+
<!-- Read node: trigger → measurements -->
|
|
95
|
+
<script type="text/javascript">
|
|
96
|
+
RED.nodes.registerType('periph-bme680', {
|
|
97
|
+
category: 'Peripheral',
|
|
98
|
+
color: '#a6bbcf',
|
|
99
|
+
defaults: {
|
|
100
|
+
name: { value: '' },
|
|
101
|
+
device: { value: '', type: 'bme680-device', required: true },
|
|
102
|
+
seaLevelHpa: { value: '1013.25' }
|
|
103
|
+
},
|
|
104
|
+
inputs: 1,
|
|
105
|
+
outputs: 1,
|
|
106
|
+
icon: 'font-awesome/fa-tachometer',
|
|
107
|
+
label: function() { return this.name || 'BME680'; },
|
|
108
|
+
paletteLabel: 'BME680'
|
|
109
|
+
});
|
|
110
|
+
</script>
|
|
111
|
+
|
|
112
|
+
<script type="text/html" data-template-name="periph-bme680">
|
|
113
|
+
<div class="form-row">
|
|
114
|
+
<label for="node-input-device"><i class="fa fa-microchip"></i> Device</label>
|
|
115
|
+
<input type="text" id="node-input-device">
|
|
116
|
+
</div>
|
|
117
|
+
<div class="form-row">
|
|
118
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
119
|
+
<input type="text" id="node-input-name" placeholder="BME680">
|
|
120
|
+
</div>
|
|
121
|
+
<div class="form-row">
|
|
122
|
+
<label for="node-input-seaLevelHpa">Sea-level pressure (hPa)</label>
|
|
123
|
+
<input type="number" id="node-input-seaLevelHpa" min="800" max="1100" step="0.01" style="width:100px">
|
|
124
|
+
</div>
|
|
125
|
+
</script>
|
|
126
|
+
|
|
127
|
+
<script type="text/html" data-help-name="periph-bme680">
|
|
128
|
+
<p>Reads temperature, pressure, humidity, gas resistance, altitude, and dew point
|
|
129
|
+
from a BME680 4-in-1 environmental sensor.</p>
|
|
130
|
+
<p>Any input message triggers a measurement. The device is configured via a <b>bme680-device</b> config node.</p>
|
|
131
|
+
|
|
132
|
+
<h3>Inputs</h3>
|
|
133
|
+
<dl class="message-properties">
|
|
134
|
+
<dt>payload <span class="property-type">any</span></dt>
|
|
135
|
+
<dd>Any incoming message triggers a measurement.</dd>
|
|
136
|
+
</dl>
|
|
137
|
+
|
|
138
|
+
<h3>Outputs</h3>
|
|
139
|
+
<dl class="message-properties">
|
|
140
|
+
<dt>payload.temperature <span class="property-type">number</span></dt>
|
|
141
|
+
<dd>Temperature in degrees Celsius.</dd>
|
|
142
|
+
<dt>payload.pressure <span class="property-type">number</span></dt>
|
|
143
|
+
<dd>Pressure in hPa.</dd>
|
|
144
|
+
<dt>payload.humidity <span class="property-type">number</span></dt>
|
|
145
|
+
<dd>Relative humidity in %RH.</dd>
|
|
146
|
+
<dt>payload.gas_resistance <span class="property-type">number | null</span></dt>
|
|
147
|
+
<dd>Gas sensor resistance in Ohms, or null if the reading was invalid.</dd>
|
|
148
|
+
<dt>payload.altitude <span class="property-type">number</span></dt>
|
|
149
|
+
<dd>Altitude in metres computed from the configured sea-level pressure.</dd>
|
|
150
|
+
<dt>payload.dew_point <span class="property-type">number</span></dt>
|
|
151
|
+
<dd>Dew point in degrees Celsius (Magnus-Tetens approximation).</dd>
|
|
152
|
+
</dl>
|
|
153
|
+
|
|
154
|
+
<h3>Configuration</h3>
|
|
155
|
+
<p>Set the I²C bus number, I²C address (<code>0x76</code> or <code>0x77</code>),
|
|
156
|
+
temperature, pressure, and humidity oversampling, IIR filter coefficient,
|
|
157
|
+
heater target temperature and duration in the device config node.
|
|
158
|
+
The sea-level reference pressure defaults to <code>1013.25</code> hPa and can be
|
|
159
|
+
adjusted in the read node config panel.</p>
|
|
160
|
+
</script>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = function(RED) {
|
|
4
|
+
const { I2CTransport } = require('periph/src/transport/i2c');
|
|
5
|
+
const { BME680Full } = require('periph/src/chips/environmental/bme680');
|
|
6
|
+
|
|
7
|
+
function BME680DeviceNode(config) {
|
|
8
|
+
RED.nodes.createNode(this, config);
|
|
9
|
+
const node = this;
|
|
10
|
+
try {
|
|
11
|
+
const addr = parseInt(config.addr) || 0x76;
|
|
12
|
+
const transport = new I2CTransport(parseInt(config.bus), addr);
|
|
13
|
+
node.driver = new BME680Full(transport);
|
|
14
|
+
node.driver.configure(
|
|
15
|
+
parseInt(config.osrsT) || 1,
|
|
16
|
+
parseInt(config.osrsP) || 1,
|
|
17
|
+
parseInt(config.osrsH) || 1,
|
|
18
|
+
1,
|
|
19
|
+
parseInt(config.filter) || 0
|
|
20
|
+
);
|
|
21
|
+
node.driver.setHeater(
|
|
22
|
+
parseInt(config.heaterTemp) || 320,
|
|
23
|
+
parseInt(config.heaterDur) || 150
|
|
24
|
+
);
|
|
25
|
+
node.transport = transport;
|
|
26
|
+
} catch (e) {
|
|
27
|
+
node.error('BME680 init failed: ' + e.message);
|
|
28
|
+
}
|
|
29
|
+
node.on('close', function() {
|
|
30
|
+
if (node.transport) node.transport.close();
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
RED.nodes.registerType('bme680-device', BME680DeviceNode);
|
|
34
|
+
|
|
35
|
+
function BME680ReadNode(config) {
|
|
36
|
+
RED.nodes.createNode(this, config);
|
|
37
|
+
const node = this;
|
|
38
|
+
node.device = RED.nodes.getNode(config.device);
|
|
39
|
+
|
|
40
|
+
node.on('input', function(msg, send, done) {
|
|
41
|
+
if (!node.device || !node.device.driver) {
|
|
42
|
+
node.error('No BME680 device configured', msg);
|
|
43
|
+
done();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const d = node.device.driver;
|
|
48
|
+
const r = d.readAll();
|
|
49
|
+
const seaLevelHpa = parseFloat(config.seaLevelHpa) || 1013.25;
|
|
50
|
+
const altitude = 44330 * (1 - Math.pow(r.pressure / seaLevelHpa, 1 / 5.255));
|
|
51
|
+
const a = 17.62, b = 243.12;
|
|
52
|
+
const gamma = Math.log(r.humidity / 100) + (a * r.temperature) / (b + r.temperature);
|
|
53
|
+
const dewPoint = (b * gamma) / (a - gamma);
|
|
54
|
+
msg.payload = {
|
|
55
|
+
temperature: r.temperature,
|
|
56
|
+
pressure: r.pressure,
|
|
57
|
+
humidity: r.humidity,
|
|
58
|
+
gas_resistance: isNaN(r.gasResistance) ? null : r.gasResistance,
|
|
59
|
+
altitude: altitude,
|
|
60
|
+
dew_point: dewPoint
|
|
61
|
+
};
|
|
62
|
+
send(msg);
|
|
63
|
+
done();
|
|
64
|
+
} catch (e) {
|
|
65
|
+
done(e);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
RED.nodes.registerType('periph-bme680', BME680ReadNode);
|
|
70
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-periph-environmental",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Node-RED nodes for environmental sensor chips",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"node-red",
|
|
@@ -10,8 +10,13 @@
|
|
|
10
10
|
"sensor",
|
|
11
11
|
"peripheral"
|
|
12
12
|
],
|
|
13
|
+
"main": "index.js",
|
|
13
14
|
"node-red": {
|
|
14
|
-
"nodes": {
|
|
15
|
+
"nodes": {
|
|
16
|
+
"aht21": "nodes/aht21/aht21.js",
|
|
17
|
+
"bme280": "nodes/bme280/bme280.js",
|
|
18
|
+
"bme680": "nodes/bme680/bme680.js"
|
|
19
|
+
}
|
|
15
20
|
},
|
|
16
21
|
"dependencies": {
|
|
17
22
|
"periph": "*"
|
package/examples/.gitkeep
DELETED
|
File without changes
|
package/nodes/.gitkeep
DELETED
|
File without changes
|