node-red-contrib-3dm-space 0.0.8 → 1.0.2

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.
@@ -10,35 +10,99 @@
10
10
  password: { type: "password" }
11
11
  },
12
12
  label: function () {
13
- return this.name || "3DM Cloud Login";
13
+ return this.name || this.clientid || "3DM Cloud Login";
14
14
  }
15
15
  });
16
16
  </script>
17
17
 
18
18
  <script type="text/html" data-template-name="3dm-cloud-config">
19
- <div class="form-row">
20
- <label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
21
- <input type="text" id="node-config-input-name" placeholder="e.g. Main PassPort">
22
- </div>
19
+ <style>
20
+ .three-dm-login-panel {
21
+ background: #2f3544;
22
+ border-radius: 10px;
23
+ padding: 14px;
24
+ margin-bottom: 12px;
25
+ }
23
26
 
24
- <div class="form-row">
25
- <label for="node-config-input-username"><i class="fa fa-user"></i> Username</label>
26
- <input type="text" id="node-config-input-username" placeholder="Paste device username">
27
- </div>
27
+ .three-dm-login-row {
28
+ background: #3a4152;
29
+ border-radius: 8px;
30
+ padding: 10px 12px;
31
+ margin-bottom: 12px;
32
+ }
28
33
 
29
- <div class="form-row">
30
- <label for="node-config-input-password"><i class="fa fa-key"></i> Password</label>
31
- <input type="password" id="node-config-input-password" placeholder="Paste device password">
34
+ .three-dm-login-row label {
35
+ display: block;
36
+ width: 100%;
37
+ color: #69d36f;
38
+ font-weight: bold;
39
+ margin-bottom: 6px;
40
+ }
41
+
42
+ .three-dm-login-row input {
43
+ width: 100%;
44
+ box-sizing: border-box;
45
+ background: #3a4152;
46
+ color: #ffffff;
47
+ border: none;
48
+ font-size: 15px;
49
+ padding: 4px 0;
50
+ }
51
+
52
+ .three-dm-login-row input::placeholder {
53
+ color: #b8beca;
54
+ }
55
+
56
+ .three-dm-login-row input:focus {
57
+ outline: none;
58
+ box-shadow: none;
59
+ }
60
+
61
+ .three-dm-login-note {
62
+ color: #c8ccd6;
63
+ font-size: 12px;
64
+ margin-top: 4px;
65
+ }
66
+ </style>
67
+
68
+ <div class="three-dm-login-panel">
69
+ <div class="three-dm-login-row">
70
+ <label for="node-config-input-clientid">Client ID</label>
71
+ <input type="text" id="node-config-input-clientid" placeholder="Paste Client ID from 3DM.space">
72
+ </div>
73
+
74
+ <div class="three-dm-login-row">
75
+ <label for="node-config-input-username">User Name*</label>
76
+ <input type="text" id="node-config-input-username" placeholder="Paste User Name from 3DM.space">
77
+ </div>
78
+
79
+ <div class="three-dm-login-row">
80
+ <label for="node-config-input-password">Password</label>
81
+ <input type="password" id="node-config-input-password" placeholder="Paste Password from 3DM.space">
82
+ </div>
32
83
  </div>
33
84
 
34
85
  <div class="form-row">
35
- <label for="node-config-input-clientid"><i class="fa fa-id-card"></i> Client ID</label>
36
- <input type="text" id="node-config-input-clientid" placeholder="Optional, leave blank unless supplied">
86
+ <label for="node-config-input-name">
87
+ <i class="fa fa-tag"></i> Node Name
88
+ </label>
89
+ <input type="text" id="node-config-input-name" placeholder="Optional, e.g. Main PassPort">
90
+ <div class="three-dm-login-note">
91
+ This is only the name shown inside Node-RED.
92
+ </div>
37
93
  </div>
38
94
  </script>
39
95
 
40
96
  <script type="text/html" data-help-name="3dm-cloud-config">
41
97
  <p>Login details for 3DM.space SCADA Cloud.</p>
