Start SDM series energy meters tools repo

This commit is contained in:
Sprinterfreak 2024-07-23 00:27:15 +02:00
commit 4011b2d8a6
14 changed files with 1844 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.o
**.swp

17
Makefile Normal file
View File

@ -0,0 +1,17 @@
sdm2mqtt: sdm.o sdm2mqtt.o
cc -o sdm2mqtt sdm2mqtt.o sdm.o -lmosquitto -lmodbus
sdm2mqtt.o: sdm2mqtt.c config.h
cc -c sdm2mqtt.c
sdm.o: sdm.c sdm*_registers.h
cc -c sdm.c
clean:
rm -rf *.o
install: sdm2mqtt
envsubst <sdm2mqtt.service >/etc/systemd/system/sdm2mqtt.service
systemctl daemon-reload
systemctl enable sdm2mqtt.service
systemctl start sdm2mqtt.service

31
README.md Normal file
View File

@ -0,0 +1,31 @@
## SDM2MQTT Bridge
Reads SDM series energy meters modbus registers and pushes them to mqtt topic
as json object.
#### Requirements
```bash
apt install git make libmodbus-dev libmosquitto-dev
```
#### Configure
Modify `config.h` to your needs
#### Build
```bash
make
```
#### Install
```bash
make install
```
### Copyright
2024 Waijb
Documentation by Sprinterfreak

55
config.h Normal file
View File

@ -0,0 +1,55 @@
#ifndef CONFIG_H
#define CONFIG_H
#include "sdm.h"
typedef struct {
int addr;
char *friendlyname;
enum sdm_types type;
} config_meters_t;
static config_meters_t meters[] = {
{ 11,
"Feld",
SDM630M
},
{ 12,
"Vorzelt",
SDM630M
},
{ 0,
NULL,
SDMNONE
}
};
typedef struct {
char *ser_device;
int ser_baud;
int ser_databits;
char ser_parity;
int ser_stopbits;
config_meters_t *meters;
const char *mosq_name;
const char *mosq_topicprefix;
const char *mosq_host;
const int mosq_port;
const int mosq_keepalive;
} config_t;
static config_t cfg = {
"/dev/ttyUSB0",
38400,
8,
'N',
1,
meters,
"sdm2mqtt2",
"sdm2mqtt2",
"localhost",
1883,
5
};
#endif

1001
grafana/dashboard.json Normal file

File diff suppressed because it is too large Load Diff

0
logs/.keep Normal file
View File

333
sdm.c Normal file
View File

