mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-01-14 15:54:47 +00:00
701 lines
22 KiB
C++
701 lines
22 KiB
C++
/**
|
|
* @copyright Utilitech AS 2023
|
|
* License: Fair Source
|
|
*
|
|
*/
|
|
|
|
#include "PassiveMeterCommunicator.h"
|
|
#include "IEC6205675.h"
|
|
#include "IEC6205621.h"
|
|
#include "LNG.h"
|
|
#include "LNG2.h"
|
|
|
|
#if defined(ESP32)
|
|
#include <driver/uart.h>
|
|
#endif
|
|
|
|
PassiveMeterCommunicator::PassiveMeterCommunicator(RemoteDebug* debugger) {
|
|
this->debugger = debugger;
|
|
bauds[0] = 2400;
|
|
parities[0] = 11;
|
|
inverts[0] = false;
|
|
|
|
bauds[1] = 2400;
|
|
parities[1] = 3;
|
|
inverts[1] = false;
|
|
|
|
bauds[2] = 115200;
|
|
parities[2] = 3;
|
|
inverts[2] = false;
|
|
|
|
bauds[3] = 2400;
|
|
parities[3] = 11;
|
|
inverts[3] = true;
|
|
|
|
bauds[4] = 2400;
|
|
parities[4] = 3;
|
|
inverts[4] = true;
|
|
|
|
bauds[5] = 115200;
|
|
parities[5] = 3;
|
|
inverts[5] = true;
|
|
}
|
|
|
|
void PassiveMeterCommunicator::configure(MeterConfig& meterConfig, Timezone* tz) {
|
|
this->meterConfig = meterConfig;
|
|
this->configChanged = false;
|
|
this->tz = tz;
|
|
setupHanPort(meterConfig.baud, meterConfig.parity, meterConfig.invert);
|
|
if(gcmParser != NULL) {
|
|
delete gcmParser;
|
|
gcmParser = NULL;
|
|
}
|
|
}
|
|
|
|
bool PassiveMeterCommunicator::loop() {
|
|
if(hanBufferSize == 0) return false;
|
|
|
|
unsigned long start, end;
|
|
if(!hanSerial->available()) {
|
|
return false;
|
|
}
|
|
|
|
// Before reading, empty serial buffer to increase chance of getting first byte of a data transfer
|
|
if(!serialInit) {
|
|
hanSerial->readBytes(hanBuffer, hanBufferSize);
|
|
serialInit = true;
|
|
return false;
|
|
}
|
|
|
|
unsigned long now = millis();
|
|
if(autodetect) handleAutodetect(now);
|
|
|
|
dataAvailable = false;
|
|
ctx = {0,0,0,0};
|
|
memset(ctx.system_title, 0, 8);
|
|
pos = DATA_PARSE_INCOMPLETE;
|
|
// For each byte received, check if we have a complete frame we can handle
|
|
start = millis();
|
|
while(hanSerial->available() && pos == DATA_PARSE_INCOMPLETE) {
|
|
// If buffer was overflowed, reset
|
|
if(len >= hanBufferSize) {
|
|
hanSerial->readBytes(hanBuffer, hanBufferSize);
|
|
len = 0;
|
|
if (debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Buffer overflow, resetting\n"));
|
|
return false;
|
|
}
|
|
hanBuffer[len++] = hanSerial->read();
|
|
ctx.length = len;
|
|
pos = unwrapData((uint8_t *) hanBuffer, ctx);
|
|
if(ctx.type > 0 && pos >= 0) {
|
|
switch(ctx.type) {
|
|
case DATA_TAG_DLMS:
|
|
if (debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Received valid DLMS at %d\n"), pos);
|
|
break;
|
|
case DATA_TAG_DSMR:
|
|
if (debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Received valid DSMR at %d\n"), pos);
|
|
break;
|
|
case DATA_TAG_SNRM:
|
|
if (debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Received valid SNMR at %d\n"), pos);
|
|
break;
|
|
case DATA_TAG_AARE:
|
|
if (debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Received valid AARE at %d\n"), pos);
|
|
break;
|
|
case DATA_TAG_RES:
|
|
if (debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Received valid Get Response at %d\n"), pos);
|
|
break;
|
|
case DATA_TAG_HDLC:
|
|
if (debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Received valid HDLC at %d\n"), pos);
|
|
break;
|
|
default:
|
|
// TODO: Move this so that payload is sent to MQTT
|
|
if (debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("Unknown tag %02X at pos %d\n"), ctx.type, pos);
|
|
len = 0;
|
|
return false;
|
|
}
|
|
}
|
|
yield();
|
|
}
|
|
end = millis();
|
|
if(end-start > 1000) {
|
|
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Used %dms to unwrap HAN data\n"), end-start);
|
|
}
|
|
|
|
if(pos == DATA_PARSE_INCOMPLETE) {
|
|
return false;
|
|
} else if(pos == DATA_PARSE_UNKNOWN_DATA) {
|
|
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Unknown data received\n"));
|
|
lastError = pos;
|
|
len = len + hanSerial->readBytes(hanBuffer+len, hanBufferSize-len);
|
|
if(debugger->isActive(RemoteDebug::VERBOSE)) {
|
|
debugger->printf_P(PSTR(" payload:\n"));
|
|
debugPrint(hanBuffer, 0, len);
|
|
}
|
|
len = 0;
|
|
return false;
|
|
}
|
|
if(pos == DATA_PARSE_INTERMEDIATE_SEGMENT) {
|
|
len = 0;
|
|
return false;
|
|
} else if(pos < 0) {
|
|
lastError = pos;
|
|
printHanReadError(pos);
|
|
len += hanSerial->readBytes(hanBuffer+len, hanBufferSize-len);
|
|
if(pt != NULL) {
|
|
pt->publishBytes(hanBuffer+pos, len);
|
|
}
|
|
while(hanSerial->available()) hanSerial->read(); // Make sure it is all empty, in case we overflowed buffer above
|
|
len = 0;
|
|
return false;
|
|
}
|
|
|
|
if(ctx.type == 0) {
|
|
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Ended up with context type %d, return code %d and length: %lu/%lu\n"), ctx.type, pos, ctx.length, len);
|
|
lastError = pos;
|
|
len = len + hanSerial->readBytes(hanBuffer+len, hanBufferSize-len);
|
|
if(debugger->isActive(RemoteDebug::VERBOSE)) {
|
|
debugger->printf_P(PSTR(" payload:\n"));
|
|
debugPrint(hanBuffer, 0, len);
|
|
}
|
|
len = 0;
|
|
return false;
|
|
}
|
|
|
|
// Data is valid, clear the rest of the buffer to avoid tainted parsing
|
|
for(int i = pos+ctx.length; i<hanBufferSize; i++) {
|
|
hanBuffer[i] = 0x00;
|
|
}
|
|
dataAvailable = true;
|
|
lastError = DATA_PARSE_OK;
|
|
|
|
// After one hour, adjust buffer size to match the largest payload
|
|
if(!maxDetectPayloadDetectDone && now > 3600000) {
|
|
if(maxDetectedPayloadSize * 1.25 > meterConfig.bufferSize * 64) {
|
|
int bufferSize = min((double) 64, ceil((maxDetectedPayloadSize * 1.25) / 64));
|
|
#if defined(ESP8266)
|
|
if(meterConfig.rxPin != 3 && meterConfig.rxPin != 113) {
|
|
bufferSize = min(bufferSize, 2);
|
|
} else {
|
|
bufferSize = min(bufferSize, 8);
|
|
}
|
|
#endif
|
|
if(bufferSize != meterConfig.bufferSize) {
|
|
if (debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Increasing RX buffer to %d bytes\n"), meterConfig.bufferSize * 64);
|
|
meterConfig.bufferSize = bufferSize;
|
|
configChanged = true;
|
|
}
|
|
}
|
|
maxDetectPayloadDetectDone = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
AmsData* PassiveMeterCommunicator::getData(AmsData& meterState) {
|
|
if(!dataAvailable) return NULL;
|
|
if(ctx.length > hanBufferSize) {
|
|
debugger->printf_P(PSTR("Invalid context length\n"));
|
|
dataAvailable = false;
|
|
return NULL;
|
|
}
|
|
|
|
AmsData* data = NULL;
|
|
char* payload = ((char *) (hanBuffer)) + pos;
|
|
if(maxDetectedPayloadSize < pos) maxDetectedPayloadSize = pos;
|
|
if(ctx.type == DATA_TAG_DLMS) {
|
|
if(pt != NULL) {
|
|
pt->publishBytes((uint8_t*) payload, ctx.length);
|
|
}
|
|
|
|
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("Using application data:\n"));
|
|
if(debugger->isActive(RemoteDebug::VERBOSE)) debugPrint((byte*) payload, 0, ctx.length);
|
|
|
|
// Rudimentary detector for L&G proprietary format, this is terrible code... Fix later
|
|
if(payload[0] == CosemTypeStructure && payload[2] == CosemTypeArray && payload[1] == payload[3]) {
|
|
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("LNG\n"));
|
|
LNG lngData = LNG(payload, meterState.getMeterType(), &meterConfig, ctx, debugger);
|
|
if(lngData.getListType() >= 3) {
|
|
data = new AmsData();
|
|
data->apply(meterState);
|
|
data->apply(lngData);
|
|
}
|
|
} else if(payload[0] == CosemTypeStructure &&
|
|
payload[2] == CosemTypeLongUnsigned &&
|
|
payload[5] == CosemTypeLongUnsigned &&
|
|
payload[8] == CosemTypeLongUnsigned &&
|
|
payload[11] == CosemTypeLongUnsigned &&
|
|
payload[14] == CosemTypeLongUnsigned &&
|
|
payload[17] == CosemTypeLongUnsigned
|
|
) {
|
|
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("LNG2\n"));
|
|
LNG2 lngData = LNG2(payload, meterState.getMeterType(), &meterConfig, ctx, debugger);
|
|
if(lngData.getListType() >= 3) {
|
|
data = new AmsData();
|
|
data->apply(meterState);
|
|
data->apply(lngData);
|
|
}
|
|
} else {
|
|
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("DLMS\n"));
|
|
// TODO: Split IEC6205675 into DataParserKaifa and DataParserObis. This way we can add other means of parsing, for those other proprietary formats
|
|
data = new IEC6205675(payload, meterState.getMeterType(), &meterConfig, ctx, meterState);
|
|
}
|
|
} else if(ctx.type == DATA_TAG_DSMR) {
|
|
data = new IEC6205621(payload, tz, &meterConfig);
|
|
}
|
|
len = 0;
|
|
if(data != NULL) {
|
|
if(data->getListType() > 0) {
|
|
validDataReceived = true;
|
|
if(rxBufferErrors > 0) rxBufferErrors--;
|
|
}
|
|
}
|
|
dataAvailable = false;
|
|
return data;
|
|
}
|
|
|
|
int PassiveMeterCommunicator::getLastError() {
|
|
#if defined ESP8266
|
|
if(hwSerial != NULL) {
|
|
if(hwSerial->hasRxError()) {
|
|
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("Serial RX error\n"));
|
|
lastError = 96;
|
|
}
|
|
if(hwSerial->hasOverrun()) {
|
|
rxerr(2);
|
|
}
|
|
} else if(swSerial != NULL) {
|
|
if(swSerial->overflow()) {
|
|
rxerr(2);
|
|
}
|
|
}
|
|
#endif
|
|
return lastError;
|
|
}
|
|
|
|
bool PassiveMeterCommunicator::isConfigChanged() {
|
|
return configChanged;
|
|
}
|
|
|
|
void PassiveMeterCommunicator::getCurrentConfig(MeterConfig& meterConfig) {
|
|
meterConfig = this->meterConfig;
|
|
}
|
|
|
|
|
|
int16_t PassiveMeterCommunicator::unwrapData(uint8_t *buf, DataParserContext &context) {
|
|
int16_t ret = 0;
|
|
bool doRet = false;
|
|
uint16_t end = hanBufferSize;
|
|
uint8_t tag = (*buf);
|
|
uint8_t lastTag = DATA_TAG_NONE;
|
|
while(tag != DATA_TAG_NONE) {
|
|
int16_t curLen = context.length;
|
|
int8_t res = 0;
|
|
switch(tag) {
|
|
case DATA_TAG_HDLC:
|
|
if(hdlcParser == NULL) hdlcParser = new HDLCParser();
|
|
res = hdlcParser->parse(buf, context);
|
|
if(context.length < 3) doRet = true;
|
|
break;
|
|
case DATA_TAG_MBUS:
|
|
if(mbusParser == NULL) mbusParser = new MBUSParser();
|
|
res = mbusParser->parse(buf, context);
|
|
break;
|
|
case DATA_TAG_GBT:
|
|
if(gbtParser == NULL) gbtParser = new GBTParser();
|
|
res = gbtParser->parse(buf, context);
|
|
break;
|
|
case DATA_TAG_GCM:
|
|
if(gcmParser == NULL) gcmParser = new GCMParser(meterConfig.encryptionKey, meterConfig.authenticationKey);
|
|
res = gcmParser->parse(buf, context);
|
|
break;
|
|
case DATA_TAG_LLC:
|
|
if(llcParser == NULL) llcParser = new LLCParser();
|
|
res = llcParser->parse(buf, context);
|
|
break;
|
|
case DATA_TAG_DLMS:
|
|
if(dlmsParser == NULL) dlmsParser = new DLMSParser();
|
|
res = dlmsParser->parse(buf, context);
|
|
if(res >= 0) doRet = true;
|
|
break;
|
|
case DATA_TAG_DSMR:
|
|
if(dsmrParser == NULL) dsmrParser = new DSMRParser();
|
|
res = dsmrParser->parse(buf, context, lastTag != DATA_TAG_NONE);
|
|
if(res >= 0) doRet = true;
|
|
break;
|
|
case DATA_TAG_SNRM:
|
|
case DATA_TAG_AARE:
|
|
case DATA_TAG_RES:
|
|
res = DATA_PARSE_OK;
|
|
doRet = true;
|
|
break;
|
|
default:
|
|
if (debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("Ended up in default case while unwrapping...(tag %02X)\n"), tag);
|
|
return DATA_PARSE_UNKNOWN_DATA;
|
|
}
|
|
lastTag = tag;
|
|
if(res == DATA_PARSE_INCOMPLETE) {
|
|
return res;
|
|
}
|
|
if(context.length > end) {
|
|
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("Context length %lu > %lu:\n"), context.length, end);
|
|
context.type = 0;
|
|
context.length = 0;
|
|
return false;
|
|
}
|
|
switch(tag) {
|
|
case DATA_TAG_HDLC:
|
|
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("HDLC frame:\n"));
|
|
if(pt != NULL) {
|
|
pt->publishBytes(buf, curLen);
|
|
}
|
|
break;
|
|
case DATA_TAG_MBUS:
|
|
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("MBUS frame:\n"));
|
|
if(pt != NULL) {
|
|
pt->publishBytes(buf, curLen);
|
|
}
|
|
break;
|
|
case DATA_TAG_GBT:
|
|
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("GBT frame:\n"));
|
|
break;
|
|
case DATA_TAG_GCM:
|
|
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("GCM frame:\n"));
|
|
break;
|
|
case DATA_TAG_LLC:
|
|
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("LLC frame:\n"));
|
|
break;
|
|
case DATA_TAG_DLMS:
|
|
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("DLMS frame:\n"));
|
|
break;
|
|
case DATA_TAG_DSMR:
|
|
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("DSMR frame:\n"));
|
|
if(pt != NULL) {
|
|
pt->publishString((char*) buf);
|
|
}
|
|
break;
|
|
case DATA_TAG_SNRM:
|
|
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("SNMR frame:\n"));
|
|
break;
|
|
case DATA_TAG_AARE:
|
|
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("AARE frame:\n"));
|
|
break;
|
|
case DATA_TAG_RES:
|
|
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("RES frame:\n"));
|
|
break;
|
|
}
|
|
if(debugger->isActive(RemoteDebug::VERBOSE)) debugPrint(buf, 0, curLen);
|
|
if(res == DATA_PARSE_FINAL_SEGMENT) {
|
|
if(tag == DATA_TAG_MBUS) {
|
|
res = mbusParser->write(buf, context);
|
|
}
|
|
}
|
|
|
|
if(res < 0) {
|
|
return res;
|
|
}
|
|
buf += res;
|
|
end -= res;
|
|
ret += res;
|
|
|
|
// If we are ready to return, do that
|
|
if(doRet) {
|
|
context.type = tag;
|
|
return ret;
|
|
}
|
|
|
|
// Use start byte of new buffer position as tag for next round in loop
|
|
tag = (*buf);
|
|
}
|
|
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("Got to end of unwrap method...\n"));
|
|
return DATA_PARSE_UNKNOWN_DATA;
|
|
}
|
|
|
|
void PassiveMeterCommunicator::debugPrint(byte *buffer, int start, int length) {
|
|
for (int i = start; i < start + length; i++) {
|
|
if (buffer[i] < 0x10)
|
|
debugger->print(F("0"));
|
|
debugger->print(buffer[i], HEX);
|
|
debugger->print(F(" "));
|
|
if ((i - start + 1) % 16 == 0)
|
|
debugger->println(F(""));
|
|
else if ((i - start + 1) % 4 == 0)
|
|
debugger->print(F(" "));
|
|
|
|
yield(); // Let other get some resources too
|
|
}
|
|
debugger->println(F(""));
|
|
}
|
|
|
|
void PassiveMeterCommunicator::printHanReadError(int pos) {
|
|
if(debugger->isActive(RemoteDebug::WARNING)) {
|
|
switch(pos) {
|
|
case DATA_PARSE_BOUNDRY_FLAG_MISSING:
|
|
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Boundry flag missing\n"));
|
|
break;
|
|
case DATA_PARSE_HEADER_CHECKSUM_ERROR:
|
|
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Header checksum error\n"));
|
|
break;
|
|
case DATA_PARSE_FOOTER_CHECKSUM_ERROR:
|
|
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Frame checksum error\n"));
|
|
break;
|
|
case DATA_PARSE_INCOMPLETE:
|
|
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Received frame is incomplete\n"));
|
|
break;
|
|
case GCM_AUTH_FAILED:
|
|
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Decrypt authentication failed\n"));
|
|
break;
|
|
case GCM_ENCRYPTION_KEY_FAILED:
|
|
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Setting decryption key failed\n"));
|
|
break;
|
|
case GCM_DECRYPT_FAILED:
|
|
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Decryption failed\n"));
|
|
break;
|
|
case MBUS_FRAME_LENGTH_NOT_EQUAL:
|
|
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Frame length mismatch\n"));
|
|
break;
|
|
case DATA_PARSE_INTERMEDIATE_SEGMENT:
|
|
if (debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Intermediate segment received\n"));
|
|
break;
|
|
case DATA_PARSE_UNKNOWN_DATA:
|
|
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Unknown data format %02X\n"), hanBuffer[0]);
|
|
break;
|
|
default:
|
|
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Unspecified error while reading data: %d\n"), pos);
|
|
}
|
|
}
|
|
}
|
|
|
|
void PassiveMeterCommunicator::setupHanPort(uint32_t baud, uint8_t parityOrdinal, bool invert) {
|
|
uint8_t pin = meterConfig.rxPin;
|
|
|
|
if (debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(setupHanPort) Setting up HAN on pin %d with baud %d and parity %d\n"), pin, baud, parityOrdinal);
|
|
|
|
if(baud == 0) {
|
|
baud = bauds[meterAutoIndex];
|
|
parityOrdinal = parities[meterAutoIndex];
|
|
invert = inverts[meterAutoIndex];
|
|
}
|
|
if(parityOrdinal == 0) {
|
|
parityOrdinal = 3; // 8N1
|
|
}
|
|
|
|
if(pin == 3 || pin == 113) {
|
|
#if ARDUINO_USB_CDC_ON_BOOT
|
|
hwSerial = &Serial0;
|
|
#else
|
|
hwSerial = &Serial;
|
|
#endif
|
|
}
|
|
|
|
uint8_t uart_num = 0;
|
|
#if defined(ESP32)
|
|
hwSerial = &Serial1;
|
|
uart_num = UART_NUM_1;
|
|
#if defined(CONFIG_IDF_TARGET_ESP32)
|
|
if(pin == 16) {
|
|
hwSerial = &Serial2;
|
|
uart_num = UART_NUM_2;
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
if(pin == 0) {
|
|
if (debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("Invalid GPIO configured for HAN\n"));
|
|
return;
|
|
}
|
|
|
|
if(meterConfig.bufferSize < 1) meterConfig.bufferSize = 1;
|
|
if(meterConfig.bufferSize > 64) meterConfig.bufferSize = 64;
|
|
|
|
if(hwSerial != NULL) {
|
|
if (debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Hardware serial\n"));
|
|
Serial.flush();
|
|
#if defined(ESP8266)
|
|
SerialConfig serialConfig;
|
|
#elif defined(ESP32)
|
|
uint32_t serialConfig;
|
|
#endif
|
|
switch(parityOrdinal) {
|
|
case 2:
|
|
serialConfig = SERIAL_7N1;
|
|
break;
|
|
case 3:
|
|
serialConfig = SERIAL_8N1;
|
|
break;
|
|
case 10:
|
|
serialConfig = SERIAL_7E1;
|
|
break;
|
|
default:
|
|
serialConfig = SERIAL_8E1;
|
|
break;
|
|
}
|
|
if(meterConfig.bufferSize < 4) meterConfig.bufferSize = 4; // 64 bytes (1) is default for software serial, 256 bytes (4) for hardware
|
|
|
|
hwSerial->setRxBufferSize(64 * meterConfig.bufferSize);
|
|
#if defined(ESP32)
|
|
hwSerial->begin(baud, serialConfig, -1, -1, invert);
|
|
uart_set_pin(uart_num, -1, pin, -1, -1);
|
|
#else
|
|
hwSerial->begin(baud, serialConfig, SERIAL_FULL, 1, invert);
|
|
#endif
|
|
|
|
#if defined(ESP8266)
|
|
if(pin == 3) {
|
|
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Switching UART0 to pin 1 & 3\n"));
|
|
Serial.pins(1,3);
|
|
} else if(pin == 113) {
|
|
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Switching UART0 to pin 15 & 13\n"));
|
|
Serial.pins(15,13);
|
|
}
|
|
#endif
|
|
|
|
// Prevent pullup on TX pin if not uart0
|
|
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3)
|
|
pinMode(17, INPUT);
|
|
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
|
|
pinMode(7, INPUT);
|
|
#elif defined(ESP32)
|
|
if(pin == 9) {
|
|
pinMode(10, INPUT);
|
|
} else if(pin == 16) {
|
|
pinMode(17, INPUT);
|
|
}
|
|
#elif defined(ESP8266)
|
|
if(pin == 113) {
|
|
pinMode(15, INPUT);
|
|
}
|
|
#endif
|
|
|
|
hanSerial = hwSerial;
|
|
#if defined(ESP8266)
|
|
if(swSerial != NULL) {
|
|
swSerial->end();
|
|
delete swSerial;
|
|
swSerial = NULL;
|
|
}
|
|
#endif
|
|
} else {
|
|
#if defined(ESP8266)
|
|
if (debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Software serial\n"));
|
|
Serial.flush();
|
|
|
|
if(swSerial == NULL) {
|
|
swSerial = new SoftwareSerial(pin, -1, invert);
|
|
} else {
|
|
swSerial->end();
|
|
}
|
|
|
|
SoftwareSerialConfig serialConfig;
|
|
switch(parityOrdinal) {
|
|
case 2:
|
|
serialConfig = SWSERIAL_7N1;
|
|
break;
|
|
case 3:
|
|
serialConfig = SWSERIAL_8N1;
|
|
break;
|
|
case 10:
|
|
serialConfig = SWSERIAL_7E1;
|
|
break;
|
|
default:
|
|
serialConfig = SWSERIAL_8E1;
|
|
break;
|
|
}
|
|
|
|
uint8_t bufferSize = meterConfig.bufferSize;
|
|
#if defined(ESP8266)
|
|
if(bufferSize > 2) bufferSize = 2;
|
|
#endif
|
|
if (debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Using serial buffer size %d"), 64 * bufferSize);
|
|
swSerial->begin(baud, serialConfig, pin, -1, invert, meterConfig.bufferSize * 64);
|
|
hanSerial = swSerial;
|
|
|
|
Serial.end();
|
|
Serial.begin(115200);
|
|
hwSerial = NULL;
|
|
#else
|
|
if (debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Software serial not available\n"));
|
|
return;
|
|
#endif
|
|
}
|
|
|
|
if(hanBuffer != NULL) {
|
|
free(hanBuffer);
|
|
}
|
|
hanBufferSize = max(64 * meterConfig.bufferSize * 2, 512);
|
|
hanBuffer = (uint8_t*) malloc(hanBufferSize);
|
|
|
|
// The library automatically sets the pullup in Serial.begin()
|
|
if(!meterConfig.rxPinPullup) {
|
|
if (debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("HAN pin pullup disabled\n"));
|
|
pinMode(meterConfig.rxPin, INPUT);
|
|
}
|
|
|
|
hanSerial->setTimeout(250);
|
|
|
|
// Empty buffer before starting
|
|
while (hanSerial->available() > 0) {
|
|
hanSerial->read();
|
|
}
|
|
#if defined(ESP8266)
|
|
if(hwSerial != NULL) {
|
|
hwSerial->hasOverrun();
|
|
} else if(swSerial != NULL) {
|
|
swSerial->overflow();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
HardwareSerial* PassiveMeterCommunicator::getHwSerial() {
|
|
return hwSerial;
|
|
}
|
|
|
|
void PassiveMeterCommunicator::rxerr(int err) {
|
|
if(err == 0) return;
|
|
switch(err) {
|
|
case 2:
|
|
if (debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("Serial buffer overflow\n"));
|
|
rxBufferErrors++;
|
|
if(rxBufferErrors > 3 && meterConfig.bufferSize < 64) {
|
|
meterConfig.bufferSize += 2;
|
|
if (debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Increasing RX buffer to %d bytes\n"), meterConfig.bufferSize * 64);
|
|
configChanged = true;
|
|
rxBufferErrors = 0;
|
|
}
|
|
break;
|
|
case 3:
|
|
if (debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("Serial FIFO overflow\n"));
|
|
break;
|
|
case 4:
|
|
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Serial frame error\n"));
|
|
break;
|
|
case 5:
|
|
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Serial parity error\n"));
|
|
break;
|
|
}
|
|
// Do not include serial break
|
|
if(err > 1) lastError = 90+err;
|
|
}
|
|
|
|
void PassiveMeterCommunicator::handleAutodetect(unsigned long now) {
|
|
if(!autodetect) return;
|
|
|
|
if(!validDataReceived) {
|
|
if(now - meterAutodetectLastChange > 20000 && (meterConfig.baud == 0 || meterConfig.parity == 0)) {
|
|
autodetect = true;
|
|
meterAutoIndex++; // Default is to try the first one in setup()
|
|
if (debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Meter serial autodetect, swapping to: %d, %d, %s\n"), bauds[meterAutoIndex], parities[meterAutoIndex], inverts[meterAutoIndex] ? "true" : "false");
|
|
if(meterAutoIndex >= 6) meterAutoIndex = 0;
|
|
setupHanPort(bauds[meterAutoIndex], parities[meterAutoIndex], inverts[meterAutoIndex]);
|
|
meterAutodetectLastChange = now;
|
|
}
|
|
} else if(autodetect) {
|
|
if (debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Meter serial autodetected, saving: %d, %d, %s\n"), bauds[meterAutoIndex], parities[meterAutoIndex], inverts[meterAutoIndex] ? "true" : "false");
|
|
autodetect = false;
|
|
meterConfig.baud = bauds[meterAutoIndex];
|
|
meterConfig.parity = parities[meterAutoIndex];
|
|
meterConfig.invert = inverts[meterAutoIndex];
|
|
configChanged = true;
|
|
setupHanPort(meterConfig.baud, meterConfig.parity, meterConfig.invert);
|
|
}
|
|
}
|