Compare commits

...
333 Commits
Author SHA1 Message Date
retoor 8f2772e4e7 Added review.
Tests / Python ${{ matrix.python-version }} (3.10) (push) Successful in 5m31s
Tests / Python ${{ matrix.python-version }} (3.12) (push) Successful in 3m4s
Tests / Python ${{ matrix.python-version }} (3.11) (push) Successful in 10m46s
Tests / Python ${{ matrix.python-version }} (3.7) (push) Successful in 3m11s
Tests / Python ${{ matrix.python-version }} (3.9) (push) Successful in 2m39s
Tests / Python ${{ matrix.python-version }} (3.8) (push) Successful in 10m58s
2024-12-29 20:48:56 +01:00
retoor 0ef1b057a6 Added review.
Tests / Python ${{ matrix.python-version }} (3.12) (push) Waiting to run
Tests / Python ${{ matrix.python-version }} (3.7) (push) Waiting to run
Tests / Python ${{ matrix.python-version }} (3.8) (push) Waiting to run
Tests / Python ${{ matrix.python-version }} (3.9) (push) Waiting to run
Tests / Python ${{ matrix.python-version }} (3.10) (push) Has been cancelled
Tests / Python ${{ matrix.python-version }} (3.11) (push) Has been cancelled
2024-12-29 20:47:35 +01:00
Nigel 99ad21c896 added a bit of documentation on how to run tests (#165)
* added a bit of documentation on how to run tests

* formatting

* formatting

* formatting

* formatting

* formatting

* formatting
2024-08-25 14:55:22 +02:00
Nigel 94189b6b63 Preparing release 1.4.2 2024-07-14 14:21:13 +02:00
Dennis Siemensma 8829260b15 Bump Github Actions to latest versions in favor of Node deprecations (#159) 2024-06-15 11:33:15 +02:00
Ido Szargel 55ac551a2a Add socket timeout (#155)
* Add socket timeout
2024-06-10 16:19:01 +02:00
J. Nick Koston fc65326370 Swap pyserial-asyncio for pyserial-asyncio-fast (#158)
* Swap pyserial-asyncio for pyserial-asyncio-fast

fixes #154

* actually commit the import change
2024-06-07 16:08:35 +02:00
Nigel 059c0802ad Fix changelog 2024-06-04 16:06:38 +02:00
Nigel d7f30b5069 Release 1.4.1 2024-06-04 15:59:32 +02:00
Joakim Plate b42ceb6555 Avoid loading timezone at runtime (#157)
* Avoid loading timezone at runtime

Pytz will load timezone info from files in
a lazy fashion on first access. This triggers warnings in HA due to it blocking the event loop.

Pre-load the needed timezone info at module import instead, which will run in
executor in HA.

* Update value_types.py
2024-06-04 15:51:03 +02:00
gigatexel 3cf627eedc Change checksum log level (#151)
* Update filereader.py

* Update socket_.py

* Update serial_.py

* Update protocol.py

* Update socket_.py
2024-03-28 12:40:17 +01:00
Nigel d680d44683 Update CHANGELOG.rst 2024-03-12 13:26:04 +01:00
Nigel 6387cc4440 CHANGELOG formatting 2024-03-12 12:56:13 +01:00
Nigel 61985c5c89 Changelog formatting 2024-03-12 12:54:28 +01:00
Nigel 88aaa2d30f Release 1.4.0 2024-03-12 12:50:36 +01:00
dupondje 1ce0925b61 Copy head_parsers list on construct. (#150)
We need to create a copy of the head_parsers.
Otherwise the self.value_formats contains just a reference to
head_parsers.
But the self.value_formats is modified in the _parse_values, causing
invalid values in the head_parsers list.
2024-03-12 11:14:43 +01:00
dupondje bc35a051f2 Change DSMR 5 and 5B to use DSMR devices (#142)
At least the BE and the NL DSMR devices use MBUS.
Therefore we make the MBUS obis reference generic, and convert the V5
telegram to use MBUS values.

Finally set self.maxDiff = None to have a full test output on some
tests.
2024-03-07 11:21:23 +01:00
Gunnar Klauberg fcbcbb30e0 Q3D add CURRENT_ELECTRICITY_DELIVERY (#149)
add CURRENT_ELECTRICITY_DELIVERY sensor for Q3D
2024-02-19 14:31:09 +01:00
Nigel be61d4aaf9 Prepare release 1.3.2 2024-01-29 14:29:02 +01:00
Nigel 1680cacf49 fix protocol unit test for python3.12 (#148)
* fix protocol unit test for python3.12

* add workflow test for python 3.12
2024-01-29 14:25:39 +01:00
dupondje 739419e3ee Remove BELGIUM_MAX_POWER_PER_PHASE (#144)
This commit removes BELGIUM_MAX_POWER_PER_PHASE and uses
ACTUAL_TRESHOLD_ELECTRICITY as this has same regex. So useless to keep
the BELIUM variant.
2023-11-07 12:10:42 +01:00
Jean-Louis Dupond d2ff6f4bb9 Fix tests after FUSE_TRESHOLD rename 2023-11-06 15:10:36 +01:00
Jean-Louis Dupond a316d93082 Adjust the naming of the MAX_POWER and MAX_CURRENT obis values 2023-11-06 15:10:36 +01:00
Jean-Louis Dupond 01d7622c40 Release 1.3.1 2023-11-06 15:09:47 +01:00
dupondje e8a86272ea Merge pull request #143 from dupondje/fix_invalid_occured
Fix parsing peak usage when value is not set
2023-11-06 14:30:20 +01:00
Jean-Louis Dupond 389bcefb50 Fix parsing peak usage when value is not set
Sometimes peak usage is not yet visible in the 13 months history code.
It gives a 0.0kW value with timestamp (632525252525W).

Also the peak usage value can be invalid, handle this case also.
2023-11-06 09:50:34 +01:00
Nigel 88923622a9 Release 1.3.0 2023-08-01 09:26:40 +02:00
balazs92117 2f1e080df3 Refactor of name mapping, added E.ON Hungary (#137)
* Added EON HUNGARY specification

* refactoring obis name mapping
2023-08-01 09:23:17 +02:00
Nigel af2e6558de Revert "Added E.ON HUNGARY specification (#134)" (#135)
This reverts commit 0752deb58a.
2023-07-26 15:03:29 +02:00
balazs92117 0752deb58a Added E.ON HUNGARY specification (#134)
* Added EON HUNGARY specification
2023-07-26 13:31:59 +02:00
Nigel 3fd032f18f Release 1.2.4 2023-07-11 22:01:00 +02:00
Emilio Jose Palacios-Garcia 4602e8c99f EQUIPMENT IDENTIFIER is wrong for Fluvius meters when other mbus devices are present (#133)
* change obis of fluvius meter ID to BELGIUM_EQUIPMENT_IDENTIFIER

* update test case to use direct access to attributes

* fix test case for pipeline
2023-07-11 21:43:41 +02:00
Emilio Jose Palacios-Garcia f3444a515d EQUIPMENT IDENTIFIER is wrong for Fluvius meters when other mbus devices are present (#133)
* change obis of fluvius meter ID to BELGIUM_EQUIPMENT_IDENTIFIER
2023-07-11 21:43:25 +02:00
Nigel 99c7b0d9f0 Prepare release 1.2.3 2023-04-18 20:48:30 +02:00
dupondje 84973872af Fix parsing tests and line start matching (#132)
* Raise parsing exception and check it in tests

* use multiline regex and match everything at the line start
2023-04-14 16:51:01 +02:00
Nigel ae8a2baaab Merge branch 'master' of github.com:ndokter/dsmr_parser 2023-04-12 10:58:40 +02:00
Nigel d3fab4f105 Release 1.2.2 2023-04-12 10:57:38 +02:00
Nigel 5a59c36646 improve performance parsing speed by about 6% by caching compiled regexes (#131)
* improve performance parsing speed by about 6% by caching compiled regexes
2023-04-12 10:52:11 +02:00
Nigel 9847bdf66b Prepare releasse 1.2.1 2023-04-05 11:19:42 +02:00
Nigel 76da334234 Update release notes for 1.2.0 where not all additions where added 2023-04-05 11:14:43 +02:00
Emilio Jose Palacios-GarciaandEmilio Jose Palacios Garcia c03d2483d7 Bug/duplicate index BELGIUM_MAXIMUM_DEMAND_13_MONTHS (#129)
* fix index offset for month timestamp in BELGIUM_MAXIMUM_DEMAND_13_MONTHS

* add timestamps to test case

---------

Co-authored-by: Emilio Jose Palacios Garcia <epalacio@emilio.esat.kuleuven.be>
2023-04-05 11:06:21 +02:00
Nigel de167c89b6 issue-51-telegram refactored TelegramParser.parse to return Telegram … (#121)
* issue-51-telegram improved mbus device parsing; refactored TelegramParser.parse to return Telegram object which is backwards compatible with the dict result
2023-02-19 12:24:44 +01:00
Nigel 6fb2ad3495 Merge pull request #126 from jchevalier7/feat/iskra_ie
Add Iskra IE.x meters specification
2023-02-12 13:45:43 +01:00
Nigel f156995138 Merge pull request #125 from dupondje/ignore_invalid_time
Fix parsing with invalid timestamps
2023-02-12 13:43:43 +01:00
AA61SL fe175a9a69 feat: Fixing test file to run setup.py test 2023-02-09 11:07:21 +01:00
AA61SL 78880a1881 feat: add Iskra IE.x meters 2023-02-09 11:01:00 +01:00
Nigel ca8392504d Release 1.1.0 2023-02-08 12:02:14 +01:00
Jean-Louis Dupond edf535f987 Fix parsing with invalid timestamps
Sometimes the timestamp in the DSMR message is invalid (when no data
read read from the mbus meter?), and then parsing fails.
Fixing this by handling the exception and returning None for invalid
timestamps. Fixes: #120
2023-01-30 14:45:32 +01:00
Nigel 026b91cd49 Merge pull request #124 from yada75/reactive
add instantaneous reactive power + fixed swapped reactive total import export
2023-01-28 13:32:32 +01:00
yada75 02eb3a34dd Update obis_name_mapping.py 2023-01-18 20:02:54 +01:00
yada75 142b7359a1 Update telegram_specifications.py 2023-01-18 19:39:29 +01:00
yada75 21b8547655 Update obis_name_mapping.py 2023-01-18 19:34:35 +01:00
yada75 a2b9fe5e3b Update obis_references.py 2023-01-18 19:31:39 +01:00
Nigel 0a259a3c9d fix code style 2023-01-01 20:55:58 +01:00
Nigel f851f48225 Merge pull request #118 from dennissiemensma/bug/readme-shield
Fixed Shields badge in favor of badges/shields#8671
2023-01-01 20:37:24 +01:00
Nigel 3a3054324a Merge pull request #116 from lowdef/add_pylama_backin_post_py36
add pylama back in + fix uniform style issues reported by pylama
2023-01-01 20:36:39 +01:00
Nigel 041ac758f4 Merge pull request #115 from robcalon/patch-1
asynchronous read_as_object method
2023-01-01 20:35:48 +01:00
Dennis Siemensma d50d775e38 Fixed Shields badge in favor of badges/shields#8671 2022-12-30 21:05:17 +01:00
Nigel 10613acbd9 Merge pull request #114 from dupondje/fix_name_mapping
Fix missing value from name mapping
2022-12-29 19:50:26 +01:00
Hans Erik van Elburg 5f58bc8f72 add pylama back in + fix uniform style issues reported by pylama 2022-12-27 18:58:48 +01:00
Rob Calon 371e1f3c5e update docstring 2022-12-27 17:36:10 +01:00
Rob Calon 0eae28e183 correct rtype 2022-12-27 17:27:23 +01:00
Rob Calon 431ddadb5f asynchronous read_as_object method
fix inheritance of synchronous read_as_object method from parent
2022-12-27 17:20:24 +01:00
Jean-Louis Dupond 9e01dc32c0 Fix missing value from name mapping 2022-12-26 12:50:59 +01:00
Nigel 650d16862b Fix changelog 2022-12-22 10:18:47 +01:00
Nigel 301a00e226 Prepare release 1.0.0 2022-12-22 10:13:26 +01:00
Nigel d0cc429eda Merge pull request #113 from dupondje/add_peakusage
Add peakusage
2022-12-12 20:32:42 +01:00
Jean-Louis Dupond d05fe2692b Add support for Fluvius V1.7.1 DSMR messages
These include (since 1.6) the water mater messages.
And since 1.7.X also peak usage values.

https://maakjemeterslim.be/rails/active_storage/blobs/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBZ0lEIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--cdd9b48fd0838e89b177f03b745b23450fd8f53e/e-MUCS_P1_Ed_1_7_1.pdf?disposition=attachment
2022-12-12 20:27:53 +01:00
Dennis Siemensma e80ba9862b No longer run Python 3.6 in CI as it is end-of-life 2022-12-07 20:33:58 +01:00
Dennis Siemensma cffcac2e87 Bump versions of Actions used in CI 2022-12-07 20:32:01 +01:00
Dennis Siemensma 357bae1bce Run tests for Python 3.11 as well 2022-12-07 20:30:58 +01:00
Nigel 98a10998e9 Remove pylama because of incompatibility with python3.6, maybe find replacement 2022-10-19 14:20:29 +02:00
Nigel 00ac7c71be Prepare version 0.34 2022-10-19 13:52:08 +02:00
Nigel 594db890fe Merge pull request #110 from debug-richard/master
Adds support for the Sagemcom T210-D-r smart meter
2022-10-16 12:49:55 +02:00
Richard Schleich 09f4afcada Adds support for the Sagemcom T210-D-r smart meter installed by Austrian grid operators.
The smart meter uses the DSMR/P1 standard and embeds the data in an encrypted and authenticated "DLMS General Global Cipher" frame.
The encryption/decryption is handled by the "dlms_cosem" lib.
Test cases are included.
Adds OBIS codes for reactive energy.
2022-10-14 17:50:02 +02:00
Nigel 2aba206c6f Merge branch 'master' of github.com:ndokter/dsmr_parser 2022-04-20 22:10:27 +02:00
Nigel 247a7446f5 Prepare release 0.33 2022-04-20 22:10:06 +02:00
Nigel 5c378f3419 Update README.rst 2022-04-20 22:02:35 +02:00
Nigel b825faa719 Update README.rst 2022-04-20 22:00:58 +02:00
Nigel 7f35cd3c73 Merge pull request #63 from mjkl-gh/master
Improve documentation asyncio
2022-04-20 21:58:59 +02:00
Nigel 63338fbf06 Merge branch 'master' into master 2022-04-20 21:58:51 +02:00
Nigel dd9e264f5c Merge pull request #107 from dennissiemensma/feature/python-3.10
Test Python 3.10 in CI + legacy badge fix
2022-04-20 21:48:09 +02:00
Nigel 5c4d6ed98b Merge pull request #106 from gigatexel/master
Update telegram_specifications.py
2022-04-20 21:47:27 +02:00
Dennis Siemensma f238eb14a1 Converted markdown link to restructured link 2022-04-20 21:32:41 +02:00
Dennis Siemensma 179a75e58c Typo fix 2022-04-20 21:30:50 +02:00
Dennis Siemensma 32c20b61ac Refer to CI config for the currently supported/tested Python versions 2022-04-20 21:29:08 +02:00
Dennis Siemensma 602ed4928a YAML fix for CI config 2022-04-20 21:28:31 +02:00
Dennis Siemensma f5bedb1e6e CI now tests Python 3.10 as well 2022-04-20 21:26:07 +02:00
Dennis Siemensma bc6eab73de Replace legacy Travis status badge with GitHub Actions 2022-04-20 21:25:24 +02:00
gigatexel e5eddd006e Update telegram_specifications.py 2022-04-08 14:20:11 +02:00
gigatexel 0ed5fc0220 Update telegram_specifications.py 2022-04-08 14:09:56 +02:00
Nigel c0a509dab3 Merge pull request #105 from gigatexel/master
Improve compatiblity with Belgian standard
2022-04-08 10:06:02 +02:00
gigatexel 3d5599289e Update obis_name_mapping.py 2022-04-08 09:57:02 +02:00
gigatexel 99ab86fffb Update telegram_specifications.py 2022-04-08 09:56:36 +02:00
gigatexel 15b3653a02 Update telegram_specifications.py 2022-04-06 13:30:58 +02:00
gigatexel 527730781c Update obis_name_mapping.py 2022-04-06 13:30:12 +02:00
gigatexel 3f41a73b9d Update obis_references.py 2022-04-06 13:28:57 +02:00
Nigel ddbcb67088 Merge pull request #104 from ndokter/issue-101
Issue 101
2022-02-23 13:39:30 +01:00
Nigel 746fbc4fff Merge pull request #103 from vladimirvrabely/issue-101
Add FileReader test
2022-02-22 20:35:03 +01:00
Nigel 304593c246 Merge pull request #102 from ndokter/issue-101
fix FileReader from not completing
2022-02-22 20:33:31 +01:00
Vladimir 7610646e10 Add FileReader test 2022-02-22 09:50:11 +01:00
Nigel Dokter a6bc66667b fix FileReader from not completing 2022-02-21 20:05:23 +01:00
Nigel 0340b25733 Update README.rst 2022-02-21 13:38:06 +01:00
Nigel 5c838efbe1 Fix CHANGELOG formatting 2022-01-04 21:16:03 +01:00
Nigel 9f66c075d6 Prepare version 0.32 2022-01-04 21:14:53 +01:00
Nigel 2dafc7e7e4 Merge branch 'master' of github.com:ndokter/dsmr_parser 2022-01-04 21:10:44 +01:00
Nigel b74572238e Merge pull request #98 from rhpijnacker/feature/rfxtrx
Support DSMR data read via RFXtrx with integrated P1 reader
2022-01-04 09:59:54 +01:00
Ronald Pijnacker cedf71dbb5 Merge remote-tracking branch 'upstream/master' into feature/rfxtrx 2022-01-04 09:55:00 +01:00
Ronald Pijnacker eace91b591 Fix coding style issues 2022-01-04 09:53:49 +01:00
Ronald Pijnacker dd6d26670e Rewrite for compatibility with python 3.6 2022-01-04 09:49:11 +01:00
Ronald Pijnacker 7d28d0e370 Update according to coding style 2022-01-03 21:25:19 +01:00
Ronald Pijnacker c082cf4868 Add test case 2022-01-01 20:19:24 +01:00
Ronald Pijnacker 188cac5287 Small update 2021-12-30 20:32:44 +01:00
Ronald Pijnacker c7ed4acb03 Refactor into separate file 2021-12-30 20:23:58 +01:00
Ronald Pijnacker 8b64adb80c Small rename 2021-12-30 17:23:31 +01:00
Ronald Pijnacker 3eed3654d4 Wrap DSMR protocol in RFXtrx wrapper 2021-12-30 17:01:29 +01:00
Nigel b562510e40 Merge pull request #96 from lowdef/master
resolve name clash as reported in issue #95
2021-11-28 12:27:22 +01:00
Hans Erik van Elburg f0e035c8ed fix newer version pylama errors 2021-11-28 01:12:32 +01:00
Hans Erik van Elburg 83ef354c12 resolve name clash as reported in issue #95 2021-11-27 19:55:58 +01:00
Nigel dc2f493739 Merge pull request #70 from bremme/master
Add SocketReader to connect to remote serial port
2021-11-21 18:10:17 +01:00
Nigel 0a20dea7ed Merge pull request #93 from lowdef/master
fix pylama errors on PR92 + editorial fix
2021-11-21 17:56:01 +01:00
Hans Erik van Elburg 28e3c51f0d fix pylama errors on PR92 + editorial fix 2021-11-21 16:08:47 +01:00
Nigel 60af45be25 Prepare version 0.31 2021-11-21 14:06:11 +01:00
Nigel 00248f490e Merge pull request #92 from Aeroid/master
Support for (German) EasyMeter Q3D using COM-1 Ethernet Gateway
2021-11-21 13:59:35 +01:00
Gunnar Klauberg 45cc88942a latin-1 changes folded into data_received 2021-11-21 11:25:05 +00:00
Gunnar Klauberg 26ac27c347 re-adding ascii decoding at telegram level 2021-11-14 22:29:37 +00:00
Gunnar Klauberg 7a89d6e97b re-adding ascii decoding at telegram level 2021-11-14 22:20:00 +00:00
Gunnar Klauberg de4dc2ec98 Q3D COM-1 test telegrams 2021-11-14 22:05:27 +00:00
Gunnar Klauberg 6866e7d585 leaving version as it was 2021-11-14 21:52:38 +00:00
Gunnar Klauberg df59f3a498 decode latin1 and added ELECTRICITY_EXPORTED_TOTAL 2021-11-14 21:49:43 +00:00
Gunnar Klauberg 4073917d3e decode latin1 and added ELECTRICITY_EXPORTED_TOTAL 2021-11-14 21:29:24 +00:00
Nigel 3a4fcd7da1 Merge pull request #89 from dennissiemensma/feature/github-actions-with-fixes
Replace Travis with GitHub Actions (+ minor fixes)
2021-11-14 21:08:09 +01:00
Nigel d5b427ba4f Merge pull request #82 from Lennart99/master
add special option for Landis+Gyr E360 (DMSR 5 with serial version 4)
2021-11-14 21:06:58 +01:00
Gunnar Klauberg c1202f33e9 clean-up re-format 2021-11-12 20:12:47 +00:00
Gunnar Klauberg 3092ba8b1f clean-up re-format 2021-11-12 19:24:44 +00:00
Gunnar Klauberg 8e2bdd3279 clean-up re-format 2021-11-12 19:23:38 +00:00
Gunnar Klauberg 2b724b87c3 clean-up re-format 2021-11-12 19:21:32 +00:00
Gunnar Klauberg 17b56ae07b clean-up re-format 2021-11-12 17:51:24 +00:00
Gunnar Klauberg e4f384c2b7 clean-up re-format 2021-11-12 17:48:21 +00:00
Gunnar Klauberg 8b8b952ce1 clean-up re-format 2021-11-12 17:38:16 +00:00
Gunnar Klauberg 007b3ea089 ignoring trailing \xff in byte stream 2021-11-12 18:13:48 +01:00
Gunnar Klauberg 761aaccb3f adding EasyMeter Q3D support 2021-11-12 15:19:43 +00:00
Nigel dc608f9da3 Update README.rst 2021-10-31 13:08:42 +01:00
Dennis Siemensma 88ef4f4921 Split dependency installation step in Actions 2021-09-22 21:38:24 +02:00
Dennis Siemensma 61de170a79 Tox no longer relies on every Python version installed, due to multiple CI runners 2021-09-22 21:36:46 +02:00
Dennis Siemensma 947fd64377 E501 line too long (170 > 120 characters) [pycodestyle] 2021-09-22 21:33:22 +02:00
Dennis Siemensma 62aa0ac3ab W0611 'asyncio' imported but unused [pyflakes] 2021-09-22 21:31:11 +02:00
Dennis Siemensma da98cf1d05 Drop Travis (travis.org is EOL) 2021-09-22 21:28:36 +02:00
Dennis Siemensma fcb0dc600b CI tests using Github Actions 2021-09-22 21:28:17 +02:00
Dennis Siemensma b3a705a74d "pytest-catchlog plugin has been merged into the core, please remove it from your requirements" 2021-09-22 21:27:22 +02:00
Dennis Siemensma 4688cf9058 test/test_protocol.py:6:1: W0611 'dsmr_parser.telegram_specifications' imported but unused [pyflakes]
test/test_protocol.py:7:1: W0611 'dsmr_parser.parsers.TelegramParser' imported but unused [pyflakes]
2021-09-22 21:26:54 +02:00
Lennart99 fb9e488102 Merge branch 'ndokter:master' into master 2021-09-07 20:40:35 +02:00
Nigel d7e1f41162 Prepare version 0.30 2021-08-18 16:59:19 +02:00
Nigel 45de349062 Prepare version 0.29 2021-08-18 16:54:44 +02:00
Nigel f46f88bdb8 Merge pull request #86 from emontnemery/dsmr_sweden
Add support for Swedish smart meters
2021-08-16 17:47:47 +02:00
Erik fa70ada0bf Add support for Swedish smart meters 2021-08-14 17:08:27 +02:00
Lennart99 e30c951c7f add special option for Landis+Gyr E360 (DMSR 5 with serial version 4) 2021-06-10 12:23:20 +02:00
Nigel d989cfd0e6 Merge pull request #78 from fabaff/patch-1
Add license tag
2021-04-22 10:24:02 +02:00
Fabian Affolter ada02bf993 Add license tag 2021-04-21 23:36:25 +02:00
Nigel e607b62fa2 Merge pull request #71 from lowdef/fix_ProfileGenericObject_for_telegram_iterator
add value and unit properties to ProfileGenericObject to make sure they do not break
2021-03-04 21:49:03 +01:00
Nigel 247860be16 Merge pull request #76 from frenck/frenck-2021-0772
Remove deprecated asyncio coroutine decorator
2021-03-04 21:48:04 +01:00
Nigel c590109a1d Update README.rst
Fixed small typo
2021-03-04 21:47:12 +01:00
Franck Nijhof adaa2dcad5 Remove deprecated asyncio coroutine decorator 2021-03-03 11:48:49 +01:00
Hans Erik van Elburg 8c861ee308 resolved comment 2021-02-14 22:08:35 +01:00
Nigel f806cc01d3 Preparing release 0.28 2021-02-12 17:56:33 +01:00
Nigel 399532f244 Merge pull request #73 from hogend/keep-alive
Optional keep alive monitoring for TCP/IP connections
2021-02-12 17:38:48 +01:00
Rene Hogendoorn b901b3f74e Add unit test for keep-alive 2021-02-11 11:08:19 +01:00
Nigel a255380953 Merge pull request #74 from lowdef/issue72_robustness_fix
catch parse errors in TelegramParser, ignore lines that can not be parsed
2021-02-07 14:52:45 +01:00
Hans Erik van Elburg 1cdda2eaba catch parse errors in TelegramParser, ignore lines that can not be parsed 2021-02-07 13:40:20 +01:00
Rene Hogendoorn 7453534927 Raised log level to warning for failed keep-alive check 2021-01-19 10:55:20 +01:00
Rene Hogendoorn bbd73897a0 Optional keep alive monitoring for TCP/IP connections
* Since dsmr-parser is listen-only, it will not notice interrupted connections
  and DSMR device restarts. The connection will be reset after an (optional)
  keep-alive interval if no messages were received from the device.
2021-01-19 08:26:28 +01:00
Hans Erik van Elburg 804747c370 add value and unit properties to ProfileGenericObject to make sure that code like iterators that rely on that do not break 2021-01-03 19:58:19 +01:00
bremme 97786576cf Add SocketReader documentation 2020-12-27 18:58:14 +01:00
bremme e12aff5c0d Add SocketReader for reading ipv4 tcp sockets 2020-12-27 18:57:21 +01:00
Nigel 3dc77a8231 Prepare for release 0.27 2020-12-24 21:46:41 +01:00
Nigel d61f2229c8 Merge pull request #69 from lowdef/issue57_revisited
Issue57 revisited
2020-12-24 21:42:08 +01:00
Hans Erik van Elburg 3ddf0366e6 small fix 2020-12-24 12:44:49 +01:00
Hans Erik van Elburg 629767590b finished tox tests for issue 57 fix 2020-12-24 12:37:45 +01:00
Hans Erik van Elburg 81cccbd228 fix tox tests 2020-12-24 01:52:17 +01:00
Hans Erik van Elburg 5b1e830018 make sure that for the special case (actually invalid syntax) where a ProfileGeneric line only contains (); an empty ProfileGenericObject is created 2020-12-24 00:22:29 +01:00
albert 2d712b506d referring to parent method from _parse_values in ProfileGenericParser 2020-12-23 18:47:34 +01:00
albert 1318204d0c tempfix for empty profileGenericParser 2020-12-23 18:03:49 +01:00
Nigel feb0f88ddc Preparing release 0.26 2020-12-15 15:03:22 +01:00
Nigel 602129a665 Merge pull request #68 from ndokter/revert-57-master
Revert "fix for empty parentheses in ProfileGenericParser"
2020-12-15 15:00:07 +01:00
Nigel af9a99d995 Revert "fix for empty parentheses in ProfileGenericParser" 2020-12-15 14:59:35 +01:00
Nigel a7b4929eab preparation for v0.25 2020-12-14 17:21:16 +01:00
Nigel 8999b1c924 Merge branch 'master' of github.com:ndokter/dsmr_parser 2020-12-14 17:16:55 +01:00
Nigel 2391f38c4a Merge pull request #57 from avandermeer/master
fix for empty parentheses in ProfileGenericParser
2020-12-14 16:55:38 +01:00
Maarten Kleijwegt bc7961f840 add asyncio read as object 2020-12-13 16:36:30 +01:00
Nigel d1a6e15c68 Merge pull request #66 from GuyFoetz/foreign_name_mapping
adding the # Alternate codes for foreign countries.
2020-12-13 13:22:15 +01:00
Guy Foetz b7c8626d0c adding the # Alternate codes for foreign countries. 2020-12-13 12:15:51 +00:00
Nigel 684023d0b6 Preparing for release 0.24 2020-11-27 21:18:59 +01:00
Your Name 58851b5c5c Merge branch 'master' of https://github.com/mjkl-gh/dsmr_parser 2020-11-22 23:11:32 +01:00
Your Name ba9f3f3c25 First draft addin async documentation 2020-11-22 23:10:04 +01:00
Nigel 1a886aa03b Merge pull request #62 from RobBie1221/add_lux_eq
Add Luxembourg equipment identifier
2020-11-12 17:10:54 +01:00
Nigel 1c7535448e Updated changelog and version 2020-11-07 08:56:24 +01:00
Rob Bierbooms 2a95d33c02 Add Luxembourg equipment identifier 2020-11-04 20:42:38 +01:00
Nigel de6725dff8 Merge pull request #58 from dunajski/master
Corrects example telegrams.
2020-11-03 21:29:35 +01:00
Nigel 1b1a3f505e Merge pull request #59 from scop/dont-install-tests
Do not install tests
2020-11-03 21:27:21 +01:00
Nigel ebea385753 Merge pull request #61 from lowdef/issue60
issue#60 resolved issue with x-x:24.3.0 where it contains non-integer…
2020-11-03 21:25:39 +01:00
Hans Erik van Elburg 700cf6a2b3 issue#60 resolved issue with x-x:24.3.0 where it contains non-integer characters, parse 2nd value as string instead of integer 2020-11-02 00:29:03 +01:00
Ville Skyttä 7a687a99a6 Do not install tests 2020-10-18 10:49:09 +03:00
dunajski 86d665df39 Change README.md according to change in ec1d37e
In ec1d37ea08 changed example telegrams,
so example telegram from README.md should be corrected too.
2020-09-28 11:35:47 +02:00
dunajski ec1d37ea08 Corrects example telegrams.
In example telegrams there a few mistakes in header strings. Using '\'
in strings have special feature to add to string some non-printable
characters, for example to add new line we can add \n for tab \t etc. So
whenever we want to just print backslash as normal character we have to
add another '\' before. In short string "On\\Off" prints "On\Off" ascii
string.

In enquired telegrams there are few strings with \2 sign in header,
'/ISk5\2MT382-1000\r\n' and when we calculate CRC for whole given
telegram part of "..\2.." is interpreted as one character (value 0x02)
instead of two characters ('\', '2').

I think in a header in the example enlosed in P1 DSMR Companion Standard
/ISk5\2.. stand for printable '\' and '2' character instead of
"converted" to special '\2' character.
2020-09-28 10:22:16 +02:00
albert dc902a83e9 referring to parent method from _parse_values in ProfileGenericParser 2020-09-08 12:30:51 +02:00
albert be4386bc59 tempfix for empty profileGenericParser 2020-09-07 18:15:49 +02:00
Nigel 0427ace079 Updated changelog 2020-08-23 09:36:31 +02:00
Nigel addf9b590e Merge pull request #55 from aequitas/exception
Pass exception to log.exception as it is not called from within an ex…
2020-08-23 09:32:44 +02:00
Nigel 51f821a7fc Merge pull request #49 from dupondje/fix_pycrc
Improve CRC speed
2020-08-23 09:30:22 +02:00
Johan Bloemberg 5630520771 Pass exception to log.exception as it is not called from within an except: clause. Add Python 3.8 test support, drop Python 3.4 test support. 2020-07-21 10:31:40 +02:00
Nigel f9e188812f Merge pull request #54 from lowdef/fix_json_serialization
Fix json serialization
2020-05-29 17:22:53 +02:00
Hans Erik van Elburg 837ba3b6f7 add json serialization 2020-05-25 01:38:14 +02:00
Hans Erik van Elburg 94447c3571 GenericProfileObject value: make embedded buffer items also appear as dicts 2020-05-17 16:58:28 +02:00
Hans Erik van Elburg c2dea29c83 add a value property to GenericProfileObject, return a dict 2020-05-17 16:49:29 +02:00
Nigel bb486fc76f Merge pull request #53 from lowdef/profile_generic_parser
Profile generic parser
2020-05-17 09:12:56 +02:00
Hans Erik van Elburg 789871899c ProfileGeneric parser working, ProfileGenericObject implemented and Test for V4 telegram completed. 2020-05-17 01:25:02 +02:00
Hans Erik van Elburg b6278a8991 ProfileGeneric parser working, TODO complete ProfileGenericObject + Test 2020-05-16 16:31:26 +02:00
Hans Erik van Elburg a0ce89054a make all objects able to print their own values 2020-05-12 23:45:16 +02:00
Hans Erik van Elburg 9610fbc3c1 Merge branch 'add_missing_elements_DSRM_Specification_4' 2020-05-11 22:22:00 +02:00
Hans Erik van Elburg d1ad4fa585 igonre venv/ 2020-05-11 21:08:20 +02:00
Hans Erik van Elburg a44afb1a59 ignore .venv 2020-05-10 20:47:11 +02:00
Hans Erik van Elburg d98c93a57f modified changelog 2020-05-03 11:37:53 +02:00
Hans Erik van Elburg fc4a96ebab bump version 2020-05-03 11:18:08 +02:00
Hans Erik van Elburg c4331f6cd6 add tests for the missing elements and correct some test bugs 2020-05-02 15:50:00 +02:00
Hans Erik van Elburg 88c9ccd83d Add following missing signatures to the V4 telegram specification SHORT_POWER_FAILURE_COUNT, INSTANTANEOUS_CURRENT_L1, INSTANTANEOUS_CURRENT_L2, INSTANTANEOUS_CURRENT_L3. 2020-05-01 20:36:35 +02:00
Jean-Louis Dupond 78419f6cc7 Improve CRC speed 2020-02-16 09:31:06 +01:00
Nigel d423070853 Merge pull request #45 from lowdef/extend_filereader
Extend filereader
2020-02-03 17:31:15 +01:00
Nigel 987d35332a Merge branch 'master' into extend_filereader 2020-02-03 17:29:39 +01:00
lowdef fe710e9272 redo essential change 2020-02-02 22:54:21 +01:00
lowdef a7b0b03391 remove conflicting entries 2020-02-02 22:52:06 +01:00
Hans Erik van Elburg 5d88284d8d remove requires property again 2020-02-02 22:34:17 +01:00
Hans Erik van Elburg b6537678a7 cleaned up based on pylama complaints / pinpointed to coverage version 4.5.4 as next version is incompatible 2020-02-02 22:12:25 +01:00
Hans Erik van Elburg fee3f696c4 merged upstream 0.18 version and resolved conflict 2020-02-02 17:26:47 +01:00
Nigel dc6c35a0b6 Updated changelog 2020-01-28 19:40:27 +01:00
Nigel 47c20e763f Merge branch 'master' of github.com:ndokter/dsmr_parser 2020-01-28 19:36:23 +01:00
Nigel e7164d9af7 Merge pull request #48 from dupondje/fix_pycrc
Include needed PyCRC code
2020-01-28 19:35:42 +01:00
Jean-Louis Dupond d714528c5a Include needed PyCRC code 2020-01-28 17:26:45 +01:00
Hans Erik van Elburg 0675a6e2e7 add tailer dependency 2019-12-27 16:22:10 +01:00
Hans Erik van Elburg e6625df4a7 add documentation to FileTailReader 2019-12-27 15:18:35 +01:00
Hans Erik van Elburg 8c2485d70f added file tail reader 2019-12-24 00:57:44 +01:00
Nigel eac9681e07 updated changelog 2019-12-21 17:37:50 +01:00
Nigel 659560222a Merge pull request #40 from lowdef/add_a_true_telegram_object
Add a true telegram object
2019-12-21 17:36:28 +01:00
Nigel 74fe1f2d1e Merge branch 'master' into add_a_true_telegram_object 2019-12-21 17:34:58 +01:00
Nigel a01e673646 updated changelog for new release 2019-12-21 15:02:13 +01:00
Nigel 09614860a6 Merge pull request #44 from dupondje/master
Add support for Belgian and Smarty meters
2019-12-21 14:57:02 +01:00
Jean-Louis Dupond 3bfb555d0e Add support for Belgian and Smarty meters 2019-12-21 14:53:26 +01:00
Hans Erik van Elburg 2d4b0d8e72 fix documentation FileInputReader 2019-12-16 15:41:24 +01:00
Hans Erik van Elburg 43500e6bc2 fix failing import 2019-12-16 15:36:13 +01:00
Hans Erik van Elburg 50cef2646b add fileinputreader 2019-12-16 15:30:55 +01:00
Nigel 3a8b4d2458 Corrected changelog 2019-12-12 22:33:14 +01:00
Nigel 12aa003799 Updated version number 2019-12-12 22:29:18 +01:00
Nigel 86690ef44b Merge pull request #43 from WoLpH/patch-1
`create_tcp_dsmr_reader` accepts `loop=None` but always expects a loop. Fixes: #36
2019-12-12 22:22:47 +01:00
Rick van Hattem 7c9c59308e create_tcp_dsmr_reader accepts loop=None but always expects a loop. Fixes #36 2019-12-12 22:20:16 +01:00
Hans Erik van Elburg c1d7ba151d add some documentation for the file reader 2019-12-09 23:48:48 +01:00
Hans Erik van Elburg a65949823d fix filereader 2019-12-09 23:27:57 +01:00
Hans Erik van Elburg 3c78b0b6c4 add file reader 2019-12-09 22:36:46 +01:00
Hans Erik van Elburg 5313edd6cb add file reader 2019-12-09 22:31:40 +01:00
Hans Erik van Elburg aa4f9fb967 update version 2019-12-01 18:55:53 +01:00
Hans Erik van Elburg ea804d485e add some documentation for the use of the telegram as an object 2019-12-01 18:39:21 +01:00
Hans Erik van Elburg 1b522fc7f0 add some documentation for the use of the telegram as an object 2019-12-01 18:34:21 +01:00
Hans Erik van Elburg a137ef0e02 add some documentation for the use of the telegram as an object 2019-12-01 17:47:22 +01:00
Hans Erik van Elburg d712d468ac remove useless space 2019-11-25 20:44:25 +01:00
Hans Erik van Elburg 95bab6a51a Merge branch 'master' into add_a_true_telegram_object 2019-11-25 20:30:35 +01:00
Hans Erik van Elburg 49999618ea Merge remote-tracking branch 'upstream/master' 2019-11-25 20:29:20 +01:00
Hans Erik van Elburg 8ba400800b small fixes 2019-11-25 01:53:54 +01:00
Hans Erik van Elburg f7ba363b93 small fixes 2019-11-25 01:37:48 +01:00
Nigel b31bcd61fc Updated version number 2019-10-08 22:13:58 +02:00
Hans Erik van Elburg f9e9e70771 experimentation file 2019-10-03 21:33:11 +02:00
Nigel Dokter 50a5cb9203 Merge pull request #37 from tneele/master
Improve performance: read more data from serial port at once
2019-07-29 10:13:13 +02:00
Thomas Neele 8388624721 Read more data from serial port at once
A telegram can contain dozens of lines. Reading them one by one is somewhat inefficient.
With this change, the client tries to read all data that is available.
This significantly reduced CPU load for me.
2019-07-22 21:33:15 +02:00
Hans Erik van Elburg c36f68a884 working version of the Telegram object 2019-06-06 05:41:55 +02:00
Hans Erik van Elburg 8bdf77c78d ensure build and dist directories are not synced 2019-04-06 12:56:27 +02:00
Nigel c04b0a5add Updated version number 2019-03-04 20:31:51 +01:00
Nigel Dokter ed2290a597 Merge pull request #33 from mleenaerts/patch-1
Fix DSMR v5.0 serial settings which were not used
2019-01-22 19:27:56 +01:00
Mark Leenaerts 85c67464a1 Fix DSMR v5.0 serial settings which were not used
While analysing some CRC check errors I encounter within the home-assistant plugin (which uses this component) I encountered this oversight in the code.
2019-01-19 16:11:12 +01:00
Nigel Dokter 48783acc00 Update CHANGELOG.rst 2018-09-23 13:01:22 +02:00
Nigel Dokter 41f6a7ac16 Update CHANGELOG.rst 2018-09-23 13:01:04 +02:00
Nigel Dokter 59811dbf9f Update setup.py 2018-09-23 12:59:02 +02:00
Nigel Dokter 9b09969763 Merge pull request #31 from tneele/master
Add serial settings for DSMR v5.0
2018-09-23 12:50:01 +02:00
Nigel Dokter 64f5dc2e87 Merge pull request #32 from bossjl/master
Lux-creos-obis-1.8.0
2018-09-23 12:49:42 +02:00
bossjl 887dd3a2aa Lux-creos-obis-1.8.0 2018-09-21 18:37:32 +02:00
Thomas Neele ad6ab304f5 Added serial settings for DSMR v5.0 2018-09-15 21:23:22 +02:00
Nigel Dokter 8582395017 Merge pull request #30 from jk-5/sagemcom-t210-d
Added missing values for 3-phase sagemcom TD210-D meter
2018-09-12 23:45:29 +02:00
jk-5 3327c78c0e Updated unittests 2018-09-06 15:49:11 +02:00
jk-5 472c54968e Corrected meter types 2018-09-06 15:40:02 +02:00
jk-5 d534b1d8b0 Added missing values for 3-phase sagemcom TD210-D meter 2018-09-06 14:53:30 +02:00
Nigel Dokter d94bc8de03 updated version 2017-09-18 12:02:36 +02:00
Nigel Dokter 41381f1705 Merge pull request #26 from vavdb/patch-1
Optional NUL after checksum
2017-09-18 11:55:31 +02:00
Vincent van den Braken e3203a5334 Optional NUL after checksum
My smart meter returns 00 0D 0A after the checksum, not just 0D 0A.
2017-09-15 13:48:10 +02:00
Nigel Dokter c78ebe3e2d fixed import errors 2017-06-05 21:06:48 +02:00
Nigel Dokter d6e28db116 log checksum errors as warning; dont force full telegram signatures; removed unused code for automatic telegram version detection; 2017-06-05 21:02:59 +02:00
Nigel Dokter 5f1afeb1f8 Merge pull request #25 from AlexMekkering/optional_telegram_lines
Support optional telegram signatures
2017-06-05 20:45:38 +02:00
Alex Mekkering dcb59fddb1 Support optional telegram signatures 2017-06-05 09:07:47 +02:00
Nigel Dokter 9b0a85e84b updated version 2017-05-12 21:37:54 +02:00
Nigel Dokter 1dca6ab396 Merge pull request #23 from aequitas/dsmr5
Add DSMR5 option to protocol.
2017-05-12 21:34:06 +02:00
Johan Bloemberg efc09df71f Add DSMR5 option to protocol. 2017-05-12 20:30:53 +02:00
Nigel Dokter de5c884d8d Small comment improvement 2017-02-27 20:44:18 +01:00
Nigel Dokter d2f57a8926 fixed pep8 2017-02-27 20:35:19 +01:00
Nigel Dokter 3d64fea247 Updated README 2017-02-27 20:26:15 +01:00
Nigel Dokter 46860e04c1 Formatting 2017-02-03 22:42:31 +01:00
Nigel Dokter 148bdabc12 Updated docs 2017-02-03 22:40:39 +01:00
Nigel Dokter 9d20bb8ad5 experimenting with version detection 2017-01-28 17:01:33 +01:00
Nigel Dokter 24ab9aa712 experimenting with version detection 2017-01-28 17:01:02 +01:00
Nigel Dokter 0c40070752 fixed changelog date 2017-01-26 21:18:56 +01:00
Nigel Dokter 6c8a9dcbdb added python 3.6 support 2017-01-26 19:06:03 +01:00
Nigel Dokter c1a6b930c8 try python 3.6 support 2017-01-26 19:04:01 +01:00
Nigel Dokter 9e74c4c23c adjusting supported python versions 2017-01-26 19:02:15 +01:00
Nigel Dokter b4a520c8b4 updated readme 2017-01-26 19:00:31 +01:00
Nigel Dokter a88dfe1a41 added DSMR v3 specification; updated changelog; 2017-01-26 18:50:30 +01:00
Nigel Dokter c4dcc73191 added DSMR v3 specification 2017-01-26 18:02:21 +01:00
Nigel Dokter c4caf54576 pep8 2017-01-25 19:34:38 +01:00
Nigel Dokter 8a868ce826 pep8 2017-01-25 19:33:42 +01:00
Nigel Dokter 9623f3b092 added unit test for DSMR v5 parsing 2017-01-25 19:32:30 +01:00
Nigel Dokter adcfdfe2ae fixed v2 specification typo; fixed wrongly edited obis reference; 2017-01-22 16:42:02 +01:00
Nigel Dokter 45ee8dbb32 added basic config for DSMR v5 specification; added DSMR v5 example telegram for testing; 2017-01-22 16:39:16 +01:00
Nigel Dokter 7a4c204850 added code comments 2017-01-21 10:42:17 +01:00
Nigel Dokter 45f5fe2c36 define checksum support in telegram specification; moved telegram v2 exception temporarily from parser to MBUSObject; 2017-01-21 10:33:17 +01:00
Nigel Dokter 07634abed1 Progress on removing TelegramParserV2_2 and TelegramParserV4 in favor of a generic TelegramParser 2017-01-20 23:02:19 +01:00
Nigel Dokter e2e4bb36a2 updated changelog 2017-01-14 20:10:42 +01:00
Nigel Dokter 4eeefec426 update version number to 0.7 2017-01-11 17:44:48 +01:00
Nigel Dokter f3d8311ac2 updated readme and changelog for upcoming breaking API changes; skip pylama check for unused imports in clients module; 2017-01-11 17:40:25 +01:00
Nigel Dokter 9b488e74f8 renamed serial.py to serial_.py 2017-01-10 20:57:50 +01:00
Nigel Dokter fadf206715 moved serial clients to own package 2017-01-10 20:09:33 +01:00
Nigel Dokter e97ab7c7ea import issues 2017-01-09 21:47:51 +01:00
Nigel Dokter 759e0a0d92 removed absolute imports back to relative due to serial.py importing from serial 2017-01-09 21:31:08 +01:00
Nigel Dokter 21334e5a0a changed relative imports to absolute; renamed TelegramBuffer.put to TelegramBuffer.append; 2017-01-09 20:15:55 +01:00
Nigel Dokter 11672d0512 pep8 2017-01-08 11:44:20 +01:00
Nigel Dokter 87a5a2d2fa dev progress 2017-01-08 11:28:15 +01:00
Nigel Dokter 663024239f dev progress 2017-01-08 11:24:04 +01:00
Nigel Dokter 0e7819b535 dev progress 2017-01-07 22:29:02 +01:00
Nigel Dokter 60317a0dc5 dev progress 2017-01-07 21:26:21 +01:00
Nigel Dokter d990a316ad finishing implementation of TelegramBuffer 2017-01-07 11:25:43 +01:00
Nigel Dokter f10032f701 refactored TelegramParser.parse to accept a str instead of list 2017-01-05 21:24:41 +01:00
112 changed files with 7414 additions and 696 deletions
+47
View File
@@ -0,0 +1,47 @@
name: Tests
on:
push: ~
pull_request: ~
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 10 # Don't run forever when stale
strategy:
matrix:
python-version:
- '3.7'
- '3.8'
- '3.9'
- '3.10'
- '3.11'
- '3.12'
name: Python ${{ matrix.python-version }}
steps:
- uses: actions/checkout@v4
- name: Setup Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Cached PIP dependencies
uses: actions/cache@v4
with:
path: |
~/.cache/pip
~/.tox/python/.pytest_cache
key: pip-${{ matrix.python-version }}-${{ hashFiles('setup.py', 'tox.ini') }}
restore-keys: pip-${{ matrix.python-version }}-
- name: Install dependencies
run: pip install tox
- name: Run tests
run: tox
- name: Code coverage upload
uses: codecov/codecov-action@v4
+6
View File
@@ -2,7 +2,13 @@
*.pyc
.tox
.cache
.venv
*.egg-info
/.project
/.pydevproject
/.coverage
build/
dist/
venv/
*.*~
*~
-17
View File
@@ -1,17 +0,0 @@
language: python
python:
- 2.7
- 3.4
- 3.5
install: pip install tox-travis codecov
script: tox
after_success:
- codecov
matrix:
allow_failures:
- python: 2.7
+208 -6
View File
@@ -1,10 +1,212 @@
Change Log
----------
**1.4.2** (2024-07-14)
- Bump Github Actions to latest versions in favor of Node deprecations (`PR #159 <https://github.com/ndokter/dsmr_parser/pull/159>`_ by `dennissiemensma <https://github.com/dennissiemensma>`_)
- Swap pyserial-asyncio for pyserial-asyncio-fast (`PR #158 <https://github.com/ndokter/dsmr_parser/pull/158>`_ by `bdraco <https://github.com/bdraco>`_)
**1.4.1** (2024-06-04)
- Avoid loading timezone at runtime (`PR #157 <https://github.com/ndokter/dsmr_parser/pull/157>`_ by `elupus <https://github.com/elupus>`_)
**1.4.0** (2024-03-12)
- Mbus alt (`PR #142 <https://github.com/ndokter/dsmr_parser/pull/142>`_ by `dupondje <https://github.com/dupondje>`_)
- Q3D add CURRENT_ELECTRICITY_DELIVERY (`PR #149 <https://github.com/ndokter/dsmr_parser/pull/149>`_ by `Aeroid <https://github.com/Aeroid>`_)
- Copy head_parsers list on construct. (`PR #150 <https://github.com/ndokter/dsmr_parser/pull/150>`_ by `dupondje <https://github.com/dupondje>`_)
**1.3.2** (2024-01-29)
- Fix unit test for pyton 3.12 (`PR #148 <https://github.com/ndokter/dsmr_parser/pull/148>`_ by `ndokter <https://github.com/ndokter>`_)
**1.3.1** (2023-11-06)
- Fix parsing peak usage with invalid timestamps (`PR #143 <https://github.com/ndokter/dsmr_parser/pull/143>`_ by `dupondje <https://github.com/dupondje>`_)
**1.3.0** (2023-08-01)
- added E.ON Hungary; refactored DSMR specifications to fix obis reference conflicts (`PR #137 <https://github.com/ndokter/dsmr_parser/pull/137>`_ by `balazs92117 <https://github.com/balazs92117>`_)
**1.2.4** (2023-07-11)
- EQUIPMENT IDENTIFIER is wrong for Fluvius meters when other mbus devices are present (`PR #133 <https://github.com/ndokter/dsmr_parser/pull/133>`_ by `ejpalacios <https://github.com/ejpalacios>`_)
**1.2.3** (2023-04-18)
- Fix parsing tests and line start matching (`PR #132 <https://github.com/ndokter/dsmr_parser/pull/132>`_ by `dupondje <https://github.com/dupondje>`_)
**1.2.2** (2023-04-12)
- Improve performance. Thanks to `ejpalacios <https://github.com/bdraco>`_ (`PR #130 <https://github.com/ndokter/dsmr_parser/pull/130>`_ by `ndokter <https://github.com/ndokter>`_)
**1.2.1** (2023-04-05)
- Bug/duplicate index BELGIUM_MAXIMUM_DEMAND_13_MONTHS (`PR #129 <https://github.com/ndokter/dsmr_parser/pull/129>`_ by `ejpalacios <https://github.com/ejpalacios>`_)
**1.2.0** (2023-02-18)
- Improved gas meter (mbus devices) support and replaced Telegram dictionary with backwards compatible object (`PR #121 <https://github.com/ndokter/dsmr_parser/pull/121>`_ by `ndokter <https://github.com/ndokter>`_)
- Fix parsing with invalid timestamps (`PR #125 <https://github.com/ndokter/dsmr_parser/pull/125>`_ by `dupondje <https://github.com/dupondje>`_)
- Add Iskra IE.x meters specification (`PR #126 <https://github.com/ndokter/dsmr_parser/pull/126>`_ by `jchevalier7 <https://github.com/jchevalier7>`_)
**1.1.0** (2023-02-08)
- Add instantaneous reactive power + fixed swapped reactive total import export (`PR #124 <https://github.com/ndokter/dsmr_parser/pull/124>`_ by `yada75 <https://github.com/yada75>`_)
**1.0.0** (2022-12-22)
- switched to new numbering scheme https://semver.org/
- Added support for Python 3.11 and dropped support for Python 3.6 (`PR #112 <https://github.com/ndokter/dsmr_parser/pull/112>`_ by `dennissiemensma <https://github.com/dennissiemensma>`_)
- Add support for Fluvius V1.7.1 DSMR messages (`PR #110 <https://github.com/ndokter/dsmr_parser/pull/113>`_ by `dupondje <https://github.com/dupondje>`_)
**0.34** (2022-10-19)
- Adds support for the Sagemcom T210-D-r smart meter (`PR #110 <https://github.com/ndokter/dsmr_parser/pull/110>`_).
**0.33** (2022-04-20)
- Test Python 3.10 in CI + legacy badge fix (`PR #105 <https://github.com/ndokter/dsmr_parser/pull/105>`_).
- Update telegram_specifications.py (`PR #106 <https://github.com/ndokter/dsmr_parser/pull/106>`_).
- Improve compatiblity with Belgian standard (`PR #107 <https://github.com/ndokter/dsmr_parser/pull/107>`_).
- Improve documentation asyncio (`PR #63 <https://github.com/ndokter/dsmr_parser/pull/63>`_).
**0.32** (2022-01-04)
- Support DSMR data read via RFXtrx with integrated P1 reader (`PR #98 <https://github.com/ndokter/dsmr_parser/pull/98>`_).
**0.31** (2021-11-21)
- Support for (German) EasyMeter Q3D using COM-1 Ethernet Gateway (`PR #92 <https://github.com/ndokter/dsmr_parser/pull/92>`_).
**0.30** (2021-08-18)
- Add support for Swedish smart meters (`PR #86 <https://github.com/ndokter/dsmr_parser/pull/86>`_).
**0.29** (2021-04-18)
- Add value and unit properties to ProfileGenericObject to make sure that code like iterators that rely on that do not break (`PR #71 <https://github.com/ndokter/dsmr_parser/pull/71>`_).
Remove deprecated asyncio coroutine decorator (`PR #76 <https://github.com/ndokter/dsmr_parser/pull/76>`_).
**0.28** (2021-02-21)
- Optional keep alive monitoring for TCP/IP connections (`PR #73 <https://github.com/ndokter/dsmr_parser/pull/73>`_).
- Catch parse errors in TelegramParser, ignore lines that can not be parsed (`PR #74 <https://github.com/ndokter/dsmr_parser/pull/74>`_).
**0.27** (2020-12-24)
- fix for empty parentheses in ProfileGenericParser (redone) (`PR #69 <https://github.com/ndokter/dsmr_parser/pull/69>`_).
**0.26** (2020-12-15)
- reverted fix for empty parentheses in ProfileGenericParser (`PR #68 <https://github.com/ndokter/dsmr_parser/pull/68>`_).
**0.25** (2020-12-14)
- fix for empty parentheses in ProfileGenericParser (`PR #57 <https://github.com/ndokter/dsmr_parser/pull/57>`_).
**0.24** (2020-11-27)
- Add Luxembourg equipment identifier (`PR #62 <https://github.com/ndokter/dsmr_parser/pull/62>`_).
**0.23** (2020-11-07)
- Resolved issue with x-x:24.3.0 where it contains non-integer character (`PR #61 <https://github.com/ndokter/dsmr_parser/pull/61>`_).
- Tests are not installed anymore (`PR #59 <https://github.com/ndokter/dsmr_parser/pull/59>`_).
- Example telegram improvement (`PR #58 <https://github.com/ndokter/dsmr_parser/pull/58>`_).
**0.22** (2020-08-23)
- CRC check speed is improved
- Exception info improvement
**0.21** (2020-05-25)
- All objects can produce a json serialization of their state.
**0.20** (2020-05-12)
- All objects can now print their values
- Add parser + object for generic profile
**0.19** (2020-05-03)
- Add following missing elements to telegram specification v4:
- SHORT_POWER_FAILURE_COUNT,
- INSTANTANEOUS_CURRENT_L1,
- INSTANTANEOUS_CURRENT_L2,
- INSTANTANEOUS_CURRENT_L3
- Add missing tests + fix small test bugs
- Complete telegram object v4 parse test
**0.18** (2020-01-28)
- PyCRC replacement (`PR #48 <https://github.com/ndokter/dsmr_parser/pull/48>`_).
**0.17** (2019-12-21)
- Add a true telegram object (`PR #40 <https://github.com/ndokter/dsmr_parser/pull/40>`_).
**0.16** (2019-12-21)
- Add support for Belgian and Smarty meters (`PR #44 <https://github.com/ndokter/dsmr_parser/pull/44>`_).
**0.15** (2019-12-12)
- Fixed asyncio loop issue (`PR #43 <https://github.com/ndokter/dsmr_parser/pull/43>`_).
**0.14** (2019-10-08)
- Changed serial reading to reduce CPU usage (`PR #37 <https://github.com/ndokter/dsmr_parser/pull/37>`_).
**0.13** (2019-03-04)
- Fix DSMR v5.0 serial settings which were not used (`PR #33 <https://github.com/ndokter/dsmr_parser/pull/33>`_).
**0.12** (2018-09-23)
- Add serial settings for DSMR v5.0 (`PR #31 <https://github.com/ndokter/dsmr_parser/pull/31>`_).
- Lux-creos-obis-1.8.0 (`PR #32 <https://github.com/ndokter/dsmr_parser/pull/32>`_).
**0.11** (2017-09-18)
- NULL value fix in checksum (`PR #26 <https://github.com/ndokter/dsmr_parser/pull/26>`_)
**0.10** (2017-06-05)
- bugfix: don't force full telegram signatures (`PR #25 <https://github.com/ndokter/dsmr_parser/pull/25>`_)
- removed unused code for automatic telegram detection as this needs reworking after the fix mentioned above
- InvalidChecksumError's are logged as warning instead of error
**0.9** (2017-05-12)
- added DSMR v5 serial settings
**0.8** (2017-01-26)
- added support for DSMR v3
- added support for DSMR v5
**IMPORTANT: this release has the following backwards incompatible changes:**
- Removed TelegramParserV2_2 in favor of TelegramParser
- Removed TelegramParserV4 in favor of TelegramParser
**0.7** (2017-01-14)
- Internal refactoring related to the way clients feed their data into the parse module. Clients can now supply the telegram data in single characters, lines (which was common) or complete telegram strings. (`PR #17 <https://github.com/ndokter/dsmr_parser/pull/17>`_)
**IMPORTANT: this release has the following backwards incompatible changes:**
- Client related imports from dsmr_parser.serial and dsmr_parser.protocol have been moved to dsmr_parser.clients (import these from the clients/__init__.py module)
- The .parse() method of TelegramParser, TelegramParserV2_2, TelegramParserV4 now accepts a string containing the entire telegram (including \r\n characters) and not a list
**0.6** (2017-01-04)
- Fixed bug in CRC checksum verification for the asyncio client (`pull request #15 <https://github.com/ndokter/dsmr_parser/pull/15>`_)
- Support added for TCP connections using the asyncio client (`pull request #12 <https://github.com/ndokter/dsmr_parser/pull/12/>`_)
- Fixed bug in CRC checksum verification for the asyncio client (`PR #15 <https://github.com/ndokter/dsmr_parser/pull/15>`_)
- Support added for TCP connections using the asyncio client (`PR #12 <https://github.com/ndokter/dsmr_parser/pull/12/>`_)
**0.5** (2016-12-29)
@@ -12,16 +214,16 @@ Change Log
**0.4** (2016-11-21)
- DSMR v2.2 serial settings now uses parity serial.EVEN by default (`pull request #5 <https://github.com/ndokter/dsmr_parser/pull/5>`_)
- improved asyncio reader and improve it's error handling (`pull request #8 <https://github.com/ndokter/dsmr_parser/pull/8>`_)
- DSMR v2.2 serial settings now uses parity serial.EVEN by default (`PR #5 <https://github.com/ndokter/dsmr_parser/pull/5>`_)
- improved asyncio reader and improve it's error handling (`PR #8 <https://github.com/ndokter/dsmr_parser/pull/8>`_)
**0.3** (2016-11-12)
- asyncio reader for non-blocking reads (`pull request #3 <https://github.com/ndokter/dsmr_parser/pull/3>`_)
- asyncio reader for non-blocking reads (`PR #3 <https://github.com/ndokter/dsmr_parser/pull/3>`_)
**0.2** (2016-11-08)
- support for DMSR version 2.2 (`pull request #2 <https://github.com/ndokter/dsmr_parser/pull/2>`_)
- support for DMSR version 2.2 (`PR #2 <https://github.com/ndokter/dsmr_parser/pull/2>`_)
**0.1** (2016-08-22)
+19
View File
@@ -0,0 +1,19 @@
This is a review regarding https://github.com/ndokter/dsmr_parser.
There seems to something wrong with this index page. But look in the [reviews](reviews) folder for a detailed review per file. JSON files are for LLM's. Markdown is for humans.
## Review
tialize and clean up resources for multiple test cases, which can reduce code repetition.
## Good Points
- Thoroughly tests various aspects of the DSMR v4.2 telegram, including both successful parsing and error scenarios.
- Effectively leverages the `unittest` framework for organizing test cases.
- Consistently uses appropriate data types like `Decimal` for numeric precision.
## Summary
This test suite is thorough and effectively covers the parsing of DSMR v4.2 telegrams. It includes tests for successful parsing, as well as handling invalid and missing checksum scenarios. The suite could be further improved by optimizing repetitive code and adopting more descriptive assertion methods provided by the `unittest` framework. Overall, it is a well-structured and comprehensive set of tests that offers good coverage.
## Open Source Alternatives
- **Pytest**: A popular testing framework that can simplify complex test cases and offers more advanced features compared to `unittest`.
- **Testcontainers**: A library for integration and end-to-end testing using Docker containers, which might be useful if the project scales to include more external services and components.
-72
View File
@@ -1,72 +0,0 @@
DSMR Parser
===========
.. image:: https://img.shields.io/pypi/v/dsmr-parser.svg
:target: https://pypi.python.org/pypi/dsmr-parser
.. image:: https://travis-ci.org/ndokter/dsmr_parser.svg?branch=master
:target: https://travis-ci.org/ndokter/dsmr_parser
A library for parsing Dutch Smart Meter Requirements (DSMR) telegram data. It
also includes a serial client to directly read and parse smart meter data.
Features
--------
DSMR Parser currently supports DSMR versions 2.2 and 4.x. It has been tested with Python 3.4 and 3.5.
Examples
--------
Using the serial reader to connect to your smart meter and parse it's telegrams:
.. code-block:: python
from dsmr_parser import telegram_specifications
from dsmr_parser import obis_references
from dsmr_parser.serial import SerialReader, SERIAL_SETTINGS_V4
serial_reader = SerialReader(
device='/dev/ttyUSB0',
serial_settings=SERIAL_SETTINGS_V4,
telegram_specification=telegram_specifications.V4
)
for telegram in serial_reader.read():
# The telegram message timestamp.
message_datetime = telegram[obis_references.P1_MESSAGE_TIMESTAMP]
# Using the active tariff to determine the electricity being used and
# delivered for the right tariff.
tariff = telegram[obis_references.ELECTRICITY_ACTIVE_TARIFF]
tariff = int(tariff.value)
electricity_used_total \
= telegram[obis_references.ELECTRICITY_USED_TARIFF_ALL[tariff - 1]]
electricity_delivered_total = \
telegram[obis_referencesELECTRICITY_DELIVERED_TARIFF_ALL[tariff - 1]]
gas_reading = telegram[obis_references.HOURLY_GAS_METER_READING]
# See dsmr_reader.obis_references for all readable telegram values.
Installation
------------
To install DSMR Parser:
.. code-block:: bash
$ pip install dsmr-parser
Known issues
------------
If the serial settings SERIAL_SETTINGS_V2_2 or SERIAL_SETTINGS_V4 don't work.
Make sure to try and replace the parity settings to EVEN or NONE.
It's possible that alternative settings will be added in the future if these
settings don't work for the majority of meters.
+332
View File
@@ -0,0 +1,332 @@
DSMR Parser
===========
.. image:: https://img.shields.io/pypi/v/dsmr-parser.svg
:target: https://pypi.python.org/pypi/dsmr-parser
.. image:: https://img.shields.io/github/actions/workflow/status/ndokter/dsmr_parser/tests.yml?branch=master
:target: https://github.com/ndokter/dsmr_parser/actions/workflows/tests.yml
A library for parsing Dutch Smart Meter Requirements (DSMR) telegram data. It
also includes client implementation to directly read and parse smart meter data.
Features
--------
DSMR Parser supports DSMR versions 2, 3, 4 and 5. See for the `currently supported/tested Python versions here <https://github.com/ndokter/dsmr_parser/blob/master/.github/workflows/tests.yml#L14>`_.
Client module usage
-------------------
**Serial client**
Read the serial port and work with the parsed telegrams. It should be run in a separate
process because the code is blocking (not asynchronous):
.. code-block:: python
from dsmr_parser import telegram_specifications
from dsmr_parser.clients import SerialReader, SERIAL_SETTINGS_V4
serial_reader = SerialReader(
device='/dev/ttyUSB0',
serial_settings=SERIAL_SETTINGS_V4,
telegram_specification=telegram_specifications.V4
)
for telegram in serial_reader.read():
print(telegram) # see 'Telegram object' docs below
**Socket client**
Read a remote serial port (for example using ser2net) and work with the parsed telegrams.
It should be run in a separate process because the code is blocking (not asynchronous):
.. code-block:: python
from dsmr_parser import telegram_specifications
from dsmr_parser.clients import SocketReader
socket_reader = SocketReader(
host='127.0.0.1',
port=2001,
telegram_specification=telegram_specifications.V4
)
for telegram in socket_reader.read():
print(telegram) # see 'Telegram object' docs below
**AsyncIO client**
For a test run using a tcp server (lasting 20 seconds) use the following example:
.. code-block:: python
import asyncio
import logging
from dsmr_parser import obis_references
from dsmr_parser.clients.protocol import create_dsmr_reader, create_tcp_dsmr_reader
logging.basicConfig(level=logging.INFO, format='%(message)s')
HOST = MY_HOST
PORT = MY_PORT
DSMR_VERSION = MY_DSMR_VERSION
logger = logging.getLogger('tcpclient')
logger.debug("Logger created")
def printTelegram(telegram):
logger.info(telegram)
async def main():
try:
logger.debug("Getting loop")
loop = asyncio.get_event_loop()
logger.debug("Creating reader")
await create_tcp_dsmr_reader(
HOST,
PORT,
DSMR_VERSION,
printTelegram,
loop
)
logger.debug("Reader created going to sleep now")
await asyncio.sleep(20)
logger.info('Finished run')
except Exception as e:
logger.error("Unexpected error: "+ e)
asyncio.run(main())
Note the creation of a callback function to call when a telegram is received. In this case `printTelegram`. Normally the used loop is the one running.
Currently the asyncio implementation does not support returning telegram objects directly as a `read_as_object()` for async tcp is currently not implemented.
Moreover, the telegram passed to `telegram_callback(telegram)` is already parsed. Therefore we can't feed it into the telegram constructor directly as that expects unparsed telegrams
However, if we construct a mock TelegramParser that just returns the already parsed object we can work around this. An example is below:
.. code-block:: python
import asyncio
import logging
from dsmr_parser import telegram_specifications
from dsmr_parser.clients.protocol import create_tcp_dsmr_reader
logging.basicConfig(level=logging.INFO, format='%(message)s')
HOST = MY_HOST
PORT = MY_PORT
DSMR_VERSION = MY_DSMR_VERSION
logger = logging.getLogger('tcpclient')
logger.debug("Logger created")
class mockTelegramParser(object):
def parse(self, telegram):
return telegram
telegram_parser = mockTelegramParser()
def printTelegram(telegram):
try:
logger.info(Telegram(telegram, telegram_parser, telegram_specifications.V4))
except InvalidChecksumError as e:
logger.warning(str(e))
except ParseError as e:
logger.error('Failed to parse telegram: %s', e)
async def main():
try:
logger.debug("Getting loop")
loop = asyncio.get_event_loop()
logger.debug("Creating reader")
await create_tcp_dsmr_reader(
HOST,
PORT,
DSMR_VERSION,
printTelegram,
loop
)
logger.debug("Reader created going to sleep now")
while True:
await asyncio.sleep(1)
except Exception as e:
logger.error("Unexpected error: "+ e)
raise
if __name__ == '__main__':
try:
asyncio.run(main())
except (KeyboardInterrupt, SystemExit):
logger.info('Closing down...')
except Exception as e:
logger.error("Unexpected error: "+ e)
Parsing module usage
--------------------
The parsing module accepts complete unaltered telegram strings and parses these
into a Telegram object.
Tip: getting full telegrams from a bytestream can be made easier by using the TelegramBuffer helper class.
.. code-block:: python
from dsmr_parser import telegram_specifications
from dsmr_parser.parsers import TelegramParser
# String is formatted in separate lines for readability.
telegram_str = (
'/ISk5\\2MT382-1000\r\n'
'\r\n'
'0-0:96.1.1(4B384547303034303436333935353037)\r\n'
'1-0:1.8.1(12345.678*kWh)\r\n'
'1-0:1.8.2(12345.678*kWh)\r\n'
'1-0:2.8.1(12345.678*kWh)\r\n'
'1-0:2.8.2(12345.678*kWh)\r\n'
'0-0:96.14.0(0002)\r\n'
'1-0:1.7.0(001.19*kW)\r\n'
'1-0:2.7.0(000.00*kW)\r\n'
'0-0:17.0.0(016*A)\r\n'
'0-0:96.3.10(1)\r\n'
'0-0:96.13.1(303132333435363738)\r\n'
'0-0:96.13.0(303132333435363738393A3B3C3D3E3F303132333435363738393A3B3C3D3E'
'3F303132333435363738393A3B3C3D3E3F303132333435363738393A3B3C3D3E3F30313233'
'3435363738393A3B3C3D3E3F)\r\n'
'0-1:96.1.0(3232323241424344313233343536373839)\r\n'
'0-1:24.1.0(03)\r\n'
'0-1:24.3.0(090212160000)(00)(60)(1)(0-1:24.2.1)(m3)\r\n'
'(00001.001)\r\n'
'0-1:24.4.0(1)\r\n'
'!\r\n'
)
parser = TelegramParser(telegram_specifications.V3)
# see 'Telegram object' docs below
telegram = parser.parse(telegram_str)
Telegram object
---------------------
A Telegram has attributes for all the parsed values according to the given telegram specification. Each value is a DsmrObject which have a 'value' and 'unit' property. MBusObject's, which are DsmrObject's as well additionally have a 'datetime' property. The 'value' can contain any python type (int, str, Decimal) depending on the field. The 'unit' contains 'kW', 'A', 'kWh' or 'm3'.
Note: Telegram extends dictionary, which done for backwards compatibility. The use of keys (e.g. `telegram[obis_references.CURRENT_ELECTRICITY_USAGE]`) is deprecated.
Below are some examples on how to get the meter data. Alternatively check out the following unit test for a complete example: TelegramParserV5Test.test_parse
.. code-block:: python
# Print contents of all available values
# See dsmr_parser.obis_name_mapping for all readable telegram values.
# The available values differ per DSMR version and meter.
print(telegram)
# P1_MESSAGE_HEADER: 42 [None]
# P1_MESSAGE_TIMESTAMP: 2016-11-13 19:57:57+00:00 [None]
# EQUIPMENT_IDENTIFIER: 3960221976967177082151037881335713 [None]
# ELECTRICITY_USED_TARIFF_1: 1581.123 [kWh]
# etc.
# Example to get current electricity usage
print(telegram.CURRENT_ELECTRICITY_USAGE) # <dsmr_parser.objects.CosemObject at 0x7f5e98ae5ac8>
print(telegram.CURRENT_ELECTRICITY_USAGE.value) # Decimal('2.027')
print(telegram.CURRENT_ELECTRICITY_USAGE.unit) # 'kW'
# All Mbus device readings like gas meters and water meters can be retrieved as follows. This
# returns a list of MbusDevice objects:
mbus_devices = telegram.MBUS_DEVICES
# A specific MbusDevice based on the channel it's connected to, can be retrieved as follows:
mbus_device = telegram.get_mbus_device_by_channel(1)
print(mbus_device.DEVICE_TYPE.value) # 3
print(mbus_device.EQUIPMENT_IDENTIFIER_GAS.value) # '4730303339303031393336393930363139'
print(mbus_device.HOURLY_GAS_METER_READING.value) # Decimal('246.138')
# DEPRECATED: the dictionary approach of getting the values by key or `.items()' or '.get() is deprecated
telegram[obis_references.CURRENT_ELECTRICITY_USAGE]
The telegram object has an iterator, can be used to find all the information elements in the current telegram:
.. code-block:: python
[attr for attr, value in telegram]
Out[11]:
['P1_MESSAGE_HEADER',
'P1_MESSAGE_TIMESTAMP',
'EQUIPMENT_IDENTIFIER',
'ELECTRICITY_USED_TARIFF_1',
'ELECTRICITY_USED_TARIFF_2',
'ELECTRICITY_DELIVERED_TARIFF_1',
'ELECTRICITY_DELIVERED_TARIFF_2',
'ELECTRICITY_ACTIVE_TARIFF',
'CURRENT_ELECTRICITY_USAGE',
'CURRENT_ELECTRICITY_DELIVERY',
'LONG_POWER_FAILURE_COUNT',
'VOLTAGE_SAG_L1_COUNT',
'VOLTAGE_SAG_L2_COUNT',
'VOLTAGE_SAG_L3_COUNT',
'VOLTAGE_SWELL_L1_COUNT',
'VOLTAGE_SWELL_L2_COUNT',
'VOLTAGE_SWELL_L3_COUNT',
'TEXT_MESSAGE_CODE',
'TEXT_MESSAGE',
'DEVICE_TYPE',
'INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE',
'INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE',
'INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE',
'INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE',
'INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE',
'INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE',
'EQUIPMENT_IDENTIFIER_GAS',
'HOURLY_GAS_METER_READING']
Installation
------------
To install DSMR Parser:
.. code-block:: bash
$ pip install dsmr-parser
Development
-----------
Create a virtualenv and activate it followed by the installation of the dsmr-parser:
.. code-block:: bash
python3 -m venv venv
source venv/bin/activate
pip install -e .
Install tox and run it:
.. code-block:: bash
pip install tox
tox
You should see that the tests have succeeded:
.. code-block:: text
======================================================================================================== 59 passed in 0.91s ========================================================================================================
py: commands[1]> pylama dsmr_parser test
py: OK (11.55=setup[9.73]+cmd[1.29,0.53] seconds)
congratulations :) (11.69 seconds)
Now you can make changes by editing the code and rerunning tox to verify your changes.
Known issues
------------
If the serial settings SERIAL_SETTINGS_V2_2 or SERIAL_SETTINGS_V4 don't work.
Make sure to try and replace the parity settings to EVEN or NONE.
It's possible that alternative settings will be added in the future if these
settings don't work for the majority of meters.
+4 -4
View File
@@ -1,9 +1,9 @@
from functools import partial
import argparse
import asyncio
import logging
from functools import partial
from .protocol import create_dsmr_reader, create_tcp_dsmr_reader
from dsmr_parser.clients import create_dsmr_reader, create_tcp_dsmr_reader
def console():
@@ -16,8 +16,8 @@ def console():
help='alternatively connect using TCP host.')
parser.add_argument('--port', default=None,
help='TCP port to use for connection')
parser.add_argument('--version', default='2.2', choices=['2.2', '4'],
help='DSMR version (2.2, 4)')
parser.add_argument('--version', default='2.2', choices=['2.2', '4', '5', '5B', '5L', '5S', 'Q3D'],
help='DSMR version (2.2, 4, 5, 5B, 5L, 5S, Q3D)')
parser.add_argument('--verbose', '-v', action='count')
args = parser.parse_args()
+6
View File
@@ -0,0 +1,6 @@
from dsmr_parser.clients.settings import SERIAL_SETTINGS_V2_2, \
SERIAL_SETTINGS_V4, SERIAL_SETTINGS_V5
from dsmr_parser.clients.serial_ import SerialReader, AsyncSerialReader
from dsmr_parser.clients.socket_ import SocketReader
from dsmr_parser.clients.protocol import create_dsmr_protocol, \
create_dsmr_reader, create_tcp_dsmr_reader
+173
View File
@@ -0,0 +1,173 @@
import logging
import fileinput
import tailer
from dsmr_parser.clients.telegram_buffer import TelegramBuffer
from dsmr_parser.exceptions import ParseError, InvalidChecksumError
from dsmr_parser.parsers import TelegramParser
logger = logging.getLogger(__name__)
class FileReader(object):
"""
Filereader to read and parse raw telegram strings from a file and instantiate Telegram objects
for each read telegram.
Usage:
from dsmr_parser import telegram_specifications
from dsmr_parser.clients.filereader import FileReader
if __name__== "__main__":
infile = '/data/smartmeter/readings.txt'
file_reader = FileReader(
file = infile,
telegram_specification = telegram_specifications.V4
)
for telegram in file_reader.read_as_object():
print(telegram)
The file can be created like:
from dsmr_parser import telegram_specifications
from dsmr_parser.clients import SerialReader, SERIAL_SETTINGS_V5
if __name__== "__main__":
outfile = '/data/smartmeter/readings.txt'
serial_reader = SerialReader(
device='/dev/ttyUSB0',
serial_settings=SERIAL_SETTINGS_V5,
telegram_specification=telegram_specifications.V4
)
for telegram in serial_reader.read_as_object():
f=open(outfile,"ab+")
f.write(telegram._telegram_data.encode())
f.close()
"""
def __init__(self, file, telegram_specification):
self._file = file
self.telegram_parser = TelegramParser(telegram_specification)
self.telegram_buffer = TelegramBuffer()
self.telegram_specification = telegram_specification
def read_as_object(self):
"""
Read complete DSMR telegram's from a file and return a Telegram object.
:rtype: generator
"""
with open(self._file, "rb") as file_handle:
while True:
data = file_handle.readline()
if not data:
break
self.telegram_buffer.append(data.decode())
for telegram in self.telegram_buffer.get_all():
try:
yield self.telegram_parser.parse(telegram)
except InvalidChecksumError as e:
logger.info(str(e))
except ParseError as e:
logger.error('Failed to parse telegram: %s', e)
class FileInputReader(object):
"""
Filereader to read and parse raw telegram strings from stdin or files specified at the commandline
and instantiate Telegram objects for each read telegram.
Usage python script "syphon_smartmeter_readings_stdin.py":
from dsmr_parser import telegram_specifications
from dsmr_parser.clients.filereader import FileInputReader
if __name__== "__main__":
fileinput_reader = FileReader(
file = infile,
telegram_specification = telegram_specifications.V4
)
for telegram in fileinput_reader.read_as_object():
print(telegram)
Command line:
tail -f /data/smartmeter/readings.txt | python3 syphon_smartmeter_readings_stdin.py
"""
def __init__(self, telegram_specification):
self.telegram_parser = TelegramParser(telegram_specification)
self.telegram_buffer = TelegramBuffer()
self.telegram_specification = telegram_specification
def read_as_object(self):
"""
Read complete DSMR telegram's from stdin of filearguments specified on teh command line
and return a Telegram object.
:rtype: generator
"""
with fileinput.input(mode='rb') as file_handle:
while True:
data = file_handle.readline()
str = data.decode()
self.telegram_buffer.append(str)
for telegram in self.telegram_buffer.get_all():
try:
yield self.telegram_parser.parse(telegram)
except InvalidChecksumError as e:
logger.warning(str(e))
except ParseError as e:
logger.error('Failed to parse telegram: %s', e)
class FileTailReader(object):
"""
Filereader to read and parse raw telegram strings from the tail of a
given file and instantiate Telegram objects for each read telegram.
Usage python script "syphon_smartmeter_readings_stdin.py":
from dsmr_parser import telegram_specifications
from dsmr_parser.clients.filereader import FileTailReader
if __name__== "__main__":
infile = '/data/smartmeter/readings.txt'
filetail_reader = FileTailReader(
file = infile,
telegram_specification = telegram_specifications.V5
)
for telegram in filetail_reader.read_as_object():
print(telegram)
"""
def __init__(self, file, telegram_specification):
self._file = file
self.telegram_parser = TelegramParser(telegram_specification)
self.telegram_buffer = TelegramBuffer()
self.telegram_specification = telegram_specification
def read_as_object(self):
"""
Read complete DSMR telegram's from a files tail and return a Telegram object.
:rtype: generator
"""
with open(self._file, "rb") as file_handle:
for data in tailer.follow(file_handle):
str = data.decode()
self.telegram_buffer.append(str)
for telegram in self.telegram_buffer.get_all():
try:
yield self.telegram_parser.parse(telegram)
except InvalidChecksumError as e:
logger.warning(str(e))
except ParseError as e:
logger.error('Failed to parse telegram: %s', e)
+169
View File
@@ -0,0 +1,169 @@
"""Asyncio protocol implementation for handling telegrams."""
from functools import partial
import asyncio
import logging
from serial_asyncio_fast import create_serial_connection
from dsmr_parser import telegram_specifications
from dsmr_parser.clients.telegram_buffer import TelegramBuffer
from dsmr_parser.exceptions import ParseError, InvalidChecksumError
from dsmr_parser.parsers import TelegramParser
from dsmr_parser.clients.settings import SERIAL_SETTINGS_V2_2, \
SERIAL_SETTINGS_V4, SERIAL_SETTINGS_V5
def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None, **kwargs):
"""Creates a DSMR asyncio protocol."""
protocol = _create_dsmr_protocol(dsmr_version, telegram_callback,
DSMRProtocol, loop, **kwargs)
return protocol
# pylama noqa - because of "complex" (too long) if-elif-else.
# Match - case might be a solution but it is not available in <3.10
def _create_dsmr_protocol(dsmr_version, telegram_callback, protocol, loop=None, **kwargs): #noqa
"""Creates a DSMR asyncio protocol."""
if dsmr_version == '2.2':
specification = telegram_specifications.V2_2
serial_settings = SERIAL_SETTINGS_V2_2
elif dsmr_version == '4':
specification = telegram_specifications.V4
serial_settings = SERIAL_SETTINGS_V4
elif dsmr_version == '4+':
specification = telegram_specifications.V5
serial_settings = SERIAL_SETTINGS_V4
elif dsmr_version == '5':
specification = telegram_specifications.V5
serial_settings = SERIAL_SETTINGS_V5
elif dsmr_version == '5B':
specification = telegram_specifications.BELGIUM_FLUVIUS
serial_settings = SERIAL_SETTINGS_V5
elif dsmr_version == "5L":
specification = telegram_specifications.LUXEMBOURG_SMARTY
serial_settings = SERIAL_SETTINGS_V5
elif dsmr_version == "5S":
specification = telegram_specifications.SWEDEN
serial_settings = SERIAL_SETTINGS_V5
elif dsmr_version == "Q3D":
specification = telegram_specifications.Q3D
serial_settings = SERIAL_SETTINGS_V5
elif dsmr_version == 'ISKRA_IE':
specification = telegram_specifications.ISKRA_IE
serial_settings = SERIAL_SETTINGS_V5
elif dsmr_version == '5EONHU':
specification = telegram_specifications.EON_HUNGARY
serial_settings = SERIAL_SETTINGS_V5
else:
raise NotImplementedError("No telegram parser found for version: %s",
dsmr_version)
protocol = partial(protocol, loop, TelegramParser(specification),
telegram_callback=telegram_callback, **kwargs)
return protocol, serial_settings
def create_dsmr_reader(port, dsmr_version, telegram_callback, loop=None):
"""Creates a DSMR asyncio protocol coroutine using serial port."""
protocol, serial_settings = create_dsmr_protocol(
dsmr_version, telegram_callback, loop=None)
serial_settings['url'] = port
conn = create_serial_connection(loop, protocol, **serial_settings)
return conn
def create_tcp_dsmr_reader(host, port, dsmr_version,
telegram_callback, loop=None,
keep_alive_interval=None):
"""Creates a DSMR asyncio protocol coroutine using TCP connection."""
if not loop:
loop = asyncio.get_event_loop()
protocol, _ = create_dsmr_protocol(
dsmr_version, telegram_callback, loop=loop,
keep_alive_interval=keep_alive_interval)
conn = loop.create_connection(protocol, host, port)
return conn
class DSMRProtocol(asyncio.Protocol):
"""Assemble and handle incoming data into complete DSM telegrams."""
transport = None
telegram_callback = None
def __init__(self, loop, telegram_parser,
telegram_callback=None, keep_alive_interval=None):
"""Initialize class."""
self.loop = loop
self.log = logging.getLogger(__name__)
self.telegram_parser = telegram_parser
# callback to call on complete telegram
self.telegram_callback = telegram_callback
# buffer to keep incomplete incoming data
self.telegram_buffer = TelegramBuffer()
# keep a lock until the connection is closed
self._closed = asyncio.Event()
self._keep_alive_interval = keep_alive_interval
self._active = True
def connection_made(self, transport):
"""Just logging for now."""
self.transport = transport
self.log.debug('connected')
self._active = False
if self.loop and self._keep_alive_interval:
self.loop.call_later(self._keep_alive_interval, self.keep_alive)
def data_received(self, data):
"""Add incoming data to buffer."""
# accept latin-1 (8-bit) on the line, to allow for non-ascii transport or padding
data = data.decode("latin1")
self._active = True
self.log.debug('received data: %s', data)
self.telegram_buffer.append(data)
for telegram in self.telegram_buffer.get_all():
# ensure actual telegram is ascii (7-bit) only (ISO 646:1991 IRV required in section 5.5 of IEC 62056-21)
telegram = telegram.encode("latin1").decode("ascii")
self.handle_telegram(telegram)
def keep_alive(self):
if self._active:
self.log.debug('keep-alive checked')
self._active = False
if self.loop:
self.loop.call_later(self._keep_alive_interval, self.keep_alive)
else:
self.log.warning('keep-alive check failed')
if self.transport:
self.transport.close()
def connection_lost(self, exc):
"""Stop when connection is lost."""
if exc:
self.log.exception('disconnected due to exception', exc_info=exc)
else:
self.log.info('disconnected because of close/abort.')
self._closed.set()
def handle_telegram(self, telegram):
"""Send off parsed telegram to handling callback."""
self.log.debug('got telegram: %s', telegram)
try:
parsed_telegram = self.telegram_parser.parse(telegram)
except InvalidChecksumError as e:
self.log.info(str(e))
except ParseError:
self.log.exception("failed to parse telegram")
else:
self.telegram_callback(parsed_telegram)
async def wait_closed(self):
"""Wait until connection is closed."""
await self._closed.wait()
+62
View File
@@ -0,0 +1,62 @@
"""Asyncio protocol implementation for handling telegrams over a RFXtrx connection ."""
import asyncio
from serial_asyncio_fast import create_serial_connection
from .protocol import DSMRProtocol, _create_dsmr_protocol
def create_rfxtrx_dsmr_protocol(dsmr_version, telegram_callback, loop=None, **kwargs):
"""Creates a RFXtrxDSMR asyncio protocol."""
protocol = _create_dsmr_protocol(dsmr_version, telegram_callback,
RFXtrxDSMRProtocol, loop, **kwargs)
return protocol
def create_rfxtrx_dsmr_reader(port, dsmr_version, telegram_callback, loop=None):
"""Creates a DSMR asyncio protocol coroutine using a RFXtrx serial port."""
protocol, serial_settings = create_rfxtrx_dsmr_protocol(
dsmr_version, telegram_callback, loop=None)
serial_settings['url'] = port
conn = create_serial_connection(loop, protocol, **serial_settings)
return conn
def create_rfxtrx_tcp_dsmr_reader(host, port, dsmr_version,
telegram_callback, loop=None,
keep_alive_interval=None):
"""Creates a DSMR asyncio protocol coroutine using a RFXtrx TCP connection."""
if not loop:
loop = asyncio.get_event_loop()
protocol, _ = create_rfxtrx_dsmr_protocol(
dsmr_version, telegram_callback, loop=loop,
keep_alive_interval=keep_alive_interval)
conn = loop.create_connection(protocol, host, port)
return conn
PACKETTYPE_DSMR = 0x62
SUBTYPE_P1 = 0x01
class RFXtrxDSMRProtocol(DSMRProtocol):
remaining_data = b''
def data_received(self, data):
"""Add incoming data to buffer."""
data = self.remaining_data + data
packetlength = data[0] + 1 if len(data) > 0 else 1
while packetlength <= len(data):
packettype = data[1]
subtype = data[2]
if (packettype == PACKETTYPE_DSMR and subtype == SUBTYPE_P1):
dsmr_data = data[4:packetlength]
super().data_received(dsmr_data)
data = data[packetlength:]
packetlength = data[0] + 1 if len(data) > 0 else 1
self.remaining_data = data
+128
View File
@@ -0,0 +1,128 @@
import logging
import serial
import serial_asyncio_fast
from dsmr_parser.clients.telegram_buffer import TelegramBuffer
from dsmr_parser.exceptions import ParseError, InvalidChecksumError
from dsmr_parser.parsers import TelegramParser
logger = logging.getLogger(__name__)
class SerialReader(object):
PORT_KEY = 'port'
def __init__(self, device, serial_settings, telegram_specification):
self.serial_settings = serial_settings
self.serial_settings[self.PORT_KEY] = device
self.telegram_parser = TelegramParser(telegram_specification)
self.telegram_buffer = TelegramBuffer()
self.telegram_specification = telegram_specification
def read(self):
"""
Read complete DSMR telegram's from the serial interface and parse it
into CosemObject's and MbusObject's
:rtype: generator
"""
with serial.Serial(**self.serial_settings) as serial_handle:
while True:
data = serial_handle.read(max(1, min(1024, serial_handle.in_waiting)))
self.telegram_buffer.append(data.decode('ascii'))
for telegram in self.telegram_buffer.get_all():
try:
yield self.telegram_parser.parse(telegram)
except InvalidChecksumError as e:
logger.info(str(e))
except ParseError as e:
logger.error('Failed to parse telegram: %s', e)
def read_as_object(self):
"""
Read complete DSMR telegram's from the serial interface and return a Telegram object.
:rtype: generator
"""
with serial.Serial(**self.serial_settings) as serial_handle:
while True:
data = serial_handle.readline()
self.telegram_buffer.append(data.decode('ascii'))
for telegram in self.telegram_buffer.get_all():
try:
yield self.telegram_parser.parse(telegram)
except InvalidChecksumError as e:
logger.warning(str(e))
except ParseError as e:
logger.error('Failed to parse telegram: %s', e)
class AsyncSerialReader(SerialReader):
"""Serial reader using asyncio pyserial."""
PORT_KEY = 'url'
async def read(self, queue):
"""
Read complete DSMR telegram's from the serial interface and parse it
into CosemObject's and MbusObject's.
Instead of being a generator, values are pushed to provided queue for
asynchronous processing.
:rtype: None
"""
# create Serial StreamReader
conn = serial_asyncio_fast.open_serial_connection(**self.serial_settings)
reader, _ = await conn
while True:
# Read line if available or give control back to loop until new
# data has arrived.
data = await reader.readline()
self.telegram_buffer.append(data.decode('ascii'))
for telegram in self.telegram_buffer.get_all():
try:
# Push new parsed telegram onto queue.
queue.put_nowait(
self.telegram_parser.parse(telegram)
)
except ParseError as e:
logger.warning('Failed to parse telegram: %s', e)
async def read_as_object(self, queue):
"""
Read complete DSMR telegram's from the serial interface
and return a Telegram object.
Instead of being a generator, Telegram objects are pushed
to provided queue for asynchronous processing.
:rtype: None
"""
# create Serial StreamReader
conn = serial_asyncio_fast.open_serial_connection(**self.serial_settings)
reader, _ = await conn
while True:
# Read line if available or give control back to loop until new
# data has arrived.
data = await reader.readline()
self.telegram_buffer.append(data.decode('ascii'))
for telegram in self.telegram_buffer.get_all():
try:
queue.put_nowait(
self.telegram_parser.parse(telegram)
)
except InvalidChecksumError as e:
logger.warning(str(e))
except ParseError as e:
logger.error('Failed to parse telegram: %s', e)
+32
View File
@@ -0,0 +1,32 @@
import serial
SERIAL_SETTINGS_V2_2 = {
'baudrate': 9600,
'bytesize': serial.SEVENBITS,
'parity': serial.PARITY_EVEN,
'stopbits': serial.STOPBITS_ONE,
'xonxoff': 0,
'rtscts': 0,
'timeout': 20
}
SERIAL_SETTINGS_V4 = {
'baudrate': 115200,
'bytesize': serial.SEVENBITS,
'parity': serial.PARITY_EVEN,
'stopbits': serial.STOPBITS_ONE,
'xonxoff': 0,
'rtscts': 0,
'timeout': 20
}
SERIAL_SETTINGS_V5 = {
'baudrate': 115200,
'bytesize': serial.EIGHTBITS,
'parity': serial.PARITY_NONE,
'stopbits': serial.STOPBITS_ONE,
'xonxoff': 0,
'rtscts': 0,
'timeout': 20
}
+98
View File
@@ -0,0 +1,98 @@
import logging
import socket
from dsmr_parser.clients.telegram_buffer import TelegramBuffer
from dsmr_parser.exceptions import ParseError, InvalidChecksumError
from dsmr_parser.parsers import TelegramParser
logger = logging.getLogger(__name__)
class SocketReader(object):
BUFFER_SIZE = 256
def __init__(self, host, port, telegram_specification):
self.host = host
self.port = port
self.telegram_parser = TelegramParser(telegram_specification)
self.telegram_buffer = TelegramBuffer()
self.telegram_specification = telegram_specification
def read(self):
"""
Read complete DSMR telegram's from remote interface and parse it
into CosemObject's and MbusObject's
:rtype: generator
"""
buffer = b""
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as socket_handle:
socket_handle.settimeout(60)
socket_handle.connect((self.host, self.port))
while True:
try:
buffer += socket_handle.recv(self.BUFFER_SIZE)
except socket.timeout:
logger.error("Socket timeout occurred, exiting")
break
lines = buffer.splitlines(keepends=True)
if len(lines) == 0:
continue
for data in lines:
try:
self.telegram_buffer.append(data.decode('ascii'))
except UnicodeDecodeError:
# Some garbage came through the channel
# E.g.: Happens at EON_HUNGARY, but only once at the start of the socket.
logger.error('Failed to parse telegram due to unicode decode error')
for telegram in self.telegram_buffer.get_all():
try:
yield self.telegram_parser.parse(telegram)
except InvalidChecksumError as e:
logger.info(str(e))
except ParseError as e:
logger.error('Failed to parse telegram: %s', e)
buffer = b""
def read_as_object(self):
"""
Read complete DSMR telegram's from remote and return a Telegram object.
:rtype: generator
"""
buffer = b""
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as socket_handle:
socket_handle.connect((self.host, self.port))
while True:
buffer += socket_handle.recv(self.BUFFER_SIZE)
lines = buffer.splitlines(keepends=True)
if len(lines) == 0:
continue
for data in lines:
self.telegram_buffer.append(data.decode('ascii'))
for telegram in self.telegram_buffer.get_all():
try:
yield self.telegram_parser.parse(telegram)
except InvalidChecksumError as e:
logger.warning(str(e))
except ParseError as e:
logger.error('Failed to parse telegram: %s', e)
buffer = b""
+47
View File
@@ -0,0 +1,47 @@
import re
# - Match all characters after start of telegram except for the start
# itself again '^\/]+', which eliminates incomplete preceding telegrams.
# - Do non greedy match using '?' so start is matched up to the first
# checksum that's found.
# - The checksum is optional '{0,4}' because not all telegram versions
# support it.
_FIND_TELEGRAMS_REGEX = re.compile(r"\/[^\/]+?\![A-F0-9]{0,4}\0?\r\n", re.DOTALL)
class TelegramBuffer(object):
"""
Used as a buffer for a stream of telegram data. Constructs full telegram
strings from the buffered data and returns it.
"""
def __init__(self):
self._buffer = ""
def get_all(self):
"""
Remove complete telegrams from buffer and yield them.
:rtype generator:
"""
for telegram in _FIND_TELEGRAMS_REGEX.findall(self._buffer):
self._remove(telegram)
yield telegram
def append(self, data):
"""
Add telegram data to buffer.
:param str data: chars, lines or full telegram strings of telegram data
"""
self._buffer += data
def _remove(self, telegram):
"""
Remove telegram from buffer and incomplete data preceding it. This
is easier than validating the data before adding it to the buffer.
:param str telegram:
:return:
"""
# Remove data leading up to the telegram and the telegram itself.
index = self._buffer.index(telegram) + len(telegram)
self._buffer = self._buffer[index:]
+123 -33
View File
@@ -1,36 +1,93 @@
P1_MESSAGE_HEADER = r'1-3:0\.2\.8'
P1_MESSAGE_TIMESTAMP = r'0-0:1\.0\.0'
ELECTRICITY_USED_TARIFF_1 = r'1-0:1\.8\.1'
ELECTRICITY_USED_TARIFF_2 = r'1-0:1\.8\.2'
ELECTRICITY_DELIVERED_TARIFF_1 = r'1-0:2\.8\.1'
ELECTRICITY_DELIVERED_TARIFF_2 = r'1-0:2\.8\.2'
ELECTRICITY_ACTIVE_TARIFF = r'0-0:96\.14\.0'
EQUIPMENT_IDENTIFIER = r'0-0:96\.1\.1'
CURRENT_ELECTRICITY_USAGE = r'1-0:1\.7\.0'
CURRENT_ELECTRICITY_DELIVERY = r'1-0:2\.7\.0'
LONG_POWER_FAILURE_COUNT = r'96\.7\.9'
POWER_EVENT_FAILURE_LOG = r'99\.97\.0'
VOLTAGE_SAG_L1_COUNT = r'1-0:32\.32\.0'
VOLTAGE_SAG_L2_COUNT = r'1-0:52\.32\.0'
VOLTAGE_SAG_L3_COUNT = r'1-0:72\.32\.0'
VOLTAGE_SWELL_L1_COUNT = r'1-0:32\.36\.0'
VOLTAGE_SWELL_L2_COUNT = r'1-0:52\.36\.0'
VOLTAGE_SWELL_L3_COUNT = r'1-0:72\.36\.0'
TEXT_MESSAGE_CODE = r'0-0:96\.13\.1'
TEXT_MESSAGE = r'0-0:96\.13\.0'
DEVICE_TYPE = r'0-\d:24\.1\.0'
INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE = r'1-0:21\.7\.0'
INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE = r'1-0:41\.7\.0'
INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE = r'1-0:61\.7\.0'
INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE = r'1-0:22\.7\.0'
INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE = r'1-0:42\.7\.0'
INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE = r'1-0:62\.7\.0'
EQUIPMENT_IDENTIFIER_GAS = r'0-\d:96\.1\.0'
HOURLY_GAS_METER_READING = r'0-1:24\.2\.1'
GAS_METER_READING = r'0-\d:24\.3\.0'
ACTUAL_TRESHOLD_ELECTRICITY = r'0-0:17\.0\.0'
ACTUAL_SWITCH_POSITION = r'0-0:96\.3\.10'
VALVE_POSITION_GAS = r'0-\d:24\.4\.0'
"""
Contains the signatures of each telegram line.
Previously contained the channel + obis reference signatures, but has been
refactored to full line signatures to maintain backwards compatibility.
Might be refactored in a backwards incompatible way as soon as proper telegram
objects are introduced.
"""
P1_MESSAGE_HEADER = r'^\d-\d:0\.2\.8.+?\r\n'
P1_MESSAGE_TIMESTAMP = r'^\d-\d:1\.0\.0.+?\r\n'
ELECTRICITY_USED_TARIFF_1 = r'^\d-\d:1\.8\.1.+?\r\n'
ELECTRICITY_USED_TARIFF_2 = r'^\d-\d:1\.8\.2.+?\r\n'
ELECTRICITY_USED_TARIFF_3 = r'^\d-\d:1\.8\.3.+?\r\n'
ELECTRICITY_USED_TARIFF_4 = r'^\d-\d:1\.8\.4.+?\r\n'
ELECTRICITY_DELIVERED_TARIFF_1 = r'^\d-\d:2\.8\.1.+?\r\n'
ELECTRICITY_DELIVERED_TARIFF_2 = r'^\d-\d:2\.8\.2.+?\r\n'
ELECTRICITY_DELIVERED_TARIFF_3 = r'^\d-\d:2\.8\.3.+?\r\n'
ELECTRICITY_DELIVERED_TARIFF_4 = r'^\d-\d:2\.8\.4.+?\r\n'
CURRENT_REACTIVE_IMPORTED = r'^\d-\d:3\.7\.0.+?\r\n'
ELECTRICITY_REACTIVE_IMPORTED_TOTAL = r'^\d-\d:3\.8\.0.+?\r\n'
ELECTRICITY_REACTIVE_IMPORTED_TARIFF_1 = r'^\d-\d:3\.8\.1.+?\r\n'
ELECTRICITY_REACTIVE_IMPORTED_TARIFF_2 = r'^\d-\d:3\.8\.2.+?\r\n'
CURRENT_REACTIVE_EXPORTED = r'^\d-\d:4\.7\.0.+?\r\n'
ELECTRICITY_REACTIVE_EXPORTED_TOTAL = r'^\d-\d:4\.8\.0.+?\r\n'
ELECTRICITY_REACTIVE_EXPORTED_TARIFF_1 = r'^\d-\d:4\.8\.1.+?\r\n'
ELECTRICITY_REACTIVE_EXPORTED_TARIFF_2 = r'^\d-\d:4\.8\.2.+?\r\n'
ELECTRICITY_ACTIVE_TARIFF = r'^\d-\d:96\.14\.0.+?\r\n'
EQUIPMENT_IDENTIFIER = r'^\d-\d:96\.1\.1.+?\r\n'
CURRENT_ELECTRICITY_USAGE = r'^\d-\d:1\.7\.0.+?\r\n'
CURRENT_ELECTRICITY_DELIVERY = r'^\d-\d:2\.7\.0.+?\r\n'
LONG_POWER_FAILURE_COUNT = r'^\d-\d:96\.7\.9.+?\r\n'
SHORT_POWER_FAILURE_COUNT = r'^\d-\d:96\.7\.21.+?\r\n'
POWER_EVENT_FAILURE_LOG = r'^\d-\d:99\.97\.0.+?\r\n'
VOLTAGE_SAG_L1_COUNT = r'^\d-\d:32\.32\.0.+?\r\n'
VOLTAGE_SAG_L2_COUNT = r'^\d-\d:52\.32\.0.+?\r\n'
VOLTAGE_SAG_L3_COUNT = r'^\d-\d:72\.32\.0.+?\r\n'
VOLTAGE_SWELL_L1_COUNT = r'^\d-\d:32\.36\.0.+?\r\n'
VOLTAGE_SWELL_L2_COUNT = r'^\d-\d:52\.36\.0.+?\r\n'
VOLTAGE_SWELL_L3_COUNT = r'^\d-\d:72\.36\.0.+?\r\n'
INSTANTANEOUS_VOLTAGE_L1 = r'^\d-\d:32\.7\.0.+?\r\n'
INSTANTANEOUS_VOLTAGE_L2 = r'^\d-\d:52\.7\.0.+?\r\n'
INSTANTANEOUS_VOLTAGE_L3 = r'^\d-\d:72\.7\.0.+?\r\n'
INSTANTANEOUS_CURRENT_L1 = r'^\d-\d:31\.7\.0.+?\r\n'
INSTANTANEOUS_CURRENT_L2 = r'^\d-\d:51\.7\.0.+?\r\n'
INSTANTANEOUS_CURRENT_L3 = r'^\d-\d:71\.7\.0.+?\r\n'
FUSE_THRESHOLD_L1 = r'^\d-\d:31\.4\.0.+?\r\n' # Applicable when current limitation is active
FUSE_THRESHOLD_L2 = r'^\d-\d:51\.4\.0.+?\r\n' # Applicable when current limitation is active
FUSE_THRESHOLD_L3 = r'^\d-\d:71\.4\.0.+?\r\n' # Applicable when current limitation is active
TEXT_MESSAGE_CODE = r'^\d-\d:96\.13\.1.+?\r\n'
TEXT_MESSAGE = r'^\d-\d:96\.13\.0.+?\r\n'
DEVICE_TYPE = r'^\d-\d:24\.1\.0.+?\r\n'
INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE = r'^\d-\d:21\.7\.0.+?\r\n'
INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE = r'^\d-\d:41\.7\.0.+?\r\n'
INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE = r'^\d-\d:61\.7\.0.+?\r\n'
INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE = r'^\d-\d:22\.7\.0.+?\r\n'
INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE = r'^\d-\d:42\.7\.0.+?\r\n'
INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE = r'^\d-\d:62\.7\.0.+?\r\n'
INSTANTANEOUS_REACTIVE_POWER_L1_POSITIVE = r'^\d-\d:23\.7\.0.+?\r\n'
INSTANTANEOUS_REACTIVE_POWER_L1_NEGATIVE = r'^\d-\d:24\.7\.0.+?\r\n'
INSTANTANEOUS_REACTIVE_POWER_L2_POSITIVE = r'^\d-\d:43\.7\.0.+?\r\n'
INSTANTANEOUS_REACTIVE_POWER_L2_NEGATIVE = r'^\d-\d:44\.7\.0.+?\r\n'
INSTANTANEOUS_REACTIVE_POWER_L3_POSITIVE = r'^\d-\d:63\.7\.0.+?\r\n'
INSTANTANEOUS_REACTIVE_POWER_L3_NEGATIVE = r'^\d-\d:64\.7\.0.+?\r\n'
EQUIPMENT_IDENTIFIER_GAS = r'^\d-\d:96\.1\.0.+?\r\n'
# TODO differences between gas meter readings in v3 and lower and v4 and up
HOURLY_GAS_METER_READING = r'^\d-\d:24\.2\.1.+?\r\n'
GAS_METER_READING = r'^\d-\d:24\.3\.0.+?\r\n.+?\r\n'
ACTUAL_TRESHOLD_ELECTRICITY = r'^\d-\d:17\.0\.0.+?\r\n'
ACTUAL_SWITCH_POSITION = r'^\d-\d:96\.3\.10.+?\r\n'
VALVE_POSITION_GAS = r'^\d-\d:24\.4\.0.+?\r\n'
# Multiple 'slaves' can be linked to the main device.
# The type is reported on 24.1.0
# Specifications are in EN 13757-3
# For example: Water mater = 7, Gas meter = 3
# Identifier is on 96.1.0 (in NL for ex) or
# on 96.1.1 (in BE for ex)
# The values are reported on 24.2.1
# With an exception in Belgium for the GAS meter
# Be aware that for the gas volume, another OBIS-code is published
# than the one listed in section 7 of DSMR P1.
# This is due to the fact that in Belgium the not-temperature
# corrected gas volume is used while in the Netherlands,
# the temperature corrected gas volume is used.
MBUS_DEVICE_TYPE = r'^\d-[1-9]:24\.1\.0.+?\r\n'
MBUS_EQUIPMENT_IDENTIFIER = r'^\d-[1-9]:96\.1\.[01].+?\r\n'
MBUS_VALVE_POSITION = r'^\d-[1-9]:24\.4\.0.+?\r\n'
MBUS_METER_READING = r'^\d-[1-9]:24\.2\.[13].+?\r\n'
# TODO 17.0.0
ELECTRICITY_USED_TARIFF_ALL = (
ELECTRICITY_USED_TARIFF_1,
@@ -40,3 +97,36 @@ ELECTRICITY_DELIVERED_TARIFF_ALL = (
ELECTRICITY_DELIVERED_TARIFF_1,
ELECTRICITY_DELIVERED_TARIFF_2
)
# International generalized additions
ELECTRICITY_IMPORTED_TOTAL = r'^\d-\d:1\.8\.0.+?\r\n' # Total imported energy register (P+)
ELECTRICITY_EXPORTED_TOTAL = r'^\d-\d:2\.8\.0.+?\r\n' # Total exported energy register (P-)
# International non generalized additions (country specific) / risk for necessary refactoring
BELGIUM_VERSION_INFORMATION = r'^\d-\d:96\.1\.4.+?\r\n'
BELGIUM_EQUIPMENT_IDENTIFIER = r'^\d-0:96\.1\.1.+?\r\n'
BELGIUM_CURRENT_AVERAGE_DEMAND = r'^\d-\d:1\.4\.0.+?\r\n'
BELGIUM_MAXIMUM_DEMAND_MONTH = r'^\d-\d:1\.6\.0.+?\r\n'
BELGIUM_MAXIMUM_DEMAND_13_MONTHS = r'^\d-\d:98\.1\.0.+?\r\n'
LUXEMBOURG_EQUIPMENT_IDENTIFIER = r'^\d-\d:42\.0\.0.+?\r\n' # Logical device name
Q3D_EQUIPMENT_IDENTIFIER = r'^\d-\d:0\.0\.0.+?\r\n' # Logical device name
Q3D_EQUIPMENT_STATE = r'^\d-\d:96\.5\.5.+?\r\n' # Device state (hexadecimal)
Q3D_EQUIPMENT_SERIALNUMBER = r'^\d-\d:96\.1\.255.+?\r\n' # Device Serialnumber
# EON Hungary
EON_HU_ELECTRICITY_REACTIVE_TOTAL_Q1 = r'^\d-\d:5\.8\.0.+?\r\n'
EON_HU_ELECTRICITY_REACTIVE_TOTAL_Q2 = r'^\d-\d:6\.8\.0.+?\r\n'
EON_HU_ELECTRICITY_REACTIVE_TOTAL_Q3 = r'^\d-\d:7\.8\.0.+?\r\n'
EON_HU_ELECTRICITY_REACTIVE_TOTAL_Q4 = r'^\d-\d:8\.8\.0.+?\r\n'
EON_HU_ELECTRICITY_COMBINED = r'^\d-\d:15\.8\.0.+?\r\n'
EON_HU_INSTANTANEOUS_POWER_FACTOR_TOTAL = r'^\d-\d:13\.7\.0.+?\r\n'
EON_HU_INSTANTANEOUS_POWER_FACTOR_L1 = r'^\d-\d:33\.7\.0.+?\r\n'
EON_HU_INSTANTANEOUS_POWER_FACTOR_L2 = r'^\d-\d:53\.7\.0.+?\r\n'
EON_HU_INSTANTANEOUS_POWER_FACTOR_L3 = r'^\d-\d:73\.7\.0.+?\r\n'
EON_HU_FREQUENCY = r'^\d-\d:14\.7\.0.+?\r\n'
EON_HU_INSTANTANEOUS_REACTIVE_POWER_Q1 = r'^\d-\d:5\.7\.0.+?\r\n'
EON_HU_INSTANTANEOUS_REACTIVE_POWER_Q2 = r'^\d-\d:6\.7\.0.+?\r\n'
EON_HU_INSTANTANEOUS_REACTIVE_POWER_Q3 = r'^\d-\d:7\.7\.0.+?\r\n'
EON_HU_INSTANTANEOUS_REACTIVE_POWER_Q4 = r'^\d-\d:8\.7\.0.+?\r\n'
+328 -10
View File
@@ -1,8 +1,117 @@
class DSMRObject(object):
from decimal import Decimal
def __init__(self, values):
import datetime
import json
import pytz
class Telegram(dict):
"""
Container for parsed telegram data.
Attributes can be accessed on a telegram object by addressing by their english name, for example:
telegram.ELECTRICITY_USED_TARIFF_1
All attributes in a telegram can be iterated over, for example:
[k for k,v in telegram]
yields:
['P1_MESSAGE_HEADER', 'P1_MESSAGE_TIMESTAMP', 'EQUIPMENT_IDENTIFIER', ...]
Note: Dict like usage is deprecated. The inheritance from dict is because of backwards compatibility.
"""
def __init__(self, *args, **kwargs):
self._item_names = []
self._mbus_devices = []
super().__init__(*args, **kwargs)
def add(self, obis_reference, dsmr_object, obis_name):
# Update name mapping used to get value by attribute. Example: telegram.P1_MESSAGE_HEADER
setattr(self, obis_name, dsmr_object)
# TODO isinstance check: MaxDemandParser (BELGIUM_MAXIMUM_DEMAND_13_MONTHS) returns a list
if isinstance(dsmr_object, DSMRObject) and dsmr_object.is_mbus_reading:
self._add_mbus(obis_reference, dsmr_object, obis_name)
elif obis_name not in self._item_names: # TODO repeating obis references
self._item_names.append(obis_name)
# Fill dict which is only used for backwards compatibility
if obis_reference not in self:
self[obis_reference] = dsmr_object
def _add_mbus(self, obis_reference, dsmr_object, obis_name):
"""
The given DsmrObject is assumed to be Mbus related and will be grouped into a MbusDevice.
Grouping is done by the DsmrObject channel ID.
"""
channel_id = dsmr_object.obis_id_code[1]
# Create new MbusDevice or update existing one as it's records are being added one by one.
mbus_device = self.get_mbus_device_by_channel(channel_id)
if not mbus_device:
mbus_device = MbusDevice(channel_id=channel_id)
self._mbus_devices.append(mbus_device)
mbus_device.add(obis_reference, dsmr_object, obis_name)
if not hasattr(self, 'MBUS_DEVICES'):
setattr(self, 'MBUS_DEVICES', self._mbus_devices)
self._item_names.append('MBUS_DEVICES')
def get_mbus_device_by_channel(self, channel_id):
"""
:rtype: MbusDevice|None
"""
for mbus_device in self._mbus_devices:
if mbus_device.channel_id == channel_id:
return mbus_device
def __iter__(self):
for attr in self._item_names:
value = getattr(self, attr)
yield attr, value
def __str__(self):
output = ""
for attr, value in self:
if isinstance(value, list):
output += ''.join(map(str, value))
else:
output += "{}: \t {}\n".format(attr, str(value))
return output
def to_json(self):
json_data = {}
for attr, value in self:
if isinstance(value, list):
json_data[attr] = [json.loads(item.to_json() if hasattr(item, 'to_json') else item)
for item in value]
elif hasattr(value, 'to_json'):
json_data[attr] = json.loads(value.to_json())
return json.dumps(json_data)
class DSMRObject(object):
"""
Represents all data from a single telegram line.
"""
def __init__(self, obis_id_code, values):
self.obis_id_code = obis_id_code
self.values = values
@property
def is_mbus_reading(self):
""" Detect Mbus related readings using obis id + channel. """
obis_id, channel_id = self.obis_id_code
return obis_id == 0 and channel_id != 0
def to_json(self):
raise NotImplementedError
class MBusObject(DSMRObject):
@@ -12,26 +121,105 @@ class MBusObject(DSMRObject):
@property
def value(self):
return self.values[1]['value']
# TODO temporary workaround for DSMR v2.2. Maybe use the same type of
# TODO object, but let the parse set them differently? So don't use
# TODO hardcoded indexes here.
if len(self.values) != 2: # v2
return self.values[6]['value']
else:
return self.values[1]['value']
@property
def unit(self):
return self.values[1]['unit']
# TODO temporary workaround for DSMR v2.2. Maybe use the same type of
# TODO object, but let the parse set them differently? So don't use
# TODO hardcoded indexes here.
if len(self.values) != 2: # v2
return self.values[5]['value']
else:
return self.values[1]['unit']
def __str__(self):
timestamp = self.datetime
if isinstance(timestamp, datetime.datetime):
timestamp = timestamp.astimezone().astimezone(pytz.utc).isoformat()
output = "{}\t[{}] at {}".format(
str(self.value),
str(self.unit),
str(timestamp)
)
return output
def to_json(self):
timestamp = self.datetime
if isinstance(timestamp, datetime.datetime):
timestamp = timestamp.astimezone().astimezone(pytz.utc).isoformat()
value = self.value
if isinstance(value, datetime.datetime):
value = value.astimezone().astimezone(pytz.utc).isoformat()
if isinstance(value, Decimal):
value = float(value)
output = {
'datetime': timestamp,
'value': value,
'unit': self.unit
}
return json.dumps(output)
class MBusObjectV2_2(DSMRObject):
class MBusObjectPeak(DSMRObject):
@property
def datetime(self):
return self.values[0]['value']
@property
def occurred(self):
return self.values[1]['value']
@property
def value(self):
return self.values[5]['value']
return self.values[2]['value']
@property
def unit(self):
return self.values[4]['value']
return self.values[2]['unit']
def __str__(self):
timestamp = self.datetime
if isinstance(timestamp, datetime.datetime):
timestamp = timestamp.astimezone().astimezone(pytz.utc).isoformat()
timestamp_occurred = self.occurred
if isinstance(timestamp_occurred, datetime.datetime):
timestamp_occurred = timestamp_occurred.astimezone().astimezone(pytz.utc).isoformat()
value = self.value
if isinstance(value, datetime.datetime):
value = value.astimezone().astimezone(pytz.utc).isoformat()
if isinstance(value, Decimal):
value = float(value)
output = "{}\t[{}] at {} occurred {}"\
.format(str(value), str(self.unit), str(timestamp), str(timestamp_occurred))
return output
def to_json(self):
timestamp = self.datetime
if isinstance(timestamp, datetime.datetime):
timestamp = timestamp.astimezone().astimezone(pytz.utc).isoformat()
timestamp_occurred = self.occurred
if isinstance(timestamp_occurred, datetime.datetime):
timestamp_occurred = timestamp_occurred.astimezone().astimezone(pytz.utc).isoformat()
value = self.value
if isinstance(value, datetime.datetime):
value = value.astimezone().astimezone(pytz.utc).isoformat()
if isinstance(value, Decimal):
value = float(value)
output = {
'datetime': timestamp,
'occurred': timestamp_occurred,
'value': value,
'unit': self.unit
}
return json.dumps(output)
class CosemObject(DSMRObject):
@@ -44,7 +232,137 @@ class CosemObject(DSMRObject):
def unit(self):
return self.values[0]['unit']
def __str__(self):
print_value = self.value
if isinstance(self.value, datetime.datetime):
print_value = self.value.astimezone().astimezone(pytz.utc).isoformat()
output = "{}\t[{}]".format(str(print_value), str(self.unit))
return output
class ProfileGeneric(DSMRObject):
pass
# TODO implement
def to_json(self):
json_value = self.value
if isinstance(self.value, datetime.datetime):
json_value = self.value.astimezone().astimezone(pytz.utc).isoformat()
if isinstance(self.value, Decimal):
json_value = float(self.value)
output = {
'value': json_value,
'unit': self.unit
}
return json.dumps(output)
class ProfileGenericObject(DSMRObject):
"""
Represents all data in a GenericProfile value.
All buffer values are returned as a list of MBusObjects,
containing the datetime (timestamp) and the value.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._buffer_list = None
@property
def value(self):
# value is added to make sure the telegram iterator does not break
return self.values
@property
def unit(self):
# value is added to make sure all items have a unit so code that relies on that does not break
return None
@property
def buffer_length(self):
return self.values[0]['value']
@property
def buffer_type(self):
return self.values[1]['value']
@property
def buffer(self):
if self._buffer_list is None:
self._buffer_list = []
values_offset = 2
for i in range(self.buffer_length):
offset = values_offset + i * 2
self._buffer_list.append(
MBusObject(
obis_id_code=self.obis_id_code,
values=[self.values[offset], self.values[offset + 1]]
)
)
return self._buffer_list
def __str__(self):
output = "\t buffer length: {}\n".format(self.buffer_length)
output += "\t buffer type: {}".format(self.buffer_type)
for buffer_value in self.buffer:
timestamp = buffer_value.datetime
if isinstance(timestamp, datetime.datetime):
timestamp = str(timestamp.astimezone().astimezone(pytz.utc).isoformat())
output += "\n\t event occured at: {}".format(timestamp)
output += "\t for: {} [{}]".format(buffer_value.value, buffer_value.unit)
return output
def to_json(self):
"""
:return: A json of all values in the GenericProfileObject , with the following structure
{'buffer_length': n,
'buffer_type': obis_ref,
'buffer': [{'datetime': d1,
'value': v1,
'unit': u1},
...
{'datetime': dn,
'value': vn,
'unit': un}
]
}
"""
list = [['buffer_length', self.buffer_length]]
list.append(['buffer_type', self.buffer_type])
buffer_repr = [json.loads(buffer_item.to_json()) for buffer_item in self.buffer]
list.append(['buffer', buffer_repr])
output = dict(list)
return json.dumps(output)
class MbusDevice:
"""
This object is similar to the Telegram except that it only contains readings related to the same mbus device.
"""
def __init__(self, channel_id):
self.channel_id = channel_id
self._item_names = []
def add(self, obis_reference, dsmr_object, obis_name):
# Update name mapping used to get value by attribute. Example: telegram.P1_MESSAGE_HEADER
# Also keep track of the added names internally
setattr(self, obis_name, dsmr_object)
self._item_names.append(obis_name)
def __len__(self):
return len(self._item_names)
def __iter__(self):
for attr in self._item_names:
value = getattr(self, attr)
yield attr, value
def __str__(self):
output = "MBUS DEVICE (channel {})\n".format(self.channel_id)
for attr, value in self:
output += "\t{}: \t {}\n".format(attr, str(value))
return output
def to_json(self):
data = {obis_name: json.loads(value.to_json()) for obis_name, value in self}
data['CHANNEL_ID'] = self.channel_id
return json.dumps(data)
+260 -87
View File
@@ -1,73 +1,131 @@
import logging
import re
from binascii import unhexlify
from PyCRC.CRC16 import CRC16
from ctypes import c_ushort
from decimal import Decimal
from .objects import MBusObject, MBusObjectV2_2, CosemObject
from .exceptions import ParseError, InvalidChecksumError
from .obis_references import GAS_METER_READING
from dlms_cosem.connection import XDlmsApduFactory
from dlms_cosem.protocol.xdlms import GeneralGlobalCipher
from dsmr_parser.objects import MBusObject, MBusObjectPeak, CosemObject, ProfileGenericObject, Telegram
from dsmr_parser.exceptions import ParseError, InvalidChecksumError
from dsmr_parser.value_types import timestamp
logger = logging.getLogger(__name__)
class TelegramParser(object):
crc16_tab = []
def __init__(self, telegram_specification):
def __init__(self, telegram_specification, apply_checksum_validation=True):
"""
:param telegram_specification: determines how the telegram is parsed
:param apply_checksum_validation: validate checksum if applicable for
telegram DSMR version (v4 and up).
:type telegram_specification: dict
"""
self.apply_checksum_validation = apply_checksum_validation
self.telegram_specification = telegram_specification
# Regexes are compiled once to improve performance
self.telegram_specification_regexes = {
object["obis_reference"]: re.compile(object["obis_reference"], re.DOTALL | re.MULTILINE)
for object in self.telegram_specification['objects']
}
def _find_line_parser(self, line_value):
for obis_reference, parser in self.telegram_specification.items():
if re.search(obis_reference, line_value):
return obis_reference, parser
return None, None
def parse(self, line_values):
telegram = {}
for line_value in line_values:
# TODO temporarily strip newline characters.
line_value = line_value.strip()
obis_reference, dsmr_object = self.parse_line(line_value)
telegram[obis_reference] = dsmr_object
return telegram
def parse_line(self, line_value):
logger.debug('Parsing line \'%s\'', line_value)
obis_reference, parser = self._find_line_parser(line_value)
if not parser:
logger.warning("No line class found for: '%s'", line_value)
return None, None
return obis_reference, parser.parse(line_value)
class TelegramParserV4(TelegramParser):
@staticmethod
def validate_telegram_checksum(line_values):
def parse(self, telegram_data, encryption_key="", authentication_key="", throw_ex=False): # noqa: C901
"""
:type line_values: list
Parse telegram from string to dict.
The telegram str type makes python 2.x integration easier.
:param str telegram_data: full telegram from start ('/') to checksum
('!ABCD') including line endings in between the telegram's lines
:param str encryption_key: encryption key
:param str authentication_key: authentication key
:rtype: Telegram
:raises ParseError:
:raises InvalidChecksumError:
"""
full_telegram = ''.join(line_values)
if "general_global_cipher" in self.telegram_specification:
if self.telegram_specification["general_global_cipher"]:
enc_key = unhexlify(encryption_key)
auth_key = unhexlify(authentication_key)
telegram_data = unhexlify(telegram_data)
apdu = XDlmsApduFactory.apdu_from_bytes(apdu_bytes=telegram_data)
if apdu.security_control.security_suite != 0:
logger.warning("Untested security suite")
if apdu.security_control.authenticated and not apdu.security_control.encrypted:
logger.warning("Untested authentication only")
if not apdu.security_control.authenticated and not apdu.security_control.encrypted:
logger.warning("Untested not encrypted or authenticated")
if apdu.security_control.compressed:
logger.warning("Untested compression")
if apdu.security_control.broadcast_key:
logger.warning("Untested broadcast key")
telegram_data = apdu.to_plain_apdu(enc_key, auth_key).decode("ascii")
else:
try:
if unhexlify(telegram_data[0:2])[0] == GeneralGlobalCipher.TAG:
raise RuntimeError("Looks like a general_global_cipher frame "
"but telegram specification is not matching!")
except Exception:
pass
else:
try:
if unhexlify(telegram_data[0:2])[0] == GeneralGlobalCipher.TAG:
raise RuntimeError(
"Looks like a general_global_cipher frame but telegram specification is not matching!")
except Exception:
pass
# Extract the bytes that count towards the checksum.
checksum_contents = re.search(r'\/.+\!', full_telegram, re.DOTALL)
if self.apply_checksum_validation and self.telegram_specification['checksum_support']:
self.validate_checksum(telegram_data)
telegram = Telegram()
for object in self.telegram_specification['objects']:
pattern = self.telegram_specification_regexes[object["obis_reference"]]
matches = pattern.findall(telegram_data)
# Some signatures are optional and may not be present,
# so only parse lines that match
for match in matches:
try:
dsmr_object = object["value_parser"].parse(match)
except ParseError:
logger.error(
"ignore line with signature {}, because parsing failed.".format(object["obis_reference"]),
exc_info=True
)
if throw_ex:
raise
except Exception as err:
logger.error("Unexpected {}: {}".format(type(err), err))
raise
else:
telegram.add(
obis_reference=object["obis_reference"],
dsmr_object=dsmr_object,
obis_name=object["value_name"]
)
return telegram
@staticmethod
def validate_checksum(telegram):
"""
:param str telegram:
:raises ParseError:
:raises InvalidChecksumError:
"""
# Extract the part for which the checksum applies.
checksum_contents = re.search(r'\/.+\!', telegram, re.DOTALL)
# Extract the hexadecimal checksum value itself.
checksum_hex = re.search(r'((?<=\!)[0-9A-Z]{4}(?=\r\n))+', full_telegram)
# The line ending '\r\n' for the checksum line can be ignored.
checksum_hex = re.search(r'((?<=\!)[0-9A-Z]{4})+', telegram)
if not checksum_contents or not checksum_hex:
raise ParseError(
@@ -75,9 +133,8 @@ class TelegramParserV4(TelegramParser):
'incomplete. The checksum and/or content values are missing.'
)
calculated_crc = CRC16().calculate(checksum_contents.group(0))
expected_crc = checksum_hex.group(0)
expected_crc = int(expected_crc, base=16)
calculated_crc = TelegramParser.crc16(checksum_contents.group(0))
expected_crc = int(checksum_hex.group(0), base=16)
if calculated_crc != expected_crc:
raise InvalidChecksumError(
@@ -88,51 +145,78 @@ class TelegramParserV4(TelegramParser):
)
)
def parse(self, line_values):
self.validate_telegram_checksum(line_values)
@staticmethod
def crc16(telegram):
"""
Calculate the CRC16 value for the given telegram
return super().parse(line_values)
:param str telegram:
"""
crcValue = 0x0000
if len(TelegramParser.crc16_tab) == 0:
for i in range(0, 256):
crc = c_ushort(i).value
for j in range(0, 8):
if (crc & 0x0001):
crc = c_ushort(crc >> 1).value ^ 0xA001
else:
crc = c_ushort(crc >> 1).value
TelegramParser.crc16_tab.append(hex(crc))
class TelegramParserV2_2(TelegramParser):
for c in telegram:
d = ord(c)
tmp = crcValue ^ d
rotated = c_ushort(crcValue >> 8).value
crcValue = rotated ^ int(TelegramParser.crc16_tab[(tmp & 0x00ff)], 0)
def parse(self, line_values):
"""Join lines for gas meter."""
def join_lines(line_values):
join_next = re.compile(GAS_METER_READING)
join = None
for line_value in line_values:
if join:
yield join.strip() + line_value
join = None
elif join_next.match(line_value):
join = line_value
else:
yield line_value
return super().parse(join_lines(line_values))
return crcValue
class DSMRObjectParser(object):
"""
Parses an object (can also be see as a 'line') from a telegram.
"""
def __init__(self, *value_formats):
self.value_formats = value_formats
def _is_line_wellformed(self, line, values):
# allows overriding by child class
return (values and (len(values) == len(self.value_formats)))
def _parse_values(self, values):
# allows overriding by child class
return [self.value_formats[i].parse(value)
for i, value in enumerate(values)]
def _parse_obis_id_code(self, line):
"""
Get the OBIS ID code
Example line:
'0-2:24.2.1(200426223001S)(00246.138*m3)'
OBIS ID code = 0-2 returned as tuple
"""
try:
return int(line[0]), int(line[2])
except ValueError:
raise ParseError("Invalid OBIS ID code for line '%s' in '%s'", line, self)
def _parse(self, line):
# Match value groups, but exclude the parentheses
pattern = re.compile(r'((?<=\()[0-9a-zA-Z\.\*]{0,}(?=\)))+')
pattern = re.compile(r'((?<=\()[0-9a-zA-Z\.\*\-\:]{0,}(?=\)))')
values = re.findall(pattern, line)
if not self._is_line_wellformed(line, values):
raise ParseError("Invalid '%s' line for '%s'", line, self)
# Convert empty value groups to None for clarity.
values = [None if value == '' else value for value in values]
if not values or len(values) != len(self.value_formats):
raise ParseError("Invalid '%s' line for '%s'", line, self)
return [self.value_formats[i].parse(value)
for i, value in enumerate(values)]
return self._parse_values(values)
class MBusParser(DSMRObjectParser):
@@ -153,11 +237,52 @@ class MBusParser(DSMRObjectParser):
"""
def parse(self, line):
values = self._parse(line)
if len(values) == 2:
return MBusObject(values)
else:
return MBusObjectV2_2(values)
return MBusObject(
obis_id_code=self._parse_obis_id_code(line),
values=self._parse(line)
)
class MaxDemandParser(DSMRObjectParser):
"""
Max demand history parser.
These are lines with multiple values. Each containing 2 timestamps and a value
Line format:
'ID (Count) (ID) (ID) (TST) (TST) (Mv1*U1)'
1 2 3 4 5 6 7
1) OBIS Reduced ID-code
2) Amount of values in the response
3) ID of the source
4) ^^
5) Time Stamp (TST) of the month
6) Time Stamp (TST) when the max demand occured
6) Measurement value 1 (most recent entry of buffer attribute without unit)
7) Unit of measurement values (Unit of capture objects attribute)
"""
def parse(self, line):
pattern = re.compile(r'((?<=\()[0-9a-zA-Z\.\*\-\:]{0,}(?=\)))')
values = re.findall(pattern, line)
obis_id_code = self._parse_obis_id_code(line)
objects = []
count = int(values[0])
for i in range(1, count + 1):
timestamp_month = ValueParser(timestamp).parse(values[i * 3 + 0])
timestamp_occurred = ValueParser(timestamp).parse(values[i * 3 + 1])
value = ValueParser(Decimal).parse(values[i * 3 + 2])
objects.append(MBusObjectPeak(
obis_id_code=obis_id_code,
values=[timestamp_month, timestamp_occurred, value]
))
return objects
class CosemParser(DSMRObjectParser):
@@ -173,14 +298,18 @@ class CosemParser(DSMRObjectParser):
1 23 45
1) OBIS Reduced ID-code
2) Separator “(“, ASCII 28h
2) Separator "(", ASCII 28h
3) COSEM object attribute value
4) Unit of measurement values (Unit of capture objects attribute) – only if applicable
5) Separator “)”, ASCII 29h
4) Unit of measurement values (Unit of capture objects attribute) - only if
applicable
5) Separator ")", ASCII 29h
"""
def parse(self, line):
return CosemObject(self._parse(line))
return CosemObject(
obis_id_code=self._parse_obis_id_code(line),
values=self._parse(line)
)
class ProfileGenericParser(DSMRObjectParser):
@@ -205,17 +334,61 @@ class ProfileGenericParser(DSMRObjectParser):
9) Unit of buffer values (Unit of capture objects attribute)
"""
def __init__(self, buffer_types, head_parsers, parsers_for_unidentified):
self.value_formats = head_parsers.copy()
self.buffer_types = buffer_types
self.parsers_for_unidentified = parsers_for_unidentified
def _is_line_wellformed(self, line, values):
if values and (len(values) == 1) and (values[0] == ''):
# special case: single empty parentheses (indicated by empty string)
return True
if values and (len(values) >= 2) and (values[0].isdigit()):
buffer_length = int(values[0])
return (buffer_length <= 10) and (len(values) == (buffer_length * 2 + 2))
else:
return False
def _parse_values(self, values):
if values and (len(values) == 1) and (values[0] is None):
# special case: single empty parentheses; make sure empty ProfileGenericObject is created
values = [0, None] # buffer_length=0, buffer_value_obis_ID=None
buffer_length = int(values[0])
buffer_value_obis_ID = values[1]
if (buffer_length > 0):
if buffer_value_obis_ID in self.buffer_types:
bufferValueParsers = self.buffer_types[buffer_value_obis_ID]
else:
bufferValueParsers = self.parsers_for_unidentified
# add the parsers for the encountered value type z times
for _ in range(buffer_length):
self.value_formats.extend(bufferValueParsers)
return [self.value_formats[i].parse(value) for i, value in enumerate(values)]
def parse(self, line):
raise NotImplementedError()
return ProfileGenericObject(
obis_id_code=self._parse_obis_id_code(line),
values=self._parse(line)
)
class ValueParser(object):
"""
Parses a single value from DSMRObject's.
Example with coerce_type being int:
(002*A) becomes {'value': 2, 'unit': 'A'}
Example with coerce_type being str:
(42) becomes {'value': '42', 'unit': None}
"""
def __init__(self, coerce_type):
self.coerce_type = coerce_type
def parse(self, value):
unit_of_measurement = None
if value and '*' in value:
@@ -0,0 +1,10 @@
from dsmr_parser.parsers import ValueParser
from dsmr_parser.value_types import timestamp
PG_FAILURE_EVENT = r'0-0:96.7.19'
PG_HEAD_PARSERS = [ValueParser(int), ValueParser(str)]
PG_UNIDENTIFIED_BUFFERTYPE_PARSERS = [ValueParser(str), ValueParser(str)]
BUFFER_TYPES = {
PG_FAILURE_EVENT: [ValueParser(timestamp), ValueParser(int)]
}
-125
View File
@@ -1,125 +0,0 @@
"""Asyncio protocol implementation for handling telegrams."""
import asyncio
import logging
from functools import partial
from serial_asyncio import create_serial_connection
from . import telegram_specifications
from .exceptions import ParseError
from .parsers import TelegramParserV2_2, TelegramParserV4
from .serial import (SERIAL_SETTINGS_V2_2, SERIAL_SETTINGS_V4,
is_end_of_telegram, is_start_of_telegram)
def create_dsmr_protocol(dsmr_version, telegram_callback, loop=None):
"""Creates a DSMR asyncio protocol."""
if dsmr_version == '2.2':
specifications = telegram_specifications.V2_2
telegram_parser = TelegramParserV2_2
serial_settings = SERIAL_SETTINGS_V2_2
elif dsmr_version == '4':
specifications = telegram_specifications.V4
telegram_parser = TelegramParserV4
serial_settings = SERIAL_SETTINGS_V4
protocol = partial(DSMRProtocol, loop, telegram_parser(specifications),
telegram_callback=telegram_callback)
return protocol, serial_settings
def create_dsmr_reader(port, dsmr_version, telegram_callback, loop=None):
"""Creates a DSMR asyncio protocol coroutine using serial port."""
protocol, serial_settings = create_dsmr_protocol(
dsmr_version, telegram_callback, loop=None)
serial_settings['url'] = port
conn = create_serial_connection(loop, protocol, **serial_settings)
return conn
def create_tcp_dsmr_reader(host, port, dsmr_version,
telegram_callback, loop=None):
"""Creates a DSMR asyncio protocol coroutine using TCP connection."""
protocol, _ = create_dsmr_protocol(
dsmr_version, telegram_callback, loop=None)
conn = loop.create_connection(protocol, host, port)
return conn
class DSMRProtocol(asyncio.Protocol):
"""Assemble and handle incoming data into complete DSM telegrams."""
transport = None
telegram_callback = None
def __init__(self, loop, telegram_parser, telegram_callback=None):
"""Initialize class."""
self.loop = loop
self.log = logging.getLogger(__name__)
self.telegram_parser = telegram_parser
# callback to call on complete telegram
self.telegram_callback = telegram_callback
# buffer to keep incoming telegram lines
self.telegram = []
# buffer to keep incomplete incoming data
self.buffer = ''
# keep a lock until the connection is closed
self._closed = asyncio.Event()
def connection_made(self, transport):
"""Just logging for now."""
self.transport = transport
self.log.debug('connected')
def data_received(self, data):
"""Add incoming data to buffer."""
data = data.decode()
self.log.debug('received data: %s', data.strip())
self.buffer += data
self.handle_lines()
def handle_lines(self):
"""Assemble incoming data into single lines."""
crlf = "\r\n"
while crlf in self.buffer:
line, self.buffer = self.buffer.split(crlf, 1)
self.log.debug('got line: %s', line)
line += crlf # add the trailing crlf again
# Telegrams need to be complete because the values belong to a
# particular reading and can also be related to eachother.
if not self.telegram and not is_start_of_telegram(line):
continue
self.telegram.append(line)
if is_end_of_telegram(line):
try:
parsed_telegram = self.telegram_parser.parse(self.telegram)
self.handle_telegram(parsed_telegram)
except ParseError:
self.log.exception("failed to parse telegram")
self.telegram = []
def connection_lost(self, exc):
"""Stop when connection is lost."""
if exc:
self.log.exception('disconnected due to exception')
else:
self.log.info('disconnected because of close/abort.')
self._closed.set()
def handle_telegram(self, telegram):
"""Send off parsed telegram to handling callback."""
self.log.debug('got telegram: %s', telegram)
if self.telegram_callback:
self.telegram_callback(telegram)
@asyncio.coroutine
def wait_closed(self):
"""Wait until connection is closed."""
yield from self._closed.wait()
-138
View File
@@ -1,138 +0,0 @@
import asyncio
import logging
import serial
import serial_asyncio
from dsmr_parser.exceptions import ParseError
from dsmr_parser.parsers import TelegramParser, TelegramParserV2_2, \
TelegramParserV4
logger = logging.getLogger(__name__)
SERIAL_SETTINGS_V2_2 = {
'baudrate': 9600,
'bytesize': serial.SEVENBITS,
'parity': serial.PARITY_EVEN,
'stopbits': serial.STOPBITS_ONE,
'xonxoff': 0,
'rtscts': 0,
'timeout': 20
}
SERIAL_SETTINGS_V4 = {
'baudrate': 115200,
'bytesize': serial.SEVENBITS,
'parity': serial.PARITY_EVEN,
'stopbits': serial.STOPBITS_ONE,
'xonxoff': 0,
'rtscts': 0,
'timeout': 20
}
def is_start_of_telegram(line):
"""
:type line: line
"""
return line.startswith('/')
def is_end_of_telegram(line):
"""
:type line: line
"""
return line.startswith('!')
class SerialReader(object):
PORT_KEY = 'port'
def __init__(self, device, serial_settings, telegram_specification):
self.serial_settings = serial_settings
self.serial_settings[self.PORT_KEY] = device
if serial_settings is SERIAL_SETTINGS_V2_2:
telegram_parser = TelegramParserV2_2
elif serial_settings is SERIAL_SETTINGS_V4:
telegram_parser = TelegramParserV4
else:
telegram_parser = TelegramParser
self.telegram_parser = telegram_parser(telegram_specification)
def read(self):
"""
Read complete DSMR telegram's from the serial interface and parse it
into CosemObject's and MbusObject's
:rtype dict
"""
with serial.Serial(**self.serial_settings) as serial_handle:
telegram = []
while True:
line = serial_handle.readline()
line = line.decode('ascii') # TODO move this to the parser?
# Telegrams need to be complete because the values belong to a
# particular reading and can also be related to eachother.
if not telegram and not is_start_of_telegram(line):
continue
telegram.append(line)
if is_end_of_telegram(line):
try:
yield self.telegram_parser.parse(telegram)
except ParseError as e:
logger.error('Failed to parse telegram: %s', e)
telegram = []
class AsyncSerialReader(SerialReader):
"""Serial reader using asyncio pyserial."""
PORT_KEY = 'url'
@asyncio.coroutine
def read(self, queue):
"""
Read complete DSMR telegram's from the serial interface and parse it
into CosemObject's and MbusObject's.
Instead of being a generator, values are pushed to provided queue for
asynchronous processing.
:rtype Generator/Async
"""
# create Serial StreamReader
conn = serial_asyncio.open_serial_connection(**self.serial_settings)
reader, _ = yield from conn
telegram = []
while True:
# read line if available or give control back to loop until
# new data has arrived
line = yield from reader.readline()
line = line.decode('ascii')
# Telegrams need to be complete because the values belong to a
# particular reading and can also be related to eachother.
if not telegram and not is_start_of_telegram(line):
continue
telegram.append(line)
if is_end_of_telegram(line):
try:
parsed_telegram = self.telegram_parser.parse(telegram)
# push new parsed telegram onto queue
queue.put_nowait(parsed_telegram)
except ParseError as e:
logger.warning('Failed to parse telegram: %s', e)
telegram = []
File diff suppressed because it is too large Load Diff
+14 -3
View File
@@ -2,17 +2,28 @@ import datetime
import pytz
# TODO : Use system timezone
# Preload timezone to avoid loading in event loop later
local_tz = pytz.timezone('Europe/Amsterdam')
def timestamp(value):
naive_datetime = datetime.datetime.strptime(value[:-1], '%y%m%d%H%M%S')
try:
naive_datetime = datetime.datetime.strptime(value[:-1], '%y%m%d%H%M%S')
except ValueError:
return None
# TODO comment on this exception
# Timestamp has the following format:
# YYMMDDhhmmssX
# ASCII presentation of Time stamp with
# Year, Month, Day, Hour, Minute, Second,
# and an indication whether DST is active
# (X=S) or DST is not active (X=W)
if len(value) == 13:
is_dst = value[12] == 'S' # assume format 160322150000W
else:
is_dst = False
local_tz = pytz.timezone('Europe/Amsterdam')
localized_datetime = local_tz.localize(naive_datetime, is_dst=is_dst)
return localized_datetime.astimezone(pytz.utc)
+11
View File
@@ -0,0 +1,11 @@
{
"extension": ".py",
"source": "from functools import partial\nimport argparse\nimport asyncio\nimport logging\n\nfrom dsmr_parser.clients import create_dsmr_reader, create_tcp_dsmr_reader\n\n\ndef console():\n \"\"\"Output DSMR data to console.\"\"\"\n\n parser = argparse.ArgumentParser(description=console.__doc__)\n parser.add_argument('--device', default='/dev/ttyUSB0',\n help='port to read DSMR data from')\n parser.add_argument('--host', default=None,\n help='alternatively connect using TCP host.')\n parser.add_argument('--port', default=None,\n help='TCP port to use for connection')\n parser.add_argument('--version', default='2.2', choices=['2.2', '4', '5', '5B', '5L', '5S', 'Q3D'],\n help='DSMR version (2.2, 4, 5, 5B, 5L, 5S, Q3D)')\n parser.add_argument('--verbose', '-v', action='count')\n\n args = parser.parse_args()\n\n if args.verbose:\n level = logging.DEBUG\n else:\n level = logging.ERROR\n logging.basicConfig(level=level)\n\n loop = asyncio.get_event_loop()\n\n def print_callback(telegram):\n \"\"\"Callback that prints telegram values.\"\"\"\n for obiref, obj in telegram.items():\n if obj:\n print(obj.value, obj.unit)\n print()\n\n # create tcp or serial connection depending on args\n if args.host and args.port:\n create_connection = partial(create_tcp_dsmr_reader,\n args.host, args.port, args.version,\n print_callback, loop=loop)\n else:\n create_connection = partial(create_dsmr_reader,\n args.device, args.version,\n print_callback, loop=loop)\n\n try:\n # connect and keep connected until interrupted by ctrl-c\n while True:\n # create serial or tcp connection\n conn = create_connection()\n transport, protocol = loop.run_until_complete(conn)\n # wait until connection it closed\n loop.run_until_complete(protocol.wait_closed())\n # wait 5 seconds before attempting reconnect\n loop.run_until_complete(asyncio.sleep(5))\n except KeyboardInterrupt:\n # cleanup connection after user initiated shutdown\n transport.close()\n loop.run_until_complete(asyncio.sleep(0))\n finally:\n loop.close()\n",
"review": "**Grade: 7**\n\n### Bugs\n\n- There are no identifiable bugs causing the script to malfunction, but use of exceptions could be refined.\n\n### Optimizations\n\n- Use `asyncio.run()` instead of `loop.run_until_complete()` for simplicity and better structure in modern Python versions.\n- Consider using a specific logger instead of the root logger for more flexible logging configurations.\n- Add exception handling around network or serial connection to manage specific exceptions like `ConnectionError`.\n\n### Good points\n\n- The use of `argparse` makes the script flexible and easy to configure via command-line arguments.\n- Persistent reconnection strategy in case of connection drops is well-implemented.\n- Callback function (`print_callback`) is defined clearly and is easily extensible.\n\n### Summary\n\nThe script uses Python's asyncio feature to continuously read data from a DSMR device, either over a network or serial port, and outputs the data to the console. It is flexible due to the use of `argparse` for command-line argument parsing and handles reconnection attempts upon connection loss effectively. However, the script would benefit from some modernization using the latest asyncio practices and improved logging management.\n\n### Open source alternatives\n\n- **Home Assistant**: An open-source platform that supports DSMR readers among many other devices, offering a more comprehensive home automation solution.\n- **Domoticz**: Another home automation system that includes support for DSMR as one of its features for monitoring energy consumption.",
"filename": "__main__.py",
"path": "dsmr_parser/__main__.py",
"directory": "dsmr_parser",
"grade": 7,
"size": 2406,
"line_count": 66
}
+26
View File
@@ -0,0 +1,26 @@
**Grade: 7**
### Bugs
- There are no identifiable bugs causing the script to malfunction, but use of exceptions could be refined.
### Optimizations
- Use `asyncio.run()` instead of `loop.run_until_complete()` for simplicity and better structure in modern Python versions.
- Consider using a specific logger instead of the root logger for more flexible logging configurations.
- Add exception handling around network or serial connection to manage specific exceptions like `ConnectionError`.
### Good points
- The use of `argparse` makes the script flexible and easy to configure via command-line arguments.
- Persistent reconnection strategy in case of connection drops is well-implemented.
- Callback function (`print_callback`) is defined clearly and is easily extensible.
### Summary
The script uses Python's asyncio feature to continuously read data from a DSMR device, either over a network or serial port, and outputs the data to the console. It is flexible due to the use of `argparse` for command-line argument parsing and handles reconnection attempts upon connection loss effectively. However, the script would benefit from some modernization using the latest asyncio practices and improved logging management.
### Open source alternatives
- **Home Assistant**: An open-source platform that supports DSMR readers among many other devices, offering a more comprehensive home automation solution.
- **Domoticz**: Another home automation system that includes support for DSMR as one of its features for monitoring energy consumption.
@@ -0,0 +1,11 @@
{
"extension": ".py",
"source": "from dsmr_parser.clients.settings import SERIAL_SETTINGS_V2_2, \\\n SERIAL_SETTINGS_V4, SERIAL_SETTINGS_V5\nfrom dsmr_parser.clients.serial_ import SerialReader, AsyncSerialReader\nfrom dsmr_parser.clients.socket_ import SocketReader\nfrom dsmr_parser.clients.protocol import create_dsmr_protocol, \\\n create_dsmr_reader, create_tcp_dsmr_reader\n",
"review": "**8**\n\n### Bugs\n- No bugs detected in the provided code snippet.\n\n### Optimizations\n- Consider adding comments or docstrings to improve code readability and maintainability.\n- Ensure that the necessary exception handling is in place when using these imports in the main logic to prevent runtime errors.\n \n### Good points\n- The code is well-organized with logical grouping of related imports.\n- Efficient use of line continuation with backslashes for better readability.\n- The imports seem to imply a modular program structure which is beneficial for maintenance and testing.\n\n### Summary\nThe code is clean and well-organized, with a logical structure in grouping related imports. Although the snippet doesn't reveal implementation details, it suggests a modular design that is beneficial for maintenance and testing. Improvements in documentation such as comments and docstrings would enhance the comprehensibility of this snippet when integrated into a larger codebase. It is essential to ensure exception handling when these imports are used in practice.\n\n### Open source alternatives\n- **Home Assistant**: It is an open-source platform designed to help you manage your smart home devices. Home Assistant integrates with devices and services like DSMR (Dutch Smart Meter Reading).\n- **OpenEnergyMonitor**: A tool for monitoring energy usage using open-source blueprints for energy monitoring systems and can be adapted for smart meter readings.",
"filename": "__init__.py",
"path": "dsmr_parser/clients/__init__.py",
"directory": "clients",
"grade": 8,
"size": 345,
"line_count": 7
}
@@ -0,0 +1,20 @@
**8**
### Bugs
- No bugs detected in the provided code snippet.
### Optimizations
- Consider adding comments or docstrings to improve code readability and maintainability.
- Ensure that the necessary exception handling is in place when using these imports in the main logic to prevent runtime errors.
### Good points
- The code is well-organized with logical grouping of related imports.
- Efficient use of line continuation with backslashes for better readability.
- The imports seem to imply a modular program structure which is beneficial for maintenance and testing.
### Summary
The code is clean and well-organized, with a logical structure in grouping related imports. Although the snippet doesn't reveal implementation details, it suggests a modular design that is beneficial for maintenance and testing. Improvements in documentation such as comments and docstrings would enhance the comprehensibility of this snippet when integrated into a larger codebase. It is essential to ensure exception handling when these imports are used in practice.
### Open source alternatives
- **Home Assistant**: It is an open-source platform designed to help you manage your smart home devices. Home Assistant integrates with devices and services like DSMR (Dutch Smart Meter Reading).
- **OpenEnergyMonitor**: A tool for monitoring energy usage using open-source blueprints for energy monitoring systems and can be adapted for smart meter readings.
File diff suppressed because one or more lines are too long
@@ -0,0 +1,24 @@
# 7
## Bugs
- In the `FileInputReader.read_as_object` method, a local variable named `str` is used, which shadows the built-in `str` type.
- There are inconsistencies with the use of exception logging levels between classes (e.g., `logger.info` vs `logger.warning`).
- The code comments mention the usage of `FileReader` instead of `FileInputReader` in the `FileInputReader` class documentation.
## Optimizations
- Consider using context managers `with open()` for writing to the file in the commented documentation for better resource handling.
- Refactor repetitive code patterns in different classes into a shared function or base class to follow the DRY principle.
- Use more explicit exception messages for easier debugging inside the exception handling.
- Ensure `telegram_buffer` and `telegram_parser` are adequately validated on initialization to handle potential errors gracefully.
## Good points
- The code is well-structured and organized with each class having a specific responsibility.
- Detailed documentation is provided for usage of these classes in different contexts.
- Makes effective use of Python's generator pattern offering optimal memory usage.
## Summary
The code effectively reads and parses DSMR telegram data from files and standard input, offering structured class-based utilities for different ways of input. There are a few inconsistencies in variable naming and logging levels, which could potentially lead to maintenance challenges in the future. The repeated code could benefit from a refactor to improve readability and reduce duplication. However, the code is well-documented and designed with clear intentions, which helps subsequent developers understand the workflow.
## Open source alternatives
- **OpenDSMR**: An open-source DSMR reading platform with extensive support for various formats and devices.
- **dsmr-reader**: A web application that reads and processes DSMR telegram data, provides dashboards and API access.
File diff suppressed because one or more lines are too long
@@ -0,0 +1,27 @@
# 6
## Bugs
- Potential memory leak if large amounts of data are appended to `telegram_buffer` without being processed and cleared.
- The `SERIAL_SETTINGS` are specified but not provided, making it unclear if the implementation correctly interfaces with serial connections.
- Exception messages are logged but not raised or handled in any way that could allow upstream error handling.
- The `loop=None` argument in `create_dsmr_reader` is not used when invoking `create_dsmr_protocol`.
## Optimizations
- Consider using the Python `async`/`await` syntax more extensively to simplify structure and improve readability, especially around network connection management.
- The lengthy if-elif chain in `_create_dsmr_protocol` could be replaced with a dictionary mapping DSMR versions to their specifications and serial settings, which would simplify readability and maintenance.
- Implement `match-case` for cleaner and more efficient branching when Python 3.10+ become a baseline.
- Include error recovery mechanisms or retries in case of connection failures.
- Use a timeout or limit for the connection methods to avoid indefinite blocking.
## Good points
- The use of logging helps in debugging and tracking the flow of data and errors.
- Protocol separation caters well to both serial and TCP connection methods.
- Asyncio `Protocol` based design is a good choice for handling asynchronous data over IO streams.
## Summary
The code provides an asyncio-based DSMR protocol implementation aimed at handling telegram data via various connection types. While it effectively utilizes asyncio for asynchronous operations, the structure would benefit from using modern Python features for cleaner and more maintainable code. Several issues, such as potential memory leaks and incomplete error handling, would require attention. The use of logging is well-implemented, facilitating better debugging and monitoring during runtime.
## Open source alternatives
- **dsmr-reader**: A software tool for reading and visualizing DSMR data which uses Django as its web framework and has a rich set of features including long-term data storage.
- **home-Assistant DSMR**: A popular home automation system that supports DSMR protocol for Dutch smart meters as part of its core or as an integration.
- **pySerial**: A library offering serial port bindings for Python, which can be used as a foundation for handling serial communications.
@@ -0,0 +1,11 @@
{
"extension": ".py",
"source": "\"\"\"Asyncio protocol implementation for handling telegrams over a RFXtrx connection .\"\"\"\n\nimport asyncio\n\nfrom serial_asyncio_fast import create_serial_connection\nfrom .protocol import DSMRProtocol, _create_dsmr_protocol\n\n\ndef create_rfxtrx_dsmr_protocol(dsmr_version, telegram_callback, loop=None, **kwargs):\n \"\"\"Creates a RFXtrxDSMR asyncio protocol.\"\"\"\n protocol = _create_dsmr_protocol(dsmr_version, telegram_callback,\n RFXtrxDSMRProtocol, loop, **kwargs)\n return protocol\n\n\ndef create_rfxtrx_dsmr_reader(port, dsmr_version, telegram_callback, loop=None):\n \"\"\"Creates a DSMR asyncio protocol coroutine using a RFXtrx serial port.\"\"\"\n protocol, serial_settings = create_rfxtrx_dsmr_protocol(\n dsmr_version, telegram_callback, loop=None)\n serial_settings['url'] = port\n\n conn = create_serial_connection(loop, protocol, **serial_settings)\n return conn\n\n\ndef create_rfxtrx_tcp_dsmr_reader(host, port, dsmr_version,\n telegram_callback, loop=None,\n keep_alive_interval=None):\n \"\"\"Creates a DSMR asyncio protocol coroutine using a RFXtrx TCP connection.\"\"\"\n if not loop:\n loop = asyncio.get_event_loop()\n protocol, _ = create_rfxtrx_dsmr_protocol(\n dsmr_version, telegram_callback, loop=loop,\n keep_alive_interval=keep_alive_interval)\n conn = loop.create_connection(protocol, host, port)\n return conn\n\n\nPACKETTYPE_DSMR = 0x62\nSUBTYPE_P1 = 0x01\n\n\nclass RFXtrxDSMRProtocol(DSMRProtocol):\n\n remaining_data = b''\n\n def data_received(self, data):\n \"\"\"Add incoming data to buffer.\"\"\"\n\n data = self.remaining_data + data\n\n packetlength = data[0] + 1 if len(data) > 0 else 1\n while packetlength <= len(data):\n packettype = data[1]\n subtype = data[2]\n if (packettype == PACKETTYPE_DSMR and subtype == SUBTYPE_P1):\n dsmr_data = data[4:packetlength]\n super().data_received(dsmr_data)\n data = data[packetlength:]\n packetlength = data[0] + 1 if len(data) > 0 else 1\n\n self.remaining_data = data\n",
"review": "**Grade: 7**\n\n### Bugs\n- Missing error handling or verification when unpacking the data could lead to potential runtime errors if the data source sends malformed packets.\n\n### Optimizations\n- Consider adding logging for connection attempts and data receipt, which would aid in debugging and monitoring the system.\n- Potentially utilize the modern `asyncio.run()` function or `async/await` syntax to handle the coroutine for Python 3.7+ for a more readable and concise structure.\n- The method `create_rfxtrx_dsmr_reader` redundantly sets `loop=None` in the `create_rfxtrx_dsmr_protocol()` call, as `None` is already the default value.\n- Checking packet integrity and validating specifically sized packets before processing would enhance robustness.\n\n### Good Points\n- The code leverages `asyncio` for asynchronous operations, making it suitable for I/O-bound tasks without blocking the executing thread.\n- The modular design by using factory functions (`create_rfxtrx_dsmr_protocol`, `create_rfxtrx_dsmr_reader`, etc.) enhances reusability and clarity.\n- Clear separation of concerns; handling of data reception and protocol setup are well-defined and focused.\n\n### Summary\nThe given asyncio protocol implementation is well-structured, with a focus on establishing and handling RFXtrx connections. While the code handles the basic requirements adequately, it lacks error checking and logging, which are crucial in network programming for reliability and troubleshooting. Updating to more modern asynchronous patterns could enhance its readability and ease of use.\n\n### Open source alternatives\n- **pyRFXtrx**: A library specifically designed for RFXtrx connections and communications, with broader device support and robust functionalities.\n- **home-assistant-rfxtrx**: A component of Home Assistant, although more comprehensive, it provides extended utilities and is actively maintained by the community.",
"filename": "rfxtrx_protocol.py",
"path": "dsmr_parser/clients/rfxtrx_protocol.py",
"directory": "clients",
"grade": 7,
"size": 2173,
"line_count": 63
}
@@ -0,0 +1,22 @@
**Grade: 7**
### Bugs
- Missing error handling or verification when unpacking the data could lead to potential runtime errors if the data source sends malformed packets.
### Optimizations
- Consider adding logging for connection attempts and data receipt, which would aid in debugging and monitoring the system.
- Potentially utilize the modern `asyncio.run()` function or `async/await` syntax to handle the coroutine for Python 3.7+ for a more readable and concise structure.
- The method `create_rfxtrx_dsmr_reader` redundantly sets `loop=None` in the `create_rfxtrx_dsmr_protocol()` call, as `None` is already the default value.
- Checking packet integrity and validating specifically sized packets before processing would enhance robustness.
### Good Points
- The code leverages `asyncio` for asynchronous operations, making it suitable for I/O-bound tasks without blocking the executing thread.
- The modular design by using factory functions (`create_rfxtrx_dsmr_protocol`, `create_rfxtrx_dsmr_reader`, etc.) enhances reusability and clarity.
- Clear separation of concerns; handling of data reception and protocol setup are well-defined and focused.
### Summary
The given asyncio protocol implementation is well-structured, with a focus on establishing and handling RFXtrx connections. While the code handles the basic requirements adequately, it lacks error checking and logging, which are crucial in network programming for reliability and troubleshooting. Updating to more modern asynchronous patterns could enhance its readability and ease of use.
### Open source alternatives
- **pyRFXtrx**: A library specifically designed for RFXtrx connections and communications, with broader device support and robust functionalities.
- **home-assistant-rfxtrx**: A component of Home Assistant, although more comprehensive, it provides extended utilities and is actively maintained by the community.
@@ -0,0 +1,11 @@
{
"extension": ".py",
"source": "import logging\nimport serial\nimport serial_asyncio_fast\n\nfrom dsmr_parser.clients.telegram_buffer import TelegramBuffer\nfrom dsmr_parser.exceptions import ParseError, InvalidChecksumError\nfrom dsmr_parser.parsers import TelegramParser\n\n\nlogger = logging.getLogger(__name__)\n\n\nclass SerialReader(object):\n PORT_KEY = 'port'\n\n def __init__(self, device, serial_settings, telegram_specification):\n self.serial_settings = serial_settings\n self.serial_settings[self.PORT_KEY] = device\n\n self.telegram_parser = TelegramParser(telegram_specification)\n self.telegram_buffer = TelegramBuffer()\n self.telegram_specification = telegram_specification\n\n def read(self):\n \"\"\"\n Read complete DSMR telegram's from the serial interface and parse it\n into CosemObject's and MbusObject's\n\n :rtype: generator\n \"\"\"\n with serial.Serial(**self.serial_settings) as serial_handle:\n while True:\n data = serial_handle.read(max(1, min(1024, serial_handle.in_waiting)))\n self.telegram_buffer.append(data.decode('ascii'))\n\n for telegram in self.telegram_buffer.get_all():\n try:\n yield self.telegram_parser.parse(telegram)\n except InvalidChecksumError as e:\n logger.info(str(e))\n except ParseError as e:\n logger.error('Failed to parse telegram: %s', e)\n\n def read_as_object(self):\n \"\"\"\n Read complete DSMR telegram's from the serial interface and return a Telegram object.\n\n :rtype: generator\n \"\"\"\n with serial.Serial(**self.serial_settings) as serial_handle:\n while True:\n data = serial_handle.readline()\n self.telegram_buffer.append(data.decode('ascii'))\n\n for telegram in self.telegram_buffer.get_all():\n try:\n yield self.telegram_parser.parse(telegram)\n except InvalidChecksumError as e:\n logger.warning(str(e))\n except ParseError as e:\n logger.error('Failed to parse telegram: %s', e)\n\n\nclass AsyncSerialReader(SerialReader):\n \"\"\"Serial reader using asyncio pyserial.\"\"\"\n\n PORT_KEY = 'url'\n\n async def read(self, queue):\n \"\"\"\n Read complete DSMR telegram's from the serial interface and parse it\n into CosemObject's and MbusObject's.\n\n Instead of being a generator, values are pushed to provided queue for\n asynchronous processing.\n\n :rtype: None\n \"\"\"\n # create Serial StreamReader\n conn = serial_asyncio_fast.open_serial_connection(**self.serial_settings)\n reader, _ = await conn\n\n while True:\n # Read line if available or give control back to loop until new\n # data has arrived.\n data = await reader.readline()\n self.telegram_buffer.append(data.decode('ascii'))\n\n for telegram in self.telegram_buffer.get_all():\n try:\n # Push new parsed telegram onto queue.\n queue.put_nowait(\n self.telegram_parser.parse(telegram)\n )\n except ParseError as e:\n logger.warning('Failed to parse telegram: %s', e)\n\n async def read_as_object(self, queue):\n \"\"\"\n Read complete DSMR telegram's from the serial interface\n and return a Telegram object.\n\n Instead of being a generator, Telegram objects are pushed\n to provided queue for asynchronous processing.\n\n :rtype: None\n \"\"\"\n\n # create Serial StreamReader\n conn = serial_asyncio_fast.open_serial_connection(**self.serial_settings)\n reader, _ = await conn\n\n while True:\n\n # Read line if available or give control back to loop until new\n # data has arrived.\n data = await reader.readline()\n self.telegram_buffer.append(data.decode('ascii'))\n\n for telegram in self.telegram_buffer.get_all():\n try:\n queue.put_nowait(\n self.telegram_parser.parse(telegram)\n )\n except InvalidChecksumError as e:\n logger.warning(str(e))\n except ParseError as e:\n logger.error('Failed to parse telegram: %s', e)\n",
"review": "# 7\n\n## Bugs\n- Potentially, `serial_asyncio_fast` is a non-standard library as there is no common knowledge about it, which might lead to compatibility issues or conflicts.\n- The repeated use of `data.decode('ascii')` could fail if non-ASCII bytes are received, causing an exception.\n- `serial_asyncio_fast.open_serial_connection` usage might be problematic if `serial_asyncio_fast` has different configurations or parameters for `open_serial_connection`.\n\n## Optimizations\n- Consider using a context manager for managing the async serial connection in `AsyncSerialReader`.\n- Add error handling to manage unexpected EOF or IOError during serial reads.\n- Use a more robust and universal encoding like `utf-8` rather than `ascii`.\n- Replace synchronous sleep loops with asyncio-compatible sleep methods for better event loop performance.\n\n## Good points\n- The code is structured into clear and concise classes, facilitating object-oriented approach.\n- It uses logging effectively to capture and report errors or warnings.\n- Asynchronous handling with `asyncio` is utilized for potentially better performance and integration in async environments.\n- The code provides separate synchronous and asynchronous classes, offering flexibility depending on the use case.\n\n## Summary\nThe provided code adeptly handles DSMR telegram reading, parsing, and processing, with solid separation of concerns through its class design. However, it could be improved by addressing potential bugs concerning byte decoding, the unconventional import of `serial_asyncio_fast`, and further optimizing asynchronous serial operations. Additionally, some encoding errors might arise due to non-ASCII byte communication, highlighting the need for safer encoding practices. Finally, leveraging context managers for asynchronous operations could result in more elegant and error-free code.\n\n## Open source alternatives\n- [Pyserial](https://github.com/pyserial/pyserial): Offers both synchronous and asynchronous methods for serial communication in Python.\n- [PySerial-Asyncio](https://github.com/pyserial/pyserial-asyncio): Provides asyncio support for pyserial, suitable for async serial communication in Python environments.\n- [PyDSM](https://github.com/ndokter/pyDSM): Specifically aimed at parsing DSMR protocols, which can be used for similar functionalities.",
"filename": "serial_.py",
"path": "dsmr_parser/clients/serial_.py",
"directory": "clients",
"grade": 7,
"size": 4532,
"line_count": 129
}
+26
View File
@@ -0,0 +1,26 @@
# 7
## Bugs
- Potentially, `serial_asyncio_fast` is a non-standard library as there is no common knowledge about it, which might lead to compatibility issues or conflicts.
- The repeated use of `data.decode('ascii')` could fail if non-ASCII bytes are received, causing an exception.
- `serial_asyncio_fast.open_serial_connection` usage might be problematic if `serial_asyncio_fast` has different configurations or parameters for `open_serial_connection`.
## Optimizations
- Consider using a context manager for managing the async serial connection in `AsyncSerialReader`.
- Add error handling to manage unexpected EOF or IOError during serial reads.
- Use a more robust and universal encoding like `utf-8` rather than `ascii`.
- Replace synchronous sleep loops with asyncio-compatible sleep methods for better event loop performance.
## Good points
- The code is structured into clear and concise classes, facilitating object-oriented approach.
- It uses logging effectively to capture and report errors or warnings.
- Asynchronous handling with `asyncio` is utilized for potentially better performance and integration in async environments.
- The code provides separate synchronous and asynchronous classes, offering flexibility depending on the use case.
## Summary
The provided code adeptly handles DSMR telegram reading, parsing, and processing, with solid separation of concerns through its class design. However, it could be improved by addressing potential bugs concerning byte decoding, the unconventional import of `serial_asyncio_fast`, and further optimizing asynchronous serial operations. Additionally, some encoding errors might arise due to non-ASCII byte communication, highlighting the need for safer encoding practices. Finally, leveraging context managers for asynchronous operations could result in more elegant and error-free code.
## Open source alternatives
- [Pyserial](https://github.com/pyserial/pyserial): Offers both synchronous and asynchronous methods for serial communication in Python.
- [PySerial-Asyncio](https://github.com/pyserial/pyserial-asyncio): Provides asyncio support for pyserial, suitable for async serial communication in Python environments.
- [PyDSM](https://github.com/ndokter/pyDSM): Specifically aimed at parsing DSMR protocols, which can be used for similar functionalities.
@@ -0,0 +1,11 @@
{
"extension": ".py",
"source": "import serial\n\n\nSERIAL_SETTINGS_V2_2 = {\n 'baudrate': 9600,\n 'bytesize': serial.SEVENBITS,\n 'parity': serial.PARITY_EVEN,\n 'stopbits': serial.STOPBITS_ONE,\n 'xonxoff': 0,\n 'rtscts': 0,\n 'timeout': 20\n}\n\nSERIAL_SETTINGS_V4 = {\n 'baudrate': 115200,\n 'bytesize': serial.SEVENBITS,\n 'parity': serial.PARITY_EVEN,\n 'stopbits': serial.STOPBITS_ONE,\n 'xonxoff': 0,\n 'rtscts': 0,\n 'timeout': 20\n}\n\nSERIAL_SETTINGS_V5 = {\n 'baudrate': 115200,\n 'bytesize': serial.EIGHTBITS,\n 'parity': serial.PARITY_NONE,\n 'stopbits': serial.STOPBITS_ONE,\n 'xonxoff': 0,\n 'rtscts': 0,\n 'timeout': 20\n}\n",
"review": "# 6\n\n## Bugs\n- There are no apparent bugs in the given code snippet as it consists of properly defined dictionaries with settings.\n\n## Optimizations\n- **DRY Principle**: Consider refactoring the settings dictionaries to reduce redundancy. Common configurations could be maintained separately, with only differing settings defined in each version.\n- **Constants for Magic Numbers**: Define constants for repetitive values like the timeout and flow control settings (xonxoff, rtscts) which are consistently set to 0.\n- **Inline Comments**: Add comments explaining the context or purpose of each serial setting to improve readability.\n\n## Good points\n- **Clarity**: The code is clear and easy to understand, with settings being neatly partitioned by version.\n- **Use of Python's Serial Library Constants**: Proper use of constants from the `serial` library ensures settings are standardized and readable.\n\n## Summary\nThe code snippet sets serial connection settings for different versions, demonstrating clean and straightforward use of Python dictionaries. While there aren't any direct errors present, the design could be improved by reducing repetitive values through refactoring. Additionally, explaining the context of these configurations with comments might aid future developers in understanding the code's purpose.\n\n## Open source alternatives\n- **PySerial**: An open-source library for serial communication. The settings here could serve as configurations for creating or managing serial connections using this library.",
"filename": "settings.py",
"path": "dsmr_parser/clients/settings.py",
"directory": "clients",
"grade": 6,
"size": 639,
"line_count": 33
}
@@ -0,0 +1,19 @@
# 6
## Bugs
- There are no apparent bugs in the given code snippet as it consists of properly defined dictionaries with settings.
## Optimizations
- **DRY Principle**: Consider refactoring the settings dictionaries to reduce redundancy. Common configurations could be maintained separately, with only differing settings defined in each version.
- **Constants for Magic Numbers**: Define constants for repetitive values like the timeout and flow control settings (xonxoff, rtscts) which are consistently set to 0.
- **Inline Comments**: Add comments explaining the context or purpose of each serial setting to improve readability.
## Good points
- **Clarity**: The code is clear and easy to understand, with settings being neatly partitioned by version.
- **Use of Python's Serial Library Constants**: Proper use of constants from the `serial` library ensures settings are standardized and readable.
## Summary
The code snippet sets serial connection settings for different versions, demonstrating clean and straightforward use of Python dictionaries. While there aren't any direct errors present, the design could be improved by reducing repetitive values through refactoring. Additionally, explaining the context of these configurations with comments might aid future developers in understanding the code's purpose.
## Open source alternatives
- **PySerial**: An open-source library for serial communication. The settings here could serve as configurations for creating or managing serial connections using this library.
@@ -0,0 +1,11 @@
{
"extension": ".py",
"source": "import logging\nimport socket\n\nfrom dsmr_parser.clients.telegram_buffer import TelegramBuffer\nfrom dsmr_parser.exceptions import ParseError, InvalidChecksumError\nfrom dsmr_parser.parsers import TelegramParser\n\n\nlogger = logging.getLogger(__name__)\n\n\nclass SocketReader(object):\n\n BUFFER_SIZE = 256\n\n def __init__(self, host, port, telegram_specification):\n self.host = host\n self.port = port\n\n self.telegram_parser = TelegramParser(telegram_specification)\n self.telegram_buffer = TelegramBuffer()\n self.telegram_specification = telegram_specification\n\n def read(self):\n \"\"\"\n Read complete DSMR telegram's from remote interface and parse it\n into CosemObject's and MbusObject's\n\n :rtype: generator\n \"\"\"\n buffer = b\"\"\n\n with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as socket_handle:\n socket_handle.settimeout(60)\n socket_handle.connect((self.host, self.port))\n\n while True:\n try:\n buffer += socket_handle.recv(self.BUFFER_SIZE)\n except socket.timeout:\n logger.error(\"Socket timeout occurred, exiting\")\n break\n\n lines = buffer.splitlines(keepends=True)\n\n if len(lines) == 0:\n continue\n\n for data in lines:\n try:\n self.telegram_buffer.append(data.decode('ascii'))\n except UnicodeDecodeError:\n # Some garbage came through the channel\n # E.g.: Happens at EON_HUNGARY, but only once at the start of the socket.\n logger.error('Failed to parse telegram due to unicode decode error')\n\n for telegram in self.telegram_buffer.get_all():\n try:\n yield self.telegram_parser.parse(telegram)\n except InvalidChecksumError as e:\n logger.info(str(e))\n except ParseError as e:\n logger.error('Failed to parse telegram: %s', e)\n\n buffer = b\"\"\n\n def read_as_object(self):\n \"\"\"\n Read complete DSMR telegram's from remote and return a Telegram object.\n\n :rtype: generator\n \"\"\"\n buffer = b\"\"\n\n with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as socket_handle:\n\n socket_handle.connect((self.host, self.port))\n\n while True:\n buffer += socket_handle.recv(self.BUFFER_SIZE)\n\n lines = buffer.splitlines(keepends=True)\n\n if len(lines) == 0:\n continue\n\n for data in lines:\n self.telegram_buffer.append(data.decode('ascii'))\n\n for telegram in self.telegram_buffer.get_all():\n try:\n yield self.telegram_parser.parse(telegram)\n except InvalidChecksumError as e:\n logger.warning(str(e))\n except ParseError as e:\n logger.error('Failed to parse telegram: %s', e)\n\n buffer = b\"\"\n",
"review": "# Grade: 7\n\n## Bugs\n- The `socket.timeout` exception is handled in `read()` method but not in `read_as_object()`, leading to possible unhandled exceptions.\n- Potential UnicodeDecodeError is logged but not handled properly in the `read_as_object()` method.\n\n## Optimizations\n- Repeated code in methods `read()` and `read_as_object()` should be refactored to enhance maintainability and reduce duplication.\n- The `BUFFER_SIZE` could potentially be increased for more efficient data transfer if network constraints allow.\n- Log messages could be made more descriptive to aid debugging.\n- Add exception handling for other socket-related errors, like `socket.error`, to ensure robustness.\n- In `read_as_object`, setting a socket timeout would make the code more consistent and prevent indefinite blocking.\n\n## Good points\n- Use of `with` statement to ensure sockets are properly closed.\n- Logging is used to record errors and exceptions which helps in diagnosing issues.\n- Usage of generator functions provides memory efficiency.\n\n## Summary\nThe code is generally structured well, with proper logging mechanisms and efficient resource management using context managers. However, there are areas for improvement, particularly regarding code repetition, exception handling enhancements, and potentially optimizing socket read operations for efficiency. Refactoring the repeated code would make the codebase more maintainable and robust. Exception handling could be more comprehensive to manage all potential errors that might occur during socket communication.\n\n## Open source alternatives\n- **dsmr-reader**: A free and open source project to visually track and show all your smart meter data, directly from your own database.\n- **pysml**: A library for parsing smart meter language (SML) from electronic meters, commonly used in various energy sectors.",
"filename": "socket_.py",
"path": "dsmr_parser/clients/socket_.py",
"directory": "clients",
"grade": 7,
"size": 3265,
"line_count": 99
}
+24
View File
@@ -0,0 +1,24 @@
# Grade: 7
## Bugs
- The `socket.timeout` exception is handled in `read()` method but not in `read_as_object()`, leading to possible unhandled exceptions.
- Potential UnicodeDecodeError is logged but not handled properly in the `read_as_object()` method.
## Optimizations
- Repeated code in methods `read()` and `read_as_object()` should be refactored to enhance maintainability and reduce duplication.
- The `BUFFER_SIZE` could potentially be increased for more efficient data transfer if network constraints allow.
- Log messages could be made more descriptive to aid debugging.
- Add exception handling for other socket-related errors, like `socket.error`, to ensure robustness.
- In `read_as_object`, setting a socket timeout would make the code more consistent and prevent indefinite blocking.
## Good points
- Use of `with` statement to ensure sockets are properly closed.
- Logging is used to record errors and exceptions which helps in diagnosing issues.
- Usage of generator functions provides memory efficiency.
## Summary
The code is generally structured well, with proper logging mechanisms and efficient resource management using context managers. However, there are areas for improvement, particularly regarding code repetition, exception handling enhancements, and potentially optimizing socket read operations for efficiency. Refactoring the repeated code would make the codebase more maintainable and robust. Exception handling could be more comprehensive to manage all potential errors that might occur during socket communication.
## Open source alternatives
- **dsmr-reader**: A free and open source project to visually track and show all your smart meter data, directly from your own database.
- **pysml**: A library for parsing smart meter language (SML) from electronic meters, commonly used in various energy sectors.
@@ -0,0 +1,11 @@
{
"extension": ".py",
"source": "import re\n\n# - Match all characters after start of telegram except for the start\n# itself again '^\\/]+', which eliminates incomplete preceding telegrams.\n# - Do non greedy match using '?' so start is matched up to the first\n# checksum that's found.\n# - The checksum is optional '{0,4}' because not all telegram versions\n# support it.\n_FIND_TELEGRAMS_REGEX = re.compile(r\"\\/[^\\/]+?\\![A-F0-9]{0,4}\\0?\\r\\n\", re.DOTALL)\n\n\nclass TelegramBuffer(object):\n \"\"\"\n Used as a buffer for a stream of telegram data. Constructs full telegram\n strings from the buffered data and returns it.\n \"\"\"\n\n def __init__(self):\n self._buffer = \"\"\n\n def get_all(self):\n \"\"\"\n Remove complete telegrams from buffer and yield them.\n :rtype generator:\n \"\"\"\n for telegram in _FIND_TELEGRAMS_REGEX.findall(self._buffer):\n self._remove(telegram)\n yield telegram\n\n def append(self, data):\n \"\"\"\n Add telegram data to buffer.\n :param str data: chars, lines or full telegram strings of telegram data\n \"\"\"\n self._buffer += data\n\n def _remove(self, telegram):\n \"\"\"\n Remove telegram from buffer and incomplete data preceding it. This\n is easier than validating the data before adding it to the buffer.\n :param str telegram:\n :return:\n \"\"\"\n # Remove data leading up to the telegram and the telegram itself.\n index = self._buffer.index(telegram) + len(telegram)\n\n self._buffer = self._buffer[index:]\n",
"review": "6\n\n### Bugs\n- The regular expression may miss some valid telegrams or mistakenly identify invalid ones due to the pattern being overly broad.\n- Using the `index()` method without precaution could throw a `ValueError` if the telegram is not present in the buffer, though this is unlikely due to the design.\n- It seems the `\\0` in the regex has no apparent purpose as a null character isn't commonly part of telegram line endings.\n\n### Optimizations\n- Could utilize `deque` for efficient popping from the left.\n- Use `str.startswith()` when matching telegram headers for checks instead of regex when modifications are needed.\n- Consider more detailed regex to prevent capturing unwanted characters or malformed strings if particular telegram structure is known.\n- Implement error handling to make the code more robust especially around index checks and regex matching.\n- Use more descriptive comments or docstrings to enhance understanding, especially on the `append` method.\n\n### Good points\n- The code is well-structured and follows an object-oriented design.\n- Use of regular expressions to identify specific data patterns is efficient for parsing logs or streams.\n- Buffers telegram messages correctly to separate complete telegrams from incomplete input.\n\n### Summary\nThe code displays a good initial implementation of a buffer for telegram message strings. It is efficient in its approach using regex to parse and dissect data. However, robustness can be improved with better error handling and more optimized data structures. There may also be room for refinement in the regex to avoid potential edge cases which could lead to runtime errors.\n\n### Open source alternatives\n- **mqtt**: An open-source implementation for MQTT protocol, useful for messaging applications.\n- **Telethon**: A Python library for interacting with Telegram's API directly, beneficial for secure telegram processing.",
"filename": "telegram_buffer.py",
"path": "dsmr_parser/clients/telegram_buffer.py",
"directory": "clients",
"grade": 6,
"size": 1543,
"line_count": 48
}
@@ -0,0 +1,25 @@
6
### Bugs
- The regular expression may miss some valid telegrams or mistakenly identify invalid ones due to the pattern being overly broad.
- Using the `index()` method without precaution could throw a `ValueError` if the telegram is not present in the buffer, though this is unlikely due to the design.
- It seems the `\0` in the regex has no apparent purpose as a null character isn't commonly part of telegram line endings.
### Optimizations
- Could utilize `deque` for efficient popping from the left.
- Use `str.startswith()` when matching telegram headers for checks instead of regex when modifications are needed.
- Consider more detailed regex to prevent capturing unwanted characters or malformed strings if particular telegram structure is known.
- Implement error handling to make the code more robust especially around index checks and regex matching.
- Use more descriptive comments or docstrings to enhance understanding, especially on the `append` method.
### Good points
- The code is well-structured and follows an object-oriented design.
- Use of regular expressions to identify specific data patterns is efficient for parsing logs or streams.
- Buffers telegram messages correctly to separate complete telegrams from incomplete input.
### Summary
The code displays a good initial implementation of a buffer for telegram message strings. It is efficient in its approach using regex to parse and dissect data. However, robustness can be improved with better error handling and more optimized data structures. There may also be room for refinement in the regex to avoid potential edge cases which could lead to runtime errors.
### Open source alternatives
- **mqtt**: An open-source implementation for MQTT protocol, useful for messaging applications.
- **Telethon**: A Python library for interacting with Telegram's API directly, beneficial for secure telegram processing.
+11
View File
@@ -0,0 +1,11 @@
{
"extension": ".py",
"source": "class ParseError(Exception):\n pass\n\n\nclass InvalidChecksumError(ParseError):\n pass\n",
"review": "5\n\n### Bugs\n- No explicit code functionality is given, so no bugs are currently identifiable.\n- The classes do not include any methods, attributes, or detailed documentation.\n\n### Optimizations\n- Add docstrings to explain the purpose of each class and how they should be used.\n- Implement methods or attributes, if applicable, to provide functionality related to parsing and checksum validation.\n- Consider leveraging built-in exception-handling features in Python to reduce code maintenance.\n\n### Good points\n- The use of custom exception classes (`ParseError` and `InvalidChecksumError`) is a good practice for handling specific errors.\n- Both classes inherit from Python's `Exception` class, which is a correct use of inheritance for error handling.\n \n### Summary\nThe code defines two custom exception classes for handling parse errors and invalid checksum errors, but there is no additional functionality provided. It serves as a basic template for extending error handling in a larger application. To improve it, consider expanding on its functionality and documentation for clarity and usability.\n\n### Open source alternatives\n- [pyparsing](https://github.com/pyparsing/pyparsing): A general parsing module for creating and executing parsers.\n- [lxml](https://github.com/lxml/lxml): Useful for XML parsing which inherently deals with checksum and parse validation.",
"filename": "exceptions.py",
"path": "dsmr_parser/exceptions.py",
"directory": "dsmr_parser",
"grade": 5,
"size": 89,
"line_count": 7
}
+21
View File
@@ -0,0 +1,21 @@
5
### Bugs
- No explicit code functionality is given, so no bugs are currently identifiable.
- The classes do not include any methods, attributes, or detailed documentation.
### Optimizations
- Add docstrings to explain the purpose of each class and how they should be used.
- Implement methods or attributes, if applicable, to provide functionality related to parsing and checksum validation.
- Consider leveraging built-in exception-handling features in Python to reduce code maintenance.
### Good points
- The use of custom exception classes (`ParseError` and `InvalidChecksumError`) is a good practice for handling specific errors.
- Both classes inherit from Python's `Exception` class, which is a correct use of inheritance for error handling.
### Summary
The code defines two custom exception classes for handling parse errors and invalid checksum errors, but there is no additional functionality provided. It serves as a basic template for extending error handling in a larger application. To improve it, consider expanding on its functionality and documentation for clarity and usability.
### Open source alternatives
- [pyparsing](https://github.com/pyparsing/pyparsing): A general parsing module for creating and executing parsers.
- [lxml](https://github.com/lxml/lxml): Useful for XML parsing which inherently deals with checksum and parse validation.
File diff suppressed because one or more lines are too long
+21
View File
@@ -0,0 +1,21 @@
# 8
## Bugs
- No major bugs detected as the code mainly consists of constant definitions for regex patterns, serving as configuration or supportive code.
## Optimizations
- Consider organizing constants into categories or groups for better readability and maintainability, possibly grouping them into dictionaries or classes.
- Utilize comments more effectively to clarify complex regex patterns.
- If some of these regex patterns are used frequently in different parts of the program, encapsulate them in a function or class to avoid redundant code and improve manageability.
## Good Points
- Clarity in the structure of the patterns with consistent naming conventions for variables, making it easier to understand the purpose of each pattern.
- Well-commented sections that provide context and guide the reader about potential refactoring, specifically for future enhancements or changes.
## Summary
The provided code demonstrates consistent use of descriptive variable names in defining regex patterns, primarily purposed for parsing telegram messages related to power and gas metering. While generally clean and organized, it can be further optimized by categorizing related patterns. This will enhance maintainability, particularly when the codebase scales or undergoes changes. Overall, it's a solid foundation for handling meter data but would benefit from thoughtful refactoring for broader usage beyond configuration purposes.
## Open source alternatives
- **Open Energy Monitoring**: A suite of open-source tools for energy monitoring which might include similar functionalities.
- **GridLAB-D**: An open-source simulation and analysis tool that might handle similar data parsing for utility data.
- **Home Assistant**: An open-source platform that supports home automation, which could work with similar data patterns.
File diff suppressed because one or more lines are too long
+29
View File
@@ -0,0 +1,29 @@
# 6
## Bugs
- Potentially unsafe handling of JSON serialization, especially if any of the objects have complex types.
- The handling of `Decimal` to `float` conversion within JSON methods may lead to precision loss.
- Missed implementation of the `to_json` method in `DSMRObject`, which is raising `NotImplementedError`.
- Direct manipulation of timezone might be incorrect depending on the input timezone given that the offsets are called twice consecutively without any reason.
## Optimizations
- Instead of hard-coded indexes for accessing values, consider more dynamic or descriptive access methods.
- Handling of strings within `__str__` methods can be optimized via `str.join` rather than string concatenation.
- Utilize more efficient data structures or iterators for attribute storage and retrieval, such as collections.OrderedDict.
- Error handling around timezone conversions and JSON manipulations should be improved.
- Introduce lazy loading for buffer processing in `ProfileGenericObject` with property decorators.
- The structure could be modularized further by separating responsibilities into different modules/classes.
## Good points
- Use of class-based design organizes functionality by logical grouping.
- The transition from backward-compatible to new code structure appears to be documented and considered.
- Proper use of Python's `property` decorators for value encapsulation.
- Inclusion of detailed object representations via `__str__`.
## Summary
The code is a well-designed hierarchical structure for processing and managing DSMR telegram data. It makes good use of object-oriented principles and provides a clear interface for interacting with telegram attributes. However, a few improvements can be made, particularly regarding handling serialization, hard-coded indices and ensuring robust timezone handling. Additionally, optimization around inefficiencies in code execution, such as using more appropriate data structures or methods, would enhance performance.
## Open source alternatives
- **DSMR-Reader**: An open-source application for reading DSMR telegrams with more features, including more detailed data processing and UI components.
- **Home Assistant DSMR Slimme Meter**: Integrates with Home Assistant to visualize and log energy consumption.
- **Open Energy Monitor**: For broader energy monitoring solutions that can integrate with DSMR readers for analytics and monitoring.
File diff suppressed because one or more lines are too long
+24
View File
@@ -0,0 +1,24 @@
# 7
## Bugs
- The `crc16_tab` is mutable but populated only once, thus it is vulnerable to potential side-effects or race conditions if accessed in a multi-threaded context.
- In the `ProfileGenericParser`, the logic assumes `values` length is correctly calculated; however, inadequate handling when regex fails to capture suitable groups is present.
- The exception handling for hex operations could provide more specific cases rather than catching all Exceptions.
## Optimizations
- Preloading and reusing the compiled regex patterns across other parsing methods could improve performance and consistency.
- The `crc16` method tabulation could be optimized or replaced with an alternative optimized library for CRC calculation.
- Repeated warning checks for unhandled `security_suite` cases could be refactored into a helper method for better code reuse and maintainability.
- Consider using a more modern Python class inheritance structure by extending directly from `object`.
## Good Points
- The use of logging to aid error-tracking and debugging.
- Modular approach with multiple parsing classes, each with a specific responsibility.
## Summary
The overall design of the code is modular, which is advantageous for maintainability. However, there are areas where optimizations, especially related to performance and security handling, could be implemented. Bugs are mostly related to error handling and potential issues when operating in a multi-threaded environment. Refactoring for more modern Python practices could enhance code readability and performance.
## Open source alternatives
- **PyCRC**: It offers common CRC algorithms like CRC16, which can be used instead of manually performing the CRC calculations.
- **OpenDSS**: Data parsing and manipulation for smart grids and DSMR-related purposes.
- **pyOBIS**: A library specifically for decoding OBIS (Object Identification System) codes.
@@ -0,0 +1,11 @@
{
"extension": ".py",
"source": "from dsmr_parser.parsers import ValueParser\nfrom dsmr_parser.value_types import timestamp\n\nPG_FAILURE_EVENT = r'0-0:96.7.19'\n\nPG_HEAD_PARSERS = [ValueParser(int), ValueParser(str)]\nPG_UNIDENTIFIED_BUFFERTYPE_PARSERS = [ValueParser(str), ValueParser(str)]\nBUFFER_TYPES = {\n PG_FAILURE_EVENT: [ValueParser(timestamp), ValueParser(int)]\n}\n",
"review": "**7**\n\n### Bugs\n- No known bugs are evident from the code snippet.\n\n### Optimizations\n- Consider using a more descriptive variable name than `PG_HEAD_PARSERS` and `PG_UNIDENTIFIED_BUFFERTYPE_PARSERS` to make the code self-documenting.\n- If `timestamp` is a function or a complex class, ensure it is being used correctly with `ValueParser`.\n\n### Good points\n- The use of regular expressions like `r'0-0:96.7.19'` for identifying buffer types appears effective.\n- The code is structured neatly with clear separation of constants and mapping.\n\n### Summary\nThe code defines a set of value parsers and maps certain parser configurations to specific string identifiers. It appears well-organized and straightforward. Naming conventions could be improved to increase code maintainability and readability. No immediate bugs are apparent, but some minor enhancements could further polish the code.\n\n### Open source alternatives\n- **pySerial**: A library for serial communication in Python, which can also interpret specific data structures.\n- **DSMR-reader**: An open-source project that reads and interprets smart meter data, similar to what `dsmr_parser` aims to achieve.",
"filename": "profile_generic_specifications.py",
"path": "dsmr_parser/profile_generic_specifications.py",
"directory": "dsmr_parser",
"grade": 7,
"size": 340,
"line_count": 11
}
@@ -0,0 +1,19 @@
**7**
### Bugs
- No known bugs are evident from the code snippet.
### Optimizations
- Consider using a more descriptive variable name than `PG_HEAD_PARSERS` and `PG_UNIDENTIFIED_BUFFERTYPE_PARSERS` to make the code self-documenting.
- If `timestamp` is a function or a complex class, ensure it is being used correctly with `ValueParser`.
### Good points
- The use of regular expressions like `r'0-0:96.7.19'` for identifying buffer types appears effective.
- The code is structured neatly with clear separation of constants and mapping.
### Summary
The code defines a set of value parsers and maps certain parser configurations to specific string identifiers. It appears well-organized and straightforward. Naming conventions could be improved to increase code maintainability and readability. No immediate bugs are apparent, but some minor enhancements could further polish the code.
### Open source alternatives
- **pySerial**: A library for serial communication in Python, which can also interpret specific data structures.
- **DSMR-reader**: An open-source project that reads and interprets smart meter data, similar to what `dsmr_parser` aims to achieve.
File diff suppressed because one or more lines are too long
@@ -0,0 +1,25 @@
# 8
## Bugs
- Inconsistent trailing commas within list and dictionary entries.
- Typo in `'ACTUAL_TRESHOLD_ELECTRICITY'`. The correct spelling should be `'THRESHOLD'` instead of `'TRESHOLD'`.
- Redundant definition of `V3` as `V2_2` without any modifications.
- Obsolete references or undefined elements in some comments, e.g., `NonExistingParser`.
## Optimizations
- Remove the redundant assignment of `V3 = V2_2` as they are identical.
- Use a helper function to reduce redundancy when defining similar dictionary objects for different versions.
- Consolidate or document repetitive structures/options like `checksum_support` which appear across different dictionaries with explanations or use variables for descriptions.
- Improve comments for clarity, specifying which parts are unique to certain regions or versions.
## Good points
- The code is well-structured, clearly defining DSMR specifications and neatly organizing protocol versions.
- Use of descriptive names for the objects and dictionaries adds to code readability.
- The utilization of specific parser classes demonstrates good understanding and application of object-oriented principles.
## Summary
The source code provided defines DSMR telegram specifications using structured dictionaries to map out OBIS references with appropriate parsers and value names. Although generally well-organized, the code contains some redundancies and minor typos that could be refined. The parser mappings are comprehensive and structured clearly, with potential optimizations in eliminating unnecessary repetition. Ensuring consistent syntax and improving documentation would further enhance the code's quality.
## Open source alternatives
- **dsmr_parser**: The library this code corresponds to is itself an open-source solution for parsing DSMR telegrams.
- **Home Assistant DSMR Reader**: An integration that can read DSMR telegrams and provide a user-friendly interface for monitoring energy data.
+11
View File
@@ -0,0 +1,11 @@
{
"extension": ".py",
"source": "import datetime\n\nimport pytz\n\n# TODO : Use system timezone\n# Preload timezone to avoid loading in event loop later\nlocal_tz = pytz.timezone('Europe/Amsterdam')\n\n\ndef timestamp(value):\n try:\n naive_datetime = datetime.datetime.strptime(value[:-1], '%y%m%d%H%M%S')\n except ValueError:\n return None\n\n # Timestamp has the following format:\n # YYMMDDhhmmssX\n # ASCII presentation of Time stamp with\n # Year, Month, Day, Hour, Minute, Second,\n # and an indication whether DST is active\n # (X=S) or DST is not active (X=W)\n if len(value) == 13:\n is_dst = value[12] == 'S' # assume format 160322150000W\n else:\n is_dst = False\n\n localized_datetime = local_tz.localize(naive_datetime, is_dst=is_dst)\n\n return localized_datetime.astimezone(pytz.utc)\n",
"review": "# 6\n\n## Bugs\n- The function might incorrectly handle timestamps if another timezone is used without modification.\n- Hardcoded to 'Europe/Amsterdam' timezone, disregards the system timezone or caller's needs.\n\n## Optimizations\n- Allow dynamic use of system or parameter-specified timezone instead of hardcoding 'Europe/Amsterdam'.\n- Handle invalid timestamp length gracefully, ensuring function does not break with unexpected inputs.\n- Consider more efficient error handling mechanisms, like logging, apart from returning None.\n- Reuse the `localize` function with updated methods from `pytz` if they become deprecated.\n\n## Good Points\n- Utilizes `pytz` for timezone management, which is a common library for such tasks.\n- Includes detailed comments explaining the timestamp format and handling.\n- The function handles DST (Daylight Saving Time) correctly based on the input.\n\n## Summary\nThe code is functional but limited due to its hardcoded timezone and lack of flexibility. While the approach to time conversion and DST handling is correct, there's room for improvement in adding robustness and versatility to the function. Allowing specification of the timezone, and handling errors more comprehensively, can enhance its utility and resilience.\n\n## Open source alternatives\n- **dateutil module**: Offers timezone and time conversion functions with more flexibility and less dependency on hardcoded values. \n- **Arrow library**: Provides better UTC and timezone management and human-friendly datetime manipulation.",
"filename": "value_types.py",
"path": "dsmr_parser/value_types.py",
"directory": "dsmr_parser",
"grade": 6,
"size": 804,
"line_count": 30
}
+23
View File
@@ -0,0 +1,23 @@
# 6
## Bugs
- The function might incorrectly handle timestamps if another timezone is used without modification.
- Hardcoded to 'Europe/Amsterdam' timezone, disregards the system timezone or caller's needs.
## Optimizations
- Allow dynamic use of system or parameter-specified timezone instead of hardcoding 'Europe/Amsterdam'.
- Handle invalid timestamp length gracefully, ensuring function does not break with unexpected inputs.
- Consider more efficient error handling mechanisms, like logging, apart from returning None.
- Reuse the `localize` function with updated methods from `pytz` if they become deprecated.
## Good Points
- Utilizes `pytz` for timezone management, which is a common library for such tasks.
- Includes detailed comments explaining the timestamp format and handling.
- The function handles DST (Daylight Saving Time) correctly based on the input.
## Summary
The code is functional but limited due to its hardcoded timezone and lack of flexibility. While the approach to time conversion and DST handling is correct, there's room for improvement in adding robustness and versatility to the function. Allowing specification of the timezone, and handling errors more comprehensively, can enhance its utility and resilience.
## Open source alternatives
- **dateutil module**: Offers timezone and time conversion functions with more flexibility and less dependency on hardcoded values.
- **Arrow library**: Provides better UTC and timezone management and human-friendly datetime manipulation.
+11
View File
@@ -0,0 +1,11 @@
{
"extension": ".py",
"source": "from setuptools import setup, find_packages\n\nsetup(\n name='dsmr-parser',\n description='Library to parse Dutch Smart Meter Requirements (DSMR)',\n author='Nigel Dokter and many others',\n author_email='mail@nldr.net',\n license='MIT',\n url='https://github.com/ndokter/dsmr_parser',\n version='1.4.2',\n packages=find_packages(exclude=('test', 'test.*')),\n install_requires=[\n 'pyserial>=3,<4',\n 'pyserial-asyncio-fast>=0.11',\n 'pytz',\n 'Tailer==0.4.1',\n 'dlms_cosem==21.3.2'\n ],\n entry_points={\n 'console_scripts': ['dsmr_console=dsmr_parser.__main__:console']\n },\n)\n",
"review": "# 8\n\n## Bugs\n- No apparent bugs in the setup configuration.\n\n## Optimizations\n- Ensure that the listed dependencies are up to date and compatible with the rest of your project. Dependency constraints like `'pyserial>=3,<4'` can limit updates, missing out on potential bug fixes or improvements present in newer versions.\n- Consider using a `requirements.txt` file for better dependency handling and management or allowing pip to compile accurate dependency trees.\n- Use classifiers in the setup call to provide more metadata about the package (e.g., specifying the Python version compatibility, stable/dev status, etc.).\n\n## Good points\n- Uses `find_packages(exclude=...)`, which is efficient for including all relevant packages without needing to list each explicitly.\n- Clearly defined `entry_points` for console scripts make it easy to use the package's functionalities from the command line.\n- Comprehensive use of semantic versioning for package versioning.\n\n## Summary\nThis setup script is well-structured and adheres to standard practices found in Python packaging using `setuptools`. The use of `find_packages` and entry points are particularly well-done, offering both flexibility and ease of use. There\u2019s room for improvement regarding dependency management and metadata enhancement (such as classifiers for greater clarity).\n\n## Open source alternatives\n- **dsmr-reader**: A popular project for reading and logging data from smart meters which also has parsing capabilities.\n- **home-assistant.io**: Although primarily a home automation platform, it has integrations and parsers for DSMR data.",
"filename": "setup.py",
"path": "setup.py",
"directory": "",
"grade": 8,
"size": 639,
"line_count": 23
}
+21
View File
@@ -0,0 +1,21 @@
# 8
## Bugs
- No apparent bugs in the setup configuration.
## Optimizations
- Ensure that the listed dependencies are up to date and compatible with the rest of your project. Dependency constraints like `'pyserial>=3,<4'` can limit updates, missing out on potential bug fixes or improvements present in newer versions.
- Consider using a `requirements.txt` file for better dependency handling and management or allowing pip to compile accurate dependency trees.
- Use classifiers in the setup call to provide more metadata about the package (e.g., specifying the Python version compatibility, stable/dev status, etc.).
## Good points
- Uses `find_packages(exclude=...)`, which is efficient for including all relevant packages without needing to list each explicitly.
- Clearly defined `entry_points` for console scripts make it easy to use the package's functionalities from the command line.
- Comprehensive use of semantic versioning for package versioning.
## Summary
This setup script is well-structured and adheres to standard practices found in Python packaging using `setuptools`. The use of `find_packages` and entry points are particularly well-done, offering both flexibility and ease of use. There’s room for improvement regarding dependency management and metadata enhancement (such as classifiers for greater clarity).
## Open source alternatives
- **dsmr-reader**: A popular project for reading and logging data from smart meters which also has parsing capabilities.
- **home-assistant.io**: Although primarily a home automation platform, it has integrations and parsers for DSMR data.
File diff suppressed because one or more lines are too long
+21
View File
@@ -0,0 +1,21 @@
**Grade: 5**
### Bugs
- None observed, but there may be potential for validation issues since there isn't any parsing or validation logic provided here.
### Optimizations
- Consider encapsulating telegrams in a data structure or class to provide methods for parsing and validation.
- Add comments to explain the purpose of each telegram or important sections for better readability.
- Use a consistent naming convention for variables to improve maintainability.
### Good points
- Data is well-organized in distinct variables for each telegram type.
- Usage of constant variables to hold telegrams, which promotes immutability and safer code practices.
### Summary
The given code contains multiple datasets representing telegrams for different versions and types of smart meter readings. While the structure is clear and data is organized, the absence of any processing, parsing, or validation function limits its current utility in application beyond storage. Improvements can be made by providing encapsulation, adding functions for parsing telegrams, and ensuring consistency in naming and styling practices. Overall, the code serves as a baseline data source but lacks interactive features or error handling that might be necessary for real-world applications.
### Open source alternatives
- **Open Energy Monitor**: Provides tools and platforms to monitor and analyze electricity usage.
- **Home Assistant**: A widely used open-source home automation platform that can integrate energy monitoring.
- **OpenEMS**: Provides a framework for energy management systems, including support for various protocols and integrations.
+11
View File
@@ -0,0 +1,11 @@
{
"extension": ".py",
"source": "from dsmr_parser import telegram_specifications\nfrom dsmr_parser.parsers import TelegramParser\nfrom test.example_telegrams import TELEGRAM_V4_2\nparser = TelegramParser(telegram_specifications.V4)\ntelegram = parser.parse(TELEGRAM_V4_2)\n\nprint(telegram)\n",
"review": "8\n\n### Bugs\n- No apparent bugs in the code snippet given the current context.\n\n### Optimizations\n- Add error handling for cases where parsing might fail.\n- Consider logging for troubleshooting, especially for parsing operations.\n- If the `TELEGRAM_V4_2` data might be large, consider using a more efficient storage or processing method to handle the telegram data.\n\n### Good points\n- The code is clear and concise, directly implementing telegram parsing in a few lines.\n- Utilizes external library `dsmr_parser` effectively for parsing DSMR telegrams.\n\n### Summary\nThe provided Python code is a straightforward and effective example of how to parse DSMR telegrams using the `dsmr_parser` library. It is well-structured and uses the functionalities of the `dsmr_parser` library appropriately. Minor improvements could be made in terms of error handling and logging enhancements.\n\n### Open source alternatives\n- There are no direct alternatives to `dsmr_parser` within the specific niche of DSMR telegram parsing, but `pyserial` could be used if implementing lower-level serial communication and parsing logic manually for DSMR devices.",
"filename": "experiment_telegram.py",
"path": "test/experiment_telegram.py",
"directory": "test",
"grade": 8,
"size": 252,
"line_count": 8
}
+19
View File
@@ -0,0 +1,19 @@
8
### Bugs
- No apparent bugs in the code snippet given the current context.
### Optimizations
- Add error handling for cases where parsing might fail.
- Consider logging for troubleshooting, especially for parsing operations.
- If the `TELEGRAM_V4_2` data might be large, consider using a more efficient storage or processing method to handle the telegram data.
### Good points
- The code is clear and concise, directly implementing telegram parsing in a few lines.
- Utilizes external library `dsmr_parser` effectively for parsing DSMR telegrams.
### Summary
The provided Python code is a straightforward and effective example of how to parse DSMR telegrams using the `dsmr_parser` library. It is well-structured and uses the functionalities of the `dsmr_parser` library appropriately. Minor improvements could be made in terms of error handling and logging enhancements.
### Open source alternatives
- There are no direct alternatives to `dsmr_parser` within the specific niche of DSMR telegram parsing, but `pyserial` could be used if implementing lower-level serial communication and parsing logic manually for DSMR devices.
@@ -0,0 +1,11 @@
{
"extension": ".py",
"source": "from decimal import Decimal\n\nimport json\nimport unittest\n\nfrom dsmr_parser import telegram_specifications, obis_references\nfrom dsmr_parser.objects import MbusDevice\n\n\nclass MbusDeviceTest(unittest.TestCase):\n\n def setUp(self):\n v5_objects = telegram_specifications.V5['objects']\n\n device_type_parser = [\n object[\"value_parser\"]\n for object in v5_objects\n if object[\"obis_reference\"] == obis_references.MBUS_DEVICE_TYPE\n ][0]\n device_type = device_type_parser.parse('0-2:24.1.0(003)\\r\\n')\n\n equipment_parser = [\n object[\"value_parser\"]\n for object in v5_objects\n if object[\"obis_reference\"] == obis_references.MBUS_EQUIPMENT_IDENTIFIER\n ][0]\n equipment = equipment_parser.parse('0-2:96.1.0(4730303339303031393336393930363139)\\r\\n')\n\n gas_reading_parser = [\n object[\"value_parser\"]\n for object in v5_objects\n if object[\"obis_reference\"] == obis_references.MBUS_METER_READING\n ][0]\n gas_reading = gas_reading_parser.parse('0-2:24.2.1(200426223001S)(00246.138*m3)\\r\\n')\n\n mbus_device = MbusDevice(channel_id=2)\n mbus_device.add(obis_references.MBUS_DEVICE_TYPE, device_type, \"MBUS_DEVICE_TYPE\")\n mbus_device.add(obis_references.MBUS_EQUIPMENT_IDENTIFIER, equipment, \"MBUS_EQUIPMENT_IDENTIFIER\")\n mbus_device.add(obis_references.MBUS_METER_READING, gas_reading, \"MBUS_METER_READING\")\n\n self.mbus_device = mbus_device\n\n def test_attributes(self):\n self.assertEqual(self.mbus_device.MBUS_DEVICE_TYPE.value, 3)\n self.assertEqual(self.mbus_device.MBUS_DEVICE_TYPE.unit, None)\n\n self.assertEqual(self.mbus_device.MBUS_EQUIPMENT_IDENTIFIER.value,\n '4730303339303031393336393930363139')\n self.assertEqual(self.mbus_device.MBUS_EQUIPMENT_IDENTIFIER.unit, None)\n\n self.assertEqual(self.mbus_device.MBUS_METER_READING.value, Decimal('246.138'))\n self.assertEqual(self.mbus_device.MBUS_METER_READING.unit, 'm3')\n\n def test_to_json(self):\n self.assertEqual(\n json.loads(self.mbus_device.to_json()),\n {\n 'CHANNEL_ID': 2,\n 'MBUS_DEVICE_TYPE': {'value': 3, 'unit': None},\n 'MBUS_EQUIPMENT_IDENTIFIER': {'value': '4730303339303031393336393930363139', 'unit': None},\n 'MBUS_METER_READING': {'datetime': '2020-04-26T20:30:01+00:00', 'value': 246.138, 'unit': 'm3'}}\n )\n\n def test_str(self):\n self.assertEqual(\n str(self.mbus_device),\n (\n 'MBUS DEVICE (channel 2)\\n'\n '\\tMBUS_DEVICE_TYPE: \t 3\t[None]\\n'\n '\\tMBUS_EQUIPMENT_IDENTIFIER: \t 4730303339303031393336393930363139\t[None]\\n'\n '\\tMBUS_METER_READING: \t 246.138\t[m3] at 2020-04-26T20:30:01+00:00\\n'\n )\n )\n",
"review": "# 8\n\n## Bugs\n- No explicit bug was detected in this segment of the code.\n\n## Optimizations\n- Include error handling for parsing functions to manage unexpected inputs more gracefully.\n- Consider using constants or an enum for repeated string literals to improve maintainability.\n- The parsing of date from the MBUS_METER_READING should ensure timezone consistency.\n\n## Good points\n- Makes effective use of unit tests to validate the functionality of the `MbusDevice` class.\n- Uses `unittest` which is a standard Python module for testing, showing good practice.\n- Demonstrates clarity in structuring tests into distinct methods.\n- Code adheres to PEP 8 style guidelines making it readable.\n\n## Summary\nThe code presents a unit test suite for an `MbusDevice` class, verifying its parsing and JSON serialization functionalities. The tests are well-organized, covering attributes and different string representations of the device. While the code is generally well-written and free from apparent bugs, improvements can be made in terms of error handling and simplifying repetitive structures.\n\n## Open source alternatives\n- [Home Assistant](https://www.home-assistant.io/): An open-source platform that can receive and decode DSMR data from smart meters.\n- [DSMR Reader](https://github.com/dsmrreader/dsmr-reader): An open-source application for monitoring and visualizing Dutch Smart Meter data.",
"filename": "test_mbusdevice.py",
"path": "test/objects/test_mbusdevice.py",
"directory": "objects",
"grade": 8,
"size": 2920,
"line_count": 74
}
@@ -0,0 +1,22 @@
# 8
## Bugs
- No explicit bug was detected in this segment of the code.
## Optimizations
- Include error handling for parsing functions to manage unexpected inputs more gracefully.
- Consider using constants or an enum for repeated string literals to improve maintainability.
- The parsing of date from the MBUS_METER_READING should ensure timezone consistency.
## Good points
- Makes effective use of unit tests to validate the functionality of the `MbusDevice` class.
- Uses `unittest` which is a standard Python module for testing, showing good practice.
- Demonstrates clarity in structuring tests into distinct methods.
- Code adheres to PEP 8 style guidelines making it readable.
## Summary
The code presents a unit test suite for an `MbusDevice` class, verifying its parsing and JSON serialization functionalities. The tests are well-organized, covering attributes and different string representations of the device. While the code is generally well-written and free from apparent bugs, improvements can be made in terms of error handling and simplifying repetitive structures.
## Open source alternatives
- [Home Assistant](https://www.home-assistant.io/): An open-source platform that can receive and decode DSMR data from smart meters.
- [DSMR Reader](https://github.com/dsmrreader/dsmr-reader): An open-source application for monitoring and visualizing Dutch Smart Meter data.
@@ -0,0 +1,11 @@
{
"extension": ".py",
"source": "import unittest\n\nfrom dsmr_parser import telegram_specifications\n\nfrom dsmr_parser.objects import ProfileGenericObject\nfrom dsmr_parser.parsers import TelegramParser\nfrom dsmr_parser.parsers import ProfileGenericParser\nfrom dsmr_parser.profile_generic_specifications import BUFFER_TYPES\nfrom dsmr_parser.profile_generic_specifications import PG_HEAD_PARSERS\nfrom dsmr_parser.profile_generic_specifications import PG_UNIDENTIFIED_BUFFERTYPE_PARSERS\nfrom test.example_telegrams import TELEGRAM_V5\n\n\nclass TestParserCornerCases(unittest.TestCase):\n \"\"\" Test instantiation of Telegram object \"\"\"\n\n def test_power_event_log_empty_1(self):\n # POWER_EVENT_FAILURE_LOG (1-0:99.97.0)\n parser = TelegramParser(telegram_specifications.V5)\n telegram = parser.parse(TELEGRAM_V5)\n\n object_type = ProfileGenericObject\n testitem = telegram.POWER_EVENT_FAILURE_LOG\n assert isinstance(testitem, object_type)\n assert testitem.buffer_length == 0\n assert testitem.buffer_type == '0-0:96.7.19'\n buffer = testitem.buffer\n assert isinstance(testitem.buffer, list)\n assert len(buffer) == 0\n\n def test_power_event_log_empty_2(self):\n pef_parser = ProfileGenericParser(BUFFER_TYPES, PG_HEAD_PARSERS, PG_UNIDENTIFIED_BUFFERTYPE_PARSERS)\n object_type = ProfileGenericObject\n\n # Power Event Log with 0 items and no object type\n pefl_line = r'1-0:99.97.0(0)()\\r\\n'\n testitem = pef_parser.parse(pefl_line)\n\n assert isinstance(testitem, object_type)\n assert testitem.buffer_length == 0\n assert testitem.buffer_type is None\n buffer = testitem.buffer\n assert isinstance(testitem.buffer, list)\n assert len(buffer) == 0\n assert testitem.values == [{'value': 0, 'unit': None}, {'value': None, 'unit': None}]\n json = testitem.to_json()\n assert json == '{\"buffer_length\": 0, \"buffer_type\": null, \"buffer\": []}'\n\n def test_power_event_log_null_values(self):\n pef_parser = ProfileGenericParser(BUFFER_TYPES, PG_HEAD_PARSERS, PG_UNIDENTIFIED_BUFFERTYPE_PARSERS)\n object_type = ProfileGenericObject\n\n # Power Event Log with 1 item and no object type and nno values for the item\n pefl_line = r'1-0:99.97.0(1)()()()\\r\\n'\n testitem = pef_parser.parse(pefl_line)\n\n assert isinstance(testitem, object_type)\n assert testitem.buffer_length == 1\n assert testitem.buffer_type is None\n buffer = testitem.buffer\n assert isinstance(testitem.buffer, list)\n assert len(buffer) == 1\n assert testitem.values == [{'value': 1, 'unit': None}, {'value': None, 'unit': None},\n {'value': None, 'unit': None}, {'value': None, 'unit': None}]\n json = testitem.to_json()\n assert json == \\\n '{\"buffer_length\": 1, \"buffer_type\": null, \"buffer\": [{\"datetime\": null, \"value\": null, \"unit\": null}]}'\n\n def test_power_event_log_brackets_only(self):\n # POWER_EVENT_FAILURE_LOG (1-0:99.97.0)\n # Issue 57\n # Test of an ill formatted empty POWER_EVENT_FAILURE_LOG, observed on some smartmeters\n # The idea is that instead of failing, the parser converts it to an empty POWER_EVENT_FAILURE_LOG\n pef_parser = ProfileGenericParser(BUFFER_TYPES, PG_HEAD_PARSERS, PG_UNIDENTIFIED_BUFFERTYPE_PARSERS)\n object_type = ProfileGenericObject\n\n pefl_line = r'1-0:99.97.0()\\r\\n'\n testitem = pef_parser.parse(pefl_line)\n\n assert isinstance(testitem, object_type)\n assert testitem.buffer_length == 0\n assert testitem.buffer_type is None\n buffer = testitem.buffer\n assert isinstance(testitem.buffer, list)\n assert len(buffer) == 0\n assert testitem.values == [{'value': 0, 'unit': None}, {'value': None, 'unit': None}]\n json = testitem.to_json()\n assert json == '{\"buffer_length\": 0, \"buffer_type\": null, \"buffer\": []}'\n",
"review": "# 7\n\n## Bugs\n- No evident bugs found in the code provided. However, potential issues might arise if expected libraries or modules (`dsmr_parser`, `test.example_telegrams`) are missing or not compatible.\n\n## Optimizations\n- The use of hard-coded assert statements can be improved. Instead, using `self.assertEqual`, `self.assertIsInstance`, etc., would provide more robust and informative test outcomes.\n- There is a repeated pattern in obtaining the `ProfileGenericObject`; consider refactoring this into a setup method for reuse.\n- Consider improving the handling of test names to reflect the specific conditions being tested more clearly.\n\n## Good points\n- The tests cover various edge cases such as empty buffers and incorrect formatting, showing a good consideration of potential issues.\n- Usage of regular expression-like syntax to simulate data which suggests potential extensibility.\n- Converts issue-specific scenarios into actionable test cases that prevent regressions.\n\n## Summary\nThe code is a solid test suite for handling edge cases of DSMR (Dutch Smart Meter Requirements) telegram parsing. It focuses on ensuring the parser's robustness against incorrectly formatted or empty logs, and it is clear about the data format expectations. Some improvements might include refactoring to reduce repetition and improve test clarity, but overall, it provides a thorough coverage of corner cases.\n\n## Open source alternatives\n- `PyDSMR`: A Python library to parse DSMR telemetry data, which might include similar parsing and testing utilities.\n- `dsmr-reader`: An open-source project that reads DSMR data and stores it in a database, potentially including parsing capabilities.",
"filename": "test_parser_corner_cases.py",
"path": "test/objects/test_parser_corner_cases.py",
"directory": "objects",
"grade": 7,
"size": 3978,
"line_count": 89
}
@@ -0,0 +1,21 @@
# 7
## Bugs
- No evident bugs found in the code provided. However, potential issues might arise if expected libraries or modules (`dsmr_parser`, `test.example_telegrams`) are missing or not compatible.
## Optimizations
- The use of hard-coded assert statements can be improved. Instead, using `self.assertEqual`, `self.assertIsInstance`, etc., would provide more robust and informative test outcomes.
- There is a repeated pattern in obtaining the `ProfileGenericObject`; consider refactoring this into a setup method for reuse.
- Consider improving the handling of test names to reflect the specific conditions being tested more clearly.
## Good points
- The tests cover various edge cases such as empty buffers and incorrect formatting, showing a good consideration of potential issues.
- Usage of regular expression-like syntax to simulate data which suggests potential extensibility.
- Converts issue-specific scenarios into actionable test cases that prevent regressions.
## Summary
The code is a solid test suite for handling edge cases of DSMR (Dutch Smart Meter Requirements) telegram parsing. It focuses on ensuring the parser's robustness against incorrectly formatted or empty logs, and it is clear about the data format expectations. Some improvements might include refactoring to reduce repetition and improve test clarity, but overall, it provides a thorough coverage of corner cases.
## Open source alternatives
- `PyDSMR`: A Python library to parse DSMR telemetry data, which might include similar parsing and testing utilities.
- `dsmr-reader`: An open-source project that reads DSMR data and stores it in a database, potentially including parsing capabilities.
File diff suppressed because one or more lines are too long
+22
View File
@@ -0,0 +1,22 @@
**7**
### Bugs
- No specific bugs found in the code.
### Optimizations
- Consider replacing `eval()` with a safer alternative to access object attributes to avoid potential security issues.
- Instead of using manual assertions, consider using unittest's built-in methods such as `assertIsInstance()`, `assertEqual()`, etc., which provide better readability and error messages.
### Good points
- Extensive use of unit tests covering various scenarios to ensure the correctness of the parser.
- Use of helper function `verify_telegram_item` reduces code duplication and enhances readability.
- The code follows a consistent pattern in its testing methods, making it easy to follow.
- The use of descriptive variable names aids in understanding the purpose of the tests.
- The tests ensure that the data is correctly parsed and transformed into the expected output formats, including JSON and string representations.
### Summary
The code presented is a test suite for a DSMR (Dutch Smart Meter Requirements) telegram parser. The suite thoroughly tests the parsing of V4 and V5 telegram data into expected object structures, values, and types. It covers a wide range of test cases to ensure the parser accurately processes DSMR telegrams into both object attributes and JSON outputs. Although the test suite is well-structured and effective, using `eval()` can pose a security risk, and employing unittest's built-in assertion methods could improve the code's clarity and maintainability.
### Open source alternatives
- **PyDSMR**: An open-source library to parse DSMR data, focusing on extensibility and Pythonic conventions.
- **dsmr-parser**: A simple DSMR parser library for extracting information from DSMR telegrams.
+11
View File
@@ -0,0 +1,11 @@
{
"extension": ".py",
"source": "import unittest\nimport tempfile\n\nfrom dsmr_parser.clients.filereader import FileReader\nfrom dsmr_parser.telegram_specifications import V5\nfrom test.example_telegrams import TELEGRAM_V5\n\n\nclass FileReaderTest(unittest.TestCase):\n def test_read_as_object(self):\n with tempfile.NamedTemporaryFile() as file:\n with open(file.name, \"w\") as f:\n f.write(TELEGRAM_V5)\n\n telegrams = []\n reader = FileReader(file=file.name, telegram_specification=V5)\n # Call\n for telegram in reader.read_as_object():\n telegrams.append(telegram)\n\n self.assertEqual(len(telegrams), 1)\n",
"review": "**7**\n\n### Bugs\n- There are no apparent bugs in this test code snippet.\n\n### Optimizations\n- Use `self.assertListEqual` to compare the expected telegram list with the actual telegrams for more descriptive error messages.\n- Consider initializing `reader` inside the `with` statement to ensure file availability.\n- The temporary file could be managed using a `contextmanager` to make the code cleaner.\n\n### Good Points\n- The use of `tempfile.NamedTemporaryFile` ensures that test files are handled securely.\n- Clear separation of setup, execution, and assertion phases in the `test_read_as_object` method.\n- Good use of context management to handle files, reducing the risk of unclosed file handles.\n\n### Summary\nThe code is a unit test implementation that checks the reading functionality of file-based data into telegram objects using a predefined specification (`V5`). The use of temporary files for testing data handling is appropriate, even though a few improvements in comparison methods and structure can make the test more robust and expressive.\n\n### Open source alternatives\n- **pySerial**: A Python library that can be used to interface with serial ports, commonly used for reading meter data.\n- **Grid2Op**: A library to simulate and control power grid operations.\n- **Pymodbus**: A fully implemented suite of communication tools to work with the Modbus protocol.",
"filename": "test_filereader.py",
"path": "test/test_filereader.py",
"directory": "test",
"grade": 7,
"size": 663,
"line_count": 22
}
+22
View File
@@ -0,0 +1,22 @@
**7**
### Bugs
- There are no apparent bugs in this test code snippet.
### Optimizations
- Use `self.assertListEqual` to compare the expected telegram list with the actual telegrams for more descriptive error messages.
- Consider initializing `reader` inside the `with` statement to ensure file availability.
- The temporary file could be managed using a `contextmanager` to make the code cleaner.
### Good Points
- The use of `tempfile.NamedTemporaryFile` ensures that test files are handled securely.
- Clear separation of setup, execution, and assertion phases in the `test_read_as_object` method.
- Good use of context management to handle files, reducing the risk of unclosed file handles.
### Summary
The code is a unit test implementation that checks the reading functionality of file-based data into telegram objects using a predefined specification (`V5`). The use of temporary files for testing data handling is appropriate, even though a few improvements in comparison methods and structure can make the test more robust and expressive.
### Open source alternatives
- **pySerial**: A Python library that can be used to interface with serial ports, commonly used for reading meter data.
- **Grid2Op**: A library to simulate and control power grid operations.
- **Pymodbus**: A fully implemented suite of communication tools to work with the Modbus protocol.
File diff suppressed because one or more lines are too long
+23
View File
@@ -0,0 +1,23 @@
**Grade: 8**
### Bugs
- None observed in this segment.
### Optimizations
- Rather than numerous inline assertions, consider using table-driven tests to streamline data processing and verification.
- Consider creating separate validation methods for common validation steps to DRY (Don't Repeat Yourself) the code.
- Utilize `unittest.TestCase` methods like `self.assertIsInstance`, `self.assertEqual` for more readable and better error-reported assertions.
### Good Points
- Comprehensive test coverage for multiple aspects of parsing a DSMR Fluvius telegram.
- Any raised exception in `test_parse` is caught, ensuring graceful error handling.
- Utilizes the `unittest` framework for structured and systematic testing.
- Tests multiple lines of a telegram, providing confidence in broader coverage and reliability of parsing implementation.
### Summary
The provided code efficiently validates and parses DSMR Fluvius telegrams, with robust unit tests that cover numerous aspects and edge cases of the parsing process. The tests ensure that the various components of the telegram are accurately parsed and checked against expected values. Opportunities for improvement include reducing code repetition and utilizing Python's unittest asserts more thoroughly for readability and maintainability improvements.
### Open Source Alternatives
- **Home Assistant DSMR integration**: An integration that reads out DSMR telegram data as sensors in the Home Assistant ecosystem.
- **DSMR Reader**: An application to display and graph data from the Dutch Smart Meter Requirement (DSMR) in a user-friendly manner.
- **datalogger**: A Python library to read data from different types of smart meters and log them.
File diff suppressed because one or more lines are too long
+22
View File
@@ -0,0 +1,22 @@
# 8
## Bugs
- No significant bugs identified in the test code.
## Optimizations
- Use `self.assertIsInstance` and `self.assertEqual` instead of `assert` statements for better test framework integration.
- Consider using `setUp` method to initialize common variables to reduce repetition.
- Group similar assertions together for better readability and performance where possible.
## Good points
- The code is well-structured and highly readable.
- Comprehensive test coverage for various scenarios including valid parsing and checksum cases as well as invalid ones.
- Use of specific exceptions (`InvalidChecksumError`, `ParseError`) enhances clarity and debugging.
- Efficient use of the `unittest` framework for structured testing.
## Summary
The code is a well-developed test suite for the `TelegramParser` class related to parsing Iskra IE5 telegrams. It effectively checks both normal and edge cases, including parsing and validation operations. Optimization could be done by using more features from the `unittest` module to standardize and simplify assertion checks, thereby improving maintainability.
## Open source alternatives
- **PyDSM**: A Python library providing an interface for reading DSMR telegrams, capturing similar functionality.
- **dsmr-parser-python**: Another Python parser specifically designed for DSMR telegrams, very similar to the approach used here.
@@ -0,0 +1,11 @@
{
"extension": ".py",
"source": "from binascii import unhexlify\nfrom copy import deepcopy\n\nimport unittest\n\nfrom dlms_cosem.exceptions import DecryptionError\nfrom dlms_cosem.protocol.xdlms import GeneralGlobalCipher\nfrom dlms_cosem.security import SecurityControlField, encrypt\n\nfrom dsmr_parser import telegram_specifications\nfrom dsmr_parser.exceptions import ParseError\nfrom dsmr_parser.parsers import TelegramParser\nfrom test.example_telegrams import TELEGRAM_SAGEMCOM_T210_D_R\n\n\nclass TelegramParserEncryptedTest(unittest.TestCase):\n \"\"\" Test parsing of a DSML encypted DSMR v5.x telegram. \"\"\"\n DUMMY_ENCRYPTION_KEY = \"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\"\n DUMMY_AUTHENTICATION_KEY = \"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\"\n\n def __generate_encrypted(self, security_suite=0, authenticated=True, encrypted=True):\n security_control = SecurityControlField(\n security_suite=security_suite, authenticated=authenticated, encrypted=encrypted\n )\n encryption_key = unhexlify(self.DUMMY_ENCRYPTION_KEY)\n authentication_key = unhexlify(self.DUMMY_AUTHENTICATION_KEY)\n system_title = \"SYSTEMID\".encode(\"ascii\")\n invocation_counter = int.from_bytes(bytes.fromhex(\"10000001\"), \"big\")\n plain_data = TELEGRAM_SAGEMCOM_T210_D_R.encode(\"ascii\")\n\n encrypted = encrypt(\n security_control=security_control,\n key=encryption_key,\n auth_key=authentication_key,\n system_title=system_title,\n invocation_counter=invocation_counter,\n plain_text=plain_data,\n )\n\n full_frame = bytearray(GeneralGlobalCipher.TAG.to_bytes(1, \"big\", signed=False))\n full_frame.extend(len(system_title).to_bytes(1, \"big\", signed=False))\n full_frame.extend(system_title)\n full_frame.extend([0x82]) # Length of the following length bytes\n # https://github.com/pwitab/dlms-cosem/blob/739f81a58e5f07663a512d4a128851333a0ed5e6/dlms_cosem/a_xdr.py#L33\n\n security_control = security_control.to_bytes()\n invocation_counter = invocation_counter.to_bytes(4, \"big\", signed=False)\n full_frame.extend((len(encrypted)\n + len(invocation_counter)\n + len(security_control)).to_bytes(2, \"big\", signed=False))\n full_frame.extend(security_control)\n full_frame.extend(invocation_counter)\n full_frame.extend(encrypted)\n\n return full_frame\n\n def test_parse(self):\n parser = TelegramParser(telegram_specifications.SAGEMCOM_T210_D_R)\n result = parser.parse(self.__generate_encrypted().hex(),\n self.DUMMY_ENCRYPTION_KEY,\n self.DUMMY_AUTHENTICATION_KEY)\n self.assertEqual(len(result), 18)\n\n def test_damaged_frame(self):\n # If the frame is damaged decrypting fails (crc is technically not needed)\n parser = TelegramParser(telegram_specifications.SAGEMCOM_T210_D_R)\n\n generated = self.__generate_encrypted()\n generated[150] = 0x00\n generated = generated.hex()\n\n with self.assertRaises(DecryptionError):\n parser.parse(generated, self.DUMMY_ENCRYPTION_KEY, self.DUMMY_AUTHENTICATION_KEY)\n\n def test_plain(self):\n # If a plain request is parsed with \"general_global_cipher\": True it fails\n parser = TelegramParser(telegram_specifications.SAGEMCOM_T210_D_R)\n\n with self.assertRaises(Exception):\n parser.parse(TELEGRAM_SAGEMCOM_T210_D_R, self.DUMMY_ENCRYPTION_KEY, self.DUMMY_AUTHENTICATION_KEY)\n\n def test_general_global_cipher_not_specified(self):\n # If a GGC frame is detected but general_global_cipher is not set it fails\n parser = TelegramParser(telegram_specifications.SAGEMCOM_T210_D_R)\n parser = deepcopy(parser) # We do not want to change the module value\n parser.telegram_specification['general_global_cipher'] = False\n\n with self.assertRaises(ParseError):\n parser.parse(self.__generate_encrypted().hex(), self.DUMMY_ENCRYPTION_KEY, self.DUMMY_AUTHENTICATION_KEY)\n\n def test_only_encrypted(self):\n # Not implemented by dlms_cosem\n parser = TelegramParser(telegram_specifications.SAGEMCOM_T210_D_R)\n\n only_auth = self.__generate_encrypted(0, authenticated=False, encrypted=True).hex()\n\n with self.assertRaises(ValueError):\n parser.parse(only_auth, self.DUMMY_ENCRYPTION_KEY)\n\n def test_only_auth(self):\n # Not implemented by dlms_cosem\n parser = TelegramParser(telegram_specifications.SAGEMCOM_T210_D_R)\n\n only_auth = self.__generate_encrypted(0, authenticated=True, encrypted=False).hex()\n\n with self.assertRaises(ValueError):\n parser.parse(only_auth, authentication_key=self.DUMMY_AUTHENTICATION_KEY)\n",
"review": "# 7\n\n## Bugs\n- The test case method `test_only_encrypted` calls `parser.parse` without the required `self.DUMMY_AUTHENTICATION_KEY`, which leads to a `TypeError` due to a missing argument.\n- The method `__generate_encrypted` does not correctly handle dynamic lengths for the generated frame; this might lead to misinterpretations with varying-sized inputs.\n\n## Optimizations\n- Parameterize the test cases to reduce code redundancy, particularly those with similar setup and usage patterns.\n- Improve name clarity for variables and methods for better readability, like renaming `__generate_encrypted` to `generate_encrypted_frame`.\n- Use constants for repeated values (e.g., magic numbers) to provide context within the code.\n- Utilize Python's logging to capture more detailed runtime information during test failures for easier diagnosis.\n- Ensure tests are independent and reusable by abstracting common setup processes into utility functions or fixtures.\n\n## Good points\n- Test classes use meaningful docstrings that describe their purpose.\n- Thorough testing of different scenarios, including edge cases like damaged frames and only encrypted/authenticated frames, is present.\n- The code utilizes exception handling properly, asserting that expected errors are raised under specific faulty conditions.\n\n## Summary\nThe code provides a test suite for parsing encrypted DSMR v5.x telegrams with varied test conditions. While generally well-structured, there is room for improvement in terms of parameterization and error handling. The few existing bugs may affect the robustness of the test suite. Enhanced logging and variable naming could further improve code readability and maintainability.\n\n## Open source alternatives\n- **pyDSMR**: A Python library to analyze data captured from Dutch Smart Meters (DSMR). Provides parsing functionalities similar to what is attempted in the current code.\n- **pymeterreader**: Another library that supports reading and parsing meter data from DSMR devices and handles encrypted telegrams.",
"filename": "test_parse_sagemcom_t210_d_r.py",
"path": "test/test_parse_sagemcom_t210_d_r.py",
"directory": "test",
"grade": 7,
"size": 4788,
"line_count": 108
}
@@ -0,0 +1,24 @@
# 7
## Bugs
- The test case method `test_only_encrypted` calls `parser.parse` without the required `self.DUMMY_AUTHENTICATION_KEY`, which leads to a `TypeError` due to a missing argument.
- The method `__generate_encrypted` does not correctly handle dynamic lengths for the generated frame; this might lead to misinterpretations with varying-sized inputs.
## Optimizations
- Parameterize the test cases to reduce code redundancy, particularly those with similar setup and usage patterns.
- Improve name clarity for variables and methods for better readability, like renaming `__generate_encrypted` to `generate_encrypted_frame`.
- Use constants for repeated values (e.g., magic numbers) to provide context within the code.
- Utilize Python's logging to capture more detailed runtime information during test failures for easier diagnosis.
- Ensure tests are independent and reusable by abstracting common setup processes into utility functions or fixtures.
## Good points
- Test classes use meaningful docstrings that describe their purpose.
- Thorough testing of different scenarios, including edge cases like damaged frames and only encrypted/authenticated frames, is present.
- The code utilizes exception handling properly, asserting that expected errors are raised under specific faulty conditions.
## Summary
The code provides a test suite for parsing encrypted DSMR v5.x telegrams with varied test conditions. While generally well-structured, there is room for improvement in terms of parameterization and error handling. The few existing bugs may affect the robustness of the test suite. Enhanced logging and variable naming could further improve code readability and maintainability.
## Open source alternatives
- **pyDSMR**: A Python library to analyze data captured from Dutch Smart Meters (DSMR). Provides parsing functionalities similar to what is attempted in the current code.
- **pymeterreader**: Another library that supports reading and parsing meter data from DSMR devices and handles encrypted telegrams.
+11
View File
@@ -0,0 +1,11 @@
{
"extension": ".py",
"source": "import unittest\n\nfrom decimal import Decimal\n\nfrom dsmr_parser.objects import MBusObject, CosemObject\nfrom dsmr_parser.parsers import TelegramParser\nfrom dsmr_parser import telegram_specifications\nfrom dsmr_parser import obis_references as obis\nfrom test.example_telegrams import TELEGRAM_V2_2\n\n\nclass TelegramParserV2_2Test(unittest.TestCase):\n \"\"\" Test parsing of a DSMR v2.2 telegram. \"\"\"\n\n def test_parse(self):\n parser = TelegramParser(telegram_specifications.V2_2)\n try:\n result = parser.parse(TELEGRAM_V2_2, throw_ex=True)\n except Exception as ex:\n assert False, f\"parse trigged an exception {ex}\"\n\n # ELECTRICITY_USED_TARIFF_1 (1-0:1.8.1)\n assert isinstance(result[obis.ELECTRICITY_USED_TARIFF_1], CosemObject)\n assert result[obis.ELECTRICITY_USED_TARIFF_1].unit == 'kWh'\n assert isinstance(result[obis.ELECTRICITY_USED_TARIFF_1].value, Decimal)\n assert result[obis.ELECTRICITY_USED_TARIFF_1].value == Decimal('1.001')\n\n # ELECTRICITY_USED_TARIFF_2 (1-0:1.8.2)\n assert isinstance(result[obis.ELECTRICITY_USED_TARIFF_2], CosemObject)\n assert result[obis.ELECTRICITY_USED_TARIFF_2].unit == 'kWh'\n assert isinstance(result[obis.ELECTRICITY_USED_TARIFF_2].value, Decimal)\n assert result[obis.ELECTRICITY_USED_TARIFF_2].value == Decimal('1.001')\n\n # ELECTRICITY_DELIVERED_TARIFF_1 (1-0:2.8.1)\n assert isinstance(result[obis.ELECTRICITY_DELIVERED_TARIFF_1], CosemObject)\n assert result[obis.ELECTRICITY_DELIVERED_TARIFF_1].unit == 'kWh'\n assert isinstance(result[obis.ELECTRICITY_DELIVERED_TARIFF_1].value, Decimal)\n assert result[obis.ELECTRICITY_DELIVERED_TARIFF_1].value == Decimal('1.001')\n\n # ELECTRICITY_DELIVERED_TARIFF_2 (1-0:2.8.2)\n assert isinstance(result[obis.ELECTRICITY_DELIVERED_TARIFF_2], CosemObject)\n assert result[obis.ELECTRICITY_DELIVERED_TARIFF_2].unit == 'kWh'\n assert isinstance(result[obis.ELECTRICITY_DELIVERED_TARIFF_2].value, Decimal)\n assert result[obis.ELECTRICITY_DELIVERED_TARIFF_2].value == Decimal('1.001')\n\n # ELECTRICITY_ACTIVE_TARIFF (0-0:96.14.0)\n assert isinstance(result[obis.ELECTRICITY_ACTIVE_TARIFF], CosemObject)\n assert result[obis.ELECTRICITY_ACTIVE_TARIFF].unit is None\n assert isinstance(result[obis.ELECTRICITY_ACTIVE_TARIFF].value, str)\n assert result[obis.ELECTRICITY_ACTIVE_TARIFF].value == '0001'\n\n # EQUIPMENT_IDENTIFIER (0-0:96.1.1)\n assert isinstance(result[obis.EQUIPMENT_IDENTIFIER], CosemObject)\n assert result[obis.EQUIPMENT_IDENTIFIER].unit is None\n assert isinstance(result[obis.EQUIPMENT_IDENTIFIER].value, str)\n assert result[obis.EQUIPMENT_IDENTIFIER].value == '00000000000000'\n\n # CURRENT_ELECTRICITY_USAGE (1-0:1.7.0)\n assert isinstance(result[obis.CURRENT_ELECTRICITY_USAGE], CosemObject)\n assert result[obis.CURRENT_ELECTRICITY_USAGE].unit == 'kW'\n assert isinstance(result[obis.CURRENT_ELECTRICITY_USAGE].value, Decimal)\n assert result[obis.CURRENT_ELECTRICITY_USAGE].value == Decimal('1.01')\n\n # CURRENT_ELECTRICITY_DELIVERY (1-0:2.7.0)\n assert isinstance(result[obis.CURRENT_ELECTRICITY_DELIVERY], CosemObject)\n assert result[obis.CURRENT_ELECTRICITY_DELIVERY].unit == 'kW'\n assert isinstance(result[obis.CURRENT_ELECTRICITY_DELIVERY].value, Decimal)\n assert result[obis.CURRENT_ELECTRICITY_DELIVERY].value == Decimal('0')\n\n # TEXT_MESSAGE_CODE (0-0:96.13.1)\n assert isinstance(result[obis.TEXT_MESSAGE_CODE], CosemObject)\n assert result[obis.TEXT_MESSAGE_CODE].unit is None\n\n # TEXT_MESSAGE (0-0:96.13.0)\n assert isinstance(result[obis.TEXT_MESSAGE], CosemObject)\n assert result[obis.TEXT_MESSAGE].unit is None\n assert result[obis.TEXT_MESSAGE].value is None\n\n # DEVICE_TYPE (0-x:24.1.0)\n assert isinstance(result[obis.TEXT_MESSAGE], CosemObject)\n assert result[obis.DEVICE_TYPE].unit is None\n assert isinstance(result[obis.DEVICE_TYPE].value, str)\n assert result[obis.DEVICE_TYPE].value == '3'\n\n # EQUIPMENT_IDENTIFIER_GAS (0-x:96.1.0)\n assert isinstance(result[obis.EQUIPMENT_IDENTIFIER_GAS], CosemObject)\n assert result[obis.EQUIPMENT_IDENTIFIER_GAS].unit is None\n assert isinstance(result[obis.EQUIPMENT_IDENTIFIER_GAS].value, str)\n assert result[obis.EQUIPMENT_IDENTIFIER_GAS].value == '000000000000'\n\n # GAS_METER_READING (0-1:24.3.0)\n assert isinstance(result[obis.GAS_METER_READING], MBusObject)\n assert result[obis.GAS_METER_READING].unit == 'm3'\n assert isinstance(result[obis.GAS_METER_READING].value, Decimal)\n assert result[obis.GAS_METER_READING].value == Decimal('1.001')\n",
"review": "**Grade: 8**\n\n## Bugs\n- No apparent bugs were found in the provided code.\n\n## Optimizations\n- Use `self.assertEqual()` and similar functions from `unittest` instead of `assert` for better test case reporting and compatibility with test runners.\n- Avoid using camelCase for class names in Python unless following a specific naming convention for imports.\n\n## Good points\n- Comprehensive test coverage of various fields in the DSMR telegram.\n- Use of `Decimal` for precise fixed-point arithmetic, ensuring accuracy of test assertions.\n- Good organization and readability of test cases with clear checks for both values and units.\n\n## Summary\nThe code is a unit test for parsing DSMR v2.2 telegrams. It checks the parsing of numerous fields using the `unittest` framework, ensuring parsed values match expected data. Replacing `assert` statements with `unittest` assert methods would improve readability and compatibility with test suite tools. No bugs were identified, and the tests are well-structured for clarity.\n\n## Open source alternatives\n- [dsmr_parser](https://github.com/ndokter/dsmr_parser): The provided test cases seem to be a part of this library which handles parsing of DSMR telegrams, implying it might offer similar functionalities for DSMR telegram parsing.",
"filename": "test_parse_v2_2.py",
"path": "test/test_parse_v2_2.py",
"directory": "test",
"grade": 8,
"size": 4845,
"line_count": 96
}
+19
View File
@@ -0,0 +1,19 @@
**Grade: 8**
## Bugs
- No apparent bugs were found in the provided code.
## Optimizations
- Use `self.assertEqual()` and similar functions from `unittest` instead of `assert` for better test case reporting and compatibility with test runners.
- Avoid using camelCase for class names in Python unless following a specific naming convention for imports.
## Good points
- Comprehensive test coverage of various fields in the DSMR telegram.
- Use of `Decimal` for precise fixed-point arithmetic, ensuring accuracy of test assertions.
- Good organization and readability of test cases with clear checks for both values and units.
## Summary
The code is a unit test for parsing DSMR v2.2 telegrams. It checks the parsing of numerous fields using the `unittest` framework, ensuring parsed values match expected data. Replacing `assert` statements with `unittest` assert methods would improve readability and compatibility with test suite tools. No bugs were identified, and the tests are well-structured for clarity.
## Open source alternatives
- [dsmr_parser](https://github.com/ndokter/dsmr_parser): The provided test cases seem to be a part of this library which handles parsing of DSMR telegrams, implying it might offer similar functionalities for DSMR telegram parsing.
File diff suppressed because one or more lines are too long
+20
View File
@@ -0,0 +1,20 @@
# 8
## Bugs
- None observed in the current code.
## Optimizations
- Use `self.assert*` methods from `unittest` instead of plain `assert` statements for consistency with the `unittest` framework.
- Break down the test into smaller sub-tests using `subTest` for each section relating to different OBIS references. This improves maintainability and readability.
- Consider using parameterized tests to reduce redundancy when asserting similar conditions across different OBIS references.
## Good points
- Comprehensive testing of various aspects of DSMR v3 telegram parsing, ensuring all critical data points are covered.
- Use of exception handling to detect and report parsing errors during test execution.
- Clear and logical organization of the tests, providing a good structure and flow.
## Summary
The code provides a detailed unit test for parsing DSMR v3 telegrams, covering numerous OBIS references and possible values effectively. There are no evident bugs, but the use of the `unittest` framework could be optimized by replacing inline assertions with framework-specific methods for better integration and error reporting. The tests are well-structured and provide thorough coverage of the parsing functionality.
## Open source alternatives
- The `dmsr_parser` library seems like a specific utility for DSMR telegram parsing, and there might not be direct open-source alternatives that perform the exact function unless they're part of broader smart meter or energy management suites. Some commonly used data parsing libraries like `pandas` could potentially be adapted for similar purposes with custom function implementations.
File diff suppressed because one or more lines are too long
+22
View File
@@ -0,0 +1,22 @@
# Grade: 8
## Bugs
- No identified bugs.
## Optimizations
- The `assert` statements could be replaced with `self.assertEqual()` or other appropriate `unittest` functions to take full advantage of Python's `unittest` capabilities.
- Consolidate repetitive code by potentially using loops or helper functions, especially for testing similar properties across multiple data points.
- Consider using `setUp` and `tearDown` methods in `unittest` to initialize and clean up the test environment, which can improve readability and maintenability.
## Good points
- The test comprehensively covers a wide range of data points for parsing DSMR v4.2 telegrams.
- Includes tests for both valid and corrupted checksums, enhancing the robustness of the test suite.
- Uses comprehensive assertions to check the correctness of parsed values against expected results.
- Proper use of `Decimal` for financial and energy data, ensuring precision.
## Summary
The `TelegramParserV4_2Test` class provides a thorough suite of tests for parsing DSMR v4.2 telegrams. The integration with obis references and assertions for each data field ensures data integrity in parsing test cases. Corrections are suggested for employing `unittest` assert methods for improved diagnostics and potentially restructuring for common test logic to enhance maintainability.
## Open source alternatives
- **dsmr-reader**: A web application that reads DSMR telegrams and offers a user-friendly interface to analyze energy usage.
- **SMAP**: A Simple Measurement and Actuation Profile system to manage and process smart meter data inputs.
File diff suppressed because one or more lines are too long
+23
View File
@@ -0,0 +1,23 @@
**7**
### Bugs
- No direct identification of issues, but exceptions could be handled much better with more detailed information.
- The gas meter reading asserts for unit contradiction, once as 'm3' and another time as `None`.
### Optimizations
- Consider using `self.assert` over Python's `assert` statements for better unittest integration.
- The test could be refactored to be more DRY (Don't Repeat Yourself) by creating helper functions for shared assertion patterns.
- Error messages in exceptions could be made more informative and specific to enhance debugging.
- Improve exception messages to clearly communicate which part of the telegram caused the error.
### Good points
- Comprehensive range of tests covering different data points ensures thorough verification.
- Assertions use a good combination of type checks and value comparisons.
- Proper usage of Python's unittest framework provides a structured testing approach.
### Summary
The provided code is robust in terms of testing a broad spectrum of possible DSMR telegram parsing scenarios and covers multiple cases such as valid data, checksum validation, and error handling for corrupted content. However, it lacks clarity in exception messages and can be refactored to reduce repetitive code. Moving from Python's generic `assert` to unittest's `self.assert` can better integrate with test runners and produce more informative output during failures.
### Open source alternatives
- **DSMR-reader**: An open source application to read and visualize DSMR data.
- **Home Assistant DSMR integration**: A component for Home Assistant that supports parsing data from a smart meter via DSMR.
File diff suppressed because one or more lines are too long
@@ -0,0 +1,23 @@
# 8
## Bugs
- No prominent bugs were identified, but exception handling in `test_parse` could miss specific issues due to a generic exception catch.
## Optimizations
- Use `self.assertIsInstance` instead of `assert isinstance` for consistency with unittest methods.
- Refactor the test to separate setup, execution, and assertions for better readability and maintenance.
- Consider using parameterized tests to avoid repetition and improve maintainability.
- The generic exception handling in the `test_parse` method could be more specific to catch only expected exceptions.
## Good points
- Comprehensive test coverage for various fields extracted from the telegram.
- Uses the `unittest` framework appropriately to perform unit testing.
- Coverage includes validation for both valid and invalid checksums.
- Correct use of `Decimal` for precise representation of numeric values.
## Summary
The code provides thorough unittest coverage for parsing DSMR v5 EON Hungary telegrams. It ensures that each field in the telegram is correctly parsed and validated, covering cases such as valid, invalid, and missing checksums. The test structure is mostly solid, though improvements could be made by using `unittest` assertions to enhance readability and error reporting. Parameterizing repetitive assertions would also help in maintaining the code more efficiently.
## Open source alternatives
- **pytz**: This library is being used in the code for timezone localization and is a robust package for working with time zones.
- **Decouple**: If managing environment-specific configurations becomes complex, you may consider using Decouple for configuration management.
+11
View File
@@ -0,0 +1,11 @@
{
"extension": ".py",
"source": "from unittest.mock import Mock\n\nimport unittest\n\nfrom dsmr_parser import obis_references as obis\nfrom dsmr_parser.clients.protocol import create_dsmr_protocol\nfrom dsmr_parser.objects import Telegram\n\nTELEGRAM_V2_2 = (\n '/ISk5\\2MT382-1004\\r\\n'\n '\\r\\n'\n '0-0:96.1.1(00000000000000)\\r\\n'\n '1-0:1.8.1(00001.001*kWh)\\r\\n'\n '1-0:1.8.2(00001.001*kWh)\\r\\n'\n '1-0:2.8.1(00001.001*kWh)\\r\\n'\n '1-0:2.8.2(00001.001*kWh)\\r\\n'\n '0-0:96.14.0(0001)\\r\\n'\n '1-0:1.7.0(0001.01*kW)\\r\\n'\n '1-0:2.7.0(0000.00*kW)\\r\\n'\n '0-0:17.0.0(0999.00*kW)\\r\\n'\n '0-0:96.3.10(1)\\r\\n'\n '0-0:96.13.1()\\r\\n'\n '0-0:96.13.0()\\r\\n'\n '0-1:24.1.0(3)\\r\\n'\n '0-1:96.1.0(000000000000)\\r\\n'\n '0-1:24.3.0(161107190000)(00)(60)(1)(0-1:24.2.1)(m3)\\r\\n'\n '(00001.001)\\r\\n'\n '0-1:24.4.0(1)\\r\\n'\n '!\\r\\n'\n)\n\n\nclass ProtocolTest(unittest.TestCase):\n\n def setUp(self):\n new_protocol, _ = create_dsmr_protocol('2.2',\n telegram_callback=Mock(),\n keep_alive_interval=1)\n self.protocol = new_protocol()\n\n def test_complete_packet(self):\n \"\"\"Protocol should assemble incoming lines into complete packet.\"\"\"\n\n self.protocol.data_received(TELEGRAM_V2_2.encode('ascii'))\n\n telegram = self.protocol.telegram_callback.call_args_list[0][0][0]\n assert isinstance(telegram, Telegram)\n\n assert float(telegram[obis.CURRENT_ELECTRICITY_USAGE].value) == 1.01\n assert telegram[obis.CURRENT_ELECTRICITY_USAGE].unit == 'kW'\n\n assert float(telegram[obis.GAS_METER_READING].value) == 1.001\n assert telegram[obis.GAS_METER_READING].unit == 'm3'\n\n def test_receive_packet(self):\n \"\"\"Protocol packet reception.\"\"\"\n\n mock_transport = Mock()\n self.protocol.connection_made(mock_transport)\n assert not self.protocol._active\n\n self.protocol.data_received(TELEGRAM_V2_2.encode('ascii'))\n assert self.protocol._active\n\n # 1st call of keep_alive resets 'active' flag\n self.protocol.keep_alive()\n assert not self.protocol._active\n\n # 2nd call of keep_alive should close the transport\n self.protocol.keep_alive()\n mock_transport.close.assert_called_once()\n\n self.protocol.connection_lost(None)\n",
"review": "**Grade: 7**\n\n### Bugs\n- No significant bugs were found in the code.\n\n### Optimizations\n- Use `self.assertIsInstance` instead of the `assert` statement for consistency and better error messages within unittest framework.\n- Instead of `assert`, use `self.assertEqual`, `self.assertAlmostEqual`, and `self.assertFalse` for assertions to fit within the unittest framework's conventions, which provide more informative output.\n- Applying consistent naming conventions to better clarify method purposes, such as prefixing test methods with `test_`.\n\n### Good points\n- The use of `unittest` framework which is widely accepted and provides a structured way to test in Python.\n- Code is organized with setup and teardown functionalities method which ensures each test runs in isolation, maintaining test integrity.\n- Mocks are appropriately used to simulate and assert interactions in tests.\n\n### Summary\nThe code presents a well-structured unit test for a protocol that processes data telegrams. It leverages Python's `unittest` library to ensure the functionalities of receiving and processing data packets are verified. While it is mostly well-written, it could benefit from adhering more rigorously to unittest's built-in methods for making assertions versus using base `assert` statements, as well as slightly improving method naming conventions for clarity. \n\n### Open source alternatives\n- `pytest`: A powerful alternative to `unittest`, known for its simple syntax and ability to use fixtures, which can simplify setup and teardown processes.\n- `nose2`: Another testing framework that extends unittest and offers plugin support for extensibility.",
"filename": "test_protocol.py",
"path": "test/test_protocol.py",
"directory": "test",
"grade": 7,
"size": 2327,
"line_count": 74
}
+21
View File
@@ -0,0 +1,21 @@
**Grade: 7**
### Bugs
- No significant bugs were found in the code.
### Optimizations
- Use `self.assertIsInstance` instead of the `assert` statement for consistency and better error messages within unittest framework.
- Instead of `assert`, use `self.assertEqual`, `self.assertAlmostEqual`, and `self.assertFalse` for assertions to fit within the unittest framework's conventions, which provide more informative output.
- Applying consistent naming conventions to better clarify method purposes, such as prefixing test methods with `test_`.
### Good points
- The use of `unittest` framework which is widely accepted and provides a structured way to test in Python.
- Code is organized with setup and teardown functionalities method which ensures each test runs in isolation, maintaining test integrity.
- Mocks are appropriately used to simulate and assert interactions in tests.
### Summary
The code presents a well-structured unit test for a protocol that processes data telegrams. It leverages Python's `unittest` library to ensure the functionalities of receiving and processing data packets are verified. While it is mostly well-written, it could benefit from adhering more rigorously to unittest's built-in methods for making assertions versus using base `assert` statements, as well as slightly improving method naming conventions for clarity.
### Open source alternatives
- `pytest`: A powerful alternative to `unittest`, known for its simple syntax and ability to use fixtures, which can simplify setup and teardown processes.
- `nose2`: Another testing framework that extends unittest and offers plugin support for extensibility.
+11
View File
@@ -0,0 +1,11 @@
{
"extension": ".py",
"source": "from unittest.mock import Mock\n\nimport unittest\n\nfrom dsmr_parser import obis_references as obis\nfrom dsmr_parser.clients.rfxtrx_protocol import create_rfxtrx_dsmr_protocol, PACKETTYPE_DSMR, SUBTYPE_P1\nfrom dsmr_parser.objects import Telegram\n\nTELEGRAM_V2_2 = (\n '/ISk5\\2MT382-1004\\r\\n'\n '\\r\\n'\n '0-0:96.1.1(00000000000000)\\r\\n'\n '1-0:1.8.1(00001.001*kWh)\\r\\n'\n '1-0:1.8.2(00001.001*kWh)\\r\\n'\n '1-0:2.8.1(00001.001*kWh)\\r\\n'\n '1-0:2.8.2(00001.001*kWh)\\r\\n'\n '0-0:96.14.0(0001)\\r\\n'\n '1-0:1.7.0(0001.01*kW)\\r\\n'\n '1-0:2.7.0(0000.00*kW)\\r\\n'\n '0-0:17.0.0(0999.00*kW)\\r\\n'\n '0-0:96.3.10(1)\\r\\n'\n '0-0:96.13.1()\\r\\n'\n '0-0:96.13.0()\\r\\n'\n '0-1:24.1.0(3)\\r\\n'\n '0-1:96.1.0(000000000000)\\r\\n'\n '0-1:24.3.0(161107190000)(00)(60)(1)(0-1:24.2.1)(m3)\\r\\n'\n '(00001.001)\\r\\n'\n '0-1:24.4.0(1)\\r\\n'\n '!\\r\\n'\n)\n\nOTHER_RF_PACKET = b'\\x03\\x01\\x02\\x03'\n\n\ndef encode_telegram_as_RF_packets(telegram):\n data = b''\n\n for line in telegram.split('\\n'):\n packet_data = (line + '\\n').encode('ascii')\n packet_header = bytes(bytearray([\n len(packet_data) + 3, # excluding length byte\n PACKETTYPE_DSMR,\n SUBTYPE_P1,\n 0 # seq num (ignored)\n ]))\n\n data += packet_header + packet_data\n # other RF packets can pass by on the line\n data += OTHER_RF_PACKET\n\n return data\n\n\nclass RFXtrxProtocolTest(unittest.TestCase):\n\n def setUp(self):\n new_protocol, _ = create_rfxtrx_dsmr_protocol('2.2',\n telegram_callback=Mock(),\n keep_alive_interval=1)\n self.protocol = new_protocol()\n\n def test_complete_packet(self):\n \"\"\"Protocol should assemble incoming lines into complete packet.\"\"\"\n\n data = encode_telegram_as_RF_packets(TELEGRAM_V2_2)\n # send data broken up in two parts\n self.protocol.data_received(data[0:200])\n self.protocol.data_received(data[200:])\n\n telegram = self.protocol.telegram_callback.call_args_list[0][0][0]\n assert isinstance(telegram, Telegram)\n\n assert float(telegram[obis.CURRENT_ELECTRICITY_USAGE].value) == 1.01\n assert telegram[obis.CURRENT_ELECTRICITY_USAGE].unit == 'kW'\n\n assert float(telegram[obis.GAS_METER_READING].value) == 1.001\n assert telegram[obis.GAS_METER_READING].unit == 'm3'\n",
"review": "# Grade: 7\n\n## Bugs\n- None detected\n\n## Optimizations\n- Use `self.assertIsInstance` instead of `assert isinstance` for better unittest integration.\n- Consider proper asynchronous function handling since `data_received` might be designed to work in an async environment.\n- Instead of hardcoding values, consider defining constants for repeating values, such as `3` in `packet_header`.\n- The expression `(line + '\\n')` can be optimized by using formatted strings.\n\n## Good points\n- The code has proper use of unittest framework with clear test structure.\n- Effective use of Mock to simulate external dependencies.\n- Code is modular, breaking logic into clearly defined functions.\n\n## Summary\nThe code is a unit test for the RFXtrx DSMR protocol using the unittest framework. It includes a helper function to encode a DSMR telegram into RF packets and uses Mock to simulate the telegram callback. The test checks that incoming data is correctly interpreted as a telegram object with the expected electricity and gas readings. The code is clean and follows good practices in terms of structure and decomposition, although minor optimizations could improve performance and readability.\n\n## Open source alternatives\n- [dsmr_parser](https://github.com/ndokter/dsmr_parser): While this is more focused on parsing DSMR readings, related functionalities of DSMR processing are relevant.\n- [Home Assistant](https://github.com/home-assistant/core): Offers broader smart home integrations, including DSMR integrations.\n- [pyRFXtrx](https://github.com/Danielhiversen/pyRFXtrx): A Python library that controls RFXtrx chips and can contribute to understanding RF protocol integration.",
"filename": "test_rfxtrx_protocol.py",
"path": "test/test_rfxtrx_protocol.py",
"directory": "test",
"grade": 7,
"size": 2436,
"line_count": 78
}
+23
View File
@@ -0,0 +1,23 @@
# Grade: 7
## Bugs
- None detected
## Optimizations
- Use `self.assertIsInstance` instead of `assert isinstance` for better unittest integration.
- Consider proper asynchronous function handling since `data_received` might be designed to work in an async environment.
- Instead of hardcoding values, consider defining constants for repeating values, such as `3` in `packet_header`.
- The expression `(line + '\n')` can be optimized by using formatted strings.
## Good points
- The code has proper use of unittest framework with clear test structure.
- Effective use of Mock to simulate external dependencies.
- Code is modular, breaking logic into clearly defined functions.
## Summary
The code is a unit test for the RFXtrx DSMR protocol using the unittest framework. It includes a helper function to encode a DSMR telegram into RF packets and uses Mock to simulate the telegram callback. The test checks that incoming data is correctly interpreted as a telegram object with the expected electricity and gas readings. The code is clean and follows good practices in terms of structure and decomposition, although minor optimizations could improve performance and readability.
## Open source alternatives
- [dsmr_parser](https://github.com/ndokter/dsmr_parser): While this is more focused on parsing DSMR readings, related functionalities of DSMR processing are relevant.
- [Home Assistant](https://github.com/home-assistant/core): Offers broader smart home integrations, including DSMR integrations.
- [pyRFXtrx](https://github.com/Danielhiversen/pyRFXtrx): A Python library that controls RFXtrx chips and can contribute to understanding RF protocol integration.
+11
View File
@@ -0,0 +1,11 @@
{
"extension": ".py",
"source": "import unittest\n\nfrom dsmr_parser.clients.telegram_buffer import TelegramBuffer\nfrom test.example_telegrams import TELEGRAM_V2_2, TELEGRAM_V4_2\n\n\nclass TelegramBufferTest(unittest.TestCase):\n\n def setUp(self):\n self.telegram_buffer = TelegramBuffer()\n\n def test_v22_telegram(self):\n self.telegram_buffer.append(TELEGRAM_V2_2)\n\n telegram = next(self.telegram_buffer.get_all())\n\n self.assertEqual(telegram, TELEGRAM_V2_2)\n self.assertEqual(self.telegram_buffer._buffer, '')\n\n def test_v42_telegram(self):\n self.telegram_buffer.append(TELEGRAM_V4_2)\n\n telegram = next(self.telegram_buffer.get_all())\n\n self.assertEqual(telegram, TELEGRAM_V4_2)\n self.assertEqual(self.telegram_buffer._buffer, '')\n\n def test_multiple_mixed_telegrams(self):\n self.telegram_buffer.append(\n ''.join((TELEGRAM_V2_2, TELEGRAM_V4_2, TELEGRAM_V2_2))\n )\n\n telegrams = list(self.telegram_buffer.get_all())\n\n self.assertListEqual(\n telegrams,\n [\n TELEGRAM_V2_2,\n TELEGRAM_V4_2,\n TELEGRAM_V2_2\n ]\n )\n\n self.assertEqual(self.telegram_buffer._buffer, '')\n\n def test_v42_telegram_preceded_with_unclosed_telegram(self):\n # There are unclosed telegrams at the start of the buffer.\n incomplete_telegram = TELEGRAM_V4_2[:-1]\n\n self.telegram_buffer.append(incomplete_telegram + TELEGRAM_V4_2)\n\n telegram = next(self.telegram_buffer.get_all())\n\n self.assertEqual(telegram, TELEGRAM_V4_2)\n self.assertEqual(self.telegram_buffer._buffer, '')\n\n def test_v42_telegram_preceded_with_unopened_telegram(self):\n # There is unopened telegrams at the start of the buffer indicating that\n # the buffer was being filled while the telegram was outputted halfway.\n incomplete_telegram = TELEGRAM_V4_2[1:]\n\n self.telegram_buffer.append(incomplete_telegram + TELEGRAM_V4_2)\n\n telegram = next(self.telegram_buffer.get_all())\n\n self.assertEqual(telegram, TELEGRAM_V4_2)\n self.assertEqual(self.telegram_buffer._buffer, '')\n\n def test_v42_telegram_trailed_by_unclosed_telegram(self):\n incomplete_telegram = TELEGRAM_V4_2[:-1]\n\n self.telegram_buffer.append(TELEGRAM_V4_2 + incomplete_telegram)\n\n telegram = next(self.telegram_buffer.get_all())\n\n self.assertEqual(telegram, TELEGRAM_V4_2)\n self.assertEqual(self.telegram_buffer._buffer, incomplete_telegram)\n\n def test_v42_telegram_trailed_by_unopened_telegram(self):\n incomplete_telegram = TELEGRAM_V4_2[1:]\n\n self.telegram_buffer.append(TELEGRAM_V4_2 + incomplete_telegram)\n\n telegram = next(self.telegram_buffer.get_all())\n\n self.assertEqual(telegram, TELEGRAM_V4_2)\n self.assertEqual(self.telegram_buffer._buffer, incomplete_telegram)\n\n def test_v42_telegram_adding_line_by_line(self):\n for line in TELEGRAM_V4_2.splitlines(keepends=True):\n self.telegram_buffer.append(line)\n\n telegram = next(self.telegram_buffer.get_all())\n\n self.assertEqual(telegram, TELEGRAM_V4_2)\n self.assertEqual(self.telegram_buffer._buffer, '')\n\n def test_v42_telegram_adding_char_by_char(self):\n for char in TELEGRAM_V4_2:\n self.telegram_buffer.append(char)\n\n telegram = next(self.telegram_buffer.get_all())\n\n self.assertEqual(telegram, TELEGRAM_V4_2)\n self.assertEqual(self.telegram_buffer._buffer, '')\n",
"review": "**8**\n\n### Bugs\n- No explicit bugs were found in the provided code snippet.\n\n### Optimizations\n- Consider using a mock library to handle the TELGRAM data instead of importing it directly to allow more flexible testing.\n- Add more detailed assertions to handle potential edge cases or unexpected states in the TelegramBuffer methods.\n- Consider using the `with` statement with unittest (e.g., `with self.subTest(...):`) to better manage and report multiple test cases within the same test method.\n\n### Good points\n- Comprehensive test cases covering various scenarios.\n- Use of `unittest.TestCase` for a structured testing approach.\n- Use of descriptive method names that clearly state the intent of each test case.\n- The tests seem well-organized and appear to cover a wide variety of buffer states.\n\n### Summary\nThe provided code is a unit test suite for the `TelegramBuffer` class, which handles the parsing and storage of telegrams in a buffer. The suite includes tests for normal operation, boundary conditions, and malformed telegrams, providing solid coverage. There's room for improvement with the flexibility and granularity of the tests using mocks and more assertive testing of edge cases. No significant bugs were identified, indicating a stable code base.\n\n### Open source alternatives\n- `pytest` - A more feature-rich and flexible testing framework than unittest.\n- `nose2` - Extends unittest for additional functionality and easier test organization.\n- `hypothesis` - Provides property-based testing, useful for edge cases not explicitly defined in the test scenarios.",
"filename": "test_telegram_buffer.py",
"path": "test/test_telegram_buffer.py",
"directory": "test",
"grade": 8,
"size": 3535,
"line_count": 106
}
+23
View File
@@ -0,0 +1,23 @@
**8**
### Bugs
- No explicit bugs were found in the provided code snippet.
### Optimizations
- Consider using a mock library to handle the TELGRAM data instead of importing it directly to allow more flexible testing.
- Add more detailed assertions to handle potential edge cases or unexpected states in the TelegramBuffer methods.
- Consider using the `with` statement with unittest (e.g., `with self.subTest(...):`) to better manage and report multiple test cases within the same test method.
### Good points
- Comprehensive test cases covering various scenarios.
- Use of `unittest.TestCase` for a structured testing approach.
- Use of descriptive method names that clearly state the intent of each test case.
- The tests seem well-organized and appear to cover a wide variety of buffer states.
### Summary
The provided code is a unit test suite for the `TelegramBuffer` class, which handles the parsing and storage of telegrams in a buffer. The suite includes tests for normal operation, boundary conditions, and malformed telegrams, providing solid coverage. There's room for improvement with the flexibility and granularity of the tests using mocks and more assertive testing of edge cases. No significant bugs were identified, indicating a stable code base.
### Open source alternatives
- `pytest` - A more feature-rich and flexible testing framework than unittest.
- `nose2` - Extends unittest for additional functionality and easier test organization.
- `hypothesis` - Provides property-based testing, useful for edge cases not explicitly defined in the test scenarios.
+8 -6
View File
@@ -3,16 +3,18 @@ from setuptools import setup, find_packages
setup(
name='dsmr-parser',
description='Library to parse Dutch Smart Meter Requirements (DSMR)',
author='Nigel Dokter',
author_email='nigeldokter@gmail.com',
author='Nigel Dokter and many others',
author_email='mail@nldr.net',
license='MIT',
url='https://github.com/ndokter/dsmr_parser',
version='0.6',
packages=find_packages(),
version='1.4.2',
packages=find_packages(exclude=('test', 'test.*')),
install_requires=[
'pyserial>=3,<4',
'pyserial-asyncio<1',
'pyserial-asyncio-fast>=0.11',
'pytz',
'PyCRC>=1.2,<2'
'Tailer==0.4.1',
'dlms_cosem==21.3.2'
],
entry_points={
'console_scripts': ['dsmr_console=dsmr_parser.__main__:console']
+407
View File
@@ -0,0 +1,407 @@
TELEGRAM_V2_2 = (
'/ISk5\\2MT382-1004\r\n'
'\r\n'
'0-0:96.1.1(00000000000000)\r\n'
'1-0:1.8.1(00001.001*kWh)\r\n'
'1-0:1.8.2(00001.001*kWh)\r\n'
'1-0:2.8.1(00001.001*kWh)\r\n'
'1-0:2.8.2(00001.001*kWh)\r\n'
'0-0:96.14.0(0001)\r\n'
'1-0:1.7.0(0001.01*kW)\r\n'
'1-0:2.7.0(0000.00*kW)\r\n'
'0-0:17.0.0(0999.00*kW)\r\n'
'0-0:96.3.10(1)\r\n'
'0-0:96.13.1()\r\n'
'0-0:96.13.0()\r\n'
'0-1:24.1.0(3)\r\n'
'0-1:96.1.0(000000000000)\r\n'
'0-1:24.3.0(161107190000)(00)(60)(1)(0-1:24.2.1)(m3)\r\n'
'(00001.001)\r\n'
'0-1:24.4.0(1)\r\n'
'!\r\n'
)
TELEGRAM_V3 = (
'/ISk5\\2MT382-1000\r\n'
'\r\n'
'0-0:96.1.1(4B384547303034303436333935353037)\r\n'
'1-0:1.8.1(12345.678*kWh)\r\n'
'1-0:1.8.2(12345.678*kWh)\r\n'
'1-0:2.8.1(12345.678*kWh)\r\n'
'1-0:2.8.2(12345.678*kWh)\r\n'
'0-0:96.14.0(0002)\r\n'
'1-0:1.7.0(001.19*kW)\r\n'
'1-0:2.7.0(000.00*kW)\r\n'
'0-0:17.0.0(016*A)\r\n'
'0-0:96.3.10(1)\r\n'
'0-0:96.13.1(303132333435363738)\r\n'
'0-0:96.13.0(303132333435363738393A3B3C3D3E3F303132333435363738393A3B3C3D3E'
'3F303132333435363738393A3B3C3D3E3F303132333435363738393A3B3C3D3E3F30313233'
'3435363738393A3B3C3D3E3F)\r\n'
'0-1:96.1.0(3232323241424344313233343536373839)\r\n'
'0-1:24.1.0(03)\r\n'
'0-1:24.3.0(090212160000)(00)(60)(1)(0-1:24.2.1)(m3)\r\n'
'(00001.001)\r\n'
'0-1:24.4.0(1)\r\n'
'!\r\n'
)
TELEGRAM_V4_2 = (
'/KFM5KAIFA-METER\r\n'
'\r\n'
'1-3:0.2.8(42)\r\n'
'0-0:1.0.0(161113205757W)\r\n'
'0-0:96.1.1(3960221976967177082151037881335713)\r\n'
'1-0:1.8.1(001581.123*kWh)\r\n'
'1-0:1.8.2(001435.706*kWh)\r\n'
'1-0:2.8.1(000000.000*kWh)\r\n'
'1-0:2.8.2(000000.000*kWh)\r\n'
'0-0:96.14.0(0002)\r\n'
'1-0:1.7.0(02.027*kW)\r\n'
'1-0:2.7.0(00.000*kW)\r\n'
'0-0:96.7.21(00015)\r\n'
'0-0:96.7.9(00007)\r\n'
'1-0:99.97.0(3)(0-0:96.7.19)(000104180320W)(0000237126*s)(000101000001W)'
'(2147583646*s)(000102000003W)(2317482647*s)\r\n'
'1-0:32.32.0(00000)\r\n'
'1-0:52.32.0(00000)\r\n'
'1-0:72.32.0(00000)\r\n'
'1-0:32.36.0(00000)\r\n'
'1-0:52.36.0(00000)\r\n'
'1-0:72.36.0(00000)\r\n'
'0-0:96.13.1()\r\n'
'0-0:96.13.0()\r\n'
'1-0:31.7.0(000*A)\r\n'
'1-0:51.7.0(006*A)\r\n'
'1-0:71.7.0(002*A)\r\n'
'1-0:21.7.0(00.170*kW)\r\n'
'1-0:22.7.0(00.000*kW)\r\n'
'1-0:41.7.0(01.247*kW)\r\n'
'1-0:42.7.0(00.000*kW)\r\n'
'1-0:61.7.0(00.209*kW)\r\n'
'1-0:62.7.0(00.000*kW)\r\n'
'0-1:24.1.0(003)\r\n'
'0-1:96.1.0(4819243993373755377509728609491464)\r\n'
'0-1:24.2.1(161129200000W)(00981.443*m3)\r\n'
'!6796\r\n'
)
TELEGRAM_V5 = (
'/ISk5\\2MT382-1000\r\n'
'\r\n'
'1-3:0.2.8(50)\r\n'
'0-0:1.0.0(170102192002W)\r\n'
'0-0:96.1.1(4B384547303034303436333935353037)\r\n'
'1-0:1.8.1(000004.426*kWh)\r\n'
'1-0:1.8.2(000002.399*kWh)\r\n'
'1-0:2.8.1(000002.444*kWh)\r\n'
'1-0:2.8.2(000000.000*kWh)\r\n'
'0-0:96.14.0(0002)\r\n'
'1-0:1.7.0(00.244*kW)\r\n'
'1-0:2.7.0(00.000*kW)\r\n'
'0-0:96.7.21(00013)\r\n'
'0-0:96.7.9(00000)\r\n'
'1-0:99.97.0(0)(0-0:96.7.19)\r\n'
'1-0:32.32.0(00000)\r\n'
'1-0:52.32.0(00000)\r\n'
'1-0:72.32.0(00000)\r\n'
'1-0:32.36.0(00000)\r\n'
'1-0:52.36.0(00000)\r\n'
'1-0:72.36.0(00000)\r\n'
'0-0:96.13.0()\r\n'
'1-0:32.7.0(0230.0*V)\r\n'
'1-0:52.7.0(0230.0*V)\r\n'
'1-0:72.7.0(0229.0*V)\r\n'
'1-0:31.7.0(0.48*A)\r\n'
'1-0:51.7.0(0.44*A)\r\n'
'1-0:71.7.0(0.86*A)\r\n'
'1-0:21.7.0(00.070*kW)\r\n'
'1-0:41.7.0(00.032*kW)\r\n'
'1-0:61.7.0(00.142*kW)\r\n'
'1-0:22.7.0(00.000*kW)\r\n'
'1-0:42.7.0(00.000*kW)\r\n'
'1-0:62.7.0(00.000*kW)\r\n'
'0-1:24.1.0(003)\r\n'
'0-1:96.1.0(3232323241424344313233343536373839)\r\n'
'0-1:24.2.1(170102161005W)(00000.107*m3)\r\n'
'0-2:24.1.0(003)\r\n'
'0-2:96.1.0()\r\n'
'!6EEE\r\n'
)
# V5 telegram with 2 MBUS devices
TELEGRAM_V5_TWO_MBUS = (
'/ISK5\\2M550T-1012\r\n'
'\r\n'
'1-3:0.2.8(50)\r\n'
'0-0:1.0.0(200426223325S)\r\n'
'0-0:96.1.1(4530303434303037333832323436303139)\r\n'
'1-0:1.8.1(002130.115*kWh)\r\n'
'1-0:1.8.2(000245.467*kWh)\r\n'
'1-0:2.8.1(000000.000*kWh)\r\n'
'1-0:2.8.2(000000.000*kWh)\r\n'
'0-0:96.14.0(0001)\r\n'
'1-0:1.7.0(00.111*kW)\r\n'
'1-0:2.7.0(00.000*kW)\r\n'
'0-0:96.7.21(00005)\r\n'
'0-0:96.7.9(00003)\r\n'
'1-0:99.97.0(1)(0-0:96.7.19)(190326095015W)(0000002014*s)\r\n'
'1-0:32.32.0(00001)\r\n'
'1-0:52.32.0(00001)\r\n'
'1-0:72.32.0(00192)\r\n'
'1-0:32.36.0(00001)\r\n'
'1-0:52.36.0(00001)\r\n'
'1-0:72.36.0(00001)\r\n'
'0-0:96.13.0()\r\n'
'1-0:32.7.0(229.9*V)\r\n'
'1-0:52.7.0(229.2*V)\r\n'
'1-0:72.7.0(222.9*V)\r\n'
'1-0:31.7.0(000*A)\r\n'
'1-0:51.7.0(000*A)\r\n'
'1-0:71.7.0(001*A)\r\n'
'1-0:21.7.0(00.056*kW)\r\n'
'1-0:41.7.0(00.000*kW)\r\n'
'1-0:61.7.0(00.055*kW)\r\n'
'1-0:22.7.0(00.000*kW)\r\n'
'1-0:42.7.0(00.000*kW)\r\n'
'1-0:62.7.0(00.000*kW)\r\n'
'0-1:24.1.0(003)\r\n'
'0-1:96.1.0()\r\n'
'0-1:24.2.1(700101010000W)(00000000)\r\n'
'0-2:24.1.0(003)\r\n'
'0-2:96.1.0(4730303339303031393336393930363139)\r\n'
'0-2:24.2.1(200426223001S)(00246.138*m3)\r\n'
'!56DD\r\n'
)
TELEGRAM_FLUVIUS_V171 = (
'/FLU5\\253769484_A\r\n'
'\r\n'
'0-0:96.1.4(50217)\r\n'
'0-0:96.1.1(3153414733313031303231363035)\r\n'
'0-0:1.0.0(200512135409S)\r\n'
'1-0:1.8.1(000000.034*kWh)\r\n'
'1-0:1.8.2(000015.758*kWh)\r\n'
'1-0:2.8.1(000000.000*kWh)\r\n'
'1-0:2.8.2(000000.011*kWh)\r\n'
'1-0:1.4.0(02.351*kW)\r\n'
'1-0:1.6.0(200509134558S)(02.589*kW)\r\n'
'0-0:98.1.0(3)(1-0:1.6.0)(1-0:1.6.0)(200501000000S)(200423192538S)(03.695*kW)(200401000000S)(200305122139S)(05.980*kW)(200301000000S)(200210035421W)(04.318*kW)\r\n'
'0-0:96.14.0(0001)\r\n'
'1-0:1.7.0(00.000*kW)\r\n'
'1-0:2.7.0(00.000*kW)\r\n'
'1-0:21.7.0(00.000*kW)\r\n'
'1-0:41.7.0(00.000*kW)\r\n'
'1-0:61.7.0(00.000*kW)\r\n'
'1-0:22.7.0(00.000*kW)\r\n'
'1-0:42.7.0(00.000*kW)\r\n'
'1-0:62.7.0(00.000*kW)\r\n'
'1-0:32.7.0(234.7*V)\r\n'
'1-0:52.7.0(234.7*V)\r\n'
'1-0:72.7.0(234.7*V)\r\n'
'1-0:31.7.0(000.00*A)\r\n'
'1-0:51.7.0(000.00*A)\r\n'
'1-0:71.7.0(000.00*A)\r\n'
'0-0:96.3.10(1)\r\n'
'0-0:17.0.0(999.9*kW)\r\n'
'1-0:31.4.0(999*A)\r\n'
'0-0:96.13.0()\r\n'
'0-1:24.1.0(003)\r\n'
'0-1:96.1.1(37464C4F32313139303333373333)\r\n'
'0-1:24.4.0(1)\r\n'
'0-1:24.2.3(200512134558S)(00112.384*m3)\r\n'
'0-2:24.1.0(007)\r\n'
'0-2:96.1.1(3853414731323334353637383930)\r\n'
'0-2:24.2.1(200512134558S)(00872.234*m3)\r\n'
'!3AD7\r\n'
)
TELEGRAM_FLUVIUS_V171_ALT = (
'/FLU5\\253769484_A\r\n'
'\r\n'
'0-0:96.1.4(50217)\r\n'
'0-0:96.1.1(3153414733313030373231333236)\r\n'
'0-0:1.0.0(231102121548W)\r\n'
'1-0:1.8.1(000301.548*kWh)\r\n'
'1-0:1.8.2(000270.014*kWh)\r\n'
'1-0:2.8.1(000000.005*kWh)\r\n'
'1-0:2.8.2(000000.000*kWh)\r\n'
'0-0:96.14.0(0001)\r\n'
'1-0:1.4.0(00.052*kW)\r\n'
'1-0:1.6.0(231102114500W)(03.064*kW)\r\n'
'0-0:98.1.0(4)(1-0:1.6.0)(1-0:1.6.0)(230801000000S)(632525252525W)(00.000*kW)(230901000000S)(230831181500S)(01.862*kW)(231001000000S)(230910183000S)(04.229*kW)(231101000000W)(231016130000S)(04.927*kW)\r\n'
'1-0:1.7.0(00.338*kW)\r\n'
'1-0:2.7.0(00.000*kW)\r\n'
'1-0:21.7.0(00.047*kW)\r\n'
'1-0:41.7.0(00.179*kW)\r\n'
'1-0:61.7.0(00.111*kW)\r\n'
'1-0:22.7.0(00.000*kW)\r\n'
'1-0:42.7.0(00.000*kW)\r\n'
'1-0:62.7.0(00.000*kW)\r\n'
'1-0:32.7.0(232.9*V)\r\n'
'1-0:52.7.0(228.1*V)\r\n'
'1-0:72.7.0(228.1*V)\r\n'
'1-0:31.7.0(000.27*A)\r\n'
'1-0:51.7.0(000.88*A)\r\n'
'1-0:71.7.0(000.52*A)\r\n'
'0-0:96.3.10(1)\r\n'
'0-0:17.0.0(999.9*kW)\r\n'
'1-0:31.4.0(999*A)\r\n'
'0-0:96.13.0()\r\n'
'0-1:24.1.0(003)\r\n'
'0-1:96.1.1(37464C4F32313233303838303237)\r\n'
'0-1:24.4.0(1)\r\n'
'0-1:24.2.3(231102121002W)(00092.287*m3)\r\n'
'0-2:24.1.0(007)\r\n'
'0-2:96.1.1(3853455430303030393631313733)\r\n'
'0-2:24.2.1(231102121532W)(00008.579*m3)\r\n'
'!C4B0\r\n'
)
# EasyMeter via COM-1 Ethernet Gateway
# Q3D Manual (german) https://www.easymeter.com/downloads/products/zaehler/Q3D/Easymeter_Q3D_DE_2016-06-15.pdf
# - type code on page 8
# - D0-Specs on page 20
#
# last two lines are added by the COM-1 Ethernet Gateway
TELEGRAM_ESY5Q3DB1024_V304 = (
'/ESY5Q3DB1024 V3.04\r\n'
'\r\n'
'1-0:0.0.0*255(0272031312565)\r\n'
'1-0:1.8.0*255(00052185.7825309*kWh)\r\n'
'1-0:2.8.0*255(00019949.3221493*kWh)\r\n'
'1-0:21.7.0*255(000747.85*W)\r\n'
'1-0:41.7.0*255(000737.28*W)\r\n'
'1-0:61.7.0*255(000639.73*W)\r\n'
'1-0:1.7.0*255(002124.86*W)\r\n'
'1-0:96.5.5*255(80)\r\n'
'0-0:96.1.255*255(1ESY1313002565)\r\n'
'!\r\n'
' 25803103\r\n'
'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
'\xff\xff\xff\xff\xff\r\n'
)
TELEGRAM_ESY5Q3DA1004_V304 = (
'/ESY5Q3DA1004 V3.04\r\n'
'\r\n'
'1-0:0.0.0*255(1336001560)\r\n'
'1-0:1.8.0*255(00032549.5061662*kWh)\r\n'
'1-0:21.7.0*255(000557.29*W)\r\n'
'1-0:41.7.0*255(000521.62*W)\r\n'
'1-0:61.7.0*255(000609.30*W)\r\n'
'1-0:1.7.0*255(001688.21*W)\r\n'
'1-0:96.5.5*255(80)\r\n'
'0-0:96.1.255*255(1ESY1336001560)\r\n'
'!\r\n'
' 25818685\r\n'
'DE0000000000000000000000000000003\r\n'
)
TELEGRAM_SAGEMCOM_T210_D_R = (
'/EST5\\253710000_A\r\n'
'\r\n'
'1-3:0.2.8(50)\r\n'
'0-0:1.0.0(221006155014S)\r\n'
'1-0:1.8.0(006545766*Wh)\r\n'
'1-0:1.8.1(005017120*Wh)\r\n'
'1-0:1.8.2(001528646*Wh)\r\n'
'1-0:1.7.0(000000286*W)\r\n'
'1-0:2.8.0(000000058*Wh)\r\n'
'1-0:2.8.1(000000000*Wh)\r\n'
'1-0:2.8.2(000000058*Wh)\r\n'
'1-0:2.7.0(000000000*W)\r\n'
'1-0:3.8.0(000000747*varh)\r\n'
'1-0:3.8.1(000000000*varh)\r\n'
'1-0:3.8.2(000000747*varh)\r\n'
'1-0:3.7.0(000000000*var)\r\n'
'1-0:4.8.0(003897726*varh)\r\n'
'1-0:4.8.1(002692848*varh)\r\n'
'1-0:4.8.2(001204878*varh)\r\n'
'1-0:4.7.0(000000166*var)\r\n'
'!7EF9\r\n'
)
TELEGRAM_ISKRA_IE = (
'/ISk5\2MIE5T-200\r\n'
'\r\n'
'1-0:0.0.0(00000000)\r\n'
'0-0:96.1.0(09610)\r\n'
'0-0:1.0.0(230202132747S)\r\n'
'1-0:1.8.1(000010.181*kWh)\r\n'
'1-0:1.8.2(000010.182*kWh)\r\n'
'1-0:2.8.1(000010.281*kWh)\r\n'
'1-0:2.8.2(000010.282*kWh)\r\n'
'0-0:96.14.0(0001)\r\n'
'1-0:1.7.0(00.170*kW)\r\n'
'1-0:2.7.0(00.270*kW)\r\n'
'1-0:21.7.0(00.217*kW)\r\n'
'1-0:41.7.0(00.417*kW)\r\n'
'1-0:61.7.0(00.617*kW)\r\n'
'1-0:22.7.0(00.227*kW)\r\n'
'1-0:42.7.0(00.427*kW)\r\n'
'1-0:62.7.0(00.627*kW)\r\n'
'1-0:32.7.0(242.5*V)\r\n'
'1-0:52.7.0(241.7*V)\r\n'
'1-0:72.7.0(243.3*V)\r\n'
'1-0:31.7.0(000*A)\r\n'
'1-0:51.7.0(000*A)\r\n'
'1-0:71.7.0(000*A)\r\n'
'0-0:96.3.10(1)\r\n'
'0-0:96.13.0()\r\n'
'0-1:96.1.1()\r\n'
'!AD3B\r\n'
)
# V5 telegram of EON in Hungary
TELEGRAM_V5_EON_HU = (
'/SAG5SAG-METER\r\n'
'\r\n'
'0-0:1.0.0(230724150730S)\r\n'
'0-0:42.0.0(53414733303832323030303032313630)\r\n'
'0-0:96.1.0(383930303832323030303032313630)\r\n'
'0-0:96.14.0(0001)\r\n'
'0-0:96.3.10(1)\r\n'
'0-0:17.0.0(90.000*kW)\r\n'
'1-0:1.8.0(000173.640*kWh)\r\n'
'1-0:1.8.1(000047.719*kWh)\r\n'
'1-0:1.8.2(000125.921*kWh)\r\n'
'1-0:1.8.3(000000.000*kWh)\r\n'
'1-0:1.8.4(000000.000*kWh)\r\n'
'1-0:2.8.0(000627.177*kWh)\r\n'
'1-0:2.8.1(000401.829*kWh)\r\n'
'1-0:2.8.2(000225.348*kWh)\r\n'
'1-0:2.8.3(000000.000*kWh)\r\n'
'1-0:2.8.4(000000.000*kWh)\r\n'
'1-0:3.8.0(000000.123*kvarh)\r\n'
'1-0:4.8.0(000303.131*kvarh)\r\n'
'1-0:5.8.0(000000.668*kvarh)\r\n'
'1-0:6.8.0(000000.071*kvarh)\r\n'
'1-0:7.8.0(000160.487*kvarh)\r\n'
'1-0:8.8.0(000143.346*kvarh)\r\n'
'1-0:15.8.0(000800.817*kWh)\r\n'
'1-0:32.7.0(240.4*V)\r\n'
'1-0:52.7.0(239.1*V)\r\n'
'1-0:72.7.0(241.2*V)\r\n'
'1-0:31.7.0(003*A)\r\n'
'1-0:51.7.0(004*A)\r\n'
'1-0:71.7.0(003*A)\r\n'
'1-0:13.7.0(4.556)\r\n'
'1-0:33.7.0(4.591)\r\n'
'1-0:53.7.0(4.542)\r\n'
'1-0:73.7.0(4.552)\r\n'
'1-0:14.7.0(50.00*Hz)\r\n'
'1-0:1.7.0(00.000*kW)\r\n'
'1-0:2.7.0(02.601*kW)\r\n'
'1-0:5.7.0(00.000*kvar)\r\n'
'1-0:6.7.0(00.000*kvar)\r\n'
'1-0:7.7.0(00.504*kvar)\r\n'
'1-0:8.7.0(00.000*kvar)\r\n'
'1-0:31.4.0(200.00*A)\r\n'
'1-0:51.4.0(200.00*A)\r\n'
'1-0:71.4.0(200.00*A)\r\n'
'0-0:98.1.0(230701000000S)(000040.777*kWh)(000008.950*kWh)(000031.827*kWh)(000142.250*kWh)(000111.164*kWh)(000031.086*kWh)(000000.030*kvarh)(000073.988*kvarh)(000000.205*kvarh)(000000.048*kvarh)(000039.199*kvarh)(000035.020*kvarh)(000183.027*kWh)(03.564*kW)(02.156*kW)(03.564*kW)(04.104*kW)(04.104*kW)(03.400*kW)\r\n'
'0-0:96.13.0()\r\n'
'!99DA\r\n'
)
+7
View File
@@ -0,0 +1,7 @@
from dsmr_parser import telegram_specifications
from dsmr_parser.parsers import TelegramParser
from test.example_telegrams import TELEGRAM_V4_2
parser = TelegramParser(telegram_specifications.V4)
telegram = parser.parse(TELEGRAM_V4_2)
print(telegram)
View File
+73
View File
@@ -0,0 +1,73 @@
from decimal import Decimal
import json
import unittest
from dsmr_parser import telegram_specifications, obis_references
from dsmr_parser.objects import MbusDevice
class MbusDeviceTest(unittest.TestCase):
def setUp(self):
v5_objects = telegram_specifications.V5['objects']
device_type_parser = [
object["value_parser"]
for object in v5_objects
if object["obis_reference"] == obis_references.MBUS_DEVICE_TYPE
][0]
device_type = device_type_parser.parse('0-2:24.1.0(003)\r\n')
equipment_parser = [
object["value_parser"]
for object in v5_objects
if object["obis_reference"] == obis_references.MBUS_EQUIPMENT_IDENTIFIER
][0]
equipment = equipment_parser.parse('0-2:96.1.0(4730303339303031393336393930363139)\r\n')
gas_reading_parser = [
object["value_parser"]
for object in v5_objects
if object["obis_reference"] == obis_references.MBUS_METER_READING
][0]
gas_reading = gas_reading_parser.parse('0-2:24.2.1(200426223001S)(00246.138*m3)\r\n')
mbus_device = MbusDevice(channel_id=2)
mbus_device.add(obis_references.MBUS_DEVICE_TYPE, device_type, "MBUS_DEVICE_TYPE")
mbus_device.add(obis_references.MBUS_EQUIPMENT_IDENTIFIER, equipment, "MBUS_EQUIPMENT_IDENTIFIER")
mbus_device.add(obis_references.MBUS_METER_READING, gas_reading, "MBUS_METER_READING")
self.mbus_device = mbus_device
def test_attributes(self):
self.assertEqual(self.mbus_device.MBUS_DEVICE_TYPE.value, 3)
self.assertEqual(self.mbus_device.MBUS_DEVICE_TYPE.unit, None)
self.assertEqual(self.mbus_device.MBUS_EQUIPMENT_IDENTIFIER.value,
'4730303339303031393336393930363139')
self.assertEqual(self.mbus_device.MBUS_EQUIPMENT_IDENTIFIER.unit, None)
self.assertEqual(self.mbus_device.MBUS_METER_READING.value, Decimal('246.138'))
self.assertEqual(self.mbus_device.MBUS_METER_READING.unit, 'm3')
def test_to_json(self):
self.assertEqual(
json.loads(self.mbus_device.to_json()),
{
'CHANNEL_ID': 2,
'MBUS_DEVICE_TYPE': {'value': 3, 'unit': None},
'MBUS_EQUIPMENT_IDENTIFIER': {'value': '4730303339303031393336393930363139', 'unit': None},
'MBUS_METER_READING': {'datetime': '2020-04-26T20:30:01+00:00', 'value': 246.138, 'unit': 'm3'}}
)
def test_str(self):
self.assertEqual(
str(self.mbus_device),
(
'MBUS DEVICE (channel 2)\n'
'\tMBUS_DEVICE_TYPE: 3 [None]\n'
'\tMBUS_EQUIPMENT_IDENTIFIER: 4730303339303031393336393930363139 [None]\n'
'\tMBUS_METER_READING: 246.138 [m3] at 2020-04-26T20:30:01+00:00\n'
)
)
+88
View File
@@ -0,0 +1,88 @@
import unittest
from dsmr_parser import telegram_specifications
from dsmr_parser.objects import ProfileGenericObject
from dsmr_parser.parsers import TelegramParser
from dsmr_parser.parsers import ProfileGenericParser
from dsmr_parser.profile_generic_specifications import BUFFER_TYPES
from dsmr_parser.profile_generic_specifications import PG_HEAD_PARSERS
from dsmr_parser.profile_generic_specifications import PG_UNIDENTIFIED_BUFFERTYPE_PARSERS
from test.example_telegrams import TELEGRAM_V5
class TestParserCornerCases(unittest.TestCase):
""" Test instantiation of Telegram object """
def test_power_event_log_empty_1(self):
# POWER_EVENT_FAILURE_LOG (1-0:99.97.0)
parser = TelegramParser(telegram_specifications.V5)
telegram = parser.parse(TELEGRAM_V5)
object_type = ProfileGenericObject
testitem = telegram.POWER_EVENT_FAILURE_LOG
assert isinstance(testitem, object_type)
assert testitem.buffer_length == 0
assert testitem.buffer_type == '0-0:96.7.19'
buffer = testitem.buffer
assert isinstance(testitem.buffer, list)
assert len(buffer) == 0
def test_power_event_log_empty_2(self):
pef_parser = ProfileGenericParser(BUFFER_TYPES, PG_HEAD_PARSERS, PG_UNIDENTIFIED_BUFFERTYPE_PARSERS)
object_type = ProfileGenericObject
# Power Event Log with 0 items and no object type
pefl_line = r'1-0:99.97.0(0)()\r\n'
testitem = pef_parser.parse(pefl_line)
assert isinstance(testitem, object_type)
assert testitem.buffer_length == 0
assert testitem.buffer_type is None
buffer = testitem.buffer
assert isinstance(testitem.buffer, list)
assert len(buffer) == 0
assert testitem.values == [{'value': 0, 'unit': None}, {'value': None, 'unit': None}]
json = testitem.to_json()
assert json == '{"buffer_length": 0, "buffer_type": null, "buffer": []}'
def test_power_event_log_null_values(self):
pef_parser = ProfileGenericParser(BUFFER_TYPES, PG_HEAD_PARSERS, PG_UNIDENTIFIED_BUFFERTYPE_PARSERS)
object_type = ProfileGenericObject
# Power Event Log with 1 item and no object type and nno values for the item
pefl_line = r'1-0:99.97.0(1)()()()\r\n'
testitem = pef_parser.parse(pefl_line)
assert isinstance(testitem, object_type)
assert testitem.buffer_length == 1
assert testitem.buffer_type is None
buffer = testitem.buffer
assert isinstance(testitem.buffer, list)
assert len(buffer) == 1
assert testitem.values == [{'value': 1, 'unit': None}, {'value': None, 'unit': None},
{'value': None, 'unit': None}, {'value': None, 'unit': None}]
json = testitem.to_json()
assert json == \
'{"buffer_length": 1, "buffer_type": null, "buffer": [{"datetime": null, "value": null, "unit": null}]}'
def test_power_event_log_brackets_only(self):
# POWER_EVENT_FAILURE_LOG (1-0:99.97.0)
# Issue 57
# Test of an ill formatted empty POWER_EVENT_FAILURE_LOG, observed on some smartmeters
# The idea is that instead of failing, the parser converts it to an empty POWER_EVENT_FAILURE_LOG
pef_parser = ProfileGenericParser(BUFFER_TYPES, PG_HEAD_PARSERS, PG_UNIDENTIFIED_BUFFERTYPE_PARSERS)
object_type = ProfileGenericObject
pefl_line = r'1-0:99.97.0()\r\n'
testitem = pef_parser.parse(pefl_line)
assert isinstance(testitem, object_type)
assert testitem.buffer_length == 0
assert testitem.buffer_type is None
buffer = testitem.buffer
assert isinstance(testitem.buffer, list)
assert len(buffer) == 0
assert testitem.values == [{'value': 0, 'unit': None}, {'value': None, 'unit': None}]
json = testitem.to_json()
assert json == '{"buffer_length": 0, "buffer_type": null, "buffer": []}'
+486
View File
@@ -0,0 +1,486 @@
import json
import unittest
import datetime
import pytz
from dsmr_parser import telegram_specifications, obis_references
from dsmr_parser.objects import CosemObject
from dsmr_parser.objects import MBusObject
from dsmr_parser.objects import ProfileGenericObject
from dsmr_parser.parsers import TelegramParser
from test.example_telegrams import TELEGRAM_V4_2, TELEGRAM_V5_TWO_MBUS, TELEGRAM_V5
from decimal import Decimal
class TelegramTest(unittest.TestCase):
""" Test instantiation of Telegram object """
def __init__(self, *args, **kwargs):
self.item_names_tested = []
super(TelegramTest, self).__init__(*args, **kwargs)
def verify_telegram_item(self, telegram, testitem_name, object_type, unit_val, value_type, value_val):
testitem = eval("telegram.{}".format(testitem_name))
assert isinstance(testitem, object_type)
assert testitem.unit == unit_val
assert isinstance(testitem.value, value_type)
assert testitem.value == value_val
self.item_names_tested.append(testitem_name)
def test_instantiate(self):
parser = TelegramParser(telegram_specifications.V4)
telegram = parser.parse(TELEGRAM_V4_2)
# P1_MESSAGE_HEADER (1-3:0.2.8)
self.verify_telegram_item(telegram,
'P1_MESSAGE_HEADER',
object_type=CosemObject,
unit_val=None,
value_type=str,
value_val='42')
# P1_MESSAGE_TIMESTAMP (0-0:1.0.0)
self.verify_telegram_item(telegram,
'P1_MESSAGE_TIMESTAMP',
CosemObject,
unit_val=None,
value_type=datetime.datetime,
value_val=datetime.datetime(2016, 11, 13, 19, 57, 57, tzinfo=pytz.UTC))
# ELECTRICITY_USED_TARIFF_1 (1-0:1.8.1)
self.verify_telegram_item(telegram,
'ELECTRICITY_USED_TARIFF_1',
object_type=CosemObject,
unit_val='kWh',
value_type=Decimal,
value_val=Decimal('1581.123'))
# ELECTRICITY_USED_TARIFF_2 (1-0:1.8.2)
self.verify_telegram_item(telegram,
'ELECTRICITY_USED_TARIFF_2',
object_type=CosemObject,
unit_val='kWh',
value_type=Decimal,
value_val=Decimal('1435.706'))
# ELECTRICITY_DELIVERED_TARIFF_1 (1-0:2.8.1)
self.verify_telegram_item(telegram,
'ELECTRICITY_DELIVERED_TARIFF_1',
object_type=CosemObject,
unit_val='kWh',
value_type=Decimal,
value_val=Decimal('0'))
# ELECTRICITY_DELIVERED_TARIFF_2 (1-0:2.8.2)
self.verify_telegram_item(telegram,
'ELECTRICITY_DELIVERED_TARIFF_2',
object_type=CosemObject,
unit_val='kWh',
value_type=Decimal,
value_val=Decimal('0'))
# ELECTRICITY_ACTIVE_TARIFF (0-0:96.14.0)
self.verify_telegram_item(telegram,
'ELECTRICITY_ACTIVE_TARIFF',
object_type=CosemObject,
unit_val=None,
value_type=str,
value_val='0002')
# EQUIPMENT_IDENTIFIER (0-0:96.1.1)
self.verify_telegram_item(telegram,
'EQUIPMENT_IDENTIFIER',
object_type=CosemObject,
unit_val=None,
value_type=str,
value_val='3960221976967177082151037881335713')
# CURRENT_ELECTRICITY_USAGE (1-0:1.7.0)
self.verify_telegram_item(telegram,
'CURRENT_ELECTRICITY_USAGE',
object_type=CosemObject,
unit_val='kW',
value_type=Decimal,
value_val=Decimal('2.027'))
# CURRENT_ELECTRICITY_DELIVERY (1-0:2.7.0)
self.verify_telegram_item(telegram,
'CURRENT_ELECTRICITY_DELIVERY',
object_type=CosemObject,
unit_val='kW',
value_type=Decimal,
value_val=Decimal('0'))
# SHORT_POWER_FAILURE_COUNT (1-0:96.7.21)
self.verify_telegram_item(telegram,
'SHORT_POWER_FAILURE_COUNT',
object_type=CosemObject,
unit_val=None,
value_type=int,
value_val=15)
# LONG_POWER_FAILURE_COUNT (96.7.9)
self.verify_telegram_item(telegram,
'LONG_POWER_FAILURE_COUNT',
object_type=CosemObject,
unit_val=None,
value_type=int,
value_val=7)
# VOLTAGE_SAG_L1_COUNT (1-0:32.32.0)
self.verify_telegram_item(telegram,
'VOLTAGE_SAG_L1_COUNT',
object_type=CosemObject,
unit_val=None,
value_type=int,
value_val=0)
# VOLTAGE_SAG_L2_COUNT (1-0:52.32.0)
self.verify_telegram_item(telegram,
'VOLTAGE_SAG_L2_COUNT',
object_type=CosemObject,
unit_val=None,
value_type=int,
value_val=0)
# VOLTAGE_SAG_L3_COUNT (1-0:72.32.0)
self.verify_telegram_item(telegram,
'VOLTAGE_SAG_L3_COUNT',
object_type=CosemObject,
unit_val=None,
value_type=int,
value_val=0)
# VOLTAGE_SWELL_L1_COUNT (1-0:32.36.0)
self.verify_telegram_item(telegram,
'VOLTAGE_SWELL_L1_COUNT',
object_type=CosemObject,
unit_val=None,
value_type=int,
value_val=0)
# VOLTAGE_SWELL_L2_COUNT (1-0:52.36.0)
self.verify_telegram_item(telegram,
'VOLTAGE_SWELL_L2_COUNT',
object_type=CosemObject,
unit_val=None,
value_type=int,
value_val=0)
# VOLTAGE_SWELL_L3_COUNT (1-0:72.36.0)
self.verify_telegram_item(telegram,
'VOLTAGE_SWELL_L3_COUNT',
object_type=CosemObject,
unit_val=None,
value_type=int,
value_val=0)
# TEXT_MESSAGE_CODE (0-0:96.13.1)
self.verify_telegram_item(telegram,
'TEXT_MESSAGE_CODE',
object_type=CosemObject,
unit_val=None,
value_type=type(None),
value_val=None)
# TEXT_MESSAGE (0-0:96.13.0)
self.verify_telegram_item(telegram,
'TEXT_MESSAGE',
object_type=CosemObject,
unit_val=None,
value_type=type(None),
value_val=None)
# INSTANTANEOUS_CURRENT_L1 (1-0:31.7.0)
self.verify_telegram_item(telegram,
'INSTANTANEOUS_CURRENT_L1',
object_type=CosemObject,
unit_val='A',
value_type=Decimal,
value_val=Decimal('0'))
# INSTANTANEOUS_CURRENT_L2 (1-0:51.7.0)
self.verify_telegram_item(telegram,
'INSTANTANEOUS_CURRENT_L2',
object_type=CosemObject,
unit_val='A',
value_type=Decimal,
value_val=Decimal('6'))
# INSTANTANEOUS_CURRENT_L3 (1-0:71.7.0)
self.verify_telegram_item(telegram,
'INSTANTANEOUS_CURRENT_L3',
object_type=CosemObject,
unit_val='A',
value_type=Decimal,
value_val=Decimal('2'))
# INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE (1-0:21.7.0)
self.verify_telegram_item(telegram,
'INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE',
object_type=CosemObject,
unit_val='kW',
value_type=Decimal,
value_val=Decimal('0.170'))
# INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE (1-0:41.7.0)
self.verify_telegram_item(telegram,
'INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE',
object_type=CosemObject,
unit_val='kW',
value_type=Decimal,
value_val=Decimal('1.247'))
# INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE (1-0:61.7.0)
self.verify_telegram_item(telegram,
'INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE',
object_type=CosemObject,
unit_val='kW',
value_type=Decimal,
value_val=Decimal('0.209'))
# INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE (1-0:22.7.0)
self.verify_telegram_item(telegram,
'INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE',
object_type=CosemObject,
unit_val='kW',
value_type=Decimal,
value_val=Decimal('0'))
# INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE (1-0:42.7.0)
self.verify_telegram_item(telegram,
'INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE',
object_type=CosemObject,
unit_val='kW',
value_type=Decimal,
value_val=Decimal('0'))
# INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE (1-0:62.7.0)
self.verify_telegram_item(telegram,
'INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE',
object_type=CosemObject,
unit_val='kW',
value_type=Decimal,
value_val=Decimal('0'))
# DEVICE_TYPE (0-1:24.1.0)
self.verify_telegram_item(telegram,
'DEVICE_TYPE',
object_type=CosemObject,
unit_val=None,
value_type=int,
value_val=3)
# EQUIPMENT_IDENTIFIER_GAS (0-1:96.1.0)
self.verify_telegram_item(telegram,
'EQUIPMENT_IDENTIFIER_GAS',
object_type=CosemObject,
unit_val=None,
value_type=str,
value_val='4819243993373755377509728609491464')
# HOURLY_GAS_METER_READING (0-1:24.2.1)
self.verify_telegram_item(telegram,
'HOURLY_GAS_METER_READING',
object_type=MBusObject,
unit_val='m3',
value_type=Decimal,
value_val=Decimal('981.443'))
# POWER_EVENT_FAILURE_LOG (1-0:99.97.0)
testitem_name = 'POWER_EVENT_FAILURE_LOG'
object_type = ProfileGenericObject
testitem = eval("telegram.{}".format(testitem_name))
assert isinstance(testitem, object_type)
assert testitem.buffer_length == 3
assert testitem.buffer_type == '0-0:96.7.19'
buffer = testitem.buffer
assert isinstance(testitem.buffer, list)
assert len(buffer) == 3
assert all([isinstance(item, MBusObject) for item in buffer])
date0 = datetime.datetime(2000, 1, 4, 17, 3, 20, tzinfo=datetime.timezone.utc)
date1 = datetime.datetime(1999, 12, 31, 23, 0, 1, tzinfo=datetime.timezone.utc)
date2 = datetime.datetime(2000, 1, 1, 23, 0, 3, tzinfo=datetime.timezone.utc)
assert buffer[0].datetime == date0
assert buffer[1].datetime == date1
assert buffer[2].datetime == date2
assert buffer[0].value == 237126
assert buffer[1].value == 2147583646
assert buffer[2].value == 2317482647
assert all([isinstance(item.value, int) for item in buffer])
assert all([isinstance(item.unit, str) for item in buffer])
assert all([(item.unit == 's') for item in buffer])
self.item_names_tested.append(testitem_name)
# check if all items in telegram V4 specification are covered
V4_name_list = [object["value_name"] for object in
telegram_specifications.V4['objects']]
V4_name_set = set(V4_name_list)
item_names_tested_set = set(self.item_names_tested)
assert item_names_tested_set == V4_name_set
def test_iter(self):
parser = TelegramParser(telegram_specifications.V5)
telegram = parser.parse(TELEGRAM_V5)
for obis_name, dsmr_object in telegram:
break
# Verify that the iterator works for at least one value
self.assertEqual(obis_name, "P1_MESSAGE_HEADER")
self.assertEqual(dsmr_object.value, '50')
def test_mbus_devices(self):
parser = TelegramParser(telegram_specifications.V5)
telegram = parser.parse(TELEGRAM_V5_TWO_MBUS)
mbus_devices = telegram.MBUS_DEVICES
self.assertEqual(len(mbus_devices), 2)
mbus_device_1 = mbus_devices[0]
self.assertEqual(mbus_device_1.MBUS_DEVICE_TYPE.value, 3)
self.assertEqual(mbus_device_1.MBUS_EQUIPMENT_IDENTIFIER.value, None)
self.assertEqual(mbus_device_1.MBUS_METER_READING.value, Decimal('0'))
mbus_device_2 = mbus_devices[1]
self.assertEqual(mbus_device_2.MBUS_DEVICE_TYPE.value, 3)
self.assertEqual(mbus_device_2.MBUS_EQUIPMENT_IDENTIFIER.value, '4730303339303031393336393930363139')
self.assertEqual(mbus_device_2.MBUS_METER_READING.value, Decimal('246.138'))
def test_get_mbus_device_by_channel(self):
parser = TelegramParser(telegram_specifications.V5)
telegram = parser.parse(TELEGRAM_V5_TWO_MBUS)
mbus_device_1 = telegram.get_mbus_device_by_channel(1)
self.assertEqual(mbus_device_1.MBUS_DEVICE_TYPE.value, 3)
self.assertEqual(mbus_device_1.MBUS_EQUIPMENT_IDENTIFIER.value, None)
self.assertEqual(mbus_device_1.MBUS_METER_READING.value, Decimal('0'))
mbus_device_2 = telegram.get_mbus_device_by_channel(2)
self.assertEqual(mbus_device_2.MBUS_DEVICE_TYPE.value, 3)
self.assertEqual(mbus_device_2.MBUS_EQUIPMENT_IDENTIFIER.value, '4730303339303031393336393930363139')
self.assertEqual(mbus_device_2.MBUS_METER_READING.value, Decimal('246.138'))
def test_without_mbus_devices(self):
parser = TelegramParser(telegram_specifications.V5, apply_checksum_validation=False)
telegram = parser.parse('')
self.assertFalse(hasattr(telegram, 'MBUS_DEVICES'))
self.assertIsNone(telegram.get_mbus_device_by_channel(1))
def test_to_json(self):
parser = TelegramParser(telegram_specifications.V5)
telegram = parser.parse(TELEGRAM_V5)
json_data = json.loads(telegram.to_json())
self.maxDiff = None
self.assertEqual(
json_data,
{'CURRENT_ELECTRICITY_DELIVERY': {'unit': 'kW', 'value': 0.0},
'CURRENT_ELECTRICITY_USAGE': {'unit': 'kW', 'value': 0.244},
'ELECTRICITY_ACTIVE_TARIFF': {'unit': None, 'value': '0002'},
'ELECTRICITY_DELIVERED_TARIFF_1': {'unit': 'kWh', 'value': 2.444},
'ELECTRICITY_DELIVERED_TARIFF_2': {'unit': 'kWh', 'value': 0.0},
'ELECTRICITY_USED_TARIFF_1': {'unit': 'kWh', 'value': 4.426},
'ELECTRICITY_USED_TARIFF_2': {'unit': 'kWh', 'value': 2.399},
'EQUIPMENT_IDENTIFIER': {'unit': None,
'value': '4B384547303034303436333935353037'},
'INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE': {'unit': 'kW', 'value': 0.0},
'INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE': {'unit': 'kW', 'value': 0.07},
'INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE': {'unit': 'kW', 'value': 0.0},
'INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE': {'unit': 'kW', 'value': 0.032},
'INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE': {'unit': 'kW', 'value': 0.0},
'INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE': {'unit': 'kW', 'value': 0.142},
'INSTANTANEOUS_CURRENT_L1': {'unit': 'A', 'value': 0.48},
'INSTANTANEOUS_CURRENT_L2': {'unit': 'A', 'value': 0.44},
'INSTANTANEOUS_CURRENT_L3': {'unit': 'A', 'value': 0.86},
'INSTANTANEOUS_VOLTAGE_L1': {'unit': 'V', 'value': 230.0},
'INSTANTANEOUS_VOLTAGE_L2': {'unit': 'V', 'value': 230.0},
'INSTANTANEOUS_VOLTAGE_L3': {'unit': 'V', 'value': 229.0},
'LONG_POWER_FAILURE_COUNT': {'unit': None, 'value': 0},
'MBUS_DEVICES': [{'CHANNEL_ID': 1,
'MBUS_DEVICE_TYPE': {'unit': None, 'value': 3},
'MBUS_EQUIPMENT_IDENTIFIER': {'unit': None,
'value': '3232323241424344313233343536373839'},
'MBUS_METER_READING': {'datetime': '2017-01-02T15:10:05+00:00',
'unit': 'm3',
'value': 0.107}},
{'CHANNEL_ID': 2,
'MBUS_DEVICE_TYPE': {'unit': None, 'value': 3},
'MBUS_EQUIPMENT_IDENTIFIER': {'unit': None,
'value': None}}],
'P1_MESSAGE_HEADER': {'unit': None, 'value': '50'},
'P1_MESSAGE_TIMESTAMP': {'unit': None, 'value': '2017-01-02T18:20:02+00:00'},
'POWER_EVENT_FAILURE_LOG': {'buffer': [],
'buffer_length': 0,
'buffer_type': '0-0:96.7.19'},
'SHORT_POWER_FAILURE_COUNT': {'unit': None, 'value': 13},
'TEXT_MESSAGE': {'unit': None, 'value': None},
'VOLTAGE_SAG_L1_COUNT': {'unit': None, 'value': 0},
'VOLTAGE_SAG_L2_COUNT': {'unit': None, 'value': 0},
'VOLTAGE_SAG_L3_COUNT': {'unit': None, 'value': 0},
'VOLTAGE_SWELL_L1_COUNT': {'unit': None, 'value': 0},
'VOLTAGE_SWELL_L2_COUNT': {'unit': None, 'value': 0},
'VOLTAGE_SWELL_L3_COUNT': {'unit': None, 'value': 0}}
)
def test_to_str(self):
parser = TelegramParser(telegram_specifications.V5)
telegram = parser.parse(TELEGRAM_V5)
self.maxDiff = None
self.assertEqual(
str(telegram),
(
'P1_MESSAGE_HEADER: 50 [None]\n'
'P1_MESSAGE_TIMESTAMP: 2017-01-02T18:20:02+00:00 [None]\n'
'EQUIPMENT_IDENTIFIER: 4B384547303034303436333935353037 [None]\n'
'ELECTRICITY_USED_TARIFF_1: 4.426 [kWh]\n'
'ELECTRICITY_USED_TARIFF_2: 2.399 [kWh]\n'
'ELECTRICITY_DELIVERED_TARIFF_1: 2.444 [kWh]\n'
'ELECTRICITY_DELIVERED_TARIFF_2: 0.000 [kWh]\n'
'ELECTRICITY_ACTIVE_TARIFF: 0002 [None]\n'
'CURRENT_ELECTRICITY_USAGE: 0.244 [kW]\n'
'CURRENT_ELECTRICITY_DELIVERY: 0.000 [kW]\n'
'LONG_POWER_FAILURE_COUNT: 0 [None]\n'
'SHORT_POWER_FAILURE_COUNT: 13 [None]\n'
'POWER_EVENT_FAILURE_LOG: buffer length: 0\n'
' buffer type: 0-0:96.7.19\n'
'VOLTAGE_SAG_L1_COUNT: 0 [None]\n'
'VOLTAGE_SAG_L2_COUNT: 0 [None]\n'
'VOLTAGE_SAG_L3_COUNT: 0 [None]\n'
'VOLTAGE_SWELL_L1_COUNT: 0 [None]\n'
'VOLTAGE_SWELL_L2_COUNT: 0 [None]\n'
'VOLTAGE_SWELL_L3_COUNT: 0 [None]\n'
'INSTANTANEOUS_VOLTAGE_L1: 230.0 [V]\n'
'INSTANTANEOUS_VOLTAGE_L2: 230.0 [V]\n'
'INSTANTANEOUS_VOLTAGE_L3: 229.0 [V]\n'
'INSTANTANEOUS_CURRENT_L1: 0.48 [A]\n'
'INSTANTANEOUS_CURRENT_L2: 0.44 [A]\n'
'INSTANTANEOUS_CURRENT_L3: 0.86 [A]\n'
'TEXT_MESSAGE: None [None]\n'
'INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE: 0.070 [kW]\n'
'INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE: 0.032 [kW]\n'
'INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE: 0.142 [kW]\n'
'INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE: 0.000 [kW]\n'
'INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE: 0.000 [kW]\n'
'INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE: 0.000 [kW]\n'
'MBUS DEVICE (channel 1)\n'
' MBUS_DEVICE_TYPE: 3 [None]\n'
' MBUS_EQUIPMENT_IDENTIFIER: 3232323241424344313233343536373839 [None]\n'
' MBUS_METER_READING: 0.107 [m3] at 2017-01-02T15:10:05+00:00\n'
'MBUS DEVICE (channel 2)\n'
' MBUS_DEVICE_TYPE: 3 [None]\n'
' MBUS_EQUIPMENT_IDENTIFIER: None [None]\n'
)
)
def test_getitem(self):
parser = TelegramParser(telegram_specifications.V5)
telegram = parser.parse(TELEGRAM_V5)
self.assertEqual(telegram[obis_references.P1_MESSAGE_HEADER].value, '50')
+21
View File
@@ -0,0 +1,21 @@
import unittest
import tempfile
from dsmr_parser.clients.filereader import FileReader
from dsmr_parser.telegram_specifications import V5
from test.example_telegrams import TELEGRAM_V5
class FileReaderTest(unittest.TestCase):
def test_read_as_object(self):
with tempfile.NamedTemporaryFile() as file:
with open(file.name, "w") as f:
f.write(TELEGRAM_V5)
telegrams = []
reader = FileReader(file=file.name, telegram_specification=V5)
# Call
for telegram in reader.read_as_object():
telegrams.append(telegram)
self.assertEqual(len(telegrams), 1)

Some files were not shown because too many files have changed in this diff Show More