Skip to content

Commit 6ee28d5

Browse files
authored
Merge pull request #26 from athombv/master
MMIP
2 parents 9d796f1 + f03575e commit 6ee28d5

31 files changed

+1381
-145
lines changed

.github/workflows/build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ jobs:
1616
runs-on: buildjet-2vcpu-ubuntu-2204-arm
1717
steps:
1818
- name: Checkout
19-
uses: actions/checkout@v4
19+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
2020

2121
- name: Setup Node.js
22-
uses: actions/setup-node@v4
22+
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e
2323
with:
2424
node-version-file: '.nvmrc'
2525
registry-url: 'https://npm.pkg.github.com'

.github/workflows/lint.yml

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,20 @@ on:
1313
jobs:
1414
lint:
1515
name: Lint
16-
uses: athombv/athom-github-workflow/.github/workflows/lint.yml@master
17-
secrets: inherit
16+
runs-on: buildjet-2vcpu-ubuntu-2204-arm
17+
steps:
18+
- name: Checkout
19+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
20+
21+
- name: Setup Node.js
22+
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e
23+
with:
24+
node-version-file: '.nvmrc'
25+
registry-url: 'https://npm.pkg.github.com'
26+
27+
- name: Lint
28+
run: |
29+
npm ci --ignore-scripts --audit=false
30+
npm run lint
31+
env:
32+
NODE_AUTH_TOKEN: ${{ secrets.HOMEY_GITHUB_ACTIONS_BOT_PERSONAL_ACCESS_TOKEN }}

.github/workflows/npm-version.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@ jobs:
3838
exit 1
3939
4040
- name: Checkout git repository
41-
uses: actions/checkout@v4
41+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
4242

4343
- name: Setup Node.js
44-
uses: actions/setup-node@v4
44+
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e
4545
with:
4646
node-version-file: '.nvmrc'
4747
registry-url: 'https://npm.pkg.github.com'

.github/workflows/release.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ jobs:
1212
runs-on: buildjet-2vcpu-ubuntu-2204-arm
1313
steps:
1414
- name: Checkout
15-
uses: actions/checkout@v4
15+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
1616

1717
- name: Setup Node.js
18-
uses: actions/setup-node@v4
18+
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e
1919
with:
2020
node-version-file: '.nvmrc'
2121
registry-url: 'https://npm.pkg.github.com'
@@ -55,7 +55,7 @@ jobs:
5555
# Post a Slack notification on success/failure
5656
- name: Slack notify
5757
if: always()
58-
uses: innocarpe/actions-slack@v1
58+
uses: innocarpe/actions-slack@b2f50d9d8037ef8a1f77fa4659767747120124e4
5959
with:
6060
status: ${{ job.status }}
6161
success_text: '${{github.repository}} - Published ${{ env.package_name }}@${{ env.package_version }} to GitHub Packages Registry 🚀'

.github/workflows/test.yml

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,23 @@ on:
1313
jobs:
1414
test:
1515
name: Test
16-
uses: athombv/athom-github-workflow/.github/workflows/test.yml@master
17-
secrets: inherit
16+
runs-on: buildjet-2vcpu-ubuntu-2204-arm
17+
steps:
18+
- name: Checkout
19+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
20+
21+
- name: Setup Node.js
22+
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e
23+
with:
24+
node-version-file: '.nvmrc'
25+
registry-url: 'https://npm.pkg.github.com'
26+
27+
- name: Build
28+
run: |
29+
npm ci --audit=false
30+
npm run build
31+
env:
32+
NODE_AUTH_TOKEN: ${{ secrets.HOMEY_GITHUB_ACTIONS_BOT_PERSONAL_ACCESS_TOKEN }}
33+
34+
- name: Test
35+
run: npm run test

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
/node_modules
2-
/dist
2+
/dist
3+
/private

README.md

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# DSMR Parser
22

