Skip to content

Commit a0e4641

Browse files
committed
first commit
1 parent b58e423 commit a0e4641

File tree

7 files changed

+918
-0
lines changed

7 files changed

+918
-0
lines changed

.github/workflows/npm-publish.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
name: Publish Package to npmjs
2+
on:
3+
release:
4+
types: [published]
5+
jobs:
6+
build:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- uses: actions/checkout@v3
10+
# Setup .npmrc file to publish to npm
11+
- uses: actions/setup-node@v3
12+
with:
13+
node-version: '16.x'
14+
registry-url: 'https://registry.npmjs.org'
15+
- run: npm ci
16+
- run: npm publish
17+
env:
18+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
.env
3+
.vscode

.npmignore

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
.*.swp
2+
._*
3+
.DS_Store
4+
.git
5+
.gitignore
6+
.hg
7+
.npmignore
8+
.npmrc
9+
.lock-wscript
10+
.svn
11+
.wafpickle-*
12+
config.gypi
13+
CVS
14+
npm-debug.log
15+
16+
node_modules
17+
.env
18+
.vscode
19+
.github

linky/linky.html

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
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+
<script type="text/javascript">
23+
RED.nodes.registerType('linky-api',{
24+
category: 'linky',
25+
color: '#a6bbcf',
26+
defaults: {
27+
name: {value: "", required: false},
28+
prm: {value: "", required: false},
29+
token: {value: "", required: false},
30+
type: {value: "", required: false}
31+
},
32+
credentials: {
33+
prm: {type: "text"},
34+
token: {type: "password"}
35+
},
36+
inputs:1,
37+
outputs:1,
38+
icon: 'font-awesome/fa-tachometer',
39+
paletteLabel:'linky',
40+
label: function() {
41+
return this.name || "linky";
42+
}
43+
});
44+
</script>
45+
46+
<script type="text/html" data-template-name="linky-api">
47+
<div class="form-row">
48+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
49+
<input type="text" id="node-input-name" placeholder="Name">
50+
</div>
51+
<div class="form-row">
52+
<label for="node-input-token"><i class="fa fa-lock"></i> Token</label>
53+
<input type="password" id="node-input-token" placeholder="Your token or in payload"/>
54+
</div>
55+
<div class="form-row">
56+
<label for="node-input-prm"><i class="fa fa-wave-pulse"></i> PRM</label>
57+
<input type="text" id="node-input-prm" placeholder="Your PRM or in payload"/>
58+
</div>
59+
</script>
60+
61+
<script type="text/html" data-help-name="linky-api">
62+
<p>Retreive linky power consumption and production from <a href="https://github.com/bokub/conso-api#readme">bokub/conso-api</a> api</p>
63+
<dl class="message-properties">
64+
<dd> Create an Enedis account: </dd>
65+
<ul>
66+
<li><a href="https://mon-compte-client.enedis.fr/">https://mon-compte-client.enedis.fr/</a></li>
67+
</ul>
68+
</dl>
69+
<dl class="message-properties">
70+
<dd>Configure your account: </dd>
71+
<ul>
72+
<li>Activate load curve recording</li>
73+
<li>Enable data collection</li>
74+
</ul>
75+
</dl>
76+
<dl class="message-properties">
77+
<dd>Get a <b>token</b>: </dd>
78+
<li><a href="https://conso.boris.sh/api/auth">https://conso.boris.sh/api/auth</a></li>
79+
<li>Copy the <b>token</b> in the linky node configuration</li>
80+
</dl>
81+
<h3>Properties</h3>
82+
<dl class="message-properties">
83+
<dt>Name <span class="property-type">string</span></dt>
84+
<dd>Can be used to override the default name of the node.</dd>
85+
<dt>Token <span class="property-type">string</span></dt>
86+
<dd>Enedis token.</dd>
87+
<dt>PRM <span class="property-type">string</span></dt>
88+
<dd>Enedis PRM.</dd>
89+
</dl>
90+
91+
<h3>Input</h3>
92+
<dl class="message-properties">
93+
<dt>payload <span class="property-type">object</span></dt>
94+
<dd>Parameters in<code>JSON</code> format.</dd>
95+
<pre><code>"payload" : {
96+
"type": "daily_consumption",
97+
"start": "YYYY-MM-DD",
98+
"end": "YYYY-MM-DD"
99+
}</code></pre><br/>
100+
<p>
101+
All parameters:<br/>
102+
<pre><code>"type": see <code>available types</code> below
103+
"token": token set in payload or in node
104+
"prm": PRM set in payload or in node
105+
"start": start date in RFC 3339 format ("YYYY-MM-DD")
106+
"end": end date in RFC 3339 format ("YYYY-MM-DD")
107+
"options_retry_limit": got option in ms(default 2)
108+
"options_timeout_lookup": got option in ms(default 500)
109+
"options_timeout_connect": got option in ms(default 500)
110+
"options_timeout_secureconnect": got option in ms(default 500)
111+
"options_timeout_socket": got option in ms(default 5000)
112+
"options_timeout_send": got option in ms(default 1000)
113+
"options_timeout_response": got option in ms(default 1000)
114+
"random_delay": fetch after a random delay in ms (default 5000)
115+
"endpoint": endpoint api (default "https://conso.boris.sh/api/)"
116+
</code></pre></p>
117+
Available types are: <code>"daily_consumption"</code>, <code>"consumption_load_curve"</code>, <code>"consumption_max_power"</code> in consumption<br/>
118+
<code>"daily_production"</code>, <code>"production_load_curve"</code> in production
119+
</dl>
120+
121+
<h3>Output</h3>
122+
<dl class="message-properties">
123+
<dt>payload <span class="property-type">object</span></dt>
124+
<dd>The consumption datas in <code>JSON</code> format.</dd>
125+
<dd>Example for daily_consumption:</dd>
126+
127+
<dd><pre><code>"payload": {
128+
"usage_point_id": "01...",
129+
"start": "2023-06-09",
130+
"end": "2023-06-16",
131+
"quality": "BRUT",
132+
"reading_type": {
133+
"unit": "Wh",
134+
"measurement_kind": "energy",
135+
"aggregate": "sum",
136+
"measuring_period": "P1D"
137+
},
138+
"interval_reading": [
139+
{
140+
"value": "9748",
141+
"date": "2023-06-09"
142+
},
143+
...
144+
{
145+
"value": "8789",
146+
"date": "2023-06-15"
147+
}
148+
]
149+
}</code></pre></dd>
150+
</dl>
151+
152+
<h3>References</h3>
153+
<ul><li>Node repository <a href="https://github.com/flecoufle/node-red-contrib-linky">flecoufle/node-red-contrib-linky</a></li></ul>
154+
</script>

linky/linky.js

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
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

Comments
 (0)