Compare commits

..

116 Commits

Author SHA1 Message Date
Thomas Basler
653efb41a2 Fix: Syntax error in defines 2025-01-14 23:07:46 +01:00
Thomas Basler
571ba2f350 Fix: Hint regarding required device profile is shown for profiles which don't need a device profile
This patch introduces a define which allows to specifiy for each environment if a device profile is absolutly required.

Fixes #2500
2025-01-14 22:13:48 +01:00
Thomas Basler
220cfbf7ae Update nrf24/RF24 from 1.4.10 to 1.4.11 2025-01-14 19:17:14 +01:00
Thomas Basler
db130f646e Upgrade ESPAsyncWebServer from 3.4.2 to 3.6.0 2025-01-14 19:14:17 +01:00
Thomas Basler
5510c9ff57 webapp: add app.js.gz 2025-01-14 18:43:41 +01:00
Thomas Basler
ebf4e921ee Merge branch 'pr2420' into dev 2025-01-14 18:38:23 +01:00
Thomas Basler
d068542c94 Merge branch 'pr2421' into dev 2025-01-14 18:37:40 +01:00
Thomas Basler
19fa310f43 webapp: Update dependencies 2025-01-14 18:35:45 +01:00
Thomas Basler
87772cb76b Feature: Add support for HERF-600 inverters
Fixes #2492
2025-01-08 17:52:33 +01:00
Thomas Basler
50207a42bf webapp: Update dependencies 2024-12-31 16:08:56 +01:00
Thomas Basler
a0e6942537 Feature: Detect if inverter supports 'Power Distribution Logic'
The detection of 'Power Distribution Logic' is based on the firmware version for specific models and is needed to disable any means of overscaling, as it simply does not work when 'Power Distrbution Logic' is available.

Based on the code from @AndreasBoehm
2024-12-31 16:08:37 +01:00
Thomas Basler
c37397acca Fix lint errors 2024-12-30 00:16:14 +01:00
Thomas Basler
498afe377b Remove extra semikolon 2024-12-30 00:06:08 +01:00
Thomas Basler
d43ac7fb92 Update bblanchon/ArduinoJson from 7.2.1 to 7.3.0 2024-12-29 22:32:43 +01:00
Thomas Basler
11105944be Fix: Uptime overflow after ~50 days
Fixes #2473
2024-12-29 20:53:20 +01:00
Thomas Basler
c7fa4ff212 Disable queue debugging 2024-12-21 11:59:51 +01:00
Thomas Basler
96ba58af8c Fix: Wifi.begin was called with wrong parameters
The third parameter should be a optional channel name and not a scan method. There exists a separate method for the scan method.
2024-12-18 23:00:27 +01:00
Thomas Basler
d485d1b820 Show totals in blue and producing inverters in green 2024-12-18 22:16:23 +01:00
Thomas Basler
8f60a3a12a Feature: Show inverter status and current power in overview (if multiple inverters are available) 2024-12-18 21:21:10 +01:00
Thomas Basler
940027ab19 Upgrade ESPAsyncWebServer from 3.4.1 to 3.4.2 2024-12-18 19:31:56 +01:00
Thomas Basler
24b3f27364 webapp: Update dependencies 2024-12-16 20:50:09 +01:00
Thomas Basler
5265c6281f Feature: Set Limit transfer only to "OK" if the queue does not contain any more commands 2024-12-15 20:45:32 +01:00
Thomas Basler
8acae28c59 Feature: New handling of command queue
Goal of this change is to  prevent a overflow in the command queue by flooding it with MQTT commands and therefor also prevent  the reading of the inverter data.

To achieve this it is now possible to specify a insert type for each  queue element.
2024-12-15 20:45:32 +01:00
Thomas Basler
0061d5e159 Upgrade ESPAsyncWebServer from 3.4.0 to 3.4.1 2024-12-15 20:45:15 +01:00
Thomas Basler
5d14454185 Fix: Auto reboot was not triggered on pin mapping change 2024-12-14 13:33:44 +01:00
Thomas Basler
58382be16c Feature: show hint if device profile missing or not selected 2024-12-14 13:07:31 +01:00
Thomas Basler
2edec642fb Fix: Remove temperature readings for ESP32-S2 modules
For some reasons this leads to WDT resets on this kind of module.
This is just a workaround until another solution is found.
2024-12-14 12:36:50 +01:00
Thomas Basler
726a08ec2c Upgrade ESPAsyncWebServer from 3.3.23 to 3.4.0 2024-12-13 21:07:50 +01:00
Thomas Basler
d775ee9e89 Update bblanchon/ArduinoJson from 7.2.0 to 7.2.1 2024-12-05 22:23:18 +01:00
Thomas Basler
1c1fcbea51 Upgrade ESPAsyncWebServer from 3.3.22 to 3.3.23 2024-12-05 22:22:45 +01:00
Thomas Basler
bf89fd7558 webapp: Update dependencies 2024-12-05 22:21:48 +01:00
Bernhard Kirchen
8247070aae webapp: improve styling of hints on home view
* increase the spacing between the icon and the text.
* put the text into its own box so it does not flow around the icon.
* vertically center the icon to account for multiple lines of text.
* vertically center the text to account for a single line of text.

closes #1441.
2024-12-03 22:59:25 +01:00
Thomas Basler
241ee1e99d webapp: Update dependencies 2024-12-02 23:07:55 +01:00
Thomas Basler
a75543c309 Feature:: Added support for HMS-450 inverters which begin with 1400 2024-12-02 22:45:50 +01:00
Bernhard Kirchen
1c5a3cf6fe webapp: avoid undefined serial for InputSerial
if variables are set with 'const foo = {} as Inverter', then
'foo.serial' will be undefined, causing warnings and errors
when using InputSerial.
2024-12-02 22:20:52 +01:00
Bernhard Kirchen
b2dcac549c Fix: need to skip BOM also when migrating config 2024-12-02 22:20:12 +01:00
Bernhard Kirchen
37b173071e webapp: fix line break for reload button
if a page uses the reload button, it had only 1 column of space, and
only if the viewport was at least "sm". this is not the case for typical
smartphones, in which case the reload button would appear on its own row
instead of to the right.

we now limit the heading to 10 columns if and only if the reload button
is to be used, otherwise the heading uses all 12 columns, regardless of
the viewport size. the reload buton uses two columns -- if it is
displayed at all.

the font size of the icon is increased slightly.

as the font size of h1 headings changes with the viewport size, we need
to center both the heading and the button vertically.
2024-12-02 22:19:03 +01:00
Florian
041ae7bae7 add Sum of DC Powrr
add Sum of DC power of all enabled inverters to Homeassistant MQTT autodiscovery
2024-11-22 08:21:13 +01:00
stefan123t
680863fb00
Update README.md 2024-11-20 08:02:07 +01:00
stefan123t
8297591853
change markdown table to github style 2024-11-20 08:00:26 +01:00
Thomas Basler
cc3290be8e Use correct variable 2024-11-13 19:21:41 +01:00
Thomas Basler
9bfded055a Remove not required string generation 2024-11-13 19:20:56 +01:00
Thomas Basler
2b07e3c2c8 Organize includes 2024-11-12 20:23:28 +01:00
Thomas Basler
eac2e2fb39 Fix: Really always execute the generate of the factory.bin file 2024-11-11 23:52:59 +01:00
Thomas Basler
d843ac6422 Fix comment 2024-11-11 22:11:57 +01:00
Thomas Basler
33a9b7454c Make function getClientId const 2024-11-10 02:45:42 +01:00
Thomas Basler
3dc70ab40a webapp: add app.js.gz 2024-11-07 19:13:45 +01:00
Thomas Basler
ecb5e9cc32 Build factory.bin in every compile attempt
This is required to apply changes  which are maybe only related to  the data directory.
2024-11-07 19:10:41 +01:00
Thomas Basler
9a53d6e209 Fix lint errors 2024-11-07 18:30:29 +01:00
Thomas Basler
63405a712c Upgrade ESPAsyncWebServer from 3.3.21 to 3.3.22 2024-11-07 18:21:59 +01:00
Thomas Basler
69c67f96e7 webapp: Update dependencies 2024-11-07 18:16:18 +01:00
Bernhard Kirchen
d088021902 webapp: declare emitted event in FormFooter component
fixes an annoying warning (visible in the browser console):

[Vue warn]: Extraneous non-emits event listeners (reload) were passed to
component but could not be automatically inherited because component
renders fragment or text root nodes. If the listener is intended to be a
component custom event listener only, declare it using the "emits"
option.
2024-11-06 22:34:56 +01:00
Thomas Basler
74e3947cb2 Merge branch 'pr2360' into dev 2024-11-06 19:55:03 +01:00
Thomas Basler
ca060e406e Remove not required include 2024-11-06 19:39:39 +01:00
Thomas Basler
53b496fd00 Replace multiline print by printf 2024-11-06 19:39:24 +01:00
Thomas Basler
ab60875142 Remove not required include 2024-11-06 19:24:37 +01:00
Bernhard Kirchen
3948adf460 keep console.log() when serving webapp
the removal of console and debugger statements by esbuild even when not
building for production seems to be a regression, as these were
definitely working in the past.

this change uses the command parameter to configure esbuild to either
keep or indeed remove the respective statements. they are only kept if
command is not "serve".

to avoid having to indent everything in defineConfig() by one block, the
return statement and closing curly brace were added "inline".
2024-11-05 19:21:45 +01:00
Bernhard Kirchen
3c56ec3738 webapp: fix inverter selection button breaking
on small viewports, the icon and the inverter label would be displayed
in two lines. this change keeps the icon and the label tied together in
any case, and the icon is centered vertically around the label.
2024-11-03 20:35:14 +01:00
Bernhard Kirchen
661ea6c022 webapp: always scroll up when navigating to another view 2024-11-02 00:32:49 +01:00
Bernhard Kirchen
3fa864ce52 webapp: device manager: optimize cards for tab nav
the top border of the card was breaking the design of the tabs, where
the active tab would be "visually connected" to the content. also, the
rounded border at the top did not blend in with the navbar's bottom
border.
2024-11-02 00:30:03 +01:00
Bernhard Kirchen
71f312d830 webapp: show pin mapping categories as cards
on a desktop browser, this approach allows to display all categories at
once. we also increase readability as the values are much closer to
their label. previously, the values were far to the right of the screen
and it was unpleasent to read which value belonged to which setting. the
grouping of values per category was also not very well conceived.

by using cards, we also avoid some styling issues, namely the use of
rowspan, which caused a spurious table cell border at the end of the old
table layout.
2024-11-02 00:28:18 +01:00
Bernhard Kirchen
f1c095e41d webapp: optimize placement of device profile doc buttons
* remove empty container for device profile links. if a device profile
  has no links, no buttons are generated, but a row was still part of
  the DOM, adding spurious space between the select and the alert with
  the hint.
2024-11-02 00:25:36 +01:00
Bernhard Kirchen
bac7179f73 webapp: consistently use no colon in form labels
there are no colons for table headers as well. some form labels had no
colon already, so this change uses a unified look among form labels.
2024-11-02 00:10:05 +01:00
Bernhard Kirchen
9f315207d4 webapp: optimize body bottom padding and length
long forms, when scrolled to the bottom, would leave no space between
the bottom of the viewport and the buttons, which is unpleasent.

short views would still createa large (high) body, for apparently no
reason.
2024-11-01 23:56:48 +01:00
Bernhard Kirchen
08f4d623c7 webapp: optimize look of login page
improve spacing and align login buton to the right, where all our
buttons are.
2024-11-01 23:55:25 +01:00
Bernhard Kirchen
f85297d52f webapp: inverter advanced tab needs space at the top
this avoids the input text box from colliding with the tab navigation
bottom border.
2024-11-01 23:54:39 +01:00
Bernhard Kirchen
e724fb8375 webapp: optimize look of firmware update cards 2024-11-01 23:53:24 +01:00
Bernhard Kirchen
54b4a2e9e8 webapp: properly space alert with hint for hostname 2024-11-01 23:50:28 +01:00
Bernhard Kirchen
94cecc23f5 webapp: avoid inline style for inverter channel info value 2024-11-01 23:46:35 +01:00
Bernhard Kirchen
866b539757 webapp: MQTT: no login with cert if TLS disabled
in the settings view we hide the "login with cert" setting while TLS is
disabled, so we should also hide that info in the info view when TLS is
disabled.
2024-11-01 23:46:01 +01:00
Bernhard Kirchen
9132a88963 webapp: MQTT: use v-if in favor of v-show
if we hide elements (which is done using style="display:none;"), they
are still part of the DOM and mess with CSS rules that shall apply to
the last element of a card or the last row of a table.
2024-11-01 23:44:35 +01:00
Bernhard Kirchen
eecd7f7c28 webapp: optimize spacing on bottom of cards
if the last child in a card (div.card > div.card-body) adds bottom
marging, we don't want the card to add more space through its
padding-bottom. most cards have children that add sufficient space
at the bottom anyways.
2024-11-01 23:43:51 +01:00
Bernhard Kirchen
ba304b2871 webapp: fix inverter "add" and "save order" button positions
the source tells us that the buttons are supposed to be on the right of
tha card, but the CSS broke at some point.
2024-11-01 23:42:56 +01:00
Bernhard Kirchen
0832d3e18c webapp: beautify radio statistics reset button
it would be nice to have this in the header of the accordion, which is
hard, but doable. however, clicking the button then also toggles the
accordion, which is unacceptable. preventing that seems non-trivial, as
the @click.stop() is not enough. also, nesting interactive elements is
simply bad practice. the button can also go to the right of header, with
reasonable effort, but the corner radii are then messed up and would
need to react interactively (accordion collapsed or not), which is also
a pain.

we now "float" the reset button to the right, add a nice icon, and give
the button some space so it at least looks like it belongs there.
2024-11-01 23:42:04 +01:00
Bernhard Kirchen
3c188f2f9f webapp: adjust look of tables in accordions to live view cards
this is relevant for the radio statistics table, as well as the tables
in the grid profile modal.
2024-11-01 23:40:52 +01:00
Bernhard Kirchen
68d2f7bf29 webapp: apply card-table class to info view cards
the cards in all information views still used a div.card-body around the
table, which added a margin on all sides of the table. to achieve a
unified look, these cards and tables now look the same as the inverter
channel cards.
2024-11-01 23:39:54 +01:00
Bernhard Kirchen
ad73fd8abd webapp: align table headers with card headers
set the left margin of table header cells to the same marging the card
header use, such that the text align on the same axis.
2024-11-01 23:39:00 +01:00
Bernhard Kirchen
e6a994fd7a webapp: use reasonable name for radio stats accordion 2024-11-01 23:38:29 +01:00
Bernhard Kirchen
d324a5c83f webapp: equalize style of cards with tables in live view
this change adjusts the style of cards showing tables such that they
look the same as inverter channel info tables.
2024-11-01 23:37:49 +01:00
Bernhard Kirchen
0aba1595df webapp: avoid inline style in inverter channel info card 2024-11-01 23:37:03 +01:00
Bernhard Kirchen
376912d821 webapp: add gap between inverter selectors 2024-11-01 23:36:17 +01:00
Bernhard Kirchen
d3eabc3311 webapp: remove table's bottom margin
we don't need a margin at the bottom of tables in general. not sure why
this is even a thing in bootstrap. this change, in particular, makes the
space between a table and a parent card symmetric on all sides.
2024-11-01 23:35:34 +01:00
Bernhard Kirchen
d06ea51c7a webapp: last table row shall have no bottom border
similar to the first row which has no border at the top.
2024-11-01 23:34:41 +01:00
Bernhard Kirchen
c750defc5f webapp: right-align labels for inputs on non-sm viewports
this change tries to achieve a pleasing look of input forms by
right-aligning the texts of labels. the input form now looks similar
to a table, achieving a cleaner look, especially for forms where the
labels have varying text lenghts.
2024-11-01 23:33:48 +01:00
Thomas Basler
130d90ce04 Merge branch 'pr2328' into dev 2024-11-01 22:12:41 +01:00
Thomas Basler
55c98ef880 Fix: skip BOM in JSON files (pin_mapping and config)
based on #2387
2024-11-01 22:07:48 +01:00
Thomas Basler
6c903abda1 Fix: Lint Error 2024-11-01 22:01:30 +01:00
Thomas Basler
28788070b2 Upgrade ESPAsyncWebServer from 3.3.17 to 3.3.21 2024-11-01 21:56:41 +01:00
Thomas Basler
0fc1ffc4d3 webapp: Update dependencies 2024-11-01 21:52:55 +01:00
Thomas Basler
8019eaf182 Feature: Validate JSON before uploading 2024-11-01 21:52:13 +01:00
Thomas Basler
225cab676a Fix: Take DST into account when recalculating the sunrise sunset time
If it is not considered the correct sunset / sunrise time is only calculated at the next day

Fixes: #2377
2024-10-27 14:03:44 +01:00
Thomas Basler
4594bcb23e Feature: Added device info for HMS-700 2024-10-26 14:51:03 +02:00
Thomas Basler
b21e8f8c80 Added README.md to lang folder 2024-10-25 23:04:14 +02:00
Thomas Basler
8566b08723 Feature: Added italian display translation 2024-10-25 22:42:47 +02:00
Thomas Basler
8452a8d110 Feature: Added spanish display translation 2024-10-25 22:39:25 +02:00
Thomas Basler
70f301941b Feature: Implement language pack support for display texts 2024-10-25 22:38:55 +02:00
Thomas Basler
d259042542 Rewrite display language handling to work with locale strings instead of magic numbers.
This is required to implement further i18n functions using the language packs
2024-10-25 21:43:29 +02:00
Thomas Basler
6113e0737b webapp: Fix: WaitRetstartView showed basic auth dialog 2024-10-25 21:42:52 +02:00
Bernhard Kirchen
b1edb13b3c add and use configuration write guard
the configuration write guard is now required when the configuration
struct shall be mutated. the write guards locks multiple writers against
each other and also, more importantly, makes the writes synchronous to
the main loop. all code running in the main loop can now be sure that
(1) reads from the configuration struct are non-preemtive and (2) the
configuration struct as a whole is in a consistent state when reading
from it.