42
- <p>Give this login a friendly name, then paste the device username and password from 3DM.space.</p>
43
- <p>Client ID is optional. Leave it blank unless supplied.</p>
98
+
99
+ <p>Copy the details from 3DM.space in this order:</p>
100
+
101
+ <ol>
102
+ <li>Client ID</li>
103
+ <li>User Name*</li>
104
+ <li>Password</li>
105
+ </ol>
106
+
107
+ <p>Node Name is optional and is only used inside Node-RED.</p>
44
108
  </script>
@@ -1,192 +1,197 @@
1
- module.exports = function (RED) {
2
- "use strict";
3
-
4
- var mqtt = require("mqtt");
5
-
6
- /*
7
- * 3DM.space Cloud connection settings
8
- * Hidden from users.
9
- */
10
- var CLOUD_HOST = "3dm.space";
11
- var CLOUD_PORT = 1883;
12
- var CLOUD_URL = "mqtt://" + CLOUD_HOST + ":" + CLOUD_PORT;
13
-
14
- var MQTT_KEEPALIVE = 60;
15
- var MQTT_CLEAN_SESSION = true;
16
- var MQTT_RECONNECT_MS = 5000;
17
- var MQTT_CONNECT_TIMEOUT_MS = 15000;
18
-
19
- /*
20
- * Prevent the logs getting hammered with the same auth error every 5 seconds.
21
- */
22
- var REPEAT_ERROR_LOG_MS = 60000;
23
-
24
- function cleanText(value) {
25
- if (value === null || value === undefined) {
26
- return "";
27
- }
28
-
29
- return String(value).trim();
30
- }
31
-
32
- function ThreeDMCloudConfigNode(config) {
33
- RED.nodes.createNode(this, config);
34
-
35
- var node = this;
36
-
37
- node.name = cleanText(config.name);
38
- node.clientid = cleanText(config.clientid);
39
-
40
- node.username = node.credentials && node.credentials.username
41
- ? cleanText(node.credentials.username)
42
- : "";
43
-
44
- node.password = node.credentials && node.credentials.password
45
- ? cleanText(node.credentials.password)
46
- : "";
47
-
48
- node.connected = false;
49
- node.client = null;
50
- node.subscriptions = {};
51
-
52
- node.lastErrorText = "";
53
- node.lastErrorTime = 0;
54
-
55
- node.setMaxListeners(0);
56
-
57
- function warnOncePerMinute(text) {
58
- var now = Date.now();
59
-
60
- if (text !== node.lastErrorText || (now - node.lastErrorTime) > REPEAT_ERROR_LOG_MS) {
61
- node.lastErrorText = text;
62
- node.lastErrorTime = now;
63
- node.warn(text);
64
- }
65
- }
66
-
67
- node.connect = function () {
68
- if (!node.username) {
69
- node.connected = false;
70
- node.emit("state", false);
71
- warnOncePerMinute("3DM Cloud username is missing");
72
- return;
73
- }
74
-
75
- var options = {
76
- username: node.username,
77
- password: node.password,
78
- clean: MQTT_CLEAN_SESSION,
79
- keepalive: MQTT_KEEPALIVE,
80
- reconnectPeriod: MQTT_RECONNECT_MS,
81
- connectTimeout: MQTT_CONNECT_TIMEOUT_MS
82
- };
83
-
84
- /*
85
- * Only send Client ID if the user entered one.
86
- */
87
- if (node.clientid) {
88
- options.clientId = node.clientid;
89
- }
90
-
91
- node.client = mqtt.connect(CLOUD_URL, options);
92
-
93
- node.client.on("connect", function () {
94
- node.connected = true;
95
- node.lastErrorText = "";
96
- node.lastErrorTime = 0;
97
-
98
- node.emit("state", true);
99
-
100
- Object.keys(node.subscriptions).forEach(function (topic) {
101
- node.client.subscribe(topic, node.subscriptions[topic], function (err) {
102
- if (err) {
103
- warnOncePerMinute("3DM Cloud subscribe failed: " + (err.message || err));
104
- }
105
- });
106
- });
107
- });
108
-
109
- node.client.on("reconnect", function () {
110
- node.emit("reconnect");
111
- });
112
-
113
- node.client.on("offline", function () {
114
- node.connected = false;
115
- node.emit("state", false);
116
- });
117
-
118
- node.client.on("close", function () {
119
- node.connected = false;
120
- node.emit("state", false);
121
- });
122
-
123
- node.client.on("error", function (err) {
124
- node.connected = false;
125
- node.emit("state", false);
126
-
127
- var text = err && err.message ? err.message : String(err);
128
-
129
- if (text.indexOf("Not authorized") !== -1 || text.indexOf("not authorized") !== -1) {
130
- warnOncePerMinute("3DM Cloud login rejected. Check username, password, and Client ID.");
131
- } else {
132
- warnOncePerMinute("3DM Cloud connection error: " + text);
133
- }
134
- });
135
-
136
- node.client.on("message", function (topic, payload) {
137
- node.emit("cloud-message", topic, payload);
138
- });
139
- };
140
-
141
- node.publish = function (topic, payload, options, callback) {
142
- if (!node.client || !node.connected) {
143
- var err = new Error("3DM Cloud is not connected");
144
-
145
- if (callback) {
146
- callback(err);
147
- }
148
-
149
- return;
150
- }
151
-
152
- var data = payload;
153
-
154
- if (typeof data !== "string" && !Buffer.isBuffer(data)) {
155
- data = JSON.stringify(data);
156
- }
157
-
158
- node.client.publish(topic, data, options || {}, callback);
159
- };
160
-
161
- node.subscribe = function (topic, options, callback) {
162
- node.subscriptions[topic] = options || {};
163
-
164
- if (node.client && node.connected) {
165
- node.client.subscribe(topic, options || {}, callback);
166
- } else if (callback) {
167
- callback();
168
- }
169
- };
170
-
171
- node.on("close", function (removed, done) {
172
- node.connected = false;
173
-
174
- if (node.client) {
175
- node.client.end(true, function () {
176
- done();
177
- });
178
- } else {
179
- done();
180
- }
181
- });
182
-
183
- node.connect();
184
- }
185
-
186
- RED.nodes.registerType("3dm-cloud-config", ThreeDMCloudConfigNode, {
187
- credentials: {
188
- username: { type: "text" },
189
- password: { type: "password" }
190
- }
191
- });
192
- };
1
+ module.exports = function (RED) {
2
+ "use strict";
3
+
4
+ var mqtt = require("mqtt");
5
+
6
+ /*
7
+ * Hidden 3DM.space Cloud connection settings.
8
+ */
9
+ var CLOUD_HOST = "3dm.space";
10
+ var CLOUD_PORT = 1883;
11
+ var CLOUD_URL = "mqtt://" + CLOUD_HOST + ":" + CLOUD_PORT;
12
+
13
+ var MQTT_KEEPALIVE = 60;
14
+ var MQTT_CLEAN_SESSION = true;
15
+ var MQTT_RECONNECT_MS = 5000;
16
+ var MQTT_CONNECT_TIMEOUT_MS = 15000;
17
+
18
+ /*
19
+ * Prevent the same connection error being logged every few seconds.
20
+ */
21
+ var REPEAT_ERROR_LOG_MS = 60000;
22
+
23
+ function cleanText(value) {
24
+ if (value === null || value === undefined) {
25
+ return "";
26
+ }
27
+
28
+ return String(value).trim();
29
+ }
30
+
31
+ function ThreeDMCloudConfigNode(config) {
32
+ RED.nodes.createNode(this, config);
33
+
34
+ var node = this;
35
+
36
+ node.name = cleanText(config.name);
37
+ node.clientid = cleanText(config.clientid);
38
+
39
+ node.username = node.credentials && node.credentials.username
40
+ ? cleanText(node.credentials.username)
41
+ : "";
42
+
43
+ node.password = node.credentials && node.credentials.password
44
+ ? cleanText(node.credentials.password)
45
+ : "";
46
+
47
+ node.connected = false;
48
+ node.client = null;
49
+ node.subscriptions = {};
50
+
51
+ node.lastErrorText = "";
52
+ node.lastErrorTime = 0;
53
+
54
+ node.setMaxListeners(0);
55
+
56
+ function warnOncePerMinute(text) {
57
+ var now = Date.now();
58
+
59
+ if (text !== node.lastErrorText || (now - node.lastErrorTime) > REPEAT_ERROR_LOG_MS) {
60
+ node.lastErrorText = text;
61
+ node.lastErrorTime = now;
62
+ node.warn(text);
63
+ }
64
+ }
65
+
66
+ node.connect = function () {
67
+ if (!node.username) {
68
+ node.connected = false;
69
+ node.emit("state", false);
70
+ warnOncePerMinute("3DM Cloud username is missing");
71
+ return;
72
+ }
73
+
74
+ var options = {
75
+ username: node.username,
76
+ password: node.password,
77
+ clean: MQTT_CLEAN_SESSION,
78
+ keepalive: MQTT_KEEPALIVE,
79
+ reconnectPeriod: MQTT_RECONNECT_MS,
80
+ connectTimeout: MQTT_CONNECT_TIMEOUT_MS
81
+ };
82
+
83
+ /*
84
+ * Only send Client ID if the user entered one.
85
+ */
86
+ if (node.clientid) {
87
+ options.clientId = node.clientid;
88
+ }
89
+
90
+ node.client = mqtt.connect(CLOUD_URL, options);
91
+
92
+ node.client.on("connect", function () {
93
+ node.connected = true;
94
+ node.lastErrorText = "";
95
+ node.lastErrorTime = 0;
96
+
97
+ node.emit("state", true);
98
+
99
+ Object.keys(node.subscriptions).forEach(function (topic) {
100
+ node.client.subscribe(topic, node.subscriptions[topic], function (err) {
101
+ if (err) {
102
+ warnOncePerMinute("3DM Cloud subscribe failed: " + (err.message || err));
103
+ }
104
+ });
105
+ });
106
+ });
107
+
108
+ node.client.on("reconnect", function () {
109
+ node.emit("reconnect");
110
+ });
111
+
112
+ node.client.on("offline", function () {
113
+ node.connected = false;
114
+ node.emit("state", false);
115
+ });
116
+
117
+ node.client.on("close", function () {
118
+ node.connected = false;
119
+ node.emit("state", false);
120
+ });
121
+
122
+ node.client.on("error", function (err) {
123
+ node.connected = false;
124
+ node.emit("state", false);
125
+
126
+ var text = err && err.message ? err.message : String(err);
127
+
128
+ if (text.indexOf("Not authorized") !== -1 || text.indexOf("not authorized") !== -1) {
129
+ warnOncePerMinute("3DM Cloud login rejected. Check username, password, and Client ID.");
130
+ } else {
131
+ warnOncePerMinute("3DM Cloud connection error: " + text);
132
+ }
133
+ });
134
+
135
+ node.client.on("message", function (topic, payload) {
136
+ node.emit("cloud-message", topic, payload);
137
+ });
138
+ };
139
+
140
+ node.publish = function (topic, payload, options, callback) {
141
+ if (!node.client || !node.connected) {
142
+ var err = new Error("3DM Cloud is not connected");
143
+
144
+ if (callback) {
145
+ callback(err);
146
+ }
147
+
148
+ return;
149
+ }
150
+
151
+ var data = payload;
152
+
153
+ if (typeof data !== "string" && !Buffer.isBuffer(data)) {
154
+ data = JSON.stringify(data);
155
+ }
156
+
157
+ node.client.publish(topic, data, options || {}, callback);
158
+ };
159
+
160
+ node.subscribe = function (topic, options, callback) {
161
+ node.subscriptions[topic] = options || {};
162
+
163
+ if (node.client && node.connected) {
164
+ node.client.subscribe(topic, options || {}, callback);
165
+ } else if (callback) {
166
+ callback();
167
+ }
168
+ };
169
+
170
+ node.on("close", function (removed, done) {
171
+ if (typeof removed === "function") {
172
+ done = removed;
173
+ }
174
+
175
+ done = done || function () {};
176
+
177
+ node.connected = false;
178
+
179
+ if (node.client) {
180
+ node.client.end(true, function () {
181
+ done();
182
+ });
183
+ } else {
184
+ done();
185
+ }
186
+ });
187
+
188
+ node.connect();
189
+ }
190
+
191
+ RED.nodes.registerType("3dm-cloud-config", ThreeDMCloudConfigNode, {
192
+ credentials: {
193
+ username: { type: "text" },
194
+ password: { type: "password" }
195
+ }
196
+ });
197
+ };
package/3dm-cloud-in.html CHANGED
@@ -1,7 +1,7 @@
1
1
  <script type="text/javascript">
