P1 poort data lezen/gebruiken

Beste

Het is mij onlangs gelukt telegrams te ontvangen via de P1 poort van mijn digitale teller in combinatie met een arduino UNO en "altserial" volgens deze manier
P1 poort slimme meter uitlezen (hardware)

Met de BC547.

Is het mogelijk ook de data te gebruiken om een relay te sturen?

Ik zou graag bij een bepaalde teruggave een boiler willen opladen. Liefst zonder dat er een pc aangesloten is.

Mijn kennis van arduino is minimaal :slight_smile:

Mvg Greg

Ja

Het is al een tijdje geleden dat ik me hier mee bezig heb gehouden voor een vriend. Je zult moeten filteren op een OBIS referentie, de data splitsen en vervolgens daar op reageren.

Het programma in de link lijkt alleen maar een karakter te lezen en deze op de seriële monitor te zetten. Je zult moeten bufferen. Welke OBIS referentie heeft je interesse?

Krijg je inderdaad iedere 10 seconden een set data of vaker?

Ik krijg iedere seconde Info.

Ik zou graag de terug geleverde stroom gebruiken.

Als ik 2000W op het net steek voor x aantal sec. Dan uitgang 1 schakelen.

Mvg
Greg

Kun je aub je sketch hier neerzetten.
Kun je aub de gevens van de seriële monitor hier neerzetten?

Gebruik voor beiden aub code tags (de <CODE/> knop).

Het wordt misschien wat moeilijk met de seriële monitor; het kan handig zijn om een terminal programma zoals putty, TeraTerm etc te gebruiken die het ook naar een bestand kan schrijven. Helaas ben je te nieuw om het bestand bij te sluiten maar je kunt de inhoud hier plakken.

ty
#include <AltSoftSerial.h>
// AltSoftSerial always uses these pins:
//
// Board          Transmit  Receive   PWM Unusable
// -----          --------  -------   ------------
// Teensy 2.0         9        10       (none)
// Teensy++ 2.0      25         4       26, 27
// Arduino Uno        9         8         10
// Arduino Mega      46        48       44, 45
// Wiring-S           5         6          4
// Sanguino          13        14         12
 
AltSoftSerial altSerial;
char c;
 
void setup() {
    Serial.begin(115200);
    altSerial.begin(115200);
}
 
