diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 581c60dd2..64d96cf2d 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,5 +1,3 @@ -*Let op: Het DSMR-reader project is gefocused op het uitlezen van de slimme meter en om die data met jou op zoveel mogelijk manieren te delen. Het inlezen van andere (externe) data in DSMR-reader valt buiten de scope van dit project.* - ### Jouw omgeving *(Indien van toepassing)* * DSMR-reader versie *(rechtsbovenin applicatie zichtbaar)*: `v1.X.Y` diff --git a/.travis.yml b/.travis.yml index 59c465045..6afee4765 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,23 +4,21 @@ language: python python: - 3.4 - 3.5 - - 3.5-dev - 3.6 + - 3.5-dev - 3.6-dev - 3.7-dev - + matrix: allow_failures: - # Look ahead. - python: 3.5-dev - python: 3.6-dev - - python: 3.7-dev - python: nightly - + services: - postgresql - mysql - + addons: # https://docs.travis-ci.com/user/database-setup/#PostgreSQL postgresql: "9.4" @@ -50,7 +48,9 @@ before_script: - sh -c "if [ '$DB' = 'mysql' ]; then mysql -e \"GRANT ALL PRIVILEGES ON test_dsmrreader.* TO 'travis'@'%';\" -u root; fi" script: - - py.test --pylama --cov --cov-report=term - + - py.test --cov --cov-report=term + # Only perform code audit when the package was installed. + - sh -c "if [ `command -v pylama` ] ; then pylama; else echo 'SKIPPED pylama code audit' ; fi" + after_success: - codecov diff --git a/README.md b/README.md index 297bc62c1..a13df7871 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,9 @@ [![Coverage](https://codecov.io/github/dennissiemensma/dsmr-reader/coverage.svg?branch=master)](https://codecov.io/gh/dennissiemensma/dsmr-reader/branch/master) [![Documentation Status](https://readthedocs.org/projects/dsmr-reader/badge/?version=latest)](https://dsmr-reader.readthedocs.io/en/latest/?badge=latest) [![Requirements Status](https://requires.io/github/dennissiemensma/dsmr-reader/requirements.svg?branch=master)](https://requires.io/github/dennissiemensma/dsmr-reader/requirements/?branch=master) -[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://dsmr-reader.readthedocs.io/en/latest/donations.html) +[![Donate](https://img.shields.io/badge/Donate-PayPal-brightgreen.svg)](https://dsmr-reader.readthedocs.io/en/latest/donations.html) +[![Python 3.5 & 3.6](https://img.shields.io/badge/python%203.5%20%26%203.6-supported-brightgreen.svg)](https://dsmr-reader.readthedocs.io/en/latest/requirements.html#python) +[![Python 3.4](https://img.shields.io/badge/python%203.4-support%20ended-red.svg)](https://dsmr-reader.readthedocs.io/en/latest/requirements.html#python) # DSMR Reader Datalogger/GUI @@ -17,6 +19,7 @@ - **Always free to use** (for non-commercial use only). - **Keeping your data for yourself** (none of the data is stored outside your database, unless you explicitly upload it to a third party). + ## Features - Application languages supported: **Dutch (Nederlands)** and **English**. The language used in your browser dynamicly decides the language applied in the application. - Optional Support for **exporting data to third-parties**: MinderGas.nl, PVOutput.org. diff --git a/docs/api.rst b/docs/api.rst index d0a11d9b7..bff8423c8 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -201,7 +201,7 @@ Client file in ``/home/dsmr/dsmr_datalogger_api_client.py``:: except SerialException as error: # Something else and unexpected failed. print('Serial connection failed:', error) - raise StopIteration() # Break out of yield. + return # Break out of yield. try: # Make sure weird characters are converted properly. diff --git a/docs/changelog.rst b/docs/changelog.rst index a63eae574..b2c7d481a 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -2,10 +2,6 @@ Changelog ========= -.. contents:: - :depth: 2 - - Upgrading ^^^^^^^^^ @@ -17,6 +13,50 @@ Please make sure you have a fresh **database backup** before upgrading! Upgradin - `About upgrading `_. +---- + + +v1.24.0 - 2018-09-29 +^^^^^^^^^^^^^^^^^^^^ + +.. warning:: + + The default logging level of the backend has been lowered to reduce I/O. + See ``Logging`` on `this documentation page `_ for more information. + + +**Tickets resolved in this release:** + +- [`#494 `_] Extend Usage statistics to include return +- [`#467 `_] PVO uploadtijden in sync houden +- [`#513 `_] Data being ignored in telegram grouping +- [`#512 `_] Drop support for Python 3.4 +- [`#511 `_] Add support for Python 3.7 +- [`#526 `_] Logging refactoring + + +---- + + +v1.23.1 - 2018-08-26 +^^^^^^^^^^^^^^^^^^^^ + +**Tickets resolved in this release:** + +- [`#515 `_] Missing mqtt values + + +---- + + +v1.23.1 - 2018-08-26 +^^^^^^^^^^^^^^^^^^^^ + +**Tickets resolved in this release:** + +- [`#515 `_] Missing mqtt values + + v1.23.1 - 2018-08-26 ^^^^^^^^^^^^^^^^^^^^ @@ -48,6 +88,8 @@ v1.23.0 - 2018-08-02 - [`#499 `_] Upgrade Font Awesome to v5 +---- + v1.22.1 - 2018-07-22 ^^^^^^^^^^^^^^^^^^^^ @@ -57,6 +99,8 @@ v1.22.1 - 2018-07-22 - [`#506 `_] Fasen-grafiek hangt op 'loading' +---- + v1.22.0 - 2018-07-22 ^^^^^^^^^^^^^^^^^^^^ @@ -70,6 +114,8 @@ v1.22.0 - 2018-07-22 - [`#493 `_] Requirements update (July 2018) +---- + v1.21.1 - 2018-07-16 ^^^^^^^^^^^^^^^^^^^^ @@ -80,6 +126,8 @@ v1.21.1 - 2018-07-16 - [`#497 `_] Kleinigheidje: missende vertalingen +---- + v1.21.0 - 2018-07-11 ^^^^^^^^^^^^^^^^^^^^ @@ -91,6 +139,8 @@ v1.21.0 - 2018-07-11 - [`#264 `_] Check Dropbox API token and display error messages in GUI +---- + v1.20.0 - 2018-07-04 ^^^^^^^^^^^^^^^^^^^^ @@ -104,6 +154,8 @@ v1.20.0 - 2018-07-04 - [`#487 `_] Requirements update (July 2018) +---- + v1.19.0 - 2018-06-12 ^^^^^^^^^^^^^^^^^^^^ @@ -116,6 +168,8 @@ v1.19.0 - 2018-06-12 - [`#462 `_] Get live usage trough API +---- + v1.18.0 - 2018-06-05 ^^^^^^^^^^^^^^^^^^^^ @@ -127,6 +181,8 @@ v1.18.0 - 2018-06-05 - [`#480 `_] Requirements update (June 2018) +---- + v1.17.0 - 2018-05-25 ^^^^^^^^^^^^^^^^^^^^ @@ -137,6 +193,8 @@ v1.17.0 - 2018-05-25 - [`#471 `_] Requirements update (May 2018) +---- + v1.16.0 - 2018-04-04 ^^^^^^^^^^^^^^^^^^^^ @@ -149,6 +207,8 @@ v1.16.0 - 2018-04-04 - Fixed some missing names on the contribution page in the DOCS +---- + v1.15.0 - 2018-03-21 ^^^^^^^^^^^^^^^^^^^^ @@ -160,6 +220,8 @@ v1.15.0 - 2018-03-21 - [`#342 `_] Backup to dropbox never finish (free plan no more space) +---- + v1.14.0 - 2018-03-11 ^^^^^^^^^^^^^^^^^^^^ @@ -175,6 +237,8 @@ v1.14.0 - 2018-03-11 - [`#447 `_] Kosten via MQTT +---- + v1.13.2 - 2018-02-02 ^^^^^^^^^^^^^^^^^^^^ @@ -184,6 +248,8 @@ v1.13.2 - 2018-02-02 - [`#431 `_] Django security releases issued: 2.0.2 +---- + v1.13.1 - 2018-01-28 ^^^^^^^^^^^^^^^^^^^^ @@ -193,6 +259,8 @@ v1.13.1 - 2018-01-28 - [`#428 `_] Django 2.0: Null characters are not allowed in telegram (esp8266) +---- + v1.13.0 - 2018-01-23 ^^^^^^^^^^^^^^^^^^^^ @@ -206,6 +274,8 @@ v1.13.0 - 2018-01-23 - [`#427 `_] Reconnect to postgresql - [`#394 `_] Django 2.0 +---- + v1.12.0 - 2018-01-14 ^^^^^^^^^^^^^^^^^^^^ @@ -223,6 +293,8 @@ v1.12.0 - 2018-01-14 - [`#419 `_] Requirements update (January 2018) +---- + v1.11.0 - 2017-11-24 ^^^^^^^^^^^^^^^^^^^^ @@ -238,6 +310,8 @@ v1.11.0 - 2017-11-24 - [`#378 `_] Processing of telegrams stalled +---- + v1.10.0 - 2017-10-19 ^^^^^^^^^^^^^^^^^^^^ @@ -248,6 +322,9 @@ v1.10.0 - 2017-10-19 If you wish to continue using this feature, add ``DSMRREADER_LOG_TELEGRAMS = True`` to your ``settings.py`` and reload the application. +---- + + **Tickets resolved in this release:** - [`#363 `_] Show electricity_merged in the Total row for current month - by helmo @@ -257,6 +334,8 @@ v1.10.0 - 2017-10-19 - [`#366 `_] Restructure docs +---- + v1.9.0 - 2017-10-08 ^^^^^^^^^^^^^^^^^^^ @@ -289,6 +368,8 @@ v1.9.0 - 2017-10-08 - [`#300 `_] Upgrade to Django 1.11 LTS +---- + v1.8.2 - 2017-08-12 ^^^^^^^^^^^^^^^^^^^ @@ -298,6 +379,8 @@ v1.8.2 - 2017-08-12 - [`#346 `_] Defer statistics page XHR +---- + v1.8.1 - 2017-07-04 ^^^^^^^^^^^^^^^^^^^ @@ -307,6 +390,8 @@ v1.8.1 - 2017-07-04 - [`#339 `_] Upgrade Dropbox-client to v8.x +---- + v1.8.0 - 2017-06-14 ^^^^^^^^^^^^^^^^^^^ @@ -318,6 +403,8 @@ v1.8.0 - 2017-06-14 - [`#299 `_] Support Python 3.6 +---- + v1.7.0 - 2017-05-04 ^^^^^^^^^^^^^^^^^^^ @@ -339,6 +426,8 @@ v1.7.0 - 2017-05-04 - [`#230 `_] Support for exporting data via API +---- + v1.6.2 - 2017-04-23 ^^^^^^^^^^^^^^^^^^^ @@ -349,6 +438,8 @@ v1.6.2 - 2017-04-23 - [`#303 `_] Archive page's default day sorting +---- + v1.6.1 - 2017-04-06 ^^^^^^^^^^^^^^^^^^^ @@ -358,6 +449,8 @@ v1.6.1 - 2017-04-06 - [`#298 `_] Update requirements (Django 1.10.7) +---- + v1.6.0 - 2017-03-18 ^^^^^^^^^^^^^^^^^^^ @@ -389,6 +482,8 @@ v1.6.0 - 2017-03-18 - [`#274 `_] Requirements update (March 2017). +---- + v1.5.5 - 2017-01-19 ^^^^^^^^^^^^^^^^^^^ @@ -398,6 +493,8 @@ v1.5.5 - 2017-01-19 - Remove readonly restriction for editing statistics in admin interface (`#242 `_). +---- + v1.5.4 - 2017-01-12 ^^^^^^^^^^^^^^^^^^^ @@ -408,6 +505,8 @@ v1.5.4 - 2017-01-12 - Fixed another bug in MinderGas API client implementation (`#228 `_). +---- + v1.5.5 - 2017-01-19 ^^^^^^^^^^^^^^^^^^^ @@ -417,6 +516,8 @@ v1.5.5 - 2017-01-19 - Remove readonly restriction for editing statistics in admin interface (`#242 `_). +---- + v1.5.4 - 2017-01-12 ^^^^^^^^^^^^^^^^^^^ @@ -427,6 +528,8 @@ v1.5.4 - 2017-01-12 - Fixed another bug in MinderGas API client implementation (`#228 `_). +---- + v1.5.3 - 2017-01-11 ^^^^^^^^^^^^^^^^^^^ @@ -436,6 +539,8 @@ v1.5.3 - 2017-01-11 - Improve MinderGas API client implementation (`#228 `_). +---- + v1.5.2 - 2017-01-09 ^^^^^^^^^^^^^^^^^^^ @@ -449,6 +554,8 @@ v1.5.2 - 2017-01-09 - Log errors occured to file (`#181 `_). +---- + v1.5.1 - 2017-01-04 ^^^^^^^^^^^^^^^^^^^ @@ -463,6 +570,8 @@ v1.5.1 - 2017-01-04 - Fix for issues `#200 `_ & `#217 `_, which is caused by omitting the switch to the VirtualEnv. This was not documented well enough in early versions of this project, causing failed upgrades. +---- + v1.5.0 - 2017-01-01 ^^^^^^^^^^^^^^^^^^^ @@ -498,6 +607,8 @@ v1.5.0 - 2017-01-01 - Improved backend process logging (`#184 `_). +---- + v1.4.1 - 2016-12-12 ^^^^^^^^^^^^^^^^^^^ @@ -508,6 +619,8 @@ v1.4.1 - 2016-12-12 - NoReverseMatch at / Reverse for 'docs' (`#175 `_). +---- + v1.4.0 - 2016-11-28 ^^^^^^^^^^^^^^^^^^^ @@ -527,6 +640,8 @@ v1.4.0 - 2016-11-28 - Capability based push notifications (`#165 `_). +---- + v1.3.2 - 2016-11-08 ^^^^^^^^^^^^^^^^^^^ @@ -535,6 +650,8 @@ v1.3.2 - 2016-11-08 - Requirements update (november 2016) (`#150 `_). +---- + v1.3.1 - 2016-08-16 ^^^^^^^^^^^^^^^^^^^ @@ -546,6 +663,8 @@ v1.3.1 - 2016-08-16 - Query performance improvements (`#149 `_). +---- + v1.3.0 - 2016-07-15 ^^^^^^^^^^^^^^^^^^^ @@ -558,6 +677,8 @@ v1.3.0 - 2016-07-15 - Installation wizard for first time use (`#139 `_). +---- + v1.2.0 - 2016-05-18 ^^^^^^^^^^^^^^^^^^^ @@ -583,6 +704,8 @@ v1.1.2 - 2016-05-01 - Trends page giving errors (when lacking data) (`#125 `_). +---- + v1.1.1 - 2016-04-27 ^^^^^^^^^^^^^^^^^^^ @@ -591,6 +714,8 @@ v1.1.1 - 2016-04-27 - Improve readme (`#124 `_). +---- + v1.1.0 - 2016-04-23 ^^^^^^^^^^^^^^^^^^^ @@ -605,6 +730,8 @@ v1.1.0 - 2016-04-23 - Support for Iskra meter (DSMR 2.x) (`#120 `_). +---- + v1.0.1 - 2016-04-07 ^^^^^^^^^^^^^^^^^^^ @@ -613,12 +740,16 @@ v1.0.1 - 2016-04-07 - Update licence to OSI compatible one (`#119 `_). +---- + v1.0.0 - 2016-04-07 ^^^^^^^^^^^^^^^^^^^ - First official stable release. +---- + [β] v0.1 (2015-10-29) to 0.16 (2016-04-06) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/locale/nl/LC_MESSAGES/changelog.mo b/docs/locale/nl/LC_MESSAGES/changelog.mo index 652805f09..82024ebf5 100644 Binary files a/docs/locale/nl/LC_MESSAGES/changelog.mo and b/docs/locale/nl/LC_MESSAGES/changelog.mo differ diff --git a/docs/locale/nl/LC_MESSAGES/changelog.po b/docs/locale/nl/LC_MESSAGES/changelog.po index 0dbeb361c..3ab49a399 100644 --- a/docs/locale/nl/LC_MESSAGES/changelog.po +++ b/docs/locale/nl/LC_MESSAGES/changelog.po @@ -8,17 +8,14 @@ msgid "" msgstr "" "Project-Id-Version: DSMR Reader v1.x\n" "Report-Msgid-Bugs-To: Dennis Siemensma \n" -"POT-Creation-Date: 2018-08-02 18:11+0200\n" -"PO-Revision-Date: 2018-08-02 18:12+0200\n" "Last-Translator: Dennis Siemensma \n" "Language: nl\n" "Language-Team: Dennis Siemensma \n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.5.1\n" -"X-Generator: Poedit 1.8.7.1\n" #: ../../changelog.rst:2 msgid "Changelog" @@ -43,586 +40,610 @@ msgstr "" #: ../../changelog.rst:16 msgid "" -"`About back-ups `_." +"`About back-ups `_." msgstr "" -"`Over back-ups `_." +"`Over back-ups `_." #: ../../changelog.rst:17 msgid "" -"`About upgrading `_." +"`About upgrading `_." msgstr "" -"`Over upgraden `_." +"`Over upgraden `_." #: ../../changelog.rst:22 -msgid "v1.23.0 - 2018-08-02" +msgid "v2.0.0 - 2018-xx-xx" msgstr "" #: ../../changelog.rst:26 -msgid "" -"Support for **MQTT** has been completely reworked in this release and " -"now **requires** a new ``dsmr_mqtt`` process in Supervisor. `Additional " -"steps for upgrading can be found here `_." -msgstr "" -"Ondersteuning voor **MQTT** is compleet op de schop gegaan in deze " -"release en vereist voortaan een nieuw ``dsmr_mqtt`` proces in " -"Supervisor. `De extra stappen voor upgraden kun je hier terugvinden " -"`_." - -#: ../../changelog.rst:30 ../../changelog.rst:46 ../../changelog.rst:55 -#: ../../changelog.rst:68 ../../changelog.rst:78 ../../changelog.rst:89 -#: ../../changelog.rst:102 ../../changelog.rst:114 ../../changelog.rst:125 -#: ../../changelog.rst:135 ../../changelog.rst:147 ../../changelog.rst:158 -#: ../../changelog.rst:173 ../../changelog.rst:182 ../../changelog.rst:191 -#: ../../changelog.rst:204 ../../changelog.rst:221 ../../changelog.rst:242 -#: ../../changelog.rst:261 ../../changelog.rst:287 ../../changelog.rst:296 -#: ../../changelog.rst:305 ../../changelog.rst:328 ../../changelog.rst:337 -#: ../../changelog.rst:347 ../../changelog.rst:369 ../../changelog.rst:387 -#: ../../changelog.rst:396 ../../changelog.rst:406 ../../changelog.rst:415 -#: ../../changelog.rst:425 ../../changelog.rst:434 ../../changelog.rst:452 -#: ../../changelog.rst:471 ../../changelog.rst:496 ../../changelog.rst:509 -#: ../../changelog.rst:524 ../../changelog.rst:532 ../../changelog.rst:543 -#: ../../changelog.rst:555 ../../changelog.rst:572 ../../changelog.rst:580 -#: ../../changelog.rst:588 ../../changelog.rst:602 +msgid "Support for Python ``3.4`` has been **dropped**!" +msgstr "" + +#: ../../changelog.rst:29 ../../changelog.rst:45 ../../changelog.rst:61 +#: ../../changelog.rst:70 ../../changelog.rst:83 ../../changelog.rst:93 +#: ../../changelog.rst:104 ../../changelog.rst:117 ../../changelog.rst:129 +#: ../../changelog.rst:140 ../../changelog.rst:150 ../../changelog.rst:162 +#: ../../changelog.rst:173 ../../changelog.rst:188 ../../changelog.rst:197 +#: ../../changelog.rst:206 ../../changelog.rst:219 ../../changelog.rst:236 +#: ../../changelog.rst:257 ../../changelog.rst:276 ../../changelog.rst:302 +#: ../../changelog.rst:311 ../../changelog.rst:320 ../../changelog.rst:343 +#: ../../changelog.rst:352 ../../changelog.rst:362 ../../changelog.rst:384 +#: ../../changelog.rst:402 ../../changelog.rst:411 ../../changelog.rst:421 +#: ../../changelog.rst:430 ../../changelog.rst:440 ../../changelog.rst:449 +#: ../../changelog.rst:467 ../../changelog.rst:486 ../../changelog.rst:511 +#: ../../changelog.rst:524 ../../changelog.rst:539 ../../changelog.rst:547 +#: ../../changelog.rst:558 ../../changelog.rst:570 ../../changelog.rst:587 +#: ../../changelog.rst:595 ../../changelog.rst:603 ../../changelog.rst:617 msgid "**Tickets resolved in this release:**" msgstr "**Tickets opgelost in deze release:**" +#: ../../changelog.rst:31 +msgid "" +"[`#511 `_] Add" +" support for Python 3.7" +msgstr "" + #: ../../changelog.rst:32 msgid "" +"[`#512 `_] " +"Drop support for Python 3.4" +msgstr "" + +#: ../../changelog.rst:37 +msgid "v1.23.0 - 2018-08-02" +msgstr "" + +#: ../../changelog.rst:41 +msgid "" +"Support for **MQTT** has been completely reworked in this release and now" +" **requires** a new ``dsmr_mqtt`` process in Supervisor. `Additional " +"steps for upgrading can be found here `_." +msgstr "" +"Ondersteuning voor **MQTT** is compleet op de schop gegaan in deze " +"release en vereist voortaan een nieuw ``dsmr_mqtt`` proces in Supervisor." +" `De extra stappen voor upgraden kun je hier terugvinden `_." + +#: ../../changelog.rst:47 +msgid "" "[`#509 `_] " "MQTT refactoring" msgstr "" -#: ../../changelog.rst:33 +#: ../../changelog.rst:48 msgid "" -"[`#417 `_] " -"--- MQTT does connect/publish/disconnect for EACH message - every second" +"[`#417 `_] ---" +" MQTT does connect/publish/disconnect for EACH message - every second" msgstr "" -#: ../../changelog.rst:34 +#: ../../changelog.rst:49 msgid "" -"[`#505 `_] " -"--- SSL/TLS support for MQTT" +"[`#505 `_] ---" +" SSL/TLS support for MQTT" msgstr "" -#: ../../changelog.rst:35 +#: ../../changelog.rst:50 msgid "" -"[`#481 `_] " -"--- Memory Leak in dsmr_datalogger / MQTT" +"[`#481 `_] ---" +" Memory Leak in dsmr_datalogger / MQTT" msgstr "" -#: ../../changelog.rst:36 +#: ../../changelog.rst:51 msgid "" "[`#463 `_] " "MQTT: Telegram als JSON, tijdzones" msgstr "" -#: ../../changelog.rst:37 +#: ../../changelog.rst:52 msgid "" "[`#508 `_] " "Trend-grafiek kan niet gegenereerd worden" msgstr "" -#: ../../changelog.rst:38 +#: ../../changelog.rst:53 msgid "" "[`#292 `_] " "Statuspagina: onderdelen 'backup' en 'mindergas upload' toevoegen" msgstr "" -#: ../../changelog.rst:39 +#: ../../changelog.rst:54 msgid "" "[`#499 `_] " "Upgrade Font Awesome to v5" msgstr "" -#: ../../changelog.rst:44 +#: ../../changelog.rst:59 msgid "v1.22.1 - 2018-07-22" msgstr "" -#: ../../changelog.rst:48 +#: ../../changelog.rst:63 msgid "" "[`#506 `_] " "Fasen-grafiek hangt op 'loading'" msgstr "" -#: ../../changelog.rst:53 +#: ../../changelog.rst:68 msgid "v1.22.0 - 2018-07-22" msgstr "" -#: ../../changelog.rst:57 +#: ../../changelog.rst:72 msgid "" "[`#296 `_] 3 " "fasen teruglevering" msgstr "" -#: ../../changelog.rst:58 +#: ../../changelog.rst:73 msgid "" "[`#501 `_] " "Lijn grafiek bij geen teruglevering" msgstr "" -#: ../../changelog.rst:59 +#: ../../changelog.rst:74 msgid "" "[`#495 `_] " "Update documentation screenshots" msgstr "" -#: ../../changelog.rst:60 +#: ../../changelog.rst:75 msgid "" "[`#498 `_] " "Frontend improvements" msgstr "" -#: ../../changelog.rst:61 +#: ../../changelog.rst:76 msgid "" "[`#493 `_] " "Requirements update (July 2018)" msgstr "" -#: ../../changelog.rst:66 +#: ../../changelog.rst:81 msgid "v1.21.1 - 2018-07-16" msgstr "" -#: ../../changelog.rst:70 +#: ../../changelog.rst:85 msgid "" "[`#492 `_] " "Fixed some issues with eCharts (improvements)" msgstr "" -#: ../../changelog.rst:71 +#: ../../changelog.rst:86 msgid "" "[`#497 `_] " "Kleinigheidje: missende vertalingen" msgstr "" -#: ../../changelog.rst:76 +#: ../../changelog.rst:91 msgid "v1.21.0 - 2018-07-11" msgstr "" -#: ../../changelog.rst:80 +#: ../../changelog.rst:95 msgid "" "[`#489 `_] " "eCharts improved graphs for data zooming/scrolling" msgstr "" -#: ../../changelog.rst:81 +#: ../../changelog.rst:96 msgid "" "[`#434 `_] " "Omit gas readings all together" msgstr "" -#: ../../changelog.rst:82 +#: ../../changelog.rst:97 msgid "" "[`#264 `_] " "Check Dropbox API token and display error messages in GUI" msgstr "" -#: ../../changelog.rst:87 +#: ../../changelog.rst:102 msgid "v1.20.0 - 2018-07-04" msgstr "" -#: ../../changelog.rst:91 +#: ../../changelog.rst:106 msgid "" -"[`#484 `_] " -"API call om huidige versie terug te geven" +"[`#484 `_] API" +" call om huidige versie terug te geven" msgstr "" -#: ../../changelog.rst:92 +#: ../../changelog.rst:107 msgid "" -"[`#291 `_] " -"API option to get status info" +"[`#291 `_] API" +" option to get status info" msgstr "" -#: ../../changelog.rst:93 +#: ../../changelog.rst:108 msgid "" "[`#485 `_] " "Retrieve the current energycontract for the statistics page - helmo" msgstr "" -#: ../../changelog.rst:94 +#: ../../changelog.rst:109 msgid "" "[`#486 `_] " "Plugin documentation" msgstr "" -#: ../../changelog.rst:95 +#: ../../changelog.rst:110 msgid "" "[`#487 `_] " "Requirements update (July 2018)" msgstr "" -#: ../../changelog.rst:100 +#: ../../changelog.rst:115 msgid "v1.19.0 - 2018-06-12" msgstr "" -#: ../../changelog.rst:104 +#: ../../changelog.rst:119 msgid "" "[`#390 `_] " "Gas- en elektriciteitsverbruik vanaf start energie contract" msgstr "" -#: ../../changelog.rst:105 +#: ../../changelog.rst:120 msgid "" "[`#482 `_] " "Aantal items op X-as in dashboardgrafiek variabel maken" msgstr "" -#: ../../changelog.rst:106 +#: ../../changelog.rst:121 msgid "" "[`#407 `_] " "Plugin System (More than one pvoutput account)" msgstr "" -#: ../../changelog.rst:107 +#: ../../changelog.rst:122 msgid "" -"[`#462 `_] " -"Get live usage trough API" +"[`#462 `_] Get" +" live usage trough API" msgstr "" -#: ../../changelog.rst:112 +#: ../../changelog.rst:127 msgid "v1.18.0 - 2018-06-05" msgstr "" -#: ../../changelog.rst:116 +#: ../../changelog.rst:131 msgid "" -"[`#246 `_] " -"Add support for Pushover" +"[`#246 `_] Add" +" support for Pushover" msgstr "" -#: ../../changelog.rst:117 +#: ../../changelog.rst:132 msgid "" "[`#479 `_] " "Tijdsnotatie grafieken gelijktrekken" msgstr "" -#: ../../changelog.rst:118 +#: ../../changelog.rst:133 msgid "" "[`#480 `_] " "Requirements update (June 2018)" msgstr "" -#: ../../changelog.rst:123 +#: ../../changelog.rst:138 msgid "v1.17.0 - 2018-05-25" msgstr "" -#: ../../changelog.rst:127 +#: ../../changelog.rst:142 msgid "" "[`#475 `_] " "Notify my android service ended" msgstr "" -#: ../../changelog.rst:128 +#: ../../changelog.rst:143 msgid "" "[`#471 `_] " "Requirements update (May 2018)" msgstr "" -#: ../../changelog.rst:133 +#: ../../changelog.rst:148 msgid "v1.16.0 - 2018-04-04" msgstr "" -#: ../../changelog.rst:137 +#: ../../changelog.rst:152 msgid "" "[`#458 `_] " "DSMR v2.x parse-fout - by mrvanes" msgstr "" -#: ../../changelog.rst:138 +#: ../../changelog.rst:153 msgid "" "[`#455 `_] " "DOCS: Handleiding Nginx authenticatie uitbreiden - by FutureCow" msgstr "" -#: ../../changelog.rst:139 +#: ../../changelog.rst:154 msgid "" "[`#461 `_] " "Requirements update April 2018" msgstr "" -#: ../../changelog.rst:140 +#: ../../changelog.rst:155 msgid "Fixed some missing names on the contribution page in the DOCS" msgstr "" -#: ../../changelog.rst:145 +#: ../../changelog.rst:160 msgid "v1.15.0 - 2018-03-21" msgstr "" -#: ../../changelog.rst:149 ../../changelog.rst:162 +#: ../../changelog.rst:164 ../../changelog.rst:177 msgid "" "[`#449 `_] " "Meterstatistieken via MQTT beschikbaar" msgstr "" -#: ../../changelog.rst:150 +#: ../../changelog.rst:165 msgid "" "[`#208 `_] " "Notificatie bij uitblijven gegevens uit slimme meter" msgstr "" -#: ../../changelog.rst:151 +#: ../../changelog.rst:166 msgid "" "[`#342 `_] " "Backup to dropbox never finish (free plan no more space)" msgstr "" -#: ../../changelog.rst:156 +#: ../../changelog.rst:171 msgid "v1.14.0 - 2018-03-11" msgstr "" -#: ../../changelog.rst:160 +#: ../../changelog.rst:175 msgid "" "[`#441 `_] " "PVOutput exports schedulen naar ingestelde upload interval - by " "pyrocumulus" msgstr "" -#: ../../changelog.rst:161 +#: ../../changelog.rst:176 msgid "" "[`#436 `_] " "Update docs: authentication method for public webinterface" msgstr "" -#: ../../changelog.rst:163 +#: ../../changelog.rst:178 msgid "" "[`#445 `_] " "Upload/export to PVoutput doesn't work" msgstr "" -#: ../../changelog.rst:164 +#: ../../changelog.rst:179 msgid "" "[`#432 `_] " "[API] Gas cost missing at start of day" msgstr "" -#: ../../changelog.rst:165 +#: ../../changelog.rst:180 msgid "" "[`#367 `_] " "Dagverbruik en teruglevering via MQTT" msgstr "" -#: ../../changelog.rst:166 +#: ../../changelog.rst:181 msgid "" "[`#447 `_] " "Kosten via MQTT" msgstr "" -#: ../../changelog.rst:171 +#: ../../changelog.rst:186 msgid "v1.13.2 - 2018-02-02" msgstr "" -#: ../../changelog.rst:175 +#: ../../changelog.rst:190 msgid "" "[`#431 `_] " "Django security releases issued: 2.0.2" msgstr "" -#: ../../changelog.rst:180 +#: ../../changelog.rst:195 msgid "v1.13.1 - 2018-01-28" msgstr "" -#: ../../changelog.rst:184 +#: ../../changelog.rst:199 msgid "" "[`#428 `_] " "Django 2.0: Null characters are not allowed in telegram (esp8266)" msgstr "" -#: ../../changelog.rst:189 +#: ../../changelog.rst:204 msgid "v1.13.0 - 2018-01-23" msgstr "" -#: ../../changelog.rst:193 +#: ../../changelog.rst:208 msgid "" "[`#203 `_] " "One-click installer" msgstr "" -#: ../../changelog.rst:194 +#: ../../changelog.rst:209 msgid "" "[`#396 `_] " "Gecombineerd tarief tonen op 'Statistieken'-pagina" msgstr "" -#: ../../changelog.rst:195 +#: ../../changelog.rst:210 msgid "" "[`#268 `_] " "Data preservation/backups - by WatskeBart" msgstr "" -#: ../../changelog.rst:196 +#: ../../changelog.rst:211 msgid "" "[`#425 `_] " "Requests for donating a beer or coffee" msgstr "" -#: ../../changelog.rst:197 +#: ../../changelog.rst:212 msgid "" "[`#427 `_] " "Reconnect to postgresql" msgstr "" -#: ../../changelog.rst:198 +#: ../../changelog.rst:213 msgid "" "[`#394 `_] " "Django 2.0" msgstr "" -#: ../../changelog.rst:202 +#: ../../changelog.rst:217 msgid "v1.12.0 - 2018-01-14" msgstr "" -#: ../../changelog.rst:206 +#: ../../changelog.rst:221 msgid "" "[`#72 `_] " "Source data retention" msgstr "" -#: ../../changelog.rst:207 +#: ../../changelog.rst:222 msgid "" -"[`#414 `_] " -"add systemd service files - by meijjaa" +"[`#414 `_] add" +" systemd service files - by meijjaa" msgstr "" -#: ../../changelog.rst:208 +#: ../../changelog.rst:223 msgid "" "[`#405 `_] " "More updates to the Dutch translation of the documentation - by lckarssen" msgstr "" -#: ../../changelog.rst:209 +#: ../../changelog.rst:224 msgid "" -"[`#404 `_] " -"Fix minor typo in Dutch translation - by lckarssen" +"[`#404 `_] Fix" +" minor typo in Dutch translation - by lckarssen" msgstr "" -#: ../../changelog.rst:210 +#: ../../changelog.rst:225 msgid "" -"[`#398 `_] " -"iOS Web App: prevent same-window links from being opened externally - by " +"[`#398 `_] iOS" +" Web App: prevent same-window links from being opened externally - by " "Joris Vervuurt" msgstr "" -#: ../../changelog.rst:211 +#: ../../changelog.rst:226 msgid "" "[`#399 `_] " "Veel calls naar api.buienradar" msgstr "" -#: ../../changelog.rst:212 +#: ../../changelog.rst:227 msgid "" "[`#406 `_] " "Spelling correction trends page" msgstr "" -#: ../../changelog.rst:213 +#: ../../changelog.rst:228 msgid "" "[`#413 `_] " "Hoge CPU belasting op rpi 2 icm DSMR 5.0 meter" msgstr "" -#: ../../changelog.rst:214 +#: ../../changelog.rst:229 msgid "" "[`#419 `_] " "Requirements update (January 2018)" msgstr "" -#: ../../changelog.rst:219 +#: ../../changelog.rst:234 msgid "v1.11.0 - 2017-11-24" msgstr "" -#: ../../changelog.rst:223 +#: ../../changelog.rst:238 msgid "" "[`#382 `_] " "Archief klopt niet" msgstr "" -#: ../../changelog.rst:224 +#: ../../changelog.rst:239 msgid "" "[`#385 `_] " "Ververs dagverbruik op dashboard automatisch - by HugoDaBosss" msgstr "" -#: ../../changelog.rst:225 +#: ../../changelog.rst:240 msgid "" "[`#387 `_] " "There are too many unprocessed telegrams - by HugoDaBosss" msgstr "" -#: ../../changelog.rst:226 +#: ../../changelog.rst:241 msgid "" "[`#368 `_] " "Gebruik van os.environ.get - by ju5t" msgstr "" -#: ../../changelog.rst:227 +#: ../../changelog.rst:242 msgid "" "[`#370 `_] " "Pvoutput upload zonder teruglevering" msgstr "" -#: ../../changelog.rst:228 +#: ../../changelog.rst:243 msgid "" "[`#371 `_] " "fonts via https laden" msgstr "" -#: ../../changelog.rst:229 +#: ../../changelog.rst:244 msgid "" "[`#378 `_] " "Processing of telegrams stalled" msgstr "" -#: ../../changelog.rst:234 +#: ../../changelog.rst:249 msgid "v1.10.0 - 2017-10-19" msgstr "" -#: ../../changelog.rst:238 +#: ../../changelog.rst:253 msgid "" "This releases turns telegram logging **off by default**. If you wish to " "continue using this feature, add ``DSMRREADER_LOG_TELEGRAMS = True`` to " "your ``settings.py`` and reload the application." msgstr "" -#: ../../changelog.rst:244 +#: ../../changelog.rst:259 msgid "" "[`#363 `_] " "Show electricity_merged in the Total row for current month - by helmo" msgstr "" -#: ../../changelog.rst:245 +#: ../../changelog.rst:260 msgid "" "[`#305 `_] " "Trend staafdiagrammen afgelopen week / afgelopen maand altijd gelijk" msgstr "" -#: ../../changelog.rst:246 +#: ../../changelog.rst:261 msgid "" -"[`#194 `_] " -"Add timestamp to highest and lowest Watt occurance" +"[`#194 `_] Add" +" timestamp to highest and lowest Watt occurance" msgstr "" -#: ../../changelog.rst:247 +#: ../../changelog.rst:262 msgid "" "[`#365 `_] " "Turn telegram logging off by default" msgstr "" -#: ../../changelog.rst:248 +#: ../../changelog.rst:263 msgid "" "[`#366 `_] " "Restructure docs" msgstr "" -#: ../../changelog.rst:253 +#: ../../changelog.rst:268 msgid "v1.9.0 - 2017-10-08" msgstr "" -#: ../../changelog.rst:257 +#: ../../changelog.rst:272 msgid "" "This release contains an update for the API framework, which `has a fix " "for some timezone issues `_] Data " "export: PVOutput" msgstr "" -#: ../../changelog.rst:264 +#: ../../changelog.rst:279 msgid "" "[`#163 `_] " "Allow separate prices/costs for electricity returned" msgstr "" -#: ../../changelog.rst:265 +#: ../../changelog.rst:280 msgid "" -"[`#337 `_] " -"API mogelijkheid voor ophalen 'dashboard' waarden" +"[`#337 `_] API" +" mogelijkheid voor ophalen 'dashboard' waarden" msgstr "" -#: ../../changelog.rst:266 +#: ../../changelog.rst:281 msgid "" "[`#284 `_] " "Automatische backups geven alleen lege bestanden" msgstr "" -#: ../../changelog.rst:267 +#: ../../changelog.rst:282 msgid "" "[`#279 `_] " "Weather report with temperature '-' eventually results in stopped " "dsmr_backend" msgstr "" -#: ../../changelog.rst:268 +#: ../../changelog.rst:283 msgid "" "[`#245 `_] " "Grafiek gasverbruik doet wat vreemd na aantal uur geen nieuwe data" msgstr "" -#: ../../changelog.rst:269 +#: ../../changelog.rst:284 msgid "" "[`#272 `_] " "Dashboard - weergave huidig verbruik bij smalle weergave" msgstr "" -#: ../../changelog.rst:270 +#: ../../changelog.rst:285 msgid "" "[`#273 `_] " "Docker (by xirixiz) reference in docs" msgstr "" -#: ../../changelog.rst:271 +#: ../../changelog.rst:286 msgid "" "[`#286 `_] Na " "gebruik admin-pagina's geen (eenvoudige) mogelijkheid voor terugkeren " "naar de site" msgstr "" -#: ../../changelog.rst:272 +#: ../../changelog.rst:287 msgid "" "[`#332 `_] " "Launch full screen on iOS device when opening from homescreen" msgstr "" -#: ../../changelog.rst:273 +#: ../../changelog.rst:288 msgid "" "[`#276 `_] " "Display error compare page on mobile" msgstr "" -#: ../../changelog.rst:274 +#: ../../changelog.rst:289 msgid "" -"[`#288 `_] " -"Add info to FAQ" +"[`#288 `_] Add" +" info to FAQ" msgstr "" -#: ../../changelog.rst:275 +#: ../../changelog.rst:290 msgid "" "[`#320 `_] " "auto refresh op statussen op statuspagina" msgstr "" -#: ../../changelog.rst:276 +#: ../../changelog.rst:291 msgid "" -"[`#314 `_] " -"Add web-applicatie mogelijkheid ala pihole" +"[`#314 `_] Add" +" web-applicatie mogelijkheid ala pihole" msgstr "" -#: ../../changelog.rst:277 +#: ../../changelog.rst:292 msgid "" "[`#358 `_] " "Requirements update (September 2017)" msgstr "" -#: ../../changelog.rst:278 +#: ../../changelog.rst:293 msgid "" "[`#270 `_] " "Public Webinterface Warning (readthedocs.io)" msgstr "" -#: ../../changelog.rst:279 +#: ../../changelog.rst:294 msgid "" "[`#231 `_] " "Contributors update" msgstr "" -#: ../../changelog.rst:280 +#: ../../changelog.rst:295 msgid "" "[`#300 `_] " "Upgrade to Django 1.11 LTS" msgstr "" -#: ../../changelog.rst:285 +#: ../../changelog.rst:300 msgid "v1.8.2 - 2017-08-12" msgstr "" -#: ../../changelog.rst:289 +#: ../../changelog.rst:304 msgid "" "[`#346 `_] " "Defer statistics page XHR" msgstr "" -#: ../../changelog.rst:294 +#: ../../changelog.rst:309 msgid "v1.8.1 - 2017-07-04" msgstr "" -#: ../../changelog.rst:298 +#: ../../changelog.rst:313 msgid "" "[`#339 `_] " "Upgrade Dropbox-client to v8.x" msgstr "" -#: ../../changelog.rst:303 +#: ../../changelog.rst:318 msgid "v1.8.0 - 2017-06-14" msgstr "" -#: ../../changelog.rst:307 +#: ../../changelog.rst:322 msgid "" -"[`#141 `_] " -"Add MQTT support to publish readings" +"[`#141 `_] Add" +" MQTT support to publish readings" msgstr "" -#: ../../changelog.rst:308 +#: ../../changelog.rst:323 msgid "" "[`#331 `_] " "Requirements update (June 2016)" msgstr "" -#: ../../changelog.rst:309 +#: ../../changelog.rst:324 msgid "" "[`#299 `_] " "Support Python 3.6" msgstr "" -#: ../../changelog.rst:314 +#: ../../changelog.rst:329 msgid "v1.7.0 - 2017-05-04" msgstr "" -#: ../../changelog.rst:318 +#: ../../changelog.rst:333 msgid "" -"Please note that the " -"``dsmr_datalogger.0007_dsmrreading_timestamp_index`` migration **will " -"take quite some time**, as it adds an index on one of the largest " -"database tables!" +"Please note that the ``dsmr_datalogger.0007_dsmrreading_timestamp_index``" +" migration **will take quite some time**, as it adds an index on one of " +"the largest database tables!" msgstr "" -#: ../../changelog.rst:320 +#: ../../changelog.rst:335 msgid "" "It takes **around two minutes** on a RaspberryPi 2 & 3 with ``> 4.3 " "million`` readings on PostgreSQL. Results may differ on **slower " "RaspberryPi's** or **with MySQL**." msgstr "" -#: ../../changelog.rst:325 +#: ../../changelog.rst:340 msgid "" -"The API-docs for the new v2 API `can be found here `_." +"The API-docs for the new v2 API `can be found here `_." msgstr "" -#: ../../changelog.rst:330 +#: ../../changelog.rst:345 msgid "" "[`#230 `_] " "Support for exporting data via API" msgstr "" -#: ../../changelog.rst:335 +#: ../../changelog.rst:350 msgid "v1.6.2 - 2017-04-23" msgstr "" -#: ../../changelog.rst:339 +#: ../../changelog.rst:354 msgid "" -"[`#269 `_] x-" -"as gasgrafiek geeft rare waarden aan" +"[`#269 `_] " +"x-as gasgrafiek geeft rare waarden aan" msgstr "" -#: ../../changelog.rst:340 +#: ../../changelog.rst:355 msgid "" "[`#303 `_] " "Archive page's default day sorting" msgstr "" -#: ../../changelog.rst:345 +#: ../../changelog.rst:360 msgid "v1.6.1 - 2017-04-06" msgstr "" -#: ../../changelog.rst:349 +#: ../../changelog.rst:364 msgid "" "[`#298 `_] " "Update requirements (Django 1.10.7)" msgstr "" -#: ../../changelog.rst:354 +#: ../../changelog.rst:369 msgid "v1.6.0 - 2017-03-18" msgstr "" -#: ../../changelog.rst:358 +#: ../../changelog.rst:373 msgid "" "Support for ``MySQL`` has been **deprecated** since ``DSMR-reader v1.6`` " "and will be discontinued completely in a later release. Please use a " @@ -851,148 +871,148 @@ msgid "" "supported in easily migrating to PostgreSQL in the future." msgstr "" -#: ../../changelog.rst:363 +#: ../../changelog.rst:378 msgid "" "**Change in API:** The telegram creation API now returns an ``HTTP 201`` " "response when successful. An ``HTTP 200`` was returned in former " "versions. :doc:`View API docs`." msgstr "" -#: ../../changelog.rst:371 +#: ../../changelog.rst:386 msgid "" "[`#221 `_] " "Support for DSMR-firmware v5.0." msgstr "" -#: ../../changelog.rst:372 +#: ../../changelog.rst:387 msgid "" "[`#237 `_] " "Redesign: Status page." msgstr "" -#: ../../changelog.rst:373 +#: ../../changelog.rst:388 msgid "" "[`#249 `_] " "Req: Add iOS icon for Bookmark." msgstr "" -#: ../../changelog.rst:374 +#: ../../changelog.rst:389 msgid "" "[`#232 `_] " "Docs: Explain settings/options." msgstr "" -#: ../../changelog.rst:375 +#: ../../changelog.rst:390 msgid "" -"[`#260 `_] " -"Add link to readthedocs in Django for Dropbox instructions." +"[`#260 `_] Add" +" link to readthedocs in Django for Dropbox instructions." msgstr "" -#: ../../changelog.rst:376 +#: ../../changelog.rst:391 msgid "" -"[`#211 `_] " -"API request should return HTTP 201 instead of HTTP 200." +"[`#211 `_] API" +" request should return HTTP 201 instead of HTTP 200." msgstr "" -#: ../../changelog.rst:377 +#: ../../changelog.rst:392 msgid "" "[`#191 `_] " "Deprecate MySQL support." msgstr "" -#: ../../changelog.rst:378 +#: ../../changelog.rst:393 msgid "" "[`#251 `_] " "Buienradar Uncaught exception." msgstr "" -#: ../../changelog.rst:379 +#: ../../changelog.rst:394 msgid "" "[`#257 `_] " "Requirements update (February 2017)." msgstr "" -#: ../../changelog.rst:380 +#: ../../changelog.rst:395 msgid "" "[`#274 `_] " "Requirements update (March 2017)." msgstr "" -#: ../../changelog.rst:385 ../../changelog.rst:404 +#: ../../changelog.rst:400 ../../changelog.rst:419 msgid "v1.5.5 - 2017-01-19" msgstr "" -#: ../../changelog.rst:389 ../../changelog.rst:408 +#: ../../changelog.rst:404 ../../changelog.rst:423 msgid "" "Remove readonly restriction for editing statistics in admin interface " "(`#242 `_)." msgstr "" -#: ../../changelog.rst:394 ../../changelog.rst:413 +#: ../../changelog.rst:409 ../../changelog.rst:428 msgid "v1.5.4 - 2017-01-12" msgstr "" -#: ../../changelog.rst:398 ../../changelog.rst:417 +#: ../../changelog.rst:413 ../../changelog.rst:432 msgid "" -"Improve datalogger for DSMR v5.0 (`#212 `_)." +"Improve datalogger for DSMR v5.0 (`#212 " +"`_)." msgstr "" -#: ../../changelog.rst:399 ../../changelog.rst:418 +#: ../../changelog.rst:414 ../../changelog.rst:433 msgid "" -"Fixed another bug in MinderGas API client implementation (`#228 `_)." +"Fixed another bug in MinderGas API client implementation (`#228 " +"`_)." msgstr "" -#: ../../changelog.rst:423 +#: ../../changelog.rst:438 msgid "v1.5.3 - 2017-01-11" msgstr "" -#: ../../changelog.rst:427 +#: ../../changelog.rst:442 msgid "" -"Improve MinderGas API client implementation (`#228 `_)." +"Improve MinderGas API client implementation (`#228 " +"`_)." msgstr "" -#: ../../changelog.rst:432 +#: ../../changelog.rst:447 msgid "v1.5.2 - 2017-01-09" msgstr "" -#: ../../changelog.rst:436 +#: ../../changelog.rst:451 msgid "" -"Automatic refresh of dashboard charts (`#210 `_)." +"Automatic refresh of dashboard charts (`#210 " +"`_)." msgstr "" -#: ../../changelog.rst:437 +#: ../../changelog.rst:452 msgid "" "Mindergas.nl API: Tijdstip van verzending willekeurig maken (`#204 " "`_)." msgstr "" -#: ../../changelog.rst:438 +#: ../../changelog.rst:453 msgid "" -"Extend API docs with additional example (`#185 `_)." +"Extend API docs with additional example (`#185 " +"`_)." msgstr "" -#: ../../changelog.rst:439 +#: ../../changelog.rst:454 msgid "" -"Docs: How to restore backup (`#190 `_)." +"Docs: How to restore backup (`#190 `_)." msgstr "" -#: ../../changelog.rst:440 +#: ../../changelog.rst:455 msgid "" -"Log errors occured to file (`#181 `_)." +"Log errors occured to file (`#181 `_)." msgstr "" -#: ../../changelog.rst:445 +#: ../../changelog.rst:460 msgid "v1.5.1 - 2017-01-04" msgstr "" -#: ../../changelog.rst:449 +#: ../../changelog.rst:464 msgid "" "This patch contains no new features and **only solves upgrading issues** " "for some users." @@ -1000,13 +1020,13 @@ msgstr "" "Deze patch bevat geen nieuwe features en **lost alleen upgrade-problemen " "op** voor sommige gebruikers." -#: ../../changelog.rst:454 +#: ../../changelog.rst:469 msgid "" -"Fix for issues `#200 `_ & `#217 `_, which is caused by omitting the switch to the VirtualEnv. " -"This was not documented well enough in early versions of this project, " -"causing failed upgrades." +"Fix for issues `#200 `_ & `#217 `_, which is caused by omitting the switch to the " +"VirtualEnv. This was not documented well enough in early versions of this" +" project, causing failed upgrades." msgstr "" "Oplossing voor tickets `#200 `_ & `#217 `_)." +"upgrade (`#103 `_)." msgstr "" "De ondersteuning voor ``Python 3.3`` is **vervallen** wegens de Django " -"upgrade (`#103 `_)." +"upgrade (`#103 `_)." -#: ../../changelog.rst:464 +#: ../../changelog.rst:479 msgid "" "There is **experimental support** for ``Python 3.6`` and ``Python 3.7 " -"(nightly)`` as the unittests are `now built against those versions " -"`_ as well " -"(`#167 `_)." +"(nightly)`` as the unittests are `now built against those versions `_ as well (`#167 " +"`_)." msgstr "" -"Er is **experimentele ondersteuning** voor ``Python 3.6`` en ``Python " -"3.7 (nightly)`` gezien de unittests `momenteel ook in voor die versies " -"worden uitgevoerd. `_ (`#167 `_)." +"Er is **experimentele ondersteuning** voor ``Python 3.6`` en ``Python 3.7" +" (nightly)`` gezien de unittests `momenteel ook in voor die versies " +"worden uitgevoerd. `_ (`#167 `_)." -#: ../../changelog.rst:466 +#: ../../changelog.rst:481 msgid "**Legacy warning**" msgstr "**Waarschuwing voor legacy**" -#: ../../changelog.rst:468 +#: ../../changelog.rst:483 msgid "" -"The migrations that were squashed together in (`#31 `_) have been **removed**. This " -"will only affect you when you are currently still running a dsmrreader-" -"version of **before** ``v0.13 (β)``." +"The migrations that were squashed together in (`#31 " +"`_) have been " +"**removed**. This will only affect you when you are currently still " +"running a dsmrreader-version of **before** ``v0.13 (β)``." msgstr "" -"De migraties die samengevoegd zijn in (`#31 `_) zijn **verwijderd**. Hier heb " -"je alleen maar te maken wanneer je nog gebruik maakt van een dsmrreader-" -"versie **vóór** ``v0.13 (β)``." +"De migraties die samengevoegd zijn in (`#31 " +"`_) zijn " +"**verwijderd**. Hier heb je alleen maar te maken wanneer je nog gebruik " +"maakt van een dsmrreader-versie **vóór** ``v0.13 (β)``." -#: ../../changelog.rst:469 +#: ../../changelog.rst:484 msgid "" "If you are indeed still running ``< v0.13 (β)``, please upgrade to " "``v1.4`` first (!), followed by an upgrade to ``v1.5``." msgstr "" -"Wanneer je nog steeds een versie van vóór ``< v0.13 (β)`` draait, " -"upgrade dan eerst naar ``v1.4`` (!), gevolgd door een upgrade naar " -"``v1.5``." +"Wanneer je nog steeds een versie van vóór ``< v0.13 (β)`` draait, upgrade" +" dan eerst naar ``v1.4`` (!), gevolgd door een upgrade naar ``v1.5``." -#: ../../changelog.rst:473 +#: ../../changelog.rst:488 msgid "" "Verify telegrams' CRC (`#188 `_)." msgstr "" -#: ../../changelog.rst:474 +#: ../../changelog.rst:489 msgid "" -"Display last 24 hours on dashboard (`#164 `_)." +"Display last 24 hours on dashboard (`#164 " +"`_)." msgstr "" -#: ../../changelog.rst:475 +#: ../../changelog.rst:490 msgid "" -"Status page visualisation (`#172 `_)." +"Status page visualisation (`#172 `_)." msgstr "" -#: ../../changelog.rst:476 +#: ../../changelog.rst:491 msgid "" -"Store and display phases consumption (`#161 `_)." +"Store and display phases consumption (`#161 " +"`_)." msgstr "" -#: ../../changelog.rst:477 +#: ../../changelog.rst:492 msgid "" -"Weather graph not showing when no gas data is available (`#170 `_)." +"Weather graph not showing when no gas data is available (`#170 " +"`_)." msgstr "" -#: ../../changelog.rst:478 +#: ../../changelog.rst:493 msgid "" "Upgrade to ChartJs 2.0 (`#127 `_)." msgstr "" -#: ../../changelog.rst:479 +#: ../../changelog.rst:494 msgid "" -"Improve Statistics page performance (`#173 `_)." +"Improve Statistics page performance (`#173 " +"`_)." msgstr "" -#: ../../changelog.rst:480 +#: ../../changelog.rst:495 msgid "" -"Version checker at github (`#166 `_)." +"Version checker at github (`#166 `_)." msgstr "" -#: ../../changelog.rst:481 +#: ../../changelog.rst:496 msgid "" "Remove required login for dismissal of in-app notifications (`#179 " "`_)." msgstr "" -#: ../../changelog.rst:482 +#: ../../changelog.rst:497 msgid "" -"Round numbers displayed in GUI to 2 decimals (`#183 `_)." +"Round numbers displayed in GUI to 2 decimals (`#183 " +"`_)." msgstr "" -#: ../../changelog.rst:483 +#: ../../changelog.rst:498 msgid "" -"Switch Nosetests to Pytest (+ pytest-cov) (`#167 `_)." +"Switch Nosetests to Pytest (+ pytest-cov) (`#167 " +"`_)." msgstr "" -#: ../../changelog.rst:484 +#: ../../changelog.rst:499 msgid "" -"PyLama code audit (+ pytest-cov) (`#158 `_)." +"PyLama code audit (+ pytest-cov) (`#158 " +"`_)." msgstr "" -#: ../../changelog.rst:485 +#: ../../changelog.rst:500 msgid "" "Double upgrade of Django framework ``Django 1.8`` -> ``Django 1.9`` -> " -"``Django 1.10`` (`#103 `_)." +"``Django 1.10`` (`#103 `_)." msgstr "" -#: ../../changelog.rst:486 +#: ../../changelog.rst:501 msgid "" -"Force ``PYTHONUNBUFFERED`` for supervisor commands (`#176 `_)." +"Force ``PYTHONUNBUFFERED`` for supervisor commands (`#176 " +"`_)." msgstr "" -#: ../../changelog.rst:487 +#: ../../changelog.rst:502 msgid "" -"Documentation updates for v1.5 (`#171 `_)." +"Documentation updates for v1.5 (`#171 `_)." msgstr "" -#: ../../changelog.rst:488 +#: ../../changelog.rst:503 msgid "" -"Requirements update for v1.5 (december 2016) (`#182 `_)." +"Requirements update for v1.5 (december 2016) (`#182 " +"`_)." msgstr "" -#: ../../changelog.rst:489 +#: ../../changelog.rst:504 msgid "" -"Improved backend process logging (`#184 `_)." +"Improved backend process logging (`#184 " +"`_)." msgstr "" -#: ../../changelog.rst:494 +#: ../../changelog.rst:509 msgid "v1.4.1 - 2016-12-12" msgstr "" -#: ../../changelog.rst:498 +#: ../../changelog.rst:513 msgid "" -"Consumption chart hangs due to unique_key violation (`#174 `_)." +"Consumption chart hangs due to unique_key violation (`#174 " +"`_)." msgstr "" -#: ../../changelog.rst:499 +#: ../../changelog.rst:514 msgid "" -"NoReverseMatch at / Reverse for 'docs' (`#175 `_)." +"NoReverseMatch at / Reverse for 'docs' (`#175 " +"`_)." msgstr "" -#: ../../changelog.rst:504 +#: ../../changelog.rst:519 msgid "v1.4.0 - 2016-11-28" msgstr "" -#: ../../changelog.rst:507 +#: ../../changelog.rst:522 msgid "" -"Support for ``Python 3.5`` has been added officially (`#55 `_)." +"Support for ``Python 3.5`` has been added officially (`#55 " +"`_)." msgstr "" -#: ../../changelog.rst:511 +#: ../../changelog.rst:526 msgid "" -"Push notifications for Notify My Android / Prowl (iOS), written by " -"Jeroen Peters (`#152 `_)." +"Push notifications for Notify My Android / Prowl (iOS), written by Jeroen" +" Peters (`#152 `_)." msgstr "" -#: ../../changelog.rst:512 +#: ../../changelog.rst:527 msgid "" -"Support for both single and high/low tariff (`#130 `_)." +"Support for both single and high/low tariff (`#130 " +"`_)." msgstr "" -#: ../../changelog.rst:513 +#: ../../changelog.rst:528 msgid "" -"Add new note from Dashboard has wrong time format (`#159 `_)." +"Add new note from Dashboard has wrong time format (`#159 " +"`_)." msgstr "" -#: ../../changelog.rst:514 +#: ../../changelog.rst:529 msgid "" -"Display estimated price for current usage in Dashboard (`#155 `_)." +"Display estimated price for current usage in Dashboard (`#155 " +"`_)." msgstr "" -#: ../../changelog.rst:515 +#: ../../changelog.rst:530 msgid "" -"Dropbox API v1 deprecated in June 2017 (`#142 `_)." +"Dropbox API v1 deprecated in June 2017 (`#142 " +"`_)." msgstr "" -#: ../../changelog.rst:516 +#: ../../changelog.rst:531 msgid "" "Improve code coverage (`#151 `_)." msgstr "" -#: ../../changelog.rst:517 +#: ../../changelog.rst:532 msgid "" -"Restyle configuration overview (`#156 `_)." +"Restyle configuration overview (`#156 `_)." msgstr "" -#: ../../changelog.rst:518 +#: ../../changelog.rst:533 msgid "" -"Capability based push notifications (`#165 `_)." +"Capability based push notifications (`#165 " +"`_)." msgstr "" -#: ../../changelog.rst:523 +#: ../../changelog.rst:538 msgid "v1.3.2 - 2016-11-08" msgstr "" -#: ../../changelog.rst:526 +#: ../../changelog.rst:541 msgid "" -"Requirements update (november 2016) (`#150 `_)." +"Requirements update (november 2016) (`#150 " +"`_)." msgstr "" -#: ../../changelog.rst:531 +#: ../../changelog.rst:546 msgid "v1.3.1 - 2016-08-16" msgstr "" -#: ../../changelog.rst:534 +#: ../../changelog.rst:549 msgid "" "CSS large margin-bottom (`#144 `_)." msgstr "" -#: ../../changelog.rst:535 +#: ../../changelog.rst:550 msgid "" -"Django security releases issued: 1.8.14 (`#147 `_)." +"Django security releases issued: 1.8.14 (`#147 " +"`_)." msgstr "" -#: ../../changelog.rst:536 +#: ../../changelog.rst:551 msgid "" -"Requirements update (August 2016) (`#148 `_)." +"Requirements update (August 2016) (`#148 " +"`_)." msgstr "" -#: ../../changelog.rst:537 +#: ../../changelog.rst:552 msgid "" -"Query performance improvements (`#149 `_)." +"Query performance improvements (`#149 `_)." msgstr "" -#: ../../changelog.rst:542 +#: ../../changelog.rst:557 msgid "v1.3.0 - 2016-07-15" msgstr "" -#: ../../changelog.rst:545 +#: ../../changelog.rst:560 msgid "" -"API endpoint for datalogger (`#140 `_)." +"API endpoint for datalogger (`#140 `_)." msgstr "" -#: ../../changelog.rst:546 +#: ../../changelog.rst:561 msgid "" -"Colors for charts (`#137 `_)." +"Colors for charts (`#137 `_)." msgstr "" -#: ../../changelog.rst:547 +#: ../../changelog.rst:562 msgid "" "Data export: Mindergas.nl (`#10 `_)." msgstr "" -#: ../../changelog.rst:548 +#: ../../changelog.rst:563 msgid "" "Requirement upgrade (`#143 `_)." msgstr "" -#: ../../changelog.rst:549 +#: ../../changelog.rst:564 msgid "" -"Installation wizard for first time use (`#139 `_)." +"Installation wizard for first time use (`#139 " +"`_)." msgstr "" -#: ../../changelog.rst:554 +#: ../../changelog.rst:569 msgid "v1.2.0 - 2016-05-18" msgstr "" -#: ../../changelog.rst:557 +#: ../../changelog.rst:572 msgid "" "Energy supplier prices does not indicate tariff type (Django admin) " "(`#126 `_)." msgstr "" -#: ../../changelog.rst:558 +#: ../../changelog.rst:573 msgid "" "Requirements update (`#128 `_)." msgstr "" -#: ../../changelog.rst:559 +#: ../../changelog.rst:574 msgid "" -"Force backup (`#123 `_)." +"Force backup (`#123 `_)." msgstr "" -#: ../../changelog.rst:560 +#: ../../changelog.rst:575 msgid "" "Update clean-install.md (`#131 `_)." msgstr "" -#: ../../changelog.rst:561 +#: ../../changelog.rst:576 msgid "" -"Improve data export field names (`#132 `_)." +"Improve data export field names (`#132 " +"`_)." msgstr "" -#: ../../changelog.rst:562 +#: ../../changelog.rst:577 msgid "" -"Display average temperature in archive (`#122 `_)." +"Display average temperature in archive (`#122 " +"`_)." msgstr "" -#: ../../changelog.rst:563 +#: ../../changelog.rst:578 msgid "" -"Pie charts on trends page overlap their canvas (`#136 `_)." +"Pie charts on trends page overlap their canvas (`#136 " +"`_)." msgstr "" -#: ../../changelog.rst:564 +#: ../../changelog.rst:579 msgid "" "'Slumber' consumption (`#115 `_)." msgstr "" -#: ../../changelog.rst:565 +#: ../../changelog.rst:580 msgid "" -"Show lowest & highest Watt peaks (`#138 `_)." +"Show lowest & highest Watt peaks (`#138 " +"`_)." msgstr "" -#: ../../changelog.rst:566 +#: ../../changelog.rst:581 msgid "" "Allow day & hour statistics reset due to changing energy prices (`#95 " "`_)." msgstr "" -#: ../../changelog.rst:571 +#: ../../changelog.rst:586 msgid "v1.1.2 - 2016-05-01" msgstr "" -#: ../../changelog.rst:574 +#: ../../changelog.rst:589 msgid "" -"Trends page giving errors (when lacking data) (`#125 `_)." +"Trends page giving errors (when lacking data) (`#125 " +"`_)." msgstr "" -#: ../../changelog.rst:579 +#: ../../changelog.rst:594 msgid "v1.1.1 - 2016-04-27" msgstr "" -#: ../../changelog.rst:582 +#: ../../changelog.rst:597 msgid "" -"Improve readme (`#124 `_)." +"Improve readme (`#124 `_)." msgstr "" -#: ../../changelog.rst:587 +#: ../../changelog.rst:602 msgid "v1.1.0 - 2016-04-23" msgstr "" -#: ../../changelog.rst:590 +#: ../../changelog.rst:605 msgid "" "Autorefresh dashboard (`#117 `_)." msgstr "" -#: ../../changelog.rst:591 +#: ../../changelog.rst:606 msgid "" -"Improve line graphs' visibility (`#111 `_)." +"Improve line graphs' visibility (`#111 " +"`_)." msgstr "" -#: ../../changelog.rst:592 +#: ../../changelog.rst:607 msgid "" -"Easily add notes (`#110 `_)." +"Easily add notes (`#110 `_)." msgstr "" -#: ../../changelog.rst:593 +#: ../../changelog.rst:608 msgid "" -"Export data points in CSV format (`#2 `_)." +"Export data points in CSV format (`#2 `_)." msgstr "" -#: ../../changelog.rst:594 +#: ../../changelog.rst:609 msgid "" -"Allow day/month/year comparison (`#94 `_)." +"Allow day/month/year comparison (`#94 `_)." msgstr "" -#: ../../changelog.rst:595 +#: ../../changelog.rst:610 msgid "" -"Docs: Add FAQ and generic application info (`#113 `_)." +"Docs: Add FAQ and generic application info (`#113 " +"`_)." msgstr "" -#: ../../changelog.rst:596 +#: ../../changelog.rst:611 msgid "" -"Support for Iskra meter (DSMR 2.x) (`#120 `_)." +"Support for Iskra meter (DSMR 2.x) (`#120 " +"`_)." msgstr "" -#: ../../changelog.rst:601 +#: ../../changelog.rst:616 msgid "v1.0.1 - 2016-04-07" msgstr "" -#: ../../changelog.rst:604 +#: ../../changelog.rst:619 msgid "" -"Update licence to OSI compatible one (`#119 `_)." +"Update licence to OSI compatible one (`#119 " +"`_)." msgstr "" -#: ../../changelog.rst:609 +#: ../../changelog.rst:624 msgid "v1.0.0 - 2016-04-07" msgstr "" -#: ../../changelog.rst:610 +#: ../../changelog.rst:625 msgid "First official stable release." msgstr "Eerste officiële stabiele release." -#: ../../changelog.rst:615 +#: ../../changelog.rst:630 msgid "[β] v0.1 (2015-10-29) to 0.16 (2016-04-06)" msgstr "" -#: ../../changelog.rst:618 +#: ../../changelog.rst:633 msgid "" "All previous beta releases/changes have been combined to a single list " "below." @@ -1481,442 +1500,444 @@ msgstr "" "Alle vorige bèta releases/veranderingen zijn gecombineerd tot een enkele " "lijst hieronder." -#: ../../changelog.rst:620 +#: ../../changelog.rst:635 msgid "" -"Move documentation to wiki or RTD (`#90 `_)." +"Move documentation to wiki or RTD (`#90 " +"`_)." msgstr "" -#: ../../changelog.rst:621 +#: ../../changelog.rst:636 msgid "" "Translate README to Dutch (`#16 `_)." msgstr "" -#: ../../changelog.rst:622 +#: ../../changelog.rst:637 msgid "" -"Delete (recent) history page (`#112 `_)." +"Delete (recent) history page (`#112 `_)." msgstr "" -#: ../../changelog.rst:623 +#: ../../changelog.rst:638 msgid "" -"Display most recent temperature in dashboard (`#114 `_)." +"Display most recent temperature in dashboard (`#114 " +"`_)." msgstr "" -#: ../../changelog.rst:624 +#: ../../changelog.rst:639 msgid "" "Upgrade Django to 1.8.12 (`#118 `_)." msgstr "" -#: ../../changelog.rst:626 +#: ../../changelog.rst:641 msgid "" "Redesign trends page (`#97 `_)." msgstr "" -#: ../../changelog.rst:627 +#: ../../changelog.rst:642 msgid "" "Support for summer time (`#105 `_)." msgstr "" -#: ../../changelog.rst:628 +#: ../../changelog.rst:643 msgid "" -"Support for Daylight Saving Time (DST) transition (`#104 `_)." +"Support for Daylight Saving Time (DST) transition (`#104 " +"`_)." msgstr "" -#: ../../changelog.rst:629 +#: ../../changelog.rst:644 msgid "" -"Add (error) hints to status page (`#106 `_)." +"Add (error) hints to status page (`#106 " +"`_)." msgstr "" -#: ../../changelog.rst:630 +#: ../../changelog.rst:645 msgid "" "Keep track of version (`#108 `_)." msgstr "" -#: ../../changelog.rst:632 +#: ../../changelog.rst:647 msgid "" "Django 1.8.11 released (`#82 `_)." msgstr "" -#: ../../changelog.rst:633 +#: ../../changelog.rst:648 msgid "" -"Prevent tests from failing due to moment of execution (`#88 `_)." +"Prevent tests from failing due to moment of execution (`#88 " +"`_)." msgstr "" -#: ../../changelog.rst:634 +#: ../../changelog.rst:649 msgid "" -"Statistics page meter positions are broken (`#93 `_)." +"Statistics page meter positions are broken (`#93 " +"`_)." msgstr "" -#: ../../changelog.rst:635 +#: ../../changelog.rst:650 msgid "" -"Archive only shows graph untill 23:00 (11 pm) (`#77 `_)." +"Archive only shows graph untill 23:00 (11 pm) (`#77 " +"`_)." msgstr "" -#: ../../changelog.rst:636 +#: ../../changelog.rst:651 msgid "" -"Trends page crashes due to nullable fields average (`#100 `_)." +"Trends page crashes due to nullable fields average (`#100 " +"`_)." msgstr "" -#: ../../changelog.rst:637 +#: ../../changelog.rst:652 msgid "" -"Trends: Plot peak and off-peak relative to each other (`#99 `_)." +"Trends: Plot peak and off-peak relative to each other (`#99 " +"`_)." msgstr "" -#: ../../changelog.rst:638 +#: ../../changelog.rst:653 msgid "" -"Monitor requirements with requires.io (`#101 `_)." +"Monitor requirements with requires.io (`#101 " +"`_)." msgstr "" -#: ../../changelog.rst:639 +#: ../../changelog.rst:654 msgid "" -"Terminology (`#41 `_)." +"Terminology (`#41 `_)." msgstr "" -#: ../../changelog.rst:640 +#: ../../changelog.rst:655 msgid "" -"Obsolete signals in dsmr_consumption (`#63 `_)." +"Obsolete signals in dsmr_consumption (`#63 " +"`_)." msgstr "" -#: ../../changelog.rst:641 +#: ../../changelog.rst:656 msgid "" -"Individual app testing coverage (`#64 `_)." +"Individual app testing coverage (`#64 `_)." msgstr "" -#: ../../changelog.rst:642 +#: ../../changelog.rst:657 msgid "" -"Support for extra devices on other M-bus (0-n:24.1) (`#92 `_)." +"Support for extra devices on other M-bus (0-n:24.1) (`#92 " +"`_)." msgstr "" -#: ../../changelog.rst:643 +#: ../../changelog.rst:658 msgid "" -"Separate post-deployment commands (`#102 `_)." +"Separate post-deployment commands (`#102 " +"`_)." msgstr "" -#: ../../changelog.rst:645 +#: ../../changelog.rst:660 msgid "" -"Show exceptions in production (webinterface) (`#87 `_)." +"Show exceptions in production (webinterface) (`#87 " +"`_)." msgstr "" -#: ../../changelog.rst:646 +#: ../../changelog.rst:661 msgid "" -"Keep Supervisor processes running (`#79 `_)." +"Keep Supervisor processes running (`#79 " +"`_)." msgstr "" -#: ../../changelog.rst:647 +#: ../../changelog.rst:662 msgid "" -"Hourly stats of 22:00:00+00 every day lack gas (`#78 `_)." +"Hourly stats of 22:00:00+00 every day lack gas (`#78 " +"`_)." msgstr "" -#: ../../changelog.rst:648 +#: ../../changelog.rst:663 msgid "" -"Test Travis-CI with MySQL + MariaDB + PostgreSQL (`#54 `_)." +"Test Travis-CI with MySQL + MariaDB + PostgreSQL (`#54 " +"`_)." msgstr "" -#: ../../changelog.rst:649 +#: ../../changelog.rst:664 msgid "" "PostgreSQL tests + nosetests + coverage failure: unrecognized " -"configuration parameter \"foreign_key_checks\" (`#62 `_)." +"configuration parameter \"foreign_key_checks\" (`#62 " +"`_)." msgstr "" -#: ../../changelog.rst:650 +#: ../../changelog.rst:665 msgid "" -"Performance check (`#83 `_)." +"Performance check (`#83 `_)." msgstr "" -#: ../../changelog.rst:651 +#: ../../changelog.rst:666 msgid "" -"Allow month & year archive (`#66 `_)." +"Allow month & year archive (`#66 `_)." msgstr "" -#: ../../changelog.rst:652 +#: ../../changelog.rst:667 msgid "" -"Graphs keep increasing height on tablet (`#89 `_)." +"Graphs keep increasing height on tablet (`#89 " +"`_)." msgstr "" -#: ../../changelog.rst:654 +#: ../../changelog.rst:669 msgid "" -"Delete StatsSettings(.track) settings model (`#71 `_)." +"Delete StatsSettings(.track) settings model (`#71 " +"`_)." msgstr "" -#: ../../changelog.rst:655 +#: ../../changelog.rst:670 msgid "" "Drop deprecated commands (`#22 `_)." msgstr "" -#: ../../changelog.rst:656 +#: ../../changelog.rst:671 msgid "" "Datalogger doesn't work properly with DSMR 4.2 (KAIFA-METER) (`#73 " "`_)." msgstr "" -#: ../../changelog.rst:657 +#: ../../changelog.rst:672 msgid "" -"Dashboard month statistics costs does not add up (`#75 `_)." +"Dashboard month statistics costs does not add up (`#75 " +"`_)." msgstr "" -#: ../../changelog.rst:658 +#: ../../changelog.rst:673 msgid "" -"Log unhandled exceptions and errors (`#65 `_)." +"Log unhandled exceptions and errors (`#65 " +"`_)." msgstr "" -#: ../../changelog.rst:659 +#: ../../changelog.rst:674 msgid "" "Datalogger crashes with IntegrityError because 'timestamp' is null (`#74 " "`_)." msgstr "" -#: ../../changelog.rst:660 +#: ../../changelog.rst:675 msgid "" -"Trends are always shown in UTC (`#76 `_)." +"Trends are always shown in UTC (`#76 `_)." msgstr "" -#: ../../changelog.rst:661 +#: ../../changelog.rst:676 msgid "" -"Squash migrations (`#31 `_)." +"Squash migrations (`#31 `_)." msgstr "" -#: ../../changelog.rst:662 +#: ../../changelog.rst:677 msgid "" -"Display 'electricity returned' graph in dashboard (`#81 `_)." +"Display 'electricity returned' graph in dashboard (`#81 " +"`_)." msgstr "" -#: ../../changelog.rst:663 +#: ../../changelog.rst:678 msgid "" "Optional gas (and electricity returned) capabilities tracking (`#70 " "`_)." msgstr "" -#: ../../changelog.rst:664 +#: ../../changelog.rst:679 msgid "" -"Add 'electricity returned' to trends page (`#84 `_)." +"Add 'electricity returned' to trends page (`#84 " +"`_)." msgstr "" -#: ../../changelog.rst:666 +#: ../../changelog.rst:681 msgid "" -"Archive: View past days details (`#61 `_)." +"Archive: View past days details (`#61 `_)." msgstr "" -#: ../../changelog.rst:667 +#: ../../changelog.rst:682 msgid "" -"Dashboard: Consumption total for current month (`#60 `_)." +"Dashboard: Consumption total for current month (`#60 " +"`_)." msgstr "" -#: ../../changelog.rst:668 +#: ../../changelog.rst:683 msgid "" -"Check whether gas readings are optional (`#34 `_)." +"Check whether gas readings are optional (`#34 " +"`_)." msgstr "" -#: ../../changelog.rst:669 +#: ../../changelog.rst:684 msgid "" -"Django security releases issued: 1.8.10 (`#68 `_)." +"Django security releases issued: 1.8.10 (`#68 " +"`_)." msgstr "" -#: ../../changelog.rst:670 +#: ../../changelog.rst:685 msgid "" "Notes display in archive (`#69 `_)." msgstr "" -#: ../../changelog.rst:672 +#: ../../changelog.rst:687 msgid "" -"Status page/alerts when features are disabled/unavailable (`#45 `_)." +"Status page/alerts when features are disabled/unavailable (`#45 " +"`_)." msgstr "" -#: ../../changelog.rst:673 +#: ../../changelog.rst:688 msgid "" "Integrate Travis CI (`#48 `_)." msgstr "" -#: ../../changelog.rst:674 +#: ../../changelog.rst:689 msgid "" -"Testing coverage (`#38 `_)." +"Testing coverage (`#38 `_)." msgstr "" -#: ../../changelog.rst:675 +#: ../../changelog.rst:690 msgid "" -"Implement automatic backups & Dropbox cloud storage (`#44 `_)." +"Implement automatic backups & Dropbox cloud storage (`#44 " +"`_)." msgstr "" -#: ../../changelog.rst:676 +#: ../../changelog.rst:691 msgid "" -"Link code coverage service to repository (`#56 `_)." +"Link code coverage service to repository (`#56 " +"`_)." msgstr "" -#: ../../changelog.rst:677 +#: ../../changelog.rst:692 msgid "" "Explore timezone.localtime() as replacement for datetime.astimezone() " "(`#50 `_)." msgstr "" -#: ../../changelog.rst:678 +#: ../../changelog.rst:693 msgid "" "Align GasConsumption.read_at to represent the start of hour (`#40 " "`_)." msgstr "" -#: ../../changelog.rst:680 +#: ../../changelog.rst:695 msgid "" -"Cleanup unused static files (`#47 `_)." +"Cleanup unused static files (`#47 `_)." msgstr "" -#: ../../changelog.rst:681 +#: ../../changelog.rst:696 msgid "" "Investigated mysql_tzinfo_to_sql — Load the Time Zone Tables (`#35 " "`_)." msgstr "" -#: ../../changelog.rst:682 +#: ../../changelog.rst:697 msgid "" -"Make additional DSMR data optional (`#46 `_)." +"Make additional DSMR data optional (`#46 " +"`_)." msgstr "" -#: ../../changelog.rst:683 +#: ../../changelog.rst:698 msgid "" "Localize graph x-axis (`#42 `_)." msgstr "" -#: ../../changelog.rst:684 +#: ../../changelog.rst:699 msgid "" -"Added graph formatting string to gettext file (`#42 `_)." +"Added graph formatting string to gettext file (`#42 " +"`_)." msgstr "" -#: ../../changelog.rst:685 +#: ../../changelog.rst:700 msgid "" -"Different colors for peak & off-peak electricity (`#52 `_)." +"Different colors for peak & off-peak electricity (`#52 " +"`_)." msgstr "" -#: ../../changelog.rst:686 +#: ../../changelog.rst:701 msgid "" -"Admin: Note widget (`#51 `_)." +"Admin: Note widget (`#51 `_)." msgstr "" -#: ../../changelog.rst:687 +#: ../../changelog.rst:702 msgid "" -"Allow GUI to run without data (`#26 `_)." +"Allow GUI to run without data (`#26 `_)." msgstr "" -#: ../../changelog.rst:689 +#: ../../changelog.rst:704 msgid "" "Moved project to GitHub (`#28 `_)." msgstr "" -#: ../../changelog.rst:690 +#: ../../changelog.rst:705 msgid "Added stdout to dsmr_backend to reflect progress." msgstr "" -#: ../../changelog.rst:691 +#: ../../changelog.rst:706 msgid "" "Restore note usage in GUI (`#39 `_)." msgstr "" -#: ../../changelog.rst:693 +#: ../../changelog.rst:708 msgid "" -"Store daily, weekly, monthly and yearly statistics (`#3 `_)." +"Store daily, weekly, monthly and yearly statistics (`#3 " +"`_)." msgstr "" -#: ../../changelog.rst:694 +#: ../../changelog.rst:709 msgid "" "Improved Recent History page performance a bit. (as result of `#3 " "`_)" msgstr "" -#: ../../changelog.rst:695 +#: ../../changelog.rst:710 msgid "" "Updates ChartJS library tot 1.1, disposing django-chartjs plugin. Labels " "finally work! (as result of `#3 `_)" msgstr "" -#: ../../changelog.rst:696 +#: ../../changelog.rst:711 msgid "" -"Added trends page. (as result of `#3 `_)" +"Added trends page. (as result of `#3 `_)" msgstr "" -#: ../../changelog.rst:698 +#: ../../changelog.rst:713 msgid "" -"Recent history setting: set range (`#29 `_)." +"Recent history setting: set range (`#29 " +"`_)." msgstr "" -#: ../../changelog.rst:699 +#: ../../changelog.rst:714 msgid "" "Mock required for test: dsmr_weather.test_weather_tracking (`#32 " "`_)." msgstr "" -#: ../../changelog.rst:701 +#: ../../changelog.rst:716 msgid "" -"Massive refactoring: Separating apps & using signals (`#19 `_)." +"Massive refactoring: Separating apps & using signals (`#19 " +"`_)." msgstr "" -#: ../../changelog.rst:702 +#: ../../changelog.rst:717 msgid "" -"README update: Exit character for cu (`#27 `_, by Jeroen Peters)." +"README update: Exit character for cu (`#27 " +"`_, by Jeroen " +"Peters)." msgstr "" -#: ../../changelog.rst:703 +#: ../../changelog.rst:718 msgid "Fixed untranslated strings in admin interface." msgstr "" -#: ../../changelog.rst:704 +#: ../../changelog.rst:719 msgid "Upgraded Django to 1.8.9." msgstr "" + diff --git a/docs/locale/nl/LC_MESSAGES/dropbox.mo b/docs/locale/nl/LC_MESSAGES/dropbox.mo index c4e19258d..495be767b 100644 Binary files a/docs/locale/nl/LC_MESSAGES/dropbox.mo and b/docs/locale/nl/LC_MESSAGES/dropbox.mo differ diff --git a/docs/locale/nl/LC_MESSAGES/dropbox.po b/docs/locale/nl/LC_MESSAGES/dropbox.po index 1c4604f32..843f6baa7 100644 --- a/docs/locale/nl/LC_MESSAGES/dropbox.po +++ b/docs/locale/nl/LC_MESSAGES/dropbox.po @@ -9,18 +9,23 @@ msgstr "" "Project-Id-Version: DSMR Reader v1.x\n" "Report-Msgid-Bugs-To: Dennis Siemensma \n" "Last-Translator: Dennis Siemensma \n" +"Language: nl\n" "Language-Team: Dennis Siemensma \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.3.4\n" -"Language: nl\n" +"Generated-By: Babel 2.5.1\n" "X-Generator: Poedit 1.8.7.1\n" #: ../../dropbox.rst:2 msgid "Integration: Dropbox" msgstr "Integratie: Dropbox" +#: ../../dropbox.rst:7 +msgid "Contents" +msgstr "Inhoudsopgave" + #: ../../dropbox.rst:10 msgid "Dropbox: Automated backup sync" msgstr "Dropbox: Automatisch backups uploaden" @@ -31,39 +36,40 @@ msgstr "*Hoe kan ik mijn Dropbox-account koppelen voor de backups?*" #: ../../dropbox.rst:13 msgid "" -"Make sure you have a Dropbox-account or sign up for one. Now go to `Dropbox " -"Apps `_ and click **\"Create app\"** " -"in top right corner." +"Make sure you have a Dropbox-account or sign up for one. Now go to " +"`Dropbox Apps `_ and click **" +"\"Create app\"** in top right corner." msgstr "" -"Zorg allereerst dat je een Dropbox-account hebt. Ga vervolgens naar `Dropbox " -"Apps `_ en klik op **\"Create app\"** " -"rechtsbovenin." +"Zorg allereerst dat je een Dropbox-account hebt. Ga vervolgens naar " +"`Dropbox Apps `_ en klik op **" +"\"Create app\"** rechtsbovenin." #: ../../dropbox.rst:20 msgid "" -"Choose the following options: (1) **Dropbox API** and (2) **App folder**. Then " -"enter a name for your app (3), this will also be used as directory name within " -"the Apps-folder of your Dropbox." +"Choose the following options: (1) **Dropbox API** and (2) **App " +"folder**. Then enter a name for your app (3), this will also be used as " +"directory name within the Apps-folder of your Dropbox." msgstr "" -"Kies de volgende opties: (1) **Dropbox API** en (2) **App folder**. Voer " -"vervolgens een naam voor je app in (3), deze wordt ook gebruikt als naam van " -"de submap binnen je Apps-folder in Dropbox." +"Kies de volgende opties: (1) **Dropbox API** en (2) **App folder**. " +"Voer vervolgens een naam voor je app in (3), deze wordt ook gebruikt als " +"naam van de submap binnen je Apps-folder in Dropbox." #: ../../dropbox.rst:27 msgid "" -"The app should be created in developer-mode. You can generate an access token " -"for yourself by clicking the **\"Generate\"** button somewhere below." +"The app should be created in developer-mode. You can generate an access " +"token for yourself by clicking the **\"Generate\"** button somewhere " +"below." msgstr "" -"The app is als het goed is nu aangemaakt in developer-modus. Je kunt nog een " -"toegangstoken genereren door op de knop **\"Generate\"** te klikken, die " -"verderop onderaan de pagina staat." +"De app is als het goed is nu aangemaakt in developer-modus. Je kunt nog " +"een toegangstoken genereren door op de knop **\"Generate\"** te klikken, " +"die verderop onderaan de pagina staat." #: ../../dropbox.rst:33 msgid "" -"Copy the generated access token to the DSMR-reader settings for the Dropbox-" -"configuration. The DSMR-reader application should sync any backups created " -"shortly." +"Copy the generated access token to the DSMR-reader settings for the " +"Dropbox-configuration. The DSMR-reader application should sync any " +"backups created shortly." msgstr "" -"Kopieer de gegenereerde toegangstoken naar de DSMR-reader instellingen onder " -"Dropbox-configuratie. DSMR-reader zou vrij vlot gemaakte backups moeten " -"uploaden naar je Dropbox-account." +"Kopieer de gegenereerde toegangstoken naar de DSMR-reader instellingen " +"onder Dropbox-configuratie. DSMR-reader zou vrij vlot gemaakte backups " +"moeten uploaden naar je Dropbox-account." diff --git a/docs/locale/nl/LC_MESSAGES/licence.mo b/docs/locale/nl/LC_MESSAGES/licence.mo index d97eecda8..a395db554 100644 Binary files a/docs/locale/nl/LC_MESSAGES/licence.mo and b/docs/locale/nl/LC_MESSAGES/licence.mo differ diff --git a/docs/locale/nl/LC_MESSAGES/licence.po b/docs/locale/nl/LC_MESSAGES/licence.po index 581933854..12a29e51a 100644 --- a/docs/locale/nl/LC_MESSAGES/licence.po +++ b/docs/locale/nl/LC_MESSAGES/licence.po @@ -13,7 +13,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.3.4\n" +"Generated-By: Babel 2.5.1\n" #: ../../licence.rst:2 msgid "License" @@ -29,6 +29,10 @@ msgid "" "nc/4.0/legalcode#languages" msgstr "" +#: ../../licence.rst:15 +msgid "Licence" +msgstr "" + #: ../../licence.rst:18 msgid "" "Creative Commons Attribution-NonCommercial 4.0 International Public " diff --git a/docs/locale/nl/LC_MESSAGES/mindergas.mo b/docs/locale/nl/LC_MESSAGES/mindergas.mo index 0e61e2ade..8ee58fae1 100644 Binary files a/docs/locale/nl/LC_MESSAGES/mindergas.mo and b/docs/locale/nl/LC_MESSAGES/mindergas.mo differ diff --git a/docs/locale/nl/LC_MESSAGES/mindergas.po b/docs/locale/nl/LC_MESSAGES/mindergas.po index 4d865c840..0fccdda5a 100644 --- a/docs/locale/nl/LC_MESSAGES/mindergas.po +++ b/docs/locale/nl/LC_MESSAGES/mindergas.po @@ -9,18 +9,23 @@ msgstr "" "Project-Id-Version: DSMR Reader v1.x\n" "Report-Msgid-Bugs-To: Dennis Siemensma \n" "Last-Translator: Dennis Siemensma \n" +"Language: nl\n" "Language-Team: Dennis Siemensma \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.3.4\n" -"Language: nl\n" +"Generated-By: Babel 2.5.1\n" "X-Generator: Poedit 1.8.7.1\n" #: ../../mindergas.rst:2 msgid "Integration: MinderGas.nl" msgstr "Integratie: MinderGas.nl" +#: ../../mindergas.rst:7 +msgid "Contents" +msgstr "Inhoudsopgave" + #: ../../mindergas.rst:10 msgid "Mindergas.nl: Automated gas meter position export" msgstr "Mindergas.nl: Automatisch gasmeterstanden exporteren" @@ -31,36 +36,37 @@ msgstr "*Hoe kan ik mijn mindergas.nl-account koppelen?*" #: ../../mindergas.rst:13 msgid "" -"Make sure you have a Mindergas.nl account or `signup for one `_. Now go to \"`Meterstand API `_\" and click on the button located below **" +"Make sure you have a Mindergas.nl account or `signup for one `_. Now go to \"`Meterstand API `_\" and click on the button located below **" "\"Authenticatietoken\"**." msgstr "" -"Zorg ervoor dat je een mindergas.nl-account hebt of `registreer je op hun " -"website `_. Ga nu naar \"`Meterstand " -"API `_\" en klik op de knop onder het " -"kopje **\"Authenticatietoken\"**." +"Zorg ervoor dat je een mindergas.nl-account hebt of `registreer je op " +"hun website `_. Ga nu naar " +"\"`Meterstand API `_\" en klik op " +"de knop onder het kopje **\"Authenticatietoken\"**." #: ../../mindergas.rst:20 msgid "" -"Copy the authentication token generated and paste in into the DSMR-reader " -"settings for the Mindergas.nl-configuration. Obviously the export only works " -"when there are any gas readings at all and you have ticked the 'export' " -"checkbox in the Mindergas.nl-configuration as well." +"Copy the authentication token generated and paste in into the DSMR-" +"reader settings for the Mindergas.nl-configuration. Obviously the export " +"only works when there are any gas readings at all and you have ticked " +"the 'export' checkbox in the Mindergas.nl-configuration as well." msgstr "" -"Kopieer de gegenereerde authenticatietoken in de DSMR-reader instellingen " -"onder Mindergas.nl-configuratie. Vanzelfsprekend werkt deze feature alleen " -"wanneer er gas gemeten wordt, en wanneer je de optie 'Exporteer gegevens naar " -"MinderGas' aangevinkt hebt in dezelfde configuratie." +"Kopieer de gegenereerde authenticatietoken in de DSMR-reader " +"instellingen onder Mindergas.nl-configuratie. Vanzelfsprekend werkt deze " +"feature alleen wanneer er gas gemeten wordt, en wanneer je de optie " +"'Exporteer gegevens naar MinderGas' aangevinkt hebt in dezelfde " +"configuratie." #: ../../mindergas.rst:25 msgid "" "Please note that due to policies of mindergas.nl it's not allowed to " "retroactively upload meter positions using the API. Therefor this is not " -"supported by the application. You can however, enter them manually on their " -"website." +"supported by the application. You can however, enter them manually on " +"their website." msgstr "" "N.B.: Wegens het beleid van mindergas.nl is het niet toegestaan om met " -"terugwerkende kracht meterstanden door te geven via de API. De applicatie " -"ondersteunt dat om die reden niet. Je kunt oude meterstanden echter wel via " -"hun website handmatig invoeren, indien gewenst." +"terugwerkende kracht meterstanden door te geven via de API. De " +"applicatie ondersteunt dat om die reden niet. Je kunt oude meterstanden " +"echter wel via hun website handmatig invoeren, indien gewenst." diff --git a/docs/locale/nl/LC_MESSAGES/pvoutput.mo b/docs/locale/nl/LC_MESSAGES/pvoutput.mo index 256611378..2ebc265da 100644 Binary files a/docs/locale/nl/LC_MESSAGES/pvoutput.mo and b/docs/locale/nl/LC_MESSAGES/pvoutput.mo differ diff --git a/docs/locale/nl/LC_MESSAGES/pvoutput.po b/docs/locale/nl/LC_MESSAGES/pvoutput.po index 5972b1192..00073b13c 100644 --- a/docs/locale/nl/LC_MESSAGES/pvoutput.po +++ b/docs/locale/nl/LC_MESSAGES/pvoutput.po @@ -9,18 +9,23 @@ msgstr "" "Project-Id-Version: DSMR Reader v1.x\n" "Report-Msgid-Bugs-To: Dennis Siemensma \n" "Last-Translator: Dennis Siemensma \n" +"Language: nl\n" "Language-Team: Dennis Siemensma \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.3.4\n" -"Language: nl\n" +"Generated-By: Babel 2.5.1\n" "X-Generator: Poedit 1.8.7.1\n" #: ../../pvoutput.rst:2 msgid "Integration: PVOutput" msgstr "Integratie: PVOutput" +#: ../../pvoutput.rst:7 +msgid "Contents" +msgstr "Inhoudsopgave" + #: ../../pvoutput.rst:10 msgid "PVOutput.org: Automated electricity consumption export" msgstr "PVOutput.org: Automatisch exporteren van verbruik" @@ -31,26 +36,26 @@ msgstr "*Hoe kan ik mijn PVOutput.org-account koppelen?*" #: ../../pvoutput.rst:13 msgid "" -"Make sure you have a PVOutput.org account, or `signup for an account `_. You will have to configure your account and PV system(s). " -"For any support doing that, please `see this page `_ for more information." +"Make sure you have a PVOutput.org account, or `signup for an account " +"`_. You will have to configure your account and " +"PV system(s). For any support doing that, please `see this page `_ for more information." msgstr "" "Zorg ervoor dat je een PVOutput.org-account hebt, of `registreer er een " "`_. Vervolgens zul je je account en zonnesystemen " -"moeten configureren. Voor ondersteuning hoe dat moet, `zie de documentatie " -"`_ voor meer " -"informatie." +"moeten configureren. Voor ondersteuning hoe dat moet, `zie de " +"documentatie `_ " +"voor meer informatie." #: ../../pvoutput.rst:16 msgid "" -"In order to link DSMR-reader to your account, please write down the \"API Key" -"\" and \"System ID\" from your PVOutput account. You can find them near the " -"bottom of the \"Settings\" page in PVOutput." +"In order to link DSMR-reader to your account, please write down the " +"\"API Key\" and \"System ID\" from your PVOutput account. You can find " +"them near the bottom of the \"Settings\" page in PVOutput." msgstr "" -"Om DSMR-reader te koppelen met je account, noteer de \"API Key\" en \"System ID" -"\" zoals je PVOutput-account aangeeft. Deze zijn terug te vinden onder " -"\"Settings\", binnen PVOutput." +"Om DSMR-reader te koppelen met je account, noteer de \"API Key\" en " +"\"System ID\" zoals je PVOutput-account aangeeft. Deze zijn terug te " +"vinden onder \"Settings\", binnen PVOutput." #: ../../pvoutput.rst:24 msgid "" @@ -70,8 +75,8 @@ msgstr "System ID" #: ../../pvoutput.rst:35 msgid "" -"Now navigate to another settings page in DSMR-reader: \"PVOutput: \"Add Status" -"\" configuration\"." +"Now navigate to another settings page in DSMR-reader: \"PVOutput: \"Add " +"Status\" configuration\"." msgstr "" "Navigeer nu naar een andere pagina met instellingen binnen DSMR-reader, " "genaamd \"PVOutput: Add Status configuratie\"." @@ -82,38 +87,38 @@ msgstr "Schakel het uploaden van je verbruik in." #: ../../pvoutput.rst:38 msgid "" -"Choose an interval between the uploads. You can configure this as well on the " -"PVOutput's end, in Device Settings." +"Choose an interval between the uploads. You can configure this as well " +"on the PVOutput's end, in Device Settings." msgstr "" -"Kies een interval tussen de uploads. Je kunt dit tevens instellen aan de kant " -"van PVOutput, onder Device Settings." +"Kies een interval tussen de uploads. Je kunt dit tevens instellen aan de " +"kant van PVOutput, onder Device Settings." #: ../../pvoutput.rst:39 msgid "" -"Optionally, choose an upload delay X (in minutes). If set, DSMR-reader will " -"not use data of the past X minutes." +"Optionally, choose an upload delay X (in minutes). If set, DSMR-reader " +"will not use data of the past X minutes." msgstr "" -"Voer optioneel een uploadvertraging X in. Wanneer ingesteld, zal DSMR-reader " -"geen data gebruiken van de afgelopen X minuten." +"Voer optioneel een uploadvertraging X in. Wanneer ingesteld, zal DSMR-" +"reader geen data gebruiken van de afgelopen X minuten." #: ../../pvoutput.rst:40 msgid "" -"Optionally, you can choose to enter a **processing delay in minutes** for " -"PVOutput. Please note that PVOutput will only allow this when you have a **" -"\"Donation\" account** on their website. If you do not have one, they will " -"reject each API call you make, until you disable (clear) this option in DSMR-" -"reader." +"Optionally, you can choose to enter a **processing delay in minutes** " +"for PVOutput. Please note that PVOutput will only allow this when you " +"have a **\"Donation\" account** on their website. If you do not have " +"one, they will reject each API call you make, until you disable (clear) " +"this option in DSMR-reader." msgstr "" -"Optioneel kun je ervoor kiezen om PVOutput de gegevens **vertraagd te laten " -"verwerken**. Let op, dit is een feature van PVOutput die alleen beschikbaar is " -"wanneer je daar een **\"Donatie\" account** hebt. Indien dat niet het geval " -"is, zal PVOutput elke API-call weigeren, totdat je dit uitschakelt (veld " -"leegmaakt) in DSMR-reader." +"Optioneel kun je ervoor kiezen om PVOutput de gegevens **vertraagd te " +"laten verwerken**. Let op, dit is een feature van PVOutput die alleen " +"beschikbaar is wanneer je daar een **\"Donatie\" account** hebt. Indien " +"dat niet het geval is, zal PVOutput elke API-call weigeren, totdat je " +"dit uitschakelt (veld leegmaakt) in DSMR-reader." #: ../../pvoutput.rst:47 msgid "" -"If you configured everything correctly, you should see some addional data in " -"PVOutput listed under \"Your Outputs\" momentarily." +"If you configured everything correctly, you should see some addional " +"data in PVOutput listed under \"Your Outputs\" momentarily." msgstr "" -"Indien je alles correct hebt ingesteld, zul je op korte termijn extra gegevens " -"moeten zien in PVOutput onder \"Your Outputs\"." +"Indien je alles correct hebt ingesteld, zul je op korte termijn extra " +"gegevens moeten zien in PVOutput onder \"Your Outputs\"." diff --git a/docs/locale/nl/LC_MESSAGES/requirements.mo b/docs/locale/nl/LC_MESSAGES/requirements.mo index c4ab5f868..e9ce2d8f6 100644 Binary files a/docs/locale/nl/LC_MESSAGES/requirements.mo and b/docs/locale/nl/LC_MESSAGES/requirements.mo differ diff --git a/docs/locale/nl/LC_MESSAGES/requirements.po b/docs/locale/nl/LC_MESSAGES/requirements.po index bd7632fae..da7edc578 100644 --- a/docs/locale/nl/LC_MESSAGES/requirements.po +++ b/docs/locale/nl/LC_MESSAGES/requirements.po @@ -9,18 +9,23 @@ msgstr "" "Project-Id-Version: DSMR Reader 1.x\n" "Report-Msgid-Bugs-To: \n" "Last-Translator: \n" +"Language: nl\n" "Language-Team: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.3.4\n" -"Language: nl\n" +"Generated-By: Babel 2.5.1\n" "X-Generator: Poedit 1.8.7.1\n" #: ../../requirements.rst:2 msgid "Requirements" msgstr "Vereisten" +#: ../../requirements.rst:7 +msgid "Contents" +msgstr "Inhoudsopgave" + #: ../../requirements.rst:10 msgid "OS / hardware" msgstr "OS / hardware" @@ -31,8 +36,8 @@ msgstr "**Raspbian OS of Debian-gebaseerd OS**" #: ../../requirements.rst:13 msgid "" -"Recommended and tested with, but any OS satisfying the requirements should " -"do fine." +"Recommended and tested with, but any OS satisfying the requirements should do " +"fine." msgstr "" "Aanbevolen en mee getest, al zou elk OS die dezelfde vereisten ondersteunt " "prima moeten zijn." @@ -81,26 +86,34 @@ msgid "Python" msgstr "Python" #: ../../requirements.rst:36 -msgid "**Python 3.4+**" -msgstr "**Python 3.4+**" +msgid "**Python 3.5+**" +msgstr "**Python 3.5+**" #: ../../requirements.rst:40 msgid "" "Support for ``Python 3.3`` has been **discontinued** since ``DSMR-reader " "v1.5`` (due to Django)." msgstr "" -"Ondersteuning voor ``Python 3.3`` is **vervallen** sinds ``DSMR-reader " -"v1.5`` (vanwege Django)." +"Ondersteuning voor ``Python 3.3`` is **vervallen** sinds ``DSMR-reader v1.5`` " +"(vanwege Django)." + +#: ../../requirements.rst:41 +msgid "" +"Support for ``Python 3.4`` has been **discontinued** since ``DSMR-reader " +"v2.0`` (due to Django)." +msgstr "" +"Ondersteuning voor ``Python 3.4`` is **vervallen** sinds ``DSMR-reader " +"v2.0`` (vanwege Django)." -#: ../../requirements.rst:44 +#: ../../requirements.rst:45 msgid "Database" msgstr "Database" -#: ../../requirements.rst:46 +#: ../../requirements.rst:47 msgid "**PostgreSQL 9+**" msgstr "**PostgreSQL 9+**" -#: ../../requirements.rst:50 +#: ../../requirements.rst:51 msgid "" "Support for ``MySQL`` has been **deprecated** since ``DSMR-reader v1.6`` and " "will be discontinued completely in a later release. Please use a PostgreSQL " @@ -112,19 +125,19 @@ msgstr "" "daarom PostgreSQL. Gebruikers die dit project al op MySQL draaien krijgen in " "de toekomst ondersteuning om te migreren." -#: ../../requirements.rst:55 +#: ../../requirements.rst:56 msgid "Disk space" msgstr "Schijfruimte" -#: ../../requirements.rst:57 +#: ../../requirements.rst:58 msgid "" "**Minimal 1 GB of disk space on RaspberryPi (card)** (for application " "installation & virtualenv)." msgstr "" -"**Minimaal 1 GB schijfruimte vereist op RaspberryPi (SD-kaart)** (ten " -"behoeve van de applicatie en VirtualEnv)." +"**Minimaal 1 GB schijfruimte vereist op RaspberryPi (SD-kaart)** (ten behoeve " +"van de applicatie en VirtualEnv)." -#: ../../requirements.rst:59 +#: ../../requirements.rst:60 msgid "" "More disk space is required for storing all reader data captured (optional). " "I generally advise to use a 8+ GB SD card." @@ -132,7 +145,7 @@ msgstr "" "Meer schijfruimte is nodig voor het opslaan van alle metingen (optioneel). " "Over het algemeen adviseer ik minimaal een 8 GB SD-kaart." -#: ../../requirements.rst:60 +#: ../../requirements.rst:61 msgid "" "The readings will take about 90+ percent of the disk space. Retention is on " "it's way for a future release in 2017." @@ -140,11 +153,11 @@ msgstr "" "De metingen nemen zo'n 90+ procent van alle schijfruimte in beslag. Er komt " "echter een optie voor retentie in een toekomstige release in 2017." -#: ../../requirements.rst:64 +#: ../../requirements.rst:65 msgid "Cable" msgstr "Kabel" -#: ../../requirements.rst:66 +#: ../../requirements.rst:67 msgid "" "**Smart Meter** with support for **at least DSMR 4.x+** and a **P1 telegram " "port**" @@ -152,28 +165,28 @@ msgstr "" "**Slimme meter** met ondersteuning voor **ten minste DSMR 4.x+** en een **P1 " "telegram poort**" -#: ../../requirements.rst:68 +#: ../../requirements.rst:69 msgid "Tested so far with Landis+Gyr E350, Kaifa." msgstr "Tot nu toe getest met: Landis+Gyr E350, Kaifa." -#: ../../requirements.rst:70 +#: ../../requirements.rst:71 msgid "**Smart meter P1 data cable**" msgstr "**Slimme meter P1 data kabel**" -#: ../../requirements.rst:72 +#: ../../requirements.rst:73 msgid "Can be purchased online and they cost around 15 tot 20 Euro's each." msgstr "Je kunt deze online bestellen voor ongeveer 15 à 20 Euro." -#: ../../requirements.rst:76 +#: ../../requirements.rst:77 msgid "Misc" msgstr "Overige" -#: ../../requirements.rst:78 +#: ../../requirements.rst:79 msgid "**Basic Linux knowledge for deployment, debugging and troubleshooting**" msgstr "" "**Basiskennis Linux voor het uitrollen en mogelijk debuggen van problemen**" -#: ../../requirements.rst:80 +#: ../../requirements.rst:81 msgid "It just really helps if you know what you are doing." msgstr "Het scheelt nu eenmaal een hoop wanneer je weet waar je mee bezig bent." diff --git a/docs/locale/nl/LC_MESSAGES/retention.mo b/docs/locale/nl/LC_MESSAGES/retention.mo index b54cb61b8..3fdd252af 100644 Binary files a/docs/locale/nl/LC_MESSAGES/retention.mo and b/docs/locale/nl/LC_MESSAGES/retention.mo differ diff --git a/docs/locale/nl/LC_MESSAGES/retention.po b/docs/locale/nl/LC_MESSAGES/retention.po index 6c8320f5d..1cd40d45b 100644 --- a/docs/locale/nl/LC_MESSAGES/retention.po +++ b/docs/locale/nl/LC_MESSAGES/retention.po @@ -45,6 +45,10 @@ msgstr "" "gegevens. Let op: Het inschakelen van deze feature **gooit niet alle metingen " "weg**, want **van elk uur wordt de eerste en laatste meting bewaard**." +#: ../../retention.rst:11 +msgid "Contents" +msgstr "Inhoudsopgave" + #: ../../retention.rst:14 msgid "Notes / warnings" msgstr "Opmerkingen / waarschuwingen" diff --git a/docs/locale/nl/LC_MESSAGES/settings.mo b/docs/locale/nl/LC_MESSAGES/settings.mo index 39a1bc311..55750f4c8 100644 Binary files a/docs/locale/nl/LC_MESSAGES/settings.mo and b/docs/locale/nl/LC_MESSAGES/settings.mo differ diff --git a/docs/locale/nl/LC_MESSAGES/settings.po b/docs/locale/nl/LC_MESSAGES/settings.po index 835528d6e..d04f53c5c 100644 --- a/docs/locale/nl/LC_MESSAGES/settings.po +++ b/docs/locale/nl/LC_MESSAGES/settings.po @@ -505,11 +505,11 @@ msgstr "``DSMRREADER_STATUS_READING_OFFSET_MINUTES``" #: ../../settings.rst:238 msgid "" -"Maximum interval in hours allowed since the latest reading, before ringing any " +"Maximum interval in minutes allowed since the latest reading, before ringing any " "alarms." msgstr "" -"Maximale toegestane interval, in aantal uren, sinds de laatste meting, voordat " -"er alarmbellen af gaan." +"Maximale toegestane interval, in minuten, sinds de laatste meting, voordat er " +"alarmbellen af gaan." #: ../../settings.rst:240 msgid "Defaults to ``DSMRREADER_STATUS_READING_OFFSET_MINUTES = 60``." diff --git a/docs/locale/nl/LC_MESSAGES/troubleshooting.mo b/docs/locale/nl/LC_MESSAGES/troubleshooting.mo index 24a87c4b8..804d23c87 100644 Binary files a/docs/locale/nl/LC_MESSAGES/troubleshooting.mo and b/docs/locale/nl/LC_MESSAGES/troubleshooting.mo differ diff --git a/docs/locale/nl/LC_MESSAGES/troubleshooting.po b/docs/locale/nl/LC_MESSAGES/troubleshooting.po index 2ff1dc0aa..c9356555e 100644 --- a/docs/locale/nl/LC_MESSAGES/troubleshooting.po +++ b/docs/locale/nl/LC_MESSAGES/troubleshooting.po @@ -8,6 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: DSMR Reader 1.x\n" "Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-09-28 23:30+0200\n" +"PO-Revision-Date: 2018-09-28 23:40+0200\n" "Last-Translator: \n" "Language: nl\n" "Language-Team: \n" @@ -27,8 +29,8 @@ msgid "" "If the application happens to stall unexpectedly, you can perform some " "debugging on your end." msgstr "" -"Mocht de applicatie onverwachts stoppen, dan kun je zelf het volgende " -"doen om te kijken of je een oorzaak kan vinden." +"Mocht de applicatie onverwachts stoppen, dan kun je zelf het volgende doen " +"om te kijken of je een oorzaak kan vinden." #: ../../troubleshooting.rst:6 msgid "Status page" @@ -36,84 +38,182 @@ msgstr "Status-pagina" #: ../../troubleshooting.rst:7 msgid "" -"The first place to look at is the Status page in the application. Does " -"it display any error or is it lagging data processing?" +"The first place to look at is the Status page in the application. Does it " +"display any errors or is it lagging data processing?" msgstr "" "Als eerste kun je het beste naar de Status-pagina gaan. Staat daar een " "foutmelding of loopt de dataverwerking achter?" +#: ../../troubleshooting.rst:12 +msgid "Logging" +msgstr "Logging" + #: ../../troubleshooting.rst:13 +msgid "" +"Starting from DSMR-reader ``v1.24`` all logging output has been reduced by " +"default. You can, however, have the application log more verbose by " +"increasing the logging level." +msgstr "" +"Vanaf DSMR-reader ``v1.24`` is alle logging van de applicatie beperkt. Je " +"kunt de applicatie echter veel meer informatie laten loggen, door het " +"loglevel aan te passen." + +#: ../../troubleshooting.rst:16 +msgid "Open the ``dsmrreader/settings.py`` file and look for the code below::" +msgstr "Open ``dsmrreader/settings.py`` en zoek de onderstaande code::" + +#: ../../troubleshooting.rst:28 +msgid "" +"**If you cannot find the code above, you've probably installed DSMR-reader " +"before v1.24 and you can paste the code yourself in the file.**" +msgstr "" +"**Kun je de bovenstaande code niet kunt vinden? Dan heb je DSMR-reader " +"waarschijnlijk vóór v1.24 geïnstalleerd. In dat geval kun je de code zelf in " +"het bestand plakken.**" + +#: ../../troubleshooting.rst:29 +msgid "Now remove the ``#`` and set the value to ``DEBUG``." +msgstr "Haal de ``#`` weg en zet de waarde op ``DEBUG``." + +#: ../../troubleshooting.rst:30 +msgid "" +"Make sure that you execute ``./post-deploy.sh`` after changing the settings." +msgstr "" +"Zorg ervoor dat je ``./post-deploy.sh`` uitvoert na het doorvoeren van " +"wijzigingen." + +#: ../../troubleshooting.rst:31 +msgid "Read below for more information about where to find the logging output." +msgstr "Lees hieronder meer over waar je welke informatie kunt vinden." + +#: ../../troubleshooting.rst:35 msgid "Supervisor" msgstr "Supervisor" -#: ../../troubleshooting.rst:14 +#: ../../troubleshooting.rst:36 msgid "" -"You can also view the Supervisor logfiles, depending on whether your " -"datalogger, webinterface or the data processing is broken. The logfiles " -"are located by default in ``/var/log/supervisor/``. You should find logs " -"here regarding the ``dsmr_datalogger``, ``dsmr_backend`` and " -"``dsmr_webinterface`` processes." +"You can view the Supervisor logfiles, depending on whether your datalogger, " +"webinterface or the data processing is broken." msgstr "" -"Je kunt ook de Supervisor-logfiles bekijken, afhankelijk van of je " -"datalogger, webinterface of dataverwerking gestopt is. De logfiles staan " -"standaard in ``/var/log/supervisor/``. Je vindt hier logfiles voor de " -"``dsmr_datalogger``, ``dsmr_backend`` en ``dsmr_webinterface`` processen." +"Je kunt de Supervisor-logfiles bekijken, afhankelijk of je datalogger, " +"webinterface of backend-proces kapot is." + +#: ../../troubleshooting.rst:38 ../../troubleshooting.rst:50 +msgid "The logfiles are located by default in:" +msgstr "De logbestanden zijn standaard te vinden in:" -#: ../../troubleshooting.rst:18 +#: ../../troubleshooting.rst:40 msgid "" -"Another option is to tail the (recent) logs in Supervisor. Enter the " -"control panel with ``sudo supervisorctl`` and type ``tail -f " -"PROCESSNAME`` to follow one. The process names are the ones you see when " -"you started the control panel, or you can just enter ``status`` to see " -"them. You can also use ``start``, ``stop`` or ``restart`` on the " -"processes to give control them." +"``/var/log/supervisor/dsmr_backend.log`` *(Logs regarding the backend " +"process)*" msgstr "" -"Eventueel kun je ook de (recente) logfiles direct in Supervisor " -"bekijken. Ga naar het beheerpaneel met ``sudo supervisorctl`` en typ " -"``tail -f PROCESNAAM`` om er een te volgen. De procesnamen zijn degene " -"die je ziet wanneer je het beheerpaneel opende. Je kunt ook ``status`` " -"typen om ze allemaal te weergeven. Gebruik ``start``, ``stop`` of " -"``restart`` om de processen verder te beheren." - -#: ../../troubleshooting.rst:26 +"``/var/log/supervisor/dsmr_backend.log`` *(Logs over het backend proces)*" + +#: ../../troubleshooting.rst:41 +msgid "" +"``/var/log/supervisor/dsmr_datalogger.log`` *(Logs regarding the datalogger)*" +msgstr "" +"``/var/log/supervisor/dsmr_datalogger.log`` *(Logs over de datalogger)*" + +#: ../../troubleshooting.rst:42 +msgid "" +"``/var/log/supervisor/dsmr_webinterface.log`` *(Logs regarding the " +"webinterface)*" +msgstr "" +"``/var/log/supervisor/dsmr_webinterface.log`` *(Logs over de webinterface)*" + +#: ../../troubleshooting.rst:46 msgid "Appplication / Django" msgstr "Appplicatie / Django" -#: ../../troubleshooting.rst:27 +#: ../../troubleshooting.rst:47 msgid "" "The application has it's own logfiles as well. You can find them in the " -"``logs`` directory inside the project folder. The ``django.log`` will " -"list any internal errors regarding the Django framework it's using. The " -"other logfile ``dsmrreader.log`` contains application logging, if " -"enabled." +"``logs`` directory inside the project folder." +msgstr "" +"De applicatie heeft ook eigen logfiles. Je kunt deze vinden in de ``logs`` " +"directory binnen de projectmap." + +#: ../../troubleshooting.rst:52 +msgid "" +"``/home/dsmr/dsmr-reader/logs/django.log`` *(lists any internal errors " +"regarding the Django framework it's using)*" msgstr "" -"De applicatie heeft zelf ook logfiles. Deze vindt je in de ``logs`` " -"directory binnenin het project. De ``django.log`` bevat alle interne " -"fouten m.b.t. het gebruikte Django framework. De andere logfile, " -"``dsmrreader.log`` bevat doorgaans alle ontvangen metingen, in base64-" -"formaat en wanneer ingeschakeld." +"``/home/dsmr/dsmr-reader/logs/django.log`` *(bevat interne foutmeldingen " +"over het gebruikte Django framework)*" -#: ../../troubleshooting.rst:32 +#: ../../troubleshooting.rst:53 +msgid "" +"``/home/dsmr/dsmr-reader/logs/dsmrreader.log`` *(contains application " +"logging, if enabled)*" +msgstr "" +"``/home/dsmr/dsmr-reader/logs/dsmrreader.log`` *(bevat applicatie-logging, " +"wanneer ingeschakeld)*" + +#: ../../troubleshooting.rst:57 +msgid "Telegrams" +msgstr "Telegrammen" + +#: ../../troubleshooting.rst:58 msgid "" "You can log all telegrams received, in base64 format, by adding " "``DSMRREADER_LOG_TELEGRAMS = True`` to your ``dsmrreader/settings.py`` " -"config. Make sure that you execute ``./post-deploy.sh`` after changing " -"the settings. It should now log the telegrams into ``dsmrreader.log``." +"config. Make sure that you execute ``./post-deploy.sh`` after changing the " +"settings. It should now log the telegrams into ``dsmrreader.log``." msgstr "" -"Je kunt alle ontvangen telegrammen loggen, in base64-formaat, door de " -"regel ``DSMRREADER_LOG_TELEGRAMS = True`` toe te voegen aan je " -"instellingen in ``dsmrreader/settings.py``. Zorg er wel voor dat je ``./" -"post-deploy.sh`` uitvoert na het doorvoeren van je wijzigingen. Als het " -"goed is worden nu de telegrammen gelogd in ``dsmrreader.log``." +"Je kunt alle ontvangen telegrammen loggen, in base64-formaat, door de regel " +"``DSMRREADER_LOG_TELEGRAMS = True`` toe te voegen aan je instellingen in " +"``dsmrreader/settings.py``. Zorg er wel voor dat je ``./post-deploy.sh`` " +"uitvoert na het doorvoeren van je wijzigingen. Als het goed is worden nu de " +"telegrammen gelogd in ``dsmrreader.log``." -#: ../../troubleshooting.rst:38 +#: ../../troubleshooting.rst:64 msgid "Contact" msgstr "Contact" -#: ../../troubleshooting.rst:39 +#: ../../troubleshooting.rst:65 msgid "" -"Are you unable to resolve your problem or do you need any help? :doc:" -"`More information can be found here`." +"Are you unable to resolve your problem or do you need any help? :doc:`More " +"information can be found here`." msgstr "" -"Kom je er toch niet uit of heb je hulp nodig? :doc:`Meer informatie kun " -"je hier vinden`." +"Kom je er toch niet uit of heb je hulp nodig? :doc:`Meer informatie kun je " +"hier vinden`." + +#~ msgid "" +#~ "You can also view the Supervisor logfiles, depending on whether your " +#~ "datalogger, webinterface or the data processing is broken. The logfiles " +#~ "are located by default in ``/var/log/supervisor/``. You should find logs " +#~ "here regarding the ``dsmr_datalogger``, ``dsmr_backend`` and " +#~ "``dsmr_webinterface`` processes." +#~ msgstr "" +#~ "Je kunt ook de Supervisor-logfiles bekijken, afhankelijk van of je " +#~ "datalogger, webinterface of dataverwerking gestopt is. De logfiles staan " +#~ "standaard in ``/var/log/supervisor/``. Je vindt hier logfiles voor de " +#~ "``dsmr_datalogger``, ``dsmr_backend`` en ``dsmr_webinterface`` processen." + +#~ msgid "" +#~ "Another option is to tail the (recent) logs in Supervisor. Enter the " +#~ "control panel with ``sudo supervisorctl`` and type ``tail -f " +#~ "PROCESSNAME`` to follow one. The process names are the ones you see when " +#~ "you started the control panel, or you can just enter ``status`` to see " +#~ "them. You can also use ``start``, ``stop`` or ``restart`` on the " +#~ "processes to give control them." +#~ msgstr "" +#~ "Eventueel kun je ook de (recente) logfiles direct in Supervisor bekijken. " +#~ "Ga naar het beheerpaneel met ``sudo supervisorctl`` en typ ``tail -f " +#~ "PROCESNAAM`` om er een te volgen. De procesnamen zijn degene die je ziet " +#~ "wanneer je het beheerpaneel opende. Je kunt ook ``status`` typen om ze " +#~ "allemaal te weergeven. Gebruik ``start``, ``stop`` of ``restart`` om de " +#~ "processen verder te beheren." + +#~ msgid "" +#~ "The application has it's own logfiles as well. You can find them in the " +#~ "``logs`` directory inside the project folder. The ``django.log`` will " +#~ "list any internal errors regarding the Django framework it's using. The " +#~ "other logfile ``dsmrreader.log`` contains application logging, if enabled." +#~ msgstr "" +#~ "De applicatie heeft zelf ook logfiles. Deze vindt je in de ``logs`` " +#~ "directory binnenin het project. De ``django.log`` bevat alle interne " +#~ "fouten m.b.t. het gebruikte Django framework. De andere logfile, " +#~ "``dsmrreader.log`` bevat doorgaans alle ontvangen metingen, in base64-" +#~ "formaat en wanneer ingeschakeld." diff --git a/docs/requirements.rst b/docs/requirements.rst index 384317b07..715c7d3c6 100644 --- a/docs/requirements.rst +++ b/docs/requirements.rst @@ -33,11 +33,12 @@ OS / hardware Python ^^^^^^ -**Python 3.4+** +**Python 3.5+** .. warning:: - Support for ``Python 3.3`` has been **discontinued** since ``DSMR-reader v1.5`` (due to Django). + - Support for ``Python 3.3`` has been **discontinued** since ``DSMR-reader v1.5`` (due to Django). + - Support for ``Python 3.4`` has been **discontinued** since ``DSMR-reader v2.0`` (due to Django). Database diff --git a/docs/settings.rst b/docs/settings.rst index 9ecb179ce..86f6d9d67 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -235,7 +235,7 @@ Defaults to ``DSMRREADER_RECONNECT_DATABASE = True``. ``DSMRREADER_STATUS_READING_OFFSET_MINUTES`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Maximum interval in hours allowed since the latest reading, before ringing any alarms. +Maximum interval in minutes allowed since the latest reading, before ringing any alarms. Defaults to ``DSMRREADER_STATUS_READING_OFFSET_MINUTES = 60``. diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst index 02363b499..0e37c0840 100644 --- a/docs/troubleshooting.rst +++ b/docs/troubleshooting.rst @@ -5,34 +5,60 @@ If the application happens to stall unexpectedly, you can perform some debugging Status page ----------- The first place to look at is the Status page in the application. -Does it display any error or is it lagging data processing? +Does it display any errors or is it lagging data processing? +Logging +------- +Starting from DSMR-reader ``v1.24`` all logging output has been reduced by default. +You can, however, have the application log more verbose by increasing the logging level. + +* Open the ``dsmrreader/settings.py`` file and look for the code below:: + + """ + Enable and change the logging level below to alter the verbosity of the (backend) command(s). + - DEBUG: Log everything. + - INFO: Log most things. + - WARNING (default): Log only warnings and errors. + + Restart the commands in Supervisor to apply any changes. + """ + # LOGGING['loggers']['commands']['level'] = 'DEBUG' + +* **If you cannot find the code above, you've probably installed DSMR-reader before v1.24 and you can paste the code yourself in the file.** +* Now remove the ``#`` and set the value to ``DEBUG``. +* Make sure that you execute ``./post-deploy.sh`` after changing the settings. +* Read below for more information about where to find the logging output. + Supervisor ---------- -You can also view the Supervisor logfiles, depending on whether your datalogger, webinterface or the data processing is broken. -The logfiles are located by default in ``/var/log/supervisor/``. -You should find logs here regarding the ``dsmr_datalogger``, ``dsmr_backend`` and ``dsmr_webinterface`` processes. +You can view the Supervisor logfiles, depending on whether your datalogger, webinterface or the data processing is broken. -Another option is to tail the (recent) logs in Supervisor. -Enter the control panel with ``sudo supervisorctl`` and type ``tail -f PROCESSNAME`` to follow one. -The process names are the ones you see when you started the control panel, or you can just enter ``status`` to see them. -You can also use ``start``, ``stop`` or ``restart`` on the processes to give control them. +The logfiles are located by default in: +* ``/var/log/supervisor/dsmr_backend.log`` *(Logs regarding the backend process)* +* ``/var/log/supervisor/dsmr_datalogger.log`` *(Logs regarding the datalogger)* +* ``/var/log/supervisor/dsmr_webinterface.log`` *(Logs regarding the webinterface)* Appplication / Django --------------------- The application has it's own logfiles as well. -You can find them in the ``logs`` directory inside the project folder. -The ``django.log`` will list any internal errors regarding the Django framework it's using. -The other logfile ``dsmrreader.log`` contains application logging, if enabled. +You can find them in the ``logs`` directory inside the project folder. -You can log all telegrams received, in base64 format, by adding ``DSMRREADER_LOG_TELEGRAMS = True`` to your ``dsmrreader/settings.py`` config. -Make sure that you execute ``./post-deploy.sh`` after changing the settings. It should now log the telegrams into ``dsmrreader.log``. +The logfiles are located by default in: + +* ``/home/dsmr/dsmr-reader/logs/django.log`` *(lists any internal errors regarding the Django framework it's using)* +* ``/home/dsmr/dsmr-reader/logs/dsmrreader.log`` *(contains application logging, if enabled)* +Telegrams +--------- +You can log all telegrams received, in base64 format, by adding ``DSMRREADER_LOG_TELEGRAMS = True`` to your ``dsmrreader/settings.py`` config. +Make sure that you execute ``./post-deploy.sh`` after changing the settings. +It should now log the telegrams into ``dsmrreader.log``. + Contact ------- diff --git a/dsmr_api/urls/v1.py b/dsmr_api/urls/v1.py index a7d6ca9ae..123f512d4 100644 --- a/dsmr_api/urls/v1.py +++ b/dsmr_api/urls/v1.py @@ -1,5 +1,5 @@ -from django.conf.urls import url from django.views.decorators.csrf import csrf_exempt +from django.urls.conf import path from dsmr_api.views.v1 import DataloggerDsmrReading @@ -7,5 +7,5 @@ app_name = 'api-v1' urlpatterns = [ - url(r'^datalogger/dsmrreading$', csrf_exempt(DataloggerDsmrReading.as_view()), name='datalogger-dsmrreading'), + path('datalogger/dsmrreading', csrf_exempt(DataloggerDsmrReading.as_view()), name='datalogger-dsmrreading'), ] diff --git a/dsmr_api/urls/v2.py b/dsmr_api/urls/v2.py index 3c6589f8c..7627bd9cf 100644 --- a/dsmr_api/urls/v2.py +++ b/dsmr_api/urls/v2.py @@ -1,4 +1,5 @@ -from django.conf.urls import url, include +from django.conf.urls import include +from django.urls.conf import path from dsmr_api.views import v2 as views @@ -6,40 +7,32 @@ app_name = 'api-v2' datalogger_url_patterns = [ - url(r'^dsmrreading$', views.DsmrReadingViewSet.as_view({ + path('dsmrreading', views.DsmrReadingViewSet.as_view({ 'get': 'list', 'post': 'create', }), name='dsmrreading'), ] consumption_url_patterns = [ - url(r'^electricity$', views.ElectricityConsumptionViewSet.as_view({ - 'get': 'list', - }), name='electricity-consumption'), - url(r'^gas$', views.GasConsumptionViewSet.as_view({ - 'get': 'list', - }), name='gas-consumption'), - url(r'^today$', views.TodayConsumptionView.as_view(), name='today-consumption'), - url(r'^electricity-live$', views.ElectricityLiveView.as_view(), name='electricity-live'), + path('electricity', views.ElectricityConsumptionViewSet.as_view({'get': 'list'}), name='electricity-consumption'), + path('gas', views.GasConsumptionViewSet.as_view({'get': 'list'}), name='gas-consumption'), + path('today', views.TodayConsumptionView.as_view(), name='today-consumption'), + path('electricity-live', views.ElectricityLiveView.as_view(), name='electricity-live'), ] statistics_url_patterns = [ - url(r'^day$', views.DayStatisticsViewSet.as_view({ - 'get': 'list', - }), name='day-statistics'), - url(r'^hour$', views.HourStatisticsViewSet.as_view({ - 'get': 'list', - }), name='hour-statistics'), + path('day', views.DayStatisticsViewSet.as_view({'get': 'list'}), name='day-statistics'), + path('hour', views.HourStatisticsViewSet.as_view({'get': 'list'}), name='hour-statistics'), ] application_url_patterns = [ - url(r'^version$', views.VersionView.as_view(), name='application-version'), - url(r'^status$', views.StatusView.as_view(), name='application-status'), + path('version', views.VersionView.as_view(), name='application-version'), + path('status', views.StatusView.as_view(), name='application-status'), ] urlpatterns = [ - url(r'^datalogger/', include(datalogger_url_patterns)), - url(r'^consumption/', include(consumption_url_patterns)), - url(r'^statistics/', include(statistics_url_patterns)), - url(r'^application/', include(application_url_patterns)), + path('datalogger/', include(datalogger_url_patterns)), + path('consumption/', include(consumption_url_patterns)), + path('statistics/', include(statistics_url_patterns)), + path('application/', include(application_url_patterns)), ] diff --git a/dsmr_api/views/v2.py b/dsmr_api/views/v2.py index 6beacff87..7cbb6c8ef 100644 --- a/dsmr_api/views/v2.py +++ b/dsmr_api/views/v2.py @@ -19,6 +19,13 @@ class DsmrReadingViewSet(mixins.CreateModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet): + """ + list: + Returns a list of DSMR-readings. + + create: + Creates a new DSMR-reading. + """ FIELD = 'timestamp' queryset = DsmrReading.objects.all() serializer_class = DsmrReadingSerializer @@ -28,7 +35,7 @@ class DsmrReadingViewSet(mixins.CreateModelMixin, mixins.ListModelMixin, viewset class TodayConsumptionView(APIView): - """ Returns the consumption (so far) of the current day. """ + """ Returns the consumption of the current day (so far). """ IGNORE_FIELDS = ( 'electricity1_start', 'electricity2_start', 'electricity1_end', 'electricity2_end', 'notes', 'gas_start', 'gas_end', 'electricity1_returned_start', 'electricity2_returned_start', 'electricity1_returned_end', @@ -59,11 +66,13 @@ def get(self, request): class ElectricityLiveView(APIView): + """ Returns the current electricity usage. """ def get(self, request): return Response(dsmr_consumption.services.live_electricity_consumption(use_naturaltime=False)) class ElectricityConsumptionViewSet(viewsets.ReadOnlyModelViewSet): + """ Lists electricity consumption. """ FIELD = 'read_at' queryset = ElectricityConsumption.objects.all() serializer_class = ElectricityConsumptionSerializer @@ -73,6 +82,7 @@ class ElectricityConsumptionViewSet(viewsets.ReadOnlyModelViewSet): class GasConsumptionViewSet(viewsets.ReadOnlyModelViewSet): + """ Lists gas consumption. """ FIELD = 'read_at' queryset = GasConsumption.objects.all() serializer_class = GasConsumptionSerializer @@ -82,6 +92,7 @@ class GasConsumptionViewSet(viewsets.ReadOnlyModelViewSet): class DayStatisticsViewSet(viewsets.ReadOnlyModelViewSet): + """ Lists day statistics. """ FIELD = 'day' queryset = DayStatistics.objects.all() serializer_class = DayStatisticsSerializer @@ -91,6 +102,7 @@ class DayStatisticsViewSet(viewsets.ReadOnlyModelViewSet): class HourStatisticsViewSet(viewsets.ReadOnlyModelViewSet): + """ Lists hour statistics. """ FIELD = 'hour_start' queryset = HourStatistics.objects.all() serializer_class = HourStatisticsSerializer @@ -100,6 +112,7 @@ class HourStatisticsViewSet(viewsets.ReadOnlyModelViewSet): class VersionView(APIView): + """ Returns the current version of DSMR-reader. """ def get(self, request): return Response({ 'version': '{}.{}.{}'.format(* settings.DSMRREADER_RAW_VERSION[:3]), @@ -107,5 +120,6 @@ def get(self, request): class StatusView(APIView): + """ Returns an overview of all services and their status. Similar to the Status page in the webinterface. """ def get(self, request): return Response(dsmr_backend.services.status_info()) diff --git a/dsmr_backend/management/commands/development_reset.py b/dsmr_backend/management/commands/development_reset.py index 6924f5e1e..d1bf9caf8 100644 --- a/dsmr_backend/management/commands/development_reset.py +++ b/dsmr_backend/management/commands/development_reset.py @@ -7,6 +7,7 @@ from dsmr_notification.models.settings import NotificationSetting from dsmr_consumption.models.settings import ConsumptionSettings from dsmr_mqtt.models.settings.broker import MQTTBrokerSettings +from dsmr_pvoutput.models.settings import PVOutputAPISettings from dsmr_mindergas.models.settings import MinderGasSettings from dsmr_frontend.models.message import Notification from dsmr_api.models import APISettings @@ -42,6 +43,7 @@ def handle(self, **options): MQTTBrokerSettings.objects.update( port=8883, secure=MQTTBrokerSettings.SECURE_CERT_NONE, debug=True, username='user', password='password' ) + PVOutputAPISettings.objects.update(auth_token=None, system_identifier=None) queue.Message.objects.all().delete() Notification.objects.update(read=True) Notification.objects.create(message='Development reset completed.') diff --git a/dsmr_backend/management/commands/dsmr_backend.py b/dsmr_backend/management/commands/dsmr_backend.py index 67e3b6038..54e6255cd 100644 --- a/dsmr_backend/management/commands/dsmr_backend.py +++ b/dsmr_backend/management/commands/dsmr_backend.py @@ -1,14 +1,16 @@ -import traceback +import logging from django.core.management.base import BaseCommand from django.utils.translation import ugettext as _ -from django.utils import timezone from django.conf import settings from dsmr_backend.mixins import InfiniteManagementCommandMixin import dsmr_backend.signals +logger = logging.getLogger('commands') + + class Command(InfiniteManagementCommandMixin, BaseCommand): help = _('Generates a generic event triggering apps for backend operations, cron-like.') name = __name__ # Required for PID file. @@ -16,19 +18,12 @@ class Command(InfiniteManagementCommandMixin, BaseCommand): def run(self, **options): """ InfiniteManagementCommandMixin listens to handle() and calls run() in a loop. """ - self.stdout.write('{}: Calling backend services'.format(timezone.localtime(timezone.now()))) + logger.info('Calling backend services') # send_robust() guarantees the every listener receives this signal. responses = dsmr_backend.signals.backend_called.send_robust(None) - signal_failures = [] - for current_receiver, current_response in responses: + for __, current_response in responses: if isinstance(current_response, Exception): - # Add and print traceback to help debugging any issues raised. - exception_traceback = traceback.format_tb(current_response.__traceback__, limit=100) - exception_traceback = "\n".join(exception_traceback) - - self.stdout.write(' >>> Uncaught exception :: {}'.format(current_response)) - self.stdout.write(' >>> {} :: {}'.format(current_receiver, exception_traceback)) - self.stderr.write(exception_traceback) - signal_failures.append(exception_traceback) + logger.error('Uncaught exception') + logger.exception(current_response) diff --git a/dsmr_backend/mixins.py b/dsmr_backend/mixins.py index ac555131a..2cf2f9db8 100644 --- a/dsmr_backend/mixins.py +++ b/dsmr_backend/mixins.py @@ -1,3 +1,4 @@ +import logging import signal import time import os @@ -9,6 +10,9 @@ from django.db import connection +logger = logging.getLogger('commands') + + class StopInfiniteRun(StopIteration): """ Triggers InfiniteManagementCommandMixin to stop the current loop. """ pass @@ -41,7 +45,7 @@ def handle(self, **options): self.run_loop(**options) self.shutdown() - self.stdout.write('Exited') + logger.info('Exited') def run_loop(self, **options): """ Runs in an infinite loop, until we're signaled to stop. """ @@ -51,15 +55,14 @@ def run_loop(self, **options): # We simply keep executing the management command until we are told otherwise. self._keep_alive = True - self.stdout.write('Starting infinite command loop...') # Just to make sure it gets printed. + logger.debug('Starting infinite command loop...') # Just to make sure it gets printed. while self._keep_alive: self.run_once(**options) # Do not hammer. if self.sleep_time is not None: - self.stdout.write('Sleeping {} sec(s)'.format(self.sleep_time)) - self.stdout.write('') + logger.debug('Sleeping %s sec(s)', self.sleep_time) time.sleep(self.sleep_time) # Check database connection after each run. This will force Django to reconnect as well, when having issues. @@ -75,11 +78,11 @@ def run_once(self, **options): raise except StopInfiniteRun: # Explicit exit. - self.stdout.write(' [i] Detected StopInfiniteRun exception') + logger.warning(' [i] Detected StopInfiniteRun exception') self._stop() except Exception as error: # Unforeseen errors. - self.stdout.write(' [!] Exception raised in run(): {}'.format(error)) + logger.warning(' [!] Exception raised in run(): %s', error) def initialize(self): """ Called once. Override and handle any initialization required. """ @@ -99,13 +102,13 @@ def __del__(self): def _signal_handler(self, signum, frame): # If we get called, then we must gracefully exit. - self.stdout.write('Detected signal #{}'.format(signum)) + logger.warning('Detected signal #%s', signum) self._stop() def _stop(self): """ Sets the flag for ending the command on next flag check. """ self._keep_alive = False - self.stdout.write('Exiting on next run...') + logger.warning('Exiting on next run...') def _write_pid_file(self): self._pid_file = os.path.join( @@ -128,6 +131,12 @@ def __init__(self, *args, **kwargs): super(ReadOnlyAdminModel, self).__init__(*args, **kwargs) self.readonly_fields = [x.name for x in self.model._meta.get_fields()] + def has_view_permission(self, request, obj=None): + return True + + def has_change_permission(self, request, obj=None): + return False + def has_add_permission(self, request, obj=None): return False diff --git a/dsmr_backend/tests/mixins.py b/dsmr_backend/tests/mixins.py index d1c39f9f1..f281fbed2 100644 --- a/dsmr_backend/tests/mixins.py +++ b/dsmr_backend/tests/mixins.py @@ -5,9 +5,9 @@ class InterceptStdoutMixin(object): """ Supresses stdout for tests. Returns stdout. """ - def _intercept_command_stdout(self, command, **kwargs): + def _intercept_command_stdout(self, command, *args, **kwargs): stdout = StringIO() stderr = StringIO() # Only to mute. - call_command(command, stdout=stdout, stderr=stderr, **kwargs) + call_command(command, stdout=stdout, stderr=stderr, *args, **kwargs) stdout.seek(0) return stdout.read() diff --git a/dsmr_backend/tests/test_backend.py b/dsmr_backend/tests/test_backend.py index 62a6f72af..0bc4fd2fb 100644 --- a/dsmr_backend/tests/test_backend.py +++ b/dsmr_backend/tests/test_backend.py @@ -43,17 +43,17 @@ def _fake_signal_troublemaker(*args, **kwargs): # We must disconnect to prevent other tests from failing, since this is no database action. dsmr_backend.signals.backend_called.disconnect(receiver=_fake_signal_troublemaker) - @mock.patch('traceback.format_tb') - def test_signal_exception_handling(self, format_tb_mock): + @mock.patch('logging.Logger.exception') + def test_signal_exception_handling(self, logging_mock): """ Tests signal exception handling. """ def _fake_signal_troublemaker(*args, **kwargs): raise AssertionError("Crash") dsmr_backend.signals.backend_called.connect(receiver=_fake_signal_troublemaker) - self.assertFalse(format_tb_mock.called) + self.assertFalse(logging_mock.called) self._intercept_command_stdout('dsmr_backend', run_once=True) - self.assertTrue(format_tb_mock.called) + self.assertTrue(logging_mock.called) def test_supported_vendors(self): """ Check whether supported vendors is as expected. """ diff --git a/dsmr_backend/tests/test_services.py b/dsmr_backend/tests/test_services.py index 30ee38f9e..6b5958ab6 100644 --- a/dsmr_backend/tests/test_services.py +++ b/dsmr_backend/tests/test_services.py @@ -256,7 +256,7 @@ class TestIslatestVersion(TestCase): b"__version__ = get_version(VERSION)\n" response_newer = b"from django.utils.version import get_version\n" \ - b"VERSION = (1, 25, 1, 'final', 0)\n" \ + b"VERSION = (2, 99, 0, 'final', 0)\n" \ b"__version__ = get_version(VERSION)\n" def test_true(self, request_mock): diff --git a/dsmr_backup/services/backup.py b/dsmr_backup/services/backup.py index c8f24f8ae..72af5de78 100644 --- a/dsmr_backup/services/backup.py +++ b/dsmr_backup/services/backup.py @@ -1,4 +1,5 @@ import subprocess +import logging import shutil import gzip import os @@ -8,11 +9,13 @@ from django.conf import settings from django.utils import formats - from dsmr_backup.models.settings import BackupSettings import dsmr_backup.services.dropbox +logger = logging.getLogger('commands') + + def check(): """ Checks whether a new backup should be created. Creates one if needed as well. """ backup_settings = BackupSettings.get_solo() @@ -35,7 +38,7 @@ def check(): return # For backend logging in Supervisor. - print(' - Creating new backup.') + logger.info(' - Creating new backup.') create() diff --git a/dsmr_backup/services/dropbox.py b/dsmr_backup/services/dropbox.py index e90f70e2a..b7ed47f6d 100644 --- a/dsmr_backup/services/dropbox.py +++ b/dsmr_backup/services/dropbox.py @@ -1,3 +1,4 @@ +import logging import os from django.utils.translation import ugettext_lazy as gettext @@ -10,6 +11,9 @@ import dsmr_backup.services.backup +logger = logging.getLogger('commands') + + def sync(): dropbox_settings = DropboxSettings.get_solo() @@ -55,7 +59,7 @@ def check_synced_file(file_path, dropbox_settings): upload_chunked(file_path=file_path) except dropbox.exceptions.DropboxException as exception: error_message = str(exception.error) - print(' - Dropbox error: {}'.format(error_message)) + logger.error(' - Dropbox error: %s', error_message) if 'insufficient_space' in error_message: Notification.objects.create(message=gettext( @@ -91,7 +95,7 @@ def check_synced_file(file_path, dropbox_settings): def upload_chunked(file_path): """ Uploads a file in chucks to Dropbox, allowing it to resume on (connection) failure. """ # For backend logging in Supervisor. - print(' - Uploading file to Dropbox: {}.'.format(file_path)) + logger.info(' - Uploading file to Dropbox: %s', file_path) dropbox_settings = DropboxSettings.get_solo() file_name = os.path.split(file_path)[-1] diff --git a/dsmr_consumption/fixtures/dsmr_consumption/test_dsmrreading_v5.json b/dsmr_consumption/fixtures/dsmr_consumption/test_dsmrreading_v5.json index 29098802e..2fa4d68f2 100644 --- a/dsmr_consumption/fixtures/dsmr_consumption/test_dsmrreading_v5.json +++ b/dsmr_consumption/fixtures/dsmr_consumption/test_dsmrreading_v5.json @@ -112,5 +112,27 @@ }, "model": "dsmr_datalogger.dsmrreading", "pk": 104 +}, +{ + "fields": { + "extra_device_timestamp": "2015-11-10T20:03:30Z", + "extra_device_delivered": "1.15", + "electricity_delivered_1": "530.750", + "electricity_returned_2": "123.678", + "electricity_currently_delivered": "1.123", + "timestamp": "2015-11-10T19:42:00Z", + "electricity_returned_1": "0.678", + "electricity_currently_returned": "0.123", + "processed": false, + "electricity_delivered_2": "528.500", + "phase_currently_delivered_l1": "0.123", + "phase_currently_delivered_l2": "0.456", + "phase_currently_delivered_l3": "0.789", + "phase_currently_returned_l1": "0.111", + "phase_currently_returned_l2": "0.333", + "phase_currently_returned_l3": "0.555" + }, + "model": "dsmr_datalogger.dsmrreading", + "pk": 105 } ] \ No newline at end of file diff --git a/dsmr_consumption/services.py b/dsmr_consumption/services.py index a463c9d0f..3d7b7ecf7 100644 --- a/dsmr_consumption/services.py +++ b/dsmr_consumption/services.py @@ -1,5 +1,6 @@ from datetime import time from decimal import Decimal, ROUND_UP +import logging import pytz from django.contrib.humanize.templatetags.humanize import naturaltime @@ -16,6 +17,9 @@ from dsmr_datalogger.models.statistics import MeterStatistics +logger = logging.getLogger('commands') + + def compact_all(): """ Compacts all unprocessed readings, capped by a max to prevent hanging backend. """ for current_reading in DsmrReading.objects.unprocessed()[0:1024]: @@ -35,8 +39,13 @@ def compact(dsmr_reading): ).replace(tzinfo=pytz.UTC) if grouping_type == ConsumptionSettings.COMPACTOR_GROUPING_BY_MINUTE: - # Postpone when current minute hasn't passed yet. - if timezone.now() <= reading_start + timezone.timedelta(minutes=1): + system_time_past_minute = timezone.now() >= reading_start + timezone.timedelta(minutes=1) + reading_past_minute_exists = DsmrReading.objects.filter( + timestamp__gte=reading_start + timezone.timedelta(minutes=1) + ).exists() + + # Postpone until the minute has passed on the system time. And when there are (new) readings beyond this minute. + if not system_time_past_minute or not reading_past_minute_exists: return # Create consumption records. @@ -47,7 +56,7 @@ def compact(dsmr_reading): dsmr_reading.save(update_fields=['processed']) # For backend logging in Supervisor. - print(' - Processed reading: {}'.format(dsmr_reading)) + logger.debug(' - Processed reading: %s', dsmr_reading) def _compact_electricity(dsmr_reading, grouping_type, reading_start): diff --git a/dsmr_consumption/tests/models/test_meta_default_permissions.py b/dsmr_consumption/tests/models/test_meta_default_permissions.py index a2d51caff..83b4f6ea4 100644 --- a/dsmr_consumption/tests/models/test_meta_default_permissions.py +++ b/dsmr_consumption/tests/models/test_meta_default_permissions.py @@ -5,4 +5,4 @@ class TestMetaDefaultPermissions(TestCase): def test_default_permissions(self): for current_model in apps.get_app_config('dsmr_stats').get_models(): - self.assertEqual(len(current_model()._meta.default_permissions), 0) + self.assertEqual(len(current_model()._meta.default_permissions), 0, current_model) diff --git a/dsmr_consumption/tests/test_services.py b/dsmr_consumption/tests/test_services.py index 1ceb06c4c..ab600bbd6 100644 --- a/dsmr_consumption/tests/test_services.py +++ b/dsmr_consumption/tests/test_services.py @@ -103,7 +103,6 @@ def test_duplicate_processing(self): @mock.patch('django.utils.timezone.now') def test_grouping(self, now_mock): """ Test grouping per minute, instead of the default 10-second interval. """ - now_mock.return_value = timezone.make_aware( timezone.datetime(2015, 11, 10, hour=21) ) @@ -124,6 +123,73 @@ def test_grouping(self, now_mock): else: self.assertEqual(GasConsumption.objects.count(), 0) + @mock.patch('django.utils.timezone.now') + def test_grouping_timing_bug(self, now_mock): + """ #513: Using the system time instead of telegram time, might ignore some readings. """ + now_mock.return_value = timezone.make_aware( + timezone.datetime(2018, 1, 1, hour=0, minute=0, second=0) + ) + ElectricityConsumption.objects.all().delete() + DsmrReading.objects.all().delete() + reading_timestamp = timezone.now() + DsmrReading.objects.create( + timestamp=reading_timestamp + timezone.timedelta(seconds=15), + electricity_delivered_1=1, + electricity_returned_1=0, + electricity_delivered_2=0, + electricity_returned_2=0, + electricity_currently_delivered=0, + electricity_currently_returned=0, + ) + DsmrReading.objects.create( + timestamp=reading_timestamp + timezone.timedelta(seconds=30), + electricity_delivered_1=2, + electricity_returned_1=0, + electricity_delivered_2=0, + electricity_returned_2=0, + electricity_currently_delivered=0, + electricity_currently_returned=0, + ) + + # This should do nothing, minute is not passed yet. + self.assertFalse(ElectricityConsumption.objects.exists()) + dsmr_consumption.services.compact_all() + self.assertFalse(ElectricityConsumption.objects.exists()) + + # Pass minute. Fix should keep reading unprocessed. + now_mock.return_value = timezone.make_aware(timezone.datetime(2018, 1, 1, hour=0, minute=1, second=10)) + dsmr_consumption.services.compact_all() + self.assertFalse(ElectricityConsumption.objects.exists()) + + # Add afterwards in passed minute. It should still be ignored. + DsmrReading.objects.create( + timestamp=reading_timestamp + timezone.timedelta(seconds=45), + electricity_delivered_1=3, + electricity_returned_1=0, + electricity_delivered_2=0, + electricity_returned_2=0, + electricity_currently_delivered=0, + electricity_currently_returned=0, + ) + dsmr_consumption.services.compact_all() + self.assertFalse(ElectricityConsumption.objects.exists()) + + # Now create a new reading in the next minute, it should finally trigger the grouping, as desired. + DsmrReading.objects.create( + timestamp=reading_timestamp + timezone.timedelta(seconds=60), + electricity_delivered_1=10, + electricity_returned_1=0, + electricity_delivered_2=0, + electricity_returned_2=0, + electricity_currently_delivered=0, + electricity_currently_returned=0, + ) + dsmr_consumption.services.compact_all() + self.assertTrue(ElectricityConsumption.objects.exists()) + + # Check result, should be max of all three readings. + self.assertTrue(ElectricityConsumption.objects.filter(delivered_1=3).exists()) + def test_creation(self): """ Test the datalogger's builtin fallback for initial readings. """ self.assertFalse(ElectricityConsumption.objects.exists()) @@ -379,17 +445,20 @@ class TestServicesDSMRv5(InterceptStdoutMixin, TestCase): fixtures = ['dsmr_consumption/test_dsmrreading_v5.json', 'dsmr_consumption/test_energysupplierprice.json'] def setUp(self): - self.assertEqual(DsmrReading.objects.all().count(), 6) + self.assertEqual(DsmrReading.objects.all().count(), 7) self.assertTrue(DsmrReading.objects.unprocessed().exists()) ConsumptionSettings.get_solo() MeterStatistics.get_solo() MeterStatistics.objects.all().update(dsmr_version='50') def test_processing_grouped(self): + self.assertFalse(DsmrReading.objects.processed().exists()) + self.assertEqual(DsmrReading.objects.unprocessed().count(), 7) + dsmr_consumption.services.compact_all() self.assertTrue(DsmrReading.objects.processed().exists()) - self.assertFalse(DsmrReading.objects.unprocessed().exists()) + self.assertEqual(DsmrReading.objects.unprocessed().count(), 1) self.assertEqual(GasConsumption.objects.count(), 4) self.assertEqual( [float(x.currently_delivered) for x in GasConsumption.objects.all()], @@ -403,10 +472,10 @@ def test_processing_ungrouped(self): self.assertTrue(DsmrReading.objects.processed().exists()) self.assertFalse(DsmrReading.objects.unprocessed().exists()) - self.assertEqual(GasConsumption.objects.count(), 6) + self.assertEqual(GasConsumption.objects.count(), 7) self.assertEqual( [float(x.currently_delivered) for x in GasConsumption.objects.all()], - [0.0, 0.05, 0.01, 0.01, 0.01, 0.07] + [0.0, 0.05, 0.01, 0.01, 0.01, 0.07, 0.00] ) diff --git a/dsmr_datalogger/scripts/dsmr_datalogger_api_client.py b/dsmr_datalogger/scripts/dsmr_datalogger_api_client.py index 9e88eac6e..8676aa1c7 100644 --- a/dsmr_datalogger/scripts/dsmr_datalogger_api_client.py +++ b/dsmr_datalogger/scripts/dsmr_datalogger_api_client.py @@ -63,7 +63,7 @@ def read_telegram(): except SerialException as error: # Something else and unexpected failed. print('Serial connection failed:', error) - raise StopIteration() # Break out of yield. + return # Break out of yield. try: # Make sure weird characters are converted properly. diff --git a/dsmr_datalogger/services.py b/dsmr_datalogger/services.py index 9c9cc90c2..bf9318b0a 100644 --- a/dsmr_datalogger/services.py +++ b/dsmr_datalogger/services.py @@ -23,6 +23,7 @@ dsmrreader_logger = logging.getLogger('dsmrreader') django_logger = logging.getLogger('django') +commands_logger = logging.getLogger('commands') def get_dsmr_connection_parameters(): @@ -368,7 +369,7 @@ def apply_data_retention(): ] # Now drop all others. - print('Retention | Cleaning up: {} ({})'.format(current_hour, data_set[0].__class__.__name__)) + commands_logger.debug('Retention | Cleaning up: %s (%s)', current_hour, data_set[0].__class__.__name__) data_set.exclude(pk__in=keeper_pks).delete() timezone.deactivate() diff --git a/dsmr_frontend/fixtures/dsmr_frontend/test_dsmrreading.json b/dsmr_frontend/fixtures/dsmr_frontend/test_dsmrreading.json index 5e74d42d5..85a8b78bd 100644 --- a/dsmr_frontend/fixtures/dsmr_frontend/test_dsmrreading.json +++ b/dsmr_frontend/fixtures/dsmr_frontend/test_dsmrreading.json @@ -9,7 +9,7 @@ "timestamp": "2015-11-10T19:40:47Z", "electricity_returned_1": "0.456", "electricity_currently_returned": "0.123", - "processed": false, + "processed": true, "electricity_delivered_2": "525.500" }, "model": "dsmr_datalogger.dsmrreading", @@ -25,7 +25,7 @@ "timestamp": "2015-11-10T19:40:57Z", "electricity_returned_1": "0.567", "electricity_currently_returned": "0.123", - "processed": false, + "processed": true, "electricity_delivered_2": "527.500" }, "model": "dsmr_datalogger.dsmrreading", @@ -41,7 +41,7 @@ "timestamp": "2015-11-10T19:41:07Z", "electricity_returned_1": "0.678", "electricity_currently_returned": "0.123", - "processed": false, + "processed": true, "electricity_delivered_2": "528.500", "phase_currently_delivered_l1": "0.123", "phase_currently_delivered_l2": "0.456", diff --git a/dsmr_frontend/fixtures/dsmr_frontend/test_dsmrreading_without_gas.json b/dsmr_frontend/fixtures/dsmr_frontend/test_dsmrreading_without_gas.json index be82094bc..df9a2ef54 100644 --- a/dsmr_frontend/fixtures/dsmr_frontend/test_dsmrreading_without_gas.json +++ b/dsmr_frontend/fixtures/dsmr_frontend/test_dsmrreading_without_gas.json @@ -9,7 +9,7 @@ "timestamp": "2015-11-10T19:40:47Z", "electricity_returned_1": "0.456", "electricity_currently_returned": "0.123", - "processed": false, + "processed": true, "electricity_delivered_2": "525.500" }, "model": "dsmr_datalogger.dsmrreading", @@ -25,7 +25,7 @@ "timestamp": "2015-11-10T19:40:57Z", "electricity_returned_1": "0.567", "electricity_currently_returned": "0.123", - "processed": false, + "processed": true, "electricity_delivered_2": "527.500" }, "model": "dsmr_datalogger.dsmrreading", @@ -41,7 +41,7 @@ "timestamp": "2015-11-10T19:41:07Z", "electricity_returned_1": "0.678", "electricity_currently_returned": "0.123", - "processed": false, + "processed": true, "electricity_delivered_2": "528.500", "phase_currently_delivered_l1": "0.123", "phase_currently_delivered_l2": "0.456", diff --git a/dsmr_frontend/fixtures/dsmr_frontend/test_electricity_consumption.json b/dsmr_frontend/fixtures/dsmr_frontend/test_electricity_consumption.json new file mode 100644 index 000000000..4db15f44d --- /dev/null +++ b/dsmr_frontend/fixtures/dsmr_frontend/test_electricity_consumption.json @@ -0,0 +1,40 @@ +[ +{ + "model": "dsmr_consumption.electricityconsumption", + "pk": 1, + "fields": { + "read_at": "2015-11-10T19:41:00Z", + "delivered_1": "529.750", + "returned_1": "0.567", + "delivered_2": "527.500", + "returned_2": "123.567", + "currently_delivered": "0.498", + "currently_returned": "0.123", + "phase_currently_delivered_l1": null, + "phase_currently_delivered_l2": null, + "phase_currently_delivered_l3": null, + "phase_currently_returned_l1": null, + "phase_currently_returned_l2": null, + "phase_currently_returned_l3": null + } +}, +{ + "model": "dsmr_consumption.electricityconsumption", + "pk": 2, + "fields": { + "read_at": "2015-11-10T19:42:00Z", + "delivered_1": "530.750", + "returned_1": "0.678", + "delivered_2": "528.500", + "returned_2": "123.678", + "currently_delivered": "1.123", + "currently_returned": "0.123", + "phase_currently_delivered_l1": "0.123", + "phase_currently_delivered_l2": "0.456", + "phase_currently_delivered_l3": "0.789", + "phase_currently_returned_l1": "0.111", + "phase_currently_returned_l2": "0.333", + "phase_currently_returned_l3": "0.555" + } +} +] diff --git a/dsmr_frontend/fixtures/dsmr_frontend/EnergySupplierPrice.json b/dsmr_frontend/fixtures/dsmr_frontend/test_energysupplierprice.json similarity index 100% rename from dsmr_frontend/fixtures/dsmr_frontend/EnergySupplierPrice.json rename to dsmr_frontend/fixtures/dsmr_frontend/test_energysupplierprice.json diff --git a/dsmr_frontend/fixtures/dsmr_frontend/test_gas_consumption.json b/dsmr_frontend/fixtures/dsmr_frontend/test_gas_consumption.json new file mode 100644 index 000000000..e6fb06f2b --- /dev/null +++ b/dsmr_frontend/fixtures/dsmr_frontend/test_gas_consumption.json @@ -0,0 +1,11 @@ +[ +{ + "model": "dsmr_consumption.gasconsumption", + "pk": 1, + "fields": { + "read_at": "2015-11-10T19:00:00Z", + "delivered": "845.888", + "currently_delivered": "0.000" + } +} +] diff --git a/dsmr_frontend/templates/dsmr_frontend/archive.html b/dsmr_frontend/templates/dsmr_frontend/archive.html index 447eee3c9..56f9415b1 100644 --- a/dsmr_frontend/templates/dsmr_frontend/archive.html +++ b/dsmr_frontend/templates/dsmr_frontend/archive.html @@ -1,5 +1,5 @@ {% extends "dsmr_frontend/base.html" %} -{% load staticfiles %} +{% load static %} {% load i18n %} {% load hex_to_rgb %} diff --git a/dsmr_frontend/templates/dsmr_frontend/base.html b/dsmr_frontend/templates/dsmr_frontend/base.html index d9e117a8c..64e5d1351 100644 --- a/dsmr_frontend/templates/dsmr_frontend/base.html +++ b/dsmr_frontend/templates/dsmr_frontend/base.html @@ -1,4 +1,4 @@ -{% load staticfiles %} +{% load static %} {% load i18n %} {% get_current_language as LANGUAGE_CODE %} @@ -139,6 +139,11 @@   {% trans "Help / FAQ" %} +
  • + +   {% trans "API v2 Docs" %} + +
  •   {% trans "Feedback" %} diff --git a/dsmr_frontend/templates/dsmr_frontend/dashboard.html b/dsmr_frontend/templates/dsmr_frontend/dashboard.html index dbda9f9e1..7ee73dd18 100644 --- a/dsmr_frontend/templates/dsmr_frontend/dashboard.html +++ b/dsmr_frontend/templates/dsmr_frontend/dashboard.html @@ -1,5 +1,5 @@ {% extends "dsmr_frontend/base.html" %} -{% load staticfiles %} +{% load static %} {% load humanize %} {% load i18n %} {% load hex_to_rgb %} diff --git a/dsmr_frontend/templates/dsmr_frontend/statistics.html b/dsmr_frontend/templates/dsmr_frontend/statistics.html index 3d8cfc2ad..407feaef7 100644 --- a/dsmr_frontend/templates/dsmr_frontend/statistics.html +++ b/dsmr_frontend/templates/dsmr_frontend/statistics.html @@ -156,76 +156,78 @@ + {% if electricity_statistics %}
    - {% trans "Usage statistics" %} + {% trans "All time highs" %}
    + {% if electricity_statistics.highest_usage_l1_value %} + - + {% endif %} + {% if electricity_statistics.highest_usage_l2_value %} - + + {% endif %} + {% if electricity_statistics.highest_usage_l3_value %} - + - {% if datalogger_settings.track_phases and capabilities.multi_phases %} - - + {% endif %} + + {% if electricity_statistics.highest_return_l1_value %} + + - - + {% endif %} + {% if electricity_statistics.highest_return_l2_value %} + + - - + {% endif %} + {% if electricity_statistics.highest_return_l3_value %} + + @@ -235,7 +237,8 @@ - + {% endif %} + {% endif %}
    @@ -301,38 +304,7 @@ url: "{% url 'frontend:statistics-xhr-data' %}", }).done(function(response) { $(".xhr-loader").hide(); - $("#total_reading_count").html(response.total_reading_count); - $("#slumber_consumption_watt").html(response.slumber_consumption_watt); - - if (response.total_min) { - $("#total_min_consumption_timestamp").html(response.total_min[0]); - $("#total_min_consumption_watt").html(response.total_min[1]); - } - - if (response.total_max) { - $("#total_max_consumption_timestamp").html(response.total_max[0]); - $("#total_max_consumption_watt").html(response.total_max[1]); - } - - if (response.l1_max) { - $("#l1_max_consumption_timestamp").html(response.l1_max[0]); - $("#l1_max_consumption_watt").html(response.l1_max[1]); - $("#l1_max_consumption").removeClass("hidden").show(); - } - - if (response.l2_max) { - $("#l2_max_consumption_timestamp").html(response.l2_max[0]); - $("#l2_max_consumption_watt").html(response.l2_max[1]); - $("#l2_max_consumption").removeClass("hidden").show(); - } - - if (response.l3_max) { - $("#l3_max_consumption_timestamp").html(response.l3_max[0]); - $("#l3_max_consumption_watt").html(response.l3_max[1]); - $("#l3_max_consumption").removeClass("hidden").show(); - } - $(".xhr-hidden").show(); }); } diff --git a/dsmr_frontend/templates/dsmr_frontend/trends.html b/dsmr_frontend/templates/dsmr_frontend/trends.html index c55e2bf1f..991eb5b45 100644 --- a/dsmr_frontend/templates/dsmr_frontend/trends.html +++ b/dsmr_frontend/templates/dsmr_frontend/trends.html @@ -1,5 +1,5 @@ {% extends "dsmr_frontend/base.html" %} -{% load staticfiles %} +{% load static %} {% load humanize %} {% load i18n %} {% load hex_to_rgb %} diff --git a/dsmr_frontend/tests/webinterface/test_archive.py b/dsmr_frontend/tests/webinterface/test_archive.py index c78c6194e..b4ec77494 100644 --- a/dsmr_frontend/tests/webinterface/test_archive.py +++ b/dsmr_frontend/tests/webinterface/test_archive.py @@ -18,9 +18,11 @@ class TestViews(TestCase): fixtures = [ 'dsmr_frontend/test_dsmrreading.json', 'dsmr_frontend/test_note.json', - 'dsmr_frontend/EnergySupplierPrice.json', + 'dsmr_frontend/test_energysupplierprice.json', 'dsmr_frontend/test_statistics.json', 'dsmr_frontend/test_meterstatistics.json', + 'dsmr_frontend/test_electricity_consumption.json', + 'dsmr_frontend/test_gas_consumption.json', ] namespace = 'frontend' support_data = True @@ -108,9 +110,10 @@ class TestViewsWithoutGas(TestViews): fixtures = [ 'dsmr_frontend/test_dsmrreading_without_gas.json', 'dsmr_frontend/test_note.json', - 'dsmr_frontend/EnergySupplierPrice.json', + 'dsmr_frontend/test_energysupplierprice.json', 'dsmr_frontend/test_statistics.json', 'dsmr_frontend/test_meterstatistics.json', + 'dsmr_frontend/test_electricity_consumption.json', ] support_gas = False diff --git a/dsmr_frontend/tests/webinterface/test_compare.py b/dsmr_frontend/tests/webinterface/test_compare.py index 8236e775b..7d26309ed 100644 --- a/dsmr_frontend/tests/webinterface/test_compare.py +++ b/dsmr_frontend/tests/webinterface/test_compare.py @@ -8,7 +8,6 @@ from dsmr_consumption.models.consumption import ElectricityConsumption, GasConsumption from dsmr_consumption.models.energysupplier import EnergySupplierPrice from dsmr_stats.models.statistics import DayStatistics -import dsmr_consumption.services class TestViews(TestCase): @@ -16,9 +15,11 @@ class TestViews(TestCase): fixtures = [ 'dsmr_frontend/test_dsmrreading.json', 'dsmr_frontend/test_note.json', - 'dsmr_frontend/EnergySupplierPrice.json', + 'dsmr_frontend/test_energysupplierprice.json', 'dsmr_frontend/test_statistics.json', 'dsmr_frontend/test_meterstatistics.json', + 'dsmr_frontend/test_electricity_consumption.json', + 'dsmr_frontend/test_gas_consumption.json', ] namespace = 'frontend' support_data = True @@ -27,7 +28,6 @@ class TestViews(TestCase): def setUp(self): self.client = Client() self.user = User.objects.create_user('testuser', 'unknown@localhost', 'passwd') - dsmr_consumption.services.compact_all() @mock.patch('django.utils.timezone.now') def test_compare(self, now_mock): @@ -69,9 +69,10 @@ class TestViewsWithoutGas(TestViews): fixtures = [ 'dsmr_frontend/test_dsmrreading_without_gas.json', 'dsmr_frontend/test_note.json', - 'dsmr_frontend/EnergySupplierPrice.json', + 'dsmr_frontend/test_energysupplierprice.json', 'dsmr_frontend/test_statistics.json', 'dsmr_frontend/test_meterstatistics.json', + 'dsmr_frontend/test_electricity_consumption.json', ] support_gas = False diff --git a/dsmr_frontend/tests/webinterface/test_dashboard.py b/dsmr_frontend/tests/webinterface/test_dashboard.py index b9be5e4bd..3f4f15081 100644 --- a/dsmr_frontend/tests/webinterface/test_dashboard.py +++ b/dsmr_frontend/tests/webinterface/test_dashboard.py @@ -15,7 +15,6 @@ from dsmr_stats.models.statistics import DayStatistics from dsmr_frontend.models.message import Notification from dsmr_datalogger.models.reading import DsmrReading -import dsmr_consumption.services from dsmr_frontend.models.settings import FrontendSettings from dsmr_weather.models.reading import TemperatureReading @@ -25,9 +24,11 @@ class TestViews(TestCase): fixtures = [ 'dsmr_frontend/test_dsmrreading.json', 'dsmr_frontend/test_note.json', - 'dsmr_frontend/EnergySupplierPrice.json', + 'dsmr_frontend/test_energysupplierprice.json', 'dsmr_frontend/test_statistics.json', 'dsmr_frontend/test_meterstatistics.json', + 'dsmr_frontend/test_electricity_consumption.json', + 'dsmr_frontend/test_gas_consumption.json', ] namespace = 'frontend' support_data = True @@ -36,7 +37,6 @@ class TestViews(TestCase): def setUp(self): self.client = Client() self.user = User.objects.create_user('testuser', 'unknown@localhost', 'passwd') - dsmr_consumption.services.compact_all() @mock.patch('django.utils.timezone.now') def test_dashboard(self, now_mock): @@ -364,9 +364,10 @@ class TestViewsWithoutGas(TestViews): fixtures = [ 'dsmr_frontend/test_dsmrreading_without_gas.json', 'dsmr_frontend/test_note.json', - 'dsmr_frontend/EnergySupplierPrice.json', + 'dsmr_frontend/test_energysupplierprice.json', 'dsmr_frontend/test_statistics.json', 'dsmr_frontend/test_meterstatistics.json', + 'dsmr_frontend/test_electricity_consumption.json', ] support_gas = False diff --git a/dsmr_frontend/tests/webinterface/test_energy_contracts.py b/dsmr_frontend/tests/webinterface/test_energy_contracts.py index 7d72eb42b..f94b0d625 100644 --- a/dsmr_frontend/tests/webinterface/test_energy_contracts.py +++ b/dsmr_frontend/tests/webinterface/test_energy_contracts.py @@ -8,13 +8,12 @@ from dsmr_consumption.models.consumption import ElectricityConsumption, GasConsumption from dsmr_consumption.models.energysupplier import EnergySupplierPrice from dsmr_stats.models.statistics import DayStatistics -import dsmr_consumption.services class TestViews(TestCase): """ Test whether views render at all. """ fixtures = [ - 'dsmr_frontend/EnergySupplierPrice.json', + 'dsmr_frontend/test_energysupplierprice.json', 'dsmr_frontend/test_statistics.json', ] namespace = 'frontend' @@ -23,7 +22,6 @@ class TestViews(TestCase): def setUp(self): self.client = Client() - dsmr_consumption.services.compact_all() @mock.patch('django.utils.timezone.now') def test_energy_contracts(self, now_mock): @@ -85,7 +83,7 @@ def setUp(self): class TestViewsWithoutGas(TestViews): """ Same tests as above, but without any GAS related data. """ fixtures = [ - 'dsmr_frontend/EnergySupplierPrice.json', + 'dsmr_frontend/test_energysupplierprice.json', 'dsmr_frontend/test_statistics.json', ] support_gas = False diff --git a/dsmr_frontend/tests/webinterface/test_export.py b/dsmr_frontend/tests/webinterface/test_export.py index a4dd93b2f..ea194ccf4 100644 --- a/dsmr_frontend/tests/webinterface/test_export.py +++ b/dsmr_frontend/tests/webinterface/test_export.py @@ -8,7 +8,6 @@ from dsmr_consumption.models.energysupplier import EnergySupplierPrice from dsmr_stats.models.statistics import DayStatistics from dsmr_frontend.forms import ExportAsCsvForm -import dsmr_consumption.services class TestViews(TestCase): @@ -16,9 +15,11 @@ class TestViews(TestCase): fixtures = [ 'dsmr_frontend/test_dsmrreading.json', 'dsmr_frontend/test_note.json', - 'dsmr_frontend/EnergySupplierPrice.json', + 'dsmr_frontend/test_energysupplierprice.json', 'dsmr_frontend/test_statistics.json', 'dsmr_frontend/test_meterstatistics.json', + 'dsmr_frontend/test_electricity_consumption.json', + 'dsmr_frontend/test_gas_consumption.json', ] namespace = 'frontend' support_data = True @@ -27,7 +28,6 @@ class TestViews(TestCase): def setUp(self): self.client = Client() self.user = User.objects.create_user('testuser', 'unknown@localhost', 'passwd') - dsmr_consumption.services.compact_all() def test_export(self): view_url = reverse('{}:export'.format(self.namespace)) @@ -113,9 +113,10 @@ class TestViewsWithoutGas(TestViews): fixtures = [ 'dsmr_frontend/test_dsmrreading_without_gas.json', 'dsmr_frontend/test_note.json', - 'dsmr_frontend/EnergySupplierPrice.json', + 'dsmr_frontend/test_energysupplierprice.json', 'dsmr_frontend/test_statistics.json', 'dsmr_frontend/test_meterstatistics.json', + 'dsmr_frontend/test_electricity_consumption.json', ] support_gas = False diff --git a/dsmr_frontend/tests/webinterface/test_generic.py b/dsmr_frontend/tests/webinterface/test_generic.py index aa8a3554b..a7b739bd2 100644 --- a/dsmr_frontend/tests/webinterface/test_generic.py +++ b/dsmr_frontend/tests/webinterface/test_generic.py @@ -8,7 +8,6 @@ from dsmr_consumption.models.consumption import ElectricityConsumption, GasConsumption from dsmr_consumption.models.energysupplier import EnergySupplierPrice from dsmr_stats.models.statistics import DayStatistics -import dsmr_consumption.services class TestViews(TestCase): @@ -16,9 +15,11 @@ class TestViews(TestCase): fixtures = [ 'dsmr_frontend/test_dsmrreading.json', 'dsmr_frontend/test_note.json', - 'dsmr_frontend/EnergySupplierPrice.json', + 'dsmr_frontend/test_energysupplierprice.json', 'dsmr_frontend/test_statistics.json', 'dsmr_frontend/test_meterstatistics.json', + 'dsmr_frontend/test_electricity_consumption.json', + 'dsmr_frontend/test_gas_consumption.json', ] namespace = 'frontend' support_data = True @@ -27,7 +28,6 @@ class TestViews(TestCase): def setUp(self): self.client = Client() self.user = User.objects.create_user('testuser', 'unknown@localhost', 'passwd') - dsmr_consumption.services.compact_all() def test_admin(self): response = self.client.get( @@ -93,9 +93,10 @@ class TestViewsWithoutGas(TestViews): fixtures = [ 'dsmr_frontend/test_dsmrreading_without_gas.json', 'dsmr_frontend/test_note.json', - 'dsmr_frontend/EnergySupplierPrice.json', + 'dsmr_frontend/test_energysupplierprice.json', 'dsmr_frontend/test_statistics.json', 'dsmr_frontend/test_meterstatistics.json', + 'dsmr_frontend/test_electricity_consumption.json', ] support_gas = False diff --git a/dsmr_frontend/tests/webinterface/test_statistics.py b/dsmr_frontend/tests/webinterface/test_statistics.py index b27fc2c12..3f4830114 100644 --- a/dsmr_frontend/tests/webinterface/test_statistics.py +++ b/dsmr_frontend/tests/webinterface/test_statistics.py @@ -10,7 +10,6 @@ from dsmr_consumption.models.energysupplier import EnergySupplierPrice from dsmr_datalogger.models.reading import DsmrReading from dsmr_stats.models.statistics import DayStatistics -import dsmr_consumption.services class TestViews(TestCase): @@ -18,9 +17,11 @@ class TestViews(TestCase): fixtures = [ 'dsmr_frontend/test_dsmrreading.json', 'dsmr_frontend/test_note.json', - 'dsmr_frontend/EnergySupplierPrice.json', + 'dsmr_frontend/test_energysupplierprice.json', 'dsmr_frontend/test_statistics.json', 'dsmr_frontend/test_meterstatistics.json', + 'dsmr_frontend/test_electricity_consumption.json', + 'dsmr_frontend/test_gas_consumption.json', ] namespace = 'frontend' support_data = True @@ -28,7 +29,6 @@ class TestViews(TestCase): def setUp(self): self.client = Client() - dsmr_consumption.services.compact_all() @mock.patch('django.utils.timezone.now') def test_statistics(self, now_mock): @@ -39,6 +39,7 @@ def test_statistics(self, now_mock): self.assertEqual(response.status_code, 200, response.content) self.assertIn('capabilities', response.context) self.assertIn('energy_prices', response.context) + self.assertIn('electricity_statistics', response.context) if DsmrReading.objects.exists(): self.assertIn('latest_reading', response.context) @@ -57,14 +58,6 @@ def test_statistics_xhr_data(self): json_response = json.loads(response.content.decode("utf-8")) self.assertIn('total_reading_count', json_response) - if self.support_data: - self.assertIn('slumber_consumption_watt', json_response) - self.assertIn('total_min', json_response) - self.assertIn('total_max', json_response) - self.assertIn('l1_max', json_response) - self.assertIn('l2_max', json_response) - self.assertIn('l3_max', json_response) - class TestViewsWithoutData(TestViews): """ Same tests as above, but without any data as it's flushed in setUp(). """ @@ -95,9 +88,10 @@ class TestViewsWithoutGas(TestViews): fixtures = [ 'dsmr_frontend/test_dsmrreading_without_gas.json', 'dsmr_frontend/test_note.json', - 'dsmr_frontend/EnergySupplierPrice.json', + 'dsmr_frontend/test_energysupplierprice.json', 'dsmr_frontend/test_statistics.json', 'dsmr_frontend/test_meterstatistics.json', + 'dsmr_frontend/test_electricity_consumption.json', ] support_gas = False diff --git a/dsmr_frontend/tests/webinterface/test_status.py b/dsmr_frontend/tests/webinterface/test_status.py index 9a73db837..1865d2931 100644 --- a/dsmr_frontend/tests/webinterface/test_status.py +++ b/dsmr_frontend/tests/webinterface/test_status.py @@ -10,7 +10,6 @@ from dsmr_consumption.models.energysupplier import EnergySupplierPrice from dsmr_stats.models.statistics import DayStatistics from dsmr_datalogger.models.reading import DsmrReading -import dsmr_consumption.services class TestViews(TestCase): @@ -18,9 +17,11 @@ class TestViews(TestCase): fixtures = [ 'dsmr_frontend/test_dsmrreading.json', 'dsmr_frontend/test_note.json', - 'dsmr_frontend/EnergySupplierPrice.json', + 'dsmr_frontend/test_energysupplierprice.json', 'dsmr_frontend/test_statistics.json', 'dsmr_frontend/test_meterstatistics.json', + 'dsmr_frontend/test_electricity_consumption.json', + 'dsmr_frontend/test_gas_consumption.json', ] namespace = 'frontend' support_data = True @@ -29,7 +30,6 @@ class TestViews(TestCase): def setUp(self): self.client = Client() self.user = User.objects.create_user('testuser', 'unknown@localhost', 'passwd') - dsmr_consumption.services.compact_all() @mock.patch('django.utils.timezone.now') def test_status(self, now_mock): @@ -141,9 +141,10 @@ class TestViewsWithoutGas(TestViews): fixtures = [ 'dsmr_frontend/test_dsmrreading_without_gas.json', 'dsmr_frontend/test_note.json', - 'dsmr_frontend/EnergySupplierPrice.json', + 'dsmr_frontend/test_energysupplierprice.json', 'dsmr_frontend/test_statistics.json', 'dsmr_frontend/test_meterstatistics.json', + 'dsmr_frontend/test_electricity_consumption.json', ] support_gas = False diff --git a/dsmr_frontend/tests/webinterface/test_trends.py b/dsmr_frontend/tests/webinterface/test_trends.py index 8fdafc196..79e30b425 100644 --- a/dsmr_frontend/tests/webinterface/test_trends.py +++ b/dsmr_frontend/tests/webinterface/test_trends.py @@ -9,7 +9,6 @@ from dsmr_consumption.models.consumption import ElectricityConsumption, GasConsumption from dsmr_consumption.models.energysupplier import EnergySupplierPrice from dsmr_stats.models.statistics import DayStatistics -import dsmr_consumption.services class TestViews(TestCase): @@ -17,9 +16,11 @@ class TestViews(TestCase): fixtures = [ 'dsmr_frontend/test_dsmrreading.json', 'dsmr_frontend/test_note.json', - 'dsmr_frontend/EnergySupplierPrice.json', + 'dsmr_frontend/test_energysupplierprice.json', 'dsmr_frontend/test_statistics.json', 'dsmr_frontend/test_meterstatistics.json', + 'dsmr_frontend/test_electricity_consumption.json', + 'dsmr_frontend/test_gas_consumption.json', ] namespace = 'frontend' support_data = True @@ -28,7 +29,6 @@ class TestViews(TestCase): def setUp(self): self.client = Client() self.user = User.objects.create_user('testuser', 'unknown@localhost', 'passwd') - dsmr_consumption.services.compact_all() def test_trends(self): response = self.client.get( @@ -125,9 +125,10 @@ class TestViewsWithoutGas(TestViews): fixtures = [ 'dsmr_frontend/test_dsmrreading_without_gas.json', 'dsmr_frontend/test_note.json', - 'dsmr_frontend/EnergySupplierPrice.json', + 'dsmr_frontend/test_energysupplierprice.json', 'dsmr_frontend/test_statistics.json', 'dsmr_frontend/test_meterstatistics.json', + 'dsmr_frontend/test_electricity_consumption.json', ] support_gas = False diff --git a/dsmr_frontend/urls.py b/dsmr_frontend/urls.py index 176b485d0..30ba55ddb 100644 --- a/dsmr_frontend/urls.py +++ b/dsmr_frontend/urls.py @@ -1,4 +1,4 @@ -from django.conf.urls import url +from django.urls.conf import path from dsmr_frontend.views.dashboard import Dashboard, DashboardXhrHeader, DashboardXhrConsumption, \ DashboardXhrNotificationRead, DashboardXhrElectricityConsumption, DashboardXhrGasConsumption, \ @@ -17,39 +17,39 @@ urlpatterns = [ # Public views. - url(r'^$', Dashboard.as_view(), name='dashboard'), - url(r'^xhr/header$', DashboardXhrHeader.as_view(), name='dashboard-xhr-header'), - url(r'^xhr/consumption', DashboardXhrConsumption.as_view(), name='dashboard-xhr-consumption'), - url(r'^xhr/electricity', DashboardXhrElectricityConsumption.as_view(), name='dashboard-xhr-electricity'), - url(r'^xhr/gas', DashboardXhrGasConsumption.as_view(), name='dashboard-xhr-gas'), - url(r'^xhr/temperature', DashboardXhrTemperature.as_view(), name='dashboard-xhr-temperature'), - url(r'^xhr/notification-read$', DashboardXhrNotificationRead.as_view(), name='dashboard-xhr-notification-read'), - url(r'^archive$', Archive.as_view(), name='archive'), - url(r'^archive/xhr/summary$', ArchiveXhrSummary.as_view(), name='archive-xhr-summary'), - url(r'^archive/xhr/graphs$', ArchiveXhrGraphs.as_view(), name='archive-xhr-graphs'), - url(r'^statistics$', Statistics.as_view(), name='statistics'), - url(r'^statistics/xhr/data$', StatisticsXhrData.as_view(), name='statistics-xhr-data'), - url(r'^energy-contracts$', EnergyContracts.as_view(), name='energy-contracts'), - url(r'^trends$', Trends.as_view(), name='trends'), - url(r'^trends/xhr/avg-consumption$', TrendsXhrAvgConsumption.as_view(), name='trends-xhr-avg-consumption'), - url( - r'^trends/xhr/consumption-by-tariff$', + path('', Dashboard.as_view(), name='dashboard'), + path('xhr/header', DashboardXhrHeader.as_view(), name='dashboard-xhr-header'), + path('xhr/consumption', DashboardXhrConsumption.as_view(), name='dashboard-xhr-consumption'), + path('xhr/electricity', DashboardXhrElectricityConsumption.as_view(), name='dashboard-xhr-electricity'), + path('xhr/gas', DashboardXhrGasConsumption.as_view(), name='dashboard-xhr-gas'), + path('xhr/temperature', DashboardXhrTemperature.as_view(), name='dashboard-xhr-temperature'), + path('xhr/notification-read', DashboardXhrNotificationRead.as_view(), name='dashboard-xhr-notification-read'), + path('archive', Archive.as_view(), name='archive'), + path('archive/xhr/summary', ArchiveXhrSummary.as_view(), name='archive-xhr-summary'), + path('archive/xhr/graphs', ArchiveXhrGraphs.as_view(), name='archive-xhr-graphs'), + path('statistics', Statistics.as_view(), name='statistics'), + path('statistics/xhr/data', StatisticsXhrData.as_view(), name='statistics-xhr-data'), + path('energy-contracts', EnergyContracts.as_view(), name='energy-contracts'), + path('trends', Trends.as_view(), name='trends'), + path('trends/xhr/avg-consumption', TrendsXhrAvgConsumption.as_view(), name='trends-xhr-avg-consumption'), + path( + 'trends/xhr/consumption-by-tariff', TrendsXhrElectricityByTariff.as_view(), name='trends-xhr-consumption-by-tariff' ), - url(r'^compare$', Compare.as_view(), name='compare'), + path('compare', Compare.as_view(), name='compare'), # Technical information. - url(r'^status$', Status.as_view(), name='status'), - url(r'^status/xhr/check-for-updates$', XhrUpdateChecker.as_view(), name='status-xhr-check-for-updates'), + path('status', Status.as_view(), name='status'), + path('status/xhr/check-for-updates', XhrUpdateChecker.as_view(), name='status-xhr-check-for-updates'), # Generic redirects to external (help) pages. - url(r'^changelog-redirect$', ChangelogRedirect.as_view(), name='changelog-redirect'), - url(r'^docs-redirect$', DocsRedirect.as_view(), name='docs-redirect'), - url(r'^feedback-redirect$', FeedbackRedirect.as_view(), name='feedback-redirect'), - url(r'^donations-redirect$', DonationsRedirect.as_view(), name='donations-redirect'), + path('changelog-redirect', ChangelogRedirect.as_view(), name='changelog-redirect'), + path('docs-redirect', DocsRedirect.as_view(), name='docs-redirect'), + path('feedback-redirect', FeedbackRedirect.as_view(), name='feedback-redirect'), + path('donations-redirect', DonationsRedirect.as_view(), name='donations-redirect'), # Views requiring authentication. - url(r'^export$', Export.as_view(), name='export'), - url(r'^export/csv$', ExportAsCsv.as_view(), name='export-as-csv'), + path('export', Export.as_view(), name='export'), + path('export/csv', ExportAsCsv.as_view(), name='export-as-csv'), ] diff --git a/dsmr_frontend/views/export.py b/dsmr_frontend/views/export.py index 17766ed4d..299ea565f 100644 --- a/dsmr_frontend/views/export.py +++ b/dsmr_frontend/views/export.py @@ -96,7 +96,7 @@ def write(self, value): def _generate_csv_row(self, writer, data, fields): if not data: - raise StopIteration() + return # Write header, but use the fields' verbose name. data_class = data[0].__class__ @@ -111,8 +111,6 @@ def _generate_csv_row(self, writer, data, fields): ) for current_field in fields ]) - raise StopIteration() - def _serialize_field(self, data): if isinstance(data, Decimal): return float(data) diff --git a/dsmr_frontend/views/statistics.py b/dsmr_frontend/views/statistics.py index 2f0b56ff8..44b4f876a 100644 --- a/dsmr_frontend/views/statistics.py +++ b/dsmr_frontend/views/statistics.py @@ -9,8 +9,8 @@ from dsmr_datalogger.models.statistics import MeterStatistics from dsmr_consumption.models.energysupplier import EnergySupplierPrice from dsmr_datalogger.models.settings import DataloggerSettings +from dsmr_stats.models.statistics import ElectricityStatistics import dsmr_backend.services -import dsmr_consumption.services class Statistics(TemplateView): @@ -19,6 +19,7 @@ class Statistics(TemplateView): def get_context_data(self, **kwargs): context_data = super(Statistics, self).get_context_data(**kwargs) context_data['capabilities'] = dsmr_backend.services.get_capabilities() + context_data['electricity_statistics'] = ElectricityStatistics.get_solo().export() try: latest_reading = DsmrReading.objects.all().order_by('-pk')[0] @@ -47,12 +48,6 @@ def get_context_data(self, **kwargs): class StatisticsXhrData(View): """ XHR view for fetching the dashboard header, displaying latest readings and price estimate, JSON response. """ def get(self, request): - data = { + return HttpResponse(json.dumps({ 'total_reading_count': intcomma(DsmrReading.objects.all().count()), - 'slumber_consumption_watt': dsmr_consumption.services.calculate_slumber_consumption_watt(), - } - - min_max_consumption_watt = dsmr_consumption.services.calculate_min_max_consumption_watt() - data.update(min_max_consumption_watt) - - return HttpResponse(json.dumps(data), content_type='application/json') + }), content_type='application/json') diff --git a/dsmr_mindergas/services.py b/dsmr_mindergas/services.py index 2d5dd9429..88600faa0 100644 --- a/dsmr_mindergas/services.py +++ b/dsmr_mindergas/services.py @@ -1,3 +1,4 @@ +import logging import random import json @@ -9,6 +10,9 @@ import dsmr_backend.services +logger = logging.getLogger('commands') + + def should_export(): """ Checks whether we should export data yet. Once every day. """ mindergas_settings = MinderGasSettings.get_solo() @@ -29,7 +33,7 @@ def export(): if not should_export(): return - print(' - MinderGas | Attempting to upload gas meter position.') + logger.debug(' - MinderGas | Attempting to upload gas meter position.') # Just post the latest reading of the day before. today = timezone.localtime(timezone.now()) @@ -52,9 +56,9 @@ def export(): except IndexError: # Just continue, even though we have no data... yet. last_gas_reading = None - print(' - MinderGas | No gas readings found for uploading') + logger.error(' - MinderGas | No gas readings found for uploading') else: - print(' - MinderGas | Uploading gas meter position: {}'.format(last_gas_reading.delivered)) + logger.debug(' - MinderGas | Uploading gas meter position: %s', last_gas_reading.delivered) # Register telegram by simply sending it to the application with a POST request. response = requests.post( @@ -69,11 +73,11 @@ def export(): if response.status_code != 201: # Try again in an hour. next_export = timezone.now() + timezone.timedelta(hours=1) - print(' [!] MinderGas upload failed (HTTP {}): {}'.format(response.status_code, response.text)) + logger.error(' [!] MinderGas upload failed (HTTP %s): %s', response.status_code, response.text) else: # Keep track. mindergas_settings.latest_sync = timezone.now() - print(' - MinderGas | Delaying the next upload until: {}'.format(next_export)) + logger.debug(' - MinderGas | Delaying the next upload until:%s', next_export) mindergas_settings.next_export = next_export mindergas_settings.save() diff --git a/dsmr_mqtt/management/commands/dsmr_mqtt.py b/dsmr_mqtt/management/commands/dsmr_mqtt.py index f77a3e75b..ec206ed12 100644 --- a/dsmr_mqtt/management/commands/dsmr_mqtt.py +++ b/dsmr_mqtt/management/commands/dsmr_mqtt.py @@ -1,3 +1,5 @@ +import logging + from django.core.management.base import BaseCommand from django.utils.translation import ugettext as _ from django.conf import settings @@ -7,6 +9,9 @@ import dsmr_mqtt.services.broker +logger = logging.getLogger('commands') + + class Command(InfiniteManagementCommandMixin, BaseCommand): help = _('Dedicated MQTT client for publishing messages to the broker.') name = __name__ # Required for PID file. @@ -31,7 +36,7 @@ def run(self, **options): # Check on each run. In case MQTT was either disabled, enabled or settings were changed. if broker_settings.restart_required: MQTTBrokerSettings.objects.update(restart_required=False) - print('MQTT | --- Detected settings change, requiring process restart, stopping...') + logger.warning('MQTT | --- Detected settings change, requiring process restart, stopping...') raise StopInfiniteRun() dsmr_mqtt.services.broker.run(mqtt_client=self.mqtt_client) diff --git a/dsmr_mqtt/services/broker.py b/dsmr_mqtt/services/broker.py index ccd86665d..200d5c106 100644 --- a/dsmr_mqtt/services/broker.py +++ b/dsmr_mqtt/services/broker.py @@ -1,3 +1,4 @@ +import logging import time import ssl @@ -9,12 +10,15 @@ from dsmr_backend.mixins import StopInfiniteRun +logger = logging.getLogger('commands') + + def initialize(): """ Initializes the MQTT client and returns client instance. """ broker_settings = MQTTBrokerSettings.get_solo() if not broker_settings.hostname: - print('MQTT | No hostname found in settings, restarting in a minute...') + logger.warning('MQTT | No hostname found in settings, restarting in a minute...') time.sleep(60) raise StopInfiniteRun() @@ -31,18 +35,18 @@ def initialize(): # SSL/TLS. if broker_settings.secure == MQTTBrokerSettings.SECURE_CERT_NONE: - print('MQTT | Initializing secure connection (ssl.CERT_NONE)') + logger.debug('MQTT | Initializing secure connection (ssl.CERT_NONE)') mqtt_client.tls_set(cert_reqs=ssl.CERT_NONE) elif broker_settings.secure == MQTTBrokerSettings.SECURE_CERT_REQUIRED: - print('MQTT | Initializing secure connection (ssl.CERT_REQUIRED)') + logger.debug('MQTT | Initializing secure connection (ssl.CERT_REQUIRED)') mqtt_client.tls_set(cert_reqs=ssl.CERT_REQUIRED) else: - print('MQTT | Initializing insecure connection (no TLS)') + logger.debug('MQTT | Initializing insecure connection (no TLS)') try: mqtt_client.connect(host=broker_settings.hostname, port=broker_settings.port) except Exception as error: - print('MQTT | Failed to connect to broker, restarting in a minute: {}'.format(error)) + logger.error('MQTT | Failed to connect to broker, restarting in a minute: %s', error) time.sleep(60) raise StopInfiniteRun() @@ -81,10 +85,10 @@ def on_connect(client, userdata, flags, rc): 4: 'Connection refused - bad username or password', 5: 'Connection refused - not authorised', } - print('MQTT | Paho client: on_connect(userdata, flags, rc)', userdata, flags, rc) + logger.debug('MQTT | Paho client: on_connect(userdata, flags, rc) %s %s %s', userdata, flags, rc) try: - print('MQTT | --- {} : {} -> {}'.format(client._host, client._port, RC_MAPPING[rc])) + logger.debug('MQTT | --- %s : %s -> %s'.format(client._host, client._port, RC_MAPPING[rc])) except KeyError: pass @@ -96,23 +100,23 @@ def on_disconnect(client, userdata, rc): If MQTT_ERR_SUCCESS (0), the callback was called in response to a disconnect() call. If any other value the disconnection was unexpected, such as might be caused by a network error. """ - print('MQTT | Paho client: on_disconnect(userdata, rc)', userdata, rc) + logger.debug('MQTT | Paho client: on_disconnect(userdata, rc) %s %s', userdata, rc) if rc != paho.MQTT_ERR_SUCCESS: - print('MQTT | --- Unexpected disconnect, re-connecting...') + logger.warning('MQTT | --- Unexpected disconnect, re-connecting...') try: client.reconnect() except Exception as error: - print('MQTT | Failed to re-connect to broker: {}'.format(error)) + logger.error('MQTT | Failed to re-connect to broker: %s', error) raise StopInfiniteRun() # Don't bother even to continue, just reset everything. def on_log(client, userdata, level, buf): """ MQTT client callback for logging. Outputs some debug logging. """ - print('MQTT | Paho client: on_log(userdata, level, buf)', userdata, level, buf) + logger.debug('MQTT | Paho client: on_log(userdata, level, buf) %s %s %s', userdata, level, buf) def on_publish(client, userdata, mid): """ MQTT client callback for publishing. Outputs some debug logging. """ - print('MQTT | Paho client: on_publish(userdata, mid)', userdata, mid) + logger.debug('MQTT | Paho client: on_publish(userdata, mid) %s %s', userdata, mid) diff --git a/dsmr_notification/services.py b/dsmr_notification/services.py index 1abe26503..608ca093f 100644 --- a/dsmr_notification/services.py +++ b/dsmr_notification/services.py @@ -1,3 +1,5 @@ +import logging + from django.utils.translation import ugettext_lazy as _ from django.utils import timezone from django.conf import settings @@ -11,6 +13,9 @@ import dsmr_backend.services +logger = logging.getLogger('commands') + + def notify_pre_check(): """ Checks whether we should notify """ notification_settings = NotificationSetting.get_solo() @@ -92,7 +97,7 @@ def send_notification(message, title): # Invalid request, do not retry. if str(response.status_code).startswith('4'): - print(' - Notification API returned client error, wiping settings...') + logger.error(' - Notification API returned client error, wiping settings...') NotificationSetting.objects.update( notification_service=None, pushover_api_key=None, @@ -107,7 +112,7 @@ def send_notification(message, title): # Server error, delay a bit. elif str(response.status_code).startswith('5'): - print(' - Notification API returned server error, retrying later...') + logger.warning(' - Notification API returned server error, retrying later...') NotificationSetting.objects.update( next_notification=timezone.now() + timezone.timedelta(minutes=5) ) @@ -150,7 +155,7 @@ def notify(): return # Try again in a next run # For backend logging in Supervisor. - print(' - Creating new notification containing daily usage.') + logger.debug(' - Creating new notification containing daily usage.') message = create_consumption_message(midnight, stats) send_notification(message, str(_('Daily usage notification'))) @@ -182,9 +187,9 @@ def check_status(): ) # Alert! - print(' - Sending notification about datalogger lagging behind...') + logger.debug(' - Sending notification about datalogger lagging behind...') send_notification( - str(_('It has been over an hour since the last reading received. Please check your datalogger.')), + str(_('It has been over {} hour(s) since the last reading received. Please check your datalogger.')), str(_('Datalogger check')) ) diff --git a/dsmr_pvoutput/fixtures/dsmr_pvoutput/electricity-consumption.json b/dsmr_pvoutput/fixtures/dsmr_pvoutput/electricity-consumption.json index 7cc816524..8fdb97743 100644 --- a/dsmr_pvoutput/fixtures/dsmr_pvoutput/electricity-consumption.json +++ b/dsmr_pvoutput/fixtures/dsmr_pvoutput/electricity-consumption.json @@ -3,12 +3,12 @@ "pk": 1, "fields": { "delivered_1": "2000.000", - "delivered_2": "1500.000", + "delivered_2": "1000.000", "returned_1": "1000.000", "returned_2": "250.000", "currently_returned": "0.500", "currently_delivered": "1.500", - "read_at": "2017-10-01T02:00:00Z" + "read_at": "2017-10-01T00:00:00+02:00" }, "model": "dsmr_consumption.electricityconsumption" }, @@ -16,12 +16,12 @@ "pk": 11, "fields": { "delivered_1": "2003.000", - "delivered_2": "1500.000", + "delivered_2": "1001.000", "returned_1": "1005.000", "returned_2": "250.000", "currently_returned": "1.750", "currently_delivered": "1.000", - "read_at": "2017-10-01T11:00:00Z" + "read_at": "2017-10-01T13:00:00+02:00" }, "model": "dsmr_consumption.electricityconsumption" }, @@ -29,12 +29,12 @@ "pk": 12, "fields": { "delivered_1": "2005.000", - "delivered_2": "1500.000", + "delivered_2": "1002.000", "returned_1": "1006.000", "returned_2": "250.000", "currently_returned": "1.050", "currently_delivered": "1.500", - "read_at": "2017-10-01T13:00:00Z" + "read_at": "2017-10-01T15:00:00+02:00" }, "model": "dsmr_consumption.electricityconsumption" } diff --git a/dsmr_pvoutput/migrations/0001_pvoutput_settings.py b/dsmr_pvoutput/migrations/0001_pvoutput_settings.py index 7b72493fc..098f89206 100644 --- a/dsmr_pvoutput/migrations/0001_pvoutput_settings.py +++ b/dsmr_pvoutput/migrations/0001_pvoutput_settings.py @@ -20,7 +20,7 @@ class Migration(migrations.Migration): ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('export', models.BooleanField(default=False, help_text='Whether the system uploads consumption using the Add Status Service API call.', verbose_name='Enabled')), ('upload_interval', models.IntegerField(choices=[(5, '5 minutes'), (10, '10 minutes'), (15, '15 minutes')], default=5, help_text='The interval between each upload (in minutes). Please make sure this matches the device settings.', verbose_name='Upload interval')), - ('upload_delay', models.IntegerField(default=0, help_text='An artificial delay in uploading data to PVOutput. E.g.: When you set this to "5" and the application uploads the data at 10:45, then only data between 0:00 and 10:40 will be taken into account for upload at that moment. It effectively limits its upload data search by "X minutes ago".', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(30)], verbose_name='Upload delay (minutes)')), + ('upload_delay', models.IntegerField(default=0, help_text='An artificial delay in uploading data to PVOutput. E.g.: When you set this to "5" and the application uploads the data at 10:45, then only data between until 10:40 will be taken into account. It effectively limits its upload data search by "ignore the last X minutes", where X is this setting.', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(30)], verbose_name='Upload offset (minutes)')), ('processing_delay', models.IntegerField(blank=True, default=None, help_text='Leave EMPTY to disable the feature. This parameter allows the processing of the data to be delayed, by the specified number of minutes. Allowed values: empty or 0 to 120 (minutes)', null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(120)], verbose_name='PVOutput: Processing delay (minutes)')), ('next_export', models.DateTimeField(blank=True, default=None, help_text='Timestamp of the next export. Automatically updated by application.', null=True, verbose_name='Next export')), ], diff --git a/dsmr_pvoutput/models/settings.py b/dsmr_pvoutput/models/settings.py index 1f10afca1..8ca4c8f9e 100644 --- a/dsmr_pvoutput/models/settings.py +++ b/dsmr_pvoutput/models/settings.py @@ -57,11 +57,11 @@ class PVOutputAddStatusSettings(SingletonModel): upload_delay = models.IntegerField( default=0, validators=[MinValueValidator(0), MaxValueValidator(30)], - verbose_name=_('Upload delay (minutes)'), + verbose_name=_('Upload offset (minutes)'), help_text=_( 'An artificial delay in uploading data to PVOutput. E.g.: When you set this to "5" and the application ' - 'uploads the data at 10:45, then only data between 0:00 and 10:40 will be taken into account for upload at ' - 'that moment. It effectively limits its upload data search by "X minutes ago".' + 'uploads the data at 10:45, then only data between until 10:40 will be taken into account. ' + 'It effectively limits its upload data search by "ignore the last X minutes", where X is this setting.' ) ) processing_delay = models.IntegerField( diff --git a/dsmr_pvoutput/services.py b/dsmr_pvoutput/services.py index 78a0deba7..945c52248 100644 --- a/dsmr_pvoutput/services.py +++ b/dsmr_pvoutput/services.py @@ -1,3 +1,5 @@ +import logging + from django.utils import timezone import requests @@ -7,6 +9,9 @@ import dsmr_backend.services +logger = logging.getLogger('commands') + + def should_export(): """ Checks whether we should export data yet, for Add Status calls. """ api_settings = PVOutputAPISettings.get_solo() @@ -22,7 +27,7 @@ def should_export(): def schedule_next_export(): """ Schedules the next export, according to user preference. """ next_export = get_next_export() - print(' - PVOutput | Delaying the next export until: {}'.format(next_export)) + logger.debug(' - PVOutput | Delaying the next export until: %s', timezone.localtime(next_export)) status_settings = PVOutputAddStatusSettings.get_solo() status_settings.next_export = next_export @@ -39,38 +44,45 @@ def get_next_export(): minute_marker = next_export.minute minute_marker = minute_marker - (minute_marker % status_settings.upload_interval) - return next_export.replace(minute=minute_marker, second=0) - + return next_export.replace(minute=minute_marker, second=0, microsecond=0) -def export(): - """ Exports data to PVOutput, calling Add Status. """ - if not should_export(): - return - - api_settings = PVOutputAPISettings.get_solo() - status_settings = PVOutputAddStatusSettings.get_solo() +def get_export_data(next_export, upload_delay): + """ Returns the data to export. Raises exception when 'not ready'. """ # Find the first and last consumption of today, taking any delay into account. local_now = timezone.localtime(timezone.now()) - start = local_now.replace(hour=0, minute=0, second=0) # Midnight - end = local_now - timezone.timedelta(minutes=status_settings.upload_delay) + search_start = local_now.replace(hour=0, minute=0, second=0) # Midnight + search_end = local_now - timezone.timedelta(minutes=upload_delay) - ecs = ElectricityConsumption.objects.filter(read_at__gte=start, read_at__lte=end) + ecs = ElectricityConsumption.objects.filter(read_at__gte=search_start, read_at__lte=search_end) if not ecs.exists(): - print(' [!] PVOutput: No data found for {}'.format(local_now)) - return schedule_next_export() + return None first = ecs[0] last = ecs.order_by('-read_at')[0] - diff = last - first # Custom operator + consumption_timestamp = timezone.localtime(last.read_at) + if next_export is None: + # This should only happen once, on the first upload ever. + next_export = timezone.localtime(timezone.now()) + else: + # Delay the export, until we have data that reaches at least the current upload time. (#467) + expected_data_timestamp = timezone.localtime(next_export - timezone.timedelta(minutes=upload_delay)) + + if consumption_timestamp < expected_data_timestamp: + logger.warning( + ' [i] PVOutput: Data found, not in sync. Last data timestamp < expected (%s < %s)', + consumption_timestamp, + expected_data_timestamp + ) + raise LookupError() + + diff = last - first # Custom operator for convenience total_consumption = diff['delivered_1'] + diff['delivered_2'] net_power = last.currently_delivered - last.currently_returned # Negative when returning more Watt than requested. - consumption_timestamp = timezone.localtime(last.read_at) - - data = { + return { 'd': consumption_timestamp.date().strftime('%Y%m%d'), 't': consumption_timestamp.time().strftime('%H:%M'), 'v3': int(total_consumption * 1000), # Energy Consumption (Wh) @@ -78,11 +90,29 @@ def export(): 'n': 1, # Net Flag, always enabled for smart meters } + +def export(): + """ Exports data to PVOutput, calling Add Status. """ + if not should_export(): + return + + api_settings = PVOutputAPISettings.get_solo() + status_settings = PVOutputAddStatusSettings.get_solo() + + try: + data = get_export_data(next_export=status_settings.next_export, upload_delay=status_settings.upload_delay) + except LookupError: + return + + if not data: + logger.warning(' [!] PVOutput: No data found (yet)') + return schedule_next_export() + # Optional, paid PVOutput feature. if status_settings.processing_delay: data.update({'delay': status_settings.processing_delay}) - print(' - PVOutput | Uploading data: {}'.format(data)) + logger.debug(' - PVOutput | Uploading data: %s', data) pvoutput_upload.send_robust(None, data=data) response = requests.post( @@ -95,7 +125,7 @@ def export(): ) if response.status_code != 200: - print(' [!] PVOutput upload failed (HTTP {}): {}'.format(response.status_code, response.text)) + logger.error(' [!] PVOutput upload failed (HTTP %s): %s', response.status_code, response.text) else: status_settings.latest_sync = timezone.now() status_settings.save() diff --git a/dsmr_pvoutput/tests/test_services.py b/dsmr_pvoutput/tests/test_services.py index 4cfc0c102..d0649d621 100644 --- a/dsmr_pvoutput/tests/test_services.py +++ b/dsmr_pvoutput/tests/test_services.py @@ -72,6 +72,41 @@ def test_get_next_export(self, now_mock): self.assertEqual(result.minute, 0) self.assertEqual(result.second, 0) + @mock.patch('django.utils.timezone.now') + def test_get_export_data(self, now_mock): + """ Complexity to make sure the upload is in sync. """ + now_mock.return_value = timezone.make_aware(timezone.datetime(2017, 9, 30, hour=15)) + self._apply_fake_settings() + + # Too soon, no data in today's range. + result = dsmr_pvoutput.services.get_export_data(next_export=timezone.now(), upload_delay=0) + self.assertIsNone(result) + + # First sync, next day. + now_mock.return_value = timezone.make_aware(timezone.datetime(2017, 10, 1, hour=13)) # Include 2 EC's. + result = dsmr_pvoutput.services.get_export_data(next_export=None, upload_delay=0) + self.assertEqual(result, {'d': '20171001', 'n': 1, 't': '13:00', 'v3': 4000, 'v4': -750}) + + # Now with all test EC's. + now_mock.return_value = timezone.make_aware(timezone.datetime(2017, 10, 1, hour=15)) # Include all 3 EC's. + result = dsmr_pvoutput.services.get_export_data(next_export=None, upload_delay=0) + self.assertEqual(result, {'d': '20171001', 'n': 1, 't': '15:00', 'v3': 7000, 'v4': 450}) + + # Now with delay, should not be allowed, as we wait for more data. + now_mock.return_value = timezone.make_aware(timezone.datetime(2017, 10, 1, hour=13)) # Include 2 EC's. + with self.assertRaises(LookupError): + dsmr_pvoutput.services.get_export_data(next_export=timezone.now(), upload_delay=1) + + # Again with delay, but we move forward in time. + now_mock.return_value = now_mock.return_value + timezone.timedelta(minutes=1) + result = dsmr_pvoutput.services.get_export_data( + next_export=timezone.now(), upload_delay=1 + ) + + # Make delay again just fall off, failing. + with self.assertRaises(LookupError): + dsmr_pvoutput.services.get_export_data(next_export=timezone.now(), upload_delay=2) + @mock.patch('django.utils.timezone.now') def test_should_export_okay(self, now_mock): now_mock.return_value = timezone.make_aware(timezone.datetime(2017, 10, 1, hour=15)) @@ -114,28 +149,57 @@ def test_export_no_electricity(self, now_mock, should_export_mock, requests_mock @mock.patch('requests.post') @mock.patch('dsmr_pvoutput.services.should_export') + @mock.patch('dsmr_pvoutput.services.get_export_data') @mock.patch('django.utils.timezone.now') - def test_export_fail(self, now_mock, should_export_mock, requests_post_mock): + def test_export_fail(self, now_mock, export_data_mock, should_export_mock, requests_post_mock): """ Test export() failing by denied API call. """ now_mock.return_value = timezone.make_aware(timezone.datetime(2017, 10, 1, hour=15)) + export_data_mock.return_value = {'x': 'y'} # Unimportant for this test. should_export_mock.return_value = True self._apply_fake_settings() requests_post_mock.return_value = mock.MagicMock(status_code=400, text='Error message') dsmr_pvoutput.services.export() - status_settings = PVOutputAddStatusSettings.get_solo() + status_settings = PVOutputAddStatusSettings.get_solo() self.assertEqual(status_settings.next_export, timezone.now() + timezone.timedelta(minutes=5)) self.assertTrue(requests_post_mock.called) self.assertIsNone(status_settings.latest_sync) @mock.patch('requests.post') @mock.patch('dsmr_pvoutput.services.should_export') + @mock.patch('dsmr_pvoutput.services.get_export_data') + @mock.patch('django.utils.timezone.now') + @mock.patch('dsmr_pvoutput.signals.pvoutput_upload.send_robust') + def test_export_postponed( + self, send_robust_mock, now_mock, export_data_mock, should_export_mock, requests_post_mock + ): + """ Test export() but failing due to lack of data ready. """ + now_mock.return_value = timezone.make_aware(timezone.datetime(2017, 10, 1, hour=1)) + + should_export_mock.return_value = True + export_data_mock.side_effect = LookupError() # Emulate. + requests_post_mock.return_value = mock.MagicMock(status_code=200, text='Fake accept') + self._apply_fake_settings() + + self.assertFalse(requests_post_mock.called) + self.assertFalse(send_robust_mock.called) + + dsmr_pvoutput.services.export() + + # Nothing should happen. + self.assertFalse(requests_post_mock.called) + self.assertFalse(send_robust_mock.called) + + @mock.patch('requests.post') + @mock.patch('dsmr_pvoutput.services.should_export') + @mock.patch('dsmr_pvoutput.services.get_export_data') @mock.patch('django.utils.timezone.now') @mock.patch('dsmr_pvoutput.signals.pvoutput_upload.send_robust') - def test_export_okay(self, send_robust_mock, now_mock, should_export_mock, requests_post_mock): + def test_export_okay(self, send_robust_mock, now_mock, export_data_mock, should_export_mock, requests_post_mock): """ Test export() as designed. """ now_mock.return_value = timezone.make_aware(timezone.datetime(2017, 10, 1, hour=15)) + export_data_mock.return_value = {'x': 'y'} # Unimportant for this test. should_export_mock.return_value = True requests_post_mock.return_value = mock.MagicMock(status_code=200, text='Fake accept') @@ -158,13 +222,7 @@ def test_export_okay(self, send_robust_mock, now_mock, should_export_mock, reque 'X-Pvoutput-Apikey': api_settings.auth_token, 'X-Pvoutput-SystemId': api_settings.system_identifier, }, - data={ - 'd': '20171001', - 'n': 1, - 't': '13:00', - 'v3': 3000, - 'v4': -750, - }, + data={'x': 'y'}, ) # With processing delay as well. @@ -188,11 +246,7 @@ def test_export_okay(self, send_robust_mock, now_mock, should_export_mock, reque 'X-Pvoutput-SystemId': api_settings.system_identifier, }, data={ - 'd': '20171001', + 'x': 'y', 'delay': 5, - 'n': 1, - 't': '13:00', - 'v3': 3000, - 'v4': -750, }, ) diff --git a/dsmr_stats/admin.py b/dsmr_stats/admin.py index 1399ed82f..1b90086e8 100644 --- a/dsmr_stats/admin.py +++ b/dsmr_stats/admin.py @@ -2,9 +2,11 @@ from django.contrib import admin from django.forms import widgets from django.db import models +from solo.admin import SingletonModelAdmin from .models.note import Note -from .models.statistics import HourStatistics, DayStatistics +from .models.statistics import HourStatistics, DayStatistics, ElectricityStatistics +from dsmr_backend.mixins import ReadOnlyAdminModel @admin.register(Note) @@ -42,3 +44,9 @@ class HourStatisticsAdmin(admin.ModelAdmin): list_filter = ( ('hour_start', DateFieldListFilter), ) + + +@admin.register(ElectricityStatistics) +class ElectricityStatisticsAdmin(SingletonModelAdmin, ReadOnlyAdminModel): + """ Read only model. """ + pass diff --git a/dsmr_stats/apps.py b/dsmr_stats/apps.py index 1046c95c0..ca5ffe8a5 100644 --- a/dsmr_stats/apps.py +++ b/dsmr_stats/apps.py @@ -1,5 +1,6 @@ from django.apps import AppConfig from django.utils.translation import ugettext_lazy as _ +import django.db.models.signals import dsmr_backend.signals @@ -9,12 +10,26 @@ class AppConfig(AppConfig): verbose_name = _('Trend & statistics') def ready(self): + from dsmr_datalogger.models.reading import DsmrReading dsmr_backend.signals.backend_called.connect( receiver=self._on_backend_called_signal, dispatch_uid=self.__class__ ) + django.db.models.signals.post_save.connect( + receiver=self._on_dsmrreading_created_signal, + dispatch_uid=self.__class__, + sender=DsmrReading + ) def _on_backend_called_signal(self, sender, **kwargs): # Import below prevents an AppRegistryNotReady error on Django init. import dsmr_stats.services dsmr_stats.services.analyze() + + def _on_dsmrreading_created_signal(self, instance, created, raw, **kwargs): + # Skip new or imported (fixture) instances. + if not created or raw: + return + + import dsmr_stats.services + dsmr_stats.services.update_electricity_statistics(reading=instance) diff --git a/dsmr_stats/migrations/0012_electricity_statistics.py b/dsmr_stats/migrations/0012_electricity_statistics.py new file mode 100644 index 000000000..84a904bc6 --- /dev/null +++ b/dsmr_stats/migrations/0012_electricity_statistics.py @@ -0,0 +1,35 @@ +# Generated by Django 2.1 on 2018-08-06 15:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dsmr_stats', '0011_note_model_index'), + ] + + operations = [ + migrations.CreateModel( + name='ElectricityStatistics', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('highest_usage_l1_timestamp', models.DateTimeField(blank=True, default=None, null=True, verbose_name='Timestamp of highest usage on L1+')), + ('highest_usage_l2_timestamp', models.DateTimeField(blank=True, default=None, null=True, verbose_name='Timestamp of highest usage on L2+')), + ('highest_usage_l3_timestamp', models.DateTimeField(blank=True, default=None, null=True, verbose_name='Timestamp of highest usage on L3+')), + ('highest_return_l1_timestamp', models.DateTimeField(blank=True, default=None, null=True, verbose_name='Timestamp of highest return on L1-')), + ('highest_return_l2_timestamp', models.DateTimeField(blank=True, default=None, null=True, verbose_name='Timestamp of highest return on L2-')), + ('highest_return_l3_timestamp', models.DateTimeField(blank=True, default=None, null=True, verbose_name='Timestamp of highest return on L3-')), + ('highest_usage_l1_value', models.DecimalField(blank=True, decimal_places=3, default=None, max_digits=9, null=True, verbose_name='Highest usage on L1+ (in kWh)')), + ('highest_usage_l2_value', models.DecimalField(blank=True, decimal_places=3, default=None, max_digits=9, null=True, verbose_name='Highest usage on L2+ (in kWh)')), + ('highest_usage_l3_value', models.DecimalField(blank=True, decimal_places=3, default=None, max_digits=9, null=True, verbose_name='Highest usage on L3+ (in kWh)')), + ('highest_return_l1_value', models.DecimalField(blank=True, decimal_places=3, default=None, max_digits=9, null=True, verbose_name='Highest return on L1- (in kWh)')), + ('highest_return_l2_value', models.DecimalField(blank=True, decimal_places=3, default=None, max_digits=9, null=True, verbose_name='Highest return on L2- (in kWh)')), + ('highest_return_l3_value', models.DecimalField(blank=True, decimal_places=3, default=None, max_digits=9, null=True, verbose_name='Highest return on L3- (in kWh)')), + ], + options={ + 'default_permissions': (), + 'verbose_name': 'Electricity statistics', + }, + ), + ] diff --git a/dsmr_stats/models/statistics.py b/dsmr_stats/models/statistics.py index 0a16fe3cc..adab24c3c 100644 --- a/dsmr_stats/models/statistics.py +++ b/dsmr_stats/models/statistics.py @@ -1,5 +1,6 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ +from solo.models import SingletonModel class DayStatistics(models.Model): @@ -103,3 +104,67 @@ def __str__(self): return '{}: {}'.format( self.__class__.__name__, self.hour_start ) + + +class ElectricityStatistics(SingletonModel): + """ Keeps track of the highest electricity statistics per phase. """ + highest_usage_l1_timestamp = models.DateTimeField( + null=True, blank=True, default=None, verbose_name=_('Timestamp of highest usage on L1+') + ) + highest_usage_l2_timestamp = models.DateTimeField( + null=True, blank=True, default=None, verbose_name=_('Timestamp of highest usage on L2+') + ) + highest_usage_l3_timestamp = models.DateTimeField( + null=True, blank=True, default=None, verbose_name=_('Timestamp of highest usage on L3+') + ) + highest_return_l1_timestamp = models.DateTimeField( + null=True, blank=True, default=None, verbose_name=_('Timestamp of highest return on L1-') + ) + highest_return_l2_timestamp = models.DateTimeField( + null=True, blank=True, default=None, verbose_name=_('Timestamp of highest return on L2-') + ) + highest_return_l3_timestamp = models.DateTimeField( + null=True, blank=True, default=None, verbose_name=_('Timestamp of highest return on L3-') + ) + + highest_usage_l1_value = models.DecimalField( + max_digits=9, decimal_places=3, null=True, blank=True, default=None, + verbose_name=_('Highest usage on L1+ (in kWh)') + ) + highest_usage_l2_value = models.DecimalField( + max_digits=9, decimal_places=3, null=True, blank=True, default=None, + verbose_name=_('Highest usage on L2+ (in kWh)') + ) + highest_usage_l3_value = models.DecimalField( + max_digits=9, decimal_places=3, null=True, blank=True, default=None, + verbose_name=_('Highest usage on L3+ (in kWh)') + ) + highest_return_l1_value = models.DecimalField( + max_digits=9, decimal_places=3, null=True, blank=True, default=None, + verbose_name=_('Highest return on L1- (in kWh)') + ) + highest_return_l2_value = models.DecimalField( + max_digits=9, decimal_places=3, null=True, blank=True, default=None, + verbose_name=_('Highest return on L2- (in kWh)') + ) + highest_return_l3_value = models.DecimalField( + max_digits=9, decimal_places=3, null=True, blank=True, default=None, + verbose_name=_('Highest return on L3- (in kWh)') + ) + + def export(self): + """ Converts the data in to ready to use format. """ + data = self.__dict__ + + for key in data.keys(): + if key.endswith('_value'): + try: + data[key] = int(data[key] * 1000) or 0 + except TypeError: + data[key] = 0 + + return data + + class Meta: + default_permissions = tuple() + verbose_name = _('Electricity statistics') diff --git a/dsmr_stats/services.py b/dsmr_stats/services.py index a962ca31b..9b0ebc75d 100644 --- a/dsmr_stats/services.py +++ b/dsmr_stats/services.py @@ -1,3 +1,6 @@ +import logging + +from decimal import Decimal from datetime import time import math @@ -8,13 +11,16 @@ from django.utils import timezone from django.conf import settings -from dsmr_stats.models.statistics import DayStatistics, HourStatistics +from dsmr_stats.models.statistics import DayStatistics, HourStatistics, ElectricityStatistics from dsmr_consumption.models.consumption import ElectricityConsumption from dsmr_datalogger.models.reading import DsmrReading import dsmr_consumption.services import dsmr_backend.services +logger = logging.getLogger('commands') + + def analyze(): # noqa: C901 """ Analyzes daily consumption and statistics to determine whether new analysis is required. """ try: @@ -84,7 +90,7 @@ def analyze(): # noqa: C901 return # For backend logging in Supervisor. - print(' - Creating day & hour statistics for: {}.'.format(day_start)) + logger.debug(' - Creating day & hour statistics for: %s', day_start) with transaction.atomic(): # One day at a time to prevent backend blocking. Flushed statistics will be regenerated quickly anyway. @@ -251,7 +257,6 @@ def month_statistics(target_date): """ Alias of daterange_statistics() for a month targeted. """ start_of_month = timezone.datetime(year=target_date.year, month=target_date.month, day=1) end_of_month = timezone.datetime.combine(start_of_month + relativedelta(months=1), time.min) - return range_statistics(start=start_of_month, end=end_of_month) @@ -259,5 +264,31 @@ def year_statistics(target_date): """ Alias of daterange_statistics() for a year targeted. """ start_of_year = timezone.datetime(year=target_date.year, month=1, day=1) end_of_year = timezone.datetime.combine(start_of_year + relativedelta(years=1), time.min) - return range_statistics(start=start_of_year, end=end_of_year) + + +def update_electricity_statistics(reading): + """ Updates the ElectricityStatistics records. """ + MAP = { + # Reading field: Stats record field. + 'phase_currently_delivered_l1': 'highest_usage_l1', + 'phase_currently_delivered_l2': 'highest_usage_l2', + 'phase_currently_delivered_l3': 'highest_usage_l3', + 'phase_currently_returned_l1': 'highest_return_l1', + 'phase_currently_returned_l2': 'highest_return_l2', + 'phase_currently_returned_l3': 'highest_return_l3', + } + stats = ElectricityStatistics.get_solo() + dirty = False + + for reading_field, stat_field in MAP.items(): + reading_value = getattr(reading, reading_field) or 0 + highest_value = getattr(stats, '{}_value'.format(stat_field)) or 0 + + if reading_value and Decimal(reading_value) > Decimal(highest_value): + dirty = True + setattr(stats, '{}_value'.format(stat_field), reading_value) + setattr(stats, '{}_timestamp'.format(stat_field), reading.timestamp) + + if dirty: + stats.save() diff --git a/dsmr_stats/tests/test_services.py b/dsmr_stats/tests/test_services.py index e24c116eb..7d48fa203 100644 --- a/dsmr_stats/tests/test_services.py +++ b/dsmr_stats/tests/test_services.py @@ -7,7 +7,7 @@ from dsmr_backend.tests.mixins import InterceptStdoutMixin from dsmr_consumption.models.consumption import ElectricityConsumption, GasConsumption -from dsmr_stats.models.statistics import DayStatistics, HourStatistics +from dsmr_stats.models.statistics import DayStatistics, HourStatistics, ElectricityStatistics from dsmr_datalogger.models.reading import DsmrReading import dsmr_backend.services import dsmr_stats.services @@ -405,6 +405,70 @@ def test_electricity_tariff_percentage(self): DayStatistics.objects.all().delete() percentages = dsmr_stats.services.electricity_tariff_percentage(start_date=target_date.date()) + @mock.patch('dsmr_stats.services.update_electricity_statistics') + def test_dsmr_update_electricity_statistics_signal(self, service_mock): + """ Test reading creation signal connects service. """ + self.assertFalse(service_mock.called) + reading = DsmrReading.objects.create( + timestamp=timezone.now(), + electricity_delivered_1=0, + electricity_returned_1=0, + electricity_delivered_2=0, + electricity_returned_2=0, + electricity_currently_delivered=0, + electricity_currently_returned=0, + ) + service_mock.assert_called_once_with(reading=reading) + + @mock.patch('django.utils.timezone.now') + def test_update_electricity_statistics(self, now_mock): + now_mock.return_value = timezone.make_aware(timezone.datetime(2018, 1, 1, hour=0)) + stats = ElectricityStatistics.get_solo() + self.assertIsNone(stats.highest_usage_l1_value) + self.assertIsNone(stats.highest_usage_l2_value) + self.assertIsNone(stats.highest_usage_l3_value) + self.assertIsNone(stats.highest_return_l1_value) + self.assertIsNone(stats.highest_return_l2_value) + self.assertIsNone(stats.highest_return_l3_value) + + # This has to differ, to make sure the right timestamp is used. + reading_timestamp = timezone.now() + reading = DsmrReading.objects.create( + timestamp=reading_timestamp, + electricity_delivered_1=0, + electricity_returned_1=0, + electricity_delivered_2=0, + electricity_returned_2=0, + electricity_currently_delivered=0.6, + electricity_currently_returned=1.6, + phase_currently_delivered_l1=0.1, + phase_currently_delivered_l2=0.2, + phase_currently_delivered_l3=0.3, + phase_currently_returned_l1=1.1, + phase_currently_returned_l2=1.2, + phase_currently_returned_l3=1.3, + ) + + # Alter time, processing of reading is later. + now_mock.return_value = timezone.make_aware(timezone.datetime(2018, 1, 1, hour=12)) + self.assertNotEqual(reading_timestamp, timezone.now()) + dsmr_stats.services.update_electricity_statistics(reading=reading) + + # Should be updated now. + stats = ElectricityStatistics.get_solo() + self.assertEqual(stats.highest_usage_l1_value, Decimal('0.100')) + self.assertEqual(stats.highest_usage_l2_value, Decimal('0.200')) + self.assertEqual(stats.highest_usage_l3_value, Decimal('0.300')) + self.assertEqual(stats.highest_return_l1_value, Decimal('1.100')) + self.assertEqual(stats.highest_return_l2_value, Decimal('1.200')) + self.assertEqual(stats.highest_return_l3_value, Decimal('1.300')) + self.assertEqual(stats.highest_usage_l1_timestamp, reading_timestamp) + self.assertEqual(stats.highest_usage_l2_timestamp, reading_timestamp) + self.assertEqual(stats.highest_usage_l3_timestamp, reading_timestamp) + self.assertEqual(stats.highest_return_l1_timestamp, reading_timestamp) + self.assertEqual(stats.highest_return_l2_timestamp, reading_timestamp) + self.assertEqual(stats.highest_return_l3_timestamp, reading_timestamp) + class TestServicesWithoutGas(TestServices): fixtures = ['dsmr_stats/electricity-consumption.json'] diff --git a/dsmr_weather/services.py b/dsmr_weather/services.py index 54373559b..2ffcd3e00 100644 --- a/dsmr_weather/services.py +++ b/dsmr_weather/services.py @@ -1,6 +1,7 @@ import xml.etree.ElementTree as ET -import urllib.request from decimal import Decimal +import urllib.request +import logging from django.utils import timezone @@ -9,6 +10,9 @@ from dsmr_weather.buienradar import BUIENRADAR_API_URL, BUIENRADAR_XPATH +logger = logging.getLogger('commands') + + def should_sync(): """ Checks whether we should sync yet. """ weather_settings = WeatherSettings.get_solo() @@ -29,7 +33,7 @@ def read_weather(): return # For backend logging in Supervisor. - print(' - Performing temperature reading at Buienradar.') + logger.debug(' - Performing temperature reading at Buienradar.') weather_settings = WeatherSettings.get_solo() @@ -37,7 +41,7 @@ def read_weather(): # Fetch XML from API. request = urllib.request.urlopen(BUIENRADAR_API_URL) except Exception as e: - print(' [!] Failed reading temperature: {}'.format(e)) + logger.error(' [!] Failed reading temperature: %s', e) # Try again in 5 minutes. weather_settings.next_sync = timezone.now() + timezone.timedelta(minutes=5) @@ -55,7 +59,7 @@ def read_weather(): ) temperature_element = root.find(xpath) temperature = temperature_element.text - print(' - Read temperature: {}'.format(temperature)) + logger.debug(' - Read temperature: %s', temperature) # Gas readings trigger these readings, so the 'read at' timestamp should be somewhat in sync. # Therefor we align temperature readings with them, having them grouped by hour that is.. diff --git a/dsmrreader/__init__.py b/dsmrreader/__init__.py index fb89722d1..df8b894c5 100644 --- a/dsmrreader/__init__.py +++ b/dsmrreader/__init__.py @@ -17,6 +17,6 @@ from django.utils.version import get_version -VERSION = (1, 23, 1, 'final', 0) +VERSION = (1, 24, 0, 'final', 0) __version__ = get_version(VERSION) diff --git a/dsmrreader/config/base.py b/dsmrreader/config/base.py index 5aa096af9..27b8d2af7 100644 --- a/dsmrreader/config/base.py +++ b/dsmrreader/config/base.py @@ -158,6 +158,58 @@ LOCALE_PATHS = (os.path.join(BASE_DIR, 'locales'), ) +""" Python Logging. """ +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'formatters': { + 'simple': { + 'format': '[%(asctime)s] %(levelname)-8s %(message)s' + }, + 'verbose': { + 'format': '[%(asctime)s] %(levelname)-8s @ %(module)s | %(message)s' + }, + }, + 'handlers': { + 'console': { + 'class': 'logging.StreamHandler', + 'formatter': 'simple', + }, + 'django_file': { + 'class': 'logging.handlers.RotatingFileHandler', + 'filename': os.path.join(BASE_DIR, '..', 'logs', 'django.log'), + 'formatter': 'verbose', + 'maxBytes': 5 * 1024 * 1024, # 5 MB max. + 'backupCount': 7, + }, + 'dsmrreader_file': { + 'class': 'logging.handlers.RotatingFileHandler', + 'filename': os.path.join(BASE_DIR, '..', 'logs', 'dsmrreader.log'), + 'formatter': 'verbose', + 'maxBytes': 5 * 1024 * 1024, # 5 MB max. + 'backupCount': 7, + }, + }, + 'loggers': { + 'commands': { + 'handlers': ['console'], + 'level': 'WARNING', + 'propagate': True, + }, + 'django': { + 'handlers': ['django_file'], + 'level': 'WARNING', + 'propagate': True, + }, + 'dsmrreader': { + 'handlers': ['dsmrreader_file'], + 'level': 'INFO', + 'propagate': True, + }, + }, +} + + """ Django Rest Framework. """ REST_FRAMEWORK = { diff --git a/dsmrreader/config/development.py b/dsmrreader/config/development.py index b5e3a9e78..842f4960f 100644 --- a/dsmrreader/config/development.py +++ b/dsmrreader/config/development.py @@ -4,35 +4,6 @@ DEBUG = True CACHES['default']['TIMEOUT'] = 0 -LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'handlers': { - 'console': { - # logging handler that outputs log messages to terminal - 'class': 'logging.StreamHandler', - 'level': 'DEBUG', # message level to be written to console - }, - }, - 'loggers': { - 'django.db.backends': { -# 'handlers': ['console'], - 'level': 'DEBUG', - 'propagate': False, - }, - 'django': { - 'handlers': ['console'], - 'level': 'WARNING', - 'propagate': True, - }, - 'dsmrreader': { - 'handlers': ['console'], - 'level': 'INFO', - 'propagate': True, - }, - }, -} - INSTALLED_APPS = list(INSTALLED_APPS) INSTALLED_APPS.append('debug_toolbar') diff --git a/dsmrreader/config/production.py b/dsmrreader/config/production.py index 9c760474c..feb7295d0 100644 --- a/dsmrreader/config/production.py +++ b/dsmrreader/config/production.py @@ -15,46 +15,6 @@ CACHES['default']['TIMEOUT'] = 1 * 60 - -LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'formatters': { - 'verbose': { - 'format': '[%(asctime)s] %(levelname)s @ %(module)s | %(message)s' - }, - }, - 'handlers': { - 'django_file': { - 'class': 'logging.handlers.RotatingFileHandler', - 'filename': os.path.join(BASE_DIR, '..', 'logs', 'django.log'), - 'formatter': 'verbose', - 'maxBytes': 5 * 1024 * 1024, # 5 MB max. - 'backupCount': 7, - }, - 'dsmrreader_file': { - 'class': 'logging.handlers.RotatingFileHandler', - 'filename': os.path.join(BASE_DIR, '..', 'logs', 'dsmrreader.log'), - 'formatter': 'verbose', - 'maxBytes': 5 * 1024 * 1024, # 5 MB max. - 'backupCount': 7, - }, - }, - 'loggers': { - 'django': { - 'handlers': ['django_file'], - 'level': 'WARNING', - 'propagate': True, - }, - 'dsmrreader': { - 'handlers': ['dsmrreader_file'], - 'level': 'INFO', - 'propagate': True, - }, - }, -} - - # Disable Django Toolbar. MIDDLEWARE = list(MIDDLEWARE) MIDDLEWARE.remove('debug_toolbar.middleware.DebugToolbarMiddleware') diff --git a/dsmrreader/locales/nl/LC_MESSAGES/django.mo b/dsmrreader/locales/nl/LC_MESSAGES/django.mo index d8480bb7b..632398463 100644 Binary files a/dsmrreader/locales/nl/LC_MESSAGES/django.mo and b/dsmrreader/locales/nl/LC_MESSAGES/django.mo differ diff --git a/dsmrreader/locales/nl/LC_MESSAGES/django.po b/dsmrreader/locales/nl/LC_MESSAGES/django.po index 6c8b1c936..cd4a89e13 100644 --- a/dsmrreader/locales/nl/LC_MESSAGES/django.po +++ b/dsmrreader/locales/nl/LC_MESSAGES/django.po @@ -610,6 +610,9 @@ msgstr "Wijzigingenoverzicht" msgid "Help / FAQ" msgstr "Help / FAQ" +msgid "API v2 Docs" +msgstr "API v2 Docs" + msgid "Feedback" msgstr "Feedback" @@ -811,29 +814,26 @@ msgstr "Het aantal P1 DSMR telegram-metingen die opgeslagen zijn." msgid "Show" msgstr "Toon" -msgid "Usage statistics" -msgstr "Verbruiksstatistieken" - -msgid "Most common electricity consumption" -msgstr "Veelvoorkomend elektriciteitsverbruik" +msgid "All time highs" +msgstr "Allertijden records" -msgid "Average calculated among the top five most common electricity consumption read. This might also be the (minimum) constant electricity consumption in your home." -msgstr "Gemiddeld elektriciteitsverbruik berekend over de vijf meest voorkomende waardes. Dit is vaak ook het (minimale) elektriciteitsverbruik van je woning." +msgid "Highest usage (L1+)" +msgstr "Hoogste verbruik (L1+)" -msgid "Lowest wattage" -msgstr "Laagste wattage" +msgid "Highest usage (L2+)" +msgstr "Hoogste verbruik (L2+)" -msgid "Highest wattage" -msgstr "Hoogste wattage" +msgid "Highest usage (L3+)" +msgstr "Hoogste verbruik (L3+)" -msgid "Highest wattage (L1)" -msgstr "Hoogste wattage (L1)" +msgid "Highest return (L1-)" +msgstr "Hoogste teruglevering (L1-)" -msgid "Highest wattage (L2)" -msgstr "Hoogste wattage (L2)" +msgid "Highest return (L2-)" +msgstr "Hoogste teruglevering (L2-)" -msgid "Highest wattage (L3)" -msgstr "Hoogste wattage (L3)" +msgid "Highest return (L3-)" +msgstr "Hoogste teruglevering (L3-)" msgid "Current energy prices" msgstr "Huidige energietarieven" @@ -1510,8 +1510,8 @@ msgstr "Totale kosten: € {}" msgid "Daily usage notification" msgstr "Dagelijkse verbruiksnotificatie" -msgid "It has been over an hour since the last reading received. Please check your datalogger." -msgstr "Er is meer dan een uur verstreken sinds de laatst ontvangen meting. Controleer de datalogger." +msgid "It has been over {} hour(s) since the last reading received. Please check your datalogger." +msgstr "Er is meer dan {} uur verstreken sinds de laatst ontvangen meting. Controleer de datalogger." msgid "Datalogger check" msgstr "Dataloggercontrole" @@ -1558,11 +1558,11 @@ msgstr "Upload interval" msgid "The interval between each upload (in minutes). Please make sure this matches the device settings." msgstr "De interval tussen elke upload (in minuten). Zorg ervoor dat dit overeenkomt met de apparaatinstellingen in PVOutput." -msgid "Upload delay (minutes)" +msgid "Upload offset (minutes)" msgstr "Uploadvertraging (in minuten)" -msgid "An artificial delay in uploading data to PVOutput. E.g.: When you set this to \"5\" and the application uploads the data at 10:45, then only data between 0:00 and 10:40 will be taken into account for upload at that moment. It effectively limits its upload data search by \"X minutes ago\"." -msgstr "Een kunstmatige vertraging in het uploaden van gegevens naar PVOutput. Voorbeeld: Wanneer je dit instelt op \"5\" en de applicatie probeert om 10:45 gegevens te uploaden, dan worden alleen gegevens tussen 0:00 en 10:40 gebruikt. Effectief beperkt deze instelling dus de zoekradius met \"X minuten geleden\"." +msgid "An artificial delay in uploading data to PVOutput. E.g.: When you set this to \"5\" and the application uploads the data at 10:45, then only data between until 10:40 will be taken into account. It effectively limits its upload data search by \"ignore the last X minutes\", where X is this setting." +msgstr "Een kunstmatige vertraging in het uploaden van gegevens naar PVOutput. Voorbeeld: Wanneer je dit instelt op \"5\" en de applicatie probeert om 10:45 gegevens te uploaden, dan worden alleen gegevens tussen 0:00 en 10:40 gebruikt. Effectief zorgt deze instelling er dus voor dat de te uploaden data beperkt wordt met \"negeer data van de afgelopen X minuten\", waarbij X de waarde van deze instelling is." msgid "PVOutput: Processing delay (minutes)" msgstr "PVOutput: Verwerkingsvertraging (in minuten)" @@ -1615,6 +1615,42 @@ msgstr "Begintijd uur" msgid "Hour statistics (automatically generated data)" msgstr "Uurstatistieken (automatisch gegenereerd)" +msgid "Timestamp of highest usage on L1+" +msgstr "Moment van hoogste verbruik op L1+" + +msgid "Timestamp of highest usage on L2+" +msgstr "Moment van hoogste verbruik op L2+" + +msgid "Timestamp of highest usage on L3+" +msgstr "Moment van hoogste verbruik op L3+" + +msgid "Timestamp of highest return on L1-" +msgstr "Moment van hoogste teruglevering op L1-" + +msgid "Timestamp of highest return on L2-" +msgstr "Moment van hoogste teruglevering op L2-" + +msgid "Timestamp of highest return on L3-" +msgstr "Moment van hoogste teruglevering op L3-" + +msgid "Highest usage on L1+ (in kWh)" +msgstr "Hoogste verbruik op L1+ (in kWh)" + +msgid "Highest usage on L2+ (in kWh)" +msgstr "Hoogste verbruik op L2+ (in kWh)" + +msgid "Highest usage on L3+ (in kWh)" +msgstr "Hoogste verbruik op L3+ (in kWh)" + +msgid "Highest return on L1- (in kWh)" +msgstr "Hoogste teruglevering op L1- (in kWh)" + +msgid "Highest return on L2- (in kWh)" +msgstr "Hoogste teruglevering op L2- (in kWh)" + +msgid "Highest return on L3- (in kWh)" +msgstr "Hoogste teruglevering op L3- (in kWh)" + msgid "Buienradar" msgstr "Buienradar" @@ -1804,8 +1840,5 @@ msgstr "Nederlands" msgid "English" msgstr "Engels" -#~ msgid "The port of the broker to send MQTT messages to." -#~ msgstr "De poort om MQTT-berichten naar te versturen." - -#~ msgid "Tests MQTT setup." -#~ msgstr "Test MQTT setup." +msgid "Full documentation available at: https://dsmr-reader.readthedocs.io/en/latest/api.html" +msgstr "Volledige documentatie beschikbaar op: https://dsmr-reader.readthedocs.io/nl/latest/api.html" diff --git a/dsmrreader/provisioning/django/mysql.py b/dsmrreader/provisioning/django/mysql.py index ddf5d7d67..fbe398de8 100644 --- a/dsmrreader/provisioning/django/mysql.py +++ b/dsmrreader/provisioning/django/mysql.py @@ -27,3 +27,14 @@ } SECRET_KEY = os.environ.get('SECRET_KEY', DSMRREADER_SECRET_KEY) + + +""" + Enable and change the logging level below to alter the verbosity of the (backend) command(s). + - DEBUG: Log everything. + - INFO: Log most things. + - WARNING (default): Log only warnings and errors. + + Restart the commands in Supervisor to apply any changes. +""" +# LOGGING['loggers']['commands']['level'] = 'DEBUG' diff --git a/dsmrreader/provisioning/django/postgresql.py b/dsmrreader/provisioning/django/postgresql.py index aca0662a3..314b6abd6 100644 --- a/dsmrreader/provisioning/django/postgresql.py +++ b/dsmrreader/provisioning/django/postgresql.py @@ -27,3 +27,14 @@ } SECRET_KEY = os.environ.get('SECRET_KEY', DSMRREADER_SECRET_KEY) + + +""" + Enable and change the logging level below to alter the verbosity of the (backend) command(s). + - DEBUG: Log everything. + - INFO: Log most things. + - WARNING (default): Log only warnings and errors. + + Restart the commands in Supervisor to apply any changes. +""" +# LOGGING['loggers']['commands']['level'] = 'DEBUG' diff --git a/dsmrreader/provisioning/requirements/base.txt b/dsmrreader/provisioning/requirements/base.txt index cf28ec68d..f61907305 100644 --- a/dsmrreader/provisioning/requirements/base.txt +++ b/dsmrreader/provisioning/requirements/base.txt @@ -2,7 +2,7 @@ crcmod==1.7 coreapi==2.3.3 django==2.0.8 django-colorfield==0.1.15 -django-debug-toolbar==1.9.1 +django-debug-toolbar==1.10.1 django-filter==2.0.0 djangorestframework==3.8.2 django-solo==1.1.3 diff --git a/dsmrreader/provisioning/requirements/dev.txt b/dsmrreader/provisioning/requirements/dev.txt index e10310176..68560b206 100644 --- a/dsmrreader/provisioning/requirements/dev.txt +++ b/dsmrreader/provisioning/requirements/dev.txt @@ -1,4 +1,4 @@ -sphinx==1.7.6 +sphinx==1.8.1 sphinx-autobuild==0.7.1 sphinx-intl==0.9.11 sphinx-rtd-theme==0.4.1 diff --git a/dsmrreader/provisioning/requirements/test.txt b/dsmrreader/provisioning/requirements/test.txt index 00c4d6be8..50e49e753 100644 --- a/dsmrreader/provisioning/requirements/test.txt +++ b/dsmrreader/provisioning/requirements/test.txt @@ -1,5 +1,5 @@ polib==1.1.0 -pylama==7.4.3 -pytest-cov==2.5.1 -pytest-django==3.3.3 -pytest-xdist==1.22.5 +pylama==7.4.3 ; python_version < '3.7' +pytest-cov==2.6.0 +pytest-django==3.4.3 +pytest-xdist==1.23.0 diff --git a/dsmrreader/provisioning/requirements/travis.txt b/dsmrreader/provisioning/requirements/travis.txt index 17f1c13b6..293b4c56c 100644 --- a/dsmrreader/provisioning/requirements/travis.txt +++ b/dsmrreader/provisioning/requirements/travis.txt @@ -1,2 +1,2 @@ # For some reason Travis needs to be forced installing this version, or the build pretend like it doesn't know 'pytest'. -pytest==3.7.0 +pytest==3.8.1 diff --git a/dsmrreader/provisioning/supervisor/dsmr-reader.conf b/dsmrreader/provisioning/supervisor/dsmr-reader.conf index c2a054806..8dab38da5 100644 --- a/dsmrreader/provisioning/supervisor/dsmr-reader.conf +++ b/dsmrreader/provisioning/supervisor/dsmr-reader.conf @@ -25,6 +25,7 @@ stdout_logfile_backups=5 ################################################################################### [program:dsmr_backend] +environment=PYTHONUNBUFFERED=1 command=/usr/bin/nice -n 10 /home/dsmr/.virtualenvs/dsmrreader/bin/python3 -u /home/dsmr/dsmr-reader/manage.py dsmr_backend directory=/home/dsmr/dsmr-reader/ user=dsmr @@ -46,6 +47,7 @@ stdout_logfile_backups=5 ###################################################### [program:dsmr_mqtt] +environment=PYTHONUNBUFFERED=1 command=/usr/bin/nice -n 15 /home/dsmr/.virtualenvs/dsmrreader/bin/python3 -u /home/dsmr/dsmr-reader/manage.py dsmr_mqtt directory=/home/dsmr/dsmr-reader/ user=dsmr @@ -67,6 +69,7 @@ stdout_logfile_backups=5 ######################################## [program:dsmr_webinterface] +environment=PYTHONUNBUFFERED=1 command=/usr/bin/nice -n 15 /home/dsmr/.virtualenvs/dsmrreader/bin/gunicorn --timeout 60 --max-requests 500 --bind unix:/var/tmp/gunicorn--%(program_name)s.socket --pid /var/tmp/gunicorn--%(program_name)s.pid dsmrreader.wsgi directory=/home/dsmr/dsmr-reader/ user=dsmr diff --git a/dsmrreader/urls.py b/dsmrreader/urls.py index d21942af7..3a483139d 100644 --- a/dsmrreader/urls.py +++ b/dsmrreader/urls.py @@ -1,18 +1,27 @@ -from django.conf.urls import include, url +from django.utils.translation import ugettext_lazy as _ +from django.conf.urls import include +from django.urls.conf import path from django.contrib import admin from django.conf import settings +from rest_framework.documentation import include_docs_urls urlpatterns = [ - url(r'^admin/', admin.site.urls), - url(r'^api/v1/', include('dsmr_api.urls.v1')), - url(r'^api/v2/', include('dsmr_api.urls.v2')), - url(r'^', include('dsmr_frontend.urls')), + path('admin/', admin.site.urls), + path('api/v1/', include('dsmr_api.urls.v1')), + path('api/v2/', include('dsmr_api.urls.v2')), + path('docs/v2/', include_docs_urls( + title='DSMR-reader API v2', + description=_('Full documentation available at: https://dsmr-reader.readthedocs.io/en/latest/api.html'), + authentication_classes=[], + permission_classes=[] + )), + path('', include('dsmr_frontend.urls')), ] if settings.DEBUG: import debug_toolbar # pragma: no cover urlpatterns += [ - url(r'^__debug__/', include(debug_toolbar.urls)), + path('__debug__/', include(debug_toolbar.urls)), ] # pragma: no cover diff --git a/tools/quick-test.sh b/tools/quick-test.sh index 6baa6f264..acff0940a 100755 --- a/tools/quick-test.sh +++ b/tools/quick-test.sh @@ -1,6 +1,5 @@ #!/bin/bash - ARGS="" for CURRENT in "$@" @@ -11,7 +10,20 @@ done echo "" echo "--- Testing with SQLite (4 processes)..." -pytest --pylama --cov --cov-report=html:coverage_report/html --cov-report=term --ds=dsmrreader.config.test.sqlite -n 4 $ARGS +pytest --cov --cov-report=html:coverage_report/html --cov-report=term --ds=dsmrreader.config.test.sqlite -n 4 $ARGS + + +echo "" +echo "--- Running Pylama for code audit..." +pylama + +# Abort when audit fails. +if [ $? -ne 0 ]; then + echo "[!] Code audit failed [!]" + exit; +fi + +echo "OK" DIR=$(cd `dirname $0` && pwd)
    {% trans "Highest usage (L1+)" %} - {% trans "Most common electricity consumption" %} -
    - {% blocktrans %}Average calculated among the top five most common electricity consumption read. This might also be the (minimum) constant electricity consumption in your home.{% endblocktrans %}
    -
    -   {% trans 'Show' %} - - -     {% trans "Watt" %} - + +   {{ electricity_statistics.highest_usage_l1_value }}   + {% trans "Watt" %}   -   {{ electricity_statistics.highest_usage_l1_timestamp }} +
    {% trans "Lowest wattage" %}{% trans "Highest usage (L2+)" %} -   {% trans 'Show' %} - - -     {% trans "Watt" %}   -   - + +   {{ electricity_statistics.highest_usage_l2_value }}   + {% trans "Watt" %}   -   {{ electricity_statistics.highest_usage_l2_timestamp }} +
    {% trans "Highest wattage" %}{% trans "Highest usage (L3+)" %} -   {% trans 'Show' %} - - -     {% trans "Watt" %}   -   - + +   {{ electricity_statistics.highest_usage_l3_value }}   + {% trans "Watt" %}   -   {{ electricity_statistics.highest_usage_l3_timestamp }} +
    {% trans "Highest return (L1-)" %} -   {% trans 'Show' %} - - -     {% trans "Watt" %}   -   + +   {{ electricity_statistics.highest_return_l1_value }}   + {% trans "Watt" %}   -   {{ electricity_statistics.highest_return_l1_timestamp }}
    {% trans "Highest return (L2-)" %} -   {% trans 'Show' %} - - -     {% trans "Watt" %}   -   + +   {{ electricity_statistics.highest_return_l2_value }}   + {% trans "Watt" %}   -   {{ electricity_statistics.highest_return_l2_timestamp }}
    {% trans "Highest return (L3-)" %} -   {% trans 'Show' %} - - -     {% trans "Watt" %}   -   + +   {{ electricity_statistics.highest_return_l3_value }}   + {% trans "Watt" %}   -   {{ electricity_statistics.highest_return_l3_timestamp }}