NOTE that acquiring a write guard from within the main loop's task will
immediately cause a deadlock and the watchdog will trigger a reset. if
writing from inside the main loop should ever become necessary, the
write guard must be updated to only lock the mutex but not wait for a
signal.
2024-10-22 20:39:23 +02:00
Thomas Basler
2a21e53422 webapp: Rename interface to prevent lint errors 2024-10-21 22:41:52 +02:00
Thomas Basler
521fce35e4 webapp: Added global reboot wait screen 2024-10-21 21:59:38 +02:00
Thomas Basler
2e23c7e0ae Check if language pack metadata are valid 2024-10-21 20:15:56 +02:00
Thomas Basler
68c87c9217 Move lookup for translation path to separate method 2024-10-21 20:15:56 +02:00
Thomas Basler
4a247f5e94 Feature: Added italian language pack 2024-10-21 20:15:56 +02:00
Thomas Basler
05006e0642 Feature: Added spanish language pack 2024-10-21 20:15:56 +02:00
Thomas Basler
d9a8461a2e Feature: Allow custom language pack for webapp 2024-10-21 20:15:56 +02:00
Thomas Basler
c3d3d947d7 webapp: Allow upload of language packs 2024-10-21 19:02:50 +02:00
Thomas Basler
e29b86e4dc Add API endpoint to retrieve custom languages and complete language pack 2024-10-21 19:02:50 +02:00
Thomas Basler
8257eb7aa2 webapp: Use global AlertResponse interface 2024-10-19 17:39:12 +02:00
Thomas Basler
1e857b79c1 Feature: Refactor config management interface 2024-10-19 17:35:19 +02:00
Thomas Basler
16901482d9 Refactor file handling API and add endpoint to delete files 2024-10-19 12:40:43 +02:00
Thomas Basler
aa9f36ee8f Rename config API to file API 2024-10-19 11:07:15 +02:00
Thomas Basler
cf1693e1a0 Upgrade ESPAsyncWebServer from 3.3.16 to 3.3.17 2024-10-16 21:25:29 +02:00
Thomas Basler
e5cf12cebd Update nrf24/RF24 from 1.4.9 to 1.4.10 2024-10-16 21:24:01 +02:00
Thomas Basler
bcf4b70dc9 Fix: cpplint errors 2024-10-15 19:11:17 +02:00
janrombold
d0b2b972e2
Update UpgradePartition.md
Fixed typo
2024-10-04 00:19:26 +02:00
339 changed files with 4751 additions and 22683 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -4,19 +4,19 @@ labels: ["bug"]
body: body:
- type: markdown - type: markdown
attributes: attributes:
value: | value: >
### ⚠️ Please remember: issues are for *bugs*⚠️ ### ⚠️ Please remember: issues are for *bugs*
That is, something you believe affects every single user of OpenDTU-OnBattery, not just you. If you're not sure, start with one of the other options below. That is, something you believe affects every single user of OpenDTU, not just you. If you're not sure, start with one of the other options below.
- type: markdown - type: markdown
attributes: attributes:
value: | value: |
#### Have a question? 👉 [Start a new discussion](https://github.com/helgeerbe/OpenDTU-OnBattery/discussions/new/choose) or [ask in chat](https://discord.gg/WzhxEY62mB). #### Have a question? 👉 [Start a new discussion](https://github.com/tbnobody/OpenDTU/discussions/new) or [ask in chat](https://discord.gg/WzhxEY62mB).
#### Before opening an issue, please double check: #### Before opening an issue, please double check:
- [Documentation](https://opendtu-onbattery.net) - [Documentation](https://www.opendtu.solar).
- [The FAQs](https://opendtu-onbattery.net/firmware/faq/) - [The FAQs](https://www.opendtu.solar/firmware/faq/).
- [Existing issues and discussions](https://github.com/helgeerbe/OpenDTU-OnBattery/search?q=&type=issues) - [Existing issues and discussions](https://github.com/tbnobody/OpenDTU/search?q=&type=issues).
- type: textarea - type: textarea
id: what-happened id: what-happened
attributes: attributes:
@ -45,7 +45,7 @@ body:
id: install_format id: install_format
attributes: attributes:
label: Install Method label: Install Method
description: How did you install OpenDTU-OnBattery? description: How did you install OpenDTU?
options: options:
- Pre-Compiled binary from GitHub releases - Pre-Compiled binary from GitHub releases
- Pre-Compiled binary from GitHub actions/pull-request - Pre-Compiled binary from GitHub actions/pull-request
@ -55,25 +55,17 @@ body:
- type: input - type: input
id: version id: version
attributes: attributes:
label: What git-hash/version of OpenDTU-OnBattery? label: What git-hash/version of OpenDTU?
description: You can find this in the Web UI at Info -> System. description: You can find this in by going to Info -> System
placeholder: "e.g. 359d513" placeholder: "e.g. 359d513"
validations: validations:
required: true required: true
- type: dropdown - type: input
id: environment id: environment
attributes: attributes:
label: What firmware variant (PIO Environment)? label: What firmware variant (PIO Environment) are you using?
description: You can find this in the Web UI at Info -> System. description: You can find this in by going to Info -> System
options: placeholder: "generic_esp32s3_usb"
- "generic_esp32s3_usb"
- "generic_esp32s3"
- "generic_esp32_8mb"
- "generic_esp32_4mb_no_ota"
- "generic_esp32"
- "generic"
- "opendtufusionv2"
- "other (tell us in 'Anything else?')"
validations: validations:
required: true required: true
- type: textarea - type: textarea
@ -95,7 +87,7 @@ body:
attributes: attributes:
label: Please confirm the following label: Please confirm the following
options: options:
- label: I believe this issue is a bug that affects all users of OpenDTU-OnBattery, not something specific to my installation. - label: I believe this issue is a bug that affects all users of OpenDTU, not something specific to my installation.
required: true required: true
- label: I have already searched for relevant existing issues and discussions before opening this report. - label: I have already searched for relevant existing issues and discussions before opening this report.
required: true required: true

View File

@ -4,5 +4,5 @@ contact_links:
url: https://discord.gg/WzhxEY62mB url: https://discord.gg/WzhxEY62mB
about: Discuss with us on Discord about: Discuss with us on Discord
- name: 🤔 Have questions or need support? - name: 🤔 Have questions or need support?
url: https://github.com/helgeerbe/OpenDTU-OnBattery/discussions url: https://github.com/tbnobody/OpenDTU/discussions
about: Use the GitHub Discussions feature about: Use the GitHub Discussions feature

View File

@ -1,12 +1,12 @@
name: ✨ Request a feature name: ✨ Request a feature
description: Suggest an improvement idea for OpenDTU-OnBattery! description: Suggest an improvement idea for OpenDTU!
title: "[Request]" title: "[Request]"
labels: ["enhancement"] labels: ["enhancement"]
body: body:
- type: markdown - type: markdown
attributes: attributes:
value: > value: >
**Thank you for wanting to request a feature in OpenDTU-OnBattery!** **Thank you for wanting to request a feature in OpenDTU!**
Before you go ahead with your request, please first consider if it wouldn't be Before you go ahead with your request, please first consider if it wouldn't be
better suited in a external home automation software like OpenHAB, ioBroker, Home Assistant etc. better suited in a external home automation software like OpenHAB, ioBroker, Home Assistant etc.

View File

@ -1,15 +1,10 @@
name: OpenDTU-OnBattery Build name: OpenDTU Build
on: on:
push: push:
paths-ignore: paths-ignore:
- docs/** - docs/**
- '**/*.md' - '**/*.md'
branches:
- master
- development
tags-ignore:
- 'v**'
pull_request: pull_request:
paths-ignore: paths-ignore:
- docs/** - docs/**
@ -60,15 +55,6 @@ jobs:
- name: Get tags - name: Get tags
run: git fetch --force --tags origin run: git fetch --force --tags origin
- name: Create and switch to a meaningful branch for pull-requests
if: github.event_name == 'pull_request'
run: |
OWNER=${{ github.repository_owner }}
NAME=${{ github.event.repository.name }}
ID=${{ github.event.pull_request.number }}
DATE=$(date +'%Y%m%d%H%M')
git switch -c ${OWNER}/${NAME}/pr${ID}-${DATE}
- name: Cache pip - name: Cache pip
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
@ -119,48 +105,27 @@ jobs:
run: pio run -e ${{ matrix.environment }} run: pio run -e ${{ matrix.environment }}
- name: Rename Firmware - name: Rename Firmware
run: mv .pio/build/${{ matrix.environment }}/firmware.bin .pio/build/${{ matrix.environment }}/opendtu-onbattery-${{ matrix.environment }}.bin run: mv .pio/build/${{ matrix.environment }}/firmware.bin .pio/build/${{ matrix.environment }}/opendtu-${{ matrix.environment }}.bin
- name: Rename Factory Firmware - name: Rename Factory Firmware
run: mv .pio/build/${{ matrix.environment }}/firmware.factory.bin .pio/build/${{ matrix.environment }}/opendtu-onbattery-${{ matrix.environment }}.factory.bin run: mv .pio/build/${{ matrix.environment }}/firmware.factory.bin .pio/build/${{ matrix.environment }}/opendtu-${{ matrix.environment }}.factory.bin
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
with: with:
name: opendtu-onbattery-${{ matrix.environment }} name: opendtu-${{ matrix.environment }}
path: | path: |
.pio/build/${{ matrix.environment }}/opendtu-onbattery-${{ matrix.environment }}.bin .pio/build/${{ matrix.environment }}/opendtu-${{ matrix.environment }}.bin
!.pio/build/generic_esp32_4mb_no_ota/opendtu-onbattery-generic_esp32_4mb_no_ota.bin .pio/build/${{ matrix.environment }}/opendtu-${{ matrix.environment }}.factory.bin
.pio/build/${{ matrix.environment }}/opendtu-onbattery-${{ matrix.environment }}.factory.bin
release: release:
name: Create Release name: Create Release
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [get_default_envs, build] needs: [get_default_envs, build]
if: startsWith(github.ref, 'refs/tags/2') if: startsWith(github.ref, 'refs/tags/')
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Get tags
run: git fetch --force --tags origin
- name: Get openDTU core release
run: |
echo "OPEN_DTU_CORE_RELEASE=$(git for-each-ref --sort=creatordate --format '%(refname) %(creatordate)' refs/tags | grep 'refs/tags/v' | tail -1 | sed 's#.*/##' | sed 's/ .*//')" >> $GITHUB_ENV
# disabled as uploading the changed gist failed repeatedly.
# maybe the token in secrets.GIST_SECRET has expired?
# need help from repo owner @helgeerbe to fix this.
# - name: Create openDTU-core-release-Badge
# uses: schneegans/dynamic-badges-action@v1.6.0
# with:
# auth: ${{ secrets.GIST_SECRET }}
# gistID: 68b47cc8c8994d04ab3a4fa9d8aee5e6
# filename: openDTUcoreRelease.json
# label: based on original OpenDTU
# message: ${{ env.OPEN_DTU_CORE_RELEASE }}
# color: lightblue
- name: Build Changelog - name: Build Changelog
id: github_release id: github_release
uses: mikepenz/release-changelog-builder-action@v4 uses: mikepenz/release-changelog-builder-action@v4
@ -179,7 +144,7 @@ jobs:
run: | run: |
ls -R ls -R
cd artifacts cd artifacts
for i in */; do cp ${i}opendtu-onbattery-*.bin ./; done for i in */; do cp ${i}opendtu-*.bin ./; done
- name: Create release - name: Create release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
@ -189,4 +154,4 @@ jobs:
files: | files: |
artifacts/*.zip, artifacts/*.bin artifacts/*.zip, artifacts/*.bin
env: env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -36,7 +36,7 @@
} }
], ],
"template": "${{CHANGELOG}}", "template": "${{CHANGELOG}}",
"pr_template": "- [${{TITLE}}](https://github.com/helgeerbe/OpenDTU-OnBattery/commit/${{MERGE_SHA}})", "pr_template": "- [${{TITLE}}](https://github.com/tbnobody/OpenDTU/commit/${{MERGE_SHA}})",
"empty_template": "- no changes", "empty_template": "- no changes",
"label_extractor": [ "label_extractor": [
{ {

View File

@ -6,10 +6,6 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
# prevent push event from triggering if it's part of a local PR, see
# https://github.com/orgs/community/discussions/57827#discussioncomment-6579237
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Set up Python - name: Set up Python
@ -19,9 +15,7 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install cpplint==1.6.1 pip install cpplint
- name: Linting - name: Linting
run: | run: |
cpplint --repository=. --recursive \ cpplint --repository=. --recursive --filter=-build/c++11,-runtime/references,-readability/braces,-whitespace,-legal,-build/include ./src ./include ./lib/Hoymiles ./lib/MqttSubscribeParser ./lib/TimeoutHelper ./lib/ResetReason
--filter=-build/c++11,-runtime/references,-readability/braces,-whitespace,-legal,-build/include \
./src ./include ./lib/Hoymiles ./lib/MqttSubscribeParser ./lib/TimeoutHelper ./lib/ResetReason

View File

@ -6,10 +6,6 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
# prevent push event from triggering if it's part of a local PR, see
# https://github.com/orgs/community/discussions/57827#discussioncomment-6579237
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
defaults: defaults:
run: run:
working-directory: webapp working-directory: webapp

View File

@ -6,10 +6,6 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
# prevent push event from triggering if it's part of a local PR, see
# https://github.com/orgs/community/discussions/57827#discussioncomment-6579237
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
defaults: defaults:
run: run:
working-directory: webapp working-directory: webapp

1
.gitignore vendored
View File

@ -7,5 +7,4 @@
platformio-device-monitor*.log platformio-device-monitor*.log
logs/device-monitor*.log logs/device-monitor*.log
platformio_override.ini platformio_override.ini
webapp_dist/
.DS_Store .DS_Store

62
.vscode/settings.json vendored
View File

@ -1,63 +1,3 @@
{ {
"C_Cpp.clang_format_style": "WebKit", "C_Cpp.clang_format_style": "WebKit"
"files.associations": {
"*.tcc": "cpp",
"algorithm": "cpp",
"array": "cpp",
"atomic": "cpp",
"bitset": "cpp",
"cctype": "cpp",
"chrono": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"condition_variable": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdint": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"deque": "cpp",
"list": "cpp",
"unordered_map": "cpp",
"unordered_set": "cpp",
"vector": "cpp",
"exception": "cpp",
"functional": "cpp",
"iterator": "cpp",
"map": "cpp",
"memory": "cpp",
"memory_resource": "cpp",
"numeric": "cpp",
"optional": "cpp",
"random": "cpp",
"ratio": "cpp",
"regex": "cpp",
"string": "cpp",
"string_view": "cpp",
"system_error": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"fstream": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"limits": "cpp",
"mutex": "cpp",
"new": "cpp",
"ostream": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"streambuf": "cpp",
"thread": "cpp",
"cinttypes": "cpp",
"typeinfo": "cpp",
"variant": "cpp"
}
} }

View File

@ -1,71 +1,43 @@
- [OpenDTU-OnBattery](#opendtu-onbattery) # OpenDTU
- [What is OpenDTU-OnBattery](#what-is-opendtu-onbattery)
- [Documentation](#documentation)
- [State of the project](#state-of-the-project)
- [History of the project](#history-of-the-project)
- [Acknowledgments](#acknowledgments)
# OpenDTU-OnBattery [![OpenDTU Build](https://github.com/tbnobody/OpenDTU/actions/workflows/build.yml/badge.svg)](https://github.com/tbnobody/OpenDTU/actions/workflows/build.yml)
[![cpplint](https://github.com/tbnobody/OpenDTU/actions/workflows/cpplint.yml/badge.svg)](https://github.com/tbnobody/OpenDTU/actions/workflows/cpplint.yml)
[![Yarn Linting](https://github.com/tbnobody/OpenDTU/actions/workflows/yarnlint.yml/badge.svg)](https://github.com/tbnobody/OpenDTU/actions/workflows/yarnlint.yml)
This is a fork of [OpenDTU](https://github.com/tbnobody/OpenDTU). ## !! IMPORTANT UPGRADE NOTES !!
<!--- If you are upgrading from a version before 15.03.2023 you have to upgrade the partition table of the ESP32. Please follow the [this](docs/UpgradePartition.md) documentation!
disabled while "create release badge" action is broken, see .github/build.yml
![GitHub tag (latest SemVer)](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/helgeerbe/68b47cc8c8994d04ab3a4fa9d8aee5e6/raw/openDTUcoreRelease.json)
--->
[![OpenDTU-OnBattery Build](https://github.com/hoylabs/OpenDTU-OnBattery/actions/workflows/build.yml/badge.svg)](https://github.com/hoylabs/OpenDTU-OnBattery/actions/workflows/build.yml) ## Background
[![cpplint](https://github.com/hoylabs/OpenDTU-OnBattery/actions/workflows/cpplint.yml/badge.svg)](https://github.com/hoylabs/OpenDTU-OnBattery/actions/workflows/cpplint.yml)
[![Yarn Linting](https://github.com/hoylabs/OpenDTU-OnBattery/actions/workflows/yarnlint.yml/badge.svg)](https://github.com/hoylabs/OpenDTU-OnBattery/actions/workflows/yarnlint.yml)
## What is OpenDTU-OnBattery This project was started from [this](https://www.mikrocontroller.net/topic/525778) discussion (Mikrocontroller.net).
It was the goal to replace the original Hoymiles DTU (Telemetry Gateway) with their cloud access. With a lot of reverse engineering the Hoymiles protocol was decrypted and analyzed.
OpenDTU-OnBattery is an extension of the original OpenDTU to support battery
chargers, battery management systems (BMS) and power meters on a single ESP32.
With the help of a Dynamic Power Limiter, the power production can be adjusted
to the actual consumption. In this way, it is possible to implement a zero
export policy.
## Documentation ## Documentation
The canonical documentation of OpenDTU-OnBattery is hosted at The documentation can be found [here](https://tbnobody.github.io/OpenDTU-docs/).
[https://opendtu-onbattery.net](https://opendtu-onbattery.net). Please feel free to support and create a PR in [this](https://github.com/tbnobody/OpenDTU-docs) repository to make the documentation even better.
You may find additional helpful information in the project's ## Breaking changes
community-maintained [Github
Wiki](https://github.com/hoylabs/OpenDTU-OnBattery/wiki).
To find out what's new or improved have a look at the changelog of the Generated using: `git log --date=short --pretty=format:"* %h%x09%ad%x09%s" | grep BREAKING`
[releases](https://github.com/hoylabs/OpenDTU-OnBattery/releases).
## State of the project ```code
* 1b637f08 2024-01-30 BREAKING CHANGE: Web API Endpoint /api/livedata/status and /api/prometheus/metrics
* e1564780 2024-01-30 BREAKING CHANGE: Web API Endpoint /api/livedata/status and /api/prometheus/metrics
* f0b5542c 2024-01-30 BREAKING CHANGE: Web API Endpoint /api/livedata/status and /api/prometheus/metrics
* c27ecc36 2024-01-29 BREAKING CHANGE: Web API Endpoint /api/livedata/status
* 71d1b3b 2023-11-07 BREAKING CHANGE: Home Assistant Auto Discovery to new naming scheme
* 04f62e0 2023-04-20 BREAKING CHANGE: Web API Endpoint /api/eventlog/status no nested serial object
* 59f43a8 2023-04-17 BREAKING CHANGE: Web API Endpoint /api/devinfo/status requires GET parameter inv=
* 318136d 2023-03-15 BREAKING CHANGE: Updated partition table: Make sure you have a configuration backup and completly reflash the device!
* 3b7aef6 2023-02-13 BREAKING CHANGE: Web API!
* d4c838a 2023-02-06 BREAKING CHANGE: Prometheus API!
* daf847e 2022-11-14 BREAKING CHANGE: Removed deprecated config parsing method
* 69b675b 2022-11-01 BREAKING CHANGE: Structure WebAPI /api/livedata/status changed
* 27ed4e3 2022-10-31 BREAKING: Change power factor from percent value to value between 0 and 1
```
OpenDTU-OnBattery is actively maintained. Please note that OpenDTU-OnBattery ## Currently supported Inverters
may change significantly during its development. Bug reports, comments, feature
requests and pull requests are welcome!
## History of the project A list of all currently supported inverters can be found [here](https://www.opendtu.solar/hardware/inverter_overview/)
The original OpenDTU project was started from [a discussion on
Mikrocontroller.net](https://www.mikrocontroller.net/topic/525778). It was the
goal to replace the original Hoymiles DTU (Telemetry Gateway) to avoid using
Hoymile's cloud. With a lot of reverse engineering the Hoymiles protocol was
decrypted and analyzed.
In the summer of 2022 @helgeerbe bought a Victron MPPT charge cntroller, and
didn't like the idea to set up a separate ESP32 to receive the charger's data.
He decided to fork OpenDTU and extend it with battery charger support and a
Dynamic Power Limiter.
In early October 2024, the project moved to the newly founded GitHub
organisation `hoylabs` and is since maintained by multiple community members.
## Acknowledgments
* Special thanks to Thomas Basler (@tbnobody), the author of the [upstream
project](https://github.com/tbnobody/OpenDTU), for hist continued effort!
* Thanks to @helgeerbe for starting OpenDTU-OnBattery and his dedication to the
project, as well as his trust in the current maintainers of the project,
which act as part of the `hoylabs` GitHub organisation.
* We like to thank all contributors. With your ideas and enhancements, you have
made OpenDTU-OnBattery much more than @helgeerbe originally had in mind.

View File

@ -1,3 +0,0 @@
# Moved
Have a look at the [OpenDTU-OnBattery documentation](https://opendtu-onbattery.net).

View File

@ -1,3 +1,3 @@
# Moved # Device Profiles
Have a look at the [OpenDTU-OnBattery documentation](https://opendtu-onbattery.net/firmware/device_profiles/). This documentation will has been moved and can be found here: <https://tbnobody.github.io/OpenDTU-docs/firmware/device_profiles/>

View File

@ -1,3 +1,3 @@
# Moved # Display integration
Have a look at the [OpenDTU-OnBattery documentation](https://opendtu-onbattery.net/hardware/display/). This documentation will has been moved and can be found here: <https://tbnobody.github.io/OpenDTU-docs/hardware/display/>

View File

@ -1,3 +1,3 @@
# Moved # MQTT Topics
Have a look at the [OpenDTU-OnBattery documentation](https://opendtu-onbattery.net/firmware/mqtt_topics/). This documentation will has been moved and can be found here: <https://tbnobody.github.io/OpenDTU-docs/firmware/mqtt_topics/>

View File

@ -1,3 +1,13 @@
# Moved # Documents - Table of content
Have a look at the [OpenDTU-OnBattery documentation](https://opendtu-onbattery.net). More detailed descriptions for some topics can be found here.
## [Display Documentation](Display.md)
## [MQTT Topic Documentation](MQTT_Topics.md)
## [Web API Documentation](Web-API.md)
## [Device Profile Documentation](DeviceProfiles.md)
## [Builds](builds/README.md)

View File

@ -1,3 +1,3 @@
# Moved # Upgrade Partition
Have a look at the [OpenDTU-OnBattery documentation](https://opendtu-onbattery.net/firmware/howto/upgrade_8mb/). This documentation has been moved and can be found here: <https://tbnobody.github.io/OpenDTU-docs/firmware/howto/upgrade_partition/>

View File

@ -1,3 +1,3 @@
# Moved # Web API
Have a look at the [OpenDTU-OnBattery documentation](https://opendtu-onbattery.net/firmware/web_api/). This documentation will has been moved and can be found here: <https://tbnobody.github.io/OpenDTU-docs/firmware/web_api/>

BIN
docs/Wiring_ESP32.fzz Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

28
docs/builds/README.md Normal file
View File

@ -0,0 +1,28 @@
# Builds using different boards
## ESP32 Dev Board
### Build by @tbnobody, jan and @marove2000
* Used build environment: generic
* Case: https://www.printables.com/de/model/441037-opendtu-breakoutboard-case
* Soldering Kit: https://shop.blinkyparts.com/en/OpenDTU-Breakoutboard-Your-evaluation-for-your-balcony-solar-system/blink237542
* Breakout board: https://github.com/marove2000/openDTU_BreakoutBoard
![](opendtu_breakoutboard.jpg)
![](thumbnail.jpg)
### Build by @Marc--
* Used build environment: generic
* Case: https://www.thingiverse.com/thing:5435911
![](large_display_PXL_20220715_145622277.jpg)
### Build by @cepresso
* Used build environment: generic
* Case: https://www.printables.com/de/model/293003-sol-opendtu-esp32-nrf24l01-case
![](sol.webp)
## LILYGO® TTGO T-Internet-POE
### Build by @fromCologne
* Used build environment: LilyGO_T_ETH_POE
* Board info: http://www.lilygo.cn/claprod_view.aspx?TypeId=21&Id=1344&FId=t28:21:28
* Case: https://www.thingiverse.com/thing:5244895
![](202654506-8a4ac4ef-c883-490e-8ee1-1e1f7fa34972.jpg)

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
docs/builds/sol.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

BIN
docs/builds/thumbnail.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

View File

@ -1,3 +0,0 @@
# Moved
Have a look at the [OpenDTU-OnBattery documentation](https://opendtu-onbattery.net/firmware/).

BIN
docs/nodemcu-esp32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

BIN
docs/nrf24l01plus.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -0,0 +1,91 @@
# OpenDTU Screenshots
here are some screenshots of OpenDTU's web interface.
***
![Live View](01_LiveView.png)
***
![Limit Settings](15_LimitSettings.png)
***
![Power Settings](16_PowerSettings.png)
***
![Inverter Info](17_InverterInfo.png)
***
![Eventlog](12_Eventlog.png)
***
![Network Admin](02_NetworkAdmin.png)
***
![NTP Admin](03_NtpAdmin.png)
***
![MQTT Admin](04_MqttAdmin.png)
***
![Inverter Admin](05_InverterAdmin.png)
***
![Inverter Settings](13_InverterSettings.png)
***
![Security](22_Security.png)
***
![DTU Admin](06_DtuAdmin.png)
***
![Device Manager Pin](20_DeviceManager_Pin.png)
***
![Device Manager Display](21_DeviceManager_Display.png)
***
![Config Management](14_ConfigManagement.png)
***
![Firmware Upgrade](07_FirmwareUpgrade.png)
***
![Reboot](19_Reboot.png)
***
![System Info](11_SystemInfo.png)
***
![Network Info](08_NetworkInfo.png)
***
![NTP Info](09_NtpInfo.png)
***
![MQTT Info](10_MqttInfo.png)
***
![Console](18_Console.png)

View File

@ -1,36 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <mutex>
#include <TaskSchedulerDeclarations.h>
#include "BatteryStats.h"
class BatteryProvider {
public:
// returns true if the provider is ready for use, false otherwise
virtual bool init(bool verboseLogging) = 0;
virtual void deinit() = 0;
virtual void loop() = 0;
virtual std::shared_ptr<BatteryStats> getStats() const = 0;
};
class BatteryClass {
public:
void init(Scheduler&);
void updateSettings();
float getDischargeCurrentLimit();
std::shared_ptr<BatteryStats const> getStats() const;
private:
void loop();
Task _loopTask;
mutable std::mutex _mutex;
std::unique_ptr<BatteryProvider> _upProvider = nullptr;
};
extern BatteryClass Battery;

View File

@ -1,29 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "Battery.h"
#include <driver/twai.h>
#include <Arduino.h>
class BatteryCanReceiver : public BatteryProvider {
public:
bool init(bool verboseLogging, char const* providerName);
void deinit() final;
void loop() final;
virtual void onMessage(twai_message_t rx_message) = 0;
protected:
uint8_t readUnsignedInt8(uint8_t *data);
uint16_t readUnsignedInt16(uint8_t *data);
int16_t readSignedInt16(uint8_t *data);
uint32_t readUnsignedInt32(uint8_t *data);
int32_t readSignedInt24(uint8_t *data);
float scaleValue(int32_t value, float factor);
bool getBit(uint8_t value, uint8_t bit);
bool _verboseLogging = true;
private:
char const* _providerName = "Battery CAN";
};

View File

@ -1,325 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <stdint.h>
#include "AsyncJson.h"
#include "Arduino.h"
#include "JkBmsDataPoints.h"
#include "VeDirectShuntController.h"
#include <cfloat>
// mandatory interface for all kinds of batteries
class BatteryStats {
public:
String const& getManufacturer() const { return _manufacturer; }
// the last time *any* data was updated
uint32_t getAgeSeconds() const { return (millis() - _lastUpdate) / 1000; }
bool updateAvailable(uint32_t since) const;
float getSoC() const { return _soc; }
uint32_t getSoCAgeSeconds() const { return (millis() - _lastUpdateSoC) / 1000; }
uint8_t getSoCPrecision() const { return _socPrecision; }
float getVoltage() const { return _voltage; }
uint32_t getVoltageAgeSeconds() const { return (millis() - _lastUpdateVoltage) / 1000; }
float getChargeCurrent() const { return _current; };
uint8_t getChargeCurrentPrecision() const { return _currentPrecision; }
float getDischargeCurrentLimit() const { return _dischargeCurrentLimit; };
uint32_t getDischargeCurrentLimitAgeSeconds() const { return (millis() - _lastUpdateDischargeCurrentLimit) / 1000; }
// convert stats to JSON for web application live view
virtual void getLiveViewData(JsonVariant& root) const;
void mqttLoop();
// the interval at which all battery data will be re-published, even
// if they did not change. used to calculate Home Assistent expiration.
virtual uint32_t getMqttFullPublishIntervalMs() const;
bool isSoCValid() const { return _lastUpdateSoC > 0; }
bool isVoltageValid() const { return _lastUpdateVoltage > 0; }
bool isCurrentValid() const { return _lastUpdateCurrent > 0; }
bool isDischargeCurrentLimitValid() const { return _lastUpdateDischargeCurrentLimit > 0; }
// returns true if the battery reached a critically low voltage/SoC,
// such that it is in need of charging to prevent degredation.
virtual bool getImmediateChargingRequest() const { return false; };
virtual float getChargeCurrentLimitation() const { return FLT_MAX; };
virtual bool supportsAlarmsAndWarnings() const { return true; };
protected:
virtual void mqttPublish() const;
void setSoC(float soc, uint8_t precision, uint32_t timestamp) {
_soc = soc;
_socPrecision = precision;
_lastUpdateSoC = _lastUpdate = timestamp;
}
void setVoltage(float voltage, uint32_t timestamp) {
_voltage = voltage;
_lastUpdateVoltage = _lastUpdate = timestamp;
}
void setCurrent(float current, uint8_t precision, uint32_t timestamp) {
_current = current;
_currentPrecision = precision;
_lastUpdateCurrent = _lastUpdate = timestamp;
}
void setDischargeCurrentLimit(float dischargeCurrentLimit, uint32_t timestamp) {
_dischargeCurrentLimit = dischargeCurrentLimit;
_lastUpdateDischargeCurrentLimit = _lastUpdate = timestamp;
}
void setManufacturer(const String& m);
String _hwversion = "";
String _fwversion = "";
String _serial = "";
uint32_t _lastUpdate = 0;
private:
String _manufacturer = "unknown";
uint32_t _lastMqttPublish = 0;
float _soc = 0;
uint8_t _socPrecision = 0; // decimal places
uint32_t _lastUpdateSoC = 0;
float _voltage = 0; // total battery pack voltage
uint32_t _lastUpdateVoltage = 0;
// total current into (positive) or from (negative)
// the battery, i.e., the charging current
float _current = 0;
uint8_t _currentPrecision = 0; // decimal places
uint32_t _lastUpdateCurrent = 0;
float _dischargeCurrentLimit = 0;
uint32_t _lastUpdateDischargeCurrentLimit = 0;
};
class PylontechBatteryStats : public BatteryStats {
friend class PylontechCanReceiver;
public:
void getLiveViewData(JsonVariant& root) const final;
void mqttPublish() const final;
bool getImmediateChargingRequest() const { return _chargeImmediately; } ;
float getChargeCurrentLimitation() const { return _chargeCurrentLimitation; } ;
private:
void setLastUpdate(uint32_t ts) { _lastUpdate = ts; }
float _chargeVoltage;
float _chargeCurrentLimitation;
float _dischargeVoltageLimitation;
uint16_t _stateOfHealth;
float _temperature;
bool _alarmOverCurrentDischarge;
bool _alarmOverCurrentCharge;
bool _alarmUnderTemperature;
bool _alarmOverTemperature;
bool _alarmUnderVoltage;
bool _alarmOverVoltage;
bool _alarmBmsInternal;
bool _warningHighCurrentDischarge;
bool _warningHighCurrentCharge;
bool _warningLowTemperature;
bool _warningHighTemperature;
bool _warningLowVoltage;
bool _warningHighVoltage;
bool _warningBmsInternal;
bool _chargeEnabled;
bool _dischargeEnabled;
bool _chargeImmediately;
uint8_t _moduleCount;
};
class SBSBatteryStats : public BatteryStats {
friend class SBSCanReceiver;
public:
void getLiveViewData(JsonVariant& root) const final;
void mqttPublish() const final;
float getChargeCurrent() const { return _current; } ;
float getChargeCurrentLimitation() const { return _chargeCurrentLimitation; } ;
private:
void setLastUpdate(uint32_t ts) { _lastUpdate = ts; }
float _chargeVoltage;
float _chargeCurrentLimitation;
float _dischargeCurrentLimitation;
uint16_t _stateOfHealth;
float _current;
float _temperature;
bool _alarmUnderTemperature;
bool _alarmOverTemperature;
bool _alarmUnderVoltage;
bool _alarmOverVoltage;
bool _alarmBmsInternal;
bool _warningHighCurrentDischarge;
bool _warningHighCurrentCharge;
bool _chargeEnabled;
bool _dischargeEnabled;
};
class PytesBatteryStats : public BatteryStats {
friend class PytesCanReceiver;
public:
void getLiveViewData(JsonVariant& root) const final;
void mqttPublish() const final;
bool getImmediateChargingRequest() const { return _chargeImmediately; };
float getChargeCurrentLimitation() const { return _chargeCurrentLimit; };
private:
void setLastUpdate(uint32_t ts) { _lastUpdate = ts; }
void updateSerial() {
if (!_serialPart1.isEmpty() && !_serialPart2.isEmpty()) {
_serial = _serialPart1 + _serialPart2;
}
}
String _serialPart1 = "";
String _serialPart2 = "";
float _chargeVoltageLimit;
float _chargeCurrentLimit;
float _dischargeVoltageLimit;
uint16_t _stateOfHealth;
int _chargeCycles = -1;
int _balance = -1;
float _temperature;
uint16_t _cellMinMilliVolt;
uint16_t _cellMaxMilliVolt;
float _cellMinTemperature;
float _cellMaxTemperature;
String _cellMinVoltageName;
String _cellMaxVoltageName;
String _cellMinTemperatureName;
String _cellMaxTemperatureName;
uint8_t _moduleCountOnline;
uint8_t _moduleCountOffline;
uint8_t _moduleCountBlockingCharge;
uint8_t _moduleCountBlockingDischarge;
float _totalCapacity;
float _availableCapacity;
uint8_t _capacityPrecision = 0; // decimal places
float _chargedEnergy = -1;
float _dischargedEnergy = -1;
bool _alarmUnderVoltage;
bool _alarmOverVoltage;
bool _alarmOverCurrentCharge;
bool _alarmOverCurrentDischarge;
bool _alarmUnderTemperature;
bool _alarmOverTemperature;
bool _alarmUnderTemperatureCharge;
bool _alarmOverTemperatureCharge;
bool _alarmInternalFailure;
bool _alarmCellImbalance;
bool _warningLowVoltage;
bool _warningHighVoltage;
bool _warningHighChargeCurrent;
bool _warningHighDischargeCurrent;
bool _warningLowTemperature;
bool _warningHighTemperature;
bool _warningLowTemperatureCharge;
bool _warningHighTemperatureCharge;
bool _warningInternalFailure;
bool _warningCellImbalance;
bool _chargeImmediately;
};
class JkBmsBatteryStats : public BatteryStats {
public:
void getLiveViewData(JsonVariant& root) const final {
getJsonData(root, false);
}
void getInfoViewData(JsonVariant& root) const {
getJsonData(root, true);
}
void mqttPublish() const final;
uint32_t getMqttFullPublishIntervalMs() const final { return 60 * 1000; }
void updateFrom(JkBms::DataPointContainer const& dp);
private:
void getJsonData(JsonVariant& root, bool verbose) const;
JkBms::DataPointContainer _dataPoints;
mutable uint32_t _lastMqttPublish = 0;
mutable uint32_t _lastFullMqttPublish = 0;
uint16_t _cellMinMilliVolt = 0;
uint16_t _cellAvgMilliVolt = 0;
uint16_t _cellMaxMilliVolt = 0;
uint32_t _cellVoltageTimestamp = 0;
};
class VictronSmartShuntStats : public BatteryStats {
public:
void getLiveViewData(JsonVariant& root) const final;
void mqttPublish() const final;
void updateFrom(VeDirectShuntController::data_t const& shuntData);
private:
float _temperature;
bool _tempPresent;
uint8_t _chargeCycles;
uint32_t _timeToGo;
float _chargedEnergy;
float _dischargedEnergy;
int32_t _instantaneousPower;
float _midpointVoltage;
float _midpointDeviation;
float _consumedAmpHours;
int32_t _lastFullCharge;
bool _alarmLowVoltage;
bool _alarmHighVoltage;
bool _alarmLowSOC;
bool _alarmLowTemperature;
bool _alarmHighTemperature;
};
class MqttBatteryStats : public BatteryStats {
friend class MqttBattery;
public:
// since the source of information was MQTT in the first place,
// we do NOT publish the same data under a different topic.
void mqttPublish() const final { }
void getLiveViewData(JsonVariant& root) const final;
bool supportsAlarmsAndWarnings() const final { return false; }
};

View File

@ -3,17 +3,17 @@
#include "PinMapping.h" #include "PinMapping.h"
#include <cstdint> #include <cstdint>
#include <ArduinoJson.h> #include <TaskSchedulerDeclarations.h>
#include <mutex>
#include <condition_variable>
#define CONFIG_FILENAME "/config.json" #define CONFIG_FILENAME "/config.json"
#define CONFIG_VERSION 0x00011c00 // 0.1.28 // make sure to clean all after change #define CONFIG_VERSION 0x00011d00 // 0.1.29 // make sure to clean all after change
#define WIFI_MAX_SSID_STRLEN 32 #define WIFI_MAX_SSID_STRLEN 32
#define WIFI_MAX_PASSWORD_STRLEN 64 #define WIFI_MAX_PASSWORD_STRLEN 64
#define WIFI_MAX_HOSTNAME_STRLEN 31 #define WIFI_MAX_HOSTNAME_STRLEN 31
#define SYSLOG_MAX_HOSTNAME_STRLEN 128
#define NTP_MAX_SERVER_STRLEN 31 #define NTP_MAX_SERVER_STRLEN 31
#define NTP_MAX_TIMEZONE_STRLEN 50 #define NTP_MAX_TIMEZONE_STRLEN 50
#define NTP_MAX_TIMEZONEDESCR_STRLEN 50 #define NTP_MAX_TIMEZONEDESCR_STRLEN 50
@ -22,7 +22,7 @@
#define MQTT_MAX_CLIENTID_STRLEN 64 #define MQTT_MAX_CLIENTID_STRLEN 64
#define MQTT_MAX_USERNAME_STRLEN 64 #define MQTT_MAX_USERNAME_STRLEN 64
#define MQTT_MAX_PASSWORD_STRLEN 64 #define MQTT_MAX_PASSWORD_STRLEN 64
#define MQTT_MAX_TOPIC_STRLEN 256 #define MQTT_MAX_TOPIC_STRLEN 32
#define MQTT_MAX_LWTVALUE_STRLEN 20 #define MQTT_MAX_LWTVALUE_STRLEN 20
#define MQTT_MAX_CERT_STRLEN 2560 #define MQTT_MAX_CERT_STRLEN 2560
@ -33,17 +33,7 @@
#define CHAN_MAX_NAME_STRLEN 31 #define CHAN_MAX_NAME_STRLEN 31
#define DEV_MAX_MAPPING_NAME_STRLEN 63 #define DEV_MAX_MAPPING_NAME_STRLEN 63
#define LOCALE_STRLEN 2
#define HTTP_REQUEST_MAX_URL_STRLEN 1024
#define HTTP_REQUEST_MAX_USERNAME_STRLEN 64
#define HTTP_REQUEST_MAX_PASSWORD_STRLEN 64
#define HTTP_REQUEST_MAX_HEADER_KEY_STRLEN 64
#define HTTP_REQUEST_MAX_HEADER_VALUE_STRLEN 256
#define POWERMETER_MQTT_MAX_VALUES 3
#define POWERMETER_HTTP_JSON_MAX_VALUES 3
#define POWERMETER_HTTP_JSON_MAX_PATH_STRLEN 256
#define BATTERY_JSON_MAX_PATH_STRLEN 128
struct CHANNEL_CONFIG_T { struct CHANNEL_CONFIG_T {
uint16_t MaxChannelPower; uint16_t MaxChannelPower;
@ -67,91 +57,6 @@ struct INVERTER_CONFIG_T {
CHANNEL_CONFIG_T channel[INV_MAX_CHAN_COUNT]; CHANNEL_CONFIG_T channel[INV_MAX_CHAN_COUNT];
}; };
struct HTTP_REQUEST_CONFIG_T {
char Url[HTTP_REQUEST_MAX_URL_STRLEN + 1];
enum Auth { None, Basic, Digest };
Auth AuthType;
char Username[HTTP_REQUEST_MAX_USERNAME_STRLEN + 1];
char Password[HTTP_REQUEST_MAX_PASSWORD_STRLEN + 1];
char HeaderKey[HTTP_REQUEST_MAX_HEADER_KEY_STRLEN + 1];
char HeaderValue[HTTP_REQUEST_MAX_HEADER_VALUE_STRLEN + 1];
uint16_t Timeout;
};
using HttpRequestConfig = struct HTTP_REQUEST_CONFIG_T;
struct POWERMETER_MQTT_VALUE_T {
char Topic[MQTT_MAX_TOPIC_STRLEN + 1];
char JsonPath[POWERMETER_HTTP_JSON_MAX_PATH_STRLEN + 1];
enum Unit { Watts = 0, MilliWatts = 1, KiloWatts = 2 };
Unit PowerUnit;
bool SignInverted;
};
using PowerMeterMqttValue = struct POWERMETER_MQTT_VALUE_T;
struct POWERMETER_MQTT_CONFIG_T {
PowerMeterMqttValue Values[POWERMETER_MQTT_MAX_VALUES];
};
using PowerMeterMqttConfig = struct POWERMETER_MQTT_CONFIG_T;
struct POWERMETER_SERIAL_SDM_CONFIG_T {
uint32_t Address;
uint32_t PollingInterval;
};
using PowerMeterSerialSdmConfig = struct POWERMETER_SERIAL_SDM_CONFIG_T;
struct POWERMETER_HTTP_JSON_VALUE_T {
HttpRequestConfig HttpRequest;
bool Enabled;
char JsonPath[POWERMETER_HTTP_JSON_MAX_PATH_STRLEN + 1];
enum Unit { Watts = 0, MilliWatts = 1, KiloWatts = 2 };
Unit PowerUnit;
bool SignInverted;
};
using PowerMeterHttpJsonValue = struct POWERMETER_HTTP_JSON_VALUE_T;
struct POWERMETER_HTTP_JSON_CONFIG_T {
uint32_t PollingInterval;
bool IndividualRequests;
PowerMeterHttpJsonValue Values[POWERMETER_HTTP_JSON_MAX_VALUES];
};
using PowerMeterHttpJsonConfig = struct POWERMETER_HTTP_JSON_CONFIG_T;
struct POWERMETER_HTTP_SML_CONFIG_T {
uint32_t PollingInterval;
HttpRequestConfig HttpRequest;
};
using PowerMeterHttpSmlConfig = struct POWERMETER_HTTP_SML_CONFIG_T;
enum BatteryVoltageUnit { Volts = 0, DeciVolts = 1, CentiVolts = 2, MilliVolts = 3 };
enum BatteryAmperageUnit { Amps = 0, MilliAmps = 1 };
struct BATTERY_CONFIG_T {
bool Enabled;
bool VerboseLogging;
uint8_t Provider;
uint8_t JkBmsInterface;
uint8_t JkBmsPollingInterval;
char MqttSocTopic[MQTT_MAX_TOPIC_STRLEN + 1];
char MqttSocJsonPath[BATTERY_JSON_MAX_PATH_STRLEN + 1];
char MqttVoltageTopic[MQTT_MAX_TOPIC_STRLEN + 1];
char MqttVoltageJsonPath[BATTERY_JSON_MAX_PATH_STRLEN + 1];
BatteryVoltageUnit MqttVoltageUnit;
bool EnableDischargeCurrentLimit;
float DischargeCurrentLimit;
bool UseBatteryReportedDischargeCurrentLimit;
char MqttDischargeCurrentTopic[MQTT_MAX_TOPIC_STRLEN + 1];
char MqttDischargeCurrentJsonPath[BATTERY_JSON_MAX_PATH_STRLEN + 1];
BatteryAmperageUnit MqttAmperageUnit;
};
using BatteryConfig = struct BATTERY_CONFIG_T;
struct CONFIG_T { struct CONFIG_T {
struct { struct {
uint32_t Version; uint32_t Version;
@ -175,12 +80,6 @@ struct CONFIG_T {
bool Enabled; bool Enabled;
} Mdns; } Mdns;
struct {
bool Enabled;
char Hostname[SYSLOG_MAX_HOSTNAME_STRLEN + 1];
uint16_t Port;
} Syslog;
struct { struct {
char Server[NTP_MAX_SERVER_STRLEN + 1]; char Server[NTP_MAX_SERVER_STRLEN + 1];
char Timezone[NTP_MAX_TIMEZONE_STRLEN + 1]; char Timezone[NTP_MAX_TIMEZONE_STRLEN + 1];
@ -193,7 +92,6 @@ struct CONFIG_T {
struct { struct {
bool Enabled; bool Enabled;
char Hostname[MQTT_MAX_HOSTNAME_STRLEN + 1]; char Hostname[MQTT_MAX_HOSTNAME_STRLEN + 1];
bool VerboseLogging;
uint32_t Port; uint32_t Port;
char ClientId[MQTT_MAX_CLIENTID_STRLEN + 1]; char ClientId[MQTT_MAX_CLIENTID_STRLEN + 1];
char Username[MQTT_MAX_USERNAME_STRLEN + 1]; char Username[MQTT_MAX_USERNAME_STRLEN + 1];
@ -238,7 +136,6 @@ struct CONFIG_T {
uint32_t Frequency; uint32_t Frequency;
uint8_t CountryMode; uint8_t CountryMode;
} Cmt; } Cmt;
bool VerboseLogging;
} Dtu; } Dtu;
struct { struct {
@ -251,7 +148,7 @@ struct CONFIG_T {
bool ScreenSaver; bool ScreenSaver;
uint8_t Rotation; uint8_t Rotation;
uint8_t Contrast; uint8_t Contrast;
uint8_t Language; char Locale[LOCALE_STRLEN + 1];
struct { struct {
uint32_t Duration; uint32_t Duration;
uint8_t Mode; uint8_t Mode;
@ -262,98 +159,38 @@ struct CONFIG_T {
uint8_t Brightness; uint8_t Brightness;
} Led_Single[PINMAPPING_LED_COUNT]; } Led_Single[PINMAPPING_LED_COUNT];
struct {
bool Enabled;
bool VerboseLogging;
bool UpdatesOnly;
} Vedirect;
struct PowerMeterConfig {
bool Enabled;
bool VerboseLogging;
uint32_t Source;
PowerMeterMqttConfig Mqtt;
PowerMeterSerialSdmConfig SerialSdm;
PowerMeterHttpJsonConfig HttpJson;
PowerMeterHttpSmlConfig HttpSml;
} PowerMeter;
struct {
bool Enabled;
bool VerboseLogging;
bool SolarPassThroughEnabled;
uint8_t SolarPassThroughLosses;
bool BatteryAlwaysUseAtNight;
uint32_t Interval;
bool IsInverterBehindPowerMeter;
bool IsInverterSolarPowered;
bool UseOverscalingToCompensateShading;
uint64_t InverterId;
uint8_t InverterChannelId;
int32_t TargetPowerConsumption;
int32_t TargetPowerConsumptionHysteresis;
int32_t LowerPowerLimit;
int32_t BaseLoadLimit;
int32_t UpperPowerLimit;
bool IgnoreSoc;
uint32_t BatterySocStartThreshold;
uint32_t BatterySocStopThreshold;
float VoltageStartThreshold;
float VoltageStopThreshold;
float VoltageLoadCorrectionFactor;
int8_t RestartHour;
uint32_t FullSolarPassThroughSoc;
float FullSolarPassThroughStartVoltage;
float FullSolarPassThroughStopVoltage;
} PowerLimiter;
BatteryConfig Battery;
struct {
bool Enabled;
bool VerboseLogging;
uint32_t CAN_Controller_Frequency;
bool Auto_Power_Enabled;
bool Auto_Power_BatterySoC_Limits_Enabled;
bool Emergency_Charge_Enabled;
float Auto_Power_Voltage_Limit;
float Auto_Power_Enable_Voltage_Limit;
float Auto_Power_Lower_Power_Limit;
float Auto_Power_Upper_Power_Limit;
uint8_t Auto_Power_Stop_BatterySoC_Threshold;
float Auto_Power_Target_Power_Consumption;
} Huawei;
INVERTER_CONFIG_T Inverter[INV_MAX_COUNT]; INVERTER_CONFIG_T Inverter[INV_MAX_COUNT];
char Dev_PinMapping[DEV_MAX_MAPPING_NAME_STRLEN + 1]; char Dev_PinMapping[DEV_MAX_MAPPING_NAME_STRLEN + 1];
}; };
class ConfigurationClass { class ConfigurationClass {
public: public:
void init(); void init(Scheduler& scheduler);
bool read(); bool read();
bool write(); bool write();
void migrate(); void migrate();
CONFIG_T& get(); CONFIG_T const& get();
class WriteGuard {
public:
WriteGuard();
CONFIG_T& getConfig();
~WriteGuard();
private:
std::unique_lock<std::mutex> _lock;
};
WriteGuard getWriteGuard();
INVERTER_CONFIG_T* getFreeInverterSlot(); INVERTER_CONFIG_T* getFreeInverterSlot();
INVERTER_CONFIG_T* getInverterConfig(const uint64_t serial); INVERTER_CONFIG_T* getInverterConfig(const uint64_t serial);
void deleteInverterById(const uint8_t id); void deleteInverterById(const uint8_t id);
static void serializeHttpRequestConfig(HttpRequestConfig const& source, JsonObject& target); private:
static void serializePowerMeterMqttConfig(PowerMeterMqttConfig const& source, JsonObject& target); void loop();
static void serializePowerMeterSerialSdmConfig(PowerMeterSerialSdmConfig const& source, JsonObject& target);
static void serializePowerMeterHttpJsonConfig(PowerMeterHttpJsonConfig const& source, JsonObject& target);
static void serializePowerMeterHttpSmlConfig(PowerMeterHttpSmlConfig const& source, JsonObject& target);
static void serializeBatteryConfig(BatteryConfig const& source, JsonObject& target);
static void deserializeHttpRequestConfig(JsonObject const& source, HttpRequestConfig& target); Task _loopTask;
static void deserializePowerMeterMqttConfig(JsonObject const& source, PowerMeterMqttConfig& target);
static void deserializePowerMeterSerialSdmConfig(JsonObject const& source, PowerMeterSerialSdmConfig& target);
static void deserializePowerMeterHttpJsonConfig(JsonObject const& source, PowerMeterHttpJsonConfig& target);
static void deserializePowerMeterHttpSmlConfig(JsonObject const& source, PowerMeterHttpSmlConfig& target);
static void deserializeBatteryConfig(JsonObject const& source, BatteryConfig& target);
}; };
extern ConfigurationClass Configuration; extern ConfigurationClass Configuration;

View File

@ -40,7 +40,7 @@ public:
void setContrast(const uint8_t contrast); void setContrast(const uint8_t contrast);
void setStatus(const bool turnOn); void setStatus(const bool turnOn);
void setOrientation(const uint8_t rotation = DISPLAY_ROTATION); void setOrientation(const uint8_t rotation = DISPLAY_ROTATION);
void setLanguage(const uint8_t language); void setLocale(const String& locale);
void setDiagramMode(DiagramMode_t mode); void setDiagramMode(DiagramMode_t mode);
void setStartupDisplay(); void setStartupDisplay();
@ -65,7 +65,7 @@ private:
DisplayType_t _display_type = DisplayType_t::None; DisplayType_t _display_type = DisplayType_t::None;
DiagramMode_t _diagram_mode = DiagramMode_t::Off; DiagramMode_t _diagram_mode = DiagramMode_t::Off;
uint8_t _display_language = DISPLAY_LANGUAGE; String _display_language = DISPLAY_LOCALE;
uint8_t _mExtra; uint8_t _mExtra;
const uint16_t _period = 1000; const uint16_t _period = 1000;
const uint16_t _interval = 60000; // interval at which to power save (milliseconds) const uint16_t _interval = 60000; // interval at which to power save (milliseconds)
@ -73,6 +73,15 @@ private:
char _fmtText[32]; char _fmtText[32];
bool _isLarge = false; bool _isLarge = false;
uint8_t _lineOffsets[5]; uint8_t _lineOffsets[5];
String _i18n_offline;
String _i18n_yield_today_kwh;
String _i18n_yield_today_wh;
String _i18n_date_format;
String _i18n_current_power_kw;
String _i18n_current_power_w;
String _i18n_yield_total_mwh;
String _i18n_yield_total_kwh;
}; };
extern DisplayGraphicClass Display; extern DisplayGraphicClass Display;

View File

@ -1,90 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "Configuration.h"
#include <memory>
#include <vector>
#include <utility>
#include <string>
#include <HTTPClient.h>
#include <WiFiClient.h>
class HttpGetterClient : public HTTPClient {
public:
void restartTCP() {
// keeps the NetworkClient, and closes the TCP connections (as we
// effectively do not support keep-alive with HTTP 1.0).
HTTPClient::disconnect(true);
HTTPClient::connect();
}
};
using up_http_client_t = std::unique_ptr<HttpGetterClient>;
using sp_wifi_client_t = std::shared_ptr<WiFiClient>;
class HttpRequestResult {
public:
HttpRequestResult(bool success,
up_http_client_t upHttpClient = nullptr,
sp_wifi_client_t spWiFiClient = nullptr)
: _success(success)
, _upHttpClient(std::move(upHttpClient))
, _spWiFiClient(std::move(spWiFiClient)) { }
~HttpRequestResult() {
// the wifi client *must* die *after* the http client, as the http
// client uses the wifi client in its destructor.
if (_upHttpClient) { _upHttpClient->end(); }
_upHttpClient = nullptr;
_spWiFiClient = nullptr;
}
HttpRequestResult(HttpRequestResult const&) = delete;
HttpRequestResult(HttpRequestResult&&) = delete;
HttpRequestResult& operator=(HttpRequestResult const&) = delete;
HttpRequestResult& operator=(HttpRequestResult&&) = delete;
operator bool() const { return _success; }
Stream* getStream() {
if(!_upHttpClient) { return nullptr; }
return _upHttpClient->getStreamPtr();
}
private:
bool _success;
up_http_client_t _upHttpClient;
sp_wifi_client_t _spWiFiClient;
};
class HttpGetter {
public:
explicit HttpGetter(HttpRequestConfig const& cfg)
: _config(cfg) { }
bool init();
void addHeader(char const* key, char const* value);
HttpRequestResult performGetRequest();
char const* getErrorText() const { return _errBuffer; }
private:
std::pair<bool, String> getAuthDigest();
HttpRequestConfig const& _config;
template<typename... Args>
void logError(char const* format, Args... args);
char _errBuffer[256];
bool _useHttps;
String _host;
String _uri;
uint16_t _port;
String _wwwAuthenticate = "";
unsigned _nonceCounter = 0;
sp_wifi_client_t _spWiFiClient; // reused for multiple HTTP requests
std::vector<std::pair<std::string, std::string>> _additionalHeaders;
};

View File

@ -1,158 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <cstdint>
#include "SPI.h"
#include <mcp_can.h>
#include <mutex>
#include <TaskSchedulerDeclarations.h>
#ifndef HUAWEI_PIN_MISO
#define HUAWEI_PIN_MISO 12
#endif
#ifndef HUAWEI_PIN_MOSI
#define HUAWEI_PIN_MOSI 13
#endif
#ifndef HUAWEI_PIN_SCLK
#define HUAWEI_PIN_SCLK 26
#endif
#ifndef HUAWEI_PIN_IRQ
#define HUAWEI_PIN_IRQ 25
#endif
#ifndef HUAWEI_PIN_CS
#define HUAWEI_PIN_CS 15
#endif
#ifndef HUAWEI_PIN_POWER
#define HUAWEI_PIN_POWER 33
#endif
#define HUAWEI_MINIMAL_OFFLINE_VOLTAGE 48
#define HUAWEI_MINIMAL_ONLINE_VOLTAGE 42
#define MAX_CURRENT_MULTIPLIER 20
// Index values for rec_values array
#define HUAWEI_INPUT_POWER_IDX 0
#define HUAWEI_INPUT_FREQ_IDX 1
#define HUAWEI_INPUT_CURRENT_IDX 2
#define HUAWEI_OUTPUT_POWER_IDX 3
#define HUAWEI_EFFICIENCY_IDX 4
#define HUAWEI_OUTPUT_VOLTAGE_IDX 5
#define HUAWEI_OUTPUT_CURRENT_MAX_IDX 6
#define HUAWEI_INPUT_VOLTAGE_IDX 7
#define HUAWEI_OUTPUT_TEMPERATURE_IDX 8
#define HUAWEI_INPUT_TEMPERATURE_IDX 9
#define HUAWEI_OUTPUT_CURRENT_IDX 10
#define HUAWEI_OUTPUT_CURRENT1_IDX 11
// Defines and index values for tx_values array
#define HUAWEI_OFFLINE_VOLTAGE 0x01
#define HUAWEI_ONLINE_VOLTAGE 0x00
#define HUAWEI_OFFLINE_CURRENT 0x04
#define HUAWEI_ONLINE_CURRENT 0x03
// Modes of operation
#define HUAWEI_MODE_OFF 0
#define HUAWEI_MODE_ON 1
#define HUAWEI_MODE_AUTO_EXT 2
#define HUAWEI_MODE_AUTO_INT 3
// Error codes
#define HUAWEI_ERROR_CODE_RX 0x01
#define HUAWEI_ERROR_CODE_TX 0x02
// Wait time/current before shuting down the PSU / charger
// This is set to allow the fan to run for some time
#define HUAWEI_AUTO_MODE_SHUTDOWN_DELAY 60000
#define HUAWEI_AUTO_MODE_SHUTDOWN_CURRENT 0.75
// Updateinterval used to request new values from the PSU
#define HUAWEI_DATA_REQUEST_INTERVAL_MS 2500
typedef struct RectifierParameters {
float input_voltage;
float input_frequency;
float input_current;
float input_power;
float input_temp;
float efficiency;
float output_voltage;
float output_current;
float max_output_current;
float output_power;
float output_temp;
float amp_hour;
} RectifierParameters_t;
class HuaweiCanCommClass {
public:
bool init(uint8_t huawei_miso, uint8_t huawei_mosi, uint8_t huawei_clk,
uint8_t huawei_irq, uint8_t huawei_cs, uint32_t frequency);
void loop();
bool gotNewRxDataFrame(bool clear);
uint8_t getErrorCode(bool clear);
uint32_t getParameterValue(uint8_t parameter);
void setParameterValue(uint16_t in, uint8_t parameterType);
private:
void sendRequest();
SPIClass *SPI;
MCP_CAN *_CAN;
uint8_t _huaweiIrq; // IRQ pin
uint32_t _nextRequestMillis = 0; // When to send next data request to PSU
std::mutex _mutex;
uint32_t _recValues[12];
uint16_t _txValues[5];
bool _hasNewTxValue[5];
uint8_t _errorCode;
bool _completeUpdateReceived;
};
class HuaweiCanClass {
public:
void init(Scheduler& scheduler, uint8_t huawei_miso, uint8_t huawei_mosi, uint8_t huawei_clk, uint8_t huawei_irq, uint8_t huawei_cs, uint8_t huawei_power);
void updateSettings(uint8_t huawei_miso, uint8_t huawei_mosi, uint8_t huawei_clk, uint8_t huawei_irq, uint8_t huawei_cs, uint8_t huawei_power);
void setValue(float in, uint8_t parameterType);
void setMode(uint8_t mode);
RectifierParameters_t * get();
uint32_t getLastUpdate() const { return _lastUpdateReceivedMillis; };
bool getAutoPowerStatus() const { return _autoPowerEnabled; };
uint8_t getMode() const { return _mode; };
private:
void loop();
void processReceivedParameters();
void _setValue(float in, uint8_t parameterType);
Task _loopTask;
TaskHandle_t _HuaweiCanCommunicationTaskHdl = NULL;
bool _initialized = false;
uint8_t _huaweiPower; // Power pin
uint8_t _mode = HUAWEI_MODE_AUTO_EXT;
RectifierParameters_t _rp;
uint32_t _lastUpdateReceivedMillis; // Timestamp for last data seen from the PSU
uint32_t _outputCurrentOnSinceMillis; // Timestamp since when the PSU was idle at zero amps
uint32_t _nextAutoModePeriodicIntMillis; // When to set the next output voltage in automatic mode
uint32_t _lastPowerMeterUpdateReceivedMillis; // Timestamp of last seen power meter value
uint32_t _autoModeBlockedTillMillis = 0; // Timestamp to block running auto mode for some time
uint8_t _autoPowerEnabledCounter = 0;
bool _autoPowerEnabled = false;
bool _batteryEmergencyCharging = false;
};
extern HuaweiCanClass HuaweiCan;
extern HuaweiCanCommClass HuaweiCanComm;

35
include/I18n.h Normal file
View File

@ -0,0 +1,35 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <TaskSchedulerDeclarations.h>
#include <WString.h>
#include <list>
struct LanguageInfo_t {
String code;
String name;
String filename;
};
class I18nClass {
public:
I18nClass();
void init(Scheduler& scheduler);
std::list<LanguageInfo_t> getAvailableLanguages();
String getFilenameByLocale(const String& locale) const;
void readDisplayStrings(
const String& locale,
String& date_format,
String& offline,
String& power_w, String& power_kw,
String& yield_today_wh, String& yield_today_kwh,
String& yield_total_kwh, String& yield_total_mwh);
private:
void readLangPacks();
void readConfig(String file);
std::list<LanguageInfo_t> _availLanguages;
};
extern I18nClass I18n;

View File

@ -1,87 +0,0 @@
#pragma once
#include <memory>
#include <vector>
#include <frozen/string.h>
#include "Battery.h"
#include "JkBmsSerialMessage.h"
#include "JkBmsDummy.h"
//#define JKBMS_DUMMY_SERIAL
class DataPointContainer;
namespace JkBms {
class Controller : public BatteryProvider {
public:
Controller() = default;
bool init(bool verboseLogging) final;
void deinit() final;
void loop() final;
std::shared_ptr<BatteryStats> getStats() const final { return _stats; }
private:
static char constexpr _serialPortOwner[] = "JK BMS";
#ifdef JKBMS_DUMMY_SERIAL
std::unique_ptr<DummySerial> _upSerial;
#else
std::unique_ptr<HardwareSerial> _upSerial;
#endif
enum class Status : unsigned {
Initializing,
Timeout,
WaitingForPollInterval,
HwSerialNotAvailableForWrite,
BusyReading,
RequestSent,
FrameCompleted
};
frozen::string const& getStatusText(Status status);
void announceStatus(Status status);
void sendRequest(uint8_t pollInterval);
void rxData(uint8_t inbyte);
void reset();
void frameComplete();
void processDataPoints(DataPointContainer const& dataPoints);
enum class Interface : unsigned {
Invalid,
Uart,
Transceiver
};
Interface getInterface() const;
enum class ReadState : unsigned {
Idle,
WaitingForFrameStart,
FrameStartReceived,
StartMarkerReceived,
FrameLengthMsbReceived,
ReadingFrame
};
ReadState _readState;
void setReadState(ReadState state) {
_readState = state;
}
bool _verboseLogging = true;
int8_t _rxEnablePin = -1;
int8_t _txEnablePin = -1;
Status _lastStatus = Status::Initializing;
uint32_t _lastStatusPrinted = 0;
uint32_t _lastRequest = 0;
uint16_t _frameLength = 0;
uint8_t _protocolVersion = -1;
SerialResponse::tData _buffer = {};
std::shared_ptr<JkBmsBatteryStats> _stats =
std::make_shared<JkBmsBatteryStats>();
};
} /* namespace JkBms */

View File

@ -1,304 +0,0 @@
#pragma once
#include <Arduino.h>
#include <map>
#include <optional>
#include <string>
#include <unordered_map>
#include <variant>
#include <frozen/map.h>
#include <frozen/string.h>
namespace JkBms {
#define ALARM_BITS(fnc) \
fnc(LowCapacity, (1<<0)) \
fnc(BmsOvertemperature, (1<<1)) \
fnc(ChargingOvervoltage, (1<<2)) \
fnc(DischargeUndervoltage, (1<<3)) \
fnc(BatteryOvertemperature, (1<<4)) \
fnc(ChargingOvercurrent, (1<<5)) \
fnc(DischargeOvercurrent, (1<<6)) \
fnc(CellVoltageDifference, (1<<7)) \
fnc(BatteryBoxOvertemperature, (1<<8)) \
fnc(BatteryUndertemperature, (1<<9)) \
fnc(CellOvervoltage, (1<<10)) \
fnc(CellUndervoltage, (1<<11)) \
fnc(AProtect, (1<<12)) \
fnc(BProtect, (1<<13)) \
fnc(Reserved1, (1<<14)) \
fnc(Reserved2, (1<<15))
enum class AlarmBits : uint16_t {
#define ALARM_ENUM(name, value) name = value,
ALARM_BITS(ALARM_ENUM)
#undef ALARM_ENUM
};
static const frozen::map<AlarmBits, frozen::string, 16> AlarmBitTexts = {
#define ALARM_TEXT(name, value) { AlarmBits::name, #name },
ALARM_BITS(ALARM_TEXT)
#undef ALARM_TEXT
};
#define STATUS_BITS(fnc) \
fnc(ChargingActive, (1<<0)) \
fnc(DischargingActive, (1<<1)) \
fnc(BalancingActive, (1<<2)) \
fnc(BatteryOnline, (1<<3))
enum class StatusBits : uint16_t {
#define STATUS_ENUM(name, value) name = value,
STATUS_BITS(STATUS_ENUM)
#undef STATUS_ENUM
};
static const frozen::map<StatusBits, frozen::string, 4> StatusBitTexts = {
#define STATUS_TEXT(name, value) { StatusBits::name, #name },
STATUS_BITS(STATUS_TEXT)
#undef STATUS_TEXT
};
enum class DataPointLabel : uint8_t {
CellsMilliVolt = 0x79,
BmsTempCelsius = 0x80,
BatteryTempOneCelsius = 0x81,
BatteryTempTwoCelsius = 0x82,
BatteryVoltageMilliVolt = 0x83,
BatteryCurrentMilliAmps = 0x84,
BatterySoCPercent = 0x85,
BatteryTemperatureSensorAmount = 0x86,
BatteryCycles = 0x87,
BatteryCycleCapacity = 0x89,
BatteryCellAmount = 0x8a,
AlarmsBitmask = 0x8b,
StatusBitmask = 0x8c,
TotalOvervoltageThresholdMilliVolt = 0x8e,
TotalUndervoltageThresholdMilliVolt = 0x8f,
CellOvervoltageThresholdMilliVolt = 0x90,
CellOvervoltageRecoveryMilliVolt = 0x91,
CellOvervoltageProtectionDelaySeconds = 0x92,
CellUndervoltageThresholdMilliVolt = 0x93,
CellUndervoltageRecoveryMilliVolt = 0x94,
CellUndervoltageProtectionDelaySeconds = 0x95,
CellVoltageDiffThresholdMilliVolt = 0x96,
DischargeOvercurrentThresholdAmperes = 0x97,
DischargeOvercurrentDelaySeconds = 0x98,
ChargeOvercurrentThresholdAmps = 0x99,
ChargeOvercurrentDelaySeconds = 0x9a,
BalanceCellVoltageThresholdMilliVolt = 0x9b,
BalanceVoltageDiffThresholdMilliVolt = 0x9c,
BalancingEnabled = 0x9d,
BmsTempProtectionThresholdCelsius = 0x9e,
BmsTempRecoveryThresholdCelsius = 0x9f,
BatteryTempProtectionThresholdCelsius = 0xa0,
BatteryTempRecoveryThresholdCelsius = 0xa1,
BatteryTempDiffThresholdCelsius = 0xa2,
ChargeHighTempThresholdCelsius = 0xa3,
DischargeHighTempThresholdCelsius = 0xa4,
ChargeLowTempThresholdCelsius = 0xa5,
ChargeLowTempRecoveryCelsius = 0xa6,
DischargeLowTempThresholdCelsius = 0xa7,
DischargeLowTempRecoveryCelsius = 0xa8,
CellAmountSetting = 0xa9,
BatteryCapacitySettingAmpHours = 0xaa,
BatteryChargeEnabled = 0xab,
BatteryDischargeEnabled = 0xac,
CurrentCalibrationMilliAmps = 0xad,
BmsAddress = 0xae,
BatteryType = 0xaf,
SleepWaitTime = 0xb0, // what's this?
LowCapacityAlarmThresholdPercent = 0xb1,
ModificationPassword = 0xb2,
DedicatedChargerSwitch = 0xb3, // what's this?
EquipmentId = 0xb4,
DateOfManufacturing = 0xb5,
BmsHourMeterMinutes = 0xb6,
BmsSoftwareVersion = 0xb7,
CurrentCalibration = 0xb8,
ActualBatteryCapacityAmpHours = 0xb9,
ProductId = 0xba,
ProtocolVersion = 0xc0
};
using tCells = std::map<uint8_t, uint16_t>;
template<DataPointLabel> struct DataPointLabelTraits;
#define LABEL_TRAIT(n, t, u) template<> struct DataPointLabelTraits<DataPointLabel::n> { \
using type = t; \
static constexpr char const name[] = #n; \
static constexpr char const unit[] = u; \
};
/**
* the types associated with the labels are the types for the respective data
* points in the JkBms::DataPoint class. they are *not* always equal to the
* type used in the serial message.
*
* it is unfortunate that we have to repeat all enum values here to define the
* traits. code generation could help here (labels are defined in a single
* source of truth and this code is generated -- no typing errors, etc.).
* however, the compiler will complain if an enum is misspelled or traits are
* defined for a removed enum, so we will notice. it will also complain when a
* trait is missing and if a data point for a label without traits is added to
* the DataPointContainer class, because the traits must be available then.
* even though this is tedious to maintain, human errors will be caught.
*/
LABEL_TRAIT(CellsMilliVolt, tCells, "mV");
LABEL_TRAIT(BmsTempCelsius, int16_t, "°C");
LABEL_TRAIT(BatteryTempOneCelsius, int16_t, "°C");
LABEL_TRAIT(BatteryTempTwoCelsius, int16_t, "°C");
LABEL_TRAIT(BatteryVoltageMilliVolt, uint32_t, "mV");
LABEL_TRAIT(BatteryCurrentMilliAmps, int32_t, "mA");
LABEL_TRAIT(BatterySoCPercent, uint8_t, "%");
LABEL_TRAIT(BatteryTemperatureSensorAmount, uint8_t, "");
LABEL_TRAIT(BatteryCycles, uint16_t, "");
LABEL_TRAIT(BatteryCycleCapacity, uint32_t, "Ah");
LABEL_TRAIT(BatteryCellAmount, uint16_t, "");
LABEL_TRAIT(AlarmsBitmask, uint16_t, "");
LABEL_TRAIT(StatusBitmask, uint16_t, "");
LABEL_TRAIT(TotalOvervoltageThresholdMilliVolt, uint32_t, "mV");
LABEL_TRAIT(TotalUndervoltageThresholdMilliVolt, uint32_t, "mV");
LABEL_TRAIT(CellOvervoltageThresholdMilliVolt, uint16_t, "mV");
LABEL_TRAIT(CellOvervoltageRecoveryMilliVolt, uint16_t, "mV");
LABEL_TRAIT(CellOvervoltageProtectionDelaySeconds, uint16_t, "s");
LABEL_TRAIT(CellUndervoltageThresholdMilliVolt, uint16_t, "mV");
LABEL_TRAIT(CellUndervoltageRecoveryMilliVolt, uint16_t, "mV");
LABEL_TRAIT(CellUndervoltageProtectionDelaySeconds, uint16_t, "s");
LABEL_TRAIT(CellVoltageDiffThresholdMilliVolt, uint16_t, "mV");
LABEL_TRAIT(DischargeOvercurrentThresholdAmperes, uint16_t, "A");
LABEL_TRAIT(DischargeOvercurrentDelaySeconds, uint16_t, "s");
LABEL_TRAIT(ChargeOvercurrentThresholdAmps, uint16_t, "A");
LABEL_TRAIT(ChargeOvercurrentDelaySeconds, uint16_t, "s");
LABEL_TRAIT(BalanceCellVoltageThresholdMilliVolt, uint16_t, "mV");
LABEL_TRAIT(BalanceVoltageDiffThresholdMilliVolt, uint16_t, "mV");
LABEL_TRAIT(BalancingEnabled, bool, "");
LABEL_TRAIT(BmsTempProtectionThresholdCelsius, uint16_t, "°C");
LABEL_TRAIT(BmsTempRecoveryThresholdCelsius, uint16_t, "°C");
LABEL_TRAIT(BatteryTempProtectionThresholdCelsius, uint16_t, "°C");
LABEL_TRAIT(BatteryTempRecoveryThresholdCelsius, uint16_t, "°C");
LABEL_TRAIT(BatteryTempDiffThresholdCelsius, uint16_t, "°C");
LABEL_TRAIT(ChargeHighTempThresholdCelsius, uint16_t, "°C");
LABEL_TRAIT(DischargeHighTempThresholdCelsius, uint16_t, "°C");
LABEL_TRAIT(ChargeLowTempThresholdCelsius, int16_t, "°C");
LABEL_TRAIT(ChargeLowTempRecoveryCelsius, int16_t, "°C");
LABEL_TRAIT(DischargeLowTempThresholdCelsius, int16_t, "°C");
LABEL_TRAIT(DischargeLowTempRecoveryCelsius, int16_t, "°C");
LABEL_TRAIT(CellAmountSetting, uint8_t, "");
LABEL_TRAIT(BatteryCapacitySettingAmpHours, uint32_t, "Ah");
LABEL_TRAIT(BatteryChargeEnabled, bool, "");
LABEL_TRAIT(BatteryDischargeEnabled, bool, "");
LABEL_TRAIT(CurrentCalibrationMilliAmps, uint16_t, "mA");
LABEL_TRAIT(BmsAddress, uint8_t, "");
LABEL_TRAIT(BatteryType, uint8_t, "");
LABEL_TRAIT(SleepWaitTime, uint16_t, "s");
LABEL_TRAIT(LowCapacityAlarmThresholdPercent, uint8_t, "%");
LABEL_TRAIT(ModificationPassword, std::string, "");
LABEL_TRAIT(DedicatedChargerSwitch, bool, "");
LABEL_TRAIT(EquipmentId, std::string, "");
LABEL_TRAIT(DateOfManufacturing, std::string, "");
LABEL_TRAIT(BmsHourMeterMinutes, uint32_t, "min");
LABEL_TRAIT(BmsSoftwareVersion, std::string, "");
LABEL_TRAIT(CurrentCalibration, bool, "");
LABEL_TRAIT(ActualBatteryCapacityAmpHours, uint32_t, "Ah");
LABEL_TRAIT(ProductId, std::string, "");
LABEL_TRAIT(ProtocolVersion, uint8_t, "");
#undef LABEL_TRAIT
class DataPoint {
friend class DataPointContainer;
public:
using tValue = std::variant<bool, uint8_t, uint16_t, uint32_t,
int16_t, int32_t, std::string, tCells>;
DataPoint() = delete;
DataPoint(DataPoint const& other)
: _strLabel(other._strLabel)
, _strValue(other._strValue)
, _strUnit(other._strUnit)
, _value(other._value)
, _timestamp(other._timestamp) { }
DataPoint(std::string const& strLabel, std::string const& strValue,
std::string const& strUnit, tValue value, uint32_t timestamp)
: _strLabel(strLabel)
, _strValue(strValue)
, _strUnit(strUnit)
, _value(std::move(value))
, _timestamp(timestamp) { }
std::string const& getLabelText() const { return _strLabel; }
std::string const& getValueText() const { return _strValue; }
std::string const& getUnitText() const { return _strUnit; }
uint32_t getTimestamp() const { return _timestamp; }
bool operator==(DataPoint const& other) const {
return _value == other._value;
}
private:
std::string _strLabel;
std::string _strValue;
std::string _strUnit;
tValue _value;
uint32_t _timestamp;
};
template<typename T> std::string dataPointValueToStr(T const& v);
class DataPointContainer {
public:
DataPointContainer() = default;
using Label = DataPointLabel;
template<Label L> using Traits = JkBms::DataPointLabelTraits<L>;
template<Label L>
void add(typename Traits<L>::type val) {
_dataPoints.emplace(
L,
DataPoint(
Traits<L>::name,
dataPointValueToStr(val),
Traits<L>::unit,
DataPoint::tValue(std::move(val)),
millis()
)
);
}
// make sure add() is only called with the type expected for the
// respective label, no implicit conversions allowed.
template<Label L, typename T>
void add(T) = delete;
template<Label L>
std::optional<DataPoint const> getDataPointFor() const {
auto it = _dataPoints.find(L);
if (it == _dataPoints.end()) { return std::nullopt; }
return it->second;
}
template<Label L>
std::optional<typename Traits<L>::type> get() const {
auto optionalDataPoint = getDataPointFor<L>();
if (!optionalDataPoint.has_value()) { return std::nullopt; }
return std::get<typename Traits<L>::type>(optionalDataPoint->_value);
}
using tMap = std::unordered_map<Label, DataPoint const>;
tMap::const_iterator cbegin() const { return _dataPoints.cbegin(); }
tMap::const_iterator cend() const { return _dataPoints.cend(); }
// copy all data points from source into this instance, overwriting
// existing data points in this instance.
void updateFrom(DataPointContainer const& source);
private:
tMap _dataPoints;
};
} /* namespace JkBms */

View File

@ -1,196 +0,0 @@
#pragma once
#include <Arduino.h>
#include <vector>
#include "MessageOutput.h"
namespace JkBms {
class DummySerial {
public:
DummySerial() = default;
void begin(uint32_t, uint32_t, int8_t, int8_t) {
MessageOutput.println("JK BMS Dummy Serial: begin()");
}
void end() { MessageOutput.println("JK BMS Dummy Serial: end()"); }
void flush() { }
bool availableForWrite() const { return true; }
size_t write(const uint8_t *buffer, size_t size) {
MessageOutput.printf("JK BMS Dummy Serial: write(%d Bytes)\r\n", size);
_byte_idx = 0;
_msg_idx = (_msg_idx + 1) % _data.size();
return size;
}
bool available() const {
return _byte_idx < _data[_msg_idx].size();
}
int read() {
if (_byte_idx >= _data[_msg_idx].size()) { return 0; }
return _data[_msg_idx][_byte_idx++];
}
private:
std::vector<std::vector<uint8_t>> const _data =
{
{
0x4e, 0x57, 0x01, 0x21, 0x00, 0x00, 0x00, 0x00,
0x06, 0x00, 0x01, 0x79, 0x30, 0x01, 0x0c, 0xfb,
0x02, 0x0c, 0xfb, 0x03, 0x0c, 0xfb, 0x04, 0x0c,
0xfb, 0x05, 0x0c, 0xfb, 0x06, 0x0c, 0xfb, 0x07,
0x0c, 0xfb, 0x08, 0x0c, 0xf7, 0x09, 0x0d, 0x01,
0x0a, 0x0c, 0xf9, 0x0b, 0x0c, 0xfb, 0x0c, 0x0c,
0xfb, 0x0d, 0x0c, 0xfb, 0x0e, 0x0c, 0xf8, 0x0f,
0x0c, 0xf9, 0x10, 0x0c, 0xfb, 0x80, 0x00, 0x1a,
0x81, 0x00, 0x12, 0x82, 0x00, 0x12, 0x83, 0x14,
0xc3, 0x84, 0x83, 0xf4, 0x85, 0x2e, 0x86, 0x02,
0x87, 0x00, 0x15, 0x89, 0x00, 0x00, 0x13, 0x52,
0x8a, 0x00, 0x10, 0x8b, 0x00, 0x00, 0x8c, 0x00,
0x03, 0x8e, 0x16, 0x80, 0x8f, 0x12, 0xc0, 0x90,
0x0e, 0x10, 0x91, 0x0c, 0xda, 0x92, 0x00, 0x05,
0x93, 0x0b, 0xb8, 0x94, 0x0c, 0x80, 0x95, 0x00,
0x05, 0x96, 0x01, 0x2c, 0x97, 0x00, 0x28, 0x98,
0x01, 0x2c, 0x99, 0x00, 0x28, 0x9a, 0x00, 0x1e,
0x9b, 0x0b, 0xb8, 0x9c, 0x00, 0x0a, 0x9d, 0x01,
0x9e, 0x00, 0x64, 0x9f, 0x00, 0x50, 0xa0, 0x00,
0x64, 0xa1, 0x00, 0x64, 0xa2, 0x00, 0x14, 0xa3,
0x00, 0x46, 0xa4, 0x00, 0x46, 0xa5, 0x00, 0x00,
0xa6, 0x00, 0x02, 0xa7, 0xff, 0xec, 0xa8, 0xff,
0xf6, 0xa9, 0x10, 0xaa, 0x00, 0x00, 0x00, 0xe6,
0xab, 0x01, 0xac, 0x01, 0xad, 0x04, 0x4d, 0xae,
0x01, 0xaf, 0x00, 0xb0, 0x00, 0x0a, 0xb1, 0x14,
0xb2, 0x32, 0x32, 0x31, 0x31, 0x38, 0x37, 0x00,
0x00, 0x00, 0x00, 0xb3, 0x00, 0xb4, 0x62, 0x65,
0x6b, 0x69, 0x00, 0x00, 0x00, 0x00, 0xb5, 0x32,
0x33, 0x30, 0x36, 0xb6, 0x00, 0x01, 0x4a, 0xc3,
0xb7, 0x31, 0x31, 0x2e, 0x58, 0x57, 0x5f, 0x53,
0x31, 0x31, 0x2e, 0x32, 0x36, 0x32, 0x48, 0x5f,
0xb8, 0x00, 0xb9, 0x00, 0x00, 0x00, 0xe6, 0xba,
0x62, 0x65, 0x6b, 0x69, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x4a, 0x4b, 0x5f, 0x42,
0x31, 0x41, 0x32, 0x34, 0x53, 0x31, 0x35, 0x50,
0xc0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00,
0x00, 0x53, 0xbb
},
{
0x4e, 0x57, 0x01, 0x21, 0x00, 0x00, 0x00, 0x00,
0x06, 0x00, 0x01, 0x79, 0x30, 0x01, 0x0c, 0xc0,
0x02, 0x0c, 0xc1, 0x03, 0x0c, 0xc0, 0x04, 0x0c,
0xc4, 0x05, 0x0c, 0xc4, 0x06, 0x0c, 0xc2, 0x07,
0x0c, 0xc2, 0x08, 0x0c, 0xc1, 0x09, 0x0c, 0xba,
0x0a, 0x0c, 0xc1, 0x0b, 0x0c, 0xc2, 0x0c, 0x0c,
0xc2, 0x0d, 0x0c, 0xc2, 0x0e, 0x0c, 0xc4, 0x0f,
0x0c, 0xc2, 0x10, 0x0c, 0xc1, 0x80, 0x00, 0x1b,
0x81, 0x00, 0x1b, 0x82, 0x00, 0x1a, 0x83, 0x14,
0x68, 0x84, 0x03, 0x70, 0x85, 0x3c, 0x86, 0x02,
0x87, 0x00, 0x19, 0x89, 0x00, 0x00, 0x16, 0x86,
0x8a, 0x00, 0x10, 0x8b, 0x00, 0x00, 0x8c, 0x00,
0x07, 0x8e, 0x16, 0x80, 0x8f, 0x12, 0xc0, 0x90,
0x0e, 0x10, 0x91, 0x0c, 0xda, 0x92, 0x00, 0x05,
0x93, 0x0b, 0xb8, 0x94, 0x0c, 0x80, 0x95, 0x00,
0x05, 0x96, 0x01, 0x2c, 0x97, 0x00, 0x28, 0x98,
0x01, 0x2c, 0x99, 0x00, 0x28, 0x9a, 0x00, 0x1e,
0x9b, 0x0b, 0xb8, 0x9c, 0x00, 0x0a, 0x9d, 0x01,
0x9e, 0x00, 0x64, 0x9f, 0x00, 0x50, 0xa0, 0x00,
0x64, 0xa1, 0x00, 0x64, 0xa2, 0x00, 0x14, 0xa3,
0x00, 0x46, 0xa4, 0x00, 0x46, 0xa5, 0x00, 0x00,
0xa6, 0x00, 0x02, 0xa7, 0xff, 0xec, 0xa8, 0xff,
0xf6, 0xa9, 0x10, 0xaa, 0x00, 0x00, 0x00, 0xe6,
0xab, 0x01, 0xac, 0x01, 0xad, 0x04, 0x4d, 0xae,
0x01, 0xaf, 0x00, 0xb0, 0x00, 0x0a, 0xb1, 0x14,
0xb2, 0x32, 0x32, 0x31, 0x31, 0x38, 0x37, 0x00,
0x00, 0x00, 0x00, 0xb3, 0x00, 0xb4, 0x62, 0x65,
0x6b, 0x69, 0x00, 0x00, 0x00, 0x00, 0xb5, 0x32,
0x33, 0x30, 0x36, 0xb6, 0x00, 0x01, 0x7f, 0x2a,
0xb7, 0x31, 0x31, 0x2e, 0x58, 0x57, 0x5f, 0x53,
0x31, 0x31, 0x2e, 0x32, 0x36, 0x32, 0x48, 0x5f,
0xb8, 0x00, 0xb9, 0x00, 0x00, 0x00, 0xe6, 0xba,
0x62, 0x65, 0x6b, 0x69, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x4a, 0x4b, 0x5f, 0x42,
0x31, 0x41, 0x32, 0x34, 0x53, 0x31, 0x35, 0x50,
0xc0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00,
0x00, 0x4f, 0xc1
},
{
0x4e, 0x57, 0x01, 0x21, 0x00, 0x00, 0x00, 0x00,
0x06, 0x00, 0x01, 0x79, 0x30, 0x01, 0x0c, 0x13,
0x02, 0x0c, 0x12, 0x03, 0x0c, 0x0f, 0x04, 0x0c,
0x15, 0x05, 0x0c, 0x0d, 0x06, 0x0c, 0x13, 0x07,
0x0c, 0x16, 0x08, 0x0c, 0x13, 0x09, 0x0b, 0xdb,
0x0a, 0x0b, 0xf6, 0x0b, 0x0c, 0x17, 0x0c, 0x0b,
0xf5, 0x0d, 0x0c, 0x16, 0x0e, 0x0c, 0x1a, 0x0f,
0x0c, 0x1b, 0x10, 0x0c, 0x1c, 0x80, 0x00, 0x18,
0x81, 0x00, 0x18, 0x82, 0x00, 0x18, 0x83, 0x13,
0x49, 0x84, 0x00, 0x00, 0x85, 0x00, 0x86, 0x02,
0x87, 0x00, 0x23, 0x89, 0x00, 0x00, 0x20, 0x14,
0x8a, 0x00, 0x10, 0x8b, 0x00, 0x08, 0x8c, 0x00,
0x05, 0x8e, 0x16, 0x80, 0x8f, 0x12, 0xc0, 0x90,
0x0e, 0x10, 0x91, 0x0c, 0xda, 0x92, 0x00, 0x05,
0x93, 0x0b, 0xb8, 0x94, 0x0c, 0x80, 0x95, 0x00,
0x05, 0x96, 0x01, 0x2c, 0x97, 0x00, 0x28, 0x98,
0x01, 0x2c, 0x99, 0x00, 0x28, 0x9a, 0x00, 0x1e,
0x9b, 0x0b, 0xb8, 0x9c, 0x00, 0x0a, 0x9d, 0x01,
0x9e, 0x00, 0x64, 0x9f, 0x00, 0x50, 0xa0, 0x00,
0x64, 0xa1, 0x00, 0x64, 0xa2, 0x00, 0x14, 0xa3,
0x00, 0x46, 0xa4, 0x00, 0x46, 0xa5, 0x00, 0x00,
0xa6, 0x00, 0x02, 0xa7, 0xff, 0xec, 0xa8, 0xff,
0xf6, 0xa9, 0x10, 0xaa, 0x00, 0x00, 0x00, 0xe6,
0xab, 0x01, 0xac, 0x01, 0xad, 0x04, 0x4d, 0xae,
0x01, 0xaf, 0x00, 0xb0, 0x00, 0x0a, 0xb1, 0x14,
0xb2, 0x32, 0x32, 0x31, 0x31, 0x38, 0x37, 0x00,
0x00, 0x00, 0x00, 0xb3, 0x00, 0xb4, 0x62, 0x65,
0x6b, 0x69, 0x00, 0x00, 0x00, 0x00, 0xb5, 0x32,
0x33, 0x30, 0x36, 0xb6, 0x00, 0x02, 0x17, 0x10,
0xb7, 0x31, 0x31, 0x2e, 0x58, 0x57, 0x5f, 0x53,
0x31, 0x31, 0x2e, 0x32, 0x36, 0x32, 0x48, 0x5f,
0xb8, 0x00, 0xb9, 0x00, 0x00, 0x00, 0xe6, 0xba,
0x62, 0x65, 0x6b, 0x69, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x4a, 0x4b, 0x5f, 0x42,
0x31, 0x41, 0x32, 0x34, 0x53, 0x31, 0x35, 0x50,
0xc0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00,
0x00, 0x45, 0xce
},
{
0x4e, 0x57, 0x01, 0x21, 0x00, 0x00, 0x00, 0x00,
0x06, 0x00, 0x01, 0x79, 0x30, 0x01, 0x0c, 0x07,
0x02, 0x0c, 0x0a, 0x03, 0x0c, 0x0b, 0x04, 0x0c,
0x08, 0x05, 0x0c, 0x05, 0x06, 0x0c, 0x0b, 0x07,
0x0c, 0x07, 0x08, 0x0c, 0x0a, 0x09, 0x0c, 0x08,
0x0a, 0x0c, 0x06, 0x0b, 0x0c, 0x0a, 0x0c, 0x0c,
0x05, 0x0d, 0x0c, 0x0a, 0x0e, 0x0c, 0x0a, 0x0f,
0x0c, 0x0a, 0x10, 0x0c, 0x0a, 0x80, 0x00, 0x06,
0x81, 0x00, 0x03, 0x82, 0x00, 0x03, 0x83, 0x13,
0x40, 0x84, 0x00, 0x00, 0x85, 0x29, 0x86, 0x02,
0x87, 0x00, 0x01, 0x89, 0x00, 0x00, 0x01, 0x0a,
0x8a, 0x00, 0x10, 0x8b, 0x02, 0x00, 0x8c, 0x00,
0x02, 0x8e, 0x16, 0x80, 0x8f, 0x10, 0x40, 0x90,
0x0e, 0x10, 0x91, 0x0d, 0xde, 0x92, 0x00, 0x05,
0x93, 0x0a, 0x28, 0x94, 0x0a, 0x5a, 0x95, 0x00,
0x05, 0x96, 0x01, 0x2c, 0x97, 0x00, 0x28, 0x98,
0x01, 0x2c, 0x99, 0x00, 0x28, 0x9a, 0x00, 0x1e,
0x9b, 0x0b, 0xb8, 0x9c, 0x00, 0x0a, 0x9d, 0x01,
0x9e, 0x00, 0x5a, 0x9f, 0x00, 0x50, 0xa0, 0x00,
0x64, 0xa1, 0x00, 0x64, 0xa2, 0x00, 0x14, 0xa3,
0x00, 0x37, 0xa4, 0x00, 0x37, 0xa5, 0x00, 0x03,
0xa6, 0x00, 0x05, 0xa7, 0xff, 0xec, 0xa8, 0xff,
0xf6, 0xa9, 0x10, 0xaa, 0x00, 0x00, 0x00, 0xe6,
0xab, 0x01, 0xac, 0x01, 0xad, 0x04, 0x4d, 0xae,
0x01, 0xaf, 0x00, 0xb0, 0x00, 0x0a, 0xb1, 0x14,
0xb2, 0x32, 0x32, 0x31, 0x31, 0x38, 0x37, 0x00,
0x00, 0x00, 0x00, 0xb3, 0x00, 0xb4, 0x62, 0x65,
0x6b, 0x69, 0x00, 0x00, 0x00, 0x00, 0xb5, 0x32,
0x33, 0x30, 0x36, 0xb6, 0x00, 0x03, 0xb7, 0x2d,
0xb7, 0x31, 0x31, 0x2e, 0x58, 0x57, 0x5f, 0x53,
0x31, 0x31, 0x2e, 0x32, 0x36, 0x32, 0x48, 0x5f,
0xb8, 0x00, 0xb9, 0x00, 0x00, 0x00, 0xe6, 0xba,
0x62, 0x65, 0x6b, 0x69, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x4a, 0x4b, 0x5f, 0x42,
0x31, 0x41, 0x32, 0x34, 0x53, 0x31, 0x35, 0x50,
0xc0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00,
0x00, 0x41, 0x7b
}
};
size_t _msg_idx = 0;
size_t _byte_idx = 0;
};
} /* namespace JkBms */

View File

@ -1,93 +0,0 @@
#pragma once
#include <utility>
#include <vector>
#include <Arduino.h>
#include "JkBmsDataPoints.h"
namespace JkBms {
class SerialMessage {
public:
using tData = std::vector<uint8_t>;
SerialMessage() = delete;
enum class Command : uint8_t {
Activate = 0x01,
Write = 0x02,
Read = 0x03,
Password = 0x05,
ReadAll = 0x06
};
Command getCommand() const { return static_cast<Command>(_raw[8]); }
enum class Source : uint8_t {
BMS = 0x00,
Bluetooth = 0x01,
GPS = 0x02,
Host = 0x03
};
Source getSource() const { return static_cast<Source>(_raw[9]); }
enum class Type : uint8_t {
Command = 0x00,
Response = 0x01,
Unsolicited = 0x02
};
Type getType() const { return static_cast<Type>(_raw[10]); }
// this does *not* include the two byte start marker
uint16_t getFrameLength() const { return get<uint16_t>(_raw.cbegin()+2); }
uint32_t getTerminalId() const { return get<uint32_t>(_raw.cbegin()+4); }
// there are 20 bytes of overhead. two of those are the start marker
// bytes, which are *not* counted by the frame length.
uint16_t getVariableFieldLength() const { return getFrameLength() - 18; }
// the upper byte of the 4-byte "record number" is reserved (for encryption)
uint32_t getSequence() const { return get<uint32_t>(_raw.cend()-9) >> 8; }
bool isValid() const;
uint8_t const* data() { return _raw.data(); }
size_t size() { return _raw.size(); }
protected:
template <typename... Args>
explicit SerialMessage(Args&&... args) : _raw(std::forward<Args>(args)...) { }
template<typename T, typename It> T get(It&& pos) const;
template<typename It> bool getBool(It&& pos) const;
template<typename It> int16_t getTemperature(It&& pos) const;
template<typename It> std::string getString(It&& pos, size_t len, bool replaceZeroes = false) const;
void processBatteryCurrent(tData::const_iterator& pos, uint8_t protocolVersion);
template<typename T> void set(tData::iterator const& pos, T val);
uint16_t calcChecksum() const;
void updateChecksum();
tData _raw;
JkBms::DataPointContainer _dp;
static constexpr uint16_t startMarker = 0x4e57;
static constexpr uint8_t endMarker = 0x68;
};
class SerialResponse : public SerialMessage {
public:
using tData = SerialMessage::tData;
explicit SerialResponse(tData&& raw, uint8_t protocolVersion = -1);
DataPointContainer const& getDataPoints() const { return _dp; }
};
class SerialCommand : public SerialMessage {
public:
using Command = SerialMessage::Command;
explicit SerialCommand(Command cmd);
};
} /* namespace JkBms */

View File

@ -2,13 +2,12 @@
#pragma once #pragma once
#include <AsyncWebSocket.h> #include <AsyncWebSocket.h>
#include <HardwareSerial.h>
#include <Stream.h>
#include <TaskSchedulerDeclarations.h> #include <TaskSchedulerDeclarations.h>
#include <Print.h>
#include <freertos/task.h>
#include <mutex> #include <mutex>
#include <vector>
#include <unordered_map> #define BUFFER_SIZE 500
#include <queue>
class MessageOutputClass : public Print { class MessageOutputClass : public Print {
public: public:
@ -23,19 +22,13 @@ private:
Task _loopTask; Task _loopTask;
using message_t = std::vector<uint8_t>;
// we keep a buffer for every task and only write complete lines to the
// serial output and then move them to be pushed through the websocket.
// this way we prevent mangling of messages from different contexts.
std::unordered_map<TaskHandle_t, message_t> _task_messages;
std::queue<message_t> _lines;
AsyncWebSocket* _ws = nullptr; AsyncWebSocket* _ws = nullptr;
char _buffer[BUFFER_SIZE];
uint16_t _buff_pos = 0;
uint32_t _lastSend = 0;
bool _forceSend = false;
std::mutex _msgLock; std::mutex _msgLock;
void serialWrite(message_t const& m);
}; };
extern MessageOutputClass MessageOutput; extern MessageOutputClass MessageOutput;

View File

@ -1,32 +0,0 @@
#pragma once
#include <optional>
#include "Battery.h"
#include <espMqttClient.h>
class MqttBattery : public BatteryProvider {
public:
MqttBattery() = default;
bool init(bool verboseLogging) final;
void deinit() final;
void loop() final { return; } // this class is event-driven
std::shared_ptr<BatteryStats> getStats() const final { return _stats; }
private:
bool _verboseLogging = false;
String _socTopic;
String _voltageTopic;
String _dischargeCurrentLimitTopic;
std::shared_ptr<MqttBatteryStats> _stats = std::make_shared<MqttBatteryStats>();
void onMqttMessageSoC(espMqttClientTypes::MessageProperties const& properties,
char const* topic, uint8_t const* payload, size_t len, size_t index, size_t total,
char const* jsonPath);
void onMqttMessageVoltage(espMqttClientTypes::MessageProperties const& properties,
char const* topic, uint8_t const* payload, size_t len, size_t index, size_t total,
char const* jsonPath);
void onMqttMessageDischargeCurrentLimit(espMqttClientTypes::MessageProperties const& properties,
char const* topic, uint8_t const* payload, size_t len, size_t index, size_t total,
char const* jsonPath);
};

View File

@ -1,25 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <ArduinoJson.h>
#include <TaskSchedulerDeclarations.h>
class MqttHandleBatteryHassClass {
public:
void init(Scheduler& scheduler);
void forceUpdate() { _doPublish = true; }
private:
void loop();
void publish(const String& subtopic, const String& payload);
void publishBinarySensor(const char* caption, const char* icon, const char* subTopic, const char* payload_on, const char* payload_off);
void publishSensor(const char* caption, const char* icon, const char* subTopic, const char* deviceClass = NULL, const char* stateClass = NULL, const char* unitOfMeasurement = NULL);
void createDeviceInfo(JsonObject& object);
Task _loopTask;
bool _doPublish = true;
String serial = "0001"; // pseudo-serial, can be replaced in future with real serialnumber
};
extern MqttHandleBatteryHassClass MqttHandleBatteryHass;

View File

@ -69,9 +69,6 @@ public:
void publishConfig(); void publishConfig();
void forceUpdate(); void forceUpdate();
static String getDtuUniqueId();
static String getDtuUrl();
private: private:
void loop(); void loop();
static void publish(const String& subtopic, const String& payload); static void publish(const String& subtopic, const String& payload);
@ -98,6 +95,9 @@ private:
static void createDeviceInfo(JsonDocument& doc, const String& name, const String& identifiers, const String& configuration_url, const String& manufacturer, const String& model, const String& sw_version, const String& via_device = ""); static void createDeviceInfo(JsonDocument& doc, const String& name, const String& identifiers, const String& configuration_url, const String& manufacturer, const String& model, const String& sw_version, const String& via_device = "");
static String getDtuUniqueId();
static String getDtuUrl();
Task _loopTask; Task _loopTask;
bool _wasConnected = false; bool _wasConnected = false;

View File

@ -1,60 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "Configuration.h"
#include <Huawei_can.h>
#include <espMqttClient.h>
#include <TaskSchedulerDeclarations.h>
#include <mutex>
#include <deque>
#include <functional>
#include <frozen/map.h>
#include <frozen/string.h>
class MqttHandleHuaweiClass {
public:
void init(Scheduler& scheduler);
void forceUpdate();
void subscribeTopics();
void unsubscribeTopics();
private:
void loop();
enum class Topic : unsigned {
LimitOnlineVoltage,
LimitOnlineCurrent,
LimitOfflineVoltage,
LimitOfflineCurrent,
Mode
};
static constexpr frozen::string _cmdtopic = "huawei/cmd/";
static constexpr frozen::map<frozen::string, Topic, 5> _subscriptions = {
{ "limit_online_voltage", Topic::LimitOnlineVoltage },
{ "limit_online_current", Topic::LimitOnlineCurrent },
{ "limit_offline_voltage", Topic::LimitOfflineVoltage },
{ "limit_offline_current", Topic::LimitOfflineCurrent },
{ "mode", Topic::Mode },
};
void onMqttMessage(Topic t,
const espMqttClientTypes::MessageProperties& properties,
const char* topic, const uint8_t* payload, size_t len,
size_t index, size_t total);
Task _loopTask;
uint32_t _lastPublishStats;
uint32_t _lastPublish;
// MQTT callbacks to process updates on subscribed topics are executed in
// the MQTT thread's context. we use this queue to switch processing the
// user requests into the main loop's context (TaskScheduler context).
mutable std::mutex _mqttMutex;
std::deque<std::function<void()>> _mqttCallbacks;
};
extern MqttHandleHuaweiClass MqttHandleHuawei;

View File

@ -1,66 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "Configuration.h"
#include <espMqttClient.h>
#include <TaskSchedulerDeclarations.h>
#include <mutex>
#include <deque>
#include <functional>
#include <frozen/map.h>
#include <frozen/string.h>
class MqttHandlePowerLimiterClass {
public:
void init(Scheduler& scheduler);
void forceUpdate();
void subscribeTopics();
void unsubscribeTopics();
private:
void loop();
enum class MqttPowerLimiterCommand : unsigned {
Mode,
BatterySoCStartThreshold,
BatterySoCStopThreshold,
FullSolarPassthroughSoC,
VoltageStartThreshold,
VoltageStopThreshold,
FullSolarPassThroughStartVoltage,
FullSolarPassThroughStopVoltage,
UpperPowerLimit,
TargetPowerConsumption
};
static constexpr frozen::string _cmdtopic = "powerlimiter/cmd/";
static constexpr frozen::map<frozen::string, MqttPowerLimiterCommand, 10> _subscriptions = {
{ "threshold/soc/start", MqttPowerLimiterCommand::BatterySoCStartThreshold },
{ "threshold/soc/stop", MqttPowerLimiterCommand::BatterySoCStopThreshold },
{ "threshold/soc/full_solar_passthrough", MqttPowerLimiterCommand::FullSolarPassthroughSoC },
{ "threshold/voltage/start", MqttPowerLimiterCommand::VoltageStartThreshold },
{ "threshold/voltage/stop", MqttPowerLimiterCommand::VoltageStopThreshold },
{ "threshold/voltage/full_solar_passthrough_start", MqttPowerLimiterCommand::FullSolarPassThroughStartVoltage },
{ "threshold/voltage/full_solar_passthrough_stop", MqttPowerLimiterCommand::FullSolarPassThroughStopVoltage },
{ "mode", MqttPowerLimiterCommand::Mode },
{ "upper_power_limit", MqttPowerLimiterCommand::UpperPowerLimit },
{ "target_power_consumption", MqttPowerLimiterCommand::TargetPowerConsumption },
};
void onMqttCmd(MqttPowerLimiterCommand command, const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total);
Task _loopTask;
uint32_t _lastPublishStats;
uint32_t _lastPublish;
// MQTT callbacks to process updates on subscribed topics are executed in
// the MQTT thread's context. we use this queue to switch processing the
// user requests into the main loop's context (TaskScheduler context).
mutable std::mutex _mqttMutex;
std::deque<std::function<void()>> _mqttCallbacks;
};
extern MqttHandlePowerLimiterClass MqttHandlePowerLimiter;

View File

@ -1,27 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <ArduinoJson.h>
#include <TaskSchedulerDeclarations.h>
class MqttHandlePowerLimiterHassClass {
public:
void init(Scheduler& scheduler);
void publishConfig();
void forceUpdate();
private:
void loop();
void publish(const String& subtopic, const String& payload);
void publishNumber(const char* caption, const char* icon, const char* category, const char* commandTopic, const char* stateTopic, const char* unitOfMeasure, const int16_t min, const int16_t max, const float step);
void publishSelect(const char* caption, const char* icon, const char* category, const char* commandTopic, const char* stateTopic);
void publishBinarySensor(const char* caption, const char* icon, const char* stateTopic, const char* payload_on, const char* payload_off);
void createDeviceInfo(JsonDocument& root);
Task _loopTask;
bool _wasConnected = false;
bool _updateForced = false;
};
extern MqttHandlePowerLimiterHassClass MqttHandlePowerLimiterHass;

View File

@ -1,40 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "VeDirectMpptController.h"
#include "Configuration.h"
#include <Arduino.h>
#include <map>
#include <TaskSchedulerDeclarations.h>
#ifndef VICTRON_PIN_RX
#define VICTRON_PIN_RX 22
#endif
#ifndef VICTRON_PIN_TX
#define VICTRON_PIN_TX 21
#endif
class MqttHandleVedirectClass {
public:
void init(Scheduler& scheduler);
void forceUpdate();
private:
void loop();
std::map<std::string, VeDirectMpptController::data_t> _kvFrames;
Task _loopTask;
// point of time in millis() when updated values will be published
uint32_t _nextPublishUpdatesOnly = 0;
// point of time in millis() when all values will be published
uint32_t _nextPublishFull = 1;
bool _PublishFull;
void publish_mppt_data(const VeDirectMpptController::data_t &mpptData,
const VeDirectMpptController::data_t &frame) const;
};
extern MqttHandleVedirectClass MqttHandleVedirect;

View File

@ -1,33 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <ArduinoJson.h>
#include "VeDirectMpptController.h"
#include <TaskSchedulerDeclarations.h>
class MqttHandleVedirectHassClass {
public:
void init(Scheduler& scheduler);
void publishConfig();
void forceUpdate();
private:
void loop();
void publish(const String& subtopic, const String& payload);
void publishBinarySensor(const char *caption, const char *icon, const char *subTopic,
const char *payload_on, const char *payload_off,
const VeDirectMpptController::data_t &mpptData);
void publishSensor(const char *caption, const char *icon, const char *subTopic,
const char *deviceClass, const char *stateClass,
const char *unitOfMeasurement,
const VeDirectMpptController::data_t &mpptData);
void createDeviceInfo(JsonObject &object,
const VeDirectMpptController::data_t &mpptData);
Task _loopTask;
bool _wasConnected = false;
bool _updateForced = false;
};
extern MqttHandleVedirectHassClass MqttHandleVedirectHass;

View File

@ -11,7 +11,6 @@ class MqttSettingsClass {
public: public:
MqttSettingsClass(); MqttSettingsClass();
void init(); void init();
void loop();
void performReconnect(); void performReconnect();
bool getConnected(); bool getConnected();
void publish(const String& subtopic, const String& payload); void publish(const String& subtopic, const String& payload);
@ -21,7 +20,7 @@ public:
void unsubscribe(const String& topic); void unsubscribe(const String& topic);
String getPrefix() const; String getPrefix() const;
String getClientId(); String getClientId() const;
private: private:
void NetworkEvent(network_event event); void NetworkEvent(network_event event);
@ -39,7 +38,6 @@ private:
Ticker _mqttReconnectTimer; Ticker _mqttReconnectTimer;
MqttSubscribeParser _mqttSubscribeParser; MqttSubscribeParser _mqttSubscribeParser;
std::mutex _clientLock; std::mutex _clientLock;
bool _verboseLogging = true;
}; };
extern MqttSettingsClass MqttSettings; extern MqttSettingsClass MqttSettings;

View File

@ -51,29 +51,6 @@ struct PinMapping_t {
uint8_t display_reset; uint8_t display_reset;
int8_t led[PINMAPPING_LED_COUNT]; int8_t led[PINMAPPING_LED_COUNT];
// OpenDTU-OnBattery-specific pins below
int8_t victron_tx;
int8_t victron_rx;
int8_t victron_tx2;
int8_t victron_rx2;
int8_t victron_tx3;
int8_t victron_rx3;
int8_t battery_rx;
int8_t battery_rxen;
int8_t battery_tx;
int8_t battery_txen;
int8_t huawei_miso;
int8_t huawei_mosi;
int8_t huawei_clk;
int8_t huawei_irq;
int8_t huawei_cs;
int8_t huawei_power;
int8_t powermeter_rx;
int8_t powermeter_tx;
int8_t powermeter_dere;
int8_t powermeter_rxen;
int8_t powermeter_txen;
}; };
class PinMappingClass { class PinMappingClass {
@ -82,16 +59,19 @@ public:
bool init(const String& deviceMapping); bool init(const String& deviceMapping);
PinMapping_t& get(); PinMapping_t& get();
bool isMappingSelected() const { return _mappingSelected; }
bool isValidNrf24Config() const; bool isValidNrf24Config() const;
bool isValidCmt2300Config() const; bool isValidCmt2300Config() const;
bool isValidW5500Config() const; bool isValidW5500Config() const;
#if CONFIG_ETH_USE_ESP32_EMAC #if CONFIG_ETH_USE_ESP32_EMAC
bool isValidEthConfig() const; bool isValidEthConfig() const;
#endif #endif
bool isValidHuaweiConfig() const;
private: private:
PinMapping_t _pinMapping; PinMapping_t _pinMapping;
bool _mappingSelected = false;
}; };
extern PinMappingClass PinMapping; extern PinMappingClass PinMapping;

