Compare commits

..

270 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
Thomas Basler
dc5eb96f50 webapp: add app.js.gz 2024-10-15 18:22:57 +02:00
Thomas Basler
507e86d3b6 Upgrade ESPAsyncWebServer from 3.3.15 to 3.3.16 2024-10-15 18:12:41 +02:00
Thomas Basler
1900d78122 webapp: Update dependencies 2024-10-15 18:11:31 +02:00
Thomas Basler
b2522961cd Upgrade olikraus/U8g2 from 2.35.30 to 2.36.2 2024-10-14 19:46:17 +02:00
Thomas Basler
499f872641 Upgrade ESPAsyncWebServer from 3.3.14 to 3.3.15 2024-10-14 19:42:33 +02:00
Thomas Basler
fcf401d20a Upgrade ESPAsyncWebServer from 3.3.13 to 3.3.14 2024-10-13 12:33:12 +02:00
Thomas Basler
0468ccc34a webapp: Update dependencies 2024-10-12 21:38:07 +02:00
Thomas Basler
f8ad1acca9 Fix: Correct output of wifi disconnect reason code 2024-10-12 21:22:02 +02:00
Thomas Basler
36f0ed9ff8 Upgrade ESPAsyncWebServer from 3.3.12 to 3.3.13 2024-10-12 21:21:39 +02:00
Thomas Basler
ecef32a58e Merge branch 'pr2344' into dev 2024-10-12 13:13:24 +02:00
LennartF22
9ee947e9b0 Hotfix to not use DMA on SPI3 of ESP32-S2
See issue #2343.
2024-10-10 02:06:51 +02:00
Thomas Basler
e533320d92 Merge branch 'pr2115' into dev 2024-10-09 18:38:16 +02:00
Thomas Basler
4e293d4b93 Merge branch 'pr2340' into dev 2024-10-09 18:37:06 +02:00
Bernhard Kirchen
096a1ba3a0 Feature: show task details in system info view
shows whether or not known tasks are alive, and in particular shows how
much of the respective stack is still available.
2024-10-09 18:31:06 +02:00
LennartF22
6297ae3428 Don't set TX timeout to 0 anymore for HW/USB CDC
Due to a change in the Espressif Arduino core, the TX timeout for the HW CDC
(used in the ESP32-S3, for example) must not be set to 0, as otherwise, an
integer underflow occurs.

Removing the TX timeout is not necessary anymore anyways, because it is now
detected when CDC is not active, and attempts to write will return immediately
until the host read something again. Only when the transmit buffer becomes
full initially, the default timeout of just 100ms takes effect once.

For USB CDC (used with the ESP32-S2, for example), the timeout is not relevant
either.
2024-10-09 02:36:37 +02:00
Thomas Basler
e3b66f7ffe webapp: Update dependencies 2024-10-08 18:15:03 +02:00
Bernhard Kirchen
da9fb13079 webapp: pin assignment: hide unsupported pins
if the pin_mapping.json includes unsupported pins, e.g., `eth` pins on
an ESP32-S3, the whole category should still be hidden in the device
manager.
2024-10-06 22:37:05 +02:00
Thomas Basler
b7f830f64e webapp: add app.js.gz 2024-10-06 18:43:06 +02:00
Thomas Basler
90ea73b2ba Upgrade ESPAsyncWebServer from 3.3.11 to 3.3.12 2024-10-06 18:40:36 +02:00
Thomas Basler
eaa2f07cf3 Merge branch 'pr2333' into dev 2024-10-06 11:46:00 +02:00
Thomas Basler
b5ca2cfd21 Fix: "Equal brightness" in LED settings does not work correctly
fixes: #2332
2024-10-06 11:39:09 +02:00
Thomas Basler
2659204d96 Initialize the last rssi value with -127 instead of 0 to indicate a non existing connection of no data was received yet 2024-10-06 11:08:10 +02:00
LennartF22
6d048ae01d Remove EMAC related code for devices that don't have one 2024-10-06 03:08:58 +02:00
CommanderRedYT
d3d96b51ce
webapp: Fix eslint issues 2024-10-05 23:33:23 +02:00
Thomas Basler
4cd5d79c73 webapp: add app.js.gz 2024-10-05 22:14:14 +02:00
Thomas Basler
2c10e2510b webapp: Update dependencies 2024-10-05 22:12:49 +02:00
Thomas Basler
8f4b89a193 Replace format strings by platform independent macros 2024-10-05 00:50:13 +02:00
Thomas Basler
7dac96810f Rename NetworkEventCbList_t to DtuNetworkEventCbList_t for further upgrades 2024-10-04 23:02:12 +02:00
Thomas Basler
10b97fabb4 webapp: Update dependencies 2024-10-04 18:59:01 +02:00
Thomas Basler
d5abdc6d74 Upgrade ESPAsyncWebServer from 3.3.7 to 3.3.11 2024-10-04 18:44:23 +02:00
Thomas Basler
edfe06e31e Feature: Show RSSI of last received packet in radio stats
The value is also published via MQTT
2024-10-04 17:36:17 +02:00
janrombold
d0b2b972e2
Update UpgradePartition.md
Fixed typo
2024-10-04 00:19:26 +02:00
Thomas Basler
0c2b6f1a61 Fix: Add state_class to several Home Assistant sensors
state_class was added to yieldtotal, yieldday ac power and temperature for the whole dtu

closes: #2324
2024-10-02 18:13:12 +02:00
Thomas Basler
68793001a2 Merge branch 'pr2323' into dev 2024-10-02 11:50:43 +02:00
Thomas Basler
5040636aa2 Merge branch 'pr2322' into dev 2024-10-02 11:50:08 +02:00
mbo18
9df3e30bb2
Remove unused DEVICE_CLASS_TEMP 2024-10-02 11:02:52 +02:00
mbo18
38b5807ef7
Remove icon because device_class is set 2024-10-02 10:44:43 +02:00
Thomas Basler
2234ac9703 Upgrade ESPAsyncWebServer from 3.3.1 to 3.3.7 2024-10-02 10:32:58 +02:00
Thomas Basler
99a37fe01c webapp: Update dependencies 2024-09-30 18:47:41 +02:00
Thomas Basler
aa5087cc8a Merge branch 'pr2320' into dev 2024-09-30 16:02:58 +02:00
Bernhard Kirchen
d5d1a9982f Fix: force websocket clients to authenticate
when changing the security settings (disabling read-only access or
changing the password), existing websocket connections are now closed,
forcing the respective clients to authenticate (with the new password).
otherwise, existing websocket clients keep connected even though the
security settings now expect authentication with a (changed) password.
2024-09-30 15:54:55 +02:00
Bernhard Kirchen
ebb225f6c0 Fix: avoid deprecated setAuthentication() to fix memory exhaustion
with ESPAsyncWebServer 3.3.0, the setAuthentication() method became
deprecated and a replacement method was provided which acts as a shim
and uses the new middleware-based approach to setup authentication. in
order to eventually apply a changed "read-only access allowed" setting,
the setAuthentication() method was called periodically. the shim
implementation each time allocates a new AuthenticationMiddleware and
adds it to the chain of middlewares, eventually exhausting the memory.

we now use the new middleware-based approach ourselves and only add the
respective AuthenticatonMiddleware instance once to the respective
websocket server instance.

