Files
modborno3/modb_orno3.c
ms fd8dce7535 Update modb_orno3.c
W trybie zwiększonej fluktuacji przyspieszamy wyłącznie odczyt napięc.
Poprawka wygenerowana przez qwen3_coder
2026-02-17 13:12:52 +01:00

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(&reg[0]);
voltage->U2 = modbus_get_float_abcd(&reg[2]);
voltage->U3 = modbus_get_float_abcd(&reg[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(&reg[0]);
current_voltage.U2 = modbus_get_float_abcd(&reg[2]);
current_voltage.U3 = modbus_get_float_abcd(&reg[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(&reg[0]);
current_current.I2 = modbus_get_float_abcd(&reg[2]);
current_current.I3 = modbus_get_float_abcd(&reg[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(&reg[0]);
current_power.P1 = modbus_get_float_abcd(&reg[2]);
current_power.P2 = modbus_get_float_abcd(&reg[4]);
current_power.P3 = modbus_get_float_abcd(&reg[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(&reg[0]);
current_energy.W1 = modbus_get_float_abcd(&reg[2]);
current_energy.W2 = modbus_get_float_abcd(&reg[4]);
current_energy.W3 = modbus_get_float_abcd(&reg[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(&reg[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;
}