Hot water analog control via P1 meter XT211 (BE Fluvius) energy meter reading

@StefanL38 I am also new on this form and having issues with P1 port of my XT211 energy meter reading.
Before writing this message, I kept on reading as much as I could, to find a way to communicate with this unit. (I've re/started mid January, when my meter was changed from analog to Digital.)
I've read that the signal is inverted. So, purchased components suggested on web to correct that.
I've also read how to and to what pin to connect.

So, here is where I am / have:

So far all examples I've found are talking how to export the data to a computer or mobile phone / cloud / etc. Not what I want.
To me, an automation shall work independent and not requiring my attention.
I just want to read specific strings from the meter (like consumed power 2 or 3 values) and returned values (also 1-2 values). Do some mathematic with them. Mainly adding these values.
In later stage to export some values to a DO and / or a PWM.
I have a bit of knowledge of programing. Just syntax of C++ its killing.

But, one step at a time.
Will you be able to go along with me and help me to read the data from the meter?

For instance, I've taken the code from DSMR-BE-NL / read.

/*
 * Permission is hereby granted, free of charge, to anyone
 * obtaining a copy of this document and accompanying files,
 * to do whatever they want with them without any restriction,
 * including, but not limited to, copying, modification and redistribution.
 * NO WARRANTY OF ANY KIND IS PROVIDED.
 *
 * Example that shows how to periodically read a P1 message from a
 * serial port and automatically print the result.
*/

#include "dsmr.h"

/**
 * Define the data we're interested in, as well as the datastructure to
 * hold the parsed data. This list shows all supported fields, remove
 * any fields you are not using from the below list to make the parsing
 * and printing code smaller.
 * Each template argument below results in a field of the same name.
 */
using MyData = ParsedData<
  /* String */ identification,
  /* String */ p1_version,
  /* String */ timestamp,
  /* String */ equipment_id,
  /* FixedValue */ energy_delivered_tariff1,
  /* FixedValue */ energy_delivered_tariff2,
  /* FixedValue */ energy_returned_tariff1,
  /* FixedValue */ energy_returned_tariff2,
  /* String */ electricity_tariff,
  /* FixedValue */ power_delivered,
  /* FixedValue */ power_returned,
  /* FixedValue */ electricity_threshold,
  /* uint8_t */ electricity_switch_position,
  /* uint32_t */ electricity_failures,
  /* uint32_t */ electricity_long_failures,
  /* String */ electricity_failure_log,
  /* uint32_t */ electricity_sags_l1,
  /* uint32_t */ electricity_sags_l2,
  /* uint32_t */ electricity_sags_l3,
  /* uint32_t */ electricity_swells_l1,
  /* uint32_t */ electricity_swells_l2,
  /* uint32_t */ electricity_swells_l3,
  /* String */ message_short,
  /* String */ message_long,
  /* FixedValue */ voltage_l1,
  /* FixedValue */ voltage_l2,
  /* FixedValue */ voltage_l3,
  /* FixedValue */ current_l1,
  /* FixedValue */ current_l2,
  /* FixedValue */ current_l3,
  /* FixedValue */ power_delivered_l1,
  /* FixedValue */ power_delivered_l2,
  /* FixedValue */ power_delivered_l3,
  /* FixedValue */ power_returned_l1,
  /* FixedValue */ power_returned_l2,
  /* FixedValue */ power_returned_l3,
  /* uint16_t */ gas_device_type,
  /* String */ gas_equipment_id,
  /* uint8_t */ gas_valve_position,
  /* TimestampedFixedValue */ gas_delivered,
  /* uint16_t */ thermal_device_type,
  /* String */ thermal_equipment_id,
  /* uint8_t */ thermal_valve_position,
  /* TimestampedFixedValue */ thermal_delivered,
  /* uint16_t */ water_device_type,
  /* String */ water_equipment_id,
  /* uint8_t */ water_valve_position,
  /* TimestampedFixedValue */ water_delivered,
  /* uint16_t */ slave_device_type,
  /* String */ slave_equipment_id,
  /* uint8_t */ slave_valve_position,
  /* TimestampedFixedValue */ slave_delivered
>;

/**
 * This illustrates looping over all parsed fields using the
 * ParsedData::applyEach method.
 *
 * When passed an instance of this Printer object, applyEach will loop
 * over each field and call Printer::apply, passing a reference to each
 * field in turn. This passes the actual field object, not the field
 * value, so each call to Printer::apply will have a differently typed
 * parameter.
 *
 * For this reason, Printer::apply is a template, resulting in one
 * distinct apply method for each field used. This allows looking up
 * things like Item::name, which is different for every field type,
 * without having to resort to virtual method calls (which result in
 * extra storage usage). The tradeoff is here that there is more code
 * generated (but due to compiler inlining, it's pretty much the same as
 * if you just manually printed all field names and values (with no
 * cost at all if you don't use the Printer).
 */
struct Printer {
  template<typename Item>
  void apply(Item &i) {
    if (i.present()) {
      Serial.print(Item::name);
      Serial.print(F(": "));
      Serial.print(i.val());
      Serial.print(Item::unit());
      Serial.println();
    }
  }
};

// Set up to read from the second serial port, and use D2 as the request
// pin. On boards with only one (USB) serial port, you can also use
// SoftwareSerial.
#ifdef ARDUINO_ARCH_ESP32
// Create Serial1 connected to UART 1
HardwareSerial Serial1(1);
#endif
P1Reader reader(&Serial1, 2);

unsigned long last;

void setup() {
  Serial.begin(115200);
  Serial1.begin(115200);
  #ifdef VCC_ENABLE
  // This is needed on Pinoccio Scout boards to enable the 3V3 pin.
  pinMode(VCC_ENABLE, OUTPUT);
  digitalWrite(VCC_ENABLE, HIGH);
  #endif

  // start a read right away
  reader.enable(true);
  last = millis();
}

void loop () {
  // Allow the reader to check the serial buffer regularly
  reader.loop();

  // Every minute, fire off a one-off reading
  unsigned long now = millis();
  if (now - last > 60000) {
    reader.enable(true);
    last = now;
  }

  if (reader.available()) {
    MyData data;
    String err;
    if (reader.parse(&data, &err)) {
      // Parse succesful, print result
      data.applyEach(Printer());
    } else {
      // Parser error, print error
      Serial.println(err);
    }
  }
}

Expected output would look like below:

0-0:96.1.4(xxxxx)
0-0:96.1.1(xxxxxxxxxxxxxxxxxxxxxxxxxxxx)
0-0:1.0.0(210204163628W)
1-0:1.8.1(000439.094kWh)
1-0:1.8.2(000435.292kWh)
1-0:2.8.1(000035.805kWh)
1-0:2.8.2(000012.156kWh)
0-0:96.14.0(0001)
1-0:1.7.0(00.233kW)
1-0:2.7.0(00.000kW)
1-0:21.7.0(00.233kW)
1-0:22.7.0(00.000kW)
1-0:32.7.0(236.2V)
1-0:31.7.0(002.04A)
0-0:96.3.10(1)
0-0:17.0.0(999.9kW)
1-0:31.4.0(999A)
0-0:96.13.0()
0-1:24.1.0(003)
0-1:96.1.1(xxxxxxxxxxxxxxxxxxxxxxxxxxxx)
0-1:24.4.0(1)
0-1:24.2.3(210204163500W)(00343.925*m3)
!1374

If I run the code, I got an error like Serial1 is not used correct.
"read.ino:121:3: error: 'Serial1' was not declared in this scope"

And here ... I am stuck.

Thanks.
PS. If no time, I understand. I will not give up anyway....

The example is designed for a board with more than one hardware serial port, e.g. Arduino Mega

If you are using an Uno you need to adapt it to use a single serial port. Maybe not the best example to start from, but your life will be easy if you had a Mega.

I recommend using an pure ESP32, because the ESP32 has a real second hardware UART for both directions send and receive. The ESP32 like ESP8266 offers WiFi-connectivity.

I recommend to use a pure ESP32 because you can write code for them the exact same way as for "original" arduinos and for ESP32 there are a lot libraries and code-examples online.

I guess very high probability. You will have to specify what exact kind of interface that uses what kind of protocol. THere might be differencies around the world.

What sensor-module works best depends on the interface. You wil have to specifiy this as well

@bobcousins ok....
If I read your text ..."If you are using an Uno you need to adapt it to use a single serial port. "
then it looks like possible with ono also.

  • I have the hardware. So forced to take most out of it.
  • I do not need all data sent over. Just 2 values: 1-0:1.7.0(00.233kW) and 1-0:2.7.0(00.000kW) (Consumed power and injected power)
  • Have the numbers only.
  • Do some mathematic.
  • Export DO and / or PWM.
    So, possible?
    Any hint?

Seems quite easy. I'm not sure what you mean by "export to DO/PWM". What is DO?

By PWM I assume you meant output to some device controlled by PWM, like an LED.

@bobcousins DO = Digital output. I intend to control a relay (5V) which will control another relay (230) which is driving a socket to supply a resistive load. (When solar produced energy will be enough to supply the house and the full resistive load in same time. Just to get PWM out of circuit.)
With PWM I want to give to same load / resistor just the energy my solar panes are over producing after house is supplied. So, redirecting the energy "escaping" from house to a resistive load. (PWM converted to 0-5V via SSR - Solid Static Relay to load resistor).
But this part with output is solved.
My struggle is with energy meter data reading.
There is where I need the help.
Will you be able to draft a code to read that for one value? From there I will multiply to what I need.

Actually it seems the DSMR library will work with SoftwareSerial, so you can have 2 ports on the Uno.

I adapted the sketch to use SoftwareSerial for the meter, and will print output to Serial Monitor.

Connect pin 2 to RTS as before, but connect the meter RX to pin 3.

#include <SoftwareSerial.h>

/*
 * Permission is hereby granted, free of charge, to anyone
 * obtaining a copy of this document and accompanying files,
 * to do whatever they want with them without any restriction,
 * including, but not limited to, copying, modification and redistribution.
 * NO WARRANTY OF ANY KIND IS PROVIDED.
 *
 * Example that shows how to periodically read a P1 message from a
 * serial port and automatically print the result.
*/

#include "dsmr.h"

/**
 * Define the data we're interested in, as well as the datastructure to
 * hold the parsed data. This list shows all supported fields, remove
 * any fields you are not using from the below list to make the parsing
 * and printing code smaller.
 * Each template argument below results in a field of the same name.
 */
using MyData = ParsedData<
  /* String */ identification,
  /* String */ p1_version,
  /* String */ timestamp,
  /* String */ equipment_id,
  /* FixedValue */ energy_delivered_tariff1,
  /* FixedValue */ energy_delivered_tariff2,
  /* FixedValue */ energy_returned_tariff1,
  /* FixedValue */ energy_returned_tariff2,
  /* String */ electricity_tariff,
  /* FixedValue */ power_delivered,
  /* FixedValue */ power_returned,
  /* FixedValue */ electricity_threshold,
  /* uint8_t */ electricity_switch_position,
  /* uint32_t */ electricity_failures,
  /* uint32_t */ electricity_long_failures,
  /* String */ electricity_failure_log,
  /* uint32_t */ electricity_sags_l1,
  /* uint32_t */ electricity_sags_l2,
  /* uint32_t */ electricity_sags_l3,
  /* uint32_t */ electricity_swells_l1,
  /* uint32_t */ electricity_swells_l2,
  /* uint32_t */ electricity_swells_l3,
  /* String */ message_short,
  /* String */ message_long,
  /* FixedValue */ voltage_l1,
  /* FixedValue */ voltage_l2,
  /* FixedValue */ voltage_l3,
  /* FixedValue */ current_l1,
  /* FixedValue */ current_l2,
  /* FixedValue */ current_l3,
  /* FixedValue */ power_delivered_l1,
  /* FixedValue */ power_delivered_l2,
  /* FixedValue */ power_delivered_l3,
  /* FixedValue */ power_returned_l1,
  /* FixedValue */ power_returned_l2,
  /* FixedValue */ power_returned_l3,
  /* uint16_t */ gas_device_type,
  /* String */ gas_equipment_id,
  /* uint8_t */ gas_valve_position,
  /* TimestampedFixedValue */ gas_delivered,
  /* uint16_t */ thermal_device_type,
  /* String */ thermal_equipment_id,
  /* uint8_t */ thermal_valve_position,
  /* TimestampedFixedValue */ thermal_delivered,
  /* uint16_t */ water_device_type,
  /* String */ water_equipment_id,
  /* uint8_t */ water_valve_position,
  /* TimestampedFixedValue */ water_delivered,
  /* uint16_t */ slave_device_type,
  /* String */ slave_equipment_id,
  /* uint8_t */ slave_valve_position,
  /* TimestampedFixedValue */ slave_delivered >;

/**
 * This illustrates looping over all parsed fields using the
 * ParsedData::applyEach method.
 *
 * When passed an instance of this Printer object, applyEach will loop
 * over each field and call Printer::apply, passing a reference to each
 * field in turn. This passes the actual field object, not the field
 * value, so each call to Printer::apply will have a differently typed
 * parameter.
 *
 * For this reason, Printer::apply is a template, resulting in one
 * distinct apply method for each field used. This allows looking up
 * things like Item::name, which is different for every field type,
 * without having to resort to virtual method calls (which result in
 * extra storage usage). The tradeoff is here that there is more code
 * generated (but due to compiler inlining, it's pretty much the same as
 * if you just manually printed all field names and values (with no
 * cost at all if you don't use the Printer).
 */
struct Printer
{
  template<typename Item>
  void apply(Item &i)
  {
    if (i.present())
    {
      Serial.print(Item::name);
      Serial.print(F(": "));
      Serial.print(i.val());
      Serial.print(Item::unit());
      Serial.println();
    }
  }
};

// Set up to read from the serial port, and use D2 as the request
// pin. On boards with only one (USB) serial port, you can also use
// SoftwareSerial.

SoftwareSerial serialMeter (3, 4); // RX, TX

#ifdef ARDUINO_ARCH_ESP32
// Create Serial1 connected to UART 1
HardwareSerial Serial1(1);
#endif

P1Reader reader(&serialMeter, 2);

unsigned long last;

void setup()
{
  Serial.begin(115200);
  serialMeter.begin(115200);
#ifdef VCC_ENABLE
  // This is needed on Pinoccio Scout boards to enable the 3V3 pin.
  pinMode(VCC_ENABLE, OUTPUT);
  digitalWrite(VCC_ENABLE, HIGH);
#endif

  // start a read right away
  reader.enable(true);
  last = millis();
}

void loop()
{
  // Allow the reader to check the serial buffer regularly
  reader.loop();

  // Every minute, fire off a one-off reading
  unsigned long now = millis();
  if (now - last > 60000)
  {
    reader.enable(true);
    last = now;
  }

  if (reader.available())
  {
    MyData data;
    String err;
    if (reader.parse(&data, &err))
    {
      // Parse succesful, print result
      data.applyEach(Printer());
    }
    else
    {
      // Parser error, print error
      Serial.println(err);
    }
  }
}
1 Like

@bobcousins "fatal error: SoftwareSerial.h: No such file or directory"
I've briefly looked for, but could not find it. Can you share it?

It's one of the standard libraries, go to Sketch | Include Library then select "SoftwareSerial"

@bobcousins Ok. Thanks. Looks loaded now / the library. So far so good.
To be continued.
I will keep you updated. Might be next week-end. Only then I have time.

I just checked my meter; appears to be no port available.

I'd say Holland and Belgium did a better job than the UK when it comes to digital meters.

what does "no port available" mean?

  • no com-port?
  • no optical output?

Post a picture of your energymeter and the datasheet.

Does your energymeter at least have an LED that flashes 1000 times per kWh or whatever energy to flash ratio

@bobcousins Follow up.
In short, still not there, but making progress.

#include <SoftwareSerial.h>

/*
 * Permission is hereby granted, free of charge, to anyone
 * obtaining a copy of this document and accompanying files,
 * to do whatever they want with them without any restriction,
 * including, but not limited to, copying, modification and redistribution.
 * NO WARRANTY OF ANY KIND IS PROVIDED.
 *
 * Example that shows how to periodically read a P1 message from a
 * serial port and automatically print the result.
*/

#include "dsmr.h"

/**
 * Define the data we're interested in, as well as the datastructure to
 * hold the parsed data. This list shows all supported fields, remove
 * any fields you are not using from the below list to make the parsing
 * and printing code smaller.
 * Each template argument below results in a field of the same name.
 */
using MyData = ParsedData<
  /* String */ identification,
  /* String */ p1_version,
  /* String */ timestamp,
  /* String */ equipment_id,
  /* FixedValue */ energy_delivered_tariff1,
  /* FixedValue */ energy_delivered_tariff2,
  /* FixedValue */ energy_returned_tariff1,
  /* FixedValue */ energy_returned_tariff2,
  /* String */ electricity_tariff,
  /* FixedValue */ power_delivered,
  /* FixedValue */ power_returned,
  /* FixedValue */ electricity_threshold,
  /* uint8_t */ electricity_switch_position,
  /* uint32_t */ electricity_failures,
  /* uint32_t */ electricity_long_failures,
  /* String */ electricity_failure_log,
  /* uint32_t */ electricity_sags_l1,
  /* uint32_t */ electricity_sags_l2,
  /* uint32_t */ electricity_sags_l3,
  /* uint32_t */ electricity_swells_l1,
  /* uint32_t */ electricity_swells_l2,
  /* uint32_t */ electricity_swells_l3,
  /* String */ message_short,
  /* String */ message_long,
  /* FixedValue */ voltage_l1,
  /* FixedValue */ voltage_l2,
  /* FixedValue */ voltage_l3,
  /* FixedValue */ current_l1,
  /* FixedValue */ current_l2,
  /* FixedValue */ current_l3,
  /* FixedValue */ power_delivered_l1,
  /* FixedValue */ power_delivered_l2,
  /* FixedValue */ power_delivered_l3,
  /* FixedValue */ power_returned_l1,
  /* FixedValue */ power_returned_l2,
  /* FixedValue */ power_returned_l3,
  /* uint16_t */ gas_device_type,
  /* String */ gas_equipment_id,
  /* uint8_t */ gas_valve_position,
  /* TimestampedFixedValue */ gas_delivered,
  /* uint16_t */ thermal_device_type,
  /* String */ thermal_equipment_id,
  /* uint8_t */ thermal_valve_position,
  /* TimestampedFixedValue */ thermal_delivered,
  /* uint16_t */ water_device_type,
  /* String */ water_equipment_id,
  /* uint8_t */ water_valve_position,
  /* TimestampedFixedValue */ water_delivered,
  /* uint16_t */ slave_device_type,
  /* String */ slave_equipment_id,
  /* uint8_t */ slave_valve_position,
  /* TimestampedFixedValue */ slave_delivered >;

/**
 * This illustrates looping over all parsed fields using the
 * ParsedData::applyEach method.
 *
 * When passed an instance of this Printer object, applyEach will loop
 * over each field and call Printer::apply, passing a reference to each
 * field in turn. This passes the actual field object, not the field
 * value, so each call to Printer::apply will have a differently typed
 * parameter.
 *
 * For this reason, Printer::apply is a template, resulting in one
 * distinct apply method for each field used. This allows looking up
 * things like Item::name, which is different for every field type,
 * without having to resort to virtual method calls (which result in
 * extra storage usage). The tradeoff is here that there is more code
 * generated (but due to compiler inlining, it's pretty much the same as
 * if you just manually printed all field names and values (with no
 * cost at all if you don't use the Printer).
 */
struct Printer
{
  template<typename Item>
  void apply(Item &i)
  {
    if (i.present())
    {
      Serial.print(Item::name);
      Serial.print(F(": "));
      Serial.print(i.val());
      Serial.print(Item::unit());
      Serial.println();
    }
  }
};

// Set up to read from the serial port, and use D2 as the request
// pin. On boards with only one (USB) serial port, you can also use
// SoftwareSerial.

SoftwareSerial serialMeter (3, 4); // RX, TX

#ifdef ARDUINO_ARCH_ESP32
// Create Serial1 connected to UART 1
HardwareSerial Serial1(1);
#endif

P1Reader reader(&serialMeter, 2);

unsigned long last;

void setup()
{
  Serial.begin(115200);
  serialMeter.begin(115200);
#ifdef VCC_ENABLE
  // This is needed on Pinoccio Scout boards to enable the 3V3 pin.
  pinMode(VCC_ENABLE, OUTPUT);
  digitalWrite(VCC_ENABLE, HIGH);
#endif

  // start a read right away
  reader.enable(true);
  last = millis();
}

void loop()
{
  // Allow the reader to check the serial buffer regularly
  reader.loop();
  Serial.println("In loop");
  // Every minute, fire off a one-off reading
  unsigned long now = millis();
  if (now - last > 2000)
  {
    reader.enable(true);
    last = now;
  }

  if (reader.available())
  {
    MyData data;
    String err;
    Serial.println("read");
    if (reader.parse(&data, &err))
    {
      // Parse succesful, print result
      data.applyEach(Printer());
      Serial.println("True");
    }
    else
    {
      // Parser error, print error
      Serial.println(err);
      Serial.println("Error");
    }
  }
  delay(1000);
}




So, there is nothing on Serial monitor.
There is no filter added. It shall come some garbage for now.

I've changed the code slightly, to see where it gets.
However, it does not recognize the strings.

In week-end I will insert the filter, to see how that would go.

Hi, @viorel_c
Where is your gnd level on your scope pattern?
It looks like you have the scope inputs in AC mode, please redo the measurements in DC mode.

Is your scope probe in x1 or x10 mode?

How are you connected to the meter?
An image of the meter connection and a schematic would help.

The link to the sensor is blocked.

Tom.... :smiley: :+1: :coffee: :australia:

@TomGeorge "Where is your gnd level on your scope pattern?" You can see that I have this oscilloscope as a new toy. So, I need to investigate to answer you on this one. I do not know what you ask me.
"It looks like you have the scope inputs in AC mode" OOps. Correct. I have made them again.
"Is your scope probe in x1 or x10 mode?" x1
"How are you connected to the meter?"
1 and 6 are not connected. I use only 2,3,5.




Slimme_meter_15_a727fce1f1.pdf (456.5 KB)



A wetrasfer movie with how is moving the signal.

Still arduino does not read.
So, what is wrong?

Hi,
Pin 2 on your UNO needs to have a pullup resistor, as the data line pin 5 on the device is open collector.

Have you powered the device up between pin 6 gnd and pin 1 +5V?
Then connect pin 3 data gnd to pin 6 sensor gnd.

With the device powered up and a 10K resistor between pin2 of the UNO and 5V on the UNO, you should see a 5V signal on the data line.

You may also need to connect a UNO output to the sensor pin 2 to request data.

Tom.... :smiley: :+1: :coffee: :australia:
PS, Have you looked at this link?

Hi, @viorel_c

Can you please post your code, in code tags?

Tom.... :smiley: :+1: :coffee: :australia:

@TomGeorge The code is in Post#16. bbcounsins have helped me with it at post#10, and changed a bit (just prints under "if" 's) to see how far it gets.
What I will do, I will shorten the serial line / cable, to see if something would change. I will let you know.

@TomGeorge @bobcousins I've come next to the energy meter (~1m). Same result.
I see the data on oscilloscope, but it does not come in Arduino.



Code same as on post#16.
Inverting filter not yet applied. Expecting at least garbage.
If I need to implement the filter, than I will do.
Waiting for instructions.

Please help! Thanks in advance.

A slow rise time indicates the need for a pullup resistor, I would normally use 4k7, 10k may also work. Note that the pullup needs to be applied before the inverting circuit (when you have one).