View File

@ -1,107 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "Configuration.h"
#include <espMqttClient.h>
#include <Arduino.h>
#include <Hoymiles.h>
#include <memory>
#include <functional>
#include <optional>
#include <TaskSchedulerDeclarations.h>
#include <frozen/string.h>
#define PL_UI_STATE_INACTIVE 0
#define PL_UI_STATE_CHARGING 1
#define PL_UI_STATE_USE_SOLAR_ONLY 2
#define PL_UI_STATE_USE_SOLAR_AND_BATTERY 3
class PowerLimiterClass {
public:
enum class Status : unsigned {
Initializing,
DisabledByConfig,
DisabledByMqtt,
WaitingForValidTimestamp,
PowerMeterPending,
InverterInvalid,
InverterChanged,
InverterOffline,
InverterCommandsDisabled,
InverterLimitPending,
InverterPowerCmdPending,
InverterDevInfoPending,
InverterStatsPending,
CalculatedLimitBelowMinLimit,
UnconditionalSolarPassthrough,
NoVeDirect,
NoEnergy,
HuaweiPsu,
Stable,
};
void init(Scheduler& scheduler);
uint8_t getInverterUpdateTimeouts() const { return _inverterUpdateTimeouts; }
uint8_t getPowerLimiterState();
int32_t getLastRequestedPowerLimit() { return _lastRequestedPowerLimit; }
bool getFullSolarPassThroughEnabled() const { return _fullSolarPassThroughEnabled; }
enum class Mode : unsigned {
Normal = 0,
Disabled = 1,
UnconditionalFullSolarPassthrough = 2
};
void setMode(Mode m) { _mode = m; }
Mode getMode() const { return _mode; }
void calcNextInverterRestart();
private:
void loop();
Task _loopTask;
int32_t _lastRequestedPowerLimit = 0;
bool _shutdownPending = false;
std::optional<uint32_t> _oInverterStatsMillis = std::nullopt;
std::optional<uint32_t> _oUpdateStartMillis = std::nullopt;
std::optional<int32_t> _oTargetPowerLimitWatts = std::nullopt;
std::optional<bool> _oTargetPowerState = std::nullopt;
Status _lastStatus = Status::Initializing;
uint32_t _lastStatusPrinted = 0;
uint32_t _lastCalculation = 0;
static constexpr uint32_t _calculationBackoffMsDefault = 128;
uint32_t _calculationBackoffMs = _calculationBackoffMsDefault;
Mode _mode = Mode::Normal;
std::shared_ptr<InverterAbstract> _inverter = nullptr;
bool _batteryDischargeEnabled = false;
bool _nighttimeDischarging = false;
uint32_t _nextInverterRestart = 0; // Values: 0->not calculated / 1->no restart configured / >1->time of next inverter restart in millis()
uint32_t _nextCalculateCheck = 5000; // time in millis for next NTP check to calulate restart
bool _fullSolarPassThroughEnabled = false;
bool _verboseLogging = true;
uint8_t _inverterUpdateTimeouts = 0;
frozen::string const& getStatusText(Status status);
void announceStatus(Status status);
bool shutdown(Status status);
bool shutdown() { return shutdown(_lastStatus); }
float getBatteryVoltage(bool log = false);
int32_t inverterPowerDcToAc(std::shared_ptr<InverterAbstract> inverter, int32_t dcPower);
void unconditionalSolarPassthrough(std::shared_ptr<InverterAbstract> inverter);
bool canUseDirectSolarPower();
bool calcPowerLimit(std::shared_ptr<InverterAbstract> inverter, int32_t solarPower, int32_t batteryPowerLimit, bool batteryPower);
bool updateInverter();
bool setNewPowerLimit(std::shared_ptr<InverterAbstract> inverter, int32_t newPowerLimit);
int32_t getSolarPower();
int32_t getBatteryDischargeLimit();
float getLoadCorrectedVoltage();
bool testThreshold(float socThreshold, float voltThreshold,
std::function<bool(float, float)> compare);
bool isStartThresholdReached();
bool isStopThresholdReached();
bool isBelowStopThreshold();
bool useFullSolarPassthrough();
};
extern PowerLimiterClass PowerLimiter;

