Water meter reading via NFC

Yesterday the local water company installed a new remote readable water meter in my house. The meter made by Zenner and the type is DE-20-MI001-PTB011.

I searched technical data for the meter and found out that the meter communicates via LoRa to the water company servers. But the meter also has NFC. I was able read the NFC with my phone(NFC tools). NFC data included the meter S/N, date/time, Total water consumption, temperature, battery status, etc. NFC tools shows the Payload in UTF8, hex and ascii.

My question is that can I use ESPHome+PN532 nfc module to read the water consumption and temperature data and send it via mqtt to Homeassistant? Can the PN532 module be mounted permanently to the water meter housing and can I define the read interval in the configuration to be for example 1 hour?

Any help would be appreciated.

1 Like

Well Ive seen nfc readers for passive nfc tags, so reading is not the issue. The message to the broker timed sounds also like a simple timer. I see no issue
I’m interested in this!

As implemented the PN532 will only read a tag once, and won’t re-read it unless the tag is removed then replaced. Depending on how the meter operates this may be an issue…

EDIT: just had a thought - if you only wanted a reading an hour, use deep sleep.

I tested this and when the ESP wakes from deep sleep it re-reads the tag. So again depending on the meter this may work.

I was also thinking same thing that it will read the tag only once. Deep sleep might be the solution for that. Thank’s for testing that. I need to order one PN532 and test it with my meter will it work.

Is the template sensor way to go for sending the nfc payload to mqtt? I have not used that before and need to learn it.

Or a few template sensors, depending on the records in the NDEF message. This code reads the first 2 NDEF records and publishes them to template sensors:

text_sensor:
  - platform: template
    name: "NDEF data 1"
    id: ndef_payload1
  - platform: template
    name: "NDEF data 2"
    id: ndef_payload2

i2c:
  sda: 21
  scl: 22
  scan: true
  id: bus_a

pn532_i2c:
  update_interval: 1s
  on_tag:
    then:
      - lambda: |-
          if (tag.has_ndef_message()) {
            auto message = tag.get_ndef_message();
            auto records = message->get_records();
            std::string payload1 = records[0]->get_payload();
            std::string payload2 = records[1]->get_payload();
            id(ndef_payload1).publish_state(payload1);
            id(ndef_payload2).publish_state(payload2);
          }
1 Like

Thank you for the example code. I will try it and let you know if it works.

Hi,

I just want to thank you for this thread. I have exactly the same situation as @puuhapet
I had RFID-RC522 on hand but quickly realized that it’s not able to read tag contents. So I ordered a few PN532 readers.
For testing purposes I went to my water meter and copied the NFC contents to another tag with my phone. So far so good.

With the help from @zoogara I got my esp32 configured with i2c. However when I try to read a tag my ESP32 crashes :S

I was able to get some data out of my POC tag like so before it crashed. It was not able to send any information to HA however

11:36:47][V][pn532_i2c:049]: Reading response of length 18
[11:36:47][V][nfc.ndef_message:009]: Building NdefMessage with 300 bytes
[11:36:47][V][nfc.ndef_message:073]: Adding record type T = Ultrasonic Water Meter
S/N:
Time: 2023-11-06 07:00
Vol: 4.329 m³
KDate: 2024-01-01
KVol: – m³
2023-11-01: 3.614 m³
2023-10-16: 1.328 m³
2023-08-23: 0.000 m³
Temp: 12.9 °C
FVol: 4.329 m³
RVol: 0.000 m³
Battery: 2039
FW: 1.07.9.1791
CRC 57801

WARNING vesimittari.local: Connection error occurred: [Errno 104] Connection reset by peer
INFO Processing unexpected disconnect from ESPHome API for vesimittari.local
WARNING Disconnected from API

Any Idea what might be going on here? Did you @puuhapet get this to work?
I have a fall back plan to use rpi but it’s a bit overkill imo.

Hmm possibly an issue with your lambda? Post your yaml and maybe someone can spot the cause.

1 Like

Here’s my relevant YAML. Thanks for the lambda suggestion. I’ll try search and learn more about these lambda functions. This esp32 world is all new to me :slight_smile:

i2c:
  sda: GPIO18
  scl: GPIO19
  scan: true

