FKW9 / esp-smartmeter-netznoe

Read Smartmeter Data and store to SD Card or publish to Graphite
MIT License
14 stars 4 forks source link

Code auf Arduino verwenden #8

Closed EIKSEU closed 1 year ago

EIKSEU commented 1 year ago

Hallo,

versuche den Code auf einem Arduino zu verwenden kannst du mir dabei helfen ich hänge beim entschlüsseln.....

Mfg

EIKSEU commented 1 year ago

`

include

include

include

include

include "dlms.h"

include "obis.h"

include "key.h"

GCM *gcmaes128 = 0;

// Variables for DLMS decoding uint32_t last_read = 0; // Timestamp when data was last read uint16_t receive_buffer_index = 0; // Current position in the receive buffer uint8_t receive_buffer[RECEIVE_BUFFER_SIZE]; // Stores the received data

// Function prototypes uint32_t swap_uint32(uint32_t val); uint16_t swap_uint16(uint16_t val); void serial_dump();

// SETUP void setup() { // Debug port Serial.begin(115200);

// MBus input
Serial1.begin(2400, SERIAL_8E1);

// Serial1.txBufferSize(RECEIVE_BUFFER_SIZE); gcmaes128 = new GCM(); Serial1.setTimeout(2); Serial.print("\nWarte auf Smartmeter Daten..."); delay(2000); }

// MAIN LOOP void loop() { uint32_t current_time = millis(); // Read while data is available while (Serial1.available()) { if (receive_buffer_index >= RECEIVE_BUFFER_SIZE) { Serial.println("Buffer overflow!"); receive_buffer_index = 0; return; }

    receive_buffer[receive_buffer_index++] = Serial1.read();

    last_read = current_time;
}
  static unsigned long previous_millis = 0;

    Serial.println(receive_buffer_index);

    uint16_t payload_length = 243;
    uint16_t payload_length_msg1 = 228;
    uint16_t payload_length_msg2 = payload_length - payload_length_msg1;

    uint8_t iv[12]; // Initialization vector

    memcpy(&iv[0], &receive_buffer[DLMS_SYST_OFFSET], DLMS_SYST_LENGTH); // Copy system title to IV
    memcpy(&iv[8], &receive_buffer[DLMS_IC_OFFSET], DLMS_IC_LENGTH);     // Copy invocation counter to IV

    uint8_t ciphertext[payload_length];
    memcpy(&ciphertext[0], &receive_buffer[DLMS_HEADER1_LENGTH], payload_length_msg1);
    memcpy(&ciphertext[payload_length_msg1], &receive_buffer[DLMS_HEADER2_OFFSET + DLMS_HEADER2_LENGTH], payload_length_msg2);

    // Start decrypting
    uint8_t plaintext[payload_length];

    gcmaes128->setKey(key, gcmaes128->keySize());
    gcmaes128->setIV(iv, 12);
    gcmaes128->decrypt(plaintext, ciphertext, payload_length);

    // Decode data
    uint16_t current_position = DECODER_START_OFFSET;
    meterData meter_data;

        if (plaintext[current_position + OBIS_TYPE_OFFSET] != DATA_OCTET_STRING)
        {
            Serial.println("Unsupported OBIS header type!");
            receive_buffer_index = 0;
            return;
        }

        uint8_t data_length = plaintext[current_position + OBIS_LENGTH_OFFSET];

        if (data_length != 0x06)
        {
            // read timestamp
            if ((data_length == 0x0C) && (current_position == DECODER_START_OFFSET))
            {
                uint8_t dateTime[data_length];
                memcpy(&dateTime[0], &plaintext[current_position + 2], data_length);

                uint16_t year;
                uint8_t month, day, hour, minute, second;

                year = (plaintext[current_position + 2] << 8) + plaintext[current_position + 3];
                month = plaintext[current_position + 4];
                day = plaintext[current_position + 5];
                hour = plaintext[current_position + 7];
                minute = plaintext[current_position + 8];
                second = plaintext[current_position + 9];

                sprintf(meter_data.timestamp_str, "%02u.%02u.%04u %02u:%02u:%02u", day, month, year, hour, minute, second);

                meter_data.timestamp_unix = -1;

                // COMMENTED OUT BECAUSE I DONT WANT THE PAIN CONVERSION WITH TIMEZONZES
                // JUST USE -1 TO USE TIME OF ARRIVAL ON GRAPHITE HOST
                //
                // convert to unix timestamp for graphite
                // struct tm tm;
                // if (strptime(meter_data.timestamp_str, "%d.%m.%Y %H:%M:%S", &tm) != NULL)
                // {
                //// TODO: detect time zone summer time/winter time
                //     meter_data.timestamp_unix = mktime(&tm) - 7200;
                //     Serial.print("Unix Time: ");
                //     Serial.println(meter_data.timestamp_unix);
                // }
                // else
                // {
                //     Serial.println("Invalid Timestamp");
                //     receive_buffer_index = 0;
                //     return;
                // }

                Serial.print("Timestamp: ");
                Serial.println(meter_data.timestamp_str);

                current_position = 34;
                data_length = plaintext[current_position + OBIS_LENGTH_OFFSET];
            }
            else if ((data_length == 0x0C) && (current_position > 225))
            {
                uint8_t meterNumber[data_length];
                memcpy(&meterNumber[0], &plaintext[current_position + 2], data_length);

                // THIS IS THE END OF THE PACKET
              //  break;
            }
            else
            {
                Serial.println("Unsupported OBIS header length");
                receive_buffer_index = 0;
                return;
            }

        uint8_t obis_code[data_length];
        memcpy(&obis_code[0], &plaintext[current_position + OBIS_CODE_OFFSET], data_length); // Copy OBIS code to array

        current_position += data_length + 2; // Advance past code, position and type

        uint8_t obis_data_type = plaintext[current_position];
        current_position++; // Advance past data type

        uint8_t obis_data_length = 0x00;
        uint8_t code_type = TYPE_UNKNOWN;

        if (obis_code[OBIS_A] == 0x01)
        {
            // Compare C and D against code
            if (memcmp(&obis_code[OBIS_C], OBIS_VOLTAGE_L1, 2) == 0)
            {
                code_type = TYPE_VOLTAGE_L1;
            }
            else if (memcmp(&obis_code[OBIS_C], OBIS_VOLTAGE_L2, 2) == 0)
            {
                code_type = TYPE_VOLTAGE_L2;
            }
            else if (memcmp(&obis_code[OBIS_C], OBIS_VOLTAGE_L3, 2) == 0)
            {
                code_type = TYPE_VOLTAGE_L3;
            }

            else if (memcmp(&obis_code[OBIS_C], OBIS_CURRENT_L1, 2) == 0)
            {
                code_type = TYPE_CURRENT_L1;
            }
            else if (memcmp(&obis_code[OBIS_C], OBIS_CURRENT_L2, 2) == 0)
            {
                code_type = TYPE_CURRENT_L2;
            }
            else if (memcmp(&obis_code[OBIS_C], OBIS_CURRENT_L3, 2) == 0)
            {
                code_type = TYPE_CURRENT_L3;
            }

            else if (memcmp(&obis_code[OBIS_C], OBIS_ACTIVE_POWER_PLUS, 2) == 0)
            {
                code_type = TYPE_ACTIVE_POWER_PLUS;
            }
            else if (memcmp(&obis_code[OBIS_C], OBIS_ACTIVE_POWER_MINUS, 2) == 0)
            {
                code_type = TYPE_ACTIVE_POWER_MINUS;
            }

            else if (memcmp(&obis_code[OBIS_C], OBIS_ACTIVE_ENERGY_PLUS, 2) == 0)
            {
                code_type = TYPE_ACTIVE_ENERGY_PLUS;
            }
            else if (memcmp(&obis_code[OBIS_C], OBIS_ACTIVE_ENERGY_MINUS, 2) == 0)
            {
                code_type = TYPE_ACTIVE_ENERGY_MINUS;
            }

            else if (memcmp(&obis_code[OBIS_C], OBIS_POWER_FACTOR, 2) == 0)
            {
                code_type = TYPE_POWER_FACTOR;
            }
            else
            {
                Serial.println("Unsupported OBIS code");
            }
        }
        else
        {
            Serial.println("Unsupported OBIS medium");
            receive_buffer_index = 0;
            return;
        }

        uint16_t uint16_value;
        uint32_t uint32_value;
        float float_value;

        switch (obis_data_type)
        {
        case DATA_LONG_DOUBLE_UNSIGNED:
            obis_data_length = 4;

            memcpy(&uint32_value, &plaintext[current_position], 4); // Copy uint8_ts to integer
            uint32_value = swap_uint32(uint32_value);               // Swap uint8_ts

            float_value = uint32_value; // Ignore decimal digits for now

            switch (code_type)
            {
            case TYPE_ACTIVE_POWER_PLUS:
                meter_data.power_plus = float_value;
                Serial.print("ActivePowerPlus ");
                Serial.println(float_value);
                break;

            case TYPE_ACTIVE_POWER_MINUS:
                meter_data.power_minus = float_value;
                Serial.print("ActivePowerMinus ");
                Serial.println(float_value);
                break;

            case TYPE_ACTIVE_ENERGY_PLUS:
                meter_data.energy_plus = float_value;
                Serial.print("ActiveEnergyPlus ");
                Serial.println(float_value);
                break;

            case TYPE_ACTIVE_ENERGY_MINUS:
                meter_data.energy_minus = float_value;
                Serial.print("ActiveEnergyMinus ");
                Serial.println(float_value);
                break;
            }
            break;

        case DATA_LONG:
        case DATA_LONG_UNSIGNED:
            obis_data_length = 2;

            memcpy(&uint16_value, &plaintext[current_position], 2); // Copy uint8_ts to integer
            uint16_value = swap_uint16(uint16_value);               // Swap uint8_ts

            if (plaintext[current_position + 5] == SCALE_TENTHS)
                float_value = uint16_value / 10.0;
            else if (plaintext[current_position + 5] == SCALE_HUNDREDTHS)
                float_value = uint16_value / 100.0;
            else if (plaintext[current_position + 5] == SCALE_THOUSANDS)
                float_value = uint16_value / 1000.0;
            else
                float_value = uint16_value;

            switch (code_type)
            {
            case TYPE_VOLTAGE_L1:
                meter_data.voltage_l1 = float_value;
                Serial.print("VoltageL1 ");
                Serial.println(float_value);
                break;

            case TYPE_VOLTAGE_L2:
                meter_data.voltage_l2 = float_value;
                Serial.print("VoltageL2 ");
                Serial.println(float_value);
                break;

            case TYPE_VOLTAGE_L3:
                meter_data.voltage_l3 = float_value;
                Serial.print("VoltageL3 ");
                Serial.println(float_value);
                break;

            case TYPE_CURRENT_L1:
                meter_data.current_l1 = float_value;
                Serial.print("CurrentL1 ");
                Serial.println(float_value);
                break;

            case TYPE_CURRENT_L2:
                meter_data.current_l2 = float_value;
                Serial.print("CurrentL2 ");
                Serial.println(float_value);
                break;

            case TYPE_CURRENT_L3:
                meter_data.current_l3 = float_value;
                Serial.print("CurrentL3 ");
                Serial.println(float_value);
                break;

            case TYPE_POWER_FACTOR:
                meter_data.cos_phi = float_value;
                Serial.print("PowerFactor ");
                Serial.println(float_value);
                break;
            }
            break;

        case DATA_OCTET_STRING:
            obis_data_length = plaintext[current_position];
            current_position++; // Advance past string length
            break;

        default:
            Serial.println("Unsupported OBIS data type");
            receive_buffer_index = 0;
            return;
            break;
        }

        current_position += obis_data_length; // Skip data length

        current_position += 2; // Skip pause after data

        if (plaintext[current_position] == 0x0F)  // There is still additional data for this type, skip it
            current_position += 4;                // Skip additional data and additional break; this will jump out of bounds on last frame
    } while (current_position <= payload_length); // Loop until end of packet

    receive_buffer_index = 0;
    Serial.println("Received valid data!");

        // copy timestamp into file string
        // creates new file every month
        char filename[13] = {"/YYYY_MM.CSV"};
        memcpy(&filename[6], &meter_data.timestamp_str[3], 2);
        memcpy(&filename[1], &meter_data.timestamp_str[6], 4);

ifdef TEST_SETUP

        sprintf(filename, "/%d_%02d.CSV", random(2021, 2026), random(1, 12));

endif

        char record[128];
        sprintf(record, "%.1f;%.1f;%.1f;%.2f;%.2f;%.2f;%.3f;%.1f;%.1f;%.0f;%.0f;%.2f;%.2f;%d", meter_data.voltage_l1, meter_data.voltage_l2, meter_data.voltage_l3, meter_data.current_l1, meter_data.current_l2, meter_data.current_l3, meter_data.cos_phi, meter_data.power_plus, meter_data.power_minus, meter_data.energy_plus, meter_data.energy_minus, meter_data.temperature, meter_data.humidity, meter_data.rssi);
        String _tmp = String(record);
        _tmp.replace(".", ",");
        sprintf(record, "%s;%s", meter_data.timestamp_str, _tmp.c_str());

}

uint16_t swap_uint16(uint16_t val) { return (val << 8) | (val >> 8); }

uint32_t swap_uint32(uint32_t val) { val = ((val << 8) & 0xFF00FF00) | ((val >> 8) & 0xFF00FF); return (val << 16) | (val >> 16); }`