@ -0,0 +1,333 @@
#include "sdm.h"
#include "sdm230m_registers.h"
#include "sdm630m_registers.h"
#include "sdm72dm_registers.h"
static char *sdm_typenames[] = {
"none",
"SDM230M",
"SDM630M",
"SDM72DM"
};
static void swap(sdm_values_t *value) {
uint16_t tmp;
tmp = value->v16[0];
value->v16[0] = value->v16[1];
value->v16[1] = tmp;
}
const char *sdm_gettypename(sdm_t *sdm) {
return sdm_typenames[sdm->type];
}
sdm_t *sdm_new(modbus_t *mb, int address, char *friendlyname, enum sdm_types type, int *error) {
sdm_t *sdm;
if (mb == NULL) {
*error = SDM_MB_ERR;
return NULL;
}
if (address <= 0 || address > 247) {
*error = SDM_ADDR_ERR;
return NULL;
}
if (friendlyname == NULL) {
*error = SDM_NAME_ERR;
return NULL;
}
sdm = (sdm_t *)calloc(sizeof(sdm_t), 1);
if (sdm == NULL) {
*error = SDM_MEM_ERR;
return NULL;
}
sdm->mb = mb;
sdm->address = address;
sdm->friendlyname = strdup(friendlyname);
if (sdm->friendlyname == NULL) {
free(sdm);
*error = SDM_MEM_ERR;
return NULL;
}
sdm->type = type;
switch (type) {
case SDM230M:
sdm->values = (sdm_values_t *)calloc(sizeof(sdm_values_t) * SMD230M_NRVAL, 1);
break;
case SDM630M:
sdm->values = (sdm_values_t *)calloc(sizeof(sdm_values_t) * SMD630M_NRVAL, 1);
break;
case SDM72DM:
sdm->values = (sdm_values_t *)calloc(sizeof(sdm_values_t) * SMD72DM_NRVAL, 1);
break;
default:
free(sdm->friendlyname);
free(sdm);
*error = SDM_MEM_ERR;
return NULL;
}
if (sdm->values == NULL) {
free(sdm->friendlyname);
free(sdm);
*error = SDM_MEM_ERR;
return NULL;
}
sdm->valid = false;
return sdm;
}
int sdm_free(sdm_t *sdm) {
if (sdm == NULL) {
return SDM_EMPTY;
}
free(sdm->friendlyname);
free(sdm->values);
free(sdm);
return SDM_OK;
}
int sdm_update(sdm_t *sdm) {
sdm_registers_t *regs;
sdm_values_t *value = sdm->values;
sdm->valid = false;
switch (sdm->type) {
case SDM230M:
regs = sdm230m_registers;
break;
case SDM630M:
regs = sdm630m_registers;
break;
case SDM72DM:
regs = sdm72dm_registers;
break;
case SDMNONE:
regs = NULL;
break;
}
modbus_flush(sdm->mb);
modbus_set_slave(sdm->mb, sdm->address);
while (regs->name != NULL) {
if (-1 == modbus_read_input_registers(sdm->mb, regs->regno, 2, value->v16)) {
return SDM_COMM_ERR;
}
swap(value);
regs++;
value++;
}
sdm->valid = true;
return SDM_OK;
}
int sdm_print_csv_header(sdm_t *sdm, char **header) {
char tmp[4096], *ret;
int free = 4095, used = 0, count = 0;
sdm_registers_t *regs;
sdm_values_t *value = sdm->values;
switch (sdm->type) {
case SDM230M:
regs = sdm230m_registers;
break;
case SDM630M:
regs = sdm630m_registers;
break;
case SDM72DM:
regs = sdm72dm_registers;
break;
case SDMNONE:
regs = NULL;
break;
}
count = snprintf(tmp, free, "time;friendlyname;address;typename;valid;");
if (count < 0 || count >= free) {
//FIXME ERRORHANDLING
return SDM_MEM_ERR;
}
free -= count;
used += count;
count = 0;
while (regs->name != NULL) {
count = snprintf(tmp + used, free, "%s;", regs->name);
if (count < 0 || count >= free) {
//FIXME ERRORHANDLING
return SDM_MEM_ERR;
}
free -= count;
used += count;
count = 0;
regs++;
value++;
}
ret = strdup(tmp);
if (ret == NULL) {
return SDM_MB_ERR;
}
*header = ret;
return SDM_OK;
return 0;
}
int sdm_print_csv(sdm_t *sdm, char **csv) {
char tmp[4096], *ret;
int free = 4095, used = 0, count = 0;
time_t now;
sdm_registers_t *regs;
sdm_values_t *value = sdm->values;
time(&now);
tmp[4095] = 0;
switch (sdm->type) {
case SDM230M:
regs = sdm230m_registers;
break;
case SDM630M:
regs = sdm630m_registers;
break;
case SDM72DM:
regs = sdm72dm_registers;
break;
case SDMNONE:
regs = NULL;
break;
}
count = strftime(tmp, free, "%FT%TZ;", gmtime(&now));
count += snprintf(tmp + count, free, "%s;%03d;%s;%s;", sdm->friendlyname, sdm->address, sdm_gettypename(sdm), sdm->valid?"true":"false");
if (count < 0 || count >= free) {
//FIXME ERRORHANDLING
return SDM_MEM_ERR;
}
free -= count;
used += count;
count = 0;
while (regs->name != NULL && sdm->valid == true) {
count = snprintf(tmp + used, free, "%f;", value->vf);
if (count < 0 || count >= free) {
//FIXME ERRORHANDLING
return SDM_MEM_ERR;
}
free -= count;
used += count;
count = 0;
regs++;
value++;
}
ret = strdup(tmp);
if (ret == NULL) {
return SDM_MB_ERR;
}
*csv = ret;
return SDM_OK;
}
int sdm_print_json(sdm_t *sdm, char **json) {
char tmp[4096], *ret;
int free = 4095, used = 0, count = 0;
sdm_registers_t *regs;
sdm_values_t *value = sdm->values;
tmp[4095] = 0;
switch (sdm->type) {
case SDM230M:
regs = sdm230m_registers;
break;
case SDM630M:
regs = sdm630m_registers;
break;
case SDM72DM:
regs = sdm72dm_registers;
break;
case SDMNONE:
regs = NULL;
break;
}
count = snprintf(tmp, free, "{\"name\":\"%s\",\"address\":%d,\"type\":\"%s\",\"valid\":%s", sdm->friendlyname, sdm->address, sdm_gettypename(sdm), sdm->valid?"true":"false");
if (count < 0 || count >= free) {
//FIXME ERRORHANDLING
return SDM_MEM_ERR;
}
free -= count;
used += count;
count = 0;
while (regs->name != NULL && sdm->valid == true) {
count = snprintf(tmp + used, free, ",\"%s\":%f", regs->name, value->vf);
if (count < 0 || count >= free) {
//FIXME ERRORHANDLING
return SDM_MEM_ERR;
}
free -= count;
used += count;
count = 0;
regs++;
value++;
}
count = snprintf(tmp + used, free, "}");
if (count < 0 || count >= free) {
//FIXME ERRORHANDLING
return SDM_MEM_ERR;
}
ret = strdup(tmp);
if (ret == NULL) {
return SDM_MB_ERR;
}
*json = ret;
return SDM_OK;
}