a regression where enabling unauthenticated read-only access is not
applied until reboot is also fixed. all the AuthenticationMiddleware
instances were never removed from the chain of middlewares when calling
setAuthentication("", "").
2024-09-30 15:16:30 +02:00
Thomas Basler
3a7295c341 Merge branch 'pr2311' into dev 2024-09-28 10:45:09 +02:00
LennartF22
69d2727106 Add device profiles for OpenDTU Fusion v2 PoE with displays 2024-09-28 02:42:31 +02:00
LennartF22
cafdb305a3 Adjust name of OpenDTU Fusion v2 PoE build environment 2024-09-28 02:37:09 +02:00
LennartF22
b05975b97c Prevent warning on GPIO ISR service registration 2024-09-28 02:26:40 +02:00
LennartF22
251bb7bd89 Add connection check for W5500 before full initialization 2024-09-28 02:26:36 +02:00
Bernhard Kirchen
6f9ded5f20 issue template: fix typo 2024-09-28 02:02:44 +02:00
Thomas Basler
b206cee820 webapp: add app.js.gz 2024-09-28 00:52:28 +02:00
Thomas Basler
759f899620 webapp: Update dependencies 2024-09-28 00:50:57 +02:00
Thomas Basler
d758a347eb Update espressif32 from 6.8.1 to 6.9.0 2024-09-27 19:36:52 +02:00
Thomas Basler
0fcf6061c1 Added required include to work with IDF 5 2024-09-27 18:30:44 +02:00
Thomas Basler
8b05bd22b5 Take care of different signature of ETH.begin method in Arduino Core 3.x 2024-09-27 18:27:26 +02:00
Thomas Basler
b85e0ab574 Add default values for ethernet pins in case they are not defined for a specific board 2024-09-27 17:35:33 +02:00
Thomas Basler
b43383007a Rename NetworkEventCb to DtuNetworkEventCb to prevent further upgrade issues 2024-09-27 17:32:28 +02:00
vaterlangen
d770566aec increase chunkSizeWarningLimit for webapp build (#1287)
increase from 500k (default) to 1024k in order to get rid of the warning messages.
2024-09-26 21:31:53 +02:00
Thomas Basler
12b9542f72 Added device profile for OpenDTU Fusion v2 PoE 2024-09-26 20:15:19 +02:00
Thomas Basler
a18e298cdd Apply automatic code formatting 2024-09-26 19:22:30 +02:00
Thomas Basler
7746d01fc0 Apply license headers and automatic code formatting to SpiManager 2024-09-26 18:47:27 +02:00
Thomas Basler
326525c961 Merge branch 'pr2306' into dev 2024-09-26 18:34:07 +02:00
Thomas Basler
355900743d webapp: add app.js.gz 2024-09-26 18:21:16 +02:00
Thomas Basler
818fdc42c9 Simplify inverter handling 2024-09-26 18:17:11 +02:00
Thomas Basler
595b153bbf Simplify network callback handling 2024-09-26 18:08:48 +02:00
Thomas Basler
cc7145361e webapp: Update dependencies 2024-09-26 18:01:59 +02:00
Thomas Basler
8db267b21a webapp: Apply auto format 2024-09-26 18:00:30 +02:00
Thomas Basler
8e26ef4e2e Fix: Only count RF RX packets when packets where sent
This mainly occours after a reset of  the statistics that receive count is higher then transmit count
2024-09-26 17:45:34 +02:00
Thomas Basler
67cae68e83 GitHub Build Action: Automatically generate littlefs image
If a data directory exists, the content of this directory will be placed in the littlefs image and embedded into the factory.bin file
2024-09-26 17:43:07 +02:00
Thomas Basler
468cbad4f3 Upgrade github actions/checkout to v4 2024-09-25 21:53:30 +02:00
Thomas Basler
d69a43373e Slight adjustments to github bug_report template 2024-09-25 21:49:38 +02:00
Thomas Basler
155735c828 Embed current branch into building process 2024-09-25 21:46:38 +02:00
Thomas Basler
0847f021f1 webapp: Update dependencies 2024-09-25 20:21:24 +02:00
Thomas Basler
9b565596d5 Feature: Allow reset of radio statistics via WebApp 2024-09-25 20:18:36 +02:00
LennartF22
31cf756a7e Only use a single SPI device for CMT 2024-09-25 00:37:06 +02:00
LennartF22
36da830f96 Use shared SPI bus for CMT and W5500 2024-09-25 00:37:06 +02:00
LennartF22
5457db269c Use SpiManager for nRF, CMT and W5500 2024-09-25 00:37:06 +02:00
LennartF22
ece4520687 Add Arduino SPI translation 2024-09-25 00:37:06 +02:00
LennartF22
1a583e765d Change cmt_spi3 implementation from C to C++ 2024-09-25 00:37:06 +02:00
LennartF22
4364daf54c Optimize CMT FIFO access 2024-09-25 00:37:06 +02:00
LennartF22
9b9c1e29f1 Add SpiManager library 2024-09-25 00:37:06 +02:00
LennartF22
851190dbcc Implement W5500 support 2024-09-25 00:37:03 +02:00
LennartF22
992e174bb2 Remove unnecessary delays 2024-09-25 00:31:05 +02:00
LennartF22
ec47e8978f Fix cs_ena_posttrans calculation 2024-09-25 00:31:05 +02:00
LennartF22
a02ad8b52c Remove unnecessary CMT SPI inversions 2024-09-25 00:31:05 +02:00
Thomas Basler
d3903d8602 MQTT Hass: Implement method to add common metadata to json output 2024-09-24 23:23:08 +02:00
Thomas Basler
2230850201 MQTT Hass: Implement device class as enum instead of String 2024-09-24 22:55:18 +02:00
Thomas Basler
bb4be0bbf7 MQTT Hass: Implement category as enum instead of String 2024-09-24 22:38:52 +02:00
Thomas Basler
2fb026074a Feature: Publish YieldTotal, YieldDay and Power of all inverters to Home Assistant 2024-09-24 22:16:17 +02:00
Thomas Basler
01e43777d2 MQTT Hass: Append dtu prefix topic for each single sensor 2024-09-24 22:04:07 +02:00
Thomas Basler
2213ad7bce MQTT Hass: Move serialization and allocation check into own method 2024-09-24 21:47:56 +02:00
Thomas Basler
9a318d5170 MQTT Hass: Reorder defines 2024-09-24 20:47:43 +02:00
Thomas Basler
c699f1b487 MQTT Hass: Add device_type and category to publishInverterBinarySensor 2024-09-24 20:45:55 +02:00
Thomas Basler
ac5a960581 MQTT Hass: Move yield into the publish method 2024-09-24 20:42:38 +02:00
Thomas Basler
239a77198d MQTT Hass: Move publishSensor logic into separate method 2024-09-24 20:38:12 +02:00
Thomas Basler
e5ca0ab784 MQTT Hass: Reorder binary sensor methods 2024-09-24 20:06:45 +02:00
Thomas Basler
f46a5017c7 MQTT Hass: Move publishBinarySensor logic into separate method 2024-09-24 20:03:42 +02:00
Thomas Basler
27910042ea MQTT Hass: Remove no more required checks 2024-09-24 19:47:23 +02:00
Thomas Basler
d899ea7364 MQTT Hass: Harmonise parameter names 2024-09-24 19:44:58 +02:00
Thomas Basler
7aca72b8fd MQTT Hass: Change parameter order for publishInverterNumber 2024-09-24 19:39:14 +02:00
Thomas Basler
483c10785b MQTT Hass: Change parameter order for publishInverterButton 2024-09-24 19:30:21 +02:00
Thomas Basler
a7100f238b MQTT Hass: Change parameter order for publishDtuBinarySensor 2024-09-24 19:23:04 +02:00
Thomas Basler
57c5b8c97e MQTT Hass: Make publish methods static 2024-09-24 19:22:05 +02:00
Thomas Basler
1c3e7de390 MQTT Hass: Change parameter order for publishDtuSensor 2024-09-24 19:21:06 +02:00
Thomas Basler
96e83f3d37 MQTT Hass: Change parameter order for publishInverterSensor 2024-09-24 19:18:56 +02:00
Thomas Basler
8e68632ed9 MQTT Hass: Rename caption parameter to name 2024-09-24 18:17:42 +02:00
Thomas Basler
8de1f7e70f MQTT Hass: Change char* to String& 2024-09-24 18:15:38 +02:00
Thomas Basler
bef81eed45 Feature: Publish Radio statistics to home assistant 2024-09-23 23:13:23 +02:00
Thomas Basler
181802a76b Feature: Allow reset of radio statistics via mqtt 2024-09-23 22:46:23 +02:00
Thomas Basler
0c012bf62a Move inverter housekeeping tasks inside the InverterAbstract class 2024-09-23 22:08:53 +02:00
Thomas Basler
93b6e5a885 Optimize MQTT subscription handling 2024-09-23 21:59:43 +02:00
Thomas Basler
d6a5fef4e7 Decrease restart delay to 1 second
This prevents a reload of the webapp (during firmware update) before the esp is online again
2024-09-23 18:33:01 +02:00
Thomas Basler
00584a0787 webapp: add app.js.gz 2024-09-23 18:25:49 +02:00
Thomas Basler
e29ac4f171 webapp: Fix data type for all range inputs 2024-09-23 18:24:22 +02:00
Thomas Basler
e37baedddb webapp: Update dependencies 2024-09-23 18:16:05 +02:00
Thomas Basler
e785904fca Fix: Restart was triggered before all website data was sent
This led to the effect that e.g. the confirmation messages where  not shown.

It is somehow related to ESPAsyncWebServer 3.3.0
2024-09-23 18:11:52 +02:00
Thomas Basler
5c460e26c9 Fix: Unable to CMT transmit power in WebApp
The pa_level was sent as string instead of a number.

fixes #2299
2024-09-23 17:57:29 +02:00
Thomas Basler
a3bd6dd7fb webapp: add app.js.gz 2024-09-22 19:01:43 +02:00
Thomas Basler
c4efda2e0c Added icon to radio statistics 2024-09-22 18:51:07 +02:00
Thomas Basler
a54b19bf5b Feature: Inverter radio statistics (rx/tx statistics)
The  statistics are shown in the WebApp and published via MQTT.
Statistics are reset at midnight.
2024-09-22 18:51:07 +02:00
Thomas Basler
1115418ce1 Publish temperature only if its not NAN 2024-09-22 18:51:07 +02:00
Thomas Basler
84e5c0821c Fix: Saving DTU config values just returned "Values are missing" 2024-09-22 13:16:34 +02:00
Thomas Basler
0c5e702a28 Fix: Wrong topic in home assistant auto discovery for maxalloc and minfree 2024-09-22 13:01:34 +02:00
Thomas Basler
a1fddb4ac1 Merge branch 'pr2293' into dev 2024-09-22 12:52:06 +02:00
Tobias Diedrich
fdcbf9de95 Publish ESP heap and temperature details on MQTT
I noticed that some useful ESP stats are missing on the MQTT feed, so this adds:

- ESP temperature
- ESP heap stats (size, free, minFree, maxAlloc)
2024-09-21 22:39:48 +02:00
Thomas Basler
175e5752fe Github Action: Update node version from 20 to 22 2024-09-21 20:30:42 +02:00
Thomas Basler
98f4aedbfb webapp: add app.js.gz 2024-09-21 19:09:32 +02:00
Thomas Basler
2f41f43d49 Update bblanchon/ArduinoJson from 7.1.0 to 7.2.0 2024-09-21 00:12:15 +02:00
Thomas Basler
3b3e6995c2 Fix: WebApp was not reloaded after firmware update
With the upgrade from ESPAsyncWebServer to 3.3.1 it seems that something has changed. Have to trigger the reboot from the main context.
2024-09-21 00:04:27 +02:00
Thomas Basler
34e1c43ca7 webapp: Fix html error in eventlog 2024-09-20 23:08:08 +02:00
Bernhard Kirchen
43394bc1bc actions: enable corepack to use fixed version of yarn
this allows us to fix the version of yarn, the Node.js package manager,
to a particular version. using corepack is the recommended way to use
yarn these days.
2024-09-20 22:30:46 +02:00
Thomas Basler
a204263fb2 webapp: add app.js.gz 2024-09-20 22:14:41 +02:00
Thomas Basler
0fec55a659 webapp: Update dependencies 2024-09-20 22:13:46 +02:00
Thomas Basler
e9b5f3eac7 Upgrade olikraus/U8g2 from 2.35.27 to 2.35.30 2024-09-20 22:09:23 +02:00
Thomas Basler
0b59a662df Doc: Remove inverter list and add a link to the documentation
This reduces redundant effort when a inverter is added.
2024-09-20 21:36:18 +02:00
Bernhard Kirchen
304b898ddc changelogs: group webapp-related changes 2024-09-20 21:12:08 +02:00
Bernhard Kirchen
1fd09d527a actions: fix a typo 2024-09-20 21:11:44 +02:00
Bernhard Kirchen
bd22f00539 actions: run yarn prettier to check web app formatting 2024-09-20 21:11:18 +02:00
Bernhard Kirchen
2f77b9e500 actions: switch to node version 20 for linting
use version consistent with the version used when building the web
application.
2024-09-20 21:10:18 +02:00
Bernhard Kirchen
ee3b62d671 actions: use setup-node@v4 as v3 causes warning
the "Yarn Linting" action causes a warning to appear about a deprecated
Node version. switch to actions/setup-node@v4, which is already in use
by the action building the web app for the firmware, to avoid this
warning.
2024-09-20 21:09:50 +02:00
Bernhard Kirchen
00626b63f7 issue template: asks for firmware variant 2024-09-20 16:37:33 +02:00
Thomas Basler
e8b1e7a71c webapp: Parse version string event if update search is not allowed 2024-09-16 19:30:45 +02:00
Thomas Basler
d3d92e90e0 webapp: Upgrade tsconfig node18 to node22 2024-09-16 19:13:17 +02:00
Thomas Basler
3e3cf3cd64 webapp: Update dependencies 2024-09-16 19:04:26 +02:00
Thomas Basler
c2e50a9594 Upgrade olikraus/U8g2 from 2.35.21 to 2.35.27 2024-09-16 18:41:18 +02:00
Thomas Basler
5a1d4946fb Upgrade ESPAsyncWebServer from 3.2.0 to 3.3.1 2024-09-16 18:37:51 +02:00
Thomas Basler
a949776966 Feature: Add support for HERF 1 channel inverters 2024-09-02 20:44:26 +02:00
Thomas Basler
ac5b6f3097 webapp: update dependencies 2024-09-02 20:37:54 +02:00
Thomas Basler
e00d831103 Upgrade arkhipenko/TaskScheduler from git #testing to 3.8.5 2024-09-02 20:28:21 +02:00
Thomas Basler
8529cb0fca Upgrade olikraus/U8g2 from 2.35.19 to 2.35.21 2024-09-02 20:21:38 +02:00
Thomas Basler
7b60c92db9 Upgrade ESPAsyncWebServer from 3.1.2 to 3.2.0 2024-09-02 20:18:37 +02:00
Thomas Basler
b52cd31309 Output WiFi disconnect reason in console 2024-09-02 20:16:03 +02:00
Thomas Basler
1f3af949a0 Add serial prefix 1410 to HMS_2CH inverters
This is related to #2235 and fixes #2230
2024-08-28 21:25:15 +02:00
Thomas Basler
e4260d3370 webapp: Update dependencies 2024-08-17 11:34:26 +02:00
Marc-Philip
5ee411fcc6
Update patch_apply.py 2024-07-01 06:53:10 +02:00
Marc-Philip
1d92d9ed08
fix comment 2024-06-30 20:46:12 +02:00
Marc-Philip
fc1267fe55 massage file handling 2024-06-30 18:55:33 +02:00
364 changed files with 8996 additions and 23675 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -47,7 +47,8 @@ body:
label: Install Method
description: How did you install OpenDTU?
options:
- Pre-Compiled binary from GitHub
- Pre-Compiled binary from GitHub releases
- Pre-Compiled binary from GitHub actions/pull-request
- Self-Compiled
validations:
required: true
@ -59,6 +60,14 @@ body:
placeholder: "e.g. 359d513"
validations:
required: true
- type: input
id: environment
attributes:
label: What firmware variant (PIO Environment) are you using?
description: You can find this in by going to Info -> System
placeholder: "generic_esp32s3_usb"
validations:
required: true
- type: textarea
id: logs
attributes:
@ -84,5 +93,5 @@ body:
required: true
- label: I have updated the title field above with a concise description.
required: true
- label: I have double checked that my inverter does not contain a W in the model name (like HMS-xxxW) as they are not supported
- label: I have double checked that my inverter does not contain a W in the model name (like HMS-xxxW) as they are not supported.
required: true

View File

@ -1,15 +1,10 @@
name: OpenDTU-OnBattery Build
name: OpenDTU Build
on:
push:
paths-ignore:
- docs/**
- '**/*.md'
branches:
- master
- development
tags-ignore:
- 'v**'
pull_request:
paths-ignore:
- docs/**
@ -48,7 +43,7 @@ jobs:
environments: ${{ steps.envs.outputs.environments }}
build:
name: Build Enviornments
name: Build Environments
runs-on: ubuntu-latest
needs: get_default_envs
strategy:
@ -60,15 +55,6 @@ jobs:
- name: Get tags
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
uses: actions/cache@v4
with:
@ -93,64 +79,52 @@ jobs:
python -m pip install --upgrade pip
pip install --upgrade platformio setuptools
- name: Enable Corepack
run: |
cd webapp
corepack enable
- name: Setup Node.js and yarn
uses: actions/setup-node@v4
with:
node-version: "20"
node-version: "22"
cache: "yarn"
cache-dependency-path: "webapp/yarn.lock"
- name: Install WebApp dependencies
run: yarn --cwd webapp install --frozen-lockfile
run: |
cd webapp
yarn install --frozen-lockfile
- name: Build WebApp
run: yarn --cwd webapp build
run: |
cd webapp
yarn build
- name: Build firmware
run: pio run -e ${{ matrix.environment }}
- 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
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
with:
name: opendtu-onbattery-${{ matrix.environment }}
name: opendtu-${{ matrix.environment }}
path: |
.pio/build/${{ matrix.environment }}/opendtu-onbattery-${{ matrix.environment }}.bin
!.pio/build/generic_esp32_4mb_no_ota/opendtu-onbattery-generic_esp32_4mb_no_ota.bin
.pio/build/${{ matrix.environment }}/opendtu-onbattery-${{ matrix.environment }}.factory.bin
.pio/build/${{ matrix.environment }}/opendtu-${{ matrix.environment }}.bin
.pio/build/${{ matrix.environment }}/opendtu-${{ matrix.environment }}.factory.bin
release:
name: Create Release
runs-on: ubuntu-latest
needs: [get_default_envs, build]
if: startsWith(github.ref, 'refs/tags/2')
if: startsWith(github.ref, 'refs/tags/')
steps:
- name: Checkout
uses: actions/checkout@v3
- 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
uses: actions/checkout@v4
- name: Build Changelog
id: github_release
@ -170,7 +144,7 @@ jobs:
run: |
ls -R
cd artifacts
for i in */; do cp ${i}opendtu-onbattery-*.bin ./; done
for i in */; do cp ${i}opendtu-*.bin ./; done
- name: Create release
uses: softprops/action-gh-release@v2

View File

@ -18,6 +18,12 @@
"fix"
]
},
{
"title": "## 🌎 Web Application",
"labels": [
"webapp"
]
},
{
"title": "## 📚 Documentation",
"labels": [
@ -30,7 +36,7 @@
}
],
"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",
"label_extractor": [
{

View File

@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:

View File

@ -1,103 +0,0 @@
name: OpenDTU-onBattery Test Build
on: workflow_dispatch
jobs:
get_default_envs:
name: Gather Environments
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Cache pip
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- uses: actions/setup-python@v4
with:
python-version: "3.9"
- name: Install PlatformIO
run: |
python -m pip install --upgrade pip
pip install --upgrade platformio
- name: Get default environments
id: envs
run: |
echo "environments=$(pio project config --json-output | jq -cr '.[1][1][0][1]|split(",")')" >> $GITHUB_OUTPUT
outputs:
environments: ${{ steps.envs.outputs.environments }}
build:
name: Build Enviornments
runs-on: ubuntu-latest
needs: get_default_envs
strategy:
matrix:
environment: ${{ fromJSON(needs.get_default_envs.outputs.environments) }}
steps:
- uses: actions/checkout@v3
- name: Get tags
run: git fetch --force --tags origin
- name: Cache pip
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Cache PlatformIO
uses: actions/cache@v3
with:
path: ~/.platformio
key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.9"
- name: Install PlatformIO
run: |
python -m pip install --upgrade pip
pip install --upgrade platformio
- name: Setup Node.js and yarn
uses: actions/setup-node@v3
with:
node-version: "18"
cache: "yarn"
cache-dependency-path: "webapp/yarn.lock"
- name: Install WebApp dependencies
run: yarn --cwd webapp install --frozen-lockfile
- name: Build WebApp
run: yarn --cwd webapp build
- name: Build firmware
run: pio run -e ${{ matrix.environment }}
- name: Rename Firmware
run: mv .pio/build/${{ matrix.environment }}/firmware.bin .pio/build/${{ matrix.environment }}/opendtu-onbattery-${{ matrix.environment }}.bin
- name: Rename Factory Firmware
run: mv .pio/build/${{ matrix.environment }}/firmware.factory.bin .pio/build/${{ matrix.environment }}/opendtu-onbattery-${{ matrix.environment }}.factory.bin
- uses: actions/upload-artifact@v3
with:
name: opendtu-onbattery-${{ matrix.environment }}
path: |
.pio/build/${{ matrix.environment }}/opendtu-onbattery-${{ matrix.environment }}.bin
.pio/build/${{ matrix.environment }}/opendtu-onbattery-${{ matrix.environment }}.factory.bin

View File

@ -6,17 +6,23 @@ jobs:
build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: webapp
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Enable Corepack
run: corepack enable
- name: Setup Node.js and yarn
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: "18"
node-version: "22"
cache: "yarn"
cache-dependency-path: "webapp/yarn.lock"
- name: Install WebApp dependencies
run: yarn --cwd webapp install --frozen-lockfile
run: yarn install --frozen-lockfile
- name: Linting
run: yarn --cwd webapp lint
run: yarn lint

28
.github/workflows/yarnprettier.yml vendored Normal file
View File

@ -0,0 +1,28 @@
name: Yarn Prettier
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: webapp
steps:
- uses: actions/checkout@v4
- name: Enable Corepack
run: corepack enable
- name: Setup Node.js and yarn
uses: actions/setup-node@v4
with:
node-version: "22"
cache: "yarn"
cache-dependency-path: "webapp/yarn.lock"
- name: Install WebApp dependencies
run: yarn install --frozen-lockfile
- name: Check Formatting
run: yarn prettier --check src/

62
.vscode/settings.json vendored
View File

@ -1,63 +1,3 @@
{
"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"
}
"C_Cpp.clang_format_style": "WebKit"
}

View File

@ -1,66 +1,43 @@
- [OpenDTU-OnBattery](#opendtu-onbattery)
- [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)
- [Acknowledgment](#acknowledgment)
# OpenDTU
# 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 !!
<!---
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)
--->
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!
[![OpenDTU-OnBattery Build](https://github.com/helgeerbe/OpenDTU-OnBattery/actions/workflows/build.yml/badge.svg)](https://github.com/helgeerbe/OpenDTU-OnBattery/actions/workflows/build.yml)
[![cpplint](https://github.com/helgeerbe/OpenDTU-OnBattery/actions/workflows/cpplint.yml/badge.svg)](https://github.com/helgeerbe/OpenDTU-OnBattery/actions/workflows/cpplint.yml)
[![Yarn Linting](https://github.com/helgeerbe/OpenDTU-OnBattery/actions/workflows/yarnlint.yml/badge.svg)](https://github.com/helgeerbe/OpenDTU-OnBattery/actions/workflows/yarnlint.yml)
## Background
## What is OpenDTU-OnBattery
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.
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.
## Documentation
The canonical documentation of OpenDTU-OnBattery is hosted at
[https://opendtu-onbattery.net](https://opendtu-onbattery.net).
The documentation can be found [here](https://tbnobody.github.io/OpenDTU-docs/).
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
community-maintained [Github
Wiki](https://github.com/helgeerbe/OpenDTU-OnBattery/wiki).
## Breaking changes
To find out what's new or improved have a look at the changelog of the
[releases](https://github.com/helgeerbe/OpenDTU-OnBattery/releases).
Generated using: `git log --date=short --pretty=format:"* %h%x09%ad%x09%s" | grep BREAKING`
## 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
may change significantly during its development. Bug reports, comments, feature
requests and pull requests are welcome!
## Currently supported Inverters
## History of the project
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.
## Acknowledgment
A special Thank to Thomas Basler (tbnobody) the author of the original [OpenDTU](https://github.com/tbnobody/OpenDTU) project. You are doing a great job!
@helgeerbe: Last but not least, I would like to thank all the contributors.
With your ideas and enhancements, you have made OpenDTU-OnBattery much more
than I originally had in mind.
A list of all currently supported inverters can be found [here](https://www.opendtu.solar/hardware/inverter_overview/)

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,6 +1,9 @@
[
{
"name": "OpenDTU Fusion v1",
"links": [
{"name": "Information", "url": "https://github.com/markusdd/OpenDTUFusionDocs"}
],
"nrf24": {
"miso": 48,
"mosi": 35,
@ -25,6 +28,9 @@
},
{
"name": "OpenDTU Fusion v1 with SSD1306 Display",
"links": [
{"name": "Information", "url": "https://github.com/markusdd/OpenDTUFusionDocs"}
],
"nrf24": {
"miso": 48,
"mosi": 35,
@ -54,6 +60,9 @@
},
{
"name": "OpenDTU Fusion v1 with SH1106 Display",
"links": [
{"name": "Information", "url": "https://github.com/markusdd/OpenDTUFusionDocs"}
],
"nrf24": {
"miso": 48,
"mosi": 35,
@ -83,6 +92,9 @@
},
{
"name": "OpenDTU Fusion v2 with CMT2300A and NRF24",
"links": [
{"name": "Information", "url": "https://github.com/markusdd/OpenDTUFusionDocs"}
],
"nrf24": {
"miso": 48,
"mosi": 35,
@ -115,6 +127,9 @@
},
{
"name": "OpenDTU Fusion v2 with CMT2300A, NRF24 and SH1106 Display",
"links": [
{"name": "Information", "url": "https://github.com/markusdd/OpenDTUFusionDocs"}
],
"nrf24": {
"miso": 48,
"mosi": 35,
@ -152,6 +167,9 @@
},
{
"name": "OpenDTU Fusion v2 with CMT2300A, NRF24 and SSD1306 Display",
"links": [
{"name": "Information", "url": "https://github.com/markusdd/OpenDTUFusionDocs"}
],
"nrf24": {
"miso": 48,
"mosi": 35,
@ -186,5 +204,122 @@
"data": 2,
"clk": 1
}
},
{
"name": "OpenDTU Fusion v2 PoE",
"links": [
{"name": "Information", "url": "https://github.com/markusdd/OpenDTUFusionDocs"}
],
"nrf24": {
"miso": 48,
"mosi": 35,
"clk": 36,
"irq": 47,
"en": 38,
"cs": 37
},
"cmt": {
"clk": 6,
"cs": 4,
"fcs": 21,
"sdio": 5,
"gpio2": 3,
"gpio3": 8
},
"w5500": {
"mosi": 40,
"miso": 41,
"sclk": 39,
"cs": 42,
"int": 44,
"rst": 43
},
"led": {
"led0": 17,
"led1": 18
},
"display": {
"type": 0,
"data": 2,
"clk": 1
}
},
{
"name": "OpenDTU Fusion v2 PoE with SH1106 Display",
"links": [
{"name": "Information", "url": "https://github.com/markusdd/OpenDTUFusionDocs"}
],
"nrf24": {
"miso": 48,
"mosi": 35,
"clk": 36,
"irq": 47,
"en": 38,
"cs": 37
},
"cmt": {
"clk": 6,
"cs": 4,
"fcs": 21,
"sdio": 5,
"gpio2": 3,
"gpio3": 8
},
"w5500": {
"mosi": 40,
"miso": 41,
"sclk": 39,
"cs": 42,
"int": 44,
"rst": 43
},
"led": {
"led0": 17,
"led1": 18
},
"display": {
"type": 3,
"data": 2,
"clk": 1
}
},
{
"name": "OpenDTU Fusion v2 PoE with SSD1306 Display",
"links": [
{"name": "Information", "url": "https://github.com/markusdd/OpenDTUFusionDocs"}
],
"nrf24": {
"miso": 48,
"mosi": 35,
"clk": 36,
"irq": 47,
"en": 38,
"cs": 37
},
"cmt": {
"clk": 6,
"cs": 4,
"fcs": 21,
"sdio": 5,
"gpio2": 3,
"gpio3": 8
},
"w5500": {
"mosi": 40,
"miso": 41,
"sclk": 39,
"cs": 42,
"int": 44,
"rst": 43
},
"led": {
"led0": 17,
"led1": 18
},
"display": {
"type": 2,
"data": 2,
"clk": 1
}
}
]

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,34 +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();
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,28 +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);
float scaleValue(int16_t value, float factor);
bool getBit(uint8_t value, uint8_t bit);
bool _verboseLogging = true;
private:
char const* _providerName = "Battery CAN";
};

View File

@ -1,272 +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;
uint8_t 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; }
// 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; }
// 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; };
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;
}
String _manufacturer = "unknown";
String _hwversion = "";
String _fwversion = "";
String _serial = "";
uint32_t _lastUpdate = 0;
private:
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;
};
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 setManufacturer(String&& m) { _manufacturer = std::move(m); }
void setLastUpdate(uint32_t ts) { _lastUpdate = ts; }
float _chargeVoltage;
float _chargeCurrentLimitation;
float _dischargeCurrentLimitation;
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;
};
class PytesBatteryStats : public BatteryStats {
friend class PytesCanReceiver;
public:
void getLiveViewData(JsonVariant& root) const final;
void mqttPublish() const final;
float getChargeCurrentLimitation() const { return _chargeCurrentLimit; } ;
private:
void setManufacturer(String&& m) { _manufacturer = std::move(m); }
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;
float _dischargeCurrentLimit;
uint16_t _stateOfHealth;
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;
uint16_t _totalCapacity;
uint16_t _availableCapacity;
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;
};
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 { }
// we don't need a card in the liveview, since the SoC and
// voltage (if available) is already displayed at the top.
void getLiveViewData(JsonVariant& root) const final { }
};

View File

@ -3,10 +3,12 @@
#include "PinMapping.h"
#include <cstdint>
#include <ArduinoJson.h>
#include <TaskSchedulerDeclarations.h>
#include <mutex>
#include <condition_variable>
#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_PASSWORD_STRLEN 64
@ -20,7 +22,7 @@
#define MQTT_MAX_CLIENTID_STRLEN 64
#define MQTT_MAX_USERNAME_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_CERT_STRLEN 2560
@ -31,17 +33,7 @@
#define CHAN_MAX_NAME_STRLEN 31
#define DEV_MAX_MAPPING_NAME_STRLEN 63
#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
#define LOCALE_STRLEN 2
struct CHANNEL_CONFIG_T {
uint16_t MaxChannelPower;
@ -65,69 +57,6 @@ struct INVERTER_CONFIG_T {
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 };
struct CONFIG_T {
struct {
uint32_t Version;
@ -163,7 +92,6 @@ struct CONFIG_T {
struct {
bool Enabled;
char Hostname[MQTT_MAX_HOSTNAME_STRLEN + 1];
bool VerboseLogging;
uint32_t Port;
char ClientId[MQTT_MAX_CLIENTID_STRLEN + 1];
char Username[MQTT_MAX_USERNAME_STRLEN + 1];
@ -208,7 +136,6 @@ struct CONFIG_T {
uint32_t Frequency;
uint8_t CountryMode;
} Cmt;
bool VerboseLogging;
} Dtu;
struct {
@ -221,7 +148,7 @@ struct CONFIG_T {
bool ScreenSaver;
uint8_t Rotation;
uint8_t Contrast;
uint8_t Language;
char Locale[LOCALE_STRLEN + 1];
struct {
uint32_t Duration;
uint8_t Mode;
@ -232,107 +159,38 @@ struct CONFIG_T {
uint8_t Brightness;
} 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;
struct {
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;
} 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];
char Dev_PinMapping[DEV_MAX_MAPPING_NAME_STRLEN + 1];
};
class ConfigurationClass {
public:
void init();
void init(Scheduler& scheduler);
bool read();
bool write();
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* getInverterConfig(const uint64_t serial);
void deleteInverterById(const uint8_t id);
static void serializeHttpRequestConfig(HttpRequestConfig const& source, JsonObject& target);
static void serializePowerMeterMqttConfig(PowerMeterMqttConfig const& source, JsonObject& target);
static void serializePowerMeterSerialSdmConfig(PowerMeterSerialSdmConfig const& source, JsonObject& target);
static void serializePowerMeterHttpJsonConfig(PowerMeterHttpJsonConfig const& source, JsonObject& target);
static void serializePowerMeterHttpSmlConfig(PowerMeterHttpSmlConfig const& source, JsonObject& target);
private:
void loop();
static void deserializeHttpRequestConfig(JsonObject const& source, HttpRequestConfig& target);
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);
Task _loopTask;
};
extern ConfigurationClass Configuration;

View File

@ -40,7 +40,7 @@ public:
void setContrast(const uint8_t contrast);
void setStatus(const bool turnOn);
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 setStartupDisplay();
@ -65,7 +65,7 @@ private:
DisplayType_t _display_type = DisplayType_t::None;
DiagramMode_t _diagram_mode = DiagramMode_t::Off;
uint8_t _display_language = DISPLAY_LANGUAGE;
String _display_language = DISPLAY_LOCALE;
uint8_t _mExtra;
const uint16_t _period = 1000;
const uint16_t _interval = 60000; // interval at which to power save (milliseconds)
@ -73,6 +73,15 @@ private:
char _fmtText[32];
bool _isLarge = false;
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;

View File

@ -1,77 +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>
using up_http_client_t = std::unique_ptr<HTTPClient>;
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:
String getAuthDigest(String const& authReq, unsigned int counter);
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;
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
#include <AsyncWebSocket.h>
#include <HardwareSerial.h>
#include <Stream.h>
#include <TaskSchedulerDeclarations.h>
#include <Print.h>
#include <freertos/task.h>
#include <mutex>
#include <vector>
#include <unordered_map>
#include <queue>
#define BUFFER_SIZE 500
class MessageOutputClass : public Print {
public:
@ -23,19 +22,13 @@ private:
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;
char _buffer[BUFFER_SIZE];
uint16_t _buff_pos = 0;
uint32_t _lastSend = 0;
bool _forceSend = false;
std::mutex _msgLock;
void serialWrite(message_t const& m);
};
extern MessageOutputClass MessageOutput;

View File

@ -1,28 +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;
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);
};

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

@ -6,29 +6,42 @@
#include <TaskSchedulerDeclarations.h>
// mqtt discovery device classes
enum {
enum DeviceClassType {
DEVICE_CLS_NONE = 0,
DEVICE_CLS_CURRENT,
DEVICE_CLS_ENERGY,
DEVICE_CLS_PWR,
DEVICE_CLS_VOLTAGE,
DEVICE_CLS_FREQ,
DEVICE_CLS_TEMP,
DEVICE_CLS_POWER_FACTOR,
DEVICE_CLS_REACTIVE_POWER
DEVICE_CLS_REACTIVE_POWER,
DEVICE_CLS_CONNECTIVITY,
DEVICE_CLS_DURATION,
DEVICE_CLS_SIGNAL_STRENGTH,
DEVICE_CLS_TEMPERATURE,
DEVICE_CLS_RESTART
};
const char* const deviceClasses[] = { 0, "current", "energy", "power", "voltage", "frequency", "temperature", "power_factor", "reactive_power" };
enum {
const char* const deviceClass_name[] = { 0, "current", "energy", "power", "voltage", "frequency", "power_factor", "reactive_power", "connectivity", "duration", "signal_strength", "temperature", "restart" };
enum StateClassType {
STATE_CLS_NONE = 0,
STATE_CLS_MEASUREMENT,
STATE_CLS_TOTAL_INCREASING
};
const char* const stateClasses[] = { 0, "measurement", "total_increasing" };
const char* const stateClass_name[] = { 0, "measurement", "total_increasing" };
enum CategoryType {
CATEGORY_NONE = 0,
CATEGORY_CONFIG,
CATEGORY_DIAGNOSTIC
};
const char* const category_name[] = { 0, "config", "diagnostic" };
typedef struct {
FieldId_t fieldId; // field id
uint8_t deviceClsId; // device class
uint8_t stateClsId; // state class
DeviceClassType deviceClsId; // device class
StateClassType stateClsId; // state class
} byteAssign_fieldDeviceClass_t;
const byteAssign_fieldDeviceClass_t deviceFieldAssignment[] = {
@ -41,7 +54,7 @@ const byteAssign_fieldDeviceClass_t deviceFieldAssignment[] = {
{ FLD_IAC, DEVICE_CLS_CURRENT, STATE_CLS_MEASUREMENT },
{ FLD_PAC, DEVICE_CLS_PWR, STATE_CLS_MEASUREMENT },
{ FLD_F, DEVICE_CLS_FREQ, STATE_CLS_MEASUREMENT },
{ FLD_T, DEVICE_CLS_TEMP, STATE_CLS_MEASUREMENT },
{ FLD_T, DEVICE_CLS_TEMPERATURE, STATE_CLS_MEASUREMENT },
{ FLD_PF, DEVICE_CLS_POWER_FACTOR, STATE_CLS_MEASUREMENT },
{ FLD_EFF, DEVICE_CLS_NONE, STATE_CLS_NONE },
{ FLD_IRR, DEVICE_CLS_NONE, STATE_CLS_NONE },
@ -56,24 +69,35 @@ public:
void publishConfig();
void forceUpdate();
static String getDtuUniqueId();
static String getDtuUrl();
private:
void loop();
void publish(const String& subtopic, const String& payload);
void publishDtuSensor(const char* name, const char* device_class, const char* category, const char* icon, const char* unit_of_measure, const char* subTopic);
void publishDtuBinarySensor(const char* name, const char* device_class, const char* category, const char* payload_on, const char* payload_off, const char* subTopic = "");
void publishInverterField(std::shared_ptr<InverterAbstract> inv, const ChannelType_t type, const ChannelNum_t channel, const byteAssign_fieldDeviceClass_t fieldType, const bool clear = false);
void publishInverterButton(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* icon, const char* category, const char* deviceClass, const char* subTopic, const char* payload);
void publishInverterNumber(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* icon, const char* category, const char* commandTopic, const char* stateTopic, const char* unitOfMeasure, const int16_t min = 1, const int16_t max = 100, float step = 1.0);
void publishInverterBinarySensor(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* subTopic, const char* payload_on, const char* payload_off);
static void publish(const String& subtopic, const String& payload);
static void publish(const String& subtopic, const JsonDocument& doc);
static void addCommonMetadata(JsonDocument& doc, const String& unit_of_measure, const String& icon, const DeviceClassType device_class, const StateClassType state_class, const CategoryType category);
// Binary Sensor
static void publishBinarySensor(JsonDocument& doc, const String& root_device, const String& unique_id_prefix, const String& name, const String& state_topic, const String& payload_on, const String& payload_off, const DeviceClassType device_class, const StateClassType state_class, const CategoryType category);
static void publishDtuBinarySensor(const String& name, const String& state_topic, const String& payload_on, const String& payload_off, const DeviceClassType device_class, const StateClassType state_class, const CategoryType category);
static void publishInverterBinarySensor(std::shared_ptr<InverterAbstract> inv, const String& name, const String& state_topic, const String& payload_on, const String& payload_off, const DeviceClassType device_class, const StateClassType state_class, const CategoryType category);
// Sensor
static void publishSensor(JsonDocument& doc, const String& root_device, const String& unique_id_prefix, const String& name, const String& state_topic, const String& unit_of_measure, const String& icon, const DeviceClassType device_class, const StateClassType state_class, const CategoryType category);
static void publishDtuSensor(const String& name, const String& state_topic, const String& unit_of_measure, const String& icon, const DeviceClassType device_class, const StateClassType state_class, const CategoryType category);
static void publishInverterSensor(std::shared_ptr<InverterAbstract> inv, const String& name, const String& state_topic, const String& unit_of_measure, const String& icon, const DeviceClassType device_class, const StateClassType state_class, const CategoryType category);
static void publishInverterField(std::shared_ptr<InverterAbstract> inv, const ChannelType_t type, const ChannelNum_t channel, const byteAssign_fieldDeviceClass_t fieldType, const bool clear = false);
static void publishInverterButton(std::shared_ptr<InverterAbstract> inv, const String& name, const String& state_topic, const String& payload, const String& icon, const DeviceClassType device_class, const StateClassType state_class, const CategoryType category);
static void publishInverterNumber(std::shared_ptr<InverterAbstract> inv, const String& name, const String& state_topic, const String& command_topic, const int16_t min, const int16_t max, float step, const String& unit_of_measure, const String& icon, const StateClassType state_class, const CategoryType category);
static void createInverterInfo(JsonDocument& doc, std::shared_ptr<InverterAbstract> inv);
static void createDtuInfo(JsonDocument& doc);
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;
bool _wasConnected = false;

View File

@ -1,44 +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>
class MqttHandleHuaweiClass {
public:
void init(Scheduler& scheduler);
private:
void loop();
enum class Topic : unsigned {
LimitOnlineVoltage,
LimitOnlineCurrent,
LimitOfflineVoltage,
LimitOfflineCurrent,
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

@ -5,6 +5,8 @@
#include <Hoymiles.h>
#include <TaskSchedulerDeclarations.h>
#include <espMqttClient.h>
#include <frozen/map.h>
#include <frozen/string.h>
class MqttHandleInverterClass {
public:
@ -19,7 +21,6 @@ public:
private:
void loop();
void publishField(std::shared_ptr<InverterAbstract> inv, const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId);
void onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, const size_t len, const size_t index, const size_t total);
Task _loopTask;
@ -41,6 +42,29 @@ private:
FLD_IRR,
FLD_Q
};
enum class Topic : unsigned {
LimitPersistentRelative,
LimitPersistentAbsolute,
LimitNonPersistentRelative,
LimitNonPersistentAbsolute,
Power,
Restart,
ResetRfStats,
};
static constexpr frozen::string _cmdtopic = "+/cmd/";
static constexpr frozen::map<frozen::string, Topic, 7> _subscriptions = {
{ "limit_persistent_relative", Topic::LimitPersistentRelative },
{ "limit_persistent_absolute", Topic::LimitPersistentAbsolute },
{ "limit_nonpersistent_relative", Topic::LimitNonPersistentRelative },
{ "limit_nonpersistent_absolute", Topic::LimitNonPersistentAbsolute },
{ "power", Topic::Power },
{ "restart", Topic::Restart },
{ "reset_rf_stats", Topic::ResetRfStats },
};
void onMqttMessage(Topic t, const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, const size_t len, const size_t index, const size_t total);
};
extern MqttHandleInverterClass MqttHandleInverter;

View File

@ -1,45 +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>
class MqttHandlePowerLimiterClass {
public:
void init(Scheduler& scheduler);
private:
void loop();
enum class MqttPowerLimiterCommand : unsigned {
Mode,
BatterySoCStartThreshold,
BatterySoCStopThreshold,
FullSolarPassthroughSoC,
VoltageStartThreshold,
VoltageStopThreshold,
FullSolarPassThroughStartVoltage,
FullSolarPassThroughStopVoltage,
UpperPowerLimit,
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:
MqttSettingsClass();
void init();
void loop();
void performReconnect();
bool getConnected();
void publish(const String& subtopic, const String& payload);
@ -21,7 +20,7 @@ public:
void unsubscribe(const String& topic);
String getPrefix() const;
String getClientId();
String getClientId() const;
private:
void NetworkEvent(network_event event);
@ -39,7 +38,6 @@ private:
Ticker _mqttReconnectTimer;
MqttSubscribeParser _mqttSubscribeParser;
std::mutex _clientLock;
bool _verboseLogging = true;
};
extern MqttSettingsClass MqttSettings;

View File

@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "W5500.h"
#include <DNSServer.h>
#include <TaskSchedulerDeclarations.h>
#include <WiFi.h>
@ -23,18 +24,18 @@ enum class network_event {
NETWORK_EVENT_MAX
};
typedef std::function<void(network_event event)> NetworkEventCb;
typedef std::function<void(network_event event)> DtuNetworkEventCb;
typedef struct NetworkEventCbList {
NetworkEventCb cb;
typedef struct DtuNetworkEventCbList {
DtuNetworkEventCb cb;
network_event event;
NetworkEventCbList()
DtuNetworkEventCbList()
: cb(nullptr)
, event(network_event::NETWORK_UNKNOWN)
{
}
} NetworkEventCbList_t;
} DtuNetworkEventCbList_t;
class NetworkSettingsClass {
public:
@ -53,7 +54,7 @@ public:
bool isConnected() const;
network_mode NetworkMode() const;
bool onEvent(NetworkEventCb cbEvent, const network_event event = network_event::NETWORK_EVENT_MAX);
bool onEvent(DtuNetworkEventCb cbEvent, const network_event event = network_event::NETWORK_EVENT_MAX);
void raiseEvent(const network_event event);
private:
@ -62,7 +63,7 @@ private:
void setStaticIp();
void handleMDNS();
void setupMode();
void NetworkEvent(const WiFiEvent_t event);
void NetworkEvent(const WiFiEvent_t event, WiFiEventInfo_t info);
Task _loopTask;
@ -81,8 +82,9 @@ private:
bool _dnsServerStatus = false;
network_mode _networkMode = network_mode::Undefined;
bool _ethConnected = false;
std::vector<NetworkEventCbList_t> _cbEventList;
std::vector<DtuNetworkEventCbList_t> _cbEventList;
bool _lastMdnsEnabled = false;
std::unique_ptr<W5500> _w5500;
};
extern NetworkSettingsClass NetworkSettings;

View File

@ -12,6 +12,7 @@
struct PinMapping_t {
char name[MAPPING_NAME_STRLEN + 1];
int8_t nrf24_miso;
int8_t nrf24_mosi;
int8_t nrf24_clk;
@ -26,6 +27,14 @@ struct PinMapping_t {
int8_t cmt_gpio3;
int8_t cmt_sdio;
int8_t w5500_mosi;
int8_t w5500_miso;
int8_t w5500_sclk;
int8_t w5500_cs;
int8_t w5500_int;
int8_t w5500_rst;
#if CONFIG_ETH_USE_ESP32_EMAC
int8_t eth_phy_addr;
bool eth_enabled;
int eth_power;
@ -33,33 +42,15 @@ struct PinMapping_t {
int eth_mdio;
eth_phy_type_t eth_type;
eth_clock_mode_t eth_clk_mode;
#endif
uint8_t display_type;
uint8_t display_data;
uint8_t display_clk;
uint8_t display_cs;
uint8_t display_reset;
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 led[PINMAPPING_LED_COUNT];
};
class PinMappingClass {
@ -68,13 +59,19 @@ public:
bool init(const String& deviceMapping);
PinMapping_t& get();
bool isMappingSelected() const { return _mappingSelected; }
bool isValidNrf24Config() const;
bool isValidCmt2300Config() const;
bool isValidW5500Config() const;
#if CONFIG_ETH_USE_ESP32_EMAC
bool isValidEthConfig() const;
bool isValidHuaweiConfig() const;
#endif
private:
PinMapping_t _pinMapping;
bool _mappingSelected = false;
};
extern PinMappingClass PinMapping;

View File

@ -1,106 +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, bool batteryPower);
bool updateInverter();
bool setNewPowerLimit(std::shared_ptr<InverterAbstract> inverter, int32_t newPowerLimit);
int32_t getSolarPower();
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,37 +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;
void doMqttPublish() 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);
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>();
};

18
include/RestartHelper.h Normal file
View File

@ -0,0 +1,18 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <TaskSchedulerDeclarations.h>
class RestartHelperClass {
public:
RestartHelperClass();
void init(Scheduler& scheduler);
void triggerRestart();
private:
void loop();
Task _rebootTask;
};
extern RestartHelperClass RestartHelper;

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

@ -2,23 +2,16 @@
#pragma once
#include <ArduinoJson.h>
#include <LittleFS.h>
#include <cstdint>
#include <utility>
class Utils {
public:
static uint32_t getChipId();
static uint64_t generateDtuSerial();
static int getTimezoneOffset();
static void restartDtu();
static bool checkJsonAlloc(const JsonDocument& doc, const char* function, const uint16_t line);
static void removeAllFiles();
/* OpenDTU-OnBatter-specific utils go here: */
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);
static String generateMd5FromFile(String file);
static void skipBom(File& f);
};

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>();
};

29
include/W5500.h Normal file
View File

@ -0,0 +1,29 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <Arduino.h>
#include <driver/spi_master.h>
#include <esp_eth.h> // required for esp_eth_handle_t
#include <esp_netif.h>
#include <memory>
class W5500 {
private:
explicit W5500(spi_device_handle_t spi, gpio_num_t pin_int);
public:
W5500(const W5500&) = delete;
W5500& operator=(const W5500&) = delete;
~W5500();
static std::unique_ptr<W5500> setup(int8_t pin_mosi, int8_t pin_miso, int8_t pin_sclk, int8_t pin_cs, int8_t pin_int, int8_t pin_rst);
String macAddress();
private:
static bool connection_check_spi(spi_device_handle_t spi);
static bool connection_check_interrupt(gpio_num_t pin_int);
esp_eth_handle_t eth_handle;
esp_netif_t* eth_netif;
};

View File

@ -1,15 +1,15 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "WebApi_battery.h"
#include "WebApi_config.h"
#include "WebApi_device.h"
#include "WebApi_devinfo.h"
#include "WebApi_dtu.h"
#include "WebApi_errors.h"
#include "WebApi_eventlog.h"
#include "WebApi_file.h"
#include "WebApi_firmware.h"
#include "WebApi_gridprofile.h"
#include "WebApi_i18n.h"
#include "WebApi_inverter.h"
#include "WebApi_limit.h"
#include "WebApi_maintenance.h"
@ -17,8 +17,6 @@
#include "WebApi_network.h"
#include "WebApi_ntp.h"
#include "WebApi_power.h"
#include "WebApi_powermeter.h"
#include "WebApi_powerlimiter.h"
#include "WebApi_prometheus.h"
#include "WebApi_security.h"
#include "WebApi_sysstatus.h"
@ -26,11 +24,6 @@
#include "WebApi_ws_console.h"
#include "WebApi_ws_live.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 <TaskSchedulerDeclarations.h>
@ -38,6 +31,7 @@ class WebApiClass {
public:
WebApiClass();
void init(Scheduler& scheduler);
void reload();
static bool checkCredentials(AsyncWebServerRequest* request);
static bool checkCredentialsReadonly(AsyncWebServerRequest* request);
@ -53,14 +47,14 @@ public:
private:
AsyncWebServer _server;
WebApiBatteryClass _webApiBattery;
WebApiConfigClass _webApiConfig;
WebApiDeviceClass _webApiDevice;
WebApiDevInfoClass _webApiDevInfo;
WebApiDtuClass _webApiDtu;
WebApiEventlogClass _webApiEventlog;
WebApiFileClass _webApiFile;
WebApiFirmwareClass _webApiFirmware;
WebApiGridProfileClass _webApiGridprofile;
WebApiI18nClass _webApiI18n;
WebApiInverterClass _webApiInverter;
WebApiLimitClass _webApiLimit;
WebApiMaintenanceClass _webApiMaintenance;
@ -68,19 +62,12 @@ private:
WebApiNetworkClass _webApiNetwork;
WebApiNtpClass _webApiNtp;
WebApiPowerClass _webApiPower;
WebApiPowerMeterClass _webApiPowerMeter;
WebApiPowerLimiterClass _webApiPowerLimiter;
WebApiPrometheusClass _webApiPrometheus;
WebApiSecurityClass _webApiSecurity;
WebApiSysstatusClass _webApiSysstatus;
WebApiWebappClass _webApiWebapp;
WebApiWsConsoleClass _webApiWsConsole;
WebApiWsLiveClass _webApiWsLive;
WebApiWsVedirectLiveClass _webApiWsVedirectLive;
WebApiVedirectClass _webApiVedirect;
WebApiHuaweiClass _webApiHuaweiClass;
WebApiWsHuaweiLiveClass _webApiWsHuaweiLive;
WebApiWsBatteryLiveClass _webApiWsBatteryLive;
};
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;
};

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