Skip to content

Commit

Permalink
Merge pull request letscontrolit#5148 from tonhuisman/feature/P176-ad…
Browse files Browse the repository at this point in the history
…d-plugin-victron-ve.direct

[P176] Add plugin Communication - Victron VE.Direct
  • Loading branch information
TD-er authored Dec 14, 2024
2 parents 30e4197 + eaface4 commit 13555c1
Show file tree
Hide file tree
Showing 10 changed files with 1,067 additions and 1 deletion.
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 @@ -400,6 +400,7 @@ There are different released versions of ESP Easy:
":ref:`P172_page`","|P172_status|","P172"
":ref:`P173_page`","|P173_status|","P173"
":ref:`P175_page`","|P175_status|","P175"
":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`, :ref:`P175_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 @@ -49,3 +49,16 @@
.. |P175_maintainer| replace:: `.`
.. |P175_compileinfo| replace:: `.`
.. |P175_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

0 comments on commit 13555c1

Please sign in to comment.