56
sdm.h Normal file
View File

@ -0,0 +1,56 @@
#ifndef sdm_h
#define sdm_h
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stdbool.h>
#include <modbus/modbus.h>
#define SDM_OK 0
#define SDM_EMPTY -1
#define SDM_MB_ERR -2
#define SDM_ADDR_ERR -3
#define SDM_NAME_ERR -4
#define SDM_TYPE_ERR -5
#define SDM_COMM_ERR -6
#define SDM_NOT_IMPL -7
#define SDM_MEM_ERR -8
enum sdm_types {
SDMNONE,
SDM230M,
SDM630M,
SDM72DM
};
typedef struct {
const char *name;
const char *unit;
const unsigned int regno;
} sdm_registers_t;
typedef union {
uint16_t v16[2];
float vf;
} __attribute__ ((packed)) sdm_values_t;
typedef struct {
modbus_t *mb;
int address;
char *friendlyname;
enum sdm_types type;
sdm_values_t *values;
bool valid;
} sdm_t;
sdm_t *sdm_new(modbus_t *mb, int address, char *friendlyname, enum sdm_types type, int *error);
int sdm_free(sdm_t *sdm);
int sdm_update(sdm_t *sdm);
const char *sdm_gettypename(sdm_t *sdm);
int sdm_print_csv(sdm_t *sdm, char **csv);
int sdm_print_csv_header(sdm_t *sdm, char **header);
int sdm_print_json(sdm_t *sdm, char **json);
#endif

35
sdm230m_registers.h Normal file
View File

@ -0,0 +1,35 @@
#ifndef sdm230m_registers_h
#define sdm230m_registers_h
#define SMD230M_NRVAL 24
// 24 Messwerte
static sdm_registers_t sdm230m_registers[] = {
{ "L-N Voltage", "V" , 0 },
{ "Current", "A" , 6 },
{ "Power", "W" , 12 },
{ "Apparent Power", "VA" , 18 },
{ "Reactive Power", "VAr" , 24 },
{ "Power Factor", "1" , 30 },
{ "Phase Angle", "°" , 36 },
{ "Frequency", "Hz" , 70 },
{ "Energy Import", "kWh" , 72 },
{ "Energy Export", "kWh" , 74 },
{ "Reactive Energy Import", "kVArh" , 76 },
{ "Reactive Energy Export", "kVArh" , 78 },
{ "Total Power Demand", "W" , 84 },
{ "Total Power Demand (Max)", "W" , 86 },
{ "Current Power Demand (Import)", "W" , 88 },
{ "Max Power Demand (Import)", "W" , 90 },
{ "Current Power Demand (Export)", "W" , 92 },
{ "Max Power Demand (Export)", "W" , 94 },
{ "L Current Demand", "A" , 258 },
{ "L Current Demand (Max)", "A" , 264 },
{ "Total Energy", "kWh" , 342 },
{ "Total Reactive Energy", "kVArh" , 344 },
{ "Total Energy (resettable)", "kWh" , 384 },
{ "Total Reactive Energy (resettable)", "kVArh" , 386 },
{ NULL, NULL , 0 }
};
#endif