pn532_i2c:
  update_interval: 1s
  on_tag:
    then:
      - lambda: |-
          if (tag.has_ndef_message()) {
            auto message = tag.get_ndef_message();
            auto records = message->get_records();
            std::string payload1 = records[0]->get_payload();
            std::string payload2 = records[1]->get_payload();
            id(ndef_payload1).publish_state(payload1);
            id(ndef_payload2).publish_state(payload2);
          }

text_sensor:
- platform: template
  name: "NDEF data 1"
  id: ndef_payload1
- platform: template
  name: "NDEF data 2"
  id: ndef_payload2

Not your yaml by the look. Maybe the PN532 component library has issues with lots of data fields. What happens if you read a tag with only a couple of fields defined?

1 Like

Thanks for the suggestion. I wrote a tag with a simple text ā€œHello world!ā€. Unfortunately the same result.
I also tried a different PN532 but the result was the same
Then I updated ESPhome to 2023.10.6 from 2023.9.someversion but no changes
I have a bunch of these small ESP-WROOM-32 boards so I will try another one just in case. Unfortunately life gets in the way of tinkering so it will take some time until I can try this.

[09:59:22][W][component:214]: Component pn532 took a long time for an operation (0.13 s).
[09:59:22][W][component:215]: Components should block for at most 20-30ms.
[09:59:22][V][pn532:274]: Reading ACK…
[09:59:22][V][pn532:285]: ACK valid: YES
[09:59:22][V][pn532_i2c:043]: Reading response
[09:59:22][V][pn532:290]: Sending NACK for retransmit
[09:59:22][V][pn532_i2c:049]: Reading response of length 11
[09:59:22][D][pn532:308]: Mifare classic
[09:59:22][V][pn532:274]: Reading ACK…
[09:59:22][V][pn532:285]: ACK valid: YES
[09:59:22][V][pn532_i2c:043]: Reading response
[09:59:22][V][pn532:290]: Sending NACK for retransmit
[09:59:22][V][pn532_i2c:049]: Reading response of length 2
[09:59:23][V][pn532:274]: Reading ACK…
[09:59:23][V][pn532:285]: ACK valid: YES
[09:59:23][V][pn532_i2c:043]: Reading response
[09:59:23][V][pn532:290]: Sending NACK for retransmit
[09:59:23][V][pn532_i2c:049]: Reading response of length 18
[09:59:23][V][pn532:274]: Reading ACK…
[09:59:23][V][pn532:285]: ACK valid: YES
[09:59:23][V][pn532_i2c:043]: Reading response
[09:59:23][V][pn532:290]: Sending NACK for retransmit
[09:59:23][V][pn532_i2c:049]: Reading response of length 2
[09:59:23][V][pn532:274]: Reading ACK…
[09:59:23][V][pn532:285]: ACK valid: YES
[09:59:23][V][pn532_i2c:043]: Reading response
[09:59:23][V][pn532:290]: Sending NACK for retransmit
[09:59:23][V][pn532_i2c:049]: Reading response of length 18
[09:59:23][V][pn532:274]: Reading ACK…
[09:59:23][V][pn532:285]: ACK valid: YES
[09:59:23][V][pn532_i2c:043]: Reading response
[09:59:23][V][pn532:290]: Sending NACK for retransmit
[09:59:23][V][pn532_i2c:049]: Reading response of length 18
[09:59:23][V][nfc.ndef_message:009]: Building NdefMessage with 30 bytes
[09:59:23][V][nfc.ndef_message:073]: Adding record type T = Hello world!
WARNING vesimittari.local: Connection error occurred: [Errno 104] Connection reset by peer
INFO Processing unexpected disconnect from ESPHome API for vesimittari.local
WARNING Disconnected from API
WARNING Can’t connect to ESPHome API for vesimittari.local: Error connecting to (ā€˜192.168.1.155’, 6053): [Errno 111] Connect call failed (ā€˜192.168.1.155’, 6053) (SocketAPIError)
INFO Trying to connect to vesimittari.local in the background
INFO Successfully connected to vesimittari.local

Ok - that’s weird - all I can suggest now is you have a hardware issue somewhere - maybe double check the wiring and the dip switches.

Shame because it looks like the concept will work based on the data you have shown.

1 Like

I finally received my PN532. I tried with ESP32 dev and wemos D1 mini board in SPI mode and did not get it working. I also tried creating a simple Hello-text with my phone like @jonixx. The ESPhome log shows that the PN532 recognises the tag as Mifare classic but is not able to read the content. The log is not showing anything when trying to read the water meter.