View File

@ -1,27 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "PowerMeterProvider.h"
#include <TaskSchedulerDeclarations.h>
#include <memory>
#include <mutex>
class PowerMeterClass {
public:
void init(Scheduler& scheduler);
void updateSettings();
float getPowerTotal() const;
uint32_t getLastUpdate() const;
bool isDataValid() const;
private:
void loop();
Task _loopTask;
mutable std::mutex _mutex;
std::unique_ptr<PowerMeterProvider> _upProvider = nullptr;
};
extern PowerMeterClass PowerMeter;

View File

@ -1,53 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
#include <array>
#include <variant>
#include <memory>
#include <condition_variable>
#include <mutex>
#include <stdint.h>
#include "HttpGetter.h"
#include "Configuration.h"
#include "PowerMeterProvider.h"
using Auth_t = HttpRequestConfig::Auth;
using Unit_t = PowerMeterHttpJsonValue::Unit;
class PowerMeterHttpJson : public PowerMeterProvider {
public:
explicit PowerMeterHttpJson(PowerMeterHttpJsonConfig const& cfg)
: _cfg(cfg) { }
~PowerMeterHttpJson();
bool init() final;
void loop() final;
float getPowerTotal() const final;
bool isDataValid() const final;
void doMqttPublish() const final;
using power_values_t = std::array<float, POWERMETER_HTTP_JSON_MAX_VALUES>;
using poll_result_t = std::variant<power_values_t, String>;
poll_result_t poll();
private:
static void pollingLoopHelper(void* context);
std::atomic<bool> _taskDone;
void pollingLoop();
PowerMeterHttpJsonConfig const _cfg;
uint32_t _lastPoll = 0;
mutable std::mutex _valueMutex;
power_values_t _powerValues = {};
std::array<std::unique_ptr<HttpGetter>, POWERMETER_HTTP_JSON_MAX_VALUES> _httpGetters;
TaskHandle_t _taskHandle = nullptr;
bool _stopPolling;
mutable std::mutex _pollingMutex;
std::condition_variable _cv;
};