163
sdm2mqtt.c Normal file
View File

@ -0,0 +1,163 @@
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <mosquitto.h>
#include <modbus/modbus.h>
#include "sdm.h"
#include "config.h"
int main(int argc, char **argv) {
int nmeters, i, err;
struct mosquitto *mosq;
modbus_t *mb;
sdm_t **sdms;
FILE **logfiles;
char *printme;
char buf[4096];
mosquitto_lib_init();
mosq = mosquitto_new(cfg.mosq_name, false, NULL);
if (mosq == NULL) {
//FIXME
return -1;
}
err = mosquitto_connect(mosq, cfg.mosq_host, cfg.mosq_port, cfg.mosq_keepalive);
if (err != MOSQ_ERR_SUCCESS) {
//FIXME
return -1;
}
mb = modbus_new_rtu(cfg.ser_device, cfg.ser_baud, cfg.ser_parity, cfg.ser_databits, cfg.ser_stopbits);
if (mb == NULL) {
fprintf(stderr, "%s:%d: failed to create Modbus Context\n", __FILE__, __LINE__);
return -1;
}
if (modbus_connect(mb) == -1) {
fprintf(stderr, "%s:%d: Modbus connection failed: %s\n", __FILE__, __LINE__, modbus_strerror(errno));
return -2;
}
nmeters = 0;
while (cfg.meters[nmeters].friendlyname != NULL) {
nmeters++;
}
sdms = (sdm_t **)calloc(sizeof(sdm_t *) * nmeters, 1);
logfiles = (FILE **)calloc(sizeof(FILE *) * nmeters, 1);
for (i = 0; i < nmeters; i++) {
err = 0;
sdms[i] = sdm_new(mb, cfg.meters[i].addr, cfg.meters[i].friendlyname, cfg.meters[i].type, &err);
if (sdms[i] == NULL) {
fprintf(stderr, "%s:%d: Failed to create meter %s\n", __FILE__, __LINE__, cfg.meters[i].friendlyname);
}
snprintf(buf, 4095, "logs/%s.log", sdms[i]->friendlyname);
logfiles[i] = fopen(buf, "r");
if (logfiles[i] == NULL) {
char *header;
logfiles[i] = fopen(buf, "a");
if (logfiles[i] == NULL) {
fprintf(stderr, "%s:%d: Failed to open logfile %s:\n", __FILE__, __LINE__, cfg.meters[i].friendlyname);
perror("");
}
if (sdm_print_csv_header(sdms[i], &header)) {
//FIXME
}
fprintf(logfiles[i], "%s\n", header);
free(header);
} else {
logfiles[i] = freopen(buf, "a", logfiles[i]);
}
if (logfiles[i] == NULL) {
fprintf(stderr, "%s:%d: Failed to open logfile %s:\n", __FILE__, __LINE__, cfg.meters[i].friendlyname);
perror("");
}
}
while (1) {
for (i = 0; i < nmeters; i++) {
if (sdm_update(sdms[i]) != SDM_OK) {
int errsv = errno;
fprintf(stderr, "%s:%d: %s: %s (%d)\n", __FILE__, __LINE__, modbus_strerror(errsv), sdms[i]->friendlyname, sdms[i]->address);
if (errsv == ECONNRESET || errsv == EBADF) {
// USB Connection failed?
// try to reconnect...
modbus_close(mb);
if (modbus_connect(mb) == -1) {
fprintf(stderr, "%s:%d: Modbus reconnect failed: %s\n", __FILE__, __LINE__, modbus_strerror(errno));
sleep(1);
}
}
}
if (sdm_print_csv(sdms[i], &printme) != SDM_OK) {
fprintf(stderr, "%s:%d: error in sdm_print_csv!\n", __FILE__, __LINE__);
} else {
fprintf(logfiles[i], "%s\n", printme);
fflush(logfiles[i]);
free(printme);
}
if (sdm_print_json(sdms[i], &printme) != SDM_OK) {
fprintf(stderr, "%s:%d: error in sdm_print_json!\n", __FILE__, __LINE__);
} else {
int err;
//FIXME
char topic[256];
snprintf(topic, 255, "%s/%s", cfg.mosq_topicprefix, sdms[i]->friendlyname);
err = mosquitto_publish(mosq, NULL, topic, strlen(printme), printme, 0, false);
if (err != MOSQ_ERR_SUCCESS) {
fprintf(stderr, "%s:%d: mosquitto error! (%d)\n", __FILE__, __LINE__, err);
err = mosquitto_reconnect(mosq);
if (err != MOSQ_ERR_SUCCESS) {
//FIXME
fprintf(stderr, "%s:%d: Mosquitto reconnect failed.\n", __FILE__, __LINE__);
}
err = mosquitto_publish(mosq, NULL, topic, strlen(printme), printme, 0, false);
if (err != MOSQ_ERR_SUCCESS) {
//FIXME
fprintf(stderr, "%s:%d: publish failed after reconnect.\n", __FILE__, __LINE__);
}
}
free(printme);
}
usleep(10000);
}
}
for (i = 0; i < nmeters; i++) {
sdm_free(sdms[i]);
fclose(logfiles[i]);
}
free(logfiles);
free(sdms);
modbus_close(mb);
//broken: "free(): double free detected in tcache 2"
//modbus_free(mb);
mosquitto_lib_cleanup();
return 0;
}

