Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[P176] Add plugin Communication - Victron VE.Direct #5148

Open
wants to merge 24 commits into
base: mega
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
4cd1d80
[P176] Add plugin Communication - Victron VE.Direct
tonhuisman Oct 25, 2024
9dd301b
Merge branch 'mega' of https://github.com/letscontrolit/ESPEasy into …
tonhuisman Oct 26, 2024
0aecf3b
[P176] Add some features and code improvements
tonhuisman Oct 26, 2024
9d8135e
[P176] Fix ESP8266 build issue
tonhuisman Oct 26, 2024
d67d8e6
[P176] Update taskvalues at receiving data, other improvements
tonhuisman Oct 27, 2024
78d6a2c
[P176] Fix use of plugin compiletime feature flags
tonhuisman Oct 27, 2024
8c3eb08
Merge branch 'mega' of https://github.com/letscontrolit/ESPEasy into …
tonhuisman Oct 28, 2024
f5068aa
[P176] Move Led initialization to DataStruct initialization function
tonhuisman Oct 28, 2024
3e862bc
[P176] Add documentation
tonhuisman Oct 28, 2024
7a5f744
[P176] Refactorings to optimize performance (lower load), remove RX T…
tonhuisman Oct 29, 2024
9ca8f79
[P176] Update documentation
tonhuisman Oct 29, 2024
88f7940
[P176] Simplify handling temporary values before checksum checked
TD-er Oct 29, 2024
f2a01e6
[P176] Avoid calling serial->available() too often, uncrustify some r…
tonhuisman Oct 30, 2024
8f475f1
[P176] Update changelog (and GH Actions failed)
tonhuisman Oct 30, 2024
21ff328
[P176] Handle ON and OFF values as 1 and 0
tonhuisman Oct 30, 2024
7de795e
[P176] Fix checksum validation, was ignoring the first line after rec…
tonhuisman Nov 2, 2024
c8d6e45
Merge branch 'mega' into feature/P176-add-plugin-victron-ve.direct
TD-er Nov 4, 2024
ed33fd0
Merge branch 'mega' of https://github.com/letscontrolit/ESPEasy into …
tonhuisman Nov 8, 2024
7954406
[P176] Add variables `successcount`, `errorcount` and `ischanged`
tonhuisman Nov 8, 2024
90c4a2f
[P176] Rename variable `ischanged` to `updated`
tonhuisman Nov 10, 2024
78cf151
Merge branch 'mega' into feature/P176-add-plugin-victron-ve.direct
TD-er Nov 13, 2024
db133eb
Merge branch 'mega' of https://github.com/letscontrolit/ESPEasy into …
tonhuisman Nov 24, 2024
bbda83d
[P176] Add Events only when updated option and docs
tonhuisman Nov 24, 2024
d39c3f9
[P176] Fix bug in screenshot :-)
tonhuisman Nov 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions docs/source/Plugin/P176.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
.. include:: ../Plugin/_plugin_substitutions_p17x.repl
.. _P176_page:

|P176_typename|
==================================================

|P176_shortinfo|

Plugin details
--------------

Type: |P176_type|

Name: |P176_name|

Status: |P176_status|

GitHub: |P176_github|_

Maintainer: |P176_maintainer|

Used libraries: |P176_usedlibraries|

Supported hardware
------------------

|P176_usedby|

Configuration
-------------

.. image:: P176_DeviceConfiguration.png

* **Name** In the Name field a unique name should be entered.

* **Enabled** When unchecked the plugin is not enabled.

Sensor
^^^^^^

See: :ref:`SerialHelper_page`

Device Settings
^^^^^^^^^^^^^^^

* **Baud Rate / Serial config**: See *Serial helper configuration*, above.

* **RX Buffersize**: The buffer to be used for ESPEasySerial, default and minimum is 128 bytes. Can be expanded up to 2048 bytes if processing of the received data takes too much time and data gets discarded.

* **Ignore data on checksum error**: The protocol includes a checksum calculation, and when this fails, the data is probably unreliable, and should better be dismissed.

* **Events only on updated data**: With this checkbox enabled, events and outgoing data to Controllers is only sent if at least 1 new packet is received since the last Interval or TaskRun. When disabled, data is sent out every Interval, when data is available (so at least a single packet is successfully received). This option is only available if Checksum processing is included in the build.