EIKSEU commented 1 year ago

code von evn habe ich

EIKSEU commented 1 year ago

`String keystring = ""; char char_keystring[] = "00000000000000000000000000000000"; byte key[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; char convertCharToHex(char ch) { char returnType; switch (ch) { case '0': returnType = 0; break; case '1': returnType = 1; break; case '2': returnType = 2; break; case '3': returnType = 3; break; case '4': returnType = 4; break; case '5': returnType = 5; break; case '6': returnType = 6; break; case '7': returnType = 7; break; case '8': returnType = 8; break; case '9': returnType = 9; break; case 'A': case 'a': returnType = 10; break; case 'B': case 'b': returnType = 11; break; case 'C': case 'c': returnType = 12; break; case 'D': case 'd': returnType = 13; break; case 'E': case 'e': returnType = 14; break; case 'F': case 'f': returnType = 15; break; default: returnType = -1; break; } return returnType; }

void setup() { Serial.begin(115200);

keystring = "mein code"; keystring.toCharArray(char_keystring, keystring.length() + 1);

if (keystring.length() != 32) { Serial.println("ERROR - key Eingabefehler!"); } else { for (char i = 0; i < 16; i++) { byte extract; char a = keystring[2 i]; char b = keystring[2 i + 1]; extract = convertCharToHex(a) << 4 | convertCharToHex(b);

    key[i] = extract;
    Serial.print("key von der Eingabe: ");Serial.print(key[i], HEX); Serial.print(" ");

  }
}

}

void loop() { keystring = "meinevncode"; keystring.toCharArray(char_keystring, keystring.length() + 1);

if (keystring.length() != 32) { Serial.println("ERROR - key Eingabefehler!"); } else { for (char i = 0; i < 16; i++) { byte extract; char a = keystring[2 i]; char b = keystring[2 i + 1]; extract = convertCharToHex(a) << 4 | convertCharToHex(b); delay(1000);

  key[i] = extract;
  if (i == 0){
    Serial.print("Start: ");Serial.print(key[i], HEX); Serial.print("----- ");}
    Serial.print("0x");Serial.print(key[i], HEX); Serial.print(", ");

}
}

}`