12
sdm2mqtt.service Normal file
View File

@ -0,0 +1,12 @@
[Unit]
Description=SDM-Series Energy Meter MQTT Bridge
After=multi-user.service
[Service]
Type=simple
WorkingDirectory=$PWD
ExecStart=sdm2mqtt
Restart=on-failure
[Install]
WantedBy=multi-user.target

95
sdm630m_registers.h Normal file
View File

@ -0,0 +1,95 @@
#ifndef sdm630m_registers_h
#define sdm630m_registers_h
#define SMD630M_NRVAL 85
// 85 Messwerte
static sdm_registers_t sdm630m_registers[] = {
{ "L1-N Voltage", "V" , 0 },
{ "L2-N Voltage", "V" , 2 },
{ "L3-N Voltage", "V" , 4 },
{ "L1 Current", "A" , 6 },
{ "L2 Current", "A" , 8 },
{ "L3 Current", "A" , 10 },
{ "L1 Power", "W" , 12 },
{ "L2 Power", "W" , 14 },
{ "L3 Power", "W" , 16 },
{ "L1 Apparent Power", "VA" , 18 },
{ "L2 Apparent Power", "VA" , 20 },
{ "L3 Apparent Power", "VA" , 22 },
{ "L1 Reactive Power", "VAr" , 24 },
{ "L2 Reactive Power", "VAr" , 26 },
{ "L3 Reactive Power", "VAr" , 28 },
{ "L1 Power Factor", "1" , 30 },
{ "L2 Power Factor", "1" , 32 },
{ "L3 Power Factor", "1" , 34 },
{ "L1 Phase Angle", "°" , 36 },
{ "L2 Phase Angle", "°" , 38 },
{ "L3 Phase Angle", "°" , 40 },
{ "Average Voltage", "V" , 42 },
{ "Average Current", "A" , 46 },
{ "Current (Sum)", "A" , 48 },
{ "Total Power", "W" , 52 },
{ "Total Apparent Power", "VA" , 56 },
{ "Total Reactive Power", "VAr" , 60 },
{ "Total Power Factor", "1" , 62 },
{ "Total Phase Angle", "°" , 66 },
{ "Frequency", "Hz" , 70 },
{ "Energy Import", "kWh" , 72 },
{ "Energy Export", "kWh" , 74 },
{ "Reactive Energy Import", "kVArh" , 76 },
{ "Reactive Energy Export", "kVArh" , 78 },
{ "Total VAh", "kVAh" , 80 },
{ "Total Ah", "kAh" , 82 },
{ "Total Power Demand", "W" , 84 },
{ "Total Power Demand (Max)", "W" , 86 },
{ "Total Apparent Power Demand", "VA" , 100 },
{ "Total Apparent Power Demand (Max)", "VA" , 102 },
{ "Neutral Current Demand", "A" , 104 },
{ "Neutral Current Demand (Max)", "A" , 106 },
{ "L1-L2 Voltage", "V" , 200 },
{ "L2-L3 Voltage", "V" , 202 },
{ "L3-L1 Voltage", "V" , 204 },
{ "Average Line-Line Voltage", "V" , 206 },
{ "Neutral Current", "A" , 224 },
{ "L1-N THD (U)", "%" , 234 },
{ "L2-N THD (U)", "%" , 236 },
{ "L3-N THD (U)", "%" , 238 },
{ "L1 THD (I)", "%" , 240 },
{ "L2 THD (I)", "%" , 242 },
{ "L3 THD (I)", "%" , 244 },
{ "Average THD (U)", "%" , 248 },
{ "Average THD (I)", "%" , 250 },
{ "L1 Current Demand", "A" , 258 },
{ "L2 Current Demand", "A" , 260 },
{ "L3 Current Demand", "A" , 262 },
{ "L1 Current Demand (Max)", "A" , 264 },
{ "L2 Current Demand (Max)", "A" , 266 },
{ "L3 Current Demand (Max)", "A" , 268 },
{ "L1-L2 THD (U)", "%" , 334 },
{ "L2-L3 THD (U)", "%" , 336 },
{ "L3-L1 THD (U)", "%" , 338 },
{ "Average L-L THD", "%" , 340 },
{ "Total Energy", "kWh" , 342 },
{ "Total Reactive Energy", "kVArh" , 344 },
{ "L1 Energy Import", "kWh" , 346 },
{ "L2 Energy Import", "kWh" , 348 },
{ "L3 Energy Import", "kWh" , 350 },
{ "L1 Energy Export", "kWh" , 352 },
{ "L2 Energy Export", "kWh" , 354 },
{ "L3 Energy Export", "kWh" , 356 },
{ "L1 Energy (Total)", "kWh" , 358 },
{ "L2 Energy (Total)", "kWh" , 360 },
{ "L3 Energy (Total)", "kWh" , 362 },
{ "L1 Reactive Energy Import", "kVArh" , 364 },
{ "L2 Reactive Energy Import", "kVArh" , 366 },
{ "L3 Reactive Energy Import", "kVArh" , 368 },
{ "L1 Reactive Energy Export", "kVArh" , 370 },
{ "L2 Reactive Energy Export", "kVArh" , 372 },
{ "L3 Reactive Energy Export", "kVArh" , 374 },
{ "L1 Reactive Energy (Total)", "kVArh" , 376 },
{ "L2 Reactive Energy (Total)", "kVArh" , 378 },
{ "L3 Reactive Energy (Total)", "kVArh" , 380 },
{ NULL, NULL , 0 }
};
#endif

