Merge branch 'mega' into build/reduce_minimal_bin_size

This commit is contained in:
TD-er
2023-08-06 18:28:39 +02:00
committed by GitHub
35 changed files with 2766 additions and 564 deletions
+227 -8
View File
@@ -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
...
+56
View File
@@ -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`
+11
View File
@@ -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;
+5
View File
@@ -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
+6 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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 Garca (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
View File
@@ -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
+78 -21
View File
@@ -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>
)=====";
@@ -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();
}
@@ -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
}
+6
View File
@@ -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
-31
View File
@@ -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"
]
}
+4 -5
View File
@@ -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
View File
@@ -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;
}
+14
View File
@@ -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;
}
+5
View File
@@ -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);
+513
View File
@@ -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
+183
View File
@@ -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