NFC tool shows the tag type as Mifare Ultra. Is the PN532 and ESPhome able to read this type of tag?

I’m using ESPhome version 2023.11.3

Hi Tuomas,

I have just received the exact same water meter, so in case youā€˜ve made any progress it would be interesting to hear more about your findings.

Hello

I haven’t got it working. My ESPhome+PN532 is not able to read the water meter tag and I don’t know why. I have not had motivation to work on this. I’m open for ideas.

I just got a similar meter by Zenner. Also interested in this, but at least my meter doesn’t give any NFC detection when I try to read it with NFC Tools…

I just got the same meter, Zenner. Any progress?

No progress with ESPhome+PN532. I can read the meter with my phone and NFC tools. I have tried to find a way to use my old phone and automate the nfc reading and send the data with mqtt to homeassistant. With Tasker I can do the reading of the nfc but I have not found a way to send the data with mqtt.

1 Like

Yes, it is possible to read those Meters - but it took a long time to get it done…

With:

  • homeassistant
  • esphome
  • mqtt
  • esp32 (wt32-eth01)
  • pn523

(There are already a lot of descriptions how to build the reader…and how to get firmware on them via esphome:

Understanding the hardware: https://warlord0blog.wordpress.com/2021/10/09/esp32-and-nfc-over-i2c/)
esphome with esp32 und pn532: https://esphome.io/components/binary_sensor/pn532.html

and a lot more if you are beginning to search…)