Led
^^^

* **Led pin**: The GPIO pin a Led is connected to. To enable a *data is being processed* activity led.

* **Led inverted**: Inverts the on/off state for the Led.

Logging
^^^^^^^

* **Enable logging (for debug)**: When enabled, all received data, and the current checksum counter, are logged at INFO level, to validate if, and what, data is being received. During normal operation, this should be disabled.

* **Quiet logging (reduces logging)**: During normal operation there is some minimal logging available, informing about successfully received packets. This logging is suppressed when Quiet logging is enabled.

Data Acquisition
^^^^^^^^^^^^^^^^

This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.

* **Interval** By default, Interval will be set to 0 sec. In this situation, no data is sent automatically, and no events are generated. The data will still be collected and can be retrieved for use on a display, or further processing from other rules. When an Interval is set the data can be optionally sent to any configured controllers and either a single ``#All`` event or an event per value will be generated. As an alternative, the ``TaskRun`` command can be used to publish the data at any moment.

Values
^^^^^^

The plugin provides all values that are received from a VE.Direct device. By entering the Name of such value here, that value will be made available. Some values are text-only, and can not be used in this way, but from rules these values can still be sent to a controller or shown on a display.

The number of decimals to use for displaying in the Devices overview and sending to Controllers, can be selected, and an optional formula can be applied to the available value, f.e. for applying some kind of correction.

In selected builds, per Value **Stats** options are available, that when enabled, will gather the measured data and present most recent data in a graph, as described here: :ref:`Task Value Statistics: <Task Value Statistics>`

