Compare commits

..

233 Commits

Author SHA1 Message Date
Gunnar Skjold
473932f1bc Fixed Zmartcharge configuration issue 2025-10-15 16:35:36 +02:00
Gunnar Skjold
d7e68ce692 Fixing board type overwrite, zmartcharge default issues and disabling entsoe 2025-10-10 15:25:12 +02:00
Gunnar Skjold
e11fac3d11 Added deploy action for Localazy translations (#1014)
* Added deploy action for Localazy translations

* Fixed yml
2025-09-25 14:03:51 +02:00
Gunnar Skjold
031422f783 Show error when unknown data was received (#1013) 2025-09-25 12:20:31 +02:00
Gunnar Skjold
2ff8fddc14 Fixed incorrect enable state for ZmartCharge when no config (#1012) 2025-09-25 12:05:18 +02:00
Gunnar Skjold
e5d260ae3e Zmartcharge support (#1007)
* ZC initial implementation

* ZmartCharge

* Fixed zc bug

* Adjustments to ZmartCharge connection
2025-09-25 11:38:05 +02:00
Gunnar Skjold
633671851e Fixed Kamstrup timestamp parsing (#1011)
* Fixed kamstrup timestamp deviation

* Fixed check for time zone
2025-09-25 10:47:47 +02:00
Gunnar Skjold
69da9f9d48 Only allow board type overwrite from config if not set (#1010) 2025-09-25 10:47:35 +02:00
Gunnar Skjold
19f78126d6 Auto enable price fetch when region is changed (#1009) 2025-09-25 10:47:22 +02:00
Gunnar Skjold
d3cc92949a Trim leading and trailing whitespace (#1008) 2025-09-25 10:46:26 +02:00
Gunnar Skjold
f1089faab5 Maintian "no config" state after vendor config (#999) 2025-09-25 10:46:02 +02:00
Gunnar Skjold
4a3ad6ab9b Fixed /setup redirect after /vendor (#998) 2025-09-25 10:44:17 +02:00
Falke Carlsen
86449949c5 refactor: fix 'boundry' typo to 'boundary' (#982) 2025-09-25 10:43:57 +02:00
Gunnar Skjold
9bd9826835 Added GPIO 20 and 21 for ESP32-C3 (#979)
* Added GPIO 20 and 21 for C3

* Updated webapp after changing GPIO for C3
2025-09-25 10:39:55 +02:00
ArnieO
94ff9d6da7 Added Omnipower frame (#1002)
Kamstrup Omnipower frames from Bornholm, DK - with two decimals on the voltage reading. The customary is voltage without decimals. 
Received 9-sep-25.
2025-09-09 17:25:50 +02:00
ArnieO
9518d1811b Uploaded new *.raw file (#995)
From Landis&Gyr E350, HAN-NVE, Norway. Meter owner: https://klive.no/
2025-08-22 15:28:09 +02:00
ArnieO
983426b36c Add Poland docs (#987)
* Create Poland/Stoen folder structure

* Add PDF documents
2025-07-29 09:18:45 +02:00
Gunnar Skjold
bcb3c3b2ec Fixed price config with no dates (#974) 2025-06-05 11:47:36 +02:00
Gunnar Skjold
9fd383c1ef Updated release title (#973) 2025-06-05 08:05:50 +02:00
dependabot[bot]
a931f4cef8 Bump vite from 4.5.9 to 4.5.14 in /lib/SvelteUi/app (#972)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.5.9 to 4.5.14.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v4.5.14/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.5.14/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 4.5.14
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-05 07:42:29 +02:00
Gunnar Skjold
fddecaab39 Added meter multipliers to config file (#971) 2025-06-05 07:42:12 +02:00
Gunnar Skjold
e5eab82d68 Use list type 4 when phase power is included in Slovenian format (#967) 2025-06-05 07:41:50 +02:00
Gunnar Skjold
8ae1d46b2a Fixed DSMR timestamp parsing (#966)
* Fixed DSMR parsing and added support for more formats

* Add current time as package timestamp for DSMR
2025-06-05 07:41:36 +02:00
Gunnar Skjold
99ccb03b45 Added per phase power for L&G (#965)
* Added phase power parsing for Austrian L&G

* Use list type 4 when L&G phase power is present
2025-06-05 07:41:19 +02:00
Gunnar Skjold
b8f2d501a5 Increased kwh resultion in plot json (#961) 2025-06-05 07:40:57 +02:00
Gunnar Skjold
e042806619 Fixed multiplier problem for some L&G meters (#960) 2025-06-05 07:40:44 +02:00
Gunnar Skjold
16f9ed7ecb Fixed hour skew on price modifier (#959)
* Use local timezone in price config

* Additional changes for previous commit

* Use CET/CEST for energy prices collected from server
2025-06-05 07:40:29 +02:00
Gunnar Skjold
3eaefefd26 Fixed price modifier date inclusion (#958)
* Support price config end before start

* More changes to fix price modifiers

* More changes to fix price modifiers
2025-06-05 07:40:09 +02:00
Gunnar Skjold
03c8c3ddbc Fixed release name (#953) 2025-06-05 07:39:36 +02:00
dependabot[bot]
08371b9078 Bump http-proxy-middleware from 2.0.7 to 2.0.9 in /lib/SvelteUi/app (#950)
Bumps [http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware) from 2.0.7 to 2.0.9.
- [Release notes](https://github.com/chimurai/http-proxy-middleware/releases)
- [Changelog](https://github.com/chimurai/http-proxy-middleware/blob/v2.0.9/CHANGELOG.md)
- [Commits](https://github.com/chimurai/http-proxy-middleware/compare/v2.0.7...v2.0.9)

---
updated-dependencies:
- dependency-name: http-proxy-middleware
  dependency-version: 2.0.9
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-05 07:39:14 +02:00
Gunnar Skjold
a6ae25f3e7 Changed way of creating release (#944) 2025-03-24 10:51:07 +01:00
dependabot[bot]
8051db6a9b Bump esbuild from 0.18.20 to 0.25.1 in /lib/SvelteUi/app (#943)
Bumps [esbuild](https://github.com/evanw/esbuild) from 0.18.20 to 0.25.1.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG-2023.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.18.20...v0.25.1)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-24 09:10:54 +01:00
Gunnar Skjold
d0bfdae5d8 Automatic release notes (#942) 2025-03-24 09:05:02 +01:00
Gunnar Skjold
4d681ed2e2 Support fw upgrade via MQTT with JSON payload (#941) 2025-03-24 09:00:05 +01:00
Gunnar Skjold
8ee3f53714 Fixed firmare upload when web context is defined (#938) 2025-03-24 08:59:52 +01:00
Gunnar Skjold
e8cf8a98ed Fixed baud/parity autodetect (#937) 2025-03-24 08:59:40 +01:00
Gunnar Skjold
9153a98694 Fix: #935 - Brownout reboot loop (#936) 2025-03-24 08:59:30 +01:00
Gunnar Skjold
37aa6ae816 Fix: #918 - MQTT/SSL does not reconnect after disconnect (#933) 2025-03-24 08:59:18 +01:00
Gunnar Skjold
8a35346fcf Fixed backup and restore of price modifiers (#925) (#932)
* Fix: #925 - Backup/restore of price modifiers

* Removed unused importy
2025-03-24 08:58:31 +01:00
Gunnar Skjold
792ae4c935 Fix: #926 - Historical data lost when upgrading ESP8266 between 2.4.x versions (#931) 2025-03-24 08:58:14 +01:00
Gunnar Skjold
a7324d828a Fixed nullpointer in upgrade 2025-02-14 18:51:07 +01:00
Gunnar Skjold
fe739c51d3 Cloud stuff 2025-02-14 18:04:12 +01:00
Gunnar Skjold
795d2d0375 Some debugging of cloud connection 2025-02-14 14:51:47 +01:00
Gunnar Skjold
5ef36a91f6 Updated esbuild dependency 2025-02-13 13:10:50 +01:00
Gunnar Skjold
8491d6c471 Changed to the latest possible node version 2025-02-13 13:07:57 +01:00
Gunnar Skjold
f95f22058a Merge branch 'main' of github.com:UtilitechAS/amsreader-firmware 2025-02-13 13:01:05 +01:00
Gunnar Skjold
b5c45cebfa Merge pull request #866 from saddfox/devcontainer
Add devcontainer
2025-02-13 12:55:57 +01:00
Gunnar Skjold
19a953b269 Updated workflows 2025-02-13 12:55:43 +01:00
Gunnar Skjold
6ae970ff68 Updated workflows 2025-02-13 12:45:43 +01:00
Gunnar Skjold
0f0ee82af9 Various bug fix for GUI 2025-02-13 12:33:54 +01:00
Gunnar Skjold
d84b9351e1 Fixed ESP8266 build 2025-02-03 18:02:20 +01:00
Gunnar Skjold
6668258b66 Support for update entity in HA 2025-02-03 17:51:36 +01:00
Gunnar Skjold
9c42aab04f Fixed extra price 2025-01-27 16:31:43 +01:00
Gunnar Skjold
c771870e3e Merge branch 'main' of github.com:UtilitechAS/amsreader-firmware 2025-01-27 16:08:02 +01:00
Gunnar Skjold
fb59ee52c1 Merge pull request #890 from UtilitechAS/dependabot/npm_and_yarn/lib/SvelteUi/app/nanoid-3.3.8
Bump nanoid from 3.3.7 to 3.3.8 in /lib/SvelteUi/app
2025-01-27 16:04:28 +01:00
Gunnar Skjold
3483910136 Merge pull request #907 from UtilitechAS/dependabot/npm_and_yarn/lib/SvelteUi/app/vite-4.5.9
Bump vite from 4.5.5 to 4.5.9 in /lib/SvelteUi/app
2025-01-27 16:04:16 +01:00
Gunnar Skjold
13e70f7bd4 Merge pull request #906 from dbeinder/nullterm
Reserve space for null terminator
2025-01-27 16:03:44 +01:00
Gunnar Skjold
210001e232 Prevent meterid override 2025-01-24 13:22:08 +01:00
Gunnar Skjold
094e588ad5 Strip non-ascii from meter id 2025-01-24 07:44:21 +01:00
Gunnar Skjold
f9d0cdfa47 Fixed nullpointer 2025-01-23 21:37:17 +01:00
Gunnar Skjold
0476058958 Fixed wifi scan for 8266 2025-01-23 14:46:52 +01:00
Gunnar Skjold
3e337a5639 Option to enter manual SSID 2025-01-23 14:37:28 +01:00
Gunnar Skjold
bf0e1d1bf3 Lib updates 2025-01-23 14:16:05 +01:00
Gunnar Skjold
d3b65b0175 SSID scan 2025-01-23 14:13:23 +01:00
Gunnar Skjold
2caa7252a0 Enforce pattern on all input fields 2025-01-23 12:01:39 +01:00
Gunnar Skjold
f7596de166 Fixed memory leak 2025-01-23 11:22:03 +01:00
Gunnar Skjold
f5178459e8 Fixed incorrect additional price in array 2025-01-22 19:59:41 +01:00
Gunnar Skjold
a55f7dc66a Fixed vcc reading on esp8266 2025-01-22 19:09:08 +01:00
Gunnar Skjold
3d9cad8953 Fixed invalid characters in meterId 2025-01-22 17:56:33 +01:00
Gunnar Skjold
67535b2792 Fixed cloud connection for C3 2025-01-22 17:49:54 +01:00
Gunnar Skjold
2b5f3f50df Fixed cloud connection for C3 2025-01-22 17:49:38 +01:00
Gunnar Skjold
111807a743 Web framework update 2025-01-22 17:32:41 +01:00
dependabot[bot]
348ba8cfc4 Bump vite from 4.5.5 to 4.5.9 in /lib/SvelteUi/app
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.5.5 to 4.5.9.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v4.5.9/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.5.9/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-22 06:43:57 +00:00
david-beinder
81c72f0ca3 Reserve space for null terminator 2025-01-18 01:11:24 +01:00
Gunnar Skjold
20de294a8c Updated build 2025-01-12 18:21:40 +01:00
Gunnar Skjold
e31faaa726 Added a DSMR frame as documentation 2025-01-12 17:59:26 +01:00
Gunnar Skjold
4ad2921132 Fixed config reload for HA and Domoticz if changed 2025-01-12 12:01:16 +01:00
Gunnar Skjold
1fa62fa97c Fixed aidon package timestamp 2025-01-12 10:32:14 +01:00
Gunnar Skjold
affa66f78f Allow special characters in mqtt user/pass 2025-01-12 10:23:57 +01:00
Gunnar Skjold
84860bc684 Failsafe on combining tariff counters 2025-01-12 10:16:19 +01:00
Gunnar Skjold
0cadedf9df Fixed red blink after 49 days 2025-01-12 10:05:57 +01:00
Gunnar Skjold
0c3eea8d37 Added timestamp sensors to HA 2025-01-12 09:57:29 +01:00
Gunnar Skjold
1d4e7e564d Fixed timestamp bug 2025-01-09 15:35:22 +01:00
Gunnar Skjold
067aa0c6cb More changes for slovakian iskra 2024-12-23 11:57:32 +01:00
dependabot[bot]
3d540e2a65 Bump nanoid from 3.3.7 to 3.3.8 in /lib/SvelteUi/app
Bumps [nanoid](https://github.com/ai/nanoid) from 3.3.7 to 3.3.8.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/3.3.7...3.3.8)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-17 12:48:44 +00:00
Gunnar Skjold
61d4728ffa DSMR with encrypted payload 2024-12-14 10:54:02 +01:00
Gunnar Skjold
42e0ca963c Merge branch 'main' into pnu 2024-12-12 10:51:52 +01:00
Gunnar Skjold
3bc40cb136 Some adjustments for Iskra 2024-12-12 09:44:10 +01:00
Gunnar Skjold
1453bbbb7e Adjustments on auto updater for 8266 2024-12-07 21:45:47 +01:00
Gunnar Skjold
9da2d0760e More adjustments for auto update 2024-12-07 11:32:45 +01:00
Gunnar Skjold
18af98511a Some adjustments 2024-12-05 20:10:44 +01:00
Gunnar Skjold
40c84d2429 Fixed firmware upload from GUI 2024-12-05 19:15:17 +01:00
Gunnar Skjold
c0d95f918e Merge branch 'main' into pnu 2024-12-05 18:15:37 +01:00
Gunnar Skjold
0947d335ce Fixed unprocessable characters in json 2024-12-05 14:20:12 +01:00
Gunnar Skjold
43e2b2478b Added more for Slovenian Iskra 2024-12-05 13:57:33 +01:00
Gunnar Skjold
a5fc1f0cbe New firmware upload 2024-12-05 13:40:30 +01:00
Gunnar Skjold
bf3059ba04 Now moving LittleFS in a better way 2024-12-04 20:37:56 +01:00
Gunnar Skjold
319b534d4f Improved firmware update 2024-11-29 16:44:56 +01:00
Gunnar Skjold
b7c2510098 Store upgrade progress to eeprom 2024-11-29 11:26:00 +01:00
Gunnar Skjold
07205b8008 Merge branch 'main' into pnu
# Conflicts:
#	lib/HwTools/src/HwTools.cpp
#	lib/SvelteUi/app/dist/index.css
#	lib/SvelteUi/app/dist/index.js
#	lib/SvelteUi/app/vite.config.js
2024-11-29 08:05:49 +01:00
saddfox
165a385844 add devcontainer 2024-11-10 18:09:18 +01:00
Gunnar Skjold
fa76233151 Added missing code for setting mqtt keep alive 2024-11-09 15:44:30 +01:00
Gunnar Skjold
22b7877ee9 Fixed build error 2024-11-09 15:29:19 +01:00
Gunnar Skjold
bd9a1bb330 Some frame data 2024-11-09 15:17:04 +01:00
Gunnar Skjold
1b5efaa937 Fixed some labels 2024-11-09 15:16:53 +01:00
Gunnar Skjold
05ce4c5c1a Ability to change MQTT timeout and keepalive 2024-11-09 15:12:54 +01:00
Gunnar Skjold
e022f630b2 Removed UDP as recommended cloud connection method 2024-11-07 09:55:15 +01:00
Gunnar Skjold
a73ff87653 Removed UDP as recommended cloud connection method 2024-11-07 09:37:05 +01:00
Gunnar Skjold
70d6a8699b Added default settings to KMP communicator 2024-11-06 18:22:56 +01:00
Gunnar Skjold
118c633878 Fixed autodetect 2024-11-06 18:10:38 +01:00
Gunnar Skjold
fa5985f60b Prevent redirect loop when changing from context to empty 2024-11-04 08:41:34 +01:00
Gunnar Skjold
c1309e9a13 Add package timestamp if missing 2024-11-03 11:28:03 +01:00
Gunnar Skjold
718eef7999 Fixed configfile download when using context path 2024-11-03 11:21:35 +01:00
Gunnar Skjold
afdd282adf Make client id required 2024-11-03 10:43:27 +01:00
Gunnar Skjold
1302f18fab Adjustments to make IPv6 address visible in GUI 2024-11-03 10:21:13 +01:00
Gunnar Skjold
fc89670ea4 Manually merged changes from pull #826 2024-11-03 10:07:51 +01:00
Gunnar Skjold
225320ea7d git branchMerge branch 'main' of github.com:UtilitechAS/amsreader-firmware 2024-11-03 10:02:00 +01:00
Gunnar Skjold
b2cf3bb0c4 Merge pull request #847 from UtilitechAS/dependabot/npm_and_yarn/lib/SvelteUi/app/rollup-3.29.5
Bump rollup from 3.29.4 to 3.29.5 in /lib/SvelteUi/app
2024-11-03 09:59:23 +01:00
Gunnar Skjold
e36a8ade42 Merge pull request #848 from ackjewtn/add_wesp32_poe_board
Add support for wESP32-PoE board
2024-11-03 09:59:13 +01:00
Gunnar Skjold
2ef67d92c4 Fixed missing prices after changes in entsoe API 2024-11-03 09:51:54 +01:00
Peter Wahlberg
1c9af6b1c2 Revert formatting 2024-09-27 17:17:02 +02:00
Peter Wahlberg
4f47ffa21e Add support for wESP32-PoE board 2024-09-27 17:01:30 +02:00
dependabot[bot]
3d1026f829 Bump rollup from 3.29.4 to 3.29.5 in /lib/SvelteUi/app
Bumps [rollup](https://github.com/rollup/rollup) from 3.29.4 to 3.29.5.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v3.29.4...v3.29.5)

---
updated-dependencies:
- dependency-name: rollup
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-27 05:02:27 +00:00
Gunnar Skjold
af6ecc5d47 Adjustments of clock parsing from dlms 2024-09-21 20:03:46 +02:00
Gunnar Skjold
025c556626 More relaxed on when hourly data is received 2024-09-21 16:04:05 +02:00
Gunnar Skjold
0b342d6c49 Some changes to data update 2024-09-21 13:00:22 +02:00
Gunnar Skjold
ada57b1052 Trying to fix data reset issue 2024-09-19 12:54:58 +02:00
Gunnar Skjold
cf773985b1 Merge pull request #836 from UtilitechAS/dependabot/npm_and_yarn/lib/SvelteUi/app/svelte-4.2.19
Bump svelte from 3.59.2 to 4.2.19 in /lib/SvelteUi/app
2024-09-19 12:39:39 +02:00
Gunnar Skjold
75a14ad9cb Merge pull request #840 from ruffy91/patch-1
Added kWH multiplier to phases
2024-09-19 12:39:26 +02:00
Fabian
fbe90cbc5a Update IEC6205675.cpp
kWh multiplier also applies to all the phases, not only total energy
2024-09-04 21:38:20 +02:00
dependabot[bot]
f0bc895952 Bump svelte from 3.59.2 to 4.2.19 in /lib/SvelteUi/app
Bumps [svelte](https://github.com/sveltejs/svelte/tree/HEAD/packages/svelte) from 3.59.2 to 4.2.19.
- [Release notes](https://github.com/sveltejs/svelte/releases)
- [Changelog](https://github.com/sveltejs/svelte/blob/svelte@4.2.19/packages/svelte/CHANGELOG.md)
- [Commits](https://github.com/sveltejs/svelte/commits/svelte@4.2.19/packages/svelte)

---
updated-dependencies:
- dependency-name: svelte
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-30 17:21:59 +00:00
Gunnar Skjold
f5c10c1a50 Send price config to cloud 2024-08-24 21:22:43 +02:00
Gunnar Skjold
c4736c730e Send price config to cloud 2024-08-24 20:55:34 +02:00
Gunnar Skjold
d2c96c733c Send price config to cloud 2024-08-24 09:40:58 +02:00
Gunnar Skjold
d0dd9de4d7 Updated fix for missing graph data 2024-08-24 08:37:24 +02:00
Gunnar Skjold
159244974d Trying to fix issue 2024-08-20 20:55:26 +02:00
Gunnar Skjold
933926fbb4 Fixed debug logging 2024-08-15 20:02:35 +02:00
Gunnar Skjold
2c95f036ba Fixed power factor sensors in HA 2024-08-15 19:44:24 +02:00
Gunnar Skjold
c5c0b52eb7 Try to fix day clearing issue 2024-08-15 19:33:30 +02:00
Gunnar Skjold
561b62cafe Cloud configuration 2024-08-15 19:18:10 +02:00
Gunnar Skjold
55520cd7f6 GUI package upgrades 2024-08-06 20:54:39 +02:00
Gunnar Skjold
e27d41839e Merge branch 'main' of github.com:UtilitechAS/amsreader-firmware 2024-08-06 20:52:41 +02:00
Gunnar Skjold
569a0ddfaa Some final adjustments 2024-08-06 20:51:02 +02:00
Gunnar Skjold
de63263dbb Fixed date skew when computer is on different timezone 2024-08-02 22:34:53 -08:00
Gunnar Skjold
8adb7055c0 Fixed date skew when computer is on different timezone 2024-08-02 19:02:00 -11:00
Gunnar Skjold
11e09c5b56 Fixed undefined month 2024-08-03 07:32:10 +02:00
Gunnar Skjold
ac32f74eba Bugfix 2024-08-02 19:59:30 +02:00
Gunnar Skjold
d49b8c3839 Option to disable UART TX 2024-08-02 19:59:16 +02:00
Gunnar Skjold
054fd43a0d Initial implementation of custom upgrade code 2024-08-02 17:56:39 +02:00
ArnieO
423c5da25e Uploaded overview of meters / interfaces 2024-07-17 09:11:04 +02:00
Gunnar Skjold
9a4a8a10f2 Corrected RSA block size 2024-07-14 21:31:17 +02:00
Gunnar Skjold
d15992142e Removed test ip 2024-07-14 21:24:29 +02:00
Gunnar Skjold
fed3948f85 Corrected RSA block size 2024-07-14 21:23:45 +02:00
Gunnar Skjold
b77342a648 Some cloud changes 2024-07-14 21:07:50 +02:00
Gunnar Skjold
04f407aba0 Stability improvements 2024-07-11 17:48:50 +02:00
Gunnar Skjold
b122fae04c Updated license headers 2024-07-11 14:03:00 +02:00
Gunnar Skjold
8a4efd0047 Restructuring to be able to include precompiled kmp lib 2024-07-11 13:15:22 +02:00
Gunnar Skjold
4407526d96 Changed order of if cases 2024-07-03 09:56:08 +02:00
Gunnar Skjold
1d91416348 Fix for meters with clock skew 2024-07-03 09:22:09 +02:00
Gunnar Skjold
a1bbcc20e3 Fixed 8266 build 2024-06-29 10:24:38 +02:00
Gunnar Skjold
2b779920d7 Fixed issue when graphs go blank if meter clock is running fast 2024-06-29 10:18:24 +02:00
Gunnar Skjold
de79fd4c43 Send prices to MQTT when having fixed price only 2024-06-29 10:15:15 +02:00
Gunnar Skjold
8dfaa34d03 Buffer adjustments for 8266 2024-06-29 10:14:43 +02:00
Gunnar Skjold
0d0dc07903 Some changes for cloud service 2024-06-29 10:13:41 +02:00
Gunnar Skjold
e38a064928 Fixed timezone issue in DLMS timestamps 2024-06-27 10:54:22 +02:00
Gunnar Skjold
81b3aacc4d Fixed debug 2024-06-15 09:21:12 +02:00
Gunnar Skjold
6277d5880d Updated frontened 2024-06-10 17:28:34 +02:00
Gunnar Skjold
8424d1f75e Set threephase when applying OBIS codes 2024-06-09 10:21:55 +02:00
Gunnar Skjold
099b23d2d5 Merge branch 'main' of github.com:UtilitechAS/amsreader-firmware 2024-06-07 19:13:21 +02:00
Gunnar Skjold
28d746415c Remote KMP as selection in config 2024-06-07 19:13:03 +02:00
Gunnar Skjold
fd373a5846 Disable remote debug for C3 2024-06-07 19:12:33 +02:00
Gunnar Skjold
cd7315990f Build option to enable KMP 2024-06-07 18:55:37 +02:00
Gunnar Skjold
59bf0ce066 Build option to disable remote debug 2024-06-07 18:48:26 +02:00
Gunnar Skjold
93e55f457a Tariff thresholds on MQTT 2024-06-06 21:38:03 +02:00
Gunnar Skjold
4d340c5482 Use estimated if Aidon sends same value as last hour 2024-06-06 21:37:45 +02:00
Gunnar Skjold
1db0082103 Added export price sensor to HA payload 2024-06-06 18:09:33 +02:00
Gunnar Skjold
f593e14e68 Combined import/export in data.json 2024-06-06 17:34:33 +02:00
Gunnar Skjold
da6328c246 Fixed HA hostname ESP8266 build issue 2024-06-06 17:17:59 +02:00
Gunnar Skjold
5eeab2ba89 Fixed amperage plot 2024-06-06 15:55:43 +02:00
Gunnar Skjold
eeaaf088ac Fixed issues with HA sensors 2024-06-06 15:41:21 +02:00
Gunnar Skjold
32afea2817 Iskra 2024-06-04 20:33:03 +02:00
Gunnar Skjold
e7b496280d KMP stuff 2024-06-03 18:09:40 +02:00
Gunnar Skjold
d0621e98cd Fixed blank webpage 2024-05-30 14:07:45 +02:00
Gunnar Skjold
202d57843c Iskra case fix? 2024-05-30 13:28:50 +02:00
Gunnar Skjold
770d662f2c Changes to allow interaction with cloud service 2024-05-30 09:46:45 +02:00
Gunnar Skjold
da1d5993f4 Merge pull request #796 from devdems/main
Add Slovenia
2024-05-23 23:34:06 +02:00
Gunnar Skjold
7ad97daf86 Some cleanup 2024-05-23 22:20:01 +02:00
Gunnar Skjold
3b155d78b1 Removed som debugging 2024-05-23 21:58:17 +02:00
Gunnar Skjold
37a1e3b93e Another iskra special case 2024-05-23 20:57:32 +02:00
Gunnar Skjold
846cf85331 Use export price for export calculations 2024-05-23 20:33:13 +02:00
Gunnar Skjold
a1a6953521 Use export price for export cost calculations 2024-05-23 20:21:56 +02:00
Gunnar Skjold
a137316cec Added voltage check for all Pow* devices 2024-05-23 19:40:28 +02:00
Gunnar Skjold
faa468b287 Fixed loading order of json and when to display price graph 2024-05-23 19:12:38 +02:00
Gunnar Skjold
703c68a2cf Fixed autodetect 2024-05-23 18:58:20 +02:00
Gunnar Skjold
b6168a0082 Fixed 404 2024-05-23 17:35:17 +02:00
Gunnar Skjold
38ec99f2ee Move price elements 2024-05-23 17:34:26 +02:00
Gunnar Skjold
22f8349f2f Fixed bugs after testing 2024-05-23 17:13:32 +02:00
Gunnar Skjold
d98a021f39 Edit day and month data 2024-05-23 15:17:19 +02:00
Gunnar Skjold
77d899fe0a Fixed month in clock 2024-05-23 14:06:42 +02:00
Gunnar Skjold
c03bfcbcd7 Auto change buffer size based on baud rate when autodetect 2024-05-23 14:03:16 +02:00
Gunnar Skjold
f3732674b0 Fixed autodetect and enabled for Pow-K (To handle when configured as P1) 2024-05-23 13:59:16 +02:00
devdems
5926ddfaf9 Update ConfigurationPanel.svelte
Add Slovenia
2024-05-21 16:07:20 +02:00
Gunnar Skjold
99e341e582 Changed buffer size defaults and some null terminator issues on json mqtt 2024-05-01 07:38:30 +02:00
Gunnar Skjold
05506cdc9f Fixed ESP8266 build issue 2024-04-21 10:35:30 +02:00
Gunnar Skjold
3f1861deda MQTT on interval 2024-04-21 10:21:39 +02:00
Gunnar Skjold
efacbd4b31 Code restructuring 2024-04-21 08:43:59 +02:00
Gunnar Skjold
beaaa191e8 Color fix 2024-04-20 13:08:39 +02:00
Gunnar Skjold
43d49f21a3 Changed GPIO dropdowns 2024-04-20 10:51:27 +02:00
Gunnar Skjold
95c495b773 Some changes for cloud service 2024-04-20 09:13:41 +02:00
Gunnar Skjold
68c680debf Set TX pin low if passive communicator 2024-04-19 14:51:27 +02:00
Gunnar Skjold
fec6cc7612 Updated HA state class on realtime sensors 2024-04-19 08:48:57 +02:00
Gunnar Skjold
dc3cea80b6 Fixed HA state class on realtime sensors 2024-04-15 19:54:40 +02:00
Gunnar Skjold
ce67ef2bea Show current export price in export gauge 2024-04-14 14:47:53 +02:00
Gunnar Skjold
3a980fac4c Fixed price config 2024-04-14 14:20:46 +02:00
Gunnar Skjold
77873f4a38 Fixed mixup of last month cost and kwh 2024-04-14 13:33:11 +02:00
Gunnar Skjold
d9d384ea02 Fixed code 2024-04-14 13:30:46 +02:00
Gunnar Skjold
16fb1ea87c Fixed context bug 2024-04-14 13:24:20 +02:00
Gunnar Skjold
c74e719327 Fixed ESP8266 build issue 2024-04-13 11:38:03 +02:00
Gunnar Skjold
8c8e14f60c Some changes for cloud 2024-04-13 11:29:56 +02:00
Gunnar Skjold
3b93897a8e Added new headers to upgrade request 2024-04-13 10:19:08 +02:00
Gunnar Skjold
e080c7d535 Fixed static IP 2024-04-11 22:08:58 +02:00
Gunnar Skjold
64ea8c4888 Fixed static IP 2024-04-10 20:44:05 +02:00
Gunnar Skjold
e7ae24b26f Fixed accidental clearing more than just GPIO 2024-04-10 20:36:43 +02:00
Gunnar Skjold
84ff999c4c Fixed missing config tile 2024-04-09 21:57:44 +02:00
Gunnar Skjold
71e7e779da Some adjustments to GUI 2024-04-09 20:52:57 +02:00
Gunnar Skjold
99904f9097 Fixed stuck value 2024-04-09 18:12:23 +02:00
Gunnar Skjold
89015191de Improved MQTT stability and changed some defaults 2024-04-09 17:16:12 +02:00
Gunnar Skjold
a7d3382947 Fixed missing L2 2024-04-08 14:59:53 +02:00
Gunnar Skjold
abef32c73c Fixed some defaults 2024-04-07 12:01:05 +02:00
Gunnar Skjold
b52c580f6f Merge branch 'main' of github.com:UtilitechAS/amsreader-firmware 2024-04-07 11:50:02 +02:00
Gunnar Skjold
429f0ba699 Merge pull request #761 from avandorp/avandorp-ha-uptime-device-class
Use correct home assistant device class for uptime
2024-04-07 11:45:02 +02:00
Gunnar Skjold
d918a593ce Removed connect timeout from MQTT 2024-04-07 11:31:21 +02:00
Arthur van Dorp
fb410ecfef Use correct home assistant device class for uptime 2024-04-06 21:43:06 +02:00
153 changed files with 10801 additions and 4363 deletions

View File

@@ -0,0 +1,31 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu
{
"name": "amsreader-devcontainer",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/base:jammy",
"features": {
"ghcr.io/devcontainers/features/node:1": {
"version": "19",
"pnpmVersion": "none",
"nvmVersion": "latest"
},
"ghcr.io/devcontainers/features/python:1": {
"version": "3.9"
}
},
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": ".devcontainer/postCreateCommand.sh",
// Configure tool-specific properties.
"customizations": {
"vscode": {
"extensions": [
"platformio.platformio-ide",
"ms-vscode.cpptools",
"svelte.svelte-vscode"
]
}
}
}

View File

@@ -0,0 +1,18 @@
#!/bin/bash
set -e
# Upgrade pip
python -m pip install --upgrade pip
# Install Python packages
pip install -U platformio css_html_js_minify
# Navigate to the Svelte app directory
cd lib/SvelteUi/app
# Install npm dependencies and build the app
npm ci
npm run build
# Return to the previous directory
cd -

3
.gitattributes vendored Normal file
View File

@@ -0,0 +1,3 @@
* text=auto eol=lf
*.{cmd,[cC][mM][dD]} text eol=crlf
*.{bat,[bB][aA][tT]} text eol=crlf

View File

@@ -30,12 +30,12 @@ jobs:
sed -i 's/NO_ENERGY_SPEEDOMETER_USER/ENERGY_SPEEDOMETER_USER=\\"${{secrets.ENERGY_SPEEDOMETER_USER}}\\"/g' platformio.ini
sed -i 's/NO_ENERGY_SPEEDOMETER_PASS/ENERGY_SPEEDOMETER_PASS=\\"${{secrets.ENERGY_SPEEDOMETER_PASS}}\\"/g' platformio.ini
- name: Cache Python dependencies
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('platformio.ini') }}
- name: Cache PlatformIO dependencies
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ~/.pio/libdeps
key: ${{ runner.os }}-pio-${{ hashFiles('platformio.ini') }}
@@ -50,7 +50,7 @@ jobs:
- name: Set up node
uses: actions/setup-node@v4
with:
node-version: '16.x'
node-version: '19.x'
- name: Build with node
run: |
cd lib/SvelteUi/app

41
.github/workflows/localazy.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
name: Deploy language files from localazy
on:
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Check out code from repo
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '22.x'
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: eu-north-1
- name: Generate localazy-keys.json
run: |
echo '{"writeKey": "", "readKey": "${{secrets.LOCALAZY_READ_KEY}}"}' > localazy/localazy-keys.json
- name: Create localazy language folder
run: mkdir -p localazy/language
- name: Install Localazy CLI
run: npm install -g @localazy/cli
- name: Download translations
working-directory: localazy
run: localazy download -k localazy-keys.json
- name: Upload translations to S3
run: aws s3 sync ./localazy/language/ s3://amscloud-private/language/

View File

@@ -13,7 +13,7 @@ jobs:
steps:
- name: Check out code from repo
uses: actions/checkout@v1
uses: actions/checkout@v4
- name: Get release version for filenames
id: release_tag
env:
@@ -33,17 +33,17 @@ jobs:
sed -i 's/NO_ENERGY_SPEEDOMETER_PASS/ENERGY_SPEEDOMETER_PASS=\\"${{secrets.ENERGY_SPEEDOMETER_PASS}}\\"/g' platformio.ini
- name: Cache Python dependencies
uses: actions/cache@v1
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('platformio.ini') }}
- name: Cache PlatformIO dependencies
uses: actions/cache@v1
uses: actions/cache@v4
with:
path: ~/.pio/libdeps
key: ${{ runner.os }}-pio-${{ hashFiles('platformio.ini') }}
- name: Set up Python 3.9
uses: actions/setup-python@v1
uses: actions/setup-python@v5
with:
python-version: 3.9
- name: Install dependencies
@@ -52,9 +52,9 @@ jobs:
pip install -U platformio css_html_js_minify
- name: Set up node
uses: actions/setup-node@v1
uses: actions/setup-node@v4
with:
node-version: '16.x'
node-version: '19.x'
- name: Build with node
run: |
cd lib/SvelteUi/app
@@ -67,16 +67,12 @@ jobs:
- name: PlatformIO lib install
run: pio lib install
- name: Create Release
- name: Create release with release notes
id: create_release
uses: actions/create-release@v1.0.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
uses: ncipollo/release-action@v1
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
draft: false
prerelease: false
name: Release v${{ steps.release_tag.outputs.tag }}
generateReleaseNotes: true
- name: Build esp8266 firmware
run: pio run -e esp8266

View File

@@ -1,63 +0,0 @@
name: Test ESP8266
on:
workflow_dispatch:
jobs:
esp8266:
runs-on: esp8266
steps:
- uses: actions/checkout@v4
- name: Get commit hash
id: vars
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Check outputs
run: echo ${{ steps.vars.outputs.sha_short }}
- name: Inject secrets into ini file
run: |
sed -i 's/NO_AMS2MQTT_PRICE_KEY/AMS2MQTT_PRICE_KEY="${{secrets.AMS2MQTT_PRICE_KEY}}"/g' platformio.ini
sed -i 's/NO_AMS2MQTT_PRICE_AUTHENTICATION/AMS2MQTT_PRICE_AUTHENTICATION="${{secrets.AMS2MQTT_PRICE_AUTHENTICATION}}"/g' platformio.ini
sed -i 's/NO_ENERGY_SPEEDOMETER_USER/ENERGY_SPEEDOMETER_USER=\\"${{secrets.ENERGY_SPEEDOMETER_USER}}\\"/g' platformio.ini
sed -i 's/NO_ENERGY_SPEEDOMETER_PASS/ENERGY_SPEEDOMETER_PASS=\\"${{secrets.ENERGY_SPEEDOMETER_PASS}}\\"/g' platformio.ini
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -U platformio css_html_js_minify
- name: Set up node
uses: actions/setup-node@v4
with:
node-version: '16.x'
- name: Configure PlatformIO environment
run: |
echo "[platformio]
default_envs = dev8266
[env:dev8266]
platform = espressif8266@4.2.0
framework = arduino
board = esp12e
board_build.ldscript = eagle.flash.4m2m.ld
build_flags = \${common.build_flags}
lib_ldf_mode = off
lib_compat_mode = off
lib_deps = ESP8266WiFi, ESP8266mDNS, ESP8266WebServer, ESP8266HTTPClient, ESP8266httpUpdate, ESP8266SSDP, \${common.lib_deps}
lib_ignore = \${common.lib_ignore}
extra_scripts = \${common.extra_scripts}" > platformio-user.ini
- name: Build with node
run: |
cd lib/SvelteUi/app
npm ci
npm run build
cd -
env:
CI: true
- name: PlatformIO lib install
run: pio pkg update
- name: PlatformIO run
run: pio run -t upload --upload-port /dev/ttyUSB0
- name: Wait for device to come online
run: waitforhost 10.42.0.11 80
- name: Confirm version
run: curl -s http://10.42.0.11/sysinfo.json|jq -r .version | grep "${{ steps.vars.outputs.sha_short }}" || exit 1
- name: Running amsreader-test
run: amsreader-test 10.42.0.11

5
.gitignore vendored
View File

@@ -1,4 +1,5 @@
.vs/
.idea/
[Dd]ebug/
[Rr]elease/
**/__vm/
@@ -18,5 +19,5 @@ platformio-user.ini
node_modules
/gui/dist
/scripts/*dev
/src/KmpCommunicator.cpp
/src/KmpCommunicatorDefs.h
localazy-keys.json
localazy/language

7
custom_partition.csv Normal file
View File

@@ -0,0 +1,7 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x1D0000,
app1, app, ota_1, 0x1E0000,0x1D0000,
spiffs, data, spiffs, 0x3B0000,0x40000,
coredump, data, coredump,0x3F0000,0x10000,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x5000
3 otadata data ota 0xe000 0x2000
4 app0 app ota_0 0x10000 0x1D0000
5 app1 app ota_1 0x1E0000 0x1D0000
6 spiffs data spiffs 0x3B0000 0x40000
7 coredump data coredump 0x3F0000 0x10000

Binary file not shown.

View File

@@ -0,0 +1 @@

View File

@@ -31,3 +31,59 @@ Object with three values Value Object with two values
02 03 09 06 01 00 04 08 00 FF 06 00 0A F5 EC 02 02 0F 01 16 20
51 D7
7E
Received 21.09.24 20:00:10:
(V) 7E A1 8A 41 08 83 13 EB FD E6 E7 00 0F 40 00 00
(V) 00 00 01 12 02 02 09 06 01 01 00 02 81 FF 0A 0B
(V) 41 49 44 4F 4E 5F 56 30 30 30 31 02 02 09 06 00
(V) 00 60 01 00 FF 0A 10 37 33 35 39 39 39 32 38 39
(V) 30 34 39 37 39 39 37 02 02 09 06 00 00 60 01 07
(V) FF 0A 04 36 35 33 34 02 03 09 06 01 00 01 07 00
(V) FF 06 00 00 02 71 02 02 0F 00 16 1B 02 03 09 06
(V) 01 00 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16
(V) 1B 02 03 09 06 01 00 03 07 00 FF 06 00 00 00 00
(V) 02 02 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF
(V) 06 00 00 01 F0 02 02 0F 00 16 1D 02 03 09 06 01
(V) 00 1F 07 00 FF 10 00 06 02 02 0F FF 16 21 02 03
(V) 09 06 01 00 33 07 00 FF 10 00 05 02 02 0F FF 16
(V) 21 02 03 09 06 01 00 47 07 00 FF 10 00 17 02 02
(V) 0F FF 16 21 02 03 09 06 01 00 20 07 00 FF 12 09
(V) 3F 02 02 0F FF 16 23 02 03 09 06 01 00 34 07 00
(V) FF 12 09 36 02 02 0F FF 16 23 02 03 09 06 01 00
(V) 48 07 00 FF 12 09 22 02 02 0F FF 16 23 02 02 09
(V) 06 00 00 01 00 00 FF 09 0C 07 E8 09 15 06 13 00
(V) 00 FF 00 00 00 02 03 09 06 01 00 01 08 00 FF 06
(V) 00 8D 98 13 02 02 0F 01 16 1E 02 03 09 06 01 00
(V) 02 08 00 FF 06 00 00 00 00 02 02 0F 01 16 1E 02
(V) 03 09 06 01 00 03 08 00 FF 06 00 00 29 6E 02 02
(V) 0F 01 16 20 02 03 09 06 01 00 04 08 00 FF 06 00
(V) 13 F2 06 02 02 0F 01 16 20 C8 1D 7E
Received 27.10.24 11:00:13:
(V) 7E A1 8A 41 08 83 13 EB FD E6 E7 00 0F 40 00 00
(V) 00 00 01 12 02 02 09 06 01 01 00 02 81 FF 0A 0B
(V) 41 49 44 4F 4E 5F 56 30 30 30 31 02 02 09 06 00
(V) 00 60 01 00 FF 0A 10 37 33 35 39 39 39 32 38 39
(V) 30 34 39 37 39 39 37 02 02 09 06 00 00 60 01 07
(V) FF 0A 04 36 35 33 34 02 03 09 06 01 00 01 07 00
(V) FF 06 00 00 02 AA 02 02 0F 00 16 1B 02 03 09 06
(V) 01 00 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16
(V) 1B 02 03 09 06 01 00 03 07 00 FF 06 00 00 00 00
(V) 02 02 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF
(V) 06 00 00 01 CC 02 02 0F 00 16 1D 02 03 09 06 01
(V) 00 1F 07 00 FF 10 00 02 02 02 0F FF 16 21 02 03
(V) 09 06 01 00 33 07 00 FF 10 00 0B 02 02 0F FF 16
(V) 21 02 03 09 06 01 00 47 07 00 FF 10 00 16 02 02
(V) 0F FF 16 21 02 03 09 06 01 00 20 07 00 FF 12 09
(V) 2F 02 02 0F FF 16 23 02 03 09 06 01 00 34 07 00
(V) FF 12 09 00 02 02 0F FF 16 23 02 03 09 06 01 00
(V) 48 07 00 FF 12 09 0F 02 02 0F FF 16 23 02 02 09
(V) 06 00 00 01 00 00 FF 09 0C 07 E8 0A 1B 00 0B 00
(V) 00 FF 00 00 00 02 03 09 06 01 00 01 08 00 FF 06
(V) 00 8F 25 6F 02 02 0F 01 16 1E 02 03 09 06 01 00
(V) 02 08 00 FF 06 00 00 00 00 02 02 0F 01 16 1E 02
(V) 03 09 06 01 00 03 08 00 FF 06 00 00 29 8D 02 02
(V) 0F 01 16 20 02 03 09 06 01 00 04 08 00 FF 06 00
(V) 14 44 26 02 02 0F 01 16 20 84 09 7E

14
frames/EGM5G35.txt Normal file
View File

@@ -0,0 +1,14 @@
2F 45 47 4D 35 47 33 35 // DSMR Identifier
0D 0A // CRLF
0D 0A // CRLF
00 // System title, blank
82 // Two-byte length follows
02 30 // 560 bytes
30 // Security tag 0011 0000, 0=Compression off, 0=Unicast, 1=Encryption, 1=Authentication, 0000= Security Suite ID
00 00 00 00 // Frame counter, blank
// Encrypted payload follows (560 - security tag - frame counter = 555 bytes)
30 10 73 D6 59 85 96 B0 2A 8B CC E7 33 AD 19 A3 A5 4A 4D 0E 3A CE F6 33 DB 57 0A 45 9A C3 5B F0 D4 C0 AA 15 B9 93 B0 E8 67 85 EC 22 CA 40 8C D7 1A B5 62 AE DF 0F 48 EA D9 E6 56 FB 84 B3 7B 47 C6 29 12 F1 EE BB 22 88 26 75 5C F8 A2 20 F4 93 F3 64 CA 7C C6 60 32 62 B0 7F F9 7F 71 0A 9F DF AB 61 89 6A 9A 10 B1 DF 94 2C 74 8F 51 B6 09 5C F2 45 6C 38 54 52 FE B6 CB 1F 42 F2 93 DC 57 BE EE 5A 0D F8 1F 53 55 0E 21 EF 46 0A 72 74 CA AB D8 9C A9 05 9E FD 54 5E CC 7B 71 40 E1 22 B2 25 5C D5 63 80 BD 97 6E 54 4B 24 A6 58 1F 79 1F 45 C7 DF C0 83 3B 7F 1E C7 43 B9 26 F6 EB 6E F2 B1 7C 99 3A 91 EA 39 72 E5 FB 72 B1 E1 24 AF 9D 19 8A 6C 50 69 97 90 D3 3D 68 97 57 C2 EC 49 EF 12 1D 11 44 46 70 46 88 88 98 37 6F B0 93 FC D2 3F 37 66 1C C7 F0 93 E3 AF 4D 3C BE 41 8E B9 48 77 A9 92 5B 42 0A 96 E5 34 4D 30 56 90 5A 08 03 CC 41 78 58 79 FD 89 82 E0 46 3D 66 AA 42 85 F4 A8 46 2D 4A E4 81 12 40 68 D6 F5 F9 11 5A 94 51 21 3C 9F 4F F7 FE B3 B5 BF 1E D2 12 CF B9 FB 28 D0 B4 82 9E 11 D8 1C FC E3 36 EC DE 8A 7C 83 76 F7 EB 3D 2C 4A B7 7F 75 91 F1 F5 18 D4 70 6A C9 3E 3D 3F 9E 0F C6 0F A7 E7 20 11 03 E7 75 4E 40 F1 5E BA 86 30 6D C0 28 C4 14 47 DF 6A EF CC FE E4 B6 23 58 2B 9D 4F BF 6A A8 93 84 F4 CD 4D FF 47 B5 C4 81 D2 DA D3 6B C3 F4 1D C4 6E E1 9B 24 30 91 7B 33 57 67 B6 E4 93 FE 8B 5B AE 8A 0D 81 DE A5 C3 3F 82 9A CE E2 BA 6D 50 39 FC 99 E6 7A 7B B4 E9 A9 84 78 AF 2D 4C D2 15 53 6B 98 2B 1F C8 3D 57 9E 49 68 40 0C D3 4A 9E D6 4F 1F 12 50 84 32 AD 62 C6 D3 42 34 46 66 65 56 49 81 F4 BC 8E C6 75 77 11 1F 29 BB 80 20 23 0A 80 D5 31 50 16 89 6F 07 4A 25 BE F0 4B 60 36 87 AE A6 2A 53 79 22 15 A5 2A C7 1D 15 7B 87 DD B5 07 3D 1C 28 C1 B6 51 47 2F 39 DE D9 B9 29 B6 78
21 // End of DSMR (!)
46 45 30 43 // FE0C - Checksum
0D 0A // CRLF

View File

@@ -31,4 +31,183 @@ C9 95 7E // CRC and end tag
06 00 00 00 00 06 00 00 00 00 06 00 00 01 67 06 00 00 03 BF 06 00 00 05 05
06 00 00 24 34 06 00 00 09 45 06 00 00 09 4F 06 00 00 09 3B
09 0C 07 E5 03 17 02 13 00 0A FF 80 00 00 06 01 34 3B 5D 06 00 00 00 00 06 00 00 09 36 06 00 3C 7A 98 DA 15 7E
7E A0 79 01 02 01 10 80 93 E6 E7 00 0F 40 00 00 00 09 0C 07 E1 09 0E 04 15 1F 14 FF 80 00 00 02 0D 09 07 4B 46 4D 5F 30 30 31 09 10 36 39 37 30 36 33 31 34 30 31 37 35 33 39 38 35 09 08 4D 41 33 30 34 48 33 45 06 00 00 04 0C 06 00 00 00 00 06 00 00 00 00 06 00 00 00 4E 06 00 00 07 C1 06 00 00 0C 9E 06 00 00 0D 7E 06 00 00 09 5F 06 00 00 00 00 06 00 00 09 66 87 96 7E
7E A0 79 01 02 01 10 80 93 E6 E7 00 0F 40 00 00 00 09 0C 07 E1 09 0E 04 15 1F 14 FF 80 00 00 02 0D 09 07 4B 46 4D 5F 30 30 31 09 10 36 39 37 30 36 33 31 34 30 31 37 35 33 39 38 35 09 08 4D 41 33 30 34 48 33 45 06 00 00 04 0C 06 00 00 00 00 06 00 00 00 00 06 00 00 00 4E 06 00 00 07 C1 06 00 00 0C 9E 06 00 00 0D 7E 06 00 00 09 5F 06 00 00 00 00 06 00 00 09 66 87 96 7E
01.10.24 19:00 ish:
(D) READY to update (internal clock 16:59:58 UTC, meter clock: 00:00:00, list type 1, est: 1, using clock: 1)
(V) HDLC frame:
(V) 7E A0 78 01 02 01 10 C4 98 E6 E7 00 0F 40 00 00
(V) 00 09 0C 07 E8 0A 01 02 13 00 00 FF 80 00 00 02
(V) 0D 09 07 4B 46 4D 5F 30 30 31 09 10 36 39 37 30
(V) 36 33 31 34 30 37 32 36 32 39 38 36 09 07 4D 41
(V) 33 30 34 48 34 06 00 00 13 C0 06 00 00 00 00 06
(V) 00 00 00 00 06 00 00 01 D5 06 00 00 4C DF 06 00
(V) 00 05 0C 06 00 00 03 9B 06 00 00 09 34 06 00 00
(V) 09 69 06 00 00 09 59 18 EB 7E
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
(V) 00 00 FF 80 00 00 02 0D 09 07 4B 46 4D 5F 30 30
(V) 31 09 10 36 39 37 30 36 33 31 34 30 37 32 36 32
(V) 39 38 36 09 07 4D 41 33 30 34 48 34 06 00 00 13
(V) C0 06 00 00 00 00 06 00 00 00 00 06 00 00 01 D5
(V) 06 00 00 4C DF 06 00 00 05 0C 06 00 00 03 9B 06
(V) 00 00 09 34 06 00 00 09 69 06 00 00 09 59
(V) DLMS frame:
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 00 FF
(V) 80 00 00 02 0D 09 07 4B 46 4D 5F 30 30 31 09 10
(V) 36 39 37 30 36 33 31 34 30 37 32 36 32 39 38 36
(V) 09 07 4D 41 33 30 34 48 34 06 00 00 13 C0 06 00
(V) 00 00 00 06 00 00 00 00 06 00 00 01 D5 06 00 00
(V) 4C DF 06 00 00 05 0C 06 00 00 03 9B 06 00 00 09
(V) 34 06 00 00 09 69 06 00 00 09 59
(D) Received valid DLMS at 31
(V) Using application data:
(V) 02 0D 09 07 4B 46 4D 5F 30 30 31 09 10 36 39 37
(V) 30 36 33 31 34 30 37 32 36 32 39 38 36 09 07 4D
(V) 41 33 30 34 48 34 06 00 00 13 C0 06 00 00 00 00
(V) 06 00 00 00 00 06 00 00 01 D5 06 00 00 4C DF 06
(V) 00 00 05 0C 06 00 00 03 9B 06 00 00 09 34 06 00
(V) 00 09 69 06 00 00 09 59
(V) DLMS
(D) READY to update (internal clock 17:00:01 UTC, meter clock: 00:00:00, list type 2, est: 1, using clock: 1)
(V) HDLC frame:
(V) 7E A0 27 01 02 01 10 5A 87 E6 E7 00 0F 40 00 00
(V) 00 09 0C 07 E8 0A 01 02 13 00 02 FF 80 00 00 02
(V) 01 06 00 00 13 C1 56 F5 7E
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
(V) 00 02 FF 80 00 00 02 01 06 00 00 13 C1
(V) DLMS frame:
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 02 FF
(V) 80 00 00 02 01 06 00 00 13 C1
(D) Received valid DLMS at 31
(V) Using application data:
(V) 02 01 06 00 00 13 C1
(V) DLMS
(D) READY to update (internal clock 17:00:02 UTC, meter clock: 00:00:00, list type 1, est: 1, using clock: 1)
(V) HDLC frame:
(V) 7E A0 27 01 02 01 10 5A 87 E6 E7 00 0F 40 00 00
(V) 00 09 0C 07 E8 0A 01 02 13 00 04 FF 80 00 00 02
(V) 01 06 00 00 13 BC F9 5A 7E
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
(V) 00 04 FF 80 00 00 02 01 06 00 00 13 BC
(V) DLMS frame:
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 04 FF
(V) 80 00 00 02 01 06 00 00 13 BC
(D) Received valid DLMS at 31
(V) Using application data:
(V) 02 01 06 00 00 13 BC
(V) DLMS
(D) READY to update (internal clock 17:00:04 UTC, meter clock: 00:00:00, list type 1, est: 1, using clock: 1)
(V) HDLC frame:
(V) 7E A0 27 01 02 01 10 5A 87 E6 E7 00 0F 40 00 00
(V) 00 09 0C 07 E8 0A 01 02 13 00 06 FF 80 00 00 02
(V) 01 06 00 00 13 BC 42 58 7E
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
(V) 00 06 FF 80 00 00 02 01 06 00 00 13 BC
(V) DLMS frame:
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 06 FF
(V) 80 00 00 02 01 06 00 00 13 BC
(D) Received valid DLMS at 31
(V) Using application data:
(V) 02 01 06 00 00 13 BC
(V) DLMS
(D) READY to update (internal clock 17:00:06 UTC, meter clock: 00:00:00, list type 1, est: 1, using clock: 1)
(V) HDLC frame:
(V) 7E A0 27 01 02 01 10 5A 87 E6 E7 00 0F 40 00 00
(V) 00 09 0C 07 E8 0A 01 02 13 00 08 FF 80 00 00 02
(V) 01 06 00 00 13 BB DC 21 7E
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
(V) 00 08 FF 80 00 00 02 01 06 00 00 13 BB
(V) DLMS frame:
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 08 FF
(V) 80 00 00 02 01 06 00 00 13 BB
(D) Received valid DLMS at 31
(V) Using application data:
(V) 02 01 06 00 00 13 BB
(V) DLMS
(D) READY to update (internal clock 17:00:08 UTC, meter clock: 00:00:00, list type 1, est: 1, using clock: 1)
(V) HDLC frame:
(V) 7E A0 9A 01 02 01 10 AA A5 E6 E7 00 0F 40 00 00
(V) 00 09 0C 07 E8 0A 01 02 13 00 0A FF 80 00 00 02
(V) 12 09 07 4B 46 4D 5F 30 30 31 09 10 36 39 37 30
(V) 36 33 31 34 30 37 32 36 32 39 38 36 09 07 4D 41
(V) 33 30 34 48 34 06 00 00 13 C0 06 00 00 00 00 06
(V) 00 00 00 00 06 00 00 01 D5 06 00 00 4C DD 06 00
(V) 00 05 0E 06 00 00 03 90 06 00 00 09 34 06 00 00
(V) 09 69 06 00 00 09 59 09 0C 07 E8 0A 01 02 13 00
(V) 0A FF 80 00 00 06 02 8E 4B 5E 06 00 00 00 00 06
(V) 00 97 35 DE 06 00 08 97 2F FA E5 7E
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
(V) 00 0A FF 80 00 00 02 12 09 07 4B 46 4D 5F 30 30
(V) 31 09 10 36 39 37 30 36 33 31 34 30 37 32 36 32
(V) 39 38 36 09 07 4D 41 33 30 34 48 34 06 00 00 13
(V) C0 06 00 00 00 00 06 00 00 00 00 06 00 00 01 D5
(V) 06 00 00 4C DD 06 00 00 05 0E 06 00 00 03 90 06
(V) 00 00 09 34 06 00 00 09 69 06 00 00 09 59 09 0C
(V) 07 E8 0A 01 02 13 00 0A FF 80 00 00 06 02 8E 4B
(V) 5E 06 00 00 00 00 06 00 97 35 DE 06 00 08 97 2F
(V)
(V) DLMS frame:
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 0A FF
(V) 80 00 00 02 12 09 07 4B 46 4D 5F 30 30 31 09 10
(V) 36 39 37 30 36 33 31 34 30 37 32 36 32 39 38 36
(V) 09 07 4D 41 33 30 34 48 34 06 00 00 13 C0 06 00
(V) 00 00 00 06 00 00 00 00 06 00 00 01 D5 06 00 00
(V) 4C DD 06 00 00 05 0E 06 00 00 03 90 06 00 00 09
(V) 34 06 00 00 09 69 06 00 00 09 59 09 0C 07 E8 0A
(V) 01 02 13 00 0A FF 80 00 00 06 02 8E 4B 5E 06 00
(V) 00 00 00 06 00 97 35 DE 06 00 08 97 2F
(D) Received valid DLMS at 31
(V) Using application data:
(V) 02 12 09 07 4B 46 4D 5F 30 30 31 09 10 36 39 37
(V) 30 36 33 31 34 30 37 32 36 32 39 38 36 09 07 4D
(V) 41 33 30 34 48 34 06 00 00 13 C0 06 00 00 00 00
(V) 06 00 00 00 00 06 00 00 01 D5 06 00 00 4C DD 06
(V) 00 00 05 0E 06 00 00 03 90 06 00 00 09 34 06 00
(V) 00 09 69 06 00 00 09 59 09 0C 07 E8 0A 01 02 13
(V) 00 0A FF 80 00 00 06 02 8E 4B 5E 06 00 00 00 00
(V) 06 00 97 35 DE 06 00 08 97 2F
(V) DLMS
(D) READY to update (internal clock 17:00:12 UTC, meter clock: 17:00:10, list type 3, est: 1, using clock: 0)
(D) Updating data storage using actual data
(D) Clearing hours from 16 to 17
(D) Clearing days from 1 to 1
(D) Day is not happy
(D) - normal
(I) Saving data
(I) Saving energy accounting
(W) Used 1249ms to read HAN port (true)
(V) HDLC frame:
(V) 7E A0 27 01 02 01 10 5A 87 E6 E7 00 0F 40 00 00
(V) 00 09 0C 07 E8 0A 01 02 13 00 0C FF 80 00 00 02
(V) 01 06 00 00 13 BC 15 50 7E
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
(V) 00 0C FF 80 00 00 02 01 06 00 00 13 BC
(V) DLMS frame:
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 0C FF
(V) 80 00 00 02 01 06 00 00 13 BC
(D) Received valid DLMS at 31
(V) Using application data:
(V) 02 01 06 00 00 13 BC
(V) DLMS
(D) NOT Ready to update (internal clock 17:00:12 UTC, meter clock: 00:00:00, list type 1, est: 0)
(V) HDLC frame:
(V) 7E A0 27 01 02 01 10 5A 87 E6 E7 00 0F 40 00 00
(V) 00 09 0C 07 E8 0A 01 02 13 00 0E FF 80 00 00 02
(V) 01 06 00 00 13 B7 7D EC 7E
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
(V) 00 0E FF 80 00 00 02 01 06 00 00 13 B7
(V) DLMS frame:
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 0E FF
(V) 80 00 00 02 01 06 00 00 13 B7
(D) Received valid DLMS at 31
(V) Using application data:
(V) 02 01 06 00 00 13 B7

File diff suppressed because it is too large Load Diff

682
frames/L&G-E350_Norway.raw Normal file
View File

@@ -0,0 +1,682 @@
*** Remote debug - over telnet - for ESP32 - version 3.0.5
* Host name: ams-e603 IP:10.10.10.62 Mac address:D8:3B:DA:C4:03:E6
* Free Heap RAM: 87952
* ESP SDK version: 4.4.5.230722
******************************************************
* Commands:
? or help -> display these help of commands
q -> quit (close this connection)
m -> display memory available
v -> set debug level to verbose
d -> set debug level to debug
i -> set debug level to info
w -> set debug level to warning
e -> set debug level to errors
s -> set debug silence on/off
l -> show debug level
t -> show time (millis)
profiler:
p -> show time between actual and last message (in millis)
p min -> show only if time is this minimal
P time -> set debug level to profiler
c -> show colors
filter:
filter <string> -> show only debugs with this
nofilter -> disable the filter
* Please type the command and press enter to execute.(? or h for this help)
***
(V) HDLC frame:
(V) 7E A1 1E 41 08 83 13 EE EE E6 E7 00 0F 40 00 00
(V) 00 00 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B
(V) 45 4D 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00
(V) 00 60 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00
(V) 00 00 00 00 00 00 00 02 02 09 06 00 00 60 01 07
(V) FF 0A 04 5A 46 46 31 02 03 09 06 01 00 01 07 00
(V) FF 06 00 00 24 F4 02 02 0F 00 16 1B 02 03 09 06
(V) 01 00 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16
(V) 1B 02 03 09 06 01 00 03 07 00 FF 06 00 00 00 31
(V) 02 02 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF
(V) 06 00 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01
(V) 00 1F 07 00 FF 10 0C 6C 02 02 0F FE 16 21 02 03
(V) 09 06 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16
(V) 21 02 03 09 06 01 00 47 07 00 FF 10 0D 68 02 02
(V) 0F FE 16 21 02 03 09 06 01 00 20 07 00 FF 12 09
(V) 2E 02 02 0F FF 16 23 02 03 09 06 01 00 34 07 00
(V) FF 12 00 00 02 02 0F FF 16 23 02 03 09 06 01 00
(V) 48 07 00 FF 12 09 10 02 02 0F FF 16 23 2F AF 7E
(V)
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 00 01 0D 02 02 09 06 01
(V) 01 00 02 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31
(V) 5F 31 02 02 09 06 00 00 60 01 00 FF 0A 10 31 33
(V) 39 34 33 35 39 37 00 00 00 00 00 00 00 00 02 02
(V) 09 06 00 00 60 01 07 FF 0A 04 5A 46 46 31 02 03
(V) 09 06 01 00 01 07 00 FF 06 00 00 24 F4 02 02 0F
(V) 00 16 1B 02 03 09 06 01 00 02 07 00 FF 06 00 00
(V) 00 00 02 02 0F 00 16 1B 02 03 09 06 01 00 03 07
(V) 00 FF 06 00 00 00 31 02 02 0F 00 16 1D 02 03 09
(V) 06 01 00 04 07 00 FF 06 00 00 00 00 02 02 0F 00
(V) 16 1D 02 03 09 06 01 00 1F 07 00 FF 10 0C 6C 02
(V) 02 0F FE 16 21 02 03 09 06 01 00 33 07 00 FF 10
(V) 00 00 02 02 0F FE 16 21 02 03 09 06 01 00 47 07
(V) 00 FF 10 0D 68 02 02 0F FE 16 21 02 03 09 06 01
(V) 00 20 07 00 FF 12 09 2E 02 02 0F FF 16 23 02 03
(V) 09 06 01 00 34 07 00 FF 12 00 00 02 02 0F FF 16
(V) 23 02 03 09 06 01 00 48 07 00 FF 12 09 10 02 02
(V) 0F FF 16 23
(V) DLMS frame:
(V) 0F 40 00 00 00 00 01 0D 02 02 09 06 01 01 00 02
(V) 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31 5F 31 02
(V) 02 09 06 00 00 60 01 00 FF 0A 10 31 33 39 34 33
(V) 35 39 37 00 00 00 00 00 00 00 00 02 02 09 06 00
(V) 00 60 01 07 FF 0A 04 5A 46 46 31 02 03 09 06 01
(V) 00 01 07 00 FF 06 00 00 24 F4 02 02 0F 00 16 1B
(V) 02 03 09 06 01 00 02 07 00 FF 06 00 00 00 00 02
(V) 02 0F 00 16 1B 02 03 09 06 01 00 03 07 00 FF 06
(V) 00 00 00 31 02 02 0F 00 16 1D 02 03 09 06 01 00
(V) 04 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1D 02
(V) 03 09 06 01 00 1F 07 00 FF 10 0C 6C 02 02 0F FE
(V) 16 21 02 03 09 06 01 00 33 07 00 FF 10 00 00 02
(V) 02 0F FE 16 21 02 03 09 06 01 00 47 07 00 FF 10
(V) 0D 68 02 02 0F FE 16 21 02 03 09 06 01 00 20 07
(V) 00 FF 12 09 2E 02 02 0F FF 16 23 02 03 09 06 01
(V) 00 34 07 00 FF 12 00 00 02 02 0F FF 16 23 02 03
(V) 09 06 01 00 48 07 00 FF 12 09 10 02 02 0F FF 16
(V) 23
(D) Received valid DLMS at 18 +267
(V) Using application data:
(V) 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B 45 4D
(V) 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00 00 60
(V) 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00 00 00
(V) 00 00 00 00 00 02 02 09 06 00 00 60 01 07 FF 0A
(V) 04 5A 46 46 31 02 03 09 06 01 00 01 07 00 FF 06
(V) 00 00 24 F4 02 02 0F 00 16 1B 02 03 09 06 01 00
(V) 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1B 02
(V) 03 09 06 01 00 03 07 00 FF 06 00 00 00 31 02 02
(V) 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF 06 00
(V) 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01 00 1F
(V) 07 00 FF 10 0C 6C 02 02 0F FE 16 21 02 03 09 06
(V) 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16 21 02
(V) 03 09 06 01 00 47 07 00 FF 10 0D 68 02 02 0F FE
(V) 16 21 02 03 09 06 01 00 20 07 00 FF 12 09 2E 02
(V) 02 0F FF 16 23 02 03 09 06 01 00 34 07 00 FF 12
(V) 00 00 02 02 0F FF 16 23 02 03 09 06 01 00 48 07
(V) 00 FF 12 09 10 02 02 0F FF 16 23
(V) DLMS
(D) NOT Ready to update (internal clock 12:32:06 UTC, meter clock: 00:00:00, list type 2, est: 1)
(V) HDLC frame:
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
(V) 00 24 F4 02 02 0F 00 16 1B A7 7F 7E
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
(V) 00 01 07 00 FF 06 00 00 24 F4 02 02 0F 00 16 1B
(V)
(V) DLMS frame:
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
(V) 00 FF 06 00 00 24 F4 02 02 0F 00 16 1B
(D) Received valid DLMS at 18 +23
(V) Using application data:
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
(V) F4 02 02 0F 00 16 1B
(V) DLMS
(D) NOT Ready to update (internal clock 12:32:07 UTC, meter clock: 00:00:00, list type 1, est: 1)
(V) HDLC frame:
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
(V) 00 24 F4 02 02 0F 00 16 1B A7 7F 7E
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
(V) 00 01 07 00 FF 06 00 00 24 F4 02 02 0F 00 16 1B
(V)
(V) DLMS frame:
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
(V) 00 FF 06 00 00 24 F4 02 02 0F 00 16 1B
(D) Received valid DLMS at 18 +23
(V) Using application data:
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
(V) F4 02 02 0F 00 16 1B
(V) DLMS
(D) NOT Ready to update (internal clock 12:32:10 UTC, meter clock: 00:00:00, list type 1, est: 1)
(V) HDLC frame:
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
(V) 00 24 E0 02 02 0F 00 16 1B 18 A5 7E
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
(V) 00 01 07 00 FF 06 00 00 24 E0 02 02 0F 00 16 1B
(V)
(V) DLMS frame:
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
(V) 00 FF 06 00 00 24 E0 02 02 0F 00 16 1B
(D) Received valid DLMS at 18 +23
(V) Using application data:
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
(V) E0 02 02 0F 00 16 1B
(V) DLMS
(D) NOT Ready to update (internal clock 12:32:12 UTC, meter clock: 00:00:00, list type 1, est: 1)
(V) HDLC frame:
(V) 7E A1 1E 41 08 83 13 EE EE E6 E7 00 0F 40 00 00
(V) 00 00 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B
(V) 45 4D 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00
(V) 00 60 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00
(V) 00 00 00 00 00 00 00 02 02 09 06 00 00 60 01 07
(V) FF 0A 04 00 00 00 00 02 03 09 06 01 00 01 07 00
(V) FF 06 00 00 24 E0 02 02 0F 00 16 1B 02 03 09 06
(V) 01 00 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16
(V) 1B 02 03 09 06 01 00 03 07 00 FF 06 00 00 00 28
(V) 02 02 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF
(V) 06 00 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01
(V) 00 1F 07 00 FF 10 0C 68 02 02 0F FE 16 21 02 03
(V) 09 06 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16
(V) 21 02 03 09 06 01 00 47 07 00 FF 10 0D 5D 02 02
(V) 0F FE 16 21 02 03 09 06 01 00 20 07 00 FF 12 09
(V) 2E 02 02 0F FF 16 23 02 03 09 06 01 00 34 07 00
(V) FF 12 00 00 02 02 0F FF 16 23 02 03 09 06 01 00
(V) 48 07 00 FF 12 09 10 02 02 0F FF 16 23 AE 9D 7E
(V)
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 00 01 0D 02 02 09 06 01
(V) 01 00 02 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31
(V) 5F 31 02 02 09 06 00 00 60 01 00 FF 0A 10 31 33
(V) 39 34 33 35 39 37 00 00 00 00 00 00 00 00 02 02
(V) 09 06 00 00 60 01 07 FF 0A 04 00 00 00 00 02 03
(V) 09 06 01 00 01 07 00 FF 06 00 00 24 E0 02 02 0F
(V) 00 16 1B 02 03 09 06 01 00 02 07 00 FF 06 00 00
(V) 00 00 02 02 0F 00 16 1B 02 03 09 06 01 00 03 07
(V) 00 FF 06 00 00 00 28 02 02 0F 00 16 1D 02 03 09
(V) 06 01 00 04 07 00 FF 06 00 00 00 00 02 02 0F 00
(V) 16 1D 02 03 09 06 01 00 1F 07 00 FF 10 0C 68 02
(V) 02 0F FE 16 21 02 03 09 06 01 00 33 07 00 FF 10
(V) 00 00 02 02 0F FE 16 21 02 03 09 06 01 00 47 07
(V) 00 FF 10 0D 5D 02 02 0F FE 16 21 02 03 09 06 01
(V) 00 20 07 00 FF 12 09 2E 02 02 0F FF 16 23 02 03
(V) 09 06 01 00 34 07 00 FF 12 00 00 02 02 0F FF 16
(V) 23 02 03 09 06 01 00 48 07 00 FF 12 09 10 02 02
(V) 0F FF 16 23
(V) DLMS frame:
(V) 0F 40 00 00 00 00 01 0D 02 02 09 06 01 01 00 02
(V) 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31 5F 31 02
(V) 02 09 06 00 00 60 01 00 FF 0A 10 31 33 39 34 33
(V) 35 39 37 00 00 00 00 00 00 00 00 02 02 09 06 00
(V) 00 60 01 07 FF 0A 04 00 00 00 00 02 03 09 06 01
(V) 00 01 07 00 FF 06 00 00 24 E0 02 02 0F 00 16 1B
(V) 02 03 09 06 01 00 02 07 00 FF 06 00 00 00 00 02
(V) 02 0F 00 16 1B 02 03 09 06 01 00 03 07 00 FF 06
(V) 00 00 00 28 02 02 0F 00 16 1D 02 03 09 06 01 00
(V) 04 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1D 02
(V) 03 09 06 01 00 1F 07 00 FF 10 0C 68 02 02 0F FE
(V) 16 21 02 03 09 06 01 00 33 07 00 FF 10 00 00 02
(V) 02 0F FE 16 21 02 03 09 06 01 00 47 07 00 FF 10
(V) 0D 5D 02 02 0F FE 16 21 02 03 09 06 01 00 20 07
(V) 00 FF 12 09 2E 02 02 0F FF 16 23 02 03 09 06 01
(V) 00 34 07 00 FF 12 00 00 02 02 0F FF 16 23 02 03
(V) 09 06 01 00 48 07 00 FF 12 09 10 02 02 0F FF 16
(V) 23
(D) Received valid DLMS at 18 +267
(V) Using application data:
(V) 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B 45 4D
(V) 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00 00 60
(V) 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00 00 00
(V) 00 00 00 00 00 02 02 09 06 00 00 60 01 07 FF 0A
(V) 04 00 00 00 00 02 03 09 06 01 00 01 07 00 FF 06
(V) 00 00 24 E0 02 02 0F 00 16 1B 02 03 09 06 01 00
(V) 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1B 02
(V) 03 09 06 01 00 03 07 00 FF 06 00 00 00 28 02 02
(V) 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF 06 00
(V) 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01 00 1F
(V) 07 00 FF 10 0C 68 02 02 0F FE 16 21 02 03 09 06
(V) 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16 21 02
(V) 03 09 06 01 00 47 07 00 FF 10 0D 5D 02 02 0F FE
(V) 16 21 02 03 09 06 01 00 20 07 00 FF 12 09 2E 02
(V) 02 0F FF 16 23 02 03 09 06 01 00 34 07 00 FF 12
(V) 00 00 02 02 0F FF 16 23 02 03 09 06 01 00 48 07
(V) 00 FF 12 09 10 02 02 0F FF 16 23
(V) DLMS
(D) NOT Ready to update (internal clock 12:32:16 UTC, meter clock: 00:00:00, list type 2, est: 1)
(V) HDLC frame:
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
(V) 00 24 E0 02 02 0F 00 16 1B 18 A5 7E
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
(V) 00 01 07 00 FF 06 00 00 24 E0 02 02 0F 00 16 1B
(V)
(V) DLMS frame:
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
(V) 00 FF 06 00 00 24 E0 02 02 0F 00 16 1B
(D) Received valid DLMS at 18 +23
(V) Using application data:
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
(V) E0 02 02 0F 00 16 1B
(V) DLMS
(D) NOT Ready to update (internal clock 12:32:17 UTC, meter clock: 00:00:00, list type 1, est: 1)
(V) HDLC frame:
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
(V) 00 24 A4 02 02 0F 00 16 1B 68 0D 7E
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
(V) 00 01 07 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
(V)
(V) DLMS frame:
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
(V) 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
(D) Received valid DLMS at 18 +23
(V) Using application data:
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
(V) A4 02 02 0F 00 16 1B
(V) DLMS
(D) NOT Ready to update (internal clock 12:32:20 UTC, meter clock: 00:00:00, list type 1, est: 1)
(V) HDLC frame:
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
(V) 00 24 A4 02 02 0F 00 16 1B 68 0D 7E
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
(V) 00 01 07 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
(V)
(V) DLMS frame:
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
(V) 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
(D) Received valid DLMS at 18 +23
(V) Using application data:
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
(V) A4 02 02 0F 00 16 1B
(V) DLMS
(D) NOT Ready to update (internal clock 12:32:22 UTC, meter clock: 00:00:00, list type 1, est: 1)
(V) HDLC frame:
(V) 7E A1 1E 41 08 83 13 EE EE E6 E7 00 0F 40 00 00
(V) 00 00 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B
(V) 45 4D 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00
(V) 00 60 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00
(V) 00 00 00 00 00 00 00 02 02 09 06 00 00 60 01 07
(V) FF 0A 04 5A 46 46 31 02 03 09 06 01 00 01 07 00
(V) FF 06 00 00 24 A4 02 02 0F 00 16 1B 02 03 09 06
(V) 01 00 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16
(V) 1B 02 03 09 06 01 00 03 07 00 FF 06 00 00 00 31
(V) 02 02 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF
(V) 06 00 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01
(V) 00 1F 07 00 FF 10 0C 6E 02 02 0F FE 16 21 02 03
(V) 09 06 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16
(V) 21 02 03 09 06 01 00 47 07 00 FF 10 0D 5C 02 02
(V) 0F FE 16 21 02 03 09 06 01 00 20 07 00 FF 12 09
(V) 38 02 02 0F FF 16 23 02 03 09 06 01 00 34 07 00
(V) FF 12 00 00 02 02 0F FF 16 23 02 03 09 06 01 00
(V) 48 07 00 FF 12 09 10 02 02 0F FF 16 23 4A DF 7E
(V)
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 00 01 0D 02 02 09 06 01
(V) 01 00 02 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31
(V) 5F 31 02 02 09 06 00 00 60 01 00 FF 0A 10 31 33
(V) 39 34 33 35 39 37 00 00 00 00 00 00 00 00 02 02
(V) 09 06 00 00 60 01 07 FF 0A 04 5A 46 46 31 02 03
(V) 09 06 01 00 01 07 00 FF 06 00 00 24 A4 02 02 0F
(V) 00 16 1B 02 03 09 06 01 00 02 07 00 FF 06 00 00
(V) 00 00 02 02 0F 00 16 1B 02 03 09 06 01 00 03 07
(V) 00 FF 06 00 00 00 31 02 02 0F 00 16 1D 02 03 09
(V) 06 01 00 04 07 00 FF 06 00 00 00 00 02 02 0F 00
(V) 16 1D 02 03 09 06 01 00 1F 07 00 FF 10 0C 6E 02
(V) 02 0F FE 16 21 02 03 09 06 01 00 33 07 00 FF 10
(V) 00 00 02 02 0F FE 16 21 02 03 09 06 01 00 47 07
(V) 00 FF 10 0D 5C 02 02 0F FE 16 21 02 03 09 06 01
(V) 00 20 07 00 FF 12 09 38 02 02 0F FF 16 23 02 03
(V) 09 06 01 00 34 07 00 FF 12 00 00 02 02 0F FF 16
(V) 23 02 03 09 06 01 00 48 07 00 FF 12 09 10 02 02
(V) 0F FF 16 23
(V) DLMS frame:
(V) 0F 40 00 00 00 00 01 0D 02 02 09 06 01 01 00 02
(V) 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31 5F 31 02
(V) 02 09 06 00 00 60 01 00 FF 0A 10 31 33 39 34 33
(V) 35 39 37 00 00 00 00 00 00 00 00 02 02 09 06 00
(V) 00 60 01 07 FF 0A 04 5A 46 46 31 02 03 09 06 01
(V) 00 01 07 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
(V) 02 03 09 06 01 00 02 07 00 FF 06 00 00 00 00 02
(V) 02 0F 00 16 1B 02 03 09 06 01 00 03 07 00 FF 06
(V) 00 00 00 31 02 02 0F 00 16 1D 02 03 09 06 01 00
(V) 04 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1D 02
(V) 03 09 06 01 00 1F 07 00 FF 10 0C 6E 02 02 0F FE
(V) 16 21 02 03 09 06 01 00 33 07 00 FF 10 00 00 02
(V) 02 0F FE 16 21 02 03 09 06 01 00 47 07 00 FF 10
(V) 0D 5C 02 02 0F FE 16 21 02 03 09 06 01 00 20 07
(V) 00 FF 12 09 38 02 02 0F FF 16 23 02 03 09 06 01
(V) 00 34 07 00 FF 12 00 00 02 02 0F FF 16 23 02 03
(V) 09 06 01 00 48 07 00 FF 12 09 10 02 02 0F FF 16
(V) 23
(D) Received valid DLMS at 18 +267
(V) Using application data:
(V) 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B 45 4D
(V) 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00 00 60
(V) 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00 00 00
(V) 00 00 00 00 00 02 02 09 06 00 00 60 01 07 FF 0A
(V) 04 5A 46 46 31 02 03 09 06 01 00 01 07 00 FF 06
(V) 00 00 24 A4 02 02 0F 00 16 1B 02 03 09 06 01 00
(V) 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1B 02
(V) 03 09 06 01 00 03 07 00 FF 06 00 00 00 31 02 02
(V) 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF 06 00
(V) 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01 00 1F
(V) 07 00 FF 10 0C 6E 02 02 0F FE 16 21 02 03 09 06
(V) 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16 21 02
(V) 03 09 06 01 00 47 07 00 FF 10 0D 5C 02 02 0F FE
(V) 16 21 02 03 09 06 01 00 20 07 00 FF 12 09 38 02
(V) 02 0F FF 16 23 02 03 09 06 01 00 34 07 00 FF 12
(V) 00 00 02 02 0F FF 16 23 02 03 09 06 01 00 48 07
(V) 00 FF 12 09 10 02 02 0F FF 16 23
(V) DLMS
(D) NOT Ready to update (internal clock 12:32:26 UTC, meter clock: 00:00:00, list type 2, est: 1)
(V) HDLC frame:
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
(V) 00 24 B8 02 02 0F 00 16 1B 3B 09 7E
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
(V) 00 01 07 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
(V)
(V) DLMS frame:
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
(V) 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
(D) Received valid DLMS at 18 +23
(V) Using application data:
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
(V) B8 02 02 0F 00 16 1B
(V) DLMS
(D) NOT Ready to update (internal clock 12:32:27 UTC, meter clock: 00:00:00, list type 1, est: 1)
(V) HDLC frame:
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
(V) 00 24 B8 02 02 0F 00 16 1B 3B 09 7E
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
(V) 00 01 07 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
(V)
(V) DLMS frame:
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
(V) 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
(D) Received valid DLMS at 18 +23
(V) Using application data:
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
(V) B8 02 02 0F 00 16 1B
(V) DLMS
(D) NOT Ready to update (internal clock 12:32:29 UTC, meter clock: 00:00:00, list type 1, est: 1)
(V) HDLC frame:
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
(V) 00 24 B8 02 02 0F 00 16 1B 3B 09 7E
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
(V) 00 01 07 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
(V)
(V) DLMS frame:
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
(V) 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
(D) Received valid DLMS at 18 +23
(V) Using application data:
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
(V) B8 02 02 0F 00 16 1B
(V) DLMS
(D) NOT Ready to update (internal clock 12:32:32 UTC, meter clock: 00:00:00, list type 1, est: 1)
q(V) HDLC frame:
(V) 7E A1 1E 41 08 83 13 EE EE E6 E7 00 0F 40 00 00
(V) 00 00 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B
(V) 45 4D 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00
(V) 00 60 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00
(V) 00 00 00 00 00 00 00 02 02 09 06 00 00 60 01 07
(V) FF 0A 04 5A 46 46 31 02 03 09 06 01 00 01 07 00
(V) FF 06 00 00 24 A4 02 02 0F 00 16 1B 02 03 09 06
(V) 01 00 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16
(V) 1B 02 03 09 06 01 00 03 07 00 FF 06 00 00 00 46
(V) 02 02 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF
(V) 06 00 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01
(V) 00 1F 07 00 FF 10 0C 67 02 02 0F FE 16 21 02 03
(V) 09 06 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16
(V) 21 02 03 09 06 01 00 47 07 00 FF 10 0D 5D 02 02
(V) 0F FE 16 21 02 03 09 06 01 00 20 07 00 FF 12 09
(V) 2E 02 02 0F FF 16 23 02 03 09 06 01 00 34 07 00
(V) FF 12 00 00 02 02 0F FF 16 23 02 03 09 06 01 00
(V) 48 07 00 FF 12 09 06 02 02 0F FF 16 23 3A 49 7E
(V)
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 00 01 0D 02 02 09 06 01
(V) 01 00 02 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31
(V) 5F 31 02 02 09 06 00 00 60 01 00 FF 0A 10 31 33
(V) 39 34 33 35 39 37 00 00 00 00 00 00 00 00 02 02
(V) 09 06 00 00 60 01 07 FF 0A 04 5A 46 46 31 02 03
(V) 09 06 01 00 01 07 00 FF 06 00 00 24 A4 02 02 0F
(V) 00 16 1B 02 03 09 06 01 00 02 07 00 FF 06 00 00
(V) 00 00 02 02 0F 00 16 1B 02 03 09 06 01 00 03 07
(V) 00 FF 06 00 00 00 46 02 02 0F 00 16 1D 02 03 09
(V) 06 01 00 04 07 00 FF 06 00 00 00 00 02 02 0F 00
(V) 16 1D 02 03 09 06 01 00 1F 07 00 FF 10 0C 67 02
(V) 02 0F FE 16 21 02 03 09 06 01 00 33 07 00 FF 10
(V) 00 00 02 02 0F FE 16 21 02 03 09 06 01 00 47 07
(V) 00 FF 10 0D 5D 02 02 0F FE 16 21 02 03 09 06 01
(V) 00 20 07 00 FF 12 09 2E 02 02 0F FF 16 23 02 03
(V) 09 06 01 00 34 07 00 FF 12 00 00 02 02 0F FF 16
(V) 23 02 03 09 06 01 00 48 07 00 FF 12 09 06 02 02
(V) 0F FF 16 23
(V) DLMS frame:
(V) 0F 40 00 00 00 00 01 0D 02 02 09 06 01 01 00 02
(V) 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31 5F 31 02
(V) 02 09 06 00 00 60 01 00 FF 0A 10 31 33 39 34 33
(V) 35 39 37 00 00 00 00 00 00 00 00 02 02 09 06 00
(V) 00 60 01 07 FF 0A 04 5A 46 46 31 02 03 09 06 01
(V) 00 01 07 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
(V) 02 03 09 06 01 00 02 07 00 FF 06 00 00 00 00 02
(V) 02 0F 00 16 1B 02 03 09 06 01 00 03 07 00 FF 06
(V) 00 00 00 46 02 02 0F 00 16 1D 02 03 09 06 01 00
(V) 04 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1D 02
(V) 03 09 06 01 00 1F 07 00 FF 10 0C 67 02 02 0F FE
(V) 16 21 02 03 09 06 01 00 33 07 00 FF 10 00 00 02
(V) 02 0F FE 16 21 02 03 09 06 01 00 47 07 00 FF 10
(V) 0D 5D 02 02 0F FE 16 21 02 03 09 06 01 00 20 07
(V) 00 FF 12 09 2E 02 02 0F FF 16 23 02 03 09 06 01
(V) 00 34 07 00 FF 12 00 00 02 02 0F FF 16 23 02 03
(V) 09 06 01 00 48 07 00 FF 12 09 06 02 02 0F FF 16
(V) 23
(D) Received valid DLMS at 18 +267
(V) Using application data:
(V) 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B 45 4D
(V) 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00 00 60
(V) 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00 00 00
(V) 00 00 00 00 00 02 02 09 06 00 00 60 01 07 FF 0A
(V) 04 5A 46 46 31 02 03 09 06 01 00 01 07 00 FF 06
(V) 00 00 24 A4 02 02 0F 00 16 1B 02 03 09 06 01 00
(V) 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1B 02
(V) 03 09 06 01 00 03 07 00 FF 06 00 00 00 46 02 02
(V) 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF 06 00
(V) 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01 00 1F
(V) 07 00 FF 10 0C 67 02 02 0F FE 16 21 02 03 09 06
(V) 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16 21 02
(V) 03 09 06 01 00 47 07 00 FF 10 0D 5D 02 02 0F FE
(V) 16 21 02 03 09 06 01 00 20 07 00 FF 12 09 2E 02
(V) 02 0F FF 16 23 02 03 09 06 01 00 34 07 00 FF 12
(V) 00 00 02 02 0F FF 16 23 02 03 09 06 01 00 48 07
(V) 00 FF 12 09 06 02 02 0F FF 16 23
(V) DLMS
(D) NOT Ready to update (internal clock 12:32:36 UTC, meter clock: 00:00:00, list type 2, est: 1)
(V) HDLC frame:
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
(V) 00 24 A4 02 02 0F 00 16 1B 68 0D 7E
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
(V) 00 01 07 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
(V)
(V) DLMS frame:
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
(V) 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
(D) Received valid DLMS at 18 +23
(V) Using application data:
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
(V) A4 02 02 0F 00 16 1B
(V) DLMS
(D) NOT Ready to update (internal clock 12:32:37 UTC, meter clock: 00:00:00, list type 1, est: 1)
(V) HDLC frame:
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
(V) 00 24 A4 02 02 0F 00 16 1B 68 0D 7E
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
(V) 00 01 07 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
(V)
(V) DLMS frame:
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
(V) 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
(D) Received valid DLMS at 18 +23
(V) Using application data:
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
(V) A4 02 02 0F 00 16 1B
(V) DLMS
(D) NOT Ready to update (internal clock 12:32:39 UTC, meter clock: 00:00:00, list type 1, est: 1)
(V) HDLC frame:
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
(V) 00 24 D5 02 02 0F 00 16 1B F1 83 7E
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
(V) 00 01 07 00 FF 06 00 00 24 D5 02 02 0F 00 16 1B
(V)
(V) DLMS frame:
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
(V) 00 FF 06 00 00 24 D5 02 02 0F 00 16 1B
(D) Received valid DLMS at 18 +23
(V) Using application data:
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
(V) D5 02 02 0F 00 16 1B
(V) DLMS
(D) NOT Ready to update (internal clock 12:32:42 UTC, meter clock: 00:00:00, list type 1, est: 1)
(V) HDLC frame:
(V) 7E A1 1E 41 08 83 13 EE EE E6 E7 00 0F 40 00 00
(V) 00 00 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B
(V) 45 4D 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00
(V) 00 60 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00
(V) 00 00 00 00 00 00 00 02 02 09 06 00 00 60 01 07
(V) FF 0A 04 5A 46 46 31 02 03 09 06 01 00 01 07 00
(V) FF 06 00 00 24 D5 02 02 0F 00 16 1B 02 03 09 06
(V) 01 00 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16
(V) 1B 02 03 09 06 01 00 03 07 00 FF 06 00 00 00 3C
(V) 02 02 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF
(V) 06 00 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01
(V) 00 1F 07 00 FF 10 0C 77 02 02 0F FE 16 21 02 03
(V) 09 06 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16
(V) 21 02 03 09 06 01 00 47 07 00 FF 10 0D 5F 02 02
(V) 0F FE 16 21 02 03 09 06 01 00 20 07 00 FF 12 09
(V) 2E 02 02 0F FF 16 23 02 03 09 06 01 00 34 07 00
(V) FF 12 00 00 02 02 0F FF 16 23 02 03 09 06 01 00
(V) 48 07 00 FF 12 09 06 02 02 0F FF 16 23 4A 9D 7E
(V)
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 00 01 0D 02 02 09 06 01
(V) 01 00 02 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31
(V) 5F 31 02 02 09 06 00 00 60 01 00 FF 0A 10 31 33
(V) 39 34 33 35 39 37 00 00 00 00 00 00 00 00 02 02
(V) 09 06 00 00 60 01 07 FF 0A 04 5A 46 46 31 02 03
(V) 09 06 01 00 01 07 00 FF 06 00 00 24 D5 02 02 0F
(V) 00 16 1B 02 03 09 06 01 00 02 07 00 FF 06 00 00
(V) 00 00 02 02 0F 00 16 1B 02 03 09 06 01 00 03 07
(V) 00 FF 06 00 00 00 3C 02 02 0F 00 16 1D 02 03 09
(V) 06 01 00 04 07 00 FF 06 00 00 00 00 02 02 0F 00
(V) 16 1D 02 03 09 06 01 00 1F 07 00 FF 10 0C 77 02
(V) 02 0F FE 16 21 02 03 09 06 01 00 33 07 00 FF 10
(V) 00 00 02 02 0F FE 16 21 02 03 09 06 01 00 47 07
(V) 00 FF 10 0D 5F 02 02 0F FE 16 21 02 03 09 06 01
(V) 00 20 07 00 FF 12 09 2E 02 02 0F FF 16 23 02 03
(V) 09 06 01 00 34 07 00 FF 12 00 00 02 02 0F FF 16
(V) 23 02 03 09 06 01 00 48 07 00 FF 12 09 06 02 02
(V) 0F FF 16 23
(V) DLMS frame:
(V) 0F 40 00 00 00 00 01 0D 02 02 09 06 01 01 00 02
(V) 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31 5F 31 02
(V) 02 09 06 00 00 60 01 00 FF 0A 10 31 33 39 34 33
(V) 35 39 37 00 00 00 00 00 00 00 00 02 02 09 06 00
(V) 00 60 01 07 FF 0A 04 5A 46 46 31 02 03 09 06 01
(V) 00 01 07 00 FF 06 00 00 24 D5 02 02 0F 00 16 1B
(V) 02 03 09 06 01 00 02 07 00 FF 06 00 00 00 00 02
(V) 02 0F 00 16 1B 02 03 09 06 01 00 03 07 00 FF 06
(V) 00 00 00 3C 02 02 0F 00 16 1D 02 03 09 06 01 00
(V) 04 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1D 02
(V) 03 09 06 01 00 1F 07 00 FF 10 0C 77 02 02 0F FE
(V) 16 21 02 03 09 06 01 00 33 07 00 FF 10 00 00 02
(V) 02 0F FE 16 21 02 03 09 06 01 00 47 07 00 FF 10
(V) 0D 5F 02 02 0F FE 16 21 02 03 09 06 01 00 20 07
(V) 00 FF 12 09 2E 02 02 0F FF 16 23 02 03 09 06 01
(V) 00 34 07 00 FF 12 00 00 02 02 0F FF 16 23 02 03
(V) 09 06 01 00 48 07 00 FF 12 09 06 02 02 0F FF 16
(V) 23
(D) Received valid DLMS at 18 +267
(V) Using application data:
(V) 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B 45 4D
(V) 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00 00 60
(V) 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00 00 00
(V) 00 00 00 00 00 02 02 09 06 00 00 60 01 07 FF 0A
(V) 04 5A 46 46 31 02 03 09 06 01 00 01 07 00 FF 06
(V) 00 00 24 D5 02 02 0F 00 16 1B 02 03 09 06 01 00
(V) 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1B 02
(V) 03 09 06 01 00 03 07 00 FF 06 00 00 00 3C 02 02
(V) 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF 06 00
(V) 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01 00 1F
(V) 07 00 FF 10 0C 77 02 02 0F FE 16 21 02 03 09 06
(V) 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16 21 02
(V) 03 09 06 01 00 47 07 00 FF 10 0D 5F 02 02 0F FE
(V) 16 21 02 03 09 06 01 00 20 07 00 FF 12 09 2E 02
(V) 02 0F FF 16 23 02 03 09 06 01 00 34 07 00 FF 12
(V) 00 00 02 02 0F FF 16 23 02 03 09 06 01 00 48 07
(V) 00 FF 12 09 06 02 02 0F FF 16 23
(V) DLMS
(D) NOT Ready to update (internal clock 12:32:46 UTC, meter clock: 00:00:00, list type 2, est: 1)
qq(V) HDLC frame:
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
(V) 00 24 D5 02 02 0F 00 16 1B F1 83 7E
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
(V) 00 01 07 00 FF 06 00 00 24 D5 02 02 0F 00 16 1B
(V)
(V) DLMS frame:
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
(V) 00 FF 06 00 00 24 D5 02 02 0F 00 16 1B
(D) Received valid DLMS at 18 +23
(V) Using application data:
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
(V) D5 02 02 0F 00 16 1B
(V) DLMS
(D) NOT Ready to update (internal clock 12:32:47 UTC, meter clock: 00:00:00, list type 1, est: 1)
q
* Debug: Command received: qqqqq
(V) HDLC frame:
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
(V) 00 24 B8 02 02 0F 00 16 1B 3B 09 7E
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
(V) 00 01 07 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
(V)
(V) DLMS frame:
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
(V) 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
(D) Received valid DLMS at 18 +23
(V) Using application data:
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
(V) B8 02 02 0F 00 16 1B
(V) DLMS
(D) NOT Ready to update (internal clock 12:32:49 UTC, meter clock: 00:00:00, list type 1, est: 1)
(V) HDLC frame:
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
(V) 00 24 B8 02 02 0F 00 16 1B 3B 09 7E
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
(V) 00 01 07 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
(V)
(V) DLMS frame:
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
(V) 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
(D) Received valid DLMS at 18 +23
(V) Using application data:
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
(V) B8 02 02 0F 00 16 1B
(V) DLMS
(D) NOT Ready to update (internal clock 12:32:51 UTC, meter clock: 00:00:00, list type 1, est: 1)
q
* Debug: Command received: q
* Closing client connection ...

1
frames/dsmr.raw Normal file
View File

@@ -0,0 +1 @@
2F454C4C355C3235333833333633355F410D0A0D0A302D303A312E302E302832343132313232303133343857290D0A312D303A312E382E302830303034373532322E3237382A6B5768290D0A312D303A322E382E302830303030303030302E3030312A6B5768290D0A312D303A332E382E302830303030303035392E3033392A6B76617268290D0A312D303A342E382E302830303031323537302E3937362A6B76617268290D0A312D303A312E372E3028303030312E3233312A6B57290D0A312D303A322E372E3028303030302E3030302A6B57290D0A312D303A332E372E3028303030302E3030302A6B766172290D0A312D303A342E372E3028303030302E3435312A6B766172290D0A312D303A32312E372E3028303030302E3337362A6B57290D0A312D303A34312E372E3028303030302E3832342A6B57290D0A312D303A36312E372E3028303030302E3033302A6B57290D0A312D303A32322E372E3028303030302E3030302A6B57290D0A312D303A34322E372E3028303030302E3030302A6B57290D0A312D303A36322E372E3028303030302E3030302A6B57290D0A312D303A32332E372E3028303030302E3030302A6B766172290D0A312D303A34332E372E3028303030302E3030302A6B766172290D0A312D303A36332E372E3028303030302E3030302A6B766172290D0A312D303A32342E372E3028303030302E3037322A6B766172290D0A312D303A34342E372E3028303030302E3334392A6B766172290D0A312D303A36342E372E3028303030302E3032392A6B766172290D0A312D303A33322E372E30283233322E382A56290D0A312D303A35322E372E30283233332E352A56290D0A312D303A37322E372E30283233382E352A56290D0A312D303A33312E372E30283030312E382A41290D0A312D303A35312E372E30283030342E302A41290D0A312D303A37312E372E30283030302E312A41290D0A21343736300D0A

View File

@@ -13,10 +13,8 @@
#define EEPROM_CHECK_SUM 104 // Used to check if config is stored. Change if structure changes
#define EEPROM_CLEARED_INDICATOR 0xFC
#define EEPROM_CONFIG_ADDRESS 0
#define EEPROM_TEMP_CONFIG_ADDRESS 2048
#define CONFIG_SYSTEM_START 8
#define CONFIG_UPGRADE_INFO_START 16
#define CONFIG_NETWORK_START 40
#define CONFIG_METER_START 296
#define CONFIG_GPIO_START 368
@@ -30,6 +28,8 @@
#define CONFIG_HA_START 1552
#define CONFIG_UI_START 1720
#define CONFIG_CLOUD_START 1742
#define CONFIG_UPGRADE_INFO_START 1934
#define CONFIG_ZC_START 2000
#define CONFIG_METER_START_103 32
#define CONFIG_UPGRADE_INFO_START_103 216
@@ -63,7 +63,7 @@ struct SystemConfig {
uint8_t dataCollectionConsent; // 0 = unknown, 1 = accepted, 2 = declined
char country[3];
uint8_t energyspeedometer;
}; // 9
}; // 8
struct NetworkConfig {
char ssid[32];
@@ -92,7 +92,12 @@ struct MqttConfig {
char password[256];
uint8_t payloadFormat;
bool ssl;
}; // 676
uint8_t magic;
bool stateUpdate;
uint16_t stateUpdateInterval;
uint16_t timeout;
uint8_t keepalive;
}; // 685
struct WebConfig {
uint8_t security;
@@ -211,11 +216,6 @@ struct EnergyAccountingConfig {
uint8_t hours;
}; // 21
struct EnergyAccountingConfig101 {
uint8_t thresholds[10];
uint8_t hours;
}; // 11
struct UiConfig {
uint8_t showImport;
uint8_t showExport;
@@ -238,9 +238,12 @@ struct UiConfig {
struct UpgradeInformation {
char fromVersion[8];
char toVersion[8];
int16_t exitCode;
int16_t errorCode;
}; // 20
uint32_t size;
uint16_t block_position;
uint8_t retry_count;
uint8_t reboot_count;
int8_t errorCode;
}; // 25
struct CloudConfig {
bool enabled;
@@ -248,7 +251,14 @@ struct CloudConfig {
char hostname[64];
uint16_t port;
uint8_t clientId[16];
}; // 69
uint8_t proto;
}; // 88
struct ZmartChargeConfig {
bool enabled;
char token[21];
char baseUrl[64];
}; // 86
class AmsConfiguration {
public:
@@ -279,6 +289,8 @@ public:
bool getWebConfig(WebConfig&);
bool setWebConfig(WebConfig&);
void clearWebConfig(WebConfig&);
bool isWebChanged();
void ackWebChange();
bool getMeterConfig(MeterConfig&);
bool setMeterConfig(MeterConfig&);
@@ -295,7 +307,7 @@ public:
bool getGpioConfig(GpioConfig&);
bool setGpioConfig(GpioConfig&);
void clearGpio(GpioConfig&);
void clearGpio(GpioConfig& config, bool all=true);
void print(Print* debugger);
@@ -333,7 +345,7 @@ public:
void ackUiLanguageChange();
bool getUpgradeInformation(UpgradeInformation&);
bool setUpgradeInformation(int16_t exitCode, int16_t errorCode, const char* currentVersion, const char* nextVersion);
bool setUpgradeInformation(UpgradeInformation&);
void clearUpgradeInformation(UpgradeInformation&);
bool getCloudConfig(CloudConfig&);
@@ -341,6 +353,13 @@ public:
void clearCloudConfig(CloudConfig&);
bool isCloudChanged();
void ackCloudConfig();
bool getZmartChargeConfig(ZmartChargeConfig&);
bool setZmartChargeConfig(ZmartChargeConfig&);
void clearZmartChargeConfig(ZmartChargeConfig&);
bool isZmartChargeConfigChanged();
void ackZmartChargeConfig();
void clear();
@@ -349,10 +368,8 @@ protected:
private:
uint8_t configVersion = 0;
bool sysChanged = false, networkChanged, mqttChanged, meterChanged = true, ntpChanged = true, priceChanged = false, energyAccountingChanged = true, cloudChanged = true, uiLanguageChanged = false;
bool sysChanged = false, networkChanged = false, mqttChanged = false, webChanged = false, meterChanged = true, ntpChanged = true, priceChanged = false, energyAccountingChanged = true, cloudChanged = true, uiLanguageChanged = false, zcChanged = true;
bool relocateConfig101(); // 2.2.0 through 2.2.8
bool relocateConfig102(); // 2.2.9 through 2.2.11
bool relocateConfig103(); // 2.2.12, until, but not including 2.3
void saveToFs();

View File

@@ -7,7 +7,7 @@
#ifndef _AMSSTORAGE_H
#define _AMSSTORAGE_H
#define FILE_FIRMWARE "/firmware.bin"
#define FILE_FIRMWARE_DELETE "/firmware.bin"
#define FILE_MQTT_CA "/mqtt-ca.pem"
#define FILE_MQTT_CERT "/mqtt-cert.pem"

View File

@@ -13,6 +13,7 @@
String toHex(uint8_t* in);
String toHex(uint8_t* in, uint16_t size);
void fromHex(uint8_t *out, String in, uint16_t size);
bool stripNonAscii(uint8_t* in, uint16_t size, bool extended = false);
bool stripNonAscii(uint8_t* in, uint16_t size, bool extended = false, bool trim = true);
void debugPrint(uint8_t *buffer, uint16_t start, uint16_t length, Print* debugger);
#endif

View File

@@ -13,13 +13,18 @@
bool AmsConfiguration::getSystemConfig(SystemConfig& config) {
EEPROM.begin(EEPROM_SIZE);
uint8_t configVersion = EEPROM.read(EEPROM_CONFIG_ADDRESS);
if(configVersion == EEPROM_CHECK_SUM || configVersion == EEPROM_CLEARED_INDICATOR) {
EEPROM.get(CONFIG_SYSTEM_START, config);
EEPROM.end();
EEPROM.get(CONFIG_SYSTEM_START, config);
EEPROM.end();
if(configVersion == EEPROM_CHECK_SUM) {
return true;
} else {
config.boardType = 0xFF;
config.vendorConfigured = false;
if(configVersion == EEPROM_CLEARED_INDICATOR && config.boardType > 0 && config.boardType < 250) {
config.vendorConfigured = true;
} else {
config.vendorConfigured = false;
config.boardType = 0xFF;
clear();
}
config.userConfigured = false;
config.dataCollectionConsent = 0;
config.energyspeedometer = 0;
@@ -91,7 +96,7 @@ bool AmsConfiguration::setNetworkConfig(NetworkConfig& config) {
}
stripNonAscii((uint8_t*) config.ssid, 32, true);
stripNonAscii((uint8_t*) config.psk, 64);
stripNonAscii((uint8_t*) config.psk, 64, true, false);
stripNonAscii((uint8_t*) config.ip, 16);
stripNonAscii((uint8_t*) config.gateway, 16);
stripNonAscii((uint8_t*) config.subnet, 16);
@@ -147,6 +152,15 @@ bool AmsConfiguration::getMqttConfig(MqttConfig& config) {
EEPROM.begin(EEPROM_SIZE);
EEPROM.get(CONFIG_MQTT_START, config);
EEPROM.end();
if(config.magic != 0x9C) {
if(config.magic != 0x7B) {
config.stateUpdate = false;
config.stateUpdateInterval = 10;
}
config.timeout = 1000;
config.keepalive = 60;
config.magic = 0x9C;
}
return true;
} else {
clearMqtt(config);
@@ -166,6 +180,8 @@ bool AmsConfiguration::setMqttConfig(MqttConfig& config) {
mqttChanged |= strcmp(config.password, existing.password) != 0;
mqttChanged |= config.payloadFormat != existing.payloadFormat;
mqttChanged |= config.ssl != existing.ssl;
mqttChanged |= config.stateUpdate != existing.stateUpdate;
mqttChanged |= config.stateUpdateInterval != existing.stateUpdateInterval;
} else {
mqttChanged = true;
}
@@ -174,8 +190,12 @@ bool AmsConfiguration::setMqttConfig(MqttConfig& config) {
stripNonAscii((uint8_t*) config.clientId, 32);
stripNonAscii((uint8_t*) config.publishTopic, 64);
stripNonAscii((uint8_t*) config.subscribeTopic, 64);
stripNonAscii((uint8_t*) config.username, 128);
stripNonAscii((uint8_t*) config.password, 256);
stripNonAscii((uint8_t*) config.username, 128, true);
stripNonAscii((uint8_t*) config.password, 256, true, false);
if(config.timeout < 500) config.timeout = 1000;
if(config.timeout > 10000) config.timeout = 1000;
if(config.keepalive < 5) config.keepalive = 60;
if(config.keepalive > 240) config.keepalive = 60;
EEPROM.begin(EEPROM_SIZE);
EEPROM.put(CONFIG_MQTT_START, config);
@@ -195,6 +215,11 @@ void AmsConfiguration::clearMqtt(MqttConfig& config) {
memset(config.password, 0, 256);
config.payloadFormat = 0;
config.ssl = false;
config.magic = 0x7B;
config.stateUpdate = false;
config.stateUpdateInterval = 10;
config.timeout = 1000;
config.keepalive = 60;
}
void AmsConfiguration::setMqttChanged() {
@@ -222,9 +247,17 @@ bool AmsConfiguration::getWebConfig(WebConfig& config) {
}
bool AmsConfiguration::setWebConfig(WebConfig& config) {
WebConfig existing;
if(getWebConfig(existing)) {
webChanged |= strcmp(config.username, existing.username) != 0;
webChanged |= strcmp(config.password, existing.password) != 0;
webChanged |= strcmp(config.context, existing.context) != 0;
} else {
webChanged = true;
}
stripNonAscii((uint8_t*) config.username, 37);
stripNonAscii((uint8_t*) config.password, 37);
stripNonAscii((uint8_t*) config.password, 37, false, false);
stripNonAscii((uint8_t*) config.context, 37);
EEPROM.begin(EEPROM_SIZE);
@@ -241,12 +274,27 @@ void AmsConfiguration::clearWebConfig(WebConfig& config) {
memset(config.context, 0, 37);
}
bool AmsConfiguration::isWebChanged() {
return webChanged;
}
void AmsConfiguration::ackWebChange() {
webChanged = false;
}
bool AmsConfiguration::getMeterConfig(MeterConfig& config) {
if(hasConfig()) {
EEPROM.begin(EEPROM_SIZE);
EEPROM.begin(EEPROM_SIZE);
uint8_t configVersion = EEPROM.read(EEPROM_CONFIG_ADDRESS);
if(configVersion == EEPROM_CHECK_SUM || configVersion == EEPROM_CLEARED_INDICATOR) {
EEPROM.get(CONFIG_METER_START, config);
EEPROM.end();
if(config.bufferSize < 1 || config.bufferSize > 64) config.bufferSize = 4;
if(config.bufferSize < 1 || config.bufferSize > 64) {
#if defined(ESP32)
config.bufferSize = 2;
#else
config.bufferSize = 1;
#endif
}
return true;
} else {
clearMeter(config);
@@ -394,6 +442,7 @@ bool AmsConfiguration::getHomeAssistantConfig(HomeAssistantConfig& config) {
EEPROM.end();
if(stripNonAscii((uint8_t*) config.discoveryPrefix, 64) || stripNonAscii((uint8_t*) config.discoveryHostname, 64) || stripNonAscii((uint8_t*) config.discoveryNameTag, 16)) {
clearHomeAssistantConfig(config);
return false;
}
return true;
} else {
@@ -512,7 +561,7 @@ bool AmsConfiguration::setGpioConfig(GpioConfig& config) {
return ret;
}
void AmsConfiguration::clearGpio(GpioConfig& config) {
void AmsConfiguration::clearGpio(GpioConfig& config, bool all) {
config.apPin = 0xFF;
config.ledPin = 0xFF;
config.ledInverted = true;
@@ -523,13 +572,16 @@ void AmsConfiguration::clearGpio(GpioConfig& config) {
config.tempSensorPin = 0xFF;
config.tempAnalogSensorPin = 0xFF;
config.vccPin = 0xFF;
config.vccOffset = 0;
config.vccMultiplier = 1000;
config.vccBootLimit = 0;
config.vccResistorGnd = 0;
config.vccResistorVcc = 0;
config.ledDisablePin = 0xFF;
config.ledBehaviour = LED_BEHAVIOUR_DEFAULT;
if(all) {
config.vccOffset = 0;
config.vccMultiplier = 1000;
config.vccBootLimit = 0;
config.vccResistorGnd = 0;
config.vccResistorVcc = 0;
config.ledBehaviour = LED_BEHAVIOUR_DEFAULT;
}
}
bool AmsConfiguration::getNtpConfig(NtpConfig& config) {
@@ -593,6 +645,7 @@ bool AmsConfiguration::getPriceServiceConfig(PriceServiceConfig& config) {
EEPROM.end();
if(strlen(config.entsoeToken) != 0 && strlen(config.entsoeToken) != 36) {
clearPriceServiceConfig(config);
return false;
}
return true;
} else {
@@ -648,6 +701,7 @@ bool AmsConfiguration::getEnergyAccountingConfig(EnergyAccountingConfig& config)
EEPROM.end();
if(config.thresholds[9] != 0xFFFF) {
clearEnergyAccountingConfig(config);
return false;
}
if(config.hours > 5) config.hours = 5;
return true;
@@ -705,7 +759,6 @@ bool AmsConfiguration::getUiConfig(UiConfig& config) {
EEPROM.begin(EEPROM_SIZE);
EEPROM.get(CONFIG_UI_START, config);
if(config.showImport > 2) clearUiConfig(config); // Must be wrong
if(config.showRealtimePlot > 2) config.showRealtimePlot = 1; // TODO: Move to new config version for v2.3
EEPROM.end();
return true;
} else {
@@ -756,13 +809,7 @@ void AmsConfiguration::ackUiLanguageChange() {
uiLanguageChanged = false;
}
bool AmsConfiguration::setUpgradeInformation(int16_t exitCode, int16_t errorCode, const char* currentVersion, const char* nextVersion) {
UpgradeInformation upinfo;
upinfo.exitCode = exitCode;
upinfo.errorCode = errorCode;
strcpy(upinfo.fromVersion, currentVersion);
strcpy(upinfo.toVersion, nextVersion);
bool AmsConfiguration::setUpgradeInformation(UpgradeInformation& upinfo) {
stripNonAscii((uint8_t*) upinfo.fromVersion, 8);
stripNonAscii((uint8_t*) upinfo.toVersion, 8);
@@ -780,6 +827,7 @@ bool AmsConfiguration::getUpgradeInformation(UpgradeInformation& upinfo) {
EEPROM.end();
if(stripNonAscii((uint8_t*) upinfo.fromVersion, 8) || stripNonAscii((uint8_t*) upinfo.toVersion, 8)) {
clearUpgradeInformation(upinfo);
return false;
}
return true;
} else {
@@ -789,10 +837,13 @@ bool AmsConfiguration::getUpgradeInformation(UpgradeInformation& upinfo) {
}
void AmsConfiguration::clearUpgradeInformation(UpgradeInformation& upinfo) {
upinfo.exitCode = -1;
upinfo.errorCode = 0;
memset(upinfo.fromVersion, 0, 8);
memset(upinfo.toVersion, 0, 8);
upinfo.errorCode = 0;
upinfo.size = 0;
upinfo.block_position = 0;
upinfo.retry_count = 0;
upinfo.reboot_count = 0;
}
bool AmsConfiguration::getCloudConfig(CloudConfig& config) {
@@ -800,6 +851,7 @@ bool AmsConfiguration::getCloudConfig(CloudConfig& config) {
EEPROM.begin(EEPROM_SIZE);
EEPROM.get(CONFIG_CLOUD_START, config);
EEPROM.end();
if(config.proto > 2) config.proto = 0;
return true;
} else {
clearCloudConfig(config);
@@ -813,6 +865,7 @@ bool AmsConfiguration::setCloudConfig(CloudConfig& config) {
cloudChanged |= config.enabled != existing.enabled;
cloudChanged |= config.interval!= existing.interval;
cloudChanged |= config.port!= existing.port;
cloudChanged |= config.proto!= existing.proto;
cloudChanged |= strcmp(config.hostname, existing.hostname) != 0;
cloudChanged |= memcmp(config.clientId, existing.clientId, 16) != 0;
} else {
@@ -831,6 +884,7 @@ bool AmsConfiguration::setCloudConfig(CloudConfig& config) {
void AmsConfiguration::clearCloudConfig(CloudConfig& config) {
config.enabled = false;
strcpy_P(config.hostname, PSTR("cloud.amsleser.no"));
config.proto = 1;
config.port = 7443;
config.interval = 10;
memset(config.clientId, 0, 16);
@@ -844,6 +898,65 @@ void AmsConfiguration::ackCloudConfig() {
cloudChanged = false;
}
bool AmsConfiguration::getZmartChargeConfig(ZmartChargeConfig& config) {
if(hasConfig()) {
EEPROM.begin(EEPROM_SIZE);
EEPROM.get(CONFIG_ZC_START, config);
EEPROM.end();
stripNonAscii((uint8_t*) config.token, 21);
stripNonAscii((uint8_t*) config.baseUrl, 64);
if(strlen(config.token) != 20 || !config.enabled) {
config.enabled = false;
memset(config.token, 0, 64);
memset(config.baseUrl, 0, 64);
}
if(strlen(config.baseUrl) == 0 || strncmp_P(config.baseUrl, PSTR("https"), 5) != 0) {
memset(config.baseUrl, 0, 64);
snprintf_P(config.baseUrl, 64, PSTR("https://main.zmartcharge.com/api"));
}
return true;
} else {
clearZmartChargeConfig(config);
return false;
}
}
bool AmsConfiguration::setZmartChargeConfig(ZmartChargeConfig& config) {
ZmartChargeConfig existing;
if(getZmartChargeConfig(existing)) {
zcChanged |= config.enabled != existing.enabled;
zcChanged |= strcmp(config.token, existing.token) != 0;
zcChanged |= strcmp(config.baseUrl, existing.baseUrl) != 0;
} else {
zcChanged = true;
}
stripNonAscii((uint8_t*) config.token, 21);
stripNonAscii((uint8_t*) config.baseUrl, 64);
if(strncmp_P(config.baseUrl, PSTR("https"), 5) != 0) {
memset(config.baseUrl, 0, 64);
}
EEPROM.begin(EEPROM_SIZE);
EEPROM.put(CONFIG_ZC_START, config);
bool ret = EEPROM.commit();
EEPROM.end();
return ret;
}
void AmsConfiguration::clearZmartChargeConfig(ZmartChargeConfig& config) {
config.enabled = false;
memset(config.token, 0, 21);
}
bool AmsConfiguration::isZmartChargeConfigChanged() {
return zcChanged;
}
void AmsConfiguration::ackZmartChargeConfig() {
zcChanged = false;
}
void AmsConfiguration::setUiLanguageChanged() {
uiLanguageChanged = true;
}
@@ -911,6 +1024,10 @@ void AmsConfiguration::clear() {
clearCloudConfig(cloud);
EEPROM.put(CONFIG_CLOUD_START, cloud);
ZmartChargeConfig zc;
clearZmartChargeConfig(zc);
EEPROM.put(CONFIG_ZC_START, zc);
EEPROM.put(EEPROM_CONFIG_ADDRESS, EEPROM_CLEARED_INDICATOR);
EEPROM.commit();
EEPROM.end();
@@ -930,22 +1047,6 @@ bool AmsConfiguration::hasConfig() {
}
} else {
switch(configVersion) {
case 101:
configVersion = -1; // Prevent loop
if(relocateConfig101()) {
configVersion = 102;
} else {
configVersion = 0;
return false;
}
case 102:
configVersion = -1; // Prevent loop
if(relocateConfig102()) {
configVersion = 103;
} else {
configVersion = 0;
return false;
}
case 103:
configVersion = -1; // Prevent loop
if(relocateConfig103()) {
@@ -968,48 +1069,6 @@ int AmsConfiguration::getConfigVersion() {
return configVersion;
}
bool AmsConfiguration::relocateConfig101() {
EEPROM.begin(EEPROM_SIZE);
EnergyAccountingConfig config;
EnergyAccountingConfig101 config101;
EEPROM.get(CONFIG_ENERGYACCOUNTING_START_103, config101);
for(uint8_t i = 0; i < 9; i++) {
config.thresholds[i] = config101.thresholds[i];
}
config.thresholds[9] = 0xFFFF;
config.hours = config101.hours;
EEPROM.put(CONFIG_ENERGYACCOUNTING_START_103, config);
EEPROM.put(EEPROM_CONFIG_ADDRESS, 102);
bool ret = EEPROM.commit();
EEPROM.end();
return ret;
}
bool AmsConfiguration::relocateConfig102() {
EEPROM.begin(EEPROM_SIZE);
GpioConfig103 gpioConfig;
EEPROM.get(CONFIG_GPIO_START_103, gpioConfig);
gpioConfig.hanPinPullup = true;
EEPROM.put(CONFIG_GPIO_START_103, gpioConfig);
HomeAssistantConfig haconf;
clearHomeAssistantConfig(haconf);
EEPROM.put(CONFIG_HA_START_103, haconf);
PriceServiceConfig entsoe;
EEPROM.get(CONFIG_ENTSOE_START_103, entsoe);
entsoe.unused2 = 0;
EEPROM.put(CONFIG_ENTSOE_START_103, entsoe);
EEPROM.put(EEPROM_CONFIG_ADDRESS, 103);
bool ret = EEPROM.commit();
EEPROM.end();
return ret;
}
bool AmsConfiguration::relocateConfig103() {
EEPROM.begin(EEPROM_SIZE);
@@ -1044,6 +1103,13 @@ bool AmsConfiguration::relocateConfig103() {
meter.rxPin = gpio103.hanPin;
meter.txPin = 0xFF;
meter.rxPinPullup = gpio103.hanPinPullup;
meter.source = 1;
meter.parser = 0;
#if defined(ESP8266)
if(meter.rxPin != 3 && meter.rxPin != 113) {
meter.bufferSize = 1;
}
#endif
wifi.mode = 1; // 1 == WiFi client
wifi.ipv6 = false;
@@ -1068,11 +1134,12 @@ bool AmsConfiguration::relocateConfig103() {
};
WebConfig web = {web103.security};
strcpy_P(web.username, web103.username);
strcpy_P(web.password, web103.password);
strcpy(web.username, web103.username);
strcpy(web.password, web103.password);
memset(web.context, 0, 37);
strcpy_P(ui.language, PSTR("en"));
ui.showRealtimePlot = 2;
ui.showPerPhasePower = 2;
ui.showPowerFactor = 2;
ui.darkMode = 2;
@@ -1095,6 +1162,10 @@ bool AmsConfiguration::relocateConfig103() {
clearCloudConfig(cloud);
EEPROM.put(CONFIG_CLOUD_START, cloud);
ZmartChargeConfig zcc;
clearZmartChargeConfig(zcc);
EEPROM.put(CONFIG_ZC_START, zcc);
EEPROM.put(EEPROM_CONFIG_ADDRESS, 104);
bool ret = EEPROM.commit();
EEPROM.end();
@@ -1103,6 +1174,7 @@ bool AmsConfiguration::relocateConfig103() {
bool AmsConfiguration::save() {
EEPROM.begin(EEPROM_SIZE);
uint8_t configVersion = EEPROM.read(EEPROM_CONFIG_ADDRESS);
EEPROM.put(EEPROM_CONFIG_ADDRESS, EEPROM_CHECK_SUM);
bool success = EEPROM.commit();
EEPROM.end();
@@ -1282,27 +1354,50 @@ void AmsConfiguration::print(Print* debugger)
debugger->printf_P(PSTR("Area: %s\r\n"), price.area);
debugger->printf_P(PSTR("Currency: %s\r\n"), price.currency);
debugger->printf_P(PSTR("ENTSO-E Token: %s\r\n"), price.entsoeToken);
debugger->println(F(""));
delay(10);
debugger->flush();
}
debugger->println(F(""));
delay(10);
debugger->flush();
}
UiConfig ui;
if(getUiConfig(ui)) {
debugger->println(F("--UI configuration--"));
debugger->printf_P(PSTR("Language: %s\r\n"), ui.language);
debugger->println(F(""));
delay(10);
debugger->flush();
}
#if defined(ESP32)
CloudConfig cc;
if(getCloudConfig(cc)) {
String uuid = ESPRandom::uuidToString(cc.clientId);;
debugger->println(F("--UI configuration--"));
debugger->println(F("--Cloud configuration--"));
debugger->printf_P(PSTR("Enabled: %s\r\n"), cc.enabled ? "Yes" : "No");
debugger->printf_P(PSTR("Hostname: %s\r\n"), cc.hostname);
debugger->printf_P(PSTR("Client ID: %s\r\n"), uuid.c_str());
debugger->printf_P(PSTR("Interval: %d\r\n"), cc.interval);
if(cc.enabled) {
debugger->printf_P(PSTR("Hostname: %s\r\n"), cc.hostname);
debugger->printf_P(PSTR("Client ID: %s\r\n"), uuid.c_str());
debugger->printf_P(PSTR("Interval: %d\r\n"), cc.interval);
}
debugger->println(F(""));
delay(10);
debugger->flush();
}
#endif
#if defined(ZMART_CHARGE)
ZmartChargeConfig zc;
if(getZmartChargeConfig(zc)) {
debugger->println(F("--ZmartCharge configuration--"));
debugger->printf_P(PSTR("Enabled: %s\r\n"), zc.enabled ? "Yes" : "No");
if(zc.enabled) {
debugger->printf_P(PSTR("Base URL: '%s'\r\n"), zc.baseUrl);
debugger->printf_P(PSTR("Token: '%s'\r\n"), zc.token);
}
debugger->println(F(""));
delay(10);
debugger->flush();
}
#endif

View File

@@ -28,14 +28,14 @@ void fromHex(uint8_t *out, String in, uint16_t size) {
}
}
bool stripNonAscii(uint8_t* in, uint16_t size, bool extended) {
bool stripNonAscii(uint8_t* in, uint16_t size, bool extended, bool trim) {
bool ret = false;
for(uint16_t i = 0; i < size; i++) {
if(in[i] == 0) { // Clear the rest with null-terminator
memset(in+i, 0, size-i);
break;
}
if(extended && (in[i] < 32 || in[i] == 127 || in[i] == 129 || in[i] == 141 || in[i] == 143 || in[i] == 144 || in[i] == 157)) {
if(extended && (in[i] < 32 || in[i] == 127 || in[i] == 129 || in[i] == 141 || in[i] == 143 || in[i] == 144 || in[i] == 157 || in[i] == 160)) {
memset(in+i, ' ', 1);
ret = true;
} else if(!extended && (in[i] < 32 || in[i] > 126)) {
@@ -43,6 +43,38 @@ bool stripNonAscii(uint8_t* in, uint16_t size, bool extended) {
ret = true;
}
}
if(trim) {
// Strip leading spaces
while(in[0] == ' ') {
for(uint16_t i = 0; i < size; i++) {
in[i] = in[i+1];
}
}
// Strip trailing spaces
for(int i = size-1; i > 0; i--) {
if(in[i] == ' ' || in[i] == 0) {
memset(in+i, 0, 1);
} else {
break;
}
}
}
memset(in+size-1, 0, 1); // Make sure the last character is null-terminator
return ret;
}
void debugPrint(uint8_t *buffer, uint16_t start, uint16_t length, Print* debugger) {
for (uint16_t i = start; i < start + length; i++) {
if (buffer[i] < 0x10)
debugger->print(F("0"));
debugger->print(buffer[i], HEX);
debugger->print(F(" "));
if ((i - start + 1) % 16 == 0)
debugger->println(F(""));
else if ((i - start + 1) % 4 == 0)
debugger->print(F(" "));
yield(); // Let other get some resources too
}
debugger->println(F(""));
}

View File

@@ -105,6 +105,7 @@ protected:
double l1activeExportCounter = 0, l2activeExportCounter = 0, l3activeExportCounter = 0;
float powerFactor = 0, l1PowerFactor = 0, l2PowerFactor = 0, l3PowerFactor = 0;
double activeImportCounter = 0, reactiveImportCounter = 0, activeExportCounter = 0, reactiveExportCounter = 0;
double lastKnownCounter = 0;
bool threePhase = false, twoPhase = false, counterEstimated = false, l2currentMissing = false;;
int8_t lastError = 0x00;

View File

@@ -59,6 +59,10 @@ const OBIS_code_t OBIS_POWER_FACTOR PROGMEM = { 13, 7, 0 };
const OBIS_code_t OBIS_ACTIVE_IMPORT_L1 PROGMEM = { 21, 7, 0 };
const OBIS_code_t OBIS_ACTIVE_EXPORT_L1 PROGMEM = { 22, 7, 0 };
const OBIS_code_t OBIS_REACTIVE_IMPORT_L1 PROGMEM = { 23, 7, 0 };
const OBIS_code_t OBIS_REACTIVE_EXPORT_L1 PROGMEM = { 24, 7, 0 };
const OBIS_code_t OBIS_ACTIVE_IMPORT_COUNT_L1 PROGMEM ={ 21, 8, 0 };
const OBIS_code_t OBIS_REACTIVE_IMPORT_COUNT_L1 PROGMEM ={ 22, 8, 0 };
const OBIS_code_t OBIS_CURRENT_L1 PROGMEM = { 31, 7, 0 };
const OBIS_code_t OBIS_VOLTAGE_L1 PROGMEM = { 32, 7, 0 };
@@ -66,6 +70,10 @@ const OBIS_code_t OBIS_POWER_FACTOR_L1 PROGMEM = { 33, 7, 0 };
const OBIS_code_t OBIS_ACTIVE_IMPORT_L2 PROGMEM = { 41, 7, 0 };
const OBIS_code_t OBIS_ACTIVE_EXPORT_L2 PROGMEM = { 42, 7, 0 };
const OBIS_code_t OBIS_REACTIVE_IMPORT_L2 PROGMEM = { 43, 7, 0 };
const OBIS_code_t OBIS_REACTIVE_EXPORT_L2 PROGMEM = { 44, 7, 0 };
const OBIS_code_t OBIS_ACTIVE_IMPORT_COUNT_L2 PROGMEM ={ 41, 8, 0 };
const OBIS_code_t OBIS_REACTIVE_IMPORT_COUNT_L2 PROGMEM ={ 42, 8, 0 };
const OBIS_code_t OBIS_CURRENT_L2 PROGMEM = { 51, 7, 0 };
const OBIS_code_t OBIS_VOLTAGE_L2 PROGMEM = { 52, 7, 0 };
@@ -73,6 +81,10 @@ const OBIS_code_t OBIS_POWER_FACTOR_L2 PROGMEM = { 53, 7, 0 };
const OBIS_code_t OBIS_ACTIVE_IMPORT_L3 PROGMEM = { 61, 7, 0 };
const OBIS_code_t OBIS_ACTIVE_EXPORT_L3 PROGMEM = { 62, 7, 0 };
const OBIS_code_t OBIS_REACTIVE_IMPORT_L3 PROGMEM = { 63, 7, 0 };
const OBIS_code_t OBIS_REACTIVE_EXPORT_L3 PROGMEM = { 64, 7, 0 };
const OBIS_code_t OBIS_ACTIVE_IMPORT_COUNT_L3 PROGMEM ={ 61, 8, 0 };
const OBIS_code_t OBIS_REACTIVE_IMPORT_COUNT_L3 PROGMEM ={ 62, 8, 0 };
const OBIS_code_t OBIS_CURRENT_L3 PROGMEM = { 71, 7, 0 };
const OBIS_code_t OBIS_VOLTAGE_L3 PROGMEM = { 72, 7, 0 };

View File

@@ -69,10 +69,23 @@ void AmsData::apply(AmsData& other) {
this->l3activeExportCounter = other.getL3ActiveExportCounter();
case 3:
this->meterTimestamp = other.getMeterTimestamp();
this->activeImportCounter = other.getActiveImportCounter();
this->activeExportCounter = other.getActiveExportCounter();
this->reactiveImportCounter = other.getReactiveImportCounter();
this->reactiveExportCounter = other.getReactiveExportCounter();
// Aidon tends to sometime send the same counter as last hour by accident
if(meterType == AmsTypeAidon && counterEstimated && lastKnownCounter == other.getActiveImportCounter()-other.getActiveExportCounter()) {
double diff = activeImportCounter - activeExportCounter - lastKnownCounter;
if(diff < 1.0) { // In case a very low value have been calculated, use the new values
this->activeImportCounter = other.getActiveImportCounter();
this->activeExportCounter = other.getActiveExportCounter();
this->reactiveImportCounter = other.getReactiveImportCounter();
this->reactiveExportCounter = other.getReactiveExportCounter();
this->lastKnownCounter = activeImportCounter - activeExportCounter;
}
} else {
this->activeImportCounter = other.getActiveImportCounter();
this->activeExportCounter = other.getActiveExportCounter();
this->reactiveImportCounter = other.getReactiveImportCounter();
this->reactiveExportCounter = other.getReactiveExportCounter();
this->lastKnownCounter = activeImportCounter - activeExportCounter;
}
this->counterEstimated = false;
case 2:
this->listId = other.getListId();
@@ -100,88 +113,156 @@ void AmsData::apply(AmsData& other) {
}
void AmsData::apply(OBIS_code_t obis, double value) {
switch(obis.gr) {
case 1:
switch(obis.sensor) {
case 7:
switch(obis.tariff) {
case 0:
activeImportPower = value;
listType = max(listType, (uint8_t) 1);
break;
}
break;
case 8:
switch(obis.tariff) {
case 0:
activeImportCounter = value;
listType = max(listType, (uint8_t) 3);
break;
}
break;
}
break;
case 2:
switch(obis.sensor) {
case 7:
switch(obis.tariff) {
case 0:
activeExportPower = value;
listType = max(listType, (uint8_t) 2);
break;
}
break;
case 8:
switch(obis.tariff) {
case 0:
activeExportCounter = value;
listType = max(listType, (uint8_t) 3);
break;
}
break;
}
break;
case 3:
switch(obis.sensor) {
case 7:
switch(obis.tariff) {
case 0:
reactiveImportPower = value;
listType = max(listType, (uint8_t) 2);
break;
}
break;
case 8:
switch(obis.tariff) {
case 0:
reactiveImportCounter = value;
listType = max(listType, (uint8_t) 3);
break;
}
break;
}
break;
case 4:
switch(obis.sensor) {
case 7:
switch(obis.tariff) {
case 0:
reactiveExportPower = value;
listType = max(listType, (uint8_t) 2);
break;
}
break;
case 8:
switch(obis.tariff) {
case 0:
reactiveExportCounter = value;
listType = max(listType, (uint8_t) 3);
break;
}
break;
}
break;
if(obis.sensor == 0 && obis.gr == 0 && obis.tariff == 0) {
meterType = value;
}
if(obis.gr == 1) {
if(obis.sensor == 96) {
if(obis.tariff == 0) {
meterId = String((long) value, 10);
return;
} else if(obis.tariff == 1) {
return;
}
}
}
if(obis.tariff != 0) {
Serial.println("Tariff not implemented");
return;
}
if(obis.gr == 7) { // Instant values
switch(obis.sensor) {
case 1:
activeImportPower = value;
listType = max(listType, (uint8_t) 2);
break;
case 2:
activeExportPower = value;
listType = max(listType, (uint8_t) 2);
break;
case 3:
reactiveImportPower = value;
listType = max(listType, (uint8_t) 2);
break;
case 4:
reactiveExportPower = value;
listType = max(listType, (uint8_t) 2);
break;
case 13:
powerFactor = value;
listType = max(listType, (uint8_t) 4);
break;
case 21:
l1activeImportPower = value;
listType = max(listType, (uint8_t) 4);
break;
case 22:
l1activeExportPower = value;
listType = max(listType, (uint8_t) 4);
break;
case 31:
l1current = value;
listType = max(listType, (uint8_t) 2);
break;
case 32:
l1voltage = value;
listType = max(listType, (uint8_t) 2);
break;
case 33:
l1PowerFactor = value;
listType = max(listType, (uint8_t) 4);
break;
case 41:
l2activeImportPower = value;
listType = max(listType, (uint8_t) 4);
break;
case 42:
l2activeExportPower = value;
listType = max(listType, (uint8_t) 4);
break;
case 51:
l2current = value;
listType = max(listType, (uint8_t) 2);
break;
case 52:
l2voltage = value;
listType = max(listType, (uint8_t) 2);
break;
case 53:
l2PowerFactor = value;
listType = max(listType, (uint8_t) 4);
break;
case 61:
l3activeImportPower = value;
listType = max(listType, (uint8_t) 4);
break;
case 62:
l3activeExportPower = value;
listType = max(listType, (uint8_t) 4);
break;
case 71:
l3current = value;
listType = max(listType, (uint8_t) 2);
break;
case 72:
l3voltage = value;
listType = max(listType, (uint8_t) 2);
break;
case 73:
l3PowerFactor = value;
listType = max(listType, (uint8_t) 4);
break;
}
} else if(obis.gr == 8) { // Accumulated values
switch(obis.sensor) {
case 1:
activeImportCounter = value;
listType = max(listType, (uint8_t) 3);
break;
case 2:
activeExportCounter = value;
listType = max(listType, (uint8_t) 3);
break;
case 3:
reactiveImportCounter = value;
listType = max(listType, (uint8_t) 3);
break;
case 4:
reactiveExportCounter = value;
listType = max(listType, (uint8_t) 3);
break;
case 21:
l1activeImportCounter = value;
listType = max(listType, (uint8_t) 4);
break;
case 22:
l1activeExportCounter = value;
listType = max(listType, (uint8_t) 4);
break;
case 41:
l2activeImportCounter = value;
listType = max(listType, (uint8_t) 4);
break;
case 42:
l2activeExportCounter = value;
listType = max(listType, (uint8_t) 4);
break;
case 61:
l3activeImportCounter = value;
listType = max(listType, (uint8_t) 4);
break;
case 62:
l3activeExportCounter = value;
listType = max(listType, (uint8_t) 4);
break;
}
}
if(listType > 0)
lastUpdateMillis = millis();
threePhase = l1voltage > 0 && l2voltage > 0 && l3voltage > 0;
if(!threePhase)
twoPhase = (l1voltage > 0 && l2voltage > 0) || (l2voltage > 0 && l3voltage > 0) || (l3voltage > 0 && l1voltage > 0);
}
uint64_t AmsData::getLastUpdateMillis() {

View File

@@ -8,7 +8,9 @@
#define _AMSDATASTORAGE_H
#include "Arduino.h"
#include "AmsData.h"
#if defined(AMS_REMOTE_DEBUG)
#include "RemoteDebug.h"
#endif
#include "Timezone.h"
struct DayDataPoints5 {
@@ -53,9 +55,13 @@ struct MonthDataPoints {
class AmsDataStorage {
public:
#if defined(AMS_REMOTE_DEBUG)
AmsDataStorage(RemoteDebug*);
#else
AmsDataStorage(Stream*);
#endif
void setTimezone(Timezone*);
bool update(AmsData*);
bool update(AmsData* data, time_t now);
uint32_t getHourImport(uint8_t);
uint32_t getHourExport(uint8_t);
uint32_t getDayImport(uint8_t);
@@ -73,12 +79,17 @@ public:
uint8_t getMonthAccuracy();
void setMonthAccuracy(uint8_t);
bool isHappy();
bool isDayHappy();
bool isMonthHappy();
bool isHappy(time_t now);
bool isDayHappy(time_t now);
bool isMonthHappy(time_t now);
double getEstimatedImportCounter();
void setHourImport(uint8_t, uint32_t);
void setHourExport(uint8_t, uint32_t);
void setDayImport(uint8_t, uint32_t);
void setDayExport(uint8_t, uint32_t);
private:
Timezone* tz;
DayDataPoints day = {
@@ -95,11 +106,11 @@ private:
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
10
};
#if defined(AMS_REMOTE_DEBUG)
RemoteDebug* debugger;
void setHourImport(uint8_t, uint32_t);
void setHourExport(uint8_t, uint32_t);
void setDayImport(uint8_t, uint32_t);
void setDayExport(uint8_t, uint32_t);
#else
Stream* debugger;
#endif
};
#endif

View File

@@ -10,7 +10,11 @@
#include "AmsStorage.h"
#include "FirmwareVersion.h"
#if defined(AMS_REMOTE_DEBUG)
AmsDataStorage::AmsDataStorage(RemoteDebug* debugger) {
#else
AmsDataStorage::AmsDataStorage(Stream* debugger) {
#endif
day.version = 6;
day.accuracy = 1;
month.version = 7;
@@ -22,13 +26,20 @@ void AmsDataStorage::setTimezone(Timezone* tz) {
this->tz = tz;
}
bool AmsDataStorage::update(AmsData* data) {
if(isHappy()) {
bool AmsDataStorage::update(AmsData* data, time_t now) {
if(isHappy(now)) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Happy, not updating\n"));
return false;
}
time_t now = time(nullptr);
if(tz == NULL) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("No timezone, not updating\n"));
return false;
}
if(now < FirmwareVersion::BuildEpoch) {
@@ -39,6 +50,10 @@ bool AmsDataStorage::update(AmsData* data) {
}
}
if(now < FirmwareVersion::BuildEpoch) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Before build time, not updating\n"));
return false;
}
@@ -52,11 +67,31 @@ bool AmsDataStorage::update(AmsData* data) {
uint64_t exportCounter = data->getActiveExportCounter() * 1000;
// Clear hours between last update and now
if(day.lastMeterReadTime > now) {
if(!isDayHappy(now) && day.lastMeterReadTime > now) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Day was updated in the future, resetting\n"));
day.activeImport = importCounter;
day.activeExport = exportCounter;
day.lastMeterReadTime = now;
} else if((importCounter > 0 && day.activeImport == 0) || now - day.lastMeterReadTime > 86400) {
} else if(importCounter > 0 && day.activeImport == 0) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Initializing day data\n"));
day.activeImport = importCounter;
day.activeExport = exportCounter;
day.lastMeterReadTime = now;
for(int i = 0; i<24; i++) {
setHourImport(i, 0);
setHourExport(i, 0);
}
} else if(now - day.lastMeterReadTime > 86400) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Day was updated to long ago, clearing\n"));
day.activeImport = importCounter;
day.activeExport = exportCounter;
day.lastMeterReadTime = now;
@@ -68,6 +103,10 @@ bool AmsDataStorage::update(AmsData* data) {
tmElements_t last;
breakTime(day.lastMeterReadTime, last);
uint8_t endHour = utc.Hour;
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Clearing hours from %d to %d\n"), last.Hour, endHour);
if(last.Hour > utc.Hour){
for(int i = 0; i < utc.Hour; i++) {
setHourImport(i, 0);
@@ -82,11 +121,31 @@ bool AmsDataStorage::update(AmsData* data) {
}
// Clear days between last update and now
if(month.lastMeterReadTime > now) {
if(!isMonthHappy(now) && month.lastMeterReadTime > now) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Month was updated in the future, resetting\n"));
month.activeImport = importCounter;
month.activeExport = exportCounter;
month.lastMeterReadTime = now;
} else if((importCounter > 0 && month.activeImport == 0) || now - month.lastMeterReadTime > 2682000) {
} else if(importCounter > 0 && month.activeImport == 0) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Initializing month data\n"));
month.activeImport = importCounter;
month.activeExport = exportCounter;
month.lastMeterReadTime = now;
for(int i = 1; i<=31; i++) {
setDayImport(i, 0);
setDayExport(i, 0);
}
} else if(now - month.lastMeterReadTime > 2682000) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Month was updated to long ago, clearing\n"));
month.activeImport = importCounter;
month.activeExport = exportCounter;
month.lastMeterReadTime = now;
@@ -98,6 +157,10 @@ bool AmsDataStorage::update(AmsData* data) {
tmElements_t last;
breakTime(tz->toLocal(month.lastMeterReadTime), last);
uint8_t endDay = ltz.Day;
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Clearing days from %d to %d\n"), last.Day, endDay);
if(last.Day > ltz.Day) {
for(int i = 1; i < ltz.Day; i++) {
setDayImport(i, 0);
@@ -112,20 +175,36 @@ bool AmsDataStorage::update(AmsData* data) {
}
if(data->getListType() < 3) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Type %d, not updating\n"), data->getListType());
return false;
}
bool ret = false;
// Update day plot
if(!isDayHappy()) {
if(!isDayHappy(now)) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Day is not happy\n"));
if(day.activeImport > importCounter || day.activeExport > exportCounter) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR(" - reset\n"));
day.activeImport = importCounter;
day.activeExport = exportCounter;
day.lastMeterReadTime = now;
setHourImport(utcYesterday.Hour, 0);
setHourExport(utcYesterday.Hour, 0);
} else if(now - day.lastMeterReadTime < 4000) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR(" - normal\n"));
uint32_t imp = importCounter - day.activeImport;
uint32_t exp = exportCounter - day.activeExport;
setHourImport(utcYesterday.Hour, imp);
@@ -135,6 +214,10 @@ bool AmsDataStorage::update(AmsData* data) {
day.activeExport = exportCounter;
day.lastMeterReadTime = now;
} else {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR(" - average\n"));
float mins = (now - day.lastMeterReadTime) / 60.0;
uint32_t im = importCounter - day.activeImport;
uint32_t ex = exportCounter - day.activeExport;
@@ -165,14 +248,26 @@ bool AmsDataStorage::update(AmsData* data) {
}
// Update month plot
if(ltz.Hour == 0 && !isMonthHappy()) {
if(ltz.Hour == 0 && !isMonthHappy(now)) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Month is not happy\n"));
if(month.activeImport > importCounter || month.activeExport > exportCounter) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR(" - reset\n"));
month.activeImport = importCounter;
month.activeExport = exportCounter;
month.lastMeterReadTime = now;
setDayImport(ltzYesterDay.Day, 0);
setDayExport(ltzYesterDay.Day, 0);
} else if(now - month.lastMeterReadTime < 90100 && now - month.lastMeterReadTime > 82700) { // DST days are 23h (82800s) and 25h (90000)
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR(" - normal\n"));
uint32_t imp = importCounter - month.activeImport;
uint32_t exp = exportCounter - month.activeExport;
@@ -182,6 +277,10 @@ bool AmsDataStorage::update(AmsData* data) {
month.activeExport = exportCounter;
month.lastMeterReadTime = now;
} else {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR(" - average\n"));
// Make sure last month read is at midnight
tmElements_t last;
breakTime(tz->toLocal(month.lastMeterReadTime), last);
@@ -531,22 +630,23 @@ void AmsDataStorage::setMonthAccuracy(uint8_t accuracy) {
month.accuracy = accuracy;
}
bool AmsDataStorage::isHappy() {
return isDayHappy() && isMonthHappy();
bool AmsDataStorage::isHappy(time_t now) {
return isDayHappy(now) && isMonthHappy(now);
}
bool AmsDataStorage::isDayHappy() {
bool AmsDataStorage::isDayHappy(time_t now) {
if(tz == NULL) {
return false;
}
time_t now = time(nullptr);
if(now < FirmwareVersion::BuildEpoch) return false;
if(now < day.lastMeterReadTime) {
return false;
}
if(now-day.lastMeterReadTime > 3600) {
// There are cases where the meter reports before the hour. The update method will then receive the meter timestamp as reference, thus there will not be 3600s between.
// Leaving a 100s buffer for these cases
if(now-day.lastMeterReadTime > 3500) {
return false;
}
@@ -560,26 +660,26 @@ bool AmsDataStorage::isDayHappy() {
return true;
}
bool AmsDataStorage::isMonthHappy() {
bool AmsDataStorage::isMonthHappy(time_t now) {
if(tz == NULL) {
return false;
}
time_t now = time(nullptr);
if(now < FirmwareVersion::BuildEpoch) return false;
tmElements_t tm, last;
if(now < month.lastMeterReadTime) {
return false;
}
breakTime(tz->toLocal(now), tm);
breakTime(tz->toLocal(month.lastMeterReadTime), last);
if(tm.Day != last.Day) {
// 25 hours, because of DST
if(now-month.lastMeterReadTime > 90000) {
return false;
}
if(now-month.lastMeterReadTime > 90100) {
tmElements_t tm, last;
breakTime(tz->toLocal(now), tm);
breakTime(tz->toLocal(month.lastMeterReadTime), last);
if(tm.Day != last.Day) {
return false;
}

View File

@@ -14,14 +14,18 @@ enum CosemType {
CosemTypeNull = 0x00,
CosemTypeArray = 0x01,
CosemTypeStructure = 0x02,
CosemTypeBoolean = 0x03,
CosemTypeOctetString = 0x09,
CosemTypeString = 0x0A,
CosemTypeDLongSigned = 0x05,
CosemTypeDLongUnsigned = 0x06,
CosemTypeSigned = 0x0F,
CosemTypeLongSigned = 0x10,
CosemTypeUnsigned = 0x11,
CosemTypeLongUnsigned = 0x12,
CosemTypeLong64Signed = 0x14,
CosemTypeLong64Unsigned = 0x15,
CosemTypeEnum = 0x16,
CosemTypeDateTime = 0x19
};

View File

@@ -24,7 +24,7 @@
#define DATA_PARSE_OK 0
#define DATA_PARSE_FAIL -1
#define DATA_PARSE_INCOMPLETE -2
#define DATA_PARSE_BOUNDRY_FLAG_MISSING -3
#define DATA_PARSE_BOUNDARY_FLAG_MISSING -3
#define DATA_PARSE_HEADER_CHECKSUM_ERROR -4
#define DATA_PARSE_FOOTER_CHECKSUM_ERROR -5
#define DATA_PARSE_INTERMEDIATE_SEGMENT -6

View File

@@ -9,15 +9,19 @@
#include "Arduino.h"
#include "DataParser.h"
#include "GcmParser.h"
class DSMRParser {
public:
int8_t parse(uint8_t *buf, DataParserContext &ctx, bool verified);
DSMRParser(GCMParser* gcmParser) { this->gcmParser = gcmParser; };
int8_t parse(uint8_t *buf, DataParserContext &ctx, bool verified, Print* debugger);
uint16_t getCrc();
uint16_t getCrcCalc();
private:
uint16_t crc;
uint16_t crc_calc;
GCMParser* gcmParser;
};
#endif

View File

@@ -18,7 +18,7 @@
class GCMParser {
public:
GCMParser(uint8_t *encryption_key, uint8_t *authentication_key);
int8_t parse(uint8_t *buf, DataParserContext &ctx);
int8_t parse(uint8_t *buf, DataParserContext &ctx, bool hastag = true);
private:
uint8_t encryption_key[16];
uint8_t authentication_key[16];

View File

@@ -12,6 +12,5 @@
uint16_t crc16(const uint8_t* p, int len);
uint16_t crc16_x25(const uint8_t* p, int len);
uint16_t crc16_1021(const uint8_t* p, int len);
#endif

View File

@@ -19,12 +19,10 @@ time_t decodeCosemDateTime(CosemDateTime timestamp) {
tm.Minute = timestamp.minute;
tm.Second = timestamp.second;
//Serial.printf("\nY: %d, M: %d, D: %d, h: %d, m: %d, s: %d, deviation: 0x%2X, status: 0x%1X\n", tm.Year, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second, timestamp.deviation, timestamp.status);
time_t time = makeTime(tm);
int16_t deviation = ntohs(timestamp.deviation);
if(deviation >= -720 && deviation <= 720) {
time -= deviation * 60;
time += deviation * 60;
}
return time;
}

View File

@@ -9,27 +9,77 @@
#include "hexutils.h"
#include "lwip/def.h"
int8_t DSMRParser::parse(uint8_t *buf, DataParserContext &ctx, bool verified) {
// verified indicates that this data was encapsulated in something else, so we know this has the correct size etc
int8_t DSMRParser::parse(uint8_t *buf, DataParserContext &ctx, bool verified, Print* debugger) {
uint16_t lenBefore = ctx.length;
uint16_t crcPos = 0;
bool reachedEnd = verified;
uint8_t lastByte = 0x00;
for(int pos = 0; pos < ctx.length; pos++) {
for(uint16_t pos = 0; pos < ctx.length; pos++) {
uint8_t b = *(buf+pos);
if(pos == 0 && b != '/') return DATA_PARSE_BOUNDRY_FLAG_MISSING;
if(pos > 0 && b == '!' && lastByte == '\n') crcPos = pos+1;
if(crcPos > 0 && b == '\n') reachedEnd = true;
if(pos == 0 && b != '/') return DATA_PARSE_BOUNDARY_FLAG_MISSING;
if(pos > 0 && b == '!') crcPos = pos+1;
if(crcPos > 0 && b == 0x0A && lastByte == 0x0D) {
reachedEnd = true;
ctx.length = pos;
break;
}
lastByte = b;
}
if(!reachedEnd) return DATA_PARSE_INCOMPLETE;
buf[ctx.length+1] = '\0';
if(crcPos > 0) {
// If we expect data to be encrypted and it was not previously verified, decrypt content
if(gcmParser != NULL && !verified) {
uint8_t* ptr = (uint8_t*) buf;
while(*ptr != 0x0D && *ptr != 0x0A) ptr++;
while(*ptr == 0x0D || *ptr == 0x0A) ptr++;
uint16_t pos = ptr-buf;
DataParserContext gcmCtx = {
DATA_TAG_GCM,
crcPos - pos - 1,
ctx.timestamp
};
if(debugger != NULL) {
debugger->printf_P(PSTR("DSMR wants to decrypt at position %lu, length: %d, payload:\n"), pos, gcmCtx.length);
debugPrint(ptr, 0, gcmCtx.length, debugger);
}
int8_t gcmRet = gcmParser->parse(ptr, gcmCtx, false);
if(gcmRet < 0) {
if(debugger != NULL) {
debugger->printf_P(PSTR(" - Failed! (%d)\n"), gcmRet);
}
return gcmRet;
} else {
if(debugger != NULL) {
debugger->printf_P(PSTR(" - Success! (%d)\n"), gcmRet);
}
ptr += gcmRet;
for(uint16_t i = 0; i < gcmCtx.length; i++) {
buf[pos++] = ptr[i];
}
ptr = buf + crcPos - 1;
crcPos = pos + 1;
while(*ptr != '\0') {
ctx.length = pos;
buf[pos++] = *(ptr++);
}
while(pos < lenBefore) {
buf[pos++] = '\0';
}
}
} else if(crcPos > 0) {
crc_calc = crc16(buf, crcPos);
crc = 0x0000;
fromHex((uint8_t*) &crc, String((char*) buf+crcPos), 2);
crc = ntohs(crc);
if(crc != crc_calc)
if(crc != crc_calc) {
if(debugger != NULL) {
debugger->printf_P(PSTR("CRC incorrrect, %04X != %04X at position %lu\n"), crc, crc_calc, crcPos);
}
return DATA_PARSE_FOOTER_CHECKSUM_ERROR;
}
}
return DATA_PARSE_OK;
}

View File

@@ -11,7 +11,7 @@ int8_t GBTParser::parse(uint8_t *d, DataParserContext &ctx) {
GBTHeader* h = (GBTHeader*) (d);
uint16_t sequence = ntohs(h->sequence);
if(h->flag != GBT_TAG) return DATA_PARSE_BOUNDRY_FLAG_MISSING;
if(h->flag != GBT_TAG) return DATA_PARSE_BOUNDARY_FLAG_MISSING;
if(sequence == 1) {
if(buf == NULL) buf = (uint8_t *)malloc((size_t)1024); // TODO find out from first package ?

View File

@@ -17,25 +17,34 @@ GCMParser::GCMParser(uint8_t *encryption_key, uint8_t *authentication_key) {
memcpy(this->authentication_key, authentication_key, 16);
}
int8_t GCMParser::parse(uint8_t *d, DataParserContext &ctx) {
int8_t GCMParser::parse(uint8_t *d, DataParserContext &ctx, bool hastag) {
if(ctx.length < 12) return DATA_PARSE_INCOMPLETE;
uint32_t headersize = 0;
uint8_t* ptr = (uint8_t*) d;
if(*ptr != GCM_TAG) return DATA_PARSE_BOUNDRY_FLAG_MISSING;
ptr++;
if(hastag) {
if(*ptr != GCM_TAG) return DATA_PARSE_BOUNDARY_FLAG_MISSING;
ptr++;
headersize++;
}
// Encrypted APDU
// http://www.weigu.lu/tutorials/sensors2bus/04_encryption/index.html
uint8_t systemTitleLength = *ptr;
ptr++;
headersize++;
uint8_t initialization_vector[12];
memcpy(ctx.system_title, ptr, systemTitleLength);
memcpy(initialization_vector, ctx.system_title, systemTitleLength);
memset(ctx.system_title, 0, 8);
memset(initialization_vector, 0, 12);
if(systemTitleLength > 0) {
memcpy(ctx.system_title, ptr, systemTitleLength);
memcpy(initialization_vector, ctx.system_title, systemTitleLength);
ptr += systemTitleLength;
headersize += systemTitleLength;
}
uint32_t len = 0;
uint32_t headersize = 2 + systemTitleLength;
ptr += systemTitleLength;
if(((*ptr) & 0xFF) == 0x81) {
// 1-byte payload length
ptr++;

View File

@@ -29,7 +29,7 @@ int8_t HDLCParser::parse(uint8_t *d, DataParserContext &ctx) {
// First and last byte should be HDLC_FLAG
if(h->flag != HDLC_FLAG || f->flag != HDLC_FLAG)
return DATA_PARSE_BOUNDRY_FLAG_MISSING;
return DATA_PARSE_BOUNDARY_FLAG_MISSING;
// Verify FCS
if(ntohs(f->fcs) != crc16_x25(d + 1, len - sizeof *f - 1))

View File

@@ -19,7 +19,7 @@ int8_t MBUSParser::parse(uint8_t *d, DataParserContext &ctx) {
MbusHeader* mh = (MbusHeader*) d;
if(mh->flag1 != MBUS_START || mh->flag2 != MBUS_START)
return DATA_PARSE_BOUNDRY_FLAG_MISSING;
return DATA_PARSE_BOUNDARY_FLAG_MISSING;
// First two bytes is 1-byte length value repeated. Only used for last segment
if(mh->len1 != mh->len2)
@@ -40,7 +40,7 @@ int8_t MBUSParser::parse(uint8_t *d, DataParserContext &ctx) {
MbusFooter* mf = (MbusFooter*) (d + len + headersize);
if(mf->flag != MBUS_END)
return DATA_PARSE_BOUNDRY_FLAG_MISSING;
return DATA_PARSE_BOUNDARY_FLAG_MISSING;
if(checksum(d + headersize, len) != mf->fcs)
return DATA_PARSE_FOOTER_CHECKSUM_ERROR;

View File

@@ -33,22 +33,3 @@ uint16_t crc16 (const uint8_t *p, int len) {
return crc;
}
uint16_t crc16_1021(const uint8_t *p, int len) {
uint32_t crc = 0x0000;
for(int i = 0; i < len; i++) {
int mask = 0x80;
while(mask > 0) {
crc <<= 1;
if (p[i] & mask){
crc |= 1;
}
mask>>=1;
if (crc & 0x10000) {
crc &= 0xffff;
crc ^= 0x1021;
}
}
}
return crc;
}

View File

@@ -0,0 +1,143 @@
#pragma once
#include <stdint.h>
#include <Print.h>
#include "HwTools.h"
#include "AmsData.h"
#include "AmsConfiguration.h"
#if defined(ESP32)
#include "esp_flash_partitions.h"
#include "LittleFS.h"
#include "WiFi.h"
#include "HTTPClient.h"
#define AMS_PARTITION_TABLE_OFFSET 0x8000
#define AMS_PARTITION_APP0_OFFSET 0x10000
#define AMS_PARTITION_APP_SIZE 0x1D0000
#define AMS_PARTITION_MIN_SPIFFS_SIZE 0x20000
#elif defined(ESP8266)
#include <ESP8266HTTPClient.h>
#define AMS_FLASH_SKETCH_SIZE 0xFEFF0
#define AMS_FLASH_OTA_START AMS_FLASH_OTA_SIZE
#endif
#if defined(AMS_REMOTE_DEBUG)
#include "RemoteDebug.h"
#endif
#define AMS_UPDATE_ERR_OK 0
#define AMS_UPDATE_ERR_DETAILS 1
#define AMS_UPDATE_ERR_FETCH 2
#define AMS_UPDATE_ERR_ERASE 3
#define AMS_UPDATE_ERR_WRITE 4
#define AMS_UPDATE_ERR_READ 5
#define AMS_UPDATE_ERR_MD5 6
#define AMS_UPDATE_ERR_ACTIVATE 7
#define AMS_UPDATE_ERR_REBOOT 64
#define AMS_UPDATE_ERR_SUCCESS_SIGNAL 122
#define AMS_UPDATE_ERR_SUCCESS_CONFIRMED 123
#define UPDATE_BUF_SIZE 4096
class AmsFirmwareUpdater {
public:
#if defined(AMS_REMOTE_DEBUG)
AmsFirmwareUpdater(RemoteDebug* debugger, HwTools* hw, AmsData* meterState);
#else
AmsFirmwareUpdater(Print* debugger, HwTools* hw, AmsData* meterState);
#endif
bool relocateOrRepartitionIfNecessary();
void loop();
char* getNextVersion();
bool setTargetVersion(const char* version);
void getUpgradeInformation(UpgradeInformation&);
float getProgress();
bool activateDownloadedFirmware();
void setUpgradeInformation(UpgradeInformation&);
bool isUpgradeInformationChanged();
void ackUpgradeInformationChanged();
bool startFirmwareUpload(uint32_t size, const char* version);
bool addFirmwareUploadChunk(uint8_t* buf, size_t length);
bool completeFirmwareUpload(uint32_t size);
private:
#if defined(ESP8266)
char chipType[10] = "esp8266";
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
char chipType[10] = "esp32s2";
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
char chipType[10] = "esp32s3";
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
char chipType[10] = "esp32c3";
#elif defined(ESP32)
#if defined(CONFIG_FREERTOS_UNICORE)
char chipType[10] = "esp32solo";
#else
char chipType[10] = "esp32";
#endif
#endif
#if defined(AMS_REMOTE_DEBUG)
RemoteDebug* debugger;
#else
Print* debugger;
#endif
HwTools* hw;
AmsData* meterState;
bool updateStatusChanged = false;
UpgradeInformation updateStatus = {"","",0,0,0,0,0};
uint16_t lastSaveBlocksWritten = 0;
String md5;
uint32_t lastVersionCheck = 0;
uint8_t firmwareVariant;
bool autoUpgrade;
char nextVersion[10];
bool fetchNextVersion();
bool fetchVersionDetails();
bool fetchFirmwareChunk(HTTPClient& http);
bool writeBufferToFlash();
bool verifyChecksum();
bool activateNewFirmware();
bool writeUpdateStatus();
bool isFlashReadyForNextUpdateVersion(uint32_t size);
uint8_t* buf = NULL;
uint16_t bufPos = 0;
#if defined(ESP32)
bool readPartition(uint8_t num, const esp_partition_info_t* info);
bool writePartition(uint8_t num, const esp_partition_info_t* info);
bool copyData(const esp_partition_info_t* src, esp_partition_info_t* dst, bool eraseFirst=true);
bool copyFile(fs::LittleFSFS* src, fs::LittleFSFS* dst, const char* filename);
uint8_t* extractFileData(const char* filename, size_t& size);
void saveFileData(const char* filename, uint8_t* data, size_t size);
bool relocateAppToFirst(const esp_partition_t* active);
bool findPartition(const char* label, const esp_partition_info_t* info);
bool hasLargeEnoughAppPartitions();
bool canMigratePartitionTable();
bool hasTwoSpiffs();
bool spiffsOnCorrectLocation();
bool hasFiles();
bool clearPartitionTable();
bool writeNewPartitionChecksum(uint8_t num);
bool writePartitionTableWithSpiffsAtOldAndApp1();
bool writePartitionTableWithSpiffsAtApp1AndNew();
bool writePartitionTableFinal();
bool moveLittleFsFromOldToApp1();
bool moveLittleFsFromApp1ToNew();
#elif defined(ESP8266)
uintptr_t getFirmwareUpdateStart();
#endif
};

File diff suppressed because it is too large Load Diff

View File

@@ -14,6 +14,7 @@
#include "EnergyAccounting.h"
#include "HwTools.h"
#include "PriceService.h"
#include "AmsFirmwareUpdater.h"
#if defined(ESP32)
#include <esp_task_wdt.h>
@@ -21,13 +22,24 @@
class AmsMqttHandler {
public:
AmsMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf) {
#if defined(AMS_REMOTE_DEBUG)
AmsMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, AmsFirmwareUpdater* updater) {
this->mqttConfig = mqttConfig;
this->mqttConfigChanged = true;
this->debugger = debugger;
this->json = buf;
this->updater = updater;
mqtt.dropOverflow(true);
};
#else
AmsMqttHandler(MqttConfig& mqttConfig, Stream* debugger, char* buf) {
this->mqttConfig = mqttConfig;
this->mqttConfigChanged = true;
this->debugger = debugger;
this->json = buf;
mqtt.dropOverflow(true);
};
#endif
void setCaVerification(bool);
void setConfig(MqttConfig& mqttConfig);
@@ -40,11 +52,13 @@ public:
virtual uint8_t getFormat() { return 0; };
virtual bool postConnect() { return false; };
virtual bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, PriceService* ps) { return false; };
virtual bool publishTemperatures(AmsConfiguration*, HwTools*) { return false; };
virtual bool publishPrices(PriceService* ps) { return false; };
virtual bool publishSystem(HwTools*, PriceService*, EnergyAccounting*) { return false; };
virtual bool publishRaw(String data) { return false; };
virtual bool publishFirmware() { return false; };
virtual void onMessage(String &topic, String &payload) {};
virtual ~AmsMqttHandler() {
@@ -55,7 +69,11 @@ public:
};
protected:
#if defined(AMS_REMOTE_DEBUG)
RemoteDebug* debugger;
#else
Stream* debugger;
#endif
MqttConfig mqttConfig;
bool mqttConfigChanged = true;
MQTTClient mqtt = MQTTClient(256);
@@ -65,6 +83,9 @@ protected:
WiFiClientSecure *mqttSecureClient = NULL;
char* json;
uint16_t BufferSize = 2048;
uint64_t lastStateUpdate = 0;
AmsFirmwareUpdater* updater = NULL;
};
#endif

View File

@@ -33,15 +33,18 @@ bool AmsMqttHandler::connect() {
if(epoch < FirmwareVersion::BuildEpoch) {
return false;
}
bool applySslConfiguration = mqttConfigChanged;
if(mqttSecureClient == NULL) {
mqttSecureClient = new WiFiClientSecure();
#if defined(ESP8266)
mqttSecureClient->setBufferSizes(512, 512);
return false;
#endif
applySslConfiguration = true;
}
if(mqttConfigChanged) {
if(applySslConfiguration) {
if(caVerification && LittleFS.begin()) {
File file;
@@ -99,9 +102,9 @@ bool AmsMqttHandler::connect() {
actualClient = mqttClient;
}
actualClient->setTimeout(4);
mqttConfigChanged = false;
mqtt.setTimeout(mqttConfig.timeout);
mqtt.setKeepAlive(mqttConfig.keepalive);
mqtt.begin(mqttConfig.host, mqttConfig.port, *actualClient);
String statusTopic = String(mqttConfig.publishTopic) + "/status";
mqtt.setWill(statusTopic.c_str(), "offline", true, 0);
@@ -116,18 +119,32 @@ bool AmsMqttHandler::connect() {
// Connect to a unsecure or secure MQTT server
if ((strlen(mqttConfig.username) == 0 && mqtt.connect(mqttConfig.clientId)) ||
(strlen(mqttConfig.username) > 0 && mqtt.connect(mqttConfig.clientId, mqttConfig.username, mqttConfig.password))) {
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Successfully connected to MQTT\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("Successfully connected to MQTT\n"));
mqtt.onMessage(std::bind(&AmsMqttHandler::onMessage, this, std::placeholders::_1, std::placeholders::_2));
if(strlen(mqttConfig.subscribeTopic) > 0) {
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR(" Subscribing to [%s]\n"), mqttConfig.subscribeTopic);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR(" Subscribing to [%s]\n"), mqttConfig.subscribeTopic);
if(!mqtt.subscribe(mqttConfig.subscribeTopic)) {
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR(" Unable to subscribe to to [%s]\n"), mqttConfig.subscribeTopic);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR(" Unable to subscribe to to [%s]\n"), mqttConfig.subscribeTopic);
}
}
mqtt.publish(statusTopic, "online", true, 0);
mqtt.loop();
postConnect();
return true;
} else {
if (debugger->isActive(RemoteDebug::ERROR)) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
{
debugger->printf_P(PSTR("Failed to connect to MQTT: %d\n"), mqtt.lastError());
#if defined(ESP8266)
if(mqttSecureClient) {
@@ -143,6 +160,10 @@ bool AmsMqttHandler::connect() {
void AmsMqttHandler::disconnect() {
mqtt.disconnect();
mqtt.loop();
if(mqttSecureClient != NULL) {
delete mqttSecureClient;
mqttSecureClient = NULL;
}
delay(10);
yield();
}

View File

@@ -7,7 +7,9 @@
#ifndef _CLOUDCONNECTOR_H
#define _CLOUDCONNECTOR_H
#if defined(AMS_REMOTE_DEBUG)
#include "RemoteDebug.h"
#endif
#include "mbedtls/ssl.h"
#include "mbedtls/platform.h"
#include "mbedtls/net.h"
@@ -35,14 +37,14 @@
#warning "Unsupported board type"
#endif
#define CC_BUF_SIZE 2048
#define CC_BUF_SIZE 4096
static const char CC_JSON_POWER[] PROGMEM = ",\"%s\":{\"P\":%lu,\"Q\":%lu}";
static const char CC_JSON_POWER_LIST3[] PROGMEM = ",\"%s\":{\"P\":%lu,\"Q\":%lu,\"tP\":%.3f,\"tQ\":%.3f}";
static const char CC_JSON_PHASE[] PROGMEM = "%s\"%d\":{\"u\":%.2f,\"i\":%s}";
static const char CC_JSON_PHASE_LIST4[] PROGMEM = "%s\"%d\":{\"u\":%.2f,\"i\":%s,\"Pim\":%lu,\"Pex\":%lu,\"pf\":%.2f}";
static const char CC_JSON_STATUS[] PROGMEM = ",\"status\":{\"esp\":{\"state\":%d,\"error\":%d},\"han\":{\"state\":%d,\"error\":%d},\"wifi\":{\"state\":%d,\"error\":%d},\"mqtt\":{\"state\":%d,\"error\":%d}}";
static const char CC_JSON_INIT[] PROGMEM = ",\"init\":{\"mac\":\"%s\",\"apmac\":\"%s\",\"version\":\"%s\",\"boardType\":%d,\"bootReason\":%d,\"bootCause\":%d,\"utcOffset\":%d},\"meter\":{\"manufacturerId\":%d,\"manufacturer\":\"%s\",\"model\":\"%s\",\"id\":\"%s\",\"system\":\"%s\",\"fuse\":%d,\"import\":%d,\"export\":%d},\"network\":{\"ip\":\"%s\",\"mask\":\"%s\",\"gw\":\"%s\",\"dns1\":\"%s\",\"dns2\":\"%s\"}";
static const char CC_JSON_INIT[] PROGMEM = ",\"init\":{\"mac\":\"%s\",\"apmac\":\"%s\",\"version\":\"%s\",\"boardType\":%d,\"bootReason\":%d,\"bootCause\":%d,\"tz\":\"%s\"},\"meter\":{\"manufacturerId\":%d,\"manufacturer\":\"%s\",\"model\":\"%s\",\"id\":\"%s\",\"system\":\"%s\",\"fuse\":%d,\"import\":%d,\"export\":%d},\"network\":{\"ip\":\"%s\",\"mask\":\"%s\",\"gw\":\"%s\",\"dns1\":\"%s\",\"dns2\":\"%s\"}";
struct CloudData {
uint8_t type;
@@ -51,26 +53,43 @@ struct CloudData {
class CloudConnector {
public:
#if defined(AMS_REMOTE_DEBUG)
CloudConnector(RemoteDebug*);
bool setup(CloudConfig& config, MeterConfig& meter, SystemConfig& system, HwTools* hw, ResetDataContainer* rdc);
#else
CloudConnector(Stream*);
#endif
bool setup(CloudConfig& config, MeterConfig& meter, SystemConfig& system, NtpConfig& ntp, HwTools* hw, ResetDataContainer* rdc, PriceService* ps);
void setMqttHandler(AmsMqttHandler* mqttHandler);
void update(AmsData& data, EnergyAccounting& ea);
void setPriceConfig(PriceServiceConfig&);
void setEnergyAccountingConfig(EnergyAccountingConfig&);
void forceUpdate();
void setTimezone(Timezone* tz);
void forcePriceUpdate();
void setConnectionHandler(ConnectionHandler* ch);
String generateSeed();
private:
#if defined(AMS_REMOTE_DEBUG)
RemoteDebug* debugger = NULL;
#else
Stream* debugger = NULL;
#endif
HwTools* hw = NULL;
ConnectionHandler* ch = NULL;
ResetDataContainer* rdc = NULL;
Timezone* tz = NULL;
PriceService* ps = NULL;
AmsMqttHandler* mqttHandler = NULL;
CloudConfig config;
PriceServiceConfig priceConfig;
unsigned long lastPriceConfig = 0;
EnergyAccountingConfig eac;
unsigned long lastEac = 0;
HTTPClient http;
WiFiUDP udp;
WiFiClient tcp;
int maxPwr = 0;
uint8_t boardType = 0;
char timezone[32];
uint8_t distributionSystem = 0;
uint16_t mainFuse = 0, productionCapacity = 0;
@@ -80,7 +99,10 @@ private:
char mac[18];
char apmac[18];
String seed = "";
char clearBuffer[CC_BUF_SIZE];
uint8_t* httpBuffer = NULL;
unsigned char encryptedBuffer[256];
mbedtls_rsa_context* rsa = nullptr;
mbedtls_ctr_drbg_context ctr_drbg;
@@ -88,7 +110,6 @@ private:
char* pers = "amsreader";
bool init();
void debugPrint(byte *buffer, int start, int length);
String meterManufacturer(uint8_t type) {
switch(type) {

View File

@@ -23,7 +23,11 @@
#include "esp32s3/rom/rtc.h"
#endif
#if defined(AMS_REMOTE_DEBUG)
CloudConnector::CloudConnector(RemoteDebug* debugger) {
#else
CloudConnector::CloudConnector(Stream* debugger) {
#endif
this->debugger = debugger;
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
@@ -46,7 +50,7 @@ CloudConnector::CloudConnector(RemoteDebug* debugger) {
sprintf_P(this->apmac, PSTR("%02X:%02X:%02X:%02X:%02X:%02X"), apmac[0], apmac[1], apmac[2], apmac[3], apmac[4], apmac[5]);
}
bool CloudConnector::setup(CloudConfig& config, MeterConfig& meter, SystemConfig& system, HwTools* hw, ResetDataContainer* rdc) {
bool CloudConnector::setup(CloudConfig& config, MeterConfig& meter, SystemConfig& system, NtpConfig& ntp, HwTools* hw, ResetDataContainer* rdc, PriceService* ps) {
bool ret = false;
#if defined(ESP32)
if(!ESPRandom::isValidV4Uuid(config.clientId)) {
@@ -59,8 +63,10 @@ bool CloudConnector::setup(CloudConfig& config, MeterConfig& meter, SystemConfig
this->config = config;
this->hw = hw;
this->rdc = rdc;
this->ps = ps;
this->boardType = system.boardType;
strcpy(this->timezone, ntp.timezone);
this->maxPwr = 0;
this->distributionSystem = meter.distributionSystem;
@@ -84,7 +90,10 @@ bool CloudConnector::init() {
strcpy_P(config.hostname, PSTR("cloud.amsleser.no"));
snprintf_P(clearBuffer, CC_BUF_SIZE, PSTR("http://%s/hub/cloud/public.key"), config.hostname);
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(CloudConnector) Downloading public key from %s\n"), clearBuffer);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("(CloudConnector) Downloading public key from %s\n"), clearBuffer);
#if defined(ESP8266)
WiFiClient client;
client.setTimeout(5000);
@@ -107,14 +116,11 @@ bool CloudConnector::init() {
memset(clearBuffer, 0, CC_BUF_SIZE);
snprintf(clearBuffer, CC_BUF_SIZE, pub.c_str());
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Cloud public key:\n%s\n"), clearBuffer);
mbedtls_pk_context pk;
mbedtls_pk_init(&pk);
int error_code = 0;
if((error_code = mbedtls_pk_parse_public_key(&pk, (unsigned char*) clearBuffer, strlen((const char*) clearBuffer)+1)) == 0){
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("RSA public key OK\n"));
rsa = mbedtls_pk_rsa(pk);
mbedtls_ctr_drbg_init(&ctr_drbg);
mbedtls_entropy_init(&entropy);
@@ -123,18 +129,37 @@ bool CloudConnector::init() {
&entropy, (const unsigned char *) pers,
strlen(pers));
if(ret != 0) {
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("mbedtls_ctr_drbg_seed return code: %d\n"), ret);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("mbedtls_ctr_drbg_seed return code: %d\n"), ret);
}
return ret == 0;
} else {
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf("RSA public key read error: ");
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf("RSA public key read error: ");
mbedtls_strerror(error_code, clearBuffer, CC_BUF_SIZE);
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf("%s\n", clearBuffer);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf("%s\n", clearBuffer);
}
} else {
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("(CloudConnector) Communication error, returned status: %d\n"), status);
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf(http.errorToString(status).c_str());
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf(http.getString().c_str());
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("(CloudConnector) Communication error, returned status: %d\n"), status);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf(http.errorToString(status).c_str());
debugger->println();
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf(http.getString().c_str());
http.end();
}
@@ -144,17 +169,44 @@ bool CloudConnector::init() {
}
void CloudConnector::update(AmsData& data, EnergyAccounting& ea) {
if(!config.enabled) return;
unsigned long now = millis();
if(now-lastUpdate < config.interval*1000) return;
if(!ESPRandom::isValidV4Uuid(config.clientId)) {
if(debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("(CloudConnector) Client ID is not valid\n"));
if(now-lastUpdate < ((unsigned long)config.interval)*1000) {
return;
};
bool sendFirst = lastUpdate == 0;
lastUpdate = now;
if(config.enabled) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugger->printf_P(PSTR("(CloudConnector) Enabled\n"));
} else {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugger->printf_P(PSTR("(CloudConnector) Not enabled\n"));
return;
}
if(!ESPRandom::isValidV4Uuid(config.clientId)) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
debugger->printf_P(PSTR("(CloudConnector) Client ID is not valid\n"));
return;
}
if(data.getListType() < 2) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugger->printf_P(PSTR("(CloudConnector) List type not enough data\n"));
return;
}
if(data.getListType() < 2) return;
if(!initialized && !init()) {
if(debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Unable to initialize cloud connector\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
debugger->printf_P(PSTR("Unable to initialize cloud connector\n"));
return;
}
initialized = true;
@@ -164,8 +216,13 @@ void CloudConnector::update(AmsData& data, EnergyAccounting& ea) {
int pos = 0;
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR("{\"id\":\"%s\""), uuid.c_str());
if(!seed.isEmpty()) {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"seed\":\"%s\""), seed.c_str());
}
if(lastUpdate == 0) {
bool sendData = true;
if(sendFirst) {
seed.clear();
if(mainFuse > 0 && distributionSystem > 0) {
int voltage = distributionSystem == 2 ? 400 : 230;
if(data.isThreePhase()) {
@@ -204,7 +261,7 @@ void CloudConnector::update(AmsData& data, EnergyAccounting& ea) {
boardType,
rtc_get_reset_reason(0),
rdc == NULL ? 0 : rdc->last_cause,
tz == NULL ? 0 : (tz->toLocal(now)-now)/3600,
timezone,
data.getMeterType(),
meterManufacturer(data.getMeterType()).c_str(),
data.getMeterModel().c_str(),
@@ -219,191 +276,355 @@ void CloudConnector::update(AmsData& data, EnergyAccounting& ea) {
dns1.toString().c_str(),
dns2.toString().c_str()
);
}
} else if(lastPriceConfig == 0) {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"price\":{\"area\":\"%s\",\"currency\":\"%s\",\"modifiers\":["), priceConfig.area, priceConfig.currency);
if(ps != NULL) {
std::vector<PriceConfig> pc = ps->getPriceConfig();
if(pc.size() > 0) {
for(uint8_t i = 0; i < pc.size(); i++) {
PriceConfig& p = pc.at(i);
float vcc = 0.0;
int rssi = 0;
float temperature = -127;
if(hw != NULL) {
vcc = hw->getVcc();
rssi = hw->getWifiRssi();
temperature = hw->getTemperature();
}
String days;
for(uint8_t d = 0; d < 7; d++) {
if((p.days >> d) & 0x1 == 0x1) {
days += String(d, 10) + ",";
}
}
days = days.substring(0, days.length()-1);
uint8_t espStatus;
#if defined(ESP8266)
if(vcc < 2.0) { // Voltage not correct, ESP would not run on this voltage
espStatus = 1;
} else if(vcc > 2.8 && vcc < 3.5) {
espStatus = 1;
} else if(vcc > 2.7 && vcc < 3.6) {
espStatus = 2;
} else {
espStatus = 3;
}
#elif defined(ESP32)
if(vcc < 2.0) { // Voltage not correct, ESP would not run on this voltage
espStatus = 1;
} else if(vcc > 3.1 && vcc < 3.5) {
espStatus = 1;
} else if(vcc > 3.0 && vcc < 3.6) {
espStatus = 2;
} else {
espStatus = 3;
}
#endif
String hours;
for(uint8_t h = 0; h < 24; h++) {
if((p.hours >> h) & 0x1 == 0x1) {
hours += String(h, 10) + ",";
}
}
hours = hours.substring(0, hours.length()-1);
uint8_t hanStatus;
if(data.getLastError() != 0) {
hanStatus = 3;
} else if(data.getLastUpdateMillis() == 0 && now < 30000) {
hanStatus = 0;
} else if(now - data.getLastUpdateMillis() < 15000) {
hanStatus = 1;
} else if(now - data.getLastUpdateMillis() < 30000) {
hanStatus = 2;
} else {
hanStatus = 3;
}
char start[8];
memset(start, 0, 8);
if(p.start_dayofmonth > 0 && p.start_month > 0) {
snprintf_P(start, 8, PSTR("[%d,%d]"), p.start_month, p.start_dayofmonth);
} else {
strcpy_P(start, PSTR("null"));
}
uint8_t wifiStatus;
if(rssi > -75) {
wifiStatus = 1;
} else if(rssi > -95) {
wifiStatus = 2;
} else {
wifiStatus = 3;
}
char end[8];
memset(end, 0, 8);
if(p.end_dayofmonth > 0 && p.end_month > 0) {
snprintf_P(end, 8, PSTR("[%d,%d]"), p.end_month, p.end_dayofmonth);
} else {
strcpy_P(end, PSTR("null"));
}
uint8_t mqttStatus;
if(mqttHandler == NULL) {
mqttStatus = 0;
} else if(mqttHandler->connected()) {
mqttStatus = 1;
} else if(mqttHandler->lastError() == 0) {
mqttStatus = 2;
} else {
mqttStatus = 3;
}
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"data\":{\"clock\":%lu,\"up\":%lu,\"lastUpdate\":%lu,\"est\":%s"),
(uint32_t) time(nullptr),
(uint32_t) (millis64()/1000),
(uint32_t) (data.getLastUpdateMillis()/1000),
data.isCounterEstimated() ? "true" : "false"
);
if(data.getListType() > 2) {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER_LIST3, "import", data.getActiveImportPower(), data.getReactiveImportPower(), data.getActiveImportCounter(), data.getReactiveImportCounter());
} else {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER, "import", data.getActiveImportPower(), data.getReactiveImportPower());
}
if(data.getListType() > 2) {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER_LIST3, "export", data.getActiveExportPower(), data.getReactiveExportPower(), data.getActiveExportCounter(), data.getReactiveExportCounter());
} else {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER, "export", data.getActiveExportPower(), data.getReactiveExportPower());
}
if(data.getListType() > 1) {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"phases\":{"));
bool first = true;
if(data.getL1Voltage() > 0.0) {
if(data.getListType() > 3) {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE_LIST4, first ? "" : ",", 1, data.getL1Voltage(), String(data.getL1Current(), 2).c_str(), data.getL1ActiveImportPower(), data.getL1ActiveExportPower(), data.getL1PowerFactor());
} else {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE, first ? "" : ",", 1, data.getL1Voltage(), String(data.getL1Current(), 2).c_str());
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR("{\"type\":%d,\"name\":\"%s\",\"dir\":%d,\"days\":[%s],\"hours\":[%s],\"value\":%.4f,\"start\":%s,\"end\":%s}%s"),
p.type,
p.name,
p.direction,
days.c_str(),
hours.c_str(),
p.value / 10000.0,
start,
end,
i == pc.size()-1 ? "" : ","
);
}
}
first = false;
}
if(data.getL2Voltage() > 0.0) {
if(data.getListType() > 3) {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE_LIST4, first ? "" : ",", 2, data.getL2Voltage(), String(data.getL2Current(), 2).c_str(), data.getL2ActiveImportPower(), data.getL2ActiveExportPower(), data.getL2PowerFactor());
} else {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE, first ? "" : ",", 2, data.getL2Voltage(), data.isL2currentMissing() ? "null" : String(data.getL2Current(), 2).c_str());
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR("]}"));
lastPriceConfig = now;
sendData = false;
} else if(lastEac == 0) {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"accounting\":{\"hours\":%d,\"thresholds\":[%d,%d,%d,%d,%d,%d,%d,%d,%d]}"),
eac.hours,
eac.thresholds[0],
eac.thresholds[1],
eac.thresholds[2],
eac.thresholds[3],
eac.thresholds[4],
eac.thresholds[5],
eac.thresholds[6],
eac.thresholds[7],
eac.thresholds[8]
);
lastEac = now;
}
if(sendData) {
float vcc = 0.0;
int rssi = 0;
float temperature = -127;
if(hw != NULL) {
vcc = hw->getVcc();
rssi = hw->getWifiRssi();
temperature = hw->getTemperature();
}
uint8_t espStatus;
#if defined(ESP8266)
if(vcc < 2.0) { // Voltage not correct, ESP would not run on this voltage
espStatus = 1;
} else if(vcc > 2.8 && vcc < 3.5) {
espStatus = 1;
} else if(vcc > 2.7 && vcc < 3.6) {
espStatus = 2;
} else {
espStatus = 3;
}
#elif defined(ESP32)
if(vcc < 2.0) { // Voltage not correct, ESP would not run on this voltage
espStatus = 1;
} else if(vcc > 3.1 && vcc < 3.5) {
espStatus = 1;
} else if(vcc > 3.0 && vcc < 3.6) {
espStatus = 2;
} else {
espStatus = 3;
}
#endif
uint8_t hanStatus;
if(data.getLastError() != 0) {
hanStatus = 3;
} else if(data.getLastUpdateMillis() == 0 && now < 30000) {
hanStatus = 0;
} else if(now - data.getLastUpdateMillis() < 15000) {
hanStatus = 1;
} else if(now - data.getLastUpdateMillis() < 30000) {
hanStatus = 2;
} else {
hanStatus = 3;
}
uint8_t wifiStatus;
if(rssi > -75) {
wifiStatus = 1;
} else if(rssi > -95) {
wifiStatus = 2;
} else {
wifiStatus = 3;
}
uint8_t mqttStatus;
if(mqttHandler == NULL) {
mqttStatus = 0;
} else if(mqttHandler->connected()) {
mqttStatus = 1;
} else if(mqttHandler->lastError() == 0) {
mqttStatus = 2;
} else {
mqttStatus = 3;
}
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"data\":{\"clock\":%lu,\"up\":%lu,\"lastUpdate\":%lu,\"est\":%s"),
(uint32_t) time(nullptr),
(uint32_t) (millis64()/1000),
(uint32_t) (data.getLastUpdateMillis()/1000),
data.isCounterEstimated() ? "true" : "false"
);
if(data.getListType() > 2) {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER_LIST3, "import", data.getActiveImportPower(), data.getReactiveImportPower(), data.getActiveImportCounter(), data.getReactiveImportCounter());
} else {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER, "import", data.getActiveImportPower(), data.getReactiveImportPower());
}
if(data.getListType() > 2) {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER_LIST3, "export", data.getActiveExportPower(), data.getReactiveExportPower(), data.getActiveExportCounter(), data.getReactiveExportCounter());
} else {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER, "export", data.getActiveExportPower(), data.getReactiveExportPower());
}
if(data.getListType() > 1) {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"phases\":{"));
bool first = true;
if(data.getL1Voltage() > 0.0) {
if(data.getListType() > 3) {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE_LIST4, first ? "" : ",", 1, data.getL1Voltage(), String(data.getL1Current(), 2).c_str(), data.getL1ActiveImportPower(), data.getL1ActiveExportPower(), data.getL1PowerFactor());
} else {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE, first ? "" : ",", 1, data.getL1Voltage(), String(data.getL1Current(), 2).c_str());
}
first = false;
}
first = false;
}
if(data.getL3Voltage() > 0.0) {
if(data.getListType() > 3) {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE_LIST4, first ? "" : ",", 3, data.getL3Voltage(), String(data.getL3Current(), 2).c_str(), data.getL3ActiveImportPower(), data.getL3ActiveExportPower(), data.getL3PowerFactor());
} else {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE, first ? "" : ",", 3, data.getL3Voltage(), String(data.getL3Current(), 2).c_str());
if(data.getL2Voltage() > 0.0) {
if(data.getListType() > 3) {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE_LIST4, first ? "" : ",", 2, data.getL2Voltage(), String(data.getL2Current(), 2).c_str(), data.getL2ActiveImportPower(), data.getL2ActiveExportPower(), data.getL2PowerFactor());
} else {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE, first ? "" : ",", 2, data.getL2Voltage(), data.isL2currentMissing() ? "null" : String(data.getL2Current(), 2).c_str());
}
first = false;
}
first = false;
if(data.getL3Voltage() > 0.0) {
if(data.getListType() > 3) {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE_LIST4, first ? "" : ",", 3, data.getL3Voltage(), String(data.getL3Current(), 2).c_str(), data.getL3ActiveImportPower(), data.getL3ActiveExportPower(), data.getL3PowerFactor());
} else {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE, first ? "" : ",", 3, data.getL3Voltage(), String(data.getL3Current(), 2).c_str());
}
first = false;
}
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR("}"));
}
if(data.getListType() > 3) {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"pf\":%.2f"), data.getPowerFactor());
}
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"realtime\":{\"import\":%.3f,\"export\":%.3f}"), ea.getUseThisHour(), ea.getProducedThisHour());
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"vcc\":%.2f,\"temp\":%.2f,\"rssi\":%d,\"free\":%d"), vcc, temperature, rssi, ESP.getFreeHeap());
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_STATUS,
espStatus, 0,
hanStatus, data.getLastError(),
wifiStatus, 0,
mqttStatus, mqttHandler == NULL ? 0 : mqttHandler->lastError()
);
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR("}"));
}
if(data.getListType() > 3) {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"pf\":%.2f"), data.getPowerFactor());
}
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"realtime\":{\"import\":%.3f,\"export\":%.3f}"), ea.getUseThisHour(), ea.getProducedThisHour());
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"vcc\":%.2f,\"temp\":%.2f,\"rssi\":%d,\"free\":%d"), vcc, temperature, rssi, ESP.getFreeHeap());
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_STATUS,
espStatus, 0,
hanStatus, data.getLastError(),
wifiStatus, 0,
mqttStatus, mqttHandler == NULL ? 0 : mqttHandler->lastError()
);
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR("}"));
uint16_t crc = crc16((uint8_t*) clearBuffer, pos);
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"crc\":\"%04X\"}"), crc);
if(rsa == nullptr) return;
if(rsa == nullptr) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
debugger->printf_P(PSTR("RSA is null\n"));
return;
}
int ret = mbedtls_rsa_check_pubkey(rsa);
if(ret != 0) {
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("mbedtls_rsa_pkcs1_encrypt return code: %d\n"), ret);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("mbedtls_rsa_pkcs1_encrypt return code: %d\n"), ret);
mbedtls_strerror(ret, clearBuffer, CC_BUF_SIZE);
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("%s\n"), clearBuffer);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("%s\n"), clearBuffer);
return;
}
memset(encryptedBuffer, 0, rsa->len);
int maxlen = 100 * (rsa->len/128);
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(CloudConnector) Sending %d bytes and maxlen %d\n", pos, maxlen);
udp.beginPacket(config.hostname,7443);
for(int i = 0; i < pos; i += maxlen) {
int size = min(maxlen, pos-i);
int ret = mbedtls_rsa_pkcs1_encrypt(rsa, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PUBLIC, size, (unsigned char*) (clearBuffer+i), encryptedBuffer);
if(ret == 0) {
udp.write(encryptedBuffer, rsa->len);
delay(1);
} else {
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("mbedtls_rsa_pkcs1_encrypt return code: %d\n"), ret);
mbedtls_strerror(ret, clearBuffer, CC_BUF_SIZE);
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("%s\n"), clearBuffer);
int maxlen = rsa->len - 11; // 11 should be the correct padding size for PKCS1
Stream *stream = NULL;
if(config.proto == 0) {
udp.beginPacket(config.hostname, config.port);
stream = &udp;
} else if(config.proto == 1) {
if(!tcp.connected()) {
int ret = tcp.connect(config.hostname, config.port, 1000);
if(ret != 1) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("tcp.connect(%s, %d) return code: %d\n"), config.hostname, config.port, ret);
return;
}
tcp.setTimeout((config.interval * 1000) / 2);
}
while(tcp.available()) tcp.read(); // Empty incoming buffer
stream = &tcp;
} else if(config.proto == 2) {
if(!http.connected()) {
http.setReuse(true);
snprintf_P(clearBuffer, CC_BUF_SIZE, PSTR("http://%s/hub/cloud/data"), config.hostname);
if(!http.begin(clearBuffer)) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("(CloudConnector) Unable to start HTTP connector\n"));
http.end();
return;
}
}
if(httpBuffer == NULL) {
httpBuffer = (uint8_t*) malloc(CC_BUF_SIZE);
}
}
udp.endPacket();
lastUpdate = now;
int sendBytes = 0;
for(int i = 0; i < pos; i += maxlen) {
int ret = mbedtls_rsa_pkcs1_encrypt(rsa, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PUBLIC, maxlen, (unsigned char*) (clearBuffer+i), encryptedBuffer);
if(ret == 0) {
if(stream != NULL) {
stream->write(encryptedBuffer, rsa->len);
stream->flush();
} else {
memcpy(httpBuffer + sendBytes, encryptedBuffer, rsa->len);
}
sendBytes += rsa->len;
delay(1);
} else {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("mbedtls_rsa_pkcs1_encrypt return code: %d\n"), ret);
mbedtls_strerror(ret, clearBuffer, CC_BUF_SIZE);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("%s\n"), clearBuffer);
}
}
if(config.proto == 0) {
udp.endPacket();
} else if(config.proto == 1) {
tcp.write("\r\n");
tcp.flush();
} else if(config.proto == 2) {
http.addHeader("Content-Type", "application/octet-stream");
int status = http.POST(httpBuffer, sendBytes);
if(status != 200) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("(CloudConnector) Communication error 2, returned status: %d\n"), status);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf(http.errorToString(status).c_str());
debugger->println();
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf(http.getString().c_str());
http.end();
}
}
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("(CloudConnector) %d bytes sent to %s:%d from %s\n"), sendBytes, config.hostname, config.proto == 2 ? 80 : config.port, uuid.c_str());
}
void CloudConnector::forceUpdate() {
lastUpdate = 0;
lastPriceConfig = 0;
lastEac = 0;
}
void CloudConnector::setTimezone(Timezone* tz) {
this->tz = tz;
void CloudConnector::forcePriceUpdate() {
lastPriceConfig = 0;
}
void CloudConnector::setConnectionHandler(ConnectionHandler* ch) {
this->ch = ch;
}
void CloudConnector::debugPrint(byte *buffer, int start, int length) {
for (int i = start; i < start + length; i++) {
if (buffer[i] < 0x10)
debugger->print(F("0"));
debugger->print(buffer[i], HEX);
debugger->print(F(" "));
if ((i - start + 1) % 16 == 0)
debugger->println(F(""));
else if ((i - start + 1) % 4 == 0)
debugger->print(F(" "));
yield(); // Let other get some resources too
}
debugger->println(F(""));
void CloudConnector::setPriceConfig(PriceServiceConfig& priceConfig) {
this->priceConfig = priceConfig;
this->lastPriceConfig = 0;
}
void CloudConnector::setEnergyAccountingConfig(EnergyAccountingConfig& eac) {
this->eac = eac;
this->lastEac = 0;
}
String CloudConnector::generateSeed() {
uint8_t key[16];
ESPRandom::uuid4(key);
seed = ESPRandom::uuidToString(key);
return seed;
}

View File

@@ -9,13 +9,19 @@
#include "ConnectionHandler.h"
#include <Arduino.h>
#if defined(AMS_REMOTE_DEBUG)
#include "RemoteDebug.h"
#endif
#define CONNECTION_TIMEOUT 30000
class EthernetConnectionHandler : public ConnectionHandler {
public:
#if defined(AMS_REMOTE_DEBUG)
EthernetConnectionHandler(RemoteDebug* debugger);
#else
EthernetConnectionHandler(Stream* debugger);
#endif
bool connect(NetworkConfig config, SystemConfig sys);
void disconnect(unsigned long reconnectDelay);
@@ -33,7 +39,11 @@ public:
#endif
private:
#if defined(AMS_REMOTE_DEBUG)
RemoteDebug* debugger;
#else
Stream* debugger;
#endif
NetworkConfig config;
bool connected = false;

View File

@@ -9,12 +9,18 @@
#include "ConnectionHandler.h"
#include <Arduino.h>
#if defined(AMS_REMOTE_DEBUG)
#include "RemoteDebug.h"
#endif
#include <DNSServer.h>
class WiFiAccessPointConnectionHandler : public ConnectionHandler {
public:
#if defined(AMS_REMOTE_DEBUG)
WiFiAccessPointConnectionHandler(RemoteDebug* debugger);
#else
WiFiAccessPointConnectionHandler(Stream* debugger);
#endif
bool connect(NetworkConfig config, SystemConfig sys);
void disconnect(unsigned long reconnectDelay);
@@ -32,7 +38,11 @@ public:
#endif
private:
#if defined(AMS_REMOTE_DEBUG)
RemoteDebug* debugger;
#else
Stream* debugger;
#endif
NetworkConfig config;
DNSServer dnsServer;

View File

@@ -9,7 +9,9 @@
#include "ConnectionHandler.h"
#include <Arduino.h>
#if defined(AMS_REMOTE_DEBUG)
#include "RemoteDebug.h"
#endif
#define CONNECTION_TIMEOUT 30000
#define RECONNECT_TIMEOUT 5000
@@ -20,7 +22,11 @@ esp_err_t set_esp_interface_ip(esp_interface_t interface, IPAddress local_ip=INA
class WiFiClientConnectionHandler : public ConnectionHandler {
public:
#if defined(AMS_REMOTE_DEBUG)
WiFiClientConnectionHandler(RemoteDebug* debugger);
#else
WiFiClientConnectionHandler(Stream* debugger);
#endif
bool connect(NetworkConfig config, SystemConfig sys);
void disconnect(unsigned long reconnectDelay);
@@ -38,7 +44,11 @@ public:
#endif
private:
#if defined(AMS_REMOTE_DEBUG)
RemoteDebug* debugger;
#else
Stream* debugger;
#endif
NetworkConfig config;
bool busPowered = false;
bool firstConnect = true;

View File

@@ -12,7 +12,11 @@
#include <lwip/dns.h>
#endif
#if defined(AMS_REMOTE_DEBUG)
EthernetConnectionHandler::EthernetConnectionHandler(RemoteDebug* debugger) {
#else
EthernetConnectionHandler::EthernetConnectionHandler(Stream* debugger) {
#endif
this->debugger = debugger;
this->mode = NETWORK_MODE_ETH_CLIENT;
}
@@ -56,11 +60,21 @@ bool EthernetConnectionHandler::connect(NetworkConfig config, SystemConfig sys)
ethPowerPin = 16;
ethMdc = 23;
ethMdio = 18;
} else if (sys.boardType == 245) { // wESP32
ethType = ETH_PHY_RTL8201;
ethEnablePin = -1;
ethAddr = 0;
ethClkMode = ETH_CLOCK_GPIO0_IN;
ethPowerPin = -1;
ethMdc = 16;
ethMdio = 17;
} else if(sys.boardType == 244) {
if (debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("LilyGO T-ETH-Lite\n"));
return false; // TODO
} else {
if (debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("Board type %d incompatible with ETH\n"), sys.boardType);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("Board type %d incompatible with ETH\n"), sys.boardType);
return false;
}
@@ -69,7 +83,10 @@ bool EthernetConnectionHandler::connect(NetworkConfig config, SystemConfig sys)
digitalWrite(ethEnablePin, 1);
}
if (debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Connecting to Ethernet\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("Connecting to Ethernet\n"));
if(ETH.begin(ethAddr, ethPowerPin, ethMdc, ethMdio, ethType, ethClkMode)) {
#if defined(ESP32)
@@ -98,7 +115,10 @@ bool EthernetConnectionHandler::connect(NetworkConfig config, SystemConfig sys)
}
}
} else {
if (debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("Unable to start Ethernet\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("Unable to start Ethernet\n"));
}
}
#endif
@@ -106,7 +126,10 @@ bool EthernetConnectionHandler::connect(NetworkConfig config, SystemConfig sys)
}
void EthernetConnectionHandler::disconnect(unsigned long reconnectDelay) {
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("Disconnecting!\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("Disconnecting!\n"));
}
bool EthernetConnectionHandler::isConnected() {
@@ -118,22 +141,35 @@ void EthernetConnectionHandler::eventHandler(WiFiEvent_t event, WiFiEventInfo_t
switch(event) {
case ARDUINO_EVENT_ETH_CONNECTED:
connected = true;
if(debugger->isActive(RemoteDebug::INFO)) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
{
debugger->printf_P(PSTR("Successfully connected to Ethernet!\n"));
}
break;
case ARDUINO_EVENT_ETH_GOT_IP:
if(debugger->isActive(RemoteDebug::INFO)) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
{
debugger->printf_P(PSTR("IP: %s\n"), getIP().toString().c_str());
debugger->printf_P(PSTR("GW: %s\n"), getGateway().toString().c_str());
for(uint8_t i = 0; i < 3; i++) {
IPAddress dns4 = getDns(i);
if(!dns4.isAny()) debugger->printf_P(PSTR("DNS: %s\n"), dns4.toString().c_str());
if(dns4 == IPAddress()) {
// No IP
} else {
debugger->printf_P(PSTR("DNS: %s\n"), dns4.toString().c_str());
}
}
}
break;
case ARDUINO_EVENT_ETH_GOT_IP6: {
if(debugger->isActive(RemoteDebug::INFO)) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
{
IPv6Address ipv6 = getIPv6();
if(ipv6 == IPv6Address()) {
// No IP
@@ -154,7 +190,10 @@ void EthernetConnectionHandler::eventHandler(WiFiEvent_t event, WiFiEventInfo_t
}
case ARDUINO_EVENT_ETH_DISCONNECTED:
connected = false;
if(debugger->isActive(RemoteDebug::WARNING)) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
{
debugger->printf_P(PSTR("Ethernet was disconnected!\n"));
}
break;

View File

@@ -6,7 +6,11 @@
#include "WiFiAccessPointConnectionHandler.h"
#if defined(AMS_REMOTE_DEBUG)
WiFiAccessPointConnectionHandler::WiFiAccessPointConnectionHandler(RemoteDebug* debugger) {
#else
WiFiAccessPointConnectionHandler::WiFiAccessPointConnectionHandler(Stream* debugger) {
#endif
this->debugger = debugger;
this->mode = NETWORK_MODE_WIFI_AP;
}
@@ -42,22 +46,37 @@ void WiFiAccessPointConnectionHandler::eventHandler(WiFiEvent_t event, WiFiEvent
IPAddress stationIP;
switch(event) {
case ARDUINO_EVENT_WIFI_AP_START:
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("WiFi access point started with SSID %s\n"), config.ssid);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("WiFi access point started with SSID %s\n"), config.ssid);
break;
case ARDUINO_EVENT_WIFI_AP_STOP:
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("WiFi access point stopped!\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("WiFi access point stopped!\n"));
break;
case ARDUINO_EVENT_WIFI_AP_STACONNECTED:
memcpy(mac, info.wifi_ap_staconnected.mac, 6);
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Client connected to AP, client MAC: %02x:%02x:%02x:%02x:%02x:%02x\n"), mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("Client connected to AP, client MAC: %02x:%02x:%02x:%02x:%02x:%02x\n"), mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
break;
case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED:
memcpy(mac, info.wifi_ap_staconnected.mac, 6);
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Client disconnected from AP, client MAC: %02x:%02x:%02x:%02x:%02x:%02x\n"), mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("Client disconnected from AP, client MAC: %02x:%02x:%02x:%02x:%02x:%02x\n"), mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
break;
case ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED:
stationIP = info.wifi_ap_staipassigned.ip.addr;
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Client was assigned IP %s\n"), stationIP.toString().c_str());
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("Client was assigned IP %s\n"), stationIP.toString().c_str());
break;
}
}

View File

@@ -10,7 +10,11 @@
#include <lwip/dns.h>
#endif
#if defined(AMS_REMOTE_DEBUG)
WiFiClientConnectionHandler::WiFiClientConnectionHandler(RemoteDebug* debugger) {
#else
WiFiClientConnectionHandler::WiFiClientConnectionHandler(Stream* debugger) {
#endif
this->debugger = debugger;
this->mode = NETWORK_MODE_WIFI_CLIENT;
}
@@ -28,14 +32,20 @@ bool WiFiClientConnectionHandler::connect(NetworkConfig config, SystemConfig sys
}
if(WiFi.getMode() != WIFI_OFF) {
if (debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Not connected to WiFi, closing resources\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("Not connected to WiFi, closing resources\n"));
disconnect(RECONNECT_TIMEOUT);
return false;
}
timeout = CONNECTION_TIMEOUT;
if (debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Connecting to WiFi network: %s\n"), config.ssid);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("Connecting to WiFi network: %s\n"), config.ssid);
switch(sys.boardType) {
case 2: // spenceme
case 3: // Pow-K UART0
@@ -90,6 +100,7 @@ bool WiFiClientConnectionHandler::connect(NetworkConfig config, SystemConfig sys
}
#endif
WiFi.setAutoReconnect(true);
this->config = config;
#if defined(ESP32)
if(begin(config.ssid, config.psk)) {
#else
@@ -110,9 +121,11 @@ bool WiFiClientConnectionHandler::connect(NetworkConfig config, SystemConfig sys
}
yield();
} else {
if (debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("Unable to start WiFi\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("Unable to start WiFi\n"));
}
this->config = config;
return true;
}
return false;
@@ -164,7 +177,7 @@ wl_status_t WiFiClientConnectionHandler::begin(const char* ssid, const char* pas
}
}
if(strlen(config.ip) > 0){
if(strlen(config.ip) == 0){
if(set_esp_interface_ip(ESP_IF_WIFI_STA) != ESP_OK) {
return WL_CONNECT_FAILED;
}
@@ -208,7 +221,10 @@ void WiFiClientConnectionHandler::wifi_sta_config(wifi_config_t * wifi_config, c
#endif
void WiFiClientConnectionHandler::disconnect(unsigned long reconnectDelay) {
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("Disconnecting!\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("Disconnecting!\n"));
#if defined(ESP8266)
WiFiClient::stopAll();
#endif
@@ -235,7 +251,10 @@ void WiFiClientConnectionHandler::eventHandler(WiFiEvent_t event, WiFiEventInfo_
}
break;
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Successfully connected to WiFi!\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("Successfully connected to WiFi!\n"));
if(config.ipv6 && !WiFi.enableIpV6()) {
debugger->printf_P(PSTR("Unable to enable IPv6\n"));
}
@@ -246,7 +265,10 @@ void WiFiClientConnectionHandler::eventHandler(WiFiEvent_t event, WiFiEventInfo_
wifi_phy_mode_t phyMode;
if(esp_wifi_sta_get_negotiated_phymode(&phyMode) == ESP_OK) {
if(phyMode > WIFI_PHY_MODE_11B) {
if (debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("WiFi supports better rates than 802.11b, disabling\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("WiFi supports better rates than 802.11b, disabling\n"));
config.use11b = false;
configChanged = true;
return;
@@ -284,18 +306,28 @@ void WiFiClientConnectionHandler::eventHandler(WiFiEvent_t event, WiFiEventInfo_
#endif
break;
case ARDUINO_EVENT_WIFI_STA_GOT_IP: {
if(debugger->isActive(RemoteDebug::INFO)) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
{
debugger->printf_P(PSTR("IP: %s\n"), getIP().toString().c_str());
debugger->printf_P(PSTR("GW: %s\n"), getGateway().toString().c_str());
for(uint8_t i = 0; i < 3; i++) {
IPAddress dns4 = getDns(i);
if(!dns4.isAny()) debugger->printf_P(PSTR("DNS: %s\n"), dns4.toString().c_str());
if(dns4 == IPAddress()) {
// No IP
} else {
debugger->printf_P(PSTR("DNS: %s\n"), dns4.toString().c_str());
}
}
}
break;
}
case ARDUINO_EVENT_WIFI_STA_GOT_IP6: {
if(debugger->isActive(RemoteDebug::INFO)) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
{
IPv6Address ipv6 = getIPv6();
if(ipv6 == IPv6Address()) {
// No IP
@@ -322,7 +354,10 @@ void WiFiClientConnectionHandler::eventHandler(WiFiEvent_t event, WiFiEventInfo_
break;
default:
if(strlen(descr) > 0) {
if(debugger->isActive(RemoteDebug::WARNING)) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
{
debugger->printf_P(PSTR("WiFi disconnected, reason %s\n"), descr);
}
disconnect(RECONNECT_TIMEOUT);

View File

@@ -12,9 +12,15 @@
class DomoticzMqttHandler : public AmsMqttHandler {
public:
DomoticzMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, DomoticzConfig config) : AmsMqttHandler(mqttConfig, debugger, buf) {
#if defined(AMS_REMOTE_DEBUG)
DomoticzMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, DomoticzConfig config, AmsFirmwareUpdater* updater) : AmsMqttHandler(mqttConfig, debugger, buf, updater) {
this->config = config;
};
#else
DomoticzMqttHandler(MqttConfig& mqttConfig, Stream* debugger, char* buf, DomoticzConfig config) : AmsMqttHandler(mqttConfig, debugger, buf) {
this->config = config;
};
#endif
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, PriceService* ps);
bool publishTemperatures(AmsConfiguration*, HwTools*);
bool publishPrices(PriceService*);
@@ -25,6 +31,10 @@ public:
uint8_t getFormat();
void setDomoticzConfig(DomoticzConfig config) {
this->config = config;
}
private:
DomoticzConfig config;
double energy = 0.0;

View File

@@ -6,65 +6,83 @@
#include "DomoticzMqttHandler.h"
#include "json/domoticz_json.h"
#include "Uptime.h"
bool DomoticzMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, PriceService* ps) {
bool DomoticzMqttHandler::publish(AmsData* update, AmsData* previousState, EnergyAccounting* ea, PriceService* ps) {
bool ret = false;
AmsData data;
if(mqttConfig.stateUpdate) {
uint64_t now = millis64();
if(now-lastStateUpdate < mqttConfig.stateUpdateInterval * 1000) return false;
data.apply(*previousState);
data.apply(*update);
lastStateUpdate = now;
} else {
data = *update;
}
if (config.elidx > 0) {
if(data->getActiveImportCounter() > 1.0) {
energy = data->getActiveImportCounter();
if(data.getActiveImportCounter() > 1.0 && !data.isCounterEstimated()) {
energy = data.getActiveImportCounter();
}
if(energy > 0.0) {
char val[16];
snprintf_P(val, 16, PSTR("%.1f;%.1f"), (data->getActiveImportPower()/1.0), energy*1000.0);
snprintf_P(val, 16, PSTR("%.1f;%.1f"), (data.getActiveImportPower()/1.0), energy*1000.0);
snprintf_P(json, BufferSize, DOMOTICZ_JSON,
config.elidx,
val
);
ret = mqtt.publish(F("domoticz/in"), json);
mqtt.loop();
}
}
if(data->getListType() == 1)
if(data.getListType() == 1)
return ret;
if (config.vl1idx > 0){
char val[16];
snprintf_P(val, 16, PSTR("%.2f"), data->getL1Voltage());
snprintf_P(val, 16, PSTR("%.2f"), data.getL1Voltage());
snprintf_P(json, BufferSize, DOMOTICZ_JSON,
config.vl1idx,
val
);
ret |= mqtt.publish(F("domoticz/in"), json);
mqtt.loop();
}
if (config.vl2idx > 0){
char val[16];
snprintf_P(val, 16, PSTR("%.2f"), data->getL2Voltage());
snprintf_P(val, 16, PSTR("%.2f"), data.getL2Voltage());
snprintf_P(json, BufferSize, DOMOTICZ_JSON,
config.vl2idx,
val
);
ret |= mqtt.publish(F("domoticz/in"), json);
mqtt.loop();
}
if (config.vl3idx > 0){
char val[16];
snprintf(val, 16, "%.2f", data->getL3Voltage());
snprintf(val, 16, "%.2f", data.getL3Voltage());
snprintf_P(json, BufferSize, DOMOTICZ_JSON,
config.vl3idx,
val
);
ret |= mqtt.publish(F("domoticz/in"), json);
mqtt.loop();
}
if (config.cl1idx > 0){
char val[16];
snprintf(val, 16, "%.1f;%.1f;%.1f", data->getL1Current(), data->getL2Current(), data->getL3Current());
snprintf(val, 16, "%.1f;%.1f;%.1f", data.getL1Current(), data.getL2Current(), data.getL3Current());
snprintf_P(json, BufferSize, DOMOTICZ_JSON,
config.cl1idx,
val
);
ret |= mqtt.publish(F("domoticz/in"), json);
mqtt.loop();
}
return ret;
}

View File

@@ -80,7 +80,11 @@ struct EnergyAccountingRealtimeData {
class EnergyAccounting {
public:
#if defined(AMS_REMOTE_DEBUG)
EnergyAccounting(RemoteDebug*, EnergyAccountingRealtimeData*);
#else
EnergyAccounting(Stream*, EnergyAccountingRealtimeData*);
#endif
void setup(AmsDataStorage *ds, EnergyAccountingConfig *config);
void setPriceService(PriceService *ps);
void setTimezone(Timezone*);
@@ -123,7 +127,11 @@ public:
float getPriceForHour(uint8_t d, uint8_t h);
private:
#if defined(AMS_REMOTE_DEBUG)
RemoteDebug* debugger = NULL;
#else
Stream* debugger = NULL;
#endif
bool init = false, initPrice = false;
AmsDataStorage *ds = NULL;
PriceService *ps = NULL;

View File

@@ -9,7 +9,11 @@
#include "AmsStorage.h"
#include "FirmwareVersion.h"
#if defined(AMS_REMOTE_DEBUG)
EnergyAccounting::EnergyAccounting(RemoteDebug* debugger, EnergyAccountingRealtimeData* rtd) {
#else
EnergyAccounting::EnergyAccounting(Stream* Stream, EnergyAccountingRealtimeData* rtd) {
#endif
data.version = 1;
this->debugger = debugger;
if(rtd->magic != 0x6A) {
@@ -82,8 +86,8 @@ bool EnergyAccounting::update(AmsData* amsData) {
init = true;
}
float price = getPriceForHour(PRICE_DIRECTION_IMPORT, 0);
if(!initPrice && price != PRICE_NO_VALUE) {
float importPrice = getPriceForHour(PRICE_DIRECTION_IMPORT, 0);
if(!initPrice && importPrice != PRICE_NO_VALUE) {
calcDayCost();
}
@@ -155,8 +159,8 @@ bool EnergyAccounting::update(AmsData* amsData) {
float kwhi = (amsData->getActiveImportPower() * (((float) ms) / 3600000.0)) / 1000.0;
if(kwhi > 0) {
this->realtimeData->use += kwhi;
if(price != PRICE_NO_VALUE) {
float cost = price * kwhi;
if(importPrice != PRICE_NO_VALUE) {
float cost = importPrice * kwhi;
this->realtimeData->costHour += cost;
this->realtimeData->costDay += cost;
}
@@ -169,8 +173,9 @@ bool EnergyAccounting::update(AmsData* amsData) {
float kwhe = (amsData->getActiveExportPower() * (((float) ms) / 3600000.0)) / 1000.0;
if(kwhe > 0) {
this->realtimeData->produce += kwhe;
if(price != PRICE_NO_VALUE) {
float income = price * kwhe;
float exportPrice = getPriceForHour(PRICE_DIRECTION_EXPORT, 0);
if(exportPrice != PRICE_NO_VALUE) {
float income = exportPrice * kwhe;
this->realtimeData->incomeHour += income;
this->realtimeData->incomeDay += income;
}

View File

@@ -10,72 +10,39 @@
#include "AmsMqttHandler.h"
#include "HomeAssistantStatic.h"
#include "AmsConfiguration.h"
#include "hexutils.h"
class HomeAssistantMqttHandler : public AmsMqttHandler {
public:
HomeAssistantMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, uint8_t boardType, HomeAssistantConfig config, HwTools* hw) : AmsMqttHandler(mqttConfig, debugger, buf) {
#if defined(AMS_REMOTE_DEBUG)
HomeAssistantMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, uint8_t boardType, HomeAssistantConfig config, HwTools* hw, AmsFirmwareUpdater* updater) : AmsMqttHandler(mqttConfig, debugger, buf, updater) {
#else
HomeAssistantMqttHandler(MqttConfig& mqttConfig, Stream* debugger, char* buf, uint8_t boardType, HomeAssistantConfig config, HwTools* hw) : AmsMqttHandler(mqttConfig, debugger, buf) {
#endif
this->boardType = boardType;
this->hw = hw;
l1Init = l2Init = l2eInit = l3Init = l3eInit = l4Init = l4eInit = rtInit = rteInit = pInit = sInit = false;
topic = String(mqttConfig.publishTopic);
if(strlen(config.discoveryNameTag) > 0) {
snprintf_P(buf, 128, PSTR("AMS reader (%s)"), config.discoveryNameTag);
deviceName = String(buf);
snprintf_P(buf, 128, PSTR("[%s] "), config.discoveryNameTag);
sensorNamePrefix = String(buf);
} else {
deviceName = F("AMS reader");
sensorNamePrefix = "";
}
deviceModel = boardTypeToString(boardType);
manufacturer = boardManufacturerToString(boardType);
#if defined(ESP8266)
String hostname = WiFi.hostname();
#elif defined(ESP32)
String hostname = WiFi.getHostname();
#endif
deviceUid = hostname; // Maybe configurable in the future?
if(strlen(config.discoveryHostname) > 0) {
if(strncmp_P(config.discoveryHostname, PSTR("http"), 4) == 0) {
deviceUrl = String(config.discoveryHostname);
} else {
snprintf_P(buf, 128, PSTR("http://%s/"), config.discoveryHostname);
deviceUrl = String(buf);
}
} else {
snprintf_P(buf, 128, PSTR("http://%s.local/"), hostname);
deviceUrl = String(buf);
}
if(strlen(config.discoveryPrefix) > 0) {
snprintf_P(json, 128, PSTR("%s/status"), config.discoveryPrefix);
statusTopic = String(buf);
snprintf_P(buf, 128, PSTR("%s/sensor/"), config.discoveryPrefix);
discoveryTopic = String(buf);
} else {
statusTopic = F("homeassistant/status");
discoveryTopic = F("homeassistant/sensor/");
}
strcpy(this->mqttConfig.subscribeTopic, statusTopic.c_str());
setHomeAssistantConfig(config);
};
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, PriceService* ps);
bool publishTemperatures(AmsConfiguration*, HwTools*);
bool publishPrices(PriceService*);
bool publishSystem(HwTools* hw, PriceService* ps, EnergyAccounting* ea);
bool publishRaw(String data);
bool publishFirmware();
bool postConnect();
void onMessage(String &topic, String &payload);
uint8_t getFormat();
void setHomeAssistantConfig(HomeAssistantConfig config);
private:
String topic;
uint8_t boardType;
String pubTopic;
String subTopic;
String deviceName;
String deviceModel;
@@ -84,12 +51,14 @@ private:
String deviceUrl;
String statusTopic;
String discoveryTopic;
String sensorTopic;
String updateTopic;
String sensorNamePrefix;
bool l1Init, l2Init, l2eInit, l3Init, l3eInit, l4Init, l4eInit, rtInit, rteInit, pInit, sInit;
bool l1Init, l2Init, l2eInit, l3Init, l3eInit, l4Init, l4eInit, rtInit, rteInit, pInit, sInit, rInit, fInit;
bool tInit[32] = {false};
bool prInit[38] = {false};
uint32_t lastThresholdPublish = 0;
HwTools* hw;
@@ -113,6 +82,7 @@ private:
void publishTemperatureSensor(uint8_t index, String id);
void publishPriceSensors(PriceService* ps);
void publishSystemSensors();
void publishThresholdSensors();
String boardTypeToString(uint8_t b) {
switch(b) {

View File

@@ -20,9 +20,10 @@ struct HomeAssistantSensor {
};
const uint8_t List1SensorCount PROGMEM = 1;
const uint8_t List1SensorCount PROGMEM = 2;
const HomeAssistantSensor List1Sensors[List1SensorCount] PROGMEM = {
{"Active import", "/power", "P", 30, "W", "power", "measurement"}
{"Active import", "/power", "P", 30, "W", "power", "measurement"},
{"Data timestamp", "/power", "t", 30, "", "timestamp", ""}
};
const uint8_t List2SensorCount PROGMEM = 8;
@@ -42,11 +43,12 @@ const HomeAssistantSensor List2ExportSensors[List2ExportSensorCount] PROGMEM = {
{"Active export", "/power", "PO", 30, "W", "power", "measurement"}
};
const uint8_t List3SensorCount PROGMEM = 3;
const uint8_t List3SensorCount PROGMEM = 4;
const HomeAssistantSensor List3Sensors[List3SensorCount] PROGMEM = {
{"Accumulated active import", "/energy", "tPI", 4000, "kWh", "energy", "total_increasing"},
{"Accumulated reactive import","/energy", "tQI", 4000, "kvarh","", "total_increasing"},
{"Accumulated reactive export","/energy", "tQO", 4000, "kvarh","", "total_increasing"}
{"Accumulated reactive export","/energy", "tQO", 4000, "kvarh","", "total_increasing"},
{"Meter timestamp", "/energy", "rtc", 4000, "", "timestamp", ""}
};
const uint8_t List3ExportSensorCount PROGMEM = 1;
@@ -80,8 +82,8 @@ const HomeAssistantSensor List4ExportSensors[List4ExportSensorCount] PROGMEM = {
const uint8_t RealtimeSensorCount PROGMEM = 8;
const HomeAssistantSensor RealtimeSensors[RealtimeSensorCount] PROGMEM = {
{"Month max", "/realtime","max", 120, "kWh", "energy", "total_increasing"},
{"Tariff threshold", "/realtime","threshold", 120, "kWh", "energy", "total_increasing"},
{"Month max", "/realtime","max", 120, "kWh", "energy", ""},
{"Tariff threshold", "/realtime","threshold", 120, "kWh", "energy", ""},
{"Current hour used", "/realtime","hour.use", 120, "kWh", "energy", "total_increasing"},
{"Current hour cost", "/realtime","hour.cost", 120, "", "monetary", ""},
{"Current day used", "/realtime","day.use", 120, "kWh", "energy", "total_increasing"},
@@ -101,6 +103,7 @@ const HomeAssistantSensor RealtimeExportSensors[RealtimeExportSensorCount] PROGM
};
const HomeAssistantSensor RealtimePeakSensor PROGMEM = {"Current month peak %d", "/realtime", "peaks[%d]", 4000, "kWh", "energy", ""};
const HomeAssistantSensor RealtimeThresholdSensor PROGMEM = {"Tariff threshold %d", "/realtime", "thresholds[%d]", 4000, "kWh", "energy", ""};
const uint8_t PriceSensorCount PROGMEM = 5;
const HomeAssistantSensor PriceSensors[PriceSensorCount] PROGMEM = {
@@ -117,7 +120,7 @@ const uint8_t SystemSensorCount PROGMEM = 3;
const HomeAssistantSensor SystemSensors[SystemSensorCount] PROGMEM = {
{"Status", "/state", "rssi", 180, "dBm", "signal_strength", "measurement"},
{"Supply volt", "/state", "vcc", 180, "V", "voltage", "measurement"},
{"Uptime", "/state", "up", 180, "s", "uptime", "measurement"}
{"Uptime", "/state", "up", 180, "s", "duration", "measurement"}
};
const HomeAssistantSensor TemperatureSensor PROGMEM = {"Temperature sensor %s", "/temperatures", "temperatures['%s']", 900, "°C", "temperature", "measurement"};

View File

@@ -1,3 +1,4 @@
{
"P" : %d
"P" : %lu,
"t" : "%s"
}

View File

@@ -3,5 +3,6 @@
"tPO" : %.3f,
"tQI" : %.3f,
"tQO" : %.3f,
"rtc" : %lu
"rtc" : "%s",
"t" : "%s"
}

View File

@@ -2,14 +2,15 @@
"lv" : "%s",
"id" : "%s",
"type" : "%s",
"P" : %d,
"Q" : %d,
"PO" : %d,
"QO" : %d,
"P" : %lu,
"Q" : %lu,
"PO" : %lu,
"QO" : %lu,
"I1" : %.2f,
"I2" : %.2f,
"I3" : %.2f,
"U1" : %.2f,
"U2" : %.2f,
"U3" : %.2f
"U3" : %.2f,
"t" : "%s"
}

View File

@@ -2,30 +2,31 @@
"lv" : "%s",
"id" : "%s",
"type" : "%s",
"P" : %d,
"P1" : %d,
"P2" : %d,
"P3" : %d,
"Q" : %d,
"PO" : %d,
"PO1" : %d,
"PO2" : %d,
"PO3" : %d,
"QO" : %d,
"P" : %lu,
"P1" : %lu,
"P2" : %lu,
"P3" : %lu,
"Q" : %lu,
"PO" : %lu,
"PO1" : %lu,
"PO2" : %lu,
"PO3" : %lu,
"QO" : %lu,
"I1" : %.2f,
"I2" : %.2f,
"I3" : %.2f,
"U1" : %.2f,
"U2" : %.2f,
"U3" : %.2f,
"PF" : %.2f,
"PF1" : %.2f,
"PF2" : %.2f,
"PF3" : %.2f,
"PF" : %d,
"PF1" : %d,
"PF2" : %d,
"PF3" : %d,
"tPI1" : %.3f,
"tPI2" : %.3f,
"tPI3" : %.3f,
"tPO1" : %.3f,
"tPO2" : %.3f,
"tPO3" : %.3f
"tPO3" : %.3f,
"t" : "%s"
}

View File

@@ -1,23 +0,0 @@
{
"max" : %.1f,
"peaks" : [ %s ],
"threshold" : %d,
"hour" : {
"use" : %.2f,
"cost" : %.2f,
"produced" : %.2f,
"income" : %.2f
},
"day" : {
"use" : %.2f,
"cost" : %.2f,
"produced" : %.2f,
"income" : %.2f
},
"month" : {
"use" : %.2f,
"cost" : %.2f,
"produced" : %.2f,
"income" : %.2f
}
}

View File

@@ -13,50 +13,150 @@
#include "json/ha3_json.h"
#include "json/ha4_json.h"
#include "json/hadiscover_json.h"
#include "json/realtime_json.h"
#include "FirmwareVersion.h"
#if defined(ESP32)
#include <esp_task_wdt.h>
#endif
bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, PriceService* ps) {
if(topic.isEmpty() || !mqtt.connected())
void HomeAssistantMqttHandler::setHomeAssistantConfig(HomeAssistantConfig config) {
l1Init = l2Init = l2eInit = l3Init = l3eInit = l4Init = l4eInit = rtInit = rteInit = pInit = sInit = rInit = fInit = false;
pubTopic = String(mqttConfig.publishTopic);
subTopic = String(mqttConfig.subscribeTopic);
if(subTopic.isEmpty()) subTopic = pubTopic+"/command";
if(strlen(config.discoveryNameTag) > 0) {
snprintf_P(json, 128, PSTR("AMS reader (%s)"), config.discoveryNameTag);
deviceName = String(json);
snprintf_P(json, 128, PSTR("[%s] "), config.discoveryNameTag);
sensorNamePrefix = String(json);
} else {
deviceName = F("AMS reader");
sensorNamePrefix = "";
}
deviceModel = boardTypeToString(boardType);
manufacturer = boardManufacturerToString(boardType);
char hostname[32];
#if defined(ESP8266)
strcpy(hostname, WiFi.hostname().c_str());
#elif defined(ESP32)
strcpy(hostname, WiFi.getHostname());
#endif
stripNonAscii((uint8_t*) hostname, 32, false);
deviceUid = String(hostname); // Maybe configurable in the future?
if(strlen(config.discoveryHostname) > 0) {
if(strncmp_P(config.discoveryHostname, PSTR("http"), 4) == 0) {
deviceUrl = String(config.discoveryHostname);
} else {
snprintf_P(json, 128, PSTR("http://%s/"), config.discoveryHostname);
deviceUrl = String(json);
}
} else {
snprintf_P(json, 128, PSTR("http://%s.local/"), hostname);
deviceUrl = String(json);
}
if(strlen(config.discoveryPrefix) > 0) {
snprintf_P(json, 128, PSTR("%s/status"), config.discoveryPrefix);
statusTopic = String(json);
snprintf_P(json, 128, PSTR("%s/sensor"), config.discoveryPrefix);
sensorTopic = String(json);
snprintf_P(json, 128, PSTR("%s/update"), config.discoveryPrefix);
updateTopic = String(json);
} else {
statusTopic = F("homeassistant/status");
sensorTopic = F("homeassistant/sensor");
updateTopic = F("homeassistant/update");
}
strcpy(this->mqttConfig.subscribeTopic, statusTopic.c_str());
}
bool HomeAssistantMqttHandler::postConnect() {
if(!subTopic.isEmpty() && !mqtt.subscribe(subTopic)) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR(" Unable to subscribe to to [%s]\n"), subTopic.c_str());
return false;
}
return true;
}
bool HomeAssistantMqttHandler::publish(AmsData* update, AmsData* previousState, EnergyAccounting* ea, PriceService* ps) {
if(pubTopic.isEmpty() || !mqtt.connected())
return false;
if(time(nullptr) < FirmwareVersion::BuildEpoch)
return false;
if(data->getListType() >= 3) { // publish energy counts
publishList3(data, ea);
loop();
AmsData data;
if(mqttConfig.stateUpdate) {
uint64_t now = millis64();
if(now-lastStateUpdate < mqttConfig.stateUpdateInterval * 1000) return false;
data.apply(*previousState);
data.apply(*update);
lastStateUpdate = now;
} else {
data = *update;
}
if(data->getListType() == 1) { // publish power counts
publishList1(data, ea);
} else if(data->getListType() <= 3) { // publish power counts and volts/amps
publishList2(data, ea);
} else if(data->getListType() == 4) { // publish power counts and volts/amps/phase power and PF
publishList4(data, ea);
if(data.getListType() >= 3 && !data.isCounterEstimated()) { // publish energy counts
publishList3(&data, ea);
mqtt.loop();
}
if(data.getListType() == 1) { // publish power counts
publishList1(&data, ea);
mqtt.loop();
} else if(data.getListType() <= 3) { // publish power counts and volts/amps
publishList2(&data, ea);
mqtt.loop();
} else if(data.getListType() == 4) { // publish power counts and volts/amps/phase power and PF
publishList4(&data, ea);
mqtt.loop();
}
loop();
if(ea->isInitialized()) {
publishRealtime(data, ea, ps);
loop();
publishRealtime(&data, ea, ps);
mqtt.loop();
}
loop();
return true;
}
bool HomeAssistantMqttHandler::publishList1(AmsData* data, EnergyAccounting* ea) {
publishList1Sensors();
snprintf_P(json, BufferSize, HA1_JSON, data->getActiveImportPower());
return mqtt.publish(topic + "/power", json);
char pt[24];
memset(pt, 0, 24);
if(data->getPackageTimestamp() > 0) {
tmElements_t tm;
breakTime(data->getPackageTimestamp(), tm);
sprintf_P(pt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
}
snprintf_P(json, BufferSize, HA1_JSON, data->getActiveImportPower(), pt);
return mqtt.publish(pubTopic + "/power", json);
}
bool HomeAssistantMqttHandler::publishList2(AmsData* data, EnergyAccounting* ea) {
publishList2Sensors();
if(data->getActiveExportPower() > 0) publishList2ExportSensors();
char pt[24];
memset(pt, 0, 24);
if(data->getPackageTimestamp() > 0) {
tmElements_t tm;
breakTime(data->getPackageTimestamp(), tm);
sprintf_P(pt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
}
snprintf_P(json, BufferSize, HA3_JSON,
data->getListId().c_str(),
data->getMeterId().c_str(),
@@ -70,27 +170,55 @@ bool HomeAssistantMqttHandler::publishList2(AmsData* data, EnergyAccounting* ea)
data->getL3Current(),
data->getL1Voltage(),
data->getL2Voltage(),
data->getL3Voltage()
data->getL3Voltage(),
pt
);
return mqtt.publish(topic + "/power", json);
return mqtt.publish(pubTopic + "/power", json);
}
bool HomeAssistantMqttHandler::publishList3(AmsData* data, EnergyAccounting* ea) {
publishList3Sensors();
if(data->getActiveExportCounter() > 0.0) publishList3ExportSensors();
char mt[24];
memset(mt, 0, 24);
if(data->getMeterTimestamp() > 0) {
tmElements_t tm;
breakTime(data->getMeterTimestamp(), tm);
sprintf_P(mt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
}
char pt[24];
memset(pt, 0, 24);
if(data->getPackageTimestamp() > 0) {
tmElements_t tm;
breakTime(data->getPackageTimestamp(), tm);
sprintf_P(pt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
}
snprintf_P(json, BufferSize, HA2_JSON,
data->getActiveImportCounter(),
data->getActiveExportCounter(),
data->getReactiveImportCounter(),
data->getReactiveExportCounter(),
data->getMeterTimestamp()
mt,
pt
);
return mqtt.publish(topic + "/energy", json);
return mqtt.publish(pubTopic + "/energy", json);
}
bool HomeAssistantMqttHandler::publishList4(AmsData* data, EnergyAccounting* ea) {
publishList4Sensors();
if(data->getL1ActiveExportPower() > 0 || data->getL2ActiveExportPower() > 0 || data->getL3ActiveExportPower() > 0) publishList4ExportSensors();
char pt[24];
memset(pt, 0, 24);
if(data->getPackageTimestamp() > 0) {
tmElements_t tm;
breakTime(data->getPackageTimestamp(), tm);
sprintf_P(pt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
}
snprintf_P(json, BufferSize, HA4_JSON,
data->getListId().c_str(),
data->getMeterId().c_str(),
@@ -111,18 +239,19 @@ bool HomeAssistantMqttHandler::publishList4(AmsData* data, EnergyAccounting* ea)
data->getL1Voltage(),
data->getL2Voltage(),
data->getL3Voltage(),
data->getPowerFactor() == 0 ? 1 : data->getPowerFactor(),
data->getPowerFactor() == 0 ? 1 : data->getL1PowerFactor(),
data->getPowerFactor() == 0 ? 1 : data->getL2PowerFactor(),
data->getPowerFactor() == 0 ? 1 : data->getL3PowerFactor(),
data->getPowerFactor() == 0 ? 100 : (int) (data->getPowerFactor() * 100),
data->getPowerFactor() == 0 ? 100 : (int) (data->getL1PowerFactor() * 100),
data->getPowerFactor() == 0 ? 100 : (int) (data->getL2PowerFactor() * 100),
data->getPowerFactor() == 0 ? 100 : (int) (data->getL3PowerFactor() * 100),
data->getL1ActiveImportCounter(),
data->getL2ActiveImportCounter(),
data->getL3ActiveImportCounter(),
data->getL1ActiveExportCounter(),
data->getL2ActiveExportCounter(),
data->getL3ActiveExportCounter()
data->getL3ActiveExportCounter(),
pt
);
return mqtt.publish(topic + "/power", json);
return mqtt.publish(pubTopic + "/power", json);
}
String HomeAssistantMqttHandler::getMeterModel(AmsData* data) {
@@ -134,6 +263,7 @@ String HomeAssistantMqttHandler::getMeterModel(AmsData* data) {
bool HomeAssistantMqttHandler::publishRealtime(AmsData* data, EnergyAccounting* ea, PriceService* ps) {
publishRealtimeSensors(ea, ps);
if(ea->getProducedThisHour() > 0.0 || ea->getProducedToday() > 0.0 || ea->getProducedThisMonth() > 0.0) publishRealtimeExportSensors(ea, ps);
if(lastThresholdPublish == 0) publishThresholdSensors();
String peaks = "";
uint8_t peakCount = ea->getConfig()->hours;
if(peakCount > 5) peakCount = 5;
@@ -141,7 +271,7 @@ bool HomeAssistantMqttHandler::publishRealtime(AmsData* data, EnergyAccounting*
if(!peaks.isEmpty()) peaks += ",";
peaks += String(ea->getPeak(i).value / 100.0, 2);
}
snprintf_P(json, BufferSize, REALTIME_JSON,
uint16_t pos = snprintf_P(json, BufferSize, PSTR("{\"max\":%.1f,\"peaks\":[%s],\"threshold\":%d,\"hour\":{\"use\":%.2f,\"cost\":%.2f,\"produced\":%.2f,\"income\":%.2f},\"day\":{\"use\":%.2f,\"cost\":%.2f,\"produced\":%.2f,\"income\":%.2f},\"month\":{\"use\":%.2f,\"cost\":%.2f,\"produced\":%.2f,\"income\":%.2f}"),
ea->getMonthMax(),
peaks.c_str(),
ea->getCurrentThreshold(),
@@ -158,25 +288,50 @@ bool HomeAssistantMqttHandler::publishRealtime(AmsData* data, EnergyAccounting*
ea->getProducedThisMonth(),
ea->getIncomeThisMonth()
);
return mqtt.publish(topic + "/realtime", json);
}
uint32_t ms = millis();
if(lastThresholdPublish == 0 || ms-lastThresholdPublish > 3600000) {
EnergyAccountingConfig* conf = ea->getConfig();
pos += snprintf_P(json+pos, BufferSize-pos, PSTR(",\"thresholds\": [%d,%d,%d,%d,%d,%d,%d,%d,%d]"),
conf->thresholds[0],
conf->thresholds[1],
conf->thresholds[2],
conf->thresholds[3],
conf->thresholds[4],
conf->thresholds[5],
conf->thresholds[6],
conf->thresholds[7],
conf->thresholds[8]
);
lastThresholdPublish = ms;
}
time_t now = time(nullptr);
char pt[24];
memset(pt, 0, 24);
if(now > 0) {
tmElements_t tm;
breakTime(now, tm);
sprintf_P(pt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
}
pos += snprintf_P(json+pos, BufferSize-pos, PSTR(",\"t\":\"%s\""), pt);
json[pos++] = '}';
json[pos] = '\0';
return mqtt.publish(pubTopic + "/realtime", json);
}
bool HomeAssistantMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw) {
int count = hw->getTempSensorCount();
if(count < 2) return false;
int size = 32 + (count * 26);
char buf[size];
snprintf_P(buf, 24, PSTR("{\"temperatures\":{"));
uint16_t pos = snprintf_P(json, 24, PSTR("{\"temperatures\":{"));
for(int i = 0; i < count; i++) {
TempSensorData* data = hw->getTempSensorData(i);
if(data != NULL) {
char* pos = buf+strlen(buf);
String id = toHex(data->address, 8);
snprintf_P(pos, 26, PSTR("\"%s\":%.2f,"),
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"%s\":%.2f,"),
id.c_str(),
data->lastRead
);
@@ -184,15 +339,26 @@ bool HomeAssistantMqttHandler::publishTemperatures(AmsConfiguration* config, HwT
publishTemperatureSensor(i+1, id);
}
}
char* pos = buf+strlen(buf);
snprintf_P(count == 0 ? pos : pos-1, 8, PSTR("}}"));
bool ret = mqtt.publish(topic + "/temperatures", buf);
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("}"));
time_t now = time(nullptr);
char pt[24];
memset(pt, 0, 24);
if(now > 0) {
tmElements_t tm;
breakTime(now, tm);
sprintf_P(pt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
}
pos += snprintf_P(json+pos, BufferSize-pos, PSTR(",\"t\":\"%s\""), pt);
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("}"));
bool ret = mqtt.publish(pubTopic + "/temperatures", json);
loop();
return ret;
}
bool HomeAssistantMqttHandler::publishPrices(PriceService* ps) {
if(topic.isEmpty() || !mqtt.connected())
if(pubTopic.isEmpty() || !mqtt.connected())
return false;
if(ps->getValueForHour(PRICE_DIRECTION_IMPORT, 0) == PRICE_NO_VALUE)
return false;
@@ -285,7 +451,7 @@ bool HomeAssistantMqttHandler::publishPrices(PriceService* ps) {
}
}
snprintf_P(json+pos, BufferSize-pos, PSTR("\"min\":%.4f,\"max\":%.4f,\"cheapest1hr\":\"%s\",\"cheapest3hr\":\"%s\",\"cheapest6hr\":\"%s\"}}"),
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"min\":%.4f,\"max\":%.4f,\"cheapest1hr\":\"%s\",\"cheapest3hr\":\"%s\",\"cheapest6hr\":\"%s\"}"),
min == INT16_MAX ? 0.0 : min,
max == INT16_MIN ? 0.0 : max,
ts1hr,
@@ -293,28 +459,57 @@ bool HomeAssistantMqttHandler::publishPrices(PriceService* ps) {
ts6hr
);
bool ret = mqtt.publish(topic + "/prices", json, true, 0);
float val = ps->getValueForHour(PRICE_DIRECTION_EXPORT, now, 0);
if(val == PRICE_NO_VALUE) {
pos += snprintf_P(json+pos, BufferSize-pos, PSTR(",\"exportprices\":{\"0\":null}"));
} else {
pos += snprintf_P(json+pos, BufferSize-pos, PSTR(",\"exportprices\":{\"0\":%.4f}"), val);
}
char pt[24];
memset(pt, 0, 24);
if(now > 0) {
tmElements_t tm;
breakTime(now, tm);
sprintf_P(pt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
}
pos += snprintf_P(json+pos, BufferSize-pos, PSTR(",\"t\":\"%s\""), pt);
json[pos++] = '}';
json[pos] = '\0';
bool ret = mqtt.publish(pubTopic + "/prices", json, true, 0);
loop();
return ret;
}
bool HomeAssistantMqttHandler::publishSystem(HwTools* hw, PriceService* ps, EnergyAccounting* ea) {
if(topic.isEmpty() || !mqtt.connected())
if(pubTopic.isEmpty() || !mqtt.connected())
return false;
publishSystemSensors();
if(hw->getTemperature() > -50) publishTemperatureSensor(0, "");
snprintf_P(json, BufferSize, PSTR("{\"id\":\"%s\",\"name\":\"%s\",\"up\":%d,\"vcc\":%.3f,\"rssi\":%d,\"temp\":%.2f,\"version\":\"%s\"}"),
time_t now = time(nullptr);
char pt[24];
memset(pt, 0, 24);
if(now > 0) {
tmElements_t tm;
breakTime(now, tm);
sprintf_P(pt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
}
snprintf_P(json, BufferSize, PSTR("{\"id\":\"%s\",\"name\":\"%s\",\"up\":%d,\"vcc\":%.3f,\"rssi\":%d,\"temp\":%.2f,\"version\":\"%s\",\"t\":\"%s\"}"),
WiFi.macAddress().c_str(),
mqttConfig.clientId,
(uint32_t) (millis64()/1000),
hw->getVcc(),
hw->getWifiRssi(),
hw->getTemperature(),
FirmwareVersion::VersionString
FirmwareVersion::VersionString,
pt
);
bool ret = mqtt.publish(topic + "/state", json);
bool ret = mqtt.publish(pubTopic + "/state", json);
loop();
return ret;
}
@@ -347,7 +542,7 @@ void HomeAssistantMqttHandler::publishSensor(const HomeAssistantSensor sensor) {
strlen_P(sensor.stacl) > 0 ? (char *) FPSTR(sensor.stacl) : "",
strlen_P(sensor.stacl) > 0 ? "\"" : ""
);
mqtt.publish(discoveryTopic + deviceUid + "_" + uid.c_str() + "/config", json, true, 0);
mqtt.publish(sensorTopic + "/" + deviceUid + "_" + uid.c_str() + "/config", json, true, 0);
loop();
}
@@ -517,6 +712,21 @@ void HomeAssistantMqttHandler::publishPriceSensors(PriceService* ps) {
prInit[i] = true;
}
float exportPrice = ps->getValueForHour(PRICE_DIRECTION_EXPORT, 0);
if(exportPrice != PRICE_NO_VALUE) {
char path[20];
snprintf(path, 20, "exportprices['%d']", 0);
HomeAssistantSensor sensor = {
"Export price current hour",
PriceSensor.topic,
path,
PriceSensor.ttl,
uom.c_str(),
PriceSensor.devcl,
"total"
};
publishSensor(sensor);
}
}
void HomeAssistantMqttHandler::publishSystemSensors() {
@@ -527,6 +737,27 @@ void HomeAssistantMqttHandler::publishSystemSensors() {
sInit = true;
}
void HomeAssistantMqttHandler::publishThresholdSensors() {
if(rInit) return;
for(uint8_t i = 0; i < 9; i++) {
char name[strlen(RealtimeThresholdSensor.name)+1];
snprintf(name, strlen(RealtimeThresholdSensor.name)+2, RealtimeThresholdSensor.name, i+1);
char path[strlen(RealtimeThresholdSensor.path)+1];
snprintf(path, strlen(RealtimeThresholdSensor.path)+1, RealtimeThresholdSensor.path, i);
HomeAssistantSensor sensor = {
name,
RealtimeThresholdSensor.topic,
path,
RealtimeThresholdSensor.ttl,
RealtimeThresholdSensor.uom,
RealtimeThresholdSensor.devcl,
RealtimeThresholdSensor.stacl
};
publishSensor(sensor);
}
rInit = true;
}
uint8_t HomeAssistantMqttHandler::getFormat() {
return 4;
}
@@ -535,13 +766,44 @@ bool HomeAssistantMqttHandler::publishRaw(String data) {
return false;
}
bool HomeAssistantMqttHandler::publishFirmware() {
if(!fInit) {
snprintf_P(json, BufferSize, PSTR("{\"name\":\"%sFirmware\",\"stat_t\":\"%s/firmware\",\"dev_cla\":\"firmware\",\"cmd_t\":\"%s\",\"pl_inst\":\"fwupgrade\"}"),
sensorNamePrefix.c_str(),
pubTopic.c_str(),
subTopic.c_str()
);
fInit = mqtt.publish(updateTopic + "/" + deviceUid + "/config", json, true, 0);
loop();
return fInit;
}
snprintf_P(json, BufferSize, PSTR("{\"installed_version\":\"%s\",\"latest_version\":\"%s\",\"title\":\"amsreader firmware\",\"release_url\":\"https://github.com/UtilitechAS/amsreader-firmware/releases\",\"release_summary\":\"New version %s is available\",\"update_percentage\":%s}"),
FirmwareVersion::VersionString,
strlen(updater->getNextVersion()) == 0 ? FirmwareVersion::VersionString : updater->getNextVersion(),
strlen(updater->getNextVersion()) == 0 ? FirmwareVersion::VersionString : updater->getNextVersion(),
updater->getProgress() < 0 ? "null" : String(updater->getProgress(), 0)
);
bool ret = mqtt.publish(pubTopic + "/firmware", json);
loop();
return ret;
}
void HomeAssistantMqttHandler::onMessage(String &topic, String &payload) {
if(topic.equals(statusTopic)) {
if(payload.equals("online")) {
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Received online status from HA, resetting sensor status\n"));
l1Init = l2Init = l2eInit = l3Init = l3eInit = l4Init = l4eInit = rtInit = rteInit = pInit = sInit = false;
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("Received online status from HA, resetting sensor status\n"));
l1Init = l2Init = l2eInit = l3Init = l3eInit = l4Init = l4eInit = rtInit = rteInit = pInit = sInit = rInit = false;
for(uint8_t i = 0; i < 32; i++) tInit[i] = false;
for(uint8_t i = 0; i < 38; i++) prInit[i] = false;
}
} else if(topic.equals(subTopic)) {
if(payload.equals("fwupgrade")) {
if(strcmp(updater->getNextVersion(), FirmwareVersion::VersionString) != 0) {
updater->setTargetVersion(updater->getNextVersion());
}
}
}
}

View File

@@ -43,7 +43,7 @@ struct AdcConfig {
class HwTools {
public:
bool applyBoardConfig(uint8_t boardType, GpioConfig& gpioConfig, MeterConfig& meterConfig, uint8_t hanPin);
void setup(GpioConfig*);
void setup(SystemConfig* sys, GpioConfig* gpio);
float getVcc();
uint8_t getTempSensorCount();
TempSensorData* getTempSensorData(uint8_t);
@@ -56,15 +56,24 @@ public:
bool ledOff(uint8_t color);
bool ledBlink(uint8_t color, uint8_t blink);
void setBootSuccessful(bool value);
bool isVoltageOptimal(float range = 0.4);
uint8_t getBoardType();
HwTools() {};
private:
uint8_t boardType;
uint8_t ledPin, redPin, greenPin, bluePin, tempPin, atempPin;
uint8_t ledDisablePin, ledBehaviour;
bool ledInvert, rgbInvert;
uint8_t vccPin, vccGnd_r, vccVcc_r;
float vccOffset, vccMultiplier;
float maxVcc = 3.25; // Best to have this close to max as a start, in case Pow-U reboots and starts off with a low voltage, we dont want that to be perceived as max
uint16_t analogRange = 1024;
AdcConfig voltAdc, tempAdc;
#if defined(ESP32)
esp_adc_cal_characteristics_t* voltAdcChar, tempAdcChar;
#endif
GpioConfig* config;
bool tempSensorInit;
OneWire *oneWire = NULL;
DallasTemperature *sensorApi = NULL;

View File

@@ -75,6 +75,9 @@ bool HwTools::applyBoardConfig(uint8_t boardType, GpioConfig& gpioConfig, MeterC
case 243: // WT32-ETH01
meterConfig.rxPin = hanPin > 0 ? hanPin : 39;
return true;
case 245: // wESP32
gpioConfig.apPin = 0;
meterConfig.rxPin = hanPin > 0 ? hanPin : 39;
case 201: // D32
meterConfig.rxPin = hanPin > 0 ? hanPin : 16;
gpioConfig.apPin = 4;
@@ -144,8 +147,8 @@ bool HwTools::applyBoardConfig(uint8_t boardType, GpioConfig& gpioConfig, MeterC
return false;
}
void HwTools::setup(GpioConfig* config) {
this->config = config;
void HwTools::setup(SystemConfig* sys, GpioConfig* config) {
this->boardType = sys->boardType;
this->tempSensorInit = false;
if(sensorApi != NULL)
delete sensorApi;
@@ -153,8 +156,9 @@ void HwTools::setup(GpioConfig* config) {
delete oneWire;
if(config->tempSensorPin > 0 && config->tempSensorPin < 40) {
pinMode(config->tempSensorPin, INPUT);
tempPin = config->tempSensorPin;
} else {
config->tempSensorPin = 0xFF;
tempPin = config->tempSensorPin = 0xFF;
}
#if defined(CONFIG_IDF_TARGET_ESP32S2)
@@ -196,47 +200,63 @@ void HwTools::setup(GpioConfig* config) {
#else
pinMode(config->vccPin, INPUT);
#endif
vccPin = config->vccPin;
} else {
voltAdc.unit = 0xFF;
voltAdc.channel = 0xFF;
config->vccPin = 0xFF;
vccPin = config->vccPin = 0xFF;
}
vccOffset = config->vccOffset / 100.0;
vccMultiplier = config->vccMultiplier / 1000.0;
vccGnd_r = config->vccResistorGnd;
vccVcc_r = config->vccResistorVcc;
if(config->tempAnalogSensorPin > 0 && config->tempAnalogSensorPin < 40) {
pinMode(config->tempAnalogSensorPin, INPUT);
atempPin = config->tempAnalogSensorPin;
} else {
config->tempAnalogSensorPin = 0xFF;
atempPin = config->tempAnalogSensorPin = 0xFF;
}
if(config->ledPin > 0 && config->ledPin < 40) {
pinMode(config->ledPin, OUTPUT);
ledPin = config->ledPin;
ledInvert = config->ledInverted;
ledOff(LED_INTERNAL);
} else {
config->ledPin = 0xFF;
ledPin = config->ledPin = 0xFF;
}
if(config->ledPinRed > 0 && config->ledPinRed < 40) {
pinMode(config->ledPinRed, OUTPUT);
redPin = config->ledPinRed;
ledOff(LED_RED);
} else {
config->ledPinRed = 0xFF;
redPin = config->ledPinRed = 0xFF;
}
if(config->ledPinGreen > 0 && config->ledPinGreen < 40) {
pinMode(config->ledPinGreen, OUTPUT);
greenPin = config->ledPinGreen;
ledOff(LED_GREEN);
} else {
config->ledPinGreen = 0xFF;
greenPin = config->ledPinGreen = 0xFF;
}
if(config->ledPinBlue > 0 && config->ledPinBlue < 40) {
pinMode(config->ledPinBlue, OUTPUT);
bluePin = config->ledPinBlue;
ledOff(LED_BLUE);
} else {
config->ledPinBlue = 0xFF;
bluePin = config->ledPinBlue = 0xFF;
}
rgbInvert = config->ledRgbInverted;
if(config->ledDisablePin > 0 && config->ledDisablePin < 40) {
pinMode(config->ledDisablePin, OUTPUT_OPEN_DRAIN);
ledDisablePin = config->ledDisablePin;
ledBehaviour = config->ledBehaviour;
setBootSuccessful(false);
}
}
@@ -362,7 +382,7 @@ void HwTools::getAdcChannel(uint8_t pin, AdcConfig& config) {
float HwTools::getVcc() {
float volts = 0.0;
if(config->vccPin != 0xFF) {
if(vccPin != 0xFF) {
#if defined(ESP32)
if(voltAdc.unit != 0xFF) {
uint32_t x = 0;
@@ -385,32 +405,35 @@ float HwTools::getVcc() {
} else {
uint32_t x = 0;
for (int i = 0; i < 10; i++) {
x += analogRead(config->vccPin);
x += analogRead(vccPin);
}
volts = (x * 3.3) / 10.0 / analogRange;
}
#else
uint32_t x = 0;
for (int i = 0; i < 10; i++) {
x += analogRead(config->vccPin);
x += analogRead(vccPin);
}
volts = (x * 3.3) / 10.0 / analogRange;
#endif
} else {
}
if(volts == 0.0) {
#if defined(ESP8266)
volts = ESP.getVcc() / 1024.0;
#else
return 0.0;
#endif
}
if(volts == 0.0) return 0.0;
if(config->vccResistorGnd > 0 && config->vccResistorVcc > 0) {
volts *= ((float) (config->vccResistorGnd + config->vccResistorVcc) / config->vccResistorGnd);
}
float vccOffset = config->vccOffset / 100.0;
float vccMultiplier = config->vccMultiplier / 1000.0;
return vccOffset + (volts > 0.0 ? volts * vccMultiplier : 0.0);
if(vccGnd_r > 0 && vccVcc_r > 0)
volts *= ((float) (vccGnd_r + vccVcc_r) / vccGnd_r);
if(vccOffset != 0.0)
volts += vccOffset;
if(vccMultiplier != 0.0)
volts *= vccMultiplier;
return volts;
}
uint8_t HwTools::getTempSensorCount() {
@@ -425,9 +448,9 @@ TempSensorData* HwTools::getTempSensorData(uint8_t i) {
}
bool HwTools::updateTemperatures() {
if(config->tempSensorPin != 0xFF) {
if(tempPin != 0xFF) {
if(!tempSensorInit) {
oneWire = new OneWire(config->tempSensorPin);
oneWire = new OneWire(tempPin);
sensorApi = new DallasTemperature(this->oneWire);
sensorApi->begin();
delay(100);
@@ -513,9 +536,9 @@ float HwTools::getTemperature() {
return c == 0 ? DEVICE_DISCONNECTED_C : ret/c;
}
float HwTools::getTemperatureAnalog() {
if(config->tempAnalogSensorPin != 0xFF) {
if(atempPin != 0xFF) {
float adcCalibrationFactor = 1.06587;
int volts = ((float) analogRead(config->tempAnalogSensorPin) / analogRange) * 3.3;
int volts = ((float) analogRead(atempPin) / analogRange) * 3.3;
return ((volts * adcCalibrationFactor) - 0.4) / 0.0195;
}
return DEVICE_DISCONNECTED_C;
@@ -529,42 +552,42 @@ int HwTools::getWifiRssi() {
void HwTools::setBootSuccessful(bool value) {
if(bootSuccessful && value) return;
bootSuccessful = value;
if(config->ledDisablePin > 0 && config->ledDisablePin < 40) {
switch(config->ledBehaviour) {
if(ledDisablePin > 0 && ledDisablePin < 40) {
switch(ledBehaviour) {
case LED_BEHAVIOUR_ERROR_ONLY:
case LED_BEHAVIOUR_OFF:
digitalWrite(config->ledDisablePin, LOW);
digitalWrite(ledDisablePin, LOW);
break;
case LED_BEHAVIOUR_BOOT:
if(bootSuccessful) {
digitalWrite(config->ledDisablePin, LOW);
digitalWrite(ledDisablePin, LOW);
} else {
digitalWrite(config->ledDisablePin, HIGH);
digitalWrite(ledDisablePin, HIGH);
}
break;
default:
digitalWrite(config->ledDisablePin, HIGH);
digitalWrite(ledDisablePin, HIGH);
}
}
}
bool HwTools::ledOn(uint8_t color) {
if(config->ledBehaviour == LED_BEHAVIOUR_OFF) return false;
if(config->ledBehaviour == LED_BEHAVIOUR_ERROR_ONLY && color != LED_RED) return false;
if(config->ledBehaviour == LED_BEHAVIOUR_BOOT && color != LED_RED && bootSuccessful) return false;
if(ledBehaviour == LED_BEHAVIOUR_OFF) return false;
if(ledBehaviour == LED_BEHAVIOUR_ERROR_ONLY && color != LED_RED) return false;
if(ledBehaviour == LED_BEHAVIOUR_BOOT && color != LED_RED && bootSuccessful) return false;
if(color == LED_INTERNAL) {
return writeLedPin(color, config->ledInverted ? LOW : HIGH);
return writeLedPin(color, ledInvert ? LOW : HIGH);
} else {
return writeLedPin(color, config->ledRgbInverted ? LOW : HIGH);
return writeLedPin(color, rgbInvert ? LOW : HIGH);
}
}
bool HwTools::ledOff(uint8_t color) {
if(color == LED_INTERNAL) {
return writeLedPin(color, config->ledInverted ? HIGH : LOW);
return writeLedPin(color, ledInvert ? HIGH : LOW);
} else {
return writeLedPin(color, config->ledRgbInverted ? HIGH : LOW);
return writeLedPin(color, rgbInvert ? HIGH : LOW);
}
}
@@ -581,8 +604,8 @@ bool HwTools::ledBlink(uint8_t color, uint8_t blink) {
bool HwTools::writeLedPin(uint8_t color, uint8_t state) {
switch(color) {
case LED_INTERNAL: {
if(config->ledPin != 0xFF) {
digitalWrite(config->ledPin, state);
if(ledPin != 0xFF) {
digitalWrite(ledPin, state);
return true;
} else {
return false;
@@ -590,8 +613,8 @@ bool HwTools::writeLedPin(uint8_t color, uint8_t state) {
break;
}
case LED_RED: {
if(config->ledPinRed != 0xFF) {
digitalWrite(config->ledPinRed, state);
if(redPin != 0xFF) {
digitalWrite(redPin, state);
return true;
} else {
return false;
@@ -599,8 +622,8 @@ bool HwTools::writeLedPin(uint8_t color, uint8_t state) {
break;
}
case LED_GREEN: {
if(config->ledPinGreen != 0xFF) {
digitalWrite(config->ledPinGreen, state);
if(greenPin != 0xFF) {
digitalWrite(greenPin, state);
return true;
} else {
return false;
@@ -608,8 +631,8 @@ bool HwTools::writeLedPin(uint8_t color, uint8_t state) {
break;
}
case LED_BLUE: {
if(config->ledPinBlue != 0xFF) {
digitalWrite(config->ledPinBlue, state);
if(bluePin != 0xFF) {
digitalWrite(bluePin, state);
return true;
} else {
return false;
@@ -617,9 +640,9 @@ bool HwTools::writeLedPin(uint8_t color, uint8_t state) {
break;
}
case LED_YELLOW: {
if(config->ledPinRed != 0xFF && config->ledPinGreen != 0xFF) {
digitalWrite(config->ledPinRed, state);
digitalWrite(config->ledPinGreen, state);
if(redPin != 0xFF && greenPin != 0xFF) {
digitalWrite(redPin, state);
digitalWrite(greenPin, state);
return true;
} else {
return false;
@@ -629,3 +652,22 @@ bool HwTools::writeLedPin(uint8_t color, uint8_t state) {
}
return false;
}
bool HwTools::isVoltageOptimal(float range) {
if(boardType >= 5 && boardType <= 7 && maxVcc > 2.8) { // Pow-*
float vcc = getVcc();
if(vcc > 3.4 || vcc < 2.8) {
maxVcc = 0; // Voltage is outside the operating range, we have to assume voltage is OK
} else if(vcc > maxVcc) {
maxVcc = vcc;
} else {
float diff = min(maxVcc, (float) 3.3) - vcc;
return diff < range;
}
}
return true;
}
uint8_t HwTools::getBoardType() {
return boardType;
}

View File

@@ -11,14 +11,21 @@
class JsonMqttHandler : public AmsMqttHandler {
public:
JsonMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, HwTools* hw) : AmsMqttHandler(mqttConfig, debugger, buf) {
#if defined(AMS_REMOTE_DEBUG)
JsonMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, HwTools* hw, AmsFirmwareUpdater* updater) : AmsMqttHandler(mqttConfig, debugger, buf, updater) {
this->hw = hw;
};
#else
JsonMqttHandler(MqttConfig& mqttConfig, Stream* debugger, char* buf, HwTools* hw) : AmsMqttHandler(mqttConfig, debugger, buf) {
this->hw = hw;
};
#endif
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, PriceService* ps);
bool publishTemperatures(AmsConfiguration*, HwTools*);
bool publishPrices(PriceService*);
bool publishSystem(HwTools* hw, PriceService* ps, EnergyAccounting* ea);
bool publishRaw(String data);
bool publishFirmware();
void onMessage(String &topic, String &payload);

View File

@@ -9,28 +9,40 @@
#include "hexutils.h"
#include "Uptime.h"
bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, PriceService* ps) {
bool JsonMqttHandler::publish(AmsData* update, AmsData* previousState, EnergyAccounting* ea, PriceService* ps) {
if(strlen(mqttConfig.publishTopic) == 0) {
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Unable to publish data, no publish topic\n"));
return false;
}
if(!mqtt.connected()) {
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Unable to publish data, not connected\n"));
return false;
}
bool ret = false;
memset(json, 0, BufferSize);
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Publishing list ID %d!\n"), data->getListType());
if(data->getListType() == 1) {
ret = publishList1(data, ea);
} else if(data->getListType() == 2) {
ret = publishList2(data, ea);
} else if(data->getListType() == 3) {
ret = publishList3(data, ea);
} else if(data->getListType() == 4) {
ret = publishList4(data, ea);
AmsData data;
if(mqttConfig.stateUpdate) {
uint64_t now = millis64();
if(now-lastStateUpdate < mqttConfig.stateUpdateInterval * 1000) return false;
data.apply(*previousState);
data.apply(*update);
lastStateUpdate = now;
} else {
data = *update;
}
if(data.getListType() == 1) {
ret = publishList1(&data, ea);
mqtt.loop();
} else if(data.getListType() == 2) {
ret = publishList2(&data, ea);
mqtt.loop();
} else if(data.getListType() == 3) {
ret = publishList3(&data, ea);
mqtt.loop();
} else if(data.getListType() == 4) {
ret = publishList4(&data, ea);
mqtt.loop();
}
loop();
return ret;
@@ -81,7 +93,8 @@ bool JsonMqttHandler::publishList1(AmsData* data, EnergyAccounting* ea) {
}
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"P\":%d"), data->getActiveImportPower());
pos += appendJsonFooter(ea, pos);
json[pos] = '}';
json[pos++] = '}';
json[pos] = '\0';
if(mqttConfig.payloadFormat == 5) {
char topic[192];
snprintf_P(topic, 192, PSTR("%s/list1"), mqttConfig.publishTopic);
@@ -112,7 +125,8 @@ bool JsonMqttHandler::publishList2(AmsData* data, EnergyAccounting* ea) {
data->getL3Voltage()
);
pos += appendJsonFooter(ea, pos);
json[pos] = '}';
json[pos++] = '}';
json[pos] = '\0';
if(mqttConfig.payloadFormat == 5) {
char topic[192];
snprintf_P(topic, 192, PSTR("%s/list2"), mqttConfig.publishTopic);
@@ -148,7 +162,8 @@ bool JsonMqttHandler::publishList3(AmsData* data, EnergyAccounting* ea) {
data->getMeterTimestamp()
);
pos += appendJsonFooter(ea, pos);
json[pos] = '}';
json[pos++] = '}';
json[pos] = '\0';
if(mqttConfig.payloadFormat == 5) {
char topic[192];
snprintf_P(topic, 192, PSTR("%s/list3"), mqttConfig.publishTopic);
@@ -200,7 +215,8 @@ bool JsonMqttHandler::publishList4(AmsData* data, EnergyAccounting* ea) {
data->getMeterTimestamp()
);
pos += appendJsonFooter(ea, pos);
json[pos] = '}';
json[pos++] = '}';
json[pos] = '\0';
if(mqttConfig.payloadFormat == 5) {
char topic[192];
snprintf_P(topic, 192, PSTR("%s/list4"), mqttConfig.publishTopic);
@@ -241,7 +257,8 @@ bool JsonMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw)
bool ret = false;
json[pos-1] = '}';
if(mqttConfig.payloadFormat != 6) {
json[pos] = '}';
json[pos++] = '}';
json[pos] = '\0';
}
if(mqttConfig.payloadFormat == 5) {
char topic[192];
@@ -367,7 +384,8 @@ bool JsonMqttHandler::publishPrices(PriceService* ps) {
ts6hr
);
if(mqttConfig.payloadFormat != 6) {
json[pos] = '}';
json[pos++] = '}';
json[pos] = '\0';
}
bool ret = false;
if(mqttConfig.payloadFormat == 5) {
@@ -414,5 +432,26 @@ bool JsonMqttHandler::publishRaw(String data) {
return false;
}
void JsonMqttHandler::onMessage(String &topic, String &payload) {
bool JsonMqttHandler::publishFirmware() {
snprintf_P(json, BufferSize, PSTR("{\"installed_version\":\"%s\",\"latest_version\":\"%s\",\"title\":\"amsreader firmware\",\"release_url\":\"https://github.com/UtilitechAS/amsreader-firmware/releases\",\"release_summary\":\"New version %s is available\",\"update_percentage\":%s}"),
FirmwareVersion::VersionString,
strlen(updater->getNextVersion()) == 0 ? FirmwareVersion::VersionString : updater->getNextVersion(),
strlen(updater->getNextVersion()) == 0 ? FirmwareVersion::VersionString : updater->getNextVersion(),
updater->getProgress() < 0 ? "null" : String(updater->getProgress(), 0)
);
char topic[192];
snprintf_P(topic, 192, PSTR("%s/firmware"), mqttConfig.publishTopic);
bool ret = mqtt.publish(topic, json);
loop();
return ret;
}
void JsonMqttHandler::onMessage(String &topic, String &payload) {
if(strncmp(topic.c_str(), mqttConfig.subscribeTopic, 12) == 0) {
if(payload.equals("fwupgrade")) {
if(strcmp(updater->getNextVersion(), FirmwareVersion::VersionString) != 0) {
updater->setTargetVersion(updater->getNextVersion());
}
}
}
}

View File

@@ -11,6 +11,9 @@
#include "AmsConfiguration.h"
#include "DataParser.h"
#include "Cosem.h"
#if defined(AMS_REMOTE_DEBUG)
#include "RemoteDebug.h"
#endif
#define NOVALUE 0xFFFFFFFF
@@ -21,7 +24,11 @@ struct AmsOctetTimestamp {
class IEC6205675 : public AmsData {
public:
IEC6205675(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, AmsData &state);
#if defined(AMS_REMOTE_DEBUG)
IEC6205675(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, AmsData &state, RemoteDebug* debugger);
#else
IEC6205675(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, AmsData &state, Stream* debugger);
#endif
private:
CosemData* getCosemDataAt(uint8_t index, const char* ptr);
@@ -31,6 +38,8 @@ private:
float getNumber(CosemData*);
time_t getTimestamp(uint8_t* obis, int matchlength, const char* ptr);
uint8_t AMS_OBIS_UNKNOWN_1[4] = { 25, 9, 0, 255 };
uint8_t AMS_OBIS_VERSION[4] = { 0, 2, 129, 255 };
uint8_t AMS_OBIS_METER_MODEL[4] = { 96, 1, 1, 255 };
uint8_t AMS_OBIS_METER_MODEL_2[4] = { 96, 1, 7, 255 };

View File

@@ -1,3 +1,9 @@
/**
* @copyright Utilitech AS 2023
* License: Fair Source
*
*/
#ifndef _IMPULSEAMSDATA_H
#define _IMPULSEAMSDATA_H

View File

@@ -0,0 +1,39 @@
/**
* @copyright Utilitech AS 2024
* License: Fair Source
*
*/
#pragma once
#include "PassiveMeterCommunicator.h"
#if defined(AMS_REMOTE_DEBUG)
#include "RemoteDebug.h"
#endif
#include "AmsConfiguration.h"
#include "Timezone.h"
#include "ImpulseAmsData.h"
#if defined(ESP8266)
#include "SoftwareSerial.h"
#endif
#include "KmpTalker.h"
class KmpCommunicator : public PassiveMeterCommunicator {
public:
#if defined(AMS_REMOTE_DEBUG)
KmpCommunicator(RemoteDebug* debugger) : PassiveMeterCommunicator(debugger) {};
#else
KmpCommunicator(Stream* debugger) : PassiveMeterCommunicator(debugger) {};
#endif
void configure(MeterConfig&);
bool loop();
AmsData* getData(AmsData& meterState);
int getLastError();
void getCurrentConfig(MeterConfig& meterConfig) {
meterConfig = this->meterConfig;
}
private:
KmpTalker* talker = NULL;
};

View File

@@ -0,0 +1,56 @@
/**
* @copyright Utilitech AS 2023
* License: All rights reserved
*
*/
#pragma once
#include <Stream.h>
#define DATA_PARSE_OK 0
#define DATA_PARSE_FAIL -1
#define DATA_PARSE_INCOMPLETE -2
#define DATA_PARSE_FOOTER_CHECKSUM_ERROR -5
struct KmpParserContext {
uint8_t type;
uint16_t length;
};
struct KmpDataHolder {
uint32_t activeImportPower = 0, reactiveImportPower = 0, activeExportPower = 0, reactiveExportPower = 0;
float l1voltage = 0, l2voltage = 0, l3voltage = 0, l1current = 0, l2current = 0, l3current = 0;
uint32_t l1activeImportPower = 0, l2activeImportPower = 0, l3activeImportPower = 0;
uint32_t l1activeExportPower = 0, l2activeExportPower = 0, l3activeExportPower = 0;
double l1activeImportCounter = 0, l2activeImportCounter = 0, l3activeImportCounter = 0;
double l1activeExportCounter = 0, l2activeExportCounter = 0, l3activeExportCounter = 0;
float powerFactor = 0, l1PowerFactor = 0, l2PowerFactor = 0, l3PowerFactor = 0;
double activeImportCounter = 0, reactiveImportCounter = 0, activeExportCounter = 0, reactiveExportCounter = 0;
uint16_t meterId;
};
class KmpTalker {
public:
KmpTalker(Stream *hanSerial, uint8_t* hanBuffer, uint16_t hanBufferSize);
bool loop();
void getData(KmpDataHolder& data);
int getLastError();
private:
Stream *hanSerial;
uint8_t *hanBuffer = NULL;
uint16_t hanBufferSize = 0;
bool dataAvailable = false;
int len = 0;
int pos = DATA_PARSE_INCOMPLETE;
int lastError = DATA_PARSE_OK;
bool serialInit = false;
uint64_t lastUpdate = 0;
uint8_t batch = 0;
KmpParserContext ctx;
KmpDataHolder state;
};

View File

@@ -11,7 +11,6 @@
#include "AmsConfiguration.h"
#include "DataParser.h"
#include "Cosem.h"
#include "RemoteDebug.h"
struct LngHeader {
uint8_t tag;
@@ -31,7 +30,7 @@ struct LngObisDescriptor {
class LNG : public AmsData {
public:
LNG(AmsData& meterState, const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, RemoteDebug* debugger);
LNG(AmsData& meterState, const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx);
uint64_t getNumber(CosemData* item);
};

View File

@@ -11,7 +11,6 @@
#include "AmsConfiguration.h"
#include "DataParser.h"
#include "Cosem.h"
#include "RemoteDebug.h"
struct Lng2Data_3p {
CosemBasic header;
@@ -33,7 +32,7 @@ struct Lng2Data_3p {
class LNG2 : public AmsData {
public:
LNG2(AmsData& meterState, const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, RemoteDebug* debugger);
LNG2(AmsData& meterState, const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx);
private:
uint8_t getString(CosemData* item, char* target);

View File

@@ -8,7 +8,9 @@
#define _METERCOMMUNICATOR_H
#include <Arduino.h>
#if defined(AMS_REMOTE_DEBUG)
#include "RemoteDebug.h"
#endif
#include "AmsData.h"
#include "AmsConfiguration.h"
@@ -20,6 +22,7 @@ public:
virtual AmsData* getData(AmsData& meterState);
virtual int getLastError();
virtual bool isConfigChanged();
virtual void ackConfigChanged();
virtual void getCurrentConfig(MeterConfig& meterConfig);
};

View File

@@ -8,7 +8,9 @@
#define _PASSIVEMETERCOMMUNICATOR_H
#include "MeterCommunicator.h"
#if defined(AMS_REMOTE_DEBUG)
#include "RemoteDebug.h"
#endif
#include "AmsConfiguration.h"
#include "DataParsers.h"
#include "Timezone.h"
@@ -18,14 +20,21 @@
#include "SoftwareSerial.h"
#endif
const uint32_t AUTO_BAUD_RATES[] = { 2400, 9600, 115200 };
class PassiveMeterCommunicator : public MeterCommunicator {
public:
#if defined(AMS_REMOTE_DEBUG)
PassiveMeterCommunicator(RemoteDebug* debugger);
#else
PassiveMeterCommunicator(Stream* debugger);
#endif
void configure(MeterConfig&, Timezone*);
bool loop();
AmsData* getData(AmsData& meterState);
int getLastError();
bool isConfigChanged();
void ackConfigChanged();
void getCurrentConfig(MeterConfig& meterConfig);
void setPassthroughMqttHandler(PassthroughMqttHandler*);
@@ -33,7 +42,11 @@ public:
void rxerr(int err);
protected:
#if defined(AMS_REMOTE_DEBUG)
RemoteDebug* debugger = NULL;
#else
Stream* debugger = NULL;
#endif
MeterConfig meterConfig;
bool configChanged = false;
Timezone* tz;
@@ -49,12 +62,14 @@ protected:
HardwareSerial *hwSerial = NULL;
uint8_t rxBufferErrors = 0;
bool autodetect = false, validDataReceived = false;
bool autodetect = false;
uint8_t validDataReceived = 0;
unsigned long meterAutodetectLastChange = 0;
uint8_t meterAutoIndex = 0;
uint32_t bauds[6];
uint8_t parities[6];
bool inverts[6];
long rate = 10000;
uint32_t autodetectBaud = 0;
uint8_t autodetectParity = 11; // 8E1
bool autodetectInvert = false;
uint8_t autodetectCount = 0;
bool dataAvailable = false;
int len = 0;
@@ -73,11 +88,11 @@ protected:
DLMSParser *dlmsParser = NULL;
DSMRParser *dsmrParser = NULL;
void setupHanPort(uint32_t baud, uint8_t parityOrdinal, bool invert);
void setupHanPort(uint32_t baud, uint8_t parityOrdinal, bool invert, bool passive = true);
int16_t unwrapData(uint8_t *buf, DataParserContext &context);
void debugPrint(byte *buffer, int start, int length);
void printHanReadError(int pos);
void handleAutodetect(unsigned long now);
uint8_t getNextParity(uint8_t parityOrdinal);
};
#endif

View File

@@ -8,25 +8,36 @@
#define _PULSEMETERCOMMUNICATOR_H
#include "MeterCommunicator.h"
#if defined(AMS_REMOTE_DEBUG)
#include "RemoteDebug.h"
#endif
#include "AmsConfiguration.h"
#include "Timezone.h"
#include "ImpulseAmsData.h"
class PulseMeterCommunicator : public MeterCommunicator {
public:
#if defined(AMS_REMOTE_DEBUG)
PulseMeterCommunicator(RemoteDebug* debugger);
#else
PulseMeterCommunicator(Stream* debugger);
#endif
void configure(MeterConfig& config, Timezone* tz);
bool loop();
AmsData* getData(AmsData& meterState);
int getLastError();
bool isConfigChanged();
void ackConfigChanged();
void getCurrentConfig(MeterConfig& meterConfig);
void onPulse(uint8_t pulses);
protected:
#if defined(AMS_REMOTE_DEBUG)
RemoteDebug* debugger = NULL;
#else
Stream* debugger = NULL;
#endif
MeterConfig meterConfig;
bool configChanged = false;
Timezone* tz;

View File

@@ -11,6 +11,8 @@ IEC6205621::IEC6205621(const char* p, Timezone* tz, MeterConfig* meterConfig) {
if(strlen(p) < 16)
return;
this->packageTimestamp = time(nullptr);
String payload(p+1);
lastUpdateMillis = millis64();
@@ -59,15 +61,44 @@ IEC6205621::IEC6205621(const char* p, Timezone* tz, MeterConfig* meterConfig) {
}
}
tmElements_t tm { 0, 0, 0, 0, 0, 0, 0 };
String timestamp = extract(payload, F("1.0.0"));
if(timestamp.length() > 10) {
tmElements_t tm;
tm.Year = (timestamp.substring(0,2).toInt() + 2000) - 1970;
tm.Month = timestamp.substring(4,6).toInt();
tm.Day = timestamp.substring(2,4).toInt();
tm.Hour = timestamp.substring(6,8).toInt();
tm.Minute = timestamp.substring(8,10).toInt();
tm.Second = timestamp.substring(10,12).toInt();
if(timestamp.length() == 13) { // yyMMddHHmmssX
char x = timestamp.charAt(12);
if(x == 'S' || x == 'W') {
tm.Year = (timestamp.substring(0,2).toInt() + 2000) - 1970;
tm.Month = timestamp.substring(2,4).toInt();
tm.Day = timestamp.substring(4,6).toInt();
tm.Hour = timestamp.substring(6,8).toInt();
tm.Minute = timestamp.substring(8,10).toInt();
tm.Second = timestamp.substring(10,12).toInt();
}
} else if(timestamp.length() == 17) { // yyyyMMdd HH:mm:ss
char x = timestamp.charAt(11);
char y = timestamp.charAt(14);
if(x == ':' && y == ':') {
tm.Year = (timestamp.substring(0,4).toInt()) - 1970;
tm.Month = timestamp.substring(4,6).toInt();
tm.Day = timestamp.substring(6,8).toInt();
tm.Hour = timestamp.substring(9,11).toInt();
tm.Minute = timestamp.substring(12,14).toInt();
tm.Second = timestamp.substring(15,17).toInt();
}
} else if(timestamp.length() == 19) { // yyyy-MM-dd HH:mm:ss
char x = timestamp.charAt(4);
char y = timestamp.charAt(13);
if(x == '-' && y == ':') {
tm.Year = (timestamp.substring(0,4).toInt()) - 1970;
tm.Month = timestamp.substring(5,7).toInt();
tm.Day = timestamp.substring(8,10).toInt();
tm.Hour = timestamp.substring(11,13).toInt();
tm.Minute = timestamp.substring(14,16).toInt();
tm.Second = timestamp.substring(17,19).toInt();
}
} else {
meterTimestamp = 0;
}
if(tm.Year > 0) {
meterTimestamp = makeTime(tm);
if(tz != NULL) meterTimestamp = tz->toUTC(meterTimestamp);
}

View File

@@ -9,8 +9,13 @@
#include "Timezone.h"
#include "ntohll.h"
#include "Uptime.h"
#include "hexutils.h"
IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, AmsData &state) {
#if defined(AMS_REMOTE_DEBUG)
IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, AmsData &state, RemoteDebug* debugger) {
#else
IEC6205675::IEC6205675(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, AmsData &state, Stream* debugger) {
#endif
float val;
char str[64];
@@ -18,14 +23,20 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60};
Timezone tz(CEST, CET);
this->packageTimestamp = ctx.timestamp;
this->packageTimestamp = ctx.timestamp == 0 ? time(nullptr) : ctx.timestamp;
val = getNumber(AMS_OBIS_ACTIVE_IMPORT, sizeof(AMS_OBIS_ACTIVE_IMPORT), ((char *) (d)));
if(val == NOVALUE) {
CosemData* data = getCosemDataAt(1, ((char *) (d)));
// Kaifa special case...
if(data->base.type == CosemTypeOctetString) {
if(useMeterType == AmsTypeKaifa && data->base.type == CosemTypeDLongUnsigned) {
this->packageTimestamp = this->packageTimestamp > 0 ? tz.toUTC(this->packageTimestamp) : 0;
listType = 1;
meterType = AmsTypeKaifa;
activeImportPower = ntohl(data->dlu.data);
lastUpdateMillis = millis64();
} else if(data->base.type == CosemTypeOctetString) {
this->packageTimestamp = this->packageTimestamp > 0 ? tz.toUTC(this->packageTimestamp) : 0;
memcpy(str, data->oct.data, data->oct.length);
@@ -140,83 +151,471 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
int idx = 0;
data = getCosemDataAt(idx++, ((char *) (d)));
if(data->base.length == 0x12) {
listType = 2;
apply(state);
listType = state.getListType() > 4 ? state.getListType() : 4;
// 42.0.0 COSEM logical device name
idx++;
// 96.1.3 Device ID 4
data = getCosemDataAt(idx++, ((char *) (d)));
memcpy(str, data->oct.data, data->oct.length);
str[data->oct.length] = 0x00;
meterId = String(str);
// 1.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
activeImportPower = ntohl(data->dlu.data);
// 2.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
activeExportPower = ntohl(data->dlu.data);
// 3.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
reactiveImportPower = ntohl(data->dlu.data);
// 4.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
reactiveExportPower = ntohl(data->dlu.data);
// 32.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l1voltage = ntohs(data->lu.data) / 10.0;
// 52.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l2voltage = ntohs(data->lu.data) / 10.0;
// 72.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l3voltage = ntohs(data->lu.data) / 10.0;
// 31.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l1current = ntohs(data->lu.data) / 100.0;
// 51.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l2current = ntohs(data->lu.data) / 100.0;
// 71.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l3current = ntohs(data->lu.data) / 100.0;
// 21.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l1activeImportPower = ntohl(data->dlu.data);
// 41.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l2activeImportPower = ntohl(data->dlu.data);
// 61.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l3activeImportPower = ntohl(data->dlu.data);
// 22.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l1activeExportPower = ntohl(data->dlu.data);
// 42.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l2activeExportPower = ntohl(data->dlu.data);
// 62.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l3activeExportPower = ntohl(data->dlu.data);
lastUpdateMillis = millis64();
} else if(data->base.length == 0x0C) {
apply(state);
listType = 3;
idx += 4;
CosemData* no3 = getCosemDataAt(3, ((char *) (d)));
if(no3->base.type == CosemTypeBoolean) {
apply(state);
listType = state.getListType() > 3 ? state.getListType() : 3;
data = getCosemDataAt(idx++, ((char *) (d)));
activeImportCounter = ntohl(data->dlu.data) / 1000.0;
idx += 2;
data = getCosemDataAt(idx++, ((char *) (d)));
activeExportCounter = ntohl(data->dlu.data) / 1000.0;
idx += 2;
// 42.0.0 COSEM logical device name
idx++;
// 96.1.3 Device ID 4
data = getCosemDataAt(idx++, ((char *) (d)));
memcpy(str, data->oct.data, data->oct.length);
str[data->oct.length] = 0x00;
meterId = String(str);
// 96.3.10 Disconnect control
// 96.14.0 Currently acrive energy tariff
idx += 2;
// 1.8.0
data = getCosemDataAt(idx++, ((char *) (d)));
activeImportCounter = ntohl(data->dlu.data) / 1000.0;
// 1.8.1
// 1.8.2
idx += 2;
// 2.8.0
data = getCosemDataAt(idx++, ((char *) (d)));
activeExportCounter = ntohl(data->dlu.data) / 1000.0;
// 2.8.1
// 2.8.2
idx += 2;
// 3.8.0
data = getCosemDataAt(idx++, ((char *) (d)));
reactiveImportCounter = ntohl(data->dlu.data) / 1000.0;
// 4.8.0
data = getCosemDataAt(idx++, ((char *) (d)));
reactiveExportCounter = ntohl(data->dlu.data) / 1000.0;
lastUpdateMillis = millis64();
} else if(no3->base.type == CosemTypeLongUnsigned) {
apply(state);
listType = state.getListType() > 2 ? state.getListType() : 2;
// 42.0.0 COSEM logical device name
idx++;
// 96.1.2 Device ID 3
data = getCosemDataAt(idx++, ((char *) (d)));
memcpy(str, data->oct.data, data->oct.length);
str[data->oct.length] = 0x00;
meterId = String(str);
// 32.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l1voltage = ntohs(data->lu.data) / 10.0;
// 52.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l2voltage = ntohs(data->lu.data) / 10.0;
// 72.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l3voltage = ntohs(data->lu.data) / 10.0;
// 31.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l1current = ntohs(data->lu.data) / 100.0;
// 51.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l2current = ntohs(data->lu.data) / 100.0;
// 71.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l3current = ntohs(data->lu.data) / 100.0;
// 1.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
activeImportPower = ntohl(data->dlu.data);
// 2.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
activeExportPower = ntohl(data->dlu.data);
// 3.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
reactiveImportPower = ntohl(data->dlu.data);
// 4.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
reactiveExportPower = ntohl(data->dlu.data);
lastUpdateMillis = millis64();
}
} else if(data->base.length == 0x0A) {
CosemData* no7 = getCosemDataAt(7, ((char *) (d)));
if(no7->base.type == CosemTypeLongUnsigned) {
apply(state);
listType = state.getListType() > 4 ? state.getListType() : 4;
// 42.0.0 COSEM logical device name
idx++;
// 96.1.2 Device ID 3
data = getCosemDataAt(idx++, ((char *) (d)));
memcpy(str, data->oct.data, data->oct.length);
str[data->oct.length] = 0x00;
meterId = String(str);
// 1.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
activeImportPower = ntohl(data->dlu.data);
// 2.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
activeExportPower = ntohl(data->dlu.data);
// 3.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
reactiveImportPower = ntohl(data->dlu.data);
// 4.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
reactiveExportPower = ntohl(data->dlu.data);
// 32.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l1voltage = ntohs(data->lu.data) / 10.0;
// 31.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l1current = ntohs(data->lu.data) / 100.0;
// 21.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l1activeImportPower = ntohl(data->dlu.data);
// 22.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l1activeExportPower = ntohl(data->dlu.data);
lastUpdateMillis = millis64();
} else if(no7->base.type == CosemTypeDLongUnsigned) {
apply(state);
listType = state.getListType() > 3 ? state.getListType() : 3;
// 42.0.0 COSEM logical device name
idx++;
// 96.1.3 Device ID 4
data = getCosemDataAt(idx++, ((char *) (d)));
memcpy(str, data->oct.data, data->oct.length);
str[data->oct.length] = 0x00;
meterId = String(str);
// 1.8.1
data = getCosemDataAt(idx++, ((char *) (d)));
double obis181 = ntohl(data->dlu.data) / 1000.0;
// 1.8.2
data = getCosemDataAt(idx++, ((char *) (d)));
double obis182 = ntohl(data->dlu.data) / 1000.0;
if(activeImportCounter < obis181 + obis182)
activeImportCounter = obis181 + obis182;
// 2.8.1
data = getCosemDataAt(idx++, ((char *) (d)));
double obis281 = ntohl(data->dlu.data) / 1000.0;
// 2.8.2
data = getCosemDataAt(idx++, ((char *) (d)));
double obis282 = ntohl(data->dlu.data) / 1000.0;
if(activeExportCounter < obis281 + obis282)
activeExportCounter = obis281 + obis282;
// 3.8.1
data = getCosemDataAt(idx++, ((char *) (d)));
double obis381 = ntohl(data->dlu.data) / 1000.0;
// 3.8.2
data = getCosemDataAt(idx++, ((char *) (d)));
double obis382 = ntohl(data->dlu.data) / 1000.0;
if(reactiveImportCounter < obis381 + obis382)
reactiveImportCounter = obis381 + obis382;
// 4.8.1
data = getCosemDataAt(idx++, ((char *) (d)));
double obis481 = ntohl(data->dlu.data) / 1000.0;
// 4.8.2
data = getCosemDataAt(idx++, ((char *) (d)));
double obis482 = ntohl(data->dlu.data) / 1000.0;
if(reactiveExportCounter < obis481 + obis482)
reactiveExportCounter = obis481 + obis482;
lastUpdateMillis = millis64();
}
} else if(data->base.length == 0x09) {
CosemData* no7 = getCosemDataAt(7, ((char *) (d)));
if(no7->base.type == CosemTypeLongUnsigned) {
apply(state);
listType = state.getListType() > 3 ? state.getListType() : 3;
// 42.0.0 COSEM logical device name
idx++;
// 96.1.2 Device ID 3
data = getCosemDataAt(idx++, ((char *) (d)));
memcpy(str, data->oct.data, data->oct.length);
str[data->oct.length] = 0x00;
meterId = String(str);
// 1.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
activeImportPower = ntohl(data->dlu.data);
// 2.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
activeExportPower = ntohl(data->dlu.data);
// 3.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
reactiveImportPower = ntohl(data->dlu.data);
// 4.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
reactiveExportPower = ntohl(data->dlu.data);
// 32.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l1voltage = ntohs(data->lu.data) / 10.0;
// 2.8.1
data = getCosemDataAt(idx++, ((char *) (d)));
double obis281 = ntohl(data->dlu.data) / 1000.0;
// 2.8.2
data = getCosemDataAt(idx++, ((char *) (d)));
double obis282 = ntohl(data->dlu.data) / 1000.0;
activeExportCounter = obis281 + obis282;
lastUpdateMillis = millis64();
} else if(no7->base.type == CosemTypeDLongUnsigned) {
apply(state);
listType = state.getListType() > 3 ? state.getListType() : 3;
// 42.0.0 COSEM logical device name?
idx++;
// 1.7.0?
data = getCosemDataAt(idx++, ((char *) (d)));
activeImportPower = ntohl(data->dlu.data);
// 2.7.0?
data = getCosemDataAt(idx++, ((char *) (d)));
activeExportPower = ntohl(data->dlu.data);
// 1.8.0?
data = getCosemDataAt(idx++, ((char *) (d)));
activeImportCounter = ntohl(data->dlu.data) / 1000.0;
// 1.8.1?
// 1.8.2?
idx += 2;
// 2.8.0?
data = getCosemDataAt(idx++, ((char *) (d)));
activeExportCounter = ntohl(data->dlu.data) / 1000.0;
// 2.8.1?
// 2.8.2?
idx += 2;
lastUpdateMillis = millis64();
}
} else if(data->base.length == 0x08) {
// 42.0.0 COSEM logical device name
idx++;
// 96.1.2 Device ID 3
data = getCosemDataAt(idx++, ((char *) (d)));
reactiveImportCounter = ntohl(data->dlu.data) / 1000.0;
memcpy(str, data->oct.data, data->oct.length);
str[data->oct.length] = 0x00;
meterId = String(str);
// 32.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
reactiveExportCounter = ntohl(data->dlu.data) / 1000.0;
l1voltage = ntohs(data->lu.data) / 10.0;
// 31.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l1current = ntohs(data->lu.data) / 100.0;
// 1.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
activeImportPower = ntohl(data->dlu.data);
// 2.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
activeExportPower = ntohl(data->dlu.data);
// 3.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
reactiveImportPower = ntohl(data->dlu.data);
// 4.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
reactiveExportPower = ntohl(data->dlu.data);
lastUpdateMillis = millis64();
} else if(data->base.length == 0x04) {
// ?
idx++;
// ?
idx++;
// 1.8.0?
data = getCosemDataAt(idx++, ((char *) (d)));
activeImportCounter = ntohl(data->dlu.data) / 1000.0;
// 2.8.0?
data = getCosemDataAt(idx++, ((char *) (d)));
activeExportCounter = ntohl(data->dlu.data) / 1000.0;
}
} else if(useMeterType == AmsTypeIskra && data->base.type == CosemTypeOctetString) { // Iskra special case
meterType = AmsTypeIskra;
uint8_t idx = 5;
data = getCosemDataAt(idx++, ((char *) (d)));
if(data != NULL) {
activeImportCounter = ntohl(data->dlu.data) / 1000.0;
}
data = getCosemDataAt(idx++, ((char *) (d)));
if(data != NULL) {
activeExportCounter = ntohl(data->dlu.data) / 1000.0;
}
data = getCosemDataAt(idx++, ((char *) (d)));
if(data != NULL) {
reactiveImportCounter = ntohl(data->dlu.data) / 1000.0;
}
data = getCosemDataAt(idx++, ((char *) (d)));
if(data != NULL) {
reactiveExportCounter = ntohl(data->dlu.data) / 1000.0;
}
data = getCosemDataAt(idx++, ((char *) (d)));
if(data != NULL) {
activeImportPower = ntohl(data->dlu.data);
}
data = getCosemDataAt(idx++, ((char *) (d)));
if(data != NULL) {
activeExportPower = ntohl(data->dlu.data);
}
uint8_t str_len = 0;
str_len = getString(AMS_OBIS_UNKNOWN_1, sizeof(AMS_OBIS_UNKNOWN_1), ((char *) (d)), str);
if(str_len > 0) {
meterId = String(str);
}
listType = 3;
lastUpdateMillis = millis64();
} else if(useMeterType == AmsTypeUnknown) {
uint8_t str_len = 0;
str_len = getString(AMS_OBIS_UNKNOWN_1, sizeof(AMS_OBIS_UNKNOWN_1), ((char *) (d)), str);
if(str_len > 0) {
meterType = AmsTypeIskra;
meterId = String(str);
lastUpdateMillis = millis64();
listType = 3;
}
}
} else if(useMeterType == AmsTypeKaifa && data->base.type == CosemTypeDLongUnsigned) {
this->packageTimestamp = this->packageTimestamp > 0 ? tz.toUTC(this->packageTimestamp) : 0;
listType = 1;
meterType = AmsTypeKaifa;
activeImportPower = ntohl(data->dlu.data);
lastUpdateMillis = millis64();
}
// Kaifa end
} else {
listType = 1;
activeImportPower = val;
@@ -239,20 +638,6 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
}
}
}
// Try system title
if(meterType == AmsTypeUnknown) {
if(memcmp(ctx.system_title, "SAGY", 4) == 0) {
meterType = AmsTypeSagemcom;
} else if(memcmp(ctx.system_title, "KFM", 3) == 0) {
meterType = AmsTypeKaifa;
}
}
if(this->packageTimestamp > 0) {
if(meterType == AmsTypeAidon || meterType == AmsTypeKamstrup) {
this->packageTimestamp = this->packageTimestamp - 3600;
}
}
uint8_t str_len = 0;
str_len = getString(AMS_OBIS_VERSION, sizeof(AMS_OBIS_VERSION), ((char *) (d)), str);
@@ -354,8 +739,14 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
if(meterTs != NULL) {
AmsOctetTimestamp* amst = (AmsOctetTimestamp*) meterTs;
time_t ts = decodeCosemDateTime(amst->dt);
if(meterType == AmsTypeAidon || meterType == AmsTypeKamstrup) {
meterTimestamp = ts - 3600;
int16_t deviation = ntohs(amst->dt.deviation);
if(deviation < -720 || deviation > 720) { // Deviation not specified, adjust from localtime to UTC
meterTimestamp = tz.toUTC(ts);
if(ctx.timestamp > 0) {
this->packageTimestamp = tz.toUTC(ctx.timestamp);
}
} else if(meterType == AmsTypeAidon) {
meterTimestamp = ts - 3600; // 21.09.24, the clock is now correct
} else {
meterTimestamp = ts;
}
@@ -501,6 +892,22 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
lastUpdateMillis = millis64();
}
// Try system title
if(meterType == AmsTypeUnknown) {
if(memcmp(ctx.system_title, "SAGY", 4) == 0) {
meterType = AmsTypeSagemcom;
} else if(memcmp(ctx.system_title, "KFM", 3) == 0) {
meterType = AmsTypeKaifa;
} else if(memcmp(ctx.system_title, "ISK", 3) == 0) {
meterType = AmsTypeIskra;
}
if(meterId.isEmpty() && meterType != AmsTypeUnknown) {
stripNonAscii((uint8_t*) ctx.system_title, 8);
meterId = String((const char*)ctx.system_title);
}
}
if(meterConfig->wattageMultiplier > 0) {
activeImportPower = activeImportPower > 0 ? activeImportPower * (meterConfig->wattageMultiplier / 1000.0) : 0;
activeExportPower = activeExportPower > 0 ? activeExportPower * (meterConfig->wattageMultiplier / 1000.0) : 0;
@@ -522,6 +929,12 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
activeExportCounter = activeExportCounter > 0 ? activeExportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
reactiveImportCounter = reactiveImportCounter > 0 ? reactiveImportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
reactiveExportCounter = reactiveExportCounter > 0 ? reactiveExportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
l1activeImportCounter = l1activeImportCounter > 0 ? l1activeImportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
l2activeImportCounter = l2activeImportCounter > 0 ? l2activeImportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
l3activeImportCounter = l3activeImportCounter > 0 ? l3activeImportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
l1activeExportCounter = l1activeExportCounter > 0 ? l1activeExportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
l2activeExportCounter = l2activeExportCounter > 0 ? l2activeExportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
l3activeExportCounter = l3activeExportCounter > 0 ? l3activeExportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
}
threePhase = l1voltage > 0 && l2voltage > 0 && l3voltage > 0;
@@ -535,6 +948,7 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
threePhase = true;
}
}
meterId.trim();
}
CosemData* IEC6205675::getCosemDataAt(uint8_t index, const char* ptr) {

View File

@@ -1,3 +1,9 @@
/**
* @copyright Utilitech AS 2023
* License: Fair Source
*
*/
#include "ImpulseAmsData.h"
#include "Uptime.h"

View File

@@ -0,0 +1,92 @@
/**
* @copyright Utilitech AS 2023
* License: Fair Source
*
*/
#include "KmpCommunicator.h"
#include "Uptime.h"
#include "crc.h"
#include "OBIScodes.h"
#include "hexutils.h"
void KmpCommunicator::configure(MeterConfig& meterConfig) {
this->meterConfig = meterConfig;
this->configChanged = false;
if(meterConfig.baud == 0) {
this->configChanged = true;
meterConfig.baud = 9600;
meterConfig.parity = 7;
meterConfig.invert = false;
}
setupHanPort(meterConfig.baud, meterConfig.parity, meterConfig.invert, false);
talker = new KmpTalker(hanSerial, hanBuffer, hanBufferSize);
}
bool KmpCommunicator::loop() {
uint64_t now = millis64();
bool ret = talker->loop();
int lastError = getLastError();
if(ret) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugger->printf_P(PSTR("Successful loop\n"));
Serial.flush();
} else if(lastError < 0 && lastError != DATA_PARSE_INCOMPLETE) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Error code: %d\n"), getLastError());
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
{
debugger->printf_P(PSTR(" payload:\n"));
debugPrint(hanBuffer, 0, hanBufferSize, debugger);
}
}
return ret;
}
int KmpCommunicator::getLastError() {
return talker == NULL ? DATA_PARSE_FAIL : talker->getLastError();
}
AmsData* KmpCommunicator::getData(AmsData& meterState) {
if(talker == NULL) return NULL;
KmpDataHolder kmpData;
talker->getData(kmpData);
AmsData* data = new AmsData();
data->apply(OBIS_ACTIVE_IMPORT_COUNT, kmpData.activeImportCounter);
data->apply(OBIS_ACTIVE_EXPORT_COUNT, kmpData.activeExportCounter);
data->apply(OBIS_REACTIVE_IMPORT_COUNT, kmpData.reactiveImportCounter);
data->apply(OBIS_REACTIVE_EXPORT_COUNT, kmpData.reactiveExportCounter);
data->apply(OBIS_ACTIVE_IMPORT, kmpData.activeImportPower);
data->apply(OBIS_ACTIVE_EXPORT, kmpData.activeExportPower);
data->apply(OBIS_REACTIVE_IMPORT, kmpData.reactiveImportPower);
data->apply(OBIS_REACTIVE_EXPORT, kmpData.reactiveExportPower);
data->apply(OBIS_VOLTAGE_L1, kmpData.l1voltage);
data->apply(OBIS_VOLTAGE_L2, kmpData.l2voltage);
data->apply(OBIS_VOLTAGE_L3, kmpData.l3voltage);
data->apply(OBIS_CURRENT_L1, kmpData.l1current);
data->apply(OBIS_CURRENT_L2, kmpData.l2current);
data->apply(OBIS_CURRENT_L3, kmpData.l3current);
data->apply(OBIS_POWER_FACTOR_L1, kmpData.l1PowerFactor);
data->apply(OBIS_POWER_FACTOR_L2, kmpData.l2PowerFactor);
data->apply(OBIS_POWER_FACTOR_L3, kmpData.l3PowerFactor);
data->apply(OBIS_POWER_FACTOR, kmpData.powerFactor);
data->apply(OBIS_ACTIVE_IMPORT_L1, kmpData.l1activeImportPower);
data->apply(OBIS_ACTIVE_IMPORT_L2, kmpData.l2activeImportPower);
data->apply(OBIS_ACTIVE_IMPORT_L3, kmpData.l3activeImportPower);
data->apply(OBIS_ACTIVE_EXPORT_L1, kmpData.l1activeExportPower);
data->apply(OBIS_ACTIVE_EXPORT_L2, kmpData.l2activeExportPower);
data->apply(OBIS_ACTIVE_EXPORT_L3, kmpData.l3activeExportPower);
data->apply(OBIS_ACTIVE_IMPORT_COUNT_L1, kmpData.l1activeImportCounter);
data->apply(OBIS_ACTIVE_IMPORT_COUNT_L2, kmpData.l2activeImportCounter);
data->apply(OBIS_ACTIVE_IMPORT_COUNT_L3, kmpData.l3activeImportCounter);
data->apply(OBIS_METER_ID, kmpData.meterId);
data->apply(OBIS_NULL, AmsTypeKamstrup);
return data;
}

View File

@@ -9,7 +9,7 @@
#include "ntohll.h"
#include "Uptime.h"
LNG::LNG(AmsData& meterState, const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, RemoteDebug* debugger) {
LNG::LNG(AmsData& meterState, const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx) {
LngHeader* h = (LngHeader*) payload;
if(h->tag == CosemTypeStructure && h->arrayTag == CosemTypeArray) {
apply(meterState);
@@ -42,33 +42,75 @@ LNG::LNG(AmsData& meterState, const char* payload, uint8_t useMeterType, MeterCo
switch(descriptor->obis[2]) {
case 1:
o170 = getNumber(item);
if(meterConfig->wattageMultiplier > 0) {
o170 = o170 > 0 ? o170 * (meterConfig->wattageMultiplier / 1000.0) : 0;
}
break;
case 2:
o270 = getNumber(item);
if(meterConfig->wattageMultiplier > 0) {
o270 = o270 > 0 ? o270 * (meterConfig->wattageMultiplier / 1000.0) : 0;
}
break;
case 3:
reactiveImportPower = getNumber(item);
if(meterConfig->wattageMultiplier > 0) {
reactiveImportPower = reactiveImportPower > 0 ? reactiveImportPower * (meterConfig->wattageMultiplier / 1000.0) : 0;
}
break;
case 4:
reactiveExportPower = getNumber(item);
if(meterConfig->wattageMultiplier > 0) {
reactiveExportPower = reactiveExportPower > 0 ? reactiveExportPower * (meterConfig->wattageMultiplier / 1000.0) : 0;
}
break;
case 21:
l1activeImportPower = getNumber(item);
listType = listType >= 4 ? listType : 4;
break;
case 41:
l2activeImportPower = getNumber(item);
listType = listType >= 4 ? listType : 4;
break;
case 61:
l3activeImportPower = getNumber(item);
listType = listType >= 4 ? listType : 4;
break;
case 31:
l1current = getNumber(item) / 100.0;
if(meterConfig->amperageMultiplier > 0) {
l1current = l1current > 0 ? l1current * (meterConfig->amperageMultiplier / 1000.0) : 0;
}
break;
case 51:
l2current = getNumber(item) / 100.0;
if(meterConfig->amperageMultiplier > 0) {
l2current = l2current > 0 ? l2current * (meterConfig->amperageMultiplier / 1000.0) : 0;
}
break;
case 71:
l3current = getNumber(item) / 100.0;
if(meterConfig->amperageMultiplier > 0) {
l3current = l3current > 0 ? l3current * (meterConfig->amperageMultiplier / 1000.0) : 0;
}
break;
case 32:
l1voltage = getNumber(item) / 10.0;
if(meterConfig->voltageMultiplier > 0) {
l1voltage = l1voltage > 0 ? l1voltage * (meterConfig->voltageMultiplier / 1000.0) : 0;
}
break;
case 52:
l2voltage = getNumber(item) / 10.0;
if(meterConfig->voltageMultiplier > 0) {
l2voltage = l2voltage > 0 ? l2voltage * (meterConfig->voltageMultiplier / 1000.0) : 0;
}
break;
case 72:
l3voltage = getNumber(item) / 10.0;
if(meterConfig->voltageMultiplier > 0) {
l3voltage = l3voltage > 0 ? l3voltage * (meterConfig->voltageMultiplier / 1000.0) : 0;
}
break;
}
}
@@ -79,30 +121,54 @@ LNG::LNG(AmsData& meterState, const char* payload, uint8_t useMeterType, MeterCo
case 1:
o180 = getNumber(item);
activeImportCounter = o180 / 1000.0;
if(meterConfig->accumulatedMultiplier > 0) {
activeImportCounter = activeImportCounter > 0 ? activeImportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
}
break;
case 2:
o280 = getNumber(item);
activeExportCounter = o280 / 1000.0;
if(meterConfig->accumulatedMultiplier > 0) {
activeExportCounter = activeExportCounter > 0 ? activeExportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
}
break;
case 3:
o380 = getNumber(item);
reactiveImportCounter = o380 / 1000.0;
if(meterConfig->accumulatedMultiplier > 0) {
reactiveImportCounter = reactiveImportCounter > 0 ? reactiveImportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
}
break;
case 4:
o480 = getNumber(item);
reactiveExportCounter = o480 / 1000.0;
if(meterConfig->accumulatedMultiplier > 0) {
reactiveExportCounter = reactiveExportCounter > 0 ? reactiveExportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
}
break;
case 5:
o580 = getNumber(item);
if(meterConfig->accumulatedMultiplier > 0) {
o580 = o580 > 0 ? o580 * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
}
break;
case 6:
o680 = getNumber(item);
if(meterConfig->accumulatedMultiplier > 0) {
o680 = o680 > 0 ? o680 * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
}
break;
case 7:
o780 = getNumber(item);
if(meterConfig->accumulatedMultiplier > 0) {
o780 = o780 > 0 ? o780 * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
}
break;
case 8:
o880 = getNumber(item);
if(meterConfig->accumulatedMultiplier > 0) {
o880 = o880 > 0 ? o880 * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
}
break;
}
} else if(descriptor->obis[4] == 1) {
@@ -110,9 +176,15 @@ LNG::LNG(AmsData& meterState, const char* payload, uint8_t useMeterType, MeterCo
switch(descriptor->obis[2]) {
case 1:
o181 = getNumber(item);
if(meterConfig->accumulatedMultiplier > 0) {
o181 = o181 > 0 ? o181 * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
}
break;
case 2:
o281 = getNumber(item);
if(meterConfig->accumulatedMultiplier > 0) {
o281 = o281 > 0 ? o281 * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
}
break;
}
} else if(descriptor->obis[4] == 2) {
@@ -120,9 +192,15 @@ LNG::LNG(AmsData& meterState, const char* payload, uint8_t useMeterType, MeterCo
switch(descriptor->obis[2]) {
case 1:
o182 = getNumber(item);
if(meterConfig->accumulatedMultiplier > 0) {
o182 = o182 > 0 ? o182 * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
}
break;
case 2:
o282 = getNumber(item);
if(meterConfig->accumulatedMultiplier > 0) {
o282 = o282 > 0 ? o282 * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
}
break;
}
}
@@ -148,7 +226,9 @@ LNG::LNG(AmsData& meterState, const char* payload, uint8_t useMeterType, MeterCo
int32_t sum = o170-o270;
if(sum > 0) {
activeImportPower = sum;
activeExportPower = 0;
} else {
activeImportPower = 0;
activeExportPower = sum * -1;
listType = listType >= 2 ? listType : 2;
}
@@ -180,6 +260,11 @@ LNG::LNG(AmsData& meterState, const char* payload, uint8_t useMeterType, MeterCo
lastUpdateMillis = millis64();
}
lastUpdateMillis = millis64();
threePhase = l1voltage > 0 && l2voltage > 0 && l3voltage > 0;
if(!threePhase)
twoPhase = (l1voltage > 0 && l2voltage > 0) || (l2voltage > 0 && l3voltage > 0) || (l3voltage > 0 && l1voltage > 0);
}
}

View File

@@ -7,7 +7,7 @@
#include "LNG2.h"
#include "Uptime.h"
LNG2::LNG2(AmsData& meterState, const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, RemoteDebug* debugger) {
LNG2::LNG2(AmsData& meterState, const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx) {
CosemBasic* h = (CosemBasic*) payload;
if(h->length == 0x0e) {
apply(meterState);

View File

@@ -9,52 +9,55 @@
#include "IEC6205621.h"
#include "LNG.h"
#include "LNG2.h"
#include "hexutils.h"
#if defined(ESP32)
#include <driver/uart.h>
#endif
#if defined(AMS_REMOTE_DEBUG)
PassiveMeterCommunicator::PassiveMeterCommunicator(RemoteDebug* debugger) {
this->debugger = debugger;
bauds[0] = 2400;
parities[0] = 11;
inverts[0] = false;
bauds[1] = 2400;
parities[1] = 3;
inverts[1] = false;
bauds[2] = 115200;
parities[2] = 3;
inverts[2] = false;
bauds[3] = 2400;
parities[3] = 11;
inverts[3] = true;
bauds[4] = 2400;
parities[4] = 3;
inverts[4] = true;
bauds[5] = 115200;
parities[5] = 3;
inverts[5] = true;
}
#else
PassiveMeterCommunicator::PassiveMeterCommunicator(Stream* debugger) {
this->debugger = debugger;
}
#endif
void PassiveMeterCommunicator::configure(MeterConfig& meterConfig, Timezone* tz) {
this->meterConfig = meterConfig;
if(meterConfig.baud == 0) {
autodetect = true;
}
this->configChanged = false;
this->tz = tz;
setupHanPort(meterConfig.baud, meterConfig.parity, meterConfig.invert);
if(dsmrParser != NULL) {
delete dsmrParser;
dsmrParser = NULL;
}
if(gcmParser != NULL) {
delete gcmParser;
gcmParser = NULL;
}
bool encen = false;
for(uint8_t i = 0; i < 16; i++) {
if(meterConfig.encryptionKey[i] > 0) {
encen = true;
}
}
if(encen) {
gcmParser = new GCMParser(meterConfig.encryptionKey, meterConfig.authenticationKey);
}
}
bool PassiveMeterCommunicator::loop() {
if(hanBufferSize == 0) return false;
unsigned long now = millis();
if(autodetect) handleAutodetect(now);
unsigned long start, end;
if(!hanSerial->available()) {
return false;
@@ -67,9 +70,6 @@ bool PassiveMeterCommunicator::loop() {
return false;
}
unsigned long now = millis();
if(autodetect) handleAutodetect(now);
dataAvailable = false;
ctx = {0,0,0,0};
memset(ctx.system_title, 0, 8);
@@ -81,7 +81,10 @@ bool PassiveMeterCommunicator::loop() {
if(len >= hanBufferSize) {
hanSerial->readBytes(hanBuffer, hanBufferSize);
len = 0;
if (debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Buffer overflow, resetting\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("Buffer overflow, resetting\n"));
return false;
}
hanBuffer[len++] = hanSerial->read();
@@ -90,26 +93,47 @@ bool PassiveMeterCommunicator::loop() {
if(ctx.type > 0 && pos >= 0) {
switch(ctx.type) {
case DATA_TAG_DLMS:
if (debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Received valid DLMS at %d\n"), pos);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Received valid DLMS at %d +%d\n"), pos, ctx.length);
break;
case DATA_TAG_DSMR:
if (debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Received valid DSMR at %d\n"), pos);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Received valid DSMR at %d +%d\n"), pos, ctx.length);
break;
case DATA_TAG_SNRM:
if (debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Received valid SNMR at %d\n"), pos);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Received valid SNMR at %d +%d\n"), pos, ctx.length);
break;
case DATA_TAG_AARE:
if (debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Received valid AARE at %d\n"), pos);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Received valid AARE at %d +%d\n"), pos, ctx.length);
break;
case DATA_TAG_RES:
if (debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Received valid Get Response at %d\n"), pos);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Received valid Get Response at %d +%d\n"), pos, ctx.length);
break;
case DATA_TAG_HDLC:
if (debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Received valid HDLC at %d\n"), pos);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Received valid HDLC at %d +%d\n"), pos, ctx.length);
break;
default:
// TODO: Move this so that payload is sent to MQTT
if (debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("Unknown tag %02X at pos %d\n"), ctx.type, pos);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("Unknown tag %02X at pos %d\n"), ctx.type, pos);
len = 0;
return false;
}
@@ -118,18 +142,27 @@ bool PassiveMeterCommunicator::loop() {
}
end = millis();
if(end-start > 1000) {
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Used %dms to unwrap HAN data\n"), end-start);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
debugger->printf_P(PSTR("Used %dms to unwrap HAN data\n"), end-start);
}
if(pos == DATA_PARSE_INCOMPLETE) {
return false;
} else if(pos == DATA_PARSE_UNKNOWN_DATA) {
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Unknown data received\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
debugger->printf_P(PSTR("Unknown data received\n"));
lastError = pos;
len = len + hanSerial->readBytes(hanBuffer+len, hanBufferSize-len);
if(debugger->isActive(RemoteDebug::VERBOSE)) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
{
debugger->printf_P(PSTR(" payload:\n"));
debugPrint(hanBuffer, 0, len);
debugPrint(hanBuffer, 0, len, debugger);
}
len = 0;
return false;
@@ -144,9 +177,12 @@ bool PassiveMeterCommunicator::loop() {
if(pt != NULL) {
pt->publishBytes(hanBuffer, len);
}
if(debugger->isActive(RemoteDebug::VERBOSE)) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
{
debugger->printf_P(PSTR(" payload:\n"));
debugPrint(hanBuffer, 0, len);
debugPrint(hanBuffer, 0, len, debugger);
}
while(hanSerial->available()) hanSerial->read(); // Make sure it is all empty, in case we overflowed buffer above
len = 0;
@@ -154,12 +190,18 @@ bool PassiveMeterCommunicator::loop() {
}
if(ctx.type == 0) {
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Ended up with context type %d, return code %d and length: %lu/%lu\n"), ctx.type, pos, ctx.length, len);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
debugger->printf_P(PSTR("Ended up with context type %d, return code %d and length: %lu/%lu\n"), ctx.type, pos, ctx.length, len);
lastError = pos;
len = len + hanSerial->readBytes(hanBuffer+len, hanBufferSize-len);
if(debugger->isActive(RemoteDebug::VERBOSE)) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
{
debugger->printf_P(PSTR(" payload:\n"));
debugPrint(hanBuffer, 0, len);
debugPrint(hanBuffer, 0, len, debugger);
}
len = 0;
return false;
@@ -172,26 +214,6 @@ bool PassiveMeterCommunicator::loop() {
dataAvailable = true;
lastError = DATA_PARSE_OK;
// After one hour, adjust buffer size to match the largest payload
if(!maxDetectPayloadDetectDone && now > 3600000) {
if(maxDetectedPayloadSize * 1.25 > meterConfig.bufferSize * 64) {
int bufferSize = min((double) 64, ceil((maxDetectedPayloadSize * 1.25) / 64));
#if defined(ESP8266)
if(meterConfig.rxPin != 3 && meterConfig.rxPin != 113) {
bufferSize = min(bufferSize, 2);
} else {
bufferSize = min(bufferSize, 8);
}
#endif
if(bufferSize != meterConfig.bufferSize) {
if (debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Increasing RX buffer to %d bytes\n"), meterConfig.bufferSize * 64);
meterConfig.bufferSize = bufferSize;
configChanged = true;
}
}
maxDetectPayloadDetectDone = true;
}
return true;
}
@@ -211,13 +233,22 @@ AmsData* PassiveMeterCommunicator::getData(AmsData& meterState) {
pt->publishBytes((uint8_t*) payload, ctx.length);
}
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("Using application data:\n"));
if(debugger->isActive(RemoteDebug::VERBOSE)) debugPrint((byte*) payload, 0, ctx.length);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugger->printf_P(PSTR("Using application data:\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugPrint((byte*) payload, 0, ctx.length, debugger);
// Rudimentary detector for L&G proprietary format, this is terrible code... Fix later
if(payload[0] == CosemTypeStructure && payload[2] == CosemTypeArray && payload[1] == payload[3]) {
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("LNG\n"));
LNG lngData = LNG(meterState, payload, meterState.getMeterType(), &meterConfig, ctx, debugger);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugger->printf_P(PSTR("LNG\n"));
LNG lngData = LNG(meterState, payload, meterState.getMeterType(), &meterConfig, ctx);
if(lngData.getListType() >= 1) {
data = new AmsData();
data->apply(meterState);
@@ -231,17 +262,23 @@ AmsData* PassiveMeterCommunicator::getData(AmsData& meterState) {
payload[14] == CosemTypeLongUnsigned &&
payload[17] == CosemTypeLongUnsigned
) {
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("LNG2\n"));
LNG2 lngData = LNG2(meterState, payload, meterState.getMeterType(), &meterConfig, ctx, debugger);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugger->printf_P(PSTR("LNG2\n"));
LNG2 lngData = LNG2(meterState, payload, meterState.getMeterType(), &meterConfig, ctx);
if(lngData.getListType() >= 1) {
data = new AmsData();
data->apply(meterState);
data->apply(lngData);
}
} else {
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("DLMS\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugger->printf_P(PSTR("DLMS\n"));
// TODO: Split IEC6205675 into DataParserKaifa and DataParserObis. This way we can add other means of parsing, for those other proprietary formats
data = new IEC6205675(payload, meterState.getMeterType(), &meterConfig, ctx, meterState);
data = new IEC6205675(payload, meterState.getMeterType(), &meterConfig, ctx, meterState, debugger);
}
} else if(ctx.type == DATA_TAG_DSMR) {
data = new IEC6205621(payload, tz, &meterConfig);
@@ -249,7 +286,7 @@ AmsData* PassiveMeterCommunicator::getData(AmsData& meterState) {
len = 0;
if(data != NULL) {
if(data->getListType() > 0) {
validDataReceived = true;
validDataReceived++;
if(rxBufferErrors > 0) rxBufferErrors--;
}
}
@@ -261,7 +298,10 @@ int PassiveMeterCommunicator::getLastError() {
#if defined ESP8266
if(hwSerial != NULL) {
if(hwSerial->hasRxError()) {
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("Serial RX error\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("Serial RX error\n"));
lastError = 96;
}
if(hwSerial->hasOverrun()) {
@@ -280,11 +320,14 @@ bool PassiveMeterCommunicator::isConfigChanged() {
return configChanged;
}
void PassiveMeterCommunicator::ackConfigChanged() {
configChanged = false;
}
void PassiveMeterCommunicator::getCurrentConfig(MeterConfig& meterConfig) {
meterConfig = this->meterConfig;
}
int16_t PassiveMeterCommunicator::unwrapData(uint8_t *buf, DataParserContext &context) {
int16_t ret = 0;
bool doRet = false;
@@ -322,8 +365,12 @@ int16_t PassiveMeterCommunicator::unwrapData(uint8_t *buf, DataParserContext &co
if(res >= 0) doRet = true;
break;
case DATA_TAG_DSMR:
if(dsmrParser == NULL) dsmrParser = new DSMRParser();
res = dsmrParser->parse(buf, context, lastTag != DATA_TAG_NONE);
if(dsmrParser == NULL) dsmrParser = new DSMRParser(gcmParser);
#if defined(AMS_REMOTE_DEBUG)
res = dsmrParser->parse(buf, context, lastTag != DATA_TAG_NONE, debugger->isActive(RemoteDebug::VERBOSE) ? debugger : NULL);
#else
res = dsmrParser->parse(buf, context, lastTag != DATA_TAG_NONE, debugger);
#endif
if(res >= 0) doRet = true;
break;
case DATA_TAG_SNRM:
@@ -333,7 +380,10 @@ int16_t PassiveMeterCommunicator::unwrapData(uint8_t *buf, DataParserContext &co
doRet = true;
break;
default:
if (debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("Ended up in default case while unwrapping...(tag %02X)\n"), tag);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("Ended up in default case while unwrapping...(tag %02X)\n"), tag);
return DATA_PARSE_UNKNOWN_DATA;
}
lastTag = tag;
@@ -341,53 +391,89 @@ int16_t PassiveMeterCommunicator::unwrapData(uint8_t *buf, DataParserContext &co
return res;
}
if(context.length > end) {
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("Context length %lu > %lu:\n"), context.length, end);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugger->printf_P(PSTR("Context length %lu > %lu:\n"), context.length, end);
context.type = 0;
context.length = 0;
return false;
}
switch(tag) {
case DATA_TAG_HDLC:
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("HDLC frame:\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugger->printf_P(PSTR("HDLC frame:\n"));
if(pt != NULL) {
pt->publishBytes(buf, curLen);
}
break;
case DATA_TAG_MBUS:
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("MBUS frame:\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugger->printf_P(PSTR("MBUS frame:\n"));
if(pt != NULL) {
pt->publishBytes(buf, curLen);
}
break;
case DATA_TAG_GBT:
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("GBT frame:\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugger->printf_P(PSTR("GBT frame:\n"));
break;
case DATA_TAG_GCM:
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("GCM frame:\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugger->printf_P(PSTR("GCM frame:\n"));
break;
case DATA_TAG_LLC:
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("LLC frame:\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugger->printf_P(PSTR("LLC frame:\n"));
break;
case DATA_TAG_DLMS:
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("DLMS frame:\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugger->printf_P(PSTR("DLMS frame:\n"));
break;
case DATA_TAG_DSMR:
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("DSMR frame:\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugger->printf_P(PSTR("DSMR frame:\n"));
if(pt != NULL) {
pt->publishString((char*) buf);
}
break;
case DATA_TAG_SNRM:
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("SNMR frame:\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugger->printf_P(PSTR("SNMR frame:\n"));
break;
case DATA_TAG_AARE:
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("AARE frame:\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugger->printf_P(PSTR("AARE frame:\n"));
break;
case DATA_TAG_RES:
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("RES frame:\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugger->printf_P(PSTR("RES frame:\n"));
break;
}
if(debugger->isActive(RemoteDebug::VERBOSE)) debugPrint(buf, 0, curLen);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugPrint(buf, 0, curLen, debugger);
if(res == DATA_PARSE_FINAL_SEGMENT) {
if(tag == DATA_TAG_MBUS) {
res = mbusParser->write(buf, context);
@@ -410,80 +496,105 @@ int16_t PassiveMeterCommunicator::unwrapData(uint8_t *buf, DataParserContext &co
// Use start byte of new buffer position as tag for next round in loop
tag = (*buf);
}
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("Got to end of unwrap method...\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("Got to end of unwrap method...\n"));
return DATA_PARSE_UNKNOWN_DATA;
}
void PassiveMeterCommunicator::debugPrint(byte *buffer, int start, int length) {
for (int i = start; i < start + length; i++) {
if (buffer[i] < 0x10)
debugger->print(F("0"));
debugger->print(buffer[i], HEX);
debugger->print(F(" "));
if ((i - start + 1) % 16 == 0)
debugger->println(F(""));
else if ((i - start + 1) % 4 == 0)
debugger->print(F(" "));
yield(); // Let other get some resources too
}
debugger->println(F(""));
}
void PassiveMeterCommunicator::printHanReadError(int pos) {
if(debugger->isActive(RemoteDebug::WARNING)) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
{
switch(pos) {
case DATA_PARSE_BOUNDRY_FLAG_MISSING:
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Boundry flag missing\n"));
case DATA_PARSE_BOUNDARY_FLAG_MISSING:
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
debugger->printf_P(PSTR("Boundary flag missing\n"));
break;
case DATA_PARSE_HEADER_CHECKSUM_ERROR:
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Header checksum error\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
debugger->printf_P(PSTR("Header checksum error\n"));
break;
case DATA_PARSE_FOOTER_CHECKSUM_ERROR:
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Frame checksum error\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
debugger->printf_P(PSTR("Frame checksum error\n"));
break;
case DATA_PARSE_INCOMPLETE:
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Received frame is incomplete\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
debugger->printf_P(PSTR("Received frame is incomplete\n"));
break;
case GCM_AUTH_FAILED:
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Decrypt authentication failed\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
debugger->printf_P(PSTR("Decrypt authentication failed\n"));
break;
case GCM_ENCRYPTION_KEY_FAILED:
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Setting decryption key failed\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
debugger->printf_P(PSTR("Setting decryption key failed\n"));
break;
case GCM_DECRYPT_FAILED:
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Decryption failed\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
debugger->printf_P(PSTR("Decryption failed\n"));
break;
case MBUS_FRAME_LENGTH_NOT_EQUAL:
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Frame length mismatch\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
debugger->printf_P(PSTR("Frame length mismatch\n"));
break;
case DATA_PARSE_INTERMEDIATE_SEGMENT:
if (debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Intermediate segment received\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("Intermediate segment received\n"));
break;
case DATA_PARSE_UNKNOWN_DATA:
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Unknown data format %02X\n"), hanBuffer[0]);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
debugger->printf_P(PSTR("Unknown data format %02X\n"), hanBuffer[0]);
break;
default:
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Unspecified error while reading data: %d\n"), pos);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
debugger->printf_P(PSTR("Unspecified error while reading data: %d\n"), pos);
}
}
}
void PassiveMeterCommunicator::setupHanPort(uint32_t baud, uint8_t parityOrdinal, bool invert) {
void PassiveMeterCommunicator::setupHanPort(uint32_t baud, uint8_t parityOrdinal, bool invert, bool passive) {
int8_t rxpin = meterConfig.rxPin;
int8_t txpin = meterConfig.txPin;
if (debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(setupHanPort) Setting up HAN on pin %d/%d with baud %d and parity %d\n"), rxpin, txpin, baud, parityOrdinal);
int8_t txpin = passive ? -1 : meterConfig.txPin;
if(baud == 0) {
baud = bauds[meterAutoIndex];
parityOrdinal = parities[meterAutoIndex];
invert = inverts[meterAutoIndex];
autodetectBaud = baud = 2400;
}
if(parityOrdinal == 0) {
parityOrdinal = 3; // 8N1
parityOrdinal = 11; // 8E1
}
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("(setupHanPort) Setting up HAN on pin %d/%d with baud %d and parity %d\n"), rxpin, txpin, baud, parityOrdinal);
if(rxpin == 3 || rxpin == 113) {
#if ARDUINO_USB_CDC_ON_BOOT
hwSerial = &Serial0;
@@ -505,7 +616,10 @@ void PassiveMeterCommunicator::setupHanPort(uint32_t baud, uint8_t parityOrdinal
#endif
if(rxpin == 0) {
if (debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("Invalid GPIO configured for HAN\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("Invalid GPIO configured for HAN\n"));
return;
}
@@ -513,7 +627,10 @@ void PassiveMeterCommunicator::setupHanPort(uint32_t baud, uint8_t parityOrdinal
if(meterConfig.bufferSize > 64) meterConfig.bufferSize = 64;
if(hwSerial != NULL) {
if (debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Hardware serial\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Hardware serial\n"));
Serial.flush();
#if defined(ESP8266)
SerialConfig serialConfig;
@@ -549,10 +666,16 @@ void PassiveMeterCommunicator::setupHanPort(uint32_t baud, uint8_t parityOrdinal
#if defined(ESP8266)
if(rxpin == 3) {
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Switching UART0 to pin 1 & 3\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("Switching UART0 to pin 1 & 3\n"));
Serial.pins(1,3);
} else if(rxpin == 113) {
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Switching UART0 to pin 15 & 13\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("Switching UART0 to pin 15 & 13\n"));
Serial.pins(15,13);
}
#endif
@@ -584,7 +707,10 @@ void PassiveMeterCommunicator::setupHanPort(uint32_t baud, uint8_t parityOrdinal
#endif
} else {
#if defined(ESP8266)
if (debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Software serial\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Software serial\n"));
Serial.flush();
if(swSerial == NULL) {
@@ -616,15 +742,18 @@ void PassiveMeterCommunicator::setupHanPort(uint32_t baud, uint8_t parityOrdinal
#if defined(ESP8266)
if(bufferSize > 2) bufferSize = 2;
#endif
if (debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Using serial buffer size %d"), 64 * bufferSize);
swSerial->begin(baud, serialConfig, rxpin, txpin, invert, meterConfig.bufferSize * 64);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Using serial buffer size %d\n"), 64 * bufferSize);
swSerial->begin(baud, serialConfig, rxpin, txpin, invert, meterConfig.bufferSize * 64, meterConfig.bufferSize * 64);
hanSerial = swSerial;
Serial.end();
Serial.begin(115200);
hwSerial = NULL;
#else
if (debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Software serial not available\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Software serial not available\n"));
return;
#endif
}
@@ -632,15 +761,23 @@ void PassiveMeterCommunicator::setupHanPort(uint32_t baud, uint8_t parityOrdinal
if(hanBuffer != NULL) {
free(hanBuffer);
}
hanBufferSize = max(64 * meterConfig.bufferSize * 2, 512);
hanBufferSize = max(64 * meterConfig.bufferSize * 3, 512);
hanBuffer = (uint8_t*) malloc(hanBufferSize);
// The library automatically sets the pullup in Serial.begin()
if(!meterConfig.rxPinPullup) {
if (debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("HAN pin pullup disabled\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("HAN pin pullup disabled\n"));
pinMode(meterConfig.rxPin, INPUT);
}
if(meterConfig.txPin != 0xFF && passive) {
pinMode(meterConfig.txPin, OUTPUT);
digitalWrite(meterConfig.txPin, LOW);
}
hanSerial->setTimeout(250);
// Empty buffer before starting
@@ -662,25 +799,48 @@ HardwareSerial* PassiveMeterCommunicator::getHwSerial() {
void PassiveMeterCommunicator::rxerr(int err) {
if(err == 0) return;
if(lastError == 90+err) return; // Do not flood with same error
switch(err) {
case 2:
if (debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("Serial buffer overflow\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("Serial buffer overflow\n"));
rxBufferErrors++;
if(rxBufferErrors > 3 && meterConfig.bufferSize < 64) {
if(rxBufferErrors > 1 && meterConfig.bufferSize < 8) {
meterConfig.bufferSize += 2;
if (debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Increasing RX buffer to %d bytes\n"), meterConfig.bufferSize * 64);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("Increasing RX buffer to %d bytes\n"), meterConfig.bufferSize * 64);
configChanged = true;
rxBufferErrors = 0;
}
break;
case 3:
if (debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("Serial FIFO overflow\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("Serial FIFO overflow\n"));
break;
case 4:
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Serial frame error\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
debugger->printf_P(PSTR("Serial frame error\n"));
break;
case 5:
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Serial parity error\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
debugger->printf_P(PSTR("Serial parity error\n"));
unsigned long now = millis();
if(autodetect) {
meterAutodetectLastChange = 0;
} else if(validDataReceived > 2) {
meterConfig.parity = getNextParity(meterConfig.parity);
configChanged = true;
}
break;
}
// Do not include serial break
@@ -689,23 +849,55 @@ void PassiveMeterCommunicator::rxerr(int err) {
void PassiveMeterCommunicator::handleAutodetect(unsigned long now) {
if(!autodetect) return;
if(now - meterAutodetectLastChange < 12000) return;
if(!validDataReceived) {
if(now - meterAutodetectLastChange > 20000 && (meterConfig.baud == 0 || meterConfig.parity == 0)) {
if(validDataReceived < 2) {
if(meterConfig.baud == 0 || meterConfig.parity == 0) {
autodetect = true;
meterAutoIndex++; // Default is to try the first one in setup()
if (debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Meter serial autodetect, swapping to: %d, %d, %s\n"), bauds[meterAutoIndex], parities[meterAutoIndex], inverts[meterAutoIndex] ? "true" : "false");
if(meterAutoIndex >= 6) meterAutoIndex = 0;
setupHanPort(bauds[meterAutoIndex], parities[meterAutoIndex], inverts[meterAutoIndex]);
if(lastError == 95) { // If parity error, switch parity
autodetectParity = getNextParity(autodetectParity);
lastError = 0;
} else {
autodetectCount++;
}
if(autodetectCount == sizeof(AUTO_BAUD_RATES)/sizeof(AUTO_BAUD_RATES[0])) {
autodetectInvert = !autodetectInvert;
autodetectCount = 0;
}
autodetectBaud = AUTO_BAUD_RATES[autodetectCount];
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("Meter serial autodetect, swapping to: %d, %d, %s\n"), autodetectBaud, autodetectParity, autodetectInvert ? "true" : "false");
meterConfig.bufferSize = max((uint32_t) 1, autodetectBaud / 14400);
setupHanPort(autodetectBaud, autodetectParity, autodetectInvert);
meterAutodetectLastChange = now;
}
} else if(autodetect) {
if (debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Meter serial autodetected, saving: %d, %d, %s\n"), bauds[meterAutoIndex], parities[meterAutoIndex], inverts[meterAutoIndex] ? "true" : "false");
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("Meter serial autodetected, saving: %d, %d, %s\n"), autodetectBaud, autodetectParity, autodetectInvert ? "true" : "false");
autodetect = false;
meterConfig.baud = bauds[meterAutoIndex];
meterConfig.parity = parities[meterAutoIndex];
meterConfig.invert = inverts[meterAutoIndex];
meterConfig.baud = autodetectBaud;
meterConfig.parity = autodetectParity;
meterConfig.invert = autodetectInvert;
configChanged = true;
setupHanPort(meterConfig.baud, meterConfig.parity, meterConfig.invert);
}
}
uint8_t PassiveMeterCommunicator::getNextParity(uint8_t parityOrdinal) {
switch(parityOrdinal) {
case 10: // 7E1
return 2; // 7N1
case 14: // 7E2
return 6; // 7N2
case 11: // 8E1
return 3; // 8N1
case 15: // 8E2
return 7; // 8N2
}
return 3;
}

View File

@@ -1,7 +1,17 @@
/**
* @copyright Utilitech AS 2023
* License: Fair Source
*
*/
#include "PulseMeterCommunicator.h"
#include "Uptime.h"
#if defined(AMS_REMOTE_DEBUG)
PulseMeterCommunicator::PulseMeterCommunicator(RemoteDebug* debugger) {
#else
PulseMeterCommunicator::PulseMeterCommunicator(Stream* debugger) {
#endif
this->debugger = debugger;
}
@@ -37,12 +47,19 @@ bool PulseMeterCommunicator::isConfigChanged() {
return this->configChanged;
}
void PulseMeterCommunicator::ackConfigChanged() {
configChanged = false;
}
void PulseMeterCommunicator::getCurrentConfig(MeterConfig& meterConfig) {
meterConfig = this->meterConfig;
}
void PulseMeterCommunicator::setupGpio() {
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Setting up Pulse Meter GPIO, rx: %d, tx: %d\n"), meterConfig.rxPin, meterConfig.txPin);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Setting up Pulse Meter GPIO, rx: %d, tx: %d\n"), meterConfig.rxPin, meterConfig.txPin);
if(meterConfig.rxPin != NOT_A_PIN) {
pinMode(meterConfig.rxPin, meterConfig.rxPinPullup ? INPUT_PULLUP : INPUT);
}

View File

@@ -11,9 +11,15 @@
class PassthroughMqttHandler : public AmsMqttHandler {
public:
PassthroughMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf) : AmsMqttHandler(mqttConfig, debugger, buf) {
#if defined(AMS_REMOTE_DEBUG)
PassthroughMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, AmsFirmwareUpdater* updater) : AmsMqttHandler(mqttConfig, debugger, buf, updater) {
this->topic = String(mqttConfig.publishTopic);
};
#else
PassthroughMqttHandler(MqttConfig& mqttConfig, Stream* debugger, char* buf) : AmsMqttHandler(mqttConfig, debugger, buf) {
this->topic = String(mqttConfig.publishTopic);
};
#endif
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, PriceService* ps);
bool publishTemperatures(AmsConfiguration*, HwTools*);
bool publishPrices(PriceService*);

View File

@@ -11,7 +11,9 @@
#include "TimeLib.h"
#include "Timezone.h"
#if defined(AMS_REMOTE_DEBUG)
#include "RemoteDebug.h"
#endif
#include "AmsConfiguration.h"
#include "EntsoeA44Parser.h"
@@ -63,8 +65,13 @@ struct PricePart {
class PriceService {
public:
#if defined(AMS_REMOTE_DEBUG)
PriceService(RemoteDebug*);
#else
PriceService(Stream*);
#endif
void setup(PriceServiceConfig&);
void setTimezone(Timezone* tz);
bool loop();
char* getToken();
@@ -88,7 +95,11 @@ public:
bool save();
private:
#if defined(AMS_REMOTE_DEBUG)
RemoteDebug* debugger;
#else
Stream* debugger;
#endif
PriceServiceConfig* config = NULL;
HTTPClient* http = NULL;
@@ -104,6 +115,7 @@ private:
std::vector<PriceConfig> priceConfig;
Timezone* tz = NULL;
Timezone* entsoeTz = NULL;
static const uint16_t BufferSize = 256;
char* buf;
@@ -119,7 +131,6 @@ private:
PricesContainer* fetchPrices(time_t);
bool retrieve(const char* url, Stream* doc);
float getCurrencyMultiplier(const char* from, const char* to, time_t t);
void debugPrint(byte *buffer, int start, int length);
bool timeIsInPeriod(tmElements_t tm, PriceConfig pc);
};
#endif

View File

@@ -72,7 +72,10 @@ size_t EntsoeA44Parser::write(uint8_t byte) {
} else if(docPos == DOCPOS_POSITION) {
if(byte == '<') {
buf[pos] = '\0';
pointNum = String(buf).toInt() - 1;
long pn = String(buf).toInt() - 1;
if(pn < 25) {
pointNum = pn;
}
docPos = DOCPOS_SEEK;
pos = 0;
} else {
@@ -81,7 +84,10 @@ size_t EntsoeA44Parser::write(uint8_t byte) {
} else if(docPos == DOCPOS_AMOUNT) {
if(byte == '<') {
buf[pos] = '\0';
points[pointNum] = String(buf).toFloat();
float val = String(buf).toFloat();
for(uint8_t i = pointNum; i < 25; i++) {
points[i] = val;
}
docPos = DOCPOS_SEEK;
pos = 0;
} else {

View File

@@ -20,7 +20,11 @@
#include <esp_task_wdt.h>
#endif
#if defined(AMS_REMOTE_DEBUG)
PriceService::PriceService(RemoteDebug* Debug) : priceConfig(std::vector<PriceConfig>()) {
#else
PriceService::PriceService(Stream* Debug) : priceConfig(std::vector<PriceConfig>()) {
#endif
this->buf = (char*) malloc(BufferSize);
debugger = Debug;
@@ -28,7 +32,8 @@ PriceService::PriceService(RemoteDebug* Debug) : priceConfig(std::vector<PriceCo
// Entso-E uses CET/CEST
TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120};
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60};
tz = new Timezone(CEST, CET);
entsoeTz = new Timezone(CEST, CET);
tz = entsoeTz;
tomorrowFetchMinute = 15 + random(45); // Random between 13:15 and 14:00
}
@@ -68,8 +73,12 @@ void PriceService::setup(PriceServiceConfig& config) {
load();
}
void PriceService::setTimezone(Timezone* tz) {
this->tz = tz;
}
char* PriceService::getToken() {
return this->config->entsoeToken;
return ""; // Currently the implementation is not working, so lets disable it for al. Old code: this->config->entsoeToken;
}
char* PriceService::getCurrency() {
@@ -106,30 +115,23 @@ float PriceService::getValueForHour(uint8_t direction, time_t ts, int8_t hour) {
return ret;
tmElements_t tm;
breakTime(tz->toLocal(ts + (hour) * SECS_PER_HOUR), tm);
uint8_t day = 0x01 << (tm.Wday - 2);
uint32_t hrs = 0x01 << tm.Hour;
breakTime(tz->toLocal(ts + (hour * SECS_PER_HOUR)), tm);
for (uint8_t i = 0; i < priceConfig.size(); i++) {
PriceConfig pc = priceConfig.at(i);
if(pc.type == PRICE_TYPE_FIXED) continue;
uint8_t start_month = pc.start_month == 0 || pc.start_month > 12 ? 1 : pc.start_month;
uint8_t start_dayofmonth = pc.start_dayofmonth == 0 || pc.start_dayofmonth > 31 ? 1 : pc.start_dayofmonth;
uint8_t end_month = pc.end_month == 0 || pc.end_month > 12 ? 12 : pc.end_month;
uint8_t end_dayofmonth = pc.end_dayofmonth == 0 || pc.end_dayofmonth > 31 ? 31 : pc.end_dayofmonth;
if((pc.direction & direction) == direction && (pc.days & day) == day && (pc.hours & hrs) == hrs && tm.Month >= start_month && tm.Day >= start_dayofmonth && tm.Month <= end_month && tm.Day <= end_dayofmonth) {
switch(pc.type) {
case PRICE_TYPE_ADD:
ret += pc.value / 10000.0;
break;
case PRICE_TYPE_SUBTRACT:
ret -= pc.value / 10000.0;
break;
case PRICE_TYPE_PCT:
ret += ((pc.value / 10000.0) * ret) / 100.0;
break;
}
if((pc.direction & direction) != direction) continue;
if(!timeIsInPeriod(tm, pc)) continue;
switch(pc.type) {
case PRICE_TYPE_ADD:
ret += pc.value / 10000.0;
break;
case PRICE_TYPE_SUBTRACT:
ret -= pc.value / 10000.0;
break;
case PRICE_TYPE_PCT:
ret += ((pc.value / 10000.0) * ret) / 100.0;
break;
}
}
return ret;
@@ -137,54 +139,55 @@ float PriceService::getValueForHour(uint8_t direction, time_t ts, int8_t hour) {
float PriceService::getEnergyPriceForHour(uint8_t direction, time_t ts, int8_t hour) {
tmElements_t tm;
breakTime(tz->toLocal(ts + (hour) * SECS_PER_HOUR), tm);
uint8_t day = 0x01 << (tm.Wday - 2);
uint32_t hrs = 0x01 << tm.Hour;
breakTime(tz->toLocal(ts + (hour * SECS_PER_HOUR)), tm);
float value = PRICE_NO_VALUE;
for (uint8_t i = 0; i < priceConfig.size(); i++) {
PriceConfig pc = priceConfig.at(i);
if(pc.type != PRICE_TYPE_FIXED) continue;
uint8_t start_month = pc.start_month == 0 || pc.start_month > 12 ? 1 : pc.start_month;
uint8_t start_dayofmonth = pc.start_dayofmonth == 0 || pc.start_dayofmonth > 31 ? 1 : pc.start_dayofmonth;
uint8_t end_month = pc.end_month == 0 || pc.end_month > 12 ? 12 : pc.end_month;
uint8_t end_dayofmonth = pc.end_dayofmonth == 0 || pc.end_dayofmonth > 31 ? 31 : pc.end_dayofmonth;
if((pc.direction & direction) != direction) continue;
if(!timeIsInPeriod(tm, pc)) continue;
if((pc.direction & direction) == direction && (pc.days & day) == day && (pc.hours & hrs) == hrs && tm.Month >= start_month && tm.Day >= start_dayofmonth && tm.Month <= end_month && tm.Day <= end_dayofmonth) {
if(value == PRICE_NO_VALUE) {
value = pc.value / 10000.0;
} else {
value += pc.value / 10000.0;
}
if(value == PRICE_NO_VALUE) {
value = pc.value / 10000.0;
} else {
value += pc.value / 10000.0;
}
}
if(value != PRICE_NO_VALUE) return value;
int8_t pos = hour;
breakTime(tz->toLocal(ts), tm);
breakTime(entsoeTz->toLocal(ts), tm);
while(tm.Hour > 0) {
ts -= 3600;
breakTime(tz->toLocal(ts), tm);
breakTime(entsoeTz->toLocal(ts), tm);
pos++;
}
uint8_t hoursToday = 0;
uint8_t todayDate = tm.Day;
while(tm.Day == todayDate) {
ts += 3600;
breakTime(tz->toLocal(ts), tm);
breakTime(entsoeTz->toLocal(ts), tm);
hoursToday++;
}
if(pos > 49)
return PRICE_NO_VALUE;
uint8_t hoursTomorrow = 0;
uint8_t tomorrowDate = tm.Day;
while(tm.Day == tomorrowDate) {
ts += 3600;
breakTime(entsoeTz->toLocal(ts), tm);
hoursTomorrow++;
}
float multiplier = 1.0;
if(pos >= hoursToday) {
pos = pos - hoursToday;
if(pos >= hoursTomorrow) return PRICE_NO_VALUE;
if(tomorrow == NULL)
return PRICE_NO_VALUE;
if(tomorrow->points[pos-hoursToday] == PRICE_NO_VALUE)
if(tomorrow->points[pos] == PRICE_NO_VALUE)
return PRICE_NO_VALUE;
value = tomorrow->points[pos-hoursToday] / 10000.0;
value = tomorrow->points[pos] / 10000.0;
if(strcmp(tomorrow->measurementUnit, "KWH") == 0) {
// Multiplier is 1
} else if(strcmp(tomorrow->measurementUnit, "MWH") == 0) {
@@ -212,7 +215,7 @@ float PriceService::getEnergyPriceForHour(uint8_t direction, time_t ts, int8_t h
if(mult == 0) return PRICE_NO_VALUE;
multiplier *= mult;
}
return value * multiplier;
return value == PRICE_NO_VALUE ? PRICE_NO_VALUE : value * multiplier;
}
bool PriceService::loop() {
@@ -227,15 +230,13 @@ bool PriceService::loop() {
return false;
}
#endif
if(!config->enabled)
return false;
if(strlen(config->area) == 0)
return false;
if(strlen(config->currency) == 0)
return false;
tmElements_t tm;
breakTime(tz->toLocal(t), tm);
breakTime(entsoeTz->toLocal(t), tm);
if(currentDay == 0) {
currentDay = tm.Day;
@@ -250,12 +251,15 @@ bool PriceService::loop() {
}
currentDay = tm.Day;
currentHour = tm.Hour;
return today != NULL; // Only trigger MQTT publish if we have todays prices.
return today != NULL || (!config->enabled && priceConfig.capacity() != 0); // Only trigger MQTT publish if we have todays prices.
} else if(currentHour != tm.Hour) {
currentHour = tm.Hour;
return today != NULL; // Only trigger MQTT publish if we have todays prices.
return today != NULL || (!config->enabled && priceConfig.capacity() != 0); // Only trigger MQTT publish if we have todays prices.
}
if(!config->enabled)
return false;
bool readyToFetchForTomorrow = tomorrow == NULL && (tm.Hour > 13 || (tm.Hour == 13 && tm.Minute >= tomorrowFetchMinute)) && (lastTomorrowFetch == 0 || now - lastTomorrowFetch > (nextFetchDelayMinutes*60000));
if(today == NULL && (lastTodayFetch == 0 || now - lastTodayFetch > (nextFetchDelayMinutes*60000))) {
@@ -323,9 +327,18 @@ bool PriceService::retrieve(const char* url, Stream* doc) {
} else {
nextFetchDelayMinutes = 2;
}
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("(PriceService) Communication error, returned status: %d\n"), status);
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf(http->errorToString(status).c_str());
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf(http->getString().c_str());
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("(PriceService) Communication error, returned status: %d\n"), status);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf(http->errorToString(status).c_str());
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf(http->getString().c_str());
http->end();
return false;
@@ -356,12 +369,10 @@ float PriceService::getCurrencyMultiplier(const char* from, const char* to, time
float currencyMultiplier = 0;
snprintf_P(buf, BufferSize, PSTR("https://data.norges-bank.no/api/data/EXR/B.%s.NOK.SP?lastNObservations=1"), from);
if(retrieve(buf, &p)) {
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("(PriceService) got exchange rate %.4f\n"), p.getValue());
currencyMultiplier = p.getValue();
if(strncmp(to, "NOK", 3) != 0) {
snprintf_P(buf, BufferSize, PSTR("https://data.norges-bank.no/api/data/EXR/B.%s.NOK.SP?lastNObservations=1"), to);
if(retrieve(buf, &p)) {
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("(PriceService) got exchange rate %.4f\n"), p.getValue());
if(p.getValue() > 0.0) {
currencyMultiplier /= p.getValue();
} else {
@@ -373,13 +384,19 @@ float PriceService::getCurrencyMultiplier(const char* from, const char* to, time
}
}
if(currencyMultiplier != 0) {
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("(PriceService) Resulting currency multiplier: %.4f\n"), currencyMultiplier);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("(PriceService) Resulting currency multiplier: %.4f\n"), currencyMultiplier);
tmElements_t tm;
breakTime(t, tm);
lastCurrencyFetch = now + (SECS_PER_DAY * 1000) - (((((tm.Hour * 60) + tm.Minute) * 60) + tm.Second) * 1000) + (3600000 * 6) + (tomorrowFetchMinute * 60);
this->currencyMultiplier = currencyMultiplier;
} else {
if(debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("(PriceService) Multiplier ended in success, but without value\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
debugger->printf_P(PSTR("(PriceService) Multiplier ended in success, but without value\n"));
lastCurrencyFetch = now + (SECS_PER_HOUR * 1000);
if(this->currencyMultiplier == 1) return 0;
}
@@ -390,7 +407,7 @@ float PriceService::getCurrencyMultiplier(const char* from, const char* to, time
PricesContainer* PriceService::fetchPrices(time_t t) {
if(strlen(getToken()) > 0) {
tmElements_t tm;
breakTime(tz->toLocal(t), tm);
breakTime(entsoeTz->toLocal(t), tm);
time_t e1 = t - (tm.Hour * 3600) - (tm.Minute * 60) - tm.Second; // Local midnight
time_t e2 = e1 + SECS_PER_DAY;
tmElements_t d1, d2;
@@ -409,8 +426,14 @@ PricesContainer* PriceService::fetchPrices(time_t t) {
ESP.wdtFeed();
#endif
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(PriceService) Fetching prices for %02d.%02d.%04d\n"), tm.Day, tm.Month, tm.Year+1970);
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("(PriceService) url: %s\n"), buf);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("(PriceService) Fetching prices for %02d.%02d.%04d\n"), tm.Day, tm.Month, tm.Year+1970);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("(PriceService) url: %s\n"), buf);
EntsoeA44Parser a44;
if(retrieve(buf, &a44) && a44.getPoint(0) != PRICE_NO_VALUE) {
PricesContainer* ret = new PricesContainer();
@@ -421,7 +444,7 @@ PricesContainer* PriceService::fetchPrices(time_t t) {
}
} else if(hub) {
tmElements_t tm;
breakTime(tz->toLocal(t), tm);
breakTime(entsoeTz->toLocal(t), tm);
String data;
snprintf_P(buf, BufferSize, PSTR("http://hub.amsleser.no/hub/price/%s/%d/%d/%d?currency=%s"),
@@ -431,8 +454,14 @@ PricesContainer* PriceService::fetchPrices(time_t t) {
tm.Day,
config->currency
);
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(PriceService) Fetching prices for %02d.%02d.%04d\n"), tm.Day, tm.Month, tm.Year+1970);
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("(PriceService) url: %s\n"), buf);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("(PriceService) Fetching prices for %02d.%02d.%04d\n"), tm.Day, tm.Month, tm.Year+1970);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("(PriceService) url: %s\n"), buf);
#if defined(ESP8266)
WiFiClient client;
client.setTimeout(5000);
@@ -473,7 +502,10 @@ PricesContainer* PriceService::fetchPrices(time_t t) {
} else {
lastError = gcmRet;
nextFetchDelayMinutes = 60;
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("(PriceService) Error code while decrypting prices: %d\n"), gcmRet);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("(PriceService) Error code while decrypting prices: %d\n"), gcmRet);
}
} else {
lastError = status;
@@ -484,12 +516,21 @@ PricesContainer* PriceService::fetchPrices(time_t t) {
} else {
nextFetchDelayMinutes = 5;
}
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("(PriceService) Communication error, returned status: %d\n"), status);
if(debugger->isActive(RemoteDebug::ERROR)) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("(PriceService) Communication error, returned status: %d\n"), status);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
{
debugger->printf(http->errorToString(status).c_str());
debugger->println();
}
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf(http->getString().c_str());
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf(http->getString().c_str());
http->end();
}
@@ -498,22 +539,6 @@ PricesContainer* PriceService::fetchPrices(time_t t) {
return NULL;
}
void PriceService::debugPrint(byte *buffer, int start, int length) {
for (int i = start; i < start + length; i++) {
if (buffer[i] < 0x10)
debugger->print(F("0"));
debugger->print(buffer[i], HEX);
debugger->print(F(" "));
if ((i - start + 1) % 16 == 0)
debugger->println(F(""));
else if ((i - start + 1) % 4 == 0)
debugger->print(F(" "));
yield(); // Let other get some resources too
}
debugger->println(F(""));
}
int16_t PriceService::getLastError() {
return lastError;
}
@@ -523,7 +548,7 @@ std::vector<PriceConfig>& PriceService::getPriceConfig() {
}
void PriceService::setPriceConfig(uint8_t index, PriceConfig &priceConfig) {
stripNonAscii((uint8_t*) priceConfig.name, 32);
stripNonAscii((uint8_t*) priceConfig.name, 32, true);
if(this->priceConfig.capacity() != index+1)
this->priceConfig.resize(index+1);
@@ -541,11 +566,17 @@ void PriceService::cropPriceConfig(uint8_t size) {
bool PriceService::save() {
if(!LittleFS.begin()) {
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("(PriceService) Unable to load LittleFS\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("(PriceService) Unable to load LittleFS\n"));
return false;
}
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(PriceService) Saving price config\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("(PriceService) Saving price config\n"));
PriceConfig pc;
File file = LittleFS.open(FILE_PRICE_CONF, "w");
@@ -567,14 +598,19 @@ bool PriceService::save() {
bool PriceService::load() {
if(!LittleFS.begin()) {
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("(PriceService) Unable to load LittleFS\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("(PriceService) Unable to load LittleFS\n"));
return false;
}
if(!LittleFS.exists(FILE_PRICE_CONF)) {
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("(PriceService) No price config file\n"));
return false;
}
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(PriceService) Loading price config\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("(PriceService) Loading price config\n"));
this->priceConfig.clear();
@@ -588,4 +624,38 @@ bool PriceService::load() {
file.close();
return true;
}
bool PriceService::timeIsInPeriod(tmElements_t tm, PriceConfig pc) {
uint8_t day = 0x01 << ((tm.Wday+5)%7);
uint32_t hrs = 0x01 << tm.Hour;
if((pc.days & day) != day) return false;
if((pc.hours & hrs) != hrs) return false;
tmElements_t tms;
tms.Year = tm.Year;
tms.Month = pc.start_month == 0 || pc.start_month > 12 ? 1 : pc.start_month;
tms.Day = pc.start_dayofmonth == 0 || pc.start_dayofmonth > 31 ? 1 : pc.start_dayofmonth;
tms.Hour = 0;
tms.Minute = 0;
tms.Second = 0;
tmElements_t tme;
tme.Year = tm.Year;
tme.Month = pc.end_month == 0 || pc.end_month > 12 ? 12 : pc.end_month;
tme.Day = pc.end_dayofmonth == 0 || pc.end_dayofmonth > 31 ? 31 : pc.end_dayofmonth;
tme.Hour = 23;
tme.Minute = 59;
tme.Second = 59;
if(makeTime(tms) > makeTime(tme)) {
if(makeTime(tm) <= makeTime(tme)) {
tms.Year--;
} else {
tme.Year++;
}
}
return makeTime(tms) <= makeTime(tm) && makeTime(tme) >= makeTime(tm);
}

View File

@@ -11,10 +11,17 @@
class RawMqttHandler : public AmsMqttHandler {
public:
RawMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf) : AmsMqttHandler(mqttConfig, debugger, buf) {
#if defined(AMS_REMOTE_DEBUG)
RawMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, AmsFirmwareUpdater* updater) : AmsMqttHandler(mqttConfig, debugger, buf, updater) {
full = mqttConfig.payloadFormat == 2;
topic = String(mqttConfig.publishTopic);
};
#else
RawMqttHandler(MqttConfig& mqttConfig, Stream* debugger, char* buf) : AmsMqttHandler(mqttConfig, debugger, buf) {
full = mqttConfig.payloadFormat == 2;
topic = String(mqttConfig.publishTopic);
};
#endif
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, PriceService* ps);
bool publishTemperatures(AmsConfiguration*, HwTools*);
bool publishPrices(PriceService*);
@@ -28,6 +35,7 @@ public:
private:
bool full;
String topic;
uint32_t lastThresholdPublish = 0;
bool publishList1(AmsData* data, AmsData* meterState);
bool publishList2(AmsData* data, AmsData* meterState);

View File

@@ -7,26 +7,38 @@
#include "RawMqttHandler.h"
#include "hexutils.h"
#include "Uptime.h"
#include "FirmwareVersion.h"
bool RawMqttHandler::publish(AmsData* data, AmsData* meterState, EnergyAccounting* ea, PriceService* ps) {
bool RawMqttHandler::publish(AmsData* update, AmsData* previousState, EnergyAccounting* ea, PriceService* ps) {
if(topic.isEmpty() || !mqtt.connected())
return false;
if(data->getPackageTimestamp() > 0) {
mqtt.publish(topic + "/meter/dlms/timestamp", String(data->getPackageTimestamp()));
AmsData data;
if(mqttConfig.stateUpdate) {
uint64_t now = millis64();
if(now-lastStateUpdate < mqttConfig.stateUpdateInterval * 1000) return false;
data.apply(*previousState);
data.apply(*update);
lastStateUpdate = now;
} else {
data = *update;
}
switch(data->getListType()) {
if(data.getPackageTimestamp() > 0) {
mqtt.publish(topic + "/meter/dlms/timestamp", String(data.getPackageTimestamp()));
}
switch(data.getListType()) {
case 4:
publishList4(data, meterState);
publishList4(&data, previousState);
loop();
case 3:
publishList3(data, meterState);
publishList3(&data, previousState);
loop();
case 2:
publishList2(data, meterState);
publishList2(&data, previousState);
loop();
case 1:
publishList1(data, meterState);
publishList1(&data, previousState);
loop();
}
if(ea->isInitialized()) {
@@ -51,24 +63,28 @@ bool RawMqttHandler::publishList2(AmsData* data, AmsData* meterState) {
if(full || meterState->getMeterModel() != data->getMeterModel()) {
mqtt.publish(topic + "/meter/type", data->getMeterModel());
}
loop();
if(full || meterState->getL1Current() != data->getL1Current()) {
mqtt.publish(topic + "/meter/l1/current", String(data->getL1Current(), 2));
}
if(full || meterState->getL1Voltage() != data->getL1Voltage()) {
mqtt.publish(topic + "/meter/l1/voltage", String(data->getL1Voltage(), 2));
}
loop();
if(full || meterState->getL2Current() != data->getL2Current()) {
mqtt.publish(topic + "/meter/l2/current", String(data->getL2Current(), 2));
}
if(full || meterState->getL2Voltage() != data->getL2Voltage()) {
mqtt.publish(topic + "/meter/l2/voltage", String(data->getL2Voltage(), 2));
}
loop();
if(full || meterState->getL3Current() != data->getL3Current()) {
mqtt.publish(topic + "/meter/l3/current", String(data->getL3Current(), 2));
}
if(full || meterState->getL3Voltage() != data->getL3Voltage()) {
mqtt.publish(topic + "/meter/l3/voltage", String(data->getL3Voltage(), 2));
}
loop();
if(full || meterState->getReactiveExportPower() != data->getReactiveExportPower()) {
mqtt.publish(topic + "/meter/export/reactive", String(data->getReactiveExportPower()));
}
@@ -96,69 +112,103 @@ bool RawMqttHandler::publishList3(AmsData* data, AmsData* meterState) {
bool RawMqttHandler::publishList4(AmsData* data, AmsData* meterState) {
if(full || meterState->getL1ActiveImportPower() != data->getL1ActiveImportPower()) {
mqtt.publish(topic + "/meter/import/l1", String(data->getL1ActiveImportPower()));
mqtt.loop();
}
if(full || meterState->getL2ActiveImportPower() != data->getL2ActiveImportPower()) {
mqtt.publish(topic + "/meter/import/l2", String(data->getL2ActiveImportPower()));
mqtt.loop();
}
if(full || meterState->getL3ActiveImportPower() != data->getL3ActiveImportPower()) {
mqtt.publish(topic + "/meter/import/l3", String(data->getL3ActiveImportPower()));
mqtt.loop();
}
if(full || meterState->getL1ActiveExportPower() != data->getL1ActiveExportPower()) {
mqtt.publish(topic + "/meter/export/l1", String(data->getL1ActiveExportPower()));
mqtt.loop();
}
if(full || meterState->getL2ActiveExportPower() != data->getL2ActiveExportPower()) {
mqtt.publish(topic + "/meter/export/l2", String(data->getL2ActiveExportPower()));
mqtt.loop();
}
if(full || meterState->getL3ActiveExportPower() != data->getL3ActiveExportPower()) {
mqtt.publish(topic + "/meter/export/l3", String(data->getL3ActiveExportPower()));
mqtt.loop();
}
if(full || meterState->getL1ActiveImportCounter() != data->getL1ActiveImportCounter()) {
mqtt.publish(topic + "/meter/import/l1/accumulated", String(data->getL1ActiveImportCounter(), 2));
mqtt.loop();
}
if(full || meterState->getL2ActiveImportCounter() != data->getL2ActiveImportCounter()) {
mqtt.publish(topic + "/meter/import/l2/accumulated", String(data->getL2ActiveImportCounter(), 2));
mqtt.loop();
}
if(full || meterState->getL3ActiveImportCounter() != data->getL3ActiveImportCounter()) {
mqtt.publish(topic + "/meter/import/l3/accumulated", String(data->getL3ActiveImportCounter(), 2));
mqtt.loop();
}
if(full || meterState->getL1ActiveExportCounter() != data->getL1ActiveExportCounter()) {
mqtt.publish(topic + "/meter/export/l1/accumulated", String(data->getL1ActiveExportCounter(), 2));
mqtt.loop();
}
if(full || meterState->getL2ActiveExportCounter() != data->getL2ActiveExportCounter()) {
mqtt.publish(topic + "/meter/export/l2/accumulated", String(data->getL2ActiveExportCounter(), 2));
mqtt.loop();
}
if(full || meterState->getL3ActiveExportCounter() != data->getL3ActiveExportCounter()) {
mqtt.publish(topic + "/meter/export/l3/accumulated", String(data->getL3ActiveExportCounter(), 2));
mqtt.loop();
}
if(full || meterState->getPowerFactor() != data->getPowerFactor()) {
mqtt.publish(topic + "/meter/powerfactor", String(data->getPowerFactor(), 2));
mqtt.loop();
}
if(full || meterState->getL1PowerFactor() != data->getL1PowerFactor()) {
mqtt.publish(topic + "/meter/l1/powerfactor", String(data->getL1PowerFactor(), 2));
mqtt.loop();
}
if(full || meterState->getL2PowerFactor() != data->getL2PowerFactor()) {
mqtt.publish(topic + "/meter/l2/powerfactor", String(data->getL2PowerFactor(), 2));
mqtt.loop();
}
if(full || meterState->getL3PowerFactor() != data->getL3PowerFactor()) {
mqtt.publish(topic + "/meter/l3/powerfactor", String(data->getL3PowerFactor(), 2));
mqtt.loop();
}
return true;
}
bool RawMqttHandler::publishRealtime(EnergyAccounting* ea) {
mqtt.publish(topic + "/realtime/import/hour", String(ea->getUseThisHour(), 3));
mqtt.loop();
mqtt.publish(topic + "/realtime/import/day", String(ea->getUseToday(), 2));
mqtt.loop();
mqtt.publish(topic + "/realtime/import/month", String(ea->getUseThisMonth(), 1));
mqtt.loop();
uint8_t peakCount = ea->getConfig()->hours;
if(peakCount > 5) peakCount = 5;
for(uint8_t i = 1; i <= peakCount; i++) {
mqtt.publish(topic + "/realtime/import/peak/" + String(i, 10), String(ea->getPeak(i).value / 100.0, 10), true, 0);
mqtt.loop();
}
mqtt.publish(topic + "/realtime/import/threshold", String(ea->getCurrentThreshold(), 10), true, 0);
mqtt.loop();
mqtt.publish(topic + "/realtime/import/monthmax", String(ea->getMonthMax(), 3), true, 0);
mqtt.loop();
mqtt.publish(topic + "/realtime/export/hour", String(ea->getProducedThisHour(), 3));
mqtt.loop();
mqtt.publish(topic + "/realtime/export/day", String(ea->getProducedToday(), 2));
mqtt.loop();
mqtt.publish(topic + "/realtime/export/month", String(ea->getProducedThisMonth(), 1));
mqtt.loop();
uint32_t now = millis();
if(lastThresholdPublish == 0 || now-lastThresholdPublish > 3600000) {
EnergyAccountingConfig* conf = ea->getConfig();
for(uint8_t i = 0; i < 9; i++) {
mqtt.publish(topic + "/realtime/import/thresholds/" + String(i+1, 10), String(conf->thresholds[i], 10), true, 0);
mqtt.loop();
}
lastThresholdPublish = now;
}
return true;
}
@@ -169,6 +219,7 @@ bool RawMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw)
if(data != NULL && data->lastValidRead > -85) {
if(data->changed || full) {
mqtt.publish(topic + "/temperature/" + toHex(data->address), String(data->lastValidRead, 2));
mqtt.loop();
data->changed = false;
}
}
@@ -261,27 +312,40 @@ bool RawMqttHandler::publishPrices(PriceService* ps) {
float val = values[i];
if(val == PRICE_NO_VALUE) {
mqtt.publish(topic + "/price/" + String(i), "", true, 0);
mqtt.loop();
} else {
mqtt.publish(topic + "/price/" + String(i), String(val, 4), true, 0);
mqtt.loop();
}
mqtt.loop();
delay(10);
}
if(min != INT16_MAX) {
mqtt.publish(topic + "/price/min", String(min, 4), true, 0);
mqtt.loop();
}
if(max != INT16_MIN) {
mqtt.publish(topic + "/price/max", String(max, 4), true, 0);
mqtt.loop();
}
if(min1hrIdx != -1) {
mqtt.publish(topic + "/price/cheapest/1hr", String(ts1hr), true, 0);
mqtt.loop();
}
if(min3hrIdx != -1) {
mqtt.publish(topic + "/price/cheapest/3hr", String(ts3hr), true, 0);
mqtt.loop();
}
if(min6hrIdx != -1) {
mqtt.publish(topic + "/price/cheapest/6hr", String(ts6hr), true, 0);
mqtt.loop();
}
float exportPrice = ps->getEnergyPriceForHour(PRICE_DIRECTION_EXPORT, now, 0);
if(exportPrice == PRICE_NO_VALUE) {
mqtt.publish(topic + "/exportprice/0", "", true, 0);
mqtt.loop();
} else {
mqtt.publish(topic + "/exportprice/0", String(exportPrice, 4), true, 0);
mqtt.loop();
}
return true;
}
@@ -291,15 +355,21 @@ bool RawMqttHandler::publishSystem(HwTools* hw, PriceService* ps, EnergyAccounti
return false;
mqtt.publish(topic + "/id", WiFi.macAddress(), true, 0);
mqtt.loop();
mqtt.publish(topic + "/uptime", String((uint32_t) (millis64()/1000)));
mqtt.loop();
float vcc = hw->getVcc();
if(vcc > 0) {
mqtt.publish(topic + "/vcc", String(vcc, 2));
mqtt.loop();
}
mqtt.publish(topic + "/mem", String(ESP.getFreeHeap()));
mqtt.loop();
mqtt.publish(topic + "/rssi", String(hw->getWifiRssi()));
mqtt.loop();
if(hw->getTemperature() > -85) {
mqtt.publish(topic + "/temperature", String(hw->getTemperature(), 2));
mqtt.loop();
}
return true;
}
@@ -313,4 +383,11 @@ bool RawMqttHandler::publishRaw(String data) {
}
void RawMqttHandler::onMessage(String &topic, String &payload) {
if(strncmp(topic.c_str(), mqttConfig.subscribeTopic, 12) == 0) {
if(payload.equals("fwupgrade")) {
if(strcmp(updater->getNextVersion(), FirmwareVersion::VersionString) != 0) {
updater->setTargetVersion(updater->getNextVersion());
}
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

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