Start SDM series energy meters tools repo
This commit is contained in:
commit
4011b2d8a6
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*.o
|
||||
**.swp
|
17
Makefile
Normal file
17
Makefile
Normal 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
31
README.md
Normal 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
55
config.h
Normal 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
1001
grafana/dashboard.json
Normal file
File diff suppressed because it is too large
Load Diff
0
logs/.keep
Normal file
0
logs/.keep
Normal file
333
sdm.c
Normal file
333
sdm.c
Normal 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
56
sdm.h
Normal 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
35
sdm230m_registers.h
Normal 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
163
sdm2mqtt.c
Normal 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
12
sdm2mqtt.service
Normal 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
95
sdm630m_registers.h
Normal 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
20
sdm72dm_registers.h
Normal 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
24
telegraf/sdm2mqtt.conf
Normal 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" ]
|
Loading…
Reference in New Issue
Block a user