smart-nodes 0.3.15 → 0.3.26
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/LICENSE.md +1 -1
- package/README.md +51 -21
- package/central/central.js +12 -8
- package/compare/compare.html +48 -0
- package/compare/compare.js +36 -20
- package/counter/counter.html +104 -0
- package/counter/counter.js +120 -0
- package/delay/delay.js +39 -37
- package/examples/heating-curve.json +480 -0
- package/examples/heating-curve.png +0 -0
- package/examples/mixing-valve.json +622 -0
- package/examples/mixing-valve.png +0 -0
- package/forwarder/forwarder.js +28 -26
- package/heating-curve/heating-curve.html +348 -0
- package/heating-curve/heating-curve.js +134 -0
- package/hysteresis/hysteresis.js +58 -45
- package/icons/smart_counter.png +0 -0
- package/light-control/light-control.js +42 -30
- package/logic/logic.html +18 -2
- package/logic/logic.js +25 -23
- package/long-press-control/long-press-control.js +6 -4
- package/mixing-valve/mixing-valve.html +188 -0
- package/mixing-valve/mixing-valve.js +357 -0
- package/multi-press-control/multi-press-control.js +3 -1
- package/package.json +8 -1
- package/scene-control/scene-control.js +37 -27
- package/scheduler/scheduler.js +27 -25
- package/shutter-complex-control/shutter-complex-control.js +64 -62
- package/shutter-control/shutter-control.js +41 -37
- package/smart_helper.js +156 -5
- package/statistic/statistic.js +30 -28
- package/text-exec/text-exec.js +14 -6
- package/LICENSE +0 -21
package/smart_helper.js
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
module.exports = {
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* This functions converts a value into the given type.
|
|
5
|
+
* If a type is unknown the NodeRed function is used for conversation.
|
|
6
|
+
*
|
|
7
|
+
* Known types are:
|
|
8
|
+
* - null, which will return always null
|
|
9
|
+
*/
|
|
2
10
|
evaluateNodeProperty(RED, value, type)
|
|
3
11
|
{
|
|
4
12
|
try
|
|
5
13
|
{
|
|
6
14
|
switch (type)
|
|
7
15
|
{
|
|
16
|
+
case null:
|
|
8
17
|
case "null":
|
|
9
18
|
return null;
|
|
10
19
|
|
|
@@ -14,10 +23,19 @@ module.exports = {
|
|
|
14
23
|
}
|
|
15
24
|
catch (error)
|
|
16
25
|
{
|
|
26
|
+
console.error(value);
|
|
27
|
+
console.error(type);
|
|
17
28
|
console.error(error);
|
|
18
29
|
return null;
|
|
19
30
|
}
|
|
20
31
|
},
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* This functions returns the name that is set in a smart topic.
|
|
35
|
+
* The format is name#number
|
|
36
|
+
*
|
|
37
|
+
* If no name is found null is returned.
|
|
38
|
+
*/
|
|
21
39
|
getTopicName(topic)
|
|
22
40
|
{
|
|
23
41
|
if (typeof topic == "undefined" || topic == null || topic == "" || typeof topic != "string")
|
|
@@ -28,6 +46,13 @@ module.exports = {
|
|
|
28
46
|
|
|
29
47
|
return topic.substring(0, topic.indexOf("#"));
|
|
30
48
|
},
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* This functions returns the number that is set in a smart topic.
|
|
52
|
+
* The format is name#number
|
|
53
|
+
*
|
|
54
|
+
* If no number is found null is returned.
|
|
55
|
+
*/
|
|
31
56
|
getTopicNumber(topic)
|
|
32
57
|
{
|
|
33
58
|
if (typeof topic == "undefined" || topic == null || topic == "")
|
|
@@ -45,6 +70,16 @@ module.exports = {
|
|
|
45
70
|
|
|
46
71
|
return result;
|
|
47
72
|
},
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* This function converts the given value with the given unit into milli seconds.
|
|
76
|
+
*
|
|
77
|
+
* This units are available:
|
|
78
|
+
* - ms
|
|
79
|
+
* - s or sec
|
|
80
|
+
* - m or min
|
|
81
|
+
* - h
|
|
82
|
+
*/
|
|
48
83
|
getTimeInMs(value, unit)
|
|
49
84
|
{
|
|
50
85
|
value = parseInt(value, 10);
|
|
@@ -67,6 +102,16 @@ module.exports = {
|
|
|
67
102
|
return value * 3600 * 1000;
|
|
68
103
|
}
|
|
69
104
|
},
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* This function converts the given value with the given unit into seconds.
|
|
108
|
+
*
|
|
109
|
+
* This units are available:
|
|
110
|
+
* - ms
|
|
111
|
+
* - s or sec
|
|
112
|
+
* - m or min
|
|
113
|
+
* - h
|
|
114
|
+
*/
|
|
70
115
|
getTimeInS(value, unit)
|
|
71
116
|
{
|
|
72
117
|
value = parseInt(value, 10);
|
|
@@ -88,6 +133,19 @@ module.exports = {
|
|
|
88
133
|
return value * 3600;
|
|
89
134
|
}
|
|
90
135
|
},
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* This function is parsing the given value and returns the time in milli seconds.
|
|
139
|
+
*
|
|
140
|
+
* The string can have a number followed by an optional unit.
|
|
141
|
+
* If no unit is given, the default is ms.
|
|
142
|
+
*
|
|
143
|
+
* This units are available:
|
|
144
|
+
* - ms
|
|
145
|
+
* - s or sec
|
|
146
|
+
* - m or min
|
|
147
|
+
* - h
|
|
148
|
+
*/
|
|
91
149
|
getTimeInMsFromString(value)
|
|
92
150
|
{
|
|
93
151
|
// default in ms
|
|
@@ -115,17 +173,63 @@ module.exports = {
|
|
|
115
173
|
// Something went wrong
|
|
116
174
|
return 0;
|
|
117
175
|
},
|
|
118
|
-
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* This function returns a string that represents the remaining time given in time_ms.
|
|
179
|
+
* If a timeConcatWord it set, it returns also this word including the target time.
|
|
180
|
+
*/
|
|
181
|
+
formatMsToStatus(time_ms, time_concat_word = null)
|
|
119
182
|
{
|
|
120
183
|
let result = "";
|
|
121
184
|
|
|
122
|
-
if (
|
|
185
|
+
if (time_concat_word)
|
|
123
186
|
{
|
|
124
|
-
let date = (new Date(Date.now() +
|
|
125
|
-
result = " " +
|
|
187
|
+
let date = (new Date(Date.now() + time_ms));
|
|
188
|
+
result = " " + time_concat_word + " " + date.getHours() + ":" + ("" + date.getMinutes()).padStart(2, "0") + ":" + ("" + date.getSeconds()).padStart(2, "0");
|
|
126
189
|
}
|
|
127
190
|
|
|
128
|
-
if (
|
|
191
|
+
if (time_ms == 0)
|
|
192
|
+
return "0:00" + result;
|
|
193
|
+
|
|
194
|
+
// value in sec
|
|
195
|
+
time_ms = parseInt(time_ms / 1000, 10);
|
|
196
|
+
result = (time_ms % 60) + result;
|
|
197
|
+
if (time_ms % 60 < 10)
|
|
198
|
+
result = "0" + result;
|
|
199
|
+
|
|
200
|
+
// value in min
|
|
201
|
+
time_ms = parseInt(time_ms / 60, 10);
|
|
202
|
+
result = (time_ms % 60) + ":" + result;
|
|
203
|
+
if (time_ms % 60 < 10)
|
|
204
|
+
result = "0" + result;
|
|
205
|
+
|
|
206
|
+
// value in hour
|
|
207
|
+
time_ms = parseInt(time_ms / 60, 10);
|
|
208
|
+
result = (time_ms % 24) + ":" + result;
|
|
209
|
+
if (time_ms % 24 < 10)
|
|
210
|
+
result = "0" + result;
|
|
211
|
+
|
|
212
|
+
// value in days
|
|
213
|
+
time_ms = parseInt(time_ms / 24, 10);
|
|
214
|
+
if (time_ms > 0)
|
|
215
|
+
result = time_ms + "." + result;
|
|
216
|
+
|
|
217
|
+
return result;
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* This function returns a string that represents the remaining time until the given date.
|
|
222
|
+
* If a time_concat_word it set, it returns also this word including the target time.
|
|
223
|
+
*/
|
|
224
|
+
formatDateToStatus(date, time_concat_word = null)
|
|
225
|
+
{
|
|
226
|
+
let result = "";
|
|
227
|
+
|
|
228
|
+
if (time_concat_word)
|
|
229
|
+
result = " " + time_concat_word + " " + date.getHours() + ":" + ("" + date.getMinutes()).padStart(2, "0") + ":" + ("" + date.getSeconds()).padStart(2, "0");
|
|
230
|
+
|
|
231
|
+
let value = date - new Date();
|
|
232
|
+
if (value <= 0)
|
|
129
233
|
return "0:00" + result;
|
|
130
234
|
|
|
131
235
|
// value in sec
|
|
@@ -152,5 +256,52 @@ module.exports = {
|
|
|
152
256
|
result = value + "." + result;
|
|
153
257
|
|
|
154
258
|
return result;
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* This function converts a number from a specific range into another range
|
|
263
|
+
* E.g:
|
|
264
|
+
* Input range = 0 to 10
|
|
265
|
+
* Target range = 100 to 200
|
|
266
|
+
*
|
|
267
|
+
* current number = 0
|
|
268
|
+
* scale(5, 0, 10, 100, 200) => 100
|
|
269
|
+
*
|
|
270
|
+
* current number = 5
|
|
271
|
+
* scale(5, 0, 10, 100, 200) => 150
|
|
272
|
+
*
|
|
273
|
+
* current number = 10
|
|
274
|
+
* scale(5, 0, 10, 100, 200) => 200
|
|
275
|
+
*
|
|
276
|
+
* current number = 20 (out of input range!)
|
|
277
|
+
* scale(5, 0, 10, 100, 200) => 300 (out of output range!)
|
|
278
|
+
*/
|
|
279
|
+
scale(number, inMin, inMax, outMin, outMax)
|
|
280
|
+
{
|
|
281
|
+
return (number - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
|
|
282
|
+
},
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* This function returns a short string that represents the current date and time.
|
|
286
|
+
*/
|
|
287
|
+
getCurrentDateForStatus()
|
|
288
|
+
{
|
|
289
|
+
let date = new Date();
|
|
290
|
+
return date.getDate().toString().padStart(2, "0") + "." +
|
|
291
|
+
(date.getMonth() + 1).toString().padStart(2, "0") + " - " +
|
|
292
|
+
date.getHours().toString().padStart(2, "0") + ":" +
|
|
293
|
+
date.getMinutes().toString().padStart(2, "0") + ":" +
|
|
294
|
+
date.getSeconds().toString().padStart(2, "0");
|
|
295
|
+
},
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* This function returns a short string that represents the current time.
|
|
299
|
+
*/
|
|
300
|
+
getCurrentTimeForStatus()
|
|
301
|
+
{
|
|
302
|
+
let date = new Date();
|
|
303
|
+
return date.getHours().toString().padStart(2, "0") + ":" +
|
|
304
|
+
date.getMinutes().toString().padStart(2, "0") + ":" +
|
|
305
|
+
date.getSeconds().toString().padStart(2, "0");
|
|
155
306
|
}
|
|
156
307
|
};
|
package/statistic/statistic.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
module.exports = function (RED)
|
|
2
2
|
{
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
3
5
|
function StatisticNode(config)
|
|
4
6
|
{
|
|
5
7
|
const node = this;
|
|
6
8
|
RED.nodes.createNode(node, config);
|
|
7
9
|
|
|
8
|
-
const
|
|
10
|
+
const smart_context = require("../persistence.js")(RED);
|
|
9
11
|
const helper = require("../smart_helper.js");
|
|
10
12
|
|
|
11
|
-
var
|
|
13
|
+
var node_settings = {
|
|
12
14
|
values: [],
|
|
13
15
|
lastMessage: null,
|
|
14
16
|
};
|
|
@@ -16,12 +18,12 @@ module.exports = function (RED)
|
|
|
16
18
|
if (config.save_state)
|
|
17
19
|
{
|
|
18
20
|
// load old saved values
|
|
19
|
-
|
|
21
|
+
node_settings = Object.assign(node_settings, smart_context.get(node.id));
|
|
20
22
|
}
|
|
21
23
|
else
|
|
22
24
|
{
|
|
23
25
|
// delete old saved values
|
|
24
|
-
|
|
26
|
+
smart_context.del(node.id);
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
// dynamic config
|
|
@@ -39,15 +41,15 @@ module.exports = function (RED)
|
|
|
39
41
|
return;
|
|
40
42
|
}
|
|
41
43
|
|
|
42
|
-
let
|
|
43
|
-
//
|
|
44
|
-
|
|
44
|
+
let real_topic = helper.getTopicNumber(msg.topic) || 0;
|
|
45
|
+
// real_topic should be sended with 1-based, but internally 0-based is needed
|
|
46
|
+
real_topic--;
|
|
45
47
|
|
|
46
48
|
if (operation === "MOV_AVG")
|
|
47
49
|
{
|
|
48
|
-
|
|
49
|
-
if (
|
|
50
|
-
|
|
50
|
+
node_settings.values.push(parseFloat(msg.payload));
|
|
51
|
+
if (node_settings.values.length > count)
|
|
52
|
+
node_settings.values.splice(0, 1);
|
|
51
53
|
}
|
|
52
54
|
else if (operation != "ABS")
|
|
53
55
|
{
|
|
@@ -56,7 +58,7 @@ module.exports = function (RED)
|
|
|
56
58
|
node.error("Topic not set");
|
|
57
59
|
return;
|
|
58
60
|
}
|
|
59
|
-
|
|
61
|
+
node_settings.values[real_topic] = parseFloat(msg.payload);
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
msg = getResult(msg);
|
|
@@ -64,12 +66,12 @@ module.exports = function (RED)
|
|
|
64
66
|
|
|
65
67
|
if (msg)
|
|
66
68
|
{
|
|
67
|
-
|
|
69
|
+
node_settings.lastMessage = msg;
|
|
68
70
|
node.send(msg);
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
if (config.save_state)
|
|
72
|
-
|
|
74
|
+
smart_context.set(node.id, node_settings);
|
|
73
75
|
});
|
|
74
76
|
|
|
75
77
|
node.on("close", function ()
|
|
@@ -81,7 +83,7 @@ module.exports = function (RED)
|
|
|
81
83
|
let length;
|
|
82
84
|
if (operation !== "MOV_AVG" && operation !== "ABS")
|
|
83
85
|
{
|
|
84
|
-
length = Object.entries(
|
|
86
|
+
length = Object.entries(node_settings.values).length;
|
|
85
87
|
if (length == 0)
|
|
86
88
|
return null;
|
|
87
89
|
}
|
|
@@ -90,7 +92,7 @@ module.exports = function (RED)
|
|
|
90
92
|
switch (operation)
|
|
91
93
|
{
|
|
92
94
|
case "MIN":
|
|
93
|
-
result = Object.entries(
|
|
95
|
+
result = Object.entries(node_settings.values).reduce((v1, v2) =>
|
|
94
96
|
{
|
|
95
97
|
if (v1[1] <= v2[1])
|
|
96
98
|
return v1;
|
|
@@ -99,7 +101,7 @@ module.exports = function (RED)
|
|
|
99
101
|
break;
|
|
100
102
|
|
|
101
103
|
case "MAX":
|
|
102
|
-
result = Object.entries(
|
|
104
|
+
result = Object.entries(node_settings.values).reduce((v1, v2) =>
|
|
103
105
|
{
|
|
104
106
|
if (v1[1] >= v2[1])
|
|
105
107
|
return v1;
|
|
@@ -108,18 +110,18 @@ module.exports = function (RED)
|
|
|
108
110
|
break;
|
|
109
111
|
|
|
110
112
|
case "SUM":
|
|
111
|
-
result = Object.entries(
|
|
113
|
+
result = Object.entries(node_settings.values).reduce((v1, v2) =>
|
|
112
114
|
{
|
|
113
115
|
return [null, v1[1] + v2[1]];
|
|
114
116
|
});
|
|
115
117
|
break;
|
|
116
118
|
|
|
117
119
|
case "DIFF":
|
|
118
|
-
if (
|
|
120
|
+
if (node_settings.values.length >= 2)
|
|
119
121
|
{
|
|
120
122
|
result = [
|
|
121
123
|
null,
|
|
122
|
-
|
|
124
|
+
node_settings.values[0] - node_settings.values[1]
|
|
123
125
|
];
|
|
124
126
|
}
|
|
125
127
|
break;
|
|
@@ -129,17 +131,17 @@ module.exports = function (RED)
|
|
|
129
131
|
return msg;
|
|
130
132
|
|
|
131
133
|
case "ABS_DIFF":
|
|
132
|
-
if (
|
|
134
|
+
if (node_settings.values.length >= 2)
|
|
133
135
|
{
|
|
134
136
|
result = [
|
|
135
137
|
null,
|
|
136
|
-
Math.abs(
|
|
138
|
+
Math.abs(node_settings.values[0] - node_settings.values[1])
|
|
137
139
|
];
|
|
138
140
|
}
|
|
139
141
|
break;
|
|
140
142
|
|
|
141
143
|
case "AVG":
|
|
142
|
-
let value = Object.entries(
|
|
144
|
+
let value = Object.entries(node_settings.values).reduce((v1, v2) =>
|
|
143
145
|
{
|
|
144
146
|
return [null, v1[1] + v2[1]];
|
|
145
147
|
});
|
|
@@ -149,7 +151,7 @@ module.exports = function (RED)
|
|
|
149
151
|
};
|
|
150
152
|
|
|
151
153
|
case "MOV_AVG":
|
|
152
|
-
msg.payload =
|
|
154
|
+
msg.payload = node_settings.values.reduce((v1, v2) => v1 + v2) / node_settings.values.length;
|
|
153
155
|
return msg;
|
|
154
156
|
}
|
|
155
157
|
|
|
@@ -176,20 +178,20 @@ module.exports = function (RED)
|
|
|
176
178
|
return;
|
|
177
179
|
|
|
178
180
|
if (operation === "ABS")
|
|
179
|
-
node.status({ fill: "yellow", shape: "ring", text:
|
|
181
|
+
node.status({ fill: "yellow", shape: "ring", text: helper.getCurrentTimeForStatus() + ": " + operation + " => " + msg.payload });
|
|
180
182
|
else
|
|
181
|
-
node.status({ fill: "yellow", shape: "ring", text:
|
|
183
|
+
node.status({ fill: "yellow", shape: "ring", text: helper.getCurrentTimeForStatus() + ": " + operation + "(" + Object.entries(node_settings.values).map(v => v[1]).join(",") + ") => " + msg.payload });
|
|
182
184
|
}
|
|
183
185
|
|
|
184
|
-
if (config.save_state && config.resend_on_start &&
|
|
186
|
+
if (config.save_state && config.resend_on_start && node_settings.lastMessage != null)
|
|
185
187
|
{
|
|
186
188
|
setTimeout(() =>
|
|
187
189
|
{
|
|
188
|
-
node.send(
|
|
190
|
+
node.send(node_settings.lastMessage);
|
|
189
191
|
}, 10000);
|
|
190
192
|
}
|
|
191
193
|
|
|
192
|
-
setStatus(
|
|
194
|
+
setStatus(node_settings.lastMessage);
|
|
193
195
|
}
|
|
194
196
|
|
|
195
197
|
RED.nodes.registerType("smart_statistic", StatisticNode);
|
package/text-exec/text-exec.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
module.exports = function (RED)
|
|
2
2
|
{
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
3
5
|
function TextExecNode(config)
|
|
4
6
|
{
|
|
5
7
|
const node = this;
|
|
@@ -37,7 +39,7 @@ module.exports = function (RED)
|
|
|
37
39
|
return;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
node.status({ fill: "green", shape: "dot", text:
|
|
42
|
+
node.status({ fill: "green", shape: "dot", text: helper.getCurrentTimeForStatus() + ": " + message });
|
|
41
43
|
log = {
|
|
42
44
|
lookup: lookup,
|
|
43
45
|
actions: []
|
|
@@ -286,12 +288,12 @@ module.exports = function (RED)
|
|
|
286
288
|
{
|
|
287
289
|
if (action != null && affectedNodes.length > 0)
|
|
288
290
|
{
|
|
289
|
-
for (const
|
|
291
|
+
for (const targetNode of affectedNodes)
|
|
290
292
|
{
|
|
291
|
-
if (mode == "light" && !["smart_light-control", "smart_scene-control"].includes(
|
|
293
|
+
if (mode == "light" && !["smart_light-control", "smart_scene-control"].includes(targetNode.type))
|
|
292
294
|
continue;
|
|
293
295
|
|
|
294
|
-
if (mode == "shutter" && !["smart_shutter-control", "smart_shutter-complex-control"].includes(
|
|
296
|
+
if (mode == "shutter" && !["smart_shutter-control", "smart_shutter-complex-control"].includes(targetNode.type))
|
|
295
297
|
continue;
|
|
296
298
|
|
|
297
299
|
// node.log("Notify node " + node.id);
|
|
@@ -299,11 +301,17 @@ module.exports = function (RED)
|
|
|
299
301
|
{
|
|
300
302
|
// console.log({ "topic": action, "payload": number });
|
|
301
303
|
if (number != null)
|
|
302
|
-
|
|
304
|
+
{
|
|
305
|
+
// console.log(node.id + " -> " + targetNode.id);
|
|
306
|
+
// console.log({ "topic": action, "payload": number });
|
|
307
|
+
RED.events.emit("node:" + targetNode.id, { "topic": action, "payload": number });
|
|
308
|
+
}
|
|
303
309
|
}
|
|
304
310
|
else
|
|
305
311
|
{
|
|
306
|
-
|
|
312
|
+
// console.log(node.id + " -> " + targetNode.id);
|
|
313
|
+
// console.log({ "topic": action });
|
|
314
|
+
RED.events.emit("node:" + targetNode.id, { "topic": action });
|
|
307
315
|
}
|
|
308
316
|
}
|
|
309
317
|
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2024 BergenSoft
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|