void loop() {
    if (altSerial.available()) {
        c = altSerial.read();
   
        // --- 7 bits instelling ---
        c &= ~(1 << 7);
        char inChar = (char)c;
 
    Serial.print(c);
    }
}
p of plak hier code
21:38:09.261 -> /FLU4\253769484_A 
21:38:09.261 -> 
21:38:09.261 -> 0-0:96.1.4(50217)
21:38:09.262 -> 0-0:96.1.1(3153414733313030303434363637)
21:38:09.262 -> 0-0:0.0.0(241116213818W)
21:38:09.262 -> 1-0:1.8.1(003935.668*kWh)
21:38:09.298 -> 1-0:1.8.2(003944.178*kWh)
21:38:09.298 -> 1-0:2.8.1(005175.490*kWh)
21:38:09.298 -> 1-0:2.8.2(002117.862*kWh)
21:38:09.298 -> 0-0:96.14.0(0002)
21:38:09.298 -> 1-0:1.4.0(00.246*kW)
21:38:09.298 -> 1-0:1.6.0(241109100000W)(03.089*kW)
21:38:09.298 -> 0-0:98.1.0(13)(1-0:1.6.0)(1-0:1.6.0)(231101000000W)(231007093000S)(02.950*kW)(231201000000W)(231124113000W)(03.94L%-U	I" 
21:38:09.298 ->  
21:38:09.298 ->       :JB  
21:38:09.298 ->  
21:38:09.298 ->   J"*  :JB  r B"RZ]%(240201000000W)(240113110000W)(03.547*kW)(240301000000W)(2402LML    :JB  r*  RZ]%(240401000000S)(240301114500W)(03.009*kW)(240501000000S)(240427114500S)(03.548*kW)(240 
21:38:09.331 ->        JB " * B
21:38:09.331 -> "
21:38:09.331 -> *   JB  r
21:38:09.331 ->  *RZ]%(240701000000S)(240605080000S)(03.036*kW)(240801000000S)(240701134500S)(02.866*kW)(240901000000S)(240830174500S)(02.648*kW)(241001000000S)(240903213000S)(02.817*kW)(241101000000W)(241023184500S)(04.116*kW)
21:38:09.331 -> 1-0:1.7.0(02.039*kW)
21:38:09.331 -> 1-0:2.7.0(00.000*kW)
21:38:09.369 -> 1-0:21.7.0(00.079*kW)
21:38:09.369 -> 1-0:41.7.0(01.875*kW)
21:38:09.369 -> 1-0:61.7.0(00.084*kW)
21:38:09.369 -> 1-0:22.7.0(00.000*kW)
21:38:09.369 -> 1-0:42.7.0(00.000*kW)
21:38:09.369 -> 1-0:62.7.0(00.000*kW)
21:38:09.369 -> 1-0:32.7.0(234.3*V)
21:38:09.369 -> 1-0:52.7.0(233.9*V)
21:38:09.369 -> 1-0:72.7.0(233.6*V)
21:38:09.369 -> 1-0:31.7.0(000.40*A/
21:38:09.369 -> 1-0:51.7.0(008.04*A/
21:38:09.369 -> 1-0:71.7.0(000.57*A(
21:38:09.370 -> 0-0:96.3.10(1)
21:38:09.370 -> 0-0:17.0.0(999.9*kW)
21:38:09.370 -> 1-0:31.4.0(999*A(
21:38:09.370 -> 0-0:96.13.0()
21:38:09.370 -> 0-1:24.1.0(003)
21:38:09.370 -> 0-1:96.1.1(37464C4F32313230303637303032)
21:38:09.409 -> 0-1:24.4.0(1)
21:38:09.409 -> 0-1:24.2.3(241116213505W)(02816.819*m3)
21:38:09.409 -> !5CEB
21:38:10.276 -> /FLU5\253769484_A
21:38:10.276 -> 
21:38:10.276 -> 0-0:96.1.4(50217)
21:38:10.276 -> 0-0:96.1.1(3153414733313030303434363637)
21:38:10.276 -> 0-0:1.0.0(241116213819W)
21:38:10.276 -> 1-0:1.8.1(003935.668*kWh)
21:38:10.276 -> 1-0:1.8.2(003944.179*kWh)
21:38:10.276 -> 1-0:2.8.1(005175.490*kWh)
21:38:10.276 -> 1-0:2.8.2(002117.862*kWh)
21:38:10.276 -> 0-0:96.14.0(0002)
21:38:10.276 -> 1-0:1.4.0(00.248*kW)
21:38:10.276 -> 1-0:1.6.0(241108100000W)(03.089*kW)
21:38:10.479 -> 0-0:98.1.0(13)(1-0:1.6.0)(1-0:1.6.0)(231101000000W)(231007093000S)(02.950*kW)(231201000000W)(231124113000W)(03.941*kW)(240101000000W)(231210094500W)(03.284*kW)(240201000000W)(240113110000W)(03.547*kW)(240301000000W)(240215183000W)(03.503*kW)(240401000000S)(240301114500W)(03.009*kW)(240501000000S)(240427114500S)(03.548*kW)(240601000000S)(240528141500S)(03.125*kW)(240701000000S)(240605080000S)(03.036*kW)(& B 
21:38:10.480 ->        JB " : 
21:38:10.480 -> 38:10.480 -> 
21:38:10.480 ->  "*   JB  rB22RZ]%(240901000000S)(240830174500S)(02.648*kW)(241001000000S)(2409032130  %(02.817*kW)(241101000000W)(241023184500S)(04.116*kW)
21:38:10.480 -> 1-0:1.7.0(02.034*kW)
21:38:10.480 -> 1-0:2.7.0(00.000*kW)
21:38:10.480 -> 1-0:2LiH	B  r B RZ]%
21:38:10.480 -> 1-0:41.7.0(01.869*kW)
21:38:10.480 -> 1-0:61.7.0(00.084*kW)
21:38:10.480 -> 1-0:22.7.0(00.000*kW)
21:38:10.480 -> 1-0:42.7.0(00.000*kW)
21:38:10.480 -> 1-0:62.7.0(00.000*kW)
21:38:10.480 -> 1-0:32.7.0(234.3*V)
21:38:10.519 -> 1-0:52.7.0(233.8*V)
21:38:10.519 -> 1-0:72.7.0(233.5*V)
21:38:10.519 -> 1-0:31.7.0(000.41*A(
21:38:10.519 -> 1-0:51.7.0(008.02*A/
21:38:10.519 -> 1-0:71.7.0(000.57*A/
21:38:10.519 -> 0-0:96.3.10(1)
21:38:10.519 -> 0-0:17.0.0(999.9*kW)
21:38:10.519 -> 1-0:31.4.0(999*A/
21:38:10.519 -> 0-0:96.13.0()
21:38:10.519 -> 0-1:24.1.0(003)
21:38:10.519 -> 0-1:96.1.1(37464C4F32313230303637303032)
21:38:10.519 -> 0-1:24.4.0(1)
21:38:10.519 -> 0-1:24.2.3(241116213505W)(02816.819*m3)
21:38:10.519 -> !974B
21:38:11.265 -> /FLU5\253769484_A
21:38:11.266 -> 
21:38:11.266 -> 0-0:96.1.4(50217)
21:38:11.282 -> 0-0:96.1.1(3153414733313030303434363637)
21:38:11.282 -> 0-0:1.0.0(241116213820W)
21:38:11.282 -> 1-0:1.8.1(003935.668*kWh)
21:38:11.282 ->

Lijn 1-0: 2.7.0
Is de actuele geinjecteerd Kwh

Het lijkt er op dat je data niet correct is. Dit is een voorbeeld hoe veld 98.1.0 er uit zou moeten zien; het is één regel

0-0:98.1.0(3)(1-0:1.6.0)(1-0:1.6.0)(200501000000S)(200423192538S)(03.695*kW)(200401000000S)(200305122139S)(05.980*kW)(200301000000S)(200210035421W)(04.318*kW)

Bron: voorbeeld in https://maconsosouslaloupe.be/rails/active_storage/blobs/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBaEVEIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--1f4b7076418510df362a0eb5b03db812ed40a289/e-MUCS_P1_Ed_1_7_1.pdf%3Fdisposition%3Dattachment&ved=2ahUKEwjDqbDg4OKJAxU0IrkGHR5XDqEQFnoECBkQAQ&usg=AOvVaw2iRGwZ-rmH5yClihLVKo1L

Nu weet ik niet zeker waarom dat gebeurt. Eén van de redenen kan het gebruik van AltSoftSerial zijn en de noodzakelijke omzetting van 8 bits naar 7 bits. Als je een andere Arduino hebt die een vrije poort heeft (Leonardo, Micro, Mega) is het zonder meer aan te bevelen om die te gebruiken.

Of je zult de Rx en Tx van de Uno moeten gebruiken; maar dat wordt een ander verhaal aangezien je dan de optie om te debuggen kwijt raakt.

Een opmerking in één van de bibliotheken die ik gedownload heb:

Ik zal vandaag proberen een programma met een filter te schrijven.

Ik heb nog een atmega328p liggen van mijn eerste poging. Toen kreeg ik geen data. Ik probeer hem nog eens aan te sluiten en stuur eventuele data door.

Bedankt op voorhand voor de hulp

Greg

Dat gaat niet helpen; het voorstel is om een Arduino te gebruiken met een vrije seriële poort zodat je AltSoftASerial niet hoeft te gebruiken.

Goed, dan krijg je ook een stoom cursus in C programmeren voor communicatie en gebruik van tekst :smiley:

Ik gebruik Serial3 op een Mega. Om het op een Uno te draaien met AltSoftSerial moet je de eerste regel hieronder veranderen van //#define USE_ALTSOFTSERIAL naar #define USE_ALTSOFTSERIAL.

//#define USE_ALTSOFTSERIAL

#ifdef USE_ALTSOFTSERIAL
#include <AltSoftSerial.h>
AltSoftSerial altSerial;
#define P1Serial altSerial
#else
#define P1Serial Serial3
#endif

Als USE_ALTSOFTSERIAL gedefinieerd is wordt die bibliotheek gebruikt, je altSerial object aangemaakt en er wordt een "alias" P1Serial gemaakt voor altSerial.
Als USE_ALTSOFTSERIAL niet gedefinieerd is, wordt er alleen een "alias" gemaakt voor Serial3.

Door de eerste regel te veranderen kun je kiezen tussen altSerial en Serial3 en overal in het programma kun je nu P1Serial gebruiken.

Vervolgens zijn er twee globale variabelen.

// field for filter
const char fieldOfInterest[] = "1-0:2.7.0";
// P1 receive buffer
char buffer[200];

De buffer is wat groot gekozen; 50 zal waarschijnlijk groot genoeg zijn.
Vervolgens de setup() functie.

void setup()
{
  Serial.begin(115200);
#ifdef USE_ALTSOFTSERIAL
  P1Serial.begin(115200);
#else
  P1Serial.begin(115200, SERIAL_7E1);
#endif
}

Bijna hetzelfde als die je nu gebruikt maar gebaseerd op USE_ALTSOFTSERIAL wordt Serial3 (P1Serial) geïnitialiseerd voor het 7E1 formaat.

En dan loop().

void loop()
{
  // if there is data
  if (P1Serial.available())
  {
    // read and filter
    if (readP1withFilter() == true)
    {
      // print the received record
      Serial.print("Field data received: ");
      Serial.println(buffer);
      // parse the data and get the float value
      float value;
      // on success
      if (parse(buffer, value) == true)
      {
        // print it
        Serial.print("The value in the record = ");
        Serial.println(value, 3);
      }
    }
    else
    {
      Serial.println("Telegram error");
    }
  }
}

Er wordt gekeken of er data beschikbaar is van de P1 meter. Indien dat het geval is wordt de functie readP1withFilter() aangeroepen die het eigenlijke lezen en filteren doet. Als het lezen en filteren goed gegaan is wordt de ontvange data afgedrukt en vervolgen wordt de ontvangen data verwerkt met de parse() functie. Als het verwerken goed gegaan is wordt de waarde die gevonden is afgedrukt. Behalve afdrukken kun je ook iets met die waarde doen.

/*
  Read P1 and filter
  Returns:
    true if field of interest was found, else false
*/
bool readP1withFilter()
{
  // index where to store character in buffer 
  static uint16_t index;
  // return value
  bool rb = false;

  char c = P1Serial.read();
#ifdef USE_ALTSOFTSERIAL
  // --- 7 bits instelling ---
  c &= ~(1 << 7);
  char inChar = (char)c;
#endif

  if (index == 0)
  {
    // clear the buffer
    memset(buffer, '\0', sizeof(buffer));
  }

  buffer[index] = c;
  // get the field of interest
  if (index < strlen(fieldOfInterest))
  {
    // if there is a partial match with the field of interest
    if (buffer[index] == fieldOfInterest[index])
    {
      // next received character to next location in buffer
      index++;
    }
    // if there is a mismatch
    else
    {
      // reset the index.
      index = 0;
    }

    //Serial.write(c);
  }
  // we got a matching field of interest
  else
  {
    // next received character to next location in buffer
    index++;
    // prevent buffer overflow
    if (index == sizeof(buffer) - 1)
    {
      Serial.println("Buffer overflow");
      // reset the index
      index = 0;
    }
    // if the received character is the line feed that indicates the end of the record
    if (c == '\n')
    {
      // remove trailing CR and LF
      trimRight(buffer);
      // reset the index
      index = 0;
      // indicate that we got filtered data
      rb = true;
    }
  }

  return rb;
}

Eerst worden er twee variabelen gedeclareerd; de eerst is static hetgeen betekent dat deze de waarde behoudt als de functie eindigt. De tweede is een simpele bool die bijhoudt of er iets fout gegaan is of niet.

Vervolgens wordt één ontvangen karakter gelezen. Je zult de functie continu moeten aanroepen zolang er karakters bescjikbaar zijn. Indien nodig wordt dat karakter aangepast voor het gebruik van de AltDoftSerial bibliotheek.

Om te voorkomen dat de data in de buffer niet korrekt is wordt de buffer gewist als de index nul is.

Vervolgens wordt dat karakter in de buffer gezet.

Daarna vindt het controleren op de OBIS referentie gedaan; als het karakter in de buffer overeen komt met een karakter in dezelfde plaats in fieldOfInterest wordt er doorgegaan na het verhogen van de index, anders wordt de index weer op nul gezet en er van het begin af begonnen.

Als uiteindelijk het fieldOFInterest gevonden is wordt de rest van de data gelezen (iedere keer één karakter per keer) totdat er een LF (einde van de regel) wordt gezien of totdat er geen plaats meer is in de buffer.

Als er een LF gezien is wordt de inhoud van de buffer ontdaan van de van de mogelijke CR en LF op het eind en de waarde van rb wordt op true gezet.

rb wordt terug gegeven aan de aanroepende functie (loop()) zodat die weet wanneer er geschikte data gevonden is.

Nu loop() weet dat er geschikte data is wordt de parse() functie aangeroepen.

/*
  Parse received field
  In:
    text to parse; will be mangled after parsing
  Out:
    float value of record
  Returns:
    false on error, else true
*/
bool parse(char *b, float &f)
{
  char *key = b;
  char *value;
  char *units;

  // return value
  bool rb = false;

  // check if record ends with ')'
  value = strrchr(b, ')');
  // check if found
  if (value != nullptr)
  {
    // remove
    *value = '\0';

    // find opening '('
    value = strchr(b, '(');
    // check if found
    if (value != nullptr)
    {
      // split key and value
      *value = '\0';
      // point to next character
      value++;
      // print it

      units = strchr(value, '*');
      if (units != nullptr)
      {
        *units = '\0';
        units++;
        Serial.print("Key = ");
        Serial.println(key);
        Serial.print("Value = ");
        //Serial.println(value);
        rb = text2float(value, f);
        if (rb == true)
        {
          Serial.println(f, 4);
          Serial.print("Units = ");
          Serial.println(units);
        }
      }
    }
  }
  return rb;
}

De functie heeft twee argumenten, het karakter array met de tekst die gesplits moet worden en een float waar de uiteindelijke waarde in wordt opgeslagen.

We hebben weer wat variabelen; dit zijn pointers die gebruikt gaan worden om bepaalde stukjes uit de ontvagen tekst te halen; ze wijzen naar die tekst.

Om te begrijpen hoe tekst werkt in een C programma moet je weten dat het een array van karakters is die is afgesloten met een nul karakter (nul, '\0'). Bv "1-0:2.7.0(00.203*kW)"is opgeslagen als

'1' '-' '0' ':' '2' '.' '7' '.' '0' '(' '0' '0' '.' '2' '0' '3' '*' 'k' 'W' ')' 0

De pointer key wijst simpel naar het begin van de tekst, de andere twee worden "berekend".

Eerst wordt er gecontroleerd of er een afsluitend ')' is. Indien dat het geval is wordt de ')' verwijderd door deze te vervangen door het nul-karakter dat het eind van een tekst aangeeft. Het zier er nu zo uit

'1' '-' '0' ':' '2' '.' '7' '.' '0' '(' '0' '0' '.' '2' '0' '3' '*' 'k' 'W' 0 0
^key                                                                        x

De x geeft het veranderde karakter aan, ^ geeft de naam van de pointer
Vervolgens wordt er gezocht naar de openings '('; ook die wordt verwijderd (vervangen door het nul karakter) en vervolgens wordt de pointer met 1 opgehoogd zodat deze nu naar het karakter na de originale '(' wijst; dit is de pointer value. Omdat we de '(' hebben verwijderd (vervangen door '\0') ziet het er nu zo uit

'1' '-' '0' ':' '2' '.' '7' '.' '0' 0 '0' '0' '.' '2' '0' '3' '*' 'k' 'W' 0 0
^key                                |                                     x
                                    x  ^value

Daarna wordt er gezocht naar de '*' en deze wordt verwijderd op dezelfde manier en de pointer wordt met 1 verhoogd zodat deze nu naar het karakter na het veranderde karakter wijst.

'1' '-' '0' ':' '2' '.' '7' '.' '0' 0 '0' '0' '.' '2' '0' '3' 0 'k' 'W' 0 0
^key                                |                         |         x
                                    x  ^value                 |
                                                              * ^units

We hebben nu (dus) een key "1-0:2.7.0", een value "00.203" en units "kW".

De value tekst wordt vervolgens omgezet in een float met de functie text2float(). Deze functie neemt weer twee argumenten, de tekst die omgezet moet worden (bv "00.203") en de variabele waar het resultaat in moet worden opgeslagen. Als je wilt begrijpen hoe het werkt kun je bv https://cplusplus.com/reference/cstdlib/strtod/ lezen (het is in het Engels, geen idee of dat een probleem is).

/*
  Convert text to float
  In:
    text to convert
  Out:
    float value 
  Returns:
    false on error, else true
*/
bool text2float(char *txt, float &f)
{
  // return value
  bool rb = false;

  char *endptr;
  f = strtod(txt, &endptr);

  if (endptr == txt || *endptr != '\0')
  {
    Serial.println("Not a float value");
  }
  else
  {
    rb = true;
  }

  return rb;
}

En als laatste de trimRight() functie; deze heeft één argument, de tekst die opgeschoond moet worden; de opgeschoonde tekst staat weer in hetzelfde argument.

  Remove CR and/or LF from text
  In:
    text to trim; will be modified
*/
void trimRight(char *txt)
{
  while (txt[strlen(txt) - 1] == '\r' || txt[strlen(txt) - 1] == '\n')
  {
    txt[strlen(txt) - 1] = '\0';
  }
}

Deze functie kijkt simpelweg (in een while-loop) naar het laatste karakter en als dat een CR of een LF is wordt dat karakter weer vervangen door een nul-karakter.

Het volledige programma

//#define USE_ALTSOFTSERIAL

#ifdef USE_ALTSOFTSERIAL
#include <AltSoftSerial.h>
AltSoftSerial altSerial;
#define P1Serial altSerial
#else
#define P1Serial Serial3
#endif

// field for filter
const char fieldOfInterest[] = "1-0:2.7.0";
// P1 receive buffer
char buffer[200];

void setup()
{
  Serial.begin(115200);
#ifdef USE_ALTSOFTSERIAL
  P1Serial.begin(115200);
#else
  P1Serial.begin(115200, SERIAL_7E1);
#endif
}

void loop()
{
  // if there is data
  if (P1Serial.available())
  {
    // read and filter
    if (readP1withFilter() == true)
    {
      // print the received record
      Serial.print("Field data received: ");
      Serial.println(buffer);
      // parse the data and get the float value
      float value;
      // on success
      if (parse(buffer, value) == true)
      {
        // print it
        Serial.print("The value in the record = ");
        Serial.println(value, 3);
      }
    }
  }
}

/*
  Read P1 and filter
  Returns:
    true if field of interest was found, else false
*/
bool readP1withFilter()
{
  // index where to store character in buffer 
  static uint16_t index;
  // return value
  bool rb = false;

  char c = P1Serial.read();
#ifdef USE_ALTSOFTSERIAL
  // --- 7 bits instelling ---
  c &= ~(1 << 7);
  char inChar = (char)c;
#endif

  if (index == 0)
  {
    // clear the buffer
    memset(buffer, '\0', sizeof(buffer));
  }

  buffer[index] = c;
  // get the field of interest
  if (index < strlen(fieldOfInterest))
  {
    // if there is a partial match with the field of interest
    if (buffer[index] == fieldOfInterest[index])
    {
      // next received character to next location in buffer
      index++;
    }
    // if there is a mismatch
    else
    {
      // reset the index.
      index = 0;
    }

    //Serial.write(c);
  }
  // we got a matching field of interest
  else
  {
    // next received character to next location in buffer
    index++;
    // prevent buffer overflow
    if (index == sizeof(buffer) - 1)
    {
      Serial.println("Buffer overflow");
      // reset the index
      index = 0;
    }
    // if the received character is the line feed that indicates the end of the record
    if (c == '\n')
    {
      // remove trailing CR and LF
      trimRight(buffer);
      // reset the index
      index = 0;
      // indicate that we got filtered data
      rb = true;
    }
  }

  return rb;
}

/*
  Remove CR and/or LF from text
  In:
    text to trim; will be modified
*/
void trimRight(char *txt)
{
  while (txt[strlen(txt) - 1] == '\r' || txt[strlen(txt) - 1] == '\n')
  {
    txt[strlen(txt) - 1] = '\0';
  }
}

/*
  Parse received field
  In:
    text to parse; will be mangled after parsing
  Out:
    float value of record
  Returns:
    false on error, else true
*/
bool parse(char *b, float &f)
{
  char *key = b;
  char *value;
  char *units;

  // return value
  bool rb = false;

  // check if record ends with ')'
  value = strrchr(b, ')');
  // check if found
  if (value != nullptr)
  {
    // remove
    *value = '\0';

    // find opening '('
    value = strchr(b, '(');
    // check if found
    if (value != nullptr)
    {
      // split key and value
      *value = '\0';
      // point to next character
      value++;
      // print it

      units = strchr(value, '*');
      if (units != nullptr)
      {
        *units = '\0';
        units++;
        Serial.print("Key = ");
        Serial.println(key);
        Serial.print("Value = ");
        //Serial.println(value);
        rb = text2float(value, f);
        if (rb == true)
        {
          Serial.println(f, 4);
          Serial.print("Units = ");
          Serial.println(units);
        }
      }
    }
  }
  return rb;
}

/*
  Convert text to float
  In:
    text to convert
  Out:
    float value 
  Returns:
    false on error, else true
*/
bool text2float(char *txt, float &f)
{
  // return value
  bool rb = false;

  char *endptr;
  f = strtod(txt, &endptr);

  if (endptr == txt || *endptr != '\0')
  {
    Serial.println("Not a float value");
  }
  else
  {
    rb = true;
  }

  return rb;
}

En de typisch afdruk in bv de seriële monitor

Field data received: 1-0:2.7.0(00.204*kW)
Key = 1-0:2.7.0
Value = 0.2040
Units = kW
The value in the record = 0.204

De Value wordt afgedrukt met 4 decimalen, de waarde op de laatste regel wordt afgedrukt met 3 decimalen; dit om je te laten zien hoe je het aantal decimalen kunt aanpassen. Dit heeft geen invloed op de daadwerkelijke waarde.

Opmerking
Ik gebruik een Arduino Leonardo om een P1 meter te simuleren; met een druk op de knop stuurt die een telegraam naar de Mega. Ik heb ook andere versies die data kunnen pompen maar die moet ik even terug vinden indien noodzakelijk.

Als je vragen hebt hoor ik het wel.

Je zult moeten kijken of je foutmeldingen krijgt met betrekking tot het lezen, het filteren en het omzetten naar een float. Laat het een poosje draaien; een ander terminal programma (zoals eerder vermeld) kan handig zijn zodat je het een poosje kunt laten draaien en de gegevens naar een file worden heschreven die je later kunt bekijken.

Ik zal proberen deze week een wat betere versie te schrijven die controleert of er mogelijke corruptie heeft plaats gevonden door de CRC te controleren.

Goed bezig Sterretje.
Ik ga dit wel volgen want kan daar ook vast weer wat van opsteken.

Onderstand programma zou je kunnen gebruiken om uit te vinden hoe betrouwbaar de oplossing met AltSoftSerial is.

Om het geheel overzichtelijk te houden zijn er twee functies, readP1() en reader() en een paar hulp functies; de hulp functies zitten in apart bestanden.

readP1() zou eenvoudig te begrijpen moeten zijn. De enige reden dat deze functie bestaat is omdate je een software benadering hebt voor de meter.

/*
  Read character from meter
  Returns:
    -1 if no character available, else character
*/
int readP1()
{
  // if there is data
  if (P1Serial.available())
  {
    char c = P1Serial.read();

#ifdef USE_ALTSOFTSERIAL
    // --- 7 bits instelling ---
    c &= ~(1 << 7);
    char inChar = (char)c;
#endif

    return c;
  }
  else
  {
    return -1;
  }
}

Ik weet niet hoeveel je van het DSMR protocol weet. Een telegram (ik weet niet meer waar ik die naam gevonden heb, ergens in de specificaties als ik me niet vergis) begint met een '/' gevolgd door een hoop data gevolgd door een '!' gevolgd door 4 karakter voor de CRC. Dat ziet er dus ongeveer zo uit

/......................!xxxx

reader() implementeert een zogenaamde finite state machine. Er zijn een paar states (stappen).

  1. Wachten op het start karakter '/'. Zodra die gevonden is wordt er naar de volgende stap gegaan.
  2. Lezen van de ontvangen karakters totdat er een '!' gevonden wordt. Daarna wordt er naar de volgende stap gegaan die de CRC leest.
  3. Lezen van de 4 karakter CRC.
  4. Controleren of de CRC overeenkomt met een berekende CRC.

Tijdens de stappen (1) en (2) wordt een CRC berekend over de ontvangen karakters.

Het begin van het programma ziet er als volgt uit


#ifdef USE_ALTSOFTSERIAL
#include <AltSoftSerial.h>
AltSoftSerial altSerial;
#define P1Serial altSerial
#else
#define P1Serial Serial3
#endif

// to follow the process in the reader, uncomment the below line
//#define DEBUG_READER

// include crc calculation
#include "crc16.h"

// the states that the reader can be in
enum READSTATES
{
  WAITFORSTART,
  READ,
  GETCRC,
  VERIFY,
};

De #include gebruikt een iets ander formaat dan degene die je eerder gebruikt hebt voor AltSoftSerial; door gebruik te maken van dubbele aanhalingtekens wordt er eerst naar de file gezocht in de sketch directory.

De DEBUG_READER kun je gebruiken om te volgen hoe de reader() werkt.

De verschillende stappen (READSTATES) in de reader() zijn gedefiniëerd in een enum; een enum is een type (zoals int of char) die je zelf kunt definiëren en alleen bepaalde waardes kent.

Om er voor te zorgen dat loop() kan volgen wat er precies gebeurt in reader() functie geeft de reader() functie een waarde terug. Dit is weer een enum.

// status of the reader; used as return value of the read proces
enum P1STATUS
{
  OK,
  WAITING,
  READING,
  VERIFYING,
  ERR_TIMEOUT,
  ERR_CRC,
};

OK geeft aan dat een volledig telegram is ontvangen zonder fouten.
ERR_CRC geeft aan dat er een CRC fout is gedetecteerd en het telegram weggegooid moet worden omdat je niet weet wat er corrupt is.
ERR_TIMEOUT geeft aan dat er een timeout is opgetreden.
WAITING geeft aan dat de functie aan het wachten is op een '/'.
READING geeft aan dat de '/' gevonden is en opvolgende karakters gelezen worden todat er een '!' gezien wordt.
VERIFYING geeft aan dat de ontvangen crc wordt vergeleken met de berekende crc.

De reader() functie heeft een argument; het is het karakter dat gelezen is met readP1().

/*
  Process received characters
  In:
    character that was read from the P1 meter
  Returns:
    status of the reader
*/
P1STATUS reader(char c)
{
  // current state
  static READSTATES state = WAITFORSTART;
  // just 4 + 1 bytes to store crc
  static char crc[] = "0000";
  // index where to store a crc character
  static uint8_t crcIndex;

  // set the default return status
  P1STATUS rs = WAITING;

  // start time when start marker was received
  static uint32_t startTime;

  // if a timeout occured
  if (state != WAITFORSTART && state != VERIFY && millis() - startTime > timeoutDuration)
  {
    rs = ERR_TIMEOUT;
    state = WAITFORSTART;
    return rs;
  }

  switch (state)
  {
    case WAITFORSTART:
      // wait for the start marker ('/')
      rs = WAITING;
      if (c == '/')
      {
        // remember when we received the first character
        startTime = millis();
        // clear the crc
        clearCrc();
        // add received character to crc
        updateCrc(c);
        // switch to next state
#ifdef DEBUG_READER
        Serial.print(__FUNCTION__);
        Serial.println(F(" -> Switching to READ"));
#endif
        state = READ;
        rs = READING;
      }
      break;
    case READ:
      // process received character
      rs = READING;

      if (c == -1)
      {
        return rs;
      }
      // add to crc
      updateCrc(c);
      // if end marker ('!')
      if (c == '!')
      {
        // clear index in crc array so it's filled from the start
        crcIndex = 0;
        // switch to next state
#ifdef DEBUG_READER
        Serial.print(__FUNCTION__);
        Serial.println(F(" -> Switching to GETCRC"));
#endif
        state = GETCRC;
      }
      break;
    case GETCRC:
      // read crc
      rs = READING;

      if (c == -1)
      {
        return rs;
      }

      crc[crcIndex++] = c;
      // if we have 4 characters for the crc
      if (crcIndex == sizeof(crc) - 1)
      {
        // switch to next state
#ifdef DEBUG_READER
        Serial.print(__FUNCTION__);
        Serial.println(F(" -> Switching to VERIFY"));
#endif
        state = VERIFY;
        rs = VERIFYING;
      }
      break;
    case VERIFY:
      // verify received crc
      rs = VERIFYING;
#ifdef DEBUG_READER
      Serial.print(__FUNCTION__);
      Serial.print(F("-> Received CRC = "));
      Serial.println(crc);
      Serial.print(__FUNCTION__);
      Serial.print(F("-> Calculated CRC = "));
      Serial.println(getCrcAsText());
#endif

      // if there is a mismatch
      if (strcmp(crc, getCrcAsText()) != 0)
      {
        rs = ERR_CRC;
      }
      else
      {
        rs = OK;
      }
#ifdef DEBUG_READER
      Serial.print(__FUNCTION__);
      Serial.println(F(" -> Switching to WAITFORSTART"));
#endif
      state = WAITFORSTART;
      break;
  }

  return rs;
}

In loop() kun je het als volgt gebruiken.

void loop()
{
  //error counters
  static uint32_t crcCnt;
  static uint32_t timeoutCnt;

  // previous result
  static P1STATUS prevResult = VERIFYING;
  // receive and process character
  P1STATUS result = reader(readP1());

  // if there is a change in the result
  if (prevResult != result)
  {
    // remember
    prevResult = result;

    // check result
    switch (result)
    {
      /*
      case WAITING:
        Serial.print(__FUNCTION__);
        Serial.println(F(" -> Waiting"));
        break;
      case READING:
        Serial.print(__FUNCTION__);
        Serial.println(F(" -> Reading"));
        break;
      case VERIFYING:
        Serial.print(__FUNCTION__);
        Serial.println(F(" -> Verifying"));
        break;
        */
      case ERR_TIMEOUT:
        timeoutCnt++;
        Serial.print(__FUNCTION__);
        Serial.print(F(" -> Timeout error !! "));
        Serial.print(F(" Count = "));
        Serial.println(timeoutCnt);
        break;
      case ERR_CRC:
        crcCnt++;
        Serial.print(__FUNCTION__);
        Serial.print(F(" -> CRC error !!"));
        Serial.print(F(" Count = "));
        Serial.println(crcCnt);
        break;
      case OK:
        Serial.print(__FUNCTION__);
        Serial.println(F(" -> OK, ready to process"));
        break;
        break;
      default:
        break;
    }
  }
}

Er zijn twee tellers die het aantal fouten bijhouden voor timeout en crc. Vervolgens zijn er twee variabelen voor de status van de reader().
prevResult bevat het resultaat van de vorige aanroep van reader() en wordt gebruikt om een verandering in de status te detecteren van de huidige aanroep van reader(). Door alleen te reageren op veranderingen heb je geen scherm vol met herhalingen en op deze manier kun je je een beetje concentreren op de de verandering.

Gebaseerd op het (veranderde) resultaat wordt er vervolgens een bericht op je scherm afgedrukt

Het volledige hoofd programma (het ino bestand)


#ifdef USE_ALTSOFTSERIAL
#include <AltSoftSerial.h>
AltSoftSerial altSerial;
#define P1Serial altSerial
#else
#define P1Serial Serial3
#endif

// to follow the process in the reader, uncomment the below line
//#define DEBUG_READER

#include "crc16.h"

// the states that the reader can be in
enum READSTATES
{
  WAITFORSTART,
  READ,
  GETCRC,
  VERIFY,
};

// status of the reader; used as return value of the read proces
enum P1STATUS
{
  OK,
  WAITING,
  READING,
  VERIFYING,
  ERR_TIMEOUT,
  ERR_CRC,
};

// timeout duration
const uint16_t timeoutDuration = 9000;

void setup()
{
  Serial.begin(115200);
#ifdef USE_ALTSOFTSERIAL
  P1Serial.begin(115200);
#else
  P1Serial.begin(115200, SERIAL_7E1);
#endif

  Serial.print(__FUNCTION__);
  Serial.println(F(" -> P1 validator 0.1"));
  Serial.print(__FUNCTION__);
  Serial.println(F(" -> reader() starting with WAITFORSTART"));
}

void loop()
{
  //error counters
  static uint32_t crcCnt;
  static uint32_t timeoutCnt;

  // previous result
  static P1STATUS prevResult = VERIFYING;
  // receive and process character
  P1STATUS result = reader(readP1());

  // if there is a change in the result
  if (prevResult != result)
  {
    // remember
    prevResult = result;

    // check result
    switch (result)
    {
      /*
      case WAITING:
        Serial.print(__FUNCTION__);
        Serial.println(F(" -> Waiting"));
        break;
      case READING:
        Serial.print(__FUNCTION__);
        Serial.println(F(" -> Reading"));
        break;
      case VERIFYING:
        Serial.print(__FUNCTION__);
        Serial.println(F(" -> Verifying"));
        break;
        */
      case ERR_TIMEOUT:
        timeoutCnt++;
        Serial.print(__FUNCTION__);
        Serial.print(F(" -> Timeout error !! "));
        Serial.print(F(" Count = "));
        Serial.println(timeoutCnt);
        break;
      case ERR_CRC:
        crcCnt++;
        Serial.print(__FUNCTION__);
        Serial.print(F(" -> CRC error !!"));
        Serial.print(F(" Count = "));
        Serial.println(crcCnt);
        break;
      case OK:
        Serial.print(__FUNCTION__);
        Serial.println(F(" -> OK, ready to process"));
        break;
        break;
      default:
        break;
    }
  }
}

/*
  Read character from meter
  Returns:
    -1 if no character available, else character
*/
int readP1()
{
  // if there is data
  if (P1Serial.available())
  {
    char c = P1Serial.read();

#ifdef USE_ALTSOFTSERIAL
    // --- 7 bits instelling ---
    c &= ~(1 << 7);
    char inChar = (char)c;
#endif

    return c;
  }
  else
  {
    return -1;
  }
}

/*
  Process received characters
  In:
    character that was read from the P1 meter
  Returns:
    status of the reader
*/
P1STATUS reader(char c)
{
  // current state
  static READSTATES state = WAITFORSTART;
  // just 4 + 1 bytes to store crc
  static char crc[] = "0000";
  // index where to store a crc character
  static uint8_t crcIndex;

  // set the default return status
  P1STATUS rs = WAITING;

  // start time when start marker was received
  static uint32_t startTime;

  // if a timeout occured
  if (state != WAITFORSTART && state != VERIFY && millis() - startTime > timeoutDuration)
  {
    rs = ERR_TIMEOUT;
    state = WAITFORSTART;
    return rs;
  }

  switch (state)
  {
    case WAITFORSTART:
      // wait for the start marker ('/')
      rs = WAITING;
      if (c == '/')
      {
        // remember when we received the first character
        startTime = millis();
        // clear the crc
        clearCrc();
        // add received character to crc
        updateCrc(c);
        // switch to next state
#ifdef DEBUG_READER
        Serial.print(__FUNCTION__);
        Serial.println(F(" -> Switching to READ"));
#endif
        state = READ;
        rs = READING;
      }
      break;
    case READ:
      // process received character
      rs = READING;

      if (c == -1)
      {
        return rs;
      }
      // add to crc
      updateCrc(c);
      // if end marker ('!')
      if (c == '!')
      {
        // clear index in crc array so it's filled from the start
        crcIndex = 0;
        // switch to next state
#ifdef DEBUG_READER
        Serial.print(__FUNCTION__);
        Serial.println(F(" -> Switching to GETCRC"));
#endif
        state = GETCRC;
      }
      break;
    case GETCRC:
      // read crc
      rs = READING;

      if (c == -1)
      {
        return rs;
      }

      crc[crcIndex++] = c;
      // if we have 4 characters for the crc
      if (crcIndex == sizeof(crc) - 1)
      {
        // switch to next state
#ifdef DEBUG_READER
        Serial.print(__FUNCTION__);
        Serial.println(F(" -> Switching to VERIFY"));
#endif
        state = VERIFY;
        rs = VERIFYING;
      }
      break;
    case VERIFY:
      // verify received crc
      rs = VERIFYING;
#ifdef DEBUG_READER
      Serial.print(__FUNCTION__);
      Serial.print(F("-> Received CRC = "));
      Serial.println(crc);
      Serial.print(__FUNCTION__);
      Serial.print(F("-> Calculated CRC = "));
      Serial.println(getCrcAsText());
#endif

      // if there is a mismatch
      if (strcmp(crc, getCrcAsText()) != 0)
      {
        rs = ERR_CRC;
      }
      else
      {
        rs = OK;
      }
#ifdef DEBUG_READER
      Serial.print(__FUNCTION__);
      Serial.println(F(" -> Switching to WAITFORSTART"));
#endif
      state = WAITFORSTART;
      break;
  }

  return rs;
}

Het benodigde crc16.h bestand

#ifndef __CRC16_H__
#define __CRC16_H__


void clearCrc();
uint16_t updateCrc(uint8_t data);
char *getCrcAsText();

#endif

Het benodigde crc16.cpp bestand

/*
  source: https://github.com/matthijskooijman/arduino-dsmr/blob/master/src/dsmr/crc16.h
*/

// Arduino.h needed to be able to use e.g. uint16_t
#include <Arduino.h>

// the polynomial for the CRC
const uint16_t polynomial = 0xA001;
// buffer to store text version of CRC
static char crcTxt[5];
// hold calculated CRC
static uint16_t crc;

/*
  get crc as text in hex; format %04X (e.g. 0A23)
  Returns
    pointer to stored crc in hex format
*/
char *getCrcAsText()
{
  memset(crcTxt, '\0', sizeof(crcTxt));
  snprintf(crcTxt, sizeof(crcTxt), "%04X", crc);
  return crcTxt;
}

/*
  calculate crc on the fly
  In:
    byte to 'add'
  Returns:
    updated crc
*/
uint16_t updateCrc(uint8_t data)
{
  unsigned int i;

  crc ^= data;
  for (i = 0; i < 8; ++i)
  {
    if (crc & 1)
    {
      crc = (crc >> 1) ^ polynomial;
    }
    else
    {
      crc = (crc >> 1);
    }
  }
  return crc;
}

/*
  clear crc; needed when new telegram arrives
*/
void clearCrc()
{
  crc = 0;
}

Om het gemakkelijk te maken voor je heb ik de drie bestand in een ZIP gezet.

P1_CRCcheck.zip (2.8 KB)

Typische afdruk in de seriële monitor

setup -> P1 validator 0.1
setup -> reader() starting with WAITFORSTART
loop -> OK, ready to process
loop -> OK, ready to process
loop -> CRC error !! Count = 1
loop -> CRC error !! Count = 2
loop -> OK, ready to process
loop -> OK, ready to process
loop -> CRC error !! Count = 3

Zoals gezegd, dit is alleen om de benadering met AltSoftSerial te testen. Je zou geen timeout fouten mogen krijgen (of slechts af en toe) en definitief geen crc fouten. "ready to process" heeft geen echte functie omdat een P1 telegram niet wordt opgeslagen.

Ik heb net mijn simulator een beetje aangepast en gevonden dat er een klein foutje in het bovenstaande programma zit :frowning:

Je zult const uint16_t timeoutDuration = 9000; moeten veranderen naar const uint16_t timeoutDuration = 900;; nulletje teveel.

Hoe het werkt

  1. Als de start ('/') wordt gedetecteerd wordt een stopwatch gestart door de millis() op te slaan in startTime.
  2. Daarna wordt er doorlopend gekeken of een zekere tijd verstreken is (900 ms) tijdens het lezen van het telegram.
  3. Als dat het geval is heb je een timeout.

De 900 ms is gekozen gebaseerd op het feit dat je iedere seconde (1000 ms) een telegram krijgt.

Beste

Mijn hoofd draait nog even na het lezen van de tekst ;).

Ik heb de zip gedownload en geunzipped.

Daarna ino file geopend en dan de 2 andere files.

Bij testen en uploaden krijg ik dit.`

C:\Users\Cof\AppData\Local\Temp\f363891e-c646-4e52-98d0-27ab0afdb52a_P1_CRCcheck.zip.52a\P1_CRCcheck\P1_CRCcheck.ino: In function 'void setup()':
C:\Users\Cof\AppData\Local\Temp\f363891e-c646-4e52-98d0-27ab0afdb52a_P1_CRCcheck.zip.52a\P1_CRCcheck\P1_CRCcheck.ino:8:18: error: 'Serial3' was not declared in this scope
#define P1Serial Serial3
^
C:\Users\Cof\AppData\Local\Temp\f363891e-c646-4e52-98d0-27ab0afdb52a_P1_CRCcheck.zip.52a\P1_CRCcheck\P1_CRCcheck.ino:45:3: note: in expansion of macro 'P1Serial'
P1Serial.begin(115200, SERIAL_7E1);
^~~~~~~~
C:\Users\Cof\AppData\Local\Temp\f363891e-c646-4e52-98d0-27ab0afdb52a_P1_CRCcheck.zip.52a\P1_CRCcheck\P1_CRCcheck.ino:8:18: note: suggested alternative: 'Serial'
#define P1Serial Serial3
^
C:\Users\Cof\AppData\Local\Temp\f363891e-c646-4e52-98d0-27ab0afdb52a_P1_CRCcheck.zip.52a\P1_CRCcheck\P1_CRCcheck.ino:45:3: note: in expansion of macro 'P1Serial'
P1Serial.begin(115200, SERIAL_7E1);
^~~~~~~~
C:\Users\Cof\AppData\Local\Temp\f363891e-c646-4e52-98d0-27ab0afdb52a_P1_CRCcheck.zip.52a\P1_CRCcheck\P1_CRCcheck.ino: In function 'int readP1()':
C:\Users\Cof\AppData\Local\Temp\f363891e-c646-4e52-98d0-27ab0afdb52a_P1_CRCcheck.zip.52a\P1_CRCcheck\P1_CRCcheck.ino:8:18: error: 'Serial3' was not declared in this scope
#define P1Serial Serial3
^
C:\Users\Cof\AppData\Local\Temp\f363891e-c646-4e52-98d0-27ab0afdb52a_P1_CRCcheck.zip.52a\P1_CRCcheck\P1_CRCcheck.ino:121:7: note: in expansion of macro 'P1Serial'
if (P1Serial.available())
^~~~~~~~
C:\Users\Cof\AppData\Local\Temp\f363891e-c646-4e52-98d0-27ab0afdb52a_P1_CRCcheck.zip.52a\P1_CRCcheck\P1_CRCcheck.ino:8:18: note: suggested alternative: 'Serial'
#define P1Serial Serial3
^
C:\Users\Cof\AppData\Local\Temp\f363891e-c646-4e52-98d0-27ab0afdb52a_P1_CRCcheck.zip.52a\P1_CRCcheck\P1_CRCcheck.ino:121:7: note: in expansion of macro 'P1Serial'
if (P1Serial.available())
^~~~~~~~

exit status 1

Compilation error: 'Serial3' was not declared in this scope
`

De eerste regel van de ino zou //#define USE_ALTSOFTSERIAL moeten zijn en je moet de // verwijderen.

Heb je iets aangesloten op de pinnen 0 en 1 van je Uno?

Neen enkel 0v/5v en poort 8 voor data.

Correcte poort geselecteerd?
Welk besturings system gebruik je?