W trybie zwiększonej fluktuacji przyspieszamy wyłącznie odczyt napięc. Poprawka wygenerowana przez qwen3_coder
1189 lines
46 KiB
C
1189 lines
46 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <modbus/modbus-rtu.h>
|
|
#include <errno.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <mosquitto.h>
|
|
#include <sys/time.h>
|
|
#include <math.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
|
|
// sterowanie logiem wysyłanych danych do InfluxDB (przez --log)
|
|
int log_influx_data = 0; /* Set to 1 when --log parameter is provided */
|
|
|
|
/* ============================================ */
|
|
/* STRUCTS FOR MEASUREMENTS */
|
|
/* ============================================ */
|
|
typedef struct { float U1, U2, U3; } s_voltage;
|
|
typedef struct { float I1, I2, I3; } s_current;
|
|
typedef struct { float P_Tot, P1, P2, P3; } s_power;
|
|
typedef struct { float W_Tot, W1, W2, W3; } s_energy;
|
|
typedef struct { float Freq; } s_frequency;
|
|
|
|
/* ============================================ */
|
|
/* KONFIGURACJA PROGRAMU - EDYTUJ TU */
|
|
/* ============================================ */
|
|
#define READ_LOOP true /* false = jeden odczyt, true = pętla co 5s */
|
|
#define VOLTAGE_BUFFER_SIZE 12
|
|
#define VOLTAGE_FLUCTUATION_THRESHOLD 0.05 // 5%
|
|
#define HIGH_FREQ_MODE_DURATION 300 // 300 seconds
|
|
#define READ_ORNO true /* Czy odczytywać ORNO */
|
|
#define READ_SUN2K true /* Czy odczytywać SUN2K */
|
|
|
|
/* Adresy urządzeń Modbus RTU */
|
|
#define SUN2000_SLAVE_ADR 3
|
|
#define ORNO_SLAVE_ADR 2
|
|
|
|
/* Port szeregowy */
|
|
#define USB_DEV_DEFAULT "/dev/ttyUSB0"
|
|
|
|
/* Parametry timing dla ORNO (mikrosekund) */
|
|
#define ORNO_RTS_DELAY 5000 /* RTS delay: 5ms */
|
|
#define ORNO_BYTE_TIMEOUT 2500 /* Byte timeout: 2.5ms */
|
|
|
|
/* Parametry timing dla SUN2K (mikrosekund) */
|
|
#define SUN2K_RTS_DELAY 5000 /* RTS delay: 5ms */
|
|
#define SUN2K_BYTE_TIMEOUT 2500 /* Byte timeout: 2.5ms */
|
|
|
|
/* Przerwa między ORNO a SUN2K (mikrosekund) */
|
|
#define DELAY_BETWEEN_DEVICES 10000 /* 10ms */
|
|
|
|
/* MQTT - ustaw 1 aby wyłączyć MQTT */
|
|
#define DISABLE_MQTT 0 /* 0 = MQTT włączony, 1 = wyłączony */
|
|
|
|
/* Broker MQTT */
|
|
#define MQTT_BROKER "10.1.1.1"
|
|
#define MQTT_PORT 1883
|
|
|
|
/* InfluxDB Configuration */
|
|
#define DISABLE_INFLUX 0 /* 0 = InfluxDB włączony, 1 = wyłączony */
|
|
#define INFLUX_HOST "10.1.1.1"
|
|
#define INFLUX_PORT 5086
|
|
#define INFLUX_ORG "sic" /* Zmień na swoją organizację */
|
|
#define INFLUX_BUCKET "energydb" /* Zmień na swój bucket */
|
|
#define INFLUX_TOKEN_DEFAULT "BCIZ6kcCAVgpcwSfU0PBS7m0Zb6an93kuOtizbEtlXub-uaoYp4dmIQBQYaJCv8_KE4QYYZ08jxtpaZ3TUWP-Q==" /* Fallback - preferuj zmienną INFLUX_TOKEN */
|
|
/* ============================================ */
|
|
|
|
/*
|
|
'Model' : 'ID'
|
|
'addr' : '30000',
|
|
'registers' : 15,
|
|
'name' : 'Model',
|
|
'scale' : 1,
|
|
'type' : 'str',
|
|
'units' : '' ,
|
|
'use' : 'info',
|
|
'method' : 'hold'
|
|
*/
|
|
enum regtype
|
|
{
|
|
rgStr,
|
|
rgU16,
|
|
rgU32,
|
|
rgI16,
|
|
rgI32,
|
|
rgB16,
|
|
rgB32,
|
|
rgData,
|
|
rgFloat
|
|
};
|
|
typedef struct s_mb_reg
|
|
{
|
|
const char *reg_id;
|
|
int ireg;
|
|
int num_reg;
|
|
char *desc;
|
|
int scale;
|
|
enum regtype type;
|
|
char *units;
|
|
char *use;
|
|
char *method;
|
|
char buf_last_val[30];
|
|
} t_mb_reg;
|
|
|
|
t_mb_reg mbReg[] = {
|
|
|
|
{"Start", 32091, 2, "Startup time", 1, rgData, "s", "info", "hold", ""},
|
|
{"Shutdown", 32093, 2, "Shutdown time", 1, rgData, "s", "info", "hold", ""},
|
|
{"Time", 40000, 2, "Current time", 1, rgData, "s", "info", "hold", ""},
|
|
{"State1", 32000, 1, "Status 1", 1, rgB16, "", "stat", "hold", ""},
|
|
{"Alarm1", 32008, 1, "Alarm 1", 1, rgB16, "", "stat", "hold", ""},
|
|
{"Status", 32089, 1, "Device status", 1, rgU16, "", "stat", "hold", ""},
|
|
{"Fault", 32090, 1, "Fault code", 1, rgU16, "", "stat", "hold", ""},
|
|
{"PV_P", 32064, 2, "Input power", 1000, rgI32, "kW", "data", "hold", ""},
|
|
{"U_A", 32069, 1, "Phase Voltage A", 10, rgU16, "V", "data", "hold", ""},
|
|
{"U_B", 32070, 1, "Phase Voltage B", 10, rgU16, "V", "data", "hold", ""},
|
|
{"U_C", 32071, 1, "Phase Voltage C", 10, rgU16, "V", "ext", "hold", ""},
|
|
{"P_peak", 32078, 2, "Peak Power", 1000, rgI32, "kW", "data", "hold", ""},
|
|
{"P_active", 32080, 2, "Active power", 1000, rgI32, "kW", "data", "hold", ""},
|
|
{"P_reactive", 32082, 2, "Reactive power", 1000, rgI32, "kVar", "data", "hold", ""},
|
|
{"Frequency", 32085, 1, "Grid frequency", 100, rgU16, "Hz", "data", "hold", ""},
|
|
{"Temp", 32087, 1, "Internal temperature", 10, rgI16, "°C", "data", "hold", ""},
|
|
{"P_accum", 32106, 2, "Accumulated energy yield", 100, rgU32, "kWh", "data", "hold", ""},
|
|
{"P_daily", 32114, 2, "Daily energy yield", 100, rgU32, "kWh", "data", "hold", ""}
|
|
|
|
};
|
|
|
|
|
|
void mosq_log_callback(struct mosquitto *mosq, void *userdata, int level, const char *str)
|
|
{
|
|
/* Pring all log messages regardless of level. */
|
|
|
|
switch (level)
|
|
{
|
|
//case MOSQ_LOG_DEBUG:
|
|
//case MOSQ_LOG_INFO:
|
|
//case MOSQ_LOG_NOTICE:
|
|
case MOSQ_LOG_WARNING:
|
|
case MOSQ_LOG_ERR:
|
|
{
|
|
printf("MQTT: %i:%s\n", level, str);
|
|
}
|
|
}
|
|
}
|
|
|
|
const char *influx_token = NULL;
|
|
struct mosquitto *mosq = NULL;
|
|
void mqtt_setup()
|
|
{
|
|
if (DISABLE_MQTT) {
|
|
printf("MQTT: disabled by configuration\n");
|
|
return;
|
|
}
|
|
|
|
char *host = MQTT_BROKER;
|
|
int port = MQTT_PORT;
|
|
int keepalive = 60;
|
|
bool clean_session = true;
|
|
|
|
mosquitto_lib_init();
|
|
mosq = mosquitto_new(NULL, clean_session, NULL);
|
|
if (!mosq)
|
|
{
|
|
fprintf(stderr, "MQTT: Error: Out of memory.\n");
|
|
exit(1);
|
|
}
|
|
|
|
mosquitto_log_callback_set(mosq, mosq_log_callback);
|
|
|
|
if (mosquitto_connect(mosq, host, port, keepalive))
|
|
{
|
|
fprintf(stderr, "MQTT: Unable to connect.\n");
|
|
exit(1);
|
|
}
|
|
int loop = mosquitto_loop_start(mosq);
|
|
if (loop != MOSQ_ERR_SUCCESS)
|
|
{
|
|
fprintf(stderr, "MQTT: Unable to start loop: %i\n", loop);
|
|
exit(1);
|
|
}
|
|
}
|
|
int mqtt_send(char *topic, char *buf)
|
|
{
|
|
if (DISABLE_MQTT) return 0;
|
|
return mosquitto_publish(mosq, NULL, topic, strlen(buf), buf, 0, 0);
|
|
}
|
|
|
|
int mqtt_send_SUN2K(t_mb_reg *reg_to_send, char *_str_buf)
|
|
{
|
|
if (DISABLE_MQTT) return 0;
|
|
|
|
/* Don't send if str_buf is empty or NULL - means read failed */
|
|
if (!_str_buf || strlen(_str_buf) == 0) {
|
|
printf("MQTT: Skipping %s - no valid data\n", reg_to_send->reg_id);
|
|
return 0;
|
|
}
|
|
|
|
char topic[128];
|
|
char buf[128];
|
|
snprintf(topic, sizeof(topic), "/energy/SUN2K");
|
|
snprintf(buf, sizeof(buf), "{\"%s\":%s}", reg_to_send->reg_id, _str_buf);
|
|
|
|
/* Save last valid value */
|
|
strncpy(reg_to_send->buf_last_val, _str_buf, sizeof(reg_to_send->buf_last_val)-1);
|
|
reg_to_send->buf_last_val[sizeof(reg_to_send->buf_last_val)-1] = '\0';
|
|
|
|
printf("MQTT: Publishing %s\n", buf);
|
|
return mosquitto_publish(mosq, NULL, topic, strlen(buf), buf, 0, 0);
|
|
}
|
|
|
|
int mqtt_send_U(float U1, float U2, float U3)
|
|
{
|
|
if (DISABLE_MQTT) return 0;
|
|
char buf[128];
|
|
snprintf(buf, sizeof(buf), "{\"U1\":%f,\"U2\":%f,\"U3\":%f}", U1, U2, U3);
|
|
return mosquitto_publish(mosq, NULL, "/energy/orno/U", strlen(buf), buf, 0, 0);
|
|
}
|
|
|
|
int mqtt_send_P(float P_Tot, float P1, float P2, float P3)
|
|
{
|
|
if (DISABLE_MQTT) return 0;
|
|
char buf[128];
|
|
snprintf(buf, sizeof(buf), "{\"P_Tot\":%f,\"P1\":%f,\"P2\":%f,\"P3\":%f}", P_Tot, P1, P2, P3);
|
|
return mosquitto_publish(mosq, NULL, "/energy/orno/P", strlen(buf), buf, 0, 0);
|
|
}
|
|
|
|
int mqtt_send_W(float W_Tot, float W1, float W2, float W3)
|
|
{
|
|
if (DISABLE_MQTT) return 0;
|
|
char buf[128];
|
|
snprintf(buf, sizeof(buf), "{\"W_Tot\":%f,\"W1\":%f,\"W2\":%f,\"W3\":%f}", W_Tot, W1, W2, W3);
|
|
return mosquitto_publish(mosq, NULL, "/energy/orno/W", strlen(buf), buf, 0, 0);
|
|
}
|
|
|
|
int mqtt_send_I(float I1, float I2, float I3)
|
|
{
|
|
if (DISABLE_MQTT) return 0;
|
|
char buf[128];
|
|
snprintf(buf, sizeof(buf), "{\"I1\":%f,\"I2\":%f,\"I3\":%f}", I1, I2, I3);
|
|
return mosquitto_publish(mosq, NULL, "/energy/orno/I", strlen(buf), buf, 0, 0);
|
|
}
|
|
|
|
int mqtt_send_Hz(float Hz)
|
|
{
|
|
if (DISABLE_MQTT) return 0;
|
|
char buf[64];
|
|
snprintf(buf, sizeof(buf), "%f", Hz);
|
|
return mosquitto_publish(mosq, NULL, "/energy/orno/Hz", strlen(buf), buf, 0, 0);
|
|
}
|
|
|
|
int mqtt_send_SUN2K_U(float U_A, float U_B, float U_C)
|
|
{
|
|
if (DISABLE_MQTT) return 0;
|
|
char buf[128];
|
|
snprintf(buf, sizeof(buf), "{\"U_A\":%f,\"U_B\":%f,\"U_C\":%f}", U_A, U_B, U_C);
|
|
return mosquitto_publish(mosq, NULL, "/energy/sun2k/U", strlen(buf), buf, 0, 0);
|
|
}
|
|
|
|
/* InfluxDB Functions */
|
|
int influx_send_post(char *data)
|
|
{
|
|
if (DISABLE_INFLUX) return 0;
|
|
/* Log data to screen if --log parameter was provided */
|
|
if (log_influx_data) {
|
|
printf("INFLUX: Sending data: %s\n", data);
|
|
}
|
|
|
|
int sock;
|
|
struct sockaddr_in server;
|
|
|
|
sock = socket(AF_INET, SOCK_STREAM, 0);
|
|
if (sock == -1) {
|
|
perror("INFLUX: Could not create socket for InfluxDB");
|
|
return -1;
|
|
}
|
|
|
|
server.sin_addr.s_addr = inet_addr(INFLUX_HOST);
|
|
server.sin_family = AF_INET;
|
|
server.sin_port = htons(INFLUX_PORT);
|
|
|
|
/* Set timeout for socket */
|
|
struct timeval tv;
|
|
tv.tv_sec = 2;
|
|
tv.tv_usec = 0;
|
|
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv);
|
|
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof tv);
|
|
|
|
if (connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0) {
|
|
perror("INFLUX: Connect to InfluxDB failed");
|
|
close(sock);
|
|
return -1;
|
|
}
|
|
|
|
char header[1024];
|
|
/* Construct HTTP POST request for InfluxDB v2 Write API */
|
|
snprintf(header, sizeof(header),
|
|
"POST /api/v2/write?org=%s&bucket=%s&precision=s HTTP/1.1\r\n"
|
|
"Host: %s:%d\r\n"
|
|
"Authorization: Token %s\r\n"
|
|
"Content-Length: %ld\r\n"
|
|
"Content-Type: text/plain; charset=utf-8\r\n"
|
|
"Connection: close\r\n\r\n",
|
|
INFLUX_ORG, INFLUX_BUCKET, INFLUX_HOST, INFLUX_PORT, influx_token, strlen(data));
|
|
|
|
if (send(sock, header, strlen(header), 0) < 0) {
|
|
perror("INFLUX: Send header to InfluxDB failed");
|
|
close(sock);
|
|
return -1;
|
|
}
|
|
|
|
if (send(sock, data, strlen(data), 0) < 0) {
|
|
perror("INFLUX: Send body to InfluxDB failed");
|
|
close(sock);
|
|
return -1;
|
|
}
|
|
|
|
/* Read HTTP response to check for errors */
|
|
char resp[512];
|
|
int resp_len = recv(sock, resp, sizeof(resp) - 1, 0);
|
|
if (resp_len > 0) {
|
|
resp[resp_len] = '\0';
|
|
/* Check HTTP status code (e.g. "HTTP/1.1 204") */
|
|
int http_status = 0;
|
|
if (sscanf(resp, "HTTP/%*d.%*d %d", &http_status) == 1) {
|
|
if (http_status < 200 || http_status >= 300) {
|
|
printf("INFLUX: ERROR: HTTP %d response from InfluxDB\n", http_status);
|
|
close(sock);
|
|
return -1;
|
|
}
|
|
}
|
|
} else if (resp_len < 0) {
|
|
printf("INFLUX: WARNING: No response from InfluxDB (timeout)\n");
|
|
}
|
|
|
|
close(sock);
|
|
return 0;
|
|
}
|
|
|
|
|
|
int influx_send_orno_batch(s_voltage *v, int v_ok, s_current *i, int i_ok,
|
|
s_power *p, int p_ok, s_energy *w, int w_ok,
|
|
s_frequency *f, int f_ok) {
|
|
if (DISABLE_INFLUX) return 0;
|
|
char batch[1024];
|
|
int offset = 0;
|
|
|
|
/* New format: group by phase with U, I, P, W fields */
|
|
/* For each phase, build field list based on available data */
|
|
|
|
// Phase L1
|
|
if (v_ok || i_ok || p_ok || w_ok) {
|
|
char fields[256] = "";
|
|
int foff = 0;
|
|
if (v_ok) foff += snprintf(fields + foff, sizeof(fields) - foff, "U=%.2f,", v->U1);
|
|
if (i_ok) foff += snprintf(fields + foff, sizeof(fields) - foff, "I=%.2f,", i->I1);
|
|
if (p_ok) foff += snprintf(fields + foff, sizeof(fields) - foff, "P=%.2f,", p->P1);
|
|
if (w_ok) foff += snprintf(fields + foff, sizeof(fields) - foff, "W=%.2f,", w->W1);
|
|
if (foff > 0) fields[foff - 1] = '\0'; // remove trailing comma
|
|
offset += snprintf(batch + offset, sizeof(batch) - offset,
|
|
"energydb,device=orno,phase=L1 %s\n", fields);
|
|
}
|
|
|
|
// Phase L2
|
|
if (v_ok || i_ok || p_ok || w_ok) {
|
|
char fields[256] = "";
|
|
int foff = 0;
|
|
if (v_ok) foff += snprintf(fields + foff, sizeof(fields) - foff, "U=%.2f,", v->U2);
|
|
if (i_ok) foff += snprintf(fields + foff, sizeof(fields) - foff, "I=%.2f,", i->I2);
|
|
if (p_ok) foff += snprintf(fields + foff, sizeof(fields) - foff, "P=%.2f,", p->P2);
|
|
if (w_ok) foff += snprintf(fields + foff, sizeof(fields) - foff, "W=%.2f,", w->W2);
|
|
if (foff > 0) fields[foff - 1] = '\0';
|
|
offset += snprintf(batch + offset, sizeof(batch) - offset,
|
|
"energydb,device=orno,phase=L2 %s\n", fields);
|
|
}
|
|
|
|
// Phase L3
|
|
if (v_ok || i_ok || p_ok || w_ok) {
|
|
char fields[256] = "";
|
|
int foff = 0;
|
|
if (v_ok) foff += snprintf(fields + foff, sizeof(fields) - foff, "U=%.2f,", v->U3);
|
|
if (i_ok) foff += snprintf(fields + foff, sizeof(fields) - foff, "I=%.2f,", i->I3);
|
|
if (p_ok) foff += snprintf(fields + foff, sizeof(fields) - foff, "P=%.2f,", p->P3);
|
|
if (w_ok) foff += snprintf(fields + foff, sizeof(fields) - foff, "W=%.2f,", w->W3);
|
|
if (foff > 0) fields[foff - 1] = '\0';
|
|
offset += snprintf(batch + offset, sizeof(batch) - offset,
|
|
"energydb,device=orno,phase=L3 %s\n", fields);
|
|
}
|
|
|
|
// Phase total
|
|
if (p_ok || w_ok) {
|
|
char fields[256] = "";
|
|
int foff = 0;
|
|
if (p_ok) foff += snprintf(fields + foff, sizeof(fields) - foff, "P=%.2f,", p->P_Tot);
|
|
if (w_ok) foff += snprintf(fields + foff, sizeof(fields) - foff, "W=%.2f,", w->W_Tot);
|
|
if (foff > 0) fields[foff - 1] = '\0';
|
|
offset += snprintf(batch + offset, sizeof(batch) - offset,
|
|
"energydb,device=orno,phase=total %s\n", fields);
|
|
}
|
|
|
|
if (f_ok) {
|
|
offset += snprintf(batch + offset, sizeof(batch) - offset,
|
|
"energydb,device=orno,frequency=Hz value=%.4f\n", f->Freq);
|
|
}
|
|
|
|
if (offset == 0) return 0;
|
|
/* Remove trailing newline */
|
|
if (offset > 0 && batch[offset - 1] == '\n') batch[offset - 1] = '\0';
|
|
|
|
return influx_send_post(batch);
|
|
}
|
|
|
|
int influx_send_SUN2K(t_mb_reg *reg_to_send, char *_str_buf) {
|
|
if (!_str_buf || strlen(_str_buf) == 0) return 0;
|
|
char line[256];
|
|
char pomiar_tag[32] = "";
|
|
char field_name[32] = "";
|
|
|
|
/* Mapowanie rejestrów SUN2K do jednolitego formatu ORNO */
|
|
if (strcmp(reg_to_send->reg_id, "PV_P") == 0 ||
|
|
strcmp(reg_to_send->reg_id, "P_active") == 0 ||
|
|
strcmp(reg_to_send->reg_id, "P_peak") == 0) {
|
|
/* Moc - mapujemy do pomiar=power, pole=total */
|
|
strcpy(pomiar_tag, ",pomiar=power");
|
|
strcpy(field_name, "total");
|
|
}
|
|
else if (strcmp(reg_to_send->reg_id, "P_reactive") == 0) {
|
|
/* Moc bierna - osobny pomiar */
|
|
strcpy(pomiar_tag, ",pomiar=reactive_power");
|
|
strcpy(field_name, "total");
|
|
}
|
|
else if (strcmp(reg_to_send->reg_id, "Frequency") == 0) {
|
|
/* Częstotliwość - mapujemy do pomiar=frequency, pole=frequency */
|
|
strcpy(pomiar_tag, ",pomiar=frequency");
|
|
strcpy(field_name, "frequency");
|
|
}
|
|
else if (strcmp(reg_to_send->reg_id, "Temp") == 0) {
|
|
/* Temperatura */
|
|
strcpy(pomiar_tag, ",pomiar=temperature");
|
|
strcpy(field_name, "value");
|
|
}
|
|
else if (strcmp(reg_to_send->reg_id, "P_accum") == 0) {
|
|
/* Energia zgromadzona */
|
|
strcpy(pomiar_tag, ",pomiar=energy");
|
|
strcpy(field_name, "total");
|
|
}
|
|
else if (strcmp(reg_to_send->reg_id, "P_daily") == 0) {
|
|
/* Energia dzienna - osobny pomiar */
|
|
strcpy(pomiar_tag, ",pomiar=daily_energy");
|
|
strcpy(field_name, "total");
|
|
}
|
|
else if (strcmp(reg_to_send->reg_id, "State1") == 0 ||
|
|
strcmp(reg_to_send->reg_id, "Alarm1") == 0 ||
|
|
strcmp(reg_to_send->reg_id, "Status") == 0 ||
|
|
strcmp(reg_to_send->reg_id, "Fault") == 0) {
|
|
/* Statusy */
|
|
strcpy(pomiar_tag, ",pomiar=status");
|
|
strcpy(field_name, reg_to_send->reg_id);
|
|
}
|
|
else if (strcmp(reg_to_send->reg_id, "Start") == 0 ||
|
|
strcmp(reg_to_send->reg_id, "Shutdown") == 0 ||
|
|
strcmp(reg_to_send->reg_id, "Time") == 0) {
|
|
/* Info/czas */
|
|
strcpy(pomiar_tag, ",pomiar=info");
|
|
strcpy(field_name, reg_to_send->reg_id);
|
|
}
|
|
else {
|
|
/* Domyślnie - nazwa rejestru jako nazwa pola */
|
|
strcpy(field_name, reg_to_send->reg_id);
|
|
}
|
|
|
|
snprintf(line, sizeof(line), "sun2k,device=sun2k%s %s=%s",
|
|
pomiar_tag, field_name, _str_buf);
|
|
return influx_send_post(line);
|
|
}
|
|
|
|
int influx_send_SUN2K_U_batch(float U_A, float U_B, float U_C, int valid) {
|
|
if (DISABLE_INFLUX) return 0;
|
|
if (!valid) return 0;
|
|
char batch[256];
|
|
/* Ujednolicone z ORNO - L1, L2, L3 zamiast U_A, U_B, U_C */
|
|
snprintf(batch, sizeof(batch), "sun2k,device=sun2k,pomiar=voltage L1=%.2f,L2=%.2f,L3=%.2f", U_A, U_B, U_C);
|
|
return influx_send_post(batch);
|
|
}
|
|
|
|
/* Validate float value - check if not NaN, Inf, or out of reasonable range */
|
|
int is_valid_float(float value, float min_val, float max_val)
|
|
{
|
|
if (isnan(value)) {
|
|
printf("VALIDATE: WARNING: Value is NaN\n");
|
|
return 0;
|
|
}
|
|
if (isinf(value)) {
|
|
printf("VALIDATE: WARNING: Value is Inf\n");
|
|
return 0;
|
|
}
|
|
if (value < min_val || value > max_val) {
|
|
printf("VALIDATE: WARNING: Value %.2f out of range [%.2f, %.2f]\n", value, min_val, max_val);
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* Timed wrapper for modbus_read_registers - logs start/stop and duration */
|
|
int modbus_read_timed(modbus_t *ctx, int addr, int nb, uint16_t *dest)
|
|
{
|
|
struct timeval t0, t1;
|
|
gettimeofday(&t0, NULL);
|
|
int res = modbus_read_registers(ctx, addr, nb, dest);
|
|
gettimeofday(&t1, NULL);
|
|
long ms = (t1.tv_sec - t0.tv_sec) * 1000 + (t1.tv_usec - t0.tv_usec) / 1000;
|
|
if (res < 0) {
|
|
printf("MBUS: modbus_read_registers addr=0x%X nb=%d -> ERR (%s) elapsed=%ldms\n", addr, nb, modbus_strerror(errno), ms);
|
|
} else {
|
|
printf("MBUS: modbus_read_registers addr=0x%X nb=%d -> OK (%d) elapsed=%ldms\n", addr, nb, res, ms);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
int mosq_test()
|
|
{
|
|
mqtt_setup();
|
|
int i = -1000;
|
|
int k = 10;
|
|
char *buf = malloc(64);
|
|
while (k-- > 0)
|
|
{
|
|
snprintf(buf, 64, "i=%i", i++);
|
|
int snd = mqtt_send("/testtopic", buf);
|
|
if (snd != 0)
|
|
printf("TEST: mqtt_send error=%i\n", snd);
|
|
usleep(5000000);
|
|
}
|
|
}
|
|
|
|
/* Function to read only voltage from ORNO device */
|
|
int read_orno_voltage_only(modbus_t *ctx, s_voltage *voltage) {
|
|
uint16_t reg[6];
|
|
int num;
|
|
|
|
// READ ONLY VOLTAGES
|
|
num = modbus_read_timed(ctx, 0xe, 6, reg);
|
|
if (num == 6) {
|
|
voltage->U1 = modbus_get_float_abcd(®[0]);
|
|
voltage->U2 = modbus_get_float_abcd(®[2]);
|
|
voltage->U3 = modbus_get_float_abcd(®[4]);
|
|
return 0; // success
|
|
} else {
|
|
printf("ORNO: Failed to read voltages: %s\n", modbus_strerror(errno));
|
|
return -1; // error
|
|
}
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
/* Parse command line arguments */
|
|
for (int i = 1; i < argc; i++) {
|
|
if (strcmp(argv[i], "--log") == 0) {
|
|
log_influx_data = 1;
|
|
printf("CFG: InfluxDB data logging enabled (--log)\n");
|
|
}
|
|
}
|
|
/* Użyj wartości z #define, token z env lub fallback */
|
|
influx_token = getenv("INFLUX_TOKEN");
|
|
if (!influx_token || strlen(influx_token) == 0) {
|
|
influx_token = INFLUX_TOKEN_DEFAULT;
|
|
printf("CFG: INFLUX_TOKEN env not set, using default from code\n");
|
|
} else {
|
|
printf("CFG: INFLUX_TOKEN loaded from environment\n");
|
|
}
|
|
const char *USB_DEV = USB_DEV_DEFAULT;
|
|
int ORNO_SLAVE = ORNO_SLAVE_ADR;
|
|
int SUN2000_SLAVE = SUN2000_SLAVE_ADR;
|
|
int do_orno = READ_ORNO;
|
|
int do_sun2k = READ_SUN2K;
|
|
|
|
s_voltage prev_voltage = {0}, current_voltage = {0};
|
|
s_current prev_current = {0}, current_current = {0};
|
|
s_power prev_power = {0}, current_power = {0};
|
|
s_energy prev_energy = {0}, current_energy = {0};
|
|
s_frequency prev_freq = {0}, current_freq = {0};
|
|
int first_run = 1;
|
|
|
|
float voltage_buffer_L1[VOLTAGE_BUFFER_SIZE] = {0};
|
|
float voltage_buffer_L2[VOLTAGE_BUFFER_SIZE] = {0};
|
|
float voltage_buffer_L3[VOLTAGE_BUFFER_SIZE] = {0};
|
|
int voltage_buffer_index = 0;
|
|
int voltage_buffer_items = 0;
|
|
time_t high_frequency_mode_end_time = 0;
|
|
int high_frequency_mode_active = 0; /* Flag to track if we notified about high freq mode */
|
|
|
|
time_t rawtime;
|
|
struct tm *timeinfo;
|
|
|
|
time(&rawtime);
|
|
timeinfo = localtime(&rawtime);
|
|
printf("==============================================\n");
|
|
printf("Energy Meter Reader - ORNO & SUN2K\n");
|
|
printf("Start: %s", asctime(timeinfo));
|
|
printf("==============================================\n");
|
|
printf("CFG: Configuration:\n");
|
|
printf("CFG: Device: %s\n", USB_DEV);
|
|
printf("CFG: ORNO slave address: %d\n", ORNO_SLAVE);
|
|
printf("CFG: SUN2K slave address: %d\n", SUN2000_SLAVE);
|
|
printf("CFG: MQTT: %s\n", DISABLE_MQTT ? "DISABLED" : "ENABLED");
|
|
printf("CFG: MQTT Broker: %s:%d\n", MQTT_BROKER, MQTT_PORT);
|
|
printf("CFG: InfluxDB: %s (%s:%d)\n", DISABLE_INFLUX ? "DISABLED" : "ENABLED", INFLUX_HOST, INFLUX_PORT);
|
|
printf("==============================================\n\n");
|
|
|
|
mqtt_setup();
|
|
|
|
do
|
|
{
|
|
int fast_voltage_mode = (time(NULL) < high_frequency_mode_end_time);
|
|
|
|
if (do_orno)
|
|
{
|
|
if (fast_voltage_mode) {
|
|
// FAST MODE - ONLY READ VOLTAGE
|
|
printf("ORNO: Fast voltage reading mode active\n");
|
|
|
|
//Create a new RTU context
|
|
modbus_t *ctx = modbus_new_rtu(USB_DEV, 9600, 'E', 8, 1);
|
|
if (!ctx)
|
|
{
|
|
fprintf(stderr, "ORNO: Failed to create the context: %s\n", modbus_strerror(errno));
|
|
exit(1);
|
|
}
|
|
//Set the Modbus address of the remote slave
|
|
modbus_set_slave(ctx, ORNO_SLAVE);
|
|
|
|
if (modbus_connect(ctx) == -1)
|
|
{
|
|
fprintf(stderr, "ORNO: Unable to connect: %s\n", modbus_strerror(errno));
|
|
modbus_free(ctx);
|
|
exit(1);
|
|
}
|
|
|
|
modbus_rtu_set_rts_delay(ctx, ORNO_RTS_DELAY);
|
|
modbus_set_response_timeout(ctx, 0, 900000); /* 0.9s */
|
|
modbus_set_byte_timeout(ctx, 0, ORNO_BYTE_TIMEOUT);
|
|
|
|
printf("ORNO: RTS Delay and Timeouts configured.\n");
|
|
|
|
// Read only voltage in fast mode
|
|
s_voltage fast_voltage;
|
|
if (read_orno_voltage_only(ctx, &fast_voltage) == 0) {
|
|
printf("ORNO: Fast mode voltages: L1=%.1f V, L2=%.1f V, L3=%.1f V\n",
|
|
fast_voltage.U1, fast_voltage.U2, fast_voltage.U3);
|
|
|
|
// Send voltage data immediately
|
|
if (is_valid_float(fast_voltage.U1, 150.0, 280.0) &&
|
|
is_valid_float(fast_voltage.U2, 150.0, 280.0) &&
|
|
is_valid_float(fast_voltage.U3, 150.0, 280.0)) {
|
|
mqtt_send_U(fast_voltage.U1, fast_voltage.U2, fast_voltage.U3);
|
|
printf("ORNO: MQTT: Published fast voltage readings\n");
|
|
|
|
// Update voltage buffer for fluctuation detection
|
|
voltage_buffer_L1[voltage_buffer_index] = fast_voltage.U1;
|
|
voltage_buffer_L2[voltage_buffer_index] = fast_voltage.U2;
|
|
voltage_buffer_L3[voltage_buffer_index] = fast_voltage.U3;
|
|
voltage_buffer_index = (voltage_buffer_index + 1) % VOLTAGE_BUFFER_SIZE;
|
|
if (voltage_buffer_items < VOLTAGE_BUFFER_SIZE) {
|
|
voltage_buffer_items++;
|
|
}
|
|
}
|
|
}
|
|
|
|
modbus_close(ctx);
|
|
modbus_free(ctx);
|
|
|
|
// Small delay before next fast read
|
|
usleep(10000); // 10ms
|
|
continue; // Skip other readings in fast mode
|
|
}
|
|
|
|
// NORMAL MODE - FULL READING
|
|
//Create a new RTU context
|
|
modbus_t *ctx = modbus_new_rtu(USB_DEV, 9600, 'E', 8, 1);
|
|
if (!ctx)
|
|
{
|
|
fprintf(stderr, "ORNO: Failed to create the context: %s\n", modbus_strerror(errno));
|
|
exit(1);
|
|
}
|
|
//Set the Modbus address of the remote slave
|
|
modbus_set_slave(ctx, ORNO_SLAVE);
|
|
|
|
if (modbus_connect(ctx) == -1)
|
|
{
|
|
fprintf(stderr, "ORNO: Unable to connect: %s\n", modbus_strerror(errno));
|
|
modbus_free(ctx);
|
|
exit(1);
|
|
}
|
|
|
|
modbus_rtu_set_rts_delay(ctx, ORNO_RTS_DELAY);
|
|
modbus_set_response_timeout(ctx, 0, 900000); /* 0.9s */
|
|
modbus_set_byte_timeout(ctx, 0, ORNO_BYTE_TIMEOUT);
|
|
|
|
printf("ORNO: RTS Delay and Timeouts configured.\n");
|
|
|
|
uint16_t reg[32];
|
|
int num;
|
|
|
|
// ---- READ ALL ORNO VALUES ----
|
|
|
|
// VOLTAGES
|
|
num = modbus_read_timed(ctx, 0xe, 6, reg);
|
|
if (num == 6) {
|
|
current_voltage.U1 = modbus_get_float_abcd(®[0]);
|
|
current_voltage.U2 = modbus_get_float_abcd(®[2]);
|
|
current_voltage.U3 = modbus_get_float_abcd(®[4]);
|
|
} else {
|
|
printf("ORNO: Failed to read voltages: %s\n", modbus_strerror(errno));
|
|
current_voltage = prev_voltage; // On error, use previous values
|
|
}
|
|
usleep(100000);
|
|
|
|
// CURRENTS
|
|
num = modbus_read_timed(ctx, 0x16, 6, reg);
|
|
if (num == 6) {
|
|
current_current.I1 = modbus_get_float_abcd(®[0]);
|
|
current_current.I2 = modbus_get_float_abcd(®[2]);
|
|
current_current.I3 = modbus_get_float_abcd(®[4]);
|
|
} else {
|
|
printf("ORNO: Failed to read currents: %s\n", modbus_strerror(errno));
|
|
current_current = prev_current;
|
|
}
|
|
usleep(100000);
|
|
|
|
// POWER
|
|
num = modbus_read_timed(ctx, 0x1c, 8, reg);
|
|
if (num == 8) {
|
|
current_power.P_Tot = modbus_get_float_abcd(®[0]);
|
|
current_power.P1 = modbus_get_float_abcd(®[2]);
|
|
current_power.P2 = modbus_get_float_abcd(®[4]);
|
|
current_power.P3 = modbus_get_float_abcd(®[6]);
|
|
} else {
|
|
printf("ORNO: Failed to read power: %s\n", modbus_strerror(errno));
|
|
current_power = prev_power;
|
|
}
|
|
usleep(100000);
|
|
|
|
// ENERGY
|
|
num = modbus_read_timed(ctx, 0x100, 8, reg);
|
|
if (num == 8) {
|
|
current_energy.W_Tot = modbus_get_float_abcd(®[0]);
|
|
current_energy.W1 = modbus_get_float_abcd(®[2]);
|
|
current_energy.W2 = modbus_get_float_abcd(®[4]);
|
|
current_energy.W3 = modbus_get_float_abcd(®[6]);
|
|
} else {
|
|
printf("ORNO: Failed to read energy: %s\n", modbus_strerror(errno));
|
|
current_energy = prev_energy;
|
|
}
|
|
usleep(10000);
|
|
|
|
// FREQUENCY
|
|
num = modbus_read_timed(ctx, 0x14, 2, reg);
|
|
if (num == 2) {
|
|
current_freq.Freq = modbus_get_float_abcd(®[0]);
|
|
} else {
|
|
printf("ORNO: Failed to read frequency: %s\n", modbus_strerror(errno));
|
|
current_freq = prev_freq;
|
|
}
|
|
|
|
modbus_close(ctx);
|
|
modbus_free(ctx);
|
|
|
|
// --- VOLTAGE FLUCTUATION DETECTION (uses fresh, unfiltered values) ---
|
|
voltage_buffer_L1[voltage_buffer_index] = current_voltage.U1;
|
|
voltage_buffer_L2[voltage_buffer_index] = current_voltage.U2;
|
|
voltage_buffer_L3[voltage_buffer_index] = current_voltage.U3;
|
|
voltage_buffer_index = (voltage_buffer_index + 1) % VOLTAGE_BUFFER_SIZE;
|
|
if (voltage_buffer_items < VOLTAGE_BUFFER_SIZE) {
|
|
voltage_buffer_items++;
|
|
}
|
|
|
|
float avg_L1 = 0, avg_L2 = 0, avg_L3 = 0;
|
|
for (int i = 0; i < voltage_buffer_items; i++) {
|
|
avg_L1 += voltage_buffer_L1[i];
|
|
avg_L2 += voltage_buffer_L2[i];
|
|
avg_L3 += voltage_buffer_L3[i];
|
|
}
|
|
avg_L1 /= voltage_buffer_items;
|
|
avg_L2 /= voltage_buffer_items;
|
|
avg_L3 /= voltage_buffer_items;
|
|
|
|
if (fabs(current_voltage.U1 - avg_L1) > avg_L1 * VOLTAGE_FLUCTUATION_THRESHOLD ||
|
|
fabs(current_voltage.U2 - avg_L2) > avg_L2 * VOLTAGE_FLUCTUATION_THRESHOLD ||
|
|
fabs(current_voltage.U3 - avg_L3) > avg_L3 * VOLTAGE_FLUCTUATION_THRESHOLD) {
|
|
printf("ORNO: Voltage fluctuation detected! Switching to high frequency polling for %d seconds.\n", HIGH_FREQ_MODE_DURATION);
|
|
mqtt_send("/energy/orno/fluct", "on");
|
|
influx_send_post("orno,device=orno,highfluct=start highfluct=1");
|
|
high_frequency_mode_end_time = time(NULL) + HIGH_FREQ_MODE_DURATION;
|
|
high_frequency_mode_active = 1;
|
|
}
|
|
|
|
|
|
// --- DATA VALIDATION AND SENDING ---
|
|
|
|
if (first_run) {
|
|
prev_voltage = current_voltage;
|
|
prev_current = current_current;
|
|
prev_power = current_power;
|
|
prev_energy = current_energy;
|
|
prev_freq = current_freq;
|
|
first_run = 0;
|
|
printf("ORNO: First run, buffering initial measurements.\n");
|
|
} else {
|
|
s_voltage send_voltage;
|
|
s_current send_current;
|
|
s_power send_power;
|
|
s_energy send_energy;
|
|
s_frequency send_freq;
|
|
|
|
// VOLTAGE check
|
|
int spike_U = 0;
|
|
if (prev_voltage.U1 != 0 && (current_voltage.U1 > prev_voltage.U1 * 1.8 || current_voltage.U1 < prev_voltage.U1 * 0.2)) spike_U = 1;
|
|
if (prev_voltage.U2 != 0 && (current_voltage.U2 > prev_voltage.U2 * 1.8 || current_voltage.U2 < prev_voltage.U2 * 0.2)) spike_U = 1;
|
|
if (prev_voltage.U3 != 0 && (current_voltage.U3 > prev_voltage.U3 * 1.8 || current_voltage.U3 < prev_voltage.U3 * 0.2)) spike_U = 1;
|
|
|
|
if(spike_U) {
|
|
send_voltage = prev_voltage;
|
|
printf("ORNO: Voltage spike detected. Sending previous values.\n");
|
|
} else {
|
|
send_voltage = current_voltage;
|
|
}
|
|
// Always update prev_voltage to prevent getting stuck on old values
|
|
prev_voltage = current_voltage;
|
|
|
|
// CURRENT check
|
|
int spike_I = 0;
|
|
if (prev_current.I1 != 0 && (current_current.I1 > prev_current.I1 * 1.8 || current_current.I1 < prev_current.I1 * 0.2)) spike_I = 1;
|
|
if (prev_current.I2 != 0 && (current_current.I2 > prev_current.I2 * 1.8 || current_current.I2 < prev_current.I2 * 0.2)) spike_I = 1;
|
|
if (prev_current.I3 != 0 && (current_current.I3 > prev_current.I3 * 1.8 || current_current.I3 < prev_current.I3 * 0.2)) spike_I = 1;
|
|
|
|
if(spike_I) {
|
|
send_current = prev_current;
|
|
printf("ORNO: Current spike detected. Sending previous values.\n");
|
|
} else {
|
|
send_current = current_current;
|
|
}
|
|
// Always update prev_current to prevent getting stuck on old values
|
|
prev_current = current_current;
|
|
|
|
// POWER check
|
|
int spike_P = 0;
|
|
if (prev_power.P_Tot != 0 && (fabs(current_power.P_Tot - prev_power.P_Tot) > fabs(prev_power.P_Tot) * 0.8)) spike_P = 1;
|
|
|
|
if(spike_P) {
|
|
send_power = prev_power;
|
|
printf("ORNO: Power spike detected. Sending previous values.\n");
|
|
} else {
|
|
send_power = current_power;
|
|
}
|
|
// Always update prev_power to prevent getting stuck on old values
|
|
prev_power = current_power;
|
|
|
|
// ENERGY check (should only increase)
|
|
int spike_W = 0;
|
|
if (prev_energy.W_Tot != 0 && (current_energy.W_Tot > prev_energy.W_Tot * 1.8 || current_energy.W_Tot < prev_energy.W_Tot)) spike_W = 1;
|
|
|
|
if(spike_W) {
|
|
send_energy = prev_energy;
|
|
printf("ORNO: Energy spike/drop detected. Sending previous values.\n");
|
|
} else {
|
|
send_energy = current_energy;
|
|
prev_energy = current_energy;
|
|
}
|
|
|
|
// FREQUENCY check (5% threshold)
|
|
int spike_F = 0;
|
|
if (prev_freq.Freq != 0 && (current_freq.Freq > prev_freq.Freq * 1.05 || current_freq.Freq < prev_freq.Freq * 0.95)) spike_F = 1; // 5% for frequency
|
|
|
|
if(spike_F) {
|
|
send_freq = prev_freq;
|
|
printf("ORNO: Frequency spike detected. Sending previous values.\n");
|
|
} else {
|
|
send_freq = current_freq;
|
|
prev_freq = current_freq;
|
|
}
|
|
|
|
// --- Now send the selected values ---
|
|
|
|
int valid_U = 0, valid_I = 0, valid_P = 0, valid_W = 0, valid_F = 0;
|
|
|
|
printf("ORNO: Voltages: L1=%.1f V, L2=%.1f V, L3=%.1f V\n", send_voltage.U1, send_voltage.U2, send_voltage.U3);
|
|
if (is_valid_float(send_voltage.U1, 150.0, 280.0) && is_valid_float(send_voltage.U2, 150.0, 280.0) && is_valid_float(send_voltage.U3, 150.0, 280.0)) {
|
|
mqtt_send_U(send_voltage.U1, send_voltage.U2, send_voltage.U3);
|
|
valid_U = 1;
|
|
printf("ORNO: MQTT: Published voltages\n");
|
|
} else {
|
|
printf("ORNO: MQTT: Skipping voltages - invalid values\n");
|
|
}
|
|
|
|
printf("ORNO: Currents: L1=%.2f A, L2=%.2f A, L3=%.2f A\n", send_current.I1, send_current.I2, send_current.I3);
|
|
if (is_valid_float(send_current.I1, 0.0, 100.0) && is_valid_float(send_current.I2, 0.0, 100.0) && is_valid_float(send_current.I3, 0.0, 100.0)) {
|
|
mqtt_send_I(send_current.I1, send_current.I2, send_current.I3);
|
|
valid_I = 1;
|
|
printf("ORNO: MQTT: Published currents\n");
|
|
} else {
|
|
printf("ORNO: MQTT: Skipping currents - invalid values\n");
|
|
}
|
|
|
|
printf("ORNO: Power: Total=%.3f W, L1=%.3f W, L2=%.3f W, L3=%.3f W\n", send_power.P_Tot, send_power.P1, send_power.P2, send_power.P3);
|
|
if (is_valid_float(send_power.P_Tot, -25000.0, 25000.0) && is_valid_float(send_power.P1, -10000.0, 10000.0) && is_valid_float(send_power.P2, -10000.0, 10000.0) && is_valid_float(send_power.P3, -10000.0, 10000.0)) {
|
|
mqtt_send_P(send_power.P_Tot, send_power.P1, send_power.P2, send_power.P3);
|
|
valid_P = 1;
|
|
printf("ORNO: MQTT: Published power\n");
|
|
} else {
|
|
printf("ORNO: MQTT: Skipping power - invalid values\n");
|
|
}
|
|
|
|
printf("ORNO: Energy: Total=%.3f kWh, L1=%.3f kWh, L2=%.3f kWh, L3=%.3f kWh\n", send_energy.W_Tot, send_energy.W1, send_energy.W2, send_energy.W3);
|
|
if (is_valid_float(send_energy.W_Tot, 0.0, 1000000.0) && is_valid_float(send_energy.W1, 0.0, 1000000.0) && is_valid_float(send_energy.W2, 0.0, 1000000.0) && is_valid_float(send_energy.W3, 0.0, 1000000.0)) {
|
|
mqtt_send_W(send_energy.W_Tot, send_energy.W1, send_energy.W2, send_energy.W3);
|
|
valid_W = 1;
|
|
printf("ORNO: MQTT: Published energy\n");
|
|
} else {
|
|
printf("ORNO: MQTT: Skipping energy - invalid values\n");
|
|
}
|
|
|
|
printf("ORNO: Frequency: %.4f Hz\n", send_freq.Freq);
|
|
if (is_valid_float(send_freq.Freq, 47.0, 53.0)) {
|
|
mqtt_send_Hz(send_freq.Freq);
|
|
valid_F = 1;
|
|
printf("ORNO: MQTT: Published frequency\n");
|
|
} else {
|
|
printf("ORNO: MQTT: Skipping frequency - invalid value\n");
|
|
}
|
|
|
|
/* Send all ORNO data to InfluxDB in one batch (1 TCP connection instead of 5) */
|
|
influx_send_orno_batch(
|
|
&send_voltage, valid_U, &send_current, valid_I,
|
|
&send_power, valid_P, &send_energy, valid_W,
|
|
&send_freq, valid_F);
|
|
}
|
|
}
|
|
|
|
// Always execute SUN2K reading regardless of fast voltage mode
|
|
if (do_sun2k)
|
|
{
|
|
/* Delay between ORNO and SUN2K as configured */
|
|
if (do_orno) {
|
|
printf("\nSUN2K: Waiting %d ms before SUN2K...\n\n", DELAY_BETWEEN_DEVICES/1000);
|
|
usleep(DELAY_BETWEEN_DEVICES);
|
|
}
|
|
|
|
time_t rawtime;
|
|
struct tm *timeinfo;
|
|
|
|
time(&rawtime);
|
|
timeinfo = localtime(&rawtime);
|
|
printf("SUN2K: === SUN2K Reading Started: %s", asctime(timeinfo));
|
|
//Create a new RTU context with proper serial parameters (in this example,
|
|
//device name /dev/ttyS0, baud rate 9600, no parity bit, 8 data bits, 1 stop bit)
|
|
modbus_t *ctx = modbus_new_rtu(USB_DEV, 9600, 'N', 8, 1);
|
|
if (!ctx)
|
|
{
|
|
fprintf(stderr, "SUN2K: Failed to create the context: %s\n", modbus_strerror(errno));
|
|
exit(1);
|
|
}
|
|
//Set the Modbus address of the remote slave (to 3)
|
|
modbus_set_slave(ctx, SUN2000_SLAVE);
|
|
|
|
if (modbus_connect(ctx) == -1)
|
|
{
|
|
fprintf(stderr, "SUN2K: Unable to connect: %s\n", modbus_strerror(errno));
|
|
modbus_free(ctx);
|
|
exit(1);
|
|
}
|
|
|
|
/* Configure timeouts for SUN2K from #define */
|
|
modbus_rtu_set_rts_delay(ctx, SUN2K_RTS_DELAY);
|
|
modbus_set_response_timeout(ctx, 0, 900000); /* 0.9s */
|
|
modbus_set_byte_timeout(ctx, 0, SUN2K_BYTE_TIMEOUT);
|
|
|
|
/* Display current configuration */
|
|
int rts_delay = modbus_rtu_get_rts_delay(ctx);
|
|
uint32_t response_timeout_sec, response_timeout_usec;
|
|
uint32_t byte_timeout_sec, byte_timeout_usec;
|
|
modbus_get_response_timeout(ctx, &response_timeout_sec, &response_timeout_usec);
|
|
modbus_get_byte_timeout(ctx, &byte_timeout_sec, &byte_timeout_usec);
|
|
printf("SUN2K: RTS Delay %u us\n", rts_delay);
|
|
printf("SUN2K: Response Timeout %u,%06u s\n", response_timeout_sec, response_timeout_usec);
|
|
printf("SUN2K: Byte Timeout %u,%06u s\n", byte_timeout_sec, byte_timeout_usec);
|
|
|
|
/* Flush serial buffer and wait for device to be ready */
|
|
modbus_flush(ctx);
|
|
usleep(100000); /* 100ms delay before first read */
|
|
|
|
/* Enable libmodbus debug to print raw frames */
|
|
/* libmodbus debug disabled for normal runs */
|
|
/* modbus_set_debug(ctx, TRUE); */
|
|
|
|
uint16_t reg[32]; // will store read registers values
|
|
int num = 0;
|
|
int proba = 0;
|
|
char str_buf[32];
|
|
|
|
/* Read SUN2K voltages (U_A, U_B, U_C) in one batch - registers 32069-32071 */
|
|
float sun2k_U_A = 0, sun2k_U_B = 0, sun2k_U_C = 0;
|
|
int sun2k_voltage_ok = 0;
|
|
proba = 0;
|
|
do {
|
|
num = modbus_read_timed(ctx, 32069, 3, reg);
|
|
} while ((num != 3) && (proba++ < 10));
|
|
|
|
if (num == 3) {
|
|
sun2k_U_A = (float)reg[0] / 10.0;
|
|
sun2k_U_B = (float)reg[1] / 10.0;
|
|
sun2k_U_C = (float)reg[2] / 10.0;
|
|
printf("SUN2K: Voltages: U_A=%.1f V, U_B=%.1f V, U_C=%.1f V\n", sun2k_U_A, sun2k_U_B, sun2k_U_C);
|
|
if (is_valid_float(sun2k_U_A, 150.0, 280.0) &&
|
|
is_valid_float(sun2k_U_B, 150.0, 280.0) &&
|
|
is_valid_float(sun2k_U_C, 150.0, 280.0)) {
|
|
mqtt_send_SUN2K_U(sun2k_U_A, sun2k_U_B, sun2k_U_C);
|
|
sun2k_voltage_ok = 1;
|
|
printf("SUN2K: MQTT: Published voltages\n");
|
|
} else {
|
|
printf("SUN2K: MQTT: Skipping voltages - invalid values\n");
|
|
}
|
|
influx_send_SUN2K_U_batch(sun2k_U_A, sun2k_U_B, sun2k_U_C, sun2k_voltage_ok);
|
|
} else {
|
|
printf("SUN2K: Failed to read voltages: %s\n", modbus_strerror(errno));
|
|
}
|
|
usleep(10000); /* 10ms delay before continuing */
|
|
|
|
for (int i = 0; i < sizeof(mbReg) / sizeof(t_mb_reg); i++)
|
|
{
|
|
/* Skip U_A, U_B, U_C - already read and sent in batch */
|
|
if (mbReg[i].ireg == 32069 || mbReg[i].ireg == 32070 || mbReg[i].ireg == 32071) {
|
|
continue;
|
|
}
|
|
|
|
memset(str_buf, 0, sizeof(str_buf));
|
|
proba = 0;
|
|
do
|
|
{
|
|
num = modbus_read_timed(ctx, mbReg[i].ireg, mbReg[i].num_reg, reg);
|
|
} while ((num != mbReg[i].num_reg) && (proba++ < 10));
|
|
|
|
printf("SUN2K: %10i\t", mbReg[i].ireg);
|
|
printf("%u\t", reg[0]);
|
|
if (mbReg[i].num_reg == 2)
|
|
printf("%u\t", reg[1]);
|
|
else
|
|
printf("\t");
|
|
if (num != mbReg[i].num_reg)
|
|
{ // number of read registers is not the one expected
|
|
printf("%20.20s \t", mbReg[i].reg_id);
|
|
printf("SUN2K: Failed to %i read: %s\n", proba, modbus_strerror(errno));
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
printf("SUN2K: %20.20s \t", mbReg[i].reg_id);
|
|
switch (mbReg[i].type)
|
|
{
|
|
case rgStr:
|
|
{
|
|
for (int j = 0; j < mbReg[i].num_reg; j++)
|
|
{
|
|
printf("%c%c", (reg[j] / 256), (reg[j] % 256));
|
|
}
|
|
printf("\n");
|
|
break;
|
|
}
|
|
case rgData:
|
|
{
|
|
time_t td = reg[0] * 65536 + reg[1];
|
|
timeinfo = localtime(&td);
|
|
printf("%s", asctime(timeinfo));
|
|
snprintf(str_buf, sizeof(str_buf), "\"%s\"", asctime(timeinfo));
|
|
str_buf[strlen(str_buf) - 2] = '"';
|
|
str_buf[strlen(str_buf) - 1] = '\0';
|
|
break;
|
|
}
|
|
case rgI32:
|
|
{
|
|
int32_t d = (int32_t)((uint32_t)reg[0] << 16 | reg[1]);
|
|
if (mbReg[i].scale == 1)
|
|
{
|
|
printf("%i", d);
|
|
snprintf(str_buf, sizeof(str_buf), "%i", d);
|
|
}
|
|
else
|
|
{
|
|
double f = d;
|
|
f /= mbReg[i].scale;
|
|
printf("%lf", f);
|
|
snprintf(str_buf, sizeof(str_buf), "%lf", f);
|
|
}
|
|
break;
|
|
}
|
|
case rgI16:
|
|
{
|
|
int16_t d = (int16_t)reg[0];
|
|
if (mbReg[i].scale == 1)
|
|
{
|
|
printf("%i", d);
|
|
snprintf(str_buf, sizeof(str_buf), "%i", d);
|
|
}
|
|
else
|
|
{
|
|
double f = d;
|
|
f /= mbReg[i].scale;
|
|
printf("%lf", f);
|
|
snprintf(str_buf, sizeof(str_buf), "%lf", f);
|
|
}
|
|
break;
|
|
}
|
|
case rgU32:
|
|
{
|
|
uint32_t d = (uint32_t)reg[0] << 16 | reg[1];
|
|
|
|
if (mbReg[i].scale == 1)
|
|
{
|
|
printf("%u", d);
|
|
snprintf(str_buf, sizeof(str_buf), "%u", d);
|
|
}
|
|
else
|
|
{
|
|
double f = d;
|
|
f /= mbReg[i].scale;
|
|
printf("%lf", f);
|
|
snprintf(str_buf, sizeof(str_buf), "%lf", f);
|
|
}
|
|
break;
|
|
}
|
|
case rgU16:
|
|
{
|
|
uint16_t d = reg[0];
|
|
if (mbReg[i].scale == 1)
|
|
{
|
|
printf("%u", d);
|
|
snprintf(str_buf, sizeof(str_buf), "%u", d);
|
|
}
|
|
else
|
|
{
|
|
double f = d;
|
|
f /= mbReg[i].scale;
|
|
printf("%lf", f);
|
|
snprintf(str_buf, sizeof(str_buf), "%lf", f);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
for (int j = 0; j < mbReg[i].num_reg; j++)
|
|
{
|
|
printf("%i:", reg[j]);
|
|
snprintf(str_buf, sizeof(str_buf), "%i", reg[j]);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (mbReg[i].type != rgData)
|
|
printf(" %s\n", mbReg[i].units);
|
|
}
|
|
mqtt_send_SUN2K(&mbReg[i], str_buf);
|
|
influx_send_SUN2K(&mbReg[i], str_buf);
|
|
usleep(10000); /* 10ms delay between queries */
|
|
}
|
|
modbus_close(ctx);
|
|
modbus_free(ctx);
|
|
}
|
|
|
|
if (READ_LOOP) {
|
|
if (time(NULL) < high_frequency_mode_end_time) {
|
|
usleep(10000); // 10ms
|
|
} else {
|
|
// Check if high frequency mode just ended - send notification
|
|
if (high_frequency_mode_active) {
|
|
printf("ORNO: High frequency mode ended after %d seconds.\n", HIGH_FREQ_MODE_DURATION);
|
|
mqtt_send("/energy/orno/fluct", "off");
|
|
influx_send_post("orno,device=orno,highfluct=end highfluct=0");
|
|
high_frequency_mode_active = 0;
|
|
}
|
|
sleep(5); // 5s
|
|
}
|
|
}
|
|
} while (READ_LOOP);
|
|
//mosq_test();
|
|
return 0;
|
|
}
|