node-red-contrib-sht41 1.0.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.
Files changed (3) hide show
  1. package/package.json +16 -0
  2. package/sht41.html +99 -0
  3. package/sht41.js +112 -0
package/package.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "node-red-contrib-sht41",
3
+ "version": "1.0.0",
4
+ "description": "Node-RED node to read temperature and humidity from Sensirion SHT41 sensors over I2C",
5
+ "keywords": ["node-red", "sht41", "temperature", "humidity", "i2c", "sensor"],
6
+ "author": "Your Name <youremail@example.com>",
7
+ "license": "MIT",
8
+ "dependencies": {
9
+ "i2c-bus": "^5.2.2"
10
+ },
11
+ "node-red": {
12
+ "nodes": {
13
+ "sht41": "sht41.js"
14
+ }
15
+ }
16
+ }
package/sht41.html ADDED
@@ -0,0 +1,99 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType('SHT41', {
3
+ category: 'input',
4
+ color: '#1f77b4',
5
+ defaults: {
6
+ name: { value: "" },
7
+ busNum: { value: "1", required: true, validate: RED.validators.number() },
8
+ address: { value: "0x44", required: true, validate: function(v) {
9
+ return /^0x[0-9a-fA-F]+$|^[0-9]+$/.test(v);
10
+ }
11
+ },
12
+ precision: { value: "high" },
13
+ unit: { value: "C" }
14
+ },
15
+ inputs: 1,
16
+ outputs: 1,
17
+ icon: "font-awesome/fa-thermometer-half",
18
+ label: function() {
19
+ return this.name || "SHT41";
20
+ },
21
+ oneditprepare: function() {
22
+ if (this.busNum === undefined) {
23
+ $("#node-input-busNum").val(1);
24
+ }
25
+ $("#node-input-busNum").spinner({
26
+ min:0,
27
+ max:7
28
+ });
29
+ }
30
+ });
31
+ </script>
32
+
33
+ <script type="text/html" data-template-name="SHT41">
34
+
35
+ <div class="form-row">
36
+ <label for="node-input-name">
37
+ <i class="fa fa-tag"></i> Name
38
+ </label>
39
+ <input type="text" id="node-input-name" placeholder="Name">
40
+ </div>
41
+
42
+ <div class="form-row">
43
+ <label for="node-input-busNum">
44
+ <i class="fa fa-random"></i> I2C Bus
45
+ </label>
46
+ /dev/i2c-<input id="node-input-busNum" class="I2C-in-address" min=0 max=7 style="width:40px; height:16px;">
47
+ </div>
48
+
49
+ <div class="form-row">
50
+ <label for="node-input-address">
51
+ <i class="fa fa-map-marker"></i> I2C Address
52
+ </label>
53
+ <input type="text" id="node-input-address">
54
+ </div>
55
+
56
+ <div class="form-row">
57
+ <label for="node-input-precision">
58
+ <i class="fa fa-tachometer"></i> Precision
59
+ </label>
60
+ <select id="node-input-precision">
61
+ <option value="high">High</option>
62
+ <option value="medium">Medium</option>
63
+ <option value="low">Low</option>
64
+ </select>
65
+ </div>
66
+
67
+ <div class="form-row">
68
+ <label for="node-input-unit">
69
+ <i class="fa fa-thermometer-half"></i> Unit
70
+ </label>
71
+ <select id="node-input-unit">
72
+ <option value="C">°C</option>
73
+ <option value="F">°F</option>
74
+ </select>
75
+ </div>
76
+
77
+ </script>
78
+
79
+ <script type="text/html" data-help-name="SHT41">
80
+ <p>
81
+ Reads temperature and humidity from a Sensirion SHT41 sensor over I2C.
82
+ </p>
83
+ <p>
84
+ Inputs:
85
+ </p>
86
+ <ul>
87
+ <li><b>I2C Bus</b> - I2C bus number, default is 1. This is not common to change.</li>
88
+ <li><b>I2C Address</b> - Address of I2C device to poll. SHT41 is 0x44 in hex, or 68 in decimal.</li>
89
+ <li><b>Precision</b> - Lower precision can be polled more frequently. Refer to data sheet for more information.</li>
90
+ <li><b>Unit</b> - Return values in Celcius or Fahrenheit.</li>
91
+ </ul>
92
+ <p>
93
+ Outputs:
94
+ </p>
95
+ <ul>
96
+ <li><code>msg.payload.temperature</code> – Temperature (°C or °F)</li>
97
+ <li><code>msg.payload.humidity</code> – Relative Humidity (%)</li>
98
+ </ul>
99
+ </script>
package/sht41.js ADDED
@@ -0,0 +1,112 @@
1
+ module.exports = function(RED) {
2
+
3
+ const i2c = require('i2c-bus');
4
+
5
+ function SHT41Node(config) {
6
+ RED.nodes.createNode(this, config);
7
+ const node = this;
8
+
9
+ node.busNumber = parseInt(config.busNum) || 1;
10
+ node.address = parseInt(config.address) || 0x44;
11
+ node.precision = config.precision || "high";
12
+ node.unit = config.unit || "F"; // C or F
13
+
14
+ const COMMANDS = {
15
+ high: 0xFD,
16
+ medium: 0xF6,
17
+ low: 0xE0
18
+ };
19
+
20
+ let bus;
21
+
22
+ try {
23
+ bus = i2c.openSync(node.busNumber);
24
+ node.status({fill:"green",shape:"dot",text:"I2C Ready"});
25
+ } catch (err) {
26
+ node.status({fill:"red",shape:"ring",text:"I2C Error"});
27
+ node.error("Failed to open I2C bus: " + err.message);
28
+ return;
29
+ }
30
+
31
+ // CRC-8 check (polynomial 0x31)
32
+ function checkCRC(data, crc) {
33
+ let calculated = 0xFF;
34
+ for (let i = 0; i < 2; i++) {
35
+ calculated ^= data[i];
36
+ for (let bit = 8; bit > 0; --bit) {
37
+ if (calculated & 0x80) {
38
+ calculated = (calculated << 1) ^ 0x31;
39
+ } else {
40
+ calculated <<= 1;
41
+ }
42
+ }
43
+ }
44
+ calculated &= 0xFF;
45
+ return calculated === crc;
46
+ }
47
+
48
+ node.on('input', function(msg) {
49
+
50
+ const command = COMMANDS[node.precision] || 0xFD;
51
+
52
+ try {
53
+ const cmdBuffer = Buffer.from([command]);
54
+ bus.i2cWriteSync(node.address, 1, cmdBuffer);
55
+ } catch (err) {
56
+ node.status({fill:"red",shape:"ring",text:"Write Error"});
57
+ node.error("Write Error: " + err.message, msg);
58
+ return;
59
+ }
60
+
61
+ setTimeout(() => {
62
+ try {
63
+ const buf = Buffer.alloc(6);
64
+ bus.i2cReadSync(node.address, 6, buf);
65
+
66
+ // CRC validation
67
+ if (!checkCRC([buf[0], buf[1]], buf[2]) ||
68
+ !checkCRC([buf[3], buf[4]], buf[5])) {
69
+
70
+ node.status({fill:"yellow",shape:"ring",text:"CRC Fail"});
71
+ node.error("CRC Validation Failed", msg);
72
+ return;
73
+ }
74
+
75
+ const rawT = (buf[0] << 8) | buf[1];
76
+ const rawRH = (buf[3] << 8) | buf[4];
77
+
78
+ let temperature = -45 + 175 * (rawT / 65535);
79
+ const humidity = -6 + 125 * (rawRH / 65535);
80
+
81
+ if (node.unit === "F") {
82
+ temperature = (temperature * 9/5) + 32;
83
+ }
84
+
85
+ msg.payload = {
86
+ temperature: parseFloat(temperature.toFixed(2)),
87
+ humidity: parseFloat(humidity.toFixed(2)),
88
+ unit: node.unit
89
+ };
90
+
91
+ node.status({fill:"green",shape:"dot",text:"T: " + msg.payload.temperature + ", H: " + msg.payload.humidity});
92
+ node.send(msg);
93
+
94
+ } catch (err) {
95
+ node.status({fill:"red",shape:"ring",text:"Read Error"});
96
+ node.error("Read Error: " + err.message, msg);
97
+ }
98
+
99
+ }, 15);
100
+ });
101
+
102
+ node.on('close', function() {
103
+ if (bus) {
104
+ try {
105
+ bus.closeSync();
106
+ } catch (err) {}
107
+ }
108
+ });
109
+ }
110
+
111
+ RED.nodes.registerType("SHT41", SHT41Node);
112
+ }