SebTrax commented 1 year ago

Wenn dein Code von der EVN "3B4C183A" wäre, musst du im Programm einfach "0x3B, 0x4C, 0x18, 0x3A" eintragen. War zumindest bei mir so.

EIKSEU commented 1 year ago

Ok ja das kommt hin ...

SebTrax commented 1 year ago

Hattest du das so eingetragen? Bzw. war das das Problem?

EIKSEU commented 1 year ago

Den Key habe habe so in der key.h // Your Encryption Key static const unsigned char key[] = {0x00, 0x00, 0x00, 0x00, 0x00,.....};

EIKSEU commented 1 year ago

Der Fehler liegt vermutlich bei #include <mbedtls/gcm.h> da das im Arduino nicht läuft und ich das mit AES.h und GCM.h versuche zu entschlüsseln vermute der Fehler liegt hier:

    // Start decrypting
    uint8_t plaintext[payload_length];

    gcmaes128->setKey(key, gcmaes128->keySize());
    gcmaes128->setIV(iv, 12);
    gcmaes128->decrypt(plaintext, ciphertext, payload_length);
SebTrax commented 1 year ago

Hast du die mbedtls heruntergeladen und im Projektordner? https://github.com/wolfeidau/mbedtls Mit der funktionierts bei mir.

So siehts bei mir aus:

uint8_t plaintext[payload_length];

mbedtls_gcm_init(&aes);
mbedtls_gcm_setkey(&aes, MBEDTLS_CIPHER_ID_AES, KEY, KEY_LENGTH * 8);
mbedtls_gcm_auth_decrypt(&aes, payload_length, iv, sizeof(iv), NULL, 0, NULL, 0, ciphertext, plaintext);
mbedtls_gcm_free(&aes);

Von wo hast du den Code? Das ist ja nicht genau der von hier oder zumindest abgeändert.

