@flecoufle/node-red-linky 1.3.0 → 1.4.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.
- package/CHANGELOG.md +9 -0
- package/README.md +106 -28
- package/linky/linky.html +5 -3
- package/linky/linky.js +217 -77
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -1,44 +1,122 @@
|
|
|
1
1
|
# node-red-linky
|
|
2
|
-
Retreive linky power consumption and production from <a href="https://github.com/bokub/conso-api#readme">bokub/conso-api</a>
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
Package Node-RED **Linky** pour récupérer les données de **consommation** et **production** d'énergie via l'API Enedis.
|
|
5
4
|
|
|
6
|
-
##
|
|
7
|
-
- <b>Récupération de la consommation énergétique quotidienne</b>: <code>daily_consumption</code>
|
|
5
|
+
## Installation
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
Via la palette l'éditeur: @flecoufle/node-red-linky
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
ou via npm:
|
|
10
|
+
```bash
|
|
11
|
+
npm install @flecoufle/node-red-linky
|
|
12
|
+
```
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
## Prérequis
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
- **Node.js**: >= 14.0.0
|
|
17
|
+
- **Node-RED**: >= 0.17.0
|
|
18
|
+
- **Dépendances**:
|
|
19
|
+
- `got` 13.0.0
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
## Mise en oeuvre
|
|
22
|
+
- Obtenir un accès API (le token) à votre compteur: [API Conso](https://github.com/bokub/conso-api#readme) vous redirigera vers [https://conso.boris.sh](https://conso.boris.sh/)
|
|
23
|
+
- [Exemples](examples/Examples.json) d'utilisation du noeud node-red-linky une fois le noeud installé
|
|
24
|
+
- [Documentation](https://datahub-enedis.fr/services-api/data-connect/documentation/) de Enedis API Data-Connect (pour information uniquement)
|
|
25
|
+
|
|
26
|
+
## ⚠️ Important - Mise en cache des appels
|
|
23
27
|
|
|
24
|
-
|
|
28
|
+
À partir de la version **1.4.0**, le node limite les appels pour respecter les quotas de l'API et éviter le bannissement de votre IP, une mise en cache est mise en place pour permettre la continuité de service. Cette fonction est appliquée par requête unique (l'API sous-jacente elle-même renvoie déjà des données qui ne changent pas, donc cette fonction ne modifie en rien les informations retournées).
|
|
25
29
|
|
|
26
|
-
|
|
30
|
+
Pour les utilisateurs des versions antérieures: **veuillez mettre à jour** pour bénéficier de cette protection.
|
|
27
31
|
|
|
28
|
-
|
|
29
|
-
Un appel peut porter sur des données datant au maximum de 36 mois et 15 jours avant la date d’appel.
|
|
32
|
+
## Configuration et Paramètres
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
- <b>Récupération de la production énergétique quotidienne</b>: <code>daily_production</code>
|
|
34
|
+
### Paramètres obligatoires (peuvent être passés via le payload)
|
|
33
35
|
|
|
34
|
-
|
|
36
|
+
| Paramètre | Type | Description |
|
|
37
|
+
|-----------|------|-------------|
|
|
38
|
+
| `type` | string | Type de mesure (daily_consumption, daily_production, consumption_load_curve, consumption_max_power, production_load_curve) |
|
|
39
|
+
| `token` | string | Token d'authentification Enedis (via le payload ou noeud ou config) |
|
|
40
|
+
| `prm` | string | Point de livraison (numéro PRM) (via le payload ou noeud ou config)|
|
|
41
|
+
| `start` | string | Date de début (format YYYY-MM-DD) |
|
|
42
|
+
| `end` | string | Date de fin (format YYYY-MM-DD) |
|
|
35
43
|
|
|
36
|
-
|
|
44
|
+
### Paramètres optionnels (uniquement via le payload)
|
|
37
45
|
|
|
38
|
-
|
|
46
|
+
| Paramètre | Type | Défaut | Description |
|
|
47
|
+
|-----------|------|--------|-------------|
|
|
48
|
+
| `options_api_daily_limit` | number | 3 | Limite d'appels par jour pour une requête identique |
|
|
49
|
+
| `options_retry_limit` | number | 2 | Nombre de tentatives en cas d'erreur |
|
|
50
|
+
| `options_timeout_lookup` | number | 500 | Timeout lookup DNS (ms) |
|
|
51
|
+
| `options_timeout_connect` | number | 500 | Timeout connexion (ms) |
|
|
52
|
+
| `options_timeout_secureconnect` | number | 500 | Timeout connexion sécurisée (ms) |
|
|
53
|
+
| `options_timeout_socket` | number | 5000 | Timeout socket (ms) |
|
|
54
|
+
| `options_timeout_send` | number | 10000 | Timeout envoi (ms) |
|
|
55
|
+
| `options_timeout_response` | number | 10000 | Timeout réponse (ms) |
|
|
56
|
+
| `random_delay` | number | 5000 | Délai aléatoire avant exécution (ms) |
|
|
57
|
+
| `endpoint` | string | https://conso.boris.sh/api/ | Endpoint API à utiliser |
|
|
58
|
+
| `debug` | boolean | false | Afficher les détails de débogage dans les logs |
|
|
39
59
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
60
|
+
## Types d'API disponibles
|
|
61
|
+
|
|
62
|
+
### Consommation
|
|
63
|
+
|
|
64
|
+
- **`daily_consumption`** - Récupération de la consommation énergétique quotidienne
|
|
65
|
+
- Période demandée [start;end[ en Wh
|
|
66
|
+
- Historique: jusqu'à 36 mois et 15 jours avant la date d'appel
|
|
67
|
+
- Chaque valeur est datée
|
|
68
|
+
|
|
69
|
+
- **`consumption_load_curve`** - Récupération de la courbe de charge en soutirage
|
|
70
|
+
- Intervalle [start;end[
|
|
71
|
+
- Profondeur: une semaine maximum par appel
|
|
72
|
+
- Valeurs: puissances moyennes de consommation en W (intervalle par défaut 30mn)
|
|
73
|
+
- Historique: jusqu'à 24 mois et 15 jours avant la date d'appel
|
|
74
|
+
- Journées complètes de minuit à minuit (heures locales)
|
|
75
|
+
|
|
76
|
+
- **`consumption_max_power`** - Récupération de la puissance maximale soutirée quotidienne
|
|
77
|
+
- Période demandée [start;end[ en VA
|
|
78
|
+
- Historique: jusqu'à 36 mois et 15 jours avant la date d'appel
|
|
79
|
+
- Chaque valeur est datée
|
|
80
|
+
|
|
81
|
+
### Production
|
|
82
|
+
|
|
83
|
+
- **`daily_production`** - Récupération de la production énergétique quotidienne
|
|
84
|
+
- Période demandée [start;end[ en Wh
|
|
85
|
+
- Historique: jusqu'à 36 mois et 15 jours avant la date d'appel
|
|
86
|
+
- Chaque valeur est datée
|
|
87
|
+
|
|
88
|
+
- **`production_load_curve`** - Récupération de la courbe de charge de production
|
|
89
|
+
- Intervalle [start;end[
|
|
90
|
+
- Profondeur: une semaine maximum par appel
|
|
91
|
+
- Valeurs: puissances moyennes de production en W (intervalle par défaut 30mn)
|
|
92
|
+
- Historique: jusqu'à 24 mois et 15 jours avant la date d'appel
|
|
93
|
+
- Journées complètes de minuit à minuit (heures locales)
|
|
94
|
+
|
|
95
|
+
## Limitation quotidienne des appels et système de cache
|
|
96
|
+
|
|
97
|
+
Un système de limitation quotidienne est mis en place pour éviter les appels API excessifs pour une même requête identique. **Les réponses sont mises en cache**, permettant de servir les données en cache quand la limite est atteinte.
|
|
98
|
+
Cette fonctionnalité permet d'éviter le blocage d'IP, voir "Conseils d'utilisation" sur [Conso API](https://github.com/bokub/conso-api?tab=readme-ov-file#conseils-dutilisation)
|
|
99
|
+
|
|
100
|
+
### Fonctionnement de la limitation
|
|
101
|
+
|
|
102
|
+
- **Limite par défaut**: 3 appels par jour pour une requête identique
|
|
103
|
+
- **Configuration**: La limite peut être ajustée via le paramètre `options_api_daily_limit` (en cas de besoin spécifique peut être modifiée, attention au risque de bannissement)
|
|
104
|
+
- **Signature de requête**: Chaque requête est identifiée par une signature unique (hash SHA256) et **mis en cache** (cache de votre instance Node-RED dans une variable de contexte du noeud, donc reste chez vous) basée sur:
|
|
105
|
+
- Le jour de l'appel
|
|
106
|
+
- Le type d'API (`type`)
|
|
107
|
+
- Le point de livraison (`prm`)
|
|
108
|
+
- Les dates de début et fin (`start`, `end`)
|
|
109
|
+
- L'endpoint API (`endpoint`)
|
|
110
|
+
- Les configurations de timeout
|
|
111
|
+
- La limite de retry
|
|
112
|
+
|
|
113
|
+
Donc tout sauf `debug`
|
|
114
|
+
|
|
115
|
+
- **Réinitialisation automatique**: Le compteur se réinitialise automatiquement chaque jour à minuit
|
|
116
|
+
|
|
117
|
+
### Comportement avec cache
|
|
118
|
+
|
|
119
|
+
- **Appel réussi**: La réponse est automatiquement mise en cache avec la signature de la requête
|
|
120
|
+
- **Limite atteinte**:
|
|
121
|
+
- Une réponse en cache est retournée (statut du noeud: **jaune**)
|
|
122
|
+
- Si aucune réponse n'a pu être mise en cache (statut du noeud: **rouge**) cas de l'API en erreurs successives "_Daily limit of X calls for this request reached. No cache available._" sera affiché dans le log
|
package/linky/linky.html
CHANGED
|
@@ -108,17 +108,19 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
108
108
|
"prm": PRM set in payload or in node property
|
|
109
109
|
"start": start date in RFC 3339 format ("YYYY-MM-DD")
|
|
110
110
|
"end": end date in RFC 3339 format ("YYYY-MM-DD")
|
|
111
|
-
"
|
|
111
|
+
"options_api_daily_limit": daily limit of calls for identical request (default 3)
|
|
112
|
+
"options_retry_limit": got option (default 2)
|
|
112
113
|
"options_timeout_lookup": got option in ms(default 500)
|
|
113
114
|
"options_timeout_connect": got option in ms(default 500)
|
|
114
115
|
"options_timeout_secureconnect": got option in ms(default 500)
|
|
115
116
|
"options_timeout_socket": got option in ms(default 5000)
|
|
116
|
-
"options_timeout_send": got option in ms(default
|
|
117
|
-
"options_timeout_response": got option in ms(default
|
|
117
|
+
"options_timeout_send": got option in ms(default 10000)
|
|
118
|
+
"options_timeout_response": got option in ms(default 10000)
|
|
118
119
|
"random_delay": fetch after a random delay in ms (default 5000)
|
|
119
120
|
"endpoint": endpoint api (default "https://conso.boris.sh/api/")
|
|
120
121
|
"debug": debug mode (default false)
|
|
121
122
|
</code></pre></p>
|
|
123
|
+
<p><strong>🔄 Système de Cache:</strong> Les réponses réussies sont automatiquement mises en cache. Si la limite quotidienne est atteinte pour une requête identique, la réponse en cache est retournée (statut jaune) au lieu d'une erreur.</p>
|
|
122
124
|
Available types are: <code>"daily_consumption"</code>, <code>"consumption_load_curve"</code>, <code>"consumption_max_power"</code> in consumption<br/>
|
|
123
125
|
<code>"daily_production"</code>, <code>"production_load_curve"</code> in production
|
|
124
126
|
</dl>
|
package/linky/linky.js
CHANGED
|
@@ -23,14 +23,90 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
23
23
|
|
|
24
24
|
module.exports = async function (RED) {
|
|
25
25
|
const { got } = await import('got');
|
|
26
|
+
const crypto = await import('crypto');
|
|
26
27
|
|
|
27
28
|
const nodeStatus = {
|
|
28
29
|
WAIT: "Waiting...",
|
|
29
|
-
FETCH: "Fetching..."
|
|
30
|
-
OK: "OK",
|
|
31
|
-
ERROR: "Error"
|
|
30
|
+
FETCH: "Fetching..."
|
|
32
31
|
};
|
|
33
32
|
|
|
33
|
+
// Fonction pour obtenir la clé du jour (YYYY-MM-DD)
|
|
34
|
+
function getTodayKey() {
|
|
35
|
+
return new Date().toISOString().split('T')[0];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Fonction pour créer une clé unique basée sur les paramètres de la requête
|
|
39
|
+
function createRequestSignature(params, options) {
|
|
40
|
+
// Créer un objet avec les paramètres pertinents pour la requête
|
|
41
|
+
const requestIdentifier = {
|
|
42
|
+
day: getTodayKey(), // Inclure le jour pour avoir une signature par jour
|
|
43
|
+
type: params.type,
|
|
44
|
+
prm: params.prm,
|
|
45
|
+
start: params.start,
|
|
46
|
+
end: params.end,
|
|
47
|
+
endpoint: params.endpoint,
|
|
48
|
+
// Inclure les timeouts pour distinguer les requêtes avec configs différentes
|
|
49
|
+
timeout: {
|
|
50
|
+
lookup: params.options_timeout_lookup,
|
|
51
|
+
connect: params.options_timeout_connect,
|
|
52
|
+
secureConnect: params.options_timeout_secureconnect,
|
|
53
|
+
socket: params.options_timeout_socket,
|
|
54
|
+
send: params.options_timeout_send,
|
|
55
|
+
response: params.options_timeout_response
|
|
56
|
+
},
|
|
57
|
+
retry_limit: params.options_retry_limit
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Créer un hash SHA256 pour avoir une clé courte et unique
|
|
61
|
+
const hash = crypto.createHash('sha256');
|
|
62
|
+
hash.update(JSON.stringify(requestIdentifier));
|
|
63
|
+
return hash.digest('hex').substring(0, 16); // Utiliser les 16 premiers caractères
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Fonction pour obtenir/réinitialiser le compteur si nécessaire
|
|
67
|
+
function getOrResetRequestCounter(node, requestSignature) {
|
|
68
|
+
const today = getTodayKey();
|
|
69
|
+
let requestHistory = node.context().get('requestHistory') || {};
|
|
70
|
+
|
|
71
|
+
if (!requestHistory[requestSignature]) {
|
|
72
|
+
requestHistory[requestSignature] = {
|
|
73
|
+
date: today,
|
|
74
|
+
count: 0
|
|
75
|
+
};
|
|
76
|
+
} else if (requestHistory[requestSignature].date !== today) {
|
|
77
|
+
// Nouveau jour, réinitialiser
|
|
78
|
+
requestHistory[requestSignature] = {
|
|
79
|
+
date: today,
|
|
80
|
+
count: 0
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
node.context().set('requestHistory', requestHistory);
|
|
85
|
+
return requestHistory[requestSignature];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Fonction pour incrémenter et retourner le nouveau count pour une requête spécifique
|
|
89
|
+
function incrementAndGetRequestCount(node, requestSignature) {
|
|
90
|
+
let requestCounter = getOrResetRequestCounter(node, requestSignature);
|
|
91
|
+
requestCounter.count++;
|
|
92
|
+
let requestHistory = node.context().get('requestHistory');
|
|
93
|
+
requestHistory[requestSignature] = requestCounter;
|
|
94
|
+
node.context().set('requestHistory', requestHistory);
|
|
95
|
+
return requestCounter.count;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Fonction pour obtenir les infos du compteur avec détails
|
|
99
|
+
function getRequestCounterInfo(node, requestSignature, dailyLimit) {
|
|
100
|
+
const requestCounter = getOrResetRequestCounter(node, requestSignature);
|
|
101
|
+
return {
|
|
102
|
+
count: requestCounter.count,
|
|
103
|
+
date: requestCounter.date,
|
|
104
|
+
signature: requestSignature,
|
|
105
|
+
remaining: Math.max(0, dailyLimit - requestCounter.count),
|
|
106
|
+
isLimited: requestCounter.count >= dailyLimit
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
34
110
|
function LinkyMetering(config) {
|
|
35
111
|
RED.nodes.createNode(this, config);
|
|
36
112
|
var node = this;
|
|
@@ -45,16 +121,17 @@ module.exports = async function (RED) {
|
|
|
45
121
|
"prm": msg.payload?.prm || node.credentials?.prm || node.config?.prm || '',
|
|
46
122
|
"start": msg.payload?.start || '',
|
|
47
123
|
"end": msg.payload?.end || '',
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
124
|
+
"options_api_daily_limit": msg.payload?.options_api_daily_limit || 3,
|
|
125
|
+
"options_retry_limit": msg.payload?.options_retry_limit ?? 2,
|
|
126
|
+
"options_timeout_lookup": msg.payload?.options_timeout_lookup ?? 500,
|
|
127
|
+
"options_timeout_connect": msg.payload?.options_timeout_connect ?? 500,
|
|
128
|
+
"options_timeout_secureconnect": msg.payload?.options_timeout_secureconnect ?? 500,
|
|
129
|
+
"options_timeout_socket": msg.payload?.options_timeout_socket ?? 5000,
|
|
130
|
+
"options_timeout_send": msg.payload?.options_timeout_send ?? 10000,
|
|
131
|
+
"options_timeout_response": msg.payload?.options_timeout_response ?? 10000,
|
|
132
|
+
"random_delay": msg.payload?.random_delay ?? 5000,
|
|
56
133
|
"endpoint": msg.payload?.endpoint || 'https://conso.boris.sh/api/',
|
|
57
|
-
"debug": msg.payload?.debug ===
|
|
134
|
+
"debug": String(msg.payload?.debug).toLowerCase() === "true"
|
|
58
135
|
}
|
|
59
136
|
|
|
60
137
|
const options = {
|
|
@@ -78,82 +155,145 @@ module.exports = async function (RED) {
|
|
|
78
155
|
}
|
|
79
156
|
};
|
|
80
157
|
|
|
81
|
-
!_p.type
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
158
|
+
if (!_p.type) {
|
|
159
|
+
const err = 'api type not set (ex. daily_consumption) see documentation';
|
|
160
|
+
done ? done(err) : node.error(err, msg);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (!_p.token) {
|
|
164
|
+
const err = 'token not set';
|
|
165
|
+
done ? done(err) : node.error(err, msg);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (!_p.prm) {
|
|
169
|
+
const err = 'PRM not set';
|
|
170
|
+
done ? done(err) : node.error(err, msg);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (!_p.start) {
|
|
174
|
+
const err = 'start not set';
|
|
175
|
+
done ? done(err) : node.error(err, msg);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (!_p.end) {
|
|
179
|
+
const err = 'end not set';
|
|
180
|
+
done ? done(err) : node.error(err, msg);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Créer une signature unique de la requête
|
|
185
|
+
const requestSignature = createRequestSignature(_p, options);
|
|
186
|
+
|
|
187
|
+
// Vérifier si cette requête spécifique a dépassé la limite
|
|
188
|
+
const counterInfo = getRequestCounterInfo(node, requestSignature, _p.options_api_daily_limit);
|
|
189
|
+
if (counterInfo.isLimited) {
|
|
190
|
+
// Récupérer la réponse en cache
|
|
191
|
+
let responseCache = node.context().get('responseCache') || {};
|
|
192
|
+
const cachedResponse = responseCache[requestSignature];
|
|
193
|
+
|
|
194
|
+
if (cachedResponse) {
|
|
195
|
+
// Retourner la réponse en cache
|
|
196
|
+
const statusMsg = `Limit reached - Returning cached response (${counterInfo.count}/${_p.options_api_daily_limit}) - Sig: ${requestSignature}`;
|
|
197
|
+
node.status({ fill: 'yellow', shape: 'ring', text: statusMsg });
|
|
198
|
+
msg.payload = cachedResponse;
|
|
199
|
+
node.send(msg);
|
|
200
|
+
if (done) done();
|
|
201
|
+
} else {
|
|
202
|
+
// Pas de cache disponible
|
|
203
|
+
const statusMsg = `Error limit reached (${counterInfo.count}/${_p.options_api_daily_limit}) - Sig: ${requestSignature}`;
|
|
204
|
+
node.status({ fill: 'red', shape: 'ring', text: statusMsg });
|
|
205
|
+
const errorMsg = `Daily limit of ${_p.options_api_daily_limit} calls for this request reached. No cache available.`;
|
|
206
|
+
node.error(errorMsg, msg);
|
|
207
|
+
if (done) done(errorMsg);
|
|
208
|
+
}
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Incrémenter le compteur pour cette requête spécifique MAINTENANT (avant l'appel)
|
|
213
|
+
const newCount = incrementAndGetRequestCount(node, requestSignature);
|
|
214
|
+
const remaining = _p.options_api_daily_limit - newCount;
|
|
215
|
+
_p.debug && node.warn(`Request ${requestSignature} - Call ${newCount}/${_p.options_api_daily_limit} - Remaining: ${remaining}`);
|
|
216
|
+
|
|
217
|
+
let wait = Math.floor(Math.random() * _p.random_delay);
|
|
218
|
+
let msg_wait = nodeStatus.WAIT + Math.ceil(wait / 1000) + 's';
|
|
219
|
+
node.status({ fill: 'grey', shape: 'ring', text: msg_wait });
|
|
220
|
+
setTimeout(() => {
|
|
221
|
+
node.status({ fill: 'grey', shape: 'ring', text: nodeStatus.FETCH });
|
|
222
|
+
let request;
|
|
223
|
+
try {
|
|
224
|
+
let url = new URL(_p.endpoint);
|
|
225
|
+
url.pathname = url.pathname.replace(/\/$/, '') + '/' + _p.type;
|
|
226
|
+
url.search = new URLSearchParams({
|
|
227
|
+
prm: _p.prm,
|
|
228
|
+
start: _p.start,
|
|
229
|
+
end: _p.end
|
|
230
|
+
}).toString();
|
|
231
|
+
request = url.toString();
|
|
232
|
+
} catch (err) {
|
|
233
|
+
node.status({ fill: 'red', shape: 'ring', text: 'error' });
|
|
234
|
+
if (done) {
|
|
235
|
+
done('url ' + err);
|
|
236
|
+
} else {
|
|
237
|
+
node.error('url ' + err);
|
|
110
238
|
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
_p.debug && request && node.warn('Request URL:' + request);
|
|
242
|
+
if (_p.debug && options) {
|
|
243
|
+
const debugOptions = { ...options, headers: { ...options.headers } };
|
|
244
|
+
if (debugOptions.headers && debugOptions.headers['Authorization']) {
|
|
245
|
+
debugOptions.headers['Authorization'] = 'Bearer [HIDDEN]';
|
|
246
|
+
}
|
|
247
|
+
node.warn('Options:' + JSON.stringify(debugOptions));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
request && options && got(request, options)
|
|
251
|
+
.then(res => {
|
|
252
|
+
if (res.statusCode == '200') {
|
|
253
|
+
try {
|
|
254
|
+
msg.payload = JSON.parse(res.body);
|
|
255
|
+
|
|
256
|
+
// Stocker la réponse en cache
|
|
257
|
+
let responseCache = node.context().get('responseCache') || {};
|
|
258
|
+
responseCache[requestSignature] = msg.payload;
|
|
259
|
+
node.context().set('responseCache', responseCache);
|
|
260
|
+
|
|
261
|
+
node.send(msg);
|
|
262
|
+
node.status({ fill: 'blue', shape: 'dot', text: 'OK' });
|
|
111
263
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
request && options && got(request, options)
|
|
116
|
-
.then(res => {
|
|
117
|
-
if (res.statusCode == '200') {
|
|
118
|
-
try {
|
|
119
|
-
msg.payload = JSON.parse(res.body);
|
|
120
|
-
node.send(msg);
|
|
121
|
-
node.status({ fill: 'blue', shape: 'dot', text: 'OK' });
|
|
122
|
-
|
|
123
|
-
setTimeout(() => {
|
|
124
|
-
node.status({});
|
|
125
|
-
if (done) {
|
|
126
|
-
done();
|
|
127
|
-
}
|
|
128
|
-
}, 1000);
|
|
129
|
-
} catch (err) {
|
|
130
|
-
node.status({ fill: 'red', shape: 'ring', text: 'error' });
|
|
264
|
+
setTimeout(() => {
|
|
265
|
+
node.status({});
|
|
131
266
|
if (done) {
|
|
132
|
-
done(
|
|
133
|
-
} else {
|
|
134
|
-
node.error('parsing ' + err, msg);
|
|
267
|
+
done();
|
|
135
268
|
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
node.status({ fill: 'red', shape: 'ring', text:
|
|
269
|
+
}, 1000);
|
|
270
|
+
} catch (err) {
|
|
271
|
+
node.status({ fill: 'red', shape: 'ring', text: 'error' });
|
|
139
272
|
if (done) {
|
|
140
|
-
done(
|
|
273
|
+
done('parsing ' + err);
|
|
141
274
|
} else {
|
|
142
|
-
node.error(
|
|
275
|
+
node.error('parsing ' + err, msg);
|
|
143
276
|
}
|
|
144
277
|
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
node.status({ fill: 'red', shape: 'ring', text: 'error' });
|
|
148
|
-
|
|
278
|
+
} else {
|
|
279
|
+
node.status({ fill: 'red', shape: 'ring', text: res.statusCode });
|
|
149
280
|
if (done) {
|
|
150
|
-
done(
|
|
281
|
+
done(`statusCode ${res.statusCode}`);
|
|
151
282
|
} else {
|
|
152
|
-
node.error(
|
|
283
|
+
node.error(`statusCode ${res.statusCode}`, msg);
|
|
153
284
|
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
|
|
285
|
+
}
|
|
286
|
+
})
|
|
287
|
+
.catch(err => {
|
|
288
|
+
node.status({ fill: 'red', shape: 'ring', text: 'error' });
|
|
289
|
+
|
|
290
|
+
if (done) {
|
|
291
|
+
done(err);
|
|
292
|
+
} else {
|
|
293
|
+
node.error(err, msg);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
}, wait);
|
|
157
297
|
})
|
|
158
298
|
}
|
|
159
299
|
RED.nodes.registerType("linky-api", LinkyMetering, {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flecoufle/node-red-linky",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.4.2",
|
|
4
|
+
"description": "Récupérer de Linky les données de consommation et production d'énergie via l'API Enedis",
|
|
5
5
|
"main": "linky/linky.js",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|