Values that are not configured in the Values section can still be retrieved from rules, or in a display configuration, by using the regular ``[<TaskName>#<ValueName>]`` notation. The ``<TaskName>`` is the name as set in the **Name** field, above, and the ``<TaskValue>`` is the name of the data item, available from the VE.Direct device. These names are *not* case-sensitive.

An overview of the data is shown when the task is enabled, and data is received (successfully) from a VE.Direct device.

Example data
------------

.. image:: P176_ExampleData.png

This example shows the data as can be received from a VE.Direct device.

The *Name* column is what should be used in the Values fields, or when as a ``<ValueName>`` from rules or in a display configuration. The exact meaning and unit of each field can be found in the VE.Direct protocol documentation, available from Victron Energy.

The *Data* column shows the actual data as received. If the checksum validation is enabled, this column may be empty if the checksum could not be verified, like the first (possibly incomplete) packet, but as the frequency of packets is rather high, this column should not often (or long) be empty.

The *Value* column shows a factored result based on the value, as mV is not always very useful, so that's converted to V, mA to A, Wh to kWh, etc.

For items that use ON and OFF (or On and Off for older firmwares) as Data, these will be available as 1 and 0 respectively, so they can be used as Values. If needed, a transformation like ``[VE.Direct#Alarm#O]`` can be used to present the value as ON/OFF for 1/0. Adding the ``C`` justification, like ``[VE.Direct#Alarm#O#C]``, will be presented as On/Off.

It shows that it has received 2 packets, but as a packet only contains a part of the available data, and a next packet another part, etc. there's no fixed relation to the amount of samples, and the number of packets received. Some devices may send all data in a single packet, other devices may need 3 or even 4 packets to send all available data.

Also, the number of checksum errors is shown, this counter is automatically reset after receiving 50 consecutive correct packets.

Get Config values
-----------------

All data values received can be retrieved by using the ``[<taskname>#<data_name>]`` syntax. To get the success count and error count, varables ``[<taskname>#successcount]`` and ``[<taskname>#errorcount]`` are available, and also the ``[<taskname>#updated]`` value, to see if data was updated since the last call for this variable (returns 0/1). So this should only be called once for evaluation. The ``errorcount`` value is reset after 50 succesful received packets, so after receiving 50 successful packets it will return 0.

Change log
----------

.. versionchanged:: 2.0
...

|added| 2024-10-27: Initial release.





Binary file added docs/source/Plugin/P176_DeviceConfiguration.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/source/Plugin/P176_ExampleData.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/source/Plugin/_Plugin.rst
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@ There are different released versions of ESP Easy:
":ref:`P170_page`","|P170_status|","P170"
":ref:`P172_page`","|P172_status|","P172"
":ref:`P173_page`","|P173_status|","P173"
":ref:`P176_page`","|P176_status|","P176"


.. include:: _plugin_sets_overview.repl
Expand Down
2 changes: 1 addition & 1 deletion docs/source/Plugin/_plugin_categories.repl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.. |Plugin_Analog_input| replace:: :ref:`P002_page`, :ref:`P007_page`, :ref:`P025_page`, :ref:`P060_page`, :ref:`P097_page`
.. |Plugin_Acceleration| replace:: :ref:`P120_page`, :ref:`P125_page`
.. |Plugin_Color| replace:: :ref:`P112_page`
.. |Plugin_Communication| replace:: :ref:`P016_page`, :ref:`P020_page`, :ref:`P035_page`, :ref:`P044_page`, :ref:`P054_page`, :ref:`P071_page`, :ref:`P087_page`, :ref:`P089_page`, :ref:`P094_page`, :ref:`P101_page`, :ref:`P118_page`
.. |Plugin_Communication| replace:: :ref:`P016_page`, :ref:`P020_page`, :ref:`P035_page`, :ref:`P044_page`, :ref:`P054_page`, :ref:`P071_page`, :ref:`P087_page`, :ref:`P089_page`, :ref:`P094_page`, :ref:`P101_page`, :ref:`P118_page`, :ref:`P176_page`
.. |Plugin_Display| replace:: :ref:`P012_page`, :ref:`P023_page`, :ref:`P036_page`, :ref:`P057_page`, :ref:`P073_page`, :ref:`P075_page`, :ref:`P095_page`, :ref:`P104_page`, :ref:`P116_page`, :ref:`P131_page`, :ref:`P148_page`
.. |Plugin_Distance| replace:: :ref:`P013_page`, :ref:`P110_page`, :ref:`P113_page`, :ref:`P134_page`
.. |Plugin_Dust| replace:: :ref:`P018_page`, :ref:`P053_page`, :ref:`P056_page`, :ref:`P144_page`
Expand Down
13 changes: 13 additions & 0 deletions docs/source/Plugin/_plugin_substitutions_p17x.repl
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,16 @@
.. |P173_maintainer| replace:: `tonhuisman`
.. |P173_compileinfo| replace:: `.`
.. |P173_usedlibraries| replace:: `.`

.. |P176_name| replace:: :cyan:`Victron VE.Direct`
.. |P176_type| replace:: :cyan:`Communication`
.. |P176_typename| replace:: :cyan:`Communication - Victron VE.Direct`
.. |P176_porttype| replace:: `.`
.. |P176_status| replace:: :yellow:`ENERGY`
.. |P176_github| replace:: P176_VE_Direct.ino
.. _P176_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P176_VE_Direct.ino
.. |P176_usedby| replace:: `Victron Energy devices supporting the VE.Direct protocol`
.. |P176_shortinfo| replace:: `Victron VE.Direct protocol reader`
.. |P176_maintainer| replace:: `tonhuisman`
.. |P176_compileinfo| replace:: `.`
.. |P176_usedlibraries| replace:: `.`
230 changes: 230 additions & 0 deletions src/_P176_VE_Direct.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
#include "_Plugin_Helper.h"
#ifdef USES_P176

// #######################################################################################################
// ############################# Plugin 176: Communication - Victron VE.Direct ###########################
// #######################################################################################################

/** Changelog:
* 2024-11-24 tonhuisman: Add setting "Events only on updated data" to not generate events/Controller output if no new packets have been received
* This check is independent from the Updated GetConfig value.
* 2024-11-10 tonhuisman: Renamed GetConfig ischanged to updated.
* 2024-11-08 tonhuisman: Add successcount, errorcount and ischanged values for GetConfig. IsChanged will reset the state on each call, so
* should be called only once in a session.
* 2024-10-30 tonhuisman: Accept ON/On and OFF/Off as 1 and 0 numeric values, to be able to use them as Values in the device configuration
* 2024-10-30 tonhuisman: Simplifying the code somewhat, making it more robust, partially by TD-er
* 2024-10-29 tonhuisman: Optimize receiving and processing data, resulting in much lower load (based on suggestions by TD-er)
* Removed the RX Timeout setting, as it doesn't help here, seems to slow things down.
* 2024-10-27 tonhuisman: Update TaskValues as soon as data arrives (successfully), show successfully received packet count,
* reset checksum error count after 50 successful packets
* 2024-10-26 tonhuisman: Add setting for RX buffer size and optional Debug logging. Add Quit log to suppress most logging
* Store original received data names, to show in Device configuration, add default decimals to conversion factors
* Improved serial processing reading per line instead of per data-block, moved to 50/sec
* 2024-10-22 tonhuisman: Add checksum handling,
* Add conversion factors to get more usable values like V, A, Ah, %. Based on VE.Direct protocol manual v. 3.3
* 2024-10-20 tonhuisman: Start plugin for Victron VE.Direct protocol reader, based on Victron documentation
**/

# define PLUGIN_176
# define PLUGIN_ID_176 176
# define PLUGIN_NAME_176 "Communication - Victron VE.Direct"
# define PLUGIN_VALUENAME1_176 "V" // battery_voltage
# define PLUGIN_VALUENAME2_176 "I" // battery_current
# define PLUGIN_VALUENAME3_176 "P" // instantaneous_power
# define PLUGIN_VALUENAME4_176 "ERR" // error_code

# include "./src/PluginStructs/P176_data_struct.h"

boolean Plugin_176(uint8_t function, struct EventStruct *event, String& string)
{
boolean success = false;

switch (function)
{
case PLUGIN_DEVICE_ADD:
{
Device[++deviceCount].Number = PLUGIN_ID_176;
Device[deviceCount].Type = DEVICE_TYPE_SERIAL;
Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_QUAD;
Device[deviceCount].Ports = 0;
Device[deviceCount].FormulaOption = true;
Device[deviceCount].ValueCount = 4;
Device[deviceCount].SendDataOption = true;
Device[deviceCount].TimerOption = true;
Device[deviceCount].TimerOptional = true;
Device[deviceCount].PluginStats = true;

break;
}

case PLUGIN_GET_DEVICENAME:
{
string = F(PLUGIN_NAME_176);

break;
}

case PLUGIN_GET_DEVICEVALUENAMES:
{
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_176));
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_176));
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_176));
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[3], PSTR(PLUGIN_VALUENAME4_176));