View File

@ -1,45 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
#include <memory>
#include <condition_variable>
#include <mutex>
#include <stdint.h>
#include <Arduino.h>
#include "HttpGetter.h"
#include "Configuration.h"
#include "PowerMeterSml.h"
class PowerMeterHttpSml : public PowerMeterSml {
public:
explicit PowerMeterHttpSml(PowerMeterHttpSmlConfig const& cfg)
: PowerMeterSml("PowerMeterHttpSml")
, _cfg(cfg) { }
~PowerMeterHttpSml();
bool init() final;
void loop() final;
bool isDataValid() const final;
// returns an empty string on success,
// returns an error message otherwise.
String poll();
private:
static void pollingLoopHelper(void* context);
std::atomic<bool> _taskDone;
void pollingLoop();
PowerMeterHttpSmlConfig const _cfg;
uint32_t _lastPoll = 0;
std::unique_ptr<HttpGetter> _upHttpGetter;
TaskHandle_t _taskHandle = nullptr;
bool _stopPolling;
mutable std::mutex _pollingMutex;
std::condition_variable _cv;
};

View File

@ -1,39 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "Configuration.h"
#include "PowerMeterProvider.h"
#include <espMqttClient.h>
#include <vector>
#include <mutex>
#include <array>
class PowerMeterMqtt : public PowerMeterProvider {
public:
explicit PowerMeterMqtt(PowerMeterMqttConfig const& cfg)
: _cfg(cfg) { }
~PowerMeterMqtt();
bool init() final;
void loop() final { }
float getPowerTotal() const final;
private:
using MsgProperties = espMqttClientTypes::MessageProperties;
void onMessage(MsgProperties const& properties, char const* topic,
uint8_t const* payload, size_t len, size_t index,
size_t total, float* targetVariable, PowerMeterMqttValue const* cfg);
// we don't need to republish data received from MQTT
void doMqttPublish() const final { };
PowerMeterMqttConfig const _cfg;
using power_values_t = std::array<float, POWERMETER_MQTT_MAX_VALUES>;
power_values_t _powerValues;
std::vector<String> _mqttSubscriptions;
mutable std::mutex _mutex;
};