There are several problems you have to solve first:

  1. the libraries to read the tags currently cannot handle the data…
    the given format on my water meter is: mifare ultralight with nfc forum type 2 data. With the current libraries: no chance… (If you copy the tag with NFC Tools to a mifare classic card - you can read the data! (at least - i could…)
  2. Using NFC is a bit tricky as well, because normally you place (eg. your phone) next to a tag (card or whatever) or other way round… and take it away. If you want to read a tag constantly you have to do something about it. (An ā€œon-tagā€-event fires only once when placing a tag near the reader)
  3. If you are trying to get the whole string into a homeassistant sensor, you are facing the next issue: the state of a homeassistant sensor is limited to 255 bytes. So you have to find another way to store the data…
    … and a few other issues I faced during the project…

Homeassistant is quite new for me and i am definitly not the best programmer in the world… but i love to solve things…

To begin with something… here is my esphome-yaml for the reader:

esphome:
  name: h16-water-meter-reader
  friendly_name: H16 Water Meter Reader
  project:
    name: esphome.web
    version: dev
  on_boot:
    priority: -100
    then:
    - wait_until:  
        api.connected
    - logger.log: API and ethernet is connected!
    - delay: 1000ms

external_components:
  # use all components from a local folder
  - source:
      type: local
      path: components
    components:
      - nfc
      - pn532_i2c
      - pn532
      
esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:
  level: DEBUG
  logs:
    lambda: DEBUG  
    custom: DEBUG  
#    nfc: DEBUG
#    pn532_i2c: DEBUG
#    pn532: DEBUG
#    i2c: DEBUG

# Enable Home Assistant API
api:
  encryption:
    key: !secret api_skey

ota:
  - platform: esphome
    password: !secret ota_skey

web_server:

mqtt:
  broker: "192.168.xxx.yyy"
  username: !secret mqtt_u
  password: !secret mqtt_pw
  id: mqtt_client 

ethernet: 
  type: LAN8720 
  mdc_pin: GPIO23 
  mdio_pin: GPIO18 
  clk_mode: GPIO0_IN 
  power_pin: GPIO16 
  phy_addr: 1 
  manual_ip:
    static_ip: 192.168.xxx.xxx
    gateway: 192.168.xxx.1
    subnet: 255.255.255.0

i2c:
  - id: bus_a
    sda: GPIO14
    scl: GPIO15
    scan: True

deep_sleep:
   id: deep_sleep_1
   run_duration: 59500ms
   sleep_duration: 500ms 

pn532_i2c:
  id: i_pn532
  update_interval: 30s

  on_tag:
    then:
    - deep_sleep.prevent: deep_sleep_1
    - lambda: |- 
        std::string payload1 = "";
        auto nfckey = tag.get_tag_type();
        if (!tag.has_ndef_message()) {
          return;
        }
        auto message = tag.get_ndef_message();
        auto records = message->get_records();
        std::string input = records[0]->get_payload();

        if (input != "") {
          // JSON-Strings
          std::string gjson = "";
          std::string ajson = "";
          std::string hjson = "\"history\": [";
          std::string iname = "";
          std::string isn = "";
          std::string line = "";
          size_t delimiter = 0;

          // Entferne alles ab "CRC"
          size_t crc_pos = input.find("CRC");
          if (crc_pos != std::string::npos) {
            input = input.substr(0, crc_pos); // Schneidet den String vor "CRC" ab
          }

          // Aufteilen des Strings bei \r\n (CRLF)
          size_t pos = 0;
          bool is_first_line = true; // Erste Zeile behandeln
          while ((pos = input.find("\r\n")) != std::string::npos) {
            line = input.substr(0, pos); // Zeile extrahieren
            input.erase(0, pos + 2); // CRLF entfernen

            // Erste Zeile prüfen (kein Doppelpunkt, nur Name + Id und Typ in gjson)
            if (is_first_line) {
              is_first_line = false; // Nur für die erste Zeile ausführen
              // ESP_LOGI("lambda", "Name: %s", line.c_str());
              gjson += std::string("\"") + "Name" + "\": \"" + line + "\",";
              continue; // Überspringen, da kein Key-Value-Paar
            }

            // Key-Value-Paar finden
            delimiter = line.find(':');
            if (delimiter != std::string::npos) {
              std::string key = line.substr(0, delimiter);
              std::string value = line.substr(delimiter + 1);

              // Whitespace trimmen
              key.erase(0, key.find_first_not_of(" \t"));
              key.erase(key.find_last_not_of(" \t") + 1);
              value.erase(0, value.find_first_not_of(" \t"));
              value.erase(value.find_last_not_of(" \t") + 1);

              // Datum erkennen (Format: YYYY-MM-DD)
              if (key.length() == 10 && key[4] == '-' && key[7] == '-') {
                hjson += "{\"date\": \"" + key + "\", \"volume\": \"" + value + "\"},";
              }

              // Weitere Werte zuweisen
              else if (key == "Vol") {
                ajson += "\"" + key + "\": \"" + value + "\",";
              } else if (key == "Temp") {
                ajson += "\"" + key + "\": \"" + value + "\",";
              } else if (key == "S/N") {
                gjson += "\"SN\": \"" + value + "\",";
                // ilead += "\"" + "SN" + "\": \"" + value + "\",";
                gjson += "\"NFC_Id\": \"" + x + "\",";
                gjson += "\"NFC_Typ\": \"" + nfckey + "\",";
                isn = value;
              } else if (key == "Battery") {
                gjson += "\"" + key + "\": \"" + value + "\",";
              } else if (key == "FVol") {
                ajson += "\"" + key + "\": \"" + value + "\",";
              } else if (key == "RVol") {
                ajson += "\"" + key + "\": \"" + value + "\",";
              } else if (key == "KVol") {
                ajson += "\"" + key + "\": \"" + value + "\",";
              } else if (key == "KDate") {
                ajson += "\"" + key + "\": \"" + value + "\",";
              } else if (key == "Time") {
                ajson += "\"" + key + "\": \"" + value + "\",";
             }
            }
          }

          // JSON-Strings abschließen

          if (hjson.back() == ',') {
            hjson.pop_back(); // Letztes Komma entfernen
          }
          gjson = "{" + gjson + ajson + hjson + "]}";

          std::string itopic = "water_meter/"; 
          
          std::string iconv = isn;
          isn = "";
          // Lambda-Funktion zur Transformation
          for (char c : iconv) {
            if (c >= 'A' && c <= 'Z') {
              isn += c + ('a' - 'A'); // Groß- in Kleinbuchstaben
            } else {
              isn += c;
            }
          }

          ESP_LOGD("lambda", "S/N (klein): %s", isn.c_str());
          ESP_LOGD("lambda", "JSON-String: %s", gjson.c_str());

          std::string itopic_g = "water_meter/" + isn + "/data"; 
          
          id(mqtt_client).publish(itopic_g, gjson);          
        }
        delay(500);

    - deep_sleep.allow: deep_sleep_1

…to be continued…

1 Like

Thats great news. Can you share what hardware you used and the code?