3+
[![Test](https://github.com/athombv/node-dsmr-parser/actions/workflows/test.yml/badge.svg)](https://github.com/athombv/node-dsmr-parser/actions/workflows/test.yml)
4+
[![Build](https://github.com/athombv/node-dsmr-parser/actions/workflows/build.yml/badge.svg)](https://github.com/athombv/node-dsmr-parser/actions/workflows/build.yml)
5+
36
This module can parse Dutch Smart Meter Requirements (DSMR) messages, and return their contents as JavaScript Objects.
47

58
## Installation
@@ -109,7 +112,7 @@ Result: {
109112
}
110113
```
111114

112-
### Using Homey Energy Dongle
115+
### Connecting Homey Energy Dongle using USB
113116

114117
When you connect a PC to Homey Energy Dongle, you can read the raw data from the meter from Homey Energy Dongle's USB port. An example
115118
script of how to do this is located in [`examples/homey-energy-dongle-usb.js`](./examples/homey-energy-dongle-usb.js). To run this example you need to:
@@ -143,3 +146,34 @@ If the data from your meter is encrypted, you'll need to provide the decryption
143146
```sh
144147
node examples/homey-energy-dongle-usb.js /dev/tty.usbmodem101 1234567890123456
145148
```
149+
150+
### Connection Homey Energy Dongle using WebSocket
151+
152+
Homey Energy Dongle has a Local WebSocket API. An example script of how to use this Local API is located in [`examples/homey-energy-dongle-ws.js`](./examples/homey-energy-dongle-ws.js).
153+
To run this example, you need to:
154+
155+
1. Have NodeJS and git installed on your system
156+
2. Open a terminal window
157+
3. Clone this repository
158+
159+
```sh
160+
git clone https://github.com/athombv/node-dsmr-parser
161+
```
162+
163+
4. Install the dependencies and build the project
164+
165+
```sh
166+
cd node-dsmr-parser
167+
npm ci
168+
npm run build
169+
```
170+
171+
5. Connect Homey Energy Dongle to a Smart Meter
172+
6. Set up Homey Energy Dongle in the Homey app
173+
7. Enable the Local API in Homey Energy Dongle's settings in Homey
174+
- You can also find Homey Energy Dongle's IP address here
175+
8. Run the example script:
176+
177+
```sh
178+
node examples/homey-energy-dongle-ws.js <ip> <decryption key (optional)>
179+
```

examples/homey-energy-dongle-usb.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ const parser = DSMR.createStreamParser({
8181
console.log(result.raw);
8282
console.log('Parsed telegram:');
8383
delete result.raw;
84-
console.log(result);
84+
console.dir(result, { depth: Infinity });
8585
}
8686
},
8787
});

examples/homey-energy-dongle-ws.js

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/*
2+
* This is an example of how to parse Smart Meter data when connected to the WebSocket server of a
3+
* Homey Energy Dongle. Homey Energy Dongle will output the raw data from the connected Smart Meter
4+
* over its WebSocket server. This data can be parsed using the DSMR parser library.
5+
*
6+
* To get started, first make sure to enable the local API in the device settings of the Homey app.
7+
* There you can find the IP address of the Homey Energy Dongle as well. Then run this script as
8+
* follows:
9+
*
10+
* node examples/homey-energy-dongle-ws.js <ip> <decryption key (optional)>
11+
*
12+
* The script will automatically connect to the Homey Energy Dongle and start parsing data from your
13+
* Smart Meter!
14+
*/
15+
16+
import WebSocket, { createWebSocketStream } from 'ws';
17+
import { DSMRError, DSMR } from '@athombv/dsmr-parser';
18+
19+
const ENERGY_DONGLE_IP = process.argv[2];
20+
const DECRYPTION_KEY = process.argv[3];
21+
22+
if (!ENERGY_DONGLE_IP) {
23+
console.log('Usage: node examples/homey-energy-dongle-ws.js <ip> <decryption key (optional)>');
24+
console.log('No IP address provided.');
25+
process.exit(1);
26+
}
27+
28+
if (DECRYPTION_KEY) {
29+
console.log(`Decryption key: ${DECRYPTION_KEY}`);
30+
}
31+
32+
/** @type {WebSocket | undefined} */
33+
let ws;
34+
35+
process.on('SIGINT', () => {
36+
console.log('Shutting down...');
37+
if (ws) {
38+
ws.terminate();
39+
ws.close();
40+
console.log('Connection closed');
41+
}
42+
43+
console.log('Goodbye!');
44+
process.exit(0);
45+
});
46+
47+
// You can obtain the address of Homey Energy Dongle by using mDNS discovery.
48+
// Homey Energy Dongle can be found on the _energydongle._tcp service. This service contains
49+
// the "p" (short for path) and "v" (short for version) TXT records.
50+
// If the websocket server is not enabled, the "p" record will not be set.
51+
// The "p" record contains the path to the websocket server.
52+
const address = `ws://${ENERGY_DONGLE_IP}:80/ws`;
53+
54+
while (true) {
55+
console.log(`Connecting to ${address}`);
56+
57+
// Use the ws package to handle the WebSocket connection to the Homey Energy Dongle.
58+
ws = new WebSocket(address);
59+
let interval;
60+
let receivedPong = false;
61+
62+
// If the connection fails, log the error and terminate the connection.
63+
ws.on('error', (error) => {
64+
console.log('WS Error:', error);
65+
ws.terminate();
66+
});
67+
68+
// If the connection is opened, log the event and start the ping interval.
69+
// The ping interval makes sure the connection is still alive by sending a ping every so often.
70+
ws.on('open', () => {
71+
console.log(`Connected to ${address}`);
72+
73+
interval = setInterval(() => {
74+
if (!receivedPong) {
75+
console.log('No pong received, closing connection');
76+
ws.close();
77+
ws.terminate();
78+
return;
79+
}
80+
81+
receivedPong = false;
82+
ws.ping();
83+
}, 10_000);
84+
85+
ws.ping();
86+
});
87+
88+
// Set a flag when a pong is received.
89+
ws.on('pong', () => {
90+
receivedPong = true;
91+
});
92+
93+
// Stream all messages from the WebSocket connection.
94+
// The DSMR library will parse incoming data from the websocket,
95+
const stream = createWebSocketStream(ws);
96+
97+
stream.on('data', (data) => {
98+
console.log('Stream data:', data.toString());
99+
});
100+
101+
// If the stream encounters an error, log the error and terminate the connection.
102+
stream.on('error', (error) => {
103+
console.log('Stream error:', error);
104+
ws.terminate();
105+
});
106+
107+
// Create a DSMR parser that listens to the stream.
108+
const parser = DSMR.createStreamParser({
109+
stream,
110+
decryptionKey: DECRYPTION_KEY,
111+
detectEncryption: true,
112+
callback: (error, result) => {
113+
if (error instanceof DSMRError) {
114+
console.error('Error parsing DSMR data:', error.message);
115+
console.error('Raw data:', error.rawTelegram?.toString('hex'));
116+
} else if (error) {
117+
console.error('Error:', error);
118+
} else {
119+
// Not very useful to log the raw telegram here as it is already logged by the data listener on the stream.
120+
delete result.raw;
121+
console.log('Parsed telegram:');
122+
console.dir(result, { depth: Infinity });
123+
}
124+
},
125+
});
126+
127+
// Don't continue the loop until the connection is closed.
128+
await new Promise((resolve) => {
129+
ws.on('close', (code, reason) => {
130+
// Homey Energy Dongle only allows two clients to connect to the web socket at the same time.
131+
// If you get the error code 1008, with reason "Connection limit reached" there are already two clients connected.
132+
// If you get the error code 1008, with reason "Local API disabled" the local API is disabled and should be activated in the Homey app.
133+
console.log('WS disconnected:', code, reason.toString());
134+
clearInterval(interval);
135+
parser.destroy();
136+
resolve();
137+
});
138+
});
139+
140+
// Some delay before reconnecting.
141+
await new Promise((resolve) => setTimeout(resolve, 5000));
142+
}

package-lock.json

Lines changed: 26 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@athombv/dsmr-parser",
3-
"version": "1.1.1",
3+
"version": "1.2.0",
44
"description": "DSMR Parser for Smart Meters",
55
"type": "module",
66
"main": "dist/index.js",
@@ -18,7 +18,9 @@
1818
"prettier": "prettier . --write",
1919
"prettier:check": "prettier . --check",
2020
"tool:parse-telegram": "tsx ./tools/parse-telegram.ts",
21-
"tool:update-test-telegrams": "tsx ./tools/update-test-telegrams.ts"
21+
"tool:update-test-telegrams": "tsx ./tools/update-test-telegrams.ts",
22+
"tool:decrypt-telegram": "tsx ./tools/decrypt-telegram.ts",
23+
"tool:encrypt-telegram": "tsx ./tools/encrypt-telegram.ts"
2224
},
2325
"repository": {
2426
"type": "git",
@@ -42,6 +44,7 @@
4244
"serialport": "^12.0.0",
4345
"tsx": "^4.16.2",
4446
"typescript": "^5.5.3",
45-
"typescript-eslint": "^8.0.0"
47+
"typescript-eslint": "^8.0.0",
48+
"ws": "^8.18.1"
4649
}
4750
}

0 commit comments

Comments
 (0)