View File

@ -1,51 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
#include "Configuration.h"
class PowerMeterProvider {
public:
virtual ~PowerMeterProvider() { }
enum class Type : unsigned {
MQTT = 0,
SDM1PH = 1,
SDM3PH = 2,
HTTP_JSON = 3,
SERIAL_SML = 4,
SMAHM2 = 5,
HTTP_SML = 6
};
// returns true if the provider is ready for use, false otherwise
virtual bool init() = 0;
virtual void loop() = 0;
virtual float getPowerTotal() const = 0;
virtual bool isDataValid() const;
uint32_t getLastUpdate() const { return _lastUpdate; }
void mqttLoop() const;
protected:
PowerMeterProvider() {
auto const& config = Configuration.get();
_verboseLogging = config.PowerMeter.VerboseLogging;
}
void gotUpdate() { _lastUpdate = millis(); }
void mqttPublish(String const& topic, float const& value) const;
bool _verboseLogging;
private:
virtual void doMqttPublish() const = 0;
// gotUpdate() updates this variable potentially from a different thread
// than users that request to read this variable through getLastUpdate().
std::atomic<uint32_t> _lastUpdate = 0;
mutable uint32_t _lastMqttPublish = 0;
};

View File

@ -1,60 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
#include <mutex>
#include <condition_variable>
#include <SoftwareSerial.h>
#include "Configuration.h"
#include "PowerMeterProvider.h"
#include "SDM.h"
class PowerMeterSerialSdm : public PowerMeterProvider {
public:
enum class Phases {
One,
Three
};
PowerMeterSerialSdm(Phases phases, PowerMeterSerialSdmConfig const& cfg)
: _phases(phases)
, _cfg(cfg) { }
~PowerMeterSerialSdm();
bool init() final;
void loop() final;
float getPowerTotal() const final;
bool isDataValid() const final;
void doMqttPublish() const final;
private:
static void pollingLoopHelper(void* context);
bool readValue(std::unique_lock<std::mutex>& lock, uint16_t reg, float& targetVar);
std::atomic<bool> _taskDone;
void pollingLoop();
Phases _phases;
PowerMeterSerialSdmConfig const _cfg;
uint32_t _lastPoll = 0;
float _phase1Power = 0.0;
float _phase2Power = 0.0;
float _phase3Power = 0.0;
float _phase1Voltage = 0.0;
float _phase2Voltage = 0.0;
float _phase3Voltage = 0.0;
float _energyImport = 0.0;
float _energyExport = 0.0;
mutable std::mutex _valueMutex;
std::unique_ptr<SoftwareSerial> _upSdmSerial = nullptr;
std::unique_ptr<SDM> _upSdm = nullptr;
TaskHandle_t _taskHandle = nullptr;
bool _stopPolling;
mutable std::mutex _pollingMutex;
std::condition_variable _cv;
};

