mirror of
https://github.com/letscontrolit/ESPEasy.git
synced 2026-09-12 09:36:59 +00:00
Merge branch 'mega' into build/reduce_minimal_bin_size
This commit is contained in:
+227
-8
@@ -28,18 +28,235 @@ Supported hardware
|
||||
|
||||
|P078_usedby|
|
||||
|
||||
Sensor
|
||||
^^^^^^
|
||||
Introduction
|
||||
------------
|
||||
|
||||
Eastron is the manufacturer of many energy meters.
|
||||
These can be had in various form factors, from DIN rail modules to panel meters.
|
||||
There are many variations in number of phases, direct metering or using current transformer clamps (``CT`` in the name).
|
||||
|
||||
Apart from all these differences in features, there are also variations in interfacing to read the meter.
|
||||
The ESPEasy plugin for these meters only can interact with them using Modbus RTU.
|
||||
|
||||
This Modbus RTU protocol uses the half-duplex RS485 serial interface.
|
||||
|
||||
|
||||
RS485 Modbus Interface
|
||||
----------------------
|
||||
|
||||
RS485 allows to 'daisy chain' various meters on to the same bus over long distances. (upto 1200 m).
|
||||
|
||||
Interfacing RS485 devices with an ESP module requires some chip to convert typical serial signals to the differential signal required for RS485 devices.
|
||||
Commonly used chips are the MAX485 or ADM483, but there are lots of other variants which can also be used.
|
||||
|
||||
Typically such a chip has a data input (``DI`` or ``D``) and data output (``RO`` or ``R``).
|
||||
To toggle between sending and listening, the ``DE`` pin must be pulled 'high' when sending data.
|
||||
|
||||
The ``/RE`` pin is logically inverted and thus can be connected to the ``DE`` pin.
|
||||
This allows for a single GPIO pin to be used to toggle between sending and receiving data.
|
||||
|
||||
.. note::
|
||||
Most RS485 transceiver chips, like the MAX485, allow for a supply voltage (for the chip) higher than the 3.3V used for the ESP. Some even will not work reliable at 3.3 Volt.
|
||||
Keep in mind that on most RS485 transceiver boards, all microcontroller connected pins have a pull-up resistor to the supplied VCC.
|
||||
Meaning when you connect this chip to 5V, the signal connected to the ESP pins should be converted down to the lower 3.3V levels the ESP board uses.
|
||||
|
||||
|
||||
Manual switching of transmitter/receiver without collision detection
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The most commonly used schematic for connecting a RS485 transceiver chip to a microcontroller, like the ESP boards:
|
||||
|
||||
|
||||
.. code-block::
|
||||
|
||||
VCC ---------------+
|
||||
|
|
||||
+-------x-------+
|
||||
RXD <------| RO |
|
||||
| B|-----------<> B
|
||||
TXD ------>| DI ADM483 |
|
||||
ESP | | RS485 bus side
|
||||
RTS --+--->| DE |
|
||||
| | A|-----------<> A
|
||||
+----| /RE |
|
||||
+-------x-------+
|
||||
|
|
||||
GND
|
||||
|
||||
|
||||
Collision detection circuit
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
By connecting the ``/RE`` pin to GND, the transceiver chip has "receiving" enabled even when sending.
|
||||
This allows to detect "collision", when other devices are sending data when the ESP attempts to send data.
|
||||
|
||||
If there is only a single 'master' on the RS485 bus, there is no need for using this kind of setup.
|
||||
|
||||
.. note:: Only ESP32 (and variants) can handle collision detection on RS485.
|
||||
|
||||
.. code-block::
|
||||
|
||||
VCC ---------------+
|
||||
|
|
||||
+-------x-------+
|
||||
RXD <------| R |
|
||||
| B|----------<> B
|
||||
TXD ------>| D ADM483 |
|
||||
ESP32 | | RS485 bus side
|
||||
RTS ------>| DE |
|
||||
| A|----------<> A
|
||||
+----| /RE |
|
||||
| +-------x-------+
|
||||
| |
|
||||
GND GND
|
||||
|
||||
Wiring RS485 bus
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
The RS485 bus transports a differential signal to all connected modules.
|
||||
|
||||
There is some debate about whether the GND should also be connected, since the ``A`` and ``B`` line carry all what is needed.
|
||||
When there is no common GND among modules and the ESP board, the voltage on both A and B might be too high compared to the module's GND, which may cause damage to the module.
|
||||
Most cheap MAX485 boards have a 20k pull-up resistor on the ``A`` line and a 20k pull-down resistor on the ``B`` line, to prevent high voltage differences on the MAX485 chip.
|
||||
But this does not protect the modules when no GND line is present.
|
||||
So when possible, also wire the GND between modules and the GND of the RS485 transceiver chip.
|
||||
|
||||
Eastron modules do not appear to have a direct connection between GND and mains voltage. But this is not guaranteed for all RS485 modules being sold.
|
||||
So the best approach is to use proper isolation when using mains connected devices.
|
||||
For example, the ``ADM2483`` is a galvanic isolated RS485 transceiver chip.
|
||||
|
||||
It is best to twist the A/B lines when using longer wires to reduce effects of noise.
|
||||
|
||||
All modules on the same bus must be daisy chained from one to the other.
|
||||
A star topology is not allowed.
|
||||
|
||||
.. image:: RS485_wiring.png
|
||||
:width: 918px
|
||||
:height: 327px
|
||||
:scale: 50 %
|
||||
:alt: RS485 Wiring
|
||||
:align: center
|
||||
|
||||
|
||||
Most MAX485 modules already have a 120 Ohm resistor over the A/B pins.
|
||||
It is adviced to add another 120 Ohm resistor over the A/B connector of the last module on the bus to act as a 'terminator', to reduce signal reflections.
|
||||
For short links, a second 120 Ohm resistor is almost never needed, especially given the rather low baud rate used by these modules.
|
||||
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
See: :ref:`SerialHelper_page`
|
||||
|
||||
.. note:: All Eastron modules on the same ESPEasy node must use the same serial settings, like baud rate and serial port config.
|
||||
|
||||
**TODO**: Complete this documentation...
|
||||
Device Settings
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
.. Commands available
|
||||
.. ^^^^^^^^^^^^^^^^^^
|
||||
The Model Type selector only limits which values can be selected in the Output Configuration section.
|
||||
This way, only supported register reads will be offered to select.
|
||||
|
||||
.. .. include:: P078_commands.repl
|
||||
It also is used to simplify the generated strings and suggested task value names.
|
||||
For example on 1 phase modules, there is no need to specify the phase.
|
||||
|
||||
All Eastron module versions use the same internal register numbering schema.
|
||||
For example, register ``0x0000`` is always about measuring the voltage between L1 and neutral.
|
||||
But register ``0x0002`` (voltage between L2 and neutral) is only supported on 3-phase modules.
|
||||
|
||||
So selecting the wrong Model Type may still work fine, only it is possible not all supported options can be selected or unsupported options may be selected.
|
||||
|
||||
Output Configuration
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
ESPEasy only allows upto 4 task values per task.
|
||||
However the modules made by Eastron allow for much more values to be collected.
|
||||
For example, the SDM630 has almost 100 values that can be read.
|
||||
|
||||
To allow collecting more values from a module, multiple tasks may be added which all have the same serial port settings and modbus address configured.
|
||||
|
||||
All task running the Eastron plugin will add their configured registers to be read, including the modbus address, are put in a queue in ESPEasy.
|
||||
This queue is being read continuously and the read values will be immediately set on the output task values of the matching Eastron task.
|
||||
|
||||
ESPEasy tries to read the next register 10x per second.
|
||||
|
||||
The set baud rate determines the number of registers that can be read per second:
|
||||
|
||||
* @2400 baud: 3 - 5 registers per second.
|
||||
* @9600 baud: 10 registers per second.
|
||||
|
||||
When a task is being read (e.g. per set "Interval" or when calling ``TaskRun`` from the rules), the last read values will be sent to any connected controller and events will be generated with these values.
|
||||
Thus on a setup which in total reads more than 10 registers (either on a single or multiple modules) can not provide new values on each task run when set to an interval of 1 second.
|
||||
|
||||
With "Stats" enabled, the recorded min/max peak values will be updated on each read of that register.
|
||||
So when the "Interval" is set to a long interval, the peaks may be taken from the frequent reads inbetween.
|
||||
|
||||
|
||||
Units of Measure
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
Depending on the model, these Eastron modules can provide various units of measure:
|
||||
|
||||
* Line voltage and THD% (total harmonic distortion) of all phases
|
||||
* Line Frequency
|
||||
* Currents, Current demands and current THD% of all phases
|
||||
* Power, maximum power demand and power factor
|
||||
* Active energy imported and exported
|
||||
* Reactive energy imported and exported
|
||||
|
||||
The options with "Demand" in their name, are measurements within a time window.
|
||||
The default "Demand Time Interval" is a preset period of 60 minutes.
|
||||
|
||||
ESPEasy currently does not support setting the time on these modules, or triggering a new start of a demand period.
|
||||
Thus the start of such interval may not be on the start of each hour.
|
||||
|
||||
|
||||
Set Baudrate/ID
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
The default baud rate of 2400 of most of these sensors may be too low for using Software Serial.
|
||||
On ESP8266, the module may rather frequently crash due to watch dog resets when running Software Serial at such low baud rates.
|
||||
|
||||
It is adviced to set the module to 9600 baud.
|
||||
|
||||
Using a higher baud rate will also take less resources of the ESPEasy node.
|
||||
For example, writing the 8 byte command to the module takes roughly 35 msec @2400 baud and only 13 msec @9600 baud.
|
||||
|
||||
When running at 9600 baud, ESPEasy can typically read upto 10 registers per second for all connected Eastron modules.
|
||||
|
||||
On some modules, like the SDM120, the module settings can only be set via the Modbus interface.
|
||||
|
||||
When an Eastron task is enabled in ESPEasy, it will continously read the set registers on all connected Eastron modules.
|
||||
This will affect the commands used to change settings and thus this continous reading must be paused.
|
||||
|
||||
The commands in ESPEasy to change settings have a last optional parameter ``node_id``, which is the current set address of the module.
|
||||
If none is given, the factory default address ``1`` is assumed.
|
||||
Valid node IDs are ``1 ... 247``.
|
||||
|
||||
Steps to change settings:
|
||||
|
||||
* Pause continously reading registers on the module by calling ``Eastron,Pause`` in ESPEasy.
|
||||
* Enable ``-set-`` mode on the module by pressing the button on the module for 3 seconds.
|
||||
* To change the baud rate, call ``Eastron,SetBaud,<new_baudrate>[,<node_id>]`` in ESPEasy, where the new baud rate parameter can be given either as a value ``0`` ... ``5``, or the exact (supported) baud rate. (see description below).
|
||||
* To change the node ID (address), call ``Eastron,SetID,<new_id>[,<node_id>]``.
|
||||
* Press the button on the module again for 3 seconds to save the new values.
|
||||
* Reading the set registers can be resumed with the command ``Eastron,Resume``
|
||||
|
||||
Since changing the node ID is effective immediately, it makes sense to change the baud rate first and then the ID if both need to be changed.
|
||||
|
||||
.. note:: The new baud rate will only be used after a power cycle of the module.
|
||||
|
||||
|
||||
|
||||
|
||||
Sensor
|
||||
------
|
||||
|
||||
|
||||
|
||||
Commands available
|
||||
------------------
|
||||
|
||||
.. include:: P078_commands.repl
|
||||
|
||||
.. Events
|
||||
.. ~~~~~~
|
||||
@@ -52,8 +269,10 @@ Change log
|
||||
.. versionchanged:: 2.0
|
||||
...
|
||||
|
||||
|added|
|
||||
Major overhaul for 2.0 release.
|
||||
|added| 2023-08-06:
|
||||
* Add support for many more modules
|
||||
* Add commands to set ID and baud rate
|
||||
* Massively reduce use of resources when reading sensor
|
||||
|
||||
.. versionadded:: 1.0
|
||||
...
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
.. csv-table::
|
||||
:header: "Command Syntax", "Extra information"
|
||||
:widths: 20, 30
|
||||
|
||||
"
|
||||
|
||||
| ``Eastron,Pause``
|
||||
| ``Eastron,Resume``
|
||||
|
||||
","
|
||||
| ``Pause`` and ``Resume`` refer to the continuous reading of set registers of all connected Eastron modules.
|
||||
| When changing a setting on a specific module, this background register reading must be temporarily halted by calling ``Eastron,Pause`` first.
|
||||
| All commands are case insensitive.
|
||||
|
||||
"
|
||||
"
|
||||
|
||||
| ``Eastron,SetBaud,<new_baudrate>[,<node_id>]``
|
||||
| ``Eastron,SetID,<new_id>[,<node_id>]``
|
||||
|
||||
","
|
||||
| The optional ``<node_id>`` parameter indicates the current Modbus address. When not given, the default of address ``1`` is assumed.
|
||||
| Valid node IDs are ``1 ... 247``.
|
||||
| Call ``Eastron,Pause`` before changing settings on the module.
|
||||
| On some modules, like the SDM120, the user must press the button for 3 seconds to enter ``-set-`` mode.
|
||||
| After enabling the ``-set-`` mode, these commands can be given and then the user must press the button for 3 seconds to store the changed settings.
|
||||
|
|
||||
| Changing ID is active immediately, thus the new ID must be used for commands given after changing the ID.
|
||||
| Change of baud rate is only active after the module is power cycled.
|
||||
|
|
||||
| Allowed baud rates:
|
||||
| SDM120 / SDM230:
|
||||
| ``0`` = 2400 baud (default)
|
||||
| ``1`` = 4800 baud
|
||||
| ``2`` = 9600 baud
|
||||
| ``5`` = 1200 baud
|
||||
|
|
||||
| SDM320 / SDM530Y:
|
||||
| ``0`` = 2400 baud
|
||||
| ``1`` = 4800 baud
|
||||
| ``2`` = 9600 baud (default)
|
||||
| ``5`` = 1200 baud
|
||||
|
|
||||
| SDM630 / SDM72 / SDM72V2:
|
||||
| ``0`` = 2400 baud
|
||||
| ``1`` = 4800 baud
|
||||
| ``2`` = 9600 baud (default)
|
||||
| ``3`` = 19200 baud
|
||||
| ``4`` = 38400 baud
|
||||
|
|
||||
| The baud rate parameter can be given either as a value ``0`` ... ``5``, or the exact (supported) baud rate.
|
||||
|
|
||||
| All commands are case insensitive.
|
||||
|
||||
"
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 105 KiB |
@@ -99,22 +99,22 @@
|
||||
.. _P077_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P077_CSE7766.ino
|
||||
.. |P077_usedby| replace:: `Sonoff S31, Sonoff Pow R2, Sonoff POW R3xx(D), Sonoff Dual R3`
|
||||
.. |P077_shortinfo| replace:: This plugin is specifically made for Sonoff devices Sonoff S31, Sonoff Pow R2 and Sonoff POW/Dual R3 models.
|
||||
.. |P077_maintainer| replace:: `.`
|
||||
.. |P077_maintainer| replace:: TD-er
|
||||
.. |P077_compileinfo| replace:: `.`
|
||||
.. |P077_usedlibraries| replace:: `ESPEasySerial`
|
||||
|
||||
.. |P078_name| replace:: :cyan:`Eastron SDM120C/220T/230/630`
|
||||
.. |P078_name| replace:: :cyan:`Eastron SDMxxx Modbus`
|
||||
.. |P078_type| replace:: :cyan:`Energy (AC)`
|
||||
.. |P078_typename| replace:: :cyan:`Energy (AC) - Eastron SDM120C/220T/230/630`
|
||||
.. |P078_typename| replace:: :cyan:`Energy (AC) - Eastron SDMxxx Modbus`
|
||||
.. |P078_porttype| replace:: `Serial`
|
||||
.. |P078_status| replace:: :yellow:`ENERGY`
|
||||
.. |P078_github| replace:: P078_Eastron.ino
|
||||
.. _P078_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P078_Eastron.ino
|
||||
.. |P078_usedby| replace:: `.`
|
||||
.. |P078_usedby| replace:: `Modbus variants of Eastron SDM120M, SDM120CT, SDM220M, SDM230M, SDM72M, SDM72CTM, SDM630, SDM630MCT, SDM630-EV, SDM54-M, SDM320Y, DDM18SD, SMART X-96, MCS-U22`
|
||||
.. |P078_shortinfo| replace:: `.`
|
||||
.. |P078_maintainer| replace:: `.`
|
||||
.. |P078_maintainer| replace:: TD-er
|
||||
.. |P078_compileinfo| replace:: `.`
|
||||
.. |P078_usedlibraries| replace:: `.`
|
||||
.. |P078_usedlibraries| replace:: https://github.com/reaper7/SDM_Energy_Meter
|
||||
|
||||
.. |P079_name| replace:: :cyan:`Wemos / Lolin Motorshield`
|
||||
.. |P079_type| replace:: :cyan:`Motor`
|
||||
|
||||
@@ -258,6 +258,17 @@ String ESPeasySerial::getLogString() const {
|
||||
return getSerialConfig().getLogString();
|
||||
}
|
||||
|
||||
bool ESPeasySerial::setRS485Mode(int8_t rtsPin, bool enableCollisionDetection) {
|
||||
if (_serialPort != nullptr) {
|
||||
#ifdef ESP32
|
||||
return _serialPort->setRS485Mode(rtsPin, enableCollisionDetection);
|
||||
#else
|
||||
return _serialPort->setRS485Mode(rtsPin, false);
|
||||
#endif
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ESPeasySerial::isValid() const {
|
||||
// FIXME TD-er: Must call isValid() on the individual _serialPort types
|
||||
return _serialPort != nullptr;
|
||||
|
||||
@@ -163,6 +163,11 @@ public:
|
||||
return String();
|
||||
}
|
||||
|
||||
// Try enabling RTS pin and set to UART_RS485_HALF_DUPLEX
|
||||
// RTS pin can then be connected to ~RE/DE pin of MAX485
|
||||
// @retval True when supported and successful.
|
||||
bool setRS485Mode(int8_t rtsPin, bool enableCollisionDetection = false);
|
||||
|
||||
private:
|
||||
|
||||
bool isValid() const;
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
|
||||
#include "ESPEasySerialType.h"
|
||||
|
||||
#ifdef ESP32
|
||||
#include <hal/uart_types.h>
|
||||
#endif
|
||||
|
||||
Port_ESPEasySerial_HardwareSerial_t::Port_ESPEasySerial_HardwareSerial_t() {}
|
||||
|
||||
@@ -268,3 +271,19 @@ size_t Port_ESPEasySerial_HardwareSerial_t::setTxBufferSize(size_t new_size)
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
bool Port_ESPEasySerial_HardwareSerial_t::setRS485Mode(int8_t rtsPin, bool enableCollisionDetection)
|
||||
{
|
||||
#ifdef ESP32
|
||||
if (_serial != nullptr) {
|
||||
if (rtsPin >= 0) {
|
||||
return _serial->setPins(-1, -1, -1, rtsPin) &&
|
||||
_serial->setHwFlowCtrlMode(UART_HW_FLOWCTRL_RTS) &&
|
||||
_serial->setMode(enableCollisionDetection ? UART_MODE_RS485_COLLISION_DETECT : UART_MODE_RS485_HALF_DUPLEX);
|
||||
}
|
||||
_serial->setMode(UART_MODE_UART);
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
@@ -47,6 +47,11 @@ public:
|
||||
size_t setRxBufferSize(size_t new_size);
|
||||
size_t setTxBufferSize(size_t new_size);
|
||||
|
||||
// Try enabling RTS pin and set to UART_RS485_HALF_DUPLEX
|
||||
// RTS pin can then be connected to ~RE/DE pin of MAX485
|
||||
// @retval True when supported and successful.
|
||||
bool setRS485Mode(int8_t rtsPin, bool enableCollisionDetection = false);
|
||||
|
||||
private:
|
||||
|
||||
HardwareSerial *_serial = nullptr;
|
||||
|
||||
@@ -135,4 +135,10 @@ size_t Port_ESPEasySerial_I2C_SC16IS752_t::setTxBufferSize(size_t new_size)
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Port_ESPEasySerial_I2C_SC16IS752_t::setRS485Mode(int8_t rtsPin, bool enableCollisionDetection)
|
||||
{
|
||||
// TODO TD-er: Check if we can enable RTS on this chip
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif // ifndef DISABLE_SC16IS752_Serial
|
||||
|
||||
@@ -44,6 +44,7 @@ public:
|
||||
|
||||
size_t setRxBufferSize(size_t new_size);
|
||||
size_t setTxBufferSize(size_t new_size);
|
||||
bool setRS485Mode(int8_t rtsPin, bool enableCollisionDetection = false);
|
||||
|
||||
private:
|
||||
|
||||
|
||||
@@ -169,4 +169,11 @@ size_t Port_ESPEasySerial_SW_Serial_t::setTxBufferSize(size_t new_size)
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Port_ESPEasySerial_SW_Serial_t::setRS485Mode(int8_t rtsPin, bool enableCollisionDetection)
|
||||
{
|
||||
// TODO TD-er: Check if we can include toggling this pin in the SW serial lib
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
#endif // if USES_SW_SERIAL
|
||||
|
||||
@@ -46,6 +46,7 @@ public:
|
||||
size_t setRxBufferSize(size_t new_size);
|
||||
size_t setTxBufferSize(size_t new_size);
|
||||
|
||||
bool setRS485Mode(int8_t rtsPin, bool enableCollisionDetection = false);
|
||||
private:
|
||||
|
||||
#if USES_LATEST_SOFTWARE_SERIAL_LIBRARY
|
||||
|
||||
@@ -261,6 +261,12 @@ size_t Port_ESPEasySerial_USBCDC_t::setTxBufferSize(size_t new_size)
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Port_ESPEasySerial_USBCDC_t::setRS485Mode(int8_t rtsPin, bool enableCollisionDetection)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
int Port_ESPEasySerial_USBCDC_t::getBaudRate() const
|
||||
{
|
||||
if (_serial != nullptr) {
|
||||
|
||||
@@ -41,6 +41,8 @@ public:
|
||||
size_t setRxBufferSize(size_t new_size);
|
||||
size_t setTxBufferSize(size_t new_size);
|
||||
|
||||
bool setRS485Mode(int8_t rtsPin, bool enableCollisionDetection = false);
|
||||
|
||||
virtual int getBaudRate() const override;
|
||||
|
||||
private:
|
||||
|
||||
@@ -195,4 +195,10 @@ size_t Port_ESPEasySerial_USB_HWCDC_t::setTxBufferSize(size_t new_size)
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Port_ESPEasySerial_USB_HWCDC_t::setRS485Mode(int8_t rtsPin, bool enableCollisionDetection)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
#endif // if USES_HWCDC
|
||||
|
||||
@@ -40,6 +40,8 @@ public:
|
||||
size_t setRxBufferSize(size_t new_size);
|
||||
size_t setTxBufferSize(size_t new_size);
|
||||
|
||||
bool setRS485Mode(int8_t rtsPin, bool enableCollisionDetection = false);
|
||||
|
||||
private:
|
||||
|
||||
# if ARDUINO_USB_CDC_ON_BOOT // Serial used for USB CDC
|
||||
|
||||
@@ -72,10 +72,13 @@ public:
|
||||
|
||||
virtual operator bool() const = 0;
|
||||
|
||||
virtual void setDebugOutput(bool) = 0;
|
||||
virtual void setDebugOutput(bool) = 0;
|
||||
|
||||
virtual size_t setRxBufferSize(size_t new_size) = 0;
|
||||
virtual size_t setTxBufferSize(size_t new_size) = 0;
|
||||
virtual bool setRS485Mode(int8_t rtsPin, bool enableCollisionDetection) = 0;
|
||||
|
||||
|
||||
virtual size_t setRxBufferSize(size_t new_size) = 0;
|
||||
virtual size_t setTxBufferSize(size_t new_size) = 0;
|
||||
|
||||
const ESPEasySerialConfig& getSerialConfig() const {
|
||||
return _config;
|
||||
|
||||
+148
-49
@@ -1,6 +1,4 @@
|
||||
## WARNING! library initialization changed! ##
|
||||
<i>old library version is available at [old_template branch](https://github.com/reaper7/SDM_Energy_Meter/tree/old_template)</i><br>
|
||||
## Library for reading SDM120 SDM220 SDM230 SDM630 Modbus Energy meters. ##
|
||||
## Library for reading SDM72 SDM120 SDM220 SDM230 SDM630 DDM18SD Modbus Energy meters. ##
|
||||
|
||||
### SECTIONS: ###
|
||||
#### 1. [INTRODUCTION](#introduction) ####
|
||||
@@ -8,15 +6,20 @@
|
||||
#### 3. [CONFIGURING](#configuring) ####
|
||||
#### 4. [INITIALIZING](#initializing) ####
|
||||
#### 5. [READING](#reading) ####
|
||||
#### 6. [DEBUGING](#debuging) ####
|
||||
#### 6. [PROBLEMS](#problems) ####
|
||||
#### 7. [CREDITS](#credits) ####
|
||||
|
||||
---
|
||||
|
||||
### Introduction: ###
|
||||
This library allows you reading SDM module(s) using:
|
||||
- [x] Hardware Serial (<i>recommended option, smallest number of reads errors</i>) <b><i>or</i></b>
|
||||
- [x] Software Serial (<i>[library for ESP8266](https://github.com/plerup/espsoftwareserial)</i>)
|
||||
- [x] Hardware Serial (<i><b>recommended option</b>, smallest number of reads errors, especially for esp8266</i>) <b><i>or</i></b>
|
||||
- [x] Software Serial, attached as core libraries for ESP8266 and AVR or as external lib for ESP32</br>
|
||||
(<i>the new version of esp Software Serial library</br>
|
||||
has a different initialization compared to avr!</br>
|
||||
<b>This version of SDM library (>=2.2.2) works only with esp Software Serial 8.0.1 or higher!!!</b></br>
|
||||
If you have an older esp Software Serial version</br>
|
||||
then use other SDM library, details below the Credits section</i>)</br>
|
||||
|
||||
you also need rs232<->rs485 converter:
|
||||
- [x] with automatic flow direction control (<i>look at images below</i>) <b><i>or</i></b>
|
||||
@@ -24,13 +27,14 @@ you also need rs232<->rs485 converter:
|
||||
(<i>in this case MAX485 DE and RE pins must be connected together to one of uC pin</br>
|
||||
and this pin must be passed when initializing the library</i>)
|
||||
|
||||
_Tested on Wemos D1 Mini with Arduino IDE 1.8.3-1.9.0b & ESP8266 core 2.3.0-2.4.1_
|
||||
_Tested on Wemos D1 Mini with Arduino IDE 1.8.3-1.8.10 & ESP8266 core 2.3.0-2.5.2_
|
||||
|
||||
---
|
||||
|
||||
### Screenshots: ###
|
||||
<img src="https://github.com/reaper7/SDM_Energy_Meter/blob/master/img/hardware_sdm220_1.jpg" height="330"><img src="https://github.com/reaper7/SDM_Energy_Meter/blob/master/img/hardware_sdm220_2.jpg" height="330"></br>
|
||||
<p align="center">
|
||||
<img src="https://github.com/reaper7/SDM_Energy_Meter/blob/master/img/hardware_sdm220_1.jpg" height="330"></br>
|
||||
<img src="https://github.com/reaper7/SDM_Energy_Meter/blob/master/img/hardware_sdm220_2.jpg" height="330"></br>
|
||||
<img src="https://github.com/reaper7/SDM_Energy_Meter/blob/master/img/livepage.gif"></br>
|
||||
<i>live page example (extended) screenshot</i>
|
||||
</p>
|
||||
@@ -39,14 +43,26 @@ _Tested on Wemos D1 Mini with Arduino IDE 1.8.3-1.9.0b & ESP8266 core 2.3.0-2.4.
|
||||
|
||||
### Configuring: ###
|
||||
Default configuration is specified in the [SDM.h](https://github.com/reaper7/SDM_Energy_Meter/blob/master/SDM.h#L18) file, and parameters are set to:</br>
|
||||
<i>Software Serial, baud 4800, uart config SERIAL_8N1, without DE/RE pin</i>.</br>
|
||||
<i>Software Serial mode, baud 4800, uart config SERIAL_8N1, without DE/RE pin,</br>
|
||||
uart pins for esp32 hwserial and esp32/esp8266/avr swserial as NOT_A_PIN (-1).</br></br>
|
||||
For esp32 hwserial this means using the default pins for the selected uart port,</br>
|
||||
specified in the core library (HardwareSerial.cpp).</br>
|
||||
For swserial option (esp32/esp8266/avr) is necessary</br>
|
||||
to specify the pin numbers, as described below.</i>
|
||||
|
||||
User can set the parameters in two ways:
|
||||
- by editing the [SDM_Config_User.h](https://github.com/reaper7/SDM_Energy_Meter/blob/master/SDM_Config_User.h) file
|
||||
- by passing values during initialization (section below)
|
||||
|
||||
[SDM_Config_User.h](https://github.com/reaper7/SDM_Energy_Meter/blob/master/SDM_Config_User.h) file includes also two parameters that can be adjusted depending on your needs:
|
||||
- WAITING_TURNAROUND_DELAY (default set to 200ms) defines the time (after sending the query) for the response from the slave device.
|
||||
If the slave device does not send the required number of bytes (FRAMESIZE) within this time, an SDM_ERR_TIMEOUT error will be returned.
|
||||
- RESPONSE_TIMEOUT (default set to 500ms) defines the time (after sending the request and receiving the reply) to a possible response
|
||||
from other slave devices on the bus, during this time it will not be possible to execute another query.
|
||||
It is a protection time for devices that are not able to quickly respond to inquiries.
|
||||
|
||||
NOTE for Hardware Serial mode: <i>to force the Hardware Serial mode,</br>
|
||||
user must edit the corresponding entry in [SDM_Config_User.h](https://github.com/reaper7/SDM_Energy_Meter/blob/master/SDM_Config_User.h#L13) file.</br>
|
||||
user must edit the corresponding entry in [SDM_Config_User.h](https://github.com/reaper7/SDM_Energy_Meter/blob/master/SDM_Config_User.h#L17) file.</br>
|
||||
adding #define USE_HARDWARESERIAL to the main ino file is not enough.</i>
|
||||
|
||||
---
|
||||
@@ -58,20 +74,31 @@ initialization is limited to passing serial port reference (software or hardware
|
||||
and looks as follows:
|
||||
```cpp
|
||||
//lib init when Software Serial is used:
|
||||
#include <SoftwareSerial.h>
|
||||
#include <SDM.h>
|
||||
#include <SoftwareSerial.h>
|
||||
|
||||
SoftwareSerial swSerSDM(13, 15);
|
||||
|
||||
// _software serial reference
|
||||
// for ESP8266 and ESP32
|
||||
SoftwareSerial swSerSDM;
|
||||
// _______________________________software serial reference
|
||||
// |
|
||||
SDM sdm(swSerSDM);
|
||||
|
||||
|
||||
// for AVR
|
||||
SoftwareSerial swSerSDM(SDM_RX_PIN, SDM_TX_PIN);
|
||||
// | |_tx pin definition(from SDM_Config_User.h)
|
||||
// |_____________rx pin definition(from SDM_Config_User.h)
|
||||
//
|
||||
// _______________________________software serial reference
|
||||
// |
|
||||
SDM sdm(swSerSDM);
|
||||
```
|
||||
|
||||
```cpp
|
||||
//lib init when Hardware Serial is used:
|
||||
#include <SDM.h>
|
||||
|
||||
// _hardware serial reference
|
||||
// _________________________________hardware serial reference
|
||||
// |
|
||||
SDM sdm(Serial);
|
||||
```
|
||||
@@ -79,28 +106,63 @@ If the user wants to temporarily change the configuration during the initializat
|
||||
then can pass additional parameters as below:
|
||||
```cpp
|
||||
//lib init when Software Serial is used:
|
||||
#include <SoftwareSerial.h>
|
||||
#include <SDM.h>
|
||||
#include <SoftwareSerial.h>
|
||||
|
||||
SoftwareSerial swSerSDM(13, 15);
|
||||
// for ESP8266 and ESP32
|
||||
SoftwareSerial swSerSDM;
|
||||
// ________________________________________software serial reference
|
||||
// | __________________________________baudrate(optional, default from SDM_Config_User.h)
|
||||
// | | _______________________dere pin for max485(optional, default from SDM_Config_User.h)
|
||||
// | | | _________software uart config(optional, default from SDM_Config_User.h)
|
||||
// | | | | _____rx pin number(optional, default from SDM_Config_User.h)
|
||||
// | | | | | _tx pin number(optional, default from SDM_Config_User.h)
|
||||
// | | | | | |
|
||||
SDM sdm(swSerSDM, 9600, NOT_A_PIN, SWSERIAL_8N1, 13, 15);
|
||||
|
||||
// __________________software serial reference
|
||||
// | ____________baudrate(optional, default from SDM_Config_User.h)
|
||||
// | | _dere pin for max485(optional, default from SDM_Config_User.h)
|
||||
|
||||
// for AVR
|
||||
SoftwareSerial swSerSDM(10, 11);
|
||||
// ________________________________________software serial reference
|
||||
// | __________________________________baudrate(optional, default from SDM_Config_User.h)
|
||||
// | | _______________________dere pin for max485(optional, default from SDM_Config_User.h)
|
||||
// | | |
|
||||
SDM sdm(swSerSDM, 9600, NOT_A_PIN);
|
||||
```
|
||||
|
||||
|
||||
```cpp
|
||||
//lib init when Hardware Serial is used:
|
||||
#include <SDM.h>
|
||||
|
||||
// _____________________________________hardware serial reference
|
||||
// | _______________________________baudrate(optional, default from SDM_Config_User.h)
|
||||
// | | ____________________dere pin for max485(optional, default from SDM_Config_User.h)
|
||||
// | | | ________hardware uart config(optional, default from SDM_Config_User.h)
|
||||
// | | | | _swap hw serial pins from 3/1 to 13/15(optional, default from SDM_Config_User.h)
|
||||
// for ESP8266
|
||||
// ______________________________________hardware serial reference
|
||||
// | ________________________________baudrate(optional, default from SDM_Config_User.h)
|
||||
// | | _____________________dere pin for max485(optional, default from SDM_Config_User.h)
|
||||
// | | | _________hardware uart config(optional, default from SDM_Config_User.h)
|
||||
// | | | | __swap hw serial pins from 3/1 to 13/15(optional, default from SDM_Config_User.h)
|
||||
// | | | | |
|
||||
SDM sdm(Serial, 9600, NOT_A_PIN, SERIAL_8N1, false);
|
||||
|
||||
|
||||
// for ESP32
|
||||
// ______________________________________hardware serial reference
|
||||
// | ________________________________baudrate(optional, default from SDM_Config_User.h)
|
||||
// | | _____________________dere pin for max485(optional, default from SDM_Config_User.h)
|
||||
// | | | _________hardware uart config(optional, default from SDM_Config_User.h)
|
||||
// | | | | _____rx pin number(optional, default from SDM_Config_User.h)
|
||||
// | | | | | _tx pin number(optional, default from SDM_Config_User.h)
|
||||
// | | | | | |
|
||||
SDM sdm(Serial, 9600, NOT_A_PIN, SERIAL_8N1, 13, 15);
|
||||
|
||||
|
||||
// for AVR
|
||||
// ______________________________________hardware serial reference
|
||||
// | ________________________________baudrate(optional, default from SDM_Config_User.h)
|
||||
// | | _____________________dere pin for max485(optional, default from SDM_Config_User.h)
|
||||
// | | | _________hardware uart config(optional, default from SDM_Config_User.h)
|
||||
// | | | |
|
||||
// | | | |
|
||||
SDM sdm(Serial, 9600, NOT_A_PIN, SERIAL_8N1);
|
||||
```
|
||||
NOTE for ESP8266: <i>when GPIO15 is used (especially for swapped hardware serial):</br>
|
||||
some converters (like mine) have built-in pullup resistors on TX/RX lines from rs232 side,</br>
|
||||
@@ -111,22 +173,22 @@ to ensure low level on GPIO15 by built-in in most ESP8266 modules pulldown resis
|
||||
---
|
||||
|
||||
### Reading: ###
|
||||
List of available registers for SDM120/220/230/630:</br>
|
||||
https://github.com/reaper7/SDM_Energy_Meter/blob/master/SDM.h#L50
|
||||
List of available registers for SDM72/120/220/230/630:</br>
|
||||
https://github.com/reaper7/SDM_Energy_Meter/blob/master/SDM.h#L103
|
||||
```cpp
|
||||
//reading voltage from SDM with slave address 0x01 (default)
|
||||
// __________register name
|
||||
// |
|
||||
float voltage = sdm.readVal(SDM220T_VOLTAGE);
|
||||
// ____register name
|
||||
// |
|
||||
float voltage = sdm.readVal(SDM_PHASE_1_VOLTAGE);
|
||||
|
||||
//reading power from 1st SDM with slave address ID = 0x01
|
||||
//reading power from 2nd SDM with slave address ID = 0x02
|
||||
//useful with several meters on RS485 line
|
||||
// __________register name
|
||||
// | ____SDM device ID
|
||||
// | |
|
||||
float power1 = sdm.readVal(SDM220T_POWER, 0x01);
|
||||
float power2 = sdm.readVal(SDM220T_POWER, 0x02);
|
||||
// _______register name
|
||||
// | SDM device ID
|
||||
// | |
|
||||
float power1 = sdm.readVal(SDM_PHASE_1_POWER, 0x01);
|
||||
float power2 = sdm.readVal(SDM_PHASE_1_POWER, 0x02);
|
||||
```
|
||||
NOTE: <i>if you reading multiple SDM devices on the same RS485 line,</br>
|
||||
remember to set the same transmission parameters on each device,</br>
|
||||
@@ -134,7 +196,7 @@ only ID must be different for each SDM device.</i>
|
||||
|
||||
---
|
||||
|
||||
### Debuging: ###
|
||||
### Problems: ###
|
||||
Sometimes <b>readVal</b> return <b>NaN</b> value (not a number),</br>
|
||||
this means that the requested value could not be read from the sdm module for various reasons.</br>
|
||||
|
||||
@@ -159,46 +221,83 @@ The most common problems are:
|
||||
- compilation error for hardware serial mode</br>
|
||||
https://github.com/reaper7/SDM_Energy_Meter/issues/23</br>
|
||||
https://github.com/reaper7/SDM_Energy_Meter/issues/24</br>
|
||||
- SDM630 Modbus V2 serial stopbit problem</br>
|
||||
https://github.com/reaper7/SDM_Energy_Meter/issues/49</br>
|
||||
- Subsequent inquiries for slow slaves</br>
|
||||
https://github.com/reaper7/SDM_Energy_Meter/issues/50</br>
|
||||
|
||||
You can get last error code using function:
|
||||
```cpp
|
||||
//get last error code
|
||||
// __________optional parameter,
|
||||
// | true -> read and reset error code
|
||||
// | false or no parameter -> read error code
|
||||
// | but not reset stored code (for future checking)
|
||||
// | will be overwriten when next error occurs
|
||||
// ______optional parameter,
|
||||
// | true -> read and reset error code
|
||||
// | false or no parameter -> read error code
|
||||
// | but not reset stored code (for future checking)
|
||||
// | will be overwriten when next error occurs
|
||||
uint16_t lasterror = sdm.getErrCode(true);
|
||||
|
||||
//clear error code also available with:
|
||||
sdm.clearErrCode();
|
||||
```
|
||||
Errors list returned by <b>getErrCode</b>:</br>
|
||||
https://github.com/reaper7/SDM_Energy_Meter/blob/master/SDM.h#L142</br>
|
||||
https://github.com/reaper7/SDM_Energy_Meter/blob/master/SDM.h#L86</br>
|
||||
|
||||
You can also check total number of errors using function:
|
||||
```cpp
|
||||
//get total errors counter
|
||||
// _________optional parameter,
|
||||
// | true -> read and reset errors counter
|
||||
// | false or no parameter -> read errors counter
|
||||
// | but not reset stored counter (for future checking)
|
||||
// _____optional parameter,
|
||||
// | true -> read and reset errors counter
|
||||
// | false or no parameter -> read errors counter
|
||||
// | but not reset stored counter (for future checking)
|
||||
uint16_t cnterrors = sdm.getErrCount(true);
|
||||
|
||||
//clear errors counter also available with:
|
||||
sdm.clearErrCount();
|
||||
```
|
||||
|
||||
And finally you can read the counter of correctly made readings:
|
||||
```cpp
|
||||
//get total success counter
|
||||
// ___optional parameter,
|
||||
// | true -> read and reset success counter
|
||||
// | false or no parameter -> read success counter
|
||||
// | but not reset stored counter (for future checking)
|
||||
uint16_t cntsuccess = sdm.getSuccCount(true);
|
||||
|
||||
//clear success counter also available with:
|
||||
sdm.clearSuccCount();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Credits: ###
|
||||
|
||||
contribution to this project:</br>
|
||||
:+1: ESP SoftwareSerial library by Peter Lerup (https://github.com/plerup/espsoftwareserial)</br>
|
||||
:+1: crc calculation by Jaime García (https://github.com/peninquen/Modbus-Energy-Monitor-Arduino)</br>
|
||||
:+1: new registers for SDM120 and SDM630 by bart.e (https://github.com/beireken/SDM220t)</br>
|
||||
:+1: new registers for SDM120 and SDM630 by bart.e (https://github.com/reaper7/SDM_Energy_Meter/pull/3)</br>
|
||||
:+1: new registers for SDM72 by jegaha (https://github.com/reaper7/SDM_Energy_Meter/pull/34)</br>
|
||||
:+1: new registers for SDM120CT by JeroenSt (https://github.com/reaper7/SDM_Energy_Meter/pull/41)</br>
|
||||
:+1: new registers for DDM18SD by JeroenSt (https://github.com/reaper7/SDM_Energy_Meter/pull/44)</br>
|
||||
:+1: additional SDM630 registers and influxdb example by AndersV209 (https://github.com/reaper7/SDM_Energy_Meter/pull/45)</br>
|
||||
:+1: new registers for SDM72DM V2 by datjan (https://github.com/reaper7/SDM_Energy_Meter/pull/62)</br>
|
||||
:+1: compatibility with EspSoftwareSerial >= 8.0.1 by maxpautsch (https://github.com/reaper7/SDM_Energy_Meter/pull/75)</br>
|
||||
|
||||
other projects based on or using this library</br>
|
||||
:point_right: BZ40i Energy Meter by adlerweb (https://github.com/adlerweb/BZ40i_Energy_Meter)</br>
|
||||
:point_right: DDS238 Energy Meter by E-NINA (https://github.com/E-NINA/dds238_Energy_Meter)</br>
|
||||
:point_right: ESPEasy by TD-er (https://github.com/TD-er/ESPEasy)</br>
|
||||
:point_right: Sonoff-Tasmota by arendst (https://github.com/arendst/Sonoff-Tasmota)</br>
|
||||
|
||||
---
|
||||
|
||||
**2016-2018 Reaper7**
|
||||
<i>library version for old esp software serial (6.0.0 - 7.0.1) is available at [old_esp_swserial_600_701 branch](https://github.com/reaper7/SDM_Energy_Meter/tree/old_esp_swserial_600_701)</i><br>
|
||||
<i>library version for old esp software serial (5.2.0 - 5.4.0) is available at [old_esp_swserial_520_540 branch](https://github.com/reaper7/SDM_Energy_Meter/tree/old_esp_swserial_520_540)</i><br>
|
||||
<i>library version for old esp software serial (< 5.2.0) is available at [old_esp_swserial_lib branch](https://github.com/reaper7/SDM_Energy_Meter/tree/old_esp_swserial_lib)</i><br>
|
||||
<i>old template library version is available at [old_template branch](https://github.com/reaper7/SDM_Energy_Meter/tree/old_template)</i><br>
|
||||
|
||||
---
|
||||
|
||||
**2016-2023 Reaper7**
|
||||
|
||||
[paypal.me/reaper7md](https://www.paypal.me/reaper7md)
|
||||
|
||||
+278
-68
@@ -1,18 +1,34 @@
|
||||
/* Library for reading SDM 120/220/230/630 Modbus Energy meters.
|
||||
/* Library for reading SDM 72/120/220/230/630 Modbus Energy meters.
|
||||
* Reading via Hardware or Software Serial library & rs232<->rs485 converter
|
||||
* 2016-2018 Reaper7 (tested on wemos d1 mini->ESP8266 with Arduino 1.9.0-beta & 2.4.1 esp8266 core)
|
||||
* crc calculation by Jaime Garc�a (https://github.com/peninquen/Modbus-Energy-Monitor-Arduino/)
|
||||
* 2016-2023 Reaper7 (tested on wemos d1 mini->ESP8266 with Arduino 1.8.10 & 2.5.2 esp8266 core)
|
||||
* crc calculation by Jaime García (https://github.com/peninquen/Modbus-Energy-Monitor-Arduino/)
|
||||
*/
|
||||
//------------------------------------------------------------------------------
|
||||
#include "SDM.h"
|
||||
//------------------------------------------------------------------------------
|
||||
#ifdef USE_HARDWARESERIAL
|
||||
#if defined ( USE_HARDWARESERIAL )
|
||||
#if defined ( ESP8266 )
|
||||
SDM::SDM(HardwareSerial& serial, long baud, int dere_pin, int config, bool swapuart) : sdmSer(serial) {
|
||||
this->_baud = baud;
|
||||
this->_config = config;
|
||||
this->_dere_pin = dere_pin;
|
||||
this->_config = config;
|
||||
this->_swapuart = swapuart;
|
||||
}
|
||||
#elif defined ( ESP32 )
|
||||
SDM::SDM(HardwareSerial& serial, long baud, int dere_pin, int config, int8_t rx_pin, int8_t tx_pin) : sdmSer(serial) {
|
||||
this->_baud = baud;
|
||||
this->_dere_pin = dere_pin;
|
||||
this->_config = config;
|
||||
this->_rx_pin = rx_pin;
|
||||
this->_tx_pin = tx_pin;
|
||||
}
|
||||
#else
|
||||
SDM::SDM(HardwareSerial& serial, long baud, int dere_pin, int config) : sdmSer(serial) {
|
||||
this->_baud = baud;
|
||||
this->_dere_pin = dere_pin;
|
||||
this->_config = config;
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
SDM::SDM(ESPeasySerial& serial, long baud, int dere_pin) : sdmSer(serial) {
|
||||
this->_baud = baud;
|
||||
@@ -24,86 +40,104 @@ SDM::~SDM() {
|
||||
}
|
||||
|
||||
void SDM::begin(void) {
|
||||
#ifdef USE_HARDWARESERIAL
|
||||
#ifdef ESP8266
|
||||
sdmSer.begin(_baud, (SerialConfig)_config);
|
||||
#else
|
||||
sdmSer.begin(_baud, _config);
|
||||
#endif
|
||||
#if defined ( USE_HARDWARESERIAL )
|
||||
#if defined ( ESP8266 )
|
||||
sdmSer.begin(_baud, (SerialConfig)_config);
|
||||
#elif defined ( ESP32 )
|
||||
sdmSer.begin(_baud, _config, _rx_pin, _tx_pin);
|
||||
#else
|
||||
sdmSer.begin(_baud, _config);
|
||||
#endif
|
||||
#else
|
||||
sdmSer.begin(_baud);
|
||||
#endif
|
||||
#ifdef USE_HARDWARESERIAL
|
||||
#ifdef ESP8266
|
||||
if (_swapuart)
|
||||
sdmSer.swap();
|
||||
#endif
|
||||
|
||||
#if defined ( USE_HARDWARESERIAL ) && defined ( ESP8266 )
|
||||
if (_swapuart)
|
||||
sdmSer.swap();
|
||||
#endif
|
||||
if (_dere_pin != NOT_A_PIN) //set output pin mode for DE/RE pin when used (for control MAX485)
|
||||
pinMode(_dere_pin, OUTPUT);
|
||||
if (_dere_pin != NOT_A_PIN) {
|
||||
pinMode(_dere_pin, OUTPUT); //set output pin mode for DE/RE pin when used (for control MAX485)
|
||||
}
|
||||
dereSet(LOW); //set init state to receive from SDM -> DE Disable, /RE Enable (for control MAX485)
|
||||
}
|
||||
|
||||
float SDM::readVal(uint16_t reg, uint8_t node) {
|
||||
uint16_t temp;
|
||||
unsigned long resptime;
|
||||
uint8_t sdmarr[FRAMESIZE] = {node, SDM_B_02, 0, 0, SDM_B_05, SDM_B_06, 0, 0, 0};
|
||||
float res = NAN;
|
||||
uint16_t readErr = SDM_ERR_NO_ERROR;
|
||||
startReadVal(reg, node);
|
||||
|
||||
sdmarr[2] = highByte(reg);
|
||||
sdmarr[3] = lowByte(reg);
|
||||
uint16_t readErr = SDM_ERR_STILL_WAITING;
|
||||
|
||||
temp = calculateCRC(sdmarr, FRAMESIZE - 3); //calculate out crc only from first 6 bytes
|
||||
|
||||
sdmarr[6] = lowByte(temp);
|
||||
sdmarr[7] = highByte(temp);
|
||||
|
||||
#ifndef USE_HARDWARESERIAL
|
||||
sdmSer.listen(); //enable softserial rx interrupt
|
||||
#endif
|
||||
|
||||
while (sdmSer.available() > 0) { //read serial if any old data is available
|
||||
sdmSer.read();
|
||||
while (readErr == SDM_ERR_STILL_WAITING) {
|
||||
readErr = readValReady(node);
|
||||
delay(1);
|
||||
}
|
||||
|
||||
if (_dere_pin != NOT_A_PIN) //transmit to SDM -> DE Enable, /RE Disable (for control MAX485)
|
||||
digitalWrite(_dere_pin, HIGH);
|
||||
if (readErr != SDM_ERR_NO_ERROR) { //if error then copy temp error value to global val and increment global error counter
|
||||
readingerrcode = readErr;
|
||||
readingerrcount++;
|
||||
} else {
|
||||
++readingsuccesscount;
|
||||
}
|
||||
|
||||
delay(2); //fix for issue (nan reading) by sjfaustino: https://github.com/reaper7/SDM_Energy_Meter/issues/7#issuecomment-272111524
|
||||
if (readErr == SDM_ERR_NO_ERROR) {
|
||||
return decodeFloatValue();
|
||||
}
|
||||
|
||||
sdmSer.write(sdmarr, FRAMESIZE - 1); //send 8 bytes
|
||||
constexpr float res = NAN;
|
||||
return (res);
|
||||
}
|
||||
|
||||
sdmSer.flush(); //clear out tx buffer
|
||||
void SDM::startReadVal(uint16_t reg, uint8_t node, uint8_t functionCode) {
|
||||
uint8_t data[] = {
|
||||
node, // Address
|
||||
functionCode, // Modbus function
|
||||
highByte(reg), // Start address high byte
|
||||
lowByte(reg), // Start address low byte
|
||||
SDM_B_05, // Number of points high byte
|
||||
SDM_B_06, // Number of points low byte
|
||||
0, // Checksum low byte
|
||||
0}; // Checksum high byte
|
||||
|
||||
if (_dere_pin != NOT_A_PIN) //receive from SDM -> DE Disable, /RE Enable (for control MAX485)
|
||||
digitalWrite(_dere_pin, LOW);
|
||||
constexpr size_t messageLength = sizeof(data) / sizeof(data[0]);
|
||||
modbusWrite(data, messageLength);
|
||||
}
|
||||
|
||||
resptime = millis() + MAX_MILLIS_TO_WAIT;
|
||||
uint16_t SDM::readValReady(uint8_t node, uint8_t functionCode) {
|
||||
uint16_t readErr = SDM_ERR_NO_ERROR;
|
||||
if (sdmSer.available() < FRAMESIZE && ((millis() - resptime) < msturnaround))
|
||||
{
|
||||
return SDM_ERR_STILL_WAITING;
|
||||
}
|
||||
|
||||
while (sdmSer.available() < FRAMESIZE) {
|
||||
if (resptime < millis()) {
|
||||
if ((millis() - resptime) > msturnaround) {
|
||||
readErr = SDM_ERR_TIMEOUT; //err debug (4)
|
||||
|
||||
if (sdmSer.available() == 5) {
|
||||
for(int n=0; n<5; n++) {
|
||||
sdmarr[n] = sdmSer.read();
|
||||
}
|
||||
if (validChecksum(sdmarr, 5)) {
|
||||
readErr = sdmarr[2];
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
yield();
|
||||
delay(1);
|
||||
}
|
||||
|
||||
if (readErr == SDM_ERR_NO_ERROR) { //if no timeout...
|
||||
|
||||
if(sdmSer.available() >= FRAMESIZE) {
|
||||
if (sdmSer.available() >= FRAMESIZE) {
|
||||
|
||||
for(int n=0; n<FRAMESIZE; n++) {
|
||||
sdmarr[n] = sdmSer.read();
|
||||
}
|
||||
|
||||
if (sdmarr[0] == node && sdmarr[1] == SDM_B_02 && sdmarr[2] == SDM_REPLY_BYTE_COUNT) {
|
||||
|
||||
if ((calculateCRC(sdmarr, FRAMESIZE - 2)) == ((sdmarr[8] << 8) | sdmarr[7])) { //calculate crc from first 7 bytes and compare with received crc (bytes 7 & 8)
|
||||
((uint8_t*)&res)[3]= sdmarr[3];
|
||||
((uint8_t*)&res)[2]= sdmarr[4];
|
||||
((uint8_t*)&res)[1]= sdmarr[5];
|
||||
((uint8_t*)&res)[0]= sdmarr[6];
|
||||
} else {
|
||||
if (sdmarr[0] == node &&
|
||||
sdmarr[1] == functionCode &&
|
||||
sdmarr[2] == SDM_REPLY_BYTE_COUNT) {
|
||||
if (!validChecksum(sdmarr, FRAMESIZE)) {
|
||||
readErr = SDM_ERR_CRC_ERROR; //err debug (1)
|
||||
}
|
||||
|
||||
@@ -117,24 +151,108 @@ float SDM::readVal(uint16_t reg, uint8_t node) {
|
||||
|
||||
}
|
||||
|
||||
flush(mstimeout); //read serial if any old data is available and wait for RESPONSE_TIMEOUT (in ms)
|
||||
|
||||
if (sdmSer.available()) //if serial rx buffer (after RESPONSE_TIMEOUT) still contains data then something spam rs485, check node(s) or increase RESPONSE_TIMEOUT
|
||||
readErr = SDM_ERR_TIMEOUT; //err debug (4) but returned value may be correct
|
||||
|
||||
if (readErr != SDM_ERR_NO_ERROR) { //if error then copy temp error value to global val and increment global error counter
|
||||
readingerrcode = readErr;
|
||||
readingerrcount++;
|
||||
readingerrcount++;
|
||||
} else {
|
||||
++readingsuccesscount;
|
||||
}
|
||||
|
||||
while (sdmSer.available() > 0) { //read redundant serial bytes, if any
|
||||
sdmSer.read();
|
||||
#if !defined ( USE_HARDWARESERIAL )
|
||||
// sdmSer.stopListening(); //disable softserial rx interrupt
|
||||
#endif
|
||||
return readErr;
|
||||
}
|
||||
|
||||
float SDM::decodeFloatValue() const {
|
||||
if (validChecksum(sdmarr, FRAMESIZE)) {
|
||||
float res{};
|
||||
((uint8_t*)&res)[3]= sdmarr[3];
|
||||
((uint8_t*)&res)[2]= sdmarr[4];
|
||||
((uint8_t*)&res)[1]= sdmarr[5];
|
||||
((uint8_t*)&res)[0]= sdmarr[6];
|
||||
return res;
|
||||
}
|
||||
constexpr float res = NAN;
|
||||
return res;
|
||||
}
|
||||
|
||||
float SDM::readHoldingRegister(uint16_t reg, uint8_t node) {
|
||||
startReadVal(reg, node, SDM_READ_HOLDING_REGISTER);
|
||||
|
||||
uint16_t readErr = SDM_ERR_STILL_WAITING;
|
||||
|
||||
while (readErr == SDM_ERR_STILL_WAITING) {
|
||||
delay(1);
|
||||
readErr = readValReady(node, SDM_READ_HOLDING_REGISTER);
|
||||
}
|
||||
|
||||
#ifndef USE_HARDWARESERIAL
|
||||
sdmSer.end(); //disable softserial rx interrupt
|
||||
#endif
|
||||
if (readErr != SDM_ERR_NO_ERROR) { //if error then copy temp error value to global val and increment global error counter
|
||||
readingerrcode = readErr;
|
||||
readingerrcount++;
|
||||
} else {
|
||||
++readingsuccesscount;
|
||||
}
|
||||
|
||||
if (readErr == SDM_ERR_NO_ERROR) {
|
||||
return decodeFloatValue();
|
||||
}
|
||||
|
||||
constexpr float res = NAN;
|
||||
return (res);
|
||||
}
|
||||
|
||||
bool SDM::writeHoldingRegister(float value, uint16_t reg, uint8_t node) {
|
||||
{
|
||||
uint8_t data[] = {
|
||||
node, // Address
|
||||
SDM_WRITE_HOLDING_REGISTER, // Function
|
||||
highByte(reg), // Starting Address High
|
||||
lowByte(reg), // Starting Address Low
|
||||
SDM_B_05, // Number of Registers High
|
||||
SDM_B_06, // Number of Registers Low
|
||||
4, // Byte count
|
||||
((uint8_t*)&value)[3],
|
||||
((uint8_t*)&value)[2],
|
||||
((uint8_t*)&value)[1],
|
||||
((uint8_t*)&value)[0],
|
||||
0, 0};
|
||||
|
||||
constexpr size_t messageLength = sizeof(data) / sizeof(data[0]);
|
||||
modbusWrite(data, messageLength);
|
||||
}
|
||||
uint16_t readErr = SDM_ERR_STILL_WAITING;
|
||||
while (readErr == SDM_ERR_STILL_WAITING) {
|
||||
delay(1);
|
||||
readErr = readValReady(node, SDM_READ_HOLDING_REGISTER);
|
||||
}
|
||||
|
||||
if (readErr != SDM_ERR_NO_ERROR) { //if error then copy temp error value to global val and increment global error counter
|
||||
readingerrcode = readErr;
|
||||
readingerrcount++;
|
||||
} else {
|
||||
++readingsuccesscount;
|
||||
}
|
||||
|
||||
return readErr == SDM_ERR_NO_ERROR;
|
||||
}
|
||||
|
||||
uint32_t SDM::getSerialNumber(uint8_t node) {
|
||||
uint32_t res{};
|
||||
readHoldingRegister(SDM_HOLDING_SERIAL_NUMBER, node);
|
||||
// if (getErrCode() == SDM_ERR_NO_ERROR) {
|
||||
for (size_t i = 0; i < 4; ++i) {
|
||||
res = (res << 8) + sdmarr[3 + i];
|
||||
}
|
||||
// }
|
||||
return res;
|
||||
}
|
||||
|
||||
uint16_t SDM::getErrCode(bool _clear) {
|
||||
uint16_t _tmp = readingerrcode;
|
||||
if (_clear == true)
|
||||
@@ -142,15 +260,15 @@ uint16_t SDM::getErrCode(bool _clear) {
|
||||
return (_tmp);
|
||||
}
|
||||
|
||||
uint16_t SDM::getErrCount(bool _clear) {
|
||||
uint16_t _tmp = readingerrcount;
|
||||
uint32_t SDM::getErrCount(bool _clear) {
|
||||
uint32_t _tmp = readingerrcount;
|
||||
if (_clear == true)
|
||||
clearErrCount();
|
||||
return (_tmp);
|
||||
}
|
||||
|
||||
uint16_t SDM::getSuccCount(bool _clear) {
|
||||
uint16_t _tmp = readingsuccesscount;
|
||||
uint32_t SDM::getSuccCount(bool _clear) {
|
||||
uint32_t _tmp = readingsuccesscount;
|
||||
if (_clear == true)
|
||||
clearSuccCount();
|
||||
return (_tmp);
|
||||
@@ -168,11 +286,37 @@ void SDM::clearSuccCount() {
|
||||
readingsuccesscount = 0;
|
||||
}
|
||||
|
||||
uint16_t SDM::calculateCRC(uint8_t *array, uint8_t num) {
|
||||
void SDM::setMsTurnaround(uint16_t _msturnaround) {
|
||||
if (_msturnaround < SDM_MIN_DELAY)
|
||||
msturnaround = SDM_MIN_DELAY;
|
||||
else if (_msturnaround > SDM_MAX_DELAY)
|
||||
msturnaround = SDM_MAX_DELAY;
|
||||
else
|
||||
msturnaround = _msturnaround;
|
||||
}
|
||||
|
||||
void SDM::setMsTimeout(uint16_t _mstimeout) {
|
||||
if (_mstimeout < SDM_MIN_DELAY)
|
||||
mstimeout = SDM_MIN_DELAY;
|
||||
else if (_mstimeout > SDM_MAX_DELAY)
|
||||
mstimeout = SDM_MAX_DELAY;
|
||||
else
|
||||
mstimeout = _mstimeout;
|
||||
}
|
||||
|
||||
uint16_t SDM::getMsTurnaround() {
|
||||
return (msturnaround);
|
||||
}
|
||||
|
||||
uint16_t SDM::getMsTimeout() {
|
||||
return (mstimeout);
|
||||
}
|
||||
|
||||
uint16_t SDM::calculateCRC(const uint8_t *array, uint8_t len) const {
|
||||
uint16_t _crc, _flag;
|
||||
_crc = 0xFFFF;
|
||||
for (uint8_t i = 0; i < num; i++) {
|
||||
_crc = _crc ^ array[i];
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
_crc ^= (uint16_t)array[i];
|
||||
for (uint8_t j = 8; j; j--) {
|
||||
_flag = _crc & 0x0001;
|
||||
_crc >>= 1;
|
||||
@@ -182,3 +326,69 @@ uint16_t SDM::calculateCRC(uint8_t *array, uint8_t num) {
|
||||
}
|
||||
return _crc;
|
||||
}
|
||||
|
||||
void SDM::flush(unsigned long _flushtime) {
|
||||
unsigned long flushstart = millis();
|
||||
sdmSer.flush();
|
||||
int available = sdmSer.available();
|
||||
while (available > 0 || ((millis() - flushstart) < _flushtime)) {
|
||||
while (available > 0) {
|
||||
--available;
|
||||
flushstart = millis();
|
||||
//read serial if any old data is available
|
||||
sdmSer.read();
|
||||
}
|
||||
delay(1);
|
||||
available = sdmSer.available();
|
||||
}
|
||||
}
|
||||
|
||||
void SDM::dereSet(bool _state) {
|
||||
if (_dere_pin != NOT_A_PIN)
|
||||
digitalWrite(_dere_pin, _state); //receive from SDM -> DE Disable, /RE Enable (for control MAX485)
|
||||
}
|
||||
|
||||
bool SDM::validChecksum(const uint8_t* data, size_t messageLength) const {
|
||||
const uint16_t temp = calculateCRC(data, messageLength - 2); //calculate out crc only from first 6 bytes
|
||||
|
||||
return data[messageLength - 2] == lowByte(temp) &&
|
||||
data[messageLength - 1] == highByte(temp);
|
||||
|
||||
}
|
||||
|
||||
void SDM::modbusWrite(uint8_t* data, size_t messageLength) {
|
||||
const uint16_t temp = calculateCRC(data, messageLength - 2); //calculate out crc only from first 6 bytes
|
||||
|
||||
data[messageLength - 2] = lowByte(temp);
|
||||
data[messageLength - 1] = highByte(temp);
|
||||
|
||||
#if !defined ( USE_HARDWARESERIAL )
|
||||
sdmSer.listen(); //enable softserial rx interrupt
|
||||
#endif
|
||||
|
||||
flush(); //read serial if any old data is available
|
||||
|
||||
if (_dere_pin != NOT_A_PIN) {
|
||||
dereSet(HIGH); //transmit to SDM -> DE Enable, /RE Disable (for control MAX485)
|
||||
|
||||
delay(2); //fix for issue (nan reading) by sjfaustino: https://github.com/reaper7/SDM_Energy_Meter/issues/7#issuecomment-272111524
|
||||
|
||||
resptime = millis();
|
||||
}
|
||||
|
||||
sdmSer.write(data, messageLength); //send 8 bytes
|
||||
|
||||
if (_dere_pin != NOT_A_PIN) {
|
||||
// Need to wait for all bytes in TX buffer are sent.
|
||||
// N.B. flush() on serial port does often only clear the send buffer, not wait till all is sent.
|
||||
const unsigned long waitForBytesSent_ms = (messageLength * 10000) / sdmSer.getBaudRate() + 1;
|
||||
while ((millis() - resptime) < waitForBytesSent_ms) {
|
||||
delay(1); //clear out tx buffer
|
||||
}
|
||||
dereSet(LOW); //receive from SDM -> DE Disable, /RE Enable (for control MAX485)
|
||||
flush();
|
||||
}
|
||||
|
||||
resptime = millis();
|
||||
|
||||
}
|
||||
|
||||
+347
-140
@@ -1,6 +1,6 @@
|
||||
/* Library for reading SDM 120/220/230/630 Modbus Energy meters.
|
||||
/* Library for reading SDM 72/120/220/230/630 Modbus Energy meters.
|
||||
* Reading via Hardware or Software Serial library & rs232<->rs485 converter
|
||||
* 2016-2018 Reaper7 (tested on wemos d1 mini->ESP8266 with Arduino 1.9.0-beta & 2.4.1 esp8266 core)
|
||||
* 2016-2023 Reaper7 (tested on wemos d1 mini->ESP8266 with Arduino 1.8.10 & 2.5.2 esp8266 core)
|
||||
* crc calculation by Jaime García (https://github.com/peninquen/Modbus-Energy-Monitor-Arduino/)
|
||||
*/
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -9,185 +9,392 @@
|
||||
//------------------------------------------------------------------------------
|
||||
#include <Arduino.h>
|
||||
#include <SDM_Config_User.h>
|
||||
#ifdef USE_HARDWARESERIAL
|
||||
#if defined ( USE_HARDWARESERIAL )
|
||||
#include <HardwareSerial.h>
|
||||
#else
|
||||
#include <ESPeasySerial.h>
|
||||
// #include <SoftwareSerial.h>
|
||||
#endif
|
||||
//------------------------------------------------------------------------------
|
||||
//DEFAULT CONFIG (DO NOT CHANGE ANYTHING!!! for changes use SDM_Config_User.h):
|
||||
//------------------------------------------------------------------------------
|
||||
#ifndef SDM_UART_BAUD
|
||||
#define SDM_UART_BAUD 4800 //default baudrate
|
||||
#if !defined ( SDM_UART_BAUD )
|
||||
#define SDM_UART_BAUD 4800 // default baudrate
|
||||
#endif
|
||||
|
||||
#ifndef DERE_PIN
|
||||
#define DERE_PIN NOT_A_PIN //default digital pin for control MAX485 DE/RE lines (connect DE & /RE together to this pin)
|
||||
#if !defined ( DERE_PIN )
|
||||
#define DERE_PIN NOT_A_PIN // default digital pin for control MAX485 DE/RE lines (connect DE & /RE together to this pin)
|
||||
#endif
|
||||
|
||||
#ifdef USE_HARDWARESERIAL
|
||||
#if defined ( USE_HARDWARESERIAL )
|
||||
|
||||
#ifndef SDM_UART_CONFIG
|
||||
#define SDM_UART_CONFIG SERIAL_8N1 //default hardware uart config
|
||||
#if !defined ( SDM_UART_CONFIG )
|
||||
#define SDM_UART_CONFIG SERIAL_8N1 // default hardware uart config
|
||||
#endif
|
||||
|
||||
#ifndef SWAPHWSERIAL
|
||||
#define SWAPHWSERIAL 0 //(only esp8266) when hwserial used, then swap uart pins from 3/1 to 13/15 (default not swap)
|
||||
#if defined ( ESP8266 ) && !defined ( SWAPHWSERIAL )
|
||||
#define SWAPHWSERIAL 0 // (only esp8266) when hwserial used, then swap uart pins from 3/1 to 13/15 (default not swap)
|
||||
#endif
|
||||
|
||||
#if defined ( ESP32 )
|
||||
#if !defined ( SDM_RX_PIN )
|
||||
#define SDM_RX_PIN -1 // use default rx pin for selected port
|
||||
#endif
|
||||
#if !defined ( SDM_TX_PIN )
|
||||
#define SDM_TX_PIN -1 // use default tx pin for selected port
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
#if defined ( ESP8266 ) || defined ( ESP32 )
|
||||
#if !defined ( SDM_UART_CONFIG )
|
||||
#define SDM_UART_CONFIG SERIAL_8N1 // default softwareware uart config for esp8266/esp32
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// #if !defined ( SDM_RX_PIN ) || !defined ( SDM_TX_PIN )
|
||||
// #error "SDM_RX_PIN and SDM_TX_PIN must be defined in SDM_Config_User.h for Software Serial option)"
|
||||
// #endif
|
||||
|
||||
#if !defined ( SDM_RX_PIN )
|
||||
#define SDM_RX_PIN -1
|
||||
#endif
|
||||
#if !defined ( SDM_TX_PIN )
|
||||
#define SDM_TX_PIN -1
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef MAX_MILLIS_TO_WAIT
|
||||
#define MAX_MILLIS_TO_WAIT 500 //default max time to wait for response from SDM
|
||||
#if !defined ( WAITING_TURNAROUND_DELAY )
|
||||
#define WAITING_TURNAROUND_DELAY 500 // time in ms to wait for process current request
|
||||
#endif
|
||||
//------------------------------------------------------------------------------
|
||||
#define FRAMESIZE 9 //size of out/in array
|
||||
#define SDM_REPLY_BYTE_COUNT 0x04 //number of bytes with data
|
||||
|
||||
#define SDM_B_01 0x01 //BYTE 1 -> slave address (default value 1 read from node 1)
|
||||
#define SDM_B_02 0x04 //BYTE 2 -> function code (default value 0x04 read from 3X input registers)
|
||||
//BYTES 3 & 4 (BELOW)
|
||||
//SDM 120 registers
|
||||
#define SDM120C_VOLTAGE 0x0000 //V
|
||||
#define SDM120C_CURRENT 0x0006 //A
|
||||
#define SDM120C_POWER 0x000C //W
|
||||
#define SDM120C_ACTIVE_APPARENT_POWER 0x0012 //VA
|
||||
#define SDM120C_REACTIVE_APPARENT_POWER 0x0018 //VAR
|
||||
#define SDM120C_POWER_FACTOR 0x001E //
|
||||
#define SDM120C_FREQUENCY 0x0046 //Hz
|
||||
#define SDM120C_IMPORT_ACTIVE_ENERGY 0x0048 //Wh
|
||||
#define SDM120C_EXPORT_ACTIVE_ENERGY 0x004A //Wh
|
||||
#define SDM120C_TOTAL_ACTIVE_ENERGY 0x0156 //Wh
|
||||
//SDM 220 registers
|
||||
#define SDM220T_VOLTAGE 0x0000 //V
|
||||
#define SDM220T_CURRENT 0x0006 //A
|
||||
#define SDM220T_POWER 0x000C //W
|
||||
#define SDM220T_ACTIVE_APPARENT_POWER 0x0012 //VA
|
||||
#define SDM220T_REACTIVE_APPARENT_POWER 0x0018 //VAR
|
||||
#define SDM220T_POWER_FACTOR 0x001E //
|
||||
#define SDM220T_PHASE_ANGLE 0x0024 //DEGREE
|
||||
#define SDM220T_FREQUENCY 0x0046 //Hz
|
||||
#define SDM220T_IMPORT_ACTIVE_ENERGY 0x0048 //Wh
|
||||
#define SDM220T_EXPORT_ACTIVE_ENERGY 0x004A //Wh
|
||||
#define SDM220T_IMPORT_REACTIVE_ENERGY 0x004C //VARh
|
||||
#define SDM220T_EXPORT_REACTIVE_ENERGY 0x004E //VARh
|
||||
#define SDM220T_TOTAL_ACTIVE_ENERGY 0x0156 //Wh
|
||||
#define SDM220T_TOTAL_REACTIVE_ENERGY 0x0158 //VARh
|
||||
//SDM 230 registers
|
||||
#define SDM230_VOLTAGE 0x0000 //V
|
||||
#define SDM230_CURRENT 0x0006 //A
|
||||
#define SDM230_POWER 0x000C //W
|
||||
#define SDM230_ACTIVE_APPARENT_POWER 0x0012 //VA
|
||||
#define SDM230_REACTIVE_APPARENT_POWER 0x0018 //VAR
|
||||
#define SDM230_POWER_FACTOR 0x001E //
|
||||
#define SDM230_PHASE_ANGLE 0x0024 //DEGREE
|
||||
#define SDM230_FREQUENCY 0x0046 //Hz
|
||||
#define SDM230_IMPORT_ACTIVE_ENERGY 0x0048 //Wh
|
||||
#define SDM230_EXPORT_ACTIVE_ENERGY 0x004A //Wh
|
||||
#define SDM230_IMPORT_REACTIVE_ENERGY 0x004C //VARh
|
||||
#define SDM230_EXPORT_REACTIVE_ENERGY 0x004E //VARh
|
||||
#define SDM230_TOTAL_SYSTEM_POWER_DEMAND 0x0054 //W
|
||||
#define SDM230_MAXIMUM_SYSTEM_POWER_DEMAND 0x0056 //W
|
||||
#define SDM230_CURRENT_POSITIVE_POWER_DEMAND 0x0058 //W
|
||||
#define SDM230_MAXIMUM_POSITIVE_POWER_DEMAND 0x005A //W
|
||||
#define SDM230_CURRENT_REVERSE_POWER_DEMAND 0x005C //W
|
||||
#define SDM230_MAXIMUM_REVERSE_POWER_DEMAND 0x005E //W
|
||||
#define SDM230_CURRENT_DEMAND 0x0102 //Amps
|
||||
#define SDM230_MAXIMUM_CURRENT_DEMAND 0x0108 //Amps
|
||||
#define SDM230_TOTAL_ACTIVE_ENERGY 0x0156 //kwh
|
||||
#define SDM230_TOTAL_REACTIVE_ENERGY 0x0158 //kvarh
|
||||
#define SDM230_CURRENT_RESETTABLE_TOTAL_ACTIVE_ENERGY 0x0180 //Wh
|
||||
#define SDM230_CURRENT_RESETTABLE_TOTAL_REACTIVE_ENERGY 0x0182 //VARh
|
||||
//SDM 630 registers
|
||||
#define SDM630_VOLTAGE1 0x0000 //V
|
||||
#define SDM630_VOLTAGE2 0x0002 //V
|
||||
#define SDM630_VOLTAGE3 0x0004 //V
|
||||
#define SDM630_CURRENT1 0x0006 //A
|
||||
#define SDM630_CURRENT2 0x0008 //A
|
||||
#define SDM630_CURRENT3 0x000A //A
|
||||
#define SDM630_CURRENTSUM 0x0030 //A
|
||||
#define SDM630_POWER1 0x000C //W
|
||||
#define SDM630_POWER2 0x000E //W
|
||||
#define SDM630_POWER3 0x0010 //W
|
||||
#define SDM630_POWERTOTAL 0x0034 //W
|
||||
#define SDM630_VOLT_AMPS1 0x0012 //VA
|
||||
#define SDM630_VOLT_AMPS2 0x0014 //VA
|
||||
#define SDM630_VOLT_AMPS3 0x0016 //VA
|
||||
#define SDM630_VOLT_AMPS_TOTAL 0x0038 //VA
|
||||
#define SDM630_VOLT_AMPS_REACTIVE1 0x0018 //VAr
|
||||
#define SDM630_VOLT_AMPS_REACTIVE2 0x001A //VAr
|
||||
#define SDM630_VOLT_AMPS_REACTIVE3 0x001C //VAr
|
||||
#define SDM630_VOLT_AMPS_REACTIVE_TOTAL 0x003C //VAr
|
||||
#define SDM630_POWER_FACTOR1 0x001E
|
||||
#define SDM630_POWER_FACTOR2 0x0020
|
||||
#define SDM630_POWER_FACTOR3 0x0022
|
||||
#define SDM630_POWER_FACTOR_TOTAL 0x003E
|
||||
#define SDM630_PHASE_ANGLE1 0x0024 //Degrees
|
||||
#define SDM630_PHASE_ANGLE2 0x0026 //Degrees
|
||||
#define SDM630_PHASE_ANGLE3 0x0028 //Degrees
|
||||
#define SDM630_PHASE_ANGLE_TOTAL 0x0042 //Degrees
|
||||
#define SDM630_VOLTAGE_AVERAGE 0x002A //V
|
||||
#define SDM630_CURRENT_AVERAGE 0x002E //A
|
||||
#define SDM630_FREQUENCY 0x0046 //HZ
|
||||
#define SDM630_IMPORT_ACTIVE_ENERGY 0x0048 //Wh
|
||||
#define SDM630_EXPORT_ACTIVE_ENERGY 0x004A //Wh
|
||||
#define SDM630_IMPORT_REACTIVE_ENERGY 0x004C //VARh
|
||||
#define SDM630_EXPORT_REACTIVE_ENERGY 0x004E //VARh
|
||||
#define SDM630_TOTAL_SYSTEM_POWER_DEMAND 0x0054 //W
|
||||
#define SDM630_MAXIMUM_TOTAL_SYSTEM_POWER 0x0056 //W
|
||||
#define SDM630_PHASE_1_LN_VOLTS_THD 0x00EA //%
|
||||
#define SDM630_PHASE_2_LN_VOLTS_THD 0x00EC //%
|
||||
#define SDM630_PHASE_3_LN_VOLTS_THD 0x00EE //%
|
||||
#define SDM630_AVERAGE_VOLTS_THD 0x00F8 //%
|
||||
#define SDM630_PHASE_1_CURRENT_THD 0x00F0 //%
|
||||
#define SDM630_PHASE_2_CURRENT_THD 0x00F2 //%
|
||||
#define SDM630_PHASE_3_CURRENT_THD 0x00F4 //%
|
||||
#define SDM630_AVERAGE_CURRENT_THD 0x00FA //%
|
||||
#if !defined ( RESPONSE_TIMEOUT )
|
||||
#define RESPONSE_TIMEOUT 10 // time in ms to wait for return response from all devices before next request
|
||||
#endif
|
||||
|
||||
#if !defined ( SDM_MIN_DELAY )
|
||||
#define SDM_MIN_DELAY 1 // minimum value (in ms) for WAITING_TURNAROUND_DELAY and RESPONSE_TIMEOUT
|
||||
#endif
|
||||
|
||||
#if !defined ( SDM_MAX_DELAY )
|
||||
#define SDM_MAX_DELAY 20 // maximum value (in ms) for WAITING_TURNAROUND_DELAY and RESPONSE_TIMEOUT
|
||||
#endif
|
||||
|
||||
#define SDM_B_05 0x00 //BYTE 5
|
||||
#define SDM_B_06 0x02 //BYTE 6
|
||||
//------------------------------------------------------------------------------
|
||||
#define SDM_ERR_NO_ERROR 0 //no error
|
||||
#define SDM_ERR_CRC_ERROR 1 //crc error
|
||||
#define SDM_ERR_WRONG_BYTES 2 //bytes b0,b1 or b2 wrong
|
||||
#define SDM_ERR_NOT_ENOUGHT_BYTES 3 //not enough bytes from sdm
|
||||
#define SDM_ERR_TIMEOUT 4 //timeout
|
||||
|
||||
#define SDM_ERR_NO_ERROR 0 // no error
|
||||
#define SDM_ERR_ILLEGAL_FUNCTION 1
|
||||
#define SDM_ERR_ILLEGAL_DATA_ADDRESS 2
|
||||
#define SDM_ERR_ILLEGAL_DATA_VALUE 3
|
||||
#define SDM_ERR_SLAVE_DEVICE_FAILURE 5
|
||||
|
||||
#define SDM_ERR_CRC_ERROR 11 // crc error
|
||||
#define SDM_ERR_WRONG_BYTES 12 // bytes b0,b1 or b2 wrong
|
||||
#define SDM_ERR_NOT_ENOUGHT_BYTES 13 // not enough bytes from sdm
|
||||
#define SDM_ERR_TIMEOUT 14 // timeout
|
||||
#define SDM_ERR_EXCEPTION 15
|
||||
#define SDM_ERR_STILL_WAITING 16
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#define SDM_READ_HOLDING_REGISTER 0x03
|
||||
#define SDM_READ_INPUT_REGISTER 0x04
|
||||
#define SDM_WRITE_HOLDING_REGISTER 0x10
|
||||
|
||||
#define FRAMESIZE 9 // size of out/in array
|
||||
#define SDM_REPLY_BYTE_COUNT 0x04 // number of bytes with data
|
||||
|
||||
#define SDM_B_01 0x01 // BYTE 1 -> slave address (default value 1 read from node 1)
|
||||
#define SDM_B_02 SDM_READ_INPUT_REGISTER // BYTE 2 -> function code (default value 0x04 read from 3X input registers)
|
||||
#define SDM_B_05 0x00 // BYTE 5
|
||||
#define SDM_B_06 0x02 // BYTE 6
|
||||
// BYTES 3 & 4 (BELOW)
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
// REGISTERS LIST FOR SDM DEVICES |
|
||||
//---------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
// REGISTER NAME REGISTER ADDRESS UNIT | SDM630 | SDM230 | SDM220 | SDM120CT| SDM120 | SDM72D | SDM72 V2|
|
||||
//---------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
#define SDM_PHASE_1_VOLTAGE 0x0000 // V | 1 | 1 | 1 | 1 | 1 | | 1 |
|
||||
#define SDM_PHASE_2_VOLTAGE 0x0002 // V | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_3_VOLTAGE 0x0004 // V | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_1_CURRENT 0x0006 // A | 1 | 1 | 1 | 1 | 1 | | 1 |
|
||||
#define SDM_PHASE_2_CURRENT 0x0008 // A | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_3_CURRENT 0x000A // A | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_1_POWER 0x000C // W | 1 | 1 | 1 | 1 | 1 | | 1 |
|
||||
#define SDM_PHASE_2_POWER 0x000E // W | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_3_POWER 0x0010 // W | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_1_APPARENT_POWER 0x0012 // VA | 1 | 1 | 1 | 1 | 1 | | 1 |
|
||||
#define SDM_PHASE_2_APPARENT_POWER 0x0014 // VA | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_3_APPARENT_POWER 0x0016 // VA | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_1_REACTIVE_POWER 0x0018 // VAr | 1 | 1 | 1 | 1 | 1 | | 1 |
|
||||
#define SDM_PHASE_2_REACTIVE_POWER 0x001A // VAr | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_3_REACTIVE_POWER 0x001C // VAr | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_1_POWER_FACTOR 0x001E // | 1 | 1 | 1 | 1 | 1 | | 1 |
|
||||
#define SDM_PHASE_2_POWER_FACTOR 0x0020 // | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_3_POWER_FACTOR 0x0022 // | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_1_ANGLE 0x0024 // Degrees | 1 | 1 | 1 | 1 | | | |
|
||||
#define SDM_PHASE_2_ANGLE 0x0026 // Degrees | 1 | | | | | | |
|
||||
#define SDM_PHASE_3_ANGLE 0x0028 // Degrees | 1 | | | | | | |
|
||||
#define SDM_AVERAGE_L_TO_N_VOLTS 0x002A // V | 1 | | | | | | 1 |
|
||||
#define SDM_AVERAGE_LINE_CURRENT 0x002E // A | 1 | | | | | | 1 |
|
||||
#define SDM_SUM_LINE_CURRENT 0x0030 // A | 1 | | | | | | 1 |
|
||||
#define SDM_TOTAL_SYSTEM_POWER 0x0034 // W | 1 | | | | | 1 | 1 |
|
||||
#define SDM_TOTAL_SYSTEM_APPARENT_POWER 0x0038 // VA | 1 | | | | | | 1 |
|
||||
#define SDM_TOTAL_SYSTEM_REACTIVE_POWER 0x003C // VAr | 1 | | | | | | 1 |
|
||||
#define SDM_TOTAL_SYSTEM_POWER_FACTOR 0x003E // | 1 | | | | | | 1 |
|
||||
#define SDM_TOTAL_SYSTEM_PHASE_ANGLE 0x0042 // Degrees | 1 | | | | | | |
|
||||
#define SDM_FREQUENCY 0x0046 // Hz | 1 | 1 | 1 | 1 | 1 | | 1 |
|
||||
#define SDM_IMPORT_ACTIVE_ENERGY 0x0048 // kWh/MWh | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
|
||||
#define SDM_EXPORT_ACTIVE_ENERGY 0x004A // kWh/MWh | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
|
||||
#define SDM_IMPORT_REACTIVE_ENERGY 0x004C // kVArh/MVArh | 1 | 1 | 1 | 1 | 1 | | |
|
||||
#define SDM_EXPORT_REACTIVE_ENERGY 0x004E // kVArh/MVArh | 1 | 1 | 1 | 1 | 1 | | |
|
||||
#define SDM_VAH_SINCE_LAST_RESET 0x0050 // kVAh/MVAh | 1 | | | | | | |
|
||||
#define SDM_AH_SINCE_LAST_RESET 0x0052 // Ah/kAh | 1 | | | | | | |
|
||||
#define SDM_TOTAL_SYSTEM_POWER_DEMAND 0x0054 // W | 1 | 1 | | | | | |
|
||||
#define SDM_MAXIMUM_TOTAL_SYSTEM_POWER_DEMAND 0x0056 // W | 1 | 1 | | | | | |
|
||||
#define SDM_CURRENT_SYSTEM_POSITIVE_POWER_DEMAND 0x0058 // W | | 1 | | | | | |
|
||||
#define SDM_MAXIMUM_SYSTEM_POSITIVE_POWER_DEMAND 0x005A // W | | 1 | | | | | |
|
||||
#define SDM_CURRENT_SYSTEM_REVERSE_POWER_DEMAND 0x005C // W | | 1 | | | | | |
|
||||
#define SDM_MAXIMUM_SYSTEM_REVERSE_POWER_DEMAND 0x005E // W | | 1 | | | | | |
|
||||
#define SDM_TOTAL_SYSTEM_VA_DEMAND 0x0064 // VA | 1 | | | | | | |
|
||||
#define SDM_MAXIMUM_TOTAL_SYSTEM_VA_DEMAND 0x0066 // VA | 1 | | | | | | |
|
||||
#define SDM_NEUTRAL_CURRENT_DEMAND 0x0068 // A | 1 | | | | | | |
|
||||
#define SDM_MAXIMUM_NEUTRAL_CURRENT 0x006A // A | 1 | | | | | | |
|
||||
#define SDM_REACTIVE_POWER_DEMAND 0x006C // VAr | 1 | | | | | | |
|
||||
#define SDM_MAXIMUM_REACTIVE_POWER_DEMAND 0x006E // VAr | 1 | | | | | | |
|
||||
#define SDM_LINE_1_TO_LINE_2_VOLTS 0x00C8 // V | 1 | | | | | | 1 |
|
||||
#define SDM_LINE_2_TO_LINE_3_VOLTS 0x00CA // V | 1 | | | | | | 1 |
|
||||
#define SDM_LINE_3_TO_LINE_1_VOLTS 0x00CC // V | 1 | | | | | | 1 |
|
||||
#define SDM_AVERAGE_LINE_TO_LINE_VOLTS 0x00CE // V | 1 | | | | | | 1 |
|
||||
#define SDM_NEUTRAL_CURRENT 0x00E0 // A | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_1_LN_VOLTS_THD 0x00EA // % | 1 | | | | | | |
|
||||
#define SDM_PHASE_2_LN_VOLTS_THD 0x00EC // % | 1 | | | | | | |
|
||||
#define SDM_PHASE_3_LN_VOLTS_THD 0x00EE // % | 1 | | | | | | |
|
||||
#define SDM_PHASE_1_CURRENT_THD 0x00F0 // % | 1 | | | | | | |
|
||||
#define SDM_PHASE_2_CURRENT_THD 0x00F2 // % | 1 | | | | | | |
|
||||
#define SDM_PHASE_3_CURRENT_THD 0x00F4 // % | 1 | | | | | | |
|
||||
#define SDM_AVERAGE_LINE_TO_NEUTRAL_VOLTS_THD 0x00F8 // % | 1 | | | | | | |
|
||||
#define SDM_AVERAGE_LINE_CURRENT_THD 0x00FA // % | 1 | | | | | | |
|
||||
#define SDM_TOTAL_SYSTEM_POWER_FACTOR_INV 0x00FE // | 1 | | | | | | |
|
||||
#define SDM_PHASE_1_CURRENT_DEMAND 0x0102 // A | 1 | 1 | | | | | |
|
||||
#define SDM_PHASE_2_CURRENT_DEMAND 0x0104 // A | 1 | | | | | | |
|
||||
#define SDM_PHASE_3_CURRENT_DEMAND 0x0106 // A | 1 | | | | | | |
|
||||
#define SDM_MAXIMUM_PHASE_1_CURRENT_DEMAND 0x0108 // A | 1 | 1 | | | | | |
|
||||
#define SDM_MAXIMUM_PHASE_2_CURRENT_DEMAND 0x010A // A | 1 | | | | | | |
|
||||
#define SDM_MAXIMUM_PHASE_3_CURRENT_DEMAND 0x010C // A | 1 | | | | | | |
|
||||
#define SDM_LINE_1_TO_LINE_2_VOLTS_THD 0x014E // % | 1 | | | | | | |
|
||||
#define SDM_LINE_2_TO_LINE_3_VOLTS_THD 0x0150 // % | 1 | | | | | | |
|
||||
#define SDM_LINE_3_TO_LINE_1_VOLTS_THD 0x0152 // % | 1 | | | | | | |
|
||||
#define SDM_AVERAGE_LINE_TO_LINE_VOLTS_THD 0x0154 // % | 1 | | | | | | |
|
||||
#define SDM_TOTAL_ACTIVE_ENERGY 0x0156 // kWh | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
|
||||
#define SDM_TOTAL_REACTIVE_ENERGY 0x0158 // kVArh | 1 | 1 | 1 | 1 | 1 | | 1 |
|
||||
#define SDM_L1_IMPORT_ACTIVE_ENERGY 0x015A // kWh | 1 | | | | | | |
|
||||
#define SDM_L2_IMPORT_ACTIVE_ENERGY 0x015C // kWh | 1 | | | | | | |
|
||||
#define SDM_L3_IMPORT_ACTIVE_ENERGY 0x015E // kWh | 1 | | | | | | |
|
||||
#define SDM_L1_EXPORT_ACTIVE_ENERGY 0x0160 // kWh | 1 | | | | | | |
|
||||
#define SDM_L2_EXPORT_ACTIVE_ENERGY 0x0162 // kWh | 1 | | | | | | |
|
||||
#define SDM_L3_EXPORT_ACTIVE_ENERGY 0x0164 // kWh | 1 | | | | | | |
|
||||
#define SDM_L1_TOTAL_ACTIVE_ENERGY 0x0166 // kWh | 1 | | | | | | |
|
||||
#define SDM_L2_TOTAL_ACTIVE_ENERGY 0x0168 // kWh | 1 | | | | | | |
|
||||
#define SDM_L3_TOTAL_ACTIVE_ENERGY 0x016a // kWh | 1 | | | | | | |
|
||||
#define SDM_L1_IMPORT_REACTIVE_ENERGY 0x016C // kVArh | 1 | | | | | | |
|
||||
#define SDM_L2_IMPORT_REACTIVE_ENERGY 0x016E // kVArh | 1 | | | | | | |
|
||||
#define SDM_L3_IMPORT_REACTIVE_ENERGY 0x0170 // kVArh | 1 | | | | | | |
|
||||
#define SDM_L1_EXPORT_REACTIVE_ENERGY 0x0172 // kVArh | 1 | | | | | | |
|
||||
#define SDM_L2_EXPORT_REACTIVE_ENERGY 0x0174 // kVArh | 1 | | | | | | |
|
||||
#define SDM_L3_EXPORT_REACTIVE_ENERGY 0x0176 // kVArh | 1 | | | | | | |
|
||||
#define SDM_L1_TOTAL_REACTIVE_ENERGY 0x0178 // kVArh | 1 | | | | | | |
|
||||
#define SDM_L2_TOTAL_REACTIVE_ENERGY 0x017A // kVArh | 1 | | | | | | |
|
||||
#define SDM_L3_TOTAL_REACTIVE_ENERGY 0x017C // kVArh | 1 | | | | | | |
|
||||
#define SDM_CURRENT_RESETTABLE_TOTAL_ACTIVE_ENERGY 0x0180 // kWh | | 1 | | | | 1 | 1 |
|
||||
#define SDM_CURRENT_RESETTABLE_TOTAL_REACTIVE_ENERGY 0x0182 // kVArh | | 1 | | | | | |
|
||||
#define SDM_CURRENT_RESETTABLE_IMPORT_ENERGY 0x0184 // kWh | | | | | | 1 | 1 |
|
||||
#define SDM_CURRENT_RESETTABLE_EXPORT_ENERGY 0x0186 // kWh | | | | | | 1 | 1 |
|
||||
#define SDM_CURRENT_RESETTABLE_IMPORT_REACTIVE_ENERGY 0x0188 // kVArh | | | | | | 1 | 1 |
|
||||
#define SDM_CURRENT_RESETTABLE_EXPORT_REACTIVE_ENERGY 0x018A // kVArh | | | | | | 1 | 1 |
|
||||
#define SDM_NET_KWH 0x018C // kWh | | | | | | | 1 |
|
||||
#define SDM_NET_KVARH 0x018E // kVArh | | | | | | | 1 |
|
||||
#define SDM_IMPORT_POWER 0x0500 // W | | | | | | 1 | 1 |
|
||||
#define SDM_EXPORT_POWER 0x0502 // W | | | | | | 1 | 1 |
|
||||
//---------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------
|
||||
// REGISTERS LIST FOR DDM DEVICE |
|
||||
//---------------------------------------------------------------------------------------------------------
|
||||
// REGISTER NAME REGISTER ADDRESS UNIT | DDM18SD |
|
||||
//---------------------------------------------------------------------------------------------------------
|
||||
#define DDM_PHASE_1_VOLTAGE 0x0000 // V | 1 |
|
||||
#define DDM_PHASE_1_CURRENT 0x0008 // A | 1 |
|
||||
#define DDM_PHASE_1_POWER 0x0012 // W | 1 |
|
||||
#define DDM_PHASE_1_REACTIVE_POWER 0x001A // VAr | 1 |
|
||||
#define DDM_PHASE_1_POWER_FACTOR 0x002A // | 1 |
|
||||
#define DDM_FREQUENCY 0x0036 // Hz | 1 |
|
||||
#define DDM_IMPORT_ACTIVE_ENERGY 0x0100 // kWh | 1 |
|
||||
#define DDM_IMPORT_REACTIVE_ENERGY 0x0400 // kVArh | 1 |
|
||||
//---------------------------------------------------------------------------------------------------------
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------
|
||||
// REGISTERS LIST FOR DEVNAME DEVICE |
|
||||
//---------------------------------------------------------------------------------------------------------
|
||||
// REGISTER NAME REGISTER ADDRESS UNIT | DEVNAME |
|
||||
//---------------------------------------------------------------------------------------------------------
|
||||
//#define DEVNAME_VOLTAGE 0x0000 // V | 1 |
|
||||
//#define DEVNAME_CURRENT 0x0002 // A | 1 |
|
||||
//#define DEVNAME_POWER 0x0004 // W | 1 |
|
||||
//---------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------
|
||||
// REGISTERS LIST FOR DEVICE SETTINGS |
|
||||
//---------------------------------------------------------------------------------------------------------
|
||||
// REGISTER NAME REGISTER ADDRESS UNIT | DEVNAME |
|
||||
//---------------------------------------------------------------------------------------------------------
|
||||
|
||||
// Read minutes into first demand calculation.
|
||||
// When the Demand Time reaches the Demand Period
|
||||
// then the demand values are valid.
|
||||
#define SDM_HOLDING_DEMAND_TIME 0x0000
|
||||
|
||||
// Write demand period: 0~60 minutes.
|
||||
// Default 60.
|
||||
// Range: 0~60, 0 means function disabled
|
||||
#define SDM_HOLDING_DEMAND_PERIOD 0x0002
|
||||
|
||||
// Write relay on period in milliseconds:
|
||||
// 60, 100 or 200 ms.
|
||||
// default: 100 ms
|
||||
#define SDM_HOLDING_RELAY_PULSE_WIDTH 0x000C
|
||||
|
||||
// Parity / stop bit settings:
|
||||
// 0 = One stop bit and no parity, default.
|
||||
// 1 = One stop bit and even parity.
|
||||
// 2 = One s top bit and odd parity.
|
||||
// 3 = Two stop bits and no parity.
|
||||
// Requires a restart to become effective.
|
||||
#define SDM_HOLDING_NETWORK_PARITY_STOP 0x0012
|
||||
|
||||
// Ranges from 1 to 247. Default ID is 1.
|
||||
#define SDM_HOLDING_METER_ID 0x0014
|
||||
|
||||
// Write the network port baud rate for MODBUS Protocol, where:
|
||||
/*
|
||||
SDM120 / SDM230:
|
||||
0 = 2400 baud (default)
|
||||
1 = 4800 baud
|
||||
2 = 9600 baud
|
||||
5 = 1200 baud
|
||||
|
||||
SDM320 / SDM530Y:
|
||||
0 = 2400 baud
|
||||
1 = 4800 baud
|
||||
2 = 9600 baud (default)
|
||||
5 = 1200 band
|
||||
|
||||
SDM630 / SDM72 / SDM72V2:
|
||||
0 = 2400 baud
|
||||
1 = 4800 baud
|
||||
2 = 9600 baud (default)
|
||||
3 = 19200 baud
|
||||
4 = 38400 baud
|
||||
*/
|
||||
#define SDM_HOLDING_BAUD_RATE 0x001C
|
||||
|
||||
// Write MODBUS Protocol input parameter for pulse out 1:
|
||||
// 1: Import active energy
|
||||
// 2: Import + export (total) active energy
|
||||
// 4: Export active energy (default).
|
||||
// 5: Import reactive energy
|
||||
// 6: Import + export (total) reactive energy
|
||||
// 8: Export reactive energy
|
||||
#define SDM_HOLDING_PULSE_1_OUTPUT_MODE 0x0056
|
||||
|
||||
|
||||
|
||||
#define SDM_HOLDING_SERIAL_NUMBER 0xFC00
|
||||
#define SDM_HOLDING_SOFTWARE_VERSION 0xFC03
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
class SDM {
|
||||
public:
|
||||
#ifdef USE_HARDWARESERIAL
|
||||
#if defined ( USE_HARDWARESERIAL ) // hardware serial
|
||||
#if defined ( ESP8266 ) // on esp8266
|
||||
SDM(HardwareSerial& serial, long baud = SDM_UART_BAUD, int dere_pin = DERE_PIN, int config = SDM_UART_CONFIG, bool swapuart = SWAPHWSERIAL);
|
||||
#else
|
||||
#elif defined ( ESP32 ) // on esp32
|
||||
SDM(HardwareSerial& serial, long baud = SDM_UART_BAUD, int dere_pin = DERE_PIN, int config = SDM_UART_CONFIG, int8_t rx_pin = SDM_RX_PIN, int8_t tx_pin = SDM_TX_PIN);
|
||||
#else // on avr
|
||||
SDM(HardwareSerial& serial, long baud = SDM_UART_BAUD, int dere_pin = DERE_PIN, int config = SDM_UART_CONFIG);
|
||||
#endif
|
||||
#else // software serial
|
||||
SDM(ESPeasySerial& serial, long baud = SDM_UART_BAUD, int dere_pin = DERE_PIN);
|
||||
#endif
|
||||
virtual ~SDM();
|
||||
|
||||
void begin(void);
|
||||
float readVal(uint16_t reg, uint8_t node = SDM_B_01); //read value from register = reg and from deviceId = node
|
||||
uint16_t getErrCode(bool _clear = false); //return last errorcode (optional clear this value, default false)
|
||||
uint16_t getErrCount(bool _clear = false); //return total errors count (optional clear this value, default false)
|
||||
uint16_t getSuccCount(bool _clear = false); //return total success count (optional clear this value, default false)
|
||||
void clearErrCode(); //clear last errorcode
|
||||
void clearErrCount(); //clear total errors count
|
||||
void clearSuccCount(); //clear total success count
|
||||
float readVal(uint16_t reg, uint8_t node = SDM_B_01); // read value from register = reg and from deviceId = node
|
||||
void startReadVal(uint16_t reg, uint8_t node = SDM_B_01, uint8_t functionCode = SDM_B_02); // Start sending out the request to read a register from a specific node (allows for async access)
|
||||
uint16_t readValReady(uint8_t node = SDM_B_01, uint8_t functionCode = SDM_B_02); // Check to see if a reply is ready reading from a node (allow for async access)
|
||||
float decodeFloatValue() const;
|
||||
|
||||
float readHoldingRegister(uint16_t reg, uint8_t node = SDM_B_01);
|
||||
bool writeHoldingRegister(float value, uint16_t reg, uint8_t node = SDM_B_01);
|
||||
|
||||
uint32_t getSerialNumber(uint8_t node = SDM_B_01);
|
||||
|
||||
|
||||
uint16_t getErrCode(bool _clear = false); // return last errorcode (optional clear this value, default flase)
|
||||
uint32_t getErrCount(bool _clear = false); // return total errors count (optional clear this value, default flase)
|
||||
uint32_t getSuccCount(bool _clear = false); // return total success count (optional clear this value, default false)
|
||||
void clearErrCode(); // clear last errorcode
|
||||
void clearErrCount(); // clear total errors count
|
||||
void clearSuccCount(); // clear total success count
|
||||
void setMsTurnaround(uint16_t _msturnaround = WAITING_TURNAROUND_DELAY); // set new value for WAITING_TURNAROUND_DELAY (ms), min=SDM_MIN_DELAY, max=SDM_MAX_DELAY
|
||||
void setMsTimeout(uint16_t _mstimeout = RESPONSE_TIMEOUT); // set new value for RESPONSE_TIMEOUT (ms), min=SDM_MIN_DELAY, max=SDM_MAX_DELAY
|
||||
uint16_t getMsTurnaround(); // get current value of WAITING_TURNAROUND_DELAY (ms)
|
||||
uint16_t getMsTimeout(); // get current value of RESPONSE_TIMEOUT (ms)
|
||||
|
||||
private:
|
||||
#ifdef USE_HARDWARESERIAL
|
||||
|
||||
bool validChecksum(const uint8_t* data, size_t messageLength) const;
|
||||
|
||||
void modbusWrite(uint8_t* data, size_t messageLength);
|
||||
|
||||
#if defined ( USE_HARDWARESERIAL )
|
||||
HardwareSerial& sdmSer;
|
||||
#else
|
||||
ESPeasySerial& sdmSer;
|
||||
#endif
|
||||
|
||||
#ifdef USE_HARDWARESERIAL
|
||||
#if defined ( USE_HARDWARESERIAL )
|
||||
int _config = SDM_UART_CONFIG;
|
||||
#if defined ( ESP8266 )
|
||||
bool _swapuart = SWAPHWSERIAL;
|
||||
#elif defined ( ESP32 )
|
||||
int8_t _rx_pin = -1;
|
||||
int8_t _tx_pin = -1;
|
||||
#endif
|
||||
#else
|
||||
#if defined ( ESP8266 ) || defined ( ESP32 )
|
||||
int _config = SDM_UART_CONFIG;
|
||||
#endif
|
||||
int8_t _rx_pin = -1;
|
||||
int8_t _tx_pin = -1;
|
||||
#endif
|
||||
long _baud = SDM_UART_BAUD;
|
||||
int _dere_pin = DERE_PIN;
|
||||
uint16_t readingerrcode = SDM_ERR_NO_ERROR; //4 = timeout; 3 = not enough bytes; 2 = number of bytes OK but bytes b0,b1 or b2 wrong, 1 = crc error
|
||||
uint16_t readingerrcount = 0; //total errors couter
|
||||
uint32_t readingsuccesscount = 0;
|
||||
uint16_t calculateCRC(uint8_t *array, uint8_t num);
|
||||
uint16_t readingerrcode = SDM_ERR_NO_ERROR; // 4 = timeout; 3 = not enough bytes; 2 = number of bytes OK but bytes b0,b1 or b2 wrong, 1 = crc error
|
||||
uint16_t msturnaround = WAITING_TURNAROUND_DELAY;
|
||||
uint16_t mstimeout = RESPONSE_TIMEOUT;
|
||||
uint32_t readingerrcount = 0; // total errors counter
|
||||
uint32_t readingsuccesscount = 0; // total success counter
|
||||
unsigned long resptime = 0;
|
||||
uint8_t sdmarr[FRAMESIZE] = {};
|
||||
uint16_t calculateCRC(const uint8_t *array, uint8_t len) const;
|
||||
void flush(unsigned long _flushtime = 0); // read serial if any old data is available or for a given time in ms
|
||||
void dereSet(bool _state = LOW); // for control MAX485 DE/RE pins, LOW receive from SDM, HIGH transmit to SDM
|
||||
};
|
||||
#endif //SDM_h
|
||||
#endif // SDM_h
|
||||
|
||||
@@ -1,36 +1,93 @@
|
||||
/* Library for reading SDM 120/220/230/630 Modbus Energy meters.
|
||||
/* Library for reading SDM 72/120/220/230/630 Modbus Energy meters.
|
||||
* Reading via Hardware or Software Serial library & rs232<->rs485 converter
|
||||
* 2016-2018 Reaper7 (tested on wemos d1 mini->ESP8266 with Arduino 1.9.0-beta & 2.4.1 esp8266 core)
|
||||
* crc calculation by Jaime García (https://github.com/peninquen/Modbus-Energy-Monitor-Arduino/)
|
||||
* 2016-2023 Reaper7 (tested on wemos d1 mini->ESP8266 with Arduino 1.8.10 & 2.5.2 esp8266 core)
|
||||
* crc calculation by Jaime García (https://github.com/peninquen/Modbus-Energy-Monitor-Arduino/)
|
||||
*/
|
||||
//USER CONFIG:
|
||||
|
||||
/*
|
||||
* USER CONFIG:
|
||||
*/
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
#undef USE_HARDWARESERIAL //undefine USE_HARDWARESERIAL
|
||||
*/
|
||||
// or
|
||||
/*
|
||||
#define USE_HARDWARESERIAL //define USE_HARDWARESERIAL
|
||||
* define or undefine USE_HARDWARESERIAL (uncomment only one or none)
|
||||
*/
|
||||
//#undef USE_HARDWARESERIAL
|
||||
//#define USE_HARDWARESERIAL
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
#define SDM_UART_BAUD 9600 //define user baudrate
|
||||
* define user baudrate
|
||||
*/
|
||||
//#define SDM_UART_BAUD 9600
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
#define DERE_PIN NOT_A_PIN //define user DERE_PIN for control MAX485 DE/RE lines (connect DE & /RE together to this pin)
|
||||
*/
|
||||
//------------------------------------------------------------------------------
|
||||
#ifdef USE_HARDWARESERIAL
|
||||
/*
|
||||
#define SDM_UART_CONFIG SERIAL_8N1 //define user SDM_UART_CONFIG
|
||||
*/
|
||||
/*
|
||||
#define SWAPHWSERIAL 0 //define user SWAPHWSERIAL, if true(1) then swap uart pins from 3/1 to 13/15 (only ESP8266)
|
||||
* define user SDM_RX_PIN and SDM_TX_PIN for esp/avr Software Serial option
|
||||
* or ESP32 with Hardware Serial if default core pins are not suitable
|
||||
*/
|
||||
#if defined ( USE_HARDWARESERIAL )
|
||||
#if defined ( ESP32 )
|
||||
#define SDM_RX_PIN 13
|
||||
#define SDM_TX_PIN 15
|
||||
#endif
|
||||
#else
|
||||
#if defined ( ESP8266 ) || defined ( ESP32 )
|
||||
#define SDM_RX_PIN 13
|
||||
#define SDM_TX_PIN 15
|
||||
#else
|
||||
#define SDM_RX_PIN 10
|
||||
#define SDM_TX_PIN 11
|
||||
#endif
|
||||
#endif
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
#define MAX_MILLIS_TO_WAIT 500 //define user MAX_MILLIS_TO_WAIT to wait for response from SDM
|
||||
* define user DERE_PIN for control MAX485 DE/RE lines (connect DE & /RE together to this pin)
|
||||
*/
|
||||
//------------------------------------------------------------------------------
|
||||
//#define DERE_PIN NOT_A_PIN
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#if defined ( USE_HARDWARESERIAL )
|
||||
|
||||
/*
|
||||
* define user SDM_UART_CONFIG for hardware serial
|
||||
*/
|
||||
//#define SDM_UART_CONFIG SERIAL_8N1
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* define user SWAPHWSERIAL, if true(1) then swap uart pins from 3/1 to 13/15 (only ESP8266)
|
||||
*/
|
||||
//#define SWAPHWSERIAL 0
|
||||
|
||||
#else
|
||||
|
||||
/*
|
||||
* define user SDM_UART_CONFIG for software serial
|
||||
*/
|
||||
//#define SDM_UART_CONFIG SWSERIAL_8N1
|
||||
|
||||
#endif
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* define user WAITING_TURNAROUND_DELAY time in ms to wait for process current request
|
||||
*/
|
||||
//#define WAITING_TURNAROUND_DELAY 200
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* define user RESPONSE_TIMEOUT time in ms to wait for return response from all devices before next request
|
||||
*/
|
||||
//#define RESPONSE_TIMEOUT 500
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,292 @@
|
||||
/* WEMOS D1 Mini
|
||||
______________________________
|
||||
| L T L T L T L T L T L T |
|
||||
| |
|
||||
RST| 1|TX HSer
|
||||
A0| 3|RX HSer
|
||||
D0|16 5|D1
|
||||
D5|14 4|D2
|
||||
D6|12 10kPUP_0|D3
|
||||
RX SSer/HSer swap D7|13 LED_10kPUP_2|D4
|
||||
TX SSer/HSer swap D8|15 |GND
|
||||
3V3|__ |5V
|
||||
| |
|
||||
|___________________________|
|
||||
*/
|
||||
|
||||
//REMEMBER! uncomment #define USE_HARDWARESERIAL
|
||||
//in SDM_Config_User.h file if you want to use hardware uart
|
||||
|
||||
#include <SDM.h> //import SDM library
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <WiFiClient.h>
|
||||
#include <ESP8266mDNS.h>
|
||||
#include <WiFiUdp.h>
|
||||
#include <ArduinoOTA.h>
|
||||
#include <InfluxDbClient.h> //https://github.com/tobiasschuerg/InfluxDB-Client-for-Arduino
|
||||
|
||||
|
||||
|
||||
// WiFi Parameters
|
||||
const char* ssid = "SSID";
|
||||
const char* password = "PASS";
|
||||
#define HOSTNAME "SDM630" // Friedly hostname
|
||||
|
||||
|
||||
|
||||
// InfluxDB v2 server url, e.g. https://eu-central-1-1.aws.cloud2.influxdata.com (Use: InfluxDB UI -> Load Data -> Client Libraries)
|
||||
// InfluxDB v1 server url "http://192.168.10.2:8086"
|
||||
#define INFLUXDB_URL "http://INFLUXDB IP:8086"
|
||||
// InfluxDB v2 server or cloud API authentication token (Use: InfluxDB UI -> Load Data -> Tokens -> <select token>)
|
||||
//#define INFLUXDB_TOKEN "server token"
|
||||
// InfluxDB v2 organization id (Use: InfluxDB UI -> Settings -> Profile -> <name under tile> )
|
||||
//#define INFLUXDB_ORG "org id"
|
||||
// InfluxDB v2 bucket name (Use: InfluxDB UI -> Load Data -> Buckets)
|
||||
//#define INFLUXDB_BUCKET "bucket name"
|
||||
//influxdb v1 database
|
||||
#define INFLUXDB_DB_NAME "DATABASE"
|
||||
//influxdb v1 user and pass
|
||||
#define INFLUXDB_USER "USERNAME"
|
||||
#define INFLUXDB_PASSWORD "PASSWORD"
|
||||
|
||||
// Set timezone string according to https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
|
||||
// Examples:
|
||||
// Pacific Time: "PST8PDT"
|
||||
// Eastern: "EST5EDT"
|
||||
// Japanesse: "JST-9"
|
||||
// Central Europe: "CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
#define TZ_INFO "CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
#define WRITE_PRECISION WritePrecision::S
|
||||
#define MAX_BATCH_SIZE 10
|
||||
#define WRITE_BUFFER_SIZE 30
|
||||
|
||||
// InfluxDB client instance with preconfigured InfluxCloud certificate
|
||||
//InfluxDBClient client(INFLUXDB_URL, INFLUXDB_ORG, INFLUXDB_BUCKET, INFLUXDB_TOKEN, InfluxDbCloud2CACert);
|
||||
|
||||
InfluxDBClient client(INFLUXDB_URL, INFLUXDB_DB_NAME);
|
||||
|
||||
// Number for loops to sync time using NTP
|
||||
int iterations = 0;
|
||||
|
||||
const int dere_pin = 2;
|
||||
|
||||
//REMEMBER TO SET BAUD TO WHAT YOUR METER SET SET TO
|
||||
|
||||
#if defined ( USE_HARDWARESERIAL ) //for HWSERIAL
|
||||
|
||||
#if defined ( ESP8266 ) //for ESP8266
|
||||
SDM sdm(Serial, 38400, dere_pin, SERIAL_8N1); //config SDM
|
||||
#else //for AVR
|
||||
SDM sdm(Serial, 38400, dere_pin); //config SDM on Serial1 (if available!)
|
||||
#endif
|
||||
|
||||
#else //for SWSERIAL
|
||||
|
||||
#include <SoftwareSerial.h> //import SoftwareSerial library
|
||||
#if defined ( ESP8266 ) //for ESP
|
||||
SoftwareSerial swSerSDM; //config SoftwareSerial
|
||||
SDM sdm(swSerSDM, 9600, dere_pin, SWSERIAL_8N1, SDM_RX_PIN, SDM_TX_PIN); //config SDM
|
||||
#else //for AVR
|
||||
SoftwareSerial swSerSDM(SDM_RX_PIN, SDM_TX_PIN); //config SoftwareSerial
|
||||
SDM sdm(swSerSDM, 9600, dere_pin); //config SDM
|
||||
#endif
|
||||
|
||||
#endif //#if defined ( USE_HARDWARESERIAL )
|
||||
|
||||
#define READSDMEVERY 1000 //read sdm every 2000ms
|
||||
#define NBREG 23 // SET TO the number of parameters in sdm_struct sdmarr[NBREG] and maximum 40
|
||||
|
||||
|
||||
typedef struct {
|
||||
float regvalarr;
|
||||
const uint16_t regarr;
|
||||
const String regtext;
|
||||
} sdm_struct;
|
||||
|
||||
sdm_struct sdmarr[NBREG] = {
|
||||
{0.00, SDM_PHASE_1_VOLTAGE,"VoltageL1"}, //V
|
||||
{0.00, SDM_PHASE_2_VOLTAGE,"VoltageL2"}, //V
|
||||
{0.00, SDM_PHASE_3_VOLTAGE,"VoltageL3"}, //V
|
||||
{0.00, SDM_PHASE_1_CURRENT,"CurrentL1"}, //A
|
||||
{0.00, SDM_PHASE_2_CURRENT,"CurrentL2"}, //A
|
||||
{0.00, SDM_PHASE_3_CURRENT,"CurrentL3"}, //A
|
||||
{0.00, SDM_SUM_LINE_CURRENT,"CurrentSUM"}, //A
|
||||
{0.00, SDM_PHASE_1_POWER,"PowerL1"}, //W
|
||||
{0.00, SDM_PHASE_2_POWER,"PowerL2"}, //W
|
||||
{0.00, SDM_PHASE_3_POWER,"PowerL3"}, //W
|
||||
{0.00, SDM_TOTAL_SYSTEM_POWER,"PowerSUM"}, //W
|
||||
{0.00, SDM_TOTAL_SYSTEM_POWER_FACTOR,"PFTOTAL"}, //PF
|
||||
{0.00, SDM_FREQUENCY,"FREQUENCY"}, //Hz
|
||||
{0.00, SDM_IMPORT_ACTIVE_ENERGY,"ImportEnergi"}, //kWh
|
||||
{0.00, SDM_TOTAL_ACTIVE_ENERGY,"TotalEnergi"}, //kWh
|
||||
{0.00, SDM_LINE_1_TO_LINE_2_VOLTS,"VoltageL1L2"}, //V
|
||||
{0.00, SDM_LINE_2_TO_LINE_3_VOLTS,"VoltageL2L3"}, //V
|
||||
{0.00, SDM_LINE_3_TO_LINE_1_VOLTS,"VoltageL3L1"}, //V
|
||||
{0.00, SDM_TOTAL_SYSTEM_REACTIVE_POWER,"ReactivePowerSUM"}, //VAr
|
||||
{0.00, SDM_TOTAL_SYSTEM_APPARENT_POWER,"ApparentPowerSUM"}, //VA
|
||||
{0.00, SDM_L1_IMPORT_ACTIVE_ENERGY,"ImportL1"}, //kWh
|
||||
{0.00, SDM_L2_IMPORT_ACTIVE_ENERGY,"ImportL2"}, //kWh
|
||||
{0.00, SDM_L3_IMPORT_ACTIVE_ENERGY,"ImportL3"} //kWh
|
||||
};
|
||||
|
||||
unsigned long readtime;
|
||||
time_t ntpLastUpdate;
|
||||
int ntpSyncTime = 3600;
|
||||
bool read_done = false;
|
||||
|
||||
|
||||
void setup() {
|
||||
//Serial.begin(115200); //initialize serial
|
||||
sdm.begin(); //initialize SDM communication
|
||||
|
||||
// Setup wifi
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ssid, password);
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(1000);
|
||||
//Serial.println("Connecting...");
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
// Setup: MDNS responder
|
||||
// ***************************************************************************
|
||||
MDNS.begin(HOSTNAME);
|
||||
//Serial.print("Hostname: ");
|
||||
//Serial.print(HOSTNAME);
|
||||
|
||||
ArduinoOTA.begin();
|
||||
|
||||
|
||||
// Set InfluxDB 1 authentication params
|
||||
client.setConnectionParamsV1(INFLUXDB_URL, INFLUXDB_DB_NAME, INFLUXDB_USER, INFLUXDB_PASSWORD);
|
||||
|
||||
// Sync time for certificate validation
|
||||
timeSync();
|
||||
|
||||
// Check server connection
|
||||
if (client.validateConnection()) {
|
||||
//Serial.print("Connected to InfluxDB: ");
|
||||
//Serial.println(client.getServerUrl());
|
||||
} else {
|
||||
//Serial.print("InfluxDB connection failed: ");
|
||||
//Serial.println(client.getLastErrorMessage());
|
||||
}
|
||||
|
||||
//Enable messages batching and retry buffer
|
||||
client.setWriteOptions(WRITE_PRECISION, MAX_BATCH_SIZE, WRITE_BUFFER_SIZE);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// Sync time for batching once per hour
|
||||
if(time(nullptr)-ntpLastUpdate > ntpSyncTime) {//if (iterations++ >= 360) {
|
||||
timeSync();
|
||||
iterations = 0;
|
||||
}
|
||||
|
||||
if (millis() - readtime >= READSDMEVERY) {
|
||||
sdmRead();
|
||||
readtime = millis();
|
||||
}
|
||||
|
||||
if(read_done){
|
||||
//put data to influx in buffer
|
||||
time_t tnow = time(nullptr);
|
||||
for (int i = 0; i < NBREG; i++) {
|
||||
Point powerMeter("SDM630");
|
||||
//powerMeter.addTag("device", "Main");
|
||||
powerMeter.addTag("Type", sdmarr[i].regtext );
|
||||
//powerMeter.addTag("channel", String(WiFi.channel(i)));
|
||||
//powerMeter.addTag("open", String(WiFi.encryptionType(i) == WIFI_AUTH_OPEN));
|
||||
powerMeter.addField("value", sdmarr[i].regvalarr);
|
||||
powerMeter.setTime(tnow); //set the time
|
||||
|
||||
// Print what are we exactly writing
|
||||
//Serial.print("Writing: ");
|
||||
//Serial.println(powerMeter.toLineProtocol());
|
||||
|
||||
// Write point into buffer - low priority measures
|
||||
client.writePoint(powerMeter);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// End of the iteration - force write of all the values into InfluxDB as single transaction
|
||||
//Serial.println("Flushing data into InfluxDB");
|
||||
if (!client.flushBuffer()) {
|
||||
//Serial.print("InfluxDB flush failed: ");
|
||||
//Serial.println(client.getLastErrorMessage());
|
||||
//Serial.print("Full buffer: ");
|
||||
//Serial.println(client.isBufferFull() ? "Yes" : "No");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
for(int i = 0; i < NBREG; i++) {
|
||||
//Serial.print(sdmarr[i].regtext);
|
||||
//Serial.print(" = ");
|
||||
//Serial.println(sdmarr[i].regvalarr);
|
||||
|
||||
}
|
||||
read_done = false;
|
||||
|
||||
|
||||
}
|
||||
|
||||
ArduinoOTA.handle();
|
||||
yield();
|
||||
/*Serial.println("Wait 10s");
|
||||
delay(10000);*/
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void timeSync() {
|
||||
// Synchronize UTC time with NTP servers
|
||||
// Accurate time is necessary for certificate validaton and writing in batches
|
||||
configTime(0, 0, "pool.ntp.org", "time.nis.gov");
|
||||
// Set timezone
|
||||
setenv("TZ", TZ_INFO, 1);
|
||||
|
||||
// Wait till time is synced
|
||||
//Serial.print("Syncing time");
|
||||
int i = 0;
|
||||
while (time(nullptr) < 1000000000ul && i < 100) {
|
||||
//Serial.print(".");
|
||||
delay(100);
|
||||
i++;
|
||||
}
|
||||
//Serial.println();
|
||||
|
||||
// Show time
|
||||
time_t tnow = time(nullptr);
|
||||
ntpLastUpdate = time(nullptr);
|
||||
//Serial.print("Synchronized time: ");
|
||||
//Serial.println(String(ctime(&tnow)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
void sdmRead() {
|
||||
float tmpval = NAN;
|
||||
|
||||
for (uint8_t i = 0; i < NBREG; i++) {
|
||||
tmpval = sdm.readVal(sdmarr[i].regarr);
|
||||
|
||||
if (isnan(tmpval))
|
||||
sdmarr[i].regvalarr = 0.00;
|
||||
else
|
||||
sdmarr[i].regvalarr = tmpval;
|
||||
|
||||
yield();
|
||||
}
|
||||
read_done = true;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
const char index_page[] PROGMEM = R"=====(
|
||||
<!DOCTYPE HTML>
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<META name='viewport' content='width=device-width, initial-scale=1'>
|
||||
<TITLE>SDM live POWER table</TITLE>
|
||||
<SCRIPT>
|
||||
var xmlHttp=createXmlHttpObject();
|
||||
function createXmlHttpObject(){
|
||||
if(window.XMLHttpRequest){
|
||||
xmlHttp=new XMLHttpRequest();
|
||||
}else{
|
||||
xmlHttp=new ActiveXObject('Microsoft.XMLHTTP');
|
||||
}
|
||||
return xmlHttp;
|
||||
}
|
||||
function process(){
|
||||
if(xmlHttp.readyState==0 || xmlHttp.readyState==4){
|
||||
xmlHttp.open('PUT','xml',true);
|
||||
xmlHttp.onreadystatechange=handleServerResponse;
|
||||
xmlHttp.send(null);
|
||||
}
|
||||
setTimeout('process()',2000);
|
||||
}
|
||||
function handleServerResponse(){
|
||||
if(xmlHttp.readyState==4 && xmlHttp.status==200){
|
||||
xmlResponse=xmlHttp.responseXML;
|
||||
for(i=0;i<5;i++){
|
||||
xmldoc=xmlResponse.getElementsByTagName('response'+i)[0].firstChild.nodeValue;
|
||||
document.getElementById('resp'+i).innerHTML=xmldoc;
|
||||
}
|
||||
xmldoc=xmlResponse.getElementsByTagName('sdmcnt')[0].firstChild.nodeValue;
|
||||
document.getElementById('sdmreadcnt').innerHTML=xmldoc;
|
||||
xmldoc=xmlResponse.getElementsByTagName('errtotal')[0].firstChild.nodeValue;
|
||||
document.getElementById('readerrtotal').innerHTML=xmldoc;
|
||||
xmldoc=xmlResponse.getElementsByTagName('errcode')[0].firstChild.nodeValue;
|
||||
document.getElementById('readerrcode').innerHTML=xmldoc;
|
||||
xmldoc=xmlResponse.getElementsByTagName('upt')[0].firstChild.nodeValue;
|
||||
document.getElementById('uptime').innerHTML=xmldoc;
|
||||
xmldoc=xmlResponse.getElementsByTagName('freeh')[0].firstChild.nodeValue;
|
||||
document.getElementById('freeheap').innerHTML=xmldoc;
|
||||
}
|
||||
}
|
||||
</SCRIPT>
|
||||
<STYLE>
|
||||
h1 {
|
||||
font-size: 120%;
|
||||
color: blue;
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
table{
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table, th, td {
|
||||
text-align: center;
|
||||
border: 1px solid blue;
|
||||
}
|
||||
tr:nth-child(even) {background-color: #f2f2f2}
|
||||
</STYLE>
|
||||
</HEAD>
|
||||
<BODY onload='process()'>
|
||||
<CENTER>
|
||||
<H1>SDM live POWER table</H1>
|
||||
<TABLE BORDER=1>
|
||||
<TR><TH title="VOLTAGE">VOLTAGE</TH><TD><A id='resp0'></A></TD><TD>V</TD></TR>
|
||||
<TR><TH title="CURRENT">CURRENT</TH><TD><A id='resp1'></A></TD><TD>A</TD></TR>
|
||||
<TR><TH title="POWER">POWER</TH><TD><A id='resp2'></A></TD><TD>W</TD></TR>
|
||||
<TR><TH title="POWER FACTOR">POWER FACTOR</TH><TD><A id='resp3'></A></TD><TD>PF</TD></TR>
|
||||
<TR><TH title="FREQUENCY">FREQUENCY</TH><TD><A id='resp4'></A></TD><TD>Hz</TD></TR>
|
||||
<TR><TH title="SDM READ OK COUNT">SDM READ OK COUNT</TH><TD><A id='sdmreadcnt'></A></TD><TD>total</TD></TR>
|
||||
<TR><TH title="SDM READ ERR. COUNT">SDM READ ERR. COUNT</TH><TD><A id='readerrtotal'></A></TD><TD>total</TD></TR>
|
||||
<TR><TH title="SDM READ ERR. CODE ">SDM READ ERR. CODE </TH><TD><A id='readerrcode'></A></TD><TD>code</TD></TR>
|
||||
<TR><TH title="UPTIME">UPTIME</TH><TD><A id='uptime'></A></TD><TD>d h:m:s</TD></TR>
|
||||
<TR><TH title="FREE HEAP">FREE HEAP</TH><TD><A id='freeheap'></A></TD><TD>bytes</TD></TR>
|
||||
</TABLE>
|
||||
</CENTER>
|
||||
</BODY>
|
||||
</HTML>
|
||||
)=====";
|
||||
+218
@@ -0,0 +1,218 @@
|
||||
//sdm live page example by reaper7
|
||||
|
||||
#define READSDMEVERY 2000 //read sdm every 2000ms
|
||||
#define NBREG 5 //number of sdm registers to read
|
||||
//#define USE_STATIC_IP
|
||||
|
||||
/* mh et esp32 minikit
|
||||
______________________
|
||||
/ L T L T L T L T \
|
||||
| |
|
||||
|O O RST TX HW O O|
|
||||
|O O SVP RX HW O O|
|
||||
|O O IO26 IO22 O O|
|
||||
|O O IO18 IO21 O O|
|
||||
|O O IO19 IO17 O O|
|
||||
|O O IO23 IO16 O O|
|
||||
|O O IO05 GND O O|
|
||||
|O O 3V3 VCC O O|
|
||||
|O O TCK TDO O O|
|
||||
|O O SD3 SD0 O O|
|
||||
--| |
|
||||
|___________________|
|
||||
*/
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <WiFiUdp.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include <ArduinoOTA.h>
|
||||
|
||||
#include <AsyncTCP.h> //https://github.com/me-no-dev/AsyncTCP
|
||||
#include <ESPAsyncWebServer.h> //https://github.com/me-no-dev/ESPAsyncWebServer
|
||||
|
||||
#include <SDM.h> //https://github.com/reaper7/SDM_Energy_Meter
|
||||
|
||||
#include "index_page.h"
|
||||
|
||||
#if !defined ( USE_HARDWARESERIAL )
|
||||
#error "This example works with Hardware Serial on esp32, please uncomment #define USE_HARDWARESERIAL in SDM_Config_User.h"
|
||||
#endif
|
||||
//------------------------------------------------------------------------------
|
||||
AsyncWebServer server(80);
|
||||
|
||||
SDM sdm(Serial, SDM_UART_BAUD, NOT_A_PIN, SERIAL_8N1, 3, 1); //esp32 default pins for Serial0 => RX pin 3, TX pin 1
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
String devicename = "PWRMETER";
|
||||
|
||||
#if defined ( USE_STATIC_IP )
|
||||
IPAddress ip(192, 168, 0, 130);
|
||||
IPAddress gateway(192, 168, 0, 1);
|
||||
IPAddress subnet(255, 255, 255, 0);
|
||||
#endif
|
||||
|
||||
const char* wifi_ssid = "YOUR_SSID";
|
||||
const char* wifi_password = "YOUR_PASSWORD";
|
||||
|
||||
unsigned long readtime;
|
||||
//------------------------------------------------------------------------------
|
||||
typedef volatile struct {
|
||||
volatile float regvalarr;
|
||||
const uint16_t regarr;
|
||||
} sdm_struct;
|
||||
|
||||
volatile sdm_struct sdmarr[NBREG] = {
|
||||
{0.00, SDM_PHASE_1_VOLTAGE}, //V
|
||||
{0.00, SDM_PHASE_1_CURRENT}, //A
|
||||
{0.00, SDM_PHASE_1_POWER}, //W
|
||||
{0.00, SDM_PHASE_1_POWER_FACTOR}, //PF
|
||||
{0.00, SDM_FREQUENCY} //Hz
|
||||
};
|
||||
//------------------------------------------------------------------------------
|
||||
String getUptimeString() {
|
||||
uint16_t days;
|
||||
uint8_t hours;
|
||||
uint8_t minutes;
|
||||
uint8_t seconds;
|
||||
|
||||
#define SECS_PER_MIN 60
|
||||
#define SECS_PER_HOUR 3600
|
||||
#define SECS_PER_DAY 86400
|
||||
|
||||
time_t uptime = millis() / 1000;
|
||||
|
||||
seconds = uptime % SECS_PER_MIN;
|
||||
uptime -= seconds;
|
||||
minutes = (uptime % SECS_PER_HOUR) / SECS_PER_MIN;
|
||||
uptime -= minutes * SECS_PER_MIN;
|
||||
hours = (uptime % SECS_PER_DAY) / SECS_PER_HOUR;
|
||||
uptime -= hours * SECS_PER_HOUR;
|
||||
days = uptime / SECS_PER_DAY;
|
||||
|
||||
char buffer[20];
|
||||
sprintf(buffer, "%4u days %02d:%02d:%02d", days, hours, minutes, seconds);
|
||||
return buffer;
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
void xmlrequest(AsyncWebServerRequest *request) {
|
||||
String XML = F("<?xml version='1.0'?><xml>");
|
||||
for (int i = 0; i < NBREG; i++) {
|
||||
XML += "<response" + (String)i + ">";
|
||||
XML += String(sdmarr[i].regvalarr,2);
|
||||
XML += "</response" + (String)i + ">";
|
||||
}
|
||||
XML += F("<sdmcnt>");
|
||||
XML += String(sdm.getSuccCount());
|
||||
XML += F("</sdmcnt>");
|
||||
XML += F("<errtotal>");
|
||||
XML += String(sdm.getErrCount());
|
||||
XML += F("</errtotal>");
|
||||
XML += F("<errcode>");
|
||||
XML += String(sdm.getErrCode());
|
||||
XML += F("</errcode>");
|
||||
XML += F("<upt>");
|
||||
XML += getUptimeString();
|
||||
XML += F("</upt>");
|
||||
XML += F("<freeh>");
|
||||
XML += String(ESP.getFreeHeap());
|
||||
XML += F("</freeh>");
|
||||
XML += F("</xml>");
|
||||
request->send(200, "text/xml", XML);
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
void indexrequest(AsyncWebServerRequest *request) {
|
||||
request->send_P(200, "text/html", index_page);
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
void ledOn() {
|
||||
digitalWrite(LED_BUILTIN, HIGH);
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
void ledOff() {
|
||||
digitalWrite(LED_BUILTIN, LOW);
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
void ledSwap() {
|
||||
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
void otaInit() {
|
||||
ArduinoOTA.setHostname(devicename.c_str());
|
||||
|
||||
ArduinoOTA.onStart([]() {
|
||||
ledOn();
|
||||
});
|
||||
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
|
||||
ledSwap();
|
||||
});
|
||||
ArduinoOTA.onEnd([]() {
|
||||
ledOff();
|
||||
});
|
||||
ArduinoOTA.onError([](ota_error_t error) {
|
||||
ledOff();
|
||||
});
|
||||
ArduinoOTA.begin();
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
void serverInit() {
|
||||
server.on("/", HTTP_GET, indexrequest);
|
||||
server.on("/xml", HTTP_PUT, xmlrequest);
|
||||
server.onNotFound([](AsyncWebServerRequest *request){
|
||||
request->send(404);
|
||||
});
|
||||
server.begin();
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
static void wifiInit() {
|
||||
WiFi.persistent(false); // Do not write new connections to FLASH
|
||||
WiFi.mode(WIFI_STA);
|
||||
#if defined ( USE_STATIC_IP )
|
||||
WiFi.config(ip, gateway, subnet); // Set fixed IP Address
|
||||
#endif
|
||||
WiFi.begin(wifi_ssid, wifi_password);
|
||||
|
||||
while( WiFi.status() != WL_CONNECTED ) { // Wait for WiFi connection
|
||||
ledSwap();
|
||||
delay(100);
|
||||
}
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
void sdmRead() {
|
||||
float tmpval = NAN;
|
||||
|
||||
for (uint8_t i = 0; i < NBREG; i++) {
|
||||
tmpval = sdm.readVal(sdmarr[i].regarr);
|
||||
|
||||
if (isnan(tmpval))
|
||||
sdmarr[i].regvalarr = 0.00;
|
||||
else
|
||||
sdmarr[i].regvalarr = tmpval;
|
||||
|
||||
yield();
|
||||
}
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
void setup() {
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
ledOn();
|
||||
|
||||
wifiInit();
|
||||
otaInit();
|
||||
serverInit();
|
||||
sdm.begin();
|
||||
|
||||
readtime = millis();
|
||||
|
||||
ledOff();
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
void loop() {
|
||||
ArduinoOTA.handle();
|
||||
|
||||
if (millis() - readtime >= READSDMEVERY) {
|
||||
sdmRead();
|
||||
readtime = millis();
|
||||
}
|
||||
|
||||
yield();
|
||||
}
|
||||
+12
-12
@@ -28,17 +28,17 @@ TX SSer/HSer swap D8|15 |GND
|
||||
#include <ESPAsyncTCP.h> //https://github.com/me-no-dev/ESPAsyncTCP
|
||||
#include <ESPAsyncWebServer.h> //https://github.com/me-no-dev/ESPAsyncWebServer
|
||||
|
||||
#include <SoftwareSerial.h> //import SoftwareSerial library (if used)
|
||||
#include <SDM.h> //https://github.com/reaper7/SDM_Energy_Meter
|
||||
|
||||
#include "index_page.h"
|
||||
|
||||
#if !defined ( USE_HARDWARESERIAL )
|
||||
#error "This example works with Hardware Serial on esp8266, please uncomment #define USE_HARDWARESERIAL in SDM_Config_User.h"
|
||||
#endif
|
||||
//------------------------------------------------------------------------------
|
||||
AsyncWebServer server(80);
|
||||
|
||||
SoftwareSerial swSerSDM(13, 15); //config SoftwareSerial (rx->pin13 / tx->pin15) (if used)
|
||||
|
||||
SDM sdm(swSerSDM, 9600, NOT_A_PIN); //SOFTWARE SERIAL
|
||||
//SDM sdm(Serial, 9600, NOT_A_PIN, SERIAL_8N1, false); //HARDWARE SERIAL
|
||||
SDM sdm(Serial, SDM_UART_BAUD, NOT_A_PIN, SERIAL_8N1, false); //HARDWARE SERIAL
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
String devicename = "PWRMETER";
|
||||
@@ -62,16 +62,16 @@ typedef volatile struct {
|
||||
} sdm_struct;
|
||||
|
||||
volatile sdm_struct sdmarr[NBREG] = {
|
||||
{0.00, SDM220T_VOLTAGE}, //V
|
||||
{0.00, SDM220T_CURRENT}, //A
|
||||
{0.00, SDM220T_POWER}, //W
|
||||
{0.00, SDM220T_POWER_FACTOR}, //PF
|
||||
{0.00, SDM220T_FREQUENCY}, //Hz
|
||||
{0.00, SDM_PHASE_1_VOLTAGE}, //V
|
||||
{0.00, SDM_PHASE_1_CURRENT}, //A
|
||||
{0.00, SDM_PHASE_1_POWER}, //W
|
||||
{0.00, SDM_PHASE_1_POWER_FACTOR}, //PF
|
||||
{0.00, SDM_FREQUENCY} //Hz
|
||||
};
|
||||
//------------------------------------------------------------------------------
|
||||
void xmlrequest(AsyncWebServerRequest *request) {
|
||||
String XML = F("<?xml version='1.0'?><xml>");
|
||||
for (int i = 0; i < NBREG; i++) {
|
||||
for (int i = 0; i < NBREG; i++) {
|
||||
XML += "<response" + (String)i + ">";
|
||||
XML += String(sdmarr[i].regvalarr,2);
|
||||
XML += "</response" + (String)i + ">";
|
||||
@@ -87,7 +87,7 @@ void xmlrequest(AsyncWebServerRequest *request) {
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
void indexrequest(AsyncWebServerRequest *request) {
|
||||
request->send_P(200, "text/html", index_page);
|
||||
request->send_P(200, "text/html", index_page);
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
void ledOn() {
|
||||
@@ -14,12 +14,33 @@ TX SSer/HSer swap D8|15 |GND
|
||||
|___________________________|
|
||||
*/
|
||||
|
||||
#include <SoftwareSerial.h> //import SoftwareSerial library
|
||||
//REMEMBER! uncomment #define USE_HARDWARESERIAL
|
||||
//in SDM_Config_User.h file if you want to use hardware uart
|
||||
|
||||
#include <SDM.h> //import SDM library
|
||||
|
||||
SoftwareSerial swSerSDM(13, 15); //config SoftwareSerial (rx->pin13 / tx->pin15)
|
||||
#if defined ( USE_HARDWARESERIAL ) //for HWSERIAL
|
||||
|
||||
SDM sdm(swSerSDM, 9600, NOT_A_PIN); //config SDM
|
||||
#if defined ( ESP8266 ) //for ESP8266
|
||||
SDM sdm(Serial1, SDM_UART_BAUD, NOT_A_PIN, SERIAL_8N1); //config SDM
|
||||
#elif defined ( ESP32 ) //for ESP32
|
||||
SDM sdm(Serial1, SDM_UART_BAUD, NOT_A_PIN, SERIAL_8N1, SDM_RX_PIN, SDM_TX_PIN); //config SDM
|
||||
#else //for AVR
|
||||
SDM sdm(Serial1, SDM_UART_BAUD, NOT_A_PIN); //config SDM on Serial1 (if available!)
|
||||
#endif
|
||||
|
||||
#else //for SWSERIAL
|
||||
|
||||
#include <SoftwareSerial.h> //import SoftwareSerial library
|
||||
#if defined ( ESP8266 ) || defined ( ESP32 ) //for ESP
|
||||
SoftwareSerial swSerSDM; //config SoftwareSerial
|
||||
SDM sdm(swSerSDM, SDM_UART_BAUD, NOT_A_PIN, SWSERIAL_8N1, SDM_RX_PIN, SDM_TX_PIN); //config SDM
|
||||
#else //for AVR
|
||||
SoftwareSerial swSerSDM(SDM_RX_PIN, SDM_TX_PIN); //config SoftwareSerial
|
||||
SDM sdm(swSerSDM, SDM_UART_BAUD, NOT_A_PIN); //config SDM
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200); //initialize serial
|
||||
@@ -32,26 +53,20 @@ void loop() {
|
||||
Serial.print(bufout);
|
||||
|
||||
Serial.print("Voltage: ");
|
||||
Serial.print(sdm.readVal(SDM220T_VOLTAGE), 2); //display voltage
|
||||
Serial.print(sdm.readVal(SDM_PHASE_1_VOLTAGE), 2); //display voltage
|
||||
Serial.println("V");
|
||||
|
||||
delay(50);
|
||||
|
||||
Serial.print("Current: ");
|
||||
Serial.print(sdm.readVal(SDM220T_CURRENT), 2); //display current
|
||||
Serial.print(sdm.readVal(SDM_PHASE_1_CURRENT), 2); //display current
|
||||
Serial.println("A");
|
||||
|
||||
delay(50);
|
||||
|
||||
Serial.print("Power: ");
|
||||
Serial.print(sdm.readVal(SDM220T_POWER), 2); //display power
|
||||
Serial.print(sdm.readVal(SDM_PHASE_1_POWER), 2); //display power
|
||||
Serial.println("W");
|
||||
|
||||
delay(50);
|
||||
|
||||
Serial.print("Frequency: ");
|
||||
Serial.print(sdm.readVal(SDM220T_FREQUENCY), 2); //display frequency
|
||||
Serial.println("Hz");
|
||||
Serial.print(sdm.readVal(SDM_FREQUENCY), 2); //display frequency
|
||||
Serial.println("Hz");
|
||||
|
||||
delay(1000); //wait a while before next loop
|
||||
}
|
||||
|
||||
@@ -5,5 +5,11 @@ begin KEYWORD2
|
||||
readVal KEYWORD2
|
||||
getErrCode KEYWORD2
|
||||
getErrCount KEYWORD2
|
||||
getSuccCount KEYWORD2
|
||||
clearErrCode KEYWORD2
|
||||
clearErrCount KEYWORD2
|
||||
clearSuccCount KEYWORD2
|
||||
setMsTurnaround KEYWORD2
|
||||
setMsTimeout KEYWORD2
|
||||
getMsTurnaround KEYWORD2
|
||||
getMsTimeout KEYWORD2
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"name": "SDM",
|
||||
"version": "2.0.0",
|
||||
"keywords": [
|
||||
"modbus", "energy"
|
||||
],
|
||||
"description": "SDM 120/220/230/630 modbus energy meter",
|
||||
"repository":
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/reaper7/SDM_Energy_Meter"
|
||||
},
|
||||
"authors":
|
||||
[
|
||||
{
|
||||
"name": "Reaper7",
|
||||
"maintainer": true
|
||||
}
|
||||
],
|
||||
"dependencies": [
|
||||
{
|
||||
"name": "ESPeasySerial",
|
||||
"authors": "Gijs Noorlander",
|
||||
"frameworks": "arduino"
|
||||
}
|
||||
],
|
||||
"frameworks": "arduino",
|
||||
"platforms": [
|
||||
"espressif8266","espressif32"
|
||||
]
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
name=SDM
|
||||
version=2.0.0
|
||||
version=2.2.2
|
||||
author=Reaper7
|
||||
maintainer=Reaper7
|
||||
sentence=SDM 120/220/230/630 modbus energy meter
|
||||
paragraph=library for ESP8266
|
||||
sentence=SDM72/120/220/230/630 DDM18SD modbus energy meter
|
||||
paragraph=library for ESP8266, ESP32, AVR
|
||||
category=Communication
|
||||
depends=ESPEasySerial
|
||||
url=https://github.com/reaper7/SDM_Energy_Meter
|
||||
architectures=esp8266,avr
|
||||
architectures=esp8266,esp32,avr
|
||||
|
||||
+194
-207
@@ -3,63 +3,34 @@
|
||||
#ifdef USES_P078
|
||||
|
||||
// #######################################################################################################
|
||||
// ######################## Plugin 078: SDM120C/220T/230/630 Eastron Energy Meter ########################
|
||||
// ############## Plugin 078: SDM120/SDM120CT/220/230/630/72D/DDM18SD Eastron Energy Meter ###############
|
||||
// #######################################################################################################
|
||||
|
||||
/*
|
||||
Plugin written by: Sergio Faustino sjfaustino__AT__gmail.com
|
||||
|
||||
This plugin reads available values of an Eastron SDM120C Energy Meter.
|
||||
It will also work with all the other superior model such as SDM220T, SDM230 AND SDM630 series.
|
||||
This plugin reads available values of an Eastron SDM120C SDM120/SDM120CT/220/230/630/72D & also DDM18SD.
|
||||
*/
|
||||
|
||||
# define PLUGIN_078
|
||||
# define PLUGIN_ID_078 78
|
||||
# define PLUGIN_NAME_078 "Energy (AC) - Eastron SDM120C/220T/230/630"
|
||||
# define PLUGIN_NAME_078 "Energy (AC) - Eastron SDMxxx Modbus"
|
||||
|
||||
# define P078_DEV_ID PCONFIG(0)
|
||||
# define P078_DEV_ID_LABEL PCONFIG_LABEL(0)
|
||||
# define P078_MODEL PCONFIG(1)
|
||||
# define P078_MODEL_LABEL PCONFIG_LABEL(1)
|
||||
# define P078_BAUDRATE PCONFIG(2)
|
||||
# define P078_BAUDRATE_LABEL PCONFIG_LABEL(2)
|
||||
# define P078_QUERY1 PCONFIG(3)
|
||||
# define P078_QUERY2 PCONFIG(4)
|
||||
# define P078_QUERY3 PCONFIG(5)
|
||||
# define P078_QUERY4 PCONFIG(6)
|
||||
# define P078_DEPIN CONFIG_PIN3
|
||||
# define P078_NR_OUTPUT_VALUES 4
|
||||
# define P078_NR_OUTPUT_OPTIONS_SDM220_SDM120CT_SDM120 14
|
||||
# define P078_NR_OUTPUT_OPTIONS_SDM230 24
|
||||
# define P078_NR_OUTPUT_OPTIONS_SDM630 86
|
||||
# define P078_NR_OUTPUT_OPTIONS_SDM72D 9
|
||||
# define P078_NR_OUTPUT_OPTIONS_DDM18SD 7
|
||||
|
||||
# define P078_DEV_ID_DFLT 1
|
||||
# define P078_MODEL_DFLT 0 // SDM120C
|
||||
# define P078_BAUDRATE_DFLT 1 // 9600 baud
|
||||
# define P078_QUERY1_DFLT 0 // Voltage (V)
|
||||
# define P078_QUERY2_DFLT 1 // Current (A)
|
||||
# define P078_QUERY3_DFLT 2 // Power (W)
|
||||
# define P078_QUERY4_DFLT 5 // Power Factor (cos-phi)
|
||||
|
||||
# define P078_NR_OUTPUT_VALUES 4
|
||||
# define P078_NR_OUTPUT_OPTIONS 10
|
||||
# define P078_QUERY1_CONFIG_POS 3
|
||||
|
||||
# include <ESPeasySerial.h>
|
||||
# include <SDM.h> // Requires SDM library from Reaper7 - https://github.com/reaper7/SDM_Energy_Meter/
|
||||
# include "src/PluginStructs/P078_data_struct.h"
|
||||
|
||||
// These pointers may be used among multiple instances of the same plugin,
|
||||
// as long as the same serial settings are used.
|
||||
ESPeasySerial *Plugin_078_SoftSerial = nullptr;
|
||||
SDM *Plugin_078_SDM = nullptr;
|
||||
boolean Plugin_078_init = false;
|
||||
|
||||
|
||||
// Forward declaration helper functions
|
||||
const __FlashStringHelper* p078_getQueryString(uint8_t query);
|
||||
const __FlashStringHelper* p078_getQueryValueString(uint8_t query);
|
||||
unsigned int p078_getRegister(uint8_t query,
|
||||
uint8_t model);
|
||||
float p078_readVal(uint8_t query,
|
||||
uint8_t node,
|
||||
unsigned int model);
|
||||
|
||||
ESPeasySerial *Plugin_078_ESPEasySerial = nullptr;
|
||||
SDM *Plugin_078_SDM = nullptr;
|
||||
boolean Plugin_078_init = false;
|
||||
|
||||
boolean Plugin_078(uint8_t function, struct EventStruct *event, String& string)
|
||||
{
|
||||
@@ -81,6 +52,8 @@ boolean Plugin_078(uint8_t function, struct EventStruct *event, String& string)
|
||||
Device[deviceCount].SendDataOption = true;
|
||||
Device[deviceCount].TimerOption = true;
|
||||
Device[deviceCount].GlobalSyncOption = true;
|
||||
Device[deviceCount].PluginStats = true;
|
||||
Device[deviceCount].TaskLogsOwnPeaks = true;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -94,8 +67,9 @@ boolean Plugin_078(uint8_t function, struct EventStruct *event, String& string)
|
||||
{
|
||||
for (uint8_t i = 0; i < VARS_PER_TASK; ++i) {
|
||||
if (i < P078_NR_OUTPUT_VALUES) {
|
||||
uint8_t choice = PCONFIG(i + P078_QUERY1_CONFIG_POS);
|
||||
ExtraTaskSettings.setTaskDeviceValueName(i, p078_getQueryValueString(choice));
|
||||
const SDM_MODEL model = static_cast<SDM_MODEL>(P078_MODEL);
|
||||
const uint8_t choice = PCONFIG(i + P078_QUERY1_CONFIG_POS);
|
||||
ExtraTaskSettings.setTaskDeviceValueName(i, SDM_getValueNameForModel(model, choice));
|
||||
} else {
|
||||
ExtraTaskSettings.clearTaskDeviceValueName(i);
|
||||
}
|
||||
@@ -105,8 +79,7 @@ boolean Plugin_078(uint8_t function, struct EventStruct *event, String& string)
|
||||
|
||||
case PLUGIN_GET_DEVICEGPIONAMES:
|
||||
{
|
||||
serialHelper_getGpioNames(event);
|
||||
event->String3 = formatGpioName_output_optional(F("DE"));
|
||||
serialHelper_modbus_getGpioNames(event);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -163,6 +136,13 @@ boolean Plugin_078(uint8_t function, struct EventStruct *event, String& string)
|
||||
|
||||
addFormNumericBox(F("Modbus Address"), P078_DEV_ID_LABEL, P078_DEV_ID, 1, 247);
|
||||
|
||||
#ifdef ESP32
|
||||
addFormCheckBox(F("Enable Collision Detection"), F(P078_FLAG_COLL_DETECT_LABEL), P078_GET_FLAG_COLL_DETECT);
|
||||
addFormNote(F("/RE connected to GND, only supported on hardware serial"));
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
if (Plugin_078_SDM != nullptr) {
|
||||
addRowLabel(F("Checksum (pass/fail)"));
|
||||
String chksumStats;
|
||||
@@ -177,42 +157,57 @@ boolean Plugin_078(uint8_t function, struct EventStruct *event, String& string)
|
||||
|
||||
case PLUGIN_WEBFORM_LOAD_OUTPUT_SELECTOR:
|
||||
{
|
||||
const __FlashStringHelper *options[P078_NR_OUTPUT_OPTIONS];
|
||||
|
||||
for (int i = 0; i < P078_NR_OUTPUT_OPTIONS; ++i) {
|
||||
options[i] = p078_getQueryString(i);
|
||||
}
|
||||
|
||||
// In a separate scope to free memory of String array as soon as possible
|
||||
for (uint8_t i = 0; i < P078_NR_OUTPUT_VALUES; ++i) {
|
||||
const uint8_t pconfigIndex = i + P078_QUERY1_CONFIG_POS;
|
||||
sensorTypeHelper_loadOutputSelector(event, pconfigIndex, i, P078_NR_OUTPUT_OPTIONS, options);
|
||||
SDM_loadOutputSelector(event, pconfigIndex, i);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case PLUGIN_WEBFORM_LOAD:
|
||||
{
|
||||
{
|
||||
const __FlashStringHelper *options_model[4] = { F("SDM120C"), F("SDM220T"), F("SDM230"), F("SDM630") };
|
||||
addFormSelector(F("Model Type"), P078_MODEL_LABEL, 4, options_model, nullptr, P078_MODEL);
|
||||
const __FlashStringHelper *options_model[] = {
|
||||
F("SDM220 & SDM120CT & SDM120"),
|
||||
F("SDM230"),
|
||||
F("SDM72D"),
|
||||
F("DDM18SD"),
|
||||
F("SDM630"),
|
||||
F("SDM72_V2"),
|
||||
F("SDM320C")
|
||||
};
|
||||
constexpr size_t nrOptions = sizeof(options_model) / sizeof(options_model[0]);
|
||||
addFormSelector(F("Model Type"), P078_MODEL_LABEL, nrOptions, options_model, nullptr, P078_MODEL);
|
||||
addFormNote(F("Submit after changing the modell to update Output Configuration."));
|
||||
}
|
||||
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case PLUGIN_WEBFORM_SAVE:
|
||||
{
|
||||
// Save output selector parameters.
|
||||
const SDM_MODEL model = static_cast<SDM_MODEL>(P078_MODEL);
|
||||
|
||||
for (uint8_t i = 0; i < P078_NR_OUTPUT_VALUES; ++i) {
|
||||
const uint8_t pconfigIndex = i + P078_QUERY1_CONFIG_POS;
|
||||
const uint8_t choice = PCONFIG(pconfigIndex);
|
||||
sensorTypeHelper_saveOutputSelector(event, pconfigIndex, i, p078_getQueryValueString(choice));
|
||||
sensorTypeHelper_saveOutputSelector(
|
||||
event,
|
||||
pconfigIndex,
|
||||
i,
|
||||
SDM_getValueNameForModel(model, choice));
|
||||
}
|
||||
|
||||
P078_DEV_ID = getFormItemInt(P078_DEV_ID_LABEL);
|
||||
P078_MODEL = getFormItemInt(P078_MODEL_LABEL);
|
||||
P078_BAUDRATE = getFormItemInt(P078_BAUDRATE_LABEL);
|
||||
#ifdef ESP32
|
||||
P078_SET_FLAG_COLL_DETECT(isFormItemChecked(F(P078_FLAG_COLL_DETECT_LABEL)));
|
||||
#endif
|
||||
|
||||
Plugin_078_init = false; // Force device setup next time
|
||||
success = true;
|
||||
@@ -221,39 +216,82 @@ boolean Plugin_078(uint8_t function, struct EventStruct *event, String& string)
|
||||
|
||||
case PLUGIN_INIT:
|
||||
{
|
||||
if (Plugin_078_SoftSerial != nullptr) {
|
||||
delete Plugin_078_SoftSerial;
|
||||
Plugin_078_SoftSerial = nullptr;
|
||||
}
|
||||
Plugin_078_SoftSerial = new (std::nothrow) ESPeasySerial(static_cast<ESPEasySerialPort>(CONFIG_PORT), CONFIG_PIN1, CONFIG_PIN2);
|
||||
Plugin_078_init = true;
|
||||
|
||||
if (Plugin_078_SoftSerial == nullptr) {
|
||||
if (Plugin_078_ESPEasySerial != nullptr) {
|
||||
delete Plugin_078_ESPEasySerial;
|
||||
Plugin_078_ESPEasySerial = nullptr;
|
||||
}
|
||||
Plugin_078_ESPEasySerial = new (std::nothrow) ESPeasySerial(static_cast<ESPEasySerialPort>(CONFIG_PORT), CONFIG_PIN1, CONFIG_PIN2);
|
||||
|
||||
if (Plugin_078_ESPEasySerial == nullptr) {
|
||||
break;
|
||||
}
|
||||
unsigned int baudrate = p078_storageValueToBaudrate(P078_BAUDRATE);
|
||||
Plugin_078_SoftSerial->begin(baudrate);
|
||||
Plugin_078_ESPEasySerial->begin(baudrate);
|
||||
|
||||
if (Plugin_078_SDM != nullptr) {
|
||||
delete Plugin_078_SDM;
|
||||
Plugin_078_SDM = nullptr;
|
||||
}
|
||||
Plugin_078_SDM = new (std::nothrow) SDM(*Plugin_078_SoftSerial, baudrate, P078_DEPIN);
|
||||
|
||||
if (Plugin_078_ESPEasySerial->setRS485Mode(P078_DEPIN, P078_GET_FLAG_COLL_DETECT)) {
|
||||
Plugin_078_SDM = new SDM(*Plugin_078_ESPEasySerial, baudrate);
|
||||
} else {
|
||||
Plugin_078_SDM = new SDM(*Plugin_078_ESPEasySerial, baudrate, P078_DEPIN);
|
||||
}
|
||||
|
||||
if (Plugin_078_SDM != nullptr) {
|
||||
success = true;
|
||||
Plugin_078_SDM->begin();
|
||||
Plugin_078_init = true;
|
||||
success = true;
|
||||
|
||||
// Set timeout to time needed to receive 1 byte
|
||||
Plugin_078_SDM->setMsTimeout((10000 / baudrate) + 1);
|
||||
|
||||
SDM_MODEL model = static_cast<SDM_MODEL>(P078_MODEL);
|
||||
uint8_t dev_id = P078_DEV_ID;
|
||||
|
||||
/*
|
||||
if (loglevelActiveFor(LOG_LEVEL_INFO)) {
|
||||
String log;
|
||||
log = F("Eastron: SN: ");
|
||||
log += Plugin_078_SDM->getSerialNumber(dev_id);
|
||||
log += ',';
|
||||
log += Plugin_078_SDM->getErrCode(true);
|
||||
log += F(" SW-ver: ");
|
||||
log += Plugin_078_SDM->readHoldingRegister(SDM_HOLDING_SOFTWARE_VERSION, dev_id);
|
||||
log += ',';
|
||||
log += Plugin_078_SDM->getErrCode(true);
|
||||
log += F(" ID: ");
|
||||
log += Plugin_078_SDM->readHoldingRegister(SDM_HOLDING_METER_ID, dev_id);
|
||||
log += ',';
|
||||
log += Plugin_078_SDM->getErrCode(true);
|
||||
log += F(" baudrate: ");
|
||||
log += Plugin_078_SDM->readHoldingRegister(SDM_HOLDING_BAUD_RATE, dev_id);
|
||||
log += ',';
|
||||
log += Plugin_078_SDM->getErrCode(true);
|
||||
addLogMove(LOG_LEVEL_INFO, log);
|
||||
}
|
||||
*/
|
||||
for (taskVarIndex_t i = 0; i < VARS_PER_TASK; ++i) {
|
||||
const uint16_t reg = SDM_getRegisterForModel(model, PCONFIG((P078_QUERY1_CONFIG_POS) + i));
|
||||
SDM_addRegisterReadQueueElement(event->TaskIndex, i, reg, dev_id);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case PLUGIN_EXIT:
|
||||
{
|
||||
for (taskVarIndex_t i = 0; i < VARS_PER_TASK; ++i) {
|
||||
SDM_removeRegisterReadQueueElement(event->TaskIndex, i);
|
||||
}
|
||||
|
||||
Plugin_078_init = false;
|
||||
|
||||
if (Plugin_078_SoftSerial != nullptr) {
|
||||
delete Plugin_078_SoftSerial;
|
||||
Plugin_078_SoftSerial = nullptr;
|
||||
if (Plugin_078_ESPEasySerial != nullptr) {
|
||||
delete Plugin_078_ESPEasySerial;
|
||||
Plugin_078_ESPEasySerial = nullptr;
|
||||
}
|
||||
|
||||
if (Plugin_078_SDM != nullptr) {
|
||||
@@ -263,157 +301,106 @@ boolean Plugin_078(uint8_t function, struct EventStruct *event, String& string)
|
||||
break;
|
||||
}
|
||||
|
||||
case PLUGIN_TEN_PER_SECOND:
|
||||
{
|
||||
if (Plugin_078_init)
|
||||
{
|
||||
SDM_loopRegisterReadQueue(Plugin_078_SDM);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case PLUGIN_READ:
|
||||
{
|
||||
if (Plugin_078_init)
|
||||
{
|
||||
int model = P078_MODEL;
|
||||
uint8_t dev_id = P078_DEV_ID;
|
||||
UserVar[event->BaseVarIndex] = p078_readVal(P078_QUERY1, dev_id, model);
|
||||
UserVar[event->BaseVarIndex + 1] = p078_readVal(P078_QUERY2, dev_id, model);
|
||||
UserVar[event->BaseVarIndex + 2] = p078_readVal(P078_QUERY3, dev_id, model);
|
||||
UserVar[event->BaseVarIndex + 3] = p078_readVal(P078_QUERY4, dev_id, model);
|
||||
success = true;
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case PLUGIN_WRITE:
|
||||
{
|
||||
if (Plugin_078_init && (Plugin_078_SDM != nullptr)) {
|
||||
const String cmd = parseString(string, 1);
|
||||
|
||||
if (equals(cmd, F("eastron"))) {
|
||||
const String subcmd = parseString(string, 2);
|
||||
|
||||
if (equals(subcmd, F("pause"))) {
|
||||
SDM_pause_loopRegisterReadQueue();
|
||||
success = true;
|
||||
} else if (equals(subcmd, F("resume"))) {
|
||||
SDM_resume_loopRegisterReadQueue();
|
||||
success = true;
|
||||
} else {
|
||||
uint8_t node_id = event->Par3;
|
||||
|
||||
if ((node_id < 1) || (node_id > 247)) { node_id = 1; }
|
||||
|
||||
if (equals(subcmd, F("setid"))) {
|
||||
// Example command: eastron,setid,<new_id>[,<node_id>]
|
||||
|
||||
const uint8_t new_id = event->Par2;
|
||||
|
||||
if ((new_id >= 1) && (new_id <= 247) && (new_id != node_id)) {
|
||||
success = Plugin_078_SDM->writeHoldingRegister(new_id, SDM_HOLDING_METER_ID, node_id);
|
||||
}
|
||||
} else if (equals(subcmd, F("setbaud"))) {
|
||||
// Example command: eastron,setbaud,<new_baudrate>[,<id>]
|
||||
|
||||
/*
|
||||
SDM120 / SDM230:
|
||||
0 = 2400 baud (default)
|
||||
1 = 4800 baud
|
||||
2 = 9600 baud
|
||||
5 = 1200 baud
|
||||
|
||||
SDM320 / SDM530Y:
|
||||
0 = 2400 baud
|
||||
1 = 4800 baud
|
||||
2 = 9600 baud (default)
|
||||
5 = 1200 band
|
||||
|
||||
SDM630 / SDM72 / SDM72V2:
|
||||
0 = 2400 baud
|
||||
1 = 4800 baud
|
||||
2 = 9600 baud (default)
|
||||
3 = 19200 baud
|
||||
4 = 38400 baud
|
||||
*/
|
||||
int new_baud = event->Par2;
|
||||
|
||||
if (new_baud > 5) {
|
||||
const int baudrates[] = { 2400, 4800, 9600, 19200, 38400, 1200 };
|
||||
constexpr int nrBaudRates = sizeof(baudrates) / sizeof(baudrates[0]);
|
||||
|
||||
for (int i = 0; i < nrBaudRates && new_baud > 5; ++i) {
|
||||
if (new_baud == baudrates[i]) {
|
||||
new_baud = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((new_baud >= 0) && (new_baud <= 5)) {
|
||||
success = Plugin_078_SDM->writeHoldingRegister(new_baud, SDM_HOLDING_BAUD_RATE, node_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
float p078_readVal(uint8_t query, uint8_t node, unsigned int model) {
|
||||
if (Plugin_078_SDM == nullptr) { return 0.0f; }
|
||||
|
||||
uint8_t retry_count = 3;
|
||||
bool success = false;
|
||||
float _tempvar = NAN;
|
||||
|
||||
while (retry_count > 0 && !success) {
|
||||
Plugin_078_SDM->clearErrCode();
|
||||
_tempvar = Plugin_078_SDM->readVal(p078_getRegister(query, model), node);
|
||||
--retry_count;
|
||||
|
||||
if (Plugin_078_SDM->getErrCode() == SDM_ERR_NO_ERROR) {
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (loglevelActiveFor(LOG_LEVEL_INFO)) {
|
||||
String log = F("EASTRON: (");
|
||||
log += node;
|
||||
log += ',';
|
||||
log += model;
|
||||
log += F(") ");
|
||||
log += p078_getQueryString(query);
|
||||
log += F(": ");
|
||||
log += _tempvar;
|
||||
addLogMove(LOG_LEVEL_INFO, log);
|
||||
}
|
||||
return _tempvar;
|
||||
}
|
||||
|
||||
unsigned int p078_getRegister(uint8_t query, uint8_t model) {
|
||||
if (model == 0) { // SDM120C
|
||||
switch (query) {
|
||||
case 0: return SDM120C_VOLTAGE;
|
||||
case 1: return SDM120C_CURRENT;
|
||||
case 2: return SDM120C_POWER;
|
||||
case 3: return SDM120C_ACTIVE_APPARENT_POWER;
|
||||
case 4: return SDM120C_REACTIVE_APPARENT_POWER;
|
||||
case 5: return SDM120C_POWER_FACTOR;
|
||||
case 6: return SDM120C_FREQUENCY;
|
||||
case 7: return SDM120C_IMPORT_ACTIVE_ENERGY;
|
||||
case 8: return SDM120C_EXPORT_ACTIVE_ENERGY;
|
||||
case 9: return SDM120C_TOTAL_ACTIVE_ENERGY;
|
||||
}
|
||||
} else if (model == 1) { // SDM220T
|
||||
switch (query) {
|
||||
case 0: return SDM220T_VOLTAGE;
|
||||
case 1: return SDM220T_CURRENT;
|
||||
case 2: return SDM220T_POWER;
|
||||
case 3: return SDM220T_ACTIVE_APPARENT_POWER;
|
||||
case 4: return SDM220T_REACTIVE_APPARENT_POWER;
|
||||
case 5: return SDM220T_POWER_FACTOR;
|
||||
case 6: return SDM220T_FREQUENCY;
|
||||
case 7: return SDM220T_IMPORT_ACTIVE_ENERGY;
|
||||
case 8: return SDM220T_EXPORT_ACTIVE_ENERGY;
|
||||
case 9: return SDM220T_TOTAL_ACTIVE_ENERGY;
|
||||
}
|
||||
} else if (model == 2) { // SDM230
|
||||
switch (query) {
|
||||
case 0: return SDM230_VOLTAGE;
|
||||
case 1: return SDM230_CURRENT;
|
||||
case 2: return SDM230_POWER;
|
||||
case 3: return SDM230_ACTIVE_APPARENT_POWER;
|
||||
case 4: return SDM230_REACTIVE_APPARENT_POWER;
|
||||
case 5: return SDM230_POWER_FACTOR;
|
||||
case 6: return SDM230_FREQUENCY;
|
||||
case 7: return SDM230_IMPORT_ACTIVE_ENERGY;
|
||||
case 8: return SDM230_EXPORT_ACTIVE_ENERGY;
|
||||
case 9: return SDM230_CURRENT_RESETTABLE_TOTAL_ACTIVE_ENERGY;
|
||||
}
|
||||
} else if (model == 3) { // SDM630
|
||||
switch (query) {
|
||||
case 0: return SDM630_VOLTAGE_AVERAGE;
|
||||
case 1: return SDM630_CURRENTSUM;
|
||||
case 2: return SDM630_POWERTOTAL;
|
||||
case 3: return SDM630_VOLT_AMPS_TOTAL;
|
||||
case 4: return SDM630_VOLT_AMPS_REACTIVE_TOTAL;
|
||||
case 5: return SDM630_POWER_FACTOR_TOTAL;
|
||||
case 6: return SDM630_FREQUENCY;
|
||||
case 7: return SDM630_IMPORT_ACTIVE_ENERGY;
|
||||
case 8: return SDM630_EXPORT_ACTIVE_ENERGY;
|
||||
case 9: return SDM630_IMPORT_ACTIVE_ENERGY; // No equivalent for TOTAL_ACTIVE_ENERGY present in the SDM630
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const __FlashStringHelper* p078_getQueryString(uint8_t query) {
|
||||
switch (query)
|
||||
{
|
||||
case 0: return F("Voltage (V)");
|
||||
case 1: return F("Current (A)");
|
||||
case 2: return F("Power (W)");
|
||||
case 3: return F("Active Apparent Power (VA)");
|
||||
case 4: return F("Reactive Apparent Power (VAr)");
|
||||
case 5: return F("Power Factor (cos-phi)");
|
||||
case 6: return F("Frequency (Hz)");
|
||||
case 7: return F("Import Active Energy (Wh)");
|
||||
case 8: return F("Export Active Energy (Wh)");
|
||||
case 9: return F("Total Active Energy (Wh)");
|
||||
}
|
||||
return F("");
|
||||
}
|
||||
|
||||
const __FlashStringHelper* p078_getQueryValueString(uint8_t query) {
|
||||
switch (query)
|
||||
{
|
||||
case 0: return F("V");
|
||||
case 1: return F("A");
|
||||
case 2: return F("W");
|
||||
case 3: return F("VA");
|
||||
case 4: return F("VAr");
|
||||
case 5: return F("cos_phi");
|
||||
case 6: return F("Hz");
|
||||
case 7: return F("Wh_imp");
|
||||
case 8: return F("Wh_exp");
|
||||
case 9: return F("Wh_tot");
|
||||
}
|
||||
return F("");
|
||||
}
|
||||
|
||||
int p078_storageValueToBaudrate(uint8_t baudrate_setting) {
|
||||
unsigned int baudrate = 9600;
|
||||
int baudrate = 9600;
|
||||
|
||||
switch (baudrate_setting) {
|
||||
case 0: baudrate = 1200; break;
|
||||
case 1: baudrate = 2400; break;
|
||||
case 2: baudrate = 4800; break;
|
||||
case 3: baudrate = 9600; break;
|
||||
case 4: baudrate = 19200; break;
|
||||
case 5: baudrate = 38400; break;
|
||||
if (baudrate_setting < 6) {
|
||||
baudrate = 1200 << baudrate_setting;
|
||||
}
|
||||
return baudrate;
|
||||
}
|
||||
|
||||
@@ -80,6 +80,20 @@ void serialHelper_getGpioNames(struct EventStruct *event, bool rxOptional, bool
|
||||
event->String2 = formatGpioName_serialTX(txOptional);
|
||||
}
|
||||
|
||||
void serialHelper_modbus_getGpioNames(struct EventStruct *event,
|
||||
bool rxOptional,
|
||||
bool txOptional,
|
||||
bool DE_RE_optional)
|
||||
{
|
||||
serialHelper_getGpioNames(event, rxOptional, txOptional);
|
||||
event->String1.replace(F("TX"), F("TX (RO)"));
|
||||
event->String2.replace(F("RX"), F("RX (DI)"));
|
||||
if (DE_RE_optional)
|
||||
event->String3 = formatGpioName_output_optional(F("~RE/DE"));
|
||||
else
|
||||
event->String3 = formatGpioName_output(F("~RE/DE"));
|
||||
}
|
||||
|
||||
int8_t serialHelper_getRxPin(struct EventStruct *event) {
|
||||
return CONFIG_PIN1;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,11 @@ void serialHelper_getGpioNames(struct EventStruct *event,
|
||||
bool rxOptional = false,
|
||||
bool txOptional = false);
|
||||
|
||||
void serialHelper_modbus_getGpioNames(struct EventStruct *event,
|
||||
bool rxOptional = false,
|
||||
bool txOptional = false,
|
||||
bool DE_RE_optional = true);
|
||||
|
||||
int8_t serialHelper_getRxPin(struct EventStruct *event);
|
||||
|
||||
int8_t serialHelper_getTxPin(struct EventStruct *event);
|
||||
|
||||
@@ -0,0 +1,513 @@
|
||||
#include "../PluginStructs/P078_data_struct.h"
|
||||
|
||||
#include "../../_Plugin_Helper.h"
|
||||
|
||||
#ifdef USES_P078
|
||||
|
||||
# include <limits>
|
||||
|
||||
# include <SDM.h> // Requires SDM library from Reaper7 - https://github.com/reaper7/SDM_Energy_Meter/
|
||||
|
||||
// Uncrustify may mess up this nice table, so turn uncrustify off for this table.
|
||||
// *INDENT-OFF*
|
||||
constexpr p078_register_description register_description_list[] = {
|
||||
//----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
// REGISTERS LIST FOR SDM DEVICES |
|
||||
//----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
// REGISTER NAME REGISTER ADDRESS UNIT | phase | dir | SDM630 | SDM320C | SDM230 | SDM220 | SDM120CT| SDM120 | SDM72D | SDM72 V2 | DDM18SD |
|
||||
//----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
{ SDM_PHASE_1_VOLTAGE /* 0x0000 */ , SDM_UOM::V , 1 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 0 , 1 , 0 },
|
||||
{ SDM_PHASE_2_VOLTAGE /* 0x0002 */ , SDM_UOM::V , 2 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 },
|
||||
{ SDM_PHASE_3_VOLTAGE /* 0x0004 */ , SDM_UOM::V , 3 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 },
|
||||
{ SDM_PHASE_1_CURRENT /* 0x0006 */ , SDM_UOM::A , 1 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 0 , 1 , 0 },
|
||||
{ SDM_PHASE_2_CURRENT /* 0x0008 */ , SDM_UOM::A , 2 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 },
|
||||
{ SDM_PHASE_3_CURRENT /* 0x000A */ , SDM_UOM::A , 3 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 },
|
||||
{ SDM_PHASE_1_POWER /* 0x000C */ , SDM_UOM::W , 1 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 0 , 1 , 0 },
|
||||
{ SDM_PHASE_2_POWER /* 0x000E */ , SDM_UOM::W , 2 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 },
|
||||
{ SDM_PHASE_3_POWER /* 0x0010 */ , SDM_UOM::W , 3 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 },
|
||||
{ SDM_PHASE_1_APPARENT_POWER /* 0x0012 */ , SDM_UOM::VA , 1 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 0 , 1 , 0 },
|
||||
{ SDM_PHASE_2_APPARENT_POWER /* 0x0014 */ , SDM_UOM::VA , 2 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 },
|
||||
{ SDM_PHASE_3_APPARENT_POWER /* 0x0016 */ , SDM_UOM::VA , 3 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 },
|
||||
{ SDM_PHASE_1_REACTIVE_POWER /* 0x0018 */ , SDM_UOM::VAr , 1 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 0 , 1 , 0 },
|
||||
{ SDM_PHASE_2_REACTIVE_POWER /* 0x001A */ , SDM_UOM::VAr , 2 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 },
|
||||
{ SDM_PHASE_3_REACTIVE_POWER /* 0x001C */ , SDM_UOM::VAr , 3 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 },
|
||||
{ SDM_PHASE_1_POWER_FACTOR /* 0x001E */ , SDM_UOM::cos_phi , 1 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 0 , 1 , 0 },
|
||||
{ SDM_PHASE_2_POWER_FACTOR /* 0x0020 */ , SDM_UOM::cos_phi , 2 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 },
|
||||
{ SDM_PHASE_3_POWER_FACTOR /* 0x0022 */ , SDM_UOM::cos_phi , 3 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 },
|
||||
{ SDM_PHASE_1_ANGLE /* 0x0024 */ , SDM_UOM::degrees , 1 , 0 , 1 , 1 , 1 , 1 , 1 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_PHASE_2_ANGLE /* 0x0026 */ , SDM_UOM::degrees , 2 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_PHASE_3_ANGLE /* 0x0028 */ , SDM_UOM::degrees , 3 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_AVERAGE_L_TO_N_VOLTS /* 0x002A */ , SDM_UOM::V , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 },
|
||||
{ SDM_AVERAGE_LINE_CURRENT /* 0x002E */ , SDM_UOM::A , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 },
|
||||
{ SDM_SUM_LINE_CURRENT /* 0x0030 */ , SDM_UOM::A , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 },
|
||||
{ SDM_TOTAL_SYSTEM_POWER /* 0x0034 */ , SDM_UOM::W , 0 , 3 , 1 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 0 },
|
||||
{ SDM_TOTAL_SYSTEM_APPARENT_POWER /* 0x0038 */ , SDM_UOM::VA , 0 , 3 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 },
|
||||
{ SDM_TOTAL_SYSTEM_REACTIVE_POWER /* 0x003C */ , SDM_UOM::VAr , 0 , 3 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 },
|
||||
{ SDM_TOTAL_SYSTEM_POWER_FACTOR /* 0x003E */ , SDM_UOM::cos_phi , 0 , 3 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 },
|
||||
{ SDM_TOTAL_SYSTEM_PHASE_ANGLE /* 0x0042 */ , SDM_UOM::degrees , 0 , 3 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_FREQUENCY /* 0x0046 */ , SDM_UOM::Hz , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 0 , 1 , 0 },
|
||||
{ SDM_IMPORT_ACTIVE_ENERGY /* 0x0048 */ , SDM_UOM::kWh , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 0 },
|
||||
{ SDM_EXPORT_ACTIVE_ENERGY /* 0x004A */ , SDM_UOM::kWh , 0 , 2 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 0 },
|
||||
{ SDM_IMPORT_REACTIVE_ENERGY /* 0x004C */ , SDM_UOM::kVArh , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 0 , 0 , 0 },
|
||||
{ SDM_EXPORT_REACTIVE_ENERGY /* 0x004E */ , SDM_UOM::kVArh , 0 , 2 , 1 , 1 , 1 , 1 , 1 , 1 , 0 , 0 , 0 },
|
||||
{ SDM_VAH_SINCE_LAST_RESET /* 0x0050 */ , SDM_UOM::kVAh , 0 , 0 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_AH_SINCE_LAST_RESET /* 0x0052 */ , SDM_UOM::Ah , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_TOTAL_SYSTEM_POWER_DEMAND /* 0x0054 */ , SDM_UOM::W , 0 , 3 , 1 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_MAXIMUM_TOTAL_SYSTEM_POWER_DEMAND /* 0x0056 */ , SDM_UOM::W , 0 , 0 , 1 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_CURRENT_SYSTEM_POSITIVE_POWER_DEMAND /* 0x0058 */ , SDM_UOM::W , 0 , 1 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_MAXIMUM_SYSTEM_POSITIVE_POWER_DEMAND /* 0x005A */ , SDM_UOM::W , 0 , 1 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_CURRENT_SYSTEM_REVERSE_POWER_DEMAND /* 0x005C */ , SDM_UOM::W , 0 , 2 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_MAXIMUM_SYSTEM_REVERSE_POWER_DEMAND /* 0x005E */ , SDM_UOM::W , 0 , 2 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_TOTAL_SYSTEM_VA_DEMAND /* 0x0064 */ , SDM_UOM::VA , 0 , 3 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_MAXIMUM_TOTAL_SYSTEM_VA_DEMAND /* 0x0066 */ , SDM_UOM::VA , 0 , 0 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_NEUTRAL_CURRENT_DEMAND /* 0x0068 */ , SDM_UOM::A , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_MAXIMUM_NEUTRAL_CURRENT /* 0x006A */ , SDM_UOM::A , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_REACTIVE_POWER_DEMAND /* 0x006C */ , SDM_UOM::VAr , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_MAXIMUM_REACTIVE_POWER_DEMAND /* 0x006E */ , SDM_UOM::VAr , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_LINE_1_TO_LINE_2_VOLTS /* 0x00C8 */ , SDM_UOM::V , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 },
|
||||
{ SDM_LINE_2_TO_LINE_3_VOLTS /* 0x00CA */ , SDM_UOM::V , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 },
|
||||
{ SDM_LINE_3_TO_LINE_1_VOLTS /* 0x00CC */ , SDM_UOM::V , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 },
|
||||
{ SDM_AVERAGE_LINE_TO_LINE_VOLTS /* 0x00CE */ , SDM_UOM::V , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 },
|
||||
{ SDM_NEUTRAL_CURRENT /* 0x00E0 */ , SDM_UOM::A , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 },
|
||||
{ SDM_PHASE_1_LN_VOLTS_THD /* 0x00EA */ , SDM_UOM::percent , 1 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_PHASE_2_LN_VOLTS_THD /* 0x00EC */ , SDM_UOM::percent , 2 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_PHASE_3_LN_VOLTS_THD /* 0x00EE */ , SDM_UOM::percent , 3 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_PHASE_1_CURRENT_THD /* 0x00F0 */ , SDM_UOM::percent , 1 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_PHASE_2_CURRENT_THD /* 0x00F2 */ , SDM_UOM::percent , 2 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_PHASE_3_CURRENT_THD /* 0x00F4 */ , SDM_UOM::percent , 3 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_AVERAGE_LINE_TO_NEUTRAL_VOLTS_THD /* 0x00F8 */ , SDM_UOM::percent , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_AVERAGE_LINE_CURRENT_THD /* 0x00FA */ , SDM_UOM::percent , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_TOTAL_SYSTEM_POWER_FACTOR_INV /* 0x00FE */ , SDM_UOM::cos_phi , 0 , 3 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_PHASE_1_CURRENT_DEMAND /* 0x0102 */ , SDM_UOM::A , 1 , 0 , 1 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_PHASE_2_CURRENT_DEMAND /* 0x0104 */ , SDM_UOM::A , 2 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_PHASE_3_CURRENT_DEMAND /* 0x0106 */ , SDM_UOM::A , 3 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_MAXIMUM_PHASE_1_CURRENT_DEMAND /* 0x0108 */ , SDM_UOM::A , 1 , 0 , 1 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_MAXIMUM_PHASE_2_CURRENT_DEMAND /* 0x010A */ , SDM_UOM::A , 2 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_MAXIMUM_PHASE_3_CURRENT_DEMAND /* 0x010C */ , SDM_UOM::A , 3 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_LINE_1_TO_LINE_2_VOLTS_THD /* 0x014E */ , SDM_UOM::percent , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_LINE_2_TO_LINE_3_VOLTS_THD /* 0x0150 */ , SDM_UOM::percent , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_LINE_3_TO_LINE_1_VOLTS_THD /* 0x0152 */ , SDM_UOM::percent , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_AVERAGE_LINE_TO_LINE_VOLTS_THD /* 0x0154 */ , SDM_UOM::percent , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_TOTAL_ACTIVE_ENERGY /* 0x0156 */ , SDM_UOM::kWh , 0 , 3 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 0 },
|
||||
{ SDM_TOTAL_REACTIVE_ENERGY /* 0x0158 */ , SDM_UOM::kVArh , 0 , 3 , 1 , 1 , 1 , 1 , 1 , 1 , 0 , 1 , 0 },
|
||||
{ SDM_L1_IMPORT_ACTIVE_ENERGY /* 0x015A */ , SDM_UOM::kWh , 1 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_L2_IMPORT_ACTIVE_ENERGY /* 0x015C */ , SDM_UOM::kWh , 2 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_L3_IMPORT_ACTIVE_ENERGY /* 0x015E */ , SDM_UOM::kWh , 3 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_L1_EXPORT_ACTIVE_ENERGY /* 0x0160 */ , SDM_UOM::kWh , 1 , 2 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_L2_EXPORT_ACTIVE_ENERGY /* 0x0162 */ , SDM_UOM::kWh , 2 , 2 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_L3_EXPORT_ACTIVE_ENERGY /* 0x0164 */ , SDM_UOM::kWh , 3 , 2 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_L1_TOTAL_ACTIVE_ENERGY /* 0x0166 */ , SDM_UOM::kWh , 1 , 3 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_L2_TOTAL_ACTIVE_ENERGY /* 0x0168 */ , SDM_UOM::kWh , 2 , 3 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_L3_TOTAL_ACTIVE_ENERGY /* 0x016a */ , SDM_UOM::kWh , 3 , 3 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_L1_IMPORT_REACTIVE_ENERGY /* 0x016C */ , SDM_UOM::kVArh , 1 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_L2_IMPORT_REACTIVE_ENERGY /* 0x016E */ , SDM_UOM::kVArh , 2 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_L3_IMPORT_REACTIVE_ENERGY /* 0x0170 */ , SDM_UOM::kVArh , 3 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_L1_EXPORT_REACTIVE_ENERGY /* 0x0172 */ , SDM_UOM::kVArh , 1 , 2 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_L2_EXPORT_REACTIVE_ENERGY /* 0x0174 */ , SDM_UOM::kVArh , 2 , 2 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_L3_EXPORT_REACTIVE_ENERGY /* 0x0176 */ , SDM_UOM::kVArh , 3 , 2 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_L1_TOTAL_REACTIVE_ENERGY /* 0x0178 */ , SDM_UOM::kVArh , 1 , 3 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_L2_TOTAL_REACTIVE_ENERGY /* 0x017A */ , SDM_UOM::kVArh , 2 , 3 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_L3_TOTAL_REACTIVE_ENERGY /* 0x017C */ , SDM_UOM::kVArh , 3 , 3 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_CURRENT_RESETTABLE_TOTAL_ACTIVE_ENERGY /* 0x0180 */ , SDM_UOM::kWh , 0 , 3 , 0 , 0 , 1 , 0 , 0 , 0 , 1 , 1 , 0 },
|
||||
{ SDM_CURRENT_RESETTABLE_TOTAL_REACTIVE_ENERGY /* 0x0182 */ , SDM_UOM::kVArh , 0 , 3 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 },
|
||||
{ SDM_CURRENT_RESETTABLE_IMPORT_ENERGY /* 0x0184 */ , SDM_UOM::kWh , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 0 },
|
||||
{ SDM_CURRENT_RESETTABLE_EXPORT_ENERGY /* 0x0186 */ , SDM_UOM::kWh , 0 , 2 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 0 },
|
||||
{ SDM_CURRENT_RESETTABLE_IMPORT_REACTIVE_ENERGY /* 0x0188 */ , SDM_UOM::kVArh , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 0 },
|
||||
{ SDM_CURRENT_RESETTABLE_EXPORT_REACTIVE_ENERGY /* 0x018A */ , SDM_UOM::kVArh , 0 , 2 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 0 },
|
||||
{ SDM_NET_KWH /* 0x018C */ , SDM_UOM::kWh , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 },
|
||||
{ SDM_NET_KVARH /* 0x018E */ , SDM_UOM::kVArh , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 },
|
||||
{ SDM_IMPORT_POWER /* 0x0500 */ , SDM_UOM::W , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 0 },
|
||||
{ SDM_EXPORT_POWER /* 0x0502 */ , SDM_UOM::W , 0 , 2 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 0 },
|
||||
|
||||
{ DDM_PHASE_1_VOLTAGE /* 0x0000 */ , SDM_UOM::V , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 },
|
||||
{ DDM_PHASE_1_CURRENT /* 0x0008 */ , SDM_UOM::A , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 },
|
||||
{ DDM_PHASE_1_POWER /* 0x0012 */ , SDM_UOM::W , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 },
|
||||
{ DDM_PHASE_1_REACTIVE_POWER /* 0x001A */ , SDM_UOM::VAr , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 },
|
||||
{ DDM_PHASE_1_POWER_FACTOR /* 0x002A */ , SDM_UOM::cos_phi , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 },
|
||||
{ DDM_FREQUENCY /* 0x0036 */ , SDM_UOM::Hz , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 },
|
||||
{ DDM_IMPORT_ACTIVE_ENERGY /* 0x0100 */ , SDM_UOM::kWh , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 },
|
||||
{ DDM_IMPORT_REACTIVE_ENERGY /* 0x0400 */ , SDM_UOM::kVArh , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 }
|
||||
};
|
||||
// *INDENT-ON*
|
||||
|
||||
constexpr int register_description_list_size = sizeof(register_description_list) / sizeof(register_description_list[0]);
|
||||
|
||||
const __FlashStringHelper* SDM_UOMtoString(SDM_UOM uom, bool display) {
|
||||
const __FlashStringHelper *strings[] = {
|
||||
F("THD"), F("%"),
|
||||
F("Voltage"), F("V"),
|
||||
F("Current"), F("A"),
|
||||
F("Active Power"), F("W"),
|
||||
F("Active Energy"), F("kWh"),
|
||||
F("Ah"), F("Ah"),
|
||||
F("Frequency"), F("Hz"),
|
||||
F("Phase Angle"), F("Degrees"),
|
||||
F("Power Factor"), F("cosphi"),
|
||||
F("Apparent Power"), F("VA"),
|
||||
F("Reactive Power"), F("VAr"),
|
||||
F("Apparent Energy"), F("kVAh"),
|
||||
F("Reactive Energy"), F("kVArh")
|
||||
};
|
||||
constexpr size_t nrStrings = sizeof(strings) / sizeof(strings[0]);
|
||||
size_t index = 2 * static_cast<size_t>(uom);
|
||||
|
||||
if (!display) { ++index; }
|
||||
|
||||
if (index < nrStrings) { return strings[index]; }
|
||||
return F("");
|
||||
}
|
||||
|
||||
const __FlashStringHelper* SDM_directionToString(SDM_DIRECTION dir) {
|
||||
if (dir == SDM_DIRECTION::Import) { return F("Import"); }
|
||||
|
||||
if (dir == SDM_DIRECTION::Export) { return F("Export"); }
|
||||
|
||||
if (dir == SDM_DIRECTION::Total) { return F("Total"); }
|
||||
return F("");
|
||||
}
|
||||
|
||||
int SDM_getRegisterDescriptionIndexForModel(SDM_MODEL model, int x)
|
||||
{
|
||||
int count = -1;
|
||||
|
||||
for (int index = 0; index < register_description_list_size; ++index)
|
||||
{
|
||||
if (register_description_list[index].match_SDM_model(model)) {
|
||||
++count;
|
||||
|
||||
if (x == count) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint16_t SDM_getRegisterForModel(SDM_MODEL model, int choice)
|
||||
{
|
||||
const int index = SDM_getRegisterDescriptionIndexForModel(model, choice);
|
||||
|
||||
if (index >= 0) {
|
||||
return register_description_list[index].getRegister();
|
||||
}
|
||||
return std::numeric_limits<uint16_t>::max();
|
||||
}
|
||||
|
||||
String SDM_getValueNameForModel(SDM_MODEL model, int choice)
|
||||
{
|
||||
const int index = SDM_getRegisterDescriptionIndexForModel(model, choice);
|
||||
|
||||
if (index >= 0) {
|
||||
const SDM_UOM uom = register_description_list[index].getUnitOfMeasure();
|
||||
|
||||
return concat(
|
||||
SDM_UOMtoString(uom, false),
|
||||
register_description_list[index].getPhaseDescription(model, '_'));
|
||||
}
|
||||
return EMPTY_STRING;
|
||||
}
|
||||
|
||||
void SDM_loadOutputSelector(struct EventStruct *event, uint8_t pconfigIndex, uint8_t valuenr)
|
||||
{
|
||||
const SDM_MODEL model = static_cast<SDM_MODEL>(P078_MODEL);
|
||||
const String label = concat(F("Value "), valuenr + 1);
|
||||
const String id = PCONFIG_LABEL(pconfigIndex);
|
||||
|
||||
addRowLabel_tr_id(label, id);
|
||||
do_addSelector_Head(id, F("wide"), EMPTY_STRING, false);
|
||||
const int selectedIndex = PCONFIG(pconfigIndex);
|
||||
|
||||
uint8_t x{};
|
||||
|
||||
for (int index = 0; index < register_description_list_size; ++index)
|
||||
{
|
||||
if (register_description_list[index].match_SDM_model(model)) {
|
||||
const String option = register_description_list[index].getDescription(model);
|
||||
addSelector_Item(option, x, selectedIndex == x, false, EMPTY_STRING);
|
||||
++x;
|
||||
}
|
||||
|
||||
if (x % 10 == 0) { delay(0); }
|
||||
}
|
||||
addSelector_Foot();
|
||||
}
|
||||
|
||||
uint16_t p078_register_description::getRegister() const
|
||||
{
|
||||
return static_cast<uint16_t>((val >> 16) & 0xFFFF);
|
||||
}
|
||||
|
||||
SDM_UOM p078_register_description::getUnitOfMeasure() const
|
||||
{
|
||||
return static_cast<SDM_UOM>(val & 0xF);
|
||||
}
|
||||
|
||||
uint8_t p078_register_description::getPhase() const
|
||||
{
|
||||
return static_cast<uint8_t>((val >> 4) & 0x3);
|
||||
}
|
||||
|
||||
SDM_DIRECTION p078_register_description::getDirection() const
|
||||
{
|
||||
return static_cast<SDM_DIRECTION>((val >> 6) & 0x3);
|
||||
}
|
||||
|
||||
bool p078_register_description::match_SDM_model(SDM_MODEL model) const
|
||||
{
|
||||
switch (model) {
|
||||
case SDM_MODEL::SDM630: return bitRead(val, 8);
|
||||
case SDM_MODEL::SDM320C: return bitRead(val, 9);
|
||||
case SDM_MODEL::SDM230: return bitRead(val, 10);
|
||||
case SDM_MODEL::SDM220_SDM120CT_SDM120: return bitRead(val, 11);
|
||||
case SDM_MODEL::SDM72D: return bitRead(val, 12);
|
||||
case SDM_MODEL::SDM72_V2: return bitRead(val, 13);
|
||||
case SDM_MODEL::DDM18SD: return bitRead(val, 14);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
String p078_register_description::getDescription(SDM_MODEL model) const
|
||||
{
|
||||
String res;
|
||||
const SDM_DIRECTION direction = getDirection();
|
||||
const SDM_UOM uom = getUnitOfMeasure();
|
||||
bool showFullUnitOfMeasure = true;
|
||||
|
||||
// Check first for specific strings not generated using the description bitmap
|
||||
|
||||
switch (getRegister())
|
||||
{
|
||||
case SDM_MAXIMUM_TOTAL_SYSTEM_POWER_DEMAND:
|
||||
case SDM_MAXIMUM_TOTAL_SYSTEM_VA_DEMAND:
|
||||
case SDM_MAXIMUM_NEUTRAL_CURRENT:
|
||||
case SDM_MAXIMUM_SYSTEM_POSITIVE_POWER_DEMAND:
|
||||
case SDM_MAXIMUM_SYSTEM_REVERSE_POWER_DEMAND:
|
||||
case SDM_MAXIMUM_PHASE_1_CURRENT_DEMAND:
|
||||
case SDM_MAXIMUM_PHASE_2_CURRENT_DEMAND:
|
||||
case SDM_MAXIMUM_PHASE_3_CURRENT_DEMAND:
|
||||
case SDM_MAXIMUM_REACTIVE_POWER_DEMAND:
|
||||
|
||||
res = F("Maximum ");
|
||||
break;
|
||||
|
||||
case SDM_CURRENT_RESETTABLE_TOTAL_ACTIVE_ENERGY:
|
||||
case SDM_CURRENT_RESETTABLE_TOTAL_REACTIVE_ENERGY:
|
||||
case SDM_CURRENT_RESETTABLE_IMPORT_ENERGY:
|
||||
case SDM_CURRENT_RESETTABLE_EXPORT_ENERGY:
|
||||
case SDM_CURRENT_RESETTABLE_IMPORT_REACTIVE_ENERGY:
|
||||
case SDM_CURRENT_RESETTABLE_EXPORT_REACTIVE_ENERGY:
|
||||
|
||||
res = F("Resettable ");
|
||||
break;
|
||||
|
||||
case SDM_AVERAGE_LINE_TO_LINE_VOLTS:
|
||||
case SDM_AVERAGE_L_TO_N_VOLTS:
|
||||
case SDM_AVERAGE_LINE_CURRENT:
|
||||
case SDM_AVERAGE_LINE_TO_NEUTRAL_VOLTS_THD:
|
||||
case SDM_AVERAGE_LINE_CURRENT_THD:
|
||||
case SDM_AVERAGE_LINE_TO_LINE_VOLTS_THD:
|
||||
res = F("Average ");
|
||||
break;
|
||||
|
||||
case SDM_SUM_LINE_CURRENT:
|
||||
res = F("Sum ");
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (getRegister())
|
||||
{
|
||||
case SDM_NEUTRAL_CURRENT_DEMAND:
|
||||
case SDM_MAXIMUM_NEUTRAL_CURRENT:
|
||||
case SDM_NEUTRAL_CURRENT:
|
||||
|
||||
res += F("Neutral ");
|
||||
break;
|
||||
|
||||
case SDM_LINE_1_TO_LINE_2_VOLTS:
|
||||
case SDM_LINE_1_TO_LINE_2_VOLTS_THD:
|
||||
res += F("L1 to L2 ");
|
||||
break;
|
||||
case SDM_LINE_2_TO_LINE_3_VOLTS:
|
||||
case SDM_LINE_2_TO_LINE_3_VOLTS_THD:
|
||||
res += F("L2 to L3 ");
|
||||
break;
|
||||
case SDM_LINE_3_TO_LINE_1_VOLTS:
|
||||
case SDM_LINE_3_TO_LINE_1_VOLTS_THD:
|
||||
res += F("L3 to L1 ");
|
||||
break;
|
||||
case SDM_AVERAGE_LINE_TO_LINE_VOLTS:
|
||||
case SDM_AVERAGE_LINE_TO_LINE_VOLTS_THD:
|
||||
res += F("Line to Line ");
|
||||
break;
|
||||
case SDM_AVERAGE_L_TO_N_VOLTS:
|
||||
case SDM_AVERAGE_LINE_TO_NEUTRAL_VOLTS_THD:
|
||||
case SDM_PHASE_1_LN_VOLTS_THD:
|
||||
case SDM_PHASE_2_LN_VOLTS_THD:
|
||||
case SDM_PHASE_3_LN_VOLTS_THD:
|
||||
res += F("L to N ");
|
||||
break;
|
||||
case SDM_AVERAGE_LINE_CURRENT:
|
||||
case SDM_AVERAGE_LINE_CURRENT_THD:
|
||||
case SDM_SUM_LINE_CURRENT:
|
||||
res += F("Line ");
|
||||
break;
|
||||
}
|
||||
|
||||
if (direction != SDM_DIRECTION::NotSpecified) {
|
||||
res += SDM_directionToString(direction);
|
||||
res += ' ';
|
||||
}
|
||||
|
||||
if (showFullUnitOfMeasure) {
|
||||
res += SDM_UOMtoString(uom, true);
|
||||
}
|
||||
|
||||
switch (getRegister())
|
||||
{
|
||||
case SDM_TOTAL_SYSTEM_POWER_DEMAND:
|
||||
case SDM_MAXIMUM_TOTAL_SYSTEM_POWER_DEMAND:
|
||||
case SDM_CURRENT_SYSTEM_POSITIVE_POWER_DEMAND:
|
||||
case SDM_MAXIMUM_SYSTEM_POSITIVE_POWER_DEMAND:
|
||||
case SDM_CURRENT_SYSTEM_REVERSE_POWER_DEMAND:
|
||||
case SDM_MAXIMUM_SYSTEM_REVERSE_POWER_DEMAND:
|
||||
case SDM_TOTAL_SYSTEM_VA_DEMAND:
|
||||
case SDM_MAXIMUM_TOTAL_SYSTEM_VA_DEMAND:
|
||||
case SDM_NEUTRAL_CURRENT_DEMAND:
|
||||
case SDM_MAXIMUM_NEUTRAL_CURRENT:
|
||||
case SDM_REACTIVE_POWER_DEMAND:
|
||||
case SDM_MAXIMUM_REACTIVE_POWER_DEMAND:
|
||||
case SDM_PHASE_1_CURRENT_DEMAND:
|
||||
case SDM_PHASE_2_CURRENT_DEMAND:
|
||||
case SDM_PHASE_3_CURRENT_DEMAND:
|
||||
case SDM_MAXIMUM_PHASE_1_CURRENT_DEMAND:
|
||||
case SDM_MAXIMUM_PHASE_2_CURRENT_DEMAND:
|
||||
case SDM_MAXIMUM_PHASE_3_CURRENT_DEMAND:
|
||||
|
||||
res += F(" demand");
|
||||
break;
|
||||
}
|
||||
|
||||
switch (getRegister())
|
||||
{
|
||||
case SDM_VAH_SINCE_LAST_RESET:
|
||||
case SDM_AH_SINCE_LAST_RESET:
|
||||
|
||||
res += F(" since last reset");
|
||||
break;
|
||||
}
|
||||
|
||||
res += ' ';
|
||||
res += '(';
|
||||
res += SDM_UOMtoString(uom, false);
|
||||
res += ')';
|
||||
res += getPhaseDescription(model, ' ');
|
||||
return res;
|
||||
}
|
||||
|
||||
String p078_register_description::getPhaseDescription(SDM_MODEL model, char separator) const
|
||||
{
|
||||
const int phase = getPhase();
|
||||
const bool showPhase = phase != 0 &&
|
||||
(model == SDM_MODEL::SDM630 || model == SDM_MODEL::SDM72_V2);
|
||||
String res;
|
||||
|
||||
if (showPhase) {
|
||||
if (separator != '\0') {
|
||||
res += separator;
|
||||
}
|
||||
res += 'L';
|
||||
res += phase;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
SDM_RegisterReadQueue _SDM_RegisterReadQueue;
|
||||
|
||||
void SDM_removeRegisterReadQueueElement(taskIndex_t TaskIndex, taskVarIndex_t TaskVarIndex)
|
||||
{
|
||||
if (validTaskIndex(TaskIndex) && validTaskVarIndex(TaskVarIndex)) {
|
||||
for (auto it = _SDM_RegisterReadQueue.begin(); it != _SDM_RegisterReadQueue.end();) {
|
||||
if ((it->taskIndex == TaskIndex) && (it->taskVarIndex == TaskVarIndex)) {
|
||||
it = _SDM_RegisterReadQueue.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SDM_addRegisterReadQueueElement(taskIndex_t TaskIndex, taskVarIndex_t TaskVarIndex, uint16_t reg, uint8_t dev_id)
|
||||
{
|
||||
SDM_removeRegisterReadQueueElement(TaskIndex, TaskVarIndex);
|
||||
|
||||
if ((reg != std::numeric_limits<uint16_t>::max()) &&
|
||||
validTaskIndex(TaskIndex) &&
|
||||
validTaskVarIndex(TaskVarIndex)) {
|
||||
_SDM_RegisterReadQueue.emplace_back(TaskIndex, TaskVarIndex, reg, dev_id);
|
||||
|
||||
// _SDM_RegisterReadQueue.sort(compare_SDM_RegisterReadQueueElement);
|
||||
}
|
||||
}
|
||||
|
||||
void SDM_loopRegisterReadQueue(SDM *sdm)
|
||||
{
|
||||
if (sdm == nullptr) { return; }
|
||||
auto it = _SDM_RegisterReadQueue.begin();
|
||||
|
||||
if (it == _SDM_RegisterReadQueue.end()) { return; }
|
||||
|
||||
if (it->_state == 1) {
|
||||
uint16_t readErr = sdm->readValReady(it->_dev_id);
|
||||
|
||||
if (readErr == SDM_ERR_STILL_WAITING) { return; }
|
||||
|
||||
if (readErr == SDM_ERR_NO_ERROR) {
|
||||
const float value = sdm->decodeFloatValue();
|
||||
UserVar.setFloat(it->taskIndex, it->taskVarIndex, value);
|
||||
|
||||
# if FEATURE_PLUGIN_STATS
|
||||
PluginTaskData_base *taskdata = getPluginTaskDataBaseClassOnly(it->taskIndex);
|
||||
|
||||
if (taskdata != nullptr) {
|
||||
if (taskdata->getPluginStats(it->taskVarIndex) != nullptr) {
|
||||
taskdata->getPluginStats(it->taskVarIndex)->trackPeak(value);
|
||||
}
|
||||
}
|
||||
# endif // if FEATURE_PLUGIN_STATS
|
||||
} else {
|
||||
sdm->clearErrCode();
|
||||
}
|
||||
it->_state = 0;
|
||||
_SDM_RegisterReadQueue.emplace_back(*it);
|
||||
_SDM_RegisterReadQueue.pop_front();
|
||||
it = _SDM_RegisterReadQueue.begin();
|
||||
}
|
||||
|
||||
if (it->_state == 0) {
|
||||
sdm->startReadVal(it->_reg, it->_dev_id);
|
||||
it->_state = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void SDM_pause_loopRegisterReadQueue()
|
||||
{
|
||||
auto it = _SDM_RegisterReadQueue.begin();
|
||||
|
||||
if (it != _SDM_RegisterReadQueue.end()) {
|
||||
it->_state = 2;
|
||||
}
|
||||
}
|
||||
|
||||
void SDM_resume_loopRegisterReadQueue()
|
||||
{
|
||||
auto it = _SDM_RegisterReadQueue.begin();
|
||||
|
||||
if (it != _SDM_RegisterReadQueue.end()) {
|
||||
it->_state = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // ifdef USES_P078
|
||||
@@ -0,0 +1,183 @@
|
||||
#ifndef PLUGINSTRUCTS_P078_DATA_STRUCT_H
|
||||
#define PLUGINSTRUCTS_P078_DATA_STRUCT_H
|
||||
|
||||
#include "../../ESPEasy_common.h"
|
||||
|
||||
#ifdef USES_P078
|
||||
# include "../../_Plugin_Helper.h"
|
||||
# include <ESPeasySerial.h>
|
||||
|
||||
# include <SDM.h> // Requires SDM library from Reaper7 - https://github.com/reaper7/SDM_Energy_Meter/
|
||||
|
||||
|
||||
# define P078_DEV_ID PCONFIG(0)
|
||||
# define P078_DEV_ID_LABEL PCONFIG_LABEL(0)
|
||||
# define P078_MODEL PCONFIG(1)
|
||||
# define P078_MODEL_LABEL PCONFIG_LABEL(1)
|
||||
# define P078_BAUDRATE PCONFIG(2)
|
||||
# define P078_BAUDRATE_LABEL PCONFIG_LABEL(2)
|
||||
|
||||
# define P078_GET_FLAG_COLL_DETECT bitRead(PCONFIG(7), 0)
|
||||
# define P078_SET_FLAG_COLL_DETECT(x) bitWrite(PCONFIG(7), 0, x)
|
||||
# define P078_FLAG_COLL_DETECT_LABEL "colldet"
|
||||
|
||||
# define P078_QUERY1_CONFIG_POS 3
|
||||
|
||||
# define P078_QUERY1 PCONFIG(P078_QUERY1_CONFIG_POS)
|
||||
# define P078_QUERY2 PCONFIG((P078_QUERY1_CONFIG_POS)+1)
|
||||
# define P078_QUERY3 PCONFIG((P078_QUERY1_CONFIG_POS)+2)
|
||||
# define P078_QUERY4 PCONFIG((P078_QUERY1_CONFIG_POS)+3)
|
||||
# define P078_DEPIN CONFIG_PIN3
|
||||
|
||||
# define P078_DEV_ID_DFLT 1
|
||||
# define P078_MODEL_DFLT 0 // SDM120C
|
||||
# define P078_BAUDRATE_DFLT 3 // 9600 baud
|
||||
# define P078_QUERY1_DFLT 0 // Voltage (V)
|
||||
# define P078_QUERY2_DFLT 1 // Current (A)
|
||||
# define P078_QUERY3_DFLT 2 // Power (W)
|
||||
# define P078_QUERY4_DFLT 5 // Power Factor (cos-phi)
|
||||
|
||||
|
||||
enum class SDM_UOM {
|
||||
percent,
|
||||
V,
|
||||
A,
|
||||
W,
|
||||
kWh,
|
||||
Ah,
|
||||
Hz,
|
||||
degrees,
|
||||
cos_phi,
|
||||
VA,
|
||||
VAr,
|
||||
kVAh,
|
||||
kVArh
|
||||
};
|
||||
|
||||
const __FlashStringHelper* SDM_UOMtoString(SDM_UOM uom,
|
||||
bool display);
|
||||
|
||||
// Value being stored, do not change order
|
||||
enum class SDM_MODEL {
|
||||
SDM220_SDM120CT_SDM120 = 0,
|
||||
SDM230 = 1,
|
||||
SDM72D = 2,
|
||||
DDM18SD = 3,
|
||||
SDM630 = 4,
|
||||
SDM72_V2 = 5,
|
||||
SDM320C = 6
|
||||
};
|
||||
|
||||
enum class SDM_DIRECTION {
|
||||
NotSpecified = 0,
|
||||
Import = 1,
|
||||
Export = 2,
|
||||
Total = 3
|
||||
};
|
||||
|
||||
const __FlashStringHelper *SDM_directionToString(SDM_DIRECTION);
|
||||
|
||||
void SDM_loadOutputSelector(struct EventStruct *event,
|
||||
uint8_t pconfigIndex,
|
||||
uint8_t valuenr);
|
||||
|
||||
struct p078_register_description {
|
||||
// Using special constexpr constructor so the bitflags can be stored as compact as possible
|
||||
// The compiler will compute the integer values.
|
||||
constexpr p078_register_description(
|
||||
uint16_t reg,
|
||||
SDM_UOM uom,
|
||||
uint8_t phase,
|
||||
uint8_t direction,
|
||||
uint8_t SDM630,
|
||||
uint8_t SDM320C,
|
||||
uint8_t SDM230,
|
||||
uint8_t SDM220,
|
||||
uint8_t SDM120CT,
|
||||
uint8_t SDM120,
|
||||
uint8_t SDM72D,
|
||||
uint8_t SDM72_V2,
|
||||
uint8_t DDM18SD
|
||||
) : val(
|
||||
(static_cast<uint32_t>(uom) & 0xF) |
|
||||
static_cast<uint32_t>(phase & 0x3) << 4 |
|
||||
static_cast<uint32_t>(direction & 0x3) << 6 |
|
||||
static_cast<uint32_t>(SDM630 & 0x1) << 8 |
|
||||
static_cast<uint32_t>(SDM320C & 0x1) << 9 |
|
||||
static_cast<uint32_t>(SDM230 & 0x1) << 10 |
|
||||
static_cast<uint32_t>(SDM220 & 0x1) << 11 |
|
||||
static_cast<uint32_t>(SDM120CT & 0x1) << 11 | // Same set of registers as SDM220
|
||||
static_cast<uint32_t>(SDM120 & 0x1) << 11 | // Nearly same set of registers as SDM220
|
||||
static_cast<uint32_t>(SDM72D & 0x1) << 12 |
|
||||
static_cast<uint32_t>(SDM72_V2 & 0x1) << 13 |
|
||||
static_cast<uint32_t>(DDM18SD & 0x1) << 14 |
|
||||
static_cast<uint32_t>(reg & 0xFFFF) << 16)
|
||||
{}
|
||||
|
||||
uint16_t getRegister() const;
|
||||
|
||||
SDM_UOM getUnitOfMeasure() const;
|
||||
|
||||
uint8_t getPhase() const;
|
||||
|
||||
SDM_DIRECTION getDirection() const;
|
||||
|
||||
bool match_SDM_model(SDM_MODEL model) const;
|
||||
|
||||
String getDescription(SDM_MODEL model) const;
|
||||
|
||||
String getPhaseDescription(SDM_MODEL model,
|
||||
char separator) const;
|
||||
|
||||
uint32_t val{};
|
||||
};
|
||||
|
||||
int SDM_getRegisterDescriptionIndexForModel(SDM_MODEL model,
|
||||
int x);
|
||||
|
||||
uint16_t SDM_getRegisterForModel(SDM_MODEL model,
|
||||
int choice);
|
||||
|
||||
String SDM_getValueNameForModel(SDM_MODEL model,
|
||||
int choice);
|
||||
|
||||
struct SDM_RegisterReadQueueElement {
|
||||
SDM_RegisterReadQueueElement(taskIndex_t TaskIndex, taskVarIndex_t TaskVarIndex, uint16_t reg, uint8_t dev_id)
|
||||
: taskIndex(TaskIndex),
|
||||
taskVarIndex(TaskVarIndex),
|
||||
_reg(reg),
|
||||
_dev_id(dev_id)
|
||||
{}
|
||||
|
||||
taskIndex_t taskIndex = INVALID_TASK_INDEX;
|
||||
taskVarIndex_t taskVarIndex = INVALID_TASKVAR_INDEX;
|
||||
uint16_t _reg = std::numeric_limits<uint16_t>::max(); // Modbus register
|
||||
uint8_t _dev_id = 0; // Modbus address
|
||||
uint8_t _state = 0;
|
||||
};
|
||||
|
||||
/*
|
||||
bool compare_SDM_RegisterReadQueueElement(const SDM_RegisterReadQueueElement& first, const SDM_RegisterReadQueueElement& second) const {
|
||||
return first._userVarIndex < second._userVarIndex;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
typedef std::list<SDM_RegisterReadQueueElement> SDM_RegisterReadQueue;
|
||||
|
||||
void SDM_removeRegisterReadQueueElement(taskIndex_t TaskIndex,
|
||||
taskVarIndex_t TaskVarIndex);
|
||||
void SDM_addRegisterReadQueueElement(taskIndex_t TaskIndex,
|
||||
taskVarIndex_t TaskVarIndex,
|
||||
uint16_t reg,
|
||||
uint8_t dev_id);
|
||||
|
||||
void SDM_loopRegisterReadQueue(SDM *sdm);
|
||||
|
||||
void SDM_pause_loopRegisterReadQueue();
|
||||
|
||||
void SDM_resume_loopRegisterReadQueue();
|
||||
|
||||
#endif // ifdef USES_P078
|
||||
|
||||
#endif // ifndef PLUGINSTRUCTS_P078_DATA_STRUCT_H
|
||||
Reference in New Issue
Block a user