/** * @copyright Utilitech AS 2023 * License: Fair Source * */ #include "KamstrupPullCommunicator.h" #include "Uptime.h" #include "Cosem.h" #include "hexutils.h" #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) #include #endif void KamstrupPullCommunicator::configure(MeterConfig& meterConfig, Timezone* tz) { this->meterConfig = meterConfig; this->configChanged = false; this->tz = tz; setupHanPort(meterConfig.baud, meterConfig.parity, meterConfig.invert); } bool KamstrupPullCommunicator::loop() { uint64_t now = millis64(); if(PassiveMeterCommunicator::loop() || now-lastLoop > 5000) { if (debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("State: %d\n"), state); lastLoop = now; switch(state) { case STATE_DISCONNECTED: sendConnectMessage(); lastMessageTime = now; break; case STATE_CONNECTING: if(!checkForConnectConfirmed() && now-lastMessageTime > 10000) { state = STATE_DISCONNECTED; lastLoop = 0; } break; case STATE_CONNECTED_NOT_ASSOCIATED: sendAssociateMessage(); lastMessageTime = now; break; case STATE_CONNECTED_ASSOCIATING: if(!checkForAssociationConfirmed() && now-lastMessageTime > 10000) { state = STATE_CONNECTION_BROKEN; // TODO: Use state: Broken lastLoop = 0; } break; case STATE_CONNECTED_ASSOCIATED: if(dataAvailable) { if (debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Data is available: %lu\n"), ctx.length); return true; } else { lastMessageTime = now; requestData(); } break; case STATE_DISCONNECT: case STATE_CONNECTION_BROKEN: sendDisconnectMessage(); lastMessageTime = now; break; case STATE_DISCONNECTING: if(!checkForDisconnectMessage() && now-lastMessageTime > 10000) { state = STATE_DISCONNECTED; lastLoop = 0; } break; default: state = STATE_DISCONNECTED; } } return false; } void KamstrupPullCommunicator::setupHanPort(uint32_t baud, uint8_t parityOrdinal, bool invert) { uint8_t rxPin = meterConfig.rxPin; uint8_t txPin = meterConfig.txPin; if (debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(setupHanPort) Setting up HAN on pin %d/%d with baud %d and parity %d\n"), rxPin, txPin, baud, parityOrdinal); if(rxPin == 3 || rxPin == 113) { #if ARDUINO_USB_CDC_ON_BOOT hwSerial = &Serial0; #else hwSerial = &Serial; #endif } #if defined(ESP32) if(rxPin == 9) { hwSerial = &Serial1; } #if defined(CONFIG_IDF_TARGET_ESP32) if(rxPin == 16) { hwSerial = &Serial2; } #elif defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) hwSerial = &Serial1; #endif #endif if(rxPin == 0) { if (debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("Invalid GPIO configured for HAN RX\n")); return; } if(txPin == 0) { if (debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("Invalid GPIO configured for HAN TX\n")); return; } if(meterConfig.bufferSize < 1) meterConfig.bufferSize = 1; 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(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) hwSerial->begin(baud, serialConfig, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, invert); uart_set_pin(UART_NUM_1, txPin == 0xFF ? UART_PIN_NO_CHANGE : txPin, rxPin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); #elif defined(ESP32) hwSerial->begin(baud, serialConfig, -1, -1, invert); #else hwSerial->begin(baud, serialConfig, SERIAL_FULL, 1, invert); #endif #if defined(ESP8266) if(rxPin == 3) { if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Switching UART0 to pin 1 & 3\n")); Serial.pins(1,3); } else if(rxPin == 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) if(txPin != 17) pinMode(17, INPUT); #elif defined(CONFIG_IDF_TARGET_ESP32C3) if(txPin != 7) pinMode(7, INPUT); #elif defined(ESP32) if(rxPin == 9) { if(txPin != 10) pinMode(10, INPUT); } else if(rxPin == 16) { if(txPin != 17) pinMode(17, INPUT); } #elif defined(ESP8266) if(rxPin == 113) { if(txPin != 15) pinMode(15, INPUT); } #endif hanSerial = hwSerial; if(swSerial != NULL) { swSerial->end(); delete swSerial; swSerial = NULL; } } else { if (debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Software serial\n")); Serial.flush(); if(swSerial == NULL) { swSerial = new SoftwareSerial(rxPin, txPin == 0xFF ? -1 : txPin, 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; } swSerial->begin(baud, serialConfig, rxPin, txPin == 0xFF ? -1 : txPin, invert, meterConfig.bufferSize * 64); hanSerial = swSerial; Serial.end(); Serial.begin(115200); hwSerial = NULL; } // 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 } // 7E A0 15 21 03 52 5D 8A E6 E7 00 C4 01 81 00 06 00 BC 61 4F E4 36 7E AmsData* KamstrupPullCommunicator::getData(AmsData& meterState) { if(!dataAvailable) return NULL; if(ctx.length > BUF_SIZE_HAN) { debugger->printf_P(PSTR("Invalid context length %lu\n"), ctx.length); dataAvailable = false; return NULL; } if(hanBuffer[5] == 0x51) { if(debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Request was denied\n")); len = 0; dataAvailable = false; state = STATE_CONNECTION_BROKEN; return NULL; } byte* payload = ((byte *) (hanBuffer)) + pos; if(debugger->isActive(RemoteDebug::INFO)) { debugger->printf_P(PSTR("Received data from Kamstrup meter:\n")); debugPrint(payload, 0, min(ctx.length, (uint16_t) BUF_SIZE_HAN), debugger); } if(hanBuffer[11] == DATA_TAG_RES) { if(obisPosition == 1) { // Version string debugger->printf_P(PSTR("RECEIVED Firmware version\n")); } else if(obisPosition == 1) { // Meter model string debugger->printf_P(PSTR("RECEIVED Meter model\n")); } else { // All other uint32 uint32_t value = ntohl(*((uint32_t*) (hanBuffer + 16))); debugger->printf_P(PSTR("RECEIVED DATA FOR position %d, value: %lu\n"), obisPosition, value); meterState.apply(currentObis, value / 1.0); } len = 0; dataAvailable = false; return NULL; } else { return PassiveMeterCommunicator::getData(meterState); } } void KamstrupPullCommunicator::sendConnectMessage() { uint8_t i = 3; // Leave 3 bytes for header hanBuffer[i++] = serverSap; // Destination address hanBuffer[i++] = clientSap; // Source address hanBuffer[i++] = 0x93; // Control i += 2; // Leave 2 bytes for header checksum hanBuffer[i++] = 0x81; // Format identifier hanBuffer[i++] = 0x80; // Format group uint8_t glPos = i++; // Position where we should write group length uint8_t glLen = 0; // Actual group length ConnectParameter2b txMax = { 0x5, 0x2, htons(0x200) }; memcpy(hanBuffer+i, &txMax, txMax.length+2); i += txMax.length+2; glLen += txMax.length+2; ConnectParameter2b rxMax = { 0x6, 0x2, htons(0x200) }; memcpy(hanBuffer+i, &rxMax, rxMax.length+2); i += rxMax.length+2; glLen += rxMax.length+2; ConnectParameter4b txWin = { 0x7, 0x4, htonl(0x1) }; memcpy(hanBuffer+i, &txWin, txWin.length+2); i += txWin.length+2; glLen += txWin.length+2; ConnectParameter4b rxWin = { 0x8, 0x4, htonl(0x1) }; memcpy(hanBuffer+i, &rxWin, rxWin.length+2); i += rxWin.length+2; glLen += rxWin.length+2; hanBuffer[glPos] = glLen; HDLCHeader head = { HDLC_FLAG, htons(0xA000 | (i+1)) }; memcpy(hanBuffer, &head, 3); HDLC3CtrlHcs ch = { 0x93, htons(crc16_x25(hanBuffer+1, 5)) }; memcpy(hanBuffer+5, &ch, 3); HDLCFooter foot = { htons(crc16_x25(hanBuffer+1, i-1)), HDLC_FLAG }; memcpy(hanBuffer+i, &foot, 3); i += 3; for(int x = i; xwrite(hanBuffer, i); hanSerial->flush(); if(debugger->isActive(RemoteDebug::INFO)) { debugger->printf_P(PSTR("Sending data to Kamstrup meter:\n")); debugPrint(hanBuffer, 0, i, debugger); } state = STATE_CONNECTING; len = 0; dataAvailable = false; } bool KamstrupPullCommunicator::checkForConnectConfirmed() { if(!dataAvailable) return false; if(ctx.length > BUF_SIZE_HAN) { debugger->printf_P(PSTR("Invalid context length\n")); dataAvailable = false; return NULL; } byte* payload = ((byte *) (hanBuffer)) + pos; if(debugger->isActive(RemoteDebug::INFO)) { debugger->printf_P(PSTR("Received data from Kamstrup meter:\n")); debugPrint(payload, 0, min(ctx.length, (uint16_t) BUF_SIZE_HAN), debugger); } len = 0; dataAvailable = false; lastMessageTime = 0; // 7E A0 20 21 03 73 73 98 81 80 14 05 02 02 00 06 02 02 00 07 04 00 00 00 01 08 04 00 00 00 01 6F EF 7E // 7E A0 20 21 03 73 73 98 81 80 14 05 02 02 00 06 02 00 80 07 04 00 00 00 01 08 04 00 00 00 01 19 D4 7E if(payload[0] == 0x81 && payload[1] == 0x80) { state = STATE_CONNECTED_NOT_ASSOCIATED; return true; } else { state = STATE_CONNECTION_BROKEN; return false; } } // TA: Tag // LE: Legth // RA: Response allowed // PQ: Proposed QoS // PV: Proposed DLMS version // CO: Conformance // AT: Application tag // LC: Length of content field // LU: Number of unused bits in the final octet // MP: Max PDU size // DK: Dedicated key, use (0x01), length (0xXX) and data // AC: encoding of the tag of the xDLMS APDU CHOICE (InitiateRequest) // FF: Fixed // BE: Ber Object Identifier, 0x06=Object, 0x80=Context, 0x20=Constructed, 0x12=Calling auth, 0x40=Application, 0x04=String // AN: Application context name tag // NR: Name referencing, 1d=LN unciphered, 2d=unciphered, 3d=LN ciphered, 4d=cihered // CA: Calling-AP-title // CU: Calling Authentication // SR: Sender requirements // No authentication // 7E A0 2B 03 21 10 FB AF // E6 E6 00 // TA LE AN LE BO LE FF FF FF FF FF FF NR // 60 1D A1 09 06 07 60 85 74 05 08 01 01 // CA LE OS LE AC DK RA PQ PV AT AT LC LU CO CO CO MP MP // BE 10 04 0E 01 00 00 00 06 5F 1F 04 00 00 18 1D FF FF // 5F AF 7E // With authentication // 7E A0 41 21 25 10 52 3B // E6 E6 00 // BE LE AN LE // 60 33 A1 09 // 06=CALLING_AP_TITLE // BE LE FF FF FF FF FF FF NR // 06 07 60 85 74 05 08 01 01 // 8A=SENDER_ACSE_REQUIREMENTS (BE 0x80 + 0x0A) // BE LE FF FF // 8A 02 07 80 // 8B=MECHANISM_NAME (BE 0x80 + 0x0B) // BE LE FF FF FF FF FF FF NR // 8B 07 60 85 74 05 08 02 01 // AC=CALLING_AUTHENTICATION_VALUE (BE 0x80 + BE 0x20 + 0x0C) // BE LE BE LE -- Password -- // AC 07 80 05 31 32 33 34 35 // BE=USER_INFORMATION (BE 0x80 + BE 0x20 + 0x1E) // CA LE BE LE AC DK RA PQ PV AT AT LC LU CO CO CO MP MP // BE 10 04 0E 01 00 00 00 06 5F 1F 04 00 00 FE 1F FF FF // 0C FF 7E void KamstrupPullCommunicator::sendAssociateMessage() { bool usePsk = !passkey.isEmpty(); uint8_t i = 3; // Leave 3 bytes for header hanBuffer[i++] = serverSap; // Destination address hanBuffer[i++] = clientSap; // Source address hanBuffer[i++] = 0x10; // Control i += 2; // Leave 2 bytes for header checksum hanBuffer[i++] = 0xE6; // LLC dst hanBuffer[i++] = 0xE6; // LLC src hanBuffer[i++] = 0x00; // LLC control hanBuffer[i++] = DATA_TAG_AARQ; uint8_t aarqLenIdx = i++; // length placeholder hanBuffer[i++] = 0xA1; hanBuffer[i++] = 0x09; // Length hanBuffer[i++] = 0x06; // CALLING_AP_TITLE hanBuffer[i++] = 0x07; // Length hanBuffer[i++] = 0x60; // Fixed data hanBuffer[i++] = 0x85; // Fixed data hanBuffer[i++] = 0x74; // Fixed data hanBuffer[i++] = 0x05; // Fixed data hanBuffer[i++] = 0x08; // Fixed data hanBuffer[i++] = 0x01; // Fixed data hanBuffer[i++] = 0x01; // Name referencing, 1d=LN unciphered, 2d=unciphered, 3d=LN ciphered, 4d=cihered if(usePsk) { hanBuffer[i++] = 0x8A; // SENDER_ACSE_REQUIREMENTS (BE 0x80 + 0x0A) hanBuffer[i++] = 0x02; // Length hanBuffer[i++] = 0x07; // Data hanBuffer[i++] = 0x80; // Data hanBuffer[i++] = 0x8B; // MECHANISM_NAME (BE 0x80 + 0x0B) hanBuffer[i++] = 0x07; // Length hanBuffer[i++] = 0x60; // Fixed data hanBuffer[i++] = 0x85; // Fixed data hanBuffer[i++] = 0x74; // Fixed data hanBuffer[i++] = 0x05; // Fixed data hanBuffer[i++] = 0x08; // Fixed data hanBuffer[i++] = 0x02; // Fixed data hanBuffer[i++] = 0x01; // Name referencing, 1d=LN unciphered, 2d=unciphered, 3d=LN ciphered, 4d=cihered hanBuffer[i++] = 0xAC; // CALLING_AUTHENTICATION_VALUE (BE 0x80 + BE 0x20 + 0x0C) hanBuffer[i++] = passkey.length() + 2; // Length hanBuffer[i++] = 0x80; // Ber Context hanBuffer[i++] = passkey.length(); // Length const char* key = passkey.c_str(); for(uint8_t x = 0; x < passkey.length(); x++) { hanBuffer[i++] = key[x]; } } hanBuffer[i++] = 0xBE; // USER_INFORMATION (BE 0x80 + BE 0x20 + 0x1E) hanBuffer[i++] = 0x10; // Length hanBuffer[i++] = 0x04; // CALLED_AP_INVOCATION_ID hanBuffer[i++] = 0x0E; // Length hanBuffer[i++] = 0x01; // encoding of the tag of the xDLMS APDU CHOICE (InitiateRequest) hanBuffer[i++] = 0x00; // Dedicated key, use (0x01), length (0xXX) and data hanBuffer[i++] = 0x00; // Response allowed hanBuffer[i++] = 0x00; // Proposed QoS hanBuffer[i++] = 0x06; // Proposed DLMS version hanBuffer[i++] = 0x5F; // hanBuffer[i++] = 0x1F; // hanBuffer[i++] = 0x04; // Length hanBuffer[i++] = 0x00; // Number of unused bits hanBuffer[i++] = 0x00; // Conformance if(usePsk) { hanBuffer[i++] = 0xFE; // Conformance hanBuffer[i++] = 0x1F; // Conformance } else { hanBuffer[i++] = 0x18; // Conformance hanBuffer[i++] = 0x1D; // Conformance } hanBuffer[i++] = 0xFF; // Max PDU size hanBuffer[i++] = 0xFF; // Max PDU size hanBuffer[aarqLenIdx] = i-aarqLenIdx-1; HDLCHeader head = { HDLC_FLAG, htons(0xA000 | (i+1)) }; memcpy(hanBuffer, &head, 3); HDLC3CtrlHcs ch = { 0x10, htons(crc16_x25(hanBuffer+1, 5)) }; memcpy(hanBuffer+5, &ch, 3); HDLCFooter foot = { htons(crc16_x25(hanBuffer+1, i-1)), HDLC_FLAG }; memcpy(hanBuffer+i, &foot, 3); i += 3; for(int x = i; xwrite(hanBuffer, i); hanSerial->flush(); if(debugger->isActive(RemoteDebug::INFO)) { debugger->printf_P(PSTR("Sending data to Kamstrup meter:\n")); debugPrint(hanBuffer, 0, i, debugger); } state = STATE_CONNECTED_ASSOCIATING; len = 0; dataAvailable = false; } bool KamstrupPullCommunicator::checkForAssociationConfirmed() { if(!dataAvailable) return false; if(ctx.length > BUF_SIZE_HAN) { debugger->printf_P(PSTR("Invalid context length\n")); dataAvailable = false; return NULL; } byte* payload = ((byte *) (hanBuffer)) + pos; if(debugger->isActive(RemoteDebug::INFO)) { debugger->printf_P(PSTR("Received data from Kamstrup meter:\n")); debugPrint(payload, 0, ctx.length, debugger); } len = 0; dataAvailable = false; lastMessageTime = 0; if(payload[0] == DATA_TAG_AARE) { state = STATE_CONNECTED_ASSOCIATED; return true; } else { state = STATE_CONNECTION_BROKEN; return false; } return false; } // 7E A0 19 03 21 32 6F D8 E6 E6 00 C0 01 81 00 01 01 01 00 00 01 FF 02 00 A8 E3 7E // 7E A0 19 21 25 32 8C 09 E6 E6 00 C0 01 81 00 01 01 01 60 01 01 FF 02 00 5D 6F 7E // 7E A0 19 21 25 32 8C 09 E6 E6 00 C0 01 C1 00 03 01 01 20 07 00 FF 02 00 38 36 7E // 7E A0 4C 21 25 32 CD B2 E6 E6 00 C0 01 81 00 // 07 - Class // 01 01 63 01 00 FF - OBIS // 02 - Attribute number 2 // 01 01 - Array 1 // 02 04 - Struct 4 // 02 04 - Struct 4 // 12 00 08 - uint16 // 09 06 00 01 01 00 00 FF - OBIS // 0F 02 - int8 // 12 00 00 - uint16 // 09 0C 07 DD 0A 19 FF 00 00 00 00 80 00 80 - from date // 09 0C 07 DD 0A 1A FF 00 00 00 00 80 00 80 - to date // 01 00 - Empty array // 2F 84 7E bool KamstrupPullCommunicator::requestData() { bool usePsk = !passkey.isEmpty(); uint8_t i = 3; // Leave 3 bytes for header hanBuffer[i++] = serverSap; // Destination address hanBuffer[i++] = clientSap; // Source address hanBuffer[i++] = 0x32; // Control i += 2; // Leave 2 bytes for header checksum hanBuffer[i++] = 0xE6; // LLC dst hanBuffer[i++] = 0xE6; // LLC src hanBuffer[i++] = 0x00; // LLC control hanBuffer[i++] = 0xC0; // Get Request hanBuffer[i++] = 0x01; // Type, 01 = Normal hanBuffer[i++] = 0x81; // Invoke ID and priority hanBuffer[i++] = 0x00; hanBuffer[i++] = ++obisPosition < 3 ? 0x01 : 0x03; // Class ID OBIS_t obis = {1,1,OBIS_NULL,OBIS_RANGE_NA}; switch(obisPosition) { case 1: obis.code = OBIS_FIRMWARE_VERSION; break; case 2: obis.code = OBIS_METER_MODEL; break; case 3: obis.code = OBIS_METER_ID; break; case 4: obis.code = OBIS_ACTIVE_IMPORT; break; case 5: obis.code = OBIS_REACTIVE_IMPORT; break; case 6: obis.code = OBIS_ACTIVE_EXPORT; break; case 7: obis.code = OBIS_REACTIVE_EXPORT; break; default: obisPosition = 0; return false; } memcpy(hanBuffer+i, &obis, sizeof(obis)); i += sizeof(obis); currentObis = obis.code; hanBuffer[i++] = 0x02; // Attribute number hanBuffer[i++] = 0x00; HDLCHeader head = { HDLC_FLAG, htons(0xA000 | (i+1)) }; memcpy(hanBuffer, &head, 3); HDLC3CtrlHcs ch = { 0x32, htons(crc16_x25(hanBuffer+1, 5)) }; memcpy(hanBuffer+5, &ch, 3); HDLCFooter foot = { htons(crc16_x25(hanBuffer+1, i-1)), HDLC_FLAG }; memcpy(hanBuffer+i, &foot, 3); i += 3; for(int x = i; xwrite(hanBuffer, i); hanSerial->flush(); if(debugger->isActive(RemoteDebug::INFO)) { debugger->printf_P(PSTR("Sending data to Kamstrup meter:\n")); debugPrint(hanBuffer, 0, i, debugger); } len = 0; dataAvailable = false; return true; } //7E A0 07 03 21 53 03 C7 7E void KamstrupPullCommunicator::sendDisconnectMessage() { uint8_t i = 3; // Leave 3 bytes for header hanBuffer[i++] = serverSap; // Destination address hanBuffer[i++] = clientSap; // Source address hanBuffer[i++] = 0x53; // Control HDLCHeader head = { HDLC_FLAG, htons(0xA000 | (i+1)) }; memcpy(hanBuffer, &head, 3); HDLCFooter foot = { htons(crc16_x25(hanBuffer+1, i-1)), HDLC_FLAG }; memcpy(hanBuffer+i, &foot, 3); i += 3; for(int x = i; xwrite(hanBuffer, i); hanSerial->flush(); if(debugger->isActive(RemoteDebug::INFO)) { debugger->printf_P(PSTR("Sending data to Kamstrup meter:\n")); debugPrint(hanBuffer, 0, i, debugger); } state = STATE_DISCONNECTING; len = 0; dataAvailable = false; } // 7E A0 07 21 03 73 01 40 7E bool KamstrupPullCommunicator::checkForDisconnectMessage() { if(!dataAvailable) return false; if(ctx.length > BUF_SIZE_HAN) { debugger->printf_P(PSTR("Invalid context length\n")); dataAvailable = false; return NULL; } if(debugger->isActive(RemoteDebug::INFO)) { debugger->printf_P(PSTR("Received data from Kamstrup meter:\n")); debugPrint(hanBuffer, 0, ctx.length, debugger); } for(int i = 0; ihasRxError()) { if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("Serial RX error\n")); lastError = 96; } if(hwSerial->hasOverrun()) { rxerr(2); } #endif } else if(swSerial != NULL) { if(swSerial->overflow()) { rxerr(2); } } return lastError; } bool KamstrupPullCommunicator::isConfigChanged() { return configChanged; } void KamstrupPullCommunicator::getCurrentConfig(MeterConfig& meterConfig) { meterConfig = this->meterConfig; } void KamstrupPullCommunicator::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; }