20
sdm72dm_registers.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef sdm72dm_registers_h
#define sdm72dm_registers_h
#define SMD72DM_NRVAL 9
// 9 Messwerte
static sdm_registers_t sdm72dm_registers[] = {
{ "Power", "W" , 52 },
{ "Energy Import", "kWh" , 72 },
{ "Energy Export", "kWh" , 74 },
{ "Energy (Total)", "kWh" , 342 },
{ "Total Energy (resettable)", "kWh" , 384 },
{ "Energy Import (resettable)", "kWh" , 388 },
{ "Energy Export (resettable)", "kWh" , 390 },
{ "Power Import", "W" , 1280 },
{ "Power Export", "W" , 1282 },
{ NULL, NULL , 0 }
};
#endif

24
telegraf/sdm2mqtt.conf Normal file
View File

@ -0,0 +1,24 @@
# # Read metrics from MQTT topic(s)
[[inputs.mqtt_consumer]]
servers = ["tcp://127.0.0.1:1883"]
topics = [
"sdm2mqtt2/#",
]
name_override = "sdm"
# ## Username and password to connect MQTT server.
# # username = "telegraf"
# # password = "metricsmetricsmetricsmetrics"
#
# ## Optional TLS Config
# # tls_ca = "/etc/telegraf/ca.pem"
# # tls_cert = "/etc/telegraf/cert.pem"
# # tls_key = "/etc/telegraf/key.pem"
# ## Use TLS but skip chain & host verification
# # insecure_skip_verify = false
#
data_format = "json_v2"
[[inputs.mqtt_consumer.json_v2]]
[[inputs.mqtt_consumer.json_v2.object]]
path = "@this"
tags = [ "name", "address", "type" ]