Skip to content

Commit

Permalink
update SDM power meter library to version 2.2.3
Browse files Browse the repository at this point in the history
in particular, this brings improved timing (settings). the values are
now read out much quicker in succession.
  • Loading branch information
schlimmchen committed Jun 27, 2024
1 parent a5ba24f commit 8202bb2
Show file tree
Hide file tree
Showing 3 changed files with 312 additions and 53 deletions.
234 changes: 194 additions & 40 deletions lib/SdmEnergyMeter/SDM.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* Library for reading SDM 72/120/220/230/630 Modbus Energy meters.
* Reading via Hardware or Software Serial library & rs232<->rs485 converter
* 2016-2022 Reaper7 (tested on wemos d1 mini->ESP8266 with Arduino 1.8.10 & 2.5.2 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/)
*/
//------------------------------------------------------------------------------
Expand Down Expand Up @@ -60,7 +60,7 @@ void SDM::begin(void) {
#endif
#else
#if defined ( ESP8266 ) || defined ( ESP32 )
sdmSer.begin(_baud, (SoftwareSerialConfig)_config, _rx_pin, _tx_pin);
sdmSer.begin(_baud, (EspSoftwareSerial::Config)_config, _rx_pin, _tx_pin);
#else
sdmSer.begin(_baud);
#endif
Expand All @@ -77,44 +77,67 @@ void SDM::begin(void) {
}

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;

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
startReadVal(reg, node);

flush(); //read serial if any old data is available
uint16_t readErr = SDM_ERR_STILL_WAITING;

dereSet(HIGH); //transmit to SDM -> DE Enable, /RE Disable (for control MAX485)
while (readErr == SDM_ERR_STILL_WAITING) {
readErr = readValReady(node);
delay(1);
}

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) { //if error then copy temp error value to global val and increment global error counter
readingerrcode = readErr;
readingerrcount++;
} else {
++readingsuccesscount;
}

sdmSer.write(sdmarr, FRAMESIZE - 1); //send 8 bytes
if (readErr == SDM_ERR_NO_ERROR) {
return decodeFloatValue();
}

sdmSer.flush(); //clear out tx buffer
constexpr float res = NAN;
return (res);
}

dereSet(LOW); //receive from SDM -> DE Disable, /RE Enable (for control MAX485)
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

constexpr size_t messageLength = sizeof(data) / sizeof(data[0]);
modbusWrite(data, messageLength);
}

resptime = millis();
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 (millis() - resptime > msturnaround) {
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...
Expand All @@ -125,14 +148,10 @@ float SDM::readVal(uint16_t reg, uint8_t node) {
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)
}

Expand All @@ -159,12 +178,95 @@ float SDM::readVal(uint16_t reg, uint8_t node) {
}

#if !defined ( USE_HARDWARESERIAL )
sdmSer.stopListening(); //disable softserial rx interrupt
// 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);
}

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)
Expand Down Expand Up @@ -224,7 +326,7 @@ uint16_t SDM::getMsTimeout() {
return (mstimeout);
}

uint16_t SDM::calculateCRC(uint8_t *array, uint8_t len) {
uint16_t SDM::calculateCRC(const uint8_t *array, uint8_t len) const {
uint16_t _crc, _flag;
_crc = 0xFFFF;
for (uint8_t i = 0; i < len; i++) {
Expand All @@ -241,14 +343,66 @@ uint16_t SDM::calculateCRC(uint8_t *array, uint8_t len) {

void SDM::flush(unsigned long _flushtime) {
unsigned long flushstart = millis();
while (sdmSer.available() || (millis() - flushstart < _flushtime)) {
if (sdmSer.available()) //read serial if any old data is available
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(1); //fix for issue (nan reading) by sjfaustino: https://github.com/reaper7/SDM_Energy_Meter/issues/7#issuecomment-272111524

// 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 * 11000) / _baud + 1;
resptime = millis() + waitForBytesSent_ms;
}

sdmSer.write(data, messageLength); //send 8 bytes

if (_dere_pin != NOT_A_PIN) {
const int32_t timeleft = (int32_t) (resptime - millis());
if (timeleft > 0) {
delay(timeleft); //clear out tx buffer
}
dereSet(LOW); //receive from SDM -> DE Disable, /RE Enable (for control MAX485)
flush();
}

resptime = millis();
}
Loading

0 comments on commit 8202bb2

Please sign in to comment.