EIKSEU commented 1 year ago

für einen Sagecom macht das einer so: `

void decrypt_text(Vector_GCM &vect) { GCM *gcmaes128 = 0; gcmaes128 = new GCM(); gcmaes128->setKey(key, gcmaes128->keySize()); gcmaes128->setIV(iv, ivsize); gcmaes128->decrypt(plaintext, ciphertext, datasize); delete gcmaes128; }

struct Vector_GCM { const char *name; byte keysize; unsigned int datasize; byte authsize; byte ivsize; byte tagsize; uint8_t key[16]; byte plaintext[MAX_PLAINTEXT_LEN]; byte ciphertext[MAX_PLAINTEXT_LEN]; byte authdata[17]; byte iv[12]; byte tag[12]; };

IncommingData aktuelleDaten;

Vector_GCM datenMbus = { //static .name = "AES-128 GCM", .keysize = 16, .datasize = 297, .authsize = 17, .ivsize = 12, .tagsize = 12, .key = { key}, .plaintext = {}, .ciphertext = {}, .authdata = {}, .iv = {}, .tag = {},
};`

EIKSEU commented 1 year ago

aber was davon wird für n Kaifa benötigt?

SebTrax commented 1 year ago

So wie es in diesem Link ist sollte es beim Kaifa funktionieren ( zumindest gehts bei mir): https://github.com/FKW9/esp-smartmeter-netznoe/blob/master/src/main.cpp

Ich weiß nicht für was das sein soll, was du da geschickt hast, bin aber auch kein Profi :D Ich hab noch die Vorgängerversion am laufen und sie für mich etwas angepasst, dass ich die Daten über MQTT erhalte.

EIKSEU commented 1 year ago

läuft das bei dir auf einem Arduino?

Noschvie commented 1 year ago

@EIKSEU Warum verwendest du keinen ESP32? Welches M-Bus Interface hast du zur Verfügung?

SebTrax commented 1 year ago

Bei mir läufts auf einem ESP32, weil ich für MQTT W-Lan benötige. Aber fürn Arduino ist der Code eigentlich genau der gleiche. Nur halt ohne die ganzen W-Lan sachen.

Bekommst du überhaupt Daten vom Zähler oder bekommst du gar nix rein?

EIKSEU commented 1 year ago

da ich arduino sonst auch verwende. Ja es kommen daten der Fehler ist dieser

.....................smartmeter-netznoe.ino:78: undefined reference to `mbedtls_gcm_init'

.......................smartmeter-netznoe.ino:79: undefined reference to `mbedtls_gcm_setkey'

......................smartmeter-netznoe.ino:80: undefined reference to `mbedtls_gcm_auth_decrypt'

......................smartmeter-netznoe.ino:81: undefined reference to `mbedtls_gcm_free'

collect2.exe: error: ld returned 1 exit status

exit status 1

ich habe alle ordner im im Ordner mit der .ino datei kopiert das Problem habe ich von Anfang an daher habe ich es anders versucht

SebTrax commented 1 year ago

Hast du die mbedtls Library was ich oben verlinkt habe im Projektordner? Die ist hier nämlich nicht dabei.

EIKSEU commented 1 year ago

https://github.com/wolfeidau/mbedtls die meinst du?

include "mbedtls/gcm.h" die habe so eingefügt und wird erkannt

SebTrax commented 1 year ago

Ja genau. Ohne die gehts nicht. Ich hab da selbst auch etwas gebraucht, bis ich die gefunden hab. Wenn du den Ordner mbedtls in deinen Projektordner einfügst, müsste es schon funktionieren.

SebTrax commented 1 year ago

Ich hab grad nachgesehen. Ich hab die ganzen Dateien vom Ordner Source auch in den Ordner mbedtls kopiert.

EIKSEU commented 1 year ago

ok das hab ich jetzt auch aber noch kein erfolg....

EIKSEU commented 1 year ago

ich habs nun so eingefügt

include

include "dlms.h"

include "obis.h"

include "key.h"

include "source/mbedtls/gcm.h"

include "source/gcm.c"

jetzt ist noch dieser fehler .... \source\gcm.c:82:33: error: invalid conversion from 'void' to 'volatile unsigned char' [-fpermissive] volatile unsigned char p = v; while( n-- ) p++ = 0

hast eine Idee

`You need to cast as you can not convert a void* to anything without casting it first.

You would need to do

unsigned char etherhead = (unsigned char)buffer; (although you could use a static_cast also)

To learn more about void pointers, take a look at [6.13 — Void pointers](http://www.learncpp.com/cpp-tutorial/613-void-pointers/`

das habe ich versucht da sind noch mehr Fehler gekommen

SebTrax commented 1 year ago

gcm.c musst u nicht einbinden. Wie hast du denn die Ordner in deinem Projekt angelegt? liegt der mbedtls ordner im source ordner? Versuch mal so: Dein Projekt => mbedtls => hier alle Dateien vom ordner mbedtls und source

Den source Ordner kannst dann löschen und mit #include "mbedtls/gcm.h" einbinden

EIKSEU commented 1 year ago

Das hatte ich aber dan passen die includes nicht mehr und die .c datein werden anscheinend nur gefunden wenn ich sie miteinander....

SebTrax commented 1 year ago

Ich hab grad versucht es für Arduino ohne WiFi zu kompilieren, aber da fehlen dann einen haufen Librarys. Ich würd sagen, am einfachsten wäre es, wenn du dir einfach einen ESP32 kaufst. Die kosten ja ned wirklich was und du kannst dann auch mal was mit WLan programmieren, dass du dir z.B. die Werte am Handy ansehen kannst.

EIKSEU commented 1 year ago

Ok danke für deine Hilfe werde noch versuchen es zu lösen.....

FKW9 commented 1 year ago

