1
+ /*
2
+ Copyright 2023, François Lecoufle
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ this software and associated documentation files(the "Software"), to deal in
6
+ the Software without restriction, including without limitation the rights to
7
+ use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies of
8
+ the Software, and to permit persons to whom the Software is furnished to do so,
9
+ subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR
17
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ */
21
+
22
+ "use strict" ;
23
+
24
+ module . exports = function ( RED ) {
25
+ const got = require ( 'got' ) ;
26
+
27
+ const nodeStatus = {
28
+ WAIT : "Waiting..." ,
29
+ FETCH : "Fetching..." ,
30
+ OK : "OK" ,
31
+ ERROR : "Error"
32
+ } ;
33
+
34
+ function LinkyMetering ( config ) {
35
+ RED . nodes . createNode ( this , config ) ;
36
+ var node = this ;
37
+
38
+ node . on ( 'input' , function ( msg , send , done ) {
39
+ const _p = {
40
+ "type" : msg . payload . type || '' ,
41
+ "token" : msg . payload . token || node . credentials . token || '' ,
42
+ "prm" : msg . payload . prm || node . credentials . prm || '' ,
43
+ "start" : msg . payload . start || '' ,
44
+ "end" : msg . payload . end || '' ,
45
+ "options_retry_limit" : msg . payload . options_retry_limit || 2 ,
46
+ "options_timeout_lookup" : msg . payload . options_timeout_lookup || 500 ,
47
+ "options_timeout_connect" : msg . payload . options_timeout_connect || 500 ,
48
+ "options_timeout_secureconnect" : msg . payload . options_timeout_secureconnect || 500 ,
49
+ "options_timeout_socket" : msg . payload . options_timeout_socket || 5000 ,
50
+ "options_timeout_send" : msg . payload . options_timeout_send || 10000 ,
51
+ "options_timeout_response" : msg . payload . options_timeout_response || 10000 ,
52
+ "random_delay" : msg . payload . random_delay || 5000 ,
53
+ "endpoint" : msg . payload . endpoint || 'https://conso.boris.sh/api/'
54
+ }
55
+
56
+ const options = {
57
+ headers : {
58
+ 'Authorization' : `bearer ${ _p . token } ` ,
59
+ 'User-Agent' : 'github.com/flecoufle/node-red-linky'
60
+ } ,
61
+ retry : {
62
+ limit : _p . options_retry_limit ,
63
+ errorCodes : [
64
+ 'ETIMEDOUT'
65
+ ] ,
66
+ } ,
67
+ timeout : {
68
+ lookup : _p . options_timeout_lookup ,
69
+ connect : _p . options_timeout_connect ,
70
+ secureConnect : _p . options_timeout_secureconnect ,
71
+ socket : _p . options_timeout_socket ,
72
+ send : _p . options_timeout_send ,
73
+ response : _p . options_timeout_response
74
+ }
75
+ } ;
76
+
77
+ _p . token || node . error ( 'token not set' ) ;
78
+ _p . prm || node . error ( 'PRM not set' ) ;
79
+ _p . start || node . error ( 'start not set' ) ;
80
+ _p . end || node . error ( 'end not set' ) ;
81
+
82
+ let wait = Math . floor ( Math . random ( ) * _p . random_delay ) ;
83
+ let msg_wait = nodeStatus . WAIT + Math . ceil ( wait / 1000 ) + 's' ;
84
+ node . status ( { fill : 'grey' , shape : 'ring' , text : msg_wait } ) ;
85
+ setTimeout ( ( ) => {
86
+ node . status ( { fill : 'grey' , shape : 'ring' , text : nodeStatus . FETCH } ) ;
87
+ let request = `${ _p . endpoint } ${ _p . type } ?prm=${ _p . prm } &start=${ _p . start } &end=${ _p . end } ` ;
88
+ got ( request , options )
89
+ . then ( res => {
90
+ if ( res . statusCode == '200' ) {
91
+ try {
92
+ msg . payload = JSON . parse ( res . body ) ;
93
+ node . send ( msg ) ;
94
+ node . status ( { fill : 'blue' , shape : 'dot' , text : 'OK' } ) ;
95
+
96
+ setTimeout ( ( ) => {
97
+ node . status ( { } ) ;
98
+ if ( done ) {
99
+ done ( ) ;
100
+ }
101
+ } , 1000 ) ;
102
+ } catch ( err ) {
103
+ node . status ( { fill : 'red' , shape : 'ring' , text : 'error' } ) ;
104
+ if ( done ) {
105
+ done ( 'parsing ' + err ) ;
106
+ } else {
107
+ node . error ( 'parsing ' + err , msg ) ;
108
+ }
109
+ }
110
+ } else {
111
+ node . status ( { fill : 'red' , shape : 'ring' , text : res . statusCode } ) ;
112
+ if ( done ) {
113
+ done ( `statusCode ${ res . statusCode } ` ) ;
114
+ } else {
115
+ node . error ( `statusCode ${ res . statusCode } ` , msg ) ;
116
+ }
117
+ }
118
+ } )
119
+ . catch ( err => {
120
+ if ( err instanceof got . HTTPError ) {
121
+ node . warn ( request ) ;
122
+ if ( err . response . statusCode == '400' ) {
123
+ node . error ( 'Bad request: Check token and PRM' ) ;
124
+ } else if ( err . response . statusCode == '401' ) {
125
+ node . error ( 'Unauthorized: Check token and PRM' ) ;
126
+ } else if ( err . response . statusCode == '403' ) {
127
+ node . error ( 'Forbidden: Check token and PRM' ) ;
128
+ } else if ( err . response . statusCode == '404' ) {
129
+ node . error ( 'Not found: Check token and PRM' ) ;
130
+ }
131
+ } else if ( err instanceof got . TimeoutError ) {
132
+ node . warn ( 'TimeoutError: adjust payload.options_timeout_*' ) ;
133
+ }
134
+ node . status ( { fill : 'red' , shape : 'ring' , text : 'error' } ) ;
135
+ if ( done ) {
136
+ done ( err ) ;
137
+ } else {
138
+ node . error ( err , msg ) ;
139
+ }
140
+ } ) ;
141
+ } , wait ) ;
142
+ } )
143
+ }
144
+ RED . nodes . registerType ( "linky-api" , LinkyMetering , {
145
+ credentials : {
146
+ prm : {
147
+ type : "text"
148
+ } ,
149
+ token : {
150
+ type : "password"
151
+ }
152
+ }
153
+ } ) ;
154
+ }
0 commit comments