[Eastron] Add P078 plugin from the Playground (was P150)

Still problems using the Software Serial
This commit is contained in:
TD-er
2018-08-24 01:29:55 +02:00
parent a123dbf37f
commit 56569374f3
12 changed files with 983 additions and 0 deletions
+153
View File
@@ -0,0 +1,153 @@
## Library for reading SDM120 SDM220 SDM230 SDM630 Modbus Energy meters. ##
### SECTIONS: ###
#### 1. [INTRODUCTION](#introduction) ####
#### 2. [SCREENSHOTS](#screenshots) ####
#### 3. [INITIALIZING](#initializing) ####
#### 4. [READING](#reading) ####
#### 5. [DEBUGING](#debuging) ####
#### 6. [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</i> https://github.com/plerup/espsoftwareserial)
you also need rs232<->rs485 converter:
- [x] with automatic flow direction control (<i>look at images below</i>) <b><i>or</i></b>
- [x] with additional pins for flow control, like MAX485</br>
(<i>in this case MAX485 DE and RE pins must be connected together to one of esp 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_
---
### 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/livepage.gif"></br>
<i>live page example (extended) screenshot</i>
</p>
---
### Initializing: ###
```cpp
//lib init when Software Serial is used:
#include <SDM.h>
// ______________________baudrate
// | __________________rx pin
// | | ______________tx pin
// | | | __________dere pin(optional for max485)
// | | | |
SDM<4800, 13, 15, 12> sdm;
//lib init when Hardware Serial is used:
#define USE_HARDWARESERIAL
#include <SDM.h>
// ______________________baudrate
// | __________________dere pin(optional for max485)
// | | ______________swap hw serial pins from 3/1 to 13/15(default false)
// | | |
SDM<4800, 12, false> sdm;
```
NOTE: <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>
connection this type of converters to ESP8266 pin GPIO15 block booting process.</br>
In this case you can replace the pull-up resistor on converter with higher value (100k),</br>
to ensure low level on GPIO15 by built-in in most ESP8266 modules pulldown resistor.</br></i>
---
### Reading: ###
List of available registers for SDM120/220/230/630:</br>
https://github.com/reaper7/SDM_Energy_Meter/blob/master/SDM.h#L36
```cpp
//reading voltage from SDM with slave address 0x01 (default)
// __________register name
// |
float voltage = sdm.readVal(SDM220T_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);
```
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>
only ID must be different for each SDM device.</i>
---
### Debuging: ###
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>
__Please check out open and close issues, maybe the cause of your error is explained or solved there.__
The most common problems are:
- weak or poorly filtered power supply / LDO, causing NaN readings and ESP crashes</br>
https://github.com/reaper7/SDM_Energy_Meter/issues/13#issuecomment-353532711</br>
https://github.com/reaper7/SDM_Energy_Meter/issues/13#issuecomment-353572909</br>
https://github.com/reaper7/SDM_Energy_Meter/issues/8#issuecomment-381402008</br>
- faulty or incorrectly prepared converter</br>
https://github.com/reaper7/SDM_Energy_Meter/issues/16#issue-311042308</br>
- faulty esp module</br>
https://github.com/reaper7/SDM_Energy_Meter/issues/8#issuecomment-381398551</br>
- many users report that between each readings should be placed <i>delay(50);</i></br>
https://github.com/reaper7/SDM_Energy_Meter/issues/7#issuecomment-272080139</br>
(I did not observe such problems using the HardwareSerial connection)</br>
- using GPIO15 without checking signal level (note above)</br>
https://github.com/reaper7/SDM_Energy_Meter/issues/17#issue-313606825</br>
https://github.com/reaper7/SDM_Energy_Meter/issues/13#issuecomment-353413146</br>
https://github.com/reaper7/SDM_Energy_Meter/issues/13#issuecomment-353417658</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
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#L128</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)
uint16_t cnterrors = sdm.getErrCount(true);
//clear errors counter also available with:
sdm.clearErrCount();
```
---
### Credits: ###
:+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>
---
**2016-2018 Reaper7**
[paypal.me/reaper7md](https://www.paypal.me/reaper7md)
+292
View File
@@ -0,0 +1,292 @@
/* Template library for reading SDM 120/220/630 Modbus Energy meters.
* Reading via Software Serial library & rs232<->rs485 converter
* 2016 Reaper7 (tested on wemos d1 mini->ESP8266 with Arduino 1.6.9 & 2.3.0 esp8266 core)
* crc calculation by Jaime García (https://github.com/peninquen/Modbus-Energy-Monitor-Arduino/)
*/
//#define USE_HARDWARESERIAL //option - use hardware serial
#ifndef SDM_h
#define SDM_h
//------------------------------------------------------------------------------
#include <Arduino.h>
#if !defined ( USE_HARDWARESERIAL )
#include <SoftwareSerial.h>
#endif
//------------------------------------------------------------------------------
#define SDM_BAUD 4800 //baudrate
#define MAX_MILLIS_TO_WAIT 500 //max time to wait for response from SDM
#if !defined ( USE_HARDWARESERIAL )
#define SDMSER_RX_PIN 13 //RX-D7(wemos)-13
#define SDMSER_TX_PIN 15 //TX-D8(wemos)-15
#else
#define SWAPHWSERIAL 0 //when hwserial used, then swap uart pins from 3/1 to 13/15
#endif
#define DERE_PIN NOT_A_PIN //digital pin for control MAX485 DE/RE lines (connect DE & /RE together to this pin)
#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 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
//------------------------------------------------------------------------------
#if !defined ( USE_HARDWARESERIAL )
template <long _speed = SDM_BAUD, int _rx_pin = SDMSER_RX_PIN, int _tx_pin = SDMSER_TX_PIN, int _dere_pin = DERE_PIN>
#else
template <long _speed = SDM_BAUD, int _dere_pin = DERE_PIN, bool _swapuart = SWAPHWSERIAL>
#endif
struct SDM {
#if !defined ( USE_HARDWARESERIAL )
#if defined ( ESP8266 )
SoftwareSerial sdmSer = SoftwareSerial(_rx_pin, _tx_pin, false, 32); //for esp8266 SoftwareSerial (https://github.com/plerup/espsoftwareserial)
#else
SoftwareSerial sdmSer = SoftwareSerial(_rx_pin, _tx_pin); //for standard avr SoftwareSerial library
#endif
#else
#if defined ( ESP8266 )
HardwareSerial sdmSer = HardwareSerial(0);
#else
#error Hardware Serial option available only for ESP8266.
#endif
#endif
private:
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
uint16_t calculateCRC(uint8_t *array, uint8_t num) {
uint16_t temp, flag;
temp = 0xFFFF;
for (uint8_t i = 0; i < num; i++) {
temp = temp ^ array[i];
for (uint8_t j = 8; j; j--) {
flag = temp & 0x0001;
temp >>= 1;
if (flag)
temp ^= 0xA001;
}
}
return temp;
};
public:
void clearErrCode() { //clear last errorcode
readingerrcode = SDM_ERR_NO_ERROR;
};
void clearErrCount() { //clear total errors count
readingerrcount = 0;
};
uint16_t getErrCode(bool _clear = false) { //return last errorcode (optional clear this value, default flase)
uint16_t _tmp = readingerrcode;
if (_clear == true)
clearErrCode();
return (_tmp);
};
uint16_t getErrCount(bool _clear = false) { //return total errors count (optional clear this value, default flase)
uint16_t _tmp = readingerrcount;
if (_clear == true)
clearErrCount();
return (_tmp);
};
void begin() {
sdmSer.begin(_speed);
#if defined ( USE_HARDWARESERIAL )
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);
};
float readVal(uint16_t reg, uint8_t node = SDM_B_01) { //read value from register = reg and from deviceId = 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;
sdmarr[2] = highByte(reg);
sdmarr[3] = lowByte(reg);
temp = calculateCRC(sdmarr, FRAMESIZE - 3); //calculate out crc only from first 6 bytes
sdmarr[6] = lowByte(temp);
sdmarr[7] = highByte(temp);
#if !defined ( USE_HARDWARESERIAL )
sdmSer.listen(); //enable softserial rx interrupt
#endif
while (sdmSer.available() > 0) { //read serial if any old data is available
sdmSer.read();
}
if (_dere_pin != NOT_A_PIN) //transmit to SDM -> DE Enable, /RE Disable (for control MAX485)
digitalWrite(_dere_pin, HIGH);
delay(2); //fix for issue (nan reading) by sjfaustino: https://github.com/reaper7/SDM_Energy_Meter/issues/7#issuecomment-272111524
sdmSer.write(sdmarr, FRAMESIZE - 1); //send 8 bytes
sdmSer.flush(); //clear out tx buffer
if (_dere_pin != NOT_A_PIN) //receive from SDM -> DE Disable, /RE Enable (for control MAX485)
digitalWrite(_dere_pin, LOW);
resptime = millis() + MAX_MILLIS_TO_WAIT;
while (sdmSer.available() < FRAMESIZE) {
if (resptime < millis()) {
readErr = SDM_ERR_TIMEOUT; //err debug (4)
break;
}
yield();
}
if (readErr == SDM_ERR_NO_ERROR) { //if no timeout...
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 {
readErr = SDM_ERR_CRC_ERROR; //err debug (1)
}
} else {
readErr = SDM_ERR_WRONG_BYTES; //err debug (2)
}
} else {
readErr = SDM_ERR_NOT_ENOUGHT_BYTES; //err debug (3)
}
}
if (readErr != SDM_ERR_NO_ERROR) { //if error then copy temp error value to global val and increment global error counter
readingerrcode = readErr;
readingerrcount++;
}
#if !defined ( USE_HARDWARESERIAL )
sdmSer.end(); //disable softserial rx interrupt
#endif
return (res);
};
};
#endif
@@ -0,0 +1,71 @@
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<6;i++){
xmldoc=xmlResponse.getElementsByTagName('response'+i)[0].firstChild.nodeValue;
document.getElementById('resp'+i).innerHTML=xmldoc;
}
xmldoc=xmlResponse.getElementsByTagName('freeh')[0].firstChild.nodeValue;
document.getElementById('freeheap').innerHTML=xmldoc;
xmldoc=xmlResponse.getElementsByTagName('rst')[0].firstChild.nodeValue;
document.getElementById('rstreason').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="PHASE ANGLE">PHASE ANGLE</TH><TD><A id='resp4'></A></TD><TD>&deg;</TD></TR>
<TR><TH title="FREQUENCY">FREQUENCY</TH><TD><A id='resp5'></A></TD><TD>Hz</TD></TR>
<TR><TH title="FREE HEAP">FREE HEAP</TH><TD><A id='freeheap'></A></TD><TD>bytes</TD></TR>
<TR><TH title="LAST RESET REASON">LAST RESET REASON</TH><TD colspan="2"><A id='rstreason'></A></TD></TR>
</TABLE>
</CENTER>
</BODY>
</HTML>
)=====";
@@ -0,0 +1,187 @@
//sdm live page example by reaper7
//#define USE_HARDWARESERIAL
#define READSDMEVERY 2000 //read sdm every 2000ms
#define NBREG 6 //number of sdm registers to read
//#define USE_STATIC_IP
/* 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
| |
|___________________________|
*/
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <ESP8266mDNS.h>
#include <ArduinoOTA.h>
#include <ESPAsyncTCP.h> // https://github.com/me-no-dev/ESPAsyncTCP
#include <ESPAsyncWebServer.h> // https://github.com/me-no-dev/ESPAsyncWebServer
#include <SDM.h> // https://github.com/reaper7/SDM_Energy_Meter
#include "index_page.h"
//------------------------------------------------------------------------------
AsyncWebServer server(80);
#if !defined ( USE_HARDWARESERIAL ) // SOFTWARE SERIAL
SDM<4800, 13, 15, NOT_A_PIN> sdm; // baud, rx_pin, tx_pin, de/re_pin(not used in this example)
#else // HARDWARE SERIAL
SDM<4800, NOT_A_PIN, false> sdm; // baud, de/re_pin(not used in this example), uart0 pins 3/1(false) or 13/15(true)
#endif
//------------------------------------------------------------------------------
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";
String lastresetreason = "";
unsigned long readtime;
//------------------------------------------------------------------------------
typedef volatile struct {
volatile float regvalarr;
const uint16_t regarr;
} 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_PHASE_ANGLE}, //DEGREE
{0.00, SDM220T_FREQUENCY}, //Hz
};
//------------------------------------------------------------------------------
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("<freeh>");
XML += String(ESP.getFreeHeap());
XML += F("</freeh>");
XML += F("<rst>");
XML += lastresetreason;
XML += F("</rst>");
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, LOW);
}
//------------------------------------------------------------------------------
void ledOff() {
digitalWrite(LED_BUILTIN, HIGH);
}
//------------------------------------------------------------------------------
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();
lastresetreason = ESP.getResetReason();
wifiInit();
otaInit();
serverInit();
sdm.begin();
readtime = millis();
ledOff();
}
//------------------------------------------------------------------------------
void loop() {
ArduinoOTA.handle();
if (millis() - readtime >= READSDMEVERY) {
sdmRead();
readtime = millis();
}
yield();
}
@@ -0,0 +1,62 @@
/* 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
| |
|___________________________|
*/
#include <SDM.h> //import SDM template library
#define ASCII_ESC 27
char bufout[10];
//SDM<2400, 13, 15> sdm; //SDM120T baud, rx pin, tx pin, dere pin(optional for max485)
//SDM<4800, 13, 15> sdm; //SDM220T baud, rx pin, tx pin, dere pin(optional for max485)
//SDM<9600, 13, 15> sdm; //SDM630 baud, rx pin, tx pin, dere pin(optional for max485)
//or without parameters (default from SDM.h will be used):
SDM<> sdm;
void setup() {
Serial.begin(115200); //initialize serial
sdm.begin(); //initialize SDM220 communication baudrate
}
void loop() {
sprintf(bufout,"%c[1;0H",ASCII_ESC);
Serial.print(bufout);
Serial.print("Voltage: ");
Serial.print(sdm.readVal(SDM220T_VOLTAGE), 2); //display voltage
Serial.println("V");
delay(50);
Serial.print("Current: ");
Serial.print(sdm.readVal(SDM220T_CURRENT), 2); //display current
Serial.println("A");
delay(50);
Serial.print("Power: ");
Serial.print(sdm.readVal(SDM220T_POWER), 2); //display power
Serial.println("W");
delay(50);
Serial.print("Frequency: ");
Serial.print(sdm.readVal(SDM220T_FREQUENCY), 2); //display frequency
Serial.println("Hz");
delay(1000); //wait a while before next loop
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

+8
View File
@@ -0,0 +1,8 @@
SDM KEYWORD1
begin KEYWORD2
readVal KEYWORD2
getErrCode KEYWORD2
getErrCount KEYWORD2
clearErrCode KEYWORD2
clearErrCount KEYWORD2
+9
View File
@@ -0,0 +1,9 @@
name=SDM
version=1.0.4
author=Reaper7
maintainer=Reaper7
sentence=SDM 120/220/630 modbus energy meter
paragraph=template library for ESP8266
category=Communication
url=https://github.com/reaper7/SDM_Energy_Meter
architectures=esp8266,avr
+199
View File
@@ -0,0 +1,199 @@
#ifdef USES_P078
//#######################################################################################################
//############################# Plugin 078: SDM120C 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 SDM220 AND SDM630 series.
*/
#define PLUGIN_078
#define PLUGIN_ID_078 78
#define PLUGIN_NAME_078 "Energy (AC) - Eastron SDM120C"
#define PLUGIN_VALUENAME1_078 "Voltage"
boolean Plugin_078_init = false;
#include <SDM.h> // Requires SDM library from Reaper7 - https://github.com/reaper7/SDM_Energy_Meter/
SDM<2400, D6, D7> Plugin_078_SDM;
boolean Plugin_078(byte function, struct EventStruct *event, String& string)
{
boolean success = false;
switch (function)
{
case PLUGIN_DEVICE_ADD:
{
Device[++deviceCount].Number = PLUGIN_ID_078;
Device[deviceCount].Type = DEVICE_TYPE_DUAL; // connected through 2 datapins
Device[deviceCount].VType = SENSOR_TYPE_SINGLE;
Device[deviceCount].Ports = 0;
Device[deviceCount].PullUpOption = false;
Device[deviceCount].InverseLogicOption = false;
Device[deviceCount].FormulaOption = true;
Device[deviceCount].ValueCount = 1;
Device[deviceCount].SendDataOption = true;
Device[deviceCount].TimerOption = true;
Device[deviceCount].GlobalSyncOption = true;
break;
}
case PLUGIN_GET_DEVICENAME:
{
string = F(PLUGIN_NAME_078);
break;
}
case PLUGIN_GET_DEVICEVALUENAMES:
{
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_078));
break;
}
case PLUGIN_WEBFORM_LOAD:
{
byte meter_model = Settings.TaskDevicePluginConfig[event->TaskIndex][1];
byte meter_baudrate = Settings.TaskDevicePluginConfig[event->TaskIndex][2];
byte query = Settings.TaskDevicePluginConfig[event->TaskIndex][3];
String options_model[3] = { F("SDM120C"), F("SDM220T"), F("SDM630") };
addFormSelector(F("Model Type"), F("plugin_078_meter_model"), 3, options_model, NULL, meter_model );
String options_baudrate[6] = { F("1200"), F("2400"), F("4800"), F("9600"), F("19200"), F("38400") };
addFormSelector(F("Baud Rate"), F("plugin_078_meter_baudrate"), 6, options_baudrate, NULL, meter_baudrate );
if (meter_model == 0 && meter_baudrate > 3)
addFormNote(F("<span style=\"color:red\"> SDM120 only allows up to 9600 baud with default 2400!</span>"));
if (meter_model == 2 && meter_baudrate == 0)
addFormNote(F("<span style=\"color:red\"> SDM630 only allows 2400 to 38400 baud with default 9600!</span>"));
String options_query[10] = { F("Voltage (V)"),
F("Current (A)"),
F("Power (W)"),
F("Active Apparent Power (VA)"),
F("Reactive Apparent Power (VAr)"),
F("Power Factor (cos-phi)"),
F("Frequency (Hz)"),
F("Import Active Energy (Wh)"),
F("Export Active Energy (Wh)"),
F("Total Active Energy (Wh)") };
addFormSelector(F("Query"), F("plugin_078_query"), 10, options_query, NULL, query );
success = true;
break;
}
case PLUGIN_WEBFORM_SAVE:
{
Settings.TaskDevicePluginConfig[event->TaskIndex][0] = getFormItemInt(F("plugin_078"));
Settings.TaskDevicePluginConfig[event->TaskIndex][1] = getFormItemInt(F("plugin_078_meter_model"));
Settings.TaskDevicePluginConfig[event->TaskIndex][2] = getFormItemInt(F("plugin_078_meter_baudrate"));
Settings.TaskDevicePluginConfig[event->TaskIndex][3] = getFormItemInt(F("plugin_078_query"));
Plugin_078_init = false; // Force device setup next time
success = true;
break;
}
case PLUGIN_INIT:
{
Plugin_078_init = true;
// SDM<2400, Settings.TaskDevicePin1[event->TaskIndex],
// Settings.TaskDevicePin2[event->TaskIndex]> Plugin_078_SDM;
Plugin_078_SDM.begin();
success = true;
break;
}
case PLUGIN_READ:
{
if (Plugin_078_init)
{
float _tempvar = 0;
String log = F("EASTRON: ");
switch(Settings.TaskDevicePluginConfig[event->TaskIndex][3])
{
case 0:
{
_tempvar = Plugin_078_SDM.readVal(SDM120C_VOLTAGE);
log += F("Voltage ");
break;
}
case 1:
{
_tempvar = Plugin_078_SDM.readVal(SDM120C_CURRENT);
log += F("Current ");
break;
}
case 2:
{
_tempvar = Plugin_078_SDM.readVal(SDM120C_POWER);
log += F("Power ");
break;
}
case 3:
{
_tempvar = Plugin_078_SDM.readVal(SDM120C_ACTIVE_APPARENT_POWER);
log += F("Active Apparent Power ");
break;
}
case 4:
{
_tempvar = Plugin_078_SDM.readVal(SDM120C_REACTIVE_APPARENT_POWER);
log += F("Reactive Apparent Power ");
break;
}
case 5:
{
_tempvar = Plugin_078_SDM.readVal(SDM120C_POWER_FACTOR);
log += F("Power Factor ");
break;
}
case 6:
{
_tempvar = Plugin_078_SDM.readVal(SDM120C_FREQUENCY);
log += F("Frequency ");
break;
}
case 7:
{
_tempvar = Plugin_078_SDM.readVal(SDM120C_IMPORT_ACTIVE_ENERGY);
log += F("Import Active Energy ");
break;
}
case 8:
{
_tempvar = Plugin_078_SDM.readVal(SDM120C_EXPORT_ACTIVE_ENERGY);
log += F("Export Active Energy ");
break;
}
case 9:
{
_tempvar = Plugin_078_SDM.readVal(SDM120C_TOTAL_ACTIVE_ENERGY);
log += F("Total Active Energy ");
break;
}
}
UserVar[event->BaseVarIndex] = _tempvar;
log += _tempvar;
addLog(LOG_LEVEL_INFO, log);
success = true;
break;
}
break;
}
}
return success;
}
#endif // USES_P078
+2
View File
@@ -444,6 +444,8 @@ To create/register a plugin, you have to :
#define USES_P073 // 7DG
#define USES_P074 // TSL2561
#define USES_P075 // Nextion
#define USES_P078 // Eastron Modbus Energy meters
#endif