Wie SebTrax schon gesagt hat, fehlen viele Librarys für Arduino, unter anderem basiert der ganze WiFi Stack, die TFT-Lib, der Logger, das Filesystem, die GPIOs usw. alles auf dem ESP32. Am einfachsten wäre es, wenn du ein ESP32 Devkit für 14€ (auf AliExpress noch billiger) kaufst. Wenn du bei Arduino bleiben willst, wäre es besser wenn du den Code von Null-auf neu schreiben würdest, mit meinem z.B. als Vorlage. Somit kannst du immer stückweise testen, ob auch alles funktioniert.

EIKSEU commented 1 year ago

Danke für eure Hilfe ich hab s jetzt....

`#include

include "dlms.h"

include "obis.h"

include "key.h" //byte key[16] = {0x00, 0x00,.....};

include

include

include

GCM *gcmaes128 = 0;

ifdef TEST_SETUP

include

endif

// Variables for DLMS decoding uint32_t last_read = 0; // Timestamp when data was last read uint16_t receive_buffer_index = 0; // Current position in the receive buffer uint8_t receive_buffer[RECEIVE_BUFFER_SIZE]; // Stores the received data

// Function prototypes uint32_t swap_uint32(uint32_t val); uint16_t swap_uint16(uint16_t val); void serial_dump();

void PrintHex8(uint8_t *data, int length) // prints 8-bit data in hex with leading zeroes { char tmp[16]; for (int i = 0; i < length; i++) { sprintf(tmp, "0x%.2X", data[i]); Serial.print(tmp); Serial.print(" "); } }

// SETUP void setup() { // Debug port Serial.begin(115200);

// MBus input
Serial1.begin(2400, SERIAL_8E1);

// Serial1.setRxBufferSize(RECEIVE_BUFFER_SIZE); Serial1.setTimeout(2);

gcmaes128 = new GCM<AES128>();

Serial.println("\nWarte auf Smartmeter Daten...");
delay(2000);

}

// MAIN LOOP void loop() {

uint32_t current_time = millis();

// Read while data is available
while (Serial1.available())
{
    if (receive_buffer_index >= RECEIVE_BUFFER_SIZE)
    {
        Serial.println("Buffer overflow!");
        receive_buffer_index = 0;
        return;
    }

    receive_buffer[receive_buffer_index++] = Serial1.read();

    last_read = current_time;
}

ifdef TEST_SETUP

const uint8_t test_data[] = {0x68, 0xFA, 0xFA, 0x68, 0x53, 0xFF, 0x00, 0x01, 0x67, 0xDB, 0x08, 0x4B, 0x46, 0x4D, 0x67, 0x50, 0x00, 0x08, 0x81, 0x81, 0xF8, 0x20, 0x00, 0x00, 0x60, 0x05, 0xC1, 0xFA, 0x38, 0xBC, 0xC6, 0xD9, 0x59, 0x4E, 0x5A, 0x7C, 0x36, 0x59, 0x13, 0x8B, 0x7E, 0xE9, 0x9A, 0x83, 0x47, 0xBF, 0x50, 0xB9, 0x98, 0xF8, 0x33, 0x85, 0x81, 0x08, 0x45, 0x12, 0xA3, 0x78, 0x0C, 0xAC, 0xBF, 0x5B, 0xCB, 0x36, 0x67, 0x44, 0xC9, 0x9E, 0x99, 0xE5, 0x3A, 0x12, 0xE8, 0x67, 0xD4, 0xED, 0xE6, 0x8E, 0xF8, 0x2A, 0x91, 0xF6, 0xD7, 0x6C, 0x08, 0x6C, 0x73, 0xAD, 0x57, 0x54, 0xCE, 0x8F, 0x13, 0x13, 0xD9, 0xF7, 0x38, 0xC8, 0x50, 0xD4, 0x93, 0x62, 0x75, 0x0A, 0x9B, 0x6F, 0xCB, 0xE3, 0x3A, 0xDE, 0x62, 0xA1, 0x6C, 0x62, 0xA9, 0xB5, 0xCA, 0xC7, 0x93, 0x31, 0xE9, 0x78, 0x62, 0x81, 0x60, 0x4B, 0xC9, 0x24, 0x22, 0xA1, 0x24, 0xE0, 0xC3, 0xB9, 0xC9, 0x79, 0xD0, 0xE2, 0x65, 0xDA, 0x60, 0x14, 0x03, 0xC5, 0x56, 0xB8, 0x96, 0x09, 0x44, 0xC4, 0x24, 0xBA, 0xD4, 0x9D, 0x63, 0xAD, 0xAB, 0xFB, 0xAE, 0xDA, 0x07, 0x5D, 0x66, 0x88, 0x2F, 0x5C, 0xB5, 0x5A, 0x7A, 0xAD, 0x43, 0xFC, 0x24, 0x47, 0x0F, 0x3B, 0x23, 0xBD, 0x6B, 0xBB, 0xFF, 0x71, 0xC6, 0x44, 0x7A, 0x3D, 0xEB, 0x2E, 0xA4, 0x7D, 0x80, 0x60, 0xD7, 0xAA, 0xF6, 0x2F, 0x14, 0x9C, 0x0D, 0xD4, 0xCF, 0x60, 0xFD, 0xC9, 0x1C, 0x20, 0xED, 0x9A, 0x6D, 0xCC, 0xF0, 0x6D, 0xA5, 0x4D, 0xFB, 0x9B, 0x5F, 0x45, 0x60, 0xDF, 0xB5, 0x36, 0x4E, 0x0E, 0x01, 0x05, 0x6A, 0x07, 0x36, 0x4A, 0x60, 0x5F, 0x85, 0x75, 0xF3, 0x84, 0x1D, 0x51, 0x7D, 0x07, 0x22, 0x06, 0x14, 0xCF, 0xC7, 0xCF, 0x98, 0x82, 0x5E, 0xE7, 0x20, 0xC5, 0x1C, 0xBC, 0x59, 0x16, 0x68, 0x14, 0x14, 0x68, 0x53, 0xFF, 0x11, 0x01, 0x67, 0x4C, 0xC1, 0xD2, 0x17, 0xB8, 0xD8, 0x03, 0x4C, 0xC7, 0x51, 0x5F, 0xE0, 0x20, 0x95, 0x61, 0x0D, 0x16, 0x68, 0xFA, 0xFA, 0x68, 0x53, 0xFF, 0x00, 0x01, 0x67, 0xDB, 0x08, 0x4B, 0x46, 0x4D, 0x67, 0x50, 0x00, 0x08, 0x81, 0x81, 0xF8, 0x20, 0x00, 0x00, 0x60, 0x05, 0xC1, 0xFA, 0x38, 0xBC, 0xC6, 0xD9, 0x59, 0x4E, 0x5A, 0x7C, 0x36, 0x59, 0x13, 0x8B, 0x7E, 0xE9, 0x9A, 0x83, 0x47, 0xBF, 0x50, 0xB9, 0x98, 0xF8, 0x33, 0x85, 0x81, 0x08, 0x45, 0x12, 0xA3, 0x78, 0x0C, 0xAC, 0xBF, 0x5B, 0xCB, 0x36, 0x67, 0x44, 0xC9, 0x9E, 0x99, 0xE5, 0x3A, 0x12, 0xE8, 0x67, 0xD4, 0xED, 0xE6, 0x8E, 0xF8, 0x2A, 0x91, 0xF6, 0xD7, 0x6C, 0x08, 0x6C, 0x73, 0xAD, 0x57, 0x54, 0xCE, 0x8F, 0x13, 0x13, 0xD9, 0xF7, 0x38, 0xC8, 0x50, 0xD4, 0x93, 0x62, 0x75, 0x0A, 0x9B, 0x6F, 0xCB, 0xE3, 0x3A, 0xDE, 0x62, 0xA1, 0x6C, 0x62, 0xA9, 0xB5, 0xCA, 0xC7, 0x93, 0x31, 0xE9, 0x78, 0x62, 0x81, 0x60, 0x4B, 0xC9, 0x24, 0x22, 0xA1, 0x24, 0xE0, 0xC3, 0xB9, 0xC9, 0x79, 0xD0, 0xE2, 0x65, 0xDA, 0x60, 0x14, 0x03, 0xC5, 0x56, 0xB8, 0x96, 0x09, 0x44, 0xC4, 0x24, 0xBA, 0xD4, 0x9D, 0x63, 0xAD, 0xAB, 0xFB, 0xAE, 0xDA, 0x07, 0x5D, 0x66, 0x88, 0x2F, 0x5C, 0xB5, 0x5A, 0x7A, 0xAD, 0x43, 0xFC, 0x24, 0x47, 0x0F, 0x3B, 0x23, 0xBD, 0x6B, 0xBB, 0xFF, 0x71, 0xC6, 0x44, 0x7A, 0x3D, 0xEB, 0x2E, 0xA4, 0x7D, 0x80, 0x60, 0xD7, 0xAA, 0xF6, 0x2F, 0x14, 0x9C, 0x0D, 0xD4, 0xCF, 0x60, 0xFD, 0xC9, 0x1C, 0x20, 0xED, 0x9A, 0x6D, 0xCC, 0xF0, 0x6D, 0xA5, 0x4D, 0xFB, 0x9B, 0x5F, 0x45, 0x60, 0xDF, 0xB5, 0x36, 0x4E, 0x0E, 0x01, 0x05, 0x6A, 0x07, 0x36, 0x4A, 0x60, 0x5F, 0x85, 0x75, 0xF3, 0x84, 0x1D, 0x51, 0x7D, 0x07, 0x22, 0x06, 0x14, 0xCF, 0xC7, 0xCF, 0x98, 0x82, 0x5E, 0xE7, 0x20, 0xC5, 0x1C, 0xBC, 0x59, 0x16, 0x68, 0x14, 0x14, 0x68, 0x53, 0xFF, 0x11, 0x01, 0x67, 0x4C, 0xC1, 0xD2, 0x17, 0xB8, 0xD8, 0x03, 0x4C, 0xC7, 0x51, 0x5F, 0xE0, 0x20, 0x95, 0x61, 0x0D, 0x16};
static unsigned long previous_millis = 0;

if (millis() - previous_millis > 4000)
{
    previous_millis = last_read = millis();
    memcpy(&receive_buffer, &test_data, sizeof(test_data));
    receive_buffer_index = sizeof(test_data) - 1;
}

endif

if (receive_buffer_index > 0 && current_time - last_read > READ_TIMEOUT)
{
    if (receive_buffer_index < 256)
    {
        Serial.println("Received packet with invalid size!");
        Serial.println(receive_buffer_index);
        receive_buffer_index = 0;
        return;
    }

    /**
 * @TODO: ADD ROUTINE TO DETERMINE PAYLOAD LENGTHS AUTOMATICALLY
 */

    uint16_t payload_length = 243;
    uint16_t payload_length_msg1 = 228;
    uint16_t payload_length_msg2 = payload_length - payload_length_msg1;

    uint8_t iv[12]; // Initialization vector

    memcpy(&iv[0], &receive_buffer[DLMS_SYST_OFFSET], DLMS_SYST_LENGTH); // Copy system title to IV
    memcpy(&iv[8], &receive_buffer[DLMS_IC_OFFSET], DLMS_IC_LENGTH);     // Copy invocation counter to IV

    uint8_t ciphertext[payload_length];
    memcpy(&ciphertext[0], &receive_buffer[DLMS_HEADER1_LENGTH], payload_length_msg1);
    memcpy(&ciphertext[payload_length_msg1], &receive_buffer[DLMS_HEADER2_OFFSET + DLMS_HEADER2_LENGTH], payload_length_msg2);

    // Start decrypting
    uint8_t plaintext[payload_length];

     Serial.print("\n\nkey: ");
    PrintHex8(key, sizeof key);
    //Serial.print("\nkey vom Server: "); Serial.print(keystring);

    Serial.print("\niv:  ");
    PrintHex8(iv, sizeof iv);

    gcmaes128->setKey(key, gcmaes128->keySize());
    gcmaes128->setIV(iv, 12);
    gcmaes128->decrypt(plaintext, ciphertext, payload_length);

    /*mbedtls_gcm_init(&aes);
    mbedtls_gcm_setkey(&aes, MBEDTLS_CIPHER_ID_AES, KEY, KEY_LENGTH * 8);
    mbedtls_gcm_auth_decrypt(&aes, payload_length, iv, sizeof(iv), NULL, 0, NULL, 0, ciphertext, plaintext);
    mbedtls_gcm_free(&aes);
    */

    if (plaintext[0] != 0x0F || plaintext[5] != 0x0C)
    {
        Serial.println("Packet was decrypted but data is invalid!");
        receive_buffer_index = 0;
        return;
    }

    // Decode data
    uint16_t current_position = DECODER_START_OFFSET;
    meterData meter_data;

    do
    {
        if (plaintext[current_position + OBIS_TYPE_OFFSET] != DATA_OCTET_STRING)
        {
            Serial.println("Unsupported OBIS header type!");
            receive_buffer_index = 0;
            return;
        }

        uint8_t data_length = plaintext[current_position + OBIS_LENGTH_OFFSET];

        if (data_length != 0x06)
        {
            // read timestamp
            if ((data_length == 0x0C) && (current_position == DECODER_START_OFFSET))
            {
                uint8_t dateTime[data_length];
                memcpy(&dateTime[0], &plaintext[current_position + 2], data_length);

                uint16_t year;
                uint8_t month, day, hour, minute, second;

                year = (plaintext[current_position + 2] << 8) + plaintext[current_position + 3];
                month = plaintext[current_position + 4];
                day = plaintext[current_position + 5];
                hour = plaintext[current_position + 7];
                minute = plaintext[current_position + 8];
                second = plaintext[current_position + 9];

                sprintf(meter_data.timestamp_str, "%02u.%02u.%04u %02u:%02u:%02u", day, month, year, hour, minute, second);

                meter_data.timestamp_unix = -1;

                Serial.print("Timestamp: ");
                Serial.println(meter_data.timestamp_str);

                current_position = 34;
                data_length = plaintext[current_position + OBIS_LENGTH_OFFSET];
            }
            else if ((data_length == 0x0C) && (current_position > 225))
            {
                uint8_t meterNumber[data_length];
                memcpy(&meterNumber[0], &plaintext[current_position + 2], data_length);

                // THIS IS THE END OF THE PACKET
                break;
            }
            else
            {
                Serial.println("Unsupported OBIS header length");
                receive_buffer_index = 0;
                return;
            }
        }

        uint8_t obis_code[data_length];
        memcpy(&obis_code[0], &plaintext[current_position + OBIS_CODE_OFFSET], data_length); // Copy OBIS code to array

        current_position += data_length + 2; // Advance past code, position and type

        uint8_t obis_data_type = plaintext[current_position];
        current_position++; // Advance past data type

        uint8_t obis_data_length = 0x00;
        uint8_t code_type = TYPE_UNKNOWN;

        if (obis_code[OBIS_A] == 0x01)
        {
            // Compare C and D against code
            if (memcmp(&obis_code[OBIS_C], OBIS_VOLTAGE_L1, 2) == 0)
            {
                code_type = TYPE_VOLTAGE_L1;
            }
            else if (memcmp(&obis_code[OBIS_C], OBIS_VOLTAGE_L2, 2) == 0)
            {
                code_type = TYPE_VOLTAGE_L2;
            }
            else if (memcmp(&obis_code[OBIS_C], OBIS_VOLTAGE_L3, 2) == 0)
            {
                code_type = TYPE_VOLTAGE_L3;
            }

            else if (memcmp(&obis_code[OBIS_C], OBIS_CURRENT_L1, 2) == 0)
            {
                code_type = TYPE_CURRENT_L1;
            }
            else if (memcmp(&obis_code[OBIS_C], OBIS_CURRENT_L2, 2) == 0)
            {
                code_type = TYPE_CURRENT_L2;
            }
            else if (memcmp(&obis_code[OBIS_C], OBIS_CURRENT_L3, 2) == 0)
            {
                code_type = TYPE_CURRENT_L3;
            }

            else if (memcmp(&obis_code[OBIS_C], OBIS_ACTIVE_POWER_PLUS, 2) == 0)
            {
                code_type = TYPE_ACTIVE_POWER_PLUS;
            }
            else if (memcmp(&obis_code[OBIS_C], OBIS_ACTIVE_POWER_MINUS, 2) == 0)
            {
                code_type = TYPE_ACTIVE_POWER_MINUS;
            }

            else if (memcmp(&obis_code[OBIS_C], OBIS_ACTIVE_ENERGY_PLUS, 2) == 0)
            {
                code_type = TYPE_ACTIVE_ENERGY_PLUS;
            }
            else if (memcmp(&obis_code[OBIS_C], OBIS_ACTIVE_ENERGY_MINUS, 2) == 0)
            {
                code_type = TYPE_ACTIVE_ENERGY_MINUS;
            }

            else if (memcmp(&obis_code[OBIS_C], OBIS_POWER_FACTOR, 2) == 0)
            {
                code_type = TYPE_POWER_FACTOR;
            }
            else
            {
                Serial.println("Unsupported OBIS code");
            }
        }
        else
        {
            Serial.println("Unsupported OBIS medium");
            receive_buffer_index = 0;
            return;
        }

        uint16_t uint16_value;
        uint32_t uint32_value;
        float float_value;

        switch (obis_data_type)
        {
        case DATA_LONG_DOUBLE_UNSIGNED:
            obis_data_length = 4;

            memcpy(&uint32_value, &plaintext[current_position], 4); // Copy uint8_ts to integer
            uint32_value = swap_uint32(uint32_value);               // Swap uint8_ts

            float_value = uint32_value; // Ignore decimal digits for now

            switch (code_type)
            {
            case TYPE_ACTIVE_POWER_PLUS:
                meter_data.power_plus = float_value;
                Serial.print("ActivePowerPlus ");
                Serial.println(float_value);
                break;

            case TYPE_ACTIVE_POWER_MINUS:
                meter_data.power_minus = float_value;
                Serial.print("ActivePowerMinus ");
                Serial.println(float_value);
                break;

            case TYPE_ACTIVE_ENERGY_PLUS:
                meter_data.energy_plus = float_value;
                Serial.print("ActiveEnergyPlus ");
                Serial.println(float_value);
                break;

            case TYPE_ACTIVE_ENERGY_MINUS:
                meter_data.energy_minus = float_value;
                Serial.print("ActiveEnergyMinus ");
                Serial.println(float_value);
                break;
            }
            break;

        case DATA_LONG:
        case DATA_LONG_UNSIGNED:
            obis_data_length = 2;

            memcpy(&uint16_value, &plaintext[current_position], 2); // Copy uint8_ts to integer
            uint16_value = swap_uint16(uint16_value);               // Swap uint8_ts

            if (plaintext[current_position + 5] == SCALE_TENTHS)
                float_value = uint16_value / 10.0;
            else if (plaintext[current_position + 5] == SCALE_HUNDREDTHS)
                float_value = uint16_value / 100.0;
            else if (plaintext[current_position + 5] == SCALE_THOUSANDS)
                float_value = uint16_value / 1000.0;
            else
                float_value = uint16_value;

            switch (code_type)
            {
            case TYPE_VOLTAGE_L1:
                meter_data.voltage_l1 = float_value;
                Serial.print("VoltageL1 ");
                Serial.println(float_value);
                break;

            case TYPE_VOLTAGE_L2:
                meter_data.voltage_l2 = float_value;
                Serial.print("VoltageL2 ");
                Serial.println(float_value);
                break;

            case TYPE_VOLTAGE_L3:
                meter_data.voltage_l3 = float_value;
                Serial.print("VoltageL3 ");
                Serial.println(float_value);
                break;

            case TYPE_CURRENT_L1:
                meter_data.current_l1 = float_value;
                Serial.print("CurrentL1 ");
                Serial.println(float_value);
                break;

            case TYPE_CURRENT_L2:
                meter_data.current_l2 = float_value;
                Serial.print("CurrentL2 ");
                Serial.println(float_value);
                break;

            case TYPE_CURRENT_L3:
                meter_data.current_l3 = float_value;
                Serial.print("CurrentL3 ");
                Serial.println(float_value);
                break;

            case TYPE_POWER_FACTOR:
                meter_data.cos_phi = float_value;
                Serial.print("PowerFactor ");
                Serial.println(float_value);
                break;
            }
            break;

        case DATA_OCTET_STRING:
            obis_data_length = plaintext[current_position];
            current_position++; // Advance past string length
            break;

        default:
            Serial.println("Unsupported OBIS data type");
            receive_buffer_index = 0;
            return;
            break;
        }

        current_position += obis_data_length; // Skip data length

        current_position += 2; // Skip pause after data

        if (plaintext[current_position] == 0x0F)  // There is still additional data for this type, skip it
            current_position += 4;                // Skip additional data and additional break; this will jump out of bounds on last frame
    } while (current_position <= payload_length); // Loop until end of packet

    receive_buffer_index = 0;
    Serial.println("Received valid data!");

/* // send the data

ifdef USE_GRAPHITE

    submitToGraphite(meter_data.timestamp_unix, GRAPHITE_VOLTAGE_L1, meter_data.voltage_l1);
    submitToGraphite(meter_data.timestamp_unix, GRAPHITE_VOLTAGE_L2, meter_data.voltage_l2);
    submitToGraphite(meter_data.timestamp_unix, GRAPHITE_VOLTAGE_L3, meter_data.voltage_l3);
    submitToGraphite(meter_data.timestamp_unix, GRAPHITE_CURRENT_L1, meter_data.current_l1);
    submitToGraphite(meter_data.timestamp_unix, GRAPHITE_CURRENT_L2, meter_data.current_l2);
    submitToGraphite(meter_data.timestamp_unix, GRAPHITE_CURRENT_L3, meter_data.current_l3);
    submitToGraphite(meter_data.timestamp_unix, GRAPHITE_POWER_FACTOR, meter_data.cos_phi);
    submitToGraphite(meter_data.timestamp_unix, GRAPHITE_ACTIVE_POWER_PLUS, meter_data.power_plus);
    submitToGraphite(meter_data.timestamp_unix, GRAPHITE_ACTIVE_POWER_MINUS, meter_data.power_minus);
    submitToGraphite(meter_data.timestamp_unix, GRAPHITE_ACTIVE_ENERGY_PLUS, meter_data.energy_plus);
    submitToGraphite(meter_data.timestamp_unix, GRAPHITE_ACTIVE_ENERGY_MINUS, meter_data.energy_minus);
    submitToGraphite(meter_data.timestamp_unix, GRAPHITE_T, meter_data.temperature);
    submitToGraphite(meter_data.timestamp_unix, GRAPHITE_RH, meter_data.humidity);
    submitToGraphite(meter_data.timestamp_unix, GRAPHITE_RSSI, meter_data.rssi);

endif

*/ } }
uint16_t swap_uint16(uint16_t val) { return (val << 8) | (val >> 8); }

uint32_t swap_uint32(uint32_t val) { val = ((val << 8) & 0xFF00FF00) | ((val >> 8) & 0xFF00FF); return (val << 16) | (val >> 16); }`