Skip to content

feat: add support for dlms/cosem #32

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/node_modules
/dist
/private
/private
/tests/telegrams/dlms/*-data.json
/tests/telegrams/dlms/*-data.txt
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,15 +136,16 @@ npm run build
5. Connect Homey Energy Dongle to a Smart Meter
6. Connect the USB-C port of Homey Energy Dongle to your PC
7. Run the example script:
- Replace `<mode>` with either `dsmr` or `dlms`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps provide a short explanation about why there are different modes and how to choose the right one.


```sh
node examples/homey-energy-dongle-usb.js
node examples/homey-energy-dongle-usb.js <mode>
```

If the data from your meter is encrypted, you'll need to provide the decryption key and the specific serial port to connect to. For example:

```sh
node examples/homey-energy-dongle-usb.js /dev/tty.usbmodem101 1234567890123456
node examples/homey-energy-dongle-usb.js dsmr /dev/tty.usbmodem101 1234567890123456
```

### Connection Homey Energy Dongle using WebSocket
Expand Down Expand Up @@ -173,7 +174,8 @@ npm run build
7. Enable the Local API in Homey Energy Dongle's settings in Homey
- You can also find Homey Energy Dongle's IP address here
8. Run the example script:
- `mode` must be either `dsmr` or `dlms`.

```sh
node examples/homey-energy-dongle-ws.js <ip> <decryption key (optional)>
node examples/homey-energy-dongle-ws.js <mode> <ip> <decryption key (optional)>
```
87 changes: 63 additions & 24 deletions examples/homey-energy-dongle-usb.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,23 @@
* The script will automatically detect the Homey Energy Dongle and start parsing data from from your Smart Meter!
*/
import { SerialPort } from 'serialport';
import { DSMRError, DSMR } from '@athombv/dsmr-parser';
import {
DlmsStreamParser,
UnencryptedDSMRStreamParser,
EncryptedDSMRStreamParser,
} from '@athombv/dsmr-parser';

let serialPortPath = process.argv[2];
const DECRYPTION_KEY = process.argv[3];
const MODE = process.argv[2];
let serialPortPath = process.argv[3];
const DECRYPTION_KEY = process.argv[4];

if (!MODE || (MODE !== 'dsmr' && MODE !== 'dlms')) {
console.log(
'Usage: node examples/homey-energy-dongle-usb.js <mode> <port> <decryption key (optional)>',
);
console.log('No valid mode provided. Use "dsmr" or "dlms".');
process.exit(1);
}