2
2
  RED.nodes.registerType("3dm-cloud-in", {
3
3
  category: "3DM.space",
4
- color: "#656565",
4
+ color: "#6D6D6D",
5
5
  defaults: {
6
6
  name: { value: "" },
7
7
  server: { value: "", type: "3dm-cloud-config", required: true },
@@ -10,7 +10,7 @@
10
10
  },
11
11
  inputs: 0,
12
12
  outputs: 1,
13
- icon: "icon.png",
13
+ icon: "font-awesome/fa-cloud-download",
14
14
  label: function () {
15
15
  return this.name || "3DM attributes in";
16
16
  },
@@ -20,22 +20,30 @@
20
20
 
21
21
  <script type="text/html" data-template-name="3dm-cloud-in">
22
22
  <div class="form-row">
23
- <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
24
- <input type="text" id="node-input-name" placeholder="3DM attributes in">
23
+ <label for="node-input-name">
24
+ <i class="fa fa-tag"></i> Node Name
25
+ </label>
26
+ <input type="text" id="node-input-name" placeholder="Optional, e.g. Pump Command In">
25
27
  </div>
26
28
 
27
29
  <div class="form-row">
28
- <label for="node-input-server"><i class="fa fa-cloud"></i> Login</label>
30
+ <label for="node-input-server">
31
+ <i class="fa fa-cloud"></i> 3DM Cloud Login
32
+ </label>
29
33
  <input type="text" id="node-input-server">
30
34
  </div>
31
35
 
32
36
  <div class="form-row">
33
- <label for="node-input-keyFilter"><i class="fa fa-filter"></i> Key Filter</label>
34
- <input type="text" id="node-input-keyFilter" placeholder="Leave blank to allow all">
37
+ <label for="node-input-keyFilter">
38
+ <i class="fa fa-filter"></i> Attribute Key
39
+ </label>
40
+ <input type="text" id="node-input-keyFilter" placeholder="Optional, e.g. pump_cmd">
35
41
  </div>
36
42
 
37
43
  <div class="form-row">
38
- <label for="node-input-outputMode"><i class="fa fa-sign-out"></i> Output</label>
44
+ <label for="node-input-outputMode">
45
+ <i class="fa fa-sign-out"></i> Output Type
46
+ </label>
39
47
  <select id="node-input-outputMode" style="width:70%;">
40
48
  <option value="value">Value only</option>
41
49
  <option value="object">Object with key</option>
@@ -44,15 +52,20 @@
44
52
  </script>
45
53
 
46
54
  <script type="text/html" data-help-name="3dm-cloud-in">
47
- <p>Receives cloud attribute updates from 3DM.space.</p>
55
+ <p>Receives attribute updates from 3DM.space SCADA Cloud.</p>
48
56
 
49
- <p>If Key Filter is blank, all incoming attributes pass through.</p>
57
+ <p>Use this node when 3DM.space needs to send a value down to Node-RED, such as a pump command, mode change, reset command, or setpoint.</p>
50
58
 
51
- <p>If Key Filter is set to <code>pump_cmd</code>, only messages containing <code>pump_cmd</code> are passed through.</p>
59
+ <p><b>Attribute Key</b> is optional.</p>
60
+
61
+ <p>If Attribute Key is blank, all received attributes are passed through.</p>
62
+
63
+ <p>If Attribute Key is set to <code>pump_cmd</code>, only incoming messages containing <code>pump_cmd</code> are passed through.</p>
64
+
65
+ <p><b>Output Type</b> controls what the node sends:</p>
52
66
 
53
- <p>Output modes:</p>
54
67
  <ul>
55
- <li><b>Value only</b>: outputs just the selected value.</li>
56
- <li><b>Object with key</b>: outputs a JSON object containing the selected key.</li>
68
+ <li><b>Value only</b>: outputs only the selected value.</li>
69
+ <li><b>Object with key</b>: outputs a JSON object containing the selected key and value.</li>
57
70
  </ul>
58
71
  </script>