View File

@ -1,44 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "PowerMeterSml.h"
#include <SoftwareSerial.h>
class PowerMeterSerialSml : public PowerMeterSml {
public:
PowerMeterSerialSml()
: PowerMeterSml("PowerMeterSerialSml") { }
~PowerMeterSerialSml();
bool init() final;
void loop() final;
private:
// we assume that an SML datagram is complete after no additional
// characters were received for this many milliseconds.
static uint8_t constexpr _datagramGapMillis = 50;
static uint32_t constexpr _baud = 9600;
// size in bytes of the software serial receive buffer. must have the
// capacity to hold a full SML datagram, as we are processing the datagrams
// only after all data of one datagram was received.
static int constexpr _bufCapacity = 1024; // memory usage: 1 byte each
// amount of bits (RX pin state transitions) the software serial can buffer
// without decoding bits to bytes and storing those in the receive buffer.
// this value dictates how ofter we need to call a function of the software
// serial instance that performs bit decoding (we call available()).
static int constexpr _isrCapacity = 256; // memory usage: 8 bytes each (timestamp + pointer)
static void pollingLoopHelper(void* context);
std::atomic<bool> _taskDone;
void pollingLoop();
TaskHandle_t _taskHandle = nullptr;
bool _stopPolling;
mutable std::mutex _pollingMutex;
std::unique_ptr<SoftwareSerial> _upSmlSerial = nullptr;
};