break;
}

case PLUGIN_SET_DEFAULTS:
{
CONFIG_PORT = static_cast<int>(ESPEasySerialPort::serial1); // Serial1 port
int rxPin = 0;
int txPin = 0;
const ESPEasySerialPort port = static_cast<ESPEasySerialPort>(CONFIG_PORT);

ESPeasySerialType::getSerialTypePins(port, rxPin, txPin);
CONFIG_PIN1 = rxPin;
CONFIG_PIN2 = txPin;
P176_SERIAL_BAUDRATE = P176_DEFAULT_BAUDRATE;
P176_SERIAL_BUFFER = P176_DEFAULT_BUFFER;
# if P176_FAIL_CHECKSUM
P176_SET_FAIL_CHECKSUM(P176_DEFAULT_FAIL_CHECKSUM);
# endif // if P176_FAIL_CHECKSUM
P176_SET_LED_PIN(-1);
P176_SET_QUIET_LOG(true);
}

case PLUGIN_WEBFORM_SHOW_CONFIG:
{
string += serialHelper_getSerialTypeLabel(event);
success = true;
break;
}

case PLUGIN_GET_DEVICEGPIONAMES:
{
serialHelper_getGpioNames(event);
break;
}

case PLUGIN_WEBFORM_SHOW_GPIO_DESCR:
{
string = strformat(F("LED: %s"), formatGpioLabel(P176_GET_LED_PIN, false).c_str());

if (validGpio(P176_GET_LED_PIN) && P176_GET_LED_INVERTED) {
string += F(" (inv)");
}
success = true;
break;
}