if (!serialPortPath) {
const allPorts = await SerialPort.list();
Expand All @@ -25,7 +38,7 @@ if (!serialPortPath) {

if (possiblePorts.length === 0) {
console.log(
'Usage: node examples/homey-energy-dongle-usb.js <port> <decryption key (optional)>',
'Usage: node examples/homey-energy-dongle-usb.js <mode> <port> <decryption key (optional)>',
);
console.log('No Homey Energy Dongle found.');
process.exit(1);
Expand All @@ -35,7 +48,7 @@ if (!serialPortPath) {
console.log(`- ${port.path}`);
}
console.log(
'Usage: node examples/homey-energy-dongle-usb.js <port> <decryption key (optional)>',
'Usage: node examples/homey-energy-dongle-usb.js <mode> <port> <decryption key (optional)>',
);
process.exit(1);
} else {
Expand Down Expand Up @@ -66,25 +79,51 @@ const serialPort = new SerialPort(
},
);

const parser = DSMR.createStreamParser({
stream: serialPort,
decryptionKey: DECRYPTION_KEY,
detectEncryption: true,
callback: (error, result) => {
if (error instanceof DSMRError) {
console.error('Error parsing DSMR data:', error.message);
console.error('Raw data:', error.rawTelegram?.toString('hex'));
} else if (error) {
console.error('Error:', error);
} else {
console.log('Raw telegram:');
console.log(result.raw);
console.log('Parsed telegram:');
delete result.raw;
console.dir(result, { depth: Infinity });
}
},
});
// Create a DSMR parser that listens to the UART stream.
const parser = (() => {
if (MODE === 'dsmr' && !DECRYPTION_KEY) {
return new UnencryptedDSMRStreamParser({
stream,
detectEncryption: true,
callback: (error, result) => {
if (error) {
console.error('Error parsing DSMR data:', error);
} else {
console.log('Parsed telegram:');
console.dir(result, { depth: Infinity });
}
},
});
}

if (MODE === 'dsmr' && DECRYPTION_KEY) {
return new EncryptedDSMRStreamParser({
stream,
decryptionKey: DECRYPTION_KEY,
callback: (error, result) => {
if (error) {
console.error('Error parsing DSMR data:', error);
} else {
console.log('Parsed telegram:');
console.dir(result, { depth: Infinity });
}
},
});
}

return new DlmsStreamParser({
stream,
decryptionKey: DECRYPTION_KEY,
callback: (error, result) => {
if (error) {
console.log('Error parsing DLMS data:', error.message);
} else {
console.log('Parsed DLMS telegram:');
console.dir(result, { depth: Infinity });
}
},
});
})();

// Make sure to close the port when the process exits
process.on('SIGINT', () => {
Expand Down
83 changes: 62 additions & 21 deletions examples/homey-energy-dongle-ws.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,32 @@
*/

import WebSocket, { createWebSocketStream } from 'ws';
import { DSMRError, DSMR } from '@athombv/dsmr-parser';
import {
DlmsStreamParser,
UnencryptedDSMRStreamParser,
EncryptedDSMRStreamParser,
} from '@athombv/dsmr-parser';

const ENERGY_DONGLE_IP = process.argv[2];
const DECRYPTION_KEY = process.argv[3];
const MODE = process.argv[3];
const DECRYPTION_KEY = process.argv[4];

if (!ENERGY_DONGLE_IP) {
console.log('Usage: node examples/homey-energy-dongle-ws.js <ip> <decryption key (optional)>');
console.log(
'Usage: node examples/homey-energy-dongle-ws.js <ip> <mode> <decryption key (optional)>',
);
console.log('No IP address provided.');
process.exit(1);
}

if (!MODE || (MODE !== 'dsmr' && MODE !== 'dlms')) {
console.log(
'Usage: node examples/homey-energy-dongle-ws.js <ip> <mode> <decryption key (optional)>',
);
console.log('No valid mode provided. Use "dsmr" or "dlms".');
process.exit(1);
}

if (DECRYPTION_KEY) {
console.log(`Decryption key: ${DECRYPTION_KEY}`);
}
Expand Down Expand Up @@ -105,24 +120,50 @@ while (true) {
});

// Create a DSMR parser that listens to the stream.
const parser = DSMR.createStreamParser({
stream,
decryptionKey: DECRYPTION_KEY,
detectEncryption: true,
callback: (error, result) => {
if (error instanceof DSMRError) {
console.error('Error parsing DSMR data:', error.message);
console.error('Raw data:', error.rawTelegram?.toString('hex'));
} else if (error) {
console.error('Error:', error);
} else {
// Not very useful to log the raw telegram here as it is already logged by the data listener on the stream.
delete result.raw;
console.log('Parsed telegram:');
console.dir(result, { depth: Infinity });
}
},
});
const parser = (() => {
if (MODE === 'dsmr' && !DECRYPTION_KEY) {
return new UnencryptedDSMRStreamParser({
stream,
detectEncryption: true,
callback: (error, result) => {
if (error) {
console.error('Error parsing DSMR data:', error);
} else {
console.log('Parsed telegram:');
console.dir(result, { depth: Infinity });
}
},
});
}

if (MODE === 'dsmr' && DECRYPTION_KEY) {
return new EncryptedDSMRStreamParser({
stream,
decryptionKey: DECRYPTION_KEY,
callback: (error, result) => {
if (error) {
console.error('Error parsing DSMR data:', error);
} else {
console.log('Parsed telegram:');
console.dir(result, { depth: Infinity });
}
},
});
}

return new DlmsStreamParser({
stream,
decryptionKey: DECRYPTION_KEY,
callback: (error, result) => {
if (error) {
console.log('Error parsing DLMS data:', error.message);
} else {
console.log('Parsed DLMS telegram:');
console.dir(result, { depth: Infinity });
}
},
});
})();

// Don't continue the loop until the connection is closed.
await new Promise((resolve) => {
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@athombv/dsmr-parser",
"version": "1.2.2",
"version": "2.0.0-2",
"description": "DSMR Parser for Smart Meters",
"type": "module",
"main": "dist/index.js",
Expand All @@ -12,7 +12,7 @@
".": "./dist/index.js"
},
"scripts": {
"test": "node --import tsx --test-reporter spec --test ./tests/*.spec.ts",
"test": "node --import tsx --test-reporter spec --test ./tests/**/*.spec.ts",
"lint": "eslint . && npm run prettier:check",
"build": "tsc",
"prettier": "prettier . --write",
Expand Down
Loading