View File

@ -1,69 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <list>
#include <mutex>
#include <optional>
#include <stdint.h>
#include <Arduino.h>
#include <HTTPClient.h>
#include "Configuration.h"
#include "PowerMeterProvider.h"
#include "sml.h"
class PowerMeterSml : public PowerMeterProvider {
public:
float getPowerTotal() const final;
void doMqttPublish() const final;
protected:
explicit PowerMeterSml(char const* user)
: _user(user) { }
void reset();
void processSmlByte(uint8_t byte);
private:
std::string _user;
mutable std::mutex _mutex;
using values_t = struct {
std::optional<float> activePowerTotal = std::nullopt;
std::optional<float> activePowerL1 = std::nullopt;
std::optional<float> activePowerL2 = std::nullopt;
std::optional<float> activePowerL3 = std::nullopt;
std::optional<float> voltageL1 = std::nullopt;
std::optional<float> voltageL2 = std::nullopt;
std::optional<float> voltageL3 = std::nullopt;
std::optional<float> currentL1 = std::nullopt;
std::optional<float> currentL2 = std::nullopt;
std::optional<float> currentL3 = std::nullopt;
std::optional<float> energyImport = std::nullopt;
std::optional<float> energyExport = std::nullopt;
};
values_t _values;
values_t _cache;
using OBISHandler = struct {
uint8_t const OBIS[6];
void (*decoder)(float&);
std::optional<float>* target;
char const* name;
};
const std::list<OBISHandler> smlHandlerList{
{{0x01, 0x00, 0x10, 0x07, 0x00, 0xff}, &smlOBISW, &_cache.activePowerTotal, "active power total"},
{{0x01, 0x00, 0x24, 0x07, 0x00, 0xff}, &smlOBISW, &_cache.activePowerL1, "active power L1"},
{{0x01, 0x00, 0x38, 0x07, 0x00, 0xff}, &smlOBISW, &_cache.activePowerL2, "active power L2"},
{{0x01, 0x00, 0x4c, 0x07, 0x00, 0xff}, &smlOBISW, &_cache.activePowerL3, "active power L3"},
{{0x01, 0x00, 0x20, 0x07, 0x00, 0xff}, &smlOBISVolt, &_cache.voltageL1, "voltage L1"},
{{0x01, 0x00, 0x34, 0x07, 0x00, 0xff}, &smlOBISVolt, &_cache.voltageL2, "voltage L2"},
{{0x01, 0x00, 0x48, 0x07, 0x00, 0xff}, &smlOBISVolt, &_cache.voltageL3, "voltage L3"},
{{0x01, 0x00, 0x1f, 0x07, 0x00, 0xff}, &smlOBISAmpere, &_cache.currentL1, "current L1"},
{{0x01, 0x00, 0x33, 0x07, 0x00, 0xff}, &smlOBISAmpere, &_cache.currentL2, "current L2"},
{{0x01, 0x00, 0x47, 0x07, 0x00, 0xff}, &smlOBISAmpere, &_cache.currentL3, "current L3"},
{{0x01, 0x00, 0x01, 0x08, 0x00, 0xff}, &smlOBISWh, &_cache.energyImport, "energy import"},
{{0x01, 0x00, 0x02, 0x08, 0x00, 0xff}, &smlOBISWh, &_cache.energyExport, "energy export"}
};
};

View File

@ -1,31 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2024 Holger-Steffen Stapf
*/
#pragma once
#include <cstdint>
#include "PowerMeterProvider.h"
class PowerMeterUdpSmaHomeManager : public PowerMeterProvider {
public:
~PowerMeterUdpSmaHomeManager();
bool init() final;
void loop() final;
float getPowerTotal() const final { return _powerMeterPower; }
void doMqttPublish() const final;
private:
void Soutput(int kanal, int index, int art, int tarif,
char const* name, float value, uint32_t timestamp);
uint8_t* decodeGroup(uint8_t* offset, uint16_t grouplen);
float _powerMeterPower = 0.0;
float _powerMeterL1 = 0.0;
float _powerMeterL2 = 0.0;
float _powerMeterL3 = 0.0;
uint32_t _previousMillis = 0;
uint32_t _serial = 0;
};

View File

@ -1,22 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "Configuration.h"
#include "Battery.h"
#include "BatteryCanReceiver.h"
#include <driver/twai.h>
#include <Arduino.h>
class PylontechCanReceiver : public BatteryCanReceiver {
public:
bool init(bool verboseLogging) final;
void onMessage(twai_message_t rx_message) final;
std::shared_ptr<BatteryStats> getStats() const final { return _stats; }
private:
void dummyData();
std::shared_ptr<PylontechBatteryStats> _stats =
std::make_shared<PylontechBatteryStats>();
};

View File

@ -1,19 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "Configuration.h"
#include "Battery.h"
#include "BatteryCanReceiver.h"
#include <driver/twai.h>
class PytesCanReceiver : public BatteryCanReceiver {
public:
bool init(bool verboseLogging) final;
void onMessage(twai_message_t rx_message) final;
std::shared_ptr<BatteryStats> getStats() const final { return _stats; }
private:
std::shared_ptr<PytesBatteryStats> _stats =
std::make_shared<PytesBatteryStats>();
};

View File

@ -1,19 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "BatteryCanReceiver.h"
#include <driver/twai.h>
#include <Arduino.h>
class SBSCanReceiver : public BatteryCanReceiver {
public:
bool init(bool verboseLogging) final;
void onMessage(twai_message_t rx_message) final;
std::shared_ptr<BatteryStats> getStats() const final { return _stats; }
private:
void dummyData();
std::shared_ptr<SBSBatteryStats> _stats =
std::make_shared<SBSBatteryStats>();
};

View File

@ -1,21 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <optional>
#include <string>
class SerialPortManagerClass {
public:
void init();
std::optional<uint8_t> allocatePort(std::string const& owner);
void freePort(std::string const& owner);
private:
// the amount of hardare UARTs available on supported ESP32 chips
static size_t constexpr _num_controllers = 3;
std::array<std::string, _num_controllers> _ports = { "" };
};
extern SerialPortManagerClass SerialPortManager;

View File

@ -1,34 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <WiFiUdp.h>
#include <TaskSchedulerDeclarations.h>
#include <mutex>
class SyslogLogger {
public:
SyslogLogger();
void init(Scheduler& scheduler);
void updateSettings(const String&& hostname);
void write(const uint8_t *buffer, size_t size);
private:
void loop();
void disable();
void enable();
bool resolveAndStart();
bool isResolved() const {
return _address != INADDR_NONE;
}
Task _loopTask;
std::mutex _mutex;
WiFiUDP _udp;
IPAddress _address;
String _syslog_hostname;
String _proc_id;
String _header;
uint16_t _port;
bool _enabled;
};
extern SyslogLogger Syslog;

View File

@ -2,8 +2,8 @@
#pragma once #pragma once
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <LittleFS.h>
#include <cstdint> #include <cstdint>
#include <utility>
class Utils { class Utils {
public: public:
@ -12,12 +12,6 @@ public:
static int getTimezoneOffset(); static int getTimezoneOffset();
static bool checkJsonAlloc(const JsonDocument& doc, const char* function, const uint16_t line); static bool checkJsonAlloc(const JsonDocument& doc, const char* function, const uint16_t line);
static void removeAllFiles(); static void removeAllFiles();
static String generateMd5FromFile(String file);
/* OpenDTU-OnBatter-specific utils go here: */ static void skipBom(File& f);
template<typename T>
static std::pair<T, String> getJsonValueByPath(JsonDocument const& root, String const& path);
template <typename T>
static std::optional<T> getNumericValueFromMqttPayload(char const* client,
std::string const& src, char const* topic, char const* jsonPath);
}; };

View File

@ -1,63 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <mutex>
#include <memory>
#include "VeDirectMpptController.h"
#include "Configuration.h"
#include <TaskSchedulerDeclarations.h>
class VictronMpptClass {
public:
VictronMpptClass() = default;
~VictronMpptClass() = default;
void init(Scheduler& scheduler);
void updateSettings();
bool isDataValid() const;
bool isDataValid(size_t idx) const;
// returns the data age of all controllers,
// i.e, the youngest data's age is returned.
uint32_t getDataAgeMillis() const;
uint32_t getDataAgeMillis(size_t idx) const;
size_t controllerAmount() const { return _controllers.size(); }
std::optional<VeDirectMpptController::data_t> getData(size_t idx = 0) const;
// total output of all MPPT charge controllers in Watts
int32_t getPowerOutputWatts() const;
// total panel input power of all MPPT charge controllers in Watts
int32_t getPanelPowerWatts() const;
// sum of total yield of all MPPT charge controllers in kWh
float getYieldTotal() const;
// sum of today's yield of all MPPT charge controllers in kWh
float getYieldDay() const;
// minimum of all MPPT charge controllers' output voltages in V
float getOutputVoltage() const;
private:
void loop();
VictronMpptClass(VictronMpptClass const& other) = delete;
VictronMpptClass(VictronMpptClass&& other) = delete;
VictronMpptClass& operator=(VictronMpptClass const& other) = delete;
VictronMpptClass& operator=(VictronMpptClass&& other) = delete;
Task _loopTask;
mutable std::mutex _mutex;
using controller_t = std::unique_ptr<VeDirectMpptController>;
std::vector<controller_t> _controllers;
std::vector<String> _serialPortOwners;
bool initController(int8_t rx, int8_t tx, bool logging,
uint8_t instance);
};
extern VictronMpptClass VictronMppt;

View File

@ -1,19 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "Battery.h"
class VictronSmartShunt : public BatteryProvider {
public:
bool init(bool verboseLogging) final;
void deinit() final;
void loop() final;
std::shared_ptr<BatteryStats> getStats() const final { return _stats; }
private:
static char constexpr _serialPortOwner[] = "SmartShunt";
uint32_t _lastUpdate = 0;
std::shared_ptr<VictronSmartShuntStats> _stats =
std::make_shared<VictronSmartShuntStats>();
};

View File

@ -1,15 +1,15 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#pragma once #pragma once
#include "WebApi_battery.h"
#include "WebApi_config.h"
#include "WebApi_device.h" #include "WebApi_device.h"
#include "WebApi_devinfo.h" #include "WebApi_devinfo.h"
#include "WebApi_dtu.h" #include "WebApi_dtu.h"
#include "WebApi_errors.h" #include "WebApi_errors.h"
#include "WebApi_eventlog.h" #include "WebApi_eventlog.h"
#include "WebApi_file.h"
#include "WebApi_firmware.h" #include "WebApi_firmware.h"
#include "WebApi_gridprofile.h" #include "WebApi_gridprofile.h"
#include "WebApi_i18n.h"
#include "WebApi_inverter.h" #include "WebApi_inverter.h"
#include "WebApi_limit.h" #include "WebApi_limit.h"
#include "WebApi_maintenance.h" #include "WebApi_maintenance.h"
@ -17,8 +17,6 @@
#include "WebApi_network.h" #include "WebApi_network.h"
#include "WebApi_ntp.h" #include "WebApi_ntp.h"
#include "WebApi_power.h" #include "WebApi_power.h"
#include "WebApi_powermeter.h"
#include "WebApi_powerlimiter.h"
#include "WebApi_prometheus.h" #include "WebApi_prometheus.h"
#include "WebApi_security.h" #include "WebApi_security.h"
#include "WebApi_sysstatus.h" #include "WebApi_sysstatus.h"
@ -26,11 +24,6 @@
#include "WebApi_ws_console.h" #include "WebApi_ws_console.h"
#include "WebApi_ws_live.h" #include "WebApi_ws_live.h"
#include <AsyncJson.h> #include <AsyncJson.h>
#include "WebApi_ws_vedirect_live.h"
#include "WebApi_vedirect.h"
#include "WebApi_ws_Huawei.h"
#include "WebApi_Huawei.h"
#include "WebApi_ws_battery.h"
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h> #include <TaskSchedulerDeclarations.h>
@ -54,14 +47,14 @@ public:
private: private:
AsyncWebServer _server; AsyncWebServer _server;
WebApiBatteryClass _webApiBattery;
WebApiConfigClass _webApiConfig;
WebApiDeviceClass _webApiDevice; WebApiDeviceClass _webApiDevice;
WebApiDevInfoClass _webApiDevInfo; WebApiDevInfoClass _webApiDevInfo;
WebApiDtuClass _webApiDtu; WebApiDtuClass _webApiDtu;
WebApiEventlogClass _webApiEventlog; WebApiEventlogClass _webApiEventlog;
WebApiFileClass _webApiFile;
WebApiFirmwareClass _webApiFirmware; WebApiFirmwareClass _webApiFirmware;
WebApiGridProfileClass _webApiGridprofile; WebApiGridProfileClass _webApiGridprofile;
WebApiI18nClass _webApiI18n;
WebApiInverterClass _webApiInverter; WebApiInverterClass _webApiInverter;
WebApiLimitClass _webApiLimit; WebApiLimitClass _webApiLimit;
WebApiMaintenanceClass _webApiMaintenance; WebApiMaintenanceClass _webApiMaintenance;
@ -69,19 +62,12 @@ private:
WebApiNetworkClass _webApiNetwork; WebApiNetworkClass _webApiNetwork;
WebApiNtpClass _webApiNtp; WebApiNtpClass _webApiNtp;
WebApiPowerClass _webApiPower; WebApiPowerClass _webApiPower;
WebApiPowerMeterClass _webApiPowerMeter;
WebApiPowerLimiterClass _webApiPowerLimiter;
WebApiPrometheusClass _webApiPrometheus; WebApiPrometheusClass _webApiPrometheus;
WebApiSecurityClass _webApiSecurity; WebApiSecurityClass _webApiSecurity;
WebApiSysstatusClass _webApiSysstatus; WebApiSysstatusClass _webApiSysstatus;
WebApiWebappClass _webApiWebapp; WebApiWebappClass _webApiWebapp;
WebApiWsConsoleClass _webApiWsConsole; WebApiWsConsoleClass _webApiWsConsole;
WebApiWsLiveClass _webApiWsLive; WebApiWsLiveClass _webApiWsLive;
WebApiWsVedirectLiveClass _webApiWsVedirectLive;
WebApiVedirectClass _webApiVedirect;
WebApiHuaweiClass _webApiHuaweiClass;
WebApiWsHuaweiLiveClass _webApiWsHuaweiLive;
WebApiWsBatteryLiveClass _webApiWsBatteryLive;
}; };
extern WebApiClass WebApi; extern WebApiClass WebApi;

View File

@ -1,19 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
#include <AsyncJson.h>
class WebApiHuaweiClass {
public:
void init(AsyncWebServer& server, Scheduler& scheduler);
void getJsonData(JsonVariant& root);
private:
void onStatus(AsyncWebServerRequest* request);
void onAdminGet(AsyncWebServerRequest* request);
void onAdminPost(AsyncWebServerRequest* request);
void onPost(AsyncWebServerRequest* request);
AsyncWebServer* _server;
};

View File

@ -1,17 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
class WebApiBatteryClass {
public:
void init(AsyncWebServer& server, Scheduler& scheduler);
private:
void onStatus(AsyncWebServerRequest* request);
void onAdminGet(AsyncWebServerRequest* request);
void onAdminPost(AsyncWebServerRequest* request);
AsyncWebServer* _server;
};

View File

@ -1,17 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
class WebApiConfigClass {
public:
void init(AsyncWebServer& server, Scheduler& scheduler);
private:
void onConfigGet(AsyncWebServerRequest* request);
void onConfigDelete(AsyncWebServerRequest* request);
void onConfigListGet(AsyncWebServerRequest* request);
void onConfigUploadFinish(AsyncWebServerRequest* request);
void onConfigUpload(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final);
};

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