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:
- 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ā¦)
- 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)
- 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ā¦