case PLUGIN_WEBFORM_LOAD:
{
addFormNumericBox(F("Baud Rate"), F("pbaud"), P176_SERIAL_BAUDRATE, 0);
const uint8_t serialConfChoice = serialHelper_convertOldSerialConfig(P176_SERIAL_CONFIG);
serialHelper_serialconfig_webformLoad(event, serialConfChoice);

addFormNumericBox(F("RX buffersize"), F("pbuffer"), P176_SERIAL_BUFFER, 128, 2048);
addUnit(F("128..2048"));

# if P176_FAIL_CHECKSUM
addFormCheckBox(F("Ignore data on checksum error"), F("pchksm"), P176_GET_FAIL_CHECKSUM);
# endif // if P176_FAIL_CHECKSUM
# if P176_HANDLE_CHECKSUM
addFormCheckBox(F("Events only on updated data"), F("pupd"), P176_GET_READ_UPDATED);
# endif // if P176_HANDLE_CHECKSUM

{ // Led settings
addFormSubHeader(F("Led"));

addFormPinSelect(PinSelectPurpose::Generic_output, F("Led pin"), F("pledpin"), P176_GET_LED_PIN);
addFormCheckBox(F("Led inverted"), F("pledinv"), P176_GET_LED_INVERTED);
}

{
P176_data_struct *P176_data = static_cast<P176_data_struct *>(getPluginTaskData(event->TaskIndex));

if ((nullptr != P176_data) && (P176_data->getCurrentDataSize() > 0)) {
addFormSubHeader(F("Current data"));
addRowLabel(F("Recently received data"));
P176_data->showCurrentData();
# if P176_HANDLE_CHECKSUM
addRowLabel(F("Successfully received packets"));
addHtmlInt(P176_data->getSuccessfulPackets());
addRowLabel(F("Recent checksum errors"));
addHtmlInt(P176_data->getChecksumErrors());
addUnit(F("reset after 50 successful packets"));
# endif // if P176_HANDLE_CHECKSUM
}
}
# if P176_DEBUG
addFormSubHeader(F("Logging"));
addFormCheckBox(F("Enable logging (for debug)"), F("pdebug"), P176_GET_DEBUG_LOG);
# endif // if P176_DEBUG
addFormCheckBox(F("Quiet logging (reduces logging)"), F("pquiet"), P176_GET_QUIET_LOG);
success = true;
break;
}

case PLUGIN_WEBFORM_SAVE:
{
P176_SERIAL_BAUDRATE = getFormItemInt(F("pbaud"));
P176_SERIAL_CONFIG = serialHelper_serialconfig_webformSave();
P176_SERIAL_BUFFER = getFormItemInt(F("pbuffer"));
P176_SET_LED_PIN(getFormItemInt(F("pledpin")));
P176_SET_LED_INVERTED(isFormItemChecked(F("pledinv")));
# if P176_FAIL_CHECKSUM
P176_SET_FAIL_CHECKSUM(isFormItemChecked(F("pchksm")));
# endif // if P176_FAIL_CHECKSUM
# if P176_HANDLE_CHECKSUM
P176_SET_READ_UPDATED(isFormItemChecked(F("pupd")));
# endif // if P176_HANDLE_CHECKSUM
# if P176_DEBUG
P176_SET_DEBUG_LOG(isFormItemChecked(F("pdebug")));
# endif // if P176_DEBUG
P176_SET_QUIET_LOG(isFormItemChecked(F("pquiet")));

success = true;
break;
}

case PLUGIN_INIT:
{
initPluginTaskData(event->TaskIndex, new (std::nothrow) P176_data_struct(event));
P176_data_struct *P176_data = static_cast<P176_data_struct *>(getPluginTaskData(event->TaskIndex));

success = (nullptr != P176_data) && P176_data->init();

break;
}

case PLUGIN_READ:
{
P176_data_struct *P176_data = static_cast<P176_data_struct *>(getPluginTaskData(event->TaskIndex));

success = (nullptr != P176_data) && P176_data->plugin_read(event);

break;
}

case PLUGIN_FIFTY_PER_SECOND:
{
P176_data_struct *P176_data = static_cast<P176_data_struct *>(getPluginTaskData(event->TaskIndex));

success = (nullptr != P176_data) && P176_data->plugin_fifty_per_second(event);

break;
}

case PLUGIN_GET_CONFIG_VALUE:
{
P176_data_struct *P176_data = static_cast<P176_data_struct *>(getPluginTaskData(event->TaskIndex));

success = (nullptr != P176_data) && P176_data->plugin_get_config_value(event, string);

break;
}
}
return success;
}

#endif // USES_P176
Loading